Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

lib/game: refactor MooreNeighbors function for better readability. #21

Merged
merged 3 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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