clojure-turtle is a Clojure library that implements the Logo programming language in a Clojure context. Quil is used for rendering.
Logo is a simple language that is useful in introducing programming to beginners, especially young ones. Logo also happens to be a dialect of Lisp. clojure-turtle tries to maintain those beneficial aspects of Logo while using Clojure and Clojure syntax. The goal is to make learning programming and/or Clojure easier by disguising powerful concepts with fun!
clojure-turtle
artifacts are released to Clojars.
If you are using Maven, add the following repository definition to your pom.xml
:
<repository>
<id>clojars.org</id>
<url>http://clojars.org/repo</url>
</repository>
With Leiningen:
[com.google/clojure-turtle "0.3.0"]
With Maven:
<dependency>
<groupId>com.google</groupId>
<artifactId>clojure-turtle</artifactId>
<version>0.3.0</version>
</dependency>
First, install Leiningen.
Second, run a REPL session that has clojure-turtle loaded inside it. This can be done in a couple of ways:
- Create a new Leiningen project and add the clojure-turtle dependency into
project.clj
. Then runlein repl
. - Use git to clone the clojure-turtle repository, move into the
clojure-turtle
working directory, then runlein repl
.
Load the clojure-turtle.core
namespace.
(use 'clojure-turtle.core)
; WARNING: repeat already refers to: #'clojure.core/repeat in namespace: user, being replaced by: #'clojure-turtle.core/repeat
;=> nil
The symbol repeat
is overridden to behave more like the Logo function, but the Clojure core function is still available as clojure.core/repeat
.
Now load a new window that shows our Quil sketch using the new-window
form. The sketch is where our turtle lives and operates.
user=> (new-window {:size [300 480]})
;=> #'user/example
It's forward
, back
, right
, and left
as in Logo. Go forward and back by a length (in pixels). Right and left turn the turtle by an angle, in degrees. It's Clojure syntax, so 'executing commands' (function calls) are done within parentheses.
(forward 30)
;=> #<Atom@4c8829b7: {:y 29.99999999999997, :angle 90, :pen true, :x -1.3113417000558723E-6}>
(right 90)
; #<Atom@4c8829b7: {:y 29.99999999999997, :angle 0, :pen true, :x -1.3113417000558723E-6}>
repeat
is like the Logo function, or like Clojure's repeatedly
. Going from the Logo syntax to clojure-turtle's syntax for repeat
, commands that are being repeated are put within parentheses notation. The square brackets that group the repeated commands are replaced with (all ... )
. The equivalent of the Logo REPEAT 3 [FORWARD 30 RIGHT 90]
would be
(repeat 3 (all (forward 30) (right 90)))
;=> #<Atom@4c8829b7: {:y -2.6226834249807383E-6, :angle 90, :pen true, :x -9.535951726036274E-7}>
Let's see how we can simplify this.
(def side (all (forward 30) (right 90)))
;=> #'user/side
(left 90)
;=> #<Atom@4c8829b7: {:y -2.6226834249807383E-6, :angle 180, :pen true, :x -9.535951726036274E-7}>
(repeat 4 side)
;=> #<Atom@4c8829b7: {:y -1.311341712550984E-5, :angle 180, :pen true, :x -4.76797586142362E-6}>
As you just saw above, we can take the instructions that we pass into repeat
, give them a single name, and refer to that name to get the same effect.
Let's simplify further.
(def square (all (repeat 4 side)))
(left 90)
(square)
So given a named set of instructions, we can invoke the instructions by putting the name in parentheses just like we do for functions like forward
, left
, and repeat
(def square-and-turn (all (square) (left 90)))
(left 90)
(square-and-turn)
(left 45)
(repeat 4 square-and-turn)
The turtle has a pen that it drags along where it goes, creating a drawing. We can pick the pen up and put the pen down when we need to draw unconnected lines. setxy
also teleports the turtle without drawing. setheading
turns the turtle in an exact direction.
(penup)
(forward 113)
(right 135)
(pendown)
(repeat 4 (all (forward 160) (right 90)))
(setxy -100 0)
(setheading 225)
clean
erases all drawing. home
brings the turtle to its original position and direction.
(clean)
(home)
Color can be set for the turtle. A color is specified by a vector of size 1, 3, or 4.
A three-element color vector has 3 integers for the red, green, and blue components of the color (in that order) in the range 0 - 255. (See this page for examples of specifying color in terms of RGB values.)
(def octagon (all (repeat 8 (all (forward 30) (right 45)))))
(color [0 0 255])
(octagon)
The turtle sprite (the triangle representing the turtle) will be drawn in the same color as the turtle's pen.
(repeat 12 (all (octagon) (right 30)))
We can also use our color value to fill the interior of shapes that we
draw. To draw shapes will a fill color, we first have to indicate
when we start and when we end drawing the shape. For that, we use the
start-fill
and end-fill
commands. Every line segment that the turtle
draws in between start-fill
and end-fill
is assumed to form the
perimeter of the shape.
Let us define filled-octagon
as the combination of commands to draw a filled
octagon. In between the start-fill
and end-fill
that demarcate our
fill shape, we will use our octagon
function to draw the perimeter of the
octagon that we want filled.
(def filled-octagon (all (start-fill) (octagon) (end-fill)))
If a four-element color vector is given for a color, then the 4th value is known as the "alpha" value. In clojure-turtle, the alpha value is also an integer that ranges from 0 to 255. The value 0 represents full transparency, and 255 represents full opacity.
(color [255 255 0 100])
We will want to draw 4 octagons that
overlap, so we will create a points
vector of 4 x,y-coordinates from
which we will start drawing
each octagon.
(def points [[-11 -11] [-62 -11] [-36 14] [-36 -36]])
For now, let's retrieve only the first of the four points, set our position to that first point, and then draw our first octagon from there.
(let [point-1 (first points)
x (first point-1)
y (second point-1)]
(setxy x y)
(filled-octagon))
Next, we will draw our the remaining 3 octagons. Since we will perform similar actions in repetition, let's create a function to store the behavior we want to repeat.
(defn filled-octagon-from-point
[point]
(let [x (first point)
y (second point)]
(setxy x y)
(filled-octagon)))
Given a point in the form [x y]
, the function
filled-octagon-from-point
will draw a filled octagon starting at
(x,y). We label the positions (indices) of a vector starting from 0,
so the 1st element is as position 0, the 3rd element is at position 2,
and the 4th element is at position 3.
;; octagon starting from 2nd point:
(filled-octagon-from-point (second points))
;; ... from 3rd point:
(filled-octagon-from-point (nth points 2))
;; ... from 4th point:
(filled-octagon-from-point (nth points 3))
A color vector of size 1 creates a grayscale color ranging from black to white. The grayscale color is equivalent to using a 3-element RGB color vector where the values for red, green, and blue are the same.
(color [0])
(home)
We can use wait
to make the turtle pause. The number passed to wait
indicates how many milliseconds the turtle should wait (1 millisecond
= 0.001 seconds = 1 / 1000 seconds).
(clean)
(home)
(def stop-and-go (all (forward 30) (wait 2000) (right 90) (forward 30)))
(stop-and-go)
Computers today are fast. If we use wait
to slow them down, we can
watch the turtle move and perceive motion.
(clean)
(home)
(defn slower-octagon
[]
(repeat 8 (fn []
(forward 30)
(right 45)
(wait 100))))
(repeat 12 (all (slower-octagon) (right 30)))
What happens when you combine wait
with clean
? If we repeatedly
draw, wait, and clean images in a loop, we can create the effect of
motion! See the Animation page for more
information and examples.
clojure-turtle uses Quil, which uses Processing. clojure-turtle also has the full power and fun of Clojure available to it, too.
What do you get when you enter the following?
(defn square-by-length
[side-length]
(repeat 4 (all (forward side-length) (right 90))))
(square-by-length 10)
(square-by-length 20)
(def lengths [40 50 60])
(map square-by-length lengths)
(defn times-2
[x]
(* 2 x))
(right 90)
(map square-by-length (map times-2 lengths))
(right 90)
(->> lengths
(map times-2)
(map square-by-length))
(defn polygon-side
[num-sides side-length]
(forward side-length)
(right (/ 360 num-sides)))
(defn polygon
[num-sides side-length]
(repeat num-sides (all (polygon-side num-sides side-length))))
(clean)
(right 180)
(polygon 5 20)
(def side-counts [6 7 8 10 12])
(def lengths (reverse [30 40 50 60 70]))
(map polygon side-counts lengths)
(defn rand-side
[]
(forward (rand-int 50))
(setheading (rand-int 360)))
(fn? rand-side)
(fn? side)
(clean)
(home)
(repeat 4 side)
(repeat 100 rand-side)
What possibilities exist when you incorporate the full power of Clojure? What can you create?
The same codebase in clojure-turtle
can be compiled to JS and used in a JS runtime in addition to JVM bytecode. A demo of the JS version can be executed by first running the command:
lein figwheel
Where the output may look like:
$ lein figwheel
Figwheel: Starting server at http://localhost:3449
Figwheel: Watching build - dev
Compiling "demo/public/js/main.js" from ["src" "demo/src"]...
Successfully compiled "demo/public/js/main.js" in 16.311 seconds.
Launching ClojureScript REPL for build: dev
...
Then, in your browser, visit the URL in the terminal output from the command -- in this example, it is http://localhost:3449. You will see a webpage load with the Quil canvas containing the turtle. Back in your terminal, Figwheel will load a ClojureScript REPL that is connected to the webpage (more precisely, the browser REPL running in the webpage). In the ClojureScript REPL, run:
cljs.user=> (ns clojure-turtle.core)
cljs.user=> (require '[clojure-turtle.macros :refer-macros [repeat all]])
Now, the above Logo/clojure-turtle
commands can be issued in the CLJS REPL as described above, with the result visible in the Figwheel-connected browser page.
Join the clojure-turtle mailing list to post questions and receive announcements.
Interested in contributing code to the project? We would love to have your help!
Before you can contribute, you should first read the page on contributing and agree to the Contributor License Agreement. Signing the CLA can be done online and is fast. This is a one-time process.
Thereafter, contributions can be initiated through a pull request.
Distributed under the Apache 2 license.
This is not an official Google product (experimental or otherwise), it is just code that happens to be owned by Google.
Quil is distributed under the Eclipse Public License either version 1.0 (or at your option) any later version.
The official Processing.org's jars, used as dependencies of Quil, are distributed under LGPL and their code can be found on http://processing.org/