From 98ba22e7064db57316dfff1ae127feb3dceeb73e Mon Sep 17 00:00:00 2001 From: Justin Worthe Date: Thu, 31 Jul 2014 13:58:22 +0200 Subject: Initial commit --- source/logic/AllegroWrappers.cpp | 116 +++++++++++++++++++++ source/logic/AllegroWrappers.h | 112 ++++++++++++++++++++ source/logic/Car.cpp | 75 ++++++++++++++ source/logic/Car.h | 57 +++++++++++ source/logic/Checkpoint.cpp | 30 ++++++ source/logic/Checkpoint.h | 53 ++++++++++ source/logic/CollisionDetector.cpp | 64 ++++++++++++ source/logic/CollisionDetector.h | 75 ++++++++++++++ source/logic/DestroyedObjectPopup.cpp | 6 ++ source/logic/DestroyedObjectPopup.h | 33 ++++++ source/logic/EnemyCar.cpp | 104 +++++++++++++++++++ source/logic/EnemyCar.h | 97 ++++++++++++++++++ source/logic/Game.cpp | 185 ++++++++++++++++++++++++++++++++++ source/logic/Game.h | 135 +++++++++++++++++++++++++ source/logic/GameObject.cpp | 27 +++++ source/logic/GameObject.h | 82 +++++++++++++++ source/logic/LimitedTimeObject.cpp | 16 +++ source/logic/LimitedTimeObject.h | 45 +++++++++ source/logic/Maze.cpp | 77 ++++++++++++++ source/logic/Maze.h | 100 ++++++++++++++++++ source/logic/MazeMath.cpp | 18 ++++ source/logic/MazeMath.h | 59 +++++++++++ source/logic/PlayerCar.cpp | 87 ++++++++++++++++ source/logic/PlayerCar.h | 99 ++++++++++++++++++ source/logic/Rock.cpp | 6 ++ source/logic/Rock.h | 29 ++++++ source/logic/Smokescreen.cpp | 6 ++ source/logic/Smokescreen.h | 31 ++++++ 28 files changed, 1824 insertions(+) create mode 100644 source/logic/AllegroWrappers.cpp create mode 100644 source/logic/AllegroWrappers.h create mode 100644 source/logic/Car.cpp create mode 100644 source/logic/Car.h create mode 100644 source/logic/Checkpoint.cpp create mode 100644 source/logic/Checkpoint.h create mode 100644 source/logic/CollisionDetector.cpp create mode 100644 source/logic/CollisionDetector.h create mode 100644 source/logic/DestroyedObjectPopup.cpp create mode 100644 source/logic/DestroyedObjectPopup.h create mode 100644 source/logic/EnemyCar.cpp create mode 100644 source/logic/EnemyCar.h create mode 100644 source/logic/Game.cpp create mode 100644 source/logic/Game.h create mode 100644 source/logic/GameObject.cpp create mode 100644 source/logic/GameObject.h create mode 100644 source/logic/LimitedTimeObject.cpp create mode 100644 source/logic/LimitedTimeObject.h create mode 100644 source/logic/Maze.cpp create mode 100644 source/logic/Maze.h create mode 100644 source/logic/MazeMath.cpp create mode 100644 source/logic/MazeMath.h create mode 100644 source/logic/PlayerCar.cpp create mode 100644 source/logic/PlayerCar.h create mode 100644 source/logic/Rock.cpp create mode 100644 source/logic/Rock.h create mode 100644 source/logic/Smokescreen.cpp create mode 100644 source/logic/Smokescreen.h (limited to 'source/logic') diff --git a/source/logic/AllegroWrappers.cpp b/source/logic/AllegroWrappers.cpp new file mode 100644 index 0000000..4094cfe --- /dev/null +++ b/source/logic/AllegroWrappers.cpp @@ -0,0 +1,116 @@ +#include "AllegroWrappers.h" + +int AllegroInit::_initCount = 0; + +AllegroInit::AllegroInit() +{ + if (_initCount==0) + { + if (!al_init()) + { + throw InstallFailure(); + } + } + ++_initCount; +} + +AllegroInit::AllegroInit(const AllegroInit& ref) +{ + if (_initCount==0) + { + if (!al_init()) + { + throw InstallFailure(); + } + } + ++_initCount; +} + +AllegroInit::~AllegroInit() +{ + --_initCount; + if (_initCount==0) + { + al_uninstall_system(); + } +} + + +int AllegroKeyboardInit::_initCount = 0; + +AllegroKeyboardInit::AllegroKeyboardInit() +{ + if (_initCount==0) + { + if (!al_install_keyboard()) + { + throw InstallFailure(); + } + } + ++_initCount; +} + +AllegroKeyboardInit::AllegroKeyboardInit(const AllegroKeyboardInit& ref) +{ + if (_initCount==0) + { + if (!al_install_keyboard()) + { + throw InstallFailure(); + } + } + ++_initCount; +} + +AllegroKeyboardInit::~AllegroKeyboardInit() +{ + --_initCount; + if (_initCount==0) al_uninstall_keyboard(); +} + +int AllegroDrawingInit::_initCount = 0; + +AllegroDrawingInit::AllegroDrawingInit() +{ + if (_initCount==0) + { + if (!al_init_primitives_addon()) + { + throw InstallFailure(); + } + al_init_font_addon(); + if (!al_init_ttf_addon()) + { + throw InstallFailure(); + } + } + ++_initCount; +} + +AllegroDrawingInit::AllegroDrawingInit(const AllegroDrawingInit& ref) +{ + if (_initCount==0) + { + if (!al_init_primitives_addon()) + { + throw InstallFailure(); + } + al_init_font_addon(); + if (!al_init_ttf_addon()) + { + throw InstallFailure(); + } + } + ++_initCount; +} + +AllegroDrawingInit::~AllegroDrawingInit() +{ + --_initCount; + if (_initCount==0) + { + al_shutdown_ttf_addon(); + al_shutdown_font_addon(); + al_shutdown_primitives_addon(); + } +} diff --git a/source/logic/AllegroWrappers.h b/source/logic/AllegroWrappers.h new file mode 100644 index 0000000..851f219 --- /dev/null +++ b/source/logic/AllegroWrappers.h @@ -0,0 +1,112 @@ +#ifndef ALLEGRO_WRAPPERS_H +#define ALLEGRO_WRAPPERS_H + +#include +#include +#include +#include + +/** +* @brief Exception to be thrown if any component of Allegro fails to install at runtime. +* +* @author Justin Wernick +* @author David Schneider +*/ +class InstallFailure {}; + +/** +* @brief Class ensures that Allegro is initialized and uninstalled when appropriate. +* +* Any classes that use Allegro should include this class as a data member. +* +* @author Justin Wernick +* @author David Schneider +*/ +class AllegroInit +{ + public: + /** + * @brief Constructor calls al_init() if it is the first instance. + */ + AllegroInit(); + /** + * @brief Copy constructor, implemented to be included in instance count. + */ + AllegroInit(const AllegroInit& ref); + /** + * @brief Destructor calls al_uninstall_system() if it is the last instant. + */ + + //assignment operator provided by compiler. _initCount does not need incrementing on assignment, + //because assignment does not make a new instance, just changes one. + + ~AllegroInit(); + private: + static int _initCount; ///< Count of the current number of initialised AllegroInit objects. +}; + +/** +* @brief Class ensures that Allegro's keyboard is installed and uninstalled when appropriate. +* +* Any classes that use the keyboard for input should include this class as a data member. +* This class includes AllegroInit, so both of them do not need to be included. +* +* @author Justin Wernick +* @author David Schneider +*/ +class AllegroKeyboardInit +{ + public: + /** + * @brief Constructor calls al_install_keyboard() if it is the first instance. + */ + AllegroKeyboardInit(); + /** + * @brief Copy constructor, implemented to be included in instance count. + */ + AllegroKeyboardInit(const AllegroKeyboardInit& ref); + /** + * @brief Destructor calls al_uninstall_keyboard() if it is the last instant. + */ + ~AllegroKeyboardInit(); + private: + static int _initCount; ///< Count of the current number of initialised AllegroKeyboardInit objects. + AllegroInit _allegro; ///< Depends on Allegro being initialised. + + //assignment operator provided by compiler. _initCount does not need incrementing on assignment, + //because assignment does not make a new instance, just changes one. +}; + +/** +* @brief Class ensures that Allegro's primitive and text drawing is installed and uninstalled when appropriate. +* +* Any classes that draw primitives should include this class as a data member. +* This class includes AllegroInit, so both of them do not need to be included. +* +* @author Justin Wernick +* @author David Schneider +*/ +class AllegroDrawingInit +{ + public: + /** + * @brief Constructor calls al_init_primitives_addon(), al_init_font_addon(), and al_init_ttf_addon() if it is the first instance. + */ + AllegroDrawingInit(); + /** + * @brief Copy constructor, implemented to be included in instance count. + */ + AllegroDrawingInit(const AllegroDrawingInit& ref); + /** + * @brief Destructor calls al_shutdown_primitives_addon(), al_shutdown_font_addon(), and al_shutdown_ttf_addon() if it is the last instant. + */ + ~AllegroDrawingInit(); + private: + static int _initCount; + AllegroInit _allegro; + + //assignment operator provided by compiler. _initCount does not need incrementing on assignment, + //because assignment does not make a new instance, just changes one. +}; + +#endif diff --git a/source/logic/Car.cpp b/source/logic/Car.cpp new file mode 100644 index 0000000..3e531a3 --- /dev/null +++ b/source/logic/Car.cpp @@ -0,0 +1,75 @@ +#include "Car.h" + +Car::Car(double x, double y, BitmapStore::Image image, Maze::Direction facing) + :GameObject(x,y,image,facing), + _speed(_baseSpeed) +{ +} + +double Car::speed() const +{ + return _speed; +} + +void Car::move(const Maze& maze) +{ + double targetX = 0; + double targetY = 0; + + int checkX = 0; + int checkY = 0; + + switch(_facing) + { + case Maze::UP: + targetX = MazeMath::round(_x); + targetY = _y - _speed; + checkX = floor(targetX); + checkY = floor(targetY); + break; + case Maze::DOWN: + targetX = MazeMath::round(_x); + targetY = _y + _speed; + checkX = floor(targetX); + checkY = ceil(targetY); + break; + case Maze::LEFT: + targetX = _x - _speed; + targetY = MazeMath::round(_y); + checkX = floor(targetX); + checkY = floor(targetY); + break; + case Maze::RIGHT: + targetX = _x + _speed; + targetY = MazeMath::round(_y); + checkX = ceil(targetX); + checkY = floor(targetY); + break; + } + + if (!maze.getSolid(checkX, checkY)) + { + //can move that way + _x = targetX; + _y = targetY; + } + else + { + //can not move to targetX and targetY, but move to the edge of current block + switch(_facing) + { + case Maze::UP: + _y = floor(_y); + break; + case Maze::DOWN: + _y = ceil(_y); + break; + case Maze::LEFT: + _x = floor(_x); + break; + case Maze::RIGHT: + _x = ceil(_x); + break; + } + } +} diff --git a/source/logic/Car.h b/source/logic/Car.h new file mode 100644 index 0000000..4b8d16c --- /dev/null +++ b/source/logic/Car.h @@ -0,0 +1,57 @@ +#ifndef CAR_H +#define CAR_H + +#include +using namespace std; + +#include "../presentation/BitmapStore.h" +#include "../logic/GameObject.h" +#include "../logic/MazeMath.h" + +/** +* @brief GameObject that moves through the maze and changes direction. +* +* Should not be instantiated directly, but rather instantiated through one of the subclasses, +* PlayerCar or EnemyCar. +* +* @author Justin Wernick +* @author David Schneider +*/ +class Car : public GameObject +{ + public: + /** + * @brief Creates a Car at the given position, with the given image, facing in the given direction. + * + * @param [in] x x coordinate of initial position. + * @param [in] y y coordinate of initial position. + * @param [in] image Bitmap to be drawn on the screen to represent the car. + * @param [in] facing Direction in which the Car is initially facing. + */ + Car(double x, double y, BitmapStore::Image image, Maze::Direction facing); + + //assignment and copy constructors have been left with the compiler generated versions + + /** + * @brief Function to access the current speed of the car. + * + * @return The current speed of the car, in pixels per update. + */ + double speed() const; + + protected: + /** + * @brief Moves the car by its current speed in the direction of its facing. + * + * Only moves along the x or y axis, and snaps to the grid in the other direction. + * Does not allow movement through solid parts of the maze. + * + * @param [in] maze The maze in which the Car is moving, confining its movements. + */ + void move(const Maze& maze); + + double _speed; ///< The current speed that the Car is moving at. + static const double _baseSpeed = 0.1; ///< The speed that a Car moves at in normal conditions. +}; + +#endif // CAR_H diff --git a/source/logic/Checkpoint.cpp b/source/logic/Checkpoint.cpp new file mode 100644 index 0000000..8a6406c --- /dev/null +++ b/source/logic/Checkpoint.cpp @@ -0,0 +1,30 @@ +#include "Checkpoint.h" + +int Checkpoint::_checkpointCount = 0; + +Checkpoint::Checkpoint(double x, double y) + :GameObject(x,y,BitmapStore::CHECKPOINT) +{ + ++_checkpointCount; +} + +Checkpoint::Checkpoint(const Checkpoint& ref) + :GameObject(ref._x,ref._y,ref._image) +{ + ++_checkpointCount; +} + +Checkpoint::~Checkpoint() +{ + --_checkpointCount; +} + +int Checkpoint::checkpointCount() +{ + return _checkpointCount; +} + +void Checkpoint::collect() +{ + _destroyed = true; +} diff --git a/source/logic/Checkpoint.h b/source/logic/Checkpoint.h new file mode 100644 index 0000000..f6dfeb3 --- /dev/null +++ b/source/logic/Checkpoint.h @@ -0,0 +1,53 @@ +#ifndef CHECKPOINT_H +#define CHECKPOINT_H + +#include "../logic/GameObject.h" +#include "../presentation/BitmapStore.h" + +/** +* @brief GameObject that the player needs to pick up by driving over. +* +* The level is complete when all checkpoints have been collected. +* +* @author Justin Wernick +* @author David Schneider +*/ +class Checkpoint: public GameObject +{ + public: + /** + * @brief Function for accessing the number of checkpoints that currently exist. + * + * @return The number of checkpoints that currently exist. + */ + static int checkpointCount(); + + /** + * @brief Creates a checkpoint at the given coordinates. + * + * @param [in] x x coordinate of Checkpoint's position. + * @param [in] y y coordinate of Checkpoint's position. + */ + Checkpoint(double x, double y); + /** + * @brief Copy constuctor, overwritten to include in the counting of Checkpoints. + */ + Checkpoint(const Checkpoint& ref); + + //assignment operator has been left with the compiler generated version. + + /** + * @brief Destructor, decreases the number of Checkpoints in existence. + */ + ~Checkpoint(); + + /** + * @brief Function to be called when a PlayerCar collects the Checkpoint. + */ + void collect(); + + private: + static int _checkpointCount; ///< Count of the number of Checkpoints currently in existence. +}; + +#endif // CHECKPOINT_H diff --git a/source/logic/CollisionDetector.cpp b/source/logic/CollisionDetector.cpp new file mode 100644 index 0000000..85ebaa8 --- /dev/null +++ b/source/logic/CollisionDetector.cpp @@ -0,0 +1,64 @@ +#include "CollisionDetector.h" + +void CollisionDetector::checkCollisions(list& players, list& enemies, list& checkpoints, list& rocks, list& smokescreens) +{ + for (list::iterator playIter = players.begin(); playIter!=players.end(); ++playIter) + { + for (list::iterator enemyIter = enemies.begin(); enemyIter!=enemies.end(); ++enemyIter) + { + if ((abs(playIter->x() - enemyIter->x())<1)&&(abs(playIter->y() - enemyIter->y())<1)) + { + collision(*playIter, *enemyIter); + } + } + + for (list::iterator checkIter = checkpoints.begin(); checkIter!=checkpoints.end(); ++checkIter) + { + if ((abs(playIter->x() - checkIter->x())<1)&&(abs(playIter->y() - checkIter->y())<1)) + { + collision(*playIter, *checkIter); + } + } + + for (list::iterator rockIter = rocks.begin(); rockIter!=rocks.end(); ++rockIter) + { + if ((abs(playIter->x() - rockIter->x())<1)&&(abs(playIter->y() - rockIter->y())<1)) + { + collision(*playIter, *rockIter); + } + } + } + + for (list::iterator enemyIter = enemies.begin(); enemyIter!=enemies.end(); ++enemyIter) + { + for (list::iterator smokeIter = smokescreens.begin(); smokeIter!=smokescreens.end(); ++smokeIter) + { + if ((abs(enemyIter->x() - smokeIter->x())<1)&&(abs(enemyIter->y() - smokeIter->y())<1)) + { + collision(*enemyIter, *smokeIter); + } + } + } +} + +void CollisionDetector::collision(PlayerCar& player, Checkpoint& checkpoint) +{ + player.gotCheckpoint(); + checkpoint.collect(); +} + +void CollisionDetector::collision(PlayerCar& player, Rock& rock) +{ + player.crash(); +} + +void CollisionDetector::collision(PlayerCar& player, EnemyCar& enemy) +{ + player.crash(); + enemy.crash(); +} + +void CollisionDetector::collision(EnemyCar& enemy, Smokescreen& smokescreen) +{ + enemy.blind(); +} diff --git a/source/logic/CollisionDetector.h b/source/logic/CollisionDetector.h new file mode 100644 index 0000000..294852d --- /dev/null +++ b/source/logic/CollisionDetector.h @@ -0,0 +1,75 @@ +#ifndef COLLISIONDETECTOR_H +#define COLLISIONDETECTOR_H + +#include +using namespace std; + +#include "../logic/PlayerCar.h" +#include "../logic/EnemyCar.h" +#include "../logic/Checkpoint.h" +#include "../logic/Rock.h" +#include "../logic/Smokescreen.h" + +/** +* @brief Object for handling collisions between GameObjects. +* +* Collisions between all relevant objects are checked and the appropriate methods on the GameObjects +* are called when a collision occurs. +* +* @author Justin Wernick +* @author David Schneider +*/ +class CollisionDetector +{ + public: + /** + * @brief Checks for collisions between all relevant pairs of objects, and calls the relevant collision function if one is found. + * + * A collision occurs if the distance between two object's x values is less than 1, + * and the distance between their y values is also less than 1. + * + * @param [in,out] players List of PlayerCars, that can collide with EnemieCars, Checkpoints, or Rocks. + * @param [in,out] enemies List of EnemyCars, that can collide with PlayerCars, or Smokescreens. + * @param [in,out] checkpoints List of Checkpoints, that can collide with PlayerCars. + * @param [in,out] rocks List of Rocks, that can collide with PlayerCars. + * @param [in,out] smokescreens List of Smokescreens, that can collide with EnemyCars. + */ + void checkCollisions(list& players, list& enemies, list& checkpoints, list& rocks, list& smokescreens); + + //assignment and copy constructors have been left with the compiler generated versions + + private: + /** + * @brief Collision between a PlayerCar and a Checkpoint. + * + * @param [in,out] player PlayerCar involved in the collision. + * @param [in,out] checkpoint Checkpoint involved in the collision. + */ + void collision(PlayerCar& player, Checkpoint& checkpoint); + + /** + * @brief Collision between a PlayerCar and an EnemyCar. + * + * @param [in,out] player PlayerCar involved in the collision. + * @param [in,out] enemy EnemyCar involved in the collision. + */ + void collision(PlayerCar& player, EnemyCar& enemy); + + /** + * @brief Collision between a PlayerCar and a Rock. + * + * @param [in,out] player PlayerCar involved in the collision. + * @param [in,out] rock Rock involved in the collision. + */ + void collision(PlayerCar& player, Rock& rock); + + /** + * @brief Collision between an EnemyCar and a Smokescreen. + * + * @param [in,out] enemy EnemyCar involved in the collision. + * @param [in,out] smokescreen Smokescreen involved in the collision. + */ + void collision(EnemyCar& enemy, Smokescreen& smokescreen); +}; + +#endif // COLLISIONDETECTOR_H diff --git a/source/logic/DestroyedObjectPopup.cpp b/source/logic/DestroyedObjectPopup.cpp new file mode 100644 index 0000000..42ce08d --- /dev/null +++ b/source/logic/DestroyedObjectPopup.cpp @@ -0,0 +1,6 @@ +#include "DestroyedObjectPopup.h" + +DestroyedObjectPopup::DestroyedObjectPopup(double x, double y, BitmapStore::Image image) + :LimitedTimeObject(x, y, image, POPUP_TIME) +{ +} diff --git a/source/logic/DestroyedObjectPopup.h b/source/logic/DestroyedObjectPopup.h new file mode 100644 index 0000000..3e694f5 --- /dev/null +++ b/source/logic/DestroyedObjectPopup.h @@ -0,0 +1,33 @@ +#ifndef DESTROYEDOBJECTPOPUP_H +#define DESTROYEDOBJECTPOPUP_H + +#include "../logic/LimitedTimeObject.h" +#include "../presentation/BitmapStore.h" + +/** +* @brief Object that appears on the screen for a short time when another object has been destroyed. +* +* Used to give extra visual feedback when a checkpoint has been collected or a Car crashes. +* +* @author Justin Wernick +* @author David Schneider +*/ +class DestroyedObjectPopup : public LimitedTimeObject +{ + public: + /** + * @brief Creates the popup at the given location, with the given image. + * + * @param [in] x The x coordinate of the object's position. + * @param [in] y The y coordinate of the object's position. + * @param [in] image The bitmap to be shown until the popup disappears. + */ + DestroyedObjectPopup(double x, double y, BitmapStore::Image image); + + //assignment and copy constructors have been left with the compiler generated versions + + private: + static const int POPUP_TIME = 30; ///< The number of frames that the DestroyedObjectPopup exists before it is destroyed. 1 second at FPS=30. +}; + +#endif // DESTROYEDOBJECTPOPUP_H diff --git a/source/logic/EnemyCar.cpp b/source/logic/EnemyCar.cpp new file mode 100644 index 0000000..ce455a6 --- /dev/null +++ b/source/logic/EnemyCar.cpp @@ -0,0 +1,104 @@ +#include "EnemyCar.h" + +EnemyCar::EnemyCar(double x, double y) + :Car(x,y,BitmapStore::ENEMY,Maze::UP), + _state(CHASING), + _targetX(x), + _targetY(y) +{ +} + +void EnemyCar::update(const Maze& maze, const list& players, const list& rocks) +{ + if (!players.empty()) checkFacing(maze, players.front().x(), players.front().y(), rocks); + + if (_state!=BLINDED) + { + move(maze); + } + else + { + _state = CHASING; + _speed = _baseSpeed; + } +} + +void EnemyCar::checkFacing(const Maze& maze, double chasingX, double chasingY, const list& rocks) +{ + if (abs(_x - _targetX)>_speed || abs(_y - _targetY)>_speed) return; + + map > adjacentBlocks; + pair evaluatingTarget; + + adjacentBlocks[Maze::LEFT] = make_pair(MazeMath::round(_x-1), MazeMath::round(_y)); + adjacentBlocks[Maze::RIGHT] = make_pair(MazeMath::round(_x+1), MazeMath::round(_y)); + adjacentBlocks[Maze::UP] = make_pair(MazeMath::round(_x), MazeMath::round(_y-1)); + adjacentBlocks[Maze::DOWN] = make_pair(MazeMath::round(_x), MazeMath::round(_y+1)); + + //remove adjacent blocks that would result in crashing into a rock or a wall + for (map >::iterator iter=adjacentBlocks.begin(); iter!=adjacentBlocks.end(); ) + { + if (rockAtLocation(iter->second.first, iter->second.second, rocks) || maze.getSolid(static_cast(iter->second.first),static_cast(iter->second.second))) + { + adjacentBlocks.erase(iter); + iter = adjacentBlocks.begin(); + } + else + { + ++iter; + } + } + + if (adjacentBlocks.empty()) + { + _speed = 0; + return; + } + else + { + _speed = _baseSpeed; + } + + map >::iterator reverseFacing = adjacentBlocks.find(Maze::backwards(_facing)); + if ((reverseFacing != adjacentBlocks.end()) && (adjacentBlocks.size()>1)) + { + adjacentBlocks.erase(reverseFacing); + } + + map >::const_iterator closestAdjacent = adjacentBlocks.begin(); + double closestDistance = MazeMath::distance(closestAdjacent->second.first, closestAdjacent->second.second, chasingX, chasingY); + + for (map >::const_iterator iter = ++adjacentBlocks.begin(); iter!=adjacentBlocks.end(); ++iter) + { + double newDistance = MazeMath::distance(iter->second.first, iter->second.second, chasingX, chasingY); + if (newDistance < closestDistance) + { + closestDistance = newDistance; + closestAdjacent = iter; + } + } + + _targetX = closestAdjacent->second.first; + _targetY = closestAdjacent->second.second; + _facing = closestAdjacent->first; +} + +bool EnemyCar::rockAtLocation(double x, double y, const list& rocks) +{ + for (list::const_iterator iter = rocks.begin(); iter!=rocks.end(); ++iter) + { + if (abs(x - iter->x())<1 && abs(y - iter->y())<1) return true; + } + return false; +} + +void EnemyCar::crash() +{ + _destroyed = true; +} + +void EnemyCar::blind() +{ + _state = BLINDED; + _speed = 0; +} diff --git a/source/logic/EnemyCar.h b/source/logic/EnemyCar.h new file mode 100644 index 0000000..b56c9d3 --- /dev/null +++ b/source/logic/EnemyCar.h @@ -0,0 +1,97 @@ +#ifndef ENEMYCAR_H +#define ENEMYCAR_H + +#include + +#include "../presentation/BitmapStore.h" + +#include "../logic/Car.h" +#include "../logic/PlayerCar.h" +#include "../logic/Rock.h" +#include "../logic/MazeMath.h" + +/** +* @brief GameObject that chases the player around the maze. +* +* Attempts to collide with the player, causing the player to lose. +* +* @author Justin Wernick +* @author David Schneider +*/ +class EnemyCar: public Car +{ + public: + /** + * @brief Creates an EnemyCar at the given coordinates. + * + * @param [in] x The x coordinate of the EnemyCar's initial position. + * @param [in] y The y coordinate of the EnemyCar's initial position. + */ + EnemyCar(double x, double y); + + //assignment and copy constructors have been left with the compiler generated versions + + /** + * @brief Processes one frame's worth of activity for the object, called every frame. + * + * Primarily adjusts the facing if neccesary and then moves using the inhereted move function. + * + * @param maze The maze that confines the EnemyCar's movements. + * @param players The list of PlayerCars that the EnemyCar can chase. + * @param rocks The list of Rocks that need to be avoided. + */ + void update(const Maze& maze, const list& players, const list& rocks); + + /** + * @brief Function that is called when an EnemyCar crashes into a PlayerCar. + */ + void crash(); + + /** + * @brief Function that is called when an EnemyCar drives into a Smokescreen. + */ + void blind(); + + private: + /** + * @brief States that define how the EnemyCar's AI should behave. + * + * This would need to be expanded to include more states in order to make the enemies appear smarter. + */ + enum States { + BLINDED, ///< The EnemyCar can not see, and so does not move. + CHASING ///< The EnemyCar tries to drive to the block that the player is currently on. + }; + + States _state; ///< The state that the object is currently in. + double _targetX; ///< The x coordinate that the EnemyCar is driving towards. + double _targetY; ///< The y coordinate that the EnemyCar is driving towards. + + /** + * @brief Updates the direction that the EnemyCar is facing, if neccesary. + * + * The facing is only changed once the current _targetX and _targetY are reached. After that, a facing is + * chosen that points into an empty block (no maze walls or rocks) that is closest to the chasing x and y + * using a straight line. This results in the enemy not always taking the shortest route, but it makes it + * possible to escape enemies. _targetX and _targetY are updated to one block in the new facing direction. + * The enemy may only turn around and head backwards if there is no other options, so once the enemy starts + * driving down narrow a path it will continue to the end of the path. + * + * @param [in] maze The maze that confines the EnemyCar's movements. + * @param [in] chasingX The x coordinate that the EnemyCar is ultimately trying to reach. + * @param [in] chasingY The y coordinate that the EnemyCar is ultimately trying to reach. + * @param [in] rocks The Rocks that the EnemyCar needs to avoid. + */ + void checkFacing(const Maze& maze, double chasingX, double chasingY, const list& rocks); + + /** + * @brief Iterates through a list of Rocks and determines if moving to a given position would result in a collision. + * + * @param [in] x The potential new x coordinate. + * @param [in] y The potential new y coordinate. + * @param [in] rocks The Rocks that are checked for a collision at x and y. + */ + bool rockAtLocation(double x, double y, const list& rocks); +}; + +#endif // ENEMYCAR_H diff --git a/source/logic/Game.cpp b/source/logic/Game.cpp new file mode 100644 index 0000000..57abd79 --- /dev/null +++ b/source/logic/Game.cpp @@ -0,0 +1,185 @@ +#include "Game.h" + +Game::Game() + :_config("config.txt"), + _screen(_config.screenWidth(), _config.screenHeight(), _config.fullscreen()) +{ + _timer = al_create_timer(1.0/FPS); + _timerEvents = al_create_event_queue(); + al_register_event_source(_timerEvents, al_get_timer_event_source(_timer)); +} + +Game::~Game() +{ + al_destroy_event_queue(_timerEvents); + al_destroy_timer(_timer); + +} + +void Game::start() +{ + while (!_screen.exitClicked()) + { + string filename = _screen.getLevel(); + if (!filename.empty()) + { + initLevel(filename); + runloop(); + } + } +} + +void Game::initLevel(const string& levelFile) +{ + clearLists(); + LevelReader reader(levelFile); + reader.readLevel(_maze, _players, _enemies, _checkpoints, _rocks); +} + +void Game::runloop() +{ + bool gameWon = false; + bool gameLost = false; + al_start_timer(_timer); + + while (!_screen.exitClicked()) + { + al_wait_for_event(_timerEvents, NULL); + al_flush_event_queue(_timerEvents); + + update(); + _collisionDetector.checkCollisions(_players, _enemies, _checkpoints, _rocks, _smokescreens); + cleanup(); + _screen.draw(_maze, _players, _enemies, _checkpoints, _rocks, _smokescreens, _popups); + + gameLost = _players.empty(); + gameWon = Checkpoint::checkpointCount()==0; + + if (gameLost) + { + _screen.drawLoss(); + for (int i=0; i<90; i++) + { + al_wait_for_event(_timerEvents, NULL); + al_drop_next_event(_timerEvents); + } + break; + } + else if (gameWon) + { + _screen.drawWin(); + for (int i=0; i<90; i++) + { + al_wait_for_event(_timerEvents, NULL); + al_drop_next_event(_timerEvents); + } + break; + } + } + al_stop_timer(_timer); +} + +void Game::update() +{ + for (list::iterator iter = _players.begin(); iter!=_players.end(); ++iter) + { + iter->update(_maze, _smokescreens); + } + + for (list::iterator iter = _enemies.begin(); iter!=_enemies.end(); ++iter) + { + iter->update(_maze, _players, _rocks); + } + + for (list::iterator iter = _smokescreens.begin(); iter!=_smokescreens.end(); ++iter) + { + iter->update(); + } + for (list::iterator iter = _popups.begin(); iter!=_popups.end(); ++iter) + { + iter->update(); + } +} + +void Game::cleanup() +{ + for (list::iterator iter = _players.begin(); iter!=_players.end();) + { + if (iter->destroyed()) + { + _popups.push_back(DestroyedObjectPopup(iter->x(), iter->y(), BitmapStore::CRASHED_CAR)); + iter = _players.erase(iter); + } + else + { + ++iter; + } + } + for (list::iterator iter = _enemies.begin(); iter!=_enemies.end();) + { + if (iter->destroyed()) + { + _popups.push_back(DestroyedObjectPopup(iter->x(), iter->y(), BitmapStore::CRASHED_CAR)); + iter = _enemies.erase(iter); + } + else + { + ++iter; + } + } + for (list::iterator iter = _checkpoints.begin(); iter!=_checkpoints.end();) + { + if (iter->destroyed()) + { + _popups.push_back(DestroyedObjectPopup(iter->x(), iter->y(), BitmapStore::CLAIMED_CHECKPOINT)); + iter = _checkpoints.erase(iter); + } + else + { + ++iter; + } + } + for (list::iterator iter = _rocks.begin(); iter!=_rocks.end();) + { + if (iter->destroyed()) + { + iter = _rocks.erase(iter); + } + else + { + ++iter; + } + } + for (list::iterator iter = _smokescreens.begin(); iter!=_smokescreens.end();) + { + if (iter->destroyed()) + { + iter = _smokescreens.erase(iter); + } + else + { + ++iter; + } + } + for (list::iterator iter = _popups.begin(); iter!=_popups.end();) + { + if (iter->destroyed()) + { + iter = _popups.erase(iter); + } + else + { + ++iter; + } + } +} + +void Game::clearLists() +{ + _players.clear(); + _enemies.clear(); + _checkpoints.clear(); + _rocks.clear(); + _smokescreens.clear(); + _popups.clear(); +} diff --git a/source/logic/Game.h b/source/logic/Game.h new file mode 100644 index 0000000..9adff76 --- /dev/null +++ b/source/logic/Game.h @@ -0,0 +1,135 @@ +#ifndef GAME_H +#define GAME_H + +#include +#include +using namespace std; + +#include + +#include "../presentation/Screen.h" +#include "../presentation/BitmapStore.h" + +#include "../logic/Maze.h" +#include "../logic/PlayerCar.h" +#include "../logic/EnemyCar.h" +#include "../logic/Checkpoint.h" +#include "../logic/Rock.h" +#include "../logic/Smokescreen.h" +#include "../logic/DestroyedObjectPopup.h" +#include "../logic/AllegroWrappers.h" +#include "../logic/CollisionDetector.h" + +#include "../data/LevelReader.h" +#include "../data/Config.h" + +/** +* @brief The object that controls the flow of the game, and the launch point of the game. +* +* Game contains the various components, including the screen, the maze, and all of the +* objects in the maze. The timing of the gameloop also falls under Game's control. +* Essencially, Game is the central point that everything connects to. +* +* @author Justin Wernick +* @author David Schneider +*/ +class Game +{ + public: + static const unsigned int FPS = 30; ///< Frames per second, the number of times the gameloop is run every second. + + /** + * @brief Constructor, that creates the relevant Allegro entities. + */ + Game(); + + /** + * @brief Constructor, that destroys the relevant Allegro entities. + */ + ~Game(); + + /** + * @brief Entry point for the program. This should be called from main. + */ + void start(); + + private: + /** + * @brief Unimplemented copy constructor, prevents copying of Game objects. + * + * Copying a Game is unneccesary as there should only be a single Game object. + */ + Game(const Game& ref); + /** + * @brief Unimplemented assignment operator. + * + * @see Game::Game(const Game& ref) + */ + Game& operator=(const Game& rhs); + + /** + * @brief Initialises all of the GameObject lists using a file. + * + * @param [in] levelFile The path of the file that contains the level layout. + */ + void initLevel(const string& levelFile); + + /** + * @brief Main part of the game, performs the actions in each frame FPS times per second until the game is over. + * + * Each frame runs the update methods of each of the GameObjects in the lists. The CollisionDetector + * then checks for collisions between objects. Any GameObjects that have been destroyed are then removed + * from their lists. Finally, the Screen is called to draw all of the GameObjects that still exist in + * their new positions. + * + * Before the next iteration begins, a check is done for the victory and loss conditions. The loop is + * ended if either of these are met, or if the player has quit the game. + */ + void runloop(); + + /** + * @brief Calls the update method on each of the GameObjects in the game. + */ + void update(); + /** + * @brief Removes any GameObjects that have been destroyed from their lists. + */ + void cleanup(); + + /** + * @brief Destroys all GameObjects in the game, resetting the lists for a new level to be loaded. + * + * This should always be called before a new level is loaded. + */ + void clearLists(); + + AllegroInit _allegro; ///< Handles dependencies on Allegro being installed. + + Config _config; ///< Loads configuration from file on construction, used to set resolution of screen. + Screen _screen; ///< Handles all drawing operations. + ALLEGRO_TIMER* _timer; ///< Creates FPS events per second, that are put into _timerEvents. + ALLEGRO_EVENT_QUEUE* _timerEvents; ///< Catches events from _timer, used to regulate speed of runloop. + + Maze _maze; ///< The environment that confines the movements of GameObjects, specifically Cars. + /** + * @brief Typically a single PlayerCar, controlled by the person playing the game. + * + * A list was used for _players to allow the Game object to be constructed without needing to initialise + * a meaningless PlayerCar object. This also allows the PlayerCar to be destroyed by Rocks or EnemyCars. + * An added benefit is that it adds the ease of extending the game to allow multiple players. To add + * multiplayer, the KeyboardHandler would need to be modified to allow different sets of input keys, + * and the Screen would need to be modified to keep all players visible, but the Game class would be + * able to remain largely unchanged. + */ + list _players; + + list _enemies; ///< List of all EnemyCars chasing the player. + list _checkpoints; ///< List of checkpoints that the player needs to collect. + list _rocks; ///< List of rocks that the player and EnemyCars need to avoid. + list _smokescreens; ///< List of Smokescreen objects that are currently able to delay EnemyCars. + list _popups; ///< List of purely visual DestroyedObjectPopups that need to be drawn. + + CollisionDetector _collisionDetector; ///< Object that checks for collisions each frame. +}; + +#endif // GAME_H diff --git a/source/logic/GameObject.cpp b/source/logic/GameObject.cpp new file mode 100644 index 0000000..07957e5 --- /dev/null +++ b/source/logic/GameObject.cpp @@ -0,0 +1,27 @@ +#include "GameObject.h" + +GameObject::GameObject(double x, double y, BitmapStore::Image image, Maze::Direction facing) + :_x(x), _y(y), _destroyed(false), _image(image), _facing(facing) +{ +} + +double GameObject::x() const +{ + return _x; +} +double GameObject::y() const +{ + return _y; +} +bool GameObject::destroyed() const +{ + return _destroyed; +} +BitmapStore::Image GameObject::image() const +{ + return _image; +} +Maze::Direction GameObject::facing() const +{ + return _facing; +} diff --git a/source/logic/GameObject.h b/source/logic/GameObject.h new file mode 100644 index 0000000..bd7fd0b --- /dev/null +++ b/source/logic/GameObject.h @@ -0,0 +1,82 @@ +#ifndef GAMEOBJECT_H +#define GAMEOBJECT_H + +#include "../presentation/BitmapStore.h" +#include "../logic/Maze.h" + +/** +* @brief Parent class for objects that are placed in the maze. +* +* These objects are one maze block big. The image in the bitmap store will be drawn +* on the screen every frame at the Screen class's discression, being rotated to face +* in the 'facing' direction. Coordinates are given in terms of the Maze class's coordinate +* system. For example, increasing the x coordinate of an object by 1 will move it one maze +* block to the right. The number of pixels that this corresponds to is handled by the +* Screen class. +* +* When an object is in a situation that it should be destroyed, it is marked for destruction. +* It is then the responsibility of the Game class to actually destroy it. +* +* @author Justin Wernick +* @author David Schneider +*/ +class GameObject +{ + public: + /** + * @brief Creates a GameObject with the given parameters. + * + * @param [in] x The x coordinate of the new object. + * @param [in] y The y coordinate of the new object. + * @param [in] image The image that is drawn to represent the object. + * @param [in] facing The direction that the object is facing. If the object has no direction, + * such as with Checkpoint or Rock, the default value of Maze::UP should be used. + */ + GameObject(double x, double y, BitmapStore::Image image, Maze::Direction facing=Maze::UP); + + //assignment and copy constructors have been left with the compiler generated versions + + /** + * @brief Provides access to the x coordinate of the object. + * + * @return The x coordinate of the object, in maze blocks, where 0 is the far left column of the maze. + */ + double x() const; + + /** + * @brief Provides access to the y coordinate of the object. + * + * @return The y coordinate of the object, in maze blocks, where 0 is the top row of the maze. + */ + double y() const; + + /** + * @brief Checks if an object has been marked for destruction, for example through a collision. + * + * @return True is the object has been marked for destruction, false otherwise. + */ + bool destroyed() const; + + /** + * @brief Provides access to the image that should be drawn to represent the object. + * + * @return An image, corresponding to an enumerated type that can be converted into a bitmap by the BitmapStore class. + */ + BitmapStore::Image image() const; + + /** + * @brief Provides access to the direction that the object is facing. + * + * @return A direction, corresponding to the rotation that should be done to the drawn image and, in the case of Cars, the direction that they move forward. + */ + Maze::Direction facing() const; + + protected: + double _x; ///< The x coordinate of the object's position. + double _y; ///< The y coordinate of the object's position. + bool _destroyed; ///< True if the object has been marked for destruction. + BitmapStore::Image _image; ///< The bitmap that should be drawn on the screen to represent the object. + Maze::Direction _facing; ///< The direction in which the object is facing, up, down, left, or right. +}; + +#endif // GAMEOBJECT_H diff --git a/source/logic/LimitedTimeObject.cpp b/source/logic/LimitedTimeObject.cpp new file mode 100644 index 0000000..55ff31b --- /dev/null +++ b/source/logic/LimitedTimeObject.cpp @@ -0,0 +1,16 @@ +#include "LimitedTimeObject.h" + +LimitedTimeObject::LimitedTimeObject(double x, double y, BitmapStore::Image image, int time) + :GameObject(x,y,image), + _remainingTime(time) +{ +} + +void LimitedTimeObject::update() +{ + --_remainingTime; + if (_remainingTime<=0) + { + _destroyed = true; + } +} diff --git a/source/logic/LimitedTimeObject.h b/source/logic/LimitedTimeObject.h new file mode 100644 index 0000000..242965b --- /dev/null +++ b/source/logic/LimitedTimeObject.h @@ -0,0 +1,45 @@ +#ifndef LIMITEDTIMEOBJECT_H +#define LIMITEDTIMEOBJECT_H + +#include "../logic/GameObject.h" +#include "../logic/Maze.h" +#include "../presentation/BitmapStore.h" + +/** +* @brief Parent class for GameObjects that are created, exist for a given time, and are then destroyed. +* +* @author Justin Wernick +* @author David Schneider +*/ +class LimitedTimeObject: public GameObject +{ + public: + /** + * @brief Creates a LimitedTimeObject with the given parameters. + * + * @param [in] x The x coordinate of the new object. + * @param [in] y The y coordinate of the new object. + * @param [in] image The image that is drawn to represent the object. + * @param [in] time The number of times that the update function is run before the object is destroyed. + */ + LimitedTimeObject(double x, double y, BitmapStore::Image image, int time); + + //assignment and copy constructors have been left with the compiler generated versions + + /** + * @brief Function that should be run on every iteration of the gameloop. + * + * The time remaining is decremented, and the object is marked for destruction when it reaches zero. + */ + void update(); + + private: + /** + * @brief The number of times that update still needs to be run before the object is marked for destruction. + * + * For example, if the remaining time is 1, then the object is marked on the next update. + */ + int _remainingTime; +}; + +#endif // LIMITEDTIMEOBJECT_H diff --git a/source/logic/Maze.cpp b/source/logic/Maze.cpp new file mode 100644 index 0000000..ab24035 --- /dev/null +++ b/source/logic/Maze.cpp @@ -0,0 +1,77 @@ +#include "Maze.h" + +Maze::Maze() + :_width(0), + _height(0) +{ +} + +void Maze::generateMaze(const vector >& walls, int maxObjectX, int maxObjectY) +{ + //find bounds so that rectangular vector can be generated + int maxX = maxObjectX; + int maxY = maxObjectY; + for (vector >::const_iterator iter = walls.begin(); iter!=walls.end(); ++iter) + { + if (iter->first > maxX) maxX = iter->first; + if (iter->second > maxY) maxY = iter->second; + } + _width = maxX+1; //need to convert from highest index to required size + _height = maxY+1; + + + _wallLocations.clear(); + + for (int x=0; x<_width; ++x) + { + _wallLocations.push_back(vector()); + for (int y=0; y<_height; ++y) + { + _wallLocations.back().push_back(false); + } + } + + for (vector >::const_iterator iter = walls.begin(); iter!=walls.end(); ++iter) + { + _wallLocations.at(iter->first).at(iter->second) = true; + } +} + +bool Maze::getSolid(const int& x, const int& y) const +{ + if (x<0 || y<0) return true; + if (x>=width() || y>=height()) return true; + //bounds have already been checked, can use more efficient, less safe, indexing + return _wallLocations[x][y]; +} + +int Maze::width() const +{ + return _width; +} +int Maze::height() const +{ + return _height; +} + +Maze::Direction Maze::backwards(Direction forwards) +{ + Direction backwards; + switch (forwards) + { + case LEFT: + backwards = RIGHT; + break; + case RIGHT: + backwards = LEFT; + break; + case UP: + backwards = DOWN; + break; + case DOWN: + backwards = UP; + break; + } + + return backwards; +} diff --git a/source/logic/Maze.h b/source/logic/Maze.h new file mode 100644 index 0000000..245528d --- /dev/null +++ b/source/logic/Maze.h @@ -0,0 +1,100 @@ +#ifndef MAZE_H +#define MAZE_H + +#include +#include +using namespace std; + +/** +* @brief A rectangular 2D boolean array, representing where cars can drive and where they cannot. +* +* @author Justin Wernick +* @author David Schneider +*/ +class Maze +{ + public: + /** + * @brief Defines the directions in which movement can happen in the maze. + */ + enum Direction {UP, DOWN, LEFT, RIGHT}; + + /** + * @brief Creates an empty Maze with width and height of zero. + */ + Maze(); + + //assignment and copy constructors have been left with the compiler generated versions + + /** + * @brief Generates a new Maze from the vector of wall coordinates. + * + * The size of the Maze is chosen to just fit all of the walls. If objects exist outside + * of the walls, the x of the rightmost object and the y of the bottommost object can be + * passed in to make the Maze at least reach those coordinates. + * + * @param [in] walls A vector of x,y coordinate pairs representing the locations of each wall block. + * @param [in] maxObjectX The minimum x value that the Maze must be able to index. + * @param [in] maxObjectY The minimum y value that the Maze must be able to index. + */ + void generateMaze(const vector >& walls, int maxObjectX=0, int maxObjectY=0); + + /** + * @brief Checks if a given position contains a wall or not. + * + * This function is one of the most called as it is called for each block drawing the + * Maze on the Screen, by any Car checking if it can move, and by the EnemyCar to choose a + * viable direction to face. As such, it has been optimised by passing the parameters by + * constant reference, even though they are primitives. Further, the vector class's bounds + * checking is bypassed, with bounds checking performed manually with the assumption that the + * 2D vector is rectangular, to increase performance. Neither of these changes impare readability. + * + * @param [in] x The x coordinate being queried. + * @param [in] y The y coordinate being queried. + * + * @return True if the location contains a wall. Also returns true if the coordinate is outside of the Maze. + */ + bool getSolid(const int& x, const int& y) const; + + /** + * @brief Provides access to the width of the Maze object. + * + * @return The amount of blocks in each row of the maze. + */ + int width() const; + /** + * @brief Provides access to the height of the Maze object. + * + * @return The amount of blocks in each column of the maze. + */ + int height() const; + + /** + * @brief Inverts a given direction, to give the value to face in the opposite direction. + * + * @param [in] forwards The direction to be inverted. + * + * @return The inverse of the given direction. + */ + static Direction backwards(Direction forwards); + + private: + /** + * @brief Provides an easier to read pseudonym for a 2 dimensional boolean vector. + */ + typedef vector > BoolGrid; + + /** + * @brief The 2 dimensional vector that stores the locations of walls. + * + * The outer vector is columns, indexed with the x coordinate, and the inner vectors + * are the vertical positions in the column, indexed with the y coordinate. + * This results in a vector that is acced with _wallLocations.at(x).at(y). + */ + BoolGrid _wallLocations; + + int _width; ///< The number of blocks in each row. + int _height; ///< The number of blocks in each column. +}; + +#endif // MAZE_H diff --git a/source/logic/MazeMath.cpp b/source/logic/MazeMath.cpp new file mode 100644 index 0000000..84b27dd --- /dev/null +++ b/source/logic/MazeMath.cpp @@ -0,0 +1,18 @@ +#include "MazeMath.h" + +double MazeMath::round(double value) +{ + if (static_cast(value*10)%10 < 5) + { + return floor(value); + } + else + { + return ceil(value); + } +} + +double MazeMath::distance(double x1, double y1, double x2, double y2) +{ + return sqrt(pow(x1-x2, 2) + pow(y1-y2, 2)); +} diff --git a/source/logic/MazeMath.h b/source/logic/MazeMath.h new file mode 100644 index 0000000..d5a2aa9 --- /dev/null +++ b/source/logic/MazeMath.h @@ -0,0 +1,59 @@ +#ifndef MAZEMATH_H +#define MAZEMATH_H + +#include + +/** +* @brief Class of static methods for common math functions that occur in the 2D maze setting. +* +* @author Justin Wernick +* @author David Schneider +*/ +class MazeMath +{ + public: + /** + * @brief Rounds a value to the nearest integer. + * + * Values with a decimal fraction less than 0.5 are floored, while values with + * a decimal fraction greater than or eqaul to 0.5 are ceiled. + * + * @param [in] value The number to be rounded off. + * + * @return The rounded off version of the given value. + */ + static double round(double value); + + /** + * @brief Finds the straight line distance between two points on a 2D plane. + * + * Implemented using Pythagoras' Theorem. + * + * @param [in] x1 The x coordinate of the first point. + * @param [in] y1 The y coordinate of the first point. + * @param [in] x2 The x coordinate of the second point. + * @param [in] y2 The y coordinate of the second point. + * + * @return The distance between the two given points. + */ + static double distance(double x1, double y1, double x2, double y2); + + private: + /** + * @brief Unimplemented constructor. + * + * being a grouping of static functions, construction and destruction of MazeMath + * objects is unneccesary. + */ + MazeMath(); + /** + * @brief Unimplemented copy constructor. + */ + MazeMath(const MazeMath& ref); + /** + * @brief Unimplemented assignment operator. + */ + MazeMath& operator=(const MazeMath& rhs); +}; + +#endif // MAZEMATH_H diff --git a/source/logic/PlayerCar.cpp b/source/logic/PlayerCar.cpp new file mode 100644 index 0000000..5da4ba0 --- /dev/null +++ b/source/logic/PlayerCar.cpp @@ -0,0 +1,87 @@ +#include "PlayerCar.h" + +PlayerCar::PlayerCar(double x, double y, Maze::Direction facing) + :Car(x,y,BitmapStore::PLAYER, facing), + _input(_facing), + _petrol(1) +{ +} + +void PlayerCar::update(const Maze& maze, list& currentSmoke) +{ + _petrol -= PETROL_USE_RATE; + if (_petrol<0) + { + _speed = _baseSpeed/2; + _petrol = 0; + } + + _facing = _input.getFacing(); + move(maze); + + if (_input.getSmokescreen()) + { + makeSmoke(currentSmoke); + } +} + +void PlayerCar::makeSmoke(list& currentSmoke) +{ + if (_petrol < PETROL_USE_SMOKESCREEN) return; + + double targetX = 0; + double targetY = 0; + + switch(_facing) + { + case Maze::UP: + targetX = round(_x); + targetY = round(_y+1); + break; + case Maze::DOWN: + targetX = round(_x); + targetY = round(_y-1); + break; + case Maze::LEFT: + targetX = round(_x+1); + targetY = round(_y); + break; + case Maze::RIGHT: + targetX = round(_x-1); + targetY = round(_y); + break; + } + + bool overlap = false; + + for (list::const_iterator iter = currentSmoke.begin(); iter!=currentSmoke.end(); ++iter) + { + if ((abs(iter->x() - targetX)<1)&&(abs(iter->y() - targetY)<1)) + { + overlap = true; + break; + } + } + + if (!overlap) + { + currentSmoke.push_back(Smokescreen(targetX, targetY)); + _petrol -= PETROL_USE_SMOKESCREEN; + } +} + +void PlayerCar::crash() +{ + _destroyed = true; +} + +void PlayerCar::gotCheckpoint() +{ + _petrol+=PETROL_FROM_CHECKPOINT; + _speed = _baseSpeed; +} + +double PlayerCar::petrol() const +{ + return _petrol; +} diff --git a/source/logic/PlayerCar.h b/source/logic/PlayerCar.h new file mode 100644 index 0000000..8e9338d --- /dev/null +++ b/source/logic/PlayerCar.h @@ -0,0 +1,99 @@ +#ifndef PLAYERCAR_H +#define PLAYERCAR_H + +#include +#include +using namespace std; + +#include "../presentation/KeyboardHandler.h" +#include "../presentation/BitmapStore.h" + +#include "../logic/Car.h" +#include "../logic/Maze.h" +#include "../logic/Smokescreen.h" + +/** +* @brief A GameObject that is controlled by the player. +* +* Contains a KeyboardHandler to accept user input. +* +* @author Justin Wernick +* @author David Schneider +*/ +class PlayerCar: public Car +{ + public: + /** + * @brief Creates a PlayerCar at the given location facing in the given direction. + * + * In the current form of the level files, there is no way to distinguish the direction + * that the player is facing, so the default of UP is used. However, the ability to + * pass in a facing is useful in the unit tests. + * + * @param [in] x The x coordinate of the object's initial position. + * @param [in] y The y coordinate of the object's initial position. + * @param [in] facing The direction that the object is initially facing. + */ + PlayerCar(double x, double y, Maze::Direction facing=Maze::UP); + + //assignment and copy constructors have been left with the compiler generated versions + + /** + * @brief Processes one frame's worth of activity for the object, called every frame. + * + * The Keyboard handler is called for the user's input. Based on this, the direction + * can be changed, and a Smokescreen can be created and added to the list of already + * existing Smokescreens. The PlayerCar is then moved. + * Petrol is decreased by PETROL_USE_RATE on every update. + * + * @param [in] maze The Maze that confines the PlayerCar's movement. + * @param [in,out] currentSmoke The list of Smokescreens being drawn, that the new Smokescreens will be added to the back of. + */ + void update(const Maze& maze, list& currentSmoke); + + /** + * @brief Creates a Smokescreen one block behind the player if the action is viable. + * + * The action is viable if the object has more than PETROL_USE_SMOKESCREEN petrol. Further, + * the position must not overlap with existing Smokescreens. This allows the player to hold down + * the Smokescreen button without creating a new Smokescreen every frame. + * Creating a Smokescreen decreases the petrol by PETROL_USE_SMOKESCREEN. + * + * @param [in,out] currentSmoke The list of Smokescreens being drawn, that the new Smokescreens will be added to the back of. + */ + void makeSmoke(list& currentSmoke); + + /** + * @brief Function that is called when the PlayerCar collides with a Checkpoint. + * + * Increases the amount of petrol by PETROL_FROM_CHECKPOINT. + */ + void gotCheckpoint(); + + /** + * @brief Function that is called when the PlayerCar collides with an EnemyCar. + */ + void crash(); + + /** + * @brief Function to allow access to the amount of petrol that the PlayerCar still has. + */ + double petrol() const; + + private: + KeyboardHandler _input; ///< Object that handles all interaction with the player. + + /** + * @brief The amount of petrol that the PlayerCar still has. + * + * Measured as a fraction, with 1 being a full tank and 0 being empty. When the petrol reaches + * 0, it is kept at zero and the PlayerCar's speed is halved. + */ + double _petrol; + + static const double PETROL_USE_RATE = 0.0007; ///< The amount of petrol used every frame. + static const double PETROL_USE_SMOKESCREEN = 0.05; ///< The amount of petrol used to create a Smokescreen. + static const double PETROL_FROM_CHECKPOINT = 0.2; ///< The amount of petrol gained from collecting a Checkpoint. +}; + +#endif // PLAYERCAR_H diff --git a/source/logic/Rock.cpp b/source/logic/Rock.cpp new file mode 100644 index 0000000..bb1053a --- /dev/null +++ b/source/logic/Rock.cpp @@ -0,0 +1,6 @@ +#include "Rock.h" + +Rock::Rock(double x, double y) + :GameObject(x,y,BitmapStore::ROCK) +{ +} diff --git a/source/logic/Rock.h b/source/logic/Rock.h new file mode 100644 index 0000000..106fd18 --- /dev/null +++ b/source/logic/Rock.h @@ -0,0 +1,29 @@ +#ifndef ROCK_H +#define ROCK_H + +#include "../logic/GameObject.h" +#include "../presentation/BitmapStore.h" + +/** +* @brief A game object that acts as an obstacle to the player. +* +* Does nothing actively. +* +* @author Justin Wernick +* @author David Schneider +*/ +class Rock: public GameObject +{ + public: + /** + * @brief Creates a Rock at the given coordinates. + * + * @param [in] x The x coordinate of the Rock's position. + * @param [in] y The y coordinate of the Rock's position. + */ + Rock(double x, double y); + + //assignment and copy constructors have been left with the compiler generated versions +}; + +#endif // ROCK_H diff --git a/source/logic/Smokescreen.cpp b/source/logic/Smokescreen.cpp new file mode 100644 index 0000000..9ad4316 --- /dev/null +++ b/source/logic/Smokescreen.cpp @@ -0,0 +1,6 @@ +#include "Smokescreen.h" + +Smokescreen::Smokescreen(double x, double y) + :LimitedTimeObject(x,y,BitmapStore::SMOKE,SMOKE_TIME) +{ +} diff --git a/source/logic/Smokescreen.h b/source/logic/Smokescreen.h new file mode 100644 index 0000000..59d1871 --- /dev/null +++ b/source/logic/Smokescreen.h @@ -0,0 +1,31 @@ +#ifndef SMOKESCREEN_H +#define SMOKESCREEN_H + +#include "../logic/LimitedTimeObject.h" +#include "../presentation/BitmapStore.h" + +/** +* @brief GameObject that causes the EnemyCar to be delayed if they crash into it. +* +* After a short time, the SmokeScreen dissipates. +* +* @author Justin Wernick +* @author David Schneider +*/ +class Smokescreen : public LimitedTimeObject +{ + public: + /** + * @brief Creates a Smokescreen at the given location. + * + * @param [in] x The x coordinate of the object's position. + * @param [in] y The y coordinate of the object's position. + */ + Smokescreen(double x, double y); + + //assignment and copy constructors have been left with the compiler generated versions + private: + static const int SMOKE_TIME = 60; ///< The number of frames that the Smokescreen exists before it is destroyed. 2 second at FPS=30. +}; + +#endif // SMOKESCREEN_H -- cgit v1.2.3