Update standard.go
This commit is contained in:
parent
d477201b1e
commit
29a8fd1dd4
1 changed files with 204 additions and 46 deletions
250
standard.go
250
standard.go
|
|
@ -1,13 +1,18 @@
|
||||||
package rulesets
|
package rulesets
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// log "github.com/sirupsen/logrus"
|
"errors"
|
||||||
|
"math/rand"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StandardRuleset struct{}
|
type StandardRuleset struct{}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SNAKE_MAX_HEALTH = 100
|
BOARD_SIZE_SMALL = 7
|
||||||
|
BOARD_SIZE_MEDIUM = 11
|
||||||
|
BOARD_SIZE_LARGE = 19
|
||||||
|
FOOD_SPAWN_CHANCE = 0.1
|
||||||
|
SNAKE_MAX_HEALTH = 100
|
||||||
// bvanvugt - TODO: Just return formatted strings instead of codes?
|
// bvanvugt - TODO: Just return formatted strings instead of codes?
|
||||||
ELIMINATED_COLLISION = "snake-collision"
|
ELIMINATED_COLLISION = "snake-collision"
|
||||||
ELIMINATED_SELF_COLLISION = "snake-self-collision"
|
ELIMINATED_SELF_COLLISION = "snake-self-collision"
|
||||||
|
|
@ -16,50 +21,158 @@ const (
|
||||||
ELIMINATED_OUT_OF_BOUNDS = "wall-collision"
|
ELIMINATED_OUT_OF_BOUNDS = "wall-collision"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *StandardRuleset) ResolveMoves(g *Game, prevGameState *GameState, moves []*SnakeMove) (nextGameState *GameState, err error) {
|
func (r *StandardRuleset) CreateInitialBoard(width int32, height int32, snakeIDs []string) (*BoardState, error) {
|
||||||
nextGameState = &GameState{
|
var err error
|
||||||
Snakes: prevGameState.Snakes,
|
|
||||||
Food: prevGameState.Food,
|
snakes := []*Snake{}
|
||||||
|
for _, id := range snakeIDs {
|
||||||
|
snakes = append(snakes,
|
||||||
|
&Snake{
|
||||||
|
ID: id,
|
||||||
|
Health: SNAKE_MAX_HEALTH,
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Gut check the GameState?
|
initialBoardState := &BoardState{
|
||||||
|
Height: height,
|
||||||
|
Width: width,
|
||||||
|
Snakes: snakes,
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: LOG?
|
// Place Snakes
|
||||||
err = r.moveSnakes(nextGameState, moves)
|
if r.isKnownBoardSize(initialBoardState) {
|
||||||
|
err = r.placeSnakesFixed(initialBoardState)
|
||||||
|
} else {
|
||||||
|
err = r.placeSnakesRandomly(initialBoardState)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: LOG?
|
// Place Food
|
||||||
err = r.reduceSnakeHealth(nextGameState)
|
err = r.placeInitialFood(initialBoardState)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: LOG?
|
return initialBoardState, nil
|
||||||
// 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 {
|
func (r *StandardRuleset) placeSnakesFixed(b *BoardState) error {
|
||||||
|
// Sanity check
|
||||||
|
if len(b.Snakes) >= 8 {
|
||||||
|
return errors.New("too many snakes for fixed start positions")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create start points
|
||||||
|
mn, md, mx := int32(1), (b.Width-1)/2, b.Width-2
|
||||||
|
startPoints := []Point{
|
||||||
|
{mn, mn},
|
||||||
|
{mn, md},
|
||||||
|
{mn, mx},
|
||||||
|
{md, mn},
|
||||||
|
{md, mx},
|
||||||
|
{mx, mn},
|
||||||
|
{mx, md},
|
||||||
|
{mx, mx},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Randomly order them
|
||||||
|
rand.Shuffle(len(startPoints), func(i int, j int) {
|
||||||
|
startPoints[i], startPoints[j] = startPoints[j], startPoints[i]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Assign to snakes in order given
|
||||||
|
for i, snake := range b.Snakes {
|
||||||
|
p := startPoints[i]
|
||||||
|
for j := 0; j < 3; j++ {
|
||||||
|
snake.Body = append(snake.Body, &Point{p.X, p.Y})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) placeSnakesRandomly(b *BoardState) error {
|
||||||
|
for _, snake := range b.Snakes {
|
||||||
|
unoccupiedPoints := r.getUnoccupiedPoints(b)
|
||||||
|
p := unoccupiedPoints[rand.Intn(len(unoccupiedPoints))]
|
||||||
|
for j := 0; j < 3; j++ {
|
||||||
|
snake.Body = append(snake.Body, &Point{p.X, p.Y})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) isKnownBoardSize(b *BoardState) bool {
|
||||||
|
if b.Height == BOARD_SIZE_SMALL && b.Width == BOARD_SIZE_SMALL {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if b.Height == BOARD_SIZE_MEDIUM && b.Width == BOARD_SIZE_MEDIUM {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if b.Height == BOARD_SIZE_LARGE && b.Width == BOARD_SIZE_LARGE {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) placeInitialFood(b *BoardState) error {
|
||||||
|
r.spawnFood(b, len(b.Snakes))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) ResolveMoves(prevState *BoardState, moves []*SnakeMove) (*BoardState, error) {
|
||||||
|
// TODO: DO NOT REFERENCE prevState directly!!!!
|
||||||
|
// we're technically altering both states
|
||||||
|
nextState := &BoardState{
|
||||||
|
Snakes: prevState.Snakes,
|
||||||
|
Food: prevState.Food,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Gut check the BoardState?
|
||||||
|
|
||||||
|
// TODO: LOG?
|
||||||
|
err := r.moveSnakes(nextState, moves)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: LOG?
|
||||||
|
err = r.reduceSnakeHealth(nextState)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
// bvanvugt: we specifically want this to happen before elimination
|
||||||
|
// so that head-to-head collisions on food still remove the food.
|
||||||
|
// It does create an artifact though, where head-to-head collisions
|
||||||
|
// of equal length actually show length + 1
|
||||||
|
|
||||||
|
// TODO: LOG?
|
||||||
|
err = r.feedSnakes(nextState)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: LOG?
|
||||||
|
err = r.maybeSpawnFood(nextState, 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: LOG?
|
||||||
|
err = r.eliminateSnakes(nextState)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nextState, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) moveSnakes(b *BoardState, moves []*SnakeMove) error {
|
||||||
for _, move := range moves {
|
for _, move := range moves {
|
||||||
var newHead = &Point{}
|
var newHead = &Point{}
|
||||||
switch move.Move {
|
switch move.Move {
|
||||||
|
|
@ -98,21 +211,21 @@ func (r *StandardRuleset) moveSnakes(gs *GameState, moves []*SnakeMove) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) reduceSnakeHealth(gs *GameState) error {
|
func (r *StandardRuleset) reduceSnakeHealth(b *BoardState) error {
|
||||||
for _, snake := range gs.Snakes {
|
for _, snake := range b.Snakes {
|
||||||
snake.Health = snake.Health - 1
|
snake.Health = snake.Health - 1
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) eliminateSnakes(gs *GameState, boardWidth int32, boardHeight int32) error {
|
func (r *StandardRuleset) eliminateSnakes(b *BoardState) error {
|
||||||
for _, snake := range gs.Snakes {
|
for _, snake := range b.Snakes {
|
||||||
if r.snakeHasStarved(snake) {
|
if r.snakeHasStarved(snake) {
|
||||||
snake.EliminatedCause = ELIMINATED_STARVATION
|
snake.EliminatedCause = ELIMINATED_STARVATION
|
||||||
} else if r.snakeIsOutOfBounds(snake, boardWidth, boardHeight) {
|
} else if r.snakeIsOutOfBounds(snake, b.Width, b.Height) {
|
||||||
snake.EliminatedCause = ELIMINATED_OUT_OF_BOUNDS
|
snake.EliminatedCause = ELIMINATED_OUT_OF_BOUNDS
|
||||||
} else {
|
} else {
|
||||||
for _, other := range gs.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 = ELIMINATED_SELF_COLLISION
|
||||||
|
|
@ -165,13 +278,13 @@ func (r *StandardRuleset) snakeHasLostHeadToHead(s *Snake, other *Snake) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) feedSnakes(gs *GameState) error {
|
func (r *StandardRuleset) feedSnakes(b *BoardState) error {
|
||||||
var newFood []*Point
|
var newFood []*Point
|
||||||
var tail *Point
|
var tail *Point
|
||||||
|
|
||||||
for _, food := range gs.Food {
|
for _, food := range b.Food {
|
||||||
foodHasBeenEaten := false
|
foodHasBeenEaten := false
|
||||||
for _, snake := range gs.Snakes {
|
for _, snake := range b.Snakes {
|
||||||
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
|
||||||
|
|
@ -180,17 +293,62 @@ func (r *StandardRuleset) feedSnakes(gs *GameState) error {
|
||||||
snake.Body = append(snake.Body, &Point{X: tail.X, Y: tail.Y})
|
snake.Body = append(snake.Body, &Point{X: tail.X, Y: tail.Y})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Persist food to next GameState if not eaten
|
// Persist food to next BoardState if not eaten
|
||||||
if !foodHasBeenEaten {
|
if !foodHasBeenEaten {
|
||||||
newFood = append(newFood, food)
|
newFood = append(newFood, food)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gs.Food = newFood
|
b.Food = newFood
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) maybeSpawnFood(gs *GameState) error {
|
func (r *StandardRuleset) maybeSpawnFood(b *BoardState, n int) error {
|
||||||
// TODO
|
if rand.Float32() <= FOOD_SPAWN_CHANCE {
|
||||||
|
r.spawnFood(b, n)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) spawnFood(b *BoardState, n int) {
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
unoccupiedPoints := r.getUnoccupiedPoints(b)
|
||||||
|
if len(unoccupiedPoints) > 0 {
|
||||||
|
newFood := unoccupiedPoints[rand.Intn(len(unoccupiedPoints))]
|
||||||
|
b.Food = append(b.Food, newFood)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) getUnoccupiedPoints(b *BoardState) []*Point {
|
||||||
|
pointIsOccupied := map[int32]map[int32]bool{}
|
||||||
|
for _, p := range b.Food {
|
||||||
|
if _, xExists := pointIsOccupied[p.X]; !xExists {
|
||||||
|
pointIsOccupied[p.X] = map[int32]bool{}
|
||||||
|
}
|
||||||
|
pointIsOccupied[p.X][p.Y] = true
|
||||||
|
}
|
||||||
|
for _, snake := range b.Snakes {
|
||||||
|
for _, p := range snake.Body {
|
||||||
|
if _, xExists := pointIsOccupied[p.X]; !xExists {
|
||||||
|
pointIsOccupied[p.X] = map[int32]bool{}
|
||||||
|
}
|
||||||
|
pointIsOccupied[p.X][p.Y] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unoccupiedPoints := []*Point{}
|
||||||
|
for x := int32(0); x < b.Width; x++ {
|
||||||
|
for y := int32(0); y < b.Height; y++ {
|
||||||
|
if _, xExists := pointIsOccupied[x]; xExists {
|
||||||
|
if isOccupied, yExists := pointIsOccupied[x][y]; yExists {
|
||||||
|
if isOccupied {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unoccupiedPoints = append(unoccupiedPoints, &Point{X: x, Y: y})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return unoccupiedPoints
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue