trianglengin 2.0.4__cp310-cp310-musllinux_1_2_i686.whl → 2.0.6__cp310-cp310-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.
@@ -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,12 +70,9 @@ namespace trianglengin::cpp
37
70
 
38
71
  void GameStateCpp::check_initial_state_game_over()
39
72
  {
40
- get_valid_actions(true); // This calls calculate_valid_actions_internal
41
- if (!game_over_ && valid_actions_cache_ && valid_actions_cache_->empty())
42
- {
43
- force_game_over("No valid actions available at start.");
44
- // REMOVED C++ Log: std::cerr << "[GameStateCpp::check_initial_state_game_over] Forced game over: No valid actions at start." << std::endl;
45
- }
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
46
76
  }
47
77
 
48
78
  std::tuple<double, bool> GameStateCpp::step(Action action)
@@ -52,14 +82,16 @@ namespace trianglengin::cpp
52
82
  return {0.0, true};
53
83
  }
54
84
 
55
- const auto &valid_actions = get_valid_actions();
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
56
89
  if (valid_actions.find(action) == valid_actions.end())
57
90
  {
58
91
  // Invalid action detected
59
92
  force_game_over("Invalid action provided: " + std::to_string(action));
60
- // Apply penalty to score *before* returning
61
- score_ += config_.penalty_game_over;
62
- 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};
63
95
  }
64
96
 
65
97
  int shape_idx, r, c;
@@ -69,27 +101,25 @@ namespace trianglengin::cpp
69
101
  }
70
102
  catch (const std::out_of_range &e)
71
103
  {
72
- // Error during decoding (should be caught by valid_actions check, but defensive)
73
104
  force_game_over("Failed to decode action: " + std::to_string(action));
74
- score_ += config_.penalty_game_over; // Apply penalty
105
+ score_ += config_.penalty_game_over;
75
106
  return {config_.penalty_game_over, true};
76
107
  }
77
108
 
78
109
  if (shape_idx < 0 || shape_idx >= static_cast<int>(shapes_.size()) || !shapes_[shape_idx].has_value())
79
110
  {
80
- // Action references invalid slot (should be caught by valid_actions check)
81
111
  force_game_over("Action references invalid/empty shape slot: " + std::to_string(shape_idx));
82
- score_ += config_.penalty_game_over; // Apply penalty
112
+ score_ += config_.penalty_game_over;
83
113
  return {config_.penalty_game_over, true};
84
114
  }
85
115
 
86
116
  const ShapeCpp &shape_to_place = shapes_[shape_idx].value();
87
117
 
118
+ // Re-check placement just before modification (defensive)
88
119
  if (!grid_logic::can_place(grid_data_, shape_to_place, r, c))
89
120
  {
90
- // Should not happen if valid_actions is correct
91
121
  force_game_over("Placement check failed for valid action (logic error?). Action: " + std::to_string(action));
92
- score_ += config_.penalty_game_over; // Apply penalty
122
+ score_ += config_.penalty_game_over;
93
123
  return {config_.penalty_game_over, true};
94
124
  }
95
125
 
@@ -105,10 +135,11 @@ namespace trianglengin::cpp
105
135
  std::tie(dr, dc, is_up_ignored) = tri_data;
106
136
  int target_r = r + dr;
107
137
  int target_c = c + dc;
138
+ // Bounds check should be implicitly handled by can_place, but double-check
108
139
  if (!grid_data_.is_valid(target_r, target_c) || grid_data_.is_death(target_r, target_c))
109
140
  {
110
141
  force_game_over("Attempted placement out of bounds/death zone during execution. Action: " + std::to_string(action));
111
- score_ += config_.penalty_game_over; // Apply penalty
142
+ score_ += config_.penalty_game_over;
112
143
  return {config_.penalty_game_over, true};
113
144
  }
114
145
  occupied_grid[target_r][target_c] = true;
@@ -116,7 +147,7 @@ namespace trianglengin::cpp
116
147
  newly_occupied_coords.insert({target_r, target_c});
117
148
  placed_count++;
118
149
  }
119
- shapes_[shape_idx] = std::nullopt;
150
+ shapes_[shape_idx] = std::nullopt; // Clear the used shape slot
120
151
 
121
152
  // --- Line Clearing ---
122
153
  int lines_cleared_count;
@@ -143,9 +174,8 @@ namespace trianglengin::cpp
143
174
 
144
175
  // --- Update State & Check Game Over ---
145
176
  current_step_++;
146
- valid_actions_cache_ = std::nullopt;
147
- get_valid_actions(true); // Force recalculation and update game_over_ if needed
148
- // 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
149
179
 
150
180
  // --- Calculate Reward & Update Score ---
151
181
  double reward = 0.0;
@@ -154,7 +184,8 @@ namespace trianglengin::cpp
154
184
 
155
185
  if (game_over_)
156
186
  {
157
- reward += config_.penalty_game_over;
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.
158
189
  }
159
190
  else
160
191
  {
@@ -162,34 +193,24 @@ namespace trianglengin::cpp
162
193
  }
163
194
  score_ += reward; // Update score based on calculated reward
164
195
 
165
- return {reward, game_over_};
196
+ return {reward, game_over_}; // Return the potentially updated game_over_ flag
166
197
  }
167
198
 
199
+ // --- Simplified is_over ---
168
200
  bool GameStateCpp::is_over() const
169
201
  {
170
- if (game_over_)
171
- return true;
172
- // If cache exists, use it. Otherwise, calculate.
173
- if (valid_actions_cache_.has_value())
174
- return valid_actions_cache_->empty();
175
- // Need to remove const to calculate and potentially set game_over_
176
- const_cast<GameStateCpp *>(this)->calculate_valid_actions_internal();
177
- // Check game_over_ again as calculate might have set it
178
- if (game_over_)
179
- return true;
180
- // Now the cache should exist
181
- 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_;
182
205
  }
183
206
 
184
207
  void GameStateCpp::force_game_over(const std::string &reason)
185
208
  {
186
- // This function only sets the flags and reason.
187
- // Score update should happen in the context where the game over is triggered (e.g., step).
188
209
  if (!game_over_)
189
210
  {
190
211
  game_over_ = true;
191
212
  game_over_reason_ = reason;
192
- valid_actions_cache_ = std::set<Action>();
213
+ valid_actions_cache_ = std::set<Action>(); // Clear valid actions on game over
193
214
  }
194
215
  }
195
216
 
@@ -200,25 +221,34 @@ namespace trianglengin::cpp
200
221
 
201
222
  const std::set<Action> &GameStateCpp::get_valid_actions(bool force_recalculate)
202
223
  {
224
+ // If game is over, always return the cached empty set
203
225
  if (game_over_)
204
226
  {
227
+ // Ensure cache is empty if game_over is true
205
228
  if (!valid_actions_cache_.has_value() || !valid_actions_cache_->empty())
206
229
  {
207
230
  valid_actions_cache_ = std::set<Action>();
208
231
  }
209
232
  return *valid_actions_cache_;
210
233
  }
234
+
235
+ // If not forcing and cache exists, return it
211
236
  if (!force_recalculate && valid_actions_cache_.has_value())
212
237
  {
213
238
  return *valid_actions_cache_;
214
239
  }
240
+
241
+ // Otherwise, calculate (which updates the mutable cache)
215
242
  calculate_valid_actions_internal();
216
- // Check if game should end *after* calculating actions
243
+
244
+ // Check if the calculation resulted in no valid actions, triggering game over
217
245
  if (!game_over_ && valid_actions_cache_->empty())
218
246
  {
247
+ // Set the game over flag HERE, as this is the definitive check after calculation
219
248
  force_game_over("No valid actions available.");
220
- // REMOVED C++ Log: std::cerr << "[GameStateCpp::get_valid_actions] Forced game over: No valid actions found after calculation." << std::endl;
221
249
  }
250
+
251
+ // Return the calculated (and potentially now empty) cache
222
252
  return *valid_actions_cache_;
223
253
  }
224
254
 
@@ -227,19 +257,12 @@ namespace trianglengin::cpp
227
257
  valid_actions_cache_ = std::nullopt;
228
258
  }
229
259
 
230
- // Make this non-const so it can modify the cache
231
- void GameStateCpp::calculate_valid_actions_internal() const // Keep const for now, use mutable cache
260
+ // calculate_valid_actions_internal remains const, modifies mutable cache
261
+ void GameStateCpp::calculate_valid_actions_internal() const
232
262
  {
233
- if (game_over_)
234
- {
235
- valid_actions_cache_ = std::set<Action>();
236
- // REMOVED C++ Log: std::cerr << "[GameStateCpp::calculate_valid_actions_internal] Game already over. Returning empty set." << std::endl;
237
- return;
238
- }
263
+ // This function should NOT set game_over_ directly.
264
+ // It just calculates the set. The caller (get_valid_actions) checks if empty.
239
265
  std::set<Action> valid_actions;
240
- int can_place_true_count = 0; // Counter for debugging
241
- int attempts_count = 0; // Counter for total placement checks
242
-
243
266
  for (int shape_idx = 0; shape_idx < static_cast<int>(shapes_.size()); ++shape_idx)
244
267
  {
245
268
  if (!shapes_[shape_idx].has_value())
@@ -247,46 +270,26 @@ namespace trianglengin::cpp
247
270
  const ShapeCpp &shape = shapes_[shape_idx].value();
248
271
  for (int r = 0; r < config_.rows; ++r)
249
272
  {
250
- const auto &[start_c, end_c] = config_.playable_range_per_row[r];
251
- for (int c = start_c; c < end_c; ++c)
273
+ // Optimization: Check only within playable range? No, C++ can_place handles death zones.
274
+ for (int c = 0; c < config_.cols; ++c)
252
275
  {
253
- attempts_count++; // Increment attempt counter
254
- bool can_place_result = grid_logic::can_place(grid_data_, shape, r, c); // Store result
255
- if (can_place_result)
276
+ if (grid_logic::can_place(grid_data_, shape, r, c))
256
277
  {
257
278
  valid_actions.insert(encode_action(shape_idx, r, c));
258
- can_place_true_count++; // Increment counter
259
279
  }
260
280
  }
261
281
  }
262
282
  }
263
- // REMOVED C++ Log:
264
- // std::cerr << "[GameStateCpp::calculate_valid_actions_internal] Step: " << current_step_
265
- // << ", Attempts: " << attempts_count
266
- // << ", CanPlaceTrue: " << can_place_true_count
267
- // << ", ValidActionsFound: " << valid_actions.size()
268
- // << std::endl;
269
-
270
- // Use mutable cache
271
- valid_actions_cache_ = std::move(valid_actions);
283
+ valid_actions_cache_ = std::move(valid_actions); // Update the mutable cache
272
284
  }
273
285
 
274
286
  int GameStateCpp::get_current_step() const { return current_step_; }
275
287
  std::optional<std::string> GameStateCpp::get_game_over_reason() const { return game_over_reason_; }
276
288
 
289
+ // Python-facing copy method uses the C++ copy constructor
277
290
  GameStateCpp GameStateCpp::copy() const
278
291
  {
279
- GameStateCpp newState = *this;
280
- // Copy the cache state explicitly
281
- if (this->valid_actions_cache_.has_value())
282
- {
283
- newState.valid_actions_cache_ = this->valid_actions_cache_;
284
- }
285
- else
286
- {
287
- newState.valid_actions_cache_ = std::nullopt;
288
- }
289
- return newState;
292
+ return GameStateCpp(*this);
290
293
  }
291
294
 
292
295
  void GameStateCpp::debug_toggle_cell(int r, int c)
@@ -304,33 +307,36 @@ namespace trianglengin::cpp
304
307
  grid_logic::check_and_clear_lines(grid_data_, {{r, c}});
305
308
  }
306
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);
307
312
  }
308
313
  }
309
314
 
310
315
  void GameStateCpp::debug_set_shapes(const std::vector<std::optional<ShapeCpp>> &new_shapes)
311
316
  {
312
- // Overwrite the current shapes with the provided ones.
313
- // Ensure the size matches the number of slots.
314
317
  size_t num_to_copy = std::min(new_shapes.size(), shapes_.size());
315
318
  for (size_t i = 0; i < num_to_copy; ++i)
316
319
  {
317
320
  shapes_[i] = new_shapes[i];
318
321
  }
319
- // Fill remaining slots with nullopt if new_shapes is shorter
320
322
  for (size_t i = num_to_copy; i < shapes_.size(); ++i)
321
323
  {
322
324
  shapes_[i] = std::nullopt;
323
325
  }
324
- // Invalidate cache as valid actions will change
325
326
  invalidate_action_cache();
327
+ // Force recalculation of valid actions and game over state after setting shapes
328
+ get_valid_actions(true);
326
329
  }
327
330
 
328
331
  Action GameStateCpp::encode_action(int shape_idx, int r, int c) const
329
332
  {
330
333
  int grid_size = config_.rows * config_.cols;
334
+ // Basic bounds check (more robust check in can_place)
331
335
  if (shape_idx < 0 || shape_idx >= config_.num_shape_slots || r < 0 || r >= config_.rows || c < 0 || c >= config_.cols)
332
336
  {
333
- throw std::out_of_range("encode_action arguments out of range.");
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.");
334
340
  }
335
341
  return shape_idx * grid_size + r * config_.cols + c;
336
342
  }
@@ -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_;
@@ -44,7 +44,7 @@ namespace trianglengin::cpp
44
44
  precompute_lines();
45
45
  }
46
46
 
47
- // Copy constructor: Copy the config_ member
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: Copy the config_ member
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
- return !death_grid_[r][c] && occupied_grid_[r][c];
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
- if (current_line.size() >= 2)
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
  {
@@ -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 constructor
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
@@ -8,7 +8,9 @@
8
8
  #include <tuple>
9
9
  #include <string>
10
10
  #include <cstdint>
11
- #include <utility> // For std::move
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trianglengin
3
- Version: 2.0.4
3
+ Version: 2.0.6
4
4
  Summary: High-performance C++/Python engine for a triangle puzzle game.
5
5
  Author-email: "Luis Guilherme P. M." <lgpelin92@gmail.com>
6
6
  License-Expression: MIT
@@ -1,13 +1,7 @@
1
1
  trianglengin.libs/libgcc_s-f3fb5a36.so.1,sha256=SrjjCCuY7RHj-T9JLrY9XFMgCCpYD9Qmezr4uoJGVEQ,168321
2
2
  trianglengin.libs/libstdc++-d2a021ba.so.6.0.32,sha256=1zr_iwGwEBe95gyKdgiw7C4Y1RR9ijV40j66rk4elzg,3537349
3
- trianglengin-2.0.4.dist-info/WHEEL,sha256=08waIFfcuXpGphEZVrOGuGLUENYlvK_7VkprEtHqWm0,110
4
- trianglengin-2.0.4.dist-info/RECORD,,
5
- trianglengin-2.0.4.dist-info/top_level.txt,sha256=YsSWmp_2zM23wRc5TRERHpVCgQuVYieYHDTpnwVQC7Y,13
6
- trianglengin-2.0.4.dist-info/entry_points.txt,sha256=kQEqO_U-MEpMEC0xwOPSucBzQIq2Ny7XwCtFSruZhvY,57
7
- trianglengin-2.0.4.dist-info/METADATA,sha256=Ep7c5ldlwGwB_JhzXsgAWju0AHq4k5144Z5dzqmmR6Q,12066
8
- trianglengin-2.0.4.dist-info/licenses/LICENSE,sha256=So3rgoJp-HgoxkclxZLIBC3pmmTwshN4tUO8KiQ6akc,1077
9
- trianglengin/trianglengin_cpp.cpython-37m-i386-linux-gnu.so,sha256=qegst4rVmNItDATC7mRkXDR7stYLUtu0HwLaPUeQwNU,309753
10
- trianglengin/trianglengin_cpp.cpython-310-i386-linux-gnu.so,sha256=qegst4rVmNItDATC7mRkXDR7stYLUtu0HwLaPUeQwNU,309753
3
+ trianglengin/trianglengin_cpp.cpython-37m-i386-linux-gnu.so,sha256=0Km5bNHk2jzAcyVL41xJ-UceeU700fFr5JX2A8OtgPM,301561
4
+ trianglengin/trianglengin_cpp.cpython-310-i386-linux-gnu.so,sha256=0Km5bNHk2jzAcyVL41xJ-UceeU700fFr5JX2A8OtgPM,301561
11
5
  trianglengin/game_interface.py,sha256=ijfBH08DU2sHyMH6XZmQbFQZDPGsKlkk3gk3AUt3fmU,9304
12
6
  trianglengin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
7
  trianglengin/__init__.py,sha256=4INnGvOYIjzOyCew7lhX6Irc8_H7QVxWc5jopt9TCZ4,919
@@ -15,16 +9,16 @@ trianglengin/config/README.md,sha256=PKm6HMVMZkA1KB22s2lP-jh5jwB7XIDiFgoShz_xj0s
15
9
  trianglengin/config/env_config.py,sha256=IMjbOrAZxgVzLjod9tmFhv964E30paG4zI1eUS8Nvy8,2359
16
10
  trianglengin/config/display_config.py,sha256=FHY9iKuuk7L5h-xWtDTthHUlvyme4AJeA5kOk-zZJFg,1815
17
11
  trianglengin/config/__init__.py,sha256=wKb-lRhguEAJCxcPHG1qyW-AmUherzMbUfi3SZ6IEao,160
18
- trianglengin/cpp/game_state.cpp,sha256=PL_YUf_3S8yOSfFpICr9zuq5Y_9j3gthaQZ4Th5jA5E,12090
19
- trianglengin/cpp/structs.h,sha256=v_aL5O5gwXH_qRPGUIkNplr167rgf7gCVlWIJKOLV1U,993
20
- trianglengin/cpp/grid_data.cpp,sha256=KH9BbLuhi-_fmKgnT2oPuzHRDo87xFDHiBfpXCwgYsw,6904
12
+ trianglengin/cpp/game_state.cpp,sha256=kvqUndWqV0kGLHW897RJNnFl8zwC8Dor5mGarnKXgLg,12710
13
+ trianglengin/cpp/structs.h,sha256=yGJp_VatwCHpDAcYJBpsIfEMEG4BQuqdDObqd7nBY7M,2155
14
+ trianglengin/cpp/grid_data.cpp,sha256=WDdaYN8GIPHxKMNvxSI_oUWa23pVYRt5WhX0N5EMGoU,7773
21
15
  trianglengin/cpp/grid_logic.h,sha256=O6roOE_U7nbKgAkaMkC8LrEjGDvnL3QiVzOnXunXy9s,781
22
16
  trianglengin/cpp/grid_logic.cpp,sha256=8ag6wwdmZuHg8_I-skTI9LOdB84a82IrZAoREq0dkQo,3333
23
17
  trianglengin/cpp/bindings.cpp,sha256=B3hfxIF_pBAyVG1QpbBvJT1awy8uJWRJSBNxaNY5_pY,8689
24
18
  trianglengin/cpp/shape_logic.h,sha256=QvjLAPG5y42FetpXXuCcYrWlcwvKGo7csU8Ch-RmdFs,626
25
19
  trianglengin/cpp/config.h,sha256=9Xd-9wTYmFwEuf-ZjrQa5zlXF58MrLhRKU1ygyG2Nmc,715
26
- trianglengin/cpp/grid_data.h,sha256=KGOtUQQb7HJnU_LyWIWW5g6S2gJ2u3ewY32qXtoPfcs,2453
27
- trianglengin/cpp/game_state.h,sha256=MBoAiG617XPtZNGqGPE2PC1pX1dBHuWBGHLJr3iBg6c,2547
20
+ trianglengin/cpp/grid_data.h,sha256=j_VDdBCQELg8m_kJ4GIyHstt7ynyLgjHyTcdBPv_AP4,2492
21
+ trianglengin/cpp/game_state.h,sha256=yhP0Uj1YAZ_eoTU9ZmI9xMTYPmdBYriXXdtwYMWpRR4,3221
28
22
  trianglengin/cpp/shape_logic.cpp,sha256=gF1mgolaNvxpNphwDScNUvOkoq4FuxFASB3n50WI3Bo,4133
29
23
  trianglengin/cpp/CMakeLists.txt,sha256=FCUnDHVQhe7PFj9FTQcQiWFgr61b8iJg0m0UH3hGYE4,1328
30
24
  trianglengin/utils/geometry.py,sha256=0SY1woDY5u_e2_TrNpG5y6w7a8n417Ndtiq0Z4QuuW8,2066
@@ -59,3 +53,9 @@ trianglengin/ui/visualization/core/__init__.py,sha256=26XCOn_jywwQ0YszGP6rEgFJIq
59
53
  trianglengin/ui/visualization/core/fonts.py,sha256=d9kQqdilPK0pTvXhv2oVJAbVT4pmWfjKxXfEir7Avdg,2016
60
54
  trianglengin/ui/visualization/core/visualizer.py,sha256=gWY4_vZGEzTokl6Z7xMeGvdrpJMl4YdZnfoT4bgBYW4,8860
61
55
  trianglengin/core/__init__.py,sha256=zMo_rn4wzVGzrevzZM4u4rkLwicZQeDqmXbnk9fI008,460
56
+ trianglengin-2.0.6.dist-info/WHEEL,sha256=08waIFfcuXpGphEZVrOGuGLUENYlvK_7VkprEtHqWm0,110
57
+ trianglengin-2.0.6.dist-info/RECORD,,
58
+ trianglengin-2.0.6.dist-info/top_level.txt,sha256=YsSWmp_2zM23wRc5TRERHpVCgQuVYieYHDTpnwVQC7Y,13
59
+ trianglengin-2.0.6.dist-info/entry_points.txt,sha256=kQEqO_U-MEpMEC0xwOPSucBzQIq2Ny7XwCtFSruZhvY,57
60
+ trianglengin-2.0.6.dist-info/METADATA,sha256=KpwlhUXJZa2uU6il2LnLChKau4twfPHHW-YQDXet7RM,12066
61
+ trianglengin-2.0.6.dist-info/licenses/LICENSE,sha256=So3rgoJp-HgoxkclxZLIBC3pmmTwshN4tUO8KiQ6akc,1077