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/dataTests.cpp | 184 ++++++++++++ tests/logicTests.cpp | 685 ++++++++++++++++++++++++++++++++++++++++++++ tests/presentationTests.cpp | 248 ++++++++++++++++ 3 files changed, 1117 insertions(+) create mode 100644 tests/dataTests.cpp create mode 100644 tests/logicTests.cpp create mode 100644 tests/presentationTests.cpp (limited to 'tests') diff --git a/tests/dataTests.cpp b/tests/dataTests.cpp new file mode 100644 index 0000000..6bf553e --- /dev/null +++ b/tests/dataTests.cpp @@ -0,0 +1,184 @@ +/** @file dataTests.cpp +* @brief Unit tests for the data layer of a Rally-X game. +* +* The functionality of each class in the data layer was tested. +* +* The Config class was tested in terms of its ability to read an existing config file, +* as well as its ability to create a new file with default values, or fill missing parameters +* with default values. +* +* The LevelReader class was tested in the normal case, where a correct level file is given, +* and in the case where a file that does not exist is given. +* +* @author Justin Wernick +* @author David Schneider +*/ + +#include +#include +using namespace std; + +#include + +#include "../source/data/Config.h" +#include "../source/data/LevelReader.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" + +/** +* @brief Tests that a normal complete config file can be read. +*/ +TEST(Config, readsSettingsCorrectly) +{ + string testFilepath = "testConfig.txt"; + ofstream testFile(testFilepath.c_str()); + + testFile << "screen_width=123" << endl; + testFile << "screen_height=345" << endl; + testFile << "fullscreen=true" << endl; + + testFile.close(); + + Config testConfig(testFilepath); + + EXPECT_EQ((unsigned)(123), testConfig.screenWidth()); + EXPECT_EQ((unsigned)(345), testConfig.screenHeight()); + EXPECT_TRUE(testConfig.fullscreen()); + + remove(testFilepath.c_str()); +} + +/** +* @brief Tests that, if the config file does not exist, it is created with default values. +*/ +TEST(Config, createsFileIfNeeded) +{ + string testFilepath = "testConfig.txt"; + Config testConfig(testFilepath); + + ifstream testFile(testFilepath.c_str()); + EXPECT_TRUE(testFile); + testFile.close(); + + //test for default values + EXPECT_EQ((unsigned)(800), testConfig.screenWidth()); + EXPECT_EQ((unsigned)(600), testConfig.screenHeight()); + EXPECT_FALSE(testConfig.fullscreen()); + + remove(testFilepath.c_str()); +} + +/** +* @brief Tests that an incomplete config file is loaded, with defaults for the missing values. +*/ +TEST(Config, incompleteFileFilled) +{ + string testFilepath = "testConfig.txt"; + ofstream testFile(testFilepath.c_str()); + + testFile << "screen_height=345" << endl; + + testFile.close(); + + Config testConfig(testFilepath); + + EXPECT_EQ((unsigned)(800), testConfig.screenWidth()); + EXPECT_EQ((unsigned)(345), testConfig.screenHeight()); + EXPECT_FALSE(testConfig.fullscreen()); + + remove(testFilepath.c_str()); +} + +/** +* @brief Tests that a level can be loaded correctly from a file. +*/ +TEST(LevelReader, readsFileInfoObjects) +{ + string testFilepath = "testMaze.lvl"; + ofstream testFile(testFilepath.c_str()); + + testFile << " P X " << endl; + testFile << " " << endl; + testFile << " @ " << endl; + testFile << " X" << endl; + testFile << "## " << endl; + testFile << " P " << endl; + testFile << " O " << endl; + + testFile.close(); + + LevelReader testReader(testFilepath); + Maze maze; + list players; + list enemies; + list checkpoints; + list rocks; + testReader.readLevel(maze, players, enemies, checkpoints, rocks); + + list expectedPlayers; + list expectedEnemies; + list expectedCheckpoints; + list expectedRocks; + + expectedPlayers.push_back(PlayerCar(3,2)); + expectedEnemies.push_back(EnemyCar(3,0)); + expectedEnemies.push_back(EnemyCar(5,3)); + expectedCheckpoints.push_back(Checkpoint(2,0)); + expectedCheckpoints.push_back(Checkpoint(3,5)); + expectedRocks.push_back(Rock(2,6)); + + //eqality operator was not implemented for the GameObject class or its subclasses + //because it would not be meaningful. Two objects with the same position, type, and facing + //are still two different objects. + //iterators were used because the list type does not have an 'at' function. + list::const_iterator playIter = players.begin(); + EXPECT_FLOAT_EQ(playIter->x(), 3); + EXPECT_FLOAT_EQ(playIter->y(), 2); + ++playIter; + EXPECT_EQ(playIter, players.end()); + + list::const_iterator enemyIter = enemies.begin(); + EXPECT_FLOAT_EQ(enemyIter->x(), 3); + EXPECT_FLOAT_EQ(enemyIter->y(), 0); + ++enemyIter; + EXPECT_FLOAT_EQ(enemyIter->x(), 5); + EXPECT_FLOAT_EQ(enemyIter->y(), 3); + ++enemyIter; + EXPECT_EQ(enemyIter, enemies.end()); + + list::const_iterator checkIter = checkpoints.begin(); + EXPECT_FLOAT_EQ(checkIter->x(), 1); + EXPECT_FLOAT_EQ(checkIter->y(), 0); + ++checkIter; + EXPECT_FLOAT_EQ(checkIter->x(), 3); + EXPECT_FLOAT_EQ(checkIter->y(), 5); + ++checkIter; + EXPECT_EQ(checkIter, checkpoints.end()); + + list::const_iterator rockIter = rocks.begin(); + EXPECT_FLOAT_EQ(rockIter->x(), 2); + EXPECT_FLOAT_EQ(rockIter->y(), 6); + ++rockIter; + EXPECT_EQ(rockIter, rocks.end()); + + remove(testFilepath.c_str()); +} + +/** +* @brief Tests that an exception is throws if the selected file does not exist. +*/ +TEST(LevelReader, throwsExceptionOnBadFilename) +{ + string testFilepath = "testLevel.lvl"; + LevelReader testReader(testFilepath); + Maze maze; + list players; + list enemies; + list checkpoints; + list rocks; + EXPECT_ANY_THROW(testReader.readLevel(maze, players, enemies, checkpoints, rocks)); +} 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); +} diff --git a/tests/presentationTests.cpp b/tests/presentationTests.cpp new file mode 100644 index 0000000..d07ac99 --- /dev/null +++ b/tests/presentationTests.cpp @@ -0,0 +1,248 @@ +/** @file presentationTests.cpp +* @brief Unit tests for the presentation layer of a Rally-X game. +* +* The BitmapStore class is tested in its ability to return a bitmap for +* each image. The appearance of the bitmaps need to be tested manually +* by running the game and inspecting the various objects. +* +* The ColourStore class is tested in its ability to return a colour for +* each image. The appearance of the colours need to be tested manually +* by running the game and inspecting the various objects. +* +* The GamePanel and InfoPanel classes depend on their visual appearance in +* the game. Therefore, they should be tested manually. The technical part +* of their functionality, creating the back and front buffers and changing +* between them, is handled by their superclass, ScreenPanel. +* +* The KeyboardHandler depends on user inputs. It should be tested manually +* by testing that the player's direction can be controlled, and that smokescreens +* can be created. +* +* The Screen class's creation with various resolutions and fullscreen settings is +* tested. An exception should be thrown if the fullscreen resolution is not supported +* by the current hardware, but not otherwise. The visual appearance and creation of +* ScreenPanels should be tested manually by running the game. +* +* The ScreenPanel class is tested by creating a false back and front display buffer. The buffer +* that is being drawn to is tested by sampling a pixel in the middle of the buffer. +* +* @author Justin Wernick +* @author David Schneider +*/ + +#include +#include +using namespace std; + +#include + +#include "../source/presentation/BitmapStore.h" +#include "../source/presentation/ColourStore.h" +#include "../source/presentation/GamePanel.h" +#include "../source/presentation/Screen.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/DestroyedObjectPopup.h" + +/** +* @brief Tests that all images can be requested without failure occuring. +*/ +TEST(BitmapStore, returnsBitmapForAllImages) +{ + BitmapStore testStore(50); + + ALLEGRO_BITMAP* testBitmap = NULL; + + EXPECT_NO_FATAL_FAILURE(testBitmap = testStore.getBitmap(BitmapStore::PLAYER)); + EXPECT_NO_FATAL_FAILURE(testBitmap = testStore.getBitmap(BitmapStore::ENEMY)); + EXPECT_NO_FATAL_FAILURE(testBitmap = testStore.getBitmap(BitmapStore::CHECKPOINT)); + EXPECT_NO_FATAL_FAILURE(testBitmap = testStore.getBitmap(BitmapStore::ROCK)); + EXPECT_NO_FATAL_FAILURE(testBitmap = testStore.getBitmap(BitmapStore::MAZE_WALL)); + EXPECT_NO_FATAL_FAILURE(testBitmap = testStore.getBitmap(BitmapStore::MAZE_FLOOR)); + EXPECT_NO_FATAL_FAILURE(testBitmap = testStore.getBitmap(BitmapStore::SMOKE)); + EXPECT_NO_FATAL_FAILURE(testBitmap = testStore.getBitmap(BitmapStore::CRASHED_CAR)); + EXPECT_NO_FATAL_FAILURE(testBitmap = testStore.getBitmap(BitmapStore::CLAIMED_CHECKPOINT)); +} + +/** +* @brief Tests that all colours can be requested without failure occuring. +*/ +TEST(ColourStore, returnsColourForAllImages) +{ + ColourStore testStore; + + ALLEGRO_COLOR testColour; + + EXPECT_NO_FATAL_FAILURE(testColour = testStore.getColour(BitmapStore::PLAYER)); + EXPECT_NO_FATAL_FAILURE(testColour = testStore.getColour(BitmapStore::ENEMY)); + EXPECT_NO_FATAL_FAILURE(testColour = testStore.getColour(BitmapStore::CHECKPOINT)); + EXPECT_NO_FATAL_FAILURE(testColour = testStore.getColour(BitmapStore::ROCK)); + EXPECT_NO_FATAL_FAILURE(testColour = testStore.getColour(BitmapStore::MAZE_WALL)); + EXPECT_NO_FATAL_FAILURE(testColour = testStore.getColour(BitmapStore::MAZE_FLOOR)); + EXPECT_NO_FATAL_FAILURE(testColour = testStore.getColour(BitmapStore::SMOKE)); + EXPECT_NO_FATAL_FAILURE(testColour = testStore.getColour(BitmapStore::CRASHED_CAR)); + EXPECT_NO_FATAL_FAILURE(testColour = testStore.getColour(BitmapStore::CLAIMED_CHECKPOINT)); +} + +/** +* Tests that an exception is thrown if fullscreen mode is requested on an unsupported monitor resolution. +*/ +TEST(Screen, exceptionOnBadResolution) +{ + //resolution should be unsupported on most displays + int badWidth = 1000; + int badHeight = 5; + + EXPECT_ANY_THROW(Screen(badWidth, badHeight, true)); +} +/** +* Tests that an exception is not thrown if windowed mode is requested on an unsupported monitor resolution. +*/ +TEST(Screen, noExceptionOnWindowed) +{ + int badWidth = 1000; + int badHeight = 5; + + EXPECT_NO_THROW(Screen(badWidth, badHeight, false)); + +} +/** +* Tests that an exception is not thrown if fullscreen mode is requested on a supported monitor resolution. +*/ +TEST(Screen, noExceptionOnGoodResolution) +{ + //resolution should be supported on most monitors + int goodWidth = 800; + int goodHeight = 600; + + EXPECT_NO_THROW(Screen(goodWidth, goodHeight, true)); +} + +/** +* @brief Tests that when a ScreenPanel draws to the back buffer provided. +*/ +TEST(ScreenPanel, drawingToCurrentBackBuffer) +{ + al_init(); + + ALLEGRO_BITMAP* testBitmapBack = al_create_bitmap(500,500); + ALLEGRO_BITMAP* testBitmapFront = al_create_bitmap(500,500); + + ALLEGRO_COLOR blankColour = al_map_rgb(0,0,0); + al_set_target_bitmap(testBitmapBack); + al_clear_to_color(blankColour); + al_set_target_bitmap(testBitmapFront); + al_clear_to_color(blankColour); + + GamePanel testPanel(testBitmapBack, testBitmapFront, 0, 0, 500, 500); + Maze testMaze; + + vector > wallsFull; + + for (int x=0; x<20; ++x) + { + for (int y=0; y<20; ++y) + { + wallsFull.push_back(make_pair(x,y)); + } + } + testMaze.generateMaze(wallsFull,20,20); + + list players; + list enemies; + list checkpoints; + list rocks; + list smokescreens; + list popups; + + testPanel.draw(testMaze, players, enemies, checkpoints, rocks, smokescreens, popups); + + BitmapStore bitmapStore(50); + ALLEGRO_BITMAP* wall = bitmapStore.getBitmap(BitmapStore::MAZE_WALL); + ALLEGRO_COLOR mazeColour = al_get_pixel(wall, 25, 25); + + ALLEGRO_COLOR backSample = al_get_pixel(testBitmapBack, 250, 250); + ALLEGRO_COLOR frontSample = al_get_pixel(testBitmapFront, 250, 250); + + EXPECT_FLOAT_EQ(mazeColour.r, backSample.r); + EXPECT_FLOAT_EQ(mazeColour.g, backSample.g); + EXPECT_FLOAT_EQ(mazeColour.b, backSample.b); + EXPECT_FLOAT_EQ(mazeColour.a, backSample.a); + + EXPECT_FLOAT_EQ(blankColour.r, frontSample.r); + EXPECT_FLOAT_EQ(blankColour.g, frontSample.g); + EXPECT_FLOAT_EQ(blankColour.b, frontSample.b); + EXPECT_FLOAT_EQ(blankColour.a, frontSample.a); + + al_destroy_bitmap(testBitmapBack); + al_destroy_bitmap(testBitmapFront); +} + +/** +* @brief Tests that when a ScreenPanel draws to the front buffer provided after a flip has been called. +*/ +TEST(ScreenPanel, drawingToCurrentBackBufferAfterFlip) +{ + al_init(); + + al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP); //removes dependency on display existing + + ALLEGRO_BITMAP* testBitmapBack = al_create_bitmap(500,500); + ALLEGRO_BITMAP* testBitmapFront = al_create_bitmap(500,500); + + ALLEGRO_COLOR blankColour = al_map_rgb(0,0,0); + al_set_target_bitmap(testBitmapBack); + al_clear_to_color(blankColour); + al_set_target_bitmap(testBitmapFront); + al_clear_to_color(blankColour); + + GamePanel testPanel(testBitmapBack, testBitmapFront, 0, 0, 500, 500); + Maze testMaze; + + vector > wallsFull; + + for (int x=0; x<20; ++x) + { + for (int y=0; y<20; ++y) + { + wallsFull.push_back(make_pair(x,y)); + } + } + testMaze.generateMaze(wallsFull,20,20); + + list players; + list enemies; + list checkpoints; + list rocks; + list smokescreens; + list popups; + + testPanel.flip(); + + testPanel.draw(testMaze, players, enemies, checkpoints, rocks, smokescreens, popups); + + BitmapStore bitmapStore(50); + ALLEGRO_BITMAP* wall = bitmapStore.getBitmap(BitmapStore::MAZE_WALL); + ALLEGRO_COLOR mazeColour = al_get_pixel(wall, 25, 25); + + ALLEGRO_COLOR backSample = al_get_pixel(testBitmapBack, 250, 250); + ALLEGRO_COLOR frontSample = al_get_pixel(testBitmapFront, 250, 250); + + EXPECT_FLOAT_EQ(mazeColour.r, frontSample.r); + EXPECT_FLOAT_EQ(mazeColour.g, frontSample.g); + EXPECT_FLOAT_EQ(mazeColour.b, frontSample.b); + EXPECT_FLOAT_EQ(mazeColour.a, frontSample.a); + + EXPECT_FLOAT_EQ(blankColour.r, backSample.r); + EXPECT_FLOAT_EQ(blankColour.g, backSample.g); + EXPECT_FLOAT_EQ(blankColour.b, backSample.b); + EXPECT_FLOAT_EQ(blankColour.a, backSample.a); + + al_destroy_bitmap(testBitmapBack); + al_destroy_bitmap(testBitmapFront); +} -- cgit v1.2.3