Simple turtle graphics: Difference between revisions
m (→{{header|J}}) |
m (J: leave the turtle visible) |
||
Line 341: | Line 341: | ||
left 90 |
left 90 |
||
pen 1 [ penColor Blue |
pen 1 [ penColor Blue |
||
barchart 0.5 0.3333 2 1.3 0.5; 200 |
barchart 0.5 0.3333 2 1.3 0.5; 200</lang> |
||
pen 0 |
|||
left 90 |
|||
back 10 |
|||
right 90 |
|||
turtleScale 0</lang> |
|||
Opens a window with the indicated content. House is red, barchart is blue. |
Opens a window with the indicated content. House is red, barchart is blue. |
Revision as of 03:53, 24 February 2022
You are encouraged to solve this task according to the task description, using any language you may know.
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!
<lang Action!>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</lang>
- Output:
Screenshot from Atari 8-bit computer
Ada
<lang ada> 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; </lang>
J
Prerequisites (requires a network connection or, for a non-networked system, significant technical savvy), and assumes user is running the a recent version of J's qtide (perhaps J 9.3): <lang J> ;install each cut 'gl2 gles github:zerowords/tgsjo'</lang>
Implementation (note that this is meant to be copied and pasted to a file which J will load, rather than being typed in line by line -- manual typing would work if no errors were made, but that would be a painstaking approach):
<lang J>load'zerowords/tgsjo' rotR 0 0 _90 translate 0 0 _40 clearscreen createTurtle 0 0 0
rectangle=: {{
2 repeats {{ 'width height'=. y forward height left 90 forward width left 90 }} y
}}
square=: {{size=. y
rectangle size,size
}}
triangle=:{{
3 repeats {{size=. y forward size right 120}} y
}}
house=:{{size=. y
left 90 square size triangle size right 90
}}
barchart=: {{'lst size'=. y
if.#lst do. scale=. size%>./lst width=. size%#lst right 90 for_j. lst do. rectangle (j * scale),width forward width end. back size left 90 end.
}}
penColor Red house 150 pen 0 right 90 forward 10 left 90 pen 1 [ penColor Blue barchart 0.5 0.3333 2 1.3 0.5; 200</lang>
Opens a window with the indicated content. House is red, barchart is blue.
Note that we have used the Logo naming convention, which means that the height of our barchart is the width parameter in rectangle (and, likewise, the width of each bar is the height parameter in rectangle)
Julia
Outputs a PNG file. <lang julia>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() </lang>
Logo
<lang Logo>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</lang>
- Output:
Perl
Added octangle window to house attic. <lang perl>#!/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 ); }</lang>
Phix
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
<lang Python>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()</lang>
- Output:
Quackery
<lang Quackery> [ $ "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</lang>
- Output:
Wren
<lang ecmascript>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)</lang>
- Output:
Similar to Quackery image except that the house has a door and a single window.
Yabasic
<lang Yabasic>// Rosetta Code problem: http://rosettacode.org/wiki/Simple_turtle_graphics // Adapted from Python to Yabasic by Galileo, 01/2022
import turtle
sub rectang(width, height)
local i for i = 1 to 2 move(height) turn(-90) move(width) turn(-90) next
end sub
sub square(size)
rectang(size, size)
end sub
sub triang(size)
local i for i = 1 to 3 move(size) turn(120) next
end sub
sub house(size)
turn(180) square(size) triang(size) turn(180)
end sub
sub barchart(lst$, size)
local t$(1), t(1), n, m, i, scale, width n = token(lst$, t$()) redim t(n) for i = 1 to n t(i) = val(t$(i)) if t(i) > m m = t(i) next scale = size/m width = size/n for i = 1 to n rectang(t(i)*scale, width) pen(false) move(width) pen(true) next pen(false) move(-size) pen(true)
end sub
startTurtle() color 255, 255, 255 turn(90) house(150) pen(false) move(10) pen(true) barchart("0.5 0.333 2 1.3 0.5", 200)</lang>