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
|
|
@ -4,20 +4,22 @@ import (
|
|||
"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.
|
||||
func init() {
|
||||
globalRegistry.RegisterMap("snail_mode", SnailModeMap{})
|
||||
globalRegistry.RegisterMap("snail_mode", &SnailModeMap{lastTailPositions: nil})
|
||||
}
|
||||
|
||||
// ID returns a unique identifier for this map.
|
||||
func (m SnailModeMap) ID() string {
|
||||
func (m *SnailModeMap) ID() string {
|
||||
return "snail_mode"
|
||||
}
|
||||
|
||||
// Meta returns the non-functional metadata about this map.
|
||||
func (m SnailModeMap) Meta() Metadata {
|
||||
func (m *SnailModeMap) Meta() Metadata {
|
||||
return Metadata{
|
||||
Name: "Snail Mode",
|
||||
Description: "Snakes leave behind a trail of hazards",
|
||||
|
|
@ -31,7 +33,7 @@ func (m SnailModeMap) Meta() Metadata {
|
|||
}
|
||||
|
||||
// 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)
|
||||
|
||||
if len(initialBoardState.Snakes) > int(m.Meta().MaxPlayers) {
|
||||
|
|
@ -57,23 +59,6 @@ func (m SnailModeMap) SetupBoard(initialBoardState *rules.BoardState, settings r
|
|||
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
|
||||
func doubleTail(snake *rules.Snake) bool {
|
||||
almostTail := snake.Body[len(snake.Body)-2]
|
||||
|
|
@ -86,12 +71,28 @@ func isEliminated(s *rules.Snake) bool {
|
|||
return s.EliminatedCause != rules.NotEliminated
|
||||
}
|
||||
|
||||
// UpdateBoard does the work of placing the hazards along the 'snail tail' of snakes
|
||||
// This is responsible for saving the current tail location off the board
|
||||
// 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) UpdateBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
|
||||
err := StandardMap{}.UpdateBoard(lastBoardState, settings, editor)
|
||||
// PreUpdateBoard stores the tail position of each snake in memory, to be
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (m *SnailModeMap) PostUpdateBoard(lastBoardState *rules.BoardState, settings rules.Settings, editor Editor) error {
|
||||
err := StandardMap{}.PostUpdateBoard(lastBoardState, settings, editor)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -100,79 +101,38 @@ func (m SnailModeMap) UpdateBoard(lastBoardState *rules.BoardState, settings rul
|
|||
// need to be cleared first.
|
||||
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
|
||||
// Add non-double tail locations to a slice
|
||||
hazardCounts := map[rules.Point]int{}
|
||||
for _, hazard := range lastBoardState.Hazards {
|
||||
|
||||
// discard out of bound
|
||||
if outOfBounds(hazard, lastBoardState.Width, lastBoardState.Height) {
|
||||
onBoardTail := getPrevTailLocation(hazard, lastBoardState.Height)
|
||||
tailLocations = append(tailLocations, onBoardTail)
|
||||
} else {
|
||||
hazardCounts[hazard]++
|
||||
}
|
||||
hazardCounts[hazard]++
|
||||
}
|
||||
|
||||
// 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.
|
||||
for hazard, count := range hazardCounts {
|
||||
|
||||
for i := 0; i < count-1; i++ {
|
||||
editor.AddHazard(hazard)
|
||||
}
|
||||
}
|
||||
|
||||
// Store a stack of hazards for the tail of each snake. This is stored out
|
||||
// of bounds and then applied on the next turn. The stack count is equal
|
||||
// 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
|
||||
// Place a new stack of hazards where each snake's tail used to be
|
||||
NewHazardLoop:
|
||||
for location, count := range m.lastTailPositions {
|
||||
for _, snake := range lastBoardState.Snakes {
|
||||
if isEliminated(&snake) {
|
||||
continue
|
||||
}
|
||||
head := snake.Body[0]
|
||||
if p.X == head.X && p.Y == head.Y {
|
||||
isHead = true
|
||||
break
|
||||
if location.X == head.X && location.Y == head.Y {
|
||||
// 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.
|
||||
continue NewHazardLoop
|
||||
}
|
||||
}
|
||||
if isHead {
|
||||
continue
|
||||
for i := 0; i < count; i++ {
|
||||
editor.AddHazard(location)
|
||||
}
|
||||
|
||||
editor.AddHazard(p)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue