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 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.