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.

This commit is contained in:
Zed A. Shaw 2024-10-16 15:01:53 -04:00
parent 86c98c43c2
commit 33327154ad
9 changed files with 195 additions and 149 deletions

153
gui.cpp
View file

@ -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 $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!";
}
// 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)};
}
});
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!";
}
// 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
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<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;
}
});
};
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()) {