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.
Files changed (72) hide show
  1. tests/__init__.py +0 -0
  2. tests/conftest.py +108 -0
  3. tests/core/__init__.py +2 -0
  4. tests/core/environment/README.md +47 -0
  5. tests/core/environment/__init__.py +2 -0
  6. tests/core/environment/test_action_codec.py +50 -0
  7. tests/core/environment/test_game_state.py +483 -0
  8. tests/core/environment/test_grid_data.py +205 -0
  9. tests/core/environment/test_grid_logic.py +362 -0
  10. tests/core/environment/test_shape_logic.py +171 -0
  11. tests/core/environment/test_step.py +372 -0
  12. tests/core/structs/__init__.py +0 -0
  13. tests/core/structs/test_shape.py +83 -0
  14. tests/core/structs/test_triangle.py +97 -0
  15. tests/utils/__init__.py +0 -0
  16. tests/utils/test_geometry.py +93 -0
  17. trianglengin/__init__.py +18 -0
  18. trianglengin/app.py +110 -0
  19. trianglengin/cli.py +134 -0
  20. trianglengin/config/__init__.py +9 -0
  21. trianglengin/config/display_config.py +47 -0
  22. trianglengin/config/env_config.py +103 -0
  23. trianglengin/core/__init__.py +8 -0
  24. trianglengin/core/environment/__init__.py +31 -0
  25. trianglengin/core/environment/action_codec.py +37 -0
  26. trianglengin/core/environment/game_state.py +217 -0
  27. trianglengin/core/environment/grid/README.md +46 -0
  28. trianglengin/core/environment/grid/__init__.py +18 -0
  29. trianglengin/core/environment/grid/grid_data.py +140 -0
  30. trianglengin/core/environment/grid/line_cache.py +189 -0
  31. trianglengin/core/environment/grid/logic.py +131 -0
  32. trianglengin/core/environment/logic/__init__.py +3 -0
  33. trianglengin/core/environment/logic/actions.py +38 -0
  34. trianglengin/core/environment/logic/step.py +134 -0
  35. trianglengin/core/environment/shapes/__init__.py +19 -0
  36. trianglengin/core/environment/shapes/logic.py +84 -0
  37. trianglengin/core/environment/shapes/templates.py +587 -0
  38. trianglengin/core/structs/__init__.py +27 -0
  39. trianglengin/core/structs/constants.py +28 -0
  40. trianglengin/core/structs/shape.py +61 -0
  41. trianglengin/core/structs/triangle.py +48 -0
  42. trianglengin/interaction/README.md +45 -0
  43. trianglengin/interaction/__init__.py +17 -0
  44. trianglengin/interaction/debug_mode_handler.py +96 -0
  45. trianglengin/interaction/event_processor.py +43 -0
  46. trianglengin/interaction/input_handler.py +82 -0
  47. trianglengin/interaction/play_mode_handler.py +141 -0
  48. trianglengin/utils/__init__.py +9 -0
  49. trianglengin/utils/geometry.py +73 -0
  50. trianglengin/utils/types.py +10 -0
  51. trianglengin/visualization/README.md +44 -0
  52. trianglengin/visualization/__init__.py +61 -0
  53. trianglengin/visualization/core/README.md +52 -0
  54. trianglengin/visualization/core/__init__.py +12 -0
  55. trianglengin/visualization/core/colors.py +117 -0
  56. trianglengin/visualization/core/coord_mapper.py +73 -0
  57. trianglengin/visualization/core/fonts.py +55 -0
  58. trianglengin/visualization/core/layout.py +101 -0
  59. trianglengin/visualization/core/visualizer.py +232 -0
  60. trianglengin/visualization/drawing/README.md +45 -0
  61. trianglengin/visualization/drawing/__init__.py +30 -0
  62. trianglengin/visualization/drawing/grid.py +156 -0
  63. trianglengin/visualization/drawing/highlight.py +30 -0
  64. trianglengin/visualization/drawing/hud.py +39 -0
  65. trianglengin/visualization/drawing/previews.py +172 -0
  66. trianglengin/visualization/drawing/shapes.py +36 -0
  67. trianglengin-1.0.6.dist-info/METADATA +367 -0
  68. trianglengin-1.0.6.dist-info/RECORD +72 -0
  69. trianglengin-1.0.6.dist-info/WHEEL +5 -0
  70. trianglengin-1.0.6.dist-info/entry_points.txt +2 -0
  71. trianglengin-1.0.6.dist-info/licenses/LICENSE +22 -0
  72. 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,2 @@
1
+ # File: trianglengin/tests/core/__init__.py
2
+ # This file can be empty
@@ -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,2 @@
1
+ # File: trianglengin/tests/core/environment/__init__.py
2
+ # This file can be empty
@@ -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)