Royale mode does damage instead of eliminating.

This commit is contained in:
Brad Van Vugt 2020-07-27 10:59:52 -07:00
parent dd5a2fd88f
commit 2ca57f0779
2 changed files with 111 additions and 20 deletions

View file

@ -9,6 +9,7 @@ type RoyaleRuleset struct {
Turn int32 Turn int32
ShrinkEveryNTurns int32 ShrinkEveryNTurns int32
DamagePerTurn int32
// Output // Output
OutOfBounds []Point OutOfBounds []Point
@ -16,7 +17,7 @@ type RoyaleRuleset struct {
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.ShrinkEveryNTurns < 1 {
return nil, errors.New("royale game must shrink at least every 1 turn") return nil, errors.New("royale game must shrink at least every turn")
} }
nextBoardState, err := r.StandardRuleset.CreateNextBoardState(prevState, moves) nextBoardState, err := r.StandardRuleset.CreateNextBoardState(prevState, moves)
@ -24,14 +25,26 @@ func (r *RoyaleRuleset) CreateNextBoardState(prevState *BoardState, moves []Snak
return nil, err return nil, err
} }
// Algorithm:
// - Populate OOB for last 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? // TODO: LOG?
err = r.populateOutOfBounds(nextBoardState) err = r.populateOutOfBounds(nextBoardState, r.Turn-1)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO: LOG? // TODO: LOG?
err = r.eliminateOutOfBounds(nextBoardState) 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
} }
@ -39,18 +52,18 @@ func (r *RoyaleRuleset) CreateNextBoardState(prevState *BoardState, moves []Snak
return nextBoardState, nil return nextBoardState, nil
} }
func (r *RoyaleRuleset) populateOutOfBounds(b *BoardState) error { func (r *RoyaleRuleset) populateOutOfBounds(b *BoardState, turn int32) error {
r.OutOfBounds = []Point{} r.OutOfBounds = []Point{}
if r.ShrinkEveryNTurns < 1 { if r.ShrinkEveryNTurns < 1 {
return errors.New("royale game must shrink at least every 1 turn") return errors.New("royale game must shrink at least every turn")
} }
if r.Turn < r.ShrinkEveryNTurns { if turn < r.ShrinkEveryNTurns {
return nil return nil
} }
numShrinks := r.Turn / r.ShrinkEveryNTurns numShrinks := turn / r.ShrinkEveryNTurns
minX, maxX := numShrinks, b.Width-1-numShrinks minX, maxX := numShrinks, b.Width-1-numShrinks
minY, maxY := numShrinks, b.Height-1-numShrinks minY, maxY := numShrinks, b.Height-1-numShrinks
for x := int32(0); x < b.Width; x++ { for x := int32(0); x < b.Width; x++ {
@ -64,15 +77,23 @@ func (r *RoyaleRuleset) populateOutOfBounds(b *BoardState) error {
return nil return nil
} }
func (r *RoyaleRuleset) eliminateOutOfBounds(b *BoardState) error { 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++ { for i := 0; i < len(b.Snakes); i++ {
snake := &b.Snakes[i] snake := &b.Snakes[i]
if snake.EliminatedCause == NotEliminated { if snake.EliminatedCause == NotEliminated {
head := snake.Body[0] head := snake.Body[0]
for _, p := range r.OutOfBounds { for _, p := range r.OutOfBounds {
if head == p { if head == p {
// Snake is now out of bounds, eliminate it // Snake is now out of bounds, reduce health
snake.EliminatedCause = EliminatedByOutOfBounds snake.Health = snake.Health - r.DamagePerTurn
if snake.Health <= 0 {
snake.Health = 0
snake.EliminatedCause = EliminatedByStarvation
}
} }
} }
} }

View file

@ -16,14 +16,14 @@ func TestRoyaleDefaultSanity(t *testing.T) {
r := RoyaleRuleset{} r := RoyaleRuleset{}
_, err := r.CreateNextBoardState(boardState, []SnakeMove{}) _, err := r.CreateNextBoardState(boardState, []SnakeMove{})
require.Error(t, err) require.Error(t, err)
require.Equal(t, err, errors.New("royale game must shrink at least every 1 turn")) require.Equal(t, errors.New("royale game must shrink at least every turn"), err)
r = RoyaleRuleset{ShrinkEveryNTurns: 1} r = RoyaleRuleset{ShrinkEveryNTurns: 1, DamagePerTurn: 1}
_, err = r.CreateNextBoardState(boardState, []SnakeMove{}) _, err = r.CreateNextBoardState(boardState, []SnakeMove{})
require.NoError(t, err) require.NoError(t, err)
} }
func TestRoyalePopulateObstacles(t *testing.T) { func TestRoyaleOutOfBounds(t *testing.T) {
tests := []struct { tests := []struct {
Width int32 Width int32
Height int32 Height int32
@ -32,7 +32,7 @@ func TestRoyalePopulateObstacles(t *testing.T) {
Error error Error error
ExpectedOutOfBounds []Point ExpectedOutOfBounds []Point
}{ }{
{Error: errors.New("royale game must shrink at least every 1 turn")}, {Error: errors.New("royale game must shrink at least every turn")},
{ShrinkEveryNTurns: 1, ExpectedOutOfBounds: []Point{}}, {ShrinkEveryNTurns: 1, ExpectedOutOfBounds: []Point{}},
{Turn: 1, ShrinkEveryNTurns: 1, ExpectedOutOfBounds: []Point{}}, {Turn: 1, ShrinkEveryNTurns: 1, ExpectedOutOfBounds: []Point{}},
{Width: 3, Height: 3, Turn: 1, ShrinkEveryNTurns: 10, ExpectedOutOfBounds: []Point{}}, {Width: 3, Height: 3, Turn: 1, ShrinkEveryNTurns: 10, ExpectedOutOfBounds: []Point{}},
@ -62,7 +62,7 @@ func TestRoyalePopulateObstacles(t *testing.T) {
ShrinkEveryNTurns: test.ShrinkEveryNTurns, ShrinkEveryNTurns: test.ShrinkEveryNTurns,
} }
err := r.populateOutOfBounds(b) err := r.populateOutOfBounds(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
@ -81,7 +81,7 @@ func TestRoyalePopulateObstacles(t *testing.T) {
} }
} }
func TestRoyaleEliminateOutOfBounds(t *testing.T) { func TestRoyaleDamageOutOfBounds(t *testing.T) {
tests := []struct { tests := []struct {
Snakes []Snake Snakes []Snake
OutOfBounds []Point OutOfBounds []Point
@ -98,7 +98,7 @@ func TestRoyaleEliminateOutOfBounds(t *testing.T) {
{ {
Snakes: []Snake{{Body: []Point{{0, 0}}}}, Snakes: []Snake{{Body: []Point{{0, 0}}}},
OutOfBounds: []Point{{0, 0}}, OutOfBounds: []Point{{0, 0}},
ExpectedEliminatedCauses: []string{EliminatedByOutOfBounds}, ExpectedEliminatedCauses: []string{EliminatedByStarvation},
ExpectedEliminatedByIDs: []string{""}, ExpectedEliminatedByIDs: []string{""},
}, },
{ {
@ -122,15 +122,15 @@ func TestRoyaleEliminateOutOfBounds(t *testing.T) {
{Body: []Point{{3, 3}, {3, 4}, {3, 5}, {3, 6}}}, {Body: []Point{{3, 3}, {3, 4}, {3, 5}, {3, 6}}},
}, },
OutOfBounds: []Point{{3, 3}}, OutOfBounds: []Point{{3, 3}},
ExpectedEliminatedCauses: []string{NotEliminated, EliminatedByOutOfBounds}, ExpectedEliminatedCauses: []string{NotEliminated, EliminatedByStarvation},
ExpectedEliminatedByIDs: []string{"", ""}, ExpectedEliminatedByIDs: []string{"", ""},
}, },
} }
for _, test := range tests { for _, test := range tests {
b := &BoardState{Snakes: test.Snakes} b := &BoardState{Snakes: test.Snakes}
r := RoyaleRuleset{OutOfBounds: test.OutOfBounds} r := RoyaleRuleset{OutOfBounds: test.OutOfBounds, DamagePerTurn: 100}
err := r.eliminateOutOfBounds(b) err := r.damageOutOfBounds(b)
require.NoError(t, err) require.NoError(t, err)
for i, snake := range b.Snakes { for i, snake := range b.Snakes {
@ -139,3 +139,73 @@ func TestRoyaleEliminateOutOfBounds(t *testing.T) {
} }
} }
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, EliminatedByStarvation, nil},
{100, 101, 0, EliminatedByStarvation, nil},
{100, 999, 0, EliminatedByStarvation, nil},
{2, 1, 1, NotEliminated, nil},
{1, 1, 0, EliminatedByStarvation, nil},
{1, 999, 0, EliminatedByStarvation, nil},
{0, 1, 0, EliminatedByStarvation, nil},
{0, 999, 0, EliminatedByStarvation, 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) {
b := &BoardState{Width: 10, Height: 10, Snakes: []Snake{{ID: "one", Health: 100, Body: []Point{{1, 1}}}}}
r := RoyaleRuleset{Turn: 10, ShrinkEveryNTurns: 10, DamagePerTurn: 30}
m := []SnakeMove{{ID: "one", Move: "right"}}
n, err := r.CreateNextBoardState(b, m)
require.NoError(t, err)
require.Equal(t, NotEliminated, n.Snakes[0].EliminatedCause)
require.Equal(t, int32(99), n.Snakes[0].Health)
require.Equal(t, Point{2, 1}, n.Snakes[0].Body[0])
require.Equal(t, 36, len(r.OutOfBounds))
r.Turn = 20
n, err = r.CreateNextBoardState(b, m)
require.NoError(t, err)
require.Equal(t, NotEliminated, n.Snakes[0].EliminatedCause)
require.Equal(t, int32(99), n.Snakes[0].Health)
require.Equal(t, Point{2, 1}, n.Snakes[0].Body[0])
require.Equal(t, 64, len(r.OutOfBounds))
r.Turn = 21
n, err = r.CreateNextBoardState(b, m)
require.NoError(t, err)
require.Equal(t, NotEliminated, n.Snakes[0].EliminatedCause)
require.Equal(t, int32(69), n.Snakes[0].Health)
require.Equal(t, Point{2, 1}, n.Snakes[0].Body[0])
require.Equal(t, 64, len(r.OutOfBounds))
b.Snakes[0].Health = 15
n, err = r.CreateNextBoardState(b, m)
require.NoError(t, err)
require.Equal(t, EliminatedByStarvation, n.Snakes[0].EliminatedCause)
require.Equal(t, int32(0), n.Snakes[0].Health)
require.Equal(t, Point{2, 1}, n.Snakes[0].Body[0])
require.Equal(t, 64, len(r.OutOfBounds))
}