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

JackAshwell11 / Hades / 15164010934

21 May 2025 01:50PM UTC coverage: 68.206% (-16.2%) from 84.443%
15164010934

push

github

JackAshwell11
Rewrote `PlayerView` into a new `Player` class following the MVC pattern. Some bugs and improvements remain, including the tests, which will be addressed in future commits.

Refactored `window.views` meaning all view initialisations are now handled centrally in `window.py`.

Added `HadesWindow.save_background()`` to store the current blurred game image centrally, removing the need for each view to manage its own copy.

0 of 293 new or added lines in 7 files covered. (0.0%)

45 existing lines in 8 files now uncovered.

1270 of 1862 relevant lines covered (68.21%)

12440.7 hits per line

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

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

4
// Std headers
5
#include <utility>
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)
30✔
32
    : level_(level),
30✔
33
      random_generator_(seed.has_value() ? seed.value() : std::random_device{}()),
30✔
34
      level_distribution_(level, LEVEL_DISTRIBUTION_DEVIATION),
30✔
35
      registry_(std::make_shared<Registry>(random_generator_)),
31✔
36
      player_id_(-1) {
31✔
37
  if (level < 0) {
30✔
38
    throw std::length_error("Level must be bigger than or equal to 0.");
1✔
39
  }
40
  generator_ = MapGenerator{level, random_generator_};
29✔
41
  generator_.generate_rooms()
29✔
42
      .place_obstacles()
29✔
43
      .create_connections()
29✔
44
      .generate_hallways()
29✔
45
      .cellular_automata(CELLULAR_AUTOMATA_SIMULATIONS)
29✔
46
      .generate_walls()
29✔
47
      .place_player()
29✔
48
      .place_items()
29✔
49
      .place_goal();
29✔
50

51
  // Add the systems to the registry
52
  registry_->add_system<ArmourRegenSystem>();
29✔
53
  registry_->add_system<AttackSystem>();
29✔
54
  registry_->add_system<DamageSystem>();
29✔
55
  registry_->add_system<EffectSystem>();
29✔
56
  registry_->add_system<FootprintSystem>();
29✔
57
  registry_->add_system<InventorySystem>();
29✔
58
  registry_->add_system<KeyboardMovementSystem>();
29✔
59
  registry_->add_system<PhysicsSystem>();
29✔
60
  registry_->add_system<SteeringMovementSystem>();
29✔
61
  registry_->add_system<UpgradeSystem>();
29✔
62
}
31✔
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() {
26✔
69
  // Create the game objects ignoring empty and obstacle tiles
70
  const auto &grid{*generator_.get_grid().grid};
26✔
71
  for (auto i{0}; std::cmp_less(i, grid.size()); i++) {
15,626✔
72
    const auto tile_type{grid[i]};
15,600✔
73
    if (tile_type == TileType::Empty || tile_type == TileType::Obstacle) {
15,600✔
74
      continue;
1,846✔
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
    };
13,756✔
86
    const auto game_object_type{tile_to_game_object_type.at(tile_type)};
13,754✔
87
    const auto [x, y]{generator_.get_grid().convert_position(i)};
13,754✔
88
    if (tile_type != TileType::Wall && tile_type != TileType::Floor) {
13,754✔
89
      registry_->create_game_object(GameObjectType::Floor, cpv(x, y),
312✔
90
                                    get_game_object_components(GameObjectType::Floor));
312✔
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))};
13,754✔
94
    if (tile_type == TileType::Player) {
13,754✔
95
      player_id_ = game_object_id;
26✔
96
    }
97
  }
98
}
26✔
99

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

105
  // Collect all floor positions and shuffle them
106
  const auto &grid{*generator_.get_grid().grid};
12✔
107
  std::vector<cpVect> floor_positions;
12✔
108
  for (auto i{0}; std::cmp_less(i, grid.size()); i++) {
7,212✔
109
    if (grid[i] == TileType::Floor) {
7,200✔
110
      const auto [x, y]{generator_.get_grid().convert_position(i)};
4,584✔
111
      floor_positions.push_back(cpv(x, y));
4,584✔
112
    }
113
  }
114
  std::ranges::shuffle(floor_positions, random_generator_);
12✔
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++) {
12✔
118
    const auto position{floor_positions[attempt]};
12✔
119
    if (const auto player_position{cpBodyGetPosition(*registry_->get_component<KinematicComponent>(player_id_)->body)};
12✔
120
        cpSpacePointQueryNearest(registry_->get_space(), position, 0.0,
10✔
121
                                 {CP_NO_GROUP, CP_ALL_CATEGORIES, static_cast<cpBitmask>(GameObjectType::Enemy)},
122
                                 nullptr) != nullptr ||
20✔
123
        cpvdist(position, player_position) < ENEMY_GENERATION_DISTANCE * SPRITE_SIZE) {
10✔
UNCOV
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,
20✔
129
                                                      get_game_object_components(GameObjectType::Enemy))};
20✔
130
    registry_->get_component<SteeringMovement>(enemy_id)->target_id = player_id_;
10✔
131
    return;
10✔
132
  }
133
}
12✔
134

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

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

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

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

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

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