diff --git a/examples.txt b/examples.txt index e7f0b7f..60a497f 100644 --- a/examples.txt +++ b/examples.txt @@ -51,3 +51,11 @@ to fern :size :sign end window clearscreen pu bk 150 pd fern 25 1 + +clearscreen +setturtle 2 penup right 90 forward 100 left 90 pendown +repeat 100 [ + setturtle 1 forward random 4 + setturtle 2 forward random 4 + wait 2 +] diff --git a/language.html b/language.html index 5a51758..e8f34a9 100644 --- a/language.html +++ b/language.html @@ -681,6 +681,19 @@

6.3 Turtle and Window Control

setscrunch sx sy
Set the graphics scaling factors
setscrunch 1 2 arc 360 100
+ +
setturtle index +
Switch to the turtle numbered index (starting from 1 for the default turtle present at start). + If the turtle has not been used yet, it will be created at the center, facing upwards, visible, with the pen down. +
setturtle 2 rt 90 fd 100
+ +
ask turtleindex [ statements ... ] +
Execute statements as turtle number turtleindex. +
ask 2 [ rt 90 fd 100 ]
+ +
clearturtles +
Remove all turtles, keeping the current one as index 1. +
fd 50 setturtle 2 rt 90 fd 100 clearturtles

6.4 Turtle and Window Queries

@@ -697,6 +710,12 @@

6.4 Turtle and Window Queries

labelfont
Outputs the name of the font drawn by label + +
turtle +
Outputs the index of the currently-active turtle + +
turtles +
Outputs the largest index that has been passed to setturtle

6.5 Pen and Background Control

diff --git a/logo.js b/logo.js index 03c0212..e17ecb1 100644 --- a/logo.js +++ b/logo.js @@ -2243,6 +2243,31 @@ function LogoInterpreter(turtle, stream, savehook) // Not Supported: refresh // Not Supported: norefresh + def("setturtle", function(index) { + index = aexpr(index)|0; + if (index < 1) + throw err("{_PROC_}: Expected positive turtle index", ERRORS.BAD_INPUT); + turtle.currentturtle = index - 1; + }); + + def("ask", function(index, statements) { + index = aexpr(index)|0; + if (index < 1) + throw err("{_PROC_}: Expected positive turtle index", ERRORS.BAD_INPUT); + statements = reparse(lexpr(statements)); + var originalturtle = turtle.currentturtle; + turtle.currentturtle = index - 1; + return promiseFinally( + this.execute(statements), + function() { + turtle.currentturtle = originalturtle; + }); + }); + + def("clearturtles", function() { + turtle.clearturtles(); + }); + // // 6.4 Turtle and Window Queries // @@ -2265,6 +2290,14 @@ function LogoInterpreter(turtle, stream, savehook) return turtle.fontname; }); + def("turtles", function() { + return turtle.turtles; + }); + + def("turtle", function() { + return turtle.currentturtle + 1; + }); + // // 6.5 Pen and Background Control // diff --git a/tests.js b/tests.js index 5d87967..c16621c 100644 --- a/tests.js +++ b/tests.js @@ -989,7 +989,7 @@ QUnit.test("Logical Operations", function(t) { }); QUnit.test("Graphics", function(t) { - t.expect(166); + t.expect(180); // NOTE: test canvas is 300,300 (so -150...150 coordinates before hitting) // edge @@ -1157,6 +1157,17 @@ QUnit.test("Graphics", function(t) { this.run('cs setscrunch 1 1'); + this.assert_equals('cs fd 100 setturtle 2 pos', [0, 0]); + this.assert_equals('cs fd 100 setturtle 2 rt 90 setturtle 1 pos', [0, 100]); + this.assert_equals('cs ht setturtle 2 shownp', 1); + this.assert_equals('cs setturtle 2 ht setturtle 1 shownp', 1); + this.assert_equals('cs ht setturtle 2 setturtle 1 shownp', 0); + this.assert_equals('cs ht ask 2 [ pu ] shownp', 0); + this.assert_equals('cs pu setturtle 2 pendownp', 1); + this.assert_equals('cs setturtle 2 pu setturtle 1 pendownp', 1); + this.assert_equals('cs pu setturtle 2 setturtle 1 pendownp', 0); + this.assert_equals('cs pu ask 2 [ ht ] pendownp', 0); + // // 6.4 Turtle and Window Queries // @@ -1177,6 +1188,10 @@ QUnit.test("Graphics", function(t) { this.assert_equals('make "x ( item 1 bounds ) setscrunch 2 1 :x = (item 1 bounds) * 2', 1); this.assert_equals('make "y ( item 3 bounds ) setscrunch 1 3 :x = (item 3 bounds) * 3', 1); + this.assert_equals('clearturtles turtle', 1); + this.assert_equals('clearturtles setturtle 10 setturtle 5 turtle', 5); + this.assert_equals('clearturtles setturtle 10 setturtle 5 turtles', 10); + this.assert_equals('clearscreen setturtle 3 turtles', 3); // // 6.5 Pen and Background Control @@ -1996,6 +2011,7 @@ QUnit.test("Arity of Primitives", function(t) { ['arraytolist', [1, 1, 1]], ['ascii', [1, 1, 1]], ['ashift', [2, 2, 2]], + ['ask', [2, 2, 2]], ['back', [1, 1, 1]], ['background', [0, 0, 0]], ['before?', [2, 2, 2]], @@ -2025,6 +2041,7 @@ QUnit.test("Arity of Primitives", function(t) { ['clean', [0, 0, 0]], ['clearscreen', [0, 0, 0]], ['cleartext', [0, 0, 0]], + ['clearturtles', [0, 0, 0]], ['clickpos', [0, 0, 0]], //['close', [1, 1, 1]], //['co', [0, 1, 1]], @@ -2254,6 +2271,7 @@ QUnit.test("Arity of Primitives", function(t) { //['settemploc', [1, 1, 1]], ['settextcolor', /*[2, 2, 2]*/ [1, 1, 1]], /* Does not support background color */ ['settextsize', [1, 1, 1]], + ['setturtle', [1, 1, 1]], //['setwrite', [1, 1, 1]], //['setwritepos', [1, 1, 1]], ['setx', [1, 1, 1]], @@ -2293,7 +2311,9 @@ QUnit.test("Arity of Primitives", function(t) { //['traced?', [1, 1, 1]], //['tracedp', [1, 1, 1]], //['ts', [0, 0, 0]], + ['turtle', [0, 0, 0]], ['turtlemode', [0, 0, 0]], + ['turtles', [0, 0, 0]], ['type', [0, 1, -1]], ['unbury', [1, 1, 1]], //['unstep', [1, 1, 1]], diff --git a/turtle.js b/turtle.js index f1d204b..a733c19 100644 --- a/turtle.js +++ b/turtle.js @@ -68,6 +68,9 @@ this._buttons = 0; this._touches = []; + this._turtles = [{}]; + this._currentturtle = 0; + this._init(); this._tick(); @@ -122,7 +125,7 @@ requestAnimationFrame(this._tick.bind(this)); var cur = JSON.stringify([this.x, this.y, this.r, this.visible, - this.sx, this.sy, this.width, this.height]); + this.sx, this.sy, this.width, this.height, this._turtles]); if (cur === this._last_state) return; this._last_state = cur; @@ -131,48 +134,60 @@ this.turtle_ctx.clearRect(0, 0, this.width, this.height); this.turtle_ctx.restore(); - if (this.visible) { - var ctx = this.turtle_ctx; - ctx.save(); - ctx.translate(this.x, this.y); - ctx.rotate(Math.PI/2 + this.r); - ctx.beginPath(); - - var points = [ - [0, -20], // Head - [2.5, -17], - [3, -12], - - [6, -10], - [9, -13], // Arm - [13, -12], - [18, -4], - [18, 0], - [14, -1], - [10, -7], - - [8, -6], // Shell - [10, -2], - [9, 3], - [6, 10], - - [9, 13], // Foot - [6, 15], - [3, 12], - - [0, 13], - ]; - - points.concat(points.slice(1, -1).reverse().map(invert)) - .forEach(function(pair, index) { - ctx[index ? 'lineTo' : 'moveTo'](pair[0], pair[1]); - }); + function _draw(ctx, turtle) { + if (turtle.visible) { + ctx.save(); + ctx.translate(turtle.x, turtle.y); + ctx.rotate(Math.PI/2 + turtle.r); + ctx.beginPath(); + + var points = [ + [0, -20], // Head + [2.5, -17], + [3, -12], + + [6, -10], + [9, -13], // Arm + [13, -12], + [18, -4], + [18, 0], + [14, -1], + [10, -7], + + [8, -6], // Shell + [10, -2], + [9, 3], + [6, 10], + + [9, 13], // Foot + [6, 15], + [3, 12], + + [0, 13], + ]; + + points.concat(points.slice(1, -1).reverse().map(invert)) + .forEach(function(pair, index) { + ctx[index ? 'lineTo' : 'moveTo'](pair[0], pair[1]); + }); + + ctx.closePath(); + ctx.stroke(); + + ctx.restore(); + } + } - ctx.closePath(); - ctx.stroke(); + _draw(this.turtle_ctx, this); - ctx.restore(); + for (var i in this._turtles) { + if (this._turtles[i] === undefined) { + continue; + } + _draw(this.turtle_ctx, this._turtles[i]); } + + }}, _moveto: {value: function(x, y, setpos) { @@ -369,6 +384,7 @@ clearscreen: {value: function() { this.home(); + this.clearturtles(); this.clear(); }}, @@ -384,6 +400,11 @@ } }}, + clearturtles: {value: function() { + this._turtles = [{}]; + this._currentturtle = 0; + }}, + home: {value: function() { this._moveto(0, 0); this.r = deg2rad(90); @@ -624,6 +645,39 @@ touches: { get: function() { return this._touches; } + }, + + currentturtle: { + get: function() { return this._currentturtle; }, + set: function(newturtle) { + if (newturtle === this._currentturtle) return; + this._turtles[this._currentturtle] = { + x: this.x, + y: this.y, + r: this.r, + pendown: this.pendown, + visible: this.visible, + }; + this._currentturtle = newturtle; + if (this._turtles[this._currentturtle] !== undefined) { + this.x = this._turtles[this._currentturtle].x; + this.y = this._turtles[this._currentturtle].y; + this.r = this._turtles[this._currentturtle].r; + this.pendown = this._turtles[this._currentturtle].pendown; + this.visible = this._turtles[this._currentturtle].visible; + } else { + this.x = 0; + this.y = 0; + this.r = Math.PI / 2; + this.pendown = true; + this.visible = true; + } + this._turtles[this._currentturtle] = {}; + } + }, + + turtles: { + get: function() { return this._turtles.length; } } });