git @ Cat's Eye Technologies Braktif / rel_1_0_2018_0222
Merge pull request #1 from catseye/develop-1.0-2018.1 Develop 1.0 2018.1 Chris Pressey authored 7 years ago GitHub committed 7 years ago
9 changed file(s) with 1053 addition(s) and 339 deletion(s). Raw diff Collapse all Expand all
0 function launch(prefix, container, config) {
1 if (typeof container === 'string') {
2 container = document.getElementById(container);
3 }
4 config = config || {};
5 var deps = [
6 "yoob/element-factory.js",
7 "yoob/playfield.js",
8 "yoob/playfield-html-view.js",
9 "yoob/controller.js",
10 "yoob/source-manager.js",
11 "yoob/preset-manager.js",
12 "../script/braktif.js"
13 ];
14 var loaded = 0;
15 var onload = function() {
16 if (++loaded < deps.length) return;
17 /* ----- launch, phase 1: create the UI ----- */
18 var controlPanel = yoob.makeDiv(container);
19 controlPanel.id = "panel_container";
20
21 var subPanel = yoob.makeDiv(container);
22 var selectConfiguration = yoob.makeSelect(subPanel, 'example configuration:', []);
23
24 var displayContainer = yoob.makeDiv(container);
25 displayContainer.id = 'display_container';
26
27 var generationDisplay = yoob.makePre(displayContainer);
28 generationDisplay.id = 'generation_display';
29
30 var editor = yoob.makeTextArea(displayContainer, 40, 25);
31
32 var generationView = new yoob.PlayfieldHTMLView().init({
33 element: generationDisplay
34 });
35
36 // NOTE this is kind of chintzy, but, oh well
37 generationView.draw = function() {
38 this.element.innerHTML = this.pf.dump(dumpMapper);
39 };
40
41 /* ----- launch, phase 2: connect the controller ----- */
42 var pf;
43 var controller = (new yoob.Controller()).init({
44 panelContainer: controlPanel,
45 step: function() {
46 var newPf = (new yoob.Playfield()).init({ defaultValue: 'Space' });
47 evolve_playfield(pf, newPf);
48 pf = newPf;
49 generationView.setPlayfield(pf);
50 generationView.draw();
51 },
52 reset: function(text) {
53 pf = (new yoob.Playfield()).init({ defaultValue: 'Space' });
54 pf.load(0, 0, text, loadMapper);
55 generationView.setPlayfield(pf);
56 generationView.draw();
57 }
58 });
59 controller.clickStop();
60
61 var sourceManager = (new yoob.SourceManager()).init({
62 panelContainer: controlPanel,
63 editor: editor,
64 hideDuringEdit: [generationDisplay],
65 disableDuringEdit: [controller.panel],
66 storageKey: 'braktif.alp',
67 onDone: function() {
68 controller.performReset(this.getEditorText());
69 }
70 });
71 var p = (new yoob.PresetManager()).init({
72 selectElem: selectConfiguration,
73 controller: controller
74 }).populateFromPairs(sourceManager, exampleConfigurations);
75 };
76 for (var i = 0; i < deps.length; i++) {
77 var elem = document.createElement('script');
78 elem.src = prefix + deps[i];
79 elem.onload = onload;
80 document.body.appendChild(elem);
81 }
82 }
11 <head>
22 <meta charset="utf-8">
33 <title>Braktif Demo</title>
4 <style>
5 #output {
6 border: 1px solid blue;
7 }
8 .example {
9 display: none;
10 }
11 </style>
124 </head>
135 <body>
146
157 <h1>Braktif Demo</h1>
168
17 <button id="load">Load</button>
18 <button id="edit">Edit</button>
19 <button id="start">Start</button>
20 <button id="stop">Stop</button>
21 <button id="step">Step</button>
22 <button id="reset">Reset</button>
23 Speed: <input id="speed" type="range" min="0" max="200" value="0" />
24
25 <select id="select_source"></select>
26
27 <textarea id="input" rows="10" cols="50"></textarea>
28
29 <pre id="output" style="border: 1px solid blue">
30 </pre>
31
32 <div class="example" id="test"
33 >000000000000000000 &lt;*&lt;*&gt;*&gt;*&lt;*&lt;*&gt;*&gt;*
34 -----------------d-i---------------
35 </div>
36
37
38 <div class="example" id="test2"
39 > &lt;*
40 000000000000000000000000000000000000000000000000000000000 *[---]
41 --------------------------------------------------------d-i- --
42 </div>
43
44
45 <div class="example" id="test3"
46 > *
47 &lt;&lt;*[--]*
48 000000000000000000 *[----- --]
49 -----------------d-i-- --------
50
51 </div>
9 <div id="container"></div>
5210
5311 </body>
54 <script src="yoob/controller.js"></script>
55 <script src="yoob/playfield.js"></script>
56 <script src="yoob/preset-manager.js"></script>
57 <script src="../script/braktif.js"></script>
12 <script src="../eg/index.js"></script>
13 <script src="braktif-launcher.js"></script>
5814 <script>
59 var output = document.getElementById('output');
60 var c = new yoob.Controller();
61 var pf;
62
63 c.load = function(text) {
64 pf = new yoob.Playfield();
65 pf.setDefault('Space');
66 pf.load(0, 0, text, loadMapper);
67 output.innerHTML = pf.dump(dumpMapper);
68 };
69
70 c.step = function() {
71 var newPf = new yoob.Playfield();
72 newPf.setDefault('Space');
73 evolve_playfield(pf, newPf);
74 pf = newPf;
75 output.innerHTML = pf.dump(dumpMapper);
76 };
77
78 c.connect({
79 'start': 'start',
80 'stop': 'stop',
81 'step': 'step',
82 'load': 'load',
83 'edit': 'edit',
84 'reset': 'reset',
85 'speed': 'speed',
86 'source': 'input',
87 'display': 'output'
88 });
89 var p = (new yoob.PresetManager()).init({
90 selectElem: document.getElementById("select_source"),
91 controller: c
92 }).populateFromClass('example').select('test3');
93 c.click_load();
15 launch('', 'container');
9416 </script>
00 /*
1 * This file is part of yoob.js version 0.7-2015.0108
1 * This file is part of yoob.js version 0.12
22 * Available from https://github.com/catseye/yoob.js/
33 * This file is in the public domain. See http://unlicense.org/ for details.
44 */
1010 * convenience, we will refer to this as the _program state_, even though
1111 * it is of course highly adaptable and might not represent a "program".
1212 *
13 * The controller can be connected to a UI in the DOM, consisting of:
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:
1418 *
1519 * - a set of buttons which control the evolution of the state:
1620 * - start
1721 * - stop
1822 * - step
1923 * - load
20 * - edit
2124 * - reset
2225 *
2326 * - a slider control which adjusts the speed of program state evolution.
24 *
25 * - a `source` element from which an program state can be loaded,
26 * and which is generally assumed to support user-editing of the source.
27 * The `edit` button will cause the `source` to be shown and the `display`
28 * to be hidden, while the `load` button will load the program state from
29 * the `source`, hide the `source`, and show the `display`.
30 *
31 * - a `display` element on which the current program state will be
32 * depicted. Note that the controller is not directly responsible for
33 * rendering the program state; use something like yoob.PlayfieldCanvasView
34 * for that instead. The controller only knows about the `display` in order
35 * to hide it while the `source` is being edited and to show it after the
36 * `source` has been loaded.
37 *
38 * - an `input` element, which provides input to the running program.
39 *
40 * Each of these is optional, and if not configured, will not be used.
4127 *
4228 * To use a Controller, create a subclass of yoob.Controller and override
4329 * the following methods:
4430 * - make it evolve the state by one tick in the step() method
45 * - make it load the state from a multiline string in the load() method
31 * - make it load the initial state from a string in the reset(s) method
4632 *
4733 * In these methods, you will need to store the state (in whatever
4834 * representation you find convenient for processing and for depicting on
5440 *
5541 * You should *not* store it in the `.state` attribute, as a yoob.Controller
5642 * uses this to track its own state (yes, it has its own state independent of
57 * the program state. at least potentially.)
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.
5874 */
5975 yoob.Controller = function() {
6076 var STOPPED = 0; // the program has terminated (itself)
6278 var RUNNING = 2; // the program is running
6379 var BLOCKED = 3; // the program is waiting for more input
6480
65 this.intervalId = undefined;
66 this.delay = 100;
67 this.state = STOPPED;
68
69 this.source = undefined;
70 this.input = undefined;
71 this.display = undefined;
72
73 this.speed = undefined;
74 this.controls = {};
75
7681 /*
77 * This is not a public method.
78 */
79 this._makeEventHandler = function(control, key) {
80 if (this['click_' + key] !== undefined) {
81 key = 'click_' + key;
82 }
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');
83110 var $this = this;
84 return function(e) {
85 $this[key](control);
111
112 var makeEventHandler = function(control, upperAction) {
113 return function(e) {
114 $this['click' + upperAction](control);
115 };
86116 };
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 }
87161 };
88162
89163 /*
90 * Single argument is a dictionary (object) where the keys
91 * are the actions a controller can undertake, and the values
92 * are either DOM elements or strings; if strings, DOM elements
93 * with those ids will be obtained from the document and used.
94 *
95 * When the button associated with e.g. 'start' is clicked,
96 * the corresponding method (in this case, 'click_start()')
97 * on this Controller will be called. These functions are
98 * responsible for changing the state of the Controller (both
99 * the internal state, and the enabled status, etc. of the
100 * controls), and for calling other methods on the Controller
101 * to implement the particulars of the action.
102 *
103 * For example, 'click_step()' calls 'performStep()' which
104 * calls 'step()' (which a subclass or instantiator must
105 * provide an implementation for.)
106 *
107 * To simulate one of the buttons being clicked, you may
108 * call 'click_foo()' yourself in code. However, that will
109 * be subject to the current restrictions of the interface.
110 * You may be better off calling one of the "internal" methods
111 * like 'performStep()'.
112 */
113 this.connect = function(dict) {
114 var $this = this;
115
116 var keys = ["start", "stop", "step", "load", "edit", "reset"];
117 for (var i in keys) {
118 var key = keys[i];
119 var value = dict[key];
120 if (typeof value === 'string') {
121 value = document.getElementById(value);
122 }
123 if (value) {
124 value.onclick = this._makeEventHandler(value, key);
125 this.controls[key] = value;
126 }
127 }
128
129 var keys = ["speed", "source", "input", "display"];
130 for (var i in keys) {
131 var key = keys[i];
132 var value = dict[key];
133 if (typeof value === 'string') {
134 value = document.getElementById(value);
135 }
136 if (value) {
137 this[key] = value;
138 // special cases
139 if (key === 'speed') {
140 this.speed.value = this.delay;
141 this.speed.onchange = function(e) {
142 $this.setDelayFrom($this.speed);
143 if ($this.intervalId !== undefined) {
144 $this.stop();
145 $this.start();
146 }
147 }
148 } else if (key === 'input') {
149 this.input.onchange = function(e) {
150 if (this.value.length > 0) {
151 $this.unblock();
152 }
153 }
154 }
155 }
156 }
157
158 this.click_stop();
159 };
160
161 this.click_step = function(e) {
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) {
162175 if (this.state === STOPPED) return;
163 this.click_stop();
176 this.clickStop();
164177 this.state = PAUSED;
165178 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 }
166189 };
167190
168191 /*
173196 * - `block` to indicate that the program is waiting for more input.
174197 */
175198 this.step = function() {
176 alert("step() NotImplementedError");
177 };
178
179 this.performStep = function() {
180 var code = this.step();
181 if (code === 'stop') {
182 this.terminate();
183 } else if (code === 'block') {
184 this.state = BLOCKED;
185 }
186 };
187
188 this.click_load = function(e) {
189 this.click_stop();
190 this.load(this.source.value);
191 this.state = PAUSED;
192 if (this.controls.edit) this.controls.edit.style.display = "inline";
193 if (this.controls.load) this.controls.load.style.display = "none";
194 if (this.controls.start) this.controls.start.disabled = false;
195 if (this.controls.step) this.controls.step.disabled = false;
196 if (this.controls.stop) this.controls.stop.disabled = true;
197 if (this.controls.reset) this.controls.reset.disabled = false;
198 if (this.display) this.display.style.display = "block";
199 if (this.source) this.source.style.display = "none";
200 };
201
202 this.load = function(text) {
203 alert("load() NotImplementedError");
204 };
205
206 /*
207 * Loads a source text into the source element.
208 */
209 this.loadSource = function(text) {
210 if (this.source) this.source.value = text;
211 this.load(text);
212 this.state = PAUSED;
213 };
214
215 /*
216 * Loads a source text into the source element.
217 * Assumes it comes from an element in the document, so it translates
218 * the basic HTML escapes (but no others) to plain text.
219 */
220 this.loadSourceFromHTML = function(html) {
221 var text = html;
222 text = text.replace(/\&lt;/g, '<');
223 text = text.replace(/\&gt;/g, '>');
224 text = text.replace(/\&amp;/g, '&');
225 this.loadSource(text);
226 };
227
228 /*
229 * This is the basic idea, but not fleshed out yet.
230 * - Should we cache the source somewhere?
231 * - While we're waiting, should we disable the UI / show a spinny?
232 */
233 this.loadSourceFromURL = function(url, errorCallback) {
234 var http = new XMLHttpRequest();
235 var $this = this;
236 if (!errorCallback) {
237 errorCallback = function(http) {
238 $this.loadSource(
239 "Error: could not load " + url + ": " + http.statusText
240 );
241 }
242 }
243 http.open("get", url, true);
244 http.onload = function(e) {
245 if (http.readyState === 4 && http.responseText) {
246 if (http.status === 200) {
247 $this.loadSource(http.responseText);
248 } else {
249 errorCallback(http);
250 }
251 }
252 };
253 http.send(null);
254 };
255
256 this.click_edit = function(e) {
257 this.click_stop();
258 if (this.controls.edit) this.controls.edit.style.display = "none";
259 if (this.controls.load) this.controls.load.style.display = "inline";
260 if (this.controls.start) this.controls.start.disabled = true;
261 if (this.controls.step) this.controls.step.disabled = true;
262 if (this.controls.stop) this.controls.stop.disabled = true;
263 if (this.controls.reset) this.controls.reset.disabled = true;
264 if (this.display) this.display.style.display = "none";
265 if (this.source) this.source.style.display = "block";
266 };
267
268 this.click_start = function(e) {
269 this.start();
199 throw new Error("step() NotImplementedError");
200 };
201
202 /******************
203 * action: Start
204 */
205 this.clickStart = function(e) {
206 this.performStart();
270207 if (this.controls.start) this.controls.start.disabled = true;
271208 if (this.controls.step) this.controls.step.disabled = false;
272209 if (this.controls.stop) this.controls.stop.disabled = false;
273210 };
274211
212 this.performStart = function() {
213 this.start();
214 };
215
275216 this.start = function() {
276217 if (this.intervalId !== undefined)
277218 return;
278 this.step();
219 this.performStep();
279220 var $this = this;
280221 this.intervalId = setInterval(function() {
281222 $this.performStep();
283224 this.state = RUNNING;
284225 };
285226
286 this.click_stop = function(e) {
287 this.stop();
288 this.state = PAUSED;
289 /* why is this check here? ... */
290 if (this.controls.stop && this.controls.stop.disabled) {
291 return;
292 }
227 /******************
228 * action: Stop
229 */
230 this.clickStop = function(e) {
231 this.performStop();
293232 if (this.controls.start) this.controls.start.disabled = false;
294233 if (this.controls.step) this.controls.step.disabled = false;
295234 if (this.controls.stop) this.controls.stop.disabled = true;
296235 };
297236
298 this.terminate = function(e) {
237 this.performStop = function() {
299238 this.stop();
300 this.state = STOPPED;
301 if (this.controls.start) this.controls.start.disabled = true;
302 if (this.controls.step) this.controls.step.disabled = true;
303 if (this.controls.stop) this.controls.stop.disabled = true;
239 this.state = PAUSED;
304240 };
305241
306242 this.stop = function() {
310246 this.intervalId = undefined;
311247 };
312248
313 this.click_reset = function(e) {
314 this.click_stop();
315 this.load(this.source.value);
249 /******************
250 * action: Reset
251 */
252 this.clickReset = function(e) {
253 this.clickStop();
254 this.performReset();
316255 if (this.controls.start) this.controls.start.disabled = false;
317256 if (this.controls.step) this.controls.step.disabled = false;
318257 if (this.controls.stop) this.controls.stop.disabled = true;
319258 };
320259
321 /*
322 * Override this to change how the delay is acquired from the 'speed'
323 * element.
324 */
325 this.setDelayFrom = function(elem) {
326 this.delay = elem.max - elem.value;
260 this.performReset = function(state) {
261 if (state !== undefined) {
262 this.setResetState(state);
263 }
264 this.reset(this.resetState);
265 };
266
267 this.reset = function(state) {
268 throw new Error("reset() NotImplementedError");
269 };
270
271 this.setResetState = function(state) {
272 this.resetState = state;
327273 };
328274 };
0 /*
1 * This file is part of yoob.js version 0.13
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 textarea.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, fun, def) {
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 if (fun) {
163 select.onchange = function(e) {
164 fun(optionsArray[select.selectedIndex][0]);
165 };
166 }
167
168 if (def) {
169 var i = 0;
170 var opt = select.options[i];
171 while (opt) {
172 if (opt.value === def) {
173 select.selectedIndex = i;
174 if (fun) fun(def);
175 break;
176 }
177 i++;
178 opt = select.options[i];
179 }
180 }
181
182 container.appendChild(select);
183 return select;
184 };
185
186 var SliderPlusTextInput = function() {
187 this.init = function(cfg) {
188 this.slider = cfg.slider;
189 this.textInput = cfg.textInput;
190 this.callback = cfg.callback;
191 return this;
192 };
193
194 this.set = function(value) {
195 this.slider.value = "" + value;
196 this.textInput.value = "" + value;
197 this.callback(value);
198 };
199 };
200
201 yoob.makeSliderPlusTextInput = function(container, label, min_, max_, size, value, fun) {
202 yoob.makeSpan(container, label);
203 var slider = yoob.makeSlider(container, min_, max_, value);
204 var s = "" + value;
205 var textInput = yoob.makeTextInput(container, size, s);
206 slider.onchange = function(e) {
207 textInput.value = slider.value;
208 fun(parseInt(slider.value, 10));
209 };
210 textInput.onchange = function(e) {
211 var v = parseInt(textInput.value, 10);
212 if (!isNaN(v)) {
213 slider.value = "" + v;
214 fun(v);
215 }
216 };
217 return new SliderPlusTextInput().init({
218 'slider': slider,
219 'textInput': textInput,
220 'callback': fun
221 });
222 };
223
224 var RangeControl = function() {
225 this.init = function(cfg) {
226 this.slider = cfg.slider;
227 this.textInput = cfg.textInput;
228 this.callback = cfg.callback;
229 this.incButton = cfg.incButton;
230 this.decButton = cfg.decButton;
231 return this;
232 };
233
234 this.set = function(value) {
235 this.slider.value = "" + value;
236 this.textInput.value = "" + value;
237 this.callback(value);
238 };
239 };
240
241 yoob.makeRangeControl = function(container, config) {
242 var label = config.label;
243 var min_ = config['min'];
244 var max_ = config['max'];
245 var value = config.value || min_;
246 var callback = config.callback || function(v) {};
247 var textInputSize = config.textInputSize || 5;
248 var withButtons = config.withButtons === false ? false : true;
249
250 yoob.makeSpan(container, label);
251 var slider = yoob.makeSlider(container, min_, max_, value);
252 var s = "" + value;
253 var textInput = yoob.makeTextInput(container, textInputSize, s);
254 slider.onchange = function(e) {
255 textInput.value = slider.value;
256 callback(parseInt(slider.value, 10));
257 };
258 textInput.onchange = function(e) {
259 var v = parseInt(textInput.value, 10);
260 if (!isNaN(v)) {
261 slider.value = "" + v;
262 callback(v);
263 }
264 };
265 var incButton;
266 var decButton;
267 if (withButtons) {
268 decButton = yoob.makeButton(container, "-", function() {
269 var v = parseInt(textInput.value, 10);
270 if ((!isNaN(v)) && v > min_) {
271 v--;
272 textInput.value = "" + v;
273 slider.value = "" + v;
274 callback(v);
275 }
276 });
277 incButton = yoob.makeButton(container, "+", function() {
278 var v = parseInt(textInput.value, 10);
279 if ((!isNaN(v)) && v < max_) {
280 v++;
281 textInput.value = "" + v;
282 slider.value = "" + v;
283 callback(v);
284 }
285 });
286 }
287 return new RangeControl().init({
288 'slider': slider,
289 'incButton': incButton,
290 'decButton': decButton,
291 'textInput': textInput,
292 'callback': callback
293 });
294 };
295
296 yoob.makeSVG = function(container) {
297 var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
298 /* <svg viewBox = "0 0 200 200" version = "1.1"> */
299 container.appendChild(svg);
300 return svg;
301 };
302
303 yoob.makeSVGElem = function(svg, tag, cfg) {
304 var elem = document.createElementNS(svg.namespaceURI, tag);
305 Object.keys(cfg).forEach(function(key) {
306 elem.setAttribute(key, cfg[key]);
307 });
308 svg.appendChild(elem);
309 return elem;
310 };
0 /*
1 * This file is part of yoob.js version 0.11
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 yoob.PlayfieldHTMLView = function() {
12 this.init = function(cfg) {
13 this.pf = cfg.playfield;
14 this.element = cfg.element;
15 return this;
16 };
17
18 /*** Chainable setters ***/
19
20 this.setPlayfield = function(pf) {
21 this.pf = pf;
22 return this;
23 };
24
25 this.setElement = function(element) {
26 this.element = element;
27 return this;
28 };
29
30 /*
31 * For compatibility with PlayfieldCanvasView. Sets the font size.
32 */
33 this.setCellDimensions = function(cellWidth, cellHeight) {
34 this.element.style.fontSize = cellHeight + "px";
35 return this;
36 };
37
38 /*
39 * Override to convert Playfield values to HTML.
40 */
41 this.render = function(value) {
42 if (value === undefined) return ' ';
43 return value;
44 };
45
46 /*
47 * Override if you like.
48 */
49 this.wrapCursorText = function(cursor, text) {
50 var fillStyle = this.cursorFillStyle || "#50ff50";
51 return '<span style="background: ' + fillStyle + '">' +
52 text + '</span>';
53 };
54
55 /*
56 * Render the playfield, as HTML, on the DOM element.
57 */
58 this.draw = function() {
59 var text = "";
60 var cursors = this.pf.cursors;
61 var lowerY = this.pf.getLowerY();
62 var upperY = this.pf.getUpperY();
63 var lowerX = this.pf.getLowerX();
64 var upperX = this.pf.getUpperX();
65 for (var y = lowerY; y <= upperY; y++) {
66 var row = "";
67 for (var x = lowerX; x <= upperX; x++) {
68 var rendered = this.render(this.pf.get(x, y));
69 for (var i = 0; i < cursors.length; i++) {
70 if (cursors[i].x === x && cursors[i].y === y) {
71 rendered = this.wrapCursorText(cursors[i], rendered);
72 }
73 }
74 row += rendered;
75 }
76 text += row + "\n";
77 }
78 this.element.innerHTML = text;
79 };
80
81 };
00 /*
1 * This file is part of yoob.js version 0.6
1 * This file is part of yoob.js version 0.13
22 * Available from https://github.com/catseye/yoob.js/
33 * This file is in the public domain. See http://unlicense.org/ for details.
44 */
88 * A two-dimensional Cartesian grid of values.
99 */
1010 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;
11 this.init = function(cfg) {
12 cfg = cfg || {};
13 this._default = cfg.defaultValue;
14 this.cursors = cfg.cursors || [];
15 this.clear();
16 return this;
17 };
18
19 /*** Chainable setters ***/
1720
1821 /*
1922 * Set the default value for this Playfield. This
2427 this._default = v;
2528 return this;
2629 };
30
31 /*
32 * Set the list of cursors to the given list of yoob.Cursor (or compatible)
33 * objects.
34 */
35 this.setCursors = function(cursors) {
36 this.cursors = cursors;
37 return this;
38 };
39
40 /*** Accessors, etc. ***/
2741
2842 /*
2943 * Obtain the value at (x, y). The default value will
4458 this.put = function(x, y, value) {
4559 var key = x+','+y;
4660 if (value === undefined || value === this._default) {
61 // NOTE: this does not recalculate the bounds, nor
62 // will it set the bounds back to 'undefined'
63 // if the playfield is now empty.
4764 delete this._store[key];
4865 return;
4966 }
101118 this.minY = undefined;
102119 this.maxX = undefined;
103120 this.maxY = undefined;
121 return this;
104122 };
105123
106124 /*
114132 this.put(x, y, this.get(x, y - dy));
115133 }
116134 }
117 } else { alert("scrollRectangleY(" + dy + ") notImplemented"); }
135 } else {
136 throw new Error("scrollRectangleY(" + dy + ") notImplemented");
137 }
118138 };
119139
120140 this.clearRectangle = function(minX, minY, maxX, maxY) {
192212 * fun is a callback which takes three parameters:
193213 * x, y, and value. If this callback returns a value,
194214 * it is written into the Playfield at that position.
195 * This function ensures a particular order.
215 * This function ensures a particular order. For efficiency,
216 * This function knows about the structure of the backing
217 * store, so if you override .get() or .put() in a subclass,
218 * you should also override this.
196219 */
197220 this.foreach = function(fun) {
198221 for (var y = this.minY; y <= this.maxY; y++) {
202225 if (value === undefined)
203226 continue;
204227 var result = fun(x, y, value);
228 // TODO: Playfield.UNDEFINED vs. undefined meaning "no change"?
205229 if (result !== undefined) {
206 if (result === ' ') {
207 result = undefined;
208 }
230 this.put(x, y, result);
231 }
232 }
233 }
234 };
235
236 this.foreachVonNeumannNeighbour = function(x, y, fun) {
237 for (var dx = -1; dx <= 1; dx++) {
238 for (var dy = -1; dy <= 1; dy++) {
239 if (dx === 0 && dy === 0)
240 continue;
241 var value = this.get(x + dx, y + dy);
242 if (value === undefined)
243 continue;
244 var result = fun(x, y, value);
245 // TODO: Playfield.UNDEFINED vs. undefined meaning "no change"?
246 if (result !== undefined) {
209247 this.put(x, y, result);
210248 }
211249 }
235273 if (maxDy === undefined) maxDy = 0;
236274 for (var y = this.minY + minDy; y <= this.maxY + maxDy; y++) {
237275 for (var x = this.minX + minDx; x <= this.maxX + maxDx; x++) {
238 destPf.putDirty(x, y, fun(pf, x, y));
276 destPf.putDirty(x, y, fun(this, x, y));
239277 }
240278 }
241279 destPf.recalculateBounds();
282320 return this.maxY - this.minY + 1;
283321 }
284322 };
323
324 /*
325 * Return the requested bounds of the occupied portion of the playfield.
326 * "Occupation" in this sense includes all cursors.
327 *
328 * These may return 'undefined' if there is nothing in the playfield.
329 *
330 * Override these if you want to draw some portion of the
331 * playfield which is not the whole playfield.
332 */
333 this.getLowerX = function() {
334 var minX = this.getMinX();
335 for (var i = 0; i < this.cursors.length; i++) {
336 if (minX === undefined || this.cursors[i].x < minX) {
337 minX = this.cursors[i].x;
338 }
339 }
340 return minX;
341 };
342 this.getUpperX = function() {
343 var maxX = this.getMaxX();
344 for (var i = 0; i < this.cursors.length; i++) {
345 if (maxX === undefined || this.cursors[i].x > maxX) {
346 maxX = this.cursors[i].x;
347 }
348 }
349 return maxX;
350 };
351 this.getLowerY = function() {
352 var minY = this.getMinY();
353 for (var i = 0; i < this.cursors.length; i++) {
354 if (minY === undefined || this.cursors[i].y < minY) {
355 minY = this.cursors[i].y;
356 }
357 }
358 return minY;
359 };
360 this.getUpperY = function() {
361 var maxY = this.getMaxY();
362 for (var i = 0; i < this.cursors.length; i++) {
363 if (maxY === undefined || this.cursors[i].y > maxY) {
364 maxY = this.cursors[i].y;
365 }
366 }
367 return maxY;
368 };
369
370 /*
371 * Returns the number of occupied cells in the x direction.
372 * "Occupation" in this sense includes all cursors.
373 */
374 this.getCursoredExtentX = function() {
375 if (this.getLowerX() === undefined || this.getUpperX() === undefined) {
376 return 0;
377 } else {
378 return this.getUpperX() - this.getLowerX() + 1;
379 }
380 };
381
382 /*
383 * Returns the number of occupied cells in the y direction.
384 * "Occupation" in this sense includes all cursors.
385 */
386 this.getCursoredExtentY = function() {
387 if (this.getLowerY() === undefined || this.getUpperY() === undefined) {
388 return 0;
389 } else {
390 return this.getUpperY() - this.getLowerY() + 1;
391 }
392 };
393
394 /*
395 * Cursored read/write interface
396 */
397 this.read = function(index) {
398 var cursor = this.cursors[index || 0];
399 return this.get(cursor.getX(), cursor.getY());
400 };
401
402 this.write = function(value, index) {
403 var cursor = this.cursors[index || 0];
404 this.put(cursor.getX(), cursor.getY(), value);
405 return this;
406 };
285407 };
00 /*
1 * This file is part of yoob.js version 0.6
1 * This file is part of yoob.js version 0.13
22 * Available from https://github.com/catseye/yoob.js/
33 * This file is in the public domain. See http://unlicense.org/ for details.
44 */
2121 * will cause the .select() method of this manager to be called.
2222 * it will also call .onselect if that method is present.
2323 *
24 * controller: a yoob.Controller (or compatible object) that will
25 * be informed of the selection, if no callback was supplied
26 * when the item was added.
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.
2727 */
2828 this.init = function(cfg) {
2929 this.selectElem = cfg.selectElem;
30 this.controller = cfg.controller || null;
30 if (cfg.setPreset) {
31 this.setPreset = cfg.setPreset;
32 }
3133 this.clear();
3234 var $this = this;
3335 this.selectElem.onchange = function() {
5153 /*
5254 * Adds a preset to this PresetManager. When it is selected,
5355 * the given callback will be called, being passed the id as the
54 * first argument. If no callback is provided, a default callback,
55 * which loads the contents of the element with the specified id
56 * into the configured yoob.Controller, will be used.
56 * first argument. If no callback is provided, the default callback,
57 * configured with setPreset in the init() configuration, will be used.
5758 */
5859 this.add = function(id, callback) {
5960 var opt = document.createElement("option");
6162 opt.value = id;
6263 this.selectElem.options.add(opt);
6364 var $this = this;
64 this.reactTo[id] = callback || function(id) {
65 $this.controller.click_stop(); // in case it is currently running
66 $this.controller.loadSourceFromHTML(
67 document.getElementById(id).innerHTML
68 );
69 };
65 this.reactTo[id] = callback || this.setPreset;
7066 return this;
67 };
68
69 this.setPreset = function(id) {
70 throw new Error("No default setPreset callback configured");
7171 };
7272
7373 /*
119119 }
120120 return this;
121121 };
122
123 /*
124 * When called with a yoob.SourceManager and an array of
125 * 2-element arrays of a name and a source text, this preset
126 * manager will be populated with each source text as a
127 * named preset. A callback to load the source text with
128 * the SourceManager will be automatically supplied.
129 */
130 this.populateFromPairs = function(sourceManager, pairs) {
131 function makeCallback(sourceText) {
132 return function(id) {
133 sourceManager.loadSource(sourceText);
134 }
135 }
136
137 for (var i = 0; i < pairs.length; i++) {
138 this.add(pairs[i][0], makeCallback(pairs[i][1]));
139 }
140 this.select(pairs[0][0]);
141 return this;
142 };
122143 };
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 exampleConfigurations = [
1 [
2 "test.brak",
3 "000000000000000000 <*<*>*>*<*<*>*>*\n-----------------d-i---------------\n"
4 ],
5 [
6 "test2.brak",
7 " <*\n000000000000000000000000000000000000000000000000000000000 *[---]\n--------------------------------------------------------d-i- --\n"
8 ],
9 [
10 "test3.brak",
11 " *\n <<*[--]*\n000000000000000000 *[----- --]\n-----------------d-i-- --------\n\n"
12 ]
13 ];