32leaves.net

The a2 automaton and Clojure

Clojure

During the preparation time for my exams I wanted to do something else than just learning. So I decided to give functional programming a try. The language of choice was Clojure. It’s a LISP written specifically for the JVM. This language got some pretty neat features (taken from wikipedia):

  • Dynamic development with a read-eval-print loop
  • Functions as first-class objects with an emphasis on recursion instead of side-effect-based looping
  • Provides a rich set of immutable, persistent data structures
  • Concurrent programming through software transactional memory, an agent system, and a dynamic var system
  • Compatibility with Java: Clojure can natively call methods and create objects from any Java library, and Java programs can call Clojure functions
  • Clojure is a compiled language producing JVM bytecode

To try this thing out I decided to implement the a2 automaton described a few posts before (those neat picture and the videos, etc.). So below is the code for the automaton. If you’re able to read/understand LISP this should be (hopefully) understandable :-). Especially the

moore-aliveness

function is really neat.

All in all it took me about 8 hours to write this code. Functional programming is a completely new world. Especially that we’re mostly working with immutable data here. That’s also the cause for this code being propably pretty slow. My use of recursion here is most likely not the way that is supposed to be done in Clojure. Anyway it was a fun experience and broadened my (programmers) horizon.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
(def WORLD_WIDTH  800)
(def WORLD_HEIGHT 600)

(def setup
    (map (fn [x] (vec (range WORLD_HEIGHT)))
        (vec (range WORLD_WIDTH))))

(defn init ([field]
    (concat
        (vector (map (fn [x] (rem x 2)) (first field)))
        (map (fn [x] (map (fn [y] 0) x)) (rest field))
    )
))

(defn moore-aliveness ([field x y]
    (let [n (vector [-1 -1] [ 0 -1] [ 1 -1]
                            [-1  0]         [ 1  0]
                            [-1  1] [ 0  1] [ 1  1])]
                ((fn [a i] (do
                    (let [rem (rest  i)
                                cev (first i)
                                row (nth field (+ y (nth cev 0)) [])
                                col (nth row   (+ x (nth cev 1)) 0)
                                b   (+ a col)]
                    (if (nil? rem) b (recur b rem))))) 0 n))
))

(defn nextgen-for-row ([field row x y result]
    ;(println (str x "x" y ": " row))
    (let [nx (inc x) rr (rest row) cr (first row)]
        ;(println (str "[" x " " y "] " cr))
        (let [
            nr (if (== (moore-aliveness field x y) 2) 1 0)
            nresult (conj result nr)]
            (if (nil? rr) nresult (recur field rr nx y nresult))
        )
    )
))

(defn nextgen (
    [field] (
        (fn [f y r] (let [ny (inc y) nf (rest f) cf (first f)]
            (let [nr (concat r (vector (nextgen-for-row field cf 0 y ())))]
                (if (nil? nf) nr (recur nf ny nr)))
        )) field 0 ()
    )
))

(def ng (nextgen (init setup)))
Fork me on GitHub