Initial import of files for Schroedinger's Game of Life.
Chris Pressey
10 years ago
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 | .........................??. | |
7 | .........................??. | |
8 | ............................ | |
9 | .............?.............. | |
10 | .##..........?...........##. | |
11 | .??..........?...........?#. | |
12 | ............................ |
0 | ............ | |
1 | ............ | |
2 | ............ | |
3 | .....##..... | |
4 | ....#.#..... | |
5 | ......#..... | |
6 | ............ | |
7 | ............ |
0 | ............ | |
1 | ............ | |
2 | ............ | |
3 | .....##..... | |
4 | ....#.?..... | |
5 | ......#..... | |
6 | ............ | |
7 | ............ |
0 | ............ | |
1 | ............ | |
2 | ............ | |
3 | .....??..... | |
4 | ....?.?..... | |
5 | ......?..... | |
6 | ............ | |
7 | ............ |
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(/\</g, '<'); | |
180 | text = text.replace(/\>/g, '>'); | |
181 | text = text.replace(/\&/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 | ||
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:]) |