First, I created a simple scoreboard sans timer, to keep track of various states in a structure like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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})) |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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)) |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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"))})) |
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.