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