trianglengin 2.0.1__cp312-cp312-musllinux_1_2_i686.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trianglengin/__init__.py +35 -0
- trianglengin/config/README.md +38 -0
- trianglengin/config/__init__.py +8 -0
- trianglengin/config/display_config.py +47 -0
- trianglengin/config/env_config.py +62 -0
- trianglengin/core/__init__.py +10 -0
- trianglengin/cpp/CMakeLists.txt +42 -0
- trianglengin/cpp/bindings.cpp +211 -0
- trianglengin/cpp/config.h +28 -0
- trianglengin/cpp/game_state.cpp +327 -0
- trianglengin/cpp/game_state.h +73 -0
- trianglengin/cpp/grid_data.cpp +239 -0
- trianglengin/cpp/grid_data.h +78 -0
- trianglengin/cpp/grid_logic.cpp +125 -0
- trianglengin/cpp/grid_logic.h +30 -0
- trianglengin/cpp/shape_logic.cpp +100 -0
- trianglengin/cpp/shape_logic.h +28 -0
- trianglengin/cpp/structs.h +40 -0
- trianglengin/game_interface.py +222 -0
- trianglengin/py.typed +0 -0
- trianglengin/trianglengin_cpp.cpython-312-i386-linux-musl.so +0 -0
- trianglengin/trianglengin_cpp.cpython-37m-i386-linux-gnu.so +0 -0
- trianglengin/ui/README.md +35 -0
- trianglengin/ui/__init__.py +21 -0
- trianglengin/ui/app.py +107 -0
- trianglengin/ui/cli.py +123 -0
- trianglengin/ui/config.py +44 -0
- trianglengin/ui/interaction/README.md +44 -0
- trianglengin/ui/interaction/__init__.py +19 -0
- trianglengin/ui/interaction/debug_mode_handler.py +72 -0
- trianglengin/ui/interaction/event_processor.py +49 -0
- trianglengin/ui/interaction/input_handler.py +89 -0
- trianglengin/ui/interaction/play_mode_handler.py +156 -0
- trianglengin/ui/visualization/README.md +42 -0
- trianglengin/ui/visualization/__init__.py +58 -0
- trianglengin/ui/visualization/core/README.md +51 -0
- trianglengin/ui/visualization/core/__init__.py +16 -0
- trianglengin/ui/visualization/core/colors.py +115 -0
- trianglengin/ui/visualization/core/coord_mapper.py +85 -0
- trianglengin/ui/visualization/core/fonts.py +65 -0
- trianglengin/ui/visualization/core/layout.py +77 -0
- trianglengin/ui/visualization/core/visualizer.py +248 -0
- trianglengin/ui/visualization/drawing/README.md +49 -0
- trianglengin/ui/visualization/drawing/__init__.py +43 -0
- trianglengin/ui/visualization/drawing/grid.py +213 -0
- trianglengin/ui/visualization/drawing/highlight.py +31 -0
- trianglengin/ui/visualization/drawing/hud.py +43 -0
- trianglengin/ui/visualization/drawing/previews.py +181 -0
- trianglengin/ui/visualization/drawing/shapes.py +46 -0
- trianglengin/ui/visualization/drawing/utils.py +23 -0
- trianglengin/utils/__init__.py +9 -0
- trianglengin/utils/geometry.py +62 -0
- trianglengin/utils/types.py +10 -0
- trianglengin-2.0.1.dist-info/METADATA +250 -0
- trianglengin-2.0.1.dist-info/RECORD +61 -0
- trianglengin-2.0.1.dist-info/WHEEL +5 -0
- trianglengin-2.0.1.dist-info/entry_points.txt +2 -0
- trianglengin-2.0.1.dist-info/licenses/LICENSE +22 -0
- trianglengin-2.0.1.dist-info/top_level.txt +1 -0
- trianglengin.libs/libgcc_s-f3fb5a36.so.1 +0 -0
- trianglengin.libs/libstdc++-d2a021ba.so.6.0.32 +0 -0
@@ -0,0 +1,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
|
Binary file
|
Binary file
|
@@ -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
|
+
]
|