diff --git a/examples/community/game_of_life/README.md b/examples/community/game_of_life/README.md new file mode 100644 index 0000000..92d1aad --- /dev/null +++ b/examples/community/game_of_life/README.md @@ -0,0 +1,20 @@ +# Conway's Game of Lfe + +Created by [Nathan Leniz](https://github.com/terakilobyte). +Inspired by and heavily uses [the doc](https://golang.org/doc/play/life.go) + +> The Game of Life, also known simply as Life, is a cellular automaton devised by the British mathematician John Horton Conway in 1970. The "game" is a zero-player game, meaning that its evolution is determined by its initial state, requiring no further input. One interacts with the Game of Life by creating an initial configuration and observing how it evolves, or, for advanced "players", by creating patterns with particular properties. The Game has been reprogrammed multiple times in various coding languages. + +For more information, please see the [wikipedia](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) article. + +## Use + + go run main.go -h + -frameRate duration + The framerate in milliseconds (default 33ms) + -size int + The size of each cell (default 5) + -windowSize float + The pixel size of one side of the grid (default 800) + +![life](life.png) diff --git a/examples/community/game_of_life/life.png b/examples/community/game_of_life/life.png new file mode 100644 index 0000000..5b8807a Binary files /dev/null and b/examples/community/game_of_life/life.png differ diff --git a/examples/community/game_of_life/life/grid.go b/examples/community/game_of_life/life/grid.go new file mode 100644 index 0000000..e7b0a14 --- /dev/null +++ b/examples/community/game_of_life/life/grid.go @@ -0,0 +1,74 @@ +package life + +import ( + "github.com/faiface/pixel" + "github.com/faiface/pixel/imdraw" + "golang.org/x/image/colornames" +) + +// Shamelessly taken/inspired by https://golang.org/doc/play/life.go +// Grid is the structure in which the cellular automota live +type Grid struct { + h int + cellSize int + Cells [][]bool +} + +// NewGrid constructs a new Grid +func NewGrid(h, size int) *Grid { + cells := make([][]bool, h) + for i := 0; i < h; i++ { + cells[i] = make([]bool, h) + } + return &Grid{h: h, cellSize: size, Cells: cells} +} + +// Alive returns whether the specified position is alive +func (g *Grid) Alive(x, y int) bool { + x += g.h + x %= g.h + y += g.h + y %= g.h + return g.Cells[y][x] +} + +// Set sets the state of a specific location +func (g *Grid) Set(x, y int, state bool) { + g.Cells[y][x] = state +} + +// Draw draws the grid +func (g *Grid) Draw(imd *imdraw.IMDraw) { + for i := 0; i < g.h; i++ { + for j := 0; j < g.h; j++ { + if g.Alive(i, j) { + imd.Color = colornames.Black + } else { + imd.Color = colornames.White + } + imd.Push( + pixel.V(float64(i*g.cellSize), float64(j*g.cellSize)), + pixel.V(float64(i*g.cellSize+g.cellSize), float64(j*g.cellSize+g.cellSize)), + ) + imd.Rectangle(0) + } + } +} + +// Next returns the next state +func (g *Grid) 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) && g.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 && g.Alive(x, y) +} diff --git a/examples/community/game_of_life/life/life.go b/examples/community/game_of_life/life/life.go new file mode 100644 index 0000000..26d2823 --- /dev/null +++ b/examples/community/game_of_life/life/life.go @@ -0,0 +1,35 @@ +// Package life manages the "game" state +// Shamelessly taken from https://golang.org/doc/play/life.go +package life + +import "math/rand" + +// Life stores the state of a round of Conway's Game of Life. +type Life struct { + A, b *Grid + h int +} + +// NewLife returns a new Life game state with a random initial state. +func NewLife(h, size int) *Life { + a := NewGrid(h, size) + for i := 0; i < (h * h / 2); i++ { + a.Set(rand.Intn(h), rand.Intn(h), true) + } + return &Life{ + A: a, b: NewGrid(h, size), + 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.h; x++ { + l.b.Set(x, y, l.A.Next(x, y)) + } + } + // Swap fields a and b. + l.A, l.b = l.b, l.A +} diff --git a/examples/community/game_of_life/main.go b/examples/community/game_of_life/main.go new file mode 100644 index 0000000..26f5ea0 --- /dev/null +++ b/examples/community/game_of_life/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "flag" + "math/rand" + "time" + + "golang.org/x/image/colornames" + + "github.com/faiface/pixel" + "github.com/faiface/pixel/examples/community/game_of_life/life" + "github.com/faiface/pixel/imdraw" + "github.com/faiface/pixel/pixelgl" +) + +var ( + size *int + windowSize *float64 + frameRate *time.Duration +) + +func init() { + rand.Seed(time.Now().UnixNano()) + size = flag.Int("size", 5, "The size of each cell") + windowSize = flag.Float64("windowSize", 800, "The pixel size of one side of the grid") + frameRate = flag.Duration("frameRate", 33*time.Millisecond, "The framerate in milliseconds") + flag.Parse() +} + +func main() { + pixelgl.Run(run) +} + +func run() { + + cfg := pixelgl.WindowConfig{ + Title: "Pixel Rocks!", + Bounds: pixel.R(0, 0, *windowSize, *windowSize), + VSync: true, + } + win, err := pixelgl.NewWindow(cfg) + if err != nil { + panic(err) + } + win.Clear(colornames.White) + + // since the game board is square, rows and cols will be the same + rows := int(*windowSize) / *size + + gridDraw := imdraw.New(nil) + game := life.NewLife(rows, *size) + tick := time.Tick(*frameRate) + for !win.Closed() { + // game loop + select { + case <-tick: + gridDraw.Clear() + game.A.Draw(gridDraw) + gridDraw.Draw(win) + game.Step() + } + win.Update() + } +}