Skip to content

Commit

Permalink
lib/game: refactor MooreNeighbors function for better readability. (#21)
Browse files Browse the repository at this point in the history
* lib/game: refactor MooreNeighbors function for better readability.
Also provide separate implementation for wrap/non-wrapping neighbors.

Signed-off-by: deadprogram <[email protected]>

* lib/game: add more rules and rename file to rules

Signed-off-by: deadprogram <[email protected]>

* cmd: add flag to choose rules

Signed-off-by: deadprogram <[email protected]>

---------

Signed-off-by: deadprogram <[email protected]>
  • Loading branch information
deadprogram authored Nov 13, 2023
1 parent 2ef52e0 commit 5876061
Show file tree
Hide file tree
Showing 5 changed files with 353 additions and 131 deletions.
21 changes: 21 additions & 0 deletions cmd/vita/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var (
generations = flag.Int("gens", 3, "how many generations to run the universe")
population = flag.Int("pop", 45, "initial population percent of the universe")
number = flag.Int("n", 1, "number of universes to run in parallel")
rules = flag.String("rules", "conway", "rules to use for the universe")
)

func main() {
Expand All @@ -32,6 +33,17 @@ func main() {

func runSingleUniverse() {
universe := game.NewUniverse(uint32(*height), uint32(*width))
switch *rules {
case "dayandnight":
universe.Rules = game.DayAndNightRules
case "seeds":
universe.Rules = game.SeedsRules
case "wrap":
universe.Rules = game.ConwayRulesWrap
default:
// just use conway
}

universe.Randomize(*population)

for i := 0; i < *generations; i++ {
Expand Down Expand Up @@ -63,6 +75,15 @@ func createParallelUniverses() []*game.ParallelUniverse {
for row := 0; row < *number; row++ {
for col := 0; col < *number; col++ {
u := game.NewParallelUniverse(uint32(*height), uint32(*width))
switch *rules {
case "dayandnight":
u.Rules = game.DayAndNightRules
case "seeds":
u.Rules = game.SeedsRules
default:
// just use conway
}

u.Randomize(*population)
multi = append(multi, u)
}
Expand Down
55 changes: 0 additions & 55 deletions lib/game/conway.go

This file was deleted.

76 changes: 0 additions & 76 deletions lib/game/conway_test.go

This file was deleted.

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

// ConwayRules implements the classic Game of Life rules.
func (u *Universe) ConwayRules(cell uint8, row, column uint32) uint8 {
return RuleB3S23(cell, u.MooreNeighbors(row, column))
}

// ConwayRulesWrap implements the classic Game of Life rules but wraps the grid.
func (u *Universe) ConwayRulesWrap(cell uint8, row, column uint32) uint8 {
return RuleB3S23(cell, u.MooreNeighborsWrap(row, column))
}

// RuleB3S23 implements the B3/S23 rules:
// https://www.conwaylife.com/wiki/Conway%27s_Game_of_Life#Rules
func RuleB3S23(cell uint8, liveNeighbors uint8) uint8 {
switch {
case cell == Dead && liveNeighbors == 3:
// Birth - any dead cell with exactly three live neighbours
// becomes a live cell, as if by reproduction.
return Alive
case cell == Alive && (liveNeighbors == 2 || liveNeighbors == 3):
// Survival - any live cell with two or three live neighbours
// lives on to the next generation.
return Alive
case cell == Alive && (liveNeighbors < 2 || liveNeighbors > 3):
// Death - any live cell with less than two or more than three
// live neighbours dies, as if by underpopulation/overpopulation.
return Dead
default:
return cell
}
}

// SeedsRules implements the Seeds rules.
// See https://conwaylife.com/wiki/OCA:Seeds
func (u *Universe) SeedsRules(cell uint8, row, column uint32) uint8 {
return RuleB2S(cell, u.MooreNeighbors(row, column))
}

// RuleB2S implements the B2S rules:
// https://conwaylife.com/wiki/OCA:Seeds
func RuleB2S(cell uint8, liveNeighbors uint8) uint8 {
switch {
case cell == Dead && (liveNeighbors == 2):
// Birth - any dead cell with two live neighbours
// becomes a live cell as if by reproduction.
return Alive
case cell == Alive:
// Death - any live cell dies.
return Dead
default:
return cell
}
}

// DayAndNightRules implements the Day And Night rules.
// See https://conwaylife.com/wiki/OCA:Day_%26_Night
func (u *Universe) DayAndNightRules(cell uint8, row, column uint32) uint8 {
return RuleB3678S34678(cell, u.MooreNeighbors(row, column))
}

// RuleB3678S34678 implements the B3678/S34678 rules:
// https://conwaylife.com/wiki/OCA:Day_%26_Night
func RuleB3678S34678(cell uint8, liveNeighbors uint8) uint8 {
switch {
case cell == Dead && (liveNeighbors == 3 || liveNeighbors == 6 ||
liveNeighbors == 7 || liveNeighbors == 8):
// Birth - any dead cell with 3, 6, 7, or 8 live neighbours
// becomes a live cell as if by reproduction.
return Alive
case cell == Alive && (liveNeighbors == 3 || liveNeighbors == 4 ||
liveNeighbors == 6 || liveNeighbors == 7 || liveNeighbors == 8):
// Survival - any live cell with 3, 4, 6, 7, or 8 live neighbours
// lives on to the next generation.
return Alive
case cell == Alive && (liveNeighbors < 3 || liveNeighbors == 5 || liveNeighbors > 8):
// Death - any live cell with less than three, five, or more than eight
// live neighbours dies, as if by underpopulation/overpopulation.
return Dead
default:
return cell
}
}

// MooreNeighbors returns the number of alive neighbors for a given cell.
// It uses the Moore neighborhood, which includes the eight cells surrounding
// the given cell.
func (u *Universe) MooreNeighbors(row, column uint32) uint8 {
count := uint8(0)

r, c := int32(row), int32(column)
for _, neighborRow := range []int32{r - 1, r, r + 1} {
for _, neighborColumn := range []int32{c - 1, c, c + 1} {
switch {
// Skip checking the cell itself
case neighborRow == r && neighborColumn == c:
// Ignore if pixel is out of bounds
case neighborRow < 0:
case neighborColumn < 0:
case neighborRow > int32(u.height)-1:
case neighborColumn > int32(u.width)-1:
// otherwise, check neighbor
default:
neighborIdx := u.GetIndex(uint32(neighborRow), uint32(neighborColumn))
if u.Cell(neighborIdx) != Dead {
count++
}
}
}
}

return count
}

// MooreNeighborsWrap returns the number of alive neighbors for a given cell.
// It uses the Moore neighborhood, which includes the eight cells surrounding
// the given cell, but wraps to the other side if it would be off the grid.
func (u *Universe) MooreNeighborsWrap(row, column uint32) uint8 {
count := uint8(0)

r, c := int32(row), int32(column)
for _, neighborRow := range []int32{r - 1, r, r + 1} {
for _, neighborColumn := range []int32{c - 1, c, c + 1} {
switch {
// Skip checking the cell itself
case neighborRow == r && neighborColumn == c:
continue
// Wrap if pixel is out of bounds
case neighborRow < 0:
neighborRow = int32(u.height) - 1
case neighborColumn < 0:
neighborColumn = int32(u.width) - 1
case neighborRow > int32(u.height)-1:
neighborRow = 0
case neighborColumn > int32(u.width)-1:
neighborColumn = 0
}
// Now, check neighbor
neighborIdx := u.GetIndex(uint32(neighborRow), uint32(neighborColumn))
if u.Cell(neighborIdx) != Dead {
count++
}
}
}

return count
}
Loading

0 comments on commit 5876061

Please sign in to comment.