1/*-----------------------------------------------------------------------------
   2|
   3| NAME
   4|
   5|     gameOfLife.js
   6|
   7| DESCRIPTION
   8|
   9|     A JavaScript version of the cellular automata Game of Life by
  10|     Cambridge University mathematician John Horton Conway.
  11|
  12| METHOD
  13|
  14|     Life is played on a two dimensional game board which is partitioned
  15|     into cells.  Cells may be occupied with counters.
  16|
  17|     By default, we use these three (J.H. Conway) rules:
  18|
  19|     1.  BIRTH.  Each empty cell adjacent to exactly 3 neighbors will have a
  20|         birth in the next generation.  Otherwise, the cell remains empty.
  21|
  22|     2.  DEATH.  Each occupied cell with exactly 0 or 1 neighbors dies of
  23|         isolation and loneliness.  Each occupied cell with 4 or more
  24|         neighbors dies of overpopulation.
  25|
  26|     3.  SURVIVAL.  Each occupied cell with exactly 2 or 3 neighbors survives
  27|         to the next generation.
  28|
  29|     All births and deaths occur simultaneously.  Applying all rules to an
  30|     entire board creates a new generation.  Ultimately, the society dies
  31|     out, reaches some steady state (constant or oscillating) or the user
  32|     gets bored.
  33|
  34|     The ideal game board is infinite.  For this program, we wrap around
  35|     at the boundaries of the board so that it is topologically a torus.
  36|
  37|     See Mathematical Games, SCIENTIFIC AMERICAN, Vol. 223, No. 4,
  38|     October 1970, pgs. 120-123 for a description of the game.
  39|
  40| LEGAL
  41|
  42|     JavaScript Game Of Life Version 3.4 -
  43|     A JavaScript version of the cellular automata Game of Life by J. H. Conway.
  44|     Copyright (C) 2010-2024 by Sean Erik O'Connor.  All Rights Reserved.
  45|
  46|     This program is free software: you can redistribute it and/or modify
  47|     it under the terms of the GNU General Public License as published by
  48|     the Free Software Foundation, either version 3 of the License, or
  49|     (at your option) any later version.
  50|
  51|     This program is distributed in the hope that it will be useful,
  52|     but WITHOUT ANY WARRANTY; without even the implied warranty of
  53|     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  54|     GNU General Public License for more details.
  55|
  56|     You should have received a copy of the GNU General Public License
  57|     along with this program.  If not, see <http://www.gnu.org/licenses/>.
  58|
  59|     The author's address is seanerikoconnor!AT!gmail!DOT!com
  60|     with !DOT! replaced by . and the !AT! replaced by @
  61|
  62-----------------------------------------------------------------------------*/
  63
  64// I'm going to use JavaScript's prototypal inheritance model and NOT the class/instance model which
  65// was crudely slapped on top to make the language seem OO.
  66
  67// I ran through JSLint https://www.jslint.com/ to check for errors. Be aware that some messages
  68// such as "unexpected this' or 'unexpected let' are not errors, but just Doug Crawford's preferences.
  69// On Ubuntu/Linux,
  70//     sudo apt install curl
  71//     sudo apt install nodejs
  72//     curl -L https://www.jslint.com/jslint.mjs > jslint.mjs
  73//     node jslint.mjs Scripts/gameOfLife.js
  74
  75//======================================= Game of Life Object ==========================================================
  76
  77// Create basic game of life object with default settings which has a prototype chain.
  78const gameOfLifeBase =
  79{
  80    DebugPrintOptions :
  81    {
  82        GameBoard : 0,
  83        Neighbors : 1,
  84        States    : 2
  85    },
  86
  87    // Maximum values for game board dimensions.
  88    GameSettings :
  89    {
  90        GameBoardNumCols   : 100,
  91        GameBoardNumRows   : 100,
  92        MaxFileLineLength  : 80,
  93        MaxNumCommentLines : 22,
  94        MaximumAge         : 10000,
  95        OldAge             : 50,
  96        UpdateIntervalMs   : 50
  97    },
  98
  99    getAccessToDOMElements: function()
 100    {
 101        // Get access to the document's canvas, control buttons, output windows, file select buttons, etc.
 102        this.GameOfLifeCanvas    = document.getElementById( "GameOfLifeCanvas" ) ;
 103        this.GameOfLifeState     = document.getElementById( "GameOfLifeState" ) ;
 104        this.GameOfLifeCellState = document.getElementById( "GameOfLifeCellState" ) ;
 105        this.GameOfLifeDebug     = document.getElementById( "GameOfLifeDebug" ) ;
 106        this.GameOfLifeLoadFile  = document.getElementById( "GameOfLifeLoadFile" ) ;
 107        this.GameOfLifeClipboard = document.getElementById( "GameOfLifeClipboard" ) ;
 108        this.GameOfLifePatterns  = document.getElementById( "GameOfLifePatterns" ) ;
 109
 110        // We have a group of boxes and radio buttons sharing the same name so we can fetch all of 
 111        // their values as an array.  We can't use id's since they are unique to a single element.
 112        this.GameOfLifeSurvivalRules = document.getElementsByName( "GameOfLifeSurvivalRules" ) ;
 113        this.GameOfLifeBirthRules    = document.getElementsByName( "GameOfLifeBirthRules" ) ;
 114    },
 115
 116    timer : undefined,
 117
 118    addEventListeners: function()
 119    {
 120        // Register the callback function which is called when mouse cursor is clicked anywhere on 
 121        // the canvas.
 122        this.GameOfLifeCanvas.addEventListener( "click", make_onCanvasMouseClick( this ), false ) ;
 123
 124        // Register the callback function which is called when the mouse moves in the canvas.
 125        this.GameOfLifeCanvas.addEventListener( "mousemove", make_onCanvasMouseMove( this ), false ) ;
 126
 127        // Register the callback function which is called when the LoadLifeFileButton button is 
 128        // clicked.  The function argument is an event which is the list of files selected.
 129        this.GameOfLifeLoadFile.addEventListener( "change", make_GameOfLifeLoadFile( this ), false ) ;
 130
 131        // Register the callback function for the GameOfLifePatterns form selector which is called when
 132        // a pattern is selected from the list.  The event contains the pattern string.
 133        this.GameOfLifePatterns.addEventListener( "change", make_loadSampleLifePattern( this ));
 134    }
 135}
 136
 137// Create a single global object for the Game of Life from the default object and link it into the prototype chain.
 138let gameOfLife = Object.create( gameOfLifeBase ) ;
 139
 140//================================================ Game Board Object ========================================================================
 141
 142// Create basic game of life object with default settings which has a prototype chain.
 143const GameBoardBase =
 144{
 145    GameOfLifeCanvas : undefined,
 146    widthPixels :  0,
 147    heightPixels : 0,
 148    graphicsContext  : undefined,
 149}
 150
 151// Create a single global object for the Game Board from the default object and link it into the prototype chain.
 152let gameBoard = Object.create( GameBoardBase )
 153
 154//===================================================== Game Board Objects ===========================================================
 155
 156//  Define some "enum" types as class let, e.g. if (x === Occupancy.Empty) ...
 157const Occupancy =
 158{
 159    Indeterminate : -1, // No occupancy state at the beginning.
 160    Empty         :  0, // Cell is empty.
 161    Occupied      :  1  // Cell has a counter in it.
 162} ;
 163
 164const State =
 165{
 166    Indeterminate : -1, // No state at the beginning.
 167    Survival      :  0, // Survives;  no change from last time.
 168    Birth         :  1, // Empty cell has a birth.
 169    Death         :  2  // Occupied cell dies.
 170
 171} ;
 172
 173//================================================ Game of Life Member Functions =====================================================
 174
 175// Attach the game board to the Game of Life App
 176gameOfLife.gameBoard = gameBoard
 177
 178// Initialize game of life app.
 179gameOfLife.init = function()
 180{
 181    this.getAccessToDOMElements() ;
 182    this.initGameBoard() ;
 183    this.preloadPatterns() ;
 184    this.addEventListeners() ;
 185}
 186
 187gameOfLife.initGameBoard = function()
 188{
 189    // Create a new game board which is the size of the canvas and pass it the canvas graphics context.
 190    this.gameBoard.init( this.GameSettings, make_debugPrint( this ), this.DebugPrintOptions, this.GameOfLifeCanvas, this.GameOfLifeState )
 191
 192    // Draw the life grid lines.
 193    this.gameBoard.drawLifeGrid() ;
 194
 195    // Clear the game state.
 196    this.gameBoard.clearGameState() ;
 197}
 198
 199gameOfLife.preloadPatterns = function()
 200{
 201    // Preload a few examples of life into this.listOfSampleLifePatterns.
 202    // Then load a glider gun.
 203    this.preloadLifePatterns() ;
 204    this.readLifeFile( this.sampleLifePatterns[ "glidergun" ] ) ;
 205
 206    // Work around a Firefox bug which doesn't select the default in the drop down form
 207    // when refreshing the page:
 208    //    https://stackoverflow.com/questions/4831848/firefox-ignores-option-selected-selected
 209    window.onload = function() { document.forms[ "GameOfLifePatternsForm" ].reset() } ;
 210
 211    // Write game to clipboard.
 212    this.writeGameToClipboard() ;
 213
 214    // Update all counters.
 215    this.gameBoard.updateView() ;
 216
 217    // Update the rules.
 218    this.updateRulesView() ;
 219}
 220
 221// Advance the game one generation.
 222gameOfLife.cycleGame = function ()
 223{
 224    // Update the game board.
 225    this.gameBoard.updateGameBoard() ;
 226
 227    // Repaint the canvas, but only for counters which have changed.
 228    this.gameBoard.updateView() ;
 229}
 230
 231// Callback function to clear the game state.
 232gameOfLife.clearGame = function ()
 233{
 234    this.gameBoard.clearGameState() ;
 235    this.gameBoard.updateView() ;
 236}
 237
 238// Callback function to change the life rules.  flag = true for survivals, false for births.
 239gameOfLife.changeRules = function( flag )
 240{
 241    let rulesElement = undefined ;
 242
 243    // Pick the rule type.
 244    if (flag)
 245        rulesElement = this.GameOfLifeSurvivalRules ;
 246    else
 247        rulesElement = this.GameOfLifeBirthRules ;
 248
 249    let numNeighbors = [] ;
 250    let numRules = 0 ;
 251
 252    // Iterate over the whole group of checkboxes for number of neighbors for either survivals or births.
 253    //
 254    //                                +-+        +-+              +-+
 255    //     Neighbors to survive     1 |X|      2 |X|  . . .     8 | |     rulesElement = HTML element which
 256    //                                +-+        +-+              +-+     contains this row of 8 boxes.
 257    //
 258    //                                +-+        +-+              +-+
 259    //     Neighbors for birth      1 | |      2 | |  . . .     8 | | 
 260    //                                +-+        +-+              +-+
 261    //
 262    //     boxNum                      0          1                7
 263    //   
 264    //   In this example, a cell can have either 1 or 2 neighbors to survive, so there are two rules.
 265    //   numRules           2
 266    //   numNeighbors       [1, 2]
 267    numBoxes = rulesElement.length ;
 268    for (let boxNum = 0 ;  boxNum < numBoxes ;  ++boxNum)
 269    {
 270        if (rulesElement[ boxNum ].checked)
 271        {
 272            numNeighbors[ numRules++ ] = boxNum + 1 ;
 273        }
 274    }
 275
 276    let rules = 
 277    {
 278        numNeighbors : numNeighbors,
 279        numRules     : numRules
 280    }
 281
 282    /* Aside:
 283       How would we have created the "rules" object using the phony class-based syntax which
 284       pretends Javascript has classes which instantiate objects using constructors?
 285
 286       This ignores that underneath, Javascript has NO classes, NO inheritance from parent class to child class, and NO object instantiation from a class.
 287       There are only objects dynamically connected by prototype chains.  Totally different language design concepts:  no wonder class/object people have problems.
 288
 289        //   Create a base rules object for the number of neighbors required 
 290        //   for either survivals or births.  For example,
 291        //       let survival_rules = new Rules( 2, [2, 3])
 292        //       let birth_rules    = new Rules( 1, [3])
 293        //
 294        //   This is the constructor function (by custom, the name starts with a capital letter).
 295        //   Is to be called using operator "new" to create the new rules object.
 296
 297        function Rules( numRules, neighborCounts )
 298        {
 299            // Allow up to 9 rules since we can have 0-8 neighbors.
 300            this.numNeighbors = new Array( 9 ) ;
 301
 302            // Fill all rules, leaving other undefined.
 303            for (let i = 0 ;  i < neighborCounts.length ;  ++i)
 304                this.numNeighbors[ i ] = neighborCounts[ i ] ;
 305
 306            this.numRules = numRules ;
 307        } ;
 308
 309        // Create a new "rules" object using the new constructor.
 310        let rules = new Rules( numRules, numNeighbors ) ;
 311    */
 312
 313    if (flag)
 314        this.gameBoard.rules.survival = rules ;
 315    else
 316        this.gameBoard.rules.birth = rules ;
 317
 318    this.gameBoard.updateView() ;
 319}
 320
 321// Update the rules view.
 322gameOfLife.updateRulesView = function()
 323{
 324    // Uncheck all the boxes first.
 325    for (let boxNum = 0 ;  boxNum < this.GameOfLifeSurvivalRules.length ;  ++boxNum)
 326        this.GameOfLifeSurvivalRules[ boxNum ].checked = false ;
 327
 328    // Go through all the rules, checking boxes with number of neighbors.
 329    for (let i = 0 ; i < this.gameBoard.rules.survival.numRules ;  ++i)
 330        this.GameOfLifeSurvivalRules[ this.gameBoard.rules.survival.numNeighbors[ i ] - 1].checked = true ;
 331
 332    // Uncheck all the boxes first.
 333    for (let boxNum = 0 ;  boxNum < this.GameOfLifeBirthRules.length ;  ++boxNum)
 334        this.GameOfLifeBirthRules[ boxNum ].checked = false ;
 335
 336    // Go through all the rules, checking boxes with number of neighbors.
 337    for (let i = 0 ; i < this.gameBoard.rules.birth.numRules ;  ++i)
 338        this.GameOfLifeBirthRules[ this.gameBoard.rules.birth.numNeighbors[ i ] - 1].checked = true ;
 339}
 340
 341// Save the game to the clipboard.
 342gameOfLife.writeGameToClipboard = function()
 343{
 344    this.GameOfLifeClipboard.value = this.writeLifeFile( this.gameBoard ) ;
 345}
 346
 347// Read a game from the clipboard.
 348gameOfLife.readGameFromClipboard = function()
 349{
 350    // Clear out the game board, load the file from the clipboard area, update the gameboard view, status and rules.
 351    this.gameBoard.clearGameState() ;
 352    this.readLifeFile( this.GameOfLifeClipboard.value ) ;
 353    this.gameBoard.updateView() ;
 354    this.updateRulesView() ;
 355}
 356
 357// Enable or disable the timer for running the game.
 358gameOfLife.runStopGame = function()
 359{
 360    if (this.timer === undefined)
 361        this.timer = setInterval( make_cycleGame( this ), this.GameSettings.UpdateIntervalMs ) ;
 362    else
 363    {
 364        clearInterval( this.timer ) ;
 365        this.timer = undefined ;
 366    }
 367}
 368
 369// Callback function to single step the game.
 370gameOfLife.singleStepGame = function()
 371{
 372    // Stop the game from running by disabling th timer.
 373    if (this.timer !== undefined)
 374    {
 375        clearInterval( this.timer ) ;
 376        this.timer = undefined ;
 377    }
 378
 379    this.cycleGame() ;
 380}
 381
 382// Debug print the game board counters.
 383gameOfLife.printGameBoard = function()
 384{
 385    let numRows = this.gameBoard.numRows ;
 386    let numCols = this.gameBoard.numCols ;
 387
 388    // Display the game board counters.
 389    let text = "Game Board\n" ;
 390    for (let row = 0 ;  row < numRows ;  ++row)
 391    {
 392        // Up to 5 digit number with padding.  Concatenate blanks to the front of the number, then take 5 chars back from the end.
 393        text += String( "     " + row ).slice( -5 ) ;
 394        text += ":" ;
 395        for (let col = 0 ;  col < numCols ;  ++col)
 396        {
 397            let cell = this.gameBoard.cell[ row ][ col ] ;
 398            if (cell.occupied === Occupancy.Occupied)
 399                text += "O" ;
 400            else
 401                text += "." ;
 402        }
 403        text += "\n" ;
 404    }
 405
 406    return text ;
 407}
 408
 409// Debug print the game board neighbor counts.
 410gameOfLife.printNeighborCounts = function()
 411{
 412    let numRows = this.gameBoard.numRows ;
 413    let numCols = this.gameBoard.numCols ;
 414
 415    // Display the neighbor counts.
 416    let text = "Neighbor counts\n" ;
 417    for (let row = 0 ;  row < numRows ;  ++row)
 418    {
 419        // Up to 5 digit number with padding.  Concatenate blanks to the front of the number, then take 5 chars back from the end.
 420        text += String( "     " + row ).slice( -5 ) ;
 421        text += ":" ;
 422        for (let col = 0 ;  col < numCols ;  ++col)
 423        {
 424            let cell = this.gameBoard.cell[ row ][ col ] ;
 425            let num  = cell.numberOfNeighbors ;
 426            text += (num === 0 ? "." : num) ;
 427        }
 428        text += "\n" ;
 429    }
 430    return text ;
 431}
 432
 433// Debug print the game board counter states.
 434gameOfLife.printCounterState = function()
 435{
 436    let numRows = this.gameBoard.numRows ;
 437    let numCols = this.gameBoard.numCols ;
 438
 439    // Display the counter states.
 440    let text = "Counter state\n" ;
 441    for (let row = 0 ;  row < numRows ;  ++row)
 442    {
 443        // Up to 5 digit number with padding.  Concatenate blanks to the front of the number, then take 5 chars back from the end.
 444        text += String( "     " + row ).slice( -5 ) ;
 445        text += ":" ;
 446        for (let col = 0 ;  col < numCols ;  ++col)
 447        {
 448            let cell = this.gameBoard.cell[ row ][ col ] ;
 449            if (cell.state === State.Birth)
 450                text += "B" ;
 451            else if (cell.state === State.Survival)
 452                text += "s" ;
 453            else if (cell.state === State.Death)
 454                text += "d" ;
 455            else
 456                text += "." ;
 457        }
 458        text += "\n" ;
 459    }
 460
 461    return text ;
 462}
 463
 464// A small collection of life patterns.
 465gameOfLife.preloadLifePatterns = function()
 466{
 467    // Just load these sample life patterns, indexed by name into an associative array.
 468
 469    this.sampleLifePatterns =
 470    {
 471        glidergun :
 472
 473        "#Life 1.05\n" +
 474        "#D p30 glider gun (the Original)\n" +
 475        "#D This is made of two of a pattern\n" +
 476        "#D known as the \"queen bee\", which\n" +
 477        "#D sometimes occurs naturally,\n" +
 478        "#D whose debris can be deleted on\n" +
 479        "#D the sides by blocks or eaters.\n" +
 480        "#D But a collision in the center\n" +
 481        "#D can, as seen here, miraculously \n" +
 482        "#D form a glider. Just one of these\n" +
 483        "#D moving back and forth is called\n" +
 484        "#D piston (see the p30 in OSCSPN2).\n" +
 485        "#D  I added an eater at the bottom right.\n" +
 486        "#N\n" +
 487        "#P 4 -5\n" +
 488        "....*\n" +
 489        ".****\n" +
 490        "****\n" +
 491        "*..*\n" +
 492        "****\n" +
 493        ".****\n" +
 494        "....*\n" +
 495        "#P 13 -4\n" +
 496        "*\n" +
 497        "*\n" +
 498        "#P -6 -3\n" +
 499        "..*\n" +
 500        ".*.*\n" +
 501        "*...**\n" +
 502        "*...**\n" +
 503        "*...**\n" +
 504        ".*.*\n" +
 505        "..*\n" +
 506        "#P 17 -2\n" +
 507        "**\n" +
 508        "**\n" +
 509        "#P -17 0\n" +
 510        "**\n" +
 511        "**\n" +
 512        "#P 42 40\n" +
 513        "**\n" +
 514        "*.*\n" +
 515        "..*\n" +
 516        "..**\n" +
 517        "",
 518
 519        replicator :
 520
 521        "#Life 1.05\n" +
 522        "#D In February 1994, Nathan Thompson reported several interesting objects\n" +
 523        "#D that he found in a cellular automaton closely related to Conway's Life.\n" +
 524        "#D The reason that HighLife has been investigated so much is because of the\n" +
 525        "#D object known as the 'replicator'.  This amazing object starts with only\n" +
 526        "#D six live cells as shown in figure 2.  See 'HighLife - An Interesting\n" +
 527        "#D Variant of Life (part 1/3)', by David I. Bell, dbell@canb.auug.org.au,\n" +
 528        "" +
 529        "#D 7 May 1994.\n" +
 530        "" +
 531        "#R 23/36\n" +
 532        "" +
 533        "#P -2 -2\n" +
 534        "" +
 535        ".***\n" +
 536        "" +
 537        "*...\n" +
 538        "" +
 539        "*...\n" +
 540        "" +
 541        "*...\n" +
 542        "" +
 543        "",
 544        
 545        crab :
 546
 547        "#Life 1.05\n" +
 548        "" +
 549        "#D Name: Crab\n" +
 550        "" +
 551        "#D Author: Jason Summers\n" +
 552        "" +
 553        "#D The smallest known diagonal spaceship other than the glider. It was discovere\n" +
 554        "" +
 555        "#D d in September 2000.\n" +
 556        "" +
 557        "#D www.conwaylife.com/wiki/index.php?title=Crab\n" +
 558        "#N\n" +
 559        "#P -6 -6\n" +
 560        "........**\n" +
 561        ".......**\n" +
 562        ".........*\n" +
 563        "...........**\n" +
 564        "..........*\n" +
 565        ".\n" +
 566        ".........*..*\n" +
 567        ".**.....**\n" +
 568        "**.....*\n" +
 569        "..*....*.*\n" +
 570        "....**..*\n" +
 571        "....**\n" +
 572        "",
 573
 574        shickengine :
 575
 576        "#Life 1.05\n" +
 577        "#D Name: Schick engine\n" +
 578        "#D Author: Paul Schick\n" +
 579        "#D An orthogonal c/2 tagalong found in 1972.\n" +
 580        "#D www.conwaylife.com/wiki/index.php?title=Schick_engine\n" +
 581        "#N\n" +
 582        "#P -11 -4\n" +
 583        "****\n" +
 584        "*...*\n" +
 585        "*\n" +
 586        ".*..*\n" +
 587        "#P -5 -2\n" +
 588        "..*\n" +
 589        ".*******\n" +
 590        "**.***..*\n" +
 591        ".*******\n" +
 592        "..*\n" +
 593        "#P -7 -1\n" +
 594        "*\n" +
 595        "#P -11 1\n" +
 596        ".*..*\n" +
 597        "*\n" +
 598        "*...*\n" +
 599        "****\n" +
 600        "#P -7 1\n" +
 601        "*\n" +
 602        "",
 603
 604        trafficcircle :
 605
 606        "#Life 1.05\n" +
 607        "#D Traffic circle from http://www.radicaleye.com/lifepage/picgloss/picgloss.html\n" +
 608        "#N\n" +
 609        "#P -25 -25\n" +
 610        "......................**....**..........................\n" +
 611        "......................*.*..*.*...................\n" +
 612        "........................*..*.....................\n" +
 613        ".......................*....*....................\n" +
 614        ".......................*....*....................\n" +
 615        ".......................*....*....................\n" +
 616        ".........................**.....**...............\n" +
 617        "................................***..............\n" +
 618        "................................**.*.............\n" +
 619        "..................................*.*............\n" +
 620        "..........................***....*..*............\n" +
 621        "..................................**.............\n" +
 622        "..........**............*.....*..................\n" +
 623        ".........*..*...........*.....*..................\n" +
 624        ".......*..*.*...........*.....*..................\n" +
 625        "...........*.....................................\n" +
 626        ".......*.**...............***....................\n" +
 627        "........*.....*..................................\n" +
 628        "..............*..................................\n" +
 629        ".**...........*..................................\n" +
 630        ".*..***..........................................\n" +
 631        "..**......***...***............................**\n" +
 632        ".......*...................................***..*\n" +
 633        ".......*......*...............................**.\n" +
 634        "..**..........*........*..................*......\n" +
 635        ".*..***.......*......**.**............*...*......\n" +
 636        ".**....................*............**.**.....**.\n" +
 637        "......................................*....***..*\n" +
 638        "...............................................**\n" +
 639        ".................................................\n" +
 640        ".......................................*.*.......\n" +
 641        ".....................***..................*......\n" +
 642        "......................................*..*.......\n" +
 643        "...................*.....*...........*.*.*.......\n" +
 644        "...................*.....*...........*..*........\n" +
 645        "...................*.....*............**.........\n" +
 646        "..............**.................................\n" +
 647        ".............*..*....***.........................\n" +
 648        ".............*.*.*...............................\n" +
 649        "..............*.***..............................\n" +
 650        "................***..............................\n" +
 651        ".......................**........................\n" +
 652        ".....................*....*......................\n" +
 653        ".....................*....*......................\n" +
 654        ".....................*....*......................\n" +
 655        "......................*..*.......................\n" +
 656        "....................*.*..*.*.....................\n" +
 657        "....................**....**.....................\n" +
 658        "",
 659
 660        highlifeglidergun :
 661
 662        "#Life 1.05\n" +
 663        "#D Period 96 replicator based glider gun by David Bell.\n" +
 664        "#D --- The smallest known glider gun based on replicators.\n" +
 665        "#D A block perturbs the replicator to produce the glider,\n" +
 666        "#D while a period 2 clock oscillator prevents a spark \n" +
 667        "#D from being formed that would modify the block.  \n" +
 668        "#D One glider is shown where it was just created.\n" +
 669        "#D From HighLife - An Interesting Variant of Life \n" +
 670        "#D (part 1/3) by David I. Bell, dbell@canb.auug.org.au\n" +
 671        "#D 7 May 1994\n" +
 672        "#R 23/36\n" +
 673        "#P -18 -14\n" +
 674        "**...................................\n" +
 675        "**...................................\n" +
 676        "..............*......................\n" +
 677        ".............***.....................\n" +
 678        "............**.**....................\n" +
 679        "...........**.**.....................\n" +
 680        "..........**.**......................\n" +
 681        "...........***.......................\n" +
 682        "............*........................\n" +
 683        ".....................................\n" +
 684        ".....................................\n" +
 685        ".....................................\n" +
 686        ".....................................\n" +
 687        ".....................................\n" +
 688        ".....................................\n" +
 689        ".....................................\n" +
 690        ".....................................\n" +
 691        ".....................................\n" +
 692        ".........................**......**..\n" +
 693        "........................*.*......**..\n" +
 694        "..........................*..........\n" +
 695        ".....................................\n" +
 696        "...................................*.\n" +
 697        ".................................*.*.\n" +
 698        "..................................*.*\n" +
 699        ".........................**.......*..\n" +
 700        ".........................**..........\n" +
 701        "#P -5 15\n" +
 702        "..**\n..*\n" +
 703        "*.*\n" +
 704        "**\n" +
 705        ""
 706    } ;
 707}
 708
 709
 710//=========================================== Game of Life Member Functions:  File Reading ===========================================
 711
 712// Read a Game of Life file in 1.05 format.
 713//
 714// Use a recursive descent parser, similar to awk parser from
 715//        THE AWK PROGRAMMING LANGUAGE, Aho, Kernighan, Weinberger, pgs. 147-152.
 716//
 717// Here is an explanation for the Life 1.05 format from
 718//
 719//     http://www.mirekw.com/ca/ca_files_formats.html
 720//
 721// This ASCII format just draws the pattern with "." and "*" symbols. The line length should not
 722// exceed 80 characters.
 723//
 724// The "#Life" line is followed by optional description lines, which begin with "#D" and are
 725// followed by no more than 78 characters of text. Leading and trailing spaces are ignored,
 726// so the following two "#D" lines are equivalent:
 727//
 728//     #D This is a Description line
 729//     #D     This is a Description line
 730//     There should be no more than 22 "#D" lines in a .LIF file.
 731//
 732// Next comes an optional rule specification. If no rules are specified, then the pattern will
 733// run with whatever rules the Life program is currently set to. The patterns in the collection
 734// here enforce "Normal" Conway rules using the "#N" specifier. Alternate rules use
 735// "#R" ("#N" is exactly the same as "#R 23/3"). Rules are encoded as Survival/Birth,
 736// each list being a string of digits representing neighbor counts. Since there are exactly
 737// eight possible neighbors in a Conway-like rule, there is no need to separate the digits,
 738// and "9" is prohibited in both lists. For example,
 739//
 740//     #R 125/36
 741//
 742// means that the pattern should be run in a universe where 1, 2, or 5 neighbors are necessary
 743// for a cell's survival, and 3 or 6 neighbors allows a cell to come alive.
 744//
 745// Next come the cell blocks. Each cell block begins with a "#P" line, followed by "x y"
 746// coordinates of the upper-left hand corner of the block, assuming that 0 0 is the center
 747// of the current window to the Life universe.
 748//
 749// This is followed by lines that draw out the pattern in a visual way, using the "." and "*"
 750// characters (off, on). Each line must be between 1 and 80 characters wide, inclusive;
 751// therefore, a blank line is represented by a single dot, whereas any other line may truncate
 752// all dots to the right of the last "*". There is no limit to the number of lines in a cell block.
 753//
 754// Any line of zero length (just another carriage return) is completely ignored. Carriage returns
 755// are MSDOS-style (both 10 and 13).
 756//
 757// For example, a glider in Life1.05 format is saved as:
 758//
 759//     #Life 1.05
 760//
 761//     ***
 762//     *..
 763//     .*.
 764//
 765// Life 1.05 format was designed to be easily ported. You can just look at a pattern in this format
 766// in a text editor, and figure out what it is.
 767//
 768// See also http://www.conwaylife.com/wiki/Life_1.05
 769//
 770gameOfLife.readLifeFile = function( fileText )
 771{
 772    let lineOfFile = null ;
 773
 774    // Create a function to return the next line of the file.
 775    let readLine = make_readNextLine( fileText ) ;
 776
 777    // Read the file, catching any exceptions thrown during the read.
 778    try
 779    {
 780        // Eat the version number.
 781        this.parseVersionNumber( readLine() ) ;
 782
 783        // Read comment lines.
 784        let numCommentLines = 0 ;
 785        while (this.parseCommentLine( lineOfFile = readLine() ))
 786        {
 787            this.gameBoard.comment[ numCommentLines ] = lineOfFile ;
 788
 789            if (++numCommentLines > this.gameBoard.maxNumCommentLines)
 790                throw RangeError( "too many comment lines " + numCommentLines + " > " + this.gameBoard.maxNumCommentLines ) ;
 791        }
 792        this.gameBoard.numCommentLines = numCommentLines ;
 793
 794        // Read the optional rules line.
 795        let rules = this.parseRules( lineOfFile ) ;
 796        if (rules)
 797        {
 798            // It was a rules line, so fetch the next line.
 799            this.gameBoard.rules.survival = rules[ 0 ] ;
 800            this.gameBoard.rules.birth    = rules[ 1 ] ;
 801            lineOfFile = readLine() ;
 802        }
 803        else
 804        {
 805            // It wasn't a rules line:  just use the Conway rules.
 806            this.gameBoard.rules.survival = { numRules : 2, numNeighbors : [ 2, 3, , , , , , , ] } ;
 807            this.gameBoard.rules.birth    = { numRules : 1, numNeighbors : [ 3,  , , , , , , , ] } ;
 808        }
 809
 810        // Read sequences of life pattern locations and patterns.
 811        // End of file will throw an exception to break us out of the loop.
 812        for(;;)
 813        {
 814            // Pattern location.
 815            let patternLocation = this.parsePatternLocation( lineOfFile ) ;
 816
 817            if (!patternLocation)
 818                throw SyntaxError( "cannot parse pattern location line " + lineOfFile ) ;
 819
 820            // Rows of pattern lines.
 821            let patternRow = 0 ;
 822            while (this.parsePatternLine( lineOfFile = readLine(), patternLocation, patternRow, this.gameBoard ))
 823                ++patternRow ;
 824        }
 825    }
 826    catch( e )
 827    {
 828        if (e instanceof RangeError)
 829        {
 830            // End of file (actually end of string).
 831            if (e.message === "end of file")
 832                return true ;
 833            // A real error!
 834            else
 835            {
 836                alert( "ERROR in reading file: " + e.message ) ;
 837                return false ;
 838            }
 839        }
 840        // Some error got thrown above when parsing the file.
 841        else if (e instanceof SyntaxError )
 842        {
 843            alert( "ERROR in reading file: " + e.message ) ;
 844            return false ;
 845        }
 846    }
 847
 848    return true ;
 849}
 850
 851// Return true if the version number of a line is Life 1.05
 852gameOfLife.parseVersionNumber = function( lineOfFile )
 853{
 854    if (!lineOfFile.match( /^#Life 1\.05/))
 855        throw  "life file version number " + lineOfFile + " is not 1.05"  ;
 856}
 857
 858// Parse one line of a life file to see if it is a comment line.
 859// i.e. of the form
 860//     #D<sequence of characters>
 861gameOfLife.parseCommentLine = function( lineOfFile )
 862{
 863    if (lineOfFile.match( "^#D"))
 864        return true ;
 865
 866    return false ;
 867}
 868
 869// Parse a rules line.  There are two forms:
 870// (1) The normal Conway life rules
 871//          #N
 872// (2) Arbitrary rule where we list d1 ... neighbors for survival and D1 ... neighbors for a birth.
 873//          #R d1 d2 ... / D1 D2 ...
 874//     e.g. the Conway rules are encoded as
 875//          #R 23/3.
 876//     specifes number of neighbors for survival is 2 or 3 and number for a birth is 3.
 877gameOfLife.parseRules = function( lineOfFile )
 878{
 879    let survival, birth ;
 880
 881    // Return if we don't see a rules line.
 882    if (!lineOfFile.match( /^\s*#[NR]/ ))
 883        return null ;
 884
 885    // Normal Conway rules.
 886    if (lineOfFile.match( /^\s*#N/ ))
 887    {
 888        survival = { numRules : 2, numNeighbors : [ 2, 3, , , , , , , ] } ;  // Empty entries are undefined.
 889        birth    = { numRules : 1, numNeighbors : [ 3,  , , , , , , , ] } ;
 890        return [ survival, birth ] ;
 891    }
 892
 893    // Other rules of the type #R single digit list of neighbors for survivals / ... for births.
 894    let rulePattern = /^\s*#R\s*(\d+)\/(\d+)/ ;
 895
 896    // List ought to have three pieces, the match and two strings of digits:  [ "#R 23/3", "23", "3" ].
 897    let rulesString = lineOfFile.match( rulePattern ) ;
 898    if (rulesString === null || rulesString.length != 3)
 899        return null ;
 900
 901    return [ this.parseRulesList( rulesString[ 1 ] ), this.parseRulesList( rulesString[ 2 ] ) ] ;
 902}
 903
 904// Parse a rules list into a rules object.
 905gameOfLife.parseRulesList = function( rulesString )
 906{
 907    // Count survivals.
 908    let neighborCountsString = rulesString ;
 909    let numNeighbors = Array( 9 ) ;
 910    let numRules = rulesString.length ;
 911
 912    for (let i = 0 ;  i < numRules ;  ++i)
 913        numNeighbors[ i ] = parseInt( neighborCountsString.charAt( i ) ) ;
 914
 915    let rules = 
 916    {
 917        numNeighbors : numNeighbors,
 918        numRules     : numRules
 919    }
 920    return rules ;
 921}
 922
 923// Parse one line of a life file to see if it is a cell block
 924// of the form
 925//     #P <integer x coordinate> <integer y coordinate>
 926// Coordinates can have optional + or - in front, whitespace delimiters.
 927// e.g.
 928//     #P -2 2
 929gameOfLife.parsePatternLocation = function( lineOfFile )
 930{
 931    let rulePattern = /^\s*#P\s*([+-]*\d+)\s*([+-]*\d+)/ ;
 932
 933    // List ought to have three pieces, the match and two digits.
 934    let patternString = lineOfFile.match( rulePattern ) ;
 935    if (patternString === null || patternString.length != 3)
 936        return null ;
 937
 938    // Return the x and y coordinates.
 939    let point = { x:0, y:0 } ;
 940    point.x = parseInt( patternString[ 1 ] ) ;
 941    point.y = parseInt( patternString[ 2 ] ) ;
 942    return point ;
 943}
 944
 945// Parse one line of a life file to see if it is a pattern line of the form of * or .   e.g.
 946//
 947//    ...*
 948//    ....*
 949//    ....*
 950//    .****
 951//
 952// Any other character other than whitespace is an error.  Fill in the game board while parsing.
 953// If we go outside the bounds of the game board, throw an error.
 954gameOfLife.parsePatternLine = function( lineOfFile, patternLocation, patternRow, gameBoard )
 955{
 956    //  Middle row of the game board.
 957    let centerRow = gameBoard.numRows / 2 ;
 958    let centerCol = gameBoard.numCols / 2 ;
 959
 960    //  Fill in occupied cells in the game board.
 961    for (let col = 0 ; col < lineOfFile.length ;  ++col)
 962    {
 963        let counter = lineOfFile.charAt( col ) ;
 964
 965        // Record an occupied cell in the game board.
 966        if (counter === "*")
 967        {
 968            // Counter is offset by cell block upper left corner
 969            // coordinates and by row number of the pattern line.
 970            counterCol = centerCol + patternLocation.x + col ;
 971            counterRow = centerRow + patternLocation.y + patternRow ;
 972
 973            //  Out of bounds counter check.
 974            if (counterRow < 0 || counterRow >= gameBoard.numRows ||
 975                counterCol < 0 || counterCol >= gameBoard.numCols)
 976            {
 977                throw "Game pattern out of bounds at pattern location row " + patternLocation.y + " col " + patternLocation.x +
 978                       " at counter row " + counterRow + " col " + counterCol ;
 979            }
 980
 981            //  Flip the counter occupancy flag.
 982            gameBoard.cell[ counterRow ][ counterCol ].occupied = Occupancy.Occupied ;
 983        }
 984        // Ignore . or whitespace.
 985        else if (counter === "." || counter === " " || counter === "\r" || counter === "\t" || counter === "\n" )
 986            ;
 987        // Don't expect any other characters.
 988        else
 989            return false ;
 990    }
 991
 992    return true ;
 993}
 994
 995//=========================================== Game of Life Member Functions:  File Writing ===========================================
 996
 997// Write the life game board to file..
 998gameOfLife.writeLifeFile = function( gameBoard )
 999{
1000    let fileText = "#Life 1.05\n" ;
1001
1002    // Write out comments.
1003    for (let row = 0 ;  row < gameBoard.numCommentLines ;  ++row)
1004        fileText += (gameBoard.comment[ row ] + "\n") ;
1005
1006    // These are the John Horton Conway default life rules.
1007    if (gameBoard.rules.birth.numRules             === 1 &&
1008        gameBoard.rules.birth.numNeighbors[ 0 ]    === 3 &&
1009        gameBoard.rules.survival.numRules          === 2 &&
1010        gameBoard.rules.survival.numNeighbors[ 0 ] === 2 &&
1011        gameBoard.rules.survival.numNeighbors[ 1 ] === 3)
1012    {
1013        // Write the normal rules line.
1014        fileText += "#N\n" ;
1015    }
1016    // Write the full rules line.
1017    else
1018    {
1019        fileText += "#R " ;
1020
1021        //  Write the survival rules first.
1022        for (let i = 0 ;  i < gameBoard.rules.survival.numRules ; ++i)
1023            fileText += gameBoard.rules.survival.numNeighbors[ i ] ;
1024
1025        fileText += "/" ;
1026
1027        //  Write the birth rules.
1028        for (let i = 0 ;  i < gameBoard.rules.birth.numRules ; ++i)
1029            fileText += gameBoard.rules.birth.numNeighbors[ i ] ;
1030
1031        fileText += "\n" ;
1032    }
1033
1034    // Find all the connected components in the game board.
1035    let boundingBoxes = this.traverseGameBoard( gameBoard ) ;
1036
1037    // Split boxes which are too wide.
1038    for (let i = 0 ;  i < boundingBoxes.length ;  ++i)
1039    {
1040        let box = boundingBoxes[ i ] ;
1041
1042        //  Box is too wide.
1043        if (box.right - box.left > this.GameSettings.MaxFileLineLength)
1044        {
1045            // Split off the left piece.
1046            boundingBoxes[ i ].left   = box.left ;
1047            boundingBoxes[ i ].top    = box.top ;
1048            boundingBoxes[ i ].bottom = box.bottom ;
1049            boundingBoxes[ i ].right  = box.left + this.GameSettings.MaxFileLineLength - 1 ;
1050
1051            // Split off the right piece, which may still be too large, and append it
1052            // to the end, where it will be processed later.
1053            let boxRight = [] ;
1054            boxRight.left   = box.left + this.GameSettings.MaxFileLineLength ;
1055            boxRight.top    = box.top ;
1056            boxRight.bottom = box.bottom ;
1057            boxRight.right  = box.right ;
1058            boundingBoxes.push( boxRight ) ;
1059        }
1060    }
1061
1062    //  Middle of the game board.
1063    let centerRow = gameBoard.numRows / 2 ;
1064    let centerCol = gameBoard.numCols / 2 ;
1065
1066    // Now that we have all the bounding boxes, write the pattern blocks.
1067    for (i = 0 ;  i < boundingBoxes.length ;  ++i)
1068    {
1069        let box = boundingBoxes[ i ] ;
1070
1071        // Write out the pattern upper left corner offset from game board center.
1072        let patternRow = box.top  - centerRow ;
1073        let patternCol = box.left - centerCol ;
1074        fileText += ("#P " + patternCol + " " + patternRow + "\n") ;
1075
1076        // Write out rows of patterns for this block.
1077        for (let row = box.top ;  row <= box.bottom ;  ++row)
1078            fileText += this.createPatternLine( box, row ) ;
1079    }
1080
1081    return fileText ;
1082}
1083
1084// Label all the clusters of counters in the game board.
1085gameOfLife.traverseGameBoard = function()
1086{
1087    let numRows = this.gameBoard.numRows ;
1088    let numCols = this.gameBoard.numCols ;
1089
1090    //  Clear the game board connectivity information in all cells (zero all labels, unmark all edges, zero all father links).
1091    for (let row = 0 ;  row < numRows ;  ++row)
1092    {
1093        for (let col = 0 ;  col < numCols ;  ++col)
1094        {
1095            this.gameBoard.cell[ row ][ col ].label  =  0 ;
1096            this.gameBoard.cell[ row ][ col ].edge   =  0 ;
1097            this.gameBoard.cell[ row ][ col ].father =  0 ;
1098        }
1099    }
1100
1101    // Label all cells in the game board and return an array of bounding boxes for all clusters.
1102    let startLabel = 1 ;
1103    let boundingBoxes = [] ;
1104
1105    for (let row = 0 ;  row < numRows ;  ++row)
1106    {
1107        for (let col = 0 ;  col < numCols ;  ++col)
1108        {
1109            //  Cell is occupied but not labelled.  Find its cluster and return the bounding box.
1110            if (this.gameBoard.cell[ row ][ col ].occupied === Occupancy.Occupied && this.gameBoard.cell[ row ][ col ].label === 0)
1111            {
1112                boundingBoxes.push( this.depthFirstTraversal( row, col, startLabel++ ) ) ;
1113            }
1114            // Else skip over the cell because it is labelled already or is empty, i.e. it is a background cell.
1115        }
1116    }
1117
1118     return boundingBoxes ;
1119}
1120
1121// Starting from (row, col) in the game board at an occupied cell,
1122// label the cluster of cells connected to it, and return a bounding
1123// box for the cluster.
1124gameOfLife.depthFirstTraversal = function( row, col, label )
1125{
1126    // Size of game board.
1127    let numRows = this.gameBoard.numRows ;
1128    let numCols = this.gameBoard.numCols ;
1129
1130    // Label this cell as the starting cell.
1131    this.gameBoard.cell[ row ][ col ].label = -1 ;
1132
1133    // Bounding box is at least one cell's dimensions.
1134    let boundingBox =
1135    {
1136        top    : row,
1137        bottom : row,
1138        left   : col,
1139        right  : col
1140    } ;
1141
1142    let nextRow ;
1143    let nextCol ;
1144
1145    for (;;)
1146    {
1147        //  Find the next unmarked edge.
1148        let edge = this.nextUnmarkedEdge( row, col ) ;
1149
1150        // If there is an unmarked edge, investigate this direction.
1151        if (edge > 0)
1152        {
1153            // Get the location of the next cell.
1154            [nextRow, nextCol] = this.nextCell( row, col, edge ) ;
1155
1156            // Mark the edge of the current and next cell.
1157            this.markEdges( row, col, nextRow, nextCol, edge ) ;
1158
1159            // The next cell is occupied and unlabeled and on the game board.
1160            if ( (nextRow < numRows && nextCol < numCols && 0 <= nextRow && 0 <= nextCol) &&
1161                this.gameBoard.cell[ nextRow ][ nextCol ].occupied === Occupancy.Occupied &&
1162                this.gameBoard.cell[ nextRow ][ nextCol ].label    === 0)
1163            {
1164                //  Record the father of the cell.
1165                this.gameBoard.cell[ nextRow ][ nextCol ].father =
1166                    this.encodeFather( nextRow, nextCol, row, col ) ;
1167
1168                //  Label the cell.
1169                this.gameBoard.cell[ nextRow ][ nextCol ].label = label ;
1170
1171                //  Record the maximum excursions for the bounding box.
1172                boundingBox.top    = Math.min( boundingBox.top,    nextRow ) ;
1173                boundingBox.bottom = Math.max( boundingBox.bottom, nextRow ) ;
1174                boundingBox.left   = Math.min( boundingBox.left,   nextCol ) ;
1175                boundingBox.right  = Math.max( boundingBox.right,  nextCol ) ;
1176
1177                //  Step to the next cell.
1178                row = nextRow ;  col = nextCol ;
1179            }
1180            //  Rebound:  New cell was either already labelled or unoccupied,
1181            //  Pretend we've traversed the edge twice.
1182            //  NOTE:  We treat cells off the gameboard as unoccupied.
1183            //  We'll sometimes break apart clusters which would have been
1184            //  connected on our toroidal game board.
1185            //  But that just means we have more possible clusters;  the cells
1186            //  and their properties aren't affected.
1187            else ;
1188        }
1189        // All edges are marked.  Backtrack.
1190        else
1191        {
1192            //  We're back at the beginning.
1193            if (this.gameBoard.cell[ row ][ col ].label === -1)
1194            {
1195                //  Relabel the start label correctly.
1196                this.gameBoard.cell[ row ][ col ].label = label ;
1197                break ;
1198            }
1199
1200            //  Backtrack along a father edge.
1201            edge = this.gameBoard.cell[ row ][ col ].father ;
1202            [row, col] = this.nextCell( row, col, edge ) ;
1203        }
1204    } // end forever loop
1205
1206    return boundingBox ;
1207}
1208
1209// In the next few functions, we will be encoding edges at a vertex by bitmaps.  The encoding is
1210//
1211//    8  4  2
1212//     \ | /
1213//  16 - C - 1
1214//     / | \
1215//   32 64 128
1216
1217// Return the first unmarked edge in a counterclockwise scan starting from the right edge.
1218// e.g. if edge = 11110011, next unmarked edge returns 4.
1219gameOfLife.nextUnmarkedEdge = function( row, col )
1220{
1221    let mask = 1 ;
1222    let edge = this.gameBoard.cell[ row ][ col ].edge ;
1223
1224    for (let bit = 0 ;  bit < 8 ;  ++bit)
1225    {
1226        if ((mask & edge) === 0)
1227            return mask ;
1228        mask <<= 1 ;
1229    }
1230
1231    return 0 ; // No unmarked edges.
1232}
1233
1234// Mark the edge of a cell at (row, col) in the game board in the direction
1235// (row, col) to (nextRow, nextCol).  Also mark the edge at (nextRow, nextCol)
1236// in the direction to (row, col).
1237gameOfLife.markEdges = function( row, col, nextRow, nextCol, edge )
1238{
1239    let numRows = this.gameBoard.numRows ;
1240    let numCols = this.gameBoard.numCols ;
1241
1242    // Mark the edge from (row, col) to (nextRow, nextCol).
1243    this.gameBoard.cell[ row ][ col ].edge |= edge ;
1244
1245    // Encode and mark the edge from the other direction:  from (nextRow, nextCol) to (row, col).
1246    let oppositeEdge = 0 ;
1247    switch( edge )
1248    {
1249        case   0: oppositeEdge =   0 ; break ;
1250        case   1: oppositeEdge =  16 ; break ;
1251        case   2: oppositeEdge =  32 ; break ;
1252        case   4: oppositeEdge =  64 ; break ;
1253        case   8: oppositeEdge = 128 ; break ;
1254        case  16: oppositeEdge =   1 ; break ;
1255        case  32: oppositeEdge =   2 ; break ;
1256        case  64: oppositeEdge =   4 ; break ;
1257        case 128: oppositeEdge =   8 ; break ;
1258        default: break ;
1259    }
1260
1261    // But only mark the edge if it is within the game board.
1262    if (nextRow < numRows && nextCol < numCols && 0 <= nextRow && 0 <= nextCol)
1263        this.gameBoard.cell[ nextRow ][ nextCol ].edge |= oppositeEdge ;
1264}
1265
1266// Given a cell at (row, col) in the game board and an edge direction, edge,
1267// find the next cell location at the other end of the edge.
1268// Note from above, we don't mark edges which cause us to move outside the gameboard.
1269gameOfLife.nextCell = function( row, col, edge )
1270{
1271    let nextRow = nextCol = 0 ;
1272
1273    switch( edge )
1274    {
1275        case   1: nextRow = row     ;  nextCol = col + 1 ;  break ;
1276        case   2: nextRow = row - 1 ;  nextCol = col + 1 ;  break ;
1277        case   4: nextRow = row - 1 ;  nextCol = col     ;  break ;
1278        case   8: nextRow = row - 1 ;  nextCol = col - 1 ;  break ;
1279        case  16: nextRow = row     ;  nextCol = col - 1 ;  break ;
1280        case  32: nextRow = row + 1 ;  nextCol = col - 1 ;  break ;
1281        case  64: nextRow = row + 1 ;  nextCol = col     ;  break ;
1282        case 128: nextRow = row + 1 ;  nextCol = col + 1 ;  break ;
1283        default: break ;
1284    }
1285
1286    return [ nextRow, nextCol ] ;
1287}
1288
1289// Encode an edge so that nextCell() will take us to the father from the son.
1290gameOfLife.encodeFather = function( sonRow, sonCol, fatherRow, fatherCol )
1291{
1292    let edge ;
1293    let rowChange = fatherRow - sonRow ;
1294    let colChange = fatherCol - sonCol ;
1295
1296    if      (rowChange ===  0 && colChange ===  1)  edge =   1 ;
1297    else if (rowChange === -1 && colChange ===  1)  edge =   2 ;
1298    else if (rowChange === -1 && colChange ===  0)  edge =   4 ;
1299    else if (rowChange === -1 && colChange === -1)  edge =   8 ;
1300    else if (rowChange ===  0 && colChange === -1)  edge =  16 ;
1301    else if (rowChange ===  1 && colChange === -1)  edge =  32 ;
1302    else if (rowChange ===  1 && colChange ===  0)  edge =  64 ;
1303    else if (rowChange ===  1 && colChange ===  1)  edge = 128 ;
1304    else edge = 0 ;
1305
1306    return edge ;
1307}
1308
1309// Generate one pattern line of "*" and "." characters to represent occupied and empty cells at row, given the bounding box coordinates.
1310gameOfLife.createPatternLine = function(  box, row )
1311{
1312    let lineOfFile = "" ;
1313    let lastCol = box.right ;
1314
1315    // Find the last occupied cell in the row.
1316    for ( ;  lastCol >= box.left ;  --lastCol)
1317        if (this.gameBoard.cell[ row ][ lastCol ].occupied === Occupancy.Occupied)
1318            break ;
1319
1320    // Convert occupied cells to "*" and unoccupied to "."
1321    for (let col = box.left ; col <= lastCol ;  ++col)
1322    {
1323        if (this.gameBoard.cell[ row ][ col ].occupied === Occupancy.Occupied)
1324            lineOfFile += "*" ;
1325        else
1326            lineOfFile += "." ;
1327    }
1328
1329    lineOfFile += "\n" ;
1330
1331    return lineOfFile ;
1332}
1333
1334//===================================================== Callback Closure Functions ===================================================
1335
1336// Make callback functions which we can register with event handlers.
1337//
1338// We pass in event information through the callback function argument.
1339// Closures give us permanent access the entire game state. 
1340//
1341// For example, make_cycleGame( gameOfLifeApp ) returns an anonymous inner function foo( e ), then goes out of scope.
1342// However, foo( e ) has permanent access to its surrounding environment, which contains the gameOfLifeObject.
1343// Thus foo( e ) function can always access gameOfLifeApp.cycleGame() in particular.
1344//
1345// Note that what's passed by value in the maker function argument is a reference to gameOfLifeApp, not a copy of the object.  
1346// The inner function can read and write the app's member variables from now on, after make_cycleGame() returns.
1347
1348// Manufacture a callback function to single step the game to be called from the timer or directly from the GUI.
1349function make_cycleGame( gameOfLifeApp )
1350{
1351    return function( e )
1352    {
1353        gameOfLifeApp.cycleGame() ;
1354    } ;
1355}
1356
1357// Manufacture a callback function to be called whenever the mouse is in the canvas and we click it.
1358function make_onCanvasMouseClick( gameOfLifeApp )
1359{
1360    return function( e )
1361    {
1362        let pos = gameOfLifeApp.gameBoard.canvasToCellCoord( gameOfLifeApp.gameBoard.getCursorPosition( e )) ;
1363        gameOfLifeApp.gameBoard.toggleCounter( pos ) ;
1364    } ;
1365}
1366
1367// Manufacture a callback function when the mouse is moved over the canvas.
1368function make_onCanvasMouseMove( gameOfLifeApp )
1369{
1370    return function( e )
1371    {
1372        // Show the cell (row, col) position.
1373        let pos = gameOfLifeApp.gameBoard.canvasToCellCoord( gameOfLifeApp.gameBoard.getCursorPosition( e )) ;
1374
1375        // Show the complete cell state.
1376        let row = pos[ 0 ] ;
1377        let col = pos[ 1 ] ;
1378
1379        // Cursor gives a game board position out of bounds.
1380        if (row >= 0 && col >= 0 && row < gameOfLifeApp.GameSettings.GameBoardNumRows && col < gameOfLifeApp.GameSettings.GameBoardNumCols)
1381        {
1382            let state = gameOfLifeApp.gameBoard.cell[ row ][ col ].state ;
1383
1384            gameOfLifeApp.GameOfLifeCellState.innerHTML =
1385                  " row/col: "       + row + " " + col +
1386                  " occupied: "      + gameOfLifeApp.gameBoard.cell[ row ][ col ].occupied +
1387                  " occupied prev: " + gameOfLifeApp.gameBoard.cell[ row ][ col ].occupiedPreviously +
1388                  " num neighbors: " + gameOfLifeApp.gameBoard.cell[ row ][ col ].numberOfNeighbors +
1389                  " state: "         + state +
1390                  " age: "           + gameOfLifeApp.gameBoard.cell[ row ][ col ].age +
1391                  " label: "         + gameOfLifeApp.gameBoard.cell[ row ][ col ].label +
1392                  " father: "        + gameOfLifeApp.gameBoard.cell[ row ][ col ].father +
1393                  " edge: "          + gameOfLifeApp.gameBoard.cell[ row ][ col ].edge ;
1394        }
1395    } // end func
1396}
1397
1398// Callback function to load a new life pattern.
1399function make_loadSampleLifePattern( gameOfLifeApp )
1400{
1401    return function( e )
1402    {
1403        let option = e.target.value ;
1404
1405        gameOfLifeApp.gameBoard.clearGameState() ;
1406        gameOfLifeApp.readLifeFile( gameOfLifeApp.sampleLifePatterns[ option ] ) ;
1407        gameOfLifeApp.gameBoard.updateView() ;
1408        gameOfLifeApp.updateRulesView() ;
1409    }
1410}
1411
1412// Manufacture a callback function to be called from a form on a file selection.
1413function make_GameOfLifeLoadFile( gameOfLifeApp )
1414{
1415    return function( e )
1416    {
1417        // The target is the object which this event was dispatched on.
1418        // It contains a list of files.
1419        let files = e.target.files ;
1420
1421        // Loop through the FileList.
1422        for (let i = 0, f; f = files[i]; i++)
1423        {
1424            // Only process Game of Life files.
1425            if ( !f.name.match("\.lif"))
1426                continue ;
1427
1428            let reader = new FileReader() ;
1429
1430            // Callback function for file load completion.
1431            // Use lispish closure to encapsulate the reader.result which is the file contents.
1432            // Then call the inner function with the file contents.
1433            reader.onload = function()
1434            {
1435                gameOfLifeApp.GameOfLifeClipboard.value = reader.result ;
1436
1437                // Clear out the game board, load the file, update the gameboard view, status and rules.
1438                gameOfLifeApp.gameBoard.clearGameState() ;
1439                gameOfLifeApp.readLifeFile( reader.result ) ;
1440                gameOfLifeApp.gameBoard.updateView() ;
1441                gameOfLifeApp.updateRulesView() ;
1442            } ;
1443
1444          // Read in the image file text.
1445          reader.readAsText( f, "UTF-8" ) ;
1446        } // for
1447    } // function
1448}
1449
1450// Manufacture a function to print debug information.
1451function make_debugPrint( gameOfLifeApp )
1452{
1453    return function( option )
1454    {
1455        let text = gameOfLifeApp.GameOfLifeDebug.innerHTML ;
1456
1457        switch( option )
1458        {
1459            case gameOfLifeApp.DebugPrintOptions.GameBoard:
1460                // Clear the debug area when printing the gameboard.
1461                text = "" ;
1462                text += gameOfLifeApp.printGameBoard( gameOfLifeApp.gameBoard ) ;
1463            break ;
1464
1465            case gameOfLifeApp.DebugPrintOptions.Neighbors:
1466                text += gameOfLifeApp.printNeighborCounts( gameOfLifeApp.gameBoard ) ;
1467            break ;
1468
1469            case gameOfLifeApp.DebugPrintOptions.States:
1470                text += gameOfLifeApp.printCounterState( gameOfLifeApp.gameBoard ) ;
1471            break ;
1472        }
1473
1474        // Write out the text to the debug area.
1475        gameOfLifeApp.GameOfLifeDebug.innerHTML = text ;
1476
1477    } // inner function
1478}
1479
1480// Create a closure which returns the next line of text.
1481function make_readNextLine( fileText )
1482{
1483    // Split text of the entire file into lines.
1484    let linesOfFile    = fileText.split( "\n" ) ;
1485    let numLinesInFile = linesOfFile.length ;
1486
1487    let lineNum = 0 ;
1488
1489    // Returns the next line of the file.
1490    return function()
1491    {
1492        if (lineNum < numLinesInFile)
1493            return linesOfFile[ lineNum++ ] ;
1494        else
1495            throw RangeError( "end of file" ) ;
1496    }
1497}
1498
1499// Not currently used...
1500function supportsLocalStorage()
1501{
1502    // window is the default JavaScript global for the web page.
1503    return ("localStorage" in window) && window["localStorage"] !== null ;
1504}
1505
1506function writeClipboardToLocalStorage( file )
1507{
1508    if (!supportsLocalStorage())
1509        return false;
1510
1511    localStorage[ "GameOfLife.file.name" ] = file ;
1512
1513    return true;
1514}
1515
1516function readLocalStorageToClipboard()
1517{
1518    if (!supportsLocalStorage())
1519        return false;
1520
1521    file = localStorage[ "GameOfLife.file.name" ] ;
1522
1523    if (!file)
1524        return null ;
1525
1526    return file ;
1527}
1528
1529//================================================ Game Board Members ========================================================================
1530
1531gameBoard.init = function( GameSettings, debugPrint, DebugPrintOptions, GameOfLifeCanvas, GameOfLifeState )
1532{
1533    // Access the canvas from the game board.
1534    this.GameOfLifeCanvas = GameOfLifeCanvas ;
1535    this.widthPixels      = GameOfLifeCanvas.width ;
1536    this.heightPixels     = GameOfLifeCanvas.height ;
1537    this.graphicsContext  = GameOfLifeCanvas.getContext( "2d" ) ;
1538
1539    // Access the game state display area.
1540    this.GameOfLifeState = GameOfLifeState ;
1541
1542    // Copy over debug print and its options to the game board.
1543    this.DebugPrintOptions = DebugPrintOptions ;
1544    this.debugPrint = debugPrint ;
1545    this.GameSettings = GameSettings ;
1546
1547    // Initialize game board size.
1548    this.numRows    = this.GameSettings.GameBoardNumRows ;
1549    this.numCols    = this.GameSettings.GameBoardNumCols ;
1550
1551    // Initialize game board global state.
1552    this.population = 0 ;
1553    this.generation = 0 ;
1554
1555    // Normal Conway rules:  a counter survives if it has 2 or 3 neighbors else dies of loneliness;
1556    // an empty cell with 3 neighbors has a birth.
1557    this.rules =
1558    {
1559        survival : undefined,
1560        birth    : undefined
1561    } ;
1562
1563    this.rules.survival =
1564    {
1565        numRules     :   2,
1566        numNeighbors : [ 2, 3, , , , , , , ]
1567    } ;
1568
1569    this.rules.birth =
1570    {
1571        numRules     :   1,
1572        numNeighbors : [ 3, , , , , , , , ]
1573    } ;
1574
1575    // Generate the game board as an array of rows, where each row is an array of columns,
1576    // and each element is a cell.
1577    this.cell = Array( this.numRows ) ;
1578    for (let row = 0 ;  row < this.numRows ;  ++row)
1579        this.cell[ row ] = Array( this.numCols ) ;
1580
1581    // Fill each cell in the game board with default values.
1582    for (let col = 0 ;  col < this.numCols ;  ++col)
1583    {
1584        for (let row = 0 ;  row < this.numRows ;  ++row)
1585        {
1586            this.cell[ row ][ col ] = Object.create( Object.prototype, 
1587            {
1588                // A single empty game board cell and its default state.
1589                // Each variable in this object has a bunch of properties.
1590                //     writeable - we can change the value of numberOfNeighbors with an assignement operator.
1591                //     enumerable - we can use numberOfNeighbors in a for..in loop or access via Object.keys()
1592                //     configurable - we can change the data type and other attributes of numberOfNeighbors and we can delete it.
1593                //     value - initial value upon object creation.
1594                //     We don't need any get() or set() properties.
1595		numberOfNeighbors:  { value:  0, 			writable: true, enumerable: true, configurable: true, },// No neighbors.
1596		occupied:           { value:  Occupancy.Empty, 		writable: true, enumerable: true, configurable: true, },// Not occupied
1597		occupiedPreviously: { value:  Occupancy.Indeterminate,  writable: true, enumerable: true, configurable: true, },// No previous occupation.
1598		state:              { value:  State.Indeterminate, 	writable: true, enumerable: true, configurable: true, },// No state.
1599		age:                { value:  0, 			writable: true, enumerable: true, configurable: true, },// Cell is new.
1600		// For traversal only.
1601		label:              { value: -1, writable: true, enumerable: true, configurable: true, },// Cell is unlabelled.
1602		father:             { value: -1, writable: true, enumerable: true, configurable: true, },// Cell has no father.
1603		edge:               { value: -1, writable: true, enumerable: true, configurable: true, },// Edges are unmarked.
1604            } ) ;
1605        }
1606    }
1607
1608    // Comments.
1609    this.maxNumCommentLines = GameSettings.MaxNumCommentLines ;
1610    this.comment            = Array( GameSettings.MaxNumCommentLines ) ;
1611    this.numCommentLines    = GameSettings.MaxNumCommentLines ;
1612
1613    //  Leave space for blank comment lines.
1614    for (let row = 0 ;  row < GameSettings.MaxNumCommentLines ;  ++row)
1615        this.comment[ row ] = "#D" ;
1616}
1617
1618//===================================================== Game Board State Functions ===================================================
1619
1620// Update the game board to go from one generation to the next.
1621gameBoard.updateGameBoard = function()
1622{
1623    this.debugPrint( this.DebugPrintOptions.GameBoard ) ;
1624
1625    // Count the number of neighbors for each counter.
1626    this.countNeighbors() ;
1627
1628    // Apply the life rules to see who lives and dies.
1629    this.birthAndDeath() ;
1630
1631    this.debugPrint( this.DebugPrintOptions.Neighbors ) ;
1632    this.debugPrint( this.DebugPrintOptions.States ) ;
1633
1634    // We now have a new generation.
1635    ++this.generation ;
1636}
1637
1638// If a cell is occupied, update the neighbor counts for all adjacent cells.
1639// Treat the boundary of the board specially.
1640gameBoard.countNeighbors = function()
1641{
1642    //  Size of game board.
1643    let numRows = this.numRows ;
1644    let numCols = this.numCols ;
1645
1646    //  Zero out the neighbor count for each cell.
1647    for (let row = 0 ;  row < numRows ;  ++row)
1648        for (let col = 0 ;  col < numCols ;  ++col)
1649            this.cell[ row ][ col ].numberOfNeighbors = 0 ;
1650
1651    // Update neighbor counts for counters in first and last columns.
1652    for (let row = 0 ;  row < numRows ;  ++row)
1653    {
1654        if (this.cell[ row ][ 0 ].occupied === Occupancy.Occupied)
1655            this.boundaryNeighborCount( row, 0 ) ;
1656
1657        if (this.cell[ row ][ numCols - 1 ].occupied === Occupancy.Occupied)
1658            this.boundaryNeighborCount( row, numCols - 1 ) ;
1659    }
1660
1661    // Update neighbor counts for counters in the first and last rows,
1662    // skipping the corners since these have already been updated.
1663    for (let col = 1 ;  col <= numCols-2 ;  ++col)
1664    {
1665        if (this.cell[ 0 ][ col ].occupied === Occupancy.Occupied)
1666            this.boundaryNeighborCount( 0, col ) ;
1667
1668        if (this.cell[ numRows - 1 ][ col ].occupied === Occupancy.Occupied)
1669            this.boundaryNeighborCount( numRows - 1, col ) ;
1670    }
1671
1672    // Update neighbor counts on interior cells.
1673    for (let row = 1 ;  row <= numRows - 2 ;  ++row)
1674    {
1675        for (let col = 1 ;  col <= numCols - 2 ;  ++col)
1676        {
1677            //  Current cell is occupied.
1678            if (this.cell[ row ][ col ].occupied === Occupancy.Occupied)
1679            {
1680                //  Update neighbor count for all its 8 adjacent cells.
1681                ++this.cell[ row - 1 ][ col - 1 ].numberOfNeighbors ;
1682                ++this.cell[ row - 1 ][ col     ].numberOfNeighbors ;
1683                ++this.cell[ row - 1 ][ col + 1 ].numberOfNeighbors ;
1684
1685                ++this.cell[ row     ][ col - 1 ].numberOfNeighbors ;
1686                ++this.cell[ row     ][ col + 1 ].numberOfNeighbors ;
1687
1688                ++this.cell[ row + 1 ][ col - 1 ].numberOfNeighbors ;
1689                ++this.cell[ row + 1 ][ col     ].numberOfNeighbors ;
1690                ++this.cell[ row + 1 ][ col + 1 ].numberOfNeighbors ;
1691            }
1692        }
1693    }
1694}
1695
1696// Given that the boundary cell at (row, col) is occupied, update the neighbor
1697// counts for all adjacent cells.
1698gameBoard.boundaryNeighborCount = function( row, col )
1699{
1700    let adjRow, adjCol, adjTorusRow, adjTorusCol ;
1701
1702    // Iterate through all adjacent cells.
1703    for (adjRow = row - 1 ;  adjRow <= row + 1 ;  ++adjRow)
1704    {
1705        for (adjCol = col - 1 ;  adjCol <= col + 1 ;  ++adjCol)
1706        {
1707            adjTorusRow = adjRow ;
1708            adjTorusCol = adjCol ;
1709
1710            //  Wrap around so that we are topologically on a torus.
1711            if (adjTorusRow <= -1)
1712                adjTorusRow = this.numRows - 1 ;
1713
1714            if (adjTorusCol <= -1)
1715                adjTorusCol = this.numCols - 1 ;
1716
1717            if (adjTorusRow >= this.numRows)
1718                adjTorusRow = 0 ;
1719
1720            if (adjTorusCol >= this.numCols)
1721                adjTorusCol = 0 ;
1722
1723            //  All neighbors of the current cell get incremented.
1724            ++this.cell[ adjTorusRow ][ adjTorusCol ].numberOfNeighbors ;
1725        }
1726    }
1727
1728    //  Neighbor count for the cell itself was incremented above.
1729    //  Correct for this.
1730    --this.cell[ row ][ col ].numberOfNeighbors ;
1731}
1732
1733// Sweep through all cells, updating their occupancy according to the birth
1734// and death rules.  Use each cell's neighbor count from the last cycle.
1735gameBoard.birthAndDeath = function()
1736{
1737    let caseOfSurvival, caseOfBirth, cell ;
1738
1739    this.population = 0 ;
1740
1741    for (let row = 0 ;  row < this.numRows ;  ++row)
1742    {
1743        for (let col = 0 ;  col < this.numCols ;  ++col)
1744        {
1745            // Access the current cell at row, col.
1746            let cell = this.cell[ row ][ col ] ;
1747
1748            // Save the previous occupation state for this cell.
1749            cell.occupiedPreviously = cell.occupied ;
1750
1751            caseOfBirth = caseOfSurvival = false ;
1752
1753            //  An empty cell next to n1 or n2 or ... neighbors gets a birth.
1754            if (cell.occupied === Occupancy.Empty)
1755            {
1756                for (let i = 0 ; i < this.rules.birth.numRules ;  ++i)
1757                {
1758                    if (cell.numberOfNeighbors === this.rules.birth.numNeighbors[ i ])
1759                    {
1760                        caseOfBirth = true ;
1761                        cell.occupied = Occupancy.Occupied ;
1762                        cell.state    = State.Birth ;
1763                        cell.age      = 0 ;        // Cell is newborn.
1764
1765                        // Early out since some rule allowed a birth.
1766                        break ;
1767                    }
1768                } // end for
1769            }
1770            else if (cell.occupied === Occupancy.Occupied)
1771            {
1772                for (i = 0 ; i < this.rules.survival.numRules ;  ++i)
1773                {
1774                    if (cell.numberOfNeighbors === this.rules.survival.numNeighbors[ i ])
1775                    {
1776                        caseOfSurvival = true ;
1777
1778                        cell.state = State.Survival ;
1779                        ++cell.age ;                                 // Cell gets older.
1780                        if (cell.age > this.GameSettings.MaximumAge)   // Wrap around to nonzero.
1781                            cell.age = 1 ;
1782
1783                        // Early out since some rule allowed a survival.
1784                        break ;
1785                    }
1786                } // end for
1787
1788            }
1789
1790            //  All other cases, including death from overpopulation, underpopulation
1791            //  and the case where the cell stays empty with no change.
1792            if (!caseOfSurvival && !caseOfBirth)
1793            {
1794                //  Occupied cell suffers death from overpopulation or underpopulation.
1795                if (cell.occupied === Occupancy.Occupied)
1796                {
1797                    cell.occupied = Occupancy.Empty ;
1798                    cell.state    = State.Death ;
1799                    cell.age      = 0 ;
1800                }
1801                // Empty cell does not change.
1802                else
1803                {
1804                    ++cell.age ;                                // Empty cell gets older.
1805                    if (cell.age > this.GameSettings.MaximumAge)  // Wrap around to nonzero.
1806                        cell.age = 1 ;
1807                }
1808            }
1809
1810            // Update the population count.
1811            if (cell.occupied === Occupancy.Occupied)
1812                ++this.population ;
1813        } // end for col
1814    } // end for row
1815}
1816
1817//===================================================== Drawing the Game Board =======================================================
1818
1819gameBoard.drawLifeGrid = function()
1820{
1821    // White grid lines.
1822    this.graphicsContext.strokeStyle = "rgba(230,230,255,1.0)"
1823
1824    // Erase the game board area.
1825    this.graphicsContext.clearRect( 0, 0, this.widthPixels, this.heightPixels ) ;
1826
1827    // Get ready to draw lines.
1828    this.graphicsContext.beginPath();
1829
1830    let cellWidth  = this.widthPixels  / this.numCols ;
1831    let cellHeight = this.heightPixels / this.numRows ;
1832
1833    // Draw vertical lines.
1834    for (let x = 0 ;  x <= this.widthPixels ;  x += cellWidth)
1835    {
1836        this.graphicsContext.moveTo( 0.5 + x, 0 ) ;
1837        this.graphicsContext.lineTo( 0.5 + x, this.heightPixels ) ;
1838    }
1839
1840    // Draw horizontal lines.
1841    for (let y = 0; y <= this.heightPixels ; y += cellHeight )
1842    {
1843        this.graphicsContext.moveTo( 0, 0.5 + y ) ;
1844        this.graphicsContext.lineTo( this.widthPixels, 0.5 + y ) ;
1845    }
1846
1847    // Finish drawing.
1848    this.graphicsContext.stroke();
1849    this.graphicsContext.closePath() ;
1850}
1851
1852// Canvas [x, y] to game board [row, col].
1853gameBoard.canvasToCellCoord = function( pos )
1854{
1855    let cellWidth  = this.widthPixels  / this.numCols ;
1856    let cellHeight = this.heightPixels / this.numRows ;
1857
1858    let col = Math.floor( pos[0] / cellWidth  ) ;
1859    let row = Math.floor( pos[1] / cellHeight ) ;
1860
1861    return [row, col] ;
1862}
1863
1864// Game board [row, col]  to  canvas [x, y].
1865gameBoard.cellToCanvasCoord = function( pos )
1866{
1867    let cellWidth  = this.widthPixels  / this.numCols ;
1868    let cellHeight = this.heightPixels / this.numRows ;
1869
1870    // Canvas (x,y) coordinates of the center of a cell.
1871    let x = cellWidth  * pos[1] + cellWidth  / 2 ;
1872    let y = cellHeight * pos[0] + cellHeight / 2 ;
1873
1874    return [x, y] ;
1875}
1876
1877gameBoard.getCursorPosition = function( e )
1878{
1879    // Mouse position is relative to the client window.  Subtract off the canvas
1880    // element position in the client window to get canvas coordinates, 
1881    // origin at top left corner.
1882    let canvasRect = this.GameOfLifeCanvas.getBoundingClientRect() ;
1883    let x = e.clientX - canvasRect.left ;
1884    let y = e.clientY - canvasRect.top ;
1885
1886    // Correct when the canvas gets rescaled from its default size.
1887    let scaleX = this.GameOfLifeCanvas.width  / canvasRect.width ;
1888    let scaleY = this.GameOfLifeCanvas.height / canvasRect.height ;
1889
1890    x *= scaleX ;
1891    y *= scaleY ;
1892
1893    return [x, y] ;
1894}
1895
1896// Toggle the counter state and redraw it.
1897gameBoard.toggleCounter = function( pos )
1898{
1899    let cell = this.cell[ pos[0] ][ pos[1] ] ;
1900
1901    // Save the previous occupation state for this cell.
1902    cell.occupiedPreviously = cell.occupied ;
1903
1904    //  If cell is empty, mark as occupied, or vice-versa.
1905    if (cell.occupied === Occupancy.Empty)
1906        cell.occupied = Occupancy.Occupied ;
1907    else if (cell.occupied === Occupancy.Occupied)
1908        cell.occupied = Occupancy.Empty ;
1909
1910    this.drawCell( pos ) ;
1911}
1912
1913// Draw the current cell.
1914gameBoard.drawCell = function( pos )
1915{
1916    //  Get the current cell information.
1917    let cell = this.cell[ pos[0] ][ pos[1] ] ;
1918
1919    // Center canvas coordinates of cell.
1920    let centerOfCell = this.cellToCanvasCoord( pos ) ;
1921
1922    let cellWidth  = this.widthPixels  / this.numRows ;
1923    let cellHeight = this.heightPixels / this.numCols ;
1924    let radius     = cellWidth / 2 - 0.8 ;
1925
1926    // Cell occupation didn't change.  And of course, assume the board wasn't just cleared.
1927    if (cell.occupied === cell.occupiedPreviously && cell.occupied !== Occupancy.Indeterminate)
1928    {
1929        // Special case if an occupied cell just aged.
1930        if (cell.age === this.GameSettings.OldAge && cell.occupied === Occupancy.Occupied)
1931        {
1932            this.graphicsContext.beginPath();
1933            this.graphicsContext.fillStyle = "rgba( 185, 65, 64, 1.0 )" // Stable counter color:  red.
1934            this.graphicsContext.arc( centerOfCell[0], centerOfCell[1], radius, 0, Math.PI*2, true ) ;
1935            this.graphicsContext.fill();
1936            this.graphicsContext.closePath() ;
1937        }
1938
1939        // Skip drawing.
1940        return ;
1941    }
1942
1943    // If we are here, the cell occupation changed...
1944
1945    // Cell is occupied:  draw the counter.
1946    if (cell.occupied === Occupancy.Occupied)
1947    {
1948        this.graphicsContext.beginPath();
1949
1950        if( cell.age >= this.GameSettings.OldAge )
1951            this.graphicsContext.fillStyle = "rgba( 185, 65, 64, 1.0 )"    // Stable counter color:  red.
1952        else
1953            this.graphicsContext.fillStyle = "rgba(   0, 100, 255, 1.0 )"  // Active counter color:  blue.
1954
1955        this.graphicsContext.arc( centerOfCell[ 0 ], centerOfCell[ 1 ], radius, 0, Math.PI * 2, true ) ;
1956        this.graphicsContext.fill();
1957        this.graphicsContext.closePath() ;
1958    }
1959    // Cell is empty:  erase the counter.
1960    else if (cell.occupied === Occupancy.Empty)
1961    {
1962        /// alert( "clear cell[ " + pos[0] +  " " + pos[1] + " ] = " + this.cell[ pos[0] ][ pos[1] ].occupied ) ;
1963
1964        // Get the cell dimensions.
1965        let x1 = centerOfCell[ 0 ] - cellWidth  / 2 ;
1966        let y1 = centerOfCell[ 1 ] - cellHeight / 2 ;
1967        let x2 = centerOfCell[ 0 ] + cellWidth  / 2 ;
1968        let y2 = centerOfCell[ 1 ] + cellHeight / 2 ;
1969
1970        // Erase the whole cell.
1971        this.graphicsContext.clearRect( x1, y1, cellWidth, cellHeight ) ;
1972
1973        // White grid lines.
1974        this.graphicsContext.strokeStyle = "rgba(230,230,255,1.0)"
1975
1976        // Redraw the lines of the cell.
1977        this.graphicsContext.beginPath();
1978        this.graphicsContext.moveTo( x1 + 0.5, y1       ) ; // Vertical
1979        this.graphicsContext.lineTo( x1 + 0.5, y2       ) ;
1980
1981        this.graphicsContext.moveTo( x2 + 0.5, y1       ) ; // Vertical
1982        this.graphicsContext.lineTo( x2 + 0.5, y2       ) ;
1983
1984        this.graphicsContext.moveTo( x1 + 0.5, y1 + 0.5 ) ; // Horizontal
1985        this.graphicsContext.lineTo( x2 + 0.5, y1 + 0.5 ) ;
1986
1987        this.graphicsContext.stroke();
1988        this.graphicsContext.closePath() ;
1989    }
1990}
1991
1992gameBoard.clearGameState = function()
1993{
1994    this.population = 0 ;
1995    this.generation = 0 ;
1996
1997    // Fill cells with default values.
1998    for (let col = 0 ;  col < this.numCols ;  ++col)
1999    {
2000        for (let row = 0 ;  row < this.numRows ;  ++row)
2001        {
2002            let cell = this.cell[ row ][ col ] ;
2003
2004            cell.numberOfNeighbors  =  0 ;
2005            cell.occupied           =  Occupancy.Empty ;
2006            cell.occupiedPreviously =  Occupancy.Indeterminate ;
2007            cell.state              =  State.Indeterminate ;
2008            cell.age                =  0 ;
2009            cell.label              = -1 ;
2010            cell.father             = -1 ;
2011            cell.edge               = -1 ;
2012        }
2013    }
2014
2015    // Clear the comments.
2016    this.numCommentLines = 1 ;
2017    this.comment[ 0 ] = "#D Your comment here!" ;
2018}
2019
2020// Redraw the gameboard and its global state.
2021gameBoard.updateView = function()
2022{
2023    for (let row = 0 ;  row < this.numRows ;  ++row)
2024    {
2025        for (let col = 0 ;  col < this.numCols ;  ++col)
2026        {
2027            let pos = [row, col] ;
2028            this.drawCell( pos ) ;
2029        }
2030    }
2031
2032    let text = "Generation " + this.generation + " Population " + this.population ;
2033
2034    // Display the game state.
2035    this.GameOfLifeState.innerHTML = text ;
2036}