@@ -2,22 +2,36 @@ package rules
22
33import "fmt"
44
5+ // BoardState represents the internal state of a game board.
6+ // NOTE: use NewBoardState to construct these to ensure fields are initialized
7+ // correctly and that tests are resilient to changes to this type.
58type BoardState struct {
69 Turn int
710 Height int
811 Width int
912 Food []Point
1013 Snakes []Snake
1114 Hazards []Point
15+
16+ // Generic game-level state for maps and rules stages to persist data between turns.
17+ GameState map [string ]string
18+
19+ // Numeric state keyed to specific points, also persisted between turns.
20+ PointState map [Point ]int
1221}
1322
1423type Point struct {
15- X int
16- Y int
24+ X int `json:"X"`
25+ Y int `json:"Y"`
26+ TTL int `json:"TTL,omitempty"`
27+ Value int `json:"Value,omitempty"`
1728}
1829
1930// Makes it easier to copy sample points out of Go logs and test failures.
2031func (p Point ) GoString () string {
32+ if p .TTL != 0 || p .Value != 0 {
33+ return fmt .Sprintf ("{X:%d, Y:%d, TTL:%d, Value:%d}" , p .X , p .Y , p .TTL , p .Value )
34+ }
2135 return fmt .Sprintf ("{X:%d, Y:%d}" , p .X , p .Y )
2236}
2337
@@ -33,24 +47,34 @@ type Snake struct {
3347// NewBoardState returns an empty but fully initialized BoardState
3448func NewBoardState (width , height int ) * BoardState {
3549 return & BoardState {
36- Turn : 0 ,
37- Height : height ,
38- Width : width ,
39- Food : []Point {},
40- Snakes : []Snake {},
41- Hazards : []Point {},
50+ Turn : 0 ,
51+ Height : height ,
52+ Width : width ,
53+ Food : []Point {},
54+ Snakes : []Snake {},
55+ Hazards : []Point {},
56+ GameState : map [string ]string {},
57+ PointState : map [Point ]int {},
4258 }
4359}
4460
4561// Clone returns a deep copy of prevState that can be safely modified without affecting the original
4662func (prevState * BoardState ) Clone () * BoardState {
4763 nextState := & BoardState {
48- Turn : prevState .Turn ,
49- Height : prevState .Height ,
50- Width : prevState .Width ,
51- Food : append ([]Point {}, prevState .Food ... ),
52- Snakes : make ([]Snake , len (prevState .Snakes )),
53- Hazards : append ([]Point {}, prevState .Hazards ... ),
64+ Turn : prevState .Turn ,
65+ Height : prevState .Height ,
66+ Width : prevState .Width ,
67+ Food : append ([]Point {}, prevState .Food ... ),
68+ Snakes : make ([]Snake , len (prevState .Snakes )),
69+ Hazards : append ([]Point {}, prevState .Hazards ... ),
70+ GameState : make (map [string ]string , len (prevState .GameState )),
71+ PointState : make (map [Point ]int , len (prevState .PointState )),
72+ }
73+ for key , value := range prevState .GameState {
74+ nextState .GameState [key ] = value
75+ }
76+ for key , value := range prevState .PointState {
77+ nextState .PointState [key ] = value
5478 }
5579 for i := 0 ; i < len (prevState .Snakes ); i ++ {
5680 nextState .Snakes [i ].ID = prevState .Snakes [i ].ID
@@ -63,6 +87,42 @@ func (prevState *BoardState) Clone() *BoardState {
6387 return nextState
6488}
6589
90+ // Builder method to set Turn and return the modified BoardState.
91+ func (state * BoardState ) WithTurn (turn int ) * BoardState {
92+ state .Turn = turn
93+ return state
94+ }
95+
96+ // Builder method to set Food and return the modified BoardState.
97+ func (state * BoardState ) WithFood (food []Point ) * BoardState {
98+ state .Food = food
99+ return state
100+ }
101+
102+ // Builder method to set Hazards and return the modified BoardState.
103+ func (state * BoardState ) WithHazards (hazards []Point ) * BoardState {
104+ state .Hazards = hazards
105+ return state
106+ }
107+
108+ // Builder method to set Snakes and return the modified BoardState.
109+ func (state * BoardState ) WithSnakes (snakes []Snake ) * BoardState {
110+ state .Snakes = snakes
111+ return state
112+ }
113+
114+ // Builder method to set State and return the modified BoardState.
115+ func (state * BoardState ) WithGameState (gameState map [string ]string ) * BoardState {
116+ state .GameState = gameState
117+ return state
118+ }
119+
120+ // Builder method to set PointState and return the modified BoardState.
121+ func (state * BoardState ) WithPointState (pointState map [Point ]int ) * BoardState {
122+ state .PointState = pointState
123+ return state
124+ }
125+
66126// CreateDefaultBoardState is a convenience function for fully initializing a
67127// "default" board state with snakes and food.
68128// 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 {
120180 // Create start 8 points
121181 mn , md , mx := 1 , (b .Width - 1 )/ 2 , b .Width - 2
122182 cornerPoints := []Point {
123- {mn , mn },
124- {mn , mx },
125- {mx , mn },
126- {mx , mx },
183+ {X : mn , Y : mn },
184+ {X : mn , Y : mx },
185+ {X : mx , Y : mn },
186+ {X : mx , Y : mx },
127187 }
128188 cardinalPoints := []Point {
129- {mn , md },
130- {md , mn },
131- {md , mx },
132- {mx , md },
189+ {X : mn , Y : md },
190+ {X : md , Y : mn },
191+ {X : md , Y : mx },
192+ {X : mx , Y : md },
133193 }
134194
135195 // Sanity check
@@ -325,7 +385,7 @@ func PlaceFoodAutomatically(rand Rand, b *BoardState) error {
325385
326386// Deprecated: will be replaced by maps.PlaceFoodFixed
327387func PlaceFoodFixed (rand Rand , b * BoardState ) error {
328- centerCoord := Point {(b .Width - 1 ) / 2 , (b .Height - 1 ) / 2 }
388+ centerCoord := Point {X : (b .Width - 1 ) / 2 , Y : (b .Height - 1 ) / 2 }
329389
330390 isSmallBoard := b .Width * b .Height < BoardSizeMedium * BoardSizeMedium
331391 // 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 {
335395 for i := 0 ; i < len (b .Snakes ); i ++ {
336396 snakeHead := b .Snakes [i ].Body [0 ]
337397 possibleFoodLocations := []Point {
338- {snakeHead .X - 1 , snakeHead .Y - 1 },
339- {snakeHead .X - 1 , snakeHead .Y + 1 },
340- {snakeHead .X + 1 , snakeHead .Y - 1 },
341- {snakeHead .X + 1 , snakeHead .Y + 1 },
398+ {X : snakeHead .X - 1 , Y : snakeHead .Y - 1 },
399+ {X : snakeHead .X - 1 , Y : snakeHead .Y + 1 },
400+ {X : snakeHead .X + 1 , Y : snakeHead .Y - 1 },
401+ {X : snakeHead .X + 1 , Y : snakeHead .Y + 1 },
342402 }
343403
344404 // Remove any invalid/unwanted positions
@@ -448,7 +508,7 @@ func GetEvenUnoccupiedPoints(b *BoardState) []Point {
448508
449509// removeCenterCoord filters out the board's center point from a list of points.
450510func removeCenterCoord (b * BoardState , points []Point ) []Point {
451- centerCoord := Point {(b .Width - 1 ) / 2 , (b .Height - 1 ) / 2 }
511+ centerCoord := Point {X : (b .Width - 1 ) / 2 , Y : (b .Height - 1 ) / 2 }
452512 var noCenterPoints []Point
453513 for _ , p := range points {
454514 if p != centerCoord {
0 commit comments