• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JackAshwell11 / Hades / 14553372842

19 Apr 2025 10:19PM UTC coverage: 83.936% (-0.6%) from 84.508%
14553372842

push

github

JackAshwell11
Added functionality to run multiple episodes of a trained model and evaluate its performance through graphs and other statistics. This should allow models to be properly compared against each other to determine the best performing one.

Introduced `EpisodeResults` to collate the results of processing an episode and moved `plot_graphs()`'s functionality into `train_dqn()` allowing `plot_metric()` to be used by other functions as well.

1369 of 1631 relevant lines covered (83.94%)

9789.99 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.12
/src/hades_extensions/src/game_engine.cpp
1
// Related header
2
#include "game_engine.hpp"
3

4
// Std headers
5
#include <algorithm>
6

7
// Local headers
8
#include "ecs/systems/armour_regen.hpp"
9
#include "ecs/systems/attacks.hpp"
10
#include "ecs/systems/effects.hpp"
11
#include "ecs/systems/inventory.hpp"
12
#include "ecs/systems/movements.hpp"
13
#include "ecs/systems/physics.hpp"
14
#include "ecs/systems/upgrade.hpp"
15
#include "factories.hpp"
16

17
namespace {
18
// The deviation of the level distribution.
19
constexpr int LEVEL_DISTRIBUTION_DEVIATION{2};
20

21
// The number of cellular automata runs to perform.
22
constexpr int CELLULAR_AUTOMATA_SIMULATIONS{3};
23

24
// The distance from the player to generate an enemy.
25
constexpr int ENEMY_GENERATION_DISTANCE{5};
26

27
// How many times an enemy should be attempted to be generated.
28
constexpr int ENEMY_RETRY_ATTEMPTS{3};
29
}  // namespace
30

31
GameEngine::GameEngine(const int level, const std::optional<unsigned int> seed)
22✔
32
    : level_(level),
22✔
33
      random_generator_(seed.has_value() ? seed.value() : std::random_device{}()),
22✔
34
      level_distribution_(level, LEVEL_DISTRIBUTION_DEVIATION),
22✔
35
      registry_(std::make_shared<Registry>(random_generator_)),
22✔
36
      player_id_(-1) {
22✔
37
  if (level < 0) {
22✔
38
    throw std::length_error("Level must be bigger than or equal to 0.");
1✔
39
  }
40
  generator_ = MapGenerator{level, random_generator_};
21✔
41
  generator_.generate_rooms()
21✔
42
      .place_obstacles()
21✔
43
      .create_connections()
21✔
44
      .generate_hallways()
21✔
45
      .cellular_automata(CELLULAR_AUTOMATA_SIMULATIONS)
21✔
46
      .generate_walls()
21✔
47
      .place_player()
21✔
48
      .place_items()
21✔
49
      .place_goal();
21✔
50

51
  // Add the systems to the registry
52
  registry_->add_system<ArmourRegenSystem>();
21✔
53
  registry_->add_system<AttackSystem>();
21✔
54
  registry_->add_system<DamageSystem>();
21✔
55
  registry_->add_system<EffectSystem>();
21✔
56
  registry_->add_system<FootprintSystem>();
21✔
57
  registry_->add_system<InventorySystem>();
21✔
58
  registry_->add_system<KeyboardMovementSystem>();
21✔
59
  registry_->add_system<PhysicsSystem>();
21✔
60
  registry_->add_system<SteeringMovementSystem>();
21✔
61
  registry_->add_system<UpgradeSystem>();
21✔
62
}
23✔
63

64
auto GameEngine::get_level_constants() -> std::tuple<int, int, int> {
4✔
65
  return {generator_.get_grid().width, generator_.get_grid().height, generator_.get_enemy_limit()};
4✔
66
}
67

68
void GameEngine::create_game_objects() {
18✔
69
  // Create the game objects ignoring empty and obstacle tiles
70
  const auto &grid{*generator_.get_grid().grid};
18✔
71
  for (auto i{0}; i < static_cast<int>(grid.size()); i++) {
10,818✔
72
    const auto tile_type{grid[i]};
10,800✔
73
    if (tile_type == TileType::Empty || tile_type == TileType::Obstacle) {
10,800✔
74
      continue;
1,239✔
75
    }
76

77
    // If the tile is not a wall tile, we want an extra floor tile placed at the same position
78
    static const std::unordered_map<TileType, GameObjectType> tile_to_game_object_type{
79
        {TileType::Floor, GameObjectType::Floor},
80
        {TileType::Wall, GameObjectType::Wall},
81
        {TileType::Goal, GameObjectType::Goal},
82
        {TileType::Player, GameObjectType::Player},
83
        {TileType::HealthPotion, GameObjectType::HealthPotion},
84
        {TileType::Chest, GameObjectType::Chest},
85
    };
9,563✔
86
    const auto game_object_type{tile_to_game_object_type.at(tile_type)};
9,561✔
87
    const auto [x, y]{generator_.get_grid().convert_position(i)};
9,561✔
88
    if (tile_type != TileType::Wall && tile_type != TileType::Floor) {
9,561✔
89
      registry_->create_game_object(GameObjectType::Floor, cpv(x, y),
216✔
90
                                    get_game_object_components(GameObjectType::Floor));
216✔
91
    }
92
    const auto game_object_id{
93
        registry_->create_game_object(game_object_type, cpv(x, y), get_game_object_components(game_object_type))};
9,561✔
94
    if (tile_type == TileType::Player) {
9,561✔
95
      player_id_ = game_object_id;
18✔
96
    }
97
  }
98
}
18✔
99

100
void GameEngine::generate_enemy(const double /*delta_time*/) {
15✔
101
  if (static_cast<int>(registry_->get_game_object_ids(GameObjectType::Enemy).size()) >= generator_.get_enemy_limit()) {
15✔
102
    return;
13✔
103
  }
104

105
  // Collect all floor positions and shuffle them
106
  const auto &grid{*generator_.get_grid().grid};
9✔
107
  std::vector<cpVect> floor_positions;
9✔
108
  for (auto i{0}; i < static_cast<int>(grid.size()); i++) {
5,409✔
109
    if (grid[i] == TileType::Floor) {
5,400✔
110
      const auto [x, y]{generator_.get_grid().convert_position(i)};
3,438✔
111
      floor_positions.push_back(cpv(x, y));
3,438✔
112
    }
113
  }
114
  std::ranges::shuffle(floor_positions, random_generator_);
9✔
115

116
  // Determine which floor to place the enemy on only trying ENEMY_RETRY_ATTEMPTS times
117
  for (auto attempt{0}; attempt < std::min(static_cast<int>(floor_positions.size()), ENEMY_RETRY_ATTEMPTS); attempt++) {
9✔
118
    const auto position{floor_positions[attempt]};
9✔
119
    if (const auto player_position{cpBodyGetPosition(*registry_->get_component<KinematicComponent>(player_id_)->body)};
9✔
120
        cpSpacePointQueryNearest(registry_->get_space(), position, 0.0,
7✔
121
                                 {CP_NO_GROUP, CP_ALL_CATEGORIES, static_cast<cpBitmask>(GameObjectType::Enemy)},
122
                                 nullptr) != nullptr ||
14✔
123
        cpvdist(position, player_position) < ENEMY_GENERATION_DISTANCE * SPRITE_SIZE) {
7✔
124
      continue;
×
125
    }
126

127
    // Create the enemy and set its required data
128
    const auto enemy_id{registry_->create_game_object(GameObjectType::Enemy, position,
14✔
129
                                                      get_game_object_components(GameObjectType::Enemy))};
14✔
130
    registry_->get_component<SteeringMovement>(enemy_id)->target_id = player_id_;
7✔
131
    return;
7✔
132
  }
133
}
9✔
134

135
void GameEngine::on_key_press(const int symbol, const int /*modifiers*/) const {
9✔
136
  const auto player_movement{registry_->get_component<KeyboardMovement>(player_id_)};
9✔
137
  switch (symbol) {
9✔
138
    case KEY_W:
2✔
139
      player_movement->moving_north = true;
2✔
140
      break;
2✔
141
    case KEY_A:
2✔
142
      player_movement->moving_west = true;
2✔
143
      break;
2✔
144
    case KEY_S:
2✔
145
      player_movement->moving_south = true;
2✔
146
      break;
2✔
147
    case KEY_D:
2✔
148
      player_movement->moving_east = true;
2✔
149
      break;
2✔
150
    default:
1✔
151
      break;
1✔
152
  }
153
}
9✔
154

155
void GameEngine::on_key_release(const int symbol, const int /*modifiers*/) const {
5✔
156
  const auto player_movement{registry_->get_component<KeyboardMovement>(player_id_)};
5✔
157
  switch (symbol) {
5✔
158
    case KEY_W:
1✔
159
      player_movement->moving_north = false;
1✔
160
      break;
1✔
161
    case KEY_A:
1✔
162
      player_movement->moving_west = false;
1✔
163
      break;
1✔
164
    case KEY_S:
1✔
165
      player_movement->moving_south = false;
1✔
166
      break;
1✔
167
    case KEY_D:
1✔
168
      player_movement->moving_east = false;
1✔
169
      break;
1✔
170
    default:
1✔
171
      break;
1✔
172
  }
173
}
5✔
174

175
auto GameEngine::on_mouse_press(const double /*x*/, const double /*y*/, const int button, const int /*modifiers*/) const
2✔
176
    -> bool {
177
  if (button == MOUSE_BUTTON_LEFT) {
2✔
178
    return registry_->get_system<AttackSystem>()->do_attack(player_id_,
3✔
179
                                                            registry_->get_game_object_ids(GameObjectType::Enemy));
2✔
180
  }
181
  return false;
1✔
182
}
183

184
auto GameEngine::get_game_object_components(const GameObjectType game_object_type)
9,676✔
185
    -> std::vector<std::shared_ptr<ComponentBase>> {
186
  const auto &factories{get_factories()};
9,676✔
187
  return factories.at(game_object_type)(std::max(0, static_cast<int>(level_distribution_(random_generator_))));
9,676✔
188
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc