Skip to content

Commit

Permalink
DEV-1761: New rules API (#118)
Browse files Browse the repository at this point in the history
* DEV-1761: Clean up Ruleset interface (#115)

* remove legacy ruleset types and simplify ruleset interface

* remove unnecessary settings argument from Ruleset interface

* decouple rules.Settings from client API and store settings as strings

* DEV 1761: Add new BoardState and Point fields (#117)

* add Point.TTL, Point.Value, GameState and PointState to BoardState

* allow maps to access BoardState.GameState,PointState

* add PreUpdateBoard and refactor snail_mode with it

* fix bug where an extra turn was printed to the console

* fix formatting

* fix lint errors

Co-authored-by: JonathanArns <jonathan.arns@googlemail.com>
  • Loading branch information
robbles and JonathanArns authored Oct 28, 2022
1 parent 639362e commit 82e1999
Show file tree
Hide file tree
Showing 50 changed files with 1,349 additions and 1,610 deletions.
116 changes: 88 additions & 28 deletions board.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,36 @@ package rules

import "fmt"

// BoardState represents the internal state of a game board.
// NOTE: use NewBoardState to construct these to ensure fields are initialized
// correctly and that tests are resilient to changes to this type.
type BoardState struct {
Turn int
Height int
Width int
Food []Point
Snakes []Snake
Hazards []Point

// Generic game-level state for maps and rules stages to persist data between turns.
GameState map[string]string

// Numeric state keyed to specific points, also persisted between turns.
PointState map[Point]int
}

type Point struct {
X int
Y int
X int `json:"X"`
Y int `json:"Y"`
TTL int `json:"TTL,omitempty"`
Value int `json:"Value,omitempty"`
}

// Makes it easier to copy sample points out of Go logs and test failures.
func (p Point) GoString() string {
if p.TTL != 0 || p.Value != 0 {
return fmt.Sprintf("{X:%d, Y:%d, TTL:%d, Value:%d}", p.X, p.Y, p.TTL, p.Value)
}
return fmt.Sprintf("{X:%d, Y:%d}", p.X, p.Y)
}

Expand All @@ -33,24 +47,34 @@ type Snake struct {
// NewBoardState returns an empty but fully initialized BoardState
func NewBoardState(width, height int) *BoardState {
return &BoardState{
Turn: 0,
Height: height,
Width: width,
Food: []Point{},
Snakes: []Snake{},
Hazards: []Point{},
Turn: 0,
Height: height,
Width: width,
Food: []Point{},
Snakes: []Snake{},
Hazards: []Point{},
GameState: map[string]string{},
PointState: map[Point]int{},
}
}

// Clone returns a deep copy of prevState that can be safely modified without affecting the original
func (prevState *BoardState) Clone() *BoardState {
nextState := &BoardState{
Turn: prevState.Turn,
Height: prevState.Height,
Width: prevState.Width,
Food: append([]Point{}, prevState.Food...),
Snakes: make([]Snake, len(prevState.Snakes)),
Hazards: append([]Point{}, prevState.Hazards...),
Turn: prevState.Turn,
Height: prevState.Height,
Width: prevState.Width,
Food: append([]Point{}, prevState.Food...),
Snakes: make([]Snake, len(prevState.Snakes)),
Hazards: append([]Point{}, prevState.Hazards...),
GameState: make(map[string]string, len(prevState.GameState)),
PointState: make(map[Point]int, len(prevState.PointState)),
}
for key, value := range prevState.GameState {
nextState.GameState[key] = value
}
for key, value := range prevState.PointState {
nextState.PointState[key] = value
}
for i := 0; i < len(prevState.Snakes); i++ {
nextState.Snakes[i].ID = prevState.Snakes[i].ID
Expand All @@ -63,6 +87,42 @@ func (prevState *BoardState) Clone() *BoardState {
return nextState
}

// Builder method to set Turn and return the modified BoardState.
func (state *BoardState) WithTurn(turn int) *BoardState {
state.Turn = turn
return state
}

// Builder method to set Food and return the modified BoardState.
func (state *BoardState) WithFood(food []Point) *BoardState {
state.Food = food
return state
}

// Builder method to set Hazards and return the modified BoardState.
func (state *BoardState) WithHazards(hazards []Point) *BoardState {
state.Hazards = hazards
return state
}

// Builder method to set Snakes and return the modified BoardState.
func (state *BoardState) WithSnakes(snakes []Snake) *BoardState {
state.Snakes = snakes
return state
}

// Builder method to set State and return the modified BoardState.
func (state *BoardState) WithGameState(gameState map[string]string) *BoardState {
state.GameState = gameState
return state
}

// Builder method to set PointState and return the modified BoardState.
func (state *BoardState) WithPointState(pointState map[Point]int) *BoardState {
state.PointState = pointState
return state
}

// CreateDefaultBoardState is a convenience function for fully initializing a
// "default" board state with snakes and food.
// In a real game, the engine may generate the board without calling this
Expand Down Expand Up @@ -120,16 +180,16 @@ func PlaceSnakesFixed(rand Rand, b *BoardState, snakeIDs []string) error {
// Create start 8 points
mn, md, mx := 1, (b.Width-1)/2, b.Width-2
cornerPoints := []Point{
{mn, mn},
{mn, mx},
{mx, mn},
{mx, mx},
{X: mn, Y: mn},
{X: mn, Y: mx},
{X: mx, Y: mn},
{X: mx, Y: mx},
}
cardinalPoints := []Point{
{mn, md},
{md, mn},
{md, mx},
{mx, md},
{X: mn, Y: md},
{X: md, Y: mn},
{X: md, Y: mx},
{X: mx, Y: md},
}

// Sanity check
Expand Down Expand Up @@ -325,7 +385,7 @@ func PlaceFoodAutomatically(rand Rand, b *BoardState) error {

// Deprecated: will be replaced by maps.PlaceFoodFixed
func PlaceFoodFixed(rand Rand, b *BoardState) error {
centerCoord := Point{(b.Width - 1) / 2, (b.Height - 1) / 2}
centerCoord := Point{X: (b.Width - 1) / 2, Y: (b.Height - 1) / 2}

isSmallBoard := b.Width*b.Height < BoardSizeMedium*BoardSizeMedium
// Up to 4 snakes can be placed such that food is nearby on small boards.
Expand All @@ -335,10 +395,10 @@ func PlaceFoodFixed(rand Rand, b *BoardState) error {
for i := 0; i < len(b.Snakes); i++ {
snakeHead := b.Snakes[i].Body[0]
possibleFoodLocations := []Point{
{snakeHead.X - 1, snakeHead.Y - 1},
{snakeHead.X - 1, snakeHead.Y + 1},
{snakeHead.X + 1, snakeHead.Y - 1},
{snakeHead.X + 1, snakeHead.Y + 1},
{X: snakeHead.X - 1, Y: snakeHead.Y - 1},
{X: snakeHead.X - 1, Y: snakeHead.Y + 1},
{X: snakeHead.X + 1, Y: snakeHead.Y - 1},
{X: snakeHead.X + 1, Y: snakeHead.Y + 1},
}

// Remove any invalid/unwanted positions
Expand Down Expand Up @@ -448,7 +508,7 @@ func GetEvenUnoccupiedPoints(b *BoardState) []Point {

// removeCenterCoord filters out the board's center point from a list of points.
func removeCenterCoord(b *BoardState, points []Point) []Point {
centerCoord := Point{(b.Width - 1) / 2, (b.Height - 1) / 2}
centerCoord := Point{X: (b.Width - 1) / 2, Y: (b.Height - 1) / 2}
var noCenterPoints []Point
for _, p := range points {
if p != centerCoord {
Expand Down
Loading

0 comments on commit 82e1999

Please sign in to comment.