Safe Haskell | None |
---|---|
Language | Haskell98 |
Extras.Turtle
Contents
Description
Primitive Turtle commands for doing Turtle graphics. The commands follow the original LOGO naming convention, but they integrate seamlessly with the rest of CodeWorld.
Synopsis
- data Turtle
- type TurtleCommand = Turtle -> Turtle
- type TurtleProgram = [TurtleCommand]
- fd :: Number -> TurtleCommand
- bk :: Number -> TurtleCommand
- rt :: Number -> TurtleCommand
- lt :: Number -> TurtleCommand
- seth :: Number -> TurtleCommand
- setxy :: Point -> TurtleCommand
- home :: TurtleCommand
- pu :: TurtleCommand
- pd :: TurtleCommand
- overxy :: (Point -> Point) -> TurtleCommand
- sethome :: Point -> TurtleCommand
- origin :: Point
- turtle :: TurtleCommand
- randomized :: (Number -> TurtleCommand, Number) -> TurtleCommand
- repeatRandom :: (Number, TurtleProgram) -> TurtleCommand
- repeat :: (Number, [value -> value]) -> value -> value
- run :: [value -> value] -> value -> value
- foreach :: ([input], input -> output) -> [output]
- forloop :: (state, Predicate state, state -> state, state -> output) -> [output]
- type Track = [Point]
- track :: TurtleCommand -> Track
- tracks :: TurtleCommand -> [Track]
- partialTracks :: TurtleCommand -> [[Track]]
- randomTracks :: ([Number], TurtleCommand) -> [Track]
- trackLength :: Track -> Number
- alongTrack :: Track -> Number -> (Point, Number)
- polylines :: [Track] -> Picture
- thickPolylines :: ([Track], Number) -> Picture
- solidPolygons :: [Track] -> Picture
- dottylines :: [Track] -> Picture
- dottyline :: [Point] -> Picture
- turtleExamples :: Program
- customTurtle :: (Number, Number, Number) -> Turtle
- turtlePosition :: Turtle -> Point
- turtleAngle :: Turtle -> Number
Documentation
Turtle API
To use the extra features in this module, you must begin your code with this line:
import Extras.Turtle
This module allows you to use Turtle
programs to create
Turtle graphics.
For general information about Turtle graphics, you can consult
the Wikipedia page at https://en.wikipedia.org/wiki/Turtle_graphics
This module defines primitive Turtle commands to move and turn
a Turtle. Two functions, run
and repeat
, can be used
to create new Turtle commands out of lists of commands.
The primitive Turtle commands in this module follow the command names,
syntax and semantics of the original LOGO commands as much as possible,
but this module is not a standalone LOGO interpreter.
Instead, Turtle programs in this module can be
used to create Tracks, which are lists of Points. You can then
feed those Tracks into regular CodeWorld graphical primitives,
such as polyline
, curve
or polygon
. You can also use
the CodeWorld language to process the list of Points further
or to combine the shapes generated by a Turtle program with
shapes generated in other ways.
Example:
program = drawingOf(polyline(track(turtleProgram))) turtleProgram = repeat(4, [fd(1), rt(90) ])
The example above will show a square of side length 1 with its lower left corner at the origin.
A Turtle data structure contains information about the current state of a Turtle, such as its current position, its current heading and whether its pen is up or down. You cannot manipulate the inner workings of this structure. Instead, you use Turtle programs to change the internal state of a Turtle.
A Turtle is initially positioned at the origin, with the pen
down and pointing upwards. You can use the command sethome
to reset the initial position to a different point.
type TurtleCommand = Turtle -> Turtle #
A Turtle command is a function that modifies the state of a Turtle.
type TurtleProgram = [TurtleCommand] #
A Turtle program is a sequence of Turtle commands.
You can use the function
run
to convert a Turtle program into a single Turtle command that will
work the same way as the built-in Turtle commands. This type alias is not
used in this module, but you can use it in your code to specify the type of
a Turtle program. The examples in the documentation of sethome
and
overxy
show how you can use it.
Standard Turtle commands
fd :: Number -> TurtleCommand #
Move the Turtle forward by the given number of units
bk :: Number -> TurtleCommand #
Move the Turtle backward by the given number of units
rt :: Number -> TurtleCommand #
Turn the Turtle right (clockwise) by the given number of degrees
lt :: Number -> TurtleCommand #
Turn the Turle left (counter-clockwise) by the given number of degrees
seth :: Number -> TurtleCommand #
Set the Turtle heading by first orienting it upright (pointing upwards) and then rotating it clockwise by the given number of degrees
setxy :: Point -> TurtleCommand #
Move the Turtle to the absolute position given by the X and Y coordinates
home :: TurtleCommand #
Move the Turtle to the center and set the heading to upright
pu :: TurtleCommand #
Pen Up: Stop tracing the positions of the Turtle.
If the pen was down, this command will end the current
Track
. Otherwise, the command is ignored.
pd :: TurtleCommand #
Pen Down: Start tracing the positions of the Turtle.
If the pen was up, this command will start a new Track
.
Otherwise, the command is ignored.
Specific Turtle commands (only in CodeWorld)
overxy :: (Point -> Point) -> TurtleCommand #
Move the Turtle according to the given Point transformation
Example:
turtleProgram :: TurtleProgram turtleProgram = [ pu, sethome(origin), fd(1), pd , repeat(6, [overxy(turn) ]) , pu, fd(1), pd , turtle ] turn(point) = rotatedPoint(point, 360/6)
The example above constructs a hexagon without
altering the heading of the turtle. It
also illustrates how you can mix Turtle commands
with other CodeWorld functions, such as
rotatedPoint
.
sethome :: Point -> TurtleCommand #
Set the starting point for the trace to the given coordinates. Any Track recorded before using this command will be discarded. This command is only useful for initializaing your Turtle program, so that it starts at a different position.
You can also use this command to move the Turtle before starting tracing your tracks. For example, to start your Turtle with the pen up, you can use the following pattern:
turtleProgram :: TurtleProgram turtleProgram = [pu,sethome(origin)] ++ otherCommands
turtle :: TurtleCommand #
A simple drawing of a Turtle that can be used to observe its current position and heading.
randomized :: (Number -> TurtleCommand, Number) -> TurtleCommand #
randomized(cmd,maxnum)
can be used to run one of the following
Turtle commands: fd
, bk
, rt
, lt
, seth
, or with a custom command
that takes a Number as the argument, so that the command is run
with a random number between 0 (included) and maxnum (excluded).
If this function is used inside tracks
instead of inside randomTracks
,
the value maxnum/2
will be passed to cmd
instead of a random number.
Example:
program = randomDrawingOf(draw) where draw(random) = pictures([ colored(thickPolyline(t,0.25),c) | t <- randomTracks(random1,turtleProgram) | c <- random2 ]) where random1 = randomNumbers(random#1) random2 = [ RGB(r,g,b) | [r,g,b] <- groups(randomNumbers(random#2),3) ] turtleProgram = repeat(10, [pd,makeTrack,pu,home]) makeTrack = repeat(100, [ randomized(\r -> fd(2*r-1), 1.5) , randomized(\r -> rt(90*truncation(r)), 4) ])
repeatRandom :: (Number, TurtleProgram) -> TurtleCommand #
Repeat a turtle Program a random number of times up to the given
maximum. This is a specialized version of randomized
.
Program control (re-exported from Extras.Util)
repeat :: (Number, [value -> value]) -> value -> value #
Repeat a sequence of transformations a given number of times.
For example, the expression repeat(2,[f1,f2])(x)
is the same as
f2(f1(f2(f1(x))))
.
If you use a negative number or a number with decimals, the sign and
the decimals will be ignored. For example, repeat(-7.3,seq)
will repeat
7
times.
run :: [value -> value] -> value -> value #
Run a sequence of transformations in order on the given initial state.
For example, the expression run([f1,f2,f3])(x)
is the same as f3(f2(f1(x)))
foreach :: ([input], input -> output) -> [output] #
Constructs a list by applying a function to all the elements of a given list
forloop :: (state, Predicate state, state -> state, state -> output) -> [output] #
forloop(input,cond,next,output)
produces a list of outputs,
where each output is generated by repeatedly applying output
to the current state
, whose value changes after each iteration
of the loop. The state
is initially set to the value given by
input
, and new states are generated from it by applying
next
to the current state
.
The whole process continues for as long as the current state
satisfies the predicate given by cond
.
For example, the following code will produce [0,0,6,6,12,12,20,20]:
forloop(input,cond,next,output) where input = (1,0) cond(n,sum) = n <= 10 next(n,sum) = (n+1,if even(n) then sum+n else sum) output(n,sum) = sum
Using forloop to implement other iteration functions
Basically, any iteration function can be used to implement the rest.
A few examples of implementations based on forloop
follow.
iterated(next,input) = forloop(input,\x -> True,next,itself)
foreach(list,f) = forloop(list,nonEmpty,\l -> rest(l,1),\l -> f(l#1))
recurWhile(check,f)(v) = last(v:forloop(input,cond,next,output),1)#1 where input = (x,f(x)) cond (x, _) = check(x) next (_,fx) = (fx,f(fx)) output(_,fx) = fx
Tracks
track :: TurtleCommand -> Track #
Convert a Turtle command into a Track. This is a simplified version
of tracks
that can be used when you know for sure that your Turtle
command will create only one Track. When that is not the case, the
different tracks will be joined together into a single Track, as
if the Turtle pen was always down.
tracks :: TurtleCommand -> [Track] #
Convert a Turtle command into a list of Tracks. A Track ends when
you use the pu
Turtle command. After that, the Turtle position
is tracked internally, but no points are added to the Track. A
new Track can be started by using the pd
command.
Example:
program = drawingOf(pictures(fig) & solidRectangle(20,20)) where fig = [ colored(thickPolyline(t,0.03),c) | t <- tracks(turtleProgram) | c <- repeating([red, purple, blue, green, orange, yellow]) ] turtleProgram = run(foreach([1..360], singleLine)) singleLine(x) = run([pd, fd(x/100), lt(59), pu])
partialTracks :: TurtleCommand -> [[Track]] #
A list of all the partial tracks corresponding to the given command. Useful to observe a step-by-step construction of the tracks.
Example:
import Extras.Turtle import Extras.Cw(slideshow) program = slideshow(foreach(slides,\s -> polylines(s))) slides = partialTracks(turtleProgram) turtleProgram = repeat(7, [square, rt(360/7)]) square = repeat(4, [fd(4), rt(90)])
The example above creates a slide show that illustrates the
construction of seven squares. It uses the function slideshow
from the module Extras.Cw
randomTracks :: ([Number], TurtleCommand) -> [Track] #
Use this function to run Turtle programs that can use random numbers.
You need to provide an infinite list of numbers, where each of them
is between 0 (included) and 1 (excluded).
A simple way to use random numbers is with the
function randomDrawingOf
from the module Cw.
Example 1:
import Extras.Turtle import Extras.Cw(randomDrawingOf) program = randomDrawingOf(draw) where draw(random) = polylines(randomTracks(random,turtleProgram)) turtleProgram = repeat(50, [repeatRandom(100, randomLine), rt(180)]) randomLine = [fd(10),bk(9.8),rt(2)]
Example 2:
program = randomDrawingOf(draw) where draw(random) = colored(sun,yellow) where sun = solidCircle(2) & polylines(randomTracks(random,turtleProgram)) turtleProgram = repeat(500, [ pu, home, randomized(seth,360), fd(2) , pd, randomized(fd,4) ])
trackLength :: Track -> Number #
The length of the given Track
alongTrack :: Track -> Number -> (Point, Number) #
If travel = alongTrack(points)
, then travel
is a function that can be
used to traverse a track traveling at 1 unit per second. The value
travel(t)
is a pair, where the first element is the location at time t
and the second element is the angle of the corresponding segment in
the given track.
Example:
program = animationOf(movie) where movie(t) = placedAlong(turtleShape,travel(remainder(4*t,tlen))) & polyline(turtleTrack) placedAlong(pic,((x,y),a)) = translated(rotated(turtleShape,a),x,y) travel = alongTrack(turtleTrack) tlen = trackLength(turtleTrack) greenTurtle = colored(turtleShape, green) turtleShape = rotated(thickPolygon(track(turtle),0.1),-90) turtleTrack = track(run(figs(11, poly(7,18/7)))) figs(n,fig) = [repeat(n,[run(fig),lt(360/n)])] poly(n,len) = figs(n,[fd(len)])
Drawing extensions
thickPolylines :: ([Track], Number) -> Picture #
Draw each Track in a list of tracks as a thick polyline of the given thickness.
solidPolygons :: [Track] -> Picture #
Draw each Track in a list of tracks as a solid polygon.
dottylines :: [Track] -> Picture #
Draw all the points in all the polylines of a list of tracks.
Examples
The first example is based on the following code:
program = drawingOf(polylines(tracks(turtleProgram))) turtleProgram = run(figs(14, poly(7,2))) figs(n,fig) = [repeat(n,[run(fig),lt(360/n)])] poly(n,len) = figs(n,[fd(len)])
Note how normal CodeWorld functions are integrated with the turtle commands, even in the presence of recursion.
The rest of the examples are taken from the Logo 15-word challenge at http://www.mathcats.com/gallery/15wordcontest.html
The keywords before each name show information about how many repeat
statements each example used, whether it used advanced control functions,
such as foreach
or forloop
, and whether advanced math functions, such
as trig functions or logarithms were used to define the shape. You can
use this information to estimate the relative complexity of the shapes.
Here are the codes for the examples:
ring = let fd'(x) = fd(x/20) bk'(x) = bk(x/20) in sethome(4,0) : [repeat(16, [ fd'(85), lt(60), fd'(107) , bk'(72), lt(53), fd'(74)])] blade = let fd'(x) = fd(x/20) bk'(x) = bk(x/20) in sethome(-3,-4) : [repeat(36,[fd'(60),rt(61),bk'(80),lt(41),fd'(85),rt(41)])] hypercube = sethome(-3.5,1.5) : [repeat(8,[repeat(4,[rt(90),fd(3)]),bk(3),lt(45)])] star1 = sethome(-2.5,-3.5) : [repeat(18,[repeat(5,[rt(40),fd(10),rt(120)]),rt(20)])] fanflower = sethome(-1.5,5) : [repeat(12,[repeat(75,[fd(4),bk(4),rt(2)]),fd(10)])] jagged1 = sethome(0,-6) : [repeat(4,[repeat(30,[lt(90),fd(0.2),rt(90),fd(0.2)]),rt(90)])] jagged2 = sethome(4,-6) : [repeat(4,[repeat(20,[lt(160),fd(1.5),rt(160),fd(1.5)]),rt(90)])] jagged3 = sethome(2.5,-7) : lt(5) : [repeat(8,[repeat(20,[lt(170),fd(1.5),rt(170),fd(1.5)]),rt(45)])] pentahexa = sethome(-0.5,-2) : [repeat(5,[repeat(6,[fd(4),lt(72)]),lt(144)])] leaves(n) = -- Useful values for n: 1 to 7 sethome(-2.7,-1.2) : [repeat(8,[rt(45),repeat(n,[repeat(90,[fd(0.1),rt(2)]),rt(90)])])] roses(l,n,k) = foreach([1..360*n],\i -> run([fd(l/10),rt(i+x)])) where x = (2*k - n) / (2*n) -- Useful values: -- roses 5 5 3 -- roses 5 7 3 -- roses 5 10 7 -- roses 5 12 5 bullring = sethome(-3,-0.5) : foreach([0..1002],\i -> run([fd(0.4),seth(360*i^3 / 1002)])) squareSpiral = foreach([1..800],\i -> run([fd(i/40),rt(89)])) diaphragm = foreach([1..100],\i -> let fd'(x) = fd(x/20) in run([fd'(5+i),rt(45),fd'(10+i),rt(60)])) octagons = foreach( [1..15] , \i -> repeat(5, [repeat(8, [fd (0.4 + i*0.2), rt(45)]), rt(72)]) ) circleSpiral = foreach([0,0.05..4],\i -> repeat(180,[fd(i/10),rt(1)])) pentaStarSpiral = foreach( [0,3..96] , \l -> run([repeat(5,[fd(l/20),rt(144)]),fd(l/20),rt(30)])) octaStarSpiral = foreach( [0,4..120], \l -> run([repeat(8,[fd(l/30),rt(135)]),pu,fd(2*l/30),rt(30),pd])) sqcirc1 = foreach( [1..36], \i -> run([repeat(36,[fd(0.5),rt(10)]),fd(i/20),rt(90),fd(i/20)])) pentapenta = foreach([10,9..1],\i -> repeat(5,[repeat(5,[fd(i/2),lt(72)]),lt(72)])) hexagon2 = foreach( [100,95..10] , \i -> repeat(6,[repeat(6,[fd(i/20),lt(60)]),lt(60)])) hexagon1 = foreach( [100,50..50] , \i -> repeat(6,[repeat(6,[fd(i/20),lt(60)]),lt(60)])) jaggystar = fd(-6.5) : foreach([0..2200],\i -> run([fd(0.75*sin(i)), rt(i^2)])) fish1 = foreach([1..360],\t -> run(cmds(t))) where cmds(t) = [ overxy(\(x,y) -> (8*cos(2*t),y)) , overxy(\(x,y) -> (x,x*cos(t))) , home ] fish2 = foreach([-315..315],\t -> setxy(t*sin(t)/50,0.5*t*cos(2*t)/50)) gillyflower = foreach( [1..450] , \i -> let a = 73 * sin(i) in run([fd(a/20),rt(88*cos(a))])) petals(n) = foreach([0..180],\t -> run([seth(t),fd(10*sin(t*n)),home])) eye = foreach([1..1800],\i -> run([fd(log(i)/10),bk(sin(i)),rt(10)])) spirotunnel = forloop( 1,(<= 160),\i -> i + sin(i) , \i -> run([fd(i/80),bk(i/10),rt(51)]) ) neutronStar = forloop(1,(<= 4),\i -> i + sin(i+7)/2 ,\i -> let a = 2*i in run([fd(a),bk(a),rt(41)]))
Custom Turtles
customTurtle :: (Number, Number, Number) -> Turtle #
A customTurtle(x,y,angle)
is a Turtle positioned at the point (x,y)
,
which is pointing in the direction specified by the given angle
,
where the angle is measured in
the normal CodeWorld way, i.e., 0 means pointing to the right,
and the angles increase counter-clockwise.
You can use this Turtle to have finer control over the Turtle.
Notes:
- Do not use the commands
pu
andpd
on custom turtles, because they do not have any visible effect. You must generate your own tracks explicitly with the help of theturtlePosition
function. - Do not use
randomized
either, because a custom turtle does not have access to random numbers. - Probably, the only Turtle commands that are useful with custom
turtles are
fd
,bk
,rt
andlt
. Any other handling of custom turtles should be done with regular CodeWorld functions.
Example:
program = randomDrawingOf(draw) where draw(random) = colored(thickPolyline(turtleTrack,0.2),red) where turtleTrack = forloop(input,cond,next,output) input = (customTurtle(-10,-10,0), random) cond(t,_) = x < 12 && y < 12 where (x,y) = turtlePosition(t) next(t,r) = ( run([mayTurn(r#1), fd(2*r#2)])(t), rest(r,2) ) output(t,_) = turtlePosition(t) mayTurn(r) = if r < 0.5 then turned else itself turned(t) = if turtleAngle(t) < 45 then lt(90)(t) else rt(90)(t)
The example above randomly moves a custom turtle either up or to the right.
The turtle starts at the lower left corner of the output,
and it moves until it reaches either the top or the
right side of the output, whichever is reached first. This example
uses randomDrawingOf
from Extras.Cw and itself
from Extras.Util.
turtlePosition :: Turtle -> Point #
The current position of the given Turtle.
turtleAngle :: Turtle -> Number #
The orientation of the given Turtle measured in the normal way that CodeWorld uses angles, so that 0 means pointing to the right, and angles increase counter-clockwise.