3e013ce1a5
Breaking out console runner into its own file, adding an empty web runner, and starting to test that games and game states are able to represent themselves as images.
181 lines
3.1 KiB
Go
181 lines
3.1 KiB
Go
package conway
|
|
|
|
import (
|
|
"crypto/sha1"
|
|
"errors"
|
|
"fmt"
|
|
"image"
|
|
"math"
|
|
"math/rand"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
DEAD_CELL = "■"
|
|
LIVE_CELL = " "
|
|
)
|
|
|
|
type GameStateCell struct {
|
|
Value int
|
|
X int
|
|
Y int
|
|
}
|
|
|
|
type GameStateRow struct {
|
|
Y int
|
|
Cells []*GameStateCell
|
|
}
|
|
|
|
type GameState struct {
|
|
Rows []*GameStateRow
|
|
}
|
|
|
|
func NewGameState(height, width int) *GameState {
|
|
state := &GameState{}
|
|
for i := 0; i < height; i++ {
|
|
row := &GameStateRow{Y: i}
|
|
for j := 0; j < width; j++ {
|
|
row.Cells = append(row.Cells, &GameStateCell{
|
|
Value: 0,
|
|
X: j,
|
|
Y: i,
|
|
})
|
|
}
|
|
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].Cells)
|
|
}
|
|
|
|
func (state *GameState) GetRow(y int) *GameStateRow {
|
|
lenRows := len(state.Rows)
|
|
|
|
if y < 0 {
|
|
return state.GetRow(lenRows + y)
|
|
}
|
|
|
|
if y+1 > lenRows {
|
|
return state.GetRow(y % lenRows)
|
|
}
|
|
|
|
return state.Rows[y]
|
|
}
|
|
|
|
func (state *GameState) Set(x, y, value int) {
|
|
cell := state.Get(x, y)
|
|
cell.Value = value
|
|
}
|
|
|
|
func (state *GameState) Get(x, y int) *GameStateCell {
|
|
row := state.GetRow(y)
|
|
lenCells := len(row.Cells)
|
|
|
|
if x < 0 {
|
|
return state.Get(lenCells+x, row.Y)
|
|
}
|
|
|
|
if x+1 > lenCells {
|
|
return state.Get(x%lenCells, row.Y)
|
|
}
|
|
|
|
return row.Cells[x]
|
|
}
|
|
|
|
func (state *GameState) Enliven(x, y int) {
|
|
state.Set(x, y, 1)
|
|
}
|
|
|
|
func (state *GameState) Deaden(x, y int) {
|
|
state.Set(x, y, 0)
|
|
}
|
|
|
|
func (state *GameState) Import(other *GameState) {
|
|
for y, row := range other.Rows {
|
|
for x, cell := range row.Cells {
|
|
state.Set(x, y, cell.Value)
|
|
}
|
|
}
|
|
}
|
|
|
|
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++ {
|
|
for x := 0; x < width; x++ {
|
|
c <- state.Get(x, y)
|
|
}
|
|
}
|
|
}(state, cells)
|
|
|
|
return (<-chan *GameStateCell)(cells), nil
|
|
}
|
|
|
|
func (state *GameState) Mutate() {
|
|
randX := rand.Intn(state.Width())
|
|
randY := rand.Intn(state.Height())
|
|
|
|
cell := state.Get(randX, randY)
|
|
state.Set(randX, randY, int(math.Abs(float64(cell.Value-1))))
|
|
}
|
|
|
|
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
|
|
|
|
cell := state.Get(x, y)
|
|
if cell.Value == 1 {
|
|
stringVal = LIVE_CELL
|
|
}
|
|
cells = append(cells, stringVal)
|
|
}
|
|
rows = append(rows, strings.Join(cells, ""))
|
|
}
|
|
return strings.Join(rows, "\n")
|
|
}
|
|
|
|
func (state *GameState) Image(xMult, yMult int) (*image.Gray16, error) {
|
|
return nil, errors.New("Not implemented!")
|
|
}
|