Judy Malloy, Editor

Nick Montfort and Stephanie Strickland
Sea and Spar Between

To introduce Sea and Spar Between, Authoring Software begins by representing the authors with their biographies and then provides an introduction to their statement, which is the literate code with which Sea and Spar Between was created.

Nick Montfort

New media writer and scholar Nick Montfort is the author of the MIT Press books Twisty Little Passages: An Approach to Interactive Fiction (2003) and Racing the Beam: The Atari Video Computer System (2009, with Ian Bogost) and co-editor of The New Media Reader. (2003, with Noah Wardrip-Fruin) He founded and blogged at Grand Text Auto, a group blog about computer narrative, games, poetry, and art, and now blogs about interactive narrative, poetic digital writing, and new media history at Post Position.

Montfort is a leading writer and programmer, whose interactive fiction authoring system, Curveship, was recently released. His work includes The Purpling; (published in The Iowa Review Web) Ten Mobile Texts; ("five stories, an aubade, an epic, a sestina, a lipogram, and a ballad for Short Message Service", published in The New River) and Fields of Dream. (with Rachel Stevens, published in Poems that Go) Additionally, his work has been exhibited at or published in FILE, Sao Paulo, Brazil; bleuOrange, Montréal, Canada; the Carmen Conde Centenary, Spain; Cauldron & Net; the Krannert Art Museum, University of Illinois at Urbana-Champaign; the Beall Center for Art + Technology, U.C. Irvine and the Digital Arts and Culture Conference.

An Associate Professor of Digital Media in the Program in Writing and Humanistic Studies at the Massachusetts Institute of Technology, Nick Montfort currently serves as the President of the Electronic Literature Organization. He is also represented on Authoring Software with Lost One, which was written with his interactive fiction system Curveship, and he wrote the story generators adapted by J. R. Carpenter that she documents on Authoring Software in her statement Excerpts from the Chronicles of Pookie and JR.

To find out more, about Nick Montfort, visit his home page at http://nickm.com

Stephanie Strickland

Stephanie Strickland is a print and new media poet whose work has both advanced the creation of born digital poetry and creatively explored the intersections between print and digital. She is the author of V, which was one of the first works of literature to appear simultaneously both in print, V: WaveSon.nets/Losing L'una, Penguin, 2002, and on the Web, V: Vniverse. Her cyberpoem True North was published in print by the University of Notre Dame Press and as a work of literature on disk by Eastgate. True North was awarded the Ernest Sandeen Poetry Prize, the Alice Fay di Castagnola Award from the Poetry Society of America, and the Salt Hill hypertext prize. V: WaveSon.nets/Losing L'una also received the Alice Fay di Castagnola Award.

Strickland's digital works include V: Vniverse, with Cynthia Lawson Jaramillo; Errand Upon Which We Came, with M.D. Coverley; Ballad of Sand and Harry Soot; and slippingglimpse, with Cynthia Lawson Jaramillo and Paul Ryan. Print scores for the Ballad and slippingglimpse appear in her book, Zone : Zero, which was published by Ahsahta in 2008. slippingglimpse was introduced at E-Poetry in Paris and featured at E-poetry in Barcelona. Her poetry has also been published/exhibited by The Paris Review, Grand Street, New American Writing, Fence, Black Clock, Zoland Poetry, Vlak, The Poetry Foundation, The Iowa Review Web, Cauldron & Net, Drunken Boat, Hyperrhiz: New Media Cultures, Word Circuits Gallery, Blue Moon, The New River, Furtherfield, and Poets for Living Waters.

Her essays about electronic literature appear in Leonardo Electronic Almanac, ebr, Isotope, and volumes from MIT Press and Intellect Press Ltd.

A member of the Board of Directors of the Electronic Literature Organization, she edited the first volume of the Electronic Literature Collection with Kate Hayles, Nick Montfort, and Scott Rettberg. She also co-edited an issue of The Iowa Review Web.

Stephanie Strickland has taught hypermedia literature as part of experimental poetry at many colleges and universities, most recently in the PhD poetry program at the University of Utah. She lives in New York City.

In addition to Sea and Spar Between, she is represented on Authoring Software with V: Vniverse and slippingglimpse. To find out more about Stephanie Strickland, visit her home page at http://stephaniestrickland.com

Introduction to the Commented Code for Sea and Spar Between

A core addition to the exploration of how language in code can create a greater understanding of the work and at the same time have literary qualities, Montfort and Strickland's statement on Authoring Software is the commented code for the JavaScript program that implements Sea and Spar Between, which was first published on Dear Navigator at

In general, commented code incorporates statements in the program that are not executable, i.e. they do not contribute to the computer's running of the program but rather explain or describe the intentions of the programmer. The idea of "literate" programming -- which incorporates textual, sometimes poetic narrative in order to contribute both to code understanding and literary analysis -- has been of recent interest in the digital humanities community. Here, the commented code serves as a statement that not only documents the artists' intentions and but also invites the reader to understand how they were implemented by the program.

Using the poetry of Emily Dickinson (1830-1886) and Moby Dick (1851) by Herman Melville (1819-1891) to analyze congruences of language and style in approximately the same era, the work is a cyberliterary approach to 19th century American literature, as well as, in its unexpected, yet at times evocative combination of their works, a look at different 19th century approaches to nature and the relationship of humans to nature. Indeed, in the commented code the authors describe Sea and Spar Between as "a poetry generator which defines a space of language populated by a number of stanzas comparable to the number of fish in the sea, around 225 trillion."

From a practical point of view, their commented code includes the requirements for running the program and user instructions. From the point of view of literary analysis, the commented code lists arrays of the words that the computer is instructed to find and parse, and it documents the authors' intentions. "The words in Sea and Spar Between come from Emily Dickinson's poems and Herman Melville's Moby Dick," Montfort and Strickland explain on Dear Navigator. "Certain compound words (kennings) are assembled from words used frequently by one or both. Sea and Spar Between was composed using the basic digital technique of counting, which allows for the quantitative analysis of literary texts. We considered, for instance, words that were used by only one of the two authors. We also looked at certain easily enumerated, characteristic categories of words, such as those ending in 'less.'"

As a whole, the commented code for Sea and Spar Between is of interest not only to writers and students who want to learn how computers can be programmed to generate poetry and/or analyze literature but also to to programmers who want to explore literary approaches to the commenting of code.

Authoring Software


Writers and Artists
Talk about Their Work
and the Software They
use to Create Their Work

Mark Amerika

Mark Bernstein
__Interview wirh Mark Bernstein

Bill Bly

Jay Bushman

J. R. Carpenter
__ The Broadside of a Yarn
__ Entre Ville
__ Chronicles of Pookie and JR

M.D. Coverley
__ Egypt: The Book of
Going Forth by Day

__ Tin Towns

Caitlin Fisher

Chris Funkhouser

Dene Grigar
__ 24-Hr. Micro-Elit
__ Fallow Field

Fox Harrell

William Harris

Megan Heyward

Adriene Jenik

Antoinette LaFarge

Deena Larsen

Judy Malloy

Mark C. Marino

María Mencía

Nick Montfort
__Nick Montfort and
Stephanie Strickland
Sea and Spar Between

Judd Morrissey

Stuart Moulthrop
__ Interview with
Stuart Moulthrop

Karen O'Rourke

Regina Pinto

Andrew Plotkin

Sonya Rapoport:
__Interview with
Sonya Rapoport

Aaron Reed

Scott Rettberg

Stephanie Strickland
__Nick Montfort and Stephanie Strickland
Sea and Spar Between

Eugenio Tisselli

Dan Waber

Nick Montfort and Stephanie Strickland: Sea and Spar Between

/  Sea and Spar Between
//  by Nick Montfort and Stephanie Strickland
//  a poetry generator which defines a space of language 
//  populated by a number of stanzas comparable to the number 
//  of fish in the sea, around 225 trillion
//  index.html, reading.html, and style.css are part of Sea and Spar Between
//  canvastext.js is a required, public domain file by Jim Studt
//  Use the version of canvastext.js that accompanies this file, as
//  minor changes have been made to it.
//  Copyright (c) 2010, Nick Montfort and Stephanie Strickland
//  All rights reserved.
//  Redistribution and use in source and binary forms, with or without
//  modification, are permitted provided that the following conditions are 
//  met:
//    * Redistributions of source code must retain the above copyright notice,
//  this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above copyright 
//  notice, this list of conditions and the following disclaimer in the
//  documentation and/or other materials provided with the distribution.
//    * Neither the names of the copyright holders nor the names of any other
//  contributor may be used to endorse or promote products derived from this
//  software without specific prior written permission.

// The following two lines are used for checking the code with jslint:
/*jslint browser: true*/
/*global window unescape CanvasTextFunctions*/

var mouseX = 0, mouseY = 0;

// The following two lines set the initial lattice coordinates. This is the
// only code that involves randomness. If you prefer a completely
// deterministic experience, you can add something of the form "?X,Y" 
// (for example, "?0,0" or "?12345,22680") to the URL to start Sea and Spar 
// at the same specified place:
var baseI = Math.round(Math.random() * 14992383);
var baseJ = Math.round(Math.random() * 14992383);

var canvas, context;
var fontSize = 12;
var lineHeight, column, stanzaHeight, spacingX, spacingY;

// Data: Words and short phrases that are combined by the generator --

var shortPhrase = ['circle on', 'dash on', 'let them', 'listen now', 'loop on', 'oh time', 'plunge on', 'reel on', 'roll on', 'run on', 'spool on', 'steady', 'swerve me?', 'turn on', 'wheel on', 'whirl on', 'you--too--', 'fast-fish', 'loose-fish']; // Common Dickinson nouns grouped by number of syllables:
var dickinsonNoun = [ ['air', 'art', 'care', 'door', 'dust', 'each', 'ear', 'earth', 'fair', 'faith', 'fear', 'friend', 'gold', 'grace', 'grass', 'grave', 'hand', 'hill', 'house', 'joy', 'keep', 'leg', 'might', 'mind', 'morn', 'name', 'need', 'noon', 'pain', 'place', 'play', 'rest', 'rose', 'show', 'sight', 'sky', 'snow', 'star', 'thought', 'tree', 'well', 'wind', 'world', 'year'], ['again', 'alone', 'better', 'beyond', 'delight', 'dying', 'easy', 'enough', 'ever', 'father', 'flower', 'further', 'himself', 'human', 'morning', 'myself', 'power', 'purple', 'single', 'spirit', 'today'], ['another', 'paradise'], ['eternity'], ['immortality'] ]; var courseStart = ['fix upon the ', 'cut to fit the ', 'how to withstand the ']; // The following syllables are combined into compound words: var dickinsonSyllable = ['bard', 'bead', 'bee', 'bin', 'bliss', 'blot', 'blur', 'buzz', 'curl', 'dirt', 'disk', 'doll', 'drum', 'fern', 'film', 'folk', 'germ', 'hive', 'hood', 'husk', 'jay', 'pink', 'plot', 'spun', 'toll', 'web']; var melvilleSyllable = ['ash', 'bag', 'buck', 'bull', 'bunk', 'cane', 'chap', 'chop', 'clam', 'cock', 'cone', 'dash', 'dock', 'edge', 'eel', 'fin', 'goat', 'hag', 'hawk', 'hook', 'hoop', 'horn', 'howl', 'iron', 'jack', 'jaw', 'kick', 'kin', 'lime', 'loon', 'lurk', 'milk', 'net', 'pike', 'rag', 'rail', 'ram', 'sack', 'salt', 'tool']; var syllable = dickinsonSyllable; syllable.concat(melvilleSyllable); syllable.sort(); // Dickinson's poems include many words ending in "less," such as "artless." // Some of their stems (such as "art") follow, grouped by number of syllables. var dickinsonLessLess = [ ['art', 'base', 'blame', 'crumb', 'cure', 'date', 'death', 'drought', 'fail', 'flesh', 'floor', 'foot', 'frame', 'fruit', 'goal', 'grasp', 'guile', 'guilt', 'hue', 'key', 'league', 'list', 'need', 'note', 'pang', 'pause', 'phrase', 'pier', 'plash', 'price', 'shame', 'shape', 'sight', 'sound', 'star', 'stem', 'stint', 'stir', 'stop', 'swerve', 'tale', 'taste', 'thread', 'worth'], ['arrest', 'blanket', 'concern', 'costume', 'cypher', 'degree', 'desire', 'dower', 'efface', 'enchant', 'escape', 'fashion', 'flavor', 'honor', 'kinsman', 'marrow', 'perceive', 'perturb', 'plummet', 'postpone', 'recall', 'record', 'reduce', 'repeal', 'report', 'retrieve', 'tenant'], ['latitude', 'retriever'] ]; // Makes one long list of all "less" stems, combining stems of any number of // syllables: var dickinsonFlatLessLess = dickinsonLessLess[0]; dickinsonFlatLessLess.concat(dickinsonLessLess[1], dickinsonLessLess[2]); dickinsonFlatLessLess.sort(); // Verbs that can suggest a positive mood: var upVerb = ['bask', 'chime', 'dance', 'go', 'leave', 'move', 'rise', 'sing', 'speak', 'step', 'turn', 'walk']; var butBeginning = ['but', 'for', 'then']; var butEnding = ['earth', 'sea', 'sky', 'sun']; var threeToFiveSyllable = dickinsonNoun[2]; threeToFiveSyllable.concat(dickinsonNoun[3] + dickinsonNoun[4] + dickinsonLessLess[2]); var twoSyllable = dickinsonNoun[1]; twoSyllable.concat(dickinsonLessLess[1]); var nailedEnding = ['coffin', 'deck', 'desk', 'groove', 'mast', 'spar', 'pole', 'plank', 'rail', 'room', 'sash']; // Functions: These generate each type of line, assemble stanzas, draw the // lattice of stanzas in the browser window, and handle input and other events. // First line functions: function shortLine(n) { return shortPhrase[n % shortPhrase.length]; } function oneNounLine(n) { var a, b, c, d = n % dickinsonNoun[0].length; n = Math.floor(n / dickinsonNoun[0].length); c = n % dickinsonNoun[0].length; n = Math.floor(n / dickinsonNoun[0].length); b = n % dickinsonNoun[0].length; n = Math.floor(n / dickinsonNoun[0].length); a = n % dickinsonNoun[0].length; return 'one ' + dickinsonNoun[0][a] + ' one ' + dickinsonNoun[0][b] + ' one ' + dickinsonNoun[0][c] + ' one ' + dickinsonNoun[0][d]; } function compoundCourseLine(n) { var a, b, c = n % syllable.length; n = Math.floor(n / syllable.length); b = n % syllable.length; n = Math.floor(n / syllable.length); a = n % courseStart.length; return courseStart[a] + syllable[b] + syllable[c] + ' course'; } function firstLine(n) // The first line of a pair is one of the three types above. { var m = Math.floor(n / 4); if (n % 4 < 2) { return shortLine(m); } if (n % 4 === 2) { return oneNounLine(m); } return compoundCourseLine(m); } // Second line functions: function riseAndGoLine(n) { var a, b, c = n % upVerb.length, dash = ''; n = Math.floor(n / upVerb.length); b = n % upVerb.length; n = Math.floor(n / upVerb.length); a = n % dickinsonFlatLessLess.length; if (dickinsonFlatLessLess[a] in dickinsonLessLess[0]) { dash = ' --'; } return dickinsonFlatLessLess[a] + 'less ' + upVerb[b] + ' and ' + upVerb[c] + dash; } function butLine(n) { var a, b, c = n % butEnding.length; n = Math.floor(n / butEnding.length); b = n % dickinsonFlatLessLess.length; n = Math.floor(n / dickinsonFlatLessLess.length); a = n % butBeginning.length; return butBeginning[a] + ' ' + dickinsonFlatLessLess[b] + 'less is the ' + butEnding[c]; } function exclaimLine(n) { var a, b = n % twoSyllable.length; n = Math.floor(n / twoSyllable.length); a = n % threeToFiveSyllable.length; return threeToFiveSyllable[a] + '! ' + twoSyllable[b] + '!'; } function nailedLine(n) { var a = n % nailedEnding.length; return 'nailed to the ' + nailedEnding[a]; } function secondLine(n) // The second line of a pair is one of the four types above. { var m = Math.floor(n / 4); if (n % 4 === 0) { return riseAndGoLine(m); } if (n % 4 === 1) { return butLine(m); } if (n % 4 === 2) { return exclaimLine(m); } return nailedLine(m); } // Functions related to drawing text and handling events: function drawPair(i, j, x, y) // Displays two lines in the browser window. The drawing of these is done // by calling the drawText method (in canvastext.js) using the graphical // coordinates x, y. The lines themselves are determined by the functions // firstLine and secondLine (above), which are given the lattice // coordinates i, j. { y += lineHeight; context.drawText('sans', fontSize, x, y, firstLine(i + j + 1)); y += lineHeight; context.drawText('sans', fontSize, x, y, ' ' + secondLine(Math.abs(i - j) + 1)); } function readCoords() // Parses the coordinates in the URL (if there are any) and uses those as // the base lattice coordinates. { var params = window.location.search, a; if (params.substring(0, 1) === '?') { params = params.substring(1); } params = params.split(','); for (a = 0; a < params.length; a += 1) { params[a] = unescape(params[a]); } return params; } function drawCoords(i, j, x, y) // Displays the lattice coordinates of the central stanza above that stanza. { var stroke = context.strokeStyle; context.strokeStyle = "rgba(255,255,255,1.2)"; context.drawText('sans', 12, x, y, i + ' : ' + j); context.strokeStyle = stroke; } function canonical(value) // Converts any integer to a "canonical" lattice coordinate -- a value // at least 0 and at most 14992383 -- to handle negative and very large // inputs. This makes the "sea" a torus, looping in both the right/left // direction and in the up/down direction. // The large number of lines of the form "one _ one _ one _ one _" // determined the number of possible values in each direction, 14992384. { value = value % 14992384; if (value < 0) { value = value + 14992384; } return value; } function drawLattice(startI, startJ) // The main function that draws the entire visible portion of the lattice // in the browser window. { var startX, startY, i, j, x, y; // Draw the background: context.fillStyle = "rgba(199,220,254,1)"; context.fillRect(0, 0, canvas.width, canvas.height); startX = (canvas.width - column) / 2; // X position of central stanza. startY = (canvas.height - stanzaHeight) / 2; // Y position. // Draw the coordinate of that stanza: drawCoords(canonical(baseI + startI), canonical(baseJ + startJ), startX, startY); // At this point startX and startY indicate where the central stanza // will be drawn. They need to be adjusted if the window is large // enough or font small enough to accomodate other stanzas. while (startX > 0) { // Until we are at 0 or off the page to the left, startX -= spacingX; // step back one space ... startI -= 1; // so we can draw the previous, (i-1)th stanza } // to the left. while (startY > 0) { // Until we are at 0 or off the top of the page, startY -= spacingY; // step up one stanza ... startJ -= 1; // so we can draw the previous stanza with } // the pair of lines (j-2) and (j-1) up above. i = canonical(baseI + startI); // i now holds the correct first lattice coordinate for the upper left // stanza. for (x = startX; x <= canvas.width; x += spacingX) { j = canonical((baseJ + startJ) * 2); // The multiplication by two is so that the lattice moves up and down // two pairs (one stanza) at a time. If this weren't done the breaks // between stanzas would not be maintained. for (y = startY; y <= canvas.height; y += spacingY - lineHeight * 3) { // A stanza is drawn by drawing one pair of lines, then another. drawPair(i, j, x, y, lineHeight); j = canonical(j + 1); y += lineHeight * 3; drawPair(i, j, x, y, lineHeight); j = canonical(j + 1); } i = canonical(i + 1); } } function changeFontSize(delta) // Change the font size by adding delta, determine the new spacing, redraw. { fontSize += delta; fontSize = Math.max(4, fontSize); lineHeight = context.fontAscent('sans', fontSize) + context.fontDescent('sans', fontSize); column = fontSize * 22; stanzaHeight = lineHeight * 5; spacingX = fontSize * 38; spacingY = stanzaHeight * 2; drawLattice(parseInt(mouseX / 3, 10), parseInt(mouseY / 3, 10)); } function updateWheel(e) // Make the appropriate change if the mouse wheel has moved. { var evt, wheel; evt = window.event || e; // Select available event object. wheel = evt.detail ? evt.detail * (-120) : evt.wheelDelta; if (wheel > 0) { changeFontSize(1); } else { changeFontSize(-1); } } function markStanza() // Place the coordinates of the central stanza in the box. { var textInput = document.getElementById("coords"); textInput.value = canonical(baseI + parseInt(mouseX / 3, 10)) + ","; textInput.value += canonical(baseJ + parseInt(mouseY / 3, 10)); } function keyDown(e) // Handle key presses. { var key = String.fromCharCode(e.keyCode); if (key === "a" || key === "A") { changeFontSize(1); } else if (key === "z" || key === "Z") { changeFontSize(-1); } else if (key === ' ') { markStanza(); } // The rest of these if statements handle the arrow keys. else if (e.keyCode === 37) { baseI = canonical(baseI - 1); drawLattice(parseInt(mouseX / 3, 10), parseInt(mouseY / 3, 10)); } else if (e.keyCode === 38) { baseJ = canonical(baseJ - 1); drawLattice(parseInt(mouseX / 3, 10), parseInt(mouseY / 3, 10)); } else if (e.keyCode === 39) { baseI = canonical(baseI + 1); drawLattice(parseInt(mouseX / 3, 10), parseInt(mouseY / 3, 10)); } else if (e.keyCode === 40) { baseJ = canonical(baseJ + 1); drawLattice(parseInt(mouseX / 3, 10), parseInt(mouseY / 3, 10)); } } function mouseMove(e) // Handle mouse movement. { mouseX = e.clientX; mouseY = e.clientY; drawLattice(parseInt(mouseX / 3, 10), parseInt(mouseY / 3, 10)); return false; } function mouseClick(e) // Shift to a new region if a click happened near an edge, // redraw in any case. { if (mouseX > canvas.width * 2 / 3) { baseI += parseInt(canvas.width / 3, 10); } else if (mouseX < canvas.width / 3) { baseI -= parseInt(canvas.width / 3, 10); } if (mouseY > canvas.height * 2 / 3) { baseJ += parseInt(canvas.height / 3, 10); } else if (mouseY < canvas.height / 3) { baseJ -= parseInt(canvas.height / 3, 10); } drawLattice(parseInt(mouseX / 3, 10), parseInt(mouseY / 3, 10)); return false; } function resizeCanvas(e) // When the browser window is resized, resize the canvas. { var div = document.getElementsByTagName('div')[0]; canvas.width = div.scrollWidth; canvas.height = div.scrollHeight; context.strokeStyle = "rgba(0,0,128,0.75)"; drawLattice(0, 0); } function setBase(coords) // Sets the base lattice coordinates if the new array of strings is valid. // Otherwise, the existing baseI and baseJ values remain. { newI = parseInt(coords[0], 10); newJ = parseInt(coords[1], 10); if (!isNaN(newI) && !isNaN(newJ)) { baseI = newI; baseJ = newJ; } } function setup() // Runs when the page is loaded. Initializes the canvas, event listeners, etc. { var div, newI, newJ, mouseWheelEvent, params = readCoords(); if (params.length === 2) { setBase(params); } canvas = document.getElementsByTagName('canvas')[0]; if (!canvas.getContext) { return; } // Add event listeners for mouse movement, mouse click, key down: canvas.onmousemove = mouseMove; canvas.onclick = mouseClick; document.onkeydown = keyDown; mouseWheelEvent = (/Firefox/i.test(navigator.userAgent)) ? "DOMMouseScroll" : "mousewheel"; if (document.attachEvent) // For IE (and Opera depending on user setting). { document.attachEvent("on" + mouseWheelEvent, updateWheel); } else if (document.addEventListener) // For WC3 browsers. { document.addEventListener(mouseWheelEvent, updateWheel, false); } // Add the text functions to the context: context = canvas.getContext('2d'); CanvasTextFunctions.enable(context); changeFontSize(0); window.onresize = resizeCanvas; resizeCanvas(null); markStanza(); } function go() // Called when someone has pressed "enter" in the navigation box; updates coordinates. { var textInput, coordPair, URL; textInput = document.getElementById("coords"); coordPair = textInput.value; coordPair = coordPair.split(' ').join(''); coordPair = coordPair.split(':').join(','); setBase(coordPair.split(',')); drawLattice(0, 0); }

The commented code for Sea and Spar Between is also available on Dear Navigator at