Rename consts, remove pointers where not wanted/needed, snake placement tests.
This commit is contained in:
parent
53d57d8e6a
commit
010b3aa08f
3 changed files with 419 additions and 281 deletions
20
ruleset.go
20
ruleset.go
|
|
@ -1,10 +1,10 @@
|
||||||
package rulesets
|
package rulesets
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MOVE_UP = "up"
|
MoveUp = "up"
|
||||||
MOVE_DOWN = "down"
|
MoveDown = "down"
|
||||||
MOVE_RIGHT = "right"
|
MoveRight = "right"
|
||||||
MOVE_LEFT = "left"
|
MoveLeft = "left"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Point struct {
|
type Point struct {
|
||||||
|
|
@ -14,7 +14,7 @@ type Point struct {
|
||||||
|
|
||||||
type Snake struct {
|
type Snake struct {
|
||||||
ID string
|
ID string
|
||||||
Body []*Point
|
Body []Point
|
||||||
Health int32
|
Health int32
|
||||||
EliminatedCause string
|
EliminatedCause string
|
||||||
}
|
}
|
||||||
|
|
@ -22,16 +22,16 @@ type Snake struct {
|
||||||
type BoardState struct {
|
type BoardState struct {
|
||||||
Height int32
|
Height int32
|
||||||
Width int32
|
Width int32
|
||||||
Food []*Point
|
Food []Point
|
||||||
Snakes []*Snake
|
Snakes []Snake
|
||||||
}
|
}
|
||||||
|
|
||||||
type SnakeMove struct {
|
type SnakeMove struct {
|
||||||
Snake *Snake
|
ID string
|
||||||
Move string
|
Move string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Ruleset interface {
|
type Ruleset interface {
|
||||||
CreateInitialBoardState(width int32, height int32, snakeIDs []string) (*BoardState, error)
|
CreateInitialBoardState(width int32, height int32, snakeIDs []string) (*BoardState, error)
|
||||||
ResolveMoves(prevState *BoardState, moves []*SnakeMove) (*BoardState, error)
|
ResolveMoves(prevState *BoardState, moves []SnakeMove) (*BoardState, error)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
218
standard.go
218
standard.go
|
|
@ -8,50 +8,40 @@ import (
|
||||||
type StandardRuleset struct{}
|
type StandardRuleset struct{}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
BOARD_SIZE_SMALL = 7
|
BoardSizeSmall = 7
|
||||||
BOARD_SIZE_MEDIUM = 11
|
BoardSizeMedium = 11
|
||||||
BOARD_SIZE_LARGE = 19
|
BoardSizeLarge = 19
|
||||||
FOOD_SPAWN_CHANCE = 0.1
|
FoodSpawnChance = 0.1
|
||||||
SNAKE_MAX_HEALTH = 100
|
SnakeMaxHealth = 100
|
||||||
|
SnakeStartSize = 3
|
||||||
|
|
||||||
// bvanvugt - TODO: Just return formatted strings instead of codes?
|
// bvanvugt - TODO: Just return formatted strings instead of codes?
|
||||||
ELIMINATED_COLLISION = "snake-collision"
|
EliminatedByColliision = "snake-collision"
|
||||||
ELIMINATED_SELF_COLLISION = "snake-self-collision"
|
EliminatedBySelfColliision = "snake-self-collision"
|
||||||
ELIMINATED_STARVATION = "starvation"
|
EliminatedByStarvation = "starvation"
|
||||||
ELIMINATED_HEAD_TO_HEAD = "head-collision"
|
EliminatedByHeadToHeadCollision = "head-collision"
|
||||||
ELIMINATED_OUT_OF_BOUNDS = "wall-collision"
|
EliminatedByOutOfBounds = "wall-collision"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *StandardRuleset) CreateInitialBoard(width int32, height int32, snakeIDs []string) (*BoardState, error) {
|
func (r *StandardRuleset) CreateInitialBoardState(width int32, height int32, snakeIDs []string) (*BoardState, error) {
|
||||||
var err error
|
|
||||||
|
|
||||||
snakes := []*Snake{}
|
|
||||||
for _, id := range snakeIDs {
|
|
||||||
snakes = append(snakes,
|
|
||||||
&Snake{
|
|
||||||
ID: id,
|
|
||||||
Health: SNAKE_MAX_HEALTH,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
initialBoardState := &BoardState{
|
initialBoardState := &BoardState{
|
||||||
Height: height,
|
Height: height,
|
||||||
Width: width,
|
Width: width,
|
||||||
Snakes: snakes,
|
Snakes: make([]Snake, len(snakeIDs)),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Place Snakes
|
for i := 0; i < len(snakeIDs); i++ {
|
||||||
if r.isKnownBoardSize(initialBoardState) {
|
initialBoardState.Snakes[i] = Snake{
|
||||||
err = r.placeSnakesFixed(initialBoardState)
|
ID: snakeIDs[i],
|
||||||
} else {
|
Health: SnakeMaxHealth,
|
||||||
err = r.placeSnakesRandomly(initialBoardState)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := r.placeSnakes(initialBoardState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Place Food
|
|
||||||
err = r.placeInitialFood(initialBoardState)
|
err = r.placeInitialFood(initialBoardState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -60,23 +50,30 @@ func (r *StandardRuleset) CreateInitialBoard(width int32, height int32, snakeIDs
|
||||||
return initialBoardState, nil
|
return initialBoardState, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) placeSnakesFixed(b *BoardState) error {
|
func (r *StandardRuleset) placeSnakes(b *BoardState) error {
|
||||||
// Sanity check
|
if r.isKnownBoardSize(b) {
|
||||||
if len(b.Snakes) >= 8 {
|
return r.placeSnakesFixed(b)
|
||||||
return errors.New("too many snakes for fixed start positions")
|
|
||||||
}
|
}
|
||||||
|
return r.placeSnakesRandomly(b)
|
||||||
|
}
|
||||||
|
|
||||||
// Create start points
|
func (r *StandardRuleset) placeSnakesFixed(b *BoardState) error {
|
||||||
|
// Create start 8 points
|
||||||
mn, md, mx := int32(1), (b.Width-1)/2, b.Width-2
|
mn, md, mx := int32(1), (b.Width-1)/2, b.Width-2
|
||||||
startPoints := []Point{
|
startPoints := []Point{
|
||||||
{mn, mn},
|
Point{mn, mn},
|
||||||
{mn, md},
|
Point{mn, md},
|
||||||
{mn, mx},
|
Point{mn, mx},
|
||||||
{md, mn},
|
Point{md, mn},
|
||||||
{md, mx},
|
Point{md, mx},
|
||||||
{mx, mn},
|
Point{mx, mn},
|
||||||
{mx, md},
|
Point{mx, md},
|
||||||
{mx, mx},
|
Point{mx, mx},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
if len(b.Snakes) > len(startPoints) {
|
||||||
|
return errors.New("too many snakes for fixed start positions")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Randomly order them
|
// Randomly order them
|
||||||
|
|
@ -85,10 +82,9 @@ func (r *StandardRuleset) placeSnakesFixed(b *BoardState) error {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Assign to snakes in order given
|
// Assign to snakes in order given
|
||||||
for i, snake := range b.Snakes {
|
for i := 0; i < len(b.Snakes); i++ {
|
||||||
p := startPoints[i]
|
for j := 0; j < SnakeStartSize; j++ {
|
||||||
for j := 0; j < 3; j++ {
|
b.Snakes[i].Body = append(b.Snakes[i].Body, startPoints[i])
|
||||||
snake.Body = append(snake.Body, &Point{p.X, p.Y})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -96,24 +92,27 @@ func (r *StandardRuleset) placeSnakesFixed(b *BoardState) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) placeSnakesRandomly(b *BoardState) error {
|
func (r *StandardRuleset) placeSnakesRandomly(b *BoardState) error {
|
||||||
for _, snake := range b.Snakes {
|
for i := 0; i < len(b.Snakes); i++ {
|
||||||
unoccupiedPoints := r.getUnoccupiedPoints(b)
|
unoccupiedPoints := r.getUnoccupiedPoints(b)
|
||||||
|
if len(unoccupiedPoints) < len(b.Snakes)-i {
|
||||||
|
return errors.New("not enough empty squares to place snakes")
|
||||||
|
}
|
||||||
p := unoccupiedPoints[rand.Intn(len(unoccupiedPoints))]
|
p := unoccupiedPoints[rand.Intn(len(unoccupiedPoints))]
|
||||||
for j := 0; j < 3; j++ {
|
for j := 0; j < SnakeStartSize; j++ {
|
||||||
snake.Body = append(snake.Body, &Point{p.X, p.Y})
|
b.Snakes[i].Body = append(b.Snakes[i].Body, p)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) isKnownBoardSize(b *BoardState) bool {
|
func (r *StandardRuleset) isKnownBoardSize(b *BoardState) bool {
|
||||||
if b.Height == BOARD_SIZE_SMALL && b.Width == BOARD_SIZE_SMALL {
|
if b.Height == BoardSizeSmall && b.Width == BoardSizeSmall {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if b.Height == BOARD_SIZE_MEDIUM && b.Width == BOARD_SIZE_MEDIUM {
|
if b.Height == BoardSizeMedium && b.Width == BoardSizeMedium {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
if b.Height == BOARD_SIZE_LARGE && b.Width == BOARD_SIZE_LARGE {
|
if b.Height == BoardSizeLarge && b.Width == BoardSizeLarge {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
|
@ -124,12 +123,18 @@ func (r *StandardRuleset) placeInitialFood(b *BoardState) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) ResolveMoves(prevState *BoardState, moves []*SnakeMove) (*BoardState, error) {
|
func (r *StandardRuleset) ResolveMoves(prevState *BoardState, moves []SnakeMove) (*BoardState, error) {
|
||||||
// TODO: DO NOT REFERENCE prevState directly!!!!
|
// We specifically want to copy prevState, so as not to alter it directly.
|
||||||
// we're technically altering both states
|
|
||||||
nextState := &BoardState{
|
nextState := &BoardState{
|
||||||
Snakes: prevState.Snakes,
|
Height: prevState.Height,
|
||||||
Food: prevState.Food,
|
Width: prevState.Width,
|
||||||
|
Food: append([]Point{}, prevState.Food...),
|
||||||
|
Snakes: make([]Snake, len(prevState.Snakes)),
|
||||||
|
}
|
||||||
|
for i := 0; i < len(prevState.Snakes); i++ {
|
||||||
|
nextState.Snakes[i].ID = prevState.Snakes[i].ID
|
||||||
|
nextState.Snakes[i].Health = prevState.Snakes[i].Health
|
||||||
|
nextState.Snakes[i].Body = append([]Point{}, prevState.Snakes[i].Body...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Gut check the BoardState?
|
// TODO: Gut check the BoardState?
|
||||||
|
|
@ -146,6 +151,12 @@ func (r *StandardRuleset) ResolveMoves(prevState *BoardState, moves []*SnakeMove
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: LOG?
|
||||||
|
err = r.eliminateSnakes(nextState)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
// bvanvugt: we specifically want this to happen before elimination
|
// bvanvugt: we specifically want this to happen before elimination
|
||||||
// so that head-to-head collisions on food still remove the food.
|
// so that head-to-head collisions on food still remove the food.
|
||||||
|
|
@ -164,78 +175,79 @@ func (r *StandardRuleset) ResolveMoves(prevState *BoardState, moves []*SnakeMove
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: LOG?
|
|
||||||
err = r.eliminateSnakes(nextState)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nextState, nil
|
return nextState, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) moveSnakes(b *BoardState, moves []*SnakeMove) error {
|
func (r *StandardRuleset) moveSnakes(b *BoardState, moves []SnakeMove) error {
|
||||||
for _, move := range moves {
|
for _, move := range moves {
|
||||||
var newHead = &Point{}
|
var snake *Snake
|
||||||
|
for i := 0; i < len(b.Snakes); i++ {
|
||||||
|
if b.Snakes[i].ID == move.ID {
|
||||||
|
snake = &b.Snakes[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var newHead = Point{}
|
||||||
switch move.Move {
|
switch move.Move {
|
||||||
case MOVE_DOWN:
|
case MoveDown:
|
||||||
newHead.X = move.Snake.Body[0].X
|
newHead.X = snake.Body[0].X
|
||||||
newHead.Y = move.Snake.Body[0].Y + 1
|
newHead.Y = snake.Body[0].Y + 1
|
||||||
case MOVE_LEFT:
|
case MoveLeft:
|
||||||
newHead.X = move.Snake.Body[0].X - 1
|
newHead.X = snake.Body[0].X - 1
|
||||||
newHead.Y = move.Snake.Body[0].Y
|
newHead.Y = snake.Body[0].Y
|
||||||
case MOVE_RIGHT:
|
case MoveRight:
|
||||||
newHead.X = move.Snake.Body[0].X + 1
|
newHead.X = snake.Body[0].X + 1
|
||||||
newHead.Y = move.Snake.Body[0].Y
|
newHead.Y = snake.Body[0].Y
|
||||||
case MOVE_UP:
|
case MoveUp:
|
||||||
newHead.X = move.Snake.Body[0].X
|
newHead.X = snake.Body[0].X
|
||||||
newHead.Y = move.Snake.Body[0].Y - 1
|
newHead.Y = snake.Body[0].Y - 1
|
||||||
default:
|
default:
|
||||||
// Default to UP
|
// Default to UP
|
||||||
var dX int32 = 0
|
var dX int32 = 0
|
||||||
var dY int32 = -1
|
var dY int32 = -1
|
||||||
// If neck is available, use neck to determine last direction
|
// If neck is available, use neck to determine last direction
|
||||||
if len(move.Snake.Body) >= 2 {
|
if len(snake.Body) >= 2 {
|
||||||
dX = move.Snake.Body[0].X - move.Snake.Body[1].X
|
dX = snake.Body[0].X - snake.Body[1].X
|
||||||
dY = move.Snake.Body[0].Y - move.Snake.Body[1].Y
|
dY = snake.Body[0].Y - snake.Body[1].Y
|
||||||
if dX == 0 && dY == 0 {
|
if dX == 0 && dY == 0 {
|
||||||
dY = -1 // Move up if no last move was made
|
dY = -1 // Move up if no last move was made
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Apply
|
// Apply
|
||||||
newHead.X = move.Snake.Body[0].X + dX
|
newHead.X = snake.Body[0].X + dX
|
||||||
newHead.Y = move.Snake.Body[0].Y + dY
|
newHead.Y = snake.Body[0].Y + dY
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append new head, pop old tail
|
// Append new head, pop old tail
|
||||||
move.Snake.Body = append([]*Point{newHead}, move.Snake.Body[:len(move.Snake.Body)-1]...)
|
snake.Body = append([]Point{newHead}, snake.Body[:len(snake.Body)-1]...)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) reduceSnakeHealth(b *BoardState) error {
|
func (r *StandardRuleset) reduceSnakeHealth(b *BoardState) error {
|
||||||
for _, snake := range b.Snakes {
|
for i := 0; i < len(b.Snakes); i++ {
|
||||||
snake.Health = snake.Health - 1
|
b.Snakes[i].Health = b.Snakes[i].Health - 1
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) eliminateSnakes(b *BoardState) error {
|
func (r *StandardRuleset) eliminateSnakes(b *BoardState) error {
|
||||||
for _, snake := range b.Snakes {
|
for _, snake := range b.Snakes {
|
||||||
if r.snakeHasStarved(snake) {
|
if r.snakeHasStarved(&snake) {
|
||||||
snake.EliminatedCause = ELIMINATED_STARVATION
|
snake.EliminatedCause = EliminatedByStarvation
|
||||||
} else if r.snakeIsOutOfBounds(snake, b.Width, b.Height) {
|
} else if r.snakeIsOutOfBounds(&snake, b.Width, b.Height) {
|
||||||
snake.EliminatedCause = ELIMINATED_OUT_OF_BOUNDS
|
snake.EliminatedCause = EliminatedByOutOfBounds
|
||||||
} else {
|
} else {
|
||||||
for _, other := range b.Snakes {
|
for _, other := range b.Snakes {
|
||||||
if r.snakeHasBodyCollided(snake, other) {
|
if r.snakeHasBodyCollided(&snake, &other) {
|
||||||
if snake.ID == other.ID {
|
if snake.ID == other.ID {
|
||||||
snake.EliminatedCause = ELIMINATED_SELF_COLLISION
|
snake.EliminatedCause = EliminatedBySelfColliision
|
||||||
} else {
|
} else {
|
||||||
snake.EliminatedCause = ELIMINATED_COLLISION
|
snake.EliminatedCause = EliminatedByColliision
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
} else if r.snakeHasLostHeadToHead(snake, other) {
|
} else if r.snakeHasLostHeadToHead(&snake, &other) {
|
||||||
snake.EliminatedCause = ELIMINATED_HEAD_TO_HEAD
|
snake.EliminatedCause = EliminatedByHeadToHeadCollision
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -280,8 +292,8 @@ func (r *StandardRuleset) snakeHasLostHeadToHead(s *Snake, other *Snake) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) feedSnakes(b *BoardState) error {
|
func (r *StandardRuleset) feedSnakes(b *BoardState) error {
|
||||||
var newFood []*Point
|
var newFood []Point
|
||||||
var tail *Point
|
var tail Point
|
||||||
|
|
||||||
for _, food := range b.Food {
|
for _, food := range b.Food {
|
||||||
foodHasBeenEaten := false
|
foodHasBeenEaten := false
|
||||||
|
|
@ -289,9 +301,9 @@ func (r *StandardRuleset) feedSnakes(b *BoardState) error {
|
||||||
if snake.Body[0].X == food.X && snake.Body[0].Y == food.Y {
|
if snake.Body[0].X == food.X && snake.Body[0].Y == food.Y {
|
||||||
foodHasBeenEaten = true
|
foodHasBeenEaten = true
|
||||||
// Update snake
|
// Update snake
|
||||||
snake.Health = SNAKE_MAX_HEALTH
|
snake.Health = SnakeMaxHealth
|
||||||
tail = snake.Body[len(snake.Body)-1]
|
tail = snake.Body[len(snake.Body)-1]
|
||||||
snake.Body = append(snake.Body, &Point{X: tail.X, Y: tail.Y})
|
snake.Body = append(snake.Body, tail)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Persist food to next BoardState if not eaten
|
// Persist food to next BoardState if not eaten
|
||||||
|
|
@ -305,7 +317,7 @@ func (r *StandardRuleset) feedSnakes(b *BoardState) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) maybeSpawnFood(b *BoardState, n int) error {
|
func (r *StandardRuleset) maybeSpawnFood(b *BoardState, n int) error {
|
||||||
if rand.Float32() <= FOOD_SPAWN_CHANCE {
|
if rand.Float32() <= FoodSpawnChance {
|
||||||
r.spawnFood(b, n)
|
r.spawnFood(b, n)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -321,7 +333,7 @@ func (r *StandardRuleset) spawnFood(b *BoardState, n int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) getUnoccupiedPoints(b *BoardState) []*Point {
|
func (r *StandardRuleset) getUnoccupiedPoints(b *BoardState) []Point {
|
||||||
pointIsOccupied := map[int32]map[int32]bool{}
|
pointIsOccupied := map[int32]map[int32]bool{}
|
||||||
for _, p := range b.Food {
|
for _, p := range b.Food {
|
||||||
if _, xExists := pointIsOccupied[p.X]; !xExists {
|
if _, xExists := pointIsOccupied[p.X]; !xExists {
|
||||||
|
|
@ -338,7 +350,7 @@ func (r *StandardRuleset) getUnoccupiedPoints(b *BoardState) []*Point {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unoccupiedPoints := []*Point{}
|
unoccupiedPoints := []Point{}
|
||||||
for x := int32(0); x < b.Width; x++ {
|
for x := int32(0); x < b.Width; x++ {
|
||||||
for y := int32(0); y < b.Height; y++ {
|
for y := int32(0); y < b.Height; y++ {
|
||||||
if _, xExists := pointIsOccupied[x]; xExists {
|
if _, xExists := pointIsOccupied[x]; xExists {
|
||||||
|
|
@ -348,7 +360,7 @@ func (r *StandardRuleset) getUnoccupiedPoints(b *BoardState) []*Point {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
unoccupiedPoints = append(unoccupiedPoints, &Point{X: x, Y: y})
|
unoccupiedPoints = append(unoccupiedPoints, Point{X: x, Y: y})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return unoccupiedPoints
|
return unoccupiedPoints
|
||||||
|
|
|
||||||
462
standard_test.go
462
standard_test.go
|
|
@ -1,6 +1,7 @@
|
||||||
package rulesets
|
package rulesets
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -10,26 +11,172 @@ import (
|
||||||
|
|
||||||
func TestSanity(t *testing.T) {
|
func TestSanity(t *testing.T) {
|
||||||
r := StandardRuleset{}
|
r := StandardRuleset{}
|
||||||
|
|
||||||
|
state, err := r.CreateInitialBoardState(0, 0, []string{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, state)
|
||||||
|
require.Equal(t, int32(0), state.Width)
|
||||||
|
require.Equal(t, int32(0), state.Height)
|
||||||
|
require.Len(t, state.Food, 0)
|
||||||
|
require.Len(t, state.Snakes, 0)
|
||||||
|
|
||||||
next, err := r.ResolveMoves(
|
next, err := r.ResolveMoves(
|
||||||
&BoardState{},
|
&BoardState{},
|
||||||
[]*SnakeMove{},
|
[]SnakeMove{},
|
||||||
)
|
)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NotNil(t, next)
|
require.NotNil(t, next)
|
||||||
|
require.Equal(t, int32(0), state.Width)
|
||||||
|
require.Equal(t, int32(0), state.Height)
|
||||||
|
require.Len(t, state.Snakes, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create Board
|
||||||
|
// placeSnakes
|
||||||
|
// placeFood
|
||||||
|
// knownBoardSize
|
||||||
|
// REsolveMoves
|
||||||
|
// eliminateSnakes
|
||||||
|
// --> related subs
|
||||||
|
// move, reduce, feed, need to consider dead snakes
|
||||||
|
|
||||||
|
func TestCreateInitialBoardState(t *testing.T) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlaceSnakes(t *testing.T) {
|
||||||
|
// Because placement is random, we only test to ensure
|
||||||
|
// that snake bodies are populated correctly
|
||||||
|
tests := []struct {
|
||||||
|
BoardState *BoardState
|
||||||
|
Err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: 1,
|
||||||
|
Height: 1,
|
||||||
|
Snakes: make([]Snake, 1),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: 1,
|
||||||
|
Height: 1,
|
||||||
|
Snakes: make([]Snake, 2),
|
||||||
|
},
|
||||||
|
errors.New("not enough empty squares to place snakes"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: 10,
|
||||||
|
Height: 5,
|
||||||
|
Snakes: make([]Snake, 49),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: 5,
|
||||||
|
Height: 10,
|
||||||
|
Snakes: make([]Snake, 50),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: 25,
|
||||||
|
Height: 2,
|
||||||
|
Snakes: make([]Snake, 51),
|
||||||
|
},
|
||||||
|
errors.New("not enough empty squares to place snakes"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: BoardSizeSmall,
|
||||||
|
Height: BoardSizeSmall,
|
||||||
|
Snakes: make([]Snake, 1),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: BoardSizeSmall,
|
||||||
|
Height: BoardSizeSmall,
|
||||||
|
Snakes: make([]Snake, 8),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: BoardSizeSmall,
|
||||||
|
Height: BoardSizeSmall,
|
||||||
|
Snakes: make([]Snake, 9),
|
||||||
|
},
|
||||||
|
errors.New("too many snakes for fixed start positions"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: BoardSizeMedium,
|
||||||
|
Height: BoardSizeMedium,
|
||||||
|
Snakes: make([]Snake, 8),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: BoardSizeMedium,
|
||||||
|
Height: BoardSizeMedium,
|
||||||
|
Snakes: make([]Snake, 9),
|
||||||
|
},
|
||||||
|
errors.New("too many snakes for fixed start positions"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: BoardSizeLarge,
|
||||||
|
Height: BoardSizeLarge,
|
||||||
|
Snakes: make([]Snake, 8),
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
&BoardState{
|
||||||
|
Width: BoardSizeLarge,
|
||||||
|
Height: BoardSizeLarge,
|
||||||
|
Snakes: make([]Snake, 9),
|
||||||
|
},
|
||||||
|
errors.New("too many snakes for fixed start positions"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
r := StandardRuleset{}
|
||||||
|
for _, test := range tests {
|
||||||
|
require.Equal(t, test.BoardState.Width*test.BoardState.Height, int32(len(r.getUnoccupiedPoints(test.BoardState))))
|
||||||
|
err := r.placeSnakes(test.BoardState)
|
||||||
|
require.Equal(t, test.Err, err, "Snakes: %d", len(test.BoardState.Snakes))
|
||||||
|
if err == nil {
|
||||||
|
for i := 0; i < len(test.BoardState.Snakes); i++ {
|
||||||
|
require.Len(t, test.BoardState.Snakes[i].Body, 3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveMoves(t *testing.T) {
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMoveSnakes(t *testing.T) {
|
func TestMoveSnakes(t *testing.T) {
|
||||||
b := &BoardState{
|
b := &BoardState{
|
||||||
Snakes: []*Snake{
|
Snakes: []Snake{
|
||||||
{
|
{
|
||||||
ID: "one",
|
ID: "one",
|
||||||
Body: []*Point{{10, 110}, {11, 110}},
|
Body: []Point{{10, 110}, {11, 110}},
|
||||||
Health: 111111,
|
Health: 111111,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ID: "two",
|
ID: "two",
|
||||||
Body: []*Point{{23, 220}, {22, 220}, {21, 220}, {20, 220}},
|
Body: []Point{{23, 220}, {22, 220}, {21, 220}, {20, 220}},
|
||||||
Health: 222222,
|
Health: 222222,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -37,47 +184,37 @@ func TestMoveSnakes(t *testing.T) {
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
MoveOne string
|
MoveOne string
|
||||||
ExpectedOne []*Point
|
ExpectedOne []Point
|
||||||
MoveTwo string
|
MoveTwo string
|
||||||
ExpectedTwo []*Point
|
ExpectedTwo []Point
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
MOVE_UP,
|
MoveUp, []Point{{10, 109}, {10, 110}},
|
||||||
[]*Point{{10, 109}, {10, 110}},
|
MoveDown, []Point{{23, 221}, {23, 220}, {22, 220}, {21, 220}},
|
||||||
MOVE_DOWN,
|
|
||||||
[]*Point{{23, 221}, {23, 220}, {22, 220}, {21, 220}},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
MOVE_RIGHT,
|
MoveRight, []Point{{11, 109}, {10, 109}},
|
||||||
[]*Point{{11, 109}, {10, 109}},
|
MoveLeft, []Point{{22, 221}, {23, 221}, {23, 220}, {22, 220}},
|
||||||
MOVE_LEFT,
|
|
||||||
[]*Point{{22, 221}, {23, 221}, {23, 220}, {22, 220}},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
MOVE_RIGHT,
|
MoveRight, []Point{{12, 109}, {11, 109}},
|
||||||
[]*Point{{12, 109}, {11, 109}},
|
MoveLeft, []Point{{21, 221}, {22, 221}, {23, 221}, {23, 220}},
|
||||||
MOVE_LEFT,
|
|
||||||
[]*Point{{21, 221}, {22, 221}, {23, 221}, {23, 220}},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
MOVE_RIGHT,
|
MoveRight, []Point{{13, 109}, {12, 109}},
|
||||||
[]*Point{{13, 109}, {12, 109}},
|
MoveLeft, []Point{{20, 221}, {21, 221}, {22, 221}, {23, 221}},
|
||||||
MOVE_LEFT,
|
|
||||||
[]*Point{{20, 221}, {21, 221}, {22, 221}, {23, 221}},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
MOVE_UP,
|
MoveUp, []Point{{13, 108}, {13, 109}},
|
||||||
[]*Point{{13, 108}, {13, 109}},
|
MoveDown, []Point{{20, 222}, {20, 221}, {21, 221}, {22, 221}},
|
||||||
MOVE_DOWN,
|
|
||||||
[]*Point{{20, 222}, {20, 221}, {21, 221}, {22, 221}},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
r := StandardRuleset{}
|
r := StandardRuleset{}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
moves := []*SnakeMove{
|
moves := []SnakeMove{
|
||||||
{Snake: b.Snakes[0], Move: test.MoveOne},
|
{ID: "one", Move: test.MoveOne},
|
||||||
{Snake: b.Snakes[1], Move: test.MoveTwo},
|
{ID: "two", Move: test.MoveTwo},
|
||||||
}
|
}
|
||||||
err := r.moveSnakes(b, moves)
|
err := r.moveSnakes(b, moves)
|
||||||
|
|
||||||
|
|
@ -90,61 +227,61 @@ func TestMoveSnakes(t *testing.T) {
|
||||||
|
|
||||||
require.Equal(t, len(b.Snakes[0].Body), len(test.ExpectedOne))
|
require.Equal(t, len(b.Snakes[0].Body), len(test.ExpectedOne))
|
||||||
for i, e := range test.ExpectedOne {
|
for i, e := range test.ExpectedOne {
|
||||||
require.Equal(t, *e, *b.Snakes[0].Body[i])
|
require.Equal(t, e, b.Snakes[0].Body[i])
|
||||||
}
|
}
|
||||||
require.Equal(t, len(b.Snakes[1].Body), len(test.ExpectedTwo))
|
require.Equal(t, len(b.Snakes[1].Body), len(test.ExpectedTwo))
|
||||||
for i, e := range test.ExpectedTwo {
|
for i, e := range test.ExpectedTwo {
|
||||||
require.Equal(t, *e, *b.Snakes[1].Body[i])
|
require.Equal(t, e, b.Snakes[1].Body[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMoveSnakesDefault(t *testing.T) {
|
func TestMoveSnakesDefault(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Body []*Point
|
Body []Point
|
||||||
Move string
|
Move string
|
||||||
Expected []*Point
|
Expected []Point
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
Body: []*Point{{0, 0}},
|
Body: []Point{{0, 0}},
|
||||||
Move: "asdf",
|
Move: "asdf",
|
||||||
Expected: []*Point{{0, -1}},
|
Expected: []Point{{0, -1}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Body: []*Point{{5, 5}, {5, 5}},
|
Body: []Point{{5, 5}, {5, 5}},
|
||||||
Move: "",
|
Move: "",
|
||||||
Expected: []*Point{{5, 4}, {5, 5}},
|
Expected: []Point{{5, 4}, {5, 5}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Body: []*Point{{5, 5}, {5, 4}},
|
Body: []Point{{5, 5}, {5, 4}},
|
||||||
Expected: []*Point{{5, 6}, {5, 5}},
|
Expected: []Point{{5, 6}, {5, 5}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Body: []*Point{{5, 4}, {5, 5}},
|
Body: []Point{{5, 4}, {5, 5}},
|
||||||
Expected: []*Point{{5, 3}, {5, 4}},
|
Expected: []Point{{5, 3}, {5, 4}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Body: []*Point{{5, 4}, {5, 5}},
|
Body: []Point{{5, 4}, {5, 5}},
|
||||||
Expected: []*Point{{5, 3}, {5, 4}},
|
Expected: []Point{{5, 3}, {5, 4}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Body: []*Point{{4, 5}, {5, 5}},
|
Body: []Point{{4, 5}, {5, 5}},
|
||||||
Expected: []*Point{{3, 5}, {4, 5}},
|
Expected: []Point{{3, 5}, {4, 5}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Body: []*Point{{5, 5}, {4, 5}},
|
Body: []Point{{5, 5}, {4, 5}},
|
||||||
Expected: []*Point{{6, 5}, {5, 5}},
|
Expected: []Point{{6, 5}, {5, 5}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
r := StandardRuleset{}
|
r := StandardRuleset{}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
b := &BoardState{
|
b := &BoardState{
|
||||||
Snakes: []*Snake{
|
Snakes: []Snake{
|
||||||
{Body: test.Body},
|
{ID: "one", Body: test.Body},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
moves := []*SnakeMove{{Snake: b.Snakes[0], Move: test.Move}}
|
moves := []SnakeMove{{ID: "one", Move: test.Move}}
|
||||||
|
|
||||||
err := r.moveSnakes(b, moves)
|
err := r.moveSnakes(b, moves)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
@ -152,28 +289,27 @@ func TestMoveSnakesDefault(t *testing.T) {
|
||||||
require.Equal(t, len(test.Body), len(b.Snakes[0].Body))
|
require.Equal(t, len(test.Body), len(b.Snakes[0].Body))
|
||||||
require.Equal(t, len(test.Expected), len(b.Snakes[0].Body))
|
require.Equal(t, len(test.Expected), len(b.Snakes[0].Body))
|
||||||
for i, e := range test.Expected {
|
for i, e := range test.Expected {
|
||||||
require.Equal(t, *e, *b.Snakes[0].Body[i])
|
require.Equal(t, e, b.Snakes[0].Body[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReduceSnakeHealth(t *testing.T) {
|
func TestReduceSnakeHealth(t *testing.T) {
|
||||||
var err error
|
|
||||||
r := StandardRuleset{}
|
|
||||||
b := &BoardState{
|
b := &BoardState{
|
||||||
Snakes: []*Snake{
|
Snakes: []Snake{
|
||||||
&Snake{
|
{
|
||||||
Body: []*Point{{0, 0}, {0, 1}},
|
Body: []Point{{0, 0}, {0, 1}},
|
||||||
Health: 99,
|
Health: 99,
|
||||||
},
|
},
|
||||||
&Snake{
|
{
|
||||||
Body: []*Point{{5, 8}, {6, 8}, {7, 8}},
|
Body: []Point{{5, 8}, {6, 8}, {7, 8}},
|
||||||
Health: 2,
|
Health: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.reduceSnakeHealth(b)
|
r := StandardRuleset{}
|
||||||
|
err := r.reduceSnakeHealth(b)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, b.Snakes[0].Health, int32(98))
|
require.Equal(t, b.Snakes[0].Health, int32(98))
|
||||||
require.Equal(t, b.Snakes[1].Health, int32(1))
|
require.Equal(t, b.Snakes[1].Health, int32(1))
|
||||||
|
|
@ -218,8 +354,8 @@ func TestSnakeHasStarved(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSnakeIsOutOfBounds(t *testing.T) {
|
func TestSnakeIsOutOfBounds(t *testing.T) {
|
||||||
var boardWidth int32 = 10
|
boardWidth := int32(10)
|
||||||
var boardHeight int32 = 100
|
boardHeight := int32(100)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Point Point
|
Point Point
|
||||||
|
|
@ -252,172 +388,166 @@ func TestSnakeIsOutOfBounds(t *testing.T) {
|
||||||
{Point{X: math.MaxInt32, Y: math.MaxInt32}, true},
|
{Point{X: math.MaxInt32, Y: math.MaxInt32}, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
var s *Snake
|
|
||||||
r := StandardRuleset{}
|
r := StandardRuleset{}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
// Test with point as head
|
// Test with point as head
|
||||||
s = &Snake{Body: []*Point{&test.Point}}
|
s := Snake{Body: []Point{test.Point}}
|
||||||
require.Equal(t, test.Expected, r.snakeIsOutOfBounds(s, boardWidth, boardHeight), "Head%+v", test.Point)
|
require.Equal(t, test.Expected, r.snakeIsOutOfBounds(&s, boardWidth, boardHeight), "Head%+v", test.Point)
|
||||||
// Test with point as body
|
// Test with point as body
|
||||||
s = &Snake{Body: []*Point{&Point{0, 0}, &Point{0, 0}, &test.Point}}
|
s = Snake{Body: []Point{Point{0, 0}, Point{0, 0}, test.Point}}
|
||||||
require.Equal(t, test.Expected, r.snakeIsOutOfBounds(s, boardWidth, boardHeight), "Body%+v", test.Point)
|
require.Equal(t, test.Expected, r.snakeIsOutOfBounds(&s, boardWidth, boardHeight), "Body%+v", test.Point)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSnakeHasBodyCollidedSelf(t *testing.T) {
|
func TestSnakeHasBodyCollidedSelf(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Body []*Point
|
Body []Point
|
||||||
Expected bool
|
Expected bool
|
||||||
}{
|
}{
|
||||||
{[]*Point{{1, 1}}, false},
|
{[]Point{{1, 1}}, false},
|
||||||
// Self stacks should self collide
|
// Self stacks should self collide
|
||||||
// (we rely on snakes moving before we check self-collision on turn one)
|
// (we rely on snakes moving before we check self-collision on turn one)
|
||||||
{[]*Point{{2, 2}, {2, 2}}, true},
|
{[]Point{{2, 2}, {2, 2}}, true},
|
||||||
{[]*Point{{3, 3}, {3, 3}, {3, 3}}, true},
|
{[]Point{{3, 3}, {3, 3}, {3, 3}}, true},
|
||||||
{[]*Point{{5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}}, true},
|
{[]Point{{5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}}, true},
|
||||||
// Non-collision cases
|
// Non-collision cases
|
||||||
{[]*Point{{0, 0}, {1, 0}, {1, 0}}, false},
|
{[]Point{{0, 0}, {1, 0}, {1, 0}}, false},
|
||||||
{[]*Point{{0, 0}, {1, 0}, {2, 0}}, false},
|
{[]Point{{0, 0}, {1, 0}, {2, 0}}, false},
|
||||||
{[]*Point{{0, 0}, {1, 0}, {2, 0}, {2, 0}, {2, 0}}, false},
|
{[]Point{{0, 0}, {1, 0}, {2, 0}, {2, 0}, {2, 0}}, false},
|
||||||
{[]*Point{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}}, false},
|
{[]Point{{0, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}}, false},
|
||||||
{[]*Point{{0, 0}, {0, 1}, {0, 2}}, false},
|
{[]Point{{0, 0}, {0, 1}, {0, 2}}, false},
|
||||||
{[]*Point{{0, 0}, {0, 1}, {0, 2}, {0, 2}, {0, 2}}, false},
|
{[]Point{{0, 0}, {0, 1}, {0, 2}, {0, 2}, {0, 2}}, false},
|
||||||
{[]*Point{{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}}, false},
|
{[]Point{{0, 0}, {0, 1}, {0, 2}, {0, 3}, {0, 4}}, false},
|
||||||
// Collision cases
|
// Collision cases
|
||||||
{[]*Point{{0, 0}, {1, 0}, {0, 0}}, true},
|
{[]Point{{0, 0}, {1, 0}, {0, 0}}, true},
|
||||||
{[]*Point{{0, 0}, {0, 0}, {1, 0}}, true},
|
{[]Point{{0, 0}, {0, 0}, {1, 0}}, true},
|
||||||
{[]*Point{{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}}, true},
|
{[]Point{{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}}, true},
|
||||||
{[]*Point{{4, 4}, {3, 4}, {3, 3}, {4, 4}, {4, 4}}, true},
|
{[]Point{{4, 4}, {3, 4}, {3, 3}, {4, 4}, {4, 4}}, true},
|
||||||
{[]*Point{{3, 3}, {3, 4}, {3, 3}, {4, 4}, {4, 5}}, true},
|
{[]Point{{3, 3}, {3, 4}, {3, 3}, {4, 4}, {4, 5}}, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
var s *Snake
|
|
||||||
r := StandardRuleset{}
|
r := StandardRuleset{}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
s = &Snake{Body: test.Body}
|
s := Snake{Body: test.Body}
|
||||||
require.Equal(t, test.Expected, r.snakeHasBodyCollided(s, s), "Body%q", s.Body)
|
require.Equal(t, test.Expected, r.snakeHasBodyCollided(&s, &s), "Body%q", s.Body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSnakeHasBodyCollidedOther(t *testing.T) {
|
func TestSnakeHasBodyCollidedOther(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
SnakeBody []*Point
|
SnakeBody []Point
|
||||||
OtherBody []*Point
|
OtherBody []Point
|
||||||
Expected bool
|
Expected bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
// Just heads
|
// Just heads
|
||||||
[]*Point{{0, 0}},
|
[]Point{{0, 0}},
|
||||||
[]*Point{{1, 1}},
|
[]Point{{1, 1}},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Head-to-heads are not considered in body collisions
|
// Head-to-heads are not considered in body collisions
|
||||||
[]*Point{{0, 0}},
|
[]Point{{0, 0}},
|
||||||
[]*Point{{0, 0}},
|
[]Point{{0, 0}},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Stacked bodies
|
// Stacked bodies
|
||||||
[]*Point{{0, 0}},
|
[]Point{{0, 0}},
|
||||||
[]*Point{{0, 0}, {0, 0}},
|
[]Point{{0, 0}, {0, 0}},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Separate stacked bodies
|
// Separate stacked bodies
|
||||||
[]*Point{{0, 0}, {0, 0}, {0, 0}},
|
[]Point{{0, 0}, {0, 0}, {0, 0}},
|
||||||
[]*Point{{1, 1}, {1, 1}, {1, 1}},
|
[]Point{{1, 1}, {1, 1}, {1, 1}},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Stacked bodies, separated heads
|
// Stacked bodies, separated heads
|
||||||
[]*Point{{0, 0}, {1, 0}, {1, 0}},
|
[]Point{{0, 0}, {1, 0}, {1, 0}},
|
||||||
[]*Point{{2, 0}, {1, 0}, {1, 0}},
|
[]Point{{2, 0}, {1, 0}, {1, 0}},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Mid-snake collision
|
// Mid-snake collision
|
||||||
[]*Point{{1, 1}},
|
[]Point{{1, 1}},
|
||||||
[]*Point{{0, 1}, {1, 1}, {2, 1}},
|
[]Point{{0, 1}, {1, 1}, {2, 1}},
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var s *Snake
|
|
||||||
var o *Snake
|
|
||||||
r := StandardRuleset{}
|
r := StandardRuleset{}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
s = &Snake{Body: test.SnakeBody}
|
s := &Snake{Body: test.SnakeBody}
|
||||||
o = &Snake{Body: test.OtherBody}
|
o := &Snake{Body: test.OtherBody}
|
||||||
require.Equal(t, test.Expected, r.snakeHasBodyCollided(s, o), "Snake%q Other%q", s.Body, o.Body)
|
require.Equal(t, test.Expected, r.snakeHasBodyCollided(s, o), "Snake%q Other%q", s.Body, o.Body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSnakeHasLostHeadToHead(t *testing.T) {
|
func TestSnakeHasLostHeadToHead(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
SnakeBody []*Point
|
SnakeBody []Point
|
||||||
OtherBody []*Point
|
OtherBody []Point
|
||||||
Expected bool
|
Expected bool
|
||||||
ExpectedOpposite bool
|
ExpectedOpposite bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
// Just heads
|
// Just heads
|
||||||
[]*Point{{0, 0}},
|
[]Point{{0, 0}},
|
||||||
[]*Point{{1, 1}},
|
[]Point{{1, 1}},
|
||||||
false, false,
|
false, false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Just heads colliding
|
// Just heads colliding
|
||||||
[]*Point{{0, 0}},
|
[]Point{{0, 0}},
|
||||||
[]*Point{{0, 0}},
|
[]Point{{0, 0}},
|
||||||
true, true,
|
true, true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// One snake larger
|
// One snake larger
|
||||||
[]*Point{{0, 0}, {1, 0}, {2, 0}},
|
[]Point{{0, 0}, {1, 0}, {2, 0}},
|
||||||
[]*Point{{0, 0}},
|
[]Point{{0, 0}},
|
||||||
false, true,
|
false, true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Other snake equal
|
// Other snake equal
|
||||||
[]*Point{{0, 0}, {1, 0}, {2, 0}},
|
[]Point{{0, 0}, {1, 0}, {2, 0}},
|
||||||
[]*Point{{0, 0}, {0, 1}, {0, 2}},
|
[]Point{{0, 0}, {0, 1}, {0, 2}},
|
||||||
true, true,
|
true, true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Other snake longer
|
// Other snake longer
|
||||||
[]*Point{{0, 0}, {1, 0}, {2, 0}},
|
[]Point{{0, 0}, {1, 0}, {2, 0}},
|
||||||
[]*Point{{0, 0}, {0, 1}, {0, 2}, {0, 3}},
|
[]Point{{0, 0}, {0, 1}, {0, 2}, {0, 3}},
|
||||||
true, false,
|
true, false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Body collision
|
// Body collision
|
||||||
[]*Point{{0, 1}, {1, 1}, {2, 1}},
|
[]Point{{0, 1}, {1, 1}, {2, 1}},
|
||||||
[]*Point{{0, 0}, {0, 1}, {0, 2}, {0, 3}},
|
[]Point{{0, 0}, {0, 1}, {0, 2}, {0, 3}},
|
||||||
false, false,
|
false, false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Separate stacked bodies, head collision
|
// Separate stacked bodies, head collision
|
||||||
[]*Point{{3, 10}, {2, 10}, {2, 10}},
|
[]Point{{3, 10}, {2, 10}, {2, 10}},
|
||||||
[]*Point{{3, 10}, {4, 10}, {4, 10}},
|
[]Point{{3, 10}, {4, 10}, {4, 10}},
|
||||||
true, true,
|
true, true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Separate stacked bodies, head collision
|
// Separate stacked bodies, head collision
|
||||||
[]*Point{{10, 3}, {10, 2}, {10, 1}, {10, 0}},
|
[]Point{{10, 3}, {10, 2}, {10, 1}, {10, 0}},
|
||||||
[]*Point{{10, 3}, {10, 4}, {10, 5}},
|
[]Point{{10, 3}, {10, 4}, {10, 5}},
|
||||||
false, true,
|
false, true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var s *Snake
|
|
||||||
var o *Snake
|
|
||||||
r := StandardRuleset{}
|
r := StandardRuleset{}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
s = &Snake{Body: test.SnakeBody}
|
s := Snake{Body: test.SnakeBody}
|
||||||
o = &Snake{Body: test.OtherBody}
|
o := Snake{Body: test.OtherBody}
|
||||||
require.Equal(t, test.Expected, r.snakeHasLostHeadToHead(s, o), "Snake%q Other%q", s.Body, o.Body)
|
require.Equal(t, test.Expected, r.snakeHasLostHeadToHead(&s, &o), "Snake%q Other%q", s.Body, o.Body)
|
||||||
require.Equal(t, test.ExpectedOpposite, r.snakeHasLostHeadToHead(o, s), "Snake%q Other%q", s.Body, o.Body)
|
require.Equal(t, test.ExpectedOpposite, r.snakeHasLostHeadToHead(&o, &s), "Snake%q Other%q", s.Body, o.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -425,14 +555,10 @@ func TestSnakeHasLostHeadToHead(t *testing.T) {
|
||||||
func TestFeedSnakes(t *testing.T) {
|
func TestFeedSnakes(t *testing.T) {
|
||||||
r := StandardRuleset{}
|
r := StandardRuleset{}
|
||||||
b := &BoardState{
|
b := &BoardState{
|
||||||
Snakes: []*Snake{
|
Snakes: []Snake{
|
||||||
{Body: []*Point{
|
{Body: []Point{{2, 1}, {1, 1}, {1, 2}, {2, 2}}},
|
||||||
{2, 1}, {1, 1}, {1, 2}, {2, 2},
|
|
||||||
}},
|
|
||||||
},
|
|
||||||
Food: []*Point{
|
|
||||||
{2, 1},
|
|
||||||
},
|
},
|
||||||
|
Food: []Point{{2, 1}},
|
||||||
}
|
}
|
||||||
|
|
||||||
err := r.feedSnakes(b)
|
err := r.feedSnakes(b)
|
||||||
|
|
@ -444,77 +570,77 @@ func TestFeedSnakes(t *testing.T) {
|
||||||
func TestGetUnoccupiedPoints(t *testing.T) {
|
func TestGetUnoccupiedPoints(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Board *BoardState
|
Board *BoardState
|
||||||
Expected []*Point
|
Expected []Point
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
&BoardState{
|
&BoardState{
|
||||||
Height: 1,
|
Height: 1,
|
||||||
Width: 1,
|
Width: 1,
|
||||||
},
|
},
|
||||||
[]*Point{{0, 0}},
|
[]Point{{0, 0}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&BoardState{
|
&BoardState{
|
||||||
Height: 1,
|
Height: 1,
|
||||||
Width: 2,
|
Width: 2,
|
||||||
},
|
},
|
||||||
[]*Point{{0, 0}, {1, 0}},
|
[]Point{{0, 0}, {1, 0}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&BoardState{
|
&BoardState{
|
||||||
Height: 1,
|
Height: 1,
|
||||||
Width: 1,
|
Width: 1,
|
||||||
Food: []*Point{{0, 0}, {101, 202}, {-4, -5}},
|
Food: []Point{{0, 0}, {101, 202}, {-4, -5}},
|
||||||
},
|
},
|
||||||
[]*Point{},
|
[]Point{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&BoardState{
|
&BoardState{
|
||||||
Height: 2,
|
Height: 2,
|
||||||
Width: 2,
|
Width: 2,
|
||||||
Food: []*Point{{0, 0}, {1, 0}},
|
Food: []Point{{0, 0}, {1, 0}},
|
||||||
},
|
},
|
||||||
[]*Point{{0, 1}, {1, 1}},
|
[]Point{{0, 1}, {1, 1}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&BoardState{
|
&BoardState{
|
||||||
Height: 2,
|
Height: 2,
|
||||||
Width: 2,
|
Width: 2,
|
||||||
Food: []*Point{{0, 0}, {0, 1}, {1, 0}, {1, 1}},
|
Food: []Point{{0, 0}, {0, 1}, {1, 0}, {1, 1}},
|
||||||
},
|
},
|
||||||
[]*Point{},
|
[]Point{},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&BoardState{
|
&BoardState{
|
||||||
Height: 4,
|
Height: 4,
|
||||||
Width: 1,
|
Width: 1,
|
||||||
Snakes: []*Snake{
|
Snakes: []Snake{
|
||||||
{Body: []*Point{{0, 0}}},
|
{Body: []Point{{0, 0}}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[]*Point{{0, 1}, {0, 2}, {0, 3}},
|
[]Point{{0, 1}, {0, 2}, {0, 3}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&BoardState{
|
&BoardState{
|
||||||
Height: 2,
|
Height: 2,
|
||||||
Width: 3,
|
Width: 3,
|
||||||
Snakes: []*Snake{
|
Snakes: []Snake{
|
||||||
{Body: []*Point{{0, 0}, {1, 0}, {1, 1}}},
|
{Body: []Point{{0, 0}, {1, 0}, {1, 1}}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[]*Point{{0, 1}, {2, 0}, {2, 1}},
|
[]Point{{0, 1}, {2, 0}, {2, 1}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
&BoardState{
|
&BoardState{
|
||||||
Height: 2,
|
Height: 2,
|
||||||
Width: 3,
|
Width: 3,
|
||||||
Food: []*Point{{0, 0}, {1, 0}, {1, 1}, {2, 0}},
|
Food: []Point{{0, 0}, {1, 0}, {1, 1}, {2, 0}},
|
||||||
Snakes: []*Snake{
|
Snakes: []Snake{
|
||||||
{Body: []*Point{{0, 0}, {1, 0}, {1, 1}}},
|
{Body: []Point{{0, 0}, {1, 0}, {1, 1}}},
|
||||||
{Body: []*Point{{0, 1}}},
|
{Body: []Point{{0, 1}}},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[]*Point{{2, 1}},
|
[]Point{{2, 1}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -523,7 +649,7 @@ func TestGetUnoccupiedPoints(t *testing.T) {
|
||||||
unoccupiedPoints := r.getUnoccupiedPoints(test.Board)
|
unoccupiedPoints := r.getUnoccupiedPoints(test.Board)
|
||||||
require.Equal(t, len(test.Expected), len(unoccupiedPoints))
|
require.Equal(t, len(test.Expected), len(unoccupiedPoints))
|
||||||
for i, e := range test.Expected {
|
for i, e := range test.Expected {
|
||||||
require.Equal(t, *e, *unoccupiedPoints[i])
|
require.Equal(t, e, unoccupiedPoints[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -531,15 +657,15 @@ func TestGetUnoccupiedPoints(t *testing.T) {
|
||||||
func TestMaybeSpawnFood(t *testing.T) {
|
func TestMaybeSpawnFood(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Seed int64
|
Seed int64
|
||||||
ExpectedFood []*Point
|
ExpectedFood []Point
|
||||||
}{
|
}{
|
||||||
// Use pre-tested seeds and results
|
// Use pre-tested seeds and results
|
||||||
{123, []*Point{}},
|
{123, []Point{}},
|
||||||
{456, []*Point{}},
|
{456, []Point{}},
|
||||||
{789, []*Point{}},
|
{789, []Point{}},
|
||||||
{1024, []*Point{{2, 1}}},
|
{1024, []Point{{2, 1}}},
|
||||||
{511, []*Point{{2, 0}}},
|
{511, []Point{{2, 0}}},
|
||||||
{165, []*Point{{3, 1}}},
|
{165, []Point{{3, 1}}},
|
||||||
}
|
}
|
||||||
|
|
||||||
r := StandardRuleset{}
|
r := StandardRuleset{}
|
||||||
|
|
@ -547,9 +673,9 @@ func TestMaybeSpawnFood(t *testing.T) {
|
||||||
b := &BoardState{
|
b := &BoardState{
|
||||||
Height: 4,
|
Height: 4,
|
||||||
Width: 5,
|
Width: 5,
|
||||||
Snakes: []*Snake{
|
Snakes: []Snake{
|
||||||
{Body: []*Point{{1, 0}, {1, 1}}},
|
{Body: []Point{{1, 0}, {1, 1}}},
|
||||||
{Body: []*Point{{0, 1}, {0, 2}, {0, 3}}},
|
{Body: []Point{{0, 1}, {0, 2}, {0, 3}}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -558,7 +684,7 @@ func TestMaybeSpawnFood(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, len(test.ExpectedFood), len(b.Food), "Seed %d", test.Seed)
|
require.Equal(t, len(test.ExpectedFood), len(b.Food), "Seed %d", test.Seed)
|
||||||
for i, e := range test.ExpectedFood {
|
for i, e := range test.ExpectedFood {
|
||||||
require.Equal(t, *e, *b.Food[i], "Seed %d", test.Seed)
|
require.Equal(t, e, b.Food[i], "Seed %d", test.Seed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue