NaNoGenMo / 2023

National Novel Generation Month, 2023 edition.
28 stars 2 forks source link

You are now participating in the cultural event of NaNoGenMo #20

Open ikarth opened 1 year ago

ikarth commented 1 year ago

It's the tenth NaNoGenMo, and I'm finally free of academic writing obligations. Which doesn't mean I have a lot of time to work on a project, but at least I've got an idea.

I think the key to a NaNoGenMo project is having an idea to structure the project around. So the idea I'm going for this time is inspired by my interpretation of the Oulipo idea of the clinamen: the automated clockwork needs the unpredictable swerve (with the term stolen from Lucretius). I can code a clockwork automaton to produce a story of some sort, but for it to be interesting to me I need it to be unpredictable enough to surprise me.

Emergent systems can be unpredictable in the right way, so I wanted to build an emergent system that would be the core of the unpredictability in the narrative.

Blaseball (RIP) fits that description pretty well. A sports simulation is inherently unpredictable, in that you don't know who will win or even what will happen in the middle of the game. That gives me something to hang the rest of the narrative on. My impression is that sports narratives are slightly more common in film than in novels, but there are a few literary examples. My thinking is that it'll be more interesting to incorporate drama both on and off the playing field, but first I need the game.

I thought about basing it on baseball, on the grounds that I know enough about baseball to write a convincing simulation. But my friend convinced me that hockey would be a more interesting choice, on the grounds that: 1. Blaseball already did baseball. 2. It's probably better if the simulation isn't concerned about accuracy because that's actually kind of a distraction from the novel-generation point. 3. It would be amusing to have the team rosters gradually attrition over the course of the game through violence. Violence is cheap drama, and it's a lot easier to have interesting stories if the novel's characters have some obvious ways to express their passions.

So what we have here is a physics-based simulation of something that might look like hockey if you squinted at it through the wrong end of a poorly-made telescope. In other words, a perfect simulation for our purposes.

[
    "The game between TeamOne and TeamTwo begins.",
    "player_5 hits the puck_1!",
    "player_3 hits the puck_1!",
    "Goal scored in TeamOne's goal by player_3 !",
    "The score is now TeamOne 1 to TeamTwo 0",
    "Puck returned to the center point",
    "player_3 hits the puck_1!",
    "player_7 hits the puck_1!",
    "player_7 hits the puck_1!",
    "player_5 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_2 hits the puck_1!",
    "player_7 hits the puck_1!",
    "player_8 hits the puck_1!",
    "player_1 hits the puck_1!",
    "player_5 hits the puck_1!",
    "player_3 hits the puck_1!",
    "Goal scored in TeamTwo's goal by player_3 !",
    "The score is now TeamOne 1 to TeamTwo 1",
    "Puck returned to the center point",
    "player_7 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_1 hits the puck_1!",
    "Goal scored in TeamTwo's goal by player_1 !",
    "The score is now TeamOne 1 to TeamTwo 2",
    "Puck returned to the center point",
    "player_5 hits the puck_1!",
    "player_6 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_1 hits the puck_1!",
    "player_2 hits the puck_1!",
    "player_3 hits the puck_1!",
    "End of period 1, score is TeamOne 1, TeamTwo 2",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_3 went out of bounds!",
    "player_3 returned to the rink!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_8 hits the puck_1!",
    "player_6 hits the puck_1!",
    "player_2 hits the puck_1!",
    "player_8 hits the puck_1!",
    "player_6 hits the puck_1!",
    "player_6 hits the puck_1!",
    "Goal scored in TeamTwo's goal by player_6 !",
    "The score is now TeamOne 1 to TeamTwo 3",
    "Puck returned to the center point",
    "player_7 hits the puck_1!",
    "player_7 hits the puck_1!",
    "Goal scored in TeamOne's goal by player_7 !",
    "The score is now TeamOne 2 to TeamTwo 3",
    "Puck returned to the center point",
    "player_8 hits the puck_1!",
    "player_2 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_6 hits the puck_1!",
    "player_3 went out of bounds!",
    "player_3 returned to the rink!",
    "End of period 2, score is TeamOne 2, TeamTwo 3",
    "player_3 hits the puck_1!",
    "player_1 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_8 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_7 hits the puck_1!",
    "player_5 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_8 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_8 hits the puck_1!",
    "player_8 hits the puck_1!",
    "player_8 hits the puck_1!",
    "player_3 went out of bounds!",
    "player_3 returned to the rink!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_4 hits the puck_1!",
    "player_4 hits the puck_1!",
    "player_1 hits the puck_1!",
    "player_8 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_5 hits the puck_1!",
    "player_5 hits the puck_1!",
    "player_7 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_8 hits the puck_1!",
    "player_3 hits the puck_1!",
    "player_3 hits the puck_1!",
    "End of period 3, score is TeamOne 2, TeamTwo 3",
    "Game over. Final score, TeamOne 2, TeamTwo 3."
]

Now, all of this needs to be turned into an actual novel with characters and scenes and so forth. Or at least a couple of colorful announcers. But one step at a time.

ikarth commented 1 year ago

chrome_BwAdCkvXo8 Of course, my secret motivation is that I think it's funny to make a novel generator that looks like this.

kellyegan commented 1 year ago

Cool idea! I worked on a similar idea for last year using the card game "War" a much simulation for sure. Can't wait to see how yours turns out!

ikarth commented 1 year ago

Your "War" was one of the inspirations! Along with things like Hannah and The Twelve-Disk Tower of Hanoi and the National Opera Generation Month project that turned a basketball game into an opera.

greg-kennedy commented 1 year ago

go mints

ikarth commented 1 year ago

Game 1: Kalamazoo Koalas vs. Saskatoon Steelmen

Welcome to Kalamazoo Koalas vs. Saskatoon Steelmen.

That's a steal as Kate Bones takes control of the puck from the Saskatoon Steelmen!

Kate Bones steals the puck from Shawn Wolfenstein of the Saskatoon Steelmen!

A pass to Jacob Pattern

Jacob Pattern steals the puck from Shawn Wolfenstein!

None of you are free of sin.

Kate Bones does the give-and-go, Jacob Pattern picks it up.

Jacob Pattern steals the puck from Shawn Wolfenstein!

Shawn Montgomery steals the puck from the Kalamazoo Koalas!

That's a steal as Shawn Montgomery takes control of the puck from the Kalamazoo Koalas!

Shawn Montgomery steals the puck from Kate Bones! The Saskatoon Steelmen have the puck!

Shawn Montgomery steals the puck from Kate Bones; now the Saskatoon Steelmen are on the offense!

A pass to Shawn Montgomery

Shawn Montgomery steals the puck from Jacob Pattern of the Kalamazoo Koalas!

That's a steal as Shawn Montgomery takes control of the puck!

Shawn Montgomery steals the puck from the Kalamazoo Koalas!

Shawn Montgomery steals the puck from Kate Bones! The Saskatoon Steelmen have the puck!

Keeping the momentum going, Shawn Wolfenstein goes tape to tape to Shawn Montgomery.

And now for a word from our sponsor: The National Hlockey League.

Yeesh. That's gonna leave a mark.

Shawn Wolfenstein steals the puck from Jacob Pattern!

Shawn Wolfenstein steals the puck from the Kalamazoo Koalas!

Shawn Wolfenstein steals the puck from Kate Bones; now the Saskatoon Steelmen are on the offense!

That's a steal as Shawn Wolfenstein takes control of the puck!

Looks like it'll be a long game for Saskatoon Steelmen.

Kate Bones steals the puck from the Saskatoon Steelmen!

That's a steal as Kate Bones takes control of the puck!

Kate Bones steals the puck from Shawn Montgomery of the Saskatoon Steelmen!

Jacob Pattern makes a pass to Kate Bones.

That was exceptional.

Kate Bones does the give-and-go to Kate Bones.

That's a steal as Kate Bones takes control of the puck!

That's a steal as Jason Kreminski takes control of the puck!

Jason Kreminski steals the puck from Shawn Montgomery!

Jason Kreminski steals the puck from Shawn Montgomery!

And now for a word from our sponsor: The Association for the Awareness of Seasonal Aggression Disorder.

Yeesh. That looked painful.

Kate Bones sends the puck laterally. Picked up by Jason Kreminski.

Jason Kreminski steals the puck from the Saskatoon Steelmen!

Shawn Wolfenstein steals the puck from Jason Kreminski!

Shawn Wolfenstein steals the puck from the Kalamazoo Koalas!

I can hardly keep up!

The Saskatoon Steelmen are keeping the pressure on!

Shawn Wolfenstein steals the puck from Jacob Pattern of the Kalamazoo Koalas!

That's a steal as Shawn Wolfenstein takes control of the puck!

Shawn Wolfenstein steals the puck from the Kalamazoo Koalas!

Shawn Wolfenstein steals the puck from Kate Bones! The Saskatoon Steelmen have the puck!

And now for a word from our sponsor: The Toronto Dentists Guild.

Jacob Pattern steals the puck from the Saskatoon Steelmen!

Jacob Pattern steals the puck from Shawn Wolfenstein! The Kalamazoo Koalas have the puck!

Jacob Pattern steals the puck from Shawn Montgomery; now the Kalamazoo Koalas are on the offense!

Jacob Pattern steals the puck from Shawn Montgomery of the Saskatoon Steelmen!

Let's see if that works out for them!

Oof! That looked painful.

Jacob Pattern steals the puck from Shawn Wolfenstein of the Saskatoon Steelmen!

Jacob Pattern hits the puck, but the puck goes in the other team's net! A point for the Saskatoon Steelmen!

Current score, Kalamazoo Koalas 0, Saskatoon Steelmen 1.

Stella Phoneme steals the puck from Kate Bones; now the Saskatoon Steelmen are on the offense!

That's a steal as Shawn Montgomery takes control of the puck!

Saskatoon Steelmen is on the move

Youch! That's gonna leave a mark.

That's a steal as Tammy Lancaster takes control of the puck!

A pass by Tammy Lancaster

Saskatoon Steelmen is on the move

Youch. Looks like that hurt.

Shawn Montgomery steals the puck from the Kalamazoo Koalas!

Good pass from Shawn Montgomery to Shawn Montgomery.

A pass to Shawn Montgomery

Saskatoon Steelmen is on the move

The Saskatoon Steelmen are keeping them off balance!

That's a steal as Shawn Montgomery takes control of the puck!

A pass to Shawn Montgomery

They're on their game tonight!

Shawn Montgomery steals the puck from Kate Bones!

Shawn Montgomery makes a filthy pass.

Shawn Montgomery does the give-and-go.

You saw it here, folks!

Stella Phoneme passes to Jacan Montgomery.

That's a steal as Jacan Montgomery takes control of the puck from the Kalamazoo Koalas!

The Saskatoon Steelmen's Jacan Montgomery passes to Shawn Wolfenstein.

You don't see that every day!

The Saskatoon Steelmen's Shawn Montgomery makes a pass to Shawn Wolfenstein.

The Saskatoon Steelmen are keeping them off balance.

Shawn Wolfenstein steals the puck from the Kalamazoo Koalas!

And now for a word from our sponsor: The Birds. This is for'em.

I can hardly keep up!

A pass to Tammy Lancaster

You don't see that in videogames!

And now for a word from our sponsor: It's Alex's birthday! Happy birthday Alex!

Tammy Lancaster steals the puck from the Kalamazoo Koalas!

Jason Kreminski steals the puck from the Saskatoon Steelmen!

Jason Kreminski steals the puck from Shawn Wolfenstein! The Kalamazoo Koalas have the puck!

Jason Kreminski steals the puck from Jacan Montgomery! The Kalamazoo Koalas have the puck!

That's a steal as Jason Kreminski takes control of the puck from the Saskatoon Steelmen!

Jason Kreminski steals the puck from Shawn Montgomery; now the Kalamazoo Koalas are on the offense!

Jason Kreminski steals the puck from Shawn Montgomery! The Kalamazoo Koalas have the puck!

Jason Kreminski steals the puck from Tammy Lancaster!

Jason Kreminski steals the puck from Shawn Montgomery of the Saskatoon Steelmen!

Jason Kreminski steals the puck from Stella Phoneme! The Kalamazoo Koalas have the puck!

And now for a word from our sponsor: The Maple Syrup Distillers Association: We Stab Trees.

Stella Phoneme steals the puck from Jason Kreminski of the Kalamazoo Koalas!

The Saskatoon Steelmen's Shawn Wolfenstein makes a clean pass to Stella Phoneme.

And now for a word from our sponsor: It's Alex's birthday! Happy birthday Alex!

And now for a word from our sponsor: The Film Board of Canada.

Shawn Montgomery goes tape to tape to Stella Phoneme.

Let's see if that works out for them!

Saskatoon Steelmen is on the move

That's a steal as Stella Phoneme takes control of the puck from the Kalamazoo Koalas!

And that's the end of this period; the score is Kalamazoo Koalas 0, Saskatoon Steelmen 1.

Game over. Final score, Kalamazoo Koalas 0, Saskatoon Steelmen 1

ikarth commented 1 year ago

It's always a good idea to spend two weeks on international travel in the middle of a project with a deadline!

In actual progress, I've hooked up a winnow implementation of story sifting, so it can search for patterns in the events and turn them into commentary.

There's also actual teams, player rosters, and a tournament structure, though that isn't surfaced in the commentary as much as it could be.

ikarth commented 1 year ago

There's also a bunch more names and additional commentary, thanks to Max Kreminski's suggestions/contributions/sins.

ikarth commented 1 year ago

Completed novel (62,565 words): https://ikarth.github.io/pasttime/novel.html Generate your own: https://ikarth.github.io/pasttime/

ikarth commented 1 year ago

OK, now that I've recovered from all that, how does the thing work? And how close did I get to my original idea?

I knew going in that I'd effectively only have two weeks (at the start and end of the month) and that the original idea would probably be scoped down pretty heavily. Project management tip: when you're exhausted at the end of the month, killing your darlings becomes a lot easier. (Note: do not apply this project management strategy to literal children.) What I ended up with has a lot of room to be built on into something slightly more interesting, or at least more complicated. Having done NaNoGenMo before, I wasn't surprised by this.

The basic simulation (implemented in Phaser) turned out to be a really solid foundation: in theory the players have no idea how to play effectively: their AI is pretty simple and the physics are deliberately tuned so that they end up gliding all over the place. In practice, the different player attributes and traits affected their performance enough that the result was still entertaining.

The game reports the events that happen. This is mostly by checking the collision logics; my design was deliberately leaning on the academic analysis of how videogames operate, albeit in a relatively obvious way. But the basic theory was that collision logics could be translated into something that communicated with the narrative system.

The recording angel of history keeps a list of all of the events, and runs story sifting patterns on them. Extracting narratives from text has been a long-running AI goal; story sifting (or "story recognition") is specifically "the selection of events that constitute a compelling story from a larger chronicle." Some previous discussion of story sifting is in James Ryan's dissertation with Sheldon/Talk of the Town/Bad News, Max Kreminski's Felt and Winnow, Caves of Qud's backstory generation, and Shi Johnson-Bey's Centrifuge.

I probably should have used Felt to do the story sifting, but I went with the slightly more complicated Winnow because my original intent was to generate the commentary as the games were being played. Real-time commentary was cut due to time, so the generator's implementation is more complicated than is actually justified. For some NaNoGenMo novels, the extra complication is the point, but in this case it was just that I ran out of time.

Sadly, the sifting patterns that are currently implemented are relatively simple. While the system is perfectly capable of finding more complicated relationship between events, the ones that are implemented are relatively straightforward. Though the ability to bind data from the event database to variables for the commentator grammar was very useful. The simplest next step would be to add the player information to the database and bind it to the queries as well, though that's still a relatively trivial use case.

Something that did get implemented was the violence, which was added relatively late in the project. When the players collide hard enough, their collision turns violent. It took some tuning to find the exact right number of teeth that should be lost.

The sifted patterns get sent to the commentator, which turns them in to a Tracery grammar and handles them slightly differently based on which pattern was invoked. The grammar has a bunch of variables that were filled in from information in the database, making it a flexible way to report what happened.

The story really started coming alive when the players got names. (I want to thank Max Kreminski for supplying many of the player names and collaborating on the team names.)

I ended up making the tournament structure a 3-elimination knockout. At the last minute, the novel was still far too short, so I did the time-honored sports league move and added a bunch of expansion teams. A more complicated league structure with teams being regulated between different leagues would be dramatically interesting, but that'd make the data structure and reporting way more complicated and the existing tournament manages to be fairly dramatic on its own.

Blaseball was a pretty good demonstration that the basic structure of a sport in motion is a compelling dramatic experience. It's a bit better live, of course, but I like to think that the novel turned out pretty well. You certainly won't know the ending until you get there.

It's not quite at the scope I was envisioning, but that's true of all my NaNoGenMo novel generators. It's a complete dramatic narrative (if a little dry) with uncertainty about how it will end. I've got lots of ideas for building on top of it, but NaNoGenMo being time limited gives me an excuse to take a break from it for a while.

ikarth commented 1 year ago

Technically you can infer the repository location from the novel's url, but for completeness' sake, the source code is at https://github.com/ikarth/pasttime

greg-kennedy commented 11 months ago

Can I request a slider to slow down the generation? I'd like to watch the games play out but it's so fast that I can't tell what is happening.