Now in its third year, National Novel Generation Month is a whimsical offshoot of the wildly popular National Novel Writing Month. NaNoGenMo participants write a computer program that outputs a “novel” of at least 50,000 words. (There is no official definition of “novel”—any 50,000 words qualify.) The Verge had a good roundup of the 2014 entries.
Last year, I wrote a program that generated a nonsense book inspired by the mysterious 16th century Voynich Manuscript. This year I took inspiration from one of the earliest known works of computer-generated fiction, the little-known SAGA II.
SAGA II was written in 1960 by Douglas T. Ross and Harrison R. Morse at MIT, and was featured in The Thinking Machine, a delightful 1961 CBS television special. In the special (which can be viewed in its entirety online), men in suits sit in comfortable chairs smoking pipes and debating whether computers can be truly creative. What sets the show apart from other examples of breathless futurism is the emphasis on contemporary research—we see actual engineers manipulating room-size computers, light pens, and teletypes.
The SAGA II project was commissioned by the show’s CBS producer, who hypothesized that TV westerns were so stereotyped that a computer could plausibly write a screenplay. In response, the MIT engineers coded a simulation that could generate an infinite number of variations on a classic Western scene: a robber, pursued by a sheriff, breaks into a house, has a few drinks, and engages in a deadly shoot-out. The survivor takes the loot and leaves the scene. CBS filmed two of the computer-generated scripts and, charmingly, one buggy one in which the robber gets stuck in an endless loop, perhaps the earliest form of glitch art.
Here’s what an original script looked like:
(The gun is in the right hand; the money is in the left hand; the glass is on the table; the bottle is on the table; the holster is on the robber; the sheriff’s gun is in the sheriff’s right hand; the sheriff’s holster is on the sheriff.)
ROBBER: (The robber is at the window.) Go to door; open door; go thru door; close door; go to corner; put money down at corner; go to window; put gun down at window; lean on window and look; lean on window and look; go to corner; count money; go to table; pick up glass with right hand (empty); take glass from right hand with left hand; pick up bottle with right hand; pour; put bottle on table; take glass from left hand with right hand; take a drink from glass; put glass on table; pick up bottle with right hand; pour; go to corner; put bottle down at corner; go to window; pick up gun with right hand; check gun; put gun in holster; go to table; pick up glass with right hand; take a drink from glass; go to window; put glass down at window.
SHERIFF: (The sheriff is at the window.) See robber; (robber sees sheriff); go to door.
ROBBER: Take gun from holster with right hand; check gun; go to door; check gun; put gun down at door.
SHERIFF: Open door; see robber; (robber sees sheriff); go thru door; go to window.
ROBBER: Pick up gun with right hand.
SHERIFF: Go to table.
ROBBER: Aim; fire; MISSED; aim; fire; SHERIFF HIT; blow out barrel; put gun in holster.
SHERIFF: Drop gun; sheriff dies.
ROBBER: Go to corner; pick up money with right hand; go to door; go thru door; close door. CURTAIN.
The source code for SAGA II is long gone, but enough artifacts remained that I felt confident I could write a reasonable fascimile. I had three primary sources:
- The Thinking Machine special itself, which includes logical storyboards from the project.
- An original 1960 memo describing the technical implementation of the switching and branching system.
- Safari! To my delight, the best reference turned out to be Donald Knuth’s Art of Computer Programming, Volume 2, which includes two example scripts: sheriff wins and robber wins. I was able to infer a lot about the world model from these transcripts.
I chose to write my implementation in modern Python rather than imitating the original’s global variables and weighted probabilities. That said, I still had to write a lot of code; there’s a surprising amount of hidden logic in the original, including objects which can contain or support other objects, a model of handedness, and the quantity of whisky and bullets remaining in the actors’ inventory. In the end I arrived at a weird hybrid of interactive fiction, role-playing games (each ‘actor’ rolls for initiative based on their current state), an event queue, and the kind of object-oriented code you normally only write in computer science class (“a Robber is a type of Person that is a type of Thing”). And since I had to satisfy the one rule of NaNoGenMo, I made the program generate 350 unique scenes for a total of 53,392 words.
The most important code in the simulation: shooting.
def shoot(self, target, aimed=False):
"""Shoot first, ask questions never"""
gun = self.get_if_held(Gun)
# Usually we'll aim and then fire, sometimes we'll just fire
if not aimed:
if random.randint(0, 5) > 1:
self.queue.append((self.shoot, target, True))
log.debug("%s is trying to shoot %s", self.name, target.name)
# Establish any weighting factors based on the type of person that affects
# their accuracy (e.g. the Robber is more accurate when he's drunk)
hit_weight = self.starting_hit_weight()
# As in the original, the actors get inexplicably more accurate if they're on their
# last bullet
if gun.num_bullets == 1:
hit_weight += 1
# Also defying common sense, they get more accurate if they're injured
if self.health < DEFAULT_HEALTH:
hit_weight += 1
weighted_hit_or_miss = [('miss', 3), ('nick', 3 * hit_weight), ('hit', 1 * hit_weight)]
hit_or_nick = random.choice([val for val, cnt in weighted_hit_or_miss for i in range(cnt)])
# Print the relevant 'HIT' or 'MISS' message based on the outcome...
# ...and update the target's health based on that outcome
target.health += GUN_DAMAGE[hit_or_nick]['health']
gun.num_bullets -= 1
(I asked my husband to proofread this post and he noted that the code never checks if there are bullets remaining. PATCHES WELCOME.)
The gun is in the robber’s right hand. The money is in the robber’s
left hand. The holster is on the robber. The sheriff’s gun is in the
sheriff’s right hand. The sheriff’s holster is on the sheriff. The
glass is on the table. The bottle is on the table.
ROBBER: (The robber is at the window.) Open door; go through door;
close door; go to corner; put money on corner; go to table; go to
window; check gun; go to corner; go to table; pick up the glass with
the robber’s left hand; go to window; go to corner; count money
SHERIFF: Go to window; open door; go through door
ROBBER: Fire; sheriff NICKED
SHERIFF: Close door; aim
ROBBER: Fire; MISSED
SHERIFF: Fire; MISSED
ROBBER: Fire; sheriff NICKED
SHERIFF: Aim; fire; robber NICKED
ROBBER: Aim; fire; sheriff HIT; aim; fire; sheriff NICKED
SHERIFF: Sheriff dies.
ROBBER: Blow out barrel; put gun in holster; pick up the money with the
robber’s right hand; go to table; open door; go through door; close
For fun I also tried a variant where I spawned dozens of robbers and sheriffs who all wanted to kill each other:
GREEN ROBBER: close door; go to corner
NAVY ROBBER: aim
OCHRE SHERIFF: close door
NAVY ROBBER: fire; MISSED; fire; navy sheriff NICKED; aim
NAVY SHERIFF: close door; aim
GREEN SHERIFF: close door
RED ROBBER: aim
GRAY ROBBER: aim
NAVY ROBBER: fire; navy sheriff NICKED; navy sheriff HIT
NAVY SHERIFF: navy sheriff dies.
…but I think to make more compelling I’d need to introduce some interactions that weren’t present in the original.
Write your own program to write its own novel
National Novel Generation Month runs through the end of November (but since it’s not a real contest anyway, anyone can participate any time). Inspiration can be found in the list of projects that are completed or in-progress for 2015.