I'm working on modernizing Rosetta Code's infrastructure. Starting with communications. Please accept this time-limited open invite to RC's Slack.. --Michael Mol (talk) 20:59, 30 May 2020 (UTC)

Simple turtle graphics

From Rosetta Code
Simple turtle graphics is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

The first turtle graphic discussed in Mindstorms: Children, Computers, and Powerful Ideas by Seymour Papert is a simple drawing of a house. It is a square with a triangle on top for the roof.

For a slightly more advanced audience, a more practical introduction to turtle graphics might be to draw a bar chart.

See image here: https://i.imgur.com/B7YbTbZ.png

Task
  • Create a function (or subroutine) that uses turtle graphics to draw a house of a specified size as described above. Optionally make it lovely by adding details such as, for example, doors and windows.
  • Create a function (or subroutine) that takes a list (array, vector) of non-negative numbers and draws a bar chart from them, scaled to fit exactly in a square of a specified size. The enclosing square need not be drawn.
  • Both functions should return the turtle to the location it was at and facing in the same direction as it was immediately before the function was executed.

Action![edit]

INCLUDE "D2:TURTLE.ACT" ;from the Action! Tool Kit
 
PROC Rectangle(INT w,h)
BYTE i
 
FOR i=1 TO 2
DO
Forward(h)
Left(90)
Forward(w)
Left(90)
OD
RETURN
 
PROC Square(INT w)
Rectangle(w,w)
RETURN
 
PROC Triangle(INT w)
BYTE i
 
FOR i=1 TO 3
DO
Forward(w)
Right(120)
OD
RETURN
 
PROC House(INT w)
Left(90)
Square(w)
Triangle(w)
Right(90)
RETURN
 
INT FUNC GetMax(INT ARRAY a INT count)
INT i,max
 
max=0
FOR i=0 TO count-1
DO
IF a(i)>max THEN
max=a(i)
FI
OD
RETURN (max)
 
PROC BarChart(INT ARRAY a INT count,w)
INT max,st,i
 
IF count=0 THEN RETURN FI
max=GetMax(a,count)
st=w/count
Right(90)
FOR i=0 TO count-1
DO
Rectangle(a(i)*w/max,st)
Forward(st)
OD
Left(180)
Forward(w)
RETURN
 
PROC Main()
BYTE CH=$02FC,COLOR1=$02C5,COLOR2=$02C6
INT ARRAY a=[50 33 200 130 50]
 
Graphics(8+16)
COLOR1=$0C
COLOR2=$02
 
Color=1
SetTurtle(150,110,90)
House(75)
 
Color=0
Right(90)
Forward(5)
Left(90)
 
Color=1
BarChart(a,5,100)
Right(90)
Forward(5)
Right(90)
 
DO UNTIL CH#$FF OD
CH=$FF
RETURN
Output:

Screenshot from Atari 8-bit computer

Ada[edit]

 
with Ada.Text_IO; use Ada.Text_IO;
with Ada.Characters; use Ada.Characters;
with Ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with Ada.Strings; use Ada.Strings;
 
-- procedure main - begins program execution
procedure main is
type Sketch_Pad is array(1 .. 50, 1 .. 50) of Character;
thePen : Boolean := True; -- pen raised by default
sketch : Sketch_Pad;
ycorr, xcorr : Integer := 25;
 
-- specifications
function penPosition(thePen : in out Boolean) return String;
procedure initGrid(sketch : in out Sketch_Pad);
procedure commandMenu(thePen : in out Boolean; xcorr : in out Integer;
ycorr : in out Integer);
procedure showMenu(xcorr : in out Integer; ycorr : in out Integer;
thePen : in out Boolean; sketch : in Sketch_Pad);
procedure moveCursor(thePen : in Boolean; sketch : in out Sketch_Pad;
xcorr : in out Integer; ycorr : in out Integer;
ch : in Integer);
procedure showGrid(sketch : in Sketch_Pad);
 
-- procedure initGrid - creates the sketchpad and initializes elements
procedure initGrid(sketch : in out Sketch_Pad) is
begin
sketch := (others => (others => ' '));
end initGrid;
 
-- procedure showMenu - displays the menu for the application
procedure showMenu(xcorr : in out Integer; ycorr : in out Integer;
thePen : in out Boolean; sketch : in Sketch_Pad) is
 
choice : Integer := 0;
begin
while choice /= 4 loop
Set_Col(15);
Put("TURTLE GRAPHICS APPLICATION");
Set_Col(15);
Put("===========================");
New_Line(2);
 
Put_Line("Enter 1 to print the grid map");
Put_Line("Enter 2 for command menu");
Put_Line("Enter 3 to raise pen up / down");
Put_Line("Enter 4 to exit the application");
choice := integer'value(Get_Line);
 
exit when choice = 4;
 
case choice is
when 1 => showGrid(sketch);
when 2 => commandMenu(thePen, xcorr, ycorr);
when 3 => Put_Line("Pen is "
& penPosition(thePen));
when others => Put_Line("Invalid input");
end case;
end loop;
end showMenu;
 
-- function penPosition - checks changes the state of whether the pen is
-- raised up or down. If value is True, pen is rasied up
function penPosition(thePen : in out Boolean) return String is
str1 : constant String := "raised UP";
str2 : constant String := "raised DOWN";
begin
if thePen = True then
thePen := False;
return str2;
else
thePen := True;
end if;
 
return str1;
end penPosition;
 
-- procedure command menu - provides a list of directions for the turtle
-- to move along the grid
procedure commandMenu(thePen : in out Boolean; xcorr : in out Integer;
ycorr : in out Integer) is
 
choice : Integer := 0;
begin
while choice <= 0 or choice > 5 loop
Set_Col(15);
Put("Command Menu");
Set_Col(15);
Put("============");
New_Line(2);
 
Put_Line("To move North enter 1");
Put_Line("To move South enter 2");
Put_Line("To move East enter 3");
Put_Line("To move West enter 4");
Put_Line("To return to previous menu enter 5");
choice := integer'value(Get_Line);
 
case choice is
when 1 => moveCursor(thePen, sketch, xcorr, ycorr, choice);
when 2 => moveCursor(thePen, sketch, xcorr, ycorr, choice);
when 3 => moveCursor(thePen, sketch, xcorr, ycorr, choice);
when 4 => moveCursor(thePen, sketch, xcorr, ycorr, choice);
when 5 => showMenu(xcorr, ycorr, thePen, sketch);
when others => Put_Line("Invalid choice");
end case;
end loop;
end commandMenu;
 
 
-- procedure moveCursor - moves the cursor around the board by taking the
-- x and y coordinates from the user. If the pen is down, a character is
-- printed at that location. If the pen is up, nothing is printed but the
-- cursor still moves to that position
procedure moveCursor(thePen : in Boolean; sketch : in out Sketch_Pad;
xcorr : in out Integer; ycorr : in out Integer;
ch : in Integer) is
 
begin
if thePen = True then -- pen up so move cursor but do not draw
case ch is
when 1 => xcorr := xcorr - 1; ycorr := ycorr;
sketch(xcorr, ycorr) := ' ';
when 2 => xcorr := xcorr + 1; ycorr := ycorr;
sketch(xcorr, ycorr) := ' ';
when 3 => xcorr := xcorr; ycorr := ycorr + 1;
sketch(xcorr, ycorr) := ' ';
when 4 => xcorr := xcorr; ycorr := ycorr - 1;
sketch(xcorr, ycorr) := ' ';
when others => Put("Unreachable Code");
end case;
 
else -- pen is down so move cursor and draw
case ch is
when 1 => xcorr := xcorr - 1; ycorr := ycorr;
sketch(xcorr, ycorr) := '#';
when 2 => xcorr := xcorr + 1; ycorr := ycorr;
sketch(xcorr, ycorr) := '#';
when 3 => xcorr := xcorr; ycorr := ycorr + 1;
sketch(xcorr, ycorr) := '#';
when 4 => xcorr := xcorr; ycorr := ycorr - 1;
sketch(xcorr, ycorr) := '#';
when others => Put("Unreachable Code");
end case;
end if;
end moveCursor;
 
-- procedure showGrid - prints the sketchpad showing the plotted moves
procedure showGrid(sketch : in Sketch_Pad) is
begin
New_Line;
 
for I in sketch'Range(1) loop
for J in sketch'Range(2) loop
Put(character'image(sketch(I,J)));
end loop;
New_Line;
end loop;
New_Line;
end showGrid;
 
begin
New_Line;
 
initGrid(sketch);
showMenu(xcorr, ycorr, thePen, sketch);
 
New_Line;
end main;
 

Julia[edit]

Translation of: Wren

Outputs a PNG file.

using Luxor, Colors
 
function house(🐢, x, y, siz)
oldorientation = 🐢.orientation
xpos, ypos = 🐢.xpos, 🐢.ypos
# house wall
Reposition(🐢, x, y)
Rectangle(🐢, siz, siz)
# roof
Reposition(🐢, x - siz / 2, y - siz / 2)
Turn(🐢, -60)
Forward(🐢, siz)
Turn(🐢, 120)
Forward(🐢, siz)
# turtle_demo
doorheight, doorwidth = siz / 2, siz / 4
Pencolor(🐢, 0, 0, 0)
Reposition(🐢, x, y + doorheight / 2)
Rectangle(🐢, doorwidth, doorheight)
# window
windowheight, windowwidth = siz /3, siz / 4
Reposition(🐢, x + siz / 4, y - siz / 4)
Rectangle(🐢, windowwidth, windowheight)
Reposition(🐢, x - siz / 4, y - siz / 4)
Rectangle(🐢, windowwidth, windowheight)
Orientation(🐢, oldorientation)
Reposition(🐢, xpos, ypos)
end
 
function barchart(🐢, data, x, y, siz)
oldorientation = 🐢.orientation
xpos, ypos = 🐢.xpos, 🐢.ypos
maxdata = maximum(data)
# scale to fit within a square with sides `siz` and draw bars of chart
barwidth = siz / length(data)
Pencolor(🐢, 1.0, 0.0, 0.5)
Reposition(🐢, x, y)
for n in data # draw each bar in chart
barheight = n * siz / maxdata
Reposition(🐢, x, y - barheight / 2)
Rectangle(🐢, barwidth, barheight)
x += barwidth
end
Orientation(🐢, oldorientation)
Reposition(🐢, xpos, ypos)
end
 
function testturtle(width = 400, height = 600)
dra = Drawing(600, 400, "turtle_demo.png")
origin()
background("midnightblue")
🐢 = Turtle()
Pencolor(🐢, "cyan")
Penwidth(🐢, 1.5)
house(🐢, -width / 3, height / 7, width / 2)
barchart(🐢, [15, 10, 50, 35, 20], width / 8, height / 8, width / 2)
finish()
end
 
testturtle()
 

[edit]

Translation of: Quackery
to rectangle :width :height
repeat 2 [
forward :height
left 90
forward :width
left 90 ]
end
 
to square :size
rectangle size size
end
 
to triangle :size
repeat 3 [
forward size
right 120 ]
end
 
to house :size
left 90
square size
triangle size
right 90
end
 
to max :lst
if equalp count lst 1 [ output first lst ]
make "x max butfirst lst
if x > first lst [ output x ]
output first lst
end
 
to barchart :lst :size
right 90
if emptyp lst [ stop ]
make "scale size / (max lst)
make "width size / count lst
foreach lst [
rectangle ? * scale width
forward width ]
back size
left 90
end
 
clearscreen hideturtle
house 150
penup
right 90 forward 10 left 90
pendown
barchart [ 0.5 0.33333 2 1.3 0.5 ] 200
left 90 back 10 right 90
Output:

https://imgur.com/4V1UrcN

Perl[edit]

Added octangle window to house attic.

#!/usr/bin/perl
 
use strict; # https://rosettacode.org/wiki/Simple_turtle_graphics
use warnings;
use Tk;
use List::Util qw( max );
 
my $c; # the canvas
 
# turtle routines
 
my $pen = 1; # true for pendown, false for penup
my @location = (0, 0); # upper left corner
my $direction = 0; # 0 for East, increasing clockwise
my @stack;
my $radian = 180 / atan2 0, -1;
sub dsin { sin $_[0] / $radian }
sub dcos { cos $_[0] / $radian }
sub save { push @stack, [ $direction, @location ] }
sub restore { ($direction, @location) = @{ pop @stack } }
sub turn { $direction += shift }
sub right { turn shift }
sub left { turn -shift }
sub forward
{
my $x = $location[0] + $_[0] * dcos $direction;
my $y = $location[1] + $_[0] * dsin $direction;
$pen and $c->createLine( @location, $x, $y, -width => 3 );
@location = ($x, $y);
}
sub back { turn 180; forward shift; turn 180 }
sub penup { $pen = 0 }
sub pendown { $pen = 1 }
sub text { $c->createText( @location, -text => shift ) }
 
# make window
 
my $mw = MainWindow->new;
$c = $mw->Canvas(
-width => 900, -height => 900,
)->pack;
$mw->Button(-text => 'Exit', -command => sub {$mw->destroy},
)->pack(-fill => 'x');
$mw->after(0, \&run);
MainLoop;
-M $0 < 0 and exec $0;
 
sub box
{
my ($w, $h) = @_;
for (1 .. 2)
{
forward $w;
left 90;
forward $h;
left 90;
}
}
 
sub house
{
my $size = shift;
box $size, $size;
right 90;
for ( 1 .. 3 )
{
right 120;
forward $size;
}
penup;
left 90;
forward $size;
left 90;
save;
forward $size * 1 / 4;
pendown;
box $size / 4, $size / 2;
penup;
forward $size * 3 / 8;
left 90;
forward $size / 4;
right 90;
pendown;
box $size / 4, $size / 4;
penup;
restore;
save;
forward $size / 2;
left 90;
forward $size + 40;
right 90;
pendown;
for (1 .. 8)
{
forward 15;
left 45;
forward 15;
}
restore;
penup;
}
 
sub graph
{
save;
my $size = shift;
my $width = $size / @_;
my $hscale = $size / max @_;
for ( @_ )
{
box $width, $hscale * $_;
save;
penup;
forward $width / 2;
left 90;
forward 10;
text $_;
pendown;
restore;
forward $width;
}
restore;
}
 
sub run
{
penup;
forward 50;
right 90;
forward 400;
pendown;
house(300);
penup;
forward 400;
pendown;
graph( 400, 2,7,4,5,1,8,6 );
}

Phix[edit]

Library: Phix/pGUI
Library: Phix/online

You can run this online here.

--
-- demo\rosetta\Simple_turtle_graphics.exw
-- =======================================
--
with javascript_semantics
include pGUI.e
Ihandle canvas, dlg
cdCanvas cdcanvas

atom x = 0,
     y = 0,
     direction = 0
bool pen_down = true

procedure walk(atom distance)
    //
    // Move forward by distance pixels.
    //
    atom start_x = x,
         start_y = y,
         angle = direction*PI/180
    x += distance*sin(angle)
    y += distance*cos(angle)
    if pen_down then
        cdCanvasLine(cdcanvas,start_x,start_y,x,y)
    end if
end procedure

procedure right(atom angle)
    direction = remainder(direction+angle,360)
end procedure

procedure left(atom angle)
    right(360-angle)
end procedure

procedure penup()
    pen_down = false
end procedure

procedure pendown(atom colour=CD_BLACK)
    pen_down = true
    cdCanvasSetForeground(cdcanvas, colour) 
end procedure

procedure move(sequence s)
    -- s is a list of angles (odd elements)
    --        and distances (even elements)
    for i=1 to length(s) do
        if odd(i) then
            right(s[i])
        else
            walk(s[i])
        end if
    end for
end procedure

procedure rectangle(atom width, height)
    move({0,height,90,width,90,height,90,width,90})
end procedure

procedure draw_house(atom width, height)
    //
    // Draw a house at the current x,y
    // direction must be 0 for house to be upright
    //
    // house walls
    rectangle(width, height)
    // door (maybe some windows too would be nice...)
    penup()
    move({90,width/7,-90})
    pendown(CD_BLUE)
    rectangle(width/8,height/2.5)
    penup()
    move({-90,width/7,90})
    // roof
    walk(height)
    pendown(CD_RED)
    atom a = arctan(width/height)*CD_RAD2DEG,
         d = sqrt(width*width+height*height)/2
    move({a,d,180-a*2,d})
    penup()
    // return to origin({qw,qh}) and direction 0:
    move({90+a,width,-90,height,180})
end procedure

procedure draw_barchart(sequence nums, atom w, h)
    // draw a barchart occupying the middle 60% of w,h
    // nums can contain +ve and/or -ve values.
    integer n = length(nums)
    atom mx = max(max(nums),0),
         mn = min(min(nums),0),
         r = mx-mn,                 -- range
         zl = abs(mn)/r*h*0.6+h/5,  -- zero line
         bw = w*0.6/n               -- bar width
    move({90,w/5,-90,zl})
    pendown()
    for i=1 to n do
        atom ni = nums[i]/r*h*0.6
        if ni>0 then
            rectangle(bw,ni)
        else
            pendown(CD_RED)
            right(90)
            rectangle(-ni,bw)
            left(90)
            pendown(CD_BLACK)
        end if
        move({90,bw,-90})
    end for
    penup()
    // return to origin({w/2,0}) and direction 0:
    move({180,zl,90,w/5+bw*n,90})
end procedure

function redraw_cb(Ihandle /*ih*/, integer /*posx*/, /*posy*/)
    integer {width, height} = IupGetIntInt(canvas, "DRAWSIZE")
    cdCanvasActivate(cdcanvas)
    cdCanvasClear(cdcanvas)
    atom qw = width/4,
         qh = height/4
    penup()
    move({0,qh,90,qw,-90})
    pendown()

    draw_house(qw,qh)       -- (at current x,y)

    penup()
    move({180,qh,90,qw,90}) -- return to {0,0}

    move({90,width/2,-90})  -- barchart in the right half

    draw_barchart({0.5, -4/3, 2, 1.3, 0.5},width/2,height)

    move({-90,width/2,90})  -- return to {0,0}

    -- sanity checks
    if round(x)!=0 then ?9/0 end if
    if round(y)!=0 then ?9/0 end if
    if round(direction)!=0 then ?9/0 end if

    cdCanvasFlush(cdcanvas)
    return IUP_DEFAULT
end function
IupOpen()
canvas = IupCanvas(Icallback("redraw_cb"),"RASTERSIZE=600x400")
dlg = IupDialog(canvas,`TITLE="Simple turtle graphics"`)
IupMap(dlg)
cdcanvas = cdCreateCanvas(CD_IUP, canvas)
IupShow(dlg)
IupSetAttribute(canvas, "RASTERSIZE", NULL) -- release the minimum limitation
if platform()!=JS then
    IupMainLoop()
    IupClose()
end if

Python[edit]

Translation of: Quackery
from turtle import *
 
def rectangle(width, height):
for _ in range(2):
forward(height)
left(90)
forward(width)
left(90)
 
def square(size):
rectangle(size, size)
 
def triangle(size):
for _ in range(3):
forward(size)
right(120)
 
def house(size):
right(180)
square(size)
triangle(size)
right(180)
 
def barchart(lst, size):
scale = size/max(lst)
width = size/len(lst)
for i in lst:
rectangle(i*scale, width)
penup()
forward(width)
pendown()
penup()
back(size)
pendown()
 
clearscreen()
hideturtle()
house(150)
penup()
forward(10)
pendown()
barchart([0.5, (1/3), 2, 1.3, 0.5], 200)
penup()
back(10)
pendown()
Output:

https://imgur.com/oBXTDem

Quackery[edit]

  [ $ "turtleduck.qky" loadfile ] now!
 
[ behead do
rot witheach
[ do 2over 2over
v< if 2swap
2drop ] ] is largest ( [ --> n/d )
 
[ 2 times
[ 2dup walk
-1 4 turn
2over walk
-1 4 turn ]
2drop 2drop ] is rectangle ( n/d n/d --> )
 
[ 2dup rectangle ] is square ( n/d --> )
 
[ 3 times
[ 2dup walk
1 3 turn ]
2drop ] is triangle ( n/d --> )
 
[ 1 2 turn
2dup square triangle
1 2 turn ] is house ( n/d --> )
 
[ stack ] is bar.width ( --> s )
 
[ stack ] is bar.scale ( --> s )
 
[ join temp put
dup size n->v
temp share do v/ 1/v
join bar.width put
dup largest
temp share do v/
join bar.scale put
witheach
[ do
bar.scale share do v/
bar.width share do
rectangle
bar.width share do fly ]
temp take do -v fly
bar.width release
bar.scale release ] is barchart ( [ n/d --> )
 
turtle
150 1 house
10 1 fly
' [ [ 1 2 ] [ 1 3 ] [ 2 1 ] [ 13 10 ] [ 1 2 ] ] 200 1 barchart
-10 1 fly
Output:

https://imgur.com/B7YbTbZ

Wren[edit]

Library: DOME
Library: Wren-turtle
import "dome" for Window
import "graphics" for Canvas, Color
import "./turtle" for Turtle
 
class Main {
construct new(width, height) {
Window.resize(width, height)
Canvas.resize(width, height)
Window.title = "Simple turtle graphics"
_w = width
_h = height
}
 
init() {
Canvas.cls(Color.white)
_t = Turtle.new()
drawHouse(_w/4)
barChart([15, 10, 50, 35, 20], _w/3)
}
 
drawHouse(size) {
// save initial turtle position and direction
var saveX = _t.x
var saveY = _t.y
var saveD = _t.dir
 
_t.pen.width = 2
 
// draw house
_t.drawRect(_w/4, _h/2, size, size)
 
// draw roof
_t.right(30)
_t.walk(size)
_t.right(120)
_t.walk(size)
 
// draw door
var doorWidth = (size/4).floor
var doorHeight = (size/2).floor
_t.drawRect(_w/4 + doorWidth/2, _h/2 + doorHeight, doorWidth, doorHeight)
 
// draw window
var windWidth = (size/3).floor
var windHeight = (size/4).floor
_t.drawRect(_w/4 + size/2, _h/2 + size/2, windWidth, windHeight)
 
// restore initial turtle position and direction
_t.x = saveX
_t.y = saveY
_t.dir = saveD
}
 
// nums assumed to be all non-negative
barChart(nums, size) {
// save intial turtle position and direction
var saveX = _t.x
var saveY = _t.y
var saveD = _t.dir
 
// find maximum
var max = 0
for (n in nums) if (n > max) max = n
 
// scale to fit within a square with sides 'size' and draw chart
var barWidth = (size / nums.count).floor
var startX = _w / 2 + 20
var startY = _h / 2
for (i in 0...nums.count) {
var barHeight = (nums[i] * size / max).round
_t.drawRect(startX, startY - barHeight, barWidth, barHeight)
startX = startX + barWidth
}
 
// restore intial turtle position and direction
_t.x = saveX
_t.y = saveY
_t.dir = saveD
}
 
update() {}
 
draw(alpha) {}
}
 
var Game = Main.new(600, 600)
Output:
Similar to Quackery image except that the house has a door and a single window.