From 1b5aeeb52e03d233edd6f456e36339bc75ea95c7 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Sat, 30 Aug 2025 16:13:44 +0100 Subject: [PATCH] Documentation and tidy up. --- README.md | 100 ++++++++++++++++++++++++++++++++++++- index.html | 83 +++++++++++++++++++----------- resources/cljs/tittle.cljs | 76 ++++++++++++++++------------ 3 files changed, 199 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index d01f518..51076ec 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,108 @@ See [Papert, S: Mindstorms](https://www.worldofbooks.com/en-gb/products/mindstor ![Polygons generated with Tittle](polygons.svg) -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 diff --git a/index.html b/index.html index cb5f4ec..7797a01 100644 --- a/index.html +++ b/index.html @@ -18,16 +18,19 @@ \ No newline at end of file diff --git a/resources/cljs/tittle.cljs b/resources/cljs/tittle.cljs index aa293e4..d6caee2 100644 --- a/resources/cljs/tittle.cljs +++ b/resources/cljs/tittle.cljs @@ -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)