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; }
}
});