Skip to content

Commit

Permalink
Chapter 0.4 and 0.4e
Browse files Browse the repository at this point in the history
  • Loading branch information
Ramblurr committed May 28, 2024
1 parent 5706371 commit 594ce33
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 24 deletions.
3 changes: 3 additions & 0 deletions dev/user.clj
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,8 @@
:index "notebooks/index.md"
:out-path "public"})

(update-in {:_ui {:foobar {:values :yay}}}
[:_ui]
#(dissoc % :foobar))
;;
)
26 changes: 26 additions & 0 deletions notebooks/chapter_0.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,5 +97,31 @@ To move a point B towards point A by a small step using the Euclidean distance,
$$

```clojure
^{::clerk/no-cache true ::clerk/viewer clerk/code}
(slurp "src/noc/chapter_0_3e.cljs")
(show-sketch :walker-dynamic)
```


## [Example 0.4: A Gaussian Distribution](https://natureofcode.com/random/#example-04-a-gaussian-distribution)
```clojure
^{::clerk/no-cache true ::clerk/viewer clerk/code}
(slurp "src/noc/chapter_0_4.cljs")
(show-sketch :random-gaussian)
```


## [Exercise 0.4: Gaussian Paint Splatter](https://natureofcode.com/random/#exercise-04)

Our first bit of UI. Quil doesn't expose the p5.js UI widgets like
[`createSlider`](https://p5js.org/reference/#/p5/createSlider).

I didn't want to call those non-pure functions inside our pure `init-state` and `tick`, so I implemented them behind the scenes. As you can see the init state function can return a map containing a `:ui` key. This lets us declaratively handle the ui widgets.

For the exercise itself the slider controls the standard deviation as suggested by the prompt. I went with a HSL color-mode that tends to be more reddish thanks to the normal distribution, but can be made a little more colorful by increasing the stdev.

```clojure
^{::clerk/no-cache true ::clerk/viewer clerk/code}
(slurp "src/noc/chapter_0_4e.cljs")
(show-sketch :c0.4e)
```
19 changes: 19 additions & 0 deletions src/noc/chapter_0_4.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
(ns noc.chapter-0-4
(:require
[quil.core :as q]))

(def size [640 240])

(defn init-state [{:keys [width height]}]
{})

(defn setup! []
(q/background 255))

(defn tick [s] s)

(defn draw! [_]
(let [x (+ 320 (* 60 (q/random-gaussian)))]
(q/no-stroke)
(q/fill 0 10)
(q/ellipse x 120 16 16)))
27 changes: 27 additions & 0 deletions src/noc/chapter_0_4e.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
(ns noc.chapter-0-4e
(:require
[quil.core :as q]))

(def size [640 240])

(defn init-state [_]
{:ui {:stdev {:type :slider
:min 5 :max 120
:value 10}}})

(defn setup! []
(q/color-mode :hsl 360)
(q/background 360))

(defn tick [s]
s)

(defn rand-gaus [mean stdev] (+ mean (* stdev (q/random-gaussian))))

(defn draw! [{:keys [width height ui]}]
(let [stdev (get-in ui [:stdev :value])
x (rand-gaus (quot width 2) stdev)
y (rand-gaus (quot height 2) stdev)]
(q/no-stroke)
(q/fill (rand-gaus 0 stdev) 200 200 200)
(q/ellipse x y 16 16)))
57 changes: 33 additions & 24 deletions src/noc/sketch.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,20 @@
(:require [quil.core :as q]
[quil.middleware :as m]
[quil.sketch :as ap :include-macros true]
[noc.ui :as ui]
[noc.chapter-0-1 :as c0.1]
[noc.chapter-0-2 :as c0.2]
[noc.chapter-0-3 :as c0.3]
[noc.chapter-0-3e :as c0.3e]))
[noc.chapter-0-3e :as c0.3e]
[noc.chapter-0-4 :as c0.4]
[noc.chapter-0-4e :as c0.4e]))

(def sketches {:walker (sketch-> c0.1)
:rand-dist (sketch-> c0.2)
:walker-right (sketch-> c0.3)
:walker-dynamic (sketch-> c0.3e)})
:walker-dynamic (sketch-> c0.3e)
:random-gaussian (sketch-> c0.4)
:c0.4e (sketch-> c0.4e)})

(defn load-sketch [s]
(when-let [sk (get sketches s)]
Expand Down Expand Up @@ -60,15 +65,18 @@
:time-acc [1]
:second? false}})

(defn setup-state [init-state]
(merge (default-state (time-now!)) init-state))
(defn setup-state [init-fn]
(-> (default-state (time-now!))
(merge (init-fn {:width (q/width) :height (q/height)}))
(ui/prepare-ui)))

(defn reset [sketch*]
(let [{:keys [applet opts init-state]} @sketch*
{:keys [setup]} opts]
(let [{:keys [applet opts]} @sketch*
{:keys [setup init]} opts]
(ap/with-sketch applet
(let [state* (q/state-atom)]
(reset! state* (setup-state init-state))
(ui/remove-all! @state*)
(reset! state* (setup-state init))
(setup)))))

(defn pause [sketch*]
Expand All @@ -85,17 +93,19 @@
(q/frame-rate (-> @state* :_time :target-frame-rate))
(swap! state* assoc :_paused? false)))))

(defn setup-wrapper [adjust-frame {:keys [target-frame-rate] :as init-state} setup]
(adjust-frame)
(q/frame-rate target-frame-rate)
(setup)
(setup-state init-state))
(defn setup-wrapper [adjust-frame init setup]
(let [{:keys [target-frame-rate] :as init-state} (setup-state init)]
(adjust-frame)
(q/frame-rate target-frame-rate)
(setup)
init-state))

(defn update-inputs [state]
(-> state
(assoc :mouse-x (q/mouse-x)
:mouse-y (q/mouse-y)
:mouse-button (q/mouse-button)
:mouse-pressed? (q/mouse-pressed?)
:key (q/key-as-keyword)
:key-code (q/key-code)
:raw-key (q/raw-key))))
Expand All @@ -104,6 +114,7 @@
(->
state
(update-inputs)
(ui/update-ui)
(assoc :width (q/width) :height (q/height))
(tick-time (time-now!) (q/current-frame-rate))
(tick)))
Expand All @@ -112,15 +123,13 @@
(draw state))

(defn show-sketch [adjust-frame {:keys [init setup tick draw size] :as opts} el]
(let [init-state (init {:width (first size) :height (last size)})]
{:applet (apply q/sketch (apply concat
(-> opts
(assoc :middleware [m/fun-mode])
(assoc :host el)
(assoc :update (partial tick-wrapper tick))
(assoc :setup (partial setup-wrapper (partial adjust-frame el) init-state setup))
(assoc :draw (partial draw-wrapper draw)))))
:sketch-name (:sketch-name opts)
:init-state init-state
:opts opts
:el el}))
{:applet (apply q/sketch (apply concat
(-> opts
(assoc :middleware [m/fun-mode])
(assoc :host el)
(assoc :update (partial tick-wrapper tick))
(assoc :setup (partial setup-wrapper (partial adjust-frame el) init setup))
(assoc :draw (partial draw-wrapper draw)))))
:sketch-name (:sketch-name opts)
:opts opts
:el el})
56 changes: 56 additions & 0 deletions src/noc/ui.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
(ns noc.ui
(:require
[quil.sketch :as ap :include-macros true]))

(defn slider
"Creates a slider <input></input> element.
ref: https://p5js.org/reference/#/p5/createSlider"
([{:keys [value step position size min max]
:or {min 0 max 100 value 0 step 0} :as opts}]
(let [slider (.createSlider (ap/current-applet) min max value step)]
(when position
(.position slider (first position) (second position)))
(when size
(.size slider size))
slider)))

(defn slider-value [s]
(.value s))

(defn prepare-element [state id {:keys [type] :as def}]
(condp = type
:slider (assoc-in state [:_ui id] (slider def))
state))

(defn prepare-ui [{:keys [ui] :as state}]
(if ui
(reduce (fn [state [id ui-def]]
(prepare-element state id ui-def)) state ui)
state))

(defn update-element [state id]
(let [handle (get-in state [:_ui id])
def (get-in state [:ui id])]
(if def
(condp = (:type def)
:slider (assoc-in state [:ui id :value] (slider-value handle))
state)
(do
(.remove handle)
(update-in state [:_ui] #(dissoc % id))))))

(defn update-ui
"Populates the state map with the current values of any ui elements."
[state]
(if (:_ui state)
(reduce (fn [state [id _]]
(update-element state id)) state (:_ui state))
state))

(defn remove-all!
"Removes all UI elements from the DOM, returns nil. "
[state]
(when (:_ui state)
(doseq [[_ handle] (:_ui state)]
(when handle
(.remove handle)))))

0 comments on commit 594ce33

Please sign in to comment.