From a342f87ed6c18f16d3d0fc099d94d047e31d4611 Mon Sep 17 00:00:00 2001 From: Brad Van Vugt <1531419+bvanvugt@users.noreply.github.com> Date: Mon, 31 Aug 2020 13:18:03 -0700 Subject: [PATCH] Ensure snakes eating on their last turn survive! --- standard.go | 23 ++++++++--------- standard_test.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 12 deletions(-) diff --git a/standard.go b/standard.go index a3fe9ef..1651eac 100644 --- a/standard.go +++ b/standard.go @@ -218,18 +218,11 @@ func (r *StandardRuleset) CreateNextBoardState(prevState *BoardState, moves []Sn } // TODO: LOG? - err = r.maybeEliminateSnakes(nextState) - if err != nil { - return nil, err - } - - // TODO - // bvanvugt: we specifically want this to happen before elimination - // so that head-to-head collisions on food still remove the food. - // It does create an artifact though, where head-to-head collisions - // of equal length actually show length + 1 - - // TODO: LOG? + // bvanvugt: We specifically want this to happen before elimination for two reasons: + // 1) We want snakes to be able to eat on their very last turn and still survive. + // 2) So that head-to-head collisions on food still remove the food. + // This does create an artifact though, where head-to-head collisions + // of equal length actually show length + 1 and full health, as if both snakes ate. err = r.maybeFeedSnakes(nextState) if err != nil { return nil, err @@ -241,6 +234,12 @@ func (r *StandardRuleset) CreateNextBoardState(prevState *BoardState, moves []Sn return nil, err } + // TODO: LOG? + err = r.maybeEliminateSnakes(nextState) + if err != nil { + return nil, err + } + return nextState, nil } diff --git a/standard_test.go b/standard_test.go index dab6a5e..b065dd3 100644 --- a/standard_test.go +++ b/standard_test.go @@ -566,6 +566,71 @@ func TestCreateNextBoardState(t *testing.T) { } } +func TestEatingOnLastMove(t *testing.T) { + // We want to specifically ensure that snakes eating food on their last turn + // survive. It used to be that this wasn't the case, and snakes were eliminated + // if they moved onto food with their final move. This behaviour wasn't "wrong" or incorrect, + // it just was less fun to watch. So let's ensure we're always giving snakes every possible + // changes to reach food before eliminating them. + tests := []struct { + prevState *BoardState + moves []SnakeMove + expectedError error + expectedState *BoardState + }{ + { + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "one", + Body: []Point{{0, 2}, {0, 1}, {0, 0}}, + Health: 1, + }, + { + ID: "two", + Body: []Point{{3, 2}, {3, 3}, {3, 4}}, + Health: 1, + }, + }, + Food: []Point{{0, 3}, {9, 9}}, + }, + []SnakeMove{ + {ID: "one", Move: MoveDown}, + {ID: "two", Move: MoveUp}, + }, + nil, + &BoardState{ + Width: 10, + Height: 10, + Snakes: []Snake{ + { + ID: "one", + Body: []Point{{0, 3}, {0, 2}, {0, 1}, {0, 1}}, + Health: 100, + }, + { + ID: "two", + Body: []Point{{3, 1}, {3, 2}, {3, 3}}, + Health: 0, + EliminatedCause: EliminatedByStarvation, + }, + }, + Food: []Point{{9, 9}}, + }, + }, + } + + rand.Seed(0) // Seed with a value that will reliably not spawn food + r := StandardRuleset{} + for _, test := range tests { + nextState, err := r.CreateNextBoardState(test.prevState, test.moves) + require.Equal(t, err, test.expectedError) + require.Equal(t, nextState, test.expectedState) + } +} + func TestMoveSnakes(t *testing.T) { b := &BoardState{ Snakes: []Snake{