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

JackAshwell11 / Hades / 14865985210

06 May 2025 05:28PM UTC coverage: 83.323% (+0.09%) from 83.234%
14865985210

push

github

JackAshwell11
Moved `Game.on_fixed_update()` as well as a majority of `Game.on_update()` and `Game.on_key_release()` over to `GameEngine` further reducing the Python module size.

All the methods of `InventorySystem` now return `void` as returning a `bool` was only useful for the tests which can check the result a different way.

20 of 35 new or added lines in 4 files covered. (57.14%)

1 existing line in 1 file now uncovered.

1404 of 1685 relevant lines covered (83.32%)

11239.88 hits per line

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

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

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

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

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

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

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

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

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

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

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

69
void GameEngine::create_game_objects() {
22✔
70
  // Create the game objects ignoring empty and obstacle tiles
71
  const auto &grid{*generator_.get_grid().grid};
22✔
72
  for (auto i{0}; std::cmp_less(i, grid.size()); i++) {
13,222✔
73
    const auto tile_type{grid[i]};
13,200✔
74
    if (tile_type == TileType::Empty || tile_type == TileType::Obstacle) {
13,200✔
75
      continue;
1,586✔
76
    }
77

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

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

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

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

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

136
void GameEngine::on_update(const double /*delta_time*/) {
3✔
137
  nearest_item_ = registry_->get_system<PhysicsSystem>()->get_nearest_item(player_id_);
3✔
138
  if (nearest_item_ != -1 && registry_->get_game_object_type(nearest_item_) == GameObjectType::Goal) {
3✔
139
    registry_->delete_game_object(player_id_);
1✔
140
  }
141
}
3✔
142

143
void GameEngine::on_fixed_update(const double delta_time) const { registry_->update(delta_time); }
1✔
144

145
void GameEngine::on_key_press(const int symbol, const int /*modifiers*/) const {
9✔
146
  const auto player_movement{registry_->get_component<KeyboardMovement>(player_id_)};
9✔
147
  switch (symbol) {
9✔
148
    case KEY_W:
2✔
149
      player_movement->moving_north = true;
2✔
150
      break;
2✔
151
    case KEY_A:
2✔
152
      player_movement->moving_west = true;
2✔
153
      break;
2✔
154
    case KEY_S:
2✔
155
      player_movement->moving_south = true;
2✔
156
      break;
2✔
157
    case KEY_D:
2✔
158
      player_movement->moving_east = true;
2✔
159
      break;
2✔
160
    default:
1✔
161
      break;
1✔
162
  }
163
}
9✔
164

165
void GameEngine::on_key_release(const int symbol, const int /*modifiers*/) const {
5✔
166
  const auto player_movement{registry_->get_component<KeyboardMovement>(player_id_)};
5✔
167
  switch (symbol) {
5✔
168
    case KEY_W:
1✔
169
      player_movement->moving_north = false;
1✔
170
      break;
1✔
171
    case KEY_A:
1✔
172
      player_movement->moving_west = false;
1✔
173
      break;
1✔
174
    case KEY_S:
1✔
175
      player_movement->moving_south = false;
1✔
176
      break;
1✔
177
    case KEY_D:
1✔
178
      player_movement->moving_east = false;
1✔
179
      break;
1✔
NEW
180
    case KEY_C:
×
NEW
181
      registry_->get_system<InventorySystem>()->add_item_to_inventory(player_id_, nearest_item_);
×
NEW
182
      break;
×
NEW
183
    case KEY_E:
×
NEW
184
      registry_->get_system<InventorySystem>()->use_item(player_id_, nearest_item_);
×
NEW
185
      break;
×
NEW
186
    case KEY_Z:
×
NEW
187
      registry_->get_component<Attack>(player_id_)->previous_ranged_attack();
×
NEW
188
      break;
×
NEW
189
    case KEY_X:
×
NEW
190
      registry_->get_component<Attack>(player_id_)->next_ranged_attack();
×
NEW
191
      break;
×
192
    default:
1✔
193
      break;
1✔
194
  }
195
}
5✔
196

197
auto GameEngine::on_mouse_press(const double /*x*/, const double /*y*/, const int button, const int /*modifiers*/) const
2✔
198
    -> bool {
199
  if (button == MOUSE_BUTTON_LEFT) {
2✔
200
    return registry_->get_system<AttackSystem>()->do_attack(player_id_, AttackType::Ranged);
1✔
201
  }
202
  return false;
1✔
203
}
204

205
auto GameEngine::get_game_object_components(const GameObjectType game_object_type)
11,753✔
206
    -> std::vector<std::shared_ptr<ComponentBase>> {
207
  const auto &factories{get_factories()};
11,753✔
208
  return factories.at(game_object_type)(std::max(0, static_cast<int>(level_distribution_(random_generator_))));
11,753✔
209
}
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