Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Day455 #920

Merged
merged 2 commits into from
Feb 19, 2020
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -452,4 +452,5 @@ problems from
* [Day 452](https://github.com/vaskoz/dailycodingproblem-go/issues/913)
* [Day 453](https://github.com/vaskoz/dailycodingproblem-go/issues/915)
* [Day 454](https://github.com/vaskoz/dailycodingproblem-go/issues/917)
* [Day 455](https://github.com/vaskoz/dailycodingproblem-go/issues/919)

152 changes: 152 additions & 0 deletions day455/problem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package day455

import (
"math"
"strings"
)

// Coord represents a coordinate in the game of life.
type Coord struct {
X, Y int64
}

// GameOfLife represents the game state.
// '*' represents a live cell and '.' represents a dead cell.
type GameOfLife struct {
living map[Coord]struct{}
minX, minY, maxX, maxY int64
}

// NewGameOfLife parses a starting board.
// The given board's lower left coordinate with be 0,0
func NewGameOfLife(board string) *GameOfLife {
gol := &GameOfLife{living: make(map[Coord]struct{})}
rows := strings.Split(board, "\n")
gol.maxY = int64(len(rows))

for y := 0; y < len(rows); y++ {
for x, val := range rows[len(rows)-1-y] {
if val == '*' {
gol.living[Coord{int64(x), int64(y)}] = struct{}{}
if int64(x) >= gol.maxX {
gol.maxX = int64(x) + 1
}
}
}
}

gol.minX, gol.minY = -1, -1

return gol
}

// String returns the string representation of the board now.
func (gol *GameOfLife) String() string {
var sb strings.Builder

for y := gol.maxY - 1; y > gol.minY; y-- {
for x := gol.minX + 1; x < gol.maxX; x++ {
if _, alive := gol.living[Coord{x, y}]; alive {
sb.WriteRune('*') // nolint: gosec
} else {
sb.WriteRune('.') // nolint: gosec
}
}
sb.WriteRune('\n') // nolint: gosec
}

return sb.String()
}

// Step executes a single step of the simulation.
func (gol *GameOfLife) Step() {
birth, death := make([]Coord, 0), make([]Coord, 0)

for coord := range gol.living {
if gol.willDie(coord) {
death = append(death, coord)
}

birth = append(birth, gol.willBeBorn(coord)...)
}

for _, d := range death {
delete(gol.living, d)
}

for _, b := range birth {
gol.living[b] = struct{}{}
}

gol.minX, gol.minY, gol.maxX, gol.maxY = math.MaxInt64, math.MaxInt64, math.MinInt64, math.MinInt64

for coord := range gol.living {
if coord.X <= gol.minX {
gol.minX = coord.X - 1
}

if coord.X >= gol.maxX {
gol.maxX = coord.X + 1
}

if coord.Y <= gol.minY {
gol.minY = coord.Y - 1
}

if coord.Y >= gol.maxY {
gol.maxY = coord.Y + 1
}
}
}

func (gol *GameOfLife) livingNeighbors(coord Coord) int {
livingNeighbors := 0

for x := coord.X - 1; x <= coord.X+1; x++ {
for y := coord.Y - 1; y <= coord.Y+1; y++ {
if x == coord.X && y == coord.Y {
continue
}

if _, alive := gol.living[Coord{x, y}]; alive {
livingNeighbors++
}
}
}

return livingNeighbors
}

func (gol *GameOfLife) willBeBorn(coord Coord) []Coord {
birth := make([]Coord, 0)
checked := make(map[Coord]struct{})

for x := coord.X - 1; x <= coord.X+1; x++ {
for y := coord.Y - 1; y <= coord.Y+1; y++ {
if x == coord.X && y == coord.Y {
continue
}

coord := Coord{x, y}

if _, alive := gol.living[coord]; alive {
continue
}

if _, done := checked[coord]; !done {
checked[coord] = struct{}{}

if living := gol.livingNeighbors(coord); living == 3 {
birth = append(birth, coord)
}
}
}
}

return birth
}

func (gol *GameOfLife) willDie(coord Coord) bool {
living := gol.livingNeighbors(coord)
return living < 2 || living > 3
}
44 changes: 44 additions & 0 deletions day455/problem_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package day455

import "testing"

// nolint
var testcases = []struct {
board string
steps int
expected string
}{
{"*\n*\n*", 1, "***\n"},
{"*\n*\n*", 2, "*\n*\n*\n"},
{"**\n**", 2, "**\n**\n"},
{"**\n*.", 1, "**\n**\n"},
{"..*\n.*.\n*..", 1, "*\n"},
{"**.\n*.*\n.*.", 2, "**.\n*.*\n.*.\n"},
{"**.\n*.*\n.*.", 20, "**.\n*.*\n.*.\n"},
}

func TestGameOfLife(t *testing.T) {
t.Parallel()

for n, tc := range testcases {
gol := NewGameOfLife(tc.board)

for i := 0; i < tc.steps; i++ {
gol.Step()
}

if result := gol.String(); result != tc.expected {
t.Errorf("TC%d Expected %v got %v", n, tc.expected, result)
}
}
}

func BenchmarkGameOfLife(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, tc := range testcases {
gol := NewGameOfLife(tc.board)
gol.Step()
_ = gol.String()
}
}
}