Skip to main content
Code Review

Return to Question

replaced http://stackoverflow.com/ with https://stackoverflow.com/
Source Link

Usually when learning a new language the first thing I do is implement Conway's Game of Life, since it's just complex enough to give a good sense of the language, but small enough in scope to be able to whip up in a couple of hours (most of which is spent wrestling with syntax). I did this with Scala a while back this with Scala a while back, and really appreciated the comments I received.

Usually when learning a new language the first thing I do is implement Conway's Game of Life, since it's just complex enough to give a good sense of the language, but small enough in scope to be able to whip up in a couple of hours (most of which is spent wrestling with syntax). I did this with Scala a while back, and really appreciated the comments I received.

Usually when learning a new language the first thing I do is implement Conway's Game of Life, since it's just complex enough to give a good sense of the language, but small enough in scope to be able to whip up in a couple of hours (most of which is spent wrestling with syntax). I did this with Scala a while back, and really appreciated the comments I received.

Added game-of-life tag
Link
200_success
  • 145.5k
  • 22
  • 190
  • 478
Tweeted twitter.com/#!/StackCodeReview/status/258447727531290624
Post Migrated Here from stackoverflow.com (revisions)
Source Link
Peter
  • 151
  • 2

Critique my Clojure "Game of Life" code

I'm a Clojure n00b (but am experienced with other languages) and am learning the language as I find time - enjoying it so far, though the strange dreams and accidental use of parenthesis elsewhere (e.g. Google searches) are a bit disconcerting!

Usually when learning a new language the first thing I do is implement Conway's Game of Life, since it's just complex enough to give a good sense of the language, but small enough in scope to be able to whip up in a couple of hours (most of which is spent wrestling with syntax). I did this with Scala a while back, and really appreciated the comments I received.

Anyhoo, having gone through this exercise with Clojure I was hoping to get similar feedback on my efforts so far?

I'm after any kind of feedback - algorithmic improvements, stylistic improvements, alternative APIs or language constructs, disgust at my insistence on checking argument types - whatever feedback you've got, I'm keen to hear it!

;
; Copyright (c) 2012 Peter Monks ([email protected])
;
; This work is licensed under the Creative Commons Attribution-ShareAlike
; 3.0 Unported License. To view a copy of this license, visit
; http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to
; Creative Commons, 444 Castro Street, Suite 900, Mountain View, California,
; 94041, USA.
;
(ns ClojureGameOfLife.core
 (:use clojure.set))
(defrecord Cell [^Integer x ^Integer y])
(def block #{(Cell. 0 0) (Cell. 1 0)
 (Cell. 0 1) (Cell. 1 1)})
(def beehive #{ (Cell. 1 0) (Cell. 2 0)
 (Cell. 0 1) (Cell. 3 1)
 (Cell. 1 2) (Cell. 2 2)})
(def blinker #{(Cell. 0 0)
 (Cell. 0 1)
 (Cell. 0 2)})
(def glider #{ (Cell. 1 0)
 (Cell. 2 1)
 (Cell. 0 2) (Cell. 1 2) (Cell. 2 2)})
(defn flat-map
 "Maps the function to the given set, returning a new set (flattened, in the case where f itself
 returns a set for each element).
 Note: There has to be a better way to do this..."
 [f s]
 {:pre [(set? s)]
 :post [(set? %)]}
 (->> s (map f) (map seq) flatten set))
(defn alive?
 "Is the given cell alive in the given generation?"
 [generation
 cell]
 {:pre [(set? generation)
 (instance? Cell cell)]
 :post [(instance? Boolean %)]}
 (contains? generation cell))
(defn neighbours
 "Returns all of the neighbours of a given cell."
 [cell]
 {:pre [(instance? Cell cell)]
 :post [(set? %)]}
 (let [x (:x cell)
 y (:y cell)]
 #{
 (Cell. (dec x) (dec y)) (Cell. x (dec y)) (Cell. (inc x) (dec y))
 (Cell. (dec x) y) (Cell. (inc x) y)
 (Cell. (dec x) (inc y)) (Cell. x (inc y)) (Cell. (inc x) (inc y))
 }))
(defn number-of-living-neighbours
 "Returns the number of living neighbours of the given cell in the given generation."
 [generation
 cell]
 {:pre [(set? generation)
 (instance? Cell cell)]
 :post [(instance? Integer %)]}
 (count (intersection generation (neighbours cell))))
(defn should-live?
 "Should the given cell live in the next generation?"
 [generation
 cell]
 {:pre [(set? generation)
 (instance? Cell cell)]
 :post [(instance? Boolean %)]}
 (let [alive (alive? generation cell)
 living-neighbours (number-of-living-neighbours generation cell)]
 (if alive
 (or (= 2 living-neighbours) (= 3 living-neighbours))
 (= 3 living-neighbours))))
(defn next-generation
 "Returns the next-generation board, given a generation."
 [generation]
 {:pre [(set? generation)]
 :post [(set? %)]}
 (set (filter #(should-live? generation %) (flat-map neighbours generation))))
(defn values
 "The values of the given generation's given dimension."
 [generation
 elem]
 {:pre [(set? generation)]
 :post [(set? %)]}
 (set (map elem generation)))
(defn x-values
 "The X values of the given generation."
 [generation]
 {:pre [(set? generation)]
 :post [(set? %)]}
 (values generation :x))
(defn y-values
 "The Y values of the given generation."
 [generation]
 {:pre [(set? generation)]
 :post [(set? %)]}
 (values generation :y))
(defn min-x
 "Minimum X value of a generation."
 [generation]
 {:pre [(set? generation)]}
 (reduce min (x-values generation)))
(defn min-y
 "Minimum Y value of a generation."
 [generation]
 {:pre [(set? generation)]}
 (reduce min (y-values generation)))
(defn max-x
 "Maximum X value of a generation."
 [generation]
 {:pre [(set? generation)]}
 (reduce max (x-values generation)))
(defn max-y
 "Maximum Y value of a generation."
 [generation]
 {:pre [(set? generation)]}
 (reduce max (y-values generation)))
(defn extend-by-1
 "Extends a collection of numbers by 1 in each direction (both up and down)."
 [c]
 (let [minimum (reduce min c)
 maximum (reduce max c)]
 (conj c (dec minimum) (inc maximum))))
(defn print-generation
 "Prints a generation."
 [generation]
 {:pre [(set? generation)]}
 (doseq [y (sort (extend-by-1 (y-values generation)))]
 (doseq [x (sort (extend-by-1 (x-values generation)))]
 (if (alive? generation (Cell. x y))
 (print "# ")
 (print ". ")))
 (println)))
(defn clear-screen
 "Clears the screen, using ANSI control characters."
 []
 (let [esc (char 27)]
 (print (str esc "[2J")) ; ANSI: clear screen
 (print (str esc "[;H")))) ; ANSI: move cursor to top left corner of screen
(defn -main
 "Run Conway's Game of Life."
 [& args]
 (loop [saved-generations #{}
 generation glider]
 (if (not (contains? saved-generations generation))
 (do
 (clear-screen)
 (print-generation generation)
 (flush)
 (Thread/sleep 250)
 (recur (conj saved-generations generation) (next-generation generation))))))
lang-clj

AltStyle によって変換されたページ (->オリジナル) /