diff --git a/src/core/character.cpp b/src/core/character.cpp index 55e2277..e60c522 100644 --- a/src/core/character.cpp +++ b/src/core/character.cpp @@ -1,26 +1,39 @@ #include "character.h" #include "core/renderer.h" #include "world.h" +#include +#include namespace rogue { -Character::Character(World &world, Tile location, CharacterData stats) +CharacterLogic::~CharacterLogic() {} + +void CharacterLogic::set_character(Character *character) { + this->character = character; +} + +Character::Character(Tile location, CharacterLogic *logic, CharacterData stats) : data{stats} +, logic{logic} , health{stats.health} , location{location} -, world{world} -{} +, world{nullptr} { + this->logic->set_character(this); +} void Character::act() { + assert(this->world != nullptr && "World generation did not initialize character properly"); if(this->health < 0) { return; } + this->logic->set_character(this); // get motion from logic - Tile target{this->data.logic(*this)}; - if(target == this->location) { + Tile target{this->logic->move()}; + if(target == this->location + || std::abs(target.x - this->location.x) + std::abs(target.y - this->location.y) != 1) { return; } // check resulting tile - TileData tile{world.query_tile(target)}; + TileData tile{world->query_tile(target)}; // if character, deal damage if(tile.character != nullptr) { tile.character->deal_damage(this->data.damage); @@ -38,7 +51,8 @@ void Character::draw() { bool Character::deal_damage(int damage) { return (this->health -= damage) <= 0; } -Tile null_character_logic_function(Character &character) { - return character.location; + +Tile NullCharacterLogic::move() { + return this->character->location; } } diff --git a/src/core/character.h b/src/core/character.h index 6bfc618..ef3b955 100644 --- a/src/core/character.h +++ b/src/core/character.h @@ -2,34 +2,46 @@ #define ROGUE_CHARACTER_H #include "core/roguedefs.h" +#include namespace rogue { class World; class Character; -typedef Tile(&CharacterLogicFunction)(Character &character); +struct CharacterLogic { + virtual ~CharacterLogic(); + + virtual Tile move() = 0; +protected: + Character *character{nullptr}; +private: + friend class Character; + void set_character(Character *character); +}; struct CharacterData { int health{1}; int damage{1}; Sprite sprite{0}; - CharacterLogicFunction logic; }; struct Character { - Character(World &world, Tile location, CharacterData stats); + Character(Tile location, CharacterLogic *logic, CharacterData stats); void act(); void draw(); bool deal_damage(int damage); CharacterData const data; + std::shared_ptr logic{nullptr}; int health{1}; Tile location{0, 0}; - World &world; + World *world{nullptr}; }; -extern Tile null_character_logic_function(Character &character); +struct NullCharacterLogic : public CharacterLogic { + virtual Tile move() override; +}; } #endif // !ROGUE_CHARACTER_H diff --git a/src/core/input.cpp b/src/core/input.cpp new file mode 100644 index 0000000..daa686d --- /dev/null +++ b/src/core/input.cpp @@ -0,0 +1,40 @@ +#include "input.h" +#include +#include +#include + +namespace rogue { +InputHandler::InputHandler() { + Input::add_handler(this); +} + +InputHandler::~InputHandler() { + Input::remove_handler(this); +} + +KeyEventHandler::KeyEventHandler(std::set code, Listener listener) +: scancode{code} +, listener{listener} {} + +void KeyEventHandler::process_event(SDL_Event event) { + if((event.type == SDL_KEYDOWN || event.type == SDL_KEYUP) && this->scancode.contains(event.key.keysym.scancode)) { + this->listener(event.type == SDL_KEYDOWN); + } +} + +void Input::add_handler(InputHandler *handler) { + Input::handlers.insert(handler); +} + +void Input::remove_handler(InputHandler *handler) { + Input::handlers.erase(handler); +} + +void Input::process_event(SDL_Event event) { + for(InputHandler *handler : Input::handlers) { + handler->process_event(event); + } +} + +std::set Input::handlers{}; +} diff --git a/src/core/input.h b/src/core/input.h new file mode 100644 index 0000000..0d1a7ea --- /dev/null +++ b/src/core/input.h @@ -0,0 +1,34 @@ +#ifndef ROGUE_INPUT_H +#define ROGUE_INPUT_H + +#include +#include +#include +#include + +namespace rogue { +struct InputHandler { + InputHandler(); + virtual ~InputHandler(); + virtual void process_event(SDL_Event event) = 0; +}; + +struct KeyEventHandler : public InputHandler { + typedef std::function Listener; + KeyEventHandler(std::set code, Listener listener); + virtual void process_event(SDL_Event event) override; + std::set scancode{}; + Listener listener{}; +}; + +class Input { +public: + static void add_handler(InputHandler *handler); + static void remove_handler(InputHandler *handler); + static void process_event(SDL_Event event); +private: + static std::set handlers; +}; +} + +#endif // !ROGUE_INPUT_H diff --git a/src/core/world.cpp b/src/core/world.cpp index 98b00ee..6445d88 100644 --- a/src/core/world.cpp +++ b/src/core/world.cpp @@ -4,6 +4,7 @@ #include "core/roguedefs.h" #include #include +#include namespace rogue { void Room::draw(Tile world_pivot) { @@ -36,6 +37,14 @@ bool Room::tile_is_wall(Tile local_tile) const { return is_outside_room && is_outside_hallways; } +World::World() +: direction_pressed{{ + SDL_SCANCODE_H, SDL_SCANCODE_J, SDL_SCANCODE_K, SDL_SCANCODE_L, + SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_UP, SDL_SCANCODE_DOWN + }, + std::bind(&World::on_input, this, std::placeholders::_1) +} {} + void World::act() { for(Character &character : this->characters) { character.act(); @@ -43,7 +52,7 @@ void World::act() { } void World::render() { - Render::clear({this->chunk_size/2, this->chunk_size/2}); + Render::clear(this->player->location); for(size_t i{0}; i < this->rooms.size(); ++i) { this->rooms[i].draw({ .x = int(i % this->shear * this->chunk_size), @@ -74,6 +83,12 @@ TileData World::query_tile(Tile tile) { return out; } +void World::on_input(bool pressed) { + if(!pressed) { + this->act(); + } +} + WorldGenerator &WorldGenerator::with_chunk_size(unsigned side_length) { assert(this->chunk_side_length == 0); assert(side_length > 0); @@ -97,22 +112,25 @@ WorldGenerator &WorldGenerator::with_room_size(unsigned min_side_length, unsigne return *this; } -World WorldGenerator::generate() { +WorldGenerator &WorldGenerator::with_player(Character character) { + this->player.emplace(character); + return *this; +} + +std::unique_ptr WorldGenerator::generate() { assert(this->chunk_side_length > 0 && "Chunk size is required to generate world"); assert(this->min_room_size > 0 && "Room size is required to generate world"); assert(this->min_room_size < this->max_room_size && "Room size is required to generate world"); assert(this->world_side_length > 0 && "World size is required to generate world"); - World world{}; - world.chunk_size = this->chunk_side_length; - world.shear = this->world_side_length; - world.characters.push_back(Character(world, {this->chunk_side_length/2, this->chunk_side_length/2}, CharacterData { - .health = 1, - .damage = 1, - .sprite = 0, - .logic = null_character_logic_function - })); - world.rooms.reserve(this->world_side_length * this->world_side_length); - world.rooms.push_back(Room { + assert(this->player.has_value() && "World cannot be generated without a player"); + std::unique_ptr world{new World()}; + world->characters.push_back(player.value()); + world->player = &world->characters[0]; + world->player->world = world.get(); + world->chunk_size = this->chunk_side_length; + world->shear = this->world_side_length; + world->rooms.reserve(this->world_side_length * this->world_side_length); + world->rooms.push_back(Room { .hallway_paths = Directions(Directions::NORTH | Directions::EAST), .chunk_size = this->chunk_side_length, .rect = { diff --git a/src/core/world.h b/src/core/world.h index e4f5809..a9f38d5 100644 --- a/src/core/world.h +++ b/src/core/world.h @@ -1,9 +1,11 @@ #ifndef ROGUE_WORLD_H #define ROGUE_WORLD_H +#include #include #include #include "character.h" +#include "core/input.h" #include "core/roguedefs.h" namespace rogue { @@ -26,18 +28,21 @@ struct Room { struct World { friend class WorldGenerator; + World(); ~World() = default; void act(); void render(); Room &get_room(Chunk chunk); TileData query_tile(Tile tile); + void on_input(bool); private: int chunk_size{1}; unsigned shear{0}; + Character *player{nullptr}; std::vector rooms{}; std::vector characters{}; + KeyEventHandler direction_pressed; private: - World() = default; }; class WorldGenerator { @@ -46,8 +51,10 @@ public: WorldGenerator &with_chunk_size(unsigned side_length); WorldGenerator &with_world_size(unsigned side_length); WorldGenerator &with_room_size(unsigned min_side_length, unsigned max_side_length); - World generate(); + WorldGenerator &with_player(Character character); + std::unique_ptr generate(); private: + std::optional player; int chunk_side_length{0}, world_side_length{0}; int max_room_size{0}, min_room_size{0}; }; diff --git a/src/main.cpp b/src/main.cpp index f7497d1..dd5a620 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,14 +1,26 @@ #include +#include +#include "core/character.h" +#include "core/input.h" #include "core/renderer.h" #include "core/world.h" +#include "player_logic.h" using namespace rogue; int main(int argc, char *argv[]) { - World world{WorldGenerator() + std::unique_ptr world{WorldGenerator() .with_world_size(5) .with_chunk_size(9) .with_room_size(2, 5) + .with_player(Character({9/2, 9/2}, + new PlayerCharacterLogic(), + CharacterData { + .health = 1, + .damage = 1, + .sprite = 0 + } + )) .generate() }; RenderData data{RenderDataSetup() @@ -22,11 +34,13 @@ int main(int argc, char *argv[]) { Render::provide_render_data(data); SDL_Event evt; for(;;) { - world.render(); + world->render(); Render::present(); if(SDL_WaitEvent(&evt)) { if(evt.type == SDL_QUIT) { goto main_loop; + } else { + Input::process_event(evt); } } } main_loop: diff --git a/src/player_logic.cpp b/src/player_logic.cpp new file mode 100644 index 0000000..4042f39 --- /dev/null +++ b/src/player_logic.cpp @@ -0,0 +1,46 @@ +#include "player_logic.h" +#include + +namespace rogue { +PlayerCharacterLogic::PlayerCharacterLogic() +: up{{SDL_SCANCODE_K, SDL_SCANCODE_UP}, std::bind(&PlayerCharacterLogic::on_up, this, std::placeholders::_1)} +, down{{SDL_SCANCODE_J, SDL_SCANCODE_DOWN}, std::bind(&PlayerCharacterLogic::on_down, this, std::placeholders::_1)} +, right{{SDL_SCANCODE_L, SDL_SCANCODE_RIGHT}, std::bind(&PlayerCharacterLogic::on_right, this, std::placeholders::_1)} +, left{{SDL_SCANCODE_H, SDL_SCANCODE_LEFT}, std::bind(&PlayerCharacterLogic::on_left, this, std::placeholders::_1)} +{} + +Tile PlayerCharacterLogic::move() { + return { + this->character->location.x + input_x, + this->character->location.y + input_y + }; +} + +void PlayerCharacterLogic::on_up(bool pressed) { + if(pressed) { + input_x = 0; + input_y = -1; + } +} + +void PlayerCharacterLogic::on_down(bool pressed) { + if(pressed) { + input_x = 0; + input_y = 1; + } +} + +void PlayerCharacterLogic::on_left(bool pressed) { + if(pressed) { + input_x = -1; + input_y = 0; + } +} + +void PlayerCharacterLogic::on_right(bool pressed) { + if(pressed) { + input_x = 1; + input_y = 0; + } +} +}; diff --git a/src/player_logic.h b/src/player_logic.h new file mode 100644 index 0000000..6ddd106 --- /dev/null +++ b/src/player_logic.h @@ -0,0 +1,27 @@ +#ifndef PLAYER_LOGIC_H +#define PLAYER_LOGIC_H + +#include "core/character.h" +#include "core/input.h" +#include "core/roguedefs.h" +#include + +namespace rogue { +class PlayerCharacterLogic : public CharacterLogic { +public: + PlayerCharacterLogic(); +private: + virtual Tile move() override; + void on_up(bool pressed); + void on_down(bool pressed); + void on_left(bool pressed); + void on_right(bool pressed); + int input_x{0}, input_y{0}; + KeyEventHandler up; + KeyEventHandler down; + KeyEventHandler right; + KeyEventHandler left; +}; +} + +#endif // !PLAYER_LOGIC_H