Here are some instructions from very basic to a little bit high level ones to walk turtles.
Walk them and draw lines by your imagination!
- preparation
If you haven't cloned out the repository, try this on the terminal:
git clone https://github.com/ClojureBridge/welcometoclojurebridge
cd welcometoclojurebridge
- load walk.clj on Nightcode
open the file
welcomeclojurebridge/src/clojurebridge_turtle/walk.clj
.
Then, click Run with REPL
.
It may take long. Evetually, you'll see the prompt,user=> nil
, on the bottom REPL pane.
Finally, click Reload File
. You'll see a window with a small triangle on the center.
This triangle is a turtle.
Once the turtles app starts running, evaluate each form (code fragment enclosed by matching parentheses) by either one of two:
- type code in the editor, select the region then click
Reload Selection
. - type code in the right bottom REPL and hit return(enter).
The difference of above two are --
If you write code in the editor, you can save it.
Click Save
on the top when you want to do.
Writing code in the REPL is handy, but you can't save it.
- load walk.clj on lein repl
startup repl, then run require
and ns
.
user=> (require 'clojurebridge-turtle.walk)
nil
user=> (ns clojurebridge-turtle.walk)
nil
clojurebridge-turtle.walk=> (turtle-names)
[:trinity]
clojurebridge-turtle.walk=> (state)
{:trinity {:x 0, :y 0, :angle 90, :color [106 40 126]}}
- initial state
See TURTLE.md for commands that turtles understand.
undo
,clean
, andhome
When the turtle has gone unexpectedly long or short distance, we can delete the
line one by one using undo
.
If the turtle should start from its initial state,
a combination of clean
and home
commands make the turtle back to
the initial state.
state
When the turtle goes far away beyond the boundary, or you lost which one is what, you can check where they are by this command. The command returns absolute values.
[note] The forward
/backward
or right
/left
commands take a
relative value to the current state.
doc
Without looking at command reference, we can check how to use each
function by Clojure's doc
. For examples, (doc forward)
displays
its usage.
- forward
clojurebridge-turtle.walk=> (forward 40)
{:trinity {:length 40}}
- right
clojurebridge-turtle.walk=> (right 90)
{:trinity {:angle 90}}
- combinations of forward and right
clojurebridge-turtle.walk=> (forward 40)
{:trinity {:length 40}}
clojurebridge-turtle.walk=> (right 90)
{:trinity {:angle 90}}
clojurebridge-turtle.walk=> (forward 50)
{:trinity {:length 50}}
clojurebridge-turtle.walk=> (right 90)
{:trinity {:angle 90}}
clojurebridge-turtle.walk=> (forward 60)
{:trinity {:length 60}}
clojurebridge-turtle.walk=> (right 90)
{:trinity {:angle 90}}
clojurebridge-turtle.walk=> (forward 70)
{:trinity {:length 70}}
clojurebridge-turtle.walk=> (right 90)
{:trinity {:angle 90}}
clojurebridge-turtle.walk=> (forward 80)
{:trinity {:length 80}}
- combination of various commands
clojurebridge-turtle.walk=> (init)
:trinity
clojurebridge-turtle.walk=> (forward 50)
{:trinity {:length 50}}
clojurebridge-turtle.walk=> (right 45)
{:trinity {:angle 45}}
clojurebridge-turtle.walk=> (backward 100)
{:trinity {:length -100}}
clojurebridge-turtle.walk=> (left 45)
{:trinity {:angle -45}}
clojurebridge-turtle.walk=> (forward 50)
{:trinity {:length 50}}
clojurebridge-turtle.walk=> (state)
{:trinity {:x -70.71068094436272, :y 29.289320335914155, :angle 90, :color [106 40 126]}}
- add turtles
clojurebridge-turtle.walk=> (init)
:trinity
clojurebridge-turtle.walk=> (add-turtle :neo)
{:neo {:x 0, :y 0, :angle 90, :color [12 84 0]}}
clojurebridge-turtle.walk=> (add-turtle :oracle)
{:oracle {:x 0, :y 0, :angle 90, :color [189 63 0]}}
clojurebridge-turtle.walk=> (add-turtle :cypher)
{:cypher {:x 0, :y 0, :angle 90, :color [75 102 125]}}
clojurebridge-turtle.walk=> (turtle-names)
[:trinity :neo :oracle :cypher]
- make turtles tilt different angles
clojurebridge-turtle.walk=> (right :neo (* 1 45))
{:neo {:angle 45}}
clojurebridge-turtle.walk=> (right :oracle (* 2 45))
{:oracle {:angle 90}}
clojurebridge-turtle.walk=> (right :cypher (* 3 45))
{:cypher {:angle 135}}
- walk four turtles forward
clojurebridge-turtle.walk=> (forward :trinity 40)
{:trinity {:length 40}}
clojurebridge-turtle.walk=> (forward :neo 40)
{:neo {:length 40}}
clojurebridge-turtle.walk=> (forward :oracle 40)
{:oracle {:length 40}}
clojurebridge-turtle.walk=> (forward :cypher 40)
{:cypher {:length 40}}
- add another turtle named :morpheus with color
clojurebridge-turtle.walk=> (add-turtle :morpheus [21, 137, 255])
{:morpheus {:x 0, :y 0, :angle 90, :color [21 137 255]}}
- tilt and go forward :morpheus
clojurebridge-turtle.walk=> (left :morpheus 45)
{:morpheus {:angle -45}}
clojurebridge-turtle.walk=> (forward :morpheus 40)
{:morpheus {:length 40}}
clojurebridge-turtle.walk=> (turtle-names)
[:trinity :neo :oracle :cypher :morpheus]
- walk five turtles forward by 20
clojurebridge-turtle.walk=> (forward :trinity 20)
{:trinity {:length 20}}
clojurebridge-turtle.walk=> (forward :neo 20)
{:neo {:length 20}}
clojurebridge-turtle.walk=> (forward :oracle 20)
{:oracle {:length 20}}
clojurebridge-turtle.walk=> (forward :cypher 20)
{:cypher {:length 20}}
clojurebridge-turtle.walk=> (forward :morpheus 20)
{:morpheus {:length 20}}
We've had five turtles and want to move or tilt those five.
Let's think how we can make all five turtles go forward by 40?
The simplest way would be to type (forward :name 40)
five times.
But wait. We are almost exhausted to type quite similar commands many times.
Is there any handy way of doing this? Yes, there is.
Clojure has many functions to accomplish this purpose.
doseq
function is one of them.
- 5.1 move 5 turtles forward using
doseq
function
clojurebridge-turtle.walk=> (doseq [n (turtle-names)] (forward n 40))
nil
- 5.2 [bonus] using
map
function (higher order function)
clojurebridge-turtle.walk=> (map #(forward % 40) (turtle-names))
({:trinity {:length 40}} {:neo {:length 40}} {:oracle {:length 40}} {:cypher {:length 40}} {:morpheus {:length 40}})
- 5.3 tilt and forward them by
doseq
s
clojurebridge-turtle.walk=> (doseq [n (turtle-names)] (right n 60))
nil
clojurebridge-turtle.walk=> (doseq [n (turtle-names)] (forward n 30))
nil
- 5.4 [bonus] put two
doseq
s in one
clojurebridge-turtle.walk=> (doseq [n (turtle-names)]
#_=> (right n 60)
#_=> (forward n 30))
nil
- 5.5 [bonus] using
map
(higher order function) andjuxt
functions
clojurebridge-turtle.walk=> (map (juxt #(right % 60) #(forward % 30)) (turtle-names))
([{:trinity {:angle 60}} {:trinity {:length 30}}]
[{:neo {:angle 60}} {:neo {:length 30}}]
[{:oracle {:angle 60}} {:oracle {:length 30}}]
[{:cypher {:angle 60}} {:cypher {:length 30}}]
[{:morpheus {:angle 60}} {:morpheus {:length 30}}])
While playing around with turtles, we may mess up their movements.
The (init)
command makes everything clean up and back to the initial state.
It is a good command, but again, we need to repeat (add-turtle name)
command many times to get five turtles.
We want something. Yes, we can define our own function for that. Once the function is defined, we can add five turtles by a single function call anytime.
- 6.1 define a function to add three turtles and a turtle with the name :neo
Since we already have :trinity, we are going to add four turtles. Let's name them, :neo, :oracle, :cypher, :morpheus. Once all are added, we will get five turtles in total.
;; function definition
(defn add-four-turtles
[]
(let [names [:neo :oracle :cypher :morpheus]]
(init)
(dotimes [i (count names)] (add-turtle (nth names i)))))
;; usage of the five-turtles function
(add-four-turtles)
;; check turtle names
(turtle-names)
- 6.2 [bonus] add a parameter to
add-four-turtles
function so that each turtle can take specified name.
Our add-four-turtles
function uses hard-coded names for turtles.
Instead, let's enjoy a freedom to choose any name for them without
rewriting the function.
Now, we are going to write multi-arity function. The arity means a
number of arguments the function takes. For example, our
add-four-turtle
function so far takes zero argument only. If we add
add-four-turtle
function which takes one argument, the function
turns to a multi-arity function. This is because the function takes
zero or one argument.
;; function definition
(defn add-four-turtles
([] (add-four-turtles [:neo :oracle :cypher :morpheus]))
([names]
(init)
(dotimes [i (count names)] (add-turtle (nth names i)))))
;; usage example
(add-four-turtles) ;; uses predefined names
(add-four-turtles [:taylor :tess :tiffany :tracy]) ;; uses given names
;; check turtle names
(turtle-names)
Look at the multi-arity function above once more. The
add-four-turtles
function doesn't need to have four
in its name.
The number of turtles can be any. If you list two names in the vector,
the function will add two turtles.
- 6.3 [bonus] [exercise] make
add-four-turtles
to add exactly four turtles
We can add a check so that only when four names are provided, the function adds four turtles; otherwise does nothing.
Hint: function body will look like:
(if (= 4 (count names))
....blah blah blah...)
- 6.4 side note
Suppose you have already five turtles and continue using the same
turtles. But, you want to clean up all lines and make them home
position. In such a case, a combination of clean-all
and
home-all
gives the same effect as add-four-turtles
function.
;; deletes all lines
(clean-all)
;; moves all turtles back to the home position
(home-all)
;; check turtles' names
(turtle-names)
Next, we want to tilt five turtles' heads in different angles so that we can see their move well. For example, :trinity 0, :neo 45, :oracle 90, :cypher 135, :morpheus 180. This function will be no more straightforward like we wrote so far. Since we need two kinds of parameters at the same time, name and angle, while previous functions used only one kind of parameter.
Again, Clojure has many ways to do this.
- 7.1 using
doseq
with a little tweak
Let's think how we can use doseq
here also.
- 7.1.1 put two parameters in one hash map
If we put two-parameter pair together to form one, we can use doseq
like previous examples.
;; put turtle names and digits together and create a hash map
clojurebridge-turtle.walk=> (def m (zipmap (turtle-names) (range 0 (count (turtle-names)))))
#'clojurebridge-turtle.walk/m
clojurebridge-turtle.walk=> m
{:trinity 0, :neo 1, :oracle 2, :cypher 3, :morpheus 4}
;; see each part is doing what
clojurebridge-turtle.walk=> (count (turtle-names))
5
clojurebridge-turtle.walk=> (range 0 5)
(0 1 2 3 4)
clojurebridge-turtle.walk=> (zipmap [:a :b] [0 1])
{:a 0, :b 1}
- 7.1.2 use
doseq
to iterate hash map
;; using m which is created by zipmap
(doseq [t m] (right (first t) (* (second t) 45)))
;; again, see each part is doing what
;; see how doseq uses m
clojurebridge-turtle.walk=> (doseq [t m] (prn t))
[:trinity 0]
[:neo 1]
[:oracle 2]
[:cypher 3]
[:morpheus 4]
nil
;; see first and second function do
clojurebridge-turtle.walk=> (doseq [t m] (prn (first t) (second t)))
:trinity 0
:neo 1
:oracle 2
:cypher 3
:morpheus 4
nil
;; see what (* (second t) 45) does
clojurebridge-turtle.walk=> (doseq [t m] (prn (first t) (* (second t) 45)))
:trinity 0
:neo 45
:oracle 90
:cypher 135
:morpheus 180
nil
- 7.1.3 put hash map creation and
doseq
in one function
;; function definition
(defn tilt-turtles
[]
(let [m (zipmap (turtle-names) (range 0 (count (turtle-names))))]
(doseq [t m] (right (first t) (* (second t) 45)))))
;; usage
(tilt-turtles)
- 7.2 [bonus] map function (higher order function) is another way to do
Using map
function, tilt-turtles function can be written a single
line as in below.
clojurebridge-turtle.walk=> (map #(right % (* %2 45)) (turtle-names) (range 0 (count (turtle-names))))
({:trinity {:angle 0}} {:neo {:angle 45}} {:oracle {:angle 90}}
{:cypher {:angle 135}} {:morpheus {:angle 180}})
- 7.3 [bonus] recursion is another way to do
;; function definition
(defn tilt-turtles-loop
[]
(loop [t nil
n (turtle-names)
s 0]
(if (empty? n) :completed
(recur (right (first n) (* s 45)) (rest n) (inc s)))))
;; usage
(tilt-turtles-loop)
We got five turtles, made their heads tilted in different directions. It's time to move all those five turtles, forward, right and forward.
- 8.1 put three movements in one
doseq
Like we tried in 5.4, only one doseq
works for the three movements.
(doseq [n (turtle-names)]
(forward n 60)
(right n 90)
(forward n 50))
- 8.2 change parameters of forwards and right
Putting three movements in one doseq
is nice, but what if we want to
change parameters?
For example, forward 100, right 150, then forward 60, or
forward 50, right 30, and forward 60.
Defining a function that takes parameters is the way to do.
;; function definition
(defn doseq-with-params
[len1 len2 angle]
(doseq [n (turtle-names)]
(forward n len1)
(right n angle)
(forward n len2)))
;; usage example
(home-all)
(clean-all)
(tilt-turtles)
(doseq-with-params 100 60 150)
Try various parameters.
So far, we added four turtles and got five in total. Each turtle's head was tilted in a different direction. Finally, five turtles walked forward, changed heads by some degrees, then walked again. There were three steps.
We've already learned writing a function makes things put into one, and repeating the same sequence makes very easy.
- 9.1 write a function so that all three steps can be done by only one function.
;; definition of walk-five-turtles functions
(defn walk-five-turtles
[names len1 len2 angle]
(add-four-turtles names)
(tilt-turtles)
(doseq-with-params len1 len2 angle))
;; usage examples
(walk-five-turtles [:taylor :tess :tiffany :tracy] 10 60 90)
- 9.2 [bonus] use hash map as a function parameter
As a number of function parameters increases, it goes confusing. For example, if we misunderstand the order of len2 and angle, turtles may go beyond the boundary.
To solve this problem, hash map is often used as a function parameter. The parameters in the hash map are picked up using destructuring. It is handy.
;; function definition
(defn walk-five-turtles-map
[{:keys [names len1 len2 angle]}]
(add-four-turtles names)
(tilt-turtles)
(doseq-with-params len1 len2 angle))
;; usage example
(walk-five-turtles-map {:names [:taylor :tess :tiffany :tracy] :len1 10 :len2 60 :angle 90})
If we look at the usage example, it's clear what parameter goes where.
Going forward 480 and repeating 48 times of forward 10 are not the
same for the turtles.
To see the difference, let define an utility function, leftmost
first.
This function moves a turtle to the leftmost position.
;; function definition
(defn leftmost
[n]
(home n)
(clean n)
(left n 90)
(forward n 240)
(clean n)
(right n 180))
Once the turtle moves to the leftmost position, try two types of forwards.
clojurebridge-turtle.walk=> (leftmost :trinity)
{:trinity {:angle 180}}
;; forward 480
clojurebridge-turtle.walk=> (forward :trinity 480)
{:trinity {:length 480}}
clojurebridge-turtle.walk=> (leftmost :trinity)
{:trinity {:angle 180}}
;; 48 times of forward 10
clojurebridge-turtle.walk=> (dotimes [i 48] (forward :trinity 10))
nil
Both walked the turtle from the leftmost to rightmost position, however, the colors are not the same. The stroke's color is calculated from the x, y values of the endpoint. Forwarding 480 has only one endpoint, while forwarding 48 times has 48 endpoints. This means the turtle changed the color 48 times.
ClojureBridge Curriculum by ClojureBridge is licensed under a
Creative
Commons Attribution 4.0 International License.