Skip to content
Snippets Groups Projects
Commit 0901d134 authored by PierreEVEN's avatar PierreEVEN
Browse files

added missing files (removed with refactor)

parent 8db678bd
Branches
No related merge requests found
File added
#include "cell.hpp"
#include "engine/logger.hpp"
#include "engine/vector2.hpp"
namespace pm
{
double Cell::draw_scale = 1.;
void Cell::set_pos(const Vector2I& in_pos)
{
pos = in_pos;
}
void Cell::set_item(EItemType in_item_type)
{
type = ECellType::Item;
item_type = in_item_type;
}
void Cell::set_wall(WallMask in_wall_mask, WallMask in_wall_mask_neg)
{
type = ECellType::Wall;
wall_masks = {in_wall_mask, in_wall_mask_neg < 0 ? wall_masks.neg : in_wall_mask_neg};
}
void Cell::set_gum(bool big)
{
type = big ? ECellType::BiGum : ECellType::Gum;
}
void Cell::set_door()
{
type = ECellType::Door;
}
ECellType Cell::get_type() const
{
return type;
}
EItemType Cell::get_item() const
{
if (type != ECellType::Item)
FATAL("this cell is not an item");
return item_type;
}
void Cell::update_type(const ECellType new_type)
{
type = new_type;
}
void Cell::update_sprite_handle(
const std::unordered_map<ECellType, SpriteHandle>& cell_sprite_handles,
const std::unordered_map<EItemType, SpriteHandle>& item_sprite_handles,
const std::array<SpriteHandle, 16>& wall_sprite_handles)
{
switch (type)
{
case ECellType::Wall:
sprite_handle = wall_sprite_handles[wall_masks.pos & ~wall_masks.neg];
break;
case ECellType::Item:
sprite_handle = item_sprite_handles.find(item_type)->second;
break;
default:
sprite_handle = cell_sprite_handles.find(type)->second;
break;
}
}
void Cell::draw(int32_t terrain_unit_scale, SDL_Surface* surface_override) const
{
if (sprite_handle)
{
Vector2I draw_pos{pos};
draw_pos *= terrain_unit_scale * static_cast<int32_t>(draw_scale);
const double ds = draw_scale * (1 + (type == ECellType::Wall));
sprite_handle.draw(draw_pos, ds, ds, surface_override);
}
}
Cell Cell::from_char(char chr)
{
Cell cell;
switch (chr)
{
case '.':
return cell;
case '#':
cell.set_wall(WALL_MASK_FULL, 0);
break;
case '^':
cell.set_wall(WALL_MASK_FULL, WALL_MASK_SOUTH);
break;
case '<':
cell.set_wall(WALL_MASK_FULL, WALL_MASK_EAST);
break;
case '>':
cell.set_wall(WALL_MASK_FULL, WALL_MASK_WEST);
break;
case 'v':
cell.set_wall(WALL_MASK_FULL, WALL_MASK_NORTH);
break;
case 'o':
cell.set_gum(false);
break;
case 'G':
cell.set_gum(true);
break;
case '-':
cell.set_door();
break;
default:
cell.set_item(EItemType::Abricot);
}
return cell;
}
}
#pragma once
#include "engine/sprite_sheet.hpp"
#include "engine/vector2.hpp"
#include <array>
#include <unordered_map>
namespace pm
{
/*
* This is the representation of a cell of pacman game's terrain
*/
enum class ECellType
{
Void,
Wall,
Gum,
Item,
BiGum,
Door,
};
// Specialized item type informations
enum class EItemType
{
Cherry,
Strawberry,
Abricot,
Apple,
Grapes,
Galaxian,
Bell,
Key
};
class Cell
{
public:
static double draw_scale;
using WallMask = int32_t;
static constexpr WallMask WALL_MASK_NORTH = 0b0001;
static constexpr WallMask WALL_MASK_WEST = 0b0010;
static constexpr WallMask WALL_MASK_EAST = 0b0100;
static constexpr WallMask WALL_MASK_SOUTH = 0b1000;
static constexpr WallMask WALL_MASK_FULL = 0b1111;
public:
static Cell from_char(char chr);
Cell() = default;
void set_pos(const Vector2I& in_pos);
void set_item(EItemType in_item_type);
void set_wall(WallMask in_wall_mask, WallMask in_wall_mask_neg = -1);
void set_gum(bool big);
void set_door();
[[nodiscard]] ECellType get_type() const;
[[nodiscard]] EItemType get_item() const;
void update_type(const ECellType new_type);
void update_sprite_handle(
const std::unordered_map<ECellType, SpriteHandle>& cell_sprite_handles,
const std::unordered_map<EItemType, SpriteHandle>& item_sprite_handles,
const std::array<SpriteHandle, 16>& wall_sprite_handles);
void draw(int32_t terrain_unit_scale, SDL_Surface* surface_override = nullptr) const;
private:
SpriteHandle sprite_handle = {};
Vector2I pos{0, 0};
ECellType type = ECellType::Void;
class InternalWallMask
{
public:
WallMask pos = 0;
WallMask neg = 0;
};
EItemType item_type = EItemType::Cherry;
InternalWallMask wall_masks;
};
}
#include "entity.hpp"
#include "engine/logger.hpp"
pm::Entity::Entity(std::shared_ptr<Terrain> new_terrain)
: direction_sprite({}), looking_direction(Direction::NONE), terrain(std::move(new_terrain))
{
}
void pm::Entity::set_direction_sprite(const Direction direction, const SpriteHandle& new_sprite)
{
direction_sprite[direction.index()] = new_sprite;
direction_sprite[direction.index()]->set_paused(true);
}
void pm::Entity::set_look_direction(const Direction new_direction)
{
looking_direction = new_direction;
if (looking_direction == Direction::NONE)
direction_sprite[Direction::NONE.index()]->reset_timer();
}
void pm::Entity::draw()
{
if (hidden)
return;
auto sprite = direction_sprite[looking_direction.index()];
if (!sprite)
{
WARNING("missing sprite for direction %d", looking_direction.index());
return;
}
auto s = Cell::draw_scale;
sprite->draw(get_absolute_discrete_pos() * static_cast<int32_t>(s), s, s);
}
void pm::Entity::pause_animation(bool paused)
{
for (auto& anim : direction_sprite)
if (anim)
anim->set_paused(paused);
}
bool pm::Entity::is_at_intersection() const
{
if (looking_direction.is_vertical())
if (terrain->is_free(get_cell_discrete_pos() + Vector2I{1, 0}) || terrain->is_free(get_cell_discrete_pos() - Vector2I{1, 0}))
return true;
if (looking_direction.is_horizontal())
if (terrain->is_free(get_cell_discrete_pos() + Vector2I{0, 1}) || terrain->is_free(get_cell_discrete_pos() - Vector2I{0, 1}))
return true;
return false;
}
#pragma once
#include "engine/direction.hpp"
#include "engine/sprite_sheet.hpp"
#include "terrain.hpp"
#include "engine/vector2.hpp"
#include <array>
namespace pm
{
class Terrain;
/*
* Main class.
* Extend this to implement an object that will move on a given terrain.
* An entity have a position and a look direction and will draw the corresponding sprite.
*/
class Entity
{
public:
Entity(std::shared_ptr<Terrain> terrain);
virtual ~Entity() = default;
// Set the sprite used for the given direction. All the 5 directions (including NONE) should be initialized.
void set_direction_sprite(const Direction direction, const SpriteHandle& new_sprite);
virtual void draw();
virtual void tick()
{
}
/*
* There are different kind of coordinates :
* - Absolute mean a position in pixel on screen (1 unit per pixel)
* - Cell mean a position in grid space (1 unit per cell)
* - Discrete give the corresponding rounded position to closest integer value
* - Linear give the raw position (or eventually in cell space)
*/
void set_absolute_linear_position(const Vector2D& new_pos) { linear_pos = new_pos; }
void set_absolute_discrete_position(const Vector2I& new_pos) { linear_pos = new_pos.cast<Vector2D>(); }
void set_cell_discrete_pos(const Vector2I& new_pos) { linear_pos = new_pos.cast<Vector2D>() * terrain->get_unit_length(); }
void set_cell_linear_pos(const Vector2D& new_pos) { linear_pos = new_pos * terrain->get_unit_length(); }
[[nodiscard]] Vector2I get_absolute_discrete_pos() const { return {static_cast<int32_t>(linear_pos.x()), static_cast<int32_t>(linear_pos.y())}; }
[[nodiscard]] const Vector2D& get_absolute_linear_pos() const { return linear_pos; }
[[nodiscard]] Vector2I get_cell_discrete_pos() const { return (linear_pos / terrain->get_unit_length()).rounded().cast<Vector2I>(); }
[[nodiscard]] Vector2D get_cell_linear_pos() const { return linear_pos / terrain->get_unit_length(); }
// Set entity's direction. It is mainly used to select which sprite to render when draw is called.
virtual void set_look_direction(const Direction new_direction);
[[nodiscard]] virtual Direction get_look_direction() const { return looking_direction; }
// Return true if one of the neighbors cells is free
[[nodiscard]] bool is_at_intersection() const;
[[nodiscard]] Terrain& get_terrain() const { return *terrain; }
// Hide entity
void hide(bool in_hide) { hidden = in_hide; }
// Reset entity to it's default state and position when game start
virtual void reset()
{
}
// Pause animations
void pause_animation(bool paused);
protected:
bool hidden = false;
private:
std::array<std::optional<SpriteHandle>, 5> direction_sprite;
Direction looking_direction;
std::shared_ptr<Terrain> terrain;
// Entity position is internally handled using double coordinates.
Vector2D linear_pos;
};
}
#include "pathfinding.hpp"
#include "engine/logger.hpp"
#include "terrain.hpp"
#include <unordered_set>
namespace pm
{
PathFinder::PathFinder(const std::shared_ptr<Terrain>& in_terrain)
: terrain(in_terrain)
{
if (!in_terrain)
FATAL("invalid terrain");
}
bool PathFinder::find_path(const Vector2I& from, Vector2I to)
{
to = terrain->closest_free_point(to);
// Using same path than last search
if (!actual_path.empty() && from == actual_path.front() && to == actual_path.back())
return true;
// Rebuilding new path
actual_path.clear();
// Ensure source is not in a wall
if (!terrain->is_free(from))
return false;
// Case source = target
if (from == to)
{
actual_path = {to};
return true;
}
const uint32_t width = terrain->get_width();
const uint32_t height = terrain->get_height();
std::vector distance_map(width * height, std::make_pair(UINT32_MAX, to));
std::unordered_set study_points = {from};
const std::vector<Vector2I> neighbors = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
distance_map[from.x() + from.y() * width] = std::make_pair(0, from);
const auto index_of = [width](const Vector2I& pt) { return pt.x() + pt.y() * width; };
const auto point_valid = [width, height](const Vector2I& pt) { return pt.x() >= 0 && pt.y() >= 0 && pt.x() < static_cast<int>(width) && pt.y() < static_cast<int>(height); };
// A* body
do
{
const auto old_sp = study_points;
study_points.clear();
for (const auto& study_point : old_sp)
{
for (const auto& n : neighbors)
{
const auto tested_neighbor = study_point + n;
if (tested_neighbor == to)
{
distance_map[index_of(to)] = std::make_pair(distance_map[index_of(study_point)].first + 1, study_point);
// Find path to root;
Vector2I current_rewind_pos = to;
while (current_rewind_pos != from)
{
actual_path.emplace_back(current_rewind_pos);
Vector2I min_dir = {};
uint32_t min_dir_val = distance_map[index_of(current_rewind_pos)].first;
for (const auto neighbor : neighbors)
{
const auto rev_test_point = current_rewind_pos + neighbor;
if (!point_valid(rev_test_point))
continue;
if (distance_map[index_of(rev_test_point)].first < min_dir_val)
{
min_dir_val = distance_map[index_of(rev_test_point)].first;
min_dir = distance_map[index_of(rev_test_point)].second;
}
}
if (min_dir == Vector2I{})
{
ERROR("failed to rewind pathfinding pos");
actual_path.clear();
return false;
}
current_rewind_pos = min_dir;
}
actual_path.emplace_back(from);
return true;
}
if (terrain->is_free(tested_neighbor))
{
const auto min = std::min(distance_map[index_of(tested_neighbor)].first, distance_map[index_of(study_point)].first + 1);
if (distance_map[index_of(tested_neighbor)].first > min)
{
distance_map[index_of(tested_neighbor)] = std::make_pair(min, study_point);
study_points.insert(tested_neighbor);
}
}
}
}
} while (!study_points.empty());
return false;
}
Direction PathFinder::direction_to_next_point(const Vector2I& current_location)
{
if (actual_path.empty())
return Direction::NONE;
if (current_location == actual_path.back())
actual_path.pop_back();
if (actual_path.empty())
return Direction::NONE;
return {actual_path.back() - current_location};
}
}
#pragma once
#include "engine/direction.hpp"
#include "engine/vector2.hpp"
#include <memory>
#include <vector>
namespace pm
{
class Terrain;
// Utility class used to find path on given terrain using A* algorithm (// not used anymore in current program)
class PathFinder
{
public:
PathFinder(const std::shared_ptr<Terrain>& in_terrain);
// Update path (only heavy if from and to have changed)
bool find_path(const Vector2I& from, Vector2I to);
// Advance in computed path and get direction to next path point
// Return Direction::NONE when complete
Direction direction_to_next_point(const Vector2I& current_location);
private:
std::vector<Vector2I> actual_path;
std::shared_ptr<Terrain> terrain;
};
}
\ No newline at end of file
#include "terrain.hpp"
#include "engine/engine.hpp"
#include "engine/logger.hpp"
#include "game/pacman_gamemode.hpp"
#include <fstream>
#include <SDL_surface.h>
namespace pm
{
std::array<EItemType, 13> Terrain::level_items{
EItemType::Key, // default
EItemType::Cherry, // 1
EItemType::Strawberry, // 2
EItemType::Abricot, // 3
EItemType::Abricot, // 4
EItemType::Apple, // 5
EItemType::Apple, // 6
EItemType::Grapes, // 7
EItemType::Grapes, // 8
EItemType::Galaxian, // 9
EItemType::Galaxian, // 10
EItemType::Bell, // 11
EItemType::Bell // 12
};
std::unordered_map<EItemType, int> Terrain::item_values = {
{EItemType::Cherry, 100},
{EItemType::Strawberry, 300},
{EItemType::Abricot, 500},
{EItemType::Apple, 700},
{EItemType::Grapes, 1000},
{EItemType::Galaxian, 2000},
{EItemType::Bell, 3000},
{EItemType::Key, 5000}
};
EItemType Terrain::get_level_item(int level) const
{
return level_items[static_cast<size_t>(level) < level_items.size() ? level : 0];
}
SpriteHandle Terrain::get_item_sprite_handle(EItemType item) const
{
return item_sprite_handles.find(item)->second;
}
void Terrain::load_from_file(const std::filesystem::path& path)
{
width = 0;
height = 0;
grid.clear();
std::vector<std::vector<char>> lines;
std::ifstream input(path);
if (!input)
{
ERROR("Failed to read map file '{}'", path.string());
return;
}
int64_t new_width = -1;
std::string line;
while (std::getline(input, line))
{
if (new_width == -1)
new_width = static_cast<int64_t>(line.length());
else if (new_width != static_cast<int64_t>(line.length()))
{
ERROR("Wrong line length : {}", line);
return;
}
lines.emplace_back(std::vector<char>{line.begin(), line.end()});
}
if (new_width < 0)
{
ERROR("File %s is empty", path.string());
return;
}
width = static_cast<uint32_t>(new_width);
height = static_cast<uint32_t>(lines.size());
initial_grid.reserve(static_cast<size_t>(width) * static_cast<size_t>(height));
initial_gum_count = 0;
for (const auto& s_line : lines)
for (const auto& cell : s_line)
{
initial_grid.emplace_back(Cell::from_char(cell));
if (initial_grid.back().get_type() == ECellType::Gum)
initial_gum_count++;
}
INFO("Successfully loaded {}", path.string());
}
void Terrain::eat(const Vector2I& pos)
{
if (pos.x() < 0 || pos.y() < 0 || static_cast<uint32_t>(pos.x()) >= width || static_cast<uint32_t>(pos.y()) >= height)
return;
auto& cell = get_cell(pos);
int32_t points;
switch (cell.get_type())
{
case ECellType::Gum:
cell.update_type(ECellType::Void);
gum_count--;
Engine::get().get_gamemode<PacmanGamemode>().add_points(10);
if (gum_count == 70 || gum_count == 170)
{
const auto level = Engine::get().get_gamemode<PacmanGamemode>().current_level();
const EItemType spawned_item = get_level_item(level);
item_timer = Engine::get().random_double(9.3333, 10);
get_cell({10, 15}).set_item(spawned_item);
}
update_sprite_handles();
if (gum_count <= 0)
Engine::get().get_gamemode<PacmanGamemode>().victory();
break;
case ECellType::Item:
points = item_values[cell.get_item()];
Engine::get().get_gamemode<PacmanGamemode>().add_points(points);
cell.update_type(ECellType::Void);
update_sprite_handles();
break;
case ECellType::BiGum:
Engine::get().get_gamemode<PacmanGamemode>().on_frightened.execute();
cell.update_type(ECellType::Void);
Engine::get().get_gamemode<PacmanGamemode>().add_points(100);
update_sprite_handles();
break;
case ECellType::Void:
case ECellType::Wall:
case ECellType::Door:
break;
}
}
Vector2I Terrain::closest_free_point(const Vector2I& location) const
{
const Vector2I clamped_location = {std::clamp(location.x(), 0, static_cast<int>(width)), std::clamp(location.y(), 0, static_cast<int>(height))};
for (int i = 0; i < static_cast<int>(std::max(width, height)); ++i)
{
for (int x = -i; x <= i; ++x)
if (is_free(clamped_location + Vector2I{i, x}))
return clamped_location + Vector2I{i, x};
for (int x = -i; x <= i; ++x)
if (is_free(clamped_location + Vector2I{-i, x}))
return clamped_location + Vector2I{-i, x};
for (int y = -i + 1; y < i; ++y)
if (is_free(clamped_location + Vector2I{y, i}))
return clamped_location + Vector2I{y, y};
for (int y = -i + 1; y < i; ++y)
if (is_free(clamped_location + Vector2I{y, -i}))
return clamped_location + Vector2I{y, -i};
}
FATAL("failed to find free point");
}
void Terrain::set_wall_color(const Uint8 r, const Uint8 g, const Uint8 b) const
{
SDL_SetSurfaceColorMod(wall_cache_surface_handle, r, g, b);
}
void Terrain::reset()
{
gum_count = initial_gum_count;
grid = initial_grid;
update_position_and_walls();
update_sprite_handles();
// Clear fruit
get_cell({10, 15}).update_type(ECellType::Void);
create_wall_cache_surface();
}
void Terrain::update_position_and_walls()
{
for (uint32_t y = 0; y < height; ++y)
for (uint32_t x = 0; x < width; ++x)
{
Cell& cell = grid[y * width + x];
cell.set_pos({int(x), int(y)});
if (cell.get_type() == ECellType::Wall)
{
Cell::WallMask mask = 0;
if (y > 0 && grid[(y - 1) * width + x].get_type() == ECellType::Wall)
mask |= Cell::WALL_MASK_NORTH;
if (x > 0 && grid[y * width + (x - 1)].get_type() == ECellType::Wall)
mask |= Cell::WALL_MASK_WEST;
if (x + 1 < width && grid[y * width + (x + 1)].get_type() == ECellType::Wall)
mask |= Cell::WALL_MASK_EAST;
if (y + 1 < height && grid[(y + 1) * width + x].get_type() == ECellType::Wall)
mask |= Cell::WALL_MASK_SOUTH;
cell.set_wall(mask);
}
}
}
void Terrain::update_sprite_handles()
{
const SpriteHandle null_handle;
const std::unordered_map<ECellType, SpriteHandle> cell_sprite_handles = {
{ECellType::Void, null_handle},
{ECellType::Gum, SpriteSheet::find_sprite_by_name_or_default("gum")},
{ECellType::BiGum, SpriteSheet::find_sprite_by_name_or_default("bigum")},
{ECellType::Door, SpriteSheet::find_sprite_by_name_or_default("door")}
};
item_sprite_handles = {
{EItemType::Cherry, SpriteSheet::find_sprite_by_name_or_default("cherry")},
{EItemType::Strawberry, SpriteSheet::find_sprite_by_name_or_default("strawberry")},
{EItemType::Abricot, SpriteSheet::find_sprite_by_name_or_default("abricot")},
{EItemType::Apple, SpriteSheet::find_sprite_by_name_or_default("apple")},
{EItemType::Grapes, SpriteSheet::find_sprite_by_name_or_default("wtfruit")},
{EItemType::Galaxian, SpriteSheet::find_sprite_by_name_or_default("axe")},
{EItemType::Bell, SpriteSheet::find_sprite_by_name_or_default("bell")},
{EItemType::Key, SpriteSheet::find_sprite_by_name_or_default("key")},
};
std::array<SpriteHandle, 16> wall_sprite_handles;
wall_sprite_handles[0] = SpriteSheet::find_sprite_by_name_or_default("wall_none");
wall_sprite_handles[Cell::WALL_MASK_NORTH] = SpriteSheet::find_sprite_by_name_or_default("wall_N");
wall_sprite_handles[Cell::WALL_MASK_EAST] = SpriteSheet::find_sprite_by_name_or_default("wall_E");
wall_sprite_handles[Cell::WALL_MASK_EAST | Cell::WALL_MASK_NORTH] = SpriteSheet::find_sprite_by_name_or_default("wall_NE");
wall_sprite_handles[Cell::WALL_MASK_WEST] = SpriteSheet::find_sprite_by_name_or_default("wall_W");
wall_sprite_handles[Cell::WALL_MASK_WEST | Cell::WALL_MASK_NORTH] = SpriteSheet::find_sprite_by_name_or_default("wall_NW");
wall_sprite_handles[Cell::WALL_MASK_WEST | Cell::WALL_MASK_EAST] = SpriteSheet::find_sprite_by_name_or_default("wall_EW");
wall_sprite_handles[Cell::WALL_MASK_WEST | Cell::WALL_MASK_EAST | Cell::WALL_MASK_NORTH] = SpriteSheet::find_sprite_by_name_or_default("wall_NEW");
wall_sprite_handles[Cell::WALL_MASK_SOUTH] = SpriteSheet::find_sprite_by_name_or_default("wall_S");
wall_sprite_handles[Cell::WALL_MASK_SOUTH | Cell::WALL_MASK_NORTH] = SpriteSheet::find_sprite_by_name_or_default("wall_NS");
wall_sprite_handles[Cell::WALL_MASK_SOUTH | Cell::WALL_MASK_EAST] = SpriteSheet::find_sprite_by_name_or_default("wall_ES");
wall_sprite_handles[Cell::WALL_MASK_SOUTH | Cell::WALL_MASK_EAST | Cell::WALL_MASK_NORTH] = SpriteSheet::find_sprite_by_name_or_default("wall_NES");
wall_sprite_handles[Cell::WALL_MASK_SOUTH | Cell::WALL_MASK_WEST] = SpriteSheet::find_sprite_by_name_or_default("wall_WS");
wall_sprite_handles[Cell::WALL_MASK_SOUTH | Cell::WALL_MASK_WEST | Cell::WALL_MASK_NORTH] = SpriteSheet::find_sprite_by_name_or_default("wall_NWS");
wall_sprite_handles[Cell::WALL_MASK_SOUTH | Cell::WALL_MASK_WEST | Cell::WALL_MASK_EAST] = SpriteSheet::find_sprite_by_name_or_default("wall_EWS");
wall_sprite_handles[Cell::WALL_MASK_FULL] = SpriteSheet::find_sprite_by_name_or_default("wall_full");
for (auto& cell : grid)
cell.update_sprite_handle(cell_sprite_handles, item_sprite_handles, wall_sprite_handles);
}
void Terrain::create_wall_cache_surface()
{
if (wall_cache_surface_handle != nullptr)
free_wall_cache_surface();
SDL_Surface* esh = pm::Engine::get().get_surface_handle();
wall_cache_surface_handle = SDL_CreateRGBSurface(0, esh->w, esh->h, 32, 0, 0, 0, 0);
if (wall_cache_surface_handle == nullptr)
{
INFO("couldn't create wall cache surface, continuing without wall caching");
return;
}
for (auto& cell : grid)
if (cell.get_type() == ECellType::Wall)
cell.draw(unit_length, wall_cache_surface_handle);
}
void Terrain::free_wall_cache_surface()
{
SDL_FreeSurface(wall_cache_surface_handle);
wall_cache_surface_handle = nullptr;
}
Cell& Terrain::get_cell(const Vector2I& pos)
{
if (pos.x() < 0 || pos.x() >= static_cast<int32_t>(width) || pos.y() < 0 || pos.y() >= static_cast<int32_t>(height))
FATAL("Cannot read grid cell {}/{}", pos.x(), pos.y());
return grid[pos.x() + pos.y() * width];
}
void Terrain::tick(double delta_time)
{
if (item_timer > 0)
{
item_timer -= delta_time;
if (item_timer <= 0)
get_cell({10, 15}).update_type(ECellType::Void);
}
}
void Terrain::draw()
{
if (wall_cache_surface_handle != nullptr)
SDL_BlitSurface(wall_cache_surface_handle, nullptr, pm::Engine::get().get_surface_handle(), nullptr);
for (auto& cell : grid)
if (wall_cache_surface_handle == nullptr || cell.get_type() != ECellType::Wall)
cell.draw(unit_length);
}
Terrain::~Terrain()
{
free_wall_cache_surface();
}
}
#pragma once
#include "cell.hpp"
#include "engine/vector2.hpp"
#include <filesystem>
struct SDL_Surface;
namespace pm
{
class Terrain
{
public:
Terrain() = default;
~Terrain();
void load_from_file(const std::filesystem::path& path);
[[nodiscard]] Cell& get_cell(const Vector2I& pos);
[[nodiscard]] int32_t get_unit_length() const
{
return unit_length;
}
[[nodiscard]] EItemType get_level_item(int level) const;
[[nodiscard]] SpriteHandle get_item_sprite_handle(EItemType item) const;
void tick(double delta_time);
void draw();
[[nodiscard]] bool is_free(const Vector2I& pos, bool is_door_free = false) const
{
if (pos.x() < 0 || pos.y() < 0 || static_cast<uint32_t>(pos.x()) >= width || static_cast<uint32_t>(pos.y()) >= height)
return false;
const auto cell_type = grid[pos.x() + pos.y() * width].get_type();
return cell_type != ECellType::Wall && (is_door_free || cell_type != ECellType::Door);
}
[[nodiscard]] bool is_tunnel(const Vector2I& pos) const
{
return (static_cast<uint32_t>(pos.x() + 2) + width) % (width + 2) >= width;
}
void eat(const Vector2I& pos);
[[nodiscard]] uint32_t get_width() const { return width; }
[[nodiscard]] uint32_t get_height() const { return height; }
[[nodiscard]] Vector2I closest_free_point(const Vector2I& location) const;
void set_wall_color(const Uint8 r, const Uint8 g, const Uint8 b) const;
void reset();
private:
void update_position_and_walls();
void update_sprite_handles();
void create_wall_cache_surface();
void free_wall_cache_surface();
static std::array<EItemType, 13> level_items;
static std::unordered_map<EItemType, int> item_values;
std::unordered_map<EItemType, SpriteHandle> item_sprite_handles;
int32_t unit_length = 16;
uint32_t width = 0;
uint32_t height = 0;
std::vector<Cell> grid = {};
std::vector<Cell> initial_grid = {};
SDL_Surface* wall_cache_surface_handle = nullptr;
int32_t gum_count = 0;
int32_t initial_gum_count = 0;
double item_timer = 0;
};
}
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment