git @ Cat's Eye Technologies Schroedingers-Game-of-Life / 302af88
Initial import of files for Schroedinger's Game of Life. Chris Pressey 10 years ago
23 changed file(s) with 2002 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 Schrödinger's Game of Life
1 ==========================
2
3 Abstract
4 --------
5
6 John Conway's Game of Life cellular automaton meets Schrödinger's Cat:
7 cells may be **Alive**, or **Dead**, or **Possibly-Alive-Possibly-Dead**.
8
9 Description
10 -----------
11
12 Recall that in the Game of Life, cells may be **Alive** (which we will depict
13 as `#`) or **Dead** (which we will depict as `.`).
14
15 A cell that is **Alive** remains **Alive** if 2 or 3 of its neighbours are
16 **Alive**; otherwise it becomes **Dead**.
17
18 A cell that is **Dead** becomes **Alive** if exactly 2 of its neighbours are
19 **Alive**, otherwise it remains **Dead**.
20
21 ("Neighbours" in this sense means a Moore neighbourhood: the 8 surrounding
22 cells, both cardinals and diagonals.)
23
24 To this, we add a third cell type, **Possibly-Alive-Possibly-Dead**. (For
25 brevity, we will also call this type **Cat**.) We will depict it as `?`.
26
27 If a cell is **Cat** we must consider both possibilities; what would its next
28 state be if it was **Alive**, and what would its next state be if it was
29 **Dead**? If the answer to both of these questions is the same, then that is
30 the outcome for that cell on the next state; but cells which have different
31 outcomes under those two conditions themselves become **Cat**.
32
33 In fact, this is just a sort of non-deterministic-ification operation
34 applied to the base cellular automaton, and such an operation could probably
35 be applied to any cellular automaton.
36
37 We could certainly implement this in a brute-force, check-all-the-possibilities
38 fashion. But observe that in a CA with only two states, adding nondeterminism
39 like this is not any different from simply adding a third state, and figuring
40 out the rules for the new set of states. It should be entirely possible to
41 derive a sort of "closed form" of the nondeterministic behaviour, in terms of
42 deterministic rules — not unlike the [powerset construction][] which turns an
43 NFA into a DFA — and that is what we shall attempt here.
44
45 I think it goes like this: instead of considering only the number of **Alive**
46 neighbours, we must now consider two counts: the minimum number of **Alive**
47 neighbours, and the maximum number of **Alive** neighbours. (Call these
48 *min-alive* and *max-alive* for brevity.)
49
50 * Each neighbouring `#` cell counts as min-alive 1 and max-alive 1.
51 * Each neighbouring `.` cell counts as min-alive 0 and max-alive 0.
52 * Each neighbouring `?` cell counts as min-alive 0 and max-alive 1.
53
54 And for each cell, we simply sum these up to achieve these two counts.
55
56 As an example, the middle cell in the following diagram has min-alive 1 and
57 max-alive 6:
58
59 ???
60 ?x?
61 #..
62
63 Now, using these two counts, the new rules are:
64
65 * A cell which is **Alive**:
66
67 * remains **Alive** if its min-alive is 2 or 3 and its max-alive is 2 or 3.
68 * becomes **Dead** if its min-alive is 4 or greater, or its max-alive is 1
69 or fewer.
70 * otherwise becomes **Cat**.
71
72 * A cell which is **Dead**:
73
74 * becomes **Alive** if its min-alive is 3 and its max-alive is 3.
75 * remains **Dead** if its min-alive is 4 or greater, or its max-alive is 2
76 or fewer.
77 * otherwise becomes **Cat**.
78
79 * A cell which is **Cat**:
80
81 * becomes **Alive** if its min-alive is 3 and its max-alive is 3.
82 * becomes **Dead** if its min-alive is 4 or greater, or its max-alive is 1
83 or fewer.
84 * otherwise remains **Cat**.
85
86 Now that we have stated the new rules, we see they are not particularly
87 difficult to implement, so, that is just what we have done. However,
88 this matter of there being two counts for each cell is a bit more than
89 [ALPACA][] can express, so the automaton has simply been implemented
90 from scratch in Python, in the file `script/slife` in this repository.
91
92 There is certainly *some* way to state the rules such that ALPACA could
93 accept them. However, I have not worked it out, and I believe there is a
94 good chance that such an "even more closed form" would only serve to
95 overcomplicate the description. I shall leave the deriving of it as an
96 exercise for the interested reader.
97
98 Anyway, now that we have an implementation, we can confirm that it meets
99 our expectations by giving it a few example configurations to test it.
100
101 Examples
102 --------
103
104 -> Tests for functionality "Evolve Schroedinger's Life for 5 steps"
105
106 -> Functionality "Evolve Schroedinger's Life for 5 steps"
107 -> is implemented by shell command "./script/slife %(test-body-file)"
108
109 When a playfield consists solely of `.` and `#` cells, this cellular automaton
110 has precisely the same behaviour as John Conway's Game of Life. For example,
111 here is a glider:
112
113 | ............
114 | ............
115 | ............
116 | .....##.....
117 | ....#.#.....
118 | ......#.....
119 | ............
120 | ............
121 = ............|............|............|............|............
122 = ............|............|............|............|............
123 = ............|............|......#.....|......##....|......##....
124 = .....##.....|.....###....|......##....|.....#.#....|.......##...
125 = ......##....|.......#....|.....#.#....|.......#....|......#.....
126 = .....#......|......#.....|............|............|............
127 = ............|............|............|............|............
128 = ............|............|............|............|............
129
130 But now we turn our attention to the newcomer, **Cat**. The first thing to
131 note is that in cases where, in Conway's Life, the result is the same
132 regardless of whether the cell is **Dead** or **Alive**, the result will
133 continue to be the same when the cell is **Cat**.
134
135 One such example is a lone cell, with no neighbours. Whether it be
136 **Dead** or **Alive**, on the next turn, it will always be **Dead**.
137
138 | .....
139 | .....
140 | ..?..
141 | .....
142 | .....
143 = .....|.....|.....|.....|.....
144 = .....|.....|.....|.....|.....
145 = .....|.....|.....|.....|.....
146 = .....|.....|.....|.....|.....
147 = .....|.....|.....|.....|.....
148
149 Another such example is the "self-healing block": if a 2x2 block is missing
150 a corner, *or* if that corner is present, the next configuration will always
151 be a full 2x2 block.
152
153 | ......
154 | ......
155 | ..##..
156 | ..#?..
157 | ......
158 | ......
159 = ......|......|......|......|......
160 = ......|......|......|......|......
161 = ..##..|..##..|..##..|..##..|..##..
162 = ..##..|..##..|..##..|..##..|..##..
163 = ......|......|......|......|......
164 = ......|......|......|......|......
165
166 This extends to a fuse that ends in a block: it survives having a **Cat** at
167 the end:
168
169 | .........
170 | ..##.....
171 | ..#.#....
172 | .....#...
173 | ......#..
174 | .......?.
175 | .........
176 = .........|.........|.........|.........|.........
177 = ..##.....|..##.....|..##.....|..##.....|..##.....
178 = ..#.#....|..#.#....|..#.?....|..#?.....|..##.....
179 = .....#...|.....?...|.........|.........|.........
180 = ......?..|.........|.........|.........|.........
181 = .........|.........|.........|.........|.........
182 = .........|.........|.........|.........|.........
183
184 Within this realm of predictability, some forms composed entirely of **Cat**s
185 behave remarkably similar to the same **Alive** forms. For example, the 2x2
186 block is stable:
187
188 | ......
189 | ......
190 | ..??..
191 | ..??..
192 | ......
193 | ......
194 = ......|......|......|......|......
195 = ......|......|......|......|......
196 = ..??..|..??..|..??..|..??..|..??..
197 = ..??..|..??..|..??..|..??..|..??..
198 = ......|......|......|......|......
199 = ......|......|......|......|......
200
201 And the blinker behaves conventionally:
202
203 | .......
204 | .......
205 | ...?...
206 | ...?...
207 | ...?...
208 | .......
209 | .......
210 = .......|.......|.......|.......|.......
211 = .......|.......|.......|.......|.......
212 = .......|...?...|.......|...?...|.......
213 = ..???..|...?...|..???..|...?...|..???..
214 = .......|...?...|.......|...?...|.......
215 = .......|.......|.......|.......|.......
216 = .......|.......|.......|.......|.......
217
218 In general, though, non-determinism is non-determinism. Adding `?`s to a
219 form adds uncertainty, and there are relatively few avenues in Life by which
220 uncertainty is reined in, as in the above examples. Thus, uncertainty tends
221 to propagate throughout the form, and, indeed, beyond.
222
223 Here is what happens if we try something which we are only *mostly* sure is a
224 glider.
225
226 | ............
227 | ............
228 | ............
229 | .....##.....
230 | ....#.?.....
231 | ......#.....
232 | ............
233 | ............
234 = ............|............|............|............|............
235 = ............|............|............|............|......?.....
236 = ............|............|......?.....|.....???....|.....???....
237 = .....#?.....|.....???....|.....???....|.....???....|....?????...
238 = ......#?....|.....???....|.....???....|....?????...|....?????...
239 = .....?......|......?.....|.....???....|.....???....|....?????...
240 = ............|............|............|......?.....|.....???....
241 = ............|............|............|............|............
242
243 In fact, this is not very different from a form which we are *completely*
244 unsure if it's a glider:
245
246 | ............
247 | ............
248 | ............
249 | .....??.....
250 | ....?.?.....
251 | ......?.....
252 | ............
253 | ............
254 = ............|............|............|............|............
255 = ............|............|............|............|......?.....
256 = ............|............|......?.....|.....???....|....????....
257 = .....??.....|.....???....|....????....|....????....|...??????...
258 = .....???....|....????....|....????....|...??????...|...??????...
259 = .....?......|.....??.....|....????....|....????....|...??????...
260 = ............|............|............|.....??.....|....????....
261 = ............|............|............|............|............
262
263 Obviously, if either of these were to continue to run for more generations,
264 the entire space would continue to be filled up with **Cat**s, unboundedly.
265
266 One way to think about this is that our probably-a-glider actually represents
267 two forms (one of which is a glider, the other isn't,) and our 5-**Cat**
268 could-be-a-glider actually represents 2^5 = 32 different forms in
269 nondeterministic superposition. In both cases, the **Cat**s that exist after
270 _n_ generations constitute a sort of "convex hull", or upper-bound
271 approximation, of where the "influence" of all these original forms could
272 possibly reach.
273
274 Discussion
275 ----------
276
277 Er, and that's it, really. I expect there are a few other forms which are
278 stable even though they're made of **Cat**s (an infinite barber pole probably
279 is.) But the mathematics of it stop here, and if that's all you're interested
280 in, you can stop reading. The rest of this document is purely opinion and
281 blather.
282
283 This cellular automaton is an original idea — I came up with it myself,
284 independently (the note "Design and implement a cellular automaton with a
285 non-deterministic state" has been sitting in my ideas file since at least 2009,
286 possibly 2007,) but I do not claim it is *novel* — in fact, I would be surprised
287 if it hasn't been looked into before. I would not be surprised if it was
288 mentioned in an academic paper I haven't read, possibly in an unpublished
289 internal report (the kind that universities sometimes collect), possibly
290 before I was even born. (I am mainly just making a distinction between
291 "original" and "novel" here because sometimes people confuse the two.)
292
293 Obviously, I'm taking a few liberties with the title. Erwin Schrödinger
294 did not invent this Game of Life. But that's not how it's supposed to be
295 read. As an _homage_ to Schrödinger, it would be even better if it was an
296 automaton that incorporated the [wave function][] somehow. I don't have any
297 good ideas on how to go about that, though, and the mathematics of it would
298 be a bit beyond me. (If you have any similar ideas though, I strongly
299 encourage you to try them out!)
300
301 Two points of trivia:
302
303 One, I understand that there's a historical-orthographic theory that the shape
304 of the glyph `?` comes from a depiction of a cat's rear end, with its tail in
305 the air. If this is true, it's not inappropriate for our use of it to depict
306 **Cat** here, then.
307
308 Two, a stalemate in Tic-Tac-Toe (a.k.a Noughts and Crosses) is sometimes called
309 a **Cat's Game**. Being neither a **Win** nor a **Loss**, it also seems
310 appropriate here.
311
312 Now, to regard those two things as anything but coincidences would be stretching
313 things. However, we may well ask why Schrödinger chose a cat for his thought
314 experiment in the first place, when a rabbit would have done just as well.
315 Or a dog. Or a [slime mold][]. Or... but wait, I'm getting ahead of myself.
316
317 I was going to write a whole long screed about Schrödinger's Cat here, but
318 that sort of thing gets tiresome. So maybe I'll just leave you with a bit
319 of terrible doggerel I wrote back in the mid-90's:
320
321 > We've all heard of Schrödinger's Cat,
322 > That famous thought 'sperimentation.
323 > But you don't suppose it's possible that
324 > The *cat* made an observation?
325
326 And here is "Schrödinger's Cat Extended Dance Mix":
327
328 Carrying a live cat and a quantum boobytrap, you walk into a room, in which is
329 a fellow experimenter and a table containing two open, empty boxes.
330
331 You give the items to your colleague, step out of the room, and close the
332 door.
333
334 A minute later you open it again and walk back in. The boxes are closed
335 and there is no sign of the cat.
336
337 You check the environment carefully to ensure there are no hidden compartments
338 and that your colleague has not eaten the cat.
339
340 You open one of the boxes.
341
342 What are the possible outcomes?
343
344 Which of these outcomes involve the collapsing of a quantum superposition,
345 and which do not?
346
347 Why does the Copenhagen Interpretation tell us that predicting some of these
348 outcomes is mere odds-making, while other outcomes involve such exotic
349 concepts as being both dead and alive at the same time?
350
351 Is the wave function not just another mathematical model, like the parabola of
352 Newtonian physics or the curved spacetime of Einsteinian relativity?
353
354 Why should we try to make our notions of ontology and epistomology bend over
355 backwards for the sake of a mathematical model?
356
357 In conclusion, it's a [safe bet][] that [Dr. Quantum][] is [bunko][].
358
359 Happy "We need to talk"!
360 Chris Pressey, Cat's Eye Technologies
361 February 7, 2015
362 Reading, England, UK
363
364 [powerset construction]: https://en.wikipedia.org/wiki/Powerset_construction
365 [ALPACA]: http://catseye.tc/node/ALPACA
366 [wave function]: http://en.wikipedia.org/wiki/Wave_function
367 [slime mold]: http://catseye.tc/node/Jaccia
368 [safe bet]: https://en.wikipedia.org/wiki/Probability_theory
369 [Dr. Quantum]: http://www.fredalanwolf.com/
370 [bunko]: http://rationalwiki.org/wiki/Quantum_woo
0 .......
1 .......
2 .......
3 ..#?#..
4 .......
5 .......
6 .......
0 ............................
1 .##......................##.
2 .##...#.................#.#.
3 .......#...............#....
4 .....###..............?.....
5 ............................
6 .........................??.
7 .........................??.
8 ............................
9 .............?..............
10 .##..........?...........##.
11 .??..........?...........?#.
12 ............................
0 ......
1 ......
2 ..??..
3 ..??..
4 ......
5 ......
0 .......
1 .......
2 ...?...
3 ...?...
4 ...?...
5 .......
6 .......
0 ............
1 ............
2 ............
3 .....##.....
4 ....#.#.....
5 ......#.....
6 ............
7 ............
0 ........
1 .##.....
2 .#.#....
3 ....#...
4 .....#..
5 ......?.
6 ........
0 ............
1 ............
2 ............
3 .....##.....
4 ....#.?.....
5 ......#.....
6 ............
7 ............
0 ......
1 ......
2 ..??..
3 ..##..
4 ......
5 ......
0 .....
1 .....
2 ..?..
3 .....
4 .....
0 ............
1 ............
2 ............
3 .....??.....
4 ....?.?.....
5 ......?.....
6 ............
7 ............
0 ......
1 ......
2 ..##..
3 ..#?..
4 ......
5 ......
0 <!DOCTYPE html>
1 <head>
2 <meta charset="utf-8">
3 <title>Schrödinger's Game of Life</title>
4 </head>
5 <body>
6
7 <h1>Schrödinger's Game of Life</h1>
8
9 <div id="container"></div>
10
11 </body>
12 <script src="../src/slife.js"></script>
13 <script>
14 launch('../src/yoob/', 'container');
15 </script>
0 "use strict";
1
2 function launch(prefix, container, config) {
3 config = config || {};
4 if (typeof(container) === 'string') {
5 container = document.getElementById(container);
6 }
7 var deps = [
8 "controller.js",
9 "playfield.js",
10 "playfield-html-view.js",
11 "element-factory.js",
12 "preset-manager.js",
13 "source-manager.js"
14 ];
15 var loaded = 0;
16 for (var i = 0; i < deps.length; i++) {
17 var elem = document.createElement('script');
18 elem.src = prefix + deps[i];
19 elem.onload = function() {
20 if (++loaded != deps.length) return;
21
22 var sourceRoot = config.sourceRoot || '../../../eg/';
23
24 /* --- state --- */
25
26 var pf;
27
28 /* --- state animation display --- */
29
30 var controlPanel = yoob.makeDiv(container);
31 controlPanel.style.textAlign = 'left';
32 var viewPort = yoob.makeDiv(container);
33 viewPort.style.textAlign = 'left';
34 var stateDisplay = yoob.makePre(viewPort);
35 var editor = yoob.makeTextArea(container, 60, 30);
36
37 var v = (new yoob.PlayfieldHTMLView).init(pf, stateDisplay);
38 v.render = function(state) {
39 return dumpMapper(state);
40 };
41
42 /* --- controller --- */
43
44 var c = (new yoob.Controller).init({
45 'panelContainer': controlPanel,
46 'reset': function(text) {
47 pf = new yoob.Playfield();
48 pf.setDefault('Dead');
49 pf.load(0, 0, text, loadMapper);
50 v.setPlayfield(pf);
51 v.draw();
52 },
53 'step': function() {
54 var newPf = new yoob.Playfield();
55 newPf.setDefault('Dead');
56 evolvePlayfield(pf, newPf);
57 pf = newPf;
58 v.setPlayfield(pf);
59 v.draw();
60 }
61 });
62
63 /* --- source manager --- */
64
65 var sm = (new yoob.SourceManager()).init({
66 'editor': editor,
67 'hideDuringEdit': [viewPort],
68 'disableDuringEdit': [c.panel],
69 'storageKey': 'slife.js',
70 'panelContainer': controlPanel,
71 'onDone': function() {
72 c.performReset(this.getEditorText());
73 }
74 });
75
76 /* --- presets --- */
77
78 var presetSelect = yoob.makeSelect(c.panel, "Preset:", []);
79
80 var p = new yoob.PresetManager();
81 p.init({
82 'selectElem': presetSelect,
83 'setPreset': function(n) {
84 c.clickStop(); // in case it is currently running
85 sm.loadSourceFromURL(sourceRoot + n);
86 sm.onDone();
87 }
88 });
89 p.add('alive-cat-alive.sgol');
90 p.add('big-demo.sgol');
91 p.add('block-of-cats.sgol');
92 p.add('cat-blinker.sgol');
93 p.add('definitely-glider.sgol');
94 p.add('fuse-with-cat.sgol');
95 p.add('glider-with-1-cat.sgol');
96 p.add('half-cat-block.sgol');
97 p.add('lone-cell.sgol');
98 p.add('maybe-glider.sgol');
99 p.add('self-healing-block.sgol');
100
101 p.select('big-demo.sgol');
102 };
103 document.body.appendChild(elem);
104 }
105 }
106
107 /*
108 * Schroedinger's Game of Life, implemented in Javascript
109 */
110
111 function getMinMaxAlive(pf, x, y) {
112 var minAlive = 0;
113 var maxAlive = 0;
114 for (var dx = -1; dx <= 1; dx++) {
115 for (var dy = -1; dy <= 1; dy++) {
116 if (dx === 0 && dy === 0) continue;
117 var nx = x + dx;
118 var ny = y + dy;
119 var st = pf.get(nx, ny);
120 if (st === 'Alive') {
121 minAlive += 1;
122 maxAlive += 1;
123 } else if (st === 'Dead') {
124 minAlive += 0;
125 maxAlive += 0;
126 } else if (st === 'Cat') {
127 minAlive += 0;
128 maxAlive += 1;
129 }
130 }
131 }
132 return [minAlive, maxAlive];
133 }
134
135 function evolvePlayfield(pf, newPf) {
136 pf.map(newPf, evalState, -1, -1, 1, 1);
137 }
138
139 function loadMapper(c) {
140 if (c === '.') return 'Dead';
141 if (c === '#') return 'Alive';
142 if (c === '?') return 'Cat';
143 };
144
145 function dumpMapper(s) {
146 if (s === 'Dead') return '.';
147 if (s === 'Alive') return '#';
148 if (s === 'Cat') return '?';
149 };
150
151 function evalAlive(pf, x, y) {
152 var pair = getMinMaxAlive(pf, x, y);
153 if ((pair[0] === 2 || pair[0] === 3) && (pair[1] === 2 || pair[1] === 3)) {
154 return 'Alive';
155 } else if (pair[0] >= 4 || pair[1] <= 1) {
156 return 'Dead';
157 } else return 'Cat';
158 }
159
160 function evalDead(pf, x, y) {
161 var pair = getMinMaxAlive(pf, x, y);
162 if (pair[0] === 3 && pair[1] === 3) {
163 return 'Alive';
164 } else if (pair[0] >= 4 || pair[1] <= 2) {
165 return 'Dead';
166 } else return 'Cat';
167 }
168
169 function evalCat(pf, x, y) {
170 var pair = getMinMaxAlive(pf, x, y);
171 if (pair[0] === 3 && pair[1] === 3) {
172 return 'Alive';
173 } else if (pair[0] >= 4 || pair[1] <= 1) {
174 return 'Dead';
175 } else return 'Cat';
176 }
177
178 function evalState(pf, x, y) {
179 var stateId = pf.get(x, y);
180 if (stateId === 'Alive') {
181 return evalAlive(pf, x, y);
182 } else if (stateId === 'Dead') {
183 return evalDead(pf, x, y);
184 } else {
185 return evalCat(pf, x, y);
186 }
187 }
0 /*
1 * This file is part of yoob.js version 0.8
2 * Available from https://github.com/catseye/yoob.js/
3 * This file is in the public domain. See http://unlicense.org/ for details.
4 */
5 if (window.yoob === undefined) yoob = {};
6
7 /*
8 * A controller for executing(/animating/evolving) states such as esolang
9 * program states or cellular automaton configurations. For the sake of
10 * convenience, we will refer to this as the _program state_, even though
11 * it is of course highly adaptable and might not represent a "program".
12 *
13 * Like most yoob objects, it is initialized after creation by calling the
14 * method `init` with a configuration object. If a DOM element is passed
15 * for `panelContainer` in the configuration, a panel containing a number
16 * of UI controls will be created and appended to that container. These
17 * are:
18 *
19 * - a set of buttons which control the evolution of the state:
20 * - start
21 * - stop
22 * - step
23 * - load
24 * - reset
25 *
26 * - a slider control which adjusts the speed of program state evolution.
27 *
28 * To use a Controller, create a subclass of yoob.Controller and override
29 * the following methods:
30 * - make it evolve the state by one tick in the step() method
31 * - make it load the initial state from a string in the reset(s) method
32 *
33 * In these methods, you will need to store the state (in whatever
34 * representation you find convenient for processing and for depicting on
35 * the `display` in some fashion) somehow. You may store it in a closed-over
36 * private variable, or in an attribute on your controller object.
37 *
38 * If you store in in an attribute on your controller object, you should use
39 * the `.programState` attribute; it is reserved for this purpose.
40 *
41 * You should *not* store it in the `.state` attribute, as a yoob.Controller
42 * uses this to track its own state (yes, it has its own state independent of
43 * the program state.)
44 *
45 * Some theory of operation:
46 *
47 * For every action 'foo', three methods are exposed on the yoob.Controller
48 * object:
49 *
50 * - clickFoo
51 *
52 * Called when the button associated button is clicked.
53 * Client code may call this method to simulate the button having been
54 * clicked, including respecting and changing the state of the buttons panel.
55 *
56 * - performFoo
57 *
58 * Called by clickFoo to request the 'foo' action be performed.
59 * Responsible also for any Controller-related housekeeping involved with
60 * the 'foo' action. Client code may call this method when it wants the
61 * controller to perform this action without respecting or changing the
62 * state of the button panel.
63 *
64 * - foo
65 *
66 * Overridden (if necessary) by a subclass, or supplied by an instantiator,
67 * of yoob.Controller to implement some action. In particular, 'step' needs
68 * to be implemented this way. Client code should not call these methods
69 * directly.
70 *
71 * The clickFoo methods take one argument, an event structure. None of the
72 * other functions take an argument, with the exception of performReset() and
73 * reset(), which take a single argument, the text-encoded state to reset to.
74 */
75 yoob.Controller = function() {
76 var STOPPED = 0; // the program has terminated (itself)
77 var PAUSED = 1; // the program is ready to step/run (stopped by user)
78 var RUNNING = 2; // the program is running
79 var BLOCKED = 3; // the program is waiting for more input
80
81 /*
82 * panelContainer: an element into which to add the created button panel
83 * (if you do not give this, no panel will be created. You're on your own.)
84 * step: if given, if a function, it becomes the step() method on this
85 * reset: if given, if a function, it becomes the reset() method on this
86 */
87 this.init = function(cfg) {
88 this.delay = 100;
89 this.state = STOPPED;
90 this.controls = {};
91 this.resetState = undefined;
92 if (cfg.panelContainer) {
93 this.panel = this.makePanel();
94 cfg.panelContainer.appendChild(this.panel);
95 }
96 if (cfg.step) {
97 this.step = cfg.step;
98 }
99 if (cfg.reset) {
100 this.reset = cfg.reset;
101 }
102 return this;
103 };
104
105 /******************
106 * UI
107 */
108 this.makePanel = function() {
109 var panel = document.createElement('div');
110 var $this = this;
111
112 var makeEventHandler = function(control, upperAction) {
113 return function(e) {
114 $this['click' + upperAction](control);
115 };
116 };
117
118 var makeButton = function(action) {
119 var button = document.createElement('button');
120 var upperAction = action.charAt(0).toUpperCase() + action.slice(1);
121 button.innerHTML = upperAction;
122 button.style.width = "5em";
123 panel.appendChild(button);
124 button.onclick = makeEventHandler(button, upperAction);
125 $this.controls[action] = button;
126 return button;
127 };
128
129 var keys = ["start", "stop", "step", "reset"];
130 for (var i = 0; i < keys.length; i++) {
131 makeButton(keys[i]);
132 }
133
134 var slider = document.createElement('input');
135 slider.type = "range";
136 slider.min = 0;
137 slider.max = 200;
138 slider.value = 100;
139 slider.onchange = function(e) {
140 $this.setDelayFrom(slider);
141 if ($this.intervalId !== undefined) {
142 $this.stop();
143 $this.start();
144 }
145 };
146
147 panel.appendChild(slider);
148 $this.controls.speed = slider;
149
150 return panel;
151 };
152
153 this.connectInput = function(elem) {
154 this.input = elem;
155 this.input.onchange = function(e) {
156 if (this.value.length > 0) {
157 // weird, where is this from?
158 $this.unblock();
159 }
160 }
161 };
162
163 /*
164 * Override this to change how the delay is acquired from the 'speed'
165 * control.
166 */
167 this.setDelayFrom = function(elem) {
168 this.delay = elem.max - elem.value; // parseInt(elem.value, 10)
169 };
170
171 /******************
172 * action: Step
173 */
174 this.clickStep = function(e) {
175 if (this.state === STOPPED) return;
176 this.clickStop();
177 this.state = PAUSED;
178 this.performStep();
179 };
180
181 this.performStep = function() {
182 var code = this.step();
183 if (code === 'stop') {
184 this.clickStop();
185 this.state = STOPPED;
186 } else if (code === 'block') {
187 this.state = BLOCKED;
188 }
189 };
190
191 /*
192 * Override this and make it evolve the program state by one tick.
193 * The method may also return a control code string:
194 *
195 * - `stop` to indicate that the program has terminated.
196 * - `block` to indicate that the program is waiting for more input.
197 */
198 this.step = function() {
199 alert("step() NotImplementedError");
200 };
201
202 /******************
203 * action: Start
204 */
205 this.clickStart = function(e) {
206 this.performStart();
207 if (this.controls.start) this.controls.start.disabled = true;
208 if (this.controls.step) this.controls.step.disabled = false;
209 if (this.controls.stop) this.controls.stop.disabled = false;
210 };
211
212 this.performStart = function() {
213 this.start();
214 };
215
216 this.start = function() {
217 if (this.intervalId !== undefined)
218 return;
219 this.performStep();
220 var $this = this;
221 this.intervalId = setInterval(function() {
222 $this.performStep();
223 }, this.delay);
224 this.state = RUNNING;
225 };
226
227 /******************
228 * action: Stop
229 */
230 this.clickStop = function(e) {
231 this.performStop();
232 if (this.controls.start) this.controls.start.disabled = false;
233 if (this.controls.step) this.controls.step.disabled = false;
234 if (this.controls.stop) this.controls.stop.disabled = true;
235 };
236
237 this.performStop = function() {
238 this.stop();
239 this.state = PAUSED;
240 };
241
242 this.stop = function() {
243 if (this.intervalId === undefined)
244 return;
245 clearInterval(this.intervalId);
246 this.intervalId = undefined;
247 };
248
249 /******************
250 * action: Reset
251 */
252 this.clickReset = function(e) {
253 this.clickStop();
254 this.performReset();
255 if (this.controls.start) this.controls.start.disabled = false;
256 if (this.controls.step) this.controls.step.disabled = false;
257 if (this.controls.stop) this.controls.stop.disabled = true;
258 };
259
260 this.performReset = function(state) {
261 if (state !== undefined) {
262 this.resetState = state;
263 }
264 this.reset(this.resetState);
265 };
266
267 this.reset = function(state) {
268 alert("reset() NotImplementedError");
269 };
270 };
0 /*
1 * This file is part of yoob.js version 0.8
2 * Available from https://github.com/catseye/yoob.js/
3 * This file is in the public domain. See http://unlicense.org/ for details.
4 */
5 if (window.yoob === undefined) yoob = {};
6
7 /*
8 * Functions for creating elements.
9 */
10
11 yoob.makeCanvas = function(container, width, height) {
12 var canvas = document.createElement('canvas');
13 if (width) {
14 canvas.width = width;
15 }
16 if (height) {
17 canvas.height = height;
18 }
19 container.appendChild(canvas);
20 return canvas;
21 };
22
23 yoob.makeButton = function(container, labelText, fun) {
24 var button = document.createElement('button');
25 button.innerHTML = labelText;
26 container.appendChild(button);
27 if (fun) {
28 button.onclick = fun;
29 }
30 return button;
31 };
32
33 yoob.checkBoxNumber = 0;
34 yoob.makeCheckbox = function(container, checked, labelText, fun) {
35 var checkbox = document.createElement('input');
36 checkbox.type = "checkbox";
37 checkbox.id = 'cfzzzb_' + yoob.checkBoxNumber;
38 checkbox.checked = checked;
39 var label = document.createElement('label');
40 label.htmlFor = 'cfzzzb_' + yoob.checkBoxNumber;
41 yoob.checkBoxNumber += 1;
42 label.appendChild(document.createTextNode(labelText));
43
44 container.appendChild(checkbox);
45 container.appendChild(label);
46
47 if (fun) {
48 checkbox.onchange = function(e) {
49 fun(checkbox.checked);
50 };
51 }
52 return checkbox;
53 };
54
55 yoob.makeTextInput = function(container, size, value) {
56 var input = document.createElement('input');
57 input.size = "" + (size || 12);
58 input.value = value || "";
59 container.appendChild(input);
60 return input;
61 };
62
63 yoob.makeSlider = function(container, min, max, value, fun) {
64 var slider = document.createElement('input');
65 slider.type = "range";
66 slider.min = min;
67 slider.max = max;
68 slider.value = value || 0;
69 if (fun) {
70 slider.onchange = function(e) {
71 fun(parseInt(slider.value, 10));
72 };
73 }
74 container.appendChild(slider);
75 return slider;
76 };
77
78 yoob.makeParagraph = function(container, innerHTML) {
79 var p = document.createElement('p');
80 p.innerHTML = innerHTML || '';
81 container.appendChild(p);
82 return p;
83 };
84
85 yoob.makeSpan = function(container, innerHTML) {
86 var span = document.createElement('span');
87 span.innerHTML = innerHTML || '';
88 container.appendChild(span);
89 return span;
90 };
91
92 yoob.makeDiv = function(container, innerHTML) {
93 var div = document.createElement('div');
94 div.innerHTML = innerHTML || '';
95 container.appendChild(div);
96 return div;
97 };
98
99 yoob.makePre = function(container, innerHTML) {
100 var elem = document.createElement('pre');
101 elem.innerHTML = innerHTML || '';
102 container.appendChild(elem);
103 return elem;
104 };
105
106 yoob.makePanel = function(container, title, isOpen) {
107 isOpen = !!isOpen;
108 var panelContainer = document.createElement('div');
109 var button = document.createElement('button');
110 var innerContainer = document.createElement('div');
111 innerContainer.style.display = isOpen ? "block" : "none";
112
113 button.innerHTML = (isOpen ? "∇" : "⊳") + " " + title;
114 button.onclick = function(e) {
115 isOpen = !isOpen;
116 button.innerHTML = (isOpen ? "∇" : "⊳") + " " + title;
117 innerContainer.style.display = isOpen ? "block" : "none";
118 };
119
120 panelContainer.appendChild(button);
121 panelContainer.appendChild(innerContainer);
122 container.appendChild(panelContainer);
123 return innerContainer;
124 };
125
126 yoob.makeTextArea = function(container, cols, rows, initial) {
127 var textarea = document.createElement('textarea');
128 textarea.rows = "" + rows;
129 textarea.cols = "" + cols;
130 if (initial) {
131 container.value = initial;
132 }
133 container.appendChild(textarea);
134 return textarea;
135 };
136
137 yoob.makeLineBreak = function(container) {
138 var br = document.createElement('br');
139 container.appendChild(br);
140 return br;
141 };
142
143 yoob.makeSelect = function(container, labelText, optionsArray) {
144 var label = document.createElement('label');
145 label.innerHTML = labelText;
146 container.appendChild(label);
147
148 var select = document.createElement("select");
149
150 for (var i = 0; i < optionsArray.length; i++) {
151 var op = document.createElement("option");
152 op.value = optionsArray[i][0];
153 op.text = optionsArray[i][1];
154 if (optionsArray[i].length > 2) {
155 op.selected = optionsArray[i][2];
156 } else {
157 op.selected = false;
158 }
159 select.options.add(op);
160 }
161
162 container.appendChild(select);
163 return select;
164 };
165
166 SliderPlusTextInput = function() {
167 this.init = function(cfg) {
168 this.slider = cfg.slider;
169 this.textInput = cfg.textInput;
170 this.callback = cfg.callback;
171 return this;
172 };
173
174 this.set = function(value) {
175 this.slider.value = "" + value;
176 this.textInput.value = "" + value;
177 this.callback(value);
178 };
179 };
180
181 yoob.makeSliderPlusTextInput = function(container, label, min_, max_, size, value, fun) {
182 yoob.makeSpan(container, label);
183 var slider = yoob.makeSlider(container, min_, max_, value);
184 var s = "" + value;
185 var textInput = yoob.makeTextInput(container, size, s);
186 slider.onchange = function(e) {
187 textInput.value = slider.value;
188 fun(parseInt(slider.value, 10));
189 };
190 textInput.onchange = function(e) {
191 var v = parseInt(textInput.value, 10);
192 if (v !== NaN) {
193 slider.value = "" + v;
194 fun(v);
195 }
196 };
197 return new SliderPlusTextInput().init({
198 'slider': slider,
199 'textInput': textInput,
200 'callback': fun
201 });
202 };
0 /*
1 * This file is part of yoob.js version 0.8
2 * Available from https://github.com/catseye/yoob.js/
3 * This file is in the public domain. See http://unlicense.org/ for details.
4 */
5 if (window.yoob === undefined) yoob = {};
6
7 /*
8 * A view (in the MVC sense) for depicting a yoob.Playfield (-compatible)
9 * object onto any DOM element that supports innerHTML.
10 *
11 * TODO: this may be incomplete; use at your own risk
12 * TODO: have this and the canvas view inherit from a common ABC?
13 */
14 yoob.PlayfieldHTMLView = function() {
15 this.pf = undefined;
16 this.element = undefined;
17
18 this.init = function(pf, element) {
19 this.pf = pf;
20 this.element = element;
21 this.cursors = [];
22 return this;
23 };
24
25 /*** Chainable setters ***/
26
27 /*
28 * Set the list of cursors to the given list of yoob.Cursor (or compatible)
29 * objects.
30 */
31 this.setCursors = function(cursors) {
32 this.cursors = cursors;
33 return this;
34 };
35
36 this.setPlayfield = function(pf) {
37 this.pf = pf;
38 return this;
39 };
40
41 /*
42 * For compatibility with PlayfieldCanvasView. Sets the font size.
43 */
44 this.setCellDimensions = function(cellWidth, cellHeight) {
45 this.element.style.fontSize = cellHeight + "px";
46 return this;
47 };
48
49 /*
50 * Return the requested bounds of the occupied portion of the playfield.
51 * "Occupation" in this sense includes all cursors.
52 *
53 * These may return 'undefined' if there is nothing in the playfield.
54 *
55 * Override these if you want to draw some portion of the
56 * playfield which is not the whole playfield.
57 */
58 this.getLowerX = function() {
59 var minX = this.pf.getMinX();
60 for (var i = 0; i < this.cursors.length; i++) {
61 if (minX === undefined || this.cursors[i].x < minX) {
62 minX = this.cursors[i].x;
63 }
64 }
65 return minX;
66 };
67 this.getUpperX = function() {
68 var maxX = this.pf.getMaxX();
69 for (var i = 0; i < this.cursors.length; i++) {
70 if (maxX === undefined || this.cursors[i].x > maxX) {
71 maxX = this.cursors[i].x;
72 }
73 }
74 return maxX;
75 };
76 this.getLowerY = function() {
77 var minY = this.pf.getMinY();
78 for (var i = 0; i < this.cursors.length; i++) {
79 if (minY === undefined || this.cursors[i].y < minY) {
80 minY = this.cursors[i].y;
81 }
82 }
83 return minY;
84 };
85 this.getUpperY = function() {
86 var maxY = this.pf.getMaxY();
87 for (var i = 0; i < this.cursors.length; i++) {
88 if (maxY === undefined || this.cursors[i].y > maxY) {
89 maxY = this.cursors[i].y;
90 }
91 }
92 return maxY;
93 };
94
95 /*
96 * Returns the number of occupied cells in the x direction.
97 * "Occupation" in this sense includes all cursors.
98 */
99 this.getExtentX = function() {
100 if (this.getLowerX() === undefined || this.getUpperX() === undefined) {
101 return 0;
102 } else {
103 return this.getUpperX() - this.getLowerX() + 1;
104 }
105 };
106
107 /*
108 * Returns the number of occupied cells in the y direction.
109 * "Occupation" in this sense includes all cursors.
110 */
111 this.getExtentY = function() {
112 if (this.getLowerY() === undefined || this.getUpperY() === undefined) {
113 return 0;
114 } else {
115 return this.getUpperY() - this.getLowerY() + 1;
116 }
117 };
118
119 /*
120 * Override to convert Playfield values to HTML.
121 */
122 this.render = function(value) {
123 if (value === undefined) return ' ';
124 return value;
125 };
126
127 /*
128 * Render the playfield, as HTML, on the DOM element.
129 */
130 this.draw = function() {
131 var text = "";
132 for (var y = this.getLowerY(); y <= this.getUpperY(); y++) {
133 var row = "";
134 for (var x = this.getLowerX(); x <= this.getUpperX(); x++) {
135 var rendered = this.render(this.pf.get(x, y));
136 for (var i = 0; i < this.cursors.length; i++) {
137 if (this.cursors[i].x === x && this.cursors[i].y === y) {
138 rendered = this.cursors[i].wrapText(rendered);
139 }
140 }
141 row += rendered;
142 }
143 text += row + "\n";
144 }
145 this.element.innerHTML = text;
146 };
147
148 };
0 /*
1 * This file is part of yoob.js version 0.6
2 * Available from https://github.com/catseye/yoob.js/
3 * This file is in the public domain. See http://unlicense.org/ for details.
4 */
5 if (window.yoob === undefined) yoob = {};
6
7 /*
8 * A two-dimensional Cartesian grid of values.
9 */
10 yoob.Playfield = function() {
11 this._store = {};
12 this.minX = undefined;
13 this.minY = undefined;
14 this.maxX = undefined;
15 this.maxY = undefined;
16 this._default = undefined;
17
18 /*
19 * Set the default value for this Playfield. This
20 * value is returned by get() for any cell that was
21 * never written to, or had `undefined` put() into it.
22 */
23 this.setDefault = function(v) {
24 this._default = v;
25 return this;
26 };
27
28 /*
29 * Obtain the value at (x, y). The default value will
30 * be returned if the cell was never written to.
31 */
32 this.get = function(x, y) {
33 var v = this._store[x+','+y];
34 if (v === undefined) return this._default;
35 return v;
36 };
37
38 /*
39 * Write a new value into (x, y). Note that writing
40 * `undefined` into a cell has the semantics of deleting
41 * the value at that cell; a subsequent get() for that
42 * location will return this Playfield's default value.
43 */
44 this.put = function(x, y, value) {
45 var key = x+','+y;
46 if (value === undefined || value === this._default) {
47 delete this._store[key];
48 return;
49 }
50 if (this.minX === undefined || x < this.minX) this.minX = x;
51 if (this.maxX === undefined || x > this.maxX) this.maxX = x;
52 if (this.minY === undefined || y < this.minY) this.minY = y;
53 if (this.maxY === undefined || y > this.maxY) this.maxY = y;
54 this._store[key] = value;
55 };
56
57 /*
58 * Like put(), but does not update the playfield bounds. Do
59 * this if you must do a batch of put()s in a more efficient
60 * manner; after doing so, call recalculateBounds().
61 */
62 this.putDirty = function(x, y, value) {
63 var key = x+','+y;
64 if (value === undefined || value === this._default) {
65 delete this._store[key];
66 return;
67 }
68 this._store[key] = value;
69 };
70
71 /*
72 * Recalculate the bounds (min/max X/Y) which are tracked
73 * internally to support methods like foreach(). This is
74 * not needed *unless* you've used putDirty() at some point.
75 * (In which case, call this immediately after your batch
76 * of putDirty()s.)
77 */
78 this.recalculateBounds = function() {
79 this.minX = undefined;
80 this.minY = undefined;
81 this.maxX = undefined;
82 this.maxY = undefined;
83
84 for (var cell in this._store) {
85 var pos = cell.split(',');
86 var x = parseInt(pos[0], 10);
87 var y = parseInt(pos[1], 10);
88 if (this.minX === undefined || x < this.minX) this.minX = x;
89 if (this.maxX === undefined || x > this.maxX) this.maxX = x;
90 if (this.minY === undefined || y < this.minY) this.minY = y;
91 if (this.maxY === undefined || y > this.maxY) this.maxY = y;
92 }
93 };
94
95 /*
96 * Clear the contents of this Playfield.
97 */
98 this.clear = function() {
99 this._store = {};
100 this.minX = undefined;
101 this.minY = undefined;
102 this.maxX = undefined;
103 this.maxY = undefined;
104 };
105
106 /*
107 * Scroll a rectangular subrectangle of this Playfield, up.
108 * TODO: support other directions.
109 */
110 this.scrollRectangleY = function(dy, minX, minY, maxX, maxY) {
111 if (dy < 1) {
112 for (var y = minY; y <= (maxY + dy); y++) {
113 for (var x = minX; x <= maxX; x++) {
114 this.put(x, y, this.get(x, y - dy));
115 }
116 }
117 } else { alert("scrollRectangleY(" + dy + ") notImplemented"); }
118 };
119
120 this.clearRectangle = function(minX, minY, maxX, maxY) {
121 // Could also do this with a foreach that checks
122 // each position. Would be faster on sparser playfields.
123 for (var y = minY; y <= maxY; y++) {
124 for (var x = minX; x <= maxX; x++) {
125 this.put(x, y, undefined);
126 }
127 }
128 };
129
130 /*
131 * Load a string into this Playfield.
132 * The string may be multiline, with newline (ASCII 10)
133 * characters delimiting lines. ASCII 13 is ignored.
134 *
135 * If transformer is given, it should be a one-argument
136 * function which accepts a character and returns the
137 * object you wish to write into the playfield upon reading
138 * that character.
139 */
140 this.load = function(x, y, string, transformer) {
141 var lx = x;
142 var ly = y;
143 if (transformer === undefined) {
144 transformer = function(c) {
145 if (c === ' ') {
146 return undefined;
147 } else {
148 return c;
149 }
150 }
151 }
152 for (var i = 0; i < string.length; i++) {
153 var c = string.charAt(i);
154 if (c === '\n') {
155 lx = x;
156 ly++;
157 } else if (c === '\r') {
158 } else {
159 this.putDirty(lx, ly, transformer(c));
160 lx++;
161 }
162 }
163 this.recalculateBounds();
164 };
165
166 /*
167 * Convert this Playfield to a multi-line string. Each row
168 * is a line, delimited with a newline (ASCII 10).
169 *
170 * If transformer is given, it should be a one-argument
171 * function which accepts a playfield element and returns a
172 * character (or string) you wish to place in the resulting
173 * string for that element.
174 */
175 this.dump = function(transformer) {
176 var text = "";
177 if (transformer === undefined) {
178 transformer = function(c) { return c; }
179 }
180 for (var y = this.minY; y <= this.maxY; y++) {
181 var row = "";
182 for (var x = this.minX; x <= this.maxX; x++) {
183 row += transformer(this.get(x, y));
184 }
185 text += row + "\n";
186 }
187 return text;
188 };
189
190 /*
191 * Iterate over every defined cell in the Playfield.
192 * fun is a callback which takes three parameters:
193 * x, y, and value. If this callback returns a value,
194 * it is written into the Playfield at that position.
195 * This function ensures a particular order.
196 */
197 this.foreach = function(fun) {
198 for (var y = this.minY; y <= this.maxY; y++) {
199 for (var x = this.minX; x <= this.maxX; x++) {
200 var key = x+','+y;
201 var value = this._store[key];
202 if (value === undefined)
203 continue;
204 var result = fun(x, y, value);
205 if (result !== undefined) {
206 if (result === ' ') {
207 result = undefined;
208 }
209 this.put(x, y, result);
210 }
211 }
212 }
213 };
214
215 /*
216 * Analogous to (monoid) map in functional languages,
217 * iterate over this Playfield, transform each value using
218 * a supplied function, and write the transformed value into
219 * a destination Playfield.
220 *
221 * Supplied function should take a Playfield (this Playfield),
222 * x, and y, and return a value.
223 *
224 * The map source may extend beyond the internal bounds of
225 * the Playfield, by giving the min/max Dx/Dy arguments
226 * (which work like margin offsets.)
227 *
228 * Useful for evolving a cellular automaton playfield. In this
229 * case, min/max Dx/Dy should be computed from the neighbourhood.
230 */
231 this.map = function(destPf, fun, minDx, minDy, maxDx, maxDy) {
232 if (minDx === undefined) minDx = 0;
233 if (minDy === undefined) minDy = 0;
234 if (maxDx === undefined) maxDx = 0;
235 if (maxDy === undefined) maxDy = 0;
236 for (var y = this.minY + minDy; y <= this.maxY + maxDy; y++) {
237 for (var x = this.minX + minDx; x <= this.maxX + maxDx; x++) {
238 destPf.putDirty(x, y, fun(this, x, y));
239 }
240 }
241 destPf.recalculateBounds();
242 };
243
244 /*
245 * Accessors for the minimum (resp. maximum) x (resp. y) values of
246 * occupied (non-default-valued) cells in this Playfield. If there are
247 * no cells in this Playfield, these will refturn undefined. Note that
248 * these are not guaranteed to be tight bounds; if values in cells
249 * are deleted, these bounds may still be considered to be outside them.
250 */
251 this.getMinX = function() {
252 return this.minX;
253 };
254 this.getMaxX = function() {
255 return this.maxX;
256 };
257 this.getMinY = function() {
258 return this.minY;
259 };
260 this.getMaxY = function() {
261 return this.maxY;
262 };
263
264 /*
265 * Returns the number of occupied cells in the x direction.
266 */
267 this.getExtentX = function() {
268 if (this.maxX === undefined || this.minX === undefined) {
269 return 0;
270 } else {
271 return this.maxX - this.minX + 1;
272 }
273 };
274
275 /*
276 * Returns the number of occupied cells in the y direction.
277 */
278 this.getExtentY = function() {
279 if (this.maxY === undefined || this.minY === undefined) {
280 return 0;
281 } else {
282 return this.maxY - this.minY + 1;
283 }
284 };
285 };
0 /*
1 * This file is part of yoob.js version 0.8
2 * Available from https://github.com/catseye/yoob.js/
3 * This file is in the public domain. See http://unlicense.org/ for details.
4 */
5 if (window.yoob === undefined) yoob = {};
6
7 /*
8 * An object for managing a set of "presets" -- which, for an esolang,
9 * might be example programs; for an emulator, might be ROM images;
10 * for a control panel, may be pre-selected combinations of settings;
11 * and so forth.
12 *
13 * Mostly intended to be connected to a yoob.Controller -- but need not be.
14 */
15 yoob.PresetManager = function() {
16 /*
17 * The single argument is a dictionary (object) where the keys are:
18 *
19 * selectElem: (required) the <select> DOM element that will be
20 * populated with the available example programs. Selecting one
21 * will cause the .select() method of this manager to be called.
22 * it will also call .onselect if that method is present.
23 *
24 * setPreset: (optional) a callback which will be called whenever
25 * a new preset is selected. If this is not given, an individual
26 * callback must be supplied with each preset as it is added.
27 */
28 this.init = function(cfg) {
29 this.selectElem = cfg.selectElem;
30 if (cfg.setPreset) {
31 this.setPreset = cfg.setPreset;
32 }
33 this.clear();
34 var $this = this;
35 this.selectElem.onchange = function() {
36 $this._select(this.options[this.selectedIndex].value);
37 }
38 return this;
39 };
40
41 /*
42 * Removes all options from the selectElem, and their associated data.
43 */
44 this.clear = function() {
45 this.reactTo = {};
46 while (this.selectElem.firstChild) {
47 this.selectElem.removeChild(this.selectElem.firstChild);
48 }
49 this.add('(select one...)', function() {});
50 return this;
51 };
52
53 /*
54 * Adds a preset to this PresetManager. When it is selected,
55 * the given callback will be called, being passed the id as the
56 * first argument. If no callback is provided, the default callback,
57 * configured with setPreset in the init() configuration, will be used.
58 */
59 this.add = function(id, callback) {
60 var opt = document.createElement("option");
61 opt.text = id;
62 opt.value = id;
63 this.selectElem.options.add(opt);
64 var $this = this;
65 this.reactTo[id] = callback || this.setPreset;
66 return this;
67 };
68
69 this.setPreset = function(id) {
70 alert("No default setPreset callback configured");
71 };
72
73 /*
74 * Called by the selectElem's onchange event. For sanity, you should
75 * probably not call this yourself.
76 */
77 this._select = function(id) {
78 this.reactTo[id](id);
79 if (this.onselect) {
80 this.onselect(id);
81 }
82 };
83
84 /*
85 * Call this to programmatically select an item. This will change
86 * the selected option in the selectElem and trigger the appropriate
87 * callback in this PresetManager.
88 */
89 this.select = function(id) {
90 var i = 0;
91 var opt = this.selectElem.options[i];
92 while (opt) {
93 if (opt.value === id) {
94 this.selectElem.selectedIndex = i;
95 this._select(id);
96 return this;
97 }
98 i++;
99 opt = this.selectElem.options[i];
100 }
101 // if not found, select the "(select one...)" option
102 this.selectElem.selectedIndex = 0;
103 return this;
104 };
105
106 /*
107 * When called, every DOM element in the document with the given
108 * class will be considered a preset, and the manager
109 * will be populated with these. Generally the CSS for the class
110 * will have `display: none` and the elements will be <div>s.
111 *
112 * callback is as described for the .add() method.
113 */
114 this.populateFromClass = function(className, callback) {
115 var elements = document.getElementsByClassName(className);
116 for (var i = 0; i < elements.length; i++) {
117 var e = elements[i];
118 this.add(e.id, callback);
119 }
120 return this;
121 };
122 };
0 /*
1 * This file is part of yoob.js version 0.8
2 * Available from https://github.com/catseye/yoob.js/
3 * This file is in the public domain. See http://unlicense.org/ for details.
4 */
5 if (window.yoob === undefined) yoob = {};
6
7 /*
8 * A SourceManager co-operates with a Controller and maybe a PresetManager.
9 * It is for editing a program/configuration in some editing interface
10 * which is mutually exclusive, UI-wise, with the run/animation interface.
11 */
12 yoob.SourceManager = function() {
13 /*
14 * editor: an element (usually a textarea) which stores the source code
15 * hideDuringEdit: a list of elements which will be hidden when editing
16 * (typically this will be the animation display of a yoob.Controller)
17 * disableDuringEdit: a list of elements which will be disabled when editing
18 * storageKey: key under which sources will be saved/loaded from localStorage
19 * panelContainer: an element into which to add the created button panel
20 * (if you do not give this, no panel will be created. You're on your own.)
21 * onDone: if given, if a function, it becomes the onDone method on this
22 */
23 this.init = function(cfg) {
24 this.supportsLocalStorage = (
25 window['localStorage'] !== undefined &&
26 window['localStorage'] !== null
27 );
28 this.editor = cfg.editor;
29 this.hideDuringEdit = cfg.hideDuringEdit;
30 this.prevDisplay = {};
31 for (var i = 0; i < this.hideDuringEdit.length; i++) {
32 this.prevDisplay[this.hideDuringEdit[i]] = this.hideDuringEdit[i].display;
33 }
34 this.disableDuringEdit = cfg.disableDuringEdit;
35 this.storageKey = cfg.storageKey || 'default';
36 this.controls = {};
37 if (cfg.panelContainer) {
38 this.panel = this.makePanel();
39 cfg.panelContainer.appendChild(this.panel);
40 }
41 if (cfg.onDone) {
42 this.onDone = cfg.onDone;
43 }
44 this.clickDone();
45 return this;
46 };
47
48 this.makePanel = function() {
49 var panel = document.createElement('div');
50 var $this = this;
51 var makeButton = function(action) {
52 var button = document.createElement('button');
53 var upperAction = action.charAt(0).toUpperCase() + action.slice(1);
54 button.innerHTML = upperAction;
55 button.style.width = "5em";
56 panel.appendChild(button);
57 button.onclick = function(e) {
58 if ($this['click' + upperAction]) {
59 $this['click' + upperAction]();
60 }
61 }
62 $this.controls[action] = button;
63 };
64 var keys = ["edit", "done", "load", "save"];
65 for (var i = 0; i < keys.length; i++) {
66 makeButton(keys[i]);
67 }
68 return panel;
69 };
70
71 this.clickEdit = function() {
72 var hde = this.hideDuringEdit;
73 for (var i = 0; i < hde.length; i++) {
74 this.prevDisplay[hde[i]] = hde[i].style.display;
75 hde[i].style.display = 'none';
76 }
77 for (var i = 0; i < this.disableDuringEdit.length; i++) {
78 this.disableDuringEdit[i].disabled = true;
79 /* But if it's not a form control, disabled is meaningless. */
80 this.disableDuringEdit[i].style.pointerEvents = 'none';
81 this.disableDuringEdit[i].style.opacity = '0.5';
82 }
83 this.editor.style.display = 'block';
84 this.controls.edit.disabled = true;
85 var keys = ["done", "load", "save"];
86 for (var i = 0; i < keys.length; i++) {
87 this.controls[keys[i]].disabled = false;
88 }
89 this.onEdit();
90 };
91
92 this.clickDone = function() {
93 var hde = this.hideDuringEdit;
94 for (var i = 0; i < hde.length; i++) {
95 hde[i].style.display = this.prevDisplay[hde[i]];
96 }
97 for (var i = 0; i < this.disableDuringEdit.length; i++) {
98 this.disableDuringEdit[i].disabled = false;
99 /* But if it's not a form control, disabled is meaningless. */
100 this.disableDuringEdit[i].style.pointerEvents = 'auto';
101 this.disableDuringEdit[i].style.opacity = '1.0';
102 }
103 this.editor.style.display = 'none';
104 this.controls.edit.disabled = false;
105 var keys = ["done", "load", "save"];
106 for (var i = 0; i < keys.length; i++) {
107 this.controls[keys[i]].disabled = true;
108 }
109 this.onDone();
110 };
111
112 this.clickLoad = function() {
113 if (!this.supportsLocalStorage) {
114 var s = "Your browser does not support Local Storage.\n\n";
115 s += "You may instead open a local file in a text editor, ";
116 s += "select all, copy to clipboard, then paste into ";
117 s += "the textarea, to load a source you have saved locally.";
118 alert(s);
119 return;
120 }
121 this.loadSource(
122 localStorage.getItem('yoob:' + this.storageKey + ':default')
123 );
124 };
125
126 this.clickSave = function() {
127 if (!this.supportsLocalStorage) {
128 var s = "Your browser does not support Local Storage.\n\n";
129 s += "You may instead select all in the textarea, copy to ";
130 s += "clipboard, open a local text editor, paste in there, ";
131 s += "and save, to save a source locally.";
132 alert(s);
133 return;
134 }
135 localStorage.setItem(
136 'yoob:' + this.storageKey + ':default',
137 this.getEditorText()
138 );
139 };
140
141 this.onEdit = function() {
142 };
143
144 /*
145 * Override this to load it into the controller
146 */
147 this.onDone = function() {
148 };
149
150 /*
151 * Loads a source text into the editor element.
152 */
153 this.loadSource = function(text) {
154 this.setEditorText(text);
155 this.onDone();
156 };
157
158 /*
159 * You may need to override if your editor is not a textarea.
160 */
161 this.setEditorText = function(text) {
162 this.editor.value = text;
163 };
164
165 /*
166 * You may need to override if your editor is not a textarea.
167 */
168 this.getEditorText = function() {
169 return this.editor.value;
170 };
171
172 /*
173 * Loads a source text into the source element.
174 * Assumes it comes from an element in the document, so it translates
175 * the basic HTML escapes (but no others) to plain text.
176 */
177 this.loadSourceFromHTML = function(html) {
178 var text = html;
179 text = text.replace(/\&lt;/g, '<');
180 text = text.replace(/\&gt;/g, '>');
181 text = text.replace(/\&amp;/g, '&');
182 this.loadSource(text);
183 };
184
185 /*
186 * This is the basic idea, but not fleshed out yet.
187 * - Should we cache the source somewhere?
188 * - While we're waiting, should we disable the UI / show a spinny?
189 */
190 this.loadSourceFromURL = function(url, errorCallback) {
191 var http = new XMLHttpRequest();
192 var $this = this;
193 if (!errorCallback) {
194 errorCallback = function(http) {
195 $this.loadSource(
196 "Error: could not load " + url + ": " + http.statusText
197 );
198 }
199 }
200 http.open("get", url, true);
201 http.onload = function(e) {
202 if (http.readyState === 4 && http.responseText) {
203 if (http.status === 200) {
204 $this.loadSource(http.responseText);
205 } else {
206 errorCallback(http);
207 }
208 }
209 };
210 http.send(null);
211 };
212 };
0 #!/usr/bin/env python
1
2 def input_playfield(f):
3 playfield = []
4 allowable_chars = set(".#?")
5 for line in f:
6 line = line.rstrip()
7 if playfield:
8 assert len(line) == len(playfield[0])
9 assert set(line) <= allowable_chars, set(line)
10 playfield.append(line)
11 return playfield
12
13
14 def print_playfields(playfields):
15 first_playfield = playfields[0]
16 for y in xrange(0, len(first_playfield)):
17 row = [playfield[y] for playfield in playfields]
18 print '|'.join(row)
19
20
21 def main(argv):
22 with open(argv[0]) as f:
23 playfield = input_playfield(f)
24
25 iters = 5
26 playfields = []
27
28 for iter in xrange(0, iters):
29 pf2 = []
30 for y in xrange(0, len(playfield)):
31 ln = ''
32 for x in xrange(0, len(playfield[y])):
33 min_alive = 0
34 max_alive = 0
35 for (dx, dy) in ((-1,-1), (-1, 0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)):
36 nx = x + dx
37 ny = y + dy
38 if nx < 0 or nx >= len(playfield[y]):
39 continue
40 if ny < 0 or ny >= len(playfield):
41 continue
42 nbcell = playfield[ny][nx]
43 if nbcell == '#':
44 min_alive += 1
45 max_alive += 1
46 elif nbcell == '.':
47 min_alive += 0
48 max_alive += 0
49 elif nbcell == '?':
50 min_alive += 0
51 max_alive += 1
52
53 cell = playfield[y][x]
54 new_cell = None
55 if cell == '#':
56 if min_alive in (2, 3) and max_alive in (2, 3):
57 new_cell = '#'
58 elif min_alive >= 4 or max_alive <= 1:
59 #print "X->_ at %s,%s: min_alive = %s, max_alive = %s" % (x,y,min_alive,max_alive)
60 new_cell = '.'
61 else:
62 new_cell = '?'
63 elif cell == '.':
64 if min_alive == 3 and max_alive == 3:
65 #print "_->X at %s,%s: min_alive = %s, max_alive = %s" % (x,y,min_alive,max_alive)
66 new_cell = '#'
67 elif min_alive >= 4 or max_alive <= 2:
68 new_cell = '.'
69 else:
70 new_cell = '?'
71 elif cell == '?':
72 if min_alive == 3 and max_alive == 3:
73 new_cell = '#'
74 elif min_alive >= 4 or max_alive <= 1:
75 new_cell = '.'
76 else:
77 new_cell = '?'
78
79 ln += new_cell
80 pf2.append(ln)
81 #print
82 playfields.append(pf2)
83 playfield = pf2
84
85 print_playfields(playfields)
86
87
88 if __name__ == '__main__':
89 import sys
90 main(sys.argv[1:])
0 #!/bin/sh
1
2 THIS=`realpath $0`
3 DIR=`dirname $THIS`
4 cd $DIR
5 (sleep 1 && python -m webbrowser -t "http://127.0.0.1:8000/impl/slife.js/demo/slife.html") &
6 python -m SimpleHTTPServer
0 #!/bin/sh
1
2 falderal README.md