DEV-1761: New rules API (#118)

* DEV-1761: Clean up Ruleset interface (#115)

* remove legacy ruleset types and simplify ruleset interface

* remove unnecessary settings argument from Ruleset interface

* decouple rules.Settings from client API and store settings as strings

* DEV 1761: Add new BoardState and Point fields (#117)

* add Point.TTL, Point.Value, GameState and PointState to BoardState

* allow maps to access BoardState.GameState,PointState

* add PreUpdateBoard and refactor snail_mode with it

* fix bug where an extra turn was printed to the console

* fix formatting

* fix lint errors

Co-authored-by: JonathanArns <jonathan.arns@googlemail.com>
This commit is contained in:
Rob O'Dwyer 2022-10-28 16:49:49 -07:00 committed by GitHub
parent 639362ef46
commit 82e1999126
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
50 changed files with 1349 additions and 1610 deletions

View file

@ -2,14 +2,19 @@ package rules
import (
"errors"
"fmt"
"math/rand"
"testing"
"github.com/stretchr/testify/require"
)
func TestRoyaleRulesetInterface(t *testing.T) {
var _ Ruleset = (*RoyaleRuleset)(nil)
func getRoyaleRuleset(hazardDamagePerTurn, shrinkEveryNTurns int) Ruleset {
settings := NewSettingsWithParams(
ParamHazardDamagePerTurn, fmt.Sprint(hazardDamagePerTurn),
ParamShrinkEveryNTurns, fmt.Sprint(shrinkEveryNTurns),
)
return NewRulesetBuilder().WithSettings(settings).NamedRuleset(GameTypeRoyale)
}
func TestRoyaleDefaultSanity(t *testing.T) {
@ -19,24 +24,19 @@ func TestRoyaleDefaultSanity(t *testing.T) {
{ID: "2", Body: []Point{{X: 0, Y: 1}}},
},
}
r := RoyaleRuleset{StandardRuleset: StandardRuleset{HazardDamagePerTurn: 1}, ShrinkEveryNTurns: 0}
_, err := r.CreateNextBoardState(boardState, []SnakeMove{{"1", "right"}, {"2", "right"}})
r := getRoyaleRuleset(1, 0)
_, _, err := r.Execute(boardState, []SnakeMove{{"1", "right"}, {"2", "right"}})
require.Error(t, err)
require.Equal(t, errors.New("royale game can't shrink more frequently than every turn"), err)
r = RoyaleRuleset{ShrinkEveryNTurns: 1}
_, 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{})
r = getRoyaleRuleset(1, 1)
_, boardState, err = r.Execute(boardState, []SnakeMove{})
require.NoError(t, err)
require.Len(t, boardState.Hazards, 0)
}
func TestRoyaleName(t *testing.T) {
r := RoyaleRuleset{}
r := getRoyaleRuleset(0, 0)
require.Equal(t, "royale", r.Name())
}
@ -57,39 +57,39 @@ func TestRoyaleHazards(t *testing.T) {
{Width: 3, Height: 3, Turn: 9, ShrinkEveryNTurns: 10, ExpectedHazards: []Point{}},
{
Width: 3, Height: 3, Turn: 10, ShrinkEveryNTurns: 10,
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}},
ExpectedHazards: []Point{{X: 0, Y: 0}, {X: 0, Y: 1}, {X: 0, Y: 2}},
},
{
Width: 3, Height: 3, Turn: 11, ShrinkEveryNTurns: 10,
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}},
ExpectedHazards: []Point{{X: 0, Y: 0}, {X: 0, Y: 1}, {X: 0, Y: 2}},
},
{
Width: 3, Height: 3, Turn: 19, ShrinkEveryNTurns: 10,
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}},
ExpectedHazards: []Point{{X: 0, Y: 0}, {X: 0, Y: 1}, {X: 0, Y: 2}},
},
{
Width: 3, Height: 3, Turn: 20, ShrinkEveryNTurns: 10,
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 2}, {2, 2}},
ExpectedHazards: []Point{{X: 0, Y: 0}, {X: 0, Y: 1}, {X: 0, Y: 2}, {X: 1, Y: 2}, {X: 2, Y: 2}},
},
{
Width: 3, Height: 3, Turn: 31, ShrinkEveryNTurns: 10,
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 1}, {1, 2}, {2, 1}, {2, 2}},
ExpectedHazards: []Point{{X: 0, Y: 0}, {X: 0, Y: 1}, {X: 0, Y: 2}, {X: 1, Y: 1}, {X: 1, Y: 2}, {X: 2, Y: 1}, {X: 2, Y: 2}},
},
{
Width: 3, Height: 3, Turn: 42, ShrinkEveryNTurns: 10,
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {2, 0}, {2, 1}, {2, 2}},
ExpectedHazards: []Point{{X: 0, Y: 0}, {X: 0, Y: 1}, {X: 0, Y: 2}, {X: 1, Y: 0}, {X: 1, Y: 1}, {X: 1, Y: 2}, {X: 2, Y: 0}, {X: 2, Y: 1}, {X: 2, Y: 2}},
},
{
Width: 3, Height: 3, Turn: 53, ShrinkEveryNTurns: 10,
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {2, 0}, {2, 1}, {2, 2}},
ExpectedHazards: []Point{{X: 0, Y: 0}, {X: 0, Y: 1}, {X: 0, Y: 2}, {X: 1, Y: 0}, {X: 1, Y: 1}, {X: 1, Y: 2}, {X: 2, Y: 0}, {X: 2, Y: 1}, {X: 2, Y: 2}},
},
{
Width: 3, Height: 3, Turn: 64, ShrinkEveryNTurns: 10,
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {2, 0}, {2, 1}, {2, 2}},
ExpectedHazards: []Point{{X: 0, Y: 0}, {X: 0, Y: 1}, {X: 0, Y: 2}, {X: 1, Y: 0}, {X: 1, Y: 1}, {X: 1, Y: 2}, {X: 2, Y: 0}, {X: 2, Y: 1}, {X: 2, Y: 2}},
},
{
Width: 3, Height: 3, Turn: 6987, ShrinkEveryNTurns: 10,
ExpectedHazards: []Point{{0, 0}, {0, 1}, {0, 2}, {1, 0}, {1, 1}, {1, 2}, {2, 0}, {2, 1}, {2, 2}},
ExpectedHazards: []Point{{X: 0, Y: 0}, {X: 0, Y: 1}, {X: 0, Y: 2}, {X: 1, Y: 0}, {X: 1, Y: 1}, {X: 1, Y: 2}, {X: 2, Y: 0}, {X: 2, Y: 1}, {X: 2, Y: 2}},
},
}
@ -99,12 +99,10 @@ func TestRoyaleHazards(t *testing.T) {
Width: test.Width,
Height: test.Height,
}
settings := Settings{
HazardDamagePerTurn: 1,
RoyaleSettings: RoyaleSettings{
ShrinkEveryNTurns: test.ShrinkEveryNTurns,
},
}.WithSeed(seed)
settings := NewSettingsWithParams(
ParamHazardDamagePerTurn, "1",
ParamShrinkEveryNTurns, fmt.Sprint(test.ShrinkEveryNTurns),
).WithSeed(seed)
_, err := PopulateHazardsRoyale(b, settings, mockSnakeMoves())
require.Equal(t, test.Error, err)
@ -139,12 +137,12 @@ var royaleCaseHazardsPlaced = gameTestCase{
Snakes: []Snake{
{
ID: "one",
Body: []Point{{1, 1}, {1, 2}},
Body: []Point{{X: 1, Y: 1}, {X: 1, Y: 2}},
Health: 100,
},
{
ID: "two",
Body: []Point{{3, 4}, {3, 3}},
Body: []Point{{X: 3, Y: 4}, {X: 3, Y: 3}},
Health: 100,
},
{
@ -154,7 +152,7 @@ var royaleCaseHazardsPlaced = gameTestCase{
EliminatedCause: EliminatedByOutOfBounds,
},
},
Food: []Point{{0, 0}, {1, 0}},
Food: []Point{{X: 0, Y: 0}, {X: 1, Y: 0}},
Hazards: []Point{},
},
[]SnakeMove{
@ -169,12 +167,12 @@ var royaleCaseHazardsPlaced = gameTestCase{
Snakes: []Snake{
{
ID: "one",
Body: []Point{{1, 0}, {1, 1}, {1, 1}},
Body: []Point{{X: 1, Y: 0}, {X: 1, Y: 1}, {X: 1, Y: 1}},
Health: 100,
},
{
ID: "two",
Body: []Point{{3, 5}, {3, 4}},
Body: []Point{{X: 3, Y: 5}, {X: 3, Y: 4}},
Health: 99,
},
{
@ -184,7 +182,7 @@ var royaleCaseHazardsPlaced = gameTestCase{
EliminatedCause: EliminatedByOutOfBounds,
},
},
Food: []Point{{0, 0}},
Food: []Point{{X: 0, Y: 0}},
Hazards: []Point{{X: 0, Y: 0}, {X: 1, Y: 0}, {X: 2, Y: 0}, {X: 3, Y: 0}, {X: 4, Y: 0}, {X: 5, Y: 0}, {X: 6, Y: 0}, {X: 7, Y: 0}, {X: 8, Y: 0}, {X: 9, Y: 0}},
},
}
@ -204,22 +202,14 @@ func TestRoyaleCreateNextBoardState(t *testing.T) {
*s2,
royaleCaseHazardsPlaced,
}
r := RoyaleRuleset{
StandardRuleset: StandardRuleset{
HazardDamagePerTurn: 1,
},
ShrinkEveryNTurns: 1,
}
rb := NewRulesetBuilder().WithParams(map[string]string{
ParamGameType: GameTypeRoyale,
ParamHazardDamagePerTurn: "1",
ParamShrinkEveryNTurns: "1",
}).WithSeed(1234)
for _, gc := range cases {
rand.Seed(1234)
gc.requireValidNextState(t, &r)
// also test a RulesBuilder constructed instance
gc.requireValidNextState(t, rb.Ruleset())
// test a RulesBuilder constructed instance
gc.requireValidNextState(t, rb.NamedRuleset(GameTypeRoyale))
// also test a pipeline with the same settings
gc.requireValidNextState(t, rb.PipelineRuleset(GameTypeRoyale, NewPipeline(royaleRulesetStages...)))
}