July 8, 2019

Conway's Game of Life

Conway's Game of Life

I did this awhile back as part of an interview. They never called me back. Enjoy!

(require '[reagent.core :refer [atom]])

(defn neighbors [[x y]]
  (for [dx [-1 0 1] dy [-1 0 1]
        :when (not (= 0 dx dy))]
    [(+ x dx) (+ y dy)]))

(defn step [lives]
  (set (for [[pos live-neighbors]
             (frequencies (mapcat neighbors lives))
             :when (or (= 3 live-neighbors)
                       (and (contains? lives pos)
                            (= 2 live-neighbors)))]
         pos)))

(def status (atom "not started"))

(def world-size (atom 10))

(def lives (atom #{}))

(def timer-id (atom 0))

(def timer (atom :off))

(defn start-timer! []
  (when (= @timer :off)
    (reset! timer-id (js/setInterval #(swap! lives step) 300))
    (reset! timer :on)))

(defn stop-timer! [timer]
  (js/clearInterval timer))

(defn size-input []
  (fn []
    [:div
     [:p "World size: "
      [:input {:type "number"
               :value @world-size
               :on-change #(reset! world-size (-> % .-target .-value))}]]]))

(defn wrap-square [[x y]]
  [(mod x (js/parseInt @world-size)) (mod y (js/parseInt @world-size))])

(defn wrap-squares [lives]
  (map wrap-square lives))

(def glider
  {:name "Glider"
   :lives #{[3 4] [4 4] [5 4] [5 3] [4 2]}
   :size 10})

(def spaceship
  {:name "Spaceship"
   :lives #{[4 3] [5 3] [7 2] [5 5] [8 3] [7 4] [5 4]
            [6 5] [4 4] [7 3] [6 2] [6 4]}
   :size 10})

(def penta-decathlon
  {:name "Penta-decathlon"
   :lives
   #{[8 5] [5 5] [13 5] [11 4] [4 5] [12 5]
     [9 5] [6 6] [7 5] [10 5] [11 6] [6 4]}
  :size 18})

(def glider-gun
  {:name "Glider gun"
   :lives #{[27 18] [32 13] [25 13] [28 15] [36 16]
            [25 19] [28 17] [47 13] [28 16] [33 14]
            [34 12] [36 12] [27 14] [24 19] [26 16]
            [23 18] [34 16] [32 15] [12 15] [33 13]
            [36 17] [46 14] [29 16] [36 11] [12 16]
            [22 15] [23 14] [24 13] [22 16] [22 17]
            [32 14] [33 15] [13 16] [47 14] [13 15] [46 13]}
   :size 30})
(defn rect-cell [x y]
  [:rect.cell
   {:x x :width 1
    :y y :height 1
    :fill "white"
    :stroke-width 0.025
    :stroke "black"}])

(defn dead [x y]
  [:rect
   {:width 0.9
    :height 0.9
    :fill "lightpink"
    :x (+ 0.05 x)
    :y (+ 0.05 y)
    :on-click
    (fn click-square [e]
      (if (= @status "not started")
        (swap! lives conj [x y])
        (js/alert "Hands off! You are not God.")))}])

(defn life [x y]
  [:rect
   {:width 0.9
    :height 0.9
    :fill "purple"
    :x (+ 0.05 x)
    :y (+ 0.05 y)
    :on-click
    (fn click-square [e]
      (if (= @status "not started")
        (swap! lives disj [x y])
        (js/alert "Ouch!")))}])

(defn render-board []
  (into
   [:svg.board
    {:view-box (str "0 0 " @world-size " " @world-size)
     :shape-rendering "auto"
     :style {:max-height "500px"}}]
   (for [x (range @world-size)
         y (range @world-size)]
     [:g
      [rect-cell x y]
      (if (some #{[x y]} (wrap-squares @lives))
        [life x y]
        [dead x y])])))

(defn config-button [m]
  [:button
   {:on-click
    (fn step-click [e]
      (reset! lives (:lives m))
      (reset! world-size (:size m)))}
   (:name m)])

(defn game []
  [:center
   [:h1 "Conway's Game of Life"]
   [size-input]
   [:div
    [:button
     {:on-click
      (fn step-click [e]
        (swap! lives step)
        (reset! status "started"))}
     "Step"]
    [:button
     {:on-click
      (fn play-click [e]
        (start-timer!)
        (reset! status "started"))}
     "Play"]
    [:button
     {:on-click
      (fn stop-click [e]
        (stop-timer! @timer-id)
        (reset! timer :off))}
     "Stop"]
    [:button
     {:on-click
      (fn reset-click [e]
        (reset! lives #{})
        (reset! status "not started"))}
     "Reset"]]
   [:div
    [config-button glider]
    [config-button spaceship]
    [config-button penta-decathlon]
    [config-button glider-gun]]
   [:div [render-board]]
   [:p (str "Current residents: " @lives)]
   [:p (str "Wrapped to: " (wrap-squares @lives))]])
Tags: Reagent games