Create standard.go
This commit is contained in:
parent
d75012f627
commit
321b67239a
1 changed files with 196 additions and 0 deletions
196
standard.go
Normal file
196
standard.go
Normal file
|
|
@ -0,0 +1,196 @@
|
||||||
|
package rulesets
|
||||||
|
|
||||||
|
import (
|
||||||
|
// log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type StandardRuleset struct{}
|
||||||
|
|
||||||
|
const (
|
||||||
|
SNAKE_MAX_HEALTH = 100
|
||||||
|
// bvanvugt - TODO: Just return formatted strings instead of codes?
|
||||||
|
ELIMINATED_COLLISION = "snake-collision"
|
||||||
|
ELIMINATED_SELF_COLLISION = "snake-self-collision"
|
||||||
|
ELIMINATED_STARVATION = "starvation"
|
||||||
|
ELIMINATED_HEAD_TO_HEAD = "head-collision"
|
||||||
|
ELIMINATED_OUT_OF_BOUNDS = "wall-collision"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *StandardRuleset) ResolveMoves(g *Game, prevGameState *GameState, moves []*SnakeMove) (nextGameState *GameState, err error) {
|
||||||
|
nextGameState = &GameState{
|
||||||
|
Snakes: prevGameState.Snakes,
|
||||||
|
Food: prevGameState.Food,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Gut check the GameState?
|
||||||
|
|
||||||
|
// TODO: LOG?
|
||||||
|
err = r.moveSnakes(nextGameState, moves)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: LOG?
|
||||||
|
err = r.reduceSnakeHealth(nextGameState)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: LOG?
|
||||||
|
// bvanvugt: we specifically want this to happen before elimination
|
||||||
|
// so that head-to-head collisions on food still remove the food.
|
||||||
|
err = r.feedSnakes(nextGameState)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: LOG?
|
||||||
|
err = r.eliminateSnakes(nextGameState, g.Width, g.Height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: LOG?
|
||||||
|
err = r.maybeSpawnFood(nextGameState)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextGameState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) moveSnakes(gs *GameState, moves []*SnakeMove) error {
|
||||||
|
for _, move := range moves {
|
||||||
|
var newHead = &Point{}
|
||||||
|
switch move.Move {
|
||||||
|
case MOVE_DOWN:
|
||||||
|
newHead.X = move.Snake.Body[0].X
|
||||||
|
newHead.Y = move.Snake.Body[0].Y + 1
|
||||||
|
case MOVE_LEFT:
|
||||||
|
newHead.X = move.Snake.Body[0].X - 1
|
||||||
|
newHead.Y = move.Snake.Body[0].Y
|
||||||
|
case MOVE_RIGHT:
|
||||||
|
newHead.X = move.Snake.Body[0].X + 1
|
||||||
|
newHead.Y = move.Snake.Body[0].Y
|
||||||
|
case MOVE_UP:
|
||||||
|
newHead.X = move.Snake.Body[0].X
|
||||||
|
newHead.Y = move.Snake.Body[0].Y - 1
|
||||||
|
default:
|
||||||
|
// Default to UP
|
||||||
|
var dX int32 = 0
|
||||||
|
var dY int32 = -1
|
||||||
|
// If neck is available, use neck to determine last direction
|
||||||
|
if len(move.Snake.Body) >= 2 {
|
||||||
|
dX = move.Snake.Body[0].X - move.Snake.Body[1].X
|
||||||
|
dY = move.Snake.Body[0].Y - move.Snake.Body[1].Y
|
||||||
|
if dX == 0 && dY == 0 {
|
||||||
|
dY = -1 // Move up if no last move was made
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Apply
|
||||||
|
newHead.X = move.Snake.Body[0].X + dX
|
||||||
|
newHead.Y = move.Snake.Body[0].Y + dY
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append new head, pop old tail
|
||||||
|
move.Snake.Body = append([]*Point{newHead}, move.Snake.Body[:len(move.Snake.Body)-1]...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) reduceSnakeHealth(gs *GameState) error {
|
||||||
|
for _, snake := range gs.Snakes {
|
||||||
|
snake.Health = snake.Health - 1
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) eliminateSnakes(gs *GameState, boardWidth int32, boardHeight int32) error {
|
||||||
|
for _, snake := range gs.Snakes {
|
||||||
|
if r.snakeHasStarved(snake) {
|
||||||
|
snake.EliminatedCause = ELIMINATED_STARVATION
|
||||||
|
} else if r.snakeIsOutOfBounds(snake, boardWidth, boardHeight) {
|
||||||
|
snake.EliminatedCause = ELIMINATED_OUT_OF_BOUNDS
|
||||||
|
} else {
|
||||||
|
for _, other := range gs.Snakes {
|
||||||
|
if r.snakeHasBodyCollided(snake, other) {
|
||||||
|
if snake.ID == other.ID {
|
||||||
|
snake.EliminatedCause = ELIMINATED_SELF_COLLISION
|
||||||
|
} else {
|
||||||
|
snake.EliminatedCause = ELIMINATED_COLLISION
|
||||||
|
}
|
||||||
|
break
|
||||||
|
} else if r.snakeHasLostHeadToHead(snake, other) {
|
||||||
|
snake.EliminatedCause = ELIMINATED_HEAD_TO_HEAD
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) snakeHasStarved(s *Snake) bool {
|
||||||
|
return s.Health <= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) snakeIsOutOfBounds(s *Snake, boardWidth int32, boardHeight int32) bool {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) snakeHasBodyCollided(s *Snake, other *Snake) bool {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) snakeHasLostHeadToHead(s *Snake, other *Snake) bool {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) feedSnakes(gs *GameState) error {
|
||||||
|
var newFood []*Point
|
||||||
|
var tail *Point
|
||||||
|
|
||||||
|
for _, food := range gs.Food {
|
||||||
|
foodHasBeenEaten := false
|
||||||
|
for _, snake := range gs.Snakes {
|
||||||
|
if snake.Body[0].X == food.X && snake.Body[0].Y == food.Y {
|
||||||
|
foodHasBeenEaten = true
|
||||||
|
// Update snake
|
||||||
|
snake.Health = SNAKE_MAX_HEALTH
|
||||||
|
tail = snake.Body[len(snake.Body)-1]
|
||||||
|
snake.Body = append(snake.Body, &Point{X: tail.X, Y: tail.Y})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Persist food to next GameState if not eaten
|
||||||
|
if !foodHasBeenEaten {
|
||||||
|
newFood = append(newFood, food)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gs.Food = newFood
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) maybeSpawnFood(gs *GameState) error {
|
||||||
|
// TODO
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue