Skip to content

Commit

Permalink
Merge pull request #2 from eleniums/hackathon
Browse files Browse the repository at this point in the history
Hackathon
  • Loading branch information
eleniums authored Apr 20, 2018
2 parents 0d3ae10 + 4071908 commit 9562d2b
Show file tree
Hide file tree
Showing 30 changed files with 1,088 additions and 6 deletions.
22 changes: 21 additions & 1 deletion Gopkg.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
EXECUTABLE=gameoflife.exe

all: game
all: $(EXECUTABLE)

deps:
dep ensure

game:
$(EXECUTABLE):
go build -o $(EXECUTABLE) main.go

clean:
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,14 @@ go run main.go
- Requires Go 1.8 or later
- Uses [dep](https://github.com/golang/dep) for dependencies
- Uses [pixel](https://github.com/faiface/pixel) for graphics and input

## Explanation

Conway's Game of Life is a zero player game, meaning the player sets an initial state and then sits back as the simulation is run. The simulation consists of a grid of cells that exist in one of two states, alive or dead. These cells are governed by 4 rules, based on the 8 neighboring cells surrounding any given cell:

- If there are less than 2 living cells surrounding a living cell, it will die, as if by underpopulation.
- If there are 2 or 3 living cells surrounding a living cell, it will continue to live.
- If there are more than 3 living cells surrounding a living cell, it will die, as if by overpopulation.
- If there are exactly 3 living cells surrounding a dead cell, it will become alive, as if by reproduction.

This leads to many interesting patterns and is fascinating to mess around with. Interestingly enough, it is also Turing complete, meaning it can simulate a computer. The Game of Life itself has been built with the Game of Life!
47 changes: 47 additions & 0 deletions assets/assets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package assets

import (
"github.com/eleniums/game-of-life-go/assets/fonts"
"github.com/eleniums/game-of-life-go/assets/images"
"github.com/faiface/pixel"
"github.com/faiface/pixel/text"
)

var (
Icon16x16 pixel.Picture
Title pixel.Picture
CellMap pixel.Picture
GrassMap pixel.Picture
PixelAtlas *text.Atlas
)

func Load() error {
var err error

Icon16x16, err = images.Load(images.Icon16x16Data)
if err != nil {
return err
}

Title, err = images.Load(images.TitleData)
if err != nil {
return err
}

CellMap, err = images.Load(images.CellMapData)
if err != nil {
return err
}

GrassMap, err = images.Load(images.GrassMapData)
if err != nil {
return err
}

PixelAtlas, err = fonts.Load(fonts.PixelData, 40)
if err != nil {
return err
}

return nil
}
24 changes: 24 additions & 0 deletions assets/fonts/fonts.go

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions assets/images/images.go

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions game/cell.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package game

type CellType int

const (
CellType_Cross CellType = iota
CellType_Plus
CellType_Circle
CellType_Dot
)

type Cell struct {
Alive bool
Type CellType
}

func (c *Cell) Clear() {
c.Alive = false
c.Type = CellType_Cross
}

func (c *Cell) Copy() *Cell {
return &Cell{
Alive: c.Alive,
Type: c.Type,
}
}
72 changes: 72 additions & 0 deletions game/grid.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package game

const (
GridMaxX = 288
GridMaxY = 288
)

type CellGrid [][]*Cell

func NewCellGrid() CellGrid {
grid := make(CellGrid, GridMaxX)
for x := 0; x < GridMaxX; x++ {
grid[x] = make([]*Cell, GridMaxY)
for y := 0; y < GridMaxY; y++ {
grid[x][y] = &Cell{
Alive: false,
Type: 0,
}
}
}

return grid
}

func (c CellGrid) Clear() {
for x := range c {
for y := range c[x] {
c[x][y].Clear()
}
}
}

func (c CellGrid) Copy() CellGrid {
grid := make(CellGrid, GridMaxX)
for x := 0; x < GridMaxX; x++ {
grid[x] = make([]*Cell, GridMaxY)
for y := 0; y < GridMaxY; y++ {
grid[x][y] = c[x][y].Copy()
}
}

return grid
}

func (c CellGrid) IsAlive(x, y int) bool {
if x < 0 || x >= GridMaxX || y < 0 || y >= GridMaxY {
return false
}

return c[x][y].Alive
}

func (c CellGrid) CountNeighbors(x, y int) (count, cross, plus, circle, dot int) {
for i := x - 1; i <= x+1; i++ {
for j := y - 1; j <= y+1; j++ {
if c.IsAlive(i, j) && !(i == x && j == y) {
switch c[i][j].Type {
case CellType_Cross:
cross++
case CellType_Plus:
plus++
case CellType_Circle:
circle++
case CellType_Dot:
dot++
}
}
}
}

return cross + plus + circle + dot, cross, plus, circle, dot
}
149 changes: 149 additions & 0 deletions game/manager.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package game

import (
"encoding/json"
"io/ioutil"
"time"
)

var (
Interval = 100
)

type Manager struct {
cells CellGrid
buffer CellGrid
memory CellGrid
running bool
ticker *time.Ticker
}

func NewManager() *Manager {
return &Manager{
cells: NewCellGrid(),
buffer: NewCellGrid(),
memory: NewCellGrid(),
running: false,
}
}

func (m *Manager) Update() {
if m.running {
select {
case <-m.ticker.C:
// iterate over grid and apply rules
for x := range m.cells {
for y := range m.cells[x] {
neighbors, cross, plus, circle, dot := m.cells.CountNeighbors(x, y)
if m.cells[x][y].Alive {
m.buffer[x][y].Type = m.cells[x][y].Type
} else {
m.buffer[x][y].Type = determineType(cross, plus, circle, dot)
}
m.buffer[x][y].Alive = applyRules(m.cells[x][y].Alive, neighbors)
}
}

// swap active cells with buffer
temp := m.cells
m.cells = m.buffer
m.buffer = temp
default:
}
}
}

func (m *Manager) Start() {
m.running = true
m.ticker = time.NewTicker(time.Duration(Interval) * time.Millisecond)
}

func (m *Manager) Stop() {
m.ticker.Stop()
m.running = false
}

func (m *Manager) Clear() {
m.cells.Clear()
m.buffer.Clear()
}

func (m *Manager) Store() {
m.memory = m.cells.Copy()
}

func (m *Manager) Reset() {
m.cells = m.memory.Copy()
}

func (m *Manager) Save(path string) error {
return save(m.cells, path)
}

func (m *Manager) Load(path string) error {
cells, err := load(path)
if err != nil {
return err
}

m.cells = cells
m.buffer.Clear()
m.memory = m.cells.Copy()

return nil
}

func (m *Manager) Cells() CellGrid {
return m.cells
}

func (m *Manager) Running() bool {
return m.running
}

type storage struct {
Cell *Cell
X int
Y int
}

func save(cells CellGrid, path string) error {
compact := []*storage{}
for x := range cells {
for y := range cells[x] {
if cells[x][y].Alive {
compact = append(compact, &storage{
Cell: cells[x][y],
X: x,
Y: y,
})
}
}
}

data, err := json.Marshal(compact)
if err != nil {
return err
}

err = ioutil.WriteFile(path, data, 0644)

return err
}

func load(path string) (CellGrid, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}

var compact []*storage
err = json.Unmarshal(data, &compact)

cells := NewCellGrid()
for _, v := range compact {
cells[v.X][v.Y] = v.Cell
}

return cells, err
}
Loading

0 comments on commit 9562d2b

Please # to comment.