diff --git a/conway/go/conway_test.go b/conway/go/conway_test.go index 9fd2f6b..735eb34 100644 --- a/conway/go/conway_test.go +++ b/conway/go/conway_test.go @@ -21,7 +21,7 @@ func init() { func TestNewGameOfLifeHasCorrectDimensions(t *testing.T) { game := NewGameOfLife(16, 16) - if game.Height != 16 || game.Width != 16 { + if game.State.Height() != 16 || game.State.Width() != 16 { t.Fail() } } @@ -40,7 +40,7 @@ func TestNewGameOfLifeHasGameStateOfSameDimensions(t *testing.T) { func TestGameStateCanImportState(t *testing.T) { game := NewGameOfLife(16, 16) - err := game.State.ImportState(TEST_SIMPLE_INITIAL_STATE) + err := game.State.Import(TEST_SIMPLE_INITIAL_STATE) if err != nil { t.Fail() } @@ -68,9 +68,10 @@ func TestGameStateCanImportState(t *testing.T) { func TestLiveCellWithFewerThanTwoLiveNeighborsDies(t *testing.T) { game := NewGameOfLife(16, 16) - err := game.State.ImportState(TEST_SIMPLE_INITIAL_STATE) + err := game.State.Import(TEST_SIMPLE_INITIAL_STATE) if err != nil { t.Error(err) + return } game.EvaluateGeneration() @@ -78,10 +79,35 @@ func TestLiveCellWithFewerThanTwoLiveNeighborsDies(t *testing.T) { value, err := game.State.Get(2, 2) if err != nil { t.Error(err) + return } if value != 0 { t.Errorf("%v != 0", value) + return + } +} + +func TestLiveCellWithTwoOrThreeLiveNeighborsLivesOn(t *testing.T) { + game := NewGameOfLife(16, 16) + err := game.ImportState(TEST_SIMPLE_INITIAL_STATE) + + if err != nil { + t.Error(err) + return + } + + game.EvaluateGeneration() + + value, err := game.State.Get(0, 0) + if err != nil { + t.Error(err) + return + } + + if value != 1 { + t.Errorf("%v != 1", value) + return } } @@ -144,3 +170,45 @@ func TestGameStateCanDeadenCellsByCoords(t *testing.T) { t.Fail() } } + +func TestNewGenerationScoreCardIsAllZeros(t *testing.T) { + gen := NewGenerationScoreCard(4, 4) + + for y := 0; y < gen.Height(); y++ { + for x := 0; x < gen.Width(); x++ { + if value, err := gen.Get(x, y); err != nil || value != 0 { + t.Fail() + } + } + } +} + +func TestGenerationScoreCardCalculateResultsInCorrectNeighborCounts(t *testing.T) { + genScore := NewGenerationScoreCard(16, 16) + state := NewGameState(16, 16) + + err := state.Import(TEST_SIMPLE_INITIAL_STATE) + if err != nil { + t.Error(err) + return + } + + genScore.Calculate(state) + value, err := genScore.Get(0, 0) + if err != nil { + t.Error(err) + return + } else if value != 3 { + t.Errorf("[0, 0] value %v != 3", value) + return + } + + value, err = genScore.Get(2, 2) + if err != nil { + t.Error(err) + return + } else if value != 1 { + t.Errorf("[2, 2] value %d != 1", value) + return + } +} diff --git a/conway/go/game_of_life.go b/conway/go/game_of_life.go index 2f75760..d38eb5f 100644 --- a/conway/go/game_of_life.go +++ b/conway/go/game_of_life.go @@ -2,12 +2,11 @@ package conway import ( "errors" + "fmt" ) type GameOfLife struct { - Height int - Width int - State *GameState + State *GameState } type gameGridRow struct { @@ -18,6 +17,10 @@ type GameState struct { Rows []*gameGridRow } +type GenerationScoreCard struct { + GameState +} + func NewGameState(height, width int) *GameState { state := &GameState{} for i := 0; i < height; i++ { @@ -30,8 +33,19 @@ func NewGameState(height, width int) *GameState { return state } +func (state *GameState) Height() int { + return len(state.Rows) +} + +func (state *GameState) Width() int { + if len(state.Rows) < 1 { + return -1 + } + return len(state.Rows[0].Cols) +} + func (state *GameState) GetRow(y int) (*gameGridRow, error) { - if len(state.Rows) < y+1 { + if y+1 > len(state.Rows) { return nil, errors.New("y coordinate is out of bounds!") } @@ -39,13 +53,19 @@ func (state *GameState) GetRow(y int) (*gameGridRow, error) { } func (state *GameState) Set(x, y, value int) error { + if x < 0 || y < 0 { + errMsg := fmt.Sprintf("coordinates (%v, %v) are out of bounds!", x, y) + return errors.New(errMsg) + } + row, err := state.GetRow(y) if err != nil { return err } - if len(row.Cols) < x+1 { - return errors.New("x coordinate is out of bounds!") + if x+1 > len(row.Cols) { + errMsg := fmt.Sprintf("x coordinate %v is out of bounds!", x) + return errors.New(errMsg) } row.Cols[x] = value @@ -73,7 +93,7 @@ func (state *GameState) Deaden(x, y int) error { return state.Set(x, y, 0) } -func (state *GameState) ImportState(other *GameState) (err error) { +func (state *GameState) Import(other *GameState) (err error) { for y, row := range other.Rows { for x, cell := range row.Cols { if err = state.Set(x, y, cell); err != nil { @@ -86,12 +106,91 @@ func (state *GameState) ImportState(other *GameState) (err error) { func NewGameOfLife(height, width int) *GameOfLife { return &GameOfLife{ - Height: height, - Width: width, - State: NewGameState(height, width), + State: NewGameState(height, width), } } -func (game *GameOfLife) EvaluateGeneration() { - return +func (game *GameOfLife) EvaluateGeneration() error { + height, width := game.State.Height(), game.State.Width() + + genScore := NewGenerationScoreCard(height, width) + genScore.Calculate(game.State) + + for y := 0; y < height-1; y++ { + for x := 0; x < width-1; x++ { + score, err := genScore.Get(x, y) + if err != nil { + return err + } + + if score < 2 { + err = game.State.Deaden(x, y) + if err != nil { + return err + } + } + } + } + + return nil +} + +func (game *GameOfLife) ImportState(state *GameState) error { + return game.State.Import(state) +} + +func NewGenerationScoreCard(height, width int) *GenerationScoreCard { + genScore := &GenerationScoreCard{} + for i := 0; i < height; i++ { + row := &gameGridRow{} + for j := 0; j < width; j++ { + row.Cols = append(row.Cols, 0) + } + genScore.Rows = append(genScore.Rows, row) + } + return genScore +} + +func (genScore *GenerationScoreCard) Calculate(state *GameState) error { + stateHeight, stateWidth := state.Height(), state.Width() + for y := 0; y < stateHeight-1; y++ { + for x := 0; x < stateWidth-1; x++ { + value, err := state.Get(x, y) + if err != nil { + return err + } + + if value == 1 { + for yOffset := -1; yOffset < 2; yOffset++ { + for xOffset := -1; xOffset < 2; xOffset++ { + xTarget := x + xOffset + yTarget := y + yOffset + + if xTarget < 0 || yTarget < 0 { + continue + } + + if xTarget+1 > stateWidth || yTarget+1 > stateWidth { + continue + } + + if xTarget == x && yTarget == y { + continue + } + + curScore, err := genScore.Get(xTarget, yTarget) + if err != nil { + continue // assume out of bounds, which is stinky + } + err = genScore.Set(xTarget, yTarget, curScore+1) + if err != nil { + return err + } + } + } + } + } + } + + return nil }