|
(ns game-of-life.core |
|
(:require [quil.core :as q])) |
|
|
|
(defn get-neighbour-pos |
|
"Cells have eight neighbours that surround it. |
|
This function delivers their position." |
|
[pos] |
|
(map #(mapv + pos %) [[-1 0] [-1 1] [0 1] [1 1] [1 0] [1 -1] [0 -1] [-1 -1]])) |
|
|
|
(defn get-alive-neighbours |
|
"Delivers the alive neighbour positions on the board around pos." |
|
[board pos] |
|
(->> |
|
(map #(get board %) (get-neighbour-pos pos)) |
|
(filter #(= ::alive %)))) |
|
|
|
(defn come-to-life-or-die |
|
"Determines if the cell at position pos comes to life, dies |
|
or remains." |
|
[board pos] |
|
(let [cell (get board pos ::dead) |
|
alive-neighbours (count (get-alive-neighbours board pos))] |
|
(cond |
|
;; A dead cell comes to life if it has exactly three live neighbours |
|
(and (= ::dead cell) (= 3 alive-neighbours)) ::alive |
|
;; A live cell dies if it has four or more neighbours |
|
(and (= ::alive cell) (>= alive-neighbours 4)) ::dead |
|
;; A live cell dies if it has one or zero neighbours |
|
(and (= ::alive cell) (<= alive-neighbours 1)) ::dead |
|
;; All other cells continue as they are |
|
:else cell))) |
|
|
|
(defn get-all-relevant-positions |
|
"Get all positions that may be affected by alive cells. |
|
This includes the neighbour cells of alive cells." |
|
[board] |
|
(let [alive-positions (keys (filter #(= ::alive (second %)) board)) |
|
alive-positions-and-neighbour-positions (set (mapcat get-neighbour-pos alive-positions))] |
|
alive-positions-and-neighbour-positions)) |
|
|
|
(defn game-of-life |
|
"Cells are arranged using a map with the position as key |
|
A cell is either alive or dead - if a position on the board is set to ::alive it is alive |
|
dead otherwise" |
|
[start-board iterations] |
|
(let [board start-board] |
|
(loop [board board i iterations boards []] |
|
(if (<= i 0) |
|
boards |
|
(recur |
|
(reduce (fn [new-board pos] |
|
(assoc new-board pos (come-to-life-or-die board pos))) |
|
{} |
|
(get-all-relevant-positions board)) |
|
(dec i) |
|
(conj boards board)))))) |
|
|
|
(defn parse-board |
|
"Parses a board in string representation into a map |
|
with the position as key. |
|
Example-Board in String representation: |
|
*** |
|
* * |
|
* * |
|
* * |
|
***" |
|
[s] |
|
(when (seq s) |
|
(let [lines (clojure.string/split s #"\n") |
|
alive-or-dead-rows (map |
|
(fn [line] |
|
(map (fn [p] (if (= \* p) |
|
::alive |
|
::dead)) |
|
(seq line))) |
|
lines)] |
|
(loop [y 0 rows alive-or-dead-rows board {}] |
|
(if (seq rows) |
|
(recur |
|
(inc y) |
|
(rest rows) |
|
(reduce (fn [m [x state]] |
|
(if (= ::alive state) |
|
(assoc m (vector x y) state) |
|
m)) |
|
board |
|
(map (fn [x state] (vector x state)) (range) (first rows)))) |
|
board))))) |
|
|
|
|
|
(comment |
|
;; This board dies after 54 iterations |
|
(def p1 " |
|
*** |
|
* * |
|
* * |
|
|
|
* * |
|
* * |
|
***") |
|
(def glider " |
|
* |
|
* * |
|
**") |
|
(def small-exploder " |
|
* |
|
*** |
|
* * |
|
*") |
|
(def exploder " |
|
* * * |
|
* * |
|
* * |
|
* * |
|
* * *") |
|
(get-neighbour-pos [0 0]) |
|
(get-neighbour-pos [1 1]) |
|
(come-to-life-or-die {[-1 0] ::alive [-1 1] ::alive [0 1] ::alive} [0 0]) |
|
(come-to-life-or-die {[0 0] ::alive [-1 0] ::alive [-1 1] ::alive [0 1] ::alive} [0 0]) |
|
(come-to-life-or-die {[0 0] ::alive [-1 0] ::alive} [0 0]) |
|
(come-to-life-or-die {[0 0] ::alive [-1 0] ::alive [-1 1] ::alive [0 1] ::alive [1 1] ::alive} [0 0]) |
|
(parse-board p1) |
|
(get-all-relevant-positions (parse-board p1)) |
|
(game-of-life {} 10) |
|
(game-of-life (parse-board p1) 54) |
|
) |
|
|
|
;; Visualize it with Quil |
|
|
|
(def exploder " |
|
* * * |
|
* * |
|
* * |
|
* * |
|
* * *") |
|
|
|
(def boards (cycle (game-of-life (parse-board exploder) 54))) |
|
|
|
(def block 10) |
|
|
|
(def colors {::background [255 255 255] |
|
::dead [255 255 255] |
|
::alive [0 0 0]}) |
|
|
|
(defn draw [] |
|
(q/with-translation [(/ (q/width) 2.5) (/ (q/height) 2.5)] |
|
; note that we don't use draw-plot here as we need |
|
; to draw only small part of a plot on each iteration |
|
(let [t (q/frame-count)] |
|
(q/background 255) |
|
(doseq [[[x y] high] (->> boards (drop t) first)] |
|
(when (= high ::alive) |
|
(apply q/fill (colors high)) |
|
(apply q/stroke (colors ::dead)) |
|
(q/rect (* block x) (* block y) block block)))))) |
|
|
|
(defn setup [] |
|
(q/frame-rate 5)) |
|
|
|
(q/defsketch game-of-life-board |
|
:size [600 600] |
|
:setup setup |
|
:draw draw) |