Story Generator (#96)

The three rules of Ruby Q.:

  1. Please do not post any solutions or spoiler discussion for this quiz
    until
    48 hours have passed from the time on this message.

  2. Support Ruby Q. by submitting ideas as often as you can:

http://www.rubyquiz.com/

  1. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps
everyone
on Ruby T. follow the discussion. Please reply to the original quiz
message,
if you can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by Morton G.

[ Editor’s Note:

I realize we’ve done a similar quiz in the past, but read on and give
this one a
chance. It has a pretty different spin than Markov Chains.

–JEG2 ]

The Dwemthy’s Array RPG example in Why’s (Poignant) Guide to Ruby[1] was
my
introduction to Ruby metaprogramming. While it’s an excellent
introduction to
metaprogramming, it’s not much of an RPG, so I thought I’d have a go at
improving it. But a funny thing happened when I started coding: the RPG
turned
in a story generator. Here are a couple of stories generated by my
current
version.

The first story is fairly typical of the shorter ones. The rabbit gets
past the
BogusFox only to fall to the Jabberwocky.

A BogusFox emerges from the gloom and cries out,"Hail, Rabbit,
prepare to die!"
"I fear you not, BogusFox!"
Rabbit [25] and BogusFox [50] fight.
Rabbit attacks BogusFox with magick sword.
Fighting lowers BogusFox life force by 9.
BogusFox suffered a minor wound.
BogusFox swings his axe.
Fighting lowers Rabbit life force by 7.
Rabbit was wounded.
Rabbit [18] and BogusFox [41] fight.
Rabbit attacks BogusFox with magick sword.
Fighting lowers BogusFox life force by 43.
BogusFox dies.
Eating magick lettuce adds 7 to Rabbit life force.
A Jabberwocky emerges from the gloom and cries out,"Ah, a tasty 

Rabbit!"
“I fear you not, Jabberwocky!”
Rabbit [25] and Jabberwocky [100] fight.
Rabbit attacks Jabberwocky with magick sword.
Fighting lowers Jabberwocky life force by 63.
Jabberwocky was seriously wounded but carries on.
Jabberwocky attacks Rabbit with teeth and claws.
Fighting lowers Rabbit life force by 33.
Rabbit dies.
It’s over. It’s all over.

The second story is an example proving that low probability events do
occur. The
rabbit actually wins! And what’s truly amazing is that he kills every
monster
with a single stroke of his magick sword. Talk about luck!

A BogusFox emerges from the gloom and cries out,"Hail, Rabbit,
prepare to die!"
"I fear you not, BogusFox!"
Rabbit [25] and BogusFox [50] fight.
Rabbit attacks BogusFox with magick sword.
Fighting lowers BogusFox life force by 59.
BogusFox dies.
Eating magick lettuce adds 37 to Rabbit life force.
A Jabberwocky emerges from the gloom and cries out,"Ah, a tasty 

Rabbit!"
“I fear you not, Jabberwocky!”
Rabbit [62] and Jabberwocky [100] fight.
Rabbit attacks Jabberwocky with magick sword.
Fighting lowers Jabberwocky life force by 155.
Jabberwocky dies.
Eating magick lettuce adds 46 to Rabbit life force.
A DemonAngel emerges from the gloom and cries out,“Rabbit, I will
eat your soul!”
“I fear you not, DemonAngel!”
Rabbit [108] and DemonAngel [540] fight.
Rabbit attacks DemonAngel with magick sword.
Fighting lowers DemonAngel life force by 600.
DemonAngel dies.
Eating magick lettuce adds 20 to Rabbit life force.
A ViciousGreenFungus emerges from the gloom and cries out,“No Rabbit
has ever left my presence alive.”
“I fear you not, ViciousGreenFungus!”
Rabbit [128] and ViciousGreenFungus [320] fight.
Rabbit attacks ViciousGreenFungus with magick sword.
Fighting lowers ViciousGreenFungus life force by 390.
ViciousGreenFungus dies.
Eating magick lettuce adds 35 to Rabbit life force.
A Dragon emerges from the gloom and cries out,“A brave Rabbit burns
just as well as a timid one.”
“I fear you not, Dragon!”
Rabbit [163] and Dragon [1340] fight.
Rabbit attacks Dragon with magick sword.
Fighting lowers Dragon life force by 1436.
Dragon dies.
Eating magick lettuce adds 44 to Rabbit life force.
It’s over. It’s all over.

The secret of the rabbit’s magick sword will be revealed when my story
generated
is posted.

“It’s hardly literature,” you say. I agree. “It’s needs more work,” you
say.
Again, I agree. But it does tell a story. Don’t you root for the rabbit?
Don’t
you feel just a little sad when he’s killed (as he almost always is)?
And isn’t
it wonderful when, once in a hundred runs or so, he actually kills the
dragon
and completes his quest?

Story generators can be a lot of fun. Even addictive. It’s fascinating
to create
your own world. And they are completely open-ended. You can always find
ways to
tweak them, either to improve the readability of the output or to
improve the
plot.

In this quiz, I ask you to write your own story generator. You can start
with
Dwemthy’s Array, as I did, or invent your own characters and plot. The
only
requirement is that the generator must produce a different story each
time it is
run.

1:  http://qa.poignantguide.net/chapter-6.html#section3

On Sep 29, 2006, at 8:18 AM, Robert D. wrote:

On 9/29/06, Ruby Q. [email protected] wrote:

The three rules of Ruby Q.:

The Quiz ittself might be too much work to do for me, but reading
it was
already an enourmous pleasure.

I too was worried it was too much effort before I ran it, but Morton
sent me his solution and it’s not out-of-line with past quizzes.

We’re not going for Pulitzer Prize winning publications here. Keep
it simple and see what you can get with a moderate effort.

Morton gives a great suggestion for where to steal code that will get
you started, but even without that you have a lot of options.
Remember grade school English? A subject, verb, and object is all
you really need.

Think outside the box. If your stories turn out comical, list that
as a feature in the quiz submission message, not a bug. :wink:

James Edward G. II

P.S. For those of you that try this, posting generated stories is
not a spoiler!

On 9/29/06, Ruby Q. [email protected] wrote:

The three rules of Ruby Q.:

The Quiz ittself might be too much work to do for me, but reading it was
already an enourmous pleasure.
What a Funny, Great Idea!

Robert


Deux choses sont infinies : l’univers et la bêtise humaine ; en ce qui
concerne l’univers, je n’en ai pas acquis la certitude absolue.

  • Albert Einstein

On Sep 29, 2006, at 9:18 AM, Robert D. wrote:

The Quiz ittself might be too much work to do for me, but reading
it was
already an enourmous pleasure.
What a Funny, Great Idea!

I glad you like the idea. Please give it a try. It doesn’t require
complex code, and it is even more fun to code this up than it is to
read the results.

Here is a surefire recipe for success :slight_smile:

  1. To a handful of goofy characters, add a list of silly things for
    them to do
  2. Season with your favorite cliches – cliches are a key ingredient
  3. Stir in a text editor until a suitably turgid consistency is reached
  4. Run in a Ruby interpreter until done

Regards, Morton

On 9/30/06, Morton G. [email protected] wrote:

Here is a surefire recipe for success :slight_smile:

  1. To a handful of goofy characters, add a list of silly things for
    them to do
  2. Season with your favorite cliches – cliches are a key ingredient

Anyone else here old enough to remember MadLibs?


Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

Rick DeNatale wrote:

Roger Price … of course. Do you remember Droodles?

On Sep 30, 2006, at 9:08 AM, Rick DeNatale wrote:

On 9/30/06, Morton G. [email protected] wrote:

Here is a surefire recipe for success :slight_smile:

  1. To a handful of goofy characters, add a list of silly things for
    them to do
  2. Season with your favorite cliches – cliches are a key ingredient

Anyone else here old enough to remember MadLibs?

Sure:

http://www.rubyquiz.com/quiz28.html

James Edward G. II

On 9/30/06, Morton G. [email protected] wrote:

read the results.

Please clarify, the text above is it your advice or just some more output
;)?
I cannot promise, I have to spend some time with my wife - what an
excuse,
but probably not the best idea, because I have never noticed any female
names in the ML :frowning: , and I do not have the slightes idea how to attack
this.

Cheers
Robert


Deux choses sont infinies : l’univers et la bêtise humaine ; en ce qui
concerne l’univers, je n’en ai pas acquis la certitude absolue.

  • Albert Einstein

On Sep 29, 2006, at 11:15 PM, Morton G. wrote:

It doesn’t require complex code, and it is even more fun to code
this up than it is to read the results.

I agree. I’m at 22 lines of code and I’m already laughing:

James kills Ruby. Ruby falls in love with James. James falls in
love
with James. Ruby slays James! James cries.

Shakespeare would be jealous for sure.

James Edward G. II

On 9/30/06, James Edward G. II [email protected] wrote:

with James. Ruby slays James! James cries.

Shakespeare would be jealous for sure.

James Edward G. II

I am @ 300 lines, only concept, my wife does not talk to me anymore, and
you are heading for the Nobel Prize(1), I hate you, even if you are the
boss
of that Quiz thingy :wink:

(1) B/c I do not know how to spell Purlitzer :slight_smile:

Robert


Deux choses sont infinies : l’univers et la bêtise humaine ; en ce qui
concerne l’univers, je n’en ai pas acquis la certitude absolue.

  • Albert Einstein

Mine is based on the 80’s movie, Legend. My favorite output so far is:

“The Lord of Darkness undulates Princess Lily as the rain falls.”

Oh my! Naughty Lord of Darkness! Heh.

Regards,
Jordan

On Sep 30, 2006, at 11:14 AM, M. Edward (Ed) Borasky wrote:

Roger Price … of course. Do you remember Droodles?

Sure, this is one my favorites:

±------------------+
| |
| |
| |
| . . |
| |
| |
| |
±------------------+

Polar Bear in a Snowstorm

Regards, Morton

On 9/29/06, James Edward G. II [email protected] wrote:

P.S. For those of you that try this, posting generated stories is
not a spoiler!

In a parallel universe in the land of Atlantis there was a tireless
Botanist named
Goldilocks. Goldilocks lived there with her loyal Armadillo Barbara,
and the two were never apart.

The land of Atlantis was in meyhem because of the repulsive
influence of a evil witch named Argonagas.

Goldilocks and Barbara came up with a daring plan - Barbara would
distract the witch, giving Goldilocks the opportinuty to attack unseen.

They rode bravely into battle and Goldilocks sliced the evil witch in
strips with her portable angle-grinder while Barbara created a
diversion by screaming synonyms!
Goldilocks and Barbara became the heroes of Atlantis and lived happily
ever after.

THE END.

On Oct 1, 2006, at 5:08 AM, Leslie V. wrote:

influence of a evil witch named Argonagas.

THE END.

Wow, I can’t wait to see that generator!

James Edward G. II

I believe the 48 hours are up, so here’s my solution:
http://www.io.com/~jimm/rubyquiz/quiz96/

I decided to modify my solution to the Lisp Game quiz so it plays
itself. The output is a random “story” that’s all plot and no
characterization.

Aside from a small tweak to one method in game.rb, all the
“story-telling” code can be found in rads.rb. The general approach I
took was to, for each turn, find all legal verbs and construct correct
game input. Look for the comment “new game-playing methods”. The one
tweak to game.rb suppresses error messages when checking for the
legality of certain moves.

The game plays itself to a conclusion in approximately five to ten
seconds on my laptop. If I pipe all output to a file instead of the
terminal, it completes in under a second.

  • rads.rb the engine, with modified “story-telling” code
  • game.rb the game description and custom verbs

To run this game, download both files and execute game.rb. See the
notes for my original game solution for details.

Jim

On 9/30/06, MonkeeSage [email protected] wrote:

Mine is based on the 80’s movie, Legend. My favorite output so far is:

“The Lord of Darkness undulates Princess Lily as the rain falls.”

Oh my! Naughty Lord of Darkness! Heh.

Regards,
Jordan

The approach I’ve taken is to modify my solution to Ruby Q. #49 (the
Lisp Game) so it plays itself. The output is a random “story” that’s
all plot and no characterization.

Jim

On Oct 1, 2006, at 9:45 AM, Jim M. wrote:

I believe the 48 hours are up, so here’s my solution:
http://www.io.com/~jimm/rubyquiz/quiz96/

Dang that’s clever Jim!

Here’s the tragedy.rb file I was taunting the list with yesterday:

#!/usr/bin/env ruby -w

class Array
def rand
fetch(Kernel.rand(size))
end
end

characters = %w[James Ruby]
sentence_structure = [ “S falls in love with O.”,
“S (slays|kills) O(.|.|.|!)”,
“S cries.” ]

output = String.new
(ARGV.shift || 5).to_i.times do
sentence = sentence_structure.rand.gsub(/\b[SO]\b/)
{ characters.rand }
output << " " << sentence.gsub(/([^()|]+(?:|[^()|]+)+)/) do |
choices|
choices[1…-2].split(“|”).rand
end
end

puts output.strip.gsub(/(.{1,80}|\S{81,})(?: +|$\n?)/, “\1\n”)

END

James Edward G. II

On 10/1/06, James Edward G. II [email protected] wrote:

puts output.strip.gsub(/(.{1,80}|\S{81,})(?: +|$\n?)/, “\1\n”)

END

I just get:

jim.rb:17: odd number list for Hash
something to do with the email formatting? I’ll have to look later.

Les

Little Red-Cap revisited

Once upon a time little red-cap opened the stomach of the huntsman.
The huntsman
thought to himself “what a tender young creature”. Soon he gave a
pair of
scissors to grandmother. Little red-cap went into the wood. And so
the huntsman
swallowed up the wolf. Grandmother gave a piece of cake to the wolf.
Mother ran
straight to grandmother’s house. And so grandmother was on her guard
although
the wolf was not afraid of grandmother until mother swallowed up the
wolf.
Grandmother was not afraid of herself. The wolf gave a pair of
scissors to the
huntsman. Little red-cap fell asleep. She said: “oh, the huntsman,
what big ears
you have”.
The End.

stories.rb

require ‘enumerator’

class Base
def self.constructor *args
attr_accessor(*args)
define_method :initialize do |*values|
(0…args.size).each do |i|
self.instance_variable_set("@#{args[i]}", values[i])
end
end
end
def to_s
@name
end
end

class Character < Base
constructor :name, :gender
end

class Action < Base
constructor :name, :objects_or_types
end

class Item < Base
constructor :name
end

class Place < Base
constructor :name
end

class PronounBase < Base
constructor :gender
class << self
attr_accessor :cases
end
def to_s
cases = self.class.cases
@gender == :female ? cases[0] : cases[1]
end
end

class PossessiveAdjective < PronounBase
self.cases = [‘her’, ‘his’]
end

class Pronoun < PronounBase
self.cases = [‘she’, ‘he’]
end

class ReflexivePronoun < PronounBase
self.cases = [‘herself’, ‘himself’]
end

class Bridge < Base
constructor :name
end

class Entities
def initialize klass
@entities = []
@klass = klass
yield(self)
end
def create *args
@entities << @klass.new(*args)
end
def pick
@entities[rand(@entities.size)]
end
end

CAST = Entities.new(Character) do |c|
c.create ‘little red-cap’, :female
c.create ‘mother’, :female
c.create ‘grandmother’, :female
c.create ‘the wolf’, :male
c.create ‘the huntsman’, :male
end

ACTIONS = Entities.new(Action) do |a|
a.create ‘met’, [Character]
a.create ‘gave’, [Item, ‘to’, Character]
a.create ‘took’, [Item]
a.create ‘ate’, [Item]
a.create ‘saw’, [Item]
a.create ‘told’, [Character, ‘to be careful’]
a.create ‘lived in’, [Place]
a.create ‘lied in’, [Place]
a.create ‘went into’, [Place]
a.create ‘ran straight to’, [Place]
a.create ‘raised’, [PossessiveAdjective, ‘eyes’]
a.create ‘was on’, [PossessiveAdjective, ‘guard’]
a.create ‘thought to’, [ReflexivePronoun, ‘“what a tender young
creature”’]
a.create ‘swallowed up’, [Character]
a.create ‘opened the stomach of’, [Character]
a.create ‘looked very strange’, []
a.create ‘was delighted’, []
a.create ‘fell asleep’, []
a.create ‘snored very loud’, []
a.create ‘said: "oh,’, [Character, ‘, what big ears you have"’]
a.create ‘was not afraid of’, [Character]
a.create ‘walked for a short time by the side of’, [Character]
a.create ‘got deeper and deeper into’, [Place]
end

ITEMS = Entities.new(Item) do |i|
i.create ‘a piece of cake’
i.create ‘a bottle of wine’
i.create ‘pretty flowers’
i.create ‘a pair of scissors’
end

PLACES = Entities.new(Place) do |p|
p.create ‘the wood’
p.create ‘the village’
p.create ‘bed’
p.create “grandmother’s house”
p.create ‘the room’
end

BRIDGES = Entities.new(Bridge) do |b|
5.times{b.create ‘.’}
b.create ‘, because’
b.create ‘, while’
b.create ‘. Later’
b.create ‘. Then’
b.create ‘. The next day’
b.create ‘. And so’
b.create ‘, but’
b.create ‘. Soon’
b.create ‘, and’
b.create ’ until’
b.create ’ although’
end

ALL = { Character => CAST, Action => ACTIONS, Place => PLACES, Item
=> ITEMS }

class Sentence
attr_accessor :subject
def initialize
@subject = CAST.pick
@verb = ACTIONS.pick
@objects = []
@verb.objects_or_types.each do |obj_or_type|
if String === obj_or_type
@objects << obj_or_type
else
if obj_or_type == PossessiveAdjective or obj_or_type ==
ReflexivePronoun
@objects << obj_or_type.new(@subject.gender)
else
thingy = ALL[obj_or_type].pick
thingy = ReflexivePronoun.new(thingy.gender) if thingy ==
@subject
@objects << thingy
end
end
end
end

def to_s
[@subject, @verb, @objects].flatten.map{|e| e.to_s}.join(’ ')
end
end

class Story
def initialize
@sentences = []
1.upto(rand(10)+10) do
@sentences << Sentence.new
end
combine_subjects
end

When the last sentence had the same subject, replace subject

with ‘he’ or ‘she’:
def combine_subjects
@sentences.each_cons(2) do |s1, s2|
if s1.subject == s2.subject
s2.subject = Pronoun.new(s1.subject.gender)
end
end
end

Combine sentences to a story:

def to_s
text = 'Once upon a time ’ + @sentences[0].to_s
@sentences[1…-1].each do |sentence|
bridge = BRIDGES.pick.to_s
text += bridge + ’ ’ + (bridge[-1,1] == ‘.’ ?
sentence.to_s.capitalize : sentence.to_s)
end
text.gsub!(/ ,/, ‘,’) # a little clean-up
text.gsub!(/(.{70,80}) /, “\1\n”)
text + “.\nThe End.\n”
end
end

puts Story.new.to_s

My submission puts out a “story” that looks more like a transcript from
a turn-based MUD like dwemthy. It’s basically just a poor man’s markov
chain generator with a fixed model. Some of the turns are pretty funny
though. :slight_smile:

#!/usr/bin/ruby

A new Legend…

turns = (ARGV.first || 14).to_i

characters = [‘Jack’, ‘Princess Lily’, ‘Honeythorn Gump’,
‘Oona’, ‘Brown Tom’, ‘Screwball’, ‘Blunder’,
‘Blix’, ‘The Lord of Darkness’]

actions = [‘bites’, ‘conjugates’, ‘hits’, ‘scares’, ‘oogles’,
‘thumps’, ‘extrapolates’, ‘undulates’, ‘pesters’,
‘sputters’, ‘liquifies’, ‘castigates’, ‘gesticulates’,
‘berates’, ‘circumscribes’, ‘burns’, ‘kicks’,
‘punctures’, ‘disembowels’, ‘stabs’, ‘smells’,
‘browbeats’, ‘villifies’, ‘deflagrates’,
‘psychoanalyzes’, ‘dominates’, ‘cajoles’]

modifiers = [‘near’, ‘under’, ‘around’, ‘past’, ‘next to’,
‘about’, ‘with’, ‘by’, ‘above’, ‘beside’,
‘close to’, ‘in the general vicinity of’]

adjectives = [‘actually’, ‘giddly’, ‘angrily’, ‘suddenly’,
‘fearfully’, ‘faithfully’, ‘fiendishly’,
‘maniacally’, ‘gradually’, ‘manually’]

fragments = [‘until noon’, ‘when darkness falls’,
‘in the mouth of chaos’, ‘as the rain falls’,
‘all day long’, ‘while unicorns roam the earth’,
‘as the world turns’, ‘in the belly of the beast’,
‘on television’, ‘in Parliment’, ‘with gentle hands’,
‘like a soccer scanger’, ‘with a cockney accent’,
‘like a malevolent spirit’, ‘like a wild banshee’]

plottwists = [‘From out of nowhere…’, ‘All of a sudden…’,
‘Somewhat randomly…’, ‘Then with utter abandon…’,
‘In contempt of life…’, ‘When he finally realizes…’,
‘Meanwhile in the forest…’, ‘After that…’,
‘Back at the office…’, ‘During normal buisness hours…’]

finales = [‘decimates destroys and otherwise obliterates’,
‘pummels drop-kicks and powerfully suplexes’,
‘burns explodes and utterly incinderates’,
‘stabs cuts and overall perferates’]

actions2 = [‘hides’, ‘sneaks’, ‘knits sweaters’, ‘steals cheese’,
‘answers the call of nature’, ‘aggrevates the wildlife’,
‘saves the whales’, ‘clear cuts old-growth forests’]

locations = [‘pools of bean curd’, ‘fountains of cheese dip’,
‘deciduous forests’, “farmer John’s chicken coops”,
‘Fruit Of The Loom underwear’, ‘the English Channel’,
‘nests of burrowing rodents’, ‘festering sores’,
‘public bathrooms’, “Hugh Hefner’s mansion”,
‘Buddhist temples’]

chains = [[‘characters’, ‘actions’, ‘characters’, ‘fragments’],
[‘characters’, ‘adjectives’, ‘actions’, ‘characters’,
‘fragments’],
[‘characters’, ‘actions’, ‘modifiers’, ‘characters’,
‘fragments’],
[‘characters’, ‘actions’, ‘modifiers’, ‘characters’,
‘adjectives’, ‘fragments’],
[‘characters’, ‘actions’, ‘characters’, ‘modifiers’,
‘characters’]]

def choose(ary)
ary[rand(ary.size)]
end

intervals = []
(turns/3).times {
interval = rand(turns)
while intervals.include?(interval) or
intervals.include?(interval+1) or
intervals.include?(interval-1)
interval = rand(turns)
end
intervals << interval
}

story = []
twists = []
turns.times {
begin
events = []
choose(chains).each { |item|
item = eval(item)
event = choose(item)
while events.include?(event)
event = choose(item)
end
events << event
}
end while story.include?(events)
story << events
}

intervals.each { |i|
twist = choose(plottwists)
while twists.include?(twist)
twist = choose(plottwists)
end
story.insert(i, [twist])
}

story.each { |event|
puts event.join(’ ') + ‘.’
}

print "\n~~~\n\nFinally, after much strife…\n#{choose(characters)} ",
"#{choose(finales)} #{choose(characters)},\nwho #{choose(actions2)} ",
"in or around #{choose(locations)} at night,\nand has thusly rid ",
“the world of the scurrilous bane forever!\n\n~ The End ~\n”

END

Regards,
Jordan