trianglengin 2.0.4__pp310-pypy310_pp73-macosx_11_0_arm64.whl → 2.0.7__pp310-pypy310_pp73-macosx_11_0_arm64.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.
@@ -1,13 +1,14 @@
1
+
1
2
  // File: src/trianglengin/cpp/bindings.cpp
2
3
  #include <pybind11/pybind11.h>
3
4
  #include <pybind11/stl.h>
4
5
  #include <pybind11/operators.h>
5
6
  #include <pybind11/numpy.h>
6
- #include <pybind11/functional.h> // Needed for std::function with optional
7
+ #include <pybind11/functional.h>
7
8
  #include <vector>
8
9
  #include <stdexcept>
9
- #include <cstring> // For memcpy with int8_t
10
- #include <optional> // Include optional
10
+ #include <cstring>
11
+ #include <optional>
11
12
 
12
13
  #include "game_state.h"
13
14
  #include "config.h"
@@ -138,6 +139,7 @@ PYBIND11_MODULE(trianglengin_cpp, m)
138
139
  .def("get_score", &tg::GameStateCpp::get_score)
139
140
  .def("get_valid_actions", &tg::GameStateCpp::get_valid_actions, py::arg("force_recalculate") = false, py::return_value_policy::reference_internal)
140
141
  .def("get_current_step", &tg::GameStateCpp::get_current_step)
142
+ .def("get_last_cleared_triangles", &tg::GameStateCpp::get_last_cleared_triangles) // Added binding
141
143
  .def("get_game_over_reason", &tg::GameStateCpp::get_game_over_reason)
142
144
  .def("get_shapes_cpp", [](const tg::GameStateCpp &gs)
143
145
  {
@@ -154,7 +156,6 @@ PYBIND11_MODULE(trianglengin_cpp, m)
154
156
  py::array_t<bool> result({rows, cols});
155
157
  auto buf = result.request();
156
158
  bool *ptr = static_cast<bool *>(buf.ptr);
157
- // Manual copy for std::vector<bool>
158
159
  for (size_t r = 0; r < rows; ++r) {
159
160
  for (size_t c = 0; c < cols; ++c) {
160
161
  ptr[r * cols + c] = grid[r][c];
@@ -169,7 +170,6 @@ PYBIND11_MODULE(trianglengin_cpp, m)
169
170
  py::array_t<int8_t> result({rows, cols});
170
171
  auto buf = result.request();
171
172
  int8_t *ptr = static_cast<int8_t *>(buf.ptr);
172
- // memcpy is fine for int8_t
173
173
  for (size_t r = 0; r < rows; ++r) {
174
174
  std::memcpy(ptr + r * cols, grid[r].data(), cols * sizeof(int8_t));
175
175
  }
@@ -182,7 +182,6 @@ PYBIND11_MODULE(trianglengin_cpp, m)
182
182
  py::array_t<bool> result({rows, cols});
183
183
  auto buf = result.request();
184
184
  bool *ptr = static_cast<bool *>(buf.ptr);
185
- // Manual copy for std::vector<bool>
186
185
  for (size_t r = 0; r < rows; ++r) {
187
186
  for (size_t c = 0; c < cols; ++c) {
188
187
  ptr[r * cols + c] = grid[r][c];
@@ -191,13 +190,11 @@ PYBIND11_MODULE(trianglengin_cpp, m)
191
190
  return result; })
192
191
  .def("copy", &tg::GameStateCpp::copy)
193
192
  .def("debug_toggle_cell", &tg::GameStateCpp::debug_toggle_cell, py::arg("r"), py::arg("c"))
194
- // Add binding for debug_set_shapes
195
193
  .def("debug_set_shapes", [](tg::GameStateCpp &gs, const py::list &shapes_py)
196
194
  {
197
195
  std::vector<std::optional<tg::ShapeCpp>> shapes_cpp;
198
196
  shapes_cpp.reserve(shapes_py.size());
199
197
  for(const auto& shape_item_handle : shapes_py) {
200
- // Cast handle to object before passing to conversion function
201
198
  py::object shape_item = py::reinterpret_borrow<py::object>(shape_item_handle);
202
199
  shapes_cpp.push_back(python_to_cpp_shape(shape_item));
203
200
  }
@@ -1,10 +1,11 @@
1
+
1
2
  // File: src/trianglengin/cpp/game_state.cpp
2
3
  #include "game_state.h"
3
4
  #include "grid_logic.h"
4
5
  #include "shape_logic.h"
5
6
  #include <stdexcept>
6
7
  #include <numeric>
7
- #include <iostream> // Keep iostream if other debug logs might be added later, or remove if not needed
8
+ #include <iostream>
8
9
  #include <algorithm> // For std::min
9
10
 
10
11
  namespace trianglengin::cpp
@@ -16,18 +17,54 @@ namespace trianglengin::cpp
16
17
  shapes_(config_.num_shape_slots),
17
18
  score_(0.0),
18
19
  current_step_(0),
20
+ last_cleared_triangles_(0), // Initialize added member
19
21
  game_over_(false),
20
22
  rng_(initial_seed)
21
23
  {
22
24
  reset();
23
25
  }
24
26
 
27
+ // --- Explicit Copy Constructor ---
28
+ GameStateCpp::GameStateCpp(const GameStateCpp &other)
29
+ : config_(other.config_),
30
+ grid_data_(other.grid_data_),
31
+ shapes_(other.shapes_),
32
+ score_(other.score_),
33
+ current_step_(other.current_step_),
34
+ last_cleared_triangles_(other.last_cleared_triangles_), // Copy added member
35
+ game_over_(other.game_over_),
36
+ game_over_reason_(other.game_over_reason_),
37
+ valid_actions_cache_(other.valid_actions_cache_),
38
+ rng_(other.rng_)
39
+ {
40
+ }
41
+
42
+ // --- Explicit Copy Assignment Operator ---
43
+ GameStateCpp &GameStateCpp::operator=(const GameStateCpp &other)
44
+ {
45
+ if (this != &other)
46
+ {
47
+ config_ = other.config_;
48
+ grid_data_ = other.grid_data_;
49
+ shapes_ = other.shapes_;
50
+ score_ = other.score_;
51
+ current_step_ = other.current_step_;
52
+ last_cleared_triangles_ = other.last_cleared_triangles_; // Copy added member
53
+ game_over_ = other.game_over_;
54
+ game_over_reason_ = other.game_over_reason_;
55
+ valid_actions_cache_ = other.valid_actions_cache_;
56
+ rng_ = other.rng_;
57
+ }
58
+ return *this;
59
+ }
60
+
25
61
  void GameStateCpp::reset()
26
62
  {
27
63
  grid_data_.reset();
28
64
  std::fill(shapes_.begin(), shapes_.end(), std::nullopt);
29
65
  score_ = 0.0;
30
66
  current_step_ = 0;
67
+ last_cleared_triangles_ = 0; // Reset added member
31
68
  game_over_ = false;
32
69
  game_over_reason_ = std::nullopt;
33
70
  valid_actions_cache_ = std::nullopt;
@@ -37,29 +74,25 @@ namespace trianglengin::cpp
37
74
 
38
75
  void GameStateCpp::check_initial_state_game_over()
39
76
  {
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
- }
77
+ get_valid_actions(true);
46
78
  }
47
79
 
48
80
  std::tuple<double, bool> GameStateCpp::step(Action action)
49
81
  {
82
+ last_cleared_triangles_ = 0; // Reset before potential clearing
83
+
50
84
  if (game_over_)
51
85
  {
52
86
  return {0.0, true};
53
87
  }
54
88
 
55
89
  const auto &valid_actions = get_valid_actions();
90
+
56
91
  if (valid_actions.find(action) == valid_actions.end())
57
92
  {
58
- // Invalid action detected
59
93
  force_game_over("Invalid action provided: " + std::to_string(action));
60
- // Apply penalty to score *before* returning
61
94
  score_ += config_.penalty_game_over;
62
- return {config_.penalty_game_over, true}; // Return penalty and done=true
95
+ return {config_.penalty_game_over, true};
63
96
  }
64
97
 
65
98
  int shape_idx, r, c;
@@ -69,17 +102,15 @@ namespace trianglengin::cpp
69
102
  }
70
103
  catch (const std::out_of_range &e)
71
104
  {
72
- // Error during decoding (should be caught by valid_actions check, but defensive)
73
105
  force_game_over("Failed to decode action: " + std::to_string(action));
74
- score_ += config_.penalty_game_over; // Apply penalty
106
+ score_ += config_.penalty_game_over;
75
107
  return {config_.penalty_game_over, true};
76
108
  }
77
109
 
78
110
  if (shape_idx < 0 || shape_idx >= static_cast<int>(shapes_.size()) || !shapes_[shape_idx].has_value())
79
111
  {
80
- // Action references invalid slot (should be caught by valid_actions check)
81
112
  force_game_over("Action references invalid/empty shape slot: " + std::to_string(shape_idx));
82
- score_ += config_.penalty_game_over; // Apply penalty
113
+ score_ += config_.penalty_game_over;
83
114
  return {config_.penalty_game_over, true};
84
115
  }
85
116
 
@@ -87,9 +118,8 @@ namespace trianglengin::cpp
87
118
 
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
 
@@ -108,7 +138,7 @@ namespace trianglengin::cpp
108
138
  if (!grid_data_.is_valid(target_r, target_c) || grid_data_.is_death(target_r, target_c))
109
139
  {
110
140
  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
141
+ score_ += config_.penalty_game_over;
112
142
  return {config_.penalty_game_over, true};
113
143
  }
114
144
  occupied_grid[target_r][target_c] = true;
@@ -125,6 +155,7 @@ namespace trianglengin::cpp
125
155
  std::tie(lines_cleared_count, cleared_coords, cleared_lines_fs) =
126
156
  grid_logic::check_and_clear_lines(grid_data_, newly_occupied_coords);
127
157
  int cleared_count = static_cast<int>(cleared_coords.size());
158
+ last_cleared_triangles_ = cleared_count; // Store cleared count
128
159
 
129
160
  // --- Refill ---
130
161
  bool all_slots_empty = true;
@@ -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();
178
+ get_valid_actions(true);
149
179
 
150
180
  // --- Calculate Reward & Update Score ---
151
181
  double reward = 0.0;
@@ -154,37 +184,24 @@ namespace trianglengin::cpp
154
184
 
155
185
  if (game_over_)
156
186
  {
157
- reward += config_.penalty_game_over;
187
+ // Penalty already applied if game over was due to invalid action this step.
158
188
  }
159
189
  else
160
190
  {
161
191
  reward += config_.reward_per_step_alive;
162
192
  }
163
- score_ += reward; // Update score based on calculated reward
193
+ score_ += reward;
164
194
 
165
195
  return {reward, game_over_};
166
196
  }
167
197
 
168
198
  bool GameStateCpp::is_over() const
169
199
  {
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();
200
+ return game_over_;
182
201
  }
183
202
 
184
203
  void GameStateCpp::force_game_over(const std::string &reason)
185
204
  {
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
205
  if (!game_over_)
189
206
  {
190
207
  game_over_ = true;
@@ -208,17 +225,19 @@ namespace trianglengin::cpp
208
225
  }
209
226
  return *valid_actions_cache_;
210
227
  }
228
+
211
229
  if (!force_recalculate && valid_actions_cache_.has_value())
212
230
  {
213
231
  return *valid_actions_cache_;
214
232
  }
233
+
215
234
  calculate_valid_actions_internal();
216
- // Check if game should end *after* calculating actions
235
+
217
236
  if (!game_over_ && valid_actions_cache_->empty())
218
237
  {
219
238
  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
239
  }
240
+
222
241
  return *valid_actions_cache_;
223
242
  }
224
243
 
@@ -227,19 +246,9 @@ namespace trianglengin::cpp
227
246
  valid_actions_cache_ = std::nullopt;
228
247
  }
229
248
 
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
249
+ void GameStateCpp::calculate_valid_actions_internal() const
232
250
  {
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
- }
239
251
  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
252
  for (int shape_idx = 0; shape_idx < static_cast<int>(shapes_.size()); ++shape_idx)
244
253
  {
245
254
  if (!shapes_[shape_idx].has_value())
@@ -247,46 +256,25 @@ namespace trianglengin::cpp
247
256
  const ShapeCpp &shape = shapes_[shape_idx].value();
248
257
  for (int r = 0; r < config_.rows; ++r)
249
258
  {
250
- const auto &[start_c, end_c] = config_.playable_range_per_row[r];
251
- for (int c = start_c; c < end_c; ++c)
259
+ for (int c = 0; c < config_.cols; ++c)
252
260
  {
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)
261
+ if (grid_logic::can_place(grid_data_, shape, r, c))
256
262
  {
257
263
  valid_actions.insert(encode_action(shape_idx, r, c));
258
- can_place_true_count++; // Increment counter
259
264
  }
260
265
  }
261
266
  }
262
267
  }
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
268
  valid_actions_cache_ = std::move(valid_actions);
272
269
  }
273
270
 
274
271
  int GameStateCpp::get_current_step() const { return current_step_; }
272
+ int GameStateCpp::get_last_cleared_triangles() const { return last_cleared_triangles_; } // Added implementation
275
273
  std::optional<std::string> GameStateCpp::get_game_over_reason() const { return game_over_reason_; }
276
274
 
277
275
  GameStateCpp GameStateCpp::copy() const
278
276
  {
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;
277
+ return GameStateCpp(*this);
290
278
  }
291
279
 
292
280
  void GameStateCpp::debug_toggle_cell(int r, int c)
@@ -298,31 +286,31 @@ namespace trianglengin::cpp
298
286
  bool was_occupied = occupied_grid[r][c];
299
287
  occupied_grid[r][c] = !was_occupied;
300
288
  color_grid[r][c] = was_occupied ? NO_COLOR_ID : DEBUG_COLOR_ID;
289
+ last_cleared_triangles_ = 0; // Reset cleared count after manual toggle
301
290
  if (!was_occupied)
302
291
  {
303
292
  // Check for line clears only if a cell becomes occupied
304
- grid_logic::check_and_clear_lines(grid_data_, {{r, c}});
293
+ auto clear_result = grid_logic::check_and_clear_lines(grid_data_, {{r, c}});
294
+ last_cleared_triangles_ = static_cast<int>(std::get<1>(clear_result).size());
305
295
  }
306
- invalidate_action_cache(); // Always invalidate after manual change
296
+ invalidate_action_cache();
297
+ get_valid_actions(true);
307
298
  }
308
299
  }
309
300
 
310
301
  void GameStateCpp::debug_set_shapes(const std::vector<std::optional<ShapeCpp>> &new_shapes)
311
302
  {
312
- // Overwrite the current shapes with the provided ones.
313
- // Ensure the size matches the number of slots.
314
303
  size_t num_to_copy = std::min(new_shapes.size(), shapes_.size());
315
304
  for (size_t i = 0; i < num_to_copy; ++i)
316
305
  {
317
306
  shapes_[i] = new_shapes[i];
318
307
  }
319
- // Fill remaining slots with nullopt if new_shapes is shorter
320
308
  for (size_t i = num_to_copy; i < shapes_.size(); ++i)
321
309
  {
322
310
  shapes_[i] = std::nullopt;
323
311
  }
324
- // Invalidate cache as valid actions will change
325
312
  invalidate_action_cache();
313
+ get_valid_actions(true);
326
314
  }
327
315
 
328
316
  Action GameStateCpp::encode_action(int shape_idx, int r, int c) const
@@ -330,7 +318,7 @@ namespace trianglengin::cpp
330
318
  int grid_size = config_.rows * config_.cols;
331
319
  if (shape_idx < 0 || shape_idx >= config_.num_shape_slots || r < 0 || r >= config_.rows || c < 0 || c >= config_.cols)
332
320
  {
333
- throw std::out_of_range("encode_action arguments out of range.");
321
+ throw std::out_of_range("encode_action arguments out of range during valid action calculation.");
334
322
  }
335
323
  return shape_idx * grid_size + r * config_.cols + c;
336
324
  }
@@ -1,4 +1,4 @@
1
- // File: src/trianglengin/cpp/game_state.h
1
+
2
2
  #ifndef TRIANGLENGIN_CPP_GAME_STATE_H
3
3
  #define TRIANGLENGIN_CPP_GAME_STATE_H
4
4
 
@@ -27,14 +27,22 @@ 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;
33
40
  double get_score() const;
34
41
  const std::set<Action> &get_valid_actions(bool force_recalculate = false);
35
42
  int get_current_step() const;
43
+ int get_last_cleared_triangles() const; // Added getter
36
44
  std::optional<std::string> get_game_over_reason() const;
37
- GameStateCpp copy() const;
45
+ GameStateCpp copy() const; // Keep Python-facing copy method
38
46
  void debug_toggle_cell(int r, int c);
39
47
  void invalidate_action_cache(); // Moved to public
40
48
  // Debug method to force shapes into slots
@@ -46,6 +54,8 @@ namespace trianglengin::cpp
46
54
  const std::vector<std::optional<ShapeCpp>> &get_shapes() const { return shapes_; }
47
55
  std::vector<std::optional<ShapeCpp>> &get_shapes_mut() { return shapes_; }
48
56
  const EnvConfigCpp &get_config() const { return config_; }
57
+ // Expose RNG state for copying if needed (or handle seeding in copy)
58
+ std::mt19937 get_rng_state() const { return rng_; }
49
59
 
50
60
  private:
51
61
  EnvConfigCpp config_;
@@ -53,6 +63,7 @@ namespace trianglengin::cpp
53
63
  std::vector<std::optional<ShapeCpp>> shapes_;
54
64
  double score_;
55
65
  int current_step_;
66
+ int last_cleared_triangles_; // Added member
56
67
  bool game_over_;
57
68
  std::optional<std::string> game_over_reason_;
58
69
  mutable std::optional<std::set<Action>> valid_actions_cache_; // Mutable for const getter
@@ -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,14 +1,13 @@
1
1
  # File: src/trianglengin/game_interface.py
2
2
  import logging
3
3
  import random
4
- from typing import Any, cast # Import necessary types
4
+ from typing import Any, cast
5
5
 
6
6
  import numpy as np
7
7
 
8
8
  from .config import EnvConfig
9
9
 
10
10
  try:
11
- # Keep the alias for clarity within this file
12
11
  import trianglengin.trianglengin_cpp as cpp_module
13
12
  except ImportError as e:
14
13
  raise ImportError(
@@ -27,7 +26,6 @@ class Shape:
27
26
  color: tuple[int, int, int],
28
27
  color_id: int,
29
28
  ):
30
- # Sort triangles for consistent representation and hashing
31
29
  self.triangles: list[tuple[int, int, bool]] = sorted(triangles)
32
30
  self.color: tuple[int, int, int] = color
33
31
  self.color_id: int = color_id
@@ -42,13 +40,11 @@ class Shape:
42
40
 
43
41
  def copy(self) -> "Shape":
44
42
  """Creates a shallow copy."""
45
- # Use sorted triangles in the copy as well
46
43
  return Shape(list(self.triangles), self.color, self.color_id)
47
44
 
48
45
  def __eq__(self, other: object) -> bool:
49
46
  if not isinstance(other, Shape):
50
47
  return NotImplemented
51
- # Comparison relies on sorted triangles
52
48
  return (
53
49
  self.triangles == other.triangles
54
50
  and self.color == other.color
@@ -56,7 +52,6 @@ class Shape:
56
52
  )
57
53
 
58
54
  def __hash__(self) -> int:
59
- # Hash relies on sorted triangles (converted to tuple)
60
55
  return hash((tuple(self.triangles), self.color, self.color_id))
61
56
 
62
57
  def __str__(self) -> str:
@@ -78,7 +73,7 @@ class GameState:
78
73
  Provides a Pythonic interface to the core game logic.
79
74
  """
80
75
 
81
- _cpp_state: cpp_module.GameStateCpp # Add type hint for instance variable
76
+ _cpp_state: cpp_module.GameStateCpp
82
77
 
83
78
  def __init__(
84
79
  self, config: EnvConfig | None = None, initial_seed: int | None = None
@@ -88,7 +83,6 @@ class GameState:
88
83
  initial_seed if initial_seed is not None else random.randint(0, 2**32 - 1)
89
84
  )
90
85
  try:
91
- # Pass the EnvConfig object directly to the C++ constructor binding
92
86
  self._cpp_state = cpp_module.GameStateCpp(self.env_config, used_seed)
93
87
  except Exception as e:
94
88
  log.exception(f"Failed to initialize C++ GameStateCpp: {e}")
@@ -108,24 +102,19 @@ class GameState:
108
102
  Returns: (reward, done)
109
103
  """
110
104
  try:
111
- # The C++ method already returns tuple[double, bool], cast might be redundant
112
- # if pybind handles it, but explicit cast helps mypy if stub is missing details.
113
105
  reward, done = cast("tuple[float, bool]", self._cpp_state.step(action))
114
106
  self._clear_caches()
115
107
  return reward, done
116
108
  except Exception as e:
117
109
  log.exception(f"Error during C++ step execution for action {action}: {e}")
118
- # Return penalty and done=True consistent with C++ logic for errors during step
119
110
  return self.env_config.PENALTY_GAME_OVER, True
120
111
 
121
112
  def is_over(self) -> bool:
122
113
  """Checks if the game is over."""
123
- # C++ returns bool, cast might be redundant but safe for mypy
124
114
  return cast("bool", self._cpp_state.is_over())
125
115
 
126
116
  def game_score(self) -> float:
127
117
  """Returns the current accumulated score."""
128
- # C++ returns double, cast might be redundant but safe for mypy
129
118
  return cast("float", self._cpp_state.get_score())
130
119
 
131
120
  def get_outcome(self) -> float:
@@ -134,18 +123,14 @@ class GameState:
134
123
  Required by MCTS implementations like trimcts.
135
124
  """
136
125
  if self.is_over():
137
- # In this game, the final score is the outcome.
138
- # Adjust if a different outcome definition is needed (e.g., +1 win, -1 loss).
139
126
  return self.game_score()
140
127
  else:
141
- # MCTS typically expects 0 for non-terminal states during simulation.
142
128
  return 0.0
143
129
 
144
130
  def valid_actions(self, force_recalculate: bool = False) -> set[int]:
145
131
  """
146
132
  Returns a set of valid encoded action indices for the current state.
147
133
  """
148
- # C++ returns std::set<int>, pybind converts to Python set. Cast is safe.
149
134
  return cast(
150
135
  "set[int]", set(self._cpp_state.get_valid_actions(force_recalculate))
151
136
  )
@@ -153,14 +138,12 @@ class GameState:
153
138
  def get_shapes(self) -> list[Shape | None]:
154
139
  """Returns the list of current shapes in the preview slots."""
155
140
  if self._cached_shapes is None:
156
- # C++ returns list[Optional[tuple[list[tuple], tuple, int]]]
157
141
  shapes_data = self._cpp_state.get_shapes_cpp()
158
142
  self._cached_shapes = []
159
143
  for data in shapes_data:
160
144
  if data is None:
161
145
  self._cached_shapes.append(None)
162
146
  else:
163
- # Explicitly cast the structure returned by pybind if needed
164
147
  tris_py, color_py, id_py = cast(
165
148
  "tuple[list[tuple[int, int, bool]], tuple[int, int, int], int]",
166
149
  data,
@@ -174,7 +157,6 @@ class GameState:
174
157
  Uses cached data if available.
175
158
  """
176
159
  if self._cached_grid_data is None:
177
- # pybind numpy bindings should return np.ndarray directly
178
160
  occupied_np = self._cpp_state.get_grid_occupied_flat()
179
161
  color_id_np = self._cpp_state.get_grid_colors_flat()
180
162
  death_np = self._cpp_state.get_grid_death_flat()
@@ -190,6 +172,10 @@ class GameState:
190
172
  """Returns the current step count."""
191
173
  return cast("int", self._cpp_state.get_current_step())
192
174
 
175
+ def get_last_cleared_triangles(self) -> int:
176
+ """Returns the number of triangles cleared in the most recent step."""
177
+ return cast("int", self._cpp_state.get_last_cleared_triangles())
178
+
193
179
  def get_game_over_reason(self) -> str | None:
194
180
  """Returns the reason why the game ended, if it's over."""
195
181
  return cast("str | None", self._cpp_state.get_game_over_reason())
@@ -198,7 +184,7 @@ class GameState:
198
184
  """Creates a deep copy of the game state."""
199
185
  new_wrapper = GameState.__new__(GameState)
200
186
  new_wrapper.env_config = self.env_config
201
- new_wrapper._cpp_state = self._cpp_state.copy() # C++ copy handles members
187
+ new_wrapper._cpp_state = self._cpp_state.copy()
202
188
  new_wrapper._cached_shapes = None
203
189
  new_wrapper._cached_grid_data = None
204
190
  return new_wrapper
@@ -212,10 +198,9 @@ class GameState:
212
198
  """
213
199
  Directly sets the shapes in the preview slots. For debugging/testing.
214
200
  """
215
- # Convert Python Shape objects (or None) to the tuple format expected by C++ binding
216
201
  shapes_data = [s.to_cpp_repr() if s else None for s in shapes]
217
202
  self._cpp_state.debug_set_shapes(shapes_data)
218
- self._clear_caches() # Invalidate caches after changing shapes
203
+ self._clear_caches()
219
204
 
220
205
  def _clear_caches(self) -> None:
221
206
  """Clears Python-level caches."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trianglengin
3
- Version: 2.0.4
3
+ Version: 2.0.7
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
@@ -57,17 +57,141 @@ It encapsulates:
57
57
 
58
58
  ---
59
59
 
60
+
61
+
60
62
  ## 🎮 The Ultimate Triangle Puzzle Guide 🧩
61
63
 
62
- *(Game rules remain the same)*
64
+ Get ready to become a Triangle Master! This guide explains everything you need to know to play the game, step-by-step, with lots of details!
65
+
66
+ ### 1. Introduction: Your Mission! 🎯
67
+
68
+ Your goal is to place colorful shapes onto a special triangular grid. By filling up lines of triangles, you make them disappear and score points! Keep placing shapes and clearing lines for as long as possible to get the highest score before the grid fills up and you run out of moves. Sounds simple? Let's dive into the details!
69
+
70
+ ### 2. The Playing Field: The Grid 🗺️
71
+
72
+ - **Triangle Cells:** The game board is a grid made of many small triangles. Some point UP (🔺) and some point DOWN (🔻). They alternate like a checkerboard pattern based on their row and column index (specifically, `(row + col) % 2 != 0` means UP).
73
+ - **Shape:** The grid itself is rectangular overall, but the playable area within it is typically shaped like a triangle or hexagon, wider in the middle and narrower at the top and bottom.
74
+ - **Playable Area:** You can only place shapes within the designated playable area.
75
+ - **Death Zones 💀:** Around the edges of the playable area (often at the start and end of rows), some triangles are marked as "Death Zones". You **cannot** place any part of a shape onto these triangles. They are off-limits! Think of them as the boundaries within the rectangular grid.
76
+
77
+ ### 3. Your Tools: The Shapes 🟦🟥🟩
78
+
79
+ - **Shape Formation:** Each shape is a collection of connected small triangles (🔺 and 🔻). They come in different colors and arrangements. Some might be a single triangle, others might be long lines, L-shapes, or more complex patterns.
80
+ - **Relative Positions:** The triangles within a shape have fixed positions _relative to each other_. When you move the shape, all its triangles move together as one block.
81
+ - **Preview Area:** You will always have **three** shapes available to choose from at any time. These are shown in a special "preview area" on the side of the screen.
82
+
83
+ ### 4. Making Your Move: Placing Shapes 🖱️➡️▦
84
+
85
+ This is the core action! Here's exactly how to place a shape:
86
+
87
+ - **Step 4a: Select a Shape:** Look at the three shapes in the preview area. Click on the one you want to place. It should highlight 💡 to show it's selected.
88
+ - **Step 4b: Aim on the Grid:** Move your mouse cursor over the main grid. You'll see a faint "ghost" image of your selected shape following your mouse. This preview helps you aim.
89
+ - **Step 4c: The Placement Rules (MUST Follow!)**
90
+ - 📏 **Rule 1: Fit Inside Playable Area:** ALL triangles of your chosen shape must land within the playable grid area. No part of the shape can land in a Death Zone 💀.
91
+ - 🧱 **Rule 2: No Overlap:** ALL triangles of your chosen shape must land on currently _empty_ spaces on the grid. You cannot place a shape on top of triangles that are already filled with color from previous shapes.
92
+ - 📐 **Rule 3: Orientation Match!** This is crucial!
93
+ - If a part of your shape is an UP triangle (🔺), it MUST land on an UP space (🔺) on the grid.
94
+ - If a part of your shape is a DOWN triangle (🔻), it MUST land on a DOWN space (🔻) on the grid.
95
+ - 🔺➡️🔺 (OK!)
96
+ - 🔻➡️🔻 (OK!)
97
+ - 🔺➡️🔻 (INVALID! ❌)
98
+ - 🔻➡️🔺 (INVALID! ❌)
99
+ - **Visual Feedback:** The game helps you!
100
+ - 👍 **Valid Spot:** If the position under your mouse follows ALL three rules, the ghost preview will usually look solid and possibly greenish. This means you _can_ place the shape here.
101
+ - 👎 **Invalid Spot:** If the position breaks _any_ of the rules (out of bounds, overlaps, wrong orientation), the ghost preview will usually look faded and possibly reddish. This means you _cannot_ place the shape here.
102
+ - **Step 4d: Confirm Placement:** Once you find a **valid** spot (👍), click the left mouse button again. _Click!_ The shape is now placed permanently on the grid! ✨
103
+
104
+ ### 5. Scoring Points: How You Win! 🏆
105
+
106
+ You score points in two main ways:
107
+
108
+ - **Placing Triangles:** You get a small number of points for _every single small triangle_ that makes up the shape you just placed. (e.g., placing a 3-triangle shape might give you 3 \* tiny_score points).
109
+ - **Clearing Lines:** This is where the BIG points come from! You get a much larger number of points for _every single small triangle_ that disappears when you clear a line (or multiple lines at once!). See the next section for details!
110
+
111
+ ### 6. Line Clearing Magic! ✨ (The Key to High Scores!)
112
+
113
+ This is the most exciting part! When you place a shape, the game immediately checks if you've completed any lines. This section explains how the game _finds_ and _clears_ these lines.
114
+
115
+ - **What Lines Can Be Cleared?** There are **three** types of lines the game looks for:
116
+
117
+ - **Horizontal Lines ↔️:** A straight, unbroken line of filled triangles going across a single row.
118
+ - **Diagonal Lines (Top-Left to Bottom-Right) ↘️:** An unbroken diagonal line of filled triangles stepping down and to the right.
119
+ - **Diagonal Lines (Bottom-Left to Top-Right) ↗️:** An unbroken diagonal line of filled triangles stepping up and to the right.
120
+
121
+ - **How Lines are Found: Pre-calculation of Maximal Lines**
63
122
 
64
- *(... Game rules section remains unchanged ...)*
123
+ - **The Idea:** Instead of checking every possible line combination all the time, the game pre-calculates all *maximal* continuous lines of playable triangles when it starts. A **maximal line** is the longest possible straight segment of *playable* triangles (not in a Death Zone) in one of the three directions (Horizontal, Diagonal ↘️, Diagonal ↗️).
124
+ - **Tracing:** For every playable triangle on the grid, the game traces outwards in each of the three directions to find the full extent of the continuous playable line passing through that triangle in that direction.
125
+ - **Storing Maximal Lines:** Only the complete maximal lines found are stored. For example, if tracing finds a playable sequence `A-B-C-D`, only the line `(A,B,C,D)` is stored, not the sub-segments like `(A,B,C)` or `(B,C,D)`. These maximal lines represent the *potential* lines that can be cleared.
126
+ - **Coordinate Map:** The game also builds a map linking each playable triangle coordinate `(r, c)` to the set of maximal lines it belongs to. This allows for quick lookup.
127
+
128
+ - **Defining the Paths (Neighbor Logic):** How does the game know which triangle is "next" when tracing? It depends on the current triangle's orientation (🔺 or 🔻) and the direction being traced:
129
+
130
+ - **Horizontal ↔️:**
131
+ - Left Neighbor: `(r, c-1)` (Always in the same row)
132
+ - Right Neighbor: `(r, c+1)` (Always in the same row)
133
+ - **Diagonal ↘️ (TL-BR):**
134
+ - If current is 🔺 (Up): Next is `(r+1, c)` (Down triangle directly below)
135
+ - If current is 🔻 (Down): Next is `(r, c+1)` (Up triangle to the right)
136
+ - **Diagonal ↗️ (BL-TR):**
137
+ - If current is 🔻 (Down): Next is `(r-1, c)` (Up triangle directly above)
138
+ - If current is 🔺 (Up): Next is `(r, c+1)` (Down triangle to the right)
139
+
140
+ - **Visualizing the Paths:**
141
+
142
+ - **Horizontal ↔️:**
143
+ ```
144
+ ... [🔻][🔺][🔻][🔺][🔻][🔺] ... (Moves left/right in the same row)
145
+ ```
146
+ - **Diagonal ↘️ (TL-BR):** (Connects via shared horizontal edges)
147
+ ```
148
+ ...[🔺]...
149
+ ...[🔻][🔺] ...
150
+ ... [🔻][🔺] ...
151
+ ... [🔻] ...
152
+ (Path alternates row/col increments depending on orientation)
153
+ ```
154
+ - **Diagonal ↗️ (BL-TR):** (Connects via shared horizontal edges)
155
+ ```
156
+ ... [🔺] ...
157
+ ... [🔺][🔻] ...
158
+ ... [🔺][🔻] ...
159
+ ... [🔻] ...
160
+ (Path alternates row/col increments depending on orientation)
161
+ ```
162
+
163
+ - **The "Full Line" Rule:** After you place a piece, the game looks at the coordinates `(r, c)` of the triangles you just placed. Using the pre-calculated map, it finds all the *maximal* lines that contain _any_ of those coordinates. For each of those maximal lines (that have at least 2 triangles), it checks: "Is _every single triangle coordinate_ in this maximal line now occupied?" If yes, that line is complete! (Note: Single isolated triangles don't count as clearable lines).
164
+
165
+ - **The _Poof_! 💨:**
166
+ - If placing your shape completes one or MORE maximal lines (of any type, length >= 2) simultaneously, all the triangles in ALL completed lines vanish instantly!
167
+ - The spaces become empty again.
168
+ - You score points for _every single triangle_ that vanished. Clearing multiple lines at once is the best way to rack up points! 🥳
169
+
170
+ ### 7. Getting New Shapes: The Refill 🪄
171
+
172
+ - **The Trigger:** The game only gives you new shapes when a specific condition is met.
173
+ - **The Condition:** New shapes appear **only when all three of your preview slots become empty at the exact same time.**
174
+ - **How it Happens:** This usually occurs right after you place your _last_ available shape (the third one).
175
+ - **The Refill:** As soon as the third slot becomes empty, _BAM!_ 🪄 Three brand new, randomly generated shapes instantly appear in the preview slots.
176
+ - **Important:** If you place a shape and only one or two slots are empty, you **do not** get new shapes yet. You must use up all three before the refill happens.
177
+
178
+ ### 8. The End of the Road: Game Over 😭
179
+
180
+ So, how does the game end?
181
+
182
+ - **The Condition:** The game is over when you **cannot legally place _any_ of the three shapes currently available in your preview slots anywhere on the grid.**
183
+ - **The Check:** After every move (placing a shape and any resulting line clears), and after any potential shape refill, the game checks: "Is there at least one valid spot on the grid for Shape 1? OR for Shape 2? OR for Shape 3?"
184
+ - **No More Moves:** If the answer is "NO" for all three shapes (meaning none of them can be placed anywhere according to the Placement Rules), then the game immediately ends.
185
+ - **Strategy:** This means you need to be careful! Don't fill up the grid in a way that leaves no room for the types of shapes you might get later. Always try to keep options open! 🤔
186
+
187
+ That's it! Now you know all the rules. Go forth and conquer the Triangle Puzzle! 🏆
65
188
 
66
189
  ---
67
190
 
68
191
  ## Purpose
69
192
 
70
- The primary goal is to provide a self-contained, installable library with a high-performance C++ core and a Python interface for the triangle puzzle game. This allows different RL agent implementations or other applications to build upon a consistent and fast game backend. The interactive UI is included but only initialized when running the specific UI commands.
193
+ The primary goal is to provide a self-contained, installable library for the core logic and basic interactive UI of the triangle puzzle game. This allows different RL agent implementations or other applications to build upon a consistent and well-defined game backend, avoiding code duplication.
194
+
71
195
 
72
196
  ## Installation
73
197
 
@@ -1,13 +1,7 @@
1
- trianglengin-2.0.4.dist-info/RECORD,,
2
- trianglengin-2.0.4.dist-info/WHEEL,sha256=8UigNX42SJ7Bc2aBUBR_PNKLdWLJBQB8todoqHTAdoY,116
3
- trianglengin-2.0.4.dist-info/entry_points.txt,sha256=kQEqO_U-MEpMEC0xwOPSucBzQIq2Ny7XwCtFSruZhvY,57
4
- trianglengin-2.0.4.dist-info/top_level.txt,sha256=YsSWmp_2zM23wRc5TRERHpVCgQuVYieYHDTpnwVQC7Y,13
5
- trianglengin-2.0.4.dist-info/METADATA,sha256=Ep7c5ldlwGwB_JhzXsgAWju0AHq4k5144Z5dzqmmR6Q,12066
6
- trianglengin-2.0.4.dist-info/licenses/LICENSE,sha256=So3rgoJp-HgoxkclxZLIBC3pmmTwshN4tUO8KiQ6akc,1077
7
- trianglengin/trianglengin_cpp.pypy310-pp73-darwin.so,sha256=OeWk67c26OdRho8r9-AuGnswV4K6Yn-dBZfruqmb1GQ,233528
1
+ trianglengin/trianglengin_cpp.pypy310-pp73-darwin.so,sha256=lC_53hyhxt9RfudMdLQgR2lpoRTchQiFniPp3pB9wwI,233528
8
2
  trianglengin/__init__.py,sha256=4INnGvOYIjzOyCew7lhX6Irc8_H7QVxWc5jopt9TCZ4,919
9
3
  trianglengin/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- trianglengin/game_interface.py,sha256=ijfBH08DU2sHyMH6XZmQbFQZDPGsKlkk3gk3AUt3fmU,9304
4
+ trianglengin/game_interface.py,sha256=2zwSGMWg8GJtAXykOKTAwjAhihl9FAcKoz07-zeYNuY,7963
11
5
  trianglengin/ui/config.py,sha256=E68Qvbr3dwyhC_Rh_PK-QHWlHOrXFQhAsARZZMoQHNs,1579
12
6
  trianglengin/ui/__init__.py,sha256=AVIFYOWoZTt69NnkX0Ht3dEAPMpxQ6WZ8G9tDvxNXvA,558
13
7
  trianglengin/ui/README.md,sha256=M-5UU-NKG6qYu1riCUkftRZIwE2Jvf-5qOZ1QGWjPSg,1718
@@ -48,11 +42,17 @@ trianglengin/cpp/grid_logic.h,sha256=O6roOE_U7nbKgAkaMkC8LrEjGDvnL3QiVzOnXunXy9s
48
42
  trianglengin/cpp/CMakeLists.txt,sha256=FCUnDHVQhe7PFj9FTQcQiWFgr61b8iJg0m0UH3hGYE4,1328
49
43
  trianglengin/cpp/grid_logic.cpp,sha256=8ag6wwdmZuHg8_I-skTI9LOdB84a82IrZAoREq0dkQo,3333
50
44
  trianglengin/cpp/config.h,sha256=9Xd-9wTYmFwEuf-ZjrQa5zlXF58MrLhRKU1ygyG2Nmc,715
51
- trianglengin/cpp/grid_data.cpp,sha256=KH9BbLuhi-_fmKgnT2oPuzHRDo87xFDHiBfpXCwgYsw,6904
52
- trianglengin/cpp/grid_data.h,sha256=KGOtUQQb7HJnU_LyWIWW5g6S2gJ2u3ewY32qXtoPfcs,2453
53
- trianglengin/cpp/game_state.h,sha256=MBoAiG617XPtZNGqGPE2PC1pX1dBHuWBGHLJr3iBg6c,2547
54
- trianglengin/cpp/game_state.cpp,sha256=PL_YUf_3S8yOSfFpICr9zuq5Y_9j3gthaQZ4Th5jA5E,12090
55
- trianglengin/cpp/structs.h,sha256=v_aL5O5gwXH_qRPGUIkNplr167rgf7gCVlWIJKOLV1U,993
45
+ trianglengin/cpp/grid_data.cpp,sha256=WDdaYN8GIPHxKMNvxSI_oUWa23pVYRt5WhX0N5EMGoU,7773
46
+ trianglengin/cpp/grid_data.h,sha256=j_VDdBCQELg8m_kJ4GIyHstt7ynyLgjHyTcdBPv_AP4,2492
47
+ trianglengin/cpp/game_state.h,sha256=DLAWM3tVMpKMBo1LJjvWCwqeHYAh9aiT27b74dOhnnI,3288
48
+ trianglengin/cpp/game_state.cpp,sha256=qrXPYYnQnlFjbgqY5e85GEldUkjCdusSr-JsFlouGf4,10431
49
+ trianglengin/cpp/structs.h,sha256=yGJp_VatwCHpDAcYJBpsIfEMEG4BQuqdDObqd7nBY7M,2155
56
50
  trianglengin/cpp/shape_logic.h,sha256=QvjLAPG5y42FetpXXuCcYrWlcwvKGo7csU8Ch-RmdFs,626
57
- trianglengin/cpp/bindings.cpp,sha256=B3hfxIF_pBAyVG1QpbBvJT1awy8uJWRJSBNxaNY5_pY,8689
51
+ trianglengin/cpp/bindings.cpp,sha256=gCtvBxKrLRLO0OLXt-nbRn3opVMXftkEQrTkpE9G0Zc,8445
58
52
  trianglengin/cpp/shape_logic.cpp,sha256=gF1mgolaNvxpNphwDScNUvOkoq4FuxFASB3n50WI3Bo,4133
53
+ trianglengin-2.0.7.dist-info/RECORD,,
54
+ trianglengin-2.0.7.dist-info/WHEEL,sha256=8UigNX42SJ7Bc2aBUBR_PNKLdWLJBQB8todoqHTAdoY,116
55
+ trianglengin-2.0.7.dist-info/entry_points.txt,sha256=kQEqO_U-MEpMEC0xwOPSucBzQIq2Ny7XwCtFSruZhvY,57
56
+ trianglengin-2.0.7.dist-info/top_level.txt,sha256=YsSWmp_2zM23wRc5TRERHpVCgQuVYieYHDTpnwVQC7Y,13
57
+ trianglengin-2.0.7.dist-info/METADATA,sha256=utkuPTxtDnky8-jg8KAa1Ec_bwbAiWnWNerFjjh6BhU,22067
58
+ trianglengin-2.0.7.dist-info/licenses/LICENSE,sha256=So3rgoJp-HgoxkclxZLIBC3pmmTwshN4tUO8KiQ6akc,1077