The weekly coding challenge in Eric Normand's newsletter this week involves implementing Langton's ant.
I wanted something on my screen so I put together a simple re-frame app. The core functions relating to the ant:
world.cljs
(ns ant.world)
(defn init-world [width height]
{:width width
:height height
:grid (into [] (repeat height (into [] (repeat width 0))))
:ant-pos [(quot width 2) (quot height 2)]
:ant-dir :up
:stopped? false})
(defn within-bounds? [[row col] width height]
(and (>= row 0) (>= col 0)
(< row width) (< col height)))
(defn turn-right [dir]
(case dir
:up :right
:right :down
:down :left
:left :up))
(defn turn-left [dir]
(case dir
:up :left
:left :down
:down :right
:right :up))
(defn move [[x y] dir]
(case dir
:up [x (dec y)]
:right [(inc x) y]
:down [x (inc y)]
:left [(dec x) y]))
(defn iter-world [{:keys [width height grid
ant-pos
ant-dir
stopped?]
:as world}]
(if (and (not stopped?) (within-bounds? ant-pos width height))
(let [cell-color (get-in grid ant-pos)
is-white? (= cell-color 0)
next-dir (if is-white? (turn-right ant-dir) (turn-left ant-dir))
next-pos (move ant-pos next-dir)
next-grid (assoc-in grid ant-pos (if is-white? 1 0))]
{:width width
:height height
:grid next-grid
:ant-pos next-pos
:ant-dir next-dir
:stopped? false})
(assoc world :stopped? true)))
And the view:
views.cljs
(ns ant.views
(:require
[re-frame.core :as re-frame]
[ant.subs :as subs]
[ant.events :as ev]
[cljs.pprint :refer [pprint]]
))
(defn main-panel []
(let [grid (re-frame/subscribe [::subs/grid])]
(into [:table {:style {:border "1px solid red"}}]
(for [row @grid]
(into [:tr]
(for [cell row]
[:td {:width "5px" :height "5px"
:style {:backgroundColor (if (= cell 1) "black" "white")}}]))))))
(defn dispatch-timer-event
[]
(let [now (js/Date.)]
(re-frame/dispatch [::ev/timer now]))) ;; <-- dispatch used
;; Call the dispatching function every second.
;; `defonce` is like `def` but it ensures only one instance is ever
;; created in the face of figwheel hot-reloading of this file.
(defonce do-timer (js/setInterval dispatch-timer-event 50))
subscriptions:
subs.cljs
(ns ant.subs
(:require
[re-frame.core :as re-frame]))
(re-frame/reg-sub
::grid
(fn [db]
(:grid db)))
Events:
events.cljs
(ns ant.events
(:require
[re-frame.core :as re-frame]
[ant.db :as db]
[ant.world :as aw]
))
(re-frame/reg-event-db
::initialize-db
(fn [_ _]
db/default-db))
(re-frame/reg-event-db
::timer
(fn [db _]
(aw/iter-world db)))
DB:
db.cljs
(ns ant.db
(:require [ant.world :as aw]))
(def default-db
(aw/init-world 100 100))
This works. But is quite slow. Though the timer is supposed to trigger an event every 50ms, the drawing rate is not so high.
What can I do to improve the performance here? Is it possible without resorting to mutable data structures?
(Any general code improvement suggestions are also most welcome)
Full code is here: https://github.com/nakiya/langton-ant