NaNoGenMo / 2016

National Novel Generation Month, 2016 edition.
https://nanogenmo.github.io
162 stars 7 forks source link

Tracery Nested Story #128

Open dranorter opened 8 years ago

dranorter commented 8 years ago

I might keep improving this a bit, but for now I'm calling it good.

Repository: here PDF: here

Some example text:

An old erotic lumber firefighter told Professor Ap Fire a story. ’‘Turn your ear,” she said to Professor Ap Fire, “to this traditional story.” Once upon a time, Edge-case Quiaw Denovich the aspiring criminal lived in an offensive courtyard. Edge-case Quiaw Denovich loved the courtyard but life there was boring. So she arranged a trip abroad. Edge-case Quiaw Denovich passed by a crab. Edge-case Quiaw Denovich encountered a waterfall. Edge-case Quiaw Denovich encountered a dark cave. Edge-case Quiaw Denovich happened upon a courtyard. A skilled applied firefighter lived in the courtyard. “I am just a first wright”, said Professor Ap Fire, “and you are a learnd erotic lumber firefighter. I will remember your story”. Professor Ap Fire began to wonder if there might be any way to make a stew without a tastylizard. But no solution occurred to her. While looking for tastylizard, Professor Ap Fire amused herself feeding gryphons. There was a quaint castle along the way, and Professor Ap Fire stopped for the night. The castle was beset by a gaggle of mad erotic seller. Professor Ap Fire fled forthwith. erotic seller are no joke, and mad erotic seller all the worse. A gaggle of them would surely mean an end to this story. There was a larger castle nearby. The castle was beset by a gaggle of mad dogseller. Professor Ap Fire fled forthwith. dogseller are no joke, and mad dogseller all the worse. A gaggle of them would surely mean an end to this story. Professor Ap Fire passed by a jackalope. A third castle was visible in the distance. It looked nothing but inviting, so into the distance Professor Ap Fire went. A group of world-spy was terrorizing the inhabitants of the castle. Professor Ap Fire resolved to find a strong fearless leader who could bring peace. An old street captain told Professor Ap Fire a story. ’‘Turn your ear,” he said to Professor Ap Fire, “to this enchanting saga.” A few years ago, King Dr. Stan Iowla the professional scorpion set out for adventure. King Dr. Stan Iowla accidentally went into a party. Within the party, King Dr. Stan Iowla accidentally went into a church. A thoughtful carthero lived in the church. King Dr. Stan Iowla found nothing further of interest in the clever party. Wemk IV found nothing further of interest in the learnd bunker. Professor Ap Fire thanked the street captain for the warning and returned home. Finally, Professor Ap Fire tracked down the tastylizard for Professor Jim Denovich.

Original post below.

The Tracery tutorial concludes with an example where one story is nested into another ad infinitum. I really liked that example, so I played around with it for a while, and then suddenly remembered that NaNoGenMo existed. That was on November 24th. I'm creating this issue now because I realized I want somewhere to post progress reports.

So, I started with GalaxyKate's code (MIT license btw):

{
    "name": ["Arjun","Yuuma","Darcy","Mia","Chiaki","Izzi","Azra","Lina"]
,   "occupationBase": ["wizard","witch","detective","ballerina","criminal","pirate","lumberjack","spy","doctor","scientist","captain","priest"]
,   "occupationMod": ["occult ","space ","professional ","gentleman ","erotic ","time ","cyber","paleo","techno","super"]
,   "strange": ["mysterious","portentous","enchanting","strange","eerie"]
,   "tale": ["story","saga","tale","legend"]
,   "occupation": ["#occupationMod##occupationBase#"]
,   "setPronouns": ["[heroThey:they][heroThem:them][heroTheir:their][heroTheirs:theirs]","[heroThey:she][heroThem:her][heroTheir:her][heroTheirs:hers]","[heroThey:he][heroThem:him][heroTheir:his][heroTheirs:his]"]
,   "setSailForAdventure": ["set sail for adventure","left #heroTheir# home","set out for adventure","went to seek #heroTheir# forture"]
,   "setCharacter": ["[#setPronouns#][hero:#name#][heroJob:#occupation#]"]
,   "openBook": ["An old #occupation# told #hero# a story. 'Listen well' she said to #hero#, 'to this #strange# #tale#. ' #origin#'","#hero# went home.","#hero# found an ancient book and opened it.  As #hero# read, the book told #strange.a# #tale#: #origin#"]
,   "story": ["#hero# the #heroJob# #setSailForAdventure#. #openBook#"]
,   "origin": ["Once upon a time, #[#setCharacter#]story#"]
}

What this does is start a bunch of stories, but it only actually puts an ending on one of them. And I immediately ran into a problem when I tried to fix that; I had to rename the symbol "hero" (to "hero2") each time I called a new story, so that I could avoid the symbol being overwritten or even deleted entirely.

With that taken care of, I added little event templates and a second type of recursion: the hero finds themselves travelling through various locations throughout the story, and nested locations within locations. I also want to add in fetch quests within fetch quests, so I've added a goal that the narrative makes reference to.

At this point, though, I seem to be running into a problem with narrative length. In principle, the things I've been doing make the output story longer. But it seems to me that everything I've added since the nested locations has actually made them shorter. How could this be? Basically, as layers of recursion get deeper, it gets less and less likely that a story will end, so that my stories are either 30 to 100 words, or they take off to hundreds of thousands of words. But I never see the long ones, because Tracery crashes before it shows them to me.

They must be really long though, because I've switched over to Node.js and increased the stack size until I start getting segfaults, and I still get basically the same ratio of short ones to long ones. I guess my expected length might be infinite; or it might be incoherent to even talk about expected length, since it could be that given enough resources, the current grammar has a nonzero chance of not halting at all.

So clearly I've gotta reduce the amount of recursion a bit. But for now, here's the longest sample text I could get out of it.


Word count: 776 
When the world was still young, Hilodpliep IV the metavampire had found refuge in a memorable series of twisting passageways. Hilodpliep IV felt safe at the series of twisting passageways but living there made ver ill. So ve had no choice but to leave ver home. Hilodpliep IV passed by a sphinx. Hilodpliep IV encountered a dark cave. Hilodpliep IV encountered a mad author along the way. The mad author demanded that Hilodpliep IV tell a story. "Turn your ear," replied the thoughtful metavampire, and began. 

A long, long time ago, Edge-case Circe the space captain left their home. Edge-case Circe accidentally went into an arcology. Soon Edge-case Circe passed through the arcology and moved on. Edge-case Circe accidentally went into a cavern. Edge-case Circe found nothing of interest in the ancient cavern. Edge-case Circe eventually went home.

The chastened mad author thanked Hilodpliep IV and left. Because of this, a second mad author ignored Hilodpliep IV. After this, a third mad author stood in Hilodpliep IV's way, but Hilodpliep IV found another path, through a cavern. Within the cavern, Hilodpliep IV found verself within a series of twisting passageways. Within the series of twisting passageways, Hilodpliep IV accidentally went into a cavern. Because of this, Hilodpliep IV passed through the cavern and moved on. Within the series of twisting passageways, Hilodpliep IV happened upon a baffling dome. Hilodpliep IV found nothing of interest in the boring baffling dome. Hilodpliep IV found nothing of interest in the blasphemous series of twisting passageways. Hilodpliep IV found nothing of interest in the ancient cavern.. In due course of time, Hilodpliep IV encountered an indignant applied scientist living in the wilderness. Hilodpliep IV thought ve might find somewhere livable at a cavern. Later that day, Hilodpliep IV passed through the cavern and moved on. An old aspiring witch told Hilodpliep IV a story. "Hey! You'd better listen," she said to Hilodpliep IV, "to this enchanting legend.'

Once upon a time, Circe the scorpion set sail for adventure. Circe passed by an indignant superwright living in the wilderness. Circe thought ve might find excitement at a series of twisting passageways. Then Circe passed through the series of twisting passageways and moved on. Then, Circe passed by a waterfall. Circe passed by an eerie sunrise. Circe happened upon a cavern. Within the cavern, Circe happened upon a cavern. Circe found nothing of interest in the ancient cavern. Then Circe passed through the cavern and moved on. Circe encountered a salesman along the way. The salesman stood in Circe's way, but Circe found another path, through an arcology. Circe found nothing of interest in the offensive arcology.. Then, a second salesman gave Circe a tasteful gift. In due course of time, a third salesman stood in Circe's way, but Circe found another path, through a cavern. Immediately, Circe passed through the cavern and moved on.. Circe still wanted  excitement. Circe happened upon a unique dome. Soon Circe passed through the unique dome and moved on. An old professional captain told Circe a story. "Rest a while, and you can listen," she said to Circe, "to this memorable saga.'

A long, long time ago, Sclucropne Esquire the mad wright was convinced by a theater wright to travel the world. Sclucropne Esquire thought he might find novelty at a village. Within the village, Sclucropne Esquire accidentally went into an arcology. Within the arcology, Sclucropne Esquire happened upon a specific location. Later, Sclucropne Esquire passed through the specific location and moved on. That day, Sclucropne Esquire passed through the arcology and moved on. Sclucropne Esquire found nothing of interest in the ancient village. Sclucropne Esquire thought he might find good times at an arcology. Sclucropne Esquire found nothing of interest in the ponderous arcology. Next, Sclucropne Esquire went home.

But Circe did not listen, and continued on. Circe happened upon a unique dome. Within the unique dome, Circe accidentally went into a village. Within the village, Circe found verself within an arcology. That day, Circe passed through the arcology and moved on. Within the village, Circe accidentally went into a series of twisting passageways. Circe found nothing of interest in the evil series of twisting passageways. Circe found nothing of interest in the eldritch village. That day, Circe passed through the unique dome and moved on. Tiring of travel, Circe settled down. 

"I am just a metavampire', said Hilodpliep IV, "and you are a very old aspiring witch. I will think about what you have said'. Hilodpliep IV encountered a raven. Hilodpliep IV thought ve might find a new home at a village. Hilodpliep IV found nothing of interest in the ponderous village. Hilodpliep IV arrived home later that day.
dranorter commented 8 years ago

Well, I'm regretting my late start! One day to go. Side quests within side quests were harder than I anticipated because there's so much context to keep from colliding; I've got stories as a whole, locations, goals, and quest-givers characters, and situations where those change individually or together. (Well, new nested stories are wholly independent, for now.) All this would be easy except for the bug which necessitates I rename them. I admit I could have avoided a lot of debugging time if I stuck to renaming conventions instead of leaving symbols with their original name when I thought it would work.

Anyway, I've got everything I originally imagined; the only nice feature I've left out is generating names and other background details for the locations the hero visits. I even have the thing creating a PDF. At present, though, my longest run got me just sixteen thousand words! And they were a pretty repetitive sixteen thousand. I've got all the nested structure I want but little content. So my last day will be spent expanding the number of possibilities and the average run length.

I realize I'm not breaking any new ground with this project, but here are the tricks I've used since my last update.

The overall shortness of my story output is no longer due to a selection effect. My main source of near-infinite recursion was the nested stories themselves, and the nested storied don't share any information with the story they're nested in, so I've simply replaced recursive story calls with "STORYGOESHERE". I then generate a bunch of stories and cobble them together, filling the holes until I get something complete. (I have a script that tries to piece together the longest complete story it can from the incomplete ones.)

To get around some bugs and tough spots, I'm running Tracery twice. The first run leaves behind some tags that are enclosed in %percent signs% instead of #number signs#. I replace the percent signs with number signs and then do the second run.

Besides that, designing story elements in Tracery has been pretty fun, and I feel like I learned some useful tricks, or design patterns even. I'd certainly like to try a purely grammar-based approach again and put things together more logically. I based a lot of things around "random event" symbols which keep getting called again and again to keep the story going, but I ended up with several separate event lists; the events which can occur any time, the one which happen inside (nested) locations, and the ones which happen during side-quests (which always involve locations). There's not any sharing between these; an "any time" type event doesn't come up during a quest, even though it should be able to. This is partly because one of the "any time" events is the story itself coming to an end, which shouldn't happen during a quest. So, I'm happy with a lot of what I did, but not with the random encounters. Who knows, maybe I'll have time to rearrange them before the month is over. (But I'd be better off just writing more of them.)

dranorter commented 7 years ago

Well, I might make some more changes but I have a final product for now.

dranorter commented 7 years ago

Oh dear! The version I posted as final had some major problems. The book renamed the hero mid-journey, and seemed to be missing sections. I can't seem to figure out exactly what's wrong so I've replaced the final PDF with a PDF from 24 hours ago. There's a lot of little pieces of story content missing but the overall effect is more what I was aiming for.

dranorter commented 7 years ago

A much better, more complete version has been uploaded, including both the pdf and code.

MichaelPaulukonis commented 7 years ago

The book renamed the hero mid-journey

https://cloud.githubusercontent.com/assets/2607898/21650553/449c7dd2-d273-11e6-91c7-22b0e80bc6d5.png

dranorter commented 7 years ago

(For those reading in the vast and inconsolate future, MichaelPaulukonis linked to an image which reads "honor your mistake as a hidden intention -Brian Eno".)

I was definitely tempted to do that at points.

TheBunyip commented 6 years ago

I think the issue you have encountered is basically that current symbol evaluations are not part of Tracery's stack state (or else the current symbol states are not correctly popped off the stack when a level in the recursion ends).