From 98ba22e7064db57316dfff1ae127feb3dceeb73e Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Thu, 31 Jul 2014 13:58:22 +0200 Subject: Initial commit --- tests/logicTests.cpp | 685 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 685 insertions(+) create mode 100644 tests/logicTests.cpp (limited to 'tests/logicTests.cpp') diff --git a/tests/logicTests.cpp b/tests/logicTests.cpp new file mode 100644 index 0000000..97d9ed9 --- /dev/null +++ b/tests/logicTests.cpp @@ -0,0 +1,685 @@ +/** @file logicTests.cpp +* @brief Unit tests for the logic layer of a Rally-X game. +* +* The classes used to run Allegro's install and uninstall functions were tested indirectly +* through the use of other classes. If the AllegroWrappers classes are not working, they will +* result in tests failing, or the program crashing. +* +* The Car class's movement is tested through implementing a subclass of Car, PlayerCar, facing +* in a given direction with and without maze walls in the way, and testing its position after +* a frame has passed. +* +* The counting system with the Checkpoint class is tested through repeated creation and +* destruction of Checkpoint objects. +* +* The CollisionDetecter class is tested by setting up situations with each type of collision, +* and checking that they have the desired results on the objects involved. +* +* The DestroyedObjectPopup class is tested to ensure that it lasts the desired amount of time +* before being destroyed. +* +* The intelligence of the EnemyCar class is not unit tested, as it may change as extra states +* and AI schemes are implemented, possibly including a random element. Therefore, it should be +* tested manually by playing example levels and monitoring if the enemies appear to behave in +* a manner that makes gameplay interesting. +* +* The Game class is not tested directly. It pulls all of the units being tested together into +* a complete game, and so should be tested by running the Game. +* +* The GameObject class is tested indirectly through testing its subclasses. +* +* The LimitedTimeObject class is tested indirectly through the DestroyedObjectPopup and Smokescreen classes. +* +* The Maze class is tested in terms of its ability to construct itself correctly from a list of +* coordinates. It is also tested with the PlayerCar class where the movement of the PlayerCar is +* tested in a Maze. +* +* The MazeMath class is tested by running each of its functions with expected input and output values. +* +* The PlayerCar class is tested on its ability to move correctly as part of the tests for the Car class. +* The functioning of the PlayerCar's petrol system is also tested. Controlling of the PlayerCar +* is not tested, and should be tested manually by running the game and verifying that the arrow keys +* cause the direction of the PlayerCar to change appropriately. Further, the creation of Smokescreens +* by pressing the spacebar should also be tested manually. +* +* The Rock class only has functionality in collisions, and so is tested through the CollisionDetector's +* tests. +* +* The Smokescreen class is tested in that it disappears after a set time. Its affect on the EnemyCar class +* is tested through the CollisionDetector's tests. +* +* @author Justin Wernick +* @author David Schneider +*/ + +#include +#include +#include +using namespace std; + +#include + +#include "../source/logic/CollisionDetector.h" +#include "../source/logic/PlayerCar.h" +#include "../source/logic/EnemyCar.h" +#include "../source/logic/Checkpoint.h" +#include "../source/logic/Rock.h" +#include "../source/logic/Smokescreen.h" +#include "../source/logic/Maze.h" +#include "../source/logic/MazeMath.h" +#include "../source/logic/DestroyedObjectPopup.h" + +/** +* @brief Tests that PlayerCar moves the correct amount in the up direction on each frame without obstacles. +*/ +TEST(Car, updateMovesPlayerUpInEmptyMaze) +{ + Maze testMaze; + vector > walls; + testMaze.generateMaze(walls,5,5); + + PlayerCar player(2,2,Maze::UP); + + list smokescreens; + player.update(testMaze, smokescreens); + + double expectX = 2; + double expectY = 2-player.speed(); + EXPECT_FLOAT_EQ(expectX, player.x()); + EXPECT_FLOAT_EQ(expectY, player.y()); +} + +/** +* @brief Tests that PlayerCar moves the correct amount in the down direction on each frame without obstacles. +*/ +TEST(Car, updateMovesPlayerDownInEmptyMaze) +{ + Maze testMaze; + vector > walls; + testMaze.generateMaze(walls,5,5); + + PlayerCar player(2,2,Maze::DOWN); + + list smokescreens; + player.update(testMaze, smokescreens); + + double expectX = 2; + double expectY = 2+player.speed(); + EXPECT_FLOAT_EQ(expectX, player.x()); + EXPECT_FLOAT_EQ(expectY, player.y()); +} + +/** +* @brief Tests that PlayerCar moves the correct amount in the left direction on each frame without obstacles. +*/ +TEST(Car, updateMovesPlayerLeftInEmptyMaze) +{ + Maze testMaze; + vector > walls; + testMaze.generateMaze(walls,5,5); + + PlayerCar player(2,2,Maze::LEFT); + + list smokescreens; + player.update(testMaze, smokescreens); + + double expectX = 2-player.speed(); + double expectY = 2; + EXPECT_FLOAT_EQ(expectX, player.x()); + EXPECT_FLOAT_EQ(expectY, player.y()); +} + +/** +* @brief Tests that PlayerCar moves the correct amount in the right direction on each frame without obstacles. +*/ +TEST(Car, updateMovesPlayerRightInEmptyMaze) +{ + Maze testMaze; + vector > walls; + testMaze.generateMaze(walls,5,5); + + PlayerCar player(2,2,Maze::RIGHT); + + list smokescreens; + player.update(testMaze, smokescreens); + + double expectX = 2+player.speed(); + double expectY = 2; + EXPECT_FLOAT_EQ(expectX, player.x()); + EXPECT_FLOAT_EQ(expectY, player.y()); +} + +/** +* @brief Tests that PlayerCar does not move up if the path is blocked. +*/ +TEST(Car, carNotMovedWhenPathUpBlocked) +{ + Maze testMaze; + vector > walls; + walls.push_back(make_pair(3,3)); + testMaze.generateMaze(walls,5,5); + + PlayerCar player(3,4,Maze::UP); + + list smokescreens; + player.update(testMaze, smokescreens); + + double expectX = 3; + double expectY = 4; + EXPECT_FLOAT_EQ(expectX, player.x()); + EXPECT_FLOAT_EQ(expectY, player.y()); +} +/** +* @brief Tests that PlayerCar does not move down if the path is blocked. +*/ +TEST(Car, carNotMovedWhenPathDownBlocked) +{ + Maze testMaze; + vector > walls; + walls.push_back(make_pair(3,3)); + testMaze.generateMaze(walls,5,5); + + PlayerCar player(3,2,Maze::DOWN); + + list smokescreens; + player.update(testMaze, smokescreens); + + double expectX = 3; + double expectY = 2; + EXPECT_FLOAT_EQ(expectX, player.x()); + EXPECT_FLOAT_EQ(expectY, player.y()); +} +/** +* @brief Tests that PlayerCar does not move left if the path is blocked. +*/ +TEST(Car, carNotMovedWhenPathLeftBlocked) +{ + Maze testMaze; + vector > walls; + walls.push_back(make_pair(3,3)); + testMaze.generateMaze(walls,5,5); + + PlayerCar player(4,3,Maze::LEFT); + + list smokescreens; + player.update(testMaze, smokescreens); + + double expectX = 4; + double expectY = 3; + EXPECT_FLOAT_EQ(expectX, player.x()); + EXPECT_FLOAT_EQ(expectY, player.y()); +} +/** +* @brief Tests that PlayerCar does not move right if the path is blocked. +*/ +TEST(Car, carNotMovedWhenPathRightBlocked) +{ + Maze testMaze; + vector > walls; + walls.push_back(make_pair(3,3)); + testMaze.generateMaze(walls,5,5); + + PlayerCar player(2,3,Maze::RIGHT); + + list smokescreens; + player.update(testMaze, smokescreens); + + double expectX = 2; + double expectY = 3; + EXPECT_FLOAT_EQ(expectX, player.x()); + EXPECT_FLOAT_EQ(expectY, player.y()); +} + +/** +* @brief Tests that PlayerCar does not move up if currently on the top row of the maze. +*/ +TEST(Car, carDoesNotMoveUpOutMaze) +{ + Maze testMaze; + vector > walls; + testMaze.generateMaze(walls,5,5); + + PlayerCar player(3,0,Maze::UP); + + list smokescreens; + player.update(testMaze, smokescreens); + + double expectX = 3; + double expectY = 0; + EXPECT_FLOAT_EQ(expectX, player.x()); + EXPECT_FLOAT_EQ(expectY, player.y()); +} +/** +* @brief Tests that PlayerCar does not move down if currently on the bottom row of the maze. +*/ +TEST(Car, carDoesNotMoveDownOutMaze) +{ + Maze testMaze; + vector > walls; + testMaze.generateMaze(walls,5,5); + + PlayerCar player(3,5,Maze::DOWN); + + list smokescreens; + player.update(testMaze, smokescreens); + + double expectX = 3; + double expectY = 5; + EXPECT_FLOAT_EQ(expectX, player.x()); + EXPECT_FLOAT_EQ(expectY, player.y()); +} +/** +* @brief Tests that PlayerCar does not move left if currently on the first column of the maze. +*/ +TEST(Car, carDoesNotMoveLeftOutMaze) +{ + Maze testMaze; + vector > walls; + testMaze.generateMaze(walls,5,5); + + PlayerCar player(0,3,Maze::LEFT); + + list smokescreens; + player.update(testMaze, smokescreens); + + double expectX = 0; + double expectY = 3; + EXPECT_FLOAT_EQ(expectX, player.x()); + EXPECT_FLOAT_EQ(expectY, player.y()); +} +/** +* @brief Tests that PlayerCar does not move right if currently on the last column of the maze. +*/ +TEST(Car, carDoesNotMoveRightOutMaze) +{ + Maze testMaze; + vector > walls; + testMaze.generateMaze(walls,5,5); + + PlayerCar player(5,3,Maze::RIGHT); + + list smokescreens; + player.update(testMaze, smokescreens); + + double expectX = 5; + double expectY = 3; + EXPECT_FLOAT_EQ(expectX, player.x()); + EXPECT_FLOAT_EQ(expectY, player.y()); +} + +/** +* @brief Tests that counting of the number of checkpoints happens correctly. +*/ +TEST(Checkpoint, countIncrementsAndDecrements) +{ + vector checkpoints; + EXPECT_EQ(0, Checkpoint::checkpointCount()); + + for (int i=0; i<1000; ++i) + { + checkpoints.push_back(Checkpoint(i,i)); + } + EXPECT_EQ(1000, Checkpoint::checkpointCount()); + + //brackets to limit scope + { + Checkpoint extraCheck1(0,0); + Checkpoint extraCheck2 = extraCheck1; + EXPECT_EQ(1002, Checkpoint::checkpointCount()); + extraCheck2 = extraCheck1; + EXPECT_EQ(1002, Checkpoint::checkpointCount()); + } + EXPECT_EQ(1000, Checkpoint::checkpointCount()); + + while(!checkpoints.empty()) + { + checkpoints.pop_back(); + } + EXPECT_EQ(0, Checkpoint::checkpointCount()); +} + +/** +* @brief Tests that when a player and enemy overlap, both are marked for destruction. +*/ +TEST(CollisionDetector, playerAndEnemyBothDestroyed) +{ + list players; + list enemies; + list checkpoints; + list rocks; + list smokescreens; + CollisionDetector detector; + + players.push_back(PlayerCar(5,5)); + players.push_back(PlayerCar(5,6)); + players.push_back(PlayerCar(6,5)); + players.push_back(PlayerCar(7,7.5)); + + enemies.push_back(EnemyCar(5,6)); + enemies.push_back(EnemyCar(4,5)); + enemies.push_back(EnemyCar(6,6)); + enemies.push_back(EnemyCar(6.5,8)); + + detector.checkCollisions(players, enemies, checkpoints, rocks, smokescreens); + + list::const_iterator playerIter = players.begin(); + EXPECT_FALSE(playerIter->destroyed()); + ++playerIter; + EXPECT_TRUE(playerIter->destroyed()); + ++playerIter; + EXPECT_FALSE(playerIter->destroyed()); + ++playerIter; + EXPECT_TRUE(playerIter->destroyed()); + + list::const_iterator enemyIter = enemies.begin(); + EXPECT_TRUE(enemyIter->destroyed()); + ++enemyIter; + EXPECT_FALSE(enemyIter->destroyed()); + ++enemyIter; + EXPECT_FALSE(enemyIter->destroyed()); + ++enemyIter; + EXPECT_TRUE(enemyIter->destroyed()); +} + +/** +* @brief Tests that when a player and rock overlap, only the player is marked for destruction. +*/ +TEST(CollisionDetector, playerDestroyedByRock) +{ + list players; + list enemies; + list checkpoints; + list rocks; + list smokescreens; + CollisionDetector detector; + + players.push_back(PlayerCar(5,5)); + players.push_back(PlayerCar(5,6)); + players.push_back(PlayerCar(6,5)); + players.push_back(PlayerCar(7,7.5)); + + rocks.push_back(Rock(5,6)); + rocks.push_back(Rock(4,5)); + rocks.push_back(Rock(6,6)); + rocks.push_back(Rock(6.5,8)); + + detector.checkCollisions(players, enemies, checkpoints, rocks, smokescreens); + + list::const_iterator playerIter = players.begin(); + EXPECT_FALSE(playerIter->destroyed()); + ++playerIter; + EXPECT_TRUE(playerIter->destroyed()); + ++playerIter; + EXPECT_FALSE(playerIter->destroyed()); + ++playerIter; + EXPECT_TRUE(playerIter->destroyed()); + + list::const_iterator rockIter = rocks.begin(); + EXPECT_FALSE(rockIter->destroyed()); + ++rockIter; + EXPECT_FALSE(rockIter->destroyed()); + ++rockIter; + EXPECT_FALSE(rockIter->destroyed()); + ++rockIter; + EXPECT_FALSE(rockIter->destroyed()); +} + +/** +* @brief Tests that when a player and checkpoint overlap, only the checkpoint is marked for destruction. +*/ +TEST(CollisionDetector, checkpointDestroyedByPlayer) +{ + list players; + list enemies; + list checkpoints; + list rocks; + list smokescreens; + CollisionDetector detector; + + players.push_back(PlayerCar(5,5)); + players.push_back(PlayerCar(5,6)); + players.push_back(PlayerCar(6,5)); + players.push_back(PlayerCar(7,7.5)); + + checkpoints.push_back(Checkpoint(5,6)); + checkpoints.push_back(Checkpoint(4,5)); + checkpoints.push_back(Checkpoint(6,6)); + checkpoints.push_back(Checkpoint(6.5,8)); + + detector.checkCollisions(players, enemies, checkpoints, rocks, smokescreens); + + list::const_iterator playerIter = players.begin(); + EXPECT_FALSE(playerIter->destroyed()); + ++playerIter; + EXPECT_FALSE(playerIter->destroyed()); + ++playerIter; + EXPECT_FALSE(playerIter->destroyed()); + ++playerIter; + EXPECT_FALSE(playerIter->destroyed()); + + list::const_iterator checkpointIter = checkpoints.begin(); + EXPECT_TRUE(checkpointIter->destroyed()); + ++checkpointIter; + EXPECT_FALSE(checkpointIter->destroyed()); + ++checkpointIter; + EXPECT_FALSE(checkpointIter->destroyed()); + ++checkpointIter; + EXPECT_TRUE(checkpointIter->destroyed()); +} + +/** +* @brief Tests that when an enemy and smokescreen overlap, the enemy's speed becomes zero. +*/ +TEST(CollisionDetector, enemyStoppedBySmokescreen) +{ + list players; + list enemies; + list checkpoints; + list rocks; + list smokescreens; + CollisionDetector detector; + + enemies.push_back(EnemyCar(5,5)); + enemies.push_back(EnemyCar(5,6)); + enemies.push_back(EnemyCar(6,5)); + enemies.push_back(EnemyCar(7,7.5)); + + smokescreens.push_back(Smokescreen(5,6)); + smokescreens.push_back(Smokescreen(4,5)); + smokescreens.push_back(Smokescreen(6,6)); + smokescreens.push_back(Smokescreen(6.5,8)); + + detector.checkCollisions(players, enemies, checkpoints, rocks, smokescreens); + + list::const_iterator enemyIter = enemies.begin(); + EXPECT_GT(enemyIter->speed(),0); + ++enemyIter; + EXPECT_FLOAT_EQ(0, enemyIter->speed()); + ++enemyIter; + EXPECT_GT(enemyIter->speed(),0); + ++enemyIter; + EXPECT_FLOAT_EQ(0, enemyIter->speed()); +} + +/** +* @brief Tests that a DestroyedObjectPopup is destroyed 30 frames after it is created. +*/ +TEST(DestroyedObjectPopup, destroyedAfterSetTime) +{ + DestroyedObjectPopup testPopup(3,2,BitmapStore::CRASHED_CAR); + + int i=0; + while (!testPopup.destroyed()) + { + testPopup.update(); + ++i; + } + + EXPECT_EQ(30,i); +} + +/** +* @brief Tests that the maze treats the undefined area outside its bounds as being walls. +*/ +TEST(Maze, queryOutsideBoundsReturnsSolid) +{ + Maze testMaze; + vector > walls; + testMaze.generateMaze(walls,5,5); + + EXPECT_FALSE(testMaze.getSolid(5,5)); + EXPECT_FALSE(testMaze.getSolid(0,0)); + EXPECT_TRUE(testMaze.getSolid(6,5)); + EXPECT_TRUE(testMaze.getSolid(5,6)); + EXPECT_TRUE(testMaze.getSolid(-1,3)); + EXPECT_TRUE(testMaze.getSolid(3,-1)); +} + +/** +* @brief Tests that a maze can be generated without extending the width and height for objects. +*/ +TEST(Maze, generationWithWallsWithoutObjectMax) +{ + Maze testMaze; + vector > walls; + walls.push_back(make_pair(1,2)); + walls.push_back(make_pair(4,3)); + testMaze.generateMaze(walls); + + //test random empty blocks + EXPECT_FALSE(testMaze.getSolid(2,1)); + EXPECT_FALSE(testMaze.getSolid(0,0)); + + //test wall blocks + EXPECT_TRUE(testMaze.getSolid(1,2)); + EXPECT_TRUE(testMaze.getSolid(4,3)); + + //test bounds are being set right + EXPECT_TRUE(testMaze.getSolid(4,4)); + EXPECT_TRUE(testMaze.getSolid(5,3)); +} + +/** +* @brief Tests that a maze can be generated with extending the width and height for objects. +*/ +TEST(Maze, generationWithWallsWithObjectMax) +{ + Maze testMaze; + vector > walls; + walls.push_back(make_pair(1,2)); + walls.push_back(make_pair(4,3)); + testMaze.generateMaze(walls,5,6); + + //test random empty blocks + EXPECT_FALSE(testMaze.getSolid(2,1)); + EXPECT_FALSE(testMaze.getSolid(0,0)); + + //test wall blocks + EXPECT_TRUE(testMaze.getSolid(1,2)); + EXPECT_TRUE(testMaze.getSolid(4,3)); + + //test bounds are being set right + EXPECT_FALSE(testMaze.getSolid(4,4)); + EXPECT_FALSE(testMaze.getSolid(5,6)); + EXPECT_TRUE(testMaze.getSolid(6,6)); + EXPECT_TRUE(testMaze.getSolid(5,7)); +} + +/** +* @brief Tests that the formula for a straight line distance works as expected. +*/ +TEST(MazeMath, distanceGivesExpectedResult) +{ + //right angle triange with sides of length 3, 4, and 5 + double x1 = 1; + double y1 = 1; + double x2 = 4; + double y2 = 5; + + double expectedResult = 5; + + EXPECT_DOUBLE_EQ(expectedResult, MazeMath::distance(x1,y1,x2,y2)); +} + +/** +* @brief Tests that rounding off function works as expected. +*/ +TEST(MazeMath, roundGivesExpectedResult) +{ + //right angle triange with sides of length 3, 4, and 5 + double roundUp = 5.5; + double roundDown = 5.49; + + double expectUp = 6; + double expectDown = 5; + + EXPECT_DOUBLE_EQ(expectUp, MazeMath::round(roundUp)); + EXPECT_DOUBLE_EQ(expectDown, MazeMath::round(roundDown)); +} + +/** +* @brief Tests that a PlayerCar can be created and destroyed repeatedly without incident. +* +* This tests that the PlayerCar can handle its own dependencies on Allegro, even when +* copy constructors are used (through vector). +*/ +TEST(PlayerCar, creationAndDestructionCanHappen) +{ + EXPECT_NO_THROW({ + vector players; + for (int i=0; i<10; ++i) + { + players.push_back(PlayerCar(i,i)); + } + while(!players.empty()) + { + players.pop_back(); + } + }); + + EXPECT_FALSE(al_is_system_installed()); +} + +/** +* @brief Tests that the player's petrol runs out, and that the player's speed is halved after that. +* +* Also tests that the speed is recovered if petrol is increased again. +*/ +TEST(PlayerCar, playerSpeedAffectedByPetrol) +{ + Maze testMaze; + vector > walls; + testMaze.generateMaze(walls,5,5); + + PlayerCar player(5,3,Maze::RIGHT); + + list smokescreens; + for (int i=0; i<1429; ++i) + { + player.update(testMaze, smokescreens); + } + + EXPECT_FLOAT_EQ(0, player.petrol()); + EXPECT_FLOAT_EQ(0.05, player.speed()); + + player.gotCheckpoint(); + EXPECT_GT(player.petrol(), 0); + EXPECT_FLOAT_EQ(0.1, player.speed()); +} + +/** +* @brief Tests that a Smokescreen is destroyed 60 frames after it is created. +*/ +TEST(Smokescreen, destroyedAfterSetTime) +{ + Smokescreen testSmokescreen(3,2); + + int i=0; + while (!testSmokescreen.destroyed()) + { + testSmokescreen.update(); + ++i; + } + + EXPECT_EQ(60,i); +} -- cgit v1.2.3