Game of Life

28 views
Skip to first unread message

lps540

unread,
Mar 3, 2009, 3:03:08 PM3/3/09
to Clojure
I've written a small example of Conway's Game of Life using Clojure
and Swing. Comments/critiques are welcome.

http://lpsherrill.blogspot.com/2009/02/conways-game-of-life.html

Thanks,
Larry

Phil Hagelberg

unread,
Mar 3, 2009, 3:30:02 PM3/3/09
to clo...@googlegroups.com
lps540 <lps...@gmail.com> writes:

> I've written a small example of Conway's Game of Life using Clojure
> and Swing. Comments/critiques are welcome.

Interesting program!

(for [x (range 32) y (range 48)]
(ref-set cells
(assoc (deref cells) [x y] (= 0 (rand-int 5)))))

While you can use ref-set this way, it's much more concise to use alter:

(for [x (range 32) y (range 48)]
(alter cells assoc [x y] (= 0 (rand-int 5))))

You should use this in calc-state as well.

Also, the paint-cells function is called only for side-effects. Instead
of using dorun+map, you should use doseq:

(defn paint-cells [graphics]
(doseq [cell @cells]
(let [x (first (first cell))
y (second (first cell))
state (second cell)]
(doto graphics
(. setColor (if state Color/RED Color/WHITE))
(. fillRect (* 10 x) (* 10 y) 10 10)))))

I'm not sure if making "running" a ref makes sense here. Do you need to
coordinate change in it across threads, or will the change only happen
in one thread? If it's the former, using a thread-local var with
"binding" is probably a better choice.

Also, instead of calling (deref cells), you can use @cells for short.

-Phil

Larry Sherrill

unread,
Mar 3, 2009, 3:45:09 PM3/3/09
to Clojure
Hi Phil,

Thanks for the tips. Yes, alter and doseq do look better.

As far as the "running" variable, there is one thread painting the
squares and another thread responding to the mouse click on start/
stop. This was the only way I could think of that the user could stop
the paint thread, using a Java idiom. I'll read up on binding.

Thank again!
Larry

Mark Volkmann

unread,
Mar 3, 2009, 3:55:55 PM3/3/09
to clo...@googlegroups.com

The paint-cells function can be shorted by using doseq and destructuring.

(defn paint-cells [graphics]
(doseq [[[x, y] state] @cells]
(doto graphics
(.setColor (if state Color/RED Color/WHITE))
(.fillRect (* 10 x) (* 10 y) 10 10))))

--
R. Mark Volkmann
Object Computing, Inc.

Mark Volkmann

unread,
Mar 3, 2009, 4:01:41 PM3/3/09
to clo...@googlegroups.com
On Tue, Mar 3, 2009 at 2:03 PM, lps540 <lps...@gmail.com> wrote:
>
> I've written a small example of Conway's Game of Life using Clojure
> and Swing. Comments/critiques are welcome.

Instead of all the uses of (- x 1), (- y 1), (+ x 1) and (+ y 1), I
think it's more idiomatic to use (dec x), (dec y), (inc x) and (inc
y).

Stephen C. Gilardi

unread,
Mar 3, 2009, 4:03:16 PM3/3/09
to clo...@googlegroups.com

On Mar 3, 2009, at 3:03 PM, lps540 wrote:

> I've written a small example of Conway's Game of Life using Clojure
> and Swing. Comments/critiques are welcome.

Very nice example!

Here's a more compact determine-new-state:

(defn determine-new-state [x y]
(let [count (reduce + (for [dx [-1 0 1] dy [-1 0 1]]
(if (cells [(+ x dx) (+ y dy)]) 1 0)))]
(or (and (cells [x y]) (> count 2) (< count 5))
(= count 3))))

I'm guessing someone can make the reduce even tighter.

I considered using :when (not (and (zero? dx) (zero? dy))) in the for
expression, but it seemed simpler to account for the extra count when
(cells [x y]) is true in the new state expression down below.

--Steve

Larry Sherrill

unread,
Mar 3, 2009, 6:44:17 PM3/3/09
to Clojure
Thanks everyone for the suggestions. Very helpful.
>  smime.p7s
> 3KViewDownload

Laurent PETIT

unread,
Mar 3, 2009, 6:53:13 PM3/3/09
to clo...@googlegroups.com
Hello Larry,

is there a reason why the code of this port of Game of Life does not seem to be under an open source license (e.g. EPL, etc.) ?

I was interested in playing with your code, but I'm a bit reluctant when I see an "all rights reserved" in the top of the file ...
It seems to me like your code is open to suggestions from others, but not for sharing with others ?

Am I "over interpreting" the meaning of the copyright clause at the top of the published code ?

Regards,

--
Laurent

2009/3/4 Larry Sherrill <lps...@gmail.com>

Thanks everyone for the suggestions. Very helpful.
- Afficher le texte des messages précédents -
>  smime.p7s
> 3KViewDownload
- Afficher le texte des messages précédents -


Stephen C. Gilardi

unread,
Mar 3, 2009, 7:37:35 PM3/3/09
to clo...@googlegroups.com

On Mar 3, 2009, at 4:03 PM, Stephen C. Gilardi wrote:

> (defn determine-new-state [x y]
> (let [count (reduce + (for [dx [-1 0 1] dy [-1 0 1]]
> (if (cells [(+ x dx) (+ y dy)]) 1 0)))]
> (or (and (cells [x y]) (> count 2) (< count 5))
> (= count 3))))

>
> I'm guessing someone can make the reduce even tighter.

Here's another shot at it:

(defn determine-new-state [x y]
(let [nearby [-1 0 1]
neighborhood (for [dx nearby dy nearby]
(cells [(+ x dx) (+ y dy)]))
alive (count (filter identity neighborhood))]
(if (cells [x y])
(< 2 alive 5)
(= alive 3))))

--Steve

Timothy Pratley

unread,
Mar 3, 2009, 11:01:34 PM3/3/09
to Clojure
Just because there is always another way :P

(defn determine-new-state [x y]
(let [alive (count (for [dx [-1 0 1] dy [-1 0 1]
:when (cells [(+ x dx) (+ y dy)])]
:alive))]

Larry Sherrill

unread,
Mar 3, 2009, 11:19:54 PM3/3/09
to Clojure
> Am I "over interpreting" the meaning of the copyright clause at the top of the published code ?

Yes. I hesitated to put it there but went ahead out of habit. :> I'll
either remove it or stick some minimal open source license on it.
Thanks for pointing that out.
> > - Afficher le texte des messages précédents -- Hide quoted text -
>
> - Show quoted text -

Stephen C. Gilardi

unread,
Mar 3, 2009, 11:44:35 PM3/3/09
to clo...@googlegroups.com

Nice!

And for more golfing fun:

(defn calc-state []
(dosync
(ref-set cells
(reduce conj {}


(for [x (range 32) y (range 48)]

[[x y] (determine-new-state x y)])))))

--Steve

Larry Sherrill

unread,
Mar 4, 2009, 3:17:21 PM3/4/09
to Clojure
I've incorporated everyone's suggestions and thought I would post the
resulting smaller code. I refactored init-cells away and just pass in
an init or new function to calc-state to reuse the for loop. I made
determine-next-state a little more verbose than technically necessary
to make conway's rules more obvious to me. The refs "cells" and
"running" don't feel very functional but I don't know how to get rid
of them.

(import '(javax.swing JFrame JPanel JButton)
'(java.awt BorderLayout Dimension Color)
'(java.awt.event ActionListener))

(def cells (ref {}))

(def running (ref false))

(defn determine-initial-state [x y]
(= 0 (rand-int 5)))

(defn determine-new-state [x y]
(let [neighbor-count
(count (for [dx [-1 0 1] dy [-1 0 1]
:when (and (not (= 0 dx dy))
(cells [(+ x dx) (+ y dy)]))]
:alive))]
(if (cells [x y])
(< 1 neighbor-count 4)
(= neighbor-count 3))))

(defn calc-state [cell-state]
(dosync
(ref-set cells
(reduce conj {}
(for [x (range 32) y (range 48)]
[[x y] (cell-state x y)])))))

(defn paint-cells [graphics]
(doseq [[[x, y] state] @cells]
(doto graphics
(. setColor (if state Color/RED Color/WHITE))
(. fillRect (* 10 x) (* 10 y) 10 10))))

(defn toggle-thread [panel button]
(if @running

(do (dosync (ref-set running false))
(. button (setText "Start")))

(do (dosync (ref-set running true))
(. button (setText "Stop"))
(. (Thread.
#(loop []
(calc-state determine-new-state)
(. panel repaint)
(Thread/sleep 100)
(if @running (recur))))
start))))

(defn main[]

(calc-state determine-initial-state)

(let [f (JFrame.)
b (JButton. "Start")
panel (proxy [JPanel] [] (paint [graphics] (paint-cells
graphics)))]

(doto f
(. setLayout (BorderLayout.))
(. setLocation 100 100)
(. setPreferredSize (Dimension. 320 540))
(. add b BorderLayout/SOUTH)
(. add panel BorderLayout/CENTER)
(. setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
(. pack)
(. setVisible true))

(. b addActionListener
(proxy [ActionListener] []
(actionPerformed [evt] (toggle-thread panel b))))))

(main)


Thanks for everyone. Very good learning experience.
Larry
http://lpsherrill.blogspot.com

Raffael Cavallaro

unread,
Mar 4, 2009, 6:57:24 PM3/4/09
to Clojure
In my experience, life is a bit more interesting if the field wraps
and it is square.

Here are some mods to your most recent version that does this. I've
also parameterized the field size, the delay, and the initial
probability of a cell being occupied.

Thanks for sharing this - it's fun.

((import '(javax.swing JFrame JPanel JButton)
'(java.awt BorderLayout Dimension Color)
'(java.awt.event ActionListener))

(def cells (ref {}))

(def running (ref false))

(def x-cells 32)

(def y-cells 32)

(def life-delay 10)

(def life-initial-prob 3)

(defn determine-initial-state [x y]
(= 0 (rand-int life-initial-prob)))

(defn determine-new-state [x y]
(let [neighbor-count
(count (for [dx [-1 0 1] dy [-1 0 1]
:when (and (not (= 0 dx dy))
(cells [(mod (+ x dx) x-cells)
(mod (+ y dy) y-cells)]))]
:alive))]
(if (cells [x y])
(< 1 neighbor-count 4)
(= neighbor-count 3))))

(defn calc-state [cell-state]
(dosync
(ref-set cells
(reduce conj {}
(for [x (range x-cells) y (range y-cells)]
[[x y] (cell-state x y)])))))

(defn paint-cells [graphics]
(doseq [[[x, y] state] @cells]
(doto graphics
(. setColor (if state Color/RED Color/WHITE))
(. fillRect (* 10 x) (* 10 y) 10 10))))

(defn toggle-thread [panel button]
(if @running
(do (dosync (ref-set running false))
(. button (setText "Start")))
(do (dosync (ref-set running true))
(. button (setText "Stop"))
(. (Thread.
#(loop []
(calc-state determine-new-state)
(. panel repaint)
(Thread/sleep life-delay)
(if @running (recur))))
start))))

(defn main[]
(calc-state determine-initial-state)
(let [f (JFrame.)
b (JButton. "Start")
panel (proxy [JPanel] [] (paint [graphics] (paint-cells
graphics)))]
(doto f
(. setLayout (BorderLayout.))
(. setLocation 100 100)
(. setPreferredSize (Dimension. (* 10 x-cells) (+ 60 (* 10 y-
cells))))
(. add b BorderLayout/SOUTH)
(. add panel BorderLayout/CENTER)
(. setDefaultCloseOperation JFrame/EXIT_ON_CLOSE)
(. pack)
(. setVisible true))
(. b addActionListener
(proxy [ActionListener] []
(actionPerformed [evt] (toggle-thread panel b))))))

(comment
(main))

Scott Fraser

unread,
Mar 14, 2009, 10:03:16 PM3/14/09
to Clojure
Hi Larry

I have a performance tweak, that gives about an order of magnitude
speedup to paint-cells when running this with a large grid and no or
little (Thread/sleep life-delay) in toggle-thread. That is how I am
running it now - 128 x 192 cells with no delay! It is also noticeably
faster on the more typical size grid.

Type hint is on the graphics param:

(defn paint-cells [#^java.awt.Graphics graphics]
(time (doseq [[[x,y] state] @cells]
(doto graphics
(. setColor (if state Color/RED Color/WHITE))
(. fillRect (* cell-size x) (* cell-size y) cell-size cell-
size)))))

Example, with type hint:

"Elapsed time: 45.871193 msecs"
"Elapsed time: 39.209662 msecs"
"Elapsed time: 43.899504 msecs"

Without:

"Elapsed time: 529.635331 msecs"
"Elapsed time: 438.145769 msecs"
"Elapsed time: 442.839872 msecs"

I would imagine there may be some other way to get clojure to cache
the reflected handle to Graphics, but I am still a little new to this
so don't have any other ideas on how to eliminate the high amount of
reflection without a type hint.

-Scott
http://fraser.blogs.com/

On Mar 4, 4:17 pm, Larry Sherrill <lps...@gmail.com> wrote:
> I've incorporated everyone's suggestions and thought I would post the
> resulting smaller code. I refactored init-cells away and just pass in
> an init or new function to calc-state to reuse the for loop. I made
> determine-next-state a little more verbose than technically necessary
> to makeconway'srules more obvious to me. The refs "cells" and

Larry Sherrill

unread,
Mar 16, 2009, 10:51:19 AM3/16/09
to Clojure
Very cool. Thanks for the tip. I didn't know a type hint would make
that much difference.

Another possible speedup would be to use send/actor to take advantage
of multiprocessor machines. Would be curious if the overhead would pay
for itself in this situation.

Thanks, Larry
> -Scotthttp://fraser.blogs.com/
Reply all
Reply to author
Forward
0 new messages