Refactor RoyaleRuleset and move hazard damage into StandardRuleset (#50)
* move hazard damage into StandardRuleset * OutOfBounds -> Hazards * remove "out of bounds" in comment * add cases for eating food to hazard damage test
This commit is contained in:
parent
dabbe7dfb5
commit
e416384007
8 changed files with 341 additions and 288 deletions
|
|
@ -135,7 +135,6 @@ var run = func(cmd *cobra.Command, args []string) {
|
||||||
snakes := buildSnakesFromOptions()
|
snakes := buildSnakesFromOptions()
|
||||||
|
|
||||||
var ruleset rules.Ruleset
|
var ruleset rules.Ruleset
|
||||||
var outOfBounds []rules.Point
|
|
||||||
ruleset = getRuleset(Seed, Turn, snakes)
|
ruleset = getRuleset(Seed, Turn, snakes)
|
||||||
state := initializeBoardFromArgs(ruleset, snakes)
|
state := initializeBoardFromArgs(ruleset, snakes)
|
||||||
for _, snake := range snakes {
|
for _, snake := range snakes {
|
||||||
|
|
@ -145,18 +144,12 @@ var run = func(cmd *cobra.Command, args []string) {
|
||||||
for v := false; !v; v, _ = ruleset.IsGameOver(state) {
|
for v := false; !v; v, _ = ruleset.IsGameOver(state) {
|
||||||
Turn++
|
Turn++
|
||||||
ruleset = getRuleset(Seed, Turn, snakes)
|
ruleset = getRuleset(Seed, Turn, snakes)
|
||||||
state = createNextBoardState(ruleset, state, outOfBounds, snakes)
|
state = createNextBoardState(ruleset, state, snakes)
|
||||||
|
|
||||||
// This is a massive hack to make Battle Royale rules work...
|
|
||||||
royaleRuleset, ok := ruleset.(*rules.RoyaleRuleset)
|
|
||||||
if ok {
|
|
||||||
outOfBounds = append([]rules.Point{}, royaleRuleset.OutOfBounds...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ViewMap {
|
if ViewMap {
|
||||||
printMap(state, outOfBounds, Turn)
|
printMap(state, Turn)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[%v]: State: %v OutOfBounds: %v\n", Turn, state, outOfBounds)
|
log.Printf("[%v]: State: %v\n", Turn, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
if TurnDelay > 0 {
|
if TurnDelay > 0 {
|
||||||
|
|
@ -196,12 +189,12 @@ func getRuleset(seed int64, gameTurn int32, snakes []Battlesnake) rules.Ruleset
|
||||||
|
|
||||||
switch GameType {
|
switch GameType {
|
||||||
case "royale":
|
case "royale":
|
||||||
|
standard.HazardDamagePerTurn = 15
|
||||||
royale = rules.RoyaleRuleset{
|
royale = rules.RoyaleRuleset{
|
||||||
StandardRuleset: standard,
|
StandardRuleset: standard,
|
||||||
Seed: seed,
|
Seed: seed,
|
||||||
Turn: gameTurn,
|
Turn: gameTurn,
|
||||||
ShrinkEveryNTurns: 10,
|
ShrinkEveryNTurns: 10,
|
||||||
DamagePerTurn: 1,
|
|
||||||
}
|
}
|
||||||
ruleset = &royale
|
ruleset = &royale
|
||||||
case "squad":
|
case "squad":
|
||||||
|
|
@ -249,7 +242,7 @@ func initializeBoardFromArgs(ruleset rules.Ruleset, snakes []Battlesnake) *rules
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
for _, snake := range snakes {
|
for _, snake := range snakes {
|
||||||
requestBody := getIndividualBoardStateForSnake(state, snake, nil, ruleset)
|
requestBody := getIndividualBoardStateForSnake(state, snake, ruleset)
|
||||||
u, _ := url.ParseRequestURI(snake.URL)
|
u, _ := url.ParseRequestURI(snake.URL)
|
||||||
u.Path = path.Join(u.Path, "start")
|
u.Path = path.Join(u.Path, "start")
|
||||||
_, err = HttpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
_, err = HttpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
||||||
|
|
@ -260,13 +253,13 @@ func initializeBoardFromArgs(ruleset rules.Ruleset, snakes []Battlesnake) *rules
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNextBoardState(ruleset rules.Ruleset, state *rules.BoardState, outOfBounds []rules.Point, snakes []Battlesnake) *rules.BoardState {
|
func createNextBoardState(ruleset rules.Ruleset, state *rules.BoardState, snakes []Battlesnake) *rules.BoardState {
|
||||||
var moves []rules.SnakeMove
|
var moves []rules.SnakeMove
|
||||||
if Sequential {
|
if Sequential {
|
||||||
for _, snake := range snakes {
|
for _, snake := range snakes {
|
||||||
for _, stateSnake := range state.Snakes {
|
for _, stateSnake := range state.Snakes {
|
||||||
if snake.ID == stateSnake.ID && stateSnake.EliminatedCause == rules.NotEliminated {
|
if snake.ID == stateSnake.ID && stateSnake.EliminatedCause == rules.NotEliminated {
|
||||||
moves = append(moves, getMoveForSnake(ruleset, state, snake, outOfBounds))
|
moves = append(moves, getMoveForSnake(ruleset, state, snake))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -278,7 +271,7 @@ func createNextBoardState(ruleset rules.Ruleset, state *rules.BoardState, outOfB
|
||||||
for _, stateSnake := range state.Snakes {
|
for _, stateSnake := range state.Snakes {
|
||||||
if snake.ID == stateSnake.ID && stateSnake.EliminatedCause == rules.NotEliminated {
|
if snake.ID == stateSnake.ID && stateSnake.EliminatedCause == rules.NotEliminated {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go getConcurrentMoveForSnake(&wg, ruleset, state, snake, outOfBounds, c)
|
go getConcurrentMoveForSnake(&wg, ruleset, state, snake, c)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -303,13 +296,13 @@ func createNextBoardState(ruleset rules.Ruleset, state *rules.BoardState, outOfB
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|
||||||
func getConcurrentMoveForSnake(wg *sync.WaitGroup, ruleset rules.Ruleset, state *rules.BoardState, snake Battlesnake, outOfBounds []rules.Point, c chan rules.SnakeMove) {
|
func getConcurrentMoveForSnake(wg *sync.WaitGroup, ruleset rules.Ruleset, state *rules.BoardState, snake Battlesnake, c chan rules.SnakeMove) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
c <- getMoveForSnake(ruleset, state, snake, outOfBounds)
|
c <- getMoveForSnake(ruleset, state, snake)
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMoveForSnake(ruleset rules.Ruleset, state *rules.BoardState, snake Battlesnake, outOfBounds []rules.Point) rules.SnakeMove {
|
func getMoveForSnake(ruleset rules.Ruleset, state *rules.BoardState, snake Battlesnake) rules.SnakeMove {
|
||||||
requestBody := getIndividualBoardStateForSnake(state, snake, outOfBounds, ruleset)
|
requestBody := getIndividualBoardStateForSnake(state, snake, ruleset)
|
||||||
u, _ := url.ParseRequestURI(snake.URL)
|
u, _ := url.ParseRequestURI(snake.URL)
|
||||||
u.Path = path.Join(u.Path, "move")
|
u.Path = path.Join(u.Path, "move")
|
||||||
res, err := HttpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
res, err := HttpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
||||||
|
|
@ -336,7 +329,7 @@ func getMoveForSnake(ruleset rules.Ruleset, state *rules.BoardState, snake Battl
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendEndRequest(ruleset rules.Ruleset, state *rules.BoardState, snake Battlesnake) {
|
func sendEndRequest(ruleset rules.Ruleset, state *rules.BoardState, snake Battlesnake) {
|
||||||
requestBody := getIndividualBoardStateForSnake(state, snake, nil, ruleset)
|
requestBody := getIndividualBoardStateForSnake(state, snake, ruleset)
|
||||||
u, _ := url.ParseRequestURI(snake.URL)
|
u, _ := url.ParseRequestURI(snake.URL)
|
||||||
u.Path = path.Join(u.Path, "end")
|
u.Path = path.Join(u.Path, "end")
|
||||||
_, err := HttpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
_, err := HttpClient.Post(u.String(), "application/json", bytes.NewBuffer(requestBody))
|
||||||
|
|
@ -345,7 +338,7 @@ func sendEndRequest(ruleset rules.Ruleset, state *rules.BoardState, snake Battle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIndividualBoardStateForSnake(state *rules.BoardState, snake Battlesnake, outOfBounds []rules.Point, ruleset rules.Ruleset) []byte {
|
func getIndividualBoardStateForSnake(state *rules.BoardState, snake Battlesnake, ruleset rules.Ruleset) []byte {
|
||||||
var youSnake rules.Snake
|
var youSnake rules.Snake
|
||||||
for _, snk := range state.Snakes {
|
for _, snk := range state.Snakes {
|
||||||
if snake.ID == snk.ID {
|
if snake.ID == snk.ID {
|
||||||
|
|
@ -363,7 +356,7 @@ func getIndividualBoardStateForSnake(state *rules.BoardState, snake Battlesnake,
|
||||||
Height: state.Height,
|
Height: state.Height,
|
||||||
Width: state.Width,
|
Width: state.Width,
|
||||||
Food: coordFromPointArray(state.Food),
|
Food: coordFromPointArray(state.Food),
|
||||||
Hazards: coordFromPointArray(outOfBounds),
|
Hazards: coordFromPointArray(state.Hazards),
|
||||||
Snakes: buildSnakesResponse(state.Snakes),
|
Snakes: buildSnakesResponse(state.Snakes),
|
||||||
},
|
},
|
||||||
You: snakeResponseFromSnake(youSnake),
|
You: snakeResponseFromSnake(youSnake),
|
||||||
|
|
@ -490,7 +483,7 @@ func buildSnakesFromOptions() []Battlesnake {
|
||||||
return snakes
|
return snakes
|
||||||
}
|
}
|
||||||
|
|
||||||
func printMap(state *rules.BoardState, outOfBounds []rules.Point, gameTurn int32) {
|
func printMap(state *rules.BoardState, gameTurn int32) {
|
||||||
var o bytes.Buffer
|
var o bytes.Buffer
|
||||||
o.WriteString(fmt.Sprintf("Ruleset: %s, Seed: %d, Turn: %v\n", GameType, Seed, gameTurn))
|
o.WriteString(fmt.Sprintf("Ruleset: %s, Seed: %d, Turn: %v\n", GameType, Seed, gameTurn))
|
||||||
board := make([][]rune, state.Width)
|
board := make([][]rune, state.Width)
|
||||||
|
|
@ -502,10 +495,10 @@ func printMap(state *rules.BoardState, outOfBounds []rules.Point, gameTurn int32
|
||||||
board[x][y] = '◦'
|
board[x][y] = '◦'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, oob := range outOfBounds {
|
for _, oob := range state.Hazards {
|
||||||
board[oob.X][oob.Y] = '░'
|
board[oob.X][oob.Y] = '░'
|
||||||
}
|
}
|
||||||
o.WriteString(fmt.Sprintf("Hazards ░: %v\n", outOfBounds))
|
o.WriteString(fmt.Sprintf("Hazards ░: %v\n", state.Hazards))
|
||||||
for _, f := range state.Food {
|
for _, f := range state.Food {
|
||||||
board[f.X][f.Y] = '⚕'
|
board[f.X][f.Y] = '⚕'
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
package commands
|
package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/BattlesnakeOfficial/rules"
|
"github.com/BattlesnakeOfficial/rules"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetIndividualBoardStateForSnake(t *testing.T) {
|
func TestGetIndividualBoardStateForSnake(t *testing.T) {
|
||||||
|
|
@ -14,8 +15,8 @@ func TestGetIndividualBoardStateForSnake(t *testing.T) {
|
||||||
Width: 11,
|
Width: 11,
|
||||||
Snakes: []rules.Snake{s1, s2},
|
Snakes: []rules.Snake{s1, s2},
|
||||||
}
|
}
|
||||||
bs := Battlesnake{Name: "one", URL: "", ID: "one"}
|
snake := Battlesnake{Name: "one", URL: "", ID: "one"}
|
||||||
requestBody := getIndividualBoardStateForSnake(state, bs, nil, &rules.StandardRuleset{})
|
requestBody := getIndividualBoardStateForSnake(state, snake, &rules.StandardRuleset{})
|
||||||
|
|
||||||
expected := "{\"game\":{\"id\":\"\",\"timeout\":500,\"ruleset\":{\"name\":\"standard\",\"version\":\"cli\"}},\"turn\":0,\"board\":{\"height\":11,\"width\":11,\"food\":[],\"hazards\":[],\"snakes\":[{\"id\":\"one\",\"name\":\"\",\"health\":0,\"body\":[{\"x\":3,\"y\":3}],\"latency\":\"0\",\"head\":{\"x\":3,\"y\":3},\"length\":1,\"shout\":\"\",\"squad\":\"\"},{\"id\":\"two\",\"name\":\"\",\"health\":0,\"body\":[{\"x\":4,\"y\":3}],\"latency\":\"0\",\"head\":{\"x\":4,\"y\":3},\"length\":1,\"shout\":\"\",\"squad\":\"\"}]},\"you\":{\"id\":\"one\",\"name\":\"\",\"health\":0,\"body\":[{\"x\":3,\"y\":3}],\"latency\":\"0\",\"head\":{\"x\":3,\"y\":3},\"length\":1,\"shout\":\"\",\"squad\":\"\"}}"
|
expected := "{\"game\":{\"id\":\"\",\"timeout\":500,\"ruleset\":{\"name\":\"standard\",\"version\":\"cli\"}},\"turn\":0,\"board\":{\"height\":11,\"width\":11,\"food\":[],\"hazards\":[],\"snakes\":[{\"id\":\"one\",\"name\":\"\",\"health\":0,\"body\":[{\"x\":3,\"y\":3}],\"latency\":\"0\",\"head\":{\"x\":3,\"y\":3},\"length\":1,\"shout\":\"\",\"squad\":\"\"},{\"id\":\"two\",\"name\":\"\",\"health\":0,\"body\":[{\"x\":4,\"y\":3}],\"latency\":\"0\",\"head\":{\"x\":4,\"y\":3},\"length\":1,\"shout\":\"\",\"squad\":\"\"}]},\"you\":{\"id\":\"one\",\"name\":\"\",\"health\":0,\"body\":[{\"x\":3,\"y\":3}],\"latency\":\"0\",\"head\":{\"x\":3,\"y\":3},\"length\":1,\"shout\":\"\",\"squad\":\"\"}}"
|
||||||
require.Equal(t, expected, string(requestBody))
|
require.Equal(t, expected, string(requestBody))
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,7 @@ func TestConstrictorCreateNextBoardState(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
nextState, err := r.CreateNextBoardState(test.prevState, test.moves)
|
nextState, err := r.CreateNextBoardState(test.prevState, test.moves)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, test.expectedState, nextState)
|
require.Equal(t, test.expectedState.Food, nextState.Food)
|
||||||
|
require.Equal(t, test.expectedState.Snakes, nextState.Snakes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
66
royale.go
66
royale.go
|
|
@ -10,19 +10,16 @@ type RoyaleRuleset struct {
|
||||||
|
|
||||||
Seed int64
|
Seed int64
|
||||||
|
|
||||||
|
// TODO: move Turn into BoardState?
|
||||||
Turn int32
|
Turn int32
|
||||||
ShrinkEveryNTurns int32
|
ShrinkEveryNTurns int32
|
||||||
DamagePerTurn int32
|
|
||||||
|
|
||||||
// Output
|
|
||||||
OutOfBounds []Point
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RoyaleRuleset) Name() string { return "royale" }
|
func (r *RoyaleRuleset) Name() string { return "royale" }
|
||||||
|
|
||||||
func (r *RoyaleRuleset) CreateNextBoardState(prevState *BoardState, moves []SnakeMove) (*BoardState, error) {
|
func (r *RoyaleRuleset) CreateNextBoardState(prevState *BoardState, moves []SnakeMove) (*BoardState, error) {
|
||||||
if r.ShrinkEveryNTurns < 1 {
|
if r.StandardRuleset.HazardDamagePerTurn < 1 {
|
||||||
return nil, errors.New("royale game must shrink at least every turn")
|
return nil, errors.New("royale damage per turn must be greater than zero")
|
||||||
}
|
}
|
||||||
|
|
||||||
nextBoardState, err := r.StandardRuleset.CreateNextBoardState(prevState, moves)
|
nextBoardState, err := r.StandardRuleset.CreateNextBoardState(prevState, moves)
|
||||||
|
|
@ -30,26 +27,8 @@ func (r *RoyaleRuleset) CreateNextBoardState(prevState *BoardState, moves []Snak
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Algorithm:
|
// Royale's only job is now to populate the hazards for next turn - StandardRuleset takes care of applying hazard damage.
|
||||||
// - Populate OOB for last turn
|
err = r.populateHazards(nextBoardState, r.Turn)
|
||||||
// - Apply damage to snake heads that are OOB
|
|
||||||
// - Re-populate OOB for this turn
|
|
||||||
// ---> This means damage on board shrinks doesn't hit until the following turn.
|
|
||||||
|
|
||||||
// TODO: LOG?
|
|
||||||
err = r.populateOutOfBounds(nextBoardState, r.Turn-1)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: LOG?
|
|
||||||
err = r.damageOutOfBounds(nextBoardState)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: LOG?
|
|
||||||
err = r.populateOutOfBounds(nextBoardState, r.Turn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -57,11 +36,11 @@ func (r *RoyaleRuleset) CreateNextBoardState(prevState *BoardState, moves []Snak
|
||||||
return nextBoardState, nil
|
return nextBoardState, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RoyaleRuleset) populateOutOfBounds(b *BoardState, turn int32) error {
|
func (r *RoyaleRuleset) populateHazards(b *BoardState, turn int32) error {
|
||||||
r.OutOfBounds = []Point{}
|
b.Hazards = []Point{}
|
||||||
|
|
||||||
if r.ShrinkEveryNTurns < 1 {
|
if r.ShrinkEveryNTurns < 1 {
|
||||||
return errors.New("royale game must shrink at least every turn")
|
return errors.New("royale game can't shrink more frequently than every turn")
|
||||||
}
|
}
|
||||||
|
|
||||||
if turn < r.ShrinkEveryNTurns {
|
if turn < r.ShrinkEveryNTurns {
|
||||||
|
|
@ -89,34 +68,7 @@ func (r *RoyaleRuleset) populateOutOfBounds(b *BoardState, turn int32) error {
|
||||||
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 x < minX || x > maxX || y < minY || y > maxY {
|
if x < minX || x > maxX || y < minY || y > maxY {
|
||||||
r.OutOfBounds = append(r.OutOfBounds, Point{x, y})
|
b.Hazards = append(b.Hazards, Point{x, y})
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RoyaleRuleset) damageOutOfBounds(b *BoardState) error {
|
|
||||||
if r.DamagePerTurn < 1 {
|
|
||||||
return errors.New("royale damage per turn must be greater than zero")
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(b.Snakes); i++ {
|
|
||||||
snake := &b.Snakes[i]
|
|
||||||
if snake.EliminatedCause == NotEliminated {
|
|
||||||
head := snake.Body[0]
|
|
||||||
for _, p := range r.OutOfBounds {
|
|
||||||
if head == p {
|
|
||||||
// Snake is now out of bounds, reduce health
|
|
||||||
snake.Health = snake.Health - r.DamagePerTurn
|
|
||||||
if snake.Health < 0 {
|
|
||||||
snake.Health = 0
|
|
||||||
}
|
|
||||||
if r.StandardRuleset.snakeIsOutOfHealth(snake) {
|
|
||||||
snake.EliminatedCause = EliminatedByOutOfHealth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
208
royale_test.go
208
royale_test.go
|
|
@ -13,14 +13,20 @@ func TestRoyaleRulesetInterface(t *testing.T) {
|
||||||
|
|
||||||
func TestRoyaleDefaultSanity(t *testing.T) {
|
func TestRoyaleDefaultSanity(t *testing.T) {
|
||||||
boardState := &BoardState{}
|
boardState := &BoardState{}
|
||||||
r := RoyaleRuleset{}
|
r := RoyaleRuleset{StandardRuleset: StandardRuleset{HazardDamagePerTurn: 1}, ShrinkEveryNTurns: 0}
|
||||||
_, err := r.CreateNextBoardState(boardState, []SnakeMove{})
|
_, err := r.CreateNextBoardState(boardState, []SnakeMove{})
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.Equal(t, errors.New("royale game must shrink at least every turn"), err)
|
require.Equal(t, errors.New("royale game can't shrink more frequently than every turn"), err)
|
||||||
|
|
||||||
r = RoyaleRuleset{ShrinkEveryNTurns: 1, DamagePerTurn: 1}
|
r = RoyaleRuleset{ShrinkEveryNTurns: 1}
|
||||||
_, err = r.CreateNextBoardState(boardState, []SnakeMove{})
|
_, err = r.CreateNextBoardState(boardState, []SnakeMove{})
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, errors.New("royale damage per turn must be greater than zero"), err)
|
||||||
|
|
||||||
|
r = RoyaleRuleset{StandardRuleset: StandardRuleset{HazardDamagePerTurn: 1}, ShrinkEveryNTurns: 1}
|
||||||
|
boardState, err = r.CreateNextBoardState(boardState, []SnakeMove{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
require.Len(t, boardState.Hazards, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRoyaleName(t *testing.T) {
|
func TestRoyaleName(t *testing.T) {
|
||||||
|
|
@ -28,7 +34,7 @@ func TestRoyaleName(t *testing.T) {
|
||||||
require.Equal(t, "royale", r.Name())
|
require.Equal(t, "royale", r.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRoyaleOutOfBounds(t *testing.T) {
|
func TestRoyaleHazards(t *testing.T) {
|
||||||
seed := int64(25543234525)
|
seed := int64(25543234525)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Width int32
|
Width int32
|
||||||
|
|
@ -36,48 +42,48 @@ func TestRoyaleOutOfBounds(t *testing.T) {
|
||||||
Turn int32
|
Turn int32
|
||||||
ShrinkEveryNTurns int32
|
ShrinkEveryNTurns int32
|
||||||
Error error
|
Error error
|
||||||
ExpectedOutOfBounds []Point
|
ExpectedHazards []Point
|
||||||
}{
|
}{
|
||||||
{Error: errors.New("royale game must shrink at least every turn")},
|
{Error: errors.New("royale game can't shrink more frequently than every turn")},
|
||||||
{ShrinkEveryNTurns: 1, ExpectedOutOfBounds: []Point{}},
|
{ShrinkEveryNTurns: 1, ExpectedHazards: []Point{}},
|
||||||
{Turn: 1, ShrinkEveryNTurns: 1, ExpectedOutOfBounds: []Point{}},
|
{Turn: 1, ShrinkEveryNTurns: 1, ExpectedHazards: []Point{}},
|
||||||
{Width: 3, Height: 3, Turn: 1, ShrinkEveryNTurns: 10, ExpectedOutOfBounds: []Point{}},
|
{Width: 3, Height: 3, Turn: 1, ShrinkEveryNTurns: 10, ExpectedHazards: []Point{}},
|
||||||
{Width: 3, Height: 3, Turn: 9, ShrinkEveryNTurns: 10, ExpectedOutOfBounds: []Point{}},
|
{Width: 3, Height: 3, Turn: 9, ShrinkEveryNTurns: 10, ExpectedHazards: []Point{}},
|
||||||
{
|
{
|
||||||
Width: 3, Height: 3, Turn: 10, ShrinkEveryNTurns: 10,
|
Width: 3, Height: 3, Turn: 10, ShrinkEveryNTurns: 10,
|
||||||
ExpectedOutOfBounds: []Point{{0, 0}, {0, 1}, {0, 2}},
|
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Width: 3, Height: 3, Turn: 11, ShrinkEveryNTurns: 10,
|
Width: 3, Height: 3, Turn: 11, ShrinkEveryNTurns: 10,
|
||||||
ExpectedOutOfBounds: []Point{{0, 0}, {0, 1}, {0, 2}},
|
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Width: 3, Height: 3, Turn: 19, ShrinkEveryNTurns: 10,
|
Width: 3, Height: 3, Turn: 19, ShrinkEveryNTurns: 10,
|
||||||
ExpectedOutOfBounds: []Point{{0, 0}, {0, 1}, {0, 2}},
|
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Width: 3, Height: 3, Turn: 20, ShrinkEveryNTurns: 10,
|
Width: 3, Height: 3, Turn: 20, ShrinkEveryNTurns: 10,
|
||||||
ExpectedOutOfBounds: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 2}, {2, 2}},
|
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 2}, {2, 2}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Width: 3, Height: 3, Turn: 31, ShrinkEveryNTurns: 10,
|
Width: 3, Height: 3, Turn: 31, ShrinkEveryNTurns: 10,
|
||||||
ExpectedOutOfBounds: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 1}, {1, 2}, {2, 1}, {2, 2}},
|
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 1}, {1, 2}, {2, 1}, {2, 2}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Width: 3, Height: 3, Turn: 42, ShrinkEveryNTurns: 10,
|
Width: 3, Height: 3, Turn: 42, ShrinkEveryNTurns: 10,
|
||||||
ExpectedOutOfBounds: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {2, 0}, {2, 1}, {2, 2}},
|
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {2, 0}, {2, 1}, {2, 2}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Width: 3, Height: 3, Turn: 53, ShrinkEveryNTurns: 10,
|
Width: 3, Height: 3, Turn: 53, ShrinkEveryNTurns: 10,
|
||||||
ExpectedOutOfBounds: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {2, 0}, {2, 1}, {2, 2}},
|
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {2, 0}, {2, 1}, {2, 2}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Width: 3, Height: 3, Turn: 64, ShrinkEveryNTurns: 10,
|
Width: 3, Height: 3, Turn: 64, ShrinkEveryNTurns: 10,
|
||||||
ExpectedOutOfBounds: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {2, 0}, {2, 1}, {2, 2}},
|
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {2, 0}, {2, 1}, {2, 2}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Width: 3, Height: 3, Turn: 6987, ShrinkEveryNTurns: 10,
|
Width: 3, Height: 3, Turn: 6987, ShrinkEveryNTurns: 10,
|
||||||
ExpectedOutOfBounds: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {2, 0}, {2, 1}, {2, 2}},
|
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {2, 0}, {2, 1}, {2, 2}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,19 +93,22 @@ func TestRoyaleOutOfBounds(t *testing.T) {
|
||||||
Height: test.Height,
|
Height: test.Height,
|
||||||
}
|
}
|
||||||
r := RoyaleRuleset{
|
r := RoyaleRuleset{
|
||||||
|
StandardRuleset: StandardRuleset{
|
||||||
|
HazardDamagePerTurn: 1,
|
||||||
|
},
|
||||||
Seed: seed,
|
Seed: seed,
|
||||||
Turn: test.Turn,
|
Turn: test.Turn,
|
||||||
ShrinkEveryNTurns: test.ShrinkEveryNTurns,
|
ShrinkEveryNTurns: test.ShrinkEveryNTurns,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := r.populateOutOfBounds(b, test.Turn)
|
err := r.populateHazards(b, test.Turn)
|
||||||
require.Equal(t, test.Error, err)
|
require.Equal(t, test.Error, err)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Obstacles should match
|
// Obstacles should match
|
||||||
require.Equal(t, test.ExpectedOutOfBounds, r.OutOfBounds)
|
require.Equal(t, test.ExpectedHazards, b.Hazards)
|
||||||
for _, expectedP := range test.ExpectedOutOfBounds {
|
for _, expectedP := range test.ExpectedHazards {
|
||||||
wasFound := false
|
wasFound := false
|
||||||
for _, actualP := range r.OutOfBounds {
|
for _, actualP := range b.Hazards {
|
||||||
if expectedP == actualP {
|
if expectedP == actualP {
|
||||||
wasFound = true
|
wasFound = true
|
||||||
break
|
break
|
||||||
|
|
@ -111,134 +120,57 @@ func TestRoyaleOutOfBounds(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRoyaleDamageOutOfBounds(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
Snakes []Snake
|
|
||||||
OutOfBounds []Point
|
|
||||||
ExpectedEliminatedCauses []string
|
|
||||||
ExpectedEliminatedByIDs []string
|
|
||||||
}{
|
|
||||||
{},
|
|
||||||
{
|
|
||||||
Snakes: []Snake{{Body: []Point{{0, 0}}}},
|
|
||||||
OutOfBounds: []Point{},
|
|
||||||
ExpectedEliminatedCauses: []string{NotEliminated},
|
|
||||||
ExpectedEliminatedByIDs: []string{""},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Snakes: []Snake{{Body: []Point{{0, 0}}}},
|
|
||||||
OutOfBounds: []Point{{0, 0}},
|
|
||||||
ExpectedEliminatedCauses: []string{EliminatedByOutOfHealth},
|
|
||||||
ExpectedEliminatedByIDs: []string{""},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Snakes: []Snake{{Body: []Point{{0, 0}, {1, 0}, {2, 0}}}},
|
|
||||||
OutOfBounds: []Point{{1, 0}, {2, 0}},
|
|
||||||
ExpectedEliminatedCauses: []string{NotEliminated},
|
|
||||||
ExpectedEliminatedByIDs: []string{""},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Snakes: []Snake{
|
|
||||||
{Body: []Point{{0, 0}, {1, 0}, {2, 0}}},
|
|
||||||
{Body: []Point{{3, 3}, {3, 4}, {3, 5}, {3, 6}}},
|
|
||||||
},
|
|
||||||
OutOfBounds: []Point{{1, 0}, {2, 0}, {3, 4}, {3, 5}, {3, 6}},
|
|
||||||
ExpectedEliminatedCauses: []string{NotEliminated, NotEliminated},
|
|
||||||
ExpectedEliminatedByIDs: []string{"", ""},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Snakes: []Snake{
|
|
||||||
{Body: []Point{{0, 0}, {1, 0}, {2, 0}}},
|
|
||||||
{Body: []Point{{3, 3}, {3, 4}, {3, 5}, {3, 6}}},
|
|
||||||
},
|
|
||||||
OutOfBounds: []Point{{3, 3}},
|
|
||||||
ExpectedEliminatedCauses: []string{NotEliminated, EliminatedByOutOfHealth},
|
|
||||||
ExpectedEliminatedByIDs: []string{"", ""},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
b := &BoardState{Snakes: test.Snakes}
|
|
||||||
r := RoyaleRuleset{OutOfBounds: test.OutOfBounds, DamagePerTurn: 100}
|
|
||||||
err := r.damageOutOfBounds(b)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
for i, snake := range b.Snakes {
|
|
||||||
require.Equal(t, test.ExpectedEliminatedCauses[i], snake.EliminatedCause)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoyaleDamagePerTurn(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
Health int32
|
|
||||||
DamagePerTurn int32
|
|
||||||
ExpectedHealth int32
|
|
||||||
ExpectedEliminationCause string
|
|
||||||
Error error
|
|
||||||
}{
|
|
||||||
{100, 0, 100, NotEliminated, errors.New("royale damage per turn must be greater than zero")},
|
|
||||||
{100, -100, 100, NotEliminated, errors.New("royale damage per turn must be greater than zero")},
|
|
||||||
{100, 1, 99, NotEliminated, nil},
|
|
||||||
{100, 99, 1, NotEliminated, nil},
|
|
||||||
{100, 100, 0, EliminatedByOutOfHealth, nil},
|
|
||||||
{100, 101, 0, EliminatedByOutOfHealth, nil},
|
|
||||||
{100, 999, 0, EliminatedByOutOfHealth, nil},
|
|
||||||
{2, 1, 1, NotEliminated, nil},
|
|
||||||
{1, 1, 0, EliminatedByOutOfHealth, nil},
|
|
||||||
{1, 999, 0, EliminatedByOutOfHealth, nil},
|
|
||||||
{0, 1, 0, EliminatedByOutOfHealth, nil},
|
|
||||||
{0, 999, 0, EliminatedByOutOfHealth, nil},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
b := &BoardState{Snakes: []Snake{{Health: test.Health, Body: []Point{{0, 0}}}}}
|
|
||||||
r := RoyaleRuleset{OutOfBounds: []Point{{0, 0}}, DamagePerTurn: test.DamagePerTurn}
|
|
||||||
|
|
||||||
err := r.damageOutOfBounds(b)
|
|
||||||
require.Equal(t, test.Error, err)
|
|
||||||
require.Equal(t, test.ExpectedHealth, b.Snakes[0].Health)
|
|
||||||
require.Equal(t, test.ExpectedEliminationCause, b.Snakes[0].EliminatedCause)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestRoyalDamageNextTurn(t *testing.T) {
|
func TestRoyalDamageNextTurn(t *testing.T) {
|
||||||
seed := int64(45897034512311)
|
seed := int64(45897034512311)
|
||||||
|
|
||||||
b := &BoardState{Width: 10, Height: 10, Snakes: []Snake{{ID: "one", Health: 100, Body: []Point{{9, 1}}}}}
|
base := &BoardState{Width: 10, Height: 10, Snakes: []Snake{{ID: "one", Health: 100, Body: []Point{{9, 1}, {9, 1}, {9, 1}}}}}
|
||||||
r := RoyaleRuleset{Seed: seed, ShrinkEveryNTurns: 10, DamagePerTurn: 30}
|
r := RoyaleRuleset{StandardRuleset: StandardRuleset{HazardDamagePerTurn: 30}, Seed: seed, ShrinkEveryNTurns: 10}
|
||||||
m := []SnakeMove{{ID: "one", Move: "down"}}
|
m := []SnakeMove{{ID: "one", Move: "down"}}
|
||||||
|
|
||||||
r.Turn = 10
|
r.Turn = 10
|
||||||
n, err := r.CreateNextBoardState(b, m)
|
err := r.populateHazards(base, r.Turn-1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, NotEliminated, n.Snakes[0].EliminatedCause)
|
next, err := r.CreateNextBoardState(base, m)
|
||||||
require.Equal(t, int32(99), n.Snakes[0].Health)
|
require.NoError(t, err)
|
||||||
require.Equal(t, Point{9, 0}, n.Snakes[0].Body[0])
|
require.Equal(t, NotEliminated, next.Snakes[0].EliminatedCause)
|
||||||
require.Equal(t, 10, len(r.OutOfBounds)) // X = 0
|
require.Equal(t, int32(99), next.Snakes[0].Health)
|
||||||
|
require.Equal(t, Point{9, 0}, next.Snakes[0].Body[0])
|
||||||
|
require.Equal(t, 10, len(next.Hazards)) // X = 0
|
||||||
|
|
||||||
r.Turn = 20
|
r.Turn = 20
|
||||||
n, err = r.CreateNextBoardState(b, m)
|
err = r.populateHazards(base, r.Turn-1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, NotEliminated, n.Snakes[0].EliminatedCause)
|
next, err = r.CreateNextBoardState(base, m)
|
||||||
require.Equal(t, int32(99), n.Snakes[0].Health)
|
require.NoError(t, err)
|
||||||
require.Equal(t, Point{9, 0}, n.Snakes[0].Body[0])
|
require.Equal(t, NotEliminated, next.Snakes[0].EliminatedCause)
|
||||||
require.Equal(t, 20, len(r.OutOfBounds)) // X = 9
|
require.Equal(t, int32(99), next.Snakes[0].Health)
|
||||||
|
require.Equal(t, Point{9, 0}, next.Snakes[0].Body[0])
|
||||||
|
require.Equal(t, 20, len(next.Hazards)) // X = 9
|
||||||
|
|
||||||
r.Turn = 21
|
r.Turn = 21
|
||||||
n, err = r.CreateNextBoardState(b, m)
|
err = r.populateHazards(base, r.Turn-1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, NotEliminated, n.Snakes[0].EliminatedCause)
|
next, err = r.CreateNextBoardState(base, m)
|
||||||
require.Equal(t, int32(69), n.Snakes[0].Health)
|
require.NoError(t, err)
|
||||||
require.Equal(t, Point{9, 0}, n.Snakes[0].Body[0])
|
require.Equal(t, NotEliminated, next.Snakes[0].EliminatedCause)
|
||||||
require.Equal(t, 20, len(r.OutOfBounds))
|
require.Equal(t, int32(69), next.Snakes[0].Health)
|
||||||
|
require.Equal(t, Point{9, 0}, next.Snakes[0].Body[0])
|
||||||
|
require.Equal(t, 20, len(next.Hazards))
|
||||||
|
|
||||||
b.Snakes[0].Health = 15
|
base.Snakes[0].Health = 15
|
||||||
n, err = r.CreateNextBoardState(b, m)
|
next, err = r.CreateNextBoardState(base, m)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, EliminatedByOutOfHealth, n.Snakes[0].EliminatedCause)
|
require.Equal(t, EliminatedByOutOfHealth, next.Snakes[0].EliminatedCause)
|
||||||
require.Equal(t, int32(0), n.Snakes[0].Health)
|
require.Equal(t, int32(0), next.Snakes[0].Health)
|
||||||
require.Equal(t, Point{9, 0}, n.Snakes[0].Body[0])
|
require.Equal(t, Point{9, 0}, next.Snakes[0].Body[0])
|
||||||
require.Equal(t, 20, len(r.OutOfBounds))
|
require.Equal(t, 20, len(next.Hazards))
|
||||||
|
|
||||||
|
base.Food = append(base.Food, Point{9, 0})
|
||||||
|
next, err = r.CreateNextBoardState(base, m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, Point{9, 0}, next.Snakes[0].Body[0])
|
||||||
|
require.Equal(t, NotEliminated, next.Snakes[0].EliminatedCause)
|
||||||
|
require.Equal(t, int32(100), next.Snakes[0].Health)
|
||||||
|
require.Equal(t, Point{9, 0}, next.Snakes[0].Body[0])
|
||||||
|
require.Equal(t, 20, len(next.Hazards))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,6 +51,7 @@ type BoardState struct {
|
||||||
Width int32
|
Width int32
|
||||||
Food []Point
|
Food []Point
|
||||||
Snakes []Snake
|
Snakes []Snake
|
||||||
|
Hazards []Point
|
||||||
}
|
}
|
||||||
|
|
||||||
type SnakeMove struct {
|
type SnakeMove struct {
|
||||||
|
|
|
||||||
66
standard.go
66
standard.go
|
|
@ -8,6 +8,7 @@ import (
|
||||||
type StandardRuleset struct {
|
type StandardRuleset struct {
|
||||||
FoodSpawnChance int32 // [0, 100]
|
FoodSpawnChance int32 // [0, 100]
|
||||||
MinimumFood int32
|
MinimumFood int32
|
||||||
|
HazardDamagePerTurn int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) Name() string { return "standard" }
|
func (r *StandardRuleset) Name() string { return "standard" }
|
||||||
|
|
@ -50,14 +51,14 @@ func (r *StandardRuleset) placeSnakesFixed(b *BoardState) error {
|
||||||
// Create start 8 points
|
// 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{
|
||||||
Point{mn, mn},
|
{mn, mn},
|
||||||
Point{mn, md},
|
{mn, md},
|
||||||
Point{mn, mx},
|
{mn, mx},
|
||||||
Point{md, mn},
|
{md, mn},
|
||||||
Point{md, mx},
|
{md, mx},
|
||||||
Point{mx, mn},
|
{mx, mn},
|
||||||
Point{mx, md},
|
{mx, md},
|
||||||
Point{mx, mx},
|
{mx, mx},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanity check
|
// Sanity check
|
||||||
|
|
@ -107,10 +108,10 @@ func (r *StandardRuleset) placeFoodFixed(b *BoardState) error {
|
||||||
for i := 0; i < len(b.Snakes); i++ {
|
for i := 0; i < len(b.Snakes); i++ {
|
||||||
snakeHead := b.Snakes[i].Body[0]
|
snakeHead := b.Snakes[i].Body[0]
|
||||||
possibleFoodLocations := []Point{
|
possibleFoodLocations := []Point{
|
||||||
Point{snakeHead.X - 1, snakeHead.Y - 1},
|
{snakeHead.X - 1, snakeHead.Y - 1},
|
||||||
Point{snakeHead.X - 1, snakeHead.Y + 1},
|
{snakeHead.X - 1, snakeHead.Y + 1},
|
||||||
Point{snakeHead.X + 1, snakeHead.Y - 1},
|
{snakeHead.X + 1, snakeHead.Y - 1},
|
||||||
Point{snakeHead.X + 1, snakeHead.Y + 1},
|
{snakeHead.X + 1, snakeHead.Y + 1},
|
||||||
}
|
}
|
||||||
availableFoodLocations := []Point{}
|
availableFoodLocations := []Point{}
|
||||||
|
|
||||||
|
|
@ -179,6 +180,7 @@ func (r *StandardRuleset) CreateNextBoardState(prevState *BoardState, moves []Sn
|
||||||
Width: prevState.Width,
|
Width: prevState.Width,
|
||||||
Food: append([]Point{}, prevState.Food...),
|
Food: append([]Point{}, prevState.Food...),
|
||||||
Snakes: make([]Snake, len(prevState.Snakes)),
|
Snakes: make([]Snake, len(prevState.Snakes)),
|
||||||
|
Hazards: append([]Point{}, prevState.Hazards...),
|
||||||
}
|
}
|
||||||
for i := 0; i < len(prevState.Snakes); i++ {
|
for i := 0; i < len(prevState.Snakes); i++ {
|
||||||
nextState.Snakes[i].ID = prevState.Snakes[i].ID
|
nextState.Snakes[i].ID = prevState.Snakes[i].ID
|
||||||
|
|
@ -202,6 +204,11 @@ func (r *StandardRuleset) CreateNextBoardState(prevState *BoardState, moves []Sn
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = r.maybeDamageHazards(nextState)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: LOG?
|
// TODO: LOG?
|
||||||
// bvanvugt: We specifically want this to happen before elimination for two reasons:
|
// bvanvugt: We specifically want this to happen before elimination for two reasons:
|
||||||
// 1) We want snakes to be able to eat on their very last turn and still survive.
|
// 1) We want snakes to be able to eat on their very last turn and still survive.
|
||||||
|
|
@ -307,6 +314,41 @@ func (r *StandardRuleset) reduceSnakeHealth(b *BoardState) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *StandardRuleset) maybeDamageHazards(b *BoardState) error {
|
||||||
|
for i := 0; i < len(b.Snakes); i++ {
|
||||||
|
snake := &b.Snakes[i]
|
||||||
|
if snake.EliminatedCause != NotEliminated {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
head := snake.Body[0]
|
||||||
|
for _, p := range b.Hazards {
|
||||||
|
if head == p {
|
||||||
|
// If there's a food in this square, don't reduce health
|
||||||
|
foundFood := false
|
||||||
|
for _, food := range b.Food {
|
||||||
|
if p == food {
|
||||||
|
foundFood = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if foundFood {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snake is in a hazard, reduce health
|
||||||
|
snake.Health = snake.Health - r.HazardDamagePerTurn
|
||||||
|
if snake.Health < 0 {
|
||||||
|
snake.Health = 0
|
||||||
|
}
|
||||||
|
if r.snakeIsOutOfHealth(snake) {
|
||||||
|
snake.EliminatedCause = EliminatedByOutOfHealth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *StandardRuleset) maybeEliminateSnakes(b *BoardState) error {
|
func (r *StandardRuleset) maybeEliminateSnakes(b *BoardState) error {
|
||||||
// First order snake indices by length.
|
// First order snake indices by length.
|
||||||
// In multi-collision scenarios we want to always attribute elimination to the longest snake.
|
// In multi-collision scenarios we want to always attribute elimination to the longest snake.
|
||||||
|
|
|
||||||
197
standard_test.go
197
standard_test.go
|
|
@ -74,6 +74,7 @@ func TestCreateInitialBoardState(t *testing.T) {
|
||||||
require.Equal(t, id, state.Snakes[i].ID)
|
require.Equal(t, id, state.Snakes[i].ID)
|
||||||
}
|
}
|
||||||
require.Len(t, state.Food, test.ExpectedNumFood, testNum)
|
require.Len(t, state.Food, test.ExpectedNumFood, testNum)
|
||||||
|
require.Len(t, state.Hazards, 0, testNum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -490,6 +491,7 @@ func TestCreateNextBoardState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Food: []Point{{0, 0}, {1, 0}},
|
Food: []Point{{0, 0}, {1, 0}},
|
||||||
|
Hazards: []Point{},
|
||||||
},
|
},
|
||||||
[]SnakeMove{},
|
[]SnakeMove{},
|
||||||
ErrorNoMoveFound,
|
ErrorNoMoveFound,
|
||||||
|
|
@ -512,6 +514,7 @@ func TestCreateNextBoardState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Food: []Point{{0, 0}, {1, 0}},
|
Food: []Point{{0, 0}, {1, 0}},
|
||||||
|
Hazards: []Point{},
|
||||||
},
|
},
|
||||||
[]SnakeMove{
|
[]SnakeMove{
|
||||||
{ID: "one", Move: MoveUp},
|
{ID: "one", Move: MoveUp},
|
||||||
|
|
@ -543,6 +546,7 @@ func TestCreateNextBoardState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Food: []Point{{0, 0}, {1, 0}},
|
Food: []Point{{0, 0}, {1, 0}},
|
||||||
|
Hazards: []Point{},
|
||||||
},
|
},
|
||||||
[]SnakeMove{
|
[]SnakeMove{
|
||||||
{ID: "one", Move: MoveDown},
|
{ID: "one", Move: MoveDown},
|
||||||
|
|
@ -572,6 +576,7 @@ func TestCreateNextBoardState(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Food: []Point{{0, 0}},
|
Food: []Point{{0, 0}},
|
||||||
|
Hazards: []Point{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -580,7 +585,13 @@ func TestCreateNextBoardState(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
nextState, err := r.CreateNextBoardState(test.prevState, test.moves)
|
nextState, err := r.CreateNextBoardState(test.prevState, test.moves)
|
||||||
require.Equal(t, test.expectedError, err)
|
require.Equal(t, test.expectedError, err)
|
||||||
require.Equal(t, test.expectedState, nextState)
|
if test.expectedState != nil {
|
||||||
|
require.Equal(t, test.expectedState.Width, nextState.Width)
|
||||||
|
require.Equal(t, test.expectedState.Height, nextState.Height)
|
||||||
|
require.Equal(t, test.expectedState.Food, nextState.Food)
|
||||||
|
require.Equal(t, test.expectedState.Snakes, nextState.Snakes)
|
||||||
|
require.Equal(t, test.expectedState.Hazards, nextState.Hazards)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -645,7 +656,12 @@ func TestEatingOnLastMove(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
nextState, err := r.CreateNextBoardState(test.prevState, test.moves)
|
nextState, err := r.CreateNextBoardState(test.prevState, test.moves)
|
||||||
require.Equal(t, err, test.expectedError)
|
require.Equal(t, err, test.expectedError)
|
||||||
require.Equal(t, nextState, test.expectedState)
|
if test.expectedState != nil {
|
||||||
|
require.Equal(t, test.expectedState.Width, nextState.Width)
|
||||||
|
require.Equal(t, test.expectedState.Height, nextState.Height)
|
||||||
|
require.Equal(t, test.expectedState.Food, nextState.Food)
|
||||||
|
require.Equal(t, test.expectedState.Snakes, nextState.Snakes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -757,7 +773,12 @@ func TestHeadToHeadOnFood(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
nextState, err := r.CreateNextBoardState(test.prevState, test.moves)
|
nextState, err := r.CreateNextBoardState(test.prevState, test.moves)
|
||||||
require.Equal(t, test.expectedError, err)
|
require.Equal(t, test.expectedError, err)
|
||||||
require.Equal(t, test.expectedState, nextState)
|
if test.expectedState != nil {
|
||||||
|
require.Equal(t, test.expectedState.Width, nextState.Width)
|
||||||
|
require.Equal(t, test.expectedState.Height, nextState.Height)
|
||||||
|
require.Equal(t, test.expectedState.Food, nextState.Food)
|
||||||
|
require.Equal(t, test.expectedState.Snakes, nextState.Snakes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -829,7 +850,12 @@ func TestRegressionIssue19(t *testing.T) {
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
nextState, err := r.CreateNextBoardState(test.prevState, test.moves)
|
nextState, err := r.CreateNextBoardState(test.prevState, test.moves)
|
||||||
require.Equal(t, err, test.expectedError)
|
require.Equal(t, err, test.expectedError)
|
||||||
require.Equal(t, nextState, test.expectedState)
|
if test.expectedState != nil {
|
||||||
|
require.Equal(t, test.expectedState.Width, nextState.Width)
|
||||||
|
require.Equal(t, test.expectedState.Height, nextState.Height)
|
||||||
|
require.Equal(t, test.expectedState.Food, nextState.Food)
|
||||||
|
require.Equal(t, test.expectedState.Snakes, nextState.Snakes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1190,7 +1216,7 @@ func TestSnakeIsOutOfBounds(t *testing.T) {
|
||||||
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{{0, 0}, {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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1366,7 +1392,7 @@ func TestMaybeEliminateSnakes(t *testing.T) {
|
||||||
{
|
{
|
||||||
"Zero Snake",
|
"Zero Snake",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{},
|
{},
|
||||||
},
|
},
|
||||||
[]string{NotEliminated},
|
[]string{NotEliminated},
|
||||||
[]string{""},
|
[]string{""},
|
||||||
|
|
@ -1375,7 +1401,7 @@ func TestMaybeEliminateSnakes(t *testing.T) {
|
||||||
{
|
{
|
||||||
"Single Starvation",
|
"Single Starvation",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Body: []Point{{1, 1}}},
|
{ID: "1", Body: []Point{{1, 1}}},
|
||||||
},
|
},
|
||||||
[]string{EliminatedByOutOfHealth},
|
[]string{EliminatedByOutOfHealth},
|
||||||
[]string{""},
|
[]string{""},
|
||||||
|
|
@ -1384,7 +1410,7 @@ func TestMaybeEliminateSnakes(t *testing.T) {
|
||||||
{
|
{
|
||||||
"Not Eliminated",
|
"Not Eliminated",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Health: 1, Body: []Point{{1, 1}}},
|
{ID: "1", Health: 1, Body: []Point{{1, 1}}},
|
||||||
},
|
},
|
||||||
[]string{NotEliminated},
|
[]string{NotEliminated},
|
||||||
[]string{""},
|
[]string{""},
|
||||||
|
|
@ -1393,7 +1419,7 @@ func TestMaybeEliminateSnakes(t *testing.T) {
|
||||||
{
|
{
|
||||||
"Out of Bounds",
|
"Out of Bounds",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Health: 1, Body: []Point{{-1, 1}}},
|
{ID: "1", Health: 1, Body: []Point{{-1, 1}}},
|
||||||
},
|
},
|
||||||
[]string{EliminatedByOutOfBounds},
|
[]string{EliminatedByOutOfBounds},
|
||||||
[]string{""},
|
[]string{""},
|
||||||
|
|
@ -1402,7 +1428,7 @@ func TestMaybeEliminateSnakes(t *testing.T) {
|
||||||
{
|
{
|
||||||
"Self Collision",
|
"Self Collision",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Health: 1, Body: []Point{{0, 0}, {0, 1}, {0, 0}}},
|
{ID: "1", Health: 1, Body: []Point{{0, 0}, {0, 1}, {0, 0}}},
|
||||||
},
|
},
|
||||||
[]string{EliminatedBySelfCollision},
|
[]string{EliminatedBySelfCollision},
|
||||||
[]string{"1"},
|
[]string{"1"},
|
||||||
|
|
@ -1411,8 +1437,8 @@ func TestMaybeEliminateSnakes(t *testing.T) {
|
||||||
{
|
{
|
||||||
"Multiple Separate Deaths",
|
"Multiple Separate Deaths",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Health: 1, Body: []Point{{0, 0}, {0, 1}, {0, 0}}},
|
{ID: "1", Health: 1, Body: []Point{{0, 0}, {0, 1}, {0, 0}}},
|
||||||
Snake{ID: "2", Health: 1, Body: []Point{{-1, 1}}},
|
{ID: "2", Health: 1, Body: []Point{{-1, 1}}},
|
||||||
},
|
},
|
||||||
[]string{
|
[]string{
|
||||||
EliminatedBySelfCollision,
|
EliminatedBySelfCollision,
|
||||||
|
|
@ -1423,8 +1449,8 @@ func TestMaybeEliminateSnakes(t *testing.T) {
|
||||||
{
|
{
|
||||||
"Other Collision",
|
"Other Collision",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Health: 1, Body: []Point{{0, 2}, {0, 3}, {0, 4}}},
|
{ID: "1", Health: 1, Body: []Point{{0, 2}, {0, 3}, {0, 4}}},
|
||||||
Snake{ID: "2", Health: 1, Body: []Point{{0, 0}, {0, 1}, {0, 2}}},
|
{ID: "2", Health: 1, Body: []Point{{0, 0}, {0, 1}, {0, 2}}},
|
||||||
},
|
},
|
||||||
[]string{
|
[]string{
|
||||||
EliminatedByCollision,
|
EliminatedByCollision,
|
||||||
|
|
@ -1435,9 +1461,9 @@ func TestMaybeEliminateSnakes(t *testing.T) {
|
||||||
{
|
{
|
||||||
"All Eliminated Head 2 Head",
|
"All Eliminated Head 2 Head",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Health: 1, Body: []Point{{1, 1}}},
|
{ID: "1", Health: 1, Body: []Point{{1, 1}}},
|
||||||
Snake{ID: "2", Health: 1, Body: []Point{{1, 1}}},
|
{ID: "2", Health: 1, Body: []Point{{1, 1}}},
|
||||||
Snake{ID: "3", Health: 1, Body: []Point{{1, 1}}},
|
{ID: "3", Health: 1, Body: []Point{{1, 1}}},
|
||||||
},
|
},
|
||||||
[]string{
|
[]string{
|
||||||
EliminatedByHeadToHeadCollision,
|
EliminatedByHeadToHeadCollision,
|
||||||
|
|
@ -1450,9 +1476,9 @@ func TestMaybeEliminateSnakes(t *testing.T) {
|
||||||
{
|
{
|
||||||
"One Snake wins Head 2 Head",
|
"One Snake wins Head 2 Head",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Health: 1, Body: []Point{{1, 1}, {0, 1}}},
|
{ID: "1", Health: 1, Body: []Point{{1, 1}, {0, 1}}},
|
||||||
Snake{ID: "2", Health: 1, Body: []Point{{1, 1}, {1, 2}, {1, 3}}},
|
{ID: "2", Health: 1, Body: []Point{{1, 1}, {1, 2}, {1, 3}}},
|
||||||
Snake{ID: "3", Health: 1, Body: []Point{{1, 1}}},
|
{ID: "3", Health: 1, Body: []Point{{1, 1}}},
|
||||||
},
|
},
|
||||||
[]string{
|
[]string{
|
||||||
EliminatedByHeadToHeadCollision,
|
EliminatedByHeadToHeadCollision,
|
||||||
|
|
@ -1465,11 +1491,11 @@ func TestMaybeEliminateSnakes(t *testing.T) {
|
||||||
{
|
{
|
||||||
"All Snakes Body Eliminated",
|
"All Snakes Body Eliminated",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Health: 1, Body: []Point{{4, 4}, {3, 3}}},
|
{ID: "1", Health: 1, Body: []Point{{4, 4}, {3, 3}}},
|
||||||
Snake{ID: "2", Health: 1, Body: []Point{{3, 3}, {2, 2}}},
|
{ID: "2", Health: 1, Body: []Point{{3, 3}, {2, 2}}},
|
||||||
Snake{ID: "3", Health: 1, Body: []Point{{2, 2}, {1, 1}}},
|
{ID: "3", Health: 1, Body: []Point{{2, 2}, {1, 1}}},
|
||||||
Snake{ID: "4", Health: 1, Body: []Point{{1, 1}, {4, 4}}},
|
{ID: "4", Health: 1, Body: []Point{{1, 1}, {4, 4}}},
|
||||||
Snake{ID: "5", Health: 1, Body: []Point{{4, 4}}}, // Body collision takes priority
|
{ID: "5", Health: 1, Body: []Point{{4, 4}}}, // Body collision takes priority
|
||||||
},
|
},
|
||||||
[]string{
|
[]string{
|
||||||
EliminatedByCollision,
|
EliminatedByCollision,
|
||||||
|
|
@ -1484,10 +1510,10 @@ func TestMaybeEliminateSnakes(t *testing.T) {
|
||||||
{
|
{
|
||||||
"All Snakes Eliminated Head 2 Head",
|
"All Snakes Eliminated Head 2 Head",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Health: 1, Body: []Point{{4, 4}, {4, 5}}},
|
{ID: "1", Health: 1, Body: []Point{{4, 4}, {4, 5}}},
|
||||||
Snake{ID: "2", Health: 1, Body: []Point{{4, 4}, {4, 3}}},
|
{ID: "2", Health: 1, Body: []Point{{4, 4}, {4, 3}}},
|
||||||
Snake{ID: "3", Health: 1, Body: []Point{{4, 4}, {5, 4}}},
|
{ID: "3", Health: 1, Body: []Point{{4, 4}, {5, 4}}},
|
||||||
Snake{ID: "4", Health: 1, Body: []Point{{4, 4}, {3, 4}}},
|
{ID: "4", Health: 1, Body: []Point{{4, 4}, {3, 4}}},
|
||||||
},
|
},
|
||||||
[]string{
|
[]string{
|
||||||
EliminatedByHeadToHeadCollision,
|
EliminatedByHeadToHeadCollision,
|
||||||
|
|
@ -1501,10 +1527,10 @@ func TestMaybeEliminateSnakes(t *testing.T) {
|
||||||
{
|
{
|
||||||
"4 Snakes Head 2 Head",
|
"4 Snakes Head 2 Head",
|
||||||
[]Snake{
|
[]Snake{
|
||||||
Snake{ID: "1", Health: 1, Body: []Point{{4, 4}, {4, 5}}},
|
{ID: "1", Health: 1, Body: []Point{{4, 4}, {4, 5}}},
|
||||||
Snake{ID: "2", Health: 1, Body: []Point{{4, 4}, {4, 3}}},
|
{ID: "2", Health: 1, Body: []Point{{4, 4}, {4, 3}}},
|
||||||
Snake{ID: "3", Health: 1, Body: []Point{{4, 4}, {5, 4}, {6, 4}}},
|
{ID: "3", Health: 1, Body: []Point{{4, 4}, {5, 4}, {6, 4}}},
|
||||||
Snake{ID: "4", Health: 1, Body: []Point{{4, 4}, {3, 4}}},
|
{ID: "4", Health: 1, Body: []Point{{4, 4}, {3, 4}}},
|
||||||
},
|
},
|
||||||
[]string{
|
[]string{
|
||||||
EliminatedByHeadToHeadCollision,
|
EliminatedByHeadToHeadCollision,
|
||||||
|
|
@ -1574,6 +1600,111 @@ func TestMaybeEliminateSnakesPriority(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMaybeDamageHazards(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Snakes []Snake
|
||||||
|
Hazards []Point
|
||||||
|
Food []Point
|
||||||
|
ExpectedEliminatedCauses []string
|
||||||
|
ExpectedEliminatedByIDs []string
|
||||||
|
}{
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
Snakes: []Snake{{Body: []Point{{0, 0}}}},
|
||||||
|
Hazards: []Point{},
|
||||||
|
ExpectedEliminatedCauses: []string{NotEliminated},
|
||||||
|
ExpectedEliminatedByIDs: []string{""},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Snakes: []Snake{{Body: []Point{{0, 0}}}},
|
||||||
|
Hazards: []Point{{0, 0}},
|
||||||
|
ExpectedEliminatedCauses: []string{EliminatedByOutOfHealth},
|
||||||
|
ExpectedEliminatedByIDs: []string{""},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Snakes: []Snake{{Body: []Point{{0, 0}}}},
|
||||||
|
Hazards: []Point{{0, 0}},
|
||||||
|
Food: []Point{{0, 0}},
|
||||||
|
ExpectedEliminatedCauses: []string{NotEliminated},
|
||||||
|
ExpectedEliminatedByIDs: []string{""},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Snakes: []Snake{{Body: []Point{{0, 0}, {1, 0}, {2, 0}}}},
|
||||||
|
Hazards: []Point{{1, 0}, {2, 0}},
|
||||||
|
ExpectedEliminatedCauses: []string{NotEliminated},
|
||||||
|
ExpectedEliminatedByIDs: []string{""},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Snakes: []Snake{
|
||||||
|
{Body: []Point{{0, 0}, {1, 0}, {2, 0}}},
|
||||||
|
{Body: []Point{{3, 3}, {3, 4}, {3, 5}, {3, 6}}},
|
||||||
|
},
|
||||||
|
Hazards: []Point{{1, 0}, {2, 0}, {3, 4}, {3, 5}, {3, 6}},
|
||||||
|
ExpectedEliminatedCauses: []string{NotEliminated, NotEliminated},
|
||||||
|
ExpectedEliminatedByIDs: []string{"", ""},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Snakes: []Snake{
|
||||||
|
{Body: []Point{{0, 0}, {1, 0}, {2, 0}}},
|
||||||
|
{Body: []Point{{3, 3}, {3, 4}, {3, 5}, {3, 6}}},
|
||||||
|
},
|
||||||
|
Hazards: []Point{{3, 3}},
|
||||||
|
ExpectedEliminatedCauses: []string{NotEliminated, EliminatedByOutOfHealth},
|
||||||
|
ExpectedEliminatedByIDs: []string{"", ""},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
b := &BoardState{Snakes: test.Snakes, Hazards: test.Hazards, Food: test.Food}
|
||||||
|
r := StandardRuleset{HazardDamagePerTurn: 100}
|
||||||
|
err := r.maybeDamageHazards(b)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
for i, snake := range b.Snakes {
|
||||||
|
require.Equal(t, test.ExpectedEliminatedCauses[i], snake.EliminatedCause)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHazardDamagePerTurn(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
Health int32
|
||||||
|
HazardDamagePerTurn int32
|
||||||
|
Food bool
|
||||||
|
ExpectedHealth int32
|
||||||
|
ExpectedEliminationCause string
|
||||||
|
Error error
|
||||||
|
}{
|
||||||
|
{100, 1, false, 99, NotEliminated, nil},
|
||||||
|
{100, 1, true, 100, NotEliminated, nil},
|
||||||
|
{100, 99, false, 1, NotEliminated, nil},
|
||||||
|
{100, 99, true, 100, NotEliminated, nil},
|
||||||
|
{100, 100, false, 0, EliminatedByOutOfHealth, nil},
|
||||||
|
{100, 101, false, 0, EliminatedByOutOfHealth, nil},
|
||||||
|
{100, 999, false, 0, EliminatedByOutOfHealth, nil},
|
||||||
|
{100, 100, true, 100, NotEliminated, nil},
|
||||||
|
{2, 1, false, 1, NotEliminated, nil},
|
||||||
|
{1, 1, false, 0, EliminatedByOutOfHealth, nil},
|
||||||
|
{1, 999, false, 0, EliminatedByOutOfHealth, nil},
|
||||||
|
{0, 1, false, 0, EliminatedByOutOfHealth, nil},
|
||||||
|
{0, 999, false, 0, EliminatedByOutOfHealth, nil},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
b := &BoardState{Snakes: []Snake{{Health: test.Health, Body: []Point{{0, 0}}}}, Hazards: []Point{{0, 0}}}
|
||||||
|
if test.Food {
|
||||||
|
b.Food = []Point{{0, 0}}
|
||||||
|
}
|
||||||
|
r := StandardRuleset{HazardDamagePerTurn: test.HazardDamagePerTurn}
|
||||||
|
|
||||||
|
err := r.maybeDamageHazards(b)
|
||||||
|
require.Equal(t, test.Error, err)
|
||||||
|
require.Equal(t, test.ExpectedHealth, b.Snakes[0].Health)
|
||||||
|
require.Equal(t, test.ExpectedEliminationCause, b.Snakes[0].EliminatedCause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMaybeFeedSnakes(t *testing.T) {
|
func TestMaybeFeedSnakes(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Name string
|
Name string
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue