Skip to content

Instantly share code, notes, and snippets.

@ckirkendall
Created January 22, 2014 11:44
Show Gist options
  • Save ckirkendall/8557364 to your computer and use it in GitHub Desktop.
Save ckirkendall/8557364 to your computer and use it in GitHub Desktop.
This was just for fun to see how much effort it would take. It is a functional clojure version of the java bouncy ball tutorial.
(ns bouncy.core
(:require [goog.math :as math :refer [Vec2]]
[goog.math.Vec2 :as vec]))
(declare step draw-circle)
(def app {:elems [{:pos (Vec2. 100 100) :speed (Vec2. -3 5) :radius 20}
{:pos (Vec2. 200 200) :speed (Vec2. 5 3) :radius 30}
{:pos (Vec2. 300 200) :speed (Vec2. 7 6) :radius 5}
{:pos (Vec2. 400 200) :speed (Vec2. -5 -6) :radius 10}
{:pos (Vec2. 200 400) :speed (Vec2. 2 -2) :radius 15}
{:pos (Vec2. 200 200) :speed (Vec2. 1 -6) :radius 25}
{:pos (Vec2. 200 300) :speed (Vec2. -1 -4) :radius 35}
{:pos (Vec2. 250 250) :speed (Vec2. 5 -4) :radius 40}
{:pos (Vec2. 300 250) :speed (Vec2. -3 -6) :radius 10}
{:pos (Vec2. 250 300) :speed (Vec2. 2 -3) :radius 15}
{:pos (Vec2. 200 400) :speed (Vec2. 1 -9) :radius 25}
{:pos (Vec2. 400 200) :speed (Vec2. -9 -1) :radius 35}]
:bounds [(Vec2. 0 0) (Vec2. 500 500)]
:last-time (.now js/window.performance)
:background "black"})
(defprotocol Lens
(fetch [_])
(push [_ el]))
(defn e-lens [app idx]
(reify Lens
(fetch [_] (get-in app [:elems idx]))
(push [_ el] (assoc-in app [:elems idx] el))))
(def pi*2 (* 2 Math/PI))
(defn draw [app ctx]
(.beginPath ctx)
(.rect ctx 0 0 500 500)
(set! (.-fillStyle ctx) (:background app))
(.fill ctx)
(doseq [el (:elems app)]
(draw-circle el ctx))
app)
(defn draw-circle [el ctx]
(.beginPath ctx)
(.arc ctx
(.-x (:pos el))
(.-y (:pos el))
(:radius el)
0
pi*2)
(set! (.-fillStyle ctx) "red")
(.fill ctx)
(set! (.-strokeStyle ctx) "#330000")
(.stroke ctx))
(defn delta-vec [vec dt]
(Vec2. (* (.-x vec) dt) (* (.-y vec) dt)))
(defn update-loc [el dt]
(assoc el :pos (vec/sum (:pos el) (delta-vec (:speed el) dt))))
(defn handle-bounds [el [top bottom]]
(let [pos (:pos el)
sp (:speed el)
x (.-x pos)
y (.-y pos)
vx (.-x sp)
vy (.-y sp)
r (:radius el)
tx (.-x top)
ty (.-y top)
bx (.-x bottom)
by (.-y bottom)]
(cond-> el
(< (- x r) tx) (assoc :speed (Vec2. (* -1 vx) vy)
:pos (Vec2. r y))
(> (+ x r) bx) (assoc :speed (Vec2. (* -1 vx) vy)
:pos (Vec2. (- bx r) y))
(< (- y r) ty) (assoc :speed (Vec2. vx (* -1 vy))
:pos (Vec2. x r))
(> (+ y r) by) (assoc :speed (Vec2. vx (* -1 vy))
:pos (Vec2. x (- by r))))))
(defn callback [app ctx]
(js/requestAnimationFrame (step app ctx)))
(defn e-step [app idx dt]
(let [lens (e-lens app idx)
el (fetch lens)]
(-> el
(update-loc dt)
(handle-bounds (:bounds app))
(#(push lens %)))))
(defn e-steps [app dt]
(reduce #(e-step %1 %2 dt) app (range (count (:elems app)))))
(defn step [app ctx]
(fn [now]
(let [dt (/ (- now (:last-time app)) 16)]
(-> app
(e-steps dt)
(draw ctx)
(assoc :last-time now)
(callback ctx)))))
(defn run []
(let [canvas (.createElement js/document "canvas")]
(.setAttribute canvas "width" (.-x (second (:bounds app))))
(.setAttribute canvas "height" (.-y (second (:bounds app))))
(.appendChild (.-body js/document) canvas)
((step app (.getContext canvas "2d")) (.now js/window.performance))))
(set! (.-onload js/window) run)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment