Turtle Graphics

Turtle Graphics“ is a conceptual model for teaching computational thinking, based on a “first-person cursor”. In this schema, developed in the 1960s by computer scientist and constructivist educator Seymour Papert, and inspired by Jean Piaget‘s studies of children’s learning and embodied cognition, vector graphics are produced using a relative cursor (the “turtle”) upon a Cartesian plane.

The Turtle object has the following methods or API (application programmer’s interface):

And here is the actual Turtle Graphics implementation for p5.js:

Turtle Example 1

Here is an example using Turtle Graphics. Note that in order to post such code on WordPress, the entire Turtle object code must be included into your p5.js sketch file. Note that the Turtle code has been “minified” to save space.

turtle-ex1


function setup() {
    createCanvas(400, 400);
    background(0);
    var turtle = makeTurtle(130, 80);
    turtle.penDown();
    turtle.setColor(255);
    for (var i = 0; i < 1000; i++) {
        turtle.forward(150);
        turtle.right(141.5);
        turtle.forward(60);
        if (i % 20 === 0) {
            turtle.forward(70);
        }
    }
    noLoop();
}


function draw() {
}


function turtleLeft(d){this.angle-=d;}function turtleRight(d){this.angle+=d;}
function turtleForward(p){var rad=radians(this.angle);var newx=this.x+cos(rad)*p;
var newy=this.y+sin(rad)*p;this.goto(newx,newy);}function turtleBack(p){
this.forward(-p);}function turtlePenDown(){this.penIsDown=true;}
function turtlePenUp(){this.penIsDown = false;}function turtleGoTo(x,y){
if(this.penIsDown){stroke(this.color);strokeWeight(this.weight);
line(this.x,this.y,x,y);}this.x = x;this.y = y;}function turtleDistTo(x,y){
return sqrt(sq(this.x-x)+sq(this.y-y));}function turtleAngleTo(x,y){
var absAngle=degrees(atan2(y-this.y,x-this.x));
var angle=((absAngle-this.angle)+360)%360.0;return angle;}
function turtleTurnToward(x,y,d){var angle = this.angleTo(x,y);if(angle< 180){
this.angle+=d;}else{this.angle-=d;}}function turtleSetColor(c){this.color=c;}
function turtleSetWeight(w){this.weight=w;}function turtleFace(angle){
this.angle = angle;}function makeTurtle(tx,ty){var turtle={x:tx,y:ty,
angle:0.0,penIsDown:true,color:color(128),weight:1,left:turtleLeft,
right:turtleRight,forward:turtleForward, back:turtleBack,penDown:turtlePenDown,
penUp:turtlePenUp,goto:turtleGoTo, angleto:turtleAngleTo,
turnToward:turtleTurnToward,distanceTo:turtleDistTo, angleTo:turtleAngleTo,
setColor:turtleSetColor, setWeight:turtleSetWeight,face:turtleFace};
return turtle;}

Turtle Example 2

If you want to see animated turtles, you can issue turtle commands in draw() without erasing (calling background()). In this case, draw becomes your loop. To get a turtle whose behavior changes over time, you would need to give it commands whose values were based on things like the millis(), random(), frameCount, or other progressively-modified global variables.

turtle-ex2


var myTurtle;
var startFrame;

function setup() {
    createCanvas(400, 400);
    background(0);
    myTurtle = makeTurtle(width / 2, height / 2);
    myTurtle.setColor(color(255, 200, 200));
    myTurtle.setWeight(2); 
    myTurtle.penDown();
    resetCanvas();
    frameRate(10);
}

function draw() {
    var step = (frameCount - startFrame)/30.0;
    myTurtle.forward(step);
    myTurtle.left(6.0);
    if (myTurtle.y > height) resetCanvas();
}

function resetCanvas() {
    background(0);
    startFrame = frameCount;
    myTurtle.penUp();
    myTurtle.goto(width / 2, height / 2);
    myTurtle.penDown();
}


function turtleLeft(d){this.angle-=d;}function turtleRight(d){this.angle+=d;}
function turtleForward(p){var rad=radians(this.angle);var newx=this.x+cos(rad)*p;
var newy=this.y+sin(rad)*p;this.goto(newx,newy);}function turtleBack(p){
this.forward(-p);}function turtlePenDown(){this.penIsDown=true;}
function turtlePenUp(){this.penIsDown = false;}function turtleGoTo(x,y){
if(this.penIsDown){stroke(this.color);strokeWeight(this.weight);
line(this.x,this.y,x,y);}this.x = x;this.y = y;}function turtleDistTo(x,y){
return sqrt(sq(this.x-x)+sq(this.y-y));}function turtleAngleTo(x,y){
var absAngle=degrees(atan2(y-this.y,x-this.x));
var angle=((absAngle-this.angle)+360)%360.0;return angle;}
function turtleTurnToward(x,y,d){var angle = this.angleTo(x,y);if(angle< 180){
this.angle+=d;}else{this.angle-=d;}}function turtleSetColor(c){this.color=c;}
function turtleSetWeight(w){this.weight=w;}function turtleFace(angle){
this.angle = angle;}function makeTurtle(tx,ty){var turtle={x:tx,y:ty,
angle:0.0,penIsDown:true,color:color(128),weight:1,left:turtleLeft,
right:turtleRight,forward:turtleForward, back:turtleBack,penDown:turtlePenDown,
penUp:turtlePenUp,goto:turtleGoTo, angleto:turtleAngleTo,
turnToward:turtleTurnToward,distanceTo:turtleDistTo, angleTo:turtleAngleTo,
setColor:turtleSetColor, setWeight:turtleSetWeight,face:turtleFace};
return turtle;}

Turtle Example 3

Here’s an example of three Turtles (red, green and blue) chasing a gray “target”, whose location is based on Perlin noise. The turtles “overshoot” the target because they always move forward but can only turn 1 degree per frame (plus or minus a random 5 degrees to introduce some wandering into their paths).

turtle-ex3

var t1, t2, t3;
function setup() {
    createCanvas(400, 400);
    background(0);
    t1 = makeTurtle(width / 2 + random(-100, 100), height / 2 + random(-100, 100));
    t2 = makeTurtle(width / 2 + random(-100, 100), height / 2 + random(-100, 100));
    t3 = makeTurtle(width / 2 + random(-100, 100), height / 2 + random(-100, 100));
    t1.penDown();
    t1.setColor(color(255, 0, 0));
    t1.setWeight(3);
    t2.penDown();
    t2.setColor(color(0, 255, 0));
    t2.setWeight(3);
    t3.penDown();
    t3.setColor(color(100, 100, 255));
    t3.setWeight(3);
    frameRate(10);
}
 
function draw() {
    t1.forward(1);
    t2.forward(2);
    t3.forward(3);
    var targetX = width * noise(frameCount / 1000);
    var targetY = height * noise(100 + frameCount / 1000);
    strokeWeight(1);
    stroke(128);
    point(targetX, targetY);
    t1.turnToward(targetX, targetY, 1);
    t2.turnToward(targetX, targetY, 1);
    t3.turnToward(targetX, targetY, 1);
    t1.left(random(-5, 5));
    t2.left(random(-5, 5));
    t3.left(random(-5, 5));
    fill(0); // erase the area to show angles
    noStroke();
    stroke(2);
    rect(0, 0, 120, 40);
    push();
        translate(20, 20);
        push();
            rotate(radians(t1.angleTo(targetX, targetY), 5, 25));
            stroke(t1.color);
            line(0, 0, 0, -20);
        pop();
        translate(40, 0);
        push();
            rotate(radians(t2.angleTo(targetX, targetY), 5, 25));
            stroke(t2.color);
            line(0, 0, 0, -20);
        pop();
        translate(40, 0);
        push();
            rotate(radians(t3.angleTo(targetX, targetY), 5, 25));
            stroke(t3.color);
            line(0, 0, 0, -20);
        pop();
    pop();
    fill(255);
    text("Angle from turtle to target", 3, 52);
}


function turtleLeft(d){this.angle-=d;}function turtleRight(d){this.angle+=d;}
function turtleForward(p){var rad=radians(this.angle);var newx=this.x+cos(rad)*p;
var newy=this.y+sin(rad)*p;this.goto(newx,newy);}function turtleBack(p){
this.forward(-p);}function turtlePenDown(){this.penIsDown=true;}
function turtlePenUp(){this.penIsDown = false;}function turtleGoTo(x,y){
if(this.penIsDown){stroke(this.color);strokeWeight(this.weight);
line(this.x,this.y,x,y);}this.x = x;this.y = y;}function turtleDistTo(x,y){
return sqrt(sq(this.x-x)+sq(this.y-y));}function turtleAngleTo(x,y){
var absAngle=degrees(atan2(y-this.y,x-this.x));
var angle=((absAngle-this.angle)+360)%360.0;return angle;}
function turtleTurnToward(x,y,d){var angle = this.angleTo(x,y);if(angle< 180){
this.angle+=d;}else{this.angle-=d;}}function turtleSetColor(c){this.color=c;}
function turtleSetWeight(w){this.weight=w;}function turtleFace(angle){
this.angle = angle;}function makeTurtle(tx,ty){var turtle={x:tx,y:ty,
angle:0.0,penIsDown:true,color:color(128),weight:1,left:turtleLeft,
right:turtleRight,forward:turtleForward, back:turtleBack,penDown:turtlePenDown,
penUp:turtlePenUp,goto:turtleGoTo, angleto:turtleAngleTo,
turnToward:turtleTurnToward,distanceTo:turtleDistTo, angleTo:turtleAngleTo,
setColor:turtleSetColor, setWeight:turtleSetWeight,face:turtleFace};
return turtle;}