diff --git a/cases_test.go b/cases_test.go new file mode 100644 index 0000000..16333dd --- /dev/null +++ b/cases_test.go @@ -0,0 +1,41 @@ +package rules + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +type gameTestCase struct { + name string + prevState *BoardState + moves []SnakeMove + expectedError error + expectedState *BoardState +} + +func (gc *gameTestCase) clone() *gameTestCase { + return &gameTestCase{ + name: gc.name, + expectedError: gc.expectedError, + moves: append([]SnakeMove{}, gc.moves...), + prevState: gc.prevState.Clone(), + expectedState: gc.expectedState.Clone(), + } +} + +// requireValidNextState requires that the ruleset produces a valid next state +func (gc *gameTestCase) requireValidNextState(t *testing.T, r Ruleset) { + t.Run(gc.name, func(t *testing.T) { + prev := gc.prevState.Clone() // clone to protect against mutation (so we can ru-use test cases) + nextState, err := r.CreateNextBoardState(prev, gc.moves) + require.Equal(t, gc.expectedError, err) + if gc.expectedState != nil { + require.Equal(t, gc.expectedState.Width, nextState.Width) + require.Equal(t, gc.expectedState.Height, nextState.Height) + require.Equal(t, gc.expectedState.Food, nextState.Food) + require.Equal(t, gc.expectedState.Snakes, nextState.Snakes) + require.Equal(t, gc.expectedState.Hazards, nextState.Hazards) + } + }) +} diff --git a/constrictor_test.go b/constrictor_test.go index 36bce42..68c987c 100644 --- a/constrictor_test.go +++ b/constrictor_test.go @@ -45,89 +45,67 @@ func TestConstrictorModifyInitialBoardState(t *testing.T) { } } -func TestConstrictorCreateNextBoardState(t *testing.T) { - tests := []struct { - prevState *BoardState - moves []SnakeMove - expectedState *BoardState - }{ - { - &BoardState{ - Width: 3, - Height: 3, - Snakes: []Snake{ - { - ID: "one", - Body: []Point{{0, 0}, {0, 0}, {0, 0}}, - Health: 100, - }, - { - ID: "two", - Body: []Point{{2, 2}, {2, 2}, {2, 2}}, - Health: 100, - }, - }, - Food: []Point{}, +// Test that two equal snakes collide and both get eliminated +// also checks: +// - food removed +// - health back to max +var constrictorMoveAndCollideMAD = gameTestCase{ + "Constrictor Case Move and Collide", + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "one", + Body: []Point{{1, 1}, {2, 1}}, + Health: 99, }, - []SnakeMove{ - {ID: "one", Move: MoveUp}, - {ID: "two", Move: MoveDown}, - }, - &BoardState{ - Width: 3, - Height: 3, - Snakes: []Snake{ - { - ID: "one", - Body: []Point{{0, 1}, {0, 0}, {0, 0}}, - Health: 100, - }, - { - ID: "two", - Body: []Point{{2, 1}, {2, 2}, {2, 2}}, - Health: 100, - }, - }, - Food: []Point{}, + { + ID: "two", + Body: []Point{{1, 2}, {2, 2}}, + Health: 99, }, }, - // Ensure snakes keep growing and are fed - { - &BoardState{ - Width: 3, - Height: 3, - Snakes: []Snake{ - { - ID: "one", - Body: []Point{{2, 0}, {1, 0}, {0, 0}, {0, 0}}, - Health: 75, - }, - }, - Food: []Point{}, + Food: []Point{{10, 10}, {9, 9}, {8, 8}}, + Hazards: []Point{}, + }, + []SnakeMove{ + {ID: "one", Move: MoveUp}, + {ID: "two", Move: MoveDown}, + }, + nil, + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "one", + Body: []Point{{1, 2}, {1, 1}, {1, 1}}, + Health: 100, + EliminatedCause: EliminatedByCollision, + EliminatedBy: "two", }, - []SnakeMove{ - {ID: "one", Move: MoveUp}, - }, - &BoardState{ - Width: 3, - Height: 3, - Snakes: []Snake{ - { - ID: "one", - Body: []Point{{2, 1}, {2, 0}, {1, 0}, {0, 0}, {0, 0}}, - Health: 100, - }, - }, - Food: []Point{}, + { + ID: "two", + Body: []Point{{1, 1}, {1, 2}, {1, 2}}, + Health: 100, + EliminatedCause: EliminatedByCollision, + EliminatedBy: "one", }, }, - } + Food: []Point{}, + Hazards: []Point{}, + }, +} +func TestConstrictorCreateNextBoardState(t *testing.T) { + cases := []gameTestCase{ + standardCaseErrNoMoveFound, + standardCaseErrZeroLengthSnake, + constrictorMoveAndCollideMAD, + } r := ConstrictorRuleset{} - for _, test := range tests { - nextState, err := r.CreateNextBoardState(test.prevState, test.moves) - require.NoError(t, err) - require.Equal(t, test.expectedState.Food, nextState.Food) - require.Equal(t, test.expectedState.Snakes, nextState.Snakes) + for _, gc := range cases { + gc.requireValidNextState(t, &r) } } diff --git a/royale_test.go b/royale_test.go index f15366f..b074172 100644 --- a/royale_test.go +++ b/royale_test.go @@ -2,6 +2,7 @@ package rules import ( "errors" + "math/rand" "testing" "github.com/stretchr/testify/require" @@ -177,3 +178,94 @@ func TestRoyalDamageNextTurn(t *testing.T) { require.Equal(t, Point{9, 0}, next.Snakes[0].Body[0]) require.Equal(t, 20, len(next.Hazards)) } + +// Checks that hazards get placed +// also that: +// - snakes move properly +// - snake gets health from eating +// - food gets consumed +// - health is decreased +var royaleCaseHazardsPlaced = gameTestCase{ + "Royale Case Hazards Placed", + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "one", + Body: []Point{{1, 1}, {1, 2}}, + Health: 100, + }, + { + ID: "two", + Body: []Point{{3, 4}, {3, 3}}, + Health: 100, + }, + { + ID: "three", + Body: []Point{}, + Health: 100, + EliminatedCause: EliminatedByOutOfBounds, + }, + }, + Food: []Point{{0, 0}, {1, 0}}, + Hazards: []Point{}, + }, + []SnakeMove{ + {ID: "one", Move: MoveDown}, + {ID: "two", Move: MoveUp}, + {ID: "three", Move: MoveLeft}, // Should be ignored + }, + nil, + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "one", + Body: []Point{{1, 0}, {1, 1}, {1, 1}}, + Health: 100, + }, + { + ID: "two", + Body: []Point{{3, 5}, {3, 4}}, + Health: 99, + }, + { + ID: "three", + Body: []Point{}, + Health: 100, + EliminatedCause: EliminatedByOutOfBounds, + }, + }, + Food: []Point{{0, 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}}, + }, +} + +func TestRoyaleCreateNextBoardState(t *testing.T) { + // add expected hazards to the standard cases that need them + s1 := standardCaseMoveEatAndGrow.clone() + s1.expectedState.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}} + s2 := standardMoveAndCollideMAD.clone() + s2.expectedState.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}} + + cases := []gameTestCase{ + // inherits these test cases from standard + standardCaseErrNoMoveFound, + standardCaseErrZeroLengthSnake, + *s1, + *s2, + royaleCaseHazardsPlaced, + } + r := RoyaleRuleset{ + StandardRuleset: StandardRuleset{ + HazardDamagePerTurn: 1, + }, + ShrinkEveryNTurns: 1, + } + rand.Seed(0) + for _, gc := range cases { + gc.requireValidNextState(t, &r) + } +} diff --git a/solo_test.go b/solo_test.go index 289e821..2155419 100644 --- a/solo_test.go +++ b/solo_test.go @@ -55,3 +55,57 @@ func TestSoloIsGameOver(t *testing.T) { require.Equal(t, test.Expected, actual) } } + +// Checks that a single snake doesn't end the game +// also that: +// - snake moves okay +// - food gets consumed +// - snake grows and gets health from food +var soloCaseNotOver = gameTestCase{ + "Solo Case Game Not Over", + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "one", + Body: []Point{{1, 1}, {1, 2}}, + Health: 100, + }, + }, + Food: []Point{{0, 0}, {1, 0}}, + Hazards: []Point{}, + }, + []SnakeMove{ + {ID: "one", Move: MoveDown}, + }, + nil, + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "one", + Body: []Point{{1, 0}, {1, 1}, {1, 1}}, + Health: 100, + }, + }, + Food: []Point{{0, 0}}, + Hazards: []Point{}, + }, +} + +func TestSoloCreateNextBoardState(t *testing.T) { + cases := []gameTestCase{ + // inherits these test cases from standard + standardCaseErrNoMoveFound, + standardCaseErrZeroLengthSnake, + standardCaseMoveEatAndGrow, + standardMoveAndCollideMAD, + soloCaseNotOver, + } + r := SoloRuleset{} + for _, gc := range cases { + gc.requireValidNextState(t, &r) + } +} diff --git a/squad_test.go b/squad_test.go index 865e36d..0cc0fb2 100644 --- a/squad_test.go +++ b/squad_test.go @@ -1,6 +1,7 @@ package rules import ( + "math/rand" "testing" "github.com/stretchr/testify/require" @@ -394,3 +395,169 @@ func TestRegressionIssue16(t *testing.T) { require.Equal(t, expectedSnakes[i].EliminatedBy, snake.EliminatedBy, snake.ID) } } + +// Checks that snakes on the same squad don't get eliminated +// when the allow squad collisions setting is enabled +// Both squads have snakes that move into each other. +var squadCaseMoveSquadCollisions = gameTestCase{ + "Squad Case Move Squad Collisions", + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "snake1squad1", + Body: []Point{{1, 1}, {2, 1}}, + Health: 100, + }, + { + ID: "snake2squad1", + Body: []Point{{1, 2}, {2, 2}}, + Health: 100, + }, + { + ID: "snake3squad2", + Body: []Point{{4, 4}, {4, 5}}, + Health: 100, + }, + { + ID: "snake4squad2", + Body: []Point{{5, 4}, {5, 5}}, + Health: 100, + }, + }, + Food: []Point{}, + Hazards: []Point{}, + }, + []SnakeMove{ + {ID: "snake1squad1", Move: MoveUp}, + {ID: "snake2squad1", Move: MoveDown}, + {ID: "snake3squad2", Move: MoveRight}, + {ID: "snake4squad2", Move: MoveLeft}, + }, + nil, + &BoardState{Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "snake1squad1", + Body: []Point{{1, 2}, {1, 1}}, + Health: 99, + }, + { + ID: "snake2squad1", + Body: []Point{{1, 1}, {1, 2}}, + Health: 99, + }, + { + ID: "snake3squad2", + Body: []Point{{5, 4}, {4, 4}}, + Health: 99, + }, + { + ID: "snake4squad2", + Body: []Point{{4, 4}, {5, 4}}, + Health: 99, + }, + }, + Food: []Point{}, + Hazards: []Point{}}, +} + +// Checks snakes on the same squad share health (assuming the setting is enabled) +var squadCaseEatFoodAndShareHealth = gameTestCase{ + "Squad Case Move Squad Collisions", + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "snake1squad1", + Body: []Point{{1, 1}, {2, 1}}, + Health: 80, + }, + { + ID: "snake2squad1", + Body: []Point{{7, 7}, {7, 8}}, + Health: 50, + }, + { + ID: "snake3squad2", + Body: []Point{{4, 4}, {4, 5}}, + Health: 60, + }, + { + ID: "snake4squad2", + Body: []Point{{5, 4}, {5, 5}}, + Health: 71, + }, + }, + Food: []Point{{1, 2}}, + Hazards: []Point{}, + }, + []SnakeMove{ + {ID: "snake1squad1", Move: MoveUp}, + {ID: "snake2squad1", Move: MoveDown}, + {ID: "snake3squad2", Move: MoveRight}, + {ID: "snake4squad2", Move: MoveLeft}, + }, + nil, + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "snake1squad1", + Body: []Point{{1, 2}, {1, 1}, {1, 1}}, + Health: 100, + }, + { + ID: "snake2squad1", + Body: []Point{{7, 6}, {7, 7}}, + Health: 100, + }, + { + ID: "snake3squad2", + Body: []Point{{5, 4}, {4, 4}}, + Health: 70, + }, + { + ID: "snake4squad2", + Body: []Point{{4, 4}, {5, 4}}, + Health: 70, + }, + }, + Food: []Point{}, + Hazards: []Point{}}, +} + +func TestSquadCreateNextBoardState(t *testing.T) { + standardCases := []gameTestCase{ + // inherits these test cases from standard + standardCaseErrNoMoveFound, + standardCaseErrZeroLengthSnake, + standardCaseMoveEatAndGrow, + } + r := SquadRuleset{ + SquadMap: map[string]string{ + "snake1squad1": "squad1", + "snake2squad1": "squad1", + "snake3squad2": "squad2", + "snake4squad2": "squad2", + }, + } + rand.Seed(0) + for _, gc := range standardCases { + gc.requireValidNextState(t, &r) + } + + extendedCases := []gameTestCase{ + squadCaseMoveSquadCollisions, + squadCaseEatFoodAndShareHealth, + } + r.SharedHealth = true + r.AllowBodyCollisions = true + for _, gc := range extendedCases { + gc.requireValidNextState(t, &r) + } +} diff --git a/standard_test.go b/standard_test.go index 776fc64..aec06dd 100644 --- a/standard_test.go +++ b/standard_test.go @@ -43,131 +43,181 @@ func TestStandardName(t *testing.T) { require.Equal(t, "standard", r.Name()) } -func TestCreateNextBoardState(t *testing.T) { - tests := []struct { - prevState *BoardState - moves []SnakeMove - expectedError error - expectedState *BoardState - }{ - { - &BoardState{ - Width: 10, - Height: 10, - Snakes: []Snake{ - { - ID: "one", - Body: []Point{{1, 1}, {1, 2}}, - Health: 100, - }, - { - ID: "two", - Body: []Point{{3, 4}, {3, 3}}, - Health: 100, - }, - }, - Food: []Point{{0, 0}, {1, 0}}, - Hazards: []Point{}, +// Checks that the error for a snake missing a move is returned +var standardCaseErrNoMoveFound = gameTestCase{ + "Standard Case Error No Move Found", + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "one", + Body: []Point{{1, 1}, {1, 2}}, + Health: 100, }, - []SnakeMove{}, - ErrorNoMoveFound, - nil, - }, - { - &BoardState{ - Width: 10, - Height: 10, - Snakes: []Snake{ - { - ID: "one", - Body: []Point{{1, 1}, {1, 2}}, - Health: 100, - }, - { - ID: "two", - Body: []Point{}, - Health: 100, - }, - }, - Food: []Point{{0, 0}, {1, 0}}, - Hazards: []Point{}, - }, - []SnakeMove{ - {ID: "one", Move: MoveUp}, - {ID: "two", Move: MoveDown}, - }, - ErrorZeroLengthSnake, - nil, - }, - { - &BoardState{ - Width: 10, - Height: 10, - Snakes: []Snake{ - { - ID: "one", - Body: []Point{{1, 1}, {1, 2}}, - Health: 100, - }, - { - ID: "two", - Body: []Point{{3, 4}, {3, 3}}, - Health: 100, - }, - { - ID: "three", - Body: []Point{}, - Health: 100, - EliminatedCause: EliminatedByOutOfBounds, - }, - }, - Food: []Point{{0, 0}, {1, 0}}, - Hazards: []Point{}, - }, - []SnakeMove{ - {ID: "one", Move: MoveDown}, - {ID: "two", Move: MoveUp}, - {ID: "three", Move: MoveLeft}, // Should be ignored - }, - nil, - &BoardState{ - Width: 10, - Height: 10, - Snakes: []Snake{ - { - ID: "one", - Body: []Point{{1, 0}, {1, 1}, {1, 1}}, - Health: 100, - }, - { - ID: "two", - Body: []Point{{3, 5}, {3, 4}}, - Health: 99, - }, - { - ID: "three", - Body: []Point{}, - Health: 100, - EliminatedCause: EliminatedByOutOfBounds, - }, - }, - Food: []Point{{0, 0}}, - Hazards: []Point{}, + { + ID: "two", + Body: []Point{{3, 4}, {3, 3}}, + Health: 100, }, }, - } + Food: []Point{{0, 0}, {1, 0}}, + Hazards: []Point{}, + }, + []SnakeMove{}, + ErrorNoMoveFound, + nil, +} +// Checks that the error for a snake with no points is returned +var standardCaseErrZeroLengthSnake = gameTestCase{ + "Standard Case Error Zero Length Snake", + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "one", + Body: []Point{{1, 1}, {1, 2}}, + Health: 100, + }, + { + ID: "two", + Body: []Point{}, + Health: 100, + }, + }, + Food: []Point{{0, 0}, {1, 0}}, + Hazards: []Point{}, + }, + []SnakeMove{ + {ID: "one", Move: MoveUp}, + {ID: "two", Move: MoveDown}, + }, + ErrorZeroLengthSnake, + nil, +} + +// Checks a basic state where a snake moves, eats and grows +var standardCaseMoveEatAndGrow = gameTestCase{ + "Standard Case Move Eat and Grow", + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "one", + Body: []Point{{1, 1}, {1, 2}}, + Health: 100, + }, + { + ID: "two", + Body: []Point{{3, 4}, {3, 3}}, + Health: 100, + }, + { + ID: "three", + Body: []Point{}, + Health: 100, + EliminatedCause: EliminatedByOutOfBounds, + }, + }, + Food: []Point{{0, 0}, {1, 0}}, + Hazards: []Point{}, + }, + []SnakeMove{ + {ID: "one", Move: MoveDown}, + {ID: "two", Move: MoveUp}, + {ID: "three", Move: MoveLeft}, // Should be ignored + }, + nil, + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "one", + Body: []Point{{1, 0}, {1, 1}, {1, 1}}, + Health: 100, + }, + { + ID: "two", + Body: []Point{{3, 5}, {3, 4}}, + Health: 99, + }, + { + ID: "three", + Body: []Point{}, + Health: 100, + EliminatedCause: EliminatedByOutOfBounds, + }, + }, + Food: []Point{{0, 0}}, + Hazards: []Point{}, + }, +} + +// Checks a basic state where two snakes of equal sizes collide, and both should +// be eliminated as a result. +var standardMoveAndCollideMAD = gameTestCase{ + "Standard Case Move and Collide", + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "one", + Body: []Point{{1, 1}, {2, 1}}, + Health: 99, + }, + { + ID: "two", + Body: []Point{{1, 2}, {2, 2}}, + Health: 99, + }, + }, + Food: []Point{}, + Hazards: []Point{}, + }, + []SnakeMove{ + {ID: "one", Move: MoveUp}, + {ID: "two", Move: MoveDown}, + }, + nil, + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "one", + Body: []Point{{1, 2}, {1, 1}}, + Health: 98, + EliminatedCause: EliminatedByCollision, + EliminatedBy: "two", + }, + { + ID: "two", + Body: []Point{{1, 1}, {1, 2}}, + Health: 98, + EliminatedCause: EliminatedByCollision, + EliminatedBy: "one", + }, + }, + Food: []Point{}, + Hazards: []Point{}, + }, +} + +func TestStandardCreateNextBoardState(t *testing.T) { + cases := []gameTestCase{ + standardCaseErrNoMoveFound, + standardCaseErrZeroLengthSnake, + standardCaseMoveEatAndGrow, + standardMoveAndCollideMAD, + } r := StandardRuleset{} - for _, test := range tests { - nextState, err := r.CreateNextBoardState(test.prevState, test.moves) - require.Equal(t, test.expectedError, err) - 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) - } + for _, gc := range cases { + gc.requireValidNextState(t, &r) } } diff --git a/wrapped.go b/wrapped.go index 6399b5d..222a5b8 100644 --- a/wrapped.go +++ b/wrapped.go @@ -60,6 +60,9 @@ func (r *WrappedRuleset) moveSnakes(b *BoardState, moves []SnakeMove) error { for i := 0; i < len(b.Snakes); i++ { snake := &b.Snakes[i] + if snake.EliminatedCause != NotEliminated { + continue + } snake.Body[0].X = replace(snake.Body[0].X, 0, b.Width-1) snake.Body[0].Y = replace(snake.Body[0].Y, 0, b.Height-1) } diff --git a/wrapped_test.go b/wrapped_test.go index 1ba3472..59c0a4f 100644 --- a/wrapped_test.go +++ b/wrapped_test.go @@ -246,3 +246,77 @@ func TestEdgeCrossingEating(t *testing.T) { } } + +// Checks that snakes moving out of bounds get wrapped to the other side. +var wrappedCaseMoveAndWrap = gameTestCase{ + "Wrapped Case Move and Wrap", + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "one", + Body: []Point{{0, 0}, {1, 0}}, + Health: 100, + }, + { + ID: "two", + Body: []Point{{3, 4}, {3, 3}}, + Health: 100, + }, + { + ID: "three", + Body: []Point{}, + Health: 100, + EliminatedCause: EliminatedBySelfCollision, + }, + }, + Food: []Point{}, + Hazards: []Point{}, + }, + []SnakeMove{ + {ID: "one", Move: MoveLeft}, + {ID: "two", Move: MoveUp}, + {ID: "three", Move: MoveLeft}, // Should be ignored + }, + nil, + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "one", + Body: []Point{{9, 0}, {0, 0}}, + Health: 99, + }, + { + ID: "two", + Body: []Point{{3, 5}, {3, 4}}, + Health: 99, + }, + { + ID: "three", + Body: []Point{}, + Health: 100, + EliminatedCause: EliminatedBySelfCollision, + }, + }, + Food: []Point{}, + Hazards: []Point{}, + }, +} + +func TestWrappedCreateNextBoardState(t *testing.T) { + cases := []gameTestCase{ + // inherits these test cases from standard + standardCaseErrNoMoveFound, + standardCaseErrZeroLengthSnake, + standardCaseMoveEatAndGrow, + standardMoveAndCollideMAD, + wrappedCaseMoveAndWrap, + } + r := WrappedRuleset{} + for _, gc := range cases { + gc.requireValidNextState(t, &r) + } +}