I had a great time at Devoxx this week – many thanks to Stephan and crew and whoever is making all this delicious beer here in Belgium.
During the end of my Cracking Clojure talk I showed a screencast I did where I progressively build up a solution to Conway’s Life in Clojure.
The end solution is completely stolen from Christophe Grand‘s elegant solution – I just filled in the work leading up to it (and refactored slightly). My end result from the ‘cast:
(defn neighbors [[x y]]
(for [dx [-1 0 1]
dy (if (zero? dx)
[-1 1]
[-1 0 1])]
[(+ dx x) (+ dy y)]))
(defn live [n alive?]
(or (= n 3)
(and (= n 2) alive?)))
(defn step [world]
(set
(for [[cell n] (frequencies (mapcat neighbors world))
:when (live n (world cell))]
cell)))
(defn life [initial-world]
(iterate step initial-world))
Note that the final function life will return you an infinite sequence of steps in the game of life starting from an initial world (a set of cells).
When I went to Vaclav Pech’s talk on GPars on Tuesday he did a Groovy life implementation in GPars with dataflow which was pretty slick and had a real gui. So, in the keynote before my talk I whipped up a ui of my own using Processing and the clj-processing wrappers. My code is gross, especially in comparison to the life impl but it wraps a ref around the infinite sequence, then peels off the next item (in do-step) each time draw is called (on draw) by Processing and writes it to the screen.
To add clj-processing to you project.clj, use:
[org.clojars.automata/rosado.processing "1.1.0"]
It appears to only play nice with Clojure 1.2.x right now due to a dynamic var in the applet.
(ns demo.lifeui
(:use [demo.life]
[rosado.processing]
[rosado.processing.applet]))
(def glider #{[0 1] [1 2] [2 0] [2 1] [2 2]})
(def blinker #{[3 3] [3 4] [3 5]})
(def worlds (ref (life glider)))
(defn setup []
(background-int 0)
(stroke-int 255)
(framerate 4))
(defn do-step []
(dosync
(let [next-world (first @worlds)]
(alter worlds #(drop 1 %))
next-world)))
(defn grid [rows cols cells]
(let [cellw (/ (width) cols)
cellh (/ (height) cols)]
(doseq [x (range rows)
y (range cols)]
(if (contains? cells [x y])
(fill-float 250 50 50)
(fill-float 0))
(rect (* x cellw) (* y cellh) cellw cellh))))
(defn draw []
(background 0)
(grid 32 32 (do-step)))
(defapplet lifeui
:title "Life"
:setup setup
:draw draw
:size [800 600])
(run lifeui)
One key problem with this whole impl is that it ignores boundary wrapping so please feel free to fix that as an exercise for the reader.
