Home] [About] [Posts] [Resources] [Applets]
dir: Home /
Applets /
Conway's game of life (64x64)
published-date: 11 Jun 2025 09:25 +0700
1window.onload = function()
2{
3
4const canvas = document.getElementById("viewport");
5const ctx = canvas.getContext("2d", {"alpha": false});
6const ftoi = (v)=>~~v;
7const iwrap = (a, b) => a<0?b+(a%b):a%b;
8const isNumeric = (v) => !isNaN(parseFloat(v)) && isFinite(v);
9var sim = null;
10var inputProxy = null;
11var timer = null;
12var recorder = null;
13const states = {};
14states.running = 1;
15states.recording = 0;
16
17
18function cTimer () {
19 this.f = 0;
20 this.fps = 0;
21 this.t = 0;
22 this.tt = 0;
23 this.pt = 0;
24 this.dt = 0;
25 this.update = () => {
26 this.f += 1;
27 this.t = Date.now();
28 this.dt = this.t - this.pt;
29 this.tt += this.dt;
30 this.pt = this.t;
31 }
32 this.ontimed = (t, cb) => {
33 if (this.tt > t) {
34 this.tt = 0;
35 cb();
36 }
37 }
38
39};
40
41
42function cInputProxy () {
43 this.values = {};
44 this.target = {};
45 this.display = {};
46 this.defaults = {};
47 this.register = (name, _default=null, cast=(v)=>v) => {
48 this.target[name] = document.getElementById(name);
49 this.display[name] = this.target[name].parentElement.querySelector("#" + name + "_value");
50 this.values[name] = cast(this.target[name].value);
51 this.defaults[name] = _default;
52 this.target[name].addEventListener("input", (event) => {
53 this.values[name] = cast(event.target.value);
54 if (this.display[name] !== null) {
55 this.display[name].innerHTML = "" + event.target.value;
56 };
57 });
58 if (_default !== null) {
59 this.target[name].value = "" + _default;
60 this.values[name] = cast(_default);
61 if (this.display[name] !== null) {
62 this.display[name].innerHTML = "" + _default;
63 };
64 };
65 return this.target[name];
66 };
67};
68
69
70class ac2DMatrix {
71 itox(i) {return i % this.w};
72 itoy(i) {return ftoi(i / this.w)};
73 ctoi(x, y) {return x + (y * this.w)};
74 get(x, y) {return this.buf[this.ctoi(x, y)]};
75 set(x, y, v) {this.buf[this.ctoi(x, y)] = v;};
76 map(cb) {for (let i=0; i<this.l; i++){this.buf[i] = cb(this.buf[i])}};
77 zeros() {for (let i=0; i<this.l; i++) {this.buf[i] = 0}};
78 sum() {let s=0; for (let i=0; i<this.l; i++) {s+=this.buf[i]}; return s};
79 count(o) {let s=0; for (let i=0; i<this.l; i++) {if(this.buf[i]===o){s+=1}}; return s};
80 load2DArray(arr) {
81 for (let x=0; x<this.w; x++) {
82 for (let y=0; y<this.h; y++) {
83 this.set(x, y, arr[y][x]);
84 };
85 };
86 };
87 to2DArray() {
88 let r = []
89 for (let y=0; y<this.h; y++) {
90 r.push([]);
91 for (let x=0; x<this.w; x++) {
92 r[y].push(this.get(x, y));
93 };
94 };
95 return r;
96 };
97 queryKernel(mk, x, y, wrap) {
98 const lx = ftoi(mk.w/2);
99 const ly = ftoi(mk.h/2);
100 for (let kx=0; kx<mk.w; kx++) {
101 for (let ky=0; ky<mk.h; ky++) {
102 let dx = x+kx-lx;
103 let dy = y+ky-ly;
104 if (wrap||false) {
105 dx = iwrap(dx, this.w)
106 dy = iwrap(dy, this.h)
107 };
108 mk.set(kx, ky, this.get(dx, dy) || null)
109 };
110 };
111 };
112};
113
114
115class cUint8Matrix extends ac2DMatrix {
116 constructor (w, h) {
117 super();
118 this.w = w; this.h = h; this.l = w*h;
119 this.buf = new Uint8Array(this.l);
120 };
121};
122
123
124class CanvasRecorder {
125 constructor (canvas) {
126 this.canvas = canvas;
127 this.media = null;
128 this.data = [];
129 this.timeout = null;
130 };
131 start(onstop, fps) {
132 if (this.media !== null) return;
133 const stream = this.canvas.captureStream();
134 this.media = new MediaRecorder(stream);
135 this.media.ondataavailable = (chunk)=>this.data.push(chunk.data);
136 this.media.onstop = ()=>{this.save(); onstop()};
137 this.timeout = setTimeout(()=>{this.stop()}, 20000);
138 this.media.start();
139 console.log('recording started')
140 };
141 stop() {
142 console.log('recording stopped')
143 if (this.timeout !== null) {
144 clearTimeout(this.timeout)
145 }
146 this.timeout = null;
147 if (this.media !== null && this.media.state === 'recording') {
148 this.media.stop();
149 }
150 };
151 save() {
152 if (this.media === null) throw "media is null";
153 if (this.data.length === 0) return;
154 const link = document.createElement('a');
155 link.href = URL.createObjectURL(new Blob(this.data, {type:'video/webm'}))
156 link.download = 'gol.webm';
157 link.click();
158 this.destroy();
159 };
160 destroy() {
161 this.data.length = 0;
162 this.media = null;
163 console.log('recorder destroyed')
164 };
165};
166
167class CellRenderer {
168 constructor (w, h, cw, ch) {
169 this.w = w; this.cw = cw;
170 this.h = h; this.ch = ch;
171 this.rx = w / cw;
172 this.ry = h / ch;
173 };
174 draw(ctx, x, y) {
175 ctx.rect(x, y, this.rx, this.ry)
176 };
177};
178
179class ParticleSim {
180 constructor (w, h) {
181 this.w = w; this.h = h, this.l=this.w*this.h;
182 this.zb = [new cUint8Matrix(this.w, this.h), new cUint8Matrix(this.w, this.h)];
183 this.z = this.zb[0];
184 this.k = null;
185 this.rules = {};
186 this.colors = ["white"];
187 this.renderQueue = Array.from({length:this.colors.length+1}, ()=>[])
188 };
189 swpz() {
190 this.z = this.zb[1];
191 this.zb[1] = this.zb[0];
192 this.zb[0] = this.z;
193 };
194 clearRenderQueue() {
195 for (let n=0; n<this.renderQueue.length; n++) {
196 if (this.renderQueue[n].length){
197 this.renderQueue[n].length = 0;
198 };
199 };
200 };
201 update() {
202 if (this.k === null) {throw "kernel is null"};
203 let a = this.zb[0];
204 let b = this.zb[1];
205 let v = null;
206 b.zeros();
207 this.clearRenderQueue()
208 for (let i=0; i<a.l; i++) {
209 let x = a.itox(i);
210 let y = a.itoy(i);
211 a.queryKernel(this.k, x, y, true);
212 v = a.get(x,y);
213 v = this.rules[v](this.k);
214 if (v > 0) {
215 this.renderQueue[v].push(i);
216 };
217 b.set(b.itox(i), b.itoy(i), v);
218 };
219 this.swpz();
220 };
221 setCell(x, y, v) {
222 this.z.set(iwrap(x, this.w), iwrap(y, this.h), v);
223 this.renderQueue[v].push(this.z.ctoi(iwrap(x, this.w), iwrap(y, this.h)))
224 };
225 render(ctx, w, h) {
226 const rx = (w-1) / this.w;
227 const ry = (h-1) / this.h;
228 const px = 2;
229 var i;
230 ctx.strokeStyle = "rgba(255,255,255,.1)";
231 ctx.beginPath();
232 for (let x=0; x<=this.w; x++) {
233 ctx.moveTo(x*rx+1, 0);
234 ctx.lineTo(x*rx+1, h);
235 };
236 for (let y=0; y<=this.h; y++) {
237 ctx.moveTo(0, y*ry+1);
238 ctx.lineTo(w, y*ry+1);
239 }
240 ctx.stroke();
241 ctx.strokeStyle = "rgba(255,255,255,.5)";
242 ctx.beginPath();
243 for (let x=0; x<=this.w; x+=10) {
244 ctx.moveTo(x*rx+1, 0);
245 ctx.lineTo(x*rx+1, 10);
246 };
247 for (let y=0; y<=this.h; y+=10) {
248 ctx.moveTo(0, y*ry+1);
249 ctx.lineTo(10, y*ry+1);
250 }
251 ctx.stroke();
252 for (let n=0; n<this.renderQueue.length; n++) {
253 if (this.renderQueue[n].length > 0) {
254 ctx.beginPath();
255 ctx.fillStyle = this.colors[(n+1)%this.colors.length]
256 while (this.renderQueue[n].length > 0) {
257 i = this.renderQueue[n].pop();
258 ctx.rect(rx*this.z.itox(i)+px, ry*this.z.itoy(i)+px, rx-px, ry-px)
259 };
260 ctx.fill();
261 };
262 };
263 };
264 reset() {
265 this.zb[0].zeros();
266 this.zb[1].zeros();
267 };
268};
269
270
271const rulesSet = {};
272rulesSet['B3/S23'] = (sim) => {
273 sim.k = new cUint8Matrix(3, 3);
274 sim.rules[0] = (k) => {
275 let c = k.count(1);
276 return c === 3 ? 1 : 0;
277 };
278 sim.rules[1] = (k) => {
279 let c = k.count(1) - 1;
280 return (c === 2 || c === 3) ? 1 : 0;
281 };
282};
283
284
285function validateRLE(s) {
286 s = s.replace(/\s/g, "");
287 let ret = true;
288 let chr = "";
289 for (let i=0; i<s.length; i++) {
290 chr = s.charAt(i);
291 ret &&= (chr === "$") || (chr === "b") || (chr === "o") || isNumeric(chr) || (i === s.length - 1 && chr === "!");
292 }
293 return ret
294};
295
296function parseRLE(s) {
297 s = s.replace(/\s/g, "");
298 let chr = "";
299 let num = "";
300 let tmp = 0;
301 let row = 0;
302 let off = 0;
303 let ret = [];
304 const flushNum = () => {
305 if (num === "") {
306 return 1;
307 };
308 tmp = parseInt(num);
309 num = "";
310 return tmp
311 };
312 for (let i=0; i<s.length; i++) {
313 chr = s.charAt(i);
314 if (chr === "!") {
315 break
316 } else if (isNumeric(chr)) {
317 num += chr;
318 } else if (chr === "$") {
319 row += flushNum();
320 off = 0;
321 } else if (chr === "b") {
322 off += flushNum();
323 } else if (chr === "o") {
324 tmp = flushNum();
325 for (let j=0; j<tmp; j++) {
326 ret.push([off, row]);
327 off += 1;
328 };
329 } else {
330 throw "rule undefined for token " + chr;
331 };
332 };
333 return ret
334};
335
336const presets = {};
337const e1ul = [[0,0],[1,0],[0,1],[1,2],[2,2],[3,2],[3,3]]
338const e1dr = [[0,0],[0,1],[1,1],[2,1],[3,2],[2,3],[3,3]]
339
340presets['placeholder'] = [[[0,0]]];
341presets['glider1'] = [];
342const g1p1dr = [[1,0],[2,2],[2,1],[1,2],[0,2]];
343const g1p2dr = [[0,0],[1,1],[2,1],[0,2],[1,2]];
344const g1p1ur = [[0,0],[1,0],[1,1],[2,1],[0,2]];
345const g1p2ur = [[0,0],[1,0],[2,0],[2,1],[1,2]];
346const g1p1dl = [[1,0],[0,1],[0,2],[1,2],[2,2]];
347const g1p2dl = [[2,0],[0,1],[1,1],[1,2],[2,2]];
348const g1p1ul = [[0,0],[1,0],[2,0],[0,1],[1,2]];
349const g1p2ul = [[0,1],[1,0],[1,1],[2,0],[2,2]];
350for (let i=0; i<4; i++) {
351 presets['glider1'].push([[ i*16, i*16], ...(Math.random() > .5 ? g1p1dr: g1p2dr)])
352 presets['glider1'].push([[ i*16+32-8, i*16-8], ...(Math.random() > .5 ? g1p1ul: g1p2ul)])
353 presets['glider1'].push([[-i*16+32-8, i*16], ...(Math.random() > .5 ? g1p1ur: g1p2ur)])
354 presets['glider1'].push([[-i*16+8-8 , i*16-8], ...(Math.random() > .5 ? g1p1dl: g1p2dl)])
355};
356
357presets['glider2'] = [];
358const g2p1 = [[1,1],[4,1],[5,2],[1,3],[5,3],[2,4],[3,4],[4,4],[5,4]];
359const g2p2 = [[4,1],[2,2],[6,2],[7,3],[2,4],[7,4],[3,5],[4,5],[5,5],[6,5],[7,5]];
360const g2p3 = [[4,1],[5,1],[2,2],[7,2],[8,3],[2,4],[8,4],[3,5],[4,5],[5,5],[6,5],[7,5],[8,5]];
361for (let i=0; i<8; i++) {presets['glider2'].push([[i*8, 20+0], ...g2p1])};
362for (let i=0; i<8; i++) {presets['glider2'].push([[i*8, 20+8], ...g2p2])};
363for (let i=0; i<4; i++) {presets['glider2'].push([[i*16,20+16], ...g2p3])};
364
365presets['glider3'] = [
366 [[0,3], ...parseRLE("4o$o3bo$o$bo2bo2$5b4o$4bo2bo$4bo2bo$4bo2bo$5b4o2$bo2bo$o$o3bo$4o!")],
367 [[15,2], ...parseRLE("2bo6b$2o7b$o2b3o2bo$o4b3ob$b3o2b2ob2$b3o2b2ob$o4b3ob$o2b3o2bo$2o7b$2bo")],
368 [[50,10], ...parseRLE("3bo$bo3bo$o$o4bo$5o2$5b2o$5b2o2$6b2o$4bo4bo$3bo$3bo5bo$3b6o!")],
369 [[32,4], ...parseRLE("2$11bo$5b3o2bobo$5bo6bo$5bobo3bo$6b2o$5b2o$3b4o$2b2o3bo$b2ob2ob2o$2bo" +
370 "4bo$3b2o2$3b2o$2bo4bo$b2ob2ob2o$2b2o3bo$3b4o$5b2o$6b2o$5bobo3bo$5bo6b" +
371 "o$5b3o2bobo$11bo!")],
372 [[10,43], ...parseRLE("21bo$18b4o$13bo2bob2o$13bo$4o8bo3bob2o$o3bo5b2ob2obobob5o$o9b2obobobo" +
373 "2b5o$bo2bo2b2o2bo3b3o2bob2o$6bo2bob2o$6bo4b2o$6bo2bob2o$bo2bo2b2o2bo3b" +
374 "3o2bob2o$o9b2obobobo2b5o$o3bo5b2ob2obobob5o$4o8bo3bob2o$13bo$13bo2bob" +
375 "2o$18b4o$21bo!")],
376 [[43,40], ...parseRLE("2b2o$b4o$2ob2o$b2o4$4b2o$3bobo4bobo$2b2obo4b2o$3b2o6bo$4bo3$2b2o$b4o$2ob2o$b2o!")],
377 [[18,18], ...parseRLE("bo2bo$o$o3bo$4o2$5b2o$6bobo$2b2o4bo$bo$2o2b2o$b2o2bo$2b3o2$2b3o$b2o2b" +
378 "o$2o2b2o$bo$2b2o4bo$6bobo$5b2o2$4o$o3bo$o$bo2bo!")],
379 [[56,32], ...parseRLE("bo2bo$o$o3bo$4o9b2o$6b3o5b2o$6b2ob2o6b3o$6b3o5b2o$4o9b2o$o3bo$o$bo2bo!")],
380];
381
382presets['gosper-gun'] = [[[1,1],[24,0],[22,1],[24,1],[12,2],[13,2],[20,2],[21,2],[34,2],[35,2],[11,3],[15,3],[20,3],[21,3],
383 [21,3],[34,3],[35,3],[0,4],[1,4],[10,4],[16,4],[20,4],[21,4],[0,5],[1,5],[1,5],[10,5],[14,5],[16,5],[17,5],[22,5],[24,5],
384 [10,6],[16,6],[24,6],[11,7],[15,7],[12,8],[13,8]],[[8,59],...e1ul]];
385
386presets['simkin-gun'] = [[[15,24],[0,0],[1,0],[7,0],[8,0],[0,1],[1,1],[7,1],[8,1],[4,3],[5,3],[4,4],[5,4],[22,9],[23,9],
387 [25,9],[26,9],[21,10],[27,10],[21,11],[28,11],[31,11],[32,11],[21,12],[22,12],[23,12],[27,12],[31,12],[32,12],[26,13]],
388 [[55+20,61+20],...e1ul],[[8-24,0-24],...e1dr]]
389
390presets['r-pentomino'] = [[[0,0],[1,1],[2,2],[3,2],[2,1],[2,0]]];
391presets['diehard'] = [[[28,28],[7, 1],[1, 2],[2, 2],[2, 3],[6, 3],[7, 3],[8, 3]]];
392presets['acorn'] = [[[28,28],[1,3],[2,3],[2,1],[4,2],[5,3],[6,3],[7,3]]];
393presets['inf1'] = [[[28,28],[7,1],[5,2],[7,2],[8,2],[5,3],[7,3],[5,4],[3,5],[3,6],[1,6]]];
394presets['inf2'] = [[[28,28],[1,1],[2,1],[3,1],[5,1],[1,2],[4,3],[5,3],[2,4],[3,4],[5,4],[1,5],[3,5],[5,5]]];
395presets['86P6H3V0'] = [[[16,53],[5,0],[6,0],[7,0],[25,0],[26,0],[27,0],[4,1],[8,1],[12,1],[20,1],[24,1],[28,1],[3,2],[4,2],
396 [9,2],[11,2],[12,2],[13,2],[19,2],[20,2],[21,2],[23,2],[28,2],[29,2],[2,3],[4,3],[6,3],[7,3],[9,3],[13,3],[14,3],[18,3],
397 [19,3],[23,3],[25,3],[26,3],[28,3],[30,3],[1,4],[2,4],[4,4],[9,4],[11,4],[14,4],[15,4],[17,4],[18,4],[21,4],[23,4],
398 [28,4],[30,4],[31,4],[0,5],[5,5],[9,5],[14,5],[18,5],[23,5],[27,5],[32,5],[14,6],[15,6],[16,6],[17,6],[18,6],[0,7],[1,7],
399 [11,7],[12,7],[14,7],[18,7],[20,7],[21,7],[31,7],[32,7],[11,8],[14,8],[18,8],[21,8],[10,9],[14,9],[16,9],[18,9],[22,9],
400 [11,10],[21,10]]];
401presets['74P8H2V0'] = [[[24,43],[5,0],[7,0],[9,0],[4,1],[5,1],[6,1],[7,1],[8,1],[9,1],[10,1],[4,2],[10,2],[5,3],[9,3],[5,4],
402 [9,4],[3,5],[4,5],[10,5],[11,5],[5,6],[9,6],[2,7],[4,7],[6,7],[8,7],[10,7],[12,7],[4,8],[6,8],[8,8],[10,8],[1,9],[2,9],
403 [4,9],[6,9],[8,9],[10,9],[12,9],[13,9],[1,10],[5,10],[6,10],[8,10],[9,10],[13,10],[0,12],[1,12],[5,12],[9,12],[13,12],
404 [14,12],[5,13],[9,13],[6,14],[7,14],[8,14],[4,16],[9,16],[10,16],[3,17],[5,17],[8,17],[9,17],[6,18],[9,18],[11,18],[3,19],
405 [6,19],[9,19],[11,19],[4,20],[6,20],[10,20]]];
406
407// https://conwaylife.com/wiki/58P5H1V1
408presets['58P5H1V1'] = [[[40,40], ...parseRLE("20b2ob$20b2ob$19bo2bo$16b2obo2bo$22bo$14b2o3bo2bo$14b2o5bob$15bob5ob$" +
409 "16bo6b3$13b3o7b$13bo9b$11b2o10b$5b2o4bo11b$5b3o3bo11b$3bo4bo14b$3bo3bo" +
410 "15b$7bo15b$2b2obobo15b$2o5bo15b$2o4b2o15b$2b4o!")]];
411
412presets['295P5H1V1'] = [[[10,10], ...parseRLE("13b2o37b$5b2o4b2obobo35b$4b3o4b4o37b$3b2o6b2o5bo33b$2b2o2b2o3bo2bo2bo" +
413 "34b$b2o5bo7bo2b2o31b$b2obo3b4o40b$4bo3b2o2b2obo36b$5b3o4bobo37b$6b2o3b" +
414 "2o2bo36b$6bo5bo39b$b4obo2bo2bo3bo35b$b3o3b5o2b7obo29b$obo4bo10bo2b2o" +
415 "29b$3obo3bo3bo5b3o31b$7bobo2bo7b2o30b$bo3bo5b2o8b2o2bobo24b$4bo7bo8b3o" +
416 "bob3o22b$3bo8b3o6bo4bo25b$5bo6bobo5bobo29b$5bo6bob2o3bo4bo27b$13bob4o" +
417 "3bo5bo2bo20b$12b2o2b2obobo3bob3o22b$17bo6bo2b3o3b3o16b$20bo2bo6b2o20b$" +
418 "16b2o4bo2bo10b2o14b$18bo13bo3bo15b$16b2o4b2o8bo19b$17bo3b3o8bobobobo" +
419 "13b$17bo4b2o8bo5b2o12b$24bo8bo2b3o13b$21bo2bo8bo8bo9b$26b4o8b2o3bo8b$" +
420 "23bo6b2o6b2o3bo8b$23bo4bo12bo10b$23bo15bo12b$25b2obobo7bo2bo10b$25bo4b" +
421 "o9b3o9b$28b3ob2o2bo3bo3bob2o4b$29bo2b2obo5bo3bo2bo3b$37b2o2bo3bo6b$34b" +
422 "ob2ob2obo2b2o3bob$31bo5bo3bo7bobo$32b2o12b2o3bo$38bo7b2o4b$39b3o3b2o2b" +
423 "o2b$38bo2bob3o6b$38bo4b2o7b$39bo12b$42bo2bo6b$41bo10b$42b2o!")]];
424
425presets['71P16H8V0'] = [[[0, 23], ...parseRLE("22b5o$21bo4bo$26bo$16b2o3bo3bo$15bo2bo4bo$bo6bo3b2o2b2o$ob2o5bo13b5o$o" +
426 "4bo3bo4bo6bo5bo$bo3bo4bo16bo$3bo12b2o3bo4bo$4b3o7bo7bo$4bo11bo2bo3bo2b" +
427 "o$5b2o10bobo7bo$9b2o12bo3bo$7bo4bo11b4o$13bo$7bo5bo$8b6o!")]];
428
429// https://conwaylife.com/forums/viewtopic.php?&p=156408#p156408 p89
430presets['hassler-unamed (>:3)'] = [[[12,12], ...parseRLE("14bo2$14b3o$14bo2bo$14bo2bo12bo$28b3o$17b2o8bo$18bo8b2o5b2o$34bo$" +
431 "25bo6bobo$26bo5b2o$24bo$23b2ob2o$14b3o3b2ob3o2bo$ob3o8bobo6b3o3bo" +
432 "$2bo10b2o8bob3o$2bo10bo10b3o$3b2obo$6b2o2$13bo$13bo11b2o$14bo11bo" +
433 "$12b4o7b3o$11b4obo6bo$9bo3bob2o4bobo$10bobo2b2o4b2o$6b2o4bo2bo$5b" +
434 "obo5b2o$5bo$4b2o2$9b2o$10bo$7b3o$7bo!")]];
435
436presets['p149 oscillator'] = [[[3,5], ...parseRLE("35bo$14b2o17b3o$15bo16bo$15bobo3bo10b2o$16b2o2bobo$6b2o13bo$6bobo2b2o" +
437 "$3bo4bo3bo16bo$3b5ob3o16bobo$7bobo6bo12b2o$3b3obobo5bobo$2bo2bob2o6b2o" +
438 "$2b2o31b2o$35bo$15b3o18b3o$3b2o10bo22bo$3bo12bo$2obo29b2o$o2bob2o26b2o" +
439 "$2b2obo$3bobo$2bo2b2o$3b2o2bo$4bob2o$4bobo$3b2obobo30b2o$7b2o30b2o2$16b" +
440 "2o30b2o$16b2o30bobob2o$50bobo$49b2obo$49bo2b2o$50b2o2bo$51bobo$51bob2o" +
441 "$22b2o26b2obo2bo$22b2o29bob2o$40bo12bo$18bo22bo10b2o$18b3o18b3o$21bo$" +
442 "20b2o31b2o$40b2o6b2obo2bo$39bobo5bobob3o$26b2o12bo6bobo$26bobo16b3ob5o" +
443 "$27bo16bo3bo4bo$44b2o2bobo$35bo13b2o$34bobo2b2o$23b2o10bo3bobo$24bo16b" +
444 "o$21b3o17b2o$21bo!")]];
445
446
447function loadCoos(sim, arr) {
448 let o = arr[0];
449 for (let i=1; i<arr.length; i++) {
450 sim.setCell(arr[i][0] + o[0], arr[i][1] + o[1], 1);
451 };
452};
453
454
455function loadPreset(sim, kind) {
456 sim.reset();
457 for (let j=0; j<presets[kind].length; j++) {
458 loadCoos(sim, presets[kind][j]);
459 };
460 if (states.running === 0) {
461 simRender();
462 };
463};
464
465
466function appInit() {
467 inputProxy = new cInputProxy();
468 sim = new ParticleSim(64, 64);
469 timer = new cTimer();
470 recorder = new CanvasRecorder(canvas);
471
472 rulesSet['B3/S23'](sim);
473
474 inputProxy.register('clearSim').addEventListener('click', (e)=>{
475 sim.reset()
476 });
477 inputProxy.register('stepSim').addEventListener('click', (e)=>{
478 simStep()
479 if (states.running === 1) {
480 states.running = 0;
481 inputProxy.target.pauseSim.value = 'resume';
482 };
483 });
484 inputProxy.register('pauseSim').addEventListener('click', (e)=>{
485 states.running ^= 1;
486 if (states.running === 1) {
487 e.target.value = 'pause';
488 loop();
489 } else {
490 e.target.value = 'resume';
491 };
492 });
493 inputProxy.register('speedSim', 100, parseFloat);
494 inputProxy.register('presetSelect');
495 while (inputProxy.target.presetSelect.firstChild) {
496 inputProxy.target.presetSelect.removeChild(inputProxy.target.presetSelect.lastChild);
497 };
498 for (let k in presets) {
499 if (k === 'placeholder') continue;
500 let opt = document.createElement('option');
501 opt.value = k;
502 opt.innerHTML = k;
503 inputProxy.target.presetSelect.append(opt);
504 };
505 inputProxy.register('simRecord').addEventListener('click', (e)=>{
506 if (states.recording === 1) {
507 states.recording = 0;
508 e.target.value = 'record'
509 recorder.stop();
510 } else {
511 recorder.start(()=>{
512 states.recording = 0;
513 e.target.value = 'record'
514 }, 25)
515 states.recording = 1;
516 e.target.value = 'stop'
517 }
518 });
519 inputProxy.register('rleOffsetX', 0, parseInt);
520 inputProxy.register('rleOffsetY', 0, parseInt);
521 inputProxy.register('rleInput');
522 inputProxy.register('rleLoad').addEventListener('click', (e)=>{
523 if (validateRLE(inputProxy.target.rleInput.value)) {
524 sim.reset();
525 loadCoos(sim, [[inputProxy.values.rleOffsetX,inputProxy.values.rleOffsetY],
526 ...parseRLE(inputProxy.target.rleInput.value)])
527 simRender();
528 }
529 });
530 inputProxy.register('presetLoad').addEventListener('click', (e)=>{
531 loadPreset(sim, inputProxy.values.presetSelect);
532 });
533 inputProxy.target.presetSelect.value = inputProxy.target.presetSelect.firstChild.value;
534 inputProxy.values.presetSelect = inputProxy.target.presetSelect.firstChild.value;
535 inputProxy.target.rleInput.value = "15bo$13b2o$13b2o$16bo$13b2obo$14bo$14bobo$15b2o$15bo$19bo$9b3o6b2o$10b2o5b3o$" +
536 "11bo$3b2ob2o17bob2o$o6b2o14b3ob2o$b2ob3o14b2o6bo$b2obo17b2ob2o$18bo$10b3o5b2o" +
537 "$10b2o6b3o$10bo$14bo$13b2o$13bobo$15bo$13bob2o$13bo$15b2o$15b2o$14bo!"
538
539};
540
541
542function simRender() {
543 ctx.clearRect(0, 0, canvas.width, canvas.height);
544 sim.render(ctx, canvas.width, canvas.height);
545};
546
547
548function simStep() {
549 sim.update();
550 simRender();
551};
552
553
554function loop() {
555 timer.update();
556 timer.ontimed(20 / (inputProxy.values.speedSim / 200), simStep);
557 if (states.running === 1) {
558 window.requestAnimationFrame(loop);
559 };
560};
561
562appInit()
563loadPreset(sim, "glider1")
564loop()
565
566};
Built with Hugo | previoip (c) 2025