1
2
3
4
5(function () {
6 'use strict';
7
8 const isNode = typeof module !== 'undefined' && typeof require !== 'undefined';
9 const Life = isNode ? require('./life.js') : window.Life;
10
11 const results = [];
12 function record(pass, msg, detail) {
13 results.push({ pass: pass, msg: msg, detail: detail || '' });
14 }
15
16 function assert(cond, msg) {
17 record(!!cond, msg);
18 }
19
20 function assertGrid(actual, expected, msg) {
21 const a = JSON.stringify(actual);
22 const e = JSON.stringify(expected);
23 record(a === e, msg, a === e ? '' : 'expected ' + e + ' got ' + a);
24 }
25
26
27 function fromAscii(rows) {
28 return rows.map(function (line) {
29 return line.split('').map(function (ch) {
30 return ch === 'O' || ch === '#' || ch === '1' || ch === '*' ? 1 : 0;
31 });
32 });
33 }
34
35
36
37
38 (function () {
39 const block = fromAscii([
40 '....',
41 '.OO.',
42 '.OO.',
43 '....',
44 ]);
45 assertGrid(Life.step(block, false), block, 'Block is a stable still life');
46 })();
47
48
49
50
51 (function () {
52 const horizontal = fromAscii([
53 '.....',
54 '.....',
55 '.OOO.',
56 '.....',
57 '.....',
58 ]);
59 const vertical = fromAscii([
60 '.....',
61 '..O..',
62 '..O..',
63 '..O..',
64 '.....',
65 ]);
66 const g1 = Life.step(horizontal, false);
67 assertGrid(g1, vertical, 'Blinker: horizontal -> vertical');
68 const g2 = Life.step(g1, false);
69 assertGrid(g2, horizontal, 'Blinker: vertical -> horizontal (period 2)');
70 })();
71
72
73
74
75 (function () {
76 const g = fromAscii([
77 '...',
78 '.O.',
79 '...',
80 ]);
81 const next = Life.step(g, false);
82 assert(next[1][1] === 0, 'Lone cell dies from underpopulation');
83 })();
84
85
86
87
88 (function () {
89
90 const g = fromAscii([
91 '.....',
92 '.....',
93 '.OOO.',
94 '.....',
95 '.....',
96 ]);
97 const next = Life.step(g, false);
98 assert(next[2][2] === 1, 'Live cell with 2 neighbours survives');
99 })();
100
101
102
103
104 (function () {
105
106
107 const g = fromAscii([
108 '.....',
109 '..O..',
110 '.OOO.',
111 '..O..',
112 '.....',
113 ]);
114 const next = Life.step(g, false);
115 assert(next[2][2] === 0, 'Live cell with 4 neighbours dies (overpopulation)');
116 })();
117
118
119
120
121 (function () {
122 const g = fromAscii([
123 '.....',
124 '.OO..',
125 '.O...',
126 '.....',
127 '.....',
128 ]);
129
130
131 const next = Life.step(g, false);
132
133 assert(next[2][2] === 1, 'Dead cell with exactly 3 neighbours is born');
134 })();
135
136
137
138
139 (function () {
140 const g = fromAscii([
141 'O.O',
142 '...',
143 'O.O',
144 ]);
145
146 assert(Life.countNeighbors(g, 1, 1, false) === 4, 'countNeighbors interior = 4');
147
148
149
150 assert(Life.countNeighbors(g, 0, 0, false) === 0, 'countNeighbors corner no-wrap = 0');
151
152
153
154 assert(Life.countNeighbors(g, 0, 0, true) === 3, 'countNeighbors corner wrap = 3');
155 })();
156
157
158
159
160 (function () {
161
162
163 const g = fromAscii([
164 '.O.',
165 '...',
166 '.O.',
167 ]);
168
169
170
171
172
173
174 assert(Life.countNeighbors(g, 1, 1, true) === 2, 'wrap neighbour count sanity');
175
176
177
178 const g2 = Life.makeGrid(4, 4);
179 g2[0][0] = 1;
180 assert(Life.countNeighbors(g2, 3, 3, true) === 1, 'top-left cell wraps to bottom-right neighbourhood');
181 assert(Life.countNeighbors(g2, 3, 3, false) === 0, 'no wrap: opposite corner sees nothing');
182 })();
183
184
185
186
187 (function () {
188 const size = 12;
189 let g = Life.makeGrid(size, size);
190 Life.placePattern(g, Life.PATTERNS.glider.cells, 1, 1, false);
191 const startPop = Life.population(g);
192
193 let moved = g;
194 for (let i = 0; i < 4; i++) {
195 moved = Life.step(moved, false);
196 }
197
198
199 const expected = Life.makeGrid(size, size);
200 Life.placePattern(expected, Life.PATTERNS.glider.cells, 2, 2, false);
201
202 assertGrid(moved, expected, 'Glider translates by (1,1) after 4 generations');
203 assert(Life.population(moved) === startPop, 'Glider preserves its 5-cell population');
204 })();
205
206
207
208
209 (function () {
210 const size = 17;
211 let g = Life.makeGrid(size, size);
212 Life.placePattern(g, Life.PATTERNS.pulsar.cells, 2, 2, false);
213 let cur = g;
214 for (let i = 0; i < 3; i++) {
215 cur = Life.step(cur, false);
216 }
217 assertGrid(cur, g, 'Pulsar returns to start after 3 generations (period 3)');
218
219 assert(JSON.stringify(Life.step(g, false)) !== JSON.stringify(g),
220 'Pulsar actually changes after 1 generation');
221 })();
222
223
224
225
226 (function () {
227 const g = fromAscii([
228 '.....',
229 '.OOO.',
230 '.....',
231 ]);
232 const snapshot = JSON.stringify(g);
233 Life.step(g, false);
234 assert(JSON.stringify(g) === snapshot, 'step() does not mutate its input grid');
235 })();
236
237
238
239
240 const passed = results.filter(function (r) { return r.pass; }).length;
241 const failed = results.length - passed;
242
243 if (isNode) {
244 results.forEach(function (r) {
245 const tag = r.pass ? '\x1b[32mPASS\x1b[0m' : '\x1b[31mFAIL\x1b[0m';
246 console.log(tag + ' ' + r.msg + (r.detail ? '\n ' + r.detail : ''));
247 });
248 console.log('\n' + passed + ' passed, ' + failed + ' failed, ' + results.length + ' total');
249 process.exit(failed > 0 ? 1 : 0);
250 } else {
251 const root = document.getElementById('results');
252 const summary = document.getElementById('summary');
253 summary.textContent = passed + ' passed, ' + failed + ' failed, ' + results.length + ' total';
254 summary.className = failed > 0 ? 'summary fail' : 'summary pass';
255 results.forEach(function (r) {
256 const li = document.createElement('li');
257 li.className = r.pass ? 'pass' : 'fail';
258 li.innerHTML = '<span class="tag">' + (r.pass ? 'PASS' : 'FAIL') + '</span>' +
259 '<span class="msg">' + r.msg + '</span>' +
260 (r.detail ? '<span class="detail">' + r.detail + '</span>' : '');
261 root.appendChild(li);
262 });
263 }
264})();
265
Discussion
No comments yet. Start the discussion. Recorded by @patrick-toulme.