diff --git a/dinkyecs.hpp b/dinkyecs.hpp index 3f7441d..31ffaa4 100644 --- a/dinkyecs.hpp +++ b/dinkyecs.hpp @@ -87,6 +87,12 @@ namespace DinkyECS { system(cb); }; } - }; + template + std::function runner(std::function cb) { + return [&]{ + system(cb); + }; + } + }; } diff --git a/entity.cpp b/entity.cpp deleted file mode 100644 index 4665349..0000000 --- a/entity.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "entity.hpp" - -void Entity::move(Point loc) { - location = loc; -} - -void Entity::event(EntityEvent ev) { - switch($state) { - FSM_STATE(EntityState, START, ev); - FSM_STATE(EntityState, HUNTING, ev); - FSM_STATE(EntityState, DEAD, ev); - } -} - -void Entity::START(EntityEvent ev) { - state(EntityState::HUNTING); -} - -void Entity::HUNTING(EntityEvent ev) { - switch(ev) { - case EntityEvent::HIT: - hp -= damage; - break; - default: - state(EntityState::HUNTING); - } - - if(hp <= 0) { - state(EntityState::DEAD); - } else { - state(EntityState::HUNTING); - } -} - -void Entity::DEAD(EntityEvent ev) { - state(EntityState::DEAD); -} diff --git a/entity.hpp b/entity.hpp deleted file mode 100644 index 0893228..0000000 --- a/entity.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once -#include "fsm.hpp" -#include "map.hpp" - -enum class EntityState { - START, HUNTING, DEAD -}; - -enum class EntityEvent { - GO, HIT -}; - - -struct Entity : public DeadSimpleFSM { - Point location{0,0}; - int hp = 20; - int damage = 10; - - Entity() { - } - - Entity(Point loc) : location(loc) { - } - - // disable copy - Entity(Entity &e) = delete; - - void move(Point loc); - void event(EntityEvent ev); - - // states - void START(EntityEvent ev); - void HUNTING(EntityEvent ev); - void DEAD(EntityEvent ev); -}; diff --git a/gui.cpp b/gui.cpp index 7892fac..5e9ef3f 100644 --- a/gui.cpp +++ b/gui.cpp @@ -25,6 +25,32 @@ using namespace fmt; using namespace std::chrono_literals; using namespace ftxui; +struct Player { + DinkyECS::Entity entity; +}; + +struct Position { + Point location; +}; + +struct Motion { + int dx; + int dy; +}; + +struct Combat { + int hp; + int damage; +}; + +struct Treasure { + int amount; +}; + +struct Tile { + std::string chr = "!"; +}; + std::array VALUES{ sf::Color{1, 4, 2}, // black sf::Color{9, 29, 16}, // dark dark @@ -69,22 +95,18 @@ GUI::GUI() : $game_map(GAME_MAP_X, GAME_MAP_Y), $map_text.setFillColor(color(Value::MID)); $game_map.generate(); - $player.location = $game_map.place_entity(0); - $enemy.location = $game_map.place_entity(1); - $goal = $game_map.place_entity($game_map.room_count() - 1); } void GUI::create_renderer() { - $map_view = Renderer([&] { + auto player = $world.get(); + + $map_view = Renderer([&, player] { + const auto& player_position = $world.component(player.entity); Matrix &walls = $game_map.walls(); - $game_map.set_target($player.location); + $game_map.set_target(player_position.location); $game_map.make_paths(); Matrix &paths = $game_map.paths(); - if($player.in_state(EntityState::DEAD)) { - $status_text = "DEAD!"; - } - for(size_t x = 0; x < walls[0].size(); ++x) { for(size_t y = 0; y < walls.size(); ++y) { string tile = walls[y][x] == 1 ? WALL_TILE : format("{}", paths[y][x]); @@ -99,18 +121,19 @@ void GUI::create_renderer() { } } - $canvas.DrawText($enemy.location.x*2, $enemy.location.y*4, ENEMY_TILE); - $canvas.DrawText($player.location.x*2, $player.location.y*4, PLAYER_TILE); - $canvas.DrawText($goal.x*2, $goal.y*4, "$"); + $world.system([&](const auto &ent, auto &pos, auto &tile) { + $canvas.DrawText(pos.location.x*2, pos.location.y*4, tile.chr); + }); return canvas($canvas); }); - $document = Renderer([&]{ + $document = Renderer([&, player]{ + const auto& player_combat = $world.component(player.entity); return hbox({ hflow( vbox( - text(format("HP: {}", $player.hp)) | border, + text(format("HP: {}", player_combat.hp)) | border, text($status_text) | border ) | xflex_grow ), @@ -122,46 +145,61 @@ void GUI::create_renderer() { void GUI::handle_events() { sf::Event event; + auto player = $world.get(); + auto& player_motion = $world.component(player.entity); + while($window.pollEvent(event)) { if(event.type == sf::Event::Closed) { $window.close(); } else if(event.type == sf::Event::KeyPressed) { - size_t x = $player.location.x; - size_t y = $player.location.y; - if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) { - x -= 1; + player_motion.dx = -1; } else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { - x += 1; + player_motion.dx = 1; } else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { - y -= 1; + player_motion.dy = -1; } else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { - y += 1; + player_motion.dy = 1; } - if($game_map.inmap(x,y) && !$game_map.iswall(x,y)) { - $game_map.clear_target($player.location); - $player.move({x, y}); - } else { - $shake_it = true; - $hit_sound.play(); - } + // COMPOSE system? You create a bunch of callbacks and then combine them into + // a single run over the data? - // move $enemy here - // BUG: when the enemy has no path it goes through walls, which means - // this neighbors function is not working right. Probably updating - // enemy.location in an out parameter isn't the best idea. - bool found = $game_map.neighbors($enemy.location, true); - if(!found) { - $status_text = "ENEMY STUCK!"; - } - - if($enemy.location.x == $player.location.x && $enemy.location.y == $player.location.y) { - $player.event(EntityEvent::HIT); - $burn_baby_burn = true; - } else if($goal.x == $player.location.x && $goal.y == $player.location.y) { - $status_text = "YOU WIN!"; - } + // move enemies system + $world.system([&](const auto &ent, auto &position, auto &motion) { + if(ent != player.entity) { + Point out = position.location; + $game_map.neighbors(out, false); + motion = { int(out.x - position.location.x), int(out.y - position.location.y)}; + } + }); + + // motion system + $world.system([&](const auto &ent, auto &position, auto &motion) { + Point move_to = { + position.location.x + motion.dx, + position.location.y + motion.dy + }; + motion = {0,0}; // clear it after getting it + + if($game_map.inmap(move_to.x, move_to.y) && !$game_map.iswall(move_to.x,move_to.y)) { + $game_map.clear_target(position.location); + position.location = move_to; + } + }); + + // combat system + auto combatSystem = [&]() { + const auto& player_position = $world.component(player.entity); + $world.system([&](const auto &ent, auto &pos, auto &combat) { + if(ent != player.entity && pos.location.x == player_position.location.x && + pos.location.y == player_position.location.y) { + $burn_baby_burn = true; + } + }); + }; + + combatSystem(); } } } @@ -187,6 +225,7 @@ void GUI::draw_screen(bool clear, float map_off_x, float map_off_y) { } void GUI::shake() { + $hit_sound.play(); for(int i = 0; i < 10; ++i) { int x = Random::uniform(-10,10); int y = Random::uniform(-10,10); @@ -196,6 +235,35 @@ void GUI::shake() { } } +void GUI::configure_world() { + dbc::check($game_map.room_count() > 1, "not enough rooms in map."); + // configure a player as a fact of the world + Player player{$world.entity()}; + $world.set(player); + + $world.assign(player.entity, {$game_map.place_entity(0)}); + $world.assign(player.entity, {0, 0}); + $world.assign(player.entity, {100, 10}); + $world.assign(player.entity, {PLAYER_TILE}); + + auto enemy = $world.entity(); + $world.assign(enemy, {$game_map.place_entity(1)}); + $world.assign(enemy, {0,0}); + $world.assign(enemy, {20, 10}); + $world.assign(enemy, {ENEMY_TILE}); + + auto enemy2 = $world.entity(); + $world.assign(enemy2, {$game_map.place_entity(2)}); + $world.assign(enemy2, {0,0}); + $world.assign(enemy2, {20, 10}); + $world.assign(enemy2, {"*"}); + + auto gold = $world.entity(); + $world.assign(gold, {$game_map.place_entity($game_map.room_count() - 1)}); + $world.assign(gold, {100}); + $world.assign(gold, {"$"}); +} + void GUI::render_scene() { Render($map_screen, $map_view->Render()); Render($screen, $document->Render()); @@ -222,6 +290,7 @@ void GUI::render_scene() { } int GUI::main() { + configure_world(); create_renderer(); while($window.isOpen()) { diff --git a/gui.hpp b/gui.hpp index 5ebb438..3cf8fab 100644 --- a/gui.hpp +++ b/gui.hpp @@ -10,8 +10,8 @@ #include #include #include -#include "entity.hpp" #include "map.hpp" +#include "dinkyecs.hpp" using std::string; using ftxui::Canvas, ftxui::Component, ftxui::Screen; @@ -42,9 +42,6 @@ class GUI { sf::Sound $hit_sound; bool $show_paths = false; string $status_text = "NOT DEAD"; - Entity $player; - Entity $enemy; - Point $goal; Component $document; Component $map_view; Canvas $canvas; @@ -57,6 +54,7 @@ class GUI { sf::RenderWindow $window; Screen $screen; Screen $map_screen; + DinkyECS::World $world; public: GUI(); @@ -71,6 +69,7 @@ public: void draw_screen(bool clear=true, float map_off_x=0.0f, float map_off_y=0.0f); void shake(); void burn(); + void configure_world(); int main(); }; diff --git a/map.cpp b/map.cpp index 223c64f..702d41a 100644 --- a/map.cpp +++ b/map.cpp @@ -271,11 +271,11 @@ bool Map::walk(Point &src, Point &target) { return false; } -void Map::set_target(Point &at, int value) { +void Map::set_target(const Point &at, int value) { $input_map[at.y][at.x] = 0; } -void Map::clear_target(Point &at) { +void Map::clear_target(const Point &at) { $input_map[at.y][at.x] = 1; } diff --git a/map.hpp b/map.hpp index 1f85408..bbb8f50 100644 --- a/map.hpp +++ b/map.hpp @@ -75,8 +75,8 @@ public: void place_rooms(Room &root); void make_paths(); void partition_map(Room &cur, int depth); - void set_target(Point &at, int value=0); - void clear_target(Point &at); + void set_target(const Point &at, int value=0); + void clear_target(const Point &at); bool walk(Point &src, Point &target); void set_door(Room &room, int value); void dump(); diff --git a/meson.build b/meson.build index 383594f..e77dd17 100644 --- a/meson.build +++ b/meson.build @@ -17,7 +17,6 @@ dependencies = [catch2, fmt, runtests = executable('runtests', [ 'dbc.cpp', 'map.cpp', - 'entity.cpp', 'rand.cpp', 'tests/fsm.cpp', 'tests/dbc.cpp', @@ -31,7 +30,6 @@ roguish = executable('roguish', [ 'map.cpp', 'gui.cpp', 'rand.cpp', - 'entity.cpp', ], dependencies: dependencies) diff --git a/scratchpad/myecstest.cpp b/scratchpad/myecstest.cpp index 78b4059..53162f3 100644 --- a/scratchpad/myecstest.cpp +++ b/scratchpad/myecstest.cpp @@ -4,7 +4,12 @@ #include using namespace fmt; -using DinkyECS::Entity, DinkyECS::World; +using DinkyECS::Entity; + +struct Player { + std::string name; + Entity eid; +}; struct Position { double x, y; @@ -18,63 +23,104 @@ struct Gravity { double level; }; -int main() { - World me; +/* + * Using a function catches instances where I'm not copying + * the data into the world. + */ +void configure(DinkyECS::World &world, Entity &test) { + println("---Configuring the base system."); + Entity test2 = world.entity(); + + world.assign(test, {10,20}); + world.assign(test, {1,2}); + + world.assign(test2, {1,1}); + world.assign(test2, {10,20}); + + println("---- Setting up the player as a fact in the system."); + + auto player_eid = world.entity(); + Player player_info{"Zed", player_eid}; + // just set some player info as a fact with the entity id + world.set(player_info); + + world.assign(player_eid, {0,0}); + world.assign(player_eid, {0,0}); + + auto enemy = world.entity(); + world.assign(enemy, {0,0}); + world.assign(enemy, {0,0}); - Entity test = me.entity(); - Entity test2 = me.entity(); + println("--- Creating facts (singletons)"); + world.set({0.9}); +} - me.assign(test, {10,20}); - me.assign(test, {1,2}); +int main() { + DinkyECS::World world; + Entity test = world.entity(); - me.assign(test2, {1,1}); - me.assign(test2, {10,20}); + configure(world, test); - Position &pos = me.component(test); + Position &pos = world.component(test); println("GOT POS x={}, y={}", pos.x, pos.y); - Velocity &vel = me.component(test); + Velocity &vel = world.component(test); println("GOT VELOCITY x={}, y={}", vel.x, vel.y); println("--- Position only system:"); - me.system([](const auto &ent, auto &pos) { + world.system([](const auto &ent, auto &pos) { println("entity={}, pos.x={}, pos.y={}", ent, pos.x, pos.y); }); println("--- Velocity only system:"); - me.system([](const auto &, auto &vel) { + world.system([](const auto &, auto &vel) { println("vel.x={}, vel.y={}", vel.x, vel.y); }); println("--- Manually get the velocity in position system:"); - me.system([&](const auto &ent, auto &pos) { - Velocity &vel = me.component(ent); + world.system([&](const auto &ent, auto &pos) { + Velocity &vel = world.component(ent); println("entity={}, vel.x, vel.y, pos.x={}, pos.y={}", ent, vel.x, vel.y, pos.x, pos.y); }); - println("--- Creating facts (singletons)"); - me.set({0.9}); - println("--- Query only entities with Position and Velocity:"); - me.system([&](const auto &ent, auto &pos, auto &vel) { - Gravity &grav = me.get(); + world.system([&](const auto &ent, auto &pos, auto &vel) { + Gravity &grav = world.get(); println("grav={}, entity={}, vel.x, vel.y, pos.x={}, pos.y={}", grav.level, ent, vel.x, vel.y, pos.x, pos.y); }); // now remove Velocity - me.remove(test); + world.remove(test); println("--- After remove test, should only result in test2:"); - me.system([&](const auto &ent, auto &pos, auto &vel) { + world.system([&](const auto &ent, auto &pos, auto &vel) { println("entity={}, vel.x, vel.y, pos.x={}, pos.y={}", ent, vel.x, vel.y, pos.x, pos.y); }); println("--- Create a stored system you can save for later."); - auto movementSystem = me.runner([&](const auto &ent, auto &pos, auto &vel) { + auto movementSystem = world.runner([&](const auto &ent, auto &pos, auto &vel) { println("entity={}, vel.x, vel.y, pos.x={}, pos.y={}", ent, vel.x, vel.y, pos.x, pos.y); }); movementSystem(); + // how to create an identified entity like the player + + + // to avoid repeatedly getting the player just make a closure with it + // QUESTION: could I just capture it and not have the double function wrapping? + auto playerVsEnemies = [&]() { + auto& player = world.get(); // grabbed it + world.system([&](const auto &ent, auto &pos) { + if(player.eid != ent) { + println("{} is enemy attacking player {}", ent, player.name); + } else { + println("{} is player", player.name); + } + }); + }; + + playerVsEnemies(); + return 0; }