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);
};
}
};
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 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{
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<Player>();
$map_view = Renderer([&, player] {
const auto& player_position = $world.component<Position>(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<Position, Tile>([&](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<Combat>(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<Player>();
auto& player_motion = $world.component<Motion>(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 enemies system
$world.system<Position, Motion>([&](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<Position, Motion>([&](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
// 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($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) {
$player.event(EntityEvent::HIT);
// combat system
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;
} 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() {
$hit_sound.play();
for(int i = 0; i < 10; ++i) {
int x = 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() {
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()) {

@ -10,8 +10,8 @@
#include <ftxui/dom/canvas.hpp>
#include <locale>
#include <string>
#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();
};

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

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

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

@ -4,7 +4,12 @@
#include <fmt/core.h>
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<Position>(test, {10,20});
world.assign<Velocity>(test, {1,2});
world.assign<Position>(test2, {1,1});
world.assign<Velocity>(test2, {10,20});
println("---- Setting up the player as a fact in the system.");
Entity test = me.entity();
Entity test2 = me.entity();
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>(player_info);
me.assign<Position>(test, {10,20});
me.assign<Velocity>(test, {1,2});
world.assign<Velocity>(player_eid, {0,0});
world.assign<Position>(player_eid, {0,0});
me.assign<Position>(test2, {1,1});
me.assign<Velocity>(test2, {10,20});
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();
Position &pos = me.component<Position>(test);
configure(world, test);
Position &pos = world.component<Position>(test);
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("--- 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("--- 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("--- Manually get the velocity in position system:");
me.system<Position>([&](const auto &ent, auto &pos) {
Velocity &vel = me.component<Velocity>(ent);
world.system<Position>([&](const auto &ent, auto &pos) {
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("--- Creating facts (singletons)");
me.set<Gravity>({0.9});
println("--- Query only entities with Position and Velocity:");
me.system<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) {
Gravity &grav = me.get<Gravity>();
world.system<Position, Velocity>([&](const auto &ent, auto &pos, auto &vel) {
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);
});
// now remove Velocity
me.remove<Velocity>(test);
world.remove<Velocity>(test);
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("--- 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);
});
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;
}

Loading…
Cancel
Save