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