From b33d93599a86d8399bcfde47a1dbf0ab54639189 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Mon, 15 Apr 2019 20:19:52 -0700 Subject: [PATCH] Unit Tests for Collision Detection --- pkg/collision/actors_test.go | 108 ++++++++++++++++++++ pkg/collision/level_test.go | 192 +++++++++++++++++++++++++++++++++++ 2 files changed, 300 insertions(+) create mode 100644 pkg/collision/actors_test.go create mode 100644 pkg/collision/level_test.go diff --git a/pkg/collision/actors_test.go b/pkg/collision/actors_test.go new file mode 100644 index 0000000..63bfe10 --- /dev/null +++ b/pkg/collision/actors_test.go @@ -0,0 +1,108 @@ +package collision_test + +import ( + "testing" + + "git.kirsle.net/apps/doodle/lib/render" + "git.kirsle.net/apps/doodle/pkg/collision" +) + +func TestActorCollision(t *testing.T) { + boxes := []render.Rect{ + // 0: intersects with 1 + render.Rect{ + X: 0, + Y: 0, + W: 100, + H: 100, + }, + + // 1: intersects with 0 + render.Rect{ + X: 90, + Y: 10, + W: 100, + H: 100, + }, + + // 2: no intersection + render.Rect{ + X: 200, + Y: 200, + W: 32, + H: 32, + }, + + // 3: intersects with 4 + render.Rect{ + X: 233, + Y: 200, + W: 32, + H: 32, + }, + + // 4: intersects with 3 + render.Rect{ + X: 240, + Y: 200, + W: 32, + H: 32, + }, + + // 5: completely contains 6 and intersects 7. + render.Rect{ + X: 300, + Y: 300, + W: 1000, + H: 600, + }, + render.Rect{ + X: 450, + Y: 500, + W: 42, + H: 42, + }, + render.Rect{ + X: 1200, + Y: 350, + W: 512, + H: 512, + }, + } + + assert := func(i int, result collision.IndexTuple, expectA, expectB int) { + if result[0] != expectA || result[1] != expectB { + t.Errorf( + "unexpected collision at index %d of BetweenBoxes() generator\n"+ + "expected: (%d,%d)\n"+ + " but got: (%d,%d)", + i, + expectA, expectB, + result[0], result[1], + ) + } + } + + var i int + for overlap := range collision.BetweenBoxes(boxes) { + a, b := overlap[0], overlap[1] + + // Ensure expected collisions happened. + switch i { + case 0: + assert(i, overlap, 0, 1) + case 1: + assert(i, overlap, 3, 4) + case 2: + assert(i, overlap, 5, 6) + case 3: + assert(i, overlap, 5, 7) + default: + t.Errorf("got unexpected collision result, index %d, tuple (%d,%d)", + i, a, b, + ) + } + + i++ + } +} diff --git a/pkg/collision/level_test.go b/pkg/collision/level_test.go new file mode 100644 index 0000000..65a8365 --- /dev/null +++ b/pkg/collision/level_test.go @@ -0,0 +1,192 @@ +package collision_test + +import ( + "fmt" + "testing" + + "git.kirsle.net/apps/doodle/lib/render" + "git.kirsle.net/apps/doodle/pkg/collision" + "git.kirsle.net/apps/doodle/pkg/doodads/dummy" + "git.kirsle.net/apps/doodle/pkg/level" +) + +func TestCollisionFunctions(t *testing.T) { + // Create a basic level for testing. + grid := level.NewChunker(1000) + solid := &level.Swatch{ + Name: "solid", + Color: render.Black, + Solid: true, + } + + // with a solid platform at y=500 and x=0..1000 + for i := 0; i < 1000; i++ { + grid.Set(render.NewPoint(int32(i), 500), solid) + } + + // and a short wall in the middle of the platform + for i := 480; i < 500; i++ { + grid.Set(render.NewPoint(500, int32(i)), solid) + } + + // Make a dummy player character. + player := dummy.NewPlayer() + playerSize := player.Size() + + // Table based test schema. + type testCase struct { + Start render.Point + MoveTo render.Point + ExpectCollision bool + Expect *collision.Collide + } + + // Describe the details of the test on failure. + describeTest := func(t testCase, result *collision.Collide, b bool) string { + return fmt.Sprintf( + " Moving From: %s to %s\n"+ + "Expected Collision: %+v (%+v)\n"+ + " Got Collision: %+v (%+v)", + t.Start, t.MoveTo, + t.ExpectCollision, t.Expect, + b, result, + ) + } + + // Test cases to check. + tests := []testCase{ + testCase{ + Start: render.NewPoint(0, 0), + MoveTo: render.NewPoint(8, 8), + ExpectCollision: false, + }, + + // Player is standing on the floor at X=100 + // with their feet at Y=500 and they move right + // 10 pixels. + testCase{ + Start: render.NewPoint( + 100, + 500-playerSize.H, + ), + MoveTo: render.NewPoint( + 110, + 500-playerSize.H, + ), + ExpectCollision: true, + Expect: &collision.Collide{ + Bottom: true, + }, + }, + + // Player walks off the right edge of the platform. + testCase{ + // TODO: if the player is perfectly touching the floor, + // this test fails and returns True for collision, so + // I use 499-playerSize.H so they hover above the floor. + Start: render.NewPoint( + 990, + 499-playerSize.H, + ), + MoveTo: render.NewPoint( + 1100, + 499-playerSize.H, + ), + ExpectCollision: false, + }, + + // Player moves through the barrier in the middle and + // is stopped in his tracks. + testCase{ + Start: render.NewPoint( + 490-playerSize.W, 500-playerSize.H, + ), + MoveTo: render.NewPoint( + 510, 500-playerSize.H, + ), + ExpectCollision: true, + Expect: &collision.Collide{ + Right: true, + Left: true, // TODO: not expected + Bottom: true, + MoveTo: render.NewPoint( + 500-playerSize.W, + 500-playerSize.H, + ), + }, + }, + + // Player moves up from below the platform and hits the ceiling. + testCase{ + Start: render.NewPoint( + 490-playerSize.W, + 550, + ), + MoveTo: render.NewPoint( + 490-playerSize.W, + 499-playerSize.H, + ), + ExpectCollision: true, + Expect: &collision.Collide{ + Top: true, + + // TODO: these are unexpected + Left: true, + Right: true, + Bottom: true, + + // TODO: the MoveTo is unexpected + MoveTo: render.NewPoint(458, 468), + // MoveTo: render.NewPoint( + // 490-playerSize.W, + // 500, + // ), + }, + }, + } + + for i, test := range tests { + player.MoveTo(test.Start) + result, collided := collision.CollidesWithGrid( + player, grid, test.MoveTo, + ) + + // Was there a collision at all? + if collided && !test.ExpectCollision { + t.Errorf( + "Test %d: we collided when we did not expect to!\n%s", + i, + describeTest(test, result, collided), + ) + } else if !collided && test.ExpectCollision { + t.Errorf( + "Test %d: we did not collide but we expected to!\n%s", + i, + describeTest(test, result, collided), + ) + } else if test.Expect != nil { + // Assert that each side is what we expected. + expect := test.Expect + if result.Top && !expect.Top || result.Left && !expect.Left || + result.Right && !expect.Right || result.Bottom && !expect.Bottom { + t.Errorf( + "Test %d: collided as expected, but not the right sides!\n%s", + i, + describeTest(test, result, collided), + ) + } + + // Was the MoveTo position expected? + if expect.MoveTo != render.Origin && result.MoveTo != expect.MoveTo { + t.Errorf( + "Test %d: collided as expected, but didn't move as expected!\n"+ + "Expected to move to: %s\n"+ + " But actually was: %s", + i, + expect.MoveTo, + result.MoveTo, + ) + } + } + } +}