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
PU
FD 120
RT 30
REPEAT 12 [
PD
REPEAT 3 [ FD 100 RT 120 ]
PU
FD 50
RT 30
]
One Response to “March Madness 4 – Crude Logo Interpreter”
Comments (1)
Cool stuff. Broken link?