Custom Def #8: Now for the Story...

Published June 29, 2020, 1:12 a.m.

In this eighth tutorial for the "creating custom definitions" series we will finally cap off our Mad-Lib Generator. Last time we adjusted our definition so that it read lines, looked for a "double underscore" as the identifier of a word to replace and then replaced those double underscored words.  This time we will clean up our code a little bit and generate a whole madlib story.

First we need to un-comment our lines and remove any "legacy" code from the text.  Then we want to replace some words in the story with the words that we want to fill in the blanks with.  Note that we are not limited to pure parts of speech.  In the lines below I have added a __Number__ and __Plural_Body_Part__ in addition to several nouns and adjectives. 

lines = []
lines.append( "\n Now for the story! \n")
lines.append( "Once upon a time there was a __noun__ with __Number__ weak __Plural_Body_Part__ .")
lines.append( "The dog was very __adjective__ and __adjective__.")
lines.append( "One day a __adjective__ __noun__ spoke to the dog and said..." )
lines.append( "'You are too weak.  You must eat protein __noun__!'")
lines.append( "The dog was annoyed, but he started excercising daily.")
lines.append( "Pretty soon, he became __adjective__ and more __adjective__!")
lines.append( "Now the dog shows off his __Plural_Body_Part__ on his own __adjective__ videos sponsored by P- __Number__ -X!" )
lines.append( "The End!")

Because of the way that we have written the code, it is important to have a single underscore or dash between multiple-word keywords.  If we did not have the single underscore on the __Plural_Body_Part__ keyword and instead tried to have "__Plural Body Part__" , then python would try to replace "__Plural" and "Part__"separately and leave "Body" unchanged! 

When our code reads the word with the single underscore it actually says the word "underscore" in its place.  This behavior is not really desireable, but it is left to you to figure out how to make python do your bidding in that regard!

So now we are almost ready to run our code and hear our madlib.  Checking below we see ...

for line in lines:
	line = readandreplaceline(line)
	engine.say(line,line)
engine.runAndWait()

My expectation is that loop will go through the lines, the line will be replaced as necessary, the engine will record the things to print and say and then, after the loop is finished it will print and say them all in order.

Going to the command window and running the program yields:

C:\PythonProject>python madlib.py

 Now for the story!

Give me an noun, and press 'Enter'.
pencil
Give me an Number, and press 'Enter'.
25
Give me an Plural_Body_Part, and press 'Enter'.
faces
Once upon a time there was a pencil with 25 weak faces .
Give me an adjective, and press 'Enter'.

and so on....  This is not what I had in mind!  I had believed that the lines would be stored and read at the end during the "engine.runAndWait()" execution.

No problem.  I just need to loop through the lines ahead of time to get the words for the madlib and then loop through again to read the story. Right?

for line in lines:
	line = readandreplaceline(line)

for line in lines:
	engine.say(line,line)
engine.runAndWait()

Running the code gives:

C:\PythonProject>python madlib.py
Give me an noun, and press 'Enter'.
pencil
Give me an Number, and press 'Enter'.
25
Give me an Plural_Body_Part, and press 'Enter'.
knees
Give me an adjective, and press 'Enter'.
ugly
Give me an adjective., and press 'Enter'.
stinky
Give me an adjective, and press 'Enter'.
filthy
Give me an noun, and press 'Enter'.
mouse
Give me an noun!', and press 'Enter'.
car
Give me an adjective, and press 'Enter'.
grey
Give me an adjective!, and press 'Enter'.
flat
Give me an Plural_Body_Part, and press 'Enter'.
heads
Give me an adjective, and press 'Enter'.
dark
Give me an Number, and press 'Enter'.
9034

 Now for the story!

Once upon a time there was a __noun__ with __Number__ weak __Plural_Body_Part__ .

Oh no!  What happened?  Just another mistake.  I looped through the lines like I wanted and I was asked for all the input like I wanted but then when I want the story read it was unchanged!  The reason is that there is a difference between making changes and saving changes!

newlines = []
for line in lines:
	line = readandreplaceline(line)
	newlines.append(line)

for line in newlines:
	engine.say(line,line)
engine.runAndWait()

The code above finally has a baseline behavior that we want.  We define a new empty list for the "new lines"  then after making the new line we save it in the new list.  Finally we make sure that our second loop goes through the new list (newlines) so that we finally read the madlib story with the new words in it!

Here is the final code in totality:

import sys, os
import pyttsx3
import random
import pickle

def onStart(name):
	print(name)
	
def onWord(name, location, length):
	print("word", name, location, length)
	
def onEnd(name, completed):
	print('Finishing', name, completed)
	
engine = pyttsx3.init()
engine.connect('started-utterance', onStart)
voices = engine.getProperty('voices')
engine.setProperty('voice', random.choice(voices).id)


def getNewWord(part_of_speech):
	query = "Give me an %s, and press 'Enter'." % part_of_speech
	engine.say(query, query)
	engine.runAndWait()
	newword = input()
	return newword

def addNewWordToDict(AnyDict, part_of_speech):
	query = "Give me an %s, and press 'Enter'." % part_of_speech
	engine.say(query, query)
	engine.runAndWait()
	newword = input()
	AnyDict[part_of_speech].append(newword)
	return AnyDict, newword

# MadLibDict = {
			  # "noun": ['dog-catcher', 'plum', 'golf ball'],	
			  # "adjective": ['ugly', 'fat', 'golden'],	
				# }
				
def getNewWordFromDict(WordDict, part_of_speech):
	# query = "Give me an %s, and press 'Enter'." % part_of_speech
	# engine.say(query, query)
	# engine.runAndWait()
	newword = random.choice(WordDict[part_of_speech])
	# newword = input()
	return newword

def saveMadlibPickle(MadLibDict):
	f = open("Madlib.pickle", 'wb')	
	pickle.dump(MadLibDict, f)
	f.close()
	
def openMadlibPickle():	
	f = open("Madlib.pickle", 'rb')
	MadLibDict = pickle.load(f)
	f.close()
	return MadLibDict
	
def readandreplaceline(line):
	copyline = line
	splitline = line.split()
	for word in splitline:
		if "__" in word:
			part_of_speech = word.replace("__", "")
			newword = getNewWord(part_of_speech)
			j =0
			while word not in copyline[:j]:
				j+=1
			copyline = copyline[:j].replace(word, newword)+copyline[j:]
	return copyline
	

lines = []
lines.append( "\n Now for the story! \n")
lines.append( "Once upon a time there was a __noun__ with __Number__ weak __Plural_Body_Part__ .")
lines.append( "The dog was very __adjective__ and __adjective__.")
lines.append( "One day a __adjective__ __noun__ spoke to the dog and said..." )
lines.append( "'You are too weak.  You must eat protein __noun__!'")
lines.append( "The dog was annoyed, but he started excercising daily.")
lines.append( "Pretty soon, he became __adjective__ and more __adjective__!")
lines.append( "Now the dog shows off his __Plural_Body_Part__ on his own __adjective__ videos sponsored by P- __Number__ -X!" )
lines.append( "The End!")

newlines = []
for line in lines:
	line = readandreplaceline(line)
	newlines.append(line)

for line in newlines:
	engine.say(line,line)
engine.runAndWait()

 

I hope that you found this tutorial series interesting.  In this series I showed how to make custom definitions work together to make a fun madlib read in a computer voice.  We touched on definitions, dictionaries, pickles, importing modules and some search and replace methods.  Keep programming and check out my other tutorials.  See you then.

  • Custom Def #1: Import pyttsx3

  • Custom Def #2: Read a story from lines.

  • Custom Def #3: Using Multiple Definitions

  • Custom Def #4: Generalized Definitions

  • Custom Def #5: Dictionaries and Definitions

  • Custom Def #6: Python Pickles

  • Custom Def #7: Search and Replace in Lines

  • Custom Def #8: Now for the Story...
    (currently viewing)