trianglengin 1.0.6__py3-none-any.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.
- tests/__init__.py +0 -0
- tests/conftest.py +108 -0
- tests/core/__init__.py +2 -0
- tests/core/environment/README.md +47 -0
- tests/core/environment/__init__.py +2 -0
- tests/core/environment/test_action_codec.py +50 -0
- tests/core/environment/test_game_state.py +483 -0
- tests/core/environment/test_grid_data.py +205 -0
- tests/core/environment/test_grid_logic.py +362 -0
- tests/core/environment/test_shape_logic.py +171 -0
- tests/core/environment/test_step.py +372 -0
- tests/core/structs/__init__.py +0 -0
- tests/core/structs/test_shape.py +83 -0
- tests/core/structs/test_triangle.py +97 -0
- tests/utils/__init__.py +0 -0
- tests/utils/test_geometry.py +93 -0
- trianglengin/__init__.py +18 -0
- trianglengin/app.py +110 -0
- trianglengin/cli.py +134 -0
- trianglengin/config/__init__.py +9 -0
- trianglengin/config/display_config.py +47 -0
- trianglengin/config/env_config.py +103 -0
- trianglengin/core/__init__.py +8 -0
- trianglengin/core/environment/__init__.py +31 -0
- trianglengin/core/environment/action_codec.py +37 -0
- trianglengin/core/environment/game_state.py +217 -0
- trianglengin/core/environment/grid/README.md +46 -0
- trianglengin/core/environment/grid/__init__.py +18 -0
- trianglengin/core/environment/grid/grid_data.py +140 -0
- trianglengin/core/environment/grid/line_cache.py +189 -0
- trianglengin/core/environment/grid/logic.py +131 -0
- trianglengin/core/environment/logic/__init__.py +3 -0
- trianglengin/core/environment/logic/actions.py +38 -0
- trianglengin/core/environment/logic/step.py +134 -0
- trianglengin/core/environment/shapes/__init__.py +19 -0
- trianglengin/core/environment/shapes/logic.py +84 -0
- trianglengin/core/environment/shapes/templates.py +587 -0
- trianglengin/core/structs/__init__.py +27 -0
- trianglengin/core/structs/constants.py +28 -0
- trianglengin/core/structs/shape.py +61 -0
- trianglengin/core/structs/triangle.py +48 -0
- trianglengin/interaction/README.md +45 -0
- trianglengin/interaction/__init__.py +17 -0
- trianglengin/interaction/debug_mode_handler.py +96 -0
- trianglengin/interaction/event_processor.py +43 -0
- trianglengin/interaction/input_handler.py +82 -0
- trianglengin/interaction/play_mode_handler.py +141 -0
- trianglengin/utils/__init__.py +9 -0
- trianglengin/utils/geometry.py +73 -0
- trianglengin/utils/types.py +10 -0
- trianglengin/visualization/README.md +44 -0
- trianglengin/visualization/__init__.py +61 -0
- trianglengin/visualization/core/README.md +52 -0
- trianglengin/visualization/core/__init__.py +12 -0
- trianglengin/visualization/core/colors.py +117 -0
- trianglengin/visualization/core/coord_mapper.py +73 -0
- trianglengin/visualization/core/fonts.py +55 -0
- trianglengin/visualization/core/layout.py +101 -0
- trianglengin/visualization/core/visualizer.py +232 -0
- trianglengin/visualization/drawing/README.md +45 -0
- trianglengin/visualization/drawing/__init__.py +30 -0
- trianglengin/visualization/drawing/grid.py +156 -0
- trianglengin/visualization/drawing/highlight.py +30 -0
- trianglengin/visualization/drawing/hud.py +39 -0
- trianglengin/visualization/drawing/previews.py +172 -0
- trianglengin/visualization/drawing/shapes.py +36 -0
- trianglengin-1.0.6.dist-info/METADATA +367 -0
- trianglengin-1.0.6.dist-info/RECORD +72 -0
- trianglengin-1.0.6.dist-info/WHEEL +5 -0
- trianglengin-1.0.6.dist-info/entry_points.txt +2 -0
- trianglengin-1.0.6.dist-info/licenses/LICENSE +22 -0
- trianglengin-1.0.6.dist-info/top_level.txt +2 -0
tests/__init__.py
ADDED
File without changes
|
tests/conftest.py
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
# File: trianglengin/tests/conftest.py
|
2
|
+
import random
|
3
|
+
from typing import TYPE_CHECKING
|
4
|
+
|
5
|
+
import numpy as np
|
6
|
+
import pytest
|
7
|
+
|
8
|
+
# Import directly from the library being tested
|
9
|
+
from trianglengin.config import EnvConfig
|
10
|
+
from trianglengin.core.environment import GameState
|
11
|
+
from trianglengin.core.structs import Shape
|
12
|
+
from trianglengin.visualization.core.colors import Color
|
13
|
+
|
14
|
+
if TYPE_CHECKING:
|
15
|
+
from trianglengin.core.environment.grid import GridData
|
16
|
+
|
17
|
+
|
18
|
+
# Use default NumPy random number generator
|
19
|
+
rng = np.random.default_rng()
|
20
|
+
|
21
|
+
# Default color for fixture shapes
|
22
|
+
DEFAULT_TEST_COLOR: Color = (100, 100, 100)
|
23
|
+
|
24
|
+
|
25
|
+
@pytest.fixture(scope="session")
|
26
|
+
def default_env_config() -> EnvConfig:
|
27
|
+
"""Provides the default EnvConfig used in the specification (session-scoped)."""
|
28
|
+
return EnvConfig()
|
29
|
+
|
30
|
+
|
31
|
+
@pytest.fixture
|
32
|
+
def game_state(default_env_config: EnvConfig) -> GameState:
|
33
|
+
"""Provides a fresh GameState instance for testing."""
|
34
|
+
return GameState(config=default_env_config, initial_seed=123)
|
35
|
+
|
36
|
+
|
37
|
+
@pytest.fixture
|
38
|
+
def game_state_with_fixed_shapes() -> GameState:
|
39
|
+
"""
|
40
|
+
Provides a game state with predictable initial shapes on a 3x3 grid.
|
41
|
+
"""
|
42
|
+
# Create a specific 3x3 config for this fixture
|
43
|
+
config_3x3 = EnvConfig(
|
44
|
+
ROWS=3,
|
45
|
+
COLS=3,
|
46
|
+
PLAYABLE_RANGE_PER_ROW=[(0, 3), (0, 3), (0, 3)], # Full 3x3 is playable
|
47
|
+
NUM_SHAPE_SLOTS=3,
|
48
|
+
)
|
49
|
+
gs = GameState(config=config_3x3, initial_seed=456)
|
50
|
+
|
51
|
+
# Override the random shapes with fixed ones for testing placement/refill
|
52
|
+
fixed_shapes = [
|
53
|
+
Shape([(0, 0, False)], DEFAULT_TEST_COLOR), # Single down
|
54
|
+
Shape([(0, 0, True)], DEFAULT_TEST_COLOR), # Single up
|
55
|
+
Shape(
|
56
|
+
[(0, 0, False), (1, 0, False)], DEFAULT_TEST_COLOR
|
57
|
+
), # Two downs (vertical)
|
58
|
+
]
|
59
|
+
assert len(fixed_shapes) == gs.env_config.NUM_SHAPE_SLOTS
|
60
|
+
|
61
|
+
# Assign copies of the shapes to the game state
|
62
|
+
gs.shapes = [shape.copy() for shape in fixed_shapes]
|
63
|
+
|
64
|
+
# Force recalculation of valid actions after manually setting shapes
|
65
|
+
gs.valid_actions(force_recalculate=True)
|
66
|
+
return gs
|
67
|
+
|
68
|
+
|
69
|
+
@pytest.fixture
|
70
|
+
def simple_shape() -> Shape:
|
71
|
+
"""Provides a simple 3-triangle connected shape (Down, Up, Down)."""
|
72
|
+
triangles = [(0, 0, False), (0, 1, True), (1, 1, False)]
|
73
|
+
color = (255, 0, 0)
|
74
|
+
return Shape(triangles, color)
|
75
|
+
|
76
|
+
|
77
|
+
@pytest.fixture
|
78
|
+
def grid_data(default_env_config: EnvConfig) -> "GridData":
|
79
|
+
"""Provides a fresh GridData instance using the default config."""
|
80
|
+
from trianglengin.core.environment.grid import GridData
|
81
|
+
|
82
|
+
return GridData(config=default_env_config)
|
83
|
+
|
84
|
+
|
85
|
+
@pytest.fixture
|
86
|
+
def game_state_almost_full(default_env_config: EnvConfig) -> GameState:
|
87
|
+
"""
|
88
|
+
Provides a game state (default config) where only a few placements are possible.
|
89
|
+
Grid is filled completely, then specific spots are made empty.
|
90
|
+
"""
|
91
|
+
gs = GameState(config=default_env_config, initial_seed=987)
|
92
|
+
playable_mask = ~gs.grid_data._death_np
|
93
|
+
gs.grid_data._occupied_np[playable_mask] = True
|
94
|
+
empty_spots = [(0, 4), (0, 5)] # Example empty spots
|
95
|
+
for r_empty, c_empty in empty_spots:
|
96
|
+
if (
|
97
|
+
gs.grid_data.valid(r_empty, c_empty)
|
98
|
+
and not gs.grid_data._death_np[r_empty, c_empty]
|
99
|
+
):
|
100
|
+
gs.grid_data._occupied_np[r_empty, c_empty] = False
|
101
|
+
gs.grid_data._color_id_np[r_empty, c_empty] = -1
|
102
|
+
return gs
|
103
|
+
|
104
|
+
|
105
|
+
@pytest.fixture
|
106
|
+
def fixed_rng() -> random.Random:
|
107
|
+
"""Provides a Random instance with a fixed seed."""
|
108
|
+
return random.Random(12345)
|
tests/core/__init__.py
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# File: trianglengin/trianglengin/core/environment/grid/README.md
|
2
|
+
# Environment Grid Submodule (`trianglengin.environment.grid`)
|
3
|
+
|
4
|
+
## Purpose and Architecture
|
5
|
+
|
6
|
+
This submodule manages the game's grid structure and related logic. It defines the triangular cells, their properties, relationships, and operations like placement validation and line clearing.
|
7
|
+
|
8
|
+
- **Cell Representation:** The `Triangle` class (defined in [`trianglengin.core.structs`](../../structs/README.md)) represents a single cell, storing its position and orientation (`is_up`). The actual state (occupied, death, color) is managed within `GridData`.
|
9
|
+
- **Grid Data Structure:** The [`GridData`](grid_data.py) class holds the grid state using efficient `numpy` arrays (`_occupied_np`, `_death_np`, `_color_id_np`). It also manages precomputed information about potential lines (sets of coordinates) for efficient clearing checks.
|
10
|
+
- **Grid Logic:** The [`logic.py`](logic.py) module (exposed as `GridLogic`) contains functions operating on `GridData`. This includes:
|
11
|
+
- Initializing the grid based on `EnvConfig` (defining death zones).
|
12
|
+
- Precomputing potential lines (`_precompute_lines`) and indexing them (`_initialize_lines_and_index`) for efficient checking.
|
13
|
+
- Checking if a shape can be placed (`can_place`), **including matching triangle orientations**.
|
14
|
+
- Checking for and clearing completed lines (`check_and_clear_lines`). **This function does NOT implement gravity.**
|
15
|
+
- **Grid Features:** Note: Any functions related to calculating scalar metrics (heights, holes, bumpiness) are expected to be handled outside this core engine library, likely in the main `alphatriangle` project's features module.
|
16
|
+
|
17
|
+
## Exposed Interfaces
|
18
|
+
|
19
|
+
- **Classes:**
|
20
|
+
- `GridData`: Holds the grid state using NumPy arrays.
|
21
|
+
- `__init__(config: EnvConfig)`
|
22
|
+
- `valid(r: int, c: int) -> bool`
|
23
|
+
- `is_death(r: int, c: int) -> bool`
|
24
|
+
- `is_occupied(r: int, c: int) -> bool`
|
25
|
+
- `get_color_id(r: int, c: int) -> int`
|
26
|
+
- `get_occupied_state() -> np.ndarray`
|
27
|
+
- `get_death_state() -> np.ndarray`
|
28
|
+
- `get_color_id_state() -> np.ndarray`
|
29
|
+
- `deepcopy() -> GridData`
|
30
|
+
- **Modules/Namespaces:**
|
31
|
+
- `logic` (often imported as `GridLogic`):
|
32
|
+
- `can_place(grid_data: GridData, shape: Shape, r: int, c: int) -> bool`
|
33
|
+
- `check_and_clear_lines(grid_data: GridData, newly_occupied_coords: Set[Tuple[int, int]]) -> Tuple[int, Set[Tuple[int, int]], Set[frozenset[Tuple[int, int]]]]` **(Returns: lines_cleared_count, unique_coords_cleared_set, set_of_cleared_lines_coord_sets)**
|
34
|
+
|
35
|
+
## Dependencies
|
36
|
+
|
37
|
+
- **[`trianglengin.config`](../../../config/README.md)**:
|
38
|
+
- `EnvConfig`: Used by `GridData` initialization and logic functions.
|
39
|
+
- **[`trianglengin.core.structs`](../../structs/README.md)**:
|
40
|
+
- Uses `Triangle`, `Shape`, `NO_COLOR_ID`.
|
41
|
+
- **`numpy`**:
|
42
|
+
- Used extensively in `GridData`.
|
43
|
+
- **Standard Libraries:** `typing`, `logging`, `numpy`, `copy`.
|
44
|
+
|
45
|
+
---
|
46
|
+
|
47
|
+
**Note:** Please keep this README updated when changing the grid structure, cell properties, placement rules, or line clearing logic. Accurate documentation is crucial for maintainability.
|
@@ -0,0 +1,50 @@
|
|
1
|
+
import pytest
|
2
|
+
|
3
|
+
# Import directly from the library being tested
|
4
|
+
from trianglengin.config import EnvConfig
|
5
|
+
from trianglengin.core.environment.action_codec import decode_action, encode_action
|
6
|
+
|
7
|
+
# Use fixtures from the local conftest.py
|
8
|
+
# Fixtures are implicitly injected by pytest
|
9
|
+
|
10
|
+
|
11
|
+
def test_encode_decode_action(default_env_config: EnvConfig):
|
12
|
+
"""Test encoding and decoding actions."""
|
13
|
+
config = default_env_config
|
14
|
+
# Test some valid actions
|
15
|
+
actions_to_test = [
|
16
|
+
(0, 0, 0),
|
17
|
+
(config.NUM_SHAPE_SLOTS - 1, config.ROWS - 1, config.COLS - 1),
|
18
|
+
(0, config.ROWS // 2, config.COLS // 2),
|
19
|
+
]
|
20
|
+
for shape_idx, r, c in actions_to_test:
|
21
|
+
encoded = encode_action(shape_idx, r, c, config)
|
22
|
+
decoded_shape_idx, decoded_r, decoded_c = decode_action(encoded, config)
|
23
|
+
assert (shape_idx, r, c) == (decoded_shape_idx, decoded_r, decoded_c)
|
24
|
+
|
25
|
+
|
26
|
+
def test_encode_action_invalid_input(default_env_config: EnvConfig):
|
27
|
+
"""Test encoding with invalid inputs."""
|
28
|
+
config = default_env_config
|
29
|
+
with pytest.raises(ValueError):
|
30
|
+
encode_action(-1, 0, 0, config) # Invalid shape index
|
31
|
+
with pytest.raises(ValueError):
|
32
|
+
encode_action(config.NUM_SHAPE_SLOTS, 0, 0, config) # Invalid shape index
|
33
|
+
with pytest.raises(ValueError):
|
34
|
+
encode_action(0, -1, 0, config) # Invalid row
|
35
|
+
with pytest.raises(ValueError):
|
36
|
+
encode_action(0, config.ROWS, 0, config) # Invalid row
|
37
|
+
with pytest.raises(ValueError):
|
38
|
+
encode_action(0, 0, -1, config) # Invalid col
|
39
|
+
with pytest.raises(ValueError):
|
40
|
+
encode_action(0, 0, config.COLS, config) # Invalid col
|
41
|
+
|
42
|
+
|
43
|
+
def test_decode_action_invalid_input(default_env_config: EnvConfig):
|
44
|
+
"""Test decoding with invalid inputs."""
|
45
|
+
config = default_env_config
|
46
|
+
action_dim = int(config.ACTION_DIM) # type: ignore[call-overload]
|
47
|
+
with pytest.raises(ValueError):
|
48
|
+
decode_action(-1, config) # Invalid action index
|
49
|
+
with pytest.raises(ValueError):
|
50
|
+
decode_action(action_dim, config) # Invalid action index (out of bounds)
|