DinkyECS is now controlling the game and can handle multiple enemies easily. Next is to clean this up so it's not just one gross pile of code in the gui.

main
Zed A. Shaw 1 month ago
parent 86c98c43c2
commit 33327154ad
  1. 8
      dinkyecs.hpp
  2. 37
      entity.cpp
  3. 35
      entity.hpp
  4. 145
      gui.cpp
  5. 7
      gui.hpp
  6. 4
      map.cpp
  7. 4
      map.hpp
  8. 2
      meson.build
  9. 92
      scratchpad/myecstest.cpp

@ -87,6 +87,12 @@ namespace DinkyECS {
system<CompA, CompB>(cb); system<CompA, CompB>(cb);
}; };
} }
};
template<typename CompA>
std::function<void()> runner(std::function<void(const Entity&, CompA&)> cb) {
return [&]{
system<CompA>(cb);
};
}
};
} }

@ -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);
}

@ -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<EntityState, EntityEvent> {
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);
};

@ -25,6 +25,32 @@ using namespace fmt;
using namespace std::chrono_literals; using namespace std::chrono_literals;
using namespace ftxui; 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<sf::Color, 10> VALUES{ std::array<sf::Color, 10> VALUES{
sf::Color{1, 4, 2}, // black sf::Color{1, 4, 2}, // black
sf::Color{9, 29, 16}, // dark dark 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)); $map_text.setFillColor(color(Value::MID));
$game_map.generate(); $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() { void GUI::create_renderer() {
$map_view = Renderer([&] { auto player = $world.get<Player>();
$map_view = Renderer([&, player] {
const auto& player_position = $world.component<Position>(player.entity);
Matrix &walls = $game_map.walls(); Matrix &walls = $game_map.walls();
$game_map.set_target($player.location); $game_map.set_target(player_position.location);
$game_map.make_paths(); $game_map.make_paths();
Matrix &paths = $game_map.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 x = 0; x < walls[0].size(); ++x) {
for(size_t y = 0; y < walls.size(); ++y) { for(size_t y = 0; y < walls.size(); ++y) {
string tile = walls[y][x] == 1 ? WALL_TILE : format("{}", paths[y][x]); 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); $world.system<Position, Tile>([&](const auto &ent, auto &pos, auto &tile) {
$canvas.DrawText($player.location.x*2, $player.location.y*4, PLAYER_TILE); $canvas.DrawText(pos.location.x*2, pos.location.y*4, tile.chr);
$canvas.DrawText($goal.x*2, $goal.y*4, "$"); });
return canvas($canvas); return canvas($canvas);
}); });
$document = Renderer([&]{ $document = Renderer([&, player]{
const auto& player_combat = $world.component<Combat>(player.entity);
return hbox({ return hbox({
hflow( hflow(
vbox( vbox(
text(format("HP: {}", $player.hp)) | border, text(format("HP: {}", player_combat.hp)) | border,
text($status_text) | border text($status_text) | border
) | xflex_grow ) | xflex_grow
), ),
@ -122,46 +145,61 @@ void GUI::create_renderer() {
void GUI::handle_events() { void GUI::handle_events() {
sf::Event event; sf::Event event;
auto player = $world.get<Player>();
auto& player_motion = $world.component<Motion>(player.entity);
while($window.pollEvent(event)) { while($window.pollEvent(event)) {
if(event.type == sf::Event::Closed) { if(event.type == sf::Event::Closed) {
$window.close(); $window.close();
} else if(event.type == sf::Event::KeyPressed) { } 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)) { if(sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) {
x -= 1; player_motion.dx = -1;
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) { } else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) {
x += 1; player_motion.dx = 1;
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) { } else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Up)) {
y -= 1; player_motion.dy = -1;
} else if(sf::Keyboard::isKeyPressed(sf::Keyboard::Down)) { } 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)) { // COMPOSE system? You create a bunch of callbacks and then combine them into
$game_map.clear_target($player.location); // a single run over the data?
$player.move({x, y});
} else { // move enemies system
$shake_it = true; $world.system<Position, Motion>([&](const auto &ent, auto &position, auto &motion) {
$hit_sound.play(); 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)};
} }
});
// move $enemy here // motion system
// BUG: when the enemy has no path it goes through walls, which means $world.system<Position, Motion>([&](const auto &ent, auto &position, auto &motion) {
// this neighbors function is not working right. Probably updating Point move_to = {
// enemy.location in an out parameter isn't the best idea. position.location.x + motion.dx,
bool found = $game_map.neighbors($enemy.location, true); position.location.y + motion.dy
if(!found) { };
$status_text = "ENEMY STUCK!"; 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;
} }
});
if($enemy.location.x == $player.location.x && $enemy.location.y == $player.location.y) { // combat system
$player.event(EntityEvent::HIT); auto combatSystem = [&]() {
const auto& player_position = $world.component<Position>(player.entity);
$world.system<Position, Combat>([&](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; $burn_baby_burn = true;
} else if($goal.x == $player.location.x && $goal.y == $player.location.y) {
$status_text = "YOU WIN!";
} }
});
};
combatSystem();
} }
} }
} }
@ -187,6 +225,7 @@ void GUI::draw_screen(bool clear, float map_off_x, float map_off_y) {
} }
void GUI::shake() { void GUI::shake() {
$hit_sound.play();
for(int i = 0; i < 10; ++i) { for(int i = 0; i < 10; ++i) {
int x = Random::uniform<int>(-10,10); int x = Random::uniform<int>(-10,10);
int y = Random::uniform<int>(-10,10); int y = Random::uniform<int>(-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>(player);
$world.assign<Position>(player.entity, {$game_map.place_entity(0)});
$world.assign<Motion>(player.entity, {0, 0});
$world.assign<Combat>(player.entity, {100, 10});
$world.assign<Tile>(player.entity, {PLAYER_TILE});
auto enemy = $world.entity();
$world.assign<Position>(enemy, {$game_map.place_entity(1)});
$world.assign<Motion>(enemy, {0,0});
$world.assign<Combat>(enemy, {20, 10});
$world.assign<Tile>(enemy, {ENEMY_TILE});
auto enemy2 = $world.entity();
$world.assign<Position>(enemy2, {$game_map.place_entity(2)});
$world.assign<Motion>(enemy2, {0,0});
$world.assign<Combat>(enemy2, {20, 10});
$world.assign<Tile>(enemy2, {"*"});
auto gold = $world.entity();
$world.assign<Position>(gold, {$game_map.place_entity($game_map.room_count() - 1)});
$world.assign<Treasure>(gold, {100});
$world.assign<Tile>(gold, {"$"});
}
void GUI::render_scene() { void GUI::render_scene() {
Render($map_screen, $map_view->Render()); Render($map_screen, $map_view->Render());
Render($screen, $document->Render()); Render($screen, $document->Render());
@ -222,6 +290,7 @@ void GUI::render_scene() {
} }
int GUI::main() { int GUI::main() {
configure_world();
create_renderer(); create_renderer();
while($window.isOpen()) { while($window.isOpen()) {

@ -10,8 +10,8 @@
#include <ftxui/dom/canvas.hpp> #include <ftxui/dom/canvas.hpp>
#include <locale> #include <locale>
#include <string> #include <string>
#include "entity.hpp"
#include "map.hpp" #include "map.hpp"
#include "dinkyecs.hpp"
using std::string; using std::string;
using ftxui::Canvas, ftxui::Component, ftxui::Screen; using ftxui::Canvas, ftxui::Component, ftxui::Screen;
@ -42,9 +42,6 @@ class GUI {
sf::Sound $hit_sound; sf::Sound $hit_sound;
bool $show_paths = false; bool $show_paths = false;
string $status_text = "NOT DEAD"; string $status_text = "NOT DEAD";
Entity $player;
Entity $enemy;
Point $goal;
Component $document; Component $document;
Component $map_view; Component $map_view;
Canvas $canvas; Canvas $canvas;
@ -57,6 +54,7 @@ class GUI {
sf::RenderWindow $window; sf::RenderWindow $window;
Screen $screen; Screen $screen;
Screen $map_screen; Screen $map_screen;
DinkyECS::World $world;
public: public:
GUI(); 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 draw_screen(bool clear=true, float map_off_x=0.0f, float map_off_y=0.0f);
void shake(); void shake();
void burn(); void burn();
void configure_world();
int main(); int main();
}; };

@ -271,11 +271,11 @@ bool Map::walk(Point &src, Point &target) {
return false; 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; $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; $input_map[at.y][at.x] = 1;
} }

@ -75,8 +75,8 @@ public:
void place_rooms(Room &root); void place_rooms(Room &root);
void make_paths(); void make_paths();
void partition_map(Room &cur, int depth); void partition_map(Room &cur, int depth);
void set_target(Point &at, int value=0); void set_target(const Point &at, int value=0);
void clear_target(Point &at); void clear_target(const Point &at);
bool walk(Point &src, Point &target); bool walk(Point &src, Point &target);
void set_door(Room &room, int value); void set_door(Room &room, int value);
void dump(); void dump();

@ -17,7 +17,6 @@ dependencies = [catch2, fmt,
runtests = executable('runtests', [ runtests = executable('runtests', [
'dbc.cpp', 'dbc.cpp',
'map.cpp', 'map.cpp',
'entity.cpp',
'rand.cpp', 'rand.cpp',
'tests/fsm.cpp', 'tests/fsm.cpp',
'tests/dbc.cpp', 'tests/dbc.cpp',
@ -31,7 +30,6 @@ roguish = executable('roguish', [
'map.cpp', 'map.cpp',
'gui.cpp', 'gui.cpp',
'rand.cpp', 'rand.cpp',
'entity.cpp',
], ],
dependencies: dependencies) dependencies: dependencies)

@ -4,7 +4,12 @@
#include <fmt/core.h> #include <fmt/core.h>
using namespace fmt; using namespace fmt;
using DinkyECS::Entity, DinkyECS::World; using DinkyECS::Entity;
struct Player {
std::string name;
Entity eid;
};
struct Position { struct Position {
double x, y; double x, y;
@ -18,63 +23,104 @@ struct Gravity {
double level; 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<Position>(test, {10,20});
world.assign<Velocity>(test, {1,2});
world.assign<Position>(test2, {1,1});
world.assign<Velocity>(test2, {10,20});
Entity test = me.entity(); println("---- Setting up the player as a fact in the system.");
Entity test2 = me.entity();
me.assign<Position>(test, {10,20}); auto player_eid = world.entity();
me.assign<Velocity>(test, {1,2}); Player player_info{"Zed", player_eid};
// just set some player info as a fact with the entity id
world.set<Player>(player_info);
me.assign<Position>(test2, {1,1}); world.assign<Velocity>(player_eid, {0,0});
me.assign<Velocity>(test2, {10,20}); world.assign<Position>(player_eid, {0,0});
Position &pos = me.component<Position>(test); auto enemy = world.entity();
world.assign<Velocity>(enemy, {0,0});
world.assign<Position>(enemy, {0,0});
println("--- Creating facts (singletons)");
world.set<Gravity>({0.9});
}
int main() {
DinkyECS::World world;
Entity test = world.entity();
configure(world, test);
Position &pos = world.component<Position>(test);
println("GOT POS x={}, y={}", pos.x, pos.y); println("GOT POS x={}, y={}", pos.x, pos.y);
Velocity &vel = me.component<Velocity>(test); Velocity &vel = world.component<Velocity>(test);
println("GOT VELOCITY x={}, y={}", vel.x, vel.y); println("GOT VELOCITY x={}, y={}", vel.x, vel.y);
println("--- Position only system:"); println("--- Position only system:");
me.system<Position>([](const auto &ent, auto &pos) { world.system<Position>([](const auto &ent, auto &pos) {
println("entity={}, pos.x={}, pos.y={}", ent, pos.x, pos.y); println("entity={}, pos.x={}, pos.y={}", ent, pos.x, pos.y);
}); });
println("--- Velocity only system:"); println("--- Velocity only system:");
me.system<Velocity>([](const auto &, auto &vel) { world.system<Velocity>([](const auto &, auto &vel) {
println("vel.x={}, vel.y={}", vel.x, vel.y); println("vel.x={}, vel.y={}", vel.x, vel.y);
}); });
println("--- Manually get the velocity in position system:"); println("--- Manually get the velocity in position system:");
me.system<Position>([&](const auto &ent, auto &pos) { world.system<Position>([&](const auto &ent, auto &pos) {
Velocity &vel = me.component<Velocity>(ent); Velocity &vel = world.component<Velocity>(ent);
println("entity={}, vel.x, vel.y, pos.x={}, pos.y={}", ent, vel.x, vel.y, pos.x, pos.y); 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<Gravity>({0.9});
println("--- Query only entities with Position and Velocity:"); println("--- Query only entities with Position and Velocity:");
me.system<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) { world.system<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) {
Gravity &grav = me.get<Gravity>(); Gravity &grav = world.get<Gravity>();
println("grav={}, entity={}, vel.x, vel.y, pos.x={}, pos.y={}", grav.level, ent, vel.x, vel.y, pos.x, pos.y); 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 // now remove Velocity
me.remove<Velocity>(test); world.remove<Velocity>(test);
println("--- After remove test, should only result in test2:"); println("--- After remove test, should only result in test2:");
me.system<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) { world.system<Position, Velocity>([&](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("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."); println("--- Create a stored system you can save for later.");
auto movementSystem = me.runner<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) { auto movementSystem = world.runner<Position, Velocity>([&](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("entity={}, vel.x, vel.y, pos.x={}, pos.y={}", ent, vel.x, vel.y, pos.x, pos.y);
}); });
movementSystem(); 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<Player>(); // grabbed it
world.system<Position>([&](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; return 0;
} }

Loading…
Cancel
Save