diff options
Diffstat (limited to 'source/presentation')
-rw-r--r-- | source/presentation/BitmapStore.cpp | 238 | ||||
-rw-r--r-- | source/presentation/BitmapStore.h | 140 | ||||
-rw-r--r-- | source/presentation/ColourStore.cpp | 25 | ||||
-rw-r--r-- | source/presentation/ColourStore.h | 60 | ||||
-rw-r--r-- | source/presentation/GamePanel.cpp | 133 | ||||
-rw-r--r-- | source/presentation/GamePanel.h | 117 | ||||
-rw-r--r-- | source/presentation/InfoPanel.cpp | 111 | ||||
-rw-r--r-- | source/presentation/InfoPanel.h | 140 | ||||
-rw-r--r-- | source/presentation/KeyboardHandler.cpp | 114 | ||||
-rw-r--r-- | source/presentation/KeyboardHandler.h | 83 | ||||
-rw-r--r-- | source/presentation/Screen.cpp | 159 | ||||
-rw-r--r-- | source/presentation/Screen.h | 159 | ||||
-rw-r--r-- | source/presentation/ScreenPanel.cpp | 24 | ||||
-rw-r--r-- | source/presentation/ScreenPanel.h | 98 |
14 files changed, 1601 insertions, 0 deletions
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 |