My setup has the following features:
1. Takes input from three separate sources: One for an NPC character (dialogM.txt), one for your character/narration (dialogY.txt), and one for menu options (dialogC.txt). These text files go in the /game folder.
2. Generates either a two-option or three-option menu choice, with random text.
3. Modifies a "like" variable at random when the player chooses a menu option. (In my original game, this is used to track your progress toward wooing Miko. The code doesn't reveal choice outcomes to the player.)
I also have code that displays random emotes from the NPC, but it's highly customized to my image set so I won't include it here. You can check out the full source by downloading the game itself; my content is CC BY licensed.
There is one major caveat: Your input files must be plain ASCII. The Markov generator can create output that breaks Ren'Py. I assume it sometimes passes escape characters that cause Ren'Py to think it's now receiving arguments instead of dialog text. There's a really simple bugfix added to the Markovgen script that sort of handles this problem. However, it is essential that you use grep or some other text editing tool to remove non-ASCII characters from your source text. All quotes must be straight, and special punctuation such as em dashes must be removed. I used TextWrangler to get my source files in shape.
Here is the modified version of markovgen.py — Place it in your /game folder.
Code: Select all
import renpy.store as store
import renpy.exports as renpy
import random
class Markov(object):
def __init__(self, open_file):
self.cache = {}
self.open_file = open_file
self.words = self.file_to_words()
self.word_size = len(self.words)
self.database()
def file_to_words(self):
self.open_file.seek(0)
data = self.open_file.read()
words = data.split()
return words
def triples(self):
""" Generates triples from the given data string. So if our string were
"What a lovely day", we'd generate (What, a, lovely) and then
(a, lovely, day).
"""
if len(self.words) < 3:
return
for i in range(len(self.words) - 2):
yield (self.words[i], self.words[i+1], self.words[i+2])
def database(self):
for w1, w2, w3 in self.triples():
key = (w1, w2)
if key in self.cache:
self.cache[key].append(w3)
else:
self.cache[key] = [w3]
def generate_markov_text(self, size=25):
seed = random.randint(0, self.word_size-3)
seed_word, next_word = self.words[seed], self.words[seed+1]
w1, w2 = seed_word, next_word
gen_words = []
for i in xrange(size): # Cheap fix for Ren'Py errors caused by EOF
gen_words.append(w1)
try:
w1, w2 = w2, random.choice(self.cache[(w1, w2)])
except KeyError:
break
gen_words.append(w2)
return ' '.join(gen_words)
Code: Select all
init python:
import markovgen
filenameM = "dialogM.txt" # NPC
filenameY = "dialogY.txt" # You
filenameC = "dialogC.txt" # Menu choice
fileM_ = open(renpy.loader.transfn(filenameM)) # Open the files so markovgen.py can access them
fileY_ = open(renpy.loader.transfn(filenameY))
fileC_ = open(renpy.loader.transfn(filenameC))
markovM = markovgen.Markov(fileM_) # Generate the Markov chain data
markovY = markovgen.Markov(fileY_)
markovC = markovgen.Markov(fileC_)
choice = 0 # records the menu choice the player selected
effectGen = 0 #
effectA = 0 # Use to randomize modding of the "like" variable
effectB = 0 # in menu choices.
effectC = 0 # (Different "win" path every time)
like = 0 # track how well you're doing (like > 0 to "win")
Code: Select all
label Msay: # Make NPC say something.
$ mString = markovM.generate_markov_text()
m "%(mString)s"
return
label Nsay: # Make narrator think something.
$ mString = markovY.generate_markov_text()
"%(mString)s"
return
label Ysay: # Make you say something.
$ mString = markovY.generate_markov_text()
y "%(mString)s"
return
label Gen: #generates a series of three strings for menu options, don't call directly
$ mStringA = markovC.generate_markov_text()
$ mStringB = markovC.generate_markov_text()
$ mStringC = markovC.generate_markov_text()
return
label GenEffect: # generates randomized effects to "like" variable in menu choices, using terrible programming. Don't call directly.
python:
effectGen = renpy.random.randint(0, 2)
if (effectGen == 0):
effectA = -1
effectGen = renpy.random.randint(0,1)
if (effectGen == 0):
effectB = 0
effectC = 1
else:
effectC = 0
effectB = 1
elif (effectGen == 1):
effectB = -1
effectGen = renpy.random.randint(0,1)
if (effectGen == 0):
effectA = 0
effectC = 1
else:
effectC = 0
effectA = 1
elif (effectGen == 2):
effectC = -1
effectGen = renpy.random.randint(0,1)
if (effectGen == 0):
effectA = 0
effectB = 1
else:
effectB = 0
effectA = 1
#"like=%(like)s\n\n%(effectA)s, %(effectB)s, %(effectC)s" -- CHEAT CODE! Uncomment to see menu effects and your "score"
return
label MenuTwo: #two menu choices
call GenEffect
call Gen
menu:
"%(mStringA)s":
$ choice = 1
$ like += effectA
return
"%(mStringB)s":
$ choice = 2
$ like += effectB
return
label MenuThree: #three menu choices
call GenEffect
call Gen
menu:
"%(mStringA)s":
$ choice = 1
$ like += effectA
return
"%(mStringB)s":
$ choice = 2
$ like += effectB
return
"%(mStringC)s":
$ choice = 3
$ like += effectC
return
Code: Select all
label start:
play music "music/music_beach_house.ogg" fadeout 0.25 fadein 0
scene bg classroom day
with dissolve
call Nsay
call Nsay
show m
with dissolve
call Msay
call Msay
call Ysay
m "..."
call Nsay
call Nsay
call Msay
call MenuTwo
# the menu modified the "like" variable, so let's change the story branch based on its new value
if like > 0:
jump branch1
else:
jump branch2
~ Amy