From 2ca57f0779b0006531dfb875deb723aa9fa22ec3 Mon Sep 17 00:00:00 2001 From: Brad Van Vugt <1531419+bvanvugt@users.noreply.github.com> Date: Mon, 27 Jul 2020 10:59:52 -0700 Subject: [PATCH] Royale mode does damage instead of eliminating. --- royale.go | 41 +++++++++++++++++------ royale_test.go | 90 ++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 111 insertions(+), 20 deletions(-) diff --git a/royale.go b/royale.go index 3ba97fd..24d2c4f 100644 --- a/royale.go +++ b/royale.go @@ -9,6 +9,7 @@ type RoyaleRuleset struct { Turn int32 ShrinkEveryNTurns int32 + DamagePerTurn int32 // Output OutOfBounds []Point @@ -16,7 +17,7 @@ type RoyaleRuleset struct { func (r *RoyaleRuleset) CreateNextBoardState(prevState *BoardState, moves []SnakeMove) (*BoardState, error) { 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) @@ -24,14 +25,26 @@ func (r *RoyaleRuleset) CreateNextBoardState(prevState *BoardState, moves []Snak 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? + err = r.populateOutOfBounds(nextBoardState, r.Turn-1) + if err != nil { + return nil, err + } + // TODO: LOG? - err = r.populateOutOfBounds(nextBoardState) + err = r.damageOutOfBounds(nextBoardState) if err != nil { return nil, err } // TODO: LOG? - err = r.eliminateOutOfBounds(nextBoardState) + err = r.populateOutOfBounds(nextBoardState, r.Turn) if err != nil { return nil, err } @@ -39,18 +52,18 @@ func (r *RoyaleRuleset) CreateNextBoardState(prevState *BoardState, moves []Snak return nextBoardState, nil } -func (r *RoyaleRuleset) populateOutOfBounds(b *BoardState) error { +func (r *RoyaleRuleset) populateOutOfBounds(b *BoardState, turn int32) error { r.OutOfBounds = []Point{} 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 } - numShrinks := r.Turn / r.ShrinkEveryNTurns + numShrinks := turn / r.ShrinkEveryNTurns minX, maxX := numShrinks, b.Width-1-numShrinks minY, maxY := numShrinks, b.Height-1-numShrinks for x := int32(0); x < b.Width; x++ { @@ -64,15 +77,23 @@ func (r *RoyaleRuleset) populateOutOfBounds(b *BoardState) error { 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++ { 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, eliminate it - snake.EliminatedCause = EliminatedByOutOfBounds + // Snake is now out of bounds, reduce health + snake.Health = snake.Health - r.DamagePerTurn + if snake.Health <= 0 { + snake.Health = 0 + snake.EliminatedCause = EliminatedByStarvation + } } } } diff --git a/royale_test.go b/royale_test.go index 178d042..1b4ad95 100644 --- a/royale_test.go +++ b/royale_test.go @@ -16,14 +16,14 @@ func TestRoyaleDefaultSanity(t *testing.T) { r := RoyaleRuleset{} _, err := r.CreateNextBoardState(boardState, []SnakeMove{}) 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{}) require.NoError(t, err) } -func TestRoyalePopulateObstacles(t *testing.T) { +func TestRoyaleOutOfBounds(t *testing.T) { tests := []struct { Width int32 Height int32 @@ -32,7 +32,7 @@ func TestRoyalePopulateObstacles(t *testing.T) { Error error 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{}}, {Turn: 1, ShrinkEveryNTurns: 1, ExpectedOutOfBounds: []Point{}}, {Width: 3, Height: 3, Turn: 1, ShrinkEveryNTurns: 10, ExpectedOutOfBounds: []Point{}}, @@ -62,7 +62,7 @@ func TestRoyalePopulateObstacles(t *testing.T) { ShrinkEveryNTurns: test.ShrinkEveryNTurns, } - err := r.populateOutOfBounds(b) + err := r.populateOutOfBounds(b, test.Turn) require.Equal(t, test.Error, err) if err == nil { // Obstacles should match @@ -81,7 +81,7 @@ func TestRoyalePopulateObstacles(t *testing.T) { } } -func TestRoyaleEliminateOutOfBounds(t *testing.T) { +func TestRoyaleDamageOutOfBounds(t *testing.T) { tests := []struct { Snakes []Snake OutOfBounds []Point @@ -98,7 +98,7 @@ func TestRoyaleEliminateOutOfBounds(t *testing.T) { { Snakes: []Snake{{Body: []Point{{0, 0}}}}, OutOfBounds: []Point{{0, 0}}, - ExpectedEliminatedCauses: []string{EliminatedByOutOfBounds}, + ExpectedEliminatedCauses: []string{EliminatedByStarvation}, ExpectedEliminatedByIDs: []string{""}, }, { @@ -122,15 +122,15 @@ func TestRoyaleEliminateOutOfBounds(t *testing.T) { {Body: []Point{{3, 3}, {3, 4}, {3, 5}, {3, 6}}}, }, OutOfBounds: []Point{{3, 3}}, - ExpectedEliminatedCauses: []string{NotEliminated, EliminatedByOutOfBounds}, + ExpectedEliminatedCauses: []string{NotEliminated, EliminatedByStarvation}, ExpectedEliminatedByIDs: []string{"", ""}, }, } for _, test := range tests { b := &BoardState{Snakes: test.Snakes} - r := RoyaleRuleset{OutOfBounds: test.OutOfBounds} - err := r.eliminateOutOfBounds(b) + r := RoyaleRuleset{OutOfBounds: test.OutOfBounds, DamagePerTurn: 100} + err := r.damageOutOfBounds(b) require.NoError(t, err) 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)) +}