11.27.2014

OM Life and Benchwarmer

With Holidays come days off, so I had a chance to explore OM and core.async in ClojureScript.

First, I created a simple scoreboard sans timer, to keep track of various states in a structure like this:
(defonce init
(reset! app-state
{:home {:home? true
:score 0
:timeouts 3}
:away {:home? false
:score 0
:timeouts 3}
:ball-on 50
:to-go 10
:down 1
:qtr 1
:time "15:00"
:possession :home}))
view raw gistfile1.clj hosted with ❤ by GitHub


With the scoreboard driven by events put on a core.async channel, I had the idea to test the state changes with an event generator:
(ns scoreboard.test
(:require [scoreboard.events :as e :refer [publish!]]
[scoreboard.core :as s]))
(def score-range (range 0 150))
(def timeouts-range (range 1 4))
(def paths-values
[[[:home :score] score-range]
[[:home :timeouts] timeouts-range]
[[:away :score] score-range]
[[:away :timeouts] timeouts-range]
[[:ball-on] (range 0 101)]
[[:to-go] (range 0 101)]
[[:down] (range 1 5)]
[[:qtr] (range 1 5)]])
(defn update-number-events []
(map
(fn [[path possible-values]]
(e/event ::s/update-number {:path path
:max (max possible-values)
:min (min possible-values)
:number (rand-nth possible-values)}))
paths-values))
(defn event-bag []
(concat [(e/event ::s/change-possession)]
(update-number-events)))
(defn publish-random-event []
(publish! (rand-nth (event-bag))))
(defn main []
(.setInterval js/window publish-random-event 10))
view raw gistfile1.clj hosted with ❤ by GitHub


The rendered results were kind of humorous: BenchWarmer

Watching the numbers on the scoreboard whiz by, I decided to redo my implementation of Conway's Game of Life using OM:
(ns om-life.core
(:require [om.core :as om :include-macros true]
[om.dom :as dom :include-macros true]
[om-life.events :as e :refer [publish! event]]
[sablono.core :as html :refer-macros [html]]))
(def seed
#{[8 13] [9 13] [4 11] [6 13] [8 14] [11 14] [4 12] [5 13]
[4 13] [14 14] [12 13] [15 14] [12 14] [14 13] [38 13]
[37 13] [34 13] [35 14] [4 14] [34 14] [5 14] [32 13]
[6 14] [27 12] [29 13] [27 11] [28 13] [31 14] [29 14]
[37 14] [24 14] [27 14] [27 13] [41 14] [18 13] [20 14]
[40 13] [18 14] [21 14] [17 14] [40 15] [17 13] [18 12]
[23 14] [23 13] [21 13] [20 13]})
(defonce life (atom seed))
(defn change-cell
([x y]
(swap! life (fn [cells]
(if (cells [x y])
(set (remove #{[x y]} cells))
(conj cells [x y])))))
([x y add?]
(if add?
(swap! life conj [x y])
(swap! life #(set (remove #{[x y]} %))))))
(defn change [x y]
(fn [e]
(.preventDefault e)
(change-cell x y)))
(defonce dragging? (atom false))
(defn drag-start [x y]
(fn [e]
(.preventDefault e)
(reset! dragging? true)))
(defn drag-end [x y]
(fn [e]
(.preventDefault e)
(reset! dragging? false)))
(defn drag-by [x y]
(fn [e]
(.preventDefault e)
(if @dragging?
(change-cell x y :add))))
(defn cell [{:keys [x y alive?]} owner]
(reify
om/IRender
(render [_]
(html
[:td {:class (if alive? "alive")
:draggable true
:on-click (change x y)
:on-mouse-down (drag-start x y)
:on-mouse-up (drag-end x y)
:on-mouse-move (drag-by x y)}]))))
(defonce ticker (atom nil))
(defn publish-step-event! []
(publish! (event ::step)))
(defn start [owner]
(fn [e]
(.preventDefault e)
(js/clearInterval @ticker)
(om/set-state! owner :running? true)
(reset! ticker (.setInterval js/window publish-step-event! 80))))
(defn stop [owner]
(fn [e]
(.preventDefault e)
(om/set-state! owner :running? false)
(js/clearInterval @ticker)))
(defn game [world owner]
(reify
om/IInitState
(init-state [_]
{:height 40
:width 70})
om/IRenderState
(render-state [_ {:keys [width height running?]}]
(html
[:div
[:div.tools
[:button {:on-click (fn [e]
(.preventDefault e)
(publish-step-event!))} "Step"]
(if running?
[:button {:on-click (stop owner)} "Stop"]
[:button {:on-click (start owner)} "Start"])]
[:table.game
(for [h (range height)]
[:tr
(for [w (range width)]
(om/build cell {:x w :y h :alive? (world [w h])}))])]]))))
(defn get-neighbors [p]
((apply juxt (for [a [-1 0 1]
b [-1 0 1]
:when (not= [0 0] [a b])]
(fn [[x y]] [(+ x a) (+ y b)]))) p))
(defn generation [cells]
(set (for [[loc n] (frequencies (mapcat get-neighbors cells))
:when (or (= n 3) (and (= n 2) (cells loc)))]
loc)))
(def benchmarks (atom []))
(defn step [_]
(swap! benchmarks #(cons (. (js/Date.) (getTime)) (take 999 %)))
(swap! life generation))
(defn bench []
(let [latest (take 1000 @benchmarks)]
(/ (- (first latest) (last latest))
(dec (count latest)))))
(defonce _
(e/subscriptions
[::step] step))
(defn main []
(om/root
game
life
{:target (. js/document (getElementById "app"))}))
view raw gistfile1.clj hosted with ❤ by GitHub


The results ended up a lot faster than my last attempt.

Old: Life

New: OM-Life
(the new one has drag-to-draw capabilities)

Check out the project on github.

No comments:

Post a Comment