From 010b3aa08fe967ddac1965bcc4f2a83ddba6fa12 Mon Sep 17 00:00:00 2001 From: bvanvugt <1531419+bvanvugt@users.noreply.github.com> Date: Thu, 2 Jan 2020 16:10:33 -0800 Subject: [PATCH] Rename consts, remove pointers where not wanted/needed, snake placement tests. --- ruleset.go | 20 +- standard.go | 218 +++++++++++----------- standard_test.go | 462 ++++++++++++++++++++++++++++++----------------- 3 files changed, 419 insertions(+), 281 deletions(-) diff --git a/ruleset.go b/ruleset.go index 7a2f38e..f62b550 100644 --- a/ruleset.go +++ b/ruleset.go @@ -1,10 +1,10 @@ package rulesets const ( - MOVE_UP = "up" - MOVE_DOWN = "down" - MOVE_RIGHT = "right" - MOVE_LEFT = "left" + MoveUp = "up" + MoveDown = "down" + MoveRight = "right" + MoveLeft = "left" ) type Point struct { @@ -14,7 +14,7 @@ type Point struct { type Snake struct { ID string - Body []*Point + Body []Point Health int32 EliminatedCause string } @@ -22,16 +22,16 @@ type Snake struct { type BoardState struct { Height int32 Width int32 - Food []*Point - Snakes []*Snake + Food []Point + Snakes []Snake } type SnakeMove struct { - Snake *Snake - Move string + ID string + Move string } type Ruleset interface { CreateInitialBoardState(width int32, height int32, snakeIDs []string) (*BoardState, error) - ResolveMoves(prevState *BoardState, moves []*SnakeMove) (*BoardState, error) + ResolveMoves(prevState *BoardState, moves []SnakeMove) (*BoardState, error) } diff --git a/standard.go b/standard.go index 6752893..84d5792 100644 --- a/standard.go +++ b/standard.go @@ -8,50 +8,40 @@ import ( type StandardRuleset struct{} const ( - BOARD_SIZE_SMALL = 7 - BOARD_SIZE_MEDIUM = 11 - BOARD_SIZE_LARGE = 19 - FOOD_SPAWN_CHANCE = 0.1 - SNAKE_MAX_HEALTH = 100 + BoardSizeSmall = 7 + BoardSizeMedium = 11 + BoardSizeLarge = 19 + FoodSpawnChance = 0.1 + SnakeMaxHealth = 100 + SnakeStartSize = 3 // 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" + EliminatedByColliision = "snake-collision" + EliminatedBySelfColliision = "snake-self-collision" + EliminatedByStarvation = "starvation" + EliminatedByHeadToHeadCollision = "head-collision" + EliminatedByOutOfBounds = "wall-collision" ) -func (r *StandardRuleset) CreateInitialBoard(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, - }, - ) - } - +func (r *StandardRuleset) CreateInitialBoardState(width int32, height int32, snakeIDs []string) (*BoardState, error) { initialBoardState := &BoardState{ Height: height, Width: width, - Snakes: snakes, + Snakes: make([]Snake, len(snakeIDs)), } - // Place Snakes - if r.isKnownBoardSize(initialBoardState) { - err = r.placeSnakesFixed(initialBoardState) - } else { - err = r.placeSnakesRandomly(initialBoardState) + for i := 0; i < len(snakeIDs); i++ { + initialBoardState.Snakes[i] = Snake{ + ID: snakeIDs[i], + Health: SnakeMaxHealth, + } } + + err := r.placeSnakes(initialBoardState) if err != nil { return nil, err } - // Place Food err = r.placeInitialFood(initialBoardState) if err != nil { return nil, err @@ -60,23 +50,30 @@ func (r *StandardRuleset) CreateInitialBoard(width int32, height int32, snakeIDs return initialBoardState, nil } -func (r *StandardRuleset) placeSnakesFixed(b *BoardState) error { - // Sanity check - if len(b.Snakes) >= 8 { - return errors.New("too many snakes for fixed start positions") +func (r *StandardRuleset) placeSnakes(b *BoardState) error { + if r.isKnownBoardSize(b) { + return r.placeSnakesFixed(b) } + 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 startPoints := []Point{ - {mn, mn}, - {mn, md}, - {mn, mx}, - {md, mn}, - {md, mx}, - {mx, mn}, - {mx, md}, - {mx, mx}, + Point{mn, mn}, + Point{mn, md}, + Point{mn, mx}, + Point{md, mn}, + Point{md, mx}, + Point{mx, mn}, + Point{mx, md}, + Point{mx, mx}, + } + + // Sanity check + if len(b.Snakes) > len(startPoints) { + return errors.New("too many snakes for fixed start positions") } // Randomly order them @@ -85,10 +82,9 @@ func (r *StandardRuleset) placeSnakesFixed(b *BoardState) error { }) // 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}) + for i := 0; i < len(b.Snakes); i++ { + for j := 0; j < SnakeStartSize; j++ { + b.Snakes[i].Body = append(b.Snakes[i].Body, startPoints[i]) } } @@ -96,24 +92,27 @@ func (r *StandardRuleset) placeSnakesFixed(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) + if len(unoccupiedPoints) < len(b.Snakes)-i { + return errors.New("not enough empty squares to place snakes") + } p := unoccupiedPoints[rand.Intn(len(unoccupiedPoints))] - for j := 0; j < 3; j++ { - snake.Body = append(snake.Body, &Point{p.X, p.Y}) + for j := 0; j < SnakeStartSize; j++ { + b.Snakes[i].Body = append(b.Snakes[i].Body, p) } } return nil } 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 } - if b.Height == BOARD_SIZE_MEDIUM && b.Width == BOARD_SIZE_MEDIUM { + if b.Height == BoardSizeMedium && b.Width == BoardSizeMedium { return true } - if b.Height == BOARD_SIZE_LARGE && b.Width == BOARD_SIZE_LARGE { + if b.Height == BoardSizeLarge && b.Width == BoardSizeLarge { return true } return false @@ -124,12 +123,18 @@ func (r *StandardRuleset) placeInitialFood(b *BoardState) error { return nil } -func (r *StandardRuleset) ResolveMoves(prevState *BoardState, moves []*SnakeMove) (*BoardState, error) { - // TODO: DO NOT REFERENCE prevState directly!!!! - // we're technically altering both states +func (r *StandardRuleset) ResolveMoves(prevState *BoardState, moves []SnakeMove) (*BoardState, error) { + // We specifically want to copy prevState, so as not to alter it directly. nextState := &BoardState{ - Snakes: prevState.Snakes, - Food: prevState.Food, + Height: prevState.Height, + 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? @@ -146,6 +151,12 @@ func (r *StandardRuleset) ResolveMoves(prevState *BoardState, moves []*SnakeMove return nil, err } + // TODO: LOG? + err = r.eliminateSnakes(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. @@ -164,78 +175,79 @@ func (r *StandardRuleset) ResolveMoves(prevState *BoardState, moves []*SnakeMove 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 { +func (r *StandardRuleset) moveSnakes(b *BoardState, moves []SnakeMove) error { 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 { - 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 + case MoveDown: + newHead.X = snake.Body[0].X + newHead.Y = snake.Body[0].Y + 1 + 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 + case MoveUp: + newHead.X = snake.Body[0].X + newHead.Y = 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 len(snake.Body) >= 2 { + dX = snake.Body[0].X - snake.Body[1].X + dY = snake.Body[0].Y - 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 + newHead.X = snake.Body[0].X + dX + newHead.Y = 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]...) + snake.Body = append([]Point{newHead}, snake.Body[:len(snake.Body)-1]...) } return nil } func (r *StandardRuleset) reduceSnakeHealth(b *BoardState) error { - for _, snake := range b.Snakes { - snake.Health = snake.Health - 1 + for i := 0; i < len(b.Snakes); i++ { + b.Snakes[i].Health = b.Snakes[i].Health - 1 } return nil } func (r *StandardRuleset) eliminateSnakes(b *BoardState) error { for _, snake := range b.Snakes { - if r.snakeHasStarved(snake) { - snake.EliminatedCause = ELIMINATED_STARVATION - } else if r.snakeIsOutOfBounds(snake, b.Width, b.Height) { - snake.EliminatedCause = ELIMINATED_OUT_OF_BOUNDS + if r.snakeHasStarved(&snake) { + snake.EliminatedCause = EliminatedByStarvation + } else if r.snakeIsOutOfBounds(&snake, b.Width, b.Height) { + snake.EliminatedCause = EliminatedByOutOfBounds } else { for _, other := range b.Snakes { - if r.snakeHasBodyCollided(snake, other) { + if r.snakeHasBodyCollided(&snake, &other) { if snake.ID == other.ID { - snake.EliminatedCause = ELIMINATED_SELF_COLLISION + snake.EliminatedCause = EliminatedBySelfColliision } else { - snake.EliminatedCause = ELIMINATED_COLLISION + snake.EliminatedCause = EliminatedByColliision } break - } else if r.snakeHasLostHeadToHead(snake, other) { - snake.EliminatedCause = ELIMINATED_HEAD_TO_HEAD + } else if r.snakeHasLostHeadToHead(&snake, &other) { + snake.EliminatedCause = EliminatedByHeadToHeadCollision break } } @@ -280,8 +292,8 @@ func (r *StandardRuleset) snakeHasLostHeadToHead(s *Snake, other *Snake) bool { } func (r *StandardRuleset) feedSnakes(b *BoardState) error { - var newFood []*Point - var tail *Point + var newFood []Point + var tail Point for _, food := range b.Food { 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 { foodHasBeenEaten = true // Update snake - snake.Health = SNAKE_MAX_HEALTH + snake.Health = SnakeMaxHealth 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 @@ -305,7 +317,7 @@ func (r *StandardRuleset) feedSnakes(b *BoardState) error { } func (r *StandardRuleset) maybeSpawnFood(b *BoardState, n int) error { - if rand.Float32() <= FOOD_SPAWN_CHANCE { + if rand.Float32() <= FoodSpawnChance { r.spawnFood(b, n) } 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{} for _, p := range b.Food { 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 y := int32(0); y < b.Height; y++ { 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 diff --git a/standard_test.go b/standard_test.go index ce8ebde..f622d0d 100644 --- a/standard_test.go +++ b/standard_test.go @@ -1,6 +1,7 @@ package rulesets import ( + "errors" "math" "math/rand" "testing" @@ -10,26 +11,172 @@ import ( func TestSanity(t *testing.T) { 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( &BoardState{}, - []*SnakeMove{}, + []SnakeMove{}, ) - require.NoError(t, err) 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) { b := &BoardState{ - Snakes: []*Snake{ + Snakes: []Snake{ { ID: "one", - Body: []*Point{{10, 110}, {11, 110}}, + Body: []Point{{10, 110}, {11, 110}}, Health: 111111, }, { ID: "two", - Body: []*Point{{23, 220}, {22, 220}, {21, 220}, {20, 220}}, + Body: []Point{{23, 220}, {22, 220}, {21, 220}, {20, 220}}, Health: 222222, }, }, @@ -37,47 +184,37 @@ func TestMoveSnakes(t *testing.T) { tests := []struct { MoveOne string - ExpectedOne []*Point + ExpectedOne []Point MoveTwo string - ExpectedTwo []*Point + ExpectedTwo []Point }{ { - MOVE_UP, - []*Point{{10, 109}, {10, 110}}, - MOVE_DOWN, - []*Point{{23, 221}, {23, 220}, {22, 220}, {21, 220}}, + MoveUp, []Point{{10, 109}, {10, 110}}, + MoveDown, []Point{{23, 221}, {23, 220}, {22, 220}, {21, 220}}, }, { - MOVE_RIGHT, - []*Point{{11, 109}, {10, 109}}, - MOVE_LEFT, - []*Point{{22, 221}, {23, 221}, {23, 220}, {22, 220}}, + MoveRight, []Point{{11, 109}, {10, 109}}, + MoveLeft, []Point{{22, 221}, {23, 221}, {23, 220}, {22, 220}}, }, { - MOVE_RIGHT, - []*Point{{12, 109}, {11, 109}}, - MOVE_LEFT, - []*Point{{21, 221}, {22, 221}, {23, 221}, {23, 220}}, + MoveRight, []Point{{12, 109}, {11, 109}}, + MoveLeft, []Point{{21, 221}, {22, 221}, {23, 221}, {23, 220}}, }, { - MOVE_RIGHT, - []*Point{{13, 109}, {12, 109}}, - MOVE_LEFT, - []*Point{{20, 221}, {21, 221}, {22, 221}, {23, 221}}, + MoveRight, []Point{{13, 109}, {12, 109}}, + MoveLeft, []Point{{20, 221}, {21, 221}, {22, 221}, {23, 221}}, }, { - MOVE_UP, - []*Point{{13, 108}, {13, 109}}, - MOVE_DOWN, - []*Point{{20, 222}, {20, 221}, {21, 221}, {22, 221}}, + MoveUp, []Point{{13, 108}, {13, 109}}, + MoveDown, []Point{{20, 222}, {20, 221}, {21, 221}, {22, 221}}, }, } r := StandardRuleset{} for _, test := range tests { - moves := []*SnakeMove{ - {Snake: b.Snakes[0], Move: test.MoveOne}, - {Snake: b.Snakes[1], Move: test.MoveTwo}, + moves := []SnakeMove{ + {ID: "one", Move: test.MoveOne}, + {ID: "two", Move: test.MoveTwo}, } 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)) 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)) 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) { tests := []struct { - Body []*Point + Body []Point Move string - Expected []*Point + Expected []Point }{ { - Body: []*Point{{0, 0}}, + Body: []Point{{0, 0}}, Move: "asdf", - Expected: []*Point{{0, -1}}, + Expected: []Point{{0, -1}}, }, { - Body: []*Point{{5, 5}, {5, 5}}, + Body: []Point{{5, 5}, {5, 5}}, Move: "", - Expected: []*Point{{5, 4}, {5, 5}}, + Expected: []Point{{5, 4}, {5, 5}}, }, { - Body: []*Point{{5, 5}, {5, 4}}, - Expected: []*Point{{5, 6}, {5, 5}}, + Body: []Point{{5, 5}, {5, 4}}, + Expected: []Point{{5, 6}, {5, 5}}, }, { - Body: []*Point{{5, 4}, {5, 5}}, - Expected: []*Point{{5, 3}, {5, 4}}, + Body: []Point{{5, 4}, {5, 5}}, + Expected: []Point{{5, 3}, {5, 4}}, }, { - Body: []*Point{{5, 4}, {5, 5}}, - Expected: []*Point{{5, 3}, {5, 4}}, + Body: []Point{{5, 4}, {5, 5}}, + Expected: []Point{{5, 3}, {5, 4}}, }, { - Body: []*Point{{4, 5}, {5, 5}}, - Expected: []*Point{{3, 5}, {4, 5}}, + Body: []Point{{4, 5}, {5, 5}}, + Expected: []Point{{3, 5}, {4, 5}}, }, { - Body: []*Point{{5, 5}, {4, 5}}, - Expected: []*Point{{6, 5}, {5, 5}}, + Body: []Point{{5, 5}, {4, 5}}, + Expected: []Point{{6, 5}, {5, 5}}, }, } r := StandardRuleset{} for _, test := range tests { b := &BoardState{ - Snakes: []*Snake{ - {Body: test.Body}, + Snakes: []Snake{ + {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) 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.Expected), len(b.Snakes[0].Body)) 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) { - var err error - r := StandardRuleset{} b := &BoardState{ - Snakes: []*Snake{ - &Snake{ - Body: []*Point{{0, 0}, {0, 1}}, + Snakes: []Snake{ + { + Body: []Point{{0, 0}, {0, 1}}, Health: 99, }, - &Snake{ - Body: []*Point{{5, 8}, {6, 8}, {7, 8}}, + { + Body: []Point{{5, 8}, {6, 8}, {7, 8}}, Health: 2, }, }, } - err = r.reduceSnakeHealth(b) + r := StandardRuleset{} + err := r.reduceSnakeHealth(b) require.NoError(t, err) require.Equal(t, b.Snakes[0].Health, int32(98)) require.Equal(t, b.Snakes[1].Health, int32(1)) @@ -218,8 +354,8 @@ func TestSnakeHasStarved(t *testing.T) { } func TestSnakeIsOutOfBounds(t *testing.T) { - var boardWidth int32 = 10 - var boardHeight int32 = 100 + boardWidth := int32(10) + boardHeight := int32(100) tests := []struct { Point Point @@ -252,172 +388,166 @@ func TestSnakeIsOutOfBounds(t *testing.T) { {Point{X: math.MaxInt32, Y: math.MaxInt32}, true}, } - var s *Snake r := StandardRuleset{} for _, test := range tests { // Test with point as head - s = &Snake{Body: []*Point{&test.Point}} - require.Equal(t, test.Expected, r.snakeIsOutOfBounds(s, boardWidth, boardHeight), "Head%+v", test.Point) + s := Snake{Body: []Point{test.Point}} + require.Equal(t, test.Expected, r.snakeIsOutOfBounds(&s, boardWidth, boardHeight), "Head%+v", test.Point) // Test with point as body - 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) + 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) } } func TestSnakeHasBodyCollidedSelf(t *testing.T) { tests := []struct { - Body []*Point + Body []Point Expected bool }{ - {[]*Point{{1, 1}}, false}, + {[]Point{{1, 1}}, false}, // Self stacks should self collide // (we rely on snakes moving before we check self-collision on turn one) - {[]*Point{{2, 2}, {2, 2}}, true}, - {[]*Point{{3, 3}, {3, 3}, {3, 3}}, true}, - {[]*Point{{5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}}, true}, + {[]Point{{2, 2}, {2, 2}}, true}, + {[]Point{{3, 3}, {3, 3}, {3, 3}}, true}, + {[]Point{{5, 5}, {5, 5}, {5, 5}, {5, 5}, {5, 5}}, true}, // Non-collision cases - {[]*Point{{0, 0}, {1, 0}, {1, 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}, {3, 0}, {4, 0}}, 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, 3}, {0, 4}}, false}, + {[]Point{{0, 0}, {1, 0}, {1, 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}, {3, 0}, {4, 0}}, 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, 3}, {0, 4}}, false}, // Collision cases - {[]*Point{{0, 0}, {1, 0}, {0, 0}}, true}, - {[]*Point{{0, 0}, {0, 0}, {1, 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{{3, 3}, {3, 4}, {3, 3}, {4, 4}, {4, 5}}, true}, + {[]Point{{0, 0}, {1, 0}, {0, 0}}, true}, + {[]Point{{0, 0}, {0, 0}, {1, 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{{3, 3}, {3, 4}, {3, 3}, {4, 4}, {4, 5}}, true}, } - var s *Snake r := StandardRuleset{} for _, test := range tests { - s = &Snake{Body: test.Body} - require.Equal(t, test.Expected, r.snakeHasBodyCollided(s, s), "Body%q", s.Body) + s := Snake{Body: test.Body} + require.Equal(t, test.Expected, r.snakeHasBodyCollided(&s, &s), "Body%q", s.Body) } } func TestSnakeHasBodyCollidedOther(t *testing.T) { tests := []struct { - SnakeBody []*Point - OtherBody []*Point + SnakeBody []Point + OtherBody []Point Expected bool }{ { // Just heads - []*Point{{0, 0}}, - []*Point{{1, 1}}, + []Point{{0, 0}}, + []Point{{1, 1}}, false, }, { // Head-to-heads are not considered in body collisions - []*Point{{0, 0}}, - []*Point{{0, 0}}, + []Point{{0, 0}}, + []Point{{0, 0}}, false, }, { // Stacked bodies - []*Point{{0, 0}}, - []*Point{{0, 0}, {0, 0}}, + []Point{{0, 0}}, + []Point{{0, 0}, {0, 0}}, true, }, { // Separate stacked bodies - []*Point{{0, 0}, {0, 0}, {0, 0}}, - []*Point{{1, 1}, {1, 1}, {1, 1}}, + []Point{{0, 0}, {0, 0}, {0, 0}}, + []Point{{1, 1}, {1, 1}, {1, 1}}, false, }, { // Stacked bodies, separated heads - []*Point{{0, 0}, {1, 0}, {1, 0}}, - []*Point{{2, 0}, {1, 0}, {1, 0}}, + []Point{{0, 0}, {1, 0}, {1, 0}}, + []Point{{2, 0}, {1, 0}, {1, 0}}, false, }, { // Mid-snake collision - []*Point{{1, 1}}, - []*Point{{0, 1}, {1, 1}, {2, 1}}, + []Point{{1, 1}}, + []Point{{0, 1}, {1, 1}, {2, 1}}, true, }, } - var s *Snake - var o *Snake r := StandardRuleset{} for _, test := range tests { - s = &Snake{Body: test.SnakeBody} - o = &Snake{Body: test.OtherBody} + s := &Snake{Body: test.SnakeBody} + o := &Snake{Body: test.OtherBody} require.Equal(t, test.Expected, r.snakeHasBodyCollided(s, o), "Snake%q Other%q", s.Body, o.Body) } } func TestSnakeHasLostHeadToHead(t *testing.T) { tests := []struct { - SnakeBody []*Point - OtherBody []*Point + SnakeBody []Point + OtherBody []Point Expected bool ExpectedOpposite bool }{ { // Just heads - []*Point{{0, 0}}, - []*Point{{1, 1}}, + []Point{{0, 0}}, + []Point{{1, 1}}, false, false, }, { // Just heads colliding - []*Point{{0, 0}}, - []*Point{{0, 0}}, + []Point{{0, 0}}, + []Point{{0, 0}}, true, true, }, { // One snake larger - []*Point{{0, 0}, {1, 0}, {2, 0}}, - []*Point{{0, 0}}, + []Point{{0, 0}, {1, 0}, {2, 0}}, + []Point{{0, 0}}, false, true, }, { // Other snake equal - []*Point{{0, 0}, {1, 0}, {2, 0}}, - []*Point{{0, 0}, {0, 1}, {0, 2}}, + []Point{{0, 0}, {1, 0}, {2, 0}}, + []Point{{0, 0}, {0, 1}, {0, 2}}, true, true, }, { // Other snake longer - []*Point{{0, 0}, {1, 0}, {2, 0}}, - []*Point{{0, 0}, {0, 1}, {0, 2}, {0, 3}}, + []Point{{0, 0}, {1, 0}, {2, 0}}, + []Point{{0, 0}, {0, 1}, {0, 2}, {0, 3}}, true, false, }, { // Body collision - []*Point{{0, 1}, {1, 1}, {2, 1}}, - []*Point{{0, 0}, {0, 1}, {0, 2}, {0, 3}}, + []Point{{0, 1}, {1, 1}, {2, 1}}, + []Point{{0, 0}, {0, 1}, {0, 2}, {0, 3}}, false, false, }, { // Separate stacked bodies, head collision - []*Point{{3, 10}, {2, 10}, {2, 10}}, - []*Point{{3, 10}, {4, 10}, {4, 10}}, + []Point{{3, 10}, {2, 10}, {2, 10}}, + []Point{{3, 10}, {4, 10}, {4, 10}}, true, true, }, { // Separate stacked bodies, head collision - []*Point{{10, 3}, {10, 2}, {10, 1}, {10, 0}}, - []*Point{{10, 3}, {10, 4}, {10, 5}}, + []Point{{10, 3}, {10, 2}, {10, 1}, {10, 0}}, + []Point{{10, 3}, {10, 4}, {10, 5}}, false, true, }, } - var s *Snake - var o *Snake r := StandardRuleset{} for _, test := range tests { - s = &Snake{Body: test.SnakeBody} - 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.ExpectedOpposite, r.snakeHasLostHeadToHead(o, s), "Snake%q Other%q", s.Body, o.Body) + s := Snake{Body: test.SnakeBody} + 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.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) { r := StandardRuleset{} b := &BoardState{ - Snakes: []*Snake{ - {Body: []*Point{ - {2, 1}, {1, 1}, {1, 2}, {2, 2}, - }}, - }, - Food: []*Point{ - {2, 1}, + Snakes: []Snake{ + {Body: []Point{{2, 1}, {1, 1}, {1, 2}, {2, 2}}}, }, + Food: []Point{{2, 1}}, } err := r.feedSnakes(b) @@ -444,77 +570,77 @@ func TestFeedSnakes(t *testing.T) { func TestGetUnoccupiedPoints(t *testing.T) { tests := []struct { Board *BoardState - Expected []*Point + Expected []Point }{ { &BoardState{ Height: 1, Width: 1, }, - []*Point{{0, 0}}, + []Point{{0, 0}}, }, { &BoardState{ Height: 1, Width: 2, }, - []*Point{{0, 0}, {1, 0}}, + []Point{{0, 0}, {1, 0}}, }, { &BoardState{ Height: 1, Width: 1, - Food: []*Point{{0, 0}, {101, 202}, {-4, -5}}, + Food: []Point{{0, 0}, {101, 202}, {-4, -5}}, }, - []*Point{}, + []Point{}, }, { &BoardState{ Height: 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{ Height: 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{ Height: 4, Width: 1, - Snakes: []*Snake{ - {Body: []*Point{{0, 0}}}, + Snakes: []Snake{ + {Body: []Point{{0, 0}}}, }, }, - []*Point{{0, 1}, {0, 2}, {0, 3}}, + []Point{{0, 1}, {0, 2}, {0, 3}}, }, { &BoardState{ Height: 2, Width: 3, - Snakes: []*Snake{ - {Body: []*Point{{0, 0}, {1, 0}, {1, 1}}}, + Snakes: []Snake{ + {Body: []Point{{0, 0}, {1, 0}, {1, 1}}}, }, }, - []*Point{{0, 1}, {2, 0}, {2, 1}}, + []Point{{0, 1}, {2, 0}, {2, 1}}, }, { &BoardState{ Height: 2, Width: 3, - Food: []*Point{{0, 0}, {1, 0}, {1, 1}, {2, 0}}, - Snakes: []*Snake{ - {Body: []*Point{{0, 0}, {1, 0}, {1, 1}}}, - {Body: []*Point{{0, 1}}}, + Food: []Point{{0, 0}, {1, 0}, {1, 1}, {2, 0}}, + Snakes: []Snake{ + {Body: []Point{{0, 0}, {1, 0}, {1, 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) require.Equal(t, len(test.Expected), len(unoccupiedPoints)) 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) { tests := []struct { Seed int64 - ExpectedFood []*Point + ExpectedFood []Point }{ // Use pre-tested seeds and results - {123, []*Point{}}, - {456, []*Point{}}, - {789, []*Point{}}, - {1024, []*Point{{2, 1}}}, - {511, []*Point{{2, 0}}}, - {165, []*Point{{3, 1}}}, + {123, []Point{}}, + {456, []Point{}}, + {789, []Point{}}, + {1024, []Point{{2, 1}}}, + {511, []Point{{2, 0}}}, + {165, []Point{{3, 1}}}, } r := StandardRuleset{} @@ -547,9 +673,9 @@ func TestMaybeSpawnFood(t *testing.T) { b := &BoardState{ Height: 4, Width: 5, - Snakes: []*Snake{ - {Body: []*Point{{1, 0}, {1, 1}}}, - {Body: []*Point{{0, 1}, {0, 2}, {0, 3}}}, + Snakes: []Snake{ + {Body: []Point{{1, 0}, {1, 1}}}, + {Body: []Point{{0, 1}, {0, 2}, {0, 3}}}, }, } @@ -558,7 +684,7 @@ func TestMaybeSpawnFood(t *testing.T) { require.NoError(t, err) require.Equal(t, len(test.ExpectedFood), len(b.Food), "Seed %d", test.Seed) 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) } } }