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