git @ Cat's Eye Technologies Erratic-Turtle-Graphics / 5d6994d
Move towards being driven by a small, Logo-like command language. Chris Pressey 2 years ago
4 changed file(s) with 235 addition(s) and 7 deletion(s). Raw diff Collapse all Expand all
00 /*
1 * dam-plus-widgets-web.js and erratic-turtle.js should be loaded before this.
1 * dam-plus-widgets-web.js, erratic-turtle.js, and erratic-logo.js should be loaded before this.
22 * After this is loaded, call launch() to start the gewgaw.
33 */
44
88 var can = canvas({ width: 1000, height: 400 });
99 config.container.appendChild(can);
1010
11 var gewgaw = (new ErraticTurtle()).init({ canvas: can });
12 gewgaw.reset();
11 var turtle = (new ErraticTurtle()).init({ canvas: can });
12 turtle.reset();
1313 var method = 'drawLines';
14 gewgaw[method]();
14 turtle[method]();
1515
1616 var controlPanel = div(
1717 div(
3333 {
3434 text: 'Circle Chain',
3535 value: 'drawCircleChain',
36 },
37 {
38 text: 'Logo',
39 value: 'logo',
40 program: "setxyp 0.125 0.5 lt 90 repeat 7 [ repeat 50 [ fd 150 lt 180 ] shiftxyp 0.125 0.0 ]"
3641 }
3742 ],
3843 onchange: function(option) {
3944 method = option.value;
40 gewgaw.reset();
41 gewgaw[method]();
45 turtle.reset();
46 if (method === 'logo') {
47 var p = (new Parser()).init(option.program);
48 var i = p.parseInstrs();
49 console.log(uneval(i));
50 interpretInstrs(i, turtle);
51 } else {
52 turtle[method]();
53 }
4254 }
4355 })
4456 ),
4557 div(
46 button("Re-roll", { onclick: function() { gewgaw.reset(); gewgaw[method](); }})
58 button("Re-roll", { onclick: function() { turtle.reset(); turtle[method](); }})
4759 )
4860 );
4961 config.container.appendChild(controlPanel);
1616
1717 <script src="dam-plus-widgets-web.js"></script>
1818 <script src="../src/erratic-turtle.js"></script>
19 <script src="../src/erratic-logo.js"></script>
1920 <script src="erratic-turtle-graphics-launcher.js"></script>
2021 <script>
2122 launch({
0 /*
1 * Small command language for Erratic Turtle Graphics.
2 */
3
4 var Scanner = function() {
5 this.init = function(cfg) {
6 this.text = undefined;
7 this.token = undefined;
8 this.type = undefined;
9 this.error = undefined;
10 this.table = cfg.table;
11 this.whitespacePattern = cfg.whitespacePattern || "^[ \\t\\n\\r]*";
12 return this;
13 };
14
15 this.reset = function(text) {
16 this.text = text;
17 this.token = undefined;
18 this.type = undefined;
19 this.error = undefined;
20 this.scan();
21 };
22
23 this.scanPattern = function(pattern, type) {
24 var re = new RegExp(pattern);
25 var match = re.exec(this.text);
26 if (match === null) return false;
27 this.type = type;
28 this.token = match[1];
29 this.text = this.text.substr(match[0].length);
30 return true;
31 };
32
33 this.scan = function() {
34 this.scanPattern(this.whitespacePattern, "whitespace");
35 if (this.text.length === 0) {
36 this.token = null;
37 this.type = "EOF";
38 return;
39 }
40 for (var i = 0; i < this.table.length; i++) {
41 var type = this.table[i][0];
42 var pattern = this.table[i][1];
43 if (this.scanPattern(pattern, type)) return;
44 }
45 if (this.scanPattern("^([\\s\\S])", "unknown character")) return;
46 // should never get here
47 };
48
49 this.expect = function(token) {
50 if (this.token === token) {
51 this.scan();
52 } else {
53 this.error = "expected '" + token + "' but found '" + this.token + "'";
54 }
55 };
56
57 this.on = function(token) {
58 return this.token === token;
59 };
60
61 this.onType = function(type) {
62 return this.type === type;
63 };
64
65 this.checkType = function(type) {
66 if (this.type !== type) {
67 this.error = "expected " + type + " but found " + this.type + " (" + this.token + ")"
68 }
69 };
70
71 this.expectType = function(type) {
72 this.checkType(type);
73 this.scan();
74 };
75
76 this.consume = function(token) {
77 if (this.on(token)) {
78 this.scan();
79 return true;
80 } else {
81 return false;
82 }
83 };
84 };
85
86
87 var Parser = function() {
88 this.scanner = undefined;
89
90 this.init = function(text) {
91 this.scanner = (new Scanner()).init({
92 table: [
93 ['paren', "^(\\[|\\])"],
94 ['atom', "^([a-zA-Z]\\w*)"],
95 ['number', "^(\\-?\\d+\\.?\\d*)"]
96 ]
97 });
98 this.scanner.reset(text);
99 return this;
100 };
101
102 /*
103 * Instrs ::= {Instr}.
104 */
105 this.parseInstrs = function() {
106 var instrs = [];
107 while (this.scanner.token !== "]" && this.scanner.type !== "EOF") {
108 var instr = this.parseInstr();
109 instrs.push(instr);
110 }
111 return instrs;
112 };
113
114 /*
115 * Instr ::= "fd" Number | "rt" Number | "lt" Number
116 * | "setxyp" Number Number | "shiftxyp" Number Number
117 * | "repeat" Number "[" Instrs "]".
118 */
119 this.parseInstr = function() {
120 if (this.scanner.consume('fd')) {
121 var val = this.scanner.token;
122 this.scanner.expectType('number');
123 return ["fd", parseFloat(val)];
124 } else if (this.scanner.consume('rt')) {
125 var val = this.scanner.token;
126 this.scanner.expectType('number');
127 return ["rt", parseFloat(val)];
128 } else if (this.scanner.consume('lt')) {
129 var val = this.scanner.token;
130 this.scanner.expectType('number');
131 return ["lt", parseFloat(val)];
132 } else if (this.scanner.consume('setxyp')) {
133 var xval = this.scanner.token;
134 this.scanner.expectType('number');
135 var yval = this.scanner.token;
136 this.scanner.expectType('number');
137 return ["setxyp", parseFloat(xval), parseFloat(yval)];
138 } else if (this.scanner.consume('shiftxyp')) {
139 var xval = this.scanner.token;
140 this.scanner.expectType('number');
141 var yval = this.scanner.token;
142 this.scanner.expectType('number');
143 return ["shiftxyp", parseFloat(xval), parseFloat(yval)];
144 } else if (this.scanner.consume('repeat')) {
145 var val = this.scanner.token;
146 this.scanner.expectType('number');
147 this.scanner.expect("[");
148 var instrs = this.parseInstrs();
149 this.scanner.expect("]");
150 return ["repeat", parseFloat(val), instrs];
151 } else {
152 /* TODO: register some kind of error */
153 var t = this.scanner.token;
154 this.scanner.scan();
155 return ["err", t];
156 }
157 };
158 };
159
160
161 function interpretInstrs(instrs, turtle) {
162 var i = 0;
163 while (i < instrs.length) {
164 var instr = instrs[i];
165 interpretInstr(instr, turtle);
166 i += 1;
167 }
168 }
169
170 function interpretInstr(instr, turtle) {
171 switch (instr[0]) {
172 case "fd":
173 turtle.moveBy(instr[1]);
174 break;
175 case "rt":
176 turtle.rotateByDeg(instr[1]);
177 break;
178 case "lt":
179 turtle.rotateByDeg(-1 * instr[1]);
180 break;
181 case "setxyp":
182 turtle.setXYProportional(instr[1], instr[2]);
183 break;
184 case "shiftxyp":
185 turtle.shiftXYProportional(instr[1], instr[2]);
186 break;
187 case "repeat":
188 for (var k = 0; k < instr[1]; k++) {
189 interpretInstrs(instr[2], turtle);
190 }
191 break;
192 default:
193 //console.log("unrecognized", instr[0]);
194 break;
195 }
196 }
2828 this.dy = Math.sin(theta);
2929 };
3030
31 this.setThetaDeg = function(theta) {
32 this.setTheta(theta * DEG);
33 };
34
35 this.setXYProportional = function(xp, yp) {
36 this.x = this.canvas.width * xp;
37 this.y = this.canvas.height * yp;
38 };
39
40 this.shiftXYProportional = function(dxp, dyp) {
41 this.x += this.canvas.width * dxp;
42 this.y += this.canvas.height * dyp;
43 };
44
3145 /* dtheta is in radians */
3246 this.rotateBy = function(dtheta) {
3347 var error = (Math.random() - 0.5) * this.rotateError;
3448 this.setTheta(this.theta + dtheta + error);
49 };
50
51 this.rotateByDeg = function(dtheta) {
52 this.rotateBy(dtheta * DEG);
3553 };
3654
3755 this.moveBy = function(units) {