DEV-1761: New rules API (#118)
* 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>
This commit is contained in:
parent
639362ef46
commit
82e1999126
50 changed files with 1349 additions and 1610 deletions
116
board.go
116
board.go
|
|
@ -2,6 +2,9 @@ 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
|
||||
|
|
@ -9,15 +12,26 @@ type BoardState struct {
|
|||
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)
|
||||
}
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue