Once Upon a…

I created a story-writing collaborator. You say a word, then the computer says a word, then you say a word, and so on, until together you build a narrative. When I have played this game with humans, I’ve always started with “once upon a time”. To fit that theme, all of the words the computer is choosing from are pulled from children’s bedtime stories I found online. I actually enjoy playing this with a computer more than with people. I’ll probably play with this app next time I’m lonely.

A video of me and my computer writing a story together:

You can play with it too, here: (make sure you’re wearing headphones or the computer will try listening to itself)

Here are some short stories I wrote with my computer:


I knew from the beginning that I wanted to perform a sort of collaboration with my computer, I just didn’t know what kind. I decided to try and work with Markov chains, because they’re neat and I hadn’t tried it before. I started playing around with Dan Schiffman’s Markov Chain examples, and feeding it weird stuff, like old Victorian erotica.

I started playing with one-word-at-a-time collaboration, and really liked it.

In this GIF, you can also see the hack I used where if the computer can’t think of any words to say it just ends the sentence. Whoops! I think the solution to that is feeding it more bedtime stories.

The project uses Markov chains that processed tons of bedtime stories I found online.
Here is a link to my Github repository, with all of the code for this project. I’ve also embedded some of it here.

If I were to improve this project, I would use more advanced techniques to help it come up with more complex sentences and hold onto the context better. I’d also like to implement some of the Markov chain algorithms myself so it’s less of a black box.

// Thank you to Daniel Schiffman's Markov chain examples:
var output = "Once upon a "
var myVoice;
var myRec;
function setup() {
  myVoice = new p5.Speech();
  myRec = new p5.SpeechRec();
  myRec.onResult = personSaidWord;
  myRec.continuous = true;
function clearIt() {
  var markovs = selectAll('.markov');
  for (var i = 0; i < markovs.length; i++) {
var markov;
var textinput;
var listening = true;
function initialize() {
  markov = new MarkovGeneratorWord(1, 100);
  textinput = select('#text');
  // Split it up into line breaks
  var lines = textinput.value().split('\n');
  // Feed in the lines
  for (var i = 0; i < lines.length; i++) {
    // Trim out any extra white space
  document.getElementById("output").innerHTML = output
function personSaidWord() {
  if(!myRec.resultValue) return
  var wrd = myRec.resultString.split(' ').pop();
  var nxt = markov.nextWord(wrd);
  if (nxt) {
    output += wrd + " " + nxt + " ";
  else {
    output += wrd + ". "
  document.getElementById("output").innerHTML = output
String.prototype.tokenize = function() {
  return this.split(/\s+/);
Array.prototype.choice = function() {
  var i = floor(random(this.length));
  return this[i];
function MarkovGeneratorWord(n, max) {
  // Order (or length) of each ngram
  this.n = n;
  // What is the maximum amount we will generate?
  this.max = max;
  // An object as dictionary
  // each ngram is the key, a list of possible next elements are the values
  this.ngrams = {};
  // A separate array of possible beginnings to generated text
  this.beginnings = [];
  // A function to feed in text to the markov chain
  this.feed = function(text) {
    var tokens = text.tokenize();
    // Discard this line if it's too short
    if (tokens.length < this.n) {
      return false;
    // Store the first ngram of this line
    var beginning = tokens.slice(0, this.n).join(' ');
      // Now let's go through everything and create the dictionary
    for (var i = 0; i < tokens.length - this.n; i++) {
      // Usings slice to pull out N elements from the array
      gram = tokens.slice(i, i + this.n).join(' ');
      // What's the next element in the array?
      next = tokens[i + this.n];
      // Is this a new one?
      if (!this.ngrams[gram]) {
        this.ngrams[gram] = [];
      // Add to the list
  this.nextWord = function(word) {
    var next = null
    if (this.ngrams[word]) {
      var possible_next = this.ngrams[word];
      next = possible_next.choice();
      return next
    else {
      return null

Additional thank you to for all the bedtime stories!