summaryrefslogtreecommitdiff
path: root/doc/play/life.go
diff options
context:
space:
mode:
Diffstat (limited to 'doc/play/life.go')
-rw-r--r--doc/play/life.go113
1 files changed, 113 insertions, 0 deletions
diff --git a/doc/play/life.go b/doc/play/life.go
new file mode 100644
index 000000000..51afb61f3
--- /dev/null
+++ b/doc/play/life.go
@@ -0,0 +1,113 @@
+// An implementation of Conway's Game of Life.
+package main
+
+import (
+ "bytes"
+ "fmt"
+ "math/rand"
+ "time"
+)
+
+// Field represents a two-dimensional field of cells.
+type Field struct {
+ s [][]bool
+ w, h int
+}
+
+// NewField returns an empty field of the specified width and height.
+func NewField(w, h int) *Field {
+ s := make([][]bool, h)
+ for i := range s {
+ s[i] = make([]bool, w)
+ }
+ return &Field{s: s, w: w, h: h}
+}
+
+// Set sets the state of the specified cell to the given value.
+func (f *Field) Set(x, y int, b bool) {
+ f.s[y][x] = b
+}
+
+// Alive reports whether the specified cell is alive.
+// If the x or y coordinates are outside the field boundaries they are wrapped
+// toroidally. For instance, an x value of -1 is treated as width-1.
+func (f *Field) Alive(x, y int) bool {
+ x += f.w
+ x %= f.w
+ y += f.h
+ y %= f.h
+ return f.s[y][x]
+}
+
+// Next returns the state of the specified cell at the next time step.
+func (f *Field) Next(x, y int) bool {
+ // Count the adjacent cells that are alive.
+ alive := 0
+ for i := -1; i <= 1; i++ {
+ for j := -1; j <= 1; j++ {
+ if (j != 0 || i != 0) && f.Alive(x+i, y+j) {
+ alive++
+ }
+ }
+ }
+ // Return next state according to the game rules:
+ // exactly 3 neighbors: on,
+ // exactly 2 neighbors: maintain current state,
+ // otherwise: off.
+ return alive == 3 || alive == 2 && f.Alive(x, y)
+}
+
+// Life stores the state of a round of Conway's Game of Life.
+type Life struct {
+ a, b *Field
+ w, h int
+}
+
+// NewLife returns a new Life game state with a random initial state.
+func NewLife(w, h int) *Life {
+ a := NewField(w, h)
+ for i := 0; i < (w * h / 4); i++ {
+ a.Set(rand.Intn(w), rand.Intn(h), true)
+ }
+ return &Life{
+ a: a, b: NewField(w, h),
+ w: w, h: h,
+ }
+}
+
+// Step advances the game by one instant, recomputing and updating all cells.
+func (l *Life) Step() {
+ // Update the state of the next field (b) from the current field (a).
+ for y := 0; y < l.h; y++ {
+ for x := 0; x < l.w; x++ {
+ l.b.Set(x, y, l.a.Next(x, y))
+ }
+ }
+ // Swap fields a and b.
+ l.a, l.b = l.b, l.a
+}
+
+// String returns the game board as a string.
+func (l *Life) String() string {
+ var buf bytes.Buffer
+ for y := 0; y < l.h; y++ {
+ for x := 0; x < l.w; x++ {
+ b := byte(' ')
+ if l.a.Alive(x, y) {
+ b = '*'
+ }
+ buf.WriteByte(b)
+ }
+ buf.WriteByte('\n')
+ }
+ return buf.String()
+}
+
+func main() {
+ l := NewLife(40, 15)
+ for i := 0; i < 300; i++ {
+ l.Step()
+ fmt.Print("\x0c", l) // Clear screen and print field.
+ time.Sleep(time.Second / 30)
+ }
+}