Refactor cells structure

Now each cell is an object of a specific class representing logic and behavior of how game should behave when player tries to move onto the following cell. Also add TeleportCell
This commit is contained in:
NaiJi ✨ 2020-03-02 19:04:17 +03:00
parent 8823543d4e
commit f24193b309
9 changed files with 364 additions and 159 deletions

130
src/cell.cpp Normal file
View File

@ -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;
}

134
src/cell.h Normal file
View File

@ -0,0 +1,134 @@
#ifndef CELL_H
#define CELL_H
#include "entity.h"
#include <memory>
#include <SFML/Graphics/Color.hpp>
class Hero;
class Level;
using HeroPtr = std::unique_ptr<Hero>;
using LevelPtr = std::unique_ptr<Level>;
///////////////////////////////////////
/// 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

46
src/entity.h Normal file
View File

@ -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

View File

@ -101,42 +101,12 @@ void Game::onMoving(sf::Keyboard::Key &key)
////////////////////////// //////////////////////////
switch (level->cellOfType(attempt_x, attempt_y)) if (!level->mapArray()[attempt_x][attempt_y]->onMovingTo(hero, level))
{
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); hero->setPosition(initial_x, initial_y);
break; if (hero->onExit())
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); loadLevel(++current_level);
break;
case CellType::Wall:
// You can't pass through wall. Hero goes back to inital coordinates
hero->setPosition(initial_x, initial_y);
break;
}
} }
void Game::renderMap() void Game::renderMap()
@ -165,30 +135,10 @@ void Game::renderMap()
// Draw map from 2D array // Draw map from 2D array
for (const Row &row : map) for (const Row &row : map)
{ {
for (const CellType &cell : row) for (const std::unique_ptr<Cell> &cell : row)
{ {
rectangle_brush.setPosition(painter_y, painter_x); rectangle_brush.setPosition(painter_y, painter_x);
switch (cell) rectangle_brush.setFillColor(cell->color());
{
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);
}
main_window.draw(rectangle_brush); main_window.draw(rectangle_brush);
@ -214,14 +164,12 @@ void Game::renderMap()
void Game::loadLevel(int level_index) void Game::loadLevel(int level_index)
{ {
Map map; Map &map = level->mapArray();
// Fill the level with water // Fill the level with water
for (Row &row : map) for (coordinate x = 0; x < side; ++x)
{ for (coordinate y = 0; y < side; ++y)
for (CellType &cell : row) map[x][y] = std::make_unique<WaterCell>(x, y);
cell = CellType::Water;
}
switch (level_index) switch (level_index)
{ {
@ -229,51 +177,25 @@ void Game::loadLevel(int level_index)
// Hardcoding is temporary! // Hardcoding is temporary!
hero->setPosition(1, 1); hero->setPosition(1, 1);
hero->setCharges(2); hero->setCharges(2);
map[0][0] = CellType::Wall; map[0][0] = std::make_unique<WallCell>(0, 0);
map[0][1] = CellType::Wall; map[0][1] = std::make_unique<WallCell>(0, 1);
map[1][0] = CellType::Wall; map[1][0] = std::make_unique<WallCell>(1, 0);
map[1][1] = CellType::Ground; map[1][1] = std::make_unique<PassableCell>(1, 1);
map[1][2] = CellType::Ground; map[1][2] = std::make_unique<PassableCell>(1, 2);
map[1][3] = CellType::Ground; map[1][3] = std::make_unique<PassableCell>(1, 3);
map[1][4] = CellType::Ground; map[1][4] = std::make_unique<PassableCell>(1, 4);
map[2][2] = CellType::Ground; map[2][1] = std::make_unique<TeleportCell>(2, 1, 6, 6);
map[3][2] = CellType::Ground; map[2][2] = std::make_unique<PassableCell>(2, 2);
map[3][3] = CellType::Ground; map[3][2] = std::make_unique<PassableCell>(3, 2);
map[3][3] = CellType::Ground; map[3][3] = std::make_unique<PassableCell>(3, 3);
map[6][3] = CellType::Ground; map[6][3] = std::make_unique<PassableCell>(6, 3);
map[6][4] = CellType::Ground; map[6][4] = std::make_unique<PassableCell>(6, 4);
map[6][5] = CellType::Ground; map[6][5] = std::make_unique<PassableCell>(6, 5);
map[6][6] = CellType::Ground; map[6][6] = std::make_unique<PassableCell>(6, 6);
map[7][6] = CellType::Ground; map[7][6] = std::make_unique<PassableCell>(7, 6);
map[9][6] = CellType::Ground; map[9][6] = std::make_unique<PassableCell>(9, 6);
map[8][7] = CellType::Exit; map[8][7] = std::make_unique<ExitCell>(8, 7);
map[2][3] = CellType::Charge; map[2][3] = std::make_unique<ChargeCell>(2, 3, 5);
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);
break; break;
default: default:
main_window.close(); main_window.close();

View File

@ -1,9 +1,8 @@
#include "hero.h" #include "hero.h"
Hero::Hero(coordinate position_x, coordinate position_y, int initial_charges) : Hero::Hero(coordinate position_x, coordinate position_y, int initial_charges) :
hero_charges(initial_charges), Entity(position_x, position_y),
pos_x(position_x), hero_charges(initial_charges)
pos_y(position_y)
{} {}
void Hero::refillCharges(int append_charges) void Hero::refillCharges(int append_charges)
@ -32,12 +31,6 @@ void Hero::setCharges(int charges) noexcept
hero_charges = charges; hero_charges = charges;
} }
void Hero::position(coordinate &x, coordinate &y) const noexcept
{
x = pos_x;
y = pos_y;
}
void Hero::move(const Direction &direction) void Hero::move(const Direction &direction)
{ {
switch (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; on_exit = true;
pos_y = y; }
bool Hero::onExit() const noexcept
{
return on_exit;
} }

View File

@ -1,6 +1,8 @@
#ifndef HERO_H #ifndef HERO_H
#define HERO_H #define HERO_H
#include "entity.h"
enum class Direction enum class Direction
{ {
Left, Left,
@ -10,14 +12,12 @@ enum class Direction
None None
}; };
using coordinate = unsigned int;
/// Represents a controlable by player game character /// Represents a controlable by player game character
class Hero class Hero : public Entity
{ {
private: private:
int hero_charges; int hero_charges;
coordinate pos_x, pos_y; bool on_exit;
public: public:
explicit Hero(coordinate position_x = 0, coordinate position_y = 0, int initial_charges = 0); 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 /// Set amount of hero charges explicitly
void setCharges(int charges) noexcept; 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 /// Move hero by one cell to any direction
void move(const Direction &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 #endif // HERO_H

View File

@ -5,25 +5,15 @@ Level::Level()
void Level::placeBridge(coordinate x, coordinate y) void Level::placeBridge(coordinate x, coordinate y)
{ {
map[x][y] = CellType::Bridge; map[x][y] = std::make_unique<PassableCell>(x, y, sf::Color::Black);
}
CellType Level::cellOfType(coordinate x, coordinate y) const
{
return map[x][y];
} }
void Level::removeCharge(coordinate x, coordinate y) void Level::removeCharge(coordinate x, coordinate y)
{ {
map[x][y] = CellType::Ground; map[x][y] = std::make_unique<PassableCell>(x, y /* Brown Color */);
} }
Map& Level::mapArray() Map& Level::mapArray()
{ {
return map; return map;
} }
void Level::setMap(const Map &new_map)
{
map = std::move(new_map);
}

View File

@ -1,23 +1,12 @@
#ifndef LEVEL_H #ifndef LEVEL_H
#define LEVEL_H #define LEVEL_H
#include "cell.h"
#include <array> #include <array>
enum class CellType
{
Water = '.',
Ground = '-',
Charge = '$',
Bridge = char(177),
Hero = '@',
Exit = '#',
Wall = 'X'
};
using coordinate = unsigned int;
constexpr coordinate side = 32; constexpr coordinate side = 32;
using Row = std::array<CellType, side>; using Row = std::array<std::unique_ptr<Cell>, side>;
using Map = std::array<Row, side>; using Map = std::array<Row, side>;
/// Abstraction over 2D array to quickly get access to level cells /// Abstraction over 2D array to quickly get access to level cells
@ -35,14 +24,8 @@ public:
/// Get the 2D array of level map /// Get the 2D array of level map
Map& mapArray(); Map& mapArray();
/// Returns type of requested map cell
CellType cellOfType(coordinate x, coordinate y) const;
/// Replace a charge cell with a ground cell /// Replace a charge cell with a ground cell
void removeCharge(coordinate x, coordinate y); void removeCharge(coordinate x, coordinate y);
/// Set new map for the level
void setMap(const Map& new_map);
}; };
#endif // LEVEL_H #endif // LEVEL_H

View File

@ -4,12 +4,15 @@ CONFIG -= console app_bundle
CONFIG -= qt CONFIG -= qt
SOURCES += \ SOURCES += \
cell.cpp \
game.cpp \ game.cpp \
hero.cpp \ hero.cpp \
level.cpp \ level.cpp \
main.cpp main.cpp
HEADERS += \ HEADERS += \
cell.h \
entity.h \
game.h \ game.h \
hero.h \ hero.h \
level.h level.h