I'm learning Clojure, and decided to write a Conway's Game of Life clone as my starting project. I ended up dicking around for a bit before diving in, and came up with a few functions that I'd like looked over. Mainly, I'm concerned about writing them more concisely and idiomatically.
I'm planning on using a 1D vector to represent a 2D field. The typical equation to get the index of a vector corresponding to an (x,y) coordinate is y * width + x. Here are my first attempts:
; A 2D point representing a coordinate, or any pair of numbers
(defrecord Point [x y])
; Represents the Game of Life world.
; cells is a vector representing a 2D matrix of cells
; dims is the dimensions of the world as a Point
(defrecord Enviro [cells dims])
(defn index-of [width x y]
(+ (* y width) x))
(defn enviro-index-of [enviro x y]
(let [width (:x (:dims enviro))]
(index-of width x y)))
(defn enviro-index-of-2 [enviro x y]
(let [width (-> enviro :dims :x)]
(index-of width x y)))
index-of
is straight forward. My issue is the 2 convenience functions to get an index by supplying the Enviro instead of the width directly. I tried deconstructing the record directly in the argument list, but it complained that it didn't recognize the keyword record keys. The work-around was to just "navigate" the records manually, and bind "width" in a let
. My first attempt was kind of naïve; using the accessor functions to get the Enviro
, then the dimensions Point
. Then I remembered the thread macro! For my second attempt, I used ->
to find the width. It seems to be much cleaner, but is this as idiomatic as it gets?
Next, I decided to try writing a function that returns a vector containing all the points surrounding a given point. I can then check the cell at each neighboring point to see whether or not the center point should be dead or alive:
(defn generate-neighbor-points
([cx cy r]
(let [start-x (- cx r)
end-x (+ cx r 1) ; Adding 1 so it's inclusive
start-y (- cy r)
end-y (+ cy r 1)]
(for [y (range start-y end-y)
x (range start-x end-x)
:when (and (not= x cx) (not= y cy))] ; Should actually be `or` instead of `and`
(Point. x y))))
([center-point r]
(let [cx (:x center-point)
cy (:y center-point)]
(generate-neighbor-points cx cy r))))
There's a couple things that I'm not happy about here:
The fact that this generates a list of cells, just so it can be checked. I might see if I can just make this a higher-order function that takes a callback that's executed on each Point surrounding the cell. That might be thinking too javascript-y though.
The fact that this requires the creation of 2 "range"s, which are really full lists. This seems to be the best way of "iterating" a range of numbers and returning a list. I'm open for suggestions on how to make this more efficient and idiomatic though.