Conway's Game of Life is a "cellular automation" game. You pick squares on a grid that will start as "alive," then hit play. The game then begins running by itself. It's not necessarily a winners-and-losers kind-of game; rather it's an exercise in mathematical beauty.
In this article, I'll briefly describe how Conway's Game of Life works. I'll also describe some common patterns that do some interesting stuff, including the famous "glider." Finally, I'll provide some CLIPS code that I wrote implementing this famous game. Now: let's get started!
To understand any game, we must first understand the rules. The playing field of Conway's Game of Life is a grid of squares. During a round, a square can be either alive or dead. Whether a square is alive or dead depends on the number of alive squares surrounding that square.
The rules are as follows:
Look at the currently alive square A. In the next round, it will remain alive due to the surrounding 3 alive squares B, C, and D. Similarly, the currently dead squares G and F will become alive. Squares B and E will perish. Finally, C and D will remain alive as they are surrounded by 2 alive squares.
This process continues. In the next round, squares H and I will come alive. Squares A and D will die, while the rest will remain alive.
We repeat this process again and again for as long as we'd like. At some point (after 7 rounds, actually), this particular pattern will end with no squares alive.
Seems easy enough! Let's implement it in CLIPS.
CLIPS has the ability to call underlying system functions. In this case,
we'll be running this in bash in Ubuntu, so we have access to a few
programs we can use to draw our playing field. Put this in a file called
life.bat:
(system "tput civis") (load* life.clp) (load* lifedemo.clp) (reset) (run) (system "tput cnorm") (exit)
First, we use tput to hide the cursor. This will otherwise
just get in the way when we render our playing field. Next, we'll
load* up our rules. We'll separate the generic rules engine
and specific playing fields into two separate files. This will allow us
to very quickly swap out different patterns as we'd like. We'll then
reset to initialize our deffacts,
run to play the game, make our cursor visible with
tput cnorm, and then exit.
Alright, next comes the framework rules for our game. Put this into a
life.clp file:
(deftemplate alive (slot x) (slot y) (slot tick (default -1))) (deftemplate next (slot x) (slot y) (slot tick) (slot surrounding)) (deffacts init (tick -1) (height 24) (width 24) (max-ticks 25)) (defrule born (tick ?tick) (next (x ?x) (y ?y) (tick ?tick) (surrounding 3)) => (assert (alive (x ?x) (y ?y) (tick ?tick)))) (defrule survive (tick ?tick) (alive (x ?x) (y ?y) (tick ?previous&:(= ?previous (- ?tick 1)))) (next (x ?x) (y ?y) (tick ?tick) (surrounding 2)) => (assert (alive (x ?x) (y ?y) (tick ?tick)))) (defrule clean-up (tick ?tick) ?t <- (tick ?previous&:(= ?previous (- ?tick 1))) => (retract ?t) (do-for-all-facts ((?n next)) (= ?n:tick ?previous) (retract ?n)) (do-for-all-facts ((?a alive)) (= ?a:tick ?previous) (retract ?a))) (defrule render-and-tick (declare (salience -1)) (max-ticks ?max) (tick ?tick&:(<= ?tick ?max)) (height ?height) (width ?width) => (system "tput clear") (bind ?nexttick (+ ?tick 1)) (loop-for-count (?y ?height) (loop-for-count (?x ?width) (assert (next (tick ?nexttick) (x ?x) (y ?y) (surrounding (length$ (find-all-facts ((?a alive)) (and (= ?a:tick ?tick) (or (<> ?x ?a:x) (<> ?y ?a:y)) (or (= ?x ?a:x) (= ?x (- ?a:x 1)) (= ?x (+ ?a:x 1))) (or (= ?y ?a:y) (= ?y (- ?a:y 1)) (= ?y (+ ?a:y 1))))))))) (if (any-factp ((?a alive)) (and (= ?a:tick ?tick) (= ?a:x ?x) (= ?a:y ?y))) then (print " # ") else (print " "))) (print crlf)) (system "sleep 0.2") (assert (tick ?nexttick)))
First we define a few templates for our facts. alive facts
will represent squares that are alive and should be rendered as such
with a #. The next template is used to
represent which squares should be alive in the next round. We compute
this during the current round to capture the number of surrounding
alive squares.
We then define some initial facts. tick is what we'll use
to denote rounds. Think of this as the ticking of a clock. As time moves
forward, so does Life. height and width define
the size of our playing field. Finally, max-ticks tells us
how many rounds long this game will be.
The born and survive Rules are responsible for
determining which squares will be alive next round. We use
clean-up to retract previously rendered alive
and their corresponding next facts.
Finally, render-and-tick draws in our terminal. We first
clear the terminal with tput clear. We then use nested
loop-for-count calls to get each coordinate on the grid. We
first assert a next fact for the given coordinate that
calculates how many alive facts surround it. Then, we
render either a # (alive) or a blank space (dead) depending
on if there is an alive fact at that coordinate. We use
length$ and find-all-facts to count the
surrounding alive facts.
Now we'll create a lifedemo.clp file in which we'll define
our initially alive squares:
(deffacts demo (alive (x 6) (y 15)) (alive (x 7) (y 14)) (alive (x 7) (y 13)) (alive (x 8) (y 9)) (alive (x 8) (y 8)) (alive (x 8) (y 7)) (alive (x 10) (y 15)) (alive (x 9) (y 15)) (alive (x 8) (y 15)) (alive (x 10) (y 14)) (alive (x 9) (y 14)) (alive (x 8) (y 14)) (alive (x 1) (y 10)) (alive (x 1) (y 11)) (alive (x 2) (y 10)) (alive (x 1) (y 5)) (alive (x 9) (y 5)) (alive (x 9) (y 6)) (alive (x 12) (y 15)) (alive (x 11) (y 14)) (alive (x 12) (y 13)) (alive (x 11) (y 9)) (alive (x 12) (y 8)) (alive (x 12) (y 7)) (alive (x 18) (y 15)) (alive (x 19) (y 14)) (alive (x 20) (y 13)) (alive (x 21) (y 9)) (alive (x 20) (y 8)) (alive (x 18) (y 7)) (alive (x 18) (y 17)) (alive (x 19) (y 17)) (alive (x 18) (y 18)) (alive (x 19) (y 18)) (alive (x 15) (y 12)) (alive (x 16) (y 12)) (alive (x 14) (y 10)) (alive (x 15) (y 10)) (alive (x 18) (y 10)) (alive (x 18) (y 5)) (alive (x 19) (y 5)) (alive (x 20) (y 6)) (alive (x 21) (y 6)) (alive (x 21) (y 5)) (alive (x 21) (y 7)) (alive (x 22) (y 7)) (alive (x 17) (y 9)) (alive (x 24) (y 9)) (alive (x 23) (y 16)) (alive (x 23) (y 14)) (alive (x 23) (y 12)) (alive (x 24) (y 16)) (alive (x 24) (y 15)) (alive (x 24) (y 14)) (alive (x 24) (y 22)) (alive (x 24) (y 23)) (alive (x 23) (y 22)) (alive (x 22) (y 22)) (alive (x 17) (y 8)))
Now we can run the rules engine with ./clips -f2 life.bat.
We should see something that looks like the first
video in this article!
There are many different patterns and behaviors that emerge in Conway's Game of Life. Check out the LifeWiki to see tons more. We'll showcase a few of them in this article next.
(deffacts oscillators (alive (x 3) (y 10)) (alive (x 4) (y 10)) (alive (x 5) (y 10)) (alive (x 10) (y 10)) (alive (x 11) (y 10)) (alive (x 12) (y 10)) (alive (x 11) (y 11)) (alive (x 12) (y 11)) (alive (x 13) (y 11)) (alive (x 17) (y 13)) (alive (x 18) (y 13)) (alive (x 17) (y 12)) (alive (x 18) (y 12)) (alive (x 19) (y 11)) (alive (x 20) (y 11)) (alive (x 19) (y 10)) (alive (x 20) (y 10)))
Blinkers are patterns that repeat themselves for several frames.
(deffacts pulsar (alive (x 6) (y 15)) (alive (x 6) (y 14)) (alive (x 6) (y 13)) (alive (x 6) (y 9)) (alive (x 6) (y 8)) (alive (x 6) (y 7)) (alive (x 10) (y 17)) (alive (x 9) (y 17)) (alive (x 8) (y 17)) (alive (x 10) (y 12)) (alive (x 9) (y 12)) (alive (x 8) (y 12)) (alive (x 10) (y 10)) (alive (x 9) (y 10)) (alive (x 8) (y 10)) (alive (x 10) (y 5)) (alive (x 9) (y 5)) (alive (x 8) (y 5)) (alive (x 11) (y 15)) (alive (x 11) (y 14)) (alive (x 11) (y 13)) (alive (x 11) (y 9)) (alive (x 11) (y 8)) (alive (x 11) (y 7)) (alive (x 13) (y 15)) (alive (x 13) (y 14)) (alive (x 13) (y 13)) (alive (x 13) (y 9)) (alive (x 13) (y 8)) (alive (x 13) (y 7)) (alive (x 14) (y 17)) (alive (x 15) (y 17)) (alive (x 16) (y 17)) (alive (x 14) (y 12)) (alive (x 15) (y 12)) (alive (x 16) (y 12)) (alive (x 14) (y 10)) (alive (x 15) (y 10)) (alive (x 16) (y 10)) (alive (x 14) (y 5)) (alive (x 15) (y 5)) (alive (x 16) (y 5)) (alive (x 18) (y 15)) (alive (x 18) (y 14)) (alive (x 18) (y 13)) (alive (x 18) (y 9)) (alive (x 18) (y 7)) (alive (x 18) (y 8)))
Spaceships appear to move across the playing field.
(deffacts gliders (alive (x 3) (y 1)) (alive (x 3) (y 2)) (alive (x 3) (y 3)) (alive (x 2) (y 3)) (alive (x 1) (y 2)) (alive (x 9) (y 3)) (alive (x 9) (y 4)) (alive (x 9) (y 5)) (alive (x 8) (y 5)) (alive (x 7) (y 4)) (alive (x 5) (y 19)) (alive (x 5) (y 20)) (alive (x 5) (y 21)) (alive (x 4) (y 19)) (alive (x 3) (y 20)) (alive (x 18) (y 19)) (alive (x 18) (y 20)) (alive (x 18) (y 21)) (alive (x 19) (y 19)) (alive (x 20) (y 20)))
(deffacts lightweight-space-ship (alive (x 2) (y 1)) (alive (x 4) (y 1)) (alive (x 5) (y 2)) (alive (x 5) (y 3)) (alive (x 5) (y 4)) (alive (x 5) (y 5)) (alive (x 2) (y 4)) (alive (x 3) (y 5)) (alive (x 4) (y 5)) (alive (x 7) (y 7)) (alive (x 7) (y 9)) (alive (x 8) (y 10)) (alive (x 9) (y 10)) (alive (x 10) (y 10)) (alive (x 11) (y 10)) (alive (x 11) (y 9)) (alive (x 11) (y 8)) (alive (x 10) (y 7)))
Conway's Game of Life can be quite fun to toy around with. It's also a great way to learn a new programming language. CLIPS once again makes it super easy to implement something awesome.
- ryjo