trianglengin 2.0.1__pp310-pypy310_pp73-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- trianglengin/Release/trianglengin_cpp.pypy310-pp73-win_amd64.pyd +0 -0
- 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.pypy310-pp73-win_amd64.pyd +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 +59 -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
Binary file
|
trianglengin/__init__.py
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# Core engine exports
|
2
|
+
from .config import EnvConfig
|
3
|
+
from .game_interface import (
|
4
|
+
GameState,
|
5
|
+
Shape,
|
6
|
+
)
|
7
|
+
from .utils import ActionType, geometry
|
8
|
+
|
9
|
+
__all__ = [
|
10
|
+
# Core Interface & Config
|
11
|
+
"GameState",
|
12
|
+
"Shape",
|
13
|
+
"EnvConfig",
|
14
|
+
# Utilities & Types
|
15
|
+
"utils",
|
16
|
+
"geometry",
|
17
|
+
"ActionType",
|
18
|
+
]
|
19
|
+
|
20
|
+
# Attempt to import the C++ module to make its version accessible (optional)
|
21
|
+
try:
|
22
|
+
from . import trianglengin_cpp as _cpp_module
|
23
|
+
|
24
|
+
__version__ = _cpp_module.__version__
|
25
|
+
except ImportError:
|
26
|
+
# If C++ module not found (e.g., during docs build without compilation)
|
27
|
+
# Try getting version from package metadata if installed
|
28
|
+
try:
|
29
|
+
from importlib.metadata import version
|
30
|
+
|
31
|
+
__version__ = version("trianglengin")
|
32
|
+
except ImportError:
|
33
|
+
__version__ = "unknown" # Fallback
|
34
|
+
except Exception:
|
35
|
+
__version__ = "unknown (C++ module not found, package metadata failed)"
|
@@ -0,0 +1,38 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
# Core Configuration Module (`trianglengin.config`)
|
4
|
+
|
5
|
+
## Purpose
|
6
|
+
|
7
|
+
This module defines the core configuration settings for the `trianglengin` game engine, independent of any UI components.
|
8
|
+
|
9
|
+
- **[`env_config.py`](env_config.py):** Defines the `EnvConfig` Pydantic model. This class holds parameters crucial to the game simulation itself, such as:
|
10
|
+
- Grid dimensions (`ROWS`, `COLS`).
|
11
|
+
- Playable area definition (`PLAYABLE_RANGE_PER_ROW`).
|
12
|
+
- Number of shape preview slots (`NUM_SHAPE_SLOTS`).
|
13
|
+
- Reward/penalty values used by the C++ engine (`REWARD_PER_PLACED_TRIANGLE`, `REWARD_PER_CLEARED_TRIANGLE`, `REWARD_PER_STEP_ALIVE`, `PENALTY_GAME_OVER`).
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
An instance of `EnvConfig` is typically created and passed to the `trianglengin.game_interface.GameState` wrapper during initialization. The wrapper then passes these settings to the underlying C++ engine.
|
18
|
+
|
19
|
+
```python
|
20
|
+
from trianglengin import GameState, EnvConfig
|
21
|
+
|
22
|
+
# Use default configuration
|
23
|
+
default_config = EnvConfig()
|
24
|
+
game = GameState(config=default_config)
|
25
|
+
|
26
|
+
# Use custom configuration
|
27
|
+
custom_config = EnvConfig(ROWS=10, COLS=10, PLAYABLE_RANGE_PER_ROW=[(0,10)]*10)
|
28
|
+
custom_game = GameState(config=custom_config)
|
29
|
+
```
|
30
|
+
|
31
|
+
## Dependencies
|
32
|
+
|
33
|
+
- **`pydantic`**: Used for data validation and settings management.
|
34
|
+
- **Standard Libraries:** `typing`.
|
35
|
+
|
36
|
+
---
|
37
|
+
|
38
|
+
**Note:** Configuration related to display or interactive UI elements (like screen size, FPS, fonts) is handled separately in the optional `trianglengin.ui.config` module.
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# trianglengin/config/display_config.py
|
2
|
+
"""
|
3
|
+
Configuration specific to display and visualization settings.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import pygame
|
7
|
+
from pydantic import BaseModel, Field
|
8
|
+
|
9
|
+
# Initialize Pygame font module if not already done (safe to call multiple times)
|
10
|
+
pygame.font.init()
|
11
|
+
|
12
|
+
# Define a placeholder font loading function or load directly here
|
13
|
+
# In a real app, this might load from files or use system fonts more robustly.
|
14
|
+
try:
|
15
|
+
DEBUG_FONT_DEFAULT = pygame.font.SysFont("monospace", 12)
|
16
|
+
except Exception:
|
17
|
+
DEBUG_FONT_DEFAULT = pygame.font.Font(None, 15) # Fallback default pygame font
|
18
|
+
|
19
|
+
|
20
|
+
class DisplayConfig(BaseModel):
|
21
|
+
"""Configuration for visualization display settings."""
|
22
|
+
|
23
|
+
# Screen and Layout
|
24
|
+
SCREEN_WIDTH: int = Field(default=1024, gt=0)
|
25
|
+
SCREEN_HEIGHT: int = Field(default=768, gt=0)
|
26
|
+
FPS: int = Field(default=60, gt=0)
|
27
|
+
PADDING: int = Field(default=10, ge=0)
|
28
|
+
HUD_HEIGHT: int = Field(default=30, ge=0)
|
29
|
+
PREVIEW_AREA_WIDTH: int = Field(default=150, ge=50)
|
30
|
+
PREVIEW_PADDING: int = Field(default=5, ge=0)
|
31
|
+
PREVIEW_INNER_PADDING: int = Field(default=3, ge=0)
|
32
|
+
PREVIEW_BORDER_WIDTH: int = Field(default=1, ge=0)
|
33
|
+
PREVIEW_SELECTED_BORDER_WIDTH: int = Field(default=3, ge=0)
|
34
|
+
|
35
|
+
# Fonts (Store font objects directly or paths/names)
|
36
|
+
# Using Field(default=...) requires the default value to be simple.
|
37
|
+
# For complex objects like fonts, use default_factory or initialize in __init__.
|
38
|
+
# For simplicity here, we'll assign the pre-loaded font.
|
39
|
+
# Consider using default_factory=lambda: pygame.font.SysFont(...)
|
40
|
+
DEBUG_FONT: pygame.font.Font = Field(default=DEBUG_FONT_DEFAULT)
|
41
|
+
|
42
|
+
class Config:
|
43
|
+
arbitrary_types_allowed = True # Allow pygame.font.Font
|
44
|
+
|
45
|
+
|
46
|
+
# Optional: Create a default instance for easy import elsewhere
|
47
|
+
DEFAULT_DISPLAY_CONFIG = DisplayConfig()
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# File: src/trianglengin/config/env_config.py
|
2
|
+
|
3
|
+
from pydantic import BaseModel, Field, ValidationInfo, field_validator, model_validator
|
4
|
+
|
5
|
+
|
6
|
+
class EnvConfig(BaseModel):
|
7
|
+
"""Configuration for the game environment (Pydantic model)."""
|
8
|
+
|
9
|
+
ROWS: int = Field(default=8, gt=0)
|
10
|
+
COLS: int = Field(default=15, gt=0)
|
11
|
+
PLAYABLE_RANGE_PER_ROW: list[tuple[int, int]] = Field( # Use List, Tuple
|
12
|
+
default=[(3, 12), (2, 13), (1, 14), (0, 15), (0, 15), (1, 14), (2, 13), (3, 12)]
|
13
|
+
)
|
14
|
+
NUM_SHAPE_SLOTS: int = Field(default=3, gt=0)
|
15
|
+
|
16
|
+
REWARD_PER_PLACED_TRIANGLE: float = Field(default=0.01)
|
17
|
+
REWARD_PER_CLEARED_TRIANGLE: float = Field(default=0.5)
|
18
|
+
REWARD_PER_STEP_ALIVE: float = Field(default=0.005)
|
19
|
+
PENALTY_GAME_OVER: float = Field(default=-10.0)
|
20
|
+
|
21
|
+
@field_validator("PLAYABLE_RANGE_PER_ROW")
|
22
|
+
@classmethod
|
23
|
+
def check_playable_range_length(
|
24
|
+
cls,
|
25
|
+
v: list[tuple[int, int]],
|
26
|
+
info: ValidationInfo, # Use List, Tuple
|
27
|
+
) -> list[tuple[int, int]]: # Use List, Tuple
|
28
|
+
"""Validates PLAYABLE_RANGE_PER_ROW."""
|
29
|
+
rows = info.data.get("ROWS")
|
30
|
+
cols = info.data.get("COLS")
|
31
|
+
|
32
|
+
if rows is None or cols is None:
|
33
|
+
return v
|
34
|
+
|
35
|
+
if len(v) != rows:
|
36
|
+
raise ValueError(
|
37
|
+
f"PLAYABLE_RANGE_PER_ROW length ({len(v)}) must equal ROWS ({rows})"
|
38
|
+
)
|
39
|
+
|
40
|
+
for i, (start, end) in enumerate(v):
|
41
|
+
if not (0 <= start <= cols):
|
42
|
+
raise ValueError(
|
43
|
+
f"Row {i}: start_col ({start}) out of bounds [0, {cols}]."
|
44
|
+
)
|
45
|
+
if not (start <= end <= cols):
|
46
|
+
raise ValueError(
|
47
|
+
f"Row {i}: end_col ({end}) invalid. Must be >= start_col ({start}) and <= COLS ({cols})."
|
48
|
+
)
|
49
|
+
return v
|
50
|
+
|
51
|
+
@model_validator(mode="after")
|
52
|
+
def check_cols_sufficient_for_ranges(self) -> "EnvConfig":
|
53
|
+
"""Ensure COLS is large enough for the specified ranges."""
|
54
|
+
if hasattr(self, "PLAYABLE_RANGE_PER_ROW") and self.PLAYABLE_RANGE_PER_ROW:
|
55
|
+
max_end_col = max(
|
56
|
+
(end for _, end in self.PLAYABLE_RANGE_PER_ROW), default=0
|
57
|
+
)
|
58
|
+
if max_end_col > self.COLS:
|
59
|
+
raise ValueError(
|
60
|
+
f"COLS ({self.COLS}) must be >= max end_col ({max_end_col})"
|
61
|
+
)
|
62
|
+
return self
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# File: src/trianglengin/core/__init__.py
|
2
|
+
# This module is now significantly smaller.
|
3
|
+
# It might only contain the environment submodule placeholder if needed,
|
4
|
+
# or be empty if environment is fully represented by game_interface.
|
5
|
+
# Let's keep it minimal for now.
|
6
|
+
|
7
|
+
# No submodules left to export directly from here in the new structure.
|
8
|
+
# The main __init__.py exports the GameState interface.
|
9
|
+
|
10
|
+
__all__: list[str] = [] # No public exports from this level anymore
|
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
cmake_minimum_required(VERSION 3.14)
|
3
|
+
project(trianglengin_cpp LANGUAGES CXX)
|
4
|
+
|
5
|
+
# Locate Pybind11 using variables passed from setup.py (pybind11_DIR)
|
6
|
+
find_package(pybind11 CONFIG REQUIRED)
|
7
|
+
|
8
|
+
# Sources
|
9
|
+
set(TRIANGLENGIN_SOURCES
|
10
|
+
bindings.cpp
|
11
|
+
game_state.cpp
|
12
|
+
grid_data.cpp
|
13
|
+
grid_logic.cpp
|
14
|
+
shape_logic.cpp
|
15
|
+
# Add other .cpp files if needed
|
16
|
+
)
|
17
|
+
|
18
|
+
# Build the pybind11 module
|
19
|
+
pybind11_add_module(trianglengin_cpp MODULE ${TRIANGLENGIN_SOURCES})
|
20
|
+
|
21
|
+
# C++17 Standard
|
22
|
+
target_compile_features(trianglengin_cpp PRIVATE cxx_std_17)
|
23
|
+
|
24
|
+
# Include directories (current source dir for headers)
|
25
|
+
target_include_directories(trianglengin_cpp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR})
|
26
|
+
|
27
|
+
# Optimisation flags and visibility
|
28
|
+
if(MSVC)
|
29
|
+
target_compile_options(trianglengin_cpp PRIVATE /O2 /W4) # Add warning level
|
30
|
+
else()
|
31
|
+
target_compile_options(trianglengin_cpp PRIVATE -O3 -DNDEBUG -Wall -Wextra -pedantic) # Add warnings
|
32
|
+
# Symbol visibility for non-Apple Unix-like systems
|
33
|
+
if(NOT APPLE)
|
34
|
+
target_compile_options(trianglengin_cpp PRIVATE -fvisibility=hidden)
|
35
|
+
endif()
|
36
|
+
endif()
|
37
|
+
|
38
|
+
# Output directory is set via CMAKE_LIBRARY_OUTPUT_DIRECTORY in setup.py
|
39
|
+
|
40
|
+
# --- Status Messages ---
|
41
|
+
message(STATUS "pybind11 Include Dirs: ${pybind11_INCLUDE_DIRS}")
|
42
|
+
message(STATUS "Building C++ extension for Trianglengin version ${TRIANGLENGIN_VERSION_INFO}")
|
@@ -0,0 +1,211 @@
|
|
1
|
+
// File: src/trianglengin/cpp/bindings.cpp
|
2
|
+
#include <pybind11/pybind11.h>
|
3
|
+
#include <pybind11/stl.h>
|
4
|
+
#include <pybind11/operators.h>
|
5
|
+
#include <pybind11/numpy.h>
|
6
|
+
#include <pybind11/functional.h> // Needed for std::function with optional
|
7
|
+
#include <vector>
|
8
|
+
#include <stdexcept>
|
9
|
+
#include <cstring> // For memcpy with int8_t
|
10
|
+
#include <optional> // Include optional
|
11
|
+
|
12
|
+
#include "game_state.h"
|
13
|
+
#include "config.h"
|
14
|
+
#include "structs.h"
|
15
|
+
|
16
|
+
namespace py = pybind11;
|
17
|
+
namespace tg = trianglengin::cpp;
|
18
|
+
|
19
|
+
// Helper to convert Python EnvConfig to C++ EnvConfigCpp
|
20
|
+
tg::EnvConfigCpp python_to_cpp_env_config(const py::object &py_config)
|
21
|
+
{
|
22
|
+
tg::EnvConfigCpp cpp_config;
|
23
|
+
try
|
24
|
+
{
|
25
|
+
cpp_config.rows = py_config.attr("ROWS").cast<int>();
|
26
|
+
cpp_config.cols = py_config.attr("COLS").cast<int>();
|
27
|
+
cpp_config.num_shape_slots = py_config.attr("NUM_SHAPE_SLOTS").cast<int>();
|
28
|
+
|
29
|
+
py::list py_ranges = py_config.attr("PLAYABLE_RANGE_PER_ROW").cast<py::list>();
|
30
|
+
cpp_config.playable_range_per_row.clear();
|
31
|
+
cpp_config.playable_range_per_row.reserve(py_ranges.size());
|
32
|
+
for (const auto &item : py_ranges)
|
33
|
+
{
|
34
|
+
py::tuple range_tuple = item.cast<py::tuple>();
|
35
|
+
if (range_tuple.size() != 2)
|
36
|
+
throw std::runtime_error("Playable range tuple must have 2 elements.");
|
37
|
+
cpp_config.playable_range_per_row.emplace_back(range_tuple[0].cast<int>(), range_tuple[1].cast<int>());
|
38
|
+
}
|
39
|
+
if (cpp_config.playable_range_per_row.size() != static_cast<size_t>(cpp_config.rows))
|
40
|
+
{
|
41
|
+
throw std::runtime_error("Mismatch between ROWS and PLAYABLE_RANGE_PER_ROW length.");
|
42
|
+
}
|
43
|
+
|
44
|
+
cpp_config.reward_per_placed_triangle = py_config.attr("REWARD_PER_PLACED_TRIANGLE").cast<double>();
|
45
|
+
cpp_config.reward_per_cleared_triangle = py_config.attr("REWARD_PER_CLEARED_TRIANGLE").cast<double>();
|
46
|
+
cpp_config.reward_per_step_alive = py_config.attr("REWARD_PER_STEP_ALIVE").cast<double>();
|
47
|
+
cpp_config.penalty_game_over = py_config.attr("PENALTY_GAME_OVER").cast<double>();
|
48
|
+
cpp_config.action_dim = cpp_config.num_shape_slots * cpp_config.rows * cpp_config.cols;
|
49
|
+
}
|
50
|
+
catch (const py::error_already_set &e)
|
51
|
+
{
|
52
|
+
throw std::runtime_error(std::string("Error accessing EnvConfig attributes: ") + e.what());
|
53
|
+
}
|
54
|
+
catch (const std::exception &e)
|
55
|
+
{
|
56
|
+
throw std::runtime_error(std::string("Error converting EnvConfig: ") + e.what());
|
57
|
+
}
|
58
|
+
return cpp_config;
|
59
|
+
}
|
60
|
+
|
61
|
+
// Helper to convert C++ optional<ShapeCpp> to Python tuple or None
|
62
|
+
py::object cpp_shape_to_python(const std::optional<tg::ShapeCpp> &shape_opt)
|
63
|
+
{
|
64
|
+
if (!shape_opt)
|
65
|
+
return py::none();
|
66
|
+
const auto &shape = shape_opt.value();
|
67
|
+
py::list triangles_py;
|
68
|
+
for (const auto &tri : shape.triangles)
|
69
|
+
{
|
70
|
+
triangles_py.append(py::make_tuple(std::get<0>(tri), std::get<1>(tri), std::get<2>(tri)));
|
71
|
+
}
|
72
|
+
py::tuple color_py = py::make_tuple(std::get<0>(shape.color), std::get<1>(shape.color), std::get<2>(shape.color));
|
73
|
+
return py::make_tuple(triangles_py, color_py, shape.color_id);
|
74
|
+
}
|
75
|
+
|
76
|
+
// Helper to convert Python tuple (or None) to C++ optional<ShapeCpp>
|
77
|
+
std::optional<tg::ShapeCpp> python_to_cpp_shape(const py::object &shape_py)
|
78
|
+
{
|
79
|
+
if (shape_py.is_none())
|
80
|
+
{
|
81
|
+
return std::nullopt;
|
82
|
+
}
|
83
|
+
try
|
84
|
+
{
|
85
|
+
py::tuple shape_tuple = shape_py.cast<py::tuple>();
|
86
|
+
if (shape_tuple.size() != 3)
|
87
|
+
{
|
88
|
+
throw std::runtime_error("Shape tuple must have 3 elements (triangles, color, id).");
|
89
|
+
}
|
90
|
+
|
91
|
+
py::list tris_py = shape_tuple[0].cast<py::list>();
|
92
|
+
py::tuple color_py = shape_tuple[1].cast<py::tuple>();
|
93
|
+
int id_py = shape_tuple[2].cast<int>();
|
94
|
+
|
95
|
+
std::vector<tg::TriangleData> tris_cpp;
|
96
|
+
tris_cpp.reserve(tris_py.size());
|
97
|
+
for (const auto &tri_item : tris_py)
|
98
|
+
{
|
99
|
+
py::tuple tri_tuple = tri_item.cast<py::tuple>();
|
100
|
+
if (tri_tuple.size() != 3)
|
101
|
+
{
|
102
|
+
throw std::runtime_error("Triangle tuple must have 3 elements (dr, dc, is_up).");
|
103
|
+
}
|
104
|
+
tris_cpp.emplace_back(tri_tuple[0].cast<int>(), tri_tuple[1].cast<int>(), tri_tuple[2].cast<bool>());
|
105
|
+
}
|
106
|
+
|
107
|
+
if (color_py.size() != 3)
|
108
|
+
{
|
109
|
+
throw std::runtime_error("Color tuple must have 3 elements (r, g, b).");
|
110
|
+
}
|
111
|
+
tg::ColorCpp color_cpp = {color_py[0].cast<int>(), color_py[1].cast<int>(), color_py[2].cast<int>()};
|
112
|
+
|
113
|
+
return tg::ShapeCpp(std::move(tris_cpp), color_cpp, id_py);
|
114
|
+
}
|
115
|
+
catch (const py::error_already_set &e)
|
116
|
+
{
|
117
|
+
throw std::runtime_error(std::string("Error converting Python shape to C++: ") + e.what());
|
118
|
+
}
|
119
|
+
catch (const std::exception &e)
|
120
|
+
{
|
121
|
+
throw std::runtime_error(std::string("Error converting Python shape to C++: ") + e.what());
|
122
|
+
}
|
123
|
+
}
|
124
|
+
|
125
|
+
PYBIND11_MODULE(trianglengin_cpp, m)
|
126
|
+
{
|
127
|
+
m.doc() = "C++ core module for Trianglengin";
|
128
|
+
|
129
|
+
py::class_<tg::GameStateCpp>(m, "GameStateCpp")
|
130
|
+
.def(py::init([](const py::object &py_config, unsigned int seed)
|
131
|
+
{
|
132
|
+
tg::EnvConfigCpp cpp_config = python_to_cpp_env_config(py_config);
|
133
|
+
return std::make_unique<tg::GameStateCpp>(cpp_config, seed); }),
|
134
|
+
py::arg("config"), py::arg("initial_seed"))
|
135
|
+
.def("reset", &tg::GameStateCpp::reset)
|
136
|
+
.def("step", &tg::GameStateCpp::step, py::arg("action"))
|
137
|
+
.def("is_over", &tg::GameStateCpp::is_over)
|
138
|
+
.def("get_score", &tg::GameStateCpp::get_score)
|
139
|
+
.def("get_valid_actions", &tg::GameStateCpp::get_valid_actions, py::arg("force_recalculate") = false, py::return_value_policy::reference_internal)
|
140
|
+
.def("get_current_step", &tg::GameStateCpp::get_current_step)
|
141
|
+
.def("get_game_over_reason", &tg::GameStateCpp::get_game_over_reason)
|
142
|
+
.def("get_shapes_cpp", [](const tg::GameStateCpp &gs)
|
143
|
+
{
|
144
|
+
py::list shapes_list;
|
145
|
+
for(const auto& shape_opt : gs.get_shapes()) {
|
146
|
+
shapes_list.append(cpp_shape_to_python(shape_opt));
|
147
|
+
}
|
148
|
+
return shapes_list; })
|
149
|
+
.def("get_grid_occupied_flat", [](const tg::GameStateCpp &gs)
|
150
|
+
{
|
151
|
+
const auto& grid = gs.get_grid_data().get_occupied_grid();
|
152
|
+
size_t rows = grid.size();
|
153
|
+
size_t cols = (rows > 0) ? grid[0].size() : 0;
|
154
|
+
py::array_t<bool> result({rows, cols});
|
155
|
+
auto buf = result.request();
|
156
|
+
bool *ptr = static_cast<bool *>(buf.ptr);
|
157
|
+
// Manual copy for std::vector<bool>
|
158
|
+
for (size_t r = 0; r < rows; ++r) {
|
159
|
+
for (size_t c = 0; c < cols; ++c) {
|
160
|
+
ptr[r * cols + c] = grid[r][c];
|
161
|
+
}
|
162
|
+
}
|
163
|
+
return result; })
|
164
|
+
.def("get_grid_colors_flat", [](const tg::GameStateCpp &gs)
|
165
|
+
{
|
166
|
+
const auto& grid = gs.get_grid_data().get_color_id_grid();
|
167
|
+
size_t rows = grid.size();
|
168
|
+
size_t cols = (rows > 0) ? grid[0].size() : 0;
|
169
|
+
py::array_t<int8_t> result({rows, cols});
|
170
|
+
auto buf = result.request();
|
171
|
+
int8_t *ptr = static_cast<int8_t *>(buf.ptr);
|
172
|
+
// memcpy is fine for int8_t
|
173
|
+
for (size_t r = 0; r < rows; ++r) {
|
174
|
+
std::memcpy(ptr + r * cols, grid[r].data(), cols * sizeof(int8_t));
|
175
|
+
}
|
176
|
+
return result; })
|
177
|
+
.def("get_grid_death_flat", [](const tg::GameStateCpp &gs)
|
178
|
+
{
|
179
|
+
const auto& grid = gs.get_grid_data().get_death_grid();
|
180
|
+
size_t rows = grid.size();
|
181
|
+
size_t cols = (rows > 0) ? grid[0].size() : 0;
|
182
|
+
py::array_t<bool> result({rows, cols});
|
183
|
+
auto buf = result.request();
|
184
|
+
bool *ptr = static_cast<bool *>(buf.ptr);
|
185
|
+
// Manual copy for std::vector<bool>
|
186
|
+
for (size_t r = 0; r < rows; ++r) {
|
187
|
+
for (size_t c = 0; c < cols; ++c) {
|
188
|
+
ptr[r * cols + c] = grid[r][c];
|
189
|
+
}
|
190
|
+
}
|
191
|
+
return result; })
|
192
|
+
.def("copy", &tg::GameStateCpp::copy)
|
193
|
+
.def("debug_toggle_cell", &tg::GameStateCpp::debug_toggle_cell, py::arg("r"), py::arg("c"))
|
194
|
+
// Add binding for debug_set_shapes
|
195
|
+
.def("debug_set_shapes", [](tg::GameStateCpp &gs, const py::list &shapes_py)
|
196
|
+
{
|
197
|
+
std::vector<std::optional<tg::ShapeCpp>> shapes_cpp;
|
198
|
+
shapes_cpp.reserve(shapes_py.size());
|
199
|
+
for(const auto& shape_item_handle : shapes_py) {
|
200
|
+
// Cast handle to object before passing to conversion function
|
201
|
+
py::object shape_item = py::reinterpret_borrow<py::object>(shape_item_handle);
|
202
|
+
shapes_cpp.push_back(python_to_cpp_shape(shape_item));
|
203
|
+
}
|
204
|
+
gs.debug_set_shapes(shapes_cpp); }, py::arg("new_shapes"), "Sets the shapes in the preview slots directly (for debugging/testing).");
|
205
|
+
|
206
|
+
#ifdef VERSION_INFO
|
207
|
+
m.attr("__version__") = VERSION_INFO;
|
208
|
+
#else
|
209
|
+
m.attr("__version__") = "dev";
|
210
|
+
#endif
|
211
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
// File: src/trianglengin/cpp/config.h
|
2
|
+
#ifndef TRIANGLENGIN_CPP_CONFIG_H
|
3
|
+
#define TRIANGLENGIN_CPP_CONFIG_H
|
4
|
+
|
5
|
+
#pragma once
|
6
|
+
|
7
|
+
#include <vector>
|
8
|
+
#include <tuple>
|
9
|
+
#include <cstdint>
|
10
|
+
|
11
|
+
namespace trianglengin::cpp
|
12
|
+
{
|
13
|
+
struct EnvConfigCpp
|
14
|
+
{
|
15
|
+
int rows = 8;
|
16
|
+
int cols = 15;
|
17
|
+
std::vector<std::tuple<int, int>> playable_range_per_row = {
|
18
|
+
{3, 12}, {2, 13}, {1, 14}, {0, 15}, {0, 15}, {1, 14}, {2, 13}, {3, 12}};
|
19
|
+
int num_shape_slots = 3;
|
20
|
+
double reward_per_placed_triangle = 0.01;
|
21
|
+
double reward_per_cleared_triangle = 0.5;
|
22
|
+
double reward_per_step_alive = 0.005;
|
23
|
+
double penalty_game_over = -10.0;
|
24
|
+
int action_dim = 0;
|
25
|
+
};
|
26
|
+
} // namespace trianglengin::cpp
|
27
|
+
|
28
|
+
#endif // TRIANGLENGIN_CPP_CONFIG_H
|