NaNoGenMo / 2019

National Novel Generation Month, 2019 edition.
98 stars 5 forks source link

Present-day Hero's Quest #29

Open verachell opened 4 years ago

verachell commented 4 years ago

I'm going for a Hero's Quest plot set in the present day. The main character has to find an item.

I'm aiming for a forward sense of narrative. As stretch goals, I aim to include simile and/or other things like imagery, plus have the character deliver the item to a specific place or location, but I'm not sure if I can get any of that done in time.

In lieu of a dev diary, I'm having my project boards be publicly viewable, see https://github.com/users/verachell/projects/3

I'm programming it in Common Lisp.

LuRsT commented 4 years ago

Wow, this sounds good, looking forward to what you come up with!

verachell commented 4 years ago

I have finished my NaNoGenMo project.

Code: https://github.com/verachell/Hero-quest-NaNoGenMo-2019

Novels:

This program generates a "novel" of greater than 50,000 words in text format. The novel has a Hero's Quest plot where the main character has the goal of finding an item.

The user is prompted for desired filename for output. The program requires no other information from the user. The choice of words used in the novel has an element of randomness, so subsequent runs of the program will result in different novels.

Credits: Many of the phrases used are taken from a Project Gutenberg book: Fifteen Thousand Useful Phrases, by Grenville Kleiser. Fifth Edition, Funk & Wagnalls Company, New York and London, 1919.

Additional phrases were necessary for the plot besides those from the book above, therefore I created additional phrases on my own. To ensure correct attribution, phrases from the book mentioned above by Grenville Kleiser are in files whose name begins with "GUTENBERG". Phrases I made up are in files whose name begins with "VERACHELL", or are hard-coded into the program.

Approach

This program uses a straightforward approach. Sentences are generated by string concatenation of phrases and words. There is an element of randomness to the selection of many of the phrases and words.

Certain types or topics of sentences are generated to provide a sense of narrative.

If I get time later, I'd like to mention a little more info about the sentence generation in a separate comment on this thread.

Features:

Sample output:

Beginning

The search for the platinum fountain pen

CHAPTER 1

My quest started with abject submission and ended with broadening fame.

I desired above all to discover the enemy's plans. I resolved to stick with my mission until I had achieved my goal. The good news was that by finding the platinum fountain pen, I would discover the enemy's plans. There was one catch though - I had to deliver the item to the newest university.

Sick, ashamed, and disillusioned, I commenced my hunt. I perceived meteors that dart like screaming birds.

CHAPTER 2

The hour was upon me to beguile into reading the luxuriance of expression.

...

Middle

...

Although hard to discern, I could have sworn that I saw screaming birds.

Exquisitely stung by the thought, armed all over with subtle antagonisms; these things I contemplated as I walked.

To improve my mood, I thought about one of my favorite things: gardening.

A woman strode in my direction. She appeared crisp, emphatic, and powerful.

"I feel very grateful to you for your kind offer," she remarked.

"I am seeking a platinum fountain pen," I mentioned.

"It is often very misleading," she mentioned.

"And yet the explanation does not wholly satisfy me," I said.

"I have not seen the item you are seeking myself, but my friend saw one in the area of indecision," she said.

The area of indecision was mentioned. Intriguing. I thanked her and we each continued our separate journeys. I racked my brains. What on earth could the area of indecision be?

With abject submission, I began walking south. Darkened by shadows, things began to carry into conflict. Confirmed by habit, I continued walking.

I had a sudden realization. The area of indecision ought to equate to the wine shop. Moments of utter idleness and insipidity, I moved toward my left. Finally I arrived without mishap other than strange frankness of cynical brutality. Suddenly, I felt abandoned. Finding the platinum fountain pen was one of the main things keeping me going at this point. I probed in the wine shop for quite some time. Reluctantly, I had to admit that I just wasn't able to find the platinum fountain pen.

...

While contemplating the current situation, I absentmindedly ran my fingers through my hair. The semblance of abject submission was constantly around. Measured by years, I was wasteful, indolent, and evasive.

...

An audacious challenge of ridicule, formless verbosity and a passionate rhetoric; these things I contemplated as I walked. Should I be more careful and discreet about my search in case anyone was watching?

...

With broadening fame, I began walking forward. Enriched by gifts, things began to enter into controversy. Bound by opinion, I continued walking.

...

End

...

All of a sudden, I knew what to do. The expanse of thankfulness had to be the cafe. All was a vague jumble of chaotic impressions, I strode north. Finally I arrived without mishap other than keen power of calculation and unhesitating audacity. Feeling stressed but not knowing why, I carried on. I was eager to find the platinum fountain pen. I poked around inside the cafe for quite some time. My heart leapt. I found a fountain pen! And it was indeed platinum.

The time had come to deliver the fountain pen to the newest university. If anyone was tracking me, I did not see them. The stakes were too high to fail to discover the enemy's plans. I knew where I needed to go. First I hiked forward and went for about twenty-five minutes in that direction, then went forward. Finally I arrived at the correct place. I was thrilled! I handed off the platinum fountain pen with a sense of broadening fame. Thankfully, I could now discover the enemy's plans.

THE END

Highlights:

...

Now was the moment to lull into indifference the jargon of philosophy.

...

Finally I arrived without mishap other than violent and unforeseen vicissitudes.

...

Stung by derision, things began to relapse into savagery.

...

A woman came toward me. She appeared gradual, cautious, and well-reasoned.

"I am seeking a platinum fountain pen," I mused.

"It is one of the grave problems of the day," she insisted.

"I think you have great appreciation of values," I said.

"So far so good," she said.

"I am more grieved than I can tell you," I mused.

"Go to the pinnacle of excess, I have once heard mention of the item you seek there," she mentioned.

Lowlights:

I decided to bullied into silence the firmness of purpose.

...

It was rigid and exact boundaries, supported by evidence.

...

A woman approached. She appeared strenuous, intelligent, and alive.

"I am glad you feel so deeply about it," I said.

"I don't see anything particularly wonderful in it," she said.

"Where might I find a platinum fountain pen?" I asked.

"No," she replied.

"There are reasons which make such a course impossible," I mused.

"That doesn't sound very logical," she said.

"Unwholesome influence, I would say," I mentioned.

"You could not pay me a higher compliment," she said.

"It sounds profoundly interesting," I said.

"I do not feel sure that I entirely share your views," she mused.

I had hoped for some more definite information, so I politely bade her farewell.

jdm commented 4 years ago

Clever techniques to both incorporate change in the narrative while also keeping distinct features that carry through!

verachell commented 4 years ago

A little more info on sentence generation - no Lisp knowledge necessary

Here I'll talk about the general approach I used; code examples are brief and don't assume any knowledge of Lisp.

Main paragraph generation

I defined functions that generated sentences about different types of topics. For example, sound, smell, emotion, item-remind (character talks of his/her desire to seek the item), motive-remind (character talks about why he/she is seeking the item), and a bunch of others.

Many, but not all, of these functions call a general function I defined, generate-level1-sentence, which acts as a "mad-libs" type of sentence generator. As arguments it takes 1. a sentence containing an underscore for a missing word/phrase (e.g. "Feeling _ but not knowing why, I carried on.") 2. A word, e.g. "cheerful". The function then replaces the underscore with the word.

Nothing about the above is surprising; yet having those functions was very useful as you'll see below, because Lisp allows lists to store any type of data. Such data can even include the names of functions that you might want to run later. (You can run them with the funcall command.)

So, I defined the following list called *section* :

(defparameter *section* '((oddness . 40)(striving . 50)(backstory . 60)(moods . 40)(motive-remind . 40)(item-remind . 40)(smell . 40)(sound . 40)(emotion . 40)(literary-expression . 90)(literary-preposition . 90)(misc-phrase . 75)(preposition-trait . 85)(scenery . 100)(simile-image . 35)(paranoia . 0)(mannerism . 20)))

This seems like a lot of stuff, but when you look, it's just pairs of items separated by a dot. In this case the items just so happen to be a function name and a number.

For each dotted pair, the bit to the left of the dot happens in my case to contain the name of a function that generates a sentence. The bit to the right of the dot in my case represents probabilities.

The probability numbers I am using range from 0 to 100 and represents the % probability of the function being called. Don't over-think this: a score of 100 does NOT mean 100% of the sentences will be on that topic. It just represents the chance of that sentence being generated when offered the option to do so. The function is either called or not called (if not called, the flow of control just moves on to the next thing).

Then to generate a chunk of text, I simply:

  1. Shuffle the *section* list

  2. Loop through the list (Lisp has a dolist command for that). During the loop, generate a random number under 100 and if it's less than that of the current dotted pair, call the function named in the dotted pair using the funcall command. The result of that function (if called) is a sentence string and is pushed into the current paragraph. To prevent walls of text coming up, a line break has a random probability of being introduced at this point.

So it's pretty simple to set up the composition of a paragraph with a) different types of sentences b) that each have a random probability of being generated c) in a random ordering and d) with a random distribution of line breaks, every time a paragraph is generated.

Increasing the paranoia level

But wait, there's more! The list above defined as *section* is not a constant but a variable. So, it's possible to change any part at any time. You may have noticed that the probability for the dotted pair containing paranoia starts at zero (paranoia . 0). So there's no probability of getting a sentence related to paranoia at the start of the story.

During iterations in novel generation, I increase the number associated with paranoia by 1 (unless it's at 100 already).

The 1 was picked empirically as being that smallest whole number that should still be expected to wind up with a full paranoia of 100 by the end of a 50000 word novel.

So it's easy to create an increase in paranoia as the novel progresses.

Conversations

Conversations were not generated by looping through a list, but rather as an iterative process:

Randomly choosing if it's a male or female speaking; assigning gender-specific pronouns; deciding who should speak first; selecting a random phrase; deciding what should come after the phrase "said", "asked", etc.

It was also important to have the main character have a high initial probability of asking about the item he/she is seeking, and then of course any reply from the other person had to come at some point after that and not before. I also ensured an end condition occurred after a maximum number of turns of conversation, regardless of whether a clue had been received.The resultant function generate-convo was by far the longest function defined in the program.

Aimless wandering

After a conversation but before going to a location to look for a clue, the main character wanders around. This is handled very similarly to the main paragraph generation described at the top: by looping through a list of sentence-generation functions.

(defparameter *aimless-wandering* '((walking . 100)(abstr-prep . 90)(backstory . 20)(striving . 30)(desc-walking . 90)))

In this case the list is not shuffled first, but is otherwise like that described previously. The list is just a little different and shorter.

Looking for the item at a location

If a clue was received during the conversation, then the main character looks for the item at a location. (If a clue was not received, then no looking is done.)

The looking was handled similarly to conversations using an iterative process.

I'm not sure why I didn't think of just looping through a non-shuffled list as I did for the aimless wandering. In that situation I would have had to define functions to generate some sentences specific to the seeking, but on the other hand I generated functions for other types of sentence generation so why not for this? In any case, looking for the item involved first having an epiphany, then walking, then seeking the item, and then not finding it (unless the end condition was satisfied).

Eating

The above sequence of sentences (main paragraph, conversations, aimless wandering, looking for the item if a clue is found) is done 3 times, then the character goes off to eat and take a break.

Eating marks the end of one chapter, and the next iteration of main paragraph begins a new chapter.

End condition

Standard ending

The standard end condition (the one in the example novels) is triggered if both the following are true:

In that end condition, when the main character seeks the item, he/she finds it and hands it in at the specified location for delivery.

Alternative ending

An alternative end condition is triggered if

In practice this alternative ending is highly unlikely to occur, but is in place to avoid endless looping. The alternative ending has the character seeking for the item in a place of his/her own choosing, and finding it. The remainder (handing it in to the delivery location) is as for the standard ending.