diff --git a/conway/game_of_life.go b/conway/game_of_life.go index d41b4da..841545b 100644 --- a/conway/game_of_life.go +++ b/conway/game_of_life.go @@ -1,222 +1,28 @@ package conway import ( - "crypto/sha1" "errors" "fmt" - "math" "math/rand" - "strings" "time" ) -const ( - DEAD_CELL = "■" - LIVE_CELL = " " -) - -var ( - neighbors = []neighbor{ - neighbor{-1, -1}, - neighbor{-1, 0}, - neighbor{-1, 1}, - neighbor{0, -1}, - neighbor{0, 1}, - neighbor{1, -1}, - neighbor{1, 0}, - neighbor{1, 1}, - } -) - func init() { rand.Seed(time.Now().UTC().UnixNano()) } -type neighbor struct { - X int - Y int -} - type GameOfLife struct { State *GameState } -type GameStateCell struct { - Value int - X int - Y int - cols []int -} - -type gameGridRow struct { - Cols []int -} - -type GameState struct { - Rows []*gameGridRow -} - -type GenerationScoreCard struct { - GameState -} - -func NewGameState(height, width int) *GameState { - state := &GameState{} - for i := 0; i < height; i++ { - row := &gameGridRow{} - for j := 0; j < width; j++ { - row.Cols = append(row.Cols, 0) - } - state.Rows = append(state.Rows, row) - } - 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 y+1 > len(state.Rows) { - return nil, errors.New("y coordinate is out of bounds!") - } - - return state.Rows[y], nil -} - -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 x+1 > len(row.Cols) { - errMsg := fmt.Sprintf("x coordinate %v is out of bounds!", x) - return errors.New(errMsg) - } - - row.Cols[x] = value - return nil -} - -func (state *GameState) Get(x, y int) (int, error) { - row, err := state.GetRow(y) - if err != nil { - return -1, err - } - - if len(row.Cols) < x+1 { - return -1, errors.New("x coordinate is out of bounds!") - } - - return row.Cols[x], nil -} - -func (state *GameState) Enliven(x, y int) error { - return state.Set(x, y, 1) -} - -func (state *GameState) Deaden(x, y int) error { - return state.Set(x, y, 0) -} - -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 { - return err - } - } - } - return nil -} - -func (state *GameState) Cells() (<-chan *GameStateCell, error) { - height := state.Height() - width := state.Width() - - if height < 0 || width < 0 { - return nil, errors.New("state has invalid dimensions!") - } - - cells := make(chan *GameStateCell) - - go func(state *GameState, c chan *GameStateCell) { - defer close(c) - height := state.Height() - width := state.Width() - - for y := 0; y < height; y++ { - row, err := state.GetRow(y) - if err != nil { - panic(err) - } - - for x := 0; x < width; x++ { - value, err := state.Get(x, y) - if err != nil { - panic(err) - } - c <- &GameStateCell{ - Value: value, - X: x, - Y: y, - cols: row.Cols, - } - } - } - }(state, cells) - - return (<-chan *GameStateCell)(cells), nil -} - func (game *GameOfLife) Mutate() error { return game.State.Mutate() } -func (state *GameState) Mutate() error { - randX := rand.Intn(state.Width()) - randY := rand.Intn(state.Height()) - - curVal, err := state.Get(randX, randY) - if err != nil { - return err - } - - err = state.Set(randX, randY, int(math.Abs(float64(curVal-1)))) - - return nil -} - func (game *GameOfLife) Checksum() (string, error) { return game.State.Checksum() } -func (state *GameState) Checksum() (string, error) { - ck := sha1.New() - cells, err := state.Cells() - if err != nil { - return "", err - } - - for cell := range cells { - fmt.Fprintf(ck, "%v", cell.Value) - } - return fmt.Sprintf("%x", ck.Sum(nil)), nil -} - func NewGameOfLife(height, width int) *GameOfLife { return &GameOfLife{ State: NewGameState(height, width), @@ -311,84 +117,3 @@ func (game *GameOfLife) ImportRandomState() error { func (game *GameOfLife) String() string { return fmt.Sprintf("%s\n", game.State) } - -func (cell *GameStateCell) SetValue(value int) { - cell.cols[cell.X] = value -} - -func (state *GameState) String() string { - var rows []string - - height := state.Height() - width := state.Width() - - for y := 0; y < height; y++ { - var cells []string - for x := 0; x < width; x++ { - stringVal := DEAD_CELL - - value, err := state.Get(x, y) - if err != nil { - return "" - } - - if value == 1 { - stringVal = LIVE_CELL - } - cells = append(cells, stringVal) - } - rows = append(rows, strings.Join(cells, "")) - } - return strings.Join(rows, "\n") -} - -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 { - stateWidth := state.Width() - stateCells, err := state.Cells() - if err != nil { - return err - } - - for stateCell := range stateCells { - if stateCell.Value == 0 { - continue - } - - for _, neighbor := range neighbors { - xTarget := stateCell.X + neighbor.X - yTarget := stateCell.Y + neighbor.Y - - if xTarget < 0 || yTarget < 0 { - continue - } - - if xTarget+1 > stateWidth || yTarget+1 > stateWidth { - continue - } - - curScore, err := genScore.Get(xTarget, yTarget) - if err != nil { - continue - } - - err = genScore.Set(xTarget, yTarget, curScore+1) - if err != nil { - return err - } - } - } - - return nil -} diff --git a/conway/game_state.go b/conway/game_state.go new file mode 100644 index 0000000..b59540b --- /dev/null +++ b/conway/game_state.go @@ -0,0 +1,198 @@ +package conway + +import ( + "crypto/sha1" + "errors" + "fmt" + "math" + "math/rand" + "strings" +) + +const ( + DEAD_CELL = "■" + LIVE_CELL = " " +) + +type gameGridRow struct { + Cols []int +} + +type GameState struct { + Rows []*gameGridRow +} + +func NewGameState(height, width int) *GameState { + state := &GameState{} + for i := 0; i < height; i++ { + row := &gameGridRow{} + for j := 0; j < width; j++ { + row.Cols = append(row.Cols, 0) + } + state.Rows = append(state.Rows, row) + } + 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 y+1 > len(state.Rows) { + return nil, errors.New("y coordinate is out of bounds!") + } + + return state.Rows[y], nil +} + +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 x+1 > len(row.Cols) { + errMsg := fmt.Sprintf("x coordinate %v is out of bounds!", x) + return errors.New(errMsg) + } + + row.Cols[x] = value + return nil +} + +func (state *GameState) Get(x, y int) (int, error) { + row, err := state.GetRow(y) + if err != nil { + return -1, err + } + + if len(row.Cols) < x+1 { + return -1, errors.New("x coordinate is out of bounds!") + } + + return row.Cols[x], nil +} + +func (state *GameState) Enliven(x, y int) error { + return state.Set(x, y, 1) +} + +func (state *GameState) Deaden(x, y int) error { + return state.Set(x, y, 0) +} + +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 { + return err + } + } + } + return nil +} + +func (state *GameState) Cells() (<-chan *GameStateCell, error) { + height := state.Height() + width := state.Width() + + if height < 0 || width < 0 { + return nil, errors.New("state has invalid dimensions!") + } + + cells := make(chan *GameStateCell) + + go func(state *GameState, c chan *GameStateCell) { + defer close(c) + height := state.Height() + width := state.Width() + + for y := 0; y < height; y++ { + row, err := state.GetRow(y) + if err != nil { + panic(err) + } + + for x := 0; x < width; x++ { + value, err := state.Get(x, y) + if err != nil { + panic(err) + } + c <- &GameStateCell{ + Value: value, + X: x, + Y: y, + cols: row.Cols, + } + } + } + }(state, cells) + + return (<-chan *GameStateCell)(cells), nil +} + +func (state *GameState) Mutate() error { + randX := rand.Intn(state.Width()) + randY := rand.Intn(state.Height()) + + curVal, err := state.Get(randX, randY) + if err != nil { + return err + } + + err = state.Set(randX, randY, int(math.Abs(float64(curVal-1)))) + + return nil +} + +func (state *GameState) Checksum() (string, error) { + ck := sha1.New() + cells, err := state.Cells() + if err != nil { + return "", err + } + + for cell := range cells { + fmt.Fprintf(ck, "%v", cell.Value) + } + return fmt.Sprintf("%x", ck.Sum(nil)), nil +} + +func (state *GameState) String() string { + var rows []string + + height := state.Height() + width := state.Width() + + for y := 0; y < height; y++ { + var cells []string + for x := 0; x < width; x++ { + stringVal := DEAD_CELL + + value, err := state.Get(x, y) + if err != nil { + return "" + } + + if value == 1 { + stringVal = LIVE_CELL + } + cells = append(cells, stringVal) + } + rows = append(rows, strings.Join(cells, "")) + } + return strings.Join(rows, "\n") +} diff --git a/conway/game_state_cell.go b/conway/game_state_cell.go new file mode 100644 index 0000000..963f20f --- /dev/null +++ b/conway/game_state_cell.go @@ -0,0 +1,12 @@ +package conway + +type GameStateCell struct { + Value int + X int + Y int + cols []int +} + +func (cell *GameStateCell) SetValue(value int) { + cell.cols[cell.X] = value +} diff --git a/conway/generation_score_card.go b/conway/generation_score_card.go new file mode 100644 index 0000000..aaa506e --- /dev/null +++ b/conway/generation_score_card.go @@ -0,0 +1,74 @@ +package conway + +var ( + neighbors = []neighbor{ + neighbor{-1, -1}, + neighbor{-1, 0}, + neighbor{-1, 1}, + neighbor{0, -1}, + neighbor{0, 1}, + neighbor{1, -1}, + neighbor{1, 0}, + neighbor{1, 1}, + } +) + +type GenerationScoreCard struct { + GameState +} + +type neighbor struct { + X int + Y int +} + +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 { + stateWidth := state.Width() + stateCells, err := state.Cells() + if err != nil { + return err + } + + for stateCell := range stateCells { + if stateCell.Value == 0 { + continue + } + + for _, neighbor := range neighbors { + xTarget := stateCell.X + neighbor.X + yTarget := stateCell.Y + neighbor.Y + + if xTarget < 0 || yTarget < 0 { + continue + } + + if xTarget+1 > stateWidth || yTarget+1 > stateWidth { + continue + } + + curScore, err := genScore.Get(xTarget, yTarget) + if err != nil { + continue + } + + err = genScore.Set(xTarget, yTarget, curScore+1) + if err != nil { + return err + } + } + } + + return nil +}