trianglengin 2.0.3__cp311-cp311-win_amd64.whl → 2.0.6__cp311-cp311-win_amd64.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/Release/trianglengin_cpp.cp39-win_amd64.pyd +0 -0
- trianglengin/cpp/game_state.cpp +90 -91
- trianglengin/cpp/game_state.h +10 -1
- trianglengin/cpp/grid_data.cpp +18 -5
- trianglengin/cpp/grid_data.h +4 -5
- trianglengin/cpp/structs.h +30 -1
- trianglengin/trianglengin_cpp.cp311-win_amd64.pyd +0 -0
- {trianglengin-2.0.3.dist-info → trianglengin-2.0.6.dist-info}/METADATA +1 -1
- {trianglengin-2.0.3.dist-info → trianglengin-2.0.6.dist-info}/RECORD +13 -13
- {trianglengin-2.0.3.dist-info → trianglengin-2.0.6.dist-info}/WHEEL +0 -0
- {trianglengin-2.0.3.dist-info → trianglengin-2.0.6.dist-info}/entry_points.txt +0 -0
- {trianglengin-2.0.3.dist-info → trianglengin-2.0.6.dist-info}/licenses/LICENSE +0 -0
- {trianglengin-2.0.3.dist-info → trianglengin-2.0.6.dist-info}/top_level.txt +0 -0
Binary file
|
trianglengin/cpp/game_state.cpp
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
#include "shape_logic.h"
|
5
5
|
#include <stdexcept>
|
6
6
|
#include <numeric>
|
7
|
-
#include <iostream> //
|
7
|
+
#include <iostream> // Keep iostream if other debug logs might be added later, or remove if not needed
|
8
8
|
#include <algorithm> // For std::min
|
9
9
|
|
10
10
|
namespace trianglengin::cpp
|
@@ -12,7 +12,7 @@ namespace trianglengin::cpp
|
|
12
12
|
|
13
13
|
GameStateCpp::GameStateCpp(const EnvConfigCpp &config, unsigned int initial_seed)
|
14
14
|
: config_(config),
|
15
|
-
grid_data_(config_),
|
15
|
+
grid_data_(config_), // Initialize GridData with config
|
16
16
|
shapes_(config_.num_shape_slots),
|
17
17
|
score_(0.0),
|
18
18
|
current_step_(0),
|
@@ -22,6 +22,39 @@ namespace trianglengin::cpp
|
|
22
22
|
reset();
|
23
23
|
}
|
24
24
|
|
25
|
+
// --- Explicit Copy Constructor ---
|
26
|
+
GameStateCpp::GameStateCpp(const GameStateCpp &other)
|
27
|
+
: config_(other.config_), // Copy config
|
28
|
+
grid_data_(other.grid_data_), // Use GridData's copy constructor
|
29
|
+
shapes_(other.shapes_), // Copy vector of optional shapes
|
30
|
+
score_(other.score_), // Copy score
|
31
|
+
current_step_(other.current_step_), // Copy step
|
32
|
+
game_over_(other.game_over_), // Copy game over flag
|
33
|
+
game_over_reason_(other.game_over_reason_), // Copy reason
|
34
|
+
valid_actions_cache_(other.valid_actions_cache_), // Copy the optional cache
|
35
|
+
rng_(other.rng_) // Copy the RNG state
|
36
|
+
{
|
37
|
+
// No additional logic needed if members handle their own copying well
|
38
|
+
}
|
39
|
+
|
40
|
+
// --- Explicit Copy Assignment Operator ---
|
41
|
+
GameStateCpp &GameStateCpp::operator=(const GameStateCpp &other)
|
42
|
+
{
|
43
|
+
if (this != &other)
|
44
|
+
{
|
45
|
+
config_ = other.config_;
|
46
|
+
grid_data_ = other.grid_data_; // Use GridData's copy assignment
|
47
|
+
shapes_ = other.shapes_;
|
48
|
+
score_ = other.score_;
|
49
|
+
current_step_ = other.current_step_;
|
50
|
+
game_over_ = other.game_over_;
|
51
|
+
game_over_reason_ = other.game_over_reason_;
|
52
|
+
valid_actions_cache_ = other.valid_actions_cache_; // Copy the optional cache
|
53
|
+
rng_ = other.rng_; // Copy the RNG state
|
54
|
+
}
|
55
|
+
return *this;
|
56
|
+
}
|
57
|
+
|
25
58
|
void GameStateCpp::reset()
|
26
59
|
{
|
27
60
|
grid_data_.reset();
|
@@ -37,13 +70,9 @@ namespace trianglengin::cpp
|
|
37
70
|
|
38
71
|
void GameStateCpp::check_initial_state_game_over()
|
39
72
|
{
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
force_game_over("No valid actions available at start.");
|
44
|
-
// Log the reason for immediate game over
|
45
|
-
std::cerr << "[GameStateCpp::check_initial_state_game_over] Forced game over: No valid actions at start." << std::endl;
|
46
|
-
}
|
73
|
+
// Force calculation which updates the cache and potentially the game_over flag
|
74
|
+
get_valid_actions(true);
|
75
|
+
// No need to check cache emptiness here, get_valid_actions handles setting the flag
|
47
76
|
}
|
48
77
|
|
49
78
|
std::tuple<double, bool> GameStateCpp::step(Action action)
|
@@ -53,14 +82,16 @@ namespace trianglengin::cpp
|
|
53
82
|
return {0.0, true};
|
54
83
|
}
|
55
84
|
|
56
|
-
|
85
|
+
// Ensure cache is populated before checking the action
|
86
|
+
const auto &valid_actions = get_valid_actions(); // This populates cache if needed
|
87
|
+
|
88
|
+
// Check against the (now guaranteed) populated cache
|
57
89
|
if (valid_actions.find(action) == valid_actions.end())
|
58
90
|
{
|
59
91
|
// Invalid action detected
|
60
92
|
force_game_over("Invalid action provided: " + std::to_string(action));
|
61
|
-
// Apply penalty
|
62
|
-
|
63
|
-
return {config_.penalty_game_over, true}; // Return penalty and done=true
|
93
|
+
score_ += config_.penalty_game_over; // Apply penalty
|
94
|
+
return {config_.penalty_game_over, true};
|
64
95
|
}
|
65
96
|
|
66
97
|
int shape_idx, r, c;
|
@@ -70,27 +101,25 @@ namespace trianglengin::cpp
|
|
70
101
|
}
|
71
102
|
catch (const std::out_of_range &e)
|
72
103
|
{
|
73
|
-
// Error during decoding (should be caught by valid_actions check, but defensive)
|
74
104
|
force_game_over("Failed to decode action: " + std::to_string(action));
|
75
|
-
score_ += config_.penalty_game_over;
|
105
|
+
score_ += config_.penalty_game_over;
|
76
106
|
return {config_.penalty_game_over, true};
|
77
107
|
}
|
78
108
|
|
79
109
|
if (shape_idx < 0 || shape_idx >= static_cast<int>(shapes_.size()) || !shapes_[shape_idx].has_value())
|
80
110
|
{
|
81
|
-
// Action references invalid slot (should be caught by valid_actions check)
|
82
111
|
force_game_over("Action references invalid/empty shape slot: " + std::to_string(shape_idx));
|
83
|
-
score_ += config_.penalty_game_over;
|
112
|
+
score_ += config_.penalty_game_over;
|
84
113
|
return {config_.penalty_game_over, true};
|
85
114
|
}
|
86
115
|
|
87
116
|
const ShapeCpp &shape_to_place = shapes_[shape_idx].value();
|
88
117
|
|
118
|
+
// Re-check placement just before modification (defensive)
|
89
119
|
if (!grid_logic::can_place(grid_data_, shape_to_place, r, c))
|
90
120
|
{
|
91
|
-
// Should not happen if valid_actions is correct
|
92
121
|
force_game_over("Placement check failed for valid action (logic error?). Action: " + std::to_string(action));
|
93
|
-
score_ += config_.penalty_game_over;
|
122
|
+
score_ += config_.penalty_game_over;
|
94
123
|
return {config_.penalty_game_over, true};
|
95
124
|
}
|
96
125
|
|
@@ -106,10 +135,11 @@ namespace trianglengin::cpp
|
|
106
135
|
std::tie(dr, dc, is_up_ignored) = tri_data;
|
107
136
|
int target_r = r + dr;
|
108
137
|
int target_c = c + dc;
|
138
|
+
// Bounds check should be implicitly handled by can_place, but double-check
|
109
139
|
if (!grid_data_.is_valid(target_r, target_c) || grid_data_.is_death(target_r, target_c))
|
110
140
|
{
|
111
141
|
force_game_over("Attempted placement out of bounds/death zone during execution. Action: " + std::to_string(action));
|
112
|
-
score_ += config_.penalty_game_over;
|
142
|
+
score_ += config_.penalty_game_over;
|
113
143
|
return {config_.penalty_game_over, true};
|
114
144
|
}
|
115
145
|
occupied_grid[target_r][target_c] = true;
|
@@ -117,7 +147,7 @@ namespace trianglengin::cpp
|
|
117
147
|
newly_occupied_coords.insert({target_r, target_c});
|
118
148
|
placed_count++;
|
119
149
|
}
|
120
|
-
shapes_[shape_idx] = std::nullopt;
|
150
|
+
shapes_[shape_idx] = std::nullopt; // Clear the used shape slot
|
121
151
|
|
122
152
|
// --- Line Clearing ---
|
123
153
|
int lines_cleared_count;
|
@@ -144,9 +174,8 @@ namespace trianglengin::cpp
|
|
144
174
|
|
145
175
|
// --- Update State & Check Game Over ---
|
146
176
|
current_step_++;
|
147
|
-
|
148
|
-
get_valid_actions(true);
|
149
|
-
// game_over_ flag is now definitive
|
177
|
+
invalidate_action_cache(); // Invalidate cache AFTER state changes
|
178
|
+
get_valid_actions(true); // Force recalculation AND update game_over_ flag if needed
|
150
179
|
|
151
180
|
// --- Calculate Reward & Update Score ---
|
152
181
|
double reward = 0.0;
|
@@ -155,7 +184,8 @@ namespace trianglengin::cpp
|
|
155
184
|
|
156
185
|
if (game_over_)
|
157
186
|
{
|
158
|
-
|
187
|
+
// Penalty was applied earlier if game over was due to invalid action this step.
|
188
|
+
// If game over is due to lack of actions *after* this step, no penalty applies here.
|
159
189
|
}
|
160
190
|
else
|
161
191
|
{
|
@@ -163,34 +193,24 @@ namespace trianglengin::cpp
|
|
163
193
|
}
|
164
194
|
score_ += reward; // Update score based on calculated reward
|
165
195
|
|
166
|
-
return {reward, game_over_};
|
196
|
+
return {reward, game_over_}; // Return the potentially updated game_over_ flag
|
167
197
|
}
|
168
198
|
|
199
|
+
// --- Simplified is_over ---
|
169
200
|
bool GameStateCpp::is_over() const
|
170
201
|
{
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
if (valid_actions_cache_.has_value())
|
175
|
-
return valid_actions_cache_->empty();
|
176
|
-
// Need to remove const to calculate and potentially set game_over_
|
177
|
-
const_cast<GameStateCpp *>(this)->calculate_valid_actions_internal();
|
178
|
-
// Check game_over_ again as calculate might have set it
|
179
|
-
if (game_over_)
|
180
|
-
return true;
|
181
|
-
// Now the cache should exist
|
182
|
-
return valid_actions_cache_->empty();
|
202
|
+
// The game_over_ flag is the single source of truth.
|
203
|
+
// It's updated by step() [via get_valid_actions()] and reset().
|
204
|
+
return game_over_;
|
183
205
|
}
|
184
206
|
|
185
207
|
void GameStateCpp::force_game_over(const std::string &reason)
|
186
208
|
{
|
187
|
-
// This function only sets the flags and reason.
|
188
|
-
// Score update should happen in the context where the game over is triggered (e.g., step).
|
189
209
|
if (!game_over_)
|
190
210
|
{
|
191
211
|
game_over_ = true;
|
192
212
|
game_over_reason_ = reason;
|
193
|
-
valid_actions_cache_ = std::set<Action>();
|
213
|
+
valid_actions_cache_ = std::set<Action>(); // Clear valid actions on game over
|
194
214
|
}
|
195
215
|
}
|
196
216
|
|
@@ -201,26 +221,34 @@ namespace trianglengin::cpp
|
|
201
221
|
|
202
222
|
const std::set<Action> &GameStateCpp::get_valid_actions(bool force_recalculate)
|
203
223
|
{
|
224
|
+
// If game is over, always return the cached empty set
|
204
225
|
if (game_over_)
|
205
226
|
{
|
227
|
+
// Ensure cache is empty if game_over is true
|
206
228
|
if (!valid_actions_cache_.has_value() || !valid_actions_cache_->empty())
|
207
229
|
{
|
208
230
|
valid_actions_cache_ = std::set<Action>();
|
209
231
|
}
|
210
232
|
return *valid_actions_cache_;
|
211
233
|
}
|
234
|
+
|
235
|
+
// If not forcing and cache exists, return it
|
212
236
|
if (!force_recalculate && valid_actions_cache_.has_value())
|
213
237
|
{
|
214
238
|
return *valid_actions_cache_;
|
215
239
|
}
|
240
|
+
|
241
|
+
// Otherwise, calculate (which updates the mutable cache)
|
216
242
|
calculate_valid_actions_internal();
|
217
|
-
|
243
|
+
|
244
|
+
// Check if the calculation resulted in no valid actions, triggering game over
|
218
245
|
if (!game_over_ && valid_actions_cache_->empty())
|
219
246
|
{
|
247
|
+
// Set the game over flag HERE, as this is the definitive check after calculation
|
220
248
|
force_game_over("No valid actions available.");
|
221
|
-
// Log the reason for game over after calculation
|
222
|
-
std::cerr << "[GameStateCpp::get_valid_actions] Forced game over: No valid actions found after calculation." << std::endl;
|
223
249
|
}
|
250
|
+
|
251
|
+
// Return the calculated (and potentially now empty) cache
|
224
252
|
return *valid_actions_cache_;
|
225
253
|
}
|
226
254
|
|
@@ -229,20 +257,12 @@ namespace trianglengin::cpp
|
|
229
257
|
valid_actions_cache_ = std::nullopt;
|
230
258
|
}
|
231
259
|
|
232
|
-
//
|
233
|
-
void GameStateCpp::calculate_valid_actions_internal() const
|
260
|
+
// calculate_valid_actions_internal remains const, modifies mutable cache
|
261
|
+
void GameStateCpp::calculate_valid_actions_internal() const
|
234
262
|
{
|
235
|
-
|
236
|
-
|
237
|
-
valid_actions_cache_ = std::set<Action>();
|
238
|
-
// Add logging for this case too
|
239
|
-
std::cerr << "[GameStateCpp::calculate_valid_actions_internal] Game already over. Returning empty set." << std::endl;
|
240
|
-
return;
|
241
|
-
}
|
263
|
+
// This function should NOT set game_over_ directly.
|
264
|
+
// It just calculates the set. The caller (get_valid_actions) checks if empty.
|
242
265
|
std::set<Action> valid_actions;
|
243
|
-
int can_place_true_count = 0; // Counter for debugging
|
244
|
-
int attempts_count = 0; // Counter for total placement checks
|
245
|
-
|
246
266
|
for (int shape_idx = 0; shape_idx < static_cast<int>(shapes_.size()); ++shape_idx)
|
247
267
|
{
|
248
268
|
if (!shapes_[shape_idx].has_value())
|
@@ -250,50 +270,26 @@ namespace trianglengin::cpp
|
|
250
270
|
const ShapeCpp &shape = shapes_[shape_idx].value();
|
251
271
|
for (int r = 0; r < config_.rows; ++r)
|
252
272
|
{
|
253
|
-
|
254
|
-
for (int c =
|
273
|
+
// Optimization: Check only within playable range? No, C++ can_place handles death zones.
|
274
|
+
for (int c = 0; c < config_.cols; ++c)
|
255
275
|
{
|
256
|
-
|
257
|
-
bool can_place_result = grid_logic::can_place(grid_data_, shape, r, c); // Store result
|
258
|
-
if (can_place_result)
|
276
|
+
if (grid_logic::can_place(grid_data_, shape, r, c))
|
259
277
|
{
|
260
278
|
valid_actions.insert(encode_action(shape_idx, r, c));
|
261
|
-
can_place_true_count++; // Increment counter
|
262
279
|
}
|
263
|
-
// Optional: Log failed attempts if needed for deep debugging
|
264
|
-
// else {
|
265
|
-
// std::cerr << "[Debug] can_place failed for shape " << shape_idx << " at (" << r << "," << c << ")" << std::endl;
|
266
|
-
// }
|
267
280
|
}
|
268
281
|
}
|
269
282
|
}
|
270
|
-
//
|
271
|
-
std::cerr << "[GameStateCpp::calculate_valid_actions_internal] Step: " << current_step_
|
272
|
-
<< ", Attempts: " << attempts_count
|
273
|
-
<< ", CanPlaceTrue: " << can_place_true_count
|
274
|
-
<< ", ValidActionsFound: " << valid_actions.size()
|
275
|
-
<< std::endl;
|
276
|
-
|
277
|
-
// Use mutable cache
|
278
|
-
valid_actions_cache_ = std::move(valid_actions);
|
283
|
+
valid_actions_cache_ = std::move(valid_actions); // Update the mutable cache
|
279
284
|
}
|
280
285
|
|
281
286
|
int GameStateCpp::get_current_step() const { return current_step_; }
|
282
287
|
std::optional<std::string> GameStateCpp::get_game_over_reason() const { return game_over_reason_; }
|
283
288
|
|
289
|
+
// Python-facing copy method uses the C++ copy constructor
|
284
290
|
GameStateCpp GameStateCpp::copy() const
|
285
291
|
{
|
286
|
-
GameStateCpp
|
287
|
-
// Copy the cache state explicitly
|
288
|
-
if (this->valid_actions_cache_.has_value())
|
289
|
-
{
|
290
|
-
newState.valid_actions_cache_ = this->valid_actions_cache_;
|
291
|
-
}
|
292
|
-
else
|
293
|
-
{
|
294
|
-
newState.valid_actions_cache_ = std::nullopt;
|
295
|
-
}
|
296
|
-
return newState;
|
292
|
+
return GameStateCpp(*this);
|
297
293
|
}
|
298
294
|
|
299
295
|
void GameStateCpp::debug_toggle_cell(int r, int c)
|
@@ -311,33 +307,36 @@ namespace trianglengin::cpp
|
|
311
307
|
grid_logic::check_and_clear_lines(grid_data_, {{r, c}});
|
312
308
|
}
|
313
309
|
invalidate_action_cache(); // Always invalidate after manual change
|
310
|
+
// Force recalculation of valid actions and game over state after toggle
|
311
|
+
get_valid_actions(true);
|
314
312
|
}
|
315
313
|
}
|
316
314
|
|
317
315
|
void GameStateCpp::debug_set_shapes(const std::vector<std::optional<ShapeCpp>> &new_shapes)
|
318
316
|
{
|
319
|
-
// Overwrite the current shapes with the provided ones.
|
320
|
-
// Ensure the size matches the number of slots.
|
321
317
|
size_t num_to_copy = std::min(new_shapes.size(), shapes_.size());
|
322
318
|
for (size_t i = 0; i < num_to_copy; ++i)
|
323
319
|
{
|
324
320
|
shapes_[i] = new_shapes[i];
|
325
321
|
}
|
326
|
-
// Fill remaining slots with nullopt if new_shapes is shorter
|
327
322
|
for (size_t i = num_to_copy; i < shapes_.size(); ++i)
|
328
323
|
{
|
329
324
|
shapes_[i] = std::nullopt;
|
330
325
|
}
|
331
|
-
// Invalidate cache as valid actions will change
|
332
326
|
invalidate_action_cache();
|
327
|
+
// Force recalculation of valid actions and game over state after setting shapes
|
328
|
+
get_valid_actions(true);
|
333
329
|
}
|
334
330
|
|
335
331
|
Action GameStateCpp::encode_action(int shape_idx, int r, int c) const
|
336
332
|
{
|
337
333
|
int grid_size = config_.rows * config_.cols;
|
334
|
+
// Basic bounds check (more robust check in can_place)
|
338
335
|
if (shape_idx < 0 || shape_idx >= config_.num_shape_slots || r < 0 || r >= config_.rows || c < 0 || c >= config_.cols)
|
339
336
|
{
|
340
|
-
|
337
|
+
// This case should ideally not be reached if called after can_place
|
338
|
+
// Return an invalid action index or throw? Let's throw for internal logic errors.
|
339
|
+
throw std::out_of_range("encode_action arguments out of range during valid action calculation.");
|
341
340
|
}
|
342
341
|
return shape_idx * grid_size + r * config_.cols + c;
|
343
342
|
}
|
trianglengin/cpp/game_state.h
CHANGED
@@ -27,6 +27,13 @@ namespace trianglengin::cpp
|
|
27
27
|
public:
|
28
28
|
explicit GameStateCpp(const EnvConfigCpp &config, unsigned int initial_seed);
|
29
29
|
|
30
|
+
// --- Rule of 5 ---
|
31
|
+
~GameStateCpp() = default; // Default destructor
|
32
|
+
GameStateCpp(const GameStateCpp &other); // Copy constructor
|
33
|
+
GameStateCpp &operator=(const GameStateCpp &other); // Copy assignment operator
|
34
|
+
GameStateCpp(GameStateCpp &&other) noexcept = default; // Default move constructor
|
35
|
+
GameStateCpp &operator=(GameStateCpp &&other) noexcept = default; // Default move assignment operator
|
36
|
+
|
30
37
|
void reset();
|
31
38
|
std::tuple<double, bool> step(Action action);
|
32
39
|
bool is_over() const;
|
@@ -34,7 +41,7 @@ namespace trianglengin::cpp
|
|
34
41
|
const std::set<Action> &get_valid_actions(bool force_recalculate = false);
|
35
42
|
int get_current_step() const;
|
36
43
|
std::optional<std::string> get_game_over_reason() const;
|
37
|
-
GameStateCpp copy() const;
|
44
|
+
GameStateCpp copy() const; // Keep Python-facing copy method
|
38
45
|
void debug_toggle_cell(int r, int c);
|
39
46
|
void invalidate_action_cache(); // Moved to public
|
40
47
|
// Debug method to force shapes into slots
|
@@ -46,6 +53,8 @@ namespace trianglengin::cpp
|
|
46
53
|
const std::vector<std::optional<ShapeCpp>> &get_shapes() const { return shapes_; }
|
47
54
|
std::vector<std::optional<ShapeCpp>> &get_shapes_mut() { return shapes_; }
|
48
55
|
const EnvConfigCpp &get_config() const { return config_; }
|
56
|
+
// Expose RNG state for copying if needed (or handle seeding in copy)
|
57
|
+
std::mt19937 get_rng_state() const { return rng_; }
|
49
58
|
|
50
59
|
private:
|
51
60
|
EnvConfigCpp config_;
|
trianglengin/cpp/grid_data.cpp
CHANGED
@@ -44,7 +44,7 @@ namespace trianglengin::cpp
|
|
44
44
|
precompute_lines();
|
45
45
|
}
|
46
46
|
|
47
|
-
// Copy constructor:
|
47
|
+
// Copy constructor: Explicitly copy all members
|
48
48
|
GridData::GridData(const GridData &other)
|
49
49
|
: config_(other.config_), // Copy config value
|
50
50
|
rows_(other.rows_),
|
@@ -55,9 +55,11 @@ namespace trianglengin::cpp
|
|
55
55
|
lines_(other.lines_),
|
56
56
|
coord_to_lines_map_(other.coord_to_lines_map_)
|
57
57
|
{
|
58
|
+
// All members are copyable, default member-wise copy is sufficient here,
|
59
|
+
// but being explicit ensures correctness if members change later.
|
58
60
|
}
|
59
61
|
|
60
|
-
// Copy assignment operator:
|
62
|
+
// Copy assignment operator: Explicitly copy all members
|
61
63
|
GridData &GridData::operator=(const GridData &other)
|
62
64
|
{
|
63
65
|
if (this != &other)
|
@@ -103,7 +105,8 @@ namespace trianglengin::cpp
|
|
103
105
|
{
|
104
106
|
throw std::out_of_range("Coordinates (" + std::to_string(r) + "," + std::to_string(c) + ") out of bounds.");
|
105
107
|
}
|
106
|
-
|
108
|
+
// An occupied cell cannot be a death cell by game logic after placement/clearing
|
109
|
+
return occupied_grid_[r][c];
|
107
110
|
}
|
108
111
|
|
109
112
|
std::optional<int> GridData::get_color_id(int r, int c) const
|
@@ -190,6 +193,7 @@ namespace trianglengin::cpp
|
|
190
193
|
for (const auto &direction : directions)
|
191
194
|
{
|
192
195
|
Coord line_start_coord = start_coord;
|
196
|
+
// Find the true start of the line segment in this direction
|
193
197
|
while (true)
|
194
198
|
{
|
195
199
|
auto prev_coord_opt = get_neighbor(std::get<0>(line_start_coord), std::get<1>(line_start_coord), direction, true);
|
@@ -200,8 +204,11 @@ namespace trianglengin::cpp
|
|
200
204
|
else
|
201
205
|
break;
|
202
206
|
}
|
207
|
+
// Check if we already processed this line starting from this coordinate and direction
|
203
208
|
if (processed_starts.count({line_start_coord, direction}))
|
204
209
|
continue;
|
210
|
+
|
211
|
+
// Trace the line forward from the true start
|
205
212
|
Line current_line;
|
206
213
|
std::optional<Coord> trace_coord_opt = line_start_coord;
|
207
214
|
while (trace_coord_opt && is_live(std::get<0>(*trace_coord_opt), std::get<1>(*trace_coord_opt)))
|
@@ -209,7 +216,9 @@ namespace trianglengin::cpp
|
|
209
216
|
current_line.push_back(*trace_coord_opt);
|
210
217
|
trace_coord_opt = get_neighbor(std::get<0>(*trace_coord_opt), std::get<1>(*trace_coord_opt), direction, false);
|
211
218
|
}
|
212
|
-
|
219
|
+
|
220
|
+
// Store the line if it's long enough and mark it as processed
|
221
|
+
if (current_line.size() >= 2) // Only store lines of length 2 or more
|
213
222
|
{
|
214
223
|
maximal_lines_set.insert(current_line);
|
215
224
|
processed_starts.insert({line_start_coord, direction});
|
@@ -218,16 +227,20 @@ namespace trianglengin::cpp
|
|
218
227
|
}
|
219
228
|
}
|
220
229
|
|
230
|
+
// Convert set to vector and sort for deterministic order
|
221
231
|
lines_ = std::vector<Line>(maximal_lines_set.begin(), maximal_lines_set.end());
|
222
232
|
std::sort(lines_.begin(), lines_.end(), [](const Line &a, const Line &b)
|
223
233
|
{
|
224
|
-
if (a.empty() || b.empty()) return b.empty();
|
234
|
+
if (a.empty() || b.empty()) return b.empty(); // Handle empty lines if they somehow occur
|
235
|
+
// Sort primarily by starting row, then starting column, then size
|
225
236
|
if (std::get<0>(a[0]) != std::get<0>(b[0])) return std::get<0>(a[0]) < std::get<0>(b[0]);
|
226
237
|
if (std::get<1>(a[0]) != std::get<1>(b[0])) return std::get<1>(a[0]) < std::get<1>(b[0]);
|
227
238
|
return a.size() < b.size(); });
|
228
239
|
|
240
|
+
// Build the coordinate-to-lines map
|
229
241
|
for (const auto &line_vec : lines_)
|
230
242
|
{
|
243
|
+
// Use a set of Coords (LineFs) as the value in the map for efficient lookup
|
231
244
|
LineFs line_fs(line_vec.begin(), line_vec.end());
|
232
245
|
for (const auto &coord : line_vec)
|
233
246
|
{
|
trianglengin/cpp/grid_data.h
CHANGED
@@ -48,10 +48,9 @@ namespace trianglengin::cpp
|
|
48
48
|
int rows() const { return rows_; }
|
49
49
|
int cols() const { return cols_; }
|
50
50
|
|
51
|
-
// Copy
|
52
|
-
GridData(const GridData &other);
|
53
|
-
// Copy assignment operator
|
54
|
-
GridData &operator=(const GridData &other);
|
51
|
+
// --- Explicit Copy Control ---
|
52
|
+
GridData(const GridData &other); // Copy constructor
|
53
|
+
GridData &operator=(const GridData &other); // Copy assignment operator
|
55
54
|
|
56
55
|
// Default move constructor/assignment should work now
|
57
56
|
GridData(GridData &&other) noexcept = default;
|
@@ -75,4 +74,4 @@ namespace trianglengin::cpp
|
|
75
74
|
|
76
75
|
} // namespace trianglengin::cpp
|
77
76
|
|
78
|
-
#endif // TRIANGLENGIN_CPP_GRID_DATA_H
|
77
|
+
#endif // TRIANGLENGIN_CPP_GRID_DATA_H
|
trianglengin/cpp/structs.h
CHANGED
@@ -8,7 +8,9 @@
|
|
8
8
|
#include <tuple>
|
9
9
|
#include <string>
|
10
10
|
#include <cstdint>
|
11
|
-
#include <utility>
|
11
|
+
#include <utility> // For std::move
|
12
|
+
#include <optional> // For optional members
|
13
|
+
#include <set> // For previous valid actions
|
12
14
|
|
13
15
|
namespace trianglengin::cpp
|
14
16
|
{
|
@@ -35,6 +37,33 @@ namespace trianglengin::cpp
|
|
35
37
|
return triangles == other.triangles && color == other.color && color_id == other.color_id;
|
36
38
|
}
|
37
39
|
};
|
40
|
+
|
41
|
+
// --- NEW: Structure to hold undo information ---
|
42
|
+
struct StepUndoInfo
|
43
|
+
{
|
44
|
+
// Cells changed by placement or clearing
|
45
|
+
// Stores (row, col, previous_occupied_state, previous_color_id)
|
46
|
+
std::vector<std::tuple<int, int, bool, int8_t>> changed_cells;
|
47
|
+
|
48
|
+
// Info about the shape that was consumed by the step
|
49
|
+
int consumed_shape_slot = -1;
|
50
|
+
std::optional<ShapeCpp> consumed_shape = std::nullopt;
|
51
|
+
|
52
|
+
// Previous game state variables
|
53
|
+
double previous_score = 0.0;
|
54
|
+
int previous_step = 0;
|
55
|
+
bool previous_game_over = false;
|
56
|
+
std::optional<std::string> previous_game_over_reason = std::nullopt;
|
57
|
+
|
58
|
+
// Cache invalidation marker (or potentially the previous cache itself)
|
59
|
+
// Storing the previous cache might be large, maybe just invalidate?
|
60
|
+
// Let's start by just marking that the cache *was* valid before the step.
|
61
|
+
bool was_action_cache_valid = false;
|
62
|
+
// OPTIONAL (more complex): Store the actual previous cache
|
63
|
+
// std::optional<std::set<Action>> previous_valid_actions_cache = std::nullopt;
|
64
|
+
};
|
65
|
+
// --- END NEW ---
|
66
|
+
|
38
67
|
} // namespace trianglengin::cpp
|
39
68
|
|
40
69
|
#endif // TRIANGLENGIN_CPP_STRUCTS_H
|
Binary file
|
@@ -1,8 +1,8 @@
|
|
1
1
|
trianglengin/__init__.py,sha256=VBYKMivxX19prPkxmSCSJJrjFXDVWkQJ9b5kQ6RzvkI,954
|
2
2
|
trianglengin/game_interface.py,sha256=7zvt61QGmAuagRfNMpRORhX11sy9NqWM9xWstsEawKY,9540
|
3
3
|
trianglengin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
4
|
-
trianglengin/trianglengin_cpp.cp311-win_amd64.pyd,sha256=
|
5
|
-
trianglengin/Release/trianglengin_cpp.cp39-win_amd64.pyd,sha256=
|
4
|
+
trianglengin/trianglengin_cpp.cp311-win_amd64.pyd,sha256=l8nWpAJySxyRaYaunVHrG7wjzeYfCfXoVcLuFRXFCp0,241664
|
5
|
+
trianglengin/Release/trianglengin_cpp.cp39-win_amd64.pyd,sha256=l8nWpAJySxyRaYaunVHrG7wjzeYfCfXoVcLuFRXFCp0,241664
|
6
6
|
trianglengin/config/README.md,sha256=zPN8ezwsN5fmH4xCsVSgOZwfyng_TwrCj3MlwEq63Y4,1544
|
7
7
|
trianglengin/config/__init__.py,sha256=sdr4D-pSCYmfXY2jOLEjPra2VewDiaWNwx1bWKp0nf8,168
|
8
8
|
trianglengin/config/display_config.py,sha256=ivZEGTCuUfmiVU6GJpVpEASFh30AAHSH42PIIi6bPZg,1862
|
@@ -11,15 +11,15 @@ trianglengin/core/__init__.py,sha256=LWZkoJctRGUhd5DZUJlPCS7usdQLZyKYkoyWUWO3L4o
|
|
11
11
|
trianglengin/cpp/CMakeLists.txt,sha256=vmhTQLRD7LRPbfFlisVVJ9-cCkeM3UMrikIde7TRLWc,1370
|
12
12
|
trianglengin/cpp/bindings.cpp,sha256=okdR2NCdxgP_FTgMjwXNbzcaFdERRrvy7m1eB_5FY8g,8899
|
13
13
|
trianglengin/cpp/config.h,sha256=rZZ6os70igGgA_fp-81fMv9TQMFJb91m6T-KeTOpKAU,742
|
14
|
-
trianglengin/cpp/game_state.cpp,sha256=
|
15
|
-
trianglengin/cpp/game_state.h,sha256=
|
16
|
-
trianglengin/cpp/grid_data.cpp,sha256=
|
17
|
-
trianglengin/cpp/grid_data.h,sha256=
|
14
|
+
trianglengin/cpp/game_state.cpp,sha256=TTwJm-9BBvof2tntAVdQ5fjMSjqAh2F4wFsMkFlgn48,13068
|
15
|
+
trianglengin/cpp/game_state.h,sha256=no5D0SP-vijTuL-sObJ4tx06nW0oaX42mzMZn1matL8,3302
|
16
|
+
trianglengin/cpp/grid_data.cpp,sha256=6iucpVisR1dYayKjSPbjCaYEKaqWaNqGVswyLm6f-zM,8024
|
17
|
+
trianglengin/cpp/grid_data.h,sha256=HVH5gqXAuHwDJrC2d7uy_maeS2ulckeGy68TkpL9gNg,2568
|
18
18
|
trianglengin/cpp/grid_logic.cpp,sha256=O8CfzMw_3r1fU0Iu3KnapPBi5kC7tSs47yWeaVsIxOw,3457
|
19
19
|
trianglengin/cpp/grid_logic.h,sha256=kWUheuixufHfywbzxY_ZEjk_3ayOnCTXk41CvAbVdpc,810
|
20
20
|
trianglengin/cpp/shape_logic.cpp,sha256=O7OrzngMmU4oLUlHhbbtwzg_he3pfdDK1EA1N8fXF_g,4232
|
21
21
|
trianglengin/cpp/shape_logic.h,sha256=GZPK748iKFunOgwus7ZVvUoC_Bu36PxK6JatxoN0hGc,653
|
22
|
-
trianglengin/cpp/structs.h,sha256=
|
22
|
+
trianglengin/cpp/structs.h,sha256=7bcabAxsiWE2lkinYn6rKQaEgzQ69yWHYiBKrTX4Ry0,2223
|
23
23
|
trianglengin/ui/README.md,sha256=NB-CFYy4sNmS6ld3YFmIax4p8UQmhnd28eDucaV8TqE,1753
|
24
24
|
trianglengin/ui/__init__.py,sha256=ANMOGFAYXw2pbATBwVvSZjfCVQvBN8-RvDg_oWHBXS8,579
|
25
25
|
trianglengin/ui/app.py,sha256=XkT5wBJPBMLZIeKDvL57GGQXC9pqdRuT6mrzgy9hIVw,4149
|
@@ -51,9 +51,9 @@ trianglengin/ui/visualization/drawing/utils.py,sha256=gtGtt8bgaIvctFA5TEmKqmXNov
|
|
51
51
|
trianglengin/utils/__init__.py,sha256=pnFOkerF1TAz9oCzkNYBw6N56uENsyOiMZzA0cQ2SrA,185
|
52
52
|
trianglengin/utils/geometry.py,sha256=nrhr5C3MGO-_xXz96w1B0OJNaATLbc_AeZwoB_O6gsI,2128
|
53
53
|
trianglengin/utils/types.py,sha256=N_CjVJISvBP5QG1QYvepgORD0oNpMJlJRCJ9IdgjmWc,241
|
54
|
-
trianglengin-2.0.
|
55
|
-
trianglengin-2.0.
|
56
|
-
trianglengin-2.0.
|
57
|
-
trianglengin-2.0.
|
58
|
-
trianglengin-2.0.
|
59
|
-
trianglengin-2.0.
|
54
|
+
trianglengin-2.0.6.dist-info/licenses/LICENSE,sha256=DNjSCXzwafRxA5zjyWccoRXZTDMpTNElFpNVKz4NEpY,1098
|
55
|
+
trianglengin-2.0.6.dist-info/METADATA,sha256=p_tcQFMpt0tSbMQ5R9lgaGXnfMvnvjMFSdjyJ1G_TNU,12316
|
56
|
+
trianglengin-2.0.6.dist-info/WHEEL,sha256=BqbYhNlPNPMeJZAa49-d9py1A8ao--DtptYt9-jUJDM,101
|
57
|
+
trianglengin-2.0.6.dist-info/entry_points.txt,sha256=kQEqO_U-MEpMEC0xwOPSucBzQIq2Ny7XwCtFSruZhvY,57
|
58
|
+
trianglengin-2.0.6.dist-info/top_level.txt,sha256=YsSWmp_2zM23wRc5TRERHpVCgQuVYieYHDTpnwVQC7Y,13
|
59
|
+
trianglengin-2.0.6.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|