/*-----------------------------------------------------------------------------
|
| NAME
|
|     gameOfLife.js
|
| DESCRIPTION
|
|     A Javascript version of the cellular automata Game of Life by
|     Cambridge University mathematician John Horton Conway.
|
| METHOD
|
|     Life is played on a two dimensional game board which is partitioned
|     into cells.  Cells may be occupied with counters.
|
|     By default, we use these three (J.H. Conway) rules:
|
|     1.  BIRTH.  Each empty cell adjacent to exactly 3 neighbors will have a
|         birth in the next generation.  Otherwise, the cell remains empty.
|
|     2.  DEATH.  Each occupied cell with exactly 0 or 1 neighbors dies of
|         isolation and loneliness.  Each occupied cell with 4 or more
|         neighbors dies of overpopulation.
|
|     3.  SURVIVAL.  Each occupied cell with exactly 2 or 3 neighbors survives
|         to the next generation.
|
|     All births and deaths occur simultaneously.  Applying all rules to an
|     entire board creates a new generation.  Ultimately, the society dies
|     out, reaches some steady state (constant or oscillating) or the user
|     gets bored.
|
|     The ideal game board is infinite.  For this program, we wrap around
|     at the boundaries of the board so that it is topologically a torus.
|
|     See Mathematical Games, SCIENTIFIC AMERICAN, Vol. 223, No. 4,
|     October 1970, pgs. 120-123 for a description of the game.
|
| LEGAL
|
|     Javascript Game Of Life Version 2.1 -
|     A Javascript version of the cellular automata Game of Life by J. H. Conway.
|     Copyright (C) 2010-2017 by Sean Erik O'Connor.  All Rights Reserved.
|
|     This program is free software: you can redistribute it and/or modify
|     it under the terms of the GNU General Public License as published by
|     the Free Software Foundation, either version 3 of the License, or
|     (at your option) any later version.
|
|     This program is distributed in the hope that it will be useful,
|     but WITHOUT ANY WARRANTY; without even the implied warranty of
|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
|     GNU General Public License for more details.
|
|     You should have received a copy of the GNU General Public License
|     along with this program.  If not, see <http://www.gnu.org/licenses/>.
|
|     The author's address is artificer!AT!seanerikoconnor!DOT!freeservers!DOT!com
|     with !DOT! replaced by . and the !AT! replaced by @
|
-----------------------------------------------------------------------------*/

//===================================================== Constants and Enumerated Types ===============================================

const GameSettings =
{
    MaxFileLineLength  : 80,
    MaxNumCommentLines : 22,
    MaximumAge         : 10000,
    UpdateIntervalMs   : 50,
    GameBoardNumRows   : 100,
    GameBoardNumCols   : 100,
    OldAge             : 50
} ;

//  Maximum values for game board dimensions.

//  Define some "enum" types as class variables, e.g. if (x == Occupancy.Empty) ...
const Occupancy =
{
    Indeterminate : -1, // No occupancy state at the beginning.
    Empty         :  0, // Cell is empty.
    Occupied      :  1  // Cell has a counter in it.
} ;

const State =
{
    Indeterminate : -1, // No state at the beginning.
    Survival :       0, // Survives;  no change from last time.
    Birth :          1, // Empty cell has a birth.
    Death :          2  // Occupied cell dies.

} ;

const DebugPrintOption =
{
    GameBoard : 0,
    Neighbors : 1,
    States    : 2
} ;

const lifePattern =
[

"#Life 1.05\n" +
"#D p30 glider gun (the Original)\n" +
"#D This is made of two of a pattern\n" +
"#D known as the \"queen bee\", which\n" +
"#D sometimes occurs naturally,\n" +
"#D whose debris can be deleted on\n" +
"#D the sides by blocks or eaters.\n" +
"#D But a collision in the center\n" +
"#D can, as seen here, miraculously \n" +
"#D form a glider. Just one of these\n" +
"#D moving back and forth is called\n" +
"#D piston (see the p30 in OSCSPN2).\n" +
"#D  I added an eater at the bottom right.\n" +
"#N\n" +
"#P 4 -5\n" +
"....*\n" +
".****\n" +
"****\n" +
"*..*\n" +
"****\n" +
".****\n" +
"....*\n" +
"#P 13 -4\n" +
"*\n" +
"*\n" +
"#P -6 -3\n" +
"..*\n" +
".*.*\n" +
"*...**\n" +
"*...**\n" +
"*...**\n" +
".*.*\n" +
"..*\n" +
"#P 17 -2\n" +
"**\n" +
"**\n" +
"#P -17 0\n" +
"**\n" +
"**\n" +
"#P 42 40\n" +
"**\n" +
"*.*\n" +
"..*\n" +
"..**\n" +
"",

"#Life 1.05\n" +
"#D In February 1994, Nathan Thompson reported several interesting objects\n" +
"#D that he found in a cellular automaton closely related to Conway's Life.\n" +
"#D The reason that HighLife has been investigated so much is because of the\n" +
"#D object known as the 'replicator'.  This amazing object starts with only\n" +
"#D six live cells as shown in figure 2.  See 'HighLife - An Interesting\n" +
"#D Variant of Life (part 1/3)', by David I. Bell, dbell@canb.auug.org.au,\n" +
"" +
"#D 7 May 1994.\n" +
"" +
"#R 23/36\n" +
"" +
"#P -2 -2\n" +
"" +
".***\n" +
"" +
"*...\n" +
"" +
"*...\n" +
"" +
"*...\n" +
"" +
"",

"#Life 1.05\n" +
"" +
"#D Name: Crab\n" +
"" +
"#D Author: Jason Summers\n" +
"" +
"#D The smallest known diagonal spaceship other than the glider. It was discovere\n" +
"" +
"#D d in September 2000.\n" +
"" +
"#D www.conwaylife.com/wiki/index.php?title=Crab\n" +
"#N\n" +
"#P -6 -6\n" +
"........**\n" +
".......**\n" +
".........*\n" +
"...........**\n" +
"..........*\n" +
".\n" +
".........*..*\n" +
".**.....**\n" +
"**.....*\n" +
"..*....*.*\n" +
"....**..*\n" +
"....**\n" +
"",

"#Life 1.05\n" +
"#D Name: Schick engine\n" +
"#D Author: Paul Schick\n" +
"#D An orthogonal c/2 tagalong found in 1972.\n" +
"#D www.conwaylife.com/wiki/index.php?title=Schick_engine\n" +
"#N\n" +
"#P -11 -4\n" +
"****\n" +
"*...*\n" +
"*\n" +
".*..*\n" +
"#P -5 -2\n" +
"..*\n" +
".*******\n" +
"**.***..*\n" +
".*******\n" +
"..*\n" +
"#P -7 -1\n" +
"*\n" +
"#P -11 1\n" +
".*..*\n" +
"*\n" +
"*...*\n" +
"****\n" +
"#P -7 1\n" +
"*\n" +
"",

"#Life 1.05\n" +
"#D Traffic circle from http://www.radicaleye.com/lifepage/picgloss/picgloss.html\n" +
"#N\n" +
"#P -25 -25\n" +
"......................**....**..........................\n" +
"......................*.*..*.*...................\n" +
"........................*..*.....................\n" +
".......................*....*....................\n" +
".......................*....*....................\n" +
".......................*....*....................\n" +
".........................**.....**...............\n" +
"................................***..............\n" +
"................................**.*.............\n" +
"..................................*.*............\n" +
"..........................***....*..*............\n" +
"..................................**.............\n" +
"..........**............*.....*..................\n" +
".........*..*...........*.....*..................\n" +
".......*..*.*...........*.....*..................\n" +
"...........*.....................................\n" +
".......*.**...............***....................\n" +
"........*.....*..................................\n" +
"..............*..................................\n" +
".**...........*..................................\n" +
".*..***..........................................\n" +
"..**......***...***............................**\n" +
".......*...................................***..*\n" +
".......*......*...............................**.\n" +
"..**..........*........*..................*......\n" +
".*..***.......*......**.**............*...*......\n" +
".**....................*............**.**.....**.\n" +
"......................................*....***..*\n" +
"...............................................**\n" +
".................................................\n" +
".......................................*.*.......\n" +
".....................***..................*......\n" +
"......................................*..*.......\n" +
"...................*.....*...........*.*.*.......\n" +
"...................*.....*...........*..*........\n" +
"...................*.....*............**.........\n" +
"..............**.................................\n" +
".............*..*....***.........................\n" +
".............*.*.*...............................\n" +
"..............*.***..............................\n" +
"................***..............................\n" +
".......................**........................\n" +
".....................*....*......................\n" +
".....................*....*......................\n" +
".....................*....*......................\n" +
"......................*..*.......................\n" +
"....................*.*..*.*.....................\n" +
"....................**....**.....................\n" +
"",

"#Life 1.05\n" +
"#D Period 96 replicator based glider gun by David Bell.\n" +
"#D --- The smallest known glider gun based on replicators.\n" +
"#D A block perturbs the replicator to produce the glider,\n" +
"#D while a period 2 clock oscillator prevents a spark \n" +
"#D from being formed that would modify the block.  \n" +
"#D One glider is shown where it was just created.\n" +
"#D From HighLife - An Interesting Variant of Life \n" +
"#D (part 1/3) by David I. Bell, dbell@canb.auug.org.au\n" +
"#D 7 May 1994\n" +
"#R 23/36\n" +
"#P -18 -14\n" +
"**...................................\n" +
"**...................................\n" +
"..............*......................\n.............***.....................\n" +
"............**.**....................\n" +
"...........**.**.....................\n" +
"..........**.**......................\n" +
"...........***.......................\n" +
"............*........................\n" +
".....................................\n" +
".....................................\n" +
".....................................\n" +
".....................................\n" +
".....................................\n" +
".....................................\n" +
".....................................\n" +
".....................................\n" +
".....................................\n" +
".........................**......**..\n" +
"........................*.*......**..\n" +
"..........................*..........\n" +
".....................................\n" +
"...................................*.\n" +
".................................*.*.\n" +
"..................................*.*\n" +
".........................**.......*..\n" +
".........................**..........\n" +
"#P -5 15\n" +
"..**\n..*\n" +
"*.*\n" +
"**\n" +
""
] ;

function min( a, b )
{
    if (a < b)
        return a ;
    else
        return b ;
}

function max( a, b )
{
    if (a > b)
        return a ;
    else
        return b ;
}
//========================================================= Global variables =========================================================

// Global callback functions.
var changeRules           = undefined ;
var updateRulesView       = undefined ;

var singleStepGame        = undefined ;
var clearGame             = undefined ;

var loadSampleLifePattern = undefined ;
var readGameFromClipboard = undefined ;
var writeGameToClipboard  = undefined ;

function runStopGame()
{
    if (this.timer == undefined)
        this.timer = setInterval( singleStepGame, GameSettings.UpdateIntervalMs ) ;
    else
    {
        clearInterval( timer ) ;
        timer = undefined ;
    }
}

function TimeSettings()
{
   this.period = 0 ;
} ;

var timer = undefined ;

//===================================================== Game Board ===================================================================

function Cell( n, o, p, s, a, l, f, e )
{
   this.numberOfNeighbors = n ;  // Number of neighbors for a cell.
   this.occupied = o;            // Flag if cell is occupied or empty.
   this.occupiedPreviously = p ; // Last occupation state.
   this.state = s ;              // Birth or death from last time?
   this.age = a;                 // Age of counter in the cell.

   //  For traversal only.
   this.label = l;               // Label for a cell.
   this.father = f ;             // Father of this cell.
   this.edge = e;                // List of edges.
} ;

// The game board for life.
function GameBoard( canvasElement, gameStateElement, numRows, numCols )
{
    // Access the canvas from the game board.
    this.canvasElement    = canvasElement ;
    this.widthPixels      = canvasElement.width ;
    this.heightPixels     = canvasElement.height ;
    this.graphicsContext  = canvasElement.getContext( "2d" ) ;

    // Access the game state display area.
    this.gameStateElement = gameStateElement ;

    // Game board global values.
    this.numRows    = numRows ;
    this.numCols    = numCols ;
    this.population = 0 ;
    this.generation = 0 ;

    // Normal Conway rules:  a counter survives if it has 2 or 3 neighbors else dies of loneliness;
    // an empty cell with 3 neighbors has a birth.
    this.rules = { survival : undefined, birth: undefined } ;
    this.rules.survival = { numRules : 2, numNeighbors : [ 2, 3, , , , , , , ] } ;
    this.rules.birth    = { numRules : 1, numNeighbors : [ 3,  , , , , , , , ] } ;

    // Attach functions as a class methods.
    this.drawLifeGrid          = drawLifeGrid ;
    this.drawCell              = drawCell ;
    this.getCursorPosition     = getCursorPosition ;
    this.canvasToCellCoord     = canvasToCellCoord ;
    this.cellToCanvasCoord     = cellToCanvasCoord ;
    this.updateGameBoard       = updateGameBoard ;
    this.updateView            = updateView ;
    this.clearGameState        = clearGameState ;
    this.birthAndDeath         = birthAndDeath ;
    this.countNeighbors        = countNeighbors ;
    this.boundaryNeighborCount = boundaryNeighborCount ;

    // Generate the game board array of cells.
    this.cell = new Array( this.numRows ) ;
    for (row = 0 ;  row < this.numRows ;  ++row)
        this.cell[ row ] = new Array( this.numCols ) ;  //  Game board array of cells.

    // Fill cells with default values.
    for (col = 0 ;  col < this.numCols ;  ++col)
    {
        for (row = 0 ;  row < this.numRows ;  ++row)
        {
            this.cell[ row ][ col ] =
                new Cell( 0,                       // No neighbors.
                          Occupancy.Empty,         // Not occupied.
                          Occupancy.Indeterminate, // Previous occupation.
                          State.Indeterminate,     // No state.
                          0,                       // Cell is new.
                         -1,                       // Cell is unlabelled.
                         -1,                       // Cell has no father.
                         -1 ) ;                    // Edges are unmarked.
        }
    }

    // Comments.
    this.comment         = new Array( GameSettings.MaxNumCommentLines ) ;
    this.numCommentLines = GameSettings.MaxNumCommentLines ;

    //  Leave space for blank comment lines.
    for (row = 0 ;  row < GameSettings.MaxNumCommentLines ;  ++row)
        this.comment[ row ] = "#D" ;

    return ;
}

// A rule for the number of neighbors required for survivals or births.
function Rules( numRules, neighborCounts )
{
    // Allow up to 9 rules since we can have 0-8 neighbors.
    this.numNeighbors = new Array( 9 ) ;

    // Fill all rules, leaving other undefined.
    for (var i = 0 ;  i < neighborCounts.length ;  ++i)
        this.numNeighbors[ i ] = neighborCounts[ i ] ;

    this.numRules = numRules ;
} ;

// Change the rules.  flag = true for survivals, false for births.
function make_changeRules( gameBoard, survivalRulesElement, birthRulesElement )
{
    return function( flag )
    {
        if (flag)
            rulesElement = survivalRulesElement ;
        else
            rulesElement = birthRulesElement ;

        var numNeighbors = [] ;
        var numRules ;

        // Iterate over the whole group of checkboxes for number of neighbors for either survivals or births.
        for (var boxNum = 0, numBoxes = rulesElement.length, numRules = 0 ;  boxNum < numBoxes ;  ++boxNum)
        {
            if (rulesElement[ boxNum ].checked)
            {
                // e.g. box 0 is checked so number of neighbors is 1.
                numNeighbors[ numRules++ ] = boxNum + 1 ;
            }
        }

        var rules = new Rules( numRules, numNeighbors ) ;

        if (flag)
            gameBoard.rules.survival = rules ;
        else
            gameBoard.rules.birth = rules ;

        gameBoard.updateView() ;
    }
}

function make_loadSampleLifePattern( gameBoard, lifePatternsElement )
{
    return function()
    {
        // Iterate over the whole group of checkboxes for number of neighbors for either survivals or births.
        for (var boxNum = 0, numBoxes = lifePatternsElement.length ;  boxNum < numBoxes ;  ++boxNum)
        {
            if (lifePatternsElement[ boxNum ].checked)
            {
                gameBoard.clearGameState() ;
                readLifeFile( lifePattern[ boxNum ], gameBoard ) ;
                gameBoard.updateView() ;
                updateRulesView() ;
                break ;
            }
        }
    }
}

// Update the rules.
function make_updateRulesView( gameBoard, survivalRulesElement, birthRulesElement )
{
    return function()
    {
        // Uncheck all the boxes first.
        for (var boxNum = 0 ;  boxNum < survivalRulesElement.length ;  ++boxNum)
            survivalRulesElement[ boxNum ].checked = false ;

        // Go through all the rules, checking boxes with number of neighbors.
        for (var i = 0 ; i < gameBoard.rules.survival.numRules ;  ++i)
            survivalRulesElement[ gameBoard.rules.survival.numNeighbors[ i ] - 1].checked = true ;

        // Uncheck all the boxes first.
        for (var boxNum = 0 ;  boxNum < birthRulesElement.length ;  ++boxNum)
            birthRulesElement[ boxNum ].checked = false ;

        // Go through all the rules, checking boxes with number of neighbors.
        for (var i = 0 ; i < gameBoard.rules.birth.numRules ;  ++i)
            birthRulesElement[ gameBoard.rules.birth.numNeighbors[ i ] - 1].checked = true ;
    }
}

// --- Set up the game of life board.
function initializeGameOfLife()
{
    // Get access to the document's canvas, control buttons, output windows, file select buttons, etc.
    var canvasElement               = document.getElementById( "GameOfLifeCanvas" ) ;
    var gameStateElement            = document.getElementById( "GameOfLifeState" ) ;
    var cellStateElement            = document.getElementById( "GameOfLifeCellState" ) ;
    var debugElement                = document.getElementById( "GameOfLifeDebug" ) ;
    var fileSelectElementForReading = document.getElementById( "GameOfLifeLoadFile" ) ;
    var clipboardElement            = document.getElementById( "GameOfLifeClipboard" ) ;


    // We have a group of boxes and radio buttons sharing the same name so we can fetch all of their values as an array.
    // We can't use id's since they are unique to a single element.
    var survivalRulesElement = document.getElementsByName( "SurvivalRules" ) ;
    var birthRulesElement    = document.getElementsByName( "BirthRules" ) ;
    var lifePatternsElement  = document.getElementsByName( "LifePatterns" ) ;


    // Create a new game board which is the size of the canvas and pass it the canvas graphics context.
    var gameBoard = new GameBoard( canvasElement, gameStateElement, GameSettings.GameBoardNumRows, GameSettings.GameBoardNumCols ) ;


    // Callback function to advance the game state one generation tick.
    // Registered with the timer function.
    singleStepGame = make_singleStepGame( gameBoard ) ;

    // Callback function to clear the game state.
    clearGame = make_clearGame( gameBoard ) ;

    // Callback function to change the life rules.
    changeRules = make_changeRules( gameBoard, survivalRulesElement, birthRulesElement ) ;
    updateRulesView = make_updateRulesView( gameBoard, survivalRulesElement, birthRulesElement ) ;

    // Callback function called when mouse cursor is clicked anywhere on the canvas.
    // Register it with the canvas listener.
    var onClick = make_onClick( gameBoard ) ;
    canvasElement.addEventListener( "click", onClick, false ) ;

    // Callback function when the mouse moves in the canvas.
    var onMouseMove = make_onMouseMove( gameBoard, cellStateElement ) ;
    canvasElement.addEventListener( 'mousemove', onMouseMove, false ) ;

    // Callback function for the LoadLifeFileButton button click.
    // Pass the event which is the list of files selected to the handler function handleFileSelectForReading.
    var handleFileSelectForReading = make_fileSelectForReading( gameBoard, debugElement, clipboardElement ) ;
    fileSelectElementForReading.addEventListener( 'change', handleFileSelectForReading, false ) ;

    // Callback function to read and write game to clipboard.
    writeGameToClipboard  = make_writeGameToClipboard( gameBoard, clipboardElement ) ;
    readGameFromClipboard = make_readGameFromClipboard( gameBoard, clipboardElement ) ;

    // Callback function to load a new life pattern.
    loadSampleLifePattern = make_loadSampleLifePattern( gameBoard, lifePatternsElement ) ;


    // Debug print for game state.
    gameBoard.debugPrint = make_debugPrint( gameBoard, debugElement ) ;

    // Draw the life grid lines.
    gameBoard.drawLifeGrid() ;

    // Clear the game state.
    gameBoard.clearGameState() ;

    // Load a glider gun.
    readLifeFile( lifePattern[ 0 ], gameBoard ) ;


    // Write game to clipboard.
    writeGameToClipboard() ;

    // Update all counters.
    gameBoard.updateView() ;

    // Update the rules.
    updateRulesView() ;
}


// Update the game board to go from one generation to the next.
function updateGameBoard()
{
    this.debugPrint( DebugPrintOption.GameBoard ) ;

    // Count the number of neighbors for each counter.
    this.countNeighbors() ;

    // Apply the life rules to see who lives and dies.
    this.birthAndDeath() ;

    this.debugPrint( DebugPrintOption.Neighbors ) ;
    this.debugPrint( DebugPrintOption.States ) ;

    // We now have a new generation.
    ++this.generation ;
}


// If a cell is occupied, update the neighbor counts for all adjacent cells.
// Treat the boundary of the board specially.
function countNeighbors()
{
    var row, col ;

    //  Size of game board.
    var numRows = this.numRows ;
    var numCols = this.numCols ;

    //  Zero out the neighbor count for each cell.
    for (row = 0 ;  row < numRows ;  ++row)
        for (col = 0 ;  col < numCols ;  ++col)
            this.cell[ row ][ col ].numberOfNeighbors = 0 ;

    // Update neighbor counts for counters in first and last columns.
    for (row = 0 ;  row < numRows ;  ++row)
    {
        if (this.cell[ row ][ 0 ].occupied == Occupancy.Occupied)
            this.boundaryNeighborCount( row, 0 ) ;

        if (this.cell[ row ][ numCols - 1 ].occupied == Occupancy.Occupied)
            this.boundaryNeighborCount( row, numCols - 1 ) ;
    }

    // Update neighbor counts for counters in the first and last rows,
    // skipping the corners since these have already been updated.
    for (col = 1 ;  col <= numCols-2 ;  ++col)
    {
        if (this.cell[ 0 ][ col ].occupied == Occupancy.Occupied)
            this.boundaryNeighborCount( 0, col ) ;

        if (this.cell[ numRows - 1 ][ col ].occupied == Occupancy.Occupied)
            this.boundaryNeighborCount( numRows - 1, col ) ;
    }

    // Update neighbor counts on interior cells.
    for (row = 1 ;  row <= numRows - 2 ;  ++row)
    {
        for (col = 1 ;  col <= numCols - 2 ;  ++col)
        {
            //  Current cell is occupied.
            if (this.cell[ row ][ col ].occupied == Occupancy.Occupied)
            {
                //  Update neighbor count for all its 8 adjacent cells.
                ++this.cell[ row - 1 ][ col - 1 ].numberOfNeighbors ;
                ++this.cell[ row - 1 ][ col     ].numberOfNeighbors ;
                ++this.cell[ row - 1 ][ col + 1 ].numberOfNeighbors ;

                ++this.cell[ row     ][ col - 1 ].numberOfNeighbors ;
                ++this.cell[ row     ][ col + 1 ].numberOfNeighbors ;

                ++this.cell[ row + 1 ][ col - 1 ].numberOfNeighbors ;
                ++this.cell[ row + 1 ][ col     ].numberOfNeighbors ;
                ++this.cell[ row + 1 ][ col + 1 ].numberOfNeighbors ;
            }
        }
    }

    return ;
}

// Given that the boundary cell at (row, col) is occupied, update the neighbor
// counts for all adjacent cells.
function boundaryNeighborCount( row, col )
{
    var adjRow, adjCol, adjTorusRow, adjTorusCol ;

    // Iterate through all adjacent cells.
    for (adjRow = row - 1 ;  adjRow <= row + 1 ;  ++adjRow)
    {
        for (adjCol = col - 1 ;  adjCol <= col + 1 ;  ++adjCol)
        {
            adjTorusRow = adjRow ;
            adjTorusCol = adjCol ;

            //  Wrap around so that we are topologically on a torus.
            if (adjTorusRow <= -1)
                adjTorusRow = this.numRows - 1 ;

            if (adjTorusCol <= -1)
                adjTorusCol = this.numCols - 1 ;

            if (adjTorusRow >= this.numRows)
                adjTorusRow = 0 ;

            if (adjTorusCol >= this.numCols)
                adjTorusCol = 0 ;

            //  All neighbors of the current cell get incremented.
            ++this.cell[ adjTorusRow ][ adjTorusCol ].numberOfNeighbors ;
        }
    }

    //  Neighbor count for the cell itself was incremented above.
    //  Correct for this.
    --this.cell[ row ][ col ].numberOfNeighbors ;

    return ;
}

// Sweep through all cells, updating their occupancy according to the birth
// and death rules.  Use each cell's neighbor count from the last cycle.
function birthAndDeath()
{
    var row, col, i, caseOfSurvival, caseOfBirth, cell ;

    this.population = 0 ;

    for (row = 0 ;  row < this.numRows ;  ++row)
    {
        for (col = 0 ;  col < this.numCols ;  ++col)
        {
            // Access the current cell at row, col.
            cell = this.cell[ row ][ col ] ;

            // Save the previous occupation state for this cell.
            cell.occupiedPreviously = cell.occupied ;

            caseOfBirth = caseOfSurvival = false ;

            //  An empty cell next to n1 or n2 or ... neighbors gets a birth.
            if (cell.occupied == Occupancy.Empty)
            {
                for (i = 0 ; i < this.rules.birth.numRules ;  ++i)
                {
                    if (cell.numberOfNeighbors == this.rules.birth.numNeighbors[ i ])
                    {
                        caseOfBirth = true ;
                        cell.occupied = Occupancy.Occupied ;
                        cell.state    = State.Birth ;
                        cell.age      = 0 ;        // Cell is newborn.

                        // Early out since some rule allowed a birth.
                        break ;
                    }
                } // end for
            }
            else if (cell.occupied == Occupancy.Occupied)
            {
                for (i = 0 ; i < this.rules.survival.numRules ;  ++i)
                {
                    if (cell.numberOfNeighbors == this.rules.survival.numNeighbors[ i ])
                    {
                        caseOfSurvival = true ;

                        cell.state = State.Survival ;
                        ++cell.age ;                                 // Cell gets older.
                        if (cell.age > GameSettings.MaximumAge)   // Wrap around to nonzero.
                            cell.age = 1 ;

                        // Early out since some rule allowed a survival.
                        break ;
                    }
                } // end for

            }

            //  All other cases, including death from overpopulation, underpopulation
            //  and the case where the cell stays empty with no change.
            if (!caseOfSurvival && !caseOfBirth)
            {
                //  Occupied cell suffers death from overpopulation or underpopulation.
                if (cell.occupied == Occupancy.Occupied)
                {
                    cell.occupied = Occupancy.Empty ;
                    cell.state    = State.Death ;
                    cell.age      = 0 ;
                }
                // Empty cell does not change.
                else
                {
                    ++cell.age ;                                // Empty cell gets older.
                    if (cell.age > GameSettings.MaximumAge)  // Wrap around to nonzero.
                        cell.age = 1 ;
                }
            }

            // Update the population count.
            if (cell.occupied == Occupancy.Occupied)
                ++this.population ;
        } // end for col
    } // end for row
}


//===================================================== Drawing the Game Board =======================================================

function drawLifeGrid()
{
    // White grid lines.
    this.graphicsContext.strokeStyle = "rgba(230,230,255,1.0)"

    // Erase the game board area.
    this.graphicsContext.clearRect( 0, 0, this.widthPixels, this.heightPixels ) ;

    this.graphicsContext.beginPath();

    var cellWidth  = this.widthPixels  / this.numCols ;
    var cellHeight = this.heightPixels / this.numRows ;

    // Draw vertical lines.
    for (var x = 0 ;  x <= this.widthPixels ;  x += cellWidth)
    {
        this.graphicsContext.moveTo( 0.5 + x, 0 ) ;
        this.graphicsContext.lineTo( 0.5 + x, this.heightPixels ) ;
    }

    // Draw horizontal lines.
    for (var y = 0; y <= this.heightPixels ; y += cellHeight )
    {
        this.graphicsContext.moveTo( 0, 0.5 + y ) ;
        this.graphicsContext.lineTo( this.widthPixels, 0.5 + y ) ;
    }

    this.graphicsContext.stroke();
    this.graphicsContext.closePath() ;
}

// Canvas [x, y]  to  game board [row, col].
function canvasToCellCoord( pos )
{
    var cellWidth  = this.widthPixels  / this.numCols ;
    var cellHeight = this.heightPixels / this.numRows ;

    var col = Math.floor( pos[0] / cellWidth  ) ;
    var row = Math.floor( pos[1] / cellHeight ) ;

    return [row, col] ;
}

// Game board [row, col]  to  canvas [x, y].
function cellToCanvasCoord( pos )
{
    var cellWidth  = this.widthPixels  / this.numCols ;
    var cellHeight = this.heightPixels / this.numRows ;

    // Canvas (x,y) coordinates of the center of a cell.
    var x = cellWidth  * pos[1] + cellWidth  / 2 ;
    var y = cellHeight * pos[0] + cellHeight / 2 ;

    return [x, y] ;
}

function getCursorPosition( e )
{
    var x;
    var y;
    // I don't know what this is.
    if (e.pageX || e.pageY)
    {
        x = e.pageX;
        y = e.pageY;
    }
    else
    {
        // Correct for scrolling on the browser.
        x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft ;
        y = e.clientY + document.body.scrollTop  + document.documentElement.scrollTop ;
    }

    // Correct for canvas offset from the body.
    x -= this.canvasElement.offsetLeft ;
    y -= this.canvasElement.offsetTop ;

    // Correct scrolling on the div with scroll properties which encloses the canvas.
    x += this.canvasElement.parentNode.scrollLeft ;
    y += this.canvasElement.parentNode.scrollTop ;

    return [x, y] ;
}

// Toggle the counter state and redraw it.
function toggleCounter( gameBoard, pos )
{
    var cell = gameBoard.cell[ pos[0] ][ pos[1] ] ;

    // Save the previous occupation state for this cell.
    cell.occupiedPreviously = cell.occupied ;

    //  If cell is empty, mark as occupied, or vice-versa.
    if (cell.occupied == Occupancy.Empty)
        cell.occupied = Occupancy.Occupied ;
    else if (cell.occupied == Occupancy.Occupied)
        cell.occupied = Occupancy.Empty ;

    gameBoard.drawCell( pos ) ;
}

// Draw the current cell.
function drawCell( pos )
{
    //  Get the current cell information.
    var cell = this.cell[ pos[0] ][ pos[1] ] ;

    // Center canvas coordinates of cell.
    var centerOfCell = this.cellToCanvasCoord( pos ) ;

    var cellWidth  = this.widthPixels  / this.numRows ;
    var cellHeight = this.heightPixels / this.numCols ;
    var radius     = cellWidth / 2 - 0.8 ;

    // Cell occupation didn't change.  And of course, assume the board wasn't just cleared.
    if (cell.occupied == cell.occupiedPreviously && cell.occupied != Occupancy.Indeterminate)
    {
        // Special case if an occupied cell just aged.
        if (cell.age == GameSettings.OldAge && cell.occupied == Occupancy.Occupied)
        {
            this.graphicsContext.beginPath();
            this.graphicsContext.fillStyle = "rgba( 185, 65, 64, 1.0 )" // Stable counter color:  red.
            this.graphicsContext.arc( centerOfCell[0], centerOfCell[1], radius, 0, Math.PI*2, true ) ;
            this.graphicsContext.fill();
            this.graphicsContext.closePath() ;
        }

        // Skip drawing.
        return ;
    }

    // If we are here, the cell occupation changed...

    // Cell is occupied:  draw the counter.
    if (cell.occupied == Occupancy.Occupied)
    {
        this.graphicsContext.beginPath();

        if( cell.age >= GameSettings.OldAge )
            this.graphicsContext.fillStyle = "rgba( 185, 65, 64, 1.0 )"    // Stable counter color:  red.
        else
            this.graphicsContext.fillStyle = "rgba(   0, 100, 255, 1.0 )"  // Active counter color:  blue.

        this.graphicsContext.arc( centerOfCell[ 0 ], centerOfCell[ 1 ], radius, 0, Math.PI * 2, true ) ;
        this.graphicsContext.fill();
        this.graphicsContext.closePath() ;
    }
    // Cell is empty:  erase the counter.
    else if (cell.occupied == Occupancy.Empty)
    {
        /// alert( "clear cell[ " + pos[0] +  " " + pos[1] + " ] = " + this.cell[ pos[0] ][ pos[1] ].occupied ) ;

        // Get the cell dimensions.
        var x1 = centerOfCell[ 0 ] - cellWidth  / 2 ;
        var y1 = centerOfCell[ 1 ] - cellHeight / 2 ;
        var x2 = centerOfCell[ 0 ] + cellWidth  / 2 ;
        var y2 = centerOfCell[ 1 ] + cellHeight / 2 ;

        // Erase the whole cell.
        this.graphicsContext.clearRect( x1, y1, cellWidth, cellHeight ) ;

        // White grid lines.
        this.graphicsContext.strokeStyle = "rgba(230,230,255,1.0)"

        // Redraw the lines of the cell.
        this.graphicsContext.beginPath();
        this.graphicsContext.moveTo( x1 + 0.5, y1       ) ; // Vertical
        this.graphicsContext.lineTo( x1 + 0.5, y2       ) ;

        this.graphicsContext.moveTo( x2 + 0.5, y1       ) ; // Vertical
        this.graphicsContext.lineTo( x2 + 0.5, y2       ) ;

        this.graphicsContext.moveTo( x1 + 0.5, y1 + 0.5 ) ; // Horizontal
        this.graphicsContext.lineTo( x2 + 0.5, y1 + 0.5 ) ;

        this.graphicsContext.stroke();
        this.graphicsContext.closePath() ;
    }
}

// Make a callback function using Lispish closures!
// Manufacture the onClick() function using
//     var onClick = make_onClick( gameBoard ) ;
// then call as usual using onClick() or pass to an event listener as onClick.
function make_onClick( gameBoard )
{
    return function( e )
    {
        var pos = gameBoard.canvasToCellCoord( gameBoard.getCursorPosition( e )) ;
        toggleCounter( gameBoard, pos ) ;
    } ;
}

// Make a callback function with an event argument.
function make_onMouseMove( gameBoard, cellStateElement )
{
    return function( e )
    {
        // Show the cell (row, col) position.
        var pos = gameBoard.canvasToCellCoord( gameBoard.getCursorPosition( e )) ;

        // Show the complete cell state.
        var row = pos[ 0 ] ;
        var col = pos[ 1 ] ;

        // Cursor gives a game board position out of bounds.
        if (row >= 0 && col >= 0 && row < GameSettings.GameBoardNumRows && col < GameSettings.GameBoardNumCols)
        {
            var state = gameBoard.cell[ row ][ col ].state ;
            var symbol ;

            switch( state )
            {
                case 2:
                    symbol = 'B' ;
                    break ;
                case 3:
                    symbol = 'd' ;
                    break ;
                default:
                    symbol = '&nbsp;' ;
            } ;

            cellStateElement.innerHTML =
                  " row/col: "       + row + " " + col +
                  " occupied: "      + gameBoard.cell[ row ][ col ].occupied +
                  " occupied prev: " + gameBoard.cell[ row ][ col ].occupiedPreviously +
                  " num neighbors: " + gameBoard.cell[ row ][ col ].numberOfNeighbors +
                  " state: "         + symbol +
                  " age: "           + gameBoard.cell[ row ][ col ].age +
                  " label: "         + gameBoard.cell[ row ][ col ].label +
                  " father: "        + gameBoard.cell[ row ][ col ].father +
                  " edge: "          + gameBoard.cell[ row ][ col ].edge ;
        }
    } // end func
}

// Make a callback function using Lispish closures!
function make_singleStepGame( gameBoard )
{
    return function( e )
    {
        // Update the game board.
        gameBoard.updateGameBoard() ;

        // Repaint the canvas, but only for counters which have changed.
        gameBoard.updateView() ;
    }
}

function printGameBoard( gameBoard )
{
    var row, col, numRows = gameBoard.numRows, numCols = gameBoard.numCols ;

    // Display the game board counters.
    var text = "Game Board\n" ;
    for (row = 0 ;  row < numRows ;  ++row)
    {
        // Up to 5 digit number with padding.  Concatenate blanks to the front of the number, then take 5 chars back from the end.
        text += String( "     " + row ).slice( -5 ) ;
        text += ": " ;
        for (col = 0 ;  col < numCols ;  ++col)
        {
            var cell = gameBoard.cell[ row ][ col ] ;
            if (cell.occupied == Occupancy.Occupied)
                text += "O " ;
            else
                text += "  " ;
        }
        text += "\n" ;
    }

    return text ;
}

function printNeighborCounts( gameBoard )
{
    var row, col, numRows = gameBoard.numRows, numCols = gameBoard.numCols ;

    // Display the neighbor counts.
    var text = "Neighbor counts\n" ;
    for (row = 0 ;  row < numRows ;  ++row)
    {
        // Up to 5 digit number with padding.  Concatenate blanks to the front of the number, then take 5 chars back from the end.
        text += String( "     " + row ).slice( -5 ) ;
        text += ": " ;
        for (col = 0 ;  col < numCols ;  ++col)
        {
            var cell = gameBoard.cell[ row ][ col ] ;
            var num  = cell.numberOfNeighbors ;
            text += (num == 0 ? ' ' : num) ;
            text += " " ;
        }
        text += "\n" ;
    }
    return text ;
}

function printCounterState( gameBoard )
{
    var row, col, numRows = gameBoard.numRows, numCols = gameBoard.numCols ;

    // Display the counter states.
    var text = "Counter state\n" ;
    for (row = 0 ;  row < numRows ;  ++row)
    {
        // Up to 5 digit number with padding.  Concatenate blanks to the front of the number, then take 5 chars back from the end.
        text += String( "     " + row ).slice( -5 ) ;
        text += ": " ;
        for (col = 0 ;  col < numCols ;  ++col)
        {
            var cell = gameBoard.cell[ row ][ col ] ;
            if (cell.state == State.Birth)
                text += "B " ;
            else if (cell.state == State.Survival)
                text += "s " ;
            else if (cell.state == State.Death)
                text += "d " ;
            else
                text += "  " ;
        }
        text += "\n" ;
    }

    return text ;
}

function make_debugPrint( gameBoard, debugElement )
{
    return function( option )
    {
        var text = debugElement.innerHTML ;

        switch( option )
        {
            case DebugPrintOption.GameBoard:
                // Clear the debug area when printing the gameboard.
                text = "" ;
                text += printGameBoard( gameBoard ) ;
            break ;

            case DebugPrintOption.Neighbors:
                text += printNeighborCounts( gameBoard ) ;
            break ;

            case DebugPrintOption.States:
                text += printCounterState( gameBoard ) ;
            break ;
        }

        // Write out the text to the debug area.
        debugElement.innerHTML = text ;

    } // inner function
}

// Clear the game board.
function make_clearGame( gameBoard )
{
    return function( e )
    {
        gameBoard.clearGameState() ;
        gameBoard.updateView() ;
    }
}

function clearGameState()
{
    var row, col ;
    this.population = 0 ;
    this.generation = 0 ;

    // Fill cells with default values.
    for (col = 0 ;  col < this.numCols ;  ++col)
    {
        for (row = 0 ;  row < this.numRows ;  ++row)
        {
            var cell = this.cell[ row ][ col ] ;

            cell.numberOfNeighbors  =  0 ;
            cell.occupied           =  Occupancy.Empty ;
            cell.occupiedPreviously =  Occupancy.Indeterminate ;
            cell.state              =  State.Indeterminate ;
            cell.age                =  0 ;
            cell.label              = -1 ;
            cell.father             = -1 ;
            cell.edge               = -1 ;
        }
    }

    // Clear the comments.
    this.numCommentLines = 1 ;
    this.comment[ 0 ] = "#D Your comment here!" ;
}

// Redraw the gameboard and its global state.
function updateView()
{
    var row, col ;

    for (row = 0 ;  row < this.numRows ;  ++row)
    {
        for (col = 0 ;  col < this.numCols ;  ++col)
        {
            var pos = [row, col] ;
            this.drawCell( pos ) ;
        }
    }

    var text = "generation " + this.generation + " population " + this.population ;

    // Display the rules and the generation.
    text += " survival " ;

    for (var i = 0 ;  i < this.rules.survival.numRules ;  ++i)
        text += this.rules.survival.numNeighbors[ i ] ;

    text += " birth " ;

    for (var i = 0 ;  i < this.rules.birth.numRules ;  ++i)
        text += this.rules.birth.numNeighbors[ i ] ;

    // Display the game state.
    this.gameStateElement.innerHTML = text ;

    return ;
}


//========================================================= Read Life File ===========================================================

// Handle file selection events.
function make_fileSelectForReading( gameBoard, debugElement, clipboardElement )
{
    return function( e )
    {
        // The target is the object which this event was dispatched on.
        // It contains a list of files.
        var files = e.target.files ;

        // Loop through the FileList.
        for (var i = 0, f; f = files[i]; i++)
        {
            // Only process Game of Life files.
            if ( !f.name.match('\.lif'))
                continue ;

            var reader = new FileReader() ;

            // Callback function for file load completion.
            // Use lispish closure to encapsulate the reader.result which is the file contents.
            // Then call the inner function with the file contents.
            reader.onload = function()
            {
                clipboardElement.value = reader.result ;

                // Clear out the game board, load the file, update the gameboard view, status and rules.
                gameBoard.clearGameState() ;
                readLifeFile( reader.result, gameBoard ) ;
                gameBoard.updateView() ;
                updateRulesView() ;
            } ;

          // Read in the image file text.
          reader.readAsText( f, 'UTF-8' ) ;
        } // for
    } // function
}

// Read a Game of Life file in 1.05 format.
//
// Use a recursive descent parser, similar to awk parser from
//        THE AWK PROGRAMMING LANGUAGE, Aho, Kernighan, Weinberger, pgs. 147-152.
//
// Here is an explanation for the Life 1.05 format from
//
//     http://www.mirekw.com/ca/ca_files_formats.html
//
// This ASCII format just draws the pattern with "." and "*" symbols. The line length should not
// exceed 80 characters.
//
// The "#Life" line is followed by optional description lines, which begin with "#D" and are
// followed by no more than 78 characters of text. Leading and trailing spaces are ignored,
// so the following two "#D" lines are equivalent:
//
//     #D This is a Description line
//     #D     This is a Description line
//     There should be no more than 22 "#D" lines in a .LIF file.
//
// Next comes an optional rule specification. If no rules are specified, then the pattern will
// run with whatever rules the Life program is currently set to. The patterns in the collection
// here enforce "Normal" Conway rules using the "#N" specifier. Alternate rules use
// "#R" ("#N" is exactly the same as "#R 23/3"). Rules are encoded as Survival/Birth,
// each list being a string of digits representing neighbor counts. Since there are exactly
// eight possible neighbors in a Conway-like rule, there is no need to separate the digits,
// and "9" is prohibited in both lists. For example,
//
//     #R 125/36
//
// means that the pattern should be run in a universe where 1, 2, or 5 neighbors are necessary
// for a cell's survival, and 3 or 6 neighbors allows a cell to come alive.
//
// Next come the cell blocks. Each cell block begins with a "#P" line, followed by "x y"
// coordinates of the upper-left hand corner of the block, assuming that 0 0 is the center
// of the current window to the Life universe.
//
// This is followed by lines that draw out the pattern in a visual way, using the "." and "*"
// characters (off, on). Each line must be between 1 and 80 characters wide, inclusive;
// therefore, a blank line is represented by a single dot, whereas any other line may truncate
// all dots to the right of the last "*". There is no limit to the number of lines in a cell block.
//
// Any line of zero length (just another carriage return) is completely ignored. Carriage returns
// are MSDOS-style (both 10 and 13).
//
// For example, a glider in Life1.05 format is saved as:
//
//     #Life 1.05
//
//     ***
//     *..
//     .*.
//
// Life 1.05 format was designed to be easily ported. You can just look at a pattern in this format
// in a text editor, and figure out what it is. Alan Hensel's pattern collection
// (http://www.mindspring.com/~alanh/lifep.zip) is stored in Life 1.05 format.
//
function readLifeFile( fileText, gameBoard )
{
    var lineOfFile = null ;

    // Create a function to return the next line of the file.
    var readLine = make_readLine( fileText ) ;

    // Read the file, catching any exceptions thrown during the read.
    try
    {
        // Eat the version number.
        parseVersionNumber( readLine() ) ;

        // Read comment lines.
        var numCommentLines = 0 ;
        while (parseCommentLine( lineOfFile = readLine() ))
        {
            gameBoard.comment[ numCommentLines ] = lineOfFile ;

            if (++numCommentLines > GameSettings.MaxNumCommentLines)
                throw "too many comment lines " + numCommentLines + " > " + GameSettings.MaxNumCommentLines  ;
        }
        gameBoard.numCommentLines = numCommentLines ;

        // Read the optional rules line.
        var rules = parseRules( lineOfFile ) ;
        if (rules)
        {
            // It was a rules line, so fetch the next line.
            gameBoard.rules.survival = rules[ 0 ] ;
            gameBoard.rules.birth    = rules[ 1 ] ;
            lineOfFile = readLine() ;
        }
        else
        {
            // It wasn't a rules line:  just use the Conway rules.
            gameBoard.rules.survival = { numRules : 2, numNeighbors : [ 2, 3, , , , , , , ] } ;
            gameBoard.rules.birth    = { numRules : 1, numNeighbors : [ 3,  , , , , , , , ] } ;
        }

        // Read sequences of life pattern locations and patterns.  End of file will throw us out of here.
        for(;;)
        {
            // Pattern location.
            var patternLocation = parsePatternLocation( lineOfFile ) ;

            if (!patternLocation)
                throw "cannot parse pattern location line " + lineOfFile ;

            // Rows of pattern lines.
            var patternRow = 0 ;
            while (parsePatternLine( lineOfFile = readLine(), patternLocation, patternRow, gameBoard ))
                ++patternRow ;
        }
    }
    catch( e )
    {
        // End of file (actually end of string).
        if (e == "end of file")
            return true ;
        // Some error got thrown above when parsing the file.
        else
        {
            alert( "ERROR in reading file: " + e ) ;
            return false ;
        }
    }

    return true ;
}

// Create a closure which returns the next line of text.
function make_readLine( fileText )
{
    // Split text of the entire file into lines.
    var linesOfFile    = fileText.split( '\n' ) ;
    var numLinesInFile = linesOfFile.length ;

    var lineNum = 0 ;

    // Returns the next line of the file.
    return function()
    {
        if (lineNum < numLinesInFile)
            return linesOfFile[ lineNum++ ] ;
        else
            throw "end of file" ;
    }
}

// Return true if the version number of a line is Life 1.05
function parseVersionNumber( lineOfFile )
{
    if (!lineOfFile.match( /^#Life 1\.05.*/))
        throw  "life file version number " + lineOfFile + " is not 1.05"  ;
}

// Parse one line of a life file to see if it is a comment line.
// i.e. of the form
//     #D<sequence of characters>
function parseCommentLine( lineOfFile )
{
    if (lineOfFile.match( "^#D"))
        return true ;

    return false ;
}

// Parse a rules line.  There are two forms:
// (1) The normal Conway life rules
//          #N
// (2) Arbitrary rule where we list d1 ... neighbors for survival and D1 ... neighbors for a birth.
//          #R d1 d2 ... / D1 D2 ...
//     e.g. the Conway rules are encoded as
//          #R 23/3.
//     specifes number of neighbors for survival is 2 or 3 and number for a birth is 3.
function parseRules( lineOfFile )
{
    var survival, birth ;

    // Return if we don't see a rules line.
    if (!lineOfFile.match( /^\s*#[NR]/ ))
        return null ;

    // Normal Conway rules.
    if (lineOfFile.match( /^\s*#N/ ))
    {
        survival = { numRules : 2, numNeighbors : [ 2, 3, , , , , , , ] } ;  // Empty entries are undefined.
        birth    = { numRules : 1, numNeighbors : [ 3,  , , , , , , , ] } ;
        return [ survival, birth ] ;
    }

    // Other rules of the type #R single digit list of neighbors for survivals / ... for births.
    rulePattern = /^\s*#R\s*(\d+)\/(\d+)/ ;

    // List ought to have three pieces, the match and two strings of digits:  [ "#R 23/3", "23", "3" ].
    var rulesString = lineOfFile.match( rulePattern ) ;
    if (rulesString == null || rulesString.length != 3)
        return null ;

    return [ parseRulesList( rulesString[ 1 ] ), parseRulesList( rulesString[ 2 ] ) ] ;
}

// Parse a rules list into a rules object.
function parseRulesList( rulesString )
{
    // Count survivals.
    var neighborCountsString = rulesString ;
    var numNeighbors = new Array( 9 ) ;
    var numRules = rulesString.length ;

    for (var i = 0 ;  i < numRules ;  ++i)
        numNeighbors[ i ] = parseInt( neighborCountsString.charAt( i ) ) ;

    return new Rules( numRules, numNeighbors ) ;
}

// Parse one line of a life file to see if it is a cell block
// of the form
//     #P <integer x coordinate> <integer y coordinate>
// e.g.
//     #P -2 2
function parsePatternLocation( lineOfFile )
{
    var rulePattern = /^\s*#P\s*([+-]*\d+)\s*([+-]*\d+)/ ;

    // List ought to have three pieces, the match and two digits.
    var patternString = lineOfFile.match( rulePattern ) ;
    if (patternString == null || patternString.length != 3)
        return null ;

    // Return the x and y coordinates.
    var point = { x:0, y:0 } ;
    point.x = parseInt( patternString[ 1 ] ) ;
    point.y = parseInt( patternString[ 2 ] ) ;
    return point ;
}

// Parse one line of a life file to see if it is a pattern line of the form of * or .   e.g.
//
//    ...*
//    ....*
//    ....*
//    .****
//
// Any other character other than whitespace is an error.  Fill in the game board while parsing.
// If we go outside the bounds of the game board, throw an error.
function parsePatternLine( lineOfFile, patternLocation, patternRow, gameBoard )
{
    //  Middle row of the game board.
    var centerRow = gameBoard.numRows / 2 ;
    var centerCol = gameBoard.numCols / 2 ;

    //  Fill in occupied cells in the game board.
    for (var col = 0 ; col < lineOfFile.length ;  ++col)
    {
        var counter = lineOfFile.charAt( col ) ;

        // Record an occupied cell in the game board.
        if (counter == '*')
        {
            // Counter is offset by cell block upper left corner
            // coordinates and by row number of the pattern line.
            counterCol = centerCol + patternLocation.x + col ;
            counterRow = centerRow + patternLocation.y + patternRow ;

            //  Out of bounds counter check.
            if (counterRow < 0 || counterRow >= gameBoard.numRows ||
                counterCol < 0 || counterCol >= gameBoard.numCols)
            {
                throw "Game pattern out of bounds at pattern location row " + patternLocation.y + " col " + patternLocation.x +
                       " at counter row " + counterRow + " col " + counterCol ;
            }

            //  Flip the counter occupancy flag.
            gameBoard.cell[ counterRow ][ counterCol ].occupied = Occupancy.Occupied ;
        }
        // Ignore . or whitespace.
        else if (counter == '.' || counter == ' ' || counter == '\r' || counter == '\t' || counter == '\n' )
            ;
        // Don't expect any other characters.
        else
            return false ;
    }

    return true ;
}

//========================================================= Write Life File ===========================================================

// Save the game to the clipboard.
function make_writeGameToClipboard( gameBoard, clipboardElement )
{
    return function( e )
    {
        clipboardElement.value = writeLifeFile( gameBoard ) ;
    }
}

function make_readGameFromClipboard( gameBoard, clipboardElement )
{
    return function( e )
    {
        // Clear out the game board, load the file from the clipboard area, update the gameboard view, status and rules.
        gameBoard.clearGameState() ;
        readLifeFile( clipboardElement.value, gameBoard ) ;
        gameBoard.updateView() ;
        updateRulesView() ;
    }
}

// Write the life game board to file..
function writeLifeFile( gameBoard )
{
    var fileText = "#Life 1.05\n" ;

    // Write out comments.
    for (row = 0 ;  row < gameBoard.numCommentLines ;  ++row)
        fileText += (gameBoard.comment[ row ] + "\n") ;

    // These are the John Horton Conway default life rules.
    if (gameBoard.rules.birth.numRules             == 1 &&
        gameBoard.rules.birth.numNeighbors[ 0 ]    == 3 &&
        gameBoard.rules.survival.numRules          == 2 &&
        gameBoard.rules.survival.numNeighbors[ 0 ] == 2 &&
        gameBoard.rules.survival.numNeighbors[ 1 ] == 3)
    {
        // Write the normal rules line.
        fileText += "#N\n" ;
    }
    // Write the full rules line.
    else
    {
        fileText += "#R " ;

        //  Write the survival rules first.
        for (var i = 0 ;  i < gameBoard.rules.survival.numRules ; ++i)
            fileText += gameBoard.rules.survival.numNeighbors[ i ] ;

        fileText += "/" ;

        //  Write the birth rules.
        for (i = 0 ;  i < gameBoard.rules.birth.numRules ; ++i)
            fileText += gameBoard.rules.birth.numNeighbors[ i ] ;

        fileText += "\n" ;
    }

    // Find all the connected components in the game board.
    var boundingBoxes = traverseGameBoard( gameBoard ) ;

    // Split boxes which are too wide.
    for (var i = 0 ;  i < boundingBoxes.length ;  ++i)
    {
        var box = boundingBoxes[ i ] ;

        //  Box is too wide.
        if (box.right - box.left > GameSettings.MaxFileLineLength)
        {
            // Split off the left piece.
            boundingBoxes[ i ].left   = box.left ;
            boundingBoxes[ i ].top    = box.top ;
            boundingBoxes[ i ].bottom = box.bottom ;
            boundingBoxes[ i ].right  = box.left + GameSettings.MaxFileLineLength - 1 ;

            // Split off the right piece, which may still be too large, and append it
            // to the end, where it will be processed later.
            var boxRight = [] ;
            boxRight.left   = box.left + GameSettings.MaxFileLineLength ;
            boxRight.top    = box.top ;
            boxRight.bottom = box.bottom ;
            boxRight.right  = box.right ;
            boundingBoxes.push( boxRight ) ;
        }
    }

    //  Middle of the game board.
    var centerRow = gameBoard.numRows / 2 ;
    var centerCol = gameBoard.numCols / 2 ;

    // Now that we have all the bounding boxes, write the pattern blocks.
    for (i = 0 ;  i < boundingBoxes.length ;  ++i)
    {
        var box = boundingBoxes[ i ] ;

        // Write out the pattern upper left corner offset from game board center.
        var patternRow = box.top  - centerRow ;
        var patternCol = box.left - centerCol ;
        fileText += ("#P " + patternCol + " " + patternRow + "\n") ;

        // Write out rows of patterns for this block.
        for (row = box.top ;  row <= box.bottom ;  ++row)
            fileText += createPatternLine( gameBoard, box, row ) ;
    }

    return fileText ;
}

// Label all the clusters of counters in the game board.
function traverseGameBoard( gameBoard )
{
    //  Size of game board.
    var numRows = gameBoard.numRows ;
    var numCols = gameBoard.numCols ;

    //  Clear the game board.
    for (var row = 0 ;  row < numRows ;  ++row)
    {
        for (var col = 0 ;  col < numCols ;  ++col)
        {
            gameBoard.cell[ row ][ col ].label  =  0 ;  // No labels.
            gameBoard.cell[ row ][ col ].edge   =  0 ;  // Unmark all edges.
            gameBoard.cell[ row ][ col ].father =  0 ;  // Nobody has a father.
        }
    }

    var startLabel = 1 ;
    var boundingBoxes = [] ;

    //  Traverse cells, starting from the next unlabeled cell.
    for (row = 0 ;  row < numRows ;  ++row)
        for (col = 0 ;  col < numCols ;  ++col)
            //  Cell is occupied but not labelled yet.  Start traversal and add another bounding box.
            if (gameBoard.cell[ row ][ col ].occupied == Occupancy.Occupied && gameBoard.cell[ row ][ col ].label == 0)
                boundingBoxes.push( depthFirstTraversal( gameBoard, row, col, startLabel++ ) ) ;
            //  Else we skip over the cell because it is labelled already or is empty.  i.e. it is a background cell.

     return boundingBoxes ;
}

// Starting from (row, col) in the game board at an occupied cell,
// label the cluster of cells connected to it, and return a bounding
// box for the cluster.
function depthFirstTraversal( gameBoard, row, col, label )
{
    var boundingBox = [] ;
    var nextRow ;
    var nextCol ;

    // Size of game board.
    var numRows = gameBoard.numRows ;
    var numCols = gameBoard.numCols ;

    // Label the starting cell.
    gameBoard.cell[ row ][ col ].label = -1 ;

    // Bounding box is initialized to one cell's dimensions.
    boundingBox.top  = boundingBox.bottom = row ;
    boundingBox.left = boundingBox.right  = col ;

    for (;;)
    {
        //  Find the next unmarked edge.
        var edge = nextUnmarkedEdge( gameBoard, row, col ) ;

        // If there is an unmarked edge, investigate this direction.
        //    -Since we don't mark edges which go outside the game board,
        //     we'll sometimes break apart clusters which would have been
        //     connected on our toroidal game board.
        //     But the patterns are not affected, we just have more possible clusters.
        if (edge > 0)
        {
            // Get the location of the next cell.
            [nextRow, nextCol] = nextCell( row, col, edge ) ;

            // Mark the edge of the current and next cell.
            markEdges( gameBoard, row, col, nextRow, nextCol, edge ) ;

            // The next cell is occupied and unlabeled and on the game board:
            // step into it.
            if (gameBoard.cell[ nextRow ][ nextCol ].occupied == Occupancy.Occupied &&
                gameBoard.cell[ nextRow ][ nextCol ].label    == 0 &&
                nextRow < numRows && nextCol < numCols && 0 <= nextRow && 0 <= nextCol)
            {
                //  Record the father of the cell.
                gameBoard.cell[ nextRow ][ nextCol ].father =
                    encodeFather( nextRow, nextCol, row, col ) ;

                //  Label the cell.
                gameBoard.cell[ nextRow ][ nextCol ].label = label ;

                //  Record the maximum excursions for the bounding box.
                boundingBox.top    = min( boundingBox.top,    nextRow ) ;
                boundingBox.bottom = max( boundingBox.bottom, nextRow ) ;
                boundingBox.left   = min( boundingBox.left,   nextCol ) ;
                boundingBox.right  = max( boundingBox.right,  nextCol ) ;

                //  Step to the next cell.
                row = nextRow ;  col = nextCol ;
            }
            else ; //  Rebound:  New cell was either already labelled or unoccupied;
                   //  pretend we've traversed the edge twice.
        }
        // All edges are marked.  Backtrack.
        else
        {
            //  We're back at the beginning.
            if (gameBoard.cell[ row ][ col ].label == -1)
            {
                //  Relabel the start label correctly.
                gameBoard.cell[ row ][ col ].label = label ;
                break ;
            }

            //  Backtrack along a father edge.
            edge = gameBoard.cell[ row ][ col ].father ;
            [row, col] = nextCell( row, col, edge ) ;
        }
    } // end forever loop

    return boundingBox ;
}

// In the next few functions, we will be encoding edges at a vertex by bitmaps.  The encoding is
//
//    8  4  2
//     \ | /
//  16 - C - 1
//     / | \
//   32 64 128

// Return the first unmarked edge in a counterclockwise scan starting from the right edge.
// e.g. if edge = 11110011, next unmarked edge returns 4.
function nextUnmarkedEdge( gameBoard, row, col )
{
    var mask = 1 ;
    var edge = gameBoard.cell[ row ][ col ].edge ;

    for (var bit = 0 ;  bit < 8 ;  ++bit)
    {
        if ((mask & edge) == 0)
            return mask ;
        mask <<= 1 ;
    }

    return 0 ; // No unmarked edges.
}


// Mark the edge of a cell at (row, col) in the game board in the direction
// (row, col) to (nextRow, nextCol).  Also mark the edge at (nextRow, nextCol)
// in the direction to (row, col).
function markEdges( gameBoard, row, col, nextRow, nextCol, edge )
{
    var numRows = gameBoard.numRows ;
    var numCols = gameBoard.numCols ;

    // Mark the edge from (row, col) to (nextRow, nextCol).
    gameBoard.cell[ row ][ col ].edge |= edge ;

    // Encode and mark the edge from the other direction:  from (nextRow, nextCol) to (row, col).
    var oppositeEdge = 0 ;
    switch( edge )
    {
        case   0: oppositeEdge =   0 ; break ;
        case   1: oppositeEdge =  16 ; break ;
        case   2: oppositeEdge =  32 ; break ;
        case   4: oppositeEdge =  64 ; break ;
        case   8: oppositeEdge = 128 ; break ;
        case  16: oppositeEdge =   1 ; break ;
        case  32: oppositeEdge =   2 ; break ;
        case  64: oppositeEdge =   4 ; break ;
        case 128: oppositeEdge =   8 ; break ;
        default: break ;
    }

    // But only mark the edge if it is within the game board.
    if (nextRow < numRows && nextCol < numCols && 0 <= nextRow && 0 <= nextCol)
        gameBoard.cell[ nextRow ][ nextCol ].edge |= oppositeEdge ;
}


// Given a cell at (row, col) in the game board and an edge direction, edge, 
// find the next cell location at the other end of the edge.
// Note from above, we don't mark edges which cause us to move outside the gameboard.
function nextCell( row, col, edge )
{
    var nextRow = nextCol = 0 ;

    switch( edge )
    {
        case   1: nextRow = row     ;  nextCol = col + 1 ;  break ;
        case   2: nextRow = row - 1 ;  nextCol = col + 1 ;  break ;
        case   4: nextRow = row - 1 ;  nextCol = col     ;  break ;
        case   8: nextRow = row - 1 ;  nextCol = col - 1 ;  break ;
        case  16: nextRow = row     ;  nextCol = col - 1 ;  break ;
        case  32: nextRow = row + 1 ;  nextCol = col - 1 ;  break ;
        case  64: nextRow = row + 1 ;  nextCol = col     ;  break ;
        case 128: nextRow = row + 1 ;  nextCol = col + 1 ;  break ;
        default: break ;
    }

    return [ nextRow, nextCol ] ;
}

// Encode an edge so that nextCell() will take us to the father from the son.
function encodeFather( sonRow, sonCol, fatherRow, fatherCol )
{
    var edge ;
    var rowChange = fatherRow - sonRow ;
    var colChange = fatherCol - sonCol ;

    if      (rowChange ==  0 && colChange ==  1)  edge =   1 ;
    else if (rowChange == -1 && colChange ==  1)  edge =   2 ;
    else if (rowChange == -1 && colChange ==  0)  edge =   4 ;
    else if (rowChange == -1 && colChange == -1)  edge =   8 ;
    else if (rowChange ==  0 && colChange == -1)  edge =  16 ;
    else if (rowChange ==  1 && colChange == -1)  edge =  32 ;
    else if (rowChange ==  1 && colChange ==  0)  edge =  64 ;
    else if (rowChange ==  1 && colChange ==  1)  edge = 128 ;
    else edge = 0 ;

    return edge ;
}

// Generate one pattern line of '*' and '.' characters to represent occupied and empty cells at row, given the bounding box coordinates.
function createPatternLine( gameBoard, box, row )
{
    var lineOfFile = "" ;

    // Find the last occupied cell in the row.
    for (var lastCol = box.right ;  lastCol >= box.left ;  --lastCol)
        if (gameBoard.cell[ row ][ lastCol ].occupied == Occupancy.Occupied)
            break ;

    // Convert occupied cells to '*' and unoccupied to '.'
    for (var col = box.left ; col <= lastCol ;  ++col)
    {
        if (gameBoard.cell[ row ][ col ].occupied == Occupancy.Occupied)
            lineOfFile += '*' ;
        else
            lineOfFile += '.' ;
    }

    lineOfFile += "\n" ;

    return lineOfFile ;
}

function supportsLocalStorage()
{
    return ('localStorage' in window) && window['localStorage'] !== null ;
}

function writeClipboardToLocalStorage( file )
{
    if (!supportsLocalStorage())
        return false;

    localStorage[ "GameOfLife.file.name" ] = file ;

    return true;
}

function readLocalStorageToClipboard()
{
    if (!supportsLocalStorage())
        return false;

    file = localStorage[ "GameOfLife.file.name" ] ;

    if (!file)
        return null ;

    return file ;
}