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
@@ -0,0 +1,30 @@
1
+ # trianglengin/visualization/drawing/__init__.py
2
+ """Drawing functions for specific visual elements."""
3
+
4
+ from .grid import (
5
+ draw_debug_grid_overlay, # Keep if used elsewhere
6
+ draw_grid_background,
7
+ # draw_grid_indices, # Removed - integrated into background
8
+ draw_grid_state, # Renamed from draw_grid_triangles
9
+ )
10
+ from .highlight import draw_debug_highlight
11
+ from .hud import render_hud
12
+ from .previews import (
13
+ draw_floating_preview,
14
+ draw_placement_preview,
15
+ render_previews,
16
+ )
17
+ from .shapes import draw_shape
18
+
19
+ __all__ = [
20
+ "draw_grid_background",
21
+ "draw_grid_state", # Export renamed function
22
+ "draw_debug_grid_overlay",
23
+ # "draw_grid_indices", # Removed
24
+ "draw_shape",
25
+ "render_previews",
26
+ "draw_placement_preview",
27
+ "draw_floating_preview",
28
+ "render_hud",
29
+ "draw_debug_highlight",
30
+ ]
@@ -0,0 +1,156 @@
1
+ # File: trianglengin/visualization/drawing/grid.py
2
+ import logging
3
+ from typing import TYPE_CHECKING
4
+
5
+ import pygame
6
+
7
+ from trianglengin.core.structs.triangle import Triangle
8
+ from trianglengin.visualization.core import colors
9
+
10
+ if TYPE_CHECKING:
11
+ from trianglengin.config import DisplayConfig, EnvConfig
12
+ from trianglengin.core.environment.grid.grid_data import GridData
13
+
14
+
15
+ log = logging.getLogger(__name__)
16
+
17
+
18
+ def draw_grid_background(
19
+ surface: pygame.Surface,
20
+ env_config: "EnvConfig",
21
+ display_config: "DisplayConfig",
22
+ cw: float,
23
+ ch: float,
24
+ ox: float,
25
+ oy: float,
26
+ game_over: bool = False,
27
+ debug_mode: bool = False,
28
+ ) -> None:
29
+ """Draws the background grid structure using pre-calculated render parameters."""
30
+ bg_color = colors.GRID_BG_GAME_OVER if game_over else colors.GRID_BG_DEFAULT
31
+ surface.fill(bg_color)
32
+
33
+ if cw <= 0 or ch <= 0:
34
+ log.warning("Cannot draw grid background with zero cell dimensions.")
35
+ return
36
+
37
+ for r in range(env_config.ROWS):
38
+ # Use PLAYABLE_RANGE_PER_ROW to determine death zones
39
+ start_col, end_col = env_config.PLAYABLE_RANGE_PER_ROW[r]
40
+ for c in range(env_config.COLS):
41
+ is_up = (r + c) % 2 != 0
42
+ is_death = not (start_col <= c < end_col) # Check if outside playable range
43
+ tri = Triangle(r, c, is_up, is_death)
44
+
45
+ if is_death:
46
+ cell_color = colors.DEATH_ZONE_COLOR
47
+ else:
48
+ cell_color = (
49
+ colors.GRID_BG_LIGHT if (r % 2 == c % 2) else colors.GRID_BG_DARK
50
+ )
51
+
52
+ points = tri.get_points(ox, oy, cw, ch)
53
+ pygame.draw.polygon(surface, cell_color, points)
54
+ pygame.draw.polygon(surface, colors.GRID_LINE_COLOR, points, 1)
55
+
56
+ if debug_mode:
57
+ font = display_config.DEBUG_FONT
58
+ text = f"{r},{c}"
59
+ text_surf = font.render(text, True, colors.DEBUG_TOGGLE_COLOR)
60
+ center_x = sum(p[0] for p in points) / 3
61
+ center_y = sum(p[1] for p in points) / 3
62
+ text_rect = text_surf.get_rect(center=(center_x, center_y))
63
+ surface.blit(text_surf, text_rect)
64
+
65
+
66
+ def draw_grid_state(
67
+ surface: pygame.Surface,
68
+ grid_data: "GridData",
69
+ cw: float,
70
+ ch: float,
71
+ ox: float,
72
+ oy: float,
73
+ ) -> None:
74
+ """Draws the occupied triangles with their colors using pre-calculated parameters."""
75
+ rows, cols = grid_data.rows, grid_data.cols
76
+ occupied_np = grid_data._occupied_np
77
+ color_id_np = grid_data._color_id_np
78
+ death_np = grid_data._death_np
79
+
80
+ if cw <= 0 or ch <= 0:
81
+ log.warning("Cannot draw grid state with zero cell dimensions.")
82
+ return
83
+
84
+ for r in range(rows):
85
+ for c in range(cols):
86
+ if death_np[r, c]:
87
+ continue
88
+
89
+ if occupied_np[r, c]:
90
+ color_id = int(color_id_np[r, c])
91
+ color = colors.ID_TO_COLOR_MAP.get(color_id)
92
+
93
+ is_up = (r + c) % 2 != 0
94
+ tri = Triangle(r, c, is_up, False)
95
+ points = tri.get_points(ox, oy, cw, ch)
96
+
97
+ if color is not None:
98
+ pygame.draw.polygon(surface, color, points)
99
+ elif color_id == colors.DEBUG_COLOR_ID:
100
+ pygame.draw.polygon(surface, colors.DEBUG_TOGGLE_COLOR, points)
101
+ else:
102
+ log.warning(
103
+ f"Occupied cell ({r},{c}) has invalid color ID: {color_id}"
104
+ )
105
+ pygame.draw.polygon(surface, colors.TRIANGLE_EMPTY_COLOR, points)
106
+
107
+
108
+ def draw_debug_grid_overlay(
109
+ surface: pygame.Surface,
110
+ grid_data: "GridData",
111
+ display_config: "DisplayConfig",
112
+ cw: float,
113
+ ch: float,
114
+ ox: float,
115
+ oy: float,
116
+ ) -> None:
117
+ """Draws debug information using pre-calculated render parameters."""
118
+ font = display_config.DEBUG_FONT
119
+ rows, cols = grid_data.rows, grid_data.cols
120
+
121
+ if cw <= 0 or ch <= 0:
122
+ log.warning("Cannot draw debug overlay with zero cell dimensions.")
123
+ return
124
+
125
+ for r in range(rows):
126
+ for c in range(cols):
127
+ is_up = (r + c) % 2 != 0
128
+ is_death = grid_data.is_death(r, c)
129
+ tri = Triangle(r, c, is_up, is_death)
130
+ points = tri.get_points(ox, oy, cw, ch)
131
+
132
+ text = f"{r},{c}"
133
+ text_surf = font.render(text, True, colors.DEBUG_TOGGLE_COLOR)
134
+ center_x = sum(p[0] for p in points) / 3
135
+ center_y = sum(p[1] for p in points) / 3
136
+ text_rect = text_surf.get_rect(center=(center_x, center_y))
137
+ surface.blit(text_surf, text_rect)
138
+
139
+
140
+ def draw_grid(
141
+ surface: pygame.Surface,
142
+ grid_data: "GridData",
143
+ env_config: "EnvConfig",
144
+ display_config: "DisplayConfig",
145
+ cw: float,
146
+ ch: float,
147
+ ox: float,
148
+ oy: float,
149
+ game_over: bool = False,
150
+ debug_mode: bool = False,
151
+ ) -> None:
152
+ """Main function to draw the entire grid including background and state."""
153
+ draw_grid_background(
154
+ surface, env_config, display_config, cw, ch, ox, oy, game_over, debug_mode
155
+ )
156
+ draw_grid_state(surface, grid_data, cw, ch, ox, oy)
@@ -0,0 +1,30 @@
1
+ # File: trianglengin/visualization/drawing/highlight.py
2
+ import pygame
3
+
4
+ # Use internal imports
5
+ from ...core.structs import Triangle
6
+ from ..core import colors
7
+
8
+
9
+ def draw_debug_highlight(
10
+ surface: pygame.Surface,
11
+ r: int,
12
+ c: int,
13
+ # config: EnvConfig, # Removed - use cw, ch, ox, oy
14
+ cw: float,
15
+ ch: float,
16
+ ox: float,
17
+ oy: float,
18
+ ) -> None:
19
+ """Highlights a specific triangle border for debugging using pre-calculated parameters."""
20
+ if surface.get_width() <= 0 or surface.get_height() <= 0:
21
+ return
22
+
23
+ if cw <= 0 or ch <= 0:
24
+ return
25
+
26
+ is_up = (r + c) % 2 != 0
27
+ temp_tri = Triangle(r, c, is_up)
28
+ pts = temp_tri.get_points(ox, oy, cw, ch)
29
+
30
+ pygame.draw.polygon(surface, colors.DEBUG_TOGGLE_COLOR, pts, 3)
@@ -0,0 +1,39 @@
1
+ # File: trianglengin/trianglengin/visualization/drawing/hud.py
2
+ from typing import Any
3
+
4
+ import pygame
5
+
6
+ # Use internal imports
7
+ from ..core import colors
8
+
9
+
10
+ def render_hud(
11
+ surface: pygame.Surface,
12
+ mode: str,
13
+ fonts: dict[str, pygame.font.Font | None],
14
+ _display_stats: dict[str, Any] | None = None, # Prefix with underscore
15
+ ) -> None:
16
+ """
17
+ Renders HUD elements for interactive modes (play/debug).
18
+ Displays only help text relevant to the mode.
19
+ Ignores _display_stats.
20
+ """
21
+ screen_w, screen_h = surface.get_size()
22
+ help_font = fonts.get("help")
23
+
24
+ if not help_font:
25
+ return
26
+
27
+ bottom_y = screen_h - 10 # Position from bottom
28
+
29
+ # --- Render Help Text Only ---
30
+ help_text = "[ESC] Quit"
31
+ if mode == "play":
32
+ help_text += " | [Click] Select/Place Shape"
33
+ elif mode == "debug":
34
+ help_text += " | [Click] Toggle Cell"
35
+
36
+ help_surf = help_font.render(help_text, True, colors.LIGHT_GRAY)
37
+ # Position help text at the bottom right
38
+ help_rect = help_surf.get_rect(bottomright=(screen_w - 15, bottom_y))
39
+ surface.blit(help_surf, help_rect)
@@ -0,0 +1,172 @@
1
+ # File: trianglengin/visualization/drawing/previews.py
2
+ import logging
3
+
4
+ import pygame
5
+
6
+ # Use internal imports
7
+ from trianglengin.config import DisplayConfig, EnvConfig
8
+ from trianglengin.core.environment import GameState
9
+ from trianglengin.core.structs import Shape, Triangle
10
+ from trianglengin.visualization.core import colors
11
+
12
+ from .shapes import draw_shape
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def render_previews(
18
+ surface: pygame.Surface,
19
+ game_state: GameState,
20
+ area_topleft: tuple[int, int],
21
+ _mode: str,
22
+ env_config: EnvConfig,
23
+ display_config: DisplayConfig,
24
+ selected_shape_idx: int = -1,
25
+ ) -> dict[int, pygame.Rect]:
26
+ """Renders shape previews in their area. Returns dict {index: screen_rect}."""
27
+ surface.fill(colors.PREVIEW_BG_COLOR)
28
+ preview_rects_screen: dict[int, pygame.Rect] = {}
29
+ num_slots = env_config.NUM_SHAPE_SLOTS
30
+ pad = display_config.PREVIEW_PADDING
31
+ inner_pad = display_config.PREVIEW_INNER_PADDING
32
+ border = display_config.PREVIEW_BORDER_WIDTH
33
+ selected_border = display_config.PREVIEW_SELECTED_BORDER_WIDTH
34
+
35
+ if num_slots <= 0:
36
+ return {}
37
+
38
+ total_pad_h = (num_slots + 1) * pad
39
+ available_h = surface.get_height() - total_pad_h
40
+ slot_h = available_h / num_slots if num_slots > 0 else 0
41
+ slot_w = surface.get_width() - 2 * pad
42
+
43
+ current_y = float(pad)
44
+
45
+ for i in range(num_slots):
46
+ slot_rect_local = pygame.Rect(pad, int(current_y), int(slot_w), int(slot_h))
47
+ slot_rect_screen = slot_rect_local.move(area_topleft)
48
+ preview_rects_screen[i] = slot_rect_screen
49
+
50
+ shape: Shape | None = game_state.shapes[i]
51
+ is_selected = selected_shape_idx == i
52
+
53
+ border_width = selected_border if is_selected else border
54
+ border_color = (
55
+ colors.PREVIEW_SELECTED_BORDER if is_selected else colors.PREVIEW_BORDER
56
+ )
57
+ pygame.draw.rect(surface, border_color, slot_rect_local, border_width)
58
+
59
+ if shape:
60
+ draw_area_w = slot_w - 2 * (border_width + inner_pad)
61
+ draw_area_h = slot_h - 2 * (border_width + inner_pad)
62
+
63
+ if draw_area_w > 0 and draw_area_h > 0:
64
+ min_r, min_c, max_r, max_c = shape.bbox()
65
+ shape_rows = max_r - min_r + 1
66
+ shape_cols_eff = (
67
+ (max_c - min_c + 1) * 0.75 + 0.25 if shape.triangles else 1
68
+ )
69
+
70
+ scale_w = (
71
+ draw_area_w / shape_cols_eff if shape_cols_eff > 0 else draw_area_w
72
+ )
73
+ scale_h = draw_area_h / shape_rows if shape_rows > 0 else draw_area_h
74
+ cell_size = max(1.0, min(scale_w, scale_h))
75
+
76
+ shape_render_w = shape_cols_eff * cell_size
77
+ shape_render_h = shape_rows * cell_size
78
+ draw_topleft_x = (
79
+ slot_rect_local.left
80
+ + border_width
81
+ + inner_pad
82
+ + (draw_area_w - shape_render_w) / 2
83
+ )
84
+ draw_topleft_y = (
85
+ slot_rect_local.top
86
+ + border_width
87
+ + inner_pad
88
+ + (draw_area_h - shape_render_h) / 2
89
+ )
90
+
91
+ draw_shape(
92
+ surface,
93
+ shape,
94
+ (int(draw_topleft_x), int(draw_topleft_y)),
95
+ cell_size,
96
+ _is_selected=is_selected,
97
+ origin_offset=(-min_r, -min_c),
98
+ )
99
+
100
+ current_y += slot_h + pad
101
+
102
+ return preview_rects_screen
103
+
104
+
105
+ def draw_placement_preview(
106
+ surface: pygame.Surface,
107
+ shape: Shape,
108
+ r: int,
109
+ c: int,
110
+ is_valid: bool,
111
+ cw: float,
112
+ ch: float,
113
+ ox: float,
114
+ oy: float,
115
+ ) -> None:
116
+ """Draws a semi-transparent shape snapped to the grid using pre-calculated parameters."""
117
+ if not shape or not shape.triangles:
118
+ return
119
+
120
+ if cw <= 0 or ch <= 0:
121
+ return
122
+
123
+ alpha = 100
124
+ base_color = (
125
+ colors.PLACEMENT_VALID_COLOR if is_valid else colors.PLACEMENT_INVALID_COLOR
126
+ )
127
+ color: colors.ColorRGBA = (base_color[0], base_color[1], base_color[2], alpha)
128
+
129
+ temp_surface = pygame.Surface(surface.get_size(), pygame.SRCALPHA)
130
+ temp_surface.fill((0, 0, 0, 0))
131
+
132
+ for dr, dc, is_up in shape.triangles:
133
+ tri_r, tri_c = r + dr, c + dc
134
+ temp_tri = Triangle(tri_r, tri_c, is_up)
135
+ pts = temp_tri.get_points(ox, oy, cw, ch)
136
+ pygame.draw.polygon(temp_surface, color, pts)
137
+
138
+ surface.blit(temp_surface, (0, 0))
139
+
140
+
141
+ def draw_floating_preview(
142
+ surface: pygame.Surface,
143
+ shape: Shape,
144
+ screen_pos: tuple[int, int],
145
+ # _config: EnvConfig, # Removed - not needed with fixed cell_size
146
+ # _mapper_module: "coord_mapper_module", # Removed
147
+ ) -> None:
148
+ """Draws a semi-transparent shape floating at the screen position."""
149
+ if not shape or not shape.triangles:
150
+ return
151
+
152
+ cell_size = 20.0 # Fixed size for floating preview
153
+ alpha = 100
154
+ color: colors.ColorRGBA = (shape.color[0], shape.color[1], shape.color[2], alpha)
155
+
156
+ temp_surface = pygame.Surface(surface.get_size(), pygame.SRCALPHA)
157
+ temp_surface.fill((0, 0, 0, 0))
158
+
159
+ min_r, min_c, max_r, max_c = shape.bbox()
160
+ center_r = (min_r + max_r) / 2.0
161
+ center_c = (min_c + max_c) / 2.0
162
+
163
+ for dr, dc, is_up in shape.triangles:
164
+ pt_x = screen_pos[0] + (dc - center_c) * (cell_size * 0.75)
165
+ pt_y = screen_pos[1] + (dr - center_r) * cell_size
166
+
167
+ temp_tri = Triangle(0, 0, is_up)
168
+ rel_pts = temp_tri.get_points(0, 0, cell_size, cell_size)
169
+ pts = [(px + pt_x, py + pt_y) for px, py in rel_pts]
170
+ pygame.draw.polygon(temp_surface, color, pts)
171
+
172
+ surface.blit(temp_surface, (0, 0))
@@ -0,0 +1,36 @@
1
+ import pygame
2
+
3
+ # Use internal imports
4
+ from ...core.structs import Shape, Triangle
5
+ from ..core import colors
6
+
7
+
8
+ def draw_shape(
9
+ surface: pygame.Surface,
10
+ shape: Shape,
11
+ topleft: tuple[int, int],
12
+ cell_size: float,
13
+ _is_selected: bool = False,
14
+ origin_offset: tuple[int, int] = (0, 0),
15
+ ) -> None:
16
+ """Draws a single shape onto a surface."""
17
+ if not shape or not shape.triangles or cell_size <= 0:
18
+ return
19
+
20
+ shape_color = shape.color
21
+ border_color = colors.GRAY
22
+
23
+ cw = cell_size
24
+ ch = cell_size
25
+
26
+ for dr, dc, is_up in shape.triangles:
27
+ adj_r, adj_c = dr + origin_offset[0], dc + origin_offset[1]
28
+
29
+ tri_x = topleft[0] + adj_c * (cw * 0.75)
30
+ tri_y = topleft[1] + adj_r * ch
31
+
32
+ temp_tri = Triangle(0, 0, is_up)
33
+ pts = [(px + tri_x, py + tri_y) for px, py in temp_tri.get_points(0, 0, cw, ch)]
34
+
35
+ pygame.draw.polygon(surface, shape_color, pts)
36
+ pygame.draw.polygon(surface, border_color, pts, 1)