March Madness 4 – Crude Logo Interpreter
Uncategorized
Add comments
Mar 052010
Two programs for you for Thursday night’s entry. A crude Logo interpreter written in Javascript using the HTML5 canvas tag and a quick logo program to go with it. Now when I say crude, I mean crude.
- You have to separate everything with whitespace because the parser is finicky.
- All the loops are unrolled so careful with your nesting depth.
- The only commands supported are the basics: FD, LT, RT, PU, PD and REPEAT.
That said you can still make some fun stuff with it. Source is in the “Read more”.
JPLT.Class.create("JPLT.Turtle", JPLT.Object, function() { this.width = 500; this.height = 500; this.queue = []; this.x = this.width/2; this.y = this.height/2; this.r = 0; this.isPenDown = true; this.debug = false; this.save = null; this.createElement(); this.appendElement(); }, { context: function() { return this.element.getContext("2d"); }, createElement: function() { this.element = document.createElement("canvas"); this.element.width = this.width; this.element.height = this.height; this.element.style.border = "1px solid #ccc"; return this.element; }, appendElement: function() { var body = document.documentElement || document.body; body.appendChild(this.element); }, reset: function() { this.clear(); this.x = this.width/2; this.y = this.height/2; this.r = 0; this.isPenDown = true; this.save = null; }, run: function() { if (!this.timer) { this.reset(); this.timer = window.setInterval(this.delegate(this.paint), 10); } }, stop: function() { window.clearInterval(this.timer); this.timer = null; }, clear: function() { var ctx = this.context(); ctx.clearRect(0,0,this.width,this.height); }, normalizeAngle: function() { if (this.r > Math.PI * 2) this.r -= Math.PI * 2; else if (this.r < 0) this.r += Math.PI * 2; }, normalizePosition: function() { if (this.x > this.width) { this.x -= this.width; } else if (this.x < 0) { this.x += this.width; } if (this.y > this.height) { this.y -= this.height; } else if (this.x < 0) { this.y += this.height; } }, leftTurn: function(repeat) { var n = Math.min(repeat,10); this.r -= Math.PI/180 * n; this.normalizeAngle(); return n; }, rightTurn: function(repeat) { var n = Math.min(repeat,10); this.r += Math.PI/180 * n; this.normalizeAngle(); return n; }, forward: function(repeat) { var n = Math.min(repeat,3); this.x += Math.cos(this.r - Math.PI/2) * n; this.y += Math.sin(this.r - Math.PI/2) * n; this.normalizePosition(); return n; }, penUp: function() { this.isPenDown = false; }, penDown: function() { this.isPenDown = true; }, repeat: function() { return 1; }, addToQueue: function(f, r) { if (!f) throw new Error("Invalid instruction"); this.queue.push({ func:f, repeat:r }); }, setQueue: function(q) { if (this.timer) this.stop(); this.queue = q; this.log("Program is " + this.queue.length + " commands."); this.run(); }, getCurrentInstruction: function() { return this.queue[0]; }, processQueue: function() { var instruction = this.getCurrentInstruction(); instruction.repeat -= instruction.func.call(this, instruction.repeat, instruction.queue); if (!instruction.repeat) { this.queue.shift(); } }, paint: function() { var oldX = this.x; var oldY = this.y; var ctx = this.context(); try { if (this.queue.length) { this.processQueue(); if (this.debug) this.log("x="+this.x+" y="+this.y+" r="+this.r); ctx.fillStyle = "black"; ctx.strokeStyle = "red"; if (this.save) ctx.putImageData(this.save,oldX-10,oldY-10); if (this.isPenDown) { ctx.beginPath(); ctx.moveTo(oldX,oldY); ctx.lineTo(this.x, this.y); ctx.stroke(); } this.save = ctx.getImageData(this.x-10, this.y-10, 20, 20); ctx.save(); ctx.translate(this.x,this.y); ctx.rotate(this.r); ctx.beginPath(); ctx.moveTo(0,-5); ctx.lineTo(5,5); ctx.lineTo(0,3); ctx.lineTo(-5,5); ctx.lineTo(0,-5); ctx.fill(); ctx.restore(); } else { this.log("Program complete"); this.stop(); } } catch (e) { this.log("Stopping execution due to error"); this.stop(); throw(e); } } } ); |
JPLT.Class.create("JPLT.Logo", JPLT.Object, function() { }, { instructionMap: { 'forward': { func:JPLT.Turtle.prototype.forward, hasParam:true }, 'fd': { func:JPLT.Turtle.prototype.forward, hasParam:true }, 'left': { func:JPLT.Turtle.prototype.leftTurn, hasParam:true }, 'lt': { func:JPLT.Turtle.prototype.leftTurn, hasParam:true }, 'right': { func:JPLT.Turtle.prototype.rightTurn, hasParam:true }, 'rt': { func:JPLT.Turtle.prototype.rightTurn, hasParam:true }, 'penup': { func:JPLT.Turtle.prototype.penUp, hasParam:false }, 'pu': { func:JPLT.Turtle.prototype.penUp, hasParam:false }, 'pendown': { func:JPLT.Turtle.prototype.penDown, hasParam:false }, 'pd': { func:JPLT.Turtle.prototype.penDown, hasParam:false }, 'repeat': { func:JPLT.Turtle.prototype.repeat, hasParam:true } }, parse:function(text, instructions) { // Strip comments text = text.replace(/;.+?[\n\r]+/,""); // Split into tokens var tokens = text.split(/\s+/); var instruction; var token; var param; if (!instructions) instructions = []; while (tokens.length) { token = tokens.shift().toLowerCase(); if (token) { instruction = this.instructionMap[token]; if (!instruction) { throw new Error("Invalid command:" + token); } param = instruction.hasParam ? tokens.shift() : null; if (isNaN(param)) { throw new Error("Invalid parameter: " + param) } if (token == "repeat") { if (tokens[0] != '[') { throw new Error("Unexpected repeat block: " + tokens[0]); } tokens.shift(); var subtokens = []; var nest = 0; while(tokens && (tokens[0] != ']' || nest)) { if (tokens[0] == '[') nest++; else if (tokens[0] == ']') nest--; subtokens.push(tokens.shift()); } tokens.shift(); for (var i=0; i<param; i++) { this.parse(subtokens.join(' '), instructions); } } else { instructions.push({ func: instruction.func, repeat: param }); } } } return instructions; } } ); |
PU FD 120 RT 30 REPEAT 12 [ PD REPEAT 3 [ FD 100 RT 120 ] PU FD 50 RT 30 ] |
-
Toby Ho

