@@ -4,22 +4,20 @@ import (
44 "github.com/BattlesnakeOfficial/rules"
55)
66
7- type SnailModeMap struct {
8- lastTailPositions map [rules.Point ]int // local state is preserved during the turn
9- }
7+ type SnailModeMap struct {}
108
119// init registers this map in the global registry.
1210func init () {
13- globalRegistry .RegisterMap ("snail_mode" , & SnailModeMap {lastTailPositions : nil })
11+ globalRegistry .RegisterMap ("snail_mode" , SnailModeMap {})
1412}
1513
1614// ID returns a unique identifier for this map.
17- func (m * SnailModeMap ) ID () string {
15+ func (m SnailModeMap ) ID () string {
1816 return "snail_mode"
1917}
2018
2119// Meta returns the non-functional metadata about this map.
22- func (m * SnailModeMap ) Meta () Metadata {
20+ func (m SnailModeMap ) Meta () Metadata {
2321 return Metadata {
2422 Name : "Snail Mode" ,
2523 Description : "Snakes leave behind a trail of hazards" ,
@@ -33,7 +31,7 @@ func (m *SnailModeMap) Meta() Metadata {
3331}
3432
3533// SetupBoard here is pretty 'standard' and doesn't do any special setup for this game mode
36- func (m * SnailModeMap ) SetupBoard (initialBoardState * rules.BoardState , settings rules.Settings , editor Editor ) error {
34+ func (m SnailModeMap ) SetupBoard (initialBoardState * rules.BoardState , settings rules.Settings , editor Editor ) error {
3735 rand := settings .GetRand (0 )
3836
3937 if len (initialBoardState .Snakes ) > int (m .Meta ().MaxPlayers ) {
@@ -59,6 +57,23 @@ func (m *SnailModeMap) SetupBoard(initialBoardState *rules.BoardState, settings
5957 return nil
6058}
6159
60+ // storeTailLocation returns an offboard point that corresponds to the given point.
61+ // This is useful for storing state that can be accessed next turn.
62+ func storeTailLocation (point rules.Point , height int ) rules.Point {
63+ return rules.Point {X : point .X , Y : point .Y + height }
64+ }
65+
66+ // getPrevTailLocation returns the onboard point that corresponds to an offboard point.
67+ // This is useful for restoring state that was stored last turn.
68+ func getPrevTailLocation (point rules.Point , height int ) rules.Point {
69+ return rules.Point {X : point .X , Y : point .Y - height }
70+ }
71+
72+ // outOfBounds determines if the given point is out of bounds for the current board size
73+ func outOfBounds (p rules.Point , w , h int ) bool {
74+ return p .X < 0 || p .Y < 0 || p .X >= w || p .Y >= h
75+ }
76+
6277// doubleTail determine if the snake has a double stacked tail currently
6378func doubleTail (snake * rules.Snake ) bool {
6479 almostTail := snake .Body [len (snake .Body )- 2 ]
@@ -71,27 +86,15 @@ func isEliminated(s *rules.Snake) bool {
7186 return s .EliminatedCause != rules .NotEliminated
7287}
7388
74- // PreUpdateBoard stores the tail position of each snake in memory, to be
75- // able to place hazards there after the snakes move.
76- func (m * SnailModeMap ) PreUpdateBoard (lastBoardState * rules.BoardState , settings rules.Settings , editor Editor ) error {
77- m .lastTailPositions = make (map [rules.Point ]int )
78- for _ , snake := range lastBoardState .Snakes {
79- if isEliminated (& snake ) {
80- continue
81- }
82- // Double tail means that the tail will stay on the same square for more
83- // than one turn, so we don't want to spawn hazards
84- if doubleTail (& snake ) {
85- continue
86- }
87- m .lastTailPositions [snake .Body [len (snake .Body )- 1 ]] = len (snake .Body )
88- }
89+ func (m SnailModeMap ) PreUpdateBoard (lastBoardState * rules.BoardState , settings rules.Settings , editor Editor ) error {
8990 return nil
9091}
9192
9293// PostUpdateBoard does the work of placing the hazards along the 'snail tail' of snakes
93- // This also handles removing one hazards from the current stacks so the hazards tails fade as the snake moves away.
94- func (m * SnailModeMap ) PostUpdateBoard (lastBoardState * rules.BoardState , settings rules.Settings , editor Editor ) error {
94+ // This is responsible for saving the current tail location off the board
95+ // and restoring the previous tail position. This also handles removing one hazards from
96+ // the current stacks so the hazards tails fade as the snake moves away.
97+ func (m SnailModeMap ) PostUpdateBoard (lastBoardState * rules.BoardState , settings rules.Settings , editor Editor ) error {
9598 err := StandardMap {}.PostUpdateBoard (lastBoardState , settings , editor )
9699 if err != nil {
97100 return err
@@ -101,38 +104,79 @@ func (m *SnailModeMap) PostUpdateBoard(lastBoardState *rules.BoardState, setting
101104 // need to be cleared first.
102105 editor .ClearHazards ()
103106
107+ // This is a list of all the hazards we want to add for the previous tails
108+ // These were stored off board in the previous turn as a way to save state
109+ // When we add the locations to this list we have already converted the off-board
110+ // points to on-board points
111+ tailLocations := make ([]rules.Point , 0 , len (lastBoardState .Snakes ))
112+
104113 // Count the number of hazards for a given position
114+ // Add non-double tail locations to a slice
105115 hazardCounts := map [rules.Point ]int {}
106116 for _ , hazard := range lastBoardState .Hazards {
107- hazardCounts [hazard ]++
117+
118+ // discard out of bound
119+ if outOfBounds (hazard , lastBoardState .Width , lastBoardState .Height ) {
120+ onBoardTail := getPrevTailLocation (hazard , lastBoardState .Height )
121+ tailLocations = append (tailLocations , onBoardTail )
122+ } else {
123+ hazardCounts [hazard ]++
124+ }
108125 }
109126
110127 // Add back existing hazards, but with a stack of 1 less than before.
111128 // This has the effect of making the snail-trail disappear over time.
112129 for hazard , count := range hazardCounts {
130+
113131 for i := 0 ; i < count - 1 ; i ++ {
114132 editor .AddHazard (hazard )
115133 }
116134 }
117135
118- // Place a new stack of hazards where each snake's tail used to be
119- NewHazardLoop:
120- for location , count := range m .lastTailPositions {
136+ // Store a stack of hazards for the tail of each snake. This is stored out
137+ // of bounds and then applied on the next turn. The stack count is equal
138+ // the lenght of the snake.
139+ for _ , snake := range lastBoardState .Snakes {
140+ if isEliminated (& snake ) {
141+ continue
142+ }
143+
144+ // Double tail means that the tail will stay on the same square for more
145+ // than one turn, so we don't want to spawn hazards
146+ if doubleTail (& snake ) {
147+ continue
148+ }
149+
150+ tail := snake .Body [len (snake .Body )- 1 ]
151+ offBoardTail := storeTailLocation (tail , lastBoardState .Height )
152+ for i := 0 ; i < len (snake .Body ); i ++ {
153+ editor .AddHazard (offBoardTail )
154+ }
155+ }
156+
157+ // Read offboard tails and move them to the board. The offboard tails are
158+ // stacked based on the length of the snake
159+ for _ , p := range tailLocations {
160+
161+ // Skip position if a snakes head occupies it.
162+ // Otherwise hazard shows up in the viewer on top of a snake head, but
163+ // does not damage the snake, which is visually confusing.
164+ isHead := false
121165 for _ , snake := range lastBoardState .Snakes {
122166 if isEliminated (& snake ) {
123167 continue
124168 }
125169 head := snake .Body [0 ]
126- if location .X == head .X && location .Y == head .Y {
127- // Skip position if a snakes head occupies it.
128- // Otherwise hazard shows up in the viewer on top of a snake head, but
129- // does not damage the snake, which is visually confusing.
130- continue NewHazardLoop
170+ if p .X == head .X && p .Y == head .Y {
171+ isHead = true
172+ break
131173 }
132174 }
133- for i := 0 ; i < count ; i ++ {
134- editor . AddHazard ( location )
175+ if isHead {
176+ continue
135177 }
178+
179+ editor .AddHazard (p )
136180 }
137181
138182 return nil
0 commit comments