diff options
Diffstat (limited to 'source')
47 files changed, 3812 insertions, 0 deletions
diff --git a/source/data/Config.cpp b/source/data/Config.cpp new file mode 100644 index 0000000..6cdda2f --- /dev/null +++ b/source/data/Config.cpp @@ -0,0 +1,105 @@ +#include "Config.h" + +const string Config::SCREEN_WIDTH_KEY("screen_width"); +const string Config::SCREEN_HEIGHT_KEY("screen_height"); +const string Config::FULLSCREEN_KEY("fullscreen"); + +const string Config::SCREEN_WIDTH_DEFAULT("800"); +const string Config::SCREEN_HEIGHT_DEFAULT("600"); +const string Config::FULLSCREEN_DEFAULT("false"); + + +Config::Config(const string& filename) + :_screenWidth(0), + _screenHeight(0) +{ + ifstream inStream(filename.c_str(), fstream::in); + + map<string, string> readValues; + map<string, string> unfoundValues; + readFile(inStream, readValues); + fillValues(readValues, unfoundValues); + + inStream.close(); + + ofstream outStream(filename.c_str(), fstream::app); + + writeUnfoundValues(outStream, unfoundValues); + + outStream.close(); +} + +unsigned int Config::screenWidth() const +{ + return _screenWidth; +} +unsigned int Config::screenHeight() const +{ + return _screenHeight; +} +bool Config::fullscreen() const +{ + return _fullscreen; +} + +void Config::readFile(ifstream& file, map<string,string>& map) +{ + if (!file.is_open()) return; + + string nextEntry; + while(!file.eof()) + { + file >> nextEntry; + + string::size_type equalsIndex = nextEntry.find("=",0); + if (equalsIndex!=string::npos) + { + string key = nextEntry.substr(0,equalsIndex); + string value = nextEntry.substr(equalsIndex+1); + + map[key] = value; + } + } +} + +void Config::fillValues(const map<string, string>& readValues, map<string, string>& unfoundValues) +{ + setScreenWidth(extractValue(readValues, unfoundValues, SCREEN_WIDTH_KEY, SCREEN_WIDTH_DEFAULT)); + setScreenHeight(extractValue(readValues, unfoundValues, SCREEN_HEIGHT_KEY, SCREEN_HEIGHT_DEFAULT)); + setFullscreen(extractValue(readValues, unfoundValues, FULLSCREEN_KEY, FULLSCREEN_DEFAULT)); +} + +string Config::extractValue(const map<string, string>& readValues, map<string, string>& unfoundValues, const string& key, const string& defaultValue) +{ + map<string, string>::const_iterator findIter = readValues.find(key); + if (findIter != readValues.end()) + { + return findIter->second; + } + else + { + unfoundValues[key] = defaultValue; + return defaultValue; + } +} + +void Config::writeUnfoundValues(ofstream& file, const map<string, string>& unfoundValues) +{ + for (map<string, string>::const_iterator iter = unfoundValues.begin(); iter!=unfoundValues.end(); ++iter) + { + file << iter->first << "=" << iter->second << endl; + } +} + +void Config::setScreenWidth(const string& screenWidthStr) +{ + _screenWidth = atoi(screenWidthStr.c_str()); +} +void Config::setScreenHeight(const string& screenHeightStr) +{ + _screenHeight = atoi(screenHeightStr.c_str()); +} +void Config::setFullscreen(const string& fullscreenStr) +{ + _fullscreen = fullscreenStr=="true"; +} diff --git a/source/data/Config.h b/source/data/Config.h new file mode 100644 index 0000000..8fdf5aa --- /dev/null +++ b/source/data/Config.h @@ -0,0 +1,136 @@ +#ifndef CONFIG_H +#define CONFIG_H + +#include <cstdlib> +#include <string> +#include <fstream> +#include <map> +using namespace std; + +/** +* @brief Object for handling user settings loaded from a file. +* +* These settings are currently all related to the screen (resolution and windowed or fullscreen). +* If custom controls are implemented in a later release, they will be loaded here. If a setting exists +* but is not found in the config file, it is set to a default value and written to the file. +* +* @author Justin Wernick +* @author David Schneider +*/ +class Config +{ + public: + /** + * @brief Constructs a Config object from a file with the given path. + * + * Opens the file and reads all of the settings in the file. The read settings are + * bound to the settings that have keys defined in the class. And settings missing from + * the file are set to default values, and the default values are written to the file. + * + * @param [in] filename The path of the file in which the settings are stored. + */ + Config(const string& filename); + + //Assignment and copy operations are handled by the compiler generated versions + + /** + * @brief Function for accessing the screen width setting in pixels. + * + * @return The desired width of the screen in pixels. + */ + unsigned int screenWidth() const; + + /** + * @brief Function for accessing the screen height setting in pixels. + * + * @return The desired height of the screen in pixels. + */ + unsigned int screenHeight() const; + + /** + * @brief Function for accessing whether the game should be displayed in fullscreen or windowed mode. + * + * @return The desired fullscreen setting. A result of true means fullscreen mode, while a result of false means windowed mode. + */ + bool fullscreen() const; + + private: + /** + * @brief Reads all of the settings defined in a file into a map. + * + * Reads each line that is in the format "key=value" in a file into a map. + * Lines that do not contain a '=' are ignored. + * + * @param [in] file An opened filestream object at the beginning of the file to be read. After the function call, file will be at the end of the file. + * @param [out] map The map that is populated with settings. + */ + void readFile(ifstream& file, map<string,string>& map); + + /** + * @brief Initialises the Config option's parameters to those in the readValues map. + * + * Parameters with a key that does not appear in readValues are initialised to a default value. + * The default value, and its key, are then added to the unfoundValues map. + * + * @param [in] readValues A map containing all of the settings read in from a config file. + * @param [out] unfoundValues A map that is populated with and settings not found in readValues. + */ + void fillValues(const map<string, string>& readValues, map<string, string>& unfoundValues); + + /** + * @brief Helper function for fillValues. Finds the value for a single key. + * + * If the given key does not appear, it is added to the unfoundValues map with the given default + * + * @param [in] readValues A map containing all of the settings read in from a config file. + * @param [out] unfoundValues A map that is populated with and settings not found in readValues. + * @param [in] key The key of the setting to be found in readValues. + * @param [in] defaultValue The value to return and add to unfoundValues if the setting is not found in readValues. + * + * @return The value corresponding to the requested key. + */ + string extractValue(const map<string, string>& readValues, map<string, string>& unfoundValues, const string& key, const string& defaultValue); + + /** + * @brief Writes settings that were not found in the file to the file with default values. + * + * @param [in] file The opened filestream to which the key=value pairs are written. + * @param [in] unfoundValues The map of key value pairs to be written to the file. + */ + void writeUnfoundValues(ofstream& file, const map<string, string>& unfoundValues); + + /** + * @brief Initializes the screen width in pixels from a given string. + * + * @param [in] screenWidthStr A string representing the desired screen width, read from a file. + */ + void setScreenWidth(const string& screenWidthStr); + + /** + * @brief Initializes the screen height in pixels from a given string. + * + * @param [in] screenHeightStr A string representing the desired screen height, read from a file. + */ + void setScreenHeight(const string& screenHeightStr); + + /** + * @brief Initializes the fullscreen setting from a given string. + * + * @param [in] fullscreenStr A string representing whether the screen should be in fullscreen mode ("true") or windowed mode (anything else). + */ + void setFullscreen(const string& fullscreenStr); + + unsigned int _screenWidth; ///< The desired width of the screen in pixels. + unsigned int _screenHeight; ///< The desired height of the screen in pixels. + bool _fullscreen; ///< The desired fullscreen or windowed setting. + + static const string SCREEN_WIDTH_KEY; ///< The key for the screen width setting, initialized to "screen_width". + static const string SCREEN_HEIGHT_KEY; ///< The key for the screen height setting, initialized to "screen_height". + static const string FULLSCREEN_KEY; ///< The key for the fullscreen setting, initialized to "fullscreen". + + static const string SCREEN_WIDTH_DEFAULT; ///< The default value for the screen width setting, initialized to 800. + static const string SCREEN_HEIGHT_DEFAULT; ///< The default value for the screen height setting, initialized to 600. + static const string FULLSCREEN_DEFAULT; ///< The default value for the fullscreen setting, initialized to false. +}; + +#endif // CONFIG_H diff --git a/source/data/LevelReader.cpp b/source/data/LevelReader.cpp new file mode 100644 index 0000000..e5364d8 --- /dev/null +++ b/source/data/LevelReader.cpp @@ -0,0 +1,54 @@ +#include "LevelReader.h" + +LevelReader::LevelReader(string filename) + :_filename(filename) +{} + +void LevelReader::readLevel(Maze& maze, list<PlayerCar>& players, list<EnemyCar>& enemies, list<Checkpoint>& checkpoints, list<Rock>& rocks) +{ + ifstream file(_filename.c_str()); + if (!file) + { + al_show_native_message_box(NULL, "Fatal error", "Fatal error", "The requested level file could not be opened.", NULL, ALLEGRO_MESSAGEBOX_ERROR); + throw FileOpenError(); + } + + int maxX = 0; + int maxY = 0; + + string line; + char element; + int y = 0; + vector <pair<int, int> > walls; + + while (!file.eof()) + { + getline (file, line); + + for (int x = 0; x < static_cast<int>(line.length()); ++x) + { + element = line.at(x); + switch (element) + { + case PLAYER_CHAR: players.push_back(PlayerCar(x,y)); + break; + case ENEMY_CHAR: enemies.push_back (EnemyCar(x,y)); + break; + case CHECKPOINT_CHAR: checkpoints.push_back(Checkpoint(x,y)); + break; + case ROCK_CHAR: rocks.push_back(Rock(x,y)); + break; + case WALL_CHAR: walls.push_back (make_pair(x,y)); + break; + } + if (maxX < x) maxX = x; + if (maxY < y) maxY = y; + } + + ++y; + } + + maze.generateMaze (walls, maxX, maxY); + + file.close(); +} diff --git a/source/data/LevelReader.h b/source/data/LevelReader.h new file mode 100644 index 0000000..293d820 --- /dev/null +++ b/source/data/LevelReader.h @@ -0,0 +1,71 @@ +#ifndef MAZEREADER_H +#define MAZEREADER_H + +#include <vector> +#include <list> +#include <string> +#include <fstream> +using namespace std; + +#include <allegro5/allegro_native_dialog.h> + +#include "../logic/Maze.h" +#include "../logic/PlayerCar.h" +#include "../logic/EnemyCar.h" +#include "../logic/Checkpoint.h" +#include "../logic/Rock.h" + +/** +* @brief An exception that is thrown if the file selected for opening does not exist. +* +* This should never be thrown, since the Allegro native file dialog is being used to select +* this file, and it only allows one to select existing files. +* +* @author Justin Wernick +* @author David Schneider +*/ +class FileOpenError{}; + +/** +* @brief Reads the game objects from a text file and calls relevant constructors. +* +* @author Justin Wernick +* @author David Schneider +*/ +class LevelReader +{ + public: + /** + * @brief Constructor that stores the path of the file containing the level to be read with the readLevel function. + * + * @param [in] filename The path of the file containing the level. + */ + LevelReader(string filename); + + //Assignment and copy operations are handled by the compiler generated versions + + /** + * @brief Function to read the chosen file into the data structures used in the game. + * + * Each character in the file is iterated through, and added to the appropriate data structure if it matches + * one of the defined constants. Lists should be cleared prior to calling this function. + * + * @param [out] maze Object representing the walls, populated with a vector of x,y pairs. + * @param [out] players List representing the player(s) in the game. + * @param [out] enemies List representing the enemies in the game. + * @param [out] checkpoints List representing the checkpoints in the game. + * @param [out] rocks List representing the rocks in the game. + */ + void readLevel(Maze& maze, list<PlayerCar>& players, list<EnemyCar>& enemies, list<Checkpoint>& checkpoints, list<Rock>& rocks); + + private: + static const char PLAYER_CHAR = '@'; ///< Character represented a PlayerCar in the level file. + static const char ENEMY_CHAR = 'X'; ///< Character represented an EnemyCar in the level file. + static const char CHECKPOINT_CHAR = 'P'; ///< Character represented a Checkpoint in the level file. + static const char ROCK_CHAR = 'O'; ///< Character represented a Rock in the level file. + static const char WALL_CHAR = '#'; ///< Character represented a solid part of the maze in the level file. + + string _filename; ///< Path of the file containing the level. +}; + +#endif // MAZEREADER_H 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 <allegro5/allegro.h> +#include <allegro5/allegro_primitives.h> +#include <allegro5/allegro_font.h> +#include <allegro5/allegro_ttf.h> + +/** +* @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 <cmath> +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<PlayerCar>& players, list<EnemyCar>& enemies, list<Checkpoint>& checkpoints, list<Rock>& rocks, list<Smokescreen>& smokescreens) +{ + for (list<PlayerCar>::iterator playIter = players.begin(); playIter!=players.end(); ++playIter) + { + for (list<EnemyCar>::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<Checkpoint>::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<Rock>::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<EnemyCar>::iterator enemyIter = enemies.begin(); enemyIter!=enemies.end(); ++enemyIter) + { + for (list<Smokescreen>::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 <list> +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<PlayerCar>& players, list<EnemyCar>& enemies, list<Checkpoint>& checkpoints, list<Rock>& rocks, list<Smokescreen>& 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<PlayerCar>& players, const list<Rock>& 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<Rock>& rocks) +{ + if (abs(_x - _targetX)>_speed || abs(_y - _targetY)>_speed) return; + + map<Maze::Direction, pair<double, double> > adjacentBlocks; + pair<double, double> 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<Maze::Direction, pair<double, double> >::iterator iter=adjacentBlocks.begin(); iter!=adjacentBlocks.end(); ) + { + if (rockAtLocation(iter->second.first, iter->second.second, rocks) || maze.getSolid(static_cast<int>(iter->second.first),static_cast<int>(iter->second.second))) + { + adjacentBlocks.erase(iter); + iter = adjacentBlocks.begin(); + } + else + { + ++iter; + } + } + + if (adjacentBlocks.empty()) + { + _speed = 0; + return; + } + else + { + _speed = _baseSpeed; + } + + map<Maze::Direction, pair<double, double> >::iterator reverseFacing = adjacentBlocks.find(Maze::backwards(_facing)); + if ((reverseFacing != adjacentBlocks.end()) && (adjacentBlocks.size()>1)) + { + adjacentBlocks.erase(reverseFacing); + } + + map<Maze::Direction, pair<double, double> >::const_iterator closestAdjacent = adjacentBlocks.begin(); + double closestDistance = MazeMath::distance(closestAdjacent->second.first, closestAdjacent->second.second, chasingX, chasingY); + + for (map<Maze::Direction, pair<double, double> >::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<Rock>& rocks) +{ + for (list<Rock>::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 <cmath> + +#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<PlayerCar>& players, const list<Rock>& 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<Rock>& 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<Rock>& 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<PlayerCar>::iterator iter = _players.begin(); iter!=_players.end(); ++iter) + { + iter->update(_maze, _smokescreens); + } + + for (list<EnemyCar>::iterator iter = _enemies.begin(); iter!=_enemies.end(); ++iter) + { + iter->update(_maze, _players, _rocks); + } + + for (list<Smokescreen>::iterator iter = _smokescreens.begin(); iter!=_smokescreens.end(); ++iter) + { + iter->update(); + } + for (list<DestroyedObjectPopup>::iterator iter = _popups.begin(); iter!=_popups.end(); ++iter) + { + iter->update(); + } +} + +void Game::cleanup() +{ + for (list<PlayerCar>::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<EnemyCar>::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<Checkpoint>::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<Rock>::iterator iter = _rocks.begin(); iter!=_rocks.end();) + { + if (iter->destroyed()) + { + iter = _rocks.erase(iter); + } + else + { + ++iter; + } + } + for (list<Smokescreen>::iterator iter = _smokescreens.begin(); iter!=_smokescreens.end();) + { + if (iter->destroyed()) + { + iter = _smokescreens.erase(iter); + } + else + { + ++iter; + } + } + for (list<DestroyedObjectPopup>::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 <list> +#include <algorithm> +using namespace std; + +#include <allegro5/allegro.h> + +#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<PlayerCar> _players; + + list<EnemyCar> _enemies; ///< List of all EnemyCars chasing the player. + list<Checkpoint> _checkpoints; ///< List of checkpoints that the player needs to collect. + list<Rock> _rocks; ///< List of rocks that the player and EnemyCars need to avoid. + list<Smokescreen> _smokescreens; ///< List of Smokescreen objects that are currently able to delay EnemyCars. + list<DestroyedObjectPopup> _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<pair<int,int> >& walls, int maxObjectX, int maxObjectY) +{ + //find bounds so that rectangular vector can be generated + int maxX = maxObjectX; + int maxY = maxObjectY; + for (vector<pair<int,int> >::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<bool>()); + for (int y=0; y<_height; ++y) + { + _wallLocations.back().push_back(false); + } + } + + for (vector<pair<int,int> >::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 <vector> +#include <utility> +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<pair<int,int> >& 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<vector<bool> > 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<int>(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 <cmath> + +/** +* @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<Smokescreen>& 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<Smokescreen>& 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<Smokescreen>::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 <cmath> +#include <list> +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<Smokescreen>& 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<Smokescreen>& 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 diff --git a/source/main.cpp b/source/main.cpp new file mode 100644 index 0000000..f990ca4 --- /dev/null +++ b/source/main.cpp @@ -0,0 +1,21 @@ +#include "logic/Game.h" + +int main() +{ + try + { + Game game; + game.start(); + } + catch (BadResolution) + { + } + catch (InstallFailure) + { + } + catch (FileOpenError) + { + } + + return 0; +} diff --git a/source/presentation/BitmapStore.cpp b/source/presentation/BitmapStore.cpp new file mode 100644 index 0000000..c289ff3 --- /dev/null +++ b/source/presentation/BitmapStore.cpp @@ -0,0 +1,238 @@ +#include "BitmapStore.h" + +BitmapStore::BitmapStore(unsigned int blockWidth) + :_blockWidth(blockWidth) +{ + _bitmapFont = al_load_font("junction 02.ttf", blockWidth/6, 0); + if (_bitmapFont == NULL) + { + al_show_native_message_box(NULL, "Fatal error", "Fatal error", "The file 'junction 02.ttf' was not found. Ensure that it is located in the working directory.", NULL, ALLEGRO_MESSAGEBOX_ERROR); + throw InstallFailure(); + } +} + +BitmapStore::~BitmapStore() +{ + for (map<Image,ALLEGRO_BITMAP*>::iterator iter = _bitmaps.begin(); + iter != _bitmaps.end(); ++iter) + { + al_destroy_bitmap(iter->second); + } + _bitmaps.clear(); + al_destroy_font(_bitmapFont); +} + +ALLEGRO_BITMAP* BitmapStore::getBitmap(Image image) +{ + map<Image,ALLEGRO_BITMAP*>::const_iterator iter = _bitmaps.find(image); + if (iter != _bitmaps.end()) + { + return iter->second; + } + else + { + ALLEGRO_BITMAP* newImage = al_create_bitmap(_blockWidth, _blockWidth); + switch (image) + { + case PLAYER: + drawPlayerCar(newImage); + break; + case ENEMY: + drawEnemyCar(newImage); + break; + case CHECKPOINT: + drawCheckpoint(newImage); + break; + case ROCK: + drawRock(newImage); + break; + case MAZE_WALL: + drawMazeWall(newImage); + break; + case MAZE_FLOOR: + drawMazeFloor(newImage); + break; + case SMOKE: + drawSmoke(newImage); + break; + case CRASHED_CAR: + drawCrashedCar(newImage); + break; + case CLAIMED_CHECKPOINT: + drawClaimedCheckpoint(newImage); + break; + } + + _bitmaps.insert(make_pair(image, newImage)); + return newImage; + } +} + +void BitmapStore::drawPlayerCar(ALLEGRO_BITMAP* canvas) +{ + ALLEGRO_BITMAP* prev_draw = al_get_target_bitmap(); + al_set_target_bitmap(canvas); + + //car body + al_draw_filled_rounded_rectangle(_blockWidth*0.2, 0, _blockWidth*0.8, _blockWidth*0.96, _blockWidth*0.1, _blockWidth*0.1, al_map_rgb(0,0,255)); + + //racing stripes + al_draw_filled_rectangle(_blockWidth*0.35, 0, _blockWidth*0.4, _blockWidth*0.3, al_map_rgb(255,255,255)); + al_draw_filled_rectangle(_blockWidth*0.6, 0, _blockWidth*0.65, _blockWidth*0.3, al_map_rgb(255,255,255)); + + //windscreen + al_draw_filled_rectangle(_blockWidth*0.3, _blockWidth*0.3, _blockWidth*0.7, _blockWidth*0.5, al_map_rgb (0,0,0)); + + //roof + al_draw_rounded_rectangle(_blockWidth*0.3, _blockWidth*0.5, _blockWidth*0.7, _blockWidth*0.9, _blockWidth*0.04, _blockWidth*0.04, al_map_rgb (25,25, 112), _blockWidth*0.04); + + //spoiler + al_draw_filled_rectangle(_blockWidth*0.2, _blockWidth*0.96, _blockWidth*0.8, _blockWidth, al_map_rgb (0,0, 225)); + al_draw_rectangle(_blockWidth*0.2, _blockWidth*0.96, _blockWidth*0.8, _blockWidth, al_map_rgb(25,25, 112),_blockWidth*0.04); + + //headlights + al_draw_filled_rectangle (_blockWidth*0.3,0,_blockWidth*0.35,_blockWidth*0.06, al_map_rgb(255,225,0)); + al_draw_filled_rectangle (_blockWidth*0.65,0,_blockWidth*0.7,_blockWidth*0.06, al_map_rgb(255,225,0)); + + //tyres + al_draw_filled_rounded_rectangle (_blockWidth*0.1,_blockWidth*0.13,_blockWidth*0.2,_blockWidth*0.37,_blockWidth*0.03,_blockWidth*0.03, al_map_rgb(131,139,131)); + al_draw_filled_rounded_rectangle (_blockWidth*0.8,_blockWidth*0.13,_blockWidth*0.9,_blockWidth*0.37,_blockWidth*0.03,_blockWidth*0.03, al_map_rgb(131,139,131)); + al_draw_filled_rounded_rectangle (_blockWidth*0.1,_blockWidth*0.63,_blockWidth*0.2,_blockWidth*0.87,_blockWidth*0.03,_blockWidth*0.03, al_map_rgb(131,139,131)); + al_draw_filled_rounded_rectangle (_blockWidth*0.8,_blockWidth*0.63,_blockWidth*0.9,_blockWidth*0.87,_blockWidth*0.03,_blockWidth*0.03, al_map_rgb(131,139,131)); + + al_set_target_bitmap(prev_draw); +} +void BitmapStore::drawEnemyCar(ALLEGRO_BITMAP* canvas) +{ + ALLEGRO_BITMAP* prev_draw = al_get_target_bitmap(); + al_set_target_bitmap(canvas); + + //car body + al_draw_filled_rounded_rectangle(_blockWidth*0.2, 0, _blockWidth*0.8, _blockWidth*0.96, _blockWidth*0.1, _blockWidth*0.1, al_map_rgb(255,0,0)); + + //racing stripes + al_draw_filled_rectangle(_blockWidth*0.35, 0, _blockWidth*0.4, _blockWidth*0.3, al_map_rgb(255,255,255)); + al_draw_filled_rectangle(_blockWidth*0.6, 0, _blockWidth*0.65, _blockWidth*0.3, al_map_rgb(255,255,255)); + + //windscreen + al_draw_filled_rectangle(_blockWidth*0.3, _blockWidth*0.3, _blockWidth*0.7, _blockWidth*0.5, al_map_rgb (0,0,0)); + + //roof + al_draw_rounded_rectangle(_blockWidth*0.3, _blockWidth*0.5, _blockWidth*0.7, _blockWidth*0.9, _blockWidth*0.04, _blockWidth*0.04, al_map_rgb (25,25, 112), _blockWidth*0.04); + + //spoiler + al_draw_filled_rectangle(_blockWidth*0.2, _blockWidth*0.96, _blockWidth*0.8, _blockWidth, al_map_rgb (0,0, 225)); + al_draw_rectangle(_blockWidth*0.2, _blockWidth*0.96, _blockWidth*0.8, _blockWidth, al_map_rgb(25,25, 112),_blockWidth*0.04); + + //headlights + al_draw_filled_rectangle (_blockWidth*0.3,0,_blockWidth*0.35,_blockWidth*0.06, al_map_rgb(255,225,0)); + al_draw_filled_rectangle (_blockWidth*0.65,0,_blockWidth*0.7,_blockWidth*0.06, al_map_rgb(255,225,0)); + + //tyres + al_draw_filled_rounded_rectangle (_blockWidth*0.1,_blockWidth*0.13,_blockWidth*0.2,_blockWidth*0.37,_blockWidth*0.03,_blockWidth*0.03, al_map_rgb(131,139,131)); + al_draw_filled_rounded_rectangle (_blockWidth*0.8,_blockWidth*0.13,_blockWidth*0.9,_blockWidth*0.37,_blockWidth*0.03,_blockWidth*0.03, al_map_rgb(131,139,131)); + al_draw_filled_rounded_rectangle (_blockWidth*0.1,_blockWidth*0.63,_blockWidth*0.2,_blockWidth*0.87,_blockWidth*0.03,_blockWidth*0.03, al_map_rgb(131,139,131)); + al_draw_filled_rounded_rectangle (_blockWidth*0.8,_blockWidth*0.63,_blockWidth*0.9,_blockWidth*0.87,_blockWidth*0.03,_blockWidth*0.03, al_map_rgb(131,139,131)); + + al_set_target_bitmap(prev_draw); +} +void BitmapStore::drawRock(ALLEGRO_BITMAP* canvas) +{ + ALLEGRO_BITMAP* prev_draw = al_get_target_bitmap(); + al_set_target_bitmap(canvas); + + ALLEGRO_COLOR colour = al_map_rgb(131,139,131); + al_draw_filled_circle(_blockWidth/2, _blockWidth/2, _blockWidth/2-1, colour); + + al_draw_filled_circle(_blockWidth/2, _blockWidth/2, _blockWidth/2-6, colour); + al_draw_filled_circle(_blockWidth/4, _blockWidth/4, _blockWidth/4-1, al_map_rgb(205,197,191)); + al_draw_filled_circle(_blockWidth/3.2, _blockWidth/4.2, _blockWidth/4-2, al_map_rgb(205,197,191)); + al_draw_filled_circle(_blockWidth/1.2, _blockWidth/2, _blockWidth/2-15, al_map_rgb(205,197,191)); + al_draw_filled_circle(_blockWidth/2, _blockWidth/2, _blockWidth/2-8, al_map_rgb(205,205,193)); + + al_set_target_bitmap(prev_draw); +} +void BitmapStore::drawCheckpoint(ALLEGRO_BITMAP* canvas) +{ + ALLEGRO_BITMAP* prev_draw = al_get_target_bitmap(); + al_set_target_bitmap(canvas); + + ALLEGRO_COLOR colour = al_map_rgb(255,255,0); + + al_draw_filled_rectangle (_blockWidth*0.44, _blockWidth*0.1, _blockWidth*0.5, _blockWidth*0.9, colour); + al_draw_filled_rounded_rectangle (_blockWidth*0.34, _blockWidth*0.9, _blockWidth*0.6, _blockWidth*0.98, _blockWidth*0.01, _blockWidth*0.01, colour); + al_draw_filled_circle (_blockWidth*0.47, _blockWidth*0.14, _blockWidth*0.1, colour); + al_draw_filled_triangle (_blockWidth*0.44, _blockWidth*0.26, _blockWidth*0.44, _blockWidth*0.58, _blockWidth*0.8, _blockWidth*0.42, colour); + + al_set_target_bitmap(prev_draw); +} +void BitmapStore::drawMazeWall(ALLEGRO_BITMAP* canvas) +{ + ALLEGRO_BITMAP* prev_draw = al_get_target_bitmap(); + al_set_target_bitmap(canvas); + + ALLEGRO_COLOR colour = al_map_rgb(203,255,151); + al_clear_to_color(colour); + + al_set_target_bitmap(prev_draw); +} +void BitmapStore::drawMazeFloor(ALLEGRO_BITMAP* canvas) +{ + ALLEGRO_BITMAP* prev_draw = al_get_target_bitmap(); + al_set_target_bitmap(canvas); + + ALLEGRO_COLOR colour = al_map_rgb(0,0,0); + al_clear_to_color(colour); + + al_set_target_bitmap(prev_draw); +} +void BitmapStore::drawSmoke(ALLEGRO_BITMAP* canvas) +{ + ALLEGRO_BITMAP* prev_draw = al_get_target_bitmap(); + al_set_target_bitmap(canvas); + + ALLEGRO_COLOR colour = al_map_rgb(255,255,255); + al_draw_circle (_blockWidth/2.3, _blockWidth/2.1, _blockWidth/2-1, colour,1); + al_draw_circle (_blockWidth/4, _blockWidth/4, _blockWidth/4, colour,2); + al_draw_circle (_blockWidth/5, _blockWidth/1.5, _blockWidth/4, colour,4); + al_draw_circle (_blockWidth/2.5, _blockWidth/2.7, _blockWidth/3, colour,3); + al_draw_circle (_blockWidth/1.2, _blockWidth/1.8, _blockWidth/3.7, colour,2); + al_draw_circle (_blockWidth/2.8, _blockWidth/2.2, _blockWidth/6, colour,3); + al_draw_circle (_blockWidth/1.1, _blockWidth/1.2, _blockWidth/3, colour,2); + al_draw_circle (_blockWidth/1.2, _blockWidth/1.7, _blockWidth/2, colour,3); + al_draw_circle (_blockWidth/1.3, _blockWidth/1.3, _blockWidth/5, colour,2); + + al_set_target_bitmap(prev_draw); +} + +void BitmapStore::drawCrashedCar(ALLEGRO_BITMAP* canvas) +{ + ALLEGRO_BITMAP* prev_draw = al_get_target_bitmap(); + al_set_target_bitmap(canvas); + + al_draw_filled_rounded_rectangle(_blockWidth/3.33, _blockWidth/5, _blockWidth/1.25, _blockWidth/1.04, 5, 5, al_map_rgb (200, 200, 200)); + al_draw_circle (_blockWidth/2.3, _blockWidth/2.1, _blockWidth/2-1, al_map_rgb (255, 0, 0),1); + al_draw_circle (_blockWidth/4, _blockWidth/4, _blockWidth/4, al_map_rgb (100, 100, 100),2); + al_draw_circle (_blockWidth/5, _blockWidth/1.5, _blockWidth/4, al_map_rgb (255, 0, 0),4); + al_draw_filled_rectangle(_blockWidth/2.5, _blockWidth/2, _blockWidth/1.43, _blockWidth/1.7, al_map_rgb (0,0, 0)); + al_draw_circle (_blockWidth/2.5, _blockWidth/2.7, _blockWidth/3, al_map_rgb (100, 100, 100),3); + al_draw_circle (_blockWidth/1.2, _blockWidth/1.8, _blockWidth/3.7, al_map_rgb (255, 0, 0),2); + al_draw_rectangle(_blockWidth/3.13, _blockWidth/1.04, _blockWidth/1.25, _blockWidth, al_map_rgb (25,25, 112),1); + al_draw_circle (_blockWidth/2.8, _blockWidth/2.2, _blockWidth/6, al_map_rgb (255, 0, 0),3); + al_draw_circle (_blockWidth/1.1, _blockWidth/1.2, _blockWidth/3, al_map_rgb (100, 100, 100),2); + al_draw_circle (_blockWidth/1.2, _blockWidth/1.7, _blockWidth/2, al_map_rgb (100, 100, 100),3); + al_draw_circle (_blockWidth/1.3, _blockWidth/1.3, _blockWidth/5, al_map_rgb (255, 0, 0),2); + + al_set_target_bitmap(prev_draw); +} + +void BitmapStore::drawClaimedCheckpoint(ALLEGRO_BITMAP* canvas) +{ + ALLEGRO_BITMAP* prev_draw = al_get_target_bitmap(); + al_set_target_bitmap(canvas); + + ALLEGRO_COLOR colour = al_map_rgb(255,255,255); + al_draw_text(_bitmapFont, colour, _blockWidth/2, _blockWidth/2, ALLEGRO_ALIGN_CENTRE , "GOTCHA"); + + al_set_target_bitmap(prev_draw); +} diff --git a/source/presentation/BitmapStore.h b/source/presentation/BitmapStore.h new file mode 100644 index 0000000..b551698 --- /dev/null +++ b/source/presentation/BitmapStore.h @@ -0,0 +1,140 @@ +#ifndef BITMAPSTORE_H +#define BITMAPSTORE_H + +#include <string> +#include <map> +using namespace std; + +#include <allegro5/allegro.h> +#include <allegro5/allegro_primitives.h> +#include <allegro5/allegro_font.h> +#include <allegro5/allegro_ttf.h> +#include <allegro5/allegro_native_dialog.h> + +#include "../logic/AllegroWrappers.h" + +/** +* @brief Class for accessing images in ALLEGRO_BITMAP format and low level drawing. +* +* The store ensures that only one copy of identical images are created. +* This is done through a map, that caches the images that have already been requested. +* If an uncached image is requested, it is added to the cache before being returned. +* The store provides an enumerated type, Image, for other classes to reference which image +* should represent the object on the screen. +* +* All images are square, to allow easy rotation and placement on the screen. +* +* @author Justin Wernick +* @author David Schneider +*/ +class BitmapStore +{ + public: + /** + * @brief Constructor for creating a BitmapStore with a set image size. + * + * @param [in] blockWidth The width (and height) of an image returned by the store in pixels. + */ + BitmapStore(unsigned int blockWidth); + /** + * @brief Destructor for clearing cache. + */ + ~BitmapStore(); + + /** + * @brief Type used to define which image should be returned. + */ + enum Image {PLAYER, ENEMY, ROCK, CHECKPOINT, MAZE_WALL, MAZE_FLOOR, SMOKE, CRASHED_CAR, CLAIMED_CHECKPOINT}; + + /** + * @brief Function to get image for drawing to the screen. + * + * @param [in] image Image to be returned. + * @return Requested image in ALLEGRO_BITMAP format. + */ + ALLEGRO_BITMAP* getBitmap(Image image); + + private: + /** + * @brief Unimplemented copy constructor, prevents copying of BitmapStore objects. + * + * Copying a BitmapStore is unneccesary as there should only be a single BitmapStore object. + */ + BitmapStore(const BitmapStore& ref); + /** + * @brief Unimplemented assignment operator. + * + * @see BitmapStore(const BitmapStore& ref); + */ + BitmapStore& operator=(const BitmapStore& rhs); + + /** + * @brief Draws the image representing the player. + * + * @param [out] canvas ALLEGRO_BITMAP onto which the image is drawn. + */ + void drawPlayerCar(ALLEGRO_BITMAP* canvas); + /** + * @brief Draws the image representing an enemy. + * + * @param [out] canvas ALLEGRO_BITMAP onto which the image is drawn. + */ + void drawEnemyCar(ALLEGRO_BITMAP* canvas); + /** + * @brief Draws the image representing a rock. + * + * @param [out] canvas ALLEGRO_BITMAP onto which the image is drawn. + */ + void drawRock(ALLEGRO_BITMAP* canvas); + /** + * @brief Draws the image representing a checkpoint. + * + * @param [out] canvas ALLEGRO_BITMAP onto which the image is drawn. + */ + void drawCheckpoint(ALLEGRO_BITMAP* canvas); + /** + * @brief Draws the image representing a solid part of the maze. + * + * @param [out] canvas ALLEGRO_BITMAP onto which the image is drawn. + */ + void drawMazeWall(ALLEGRO_BITMAP* canvas); + /** + * @brief Draws the image representing a non-solid part of the maze. + * + * @param [out] canvas ALLEGRO_BITMAP onto which the image is drawn. + */ + void drawMazeFloor(ALLEGRO_BITMAP* canvas); + /** + * @brief Draws the image representing a smokescreen. + * + * @param [out] canvas ALLEGRO_BITMAP onto which the image is drawn. + */ + void drawSmoke(ALLEGRO_BITMAP* canvas); + /** + * @brief Draws the popup that appears when a car crashes. + * + * @param [out] canvas ALLEGRO_BITMAP onto which the image is drawn. + */ + void drawCrashedCar(ALLEGRO_BITMAP* canvas); + /** + * @brief Draws the popup that appears when a checkpoint is collected. + * + * @param [out] canvas ALLEGRO_BITMAP onto which the image is drawn. + */ + void drawClaimedCheckpoint(ALLEGRO_BITMAP* canvas); + + AllegroDrawingInit _drawingInstalls; ///< Ensures that Allegro is initialized while an object of this class exists + + ALLEGRO_FONT* _bitmapFont; ///< Font used for writing text on bitmaps. + + /** + * @brief map containing pairs of Images (the enumerated type) and the actual images. + * + * Creates a cache for images once they have been drawn. + */ + map<Image, ALLEGRO_BITMAP*> _bitmaps; + + unsigned int _blockWidth; ///< The width of a square image in the store +}; + +#endif // BITMAPSTORE_H diff --git a/source/presentation/ColourStore.cpp b/source/presentation/ColourStore.cpp new file mode 100644 index 0000000..e08b6f8 --- /dev/null +++ b/source/presentation/ColourStore.cpp @@ -0,0 +1,25 @@ +#include "ColourStore.h" + +ColourStore::ColourStore() +{ + populateColours(); +} + +void ColourStore::populateColours() +{ + _colours[BitmapStore::PLAYER] = al_map_rgb(0,255,255); + _colours[BitmapStore::ENEMY] = al_map_rgb(255,0,0); + _colours[BitmapStore::CHECKPOINT] = al_map_rgb(0,255,0); + _colours[BitmapStore::MAZE_WALL] = al_map_rgb(255,255,255); + _colours[BitmapStore::MAZE_FLOOR] = al_map_rgb(0,0,0); + _transparent = al_map_rgba(0,0,0,0); +} + +ALLEGRO_COLOR ColourStore::getColour(BitmapStore::Image image) +{ + if (_colours.find(image) != _colours.end()) + { + return _colours[image]; + } + else return _transparent; +} diff --git a/source/presentation/ColourStore.h b/source/presentation/ColourStore.h new file mode 100644 index 0000000..8985099 --- /dev/null +++ b/source/presentation/ColourStore.h @@ -0,0 +1,60 @@ +#ifndef COLOURSTORE_H +#define COLOURSTORE_H + +#include <allegro5/allegro.h> + +#include "../presentation/BitmapStore.h" + +/** +* @brief Class for mapping BitmapStore images to colours for use in the minimap. +* +* @author Justin Wernick +* @author David Schneider +*/ +class ColourStore +{ + public: + /** + * @brief Creates the ColourStore object and initialises all of the colours. + */ + ColourStore(); + + /** + * @brief Returns the colour associated with a given image. + * + * If no colour makes sense for the image, then when it is requested a colour + * with an alpha of 0 (completely transparent) is returned. + * + * @param [in] image The BitmapStore image to be associated with a colour. + * + * @return The requested colour. + */ + ALLEGRO_COLOR getColour(BitmapStore::Image image); + private: + /** + * @brief Unimplemented copy constructor, prevents copying of ColourStore objects. + * + * Copying a ColourStore is unneccesary as there should only be a single ColourStore object. + */ + ColourStore(const ColourStore& ref); + /** + * @brief Unimplemented assignment operator. + * + * @see ColourStore(const ColourStore& ref); + */ + ColourStore& operator=(const ColourStore& rhs); + + map<BitmapStore::Image, ALLEGRO_COLOR> _colours; + + /** + * @brief Initialised to have an alpha of 0, and returned when the colour of an unlisted image is requested. + */ + ALLEGRO_COLOR _transparent; + + /** + * @brief Initialises all of the relevant colours. + */ + void populateColours(); +}; + +#endif // COLOURSTORE_H diff --git a/source/presentation/GamePanel.cpp b/source/presentation/GamePanel.cpp new file mode 100644 index 0000000..71ef290 --- /dev/null +++ b/source/presentation/GamePanel.cpp @@ -0,0 +1,133 @@ +#include "GamePanel.h" + +GamePanel::GamePanel(ALLEGRO_BITMAP* back, ALLEGRO_BITMAP* front, int x, int y, int width, int height) + :ScreenPanel(back, front, x, y, width, height), + _mazeblockWidth(_width/BLOCKS_PER_ROW), + _offsetX(0), + _offsetY(0), + _bitmapStore(_mazeblockWidth) +{ +} + +void GamePanel::draw(const Maze& maze, const list<PlayerCar>& players, const list<EnemyCar>& enemies, const list<Checkpoint>& checkpoints, const list<Rock>& rocks, const list<Smokescreen>& smokescreens, const list<DestroyedObjectPopup>& popups) +{ + ALLEGRO_BITMAP* prev_draw = al_get_target_bitmap(); + al_set_target_bitmap(_back); + + al_clear_to_color(BLANK); + + float _maxOffsetX = getPanelX(maze.width()) - _width; + float _maxOffsetY = getPanelY(maze.height()) - _height; + + if (!players.empty()) + { + _offsetX = getPanelX(players.front().x()) - _width/2; + if (_offsetX < 0) _offsetX = 0; + else if (_offsetX > _maxOffsetX) _offsetX = _maxOffsetX; + + _offsetY = getPanelY(players.front().y()) - _height/2; + if (_offsetY < 0) _offsetY = 0; + else if (_offsetY > _maxOffsetY) _offsetY = _maxOffsetY; + } + + draw(maze); + + for (list<PlayerCar>::const_iterator iter = players.begin(); iter != players.end(); ++iter) + { + draw(*iter); + } + for (list<EnemyCar>::const_iterator iter = enemies.begin(); iter != enemies.end(); ++iter) + { + draw(*iter); + } + for (list<Checkpoint>::const_iterator iter = checkpoints.begin(); iter != checkpoints.end(); ++iter) + { + draw(*iter); + } + for (list<Rock>::const_iterator iter = rocks.begin(); iter != rocks.end(); ++iter) + { + draw(*iter); + } + for (list<Smokescreen>::const_iterator iter = smokescreens.begin(); iter != smokescreens.end(); ++iter) + { + draw(*iter); + } + for (list<DestroyedObjectPopup>::const_iterator iter = popups.begin(); iter != popups.end(); ++iter) + { + draw(*iter); + } + + al_set_target_bitmap(prev_draw); +} + +void GamePanel::draw(const Maze& maze) +{ + //only draws a parts of the maze that would appear on the screen + int minX = floor((_offsetX-_mazeblockWidth)/_mazeblockWidth); + int maxX = ceil((_offsetX+_width)/_mazeblockWidth); + int minY = floor((_offsetY-_mazeblockWidth)/_mazeblockWidth); + int maxY = ceil((_offsetY+_height)/_mazeblockWidth); + + ALLEGRO_BITMAP* wallBitmap = _bitmapStore.getBitmap(BitmapStore::MAZE_WALL); + ALLEGRO_BITMAP* floorBitmap = _bitmapStore.getBitmap(BitmapStore::MAZE_FLOOR); + //used to only have one al_draw_bitmap command + ALLEGRO_BITMAP* currentBitmap = floorBitmap; + for (int x=minX; x<maxX&&x<maze.width(); ++x) + { + for (int y=minY; y<maxY&&y<maze.height(); ++y) + { + if (maze.getSolid(x,y)) + { + currentBitmap = wallBitmap; + } + else + { + currentBitmap = floorBitmap; + } + al_draw_bitmap(currentBitmap, getPanelX(x)-_offsetX, getPanelY(y)-_offsetY, 0); + } + } +} + +void GamePanel::draw(const GameObject& object) +{ + //only draws a gameobject if it would appear on the screen + if (object.x() < (_offsetX-_mazeblockWidth)/_mazeblockWidth) return; + if (object.x() > (_offsetX+_width)/_mazeblockWidth) return; + if (object.y() < (_offsetY-_mazeblockWidth)/_mazeblockWidth) return; + if (object.y() > (_offsetY+_height)/_mazeblockWidth) return; + + ALLEGRO_BITMAP* bitmap = _bitmapStore.getBitmap(object.image()); + + float angle = 0; + switch(object.facing()) + { + case Maze::UP: + angle = 0; + break; + case Maze::RIGHT: + angle = ALLEGRO_PI/2; + break; + case Maze::DOWN: + angle = ALLEGRO_PI; + break; + case Maze::LEFT: + angle = 3*ALLEGRO_PI/2; + break; + } + + float objectX = getPanelX(object.x()); + float objectY = getPanelY(object.y()); + float center = _mazeblockWidth/2; + + al_draw_rotated_bitmap(bitmap, center , center , objectX+center-_offsetX, objectY+center-_offsetY, angle, 0); +} + +float GamePanel::getPanelX(const double& x) const +{ + return static_cast<float>(x*_mazeblockWidth); +} +float GamePanel::getPanelY(const double& y) const +{ + return static_cast<float>(y*_mazeblockWidth); +} diff --git a/source/presentation/GamePanel.h b/source/presentation/GamePanel.h new file mode 100644 index 0000000..916ac13 --- /dev/null +++ b/source/presentation/GamePanel.h @@ -0,0 +1,117 @@ +#ifndef GAMEPANEL_H +#define GAMEPANEL_H + +#include "../presentation/ScreenPanel.h" +#include "../logic/Maze.h" +#include "../logic/GameObject.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" + +/** +* @brief ScreenPanel to be drawn on the screen to draw the area where the game takes place. +* +* This includes the scrolling maze and all of the GameObjects. +* +* @author Justin Wernick +* @author David Schneider +*/ +class GamePanel : public ScreenPanel +{ + public: + /** + * @brief Creates a GamePanel from the given back and front buffers. + * + * The sub-bitmaps that GamePanel uses are created from a rectangular region on back and front + * that has its top left corner at the coordinate x,y, is width long in the x direction, and + * height long in the y direction. + * + * @param [in] back The current back buffer of the display being sub-bitmapped. + * @param [in] front The current front buffer (image currently being displayed) of the display being sub-bitmapped. + * @param [in] x The x coordinate of the left side of the sub-bitmap in pixels. + * @param [in] y The x coordinate of the top of the sub-bitmap in pixels. + * @param [in] width The length in the x direction of the new sub-bitmap in pixels. + * @param [in] height The length in the y direction of the new sub-bitmap in pixels. + */ + GamePanel(ALLEGRO_BITMAP* back, ALLEGRO_BITMAP* front, int x, int y, int width, int height); + + + /** + * @brief Draws the given objects on the screen. + * + * The drawing is offset so that the first entry in players is in the middle of the panel. + * However, the offset will never be such that the drawing area will be outside of the maze. + * + * @param [in] maze The Maze that all of the objects are in. + * @param [in] players The list of PlayerCars to be drawn. + * @param [in] enemies The list of EnemyCars to be drawn. + * @param [in] checkpoints The list of Checkpoints to be drawn. + * @param [in] rocks The list of Rocks to be drawn. + * @param [in] smokescreens The list of Smokescreens to be drawn. + * @param [in] popups The list of DestroyedObjectPopups to be drawn. + */ + virtual void draw(const Maze& maze, const list<PlayerCar>& players, const list<EnemyCar>& enemies, const list<Checkpoint>& checkpoints, const list<Rock>& rocks, const list<Smokescreen>& smokescreens, const list<DestroyedObjectPopup>& popups); + private: + /** + * @brief Copy constructor not implemented, ScreenPanels should not be copied. + */ + GamePanel(const GamePanel& ref); + /** + * @brief Assignment operator not implemented, ScreenPanels should not be copied. + */ + GamePanel& operator=(const GamePanel& rhs); + /** + * @brief Converts an x game coordinate value to its equivalent in pixels. + * + * Converting to the pixel coordinates happens for every object every frame. To increase + * performance, the parameters are passed in by constant reference instead of by value. + * + * @param [in] x The game coordinate to be converted into pixels. + */ + float getPanelX(const double& x) const; + /** + * @brief Converts a y game coordinate value to its equivalent in pixels. + * + * Converting to the pixel coordinates happens for every object every frame. To increase + * performance, the parameters are passed in by constant reference instead of by value. + * + * @param [in] y The game coordinate to be converted into pixels. + */ + float getPanelY(const double& y) const; + + /** + * @brief Draws a Maze on the screen. + * + * Bitmaps used to represent solid and non-solid parts of the Maze are stored in the + * BitmapStore. + * + * @param [in] maze The Maze to be drawn. + */ + void draw(const Maze& maze); + + /** + * @brief Draws a single GameObject on the screen. + * + * The bitmap to be drawn is retrieved from the BitmapStore using the GameObject's image. + * + * @param [in] object The GameObject to be drawn. + */ + void draw(const GameObject& object); + + const static int BLOCKS_PER_ROW = 15; ///< The number of Maze blocks in one row shown on the panel at a time. Used to determine the scale. + + unsigned int _mazeblockWidth; ///< The width of one (square) Maze block on the screen, in pixels. + + float _offsetX; ///< The amount that drawing should be offset to the right, recalculated every frame. + float _offsetY; ///< The amount that drawing should be offset downwards, recalculated every frame. + + BitmapStore _bitmapStore; ///< Used to cache ALLEGRO_BITMAPs, so that they only need to be drawn once. + + AllegroInit _allegro; ///< Handles dependencies on Allegro. +}; + +#endif // GAMEPANEL_H diff --git a/source/presentation/InfoPanel.cpp b/source/presentation/InfoPanel.cpp new file mode 100644 index 0000000..b4d33fa --- /dev/null +++ b/source/presentation/InfoPanel.cpp @@ -0,0 +1,111 @@ +#include "InfoPanel.h" + +InfoPanel::InfoPanel(ALLEGRO_BITMAP* back, ALLEGRO_BITMAP* front, int x, int y, int width, int height) + :ScreenPanel(back, front, x, y, width, height), + _petrolHeadingY(_width/10), + _petrolGuageY(_petrolHeadingY + _width/10), + _petrolGuageHeight(_width/10), + _checkpointHeadingY(_petrolGuageY + _petrolGuageHeight + _width/10), + _checkpointValueY(_checkpointHeadingY + _width/10), + _miniMazeY(_checkpointValueY + _width/5), + _miniMazeHeight(_height - _miniMazeY), + _miniMazeblockWidth(0) +{ + _panelFont = al_load_font("junction 02.ttf", _width/10, 0); + if (_panelFont == NULL) + { + al_show_native_message_box(NULL, "Fatal error", "Fatal error", "The file 'junction 02.ttf' was not found. Ensure that it is located in the working directory.", NULL, ALLEGRO_MESSAGEBOX_ERROR); + throw InstallFailure(); + } +} + +InfoPanel::~InfoPanel() +{ + al_destroy_font(_panelFont); +} + +void InfoPanel::draw(const Maze& maze, const list<PlayerCar>& players, const list<EnemyCar>& enemies, const list<Checkpoint>& checkpoints, const list<Rock>& rocks, const list<Smokescreen>& smokescreens, const list<DestroyedObjectPopup>& popups) +{ + ALLEGRO_BITMAP* prev_draw = al_get_target_bitmap(); + al_set_target_bitmap(_back); + + double petrol = 0; + if (!players.empty()) + { + petrol = players.front().petrol(); + } + + al_clear_to_color(_colourStore.getColour(BitmapStore::MAZE_FLOOR)); + + //gets a mazeblock width the fits the current maze + _miniMazeblockWidth = min(static_cast<float>(_width)/maze.width(), static_cast<float>(_miniMazeHeight)/maze.height()); + + //draws petrol heading and bar + al_draw_text(_panelFont, al_map_rgb(255,255,255), 1, _petrolHeadingY, ALLEGRO_ALIGN_LEFT , "Petrol"); + al_draw_filled_rectangle(0,_petrolGuageY,_width*petrol, _petrolGuageY+_petrolGuageHeight, al_map_rgb(255,128,0)); + + //draws checkpoints remaining heading and value + al_draw_text(_panelFont, al_map_rgb(255,255,255), 1, _checkpointHeadingY, ALLEGRO_ALIGN_LEFT , "Checkpoints"); + stringstream checkpointCountString; + checkpointCountString << Checkpoint::checkpointCount(); + al_draw_text(_panelFont, al_map_rgb(255,255,255), 1, _checkpointValueY, ALLEGRO_ALIGN_LEFT , checkpointCountString.str().c_str()); + + draw(maze); + for (list<PlayerCar>::const_iterator iter = players.begin(); iter != players.end(); ++iter) + { + draw(*iter); + } + for (list<EnemyCar>::const_iterator iter = enemies.begin(); iter != enemies.end(); ++iter) + { + draw(*iter); + } + for (list<Checkpoint>::const_iterator iter = checkpoints.begin(); iter != checkpoints.end(); ++iter) + { + draw(*iter); + } + + //restore draw target + al_set_target_bitmap(prev_draw); +} + +void InfoPanel::draw(const Maze& maze) +{ + ALLEGRO_COLOR wallColour = _colourStore.getColour(BitmapStore::MAZE_WALL); + ALLEGRO_COLOR floorColour = _colourStore.getColour(BitmapStore::MAZE_FLOOR); + + for (int x=0; x<maze.width(); ++x) + { + for (int y=0; y<maze.height(); ++y) + { + float x1 = getPanelX(x); + float x2 = x1 + _miniMazeblockWidth; + float y1 = getPanelY(y) + _miniMazeY; + float y2 = y1 + _miniMazeblockWidth; + if (maze.getSolid(x,y)) + { + al_draw_filled_rectangle(x1, y1, x2, y2, wallColour); + } + else + { + al_draw_filled_rectangle(x1, y1, x2, y2, floorColour); + } + } + } +} + +void InfoPanel::draw(const GameObject& object) +{ + float r = _miniMazeblockWidth/2; + float cx = getPanelX(object.x()) + r; + float cy = getPanelY(object.y()) + _miniMazeY + r; + al_draw_filled_circle(cx, cy, r, _colourStore.getColour(object.image())); +} + +float InfoPanel::getPanelX(const double& x) const +{ + return static_cast<float>(x*_miniMazeblockWidth); +} +float InfoPanel::getPanelY(const double& y) const +{ + return static_cast<float>(y*_miniMazeblockWidth); +} diff --git a/source/presentation/InfoPanel.h b/source/presentation/InfoPanel.h new file mode 100644 index 0000000..9af6a70 --- /dev/null +++ b/source/presentation/InfoPanel.h @@ -0,0 +1,140 @@ +#ifndef INFOPANEL_H +#define INFOPANEL_H + +#include <allegro5/allegro.h> +#include <allegro5/allegro_primitives.h> + +#include <sstream> +using namespace std; + +#include "../presentation/ScreenPanel.h" +#include "../presentation/ColourStore.h" +#include "../logic/Maze.h" +#include "../logic/GameObject.h" +#include "../logic/PlayerCar.h" +#include "../logic/EnemyCar.h" +#include "../logic/Checkpoint.h" +#include "../logic/AllegroWrappers.h" + +/** +* @brief ScreenPanel to be drawn on the screen to give the player information. +* +* This includes the minimap, a scaled down version of the entire maze that does not scroll, with +* icons to represent the PlayerCar, EnemyCars, and Checkpoints. Text is drawn to show the player the +* number of Checkpoints that needed to be collected for victory, and a rectangle is drawn representing +* the amount of petrol that the PlayerCar has left. +* +* @author Justin Wernick +* @author David Schneider +*/ +class InfoPanel : public ScreenPanel +{ + public: + /** + * @brief Creates an InfoPanel from the given back and front buffers. + * + * The sub-bitmaps that InfoPanel uses are created from a rectangular region on back and front + * that has its top left corner at the coordinate x,y, is width long in the x direction, and + * height long in the y direction. + * + * @param [in] back The current back buffer of the display being sub-bitmapped. + * @param [in] front The current front buffer (image currently being displayed) of the display being sub-bitmapped. + * @param [in] x The x coordinate of the left side of the sub-bitmap in pixels. + * @param [in] y The x coordinate of the top of the sub-bitmap in pixels. + * @param [in] width The length in the x direction of the new sub-bitmap in pixels. + * @param [in] height The length in the y direction of the new sub-bitmap in pixels. + */ + InfoPanel(ALLEGRO_BITMAP* back, ALLEGRO_BITMAP* front, int x, int y, int width, int height); + + /** + * @brief Destructor that ensured that the font created is destroyed. + * + * The memory for the sub-bitmaps are handled by the parent class, ScreenPanel. + */ + ~InfoPanel(); + + /** + * @brief Draws the InfoPanel using the given objects. + * + * Not all of the provided objects are needed for the drawing process, but they are included + * to give the most general drawing case. This is to support polymorphism, where the InfoPanel + * can be told to draw its sub-bitmap in the same manner as any other ScreenPanel. + * + * The scale of the minimap is determined at the beginning of each frame, so that it will + * always fit even if the maze is larger than on the last frame. + * + * @param [in] maze The Maze that all of the objects are in. + * @param [in] players The list of PlayerCars to be drawn. + * @param [in] enemies The list of EnemyCars to be drawn. + * @param [in] checkpoints The list of Checkpoints to be drawn. + * @param [in] rocks Rocks are not actually drawn. + * @param [in] smokescreens Smokescreens are not actually drawn. + * @param [in] popups DestroyedObjectPopups are not actually drawn. + */ + virtual void draw(const Maze& maze, const list<PlayerCar>& players, const list<EnemyCar>& enemies, const list<Checkpoint>& checkpoints, const list<Rock>& rocks, const list<Smokescreen>& smokescreens, const list<DestroyedObjectPopup>& popups); + private: + /** + * @brief Copy constructor not implemented, ScreenPanels should not be copied. + */ + InfoPanel(const InfoPanel& ref); + /** + * @brief Assignment operator not implemented, ScreenPanels should not be copied. + */ + InfoPanel& operator=(const InfoPanel& rhs); + /** + * @brief Converts an x game coordinate value to its equivalent in pixels. + * + * Converting to the pixel coordinates happens for every object every frame. To increase + * performance, the parameters are passed in by constant reference instead of by value. + * + * @param [in] x The game coordinate to be converted into pixels. + */ + float getPanelX(const double& x) const; + /** + * @brief Converts a y game coordinate value to its equivalent in pixels. + * + * Converting to the pixel coordinates happens for every object every frame. To increase + * performance, the parameters are passed in by constant reference instead of by value. + * + * @param [in] y The game coordinate to be converted into pixels. + */ + float getPanelY(const double& y) const; + + /** + * @brief Draws a Maze on the panel. + * + * The Maze is constructed of coloured squares. The colour of the squares is retrieved from + * the ColourStore. + * + * Unlike in the GamePanel, the entire Maze is drawn. + * + * @param [in] maze The Maze to be drawn. + */ + void draw(const Maze& maze); + + /** + * @brief Draws a single GameObject on the panel. + * + * The GameObject is represented by a coloured circle in the Maze. The colour is based on + * the GameObject's image and is retrieved from the ColourStore. + * + * @param [in] object The GameObject to be drawn. + */ + void draw(const GameObject& object); + + AllegroDrawingInit _drawing; ///< Handles dependencies on Allegro's primitive drawing functions. + ColourStore _colourStore; ///< Caches colours for drawing. + + float _petrolHeadingY; ///< The y coordinate of the heading for the petrol guage. + float _petrolGuageY; ///< The y coordinate of top of the petrol guage. + float _petrolGuageHeight; ///< The height of the rectangle that is the petrol guage. + float _checkpointHeadingY; ///< The y coordinate of the heading for the number of remaining checkpoints. + float _checkpointValueY; ///< The y coordinate of the text stating the number of remaining checkpoints. + float _miniMazeY; ///< The y coordinate of the top of the Maze. + float _miniMazeHeight; ///< The height of the Maze. + float _miniMazeblockWidth; ///< The width of each Maze block being drawn. + + ALLEGRO_FONT* _panelFont; ///< The font being used to write the headings and number of checkpoints remaining. +}; + +#endif // INFOPANEL_H diff --git a/source/presentation/KeyboardHandler.cpp b/source/presentation/KeyboardHandler.cpp new file mode 100644 index 0000000..b588cbe --- /dev/null +++ b/source/presentation/KeyboardHandler.cpp @@ -0,0 +1,114 @@ +#include "KeyboardHandler.h" + +KeyboardHandler::KeyboardHandler(Maze::Direction currentFacing) + :_up(false), + _down(false), + _left(false), + _right(false), + _smokescreen(false), + _previousFacing(currentFacing) +{ + _keyboardEvents = al_create_event_queue(); + al_register_event_source(_keyboardEvents, al_get_keyboard_event_source()); +} + +KeyboardHandler::KeyboardHandler(const KeyboardHandler& ref) + :_up(ref._up), + _down(ref._down), + _left(ref._left), + _right(ref._right), + _smokescreen(ref._smokescreen), + _previousFacing(ref._previousFacing) +{ + _keyboardEvents = al_create_event_queue(); + al_register_event_source(_keyboardEvents, al_get_keyboard_event_source()); +} + +KeyboardHandler& KeyboardHandler::operator=(const KeyboardHandler& rhs) +{ + _up = rhs._up; + _down = rhs._down; + _left = rhs._left; + _right = rhs._right; + _smokescreen = rhs._smokescreen; + _previousFacing = rhs._previousFacing; + + if (_keyboardEvents!=rhs._keyboardEvents) al_destroy_event_queue(_keyboardEvents); + + _keyboardEvents = al_create_event_queue(); + al_register_event_source(_keyboardEvents, al_get_keyboard_event_source()); + + return *this; +} + +KeyboardHandler::~KeyboardHandler() +{ + al_destroy_event_queue(_keyboardEvents); +} + +void KeyboardHandler::updateFlags() +{ + ALLEGRO_EVENT event; + while (al_get_next_event(_keyboardEvents, &event)) + { + if (event.type==ALLEGRO_EVENT_KEY_DOWN) + { + switch (event.keyboard.keycode) + { + case UP_KEY: + _up = true; + break; + case DOWN_KEY: + _down = true; + break; + case LEFT_KEY: + _left = true; + break; + case RIGHT_KEY: + _right = true; + break; + case SMOKESCREEN_KEY: + _smokescreen = true; + break; + } + } + else if (event.type==ALLEGRO_EVENT_KEY_UP) + { + switch (event.keyboard.keycode) + { + case UP_KEY: + _up = false; + break; + case DOWN_KEY: + _down = false; + break; + case LEFT_KEY: + _left = false; + break; + case RIGHT_KEY: + _right = false; + break; + case SMOKESCREEN_KEY: + _smokescreen = false; + break; + } + } + } +} + +Maze::Direction KeyboardHandler::getFacing() +{ + updateFlags(); + + if (_up) _previousFacing = Maze::UP; + else if (_down) _previousFacing = Maze::DOWN; + else if (_left) _previousFacing = Maze::LEFT; + else if (_right) _previousFacing = Maze::RIGHT; + + return _previousFacing; +} + +bool KeyboardHandler::getSmokescreen() +{ + return _smokescreen; +} diff --git a/source/presentation/KeyboardHandler.h b/source/presentation/KeyboardHandler.h new file mode 100644 index 0000000..00d7821 --- /dev/null +++ b/source/presentation/KeyboardHandler.h @@ -0,0 +1,83 @@ +#ifndef KEYBOARDHANDLER_H +#define KEYBOARDHANDLER_H + +#include <allegro5/allegro.h> + +#include "../logic/Maze.h" +#include "../logic/AllegroWrappers.h" + +/** +* @brief Class for handling keyboard related game inputs from the player. +* +* Written with controlling a PlayerCar in mind. The handler keeps track of the last direction +* pushed and responds to requests from the PlayerCar for which direction it should face next, +* and whether the player is pressing the Smokescreen button. +* +* @author Justin Wernick +* @author David Schneider +*/ +class KeyboardHandler +{ + public: + /** + * @brief Creates a KeyboardHandler with a given initial state. + * + * @param [in] currentFacing The initial value for the previous facing of the object being controlled. + */ + KeyboardHandler(Maze::Direction currentFacing); + /** + * @brief Copy constructor that ensures that a copy of a KeyboardHandler will have its own event queue. + */ + KeyboardHandler(const KeyboardHandler& ref); + /** + * @brief Assignment operator that ensures that an assigned KeyboardHandler will have its own event queue. + */ + KeyboardHandler& operator=(const KeyboardHandler& rhs); + /** + * @brief Cleans up the keyboard event queue + */ + ~KeyboardHandler(); + + /** + * @brief Gives the last direction that the player entered on the keyboard. + * + * All pending keyboard events are processed, then a key out of those currently depressed is returned. + * The precendence for keys held down (up, down, left, then right) is arbitrary, since the player + * should not be holding down more than one arrow key at a time. + * If no keys are currently depressed, the value returned on the last call is returned again. + * + * @return The direction that the player has chosen through pressing arrow keys. + */ + Maze::Direction getFacing(); + + /** + * @brief Gives whether or not the key for creating a Smokescreen is currently pressed. + * + * @return True if a Smokescreen should be created. + */ + bool getSmokescreen(); + + private: + AllegroKeyboardInit _keyboard; ///< Ensures that dependencies on the Allegro keyboard library are installed. + + /** + * @brief Processes all pending keyboard inputs, and updates flags as appropriate. + */ + void updateFlags(); + + bool _up; ///< True if the up arrow key is depressed. + bool _down; ///< True if the down arrow key is depressed. + bool _left; ///< True if the left arrow key is depressed. + bool _right; ///< True if the right arrow key is depressed. + bool _smokescreen; ///< True if the smokescreen key is depressed. + Maze::Direction _previousFacing; ///< The direction that was returned on the last call of getFacing. + ALLEGRO_EVENT_QUEUE* _keyboardEvents; ///< Queue for all keyboard events. + + static const int UP_KEY = ALLEGRO_KEY_UP; ///< Key that must be pressed to turn up. + static const int DOWN_KEY = ALLEGRO_KEY_DOWN; ///< Key that must be pressed to turn down. + static const int LEFT_KEY = ALLEGRO_KEY_LEFT; ///< Key that must be pressed to turn left. + static const int RIGHT_KEY = ALLEGRO_KEY_RIGHT; ///< Key that must be pressed to turn right. + static const int SMOKESCREEN_KEY = ALLEGRO_KEY_SPACE; ///< Key that must be pressed to create a smokescreen. +}; + +#endif // KEYBOARDHANDLER_H diff --git a/source/presentation/Screen.cpp b/source/presentation/Screen.cpp new file mode 100644 index 0000000..0f5ba4c --- /dev/null +++ b/source/presentation/Screen.cpp @@ -0,0 +1,159 @@ +#include "Screen.h" + +Screen::Screen(unsigned int screenWidth, unsigned int screenHeight, bool fullscreen) + :_exitClicked(false), + _screenWidth(screenWidth), + _screenHeight(screenHeight), + _gameAreaWidth(_screenWidth*0.75), + _infoPanelWidth(_screenWidth - _gameAreaWidth) +{ + if (fullscreen) + { + al_set_new_display_flags(ALLEGRO_FULLSCREEN_WINDOW); + if (!resolutionSupported()) + { + al_show_native_message_box(NULL, "Fatal error", "Fatal error", "The fullscreen resolution specified in config.txt is not supported by your system. Please open config.txt and change the resolution to a supported resolution, or change fullscreen to false.", NULL, ALLEGRO_MESSAGEBOX_ERROR); + throw BadResolution(); + } + } + else + { + al_set_new_display_flags(ALLEGRO_WINDOWED); + //need to add error checking for windows that are way too big + } + + al_set_new_display_option(ALLEGRO_VSYNC, 1, ALLEGRO_SUGGEST); + + _display = al_create_display(_screenWidth, _screenHeight); + + al_hide_mouse_cursor(_display); + _windowEvents = al_create_event_queue(); + al_register_event_source(_windowEvents, al_get_display_event_source(_display)); + //used so that ESC can be pressed to exit. + al_register_event_source(_windowEvents, al_get_keyboard_event_source()); + + _font = al_load_font("junction 02.ttf", _screenWidth/10, 0); + if (_font == NULL) + { + al_show_native_message_box(NULL, "Fatal error", "Fatal error", "The file 'junction 02.ttf' was not found. Ensure that it is located in the working directory.", NULL, ALLEGRO_MESSAGEBOX_ERROR); + al_destroy_event_queue(_windowEvents); + al_destroy_display(_display); + throw InstallFailure(); + } + + ALLEGRO_BITMAP* front = al_get_backbuffer(_display); + al_flip_display(); + ALLEGRO_BITMAP* back = al_get_backbuffer(_display); + + _panels.push_back(new GamePanel(back, front, 0, 0, _gameAreaWidth, _screenHeight)); + _panels.push_back(new InfoPanel(back, front, _gameAreaWidth, 0, _infoPanelWidth, _screenHeight)); +} + +Screen::~Screen() +{ + for (vector<ScreenPanel*>::iterator iter = _panels.begin(); iter!=_panels.end(); ++iter) + { + delete (*iter); + } + _panels.clear(); + + al_destroy_font(_font); + al_destroy_event_queue(_windowEvents); + al_destroy_display(_display); +} + +string Screen::getLevel() +{ + string result(""); + ALLEGRO_FILECHOOSER* filechooser = al_create_native_file_dialog(".", "Choose your level", "*.lvl",ALLEGRO_FILECHOOSER_FILE_MUST_EXIST); + al_show_native_file_dialog(_display, filechooser); + if (al_get_native_file_dialog_count(filechooser)==0) + { + _exitClicked = true; + } + else + { + result = al_get_native_file_dialog_path(filechooser, 0); + } + + al_destroy_native_file_dialog(filechooser); + return result; +} + +void Screen::draw(const Maze& maze, const list<PlayerCar>& players, const list<EnemyCar>& enemies, const list<Checkpoint>& checkpoints, const list<Rock>& rocks, const list<Smokescreen>& smokescreens, const list<DestroyedObjectPopup>& popups) +{ + for (vector<ScreenPanel*>::iterator iter = _panels.begin(); iter!=_panels.end(); ++iter) + { + (*iter)->draw(maze, players, enemies, checkpoints, rocks, smokescreens, popups); + } + flip(); +} + +void Screen::flip() +{ + al_flip_display(); + for (vector<ScreenPanel*>::iterator iter = _panels.begin(); iter!=_panels.end(); ++iter) + { + (*iter)->flip(); + } +} + +void Screen::drawWin() +{ + flip(); + + ALLEGRO_COLOR transBlack = al_map_rgba(0,0,0,200); + al_draw_filled_rectangle(0,0,_screenWidth,_screenHeight, transBlack); + + ALLEGRO_COLOR textColour = al_map_rgb(255,255,255); + al_draw_text(_font, textColour, _screenWidth/2, _screenHeight/2, ALLEGRO_ALIGN_CENTRE , "You win!"); + + flip(); +} + +void Screen::drawLoss() +{ + flip(); + + ALLEGRO_COLOR transBlack = al_map_rgba(0,0,0,200); + al_draw_filled_rectangle(0,0,_screenWidth,_screenHeight, transBlack); + + ALLEGRO_COLOR textColour = al_map_rgb(255,255,255); + al_draw_text(_font, textColour, _screenWidth/2, _screenHeight/2, ALLEGRO_ALIGN_CENTRE , "You lose!"); + + flip(); +} + +bool Screen::exitClicked() +{ + if (_exitClicked) return true; + + ALLEGRO_EVENT event; + while (al_get_next_event(_windowEvents, &event)) + { + if (event.type==ALLEGRO_EVENT_DISPLAY_CLOSE || (event.type==ALLEGRO_EVENT_KEY_CHAR && event.keyboard.keycode==ALLEGRO_KEY_ESCAPE)) + { + al_flush_event_queue(_windowEvents); + _exitClicked = true; + return true; + } + } + + return false; +} + +bool Screen::resolutionSupported() +{ + ALLEGRO_DISPLAY_MODE mode; + for (int i=0; i<al_get_num_display_modes(); ++i) + { + al_get_display_mode(i, &mode); + + if (static_cast<unsigned int>(mode.width)==_screenWidth && static_cast<unsigned int>(mode.height)==_screenHeight) + { + return true; + } + } + + return false; +} diff --git a/source/presentation/Screen.h b/source/presentation/Screen.h new file mode 100644 index 0000000..16d8cee --- /dev/null +++ b/source/presentation/Screen.h @@ -0,0 +1,159 @@ +#ifndef SCREEN_H +#define SCREEN_H + +#include <list> +#include <algorithm> +#include <sstream> +using namespace std; + +#include <allegro5/allegro.h> +#include <allegro5/allegro_native_dialog.h> + +#include "../logic/Maze.h" +#include "../logic/GameObject.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 "../presentation/GamePanel.h" +#include "../presentation/InfoPanel.h" + +/** +* @brief Exception that is thrown if the Config file requests a resolution that can not work. +* +* @author Justin Wernick +* @author David Schneider +*/ +class BadResolution{}; + +/** +* @brief Class for creating a window on the screen. +* +* Anything involving drawing on the screen is the responsibility of this class. Most of these +* responsibilities are outsourced through creating ScreenPanels with more specialised tasks, +* such as drawing specifically the area where the gameplay takes place (GamePanel) or the +* providing the player with information (InfoPanel). +* +* @author Justin Wernick +* @author David Schneider +*/ +class Screen +{ + public: + /** + * @brief Creates a Screen with the given width and height in pixels, and the given fullscreen setting. + * + * @param [in] screenWidth The width of the display that will be created in pixels. + * @param [in] screenHeight The height of the display that will be created in pixels. + * @param [in] fullscreen True if the game should be in fullscreen mode. False otherwise. + */ + Screen(unsigned int screenWidth, unsigned int screenHeight, bool fullscreen); + + /** + * @brief Destructor to ensure that the display and any ScreenPanels are destroyed properly. + */ + ~Screen(); + + /** + * @brief Presents the player with a file dialog, requesting a level file to be selected. + * + * If the cancel button is clicked, and empty string is returned and exitClicked() will return + * true on its next call. + * + * @return The path of the level file, or an empty string if no file was selected. + */ + string getLevel(); + + /** + * @brief Draws the given objects on the screen. + * + * The objects are passed to each of the ScreenPanels in turn, and then the buffers + * are flipped. + * + * @param [in] maze The Maze that all of the objects are in. + * @param [in] players The list of PlayerCars to be drawn. + * @param [in] enemies The list of EnemyCars to be drawn. + * @param [in] checkpoints The list of Checkpoints to be drawn. + * @param [in] rocks The list of Rocks to be drawn. + * @param [in] smokescreens The list of Smokescreens to be drawn. + * @param [in] popups The list of DestroyedObjectPopups to be drawn. + */ + void draw(const Maze& maze, const list<PlayerCar>& players, const list<EnemyCar>& enemies, const list<Checkpoint>& checkpoints, const list<Rock>& rocks, const list<Smokescreen>& smokescreens, const list<DestroyedObjectPopup>& popups); + + /** + * @brief Function to find if the player has chosen to exit the game. + * + * The game can be exited by clicking the x in the corner of the window, pressing the ESC key + * during the game, or by clicking 'cancel' on the file selection dialog. + * + * @return True if the game should be quit. + */ + bool exitClicked(); + + /** + * @brief Draws a splash screen when the level has been won. + */ + void drawWin(); + /** + * @brief Draws a splash screen when the level has been lost. + */ + void drawLoss(); + + private: + /** + * @brief Unimplemented copy constructor, prevents copying of Screen objects. + * + * Copying a Screen is unneccesary as there should only be a single Screen object. + */ + Screen(const Screen& ref); + /** + * @brief Unimplemented assignment operator. + * + * @see Screen(const Screen& ref) + */ + Screen& operator=(const Screen& rhs); + + /** + * @brief Flips the display's buffers, as well as those for all of the ScreenPanels. + */ + void flip(); + + /** + * @brief Checks the current resolution (_screenWidth and _screenHeight) against the screen's supported resolutions. + * + * Used to test if a fullscreen mode selection will launch without issue. + * + * @return True if the current resolution is supported. + */ + bool resolutionSupported(); + + AllegroInit _allegro; ///< Ensures that Allegro has been installed, for event queues and creating the display. + AllegroKeyboardInit _keyboard; ///< Ensures that the keyboard has been installed, for checking for the ESC key. + AllegroDrawingInit _drawing; ///< Ensures that drawing operations have been installed, for drawing splash screens. + + bool _exitClicked; ///< Set to true when the user chooses to quit the game. + + unsigned int _screenWidth; ///< Horizontal number of pixels per row on the screen. + unsigned int _screenHeight; ///< Vertical number of pixels per column on the screen. + + unsigned int _gameAreaWidth; ///< Width of the GamePanel created. + unsigned int _infoPanelWidth; ///< Width of the InfoPanel created. The InfoPanel is placed directly to the right of the GamePanel. + + ALLEGRO_DISPLAY* _display; ///< The window created on the player's monitor to see the game. + ALLEGRO_EVENT_QUEUE* _windowEvents; ///< Events caught by the screen, checked for an exit command. + ALLEGRO_FONT* _font; ///< Font used in drawing splash screens. + + /** + * @brief Polymorphic container used to encapsulate the different types of drawing to the screen. + * + * Since the memory of the ScreenPanels in the vector is allocated dynamically, it must be deallocated + * in the destructor. + */ + vector<ScreenPanel*> _panels; +}; + +#endif // SCREEN_H diff --git a/source/presentation/ScreenPanel.cpp b/source/presentation/ScreenPanel.cpp new file mode 100644 index 0000000..db2db69 --- /dev/null +++ b/source/presentation/ScreenPanel.cpp @@ -0,0 +1,24 @@ +#include "ScreenPanel.h" + +const ALLEGRO_COLOR ScreenPanel::BLANK = al_map_rgb(0,0,0); + +ScreenPanel::ScreenPanel(ALLEGRO_BITMAP* back, ALLEGRO_BITMAP* front, int x, int y, int width, int height) + :_width(width), + _height(height) +{ + _back = al_create_sub_bitmap(back, x, y, _width, _height); + _front = al_create_sub_bitmap(front, x, y, _width, _height); +} + +ScreenPanel::~ScreenPanel() +{ + al_destroy_bitmap(_back); + al_destroy_bitmap(_front); +} + +void ScreenPanel::flip() +{ + ALLEGRO_BITMAP* temp = _back; + _back = _front; + _front = temp; +} diff --git a/source/presentation/ScreenPanel.h b/source/presentation/ScreenPanel.h new file mode 100644 index 0000000..13b4978 --- /dev/null +++ b/source/presentation/ScreenPanel.h @@ -0,0 +1,98 @@ +#ifndef SCREENPANEL_H +#define SCREENPANEL_H + +#include <allegro5/allegro.h> + +#include "../logic/Maze.h" +#include "../logic/GameObject.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" + +/** +* @brief Parent class for panels that are drawn on the screen. +* +* Panels are given a sub-bitmap of the Screen bitmap, which they draw their panel specific outputs on. +* The ScreenPanel class is subclassed to give specific drawing functions, like drawing the Maze and +* GameObjects on the screen. +* +* When the object is created, the back bitmap and front bitmap should correspond to the back and front buffers +* of the display respectively. This should be kept in sync by calling flip every time the display is flipped. +* +* @author Justin Wernick +* @author David Schneider +*/ +class ScreenPanel +{ + public: + /** + * @brief Creates a ScreenPanel from the given back and front buffers. + * + * The sub-bitmaps that ScreenPanel uses are created from a rectangular region on back and front + * that has its top left corner at the coordinate x,y, is width long in the x direction, and + * height long in the y direction. + * + * @param [in] back The current back buffer of the display being sub-bitmapped. + * @param [in] front The current front buffer (image currently being displayed) of the display being sub-bitmapped. + * @param [in] x The x coordinate of the left side of the sub-bitmap in pixels. + * @param [in] y The x coordinate of the top of the sub-bitmap in pixels. + * @param [in] width The length in the x direction of the new sub-bitmap in pixels. + * @param [in] height The length in the y direction of the new sub-bitmap in pixels. + */ + ScreenPanel(ALLEGRO_BITMAP* back, ALLEGRO_BITMAP* front, int x, int y, int width, int height); + + /** + * @brief Destructor to ensure that sub-bitmap memory is deallocated. + */ + virtual ~ScreenPanel(); + + /** + * @brief Pure virtual method for drawing a collection of objects onto the panel. + * + * Implementations do not need to draw all of the objects if it is not neccesary for the + * type of panel, but the interface accepts all of them to be general. + * + * @param [in] maze The Maze that all of the objects are in. + * @param [in] players The list of PlayerCars to be drawn. + * @param [in] enemies The list of EnemyCars to be drawn. + * @param [in] checkpoints The list of Checkpoints to be drawn. + * @param [in] rocks The list of Rocks to be drawn. + * @param [in] smokescreens The list of Smokescreens to be drawn. + * @param [in] popups The list of DestroyedObjectPopups to be drawn. + */ + virtual void draw(const Maze& maze, const list<PlayerCar>& players, const list<EnemyCar>& enemies, const list<Checkpoint>& checkpoints, const list<Rock>& rocks, const list<Smokescreen>& smokescreens, const list<DestroyedObjectPopup>& popups) = 0; + + /** + * @brief Swaps the front and back buffers. + * + * This function should be called every time the display is flipped. + */ + virtual void flip(); + + protected: + static const ALLEGRO_COLOR BLANK; ///< Colour used to clear the screen at the beginning of drawing operations. + + ALLEGRO_BITMAP* _back; ///< The back buffer. Only the back buffer can be drawn to. + + int _width; ///< The width of the sub-bitmaps being drawn to in pixels. + int _height; ///< The height of the sub-bitmaps being drawn to in pixels. + private: + /** + * @brief Copy constructor not implemented, ScreenPanels should not be copied. + */ + ScreenPanel(const ScreenPanel& ref); + /** + * @brief Assignment operator not implemented, ScreenPanels should not be copied. + */ + ScreenPanel& operator=(const ScreenPanel& rhs); + + AllegroInit _allegro; ///< Handles dependencies on Allegro being initialised. + + ALLEGRO_BITMAP* _front; ///< The front buffer, that is currently being shown on the screen. +}; + +#endif // SCREENPANEL_H |