1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67 const gameOfLifeBase =
68 {
69
70 GameSettings :
71 {
72 MaxFileLineLength : 80,
73 MaxNumCommentLines : 22,
74 MaximumAge : 10000,
75 UpdateIntervalMs : 50,
76 GameBoardNumRows : 100,
77 GameBoardNumCols : 100,
78 OldAge : 50
79 },
80
81 timer : undefined,
82
83 DebugPrintOptions :
84 {
85 GameBoard : 0,
86 Neighbors : 1,
87 States : 2
88 },
89
90 getAccessToDOMElements: function()
91 {
92
93 this.canvasElement = document.getElementById( "GameOfLifeCanvas" ) ;
94 this.gameStateElement = document.getElementById( "GameOfLifeState" ) ;
95 this.cellStateElement = document.getElementById( "GameOfLifeCellState" ) ;
96 this.debugElement = document.getElementById( "GameOfLifeDebug" ) ;
97 this.fileSelectElementForReading = document.getElementById( "GameOfLifeLoadFile" ) ;
98 this.clipboardElement = document.getElementById( "GameOfLifeClipboard" ) ;
99 this.lifePatternsElement = document.getElementById( "LifePatterns" ) ;
100
101
102
103 this.survivalRulesElement = document.getElementsByName( "SurvivalRules" ) ;
104 this.birthRulesElement = document.getElementsByName( "BirthRules" ) ;
105 }
106 }
107
108
109 let gameOfLife = Object.create( gameOfLifeBase ) ;
110
111
112
113 gameOfLife.init = function()
114 {
115 this.getAccessToDOMElements() ;
116
117
118 this.gameBoard = new GameBoard( this.GameSettings, make_debugPrint( this ), this.DebugPrintOptions, this.canvasElement, this.gameStateElement )
119
120
121 this.gameBoard.drawLifeGrid() ;
122
123
124 this.gameBoard.clearGameState() ;
125
126
127
128 this.preloadLifePatterns() ;
129 this.readLifeFile( this.sampleLifePatterns[ "glidergun" ] ) ;
130
131
132
133
134 window.onload = function() { document.forms[ "LifePatternsForm" ].reset() } ;
135
136
137 this.writeGameToClipboard() ;
138
139
140 this.gameBoard.updateView() ;
141
142
143 this.updateRulesView() ;
144
145
146
147 this.canvasElement.addEventListener( "click", make_onCanvasClick( this ), false ) ;
148
149
150 this.canvasElement.addEventListener( "mousemove", make_onMouseMove( this ), false ) ;
151
152
153
154 this.fileSelectElementForReading.addEventListener( "change", make_fileSelectForReading( this ), false ) ;
155
156
157
158 this.lifePatternsElement.addEventListener( "change", make_loadSampleLifePattern( this ));
159 }
160
161
162
163
164 gameOfLife.cycleGame = function ()
165 {
166
167 this.gameBoard.updateGameBoard() ;
168
169
170 this.gameBoard.updateView() ;
171 }
172
173
174 gameOfLife.clearGame = function ()
175 {
176 this.gameBoard.clearGameState() ;
177 this.gameBoard.updateView() ;
178 }
179
180
181 gameOfLife.changeRules = function( flag )
182 {
183 let rulesElement = undefined ;
184
185
186 if (flag)
187 rulesElement = this.survivalRulesElement ;
188 else
189 rulesElement = this.birthRulesElement ;
190
191 let numNeighbors = [] ;
192 let numRules = 0 ;
193
194
195 for (let boxNum = 0, numBoxes = rulesElement.length ; boxNum < numBoxes ; ++boxNum)
196 {
197 if (rulesElement[ boxNum ].checked)
198 {
199
200 numNeighbors[ numRules++ ] = boxNum + 1 ;
201 }
202 }
203
204 let rules = new Rules( numRules, numNeighbors ) ;
205
206 if (flag)
207 this.gameBoard.rules.survival = rules ;
208 else
209 this.gameBoard.rules.birth = rules ;
210
211 this.gameBoard.updateView() ;
212 }
213
214
215 gameOfLife.updateRulesView = function()
216 {
217
218 for (let boxNum = 0 ; boxNum < this.survivalRulesElement.length ; ++boxNum)
219 this.survivalRulesElement[ boxNum ].checked = false ;
220
221
222 for (let i = 0 ; i < this.gameBoard.rules.survival.numRules ; ++i)
223 this.survivalRulesElement[ this.gameBoard.rules.survival.numNeighbors[ i ] - 1].checked = true ;
224
225
226 for (let boxNum = 0 ; boxNum < this.birthRulesElement.length ; ++boxNum)
227 this.birthRulesElement[ boxNum ].checked = false ;
228
229
230 for (let i = 0 ; i < this.gameBoard.rules.birth.numRules ; ++i)
231 this.birthRulesElement[ this.gameBoard.rules.birth.numNeighbors[ i ] - 1].checked = true ;
232 }
233
234
235 gameOfLife.writeGameToClipboard = function()
236 {
237 this.clipboardElement.value = this.writeLifeFile( this.gameBoard ) ;
238 }
239
240
241 gameOfLife.readGameFromClipboard = function()
242 {
243
244 this.gameBoard.clearGameState() ;
245 this.readLifeFile( this.clipboardElement.value ) ;
246 this.gameBoard.updateView() ;
247 this.updateRulesView() ;
248 }
249
250
251
252 gameOfLife.runStopGame = function()
253 {
254 if (this.timer === undefined)
255 this.timer = setInterval( make_cycleGame( this ), this.GameSettings.UpdateIntervalMs ) ;
256 else
257 {
258 clearInterval( this.timer ) ;
259 this.timer = undefined ;
260 }
261 }
262
263
264 gameOfLife.singleStepGame = function()
265 {
266
267 if (this.timer !== undefined)
268 {
269 clearInterval( this.timer ) ;
270 this.timer = undefined ;
271 }
272
273 this.cycleGame() ;
274 }
275
276
277 gameOfLife.printGameBoard = function()
278 {
279 let numRows = this.gameBoard.numRows ;
280 let numCols = this.gameBoard.numCols ;
281
282
283 let text = "Game Board\n" ;
284 for (let row = 0 ; row < numRows ; ++row)
285 {
286
287 text += String( " " + row ).slice( -5 ) ;
288 text += ": " ;
289 for (let col = 0 ; col < numCols ; ++col)
290 {
291 let cell = this.gameBoard.cell[ row ][ col ] ;
292 if (cell.occupied === Occupancy.Occupied)
293 text += "O " ;
294 else
295 text += " " ;
296 }
297 text += "\n" ;
298 }
299
300 return text ;
301 }
302
303
304 gameOfLife.printNeighborCounts = function()
305 {
306 let numRows = this.gameBoard.numRows ;
307 let numCols = this.gameBoard.numCols ;
308
309
310 let text = "Neighbor counts\n" ;
311 for (let row = 0 ; row < numRows ; ++row)
312 {
313
314 text += String( " " + row ).slice( -5 ) ;
315 text += ": " ;
316 for (let col = 0 ; col < numCols ; ++col)
317 {
318 let cell = this.gameBoard.cell[ row ][ col ] ;
319 let num = cell.numberOfNeighbors ;
320 text += (num === 0 ? " " : num) ;
321 text += " " ;
322 }
323 text += "\n" ;
324 }
325 return text ;
326 }
327
328
329 gameOfLife.printCounterState = function()
330 {
331 let numRows = this.gameBoard.numRows ;
332 let numCols = this.gameBoard.numCols ;
333
334
335 let text = "Counter state\n" ;
336 for (let row = 0 ; row < numRows ; ++row)
337 {
338
339 text += String( " " + row ).slice( -5 ) ;
340 text += ": " ;
341 for (let col = 0 ; col < numCols ; ++col)
342 {
343 let cell = this.gameBoard.cell[ row ][ col ] ;
344 if (cell.state === State.Birth)
345 text += "B " ;
346 else if (cell.state === State.Survival)
347 text += "s " ;
348 else if (cell.state === State.Death)
349 text += "d " ;
350 else
351 text += " " ;
352 }
353 text += "\n" ;
354 }
355
356 return text ;
357 }
358
359
360 gameOfLife.preloadLifePatterns = function()
361 {
362
363
364 this.sampleLifePatterns =
365 {
366 glidergun :
367
368 "#Life 1.05\n" +
369 "#D p30 glider gun (the Original)\n" +
370 "#D This is made of two of a pattern\n" +
371 "#D known as the \"queen bee\", which\n" +
372 "#D sometimes occurs naturally,\n" +
373 "#D whose debris can be deleted on\n" +
374 "#D the sides by blocks or eaters.\n" +
375 "#D But a collision in the center\n" +
376 "#D can, as seen here, miraculously \n" +
377 "#D form a glider. Just one of these\n" +
378 "#D moving back and forth is called\n" +
379 "#D piston (see the p30 in OSCSPN2).\n" +
380 "#D I added an eater at the bottom right.\n" +
381 "#N\n" +
382 "#P 4 -5\n" +
383 "....*\n" +
384 ".****\n" +
385 "****\n" +
386 "*..*\n" +
387 "****\n" +
388 ".****\n" +
389 "....*\n" +
390 "#P 13 -4\n" +
391 "*\n" +
392 "*\n" +
393 "#P -6 -3\n" +
394 "..*\n" +
395 ".*.*\n" +
396 "*...**\n" +
397 "*...**\n" +
398 "*...**\n" +
399 ".*.*\n" +
400 "..*\n" +
401 "#P 17 -2\n" +
402 "**\n" +
403 "**\n" +
404 "#P -17 0\n" +
405 "**\n" +
406 "**\n" +
407 "#P 42 40\n" +
408 "**\n" +
409 "*.*\n" +
410 "..*\n" +
411 "..**\n" +
412 "",
413
414 replicator :
415
416 "#Life 1.05\n" +
417 "#D In February 1994, Nathan Thompson reported several interesting objects\n" +
418 "#D that he found in a cellular automaton closely related to Conway's Life.\n" +
419 "#D The reason that HighLife has been investigated so much is because of the\n" +
420 "#D object known as the 'replicator'. This amazing object starts with only\n" +
421 "#D six live cells as shown in figure 2. See 'HighLife - An Interesting\n" +
422 "#D Variant of Life (part 1/3)', by David I. Bell, dbell@canb.auug.org.au,\n" +
423 "" +
424 "#D 7 May 1994.\n" +
425 "" +
426 "#R 23/36\n" +
427 "" +
428 "#P -2 -2\n" +
429 "" +
430 ".***\n" +
431 "" +
432 "*...\n" +
433 "" +
434 "*...\n" +
435 "" +
436 "*...\n" +
437 "" +
438 "",
439
440 crab :
441
442 "#Life 1.05\n" +
443 "" +
444 "#D Name: Crab\n" +
445 "" +
446 "#D Author: Jason Summers\n" +
447 "" +
448 "#D The smallest known diagonal spaceship other than the glider. It was discovere\n" +
449 "" +
450 "#D d in September 2000.\n" +
451 "" +
452 "#D www.conwaylife.com/wiki/index.php?title=Crab\n" +
453 "#N\n" +
454 "#P -6 -6\n" +
455 "........**\n" +
456 ".......**\n" +
457 ".........*\n" +
458 "...........**\n" +
459 "..........*\n" +
460 ".\n" +
461 ".........*..*\n" +
462 ".**.....**\n" +
463 "**.....*\n" +
464 "..*....*.*\n" +
465 "....**..*\n" +
466 "....**\n" +
467 "",
468
469 shickengine :
470
471 "#Life 1.05\n" +
472 "#D Name: Schick engine\n" +
473 "#D Author: Paul Schick\n" +
474 "#D An orthogonal c/2 tagalong found in 1972.\n" +
475 "#D www.conwaylife.com/wiki/index.php?title=Schick_engine\n" +
476 "#N\n" +
477 "#P -11 -4\n" +
478 "****\n" +
479 "*...*\n" +
480 "*\n" +
481 ".*..*\n" +
482 "#P -5 -2\n" +
483 "..*\n" +
484 ".*******\n" +
485 "**.***..*\n" +
486 ".*******\n" +
487 "..*\n" +
488 "#P -7 -1\n" +
489 "*\n" +
490 "#P -11 1\n" +
491 ".*..*\n" +
492 "*\n" +
493 "*...*\n" +
494 "****\n" +
495 "#P -7 1\n" +
496 "*\n" +
497 "",
498
499 trafficcircle :
500
501 "#Life 1.05\n" +
502 "#D Traffic circle from http://www.radicaleye.com/lifepage/picgloss/picgloss.html\n" +
503 "#N\n" +
504 "#P -25 -25\n" +
505 "......................**....**..........................\n" +
506 "......................*.*..*.*...................\n" +
507 "........................*..*.....................\n" +
508 ".......................*....*....................\n" +
509 ".......................*....*....................\n" +
510 ".......................*....*....................\n" +
511 ".........................**.....**...............\n" +
512 "................................***..............\n" +
513 "................................**.*.............\n" +
514 "..................................*.*............\n" +
515 "..........................***....*..*............\n" +
516 "..................................**.............\n" +
517 "..........**............*.....*..................\n" +
518 ".........*..*...........*.....*..................\n" +
519 ".......*..*.*...........*.....*..................\n" +
520 "...........*.....................................\n" +
521 ".......*.**...............***....................\n" +
522 "........*.....*..................................\n" +
523 "..............*..................................\n" +
524 ".**...........*..................................\n" +
525 ".*..***..........................................\n" +
526 "..**......***...***............................**\n" +
527 ".......*...................................***..*\n" +
528 ".......*......*...............................**.\n" +
529 "..**..........*........*..................*......\n" +
530 ".*..***.......*......**.**............*...*......\n" +
531 ".**....................*............**.**.....**.\n" +
532 "......................................*....***..*\n" +
533 "...............................................**\n" +
534 ".................................................\n" +
535 ".......................................*.*.......\n" +
536 ".....................***..................*......\n" +
537 "......................................*..*.......\n" +
538 "...................*.....*...........*.*.*.......\n" +
539 "...................*.....*...........*..*........\n" +
540 "...................*.....*............**.........\n" +
541 "..............**.................................\n" +
542 ".............*..*....***.........................\n" +
543 ".............*.*.*...............................\n" +
544 "..............*.***..............................\n" +
545 "................***..............................\n" +
546 ".......................**........................\n" +
547 ".....................*....*......................\n" +
548 ".....................*....*......................\n" +
549 ".....................*....*......................\n" +
550 "......................*..*.......................\n" +
551 "....................*.*..*.*.....................\n" +
552 "....................**....**.....................\n" +
553 "",
554
555 highlifeglidergun :
556
557 "#Life 1.05\n" +
558 "#D Period 96 replicator based glider gun by David Bell.\n" +
559 "#D --- The smallest known glider gun based on replicators.\n" +
560 "#D A block perturbs the replicator to produce the glider,\n" +
561 "#D while a period 2 clock oscillator prevents a spark \n" +
562 "#D from being formed that would modify the block. \n" +
563 "#D One glider is shown where it was just created.\n" +
564 "#D From HighLife - An Interesting Variant of Life \n" +
565 "#D (part 1/3) by David I. Bell, dbell@canb.auug.org.au\n" +
566 "#D 7 May 1994\n" +
567 "#R 23/36\n" +
568 "#P -18 -14\n" +
569 "**...................................\n" +
570 "**...................................\n" +
571 "..............*......................\n" +
572 ".............***.....................\n" +
573 "............**.**....................\n" +
574 "...........**.**.....................\n" +
575 "..........**.**......................\n" +
576 "...........***.......................\n" +
577 "............*........................\n" +
578 ".....................................\n" +
579 ".....................................\n" +
580 ".....................................\n" +
581 ".....................................\n" +
582 ".....................................\n" +
583 ".....................................\n" +
584 ".....................................\n" +
585 ".....................................\n" +
586 ".....................................\n" +
587 ".........................**......**..\n" +
588 "........................*.*......**..\n" +
589 "..........................*..........\n" +
590 ".....................................\n" +
591 "...................................*.\n" +
592 ".................................*.*.\n" +
593 "..................................*.*\n" +
594 ".........................**.......*..\n" +
595 ".........................**..........\n" +
596 "#P -5 15\n" +
597 "..**\n..*\n" +
598 "*.*\n" +
599 "**\n" +
600 ""
601 } ;
602 }
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665 gameOfLife.readLifeFile = function( fileText )
666 {
667 let lineOfFile = null ;
668
669
670 let readLine = make_readNextLine( fileText ) ;
671
672
673 try
674 {
675
676 this.parseVersionNumber( readLine() ) ;
677
678
679 let numCommentLines = 0 ;
680 while (this.parseCommentLine( lineOfFile = readLine() ))
681 {
682 this.gameBoard.comment[ numCommentLines ] = lineOfFile ;
683
684 if (++numCommentLines > this.gameBoard.maxNumCommentLines)
685 throw RangeError( "too many comment lines " + numCommentLines + " > " + this.gameBoard.maxNumCommentLines ) ;
686 }
687 this.gameBoard.numCommentLines = numCommentLines ;
688
689
690 let rules = this.parseRules( lineOfFile ) ;
691 if (rules)
692 {
693
694 this.gameBoard.rules.survival = rules[ 0 ] ;
695 this.gameBoard.rules.birth = rules[ 1 ] ;
696 lineOfFile = readLine() ;
697 }
698 else
699 {
700
701 this.gameBoard.rules.survival = { numRules : 2, numNeighbors : [ 2, 3, , , , , , , ] } ;
702 this.gameBoard.rules.birth = { numRules : 1, numNeighbors : [ 3, , , , , , , , ] } ;
703 }
704
705
706
707 for(;;)
708 {
709
710 let patternLocation = this.parsePatternLocation( lineOfFile ) ;
711
712 if (!patternLocation)
713 throw SyntaxError( "cannot parse pattern location line " + lineOfFile ) ;
714
715
716 let patternRow = 0 ;
717 while (this.parsePatternLine( lineOfFile = readLine(), patternLocation, patternRow, this.gameBoard ))
718 ++patternRow ;
719 }
720 }
721 catch( e )
722 {
723 if (e instanceof RangeError)
724 {
725
726 if (e.message === "end of file")
727 return true ;
728
729 else
730 {
731 alert( "ERROR in reading file: " + e.message ) ;
732 return false ;
733 }
734 }
735
736 else if (e instanceof SyntaxError )
737 {
738 alert( "ERROR in reading file: " + e.message ) ;
739 return false ;
740 }
741 }
742
743 return true ;
744 }
745
746
747
748 gameOfLife.parseVersionNumber = function( lineOfFile )
749 {
750 if (!lineOfFile.match( /^#Life 1\.05/))
751 throw "life file version number " + lineOfFile + " is not 1.05" ;
752 }
753
754
755
756
757 gameOfLife.parseCommentLine = function( lineOfFile )
758 {
759 if (lineOfFile.match( "^#D"))
760 return true ;
761
762 return false ;
763 }
764
765
766
767
768
769
770
771
772
773 gameOfLife.parseRules = function( lineOfFile )
774 {
775 let survival, birth ;
776
777
778 if (!lineOfFile.match( /^\s*#[NR]/ ))
779 return null ;
780
781
782 if (lineOfFile.match( /^\s*#N/ ))
783 {
784 survival = { numRules : 2, numNeighbors : [ 2, 3, , , , , , , ] } ;
785 birth = { numRules : 1, numNeighbors : [ 3, , , , , , , , ] } ;
786 return [ survival, birth ] ;
787 }
788
789
790 rulePattern = /^\s*#R\s*(\d+)\/(\d+)/ ;
791
792
793 let rulesString = lineOfFile.match( rulePattern ) ;
794 if (rulesString === null || rulesString.length != 3)
795 return null ;
796
797 return [ this.parseRulesList( rulesString[ 1 ] ), this.parseRulesList( rulesString[ 2 ] ) ] ;
798 }
799
800
801 gameOfLife.parseRulesList = function( rulesString )
802 {
803
804 let neighborCountsString = rulesString ;
805 let numNeighbors = new Array( 9 ) ;
806 let numRules = rulesString.length ;
807
808 for (let i = 0 ; i < numRules ; ++i)
809 numNeighbors[ i ] = parseInt( neighborCountsString.charAt( i ) ) ;
810
811 return new Rules( numRules, numNeighbors ) ;
812 }
813
814
815
816
817
818
819
820 gameOfLife.parsePatternLocation = function( lineOfFile )
821 {
822 let rulePattern = /^\s*#P\s*([+-]*\d+)\s*([+-]*\d+)/ ;
823
824
825 let patternString = lineOfFile.match( rulePattern ) ;
826 if (patternString === null || patternString.length != 3)
827 return null ;
828
829
830 let point = { x:0, y:0 } ;
831 point.x = parseInt( patternString[ 1 ] ) ;
832 point.y = parseInt( patternString[ 2 ] ) ;
833 return point ;
834 }
835
836
837
838
839
840
841
842
843
844
845 gameOfLife.parsePatternLine = function( lineOfFile, patternLocation, patternRow, gameBoard )
846 {
847
848 let centerRow = gameBoard.numRows / 2 ;
849 let centerCol = gameBoard.numCols / 2 ;
850
851
852 for (let col = 0 ; col < lineOfFile.length ; ++col)
853 {
854 let counter = lineOfFile.charAt( col ) ;
855
856
857 if (counter === "*")
858 {
859
860
861 counterCol = centerCol + patternLocation.x + col ;
862 counterRow = centerRow + patternLocation.y + patternRow ;
863
864
865 if (counterRow < 0 || counterRow >= gameBoard.numRows ||
866 counterCol < 0 || counterCol >= gameBoard.numCols)
867 {
868 throw "Game pattern out of bounds at pattern location row " + patternLocation.y + " col " + patternLocation.x +
869 " at counter row " + counterRow + " col " + counterCol ;
870 }
871
872
873 gameBoard.cell[ counterRow ][ counterCol ].occupied = Occupancy.Occupied ;
874 }
875
876 else if (counter === "." || counter === " " || counter === "\r" || counter === "\t" || counter === "\n" )
877 ;
878
879 else
880 return false ;
881 }
882
883 return true ;
884 }
885
886
887
888
889 gameOfLife.writeLifeFile = function( gameBoard )
890 {
891 let fileText = "#Life 1.05\n" ;
892
893
894 for (let row = 0 ; row < gameBoard.numCommentLines ; ++row)
895 fileText += (gameBoard.comment[ row ] + "\n") ;
896
897
898 if (gameBoard.rules.birth.numRules === 1 &&
899 gameBoard.rules.birth.numNeighbors[ 0 ] === 3 &&
900 gameBoard.rules.survival.numRules === 2 &&
901 gameBoard.rules.survival.numNeighbors[ 0 ] === 2 &&
902 gameBoard.rules.survival.numNeighbors[ 1 ] === 3)
903 {
904
905 fileText += "#N\n" ;
906 }
907
908 else
909 {
910 fileText += "#R " ;
911
912
913 for (let i = 0 ; i < gameBoard.rules.survival.numRules ; ++i)
914 fileText += gameBoard.rules.survival.numNeighbors[ i ] ;
915
916 fileText += "/" ;
917
918
919 for (let i = 0 ; i < gameBoard.rules.birth.numRules ; ++i)
920 fileText += gameBoard.rules.birth.numNeighbors[ i ] ;
921
922 fileText += "\n" ;
923 }
924
925
926 let boundingBoxes = this.traverseGameBoard( gameBoard ) ;
927
928
929 for (let i = 0 ; i < boundingBoxes.length ; ++i)
930 {
931 let box = boundingBoxes[ i ] ;
932
933
934 if (box.right - box.left > this.GameSettings.MaxFileLineLength)
935 {
936
937 boundingBoxes[ i ].left = box.left ;
938 boundingBoxes[ i ].top = box.top ;
939 boundingBoxes[ i ].bottom = box.bottom ;
940 boundingBoxes[ i ].right = box.left + this.GameSettings.MaxFileLineLength - 1 ;
941
942
943
944 let boxRight = [] ;
945 boxRight.left = box.left + this.GameSettings.MaxFileLineLength ;
946 boxRight.top = box.top ;
947 boxRight.bottom = box.bottom ;
948 boxRight.right = box.right ;
949 boundingBoxes.push( boxRight ) ;
950 }
951 }
952
953
954 let centerRow = gameBoard.numRows / 2 ;
955 let centerCol = gameBoard.numCols / 2 ;
956
957
958 for (i = 0 ; i < boundingBoxes.length ; ++i)
959 {
960 let box = boundingBoxes[ i ] ;
961
962
963 let patternRow = box.top - centerRow ;
964 let patternCol = box.left - centerCol ;
965 fileText += ("#P " + patternCol + " " + patternRow + "\n") ;
966
967
968 for (let row = box.top ; row <= box.bottom ; ++row)
969 fileText += this.createPatternLine( box, row ) ;
970 }
971
972 return fileText ;
973 }
974
975
976 gameOfLife.traverseGameBoard = function()
977 {
978 let numRows = this.gameBoard.numRows ;
979 let numCols = this.gameBoard.numCols ;
980
981
982 for (let row = 0 ; row < numRows ; ++row)
983 {
984 for (let col = 0 ; col < numCols ; ++col)
985 {
986 this.gameBoard.cell[ row ][ col ].label = 0 ;
987 this.gameBoard.cell[ row ][ col ].edge = 0 ;
988 this.gameBoard.cell[ row ][ col ].father = 0 ;
989 }
990 }
991
992
993 let startLabel = 1 ;
994 let boundingBoxes = [] ;
995
996 for (let row = 0 ; row < numRows ; ++row)
997 {
998 for (let col = 0 ; col < numCols ; ++col)
999 {
1000
1001 if (this.gameBoard.cell[ row ][ col ].occupied === Occupancy.Occupied && this.gameBoard.cell[ row ][ col ].label === 0)
1002 {
1003 boundingBoxes.push( this.depthFirstTraversal( row, col, startLabel++ ) ) ;
1004 }
1005
1006 }
1007 }
1008
1009 return boundingBoxes ;
1010 }
1011
1012
1013
1014
1015 gameOfLife.depthFirstTraversal = function( row, col, label )
1016 {
1017
1018 let numRows = this.gameBoard.numRows ;
1019 let numCols = this.gameBoard.numCols ;
1020
1021
1022 this.gameBoard.cell[ row ][ col ].label = -1 ;
1023
1024
1025 let boundingBox =
1026 {
1027 top : row,
1028 bottom : row,
1029 left : col,
1030 right : col
1031 } ;
1032
1033 let nextRow ;
1034 let nextCol ;
1035
1036 for (;;)
1037 {
1038
1039 let edge = this.nextUnmarkedEdge( row, col ) ;
1040
1041
1042 if (edge > 0)
1043 {
1044
1045 [nextRow, nextCol] = this.nextCell( row, col, edge ) ;
1046
1047
1048 this.markEdges( row, col, nextRow, nextCol, edge ) ;
1049
1050
1051 if ( (nextRow < numRows && nextCol < numCols && 0 <= nextRow && 0 <= nextCol) &&
1052 this.gameBoard.cell[ nextRow ][ nextCol ].occupied === Occupancy.Occupied &&
1053 this.gameBoard.cell[ nextRow ][ nextCol ].label === 0)
1054 {
1055
1056 this.gameBoard.cell[ nextRow ][ nextCol ].father =
1057 this.encodeFather( nextRow, nextCol, row, col ) ;
1058
1059
1060 this.gameBoard.cell[ nextRow ][ nextCol ].label = label ;
1061
1062
1063 boundingBox.top = Math.min( boundingBox.top, nextRow ) ;
1064 boundingBox.bottom = Math.max( boundingBox.bottom, nextRow ) ;
1065 boundingBox.left = Math.min( boundingBox.left, nextCol ) ;
1066 boundingBox.right = Math.max( boundingBox.right, nextCol ) ;
1067
1068
1069 row = nextRow ; col = nextCol ;
1070 }
1071
1072
1073
1074
1075
1076
1077
1078 else ;
1079 }
1080
1081 else
1082 {
1083
1084 if (this.gameBoard.cell[ row ][ col ].label === -1)
1085 {
1086
1087 this.gameBoard.cell[ row ][ col ].label = label ;
1088 break ;
1089 }
1090
1091
1092 edge = this.gameBoard.cell[ row ][ col ].father ;
1093 [row, col] = this.nextCell( row, col, edge ) ;
1094 }
1095 }
1096
1097 return boundingBox ;
1098 }
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110 gameOfLife.nextUnmarkedEdge = function( row, col )
1111 {
1112 let mask = 1 ;
1113 let edge = this.gameBoard.cell[ row ][ col ].edge ;
1114
1115 for (let bit = 0 ; bit < 8 ; ++bit)
1116 {
1117 if ((mask & edge) === 0)
1118 return mask ;
1119 mask <<= 1 ;
1120 }
1121
1122 return 0 ;
1123 }
1124
1125
1126
1127
1128
1129 gameOfLife.markEdges = function( row, col, nextRow, nextCol, edge )
1130 {
1131 let numRows = this.gameBoard.numRows ;
1132 let numCols = this.gameBoard.numCols ;
1133
1134
1135 this.gameBoard.cell[ row ][ col ].edge |= edge ;
1136
1137
1138 let oppositeEdge = 0 ;
1139 switch( edge )
1140 {
1141 case 0: oppositeEdge = 0 ; break ;
1142 case 1: oppositeEdge = 16 ; break ;
1143 case 2: oppositeEdge = 32 ; break ;
1144 case 4: oppositeEdge = 64 ; break ;
1145 case 8: oppositeEdge = 128 ; break ;
1146 case 16: oppositeEdge = 1 ; break ;
1147 case 32: oppositeEdge = 2 ; break ;
1148 case 64: oppositeEdge = 4 ; break ;
1149 case 128: oppositeEdge = 8 ; break ;
1150 default: break ;
1151 }
1152
1153
1154 if (nextRow < numRows && nextCol < numCols && 0 <= nextRow && 0 <= nextCol)
1155 this.gameBoard.cell[ nextRow ][ nextCol ].edge |= oppositeEdge ;
1156 }
1157
1158
1159
1160
1161
1162 gameOfLife.nextCell = function( row, col, edge )
1163 {
1164 let nextRow = nextCol = 0 ;
1165
1166 switch( edge )
1167 {
1168 case 1: nextRow = row ; nextCol = col + 1 ; break ;
1169 case 2: nextRow = row - 1 ; nextCol = col + 1 ; break ;
1170 case 4: nextRow = row - 1 ; nextCol = col ; break ;
1171 case 8: nextRow = row - 1 ; nextCol = col - 1 ; break ;
1172 case 16: nextRow = row ; nextCol = col - 1 ; break ;
1173 case 32: nextRow = row + 1 ; nextCol = col - 1 ; break ;
1174 case 64: nextRow = row + 1 ; nextCol = col ; break ;
1175 case 128: nextRow = row + 1 ; nextCol = col + 1 ; break ;
1176 default: break ;
1177 }
1178
1179 return [ nextRow, nextCol ] ;
1180 }
1181
1182
1183 gameOfLife.encodeFather = function( sonRow, sonCol, fatherRow, fatherCol )
1184 {
1185 let edge ;
1186 let rowChange = fatherRow - sonRow ;
1187 let colChange = fatherCol - sonCol ;
1188
1189 if (rowChange === 0 && colChange === 1) edge = 1 ;
1190 else if (rowChange === -1 && colChange === 1) edge = 2 ;
1191 else if (rowChange === -1 && colChange === 0) edge = 4 ;
1192 else if (rowChange === -1 && colChange === -1) edge = 8 ;
1193 else if (rowChange === 0 && colChange === -1) edge = 16 ;
1194 else if (rowChange === 1 && colChange === -1) edge = 32 ;
1195 else if (rowChange === 1 && colChange === 0) edge = 64 ;
1196 else if (rowChange === 1 && colChange === 1) edge = 128 ;
1197 else edge = 0 ;
1198
1199 return edge ;
1200 }
1201
1202
1203 gameOfLife.createPatternLine = function( box, row )
1204 {
1205 let lineOfFile = "" ;
1206 let lastCol = box.right ;
1207
1208
1209 for ( ; lastCol >= box.left ; --lastCol)
1210 if (this.gameBoard.cell[ row ][ lastCol ].occupied === Occupancy.Occupied)
1211 break ;
1212
1213
1214 for (let col = box.left ; col <= lastCol ; ++col)
1215 {
1216 if (this.gameBoard.cell[ row ][ col ].occupied === Occupancy.Occupied)
1217 lineOfFile += "*" ;
1218 else
1219 lineOfFile += "." ;
1220 }
1221
1222 lineOfFile += "\n" ;
1223
1224 return lineOfFile ;
1225 }
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242 function make_cycleGame( gameOfLifeApp )
1243 {
1244 return function()
1245 {
1246 gameOfLifeApp.cycleGame() ;
1247 } ;
1248 }
1249
1250
1251 function make_onCanvasClick( gameOfLifeApp )
1252 {
1253 return function( e )
1254 {
1255 let pos = gameOfLifeApp.gameBoard.canvasToCellCoord( gameOfLifeApp.gameBoard.getCursorPosition( e )) ;
1256 gameOfLifeApp.gameBoard.toggleCounter( pos ) ;
1257 } ;
1258 }
1259
1260
1261 function make_onMouseMove( gameOfLifeApp )
1262 {
1263 return function( e )
1264 {
1265
1266 let pos = gameOfLifeApp.gameBoard.canvasToCellCoord( gameOfLifeApp.gameBoard.getCursorPosition( e )) ;
1267
1268
1269 let row = pos[ 0 ] ;
1270 let col = pos[ 1 ] ;
1271
1272
1273 if (row >= 0 && col >= 0 && row < gameOfLifeApp.GameSettings.GameBoardNumRows && col < gameOfLifeApp.GameSettings.GameBoardNumCols)
1274 {
1275 let state = gameOfLifeApp.gameBoard.cell[ row ][ col ].state ;
1276
1277 gameOfLifeApp.cellStateElement.innerHTML =
1278 " row/col: " + row + " " + col +
1279 " occupied: " + gameOfLifeApp.gameBoard.cell[ row ][ col ].occupied +
1280 " occupied prev: " + gameOfLifeApp.gameBoard.cell[ row ][ col ].occupiedPreviously +
1281 " num neighbors: " + gameOfLifeApp.gameBoard.cell[ row ][ col ].numberOfNeighbors +
1282 " state: " + state +
1283 " age: " + gameOfLifeApp.gameBoard.cell[ row ][ col ].age +
1284 " label: " + gameOfLifeApp.gameBoard.cell[ row ][ col ].label +
1285 " father: " + gameOfLifeApp.gameBoard.cell[ row ][ col ].father +
1286 " edge: " + gameOfLifeApp.gameBoard.cell[ row ][ col ].edge ;
1287 }
1288 }
1289 }
1290
1291
1292 function make_loadSampleLifePattern( gameOfLifeApp )
1293 {
1294 return function( e )
1295 {
1296 let option = e.target.value ;
1297
1298 gameOfLifeApp.gameBoard.clearGameState() ;
1299 gameOfLifeApp.readLifeFile( gameOfLifeApp.sampleLifePatterns[ option ] ) ;
1300 gameOfLifeApp.gameBoard.updateView() ;
1301 gameOfLifeApp.updateRulesView() ;
1302 }
1303 }
1304
1305
1306 function make_fileSelectForReading( gameOfLifeApp )
1307 {
1308 return function( e )
1309 {
1310
1311
1312 let files = e.target.files ;
1313
1314
1315 for (let i = 0, f; f = files[i]; i++)
1316 {
1317
1318 if ( !f.name.match("\.lif"))
1319 continue ;
1320
1321 let reader = new FileReader() ;
1322
1323
1324
1325
1326 reader.onload = function()
1327 {
1328 gameOfLifeApp.clipboardElement.value = reader.result ;
1329
1330
1331 gameOfLifeApp.gameBoard.clearGameState() ;
1332 gameOfLifeApp.readLifeFile( reader.result ) ;
1333 gameOfLifeApp.gameBoard.updateView() ;
1334 gameOfLifeApp.updateRulesView() ;
1335 } ;
1336
1337
1338 reader.readAsText( f, "UTF-8" ) ;
1339 }
1340 }
1341 }
1342
1343
1344 function make_debugPrint( gameOfLifeApp )
1345 {
1346 return function( option )
1347 {
1348 let text = gameOfLifeApp.debugElement.innerHTML ;
1349
1350 switch( option )
1351 {
1352 case gameOfLifeApp.DebugPrintOptions.GameBoard:
1353
1354 text = "" ;
1355 text += gameOfLifeApp.printGameBoard( gameOfLifeApp.gameBoard ) ;
1356 break ;
1357
1358 case gameOfLifeApp.DebugPrintOptions.Neighbors:
1359 text += gameOfLifeApp.printNeighborCounts( gameOfLifeApp.gameBoard ) ;
1360 break ;
1361
1362 case gameOfLifeApp.DebugPrintOptions.States:
1363 text += gameOfLifeApp.printCounterState( gameOfLifeApp.gameBoard ) ;
1364 break ;
1365 }
1366
1367
1368 gameOfLifeApp.debugElement.innerHTML = text ;
1369
1370 }
1371 }
1372
1373
1374 function make_readNextLine( fileText )
1375 {
1376
1377 let linesOfFile = fileText.split( "\n" ) ;
1378 let numLinesInFile = linesOfFile.length ;
1379
1380 let lineNum = 0 ;
1381
1382
1383 return function()
1384 {
1385 if (lineNum < numLinesInFile)
1386 return linesOfFile[ lineNum++ ] ;
1387 else
1388 throw RangeError( "end of file" ) ;
1389 }
1390 }
1391
1392
1393 function supportsLocalStorage()
1394 {
1395
1396 return ("localStorage" in window) && window["localStorage"] !== null ;
1397 }
1398
1399 function writeClipboardToLocalStorage( file )
1400 {
1401 if (!supportsLocalStorage())
1402 return false;
1403
1404 localStorage[ "GameOfLife.file.name" ] = file ;
1405
1406 return true;
1407 }
1408
1409 function readLocalStorageToClipboard()
1410 {
1411 if (!supportsLocalStorage())
1412 return false;
1413
1414 file = localStorage[ "GameOfLife.file.name" ] ;
1415
1416 if (!file)
1417 return null ;
1418
1419 return file ;
1420 }
1421
1422
1423
1424
1425
1426 const Occupancy =
1427 {
1428 Indeterminate : -1,
1429 Empty : 0,
1430 Occupied : 1
1431 } ;
1432
1433 const State =
1434 {
1435 Indeterminate : -1,
1436 Survival : 0,
1437 Birth : 1,
1438 Death : 2
1439
1440 } ;
1441
1442
1443
1444 function Cell()
1445 {
1446
1447 if (arguments.length === 0)
1448 {
1449 this.numberOfNeighbors = 0 ;
1450 this.occupied = Occupancy.Empty ;
1451 this.occupiedPreviously = Occupancy.Indeterminate ;
1452 this.state = State.Indeterminate ;
1453 this.age = 0 ;
1454
1455
1456 this.label = -1 ;
1457 this.father = -1 ;
1458 this.edge = -1 ;
1459 }
1460 else if (arguments.length == 8)
1461 {
1462 this.numberOfNeighbors = arguments[ 0 ] ;
1463 this.occupied = arguments[ 1 ] ;
1464 this.occupiedPreviously = arguments[ 2 ] ;
1465 this.state = arguments[ 3 ] ;
1466 this.age = arguments[ 4 ] ;
1467
1468
1469 this.label = arguments[ 5 ] ;
1470 this.father = arguments[ 6 ] ;
1471 this.edge = arguments[ 7 ] ;
1472 }
1473 } ;
1474
1475
1476
1477 function Rules( numRules, neighborCounts )
1478 {
1479
1480 this.numNeighbors = new Array( 9 ) ;
1481
1482
1483 for (let i = 0 ; i < neighborCounts.length ; ++i)
1484 this.numNeighbors[ i ] = neighborCounts[ i ] ;
1485
1486 this.numRules = numRules ;
1487 } ;
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497 function GameBoard( GameSettings, debugPrint, DebugPrintOptions, canvasElement, gameStateElement )
1498 {
1499
1500 this.canvasElement = canvasElement ;
1501 this.widthPixels = canvasElement.width ;
1502 this.heightPixels = canvasElement.height ;
1503 this.graphicsContext = canvasElement.getContext( "2d" ) ;
1504
1505
1506 this.gameStateElement = gameStateElement ;
1507
1508
1509 this.DebugPrintOptions = DebugPrintOptions ;
1510 this.debugPrint = debugPrint ;
1511 this.GameSettings = GameSettings ;
1512
1513
1514 this.numRows = this.GameSettings.GameBoardNumRows ;
1515 this.numCols = this.GameSettings.GameBoardNumCols ;
1516
1517
1518 this.population = 0 ;
1519 this.generation = 0 ;
1520
1521
1522
1523 this.rules =
1524 {
1525 survival : undefined,
1526 birth : undefined
1527 } ;
1528
1529 this.rules.survival =
1530 {
1531 numRules : 2,
1532 numNeighbors : [ 2, 3, , , , , , , ]
1533 } ;
1534
1535 this.rules.birth =
1536 {
1537 numRules : 1,
1538 numNeighbors : [ 3, , , , , , , , ]
1539 } ;
1540
1541
1542
1543 this.cell = new Array( this.numRows ) ;
1544 for (let row = 0 ; row < this.numRows ; ++row)
1545 this.cell[ row ] = new Array( this.numCols ) ;
1546
1547
1548 for (let col = 0 ; col < this.numCols ; ++col)
1549 {
1550 for (let row = 0 ; row < this.numRows ; ++row)
1551 {
1552 this.cell[ row ][ col ] = new Cell() ;
1553 }
1554 }
1555
1556
1557 this.maxNumCommentLines = GameSettings.MaxNumCommentLines ;
1558 this.comment = new Array( GameSettings.MaxNumCommentLines ) ;
1559 this.numCommentLines = GameSettings.MaxNumCommentLines ;
1560
1561
1562 for (let row = 0 ; row < GameSettings.MaxNumCommentLines ; ++row)
1563 this.comment[ row ] = "#D" ;
1564 }
1565
1566
1567
1568
1569 GameBoard.prototype.updateGameBoard = function()
1570 {
1571 this.debugPrint( this.DebugPrintOptions.GameBoard ) ;
1572
1573
1574 this.countNeighbors() ;
1575
1576
1577 this.birthAndDeath() ;
1578
1579 this.debugPrint( this.DebugPrintOptions.Neighbors ) ;
1580 this.debugPrint( this.DebugPrintOptions.States ) ;
1581
1582
1583 ++this.generation ;
1584 }
1585
1586
1587
1588
1589 GameBoard.prototype.countNeighbors = function()
1590 {
1591
1592 let numRows = this.numRows ;
1593 let numCols = this.numCols ;
1594
1595
1596 for (let row = 0 ; row < numRows ; ++row)
1597 for (let col = 0 ; col < numCols ; ++col)
1598 this.cell[ row ][ col ].numberOfNeighbors = 0 ;
1599
1600
1601 for (let row = 0 ; row < numRows ; ++row)
1602 {
1603 if (this.cell[ row ][ 0 ].occupied === Occupancy.Occupied)
1604 this.boundaryNeighborCount( row, 0 ) ;
1605
1606 if (this.cell[ row ][ numCols - 1 ].occupied === Occupancy.Occupied)
1607 this.boundaryNeighborCount( row, numCols - 1 ) ;
1608 }
1609
1610
1611
1612 for (let col = 1 ; col <= numCols-2 ; ++col)
1613 {
1614 if (this.cell[ 0 ][ col ].occupied === Occupancy.Occupied)
1615 this.boundaryNeighborCount( 0, col ) ;
1616
1617 if (this.cell[ numRows - 1 ][ col ].occupied === Occupancy.Occupied)
1618 this.boundaryNeighborCount( numRows - 1, col ) ;
1619 }
1620
1621
1622 for (let row = 1 ; row <= numRows - 2 ; ++row)
1623 {
1624 for (let col = 1 ; col <= numCols - 2 ; ++col)
1625 {
1626
1627 if (this.cell[ row ][ col ].occupied === Occupancy.Occupied)
1628 {
1629
1630 ++this.cell[ row - 1 ][ col - 1 ].numberOfNeighbors ;
1631 ++this.cell[ row - 1 ][ col ].numberOfNeighbors ;
1632 ++this.cell[ row - 1 ][ col + 1 ].numberOfNeighbors ;
1633
1634 ++this.cell[ row ][ col - 1 ].numberOfNeighbors ;
1635 ++this.cell[ row ][ col + 1 ].numberOfNeighbors ;
1636
1637 ++this.cell[ row + 1 ][ col - 1 ].numberOfNeighbors ;
1638 ++this.cell[ row + 1 ][ col ].numberOfNeighbors ;
1639 ++this.cell[ row + 1 ][ col + 1 ].numberOfNeighbors ;
1640 }
1641 }
1642 }
1643 }
1644
1645
1646
1647 GameBoard.prototype.boundaryNeighborCount = function( row, col )
1648 {
1649 let adjRow, adjCol, adjTorusRow, adjTorusCol ;
1650
1651
1652 for (adjRow = row - 1 ; adjRow <= row + 1 ; ++adjRow)
1653 {
1654 for (adjCol = col - 1 ; adjCol <= col + 1 ; ++adjCol)
1655 {
1656 adjTorusRow = adjRow ;
1657 adjTorusCol = adjCol ;
1658
1659
1660 if (adjTorusRow <= -1)
1661 adjTorusRow = this.numRows - 1 ;
1662
1663 if (adjTorusCol <= -1)
1664 adjTorusCol = this.numCols - 1 ;
1665
1666 if (adjTorusRow >= this.numRows)
1667 adjTorusRow = 0 ;
1668
1669 if (adjTorusCol >= this.numCols)
1670 adjTorusCol = 0 ;
1671
1672
1673 ++this.cell[ adjTorusRow ][ adjTorusCol ].numberOfNeighbors ;
1674 }
1675 }
1676
1677
1678
1679 --this.cell[ row ][ col ].numberOfNeighbors ;
1680 }
1681
1682
1683
1684 GameBoard.prototype.birthAndDeath = function()
1685 {
1686 let caseOfSurvival, caseOfBirth, cell ;
1687
1688 this.population = 0 ;
1689
1690 for (let row = 0 ; row < this.numRows ; ++row)
1691 {
1692 for (let col = 0 ; col < this.numCols ; ++col)
1693 {
1694
1695 let cell = this.cell[ row ][ col ] ;
1696
1697
1698 cell.occupiedPreviously = cell.occupied ;
1699
1700 caseOfBirth = caseOfSurvival = false ;
1701
1702
1703 if (cell.occupied === Occupancy.Empty)
1704 {
1705 for (let i = 0 ; i < this.rules.birth.numRules ; ++i)
1706 {
1707 if (cell.numberOfNeighbors === this.rules.birth.numNeighbors[ i ])
1708 {
1709 caseOfBirth = true ;
1710 cell.occupied = Occupancy.Occupied ;
1711 cell.state = State.Birth ;
1712 cell.age = 0 ;
1713
1714
1715 break ;
1716 }
1717 }
1718 }
1719 else if (cell.occupied === Occupancy.Occupied)
1720 {
1721 for (i = 0 ; i < this.rules.survival.numRules ; ++i)
1722 {
1723 if (cell.numberOfNeighbors === this.rules.survival.numNeighbors[ i ])
1724 {
1725 caseOfSurvival = true ;
1726
1727 cell.state = State.Survival ;
1728 ++cell.age ;
1729 if (cell.age > this.GameSettings.MaximumAge)
1730 cell.age = 1 ;
1731
1732
1733 break ;
1734 }
1735 }
1736
1737 }
1738
1739
1740
1741 if (!caseOfSurvival && !caseOfBirth)
1742 {
1743
1744 if (cell.occupied === Occupancy.Occupied)
1745 {
1746 cell.occupied = Occupancy.Empty ;
1747 cell.state = State.Death ;
1748 cell.age = 0 ;
1749 }
1750
1751 else
1752 {
1753 ++cell.age ;
1754 if (cell.age > this.GameSettings.MaximumAge)
1755 cell.age = 1 ;
1756 }
1757 }
1758
1759
1760 if (cell.occupied === Occupancy.Occupied)
1761 ++this.population ;
1762 }
1763 }
1764 }
1765
1766
1767
1768
1769 GameBoard.prototype.drawLifeGrid = function()
1770 {
1771
1772 this.graphicsContext.strokeStyle = "rgba(230,230,255,1.0)"
1773
1774
1775 this.graphicsContext.clearRect( 0, 0, this.widthPixels, this.heightPixels ) ;
1776
1777
1778 this.graphicsContext.beginPath();
1779
1780 let cellWidth = this.widthPixels / this.numCols ;
1781 let cellHeight = this.heightPixels / this.numRows ;
1782
1783
1784 for (let x = 0 ; x <= this.widthPixels ; x += cellWidth)
1785 {
1786 this.graphicsContext.moveTo( 0.5 + x, 0 ) ;
1787 this.graphicsContext.lineTo( 0.5 + x, this.heightPixels ) ;
1788 }
1789
1790
1791 for (let y = 0; y <= this.heightPixels ; y += cellHeight )
1792 {
1793 this.graphicsContext.moveTo( 0, 0.5 + y ) ;
1794 this.graphicsContext.lineTo( this.widthPixels, 0.5 + y ) ;
1795 }
1796
1797
1798 this.graphicsContext.stroke();
1799 this.graphicsContext.closePath() ;
1800 }
1801
1802
1803 GameBoard.prototype.canvasToCellCoord = function( pos )
1804 {
1805 let cellWidth = this.widthPixels / this.numCols ;
1806 let cellHeight = this.heightPixels / this.numRows ;
1807
1808 let col = Math.floor( pos[0] / cellWidth ) ;
1809 let row = Math.floor( pos[1] / cellHeight ) ;
1810
1811 return [row, col] ;
1812 }
1813
1814
1815 GameBoard.prototype.cellToCanvasCoord = function( pos )
1816 {
1817 let cellWidth = this.widthPixels / this.numCols ;
1818 let cellHeight = this.heightPixels / this.numRows ;
1819
1820
1821 let x = cellWidth * pos[1] + cellWidth / 2 ;
1822 let y = cellHeight * pos[0] + cellHeight / 2 ;
1823
1824 return [x, y] ;
1825 }
1826
1827 GameBoard.prototype.getCursorPosition = function( e )
1828 {
1829
1830
1831
1832 let canvasRect = this.canvasElement.getBoundingClientRect() ;
1833 let x = e.clientX - canvasRect.left ;
1834 let y = e.clientY - canvasRect.top ;
1835
1836
1837 let scaleX = this.canvasElement.width / canvasRect.width ;
1838 let scaleY = this.canvasElement.height / canvasRect.height ;
1839
1840 x *= scaleX ;
1841 y *= scaleY ;
1842
1843 return [x, y] ;
1844 }
1845
1846
1847 GameBoard.prototype.toggleCounter = function( pos )
1848 {
1849 let cell = this.cell[ pos[0] ][ pos[1] ] ;
1850
1851
1852 cell.occupiedPreviously = cell.occupied ;
1853
1854
1855 if (cell.occupied === Occupancy.Empty)
1856 cell.occupied = Occupancy.Occupied ;
1857 else if (cell.occupied === Occupancy.Occupied)
1858 cell.occupied = Occupancy.Empty ;
1859
1860 this.drawCell( pos ) ;
1861 }
1862
1863
1864 GameBoard.prototype.drawCell = function( pos )
1865 {
1866
1867 let cell = this.cell[ pos[0] ][ pos[1] ] ;
1868
1869
1870 let centerOfCell = this.cellToCanvasCoord( pos ) ;
1871
1872 let cellWidth = this.widthPixels / this.numRows ;
1873 let cellHeight = this.heightPixels / this.numCols ;
1874 let radius = cellWidth / 2 - 0.8 ;
1875
1876
1877 if (cell.occupied === cell.occupiedPreviously && cell.occupied !== Occupancy.Indeterminate)
1878 {
1879
1880 if (cell.age === this.GameSettings.OldAge && cell.occupied === Occupancy.Occupied)
1881 {
1882 this.graphicsContext.beginPath();
1883 this.graphicsContext.fillStyle = "rgba( 185, 65, 64, 1.0 )"
1884 this.graphicsContext.arc( centerOfCell[0], centerOfCell[1], radius, 0, Math.PI*2, true ) ;
1885 this.graphicsContext.fill();
1886 this.graphicsContext.closePath() ;
1887 }
1888
1889
1890 return ;
1891 }
1892
1893
1894
1895
1896 if (cell.occupied === Occupancy.Occupied)
1897 {
1898 this.graphicsContext.beginPath();
1899
1900 if( cell.age >= this.GameSettings.OldAge )
1901 this.graphicsContext.fillStyle = "rgba( 185, 65, 64, 1.0 )"
1902 else
1903 this.graphicsContext.fillStyle = "rgba( 0, 100, 255, 1.0 )"
1904
1905 this.graphicsContext.arc( centerOfCell[ 0 ], centerOfCell[ 1 ], radius, 0, Math.PI * 2, true ) ;
1906 this.graphicsContext.fill();
1907 this.graphicsContext.closePath() ;
1908 }
1909
1910 else if (cell.occupied === Occupancy.Empty)
1911 {
1912
1913
1914
1915 let x1 = centerOfCell[ 0 ] - cellWidth / 2 ;
1916 let y1 = centerOfCell[ 1 ] - cellHeight / 2 ;
1917 let x2 = centerOfCell[ 0 ] + cellWidth / 2 ;
1918 let y2 = centerOfCell[ 1 ] + cellHeight / 2 ;
1919
1920
1921 this.graphicsContext.clearRect( x1, y1, cellWidth, cellHeight ) ;
1922
1923
1924 this.graphicsContext.strokeStyle = "rgba(230,230,255,1.0)"
1925
1926
1927 this.graphicsContext.beginPath();
1928 this.graphicsContext.moveTo( x1 + 0.5, y1 ) ;
1929 this.graphicsContext.lineTo( x1 + 0.5, y2 ) ;
1930
1931 this.graphicsContext.moveTo( x2 + 0.5, y1 ) ;
1932 this.graphicsContext.lineTo( x2 + 0.5, y2 ) ;
1933
1934 this.graphicsContext.moveTo( x1 + 0.5, y1 + 0.5 ) ;
1935 this.graphicsContext.lineTo( x2 + 0.5, y1 + 0.5 ) ;
1936
1937 this.graphicsContext.stroke();
1938 this.graphicsContext.closePath() ;
1939 }
1940 }
1941
1942 GameBoard.prototype.clearGameState = function()
1943 {
1944 this.population = 0 ;
1945 this.generation = 0 ;
1946
1947
1948 for (let col = 0 ; col < this.numCols ; ++col)
1949 {
1950 for (let row = 0 ; row < this.numRows ; ++row)
1951 {
1952 let cell = this.cell[ row ][ col ] ;
1953
1954 cell.numberOfNeighbors = 0 ;
1955 cell.occupied = Occupancy.Empty ;
1956 cell.occupiedPreviously = Occupancy.Indeterminate ;
1957 cell.state = State.Indeterminate ;
1958 cell.age = 0 ;
1959 cell.label = -1 ;
1960 cell.father = -1 ;
1961 cell.edge = -1 ;
1962 }
1963 }
1964
1965
1966 this.numCommentLines = 1 ;
1967 this.comment[ 0 ] = "#D Your comment here!" ;
1968 }
1969
1970
1971 GameBoard.prototype.updateView = function()
1972 {
1973 for (let row = 0 ; row < this.numRows ; ++row)
1974 {
1975 for (let col = 0 ; col < this.numCols ; ++col)
1976 {
1977 let pos = [row, col] ;
1978 this.drawCell( pos ) ;
1979 }
1980 }
1981
1982 let text = "Generation " + this.generation + " Population " + this.population ;
1983
1984
1985 this.gameStateElement.innerHTML = text ;
1986 }