Global refactoring; add level builder

This commit is contained in:
oss 2020-03-16 00:57:39 +03:00
parent e8f06c3929
commit 6eac3c84f6
10 changed files with 261 additions and 167 deletions

View File

@ -3,10 +3,10 @@
#include "hero.h"
#include "level.h"
#define UNUSED(expr) (void)(expr);
#define UNUSED(expr) (void)(expr)
Cell::Cell(coordinate cell_x, coordinate cell_y, const sf::Color &color) :
Entity(cell_x, cell_y),
Cell::Cell(coordinate cell_row, coordinate cell_col, const sf::Color &color) :
Entity(cell_row, cell_col),
cell_color(color)
{}
@ -20,8 +20,8 @@ sf::Color Cell::color() const noexcept
///////////////////////////////////////
PassableCell::PassableCell(coordinate cell_x, coordinate cell_y, const sf::Color &color) :
Cell(cell_x, cell_y, color)
PassableCell::PassableCell(coordinate cell_row, coordinate cell_col, const sf::Color &color) :
Cell(cell_row, cell_col, color)
{}
PassableCell::~PassableCell()
@ -29,17 +29,21 @@ PassableCell::~PassableCell()
bool PassableCell::onMovingTo(HeroPtr &hero, LevelPtr &level)
{
UNUSED(hero)
UNUSED(level)
UNUSED(hero), UNUSED(level);
// Hero just moves on.
return true;
}
Cell *PassableCell::getDefaultInstance()
{
return new PassableCell();
}
///////////////////////////////////////
WaterCell::WaterCell(coordinate cell_x, coordinate cell_y, const sf::Color &color) :
Cell(cell_x, cell_y, color)
WaterCell::WaterCell(coordinate cell_row, coordinate cell_col, const sf::Color &color) :
Cell(cell_row, cell_col, color)
{}
WaterCell::~WaterCell()
@ -48,19 +52,21 @@ 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->useCharge())
level->placeBridge(entity_row, entity_col);
// If hero doesn't have enough charges, we move Hero back
return false;
}
Cell *WaterCell::getDefaultInstance()
{
return new WaterCell();
}
///////////////////////////////////////
WallCell::WallCell(coordinate cell_x, coordinate cell_y, const sf::Color &color) :
Cell(cell_x, cell_y, color)
WallCell::WallCell(coordinate cell_row, coordinate cell_col, const sf::Color &color) :
Cell(cell_row, cell_col, color)
{}
WallCell::~WallCell()
@ -68,17 +74,21 @@ WallCell::~WallCell()
bool WallCell::onMovingTo(HeroPtr &hero, LevelPtr &level)
{
UNUSED(hero)
UNUSED(level)
UNUSED(hero), UNUSED(level);
// Hero never passes this cell.
return false;
}
Cell *WallCell::getDefaultInstance()
{
return new WallCell();
}
///////////////////////////////////////
ChargeCell::ChargeCell(coordinate cell_x, coordinate cell_y, int has_charges, const sf::Color &color) :
Cell(cell_x, cell_y, color),
ChargeCell::ChargeCell(coordinate cell_row, coordinate cell_col, int has_charges, const sf::Color &color) :
Cell(cell_row, cell_col, color),
cell_charges(has_charges)
{}
@ -89,15 +99,20 @@ 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);
level->removeCharge(entity_row, entity_col);
return true;
}
Cell *ChargeCell::getDefaultInstance()
{
return new ChargeCell();
}
///////////////////////////////////////
ExitCell::ExitCell(coordinate cell_x, coordinate cell_y, const sf::Color &color) :
Cell(cell_x, cell_y, color)
ExitCell::ExitCell(coordinate cell_row, coordinate cell_col, const sf::Color &color) :
Cell(cell_row, cell_col, color)
{}
ExitCell::~ExitCell()
@ -105,19 +120,24 @@ ExitCell::~ExitCell()
bool ExitCell::onMovingTo(HeroPtr &hero, LevelPtr &level)
{
UNUSED(level)
UNUSED(level);
// Level is over.
hero->reachExit();
return true;
}
Cell *ExitCell::getDefaultInstance()
{
return new ExitCell();
}
///////////////////////////////////////
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(coordinate cell_row, coordinate cell_col, coordinate new_cell_row, coordinate new_cell_col, const sf::Color &color) :
Cell(cell_row, cell_col, color),
new_x(new_cell_row),
new_y(new_cell_col)
{}
TeleportCell::~TeleportCell()
@ -125,19 +145,24 @@ TeleportCell::~TeleportCell()
bool TeleportCell::onMovingTo(HeroPtr &hero, LevelPtr &level)
{
UNUSED(level)
UNUSED(level);
// Hero jumps into teleport!
hero->setPosition(new_x, new_y);
return true;
}
Cell *TeleportCell::getDefaultInstance()
{
return new TeleportCell();
}
///////////////////////////////////////
TriggerCell::TriggerCell(std::vector<CellPtr> &&cells_to_change, coordinate cell_x, coordinate cell_y, const sf::Color &color) :
Cell(cell_x, cell_y, color)
TriggerCell::TriggerCell(/*std::vector<CellPtr> &&cells_to_change,*/ coordinate cell_row, coordinate cell_col, const sf::Color &color) :
Cell(cell_row, cell_col, color)
{
cells = std::move(cells_to_change);
//cells = std::move(cells_to_change);
}
TriggerCell::~TriggerCell()
@ -145,20 +170,25 @@ TriggerCell::~TriggerCell()
bool TriggerCell::onMovingTo(HeroPtr &hero, LevelPtr &level)
{
UNUSED(hero)
UNUSED(hero);
Map &map = level->mapArray();
// We replace needed cells with the ones that the trigger provides.
for (CellPtr &cell : cells)
for (Cell *cell : cells)
{
const coordinate &y = cell->y();
const coordinate &x = cell->x();
const coordinate &row = cell->row();
const coordinate &col = cell->col();
map[x][y].release();
map[x][y] = std::move(cell);
delete map[row][col];
map[row][col] = cell;
}
// It's an impassable object, so player can't move to here.
return false;
}
Cell *TriggerCell::getDefaultInstance()
{
return new TriggerCell();
}

View File

@ -16,6 +16,18 @@ const sf::Color Blue = sf::Color(0, 255, 255);
const sf::Color Gray = sf::Color(125, 125, 125);
}
enum CELL_TYPES {
PASSABLE_CELL = 0,
WATER_CELL,
WALL_CELL,
CHARGE_CELL,
EXIT_CELL,
TELEPORT_CELL,
TRIGGER_CELL,
N_CELLS
};
///////////////////////////////////////
class Hero;
@ -24,7 +36,6 @@ class Cell;
using HeroPtr = std::unique_ptr<Hero>;
using LevelPtr = std::unique_ptr<Level>;
using CellPtr = std::unique_ptr<Cell>;
///////////////////////////////////////
@ -35,8 +46,8 @@ protected:
sf::Color cell_color;
public:
Cell(coordinate cell_x = 0,
coordinate cell_y = 0,
Cell(coordinate cell_row = 0,
coordinate cell_col = 0,
const sf::Color &color = palette::White);
virtual ~Cell() override;
@ -45,6 +56,8 @@ public:
/// Determine if Hero can move onto this cell or not
virtual bool onMovingTo(HeroPtr &hero, LevelPtr &level) = 0;
virtual Cell *getDefaultInstance() = 0;
};
///////////////////////////////////////
@ -53,13 +66,15 @@ public:
class PassableCell : public Cell
{
public:
PassableCell(coordinate cell_x = 0,
coordinate cell_y = 0, // Brown
PassableCell(coordinate cell_row = 0,
coordinate cell_col = 0, // Brown
const sf::Color &color = palette::Brown);
virtual ~PassableCell() override;
virtual bool onMovingTo(HeroPtr &hero, LevelPtr &level) override;
virtual Cell *getDefaultInstance() override;
};
///////////////////////////////////////
@ -68,13 +83,15 @@ public:
class WaterCell : public Cell
{
public:
WaterCell(coordinate cell_x = 0,
coordinate cell_y = 0,
WaterCell(coordinate cell_row = 0,
coordinate cell_col = 0,
const sf::Color &color = palette::Blue);
virtual ~WaterCell() override;
virtual bool onMovingTo(HeroPtr &hero, LevelPtr &level) override;
virtual Cell *getDefaultInstance() override;
};
///////////////////////////////////////
@ -83,13 +100,15 @@ public:
class WallCell : public Cell
{
public:
WallCell(coordinate cell_x = 0,
coordinate cell_y = 0, // Gray
const sf::Color &color = );
WallCell(coordinate cell_row = 0,
coordinate cell_col = 0, // Gray
const sf::Color &color = palette::Gray);
virtual ~WallCell() override;
virtual bool onMovingTo(HeroPtr &hero, LevelPtr &level) override;
virtual Cell *getDefaultInstance() override;
};
///////////////////////////////////////
@ -101,14 +120,16 @@ private:
int cell_charges;
public:
ChargeCell(coordinate cell_x = 0,
coordinate cell_y = 0,
ChargeCell(coordinate cell_row = 0,
coordinate cell_col = 0,
int has_charges = 1,
const sf::Color &color = sf::Color::Green);
virtual ~ChargeCell() override;
virtual bool onMovingTo(HeroPtr &hero, LevelPtr &level) override;
virtual Cell *getDefaultInstance() override;
};
///////////////////////////////////////
@ -117,13 +138,15 @@ public:
class ExitCell : public Cell
{
public:
ExitCell(coordinate cell_x = 0,
coordinate cell_y = 0,
ExitCell(coordinate cell_row = 0,
coordinate cell_col = 0,
const sf::Color &color = sf::Color::Red);
virtual ~ExitCell() override;
virtual bool onMovingTo(HeroPtr &hero, LevelPtr &level) override;
virtual Cell *getDefaultInstance() override;
};
///////////////////////////////////////
@ -135,15 +158,17 @@ 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
TeleportCell(coordinate cell_row = 0,
coordinate cell_col = 0,
coordinate new_cell_row = 0,
coordinate new_cell_col = 0, // Purple
const sf::Color &color = sf::Color(128, 0, 128));
virtual ~TeleportCell() override;
virtual bool onMovingTo(HeroPtr &hero, LevelPtr &level) override;
virtual Cell *getDefaultInstance() override;
};
///////////////////////////////////////
@ -153,17 +178,19 @@ class TriggerCell : public Cell
{
private:
// Vector of cells to place on map
std::vector<CellPtr> cells;
std::vector<Cell *> cells;
public:
TriggerCell(std::vector<CellPtr> &&cells_to_change,
coordinate cell_x = 0,
coordinate cell_y = 0, // Pink
TriggerCell(//std::vector<CellPtr> &&cells_to_change,
coordinate cell_row = 0,
coordinate cell_col = 0, // Pink
const sf::Color &color = sf::Color(255, 192, 203));
virtual ~TriggerCell() override;
virtual bool onMovingTo(HeroPtr &hero, LevelPtr &level) override;
virtual Cell *getDefaultInstance() override;
};
#endif // CELL_H

View File

@ -1,31 +1,31 @@
#include "entity.h"
Entity::Entity(coordinate _x, coordinate _y) :
pos_x(_x), pos_y(_y)
Entity::Entity(coordinate _row, coordinate _col) :
entity_row(_row), entity_col(_col)
{}
Entity::~Entity()
{}
/// Get current Entity position
void Entity::position(coordinate &x, coordinate &y) const noexcept
void Entity::position(coordinate &row, coordinate &col) const noexcept
{
x = pos_x;
y = pos_y;
row = entity_row;
col = entity_col;
}
void Entity::setPosition(coordinate x, coordinate y)
void Entity::setPosition(coordinate row, coordinate col)
{
pos_x = x;
pos_y = y;
entity_row = row;
entity_col = col;
}
coordinate Entity::x() const noexcept
coordinate Entity::row() const noexcept
{
return pos_x;
return entity_row;
}
coordinate Entity::y() const noexcept
coordinate Entity::col() const noexcept
{
return pos_y;
return entity_col;
}

View File

@ -1,30 +1,30 @@
#ifndef ENTITY_H
#define ENTITY_H
using coordinate = unsigned int;
using coordinate = unsigned long;
/// Interface representing entity which can be placed on the map
class Entity
{
protected:
coordinate pos_x, pos_y;
coordinate entity_row, entity_col;
public:
Entity(coordinate _x = 0, coordinate _y = 0);
Entity(coordinate _row = 0, coordinate _col = 0);
virtual ~Entity() = 0;
/// Get current Entity position
void position(coordinate &x, coordinate &y) const noexcept;
void position(coordinate &row, coordinate &col) const noexcept;
/// Set Entity position explicitly
void setPosition(coordinate x, coordinate y);
void setPosition(coordinate row, coordinate col);
/// Get current x of the Entity position
coordinate x() const noexcept;
coordinate row() const noexcept;
/// Get current y of the Entity position
coordinate y() const noexcept;
coordinate col() const noexcept;
};
#endif // ENTITY_H

View File

@ -8,7 +8,7 @@ constexpr int cell_width = 60;
constexpr int cell_height = 35;
constexpr int cell_deviation = 25;
constexpr int window_side = cell_width * side;
constexpr int window_side = cell_width * 4;
Game::Game()
{
@ -18,11 +18,11 @@ Game::Game()
// Generate level
level = std::make_unique<Level>();
main_window.create(sf::VideoMode(window_side, window_side), "SFML-Test Application", sf::Style::Default);
main_window.create(sf::VideoMode(window_side * 2, window_side * 2), "SFML-Test Application", sf::Style::Default);
main_window.setActive();
current_level = 1;
loadLevel(current_level);
//loadLevel(current_level);
}
int Game::run()
@ -54,7 +54,6 @@ int Game::run()
}
////////////////////////////////////////////////////
Direction Game::getDirection(sf::Keyboard::Key &key) const
{
switch (key)
@ -93,24 +92,20 @@ void Game::onMoving(sf::Keyboard::Key &key)
return;
// Save the initial coordinates
coordinate initial_x, initial_y;
hero->position(initial_x, initial_y);
coordinate initial_row, initial_col;
hero->position(initial_row, initial_col);
// Try to move hero
hero->move(direction);
// Save the new coordinates after moving
coordinate attempt_x, attempt_y;
hero->position(attempt_x, attempt_y);
coordinate attempt_row, attempt_col;
hero->position(attempt_row, attempt_col);
//////////////////////////
if (!level->mapArray()[attempt_x][attempt_y]->onMovingTo(hero, level))
hero->setPosition(initial_x, initial_y);
if (hero->onExit())
loadLevel(++current_level);
if (!level->mapArray()[attempt_row][attempt_col]->onMovingTo(hero, level))
hero->setPosition(initial_row, initial_col);
}
void Game::renderMap()
@ -142,22 +137,22 @@ void Game::renderMap()
text.setString("Available bridge cells: " + std::to_string(hero->charges()));
// Where is hero
coordinate hero_x, hero_y;
hero->position(hero_x, hero_y);
coordinate hero_row, hero_col;
hero->position(hero_row, hero_col);
// Draw map from 2D array
for (coordinate x = 0; x < side; ++x)
for (coordinate x = 0; x < level->width(); ++x)
{
shift = side * cell_deviation;
shift = static_cast<float>(level->width()) * cell_deviation;
for (coordinate y = 0; y < side; ++y)
for (coordinate y = 0; y < level->height(); ++y)
{
convex_brush.setPosition(shift + painter_x, painter_y);
convex_brush.setFillColor(map[x][y]->color());
convex_brush.setFillColor(map[y][x]->color());
main_window.draw(convex_brush);
if (hero_x == x && hero_y == y)
if (hero_row == y && hero_col == x)
{
// Place the hero sprite
convex_brush.setFillColor(sf::Color::White);
@ -176,52 +171,3 @@ void Game::renderMap()
main_window.draw(text);
}
void Game::loadLevel(int level_index)
{
Map &map = level->mapArray();
// Fill the level with water
for (coordinate x = 0; x < side; ++x)
for (coordinate y = 0; y < side; ++y)
map[x][y] = std::make_unique<WaterCell>(x, y);
std::vector<CellPtr> trigger_cells;
trigger_cells.emplace_back(std::make_unique<PassableCell>(0, 0));
trigger_cells.emplace_back(std::make_unique<PassableCell>(1, 0));
trigger_cells.emplace_back(std::make_unique<PassableCell>(0, 1));
switch (level_index)
{
case 1:
// Hardcoding is temporary!
hero->setPosition(1, 1);
hero->setCharges(2);
level->setDefaultGroundColor(sf::Color(165, 42, 42));
map[0][0] = std::make_unique<WallCell>(0, 0);
map[0][1] = std::make_unique<WallCell>(0, 1);
map[1][0] = std::make_unique<WallCell>(1, 0);
map[1][1] = std::make_unique<PassableCell>(1, 1);
map[1][2] = std::make_unique<PassableCell>(1, 2);
map[1][3] = std::make_unique<PassableCell>(1, 3);
map[1][4] = std::make_unique<PassableCell>(1, 4);
map[2][1] = std::make_unique<TeleportCell>(2, 1, 6, 6);
map[2][2] = std::make_unique<PassableCell>(2, 2);
map[3][2] = std::make_unique<PassableCell>(3, 2);
map[3][3] = std::make_unique<PassableCell>(3, 3);
map[6][3] = std::make_unique<PassableCell>(6, 3);
map[6][4] = std::make_unique<PassableCell>(6, 4);
map[6][5] = std::make_unique<PassableCell>(6, 5);
map[6][6] = std::make_unique<PassableCell>(6, 6);
map[7][6] = std::make_unique<PassableCell>(7, 6);
map[9][6] = std::make_unique<PassableCell>(9, 6);
map[8][7] = std::make_unique<ExitCell>(8, 7);
map[2][3] = std::make_unique<ChargeCell>(2, 3, 5);
map[3][3] = std::make_unique<TriggerCell>(std::move(trigger_cells), 3, 3);
break;
default:
main_window.close();
}
}

View File

@ -33,7 +33,7 @@ private:
void renderMap();
/// Prepare map and hero for a game level
void loadLevel(int level_index = 1);
//void loadLevel(int level_index = 1);
public:
explicit Game();

View File

@ -1,7 +1,7 @@
#include "hero.h"
Hero::Hero(coordinate position_x, coordinate position_y, int initial_charges) :
Entity(position_x, position_y),
Hero::Hero(coordinate row, coordinate col, int initial_charges) :
Entity(row, col),
hero_charges(initial_charges),
on_exit(false)
{}
@ -37,13 +37,13 @@ void Hero::move(const Direction &direction)
switch (direction)
{
case Direction::Up:
--pos_y; break;
--entity_row; break;
case Direction::Down:
++pos_y; break;
++entity_row; break;
case Direction::Left:
--pos_x; break;
--entity_col; break;
case Direction::Right:
++pos_x; break;
++entity_col; break;
case Direction::None:
break;
}

View File

@ -20,7 +20,7 @@ private:
bool on_exit;
public:
explicit Hero(coordinate position_x = 0, coordinate position_y = 0, int initial_charges = 0);
explicit Hero(coordinate row = 0, coordinate col = 0, int initial_charges = 0);
/// Add more charges for hero to use
void refillCharges(int append_charges);

View File

@ -1,16 +1,98 @@
#include "level.h"
Level::Level()
{}
#include <fstream>
#include <cstring>
void Level::placeBridge(coordinate x, coordinate y)
void Level::prepareCellInstances()
{
map[x][y] = std::make_unique<PassableCell>(x, y, sf::Color::Black);
default_cells[PASSABLE_CELL] = new PassableCell();
default_cells[WATER_CELL] = new WaterCell();
default_cells[WALL_CELL] = new WallCell();
default_cells[CHARGE_CELL] = new ChargeCell();
default_cells[EXIT_CELL] = new ExitCell();
default_cells[TELEPORT_CELL] = new TeleportCell();
default_cells[TRIGGER_CELL] = new TriggerCell();
}
void Level::removeCharge(coordinate x, coordinate y)
void Level::readMap(std::ifstream &file)
{
map[x][y] = std::make_unique<PassableCell>(x, y, color_ground);
int i;
for (coordinate j = 0; j < map.size(); ++j)
{
for (coordinate k = 0; k < map[j].size(); ++k)
{
file >> i;
map[j][k] = default_cells[i]->getDefaultInstance();
map[j][k]->setPosition(j, k);
}
}
}
Level::Level(const std::string &map_file)
{
prepareCellInstances();
std::ifstream file;
file.open(map_file);
std::string cur_line;
while (getline(file, cur_line))
{
// need fix; see std::string.compare
if (strstr(cur_line.data(), "size") != NULL)
{
file >> level_width >> level_height;
map.resize(level_height);
for (Row &row : map)
row.resize(level_width);
}
else if (strstr(cur_line.data(), "map") != NULL)
{
readMap(file);
}
else if (strstr(cur_line.data(), "teleport") != NULL)
{
coordinate src_row, src_col;
coordinate dest_row, dest_col;
file >> src_row >> src_col >> dest_row >> dest_col;
// reinterpret_cast<TeleportCell *>(map[src_row][src_col])->setDestination(dest_row, dest_col);
}
}
}
Level::~Level()
{
for (Cell *cell : default_cells)
delete cell;
for (Row &row : map)
for (Cell *cell : row)
delete cell;
}
size_t Level::width() const
{
return level_width;
}
size_t Level::height() const
{
return level_height;
}
void Level::placeBridge(coordinate row, coordinate col)
{
Cell *buf = map[row][col];
map[row][col] = new PassableCell(row, col, sf::Color::Black);
delete buf;
}
void Level::removeCharge(coordinate row, coordinate col)
{
Cell *buf = map[row][col];
map[row][col] = new PassableCell(row, col, color_ground);
delete buf;
}
Map& Level::mapArray()

View File

@ -4,10 +4,10 @@
#include <array>
#include "cell.h"
constexpr coordinate side = 32;
const std::string default_file_name = "test_map";
using Row = std::array<CellPtr, side>;
using Map = std::array<Row, side>;
using Row = std::vector<Cell *>;
using Map = std::vector<Row>;
/// Abstraction over 2D array to quickly get access to level cells
class Level
@ -15,9 +15,18 @@ class Level
private:
Map map;
sf::Color color_ground;
size_t level_width, level_height;
std::array<Cell *, N_CELLS> default_cells;
void prepareCellInstances();
void readMap(std::ifstream &file);
public:
Level();
Level(const std::string &map_file = default_file_name);
~Level();
size_t width() const;
size_t height() const;
/// Place a bridge cell
void placeBridge(coordinate x, coordinate y);