National Novel Writing Month starts in a few days, and if you still don’t have an idea, this article may be for you. You can build your own random plot generator to aid when the muses have left you.
You’ll need to have a basic understanding of React and Javascript to get started.
This project borrows many ideas from the Random Character Generator I built. See the corresponding article to get started.
Let’s Build a Random Character Generator with Faker.js!
Kailana Kahawaii ・ Oct 8 '20
Planning a plot
We all know that stories follow five basic plot points, but sometimes getting started with writing one is the hardest part. To get started, try coming up with a generic summary sentence that encapsulates a narrative.
Here’s the sentence I came up with:
A (genre) story that involves (genre related character) in a (genre related setting) and (genre related problem).
Using the variables such as genre related character and genre related setting, I brainstormed out elements that fit in those areas. I found using a spreadsheet was a good way to keep track of everything.
I’m going to stick to the fantasy genre for now.
Coming up with a list of fantasy characters is easy enough.
However, fantasy settings usually have a lot of detail to them. How can I generate contextually correct descriptors?
Thinking in data structures
It would make sense to store our setting verbs as a part of an array. That way, we can randomly choose an index. But how can we make these actions correspond to the correct noun? After all, a mountain can’t be in the path of collapse.
Since each action is assigned to an index, we can make the nouns keys to an object. Then, we can create number values that correspond to contextually correct verbs.
For example, beasts can work with the actions at indices 0, 1, 2, 4, and 5.
beasts: 0, 1, 2, 4, 5
This way, we’ll be able to correctly match the action to the noun.
Finally, we move on to the conflict, or what makes a story really tick. There are six types of conflicts in stories, but I’m going to consolidate those into two types: a character arc and a quest arc.
On the character arc side, an action will happen to a character. On the quest side, some sort of action will happen over an item, person, or place.
With this done, we’re ready to construct our object.
The fantasy object
In a new file that I’ve labeled randomPlotData.js, I build out the object from the spreadsheet.
const randomPlotData = {
genres: {
‘fantasy’: {
characters: [
‘an elf’,
[…]
],
setting: {
places: [
‘In a village’,
[…]
],
actions: [
‘beset by’,
[…]
],
plights: [
{' beasts':[0, 1, 2, 4, 5, 6]},
[…],
],
},
conflict: [
{
type1: {
problem: [
‘the death’,
[…],
],
instigator: [
‘ of a king’,
[…],
},
},
{
type2: {
problem: [
‘the hunt for’,
[…],
],
instigator: [
‘ a panacea’,
],
[…]
}
I’ve made a few changes here. First, I’ve changed the conflict types to type1 and type2. This way, we can add more conflicts later without having to change the functions. I’ve also added prepositions, articles, and spacing to make the sentences grammatically correct.
To see the full object, check out the repo.
Generate random plot points using Math.random()
Finally, it’s time to work on our component.
Create a new component called RandomPlotGenerator.js and import randomPlotData.
We’ll use Math.random() to choose a random index in the character array.
genreRelatedCharacter = (data)=> {
let charList = data.genres.fantasy.characters
let number = Math.floor(Math.random() * charList.length)
return charList[number]
}
Generating a random setting uses the same logic.
In order to generate a setting description, however, we’ll need to expand our logic a bit.
Let’s look over the data we’re using to generate these descriptors again.
actions: [
'beset by',
'in the path of',
'consumed by',
'on the verge of',
'conquered by',
'covered in',
'threatened by'
], plights: [
{' beasts':[0, 1, 2, 4, 5, 6]},
{' a raging storm':[0, 1, 2, 6]},
{' a plague':[0, 1, 2, 3, 6]},
{' collapse':[0, 3, 6]},
{' a celebration':[2, 3, 6]},
{' a malicious ruler':[0, 1, 4, 6]},
{' ice':[0, 1, 2, 5, 6]},
{' lava':[0, 1, 2, 5, 6]},
{' moss':[0, 1, 2, 5, 6]},
{' an invading army':[0, 1, 4, 6]},
]
We have an actions array and an array of objects called plights.
Let’s create a variable that’ll hold our final string called settingDesc.
let settingDesc = ""
Then, we’ll extract the list from our object and hold on to the random number that’s generated. We’ll also select our plight (remember it’s an object, so we’ll need to use Object.keys to find it).
let plightList = data.genres.fantasy.setting.plights
let plightNum = Math.floor(Math.random() * plightList.length)
let plight = Object.keys(plightList[plightNum])
This takes care of finding a plight. Now we’ll need to use this plight to find a contextually correct action.
We’ll use Object.values to access the number list assigned to each plight key. Currently, our number list looks something like this [ [ 0, 1, 2, 4, 5, 6 ] ], so we’ll need to flatten the array as well.
let plightArr = Object.values(plightList[plightNum]).flat()
That will give us an array that looks like this: [ 0, 1, 2, 4, 5, 6 ].
Again, we want to randomly select a number from this array, which will be the index of the word for our action.
let actionNum = plightArr[Math.floor(Math.random() * plightArr.length)]
let action = data.genres.fantasy.setting.actions[actionNum]
This chooses a contextually correct action word.
There’s just one problem. Our plights are keys in an object, not strings, so we’ll need to clean up that data with JSON.stringify and some regex.
let stringedPlight = JSON.stringify(plight).replace(/[\[\]']+/g,'').replace(/\"/g, "")
Finally, we concat the action and stringedPlight at the end.
return settingDesc.concat(action, stringedPlight)
The complete function will look something like this:
genreRelatedSettingDescription = (data) => {
let settingDesc = ""
let plightList = data.genres.fantasy.setting.plights
let plightNum = Math.floor(Math.random() * plightList.length)
let plight = Object.keys(plightList[plightNum])
let plightArr = Object.values(plightList[plightNum]).flat()
let actionNum = plightArr[Math.floor(Math.random() * plightArr.length)]
let action = data.genres.fantasy.setting.actions[actionNum]
let stringedPlight = JSON.stringify(plight).replace(/[\[\]']+/g,'').replace(/\"/g, "")
return settingDesc.concat(action, stringedPlight)
}
The last random set we need to generate is the conflict. Remember, we can have two possible conflict types, so we’ll need to generate three random numbers here: one for the conflict type, one for the problem, and one for the instigator.
Again, we’ll also need to concatenate the two descriptions generated.
let conflict = ""
let conflictList = data.genres.fantasy.conflict
let num = Math.floor(Math.random() * conflictList.length)
let conflictType = conflictList[num]
let conflictWrapper = conflictType[`type${num+1}`]
Because our conflict is either type1 or type2, we can simply interpolate the string.
Finally, we can generate random descriptions using the conflict type.
let problem = conflictWrapper.problem[Math.floor(Math.random() * conflictWrapper.problem.length)]
let instigator = conflictWrapper.instigator[Math.floor(Math.random() * conflictWrapper.instigator.length)]
return conflict.concat(problem, instigator)
Putting it all together
Our functions are done. In the render method, we’ll invoke each of them to construct our random plot generator!
render(){
return(
<div>
<h1>Random Plot Generator</h1>
<p>{`A story that involves ${this.genreRelatedCharacter(RandomPlotData)} in a ${this.genreRelatedSetting(RandomPlotData)} ${this.genreRelatedSettingDescription(RandomPlotData)} and ${this.genreRelatedConflict(RandomPlotData)}.`} </p>
</div>
)
}
By using objects, we can construct data for random plot generators. We can also use data structures and Math.random() to make our plot generators provide even more ideas and detail. Go ahead and add even more detail words to the object, or create a new genre object. By following the pattern, and making reusable functions, the sky’s the limit!
Top comments (0)