Revert SnailMode to not use PreUpdate (#121)

In the fall me and Jonathan worked on an update to the Engine
specifically with SnailMode in mind, and refactored SnailMode to use
this new `PreUpdate` method

This allowed us to not have to use off-board hazards as state!

However we want to use Snail Mode for our first Community Tournament!
And the web engine doesn't support PreUpdate yet, so we are reverting
Snail Mode to its old Pre-PreUpdate version :lol:
This commit is contained in:
Corey Alexander 2023-02-09 19:21:02 -05:00 committed by GitHub
parent 932f5418df
commit ef9c766d8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -4,22 +4,20 @@ import (
"github.com/BattlesnakeOfficial/rules" "github.com/BattlesnakeOfficial/rules"
) )
type SnailModeMap struct { type SnailModeMap struct{}
lastTailPositions map[rules.Point]int // local state is preserved during the turn
}
// init registers this map in the global registry. // init registers this map in the global registry.
func init() { func init() {
globalRegistry.RegisterMap("snail_mode", &SnailModeMap{lastTailPositions: nil}) globalRegistry.RegisterMap("snail_mode", SnailModeMap{})
} }
// ID returns a unique identifier for this map. // ID returns a unique identifier for this map.
func (m *SnailModeMap) ID() string { func (m SnailModeMap) ID() string {
return "snail_mode" return "snail_mode"
} }
// Meta returns the non-functional metadata about this map. // Meta returns the non-functional metadata about this map.
func (m *SnailModeMap) Meta() Metadata { func (m SnailModeMap) Meta() Metadata {
return Metadata{ return Metadata{
Name: "Snail Mode", Name: "Snail Mode",
Description: "Snakes leave behind a trail of hazards", Description: "Snakes leave behind a trail of hazards",
@ -33,7 +31,7 @@ func (m *SnailModeMap) Meta() Metadata {
} }
// SetupBoard here is pretty 'standard' and doesn't do any special setup for this game mode // SetupBoard here is pretty 'standard' and doesn't do any special setup for this game mode
func (m *SnailModeMap) SetupBoard(initialBoardState *rules.BoardState, settings rules.Settings, editor Editor) error { func (m SnailModeMap) SetupBoard(initialBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
rand := settings.GetRand(0) rand := settings.GetRand(0)
if len(initialBoardState.Snakes) > int(m.Meta().MaxPlayers) { if len(initialBoardState.Snakes) > int(m.Meta().MaxPlayers) {
@ -59,6 +57,23 @@ func (m *SnailModeMap) SetupBoard(initialBoardState *rules.BoardState, settings
return nil return nil
} }
// storeTailLocation returns an offboard point that corresponds to the given point.
// This is useful for storing state that can be accessed next turn.
func storeTailLocation(point rules.Point, height int) rules.Point {
return rules.Point{X: point.X, Y: point.Y + height}
}
// getPrevTailLocation returns the onboard point that corresponds to an offboard point.
// This is useful for restoring state that was stored last turn.
func getPrevTailLocation(point rules.Point, height int) rules.Point {
return rules.Point{X: point.X, Y: point.Y - height}
}
// outOfBounds determines if the given point is out of bounds for the current board size
func outOfBounds(p rules.Point, w, h int) bool {
return p.X < 0 || p.Y < 0 || p.X >= w || p.Y >= h
}
// doubleTail determine if the snake has a double stacked tail currently // doubleTail determine if the snake has a double stacked tail currently
func doubleTail(snake *rules.Snake) bool { func doubleTail(snake *rules.Snake) bool {
almostTail := snake.Body[len(snake.Body)-2] almostTail := snake.Body[len(snake.Body)-2]
@ -71,27 +86,15 @@ func isEliminated(s *rules.Snake) bool {
return s.EliminatedCause != rules.NotEliminated return s.EliminatedCause != rules.NotEliminated
} }
// PreUpdateBoard stores the tail position of each snake in memory, to be func (m SnailModeMap) PreUpdateBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
// able to place hazards there after the snakes move.
func (m *SnailModeMap) PreUpdateBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
m.lastTailPositions = make(map[rules.Point]int)
for _, snake := range lastBoardState.Snakes {
if isEliminated(&snake) {
continue
}
// Double tail means that the tail will stay on the same square for more
// than one turn, so we don't want to spawn hazards
if doubleTail(&snake) {
continue
}
m.lastTailPositions[snake.Body[len(snake.Body)-1]] = len(snake.Body)
}
return nil return nil
} }
// PostUpdateBoard does the work of placing the hazards along the 'snail tail' of snakes // PostUpdateBoard does the work of placing the hazards along the 'snail tail' of snakes
// This also handles removing one hazards from the current stacks so the hazards tails fade as the snake moves away. // This is responsible for saving the current tail location off the board
func (m *SnailModeMap) PostUpdateBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error { // and restoring the previous tail position. This also handles removing one hazards from
// the current stacks so the hazards tails fade as the snake moves away.
func (m SnailModeMap) PostUpdateBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
err := StandardMap{}.PostUpdateBoard(lastBoardState, settings, editor) err := StandardMap{}.PostUpdateBoard(lastBoardState, settings, editor)
if err != nil { if err != nil {
return err return err
@ -101,38 +104,79 @@ func (m *SnailModeMap) PostUpdateBoard(lastBoardState *rules.BoardState, setting
// need to be cleared first. // need to be cleared first.
editor.ClearHazards() editor.ClearHazards()
// This is a list of all the hazards we want to add for the previous tails
// These were stored off board in the previous turn as a way to save state
// When we add the locations to this list we have already converted the off-board
// points to on-board points
tailLocations := make([]rules.Point, 0, len(lastBoardState.Snakes))
// Count the number of hazards for a given position // Count the number of hazards for a given position
// Add non-double tail locations to a slice
hazardCounts := map[rules.Point]int{} hazardCounts := map[rules.Point]int{}
for _, hazard := range lastBoardState.Hazards { for _, hazard := range lastBoardState.Hazards {
hazardCounts[hazard]++
// discard out of bound
if outOfBounds(hazard, lastBoardState.Width, lastBoardState.Height) {
onBoardTail := getPrevTailLocation(hazard, lastBoardState.Height)
tailLocations = append(tailLocations, onBoardTail)
} else {
hazardCounts[hazard]++
}
} }
// Add back existing hazards, but with a stack of 1 less than before. // Add back existing hazards, but with a stack of 1 less than before.
// This has the effect of making the snail-trail disappear over time. // This has the effect of making the snail-trail disappear over time.
for hazard, count := range hazardCounts { for hazard, count := range hazardCounts {
for i := 0; i < count-1; i++ { for i := 0; i < count-1; i++ {
editor.AddHazard(hazard) editor.AddHazard(hazard)
} }
} }
// Place a new stack of hazards where each snake's tail used to be // Store a stack of hazards for the tail of each snake. This is stored out
NewHazardLoop: // of bounds and then applied on the next turn. The stack count is equal
for location, count := range m.lastTailPositions { // the lenght of the snake.
for _, snake := range lastBoardState.Snakes {
if isEliminated(&snake) {
continue
}
// Double tail means that the tail will stay on the same square for more
// than one turn, so we don't want to spawn hazards
if doubleTail(&snake) {
continue
}
tail := snake.Body[len(snake.Body)-1]
offBoardTail := storeTailLocation(tail, lastBoardState.Height)
for i := 0; i < len(snake.Body); i++ {
editor.AddHazard(offBoardTail)
}
}
// Read offboard tails and move them to the board. The offboard tails are
// stacked based on the length of the snake
for _, p := range tailLocations {
// Skip position if a snakes head occupies it.
// Otherwise hazard shows up in the viewer on top of a snake head, but
// does not damage the snake, which is visually confusing.
isHead := false
for _, snake := range lastBoardState.Snakes { for _, snake := range lastBoardState.Snakes {
if isEliminated(&snake) { if isEliminated(&snake) {
continue continue
} }
head := snake.Body[0] head := snake.Body[0]
if location.X == head.X && location.Y == head.Y { if p.X == head.X && p.Y == head.Y {
// Skip position if a snakes head occupies it. isHead = true
// Otherwise hazard shows up in the viewer on top of a snake head, but break
// does not damage the snake, which is visually confusing.
continue NewHazardLoop
} }
} }
for i := 0; i < count; i++ { if isHead {
editor.AddHazard(location) continue
} }
editor.AddHazard(p)
} }
return nil return nil