Documentation and tidy up.
This commit is contained in:
parent
a4c64297f0
commit
1b5aeeb52e
100
README.md
100
README.md
|
@ -8,10 +8,108 @@ See [Papert, S: Mindstorms](https://www.worldofbooks.com/en-gb/products/mindstor
|
|||
|
||||

|
||||
|
||||
As a proof of concept, this kind of proves the concept; but it doesn't work, the trigonometry functions are pretty badly broken.
|
||||
As a proof of concept, this proves the concept. There's a lot more to do.
|
||||
|
||||
I have not (yet) managed to get a scittle REPL running.
|
||||
|
||||
Clojure (and thus Scittle) is a fairly pure functional language. It's not good at imperative things, although of course it can do them. Turtle Graphics is by its nature imperative: you're commanding the turtle. So this is not a very idiomatic use of Scittle, and it's not very efficient; it invites you to write recursive functions (and of course I wish to encourage you to write recursive functions), but recursive functions in Scittle are not fast.
|
||||
|
||||
## Usage
|
||||
|
||||
At this stage, edit `index.html`
|
||||
|
||||
### Functions
|
||||
|
||||
Generally speaking, functions whose names end in exclamation points are imperative functions: they change the state of things by side-effect, and the return value may not be particularly relevant. I would normally consider imperative functions bad practice in Clojure.
|
||||
|
||||
#### (cos *angle*)
|
||||
|
||||
Return the cosine of this `angle`, considered to be expressed in degrees.
|
||||
|
||||
#### (degrees->radians *angle*)
|
||||
|
||||
Return the equivalent, in radians, of this `angle` espressed in degrees.
|
||||
|
||||
#### (draw-polygon! *sides* *side-length*)
|
||||
|
||||
Draw a regular polygon, with this number of `sides`, each of this `side-length`.
|
||||
|
||||
#### (draw-tree *length* *left-branch* *right-branch* *curvature* *branch-fraction* *trunk-fraction* *depth*)
|
||||
|
||||
Draw a tree. This is a fairly crude tree-drawing algorithm; there's lots of ways it can be improved, consider it a plac to start. Parameters (all numeric) as follows:
|
||||
|
||||
* `length`: the length of the current segment of the tree;
|
||||
* `left-branch`: the amount to turn left, with respect to the parent segment, to draw a left branch, in degrees;
|
||||
* `right-branch`: the amount to turn right, with respect to the parent segment, to draw a right branch, in degrees;
|
||||
* `curvature`: the amount to turn to draw the next segment of the current stem, in degrees;
|
||||
* `branch-fraction`: the length of a branch segment, as a proportion of the length of its parent segment;
|
||||
* `trunk-fraction`: the length of the next segment of a stem, as a proportion of the length of its parent segment;
|
||||
* `depth`: the maximum depth of recursion to allow.
|
||||
|
||||
#### (log-turtle!)
|
||||
|
||||
Prints the state of the turtle to the browser console.
|
||||
|
||||
#### (move! *distance*)
|
||||
|
||||
Move the turtle forward on its current heading by `distance` units.
|
||||
|
||||
**NOTE THAT** this function invokes `move-to!`, q.v., which will create a
|
||||
line element if the pen is down.
|
||||
|
||||
#### (move-to! *x* *y*)
|
||||
|
||||
Move the turtle absolutely to the coordinates `x`, `y`. If the pen is down, create a line element.
|
||||
|
||||
**NOTE THAT** this is the only function which creates new graphical elements.
|
||||
|
||||
#### (number-or-error! *n*)
|
||||
|
||||
If `n` is a number, return it, else throw an exception.
|
||||
|
||||
#### (pen-down!)
|
||||
|
||||
Put the turtle's pen down (i.e., cause line segments to be created).
|
||||
|
||||
#### (pen-down?)
|
||||
|
||||
Return `true` if the turtle's pen is down, else `false`.
|
||||
|
||||
#### (pen-up!)
|
||||
|
||||
Lift the turtle's pen up (line segments will not be created).
|
||||
|
||||
#### (pen-up?)
|
||||
|
||||
Return `true` if the turtle's pen is not down, else `false`.
|
||||
|
||||
#### (radians->degrees *angle*)
|
||||
|
||||
Return the equivalent, in degrees, of this `angle` espressed in radians.
|
||||
|
||||
#### (sanitise-angle *angle*)
|
||||
|
||||
Take this `angle`, and return a number between 0 and 360 that represents it as an angular measurement.
|
||||
|
||||
#### (set-ink! *colour*)
|
||||
|
||||
Set the ink with which the turtle draws to this `colour`, which should be a string representation of a colour known to CSS.
|
||||
|
||||
#### (sin *angle*)
|
||||
|
||||
Return the sine of this `angle`, considered to be expressed in degrees.
|
||||
|
||||
#### (turn! *angle*)
|
||||
|
||||
Turn the turtle clockwise by this `angle`, expressed in degrees with respect to the X axis. If `angle` is not a number, throw an exception.
|
||||
|
||||
#### (turn-to! *angle*)
|
||||
|
||||
Turn the turtle to face `angle`, expressed in degrees with respect to the X axis. If `angle` is not a number, throw an exception.
|
||||
|
||||
**TODO: Note that 180° is currently straight up; this is not intended, it is intended that 0° should be straight up, and this change will be made
|
||||
before version 1.0.
|
||||
|
||||
## Licence
|
||||
|
||||
Copyright © Simon Brooke 2025
|
||||
|
|
83
index.html
83
index.html
|
@ -18,16 +18,19 @@
|
|||
<script type="application/x-scittle">
|
||||
(def turtle (atom {:theta 0 :x 0 :y 0 :pen :up :ink "blue"}))
|
||||
|
||||
(defn log-turtle! []
|
||||
(defn log-turtle!
|
||||
[]
|
||||
(.log js/console
|
||||
(apply str
|
||||
(map #(str % " " (@turtle %) "; ") (keys @turtle)))))
|
||||
(cons "Turtle: "
|
||||
(map #(str % " " (@turtle %) "; ") (keys @turtle))))))
|
||||
|
||||
(log-turtle!)
|
||||
|
||||
(def playing-field (.getElementById js/document "playing-field"))
|
||||
|
||||
(defn- not-a-number! [n]
|
||||
(defn- not-a-number!
|
||||
[n]
|
||||
(throw (js/Error. (str "not a number: " n))))
|
||||
|
||||
(defn number-or-error!
|
||||
|
@ -46,7 +49,6 @@
|
|||
v (cond (.isNaN js/Number a) 0
|
||||
(< a 0.5) 0
|
||||
(<= a 360) a
|
||||
;; TODO: `rem` is possibly wrong when we get into negative numbers
|
||||
:else (loop [r a]
|
||||
(if (<= r 360) r
|
||||
(recur (- r 360)))))
|
||||
|
@ -58,7 +60,6 @@
|
|||
"Turn the turtle clockwise by this `angle`, expressed in degrees with
|
||||
respect to the X axis. If `angle` is not a number, throw an exception."
|
||||
[angle]
|
||||
(.info js/console (str "(turn! " angle ")"))
|
||||
(if (number? angle)
|
||||
(swap! turtle assoc :theta
|
||||
(sanitise-angle
|
||||
|
@ -70,9 +71,12 @@
|
|||
|
||||
(defn turn-to!
|
||||
"Turn the turtle to face `angle`, expressed in degrees with respect to the
|
||||
X axis. If `angle` is not a number, throw an exception."
|
||||
X axis. If `angle` is not a number, throw an exception.
|
||||
|
||||
**TODO: Note that 180° is currently straight up; this is not intended, it
|
||||
is intended that 0° should be straight up, and this change will be made
|
||||
before version 1.0."
|
||||
[angle]
|
||||
(.info js/console (str "(turn-to! " angle ")"))
|
||||
(if (number? angle)
|
||||
(swap! turtle assoc :theta (sanitise-angle angle))
|
||||
(not-a-number! angle))
|
||||
|
@ -80,13 +84,13 @@
|
|||
") :: :theta now " (:theta @turtle))))
|
||||
|
||||
(defn pen-down!
|
||||
"Put the turtle's pen down."
|
||||
"Put the turtle's pen down (i.e., cause line segments to be created)."
|
||||
[]
|
||||
(.info js/console "pen-down!")
|
||||
(swap! turtle assoc :pen :down))
|
||||
|
||||
(defn pen-up!
|
||||
"Lift the turtle's pen up"
|
||||
"Lift the turtle's pen up (line segments will not be created)."
|
||||
[]
|
||||
(.info js/console "pen-up!")
|
||||
(swap! turtle assoc :pen :up))
|
||||
|
@ -104,7 +108,11 @@
|
|||
(not (pen-down?)))
|
||||
|
||||
(defn move-to!
|
||||
"Move the turtle absolutely to the coordinates `x`, `y`."
|
||||
"Move the turtle absolutely to the coordinates `x`, `y`. If the
|
||||
pen is down, create a line element.
|
||||
|
||||
**NOTE THAT** this is the only function which creates new graphical
|
||||
elements."
|
||||
[x y]
|
||||
(.info js/console (str "(move-to! " x " " y ")"))
|
||||
(when (map number-or-error! [x y])
|
||||
|
@ -112,14 +120,14 @@
|
|||
y' (:y @turtle)]
|
||||
(when (pen-down?)
|
||||
(let [elt (.createElementNS js/document "http://www.w3.org/2000/svg" "line")
|
||||
id (gensym "line")]
|
||||
(.setAttribute elt "id" id)
|
||||
(.setAttribute elt "x1" x')
|
||||
(.setAttribute elt "y1" y')
|
||||
(.setAttribute elt "x2" x)
|
||||
(.setAttribute elt "y2" y)
|
||||
(.setAttribute elt "stroke" (or (:ink @turtle) "blue"))
|
||||
(.appendChild playing-field elt)))
|
||||
id (gensym "line")]
|
||||
(.setAttribute elt "id" id)
|
||||
(.setAttribute elt "x1" x')
|
||||
(.setAttribute elt "y1" y')
|
||||
(.setAttribute elt "x2" x)
|
||||
(.setAttribute elt "y2" y)
|
||||
(.setAttribute elt "stroke" (or (:ink @turtle) "blue"))
|
||||
(.appendChild playing-field elt)))
|
||||
(swap! turtle assoc :x x :y y))))
|
||||
|
||||
(def ^:const pi 3.141592653589793)
|
||||
|
@ -143,16 +151,12 @@
|
|||
(defn sin
|
||||
"Return the sine of this `angle`, considered to be expressed in degrees."
|
||||
[angle]
|
||||
(let [v (.sin js/Math (degrees->radians angle))]
|
||||
(.log js/console (str "(sin " angle ") => " v))
|
||||
v))
|
||||
(.sin js/Math (degrees->radians angle)))
|
||||
|
||||
(defn cos
|
||||
"Return the cosine of this `angle`, considered to be expressed in degrees."
|
||||
[angle]
|
||||
(let [v (.cos js/Math (degrees->radians angle))]
|
||||
(.log js/console (str "(cos " angle ") => " v))
|
||||
v))
|
||||
(.cos js/Math (degrees->radians angle)))
|
||||
|
||||
(defn move!
|
||||
"Move the turtle forward on its current heading by `distance` units."
|
||||
|
@ -170,6 +174,22 @@
|
|||
(swap! turtle assoc :ink colour)))
|
||||
|
||||
(defn draw-tree!
|
||||
"Draw a tree. This is a fairly crude tree-drawing algorithm;
|
||||
there's lots of ways it can be improved, consider it a place
|
||||
to start. Parameters (all numeric) as follows:
|
||||
|
||||
`length`: the length of the current segment of the tree;
|
||||
`left-branch`: the amount to turn left, with respect to the
|
||||
parent segment, to draw a left branch, in degrees;
|
||||
`right-branch`: the amount to turn right, with respect to the
|
||||
parent segment, to draw a right branch, in degrees;
|
||||
`curvature`: the amount to turn to draw the next segment of
|
||||
the current stem, in degrees;
|
||||
`branch-fraction`: the length of a branch segment, as a
|
||||
proportion of the length of its parent segment;
|
||||
`trunk-fraction`: the length of the next segment of a stem,
|
||||
as a proportion of the length of its parent segment;
|
||||
`depth`: the maximum depth of recursion to allow."
|
||||
[length left-branch right-branch curvature branch-fraction trunk-fraction depth]
|
||||
(log-turtle!)
|
||||
(when (> depth 0)
|
||||
|
@ -200,11 +220,13 @@
|
|||
branch-fraction
|
||||
trunk-fraction
|
||||
(dec depth))
|
||||
(turn! (- 0 right-branch curvature))
|
||||
(turn! (- 0 right-branch))
|
||||
(pen-up!)
|
||||
(move! (- 0 length))))
|
||||
|
||||
(defn draw-polygon!
|
||||
"Draw a regular polygon, with this number of `sides`, each of
|
||||
this `side-length`."
|
||||
[sides side-length]
|
||||
(when {<= 3 sides 360}
|
||||
(let [angle (/ 360 sides)]
|
||||
|
@ -219,11 +241,16 @@
|
|||
(log-turtle!)
|
||||
(pen-up!)
|
||||
(log-turtle!)
|
||||
(move-to! 350 350)
|
||||
(move-to! 350 700)
|
||||
(log-turtle!)
|
||||
(turn-to! 180)
|
||||
;; (draw-tree! 100 70 60 5 0.25 0.7 3)
|
||||
(doall (map #(draw-polygon! % 100) (range 3 20)))
|
||||
(draw-tree! 100 20 16 8 0.2 0.8 8)
|
||||
(set-ink! "blue")
|
||||
(doall (map #(let [v (* 16 (mod % 16))
|
||||
c (str "rgb(" v "," v "," (- 256 v)")")]
|
||||
(set-ink! c)
|
||||
(draw-polygon! % 100))
|
||||
(range 3 16)))
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,17 +1,21 @@
|
|||
(ns tittle)
|
||||
|
||||
(def turtle (atom {:theta 0 :x 0 :y 0 :pen :up :ink "blue"}))
|
||||
(def turtle (atom {:theta 0 :x 0 :y 0 :pen :up :ink "blue"}))
|
||||
|
||||
(defn log-turtle! []
|
||||
(defn log-turtle!
|
||||
"Prints the state of the turtle to the browser console."
|
||||
[]
|
||||
(.log js/console
|
||||
(apply str
|
||||
(map #(str % " " (@turtle %) "; ") (keys @turtle)))))
|
||||
(cons "Turtle: "
|
||||
(map #(str % " " (@turtle %) "; ") (keys @turtle))))))
|
||||
|
||||
(log-turtle!)
|
||||
|
||||
(def playing-field (.getElementById js/document "playing-field"))
|
||||
|
||||
(defn- not-a-number! [n]
|
||||
(defn- not-a-number!
|
||||
[n]
|
||||
(throw (js/Error. (str "not a number: " n))))
|
||||
|
||||
(defn number-or-error!
|
||||
|
@ -22,23 +26,29 @@
|
|||
:else (not-a-number! n)))
|
||||
|
||||
(defn sanitise-angle
|
||||
"Take this `angle`, and return a number between 0 and 360 that represents
|
||||
it as an angular measurement."
|
||||
[angle]
|
||||
(let [v (cond (.isNaN js/Number angle) 0
|
||||
(< (abs angle) 0.5) 0
|
||||
(< (abs angle) 360) angle
|
||||
:else (rem 360 angle))]
|
||||
(.log js/console (str "(sanitise-angle " angle ") -> " v))
|
||||
v))
|
||||
(let [a (abs angle)
|
||||
p (pos? angle)
|
||||
v (cond (.isNaN js/Number a) 0
|
||||
(< a 0.5) 0
|
||||
(<= a 360) a
|
||||
:else (loop [r a]
|
||||
(if (<= r 360) r
|
||||
(recur (- r 360)))))
|
||||
v' (if p v (- 360 v))]
|
||||
(.log js/console (str "(sanitise-angle " angle ") -> " v'))
|
||||
v'))
|
||||
|
||||
(defn turn!
|
||||
"Turn the turtle clockwise by this `angle`, expressed in degrees with
|
||||
respect to the X axis. If `angle` is not a number, throw an exception."
|
||||
[angle]
|
||||
(.info js/console (str "(turn! " angle ")"))
|
||||
(if (number? angle)
|
||||
(swap! turtle assoc :theta
|
||||
(sanitise-angle
|
||||
(rem 360 (+ (:theta @turtle) angle))))
|
||||
(+ (:theta @turtle) angle)))
|
||||
(not-a-number! angle))
|
||||
(.info js/console (str "(turn! " angle
|
||||
") :: :theta now " (:theta @turtle)))
|
||||
|
@ -48,7 +58,6 @@
|
|||
"Turn the turtle to face `angle`, expressed in degrees with respect to the
|
||||
X axis. If `angle` is not a number, throw an exception."
|
||||
[angle]
|
||||
(.info js/console (str "(turn-to! " angle ")"))
|
||||
(if (number? angle)
|
||||
(swap! turtle assoc :theta (sanitise-angle angle))
|
||||
(not-a-number! angle))
|
||||
|
@ -80,7 +89,8 @@
|
|||
(not (pen-down?)))
|
||||
|
||||
(defn move-to!
|
||||
"Move the turtle absolutely to the coordinates `x`, `y`."
|
||||
"Move the turtle absolutely to the coordinates `x`, `y`. If the
|
||||
pen is down, create a line element."
|
||||
[x y]
|
||||
(.info js/console (str "(move-to! " x " " y ")"))
|
||||
(when (map number-or-error! [x y])
|
||||
|
@ -119,16 +129,12 @@
|
|||
(defn sin
|
||||
"Return the sine of this `angle`, considered to be expressed in degrees."
|
||||
[angle]
|
||||
(let [v (.sin js/Math (degrees->radians angle))]
|
||||
(.log js/console (str "(sin " angle ") => " v))
|
||||
v))
|
||||
(.sin js/Math (degrees->radians angle)))
|
||||
|
||||
(defn cos
|
||||
"Return the cosine of this `angle`, considered to be expressed in degrees."
|
||||
[angle]
|
||||
(let [v (.cos js/Math (degrees->radians angle))]
|
||||
(.log js/console (str "(cos " angle ") => " v))
|
||||
v))
|
||||
(.cos js/Math (degrees->radians angle)))
|
||||
|
||||
(defn move!
|
||||
"Move the turtle forward on its current heading by `distance` units."
|
||||
|
@ -146,6 +152,22 @@
|
|||
(swap! turtle assoc :ink colour)))
|
||||
|
||||
(defn draw-tree!
|
||||
"Draw a tree. This is a fairly crude tree-drawing algorithm;
|
||||
there's lots of ways it can be improved, consider it a place
|
||||
to start. Parameters (all numeric) as follows:
|
||||
|
||||
`length`: the length of the current segment of the tree;
|
||||
`left-branch`: the amount to turn left, with respect to the
|
||||
parent segment, to draw a left branch, in degrees;
|
||||
`right-branch`: the amount to turn right, with respect to the
|
||||
parent segment, to draw a right branch, in degrees;
|
||||
`curvature`: the amount to turn to draw the next segment of
|
||||
the current stem, in degrees;
|
||||
`branch-fraction`: the length of a branch segment, as a
|
||||
proportion of the length of its parent segment;
|
||||
`trunk-fraction`: the length of the next segment of a stem,
|
||||
as a proportion of the length of its parent segment;
|
||||
`depth`: the maximum depth of recursion to allow."
|
||||
[length left-branch right-branch curvature branch-fraction trunk-fraction depth]
|
||||
(log-turtle!)
|
||||
(when (> depth 0)
|
||||
|
@ -176,11 +198,13 @@
|
|||
branch-fraction
|
||||
trunk-fraction
|
||||
(dec depth))
|
||||
(turn! (- 0 right-branch curvature))
|
||||
(turn! (- 0 right-branch))
|
||||
(pen-up!)
|
||||
(move! (- 0 length))))
|
||||
|
||||
(defn draw-polygon!
|
||||
"Draw a regular polygon, with this number of `sides`, each of
|
||||
this `side-length`."
|
||||
[sides side-length]
|
||||
(when {<= 3 sides 360}
|
||||
(let [angle (/ 360 sides)]
|
||||
|
@ -191,13 +215,3 @@
|
|||
(when (< side sides)
|
||||
(recur (inc side))))
|
||||
(pen-up!))))
|
||||
|
||||
(log-turtle!)
|
||||
(pen-up!)
|
||||
(log-turtle!)
|
||||
(move-to! 500 500)
|
||||
(log-turtle!)
|
||||
(turn-to! 180)
|
||||
;; (draw-tree! 100 70 60 5 0.25 0.7 3)
|
||||
;; (map #(draw-polygon! % 100) (range 3 20))
|
||||
(draw-polygon! 3 100)
|
||||
|
|
Loading…
Reference in a new issue