Add game over detection to each ruleset.

This commit is contained in:
Brad Van Vugt 2020-05-17 14:22:09 -07:00
parent 44b6b94666
commit 71fc6bf503
5 changed files with 150 additions and 2 deletions

View file

@ -35,4 +35,5 @@ type SnakeMove struct {
type Ruleset interface { type Ruleset interface {
CreateInitialBoardState(width int32, height int32, snakeIDs []string) (*BoardState, error) CreateInitialBoardState(width int32, height int32, snakeIDs []string) (*BoardState, error)
CreateNextBoardState(prevState *BoardState, moves []SnakeMove) (*BoardState, error) CreateNextBoardState(prevState *BoardState, moves []SnakeMove) (*BoardState, error)
IsGameFinished(state *BoardState) (bool, error)
} }

View file

@ -439,3 +439,13 @@ func (r *StandardRuleset) getUnoccupiedPoints(b *BoardState) []Point {
} }
return unoccupiedPoints return unoccupiedPoints
} }
func (r *StandardRuleset) IsGameOver(b *BoardState) (bool, error) {
numSnakesRemaining := 0
for i := 0; i < len(b.Snakes); i++ {
if b.Snakes[i].EliminatedCause == NotEliminated {
numSnakesRemaining++
}
}
return numSnakesRemaining <= 1, nil
}

View file

@ -1155,3 +1155,72 @@ func TestMaybeSpawnFood(t *testing.T) {
} }
} }
} }
func TestIsGameOver(t *testing.T) {
tests := []struct {
Snakes []Snake
Expected bool
}{
{[]Snake{}, true},
{[]Snake{{}}, true},
{[]Snake{{}, {}}, false},
{[]Snake{{}, {}, {}, {}, {}}, false},
{
[]Snake{
{EliminatedCause: EliminatedByCollision},
{EliminatedCause: NotEliminated},
},
true,
},
{
[]Snake{
{EliminatedCause: NotEliminated},
{EliminatedCause: EliminatedByCollision},
{EliminatedCause: NotEliminated},
{EliminatedCause: NotEliminated},
},
false,
},
{
[]Snake{
{EliminatedCause: EliminatedByOutOfBounds},
{EliminatedCause: EliminatedByOutOfBounds},
{EliminatedCause: EliminatedByOutOfBounds},
{EliminatedCause: EliminatedByOutOfBounds},
},
true,
},
{
[]Snake{
{EliminatedCause: EliminatedByOutOfBounds},
{EliminatedCause: EliminatedByOutOfBounds},
{EliminatedCause: EliminatedByOutOfBounds},
{EliminatedCause: NotEliminated},
},
true,
},
{
[]Snake{
{EliminatedCause: EliminatedByOutOfBounds},
{EliminatedCause: EliminatedByOutOfBounds},
{EliminatedCause: NotEliminated},
{EliminatedCause: NotEliminated},
},
false,
},
}
r := StandardRuleset{}
for _, test := range tests {
b := &BoardState{
Height: 11,
Width: 11,
Snakes: test.Snakes,
Food: []Point{},
}
actual, err := r.IsGameOver(b)
require.NoError(t, err)
require.Equal(t, test.Expected, actual)
}
}

22
team.go
View file

@ -44,7 +44,7 @@ func (r *TeamRuleset) areSnakesOnSameTeam(snake *Snake, other *Snake) bool {
} }
func (r *TeamRuleset) areSnakeIDsOnSameTeam(snakeID string, otherID string) bool { func (r *TeamRuleset) areSnakeIDsOnSameTeam(snakeID string, otherID string) bool {
return snakeID != otherID && r.TeamMap[snakeID] == r.TeamMap[otherID] return r.TeamMap[snakeID] == r.TeamMap[otherID]
} }
func (r *TeamRuleset) resurrectTeamBodyCollisions(b *BoardState) error { func (r *TeamRuleset) resurrectTeamBodyCollisions(b *BoardState) error {
@ -58,7 +58,7 @@ func (r *TeamRuleset) resurrectTeamBodyCollisions(b *BoardState) error {
if snake.EliminatedBy == "" { if snake.EliminatedBy == "" {
return errors.New("snake eliminated by collision and eliminatedby is not set") return errors.New("snake eliminated by collision and eliminatedby is not set")
} }
if r.areSnakeIDsOnSameTeam(snake.ID, snake.EliminatedBy) { if snake.ID != snake.EliminatedBy && r.areSnakeIDsOnSameTeam(snake.ID, snake.EliminatedBy) {
snake.EliminatedCause = NotEliminated snake.EliminatedCause = NotEliminated
snake.EliminatedBy = "" snake.EliminatedBy = ""
} }
@ -108,3 +108,21 @@ func (r *TeamRuleset) shareTeamAttributes(b *BoardState) error {
return nil return nil
} }
func (r *TeamRuleset) IsGameOver(b *BoardState) (bool, error) {
snakesRemaining := []*Snake{}
for i := 0; i < len(b.Snakes); i++ {
if b.Snakes[i].EliminatedCause == NotEliminated {
snakesRemaining = append(snakesRemaining, &b.Snakes[i])
}
}
for i := 0; i < len(snakesRemaining); i++ {
if !r.areSnakesOnSameTeam(snakesRemaining[i], snakesRemaining[0]) {
// There are multiple teams remaining
return false, nil
}
}
// no snakes or single team remaining
return true, nil
}

View file

@ -284,3 +284,53 @@ func TestSharedAttributesErrorLengthZero(t *testing.T) {
err := r.shareTeamAttributes(boardState) err := r.shareTeamAttributes(boardState)
require.Error(t, err) require.Error(t, err)
} }
func TestTeamIsGameOver(t *testing.T) {
tests := []struct {
Snakes []Snake
TeamMap map[string]string
Expected bool
}{
{[]Snake{}, map[string]string{}, true},
{[]Snake{{ID: "R1"}}, map[string]string{"R1": "red"}, true},
{
[]Snake{{ID: "R1"}, {ID: "R2"}, {ID: "R3"}},
map[string]string{"R1": "red", "R2": "red", "R3": "red"},
true,
},
{
[]Snake{{ID: "R1"}, {ID: "B1"}},
map[string]string{"R1": "red", "B1": "blue"},
false,
},
{
[]Snake{{ID: "R1"}, {ID: "B1"}, {ID: "B2"}, {ID: "G1"}},
map[string]string{"R1": "red", "B1": "blue", "B2": "blue", "G1": "green"},
false,
},
{
[]Snake{
{ID: "R1", EliminatedCause: EliminatedByOutOfBounds},
{ID: "B1", EliminatedCause: EliminatedBySelfCollision, EliminatedBy: "B1"},
{ID: "B2", EliminatedCause: EliminatedByCollision, EliminatedBy: "B2"},
{ID: "G1"},
},
map[string]string{"R1": "red", "B1": "blue", "B2": "blue", "G1": "green"},
true,
},
}
for _, test := range tests {
b := &BoardState{
Height: 11,
Width: 11,
Snakes: test.Snakes,
Food: []Point{},
}
r := TeamRuleset{TeamMap: test.TeamMap}
actual, err := r.IsGameOver(b)
require.NoError(t, err)
require.Equal(t, test.Expected, actual)
}
}