(ns tittle) (def turtle (atom {:theta 0 :x 0 :y 0 :pen :up :ink "blue"})) (defn log-turtle! [] (.log js/console (apply str (map #(str % " " (@turtle %) "; ") (keys @turtle))))) (log-turtle!) (def playing-field (.getElementById js/document "playing-field")) (defn- not-a-number! [n] (throw (js/Error. (str "not a number: " n)))) (defn number-or-error! "If `n` is a number, return it, else throw an exception." [n] (cond (.isNaN js/Number n) (not-a-number! n) (number? n) n :else (not-a-number! n))) (defn sanitise-angle [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)) (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)))) (not-a-number! angle)) (.info js/console (str "(turn! " angle ") :: :theta now " (:theta @turtle))) (log-turtle!)) (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." [angle] (.info js/console (str "(turn-to! " angle ")")) (if (number? angle) (swap! turtle assoc :theta (sanitise-angle angle)) (not-a-number! angle)) (.info js/console (str "(turn-to! " angle ") :: :theta now " (:theta @turtle)))) (defn pen-down! "Put the turtle's pen down." [] (.info js/console "pen-down!") (swap! turtle assoc :pen :down)) (defn pen-up! "Lift the turtle's pen up" [] (.info js/console "pen-up!") (swap! turtle assoc :pen :up)) (defn pen-down? "Return `true` if the turtle's pen is down, else `false`." [] (let [v (= (@turtle :pen) :down)] (.info js/console "(pen-down?) =>" v) v)) (defn pen-up? "Return `true` if the turtle's pen is not down, else `false`." [] (not (pen-down?))) (defn move-to! "Move the turtle absolutely to the coordinates `x`, `y`." [x y] (.info js/console (str "(move-to! " x " " y ")")) (when (map number-or-error! [x y]) (let [x' (:x @turtle) 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))) (swap! turtle assoc :x x :y y)))) (def ^:const pi 3.141592653589793) (defn degrees->radians "Return the equivalent, in radians, of this `angle` espressed in degrees" [angle] (let [v (when (number-or-error! angle) (* angle (/ pi 180)))] (.log js/console (str "(degrees->radians " angle ") => " v)) v)) (defn radians->degrees "Return the equivalent, in degrees, of this `angle` espressed in radians" [angle] (let [v (when (number-or-error! angle) (/ (* angle 180) pi))] (.log js/console (str "(radians->degrees " angle ") => " v)) v)) (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)) (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)) (defn move! "Move the turtle forward on its current heading by `distance` units." [distance] (.info js/console (str "(move! " distance ")")) (move-to! (+ (:x @turtle) (* distance (sin (:theta @turtle)))) (+ (:y @turtle) (* distance (cos (:theta @turtle)))))) (defn set-ink! "Set the ink with which the turtle draws to this `colour`, which should be a string representation of a colour known to CSS." [colour] (.log js/console (str "(set-ink! " colour ")")) (when (string? colour) (swap! turtle assoc :ink colour))) (defn draw-tree! [length left-branch right-branch curvature branch-fraction trunk-fraction depth] (log-turtle!) (when (> depth 0) (pen-down!) (set-ink! (if (<= depth 2) "green" "brown")) (move! length) (turn! curvature) (draw-tree! (* length trunk-fraction) (* left-branch branch-fraction) (* right-branch branch-fraction) curvature branch-fraction trunk-fraction (dec depth)) (turn! (- 0 (+ curvature left-branch))) (draw-tree! (* length trunk-fraction) left-branch right-branch curvature branch-fraction trunk-fraction (dec depth)) (turn! (+ left-branch right-branch)) (draw-tree! (* length trunk-fraction) left-branch right-branch curvature branch-fraction trunk-fraction (dec depth)) (turn! (- 0 right-branch curvature)) (pen-up!) (move! (- 0 length)))) (defn draw-polygon! [sides side-length] (when {<= 3 sides 360} (let [angle (/ 360 sides)] (pen-down!) (loop [side 0] (turn! angle) (move! side-length) (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)