2020-01-05 17:08:05 -08:00
|
|
|
package rules
|
2019-12-31 20:43:23 -08:00
|
|
|
|
|
|
|
|
import (
|
2020-01-01 17:23:01 -08:00
|
|
|
"math/rand"
|
2020-02-19 11:44:48 -08:00
|
|
|
"sort"
|
2019-12-31 20:43:23 -08:00
|
|
|
)
|
|
|
|
|
|
2020-11-20 12:00:58 -08:00
|
|
|
type StandardRuleset struct {
|
2022-05-25 11:17:41 -07:00
|
|
|
FoodSpawnChance int // [0, 100]
|
|
|
|
|
MinimumFood int
|
|
|
|
|
HazardDamagePerTurn int
|
2022-03-16 16:58:05 -07:00
|
|
|
HazardMap string // optional
|
|
|
|
|
HazardMapAuthor string // optional
|
2020-11-20 12:00:58 -08:00
|
|
|
}
|
2019-12-31 20:43:23 -08:00
|
|
|
|
2022-04-19 15:52:57 -07:00
|
|
|
var standardRulesetStages = []string{
|
2022-06-01 15:21:27 -07:00
|
|
|
StageGameOverStandard,
|
2022-04-19 15:52:57 -07:00
|
|
|
StageMovementStandard,
|
|
|
|
|
StageStarvationStandard,
|
|
|
|
|
StageHazardDamageStandard,
|
|
|
|
|
StageFeedSnakesStandard,
|
|
|
|
|
StageEliminationStandard,
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 16:58:05 -07:00
|
|
|
func (r *StandardRuleset) Name() string { return GameTypeStandard }
|
2021-07-02 20:00:19 -07:00
|
|
|
|
2021-08-23 17:13:58 -07:00
|
|
|
func (r *StandardRuleset) ModifyInitialBoardState(initialState *BoardState) (*BoardState, error) {
|
|
|
|
|
// No-op
|
|
|
|
|
return initialState, nil
|
2020-01-01 17:23:01 -08:00
|
|
|
}
|
|
|
|
|
|
2022-04-19 15:52:57 -07:00
|
|
|
// impl Pipeline
|
|
|
|
|
func (r StandardRuleset) Execute(bs *BoardState, s Settings, sm []SnakeMove) (bool, *BoardState, error) {
|
|
|
|
|
return NewPipeline(standardRulesetStages...).Execute(bs, s, sm)
|
2019-12-31 20:43:23 -08:00
|
|
|
}
|
|
|
|
|
|
2022-04-19 15:52:57 -07:00
|
|
|
func (r *StandardRuleset) CreateNextBoardState(prevState *BoardState, moves []SnakeMove) (*BoardState, error) {
|
|
|
|
|
_, nextState, err := r.Execute(prevState, r.Settings(), moves)
|
|
|
|
|
return nextState, err
|
2022-03-16 16:58:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func MoveSnakesStandard(b *BoardState, settings Settings, moves []SnakeMove) (bool, error) {
|
2022-04-19 15:52:57 -07:00
|
|
|
if IsInitialization(b, settings, moves) {
|
|
|
|
|
return false, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// no-op when moves are empty
|
2022-03-16 16:58:05 -07:00
|
|
|
if len(moves) == 0 {
|
|
|
|
|
return false, nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-10 11:49:41 -07:00
|
|
|
// Sanity check that all non-eliminated snakes have moves and bodies.
|
2020-05-28 19:26:44 +01:00
|
|
|
for i := 0; i < len(b.Snakes); i++ {
|
2020-09-10 11:49:41 -07:00
|
|
|
snake := &b.Snakes[i]
|
|
|
|
|
if snake.EliminatedCause != NotEliminated {
|
|
|
|
|
continue
|
2020-05-28 19:26:44 +01:00
|
|
|
}
|
2020-01-05 21:03:54 -08:00
|
|
|
|
2020-09-10 11:49:41 -07:00
|
|
|
if len(snake.Body) == 0 {
|
2022-03-16 16:58:05 -07:00
|
|
|
return false, ErrorZeroLengthSnake
|
2020-09-10 11:49:41 -07:00
|
|
|
}
|
|
|
|
|
moveFound := false
|
|
|
|
|
for _, move := range moves {
|
|
|
|
|
if snake.ID == move.ID {
|
|
|
|
|
moveFound = true
|
|
|
|
|
break
|
2020-01-02 16:10:33 -08:00
|
|
|
}
|
|
|
|
|
}
|
2020-09-10 11:49:41 -07:00
|
|
|
if !moveFound {
|
2022-03-16 16:58:05 -07:00
|
|
|
return false, ErrorNoMoveFound
|
2020-01-05 21:03:54 -08:00
|
|
|
}
|
2020-09-10 11:49:41 -07:00
|
|
|
}
|
2020-01-02 16:10:33 -08:00
|
|
|
|
2020-09-10 11:49:41 -07:00
|
|
|
for i := 0; i < len(b.Snakes); i++ {
|
|
|
|
|
snake := &b.Snakes[i]
|
2020-01-03 12:56:33 -08:00
|
|
|
if snake.EliminatedCause != NotEliminated {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-10 11:49:41 -07:00
|
|
|
for _, move := range moves {
|
|
|
|
|
if move.ID == snake.ID {
|
2022-01-11 22:44:37 +00:00
|
|
|
appliedMove := move.Move
|
2020-09-10 11:49:41 -07:00
|
|
|
switch move.Move {
|
2022-01-11 22:44:37 +00:00
|
|
|
case MoveUp, MoveDown, MoveRight, MoveLeft:
|
|
|
|
|
break
|
|
|
|
|
default:
|
2022-03-16 16:58:05 -07:00
|
|
|
appliedMove = getDefaultMove(snake.Body)
|
2022-01-11 22:44:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
newHead := Point{}
|
|
|
|
|
switch appliedMove {
|
|
|
|
|
// Guaranteed to be one of these options given the clause above
|
|
|
|
|
case MoveUp:
|
|
|
|
|
newHead.X = snake.Body[0].X
|
|
|
|
|
newHead.Y = snake.Body[0].Y + 1
|
2020-09-10 11:49:41 -07:00
|
|
|
case MoveDown:
|
|
|
|
|
newHead.X = snake.Body[0].X
|
2021-01-19 15:33:05 -08:00
|
|
|
newHead.Y = snake.Body[0].Y - 1
|
2020-09-10 11:49:41 -07:00
|
|
|
case MoveLeft:
|
|
|
|
|
newHead.X = snake.Body[0].X - 1
|
|
|
|
|
newHead.Y = snake.Body[0].Y
|
|
|
|
|
case MoveRight:
|
|
|
|
|
newHead.X = snake.Body[0].X + 1
|
|
|
|
|
newHead.Y = snake.Body[0].Y
|
2019-12-31 20:43:23 -08:00
|
|
|
}
|
2020-09-10 11:49:41 -07:00
|
|
|
|
|
|
|
|
// Append new head, pop old tail
|
|
|
|
|
snake.Body = append([]Point{newHead}, snake.Body[:len(snake.Body)-1]...)
|
2019-12-31 20:43:23 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-03-16 16:58:05 -07:00
|
|
|
return false, nil
|
2019-12-31 20:43:23 -08:00
|
|
|
}
|
|
|
|
|
|
2022-03-16 16:58:05 -07:00
|
|
|
func getDefaultMove(snakeBody []Point) string {
|
2022-01-11 22:44:37 +00:00
|
|
|
if len(snakeBody) >= 2 {
|
|
|
|
|
// Use neck to determine last move made
|
|
|
|
|
head, neck := snakeBody[0], snakeBody[1]
|
|
|
|
|
// Situations where neck is next to head
|
|
|
|
|
if head.X == neck.X+1 {
|
|
|
|
|
return MoveRight
|
|
|
|
|
} else if head.X == neck.X-1 {
|
|
|
|
|
return MoveLeft
|
|
|
|
|
} else if head.Y == neck.Y+1 {
|
|
|
|
|
return MoveUp
|
|
|
|
|
} else if head.Y == neck.Y-1 {
|
|
|
|
|
return MoveDown
|
|
|
|
|
}
|
|
|
|
|
// Consider the wrapped cases using zero axis to anchor
|
|
|
|
|
if head.X == 0 && neck.X > 0 {
|
|
|
|
|
return MoveRight
|
|
|
|
|
} else if neck.X == 0 && head.X > 0 {
|
|
|
|
|
return MoveLeft
|
|
|
|
|
} else if head.Y == 0 && neck.Y > 0 {
|
|
|
|
|
return MoveUp
|
|
|
|
|
} else if neck.Y == 0 && head.Y > 0 {
|
|
|
|
|
return MoveDown
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return MoveUp
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 16:58:05 -07:00
|
|
|
func ReduceSnakeHealthStandard(b *BoardState, settings Settings, moves []SnakeMove) (bool, error) {
|
2022-04-19 15:52:57 -07:00
|
|
|
if IsInitialization(b, settings, moves) {
|
|
|
|
|
return false, nil
|
|
|
|
|
}
|
2020-01-02 16:10:33 -08:00
|
|
|
for i := 0; i < len(b.Snakes); i++ {
|
2020-01-03 12:56:33 -08:00
|
|
|
if b.Snakes[i].EliminatedCause == NotEliminated {
|
|
|
|
|
b.Snakes[i].Health = b.Snakes[i].Health - 1
|
|
|
|
|
}
|
2019-12-31 20:43:23 -08:00
|
|
|
}
|
2022-03-16 16:58:05 -07:00
|
|
|
return false, nil
|
2019-12-31 20:43:23 -08:00
|
|
|
}
|
|
|
|
|
|
2022-03-16 16:58:05 -07:00
|
|
|
func DamageHazardsStandard(b *BoardState, settings Settings, moves []SnakeMove) (bool, error) {
|
2022-04-19 15:52:57 -07:00
|
|
|
if IsInitialization(b, settings, moves) {
|
|
|
|
|
return false, nil
|
|
|
|
|
}
|
2021-08-17 16:47:06 -07:00
|
|
|
for i := 0; i < len(b.Snakes); i++ {
|
|
|
|
|
snake := &b.Snakes[i]
|
|
|
|
|
if snake.EliminatedCause != NotEliminated {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
head := snake.Body[0]
|
|
|
|
|
for _, p := range b.Hazards {
|
|
|
|
|
if head == p {
|
|
|
|
|
// If there's a food in this square, don't reduce health
|
|
|
|
|
foundFood := false
|
|
|
|
|
for _, food := range b.Food {
|
|
|
|
|
if p == food {
|
|
|
|
|
foundFood = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if foundFood {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Snake is in a hazard, reduce health
|
2022-03-16 16:58:05 -07:00
|
|
|
snake.Health = snake.Health - settings.HazardDamagePerTurn
|
2021-08-17 16:47:06 -07:00
|
|
|
if snake.Health < 0 {
|
|
|
|
|
snake.Health = 0
|
|
|
|
|
}
|
2022-03-16 16:58:05 -07:00
|
|
|
if snakeIsOutOfHealth(snake) {
|
2021-08-17 16:47:06 -07:00
|
|
|
snake.EliminatedCause = EliminatedByOutOfHealth
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 16:58:05 -07:00
|
|
|
return false, nil
|
2021-08-17 16:47:06 -07:00
|
|
|
}
|
|
|
|
|
|
2022-03-16 16:58:05 -07:00
|
|
|
func EliminateSnakesStandard(b *BoardState, settings Settings, moves []SnakeMove) (bool, error) {
|
2022-04-19 15:52:57 -07:00
|
|
|
if IsInitialization(b, settings, moves) {
|
|
|
|
|
return false, nil
|
|
|
|
|
}
|
2020-02-19 11:44:48 -08:00
|
|
|
// First order snake indices by length.
|
|
|
|
|
// In multi-collision scenarios we want to always attribute elimination to the longest snake.
|
|
|
|
|
snakeIndicesByLength := make([]int, len(b.Snakes))
|
|
|
|
|
for i := 0; i < len(b.Snakes); i++ {
|
|
|
|
|
snakeIndicesByLength[i] = i
|
|
|
|
|
}
|
|
|
|
|
sort.Slice(snakeIndicesByLength, func(i int, j int) bool {
|
|
|
|
|
lenI := len(b.Snakes[snakeIndicesByLength[i]].Body)
|
|
|
|
|
lenJ := len(b.Snakes[snakeIndicesByLength[j]].Body)
|
|
|
|
|
return lenI > lenJ
|
|
|
|
|
})
|
|
|
|
|
|
2020-09-10 11:49:41 -07:00
|
|
|
// First, iterate over all non-eliminated snakes and eliminate the ones
|
|
|
|
|
// that are out of health or have moved out of bounds.
|
2020-01-03 11:39:19 -08:00
|
|
|
for i := 0; i < len(b.Snakes); i++ {
|
|
|
|
|
snake := &b.Snakes[i]
|
2020-09-10 11:49:41 -07:00
|
|
|
if snake.EliminatedCause != NotEliminated {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2020-01-03 11:39:19 -08:00
|
|
|
if len(snake.Body) <= 0 {
|
2022-03-16 16:58:05 -07:00
|
|
|
return false, ErrorZeroLengthSnake
|
2020-01-03 11:39:19 -08:00
|
|
|
}
|
|
|
|
|
|
2022-03-16 16:58:05 -07:00
|
|
|
if snakeIsOutOfHealth(snake) {
|
2020-09-10 12:00:56 -07:00
|
|
|
snake.EliminatedCause = EliminatedByOutOfHealth
|
2020-01-03 11:39:19 -08:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 16:58:05 -07:00
|
|
|
if snakeIsOutOfBounds(snake, b.Width, b.Height) {
|
2020-01-02 16:10:33 -08:00
|
|
|
snake.EliminatedCause = EliminatedByOutOfBounds
|
2020-01-03 11:39:19 -08:00
|
|
|
continue
|
|
|
|
|
}
|
2020-09-10 11:49:41 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Next, look for any collisions. Note we apply collision eliminations
|
|
|
|
|
// after this check so that snakes can collide with each other and be properly eliminated.
|
|
|
|
|
type CollisionElimination struct {
|
|
|
|
|
ID string
|
|
|
|
|
Cause string
|
|
|
|
|
By string
|
|
|
|
|
}
|
|
|
|
|
collisionEliminations := []CollisionElimination{}
|
|
|
|
|
for i := 0; i < len(b.Snakes); i++ {
|
|
|
|
|
snake := &b.Snakes[i]
|
|
|
|
|
if snake.EliminatedCause != NotEliminated {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if len(snake.Body) <= 0 {
|
2022-03-16 16:58:05 -07:00
|
|
|
return false, ErrorZeroLengthSnake
|
2020-09-10 11:49:41 -07:00
|
|
|
}
|
2020-01-03 11:39:19 -08:00
|
|
|
|
2020-07-21 17:11:12 -07:00
|
|
|
// Check for self-collisions first
|
2022-03-16 16:58:05 -07:00
|
|
|
if snakeHasBodyCollided(snake, snake) {
|
2020-09-10 11:49:41 -07:00
|
|
|
collisionEliminations = append(collisionEliminations, CollisionElimination{
|
|
|
|
|
ID: snake.ID,
|
|
|
|
|
Cause: EliminatedBySelfCollision,
|
|
|
|
|
By: snake.ID,
|
|
|
|
|
})
|
2020-07-21 17:11:12 -07:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check for body collisions with other snakes second
|
2020-09-10 11:49:41 -07:00
|
|
|
hasBodyCollided := false
|
2020-02-19 11:44:48 -08:00
|
|
|
for _, otherIndex := range snakeIndicesByLength {
|
|
|
|
|
other := &b.Snakes[otherIndex]
|
2020-09-10 11:49:41 -07:00
|
|
|
if other.EliminatedCause != NotEliminated {
|
2020-07-21 17:11:12 -07:00
|
|
|
continue
|
|
|
|
|
}
|
2022-03-16 16:58:05 -07:00
|
|
|
if snake.ID != other.ID && snakeHasBodyCollided(snake, other) {
|
2020-09-10 11:49:41 -07:00
|
|
|
collisionEliminations = append(collisionEliminations, CollisionElimination{
|
|
|
|
|
ID: snake.ID,
|
|
|
|
|
Cause: EliminatedByCollision,
|
|
|
|
|
By: other.ID,
|
|
|
|
|
})
|
|
|
|
|
hasBodyCollided = true
|
2020-01-03 11:39:19 -08:00
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-09-10 11:49:41 -07:00
|
|
|
if hasBodyCollided {
|
2020-01-03 11:39:19 -08:00
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-21 17:11:12 -07:00
|
|
|
// Check for head-to-heads last
|
2020-09-10 11:49:41 -07:00
|
|
|
hasHeadCollided := false
|
2020-02-19 11:44:48 -08:00
|
|
|
for _, otherIndex := range snakeIndicesByLength {
|
|
|
|
|
other := &b.Snakes[otherIndex]
|
2020-09-10 11:49:41 -07:00
|
|
|
if other.EliminatedCause != NotEliminated {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2022-03-16 16:58:05 -07:00
|
|
|
if snake.ID != other.ID && snakeHasLostHeadToHead(snake, other) {
|
2020-09-10 11:49:41 -07:00
|
|
|
collisionEliminations = append(collisionEliminations, CollisionElimination{
|
|
|
|
|
ID: snake.ID,
|
|
|
|
|
Cause: EliminatedByHeadToHeadCollision,
|
|
|
|
|
By: other.ID,
|
|
|
|
|
})
|
|
|
|
|
hasHeadCollided = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if hasHeadCollided {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply collision eliminations
|
|
|
|
|
for _, elimination := range collisionEliminations {
|
|
|
|
|
for i := 0; i < len(b.Snakes); i++ {
|
|
|
|
|
snake := &b.Snakes[i]
|
|
|
|
|
if snake.ID == elimination.ID {
|
|
|
|
|
snake.EliminatedCause = elimination.Cause
|
|
|
|
|
snake.EliminatedBy = elimination.By
|
2020-01-03 11:39:19 -08:00
|
|
|
break
|
2019-12-31 20:43:23 -08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-09-10 11:49:41 -07:00
|
|
|
|
2022-03-16 16:58:05 -07:00
|
|
|
return false, nil
|
2019-12-31 20:43:23 -08:00
|
|
|
}
|
|
|
|
|
|
2022-03-16 16:58:05 -07:00
|
|
|
func snakeIsOutOfHealth(s *Snake) bool {
|
2019-12-31 20:43:23 -08:00
|
|
|
return s.Health <= 0
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-25 11:17:41 -07:00
|
|
|
func snakeIsOutOfBounds(s *Snake, boardWidth int, boardHeight int) bool {
|
2019-12-31 20:43:23 -08:00
|
|
|
for _, point := range s.Body {
|
|
|
|
|
if (point.X < 0) || (point.X >= boardWidth) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if (point.Y < 0) || (point.Y >= boardHeight) {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 16:58:05 -07:00
|
|
|
func snakeHasBodyCollided(s *Snake, other *Snake) bool {
|
2019-12-31 20:43:23 -08:00
|
|
|
head := s.Body[0]
|
|
|
|
|
for i, body := range other.Body {
|
|
|
|
|
if i == 0 {
|
|
|
|
|
continue
|
|
|
|
|
} else if head.X == body.X && head.Y == body.Y {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 16:58:05 -07:00
|
|
|
func snakeHasLostHeadToHead(s *Snake, other *Snake) bool {
|
2019-12-31 20:43:23 -08:00
|
|
|
if s.Body[0].X == other.Body[0].X && s.Body[0].Y == other.Body[0].Y {
|
|
|
|
|
return len(s.Body) <= len(other.Body)
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 16:58:05 -07:00
|
|
|
func FeedSnakesStandard(b *BoardState, settings Settings, moves []SnakeMove) (bool, error) {
|
2020-01-03 12:56:33 -08:00
|
|
|
newFood := []Point{}
|
2020-01-01 17:23:01 -08:00
|
|
|
for _, food := range b.Food {
|
2019-12-31 20:43:23 -08:00
|
|
|
foodHasBeenEaten := false
|
2020-01-03 12:56:33 -08:00
|
|
|
for i := 0; i < len(b.Snakes); i++ {
|
|
|
|
|
snake := &b.Snakes[i]
|
|
|
|
|
|
|
|
|
|
// Ignore eliminated and zero-length snakes, they can't eat.
|
|
|
|
|
if snake.EliminatedCause != NotEliminated || len(snake.Body) == 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-31 20:43:23 -08:00
|
|
|
if snake.Body[0].X == food.X && snake.Body[0].Y == food.Y {
|
2022-03-16 16:58:05 -07:00
|
|
|
feedSnake(snake)
|
2019-12-31 20:43:23 -08:00
|
|
|
foodHasBeenEaten = true
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-01-01 17:23:01 -08:00
|
|
|
// Persist food to next BoardState if not eaten
|
2019-12-31 20:43:23 -08:00
|
|
|
if !foodHasBeenEaten {
|
|
|
|
|
newFood = append(newFood, food)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-01-01 17:23:01 -08:00
|
|
|
b.Food = newFood
|
2022-03-16 16:58:05 -07:00
|
|
|
return false, nil
|
2019-12-31 20:43:23 -08:00
|
|
|
}
|
|
|
|
|
|
2022-03-16 16:58:05 -07:00
|
|
|
func feedSnake(snake *Snake) {
|
|
|
|
|
growSnake(snake)
|
2020-02-20 10:24:44 -08:00
|
|
|
snake.Health = SnakeMaxHealth
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-16 16:58:05 -07:00
|
|
|
func growSnake(snake *Snake) {
|
2020-02-20 10:24:44 -08:00
|
|
|
if len(snake.Body) > 0 {
|
|
|
|
|
snake.Body = append(snake.Body, snake.Body[len(snake.Body)-1])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-25 11:24:27 -07:00
|
|
|
// Deprecated: handled by maps.Standard
|
2022-03-16 16:58:05 -07:00
|
|
|
func SpawnFoodStandard(b *BoardState, settings Settings, moves []SnakeMove) (bool, error) {
|
2022-04-19 15:52:57 -07:00
|
|
|
if IsInitialization(b, settings, moves) {
|
|
|
|
|
return false, nil
|
|
|
|
|
}
|
2022-05-25 11:17:41 -07:00
|
|
|
numCurrentFood := int(len(b.Food))
|
2022-03-16 16:58:05 -07:00
|
|
|
if numCurrentFood < settings.MinimumFood {
|
2022-05-11 08:26:28 -07:00
|
|
|
return false, PlaceFoodRandomly(GlobalRand, b, settings.MinimumFood-numCurrentFood)
|
2020-01-01 17:23:01 -08:00
|
|
|
}
|
2022-05-25 11:17:41 -07:00
|
|
|
if settings.FoodSpawnChance > 0 && int(rand.Intn(100)) < settings.FoodSpawnChance {
|
2022-05-11 08:26:28 -07:00
|
|
|
return false, PlaceFoodRandomly(GlobalRand, b, 1)
|
2022-03-16 16:58:05 -07:00
|
|
|
}
|
|
|
|
|
return false, nil
|
2019-12-31 20:43:23 -08:00
|
|
|
}
|
2020-01-01 17:23:01 -08:00
|
|
|
|
2020-05-17 14:22:09 -07:00
|
|
|
func (r *StandardRuleset) IsGameOver(b *BoardState) (bool, error) {
|
2022-04-19 15:52:57 -07:00
|
|
|
return GameOverStandard(b, r.Settings(), nil)
|
2022-03-16 16:58:05 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func GameOverStandard(b *BoardState, settings Settings, moves []SnakeMove) (bool, error) {
|
2020-05-17 14:22:09 -07:00
|
|
|
numSnakesRemaining := 0
|
|
|
|
|
for i := 0; i < len(b.Snakes); i++ {
|
|
|
|
|
if b.Snakes[i].EliminatedCause == NotEliminated {
|
|
|
|
|
numSnakesRemaining++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return numSnakesRemaining <= 1, nil
|
|
|
|
|
}
|
2022-03-16 16:58:05 -07:00
|
|
|
|
|
|
|
|
func (r StandardRuleset) Settings() Settings {
|
|
|
|
|
return Settings{
|
|
|
|
|
FoodSpawnChance: r.FoodSpawnChance,
|
|
|
|
|
MinimumFood: r.MinimumFood,
|
|
|
|
|
HazardDamagePerTurn: r.HazardDamagePerTurn,
|
|
|
|
|
HazardMap: r.HazardMap,
|
|
|
|
|
HazardMapAuthor: r.HazardMapAuthor,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-19 15:52:57 -07:00
|
|
|
// impl Pipeline
|
|
|
|
|
func (r StandardRuleset) Err() error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// IsInitialization checks whether the current state means the game is initialising.
|
|
|
|
|
func IsInitialization(b *BoardState, settings Settings, moves []SnakeMove) bool {
|
|
|
|
|
// We can safely assume that the game state is in the initialisation phase when
|
|
|
|
|
// the turn hasn't advanced and the moves are empty
|
|
|
|
|
return b.Turn <= 0 && len(moves) == 0
|
2022-03-16 16:58:05 -07:00
|
|
|
}
|