git @ Cat's Eye Technologies Wierd / d83e13a
Changed my mind a bit. *non-trivial*organizational*refactor* --HG-- rename : impl/wierd.js/demo/wierd.html => dialect/wierd-jnc/impl/wierd-jnc.js/demo/wierd-jnc.html rename : impl/wierd.js/src/wierd.js => dialect/wierd-jnc/impl/wierd-jnc.js/src/wierd-jnc.js rename : impl/wierd.js/src/yoob/controller.js => dialect/wierd-jnc/impl/wierd-jnc.js/src/yoob/controller.js rename : impl/wierd.js/src/yoob/cursor.js => dialect/wierd-jnc/impl/wierd-jnc.js/src/yoob/cursor.js rename : impl/wierd.js/src/yoob/element-factory.js => dialect/wierd-jnc/impl/wierd-jnc.js/src/yoob/element-factory.js rename : impl/wierd.js/src/yoob/playfield-canvas-view.js => dialect/wierd-jnc/impl/wierd-jnc.js/src/yoob/playfield-canvas-view.js rename : impl/wierd.js/src/yoob/playfield-html-view.js => dialect/wierd-jnc/impl/wierd-jnc.js/src/yoob/playfield-html-view.js rename : impl/wierd.js/src/yoob/playfield.js => dialect/wierd-jnc/impl/wierd-jnc.js/src/yoob/playfield.js rename : impl/wierd.js/src/yoob/preset-manager.js => dialect/wierd-jnc/impl/wierd-jnc.js/src/yoob/preset-manager.js rename : impl/wierd.js/src/yoob/source-manager.js => dialect/wierd-jnc/impl/wierd-jnc.js/src/yoob/source-manager.js rename : impl/wierd.js/src/yoob/stack.js => dialect/wierd-jnc/impl/wierd-jnc.js/src/yoob/stack.js rename : start.sh => dialect/wierd-jnc/impl/wierd-jnc.js/start.sh Chris Pressey 7 years ago
27 changed file(s) with 2010 addition(s) and 1972 deletion(s). Raw diff Collapse all Expand all
8080 past, but anything else that distinguishes them by the name of their author
8181 would suffice. (Therefore there are subdirectories `dialect/wierd-jnc` and
8282 `dialect/wierd-mvh` in this repository, and each of *those* contains the
83 standard `src`, `doc`, and `eg` subdirectories.)
84
85 Meanwhile, Chris Pressey's Javascript interpreter hopes to implement *both*
86 Wierd dialects. (Therefore it is an implementation in the `impl` directory
87 in the *root* of this repository.)
83 standard `src`, `doc`, and `eg` subdirectories. And in addition, because
84 Chris's interpreter implements John's Wierd, it is in the `impl` directory
85 of `dialect/wierd-jnc`.)
8886
8987 And in light of all this, it might also be acceptable to consider Wierd to be
9088 a language *family* rather than a language. I'm not yet decided on this point.
89
90 ### Pull Requests ###
91
92 You are perfectly welcome to open pull requests on this repository, but please
93 observe the layout described above:
94
95 * implementations of John's Wierd go into `dialect/wierd-jnc/impl`
96 * example programs in John's Wierd go into `dialect/wierd-jnc/eg`
97 * implementations of Milo's Wierd go into `dialect/wierd-mvh/impl`
98 * example programs in Milo's Wierd go into `dialect/wierd-mvh/eg`
99 * any other dialects of Wierd go into `dialect/your-dialect-name`
100
101 In light of the following section, I would also ask that you provide some
102 license information regarding any sources you submit. Open-source licensing
103 would definitely be preferable.
91104
92105 License
93106 -------
105118 redistributable, unmodified and for non-commercial purposes; however, I am
106119 not a lawyer, your mileage may vary, caveat emptor, etc. etc.
107120
108 In stark (I hope) contrast to this, Chris's implementation, `wierd.js`, is
121 In stark (I hope) contrast to this, Chris's implementation, `wierd-jnc.js`, is
109122 placed into the public domain (see the file `UNLICENSE` in its directory.)
0 This is free and unencumbered software released into the public domain.
1
2 Anyone is free to copy, modify, publish, use, compile, sell, or
3 distribute this software, either in source code form or as a compiled
4 binary, for any purpose, commercial or non-commercial, and by any
5 means.
6
7 In jurisdictions that recognize copyright laws, the author or authors
8 of this software dedicate any and all copyright interest in the
9 software to the public domain. We make this dedication for the benefit
10 of the public at large and to the detriment of our heirs and
11 successors. We intend this dedication to be an overt act of
12 relinquishment in perpetuity of all present and future rights to this
13 software under copyright law.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
19 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
20 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
21 OTHER DEALINGS IN THE SOFTWARE.
22
23 For more information, please refer to <http://unlicense.org/>
0 <!DOCTYPE html>
1 <head>
2 <meta charset="utf-8">
3 <title>wierd-jnc.js</title>
4 </head>
5 <body>
6
7 <h1>wierd-jnc.js</h1>
8
9 <div id="control_panel"></div>
10 <div id="container"></div>
11
12 </body>
13 <script src="../src/wierd-jnc.js"></script>
14 <script>
15 launch('../src/yoob/', document.getElementById('container'), {
16 controlPanel: document.getElementById('control_panel')
17 });
18 </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 "cursor.js",
12 "stack.js",
13 "element-factory.js",
14 "preset-manager.js",
15 "source-manager.js"
16 ];
17 var loaded = 0;
18 for (var i = 0; i < deps.length; i++) {
19 var elem = document.createElement('script');
20 elem.src = prefix + deps[i];
21 elem.onload = function() {
22 if (++loaded != deps.length) return;
23
24 var sourceRoot = config.sourceRoot || '../eg/';
25
26 var controlPanel = config.controlPanel || container;
27
28 /* --- state animation display --- */
29
30 var viewPort = yoob.makeDiv(container);
31
32 var programDisplay = yoob.makePre(viewPort);
33 programDisplay.style.display = 'inline-block';
34
35 var statePanel = yoob.makeDiv(viewPort);
36 statePanel.style.display = 'inline-block';
37 statePanel.style.verticalAlign = 'top';
38 yoob.makeSpan(statePanel, "Stack:");
39 var stackDisplay = yoob.makeCanvas(statePanel, 400, 100);
40 yoob.makeLineBreak(statePanel);
41 yoob.makeSpan(statePanel, "Input:");
42 var inputElem = yoob.makeTextInput(statePanel);
43 yoob.makeLineBreak(statePanel);
44 yoob.makeSpan(statePanel, "Output:");
45 var outputElem = yoob.makeDiv(statePanel);
46 yoob.makeLineBreak(statePanel);
47
48 var editor = yoob.makeTextArea(container, 160, 50);
49 editor.style.fontSize = "6px";
50
51 /* --- controller --- */
52
53 var proto = new yoob.Controller();
54 WierdController.prototype = proto;
55 var c = new WierdController(proto);
56 var v = new yoob.PlayfieldHTMLView;
57 v.init(null, programDisplay);
58 v.setCellDimensions(undefined, 6);
59 c.init({
60 panelContainer: controlPanel,
61 playfieldView: v,
62 stackCanvas: stackDisplay,
63 inputElem: inputElem,
64 outputElem: outputElem
65 });
66
67 /* --- source manager --- */
68
69 var sm = (new yoob.SourceManager()).init({
70 'editor': editor,
71 'hideDuringEdit': [viewPort],
72 'disableDuringEdit': [c.panel],
73 'storageKey': 'wierd.js',
74 'panelContainer': controlPanel,
75 'onDone': function() {
76 c.performReset(this.getEditorText());
77 }
78 });
79
80 /* --- presets --- */
81
82 var presetSelect = yoob.makeSelect(c.panel, "Preset:", []);
83
84 var p = new yoob.PresetManager();
85 p.init({
86 'selectElem': presetSelect,
87 'setPreset': function(n) {
88 c.clickStop(); // in case it is currently running
89 sm.loadSourceFromURL(sourceRoot + n);
90 sm.onDone();
91 }
92 });
93 p.add('hello.w');
94 p.select('hello.w');
95 };
96 document.body.appendChild(elem);
97 }
98 }
99
100 function WierdController(proto) {
101 var intervalId;
102
103 var pf;
104 var ip;
105 var stack;
106 var output;
107
108 this.init = function(cfg) {
109 proto.init.apply(this, [cfg]);
110
111 pf = new yoob.Playfield();
112 ip = new yoob.Cursor().init(1, 1, 1, 1);
113 stack = new yoob.Stack();
114 this.view = cfg.playfieldView.setPlayfield(pf).setCursors([ip]);
115 output = cfg.outputElem;
116 this.stackCanvas = cfg.stackCanvas;
117 this.inputElem = cfg.inputElem;
118 };
119
120 this.step = function() {
121 if (this.tryAhead(0, true)) { // NOP
122 } else if (this.tryAhead(45, true)) {
123 stack.push(1);
124 //console.log("[PUSH1]");
125 } else if (this.tryAhead(315, true)) {
126 /* hello.w relies on the fact that
127 if there are <2 elements on the stack, it's a NOP */
128 if (stack.size() >= 2) {
129 var a = stack.pop();
130 var b = stack.pop();
131 stack.push(b - a);
132 //console.log("[SUBT]");
133 }
134 } else if (this.tryAhead(90, false)) {
135 var a = 0;
136 if (stack.size() > 0) a = stack.pop();
137 if (a === 0) {
138 ip.rotateDegrees(90);
139 //console.log("[THEN]");
140 } else {
141 ip.rotateDegrees(180);
142 //console.log("[ELSE]");
143 }
144 ip.advance();
145 } else if (this.tryAhead(270, false)) {
146 var a = 0;
147 if (stack.size() > 0) a = stack.pop();
148 if (a === 0) {
149 ip.rotateDegrees(270);
150 //console.log("[THEN]");
151 } else {
152 ip.rotateDegrees(180);
153 // console.log("[ELSE]");
154 }
155 ip.advance();
156 } else if (this.tryAhead(135, true)) {
157 var a = stack.pop();
158 if (a !== 0) { /* spec says 0 is GET. in JC's interp, 0 is PUT. */
159 var x = stack.pop();
160 var y = stack.pop();
161 var e = (pf.get(x, y) || ' ').charCodeAt(0);
162 stack.push(e);
163 //console.log("[GET]");
164 } else {
165 var x = stack.pop();
166 var y = stack.pop();
167 var c = String.fromCharCode(stack.pop());
168 pf.put(x, y, c);
169 //console.log("[PUT]");
170 }
171 } else if (this.tryAhead(225, true)) {
172 var a = stack.pop();
173 if (a === 0) {
174 var c = this.inputElem.value;
175 if (c === '') {
176 return 'block';
177 }
178 stack.push(c.charCodeAt(0));
179 this.inputElem.value = c.substr(1);
180 //console.log("[IN]");
181 } else {
182 var a = stack.pop();
183 output.innerHTML += String.fromCharCode(a);
184 //console.log("[OUT]");
185 }
186 } else {
187 var lookahead = ip.clone();
188 lookahead.advance();
189 lookahead.advance();
190 var there = pf.get(lookahead.x, lookahead.y);
191 if (there != ' ' && there != undefined) {
192 ip.advance();
193 ip.advance();
194 //console.log("[SPRK]");
195 } else {
196 return 'stop';
197 }
198 }
199
200 this.view.draw();
201 stack.drawCanvas(this.stackCanvas, 10, 10);
202 };
203
204 this.reset = function(text) {
205 pf.clear();
206 stack = new yoob.Stack();
207 pf.load(1, 1, text);
208 ip.x = 1;
209 ip.y = 1;
210 ip.dx = 1;
211 ip.dy = 1;
212 this.view.draw();
213 stack.drawCanvas(this.stackCanvas, 10, 10);
214 };
215
216 this.tryAhead = function(degrees, advance) {
217 var lookahead = ip.clone();
218 lookahead.rotateDegrees(degrees);
219 lookahead.advance();
220 var there = pf.get(lookahead.x, lookahead.y);
221 //console.log(lookahead.x, lookahead.y, there);
222 if (there != ' ' && there != undefined) {
223 if (advance) {
224 ip.rotateDegrees(degrees);
225 ip.advance();
226 }
227 return true;
228 }
229 return false;
230 };
231 };
0 /*
1 * This file is part of yoob.js version 0.8-PRE
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.7
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 representing a position and direction in some space. The space
9 * may be one-dimensional (a yoob.Tape, or a string representing a program
10 * source) or a two-dimensional Cartesian space (such as a yoob.Playfield.)
11 * A direction vector accompanies the position, so the cursor can "know which
12 * way it's headed", but this facility need not be used.
13 *
14 * A cursor contains a built-in simple view, i.e. it knows how to render
15 * itself on a canvas (drawContext method) or in the midst of HTML text
16 * (wrapText method). These methods are used by the view classes (playfield,
17 * tape, source, etc.) The default methods draw basic block cursors in the
18 * colour given by the fillStyle attribute, if present, or a light green if
19 * it is not defined.
20 */
21 yoob.Cursor = function() {
22 this.init = function(x, y, dx, dy) {
23 this.x = x;
24 this.y = y;
25 this.dx = dx;
26 this.dy = dy;
27 return this;
28 };
29
30 this.clone = function() {
31 return new yoob.Cursor().init(this.x, this.y, this.dx, this.dy);
32 };
33
34 this.getX = function() {
35 return this.x;
36 };
37
38 this.getY = function() {
39 return this.y;
40 };
41
42 this.setX = function(x) {
43 this.x = x;
44 };
45
46 this.setY = function(y) {
47 this.y = y;
48 };
49
50 this.isHeaded = function(dx, dy) {
51 return this.dx === dx && this.dy === dy;
52 };
53
54 this.advance = function() {
55 this.x += this.dx;
56 this.y += this.dy;
57 };
58
59 this.rotateClockwise = function() {
60 if (this.dx === 0 && this.dy === -1) {
61 this.dx = 1; this.dy = -1;
62 } else if (this.dx === 1 && this.dy === -1) {
63 this.dx = 1; this.dy = 0;
64 } else if (this.dx === 1 && this.dy === 0) {
65 this.dx = 1; this.dy = 1;
66 } else if (this.dx === 1 && this.dy === 1) {
67 this.dx = 0; this.dy = 1;
68 } else if (this.dx === 0 && this.dy === 1) {
69 this.dx = -1; this.dy = 1;
70 } else if (this.dx === -1 && this.dy === 1) {
71 this.dx = -1; this.dy = 0;
72 } else if (this.dx === -1 && this.dy === 0) {
73 this.dx = -1; this.dy = -1;
74 } else if (this.dx === -1 && this.dy === -1) {
75 this.dx = 0; this.dy = -1;
76 }
77 };
78
79 this.rotateCounterclockwise = function() {
80 if (this.dx === 0 && this.dy === -1) {
81 this.dx = -1; this.dy = -1;
82 } else if (this.dx === -1 && this.dy === -1) {
83 this.dx = -1; this.dy = 0;
84 } else if (this.dx === -1 && this.dy === 0) {
85 this.dx = -1; this.dy = 1;
86 } else if (this.dx === -1 && this.dy === 1) {
87 this.dx = 0; this.dy = 1;
88 } else if (this.dx === 0 && this.dy === 1) {
89 this.dx = 1; this.dy = 1;
90 } else if (this.dx === 1 && this.dy === 1) {
91 this.dx = 1; this.dy = 0;
92 } else if (this.dx === 1 && this.dy === 0) {
93 this.dx = 1; this.dy = -1;
94 } else if (this.dx === 1 && this.dy === -1) {
95 this.dx = 0; this.dy = -1;
96 }
97 };
98
99 this.rotateDegrees = function(degrees) {
100 while (degrees > 0) {
101 this.rotateCounterclockwise();
102 degrees -= 45;
103 }
104 };
105
106 /* from yoob.TapeHead; may go away or change slightly */
107 this.move = function(delta) {
108 this.x += delta;
109 };
110
111 this.moveLeft = function(amount) {
112 if (amount === undefined) amount = 1;
113 this.x -= amount;
114 };
115
116 this.moveRight = function(amount) {
117 if (amount === undefined) amount = 1;
118 this.x += amount;
119 };
120
121 this.read = function() {
122 if (!this.tape) return undefined;
123 return this.tape.get(this.x);
124 };
125
126 this.write = function(value) {
127 if (!this.tape) return;
128 this.tape.put(this.x, value);
129 };
130
131 /*
132 * For HTML views. Override if you like.
133 */
134 this.wrapText = function(text) {
135 var fillStyle = this.fillStyle || "#50ff50";
136 return '<span style="background: ' + fillStyle + '">' +
137 text + '</span>';
138 };
139
140 /*
141 * For canvas views. Override if you like.
142 */
143 this.drawContext = function(ctx, x, y, cellWidth, cellHeight) {
144 ctx.fillStyle = this.fillStyle || "#50ff50";
145 ctx.fillRect(x, y, cellWidth, cellHeight);
146 };
147 }
0 /*
1 * This file is part of yoob.js version 0.8-PRE
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-PRE
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 on an HTML5 <canvas> element (or compatible object).
10 *
11 * TODO: don't necesarily resize canvas each time?
12 * TODO: option to stretch content rendering to fill a fixed-size canvas
13 */
14 yoob.PlayfieldCanvasView = function() {
15 this.init = function(pf, canvas) {
16 this.pf = pf;
17 this.canvas = canvas;
18 this.ctx = canvas.getContext('2d');
19 this.cursors = [];
20 this.fixedPosition = false;
21 this.fixedSizeCanvas = false;
22 this.drawCursorsFirst = true;
23 this.setCellDimensions(8, 8);
24 return this;
25 };
26
27 /*** Chainable setters ***/
28
29 /*
30 * Set the list of cursors to the given list of yoob.Cursor (or compatible)
31 * objects.
32 */
33 this.setCursors = function(cursors) {
34 this.cursors = cursors;
35 return this;
36 };
37
38 this.setPlayfield = function(pf) {
39 this.pf = pf;
40 return this;
41 };
42
43 /*
44 * Set the displayed dimensions of every cell.
45 * cellWidth and cellHeight are canvas units of measure for each cell.
46 * If cellWidth is undefined, the width of a character in the monospace
47 * font of cellHeight pixels is used.
48 */
49 this.setCellDimensions = function(cellWidth, cellHeight) {
50 this.ctx.textBaseline = "top";
51 this.ctx.font = cellHeight + "px monospace";
52
53 if (cellWidth === undefined) {
54 cellWidth = this.ctx.measureText("@").width;
55 }
56
57 this.cellWidth = cellWidth;
58 this.cellHeight = cellHeight;
59 return this;
60 };
61
62 /*
63 * Return the requested bounds of the occupied portion of the playfield.
64 * "Occupation" in this sense includes all cursors.
65 *
66 * These may return 'undefined' if there is nothing in the playfield.
67 *
68 * Override these if you want to draw some portion of the
69 * playfield which is not the whole playfield.
70 */
71 this.getLowerX = function() {
72 var minX = this.pf.getMinX();
73 for (var i = 0; i < this.cursors.length; i++) {
74 if (minX === undefined || this.cursors[i].x < minX) {
75 minX = this.cursors[i].x;
76 }
77 }
78 return minX;
79 };
80 this.getUpperX = function() {
81 var maxX = this.pf.getMaxX();
82 for (var i = 0; i < this.cursors.length; i++) {
83 if (maxX === undefined || this.cursors[i].x > maxX) {
84 maxX = this.cursors[i].x;
85 }
86 }
87 return maxX;
88 };
89 this.getLowerY = function() {
90 var minY = this.pf.getMinY();
91 for (var i = 0; i < this.cursors.length; i++) {
92 if (minY === undefined || this.cursors[i].y < minY) {
93 minY = this.cursors[i].y;
94 }
95 }
96 return minY;
97 };
98 this.getUpperY = function() {
99 var maxY = this.pf.getMaxY();
100 for (var i = 0; i < this.cursors.length; i++) {
101 if (maxY === undefined || this.cursors[i].y > maxY) {
102 maxY = this.cursors[i].y;
103 }
104 }
105 return maxY;
106 };
107
108 /*
109 * Returns the number of occupied cells in the x direction.
110 * "Occupation" in this sense includes all cursors.
111 */
112 this.getExtentX = function() {
113 if (this.getLowerX() === undefined || this.getUpperX() === undefined) {
114 return 0;
115 } else {
116 return this.getUpperX() - this.getLowerX() + 1;
117 }
118 };
119
120 /*
121 * Returns the number of occupied cells in the y direction.
122 * "Occupation" in this sense includes all cursors.
123 */
124 this.getExtentY = function() {
125 if (this.getLowerY() === undefined || this.getUpperY() === undefined) {
126 return 0;
127 } else {
128 return this.getUpperY() - this.getLowerY() + 1;
129 }
130 };
131
132 /*
133 * Draws cells of the Playfield in a drawing context.
134 * cellWidth and cellHeight are canvas units of measure.
135 *
136 * The default implementation tries to call a .draw() method on the cell's
137 * value, if one exists, and just renders it as text, in black, if not.
138 *
139 * Override if you wish to draw elements in some other way.
140 */
141 this.drawCell = function(ctx, value, playfieldX, playfieldY,
142 canvasX, canvasY, cellWidth, cellHeight) {
143 if (value.draw !== undefined) {
144 value.draw(ctx, playfieldX, playfieldY, canvasX, canvasY,
145 cellWidth, cellHeight);
146 } else {
147 ctx.fillStyle = "black";
148 ctx.fillText(value.toString(), canvasX, canvasY);
149 }
150 };
151
152 /*
153 * Draws the Playfield in a drawing context.
154 * cellWidth and cellHeight are canvas units of measure for each cell.
155 * offsetX and offsetY are canvas units of measure for the top-left
156 * of the entire playfield.
157 */
158 this.drawContext = function(ctx, offsetX, offsetY, cellWidth, cellHeight) {
159 var self = this;
160 this.pf.foreach(function (x, y, value) {
161 self.drawCell(ctx, value, x, y,
162 offsetX + x * cellWidth, offsetY + y * cellHeight,
163 cellWidth, cellHeight);
164 });
165 };
166
167 this.drawCursors = function(ctx, offsetX, offsetY, cellWidth, cellHeight) {
168 var cursors = this.cursors;
169 for (var i = 0; i < cursors.length; i++) {
170 cursors[i].drawContext(
171 ctx,
172 offsetX + cursors[i].x * cellWidth,
173 offsetY + cursors[i].y * cellHeight,
174 cellWidth, cellHeight
175 );
176 }
177 };
178
179 /*
180 * Draw the Playfield, and its set of Cursors, on the canvas element.
181 * Resizes the canvas to the needed dimensions first.
182 */
183 this.draw = function() {
184 var canvas = this.canvas;
185 var ctx = canvas.getContext('2d');
186 var cellWidth = this.cellWidth;
187 var cellHeight = this.cellHeight;
188 var cursors = this.cursors;
189
190 var width = this.getExtentX();
191 var height = this.getExtentY();
192
193 canvas.width = width * cellWidth;
194 canvas.height = height * cellHeight;
195
196 this.ctx.textBaseline = "top";
197 this.ctx.font = cellHeight + "px monospace";
198
199 var offsetX = 0;
200 var offsetY = 0;
201
202 if (!this.fixedPosition) {
203 offsetX = (this.getLowerX() || 0) * cellWidth * -1;
204 offsetY = (this.getLowerY() || 0) * cellHeight * -1;
205 }
206
207 if (this.drawCursorsFirst) {
208 this.drawCursors(ctx, offsetX, offsetY, cellWidth, cellHeight);
209 }
210
211 this.drawContext(ctx, offsetX, offsetY, cellWidth, cellHeight);
212
213 if (!this.drawCursorsFirst) {
214 this.drawCursors(ctx, offsetX, offsetY, cellWidth, cellHeight);
215 }
216 };
217
218 };
0 /*
1 * This file is part of yoob.js version 0.8-PRE
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(pf, 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-PRE
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-PRE
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 /*
1 * This file is part of yoob.js version 0.7
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 (theoretically) unbounded first-in first-out stack.
9 */
10 yoob.Stack = function() {
11 this._store = {};
12 this._top = 0;
13
14 this.pop = function() {
15 if (this._top === 0) {
16 return undefined;
17 }
18 var result = this._store[this._top];
19 this._top -= 1;
20 return result;
21 };
22
23 this.push = function(value) {
24 this._top += 1;
25 this._store[this._top] = value;
26 };
27
28 this.size = function() {
29 return this._top;
30 };
31
32 /*
33 * Iterate over every value on the stack, top to bottom.
34 * fun is a callback which takes two parameters:
35 * position (0 === top of stack) and value.
36 */
37 this.foreach = function(fun) {
38 for (var pos = this._top; pos > 0; pos--) {
39 fun(this._top - pos, this._store[pos]);
40 }
41 };
42
43 /*
44 * Draws elements of the Stack in a drawing context.
45 * x and y are canvas coordinates, and width and height
46 * are canvas units of measure.
47 * The default implementation just renders them as text,
48 * in black.
49 * Override if you wish to draw them differently.
50 */
51 this.drawElement = function(ctx, x, y, cellWidth, cellHeight, elem) {
52 ctx.fillStyle = "black";
53 ctx.fillText(elem.toString(), x, y);
54 };
55
56 /*
57 * Draws the Stack in a drawing context.
58 * cellWidth and cellHeight are canvas units of measure for each cell.
59 */
60 this.drawContext = function(ctx, cellWidth, cellHeight) {
61 var $this = this;
62 this.foreach(function (pos, elem) {
63 $this.drawElement(ctx, 0, pos * cellHeight,
64 cellWidth, cellHeight, elem);
65 });
66 };
67
68 /*
69 * Draws the Stack on a canvas element.
70 * Resizes the canvas to the needed dimensions.
71 * cellWidth and cellHeight are canvas units of measure for each cell.
72 */
73 this.drawCanvas = function(canvas, cellWidth, cellHeight) {
74 var ctx = canvas.getContext('2d');
75
76 var width = 1;
77 var height = this._top;
78
79 if (cellWidth === undefined) {
80 ctx.textBaseline = "top";
81 ctx.font = cellHeight + "px monospace";
82 cellWidth = ctx.measureText("@").width;
83 }
84
85 canvas.width = width * cellWidth;
86 canvas.height = height * cellHeight;
87
88 ctx.clearRect(0, 0, canvas.width, canvas.height);
89
90 ctx.textBaseline = "top";
91 ctx.font = cellHeight + "px monospace";
92
93 this.drawContext(ctx, cellWidth, cellHeight);
94 };
95 };
0 #!/bin/sh
1
2 THIS=`realpath $0`
3 DIR=`dirname $THIS`
4 cd $DIR
5 (sleep 1 && python -m webbrowser -t "http://127.0.0.1:8000/demo/wierd-jnc.html") &
6 python -m SimpleHTTPServer
+0
-19
impl/wierd.js/demo/wierd.html less more
0 <!DOCTYPE html>
1 <head>
2 <meta charset="utf-8">
3 <title>wierd.js</title>
4 </head>
5 <body>
6
7 <h1>Wierd</h1>
8
9 <div id="control_panel"></div>
10 <div id="container"></div>
11
12 </body>
13 <script src="../src/wierd.js"></script>
14 <script>
15 launch('../src/yoob/', document.getElementById('container'), {
16 controlPanel: document.getElementById('control_panel')
17 });
18 </script>
+0
-232
impl/wierd.js/src/wierd.js less more
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 "cursor.js",
12 "stack.js",
13 "element-factory.js",
14 "preset-manager.js",
15 "source-manager.js"
16 ];
17 var loaded = 0;
18 for (var i = 0; i < deps.length; i++) {
19 var elem = document.createElement('script');
20 elem.src = prefix + deps[i];
21 elem.onload = function() {
22 if (++loaded != deps.length) return;
23
24 var sourceRoot = config.sourceRoot || '../../../dialect/wierd-jnc/eg/';
25
26 var controlPanel = config.controlPanel || container;
27
28 /* --- state animation display --- */
29
30 var viewPort = yoob.makeDiv(container);
31
32 var programDisplay = yoob.makePre(viewPort);
33 programDisplay.style.display = 'inline-block';
34
35 var statePanel = yoob.makeDiv(viewPort);
36 statePanel.style.display = 'inline-block';
37 statePanel.style.verticalAlign = 'top';
38 yoob.makeSpan(statePanel, "Stack:");
39 var stackDisplay = yoob.makeCanvas(statePanel, 400, 100);
40 yoob.makeLineBreak(statePanel);
41 yoob.makeSpan(statePanel, "Input:");
42 var inputElem = yoob.makeTextInput(statePanel);
43 yoob.makeLineBreak(statePanel);
44 yoob.makeSpan(statePanel, "Output:");
45 var outputElem = yoob.makeDiv(statePanel);
46 yoob.makeLineBreak(statePanel);
47
48 var editor = yoob.makeTextArea(container, 160, 50);
49 editor.style.fontSize = "6px";
50
51 /* --- controller --- */
52
53 var proto = new yoob.Controller();
54 WierdController.prototype = proto;
55 var c = new WierdController(proto);
56 var v = new yoob.PlayfieldHTMLView;
57 v.init(null, programDisplay);
58 v.setCellDimensions(undefined, 6);
59 c.init({
60 panelContainer: controlPanel,
61 playfieldView: v,
62 stackCanvas: stackDisplay,
63 inputElem: inputElem,
64 outputElem: outputElem
65 });
66
67 /* --- source manager --- */
68
69 var sm = (new yoob.SourceManager()).init({
70 'editor': editor,
71 'hideDuringEdit': [viewPort],
72 'disableDuringEdit': [c.panel],
73 'storageKey': 'wierd.js',
74 'panelContainer': controlPanel,
75 'onDone': function() {
76 c.performReset(this.getEditorText());
77 }
78 });
79
80 /* --- presets --- */
81
82 var presetSelect = yoob.makeSelect(c.panel, "Preset:", []);
83
84 var p = new yoob.PresetManager();
85 p.init({
86 'selectElem': presetSelect,
87 'setPreset': function(n) {
88 c.clickStop(); // in case it is currently running
89 sm.loadSourceFromURL(sourceRoot + n);
90 sm.onDone();
91 }
92 });
93 p.add('hello.w');
94 p.select('hello.w');
95 };
96 document.body.appendChild(elem);
97 }
98 }
99
100 function WierdController(proto) {
101 var intervalId;
102
103 var pf;
104 var ip;
105 var stack;
106 var output;
107
108 this.init = function(cfg) {
109 proto.init.apply(this, [cfg]);
110
111 pf = new yoob.Playfield();
112 ip = new yoob.Cursor().init(1, 1, 1, 1);
113 stack = new yoob.Stack();
114 this.view = cfg.playfieldView.setPlayfield(pf).setCursors([ip]);
115 output = cfg.outputElem;
116 this.stackCanvas = cfg.stackCanvas;
117 this.inputElem = cfg.inputElem;
118 };
119
120 this.step = function() {
121 if (this.tryAhead(0, true)) { // NOP
122 } else if (this.tryAhead(45, true)) {
123 stack.push(1);
124 //console.log("[PUSH1]");
125 } else if (this.tryAhead(315, true)) {
126 /* hello.w relies on the fact that
127 if there are <2 elements on the stack, it's a NOP */
128 if (stack.size() >= 2) {
129 var a = stack.pop();
130 var b = stack.pop();
131 stack.push(b - a);
132 //console.log("[SUBT]");
133 }
134 } else if (this.tryAhead(90, false)) {
135 var a = 0;
136 if (stack.size() > 0) a = stack.pop();
137 if (a === 0) {
138 ip.rotateDegrees(90);
139 //console.log("[THEN]");
140 } else {
141 ip.rotateDegrees(180);
142 //console.log("[ELSE]");
143 }
144 ip.advance();
145 } else if (this.tryAhead(270, false)) {
146 var a = 0;
147 if (stack.size() > 0) a = stack.pop();
148 if (a === 0) {
149 ip.rotateDegrees(270);
150 //console.log("[THEN]");
151 } else {
152 ip.rotateDegrees(180);
153 // console.log("[ELSE]");
154 }
155 ip.advance();
156 } else if (this.tryAhead(135, true)) {
157 var a = stack.pop();
158 if (a !== 0) { /* spec says 0 is GET. in JC's interp, 0 is PUT. */
159 var x = stack.pop();
160 var y = stack.pop();
161 var e = (pf.get(x, y) || ' ').charCodeAt(0);
162 stack.push(e);
163 //console.log("[GET]");
164 } else {
165 var x = stack.pop();
166 var y = stack.pop();
167 var c = String.fromCharCode(stack.pop());
168 pf.put(x, y, c);
169 //console.log("[PUT]");
170 }
171 } else if (this.tryAhead(225, true)) {
172 var a = stack.pop();
173 if (a === 0) {
174 var c = this.inputElem.value;
175 if (c === '') {
176 return 'block';
177 }
178 stack.push(c.charCodeAt(0));
179 this.inputElem.value = c.substr(1);
180 //console.log("[IN]");
181 } else {
182 var a = stack.pop();
183 output.innerHTML += String.fromCharCode(a);
184 //console.log("[OUT]");
185 }
186 } else {
187 var lookahead = ip.clone();
188 lookahead.advance();
189 lookahead.advance();
190 var there = pf.get(lookahead.x, lookahead.y);
191 if (there != ' ' && there != undefined) {
192 ip.advance();
193 ip.advance();
194 //console.log("[SPRK]");
195 } else {
196 return 'stop';
197 }
198 }
199
200 this.view.draw();
201 stack.drawCanvas(this.stackCanvas, 10, 10);
202 };
203
204 this.reset = function(text) {
205 pf.clear();
206 stack = new yoob.Stack();
207 pf.load(1, 1, text);
208 ip.x = 1;
209 ip.y = 1;
210 ip.dx = 1;
211 ip.dy = 1;
212 this.view.draw();
213 stack.drawCanvas(this.stackCanvas, 10, 10);
214 };
215
216 this.tryAhead = function(degrees, advance) {
217 var lookahead = ip.clone();
218 lookahead.rotateDegrees(degrees);
219 lookahead.advance();
220 var there = pf.get(lookahead.x, lookahead.y);
221 //console.log(lookahead.x, lookahead.y, there);
222 if (there != ' ' && there != undefined) {
223 if (advance) {
224 ip.rotateDegrees(degrees);
225 ip.advance();
226 }
227 return true;
228 }
229 return false;
230 };
231 };
+0
-271
impl/wierd.js/src/yoob/controller.js less more
0 /*
1 * This file is part of yoob.js version 0.8-PRE
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
-148
impl/wierd.js/src/yoob/cursor.js less more
0 /*
1 * This file is part of yoob.js version 0.7
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 representing a position and direction in some space. The space
9 * may be one-dimensional (a yoob.Tape, or a string representing a program
10 * source) or a two-dimensional Cartesian space (such as a yoob.Playfield.)
11 * A direction vector accompanies the position, so the cursor can "know which
12 * way it's headed", but this facility need not be used.
13 *
14 * A cursor contains a built-in simple view, i.e. it knows how to render
15 * itself on a canvas (drawContext method) or in the midst of HTML text
16 * (wrapText method). These methods are used by the view classes (playfield,
17 * tape, source, etc.) The default methods draw basic block cursors in the
18 * colour given by the fillStyle attribute, if present, or a light green if
19 * it is not defined.
20 */
21 yoob.Cursor = function() {
22 this.init = function(x, y, dx, dy) {
23 this.x = x;
24 this.y = y;
25 this.dx = dx;
26 this.dy = dy;
27 return this;
28 };
29
30 this.clone = function() {
31 return new yoob.Cursor().init(this.x, this.y, this.dx, this.dy);
32 };
33
34 this.getX = function() {
35 return this.x;
36 };
37
38 this.getY = function() {
39 return this.y;
40 };
41
42 this.setX = function(x) {
43 this.x = x;
44 };
45
46 this.setY = function(y) {
47 this.y = y;
48 };
49
50 this.isHeaded = function(dx, dy) {
51 return this.dx === dx && this.dy === dy;
52 };
53
54 this.advance = function() {
55 this.x += this.dx;
56 this.y += this.dy;
57 };
58
59 this.rotateClockwise = function() {
60 if (this.dx === 0 && this.dy === -1) {
61 this.dx = 1; this.dy = -1;
62 } else if (this.dx === 1 && this.dy === -1) {
63 this.dx = 1; this.dy = 0;
64 } else if (this.dx === 1 && this.dy === 0) {
65 this.dx = 1; this.dy = 1;
66 } else if (this.dx === 1 && this.dy === 1) {
67 this.dx = 0; this.dy = 1;
68 } else if (this.dx === 0 && this.dy === 1) {
69 this.dx = -1; this.dy = 1;
70 } else if (this.dx === -1 && this.dy === 1) {
71 this.dx = -1; this.dy = 0;
72 } else if (this.dx === -1 && this.dy === 0) {
73 this.dx = -1; this.dy = -1;
74 } else if (this.dx === -1 && this.dy === -1) {
75 this.dx = 0; this.dy = -1;
76 }
77 };
78
79 this.rotateCounterclockwise = function() {
80 if (this.dx === 0 && this.dy === -1) {
81 this.dx = -1; this.dy = -1;
82 } else if (this.dx === -1 && this.dy === -1) {
83 this.dx = -1; this.dy = 0;
84 } else if (this.dx === -1 && this.dy === 0) {
85 this.dx = -1; this.dy = 1;
86 } else if (this.dx === -1 && this.dy === 1) {
87 this.dx = 0; this.dy = 1;
88 } else if (this.dx === 0 && this.dy === 1) {
89 this.dx = 1; this.dy = 1;
90 } else if (this.dx === 1 && this.dy === 1) {
91 this.dx = 1; this.dy = 0;
92 } else if (this.dx === 1 && this.dy === 0) {
93 this.dx = 1; this.dy = -1;
94 } else if (this.dx === 1 && this.dy === -1) {
95 this.dx = 0; this.dy = -1;
96 }
97 };
98
99 this.rotateDegrees = function(degrees) {
100 while (degrees > 0) {
101 this.rotateCounterclockwise();
102 degrees -= 45;
103 }
104 };
105
106 /* from yoob.TapeHead; may go away or change slightly */
107 this.move = function(delta) {
108 this.x += delta;
109 };
110
111 this.moveLeft = function(amount) {
112 if (amount === undefined) amount = 1;
113 this.x -= amount;
114 };
115
116 this.moveRight = function(amount) {
117 if (amount === undefined) amount = 1;
118 this.x += amount;
119 };
120
121 this.read = function() {
122 if (!this.tape) return undefined;
123 return this.tape.get(this.x);
124 };
125
126 this.write = function(value) {
127 if (!this.tape) return;
128 this.tape.put(this.x, value);
129 };
130
131 /*
132 * For HTML views. Override if you like.
133 */
134 this.wrapText = function(text) {
135 var fillStyle = this.fillStyle || "#50ff50";
136 return '<span style="background: ' + fillStyle + '">' +
137 text + '</span>';
138 };
139
140 /*
141 * For canvas views. Override if you like.
142 */
143 this.drawContext = function(ctx, x, y, cellWidth, cellHeight) {
144 ctx.fillStyle = this.fillStyle || "#50ff50";
145 ctx.fillRect(x, y, cellWidth, cellHeight);
146 };
147 }
+0
-203
impl/wierd.js/src/yoob/element-factory.js less more
0 /*
1 * This file is part of yoob.js version 0.8-PRE
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
-219
impl/wierd.js/src/yoob/playfield-canvas-view.js less more
0 /*
1 * This file is part of yoob.js version 0.8-PRE
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 on an HTML5 <canvas> element (or compatible object).
10 *
11 * TODO: don't necesarily resize canvas each time?
12 * TODO: option to stretch content rendering to fill a fixed-size canvas
13 */
14 yoob.PlayfieldCanvasView = function() {
15 this.init = function(pf, canvas) {
16 this.pf = pf;
17 this.canvas = canvas;
18 this.ctx = canvas.getContext('2d');
19 this.cursors = [];
20 this.fixedPosition = false;
21 this.fixedSizeCanvas = false;
22 this.drawCursorsFirst = true;
23 this.setCellDimensions(8, 8);
24 return this;
25 };
26
27 /*** Chainable setters ***/
28
29 /*
30 * Set the list of cursors to the given list of yoob.Cursor (or compatible)
31 * objects.
32 */
33 this.setCursors = function(cursors) {
34 this.cursors = cursors;
35 return this;
36 };
37
38 this.setPlayfield = function(pf) {
39 this.pf = pf;
40 return this;
41 };
42
43 /*
44 * Set the displayed dimensions of every cell.
45 * cellWidth and cellHeight are canvas units of measure for each cell.
46 * If cellWidth is undefined, the width of a character in the monospace
47 * font of cellHeight pixels is used.
48 */
49 this.setCellDimensions = function(cellWidth, cellHeight) {
50 this.ctx.textBaseline = "top";
51 this.ctx.font = cellHeight + "px monospace";
52
53 if (cellWidth === undefined) {
54 cellWidth = this.ctx.measureText("@").width;
55 }
56
57 this.cellWidth = cellWidth;
58 this.cellHeight = cellHeight;
59 return this;
60 };
61
62 /*
63 * Return the requested bounds of the occupied portion of the playfield.
64 * "Occupation" in this sense includes all cursors.
65 *
66 * These may return 'undefined' if there is nothing in the playfield.
67 *
68 * Override these if you want to draw some portion of the
69 * playfield which is not the whole playfield.
70 */
71 this.getLowerX = function() {
72 var minX = this.pf.getMinX();
73 for (var i = 0; i < this.cursors.length; i++) {
74 if (minX === undefined || this.cursors[i].x < minX) {
75 minX = this.cursors[i].x;
76 }
77 }
78 return minX;
79 };
80 this.getUpperX = function() {
81 var maxX = this.pf.getMaxX();
82 for (var i = 0; i < this.cursors.length; i++) {
83 if (maxX === undefined || this.cursors[i].x > maxX) {
84 maxX = this.cursors[i].x;
85 }
86 }
87 return maxX;
88 };
89 this.getLowerY = function() {
90 var minY = this.pf.getMinY();
91 for (var i = 0; i < this.cursors.length; i++) {
92 if (minY === undefined || this.cursors[i].y < minY) {
93 minY = this.cursors[i].y;
94 }
95 }
96 return minY;
97 };
98 this.getUpperY = function() {
99 var maxY = this.pf.getMaxY();
100 for (var i = 0; i < this.cursors.length; i++) {
101 if (maxY === undefined || this.cursors[i].y > maxY) {
102 maxY = this.cursors[i].y;
103 }
104 }
105 return maxY;
106 };
107
108 /*
109 * Returns the number of occupied cells in the x direction.
110 * "Occupation" in this sense includes all cursors.
111 */
112 this.getExtentX = function() {
113 if (this.getLowerX() === undefined || this.getUpperX() === undefined) {
114 return 0;
115 } else {
116 return this.getUpperX() - this.getLowerX() + 1;
117 }
118 };
119
120 /*
121 * Returns the number of occupied cells in the y direction.
122 * "Occupation" in this sense includes all cursors.
123 */
124 this.getExtentY = function() {
125 if (this.getLowerY() === undefined || this.getUpperY() === undefined) {
126 return 0;
127 } else {
128 return this.getUpperY() - this.getLowerY() + 1;
129 }
130 };
131
132 /*
133 * Draws cells of the Playfield in a drawing context.
134 * cellWidth and cellHeight are canvas units of measure.
135 *
136 * The default implementation tries to call a .draw() method on the cell's
137 * value, if one exists, and just renders it as text, in black, if not.
138 *
139 * Override if you wish to draw elements in some other way.
140 */
141 this.drawCell = function(ctx, value, playfieldX, playfieldY,
142 canvasX, canvasY, cellWidth, cellHeight) {
143 if (value.draw !== undefined) {
144 value.draw(ctx, playfieldX, playfieldY, canvasX, canvasY,
145 cellWidth, cellHeight);
146 } else {
147 ctx.fillStyle = "black";
148 ctx.fillText(value.toString(), canvasX, canvasY);
149 }
150 };
151
152 /*
153 * Draws the Playfield in a drawing context.
154 * cellWidth and cellHeight are canvas units of measure for each cell.
155 * offsetX and offsetY are canvas units of measure for the top-left
156 * of the entire playfield.
157 */
158 this.drawContext = function(ctx, offsetX, offsetY, cellWidth, cellHeight) {
159 var self = this;
160 this.pf.foreach(function (x, y, value) {
161 self.drawCell(ctx, value, x, y,
162 offsetX + x * cellWidth, offsetY + y * cellHeight,
163 cellWidth, cellHeight);
164 });
165 };
166
167 this.drawCursors = function(ctx, offsetX, offsetY, cellWidth, cellHeight) {
168 var cursors = this.cursors;
169 for (var i = 0; i < cursors.length; i++) {
170 cursors[i].drawContext(
171 ctx,
172 offsetX + cursors[i].x * cellWidth,
173 offsetY + cursors[i].y * cellHeight,
174 cellWidth, cellHeight
175 );
176 }
177 };
178
179 /*
180 * Draw the Playfield, and its set of Cursors, on the canvas element.
181 * Resizes the canvas to the needed dimensions first.
182 */
183 this.draw = function() {
184 var canvas = this.canvas;
185 var ctx = canvas.getContext('2d');
186 var cellWidth = this.cellWidth;
187 var cellHeight = this.cellHeight;
188 var cursors = this.cursors;
189
190 var width = this.getExtentX();
191 var height = this.getExtentY();
192
193 canvas.width = width * cellWidth;
194 canvas.height = height * cellHeight;
195
196 this.ctx.textBaseline = "top";
197 this.ctx.font = cellHeight + "px monospace";
198
199 var offsetX = 0;
200 var offsetY = 0;
201
202 if (!this.fixedPosition) {
203 offsetX = (this.getLowerX() || 0) * cellWidth * -1;
204 offsetY = (this.getLowerY() || 0) * cellHeight * -1;
205 }
206
207 if (this.drawCursorsFirst) {
208 this.drawCursors(ctx, offsetX, offsetY, cellWidth, cellHeight);
209 }
210
211 this.drawContext(ctx, offsetX, offsetY, cellWidth, cellHeight);
212
213 if (!this.drawCursorsFirst) {
214 this.drawCursors(ctx, offsetX, offsetY, cellWidth, cellHeight);
215 }
216 };
217
218 };
+0
-149
impl/wierd.js/src/yoob/playfield-html-view.js less more
0 /*
1 * This file is part of yoob.js version 0.8-PRE
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
-286
impl/wierd.js/src/yoob/playfield.js less more
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;