I’ve been trying to learn Clojure recently, so I wrote
Conway’s Game of Life. I’m sure that the code is highly
non-idiomatic, but I’d rather get it out into the wild, instead of
sitting on my disk.
Note that I have inline tests (the simple println statements) mostly
because I wasn’t trying to figure out how to properly test in Clojure
(yet).
(require'[clojure.set:asset]);; I feel that there are too many explicit conversions with `set`, but;; I'm not sure how to remove them.;; Reusable test cells(def center{:x0:y0})(def top-left{:x-1:y1})(def top-right{:x1:y1})(def bottom-left{:x-1:y-1})(def bottom-right{:x1:y-1})(defn neighbors"Finds all cells that are neighbors of the given cell"[cellcells](letfn[(neighbor?[poss](and (>= (poss:x)(dec (cell:x)))(<= (poss:x)(inc (cell:x)))(>= (poss:y)(dec (cell:y)))(<= (poss:y)(inc (cell:y)))(not (= posscell))))](set (filter neighbor?cells))))(println "* neighbors");;(println (= #{bottom-lefttop-right}(neighborscenter[bottom-leftcentertop-right])));;(println (= #{center}(neighborsbottom-left[bottom-leftcentertop-right])))(defn kill-off"Removes cells that do not have enough neighbors to stay alive"[cells](set (remove #(let [n_neighbors(count (neighbors%cells))](or (< n_neighbors2)(> n_neighbors3)))cells)))(println "* kill-off");; Cell with no neighbors dies(println (= #{}(kill-off[center])));; Cell with one neighbor dies(println (= #{}(kill-off[centertop-left])));; Cell with two neighbors lives(println (= #{center}(kill-off[centertop-lefttop-right])));; Cell with three neighbors lives(println (= #{center}(kill-off[centertop-lefttop-rightbottom-left])));; Cell with four neighbors dies(println (= #{}(kill-off[centertop-lefttop-rightbottom-leftbottom-right])))(defn all-neighbors"Finds the cells in a 3x3 area around the given cell"[{x:xy:y}](for [x-mod(range -12)y-mod(range -12)]{:x(+ xx-mod):y(+ yy-mod)}))(defn interesting-cells"Finds all cells that could potentially change on a given step"[cells](reduce set/union(map all-neighborscells)))(defn fringe"Finds all cells that are currently dead but could come alive"[cells](set/difference(set (interesting-cellscells))cells))(defn come-alive"Finds all cells that are currently dead but will come alive"[cells](set (remove #(not (== 3(count (neighbors%cells))))(fringecells))))(println "* come-alive");; Empty cell with no neighbors stays dead(println (= #{}(come-alive[])));; Empty cell with one neighbor stays dead(println (= #{}(come-alive[top-left])));; Empty cell with two neighbors stays dead(println (= #{}(come-alive[top-lefttop-right])));; Empty cell with three neighbors comes alive(println (= #{center}(come-alive[top-lefttop-rightbottom-left])));; Empty cell with four neighbors stays dead(println (= #{}(come-alive[top-lefttop-rightbottom-leftbottom-right])))(defn step"Returns the cells that are still alive after or came alive during a single time step"[cells](set/union(kill-offcells)(come-alivecells)));; Printing functions;; Have a 21x21 window into the world(def min-x-10)(def max-x10)(def min-y-10)(def max-y10)(defn print-cell[cell](if cell(print "X")(print " ")))(defn print-row[cells](dorun (for [x(range min-x(inc max-x))](print-cell(first (filter #(= x(%:x))cells)))))(println))(defn print-cells[cells](println "---")(dorun (for [y(reverse (range min-y(inc max-y)))](print-row(filter #(= y(%:y))cells))))(println "---"));; Two mildly-interesting start states(def blinker[{:x-1:y0}{:x0:y0}{:x1:y0}])(def glider[{:x2:y2}{:x2:y1}{:x2:y0}{:x1:y0}{:x0:y1}])(def initial-stateglider)(def life(iterate stepinitial-state))(dotimes [i50](print-cells(nth lifei)))