summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/data/Config.cpp105
-rw-r--r--source/data/Config.h136
-rw-r--r--source/data/LevelReader.cpp54
-rw-r--r--source/data/LevelReader.h71
-rw-r--r--source/logic/AllegroWrappers.cpp116
-rw-r--r--source/logic/AllegroWrappers.h112
-rw-r--r--source/logic/Car.cpp75
-rw-r--r--source/logic/Car.h57
-rw-r--r--source/logic/Checkpoint.cpp30
-rw-r--r--source/logic/Checkpoint.h53
-rw-r--r--source/logic/CollisionDetector.cpp64
-rw-r--r--source/logic/CollisionDetector.h75
-rw-r--r--source/logic/DestroyedObjectPopup.cpp6
-rw-r--r--source/logic/DestroyedObjectPopup.h33
-rw-r--r--source/logic/EnemyCar.cpp104
-rw-r--r--source/logic/EnemyCar.h97
-rw-r--r--source/logic/Game.cpp185
-rw-r--r--source/logic/Game.h135
-rw-r--r--source/logic/GameObject.cpp27
-rw-r--r--source/logic/GameObject.h82
-rw-r--r--source/logic/LimitedTimeObject.cpp16
-rw-r--r--source/logic/LimitedTimeObject.h45
-rw-r--r--source/logic/Maze.cpp77
-rw-r--r--source/logic/Maze.h100
-rw-r--r--source/logic/MazeMath.cpp18
-rw-r--r--source/logic/MazeMath.h59
-rw-r--r--source/logic/PlayerCar.cpp87
-rw-r--r--source/logic/PlayerCar.h99
-rw-r--r--source/logic/Rock.cpp6
-rw-r--r--source/logic/Rock.h29
-rw-r--r--source/logic/Smokescreen.cpp6
-rw-r--r--source/logic/Smokescreen.h31
-rw-r--r--source/main.cpp21
-rw-r--r--source/presentation/BitmapStore.cpp238
-rw-r--r--source/presentation/BitmapStore.h140
-rw-r--r--source/presentation/ColourStore.cpp25
-rw-r--r--source/presentation/ColourStore.h60
-rw-r--r--source/presentation/GamePanel.cpp133
-rw-r--r--source/presentation/GamePanel.h117
-rw-r--r--source/presentation/InfoPanel.cpp111
-rw-r--r--source/presentation/InfoPanel.h140
-rw-r--r--source/presentation/KeyboardHandler.cpp114
-rw-r--r--source/presentation/KeyboardHandler.h83
-rw-r--r--source/presentation/Screen.cpp159
-rw-r--r--source/presentation/Screen.h159
-rw-r--r--source/presentation/ScreenPanel.cpp24
-rw-r--r--source/presentation/ScreenPanel.h98
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