trianglengin 2.0.1__cp312-cp312-musllinux_1_2_i686.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trianglengin/__init__.py +35 -0
- trianglengin/config/README.md +38 -0
- trianglengin/config/__init__.py +8 -0
- trianglengin/config/display_config.py +47 -0
- trianglengin/config/env_config.py +62 -0
- trianglengin/core/__init__.py +10 -0
- trianglengin/cpp/CMakeLists.txt +42 -0
- trianglengin/cpp/bindings.cpp +211 -0
- trianglengin/cpp/config.h +28 -0
- trianglengin/cpp/game_state.cpp +327 -0
- trianglengin/cpp/game_state.h +73 -0
- trianglengin/cpp/grid_data.cpp +239 -0
- trianglengin/cpp/grid_data.h +78 -0
- trianglengin/cpp/grid_logic.cpp +125 -0
- trianglengin/cpp/grid_logic.h +30 -0
- trianglengin/cpp/shape_logic.cpp +100 -0
- trianglengin/cpp/shape_logic.h +28 -0
- trianglengin/cpp/structs.h +40 -0
- trianglengin/game_interface.py +222 -0
- trianglengin/py.typed +0 -0
- trianglengin/trianglengin_cpp.cpython-312-i386-linux-musl.so +0 -0
- trianglengin/trianglengin_cpp.cpython-37m-i386-linux-gnu.so +0 -0
- trianglengin/ui/README.md +35 -0
- trianglengin/ui/__init__.py +21 -0
- trianglengin/ui/app.py +107 -0
- trianglengin/ui/cli.py +123 -0
- trianglengin/ui/config.py +44 -0
- trianglengin/ui/interaction/README.md +44 -0
- trianglengin/ui/interaction/__init__.py +19 -0
- trianglengin/ui/interaction/debug_mode_handler.py +72 -0
- trianglengin/ui/interaction/event_processor.py +49 -0
- trianglengin/ui/interaction/input_handler.py +89 -0
- trianglengin/ui/interaction/play_mode_handler.py +156 -0
- trianglengin/ui/visualization/README.md +42 -0
- trianglengin/ui/visualization/__init__.py +58 -0
- trianglengin/ui/visualization/core/README.md +51 -0
- trianglengin/ui/visualization/core/__init__.py +16 -0
- trianglengin/ui/visualization/core/colors.py +115 -0
- trianglengin/ui/visualization/core/coord_mapper.py +85 -0
- trianglengin/ui/visualization/core/fonts.py +65 -0
- trianglengin/ui/visualization/core/layout.py +77 -0
- trianglengin/ui/visualization/core/visualizer.py +248 -0
- trianglengin/ui/visualization/drawing/README.md +49 -0
- trianglengin/ui/visualization/drawing/__init__.py +43 -0
- trianglengin/ui/visualization/drawing/grid.py +213 -0
- trianglengin/ui/visualization/drawing/highlight.py +31 -0
- trianglengin/ui/visualization/drawing/hud.py +43 -0
- trianglengin/ui/visualization/drawing/previews.py +181 -0
- trianglengin/ui/visualization/drawing/shapes.py +46 -0
- trianglengin/ui/visualization/drawing/utils.py +23 -0
- trianglengin/utils/__init__.py +9 -0
- trianglengin/utils/geometry.py +62 -0
- trianglengin/utils/types.py +10 -0
- trianglengin-2.0.1.dist-info/METADATA +250 -0
- trianglengin-2.0.1.dist-info/RECORD +61 -0
- trianglengin-2.0.1.dist-info/WHEEL +5 -0
- trianglengin-2.0.1.dist-info/entry_points.txt +2 -0
- trianglengin-2.0.1.dist-info/licenses/LICENSE +22 -0
- trianglengin-2.0.1.dist-info/top_level.txt +1 -0
- trianglengin.libs/libgcc_s-f3fb5a36.so.1 +0 -0
- trianglengin.libs/libstdc++-d2a021ba.so.6.0.32 +0 -0
@@ -0,0 +1,327 @@
|
|
1
|
+
// File: src/trianglengin/cpp/game_state.cpp
|
2
|
+
#include "game_state.h"
|
3
|
+
#include "grid_logic.h"
|
4
|
+
#include "shape_logic.h"
|
5
|
+
#include <stdexcept>
|
6
|
+
#include <numeric>
|
7
|
+
#include <iostream>
|
8
|
+
#include <algorithm> // For std::min
|
9
|
+
|
10
|
+
namespace trianglengin::cpp
|
11
|
+
{
|
12
|
+
|
13
|
+
GameStateCpp::GameStateCpp(const EnvConfigCpp &config, unsigned int initial_seed)
|
14
|
+
: config_(config),
|
15
|
+
grid_data_(config_),
|
16
|
+
shapes_(config_.num_shape_slots),
|
17
|
+
score_(0.0),
|
18
|
+
current_step_(0),
|
19
|
+
game_over_(false),
|
20
|
+
rng_(initial_seed)
|
21
|
+
{
|
22
|
+
reset();
|
23
|
+
}
|
24
|
+
|
25
|
+
void GameStateCpp::reset()
|
26
|
+
{
|
27
|
+
grid_data_.reset();
|
28
|
+
std::fill(shapes_.begin(), shapes_.end(), std::nullopt);
|
29
|
+
score_ = 0.0;
|
30
|
+
current_step_ = 0;
|
31
|
+
game_over_ = false;
|
32
|
+
game_over_reason_ = std::nullopt;
|
33
|
+
valid_actions_cache_ = std::nullopt;
|
34
|
+
shape_logic::refill_shape_slots(*this, rng_);
|
35
|
+
check_initial_state_game_over();
|
36
|
+
}
|
37
|
+
|
38
|
+
void GameStateCpp::check_initial_state_game_over()
|
39
|
+
{
|
40
|
+
get_valid_actions(true);
|
41
|
+
if (!game_over_ && valid_actions_cache_ && valid_actions_cache_->empty())
|
42
|
+
{
|
43
|
+
force_game_over("No valid actions available at start.");
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
std::tuple<double, bool> GameStateCpp::step(Action action)
|
48
|
+
{
|
49
|
+
if (game_over_)
|
50
|
+
{
|
51
|
+
return {0.0, true};
|
52
|
+
}
|
53
|
+
|
54
|
+
const auto &valid_actions = get_valid_actions();
|
55
|
+
if (valid_actions.find(action) == valid_actions.end())
|
56
|
+
{
|
57
|
+
// Invalid action detected
|
58
|
+
force_game_over("Invalid action provided: " + std::to_string(action));
|
59
|
+
// Apply penalty to score *before* returning
|
60
|
+
score_ += config_.penalty_game_over;
|
61
|
+
return {config_.penalty_game_over, true}; // Return penalty and done=true
|
62
|
+
}
|
63
|
+
|
64
|
+
int shape_idx, r, c;
|
65
|
+
try
|
66
|
+
{
|
67
|
+
std::tie(shape_idx, r, c) = decode_action(action);
|
68
|
+
}
|
69
|
+
catch (const std::out_of_range &e)
|
70
|
+
{
|
71
|
+
// Error during decoding (should be caught by valid_actions check, but defensive)
|
72
|
+
force_game_over("Failed to decode action: " + std::to_string(action));
|
73
|
+
score_ += config_.penalty_game_over; // Apply penalty
|
74
|
+
return {config_.penalty_game_over, true};
|
75
|
+
}
|
76
|
+
|
77
|
+
if (shape_idx < 0 || shape_idx >= static_cast<int>(shapes_.size()) || !shapes_[shape_idx].has_value())
|
78
|
+
{
|
79
|
+
// Action references invalid slot (should be caught by valid_actions check)
|
80
|
+
force_game_over("Action references invalid/empty shape slot: " + std::to_string(shape_idx));
|
81
|
+
score_ += config_.penalty_game_over; // Apply penalty
|
82
|
+
return {config_.penalty_game_over, true};
|
83
|
+
}
|
84
|
+
|
85
|
+
const ShapeCpp &shape_to_place = shapes_[shape_idx].value();
|
86
|
+
|
87
|
+
if (!grid_logic::can_place(grid_data_, shape_to_place, r, c))
|
88
|
+
{
|
89
|
+
// Should not happen if valid_actions is correct
|
90
|
+
force_game_over("Placement check failed for valid action (logic error?). Action: " + std::to_string(action));
|
91
|
+
score_ += config_.penalty_game_over; // Apply penalty
|
92
|
+
return {config_.penalty_game_over, true};
|
93
|
+
}
|
94
|
+
|
95
|
+
// --- Placement ---
|
96
|
+
std::set<Coord> newly_occupied_coords;
|
97
|
+
int placed_count = 0;
|
98
|
+
auto &occupied_grid = grid_data_.get_occupied_grid_mut();
|
99
|
+
auto &color_grid = grid_data_.get_color_id_grid_mut();
|
100
|
+
for (const auto &tri_data : shape_to_place.triangles)
|
101
|
+
{
|
102
|
+
int dr, dc;
|
103
|
+
bool is_up_ignored;
|
104
|
+
std::tie(dr, dc, is_up_ignored) = tri_data;
|
105
|
+
int target_r = r + dr;
|
106
|
+
int target_c = c + dc;
|
107
|
+
if (!grid_data_.is_valid(target_r, target_c) || grid_data_.is_death(target_r, target_c))
|
108
|
+
{
|
109
|
+
force_game_over("Attempted placement out of bounds/death zone during execution. Action: " + std::to_string(action));
|
110
|
+
score_ += config_.penalty_game_over; // Apply penalty
|
111
|
+
return {config_.penalty_game_over, true};
|
112
|
+
}
|
113
|
+
occupied_grid[target_r][target_c] = true;
|
114
|
+
color_grid[target_r][target_c] = static_cast<int8_t>(shape_to_place.color_id);
|
115
|
+
newly_occupied_coords.insert({target_r, target_c});
|
116
|
+
placed_count++;
|
117
|
+
}
|
118
|
+
shapes_[shape_idx] = std::nullopt;
|
119
|
+
|
120
|
+
// --- Line Clearing ---
|
121
|
+
int lines_cleared_count;
|
122
|
+
std::set<Coord> cleared_coords;
|
123
|
+
LineFsSet cleared_lines_fs;
|
124
|
+
std::tie(lines_cleared_count, cleared_coords, cleared_lines_fs) =
|
125
|
+
grid_logic::check_and_clear_lines(grid_data_, newly_occupied_coords);
|
126
|
+
int cleared_count = static_cast<int>(cleared_coords.size());
|
127
|
+
|
128
|
+
// --- Refill ---
|
129
|
+
bool all_slots_empty = true;
|
130
|
+
for (const auto &shape_opt : shapes_)
|
131
|
+
{
|
132
|
+
if (shape_opt.has_value())
|
133
|
+
{
|
134
|
+
all_slots_empty = false;
|
135
|
+
break;
|
136
|
+
}
|
137
|
+
}
|
138
|
+
if (all_slots_empty)
|
139
|
+
{
|
140
|
+
shape_logic::refill_shape_slots(*this, rng_);
|
141
|
+
}
|
142
|
+
|
143
|
+
// --- Update State & Check Game Over ---
|
144
|
+
current_step_++;
|
145
|
+
valid_actions_cache_ = std::nullopt;
|
146
|
+
get_valid_actions(true); // Force recalculation and update game_over_ if needed
|
147
|
+
// game_over_ flag is now definitive
|
148
|
+
|
149
|
+
// --- Calculate Reward & Update Score ---
|
150
|
+
double reward = 0.0;
|
151
|
+
reward += static_cast<double>(placed_count) * config_.reward_per_placed_triangle;
|
152
|
+
reward += static_cast<double>(cleared_count) * config_.reward_per_cleared_triangle;
|
153
|
+
|
154
|
+
if (game_over_)
|
155
|
+
{
|
156
|
+
reward += config_.penalty_game_over;
|
157
|
+
}
|
158
|
+
else
|
159
|
+
{
|
160
|
+
reward += config_.reward_per_step_alive;
|
161
|
+
}
|
162
|
+
score_ += reward; // Update score based on calculated reward
|
163
|
+
|
164
|
+
return {reward, game_over_};
|
165
|
+
}
|
166
|
+
|
167
|
+
bool GameStateCpp::is_over() const
|
168
|
+
{
|
169
|
+
if (game_over_)
|
170
|
+
return true;
|
171
|
+
if (valid_actions_cache_.has_value())
|
172
|
+
return valid_actions_cache_->empty();
|
173
|
+
calculate_valid_actions_internal();
|
174
|
+
return valid_actions_cache_->empty();
|
175
|
+
}
|
176
|
+
|
177
|
+
void GameStateCpp::force_game_over(const std::string &reason)
|
178
|
+
{
|
179
|
+
// This function only sets the flags and reason.
|
180
|
+
// Score update should happen in the context where the game over is triggered (e.g., step).
|
181
|
+
if (!game_over_)
|
182
|
+
{
|
183
|
+
game_over_ = true;
|
184
|
+
game_over_reason_ = reason;
|
185
|
+
valid_actions_cache_ = std::set<Action>();
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
double GameStateCpp::get_score() const
|
190
|
+
{
|
191
|
+
return score_;
|
192
|
+
}
|
193
|
+
|
194
|
+
const std::set<Action> &GameStateCpp::get_valid_actions(bool force_recalculate)
|
195
|
+
{
|
196
|
+
if (game_over_)
|
197
|
+
{
|
198
|
+
if (!valid_actions_cache_.has_value() || !valid_actions_cache_->empty())
|
199
|
+
{
|
200
|
+
valid_actions_cache_ = std::set<Action>();
|
201
|
+
}
|
202
|
+
return *valid_actions_cache_;
|
203
|
+
}
|
204
|
+
if (!force_recalculate && valid_actions_cache_.has_value())
|
205
|
+
{
|
206
|
+
return *valid_actions_cache_;
|
207
|
+
}
|
208
|
+
calculate_valid_actions_internal();
|
209
|
+
// Check if game should end *after* calculating actions
|
210
|
+
if (!game_over_ && valid_actions_cache_->empty())
|
211
|
+
{
|
212
|
+
force_game_over("No valid actions available.");
|
213
|
+
}
|
214
|
+
return *valid_actions_cache_;
|
215
|
+
}
|
216
|
+
|
217
|
+
void GameStateCpp::invalidate_action_cache()
|
218
|
+
{
|
219
|
+
valid_actions_cache_ = std::nullopt;
|
220
|
+
}
|
221
|
+
|
222
|
+
void GameStateCpp::calculate_valid_actions_internal() const
|
223
|
+
{
|
224
|
+
if (game_over_)
|
225
|
+
{
|
226
|
+
valid_actions_cache_ = std::set<Action>();
|
227
|
+
return;
|
228
|
+
}
|
229
|
+
std::set<Action> valid_actions;
|
230
|
+
for (int shape_idx = 0; shape_idx < static_cast<int>(shapes_.size()); ++shape_idx)
|
231
|
+
{
|
232
|
+
if (!shapes_[shape_idx].has_value())
|
233
|
+
continue;
|
234
|
+
const ShapeCpp &shape = shapes_[shape_idx].value();
|
235
|
+
for (int r = 0; r < config_.rows; ++r)
|
236
|
+
{
|
237
|
+
const auto &[start_c, end_c] = config_.playable_range_per_row[r];
|
238
|
+
for (int c = start_c; c < end_c; ++c)
|
239
|
+
{
|
240
|
+
if (grid_logic::can_place(grid_data_, shape, r, c))
|
241
|
+
{
|
242
|
+
valid_actions.insert(encode_action(shape_idx, r, c));
|
243
|
+
}
|
244
|
+
}
|
245
|
+
}
|
246
|
+
}
|
247
|
+
valid_actions_cache_ = std::move(valid_actions);
|
248
|
+
}
|
249
|
+
|
250
|
+
int GameStateCpp::get_current_step() const { return current_step_; }
|
251
|
+
std::optional<std::string> GameStateCpp::get_game_over_reason() const { return game_over_reason_; }
|
252
|
+
|
253
|
+
GameStateCpp GameStateCpp::copy() const
|
254
|
+
{
|
255
|
+
GameStateCpp newState = *this;
|
256
|
+
if (this->valid_actions_cache_.has_value())
|
257
|
+
{
|
258
|
+
newState.valid_actions_cache_ = this->valid_actions_cache_;
|
259
|
+
}
|
260
|
+
else
|
261
|
+
{
|
262
|
+
newState.valid_actions_cache_ = std::nullopt;
|
263
|
+
}
|
264
|
+
return newState;
|
265
|
+
}
|
266
|
+
|
267
|
+
void GameStateCpp::debug_toggle_cell(int r, int c)
|
268
|
+
{
|
269
|
+
if (grid_data_.is_valid(r, c) && !grid_data_.is_death(r, c))
|
270
|
+
{
|
271
|
+
auto &occupied_grid = grid_data_.get_occupied_grid_mut();
|
272
|
+
auto &color_grid = grid_data_.get_color_id_grid_mut();
|
273
|
+
bool was_occupied = occupied_grid[r][c];
|
274
|
+
occupied_grid[r][c] = !was_occupied;
|
275
|
+
color_grid[r][c] = was_occupied ? NO_COLOR_ID : DEBUG_COLOR_ID;
|
276
|
+
if (!was_occupied)
|
277
|
+
{
|
278
|
+
grid_logic::check_and_clear_lines(grid_data_, {{r, c}});
|
279
|
+
}
|
280
|
+
invalidate_action_cache();
|
281
|
+
}
|
282
|
+
}
|
283
|
+
|
284
|
+
void GameStateCpp::debug_set_shapes(const std::vector<std::optional<ShapeCpp>> &new_shapes)
|
285
|
+
{
|
286
|
+
// Overwrite the current shapes with the provided ones.
|
287
|
+
// Ensure the size matches the number of slots.
|
288
|
+
size_t num_to_copy = std::min(new_shapes.size(), shapes_.size());
|
289
|
+
for (size_t i = 0; i < num_to_copy; ++i)
|
290
|
+
{
|
291
|
+
shapes_[i] = new_shapes[i];
|
292
|
+
}
|
293
|
+
// Fill remaining slots with nullopt if new_shapes is shorter
|
294
|
+
for (size_t i = num_to_copy; i < shapes_.size(); ++i)
|
295
|
+
{
|
296
|
+
shapes_[i] = std::nullopt;
|
297
|
+
}
|
298
|
+
// Invalidate cache as valid actions will change
|
299
|
+
invalidate_action_cache();
|
300
|
+
}
|
301
|
+
|
302
|
+
Action GameStateCpp::encode_action(int shape_idx, int r, int c) const
|
303
|
+
{
|
304
|
+
int grid_size = config_.rows * config_.cols;
|
305
|
+
if (shape_idx < 0 || shape_idx >= config_.num_shape_slots || r < 0 || r >= config_.rows || c < 0 || c >= config_.cols)
|
306
|
+
{
|
307
|
+
throw std::out_of_range("encode_action arguments out of range.");
|
308
|
+
}
|
309
|
+
return shape_idx * grid_size + r * config_.cols + c;
|
310
|
+
}
|
311
|
+
|
312
|
+
std::tuple<int, int, int> GameStateCpp::decode_action(Action action) const
|
313
|
+
{
|
314
|
+
int action_dim = config_.num_shape_slots * config_.rows * config_.cols;
|
315
|
+
if (action < 0 || action >= action_dim)
|
316
|
+
{
|
317
|
+
throw std::out_of_range("Action index out of range: " + std::to_string(action));
|
318
|
+
}
|
319
|
+
int grid_size = config_.rows * config_.cols;
|
320
|
+
int shape_idx = action / grid_size;
|
321
|
+
int remainder = action % grid_size;
|
322
|
+
int r = remainder / config_.cols;
|
323
|
+
int c = remainder % config_.cols;
|
324
|
+
return {shape_idx, r, c};
|
325
|
+
}
|
326
|
+
|
327
|
+
} // namespace trianglengin::cpp
|
@@ -0,0 +1,73 @@
|
|
1
|
+
// File: src/trianglengin/cpp/game_state.h
|
2
|
+
#ifndef TRIANGLENGIN_CPP_GAME_STATE_H
|
3
|
+
#define TRIANGLENGIN_CPP_GAME_STATE_H
|
4
|
+
|
5
|
+
#pragma once
|
6
|
+
|
7
|
+
#include <vector>
|
8
|
+
#include <set>
|
9
|
+
#include <optional>
|
10
|
+
#include <random>
|
11
|
+
#include <memory>
|
12
|
+
#include <string> // Include string for optional<string>
|
13
|
+
#include <tuple> // Include tuple for std::tuple
|
14
|
+
|
15
|
+
#include "config.h"
|
16
|
+
#include "structs.h"
|
17
|
+
#include "grid_data.h"
|
18
|
+
// Remove direct includes causing cycles if possible, use forward declarations
|
19
|
+
// #include "grid_logic.h" // Included by game_state.cpp
|
20
|
+
// #include "shape_logic.h" // Included by game_state.cpp
|
21
|
+
|
22
|
+
namespace trianglengin::cpp
|
23
|
+
{
|
24
|
+
|
25
|
+
class GameStateCpp
|
26
|
+
{
|
27
|
+
public:
|
28
|
+
explicit GameStateCpp(const EnvConfigCpp &config, unsigned int initial_seed);
|
29
|
+
|
30
|
+
void reset();
|
31
|
+
std::tuple<double, bool> step(Action action);
|
32
|
+
bool is_over() const;
|
33
|
+
double get_score() const;
|
34
|
+
const std::set<Action> &get_valid_actions(bool force_recalculate = false);
|
35
|
+
int get_current_step() const;
|
36
|
+
std::optional<std::string> get_game_over_reason() const;
|
37
|
+
GameStateCpp copy() const;
|
38
|
+
void debug_toggle_cell(int r, int c);
|
39
|
+
void invalidate_action_cache(); // Moved to public
|
40
|
+
// Debug method to force shapes into slots
|
41
|
+
void debug_set_shapes(const std::vector<std::optional<ShapeCpp>> &new_shapes);
|
42
|
+
|
43
|
+
// Accessors needed by logic functions or bindings
|
44
|
+
const GridData &get_grid_data() const { return grid_data_; }
|
45
|
+
GridData &get_grid_data_mut() { return grid_data_; }
|
46
|
+
const std::vector<std::optional<ShapeCpp>> &get_shapes() const { return shapes_; }
|
47
|
+
std::vector<std::optional<ShapeCpp>> &get_shapes_mut() { return shapes_; }
|
48
|
+
const EnvConfigCpp &get_config() const { return config_; }
|
49
|
+
|
50
|
+
private:
|
51
|
+
EnvConfigCpp config_;
|
52
|
+
GridData grid_data_;
|
53
|
+
std::vector<std::optional<ShapeCpp>> shapes_;
|
54
|
+
double score_;
|
55
|
+
int current_step_;
|
56
|
+
bool game_over_;
|
57
|
+
std::optional<std::string> game_over_reason_;
|
58
|
+
mutable std::optional<std::set<Action>> valid_actions_cache_; // Mutable for const getter
|
59
|
+
std::mt19937 rng_;
|
60
|
+
|
61
|
+
void check_initial_state_game_over();
|
62
|
+
void force_game_over(const std::string &reason);
|
63
|
+
// void invalidate_action_cache(); // Moved from private
|
64
|
+
void calculate_valid_actions_internal() const; // Made const
|
65
|
+
|
66
|
+
// Action encoding/decoding (can be private if only used internally)
|
67
|
+
Action encode_action(int shape_idx, int r, int c) const;
|
68
|
+
std::tuple<int, int, int> decode_action(Action action) const;
|
69
|
+
};
|
70
|
+
|
71
|
+
} // namespace trianglengin::cpp
|
72
|
+
|
73
|
+
#endif // TRIANGLENGIN_CPP_GAME_STATE_H
|
@@ -0,0 +1,239 @@
|
|
1
|
+
// File: src/trianglengin/cpp/grid_data.cpp
|
2
|
+
#include "grid_data.h"
|
3
|
+
#include <stdexcept>
|
4
|
+
#include <set>
|
5
|
+
#include <algorithm>
|
6
|
+
#include <iostream>
|
7
|
+
|
8
|
+
namespace trianglengin::cpp
|
9
|
+
{
|
10
|
+
|
11
|
+
// Constructor: Initialize config_ by copying the passed config
|
12
|
+
GridData::GridData(const EnvConfigCpp &config)
|
13
|
+
: config_(config), // Copy config here
|
14
|
+
rows_(config_.rows),
|
15
|
+
cols_(config_.cols)
|
16
|
+
{
|
17
|
+
if (rows_ <= 0 || cols_ <= 0)
|
18
|
+
{
|
19
|
+
throw std::invalid_argument("Grid dimensions must be positive.");
|
20
|
+
}
|
21
|
+
occupied_grid_.assign(rows_, std::vector<bool>(cols_, false));
|
22
|
+
color_id_grid_.assign(rows_, std::vector<int8_t>(cols_, NO_COLOR_ID));
|
23
|
+
death_grid_.assign(rows_, std::vector<bool>(cols_, true));
|
24
|
+
|
25
|
+
if (config_.playable_range_per_row.size() != static_cast<size_t>(rows_))
|
26
|
+
{
|
27
|
+
throw std::invalid_argument("Playable range size mismatch with rows.");
|
28
|
+
}
|
29
|
+
for (int r = 0; r < rows_; ++r)
|
30
|
+
{
|
31
|
+
const auto &[start_col, end_col] = config_.playable_range_per_row[r];
|
32
|
+
if (start_col < 0 || end_col > cols_ || start_col > end_col) // Allow start == end
|
33
|
+
{
|
34
|
+
throw std::invalid_argument("Invalid playable range for row " + std::to_string(r));
|
35
|
+
}
|
36
|
+
if (start_col < end_col)
|
37
|
+
{
|
38
|
+
for (int c = start_col; c < end_col; ++c)
|
39
|
+
{
|
40
|
+
death_grid_[r][c] = false;
|
41
|
+
}
|
42
|
+
}
|
43
|
+
}
|
44
|
+
precompute_lines();
|
45
|
+
}
|
46
|
+
|
47
|
+
// Copy constructor: Copy the config_ member
|
48
|
+
GridData::GridData(const GridData &other)
|
49
|
+
: config_(other.config_), // Copy config value
|
50
|
+
rows_(other.rows_),
|
51
|
+
cols_(other.cols_),
|
52
|
+
occupied_grid_(other.occupied_grid_),
|
53
|
+
color_id_grid_(other.color_id_grid_),
|
54
|
+
death_grid_(other.death_grid_),
|
55
|
+
lines_(other.lines_),
|
56
|
+
coord_to_lines_map_(other.coord_to_lines_map_)
|
57
|
+
{
|
58
|
+
}
|
59
|
+
|
60
|
+
// Copy assignment operator: Copy the config_ member
|
61
|
+
GridData &GridData::operator=(const GridData &other)
|
62
|
+
{
|
63
|
+
if (this != &other)
|
64
|
+
{
|
65
|
+
config_ = other.config_; // Copy config value
|
66
|
+
rows_ = other.rows_;
|
67
|
+
cols_ = other.cols_;
|
68
|
+
occupied_grid_ = other.occupied_grid_;
|
69
|
+
color_id_grid_ = other.color_id_grid_;
|
70
|
+
death_grid_ = other.death_grid_;
|
71
|
+
lines_ = other.lines_;
|
72
|
+
coord_to_lines_map_ = other.coord_to_lines_map_;
|
73
|
+
}
|
74
|
+
return *this;
|
75
|
+
}
|
76
|
+
|
77
|
+
void GridData::reset()
|
78
|
+
{
|
79
|
+
for (int r = 0; r < rows_; ++r)
|
80
|
+
{
|
81
|
+
std::fill(occupied_grid_[r].begin(), occupied_grid_[r].end(), false);
|
82
|
+
std::fill(color_id_grid_[r].begin(), color_id_grid_[r].end(), NO_COLOR_ID);
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
bool GridData::is_valid(int r, int c) const
|
87
|
+
{
|
88
|
+
return r >= 0 && r < rows_ && c >= 0 && c < cols_;
|
89
|
+
}
|
90
|
+
|
91
|
+
bool GridData::is_death(int r, int c) const
|
92
|
+
{
|
93
|
+
if (!is_valid(r, c))
|
94
|
+
{
|
95
|
+
throw std::out_of_range("Coordinates (" + std::to_string(r) + "," + std::to_string(c) + ") out of bounds.");
|
96
|
+
}
|
97
|
+
return death_grid_[r][c];
|
98
|
+
}
|
99
|
+
|
100
|
+
bool GridData::is_occupied(int r, int c) const
|
101
|
+
{
|
102
|
+
if (!is_valid(r, c))
|
103
|
+
{
|
104
|
+
throw std::out_of_range("Coordinates (" + std::to_string(r) + "," + std::to_string(c) + ") out of bounds.");
|
105
|
+
}
|
106
|
+
return !death_grid_[r][c] && occupied_grid_[r][c];
|
107
|
+
}
|
108
|
+
|
109
|
+
std::optional<int> GridData::get_color_id(int r, int c) const
|
110
|
+
{
|
111
|
+
if (!is_valid(r, c) || death_grid_[r][c] || !occupied_grid_[r][c])
|
112
|
+
{
|
113
|
+
return std::nullopt;
|
114
|
+
}
|
115
|
+
return color_id_grid_[r][c];
|
116
|
+
}
|
117
|
+
|
118
|
+
bool GridData::is_up(int r, int c) const
|
119
|
+
{
|
120
|
+
return (r + c) % 2 != 0;
|
121
|
+
}
|
122
|
+
|
123
|
+
bool GridData::is_live(int r, int c) const
|
124
|
+
{
|
125
|
+
return is_valid(r, c) && !death_grid_[r][c];
|
126
|
+
}
|
127
|
+
|
128
|
+
std::optional<Coord> GridData::get_neighbor(int r, int c, const std::string &direction, bool backward) const
|
129
|
+
{
|
130
|
+
bool up = is_up(r, c);
|
131
|
+
int nr = -1, nc = -1;
|
132
|
+
|
133
|
+
if (direction == "h")
|
134
|
+
{
|
135
|
+
int dc = backward ? -1 : 1;
|
136
|
+
nr = r;
|
137
|
+
nc = c + dc;
|
138
|
+
}
|
139
|
+
else if (direction == "d1")
|
140
|
+
{ // TL-BR
|
141
|
+
if (backward)
|
142
|
+
{
|
143
|
+
nr = up ? r : r - 1;
|
144
|
+
nc = up ? c - 1 : c;
|
145
|
+
}
|
146
|
+
else
|
147
|
+
{
|
148
|
+
nr = up ? r + 1 : r;
|
149
|
+
nc = up ? c : c + 1;
|
150
|
+
}
|
151
|
+
}
|
152
|
+
else if (direction == "d2")
|
153
|
+
{ // BL-TR
|
154
|
+
if (backward)
|
155
|
+
{
|
156
|
+
nr = up ? r + 1 : r;
|
157
|
+
nc = up ? c : c - 1;
|
158
|
+
}
|
159
|
+
else
|
160
|
+
{
|
161
|
+
nr = up ? r : r - 1;
|
162
|
+
nc = up ? c + 1 : c;
|
163
|
+
}
|
164
|
+
}
|
165
|
+
else
|
166
|
+
{
|
167
|
+
throw std::invalid_argument("Unknown direction: " + direction);
|
168
|
+
}
|
169
|
+
|
170
|
+
if (!is_valid(nr, nc))
|
171
|
+
return std::nullopt;
|
172
|
+
return Coord{nr, nc};
|
173
|
+
}
|
174
|
+
|
175
|
+
void GridData::precompute_lines()
|
176
|
+
{
|
177
|
+
lines_.clear();
|
178
|
+
coord_to_lines_map_.clear();
|
179
|
+
std::set<Line> maximal_lines_set;
|
180
|
+
std::set<std::tuple<Coord, std::string>> processed_starts;
|
181
|
+
const std::vector<std::string> directions = {"h", "d1", "d2"};
|
182
|
+
|
183
|
+
for (int r_init = 0; r_init < rows_; ++r_init)
|
184
|
+
{
|
185
|
+
for (int c_init = 0; c_init < cols_; ++c_init)
|
186
|
+
{
|
187
|
+
if (!is_live(r_init, c_init))
|
188
|
+
continue;
|
189
|
+
Coord start_coord = {r_init, c_init};
|
190
|
+
for (const auto &direction : directions)
|
191
|
+
{
|
192
|
+
Coord line_start_coord = start_coord;
|
193
|
+
while (true)
|
194
|
+
{
|
195
|
+
auto prev_coord_opt = get_neighbor(std::get<0>(line_start_coord), std::get<1>(line_start_coord), direction, true);
|
196
|
+
if (prev_coord_opt && is_live(std::get<0>(*prev_coord_opt), std::get<1>(*prev_coord_opt)))
|
197
|
+
{
|
198
|
+
line_start_coord = *prev_coord_opt;
|
199
|
+
}
|
200
|
+
else
|
201
|
+
break;
|
202
|
+
}
|
203
|
+
if (processed_starts.count({line_start_coord, direction}))
|
204
|
+
continue;
|
205
|
+
Line current_line;
|
206
|
+
std::optional<Coord> trace_coord_opt = line_start_coord;
|
207
|
+
while (trace_coord_opt && is_live(std::get<0>(*trace_coord_opt), std::get<1>(*trace_coord_opt)))
|
208
|
+
{
|
209
|
+
current_line.push_back(*trace_coord_opt);
|
210
|
+
trace_coord_opt = get_neighbor(std::get<0>(*trace_coord_opt), std::get<1>(*trace_coord_opt), direction, false);
|
211
|
+
}
|
212
|
+
if (current_line.size() >= 2)
|
213
|
+
{
|
214
|
+
maximal_lines_set.insert(current_line);
|
215
|
+
processed_starts.insert({line_start_coord, direction});
|
216
|
+
}
|
217
|
+
}
|
218
|
+
}
|
219
|
+
}
|
220
|
+
|
221
|
+
lines_ = std::vector<Line>(maximal_lines_set.begin(), maximal_lines_set.end());
|
222
|
+
std::sort(lines_.begin(), lines_.end(), [](const Line &a, const Line &b)
|
223
|
+
{
|
224
|
+
if (a.empty() || b.empty()) return b.empty();
|
225
|
+
if (std::get<0>(a[0]) != std::get<0>(b[0])) return std::get<0>(a[0]) < std::get<0>(b[0]);
|
226
|
+
if (std::get<1>(a[0]) != std::get<1>(b[0])) return std::get<1>(a[0]) < std::get<1>(b[0]);
|
227
|
+
return a.size() < b.size(); });
|
228
|
+
|
229
|
+
for (const auto &line_vec : lines_)
|
230
|
+
{
|
231
|
+
LineFs line_fs(line_vec.begin(), line_vec.end());
|
232
|
+
for (const auto &coord : line_vec)
|
233
|
+
{
|
234
|
+
coord_to_lines_map_[coord].insert(line_fs);
|
235
|
+
}
|
236
|
+
}
|
237
|
+
}
|
238
|
+
|
239
|
+
} // namespace trianglengin::cpp
|