trianglengin 2.0.1__cp312-cp312-musllinux_1_2_i686.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. trianglengin/__init__.py +35 -0
  2. trianglengin/config/README.md +38 -0
  3. trianglengin/config/__init__.py +8 -0
  4. trianglengin/config/display_config.py +47 -0
  5. trianglengin/config/env_config.py +62 -0
  6. trianglengin/core/__init__.py +10 -0
  7. trianglengin/cpp/CMakeLists.txt +42 -0
  8. trianglengin/cpp/bindings.cpp +211 -0
  9. trianglengin/cpp/config.h +28 -0
  10. trianglengin/cpp/game_state.cpp +327 -0
  11. trianglengin/cpp/game_state.h +73 -0
  12. trianglengin/cpp/grid_data.cpp +239 -0
  13. trianglengin/cpp/grid_data.h +78 -0
  14. trianglengin/cpp/grid_logic.cpp +125 -0
  15. trianglengin/cpp/grid_logic.h +30 -0
  16. trianglengin/cpp/shape_logic.cpp +100 -0
  17. trianglengin/cpp/shape_logic.h +28 -0
  18. trianglengin/cpp/structs.h +40 -0
  19. trianglengin/game_interface.py +222 -0
  20. trianglengin/py.typed +0 -0
  21. trianglengin/trianglengin_cpp.cpython-312-i386-linux-musl.so +0 -0
  22. trianglengin/trianglengin_cpp.cpython-37m-i386-linux-gnu.so +0 -0
  23. trianglengin/ui/README.md +35 -0
  24. trianglengin/ui/__init__.py +21 -0
  25. trianglengin/ui/app.py +107 -0
  26. trianglengin/ui/cli.py +123 -0
  27. trianglengin/ui/config.py +44 -0
  28. trianglengin/ui/interaction/README.md +44 -0
  29. trianglengin/ui/interaction/__init__.py +19 -0
  30. trianglengin/ui/interaction/debug_mode_handler.py +72 -0
  31. trianglengin/ui/interaction/event_processor.py +49 -0
  32. trianglengin/ui/interaction/input_handler.py +89 -0
  33. trianglengin/ui/interaction/play_mode_handler.py +156 -0
  34. trianglengin/ui/visualization/README.md +42 -0
  35. trianglengin/ui/visualization/__init__.py +58 -0
  36. trianglengin/ui/visualization/core/README.md +51 -0
  37. trianglengin/ui/visualization/core/__init__.py +16 -0
  38. trianglengin/ui/visualization/core/colors.py +115 -0
  39. trianglengin/ui/visualization/core/coord_mapper.py +85 -0
  40. trianglengin/ui/visualization/core/fonts.py +65 -0
  41. trianglengin/ui/visualization/core/layout.py +77 -0
  42. trianglengin/ui/visualization/core/visualizer.py +248 -0
  43. trianglengin/ui/visualization/drawing/README.md +49 -0
  44. trianglengin/ui/visualization/drawing/__init__.py +43 -0
  45. trianglengin/ui/visualization/drawing/grid.py +213 -0
  46. trianglengin/ui/visualization/drawing/highlight.py +31 -0
  47. trianglengin/ui/visualization/drawing/hud.py +43 -0
  48. trianglengin/ui/visualization/drawing/previews.py +181 -0
  49. trianglengin/ui/visualization/drawing/shapes.py +46 -0
  50. trianglengin/ui/visualization/drawing/utils.py +23 -0
  51. trianglengin/utils/__init__.py +9 -0
  52. trianglengin/utils/geometry.py +62 -0
  53. trianglengin/utils/types.py +10 -0
  54. trianglengin-2.0.1.dist-info/METADATA +250 -0
  55. trianglengin-2.0.1.dist-info/RECORD +61 -0
  56. trianglengin-2.0.1.dist-info/WHEEL +5 -0
  57. trianglengin-2.0.1.dist-info/entry_points.txt +2 -0
  58. trianglengin-2.0.1.dist-info/licenses/LICENSE +22 -0
  59. trianglengin-2.0.1.dist-info/top_level.txt +1 -0
  60. trianglengin.libs/libgcc_s-f3fb5a36.so.1 +0 -0
  61. trianglengin.libs/libstdc++-d2a021ba.so.6.0.32 +0 -0
@@ -0,0 +1,78 @@
1
+ // File: src/trianglengin/cpp/grid_data.h
2
+ #ifndef TRIANGLENGIN_CPP_GRID_DATA_H
3
+ #define TRIANGLENGIN_CPP_GRID_DATA_H
4
+
5
+ #pragma once
6
+
7
+ #include <vector>
8
+ #include <set>
9
+ #include <map>
10
+ #include <tuple>
11
+ #include <memory>
12
+ #include <optional>
13
+ #include <string>
14
+ #include <stdexcept>
15
+
16
+ #include "config.h"
17
+ #include "structs.h"
18
+
19
+ namespace trianglengin::cpp
20
+ {
21
+ using Line = std::vector<Coord>;
22
+ using LineFs = std::set<Coord>;
23
+ using LineFsSet = std::set<LineFs>;
24
+ using CoordMap = std::map<Coord, LineFsSet>;
25
+
26
+ class GridData
27
+ {
28
+ public:
29
+ // Constructor takes config by const reference but stores by value
30
+ explicit GridData(const EnvConfigCpp &config);
31
+
32
+ void reset();
33
+ bool is_valid(int r, int c) const;
34
+ bool is_death(int r, int c) const;
35
+ bool is_occupied(int r, int c) const;
36
+ std::optional<int> get_color_id(int r, int c) const;
37
+ bool is_up(int r, int c) const;
38
+
39
+ const std::vector<std::vector<bool>> &get_occupied_grid() const { return occupied_grid_; }
40
+ const std::vector<std::vector<int8_t>> &get_color_id_grid() const { return color_id_grid_; }
41
+ const std::vector<std::vector<bool>> &get_death_grid() const { return death_grid_; }
42
+ std::vector<std::vector<bool>> &get_occupied_grid_mut() { return occupied_grid_; }
43
+ std::vector<std::vector<int8_t>> &get_color_id_grid_mut() { return color_id_grid_; }
44
+
45
+ const std::vector<Line> &get_lines() const { return lines_; }
46
+ const CoordMap &get_coord_to_lines_map() const { return coord_to_lines_map_; }
47
+
48
+ int rows() const { return rows_; }
49
+ int cols() const { return cols_; }
50
+
51
+ // Copy constructor
52
+ GridData(const GridData &other);
53
+ // Copy assignment operator
54
+ GridData &operator=(const GridData &other);
55
+
56
+ // Default move constructor/assignment should work now
57
+ GridData(GridData &&other) noexcept = default;
58
+ GridData &operator=(GridData &&other) noexcept = default;
59
+
60
+ private:
61
+ EnvConfigCpp config_; // Store config by value now
62
+ int rows_;
63
+ int cols_;
64
+ std::vector<std::vector<bool>> occupied_grid_;
65
+ std::vector<std::vector<int8_t>> color_id_grid_;
66
+ std::vector<std::vector<bool>> death_grid_;
67
+
68
+ std::vector<Line> lines_;
69
+ CoordMap coord_to_lines_map_;
70
+
71
+ void precompute_lines();
72
+ bool is_live(int r, int c) const;
73
+ std::optional<Coord> get_neighbor(int r, int c, const std::string &direction, bool backward) const;
74
+ };
75
+
76
+ } // namespace trianglengin::cpp
77
+
78
+ #endif // TRIANGLENGIN_CPP_GRID_DATA_H
@@ -0,0 +1,125 @@
1
+
2
+ #include "grid_logic.h"
3
+ #include <stdexcept> // For potential errors, though can_place returns bool
4
+
5
+ namespace trianglengin::cpp::grid_logic
6
+ {
7
+
8
+ bool can_place(const GridData &grid_data, const ShapeCpp &shape, int r, int c)
9
+ {
10
+ if (shape.triangles.empty())
11
+ {
12
+ return false; // Cannot place an empty shape
13
+ }
14
+
15
+ for (const auto &tri_data : shape.triangles)
16
+ {
17
+ int dr, dc;
18
+ bool shape_is_up;
19
+ std::tie(dr, dc, shape_is_up) = tri_data;
20
+
21
+ int target_r = r + dr;
22
+ int target_c = c + dc;
23
+
24
+ // 1. Check bounds
25
+ if (!grid_data.is_valid(target_r, target_c))
26
+ {
27
+ return false;
28
+ }
29
+
30
+ // 2. Check death zone
31
+ if (grid_data.is_death(target_r, target_c))
32
+ {
33
+ return false;
34
+ }
35
+
36
+ // 3. Check occupancy
37
+ if (grid_data.is_occupied(target_r, target_c))
38
+ {
39
+ return false;
40
+ }
41
+
42
+ // 4. Check orientation match
43
+ bool grid_is_up = grid_data.is_up(target_r, target_c);
44
+ if (shape_is_up != grid_is_up)
45
+ {
46
+ return false;
47
+ }
48
+ }
49
+ // If all checks passed for all triangles
50
+ return true;
51
+ }
52
+
53
+ std::tuple<int, std::set<Coord>, LineFsSet> check_and_clear_lines(
54
+ GridData &grid_data,
55
+ const std::set<Coord> &newly_occupied_coords)
56
+ {
57
+ if (newly_occupied_coords.empty())
58
+ {
59
+ return {0, {}, {}};
60
+ }
61
+
62
+ const auto &coord_map = grid_data.get_coord_to_lines_map();
63
+ LineFsSet candidate_lines;
64
+
65
+ // 1. Find all maximal lines potentially affected by the new placements
66
+ for (const auto &coord : newly_occupied_coords)
67
+ {
68
+ auto it = coord_map.find(coord);
69
+ if (it != coord_map.end())
70
+ {
71
+ // Add all lines associated with this coordinate to the candidates
72
+ candidate_lines.insert(it->second.begin(), it->second.end());
73
+ }
74
+ }
75
+
76
+ LineFsSet lines_to_clear;
77
+ std::set<Coord> coords_to_clear;
78
+
79
+ // 2. Check each candidate line for completion
80
+ for (const auto &line_fs : candidate_lines)
81
+ {
82
+ if (line_fs.size() < 2)
83
+ continue; // Should not happen based on precomputation filter
84
+
85
+ bool line_complete = true;
86
+ for (const auto &coord : line_fs)
87
+ {
88
+ // A line is complete if ALL its coordinates are occupied
89
+ if (!grid_data.is_occupied(std::get<0>(coord), std::get<1>(coord)))
90
+ {
91
+ line_complete = false;
92
+ break;
93
+ }
94
+ }
95
+
96
+ if (line_complete)
97
+ {
98
+ lines_to_clear.insert(line_fs);
99
+ // Add coordinates from this completed line to the set to be cleared
100
+ coords_to_clear.insert(line_fs.begin(), line_fs.end());
101
+ }
102
+ }
103
+
104
+ // 3. Clear the identified coordinates
105
+ if (!coords_to_clear.empty())
106
+ {
107
+ auto &occupied_grid = grid_data.get_occupied_grid_mut();
108
+ auto &color_grid = grid_data.get_color_id_grid_mut();
109
+ for (const auto &coord : coords_to_clear)
110
+ {
111
+ int r = std::get<0>(coord);
112
+ int c = std::get<1>(coord);
113
+ // Ensure we don't try to clear out-of-bounds (shouldn't happen)
114
+ if (grid_data.is_valid(r, c))
115
+ {
116
+ occupied_grid[r][c] = false;
117
+ color_grid[r][c] = NO_COLOR_ID;
118
+ }
119
+ }
120
+ }
121
+
122
+ return {static_cast<int>(lines_to_clear.size()), coords_to_clear, lines_to_clear};
123
+ }
124
+
125
+ } // namespace trianglengin::cpp::grid_logic
@@ -0,0 +1,30 @@
1
+ // File: src/trianglengin/cpp/grid_logic.h
2
+ #ifndef TRIANGLENGIN_CPP_GRID_LOGIC_H
3
+ #define TRIANGLENGIN_CPP_GRID_LOGIC_H
4
+
5
+ #pragma once
6
+
7
+ #include <set>
8
+ #include <tuple>
9
+ #include <vector>
10
+
11
+ #include "grid_data.h" // Needs GridData definition
12
+ #include "structs.h" // Needs ShapeCpp, Coord, LineFsSet definitions
13
+
14
+ namespace trianglengin::cpp
15
+ {
16
+
17
+ // Forward declare GameStateCpp if needed by functions here
18
+ class GameStateCpp;
19
+
20
+ namespace grid_logic
21
+ {
22
+ bool can_place(const GridData &grid_data, const ShapeCpp &shape, int r, int c);
23
+
24
+ std::tuple<int, std::set<Coord>, LineFsSet>
25
+ check_and_clear_lines(GridData &grid_data, const std::set<Coord> &newly_occupied_coords);
26
+
27
+ } // namespace grid_logic
28
+ } // namespace trianglengin::cpp
29
+
30
+ #endif // TRIANGLENGIN_CPP_GRID_LOGIC_H
@@ -0,0 +1,100 @@
1
+ // File: src/trianglengin/cpp/shape_logic.cpp
2
+ #include "shape_logic.h"
3
+ #include "game_state.h" // Include full definition for implementation
4
+ #include <stdexcept>
5
+ #include <algorithm>
6
+
7
+ namespace trianglengin::cpp::shape_logic
8
+ {
9
+ // --- Shape Templates (Keep as defined previously) ---
10
+ const std::vector<std::vector<TriangleData>> PREDEFINED_SHAPE_TEMPLATES_CPP = {
11
+ {{0, 0, true}},
12
+ {{0, 0, true}},
13
+ {{0, 0, true}, {1, 0, false}},
14
+ {{0, 0, true}, {1, 0, false}},
15
+ {{0, 0, false}},
16
+ {{0, 0, true}, {0, 1, false}},
17
+ {{0, 0, true}, {0, 1, false}},
18
+ {{0, 0, false}, {0, 1, true}},
19
+ {{0, 0, false}, {0, 1, true}},
20
+ {{0, 0, true}, {0, 1, false}, {0, 2, true}},
21
+ {{0, 0, false}, {0, 1, true}, {0, 2, false}},
22
+ {{0, 0, true}, {0, 1, false}, {0, 2, true}, {1, 0, false}},
23
+ {{0, 0, true}, {0, 1, false}, {0, 2, true}, {1, 2, false}},
24
+ {{0, 0, false}, {0, 1, true}, {1, 0, true}, {1, 1, false}},
25
+ {{0, 0, true}, {0, 2, true}, {1, 0, false}, {1, 1, true}, {1, 2, false}},
26
+ {{0, 0, true}, {1, -2, false}, {1, -1, true}, {1, 0, false}},
27
+ {{0, 0, true}, {0, 1, false}, {1, 0, false}, {1, 1, true}},
28
+ {{0, 0, true}, {0, 1, false}, {1, 0, false}, {1, 1, true}, {1, 2, false}},
29
+ {{0, 0, true}, {0, 1, false}, {0, 2, true}, {1, 0, false}, {1, 1, true}},
30
+ {{0, 0, true}, {0, 1, false}, {0, 2, true}, {1, 0, false}, {1, 2, false}},
31
+ {{0, 0, true}, {0, 1, false}, {0, 2, true}, {1, 1, true}, {1, 2, false}},
32
+ {{0, 0, true}, {0, 2, true}, {1, 0, false}, {1, 1, true}, {1, 2, false}},
33
+ {{0, 0, true}, {0, 1, false}, {1, 0, false}, {1, 1, true}, {1, 2, false}},
34
+ {{0, 0, false}, {0, 1, true}, {1, 1, false}},
35
+ {{0, 0, true}, {1, -1, true}, {1, 0, false}},
36
+ {{0, 0, true}, {1, 0, false}, {1, 1, true}},
37
+ {{0, 0, true}, {1, -1, true}, {1, 0, false}, {1, 1, true}},
38
+ {{0, 0, true}, {1, -1, true}, {1, 0, false}},
39
+ {{0, 0, false}, {0, 1, true}, {0, 2, false}, {1, 1, false}},
40
+ {{0, 0, false}, {0, 1, true}, {1, 1, false}},
41
+ {{0, 0, true}, {0, 1, false}, {1, 0, false}},
42
+ };
43
+
44
+ const std::vector<ColorCpp> SHAPE_COLORS_CPP = {
45
+ {220, 40, 40}, {60, 60, 220}, {40, 200, 40}, {230, 230, 40}, {240, 150, 20}, {140, 40, 140}, {40, 200, 200}, {200, 100, 180}, {100, 180, 200}};
46
+ const std::vector<int> SHAPE_COLOR_IDS_CPP = {0, 1, 2, 3, 4, 5, 6, 7, 8};
47
+
48
+ } // namespace trianglengin::cpp::shape_logic
49
+
50
+ namespace trianglengin::cpp::shape_logic
51
+ {
52
+
53
+ ShapeCpp generate_random_shape(
54
+ std::mt19937 &rng,
55
+ const std::vector<ColorCpp> &available_colors,
56
+ const std::vector<int> &available_color_ids)
57
+ {
58
+ if (PREDEFINED_SHAPE_TEMPLATES_CPP.empty() || available_colors.empty() || available_colors.size() != available_color_ids.size())
59
+ {
60
+ throw std::runtime_error("Shape templates or colors are not properly initialized.");
61
+ }
62
+
63
+ std::uniform_int_distribution<size_t> template_dist(0, PREDEFINED_SHAPE_TEMPLATES_CPP.size() - 1);
64
+ size_t template_index = template_dist(rng);
65
+ const auto &chosen_template = PREDEFINED_SHAPE_TEMPLATES_CPP[template_index];
66
+
67
+ std::uniform_int_distribution<size_t> color_dist(0, available_colors.size() - 1);
68
+ size_t color_index = color_dist(rng);
69
+ const auto &chosen_color = available_colors[color_index];
70
+ int chosen_color_id = available_color_ids[color_index];
71
+
72
+ return ShapeCpp(chosen_template, chosen_color, chosen_color_id);
73
+ }
74
+
75
+ void refill_shape_slots(GameStateCpp &game_state, std::mt19937 &rng)
76
+ {
77
+ bool needs_refill = true;
78
+ // Use the public getter to access shapes
79
+ for (const auto &shape_opt : game_state.get_shapes())
80
+ {
81
+ if (shape_opt.has_value())
82
+ {
83
+ needs_refill = false;
84
+ break;
85
+ }
86
+ }
87
+
88
+ if (!needs_refill)
89
+ return;
90
+
91
+ // Use the mutable getter to modify shapes
92
+ auto &shapes_ref = game_state.get_shapes_mut();
93
+ for (size_t i = 0; i < shapes_ref.size(); ++i)
94
+ {
95
+ shapes_ref[i] = generate_random_shape(rng, SHAPE_COLORS_CPP, SHAPE_COLOR_IDS_CPP);
96
+ }
97
+ game_state.invalidate_action_cache();
98
+ }
99
+
100
+ } // namespace trianglengin::cpp::shape_logic
@@ -0,0 +1,28 @@
1
+ // File: src/trianglengin/cpp/shape_logic.h
2
+ #ifndef TRIANGLENGIN_CPP_SHAPE_LOGIC_H
3
+ #define TRIANGLENGIN_CPP_SHAPE_LOGIC_H
4
+
5
+ #pragma once
6
+
7
+ #include <vector>
8
+ #include <random>
9
+ #include <optional>
10
+
11
+ #include "structs.h" // Needs ShapeCpp definition
12
+
13
+ namespace trianglengin::cpp
14
+ {
15
+
16
+ // Forward declare GameStateCpp as it's used in function signatures
17
+ class GameStateCpp;
18
+
19
+ namespace shape_logic
20
+ {
21
+ std::vector<ShapeCpp> load_shape_templates();
22
+
23
+ void refill_shape_slots(GameStateCpp &game_state, std::mt19937 &rng);
24
+
25
+ } // namespace shape_logic
26
+ } // namespace trianglengin::cpp
27
+
28
+ #endif // TRIANGLENGIN_CPP_SHAPE_LOGIC_H
@@ -0,0 +1,40 @@
1
+ // File: src/trianglengin/cpp/structs.h
2
+ #ifndef TRIANGLENGIN_CPP_STRUCTS_H
3
+ #define TRIANGLENGIN_CPP_STRUCTS_H
4
+
5
+ #pragma once
6
+
7
+ #include <vector>
8
+ #include <tuple>
9
+ #include <string>
10
+ #include <cstdint>
11
+ #include <utility> // For std::move
12
+
13
+ namespace trianglengin::cpp
14
+ {
15
+ using Action = int;
16
+ using Coord = std::tuple<int, int>;
17
+ using ColorCpp = std::tuple<int, int, int>;
18
+ using TriangleData = std::tuple<int, int, bool>;
19
+
20
+ const int NO_COLOR_ID = -1;
21
+ const int DEBUG_COLOR_ID = -2;
22
+
23
+ struct ShapeCpp
24
+ {
25
+ std::vector<TriangleData> triangles;
26
+ ColorCpp color;
27
+ int color_id;
28
+
29
+ ShapeCpp() : color_id(NO_COLOR_ID) {}
30
+ ShapeCpp(std::vector<TriangleData> tris, ColorCpp c, int id)
31
+ : triangles(std::move(tris)), color(c), color_id(id) {}
32
+
33
+ bool operator==(const ShapeCpp &other) const
34
+ {
35
+ return triangles == other.triangles && color == other.color && color_id == other.color_id;
36
+ }
37
+ };
38
+ } // namespace trianglengin::cpp
39
+
40
+ #endif // TRIANGLENGIN_CPP_STRUCTS_H
@@ -0,0 +1,222 @@
1
+ # File: src/trianglengin/game_interface.py
2
+ import logging
3
+ import random
4
+ from typing import Any, cast # Import necessary types
5
+
6
+ import numpy as np
7
+
8
+ from .config import EnvConfig
9
+
10
+ try:
11
+ import trianglengin.trianglengin_cpp as cpp_module
12
+ except ImportError as e:
13
+ raise ImportError(
14
+ "Trianglengin C++ extension module ('trianglengin.trianglengin_cpp') not found. "
15
+ "Ensure the package was built correctly (`pip install -e .`). "
16
+ f"Original error: {e}"
17
+ ) from e
18
+
19
+
20
+ class Shape:
21
+ """Python representation of a shape's data returned from C++."""
22
+
23
+ def __init__(
24
+ self,
25
+ triangles: list[tuple[int, int, bool]],
26
+ color: tuple[int, int, int],
27
+ color_id: int,
28
+ ):
29
+ # Sort triangles for consistent representation and hashing
30
+ self.triangles: list[tuple[int, int, bool]] = sorted(triangles)
31
+ self.color: tuple[int, int, int] = color
32
+ self.color_id: int = color_id
33
+
34
+ def bbox(self) -> tuple[int, int, int, int]:
35
+ """Calculates bounding box (min_r, min_c, max_r, max_c) in relative coords."""
36
+ if not self.triangles:
37
+ return (0, 0, 0, 0)
38
+ rows = [t[0] for t in self.triangles]
39
+ cols = [t[1] for t in self.triangles]
40
+ return (min(rows), min(cols), max(rows), max(cols))
41
+
42
+ def copy(self) -> "Shape":
43
+ """Creates a shallow copy."""
44
+ # Use sorted triangles in the copy as well
45
+ return Shape(list(self.triangles), self.color, self.color_id)
46
+
47
+ def __eq__(self, other: object) -> bool:
48
+ if not isinstance(other, Shape):
49
+ return NotImplemented
50
+ # Comparison relies on sorted triangles
51
+ return (
52
+ self.triangles == other.triangles
53
+ and self.color == other.color
54
+ and self.color_id == other.color_id
55
+ )
56
+
57
+ def __hash__(self) -> int:
58
+ # Hash relies on sorted triangles (converted to tuple)
59
+ return hash((tuple(self.triangles), self.color, self.color_id))
60
+
61
+ def __str__(self) -> str:
62
+ return f"Shape(ColorID:{self.color_id}, Tris:{len(self.triangles)})"
63
+
64
+ def to_cpp_repr(
65
+ self,
66
+ ) -> tuple[list[tuple[int, int, bool]], tuple[int, int, int], int]:
67
+ """Converts this Python Shape object to the tuple format expected by C++ bindings."""
68
+ return self.triangles, self.color, self.color_id
69
+
70
+
71
+ log = logging.getLogger(__name__)
72
+
73
+
74
+ class GameState:
75
+ """
76
+ Python wrapper for the C++ GameState implementation.
77
+ Provides a Pythonic interface to the core game logic.
78
+ """
79
+
80
+ _cpp_state: cpp_module.GameStateCpp # Add type hint for instance variable
81
+
82
+ def __init__(
83
+ self, config: EnvConfig | None = None, initial_seed: int | None = None
84
+ ):
85
+ self.env_config: EnvConfig = config if config else EnvConfig()
86
+ used_seed = (
87
+ initial_seed if initial_seed is not None else random.randint(0, 2**32 - 1)
88
+ )
89
+ try:
90
+ # Pass the EnvConfig object directly to the C++ constructor binding
91
+ self._cpp_state = cpp_module.GameStateCpp(self.env_config, used_seed)
92
+ except Exception as e:
93
+ log.exception(f"Failed to initialize C++ GameStateCpp: {e}")
94
+ raise
95
+ self._cached_shapes: list[Shape | None] | None = None
96
+ self._cached_grid_data: dict[str, np.ndarray] | None = None
97
+
98
+ def reset(self) -> None:
99
+ """Resets the game to an initial state."""
100
+ self._cpp_state.reset()
101
+ self._clear_caches()
102
+ log.debug("Python GameState wrapper reset.")
103
+
104
+ def step(self, action: int) -> tuple[float, bool]:
105
+ """
106
+ Performs one game step based on the chosen action index.
107
+ Returns: (reward, done)
108
+ """
109
+ try:
110
+ # The C++ method already returns tuple[double, bool], cast might be redundant
111
+ # if pybind handles it, but explicit cast helps mypy if stub is missing details.
112
+ reward, done = cast("tuple[float, bool]", self._cpp_state.step(action))
113
+ self._clear_caches()
114
+ return reward, done
115
+ except Exception as e:
116
+ log.exception(f"Error during C++ step execution for action {action}: {e}")
117
+ # Return penalty and done=True consistent with C++ logic for errors during step
118
+ return self.env_config.PENALTY_GAME_OVER, True
119
+
120
+ def is_over(self) -> bool:
121
+ """Checks if the game is over."""
122
+ # C++ returns bool, cast might be redundant but safe for mypy
123
+ return cast("bool", self._cpp_state.is_over())
124
+
125
+ def game_score(self) -> float:
126
+ """Returns the current accumulated score."""
127
+ # C++ returns double, cast might be redundant but safe for mypy
128
+ return cast("float", self._cpp_state.get_score())
129
+
130
+ def valid_actions(self, force_recalculate: bool = False) -> set[int]:
131
+ """
132
+ Returns a set of valid encoded action indices for the current state.
133
+ """
134
+ # C++ returns std::set<int>, pybind converts to Python set. Cast is safe.
135
+ return cast(
136
+ "set[int]", set(self._cpp_state.get_valid_actions(force_recalculate))
137
+ )
138
+
139
+ def get_shapes(self) -> list[Shape | None]:
140
+ """Returns the list of current shapes in the preview slots."""
141
+ if self._cached_shapes is None:
142
+ # C++ returns list[Optional[tuple[list[tuple], tuple, int]]]
143
+ shapes_data = self._cpp_state.get_shapes_cpp()
144
+ self._cached_shapes = []
145
+ for data in shapes_data:
146
+ if data is None:
147
+ self._cached_shapes.append(None)
148
+ else:
149
+ # Explicitly cast the structure returned by pybind if needed
150
+ tris_py, color_py, id_py = cast(
151
+ "tuple[list[tuple[int, int, bool]], tuple[int, int, int], int]",
152
+ data,
153
+ )
154
+ self._cached_shapes.append(Shape(tris_py, color_py, id_py))
155
+ return self._cached_shapes
156
+
157
+ def get_grid_data_np(self) -> dict[str, np.ndarray]:
158
+ """
159
+ Returns the grid state (occupied, colors, death) as NumPy arrays.
160
+ Uses cached data if available.
161
+ """
162
+ if self._cached_grid_data is None:
163
+ # pybind numpy bindings should return np.ndarray directly
164
+ occupied_np = self._cpp_state.get_grid_occupied_flat()
165
+ color_id_np = self._cpp_state.get_grid_colors_flat()
166
+ death_np = self._cpp_state.get_grid_death_flat()
167
+ self._cached_grid_data = {
168
+ "occupied": occupied_np,
169
+ "color_id": color_id_np,
170
+ "death": death_np,
171
+ }
172
+ return self._cached_grid_data
173
+
174
+ @property
175
+ def current_step(self) -> int:
176
+ """Returns the current step count."""
177
+ return cast("int", self._cpp_state.get_current_step())
178
+
179
+ def get_game_over_reason(self) -> str | None:
180
+ """Returns the reason why the game ended, if it's over."""
181
+ return cast("str | None", self._cpp_state.get_game_over_reason())
182
+
183
+ def copy(self) -> "GameState":
184
+ """Creates a deep copy of the game state."""
185
+ new_wrapper = GameState.__new__(GameState)
186
+ new_wrapper.env_config = self.env_config
187
+ new_wrapper._cpp_state = self._cpp_state.copy() # C++ copy handles members
188
+ new_wrapper._cached_shapes = None
189
+ new_wrapper._cached_grid_data = None
190
+ return new_wrapper
191
+
192
+ def debug_toggle_cell(self, r: int, c: int) -> None:
193
+ """Toggles the state of a cell via the C++ implementation."""
194
+ self._cpp_state.debug_toggle_cell(r, c)
195
+ self._clear_caches()
196
+
197
+ def debug_set_shapes(self, shapes: list[Shape | None]) -> None:
198
+ """
199
+ Directly sets the shapes in the preview slots. For debugging/testing.
200
+ """
201
+ # Convert Python Shape objects (or None) to the tuple format expected by C++ binding
202
+ shapes_data = [s.to_cpp_repr() if s else None for s in shapes]
203
+ self._cpp_state.debug_set_shapes(shapes_data)
204
+ self._clear_caches() # Invalidate caches after changing shapes
205
+
206
+ def _clear_caches(self) -> None:
207
+ """Clears Python-level caches."""
208
+ self._cached_shapes = None
209
+ self._cached_grid_data = None
210
+
211
+ def __str__(self) -> str:
212
+ shapes_repr = [str(s) if s else "None" for s in self.get_shapes()]
213
+ status = "Over" if self.is_over() else "Ongoing"
214
+ return (
215
+ f"GameState(Step:{self.current_step}, Score:{self.game_score():.1f}, "
216
+ f"Status:{status}, Shapes:[{', '.join(shapes_repr)}])"
217
+ )
218
+
219
+ @property
220
+ def cpp_state(self) -> Any:
221
+ """Returns the underlying C++ GameState object."""
222
+ return self._cpp_state
trianglengin/py.typed ADDED
File without changes
@@ -0,0 +1,35 @@
1
+
2
+
3
+ # UI Module (`trianglengin.ui`)
4
+
5
+ ## Purpose
6
+
7
+ This module provides components for interacting with and visualizing the `trianglengin` game engine using Pygame and Typer. It is included as part of the main package installation.
8
+
9
+ ## Components
10
+
11
+ - **[`config.py`](config.py):** Defines `DisplayConfig` for UI-specific settings (screen size, FPS, fonts, colors).
12
+ - **[`visualization/`](visualization/README.md):** Contains the `Visualizer` class and drawing functions responsible for rendering the game state using Pygame.
13
+ - **[`interaction/`](interaction/README.md):** Contains the `InputHandler` class and helper functions to process keyboard/mouse input for interactive modes.
14
+ - **[`app.py`](app.py):** The `Application` class integrates the `GameState` (from the core engine), `Visualizer`, and `InputHandler` to run the interactive application loop.
15
+ - **[`cli.py`](cli.py):** Defines the command-line interface using Typer, providing the `trianglengin play` and `trianglengin debug` commands.
16
+
17
+ ## Usage
18
+
19
+ After installing the `trianglengin` package (`pip install trianglengin`), you can run the interactive commands:
20
+
21
+ ```bash
22
+ trianglengin play
23
+ trianglengin debug --seed 123
24
+ ```
25
+
26
+ ## Dependencies
27
+
28
+ - **`trianglengin` (core):** Uses `GameState`, `EnvConfig`, `Shape`.
29
+ - **`pygame`**: Required dependency for graphics, event handling, fonts.
30
+ - **`typer`**: Required dependency for the command-line interface.
31
+ - **Standard Libraries:** `typing`, `logging`, `sys`, `random`.
32
+
33
+ ---
34
+
35
+ **Note:** While included, these UI components are designed to be initialized only when running the specific CLI commands (`play`, `debug`) and should not interfere with using the core `trianglengin` library for simulations.
@@ -0,0 +1,21 @@
1
+ # src/trianglengin/ui/__init__.py
2
+ """
3
+ UI components for the Triangle Engine, including interactive modes
4
+ and visualization. Requires 'pygame' and 'typer'.
5
+ """
6
+
7
+ # Directly import the components. Dependencies are now required.
8
+ from .app import Application
9
+ from .cli import app as cli_app
10
+ from .config import DEFAULT_DISPLAY_CONFIG, DisplayConfig
11
+ from .interaction import InputHandler
12
+ from .visualization import Visualizer
13
+
14
+ __all__ = [
15
+ "Application",
16
+ "cli_app",
17
+ "DisplayConfig",
18
+ "DEFAULT_DISPLAY_CONFIG",
19
+ "InputHandler",
20
+ "Visualizer",
21
+ ]