diff --git a/src/cell.cpp b/src/cell.cpp new file mode 100644 index 0000000..ca76a85 --- /dev/null +++ b/src/cell.cpp @@ -0,0 +1,130 @@ +#include "cell.h" + +#include "hero.h" +#include "level.h" + +#define UNUSED(expr) (void)(expr); + +Cell::Cell(coordinate cell_x, coordinate cell_y, const sf::Color &color) : + Entity(cell_x, cell_y), + cell_color(color) +{} + +sf::Color Cell::color() const noexcept +{ + return cell_color; +} + +/////////////////////////////////////// + +PassableCell::PassableCell(coordinate cell_x, coordinate cell_y, const sf::Color &color) : + Cell(cell_x, cell_y, color) +{} + +PassableCell::~PassableCell() +{} + +bool PassableCell::onMovingTo(HeroPtr &hero, LevelPtr &level) +{ + UNUSED(hero) + UNUSED(level) + + // Hero just moves on. + return true; +} + +/////////////////////////////////////// + +WaterCell::WaterCell(coordinate cell_x, coordinate cell_y, const sf::Color &color) : + Cell(cell_x, cell_y, color) +{} + +WaterCell::~WaterCell() +{} + +bool WaterCell::onMovingTo(HeroPtr &hero, LevelPtr &level) +{ + // Try to use one charge to place a bridge + if (hero->useCharge()) { + level->placeBridge(pos_x, pos_y); + return true; + } + + // If hero doesn't have enough charges, we move Hero back + return false; +} + +/////////////////////////////////////// + +WallCell::WallCell(coordinate cell_x, coordinate cell_y, const sf::Color &color) : + Cell(cell_x, cell_y, color) +{} + +WallCell::~WallCell() +{} + +bool WallCell::onMovingTo(HeroPtr &hero, LevelPtr &level) +{ + UNUSED(hero) + UNUSED(level) + + // Hero never passes this cell. + return false; +} + +/////////////////////////////////////// + +ChargeCell::ChargeCell(coordinate cell_x, coordinate cell_y, int has_charges, const sf::Color &color) : + Cell(cell_x, cell_y, color), + cell_charges(has_charges) +{} + +ChargeCell::~ChargeCell() +{} + +bool ChargeCell::onMovingTo(HeroPtr &hero, LevelPtr &level) +{ + // Hero picks up the charge; remove it from the map + hero->refillCharges(cell_charges); + level->removeCharge(pos_x, pos_y); + + return true; +} + +/////////////////////////////////////// + +ExitCell::ExitCell(coordinate cell_x, coordinate cell_y, const sf::Color &color) : + Cell(cell_x, cell_y, color) +{} + +ExitCell::~ExitCell() +{} + +bool ExitCell::onMovingTo(HeroPtr &hero, LevelPtr &level) +{ + UNUSED(level) + + // Level is over. + hero->reachExit(); + return true; +} + +/////////////////////////////////////// + +TeleportCell::TeleportCell(coordinate cell_x, coordinate cell_y, coordinate new_cell_x, coordinate new_cell_y, const sf::Color &color) : + Cell(cell_x, cell_y, color), + new_x(new_cell_x), + new_y(new_cell_y) +{} + +TeleportCell::~TeleportCell() +{} + +bool TeleportCell::onMovingTo(HeroPtr &hero, LevelPtr &level) +{ + UNUSED(level) + + // Hero jumps into teleport! + hero->setPosition(new_x, new_y); + return true; +} diff --git a/src/cell.h b/src/cell.h new file mode 100644 index 0000000..9500939 --- /dev/null +++ b/src/cell.h @@ -0,0 +1,134 @@ +#ifndef CELL_H +#define CELL_H + +#include "entity.h" +#include +#include + +class Hero; +class Level; + +using HeroPtr = std::unique_ptr; +using LevelPtr = std::unique_ptr; + +/////////////////////////////////////// + +/// Represents interface for all level cells +class Cell : public Entity +{ +protected: + sf::Color cell_color; + +public: + Cell(coordinate cell_x = 0, + coordinate cell_y = 0, + const sf::Color &color = sf::Color::White); + + virtual ~Cell() = 0; + + inline sf::Color color() const noexcept; + + /// Determine if Hero can move onto this cell or not + virtual bool onMovingTo(HeroPtr &hero, LevelPtr &level) = 0; +}; + +/////////////////////////////////////// + +/// Any cell where Hero is free to move +class PassableCell : public Cell +{ +public: + PassableCell(coordinate cell_x = 0, + coordinate cell_y = 0, // Brown + const sf::Color &color = sf::Color(165, 42, 42)); + + virtual ~PassableCell() override; + + virtual bool onMovingTo(HeroPtr &hero, LevelPtr &level) override; +}; + +/////////////////////////////////////// + +/// A cell which requires Hero to spend a charge for bridge to move on +class WaterCell : public Cell +{ +public: + WaterCell(coordinate cell_x = 0, + coordinate cell_y = 0, + const sf::Color &color = sf::Color::Blue); + + virtual ~WaterCell() override; + + virtual bool onMovingTo(HeroPtr &hero, LevelPtr &level) override; +}; + +/////////////////////////////////////// + +/// A cell which is impossible to move on +class WallCell : public Cell +{ +public: + WallCell(coordinate cell_x = 0, + coordinate cell_y = 0, // Gray + const sf::Color &color = sf::Color(125, 125, 125)); + + virtual ~WallCell() override; + + virtual bool onMovingTo(HeroPtr &hero, LevelPtr &level) override; +}; + +/////////////////////////////////////// + +/// A cell which gives hero a charge +class ChargeCell : public Cell +{ +private: + int cell_charges; + +public: + ChargeCell(coordinate cell_x = 0, + coordinate cell_y = 0, + int has_charges = 1, + const sf::Color &color = sf::Color::Green); + + virtual ~ChargeCell() override; + + virtual bool onMovingTo(HeroPtr &hero, LevelPtr &level) override; +}; + +/////////////////////////////////////// + +/// A cell which moves hero to next level +class ExitCell : public Cell +{ +public: + ExitCell(coordinate cell_x = 0, + coordinate cell_y = 0, + const sf::Color &color = sf::Color::Red); + + virtual ~ExitCell() override; + + virtual bool onMovingTo(HeroPtr &hero, LevelPtr &level) override; +}; + +/////////////////////////////////////// + +/// A cell which teleports hero to following coordinates +class TeleportCell : public Cell +{ +private: + coordinate new_x, new_y; + +public: + TeleportCell(coordinate cell_x = 0, + coordinate cell_y = 0, + coordinate new_cell_x = 0, + coordinate new_cell_y = 0, // Purple + const sf::Color &color = sf::Color(128, 0, 128)); + + virtual ~TeleportCell() override; + + virtual bool onMovingTo(HeroPtr &hero, LevelPtr &level) override; +}; + +#endif // CELL_H diff --git a/src/entity.h b/src/entity.h new file mode 100644 index 0000000..cc69958 --- /dev/null +++ b/src/entity.h @@ -0,0 +1,46 @@ +#ifndef ENTITY_H +#define ENTITY_H + +using coordinate = unsigned int; + +/// Interface representing entity which can be placed on the map +class Entity +{ +protected: + coordinate pos_x, pos_y; + +public: + Entity(coordinate _x = 0, coordinate _y = 0) : + pos_x(_x), pos_y(_y) + {} + + virtual ~Entity() = 0; + + /// Get current Entity position + void position(coordinate &x, coordinate &y) const noexcept + { + x = pos_x; + y = pos_y; + } + + /// Set Entity position explicitly + void setPosition(coordinate x, coordinate y) + { + pos_x = x; + pos_y = y; + } + + /// Get current x of the Entity position + coordinate x() const noexcept + { + return pos_x; + } + + /// Get current y of the Entity position + coordinate y() const noexcept + { + return pos_y; + } +}; + +#endif // ENTITY_H diff --git a/src/game.cpp b/src/game.cpp index 1a5e0ed..4dab395 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -101,41 +101,11 @@ void Game::onMoving(sf::Keyboard::Key &key) ////////////////////////// - switch (level->cellOfType(attempt_x, attempt_y)) - { - case CellType::Water: - - // Try to use one charge to place a bridge - if (hero->useCharge()) - level->placeBridge(attempt_x, attempt_y); - // If hero doesn't have enough charges, we move Hero back - else - hero->setPosition(initial_x, initial_y); - - break; - - case CellType::Charge: - - // Hero picks up the charge; remove it from the map - hero->refillCharges(1); - level->removeCharge(attempt_x, attempt_y); - - break; - - case CellType::Exit: - - // Hero exists the level! - loadLevel(++current_level); - - break; - - case CellType::Wall: - - // You can't pass through wall. Hero goes back to inital coordinates + if (!level->mapArray()[attempt_x][attempt_y]->onMovingTo(hero, level)) hero->setPosition(initial_x, initial_y); - break; - } + if (hero->onExit()) + loadLevel(++current_level); } @@ -165,30 +135,10 @@ void Game::renderMap() // Draw map from 2D array for (const Row &row : map) { - for (const CellType &cell : row) + for (const std::unique_ptr &cell : row) { rectangle_brush.setPosition(painter_y, painter_x); - switch (cell) - { - case CellType::Ground: - rectangle_brush.setFillColor(sf::Color(165, 42, 42)); // Brown - break; - case CellType::Charge: - rectangle_brush.setFillColor(sf::Color::Green); - break; - case CellType::Bridge: - rectangle_brush.setFillColor(sf::Color::Black); - break; - case CellType::Exit: - rectangle_brush.setFillColor(sf::Color::Red); - break; - case CellType::Wall: - rectangle_brush.setFillColor(sf::Color(60, 60, 60)); // Gray - break; - case CellType::Water: - default: - rectangle_brush.setFillColor(sf::Color::Blue); - } + rectangle_brush.setFillColor(cell->color()); main_window.draw(rectangle_brush); @@ -214,14 +164,12 @@ void Game::renderMap() void Game::loadLevel(int level_index) { - Map map; + Map &map = level->mapArray(); // Fill the level with water - for (Row &row : map) - { - for (CellType &cell : row) - cell = CellType::Water; - } + for (coordinate x = 0; x < side; ++x) + for (coordinate y = 0; y < side; ++y) + map[x][y] = std::make_unique(x, y); switch (level_index) { @@ -229,51 +177,25 @@ void Game::loadLevel(int level_index) // Hardcoding is temporary! hero->setPosition(1, 1); hero->setCharges(2); - map[0][0] = CellType::Wall; - map[0][1] = CellType::Wall; - map[1][0] = CellType::Wall; - map[1][1] = CellType::Ground; - map[1][2] = CellType::Ground; - map[1][3] = CellType::Ground; - map[1][4] = CellType::Ground; - map[2][2] = CellType::Ground; - map[3][2] = CellType::Ground; - map[3][3] = CellType::Ground; - map[3][3] = CellType::Ground; - map[6][3] = CellType::Ground; - map[6][4] = CellType::Ground; - map[6][5] = CellType::Ground; - map[6][6] = CellType::Ground; - map[7][6] = CellType::Ground; - map[9][6] = CellType::Ground; - map[8][7] = CellType::Exit; - map[2][3] = CellType::Charge; - level->setMap(map); - break; - case 2: - // Hardcoding is temporary! - hero->setPosition(5, 5); - hero->setCharges(10); - map[5][6] = CellType::Ground; - map[5][5] = CellType::Ground; - map[5][4] = CellType::Ground; - map[4][5] = CellType::Ground; - map[4][6] = CellType::Ground; - map[4][4] = CellType::Ground; - map[6][6] = CellType::Ground; - map[6][5] = CellType::Ground; - map[6][4] = CellType::Ground; - map[6][7] = CellType::Ground; - map[6][8] = CellType::Ground; - map[5][8] = CellType::Ground; - map[8][8] = CellType::Ground; - map[8][9] = CellType::Ground; - map[8][10] = CellType::Exit; - map[8][11] = CellType::Wall; - map[7][11] = CellType::Wall; - map[4][3] = CellType::Wall; - map[4][7] = CellType::Charge; - level->setMap(map); + map[0][0] = std::make_unique(0, 0); + map[0][1] = std::make_unique(0, 1); + map[1][0] = std::make_unique(1, 0); + map[1][1] = std::make_unique(1, 1); + map[1][2] = std::make_unique(1, 2); + map[1][3] = std::make_unique(1, 3); + map[1][4] = std::make_unique(1, 4); + map[2][1] = std::make_unique(2, 1, 6, 6); + map[2][2] = std::make_unique(2, 2); + map[3][2] = std::make_unique(3, 2); + map[3][3] = std::make_unique(3, 3); + map[6][3] = std::make_unique(6, 3); + map[6][4] = std::make_unique(6, 4); + map[6][5] = std::make_unique(6, 5); + map[6][6] = std::make_unique(6, 6); + map[7][6] = std::make_unique(7, 6); + map[9][6] = std::make_unique(9, 6); + map[8][7] = std::make_unique(8, 7); + map[2][3] = std::make_unique(2, 3, 5); break; default: main_window.close(); diff --git a/src/hero.cpp b/src/hero.cpp index 53de84f..373590a 100644 --- a/src/hero.cpp +++ b/src/hero.cpp @@ -1,9 +1,8 @@ #include "hero.h" Hero::Hero(coordinate position_x, coordinate position_y, int initial_charges) : - hero_charges(initial_charges), - pos_x(position_x), - pos_y(position_y) + Entity(position_x, position_y), + hero_charges(initial_charges) {} void Hero::refillCharges(int append_charges) @@ -32,12 +31,6 @@ void Hero::setCharges(int charges) noexcept hero_charges = charges; } -void Hero::position(coordinate &x, coordinate &y) const noexcept -{ - x = pos_x; - y = pos_y; -} - void Hero::move(const Direction &direction) { switch (direction) @@ -55,8 +48,12 @@ void Hero::move(const Direction &direction) } } -void Hero::setPosition(coordinate x, coordinate y) +void Hero::reachExit() noexcept { - pos_x = x; - pos_y = y; + on_exit = true; +} + +bool Hero::onExit() const noexcept +{ + return on_exit; } diff --git a/src/hero.h b/src/hero.h index b40c7c7..9bacbe3 100644 --- a/src/hero.h +++ b/src/hero.h @@ -1,6 +1,8 @@ #ifndef HERO_H #define HERO_H +#include "entity.h" + enum class Direction { Left, @@ -10,14 +12,12 @@ enum class Direction None }; -using coordinate = unsigned int; - /// Represents a controlable by player game character -class Hero +class Hero : public Entity { private: int hero_charges; - coordinate pos_x, pos_y; + bool on_exit; public: explicit Hero(coordinate position_x = 0, coordinate position_y = 0, int initial_charges = 0); @@ -34,14 +34,14 @@ public: /// Set amount of hero charges explicitly void setCharges(int charges) noexcept; - /// Get current Hero position - void position(coordinate &x, coordinate &y) const noexcept; - - /// Set Hero position explicitly - void setPosition(coordinate x, coordinate y); - /// Move hero by one cell to any direction void move(const Direction &direction); + + /// Sets on_exit flag to true, game will load next level + void reachExit() noexcept; + + /// Are we exiting level? + bool onExit() const noexcept; }; #endif // HERO_H diff --git a/src/level.cpp b/src/level.cpp index 26e24a1..7dfdddc 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -5,25 +5,15 @@ Level::Level() void Level::placeBridge(coordinate x, coordinate y) { - map[x][y] = CellType::Bridge; -} - -CellType Level::cellOfType(coordinate x, coordinate y) const -{ - return map[x][y]; + map[x][y] = std::make_unique(x, y, sf::Color::Black); } void Level::removeCharge(coordinate x, coordinate y) { - map[x][y] = CellType::Ground; + map[x][y] = std::make_unique(x, y /* Brown Color */); } Map& Level::mapArray() { return map; } - -void Level::setMap(const Map &new_map) -{ - map = std::move(new_map); -} diff --git a/src/level.h b/src/level.h index 507a807..6fa26f2 100644 --- a/src/level.h +++ b/src/level.h @@ -1,23 +1,12 @@ #ifndef LEVEL_H #define LEVEL_H +#include "cell.h" #include -enum class CellType -{ - Water = '.', - Ground = '-', - Charge = '$', - Bridge = char(177), - Hero = '@', - Exit = '#', - Wall = 'X' -}; - -using coordinate = unsigned int; constexpr coordinate side = 32; -using Row = std::array; +using Row = std::array, side>; using Map = std::array; /// Abstraction over 2D array to quickly get access to level cells @@ -35,14 +24,8 @@ public: /// Get the 2D array of level map Map& mapArray(); - /// Returns type of requested map cell - CellType cellOfType(coordinate x, coordinate y) const; - /// Replace a charge cell with a ground cell void removeCharge(coordinate x, coordinate y); - - /// Set new map for the level - void setMap(const Map& new_map); }; #endif // LEVEL_H diff --git a/src/sfml-test.pro b/src/sfml-test.pro index d6b6401..5a69dba 100644 --- a/src/sfml-test.pro +++ b/src/sfml-test.pro @@ -4,12 +4,15 @@ CONFIG -= console app_bundle CONFIG -= qt SOURCES += \ + cell.cpp \ game.cpp \ hero.cpp \ level.cpp \ main.cpp HEADERS += \ + cell.h \ + entity.h \ game.h \ hero.h \ level.h