Skip to content

Commit fbbec6a

Browse files
jlafayettecoreyja
andauthored
Snail mode (#98)
* Add snail-mode map * snail-mode: cap max hazards to 7 - Ensure that no more than 7 hazards are added to a square. This fixes a bug where some squares were getting way too many hazards applied to them. There must be some other bug at work here as well. - Change author names to be github usernames instead of first names * snail-mode: fix bug with eliminated snakes - Ensure that hazard snail-trail is not added for eliminated snakes * Update from Stream July 31 Added comments to most functions and important bits of code Also changed the map so that instead of a fixed number of 7 hazards, we add hazards equal to the length of the snake. * snail-mode: add TAG_EXPERIMENTAL and TAG_HAZARD_PLACEMENT * snail-mode: use Point as map key Co-authored-by: Corey Alexander <coreyja@gmail.com>
1 parent f82cfe5 commit fbbec6a

File tree

1 file changed

+179
-0
lines changed

1 file changed

+179
-0
lines changed

maps/snail_mode.go

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
package maps
2+
3+
import (
4+
"github.com/BattlesnakeOfficial/rules"
5+
)
6+
7+
type SnailModeMap struct{}
8+
9+
// init registers this map in the global registry.
10+
func init() {
11+
globalRegistry.RegisterMap("snail_mode", SnailModeMap{})
12+
}
13+
14+
// ID returns a unique identifier for this map.
15+
func (m SnailModeMap) ID() string {
16+
return "snail_mode"
17+
}
18+
19+
// Meta returns the non-functional metadata about this map.
20+
func (m SnailModeMap) Meta() Metadata {
21+
return Metadata{
22+
Name: "Snail Mode",
23+
Description: "Snakes leave behind a trail of hazards",
24+
Author: "coreyja and jlafayette",
25+
Version: 1,
26+
MinPlayers: 1,
27+
MaxPlayers: 16,
28+
BoardSizes: OddSizes(rules.BoardSizeSmall, rules.BoardSizeXXLarge),
29+
Tags: []string{TAG_EXPERIMENTAL, TAG_HAZARD_PLACEMENT},
30+
}
31+
}
32+
33+
// SetupBoard here is pretty 'standard' and doesn't do any special setup for this game mode
34+
func (m SnailModeMap) SetupBoard(initialBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
35+
rand := settings.GetRand(0)
36+
37+
if len(initialBoardState.Snakes) > int(m.Meta().MaxPlayers) {
38+
return rules.ErrorTooManySnakes
39+
}
40+
41+
snakeIDs := make([]string, 0, len(initialBoardState.Snakes))
42+
for _, snake := range initialBoardState.Snakes {
43+
snakeIDs = append(snakeIDs, snake.ID)
44+
}
45+
46+
tempBoardState := rules.NewBoardState(initialBoardState.Width, initialBoardState.Height)
47+
err := rules.PlaceSnakesAutomatically(rand, tempBoardState, snakeIDs)
48+
if err != nil {
49+
return err
50+
}
51+
52+
// Copy snakes from temp board state
53+
for _, snake := range tempBoardState.Snakes {
54+
editor.PlaceSnake(snake.ID, snake.Body, snake.Health)
55+
}
56+
57+
return nil
58+
}
59+
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+
77+
// doubleTail determine if the snake has a double stacked tail currently
78+
func doubleTail(snake *rules.Snake) bool {
79+
almostTail := snake.Body[len(snake.Body)-2]
80+
tail := snake.Body[len(snake.Body)-1]
81+
return almostTail.X == tail.X && almostTail.Y == tail.Y
82+
}
83+
84+
// isEliminated determines if the snake is already eliminated
85+
func isEliminated(s *rules.Snake) bool {
86+
return s.EliminatedCause != rules.NotEliminated
87+
}
88+
89+
// UpdateBoard does the work of placing the hazards along the 'snail tail' of snakes
90+
// This is responsible for saving the current tail location off the board
91+
// and restoring the previous tail position. This also handles removing one hazards from
92+
// the current stacks so the hazards tails fade as the snake moves away.
93+
func (m SnailModeMap) UpdateBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
94+
err := StandardMap{}.UpdateBoard(lastBoardState, settings, editor)
95+
if err != nil {
96+
return err
97+
}
98+
99+
// This map decrements the stack of hazards on a point each turn, so they
100+
// need to be cleared first.
101+
editor.ClearHazards()
102+
103+
// This is a list of all the hazards we want to add for the previous tails
104+
// These were stored off board in the previous turn as a way to save state
105+
// When we add the locations to this list we have already converted the off-board
106+
// points to on-board points
107+
tailLocations := make([]rules.Point, 0, len(lastBoardState.Snakes))
108+
109+
// Count the number of hazards for a given position
110+
// Add non-double tail locations to a slice
111+
hazardCounts := map[rules.Point]int{}
112+
for _, hazard := range lastBoardState.Hazards {
113+
114+
// discard out of bound
115+
if outOfBounds(hazard, lastBoardState.Width, lastBoardState.Height) {
116+
onBoardTail := getPrevTailLocation(hazard, lastBoardState.Height)
117+
tailLocations = append(tailLocations, onBoardTail)
118+
} else {
119+
hazardCounts[hazard]++
120+
}
121+
}
122+
123+
// Add back existing hazards, but with a stack of 1 less than before.
124+
// This has the effect of making the snail-trail disappear over time.
125+
for hazard, count := range hazardCounts {
126+
127+
for i := 0; i < count-1; i++ {
128+
editor.AddHazard(hazard)
129+
}
130+
}
131+
132+
// Store a stack of hazards for the tail of each snake. This is stored out
133+
// of bounds and then applied on the next turn. The stack count is equal
134+
// the lenght of the snake.
135+
for _, snake := range lastBoardState.Snakes {
136+
if isEliminated(&snake) {
137+
continue
138+
}
139+
140+
// Double tail means that the tail will stay on the same square for more
141+
// than one turn, so we don't want to spawn hazards
142+
if doubleTail(&snake) {
143+
continue
144+
}
145+
146+
tail := snake.Body[len(snake.Body)-1]
147+
offBoardTail := storeTailLocation(tail, lastBoardState.Height)
148+
for i := 0; i < len(snake.Body); i++ {
149+
editor.AddHazard(offBoardTail)
150+
}
151+
}
152+
153+
// Read offboard tails and move them to the board. The offboard tails are
154+
// stacked based on the length of the snake
155+
for _, p := range tailLocations {
156+
157+
// Skip position if a snakes head occupies it.
158+
// Otherwise hazard shows up in the viewer on top of a snake head, but
159+
// does not damage the snake, which is visually confusing.
160+
isHead := false
161+
for _, snake := range lastBoardState.Snakes {
162+
if isEliminated(&snake) {
163+
continue
164+
}
165+
head := snake.Body[0]
166+
if p.X == head.X && p.Y == head.Y {
167+
isHead = true
168+
break
169+
}
170+
}
171+
if isHead {
172+
continue
173+
}
174+
175+
editor.AddHazard(p)
176+
}
177+
178+
return nil
179+
}

0 commit comments

Comments
 (0)