Video Coming Soon...

Created by Zed A. Shaw Updated 2026-06-19 17:00:40

64: Pulling it All Together

We're going to use everything we've learned so far to create an ECS based combat engine.

The Code

I think I need to make the systems a class to make sure they use them, instead of a namespace/module style like I like to do:

View Source file ex64.cpp Only

#include <fmt/core.h>
#include <string>
#include <random>
#include <memory>
#include <algorithm>
#include <unordered_map>
#include <vector>
#include <ranges>
#include "dinkyecs.hpp"

using std::shared_ptr, std::make_shared, std::string;
using namespace DinkyECS;

std::random_device RNG;
std::mt19937 GENERATOR(RNG());

struct Combat {
  int hp{0};
  bool dead{false};
};

struct Weapon {
  std::string name;
  int damage;
};

struct Actor {
  string name;
};

struct Game {
  World world;
  Entity player_id;
};

namespace systems {
  int random_number(int from, int to) {
    std::uniform_int_distribution rand(from, to);
    return rand(GENERATOR);
  }

  Entity create_player(World& world) {
    // player system
    auto player = world.entity();
    world.set<Combat>(player, {.hp=30});
    world.set<Weapon>(player, {.name="Sword", .damage=30});
    world.set<Actor>(player, {.name="Zed"});

    return player;
  }

  void spawn_enemies(World& world, int count) {
    for(int i = 0; i < count; i++) {
      auto rat_id = world.entity();
      int hp = random_number(3, 10);
      int damage = random_number(1,5);

      world.set<Combat>(rat_id, {.hp=hp});
      world.set<Weapon>(rat_id, {.damage=damage});
      world.set<Actor>(rat_id, {fmt::format("Rat {}", rat_id)});
    }
  }

  int run_combat(World& world) {
    int actor_count = 0;

    // combat system
    world.query<Combat, Weapon>([&](auto id, auto& combat, auto& weapon) {
        // ideally we'd want to get some other actor to fight
        auto& actor = world.get<Actor>(id);

        actor_count++;
        int damage = random_number(1, weapon.damage);

        combat.hp -= damage;

        fmt::println("{}: hp={}; damage={}; id={}",
            actor.name, combat.hp, damage, id);

        if(combat.hp <= 0) combat.dead = true;
    });

    return actor_count;
  }

  void cull_dead(World& world) {
    // death system
    std::vector<Entity> is_dead;

    world.query<Combat>([&](auto id, auto& combat) {
      if(combat.dead) is_dead.push_back(id);
    });

    for(auto id : is_dead) {
      auto& actor = world.get<Actor>(id);

      fmt::println("Removing dead: {}", actor.name);

      world.remove<Combat>(id);
      world.remove<Actor>(id);
      world.remove<Weapon>(id);
    }
  }

  bool player_wins(World& world, Entity player) {
    if(auto combat = world.get_if<Combat>(player)) {
      return combat->hp > 0;
    } else {
      return false;
    }
  }
}

int main(int argc, char* argv[]) {
  Game game;

  game.player_id = systems::create_player(game.world);
  systems::spawn_enemies(game.world, 10);

  while(systems::run_combat(game.world) > 1) {
    systems::cull_dead(game.world);
  }

  if(systems::player_wins(game.world, game.player_id)) {
    fmt::println("You won!");
  } else {
    fmt::println("You lose!");
  }
}

The Breakdown

line of code
Description.

The Discussion

Blah blah.

Further Study

Previous Lesson Back to Module

Register for Learn C++ the Hard Way

Register to gain access to additional videos which demonstrate each exercise. Videos are priced to cover the cost of hosting.