Compare commits

..

2 commits

Author SHA1 Message Date
Sara 96a5a37733 git: removed callable and replaced uses with std::functional 2025-06-04 21:59:37 +02:00
Sara e75acb9747 feat: input and player logic 2025-06-04 21:59:22 +02:00
11 changed files with 242 additions and 34 deletions

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "vendor/callable"]
path = vendor/callable
url = forgejo@git.objectionable.solutions:Sara/cpp-callable.git

View file

@ -1,26 +1,39 @@
#include "character.h" #include "character.h"
#include "core/renderer.h" #include "core/renderer.h"
#include "world.h" #include "world.h"
#include <cassert>
#include <cstdlib>
namespace rogue { 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} : data{stats}
, logic{logic}
, health{stats.health} , health{stats.health}
, location{location} , location{location}
, world{world} , world{nullptr} {
{} this->logic->set_character(this);
}
void Character::act() { void Character::act() {
assert(this->world != nullptr && "World generation did not initialize character properly");
if(this->health < 0) { if(this->health < 0) {
return; return;
} }
this->logic->set_character(this);
// get motion from logic // get motion from logic
Tile target{this->data.logic(*this)}; Tile target{this->logic->move()};
if(target == this->location) { if(target == this->location
|| std::abs(target.x - this->location.x) + std::abs(target.y - this->location.y) != 1) {
return; return;
} }
// check resulting tile // check resulting tile
TileData tile{world.query_tile(target)}; TileData tile{world->query_tile(target)};
// if character, deal damage // if character, deal damage
if(tile.character != nullptr) { if(tile.character != nullptr) {
tile.character->deal_damage(this->data.damage); tile.character->deal_damage(this->data.damage);
@ -38,7 +51,8 @@ void Character::draw() {
bool Character::deal_damage(int damage) { bool Character::deal_damage(int damage) {
return (this->health -= damage) <= 0; return (this->health -= damage) <= 0;
} }
Tile null_character_logic_function(Character &character) {
return character.location; Tile NullCharacterLogic::move() {
return this->character->location;
} }
} }

View file

@ -2,34 +2,46 @@
#define ROGUE_CHARACTER_H #define ROGUE_CHARACTER_H
#include "core/roguedefs.h" #include "core/roguedefs.h"
#include <memory>
namespace rogue { namespace rogue {
class World; class World;
class Character; 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 { struct CharacterData {
int health{1}; int health{1};
int damage{1}; int damage{1};
Sprite sprite{0}; Sprite sprite{0};
CharacterLogicFunction logic;
}; };
struct Character { struct Character {
Character(World &world, Tile location, CharacterData stats); Character(Tile location, CharacterLogic *logic, CharacterData stats);
void act(); void act();
void draw(); void draw();
bool deal_damage(int damage); bool deal_damage(int damage);
CharacterData const data; CharacterData const data;
std::shared_ptr<CharacterLogic> logic{nullptr};
int health{1}; int health{1};
Tile location{0, 0}; 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 #endif // !ROGUE_CHARACTER_H

40
src/core/input.cpp Normal file
View file

@ -0,0 +1,40 @@
#include "input.h"
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_log.h>
#include <SDL2/SDL_scancode.h>
namespace rogue {
InputHandler::InputHandler() {
Input::add_handler(this);
}
InputHandler::~InputHandler() {
Input::remove_handler(this);
}
KeyEventHandler::KeyEventHandler(std::set<SDL_Scancode> 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<InputHandler*> Input::handlers{};
}

34
src/core/input.h Normal file
View file

@ -0,0 +1,34 @@
#ifndef ROGUE_INPUT_H
#define ROGUE_INPUT_H
#include <SDL2/SDL_events.h>
#include <SDL2/SDL_scancode.h>
#include <functional>
#include <set>
namespace rogue {
struct InputHandler {
InputHandler();
virtual ~InputHandler();
virtual void process_event(SDL_Event event) = 0;
};
struct KeyEventHandler : public InputHandler {
typedef std::function<void(bool down)> Listener;
KeyEventHandler(std::set<SDL_Scancode> code, Listener listener);
virtual void process_event(SDL_Event event) override;
std::set<SDL_Scancode> 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<InputHandler*> handlers;
};
}
#endif // !ROGUE_INPUT_H

View file

@ -4,6 +4,7 @@
#include "core/roguedefs.h" #include "core/roguedefs.h"
#include <SDL2/SDL_log.h> #include <SDL2/SDL_log.h>
#include <cassert> #include <cassert>
#include <memory>
namespace rogue { namespace rogue {
void Room::draw(Tile world_pivot) { 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; 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() { void World::act() {
for(Character &character : this->characters) { for(Character &character : this->characters) {
character.act(); character.act();
@ -43,7 +52,7 @@ void World::act() {
} }
void World::render() { 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) { for(size_t i{0}; i < this->rooms.size(); ++i) {
this->rooms[i].draw({ this->rooms[i].draw({
.x = int(i % this->shear * this->chunk_size), .x = int(i % this->shear * this->chunk_size),
@ -74,6 +83,12 @@ TileData World::query_tile(Tile tile) {
return out; return out;
} }
void World::on_input(bool pressed) {
if(!pressed) {
this->act();
}
}
WorldGenerator &WorldGenerator::with_chunk_size(unsigned side_length) { WorldGenerator &WorldGenerator::with_chunk_size(unsigned side_length) {
assert(this->chunk_side_length == 0); assert(this->chunk_side_length == 0);
assert(side_length > 0); assert(side_length > 0);
@ -97,22 +112,25 @@ WorldGenerator &WorldGenerator::with_room_size(unsigned min_side_length, unsigne
return *this; return *this;
} }
World WorldGenerator::generate() { WorldGenerator &WorldGenerator::with_player(Character character) {
this->player.emplace(character);
return *this;
}
std::unique_ptr<World> WorldGenerator::generate() {
assert(this->chunk_side_length > 0 && "Chunk size is required to generate world"); 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 > 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->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"); assert(this->world_side_length > 0 && "World size is required to generate world");
World world{}; assert(this->player.has_value() && "World cannot be generated without a player");
world.chunk_size = this->chunk_side_length; std::unique_ptr<World> world{new World()};
world.shear = this->world_side_length; world->characters.push_back(player.value());
world.characters.push_back(Character(world, {this->chunk_side_length/2, this->chunk_side_length/2}, CharacterData { world->player = &world->characters[0];
.health = 1, world->player->world = world.get();
.damage = 1, world->chunk_size = this->chunk_side_length;
.sprite = 0, world->shear = this->world_side_length;
.logic = null_character_logic_function world->rooms.reserve(this->world_side_length * this->world_side_length);
})); world->rooms.push_back(Room {
world.rooms.reserve(this->world_side_length * this->world_side_length);
world.rooms.push_back(Room {
.hallway_paths = Directions(Directions::NORTH | Directions::EAST), .hallway_paths = Directions(Directions::NORTH | Directions::EAST),
.chunk_size = this->chunk_side_length, .chunk_size = this->chunk_side_length,
.rect = { .rect = {

View file

@ -1,9 +1,11 @@
#ifndef ROGUE_WORLD_H #ifndef ROGUE_WORLD_H
#define ROGUE_WORLD_H #define ROGUE_WORLD_H
#include <optional>
#include <vector> #include <vector>
#include <SDL2/SDL_rect.h> #include <SDL2/SDL_rect.h>
#include "character.h" #include "character.h"
#include "core/input.h"
#include "core/roguedefs.h" #include "core/roguedefs.h"
namespace rogue { namespace rogue {
@ -26,18 +28,21 @@ struct Room {
struct World { struct World {
friend class WorldGenerator; friend class WorldGenerator;
World();
~World() = default; ~World() = default;
void act(); void act();
void render(); void render();
Room &get_room(Chunk chunk); Room &get_room(Chunk chunk);
TileData query_tile(Tile tile); TileData query_tile(Tile tile);
void on_input(bool);
private: private:
int chunk_size{1}; int chunk_size{1};
unsigned shear{0}; unsigned shear{0};
Character *player{nullptr};
std::vector<Room> rooms{}; std::vector<Room> rooms{};
std::vector<Character> characters{}; std::vector<Character> characters{};
KeyEventHandler direction_pressed;
private: private:
World() = default;
}; };
class WorldGenerator { class WorldGenerator {
@ -46,8 +51,10 @@ public:
WorldGenerator &with_chunk_size(unsigned side_length); WorldGenerator &with_chunk_size(unsigned side_length);
WorldGenerator &with_world_size(unsigned side_length); WorldGenerator &with_world_size(unsigned side_length);
WorldGenerator &with_room_size(unsigned min_side_length, unsigned max_side_length); WorldGenerator &with_room_size(unsigned min_side_length, unsigned max_side_length);
World generate(); WorldGenerator &with_player(Character character);
std::unique_ptr<World> generate();
private: private:
std::optional<Character> player;
int chunk_side_length{0}, world_side_length{0}; int chunk_side_length{0}, world_side_length{0};
int max_room_size{0}, min_room_size{0}; int max_room_size{0}, min_room_size{0};
}; };

View file

@ -1,14 +1,26 @@
#include <SDL2/SDL.h> #include <SDL2/SDL.h>
#include <memory>
#include "core/character.h"
#include "core/input.h"
#include "core/renderer.h" #include "core/renderer.h"
#include "core/world.h" #include "core/world.h"
#include "player_logic.h"
using namespace rogue; using namespace rogue;
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
World world{WorldGenerator() std::unique_ptr<World> world{WorldGenerator()
.with_world_size(5) .with_world_size(5)
.with_chunk_size(9) .with_chunk_size(9)
.with_room_size(2, 5) .with_room_size(2, 5)
.with_player(Character({9/2, 9/2},
new PlayerCharacterLogic(),
CharacterData {
.health = 1,
.damage = 1,
.sprite = 0
}
))
.generate() .generate()
}; };
RenderData data{RenderDataSetup() RenderData data{RenderDataSetup()
@ -22,11 +34,13 @@ int main(int argc, char *argv[]) {
Render::provide_render_data(data); Render::provide_render_data(data);
SDL_Event evt; SDL_Event evt;
for(;;) { for(;;) {
world.render(); world->render();
Render::present(); Render::present();
if(SDL_WaitEvent(&evt)) { if(SDL_WaitEvent(&evt)) {
if(evt.type == SDL_QUIT) { if(evt.type == SDL_QUIT) {
goto main_loop; goto main_loop;
} else {
Input::process_event(evt);
} }
} }
} main_loop: } main_loop:

46
src/player_logic.cpp Normal file
View file

@ -0,0 +1,46 @@
#include "player_logic.h"
#include <SDL2/SDL_log.h>
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;
}
}
};

27
src/player_logic.h Normal file
View file

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

1
vendor/callable vendored

@ -1 +0,0 @@
Subproject commit ea96247ecb258ea9938f038f410e0fd2739aa5de