From bc95cb1f0b93315f1128ab174fa1507a0d319e2d Mon Sep 17 00:00:00 2001 From: John McCardle Date: Wed, 26 Nov 2025 00:26:14 -0500 Subject: [PATCH 1/4] feat: Add geometry module for orbital mechanics and spatial calculations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements issue #130 with: - Basic utilities: distance, angle_between, normalize_angle, lerp, clamp - Grid algorithms: bresenham_circle, bresenham_line, filled_circle - OrbitalBody class with recursive positioning (star -> planet -> moon) - OrbitingShip class for relative ship positioning on orbit rings - Pathfinding helpers: nearest_orbit_entry, optimal_exit_heading, is_viable_waypoint, line_of_sight_blocked - Comprehensive test suite (25+ tests) Designed for Pinships turn-based space roguelike with: - Discrete time steps (planets move in whole grid squares) - Deterministic position projection - Free orbital movement while in orbit - Support for nested orbits (moons of moons) closes #130 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/scripts/geometry.py | 580 ++++++++++++++++++++++++++++++++++ tests/unit/test_geometry.py | 604 ++++++++++++++++++++++++++++++++++++ 2 files changed, 1184 insertions(+) create mode 100644 src/scripts/geometry.py create mode 100644 tests/unit/test_geometry.py diff --git a/src/scripts/geometry.py b/src/scripts/geometry.py new file mode 100644 index 0000000..52465ba --- /dev/null +++ b/src/scripts/geometry.py @@ -0,0 +1,580 @@ +""" +Geometry module for turn-based games with orbital mechanics. + +Designed for Pinships but reusable for any game needing: +- Circular orbit calculations +- Grid-aligned geometric primitives +- Recursive celestial body positioning +- Pathfinding helpers for orbital navigation + +Philosophy: "C++ every frame, Python every game step" +This module handles game logic, not rendering. +""" + +from __future__ import annotations +import math +from typing import Optional, List, Tuple, Set +from dataclasses import dataclass, field + + +# ============================================================================= +# Basic Utility Functions +# ============================================================================= + +def distance(p1: Tuple[float, float], p2: Tuple[float, float]) -> float: + """Euclidean distance between two points.""" + dx = p2[0] - p1[0] + dy = p2[1] - p1[1] + return math.sqrt(dx * dx + dy * dy) + + +def distance_squared(p1: Tuple[float, float], p2: Tuple[float, float]) -> float: + """Squared distance (avoids sqrt, useful for comparisons).""" + dx = p2[0] - p1[0] + dy = p2[1] - p1[1] + return dx * dx + dy * dy + + +def angle_between(p1: Tuple[float, float], p2: Tuple[float, float]) -> float: + """ + Angle from p1 to p2 in degrees (0-360). + 0 degrees = east (+x), 90 = north (+y in screen coords, or south in math coords). + """ + dx = p2[0] - p1[0] + dy = p2[1] - p1[1] + angle = math.degrees(math.atan2(dy, dx)) + return normalize_angle(angle) + + +def normalize_angle(angle: float) -> float: + """Normalize angle to 0-360 range.""" + angle = angle % 360 + if angle < 0: + angle += 360 + return angle + + +def angle_difference(a1: float, a2: float) -> float: + """ + Shortest angular distance between two angles (signed, -180 to 180). + Positive = counterclockwise from a1 to a2. + """ + diff = normalize_angle(a2) - normalize_angle(a1) + if diff > 180: + diff -= 360 + elif diff < -180: + diff += 360 + return diff + + +def lerp(a: float, b: float, t: float) -> float: + """Linear interpolation from a to b by factor t (0-1).""" + return a + (b - a) * t + + +def clamp(value: float, min_val: float, max_val: float) -> float: + """Clamp value to range [min_val, max_val].""" + return max(min_val, min(max_val, value)) + + +def point_on_circle( + center: Tuple[float, float], + radius: float, + angle_degrees: float +) -> Tuple[float, float]: + """Get point on circle at given angle (degrees).""" + angle_rad = math.radians(angle_degrees) + x = center[0] + radius * math.cos(angle_rad) + y = center[1] + radius * math.sin(angle_rad) + return (x, y) + + +def rotate_point( + point: Tuple[float, float], + center: Tuple[float, float], + angle_degrees: float +) -> Tuple[float, float]: + """Rotate point around center by angle (degrees).""" + angle_rad = math.radians(angle_degrees) + cos_a = math.cos(angle_rad) + sin_a = math.sin(angle_rad) + + # Translate to origin + px = point[0] - center[0] + py = point[1] - center[1] + + # Rotate + rx = px * cos_a - py * sin_a + ry = px * sin_a + py * cos_a + + # Translate back + return (rx + center[0], ry + center[1]) + + +# ============================================================================= +# Grid-Aligned Geometry (Bresenham algorithms) +# ============================================================================= + +def bresenham_circle( + center: Tuple[int, int], + radius: int +) -> List[Tuple[int, int]]: + """ + Generate all grid cells on a circle's perimeter using Bresenham's algorithm. + Returns cells in no particular order (use sort_circle_cells for ordering). + """ + if radius <= 0: + return [center] + + cx, cy = center + cells: Set[Tuple[int, int]] = set() + + x = 0 + y = radius + d = 3 - 2 * radius + + def add_circle_points(cx: int, cy: int, x: int, y: int): + """Add all 8 symmetric points.""" + cells.add((cx + x, cy + y)) + cells.add((cx - x, cy + y)) + cells.add((cx + x, cy - y)) + cells.add((cx - x, cy - y)) + cells.add((cx + y, cy + x)) + cells.add((cx - y, cy + x)) + cells.add((cx + y, cy - x)) + cells.add((cx - y, cy - x)) + + add_circle_points(cx, cy, x, y) + + while y >= x: + x += 1 + if d > 0: + y -= 1 + d = d + 4 * (x - y) + 10 + else: + d = d + 4 * x + 6 + add_circle_points(cx, cy, x, y) + + return list(cells) + + +def sort_circle_cells( + cells: List[Tuple[int, int]], + center: Tuple[int, int] +) -> List[Tuple[int, int]]: + """Sort circle cells by angle from center (for ordered traversal).""" + return sorted(cells, key=lambda p: angle_between(center, p)) + + +def bresenham_line( + p1: Tuple[int, int], + p2: Tuple[int, int] +) -> List[Tuple[int, int]]: + """Generate all grid cells on a line using Bresenham's algorithm.""" + cells = [] + x1, y1 = p1 + x2, y2 = p2 + + dx = abs(x2 - x1) + dy = abs(y2 - y1) + sx = 1 if x1 < x2 else -1 + sy = 1 if y1 < y2 else -1 + err = dx - dy + + while True: + cells.append((x1, y1)) + if x1 == x2 and y1 == y2: + break + e2 = 2 * err + if e2 > -dy: + err -= dy + x1 += sx + if e2 < dx: + err += dx + y1 += sy + + return cells + + +def filled_circle( + center: Tuple[int, int], + radius: int +) -> List[Tuple[int, int]]: + """Generate all grid cells within a filled circle.""" + if radius <= 0: + return [center] + + cx, cy = center + cells = [] + r_sq = radius * radius + + for y in range(cy - radius, cy + radius + 1): + for x in range(cx - radius, cx + radius + 1): + if (x - cx) ** 2 + (y - cy) ** 2 <= r_sq: + cells.append((x, y)) + + return cells + + +# ============================================================================= +# Orbital Body System +# ============================================================================= + +@dataclass +class OrbitalBody: + """ + A celestial body that may orbit another body. + + Supports recursive orbits: star -> planet -> moon -> moon-of-moon + Position is calculated by walking up the parent chain. + """ + + name: str + surface_radius: int # Physical size of the body + orbit_ring_radius: int # Distance from center where ships can orbit + + # Orbital parameters (ignored if parent is None) + parent: Optional[OrbitalBody] = None + orbital_radius: float = 0.0 # Distance from parent's center + angular_velocity: float = 0.0 # Degrees per turn + initial_angle: float = 0.0 # Angle at t=0 + + # Base position (only used if parent is None, i.e., the star) + base_position: Tuple[int, int] = (0, 0) + + def center_at_time(self, t: int) -> Tuple[float, float]: + """ + Get continuous (float) position at time t. + Recursively calculates position through parent chain. + """ + if self.parent is None: + # Stationary body (star) + return (float(self.base_position[0]), float(self.base_position[1])) + + # Get parent's position at this time + parent_pos = self.parent.center_at_time(t) + + # Calculate our angle at time t + angle = self.initial_angle + self.angular_velocity * t + + # Calculate offset from parent + offset = point_on_circle((0, 0), self.orbital_radius, angle) + + return (parent_pos[0] + offset[0], parent_pos[1] + offset[1]) + + def grid_position_at_time(self, t: int) -> Tuple[int, int]: + """ + Get snapped grid position at time t. + This is where the body appears on the discrete game grid. + """ + cx, cy = self.center_at_time(t) + return (round(cx), round(cy)) + + def surface_cells(self, t: int) -> List[Tuple[int, int]]: + """Get all grid cells occupied by this body's surface at time t.""" + return filled_circle(self.grid_position_at_time(t), self.surface_radius) + + def orbit_ring_cells(self, t: int) -> List[Tuple[int, int]]: + """ + Get all grid cells forming the orbit ring at time t. + Ships can occupy these cells while orbiting this body. + """ + return bresenham_circle(self.grid_position_at_time(t), self.orbit_ring_radius) + + def orbit_ring_cells_sorted(self, t: int) -> List[Tuple[int, int]]: + """Get orbit ring cells sorted by angle (for ordered traversal).""" + center = self.grid_position_at_time(t) + cells = bresenham_circle(center, self.orbit_ring_radius) + return sort_circle_cells(cells, center) + + def position_in_orbit(self, t: int, angle: float) -> Tuple[int, int]: + """ + Get the grid position for a ship orbiting this body at given angle. + The ship moves with the body - this returns absolute grid coords. + """ + center = self.grid_position_at_time(t) + pos = point_on_circle(center, self.orbit_ring_radius, angle) + return (round(pos[0]), round(pos[1])) + + def is_inside_surface(self, point: Tuple[int, int], t: int) -> bool: + """Check if a grid point is inside this body's surface.""" + center = self.grid_position_at_time(t) + return distance_squared(center, point) <= self.surface_radius ** 2 + + def is_on_orbit_ring(self, point: Tuple[int, int], t: int) -> bool: + """Check if a grid point is on this body's orbit ring.""" + return point in self.orbit_ring_cells(t) + + def nearest_orbit_angle(self, point: Tuple[float, float], t: int) -> float: + """ + Get the angle on the orbit ring closest to the given point. + Useful for determining where a ship would enter orbit. + """ + center = self.grid_position_at_time(t) + return angle_between(center, point) + + def turns_until_position_changes(self, current_t: int) -> int: + """ + Calculate how many turns until this body's grid position changes. + Returns 0 if it changes next turn, -1 if it never moves (star). + """ + if self.parent is None: + return -1 # Stars don't move + + current_pos = self.grid_position_at_time(current_t) + + # Check future turns (reasonable limit to avoid infinite loop) + for dt in range(1, 1000): + future_pos = self.grid_position_at_time(current_t + dt) + if future_pos != current_pos: + return dt + + return -1 # Essentially stationary (very slow orbit) + + +@dataclass +class OrbitingShip: + """ + A ship that is currently in orbit around a body. + + When orbiting, position is relative to the body, not absolute grid coords. + The ship moves with the body automatically. + """ + + body: OrbitalBody + orbital_angle: float # Position on orbit ring (degrees) + + def grid_position_at_time(self, t: int) -> Tuple[int, int]: + """Get absolute grid position at time t.""" + return self.body.position_in_orbit(t, self.orbital_angle) + + def move_along_orbit(self, angle_delta: float) -> None: + """Move ship along the orbit ring (free movement while orbiting).""" + self.orbital_angle = normalize_angle(self.orbital_angle + angle_delta) + + def set_orbit_angle(self, angle: float) -> None: + """Set ship to specific angle on orbit ring.""" + self.orbital_angle = normalize_angle(angle) + + +# ============================================================================= +# Pathfinding Helpers +# ============================================================================= + +def nearest_orbit_entry( + ship_pos: Tuple[float, float], + body: OrbitalBody, + t: int +) -> Tuple[Tuple[int, int], float]: + """ + Find the nearest point on a body's orbit ring to enter. + + Returns: + (grid_position, angle): Entry point and the orbital angle + """ + angle = body.nearest_orbit_angle(ship_pos, t) + entry_pos = body.position_in_orbit(t, angle) + return (entry_pos, angle) + + +def optimal_exit_heading( + body: OrbitalBody, + target: Tuple[float, float], + t: int +) -> Tuple[float, Tuple[int, int]]: + """ + Find the best angle to exit an orbit when heading toward a target. + + Returns: + (exit_angle, exit_position): Best exit angle and grid position + """ + center = body.grid_position_at_time(t) + exit_angle = angle_between(center, target) + exit_pos = body.position_in_orbit(t, exit_angle) + return (exit_angle, exit_pos) + + +def is_viable_waypoint( + ship_pos: Tuple[float, float], + body: OrbitalBody, + target: Tuple[float, float], + t: int, + angle_threshold: float = 90.0 +) -> bool: + """ + Check if an orbital body is a useful waypoint toward a target. + + A body is viable if it's roughly "on the way" - the angle from + ship to body to target isn't too sharp (would be backtracking). + + Args: + ship_pos: Ship's current position + body: Potential waypoint body + target: Final destination + t: Current time + angle_threshold: Maximum deflection angle (degrees) + + Returns: + True if using this body's orbit could help reach target + """ + body_pos = body.grid_position_at_time(t) + + # Angle from ship to body + angle_to_body = angle_between(ship_pos, body_pos) + + # Angle from ship to target + angle_to_target = angle_between(ship_pos, target) + + # How much would we deviate from direct path? + deviation = abs(angle_difference(angle_to_target, angle_to_body)) + + return deviation <= angle_threshold + + +def project_body_positions( + body: OrbitalBody, + start_t: int, + num_turns: int +) -> List[Tuple[int, Tuple[int, int]]]: + """ + Project a body's grid positions over future turns. + + Returns: + List of (turn, grid_position) tuples + """ + positions = [] + for dt in range(num_turns): + t = start_t + dt + pos = body.grid_position_at_time(t) + positions.append((t, pos)) + return positions + + +def find_intercept_turn( + ship_pos: Tuple[float, float], + ship_speed: float, + body: OrbitalBody, + start_t: int, + max_turns: int = 100 +) -> Optional[Tuple[int, Tuple[int, int]]]: + """ + Find when a ship could intercept a moving body's orbit. + + Simple approach: check each future turn to see if ship could + reach the body's orbit ring by then. + + Args: + ship_pos: Ship's starting position + ship_speed: Ship's movement per turn (grid units) + body: Target body to intercept + start_t: Current turn + max_turns: Maximum turns to search + + Returns: + (turn, intercept_position) or None if no intercept found + """ + for dt in range(1, max_turns + 1): + t = start_t + dt + body_center = body.grid_position_at_time(t) + + # Distance ship could travel + max_travel = ship_speed * dt + + # Distance to body's orbit ring + dist_to_center = distance(ship_pos, body_center) + dist_to_orbit = abs(dist_to_center - body.orbit_ring_radius) + + if dist_to_orbit <= max_travel: + # Ship could reach orbit this turn + entry_pos, _ = nearest_orbit_entry(ship_pos, body, t) + return (t, entry_pos) + + return None + + +def line_of_sight_blocked( + p1: Tuple[int, int], + p2: Tuple[int, int], + bodies: List[OrbitalBody], + t: int +) -> Optional[OrbitalBody]: + """ + Check if line of sight between two points is blocked by any body's surface. + + Returns: + The blocking body, or None if LOS is clear + """ + line_cells = set(bresenham_line(p1, p2)) + + for body in bodies: + surface = set(body.surface_cells(t)) + if line_cells & surface: # Intersection + return body + + return None + + +# ============================================================================= +# Convenience Functions +# ============================================================================= + +def create_solar_system( + grid_width: int, + grid_height: int, + star_radius: int = 10, + star_orbit_radius: int = 15 +) -> OrbitalBody: + """ + Create a star at the center of the grid. + + Returns the star body (other bodies should use it as parent). + """ + return OrbitalBody( + name="Star", + surface_radius=star_radius, + orbit_ring_radius=star_orbit_radius, + parent=None, + base_position=(grid_width // 2, grid_height // 2) + ) + + +def create_planet( + name: str, + star: OrbitalBody, + orbital_radius: float, + surface_radius: int, + orbit_ring_radius: int, + angular_velocity: float, + initial_angle: float = 0.0 +) -> OrbitalBody: + """Create a planet orbiting a star.""" + return OrbitalBody( + name=name, + surface_radius=surface_radius, + orbit_ring_radius=orbit_ring_radius, + parent=star, + orbital_radius=orbital_radius, + angular_velocity=angular_velocity, + initial_angle=initial_angle + ) + + +def create_moon( + name: str, + planet: OrbitalBody, + orbital_radius: float, + surface_radius: int, + orbit_ring_radius: int, + angular_velocity: float, + initial_angle: float = 0.0 +) -> OrbitalBody: + """Create a moon orbiting a planet (or another moon).""" + return OrbitalBody( + name=name, + surface_radius=surface_radius, + orbit_ring_radius=orbit_ring_radius, + parent=planet, + orbital_radius=orbital_radius, + angular_velocity=angular_velocity, + initial_angle=initial_angle + ) diff --git a/tests/unit/test_geometry.py b/tests/unit/test_geometry.py new file mode 100644 index 0000000..91db29c --- /dev/null +++ b/tests/unit/test_geometry.py @@ -0,0 +1,604 @@ +""" +Unit tests for the geometry module (Pinships orbital mechanics). + +Tests cover: +- Basic utility functions (distance, angle, etc.) +- Bresenham circle/line algorithms +- OrbitalBody recursive positioning +- Pathfinding helpers +""" + +import sys +import math + +# Import the geometry module +sys.path.insert(0, '/home/john/Development/McRogueFace/src/scripts') +from geometry import ( + # Utilities + distance, distance_squared, angle_between, normalize_angle, + angle_difference, lerp, clamp, point_on_circle, rotate_point, + # Grid algorithms + bresenham_circle, bresenham_line, filled_circle, sort_circle_cells, + # Orbital system + OrbitalBody, OrbitingShip, + # Pathfinding + nearest_orbit_entry, optimal_exit_heading, is_viable_waypoint, + project_body_positions, line_of_sight_blocked, + # Convenience + create_solar_system, create_planet, create_moon +) + +EPSILON = 0.0001 # Float comparison tolerance + +def approx_equal(a, b, eps=EPSILON): + """Check if two floats are approximately equal.""" + return abs(a - b) < eps + +def test_distance(): + """Test distance calculations.""" + assert approx_equal(distance((0, 0), (3, 4)), 5.0) + assert approx_equal(distance((0, 0), (0, 0)), 0.0) + assert approx_equal(distance((1, 1), (4, 5)), 5.0) + assert approx_equal(distance((-3, -4), (0, 0)), 5.0) + print(" distance: PASS") + +def test_distance_squared(): + """Test squared distance (no sqrt).""" + assert distance_squared((0, 0), (3, 4)) == 25 + assert distance_squared((0, 0), (0, 0)) == 0 + print(" distance_squared: PASS") + +def test_angle_between(): + """Test angle calculations.""" + # East = 0 degrees + assert approx_equal(angle_between((0, 0), (1, 0)), 0.0) + # North = 90 degrees (in screen coordinates, +y is down, but atan2 treats +y as up) + assert approx_equal(angle_between((0, 0), (0, 1)), 90.0) + # West = 180 degrees + assert approx_equal(angle_between((0, 0), (-1, 0)), 180.0) + # South = 270 degrees + assert approx_equal(angle_between((0, 0), (0, -1)), 270.0) + # Diagonal + assert approx_equal(angle_between((0, 0), (1, 1)), 45.0) + print(" angle_between: PASS") + +def test_normalize_angle(): + """Test angle normalization to 0-360.""" + assert approx_equal(normalize_angle(0), 0.0) + assert approx_equal(normalize_angle(360), 0.0) + assert approx_equal(normalize_angle(720), 0.0) + assert approx_equal(normalize_angle(-90), 270.0) + assert approx_equal(normalize_angle(-360), 0.0) + assert approx_equal(normalize_angle(450), 90.0) + print(" normalize_angle: PASS") + +def test_angle_difference(): + """Test shortest angular distance.""" + assert approx_equal(angle_difference(0, 90), 90.0) + assert approx_equal(angle_difference(90, 0), -90.0) + assert approx_equal(angle_difference(350, 10), 20.0) # Wrap around + assert approx_equal(angle_difference(10, 350), -20.0) + assert approx_equal(angle_difference(0, 180), 180.0) + print(" angle_difference: PASS") + +def test_lerp(): + """Test linear interpolation.""" + assert approx_equal(lerp(0, 10, 0.0), 0.0) + assert approx_equal(lerp(0, 10, 1.0), 10.0) + assert approx_equal(lerp(0, 10, 0.5), 5.0) + assert approx_equal(lerp(-5, 5, 0.5), 0.0) + print(" lerp: PASS") + +def test_clamp(): + """Test value clamping.""" + assert clamp(5, 0, 10) == 5 + assert clamp(-5, 0, 10) == 0 + assert clamp(15, 0, 10) == 10 + assert clamp(0, 0, 10) == 0 + assert clamp(10, 0, 10) == 10 + print(" clamp: PASS") + +def test_point_on_circle(): + """Test point calculation on circle.""" + center = (100, 100) + radius = 50 + + # East (0 degrees) + p = point_on_circle(center, radius, 0) + assert approx_equal(p[0], 150.0) + assert approx_equal(p[1], 100.0) + + # North (90 degrees) + p = point_on_circle(center, radius, 90) + assert approx_equal(p[0], 100.0) + assert approx_equal(p[1], 150.0) + + # West (180 degrees) + p = point_on_circle(center, radius, 180) + assert approx_equal(p[0], 50.0) + assert approx_equal(p[1], 100.0) + + print(" point_on_circle: PASS") + +def test_rotate_point(): + """Test point rotation around center.""" + center = (0, 0) + point = (1, 0) + + # Rotate 90 degrees + p = rotate_point(point, center, 90) + assert approx_equal(p[0], 0.0) + assert approx_equal(p[1], 1.0) + + # Rotate 180 degrees + p = rotate_point(point, center, 180) + assert approx_equal(p[0], -1.0) + assert approx_equal(p[1], 0.0) + + print(" rotate_point: PASS") + +def test_bresenham_circle(): + """Test Bresenham circle generation.""" + # Radius 0 = just the center + cells = bresenham_circle((5, 5), 0) + assert cells == [(5, 5)] + + # Radius 3 should give a circle-ish shape + cells = bresenham_circle((10, 10), 3) + assert len(cells) > 0 + + # All cells should be roughly radius distance from center + for x, y in cells: + dist = math.sqrt((x - 10) ** 2 + (y - 10) ** 2) + assert 2.5 <= dist <= 3.5, f"Cell ({x},{y}) has distance {dist}" + + # Should be symmetric + cells_set = set(cells) + for x, y in cells: + # Check all 4 quadrant reflections exist + dx, dy = x - 10, y - 10 + assert (10 + dx, 10 + dy) in cells_set + assert (10 - dx, 10 + dy) in cells_set + assert (10 + dx, 10 - dy) in cells_set + assert (10 - dx, 10 - dy) in cells_set + + print(" bresenham_circle: PASS") + +def test_bresenham_line(): + """Test Bresenham line generation.""" + # Horizontal line + cells = bresenham_line((0, 0), (5, 0)) + assert cells == [(0, 0), (1, 0), (2, 0), (3, 0), (4, 0), (5, 0)] + + # Vertical line + cells = bresenham_line((0, 0), (0, 3)) + assert cells == [(0, 0), (0, 1), (0, 2), (0, 3)] + + # Diagonal line + cells = bresenham_line((0, 0), (3, 3)) + assert (0, 0) in cells + assert (3, 3) in cells + assert len(cells) == 4 # Should hit 4 cells for 45-degree line + + # Start and end should be included + cells = bresenham_line((10, 20), (15, 22)) + assert (10, 20) in cells + assert (15, 22) in cells + + print(" bresenham_line: PASS") + +def test_filled_circle(): + """Test filled circle generation.""" + cells = filled_circle((5, 5), 2) + + # Center should be included + assert (5, 5) in cells + + # Edges should be included + assert (5, 3) in cells # top + assert (5, 7) in cells # bottom + assert (3, 5) in cells # left + assert (7, 5) in cells # right + + # Corners (at distance sqrt(8) ≈ 2.83) should NOT be included for radius 2 + assert (3, 3) not in cells + + print(" filled_circle: PASS") + +def test_orbital_body_stationary(): + """Test stationary body (star) positioning.""" + star = OrbitalBody( + name="Star", + surface_radius=10, + orbit_ring_radius=15, + parent=None, + base_position=(500, 500) + ) + + # Position should never change + assert star.grid_position_at_time(0) == (500, 500) + assert star.grid_position_at_time(100) == (500, 500) + assert star.grid_position_at_time(9999) == (500, 500) + + # Continuous position should match + assert star.center_at_time(0) == (500.0, 500.0) + + print(" orbital_body_stationary: PASS") + +def test_orbital_body_simple_orbit(): + """Test planet orbiting a star.""" + star = OrbitalBody( + name="Star", + surface_radius=10, + orbit_ring_radius=15, + parent=None, + base_position=(500, 500) + ) + + planet = OrbitalBody( + name="Planet", + surface_radius=5, + orbit_ring_radius=10, + parent=star, + orbital_radius=100, # 100 units from star + angular_velocity=90, # 90 degrees per turn (quarter orbit) + initial_angle=0 # Start to the east + ) + + # t=0: Planet should be east of star + pos0 = planet.center_at_time(0) + assert approx_equal(pos0[0], 600.0) # 500 + 100 + assert approx_equal(pos0[1], 500.0) + + # t=1: Planet should be north of star (rotated 90 degrees) + pos1 = planet.center_at_time(1) + assert approx_equal(pos1[0], 500.0) + assert approx_equal(pos1[1], 600.0) # 500 + 100 + + # t=2: Planet should be west of star + pos2 = planet.center_at_time(2) + assert approx_equal(pos2[0], 400.0) # 500 - 100 + assert approx_equal(pos2[1], 500.0) + + # t=4: Back to start (full orbit) + pos4 = planet.center_at_time(4) + assert approx_equal(pos4[0], 600.0) + assert approx_equal(pos4[1], 500.0) + + print(" orbital_body_simple_orbit: PASS") + +def test_orbital_body_nested_orbit(): + """Test moon orbiting a planet orbiting a star.""" + star = OrbitalBody( + name="Star", + surface_radius=10, + orbit_ring_radius=15, + parent=None, + base_position=(500, 500) + ) + + planet = OrbitalBody( + name="Planet", + surface_radius=5, + orbit_ring_radius=10, + parent=star, + orbital_radius=100, + angular_velocity=90, # Quarter orbit per turn + initial_angle=0 + ) + + moon = OrbitalBody( + name="Moon", + surface_radius=2, + orbit_ring_radius=5, + parent=planet, + orbital_radius=20, # 20 units from planet + angular_velocity=180, # Half orbit per turn (faster than planet) + initial_angle=0 + ) + + # t=0: Moon should be east of planet, which is east of star + moon_pos0 = moon.center_at_time(0) + # Planet at (600, 500), moon 20 units east = (620, 500) + assert approx_equal(moon_pos0[0], 620.0) + assert approx_equal(moon_pos0[1], 500.0) + + # t=1: Planet moved north (500, 600), moon rotated 180 degrees (west of planet) + moon_pos1 = moon.center_at_time(1) + # Planet at (500, 600), moon 20 units west = (480, 600) + assert approx_equal(moon_pos1[0], 480.0) + assert approx_equal(moon_pos1[1], 600.0) + + print(" orbital_body_nested_orbit: PASS") + +def test_orbiting_ship(): + """Test ship orbiting a body.""" + star = OrbitalBody( + name="Star", + surface_radius=10, + orbit_ring_radius=50, + parent=None, + base_position=(500, 500) + ) + + ship = OrbitingShip(body=star, orbital_angle=0) + + # Ship at angle 0 should be east of star + pos = ship.grid_position_at_time(0) + assert pos == (550, 500) # 500 + 50 + + # Move ship along orbit + ship.move_along_orbit(90) + pos = ship.grid_position_at_time(0) + assert pos == (500, 550) # North of star + + # Set specific angle + ship.set_orbit_angle(180) + pos = ship.grid_position_at_time(0) + assert pos == (450, 500) # West of star + + print(" orbiting_ship: PASS") + +def test_orbit_ring_cells(): + """Test orbit ring cell generation.""" + body = OrbitalBody( + name="Planet", + surface_radius=5, + orbit_ring_radius=10, + parent=None, + base_position=(100, 100) + ) + + cells = body.orbit_ring_cells(0) + + # Should have cells on the ring + assert len(cells) > 0 + + # All cells should be approximately orbit_ring_radius from center + for x, y in cells: + dist = math.sqrt((x - 100) ** 2 + (y - 100) ** 2) + assert 9.0 <= dist <= 11.0, f"Cell ({x},{y}) has distance {dist}" + + print(" orbit_ring_cells: PASS") + +def test_surface_cells(): + """Test surface cell generation.""" + body = OrbitalBody( + name="Planet", + surface_radius=3, + orbit_ring_radius=10, + parent=None, + base_position=(50, 50) + ) + + cells = body.surface_cells(0) + + # Center should be included + assert (50, 50) in cells + + # All cells should be within surface_radius + for x, y in cells: + dist = math.sqrt((x - 50) ** 2 + (y - 50) ** 2) + assert dist <= 3.5, f"Cell ({x},{y}) has distance {dist}" + + print(" surface_cells: PASS") + +def test_nearest_orbit_entry(): + """Test finding nearest orbit entry point.""" + body = OrbitalBody( + name="Planet", + surface_radius=5, + orbit_ring_radius=20, + parent=None, + base_position=(100, 100) + ) + + # Ship approaching from east + ship_pos = (150, 100) + entry_pos, angle = nearest_orbit_entry(ship_pos, body, 0) + + # Entry should be on the east side of orbit ring + assert approx_equal(angle, 0.0) + assert entry_pos == (120, 100) # 100 + 20 + + # Ship approaching from north-east + ship_pos = (150, 150) + entry_pos, angle = nearest_orbit_entry(ship_pos, body, 0) + assert approx_equal(angle, 45.0) + + print(" nearest_orbit_entry: PASS") + +def test_optimal_exit_heading(): + """Test finding optimal orbit exit toward target.""" + body = OrbitalBody( + name="Planet", + surface_radius=5, + orbit_ring_radius=20, + parent=None, + base_position=(100, 100) + ) + + # Target to the west + target = (0, 100) + exit_angle, exit_pos = optimal_exit_heading(body, target, 0) + + assert approx_equal(exit_angle, 180.0) + assert exit_pos == (80, 100) # 100 - 20 + + print(" optimal_exit_heading: PASS") + +def test_is_viable_waypoint(): + """Test waypoint viability check.""" + body = OrbitalBody( + name="Planet", + surface_radius=5, + orbit_ring_radius=10, + parent=None, + base_position=(100, 100) + ) + + ship_pos = (50, 100) # West of body + target_east = (200, 100) # Far east + target_west = (0, 100) # Far west + + # Body is between ship and eastern target - viable + assert is_viable_waypoint(ship_pos, body, target_east, 0, angle_threshold=90) + + # Body is NOT between ship and western target - not viable + assert not is_viable_waypoint(ship_pos, body, target_west, 0, angle_threshold=45) + + print(" is_viable_waypoint: PASS") + +def test_line_of_sight_blocked(): + """Test line of sight blocking by bodies.""" + blocker = OrbitalBody( + name="Planet", + surface_radius=10, + orbit_ring_radius=20, + parent=None, + base_position=(100, 100) + ) + + # LOS through the planet should be blocked + p1 = (50, 100) + p2 = (150, 100) + result = line_of_sight_blocked(p1, p2, [blocker], 0) + assert result == blocker + + # LOS around the planet should be clear + p1 = (50, 50) + p2 = (150, 50) + result = line_of_sight_blocked(p1, p2, [blocker], 0) + assert result is None + + print(" line_of_sight_blocked: PASS") + +def test_convenience_functions(): + """Test solar system creation helpers.""" + star = create_solar_system(1000, 1000, star_radius=15, star_orbit_radius=25) + + assert star.name == "Star" + assert star.base_position == (500, 500) + assert star.surface_radius == 15 + assert star.orbit_ring_radius == 25 + assert star.parent is None + + planet = create_planet( + name="Terra", + star=star, + orbital_radius=200, + surface_radius=10, + orbit_ring_radius=20, + angular_velocity=10, + initial_angle=45 + ) + + assert planet.name == "Terra" + assert planet.parent == star + assert planet.orbital_radius == 200 + + moon = create_moon( + name="Luna", + planet=planet, + orbital_radius=30, + surface_radius=3, + orbit_ring_radius=8, + angular_velocity=30 + ) + + assert moon.name == "Luna" + assert moon.parent == planet + + print(" convenience_functions: PASS") + +def test_discrete_movement(): + """Test that grid positions change at discrete thresholds.""" + star = OrbitalBody( + name="Star", + surface_radius=10, + orbit_ring_radius=15, + parent=None, + base_position=(500, 500) + ) + + # Planet with moderate angular velocity + planet = OrbitalBody( + name="Planet", + surface_radius=5, + orbit_ring_radius=10, + parent=star, + orbital_radius=100, + angular_velocity=1.0, # 1 degree per turn + initial_angle=0 + ) + + # Positions should be deterministic + pos0 = planet.grid_position_at_time(0) + pos10 = planet.grid_position_at_time(10) + pos10_again = planet.grid_position_at_time(10) + + # Same time = same position (deterministic) + assert pos10 == pos10_again + + # Position should change over time + assert pos0 != pos10 + + # Full orbit (360 degrees / 1 deg per turn = 360 turns) should return to start + pos360 = planet.grid_position_at_time(360) + assert pos0 == pos360 + + # Check the turns_until_position_changes function + turns = planet.turns_until_position_changes(0) + assert turns >= 1 # Should eventually change + + # Verify it actually changes at that turn + pos_before = planet.grid_position_at_time(0) + pos_after = planet.grid_position_at_time(turns) + assert pos_before != pos_after + + print(" discrete_movement: PASS") + +def run_all_tests(): + """Run all geometry tests.""" + print("Running geometry module tests...\n") + + print("Utility functions:") + test_distance() + test_distance_squared() + test_angle_between() + test_normalize_angle() + test_angle_difference() + test_lerp() + test_clamp() + test_point_on_circle() + test_rotate_point() + + print("\nGrid algorithms:") + test_bresenham_circle() + test_bresenham_line() + test_filled_circle() + + print("\nOrbital body system:") + test_orbital_body_stationary() + test_orbital_body_simple_orbit() + test_orbital_body_nested_orbit() + test_orbiting_ship() + test_orbit_ring_cells() + test_surface_cells() + test_discrete_movement() + + print("\nPathfinding helpers:") + test_nearest_orbit_entry() + test_optimal_exit_heading() + test_is_viable_waypoint() + test_line_of_sight_blocked() + + print("\nConvenience functions:") + test_convenience_functions() + + print("\n" + "=" * 50) + print("All geometry tests PASSED!") + print("=" * 50) + +if __name__ == "__main__": + run_all_tests() From 198686cba9c8dfd1aee564b8266a32a9db46c81e Mon Sep 17 00:00:00 2001 From: John McCardle Date: Wed, 26 Nov 2025 00:46:38 -0500 Subject: [PATCH 2/4] feat: Add geometry module demo system for orbital mechanics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates comprehensive visual demonstrations of the geometry module: Static demos: - Bresenham algorithms: circle/line rasterization on grid cells - Angle calculations: line elements showing angles between points, waypoint viability with angle thresholds, orbit exit headings - Pathfinding: planets with surfaces and orbit rings, optimal path using orbital slingshots vs direct path comparison Animated demos: - Solar system: planets orbiting star with discrete time steps, nested moon orbit, position updates every second - Pathfinding through moving system: ship navigates to target using orbital intercepts, anticipating planetary motion Includes 5 screenshot outputs demonstrating each feature. Run: ./mcrogueface --headless --exec tests/geometry_demo/geometry_main.py Interactive: ./mcrogueface tests/geometry_demo/geometry_main.py 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- tests/geometry_demo/__init__.py | 1 + tests/geometry_demo/geometry_main.py | 213 +++++++++++ tests/geometry_demo/screens/__init__.py | 6 + .../geometry_demo/screens/angle_lines_demo.py | 319 ++++++++++++++++ tests/geometry_demo/screens/base.py | 84 ++++ tests/geometry_demo/screens/bresenham_demo.py | 170 +++++++++ .../screens/pathfinding_animated_demo.py | 359 ++++++++++++++++++ .../screens/pathfinding_static_demo.py | 292 ++++++++++++++ .../screens/solar_system_demo.py | 274 +++++++++++++ .../geo_00_bresenham_algorithms.png | Bin 0 -> 63717 bytes .../screenshots/geo_01_angle_calculations.png | Bin 0 -> 92371 bytes .../screenshots/geo_02_static_pathfinding.png | Bin 0 -> 67505 bytes .../geo_03_solar_system_animation.png | Bin 0 -> 54605 bytes .../geo_04_animated_pathfinding.png | Bin 0 -> 52044 bytes 14 files changed, 1718 insertions(+) create mode 100644 tests/geometry_demo/__init__.py create mode 100644 tests/geometry_demo/geometry_main.py create mode 100644 tests/geometry_demo/screens/__init__.py create mode 100644 tests/geometry_demo/screens/angle_lines_demo.py create mode 100644 tests/geometry_demo/screens/base.py create mode 100644 tests/geometry_demo/screens/bresenham_demo.py create mode 100644 tests/geometry_demo/screens/pathfinding_animated_demo.py create mode 100644 tests/geometry_demo/screens/pathfinding_static_demo.py create mode 100644 tests/geometry_demo/screens/solar_system_demo.py create mode 100644 tests/geometry_demo/screenshots/geo_00_bresenham_algorithms.png create mode 100644 tests/geometry_demo/screenshots/geo_01_angle_calculations.png create mode 100644 tests/geometry_demo/screenshots/geo_02_static_pathfinding.png create mode 100644 tests/geometry_demo/screenshots/geo_03_solar_system_animation.png create mode 100644 tests/geometry_demo/screenshots/geo_04_animated_pathfinding.png diff --git a/tests/geometry_demo/__init__.py b/tests/geometry_demo/__init__.py new file mode 100644 index 0000000..a44e7d8 --- /dev/null +++ b/tests/geometry_demo/__init__.py @@ -0,0 +1 @@ +"""Geometry module demo system for Pinships orbital mechanics.""" diff --git a/tests/geometry_demo/geometry_main.py b/tests/geometry_demo/geometry_main.py new file mode 100644 index 0000000..e97d073 --- /dev/null +++ b/tests/geometry_demo/geometry_main.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 +""" +Geometry Module Demo System + +Demonstrates the geometry module for Pinships orbital mechanics: +- Bresenham algorithms for grid-aligned circles and lines +- Angle calculations for pathfinding +- Static pathfinding through planetary orbits +- Animated solar system with discrete time steps +- Ship navigation anticipating planetary motion + +Usage: + Headless (screenshots): ./mcrogueface --headless --exec tests/geometry_demo/geometry_main.py + Interactive: ./mcrogueface tests/geometry_demo/geometry_main.py +""" +import mcrfpy +from mcrfpy import automation +import sys +import os + +# Add paths for imports +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', 'src', 'scripts')) + +# Import screen modules +from geometry_demo.screens.bresenham_demo import BresenhamDemo +from geometry_demo.screens.angle_lines_demo import AngleLinesDemo +from geometry_demo.screens.pathfinding_static_demo import PathfindingStaticDemo +from geometry_demo.screens.solar_system_demo import SolarSystemDemo +from geometry_demo.screens.pathfinding_animated_demo import PathfindingAnimatedDemo + +# All demo screens in order +DEMO_SCREENS = [ + BresenhamDemo, + AngleLinesDemo, + PathfindingStaticDemo, + SolarSystemDemo, + PathfindingAnimatedDemo, +] + + +class GeometryDemoRunner: + """Manages the geometry demo system.""" + + def __init__(self): + self.screens = [] + self.current_index = 0 + self.headless = self._detect_headless() + self.screenshot_dir = os.path.join(os.path.dirname(__file__), "screenshots") + + def _detect_headless(self): + """Detect if running in headless mode.""" + try: + win = mcrfpy.Window.get() + return str(win).find("headless") >= 0 + except: + return True + + def setup_all_screens(self): + """Initialize all demo screens.""" + for i, ScreenClass in enumerate(DEMO_SCREENS): + scene_name = f"geo_{i:02d}_{ScreenClass.name.lower().replace(' ', '_')}" + screen = ScreenClass(scene_name) + screen.setup() + self.screens.append(screen) + + def create_menu(self): + """Create the main menu screen.""" + mcrfpy.createScene("geo_menu") + ui = mcrfpy.sceneUI("geo_menu") + + # Background + bg = mcrfpy.Frame(pos=(0, 0), size=(800, 600)) + bg.fill_color = mcrfpy.Color(15, 15, 25) + ui.append(bg) + + # Title + title = mcrfpy.Caption(text="Geometry Module Demo", pos=(400, 30)) + title.fill_color = mcrfpy.Color(255, 255, 255) + title.outline = 2 + title.outline_color = mcrfpy.Color(0, 0, 0) + ui.append(title) + + subtitle = mcrfpy.Caption(text="Pinships Orbital Mechanics", pos=(400, 70)) + subtitle.fill_color = mcrfpy.Color(180, 180, 180) + ui.append(subtitle) + + # Menu items + for i, screen in enumerate(self.screens): + y = 130 + i * 60 + + # Button frame + btn = mcrfpy.Frame(pos=(200, y), size=(400, 50)) + btn.fill_color = mcrfpy.Color(30, 40, 60) + btn.outline = 2 + btn.outline_color = mcrfpy.Color(80, 100, 150) + ui.append(btn) + + # Button text + label = mcrfpy.Caption(text=f"{i+1}. {screen.name}", pos=(20, 12)) + label.fill_color = mcrfpy.Color(200, 200, 255) + btn.children.append(label) + + # Description + desc = mcrfpy.Caption(text=screen.description, pos=(20, 32)) + desc.fill_color = mcrfpy.Color(120, 120, 150) + btn.children.append(desc) + + # Instructions + instr1 = mcrfpy.Caption(text="Press 1-5 to view demos", pos=(300, 480)) + instr1.fill_color = mcrfpy.Color(150, 150, 150) + ui.append(instr1) + + instr2 = mcrfpy.Caption(text="ESC = return to menu | Q = quit", pos=(270, 510)) + instr2.fill_color = mcrfpy.Color(100, 100, 100) + ui.append(instr2) + + # Credits + credits = mcrfpy.Caption(text="Geometry module: src/scripts/geometry.py", pos=(250, 560)) + credits.fill_color = mcrfpy.Color(80, 80, 100) + ui.append(credits) + + def run_headless(self): + """Run in headless mode - generate all screenshots.""" + print(f"Generating {len(self.screens)} geometry demo screenshots...") + + os.makedirs(self.screenshot_dir, exist_ok=True) + + self.current_index = 0 + self.render_wait = 0 + + def screenshot_cycle(runtime): + if self.render_wait == 0: + if self.current_index >= len(self.screens): + print("Done!") + sys.exit(0) + return + screen = self.screens[self.current_index] + mcrfpy.setScene(screen.scene_name) + self.render_wait = 1 + elif self.render_wait < 3: + # Wait for animated demos to show initial state + self.render_wait += 1 + else: + screen = self.screens[self.current_index] + filename = os.path.join(self.screenshot_dir, screen.get_screenshot_name()) + automation.screenshot(filename) + print(f" [{self.current_index+1}/{len(self.screens)}] {filename}") + + # Clean up timers for animated demos + screen.cleanup() + + self.current_index += 1 + self.render_wait = 0 + if self.current_index >= len(self.screens): + print("Done!") + sys.exit(0) + + mcrfpy.setTimer("screenshot", screenshot_cycle, 100) + + def run_interactive(self): + """Run in interactive mode with menu.""" + self.create_menu() + + def handle_key(key, state): + if state != "start": + return + + # Number keys 1-9 for direct screen access + if key in [f"Num{n}" for n in "123456789"]: + idx = int(key[-1]) - 1 + if idx < len(self.screens): + # Clean up previous screen's timers + for screen in self.screens: + screen.cleanup() + mcrfpy.setScene(self.screens[idx].scene_name) + # Re-setup the screen to restart animations + # (timers were cleaned up, need to restart) + + # ESC returns to menu + elif key == "Escape": + for screen in self.screens: + screen.cleanup() + mcrfpy.setScene("geo_menu") + + # Q quits + elif key == "Q": + sys.exit(0) + + # Register keyboard handler on all scenes + mcrfpy.setScene("geo_menu") + mcrfpy.keypressScene(handle_key) + + for screen in self.screens: + mcrfpy.setScene(screen.scene_name) + mcrfpy.keypressScene(handle_key) + + mcrfpy.setScene("geo_menu") + + +def main(): + """Main entry point.""" + runner = GeometryDemoRunner() + runner.setup_all_screens() + + if runner.headless: + runner.run_headless() + else: + runner.run_interactive() + + +# Run when executed +main() diff --git a/tests/geometry_demo/screens/__init__.py b/tests/geometry_demo/screens/__init__.py new file mode 100644 index 0000000..088c8b4 --- /dev/null +++ b/tests/geometry_demo/screens/__init__.py @@ -0,0 +1,6 @@ +"""Geometry demo screens.""" +from .bresenham_demo import BresenhamDemo +from .angle_lines_demo import AngleLinesDemo +from .pathfinding_static_demo import PathfindingStaticDemo +from .solar_system_demo import SolarSystemDemo +from .pathfinding_animated_demo import PathfindingAnimatedDemo diff --git a/tests/geometry_demo/screens/angle_lines_demo.py b/tests/geometry_demo/screens/angle_lines_demo.py new file mode 100644 index 0000000..45e0e30 --- /dev/null +++ b/tests/geometry_demo/screens/angle_lines_demo.py @@ -0,0 +1,319 @@ +"""Angle calculation demonstration with Line elements.""" +import mcrfpy +import math +from .base import (GeometryDemoScreen, angle_between, angle_difference, + normalize_angle, point_on_circle, distance) + + +class AngleLinesDemo(GeometryDemoScreen): + """Demonstrate angle calculations between points using Line elements.""" + + name = "Angle Calculations" + description = "Visualizing angles between grid positions" + + def setup(self): + self.add_title("Angle Calculations & Line Elements") + self.add_description("Computing headings, deviations, and opposite angles for pathfinding") + + # Demo 1: Basic angle between two points + self._demo_basic_angle() + + # Demo 2: Angle between three points (deviation) + self._demo_angle_deviation() + + # Demo 3: Waypoint viability visualization + self._demo_waypoint_viability() + + # Demo 4: Orbit exit heading + self._demo_orbit_exit() + + def _demo_basic_angle(self): + """Show angle from point A to point B.""" + bg = mcrfpy.Frame(pos=(30, 80), size=(350, 200)) + bg.fill_color = mcrfpy.Color(15, 15, 25) + bg.outline = 1 + bg.outline_color = mcrfpy.Color(60, 60, 100) + self.ui.append(bg) + + self.add_label("Basic Angle Calculation", 50, 85, (255, 200, 100)) + + # Point A (origin) + ax, ay = 100, 180 + # Point B (target) + bx, by = 300, 120 + + angle = angle_between((ax, ay), (bx, by)) + dist = distance((ax, ay), (bx, by)) + + # Draw the line A to B (green) + line_ab = mcrfpy.Line( + start=(ax, ay), end=(bx, by), + color=mcrfpy.Color(100, 255, 100), + thickness=3 + ) + self.ui.append(line_ab) + + # Draw reference line (east from A) in gray + line_ref = mcrfpy.Line( + start=(ax, ay), end=(ax + 150, ay), + color=mcrfpy.Color(100, 100, 100), + thickness=1 + ) + self.ui.append(line_ref) + + # Draw arc showing the angle + arc = mcrfpy.Arc( + center=(ax, ay), radius=40, + start_angle=0, end_angle=-angle, # Negative because screen Y is inverted + color=mcrfpy.Color(255, 255, 100), + thickness=2 + ) + self.ui.append(arc) + + # Points + point_a = mcrfpy.Circle(center=(ax, ay), radius=8, + fill_color=mcrfpy.Color(255, 100, 100)) + point_b = mcrfpy.Circle(center=(bx, by), radius=8, + fill_color=mcrfpy.Color(100, 255, 100)) + self.ui.append(point_a) + self.ui.append(point_b) + + # Labels + self.add_label("A", ax - 20, ay - 5, (255, 100, 100)) + self.add_label("B", bx + 10, by - 5, (100, 255, 100)) + self.add_label(f"Angle: {angle:.1f}°", 50, 250, (255, 255, 100)) + self.add_label(f"Distance: {dist:.1f}", 180, 250, (150, 150, 150)) + + def _demo_angle_deviation(self): + """Show angle deviation when considering a waypoint.""" + bg = mcrfpy.Frame(pos=(400, 80), size=(380, 200)) + bg.fill_color = mcrfpy.Color(15, 15, 25) + bg.outline = 1 + bg.outline_color = mcrfpy.Color(60, 60, 100) + self.ui.append(bg) + + self.add_label("Waypoint Deviation", 420, 85, (255, 200, 100)) + self.add_label("Is planet C a useful waypoint from A to B?", 420, 105, (150, 150, 150)) + + # Ship at A, target at B, potential waypoint C + ax, ay = 450, 230 + bx, by = 720, 180 + cx, cy = 550, 150 + + # Calculate angles + angle_to_target = angle_between((ax, ay), (bx, by)) + angle_to_waypoint = angle_between((ax, ay), (cx, cy)) + deviation = abs(angle_difference(angle_to_target, angle_to_waypoint)) + + # Draw line A to B (direct path - green) + line_ab = mcrfpy.Line( + start=(ax, ay), end=(bx, by), + color=mcrfpy.Color(100, 255, 100), + thickness=2 + ) + self.ui.append(line_ab) + + # Draw line A to C (waypoint path - yellow if viable, red if not) + viable = deviation <= 45 + waypoint_color = mcrfpy.Color(255, 255, 100) if viable else mcrfpy.Color(255, 100, 100) + line_ac = mcrfpy.Line( + start=(ax, ay), end=(cx, cy), + color=waypoint_color, + thickness=2 + ) + self.ui.append(line_ac) + + # Draw deviation arc + arc = mcrfpy.Arc( + center=(ax, ay), radius=50, + start_angle=-angle_to_target, end_angle=-angle_to_waypoint, + color=waypoint_color, + thickness=2 + ) + self.ui.append(arc) + + # Points + point_a = mcrfpy.Circle(center=(ax, ay), radius=8, + fill_color=mcrfpy.Color(255, 100, 100)) + point_b = mcrfpy.Circle(center=(bx, by), radius=8, + fill_color=mcrfpy.Color(100, 255, 100)) + point_c = mcrfpy.Circle(center=(cx, cy), radius=12, + fill_color=mcrfpy.Color(100, 100, 200), + outline_color=mcrfpy.Color(150, 150, 255), + outline=2) + self.ui.append(point_a) + self.ui.append(point_b) + self.ui.append(point_c) + + # Labels + self.add_label("A (ship)", ax - 10, ay + 10, (255, 100, 100)) + self.add_label("B (target)", bx - 20, by + 15, (100, 255, 100)) + self.add_label("C (planet)", cx + 15, cy - 5, (150, 150, 255)) + label_color = (255, 255, 100) if viable else (255, 100, 100) + self.add_label(f"Deviation: {deviation:.1f}°", 550, 250, label_color) + status = "VIABLE (<45°)" if viable else "NOT VIABLE (>45°)" + self.add_label(status, 680, 250, label_color) + + def _demo_waypoint_viability(self): + """Show multiple potential waypoints with viability indicators.""" + bg = mcrfpy.Frame(pos=(30, 300), size=(350, 280)) + bg.fill_color = mcrfpy.Color(15, 15, 25) + bg.outline = 1 + bg.outline_color = mcrfpy.Color(60, 60, 100) + self.ui.append(bg) + + self.add_label("Multiple Waypoint Analysis", 50, 305, (255, 200, 100)) + + # Ship and target + ax, ay = 80, 450 + bx, by = 320, 380 + + angle_to_target = angle_between((ax, ay), (bx, by)) + + # Draw direct path + line_ab = mcrfpy.Line( + start=(ax, ay), end=(bx, by), + color=mcrfpy.Color(100, 255, 100, 128), + thickness=2 + ) + self.ui.append(line_ab) + + # Potential waypoints at various angles + waypoints = [ + (150, 360, "W1"), # Ahead and left - viable + (200, 500, "W2"), # Below path - marginal + (100, 540, "W3"), # Behind - not viable + (250, 340, "W4"), # Almost on path - very viable + ] + + threshold = 45 + for wx, wy, label in waypoints: + angle_to_wp = angle_between((ax, ay), (wx, wy)) + deviation = abs(angle_difference(angle_to_target, angle_to_wp)) + viable = deviation <= threshold + + # Line to waypoint + color_tuple = (100, 255, 100) if viable else (255, 100, 100) + color = mcrfpy.Color(*color_tuple) + line = mcrfpy.Line( + start=(ax, ay), end=(wx, wy), + color=color, + thickness=1 + ) + self.ui.append(line) + + # Waypoint circle + wp_circle = mcrfpy.Circle( + center=(wx, wy), radius=15, + fill_color=mcrfpy.Color(80, 80, 120), + outline_color=color, + outline=2 + ) + self.ui.append(wp_circle) + + self.add_label(f"{label}:{deviation:.0f}°", wx + 18, wy - 8, color_tuple) + + # Ship and target markers + ship = mcrfpy.Circle(center=(ax, ay), radius=8, + fill_color=mcrfpy.Color(255, 200, 100)) + target = mcrfpy.Circle(center=(bx, by), radius=8, + fill_color=mcrfpy.Color(100, 255, 100)) + self.ui.append(ship) + self.ui.append(target) + + self.add_label("Ship", ax - 5, ay + 12, (255, 200, 100)) + self.add_label("Target", bx - 15, by + 12, (100, 255, 100)) + self.add_label(f"Threshold: {threshold}°", 50, 555, (150, 150, 150)) + + def _demo_orbit_exit(self): + """Show optimal orbit exit heading toward target.""" + bg = mcrfpy.Frame(pos=(400, 300), size=(380, 280)) + bg.fill_color = mcrfpy.Color(15, 15, 25) + bg.outline = 1 + bg.outline_color = mcrfpy.Color(60, 60, 100) + self.ui.append(bg) + + self.add_label("Orbit Exit Heading", 420, 305, (255, 200, 100)) + self.add_label("Ship in orbit chooses optimal exit point", 420, 325, (150, 150, 150)) + + # Planet center and orbit + px, py = 520, 450 + orbit_radius = 60 + surface_radius = 25 + + # Target position + tx, ty = 720, 380 + + # Calculate optimal exit angle + exit_angle = angle_between((px, py), (tx, ty)) + exit_x = px + orbit_radius * math.cos(math.radians(exit_angle)) + exit_y = py - orbit_radius * math.sin(math.radians(exit_angle)) # Flip for screen coords + + # Draw planet surface + planet = mcrfpy.Circle( + center=(px, py), radius=surface_radius, + fill_color=mcrfpy.Color(80, 120, 180), + outline_color=mcrfpy.Color(100, 150, 220), + outline=2 + ) + self.ui.append(planet) + + # Draw orbit ring + orbit = mcrfpy.Circle( + center=(px, py), radius=orbit_radius, + fill_color=mcrfpy.Color(0, 0, 0, 0), + outline_color=mcrfpy.Color(50, 150, 50), + outline=2 + ) + self.ui.append(orbit) + + # Draw ship positions around orbit (current position) + ship_angle = 200 # Current position + ship_x = px + orbit_radius * math.cos(math.radians(ship_angle)) + ship_y = py - orbit_radius * math.sin(math.radians(ship_angle)) + + ship = mcrfpy.Circle( + center=(ship_x, ship_y), radius=8, + fill_color=mcrfpy.Color(255, 200, 100) + ) + self.ui.append(ship) + + # Draw path: ship moves along orbit (free) to exit point + # Arc from ship position to exit position + orbit_arc = mcrfpy.Arc( + center=(px, py), radius=orbit_radius, + start_angle=-ship_angle, end_angle=-exit_angle, + color=mcrfpy.Color(255, 255, 100), + thickness=3 + ) + self.ui.append(orbit_arc) + + # Draw exit point + exit_point = mcrfpy.Circle( + center=(exit_x, exit_y), radius=6, + fill_color=mcrfpy.Color(100, 255, 100) + ) + self.ui.append(exit_point) + + # Draw line from exit to target + exit_line = mcrfpy.Line( + start=(exit_x, exit_y), end=(tx, ty), + color=mcrfpy.Color(100, 255, 100), + thickness=2 + ) + self.ui.append(exit_line) + + # Target + target = mcrfpy.Circle( + center=(tx, ty), radius=10, + fill_color=mcrfpy.Color(255, 100, 100) + ) + self.ui.append(target) + + # Labels + self.add_label("Planet", px - 20, py + surface_radius + 5, (100, 150, 220)) + self.add_label("Ship", ship_x - 25, ship_y - 15, (255, 200, 100)) + self.add_label("Exit", exit_x + 10, exit_y - 10, (100, 255, 100)) + self.add_label("Target", tx - 15, ty + 15, (255, 100, 100)) + self.add_label(f"Exit angle: {exit_angle:.1f}°", 420, 555, (150, 150, 150)) + self.add_label("Yellow arc = free orbital movement", 550, 555, (255, 255, 100)) diff --git a/tests/geometry_demo/screens/base.py b/tests/geometry_demo/screens/base.py new file mode 100644 index 0000000..3420054 --- /dev/null +++ b/tests/geometry_demo/screens/base.py @@ -0,0 +1,84 @@ +"""Base class for geometry demo screens.""" +import mcrfpy +import sys +import os + +# Add scripts path for geometry module +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src', 'scripts')) +from geometry import * + + +class GeometryDemoScreen: + """Base class for geometry demo screens.""" + + name = "Base Screen" + description = "Override this description" + + def __init__(self, scene_name): + self.scene_name = scene_name + mcrfpy.createScene(scene_name) + self.ui = mcrfpy.sceneUI(scene_name) + self.timers = [] # Track timer names for cleanup + + def setup(self): + """Override to set up the screen content.""" + pass + + def cleanup(self): + """Clean up timers when leaving screen.""" + for timer_name in self.timers: + try: + mcrfpy.delTimer(timer_name) + except: + pass + + def get_screenshot_name(self): + """Return the screenshot filename for this screen.""" + return f"{self.scene_name}.png" + + def add_title(self, text, y=10): + """Add a title caption.""" + title = mcrfpy.Caption(text=text, pos=(400, y)) + title.fill_color = mcrfpy.Color(255, 255, 255) + title.outline = 2 + title.outline_color = mcrfpy.Color(0, 0, 0) + self.ui.append(title) + return title + + def add_description(self, text, y=50): + """Add a description caption.""" + desc = mcrfpy.Caption(text=text, pos=(50, y)) + desc.fill_color = mcrfpy.Color(200, 200, 200) + self.ui.append(desc) + return desc + + def add_label(self, text, x, y, color=(200, 200, 200)): + """Add a label caption.""" + label = mcrfpy.Caption(text=text, pos=(x, y)) + label.fill_color = mcrfpy.Color(*color) + self.ui.append(label) + return label + + def create_grid(self, grid_size, pos, size, cell_size=16): + """Create a grid for visualization.""" + grid = mcrfpy.Grid( + grid_size=grid_size, + pos=pos, + size=size + ) + grid.fill_color = mcrfpy.Color(20, 20, 30) + self.ui.append(grid) + return grid + + def color_grid_cell(self, grid, x, y, color): + """Color a specific grid cell.""" + try: + point = grid.at(x, y) + point.color = mcrfpy.Color(*color) if isinstance(color, tuple) else color + except: + pass # Out of bounds + + def add_timer(self, name, callback, interval): + """Add a timer and track it for cleanup.""" + mcrfpy.setTimer(name, callback, interval) + self.timers.append(name) diff --git a/tests/geometry_demo/screens/bresenham_demo.py b/tests/geometry_demo/screens/bresenham_demo.py new file mode 100644 index 0000000..02c417b --- /dev/null +++ b/tests/geometry_demo/screens/bresenham_demo.py @@ -0,0 +1,170 @@ +"""Bresenham circle algorithm demonstration on a grid.""" +import mcrfpy +from .base import GeometryDemoScreen, bresenham_circle, bresenham_line, filled_circle + + +class BresenhamDemo(GeometryDemoScreen): + """Demonstrate Bresenham circle and line algorithms on a grid.""" + + name = "Bresenham Algorithms" + description = "Grid-aligned circle and line rasterization" + + def setup(self): + self.add_title("Bresenham Circle & Line Algorithms") + self.add_description("Grid-aligned geometric primitives for orbit rings and LOS calculations") + + # Create a grid for circle demo + grid_w, grid_h = 25, 18 + cell_size = 16 + + # We need a texture for the grid - create a simple one + # Actually, let's use Grid's built-in cell coloring via GridPoint + + # Create display area with Frame background + bg1 = mcrfpy.Frame(pos=(30, 80), size=(420, 310)) + bg1.fill_color = mcrfpy.Color(15, 15, 25) + bg1.outline = 1 + bg1.outline_color = mcrfpy.Color(60, 60, 100) + self.ui.append(bg1) + + self.add_label("Bresenham Circle (radius=8)", 50, 85, (255, 200, 100)) + self.add_label("Center: (12, 9)", 50, 105, (150, 150, 150)) + + # Draw circle using UICircle primitives to show the cells + center = (12, 9) + radius = 8 + circle_cells = bresenham_circle(center, radius) + + # Draw each cell as a small rectangle + for x, y in circle_cells: + px = 40 + x * cell_size + py = 120 + y * cell_size + cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1)) + cell_rect.fill_color = mcrfpy.Color(100, 200, 255) + cell_rect.outline = 0 + self.ui.append(cell_rect) + + # Draw center point + cx_px = 40 + center[0] * cell_size + cy_px = 120 + center[1] * cell_size + center_rect = mcrfpy.Frame(pos=(cx_px, cy_px), size=(cell_size - 1, cell_size - 1)) + center_rect.fill_color = mcrfpy.Color(255, 100, 100) + self.ui.append(center_rect) + + # Draw the actual circle outline for comparison + actual_circle = mcrfpy.Circle( + center=(40 + center[0] * cell_size + cell_size // 2, + 120 + center[1] * cell_size + cell_size // 2), + radius=radius * cell_size, + fill_color=mcrfpy.Color(0, 0, 0, 0), + outline_color=mcrfpy.Color(255, 255, 100, 128), + outline=2 + ) + self.ui.append(actual_circle) + + # Second demo: Bresenham line + bg2 = mcrfpy.Frame(pos=(470, 80), size=(310, 310)) + bg2.fill_color = mcrfpy.Color(15, 15, 25) + bg2.outline = 1 + bg2.outline_color = mcrfpy.Color(60, 60, 100) + self.ui.append(bg2) + + self.add_label("Bresenham Lines", 490, 85, (255, 200, 100)) + + # Draw multiple lines at different angles + lines_data = [ + ((2, 2), (17, 5), (255, 100, 100)), # Shallow + ((2, 7), (17, 14), (100, 255, 100)), # Diagonal-ish + ((2, 12), (10, 17), (100, 100, 255)), # Steep + ] + + for start, end, color in lines_data: + line_cells = bresenham_line(start, end) + for x, y in line_cells: + px = 480 + x * cell_size + py = 110 + y * cell_size + cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1)) + cell_rect.fill_color = mcrfpy.Color(*color) + self.ui.append(cell_rect) + + # Draw the actual line for comparison + line = mcrfpy.Line( + start=(480 + start[0] * cell_size + cell_size // 2, + 110 + start[1] * cell_size + cell_size // 2), + end=(480 + end[0] * cell_size + cell_size // 2, + 110 + end[1] * cell_size + cell_size // 2), + color=mcrfpy.Color(255, 255, 255, 128), + thickness=1 + ) + self.ui.append(line) + + # Third demo: Filled circle (planet surface) + bg3 = mcrfpy.Frame(pos=(30, 410), size=(200, 170)) + bg3.fill_color = mcrfpy.Color(15, 15, 25) + bg3.outline = 1 + bg3.outline_color = mcrfpy.Color(60, 60, 100) + self.ui.append(bg3) + + self.add_label("Filled Circle (radius=4)", 50, 415, (255, 200, 100)) + self.add_label("Planet surface representation", 50, 435, (150, 150, 150)) + + fill_center = (6, 5) + fill_radius = 4 + filled_cells = filled_circle(fill_center, fill_radius) + + for x, y in filled_cells: + px = 40 + x * cell_size + py = 460 + y * cell_size + cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1)) + # Gradient based on distance from center + dist = ((x - fill_center[0])**2 + (y - fill_center[1])**2) ** 0.5 + intensity = int(255 * (1 - dist / (fill_radius + 1))) + cell_rect.fill_color = mcrfpy.Color(intensity, intensity // 2, 50) + self.ui.append(cell_rect) + + # Fourth demo: Combined - planet with orbit ring + bg4 = mcrfpy.Frame(pos=(250, 410), size=(530, 170)) + bg4.fill_color = mcrfpy.Color(15, 15, 25) + bg4.outline = 1 + bg4.outline_color = mcrfpy.Color(60, 60, 100) + self.ui.append(bg4) + + self.add_label("Planet + Orbit Ring", 270, 415, (255, 200, 100)) + self.add_label("Surface (r=3) + Orbit (r=7)", 270, 435, (150, 150, 150)) + + planet_center = (16, 5) + surface_radius = 3 + orbit_radius = 7 + + # Draw orbit ring (behind planet) + orbit_cells = bresenham_circle(planet_center, orbit_radius) + for x, y in orbit_cells: + px = 260 + x * cell_size + py = 460 + y * cell_size + cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1)) + cell_rect.fill_color = mcrfpy.Color(50, 150, 50, 180) + self.ui.append(cell_rect) + + # Draw planet surface (on top) + surface_cells = filled_circle(planet_center, surface_radius) + for x, y in surface_cells: + px = 260 + x * cell_size + py = 460 + y * cell_size + cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1)) + dist = ((x - planet_center[0])**2 + (y - planet_center[1])**2) ** 0.5 + intensity = int(200 * (1 - dist / (surface_radius + 1))) + cell_rect.fill_color = mcrfpy.Color(50 + intensity, 100 + intensity // 2, 200) + self.ui.append(cell_rect) + + # Legend + self.add_label("Legend:", 600, 455, (200, 200, 200)) + + leg1 = mcrfpy.Frame(pos=(600, 475), size=(12, 12)) + leg1.fill_color = mcrfpy.Color(100, 150, 200) + self.ui.append(leg1) + self.add_label("Planet surface", 620, 473, (150, 150, 150)) + + leg2 = mcrfpy.Frame(pos=(600, 495), size=(12, 12)) + leg2.fill_color = mcrfpy.Color(50, 150, 50) + self.ui.append(leg2) + self.add_label("Orbit ring (ship positions)", 620, 493, (150, 150, 150)) diff --git a/tests/geometry_demo/screens/pathfinding_animated_demo.py b/tests/geometry_demo/screens/pathfinding_animated_demo.py new file mode 100644 index 0000000..2d8c2d2 --- /dev/null +++ b/tests/geometry_demo/screens/pathfinding_animated_demo.py @@ -0,0 +1,359 @@ +"""Animated pathfinding through a moving solar system.""" +import mcrfpy +import math +from .base import (GeometryDemoScreen, OrbitalBody, create_solar_system, + create_planet, point_on_circle, distance, angle_between, + normalize_angle, is_viable_waypoint, nearest_orbit_entry, + optimal_exit_heading) + + +class PathfindingAnimatedDemo(GeometryDemoScreen): + """Demonstrate ship navigation through moving orbital bodies.""" + + name = "Animated Pathfinding" + description = "Ship navigates through moving planets" + + def setup(self): + self.add_title("Pathfinding Through Moving Planets") + self.add_description("Ship anticipates planetary motion to use orbital slingshots") + + # Screen layout + self.center_x = 400 + self.center_y = 320 + self.scale = 2.0 + + # Background + bg = mcrfpy.Frame(pos=(50, 80), size=(700, 460)) + bg.fill_color = mcrfpy.Color(5, 5, 15) + bg.outline = 1 + bg.outline_color = mcrfpy.Color(40, 40, 80) + self.ui.append(bg) + + # Create solar system + self.star = create_solar_system( + grid_width=200, grid_height=200, + star_radius=10, star_orbit_radius=18 + ) + + # Create a planet that the ship will use as waypoint + self.planet = create_planet( + name="Waypoint", + star=self.star, + orbital_radius=80, + surface_radius=8, + orbit_ring_radius=15, + angular_velocity=5, # Moves 5 degrees per turn + initial_angle=180 # Starts on left side + ) + + # Ship state + self.ship_speed = 10 # Grid units per turn + self.ship_pos = [30, 100] # Start position (grid coords, relative to star) + self.ship_target = [100, -80] # Target position + self.ship_state = "approach" # approach, orbiting, exiting, traveling + self.ship_orbit_angle = 0 + self.current_time = 0 + + # Plan the path + self.path_plan = [] + self.current_path_index = 0 + + # Store UI elements + self.planet_circle = None + self.planet_orbit_ring = None + self.ship_circle = None + self.path_lines = [] + self.status_label = None + + # Draw static elements + self._draw_static() + + # Draw initial state + self._draw_dynamic() + + # Info panel + self._draw_info_panel() + + # Start animation + self.add_timer("pathfind_tick", self._tick, 1000) + + def _to_screen(self, grid_pos): + """Convert grid position (relative to star) to screen coordinates.""" + gx, gy = grid_pos + return (self.center_x + gx * self.scale, self.center_y - gy * self.scale) + + def _draw_static(self): + """Draw static elements.""" + star_screen = self._to_screen((0, 0)) + + # Star + star = mcrfpy.Circle( + center=star_screen, + radius=self.star.surface_radius * self.scale, + fill_color=mcrfpy.Color(255, 220, 100), + outline_color=mcrfpy.Color(255, 180, 50), + outline=2 + ) + self.ui.append(star) + + # Planet orbital path + orbit_path = mcrfpy.Circle( + center=star_screen, + radius=self.planet.orbital_radius * self.scale, + fill_color=mcrfpy.Color(0, 0, 0, 0), + outline_color=mcrfpy.Color(40, 40, 60), + outline=1 + ) + self.ui.append(orbit_path) + + # Target marker + target_screen = self._to_screen(self.ship_target) + target = mcrfpy.Circle( + center=target_screen, + radius=12, + fill_color=mcrfpy.Color(255, 100, 100), + outline_color=mcrfpy.Color(255, 200, 200), + outline=2 + ) + self.ui.append(target) + self.add_label("TARGET", target_screen[0] - 25, target_screen[1] + 15, (255, 100, 100)) + + # Start marker + start_screen = self._to_screen(self.ship_pos) + self.add_label("START", start_screen[0] - 20, start_screen[1] + 15, (100, 255, 100)) + + def _draw_dynamic(self): + """Draw/update dynamic elements (planet, ship, path).""" + # Get planet position at current time + planet_grid = self._get_planet_pos(self.current_time) + planet_screen = self._to_screen(planet_grid) + + # Planet + if self.planet_circle: + self.planet_circle.center = planet_screen + else: + self.planet_circle = mcrfpy.Circle( + center=planet_screen, + radius=self.planet.surface_radius * self.scale, + fill_color=mcrfpy.Color(100, 150, 255), + outline_color=mcrfpy.Color(150, 200, 255), + outline=2 + ) + self.ui.append(self.planet_circle) + + # Planet orbit ring + if self.planet_orbit_ring: + self.planet_orbit_ring.center = planet_screen + else: + self.planet_orbit_ring = mcrfpy.Circle( + center=planet_screen, + radius=self.planet.orbit_ring_radius * self.scale, + fill_color=mcrfpy.Color(0, 0, 0, 0), + outline_color=mcrfpy.Color(50, 150, 50), + outline=2 + ) + self.ui.append(self.planet_orbit_ring) + + # Ship + ship_screen = self._to_screen(self.ship_pos) + if self.ship_circle: + self.ship_circle.center = ship_screen + else: + self.ship_circle = mcrfpy.Circle( + center=ship_screen, + radius=8, + fill_color=mcrfpy.Color(255, 200, 100), + outline_color=mcrfpy.Color(255, 255, 200), + outline=2 + ) + self.ui.append(self.ship_circle) + + # Draw predicted path + self._draw_predicted_path() + + def _get_planet_pos(self, t): + """Get planet position in grid coords relative to star.""" + angle = self.planet.initial_angle + self.planet.angular_velocity * t + x = self.planet.orbital_radius * math.cos(math.radians(angle)) + y = self.planet.orbital_radius * math.sin(math.radians(angle)) + return (x, y) + + def _draw_predicted_path(self): + """Draw the predicted ship path.""" + # Clear old path lines + # (In a real implementation, we'd remove old lines from UI) + # For now, we'll just draw new ones each time + + ship_pos = tuple(self.ship_pos) + target = tuple(self.ship_target) + + # Simple path prediction: + # 1. Calculate when ship can intercept planet's orbit + # 2. Show line to intercept point + # 3. Show arc on orbit + # 4. Show line to target + + if self.ship_state == "approach": + # Find intercept time + intercept_time, intercept_pos = self._find_intercept() + if intercept_time: + # Line from ship to intercept + ship_screen = self._to_screen(ship_pos) + intercept_screen = self._to_screen(intercept_pos) + + # Draw approach line + approach_line = mcrfpy.Line( + start=ship_screen, end=intercept_screen, + color=mcrfpy.Color(100, 200, 255, 150), + thickness=2 + ) + self.ui.append(approach_line) + + def _find_intercept(self): + """Find when ship can intercept planet's orbit.""" + # Simplified: check next 20 turns + for dt in range(1, 20): + future_t = self.current_time + dt + planet_pos = self._get_planet_pos(future_t) + + # Distance ship could travel + max_dist = self.ship_speed * dt + + # Distance from ship to planet's orbit ring + dist_to_planet = distance(self.ship_pos, planet_pos) + dist_to_orbit = abs(dist_to_planet - self.planet.orbit_ring_radius) + + if dist_to_orbit <= max_dist: + # Calculate entry point + angle_to_planet = angle_between(self.ship_pos, planet_pos) + entry_x = planet_pos[0] + self.planet.orbit_ring_radius * math.cos(math.radians(angle_to_planet + 180)) + entry_y = planet_pos[1] + self.planet.orbit_ring_radius * math.sin(math.radians(angle_to_planet + 180)) + return (future_t, (entry_x, entry_y)) + + return (None, None) + + def _draw_info_panel(self): + """Draw information panel.""" + panel = mcrfpy.Frame(pos=(50, 545), size=(700, 45)) + panel.fill_color = mcrfpy.Color(20, 20, 35) + panel.outline = 1 + panel.outline_color = mcrfpy.Color(60, 60, 100) + self.ui.append(panel) + + # Time display + self.time_label = mcrfpy.Caption(text="Turn: 0", pos=(60, 555)) + self.time_label.fill_color = mcrfpy.Color(255, 255, 255) + self.ui.append(self.time_label) + + # Status display + self.status_label = mcrfpy.Caption(text="Status: Approaching planet", pos=(180, 555)) + self.status_label.fill_color = mcrfpy.Color(100, 200, 255) + self.ui.append(self.status_label) + + # Distance display + self.dist_label = mcrfpy.Caption(text="Distance to target: ---", pos=(450, 555)) + self.dist_label.fill_color = mcrfpy.Color(150, 150, 150) + self.ui.append(self.dist_label) + + def _tick(self, runtime): + """Advance one turn.""" + self.current_time += 1 + self.time_label.text = f"Turn: {self.current_time}" + + # Update ship based on state + if self.ship_state == "approach": + self._update_approach() + elif self.ship_state == "orbiting": + self._update_orbiting() + elif self.ship_state == "exiting": + self._update_exiting() + elif self.ship_state == "traveling": + self._update_traveling() + elif self.ship_state == "arrived": + pass # Done! + + # Update distance display + dist = distance(self.ship_pos, self.ship_target) + self.dist_label.text = f"Distance to target: {dist:.1f}" + + # Update visuals + self._draw_dynamic() + + def _update_approach(self): + """Move ship toward planet's predicted position.""" + # Find where planet will be when we can intercept + intercept_time, intercept_pos = self._find_intercept() + + if intercept_pos: + # Move toward intercept point + dx = intercept_pos[0] - self.ship_pos[0] + dy = intercept_pos[1] - self.ship_pos[1] + dist = math.sqrt(dx*dx + dy*dy) + + if dist <= self.ship_speed: + # Arrived at orbit - enter orbit + self.ship_pos = list(intercept_pos) + planet_pos = self._get_planet_pos(self.current_time) + self.ship_orbit_angle = angle_between(planet_pos, self.ship_pos) + self.ship_state = "orbiting" + self.status_label.text = "Status: In orbit (repositioning FREE)" + self.status_label.fill_color = mcrfpy.Color(255, 255, 100) + else: + # Move toward intercept + self.ship_pos[0] += (dx / dist) * self.ship_speed + self.ship_pos[1] += (dy / dist) * self.ship_speed + self.status_label.text = f"Status: Approaching intercept (T+{intercept_time - self.current_time})" + else: + # Can't find intercept, go direct + self.ship_state = "traveling" + + def _update_orbiting(self): + """Reposition on orbit toward optimal exit.""" + planet_pos = self._get_planet_pos(self.current_time) + + # Calculate optimal exit angle (toward target) + exit_angle = angle_between(planet_pos, self.ship_target) + + # Move along orbit toward exit angle (this is FREE movement) + angle_diff = exit_angle - self.ship_orbit_angle + if angle_diff > 180: + angle_diff -= 360 + elif angle_diff < -180: + angle_diff += 360 + + # Move up to 45 degrees per turn along orbit (arbitrary limit for demo) + move_angle = max(-45, min(45, angle_diff)) + self.ship_orbit_angle = normalize_angle(self.ship_orbit_angle + move_angle) + + # Update ship position to new orbital position + self.ship_pos[0] = planet_pos[0] + self.planet.orbit_ring_radius * math.cos(math.radians(self.ship_orbit_angle)) + self.ship_pos[1] = planet_pos[1] + self.planet.orbit_ring_radius * math.sin(math.radians(self.ship_orbit_angle)) + + # Check if we're at optimal exit + if abs(angle_diff) < 10: + self.ship_state = "exiting" + self.status_label.text = "Status: Exiting orbit toward target" + self.status_label.fill_color = mcrfpy.Color(100, 255, 100) + + def _update_exiting(self): + """Exit orbit and head toward target.""" + # Just transition to traveling + self.ship_state = "traveling" + + def _update_traveling(self): + """Travel directly toward target.""" + dx = self.ship_target[0] - self.ship_pos[0] + dy = self.ship_target[1] - self.ship_pos[1] + dist = math.sqrt(dx*dx + dy*dy) + + if dist <= self.ship_speed: + # Arrived! + self.ship_pos = list(self.ship_target) + self.ship_state = "arrived" + self.status_label.text = "Status: ARRIVED!" + self.status_label.fill_color = mcrfpy.Color(100, 255, 100) + else: + # Move toward target + self.ship_pos[0] += (dx / dist) * self.ship_speed + self.ship_pos[1] += (dy / dist) * self.ship_speed + self.status_label.text = f"Status: Traveling to target ({dist:.0f} units)" diff --git a/tests/geometry_demo/screens/pathfinding_static_demo.py b/tests/geometry_demo/screens/pathfinding_static_demo.py new file mode 100644 index 0000000..ca7c66f --- /dev/null +++ b/tests/geometry_demo/screens/pathfinding_static_demo.py @@ -0,0 +1,292 @@ +"""Static pathfinding demonstration with planets and orbit rings.""" +import mcrfpy +import math +from .base import (GeometryDemoScreen, OrbitalBody, bresenham_circle, filled_circle, + angle_between, distance, point_on_circle, is_viable_waypoint, + nearest_orbit_entry, optimal_exit_heading) + + +class PathfindingStaticDemo(GeometryDemoScreen): + """Demonstrate optimal path through a static solar system.""" + + name = "Static Pathfinding" + description = "Optimal path using orbital slingshots" + + def setup(self): + self.add_title("Pathfinding Through Orbital Bodies") + self.add_description("Using free orbital movement to optimize travel paths") + + # Create a scenario with multiple planets + # Ship needs to go from bottom-left to top-right + # Optimal path uses planetary orbits as "free repositioning stations" + + self.cell_size = 8 + self.offset_x = 50 + self.offset_y = 100 + + # Background + bg = mcrfpy.Frame(pos=(30, 80), size=(740, 480)) + bg.fill_color = mcrfpy.Color(5, 5, 15) + bg.outline = 1 + bg.outline_color = mcrfpy.Color(40, 40, 80) + self.ui.append(bg) + + # Define planets (center_x, center_y, surface_radius, orbit_radius, name) + self.planets = [ + (20, 45, 8, 14, "Alpha"), + (55, 25, 5, 10, "Beta"), + (70, 50, 6, 12, "Gamma"), + ] + + # Ship start and end + self.ship_start = (5, 55) + self.ship_end = (85, 10) + + # Draw grid reference (faint) + self._draw_grid_reference() + + # Draw planets with surfaces and orbit rings + for px, py, sr, orbit_r, name in self.planets: + self._draw_planet(px, py, sr, orbit_r, name) + + # Calculate and draw optimal path + self._draw_optimal_path() + + # Draw ship and target + self._draw_ship_and_target() + + # Legend + self._draw_legend() + + def _to_screen(self, gx, gy): + """Convert grid coords to screen coords.""" + return (self.offset_x + gx * self.cell_size, + self.offset_y + gy * self.cell_size) + + def _draw_grid_reference(self): + """Draw faint grid lines for reference.""" + for i in range(0, 91, 10): + # Vertical lines + x = self.offset_x + i * self.cell_size + line = mcrfpy.Line( + start=(x, self.offset_y), + end=(x, self.offset_y + 60 * self.cell_size), + color=mcrfpy.Color(30, 30, 50), + thickness=1 + ) + self.ui.append(line) + + for i in range(0, 61, 10): + # Horizontal lines + y = self.offset_y + i * self.cell_size + line = mcrfpy.Line( + start=(self.offset_x, y), + end=(self.offset_x + 90 * self.cell_size, y), + color=mcrfpy.Color(30, 30, 50), + thickness=1 + ) + self.ui.append(line) + + def _draw_planet(self, cx, cy, surface_r, orbit_r, name): + """Draw a planet with surface and orbit ring.""" + sx, sy = self._to_screen(cx, cy) + + # Orbit ring (using mcrfpy.Circle for smooth rendering) + orbit = mcrfpy.Circle( + center=(sx, sy), + radius=orbit_r * self.cell_size, + fill_color=mcrfpy.Color(0, 0, 0, 0), + outline_color=mcrfpy.Color(50, 150, 50, 150), + outline=2 + ) + self.ui.append(orbit) + + # Also draw Bresenham orbit cells for accuracy demo + orbit_cells = bresenham_circle((cx, cy), orbit_r) + for gx, gy in orbit_cells: + px, py = self._to_screen(gx, gy) + cell = mcrfpy.Frame( + pos=(px, py), + size=(self.cell_size - 1, self.cell_size - 1) + ) + cell.fill_color = mcrfpy.Color(40, 100, 40, 100) + self.ui.append(cell) + + # Planet surface (filled circle) + surface_cells = filled_circle((cx, cy), surface_r) + for gx, gy in surface_cells: + px, py = self._to_screen(gx, gy) + dist = math.sqrt((gx - cx)**2 + (gy - cy)**2) + intensity = int(180 * (1 - dist / (surface_r + 1))) + cell = mcrfpy.Frame( + pos=(px, py), + size=(self.cell_size - 1, self.cell_size - 1) + ) + cell.fill_color = mcrfpy.Color(60 + intensity, 80 + intensity//2, 150) + self.ui.append(cell) + + # Planet label + self.add_label(name, sx - 15, sy - surface_r * self.cell_size - 15, (150, 150, 200)) + + def _draw_optimal_path(self): + """Calculate and draw the optimal path using orbital waypoints.""" + # The optimal path: + # 1. Ship starts at (5, 55) + # 2. Direct line to Alpha's orbit entry + # 3. Free arc around Alpha to optimal exit + # 4. Direct line to Gamma's orbit entry + # 5. Free arc around Gamma to optimal exit + # 6. Direct line to target (85, 10) + + path_segments = [] + + # Current position + current = self.ship_start + + # For this demo, manually define the path through Alpha and Gamma + # (In a real implementation, this would be computed by the pathfinder) + + # Planet Alpha (20, 45, orbit_r=14) + alpha_center = (20, 45) + alpha_orbit = 14 + + # Entry to Alpha + entry_angle_alpha = angle_between(alpha_center, current) + entry_alpha = point_on_circle(alpha_center, alpha_orbit, entry_angle_alpha) + + # Draw line: start -> Alpha entry + self._draw_path_line(current, entry_alpha, (100, 200, 255)) + current = entry_alpha + + # Exit from Alpha toward Gamma + gamma_center = (70, 50) + exit_angle_alpha = angle_between(alpha_center, gamma_center) + exit_alpha = point_on_circle(alpha_center, alpha_orbit, exit_angle_alpha) + + # Draw arc on Alpha's orbit + self._draw_orbit_arc(alpha_center, alpha_orbit, entry_angle_alpha, exit_angle_alpha) + current = exit_alpha + + # Planet Gamma (70, 50, orbit_r=12) + gamma_orbit = 12 + + # Entry to Gamma + entry_angle_gamma = angle_between(gamma_center, current) + entry_gamma = point_on_circle(gamma_center, gamma_orbit, entry_angle_gamma) + + # Draw line: Alpha exit -> Gamma entry + self._draw_path_line(current, entry_gamma, (100, 200, 255)) + current = entry_gamma + + # Exit from Gamma toward target + exit_angle_gamma = angle_between(gamma_center, self.ship_end) + exit_gamma = point_on_circle(gamma_center, gamma_orbit, exit_angle_gamma) + + # Draw arc on Gamma's orbit + self._draw_orbit_arc(gamma_center, gamma_orbit, entry_angle_gamma, exit_angle_gamma) + current = exit_gamma + + # Final segment to target + self._draw_path_line(current, self.ship_end, (100, 200, 255)) + + # For comparison, draw direct path (inefficient) + direct_start = self._to_screen(*self.ship_start) + direct_end = self._to_screen(*self.ship_end) + direct_line = mcrfpy.Line( + start=direct_start, end=direct_end, + color=mcrfpy.Color(255, 100, 100, 80), + thickness=1 + ) + self.ui.append(direct_line) + + def _draw_path_line(self, p1, p2, color): + """Draw a path segment line.""" + s1 = self._to_screen(p1[0], p1[1]) + s2 = self._to_screen(p2[0], p2[1]) + line = mcrfpy.Line( + start=s1, end=s2, + color=mcrfpy.Color(*color), + thickness=3 + ) + self.ui.append(line) + + def _draw_orbit_arc(self, center, radius, start_angle, end_angle): + """Draw an arc showing orbital movement (free movement).""" + sx, sy = self._to_screen(center[0], center[1]) + + # Normalize angles for drawing + # Screen coordinates have Y inverted, so negate angles + arc = mcrfpy.Arc( + center=(sx, sy), + radius=radius * self.cell_size, + start_angle=-start_angle, + end_angle=-end_angle, + color=mcrfpy.Color(255, 255, 100), + thickness=4 + ) + self.ui.append(arc) + + def _draw_ship_and_target(self): + """Draw ship start and target end positions.""" + # Ship + ship_x, ship_y = self._to_screen(*self.ship_start) + ship = mcrfpy.Circle( + center=(ship_x + self.cell_size//2, ship_y + self.cell_size//2), + radius=10, + fill_color=mcrfpy.Color(255, 200, 100), + outline_color=mcrfpy.Color(255, 255, 200), + outline=2 + ) + self.ui.append(ship) + self.add_label("SHIP", ship_x - 10, ship_y + 20, (255, 200, 100)) + + # Target + target_x, target_y = self._to_screen(*self.ship_end) + target = mcrfpy.Circle( + center=(target_x + self.cell_size//2, target_y + self.cell_size//2), + radius=10, + fill_color=mcrfpy.Color(255, 100, 100), + outline_color=mcrfpy.Color(255, 200, 200), + outline=2 + ) + self.ui.append(target) + self.add_label("TARGET", target_x - 15, target_y + 20, (255, 100, 100)) + + def _draw_legend(self): + """Draw legend explaining the visualization.""" + leg_x = 50 + leg_y = 520 + + # Blue line = movement cost + line1 = mcrfpy.Line( + start=(leg_x, leg_y + 10), end=(leg_x + 30, leg_y + 10), + color=mcrfpy.Color(100, 200, 255), + thickness=3 + ) + self.ui.append(line1) + self.add_label("Impulse movement (costs energy)", leg_x + 40, leg_y + 3, (150, 150, 150)) + + # Yellow arc = free movement + arc1 = mcrfpy.Arc( + center=(leg_x + 15, leg_y + 45), radius=15, + start_angle=0, end_angle=180, + color=mcrfpy.Color(255, 255, 100), + thickness=3 + ) + self.ui.append(arc1) + self.add_label("Orbital movement (FREE)", leg_x + 40, leg_y + 35, (255, 255, 100)) + + # Red line = direct (inefficient) + line2 = mcrfpy.Line( + start=(leg_x + 300, leg_y + 10), end=(leg_x + 330, leg_y + 10), + color=mcrfpy.Color(255, 100, 100, 80), + thickness=1 + ) + self.ui.append(line2) + self.add_label("Direct path (for comparison)", leg_x + 340, leg_y + 3, (150, 150, 150)) + + # Green cells = orbit ring + cell1 = mcrfpy.Frame(pos=(leg_x + 300, leg_y + 35), size=(15, 15)) + cell1.fill_color = mcrfpy.Color(40, 100, 40) + self.ui.append(cell1) + self.add_label("Orbit ring cells (valid ship positions)", leg_x + 320, leg_y + 35, (150, 150, 150)) diff --git a/tests/geometry_demo/screens/solar_system_demo.py b/tests/geometry_demo/screens/solar_system_demo.py new file mode 100644 index 0000000..8210851 --- /dev/null +++ b/tests/geometry_demo/screens/solar_system_demo.py @@ -0,0 +1,274 @@ +"""Animated solar system demonstration.""" +import mcrfpy +import math +from .base import (GeometryDemoScreen, OrbitalBody, create_solar_system, + create_planet, create_moon, point_on_circle) + + +class SolarSystemDemo(GeometryDemoScreen): + """Demonstrate animated orbital mechanics with timer-based updates.""" + + name = "Solar System Animation" + description = "Planets orbiting with discrete time steps" + + def setup(self): + self.add_title("Animated Solar System") + self.add_description("Planets snap to grid positions as time advances (1 tick = 1 turn)") + + # Screen layout + self.center_x = 400 + self.center_y = 320 + self.scale = 1.5 # Pixels per grid unit + + # Background + bg = mcrfpy.Frame(pos=(50, 80), size=(700, 480)) + bg.fill_color = mcrfpy.Color(5, 5, 15) + bg.outline = 1 + bg.outline_color = mcrfpy.Color(40, 40, 80) + self.ui.append(bg) + + # Create the solar system using geometry module + self.star = create_solar_system( + grid_width=200, grid_height=200, + star_radius=15, star_orbit_radius=25 + ) + + # Create planets with different orbital speeds + self.planet1 = create_planet( + name="Mercury", + star=self.star, + orbital_radius=60, + surface_radius=5, + orbit_ring_radius=12, + angular_velocity=12, # Fast orbit + initial_angle=0 + ) + + self.planet2 = create_planet( + name="Venus", + star=self.star, + orbital_radius=100, + surface_radius=8, + orbit_ring_radius=16, + angular_velocity=7, # Medium orbit + initial_angle=120 + ) + + self.planet3 = create_planet( + name="Earth", + star=self.star, + orbital_radius=150, + surface_radius=10, + orbit_ring_radius=20, + angular_velocity=4, # Slow orbit + initial_angle=240 + ) + + # Moon orbiting Earth + self.moon = create_moon( + name="Luna", + planet=self.planet3, + orbital_radius=30, + surface_radius=3, + orbit_ring_radius=8, + angular_velocity=15, # Faster than Earth + initial_angle=45 + ) + + self.planets = [self.planet1, self.planet2, self.planet3] + self.moons = [self.moon] + + # Current time step + self.current_time = 0 + + # Store UI elements for updating + self.planet_circles = {} + self.orbit_rings = {} + self.moon_circles = {} + + # Draw static elements (star, orbit paths) + self._draw_static_elements() + + # Draw initial planet positions + self._draw_planets() + + # Time display + self.time_label = mcrfpy.Caption(text="Turn: 0", pos=(60, 530)) + self.time_label.fill_color = mcrfpy.Color(255, 255, 255) + self.ui.append(self.time_label) + + # Instructions + self.add_label("Time advances automatically every second", 200, 530, (150, 150, 150)) + + # Start the animation timer + self.add_timer("solar_tick", self._tick, 1000) # 1 second per turn + + def _to_screen(self, grid_pos): + """Convert grid position to screen coordinates.""" + gx, gy = grid_pos + # Center on screen, with star at center + star_pos = self.star.base_position + dx = (gx - star_pos[0]) * self.scale + dy = (gy - star_pos[1]) * self.scale + return (self.center_x + dx, self.center_y + dy) + + def _draw_static_elements(self): + """Draw elements that don't move (star, orbital paths).""" + star_screen = self._to_screen(self.star.base_position) + + # Star + star_circle = mcrfpy.Circle( + center=star_screen, + radius=self.star.surface_radius * self.scale, + fill_color=mcrfpy.Color(255, 220, 100), + outline_color=mcrfpy.Color(255, 180, 50), + outline=3 + ) + self.ui.append(star_circle) + + # Star glow effect + for i in range(3): + glow = mcrfpy.Circle( + center=star_screen, + radius=(self.star.surface_radius + 5 + i * 8) * self.scale, + fill_color=mcrfpy.Color(0, 0, 0, 0), + outline_color=mcrfpy.Color(255, 200, 50, 50 - i * 15), + outline=2 + ) + self.ui.append(glow) + + # Orbital paths (static ellipses showing where planets travel) + for planet in self.planets: + path = mcrfpy.Circle( + center=star_screen, + radius=planet.orbital_radius * self.scale, + fill_color=mcrfpy.Color(0, 0, 0, 0), + outline_color=mcrfpy.Color(40, 40, 60), + outline=1 + ) + self.ui.append(path) + + # Star label + self.add_label("Star", star_screen[0] - 15, star_screen[1] + self.star.surface_radius * self.scale + 5, + (255, 220, 100)) + + def _draw_planets(self): + """Draw planets at their current positions.""" + for planet in self.planets: + self._draw_planet(planet) + + for moon in self.moons: + self._draw_moon(moon) + + def _draw_planet(self, planet): + """Draw a single planet.""" + # Get grid position at current time + grid_pos = planet.grid_position_at_time(self.current_time) + screen_pos = self._to_screen(grid_pos) + + # Color based on planet + colors = { + "Mercury": (180, 180, 180), + "Venus": (255, 200, 150), + "Earth": (100, 150, 255), + } + color = colors.get(planet.name, (150, 150, 150)) + + # Planet surface + planet_circle = mcrfpy.Circle( + center=screen_pos, + radius=planet.surface_radius * self.scale, + fill_color=mcrfpy.Color(*color), + outline_color=mcrfpy.Color(255, 255, 255, 100), + outline=1 + ) + self.ui.append(planet_circle) + self.planet_circles[planet.name] = planet_circle + + # Orbit ring around planet + orbit_ring = mcrfpy.Circle( + center=screen_pos, + radius=planet.orbit_ring_radius * self.scale, + fill_color=mcrfpy.Color(0, 0, 0, 0), + outline_color=mcrfpy.Color(50, 150, 50, 100), + outline=1 + ) + self.ui.append(orbit_ring) + self.orbit_rings[planet.name] = orbit_ring + + # Planet label + label = mcrfpy.Caption( + text=planet.name, + pos=(screen_pos[0] - 20, screen_pos[1] - planet.surface_radius * self.scale - 15) + ) + label.fill_color = mcrfpy.Color(*color) + self.ui.append(label) + # Store label for updating + if not hasattr(self, 'planet_labels'): + self.planet_labels = {} + self.planet_labels[planet.name] = label + + def _draw_moon(self, moon): + """Draw a moon.""" + grid_pos = moon.grid_position_at_time(self.current_time) + screen_pos = self._to_screen(grid_pos) + + moon_circle = mcrfpy.Circle( + center=screen_pos, + radius=moon.surface_radius * self.scale, + fill_color=mcrfpy.Color(200, 200, 200), + outline_color=mcrfpy.Color(255, 255, 255, 100), + outline=1 + ) + self.ui.append(moon_circle) + self.moon_circles[moon.name] = moon_circle + + # Moon's orbit path around Earth + parent_pos = self._to_screen(moon.parent.grid_position_at_time(self.current_time)) + moon_path = mcrfpy.Circle( + center=parent_pos, + radius=moon.orbital_radius * self.scale, + fill_color=mcrfpy.Color(0, 0, 0, 0), + outline_color=mcrfpy.Color(60, 60, 80), + outline=1 + ) + self.ui.append(moon_path) + self.orbit_rings[moon.name + "_path"] = moon_path + + def _tick(self, runtime): + """Advance time by one turn and update planet positions.""" + self.current_time += 1 + + # Update time display + self.time_label.text = f"Turn: {self.current_time}" + + # Update planet positions + for planet in self.planets: + grid_pos = planet.grid_position_at_time(self.current_time) + screen_pos = self._to_screen(grid_pos) + + # Update circle position + if planet.name in self.planet_circles: + self.planet_circles[planet.name].center = screen_pos + self.orbit_rings[planet.name].center = screen_pos + + # Update label position + if hasattr(self, 'planet_labels') and planet.name in self.planet_labels: + self.planet_labels[planet.name].pos = ( + screen_pos[0] - 20, + screen_pos[1] - planet.surface_radius * self.scale - 15 + ) + + # Update moon positions + for moon in self.moons: + grid_pos = moon.grid_position_at_time(self.current_time) + screen_pos = self._to_screen(grid_pos) + + if moon.name in self.moon_circles: + self.moon_circles[moon.name].center = screen_pos + + # Update moon's orbital path center + parent_pos = self._to_screen(moon.parent.grid_position_at_time(self.current_time)) + path_key = moon.name + "_path" + if path_key in self.orbit_rings: + self.orbit_rings[path_key].center = parent_pos diff --git a/tests/geometry_demo/screenshots/geo_00_bresenham_algorithms.png b/tests/geometry_demo/screenshots/geo_00_bresenham_algorithms.png new file mode 100644 index 0000000000000000000000000000000000000000..64b43e5d5e8ca3aec4e68c40f8babb32a159b557 GIT binary patch literal 63717 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

J{Kwz{^*JM4U2w34VXOz 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 Me;Kcc)wl5eACjX@rT_o{ literal 0 HcmV?d00001 diff --git a/tests/geometry_demo/screenshots/geo_01_angle_calculations.png b/tests/geometry_demo/screenshots/geo_01_angle_calculations.png new file mode 100644 index 0000000000000000000000000000000000000000..5d1550dfe13659dc9999f8eb5d6e6ba9a4950957 GIT binary patch literal 92371 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

hV896O-NejcY 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 literal 0 HcmV?d00001 diff --git a/tests/geometry_demo/screenshots/geo_02_static_pathfinding.png b/tests/geometry_demo/screenshots/geo_02_static_pathfinding.png new file mode 100644 index 0000000000000000000000000000000000000000..833bef1ef400c23591d9ffd58422c0023b99dc7d GIT binary patch literal 67505 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 %k*`{)dC{;T1TVInF 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 literal 0 HcmV?d00001 diff --git a/tests/geometry_demo/screenshots/geo_03_solar_system_animation.png b/tests/geometry_demo/screenshots/geo_03_solar_system_animation.png new file mode 100644 index 0000000000000000000000000000000000000000..fb74ab46e21b52c2c590e8091eae6f418d824da2 GIT binary patch literal 54605 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 Date: Wed, 26 Nov 2025 04:53:31 -0500 Subject: [PATCH 3/4] cleanup: remove partial tutorial --- .../PART_6_8_IMPLEMENTATION_PLAN.md | 393 --------- roguelike_tutorial/README_PART_01.md | 100 --- roguelike_tutorial/README_PART_02.md | 82 -- roguelike_tutorial/README_PART_03.md | 87 -- roguelike_tutorial/README_PART_04.md | 131 --- roguelike_tutorial/README_PART_05.md | 169 ---- roguelike_tutorial/README_PART_06.md | 187 ----- roguelike_tutorial/README_PART_07.md | 204 ----- roguelike_tutorial/README_PART_08.md | 297 ------- roguelike_tutorial/_generated_part_5.py | 625 --------------- .../Part_0/README.md | 253 ------ .../Part_0/code/game.py | 33 - .../Part_0/code/setup_test.py | 55 -- .../Part_1/README.md | 457 ----------- .../Part_1/code/game.py | 162 ---- .../Part_2/README.md | 562 ------------- .../Part_2/code/game.py | 217 ----- .../Part_3/README.md | 548 ------------- .../Part_3/code/game.py | 312 -------- .../Part_4/README.md | 520 ------------ .../Part_4/code/game.py | 334 -------- .../Part_5/README.md | 570 -------------- .../Part_5/code/game.py | 388 --------- .../Part_6/README.md | 743 ------------------ .../Part_6/code/game.py | 568 ------------- roguelike_tutorial/part_0.py | 80 -- roguelike_tutorial/part_1.py | 116 --- roguelike_tutorial/part_1b.py | 117 --- roguelike_tutorial/part_2-naive.py | 149 ---- roguelike_tutorial/part_2-onemovequeued.py | 241 ------ roguelike_tutorial/part_2.py | 149 ---- roguelike_tutorial/part_3.py | 313 -------- roguelike_tutorial/part_4.py | 366 --------- roguelike_tutorial/part_5.py | 363 --------- roguelike_tutorial/part_6.py | 645 --------------- roguelike_tutorial/part_6a.py | 582 -------------- roguelike_tutorial/tutorial2.png | Bin 5741 -> 0 bytes roguelike_tutorial/tutorial_hero.png | Bin 16742 -> 0 bytes 38 files changed, 11118 deletions(-) delete mode 100644 roguelike_tutorial/PART_6_8_IMPLEMENTATION_PLAN.md delete mode 100644 roguelike_tutorial/README_PART_01.md delete mode 100644 roguelike_tutorial/README_PART_02.md delete mode 100644 roguelike_tutorial/README_PART_03.md delete mode 100644 roguelike_tutorial/README_PART_04.md delete mode 100644 roguelike_tutorial/README_PART_05.md delete mode 100644 roguelike_tutorial/README_PART_06.md delete mode 100644 roguelike_tutorial/README_PART_07.md delete mode 100644 roguelike_tutorial/README_PART_08.md delete mode 100644 roguelike_tutorial/_generated_part_5.py delete mode 100644 roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_0/README.md delete mode 100644 roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_0/code/game.py delete mode 100644 roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_0/code/setup_test.py delete mode 100644 roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_1/README.md delete mode 100644 roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_1/code/game.py delete mode 100644 roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_2/README.md delete mode 100644 roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_2/code/game.py delete mode 100644 roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_3/README.md delete mode 100644 roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_3/code/game.py delete mode 100644 roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_4/README.md delete mode 100644 roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_4/code/game.py delete mode 100644 roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_5/README.md delete mode 100644 roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_5/code/game.py delete mode 100644 roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_6/README.md delete mode 100644 roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_6/code/game.py delete mode 100644 roguelike_tutorial/part_0.py delete mode 100644 roguelike_tutorial/part_1.py delete mode 100644 roguelike_tutorial/part_1b.py delete mode 100644 roguelike_tutorial/part_2-naive.py delete mode 100644 roguelike_tutorial/part_2-onemovequeued.py delete mode 100644 roguelike_tutorial/part_2.py delete mode 100644 roguelike_tutorial/part_3.py delete mode 100644 roguelike_tutorial/part_4.py delete mode 100644 roguelike_tutorial/part_5.py delete mode 100644 roguelike_tutorial/part_6.py delete mode 100644 roguelike_tutorial/part_6a.py delete mode 100644 roguelike_tutorial/tutorial2.png delete mode 100644 roguelike_tutorial/tutorial_hero.png diff --git a/roguelike_tutorial/PART_6_8_IMPLEMENTATION_PLAN.md b/roguelike_tutorial/PART_6_8_IMPLEMENTATION_PLAN.md deleted file mode 100644 index 033b8e2..0000000 --- a/roguelike_tutorial/PART_6_8_IMPLEMENTATION_PLAN.md +++ /dev/null @@ -1,393 +0,0 @@ -# McRogueFace Tutorial Parts 6-8: Implementation Plan - -**Date**: Monday, July 28, 2025 -**Target Delivery**: Tuesday, July 29, 2025 - -## Executive Summary - -This document outlines the implementation plan for Parts 6-8 of the McRogueFace roguelike tutorial, adapting the libtcod Python tutorial to McRogueFace's architecture. The key discovery is that Python classes can successfully inherit from `mcrfpy.Entity` and store custom attributes, enabling a clean, Pythonic implementation. - -## Key Architectural Insights - -### Entity Inheritance Works! -```python -class GameEntity(mcrfpy.Entity): - def __init__(self, x, y, **kwargs): - super().__init__(x=x, y=y, **kwargs) - # Custom attributes work perfectly! - self.hp = 10 - self.inventory = [] - self.any_attribute = "works" -``` - -This completely changes our approach from wrapper patterns to direct inheritance. - ---- - -## Part 6: Doing (and Taking) Some Damage - -### Overview -Implement a combat system with HP tracking, damage calculation, and death mechanics using entity inheritance. - -### Core Components - -#### 1. CombatEntity Base Class -```python -class CombatEntity(mcrfpy.Entity): - """Base class for entities that can fight and take damage""" - def __init__(self, x, y, hp=10, defense=0, power=1, **kwargs): - super().__init__(x=x, y=y, **kwargs) - # Combat stats as direct attributes - self.hp = hp - self.max_hp = hp - self.defense = defense - self.power = power - self.is_alive = True - self.blocks_movement = True - - def calculate_damage(self, attacker): - """Simple damage formula: power - defense""" - return max(0, attacker.power - self.defense) - - def take_damage(self, damage, attacker=None): - """Apply damage and handle death""" - self.hp = max(0, self.hp - damage) - - if self.hp == 0 and self.is_alive: - self.is_alive = False - self.on_death(attacker) - - def on_death(self, killer=None): - """Handle death - override in subclasses""" - self.sprite_index = self.sprite_index + 180 # Corpse offset - self.blocks_movement = False -``` - -#### 2. Entity Types -```python -class PlayerEntity(CombatEntity): - """Player: HP=30, Defense=2, Power=5""" - def __init__(self, x, y, **kwargs): - kwargs['sprite_index'] = 64 # Hero sprite - super().__init__(x=x, y=y, hp=30, defense=2, power=5, **kwargs) - self.entity_type = "player" - -class OrcEntity(CombatEntity): - """Orc: HP=10, Defense=0, Power=3""" - def __init__(self, x, y, **kwargs): - kwargs['sprite_index'] = 65 # Orc sprite - super().__init__(x=x, y=y, hp=10, defense=0, power=3, **kwargs) - self.entity_type = "orc" - -class TrollEntity(CombatEntity): - """Troll: HP=16, Defense=1, Power=4""" - def __init__(self, x, y, **kwargs): - kwargs['sprite_index'] = 66 # Troll sprite - super().__init__(x=x, y=y, hp=16, defense=1, power=4, **kwargs) - self.entity_type = "troll" -``` - -#### 3. Combat Integration -- Extend `on_bump()` from Part 5 to include combat -- Add attack animations (quick bump toward target) -- Console messages initially, UI messages in Part 7 -- Death changes sprite and removes blocking - -### Key Differences from Original Tutorial -- No Fighter component - stats are direct attributes -- No AI component - behavior in entity methods -- Integrated animations for visual feedback -- Simpler architecture overall - ---- - -## Part 7: Creating the Interface - -### Overview -Add visual UI elements including health bars, message logs, and colored feedback for combat events. - -### Core Components - -#### 1. Health Bar -```python -class HealthBar: - """Health bar that reads entity HP directly""" - def __init__(self, entity, pos=(10, 740), size=(200, 20)): - self.entity = entity # Direct reference! - - # Background (dark red) - self.bg = mcrfpy.Frame(pos=pos, size=size) - self.bg.fill_color = mcrfpy.Color(64, 16, 16) - - # Foreground (green) - self.fg = mcrfpy.Frame(pos=pos, size=size) - self.fg.fill_color = mcrfpy.Color(0, 96, 0) - - # Text overlay - self.text = mcrfpy.Caption( - pos=(pos[0] + 5, pos[1] + 2), - text=f"HP: {entity.hp}/{entity.max_hp}" - ) - - def update(self): - """Update based on entity's current HP""" - ratio = self.entity.hp / self.entity.max_hp - self.fg.w = int(self.bg.w * ratio) - self.text.text = f"HP: {self.entity.hp}/{self.entity.max_hp}" - - # Color changes at low health - if ratio < 0.25: - self.fg.fill_color = mcrfpy.Color(196, 16, 16) # Red - elif ratio < 0.5: - self.fg.fill_color = mcrfpy.Color(196, 196, 16) # Yellow -``` - -#### 2. Message Log -```python -class MessageLog: - """Scrolling message log for combat feedback""" - def __init__(self, pos=(10, 600), size=(400, 120), max_messages=6): - self.frame = mcrfpy.Frame(pos=pos, size=size) - self.messages = [] # List of (text, color) tuples - self.captions = [] # Pre-allocated Caption pool - - def add_message(self, text, color=None): - """Add message with optional color""" - # Handle duplicate detection (x2, x3, etc.) - # Update caption display -``` - -#### 3. Color System -```python -class Colors: - # Combat colors - PLAYER_ATTACK = mcrfpy.Color(224, 224, 224) - ENEMY_ATTACK = mcrfpy.Color(255, 192, 192) - PLAYER_DEATH = mcrfpy.Color(255, 48, 48) - ENEMY_DEATH = mcrfpy.Color(255, 160, 48) - HEALTH_RECOVERED = mcrfpy.Color(0, 255, 0) -``` - -### UI Layout -- Health bar at bottom of screen -- Message log above health bar -- Direct binding to entity attributes -- Real-time updates during gameplay - ---- - -## Part 8: Items and Inventory - -### Overview -Implement items as entities, inventory management, and a hotbar-style UI for item usage. - -### Core Components - -#### 1. Item Entities -```python -class ItemEntity(mcrfpy.Entity): - """Base class for pickupable items""" - def __init__(self, x, y, name, sprite, **kwargs): - kwargs['sprite_index'] = sprite - super().__init__(x=x, y=y, **kwargs) - self.item_name = name - self.blocks_movement = False - self.item_type = "generic" - -class HealingPotion(ItemEntity): - """Consumable healing item""" - def __init__(self, x, y, healing_amount=4): - super().__init__(x, y, "Healing Potion", sprite=33) - self.healing_amount = healing_amount - self.item_type = "consumable" - - def use(self, user): - """Use the potion - returns (success, message)""" - if hasattr(user, 'hp'): - healed = min(self.healing_amount, user.max_hp - user.hp) - if healed > 0: - user.hp += healed - return True, f"You heal {healed} HP!" -``` - -#### 2. Inventory System -```python -class InventoryMixin: - """Mixin for entities with inventory""" - def __init__(self, *args, capacity=10, **kwargs): - super().__init__(*args, **kwargs) - self.inventory = [] - self.inventory_capacity = capacity - - def pickup_item(self, item): - """Pick up an item entity""" - if len(self.inventory) >= self.inventory_capacity: - return False, "Inventory full!" - self.inventory.append(item) - item.die() # Remove from grid - return True, f"Picked up {item.item_name}." -``` - -#### 3. Inventory UI -```python -class InventoryDisplay: - """Hotbar-style inventory display""" - def __init__(self, entity, pos=(200, 700), slots=10): - # Create slot frames and sprites - # Number keys 1-9, 0 for slots - # Highlight selected slot - # Update based on entity.inventory -``` - -### Key Features -- Items exist as entities on the grid -- Direct inventory attribute on player -- Hotkey-based usage (1-9, 0) -- Visual hotbar display -- Item effects (healing, future: damage boost, etc.) - ---- - -## Implementation Timeline - -### Tuesday Morning (Priority 1: Core Systems) -1. **8:00-9:30**: Implement CombatEntity and entity types -2. **9:30-10:30**: Add combat to bump interactions -3. **10:30-11:30**: Basic health display (text or simple bar) -4. **11:30-12:00**: ItemEntity and pickup system - -### Tuesday Afternoon (Priority 2: Integration) -1. **1:00-2:00**: Message log implementation -2. **2:00-3:00**: Full health bar with colors -3. **3:00-4:00**: Inventory UI (hotbar) -4. **4:00-5:00**: Testing and bug fixes - -### Tuesday Evening (Priority 3: Polish) -1. **5:00-6:00**: Combat animations and effects -2. **6:00-7:00**: Sound integration (use CoS splat sounds) -3. **7:00-8:00**: Additional item types -4. **8:00-9:00**: Documentation and cleanup - ---- - -## Testing Strategy - -### Automated Tests -```python -# tests/test_part6_combat.py -- Test damage calculation -- Test death mechanics -- Test combat messages - -# tests/test_part7_ui.py -- Test health bar updates -- Test message log scrolling -- Test color system - -# tests/test_part8_inventory.py -- Test item pickup/drop -- Test inventory capacity -- Test item usage -``` - -### Visual Tests -- Screenshot combat states -- Verify UI element positioning -- Check animation smoothness - ---- - -## File Structure -``` -roguelike_tutorial/ -├── part_6.py # Combat implementation -├── part_7.py # UI enhancements -├── part_8.py # Inventory system -├── combat.py # Shared combat utilities -├── ui_components.py # Reusable UI classes -├── colors.py # Color definitions -└── items.py # Item definitions -``` - ---- - -## Risk Mitigation - -### Potential Issues -1. **Performance**: Many UI updates per frame - - Solution: Update only on state changes - -2. **Entity Collection Bugs**: Known segfault issues - - Solution: Use index-based access when needed - -3. **Animation Timing**: Complex with turn-based combat - - Solution: Queue animations, process sequentially - -### Fallback Options -1. Start with console messages, add UI later -2. Simple health numbers before bars -3. Basic inventory list before hotbar - ---- - -## Success Criteria - -### Part 6 -- [x] Entities can have HP and take damage -- [x] Death changes sprite and walkability -- [x] Combat messages appear -- [x] Player can kill enemies - -### Part 7 -- [x] Health bar shows current/max HP -- [x] Messages appear in scrolling log -- [x] Colors differentiate message types -- [x] UI updates in real-time - -### Part 8 -- [x] Items can be picked up -- [x] Inventory has capacity limit -- [x] Items can be used/consumed -- [x] Hotbar shows inventory items - ---- - -## Notes for Implementation - -1. **Keep It Simple**: Start with minimum viable features -2. **Build Incrementally**: Test each component before integrating -3. **Use Part 5**: Leverage existing entity interaction system -4. **Document Well**: Clear comments for tutorial purposes -5. **Visual Feedback**: McRogueFace excels at animations - use them! - ---- - -## Comparison with Original Tutorial - -### What We Keep -- Same combat formula (power - defense) -- Same entity stats (Player, Orc, Troll) -- Same item types (healing potions to start) -- Same UI elements (health bar, message log) - -### What's Different -- Direct inheritance instead of components -- Integrated animations and visual effects -- Hotbar inventory instead of menu -- Built-in sound support -- Cleaner architecture overall - -### What's Better -- More Pythonic with real inheritance -- Better visual feedback -- Smoother animations -- Simpler to understand -- Leverages McRogueFace's strengths - ---- - -## Conclusion - -This implementation plan leverages McRogueFace's support for Python entity inheritance to create a clean, intuitive tutorial series. By using direct attributes instead of components, we simplify the architecture while maintaining all the functionality of the original tutorial. The addition of animations, sound effects, and rich UI elements showcases McRogueFace's capabilities while keeping the code beginner-friendly. - -The Tuesday delivery timeline is aggressive but achievable by focusing on core functionality first, then integration, then polish. The modular design allows for easy testing and incremental development. \ No newline at end of file diff --git a/roguelike_tutorial/README_PART_01.md b/roguelike_tutorial/README_PART_01.md deleted file mode 100644 index 4d8e156..0000000 --- a/roguelike_tutorial/README_PART_01.md +++ /dev/null @@ -1,100 +0,0 @@ -# Simple TCOD Tutorial Part 1 - Drawing the player sprite and moving it around - -This is Part 1 of the Simple TCOD Tutorial adapted for McRogueFace. It implements the sophisticated, refactored TCOD tutorial approach with professional architecture from day one. - -## Running the Code - -From your tutorial build directory (separate from the engine development build): -```bash -cd simple_tcod_tutorial/build -./mcrogueface scripts/main.py -``` - -Note: The `scripts` folder should be a symlink to your `simple_tcod_tutorial` directory. - -## Architecture Overview - -### Package Structure -``` -simple_tcod_tutorial/ -├── main.py # Entry point - ties everything together -├── game/ # Game package with proper separation -│ ├── __init__.py -│ ├── entity.py # Entity class - all game objects -│ ├── engine.py # Engine class - game coordinator -│ ├── actions.py # Action classes - command pattern -│ └── input_handlers.py # Input handling - extensible system -``` - -### Key Concepts Demonstrated - -1. **Entity-Centric Design** - - Everything in the game is an Entity - - Entities have position, appearance, and behavior - - Designed to scale to items, NPCs, and effects - -2. **Action-Based Command Pattern** - - All player actions are Action objects - - Separates input from game logic - - Enables undo, replay, and AI using same system - -3. **Professional Input Handling** - - BaseEventHandler for different input contexts - - Complete movement key support (arrows, numpad, vi, WASD) - - Ready for menus, targeting, and other modes - -4. **Engine as Coordinator** - - Manages game state without becoming a god object - - Delegates to appropriate systems - - Clean boundaries between systems - -5. **Type Safety** - - Full type annotations throughout - - Forward references with TYPE_CHECKING - - Modern Python best practices - -## Differences from Vanilla McRogueFace Tutorial - -### Removed -- Animation system (instant movement instead) -- Complex UI elements (focus on core mechanics) -- Real-time features (pure turn-based) -- Visual effects (camera following, smooth scrolling) -- Entity color property (sprites handle appearance) - -### Added -- Complete movement key support -- Professional architecture patterns -- Proper package structure -- Type annotations -- Action-based design -- Extensible handler system -- Proper exit handling (Escape/Q actually quits) - -### Adapted -- Grid rendering with proper centering -- Simplified entity system (position + sprite ID) -- Using simple_tutorial.png sprite sheet (12 sprites) -- Floor tiles using ground sprites (indices 1 and 2) -- Direct sprite indices instead of character mapping - -## Learning Objectives - -Students completing Part 1 will understand: -- How to structure a game project professionally -- The value of entity-centric design -- Command pattern for game actions -- Input handling that scales to complex UIs -- Type-driven development in Python -- Architecture that grows without refactoring - -## What's Next - -Part 2 will add: -- The GameMap class for world representation -- Tile-based movement and collision -- Multiple entities in the world -- Basic terrain (walls and floors) -- Rendering order for entities - -The architecture we've built in Part 1 makes these additions natural and painless, demonstrating the value of starting with good patterns. \ No newline at end of file diff --git a/roguelike_tutorial/README_PART_02.md b/roguelike_tutorial/README_PART_02.md deleted file mode 100644 index a34f78f..0000000 --- a/roguelike_tutorial/README_PART_02.md +++ /dev/null @@ -1,82 +0,0 @@ -# Simple TCOD Tutorial Part 2 - The generic Entity, the map, and walls - -This is Part 2 of the Simple TCOD Tutorial adapted for McRogueFace. Building on Part 1's foundation, we now introduce proper world representation and collision detection. - -## Running the Code - -From your tutorial build directory: -```bash -cd simple_tcod_tutorial/build -./mcrogueface scripts/main.py -``` - -## New Architecture Components - -### GameMap Class (`game/game_map.py`) -The GameMap inherits from `mcrfpy.Grid` and adds: -- **Tile Management**: Uses Grid's built-in point system with walkable property -- **Entity Container**: Manages entity lifecycle with `add_entity()` and `remove_entity()` -- **Spatial Queries**: `get_entities_at()`, `get_blocking_entity_at()`, `is_walkable()` -- **Direct Integration**: Leverages Grid's walkable and tilesprite properties - -### Tiles System (`game/tiles.py`) -- **Simple Tile Types**: Using NamedTuple for clean tile definitions -- **Tile Types**: Floor (walkable) and Wall (blocks movement) -- **Grid Integration**: Maps directly to Grid point properties -- **Future-Ready**: Includes transparency for FOV system in Part 4 - -### Entity Placement System -- **Bidirectional References**: Entities know their map, maps track their entities -- **`place()` Method**: Handles all bookkeeping when entities move between maps -- **Lifecycle Management**: Automatic cleanup when entities leave maps - -## Key Changes from Part 1 - -### Engine Updates -- Replaced direct grid management with GameMap -- Engine creates and configures the GameMap -- Player is placed using the new `place()` method - -### Movement System -- MovementAction now checks `is_walkable()` before moving -- Collision detection for both walls and blocking entities -- Clean separation between validation and execution - -### Visual Changes -- Walls rendered as trees (sprite index 3) -- Border of walls around the map edge -- Floor tiles still use alternating pattern - -## Architectural Benefits - -### McRogueFace Integration -- **No NumPy Dependency**: Uses Grid's native tile management -- **Direct Walkability**: Grid points have built-in walkable property -- **Unified System**: Visual and logical tile data in one place - -### Separation of Concerns -- **GameMap**: Knows about tiles and spatial relationships -- **Engine**: Coordinates high-level game state -- **Entity**: Manages its own lifecycle through `place()` -- **Actions**: Validate their own preconditions - -### Extensibility -- Easy to add new tile types -- Simple to implement different map generation -- Ready for FOV, pathfinding, and complex queries -- Entity system scales to items and NPCs - -### Type Safety -- TYPE_CHECKING imports prevent circular dependencies -- Proper type hints throughout -- Forward references maintain clean architecture - -## What's Next - -Part 3 will add: -- Procedural dungeon generation -- Room and corridor creation -- Multiple entities in the world -- Foundation for enemy placement - -The architecture established in Part 2 makes these additions straightforward, demonstrating the value of proper design from the beginning. \ No newline at end of file diff --git a/roguelike_tutorial/README_PART_03.md b/roguelike_tutorial/README_PART_03.md deleted file mode 100644 index 7bd9696..0000000 --- a/roguelike_tutorial/README_PART_03.md +++ /dev/null @@ -1,87 +0,0 @@ -# Simple TCOD Tutorial Part 3 - Generating a dungeon - -This is Part 3 of the Simple TCOD Tutorial adapted for McRogueFace. We now add procedural dungeon generation to create interesting, playable levels. - -## Running the Code - -From your tutorial build directory: -```bash -cd simple_tcod_tutorial/build -./mcrogueface scripts/main.py -``` - -## New Features - -### Procedural Generation Module (`game/procgen.py`) - -This dedicated module demonstrates separation of concerns - dungeon generation logic is kept separate from the game map implementation. - -#### RectangularRoom Class -- **Clean Abstraction**: Represents a room with position and dimensions -- **Utility Properties**: - - `center` - Returns room center for connections - - `inner` - Returns slice objects for efficient carving -- **Intersection Detection**: `intersects()` method prevents overlapping rooms - -#### Tunnel Generation -- **L-Shaped Corridors**: Simple but effective connection method -- **Iterator Pattern**: `tunnel_between()` yields coordinates efficiently -- **Random Variation**: 50/50 chance of horizontal-first vs vertical-first - -#### Dungeon Generation Algorithm -```python -def generate_dungeon(max_rooms, room_min_size, room_max_size, - map_width, map_height, engine) -> GameMap: -``` -- **Simple Algorithm**: Try to place random rooms, reject overlaps -- **Automatic Connection**: Each room connects to the previous one -- **Player Placement**: First room contains the player -- **Entity-Centric**: Uses `player.place()` for proper lifecycle - -## Architecture Benefits - -### Modular Design -- Generation logic separate from GameMap -- Easy to swap algorithms later -- Room class reusable for other features - -### Forward Thinking -- Engine parameter anticipates entity spawning -- Room list available for future features -- Iterator-based tunnel generation is memory efficient - -### Clean Integration -- Works seamlessly with existing entity placement -- Respects GameMap's tile management -- No special cases or hacks needed - -## Visual Changes - -- Map size increased to 80x45 for better dungeons -- Zoom reduced to 1.0 to see more of the map -- Random room layouts each time -- Connected rooms and corridors - -## Algorithm Details - -The generation follows these steps: -1. Start with a map filled with walls -2. Try to place up to `max_rooms` rooms -3. For each room attempt: - - Generate random size and position - - Check for intersections with existing rooms - - If valid, carve out the room - - Connect to previous room (if any) -4. Place player in center of first room - -This simple algorithm creates playable dungeons while being easy to understand and modify. - -## What's Next - -Part 4 will add: -- Field of View (FOV) system -- Explored vs unexplored areas -- Light and dark tile rendering -- Torch radius around player - -The modular dungeon generation makes it easy to add these visual features without touching the generation code. \ No newline at end of file diff --git a/roguelike_tutorial/README_PART_04.md b/roguelike_tutorial/README_PART_04.md deleted file mode 100644 index 8b38e4f..0000000 --- a/roguelike_tutorial/README_PART_04.md +++ /dev/null @@ -1,131 +0,0 @@ -# Part 4: Field of View and Exploration - -## Overview - -Part 4 introduces the Field of View (FOV) system, transforming our fully-visible dungeon into an atmospheric exploration experience. We leverage McRogueFace's built-in FOV capabilities and perspective system for efficient rendering. - -## What's New in Part 4 - -### Field of View System -- **FOV Calculation**: Using `Grid.compute_fov()` with configurable radius -- **Perspective System**: Grid tracks which entity is the viewer -- **Visibility States**: Unexplored (black), explored (dark), visible (lit) -- **Automatic Updates**: FOV recalculates on player movement - -### Implementation Details - -#### FOV with McRogueFace's Grid - -Unlike TCOD which uses numpy arrays for visibility tracking, McRogueFace's Grid has built-in FOV support: - -```python -# In GameMap.update_fov() -self.compute_fov(viewer_x, viewer_y, radius, light_walls=True, algorithm=mcrfpy.FOV_BASIC) -``` - -The Grid automatically: -- Tracks which tiles have been explored -- Applies appropriate color overlays (shroud, dark, light) -- Updates entity visibility based on FOV - -#### Perspective System - -McRogueFace uses a perspective-based rendering approach: - -```python -# Set the viewer -self.game_map.perspective = self.player - -# Grid automatically renders from this entity's viewpoint -``` - -This is more efficient than manually updating tile colors every turn. - -#### Color Overlays - -We define overlay colors but let the Grid handle application: - -```python -# In tiles.py -SHROUD = mcrfpy.Color(0, 0, 0, 255) # Unexplored -DARK = mcrfpy.Color(100, 100, 150, 128) # Explored but not visible -LIGHT = mcrfpy.Color(255, 255, 255, 0) # Currently visible -``` - -### Key Differences from TCOD - -| TCOD Approach | McRogueFace Approach | -|---------------|----------------------| -| `visible` and `explored` numpy arrays | Grid's built-in FOV state | -| Manual tile color switching | Automatic overlay system | -| `tcod.map.compute_fov()` | `Grid.compute_fov()` | -| Render conditionals for each tile | Perspective-based rendering | - -### Movement and FOV Updates - -The action system now updates FOV after player movement: - -```python -# In MovementAction.perform() -if self.entity == engine.player: - engine.update_fov() -``` - -## Architecture Notes - -### Why Grid Perspective? - -The perspective system provides several benefits: -1. **Efficiency**: No per-tile color updates needed -2. **Flexibility**: Easy to switch viewpoints (for debugging or features) -3. **Automatic**: Grid handles all rendering details -4. **Clean**: Separates game logic from rendering concerns - -### Entity Visibility - -Entities automatically update their visibility state: - -```python -# After FOV calculation -self.player.update_visibility() -``` - -This ensures entities are only rendered when visible to the current perspective. - -## Files Modified - -- `game/tiles.py`: Added FOV color overlay constants -- `game/game_map.py`: Added `update_fov()` method -- `game/engine.py`: Added FOV initialization and update method -- `game/actions.py`: Update FOV after player movement -- `main.py`: Updated part description - -## What's Next - -Part 5 will add enemies to our dungeon, introducing: -- Enemy entities with AI -- Combat system -- Turn-based gameplay -- Health and damage - -The FOV system will make enemies appear and disappear as you explore, adding tension and strategy to the gameplay. - -## Learning Points - -1. **Leverage Framework Features**: Use McRogueFace's built-in systems rather than reimplementing -2. **Perspective-Based Design**: Think in terms of viewpoints, not global state -3. **Automatic Systems**: Let the framework handle rendering details -4. **Clean Integration**: FOV updates fit naturally into the action system - -## Running Part 4 - -```bash -cd simple_tcod_tutorial/build -./mcrogueface scripts/main.py -``` - -You'll now see: -- Black unexplored areas -- Dark blue tint on previously seen areas -- Full brightness only in your field of view -- Smooth exploration as you move through the dungeon \ No newline at end of file diff --git a/roguelike_tutorial/README_PART_05.md b/roguelike_tutorial/README_PART_05.md deleted file mode 100644 index d85ae21..0000000 --- a/roguelike_tutorial/README_PART_05.md +++ /dev/null @@ -1,169 +0,0 @@ -# Part 5: Placing Enemies and Fighting Them - -## Overview - -Part 5 brings our dungeon to life with enemies! We add rats and spiders that populate the rooms, implement a combat system with melee attacks, and handle entity death by turning creatures into gravestones. - -## What's New in Part 5 - -### Actor System -- **Actor Class**: Extends Entity with combat stats (HP, defense, power) -- **Combat Properties**: Health tracking, damage calculation, alive status -- **Death Handling**: Entities become gravestones when killed - -### Enemy Types -Using our sprite sheet, we have two enemy types: -- **Rat** (sprite 5): 10 HP, 0 defense, 3 power - Common enemy -- **Spider** (sprite 4): 16 HP, 1 defense, 4 power - Tougher enemy - -### Combat System - -#### Bump-to-Attack -When the player tries to move into an enemy: -```python -# In MovementAction.perform() -target = engine.game_map.get_blocking_entity_at(dest_x, dest_y) -if target: - if self.entity == engine.player: - from game.entity import Actor - if isinstance(target, Actor) and target != engine.player: - return MeleeAction(self.entity, self.dx, self.dy).perform(engine) -``` - -#### Damage Calculation -Simple formula with defense reduction: -```python -damage = attacker.power - target.defense -``` - -#### Death System -Dead entities become gravestones: -```python -def die(self) -> None: - """Handle death by becoming a gravestone.""" - self.sprite_index = 6 # Tombstone sprite - self.blocks_movement = False - self.name = f"Grave of {self.name}" -``` - -### Entity Factories - -Factory functions create pre-configured entities: -```python -def rat(x: int, y: int, texture: mcrfpy.Texture) -> Actor: - return Actor( - x=x, y=y, - sprite_id=5, # Rat sprite - texture=texture, - name="Rat", - hp=10, defense=0, power=3, - ) -``` - -### Dungeon Population - -Enemies are placed randomly in rooms: -```python -def place_entities(room, dungeon, max_monsters, texture): - number_of_monsters = random.randint(0, max_monsters) - - for _ in range(number_of_monsters): - x = random.randint(room.x1 + 1, room.x2 - 1) - y = random.randint(room.y1 + 1, room.y2 - 1) - - if not any(entity.x == x and entity.y == y for entity in dungeon.entities): - # 80% rats, 20% spiders - if random.random() < 0.8: - monster = entity_factories.rat(x, y, texture) - else: - monster = entity_factories.spider(x, y, texture) - monster.place(x, y, dungeon) -``` - -## Key Implementation Details - -### FOV and Enemy Visibility -Enemies are automatically shown/hidden by the FOV system: -```python -def update_fov(self) -> None: - # Update visibility for all entities - for entity in self.game_map.entities: - entity.update_visibility() -``` - -### Action System Extension -The action system now handles combat: -- **MovementAction**: Detects collision, triggers attack -- **MeleeAction**: New action for melee combat -- Actions remain decoupled from entity logic - -### Gravestone System -Instead of removing dead entities: -- Sprite changes to tombstone (index 6) -- Name changes to "Grave of [Name]" -- No longer blocks movement -- Remains visible as dungeon decoration - -## Architecture Notes - -### Why Actor Extends Entity? -- Maintains entity hierarchy -- Combat stats only for creatures -- Future items/decorations won't have HP -- Clean separation of concerns - -### Why Factory Functions? -- Centralized entity configuration -- Easy to add new enemy types -- Consistent stat management -- Type-safe entity creation - -### Combat in Actions -Combat logic lives in actions, not entities: -- Entities store stats -- Actions perform combat -- Clean separation of data and behavior -- Extensible for future combat types - -## Files Modified - -- `game/entity.py`: Added Actor class with combat stats and death handling -- `game/entity_factories.py`: New module with entity creation functions -- `game/actions.py`: Added MeleeAction for combat -- `game/procgen.py`: Added enemy placement in rooms -- `game/engine.py`: Updated to use Actor type and handle all entity visibility -- `main.py`: Updated to use entity factories and Part 5 description - -## What's Next - -Part 6 will enhance the combat experience with: -- Health display UI -- Game over conditions -- Combat messages window -- More strategic combat mechanics - -## Learning Points - -1. **Entity Specialization**: Use inheritance to add features to specific entity types -2. **Factory Pattern**: Centralize object creation for consistency -3. **State Transformation**: Dead entities become decorations, not deletions -4. **Action Extensions**: Combat fits naturally into the action system -5. **Automatic Systems**: FOV handles entity visibility without special code - -## Running Part 5 - -```bash -cd simple_tcod_tutorial/build -./mcrogueface scripts/main.py -``` - -You'll now encounter rats and spiders as you explore! Walk into them to attack. Dead enemies become gravestones that mark your battles. - -## Sprite Adaptations - -Following our sprite sheet (`sprite_sheet.md`), we made these thematic changes: -- Orcs → Rats (same stats, different sprite) -- Trolls → Spiders (same stats, different sprite) -- Corpses → Gravestones (all use same tombstone sprite) - -The gameplay remains identical to the TCOD tutorial, just with different visual theming. \ No newline at end of file diff --git a/roguelike_tutorial/README_PART_06.md b/roguelike_tutorial/README_PART_06.md deleted file mode 100644 index 1348a9f..0000000 --- a/roguelike_tutorial/README_PART_06.md +++ /dev/null @@ -1,187 +0,0 @@ -# Part 6: Doing (and Taking) Damage - -## Overview - -Part 6 transforms our basic combat into a complete gameplay loop with visual feedback, enemy AI, and win/lose conditions. We add a health bar, message log, enemy AI that pursues the player, and proper game over handling. - -## What's New in Part 6 - -### User Interface Components - -#### Health Bar -A visual representation of the player's current health: -```python -class HealthBar: - def create_ui(self) -> List[mcrfpy.UIDrawable]: - # Dark red background - self.background = mcrfpy.Frame(pos=(x, y), size=(width, height)) - self.background.fill_color = mcrfpy.Color(100, 0, 0, 255) - - # Bright colored bar (green/yellow/red based on HP) - self.bar = mcrfpy.Frame(pos=(x, y), size=(width, height)) - - # Text overlay showing HP numbers - self.text = mcrfpy.Caption(pos=(x+5, y+2), - text=f"HP: {hp}/{max_hp}") -``` - -The bar changes color based on health percentage: -- Green (>60% health) -- Yellow (30-60% health) -- Red (<30% health) - -#### Message Log -A scrolling combat log that replaces console print statements: -```python -class MessageLog: - def __init__(self, max_messages: int = 5): - self.messages: deque[str] = deque(maxlen=max_messages) - - def add_message(self, message: str) -> None: - self.messages.append(message) - self.update_display() -``` - -Messages include: -- Combat actions ("Rat attacks Player for 3 hit points.") -- Death notifications ("Spider is dead!") -- Game state changes ("You have died! Press Escape to quit.") - -### Enemy AI System - -#### Basic AI Component -Enemies now actively pursue and attack the player: -```python -class BasicAI: - def take_turn(self, engine: Engine) -> None: - distance = max(abs(dx), abs(dy)) # Chebyshev distance - - if distance <= 1: - # Adjacent: Attack! - MeleeAction(self.entity, attack_dx, attack_dy).perform(engine) - elif distance <= 6: - # Can see player: Move closer - MovementAction(self.entity, move_dx, move_dy).perform(engine) -``` - -#### Turn-Based System -After each player action, all enemies take their turn: -```python -def handle_enemy_turns(self) -> None: - for entity in self.game_map.entities: - if isinstance(entity, Actor) and entity.ai and entity.is_alive: - entity.ai.take_turn(self) -``` - -### Game Over Condition - -When the player dies: -1. Game state flag is set (`engine.game_over = True`) -2. Player becomes a gravestone (sprite changes) -3. Input is restricted (only Escape works) -4. Death message appears in the message log - -```python -def handle_player_death(self) -> None: - self.game_over = True - self.message_log.add_message("You have died! Press Escape to quit.") -``` - -## Architecture Improvements - -### UI Module (`game/ui.py`) -Separates UI concerns from game logic: -- `MessageLog`: Manages combat messages -- `HealthBar`: Displays player health -- Clean interface for updating displays - -### AI Module (`game/ai.py`) -Encapsulates enemy behavior: -- `BasicAI`: Simple pursue-and-attack behavior -- Extensible for different AI types -- Uses existing action system - -### Turn Management -Player actions trigger enemy turns: -- Movement → Enemy turns -- Attack → Enemy turns -- Wait → Enemy turns -- Maintains turn-based feel - -## Key Implementation Details - -### UI Updates -Health bar updates occur: -- After player takes damage -- Automatically via `engine.update_ui()` -- Color changes based on HP percentage - -### Message Flow -Combat messages follow this pattern: -1. Action generates message text -2. `engine.message_log.add_message(text)` -3. Message appears in UI Caption -4. Old messages scroll up - -### AI Decision Making -Basic AI uses simple rules: -1. Check if player is adjacent → Attack -2. Check if player is visible (within 6 tiles) → Move toward -3. Otherwise → Do nothing - -### Game State Management -The `game_over` flag prevents: -- Player movement -- Player attacks -- Player waiting -- But allows Escape to quit - -## Files Modified - -- `game/ui.py`: New module for UI components -- `game/ai.py`: New module for enemy AI -- `game/engine.py`: Added UI setup, enemy turns, game over handling -- `game/entity.py`: Added AI component to Actor -- `game/entity_factories.py`: Attached AI to enemies -- `game/actions.py`: Integrated message log, added enemy turn triggers -- `main.py`: Updated part description - -## What's Next - -Part 7 will expand the user interface further with: -- More detailed entity inspection -- Possibly inventory display -- Additional UI panels -- Mouse interaction - -## Learning Points - -1. **UI Separation**: Keep UI logic separate from game logic -2. **Component Systems**: AI as a component allows different behaviors -3. **Turn-Based Flow**: Player action → Enemy reactions creates tactical gameplay -4. **Visual Feedback**: Health bars and message logs improve player understanding -5. **State Management**: Game over flag controls available actions - -## Running Part 6 - -```bash -cd simple_tcod_tutorial/build -./mcrogueface scripts/main.py -``` - -You'll now see: -- Health bar at the top showing your current HP -- Message log at the bottom showing combat events -- Enemies that chase you when you're nearby -- Enemies that attack when adjacent -- Death state when HP reaches 0 - -## Combat Strategy - -With enemy AI active, combat becomes more tactical: -- Enemies pursue when they see you -- Fighting in corridors limits how many can attack -- Running away is sometimes the best option -- Health management becomes critical - -The game now has a complete combat loop with clear win/lose conditions! \ No newline at end of file diff --git a/roguelike_tutorial/README_PART_07.md b/roguelike_tutorial/README_PART_07.md deleted file mode 100644 index 92034d8..0000000 --- a/roguelike_tutorial/README_PART_07.md +++ /dev/null @@ -1,204 +0,0 @@ -# Part 7: Creating the User Interface - -## Overview - -Part 7 significantly enhances the user interface, transforming our roguelike from a basic game into a more polished experience. We add mouse interaction, help displays, information panels, and better visual feedback systems. - -## What's New in Part 7 - -### Mouse Interaction - -#### Click-to-Inspect System -Since McRogueFace doesn't have mouse motion events, we use click events to show entity information: -```python -def grid_click_handler(pixel_x, pixel_y, button, state): - # Convert pixel coordinates to grid coordinates - grid_x = int(pixel_x / (self.tile_size * self.zoom)) - grid_y = int(pixel_y / (self.tile_size * self.zoom)) - - # Update hover display for this position - self.update_mouse_hover(grid_x, grid_y) -``` - -Click displays show: -- Entity names -- Current HP for living creatures -- Multiple entities if stacked (e.g., "Grave of Rat") - -#### Mouse Handler Registration -The click handler is registered as a local function to avoid issues with bound methods: -```python -# Use a local function instead of a bound method -self.game_map.click = grid_click_handler -``` - -### Help System - -#### Toggle Help Display -Press `?`, `H`, or `F1` to show/hide help: -```python -class HelpDisplay: - def toggle(self) -> None: - self.visible = not self.visible - self.panel.frame.visible = self.visible -``` - -The help panel includes: -- Movement controls for all input methods -- Combat instructions -- Mouse usage tips -- Gameplay strategies - -### Information Panels - -#### Player Stats Panel -Always-visible panel showing: -- Player name -- Current/Max HP -- Power and Defense stats -- Current grid position - -```python -class InfoPanel: - def create_ui(self, title: str) -> List[mcrfpy.Drawable]: - # Semi-transparent background frame - self.frame = mcrfpy.Frame(pos=(x, y), size=(width, height)) - self.frame.fill_color = mcrfpy.Color(20, 20, 40, 200) - - # Title and content captions as children - self.frame.children.append(self.title_caption) - self.frame.children.append(self.content_caption) -``` - -#### Reusable Panel System -The `InfoPanel` class provides: -- Titled panels with borders -- Semi-transparent backgrounds -- Easy content updates -- Consistent visual style - -### Enhanced UI Components - -#### MouseHoverDisplay Class -Manages tooltip-style hover information: -- Follows mouse position -- Shows/hides automatically -- Offset to avoid cursor overlap -- Multiple entity support - -#### UI Module Organization -Clean separation of UI components: -- `MessageLog`: Combat messages -- `HealthBar`: HP visualization -- `MouseHoverDisplay`: Entity inspection -- `InfoPanel`: Generic information display -- `HelpDisplay`: Keyboard controls - -## Architecture Improvements - -### UI Composition -Using McRogueFace's parent-child system: -```python -# Add caption as child of frame -self.frame.children.append(self.text_caption) -``` - -Benefits: -- Automatic relative positioning -- Group visibility control -- Clean hierarchy - -### Event Handler Extensions -Input handler now manages: -- Keyboard input (existing) -- Mouse motion (new) -- Mouse clicks (prepared for future) -- UI toggles (help display) - -### Dynamic Content Updates -All UI elements support real-time updates: -```python -def update_stats_panel(self) -> None: - stats_text = f"""Name: {self.player.name} -HP: {self.player.hp}/{self.player.max_hp} -Power: {self.player.power} -Defense: {self.player.defense}""" - self.stats_panel.update_content(stats_text) -``` - -## Key Implementation Details - -### Mouse Coordinate Conversion -Pixel to grid conversion: -```python -grid_x = int(x / (self.engine.tile_size * self.engine.zoom)) -grid_y = int(y / (self.engine.tile_size * self.engine.zoom)) -``` - -### Visibility Management -UI elements can be toggled: -- Help panel starts hidden -- Mouse hover hides when not over entities -- Panels can be shown/hidden dynamically - -### Color and Transparency -UI uses semi-transparent overlays: -- Panel backgrounds: `Color(20, 20, 40, 200)` -- Hover tooltips: `Color(255, 255, 200, 255)` -- Borders and outlines for readability - -## Files Modified - -- `game/ui.py`: Added MouseHoverDisplay, InfoPanel, HelpDisplay classes -- `game/engine.py`: Integrated new UI components, mouse hover handling -- `game/input_handlers.py`: Added mouse motion handling, help toggle -- `main.py`: Registered mouse handlers, updated part description - -## What's Next - -Part 8 will add items and inventory: -- Collectible items (potions, equipment) -- Inventory management UI -- Item usage mechanics -- Equipment system - -## Learning Points - -1. **UI Composition**: Use parent-child relationships for complex UI -2. **Event Delegation**: Separate input handling from UI updates -3. **Information Layers**: Multiple UI systems can coexist (hover, panels, help) -4. **Visual Polish**: Small touches like transparency and borders improve UX -5. **Reusable Components**: Generic panels can be specialized for different uses - -## Running Part 7 - -```bash -cd simple_tcod_tutorial/build -./mcrogueface scripts/main.py -``` - -New features to try: -- Click on entities to see their details -- Press ? or H to toggle help display -- Watch the stats panel update as you take damage -- See entity HP in hover tooltips -- Notice the visual polish in UI panels - -## UI Design Principles - -### Consistency -- All panels use similar visual style -- Consistent color scheme -- Uniform text sizing - -### Non-Intrusive -- Semi-transparent panels don't block view -- Hover info appears near cursor -- Help can be toggled off - -### Information Hierarchy -- Critical info (health) always visible -- Contextual info (hover) on demand -- Help info toggleable - -The UI now provides a professional feel while maintaining the roguelike aesthetic! \ No newline at end of file diff --git a/roguelike_tutorial/README_PART_08.md b/roguelike_tutorial/README_PART_08.md deleted file mode 100644 index a6dfe2b..0000000 --- a/roguelike_tutorial/README_PART_08.md +++ /dev/null @@ -1,297 +0,0 @@ -# Part 8: Items and Inventory - -## Overview - -Part 8 transforms our roguelike into a proper loot-driven game by adding items that can be collected, managed, and used. We implement a flexible inventory system with capacity limits, create consumable items like healing potions, and build UI for inventory management. - -## What's New in Part 8 - -### Parent-Child Entity Architecture - -#### Flexible Entity Ownership -Entities now have parent containers, allowing them to exist in different contexts: -```python -class Entity(mcrfpy.Entity): - def __init__(self, parent: Optional[Union[GameMap, Inventory]] = None): - self.parent = parent - - @property - def gamemap(self) -> Optional[GameMap]: - """Get the GameMap through the parent chain""" - if isinstance(self.parent, Inventory): - return self.parent.gamemap - return self.parent -``` - -Benefits: -- Items can exist in the world or in inventories -- Clean ownership transfer when picking up/dropping -- Automatic visibility management - -### Inventory System - -#### Container-Based Design -The inventory acts like a specialized entity container: -```python -class Inventory: - def __init__(self, capacity: int): - self.capacity = capacity - self.items: List[Item] = [] - self.parent: Optional[Actor] = None - - def add_item(self, item: Item) -> None: - if len(self.items) >= self.capacity: - raise Impossible("Your inventory is full.") - - # Transfer ownership - self.items.append(item) - item.parent = self - item.visible = False # Hide from map -``` - -Features: -- Capacity limits (26 items for letter selection) -- Clean item transfer between world and inventory -- Automatic visual management - -### Item System - -#### Item Entity Class -Items are entities with consumable components: -```python -class Item(Entity): - def __init__(self, consumable: Optional = None): - super().__init__(blocks_movement=False) - self.consumable = consumable - if consumable: - consumable.parent = self -``` - -#### Consumable Components -Modular system for item effects: -```python -class HealingConsumable(Consumable): - def activate(self, action: ItemAction) -> None: - if consumer.hp >= consumer.max_hp: - raise Impossible("You are already at full health.") - - amount_recovered = min(self.amount, consumer.max_hp - consumer.hp) - consumer.hp += amount_recovered - self.consume() # Remove item after use -``` - -### Exception-Driven Feedback - -#### Clean Error Handling -Using exceptions for user feedback: -```python -class Impossible(Exception): - """Action cannot be performed""" - pass - -class PickupAction(Action): - def perform(self, engine: Engine) -> None: - if not items_here: - raise Impossible("There is nothing here to pick up.") - - try: - inventory.add_item(item) - engine.message_log.add_message(f"You picked up the {item.name}!") - except Impossible as e: - engine.message_log.add_message(str(e)) -``` - -Benefits: -- Consistent error messaging -- Clean control flow -- Centralized feedback handling - -### Inventory UI - -#### Modal Inventory Screen -Interactive inventory management: -```python -class InventoryEventHandler(BaseEventHandler): - def create_ui(self) -> None: - # Semi-transparent background - self.background = mcrfpy.Frame(pos=(100, 100), size=(400, 400)) - self.background.fill_color = mcrfpy.Color(0, 0, 0, 200) - - # List items with letter keys - for i, item in enumerate(inventory.items): - item_caption = mcrfpy.Caption( - pos=(20, 80 + i * 20), - text=f"{chr(ord('a') + i)}) {item.name}" - ) -``` - -Features: -- Letter-based selection (a-z) -- Separate handlers for use/drop -- ESC to cancel -- Visual feedback - -### Enhanced Actions - -#### Item Actions -New actions for item management: -```python -class PickupAction(Action): - """Pick up items at current location""" - -class ItemAction(Action): - """Base for item usage actions""" - -class DropAction(ItemAction): - """Drop item from inventory""" -``` - -Each action: -- Self-validates -- Provides feedback -- Triggers enemy turns - -## Architecture Improvements - -### Component Relationships -Parent-based component system: -```python -# Components know their parent -consumable.parent = item -item.parent = inventory -inventory.parent = actor -actor.parent = gamemap -gamemap.engine = engine -``` - -Benefits: -- Access to game context from any component -- Clean ownership transfer -- Simplified entity lifecycle - -### Input Handler States -Modal UI through handler switching: -```python -# Main game -engine.current_handler = MainGameEventHandler(engine) - -# Open inventory -engine.current_handler = InventoryActivateHandler(engine) - -# Back to game -engine.current_handler = MainGameEventHandler(engine) -``` - -### Entity Lifecycle Management -Proper creation and cleanup: -```python -# Item spawning -item = entity_factories.health_potion(x, y, texture) -item.place(x, y, dungeon) - -# Pickup -inventory.add_item(item) # Removes from map - -# Drop -inventory.drop(item) # Returns to map - -# Death -actor.die() # Drops all items -``` - -## Key Implementation Details - -### Visibility Management -Items hide/show based on container: -```python -def add_item(self, item): - item.visible = False # Hide when in inventory - -def drop(self, item): - item.visible = True # Show when on map -``` - -### Inventory Capacity -Limited to alphabet keys: -```python -if len(inventory.items) >= 26: - raise Impossible("Your inventory is full.") -``` - -### Item Generation -Procedural item placement: -```python -def place_entities(room, dungeon, max_monsters, max_items, texture): - # Place 0-2 items per room - number_of_items = random.randint(0, max_items) - - for _ in range(number_of_items): - if space_available: - item = entity_factories.health_potion(x, y, texture) - item.place(x, y, dungeon) -``` - -## Files Modified - -- `game/entity.py`: Added parent system, Item class, inventory to Actor -- `game/inventory.py`: New inventory container system -- `game/consumable.py`: New consumable component system -- `game/exceptions.py`: New Impossible exception -- `game/actions.py`: Added PickupAction, ItemAction, DropAction -- `game/input_handlers.py`: Added InventoryEventHandler classes -- `game/engine.py`: Added current_handler, inventory UI methods -- `game/procgen.py`: Added item generation -- `game/entity_factories.py`: Added health_potion factory -- `game/ui.py`: Updated help text with inventory controls -- `main.py`: Updated to Part 8, handler management - -## What's Next - -Part 9 will add ranged attacks and targeting: -- Targeting UI for selecting enemies -- Ranged damage items (lightning staff) -- Area-of-effect items (fireball staff) -- Confusion effects - -## Learning Points - -1. **Container Architecture**: Entity ownership through parent relationships -2. **Component Systems**: Modular, reusable components with parent references -3. **Exception Handling**: Clean error propagation and user feedback -4. **Modal UI**: State-based input handling for different screens -5. **Item Systems**: Flexible consumable architecture for varied effects -6. **Lifecycle Management**: Proper entity creation, transfer, and cleanup - -## Running Part 8 - -```bash -cd simple_tcod_tutorial/build -./mcrogueface scripts/main.py -``` - -New features to try: -- Press G to pick up healing potions -- Press I to open inventory and use items -- Press O to drop items from inventory -- Heal yourself when injured in combat -- Manage limited inventory space (26 slots) -- Items drop from dead enemies - -## Design Principles - -### Flexibility Through Composition -- Items gain behavior through consumable components -- Easy to add new item types -- Reusable effect system - -### Clean Ownership Transfer -- Entities always have clear parent -- Automatic visibility management -- No orphaned entities - -### User-Friendly Feedback -- Clear error messages -- Consistent UI patterns -- Intuitive controls - -The inventory system provides the foundation for equipment, spells, and complex item interactions in future parts! \ No newline at end of file diff --git a/roguelike_tutorial/_generated_part_5.py b/roguelike_tutorial/_generated_part_5.py deleted file mode 100644 index 374061e..0000000 --- a/roguelike_tutorial/_generated_part_5.py +++ /dev/null @@ -1,625 +0,0 @@ -""" -McRogueFace Tutorial - Part 5: Entity Interactions - -This tutorial builds on Part 4 by adding: -- Entity class hierarchy (PlayerEntity, EnemyEntity, BoulderEntity, ButtonEntity) -- Non-blocking movement animations with destination tracking -- Bump interactions (combat, pushing) -- Step-on interactions (buttons, doors) -- Concurrent enemy AI with smooth animations - -Key concepts: -- Entities inherit from mcrfpy.Entity for proper C++/Python integration -- Logic operates on destination positions during animations -- Player input is processed immediately, not blocked by animations -""" -import mcrfpy -import random - -# ============================================================================ -# Entity Classes - Inherit from mcrfpy.Entity -# ============================================================================ - -class GameEntity(mcrfpy.Entity): - """Base class for all game entities with interaction logic""" - def __init__(self, x, y, **kwargs): - # Extract grid before passing to parent - grid = kwargs.pop('grid', None) - super().__init__(x=x, y=y, **kwargs) - - # Current position is tracked by parent Entity.x/y - # Add destination tracking for animation system - self.dest_x = x - self.dest_y = y - self.is_moving = False - - # Game properties - self.blocks_movement = True - self.hp = 10 - self.max_hp = 10 - self.entity_type = "generic" - - # Add to grid if provided - if grid: - grid.entities.append(self) - - def start_move(self, new_x, new_y, duration=0.2, callback=None): - """Start animating movement to new position""" - self.dest_x = new_x - self.dest_y = new_y - self.is_moving = True - - # Create animations for smooth movement - if callback: - # Only x animation needs callback since they run in parallel - anim_x = mcrfpy.Animation("x", float(new_x), duration, "easeInOutQuad", callback=callback) - else: - anim_x = mcrfpy.Animation("x", float(new_x), duration, "easeInOutQuad") - anim_y = mcrfpy.Animation("y", float(new_y), duration, "easeInOutQuad") - - anim_x.start(self) - anim_y.start(self) - - def get_position(self): - """Get logical position (destination if moving, otherwise current)""" - if self.is_moving: - return (self.dest_x, self.dest_y) - return (int(self.x), int(self.y)) - - def on_bump(self, other): - """Called when another entity tries to move into our space""" - return False # Block movement by default - - def on_step(self, other): - """Called when another entity steps on us (non-blocking)""" - pass - - def take_damage(self, damage): - """Apply damage and handle death""" - self.hp -= damage - if self.hp <= 0: - self.hp = 0 - self.die() - - def die(self): - """Remove entity from grid""" - # The C++ die() method handles removal from grid - super().die() - -class PlayerEntity(GameEntity): - """The player character""" - def __init__(self, x, y, **kwargs): - kwargs['sprite_index'] = 64 # Hero sprite - super().__init__(x=x, y=y, **kwargs) - self.damage = 3 - self.entity_type = "player" - self.blocks_movement = True - - def on_bump(self, other): - """Player bumps into something""" - if other.entity_type == "enemy": - # Deal damage - other.take_damage(self.damage) - return False # Can't move into enemy space - elif other.entity_type == "boulder": - # Try to push - dx = self.dest_x - int(self.x) - dy = self.dest_y - int(self.y) - return other.try_push(dx, dy) - return False - -class EnemyEntity(GameEntity): - """Basic enemy with AI""" - def __init__(self, x, y, **kwargs): - kwargs['sprite_index'] = 65 # Enemy sprite - super().__init__(x=x, y=y, **kwargs) - self.damage = 1 - self.entity_type = "enemy" - self.ai_state = "wander" - self.hp = 5 - self.max_hp = 5 - - def on_bump(self, other): - """Enemy bumps into something""" - if other.entity_type == "player": - other.take_damage(self.damage) - return False - return False - - def can_see_player(self, player_pos, grid): - """Check if enemy can see the player position""" - # Simple check: within 6 tiles and has line of sight - mx, my = self.get_position() - px, py = player_pos - - dist = abs(px - mx) + abs(py - my) - if dist > 6: - return False - - # Use libtcod for line of sight - line = list(mcrfpy.libtcod.line(mx, my, px, py)) - if len(line) > 7: # Too far - return False - for x, y in line[1:-1]: # Skip start and end points - cell = grid.at(x, y) - if cell and not cell.transparent: - return False - return True - - def ai_turn(self, grid, player): - """Decide next move""" - px, py = player.get_position() - mx, my = self.get_position() - - # Simple AI: move toward player if visible - if self.can_see_player((px, py), grid): - # Calculate direction toward player - dx = 0 - dy = 0 - if px > mx: - dx = 1 - elif px < mx: - dx = -1 - if py > my: - dy = 1 - elif py < my: - dy = -1 - - # Prefer cardinal movement - if dx != 0 and dy != 0: - # Pick horizontal or vertical based on greater distance - if abs(px - mx) > abs(py - my): - dy = 0 - else: - dx = 0 - - return (mx + dx, my + dy) - else: - # Random movement - dx, dy = random.choice([(0,1), (0,-1), (1,0), (-1,0)]) - return (mx + dx, my + dy) - -class BoulderEntity(GameEntity): - """Pushable boulder""" - def __init__(self, x, y, **kwargs): - kwargs['sprite_index'] = 7 # Boulder sprite - super().__init__(x=x, y=y, **kwargs) - self.entity_type = "boulder" - self.pushable = True - - def try_push(self, dx, dy): - """Attempt to push boulder in direction""" - new_x = int(self.x) + dx - new_y = int(self.y) + dy - - # Check if destination is free - if can_move_to(new_x, new_y): - self.start_move(new_x, new_y) - return True - return False - -class ButtonEntity(GameEntity): - """Pressure plate that triggers when stepped on""" - def __init__(self, x, y, target=None, **kwargs): - kwargs['sprite_index'] = 8 # Button sprite - super().__init__(x=x, y=y, **kwargs) - self.blocks_movement = False # Can be walked over - self.entity_type = "button" - self.pressed = False - self.pressed_by = set() # Track who's pressing - self.target = target # Door or other triggerable - - def on_step(self, other): - """Activate when stepped on""" - if other not in self.pressed_by: - self.pressed_by.add(other) - if not self.pressed: - self.pressed = True - self.sprite_index = 9 # Pressed sprite - if self.target: - self.target.activate() - - def on_leave(self, other): - """Deactivate when entity leaves""" - if other in self.pressed_by: - self.pressed_by.remove(other) - if len(self.pressed_by) == 0 and self.pressed: - self.pressed = False - self.sprite_index = 8 # Unpressed sprite - if self.target: - self.target.deactivate() - -class DoorEntity(GameEntity): - """Door that can be opened by buttons""" - def __init__(self, x, y, **kwargs): - kwargs['sprite_index'] = 3 # Closed door sprite - super().__init__(x=x, y=y, **kwargs) - self.entity_type = "door" - self.is_open = False - - def activate(self): - """Open the door""" - self.is_open = True - self.blocks_movement = False - self.sprite_index = 11 # Open door sprite - - def deactivate(self): - """Close the door""" - self.is_open = False - self.blocks_movement = True - self.sprite_index = 3 # Closed door sprite - -# ============================================================================ -# Global Game State -# ============================================================================ - -# Create and activate a new scene -mcrfpy.createScene("tutorial") -mcrfpy.setScene("tutorial") - -# Load the texture -texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) - -# Create a grid of tiles -grid_width, grid_height = 40, 30 -zoom = 2.0 -grid_size = grid_width * zoom * 16, grid_height * zoom * 16 -grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 - -# Create the grid -grid = mcrfpy.Grid( - pos=grid_position, - grid_size=(grid_width, grid_height), - texture=texture, - size=grid_size, -) -grid.zoom = zoom - -# Define tile types -FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] -WALL_TILES = [3, 7, 11] - -# Game state -player = None -enemies = [] -all_entities = [] -is_player_turn = True -move_duration = 0.2 - -# ============================================================================ -# Dungeon Generation (from Part 3) -# ============================================================================ - -class Room: - def __init__(self, x, y, width, height): - self.x1 = x - self.y1 = y - self.x2 = x + width - self.y2 = y + height - - def center(self): - return ((self.x1 + self.x2) // 2, (self.y1 + self.y2) // 2) - - def intersects(self, other): - return (self.x1 <= other.x2 and self.x2 >= other.x1 and - self.y1 <= other.y2 and self.y2 >= other.y1) - -def create_room(room): - """Carve out a room in the grid""" - for x in range(room.x1 + 1, room.x2): - for y in range(room.y1 + 1, room.y2): - cell = grid.at(x, y) - if cell: - cell.walkable = True - cell.transparent = True - cell.tilesprite = random.choice(FLOOR_TILES) - -def create_l_shaped_hallway(x1, y1, x2, y2): - """Create L-shaped hallway between two points""" - corner_x = x2 - corner_y = y1 - - if random.random() < 0.5: - corner_x = x1 - corner_y = y2 - - for x, y in mcrfpy.libtcod.line(x1, y1, corner_x, corner_y): - cell = grid.at(x, y) - if cell: - cell.walkable = True - cell.transparent = True - cell.tilesprite = random.choice(FLOOR_TILES) - - for x, y in mcrfpy.libtcod.line(corner_x, corner_y, x2, y2): - cell = grid.at(x, y) - if cell: - cell.walkable = True - cell.transparent = True - cell.tilesprite = random.choice(FLOOR_TILES) - -def generate_dungeon(): - """Generate a simple dungeon with rooms and hallways""" - # Initialize all cells as walls - for x in range(grid_width): - for y in range(grid_height): - cell = grid.at(x, y) - if cell: - cell.walkable = False - cell.transparent = False - cell.tilesprite = random.choice(WALL_TILES) - - rooms = [] - num_rooms = 0 - - for _ in range(30): - w = random.randint(4, 8) - h = random.randint(4, 8) - x = random.randint(0, grid_width - w - 1) - y = random.randint(0, grid_height - h - 1) - - new_room = Room(x, y, w, h) - - # Check if room intersects with existing rooms - if any(new_room.intersects(other_room) for other_room in rooms): - continue - - create_room(new_room) - - if num_rooms > 0: - # Connect to previous room - new_x, new_y = new_room.center() - prev_x, prev_y = rooms[num_rooms - 1].center() - create_l_shaped_hallway(prev_x, prev_y, new_x, new_y) - - rooms.append(new_room) - num_rooms += 1 - - return rooms - -# ============================================================================ -# Entity Management -# ============================================================================ - -def get_entities_at(x, y): - """Get all entities at a specific position (including moving ones)""" - entities = [] - for entity in all_entities: - ex, ey = entity.get_position() - if ex == x and ey == y: - entities.append(entity) - return entities - -def get_blocking_entity_at(x, y): - """Get the first blocking entity at position""" - for entity in get_entities_at(x, y): - if entity.blocks_movement: - return entity - return None - -def can_move_to(x, y): - """Check if a position is valid for movement""" - if x < 0 or x >= grid_width or y < 0 or y >= grid_height: - return False - - cell = grid.at(x, y) - if not cell or not cell.walkable: - return False - - # Check for blocking entities - if get_blocking_entity_at(x, y): - return False - - return True - -def can_entity_move_to(entity, x, y): - """Check if specific entity can move to position""" - if x < 0 or x >= grid_width or y < 0 or y >= grid_height: - return False - - cell = grid.at(x, y) - if not cell or not cell.walkable: - return False - - # Check for other blocking entities (not self) - blocker = get_blocking_entity_at(x, y) - if blocker and blocker != entity: - return False - - return True - -# ============================================================================ -# Turn Management -# ============================================================================ - -def process_player_move(key): - """Handle player input with immediate response""" - global is_player_turn - - if not is_player_turn or player.is_moving: - return # Not player's turn or still animating - - px, py = player.get_position() - new_x, new_y = px, py - - # Calculate movement direction - if key == "W" or key == "Up": - new_y -= 1 - elif key == "S" or key == "Down": - new_y += 1 - elif key == "A" or key == "Left": - new_x -= 1 - elif key == "D" or key == "Right": - new_x += 1 - else: - return # Not a movement key - - if new_x == px and new_y == py: - return # No movement - - # Check what's at destination - cell = grid.at(new_x, new_y) - if not cell or not cell.walkable: - return # Can't move into walls - - blocking_entity = get_blocking_entity_at(new_x, new_y) - - if blocking_entity: - # Try bump interaction - if not player.on_bump(blocking_entity): - # Movement blocked, but turn still happens - is_player_turn = False - mcrfpy.setTimer("enemy_turn", process_enemy_turns, 50) - return - - # Movement is valid - start player animation - is_player_turn = False - player.start_move(new_x, new_y, duration=move_duration, callback=player_move_complete) - - # Update grid center to follow player - grid_anim_x = mcrfpy.Animation("center_x", (new_x + 0.5) * 16, move_duration, "linear") - grid_anim_y = mcrfpy.Animation("center_y", (new_y + 0.5) * 16, move_duration, "linear") - grid_anim_x.start(grid) - grid_anim_y.start(grid) - - # Start enemy turns after a short delay (so player sees their move start first) - mcrfpy.setTimer("enemy_turn", process_enemy_turns, 50) - -def process_enemy_turns(timer_name): - """Process all enemy AI decisions and start their animations""" - enemies_to_move = [] - - for enemy in enemies: - if enemy.hp <= 0: # Skip dead enemies - continue - - if enemy.is_moving: - continue # Skip if still animating - - # AI decides next move based on player's destination - target_x, target_y = enemy.ai_turn(grid, player) - - # Check if move is valid - cell = grid.at(target_x, target_y) - if not cell or not cell.walkable: - continue - - # Check what's at the destination - blocking_entity = get_blocking_entity_at(target_x, target_y) - - if blocking_entity and blocking_entity != enemy: - # Try bump interaction - enemy.on_bump(blocking_entity) - # Enemy doesn't move but still took its turn - else: - # Valid move - add to list - enemies_to_move.append((enemy, target_x, target_y)) - - # Start all enemy animations simultaneously - for enemy, tx, ty in enemies_to_move: - enemy.start_move(tx, ty, duration=move_duration) - -def player_move_complete(anim, entity): - """Called when player animation finishes""" - global is_player_turn - - player.is_moving = False - - # Check for step-on interactions at new position - for entity in get_entities_at(int(player.x), int(player.y)): - if entity != player and not entity.blocks_movement: - entity.on_step(player) - - # Update FOV from new position - update_fov() - - # Player's turn is ready again - is_player_turn = True - -def update_fov(): - """Update field of view from player position""" - px, py = int(player.x), int(player.y) - grid.compute_fov(px, py, radius=8) - player.update_visibility() - -# ============================================================================ -# Input Handling -# ============================================================================ - -def handle_keys(key, state): - """Handle keyboard input""" - if state == "start": - # Movement keys - if key in ["W", "Up", "S", "Down", "A", "Left", "D", "Right"]: - process_player_move(key) - -# Register the key handler -mcrfpy.keypressScene(handle_keys) - -# ============================================================================ -# Initialize Game -# ============================================================================ - -# Generate dungeon -rooms = generate_dungeon() - -# Place player in first room -if rooms: - start_x, start_y = rooms[0].center() - player = PlayerEntity(start_x, start_y, grid=grid) - all_entities.append(player) - - # Place enemies in other rooms - for i in range(1, min(6, len(rooms))): - room = rooms[i] - ex, ey = room.center() - enemy = EnemyEntity(ex, ey, grid=grid) - enemies.append(enemy) - all_entities.append(enemy) - - # Place some boulders - for i in range(3): - room = random.choice(rooms[1:]) - bx = random.randint(room.x1 + 1, room.x2 - 1) - by = random.randint(room.y1 + 1, room.y2 - 1) - if can_move_to(bx, by): - boulder = BoulderEntity(bx, by, grid=grid) - all_entities.append(boulder) - - # Place a button and door in one of the rooms - if len(rooms) > 2: - button_room = rooms[-2] - door_room = rooms[-1] - - # Place door at entrance to last room - dx, dy = door_room.center() - door = DoorEntity(dx, door_room.y1, grid=grid) - all_entities.append(door) - - # Place button in second to last room - bx, by = button_room.center() - button = ButtonEntity(bx, by, target=door, grid=grid) - all_entities.append(button) - - # Set grid perspective to player - grid.perspective = player - grid.center_x = (start_x + 0.5) * 16 - grid.center_y = (start_y + 0.5) * 16 - - # Initial FOV calculation - update_fov() - -# Add grid to scene -mcrfpy.sceneUI("tutorial").append(grid) - -# Show instructions -title = mcrfpy.Caption((320, 10), - text="Part 5: Entity Interactions - WASD to move, bump enemies, push boulders!", -) -title.fill_color = mcrfpy.Color(255, 255, 255, 255) -mcrfpy.sceneUI("tutorial").append(title) - -print("Part 5: Entity Interactions - Tutorial loaded!") -print("- Bump into enemies to attack them") -print("- Push boulders by walking into them") -print("- Step on buttons to open doors") -print("- Enemies will pursue you when they can see you") \ No newline at end of file diff --git a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_0/README.md b/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_0/README.md deleted file mode 100644 index 7987478..0000000 --- a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_0/README.md +++ /dev/null @@ -1,253 +0,0 @@ -# Part 0 - Setting Up McRogueFace - -Welcome to the McRogueFace Roguelike Tutorial! This tutorial will teach you how to create a complete roguelike game using the McRogueFace game engine. Unlike traditional Python libraries, McRogueFace is a complete, portable game engine that includes everything you need to make and distribute games. - -## What is McRogueFace? - -McRogueFace is a high-performance game engine with Python scripting support. Think of it like Unity or Godot, but specifically designed for roguelikes and 2D games. It includes: - -- A complete Python 3.12 runtime (no installation needed!) -- High-performance C++ rendering and entity management -- Built-in UI components and scene management -- Integrated audio system -- Professional sprite-based graphics -- Easy distribution - your players don't need Python installed! - -## Prerequisites - -Before starting this tutorial, you should: - -- Have basic Python knowledge (variables, functions, classes) -- Be comfortable editing text files -- Have a text editor (VS Code, Sublime Text, Notepad++, etc.) - -That's it! Unlike other roguelike tutorials, you don't need Python installed - McRogueFace includes everything. - -## Getting McRogueFace - -### Step 1: Download the Engine - -1. Visit the McRogueFace releases page -2. Download the version for your operating system: - - `McRogueFace-Windows.zip` for Windows - - `McRogueFace-MacOS.zip` for macOS - - `McRogueFace-Linux.zip` for Linux - -### Step 2: Extract the Archive - -Extract the downloaded archive to a folder where you want to develop your game. You should see this structure: - -``` -McRogueFace/ -├── mcrogueface (or mcrogueface.exe on Windows) -├── scripts/ -│ └── game.py -├── assets/ -│ ├── sprites/ -│ ├── fonts/ -│ └── audio/ -└── lib/ -``` - -### Step 3: Run the Engine - -Run the McRogueFace executable: - -- **Windows**: Double-click `mcrogueface.exe` -- **Mac/Linux**: Open a terminal in the folder and run `./mcrogueface` - -You should see a window open with the default McRogueFace demo. This shows the engine is working correctly! - -## Your First McRogueFace Script - -Let's modify the engine to display "Hello Roguelike!" instead of the default demo. - -### Step 1: Open game.py - -Open `scripts/game.py` in your text editor. You'll see the default demo code. Replace it entirely with: - -```python -import mcrfpy - -# Create a new scene called "hello" -mcrfpy.createScene("hello") - -# Switch to our new scene -mcrfpy.setScene("hello") - -# Get the UI container for our scene -ui = mcrfpy.sceneUI("hello") - -# Create a text caption -caption = mcrfpy.Caption("Hello Roguelike!", 400, 300) -caption.font_size = 32 -caption.fill_color = mcrfpy.Color(255, 255, 255) # White text - -# Add the caption to our scene -ui.append(caption) - -# Create a smaller instruction caption -instruction = mcrfpy.Caption("Press ESC to exit", 400, 350) -instruction.font_size = 16 -instruction.fill_color = mcrfpy.Color(200, 200, 200) # Light gray -ui.append(instruction) - -# Set up a simple key handler -def handle_keys(key, state): - if state == "start" and key == "Escape": - mcrfpy.setScene(None) # This exits the game - -mcrfpy.keypressScene(handle_keys) - -print("Hello Roguelike is running!") -``` - -### Step 2: Save and Run - -1. Save the file -2. If McRogueFace is still running, it will automatically reload! -3. If not, run the engine again - -You should now see "Hello Roguelike!" displayed in the window. - -### Step 3: Understanding the Code - -Let's break down what we just wrote: - -1. **Import mcrfpy**: This is McRogueFace's Python API -2. **Create a scene**: Scenes are like game states (menu, gameplay, inventory, etc.) -3. **UI elements**: We create Caption objects for text display -4. **Colors**: McRogueFace uses RGB colors (0-255 for each component) -5. **Input handling**: We set up a callback for keyboard input -6. **Scene switching**: Setting the scene to None exits the game - -## Key Differences from Pure Python Development - -### The Game Loop - -Unlike typical Python scripts, McRogueFace runs your code inside its game loop: - -1. The engine starts and loads `scripts/game.py` -2. Your script sets up scenes, UI elements, and callbacks -3. The engine runs at 60 FPS, handling rendering and input -4. Your callbacks are triggered by game events - -### Hot Reloading - -McRogueFace can reload your scripts while running! Just save your changes and the engine will reload automatically. This makes development incredibly fast. - -### Asset Pipeline - -McRogueFace includes a complete asset system: - -- **Sprites**: Place images in `assets/sprites/` -- **Fonts**: TrueType fonts in `assets/fonts/` -- **Audio**: Sound effects and music in `assets/audio/` - -We'll explore these in later lessons. - -## Testing Your Setup - -Let's create a more interactive test to ensure everything is working properly: - -```python -import mcrfpy - -# Create our test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") -ui = mcrfpy.sceneUI("test") - -# Create a background frame -background = mcrfpy.Frame(0, 0, 1024, 768) -background.fill_color = mcrfpy.Color(20, 20, 30) # Dark blue-gray -ui.append(background) - -# Title text -title = mcrfpy.Caption("McRogueFace Setup Test", 512, 100) -title.font_size = 36 -title.fill_color = mcrfpy.Color(255, 255, 100) # Yellow -ui.append(title) - -# Status text that will update -status_text = mcrfpy.Caption("Press any key to test input...", 512, 300) -status_text.font_size = 20 -status_text.fill_color = mcrfpy.Color(200, 200, 200) -ui.append(status_text) - -# Instructions -instructions = [ - "Arrow Keys: Test movement input", - "Space: Test action input", - "Mouse Click: Test mouse input", - "ESC: Exit" -] - -y_offset = 400 -for instruction in instructions: - inst_caption = mcrfpy.Caption(instruction, 512, y_offset) - inst_caption.font_size = 16 - inst_caption.fill_color = mcrfpy.Color(150, 150, 150) - ui.append(inst_caption) - y_offset += 30 - -# Input handler -def handle_input(key, state): - if state != "start": - return - - if key == "Escape": - mcrfpy.setScene(None) - else: - status_text.text = f"You pressed: {key}" - status_text.fill_color = mcrfpy.Color(100, 255, 100) # Green - -# Set up input handling -mcrfpy.keypressScene(handle_input) - -print("Setup test is running! Try pressing different keys.") -``` - -## Troubleshooting - -### Engine Won't Start - -- **Windows**: Make sure you extracted all files, not just the .exe -- **Mac**: You may need to right-click and select "Open" the first time -- **Linux**: Make sure the file is executable: `chmod +x mcrogueface` - -### Scripts Not Loading - -- Ensure your script is named exactly `game.py` in the `scripts/` folder -- Check the console output for Python errors -- Make sure you're using Python 3 syntax - -### Performance Issues - -- McRogueFace should run smoothly at 60 FPS -- If not, check if your graphics drivers are updated -- The engine shows FPS in the window title - -## What's Next? - -Congratulations! You now have McRogueFace set up and running. You've learned: - -- How to download and run the McRogueFace engine -- The basic structure of a McRogueFace project -- How to create scenes and UI elements -- How to handle keyboard input -- The development workflow with hot reloading - -In Part 1, we'll create our player character and implement movement. We'll explore McRogueFace's entity system and learn how to create a game world. - -## Why McRogueFace? - -Before we continue, let's highlight why McRogueFace is excellent for roguelike development: - -1. **No Installation Hassles**: Your players just download and run - no Python needed! -2. **Professional Performance**: C++ engine core means smooth gameplay even with hundreds of entities -3. **Built-in Features**: UI, audio, scenes, and animations are already there -4. **Easy Distribution**: Just zip your game folder and share it -5. **Rapid Development**: Hot reloading and Python scripting for quick iteration - -Ready to make a roguelike? Let's continue to Part 1! \ No newline at end of file diff --git a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_0/code/game.py b/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_0/code/game.py deleted file mode 100644 index 00c9de2..0000000 --- a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_0/code/game.py +++ /dev/null @@ -1,33 +0,0 @@ -import mcrfpy - -# Create a new scene called "hello" -mcrfpy.createScene("hello") - -# Switch to our new scene -mcrfpy.setScene("hello") - -# Get the UI container for our scene -ui = mcrfpy.sceneUI("hello") - -# Create a text caption -caption = mcrfpy.Caption("Hello Roguelike!", 400, 300) -caption.font_size = 32 -caption.fill_color = mcrfpy.Color(255, 255, 255) # White text - -# Add the caption to our scene -ui.append(caption) - -# Create a smaller instruction caption -instruction = mcrfpy.Caption("Press ESC to exit", 400, 350) -instruction.font_size = 16 -instruction.fill_color = mcrfpy.Color(200, 200, 200) # Light gray -ui.append(instruction) - -# Set up a simple key handler -def handle_keys(key, state): - if state == "start" and key == "Escape": - mcrfpy.setScene(None) # This exits the game - -mcrfpy.keypressScene(handle_keys) - -print("Hello Roguelike is running!") \ No newline at end of file diff --git a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_0/code/setup_test.py b/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_0/code/setup_test.py deleted file mode 100644 index 0b39a49..0000000 --- a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_0/code/setup_test.py +++ /dev/null @@ -1,55 +0,0 @@ -import mcrfpy - -# Create our test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") -ui = mcrfpy.sceneUI("test") - -# Create a background frame -background = mcrfpy.Frame(0, 0, 1024, 768) -background.fill_color = mcrfpy.Color(20, 20, 30) # Dark blue-gray -ui.append(background) - -# Title text -title = mcrfpy.Caption("McRogueFace Setup Test", 512, 100) -title.font_size = 36 -title.fill_color = mcrfpy.Color(255, 255, 100) # Yellow -ui.append(title) - -# Status text that will update -status_text = mcrfpy.Caption("Press any key to test input...", 512, 300) -status_text.font_size = 20 -status_text.fill_color = mcrfpy.Color(200, 200, 200) -ui.append(status_text) - -# Instructions -instructions = [ - "Arrow Keys: Test movement input", - "Space: Test action input", - "Mouse Click: Test mouse input", - "ESC: Exit" -] - -y_offset = 400 -for instruction in instructions: - inst_caption = mcrfpy.Caption(instruction, 512, y_offset) - inst_caption.font_size = 16 - inst_caption.fill_color = mcrfpy.Color(150, 150, 150) - ui.append(inst_caption) - y_offset += 30 - -# Input handler -def handle_input(key, state): - if state != "start": - return - - if key == "Escape": - mcrfpy.setScene(None) - else: - status_text.text = f"You pressed: {key}" - status_text.fill_color = mcrfpy.Color(100, 255, 100) # Green - -# Set up input handling -mcrfpy.keypressScene(handle_input) - -print("Setup test is running! Try pressing different keys.") \ No newline at end of file diff --git a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_1/README.md b/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_1/README.md deleted file mode 100644 index f67809d..0000000 --- a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_1/README.md +++ /dev/null @@ -1,457 +0,0 @@ -# Part 1 - Drawing the '@' Symbol and Moving It Around - -In Part 0, we set up McRogueFace and created a simple "Hello Roguelike" scene. Now it's time to create the foundation of our game: a player character that can move around the screen. - -In traditional roguelikes, the player is represented by the '@' symbol. We'll honor that tradition while taking advantage of McRogueFace's powerful sprite-based rendering system. - -## Understanding McRogueFace's Architecture - -Before we dive into code, let's understand two key concepts in McRogueFace: - -### Grid - The Game World - -A `Grid` represents your game world. It's a 2D array of tiles where each tile can be: -- **Walkable or not** (for collision detection) -- **Transparent or not** (for field of view, which we'll cover later) -- **Have a visual appearance** (sprite index and color) - -Think of the Grid as the dungeon floor, walls, and other static elements. - -### Entity - Things That Move - -An `Entity` represents anything that can move around on the Grid: -- The player character -- Monsters -- Items (if you want them to be thrown or moved) -- Projectiles - -Entities exist "on top of" the Grid and automatically handle smooth movement animation between tiles. - -## Creating Our Game World - -Let's start by creating a simple room for our player to move around in. Create a new `game.py`: - -```python -import mcrfpy - -# Define some constants for our tile types -FLOOR_TILE = 0 -WALL_TILE = 1 -PLAYER_SPRITE = 2 - -# Window configuration -mcrfpy.createScene("game") -mcrfpy.setScene("game") - -# Configure window properties -window = mcrfpy.Window.get() -window.title = "McRogueFace Roguelike - Part 1" - -# Get the UI container for our scene -ui = mcrfpy.sceneUI("game") - -# Create a dark background -background = mcrfpy.Frame(0, 0, 1024, 768) -background.fill_color = mcrfpy.Color(0, 0, 0) -ui.append(background) -``` - -Now we need to set up our tileset. For this tutorial, we'll use ASCII-style sprites. McRogueFace comes with a built-in ASCII tileset: - -```python -# Load the ASCII tileset -# This tileset has characters mapped to sprite indices -# For example: @ = 64, # = 35, . = 46 -tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16) - -# Create the game grid -# 50x30 tiles is a good size for a roguelike -GRID_WIDTH = 50 -GRID_HEIGHT = 30 - -grid = mcrfpy.Grid(grid_x=GRID_WIDTH, grid_y=GRID_HEIGHT, texture=tileset) -grid.position = (100, 100) # Position on screen -grid.size = (800, 480) # Size in pixels - -# Add the grid to our UI -ui.append(grid) -``` - -## Initializing the Game World - -Now let's fill our grid with a simple room: - -```python -def create_room(): - """Create a room with walls around the edges""" - # Fill everything with floor tiles first - for y in range(GRID_HEIGHT): - for x in range(GRID_WIDTH): - cell = grid.at(x, y) - cell.walkable = True - cell.transparent = True - cell.sprite_index = 46 # '.' character - cell.color = mcrfpy.Color(50, 50, 50) # Dark gray floor - - # Create walls around the edges - for x in range(GRID_WIDTH): - # Top wall - cell = grid.at(x, 0) - cell.walkable = False - cell.transparent = False - cell.sprite_index = 35 # '#' character - cell.color = mcrfpy.Color(100, 100, 100) # Gray walls - - # Bottom wall - cell = grid.at(x, GRID_HEIGHT - 1) - cell.walkable = False - cell.transparent = False - cell.sprite_index = 35 # '#' character - cell.color = mcrfpy.Color(100, 100, 100) - - for y in range(GRID_HEIGHT): - # Left wall - cell = grid.at(0, y) - cell.walkable = False - cell.transparent = False - cell.sprite_index = 35 # '#' character - cell.color = mcrfpy.Color(100, 100, 100) - - # Right wall - cell = grid.at(GRID_WIDTH - 1, y) - cell.walkable = False - cell.transparent = False - cell.sprite_index = 35 # '#' character - cell.color = mcrfpy.Color(100, 100, 100) - -# Create the room -create_room() -``` - -## Creating the Player - -Now let's add our player character: - -```python -# Create the player entity -player = mcrfpy.Entity(x=GRID_WIDTH // 2, y=GRID_HEIGHT // 2, grid=grid) -player.sprite_index = 64 # '@' character -player.color = mcrfpy.Color(255, 255, 255) # White - -# The entity is automatically added to the grid when we pass grid= parameter -# This is equivalent to: grid.entities.append(player) -``` - -## Handling Input - -McRogueFace uses a callback system for input. For a turn-based roguelike, we only care about key presses, not releases: - -```python -def handle_input(key, state): - """Handle keyboard input for player movement""" - # Only process key presses, not releases - if state != "start": - return - - # Movement deltas - dx, dy = 0, 0 - - # Arrow keys - if key == "Up": - dy = -1 - elif key == "Down": - dy = 1 - elif key == "Left": - dx = -1 - elif key == "Right": - dx = 1 - - # Numpad movement (for true roguelike feel!) - elif key == "Num7": # Northwest - dx, dy = -1, -1 - elif key == "Num8": # North - dy = -1 - elif key == "Num9": # Northeast - dx, dy = 1, -1 - elif key == "Num4": # West - dx = -1 - elif key == "Num6": # East - dx = 1 - elif key == "Num1": # Southwest - dx, dy = -1, 1 - elif key == "Num2": # South - dy = 1 - elif key == "Num3": # Southeast - dx, dy = 1, 1 - - # Escape to quit - elif key == "Escape": - mcrfpy.setScene(None) - return - - # If there's movement, try to move the player - if dx != 0 or dy != 0: - move_player(dx, dy) - -# Register the input handler -mcrfpy.keypressScene(handle_input) -``` - -## Implementing Movement with Collision Detection - -Now let's implement the movement function with proper collision detection: - -```python -def move_player(dx, dy): - """Move the player if the destination is walkable""" - # Calculate new position - new_x = player.x + dx - new_y = player.y + dy - - # Check bounds - if new_x < 0 or new_x >= GRID_WIDTH or new_y < 0 or new_y >= GRID_HEIGHT: - return - - # Check if the destination is walkable - destination = grid.at(new_x, new_y) - if destination.walkable: - # Move the player - player.x = new_x - player.y = new_y - # The entity will automatically animate to the new position! -``` - -## Adding Visual Polish - -Let's add some UI elements to make our game look more polished: - -```python -# Add a title -title = mcrfpy.Caption("McRogueFace Roguelike", 512, 30) -title.font_size = 24 -title.fill_color = mcrfpy.Color(255, 255, 100) # Yellow -ui.append(title) - -# Add instructions -instructions = mcrfpy.Caption("Arrow Keys or Numpad to move, ESC to quit", 512, 60) -instructions.font_size = 16 -instructions.fill_color = mcrfpy.Color(200, 200, 200) # Light gray -ui.append(instructions) - -# Add a status line at the bottom -status = mcrfpy.Caption("@ You", 100, 600) -status.font_size = 18 -status.fill_color = mcrfpy.Color(255, 255, 255) -ui.append(status) -``` - -## Complete Code - -Here's the complete `game.py` for Part 1: - -```python -import mcrfpy - -# Window configuration -mcrfpy.createScene("game") -mcrfpy.setScene("game") - -window = mcrfpy.Window.get() -window.title = "McRogueFace Roguelike - Part 1" - -# Get the UI container for our scene -ui = mcrfpy.sceneUI("game") - -# Create a dark background -background = mcrfpy.Frame(0, 0, 1024, 768) -background.fill_color = mcrfpy.Color(0, 0, 0) -ui.append(background) - -# Load the ASCII tileset -tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16) - -# Create the game grid -GRID_WIDTH = 50 -GRID_HEIGHT = 30 - -grid = mcrfpy.Grid(grid_x=GRID_WIDTH, grid_y=GRID_HEIGHT, texture=tileset) -grid.position = (100, 100) -grid.size = (800, 480) -ui.append(grid) - -def create_room(): - """Create a room with walls around the edges""" - # Fill everything with floor tiles first - for y in range(GRID_HEIGHT): - for x in range(GRID_WIDTH): - cell = grid.at(x, y) - cell.walkable = True - cell.transparent = True - cell.sprite_index = 46 # '.' character - cell.color = mcrfpy.Color(50, 50, 50) # Dark gray floor - - # Create walls around the edges - for x in range(GRID_WIDTH): - # Top wall - cell = grid.at(x, 0) - cell.walkable = False - cell.transparent = False - cell.sprite_index = 35 # '#' character - cell.color = mcrfpy.Color(100, 100, 100) # Gray walls - - # Bottom wall - cell = grid.at(x, GRID_HEIGHT - 1) - cell.walkable = False - cell.transparent = False - cell.sprite_index = 35 # '#' character - cell.color = mcrfpy.Color(100, 100, 100) - - for y in range(GRID_HEIGHT): - # Left wall - cell = grid.at(0, y) - cell.walkable = False - cell.transparent = False - cell.sprite_index = 35 # '#' character - cell.color = mcrfpy.Color(100, 100, 100) - - # Right wall - cell = grid.at(GRID_WIDTH - 1, y) - cell.walkable = False - cell.transparent = False - cell.sprite_index = 35 # '#' character - cell.color = mcrfpy.Color(100, 100, 100) - -# Create the room -create_room() - -# Create the player entity -player = mcrfpy.Entity(x=GRID_WIDTH // 2, y=GRID_HEIGHT // 2, grid=grid) -player.sprite_index = 64 # '@' character -player.color = mcrfpy.Color(255, 255, 255) # White - -def move_player(dx, dy): - """Move the player if the destination is walkable""" - # Calculate new position - new_x = player.x + dx - new_y = player.y + dy - - # Check bounds - if new_x < 0 or new_x >= GRID_WIDTH or new_y < 0 or new_y >= GRID_HEIGHT: - return - - # Check if the destination is walkable - destination = grid.at(new_x, new_y) - if destination.walkable: - # Move the player - player.x = new_x - player.y = new_y - -def handle_input(key, state): - """Handle keyboard input for player movement""" - # Only process key presses, not releases - if state != "start": - return - - # Movement deltas - dx, dy = 0, 0 - - # Arrow keys - if key == "Up": - dy = -1 - elif key == "Down": - dy = 1 - elif key == "Left": - dx = -1 - elif key == "Right": - dx = 1 - - # Numpad movement (for true roguelike feel!) - elif key == "Num7": # Northwest - dx, dy = -1, -1 - elif key == "Num8": # North - dy = -1 - elif key == "Num9": # Northeast - dx, dy = 1, -1 - elif key == "Num4": # West - dx = -1 - elif key == "Num6": # East - dx = 1 - elif key == "Num1": # Southwest - dx, dy = -1, 1 - elif key == "Num2": # South - dy = 1 - elif key == "Num3": # Southeast - dx, dy = 1, 1 - - # Escape to quit - elif key == "Escape": - mcrfpy.setScene(None) - return - - # If there's movement, try to move the player - if dx != 0 or dy != 0: - move_player(dx, dy) - -# Register the input handler -mcrfpy.keypressScene(handle_input) - -# Add UI elements -title = mcrfpy.Caption("McRogueFace Roguelike", 512, 30) -title.font_size = 24 -title.fill_color = mcrfpy.Color(255, 255, 100) -ui.append(title) - -instructions = mcrfpy.Caption("Arrow Keys or Numpad to move, ESC to quit", 512, 60) -instructions.font_size = 16 -instructions.fill_color = mcrfpy.Color(200, 200, 200) -ui.append(instructions) - -status = mcrfpy.Caption("@ You", 100, 600) -status.font_size = 18 -status.fill_color = mcrfpy.Color(255, 255, 255) -ui.append(status) - -print("Part 1: The @ symbol moves!") -``` - -## Understanding What We've Built - -Let's review the key concepts we've implemented: - -1. **Grid-Entity Architecture**: The Grid represents our static world (floors and walls), while the Entity (player) moves on top of it. - -2. **Collision Detection**: By checking the `walkable` property of grid cells, we prevent the player from walking through walls. - -3. **Turn-Based Input**: By only responding to key presses (not releases), we've created true turn-based movement. - -4. **Visual Feedback**: The Entity system automatically animates movement between tiles, giving smooth visual feedback. - -## Exercises - -Try these modifications to deepen your understanding: - -1. **Add More Rooms**: Create multiple rooms connected by corridors -2. **Different Tile Types**: Add doors (walkable but different appearance) -3. **Sprint Movement**: Hold Shift to move multiple tiles at once -4. **Mouse Support**: Click a tile to pathfind to it (we'll cover pathfinding properly later) - -## ASCII Sprite Reference - -Here are some useful ASCII character indices for the default tileset: -- @ (player): 64 -- # (wall): 35 -- . (floor): 46 -- + (door): 43 -- ~ (water): 126 -- % (item): 37 -- ! (potion): 33 - -## What's Next? - -In Part 2, we'll expand our world with: -- A proper Entity system for managing multiple objects -- NPCs that can also move around -- A more interesting map layout -- The beginning of our game architecture - -The foundation is set - you have a player character that can move around a world with collision detection. This is the core of any roguelike game! \ No newline at end of file diff --git a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_1/code/game.py b/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_1/code/game.py deleted file mode 100644 index 2f0c157..0000000 --- a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_1/code/game.py +++ /dev/null @@ -1,162 +0,0 @@ -import mcrfpy - -# Window configuration -mcrfpy.createScene("game") -mcrfpy.setScene("game") - -window = mcrfpy.Window.get() -window.title = "McRogueFace Roguelike - Part 1" - -# Get the UI container for our scene -ui = mcrfpy.sceneUI("game") - -# Create a dark background -background = mcrfpy.Frame(0, 0, 1024, 768) -background.fill_color = mcrfpy.Color(0, 0, 0) -ui.append(background) - -# Load the ASCII tileset -tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16) - -# Create the game grid -GRID_WIDTH = 50 -GRID_HEIGHT = 30 - -grid = mcrfpy.Grid(grid_x=GRID_WIDTH, grid_y=GRID_HEIGHT, texture=tileset) -grid.position = (100, 100) -grid.size = (800, 480) -ui.append(grid) - -def create_room(): - """Create a room with walls around the edges""" - # Fill everything with floor tiles first - for y in range(GRID_HEIGHT): - for x in range(GRID_WIDTH): - cell = grid.at(x, y) - cell.walkable = True - cell.transparent = True - cell.sprite_index = 46 # '.' character - cell.color = mcrfpy.Color(50, 50, 50) # Dark gray floor - - # Create walls around the edges - for x in range(GRID_WIDTH): - # Top wall - cell = grid.at(x, 0) - cell.walkable = False - cell.transparent = False - cell.sprite_index = 35 # '#' character - cell.color = mcrfpy.Color(100, 100, 100) # Gray walls - - # Bottom wall - cell = grid.at(x, GRID_HEIGHT - 1) - cell.walkable = False - cell.transparent = False - cell.sprite_index = 35 # '#' character - cell.color = mcrfpy.Color(100, 100, 100) - - for y in range(GRID_HEIGHT): - # Left wall - cell = grid.at(0, y) - cell.walkable = False - cell.transparent = False - cell.sprite_index = 35 # '#' character - cell.color = mcrfpy.Color(100, 100, 100) - - # Right wall - cell = grid.at(GRID_WIDTH - 1, y) - cell.walkable = False - cell.transparent = False - cell.sprite_index = 35 # '#' character - cell.color = mcrfpy.Color(100, 100, 100) - -# Create the room -create_room() - -# Create the player entity -player = mcrfpy.Entity(x=GRID_WIDTH // 2, y=GRID_HEIGHT // 2, grid=grid) -player.sprite_index = 64 # '@' character -player.color = mcrfpy.Color(255, 255, 255) # White - -def move_player(dx, dy): - """Move the player if the destination is walkable""" - # Calculate new position - new_x = player.x + dx - new_y = player.y + dy - - # Check bounds - if new_x < 0 or new_x >= GRID_WIDTH or new_y < 0 or new_y >= GRID_HEIGHT: - return - - # Check if the destination is walkable - destination = grid.at(new_x, new_y) - if destination.walkable: - # Move the player - player.x = new_x - player.y = new_y - -def handle_input(key, state): - """Handle keyboard input for player movement""" - # Only process key presses, not releases - if state != "start": - return - - # Movement deltas - dx, dy = 0, 0 - - # Arrow keys - if key == "Up": - dy = -1 - elif key == "Down": - dy = 1 - elif key == "Left": - dx = -1 - elif key == "Right": - dx = 1 - - # Numpad movement (for true roguelike feel!) - elif key == "Num7": # Northwest - dx, dy = -1, -1 - elif key == "Num8": # North - dy = -1 - elif key == "Num9": # Northeast - dx, dy = 1, -1 - elif key == "Num4": # West - dx = -1 - elif key == "Num6": # East - dx = 1 - elif key == "Num1": # Southwest - dx, dy = -1, 1 - elif key == "Num2": # South - dy = 1 - elif key == "Num3": # Southeast - dx, dy = 1, 1 - - # Escape to quit - elif key == "Escape": - mcrfpy.setScene(None) - return - - # If there's movement, try to move the player - if dx != 0 or dy != 0: - move_player(dx, dy) - -# Register the input handler -mcrfpy.keypressScene(handle_input) - -# Add UI elements -title = mcrfpy.Caption("McRogueFace Roguelike", 512, 30) -title.font_size = 24 -title.fill_color = mcrfpy.Color(255, 255, 100) -ui.append(title) - -instructions = mcrfpy.Caption("Arrow Keys or Numpad to move, ESC to quit", 512, 60) -instructions.font_size = 16 -instructions.fill_color = mcrfpy.Color(200, 200, 200) -ui.append(instructions) - -status = mcrfpy.Caption("@ You", 100, 600) -status.font_size = 18 -status.fill_color = mcrfpy.Color(255, 255, 255) -ui.append(status) - -print("Part 1: The @ symbol moves!") \ No newline at end of file diff --git a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_2/README.md b/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_2/README.md deleted file mode 100644 index b3731be..0000000 --- a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_2/README.md +++ /dev/null @@ -1,562 +0,0 @@ -# Part 2 - The Generic Entity, the Render Functions, and the Map - -In Part 1, we created a player character that could move around a simple room. Now it's time to build a proper architecture for our roguelike. We'll create a flexible entity system, a proper map structure, and organize our code for future expansion. - -## Understanding Game Architecture - -Before diving into code, let's understand the architecture we're building: - -1. **Entities**: Anything that can exist in the game world (player, monsters, items) -2. **Game Map**: The dungeon structure with tiles that can be walls or floors -3. **Game Engine**: Coordinates everything - entities, map, input, and rendering - -In McRogueFace, we'll adapt these concepts to work with the engine's scene-based architecture. - -## Creating a Flexible Entity System - -While McRogueFace provides a built-in `Entity` class, we'll create a wrapper to add game-specific functionality: - -```python -class GameObject: - """Base class for all game objects (player, monsters, items)""" - - def __init__(self, x, y, sprite_index, color, name, blocks=False): - self.x = x - self.y = y - self.sprite_index = sprite_index - self.color = color - self.name = name - self.blocks = blocks # Does this entity block movement? - self._entity = None # The McRogueFace entity - self.grid = None # Reference to the grid - - def attach_to_grid(self, grid): - """Attach this game object to a McRogueFace grid""" - self.grid = grid - self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid) - self._entity.sprite_index = self.sprite_index - self._entity.color = self.color - - def move(self, dx, dy): - """Move by the given amount if possible""" - if not self.grid: - return - - new_x = self.x + dx - new_y = self.y + dy - - # Update our position - self.x = new_x - self.y = new_y - - # Update the visual entity - if self._entity: - self._entity.x = new_x - self._entity.y = new_y - - def destroy(self): - """Remove this entity from the game""" - if self._entity and self.grid: - # Find and remove from grid's entity list - for i, entity in enumerate(self.grid.entities): - if entity == self._entity: - del self.grid.entities[i] - break - self._entity = None -``` - -## Building the Game Map - -Let's create a proper map class that manages our dungeon: - -```python -class GameMap: - """Manages the game world""" - - def __init__(self, width, height): - self.width = width - self.height = height - self.grid = None - self.entities = [] # List of GameObjects - - def create_grid(self, tileset): - """Create the McRogueFace grid""" - self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset) - self.grid.position = (100, 100) - self.grid.size = (800, 480) - - # Initialize all tiles as walls - self.fill_with_walls() - - return self.grid - - def fill_with_walls(self): - """Fill the entire map with wall tiles""" - for y in range(self.height): - for x in range(self.width): - self.set_tile(x, y, walkable=False, transparent=False, - sprite_index=35, color=(100, 100, 100)) - - def set_tile(self, x, y, walkable, transparent, sprite_index, color): - """Set properties for a specific tile""" - if 0 <= x < self.width and 0 <= y < self.height: - cell = self.grid.at(x, y) - cell.walkable = walkable - cell.transparent = transparent - cell.sprite_index = sprite_index - cell.color = mcrfpy.Color(*color) - - def create_room(self, x1, y1, x2, y2): - """Carve out a room in the map""" - # Make sure coordinates are in the right order - x1, x2 = min(x1, x2), max(x1, x2) - y1, y2 = min(y1, y2), max(y1, y2) - - # Carve out floor tiles - for y in range(y1, y2 + 1): - for x in range(x1, x2 + 1): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, color=(50, 50, 50)) - - def create_tunnel_h(self, x1, x2, y): - """Create a horizontal tunnel""" - for x in range(min(x1, x2), max(x1, x2) + 1): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, color=(50, 50, 50)) - - def create_tunnel_v(self, y1, y2, x): - """Create a vertical tunnel""" - for y in range(min(y1, y2), max(y1, y2) + 1): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, color=(50, 50, 50)) - - def is_blocked(self, x, y): - """Check if a tile blocks movement""" - # Check map boundaries - if x < 0 or x >= self.width or y < 0 or y >= self.height: - return True - - # Check if tile is walkable - if not self.grid.at(x, y).walkable: - return True - - # Check if any blocking entity is at this position - for entity in self.entities: - if entity.blocks and entity.x == x and entity.y == y: - return True - - return False - - def add_entity(self, entity): - """Add a GameObject to the map""" - self.entities.append(entity) - entity.attach_to_grid(self.grid) - - def get_blocking_entity_at(self, x, y): - """Return any blocking entity at the given position""" - for entity in self.entities: - if entity.blocks and entity.x == x and entity.y == y: - return entity - return None -``` - -## Creating the Game Engine - -Now let's build our game engine to tie everything together: - -```python -class Engine: - """Main game engine that manages game state""" - - def __init__(self): - self.game_map = None - self.player = None - self.entities = [] - - # Create the game scene - mcrfpy.createScene("game") - mcrfpy.setScene("game") - - # Configure window - window = mcrfpy.Window.get() - window.title = "McRogueFace Roguelike - Part 2" - - # Get UI container - self.ui = mcrfpy.sceneUI("game") - - # Add background - background = mcrfpy.Frame(0, 0, 1024, 768) - background.fill_color = mcrfpy.Color(0, 0, 0) - self.ui.append(background) - - # Load tileset - self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16) - - # Create the game world - self.setup_game() - - # Setup input handling - self.setup_input() - - # Add UI elements - self.setup_ui() - - def setup_game(self): - """Initialize the game world""" - # Create the map - self.game_map = GameMap(50, 30) - grid = self.game_map.create_grid(self.tileset) - self.ui.append(grid) - - # Create some rooms - self.game_map.create_room(10, 10, 20, 20) - self.game_map.create_room(30, 15, 40, 25) - self.game_map.create_room(15, 22, 25, 28) - - # Connect rooms with tunnels - self.game_map.create_tunnel_h(20, 30, 15) - self.game_map.create_tunnel_v(20, 22, 20) - - # Create player - self.player = GameObject(15, 15, 64, (255, 255, 255), "Player", blocks=True) - self.game_map.add_entity(self.player) - - # Create an NPC - npc = GameObject(35, 20, 64, (255, 255, 0), "NPC", blocks=True) - self.game_map.add_entity(npc) - self.entities.append(npc) - - # Create some items (non-blocking) - potion = GameObject(12, 12, 33, (255, 0, 255), "Potion", blocks=False) - self.game_map.add_entity(potion) - self.entities.append(potion) - - def handle_movement(self, dx, dy): - """Handle player movement""" - new_x = self.player.x + dx - new_y = self.player.y + dy - - # Check if movement is blocked - if not self.game_map.is_blocked(new_x, new_y): - self.player.move(dx, dy) - else: - # Check if we bumped into an entity - target = self.game_map.get_blocking_entity_at(new_x, new_y) - if target: - print(f"You bump into the {target.name}!") - - def setup_input(self): - """Setup keyboard input handling""" - def handle_keys(key, state): - if state != "start": - return - - # Movement keys - movement = { - "Up": (0, -1), - "Down": (0, 1), - "Left": (-1, 0), - "Right": (1, 0), - "Num7": (-1, -1), - "Num8": (0, -1), - "Num9": (1, -1), - "Num4": (-1, 0), - "Num6": (1, 0), - "Num1": (-1, 1), - "Num2": (0, 1), - "Num3": (1, 1), - } - - if key in movement: - dx, dy = movement[key] - self.handle_movement(dx, dy) - elif key == "Escape": - mcrfpy.setScene(None) - - mcrfpy.keypressScene(handle_keys) - - def setup_ui(self): - """Setup UI elements""" - # Title - title = mcrfpy.Caption("McRogueFace Roguelike - Part 2", 512, 30) - title.font_size = 24 - title.fill_color = mcrfpy.Color(255, 255, 100) - self.ui.append(title) - - # Instructions - instructions = mcrfpy.Caption("Explore the dungeon! ESC to quit", 512, 60) - instructions.font_size = 16 - instructions.fill_color = mcrfpy.Color(200, 200, 200) - self.ui.append(instructions) -``` - -## Putting It All Together - -Here's the complete `game.py` file: - -```python -import mcrfpy - -class GameObject: - """Base class for all game objects (player, monsters, items)""" - - def __init__(self, x, y, sprite_index, color, name, blocks=False): - self.x = x - self.y = y - self.sprite_index = sprite_index - self.color = color - self.name = name - self.blocks = blocks - self._entity = None - self.grid = None - - def attach_to_grid(self, grid): - """Attach this game object to a McRogueFace grid""" - self.grid = grid - self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid) - self._entity.sprite_index = self.sprite_index - self._entity.color = mcrfpy.Color(*self.color) - - def move(self, dx, dy): - """Move by the given amount if possible""" - if not self.grid: - return - - new_x = self.x + dx - new_y = self.y + dy - - self.x = new_x - self.y = new_y - - if self._entity: - self._entity.x = new_x - self._entity.y = new_y - -class GameMap: - """Manages the game world""" - - def __init__(self, width, height): - self.width = width - self.height = height - self.grid = None - self.entities = [] - - def create_grid(self, tileset): - """Create the McRogueFace grid""" - self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset) - self.grid.position = (100, 100) - self.grid.size = (800, 480) - self.fill_with_walls() - return self.grid - - def fill_with_walls(self): - """Fill the entire map with wall tiles""" - for y in range(self.height): - for x in range(self.width): - self.set_tile(x, y, walkable=False, transparent=False, - sprite_index=35, color=(100, 100, 100)) - - def set_tile(self, x, y, walkable, transparent, sprite_index, color): - """Set properties for a specific tile""" - if 0 <= x < self.width and 0 <= y < self.height: - cell = self.grid.at(x, y) - cell.walkable = walkable - cell.transparent = transparent - cell.sprite_index = sprite_index - cell.color = mcrfpy.Color(*color) - - def create_room(self, x1, y1, x2, y2): - """Carve out a room in the map""" - x1, x2 = min(x1, x2), max(x1, x2) - y1, y2 = min(y1, y2), max(y1, y2) - - for y in range(y1, y2 + 1): - for x in range(x1, x2 + 1): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, color=(50, 50, 50)) - - def create_tunnel_h(self, x1, x2, y): - """Create a horizontal tunnel""" - for x in range(min(x1, x2), max(x1, x2) + 1): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, color=(50, 50, 50)) - - def create_tunnel_v(self, y1, y2, x): - """Create a vertical tunnel""" - for y in range(min(y1, y2), max(y1, y2) + 1): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, color=(50, 50, 50)) - - def is_blocked(self, x, y): - """Check if a tile blocks movement""" - if x < 0 or x >= self.width or y < 0 or y >= self.height: - return True - - if not self.grid.at(x, y).walkable: - return True - - for entity in self.entities: - if entity.blocks and entity.x == x and entity.y == y: - return True - - return False - - def add_entity(self, entity): - """Add a GameObject to the map""" - self.entities.append(entity) - entity.attach_to_grid(self.grid) - - def get_blocking_entity_at(self, x, y): - """Return any blocking entity at the given position""" - for entity in self.entities: - if entity.blocks and entity.x == x and entity.y == y: - return entity - return None - -class Engine: - """Main game engine that manages game state""" - - def __init__(self): - self.game_map = None - self.player = None - self.entities = [] - - mcrfpy.createScene("game") - mcrfpy.setScene("game") - - window = mcrfpy.Window.get() - window.title = "McRogueFace Roguelike - Part 2" - - self.ui = mcrfpy.sceneUI("game") - - background = mcrfpy.Frame(0, 0, 1024, 768) - background.fill_color = mcrfpy.Color(0, 0, 0) - self.ui.append(background) - - self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16) - - self.setup_game() - self.setup_input() - self.setup_ui() - - def setup_game(self): - """Initialize the game world""" - self.game_map = GameMap(50, 30) - grid = self.game_map.create_grid(self.tileset) - self.ui.append(grid) - - self.game_map.create_room(10, 10, 20, 20) - self.game_map.create_room(30, 15, 40, 25) - self.game_map.create_room(15, 22, 25, 28) - - self.game_map.create_tunnel_h(20, 30, 15) - self.game_map.create_tunnel_v(20, 22, 20) - - self.player = GameObject(15, 15, 64, (255, 255, 255), "Player", blocks=True) - self.game_map.add_entity(self.player) - - npc = GameObject(35, 20, 64, (255, 255, 0), "NPC", blocks=True) - self.game_map.add_entity(npc) - self.entities.append(npc) - - potion = GameObject(12, 12, 33, (255, 0, 255), "Potion", blocks=False) - self.game_map.add_entity(potion) - self.entities.append(potion) - - def handle_movement(self, dx, dy): - """Handle player movement""" - new_x = self.player.x + dx - new_y = self.player.y + dy - - if not self.game_map.is_blocked(new_x, new_y): - self.player.move(dx, dy) - else: - target = self.game_map.get_blocking_entity_at(new_x, new_y) - if target: - print(f"You bump into the {target.name}!") - - def setup_input(self): - """Setup keyboard input handling""" - def handle_keys(key, state): - if state != "start": - return - - movement = { - "Up": (0, -1), "Down": (0, 1), - "Left": (-1, 0), "Right": (1, 0), - "Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1), - "Num4": (-1, 0), "Num6": (1, 0), - "Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1), - } - - if key in movement: - dx, dy = movement[key] - self.handle_movement(dx, dy) - elif key == "Escape": - mcrfpy.setScene(None) - - mcrfpy.keypressScene(handle_keys) - - def setup_ui(self): - """Setup UI elements""" - title = mcrfpy.Caption("McRogueFace Roguelike - Part 2", 512, 30) - title.font_size = 24 - title.fill_color = mcrfpy.Color(255, 255, 100) - self.ui.append(title) - - instructions = mcrfpy.Caption("Explore the dungeon! ESC to quit", 512, 60) - instructions.font_size = 16 - instructions.fill_color = mcrfpy.Color(200, 200, 200) - self.ui.append(instructions) - -# Create and run the game -engine = Engine() -print("Part 2: Entities and Maps!") -``` - -## Understanding the Architecture - -### GameObject Class -Our `GameObject` class wraps McRogueFace's `Entity` and adds: -- Game logic properties (name, blocking) -- Position tracking independent of the visual entity -- Easy attachment/detachment from grids - -### GameMap Class -The `GameMap` manages: -- The McRogueFace `Grid` for visual representation -- A list of all entities in the map -- Collision detection including entity blocking -- Map generation utilities (rooms, tunnels) - -### Engine Class -The `Engine` coordinates everything: -- Scene and UI setup -- Game state management -- Input handling -- Entity-map interactions - -## Key Improvements from Part 1 - -1. **Proper Entity Management**: Multiple entities can exist and interact -2. **Blocking Entities**: Some entities block movement, others don't -3. **Map Generation**: Tools for creating rooms and tunnels -4. **Collision System**: Checks both tiles and entities -5. **Organized Code**: Clear separation of concerns - -## Exercises - -1. **Add More Entity Types**: Create different sprites for monsters, items, and NPCs -2. **Entity Interactions**: Make items disappear when walked over -3. **Random Map Generation**: Place rooms and tunnels randomly -4. **Entity Properties**: Add health, damage, or other attributes to GameObjects - -## What's Next? - -In Part 3, we'll implement proper dungeon generation with: -- Procedurally generated rooms -- Smart tunnel routing -- Entity spawning -- The beginning of a real roguelike dungeon! - -We now have a solid foundation with proper entity management and map structure. This architecture will serve us well as we add more complex features to our roguelike! \ No newline at end of file diff --git a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_2/code/game.py b/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_2/code/game.py deleted file mode 100644 index 38eef78..0000000 --- a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_2/code/game.py +++ /dev/null @@ -1,217 +0,0 @@ -import mcrfpy - -class GameObject: - """Base class for all game objects (player, monsters, items)""" - - def __init__(self, x, y, sprite_index, color, name, blocks=False): - self.x = x - self.y = y - self.sprite_index = sprite_index - self.color = color - self.name = name - self.blocks = blocks - self._entity = None - self.grid = None - - def attach_to_grid(self, grid): - """Attach this game object to a McRogueFace grid""" - self.grid = grid - self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid) - self._entity.sprite_index = self.sprite_index - self._entity.color = mcrfpy.Color(*self.color) - - def move(self, dx, dy): - """Move by the given amount if possible""" - if not self.grid: - return - - new_x = self.x + dx - new_y = self.y + dy - - self.x = new_x - self.y = new_y - - if self._entity: - self._entity.x = new_x - self._entity.y = new_y - -class GameMap: - """Manages the game world""" - - def __init__(self, width, height): - self.width = width - self.height = height - self.grid = None - self.entities = [] - - def create_grid(self, tileset): - """Create the McRogueFace grid""" - self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset) - self.grid.position = (100, 100) - self.grid.size = (800, 480) - self.fill_with_walls() - return self.grid - - def fill_with_walls(self): - """Fill the entire map with wall tiles""" - for y in range(self.height): - for x in range(self.width): - self.set_tile(x, y, walkable=False, transparent=False, - sprite_index=35, color=(100, 100, 100)) - - def set_tile(self, x, y, walkable, transparent, sprite_index, color): - """Set properties for a specific tile""" - if 0 <= x < self.width and 0 <= y < self.height: - cell = self.grid.at(x, y) - cell.walkable = walkable - cell.transparent = transparent - cell.sprite_index = sprite_index - cell.color = mcrfpy.Color(*color) - - def create_room(self, x1, y1, x2, y2): - """Carve out a room in the map""" - x1, x2 = min(x1, x2), max(x1, x2) - y1, y2 = min(y1, y2), max(y1, y2) - - for y in range(y1, y2 + 1): - for x in range(x1, x2 + 1): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, color=(50, 50, 50)) - - def create_tunnel_h(self, x1, x2, y): - """Create a horizontal tunnel""" - for x in range(min(x1, x2), max(x1, x2) + 1): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, color=(50, 50, 50)) - - def create_tunnel_v(self, y1, y2, x): - """Create a vertical tunnel""" - for y in range(min(y1, y2), max(y1, y2) + 1): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, color=(50, 50, 50)) - - def is_blocked(self, x, y): - """Check if a tile blocks movement""" - if x < 0 or x >= self.width or y < 0 or y >= self.height: - return True - - if not self.grid.at(x, y).walkable: - return True - - for entity in self.entities: - if entity.blocks and entity.x == x and entity.y == y: - return True - - return False - - def add_entity(self, entity): - """Add a GameObject to the map""" - self.entities.append(entity) - entity.attach_to_grid(self.grid) - - def get_blocking_entity_at(self, x, y): - """Return any blocking entity at the given position""" - for entity in self.entities: - if entity.blocks and entity.x == x and entity.y == y: - return entity - return None - -class Engine: - """Main game engine that manages game state""" - - def __init__(self): - self.game_map = None - self.player = None - self.entities = [] - - mcrfpy.createScene("game") - mcrfpy.setScene("game") - - window = mcrfpy.Window.get() - window.title = "McRogueFace Roguelike - Part 2" - - self.ui = mcrfpy.sceneUI("game") - - background = mcrfpy.Frame(0, 0, 1024, 768) - background.fill_color = mcrfpy.Color(0, 0, 0) - self.ui.append(background) - - self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16) - - self.setup_game() - self.setup_input() - self.setup_ui() - - def setup_game(self): - """Initialize the game world""" - self.game_map = GameMap(50, 30) - grid = self.game_map.create_grid(self.tileset) - self.ui.append(grid) - - self.game_map.create_room(10, 10, 20, 20) - self.game_map.create_room(30, 15, 40, 25) - self.game_map.create_room(15, 22, 25, 28) - - self.game_map.create_tunnel_h(20, 30, 15) - self.game_map.create_tunnel_v(20, 22, 20) - - self.player = GameObject(15, 15, 64, (255, 255, 255), "Player", blocks=True) - self.game_map.add_entity(self.player) - - npc = GameObject(35, 20, 64, (255, 255, 0), "NPC", blocks=True) - self.game_map.add_entity(npc) - self.entities.append(npc) - - potion = GameObject(12, 12, 33, (255, 0, 255), "Potion", blocks=False) - self.game_map.add_entity(potion) - self.entities.append(potion) - - def handle_movement(self, dx, dy): - """Handle player movement""" - new_x = self.player.x + dx - new_y = self.player.y + dy - - if not self.game_map.is_blocked(new_x, new_y): - self.player.move(dx, dy) - else: - target = self.game_map.get_blocking_entity_at(new_x, new_y) - if target: - print(f"You bump into the {target.name}!") - - def setup_input(self): - """Setup keyboard input handling""" - def handle_keys(key, state): - if state != "start": - return - - movement = { - "Up": (0, -1), "Down": (0, 1), - "Left": (-1, 0), "Right": (1, 0), - "Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1), - "Num4": (-1, 0), "Num6": (1, 0), - "Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1), - } - - if key in movement: - dx, dy = movement[key] - self.handle_movement(dx, dy) - elif key == "Escape": - mcrfpy.setScene(None) - - mcrfpy.keypressScene(handle_keys) - - def setup_ui(self): - """Setup UI elements""" - title = mcrfpy.Caption("McRogueFace Roguelike - Part 2", 512, 30) - title.font_size = 24 - title.fill_color = mcrfpy.Color(255, 255, 100) - self.ui.append(title) - - instructions = mcrfpy.Caption("Explore the dungeon! ESC to quit", 512, 60) - instructions.font_size = 16 - instructions.fill_color = mcrfpy.Color(200, 200, 200) - self.ui.append(instructions) - -# Create and run the game -engine = Engine() -print("Part 2: Entities and Maps!") \ No newline at end of file diff --git a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_3/README.md b/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_3/README.md deleted file mode 100644 index fe8c047..0000000 --- a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_3/README.md +++ /dev/null @@ -1,548 +0,0 @@ -# Part 3 - Generating a Dungeon - -In Parts 1 and 2, we created a player that could move around and interact with a hand-crafted dungeon. Now it's time to generate dungeons procedurally - a core feature of any roguelike game! - -## The Plan - -We'll create a dungeon generator that: -1. Places rectangular rooms randomly -2. Ensures rooms don't overlap -3. Connects rooms with tunnels -4. Places the player in the first room - -This is a classic approach used by many roguelikes, and it creates interesting, playable dungeons. - -## Creating a Room Class - -First, let's create a class to represent rectangular rooms: - -```python -class RectangularRoom: - """A rectangular room with its position and size""" - - def __init__(self, x, y, width, height): - self.x1 = x - self.y1 = y - self.x2 = x + width - self.y2 = y + height - - @property - def center(self): - """Return the center coordinates of the room""" - center_x = (self.x1 + self.x2) // 2 - center_y = (self.y1 + self.y2) // 2 - return center_x, center_y - - @property - def inner(self): - """Return the inner area of the room as a tuple of slices - - This property returns the area inside the walls. - We'll add 1 to min coordinates and subtract 1 from max coordinates. - """ - return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1 - - def intersects(self, other): - """Return True if this room overlaps with another RectangularRoom""" - return ( - self.x1 <= other.x2 - and self.x2 >= other.x1 - and self.y1 <= other.y2 - and self.y2 >= other.y1 - ) -``` - -## Implementing Tunnel Generation - -Since McRogueFace doesn't include line-drawing algorithms, let's implement simple L-shaped tunnels: - -```python -def tunnel_between(start, end): - """Return an L-shaped tunnel between two points""" - x1, y1 = start - x2, y2 = end - - # Randomly decide whether to go horizontal first or vertical first - if random.random() < 0.5: - # Horizontal, then vertical - corner_x = x2 - corner_y = y1 - else: - # Vertical, then horizontal - corner_x = x1 - corner_y = y2 - - # Generate the coordinates - # First line: from start to corner - for x in range(min(x1, corner_x), max(x1, corner_x) + 1): - yield x, y1 - for y in range(min(y1, corner_y), max(y1, corner_y) + 1): - yield corner_x, y - - # Second line: from corner to end - for x in range(min(corner_x, x2), max(corner_x, x2) + 1): - yield x, corner_y - for y in range(min(corner_y, y2), max(corner_y, y2) + 1): - yield x2, y -``` - -## The Dungeon Generator - -Now let's update our GameMap class to generate dungeons: - -```python -import random - -class GameMap: - """Manages the game world""" - - def __init__(self, width, height): - self.width = width - self.height = height - self.grid = None - self.entities = [] - self.rooms = [] # Keep track of rooms for game logic - - def generate_dungeon( - self, - max_rooms, - room_min_size, - room_max_size, - player - ): - """Generate a new dungeon map""" - # Start with everything as walls - self.fill_with_walls() - - for r in range(max_rooms): - # Random width and height - room_width = random.randint(room_min_size, room_max_size) - room_height = random.randint(room_min_size, room_max_size) - - # Random position without going out of bounds - x = random.randint(0, self.width - room_width - 1) - y = random.randint(0, self.height - room_height - 1) - - # Create the room - new_room = RectangularRoom(x, y, room_width, room_height) - - # Check if it intersects with any existing room - if any(new_room.intersects(other_room) for other_room in self.rooms): - continue # This room intersects, so go to the next attempt - - # If we get here, it's a valid room - - # Carve out this room - self.carve_room(new_room) - - # Place the player in the center of the first room - if len(self.rooms) == 0: - player.x, player.y = new_room.center - if player._entity: - player._entity.x, player._entity.y = new_room.center - else: - # All rooms after the first: - # Tunnel between this room and the previous one - self.carve_tunnel(self.rooms[-1].center, new_room.center) - - # Finally, append the new room to the list - self.rooms.append(new_room) - - def carve_room(self, room): - """Carve out a room""" - inner_x1, inner_y1, inner_x2, inner_y2 = room.inner - - for y in range(inner_y1, inner_y2): - for x in range(inner_x1, inner_x2): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, color=(50, 50, 50)) - - def carve_tunnel(self, start, end): - """Carve a tunnel between two points""" - for x, y in tunnel_between(start, end): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, color=(30, 30, 40)) # Slightly different color for tunnels -``` - -## Complete Code - -Here's the complete `game.py` with procedural dungeon generation: - -```python -import mcrfpy -import random - -class GameObject: - """Base class for all game objects""" - def __init__(self, x, y, sprite_index, color, name, blocks=False): - self.x = x - self.y = y - self.sprite_index = sprite_index - self.color = color - self.name = name - self.blocks = blocks - self._entity = None - self.grid = None - - def attach_to_grid(self, grid): - """Attach this game object to a McRogueFace grid""" - self.grid = grid - self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid) - self._entity.sprite_index = self.sprite_index - self._entity.color = mcrfpy.Color(*self.color) - - def move(self, dx, dy): - """Move by the given amount""" - if not self.grid: - return - self.x += dx - self.y += dy - if self._entity: - self._entity.x = self.x - self._entity.y = self.y - -class RectangularRoom: - """A rectangular room with its position and size""" - - def __init__(self, x, y, width, height): - self.x1 = x - self.y1 = y - self.x2 = x + width - self.y2 = y + height - - @property - def center(self): - """Return the center coordinates of the room""" - center_x = (self.x1 + self.x2) // 2 - center_y = (self.y1 + self.y2) // 2 - return center_x, center_y - - @property - def inner(self): - """Return the inner area of the room""" - return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1 - - def intersects(self, other): - """Return True if this room overlaps with another""" - return ( - self.x1 <= other.x2 - and self.x2 >= other.x1 - and self.y1 <= other.y2 - and self.y2 >= other.y1 - ) - -def tunnel_between(start, end): - """Return an L-shaped tunnel between two points""" - x1, y1 = start - x2, y2 = end - - if random.random() < 0.5: - corner_x = x2 - corner_y = y1 - else: - corner_x = x1 - corner_y = y2 - - # Generate the coordinates - for x in range(min(x1, corner_x), max(x1, corner_x) + 1): - yield x, y1 - for y in range(min(y1, corner_y), max(y1, corner_y) + 1): - yield corner_x, y - for x in range(min(corner_x, x2), max(corner_x, x2) + 1): - yield x, corner_y - for y in range(min(corner_y, y2), max(corner_y, y2) + 1): - yield x2, y - -class GameMap: - """Manages the game world""" - - def __init__(self, width, height): - self.width = width - self.height = height - self.grid = None - self.entities = [] - self.rooms = [] - - def create_grid(self, tileset): - """Create the McRogueFace grid""" - self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset) - self.grid.position = (100, 100) - self.grid.size = (800, 480) - return self.grid - - def fill_with_walls(self): - """Fill the entire map with wall tiles""" - for y in range(self.height): - for x in range(self.width): - self.set_tile(x, y, walkable=False, transparent=False, - sprite_index=35, color=(100, 100, 100)) - - def set_tile(self, x, y, walkable, transparent, sprite_index, color): - """Set properties for a specific tile""" - if 0 <= x < self.width and 0 <= y < self.height: - cell = self.grid.at(x, y) - cell.walkable = walkable - cell.transparent = transparent - cell.sprite_index = sprite_index - cell.color = mcrfpy.Color(*color) - - def generate_dungeon(self, max_rooms, room_min_size, room_max_size, player): - """Generate a new dungeon map""" - self.fill_with_walls() - - for r in range(max_rooms): - room_width = random.randint(room_min_size, room_max_size) - room_height = random.randint(room_min_size, room_max_size) - - x = random.randint(0, self.width - room_width - 1) - y = random.randint(0, self.height - room_height - 1) - - new_room = RectangularRoom(x, y, room_width, room_height) - - if any(new_room.intersects(other_room) for other_room in self.rooms): - continue - - self.carve_room(new_room) - - if len(self.rooms) == 0: - player.x, player.y = new_room.center - if player._entity: - player._entity.x, player._entity.y = new_room.center - else: - self.carve_tunnel(self.rooms[-1].center, new_room.center) - - self.rooms.append(new_room) - - def carve_room(self, room): - """Carve out a room""" - inner_x1, inner_y1, inner_x2, inner_y2 = room.inner - - for y in range(inner_y1, inner_y2): - for x in range(inner_x1, inner_x2): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, color=(50, 50, 50)) - - def carve_tunnel(self, start, end): - """Carve a tunnel between two points""" - for x, y in tunnel_between(start, end): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, color=(30, 30, 40)) - - def is_blocked(self, x, y): - """Check if a tile blocks movement""" - if x < 0 or x >= self.width or y < 0 or y >= self.height: - return True - if not self.grid.at(x, y).walkable: - return True - for entity in self.entities: - if entity.blocks and entity.x == x and entity.y == y: - return True - return False - - def add_entity(self, entity): - """Add a GameObject to the map""" - self.entities.append(entity) - entity.attach_to_grid(self.grid) - -class Engine: - """Main game engine""" - - def __init__(self): - self.game_map = None - self.player = None - self.entities = [] - - mcrfpy.createScene("game") - mcrfpy.setScene("game") - - window = mcrfpy.Window.get() - window.title = "McRogueFace Roguelike - Part 3" - - self.ui = mcrfpy.sceneUI("game") - - background = mcrfpy.Frame(0, 0, 1024, 768) - background.fill_color = mcrfpy.Color(0, 0, 0) - self.ui.append(background) - - self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16) - - self.setup_game() - self.setup_input() - self.setup_ui() - - def setup_game(self): - """Initialize the game world""" - self.game_map = GameMap(80, 45) - grid = self.game_map.create_grid(self.tileset) - self.ui.append(grid) - - # Create player (before dungeon generation) - self.player = GameObject(0, 0, 64, (255, 255, 255), "Player", blocks=True) - - # Generate the dungeon - self.game_map.generate_dungeon( - max_rooms=30, - room_min_size=6, - room_max_size=10, - player=self.player - ) - - # Add player to map - self.game_map.add_entity(self.player) - - # Add some monsters in random rooms - for i in range(5): - if i < len(self.game_map.rooms) - 1: # Don't spawn in first room - room = self.game_map.rooms[i + 1] - x, y = room.center - - # Create an orc - orc = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True) - self.game_map.add_entity(orc) - self.entities.append(orc) - - def handle_movement(self, dx, dy): - """Handle player movement""" - new_x = self.player.x + dx - new_y = self.player.y + dy - - if not self.game_map.is_blocked(new_x, new_y): - self.player.move(dx, dy) - - def setup_input(self): - """Setup keyboard input handling""" - def handle_keys(key, state): - if state != "start": - return - - movement = { - "Up": (0, -1), "Down": (0, 1), - "Left": (-1, 0), "Right": (1, 0), - "Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1), - "Num4": (-1, 0), "Num6": (1, 0), - "Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1), - } - - if key in movement: - dx, dy = movement[key] - self.handle_movement(dx, dy) - elif key == "Escape": - mcrfpy.setScene(None) - elif key == "Space": - # Regenerate the dungeon - self.regenerate_dungeon() - - mcrfpy.keypressScene(handle_keys) - - def regenerate_dungeon(self): - """Generate a new dungeon""" - # Clear existing entities - self.game_map.entities.clear() - self.game_map.rooms.clear() - self.entities.clear() - - # Clear the entity list in the grid - if self.game_map.grid: - self.game_map.grid.entities.clear() - - # Regenerate - self.game_map.generate_dungeon( - max_rooms=30, - room_min_size=6, - room_max_size=10, - player=self.player - ) - - # Re-add player - self.game_map.add_entity(self.player) - - # Add new monsters - for i in range(5): - if i < len(self.game_map.rooms) - 1: - room = self.game_map.rooms[i + 1] - x, y = room.center - orc = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True) - self.game_map.add_entity(orc) - self.entities.append(orc) - - def setup_ui(self): - """Setup UI elements""" - title = mcrfpy.Caption("Procedural Dungeon Generation", 512, 30) - title.font_size = 24 - title.fill_color = mcrfpy.Color(255, 255, 100) - self.ui.append(title) - - instructions = mcrfpy.Caption("Arrow keys to move, SPACE to regenerate, ESC to quit", 512, 60) - instructions.font_size = 16 - instructions.fill_color = mcrfpy.Color(200, 200, 200) - self.ui.append(instructions) - -# Create and run the game -engine = Engine() -print("Part 3: Procedural Dungeon Generation!") -print("Press SPACE to generate a new dungeon") -``` - -## Understanding the Algorithm - -Our dungeon generation algorithm is simple but effective: - -1. **Start with solid walls** - The entire map begins filled with wall tiles -2. **Try to place rooms** - Generate random rooms and check for overlaps -3. **Connect with tunnels** - Each new room connects to the previous one -4. **Place entities** - The player starts in the first room, monsters in others - -### Room Placement - -The algorithm attempts to place `max_rooms` rooms, but may place fewer if many attempts result in overlapping rooms. This is called "rejection sampling" - we generate random rooms and reject ones that don't fit. - -### Tunnel Design - -Our L-shaped tunnels are simple but effective. They either go: -- Horizontal first, then vertical -- Vertical first, then horizontal - -This creates variety while ensuring all rooms are connected. - -## Experimenting with Parameters - -Try adjusting these parameters to create different dungeon styles: - -```python -# Sparse dungeon with large rooms -self.game_map.generate_dungeon( - max_rooms=10, - room_min_size=10, - room_max_size=15, - player=self.player -) - -# Dense dungeon with small rooms -self.game_map.generate_dungeon( - max_rooms=50, - room_min_size=4, - room_max_size=6, - player=self.player -) -``` - -## Visual Enhancements - -Notice how we gave tunnels a slightly different color: -- Rooms: `color=(50, 50, 50)` - Medium gray -- Tunnels: `color=(30, 30, 40)` - Darker with blue tint - -This subtle difference helps players understand the dungeon layout. - -## Exercises - -1. **Different Room Shapes**: Create circular or cross-shaped rooms -2. **Better Tunnel Routing**: Implement A* pathfinding for more natural tunnels -3. **Room Types**: Create special rooms (treasure rooms, trap rooms) -4. **Dungeon Themes**: Use different tile sets and colors for different dungeon levels - -## What's Next? - -In Part 4, we'll implement Field of View (FOV) so the player can only see parts of the dungeon they've explored. This will add mystery and atmosphere to our procedurally generated dungeons! - -Our dungeon generator is now creating unique, playable levels every time. The foundation of a true roguelike is taking shape! \ No newline at end of file diff --git a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_3/code/game.py b/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_3/code/game.py deleted file mode 100644 index 1256ef9..0000000 --- a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_3/code/game.py +++ /dev/null @@ -1,312 +0,0 @@ -import mcrfpy -import random - -class GameObject: - """Base class for all game objects""" - def __init__(self, x, y, sprite_index, color, name, blocks=False): - self.x = x - self.y = y - self.sprite_index = sprite_index - self.color = color - self.name = name - self.blocks = blocks - self._entity = None - self.grid = None - - def attach_to_grid(self, grid): - """Attach this game object to a McRogueFace grid""" - self.grid = grid - self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid) - self._entity.sprite_index = self.sprite_index - self._entity.color = mcrfpy.Color(*self.color) - - def move(self, dx, dy): - """Move by the given amount""" - if not self.grid: - return - self.x += dx - self.y += dy - if self._entity: - self._entity.x = self.x - self._entity.y = self.y - -class RectangularRoom: - """A rectangular room with its position and size""" - - def __init__(self, x, y, width, height): - self.x1 = x - self.y1 = y - self.x2 = x + width - self.y2 = y + height - - @property - def center(self): - """Return the center coordinates of the room""" - center_x = (self.x1 + self.x2) // 2 - center_y = (self.y1 + self.y2) // 2 - return center_x, center_y - - @property - def inner(self): - """Return the inner area of the room""" - return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1 - - def intersects(self, other): - """Return True if this room overlaps with another""" - return ( - self.x1 <= other.x2 - and self.x2 >= other.x1 - and self.y1 <= other.y2 - and self.y2 >= other.y1 - ) - -def tunnel_between(start, end): - """Return an L-shaped tunnel between two points""" - x1, y1 = start - x2, y2 = end - - if random.random() < 0.5: - corner_x = x2 - corner_y = y1 - else: - corner_x = x1 - corner_y = y2 - - # Generate the coordinates - for x in range(min(x1, corner_x), max(x1, corner_x) + 1): - yield x, y1 - for y in range(min(y1, corner_y), max(y1, corner_y) + 1): - yield corner_x, y - for x in range(min(corner_x, x2), max(corner_x, x2) + 1): - yield x, corner_y - for y in range(min(corner_y, y2), max(corner_y, y2) + 1): - yield x2, y - -class GameMap: - """Manages the game world""" - - def __init__(self, width, height): - self.width = width - self.height = height - self.grid = None - self.entities = [] - self.rooms = [] - - def create_grid(self, tileset): - """Create the McRogueFace grid""" - self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset) - self.grid.position = (100, 100) - self.grid.size = (800, 480) - return self.grid - - def fill_with_walls(self): - """Fill the entire map with wall tiles""" - for y in range(self.height): - for x in range(self.width): - self.set_tile(x, y, walkable=False, transparent=False, - sprite_index=35, color=(100, 100, 100)) - - def set_tile(self, x, y, walkable, transparent, sprite_index, color): - """Set properties for a specific tile""" - if 0 <= x < self.width and 0 <= y < self.height: - cell = self.grid.at(x, y) - cell.walkable = walkable - cell.transparent = transparent - cell.sprite_index = sprite_index - cell.color = mcrfpy.Color(*color) - - def generate_dungeon(self, max_rooms, room_min_size, room_max_size, player): - """Generate a new dungeon map""" - self.fill_with_walls() - - for r in range(max_rooms): - room_width = random.randint(room_min_size, room_max_size) - room_height = random.randint(room_min_size, room_max_size) - - x = random.randint(0, self.width - room_width - 1) - y = random.randint(0, self.height - room_height - 1) - - new_room = RectangularRoom(x, y, room_width, room_height) - - if any(new_room.intersects(other_room) for other_room in self.rooms): - continue - - self.carve_room(new_room) - - if len(self.rooms) == 0: - player.x, player.y = new_room.center - if player._entity: - player._entity.x, player._entity.y = new_room.center - else: - self.carve_tunnel(self.rooms[-1].center, new_room.center) - - self.rooms.append(new_room) - - def carve_room(self, room): - """Carve out a room""" - inner_x1, inner_y1, inner_x2, inner_y2 = room.inner - - for y in range(inner_y1, inner_y2): - for x in range(inner_x1, inner_x2): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, color=(50, 50, 50)) - - def carve_tunnel(self, start, end): - """Carve a tunnel between two points""" - for x, y in tunnel_between(start, end): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, color=(30, 30, 40)) - - def is_blocked(self, x, y): - """Check if a tile blocks movement""" - if x < 0 or x >= self.width or y < 0 or y >= self.height: - return True - if not self.grid.at(x, y).walkable: - return True - for entity in self.entities: - if entity.blocks and entity.x == x and entity.y == y: - return True - return False - - def add_entity(self, entity): - """Add a GameObject to the map""" - self.entities.append(entity) - entity.attach_to_grid(self.grid) - -class Engine: - """Main game engine""" - - def __init__(self): - self.game_map = None - self.player = None - self.entities = [] - - mcrfpy.createScene("game") - mcrfpy.setScene("game") - - window = mcrfpy.Window.get() - window.title = "McRogueFace Roguelike - Part 3" - - self.ui = mcrfpy.sceneUI("game") - - background = mcrfpy.Frame(0, 0, 1024, 768) - background.fill_color = mcrfpy.Color(0, 0, 0) - self.ui.append(background) - - self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16) - - self.setup_game() - self.setup_input() - self.setup_ui() - - def setup_game(self): - """Initialize the game world""" - self.game_map = GameMap(80, 45) - grid = self.game_map.create_grid(self.tileset) - self.ui.append(grid) - - # Create player (before dungeon generation) - self.player = GameObject(0, 0, 64, (255, 255, 255), "Player", blocks=True) - - # Generate the dungeon - self.game_map.generate_dungeon( - max_rooms=30, - room_min_size=6, - room_max_size=10, - player=self.player - ) - - # Add player to map - self.game_map.add_entity(self.player) - - # Add some monsters in random rooms - for i in range(5): - if i < len(self.game_map.rooms) - 1: # Don't spawn in first room - room = self.game_map.rooms[i + 1] - x, y = room.center - - # Create an orc - orc = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True) - self.game_map.add_entity(orc) - self.entities.append(orc) - - def handle_movement(self, dx, dy): - """Handle player movement""" - new_x = self.player.x + dx - new_y = self.player.y + dy - - if not self.game_map.is_blocked(new_x, new_y): - self.player.move(dx, dy) - - def setup_input(self): - """Setup keyboard input handling""" - def handle_keys(key, state): - if state != "start": - return - - movement = { - "Up": (0, -1), "Down": (0, 1), - "Left": (-1, 0), "Right": (1, 0), - "Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1), - "Num4": (-1, 0), "Num6": (1, 0), - "Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1), - } - - if key in movement: - dx, dy = movement[key] - self.handle_movement(dx, dy) - elif key == "Escape": - mcrfpy.setScene(None) - elif key == "Space": - # Regenerate the dungeon - self.regenerate_dungeon() - - mcrfpy.keypressScene(handle_keys) - - def regenerate_dungeon(self): - """Generate a new dungeon""" - # Clear existing entities - self.game_map.entities.clear() - self.game_map.rooms.clear() - self.entities.clear() - - # Clear the entity list in the grid - if self.game_map.grid: - self.game_map.grid.entities.clear() - - # Regenerate - self.game_map.generate_dungeon( - max_rooms=30, - room_min_size=6, - room_max_size=10, - player=self.player - ) - - # Re-add player - self.game_map.add_entity(self.player) - - # Add new monsters - for i in range(5): - if i < len(self.game_map.rooms) - 1: - room = self.game_map.rooms[i + 1] - x, y = room.center - orc = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True) - self.game_map.add_entity(orc) - self.entities.append(orc) - - def setup_ui(self): - """Setup UI elements""" - title = mcrfpy.Caption("Procedural Dungeon Generation", 512, 30) - title.font_size = 24 - title.fill_color = mcrfpy.Color(255, 255, 100) - self.ui.append(title) - - instructions = mcrfpy.Caption("Arrow keys to move, SPACE to regenerate, ESC to quit", 512, 60) - instructions.font_size = 16 - instructions.fill_color = mcrfpy.Color(200, 200, 200) - self.ui.append(instructions) - -# Create and run the game -engine = Engine() -print("Part 3: Procedural Dungeon Generation!") -print("Press SPACE to generate a new dungeon") \ No newline at end of file diff --git a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_4/README.md b/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_4/README.md deleted file mode 100644 index 50301b4..0000000 --- a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_4/README.md +++ /dev/null @@ -1,520 +0,0 @@ -# Part 4 - Field of View - -One of the defining features of roguelikes is exploration and discovery. In Part 3, we could see the entire dungeon at once. Now we'll implement Field of View (FOV) so players can only see what their character can actually see, adding mystery and tactical depth to our game. - -## Understanding Field of View - -Field of View creates three distinct visibility states for each tile: - -1. **Visible**: Currently in the player's line of sight -2. **Explored**: Previously seen but not currently visible -3. **Unexplored**: Never seen (completely hidden) - -This creates the classic "fog of war" effect where you remember the layout of areas you've explored, but can't see current enemy positions unless they're in your view. - -## McRogueFace's FOV System - -Good news! McRogueFace includes built-in FOV support through its C++ engine. We just need to enable and configure it. The engine uses an efficient shadowcasting algorithm that provides smooth, realistic line-of-sight calculations. - -Let's update our code to use FOV: - -```python -class GameObject: - """Base class for all game objects""" - def __init__(self, x, y, sprite_index, color, name, blocks=False): - self.x = x - self.y = y - self.sprite_index = sprite_index - self.color = color - self.name = name - self.blocks = blocks - self._entity = None - self.grid = None - - def attach_to_grid(self, grid): - """Attach this game object to a McRogueFace grid""" - self.grid = grid - self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid) - self._entity.sprite_index = self.sprite_index - self._entity.color = mcrfpy.Color(*self.color) - - def move(self, dx, dy): - """Move by the given amount""" - if not self.grid: - return - self.x += dx - self.y += dy - if self._entity: - self._entity.x = self.x - self._entity.y = self.y - # Update FOV when player moves - if self.name == "Player": - self.update_fov() - - def update_fov(self): - """Update field of view from this entity's position""" - if self._entity and self.grid: - self._entity.update_fov(radius=8) -``` - -## Configuring Visibility Rendering - -McRogueFace automatically handles the rendering of visible/explored/unexplored tiles. We need to set up our grid to use perspective-based rendering: - -```python -class GameMap: - """Manages the game world""" - - def create_grid(self, tileset): - """Create the McRogueFace grid""" - self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset) - self.grid.position = (100, 100) - self.grid.size = (800, 480) - - # Enable perspective rendering (0 = first entity = player) - self.grid.perspective = 0 - - return self.grid -``` - -## Visual Appearance Configuration - -Let's define how our tiles look in different visibility states: - -```python -# Color configurations for visibility states -COLORS_VISIBLE = { - 'wall': (100, 100, 100), # Light gray - 'floor': (50, 50, 50), # Dark gray - 'tunnel': (30, 30, 40), # Dark blue-gray -} - -COLORS_EXPLORED = { - 'wall': (50, 50, 70), # Darker, bluish - 'floor': (20, 20, 30), # Very dark - 'tunnel': (15, 15, 25), # Almost black -} - -# Update the tile-setting methods to store the tile type -def set_tile(self, x, y, walkable, transparent, sprite_index, tile_type): - """Set properties for a specific tile""" - if 0 <= x < self.width and 0 <= y < self.height: - cell = self.grid.at(x, y) - cell.walkable = walkable - cell.transparent = transparent - cell.sprite_index = sprite_index - # Store both visible and explored colors - cell.color = mcrfpy.Color(*COLORS_VISIBLE[tile_type]) - # The engine will automatically darken explored tiles -``` - -## Complete Implementation - -Here's the complete updated `game.py` with FOV: - -```python -import mcrfpy -import random - -# Color configurations for visibility -COLORS_VISIBLE = { - 'wall': (100, 100, 100), - 'floor': (50, 50, 50), - 'tunnel': (30, 30, 40), -} - -class GameObject: - """Base class for all game objects""" - def __init__(self, x, y, sprite_index, color, name, blocks=False): - self.x = x - self.y = y - self.sprite_index = sprite_index - self.color = color - self.name = name - self.blocks = blocks - self._entity = None - self.grid = None - - def attach_to_grid(self, grid): - """Attach this game object to a McRogueFace grid""" - self.grid = grid - self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid) - self._entity.sprite_index = self.sprite_index - self._entity.color = mcrfpy.Color(*self.color) - - def move(self, dx, dy): - """Move by the given amount""" - if not self.grid: - return - self.x += dx - self.y += dy - if self._entity: - self._entity.x = self.x - self._entity.y = self.y - # Update FOV when player moves - if self.name == "Player": - self.update_fov() - - def update_fov(self): - """Update field of view from this entity's position""" - if self._entity and self.grid: - self._entity.update_fov(radius=8) - -class RectangularRoom: - """A rectangular room with its position and size""" - - def __init__(self, x, y, width, height): - self.x1 = x - self.y1 = y - self.x2 = x + width - self.y2 = y + height - - @property - def center(self): - center_x = (self.x1 + self.x2) // 2 - center_y = (self.y1 + self.y2) // 2 - return center_x, center_y - - @property - def inner(self): - return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1 - - def intersects(self, other): - return ( - self.x1 <= other.x2 - and self.x2 >= other.x1 - and self.y1 <= other.y2 - and self.y2 >= other.y1 - ) - -def tunnel_between(start, end): - """Return an L-shaped tunnel between two points""" - x1, y1 = start - x2, y2 = end - - if random.random() < 0.5: - corner_x = x2 - corner_y = y1 - else: - corner_x = x1 - corner_y = y2 - - for x in range(min(x1, corner_x), max(x1, corner_x) + 1): - yield x, y1 - for y in range(min(y1, corner_y), max(y1, corner_y) + 1): - yield corner_x, y - for x in range(min(corner_x, x2), max(corner_x, x2) + 1): - yield x, corner_y - for y in range(min(corner_y, y2), max(corner_y, y2) + 1): - yield x2, y - -class GameMap: - """Manages the game world""" - - def __init__(self, width, height): - self.width = width - self.height = height - self.grid = None - self.entities = [] - self.rooms = [] - - def create_grid(self, tileset): - """Create the McRogueFace grid""" - self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset) - self.grid.position = (100, 100) - self.grid.size = (800, 480) - - # Enable perspective rendering (0 = first entity = player) - self.grid.perspective = 0 - - return self.grid - - def fill_with_walls(self): - """Fill the entire map with wall tiles""" - for y in range(self.height): - for x in range(self.width): - self.set_tile(x, y, walkable=False, transparent=False, - sprite_index=35, tile_type='wall') - - def set_tile(self, x, y, walkable, transparent, sprite_index, tile_type): - """Set properties for a specific tile""" - if 0 <= x < self.width and 0 <= y < self.height: - cell = self.grid.at(x, y) - cell.walkable = walkable - cell.transparent = transparent - cell.sprite_index = sprite_index - cell.color = mcrfpy.Color(*COLORS_VISIBLE[tile_type]) - - def generate_dungeon(self, max_rooms, room_min_size, room_max_size, player): - """Generate a new dungeon map""" - self.fill_with_walls() - - for r in range(max_rooms): - room_width = random.randint(room_min_size, room_max_size) - room_height = random.randint(room_min_size, room_max_size) - - x = random.randint(0, self.width - room_width - 1) - y = random.randint(0, self.height - room_height - 1) - - new_room = RectangularRoom(x, y, room_width, room_height) - - if any(new_room.intersects(other_room) for other_room in self.rooms): - continue - - self.carve_room(new_room) - - if len(self.rooms) == 0: - player.x, player.y = new_room.center - if player._entity: - player._entity.x, player._entity.y = new_room.center - else: - self.carve_tunnel(self.rooms[-1].center, new_room.center) - - self.rooms.append(new_room) - - def carve_room(self, room): - """Carve out a room""" - inner_x1, inner_y1, inner_x2, inner_y2 = room.inner - - for y in range(inner_y1, inner_y2): - for x in range(inner_x1, inner_x2): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, tile_type='floor') - - def carve_tunnel(self, start, end): - """Carve a tunnel between two points""" - for x, y in tunnel_between(start, end): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, tile_type='tunnel') - - def is_blocked(self, x, y): - """Check if a tile blocks movement""" - if x < 0 or x >= self.width or y < 0 or y >= self.height: - return True - if not self.grid.at(x, y).walkable: - return True - for entity in self.entities: - if entity.blocks and entity.x == x and entity.y == y: - return True - return False - - def add_entity(self, entity): - """Add a GameObject to the map""" - self.entities.append(entity) - entity.attach_to_grid(self.grid) - -class Engine: - """Main game engine""" - - def __init__(self): - self.game_map = None - self.player = None - self.entities = [] - self.fov_radius = 8 - - mcrfpy.createScene("game") - mcrfpy.setScene("game") - - window = mcrfpy.Window.get() - window.title = "McRogueFace Roguelike - Part 4" - - self.ui = mcrfpy.sceneUI("game") - - background = mcrfpy.Frame(0, 0, 1024, 768) - background.fill_color = mcrfpy.Color(0, 0, 0) - self.ui.append(background) - - self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16) - - self.setup_game() - self.setup_input() - self.setup_ui() - - def setup_game(self): - """Initialize the game world""" - self.game_map = GameMap(80, 45) - grid = self.game_map.create_grid(self.tileset) - self.ui.append(grid) - - # Create player - self.player = GameObject(0, 0, 64, (255, 255, 255), "Player", blocks=True) - - # Generate the dungeon - self.game_map.generate_dungeon( - max_rooms=30, - room_min_size=6, - room_max_size=10, - player=self.player - ) - - # Add player to map - self.game_map.add_entity(self.player) - - # Add monsters in random rooms - for i in range(10): - if i < len(self.game_map.rooms) - 1: - room = self.game_map.rooms[i + 1] - x, y = room.center - - # Randomly offset from center - x += random.randint(-2, 2) - y += random.randint(-2, 2) - - # Make sure position is walkable - if self.game_map.grid.at(x, y).walkable: - if i % 2 == 0: - # Create an orc - orc = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True) - self.game_map.add_entity(orc) - self.entities.append(orc) - else: - # Create a troll - troll = GameObject(x, y, 84, (0, 127, 0), "Troll", blocks=True) - self.game_map.add_entity(troll) - self.entities.append(troll) - - # Initial FOV calculation - self.player.update_fov() - - def handle_movement(self, dx, dy): - """Handle player movement""" - new_x = self.player.x + dx - new_y = self.player.y + dy - - if not self.game_map.is_blocked(new_x, new_y): - self.player.move(dx, dy) - - def setup_input(self): - """Setup keyboard input handling""" - def handle_keys(key, state): - if state != "start": - return - - movement = { - "Up": (0, -1), "Down": (0, 1), - "Left": (-1, 0), "Right": (1, 0), - "Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1), - "Num4": (-1, 0), "Num6": (1, 0), - "Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1), - } - - if key in movement: - dx, dy = movement[key] - self.handle_movement(dx, dy) - elif key == "Escape": - mcrfpy.setScene(None) - elif key == "v": - # Toggle FOV on/off - if self.game_map.grid.perspective == 0: - self.game_map.grid.perspective = -1 # Omniscient - print("FOV disabled - omniscient view") - else: - self.game_map.grid.perspective = 0 # Player perspective - print("FOV enabled - player perspective") - elif key == "Plus" or key == "Equals": - # Increase FOV radius - self.fov_radius = min(self.fov_radius + 1, 20) - self.player._entity.update_fov(radius=self.fov_radius) - print(f"FOV radius: {self.fov_radius}") - elif key == "Minus": - # Decrease FOV radius - self.fov_radius = max(self.fov_radius - 1, 3) - self.player._entity.update_fov(radius=self.fov_radius) - print(f"FOV radius: {self.fov_radius}") - - mcrfpy.keypressScene(handle_keys) - - def setup_ui(self): - """Setup UI elements""" - title = mcrfpy.Caption("Field of View", 512, 30) - title.font_size = 24 - title.fill_color = mcrfpy.Color(255, 255, 100) - self.ui.append(title) - - instructions = mcrfpy.Caption("Arrow keys to move | V to toggle FOV | +/- to adjust radius | ESC to quit", 512, 60) - instructions.font_size = 16 - instructions.fill_color = mcrfpy.Color(200, 200, 200) - self.ui.append(instructions) - - # FOV indicator - self.fov_text = mcrfpy.Caption(f"FOV Radius: {self.fov_radius}", 900, 100) - self.fov_text.font_size = 14 - self.fov_text.fill_color = mcrfpy.Color(150, 200, 255) - self.ui.append(self.fov_text) - -# Create and run the game -engine = Engine() -print("Part 4: Field of View!") -print("Press V to toggle FOV on/off") -print("Press +/- to adjust FOV radius") -``` - -## How FOV Works - -McRogueFace's built-in FOV system uses a shadowcasting algorithm that: - -1. **Casts rays** from the player's position to tiles within the radius -2. **Checks transparency** along each ray path -3. **Marks tiles as visible** if the ray reaches them unobstructed -4. **Remembers explored tiles** automatically - -The engine handles all the complex calculations in C++ for optimal performance. - -## Visibility States in Detail - -### Visible Tiles -- Currently in the player's line of sight -- Rendered at full brightness -- Show current entity positions - -### Explored Tiles -- Previously seen but not currently visible -- Rendered darker/muted -- Show remembered terrain but not entities - -### Unexplored Tiles -- Never been in the player's FOV -- Rendered as black/invisible -- Complete mystery to the player - -## FOV Parameters - -You can customize FOV behavior: - -```python -# Basic FOV update -entity.update_fov(radius=8) - -# The grid's perspective property controls rendering: -grid.perspective = 0 # Use first entity's FOV (player) -grid.perspective = 1 # Use second entity's FOV -grid.perspective = -1 # Omniscient (no FOV, see everything) -``` - -## Performance Considerations - -McRogueFace's C++ FOV implementation is highly optimized: -- Uses efficient shadowcasting algorithm -- Only recalculates when needed -- Handles large maps smoothly -- Automatically culls entities outside FOV - -## Visual Polish - -The engine automatically handles visual transitions: -- Smooth color changes between visibility states -- Entities fade in/out of view -- Explored areas remain visible but dimmed - -## Exercises - -1. **Variable Vision**: Give different entities different FOV radii -2. **Light Sources**: Create torches that expand local FOV -3. **Blind Spots**: Add pillars that create interesting shadows -4. **X-Ray Vision**: Temporary power-up to see through walls - -## What's Next? - -In Part 5, we'll place enemies throughout the dungeon and implement basic interactions. With FOV in place, enemies will appear and disappear as you explore, creating tension and surprise! - -Field of View transforms our dungeon from a tactical puzzle into a mysterious world to explore. The fog of war adds atmosphere and gameplay depth that's essential to the roguelike experience. \ No newline at end of file diff --git a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_4/code/game.py b/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_4/code/game.py deleted file mode 100644 index e5c23da..0000000 --- a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_4/code/game.py +++ /dev/null @@ -1,334 +0,0 @@ -import mcrfpy -import random - -# Color configurations for visibility -COLORS_VISIBLE = { - 'wall': (100, 100, 100), - 'floor': (50, 50, 50), - 'tunnel': (30, 30, 40), -} - -class GameObject: - """Base class for all game objects""" - def __init__(self, x, y, sprite_index, color, name, blocks=False): - self.x = x - self.y = y - self.sprite_index = sprite_index - self.color = color - self.name = name - self.blocks = blocks - self._entity = None - self.grid = None - - def attach_to_grid(self, grid): - """Attach this game object to a McRogueFace grid""" - self.grid = grid - self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid) - self._entity.sprite_index = self.sprite_index - self._entity.color = mcrfpy.Color(*self.color) - - def move(self, dx, dy): - """Move by the given amount""" - if not self.grid: - return - self.x += dx - self.y += dy - if self._entity: - self._entity.x = self.x - self._entity.y = self.y - # Update FOV when player moves - if self.name == "Player": - self.update_fov() - - def update_fov(self): - """Update field of view from this entity's position""" - if self._entity and self.grid: - self._entity.update_fov(radius=8) - -class RectangularRoom: - """A rectangular room with its position and size""" - - def __init__(self, x, y, width, height): - self.x1 = x - self.y1 = y - self.x2 = x + width - self.y2 = y + height - - @property - def center(self): - center_x = (self.x1 + self.x2) // 2 - center_y = (self.y1 + self.y2) // 2 - return center_x, center_y - - @property - def inner(self): - return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1 - - def intersects(self, other): - return ( - self.x1 <= other.x2 - and self.x2 >= other.x1 - and self.y1 <= other.y2 - and self.y2 >= other.y1 - ) - -def tunnel_between(start, end): - """Return an L-shaped tunnel between two points""" - x1, y1 = start - x2, y2 = end - - if random.random() < 0.5: - corner_x = x2 - corner_y = y1 - else: - corner_x = x1 - corner_y = y2 - - for x in range(min(x1, corner_x), max(x1, corner_x) + 1): - yield x, y1 - for y in range(min(y1, corner_y), max(y1, corner_y) + 1): - yield corner_x, y - for x in range(min(corner_x, x2), max(corner_x, x2) + 1): - yield x, corner_y - for y in range(min(corner_y, y2), max(corner_y, y2) + 1): - yield x2, y - -class GameMap: - """Manages the game world""" - - def __init__(self, width, height): - self.width = width - self.height = height - self.grid = None - self.entities = [] - self.rooms = [] - - def create_grid(self, tileset): - """Create the McRogueFace grid""" - self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset) - self.grid.position = (100, 100) - self.grid.size = (800, 480) - - # Enable perspective rendering (0 = first entity = player) - self.grid.perspective = 0 - - return self.grid - - def fill_with_walls(self): - """Fill the entire map with wall tiles""" - for y in range(self.height): - for x in range(self.width): - self.set_tile(x, y, walkable=False, transparent=False, - sprite_index=35, tile_type='wall') - - def set_tile(self, x, y, walkable, transparent, sprite_index, tile_type): - """Set properties for a specific tile""" - if 0 <= x < self.width and 0 <= y < self.height: - cell = self.grid.at(x, y) - cell.walkable = walkable - cell.transparent = transparent - cell.sprite_index = sprite_index - cell.color = mcrfpy.Color(*COLORS_VISIBLE[tile_type]) - - def generate_dungeon(self, max_rooms, room_min_size, room_max_size, player): - """Generate a new dungeon map""" - self.fill_with_walls() - - for r in range(max_rooms): - room_width = random.randint(room_min_size, room_max_size) - room_height = random.randint(room_min_size, room_max_size) - - x = random.randint(0, self.width - room_width - 1) - y = random.randint(0, self.height - room_height - 1) - - new_room = RectangularRoom(x, y, room_width, room_height) - - if any(new_room.intersects(other_room) for other_room in self.rooms): - continue - - self.carve_room(new_room) - - if len(self.rooms) == 0: - player.x, player.y = new_room.center - if player._entity: - player._entity.x, player._entity.y = new_room.center - else: - self.carve_tunnel(self.rooms[-1].center, new_room.center) - - self.rooms.append(new_room) - - def carve_room(self, room): - """Carve out a room""" - inner_x1, inner_y1, inner_x2, inner_y2 = room.inner - - for y in range(inner_y1, inner_y2): - for x in range(inner_x1, inner_x2): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, tile_type='floor') - - def carve_tunnel(self, start, end): - """Carve a tunnel between two points""" - for x, y in tunnel_between(start, end): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, tile_type='tunnel') - - def is_blocked(self, x, y): - """Check if a tile blocks movement""" - if x < 0 or x >= self.width or y < 0 or y >= self.height: - return True - if not self.grid.at(x, y).walkable: - return True - for entity in self.entities: - if entity.blocks and entity.x == x and entity.y == y: - return True - return False - - def add_entity(self, entity): - """Add a GameObject to the map""" - self.entities.append(entity) - entity.attach_to_grid(self.grid) - -class Engine: - """Main game engine""" - - def __init__(self): - self.game_map = None - self.player = None - self.entities = [] - self.fov_radius = 8 - - mcrfpy.createScene("game") - mcrfpy.setScene("game") - - window = mcrfpy.Window.get() - window.title = "McRogueFace Roguelike - Part 4" - - self.ui = mcrfpy.sceneUI("game") - - background = mcrfpy.Frame(0, 0, 1024, 768) - background.fill_color = mcrfpy.Color(0, 0, 0) - self.ui.append(background) - - self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16) - - self.setup_game() - self.setup_input() - self.setup_ui() - - def setup_game(self): - """Initialize the game world""" - self.game_map = GameMap(80, 45) - grid = self.game_map.create_grid(self.tileset) - self.ui.append(grid) - - # Create player - self.player = GameObject(0, 0, 64, (255, 255, 255), "Player", blocks=True) - - # Generate the dungeon - self.game_map.generate_dungeon( - max_rooms=30, - room_min_size=6, - room_max_size=10, - player=self.player - ) - - # Add player to map - self.game_map.add_entity(self.player) - - # Add monsters in random rooms - for i in range(10): - if i < len(self.game_map.rooms) - 1: - room = self.game_map.rooms[i + 1] - x, y = room.center - - # Randomly offset from center - x += random.randint(-2, 2) - y += random.randint(-2, 2) - - # Make sure position is walkable - if self.game_map.grid.at(x, y).walkable: - if i % 2 == 0: - # Create an orc - orc = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True) - self.game_map.add_entity(orc) - self.entities.append(orc) - else: - # Create a troll - troll = GameObject(x, y, 84, (0, 127, 0), "Troll", blocks=True) - self.game_map.add_entity(troll) - self.entities.append(troll) - - # Initial FOV calculation - self.player.update_fov() - - def handle_movement(self, dx, dy): - """Handle player movement""" - new_x = self.player.x + dx - new_y = self.player.y + dy - - if not self.game_map.is_blocked(new_x, new_y): - self.player.move(dx, dy) - - def setup_input(self): - """Setup keyboard input handling""" - def handle_keys(key, state): - if state != "start": - return - - movement = { - "Up": (0, -1), "Down": (0, 1), - "Left": (-1, 0), "Right": (1, 0), - "Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1), - "Num4": (-1, 0), "Num6": (1, 0), - "Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1), - } - - if key in movement: - dx, dy = movement[key] - self.handle_movement(dx, dy) - elif key == "Escape": - mcrfpy.setScene(None) - elif key == "v": - # Toggle FOV on/off - if self.game_map.grid.perspective == 0: - self.game_map.grid.perspective = -1 # Omniscient - print("FOV disabled - omniscient view") - else: - self.game_map.grid.perspective = 0 # Player perspective - print("FOV enabled - player perspective") - elif key == "Plus" or key == "Equals": - # Increase FOV radius - self.fov_radius = min(self.fov_radius + 1, 20) - self.player._entity.update_fov(radius=self.fov_radius) - print(f"FOV radius: {self.fov_radius}") - elif key == "Minus": - # Decrease FOV radius - self.fov_radius = max(self.fov_radius - 1, 3) - self.player._entity.update_fov(radius=self.fov_radius) - print(f"FOV radius: {self.fov_radius}") - - mcrfpy.keypressScene(handle_keys) - - def setup_ui(self): - """Setup UI elements""" - title = mcrfpy.Caption("Field of View", 512, 30) - title.font_size = 24 - title.fill_color = mcrfpy.Color(255, 255, 100) - self.ui.append(title) - - instructions = mcrfpy.Caption("Arrow keys to move | V to toggle FOV | +/- to adjust radius | ESC to quit", 512, 60) - instructions.font_size = 16 - instructions.fill_color = mcrfpy.Color(200, 200, 200) - self.ui.append(instructions) - - # FOV indicator - self.fov_text = mcrfpy.Caption(f"FOV Radius: {self.fov_radius}", 900, 100) - self.fov_text.font_size = 14 - self.fov_text.fill_color = mcrfpy.Color(150, 200, 255) - self.ui.append(self.fov_text) - -# Create and run the game -engine = Engine() -print("Part 4: Field of View!") -print("Press V to toggle FOV on/off") -print("Press +/- to adjust FOV radius") \ No newline at end of file diff --git a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_5/README.md b/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_5/README.md deleted file mode 100644 index ad99fc8..0000000 --- a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_5/README.md +++ /dev/null @@ -1,570 +0,0 @@ -# Part 5 - Placing Enemies and Kicking Them (Harmlessly) - -Now that we have Field of View working, it's time to populate our dungeon with enemies! In this part, we'll: -- Place enemies randomly in rooms -- Implement entity-to-entity collision detection -- Create basic interactions (bumping into enemies) -- Set the stage for combat in Part 6 - -## Enemy Spawning System - -First, let's create a system to spawn enemies in our dungeon rooms. We'll avoid placing them in the first room (where the player starts) to give players a safe starting area. - -```python -def spawn_enemies_in_room(room, game_map, max_enemies=2): - """Spawn between 0 and max_enemies in a room""" - import random - - number_of_enemies = random.randint(0, max_enemies) - - for i in range(number_of_enemies): - # Try to find a valid position - attempts = 10 - while attempts > 0: - # Random position within room bounds - x = random.randint(room.x1 + 1, room.x2 - 1) - y = random.randint(room.y1 + 1, room.y2 - 1) - - # Check if position is valid - if not game_map.is_blocked(x, y): - # 80% chance for orc, 20% for troll - if random.random() < 0.8: - enemy = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True) - else: - enemy = GameObject(x, y, 84, (0, 127, 0), "Troll", blocks=True) - - game_map.add_entity(enemy) - break - - attempts -= 1 -``` - -## Enhanced Collision Detection - -We need to improve our collision detection to check for entities, not just walls: - -```python -class GameMap: - """Manages the game world""" - - def get_blocking_entity_at(self, x, y): - """Return any blocking entity at the given position""" - for entity in self.entities: - if entity.blocks and entity.x == x and entity.y == y: - return entity - return None - - def is_blocked(self, x, y): - """Check if a tile blocks movement""" - # Check boundaries - if x < 0 or x >= self.width or y < 0 or y >= self.height: - return True - - # Check walls - if not self.grid.at(x, y).walkable: - return True - - # Check entities - if self.get_blocking_entity_at(x, y): - return True - - return False -``` - -## Action System Introduction - -Let's create a simple action system to handle different types of interactions: - -```python -class Action: - """Base class for all actions""" - pass - -class MovementAction(Action): - """Action for moving an entity""" - def __init__(self, dx, dy): - self.dx = dx - self.dy = dy - -class BumpAction(Action): - """Action for bumping into something""" - def __init__(self, dx, dy, target=None): - self.dx = dx - self.dy = dy - self.target = target - -class WaitAction(Action): - """Action for waiting/skipping turn""" - pass -``` - -## Handling Player Actions - -Now let's update our movement handling to support bumping into enemies: - -```python -def handle_player_turn(self, action): - """Process the player's action""" - if isinstance(action, MovementAction): - dest_x = self.player.x + action.dx - dest_y = self.player.y + action.dy - - # Check what's at the destination - target = self.game_map.get_blocking_entity_at(dest_x, dest_y) - - if target: - # We bumped into something! - print(f"You kick the {target.name} in the shins, much to its annoyance!") - elif not self.game_map.is_blocked(dest_x, dest_y): - # Move the player - self.player.move(action.dx, action.dy) - # Update message - self.status_text.text = "Exploring the dungeon..." - else: - # Bumped into a wall - self.status_text.text = "Ouch! You bump into a wall." - - elif isinstance(action, WaitAction): - self.status_text.text = "You wait..." -``` - -## Complete Updated Code - -Here's the complete `game.py` with enemy placement and interactions: - -```python -import mcrfpy -import random - -# Color configurations -COLORS_VISIBLE = { - 'wall': (100, 100, 100), - 'floor': (50, 50, 50), - 'tunnel': (30, 30, 40), -} - -# Actions -class Action: - """Base class for all actions""" - pass - -class MovementAction(Action): - """Action for moving an entity""" - def __init__(self, dx, dy): - self.dx = dx - self.dy = dy - -class WaitAction(Action): - """Action for waiting/skipping turn""" - pass - -class GameObject: - """Base class for all game objects""" - def __init__(self, x, y, sprite_index, color, name, blocks=False): - self.x = x - self.y = y - self.sprite_index = sprite_index - self.color = color - self.name = name - self.blocks = blocks - self._entity = None - self.grid = None - - def attach_to_grid(self, grid): - """Attach this game object to a McRogueFace grid""" - self.grid = grid - self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid) - self._entity.sprite_index = self.sprite_index - self._entity.color = mcrfpy.Color(*self.color) - - def move(self, dx, dy): - """Move by the given amount""" - if not self.grid: - return - self.x += dx - self.y += dy - if self._entity: - self._entity.x = self.x - self._entity.y = self.y - # Update FOV when player moves - if self.name == "Player": - self.update_fov() - - def update_fov(self): - """Update field of view from this entity's position""" - if self._entity and self.grid: - self._entity.update_fov(radius=8) - -class RectangularRoom: - """A rectangular room with its position and size""" - - def __init__(self, x, y, width, height): - self.x1 = x - self.y1 = y - self.x2 = x + width - self.y2 = y + height - - @property - def center(self): - center_x = (self.x1 + self.x2) // 2 - center_y = (self.y1 + self.y2) // 2 - return center_x, center_y - - @property - def inner(self): - return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1 - - def intersects(self, other): - return ( - self.x1 <= other.x2 - and self.x2 >= other.x1 - and self.y1 <= other.y2 - and self.y2 >= other.y1 - ) - -def tunnel_between(start, end): - """Return an L-shaped tunnel between two points""" - x1, y1 = start - x2, y2 = end - - if random.random() < 0.5: - corner_x = x2 - corner_y = y1 - else: - corner_x = x1 - corner_y = y2 - - for x in range(min(x1, corner_x), max(x1, corner_x) + 1): - yield x, y1 - for y in range(min(y1, corner_y), max(y1, corner_y) + 1): - yield corner_x, y - for x in range(min(corner_x, x2), max(corner_x, x2) + 1): - yield x, corner_y - for y in range(min(corner_y, y2), max(corner_y, y2) + 1): - yield x2, y - -def spawn_enemies_in_room(room, game_map, max_enemies=2): - """Spawn between 0 and max_enemies in a room""" - number_of_enemies = random.randint(0, max_enemies) - - enemies_spawned = [] - - for i in range(number_of_enemies): - # Try to find a valid position - attempts = 10 - while attempts > 0: - # Random position within room bounds - x = random.randint(room.x1 + 1, room.x2 - 1) - y = random.randint(room.y1 + 1, room.y2 - 1) - - # Check if position is valid - if not game_map.is_blocked(x, y): - # 80% chance for orc, 20% for troll - if random.random() < 0.8: - enemy = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True) - else: - enemy = GameObject(x, y, 84, (0, 127, 0), "Troll", blocks=True) - - game_map.add_entity(enemy) - enemies_spawned.append(enemy) - break - - attempts -= 1 - - return enemies_spawned - -class GameMap: - """Manages the game world""" - - def __init__(self, width, height): - self.width = width - self.height = height - self.grid = None - self.entities = [] - self.rooms = [] - - def create_grid(self, tileset): - """Create the McRogueFace grid""" - self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset) - self.grid.position = (100, 100) - self.grid.size = (800, 480) - - # Enable perspective rendering - self.grid.perspective = 0 - - return self.grid - - def fill_with_walls(self): - """Fill the entire map with wall tiles""" - for y in range(self.height): - for x in range(self.width): - self.set_tile(x, y, walkable=False, transparent=False, - sprite_index=35, tile_type='wall') - - def set_tile(self, x, y, walkable, transparent, sprite_index, tile_type): - """Set properties for a specific tile""" - if 0 <= x < self.width and 0 <= y < self.height: - cell = self.grid.at(x, y) - cell.walkable = walkable - cell.transparent = transparent - cell.sprite_index = sprite_index - cell.color = mcrfpy.Color(*COLORS_VISIBLE[tile_type]) - - def generate_dungeon(self, max_rooms, room_min_size, room_max_size, player, max_enemies_per_room): - """Generate a new dungeon map""" - self.fill_with_walls() - - for r in range(max_rooms): - room_width = random.randint(room_min_size, room_max_size) - room_height = random.randint(room_min_size, room_max_size) - - x = random.randint(0, self.width - room_width - 1) - y = random.randint(0, self.height - room_height - 1) - - new_room = RectangularRoom(x, y, room_width, room_height) - - if any(new_room.intersects(other_room) for other_room in self.rooms): - continue - - self.carve_room(new_room) - - if len(self.rooms) == 0: - # First room - place player - player.x, player.y = new_room.center - if player._entity: - player._entity.x, player._entity.y = new_room.center - else: - # All other rooms - add tunnel and enemies - self.carve_tunnel(self.rooms[-1].center, new_room.center) - spawn_enemies_in_room(new_room, self, max_enemies_per_room) - - self.rooms.append(new_room) - - def carve_room(self, room): - """Carve out a room""" - inner_x1, inner_y1, inner_x2, inner_y2 = room.inner - - for y in range(inner_y1, inner_y2): - for x in range(inner_x1, inner_x2): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, tile_type='floor') - - def carve_tunnel(self, start, end): - """Carve a tunnel between two points""" - for x, y in tunnel_between(start, end): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, tile_type='tunnel') - - def get_blocking_entity_at(self, x, y): - """Return any blocking entity at the given position""" - for entity in self.entities: - if entity.blocks and entity.x == x and entity.y == y: - return entity - return None - - def is_blocked(self, x, y): - """Check if a tile blocks movement""" - if x < 0 or x >= self.width or y < 0 or y >= self.height: - return True - - if not self.grid.at(x, y).walkable: - return True - - if self.get_blocking_entity_at(x, y): - return True - - return False - - def add_entity(self, entity): - """Add a GameObject to the map""" - self.entities.append(entity) - entity.attach_to_grid(self.grid) - -class Engine: - """Main game engine""" - - def __init__(self): - self.game_map = None - self.player = None - self.entities = [] - - mcrfpy.createScene("game") - mcrfpy.setScene("game") - - window = mcrfpy.Window.get() - window.title = "McRogueFace Roguelike - Part 5" - - self.ui = mcrfpy.sceneUI("game") - - background = mcrfpy.Frame(0, 0, 1024, 768) - background.fill_color = mcrfpy.Color(0, 0, 0) - self.ui.append(background) - - self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16) - - self.setup_game() - self.setup_input() - self.setup_ui() - - def setup_game(self): - """Initialize the game world""" - self.game_map = GameMap(80, 45) - grid = self.game_map.create_grid(self.tileset) - self.ui.append(grid) - - # Create player - self.player = GameObject(0, 0, 64, (255, 255, 255), "Player", blocks=True) - - # Generate the dungeon - self.game_map.generate_dungeon( - max_rooms=30, - room_min_size=6, - room_max_size=10, - player=self.player, - max_enemies_per_room=2 - ) - - # Add player to map - self.game_map.add_entity(self.player) - - # Store reference to all entities - self.entities = [e for e in self.game_map.entities if e != self.player] - - # Initial FOV calculation - self.player.update_fov() - - def handle_player_turn(self, action): - """Process the player's action""" - if isinstance(action, MovementAction): - dest_x = self.player.x + action.dx - dest_y = self.player.y + action.dy - - # Check what's at the destination - target = self.game_map.get_blocking_entity_at(dest_x, dest_y) - - if target: - # We bumped into something! - print(f"You kick the {target.name} in the shins, much to its annoyance!") - self.status_text.text = f"You kick the {target.name}!" - elif not self.game_map.is_blocked(dest_x, dest_y): - # Move the player - self.player.move(action.dx, action.dy) - self.status_text.text = "" - else: - # Bumped into a wall - self.status_text.text = "Blocked!" - - elif isinstance(action, WaitAction): - self.status_text.text = "You wait..." - - def setup_input(self): - """Setup keyboard input handling""" - def handle_keys(key, state): - if state != "start": - return - - action = None - - # Movement keys - movement = { - "Up": (0, -1), "Down": (0, 1), - "Left": (-1, 0), "Right": (1, 0), - "Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1), - "Num4": (-1, 0), "Num5": (0, 0), "Num6": (1, 0), - "Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1), - } - - if key in movement: - dx, dy = movement[key] - if dx == 0 and dy == 0: - action = WaitAction() - else: - action = MovementAction(dx, dy) - elif key == "Period": - action = WaitAction() - elif key == "Escape": - mcrfpy.setScene(None) - return - - # Process the action - if action: - self.handle_player_turn(action) - - mcrfpy.keypressScene(handle_keys) - - def setup_ui(self): - """Setup UI elements""" - title = mcrfpy.Caption("Placing Enemies", 512, 30) - title.font_size = 24 - title.fill_color = mcrfpy.Color(255, 255, 100) - self.ui.append(title) - - instructions = mcrfpy.Caption("Arrow keys to move | . to wait | Bump into enemies! | ESC to quit", 512, 60) - instructions.font_size = 16 - instructions.fill_color = mcrfpy.Color(200, 200, 200) - self.ui.append(instructions) - - # Status text - self.status_text = mcrfpy.Caption("", 512, 600) - self.status_text.font_size = 18 - self.status_text.fill_color = mcrfpy.Color(255, 200, 200) - self.ui.append(self.status_text) - - # Entity count - entity_count = len(self.entities) - count_text = mcrfpy.Caption(f"Enemies: {entity_count}", 900, 100) - count_text.font_size = 14 - count_text.fill_color = mcrfpy.Color(150, 150, 255) - self.ui.append(count_text) - -# Create and run the game -engine = Engine() -print("Part 5: Placing Enemies!") -print("Try bumping into enemies - combat coming in Part 6!") -``` - -## Understanding Entity Interactions - -### Collision Detection -Our system now checks three things when the player tries to move: -1. **Map boundaries** - Can't move outside the map -2. **Wall tiles** - Can't walk through walls -3. **Blocking entities** - Can't walk through enemies - -### The Action System -We've introduced a simple action system that will grow in Part 6: -- `Action` - Base class for all actions -- `MovementAction` - Represents attempted movement -- `WaitAction` - Skip a turn (important for turn-based games) - -### Entity Spawning -Enemies are placed randomly in rooms with these rules: -- Never in the first room (player's starting room) -- Random number between 0 and max per room -- 80% orcs, 20% trolls -- Must be placed on walkable, unoccupied tiles - -## Visual Feedback - -With FOV enabled, enemies will appear and disappear as you explore: -- Enemies in sight are fully visible -- Enemies in explored but dark areas are hidden -- Creates tension and surprise encounters - -## Exercises - -1. **More Enemy Types**: Add different sprites and names (goblins, skeletons) -2. **Enemy Density**: Adjust spawn rates based on dungeon depth -3. **Special Rooms**: Create rooms with guaranteed enemies or treasures -4. **Better Feedback**: Add sound effects or visual effects for bumping - -## What's Next? - -In Part 6, we'll transform those harmless kicks into a real combat system! We'll add: -- Health points for all entities -- Damage calculations -- Death and corpses -- Combat messages -- The beginning of a real roguelike! - -Right now our enemies are just obstacles. Soon they'll fight back! \ No newline at end of file diff --git a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_5/code/game.py b/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_5/code/game.py deleted file mode 100644 index 3e5947f..0000000 --- a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_5/code/game.py +++ /dev/null @@ -1,388 +0,0 @@ -import mcrfpy -import random - -# Color configurations -COLORS_VISIBLE = { - 'wall': (100, 100, 100), - 'floor': (50, 50, 50), - 'tunnel': (30, 30, 40), -} - -# Actions -class Action: - """Base class for all actions""" - pass - -class MovementAction(Action): - """Action for moving an entity""" - def __init__(self, dx, dy): - self.dx = dx - self.dy = dy - -class WaitAction(Action): - """Action for waiting/skipping turn""" - pass - -class GameObject: - """Base class for all game objects""" - def __init__(self, x, y, sprite_index, color, name, blocks=False): - self.x = x - self.y = y - self.sprite_index = sprite_index - self.color = color - self.name = name - self.blocks = blocks - self._entity = None - self.grid = None - - def attach_to_grid(self, grid): - """Attach this game object to a McRogueFace grid""" - self.grid = grid - self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid) - self._entity.sprite_index = self.sprite_index - self._entity.color = mcrfpy.Color(*self.color) - - def move(self, dx, dy): - """Move by the given amount""" - if not self.grid: - return - self.x += dx - self.y += dy - if self._entity: - self._entity.x = self.x - self._entity.y = self.y - # Update FOV when player moves - if self.name == "Player": - self.update_fov() - - def update_fov(self): - """Update field of view from this entity's position""" - if self._entity and self.grid: - self._entity.update_fov(radius=8) - -class RectangularRoom: - """A rectangular room with its position and size""" - - def __init__(self, x, y, width, height): - self.x1 = x - self.y1 = y - self.x2 = x + width - self.y2 = y + height - - @property - def center(self): - center_x = (self.x1 + self.x2) // 2 - center_y = (self.y1 + self.y2) // 2 - return center_x, center_y - - @property - def inner(self): - return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1 - - def intersects(self, other): - return ( - self.x1 <= other.x2 - and self.x2 >= other.x1 - and self.y1 <= other.y2 - and self.y2 >= other.y1 - ) - -def tunnel_between(start, end): - """Return an L-shaped tunnel between two points""" - x1, y1 = start - x2, y2 = end - - if random.random() < 0.5: - corner_x = x2 - corner_y = y1 - else: - corner_x = x1 - corner_y = y2 - - for x in range(min(x1, corner_x), max(x1, corner_x) + 1): - yield x, y1 - for y in range(min(y1, corner_y), max(y1, corner_y) + 1): - yield corner_x, y - for x in range(min(corner_x, x2), max(corner_x, x2) + 1): - yield x, corner_y - for y in range(min(corner_y, y2), max(corner_y, y2) + 1): - yield x2, y - -def spawn_enemies_in_room(room, game_map, max_enemies=2): - """Spawn between 0 and max_enemies in a room""" - number_of_enemies = random.randint(0, max_enemies) - - enemies_spawned = [] - - for i in range(number_of_enemies): - # Try to find a valid position - attempts = 10 - while attempts > 0: - # Random position within room bounds - x = random.randint(room.x1 + 1, room.x2 - 1) - y = random.randint(room.y1 + 1, room.y2 - 1) - - # Check if position is valid - if not game_map.is_blocked(x, y): - # 80% chance for orc, 20% for troll - if random.random() < 0.8: - enemy = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True) - else: - enemy = GameObject(x, y, 84, (0, 127, 0), "Troll", blocks=True) - - game_map.add_entity(enemy) - enemies_spawned.append(enemy) - break - - attempts -= 1 - - return enemies_spawned - -class GameMap: - """Manages the game world""" - - def __init__(self, width, height): - self.width = width - self.height = height - self.grid = None - self.entities = [] - self.rooms = [] - - def create_grid(self, tileset): - """Create the McRogueFace grid""" - self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset) - self.grid.position = (100, 100) - self.grid.size = (800, 480) - - # Enable perspective rendering - self.grid.perspective = 0 - - return self.grid - - def fill_with_walls(self): - """Fill the entire map with wall tiles""" - for y in range(self.height): - for x in range(self.width): - self.set_tile(x, y, walkable=False, transparent=False, - sprite_index=35, tile_type='wall') - - def set_tile(self, x, y, walkable, transparent, sprite_index, tile_type): - """Set properties for a specific tile""" - if 0 <= x < self.width and 0 <= y < self.height: - cell = self.grid.at(x, y) - cell.walkable = walkable - cell.transparent = transparent - cell.sprite_index = sprite_index - cell.color = mcrfpy.Color(*COLORS_VISIBLE[tile_type]) - - def generate_dungeon(self, max_rooms, room_min_size, room_max_size, player, max_enemies_per_room): - """Generate a new dungeon map""" - self.fill_with_walls() - - for r in range(max_rooms): - room_width = random.randint(room_min_size, room_max_size) - room_height = random.randint(room_min_size, room_max_size) - - x = random.randint(0, self.width - room_width - 1) - y = random.randint(0, self.height - room_height - 1) - - new_room = RectangularRoom(x, y, room_width, room_height) - - if any(new_room.intersects(other_room) for other_room in self.rooms): - continue - - self.carve_room(new_room) - - if len(self.rooms) == 0: - # First room - place player - player.x, player.y = new_room.center - if player._entity: - player._entity.x, player._entity.y = new_room.center - else: - # All other rooms - add tunnel and enemies - self.carve_tunnel(self.rooms[-1].center, new_room.center) - spawn_enemies_in_room(new_room, self, max_enemies_per_room) - - self.rooms.append(new_room) - - def carve_room(self, room): - """Carve out a room""" - inner_x1, inner_y1, inner_x2, inner_y2 = room.inner - - for y in range(inner_y1, inner_y2): - for x in range(inner_x1, inner_x2): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, tile_type='floor') - - def carve_tunnel(self, start, end): - """Carve a tunnel between two points""" - for x, y in tunnel_between(start, end): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, tile_type='tunnel') - - def get_blocking_entity_at(self, x, y): - """Return any blocking entity at the given position""" - for entity in self.entities: - if entity.blocks and entity.x == x and entity.y == y: - return entity - return None - - def is_blocked(self, x, y): - """Check if a tile blocks movement""" - if x < 0 or x >= self.width or y < 0 or y >= self.height: - return True - - if not self.grid.at(x, y).walkable: - return True - - if self.get_blocking_entity_at(x, y): - return True - - return False - - def add_entity(self, entity): - """Add a GameObject to the map""" - self.entities.append(entity) - entity.attach_to_grid(self.grid) - -class Engine: - """Main game engine""" - - def __init__(self): - self.game_map = None - self.player = None - self.entities = [] - - mcrfpy.createScene("game") - mcrfpy.setScene("game") - - window = mcrfpy.Window.get() - window.title = "McRogueFace Roguelike - Part 5" - - self.ui = mcrfpy.sceneUI("game") - - background = mcrfpy.Frame(0, 0, 1024, 768) - background.fill_color = mcrfpy.Color(0, 0, 0) - self.ui.append(background) - - self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16) - - self.setup_game() - self.setup_input() - self.setup_ui() - - def setup_game(self): - """Initialize the game world""" - self.game_map = GameMap(80, 45) - grid = self.game_map.create_grid(self.tileset) - self.ui.append(grid) - - # Create player - self.player = GameObject(0, 0, 64, (255, 255, 255), "Player", blocks=True) - - # Generate the dungeon - self.game_map.generate_dungeon( - max_rooms=30, - room_min_size=6, - room_max_size=10, - player=self.player, - max_enemies_per_room=2 - ) - - # Add player to map - self.game_map.add_entity(self.player) - - # Store reference to all entities - self.entities = [e for e in self.game_map.entities if e != self.player] - - # Initial FOV calculation - self.player.update_fov() - - def handle_player_turn(self, action): - """Process the player's action""" - if isinstance(action, MovementAction): - dest_x = self.player.x + action.dx - dest_y = self.player.y + action.dy - - # Check what's at the destination - target = self.game_map.get_blocking_entity_at(dest_x, dest_y) - - if target: - # We bumped into something! - print(f"You kick the {target.name} in the shins, much to its annoyance!") - self.status_text.text = f"You kick the {target.name}!" - elif not self.game_map.is_blocked(dest_x, dest_y): - # Move the player - self.player.move(action.dx, action.dy) - self.status_text.text = "" - else: - # Bumped into a wall - self.status_text.text = "Blocked!" - - elif isinstance(action, WaitAction): - self.status_text.text = "You wait..." - - def setup_input(self): - """Setup keyboard input handling""" - def handle_keys(key, state): - if state != "start": - return - - action = None - - # Movement keys - movement = { - "Up": (0, -1), "Down": (0, 1), - "Left": (-1, 0), "Right": (1, 0), - "Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1), - "Num4": (-1, 0), "Num5": (0, 0), "Num6": (1, 0), - "Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1), - } - - if key in movement: - dx, dy = movement[key] - if dx == 0 and dy == 0: - action = WaitAction() - else: - action = MovementAction(dx, dy) - elif key == "Period": - action = WaitAction() - elif key == "Escape": - mcrfpy.setScene(None) - return - - # Process the action - if action: - self.handle_player_turn(action) - - mcrfpy.keypressScene(handle_keys) - - def setup_ui(self): - """Setup UI elements""" - title = mcrfpy.Caption("Placing Enemies", 512, 30) - title.font_size = 24 - title.fill_color = mcrfpy.Color(255, 255, 100) - self.ui.append(title) - - instructions = mcrfpy.Caption("Arrow keys to move | . to wait | Bump into enemies! | ESC to quit", 512, 60) - instructions.font_size = 16 - instructions.fill_color = mcrfpy.Color(200, 200, 200) - self.ui.append(instructions) - - # Status text - self.status_text = mcrfpy.Caption("", 512, 600) - self.status_text.font_size = 18 - self.status_text.fill_color = mcrfpy.Color(255, 200, 200) - self.ui.append(self.status_text) - - # Entity count - entity_count = len(self.entities) - count_text = mcrfpy.Caption(f"Enemies: {entity_count}", 900, 100) - count_text.font_size = 14 - count_text.fill_color = mcrfpy.Color(150, 150, 255) - self.ui.append(count_text) - -# Create and run the game -engine = Engine() -print("Part 5: Placing Enemies!") -print("Try bumping into enemies - combat coming in Part 6!") \ No newline at end of file diff --git a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_6/README.md b/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_6/README.md deleted file mode 100644 index 15f4739..0000000 --- a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_6/README.md +++ /dev/null @@ -1,743 +0,0 @@ -# Part 6 - Doing (and Taking) Some Damage - -It's time to turn our harmless kicks into real combat! In this part, we'll implement: -- Health points for all entities -- A damage calculation system -- Death and corpse mechanics -- Combat feedback messages -- The foundation of tactical roguelike combat - -## Adding Combat Stats - -First, let's enhance our GameObject class with combat capabilities: - -```python -class GameObject: - """Base class for all game objects""" - def __init__(self, x, y, sprite_index, color, name, - blocks=False, hp=0, defense=0, power=0): - self.x = x - self.y = y - self.sprite_index = sprite_index - self.color = color - self.name = name - self.blocks = blocks - self._entity = None - self.grid = None - - # Combat stats - self.max_hp = hp - self.hp = hp - self.defense = defense - self.power = power - - @property - def is_alive(self): - """Returns True if this entity can act""" - return self.hp > 0 - - def take_damage(self, amount): - """Apply damage to this entity""" - damage = amount - self.defense - if damage > 0: - self.hp -= damage - - # Check for death - if self.hp <= 0 and self.hp + damage > 0: - self.die() - - return damage - - def die(self): - """Handle entity death""" - if self.name == "Player": - # Player death is special - we'll handle it differently - self.sprite_index = 64 # Stay as @ but change color - self.color = (127, 0, 0) # Dark red - if self._entity: - self._entity.color = mcrfpy.Color(127, 0, 0) - print("You have died!") - else: - # Enemy death - self.sprite_index = 37 # % character for corpse - self.color = (127, 0, 0) # Dark red - self.blocks = False # Corpses don't block - self.name = f"remains of {self.name}" - - if self._entity: - self._entity.sprite_index = 37 - self._entity.color = mcrfpy.Color(127, 0, 0) -``` - -## The Combat System - -Now let's implement actual combat when entities bump into each other: - -```python -class MeleeAction(Action): - """Action for melee attacks""" - def __init__(self, attacker, target): - self.attacker = attacker - self.target = target - - def perform(self): - """Execute the attack""" - if not self.target.is_alive: - return # Can't attack the dead - - damage = self.attacker.power - self.target.defense - - if damage > 0: - attack_desc = f"{self.attacker.name} attacks {self.target.name} for {damage} damage!" - self.target.take_damage(damage) - else: - attack_desc = f"{self.attacker.name} attacks {self.target.name} but does no damage." - - return attack_desc -``` - -## Entity Factories - -Let's create factory functions for consistent entity creation: - -```python -def create_player(x, y): - """Create the player entity""" - return GameObject( - x=x, y=y, - sprite_index=64, # @ - color=(255, 255, 255), - name="Player", - blocks=True, - hp=30, - defense=2, - power=5 - ) - -def create_orc(x, y): - """Create an orc enemy""" - return GameObject( - x=x, y=y, - sprite_index=111, # o - color=(63, 127, 63), - name="Orc", - blocks=True, - hp=10, - defense=0, - power=3 - ) - -def create_troll(x, y): - """Create a troll enemy""" - return GameObject( - x=x, y=y, - sprite_index=84, # T - color=(0, 127, 0), - name="Troll", - blocks=True, - hp=16, - defense=1, - power=4 - ) -``` - -## The Message Log - -Combat needs feedback! Let's create a simple message log: - -```python -class MessageLog: - """Manages game messages""" - def __init__(self, max_messages=5): - self.messages = [] - self.max_messages = max_messages - - def add_message(self, text, color=(255, 255, 255)): - """Add a message to the log""" - self.messages.append((text, color)) - # Keep only recent messages - if len(self.messages) > self.max_messages: - self.messages.pop(0) - - def render(self, ui, x, y, line_height=20): - """Render messages to the UI""" - for i, (text, color) in enumerate(self.messages): - caption = mcrfpy.Caption(text, x, y + i * line_height) - caption.font_size = 14 - caption.fill_color = mcrfpy.Color(*color) - ui.append(caption) -``` - -## Complete Implementation - -Here's the complete `game.py` with combat: - -```python -import mcrfpy -import random - -# Color configurations -COLORS_VISIBLE = { - 'wall': (100, 100, 100), - 'floor': (50, 50, 50), - 'tunnel': (30, 30, 40), -} - -# Message colors -COLOR_PLAYER_ATK = (230, 230, 230) -COLOR_ENEMY_ATK = (255, 200, 200) -COLOR_PLAYER_DIE = (255, 100, 100) -COLOR_ENEMY_DIE = (255, 165, 0) - -# Actions -class Action: - """Base class for all actions""" - pass - -class MovementAction(Action): - """Action for moving an entity""" - def __init__(self, dx, dy): - self.dx = dx - self.dy = dy - -class MeleeAction(Action): - """Action for melee attacks""" - def __init__(self, attacker, target): - self.attacker = attacker - self.target = target - - def perform(self): - """Execute the attack""" - if not self.target.is_alive: - return None - - damage = self.attacker.power - self.target.defense - - if damage > 0: - attack_desc = f"{self.attacker.name} attacks {self.target.name} for {damage} damage!" - self.target.take_damage(damage) - - # Choose color based on attacker - if self.attacker.name == "Player": - color = COLOR_PLAYER_ATK - else: - color = COLOR_ENEMY_ATK - - return attack_desc, color - else: - attack_desc = f"{self.attacker.name} attacks {self.target.name} but does no damage." - return attack_desc, (150, 150, 150) - -class WaitAction(Action): - """Action for waiting/skipping turn""" - pass - -class GameObject: - """Base class for all game objects""" - def __init__(self, x, y, sprite_index, color, name, - blocks=False, hp=0, defense=0, power=0): - self.x = x - self.y = y - self.sprite_index = sprite_index - self.color = color - self.name = name - self.blocks = blocks - self._entity = None - self.grid = None - - # Combat stats - self.max_hp = hp - self.hp = hp - self.defense = defense - self.power = power - - @property - def is_alive(self): - """Returns True if this entity can act""" - return self.hp > 0 - - def attach_to_grid(self, grid): - """Attach this game object to a McRogueFace grid""" - self.grid = grid - self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid) - self._entity.sprite_index = self.sprite_index - self._entity.color = mcrfpy.Color(*self.color) - - def move(self, dx, dy): - """Move by the given amount""" - if not self.grid: - return - self.x += dx - self.y += dy - if self._entity: - self._entity.x = self.x - self._entity.y = self.y - # Update FOV when player moves - if self.name == "Player": - self.update_fov() - - def update_fov(self): - """Update field of view from this entity's position""" - if self._entity and self.grid: - self._entity.update_fov(radius=8) - - def take_damage(self, amount): - """Apply damage to this entity""" - self.hp -= amount - - # Check for death - if self.hp <= 0: - self.die() - - def die(self): - """Handle entity death""" - if self.name == "Player": - # Player death - self.sprite_index = 64 # Stay as @ - self.color = (127, 0, 0) # Dark red - if self._entity: - self._entity.color = mcrfpy.Color(127, 0, 0) - else: - # Enemy death - self.sprite_index = 37 # % character for corpse - self.color = (127, 0, 0) # Dark red - self.blocks = False # Corpses don't block - self.name = f"remains of {self.name}" - - if self._entity: - self._entity.sprite_index = 37 - self._entity.color = mcrfpy.Color(127, 0, 0) - -# Entity factories -def create_player(x, y): - """Create the player entity""" - return GameObject( - x=x, y=y, - sprite_index=64, # @ - color=(255, 255, 255), - name="Player", - blocks=True, - hp=30, - defense=2, - power=5 - ) - -def create_orc(x, y): - """Create an orc enemy""" - return GameObject( - x=x, y=y, - sprite_index=111, # o - color=(63, 127, 63), - name="Orc", - blocks=True, - hp=10, - defense=0, - power=3 - ) - -def create_troll(x, y): - """Create a troll enemy""" - return GameObject( - x=x, y=y, - sprite_index=84, # T - color=(0, 127, 0), - name="Troll", - blocks=True, - hp=16, - defense=1, - power=4 - ) - -class RectangularRoom: - """A rectangular room with its position and size""" - - def __init__(self, x, y, width, height): - self.x1 = x - self.y1 = y - self.x2 = x + width - self.y2 = y + height - - @property - def center(self): - center_x = (self.x1 + self.x2) // 2 - center_y = (self.y1 + self.y2) // 2 - return center_x, center_y - - @property - def inner(self): - return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1 - - def intersects(self, other): - return ( - self.x1 <= other.x2 - and self.x2 >= other.x1 - and self.y1 <= other.y2 - and self.y2 >= other.y1 - ) - -def tunnel_between(start, end): - """Return an L-shaped tunnel between two points""" - x1, y1 = start - x2, y2 = end - - if random.random() < 0.5: - corner_x = x2 - corner_y = y1 - else: - corner_x = x1 - corner_y = y2 - - for x in range(min(x1, corner_x), max(x1, corner_x) + 1): - yield x, y1 - for y in range(min(y1, corner_y), max(y1, corner_y) + 1): - yield corner_x, y - for x in range(min(corner_x, x2), max(corner_x, x2) + 1): - yield x, corner_y - for y in range(min(corner_y, y2), max(corner_y, y2) + 1): - yield x2, y - -def spawn_enemies_in_room(room, game_map, max_enemies=2): - """Spawn between 0 and max_enemies in a room""" - number_of_enemies = random.randint(0, max_enemies) - - enemies_spawned = [] - - for i in range(number_of_enemies): - attempts = 10 - while attempts > 0: - x = random.randint(room.x1 + 1, room.x2 - 1) - y = random.randint(room.y1 + 1, room.y2 - 1) - - if not game_map.is_blocked(x, y): - # 80% chance for orc, 20% for troll - if random.random() < 0.8: - enemy = create_orc(x, y) - else: - enemy = create_troll(x, y) - - game_map.add_entity(enemy) - enemies_spawned.append(enemy) - break - - attempts -= 1 - - return enemies_spawned - -class GameMap: - """Manages the game world""" - - def __init__(self, width, height): - self.width = width - self.height = height - self.grid = None - self.entities = [] - self.rooms = [] - - def create_grid(self, tileset): - """Create the McRogueFace grid""" - self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset) - self.grid.position = (100, 100) - self.grid.size = (800, 480) - - # Enable perspective rendering - self.grid.perspective = 0 - - return self.grid - - def fill_with_walls(self): - """Fill the entire map with wall tiles""" - for y in range(self.height): - for x in range(self.width): - self.set_tile(x, y, walkable=False, transparent=False, - sprite_index=35, tile_type='wall') - - def set_tile(self, x, y, walkable, transparent, sprite_index, tile_type): - """Set properties for a specific tile""" - if 0 <= x < self.width and 0 <= y < self.height: - cell = self.grid.at(x, y) - cell.walkable = walkable - cell.transparent = transparent - cell.sprite_index = sprite_index - cell.color = mcrfpy.Color(*COLORS_VISIBLE[tile_type]) - - def generate_dungeon(self, max_rooms, room_min_size, room_max_size, player, max_enemies_per_room): - """Generate a new dungeon map""" - self.fill_with_walls() - - for r in range(max_rooms): - room_width = random.randint(room_min_size, room_max_size) - room_height = random.randint(room_min_size, room_max_size) - - x = random.randint(0, self.width - room_width - 1) - y = random.randint(0, self.height - room_height - 1) - - new_room = RectangularRoom(x, y, room_width, room_height) - - if any(new_room.intersects(other_room) for other_room in self.rooms): - continue - - self.carve_room(new_room) - - if len(self.rooms) == 0: - # First room - place player - player.x, player.y = new_room.center - if player._entity: - player._entity.x, player._entity.y = new_room.center - else: - # All other rooms - add tunnel and enemies - self.carve_tunnel(self.rooms[-1].center, new_room.center) - spawn_enemies_in_room(new_room, self, max_enemies_per_room) - - self.rooms.append(new_room) - - def carve_room(self, room): - """Carve out a room""" - inner_x1, inner_y1, inner_x2, inner_y2 = room.inner - - for y in range(inner_y1, inner_y2): - for x in range(inner_x1, inner_x2): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, tile_type='floor') - - def carve_tunnel(self, start, end): - """Carve a tunnel between two points""" - for x, y in tunnel_between(start, end): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, tile_type='tunnel') - - def get_blocking_entity_at(self, x, y): - """Return any blocking entity at the given position""" - for entity in self.entities: - if entity.blocks and entity.x == x and entity.y == y: - return entity - return None - - def is_blocked(self, x, y): - """Check if a tile blocks movement""" - if x < 0 or x >= self.width or y < 0 or y >= self.height: - return True - - if not self.grid.at(x, y).walkable: - return True - - if self.get_blocking_entity_at(x, y): - return True - - return False - - def add_entity(self, entity): - """Add a GameObject to the map""" - self.entities.append(entity) - entity.attach_to_grid(self.grid) - -class Engine: - """Main game engine""" - - def __init__(self): - self.game_map = None - self.player = None - self.entities = [] - self.messages = [] # Simple message log - self.max_messages = 5 - - mcrfpy.createScene("game") - mcrfpy.setScene("game") - - window = mcrfpy.Window.get() - window.title = "McRogueFace Roguelike - Part 6" - - self.ui = mcrfpy.sceneUI("game") - - background = mcrfpy.Frame(0, 0, 1024, 768) - background.fill_color = mcrfpy.Color(0, 0, 0) - self.ui.append(background) - - self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16) - - self.setup_game() - self.setup_input() - self.setup_ui() - - def add_message(self, text, color=(255, 255, 255)): - """Add a message to the log""" - self.messages.append((text, color)) - if len(self.messages) > self.max_messages: - self.messages.pop(0) - self.update_message_display() - - def update_message_display(self): - """Update the message display""" - # Clear old messages - for caption in self.message_captions: - # Remove from UI (McRogueFace doesn't have remove, so we hide it) - caption.text = "" - - # Display current messages - for i, (text, color) in enumerate(self.messages): - if i < len(self.message_captions): - self.message_captions[i].text = text - self.message_captions[i].fill_color = mcrfpy.Color(*color) - - def setup_game(self): - """Initialize the game world""" - self.game_map = GameMap(80, 45) - grid = self.game_map.create_grid(self.tileset) - self.ui.append(grid) - - # Create player - self.player = create_player(0, 0) - - # Generate the dungeon - self.game_map.generate_dungeon( - max_rooms=30, - room_min_size=6, - room_max_size=10, - player=self.player, - max_enemies_per_room=2 - ) - - # Add player to map - self.game_map.add_entity(self.player) - - # Store reference to all entities - self.entities = [e for e in self.game_map.entities if e != self.player] - - # Initial FOV calculation - self.player.update_fov() - - # Welcome message - self.add_message("Welcome to the dungeon!", (100, 100, 255)) - - def handle_player_turn(self, action): - """Process the player's action""" - if not self.player.is_alive: - return - - if isinstance(action, MovementAction): - dest_x = self.player.x + action.dx - dest_y = self.player.y + action.dy - - # Check what's at the destination - target = self.game_map.get_blocking_entity_at(dest_x, dest_y) - - if target: - # Attack! - attack = MeleeAction(self.player, target) - result = attack.perform() - if result: - text, color = result - self.add_message(text, color) - - # Check if target died - if not target.is_alive: - death_msg = f"The {target.name.replace('remains of ', '')} is dead!" - self.add_message(death_msg, COLOR_ENEMY_DIE) - - elif not self.game_map.is_blocked(dest_x, dest_y): - # Move the player - self.player.move(action.dx, action.dy) - - elif isinstance(action, WaitAction): - pass # Do nothing - - # Enemy turns - self.handle_enemy_turns() - - def handle_enemy_turns(self): - """Let all enemies take their turn""" - for entity in self.entities: - if entity.is_alive: - # Simple AI: if player is adjacent, attack. Otherwise, do nothing. - dx = entity.x - self.player.x - dy = entity.y - self.player.y - distance = abs(dx) + abs(dy) - - if distance == 1: # Adjacent to player - attack = MeleeAction(entity, self.player) - result = attack.perform() - if result: - text, color = result - self.add_message(text, color) - - # Check if player died - if not self.player.is_alive: - self.add_message("You have died!", COLOR_PLAYER_DIE) - - def setup_input(self): - """Setup keyboard input handling""" - def handle_keys(key, state): - if state != "start": - return - - action = None - - # Movement keys - movement = { - "Up": (0, -1), "Down": (0, 1), - "Left": (-1, 0), "Right": (1, 0), - "Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1), - "Num4": (-1, 0), "Num5": (0, 0), "Num6": (1, 0), - "Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1), - } - - if key in movement: - dx, dy = movement[key] - if dx == 0 and dy == 0: - action = WaitAction() - else: - action = MovementAction(dx, dy) - elif key == "Period": - action = WaitAction() - elif key == "Escape": - mcrfpy.setScene(None) - return - - # Process the action - if action: - self.handle_player_turn(action) - - mcrfpy.keypressScene(handle_keys) - - def setup_ui(self): - """Setup UI elements""" - title = mcrfpy.Caption("Combat System", 512, 30) - title.font_size = 24 - title.fill_color = mcrfpy.Color(255, 255, 100) - self.ui.append(title) - - instructions = mcrfpy.Caption("Attack enemies by bumping into them!", 512, 60) - instructions.font_size = 16 - instructions.fill_color = mcrfpy.Color(200, 200, 200) - self.ui.append(instructions) - - # Player stats - self.hp_text = mcrfpy.Caption(f"HP: {self.player.hp}/{self.player.max_hp}", 50, 100) - self.hp_text.font_size = 18 - self.hp_text.fill_color = mcrfpy.Color(255, 100, 100) - self.ui.append(self.hp_text) - - # Message log - self.message_captions = [] - for i in range(self.max_messages): - caption = mcrfpy.Caption("", 50, 620 + i * 20) - caption.font_size = 14 - caption.fill_color = mcrfpy.Color(200, 200, 200) - self.ui.append(caption) - self.message_captions.append(caption) - - # Timer to update HP display - def update_stats(dt): - self.hp_text.text = f"HP: {self.player.hp}/{self.player.max_hp}" - if self.player.hp <= 0: - self.hp_text.fill_color = mcrfpy.Color(127, 0, 0) - elif self.player.hp < self.player.max_hp // 3: - self.hp_text.fill_color = mcrfpy.Color(255, 100, 100) - else: - self.hp_text.fill_color = mcrfpy.Color(0, 255, 0) - - mcrfpy.setTimer("update_stats", update_stats, 100) - -# Create and run the game -engine = Engine() -print("Part 6: Combat System!") -print("Attack enemies to defeat them, but watch your HP!") \ No newline at end of file diff --git a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_6/code/game.py b/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_6/code/game.py deleted file mode 100644 index c83719b..0000000 --- a/roguelike_tutorial/mcrogueface_does_the_entire_tutorial_2025/Part_6/code/game.py +++ /dev/null @@ -1,568 +0,0 @@ -import mcrfpy -import random - -# Color configurations -COLORS_VISIBLE = { - 'wall': (100, 100, 100), - 'floor': (50, 50, 50), - 'tunnel': (30, 30, 40), -} - -# Message colors -COLOR_PLAYER_ATK = (230, 230, 230) -COLOR_ENEMY_ATK = (255, 200, 200) -COLOR_PLAYER_DIE = (255, 100, 100) -COLOR_ENEMY_DIE = (255, 165, 0) - -# Actions -class Action: - """Base class for all actions""" - pass - -class MovementAction(Action): - """Action for moving an entity""" - def __init__(self, dx, dy): - self.dx = dx - self.dy = dy - -class MeleeAction(Action): - """Action for melee attacks""" - def __init__(self, attacker, target): - self.attacker = attacker - self.target = target - - def perform(self): - """Execute the attack""" - if not self.target.is_alive: - return None - - damage = self.attacker.power - self.target.defense - - if damage > 0: - attack_desc = f"{self.attacker.name} attacks {self.target.name} for {damage} damage!" - self.target.take_damage(damage) - - # Choose color based on attacker - if self.attacker.name == "Player": - color = COLOR_PLAYER_ATK - else: - color = COLOR_ENEMY_ATK - - return attack_desc, color - else: - attack_desc = f"{self.attacker.name} attacks {self.target.name} but does no damage." - return attack_desc, (150, 150, 150) - -class WaitAction(Action): - """Action for waiting/skipping turn""" - pass - -class GameObject: - """Base class for all game objects""" - def __init__(self, x, y, sprite_index, color, name, - blocks=False, hp=0, defense=0, power=0): - self.x = x - self.y = y - self.sprite_index = sprite_index - self.color = color - self.name = name - self.blocks = blocks - self._entity = None - self.grid = None - - # Combat stats - self.max_hp = hp - self.hp = hp - self.defense = defense - self.power = power - - @property - def is_alive(self): - """Returns True if this entity can act""" - return self.hp > 0 - - def attach_to_grid(self, grid): - """Attach this game object to a McRogueFace grid""" - self.grid = grid - self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid) - self._entity.sprite_index = self.sprite_index - self._entity.color = mcrfpy.Color(*self.color) - - def move(self, dx, dy): - """Move by the given amount""" - if not self.grid: - return - self.x += dx - self.y += dy - if self._entity: - self._entity.x = self.x - self._entity.y = self.y - # Update FOV when player moves - if self.name == "Player": - self.update_fov() - - def update_fov(self): - """Update field of view from this entity's position""" - if self._entity and self.grid: - self._entity.update_fov(radius=8) - - def take_damage(self, amount): - """Apply damage to this entity""" - self.hp -= amount - - # Check for death - if self.hp <= 0: - self.die() - - def die(self): - """Handle entity death""" - if self.name == "Player": - # Player death - self.sprite_index = 64 # Stay as @ - self.color = (127, 0, 0) # Dark red - if self._entity: - self._entity.color = mcrfpy.Color(127, 0, 0) - else: - # Enemy death - self.sprite_index = 37 # % character for corpse - self.color = (127, 0, 0) # Dark red - self.blocks = False # Corpses don't block - self.name = f"remains of {self.name}" - - if self._entity: - self._entity.sprite_index = 37 - self._entity.color = mcrfpy.Color(127, 0, 0) - -# Entity factories -def create_player(x, y): - """Create the player entity""" - return GameObject( - x=x, y=y, - sprite_index=64, # @ - color=(255, 255, 255), - name="Player", - blocks=True, - hp=30, - defense=2, - power=5 - ) - -def create_orc(x, y): - """Create an orc enemy""" - return GameObject( - x=x, y=y, - sprite_index=111, # o - color=(63, 127, 63), - name="Orc", - blocks=True, - hp=10, - defense=0, - power=3 - ) - -def create_troll(x, y): - """Create a troll enemy""" - return GameObject( - x=x, y=y, - sprite_index=84, # T - color=(0, 127, 0), - name="Troll", - blocks=True, - hp=16, - defense=1, - power=4 - ) - -class RectangularRoom: - """A rectangular room with its position and size""" - - def __init__(self, x, y, width, height): - self.x1 = x - self.y1 = y - self.x2 = x + width - self.y2 = y + height - - @property - def center(self): - center_x = (self.x1 + self.x2) // 2 - center_y = (self.y1 + self.y2) // 2 - return center_x, center_y - - @property - def inner(self): - return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1 - - def intersects(self, other): - return ( - self.x1 <= other.x2 - and self.x2 >= other.x1 - and self.y1 <= other.y2 - and self.y2 >= other.y1 - ) - -def tunnel_between(start, end): - """Return an L-shaped tunnel between two points""" - x1, y1 = start - x2, y2 = end - - if random.random() < 0.5: - corner_x = x2 - corner_y = y1 - else: - corner_x = x1 - corner_y = y2 - - for x in range(min(x1, corner_x), max(x1, corner_x) + 1): - yield x, y1 - for y in range(min(y1, corner_y), max(y1, corner_y) + 1): - yield corner_x, y - for x in range(min(corner_x, x2), max(corner_x, x2) + 1): - yield x, corner_y - for y in range(min(corner_y, y2), max(corner_y, y2) + 1): - yield x2, y - -def spawn_enemies_in_room(room, game_map, max_enemies=2): - """Spawn between 0 and max_enemies in a room""" - number_of_enemies = random.randint(0, max_enemies) - - enemies_spawned = [] - - for i in range(number_of_enemies): - attempts = 10 - while attempts > 0: - x = random.randint(room.x1 + 1, room.x2 - 1) - y = random.randint(room.y1 + 1, room.y2 - 1) - - if not game_map.is_blocked(x, y): - # 80% chance for orc, 20% for troll - if random.random() < 0.8: - enemy = create_orc(x, y) - else: - enemy = create_troll(x, y) - - game_map.add_entity(enemy) - enemies_spawned.append(enemy) - break - - attempts -= 1 - - return enemies_spawned - -class GameMap: - """Manages the game world""" - - def __init__(self, width, height): - self.width = width - self.height = height - self.grid = None - self.entities = [] - self.rooms = [] - - def create_grid(self, tileset): - """Create the McRogueFace grid""" - self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset) - self.grid.position = (100, 100) - self.grid.size = (800, 480) - - # Enable perspective rendering - self.grid.perspective = 0 - - return self.grid - - def fill_with_walls(self): - """Fill the entire map with wall tiles""" - for y in range(self.height): - for x in range(self.width): - self.set_tile(x, y, walkable=False, transparent=False, - sprite_index=35, tile_type='wall') - - def set_tile(self, x, y, walkable, transparent, sprite_index, tile_type): - """Set properties for a specific tile""" - if 0 <= x < self.width and 0 <= y < self.height: - cell = self.grid.at(x, y) - cell.walkable = walkable - cell.transparent = transparent - cell.sprite_index = sprite_index - cell.color = mcrfpy.Color(*COLORS_VISIBLE[tile_type]) - - def generate_dungeon(self, max_rooms, room_min_size, room_max_size, player, max_enemies_per_room): - """Generate a new dungeon map""" - self.fill_with_walls() - - for r in range(max_rooms): - room_width = random.randint(room_min_size, room_max_size) - room_height = random.randint(room_min_size, room_max_size) - - x = random.randint(0, self.width - room_width - 1) - y = random.randint(0, self.height - room_height - 1) - - new_room = RectangularRoom(x, y, room_width, room_height) - - if any(new_room.intersects(other_room) for other_room in self.rooms): - continue - - self.carve_room(new_room) - - if len(self.rooms) == 0: - # First room - place player - player.x, player.y = new_room.center - if player._entity: - player._entity.x, player._entity.y = new_room.center - else: - # All other rooms - add tunnel and enemies - self.carve_tunnel(self.rooms[-1].center, new_room.center) - spawn_enemies_in_room(new_room, self, max_enemies_per_room) - - self.rooms.append(new_room) - - def carve_room(self, room): - """Carve out a room""" - inner_x1, inner_y1, inner_x2, inner_y2 = room.inner - - for y in range(inner_y1, inner_y2): - for x in range(inner_x1, inner_x2): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, tile_type='floor') - - def carve_tunnel(self, start, end): - """Carve a tunnel between two points""" - for x, y in tunnel_between(start, end): - self.set_tile(x, y, walkable=True, transparent=True, - sprite_index=46, tile_type='tunnel') - - def get_blocking_entity_at(self, x, y): - """Return any blocking entity at the given position""" - for entity in self.entities: - if entity.blocks and entity.x == x and entity.y == y: - return entity - return None - - def is_blocked(self, x, y): - """Check if a tile blocks movement""" - if x < 0 or x >= self.width or y < 0 or y >= self.height: - return True - - if not self.grid.at(x, y).walkable: - return True - - if self.get_blocking_entity_at(x, y): - return True - - return False - - def add_entity(self, entity): - """Add a GameObject to the map""" - self.entities.append(entity) - entity.attach_to_grid(self.grid) - -class Engine: - """Main game engine""" - - def __init__(self): - self.game_map = None - self.player = None - self.entities = [] - self.messages = [] # Simple message log - self.max_messages = 5 - - mcrfpy.createScene("game") - mcrfpy.setScene("game") - - window = mcrfpy.Window.get() - window.title = "McRogueFace Roguelike - Part 6" - - self.ui = mcrfpy.sceneUI("game") - - background = mcrfpy.Frame((0, 0), (1024, 768)) - background.fill_color = mcrfpy.Color(0, 0, 0) - self.ui.append(background) - - self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16) - - self.setup_game() - self.setup_input() - self.setup_ui() - - def add_message(self, text, color=(255, 255, 255)): - """Add a message to the log""" - self.messages.append((text, color)) - if len(self.messages) > self.max_messages: - self.messages.pop(0) - self.update_message_display() - - def update_message_display(self): - """Update the message display""" - # Clear old messages - for caption in self.message_captions: - # Remove from UI (McRogueFace doesn't have remove, so we hide it) - caption.text = "" - - # Display current messages - for i, (text, color) in enumerate(self.messages): - if i < len(self.message_captions): - self.message_captions[i].text = text - self.message_captions[i].fill_color = mcrfpy.Color(*color) - - def setup_game(self): - """Initialize the game world""" - self.game_map = GameMap(80, 45) - grid = self.game_map.create_grid(self.tileset) - self.ui.append(grid) - - # Create player - self.player = create_player(0, 0) - - # Generate the dungeon - self.game_map.generate_dungeon( - max_rooms=30, - room_min_size=6, - room_max_size=10, - player=self.player, - max_enemies_per_room=2 - ) - - # Add player to map - self.game_map.add_entity(self.player) - - # Store reference to all entities - self.entities = [e for e in self.game_map.entities if e != self.player] - - # Initial FOV calculation - self.player.update_fov() - - # Welcome message - self.add_message("Welcome to the dungeon!", (100, 100, 255)) - - def handle_player_turn(self, action): - """Process the player's action""" - if not self.player.is_alive: - return - - if isinstance(action, MovementAction): - dest_x = self.player.x + action.dx - dest_y = self.player.y + action.dy - - # Check what's at the destination - target = self.game_map.get_blocking_entity_at(dest_x, dest_y) - - if target: - # Attack! - attack = MeleeAction(self.player, target) - result = attack.perform() - if result: - text, color = result - self.add_message(text, color) - - # Check if target died - if not target.is_alive: - death_msg = f"The {target.name.replace('remains of ', '')} is dead!" - self.add_message(death_msg, COLOR_ENEMY_DIE) - - elif not self.game_map.is_blocked(dest_x, dest_y): - # Move the player - self.player.move(action.dx, action.dy) - - elif isinstance(action, WaitAction): - pass # Do nothing - - # Enemy turns - self.handle_enemy_turns() - - def handle_enemy_turns(self): - """Let all enemies take their turn""" - for entity in self.entities: - if entity.is_alive: - # Simple AI: if player is adjacent, attack. Otherwise, do nothing. - dx = entity.x - self.player.x - dy = entity.y - self.player.y - distance = abs(dx) + abs(dy) - - if distance == 1: # Adjacent to player - attack = MeleeAction(entity, self.player) - result = attack.perform() - if result: - text, color = result - self.add_message(text, color) - - # Check if player died - if not self.player.is_alive: - self.add_message("You have died!", COLOR_PLAYER_DIE) - - def setup_input(self): - """Setup keyboard input handling""" - def handle_keys(key, state): - if state != "start": - return - - action = None - - # Movement keys - movement = { - "Up": (0, -1), "Down": (0, 1), - "Left": (-1, 0), "Right": (1, 0), - "Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1), - "Num4": (-1, 0), "Num5": (0, 0), "Num6": (1, 0), - "Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1), - } - - if key in movement: - dx, dy = movement[key] - if dx == 0 and dy == 0: - action = WaitAction() - else: - action = MovementAction(dx, dy) - elif key == "Period": - action = WaitAction() - elif key == "Escape": - mcrfpy.setScene(None) - return - - # Process the action - if action: - self.handle_player_turn(action) - - mcrfpy.keypressScene(handle_keys) - - def setup_ui(self): - """Setup UI elements""" - title = mcrfpy.Caption("Combat System", 512, 30) - title.font_size = 24 - title.fill_color = mcrfpy.Color(255, 255, 100) - self.ui.append(title) - - instructions = mcrfpy.Caption("Attack enemies by bumping into them!", 512, 60) - instructions.font_size = 16 - instructions.fill_color = mcrfpy.Color(200, 200, 200) - self.ui.append(instructions) - - # Player stats - self.hp_text = mcrfpy.Caption(f"HP: {self.player.hp}/{self.player.max_hp}", 50, 100) - self.hp_text.font_size = 18 - self.hp_text.fill_color = mcrfpy.Color(255, 100, 100) - self.ui.append(self.hp_text) - - # Message log - self.message_captions = [] - for i in range(self.max_messages): - caption = mcrfpy.Caption("", 50, 620 + i * 20) - caption.font_size = 14 - caption.fill_color = mcrfpy.Color(200, 200, 200) - self.ui.append(caption) - self.message_captions.append(caption) - - # Timer to update HP display - def update_stats(dt): - self.hp_text.text = f"HP: {self.player.hp}/{self.player.max_hp}" - if self.player.hp <= 0: - self.hp_text.fill_color = mcrfpy.Color(127, 0, 0) - elif self.player.hp < self.player.max_hp // 3: - self.hp_text.fill_color = mcrfpy.Color(255, 100, 100) - else: - self.hp_text.fill_color = mcrfpy.Color(0, 255, 0) - - mcrfpy.setTimer("update_stats", update_stats, 100) - -# Create and run the game -engine = Engine() -print("Part 6: Combat System!") -print("Attack enemies to defeat them, but watch your HP!") diff --git a/roguelike_tutorial/part_0.py b/roguelike_tutorial/part_0.py deleted file mode 100644 index eb9ed94..0000000 --- a/roguelike_tutorial/part_0.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -McRogueFace Tutorial - Part 0: Introduction to Scene, Texture, and Grid - -This tutorial introduces the basic building blocks: -- Scene: A container for UI elements and game state -- Texture: Loading image assets for use in the game -- Grid: A tilemap component for rendering tile-based worlds -""" -import mcrfpy -import random - -# Create and activate a new scene -mcrfpy.createScene("tutorial") -mcrfpy.setScene("tutorial") - -# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) -texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) - -# Create a grid of tiles -# Each tile is 16x16 pixels, so with 3x zoom: 16*3 = 48 pixels per tile - -grid_width, grid_height = 25, 20 # width, height in number of tiles - -# calculating the size in pixels to fit the entire grid on-screen -zoom = 2.0 -grid_size = grid_width * zoom * 16, grid_height * zoom * 16 - -# calculating the position to center the grid on the screen - assuming default 1024x768 resolution -grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 - -grid = mcrfpy.Grid( - pos=grid_position, - grid_size=(grid_width, grid_height), - texture=texture, - size=grid_size, # height and width on screen -) - -grid.zoom = zoom -grid.center = (grid_width/2.0)*16, (grid_height/2.0)*16 # center on the middle of the central tile - -# Define tile types -FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] -WALL_TILES = [3, 7, 11] - -# Fill the grid with a simple pattern -for y in range(grid_height): - for x in range(grid_width): - # Create walls around the edges - if x == 0 or x == grid_width-1 or y == 0 or y == grid_height-1: - tile_index = random.choice(WALL_TILES) - else: - # Fill interior with floor tiles - tile_index = random.choice(FLOOR_TILES) - - # Set the tile at this position - point = grid.at(x, y) - if point: - point.tilesprite = tile_index - -# Add the grid to the scene -mcrfpy.sceneUI("tutorial").append(grid) - -# Add a title caption -title = mcrfpy.Caption((320, 10), - text="McRogueFace Tutorial - Part 0", -) -title.fill_color = mcrfpy.Color(255, 255, 255, 255) -mcrfpy.sceneUI("tutorial").append(title) - -# Add instructions -instructions = mcrfpy.Caption((280, 750), - text="Scene + Texture + Grid = Tilemap!", -) -instructions.font_size=18 -instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) -mcrfpy.sceneUI("tutorial").append(instructions) - -print("Tutorial Part 0 loaded!") -print(f"Created a {grid.grid_size[0]}x{grid.grid_size[1]} grid") -print(f"Grid positioned at ({grid.x}, {grid.y})") diff --git a/roguelike_tutorial/part_1.py b/roguelike_tutorial/part_1.py deleted file mode 100644 index 4c19d6d..0000000 --- a/roguelike_tutorial/part_1.py +++ /dev/null @@ -1,116 +0,0 @@ -""" -McRogueFace Tutorial - Part 1: Entities and Keyboard Input - -This tutorial builds on Part 0 by adding: -- Entity: A game object that can be placed in a grid -- Keyboard handling: Responding to key presses to move the entity -""" -import mcrfpy -import random - -# Create and activate a new scene -mcrfpy.createScene("tutorial") -mcrfpy.setScene("tutorial") - -# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) -texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) - -# Load the hero sprite texture (32x32 sprite sheet) -hero_texture = mcrfpy.Texture("assets/custom_player.png", 16, 16) - -# Create a grid of tiles -# Each tile is 16x16 pixels, so with 3x zoom: 16*3 = 48 pixels per tile - -grid_width, grid_height = 25, 20 # width, height in number of tiles - -# calculating the size in pixels to fit the entire grid on-screen -zoom = 2.0 -grid_size = grid_width * zoom * 16, grid_height * zoom * 16 - -# calculating the position to center the grid on the screen - assuming default 1024x768 resolution -grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 - -grid = mcrfpy.Grid( - pos=grid_position, - grid_size=(grid_width, grid_height), - texture=texture, - size=grid_size, # height and width on screen -) - -grid.zoom = zoom -grid.center = (grid_width/2.0)*16, (grid_height/2.0)*16 # center on the middle of the central tile - -# Define tile types -FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] -WALL_TILES = [3, 7, 11] - -# Fill the grid with a simple pattern -for y in range(grid_height): - for x in range(grid_width): - # Create walls around the edges - if x == 0 or x == grid_width-1 or y == 0 or y == grid_height-1: - tile_index = random.choice(WALL_TILES) - else: - # Fill interior with floor tiles - tile_index = random.choice(FLOOR_TILES) - - # Set the tile at this position - point = grid.at(x, y) - if point: - point.tilesprite = tile_index - -# Add the grid to the scene -mcrfpy.sceneUI("tutorial").append(grid) - -# Create a player entity at position (4, 4) -player = mcrfpy.Entity( - (4, 4), # Entity positions are tile coordinates - texture=hero_texture, - sprite_index=0 # Use the first sprite in the texture -) - -# Add the player entity to the grid -grid.entities.append(player) - -# Define keyboard handler -def handle_keys(key, state): - """Handle keyboard input to move the player""" - if state == "start": # Only respond to key press, not release - # Get current player position in grid coordinates - px, py = player.x, player.y - - # Calculate new position based on key press - if key == "W" or key == "Up": - py -= 1 - elif key == "S" or key == "Down": - py += 1 - elif key == "A" or key == "Left": - px -= 1 - elif key == "D" or key == "Right": - px += 1 - - # Update player position (no collision checking yet) - player.x = px - player.y = py - -# Register the keyboard handler -mcrfpy.keypressScene(handle_keys) - -# Add a title caption -title = mcrfpy.Caption((320, 10), - text="McRogueFace Tutorial - Part 1", -) -title.fill_color = mcrfpy.Color(255, 255, 255, 255) -mcrfpy.sceneUI("tutorial").append(title) - -# Add instructions -instructions = mcrfpy.Caption((200, 750), - text="Use WASD or Arrow Keys to move the hero!", -) -instructions.font_size=18 -instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) -mcrfpy.sceneUI("tutorial").append(instructions) - -print("Tutorial Part 1 loaded!") -print(f"Player entity created at grid position (4, 4)") -print("Use WASD or Arrow keys to move!") diff --git a/roguelike_tutorial/part_1b.py b/roguelike_tutorial/part_1b.py deleted file mode 100644 index 3894fc7..0000000 --- a/roguelike_tutorial/part_1b.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -McRogueFace Tutorial - Part 1: Entities and Keyboard Input - -This tutorial builds on Part 0 by adding: -- Entity: A game object that can be placed in a grid -- Keyboard handling: Responding to key presses to move the entity -""" -import mcrfpy -import random - -# Create and activate a new scene -mcrfpy.createScene("tutorial") -mcrfpy.setScene("tutorial") - -# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) -texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) - -# Load the hero sprite texture (32x32 sprite sheet) -hero_texture = mcrfpy.Texture("assets/custom_player.png", 16, 16) - -# Create a grid of tiles -# Each tile is 16x16 pixels, so with 3x zoom: 16*3 = 48 pixels per tile - -grid_width, grid_height = 25, 20 # width, height in number of tiles - -# calculating the size in pixels to fit the entire grid on-screen -zoom = 2.0 -grid_size = grid_width * zoom * 16, grid_height * zoom * 16 - -# calculating the position to center the grid on the screen - assuming default 1024x768 resolution -grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 - -grid = mcrfpy.Grid( - pos=grid_position, - grid_size=(grid_width, grid_height), - texture=texture, - size=grid_size, # height and width on screen -) - -grid.zoom = 3.0 # we're not using the zoom variable! It's going to be really big! - -# Define tile types -FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] -WALL_TILES = [3, 7, 11] - -# Fill the grid with a simple pattern -for y in range(grid_height): - for x in range(grid_width): - # Create walls around the edges - if x == 0 or x == grid_width-1 or y == 0 or y == grid_height-1: - tile_index = random.choice(WALL_TILES) - else: - # Fill interior with floor tiles - tile_index = random.choice(FLOOR_TILES) - - # Set the tile at this position - point = grid.at(x, y) - if point: - point.tilesprite = tile_index - -# Add the grid to the scene -mcrfpy.sceneUI("tutorial").append(grid) - -# Create a player entity at position (4, 4) -player = mcrfpy.Entity( - (4, 4), # Entity positions are tile coordinates - texture=hero_texture, - sprite_index=0 # Use the first sprite in the texture -) - -# Add the player entity to the grid -grid.entities.append(player) -grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 # grid center is in texture/pixel coordinates - -# Define keyboard handler -def handle_keys(key, state): - """Handle keyboard input to move the player""" - if state == "start": # Only respond to key press, not release - # Get current player position in grid coordinates - px, py = player.x, player.y - - # Calculate new position based on key press - if key == "W" or key == "Up": - py -= 1 - elif key == "S" or key == "Down": - py += 1 - elif key == "A" or key == "Left": - px -= 1 - elif key == "D" or key == "Right": - px += 1 - - # Update player position (no collision checking yet) - player.x = px - player.y = py - grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 # grid center is in texture/pixel coordinates - -# Register the keyboard handler -mcrfpy.keypressScene(handle_keys) - -# Add a title caption -title = mcrfpy.Caption((320, 10), - text="McRogueFace Tutorial - Part 1", -) -title.fill_color = mcrfpy.Color(255, 255, 255, 255) -mcrfpy.sceneUI("tutorial").append(title) - -# Add instructions -instructions = mcrfpy.Caption((200, 750), - text="Use WASD or Arrow Keys to move the hero!", -) -instructions.font_size=18 -instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) -mcrfpy.sceneUI("tutorial").append(instructions) - -print("Tutorial Part 1 loaded!") -print(f"Player entity created at grid position (4, 4)") -print("Use WASD or Arrow keys to move!") diff --git a/roguelike_tutorial/part_2-naive.py b/roguelike_tutorial/part_2-naive.py deleted file mode 100644 index 6959a4b..0000000 --- a/roguelike_tutorial/part_2-naive.py +++ /dev/null @@ -1,149 +0,0 @@ -""" -McRogueFace Tutorial - Part 2: Animated Movement - -This tutorial builds on Part 1 by adding: -- Animation system for smooth movement -- Movement that takes 0.5 seconds per tile -- Input blocking during movement animation -""" -import mcrfpy -import random - -# Create and activate a new scene -mcrfpy.createScene("tutorial") -mcrfpy.setScene("tutorial") - -# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) -texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) - -# Load the hero sprite texture (32x32 sprite sheet) -hero_texture = mcrfpy.Texture("assets/custom_player.png", 16, 16) - -# Create a grid of tiles -# Each tile is 16x16 pixels, so with 3x zoom: 16*3 = 48 pixels per tile - -grid_width, grid_height = 25, 20 # width, height in number of tiles - -# calculating the size in pixels to fit the entire grid on-screen -zoom = 2.0 -grid_size = grid_width * zoom * 16, grid_height * zoom * 16 - -# calculating the position to center the grid on the screen - assuming default 1024x768 resolution -grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 - -grid = mcrfpy.Grid( - pos=grid_position, - grid_size=(grid_width, grid_height), - texture=texture, - size=grid_size, # height and width on screen -) - -grid.zoom = 3.0 # we're not using the zoom variable! It's going to be really big! - -# Define tile types -FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] -WALL_TILES = [3, 7, 11] - -# Fill the grid with a simple pattern -for y in range(grid_height): - for x in range(grid_width): - # Create walls around the edges - if x == 0 or x == grid_width-1 or y == 0 or y == grid_height-1: - tile_index = random.choice(WALL_TILES) - else: - # Fill interior with floor tiles - tile_index = random.choice(FLOOR_TILES) - - # Set the tile at this position - point = grid.at(x, y) - if point: - point.tilesprite = tile_index - -# Add the grid to the scene -mcrfpy.sceneUI("tutorial").append(grid) - -# Create a player entity at position (4, 4) -player = mcrfpy.Entity( - (4, 4), # Entity positions are tile coordinates - texture=hero_texture, - sprite_index=0 # Use the first sprite in the texture -) - -# Add the player entity to the grid -grid.entities.append(player) -grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 # grid center is in texture/pixel coordinates - -# Movement state tracking -is_moving = False -move_animations = [] # Track active animations - -# Animation completion callback -def movement_complete(runtime): - """Called when movement animation completes""" - global is_moving - is_moving = False - # Ensure grid is centered on final position - grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 - -motion_speed = 0.30 # seconds per tile -# Define keyboard handler -def handle_keys(key, state): - """Handle keyboard input to move the player""" - global is_moving, move_animations - - if state == "start" and not is_moving: # Only respond to key press when not moving - # Get current player position in grid coordinates - px, py = player.x, player.y - new_x, new_y = px, py - - # Calculate new position based on key press - if key == "W" or key == "Up": - new_y -= 1 - elif key == "S" or key == "Down": - new_y += 1 - elif key == "A" or key == "Left": - new_x -= 1 - elif key == "D" or key == "Right": - new_x += 1 - - # If position changed, start movement animation - if new_x != px or new_y != py: - is_moving = True - - # Create animations for player position - anim_x = mcrfpy.Animation("x", float(new_x), motion_speed, "easeInOutQuad") - anim_y = mcrfpy.Animation("y", float(new_y), motion_speed, "easeInOutQuad") - anim_x.start(player) - anim_y.start(player) - - # Animate grid center to follow player - center_x = mcrfpy.Animation("center_x", (new_x + 0.5) * 16, motion_speed, "linear") - center_y = mcrfpy.Animation("center_y", (new_y + 0.5) * 16, motion_speed, "linear") - center_x.start(grid) - center_y.start(grid) - - # Set a timer to mark movement as complete - mcrfpy.setTimer("move_complete", movement_complete, 500) - -# Register the keyboard handler -mcrfpy.keypressScene(handle_keys) - -# Add a title caption -title = mcrfpy.Caption((320, 10), - text="McRogueFace Tutorial - Part 2", -) -title.fill_color = mcrfpy.Color(255, 255, 255, 255) -mcrfpy.sceneUI("tutorial").append(title) - -# Add instructions -instructions = mcrfpy.Caption((150, 750), - text="Smooth movement! Each step takes 0.5 seconds.", -) -instructions.font_size=18 -instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) -mcrfpy.sceneUI("tutorial").append(instructions) - -print("Tutorial Part 2 loaded!") -print(f"Player entity created at grid position (4, 4)") -print("Movement is now animated over 0.5 seconds per tile!") -print("Use WASD or Arrow keys to move!") diff --git a/roguelike_tutorial/part_2-onemovequeued.py b/roguelike_tutorial/part_2-onemovequeued.py deleted file mode 100644 index 126c433..0000000 --- a/roguelike_tutorial/part_2-onemovequeued.py +++ /dev/null @@ -1,241 +0,0 @@ -""" -McRogueFace Tutorial - Part 2: Enhanced with Single Move Queue - -This tutorial builds on Part 2 by adding: -- Single queued move system for responsive input -- Debug display showing position and queue status -- Smooth continuous movement when keys are held -- Animation callbacks to prevent race conditions -""" -import mcrfpy -import random - -# Create and activate a new scene -mcrfpy.createScene("tutorial") -mcrfpy.setScene("tutorial") - -# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) -texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) - -# Load the hero sprite texture (32x32 sprite sheet) -hero_texture = mcrfpy.Texture("assets/custom_player.png", 16, 16) - -# Create a grid of tiles -# Each tile is 16x16 pixels, so with 3x zoom: 16*3 = 48 pixels per tile - -grid_width, grid_height = 25, 20 # width, height in number of tiles - -# calculating the size in pixels to fit the entire grid on-screen -zoom = 2.0 -grid_size = grid_width * zoom * 16, grid_height * zoom * 16 - -# calculating the position to center the grid on the screen - assuming default 1024x768 resolution -grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 - -grid = mcrfpy.Grid( - pos=grid_position, - grid_size=(grid_width, grid_height), - texture=texture, - size=grid_size, # height and width on screen -) - -grid.zoom = 3.0 # we're not using the zoom variable! It's going to be really big! - -# Define tile types -FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] -WALL_TILES = [3, 7, 11] - -# Fill the grid with a simple pattern -for y in range(grid_height): - for x in range(grid_width): - # Create walls around the edges - if x == 0 or x == grid_width-1 or y == 0 or y == grid_height-1: - tile_index = random.choice(WALL_TILES) - else: - # Fill interior with floor tiles - tile_index = random.choice(FLOOR_TILES) - - # Set the tile at this position - point = grid.at(x, y) - if point: - point.tilesprite = tile_index - -# Add the grid to the scene -mcrfpy.sceneUI("tutorial").append(grid) - -# Create a player entity at position (4, 4) -player = mcrfpy.Entity( - (4, 4), # Entity positions are tile coordinates - texture=hero_texture, - sprite_index=0 # Use the first sprite in the texture -) - -# Add the player entity to the grid -grid.entities.append(player) -grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 # grid center is in texture/pixel coordinates - -# Movement state tracking -is_moving = False -move_queue = [] # List to store queued moves (max 1 item) -#last_position = (4, 4) # Track last position -current_destination = None # Track where we're currently moving to -current_move = None # Track current move direction - -# Store animation references -player_anim_x = None -player_anim_y = None -grid_anim_x = None -grid_anim_y = None - -# Debug display caption -debug_caption = mcrfpy.Caption((10, 40), - text="Last: (4, 4) | Queue: 0 | Dest: None", -) -debug_caption.font_size = 16 -debug_caption.fill_color = mcrfpy.Color(255, 255, 0, 255) -mcrfpy.sceneUI("tutorial").append(debug_caption) - -# Additional debug caption for movement state -move_debug_caption = mcrfpy.Caption((10, 60), - text="Moving: False | Current: None | Queued: None", -) -move_debug_caption.font_size = 16 -move_debug_caption.fill_color = mcrfpy.Color(255, 200, 0, 255) -mcrfpy.sceneUI("tutorial").append(move_debug_caption) - -def key_to_direction(key): - """Convert key to direction string""" - if key == "W" or key == "Up": - return "Up" - elif key == "S" or key == "Down": - return "Down" - elif key == "A" or key == "Left": - return "Left" - elif key == "D" or key == "Right": - return "Right" - return None - -def update_debug_display(): - """Update the debug caption with current state""" - queue_count = len(move_queue) - dest_text = f"({current_destination[0]}, {current_destination[1]})" if current_destination else "None" - debug_caption.text = f"Last: ({player.x}, {player.y}) | Queue: {queue_count} | Dest: {dest_text}" - - # Update movement state debug - current_dir = key_to_direction(current_move) if current_move else "None" - queued_dir = key_to_direction(move_queue[0]) if move_queue else "None" - move_debug_caption.text = f"Moving: {is_moving} | Current: {current_dir} | Queued: {queued_dir}" - -# Animation completion callback -def movement_complete(anim, target): - """Called when movement animation completes""" - global is_moving, move_queue, current_destination, current_move - global player_anim_x, player_anim_y - print(f"In callback for animation: {anim=} {target=}") - # Clear movement state - is_moving = False - current_move = None - current_destination = None - # Clear animation references - player_anim_x = None - player_anim_y = None - - # Update last position to where we actually are now - #last_position = (int(player.x), int(player.y)) - - # Ensure grid is centered on final position - grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 - - # Check if there's a queued move - if move_queue: - # Pop the next move from the queue - next_move = move_queue.pop(0) - print(f"Processing queued move: {next_move}") - # Process it like a fresh input - process_move(next_move) - - update_debug_display() - -motion_speed = 0.30 # seconds per tile - -def process_move(key): - """Process a move based on the key""" - global is_moving, current_move, current_destination, move_queue - global player_anim_x, player_anim_y, grid_anim_x, grid_anim_y - - # If already moving, just update the queue - if is_moving: - print(f"process_move processing {key=} as a queued move (is_moving = True)") - # Clear queue and add new move (only keep 1 queued move) - move_queue.clear() - move_queue.append(key) - update_debug_display() - return - print(f"process_move processing {key=} as a new, immediate animation (is_moving = False)") - # Calculate new position from current position - px, py = int(player.x), int(player.y) - new_x, new_y = px, py - - # Calculate new position based on key press (only one tile movement) - if key == "W" or key == "Up": - new_y -= 1 - elif key == "S" or key == "Down": - new_y += 1 - elif key == "A" or key == "Left": - new_x -= 1 - elif key == "D" or key == "Right": - new_x += 1 - - # Start the move if position changed - if new_x != px or new_y != py: - is_moving = True - current_move = key - current_destination = (new_x, new_y) - # only animate a single axis, same callback from either - if new_x != px: - player_anim_x = mcrfpy.Animation("x", float(new_x), motion_speed, "easeInOutQuad", callback=movement_complete) - player_anim_x.start(player) - elif new_y != py: - player_anim_y = mcrfpy.Animation("y", float(new_y), motion_speed, "easeInOutQuad", callback=movement_complete) - player_anim_y.start(player) - - # Animate grid center to follow player - grid_anim_x = mcrfpy.Animation("center_x", (new_x + 0.5) * 16, motion_speed, "linear") - grid_anim_y = mcrfpy.Animation("center_y", (new_y + 0.5) * 16, motion_speed, "linear") - grid_anim_x.start(grid) - grid_anim_y.start(grid) - - update_debug_display() - -# Define keyboard handler -def handle_keys(key, state): - """Handle keyboard input to move the player""" - if state == "start": - # Only process movement keys - if key in ["W", "Up", "S", "Down", "A", "Left", "D", "Right"]: - print(f"handle_keys producing actual input: {key=}") - process_move(key) - - -# Register the keyboard handler -mcrfpy.keypressScene(handle_keys) - -# Add a title caption -title = mcrfpy.Caption((320, 10), - text="McRogueFace Tutorial - Part 2 Enhanced", -) -title.fill_color = mcrfpy.Color(255, 255, 255, 255) -mcrfpy.sceneUI("tutorial").append(title) - -# Add instructions -instructions = mcrfpy.Caption((150, 750), - text="One-move queue system with animation callbacks!", -) -instructions.font_size=18 -instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) -mcrfpy.sceneUI("tutorial").append(instructions) - -print("Tutorial Part 2 Enhanced loaded!") -print(f"Player entity created at grid position (4, 4)") -print("Movement now uses animation callbacks to prevent race conditions!") -print("Use WASD or Arrow keys to move!") diff --git a/roguelike_tutorial/part_2.py b/roguelike_tutorial/part_2.py deleted file mode 100644 index 66a11b0..0000000 --- a/roguelike_tutorial/part_2.py +++ /dev/null @@ -1,149 +0,0 @@ -""" -McRogueFace Tutorial - Part 2: Animated Movement - -This tutorial builds on Part 1 by adding: -- Animation system for smooth movement -- Movement that takes 0.5 seconds per tile -- Input blocking during movement animation -""" -import mcrfpy -import random - -# Create and activate a new scene -mcrfpy.createScene("tutorial") -mcrfpy.setScene("tutorial") - -# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) -texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) - -# Load the hero sprite texture (32x32 sprite sheet) -hero_texture = mcrfpy.Texture("assets/custom_player.png", 16, 16) - -# Create a grid of tiles -# Each tile is 16x16 pixels, so with 3x zoom: 16*3 = 48 pixels per tile - -grid_width, grid_height = 25, 20 # width, height in number of tiles - -# calculating the size in pixels to fit the entire grid on-screen -zoom = 2.0 -grid_size = grid_width * zoom * 16, grid_height * zoom * 16 - -# calculating the position to center the grid on the screen - assuming default 1024x768 resolution -grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 - -grid = mcrfpy.Grid( - pos=grid_position, - grid_size=(grid_width, grid_height), - texture=texture, - size=grid_size, # height and width on screen -) - -grid.zoom = 3.0 # we're not using the zoom variable! It's going to be really big! - -# Define tile types -FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] -WALL_TILES = [3, 7, 11] - -# Fill the grid with a simple pattern -for y in range(grid_height): - for x in range(grid_width): - # Create walls around the edges - if x == 0 or x == grid_width-1 or y == 0 or y == grid_height-1: - tile_index = random.choice(WALL_TILES) - else: - # Fill interior with floor tiles - tile_index = random.choice(FLOOR_TILES) - - # Set the tile at this position - point = grid.at(x, y) - if point: - point.tilesprite = tile_index - -# Add the grid to the scene -mcrfpy.sceneUI("tutorial").append(grid) - -# Create a player entity at position (4, 4) -player = mcrfpy.Entity( - (4, 4), # Entity positions are tile coordinates - texture=hero_texture, - sprite_index=0 # Use the first sprite in the texture -) - -# Add the player entity to the grid -grid.entities.append(player) -grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 # grid center is in texture/pixel coordinates - -# Movement state tracking -is_moving = False -move_animations = [] # Track active animations - -# Animation completion callback -def movement_complete(runtime): - """Called when movement animation completes""" - global is_moving - is_moving = False - # Ensure grid is centered on final position - grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 - -motion_speed = 0.30 # seconds per tile -# Define keyboard handler -def handle_keys(key, state): - """Handle keyboard input to move the player""" - global is_moving, move_animations - - if state == "start" and not is_moving: # Only respond to key press when not moving - # Get current player position in grid coordinates - px, py = player.x, player.y - new_x, new_y = px, py - - # Calculate new position based on key press - if key == "W" or key == "Up": - new_y -= 1 - elif key == "S" or key == "Down": - new_y += 1 - elif key == "A" or key == "Left": - new_x -= 1 - elif key == "D" or key == "Right": - new_x += 1 - - # If position changed, start movement animation - if new_x != px or new_y != py: - is_moving = True - - # Create animations for player position - anim_x = mcrfpy.Animation("x", float(new_x), motion_speed, "easeInOutQuad") - anim_y = mcrfpy.Animation("y", float(new_y), motion_speed, "easeInOutQuad") - anim_x.start(player) - anim_y.start(player) - - # Animate grid center to follow player - center_x = mcrfpy.Animation("center_x", (new_x + 0.5) * 16, motion_speed, "linear") - center_y = mcrfpy.Animation("center_y", (new_y + 0.5) * 16, motion_speed, "linear") - center_x.start(grid) - center_y.start(grid) - - # Set a timer to mark movement as complete - mcrfpy.setTimer("move_complete", movement_complete, 500) - -# Register the keyboard handler -mcrfpy.keypressScene(handle_keys) - -# Add a title caption -title = mcrfpy.Caption((320, 10), - text="McRogueFace Tutorial - Part 2", -) -title.fill_color = mcrfpy.Color(255, 255, 255, 255) -mcrfpy.sceneUI("tutorial").append(title) - -# Add instructions -instructions = mcrfpy.Caption((150, 750), - "Smooth movement! Each step takes 0.5 seconds.", -) -instructions.font_size=18 -instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) -mcrfpy.sceneUI("tutorial").append(instructions) - -print("Tutorial Part 2 loaded!") -print(f"Player entity created at grid position (4, 4)") -print("Movement is now animated over 0.5 seconds per tile!") -print("Use WASD or Arrow keys to move!") diff --git a/roguelike_tutorial/part_3.py b/roguelike_tutorial/part_3.py deleted file mode 100644 index cb48b8b..0000000 --- a/roguelike_tutorial/part_3.py +++ /dev/null @@ -1,313 +0,0 @@ -""" -McRogueFace Tutorial - Part 3: Procedural Dungeon Generation - -This tutorial builds on Part 2 by adding: -- Binary Space Partition (BSP) dungeon generation -- Rooms connected by hallways using libtcod.line() -- Walkable/non-walkable terrain -- Player spawning in a valid location -- Wall tiles that block movement - -Key code references: -- src/scripts/cos_level.py (lines 7-15, 184-217, 218-224) - BSP algorithm -- mcrfpy.libtcod.line() for smooth hallway generation -""" -import mcrfpy -import random - -# Create and activate a new scene -mcrfpy.createScene("tutorial") -mcrfpy.setScene("tutorial") - -# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) -texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) - -# Load the hero sprite texture -hero_texture = mcrfpy.Texture("assets/custom_player.png", 16, 16) - -# Create a grid of tiles -grid_width, grid_height = 40, 30 # Larger grid for dungeon - -# Calculate the size in pixels to fit the entire grid on-screen -zoom = 2.0 -grid_size = grid_width * zoom * 16, grid_height * zoom * 16 - -# Calculate the position to center the grid on the screen -grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 - -# Create the grid with a TCODMap for pathfinding/FOV -grid = mcrfpy.Grid( - pos=grid_position, - grid_size=(grid_width, grid_height), - texture=texture, - size=grid_size, -) - -grid.zoom = zoom - -# Define tile types -FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] -WALL_TILES = [3, 7, 11] - -# Room class for BSP -class Room: - def __init__(self, x, y, w, h): - self.x1 = x - self.y1 = y - self.x2 = x + w - self.y2 = y + h - self.w = w - self.h = h - - def center(self): - """Return the center coordinates of the room""" - center_x = (self.x1 + self.x2) // 2 - center_y = (self.y1 + self.y2) // 2 - return (center_x, center_y) - - def intersects(self, other): - """Return True if this room overlaps with another""" - return (self.x1 <= other.x2 and self.x2 >= other.x1 and - self.y1 <= other.y2 and self.y2 >= other.y1) - -# Dungeon generation functions -def carve_room(room): - """Carve out a room in the grid - referenced from cos_level.py lines 117-120""" - # Using individual updates for now (batch updates would be more efficient) - for x in range(room.x1, room.x2): - for y in range(room.y1, room.y2): - if 0 <= x < grid_width and 0 <= y < grid_height: - point = grid.at(x, y) - if point: - point.tilesprite = random.choice(FLOOR_TILES) - point.walkable = True - point.transparent = True - -def carve_hallway(x1, y1, x2, y2): - """Carve a hallway between two points using libtcod.line() - Referenced from cos_level.py lines 184-217, improved with libtcod.line() - """ - # Get all points along the line - - # Simple solution: works if your characters have diagonal movement - #points = mcrfpy.libtcod.line(x1, y1, x2, y2) - - # We don't, so we're going to carve a path with an elbow in it - points = [] - if random.choice([True, False]): - # x1,y1 -> x2,y1 -> x2,y2 - points.extend(mcrfpy.libtcod.line(x1, y1, x2, y1)) - points.extend(mcrfpy.libtcod.line(x2, y1, x2, y2)) - else: - # x1,y1 -> x1,y2 -> x2,y2 - points.extend(mcrfpy.libtcod.line(x1, y1, x1, y2)) - points.extend(mcrfpy.libtcod.line(x1, y2, x2, y2)) - - - # Carve out each point - for x, y in points: - if 0 <= x < grid_width and 0 <= y < grid_height: - point = grid.at(x, y) - if point: - point.tilesprite = random.choice(FLOOR_TILES) - point.walkable = True - point.transparent = True - -def generate_dungeon(max_rooms=10, room_min_size=4, room_max_size=10): - """Generate a dungeon using simplified BSP approach - Referenced from cos_level.py lines 218-224 - """ - rooms = [] - - # First, fill everything with walls - for y in range(grid_height): - for x in range(grid_width): - point = grid.at(x, y) - if point: - point.tilesprite = random.choice(WALL_TILES) - point.walkable = False - point.transparent = False - - # Generate rooms - for _ in range(max_rooms): - # Random room size - w = random.randint(room_min_size, room_max_size) - h = random.randint(room_min_size, room_max_size) - - # Random position (with margin from edges) - x = random.randint(1, grid_width - w - 1) - y = random.randint(1, grid_height - h - 1) - - new_room = Room(x, y, w, h) - - # Check if it overlaps with existing rooms - failed = False - for other_room in rooms: - if new_room.intersects(other_room): - failed = True - break - - if not failed: - # Carve out the room - carve_room(new_room) - - # If not the first room, connect to previous room - if rooms: - # Get centers - prev_x, prev_y = rooms[-1].center() - new_x, new_y = new_room.center() - - # Carve hallway using libtcod.line() - carve_hallway(prev_x, prev_y, new_x, new_y) - - rooms.append(new_room) - - return rooms - -# Generate the dungeon -rooms = generate_dungeon(max_rooms=8, room_min_size=4, room_max_size=8) - -# Add the grid to the scene -mcrfpy.sceneUI("tutorial").append(grid) - -# Spawn player in the first room -if rooms: - spawn_x, spawn_y = rooms[0].center() -else: - # Fallback spawn position - spawn_x, spawn_y = 4, 4 - -# Create a player entity at the spawn position -player = mcrfpy.Entity( - (spawn_x, spawn_y), - texture=hero_texture, - sprite_index=0 -) - -# Add the player entity to the grid -grid.entities.append(player) -grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 - -# Movement state tracking (from Part 2) -is_moving = False -move_queue = [] -current_destination = None -current_move = None - -# Store animation references -player_anim_x = None -player_anim_y = None -grid_anim_x = None -grid_anim_y = None - -def movement_complete(anim, target): - """Called when movement animation completes""" - global is_moving, move_queue, current_destination, current_move - global player_anim_x, player_anim_y - - is_moving = False - current_move = None - current_destination = None - player_anim_x = None - player_anim_y = None - - grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 - - if move_queue: - next_move = move_queue.pop(0) - process_move(next_move) - -motion_speed = 0.20 # Slightly faster for dungeon exploration - -def can_move_to(x, y): - """Check if a position is valid for movement""" - # Boundary check - if x < 0 or x >= grid_width or y < 0 or y >= grid_height: - return False - - # Walkability check - point = grid.at(x, y) - if point and point.walkable: - return True - return False - -def process_move(key): - """Process a move based on the key""" - global is_moving, current_move, current_destination, move_queue - global player_anim_x, player_anim_y, grid_anim_x, grid_anim_y - - if is_moving: - move_queue.clear() - move_queue.append(key) - return - - px, py = int(player.x), int(player.y) - new_x, new_y = px, py - - if key == "W" or key == "Up": - new_y -= 1 - elif key == "S" or key == "Down": - new_y += 1 - elif key == "A" or key == "Left": - new_x -= 1 - elif key == "D" or key == "Right": - new_x += 1 - - # Check if we can move to the new position - if new_x != px or new_y != py: - if can_move_to(new_x, new_y): - is_moving = True - current_move = key - current_destination = (new_x, new_y) - - if new_x != px: - player_anim_x = mcrfpy.Animation("x", float(new_x), motion_speed, "easeInOutQuad", callback=movement_complete) - player_anim_x.start(player) - elif new_y != py: - player_anim_y = mcrfpy.Animation("y", float(new_y), motion_speed, "easeInOutQuad", callback=movement_complete) - player_anim_y.start(player) - - grid_anim_x = mcrfpy.Animation("center_x", (new_x + 0.5) * 16, motion_speed, "linear") - grid_anim_y = mcrfpy.Animation("center_y", (new_y + 0.5) * 16, motion_speed, "linear") - grid_anim_x.start(grid) - grid_anim_y.start(grid) - else: - # Play a "bump" sound or visual feedback here - print(f"Can't move to ({new_x}, {new_y}) - blocked!") - -def handle_keys(key, state): - """Handle keyboard input to move the player""" - if state == "start": - if key in ["W", "Up", "S", "Down", "A", "Left", "D", "Right"]: - process_move(key) - -# Register the keyboard handler -mcrfpy.keypressScene(handle_keys) - -# Add UI elements -title = mcrfpy.Caption((320, 10), - text="McRogueFace Tutorial - Part 3: Dungeon Generation", -) -title.fill_color = mcrfpy.Color(255, 255, 255, 255) -mcrfpy.sceneUI("tutorial").append(title) - -instructions = mcrfpy.Caption((150, 750), - text=f"Procedural dungeon with {len(rooms)} rooms connected by hallways!", -) -instructions.font_size = 18 -instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) -mcrfpy.sceneUI("tutorial").append(instructions) - -# Debug info -debug_caption = mcrfpy.Caption((10, 40), - text=f"Grid: {grid_width}x{grid_height} | Player spawned at ({spawn_x}, {spawn_y})", -) -debug_caption.font_size = 16 -debug_caption.fill_color = mcrfpy.Color(255, 255, 0, 255) -mcrfpy.sceneUI("tutorial").append(debug_caption) - -print("Tutorial Part 3 loaded!") -print(f"Generated dungeon with {len(rooms)} rooms") -print(f"Player spawned at ({spawn_x}, {spawn_y})") -print("Walls now block movement!") -print("Use WASD or Arrow keys to explore the dungeon!") diff --git a/roguelike_tutorial/part_4.py b/roguelike_tutorial/part_4.py deleted file mode 100644 index 423cca8..0000000 --- a/roguelike_tutorial/part_4.py +++ /dev/null @@ -1,366 +0,0 @@ -""" -McRogueFace Tutorial - Part 4: Field of View - -This tutorial builds on Part 3 by adding: -- Field of view calculation using grid.compute_fov() -- Entity perspective rendering with grid.perspective -- Three visibility states: unexplored (black), explored (dark), visible (lit) -- Memory of previously seen areas -- Enemy entity to demonstrate perspective switching - -Key code references: -- tests/unit/test_tcod_fov_entities.py (lines 89-118) - FOV with multiple entities -- ROADMAP.md (lines 216-229) - FOV system implementation details -""" -import mcrfpy -import random - -# Create and activate a new scene -mcrfpy.createScene("tutorial") -mcrfpy.setScene("tutorial") - -# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) -texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) - -# Load the hero sprite texture -hero_texture = mcrfpy.Texture("assets/custom_player.png", 16, 16) - -# Create a grid of tiles -grid_width, grid_height = 40, 30 - -# Calculate the size in pixels -zoom = 2.0 -grid_size = grid_width * zoom * 16, grid_height * zoom * 16 - -# Calculate the position to center the grid on the screen -grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 - -# Create the grid with a TCODMap for pathfinding/FOV -grid = mcrfpy.Grid( - pos=grid_position, - grid_size=(grid_width, grid_height), - texture=texture, - size=grid_size, -) - -grid.zoom = zoom - -# Define tile types -FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] -WALL_TILES = [3, 7, 11] - -# Room class for BSP -class Room: - def __init__(self, x, y, w, h): - self.x1 = x - self.y1 = y - self.x2 = x + w - self.y2 = y + h - self.w = w - self.h = h - - def center(self): - center_x = (self.x1 + self.x2) // 2 - center_y = (self.y1 + self.y2) // 2 - return (center_x, center_y) - - def intersects(self, other): - return (self.x1 <= other.x2 and self.x2 >= other.x1 and - self.y1 <= other.y2 and self.y2 >= other.y1) - -# Dungeon generation functions (from Part 3) -def carve_room(room): - for x in range(room.x1, room.x2): - for y in range(room.y1, room.y2): - if 0 <= x < grid_width and 0 <= y < grid_height: - point = grid.at(x, y) - if point: - point.tilesprite = random.choice(FLOOR_TILES) - point.walkable = True - point.transparent = True - -def carve_hallway(x1, y1, x2, y2): - #points = mcrfpy.libtcod.line(x1, y1, x2, y2) - points = [] - if random.choice([True, False]): - # x1,y1 -> x2,y1 -> x2,y2 - points.extend(mcrfpy.libtcod.line(x1, y1, x2, y1)) - points.extend(mcrfpy.libtcod.line(x2, y1, x2, y2)) - else: - # x1,y1 -> x1,y2 -> x2,y2 - points.extend(mcrfpy.libtcod.line(x1, y1, x1, y2)) - points.extend(mcrfpy.libtcod.line(x1, y2, x2, y2)) - - for x, y in points: - if 0 <= x < grid_width and 0 <= y < grid_height: - point = grid.at(x, y) - if point: - point.tilesprite = random.choice(FLOOR_TILES) - point.walkable = True - point.transparent = True - -def generate_dungeon(max_rooms=10, room_min_size=4, room_max_size=10): - rooms = [] - - # Fill with walls - for y in range(grid_height): - for x in range(grid_width): - point = grid.at(x, y) - if point: - point.tilesprite = random.choice(WALL_TILES) - point.walkable = False - point.transparent = False - - # Generate rooms - for _ in range(max_rooms): - w = random.randint(room_min_size, room_max_size) - h = random.randint(room_min_size, room_max_size) - x = random.randint(1, grid_width - w - 1) - y = random.randint(1, grid_height - h - 1) - - new_room = Room(x, y, w, h) - - failed = False - for other_room in rooms: - if new_room.intersects(other_room): - failed = True - break - - if not failed: - carve_room(new_room) - - if rooms: - prev_x, prev_y = rooms[-1].center() - new_x, new_y = new_room.center() - carve_hallway(prev_x, prev_y, new_x, new_y) - - rooms.append(new_room) - - return rooms - -# Generate the dungeon -rooms = generate_dungeon(max_rooms=8, room_min_size=4, room_max_size=8) - -# Add the grid to the scene -mcrfpy.sceneUI("tutorial").append(grid) - -# Spawn player in the first room -if rooms: - spawn_x, spawn_y = rooms[0].center() -else: - spawn_x, spawn_y = 4, 4 - -# Create a player entity -player = mcrfpy.Entity( - (spawn_x, spawn_y), - texture=hero_texture, - sprite_index=0 -) - -# Add the player entity to the grid -grid.entities.append(player) - -# Create an enemy entity in another room (to demonstrate perspective switching) -enemy = None -if len(rooms) > 1: - enemy_x, enemy_y = rooms[1].center() - enemy = mcrfpy.Entity( - (enemy_x, enemy_y), - texture=hero_texture, - sprite_index=0 # Enemy sprite - ) - grid.entities.append(enemy) - -# Set the grid perspective to the player by default -# Note: The new perspective system uses entity references directly -grid.perspective = player - -# Initial FOV computation -def update_fov(): - """Update field of view from current perspective - Referenced from test_tcod_fov_entities.py lines 89-118 - """ - if grid.perspective == player: - grid.compute_fov(int(player.x), int(player.y), radius=8, algorithm=0) - player.update_visibility() - elif enemy and grid.perspective == enemy: - grid.compute_fov(int(enemy.x), int(enemy.y), radius=6, algorithm=0) - enemy.update_visibility() - -# Perform initial FOV calculation -update_fov() - -# Center grid on current perspective -def center_on_perspective(): - if grid.perspective == player: - grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 - elif enemy and grid.perspective == enemy: - grid.center = (enemy.x + 0.5) * 16, (enemy.y + 0.5) * 16 - -center_on_perspective() - -# Movement state tracking (from Part 3) -is_moving = False -move_queue = [] -current_destination = None -current_move = None - -# Store animation references -player_anim_x = None -player_anim_y = None -grid_anim_x = None -grid_anim_y = None - -def movement_complete(anim, target): - """Called when movement animation completes""" - global is_moving, move_queue, current_destination, current_move - global player_anim_x, player_anim_y - - is_moving = False - current_move = None - current_destination = None - player_anim_x = None - player_anim_y = None - - # Update FOV after movement - update_fov() - center_on_perspective() - - if move_queue: - next_move = move_queue.pop(0) - process_move(next_move) - -motion_speed = 0.20 - -def can_move_to(x, y): - """Check if a position is valid for movement""" - if x < 0 or x >= grid_width or y < 0 or y >= grid_height: - return False - - point = grid.at(x, y) - if point and point.walkable: - return True - return False - -def process_move(key): - """Process a move based on the key""" - global is_moving, current_move, current_destination, move_queue - global player_anim_x, player_anim_y, grid_anim_x, grid_anim_y - - # Only allow player movement when in player perspective - if grid.perspective != player: - return - - if is_moving: - move_queue.clear() - move_queue.append(key) - return - - px, py = int(player.x), int(player.y) - new_x, new_y = px, py - - if key == "W" or key == "Up": - new_y -= 1 - elif key == "S" or key == "Down": - new_y += 1 - elif key == "A" or key == "Left": - new_x -= 1 - elif key == "D" or key == "Right": - new_x += 1 - - if new_x != px or new_y != py: - if can_move_to(new_x, new_y): - is_moving = True - current_move = key - current_destination = (new_x, new_y) - - if new_x != px: - player_anim_x = mcrfpy.Animation("x", float(new_x), motion_speed, "easeInOutQuad", callback=movement_complete) - player_anim_x.start(player) - elif new_y != py: - player_anim_y = mcrfpy.Animation("y", float(new_y), motion_speed, "easeInOutQuad", callback=movement_complete) - player_anim_y.start(player) - - grid_anim_x = mcrfpy.Animation("center_x", (new_x + 0.5) * 16, motion_speed, "linear") - grid_anim_y = mcrfpy.Animation("center_y", (new_y + 0.5) * 16, motion_speed, "linear") - grid_anim_x.start(grid) - grid_anim_y.start(grid) - -def handle_keys(key, state): - """Handle keyboard input""" - if state == "start": - # Movement keys - if key in ["W", "Up", "S", "Down", "A", "Left", "D", "Right"]: - process_move(key) - - # Perspective switching - elif key == "Tab": - # Switch perspective between player and enemy - if enemy: - if grid.perspective == player: - grid.perspective = enemy - print("Switched to enemy perspective") - else: - grid.perspective = player - print("Switched to player perspective") - - # Update FOV and camera for new perspective - update_fov() - center_on_perspective() - -# Register the keyboard handler -mcrfpy.keypressScene(handle_keys) - -# Add UI elements -title = mcrfpy.Caption((320, 10), - text="McRogueFace Tutorial - Part 4: Field of View", -) -title.fill_color = mcrfpy.Color(255, 255, 255, 255) -mcrfpy.sceneUI("tutorial").append(title) - -instructions = mcrfpy.Caption((150, 720), - text="Use WASD/Arrows to move. Press Tab to switch perspective!", -) -instructions.font_size = 18 -instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) -mcrfpy.sceneUI("tutorial").append(instructions) - -# FOV info -fov_caption = mcrfpy.Caption((150, 745), - text="FOV: Player (radius 8) | Enemy visible in other room", -) -fov_caption.font_size = 16 -fov_caption.fill_color = mcrfpy.Color(100, 200, 255, 255) -mcrfpy.sceneUI("tutorial").append(fov_caption) - -# Debug info -debug_caption = mcrfpy.Caption((10, 40), - text=f"Grid: {grid_width}x{grid_height} | Rooms: {len(rooms)} | Perspective: Player", -) -debug_caption.font_size = 16 -debug_caption.fill_color = mcrfpy.Color(255, 255, 0, 255) -mcrfpy.sceneUI("tutorial").append(debug_caption) - -# Update function for perspective display -def update_perspective_display(): - current_perspective = "Player" if grid.perspective == player else "Enemy" - debug_caption.text = f"Grid: {grid_width}x{grid_height} | Rooms: {len(rooms)} | Perspective: {current_perspective}" - - if grid.perspective == player: - fov_caption.text = "FOV: Player (radius 8) | Tab to switch perspective" - else: - fov_caption.text = "FOV: Enemy (radius 6) | Tab to switch perspective" - -# Timer to update display -def update_display(runtime): - update_perspective_display() - -mcrfpy.setTimer("display_update", update_display, 100) - -print("Tutorial Part 4 loaded!") -print("Field of View system active!") -print("- Unexplored areas are black") -print("- Previously seen areas are dark") -print("- Currently visible areas are lit") -print("Press Tab to switch between player and enemy perspective!") -print("Use WASD or Arrow keys to move!") diff --git a/roguelike_tutorial/part_5.py b/roguelike_tutorial/part_5.py deleted file mode 100644 index a8d544d..0000000 --- a/roguelike_tutorial/part_5.py +++ /dev/null @@ -1,363 +0,0 @@ -""" -McRogueFace Tutorial - Part 5: Interacting with other entities - -This tutorial builds on Part 4 by adding: -- Subclassing mcrfpy.Entity -- Non-blocking movement animations with destination tracking -- Bump interactions (combat, pushing) -""" -import mcrfpy -import random - -# Create and activate a new scene -mcrfpy.createScene("tutorial") -mcrfpy.setScene("tutorial") - -# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) -texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) - -# Load the hero sprite texture -hero_texture = mcrfpy.Texture("assets/custom_player.png", 16, 16) - -# Create a grid of tiles -grid_width, grid_height = 40, 30 - -# Calculate the size in pixels -zoom = 2.0 -grid_size = grid_width * zoom * 16, grid_height * zoom * 16 - -# Calculate the position to center the grid on the screen -grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 - -# Create the grid with a TCODMap for pathfinding/FOV -grid = mcrfpy.Grid( - pos=grid_position, - grid_size=(grid_width, grid_height), - texture=texture, - size=grid_size, -) - -grid.zoom = zoom - -# Define tile types -FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] -WALL_TILES = [3, 7, 11] - -# Room class for BSP -class Room: - def __init__(self, x, y, w, h): - self.x1 = x - self.y1 = y - self.x2 = x + w - self.y2 = y + h - self.w = w - self.h = h - - def center(self): - center_x = (self.x1 + self.x2) // 2 - center_y = (self.y1 + self.y2) // 2 - return (center_x, center_y) - - def intersects(self, other): - return (self.x1 <= other.x2 and self.x2 >= other.x1 and - self.y1 <= other.y2 and self.y2 >= other.y1) - -# Dungeon generation functions (from Part 3) -def carve_room(room): - for x in range(room.x1, room.x2): - for y in range(room.y1, room.y2): - if 0 <= x < grid_width and 0 <= y < grid_height: - point = grid.at(x, y) - if point: - point.tilesprite = random.choice(FLOOR_TILES) - point.walkable = True - point.transparent = True - -def carve_hallway(x1, y1, x2, y2): - #points = mcrfpy.libtcod.line(x1, y1, x2, y2) - points = [] - if random.choice([True, False]): - # x1,y1 -> x2,y1 -> x2,y2 - points.extend(mcrfpy.libtcod.line(x1, y1, x2, y1)) - points.extend(mcrfpy.libtcod.line(x2, y1, x2, y2)) - else: - # x1,y1 -> x1,y2 -> x2,y2 - points.extend(mcrfpy.libtcod.line(x1, y1, x1, y2)) - points.extend(mcrfpy.libtcod.line(x1, y2, x2, y2)) - - for x, y in points: - if 0 <= x < grid_width and 0 <= y < grid_height: - point = grid.at(x, y) - if point: - point.tilesprite = random.choice(FLOOR_TILES) - point.walkable = True - point.transparent = True - -def generate_dungeon(max_rooms=10, room_min_size=4, room_max_size=10): - rooms = [] - - # Fill with walls - for y in range(grid_height): - for x in range(grid_width): - point = grid.at(x, y) - if point: - point.tilesprite = random.choice(WALL_TILES) - point.walkable = False - point.transparent = False - - # Generate rooms - for _ in range(max_rooms): - w = random.randint(room_min_size, room_max_size) - h = random.randint(room_min_size, room_max_size) - x = random.randint(1, grid_width - w - 1) - y = random.randint(1, grid_height - h - 1) - - new_room = Room(x, y, w, h) - - failed = False - for other_room in rooms: - if new_room.intersects(other_room): - failed = True - break - - if not failed: - carve_room(new_room) - - if rooms: - prev_x, prev_y = rooms[-1].center() - new_x, new_y = new_room.center() - carve_hallway(prev_x, prev_y, new_x, new_y) - - rooms.append(new_room) - - return rooms - -# Generate the dungeon -rooms = generate_dungeon(max_rooms=8, room_min_size=4, room_max_size=8) - -# Add the grid to the scene -mcrfpy.sceneUI("tutorial").append(grid) - -# Spawn player in the first room -if rooms: - spawn_x, spawn_y = rooms[0].center() -else: - spawn_x, spawn_y = 4, 4 - -class GameEntity(mcrfpy.Entity): - """An entity whose default behavior is to prevent others from moving into its tile.""" - - def __init__(self, x, y, walkable=False, **kwargs): - super().__init__(x=x, y=y, **kwargs) - self.walkable = walkable - self.dest_x = x - self.dest_y = y - self.is_moving = False - - def get_position(self): - """Get logical position (destination if moving, otherwise current)""" - if self.is_moving: - return (self.dest_x, self.dest_y) - return (int(self.x), int(self.y)) - - def on_bump(self, other): - return self.walkable # allow other's motion to proceed if entity is walkable - - def __repr__(self): - return f"<{self.__class__.__name__} x={self.x}, y={self.y}, sprite_index={self.sprite_index}>" - -class BumpableEntity(GameEntity): - def __init__(self, x, y, **kwargs): - super().__init__(x, y, **kwargs) - - def on_bump(self, other): - print(f"Watch it, {other}! You bumped into {self}!") - return False - -# Create a player entity -player = GameEntity( - spawn_x, spawn_y, - texture=hero_texture, - sprite_index=0 -) - -# Add the player entity to the grid -grid.entities.append(player) -for r in rooms: - enemy_x, enemy_y = r.center() - enemy = BumpableEntity( - enemy_x, enemy_y, - grid=grid, - texture=hero_texture, - sprite_index=0 # Enemy sprite - ) - -# Set the grid perspective to the player by default -# Note: The new perspective system uses entity references directly -grid.perspective = player - -# Initial FOV computation -def update_fov(): - """Update field of view from current perspective - Referenced from test_tcod_fov_entities.py lines 89-118 - """ - if grid.perspective == player: - grid.compute_fov(int(player.x), int(player.y), radius=8, algorithm=0) - player.update_visibility() - elif enemy and grid.perspective == enemy: - grid.compute_fov(int(enemy.x), int(enemy.y), radius=6, algorithm=0) - enemy.update_visibility() - -# Perform initial FOV calculation -update_fov() - -# Center grid on current perspective -def center_on_perspective(): - if grid.perspective == player: - grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 - elif enemy and grid.perspective == enemy: - grid.center = (enemy.x + 0.5) * 16, (enemy.y + 0.5) * 16 - -center_on_perspective() - -# Movement state tracking (from Part 3) -#is_moving = False # make it an entity property -move_queue = [] -current_destination = None -current_move = None - -# Store animation references -player_anim_x = None -player_anim_y = None -grid_anim_x = None -grid_anim_y = None - -def movement_complete(anim, target): - """Called when movement animation completes""" - global move_queue, current_destination, current_move - global player_anim_x, player_anim_y - - player.is_moving = False - current_move = None - current_destination = None - player_anim_x = None - player_anim_y = None - - # Update FOV after movement - update_fov() - center_on_perspective() - - if move_queue: - next_move = move_queue.pop(0) - process_move(next_move) - -motion_speed = 0.20 - -def can_move_to(x, y): - """Check if a position is valid for movement""" - if x < 0 or x >= grid_width or y < 0 or y >= grid_height: - return False - - point = grid.at(x, y) - if point and point.walkable: - for e in grid.entities: - if not e.walkable and (x, y) == e.get_position(): # blocking the way - e.on_bump(player) - return False - return True # all checks passed, no collision - return False - -def process_move(key): - """Process a move based on the key""" - global current_move, current_destination, move_queue - global player_anim_x, player_anim_y, grid_anim_x, grid_anim_y - - # Only allow player movement when in player perspective - if grid.perspective != player: - return - - if player.is_moving: - move_queue.clear() - move_queue.append(key) - return - - px, py = int(player.x), int(player.y) - new_x, new_y = px, py - - if key == "W" or key == "Up": - new_y -= 1 - elif key == "S" or key == "Down": - new_y += 1 - elif key == "A" or key == "Left": - new_x -= 1 - elif key == "D" or key == "Right": - new_x += 1 - - if new_x != px or new_y != py: - if can_move_to(new_x, new_y): - player.is_moving = True - current_move = key - current_destination = (new_x, new_y) - - if new_x != px: - player_anim_x = mcrfpy.Animation("x", float(new_x), motion_speed, "easeInOutQuad", callback=movement_complete) - player_anim_x.start(player) - elif new_y != py: - player_anim_y = mcrfpy.Animation("y", float(new_y), motion_speed, "easeInOutQuad", callback=movement_complete) - player_anim_y.start(player) - - grid_anim_x = mcrfpy.Animation("center_x", (new_x + 0.5) * 16, motion_speed, "linear") - grid_anim_y = mcrfpy.Animation("center_y", (new_y + 0.5) * 16, motion_speed, "linear") - grid_anim_x.start(grid) - grid_anim_y.start(grid) - -def handle_keys(key, state): - """Handle keyboard input""" - if state == "start": - # Movement keys - if key in ["W", "Up", "S", "Down", "A", "Left", "D", "Right"]: - process_move(key) - -# Register the keyboard handler -mcrfpy.keypressScene(handle_keys) - -# Add UI elements -title = mcrfpy.Caption((320, 10), - text="McRogueFace Tutorial - Part 5: Entity Collision", -) -title.fill_color = mcrfpy.Color(255, 255, 255, 255) -mcrfpy.sceneUI("tutorial").append(title) - -instructions = mcrfpy.Caption((150, 720), - text="Use WASD/Arrows to move. Try to bump into the other entity!", -) -instructions.font_size = 18 -instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) -mcrfpy.sceneUI("tutorial").append(instructions) - -# Debug info -debug_caption = mcrfpy.Caption((10, 40), - text=f"Grid: {grid_width}x{grid_height} | Rooms: {len(rooms)} | Perspective: Player", -) -debug_caption.font_size = 16 -debug_caption.fill_color = mcrfpy.Color(255, 255, 0, 255) -mcrfpy.sceneUI("tutorial").append(debug_caption) - -# Update function for perspective display -def update_perspective_display(): - current_perspective = "Player" if grid.perspective == player else "Enemy" - debug_caption.text = f"Grid: {grid_width}x{grid_height} | Rooms: {len(rooms)} | Perspective: {current_perspective}" - -# Timer to update display -def update_display(runtime): - update_perspective_display() - -mcrfpy.setTimer("display_update", update_display, 100) - -print("Tutorial Part 4 loaded!") -print("Field of View system active!") -print("- Unexplored areas are black") -print("- Previously seen areas are dark") -print("- Currently visible areas are lit") -print("Press Tab to switch between player and enemy perspective!") -print("Use WASD or Arrow keys to move!") diff --git a/roguelike_tutorial/part_6.py b/roguelike_tutorial/part_6.py deleted file mode 100644 index 57b74f3..0000000 --- a/roguelike_tutorial/part_6.py +++ /dev/null @@ -1,645 +0,0 @@ -""" -McRogueFace Tutorial - Part 6: Turn-based enemy movement - -This tutorial builds on Part 5 by adding: -- Turn cycles where enemies move after the player -- Enemy AI that pursues or wanders -- Shared collision detection for all entities -""" -import mcrfpy -import random - -# Create and activate a new scene -mcrfpy.createScene("tutorial") -mcrfpy.setScene("tutorial") - -# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) -texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) - -# Load the hero sprite texture -hero_texture = mcrfpy.Texture("assets/custom_player.png", 16, 16) - -# Create a grid of tiles -grid_width, grid_height = 40, 30 - -# Calculate the size in pixels -zoom = 2.0 -grid_size = grid_width * zoom * 16, grid_height * zoom * 16 - -# Calculate the position to center the grid on the screen -grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 - -# Create the grid with a TCODMap for pathfinding/FOV -grid = mcrfpy.Grid( - pos=grid_position, - grid_size=(grid_width, grid_height), - texture=texture, - size=grid_size, -) - -grid.zoom = zoom - -# Define tile types -FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] -WALL_TILES = [3, 7, 11] - -# Room class for BSP -class Room: - def __init__(self, x, y, w, h): - self.x1 = x - self.y1 = y - self.x2 = x + w - self.y2 = y + h - self.w = w - self.h = h - - def center(self): - center_x = (self.x1 + self.x2) // 2 - center_y = (self.y1 + self.y2) // 2 - return (center_x, center_y) - - def intersects(self, other): - return (self.x1 <= other.x2 and self.x2 >= other.x1 and - self.y1 <= other.y2 and self.y2 >= other.y1) - -# Dungeon generation functions (from Part 3) -def carve_room(room): - for x in range(room.x1, room.x2): - for y in range(room.y1, room.y2): - if 0 <= x < grid_width and 0 <= y < grid_height: - point = grid.at(x, y) - if point: - point.tilesprite = random.choice(FLOOR_TILES) - point.walkable = True - point.transparent = True - -def carve_hallway(x1, y1, x2, y2): - #points = mcrfpy.libtcod.line(x1, y1, x2, y2) - points = [] - if random.choice([True, False]): - # x1,y1 -> x2,y1 -> x2,y2 - points.extend(mcrfpy.libtcod.line(x1, y1, x2, y1)) - points.extend(mcrfpy.libtcod.line(x2, y1, x2, y2)) - else: - # x1,y1 -> x1,y2 -> x2,y2 - points.extend(mcrfpy.libtcod.line(x1, y1, x1, y2)) - points.extend(mcrfpy.libtcod.line(x1, y2, x2, y2)) - - for x, y in points: - if 0 <= x < grid_width and 0 <= y < grid_height: - point = grid.at(x, y) - if point: - point.tilesprite = random.choice(FLOOR_TILES) - point.walkable = True - point.transparent = True - -def generate_dungeon(max_rooms=10, room_min_size=4, room_max_size=10): - rooms = [] - - # Fill with walls - for y in range(grid_height): - for x in range(grid_width): - point = grid.at(x, y) - if point: - point.tilesprite = random.choice(WALL_TILES) - point.walkable = False - point.transparent = False - - # Generate rooms - for _ in range(max_rooms): - w = random.randint(room_min_size, room_max_size) - h = random.randint(room_min_size, room_max_size) - x = random.randint(1, grid_width - w - 1) - y = random.randint(1, grid_height - h - 1) - - new_room = Room(x, y, w, h) - - failed = False - for other_room in rooms: - if new_room.intersects(other_room): - failed = True - break - - if not failed: - carve_room(new_room) - - if rooms: - prev_x, prev_y = rooms[-1].center() - new_x, new_y = new_room.center() - carve_hallway(prev_x, prev_y, new_x, new_y) - - rooms.append(new_room) - - return rooms - -# Generate the dungeon -rooms = generate_dungeon(max_rooms=8, room_min_size=4, room_max_size=8) - -# Add the grid to the scene -mcrfpy.sceneUI("tutorial").append(grid) - -# Spawn player in the first room -if rooms: - spawn_x, spawn_y = rooms[0].center() -else: - spawn_x, spawn_y = 4, 4 - -class GameEntity(mcrfpy.Entity): - """An entity whose default behavior is to prevent others from moving into its tile.""" - - def __init__(self, x, y, walkable=False, **kwargs): - super().__init__(x=x, y=y, **kwargs) - self.walkable = walkable - self.dest_x = x - self.dest_y = y - self.is_moving = False - - def get_position(self): - """Get logical position (destination if moving, otherwise current)""" - if self.is_moving: - return (self.dest_x, self.dest_y) - return (int(self.x), int(self.y)) - - def on_bump(self, other): - return self.walkable # allow other's motion to proceed if entity is walkable - - def __repr__(self): - return f"<{self.__class__.__name__} x={self.x}, y={self.y}, sprite_index={self.sprite_index}>" - -class CombatEntity(GameEntity): - def __init__(self, x, y, hp=10, damage=(1,3), **kwargs): - super().__init__(x=x, y=y, **kwargs) - self.hp = hp - self.damage = damage - - def is_dead(self): - return self.hp <= 0 - - def start_move(self, new_x, new_y, duration=0.2, callback=None): - """Start animating movement to new position""" - self.dest_x = new_x - self.dest_y = new_y - self.is_moving = True - - # Define completion callback that resets is_moving - def movement_done(anim, entity): - self.is_moving = False - if callback: - callback(anim, entity) - - # Create animations for smooth movement - anim_x = mcrfpy.Animation("x", float(new_x), duration, "easeInOutQuad", callback=movement_done) - anim_y = mcrfpy.Animation("y", float(new_y), duration, "easeInOutQuad") - - anim_x.start(self) - anim_y.start(self) - - def can_see(self, target_x, target_y): - """Check if this entity can see the target position""" - mx, my = self.get_position() - - # Simple distance check first - dist = abs(target_x - mx) + abs(target_y - my) - if dist > 6: - return False - - # Line of sight check - line = list(mcrfpy.libtcod.line(mx, my, target_x, target_y)) - for x, y in line[1:-1]: # Skip start and end - cell = grid.at(x, y) - if cell and not cell.transparent: - return False - return True - - def ai_turn(self, player_pos): - """Decide next move""" - mx, my = self.get_position() - px, py = player_pos - - # Simple AI: move toward player if visible - if self.can_see(px, py): - # Calculate direction toward player - dx = 0 - dy = 0 - if px > mx: - dx = 1 - elif px < mx: - dx = -1 - if py > my: - dy = 1 - elif py < my: - dy = -1 - - # Prefer cardinal movement - if dx != 0 and dy != 0: - # Pick horizontal or vertical based on greater distance - if abs(px - mx) > abs(py - my): - dy = 0 - else: - dx = 0 - - return (mx + dx, my + dy) - else: - # Random wander - dx, dy = random.choice([(0,1), (0,-1), (1,0), (-1,0)]) - return (mx + dx, my + dy) - - def ai_turn_dijkstra(self): - """Decide next move using precomputed Dijkstra map""" - mx, my = self.get_position() - - # Get current distance to player - current_dist = grid.get_dijkstra_distance(mx, my) - if current_dist is None or current_dist > 20: - # Too far or unreachable - random wander - dx, dy = random.choice([(0,1), (0,-1), (1,0), (-1,0)]) - return (mx + dx, my + dy) - - # Check all adjacent cells for best move - best_moves = [] - for dx, dy in [(0,1), (0,-1), (1,0), (-1,0)]: - nx, ny = mx + dx, my + dy - - # Skip if out of bounds - if nx < 0 or nx >= grid_width or ny < 0 or ny >= grid_height: - continue - - # Skip if not walkable - cell = grid.at(nx, ny) - if not cell or not cell.walkable: - continue - - # Get distance from this cell - dist = grid.get_dijkstra_distance(nx, ny) - if dist is not None: - best_moves.append((dist, nx, ny)) - - if best_moves: - # Sort by distance - best_moves.sort() - - # If multiple moves have the same best distance, pick randomly - best_dist = best_moves[0][0] - equal_moves = [(nx, ny) for dist, nx, ny in best_moves if dist == best_dist] - - if len(equal_moves) > 1: - # Random choice among equally good moves - nx, ny = random.choice(equal_moves) - else: - _, nx, ny = best_moves[0] - - return (nx, ny) - else: - # No valid moves - return (mx, my) - -# Create a player entity -player = CombatEntity( - spawn_x, spawn_y, - texture=hero_texture, - sprite_index=0 -) - -# Add the player entity to the grid -grid.entities.append(player) - -# Track all enemies -enemies = [] - -# Spawn enemies in other rooms -for i, room in enumerate(rooms[1:], 1): # Skip first room (player spawn) - if i <= 3: # Limit to 3 enemies for now - enemy_x, enemy_y = room.center() - enemy = CombatEntity( - enemy_x, enemy_y, - texture=hero_texture, - sprite_index=0 # Enemy sprite (borrow player's) - ) - grid.entities.append(enemy) - enemies.append(enemy) - -# Set the grid perspective to the player by default -# Note: The new perspective system uses entity references directly -grid.perspective = player - -# Initial FOV computation -def update_fov(): - """Update field of view from current perspective""" - if grid.perspective == player: - grid.compute_fov(int(player.x), int(player.y), radius=8, algorithm=0) - player.update_visibility() - -# Perform initial FOV calculation -update_fov() - -# Center grid on current perspective -def center_on_perspective(): - if grid.perspective == player: - grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 - -center_on_perspective() - -# Movement state tracking (from Part 3) -#is_moving = False # make it an entity property -move_queue = [] -current_destination = None -current_move = None - -# Store animation references -player_anim_x = None -player_anim_y = None -grid_anim_x = None -grid_anim_y = None - -def movement_complete(anim, target): - """Called when movement animation completes""" - global move_queue, current_destination, current_move - global player_anim_x, player_anim_y, is_player_turn - - player.is_moving = False - current_move = None - current_destination = None - player_anim_x = None - player_anim_y = None - - # Update FOV after movement - update_fov() - center_on_perspective() - - # Player turn complete, start enemy turns and queued player move simultaneously - is_player_turn = False - process_enemy_turns_and_player_queue() - -motion_speed = 0.20 -is_player_turn = True # Track whose turn it is - -def get_blocking_entity_at(x, y): - """Get blocking entity at position""" - for e in grid.entities: - if not e.walkable and (x, y) == e.get_position(): - return e - return None - -def can_move_to(x, y, mover=None): - """Check if a position is valid for movement""" - if x < 0 or x >= grid_width or y < 0 or y >= grid_height: - return False - - point = grid.at(x, y) - if not point or not point.walkable: - return False - - # Check for blocking entities - blocker = get_blocking_entity_at(x, y) - if blocker and blocker != mover: - return False - - return True - -def process_enemy_turns_and_player_queue(): - """Process all enemy AI decisions and player's queued move simultaneously""" - global is_player_turn, move_queue - - # Compute Dijkstra map once for all enemies (if using Dijkstra) - if USE_DIJKSTRA: - px, py = player.get_position() - grid.compute_dijkstra(px, py, diagonal_cost=1.41) - - enemies_to_move = [] - claimed_positions = set() # Track where enemies plan to move - - # Collect all enemy moves - for i, enemy in enumerate(enemies): - if enemy.is_dead(): - continue - - # AI decides next move - if USE_DIJKSTRA: - target_x, target_y = enemy.ai_turn_dijkstra() - else: - target_x, target_y = enemy.ai_turn(player.get_position()) - - # Check if move is valid and not claimed by another enemy - if can_move_to(target_x, target_y, enemy) and (target_x, target_y) not in claimed_positions: - enemies_to_move.append((enemy, target_x, target_y)) - claimed_positions.add((target_x, target_y)) - - # Start all enemy animations simultaneously - any_enemy_moved = False - if enemies_to_move: - for enemy, tx, ty in enemies_to_move: - enemy.start_move(tx, ty, duration=motion_speed) - any_enemy_moved = True - - # Process player's queued move at the same time - if move_queue: - next_move = move_queue.pop(0) - process_player_queued_move(next_move) - else: - # No queued move, set up callback to return control when animations finish - if any_enemy_moved: - # Wait for animations to complete - mcrfpy.setTimer("turn_complete", check_turn_complete, int(motion_speed * 1000) + 50) - else: - # No animations, return control immediately - is_player_turn = True - -def process_player_queued_move(key): - """Process player's queued move during enemy turn""" - global current_move, current_destination - global player_anim_x, player_anim_y, grid_anim_x, grid_anim_y - - px, py = int(player.x), int(player.y) - new_x, new_y = px, py - - if key == "W" or key == "Up": - new_y -= 1 - elif key == "S" or key == "Down": - new_y += 1 - elif key == "A" or key == "Left": - new_x -= 1 - elif key == "D" or key == "Right": - new_x += 1 - - if new_x != px or new_y != py: - # Check destination at animation end time (considering enemy destinations) - future_blocker = get_future_blocking_entity_at(new_x, new_y) - - if future_blocker: - # Will bump at destination - # Schedule bump for when animations complete - mcrfpy.setTimer("delayed_bump", lambda t: handle_delayed_bump(future_blocker), int(motion_speed * 1000)) - elif can_move_to(new_x, new_y, player): - # Valid move, start animation - player.is_moving = True - current_move = key - current_destination = (new_x, new_y) - player.dest_x = new_x - player.dest_y = new_y - - # Player animation with callback - player_anim_x = mcrfpy.Animation("x", float(new_x), motion_speed, "easeInOutQuad", callback=player_queued_move_complete) - player_anim_x.start(player) - player_anim_y = mcrfpy.Animation("y", float(new_y), motion_speed, "easeInOutQuad") - player_anim_y.start(player) - - # Move camera with player - grid_anim_x = mcrfpy.Animation("center_x", (new_x + 0.5) * 16, motion_speed, "linear") - grid_anim_y = mcrfpy.Animation("center_y", (new_y + 0.5) * 16, motion_speed, "linear") - grid_anim_x.start(grid) - grid_anim_y.start(grid) - else: - # Blocked by wall, wait for turn to complete - mcrfpy.setTimer("turn_complete", check_turn_complete, int(motion_speed * 1000) + 50) - -def get_future_blocking_entity_at(x, y): - """Get entity that will be blocking at position after current animations""" - for e in grid.entities: - if not e.walkable and (x, y) == (e.dest_x, e.dest_y): - return e - return None - -def handle_delayed_bump(entity): - """Handle bump after animations complete""" - global is_player_turn - entity.on_bump(player) - is_player_turn = True - -def player_queued_move_complete(anim, target): - """Called when player's queued movement completes""" - global is_player_turn - player.is_moving = False - update_fov() - center_on_perspective() - is_player_turn = True - -def check_turn_complete(timer_name): - """Check if all animations are complete""" - global is_player_turn - - # Check if any entity is still moving - if player.is_moving: - mcrfpy.setTimer("turn_complete", check_turn_complete, 50) - return - - for enemy in enemies: - if enemy.is_moving: - mcrfpy.setTimer("turn_complete", check_turn_complete, 50) - return - - # All done - is_player_turn = True - -def process_move(key): - """Process a move based on the key""" - global current_move, current_destination, move_queue - global player_anim_x, player_anim_y, grid_anim_x, grid_anim_y, is_player_turn - - # Only allow player movement on player's turn - if not is_player_turn: - return - - # Only allow player movement when in player perspective - if grid.perspective != player: - return - - if player.is_moving: - move_queue.clear() - move_queue.append(key) - return - - px, py = int(player.x), int(player.y) - new_x, new_y = px, py - - if key == "W" or key == "Up": - new_y -= 1 - elif key == "S" or key == "Down": - new_y += 1 - elif key == "A" or key == "Left": - new_x -= 1 - elif key == "D" or key == "Right": - new_x += 1 - - if new_x != px or new_y != py: - # Check what's at destination - blocker = get_blocking_entity_at(new_x, new_y) - - if blocker: - # Bump interaction (combat will go here later) - blocker.on_bump(player) - # Still counts as a turn - is_player_turn = False - process_enemy_turns_and_player_queue() - elif can_move_to(new_x, new_y, player): - player.is_moving = True - current_move = key - current_destination = (new_x, new_y) - player.dest_x = new_x - player.dest_y = new_y - - # Start player move animation - player_anim_x = mcrfpy.Animation("x", float(new_x), motion_speed, "easeInOutQuad", callback=movement_complete) - player_anim_x.start(player) - player_anim_y = mcrfpy.Animation("y", float(new_y), motion_speed, "easeInOutQuad") - player_anim_y.start(player) - - # Move camera with player - grid_anim_x = mcrfpy.Animation("center_x", (new_x + 0.5) * 16, motion_speed, "linear") - grid_anim_y = mcrfpy.Animation("center_y", (new_y + 0.5) * 16, motion_speed, "linear") - grid_anim_x.start(grid) - grid_anim_y.start(grid) - -def handle_keys(key, state): - """Handle keyboard input""" - if state == "start": - # Movement keys - if key in ["W", "Up", "S", "Down", "A", "Left", "D", "Right"]: - process_move(key) - -# Register the keyboard handler -mcrfpy.keypressScene(handle_keys) - -# Add UI elements -title = mcrfpy.Caption((320, 10), - text="McRogueFace Tutorial - Part 6: Turn-based Movement", -) -title.fill_color = mcrfpy.Color(255, 255, 255, 255) -mcrfpy.sceneUI("tutorial").append(title) - -instructions = mcrfpy.Caption((150, 720), - text="Use WASD/Arrows to move. Enemies move after you!", -) -instructions.font_size = 18 -instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) -mcrfpy.sceneUI("tutorial").append(instructions) - -# Debug info -debug_caption = mcrfpy.Caption((10, 40), - text=f"Grid: {grid_width}x{grid_height} | Rooms: {len(rooms)} | Enemies: {len(enemies)}", -) -debug_caption.font_size = 16 -debug_caption.fill_color = mcrfpy.Color(255, 255, 0, 255) -mcrfpy.sceneUI("tutorial").append(debug_caption) - -# Update function for turn display -def update_turn_display(): - turn_text = "Player" if is_player_turn else "Enemy" - alive_enemies = sum(1 for e in enemies if not e.is_dead()) - debug_caption.text = f"Grid: {grid_width}x{grid_height} | Turn: {turn_text} | Enemies: {alive_enemies}/{len(enemies)}" - -# Configuration toggle -USE_DIJKSTRA = True # Set to False to use old line-of-sight AI - -# Timer to update display -def update_display(runtime): - update_turn_display() - -mcrfpy.setTimer("display_update", update_display, 100) - -print("Tutorial Part 6 loaded!") -print("Turn-based movement system active!") -print(f"Using {'Dijkstra' if USE_DIJKSTRA else 'Line-of-sight'} AI pathfinding") -print("- Enemies move after the player") -print("- Enemies pursue when they can see you" if not USE_DIJKSTRA else "- Enemies use optimal pathfinding") -print("- Enemies wander when they can't" if not USE_DIJKSTRA else "- All enemies share one pathfinding map") -print("Use WASD or Arrow keys to move!") diff --git a/roguelike_tutorial/part_6a.py b/roguelike_tutorial/part_6a.py deleted file mode 100644 index b385309..0000000 --- a/roguelike_tutorial/part_6a.py +++ /dev/null @@ -1,582 +0,0 @@ -""" -McRogueFace Tutorial - Part 6: Turn-based enemy movement - -This tutorial builds on Part 5 by adding: -- Turn cycles where enemies move after the player -- Enemy AI that pursues or wanders -- Shared collision detection for all entities -""" -import mcrfpy -import random - -# Create and activate a new scene -mcrfpy.createScene("tutorial") -mcrfpy.setScene("tutorial") - -# Load the texture (4x3 tiles, 64x48 pixels total, 16x16 per tile) -texture = mcrfpy.Texture("assets/tutorial2.png", 16, 16) - -# Load the hero sprite texture -hero_texture = mcrfpy.Texture("assets/custom_player.png", 16, 16) - -# Create a grid of tiles -grid_width, grid_height = 40, 30 - -# Calculate the size in pixels -zoom = 2.0 -grid_size = grid_width * zoom * 16, grid_height * zoom * 16 - -# Calculate the position to center the grid on the screen -grid_position = (1024 - grid_size[0]) / 2, (768 - grid_size[1]) / 2 - -# Create the grid with a TCODMap for pathfinding/FOV -grid = mcrfpy.Grid( - pos=grid_position, - grid_size=(grid_width, grid_height), - texture=texture, - size=grid_size, -) - -grid.zoom = zoom - -# Define tile types -FLOOR_TILES = [0, 1, 2, 4, 5, 6, 8, 9, 10] -WALL_TILES = [3, 7, 11] - -# Room class for BSP -class Room: - def __init__(self, x, y, w, h): - self.x1 = x - self.y1 = y - self.x2 = x + w - self.y2 = y + h - self.w = w - self.h = h - - def center(self): - center_x = (self.x1 + self.x2) // 2 - center_y = (self.y1 + self.y2) // 2 - return (center_x, center_y) - - def intersects(self, other): - return (self.x1 <= other.x2 and self.x2 >= other.x1 and - self.y1 <= other.y2 and self.y2 >= other.y1) - -# Dungeon generation functions (from Part 3) -def carve_room(room): - for x in range(room.x1, room.x2): - for y in range(room.y1, room.y2): - if 0 <= x < grid_width and 0 <= y < grid_height: - point = grid.at(x, y) - if point: - point.tilesprite = random.choice(FLOOR_TILES) - point.walkable = True - point.transparent = True - -def carve_hallway(x1, y1, x2, y2): - #points = mcrfpy.libtcod.line(x1, y1, x2, y2) - points = [] - if random.choice([True, False]): - # x1,y1 -> x2,y1 -> x2,y2 - points.extend(mcrfpy.libtcod.line(x1, y1, x2, y1)) - points.extend(mcrfpy.libtcod.line(x2, y1, x2, y2)) - else: - # x1,y1 -> x1,y2 -> x2,y2 - points.extend(mcrfpy.libtcod.line(x1, y1, x1, y2)) - points.extend(mcrfpy.libtcod.line(x1, y2, x2, y2)) - - for x, y in points: - if 0 <= x < grid_width and 0 <= y < grid_height: - point = grid.at(x, y) - if point: - point.tilesprite = random.choice(FLOOR_TILES) - point.walkable = True - point.transparent = True - -def generate_dungeon(max_rooms=10, room_min_size=4, room_max_size=10): - rooms = [] - - # Fill with walls - for y in range(grid_height): - for x in range(grid_width): - point = grid.at(x, y) - if point: - point.tilesprite = random.choice(WALL_TILES) - point.walkable = False - point.transparent = False - - # Generate rooms - for _ in range(max_rooms): - w = random.randint(room_min_size, room_max_size) - h = random.randint(room_min_size, room_max_size) - x = random.randint(1, grid_width - w - 1) - y = random.randint(1, grid_height - h - 1) - - new_room = Room(x, y, w, h) - - failed = False - for other_room in rooms: - if new_room.intersects(other_room): - failed = True - break - - if not failed: - carve_room(new_room) - - if rooms: - prev_x, prev_y = rooms[-1].center() - new_x, new_y = new_room.center() - carve_hallway(prev_x, prev_y, new_x, new_y) - - rooms.append(new_room) - - return rooms - -# Generate the dungeon -rooms = generate_dungeon(max_rooms=8, room_min_size=4, room_max_size=8) - -# Add the grid to the scene -mcrfpy.sceneUI("tutorial").append(grid) - -# Spawn player in the first room -if rooms: - spawn_x, spawn_y = rooms[0].center() -else: - spawn_x, spawn_y = 4, 4 - -class GameEntity(mcrfpy.Entity): - """An entity whose default behavior is to prevent others from moving into its tile.""" - - def __init__(self, x, y, walkable=False, **kwargs): - super().__init__(x=x, y=y, **kwargs) - self.walkable = walkable - self.dest_x = x - self.dest_y = y - self.is_moving = False - - def get_position(self): - """Get logical position (destination if moving, otherwise current)""" - if self.is_moving: - return (self.dest_x, self.dest_y) - return (int(self.x), int(self.y)) - - def on_bump(self, other): - return self.walkable # allow other's motion to proceed if entity is walkable - - def __repr__(self): - return f"<{self.__class__.__name__} x={self.x}, y={self.y}, sprite_index={self.sprite_index}>" - -class CombatEntity(GameEntity): - def __init__(self, x, y, hp=10, damage=(1,3), **kwargs): - super().__init__(x=x, y=y, **kwargs) - self.hp = hp - self.damage = damage - - def is_dead(self): - return self.hp <= 0 - - def start_move(self, new_x, new_y, duration=0.2, callback=None): - """Start animating movement to new position""" - self.dest_x = new_x - self.dest_y = new_y - self.is_moving = True - - # Define completion callback that resets is_moving - def movement_done(anim, entity): - self.is_moving = False - if callback: - callback(anim, entity) - - # Create animations for smooth movement - anim_x = mcrfpy.Animation("x", float(new_x), duration, "easeInOutQuad", callback=movement_done) - anim_y = mcrfpy.Animation("y", float(new_y), duration, "easeInOutQuad") - - anim_x.start(self) - anim_y.start(self) - - def can_see(self, target_x, target_y): - """Check if this entity can see the target position""" - mx, my = self.get_position() - - # Simple distance check first - dist = abs(target_x - mx) + abs(target_y - my) - if dist > 6: - return False - - # Line of sight check - line = list(mcrfpy.libtcod.line(mx, my, target_x, target_y)) - for x, y in line[1:-1]: # Skip start and end - cell = grid.at(x, y) - if cell and not cell.transparent: - return False - return True - - def ai_turn(self, player_pos): - """Decide next move""" - mx, my = self.get_position() - px, py = player_pos - - # Simple AI: move toward player if visible - if self.can_see(px, py): - # Calculate direction toward player - dx = 0 - dy = 0 - if px > mx: - dx = 1 - elif px < mx: - dx = -1 - if py > my: - dy = 1 - elif py < my: - dy = -1 - - # Prefer cardinal movement - if dx != 0 and dy != 0: - # Pick horizontal or vertical based on greater distance - if abs(px - mx) > abs(py - my): - dy = 0 - else: - dx = 0 - - return (mx + dx, my + dy) - else: - # Random wander - dx, dy = random.choice([(0,1), (0,-1), (1,0), (-1,0)]) - return (mx + dx, my + dy) - -# Create a player entity -player = CombatEntity( - spawn_x, spawn_y, - texture=hero_texture, - sprite_index=0 -) - -# Add the player entity to the grid -grid.entities.append(player) - -# Track all enemies -enemies = [] - -# Spawn enemies in other rooms -for i, room in enumerate(rooms[1:], 1): # Skip first room (player spawn) - if i <= 3: # Limit to 3 enemies for now - enemy_x, enemy_y = room.center() - enemy = CombatEntity( - enemy_x, enemy_y, - texture=hero_texture, - sprite_index=0 # Enemy sprite (borrow player's) - ) - grid.entities.append(enemy) - enemies.append(enemy) - -# Set the grid perspective to the player by default -# Note: The new perspective system uses entity references directly -grid.perspective = player - -# Initial FOV computation -def update_fov(): - """Update field of view from current perspective""" - if grid.perspective == player: - grid.compute_fov(int(player.x), int(player.y), radius=8, algorithm=0) - player.update_visibility() - -# Perform initial FOV calculation -update_fov() - -# Center grid on current perspective -def center_on_perspective(): - if grid.perspective == player: - grid.center = (player.x + 0.5) * 16, (player.y + 0.5) * 16 - -center_on_perspective() - -# Movement state tracking (from Part 3) -#is_moving = False # make it an entity property -move_queue = [] -current_destination = None -current_move = None - -# Store animation references -player_anim_x = None -player_anim_y = None -grid_anim_x = None -grid_anim_y = None - -def movement_complete(anim, target): - """Called when movement animation completes""" - global move_queue, current_destination, current_move - global player_anim_x, player_anim_y, is_player_turn - - player.is_moving = False - current_move = None - current_destination = None - player_anim_x = None - player_anim_y = None - - # Update FOV after movement - update_fov() - center_on_perspective() - - # Player turn complete, start enemy turns and queued player move simultaneously - is_player_turn = False - process_enemy_turns_and_player_queue() - -motion_speed = 0.20 -is_player_turn = True # Track whose turn it is - -def get_blocking_entity_at(x, y): - """Get blocking entity at position""" - for e in grid.entities: - if not e.walkable and (x, y) == e.get_position(): - return e - return None - -def can_move_to(x, y, mover=None): - """Check if a position is valid for movement""" - if x < 0 or x >= grid_width or y < 0 or y >= grid_height: - return False - - point = grid.at(x, y) - if not point or not point.walkable: - return False - - # Check for blocking entities - blocker = get_blocking_entity_at(x, y) - if blocker and blocker != mover: - return False - - return True - -def process_enemy_turns_and_player_queue(): - """Process all enemy AI decisions and player's queued move simultaneously""" - global is_player_turn, move_queue - - enemies_to_move = [] - - # Collect all enemy moves - for i, enemy in enumerate(enemies): - if enemy.is_dead(): - continue - - # AI decides next move based on player's position - target_x, target_y = enemy.ai_turn(player.get_position()) - - # Check if move is valid - if can_move_to(target_x, target_y, enemy): - enemies_to_move.append((enemy, target_x, target_y)) - - # Start all enemy animations simultaneously - any_enemy_moved = False - if enemies_to_move: - for enemy, tx, ty in enemies_to_move: - enemy.start_move(tx, ty, duration=motion_speed) - any_enemy_moved = True - - # Process player's queued move at the same time - if move_queue: - next_move = move_queue.pop(0) - process_player_queued_move(next_move) - else: - # No queued move, set up callback to return control when animations finish - if any_enemy_moved: - # Wait for animations to complete - mcrfpy.setTimer("turn_complete", check_turn_complete, int(motion_speed * 1000) + 50) - else: - # No animations, return control immediately - is_player_turn = True - -def process_player_queued_move(key): - """Process player's queued move during enemy turn""" - global current_move, current_destination - global player_anim_x, player_anim_y, grid_anim_x, grid_anim_y - - px, py = int(player.x), int(player.y) - new_x, new_y = px, py - - if key == "W" or key == "Up": - new_y -= 1 - elif key == "S" or key == "Down": - new_y += 1 - elif key == "A" or key == "Left": - new_x -= 1 - elif key == "D" or key == "Right": - new_x += 1 - - if new_x != px or new_y != py: - # Check destination at animation end time (considering enemy destinations) - future_blocker = get_future_blocking_entity_at(new_x, new_y) - - if future_blocker: - # Will bump at destination - # Schedule bump for when animations complete - mcrfpy.setTimer("delayed_bump", lambda t: handle_delayed_bump(future_blocker), int(motion_speed * 1000)) - elif can_move_to(new_x, new_y, player): - # Valid move, start animation - player.is_moving = True - current_move = key - current_destination = (new_x, new_y) - player.dest_x = new_x - player.dest_y = new_y - - # Player animation with callback - player_anim_x = mcrfpy.Animation("x", float(new_x), motion_speed, "easeInOutQuad", callback=player_queued_move_complete) - player_anim_x.start(player) - player_anim_y = mcrfpy.Animation("y", float(new_y), motion_speed, "easeInOutQuad") - player_anim_y.start(player) - - # Move camera with player - grid_anim_x = mcrfpy.Animation("center_x", (new_x + 0.5) * 16, motion_speed, "linear") - grid_anim_y = mcrfpy.Animation("center_y", (new_y + 0.5) * 16, motion_speed, "linear") - grid_anim_x.start(grid) - grid_anim_y.start(grid) - else: - # Blocked by wall, wait for turn to complete - mcrfpy.setTimer("turn_complete", check_turn_complete, int(motion_speed * 1000) + 50) - -def get_future_blocking_entity_at(x, y): - """Get entity that will be blocking at position after current animations""" - for e in grid.entities: - if not e.walkable and (x, y) == (e.dest_x, e.dest_y): - return e - return None - -def handle_delayed_bump(entity): - """Handle bump after animations complete""" - global is_player_turn - entity.on_bump(player) - is_player_turn = True - -def player_queued_move_complete(anim, target): - """Called when player's queued movement completes""" - global is_player_turn - player.is_moving = False - update_fov() - center_on_perspective() - is_player_turn = True - -def check_turn_complete(timer_name): - """Check if all animations are complete""" - global is_player_turn - - # Check if any entity is still moving - if player.is_moving: - mcrfpy.setTimer("turn_complete", check_turn_complete, 50) - return - - for enemy in enemies: - if enemy.is_moving: - mcrfpy.setTimer("turn_complete", check_turn_complete, 50) - return - - # All done - is_player_turn = True - -def process_move(key): - """Process a move based on the key""" - global current_move, current_destination, move_queue - global player_anim_x, player_anim_y, grid_anim_x, grid_anim_y, is_player_turn - - # Only allow player movement on player's turn - if not is_player_turn: - return - - # Only allow player movement when in player perspective - if grid.perspective != player: - return - - if player.is_moving: - move_queue.clear() - move_queue.append(key) - return - - px, py = int(player.x), int(player.y) - new_x, new_y = px, py - - if key == "W" or key == "Up": - new_y -= 1 - elif key == "S" or key == "Down": - new_y += 1 - elif key == "A" or key == "Left": - new_x -= 1 - elif key == "D" or key == "Right": - new_x += 1 - - if new_x != px or new_y != py: - # Check what's at destination - blocker = get_blocking_entity_at(new_x, new_y) - - if blocker: - # Bump interaction (combat will go here later) - blocker.on_bump(player) - # Still counts as a turn - is_player_turn = False - process_enemy_turns_and_player_queue() - elif can_move_to(new_x, new_y, player): - player.is_moving = True - current_move = key - current_destination = (new_x, new_y) - player.dest_x = new_x - player.dest_y = new_y - - # Start player move animation - player_anim_x = mcrfpy.Animation("x", float(new_x), motion_speed, "easeInOutQuad", callback=movement_complete) - player_anim_x.start(player) - player_anim_y = mcrfpy.Animation("y", float(new_y), motion_speed, "easeInOutQuad") - player_anim_y.start(player) - - # Move camera with player - grid_anim_x = mcrfpy.Animation("center_x", (new_x + 0.5) * 16, motion_speed, "linear") - grid_anim_y = mcrfpy.Animation("center_y", (new_y + 0.5) * 16, motion_speed, "linear") - grid_anim_x.start(grid) - grid_anim_y.start(grid) - -def handle_keys(key, state): - """Handle keyboard input""" - if state == "start": - # Movement keys - if key in ["W", "Up", "S", "Down", "A", "Left", "D", "Right"]: - process_move(key) - -# Register the keyboard handler -mcrfpy.keypressScene(handle_keys) - -# Add UI elements -title = mcrfpy.Caption((320, 10), - text="McRogueFace Tutorial - Part 6: Turn-based Movement", -) -title.fill_color = mcrfpy.Color(255, 255, 255, 255) -mcrfpy.sceneUI("tutorial").append(title) - -instructions = mcrfpy.Caption((150, 720), - text="Use WASD/Arrows to move. Enemies move after you!", -) -instructions.font_size = 18 -instructions.fill_color = mcrfpy.Color(200, 200, 200, 255) -mcrfpy.sceneUI("tutorial").append(instructions) - -# Debug info -debug_caption = mcrfpy.Caption((10, 40), - text=f"Grid: {grid_width}x{grid_height} | Rooms: {len(rooms)} | Enemies: {len(enemies)}", -) -debug_caption.font_size = 16 -debug_caption.fill_color = mcrfpy.Color(255, 255, 0, 255) -mcrfpy.sceneUI("tutorial").append(debug_caption) - -# Update function for turn display -def update_turn_display(): - turn_text = "Player" if is_player_turn else "Enemy" - alive_enemies = sum(1 for e in enemies if not e.is_dead()) - debug_caption.text = f"Grid: {grid_width}x{grid_height} | Turn: {turn_text} | Enemies: {alive_enemies}/{len(enemies)}" - -# Timer to update display -def update_display(runtime): - update_turn_display() - -mcrfpy.setTimer("display_update", update_display, 100) - -print("Tutorial Part 6 loaded!") -print("Turn-based movement system active!") -print("- Enemies move after the player") -print("- Enemies pursue when they can see you") -print("- Enemies wander when they can't") -print("Use WASD or Arrow keys to move!") diff --git a/roguelike_tutorial/tutorial2.png b/roguelike_tutorial/tutorial2.png deleted file mode 100644 index e7854196517cae002aa2d3381728d0adbc125ff8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5741 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 diff --git a/roguelike_tutorial/tutorial_hero.png b/roguelike_tutorial/tutorial_hero.png deleted file mode 100644 index c202176caf74bd28325f0e4352e28e35db03d2ec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16742 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 Date: Wed, 26 Nov 2025 04:54:13 -0500 Subject: [PATCH 4/4] fix: Refine geometry demos for 1024x768 and fix animations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix timer restart when switching between animated demo scenes - Update all demos from 800x600 to 1024x768 resolution - Add screen_angle_between() for correct arc angles in screen coords - Fix arc directions by accounting for screen Y inversion - Reposition labels to avoid text overlaps - Shift solar system center down to prevent moon orbit overflow - Reposition ship/target in pathfinding demo to avoid sun clipping - Scale menu screen to fill 1024x768 with wider buttons - Regenerate all demo screenshots 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- tests/geometry_demo/geometry_main.py | 33 ++- .../geometry_demo/screens/angle_lines_demo.py | 230 ++++++++------- tests/geometry_demo/screens/base.py | 38 ++- tests/geometry_demo/screens/bresenham_demo.py | 191 ++++++++----- .../screens/pathfinding_animated_demo.py | 42 ++- .../screens/pathfinding_static_demo.py | 264 ++++++++++-------- .../screens/solar_system_demo.py | 36 ++- .../geo_00_bresenham_algorithms.png | Bin 63717 -> 60812 bytes .../screenshots/geo_01_angle_calculations.png | Bin 92371 -> 87028 bytes .../screenshots/geo_02_static_pathfinding.png | Bin 67505 -> 62197 bytes .../geo_03_solar_system_animation.png | Bin 54605 -> 55249 bytes .../geo_04_animated_pathfinding.png | Bin 52044 -> 52268 bytes 12 files changed, 497 insertions(+), 337 deletions(-) diff --git a/tests/geometry_demo/geometry_main.py b/tests/geometry_demo/geometry_main.py index e97d073..e0aaa4f 100644 --- a/tests/geometry_demo/geometry_main.py +++ b/tests/geometry_demo/geometry_main.py @@ -69,28 +69,34 @@ class GeometryDemoRunner: mcrfpy.createScene("geo_menu") ui = mcrfpy.sceneUI("geo_menu") + # Screen dimensions + SCREEN_WIDTH = 1024 + SCREEN_HEIGHT = 768 + # Background - bg = mcrfpy.Frame(pos=(0, 0), size=(800, 600)) + bg = mcrfpy.Frame(pos=(0, 0), size=(SCREEN_WIDTH, SCREEN_HEIGHT)) bg.fill_color = mcrfpy.Color(15, 15, 25) ui.append(bg) # Title - title = mcrfpy.Caption(text="Geometry Module Demo", pos=(400, 30)) + title = mcrfpy.Caption(text="Geometry Module Demo", pos=(SCREEN_WIDTH // 2, 40)) title.fill_color = mcrfpy.Color(255, 255, 255) title.outline = 2 title.outline_color = mcrfpy.Color(0, 0, 0) ui.append(title) - subtitle = mcrfpy.Caption(text="Pinships Orbital Mechanics", pos=(400, 70)) + subtitle = mcrfpy.Caption(text="Pinships Orbital Mechanics", pos=(SCREEN_WIDTH // 2, 80)) subtitle.fill_color = mcrfpy.Color(180, 180, 180) ui.append(subtitle) - # Menu items + # Menu items - wider buttons centered on 1024 width + btn_width = 500 + btn_x = (SCREEN_WIDTH - btn_width) // 2 for i, screen in enumerate(self.screens): - y = 130 + i * 60 + y = 140 + i * 70 # Button frame - btn = mcrfpy.Frame(pos=(200, y), size=(400, 50)) + btn = mcrfpy.Frame(pos=(btn_x, y), size=(btn_width, 60)) btn.fill_color = mcrfpy.Color(30, 40, 60) btn.outline = 2 btn.outline_color = mcrfpy.Color(80, 100, 150) @@ -102,21 +108,21 @@ class GeometryDemoRunner: btn.children.append(label) # Description - desc = mcrfpy.Caption(text=screen.description, pos=(20, 32)) + desc = mcrfpy.Caption(text=screen.description, pos=(20, 35)) desc.fill_color = mcrfpy.Color(120, 120, 150) btn.children.append(desc) # Instructions - instr1 = mcrfpy.Caption(text="Press 1-5 to view demos", pos=(300, 480)) + instr1 = mcrfpy.Caption(text="Press 1-5 to view demos", pos=(SCREEN_WIDTH // 2 - 100, 540)) instr1.fill_color = mcrfpy.Color(150, 150, 150) ui.append(instr1) - instr2 = mcrfpy.Caption(text="ESC = return to menu | Q = quit", pos=(270, 510)) + instr2 = mcrfpy.Caption(text="ESC = return to menu | Q = quit", pos=(SCREEN_WIDTH // 2 - 130, 580)) instr2.fill_color = mcrfpy.Color(100, 100, 100) ui.append(instr2) # Credits - credits = mcrfpy.Caption(text="Geometry module: src/scripts/geometry.py", pos=(250, 560)) + credits = mcrfpy.Caption(text="Geometry module: src/scripts/geometry.py", pos=(SCREEN_WIDTH // 2 - 150, 700)) credits.fill_color = mcrfpy.Color(80, 80, 100) ui.append(credits) @@ -170,12 +176,13 @@ class GeometryDemoRunner: if key in [f"Num{n}" for n in "123456789"]: idx = int(key[-1]) - 1 if idx < len(self.screens): - # Clean up previous screen's timers + # Clean up ALL screen's timers first for screen in self.screens: screen.cleanup() + # Switch to selected scene mcrfpy.setScene(self.screens[idx].scene_name) - # Re-setup the screen to restart animations - # (timers were cleaned up, need to restart) + # Restart timers for the selected screen + self.screens[idx].restart_timers() # ESC returns to menu elif key == "Escape": diff --git a/tests/geometry_demo/screens/angle_lines_demo.py b/tests/geometry_demo/screens/angle_lines_demo.py index 45e0e30..69278c0 100644 --- a/tests/geometry_demo/screens/angle_lines_demo.py +++ b/tests/geometry_demo/screens/angle_lines_demo.py @@ -1,8 +1,8 @@ """Angle calculation demonstration with Line elements.""" import mcrfpy import math -from .base import (GeometryDemoScreen, angle_between, angle_difference, - normalize_angle, point_on_circle, distance) +from .base import (GeometryDemoScreen, screen_angle_between, angle_difference, + normalize_angle, distance, SCREEN_WIDTH, SCREEN_HEIGHT) class AngleLinesDemo(GeometryDemoScreen): @@ -15,34 +15,48 @@ class AngleLinesDemo(GeometryDemoScreen): self.add_title("Angle Calculations & Line Elements") self.add_description("Computing headings, deviations, and opposite angles for pathfinding") - # Demo 1: Basic angle between two points - self._demo_basic_angle() + margin = 30 + frame_gap = 20 + top_area = 80 + bottom_margin = 30 - # Demo 2: Angle between three points (deviation) - self._demo_angle_deviation() + # Calculate frame dimensions for 2x2 layout + frame_width = (SCREEN_WIDTH - 2 * margin - frame_gap) // 2 + available_height = SCREEN_HEIGHT - top_area - bottom_margin - frame_gap + frame_height = available_height // 2 - # Demo 3: Waypoint viability visualization - self._demo_waypoint_viability() + # Demo 1: Basic angle between two points (top-left) + self._demo_basic_angle(margin, top_area, frame_width, frame_height) - # Demo 4: Orbit exit heading - self._demo_orbit_exit() + # Demo 2: Angle deviation (top-right) + self._demo_angle_deviation(margin + frame_width + frame_gap, top_area, + frame_width, frame_height) - def _demo_basic_angle(self): + # Demo 3: Multiple waypoints (bottom-left) + self._demo_waypoint_viability(margin, top_area + frame_height + frame_gap, + frame_width, frame_height) + + # Demo 4: Orbit exit heading (bottom-right) + self._demo_orbit_exit(margin + frame_width + frame_gap, top_area + frame_height + frame_gap, + frame_width, frame_height) + + def _demo_basic_angle(self, fx, fy, fw, fh): """Show angle from point A to point B.""" - bg = mcrfpy.Frame(pos=(30, 80), size=(350, 200)) + bg = mcrfpy.Frame(pos=(fx, fy), size=(fw, fh)) bg.fill_color = mcrfpy.Color(15, 15, 25) bg.outline = 1 bg.outline_color = mcrfpy.Color(60, 60, 100) self.ui.append(bg) - self.add_label("Basic Angle Calculation", 50, 85, (255, 200, 100)) + self.add_label("Basic Angle Calculation", fx + 10, fy + 5, (255, 200, 100)) - # Point A (origin) - ax, ay = 100, 180 - # Point B (target) - bx, by = 300, 120 + # Point A (origin) - lower left area of frame + ax, ay = fx + 80, fy + fh - 80 + # Point B (target) - upper right area of frame + bx, by = fx + fw - 100, fy + 100 - angle = angle_between((ax, ay), (bx, by)) + # Calculate angle using screen coordinates + angle = screen_angle_between((ax, ay), (bx, by)) dist = distance((ax, ay), (bx, by)) # Draw the line A to B (green) @@ -54,17 +68,19 @@ class AngleLinesDemo(GeometryDemoScreen): self.ui.append(line_ab) # Draw reference line (east from A) in gray + ref_length = 120 line_ref = mcrfpy.Line( - start=(ax, ay), end=(ax + 150, ay), + start=(ax, ay), end=(ax + ref_length, ay), color=mcrfpy.Color(100, 100, 100), thickness=1 ) self.ui.append(line_ref) - # Draw arc showing the angle + # Draw arc showing the angle (from reference to target line) + # Arc goes from 0 degrees (east) to the calculated angle arc = mcrfpy.Arc( - center=(ax, ay), radius=40, - start_angle=0, end_angle=-angle, # Negative because screen Y is inverted + center=(ax, ay), radius=50, + start_angle=0, end_angle=angle, color=mcrfpy.Color(255, 255, 100), thickness=2 ) @@ -79,30 +95,30 @@ class AngleLinesDemo(GeometryDemoScreen): self.ui.append(point_b) # Labels - self.add_label("A", ax - 20, ay - 5, (255, 100, 100)) - self.add_label("B", bx + 10, by - 5, (100, 255, 100)) - self.add_label(f"Angle: {angle:.1f}°", 50, 250, (255, 255, 100)) - self.add_label(f"Distance: {dist:.1f}", 180, 250, (150, 150, 150)) + self.add_label("A", ax - 20, ay + 5, (255, 100, 100)) + self.add_label("B", bx + 12, by - 5, (100, 255, 100)) + self.add_label(f"Angle: {angle:.1f} deg", fx + 10, fy + fh - 45, (255, 255, 100)) + self.add_label(f"Distance: {dist:.1f}", fx + 10, fy + fh - 25, (150, 150, 150)) - def _demo_angle_deviation(self): + def _demo_angle_deviation(self, fx, fy, fw, fh): """Show angle deviation when considering a waypoint.""" - bg = mcrfpy.Frame(pos=(400, 80), size=(380, 200)) + bg = mcrfpy.Frame(pos=(fx, fy), size=(fw, fh)) bg.fill_color = mcrfpy.Color(15, 15, 25) bg.outline = 1 bg.outline_color = mcrfpy.Color(60, 60, 100) self.ui.append(bg) - self.add_label("Waypoint Deviation", 420, 85, (255, 200, 100)) - self.add_label("Is planet C a useful waypoint from A to B?", 420, 105, (150, 150, 150)) + self.add_label("Waypoint Deviation", fx + 10, fy + 5, (255, 200, 100)) + self.add_label("Is planet C useful from A to B?", fx + 10, fy + 25, (150, 150, 150)) # Ship at A, target at B, potential waypoint C - ax, ay = 450, 230 - bx, by = 720, 180 - cx, cy = 550, 150 + ax, ay = fx + 60, fy + fh - 100 + bx, by = fx + fw - 60, fy + fh - 60 + cx, cy = fx + fw // 2, fy + 100 - # Calculate angles - angle_to_target = angle_between((ax, ay), (bx, by)) - angle_to_waypoint = angle_between((ax, ay), (cx, cy)) + # Calculate angles using screen coordinates + angle_to_target = screen_angle_between((ax, ay), (bx, by)) + angle_to_waypoint = screen_angle_between((ax, ay), (cx, cy)) deviation = abs(angle_difference(angle_to_target, angle_to_waypoint)) # Draw line A to B (direct path - green) @@ -113,21 +129,28 @@ class AngleLinesDemo(GeometryDemoScreen): ) self.ui.append(line_ab) - # Draw line A to C (waypoint path - yellow if viable, red if not) + # Draw line A to C (waypoint path) viable = deviation <= 45 - waypoint_color = mcrfpy.Color(255, 255, 100) if viable else mcrfpy.Color(255, 100, 100) + waypoint_color = (255, 255, 100) if viable else (255, 100, 100) line_ac = mcrfpy.Line( start=(ax, ay), end=(cx, cy), - color=waypoint_color, + color=mcrfpy.Color(*waypoint_color), thickness=2 ) self.ui.append(line_ac) - # Draw deviation arc + # Draw arc showing the deviation angle between the two directions + # Arc should go from angle_to_target to angle_to_waypoint + start_ang = min(angle_to_target, angle_to_waypoint) + end_ang = max(angle_to_target, angle_to_waypoint) + # If the arc would be > 180, we need to go the other way + if end_ang - start_ang > 180: + start_ang, end_ang = end_ang, start_ang + 360 + arc = mcrfpy.Arc( center=(ax, ay), radius=50, - start_angle=-angle_to_target, end_angle=-angle_to_waypoint, - color=waypoint_color, + start_angle=start_ang, end_angle=end_ang, + color=mcrfpy.Color(*waypoint_color), thickness=2 ) self.ui.append(arc) @@ -145,30 +168,31 @@ class AngleLinesDemo(GeometryDemoScreen): self.ui.append(point_b) self.ui.append(point_c) - # Labels - self.add_label("A (ship)", ax - 10, ay + 10, (255, 100, 100)) - self.add_label("B (target)", bx - 20, by + 15, (100, 255, 100)) - self.add_label("C (planet)", cx + 15, cy - 5, (150, 150, 255)) - label_color = (255, 255, 100) if viable else (255, 100, 100) - self.add_label(f"Deviation: {deviation:.1f}°", 550, 250, label_color) - status = "VIABLE (<45°)" if viable else "NOT VIABLE (>45°)" - self.add_label(status, 680, 250, label_color) + # Labels - positioned to avoid overlap + self.add_label("A (ship)", ax - 15, ay + 15, (255, 100, 100)) + self.add_label("B (target)", bx - 30, by + 15, (100, 255, 100)) + self.add_label("C (planet)", cx + 15, cy - 10, (150, 150, 255)) - def _demo_waypoint_viability(self): + # Status at bottom + self.add_label(f"Deviation: {deviation:.1f} deg", fx + 10, fy + fh - 45, waypoint_color) + status = "VIABLE (<45 deg)" if viable else "NOT VIABLE (>45 deg)" + self.add_label(status, fx + 200, fy + fh - 45, waypoint_color) + + def _demo_waypoint_viability(self, fx, fy, fw, fh): """Show multiple potential waypoints with viability indicators.""" - bg = mcrfpy.Frame(pos=(30, 300), size=(350, 280)) + bg = mcrfpy.Frame(pos=(fx, fy), size=(fw, fh)) bg.fill_color = mcrfpy.Color(15, 15, 25) bg.outline = 1 bg.outline_color = mcrfpy.Color(60, 60, 100) self.ui.append(bg) - self.add_label("Multiple Waypoint Analysis", 50, 305, (255, 200, 100)) + self.add_label("Multiple Waypoint Analysis", fx + 10, fy + 5, (255, 200, 100)) - # Ship and target - ax, ay = 80, 450 - bx, by = 320, 380 + # Ship and target positions + ax, ay = fx + 60, fy + fh - 80 + bx, by = fx + fw - 80, fy + fh // 2 - angle_to_target = angle_between((ax, ay), (bx, by)) + angle_to_target = screen_angle_between((ax, ay), (bx, by)) # Draw direct path line_ab = mcrfpy.Line( @@ -178,26 +202,25 @@ class AngleLinesDemo(GeometryDemoScreen): ) self.ui.append(line_ab) - # Potential waypoints at various angles + # Potential waypoints at various positions waypoints = [ - (150, 360, "W1"), # Ahead and left - viable - (200, 500, "W2"), # Below path - marginal - (100, 540, "W3"), # Behind - not viable - (250, 340, "W4"), # Almost on path - very viable + (fx + 180, fy + 80, "W1"), # Upper area + (fx + 280, fy + fh - 60, "W2"), # Right of path + (fx + 80, fy + fh - 150, "W3"), # Left/behind + (fx + fw - 150, fy + fh // 2 - 30, "W4"), # Near target ] threshold = 45 for wx, wy, label in waypoints: - angle_to_wp = angle_between((ax, ay), (wx, wy)) + angle_to_wp = screen_angle_between((ax, ay), (wx, wy)) deviation = abs(angle_difference(angle_to_target, angle_to_wp)) viable = deviation <= threshold # Line to waypoint color_tuple = (100, 255, 100) if viable else (255, 100, 100) - color = mcrfpy.Color(*color_tuple) line = mcrfpy.Line( start=(ax, ay), end=(wx, wy), - color=color, + color=mcrfpy.Color(*color_tuple), thickness=1 ) self.ui.append(line) @@ -206,12 +229,12 @@ class AngleLinesDemo(GeometryDemoScreen): wp_circle = mcrfpy.Circle( center=(wx, wy), radius=15, fill_color=mcrfpy.Color(80, 80, 120), - outline_color=color, + outline_color=mcrfpy.Color(*color_tuple), outline=2 ) self.ui.append(wp_circle) - self.add_label(f"{label}:{deviation:.0f}°", wx + 18, wy - 8, color_tuple) + self.add_label(f"{label}:{deviation:.0f}", wx + 18, wy - 8, color_tuple) # Ship and target markers ship = mcrfpy.Circle(center=(ax, ay), radius=8, @@ -221,33 +244,38 @@ class AngleLinesDemo(GeometryDemoScreen): self.ui.append(ship) self.ui.append(target) - self.add_label("Ship", ax - 5, ay + 12, (255, 200, 100)) - self.add_label("Target", bx - 15, by + 12, (100, 255, 100)) - self.add_label(f"Threshold: {threshold}°", 50, 555, (150, 150, 150)) + self.add_label("Ship", ax - 10, ay + 12, (255, 200, 100)) + self.add_label("Target", bx - 20, by + 12, (100, 255, 100)) + self.add_label(f"Threshold: {threshold} deg", fx + 10, fy + fh - 25, (150, 150, 150)) - def _demo_orbit_exit(self): + def _demo_orbit_exit(self, fx, fy, fw, fh): """Show optimal orbit exit heading toward target.""" - bg = mcrfpy.Frame(pos=(400, 300), size=(380, 280)) + bg = mcrfpy.Frame(pos=(fx, fy), size=(fw, fh)) bg.fill_color = mcrfpy.Color(15, 15, 25) bg.outline = 1 bg.outline_color = mcrfpy.Color(60, 60, 100) self.ui.append(bg) - self.add_label("Orbit Exit Heading", 420, 305, (255, 200, 100)) - self.add_label("Ship in orbit chooses optimal exit point", 420, 325, (150, 150, 150)) + self.add_label("Orbit Exit Heading", fx + 10, fy + 5, (255, 200, 100)) + self.add_label("Ship repositions FREE in orbit", fx + 10, fy + 25, (150, 150, 150)) # Planet center and orbit - px, py = 520, 450 - orbit_radius = 60 - surface_radius = 25 + px, py = fx + fw // 3, fy + fh // 2 + orbit_radius = 70 + surface_radius = 30 # Target position - tx, ty = 720, 380 + tx, ty = fx + fw - 80, fy + 100 - # Calculate optimal exit angle - exit_angle = angle_between((px, py), (tx, ty)) + # Calculate optimal exit angle (toward target in screen coords) + exit_angle = screen_angle_between((px, py), (tx, ty)) exit_x = px + orbit_radius * math.cos(math.radians(exit_angle)) - exit_y = py - orbit_radius * math.sin(math.radians(exit_angle)) # Flip for screen coords + exit_y = py - orbit_radius * math.sin(math.radians(exit_angle)) # Negate for screen Y + + # Ship's current position on orbit (arbitrary starting position) + ship_angle = exit_angle + 120 # 120 degrees away from exit + ship_x = px + orbit_radius * math.cos(math.radians(ship_angle)) + ship_y = py - orbit_radius * math.sin(math.radians(ship_angle)) # Draw planet surface planet = mcrfpy.Circle( @@ -267,27 +295,25 @@ class AngleLinesDemo(GeometryDemoScreen): ) self.ui.append(orbit) - # Draw ship positions around orbit (current position) - ship_angle = 200 # Current position - ship_x = px + orbit_radius * math.cos(math.radians(ship_angle)) - ship_y = py - orbit_radius * math.sin(math.radians(ship_angle)) + # Draw arc showing orbital movement from ship to exit (FREE movement) + # Arc goes from ship_angle to exit_angle + start_ang = min(ship_angle, exit_angle) + end_ang = max(ship_angle, exit_angle) + orbit_arc = mcrfpy.Arc( + center=(px, py), radius=orbit_radius, + start_angle=start_ang, end_angle=end_ang, + color=mcrfpy.Color(255, 255, 100), + thickness=4 + ) + self.ui.append(orbit_arc) + # Draw ship ship = mcrfpy.Circle( center=(ship_x, ship_y), radius=8, fill_color=mcrfpy.Color(255, 200, 100) ) self.ui.append(ship) - # Draw path: ship moves along orbit (free) to exit point - # Arc from ship position to exit position - orbit_arc = mcrfpy.Arc( - center=(px, py), radius=orbit_radius, - start_angle=-ship_angle, end_angle=-exit_angle, - color=mcrfpy.Color(255, 255, 100), - thickness=3 - ) - self.ui.append(orbit_arc) - # Draw exit point exit_point = mcrfpy.Circle( center=(exit_x, exit_y), radius=6, @@ -310,10 +336,12 @@ class AngleLinesDemo(GeometryDemoScreen): ) self.ui.append(target) - # Labels + # Labels - positioned to avoid overlap self.add_label("Planet", px - 20, py + surface_radius + 5, (100, 150, 220)) - self.add_label("Ship", ship_x - 25, ship_y - 15, (255, 200, 100)) - self.add_label("Exit", exit_x + 10, exit_y - 10, (100, 255, 100)) - self.add_label("Target", tx - 15, ty + 15, (255, 100, 100)) - self.add_label(f"Exit angle: {exit_angle:.1f}°", 420, 555, (150, 150, 150)) - self.add_label("Yellow arc = free orbital movement", 550, 555, (255, 255, 100)) + self.add_label("Ship", ship_x - 30, ship_y - 15, (255, 200, 100)) + self.add_label("Exit", exit_x + 10, exit_y - 15, (100, 255, 100)) + self.add_label("Target", tx - 20, ty + 15, (255, 100, 100)) + + # Info at bottom + self.add_label(f"Exit angle: {exit_angle:.1f} deg", fx + 10, fy + fh - 45, (150, 150, 150)) + self.add_label("Yellow = FREE orbital move", fx + 200, fy + fh - 45, (255, 255, 100)) diff --git a/tests/geometry_demo/screens/base.py b/tests/geometry_demo/screens/base.py index 3420054..ae64303 100644 --- a/tests/geometry_demo/screens/base.py +++ b/tests/geometry_demo/screens/base.py @@ -2,11 +2,30 @@ import mcrfpy import sys import os +import math # Add scripts path for geometry module sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', '..', '..', 'src', 'scripts')) from geometry import * +# Screen resolution +SCREEN_WIDTH = 1024 +SCREEN_HEIGHT = 768 + + +def screen_angle_between(p1, p2): + """ + Calculate angle from p1 to p2 in screen coordinates. + In screen coords, Y increases downward, so we negate dy. + Returns angle in degrees where 0=right, 90=up, 180=left, 270=down. + """ + dx = p2[0] - p1[0] + dy = p1[1] - p2[1] # Negate because screen Y is inverted + angle = math.degrees(math.atan2(dy, dx)) + if angle < 0: + angle += 360 + return angle + class GeometryDemoScreen: """Base class for geometry demo screens.""" @@ -19,6 +38,7 @@ class GeometryDemoScreen: mcrfpy.createScene(scene_name) self.ui = mcrfpy.sceneUI(scene_name) self.timers = [] # Track timer names for cleanup + self._timer_configs = [] # Store timer configs for restart def setup(self): """Override to set up the screen content.""" @@ -32,13 +52,21 @@ class GeometryDemoScreen: except: pass + def restart_timers(self): + """Re-register timers after cleanup.""" + for name, callback, interval in self._timer_configs: + try: + mcrfpy.setTimer(name, callback, interval) + except Exception as e: + print(f"Timer restart failed: {e}") + def get_screenshot_name(self): """Return the screenshot filename for this screen.""" return f"{self.scene_name}.png" def add_title(self, text, y=10): - """Add a title caption.""" - title = mcrfpy.Caption(text=text, pos=(400, y)) + """Add a title caption centered at top.""" + title = mcrfpy.Caption(text=text, pos=(SCREEN_WIDTH // 2, y)) title.fill_color = mcrfpy.Color(255, 255, 255) title.outline = 2 title.outline_color = mcrfpy.Color(0, 0, 0) @@ -79,6 +107,10 @@ class GeometryDemoScreen: pass # Out of bounds def add_timer(self, name, callback, interval): - """Add a timer and track it for cleanup.""" + """Add a timer and track it for cleanup/restart.""" + if callback is None: + print(f"Warning: Timer '{name}' callback is None, skipping") + return mcrfpy.setTimer(name, callback, interval) self.timers.append(name) + self._timer_configs.append((name, callback, interval)) diff --git a/tests/geometry_demo/screens/bresenham_demo.py b/tests/geometry_demo/screens/bresenham_demo.py index 02c417b..94191e1 100644 --- a/tests/geometry_demo/screens/bresenham_demo.py +++ b/tests/geometry_demo/screens/bresenham_demo.py @@ -1,6 +1,7 @@ """Bresenham circle algorithm demonstration on a grid.""" import mcrfpy -from .base import GeometryDemoScreen, bresenham_circle, bresenham_line, filled_circle +import math +from .base import GeometryDemoScreen, bresenham_circle, bresenham_line, filled_circle, SCREEN_WIDTH, SCREEN_HEIGHT class BresenhamDemo(GeometryDemoScreen): @@ -13,48 +14,71 @@ class BresenhamDemo(GeometryDemoScreen): self.add_title("Bresenham Circle & Line Algorithms") self.add_description("Grid-aligned geometric primitives for orbit rings and LOS calculations") - # Create a grid for circle demo - grid_w, grid_h = 25, 18 cell_size = 16 + margin = 30 + frame_gap = 20 - # We need a texture for the grid - create a simple one - # Actually, let's use Grid's built-in cell coloring via GridPoint + # Calculate frame dimensions for 2x2 layout + # Available width: 1024 - 2*margin = 964, split into 2 with gap + frame_width = (SCREEN_WIDTH - 2 * margin - frame_gap) // 2 # ~472 each + # Available height for frames: 768 - 80 (top) - 30 (bottom margin) + top_area = 80 + bottom_margin = 30 + available_height = SCREEN_HEIGHT - top_area - bottom_margin - frame_gap + frame_height = available_height // 2 # ~314 each - # Create display area with Frame background - bg1 = mcrfpy.Frame(pos=(30, 80), size=(420, 310)) - bg1.fill_color = mcrfpy.Color(15, 15, 25) - bg1.outline = 1 - bg1.outline_color = mcrfpy.Color(60, 60, 100) - self.ui.append(bg1) + # Top-left: Bresenham Circle + self._draw_circle_demo(margin, top_area, frame_width, frame_height, cell_size) - self.add_label("Bresenham Circle (radius=8)", 50, 85, (255, 200, 100)) - self.add_label("Center: (12, 9)", 50, 105, (150, 150, 150)) + # Top-right: Bresenham Lines + self._draw_lines_demo(margin + frame_width + frame_gap, top_area, frame_width, frame_height, cell_size) + + # Bottom-left: Filled Circle + self._draw_filled_demo(margin, top_area + frame_height + frame_gap, frame_width, frame_height, cell_size) + + # Bottom-right: Planet + Orbit Ring + self._draw_combined_demo(margin + frame_width + frame_gap, top_area + frame_height + frame_gap, + frame_width, frame_height, cell_size) + + def _draw_circle_demo(self, x, y, w, h, cell_size): + """Draw Bresenham circle demonstration.""" + bg = mcrfpy.Frame(pos=(x, y), size=(w, h)) + bg.fill_color = mcrfpy.Color(15, 15, 25) + bg.outline = 1 + bg.outline_color = mcrfpy.Color(60, 60, 100) + self.ui.append(bg) + + self.add_label("Bresenham Circle (radius=8)", x + 10, y + 5, (255, 200, 100)) + self.add_label("Center: (12, 9)", x + 10, y + 25, (150, 150, 150)) + + # Grid origin for this demo + grid_x = x + 20 + grid_y = y + 50 - # Draw circle using UICircle primitives to show the cells center = (12, 9) radius = 8 circle_cells = bresenham_circle(center, radius) - # Draw each cell as a small rectangle - for x, y in circle_cells: - px = 40 + x * cell_size - py = 120 + y * cell_size + # Draw each cell + for cx, cy in circle_cells: + px = grid_x + cx * cell_size + py = grid_y + cy * cell_size cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1)) cell_rect.fill_color = mcrfpy.Color(100, 200, 255) cell_rect.outline = 0 self.ui.append(cell_rect) # Draw center point - cx_px = 40 + center[0] * cell_size - cy_px = 120 + center[1] * cell_size + cx_px = grid_x + center[0] * cell_size + cy_px = grid_y + center[1] * cell_size center_rect = mcrfpy.Frame(pos=(cx_px, cy_px), size=(cell_size - 1, cell_size - 1)) center_rect.fill_color = mcrfpy.Color(255, 100, 100) self.ui.append(center_rect) - # Draw the actual circle outline for comparison + # Draw actual circle outline for comparison (centered on cells) actual_circle = mcrfpy.Circle( - center=(40 + center[0] * cell_size + cell_size // 2, - 120 + center[1] * cell_size + cell_size // 2), + center=(grid_x + center[0] * cell_size + cell_size // 2, + grid_y + center[1] * cell_size + cell_size // 2), radius=radius * cell_size, fill_color=mcrfpy.Color(0, 0, 0, 0), outline_color=mcrfpy.Color(255, 255, 100, 128), @@ -62,14 +86,18 @@ class BresenhamDemo(GeometryDemoScreen): ) self.ui.append(actual_circle) - # Second demo: Bresenham line - bg2 = mcrfpy.Frame(pos=(470, 80), size=(310, 310)) - bg2.fill_color = mcrfpy.Color(15, 15, 25) - bg2.outline = 1 - bg2.outline_color = mcrfpy.Color(60, 60, 100) - self.ui.append(bg2) + def _draw_lines_demo(self, x, y, w, h, cell_size): + """Draw Bresenham lines demonstration.""" + bg = mcrfpy.Frame(pos=(x, y), size=(w, h)) + bg.fill_color = mcrfpy.Color(15, 15, 25) + bg.outline = 1 + bg.outline_color = mcrfpy.Color(60, 60, 100) + self.ui.append(bg) - self.add_label("Bresenham Lines", 490, 85, (255, 200, 100)) + self.add_label("Bresenham Lines", x + 10, y + 5, (255, 200, 100)) + + grid_x = x + 20 + grid_y = y + 40 # Draw multiple lines at different angles lines_data = [ @@ -80,91 +108,100 @@ class BresenhamDemo(GeometryDemoScreen): for start, end, color in lines_data: line_cells = bresenham_line(start, end) - for x, y in line_cells: - px = 480 + x * cell_size - py = 110 + y * cell_size + for cx, cy in line_cells: + px = grid_x + cx * cell_size + py = grid_y + cy * cell_size cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1)) cell_rect.fill_color = mcrfpy.Color(*color) self.ui.append(cell_rect) - # Draw the actual line for comparison + # Draw the actual line for comparison (through cell centers) line = mcrfpy.Line( - start=(480 + start[0] * cell_size + cell_size // 2, - 110 + start[1] * cell_size + cell_size // 2), - end=(480 + end[0] * cell_size + cell_size // 2, - 110 + end[1] * cell_size + cell_size // 2), + start=(grid_x + start[0] * cell_size + cell_size // 2, + grid_y + start[1] * cell_size + cell_size // 2), + end=(grid_x + end[0] * cell_size + cell_size // 2, + grid_y + end[1] * cell_size + cell_size // 2), color=mcrfpy.Color(255, 255, 255, 128), thickness=1 ) self.ui.append(line) - # Third demo: Filled circle (planet surface) - bg3 = mcrfpy.Frame(pos=(30, 410), size=(200, 170)) - bg3.fill_color = mcrfpy.Color(15, 15, 25) - bg3.outline = 1 - bg3.outline_color = mcrfpy.Color(60, 60, 100) - self.ui.append(bg3) + def _draw_filled_demo(self, x, y, w, h, cell_size): + """Draw filled circle demonstration.""" + bg = mcrfpy.Frame(pos=(x, y), size=(w, h)) + bg.fill_color = mcrfpy.Color(15, 15, 25) + bg.outline = 1 + bg.outline_color = mcrfpy.Color(60, 60, 100) + self.ui.append(bg) - self.add_label("Filled Circle (radius=4)", 50, 415, (255, 200, 100)) - self.add_label("Planet surface representation", 50, 435, (150, 150, 150)) + self.add_label("Filled Circle (radius=5)", x + 10, y + 5, (255, 200, 100)) + self.add_label("Planet surface representation", x + 10, y + 25, (150, 150, 150)) - fill_center = (6, 5) - fill_radius = 4 + grid_x = x + 50 + grid_y = y + 60 + + fill_center = (8, 8) + fill_radius = 5 filled_cells = filled_circle(fill_center, fill_radius) - for x, y in filled_cells: - px = 40 + x * cell_size - py = 460 + y * cell_size - cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1)) + for cx, cy in filled_cells: + px = grid_x + cx * cell_size + py = grid_y + cy * cell_size # Gradient based on distance from center - dist = ((x - fill_center[0])**2 + (y - fill_center[1])**2) ** 0.5 + dist = math.sqrt((cx - fill_center[0])**2 + (cy - fill_center[1])**2) intensity = int(255 * (1 - dist / (fill_radius + 1))) + cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1)) cell_rect.fill_color = mcrfpy.Color(intensity, intensity // 2, 50) self.ui.append(cell_rect) - # Fourth demo: Combined - planet with orbit ring - bg4 = mcrfpy.Frame(pos=(250, 410), size=(530, 170)) - bg4.fill_color = mcrfpy.Color(15, 15, 25) - bg4.outline = 1 - bg4.outline_color = mcrfpy.Color(60, 60, 100) - self.ui.append(bg4) + def _draw_combined_demo(self, x, y, w, h, cell_size): + """Draw planet + orbit ring demonstration.""" + bg = mcrfpy.Frame(pos=(x, y), size=(w, h)) + bg.fill_color = mcrfpy.Color(15, 15, 25) + bg.outline = 1 + bg.outline_color = mcrfpy.Color(60, 60, 100) + self.ui.append(bg) - self.add_label("Planet + Orbit Ring", 270, 415, (255, 200, 100)) - self.add_label("Surface (r=3) + Orbit (r=7)", 270, 435, (150, 150, 150)) + self.add_label("Planet + Orbit Ring", x + 10, y + 5, (255, 200, 100)) + self.add_label("Surface (r=3) + Orbit (r=8)", x + 10, y + 25, (150, 150, 150)) - planet_center = (16, 5) + grid_x = x + 60 + grid_y = y + 50 + + planet_center = (12, 10) surface_radius = 3 - orbit_radius = 7 + orbit_radius = 8 # Draw orbit ring (behind planet) orbit_cells = bresenham_circle(planet_center, orbit_radius) - for x, y in orbit_cells: - px = 260 + x * cell_size - py = 460 + y * cell_size + for cx, cy in orbit_cells: + px = grid_x + cx * cell_size + py = grid_y + cy * cell_size cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1)) cell_rect.fill_color = mcrfpy.Color(50, 150, 50, 180) self.ui.append(cell_rect) # Draw planet surface (on top) surface_cells = filled_circle(planet_center, surface_radius) - for x, y in surface_cells: - px = 260 + x * cell_size - py = 460 + y * cell_size - cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1)) - dist = ((x - planet_center[0])**2 + (y - planet_center[1])**2) ** 0.5 + for cx, cy in surface_cells: + px = grid_x + cx * cell_size + py = grid_y + cy * cell_size + dist = math.sqrt((cx - planet_center[0])**2 + (cy - planet_center[1])**2) intensity = int(200 * (1 - dist / (surface_radius + 1))) + cell_rect = mcrfpy.Frame(pos=(px, py), size=(cell_size - 1, cell_size - 1)) cell_rect.fill_color = mcrfpy.Color(50 + intensity, 100 + intensity // 2, 200) self.ui.append(cell_rect) - # Legend - self.add_label("Legend:", 600, 455, (200, 200, 200)) + # Legend in bottom-left of frame + leg_x = x + 10 + leg_y = y + h - 50 - leg1 = mcrfpy.Frame(pos=(600, 475), size=(12, 12)) + leg1 = mcrfpy.Frame(pos=(leg_x, leg_y), size=(12, 12)) leg1.fill_color = mcrfpy.Color(100, 150, 200) self.ui.append(leg1) - self.add_label("Planet surface", 620, 473, (150, 150, 150)) + self.add_label("Planet", leg_x + 18, leg_y - 2, (150, 150, 150)) - leg2 = mcrfpy.Frame(pos=(600, 495), size=(12, 12)) + leg2 = mcrfpy.Frame(pos=(leg_x, leg_y + 20), size=(12, 12)) leg2.fill_color = mcrfpy.Color(50, 150, 50) self.ui.append(leg2) - self.add_label("Orbit ring (ship positions)", 620, 493, (150, 150, 150)) + self.add_label("Orbit ring", leg_x + 18, leg_y + 18, (150, 150, 150)) diff --git a/tests/geometry_demo/screens/pathfinding_animated_demo.py b/tests/geometry_demo/screens/pathfinding_animated_demo.py index 2d8c2d2..a1fc7b7 100644 --- a/tests/geometry_demo/screens/pathfinding_animated_demo.py +++ b/tests/geometry_demo/screens/pathfinding_animated_demo.py @@ -4,7 +4,8 @@ import math from .base import (GeometryDemoScreen, OrbitalBody, create_solar_system, create_planet, point_on_circle, distance, angle_between, normalize_angle, is_viable_waypoint, nearest_orbit_entry, - optimal_exit_heading) + optimal_exit_heading, screen_angle_between, + SCREEN_WIDTH, SCREEN_HEIGHT) class PathfindingAnimatedDemo(GeometryDemoScreen): @@ -17,18 +18,29 @@ class PathfindingAnimatedDemo(GeometryDemoScreen): self.add_title("Pathfinding Through Moving Planets") self.add_description("Ship anticipates planetary motion to use orbital slingshots") - # Screen layout - self.center_x = 400 - self.center_y = 320 - self.scale = 2.0 + margin = 30 + top_area = 80 + bottom_panel = 60 + + # Screen layout - full width for 1024x768 + frame_width = SCREEN_WIDTH - 2 * margin + frame_height = SCREEN_HEIGHT - top_area - bottom_panel - margin + + # Center of display area + self.center_x = margin + frame_width // 2 + self.center_y = top_area + frame_height // 2 + self.scale = 2.5 # Larger scale for better visibility # Background - bg = mcrfpy.Frame(pos=(50, 80), size=(700, 460)) + bg = mcrfpy.Frame(pos=(margin, top_area), size=(frame_width, frame_height)) bg.fill_color = mcrfpy.Color(5, 5, 15) bg.outline = 1 bg.outline_color = mcrfpy.Color(40, 40, 80) self.ui.append(bg) + # Store frame boundaries + self.frame_bottom = top_area + frame_height + # Create solar system self.star = create_solar_system( grid_width=200, grid_height=200, @@ -39,7 +51,7 @@ class PathfindingAnimatedDemo(GeometryDemoScreen): self.planet = create_planet( name="Waypoint", star=self.star, - orbital_radius=80, + orbital_radius=60, # Smaller orbit to not clip edges surface_radius=8, orbit_ring_radius=15, angular_velocity=5, # Moves 5 degrees per turn @@ -47,9 +59,10 @@ class PathfindingAnimatedDemo(GeometryDemoScreen): ) # Ship state - self.ship_speed = 10 # Grid units per turn - self.ship_pos = [30, 100] # Start position (grid coords, relative to star) - self.ship_target = [100, -80] # Target position + self.ship_speed = 8 # Grid units per turn + # Position ship further from sun to avoid line clipping through it + self.ship_pos = [-80, 60] # Start position (grid coords, relative to star) - lower left + self.ship_target = [80, -60] # Target position - upper right self.ship_state = "approach" # approach, orbiting, exiting, traveling self.ship_orbit_angle = 0 self.current_time = 0 @@ -234,24 +247,25 @@ class PathfindingAnimatedDemo(GeometryDemoScreen): def _draw_info_panel(self): """Draw information panel.""" - panel = mcrfpy.Frame(pos=(50, 545), size=(700, 45)) + panel_y = self.frame_bottom + 10 + panel = mcrfpy.Frame(pos=(30, panel_y), size=(SCREEN_WIDTH - 60, 45)) panel.fill_color = mcrfpy.Color(20, 20, 35) panel.outline = 1 panel.outline_color = mcrfpy.Color(60, 60, 100) self.ui.append(panel) # Time display - self.time_label = mcrfpy.Caption(text="Turn: 0", pos=(60, 555)) + self.time_label = mcrfpy.Caption(text="Turn: 0", pos=(40, panel_y + 12)) self.time_label.fill_color = mcrfpy.Color(255, 255, 255) self.ui.append(self.time_label) # Status display - self.status_label = mcrfpy.Caption(text="Status: Approaching planet", pos=(180, 555)) + self.status_label = mcrfpy.Caption(text="Status: Approaching planet", pos=(180, panel_y + 12)) self.status_label.fill_color = mcrfpy.Color(100, 200, 255) self.ui.append(self.status_label) # Distance display - self.dist_label = mcrfpy.Caption(text="Distance to target: ---", pos=(450, 555)) + self.dist_label = mcrfpy.Caption(text="Distance to target: ---", pos=(550, panel_y + 12)) self.dist_label.fill_color = mcrfpy.Color(150, 150, 150) self.ui.append(self.dist_label) diff --git a/tests/geometry_demo/screens/pathfinding_static_demo.py b/tests/geometry_demo/screens/pathfinding_static_demo.py index ca7c66f..2692d7a 100644 --- a/tests/geometry_demo/screens/pathfinding_static_demo.py +++ b/tests/geometry_demo/screens/pathfinding_static_demo.py @@ -1,9 +1,8 @@ """Static pathfinding demonstration with planets and orbit rings.""" import mcrfpy import math -from .base import (GeometryDemoScreen, OrbitalBody, bresenham_circle, filled_circle, - angle_between, distance, point_on_circle, is_viable_waypoint, - nearest_orbit_entry, optimal_exit_heading) +from .base import (GeometryDemoScreen, bresenham_circle, filled_circle, + screen_angle_between, distance, SCREEN_WIDTH, SCREEN_HEIGHT) class PathfindingStaticDemo(GeometryDemoScreen): @@ -16,16 +15,20 @@ class PathfindingStaticDemo(GeometryDemoScreen): self.add_title("Pathfinding Through Orbital Bodies") self.add_description("Using free orbital movement to optimize travel paths") - # Create a scenario with multiple planets - # Ship needs to go from bottom-left to top-right - # Optimal path uses planetary orbits as "free repositioning stations" + margin = 30 + top_area = 80 + legend_height = 70 + + # Main display area - use most of screen + frame_width = SCREEN_WIDTH - 2 * margin + frame_height = SCREEN_HEIGHT - top_area - margin - legend_height self.cell_size = 8 - self.offset_x = 50 - self.offset_y = 100 + self.grid_x = margin + 20 + self.grid_y = top_area + 20 # Background - bg = mcrfpy.Frame(pos=(30, 80), size=(740, 480)) + bg = mcrfpy.Frame(pos=(margin, top_area), size=(frame_width, frame_height)) bg.fill_color = mcrfpy.Color(5, 5, 15) bg.outline = 1 bg.outline_color = mcrfpy.Color(40, 40, 80) @@ -33,55 +36,60 @@ class PathfindingStaticDemo(GeometryDemoScreen): # Define planets (center_x, center_y, surface_radius, orbit_radius, name) self.planets = [ - (20, 45, 8, 14, "Alpha"), - (55, 25, 5, 10, "Beta"), - (70, 50, 6, 12, "Gamma"), + (25, 50, 6, 12, "Alpha"), + (60, 25, 4, 9, "Beta"), + (85, 55, 5, 11, "Gamma"), ] # Ship start and end - self.ship_start = (5, 55) - self.ship_end = (85, 10) + self.ship_start = (8, 65) + self.ship_end = (105, 15) - # Draw grid reference (faint) + # Draw grid reference self._draw_grid_reference() - # Draw planets with surfaces and orbit rings + # Draw planets for px, py, sr, orbit_r, name in self.planets: self._draw_planet(px, py, sr, orbit_r, name) - # Calculate and draw optimal path + # Draw optimal path self._draw_optimal_path() # Draw ship and target self._draw_ship_and_target() - # Legend - self._draw_legend() + # Legend at bottom + self._draw_legend(margin, top_area + frame_height + 10) def _to_screen(self, gx, gy): - """Convert grid coords to screen coords.""" - return (self.offset_x + gx * self.cell_size, - self.offset_y + gy * self.cell_size) + """Convert grid coords to screen coords (center of cell).""" + return (self.grid_x + gx * self.cell_size + self.cell_size // 2, + self.grid_y + gy * self.cell_size + self.cell_size // 2) + + def _to_screen_corner(self, gx, gy): + """Convert grid coords to screen coords (corner of cell).""" + return (self.grid_x + gx * self.cell_size, + self.grid_y + gy * self.cell_size) def _draw_grid_reference(self): """Draw faint grid lines for reference.""" - for i in range(0, 91, 10): - # Vertical lines - x = self.offset_x + i * self.cell_size + max_x = 115 + max_y = 75 + for i in range(0, max_x + 1, 10): + x = self.grid_x + i * self.cell_size line = mcrfpy.Line( - start=(x, self.offset_y), - end=(x, self.offset_y + 60 * self.cell_size), + start=(x, self.grid_y), + end=(x, self.grid_y + max_y * self.cell_size), color=mcrfpy.Color(30, 30, 50), thickness=1 ) self.ui.append(line) - for i in range(0, 61, 10): - # Horizontal lines - y = self.offset_y + i * self.cell_size + for i in range(0, max_y + 1, 10): + y = self.grid_y + i * self.cell_size line = mcrfpy.Line( - start=(self.offset_x, y), - end=(self.offset_x + 90 * self.cell_size, y), + start=(self.grid_x, y), + end=(self.grid_x + max_x * self.cell_size, y), color=mcrfpy.Color(30, 30, 50), thickness=1 ) @@ -91,7 +99,7 @@ class PathfindingStaticDemo(GeometryDemoScreen): """Draw a planet with surface and orbit ring.""" sx, sy = self._to_screen(cx, cy) - # Orbit ring (using mcrfpy.Circle for smooth rendering) + # Orbit ring (smooth circle) orbit = mcrfpy.Circle( center=(sx, sy), radius=orbit_r * self.cell_size, @@ -101,10 +109,10 @@ class PathfindingStaticDemo(GeometryDemoScreen): ) self.ui.append(orbit) - # Also draw Bresenham orbit cells for accuracy demo + # Bresenham orbit cells orbit_cells = bresenham_circle((cx, cy), orbit_r) for gx, gy in orbit_cells: - px, py = self._to_screen(gx, gy) + px, py = self._to_screen_corner(gx, gy) cell = mcrfpy.Frame( pos=(px, py), size=(self.cell_size - 1, self.cell_size - 1) @@ -112,10 +120,10 @@ class PathfindingStaticDemo(GeometryDemoScreen): cell.fill_color = mcrfpy.Color(40, 100, 40, 100) self.ui.append(cell) - # Planet surface (filled circle) + # Planet surface surface_cells = filled_circle((cx, cy), surface_r) for gx, gy in surface_cells: - px, py = self._to_screen(gx, gy) + px, py = self._to_screen_corner(gx, gy) dist = math.sqrt((gx - cx)**2 + (gy - cy)**2) intensity = int(180 * (1 - dist / (surface_r + 1))) cell = mcrfpy.Frame( @@ -125,75 +133,90 @@ class PathfindingStaticDemo(GeometryDemoScreen): cell.fill_color = mcrfpy.Color(60 + intensity, 80 + intensity//2, 150) self.ui.append(cell) - # Planet label - self.add_label(name, sx - 15, sy - surface_r * self.cell_size - 15, (150, 150, 200)) + # Planet label (below planet to avoid overlap) + self.add_label(name, sx - 15, sy + (surface_r + 2) * self.cell_size, (150, 150, 200)) def _draw_optimal_path(self): - """Calculate and draw the optimal path using orbital waypoints.""" - # The optimal path: - # 1. Ship starts at (5, 55) - # 2. Direct line to Alpha's orbit entry - # 3. Free arc around Alpha to optimal exit - # 4. Direct line to Gamma's orbit entry - # 5. Free arc around Gamma to optimal exit - # 6. Direct line to target (85, 10) + """Draw the optimal path using orbital waypoints.""" + # The path: + # 1. Ship at (8, 65) -> Alpha orbit entry + # 2. Arc around Alpha -> exit toward Gamma + # 3. Straight to Gamma orbit entry + # 4. Arc around Gamma -> exit toward target + # 5. Straight to target (105, 15) - path_segments = [] + # Alpha: center (25, 50), orbit_r=12 + alpha_center = (25, 50) + alpha_orbit = 12 - # Current position - current = self.ship_start + # Gamma: center (85, 55), orbit_r=11 + gamma_center = (85, 55) + gamma_orbit = 11 - # For this demo, manually define the path through Alpha and Gamma - # (In a real implementation, this would be computed by the pathfinder) + ship_screen = self._to_screen(*self.ship_start) + target_screen = self._to_screen(*self.ship_end) - # Planet Alpha (20, 45, orbit_r=14) - alpha_center = (20, 45) - alpha_orbit = 14 + # --- Segment 1: Ship to Alpha orbit entry --- + # Entry angle: direction from Alpha to ship + entry_angle_alpha = screen_angle_between( + self._to_screen(*alpha_center), + ship_screen + ) + entry_alpha = ( + alpha_center[0] + alpha_orbit * math.cos(math.radians(entry_angle_alpha)), + alpha_center[1] - alpha_orbit * math.sin(math.radians(entry_angle_alpha)) # Screen Y inverted + ) + entry_alpha_screen = self._to_screen(*entry_alpha) - # Entry to Alpha - entry_angle_alpha = angle_between(alpha_center, current) - entry_alpha = point_on_circle(alpha_center, alpha_orbit, entry_angle_alpha) + self._draw_path_line(ship_screen, entry_alpha_screen, (100, 200, 255)) - # Draw line: start -> Alpha entry - self._draw_path_line(current, entry_alpha, (100, 200, 255)) - current = entry_alpha + # --- Segment 2: Arc around Alpha toward Gamma --- + exit_angle_alpha = screen_angle_between( + self._to_screen(*alpha_center), + self._to_screen(*gamma_center) + ) + exit_alpha = ( + alpha_center[0] + alpha_orbit * math.cos(math.radians(exit_angle_alpha)), + alpha_center[1] - alpha_orbit * math.sin(math.radians(exit_angle_alpha)) + ) + exit_alpha_screen = self._to_screen(*exit_alpha) - # Exit from Alpha toward Gamma - gamma_center = (70, 50) - exit_angle_alpha = angle_between(alpha_center, gamma_center) - exit_alpha = point_on_circle(alpha_center, alpha_orbit, exit_angle_alpha) + self._draw_orbit_arc(self._to_screen(*alpha_center), alpha_orbit * self.cell_size, + entry_angle_alpha, exit_angle_alpha) - # Draw arc on Alpha's orbit - self._draw_orbit_arc(alpha_center, alpha_orbit, entry_angle_alpha, exit_angle_alpha) - current = exit_alpha + # --- Segment 3: Alpha exit to Gamma entry --- + entry_angle_gamma = screen_angle_between( + self._to_screen(*gamma_center), + exit_alpha_screen + ) + entry_gamma = ( + gamma_center[0] + gamma_orbit * math.cos(math.radians(entry_angle_gamma)), + gamma_center[1] - gamma_orbit * math.sin(math.radians(entry_angle_gamma)) + ) + entry_gamma_screen = self._to_screen(*entry_gamma) - # Planet Gamma (70, 50, orbit_r=12) - gamma_orbit = 12 + self._draw_path_line(exit_alpha_screen, entry_gamma_screen, (100, 200, 255)) - # Entry to Gamma - entry_angle_gamma = angle_between(gamma_center, current) - entry_gamma = point_on_circle(gamma_center, gamma_orbit, entry_angle_gamma) + # --- Segment 4: Arc around Gamma toward target --- + exit_angle_gamma = screen_angle_between( + self._to_screen(*gamma_center), + target_screen + ) + exit_gamma = ( + gamma_center[0] + gamma_orbit * math.cos(math.radians(exit_angle_gamma)), + gamma_center[1] - gamma_orbit * math.sin(math.radians(exit_angle_gamma)) + ) + exit_gamma_screen = self._to_screen(*exit_gamma) - # Draw line: Alpha exit -> Gamma entry - self._draw_path_line(current, entry_gamma, (100, 200, 255)) - current = entry_gamma + self._draw_orbit_arc(self._to_screen(*gamma_center), gamma_orbit * self.cell_size, + entry_angle_gamma, exit_angle_gamma) - # Exit from Gamma toward target - exit_angle_gamma = angle_between(gamma_center, self.ship_end) - exit_gamma = point_on_circle(gamma_center, gamma_orbit, exit_angle_gamma) + # --- Segment 5: Gamma exit to target --- + self._draw_path_line(exit_gamma_screen, target_screen, (100, 200, 255)) - # Draw arc on Gamma's orbit - self._draw_orbit_arc(gamma_center, gamma_orbit, entry_angle_gamma, exit_angle_gamma) - current = exit_gamma - - # Final segment to target - self._draw_path_line(current, self.ship_end, (100, 200, 255)) - - # For comparison, draw direct path (inefficient) - direct_start = self._to_screen(*self.ship_start) - direct_end = self._to_screen(*self.ship_end) + # Draw direct path for comparison direct_line = mcrfpy.Line( - start=direct_start, end=direct_end, + start=ship_screen, end=target_screen, color=mcrfpy.Color(255, 100, 100, 80), thickness=1 ) @@ -201,10 +224,8 @@ class PathfindingStaticDemo(GeometryDemoScreen): def _draw_path_line(self, p1, p2, color): """Draw a path segment line.""" - s1 = self._to_screen(p1[0], p1[1]) - s2 = self._to_screen(p2[0], p2[1]) line = mcrfpy.Line( - start=s1, end=s2, + start=p1, end=p2, color=mcrfpy.Color(*color), thickness=3 ) @@ -212,81 +233,82 @@ class PathfindingStaticDemo(GeometryDemoScreen): def _draw_orbit_arc(self, center, radius, start_angle, end_angle): """Draw an arc showing orbital movement (free movement).""" - sx, sy = self._to_screen(center[0], center[1]) + # Ensure we draw the shorter arc + diff = end_angle - start_angle + if diff > 180: + start_angle, end_angle = end_angle, start_angle + elif diff < -180: + start_angle, end_angle = end_angle, start_angle - # Normalize angles for drawing - # Screen coordinates have Y inverted, so negate angles arc = mcrfpy.Arc( - center=(sx, sy), - radius=radius * self.cell_size, - start_angle=-start_angle, - end_angle=-end_angle, + center=center, + radius=radius, + start_angle=min(start_angle, end_angle), + end_angle=max(start_angle, end_angle), color=mcrfpy.Color(255, 255, 100), thickness=4 ) self.ui.append(arc) def _draw_ship_and_target(self): - """Draw ship start and target end positions.""" + """Draw ship and target markers.""" + ship_screen = self._to_screen(*self.ship_start) + target_screen = self._to_screen(*self.ship_end) + # Ship - ship_x, ship_y = self._to_screen(*self.ship_start) ship = mcrfpy.Circle( - center=(ship_x + self.cell_size//2, ship_y + self.cell_size//2), + center=ship_screen, radius=10, fill_color=mcrfpy.Color(255, 200, 100), outline_color=mcrfpy.Color(255, 255, 200), outline=2 ) self.ui.append(ship) - self.add_label("SHIP", ship_x - 10, ship_y + 20, (255, 200, 100)) + self.add_label("SHIP", ship_screen[0] - 15, ship_screen[1] + 15, (255, 200, 100)) # Target - target_x, target_y = self._to_screen(*self.ship_end) target = mcrfpy.Circle( - center=(target_x + self.cell_size//2, target_y + self.cell_size//2), + center=target_screen, radius=10, fill_color=mcrfpy.Color(255, 100, 100), outline_color=mcrfpy.Color(255, 200, 200), outline=2 ) self.ui.append(target) - self.add_label("TARGET", target_x - 15, target_y + 20, (255, 100, 100)) - - def _draw_legend(self): - """Draw legend explaining the visualization.""" - leg_x = 50 - leg_y = 520 + self.add_label("TARGET", target_screen[0] - 25, target_screen[1] + 15, (255, 100, 100)) + def _draw_legend(self, x, y): + """Draw legend.""" # Blue line = movement cost line1 = mcrfpy.Line( - start=(leg_x, leg_y + 10), end=(leg_x + 30, leg_y + 10), + start=(x, y + 15), end=(x + 40, y + 15), color=mcrfpy.Color(100, 200, 255), thickness=3 ) self.ui.append(line1) - self.add_label("Impulse movement (costs energy)", leg_x + 40, leg_y + 3, (150, 150, 150)) + self.add_label("Impulse movement (costs energy)", x + 50, y + 8, (150, 150, 150)) # Yellow arc = free movement arc1 = mcrfpy.Arc( - center=(leg_x + 15, leg_y + 45), radius=15, + center=(x + 20, y + 50), radius=18, start_angle=0, end_angle=180, color=mcrfpy.Color(255, 255, 100), thickness=3 ) self.ui.append(arc1) - self.add_label("Orbital movement (FREE)", leg_x + 40, leg_y + 35, (255, 255, 100)) + self.add_label("Orbital movement (FREE)", x + 50, y + 40, (255, 255, 100)) - # Red line = direct (inefficient) + # Red line = direct line2 = mcrfpy.Line( - start=(leg_x + 300, leg_y + 10), end=(leg_x + 330, leg_y + 10), + start=(x + 400, y + 15), end=(x + 440, y + 15), color=mcrfpy.Color(255, 100, 100, 80), thickness=1 ) self.ui.append(line2) - self.add_label("Direct path (for comparison)", leg_x + 340, leg_y + 3, (150, 150, 150)) + self.add_label("Direct path (comparison)", x + 450, y + 8, (150, 150, 150)) # Green cells = orbit ring - cell1 = mcrfpy.Frame(pos=(leg_x + 300, leg_y + 35), size=(15, 15)) + cell1 = mcrfpy.Frame(pos=(x + 400, y + 40), size=(15, 15)) cell1.fill_color = mcrfpy.Color(40, 100, 40) self.ui.append(cell1) - self.add_label("Orbit ring cells (valid ship positions)", leg_x + 320, leg_y + 35, (150, 150, 150)) + self.add_label("Orbit ring (ship positions)", x + 420, y + 40, (150, 150, 150)) diff --git a/tests/geometry_demo/screens/solar_system_demo.py b/tests/geometry_demo/screens/solar_system_demo.py index 8210851..bd6eead 100644 --- a/tests/geometry_demo/screens/solar_system_demo.py +++ b/tests/geometry_demo/screens/solar_system_demo.py @@ -2,7 +2,8 @@ import mcrfpy import math from .base import (GeometryDemoScreen, OrbitalBody, create_solar_system, - create_planet, create_moon, point_on_circle) + create_planet, create_moon, point_on_circle, + SCREEN_WIDTH, SCREEN_HEIGHT) class SolarSystemDemo(GeometryDemoScreen): @@ -15,18 +16,29 @@ class SolarSystemDemo(GeometryDemoScreen): self.add_title("Animated Solar System") self.add_description("Planets snap to grid positions as time advances (1 tick = 1 turn)") - # Screen layout - self.center_x = 400 - self.center_y = 320 - self.scale = 1.5 # Pixels per grid unit + margin = 30 + top_area = 80 + bottom_panel = 60 + + # Screen layout - centered, with room for Earth's moon orbit + frame_width = SCREEN_WIDTH - 2 * margin + frame_height = SCREEN_HEIGHT - top_area - bottom_panel - margin + + # Center of display area, shifted down a bit to give room for moon orbit at top + self.center_x = margin + frame_width // 2 + self.center_y = top_area + frame_height // 2 + 30 # Shifted down + self.scale = 2.0 # Pixels per grid unit (larger for 1024x768) # Background - bg = mcrfpy.Frame(pos=(50, 80), size=(700, 480)) + bg = mcrfpy.Frame(pos=(margin, top_area), size=(frame_width, frame_height)) bg.fill_color = mcrfpy.Color(5, 5, 15) bg.outline = 1 bg.outline_color = mcrfpy.Color(40, 40, 80) self.ui.append(bg) + # Store frame boundaries for info panel + self.frame_bottom = top_area + frame_height + # Create the solar system using geometry module self.star = create_solar_system( grid_width=200, grid_height=200, @@ -92,13 +104,21 @@ class SolarSystemDemo(GeometryDemoScreen): # Draw initial planet positions self._draw_planets() + # Info panel below the main frame + panel_y = self.frame_bottom + 10 + panel = mcrfpy.Frame(pos=(30, panel_y), size=(SCREEN_WIDTH - 60, 45)) + panel.fill_color = mcrfpy.Color(20, 20, 35) + panel.outline = 1 + panel.outline_color = mcrfpy.Color(60, 60, 100) + self.ui.append(panel) + # Time display - self.time_label = mcrfpy.Caption(text="Turn: 0", pos=(60, 530)) + self.time_label = mcrfpy.Caption(text="Turn: 0", pos=(40, panel_y + 12)) self.time_label.fill_color = mcrfpy.Color(255, 255, 255) self.ui.append(self.time_label) # Instructions - self.add_label("Time advances automatically every second", 200, 530, (150, 150, 150)) + self.add_label("Time advances automatically every second", 200, panel_y + 12, (150, 150, 150)) # Start the animation timer self.add_timer("solar_tick", self._tick, 1000) # 1 second per turn diff --git a/tests/geometry_demo/screenshots/geo_00_bresenham_algorithms.png b/tests/geometry_demo/screenshots/geo_00_bresenham_algorithms.png index 64b43e5d5e8ca3aec4e68c40f8babb32a159b557..37716e9fb1d6455d4aa2698cae526de5cc2ed3f8 100644 GIT binary patch literal 60812 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

J{Kwz{^*JM4U2w34VXOz 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 Me;Kcc)wl5eACjX@rT_o{ diff --git a/tests/geometry_demo/screenshots/geo_01_angle_calculations.png b/tests/geometry_demo/screenshots/geo_01_angle_calculations.png index 5d1550dfe13659dc9999f8eb5d6e6ba9a4950957..b7efa928f5122b4f2c38d0054eb62783d14606d7 100644 GIT binary patch literal 87028 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 M+_@orz5TiW17=)EzW@LL literal 92371 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 UjE=FMLg2@0y^UqwI!4(40OaKxxc~qF literal 67505 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 %k*`{)dC{;T1TVInF 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 diff --git a/tests/geometry_demo/screenshots/geo_03_solar_system_animation.png b/tests/geometry_demo/screenshots/geo_03_solar_system_animation.png index fb74ab46e21b52c2c590e8091eae6f418d824da2..c31c08cf53983fe81fc1ffd5c47ac2d8e4998e7e 100644 GIT binary patch literal 55249 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%0DJZFY^SLx=dnjU6bm zt-G=L6<0WK!?qK(h0iI}Z2sWk<2pJzy0<-#yBzUd=oFjvW4cvf*K+o*7rV7n-TZae z&obOD;^cTp%kGmV`@4b(E#j>U?c0X%laou8a<+&)OcbVV9URV+`TOxj^`~y8VK;A! zaTPh6ttAK{ML7P=~4zq`H$XMFX zyxR(jjqTQJvKb#Yu_MFphCc6oVnkD1_lcHnxKZKRP_OOUCU?yvdMOVdXrK1;aM`c^ zBfj>NN@1F)QOzq-DJMTWm~T@~zyPU9ldAp>PD}zQfwzJ~4F3%rt{=e`z6=R$%U!|Vmd)2qz4}D2x9O%{Z0}zz%G_0359T8rDJ_Il z4E`LL{LWX)cn0}QchIniO%bF+JFF0@8Z#sry|Z?9mIc?enkS!NKU>+A^W-UYlEjdL zVb#VQiqNxx?#SA@_{P~GdDLnlOx94(TzV(t{{1T(q@mC(>{gghg@OWYnN*Y8J#(GK z@3givm+oFl-hrNk-{;!Xz?oPaBW9+Fx+*x zy(9x_3VfkWBlahb@B~%CIN}swS;4&#H1YW7zVJR7xyr&H2YN5tWb_|Om$3p(Sh8LhNXb^wi()x* zHD1@a+{nc~bVz5Muj&5vf~t2`N%)HcD{x;fGncsg_5+A|j-j!}BHgt!`fonqWfC%* z36=ubm7{5Xbui0YhC7WW8;D_7EFKPT-JnP(g+-;^)NJ-l+DNowd&+Zg+!qxns2u(F2~(@u@MiEXhU^>}UV z@!wt#Eh_tdee2WS*i@)DIQT2v@lEqb>a|mkQ4#m#=fh(5!tA|_B-N|K6uVELW@*P} z?Q-0&*%wCgP6NsluCpPmXPD*>W*}n%+BKhw7~4XP zTTgoro(L0nT&47CIaIU~UXebGd?txJ2}ID9qjnb36eu?G8>~J+U{rpu8aL6lq}BYv zYR51P4Su_~&D;gegLp4>Dj}#o_%$z*}_u0fh_ZbDiDYdzk9@f&hdGVM$)s20X~L$wW`pxO{Uc$ityv z<`RQzh(4ibhZYW$4zo;4A?{rQlbfMlbt4$i{a~ztgg=T*le8{Jp>8n2P;}@As3J>v zf^)F*OrISpWi}pSQitLbMRBhPy1KECiUtnm5!WxlOqn<`=%@xTs<9mA4(Je^2>cD5 zaIYIyz4_XQ0H%*ufF4Ehj(M+{%SGXh#xz?Byx=Fi0J#`wpK!8YgETbCZur-kYpPq{ z0gUd$_z)*-7jRwq!ng+vaJXU!+u5J6^1p-bJ_L52E+BCVBFCuc2`r2ost~sQD3aKW z;!$uw_}&O|-oe7mPu}psYV2%F@OgUAA^bH&(DE<9I0b-HDCHGG4?+xrM$aX{`^2;} zHG(*2`CLmMZx|uSgHHRfSun9CizoGkc}xkN1W_dl$AQv0Veq|L*fU@<$rG!FD;kOv zD+eR5do}>tX_rkoflec+rJ4v<`T@T6snN0+;EVE{LU6t^U_=2BY24I04K#EJ27KeM zL_dOHiD0ta1fW;%sfElq{FNAC1RLYOz(7>+H%14d23lh8cvIt>m2Kaq5S#xO%Bv)a z+(6D_dI>CfkUA*SY-@_VzO|b$w`bx(#Tdd>B%oMX@XK|kzPjK`XtwdGVViaHAs&Em zYXL12vRD6!TMK)c$%bv6>W1n7_%RGA-8yrunWAg?H0l5W>1q5xaKzJSie;>q`Gy-emjfbjO;2PY*$Tp$- z{RSt6fioj~MCIr*@96^vQfYZnejsj|jNA?0mVs}Mo{S$)G+EobM9$QL zr8~GqUq69piuQrpOst8Opn%K1rdd(}s`U3O|vNP=YP6uz2JTq-)3PEV~Chl=T{xfN*4nkBTw906|wHoaV(5Mp;XJXC?` z2o4oN$0OkAlF37pBz){itZLO4+8S$4-Bii-Yf|l3!xFCDNPGw_)Mcn~I*QNCNCJ2< z$|jp~e1;4L*cXTlTX8ZV9E86i#FdbfCJeZ#&2-G;-R0PQ+zf)ikX5iJQU3`|cuqCiVPhtBuQB=HHMO5~3>Au$P7Y0ck9>VJGyqV*4YWY< z!r^4#v1hfB0%JTi`(@=W+YJfaE#WCRxPi&GhCuQOQpYQt_CR0p?Wo8PlKU?QUjXippt z3xg^&og} z_3>Sh2GVS0k_=?jS((xY^d+ONTR6Z=Apfh6cL$6sr~rbj2S4_2O!!m`IJ#D~E3xIk zj|s=CYZcp$M9MR|;gzWdaqq~wAQ^Gi<#?6~gUkZDiAN?7Kz#>9A!GCP<;bMuw|zF! zwY*#wPblInED$C%Y!e2ypVMMpUW2RhM}+pbC8!92tf_OmSFaEAmtk#y8ymiucO0EC ze9<1wcLCgw>(6mLfY=6127(JYkpA%$rKO9AezaeGPimq1WvQzsXvD3&2Ot-qX%bn~ zcc2LR&EpJkxBSo94&UKCCNmh5-qNK-sAoxH8|R`4+46g^*ciQwwWQboe5Nwe*&A=k2wcQVF^0Oy2pl+AXvz^OICyQsb-L zDTGh73%5yOA|GyD;O$tbNFY^RFJ%JtN%`vIQvG&K-Sx*v-9Q0_+FE-WWU6iEq$fc9 z0BIAzKL%}ohoD^~VWWcX1-k!pR06&CO#WRddVA-oE4x$N9s(I_*U8{dc$5J5&#iUV z<(>Eww1_7hB%T2J5`Y;?MA)BeZ91yz_b#~ecp{ZuYPmZw`Yx3=c({S76uH5f3tVjf zI`VowehiTCLdO7TgJ4H+{81i|I=t9G*0`skKSrhFr_<1#}JNO%Zd%XA!vw0CH^6lkORVZ6Ks192^oW!>!M8M ztd2k%EQ&(2%;y}lF2{THu`Gf$?+_aV)$d z=5e}E>T3osl=alVhHr_wo_JvvdSM6&gx3OaGT?;(;F*vK7`+I|3f*ULF=i0>8-;|8 z9*mO|)fGr4@I=6yL@2N>1&OGa3k8r7*@=WD5?F}$x)Wf5Ibiu|q6De!A^ZvW4-|oJ z$q-;M&`@y^p;AGwqS^$;xY`8x!bQSNd4`K3c?GcrT&zPZAZ81J@Ck$qhsO~N37PqC zgP6j=Gys)8iHHs%md^14>4eP&5aA9;VxvoYCh;a1ZUSlQnZ$ruoMN@WEx?lAMC6+W zONHsT-bCiG$r9zzySsn#M8*MQowme$A@e%4AkLF`D6lBXJfFh}5vD5=vs;W7DH!KT zn55!_+D;-q0<=OtMH9YU4#*80S-v1y2_IPxS_h2r5r~YD){%02xj5=1P=}b*g;NI- z`N#*0#5D@_3}I{CA%5$Zp#vb$r*%f-yH(Ltc9oumaVq%o*)S#rE&m?AHY=bfEZ_Dv z{u}WF$b)W5{Fer}I6&IJ<7NYGEf5#7K;t@L$$%VL0=^6vUu^_|eyK*$P1=7YJmODBDq=ievnT3rEdt zz#oQwkR0F{%fLn;3eOA)+hF9VufkEo3`P+_$^*PO==DLXAz?W1Dg2VDAOU$L_BRSC>_Y3tAo>p{}s#wkZp8<6Ikr&Bi1~T6Rv_WW&q@P z9*%V!IucBUF>T$gKZIL~2yh%3v3H3hgL=BpFvunu{|&cX@P`X}3jaWFgw8zNbE_IV zQDpnUlvcv_LrM=x2qB9IkOfHy3SA+IIa@=94Vv%Wx92eVcaP5`fj6jd!4pw| zIzkPsWbvp1G0=d_p7g*}Jk9~W{s@FGpI8TY=?g_7W{wV>hlVm_v!R#>e3lUYE`l+T zI|9N#_FOOpflWpSyZ^^jRs6#%1&iPkJf$h1{(oRb!oX!jkCy_jK%)?VVNvu8=P9V6 z!6$tS9#@L%3R5L7{Z07a;Ri|!BIg^o6=1~-kSDON*9&~rrQnL$)-d;uunL~&11hc; z$W#?Ua+i_kK~FW#T=--c#9SE!WdvQDEaC!Ohx_>Gb;NB3P_BgXu>zna6r2&%y`e%X zP)6c}GLj@I5$Z^vkO(VA6H$xvXe9oJ<|F}j*vM@Q=F=r&$22ef8-g99Mk=&pe5M&J zL1v_39v>lueuvwsQY307!QZfji^LYrkMI#*2nDXH04P`uS$HJtQ-bePVf+q~55W!e zpT*DsfrTHMphgc}#OTn7k%rK?0@fQUn;5NF(h-3ezV#bXnyr~&310}!@f^1gV4Hn} zfCr3_1*THdv(<6D4h6wSc7a%dvkKsZM0y|QSfDW862T)a;NXCzA!th+HHwSi+5!Fe z3%Gy`BclW*8GjwZ5ab4DgzP{yJ6QEYh-nbB835Q4LAL7M=$J2N3b61mBM#>W2^>HooFiP+cg|AZYGpg5CKMJ}>w_f^!Bw ztq|m#k>vx96taA93ZOC%s09jJIQ?YZM;<06W$d{9XZv3=|R+ z-iBB&P zd>U1Jg+Nm|k$U)th;W=0yx9x11H`B_1x1FajtUJyn@(I>P`ZuoOF^aY_;fW+b{Y6O z1ah1SL#TpyxU1mdBeE{2`w6jt>mRDXpk@di8M1+12y7svxL~0Oe@0NZgQ{vgC1|$9 z1ne;I;6aa!&MQS@UyuoOU$F1R1P#{`9E~bnfriHgf7K_n+E)QpWWaR+s!S&kknM<1 z9}s*RO{f5hiwbN38K5+xA^vIP10hbq$3dbK0RJAz(sF^Fp$^Oe5rCPsNHUN;gG@cb zTZ3(O!Zs1#3hEo!Ag6c9G}{9FmDZ7{g(jY&k>FXtsBQyBRLT!#45qFoeyCe zr5E_kZGTHfg3*Ng(K!3lf6MF_ezXRGq!XG51Bw2c$6PD8VM4@1rYx_02Qr;4}b=d8|!7&MhPtBY>hl!JHnT0JqcR*sGyIFza-um zv+e%fVuc|ca2SVV;zInls|&9m86hqmv=0fQQi5v`dH^R16@osKmQb=)0}^;!GW0v5 zu8&qhNRk2xm2V~>I20Yj!or>OLtudj{(O;foTWgtAaDwMgf|cYYT_Wg#W<=o!wh4X zy9`$dRCDR8MvW>kU>69`(#e>I$6D*{0Gl#1FxVU3KKKx@}d0hR? z%a^$|9UZSaTPk<6&z}8gW!A30;BVSAjUk2pepkJ~-cEOYEh)MfwZ~BH`N8g2st+EV zq~5r(X7>HVT9T7)dFt0jvr(=@Yt~4bi{K7Bu@H3~0Jpd#BE4eWIxF_clmD=*t99A- z_dZjL*1xOT_%i0ii9)ZYQXMjda)EbIKSw=f{||?B31^pUtUXRIQ#?OOu>t<3&inoE z4s+Vsd7pAR0Rf2Di(3eE2~xn<=*oLvbaH;qpWESdoa*#8(z;=k76J~X{*s}|3S$F3 z%Aw(U6pBgg!S1FTjwW+dlv;~qWM$vv6cy>gIg9PBYWpZ%bC=;5;(Q(eqb6OPknP{q z&YXbX>MRYBrW58tgS{`YqU@B+XCv{4TT%q!o4%O#&n>QD5u3anf>S-0_Tw2by ziPk>X@Vh4sGie{DDk~3B%ah({m@t)<0u?@I`WImjg7uF}-jVV0d+e5yW?->L|ED~u zN|vW=BBgC;v`J5C(f-g{KA%tR>5-?}L|<|>^r{g#wzYJvyN%5pvX9RUvWkjl4To#P zP|SSoRhRG21|+N#a)z&8ucZ3=%2UZ?hn(CQp6vPCp6UFP9Te`!AKp&oNXa~KtgE|1 zC6PR-PEL7Lfd6ty59}i!+rkM+fAE0H8CV@Ugq?DBa&n%nRqUMZ(=rd>_e^s0@;^vO zT=4JOpqrlYLFdTv+ci#`GgLHY1}$6mhPqy; zwbdi1J3iOzN1YG5_=2Av=lTGZePLmTjz?9>Dd&%3U-zy3Ikj-F(<{l14zW7hC#3wN z?Pj;(tXuEUfV41^;o4XdR-+rTVR(i?_QcJ@6E~~Kyi(<)Xk1g8wtkgvMCczJatN5? z+4(IZCvPwPWN%LnKsh|A5%~GB--|@xcq3Ls9hg&!}2wmLzIcorOeVSe+ zRDbBWW97X3<$pPqsooz3y_Me}?Nop6L+#3yivgZa!jR@0Z((mA@Ax8Ws76X}scLf9 z>*8Y3mA@;Ot(u?TfR6pfSN5%2TEfgF<=0kJ<~Bcn^-3i6_eO;c_ddU(OYmhcd;xyC z-z+MplmAUR#=-KdjLz5T+&}S$ENDU+0T`q0gDrK=2D-7?=CoE3zx}$pCuw{4F0Yj| zvAd|X?HSv#<=vK=ruwg6MWuw~NN&nTxwptM^{KaZPD^=M$JO3b7s1Onv$U!K2BoS| zf0{836zsLDGkl_J0WggE;lqxaP5Vo81Jn$XC;j-L(GX+0W{o;oR8*k`duz}cp;34n zSWQnGsxmh^D4tk^b#~V3v4Ms{Zg01-;(h81lxuEx29xb56DwX)7W7!iIyqgTLIGHf z9+gp)YGm|Yp`qcbU8ZoHXymbR^WtY@32A4VUrhsSR>uRsqL`4pz_>IhxnbJex%r&r*+l?1>B_!kzdPhPeU5^zt`n{M*CUoNM#*)` zXJ1ZE))tnK(DM?_b*yfh4M5K$&3vBBdL~QryEw@44!a4^4THf%AQHfmg&?S z6BG4%A(`eRe0nQANPFQzMpf2;cJ;_vzdNW_bo8Zae!=`l-1Ak2X8XR$3Nz^|SFD(P zY5uQn{g62?p0Vtsv}44M*e2zFTe!AJGUmyYdH;w$RXn@(_U(v}#)}?qn55jAFWLED z7dj1Dcm$}=3|4FOffK&}?-Q06(&$&rIW_CemBlZHhr&Z7Gv-i*goI|-Ro?w{sB8BZ zc593L#EE%FUfo#NsLe{7?D08syM6-Pn4e{^1^SMWw$27j1BvTv>|{<#ZF?3deCfpY z?b}7mK^}zOyz2V7_mtc#&b6A=mohQG9geOcWEHw^=-Cf>)cVFoQN0t=?*o5**J5u0 zVRplmzX4bwc2dMg-=b1w>yy30i&C(1`t2<3Un#jFGOFH&Q;(4E{Ka}HAz_s#Dkr6_ zQ|f!fyFSgC*}SR6T5c!+YBxO6TG#GUOT}-=So6?(C3*}s{+&w z;z3%LYAsYT4LEjkD}9dRCDQ{hu{o2H+qOA%O$G5Z0QGE6Cs`&dO-sAzOcVXXkSerf zEWU>Umfig2-mF!hD30BRmK(yW{E7|ZVj-<`9dM(Z#2={5nIm<}lry9Msv&*ZvM5q& z^_0|W*yXLUS%vWm9?DZoxAx7k_avRO+R1Wz^Y!bs+>g#N=lEgOHb9?jwadF$#am0o z#XyeT)MTaxT{b&Nx;wK<$V^ymTAp+(wbV%Mf>J?SX-hgq#A5#qQ2!)~RVa9iVe29PoN){`K8O}!!0 z3wBXe7Ux>~wr7m3Mn`O^=O_U1O1qLMoI~jc#d3m_R=sWOR&wSWl})Ozw_0C~IP}_j zgT#l0RO3d>`?(^s&%7zctTaMzrKDz$>{O$aK&hR*^<_58dqdK!SlUb%k)|3eKR*h) zhB2RAQsO@QmV(%m6LS1KW2t0EiI(*-m)1{IK3H*h%aN!z!B-@&Ey-M?7epyLs20p3 zKcTyeq<$6A=aA#xl9d;~q?6TV#LK?gt=4j5lV?t*l`TK?_4Jw96I-O6iiCH3lWy>u zdHr7}f5eipQ4(XFbqUSeuxb@KXnA&K=G^`M9ROy@RB3(xUb7R*A_1%W+rH&iHE<&{ zG=liUlI`i-*TO!576;z(>{DtUu3ouPBr{sYTSlohW>HLlrsf3dE{=k6j9QaiP*w8) zXO)gYm&Id8R@y|S`wfr26Dv%!AH`{6k-EHtX~kx+ll=38`z+f}s^2K^pH=^UqT9ze?9_ zKXLlsJS2{I$gAyxB-_NV9nerK^}2tr7VhvbE?Pm^)!P9LmD(cD&7yj1hrfsK- z)igL$e6It{Y;0PZfw0P|>msflAN`p6!jg3|c7OlPdMf3+N(9Wsjf(e6C~d*Py`2*} zjm`XOZnP)m+GFBkoqDn7o5SvPUNG?!<*AcCaHU@fU%C`k6NXtlbmT;-r$w1EJLV)? zeQ#?M`Anf2M{ZlPgmi05&QxmdtjjG;$$G4w{7~a1h2qQZpWFB*^WcC~U{zdnw8WsX zRq0!{TDID@Tx*ZskX19Hp}OS~f6wIo$u+w=`^0^HU28T^S=u}pR=s+MVVKnOt{z|V zm#dg@XK6tJHBfSDQ|E&2PVAEqNme+%Ea-Q;Fu9cR9KcO$U(xkSIr`t~>#0!^b}yD% zTMO-HT-E9PP`Gb;yc>O0=ES|Kg$c-ts5__hJ0y>CnnGW=;-Y!*8B*F`t-M6H&dEAz z3uSM$cQ>z~HaD-y(OVE(Xgs7UzvpVhKFdBy=a~zNBpdXXY6&cducQTCl-d6*)tseZ zJ&K%!+8A$;WYpLFv%yB{&5;T=>DSx!f(pL6n`I=zvDvR z`ibt*Yx=)d@qwl0GSiC7>sPNn2y8Sk-a57+_jcW+U@6Uitfil(13PvPo<2SC;Pq>! zZQsw${mpZGyZnJH{frF>=jSQ&EO>p=q1jz(7Wx4YtoNFVjtPh}-AC z#_B?s@r0-g36dG2Jj^p2uGIAD?{A#raJGo~ruUUk`Wt`tM$`*L%QgIQTKk95sQU_L zX5wVL(02L%m$L3&U#`Qw(!bq7!)1svo9Cne!(Fk7^XId~yxlsR(Zs>*&f>x)Kg&&` zVI!{~)xbqC4$g;xyJjX@mhfjRySM^r!WcB51e{^{^40X<_b1JHha@-?%`g42o+Xbm z7_kE~(h0ii6W-5Y6mn+R63bF%{@;UR&%|fUW0L>lu5j2qnvXsXNHVTTR z*mJz$l>Z%7p?M-vpG#8n#g{DW(!GMz;mF|J|JtgUPr@ zC@X+-JHYc6k;ka9n>td*C5awI1Wkm1PPM?9Az%`Gz@=HhX#iQ+WDm~tMD!^1NR6Ap zF=mWEOAfQR0Zma8PU66AD8SMmID3B?cH{wOK?zIt!)qFY$%%o%$p2FH$*MQ;3_#%N L>gTe~DWM4f{^sq3 literal 54605 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 literal 52044 zcmeEvd00&S-~XAVnNc$}6sC1bBukXCRWy~mY|+ha-=-uZAtWT4X)=jamOEq{vgEdf zkZjX}q%xyW)*_iKNs{*Yz0Wx_%}m|*_xoJe^Zi}V?|QD^KRIU3`7H1C^jO7(Z?rf{@{ogwQDPPiAIM27){?oG{M)j|~eK2XMKKkL`86Jh@KffBi?a zj%L5}{x=>iP9*u)e`*Z`G6G-r$5<0$ik1Dp_+xEJ_M#$Qdz$|Bc<`-05_d`R+!Az9o8aLlLgifrNjzZ5j>RcEEpd+yD7R$`J|A zeMhYh4N&~d`kWs8QK082+o3?r)oQRL#Sjn8dS#l zQ$#X*7DFg=vQMUX6(h0BEG;d|-G(*(`u0{4W58~I{OD23gwX88I_f+$uS zgMZGQKmV1Y<<~a_^+xxqz~E109i6NvN8KV%1%+Ey2F@*;G{IbP6yYE6nG!+2e~o&z zjo|pL4~IB*~(}=QcI?=3EG1>JZ}j6`q+jubaHHwx2d>H~wlw;NmK)z#hhT}w|7NIiRYG5_9A z-YKiG6;@mkL{t)+3tm2*p?;G=Xetv$@QSyX6`0LJ7 zH`K(;_r!#+=gtn{^ED&FDCG4kIg}NzGyfFC#KasscP>~;N91z3eOg)?pK4R{`lh$^ z?!l3(EE4~4@NIZk_a)xkrF@#1nHhJ)n)wNV=I=bX-`?D&TUlAr$BY?6mz4R7-akli zMA4~3HU;u{JZ{FOUqf<$-^Tn&-;Hf1@8>({=_VhbR{Z?@T=<$ijcS-vHGcT; z;iBTxQyn9k>bCm*s(DRcvu2HLK8MuZvf$Apy!hZ&dMy`5ee{0jP zc)zNG1N2?Hc9r)u5F7L6_Fw1mouKEoc=2M<@a50!Bbw`DEAH*+O~11AabWq!kKU^- zwi(Qwu;yctc?fIa&9I%vPMjdSCX%Mp91Y4=mFA9T^!8}C9B$@bJ$mrqI01~Cm6IbR zlzjhfq%bkky-s{<`t<3-<2}#OLqE-qYRJ9zN836UrE&bo8w4*lcfqe~mVzo(LTA&cMN-$@t$If0&{ zveI%h%+Bq3)nVt5r=CcORm;!My)%T=VP+-W8M?auycH{!WUTw#i^TJ~bNqM|p+r_! zR~Pj%;ZEf~>6#x=QAE+1>8GP+nehvbxJ8Z?Ub*7<%C&s#7NffYlka&%vA%Jf!Kdh& zvMbaAopnY}ii=l^CaqrWkg=9wG7c%b$;~2Cq+(<;DRnyKQ~BjfmmC6?EOFpdEIHIm zevXNA8;FnRb4%0wNW$H_du;fillne5Fz~+bf(1U;ji%U+zq@Q~DkNIs%2J^Fl|Lc$s`8{h9 zs_pr=8LNeaF$9Xg~sUJ@Q?H!TFeYx^apNIy*QP;Bh1%0mF+MSd{F(~-@>V}~! zQ}>01&M;Cy%&uMCb8MBSlMo&wksq6tLOr?@{F2hf%~^(oHTDb{{UegQw9zdjsm?7# z^usNL((D#;cg(AT%F0TGq{wW$`G*f5%1KT9!;9%RyP5x`vxj=5#NfH!7kO0S`^w7b z9c87Zt<;MA{E4*!f60^}z4`LqqNnN?U7dL-y8^!^GWQJmQRNe$cyr?tSXy1rZUh+f4=LXBBrN73~Jj zmIjr*JUu|Cy27-SzA+< z@#~$y^YHzQ)HuV}geZ6Xy!`ovCAM~UTTS(x`3AX!!gGSayPV+i6@>DBvh3{a*-OX8 zt}g!YXkSG>Cq^U)tapE@&j}RvjW-)jOzPdeJE3@K(ILUn8JyUovadP^bdtAjEgLjw zklqN3(etG_+ss&^4Q5e zdTo)fq^>xFoMaCeFhB&BkNIKHn(VE!BNKa^6F=4e^yw2_eDdwHXU}Zs#{T@)@bzWZ zJQvPcWXOWdTqJ+0`13&bv()zXwn<@w4gSdN^T$f&XRB$CdV4hWPTx)m7&+d&Zssw) z-b47aejqO2=#P)O^>@DI{&E&)&~eV7*}d%M<|5l&q<;~o6>vK|80RDoW+tjVno$gg z$Ftoq^cOf!7U$7b_f67W3>~{iRJ#_6-5DLb9j9j|3cm;s=-hf^P%v)cv#DoO`vnZ; z=^=S5_aBs&Rc7iPy)7=X%$r{yZ`se>bK2_(!4Z~cNo53QZ$h+RL-#s8Img@owbzsk zX`clPZf)oufmn`MZ}@qUZ~q>l_emj3$euH1^c0>yZzs&i7zH*=useOMZ}oQH8}8?h zj<;L1t?;f`-)+Rmv@2Kk7`>W$bnFu3FhN#QLLS>fJYnp_*Le|`zT)Kw(eEn!SWm~$ zm8K_JO)Eb*hq}&p#EKWWA&s?u6lrijdHKl1t>Pj*o>%ZFircwj#^+K&>$ZYZTaUWO zRD;+p7)iY%w1`ps{?aW?U|7o0Er?*Tp9ykC$VsM1)I-MB5UXc*#UZmZLkEyj-_tTp z3_`CU@!sx-hd7_>rGGyJMm}$ePcDE^8 zN6ST2Y5NB0wI067gatX2%w)!h#rnD*0TvD8XB&r_lGtoEi6`zB`fW_DuY20Q7!PnR zixOk_rymOTwF;9S3$rQD>l77xFK{YI+r3M7nx^TGln?H#a+Z6!J6wjYOS5Oz~ZTl4( zE`8W0x%nk4h;Pm@`qh1HvkPh3p&svx6dRC&!PCCz9R9MU;me%bGe0GtU$#HEf8YP- z(5qi{il((wU*{ucxk!w$yo!`)E;*ZLoBy#fJFzsxYQ$}RwrOa}xJ8TJMwaXH6_Pm$ zg=9)uq_nVm+Cd_BHSyL&@MP?t3V?FLsX5fJ%k3nC&AGLM94b75GW{)mSC-ce9kJTK zP4BXy(OvN6$7T9 zYK^Qd&x$O6-KG!>n%pjYVtsAiB+g37tc8L~!L0f1REvR)?2Pbox7vvh6cT|?$XM_d zo4j7KF?vZyGweO|j>wMf>p;wSeCWp_iZ|7-UAs2x^ge6$#8FSIH#yC>okU2f7&&)- zyb?=G!J^n9DFfDPM!uroX&D(A_WXu{q^0@B{9kd7{G>u*(vyVxA_XtQH!06MXI1)s zGo^G_bjM)W^tuih2B9%Mh1pp5L60Oqv0D1)YVlecrzL*VrcK72)?H?@lnReIUlK%b zc@CqMkUGa23xc)BQ2RcOD?XEJvhzcyt^VDAE7-&o5|VNzLUU(oyso}tq2ae-mged; z=Ie+#;4^y&ZhImr4>$a~A-y|;@cwtX2XZuKQ--lSY|1|b zu-zg0hkZZ%xBLE|TqK$r(eMRouh99*3=$7I5}vcg|B7w@Px1!M-!T3`ur+0$9>ZiU zfY>(vKP{4d$6daA*G(uC=4^Y&DrxvOUhl4~N2n8-<@IQvY#b+r$+DYziTZ_?5f`Wq z{=t-4nh_O(i6Wg|y?V7Tnlq;bBs?0Q#QY3#+RWj+qsu z(@zTuR+ZF0=d})NWe=_@`VfQ^Rl@gj>;k)+6TY5kaLnT_EUn) zX>>A0GzHC|*zmV*-Focq-J^npgap6pCnxC_7vA>woi%Ie_$gBY6O)nxdCQllV~6f90V%jeaJXyo>jNB9bo z6TE)NG?~QI7x+0M;=lgN7oR$HKk>kUhav5)8`B;O&LXTW?X3?Jj~pqoU;S>J=;ETg z)Arf`{2zAwiP6!y3vmuIYK{v z{5XBp+dE4}h02*ZheogUooOlgqQeLeSi01a|K)c0&w-^=A0HVbe5v<&d^Y*n#YG1D zJb&pqwCgOZ`uLIB((?p6{nI-LIp~m6i7~h%87vNL>~1>bL}TxafW20Bc003PyhtWw zY>g%utvonlh5ziUK|OQc-i@rN2w1$BH1l+7YF_Q<=R-wnKNdOEH#GR4=}LIp;uK^(cQMRa*^Tk{yxdM$S0!^$P5HSM#}F&ym`4RRd1Dz zjZNC9@piUs!-Lh_)dUku@=4*wA5{*m3dK>Y0Rv+F9`5c(w;X$Uj;pI{`BPR{SXg;I zap2GqtKJkKuU3+$4o%Nnm0LO~cq1pcH1VB*|10mHLy&qL<{H^fF|EOSmD@_xY` z9!r00Q*8xFc>VfF;h%r@%mFQ{U_5|;THw-M>@}v~s92PA_Us@bpO54u+0B#ewDVm$ zZ_=b4elM@Bu+1U_m9xi$8@;MP7=3s_K~XO6^0srU1kU%wMGqlx&00xVm$&}wi{IAN z&YUsBp|P=%@6uCTpY;2r$coB(25^dG%9hE~dqKLot66=@VT>q^+aGGQ5Hb!qi zZ9%Vm_S*}x3JT)T$iSg=oq~eH`t?f2K zUy5s_umY5Zdu_)Ky-`J6p_4d^==(mRuJR#0Pq--h9l6&vv1|Ls$85@?MTzn|3c5uiu57y6uXJsEZ< zdNkj6^tj;KU2~80SUhyVPkQQ`2YNK%#J`}+q9(!P1`=?UAm(bCE!r4PbWEz=ewy{Sc(?QUUTN+SaU19#{2 zE-h^|FpJ~JdYhW+g|R~@MnmEsvBjS6`Gfj|G#@O@sSIiAJ<3x@pUS1OR#NkhN+`vV z<&)`!*$Jf~f_nzXJa_NiyI0a)TU*Ony>`o%cE#qFhOB#h5}CWs@~8ck-^Pt=7wZ2- z9Ko6pzi9u>Z9YSzTiw6bdjw}NO^Z%s8jMPe>2YfG=+O#+Ii!64By(4nb68%to}yt% zQLdu@@UdJ!J=gw~ZmF)w2lIey(b42rb`m=;?k0gS#2{5xl)e1(T7v z8A?ZK9wfW~~u#n;z!z{{q`R5TZY_jN5a~Td4HbbgiX8Y+V7gHc*e$w=h_iFUe`WZ0{Bl=QgXCEXD}X|+P!FAI|R^wq0Zvljlg@P*#;R_l?C z6BwqIMbtQk3EA?g8RWui{X}9RLokcgqh!j8Nakf`iM`I5V<&#>DAf0;@D4(=y3%P= zgM)-{B1u+^{X}xcu-w@ z+L*&C8(Xf+7G1tPj8Y!diz0nyF6A@o8dww)WcEU@slV*U4t|xoV)&VcCpTSLsSxij zn&vWQdJlHS6UXq46DPE}pN*JsKGf%I-QL;1Dju<;-}NffpVsDX3%9Wup`#q*qSRrF zyD2dz5J8;Q>N)2!LU~5#j_Z8gxG0_1-SfBKbVzch?c7BbF|W!V%s5~;v73j72mSTy z*X3=kknycOsK;sf>h35NTJ_7y-ghXA5mED?_m*^8@BMC^!<&L*hWFVa(Dx(z`8uQ4 z;+2g=T3VV{UQ<)Vso~^teTJThe#AMs)ORU6w6=Fh3k5P4bf=d(b8ommY89NFPTApI zNf|r*HI#dLe5MB7Gzhe);|`A78Ih!yw>h_RQqZZTiHFm-8wBm8=H4&kJa4oUJd8{(kAeRG&yl=P{mX`#uu5zd$`CFkmNWV;q>({Em0l!!@4 zUIK_hzqe4k@qIyj&FAPT`W)=GV^i>1ciygr;K%lxe&6bN{>hK)tKPCWLPSh5bEnRW z(oa9Ju6;{WUB6OY>2^}UO-_VwQ`FdP=clNWt%TM|) z%%5IqZ!==i?&KrgwzAK^Bi(K#ZFL!aJx@At(Yrx0<0q^=k#xG`@;=tcsHAG)lSO`h zR>BJxhBS(~(yVMKU$9=BSuoW1X2h`>x9@3|)kP6B`oW%v`13c;;gQlcklS#`rVia#IVH2e9>pW6whFsbWw-COEYQc|+aH%QA#1H#_iT%SNF zDIZ{K%bk6FrFrATVNe^Pl)KL~>OW%k1M|ue(mO%E{dG4l8}87)xnAC#B#(}e_6U|* z%#fZ@KvcjTd3;fp*ZErO{_|-~y+dnA@=QCaE+SpCUK(eRTHM{ttN^JqtYuCJ*)N!P`H^N|-9^)lT_dq-MZTW3A@I)0ISY~~j0rwfCg zPXvFn;+MeL&wxEPz;?)xK!hG3&Qu@pZIlfhD}6+g?i?sTJ8}Gg zsb>xk{r;z?I5WC5)H`V7#JJl~wivrUDu0fzDKT{GP&Q$E34HCI*d2{hg6{)(wDW z#8nesBDtjOw-GTcMO0En)P*ooVS}(}Cc+4Rb!YQILi%8faeIO+CXczJ60IeJ#F3nNXZpC=DQ~(JYkiLMiv-$%k0B+ zYSg*B9<7i*+Bft;+L5_Ui~#!q4bxoAwtc4#e@*>VDWuHdTuHMuaoV7F>)2Mmce$~) z3#~CNiS6b#HPtUr0nE zi}M|s=pqgwFKE(z661>ewj*4S{6X*hCp&erALaEVtQxH>m`5>sjf0xy5^Q(VxOtxm76GJ+XQJ(o}ty3XdMaRo>C=S@Z zjK3t5xm20E68Y2k`do5h-q+VR*EtQuPx>$i6WcpmpM_?gGrx0y@zCe*GX?9r{Cih9U4uC(9x7p zp*2)5D@h(Q$JyEBu)1{fk5@UP$ost(Yo#mIsXVQ#MyO6g7JlIWj*Qv=h13z+`a}9h zLaV>Xu_DhF3Hv)a5xu6r*UUnwvq|(8X2$BpIlRHjP8p;O5YqhYnwr;bDGyckdM)la zkwq(Lwu4;a@7E}Y^fe-;EJfmM#9G4fcX_}6a?ZQcpCP9R;avYK||a7{~Wjf zccsYx>%4*T1q6Ekz9lgPFJDyu|MEpAOQibme_4S3fB8a1vVW+A{pagn|1DF_=g?*w z{Tjxv<#E(agevdltB*HTxOBAlbuL0Fzxdlu-STb%*#U*D(xKDS^YM;ZJFV8y?p7hk zuWxWV)RlVUaj-)Cf2Gcac+({Mh}mm}Tk^P0s}*_Tt;qjD%l+SC?%%?Tzf)zxU@QA@ zq?~uTbLDg;j%82%rz>9n*S$YtNA@y6%zS#N!5Vgzid~h(bhm|VTyB70Kw?_YC29MEb0yh@*&!?5*`iqJ>nKG=b`Hh=f>fazCrj5xJH% z9};ZcvDKYj`ta`9pCfVLR@SWDs|Xgcq)ELt-UaA)kU5|0sRh zK94?V$q2{RraJ1`yHDpY&w96N<)PexkM&w#0wCna)EEp2}$usCN4 zBjhy!jcrXMg+B*RkJ>W!uDFLMDWaHYnW2*FAsmmd;+aIWRH3cTUh7!M6AG zm*&45`3CIP_I(v0?*>p3kI4G|&9uZpX&CuB|GHC zXG&ujBE1|DnlR7kmq&EtN%Z7vI$#>xXN6i`R99Tw6gR4Uj%W#uUn4ijta)hpen{*)H{S>B^npmI7jnje zx0y%! zjQ1X|G``UACScSqz0upgJ->LEp4a%S|5(b5Im<+4x`*iBR$e4M$|8Dmb|eUqSQTmcO*ZU~gpZqQ! z>n;iTRnso(>!c}z_c>iIel8-u`6)#S~jChwTW7$@650O zEdHOY1XU3K+lMpCdGCzX@%!&qd|8v*XzC{g&+`0zaYI{QQqIGZOotyv_KadjXQqAf zoK6cAt-;ClRy7kt(A|(|dpL5#19rkMzaTM*kTPM3T1Zu?EqDw)ov}s3Brc>ei<`Xl zd!~j5&!c+n(eNrt)uR82+px1LNi-o^_aT=bUT=0-F^?Wp9z+z@nHm;YNe}m4n2JqUqq%yqmH2+ha;L$a$N>L^ zrVuoj^-cpGHdt|ZO$uI*&R~Wi*aaP6*FnnE-DiX-EAhE=lm4)MeS6c!JW-W5WecTr zG?FsGUvqkFTG3*h=ATtGudw1w{&=J`e$4Q~;}g>P7k=bi-p79So&B0feCpBj>NiNP1&4K%%N|#N8XM=qy)a$4*GT`b#o>J6!A`J2(^-( z$5^<<3{%pR@;faqtodOA-Je1#7b4@&BHNr1rgMwiizc?+kWT#VBIe~x$;jnnOh#93 zMVi03J##eVA8tNrV8miAY+oYTh*&zN(!oitkKX+Vb0o;dk^pTRI zoUKgO*Ls870Rg|i(hI9!U;f1{^RqXVRwy|buH;}IG{#O*p~-l`cREm(WozUP@Rt4(69Ftzit$%F5mF>y>T0ZL+!cg8J+br-%8?SvRNf@CdAyW z$hjW|pUCkwdhF&iUi05>?=#i%G^tW>OxCJpjj>0B!^v3MtWddpOC~YTTp^Q~Y_UR| zk;ER0vM55Y;)lGN&eMLu3F@UJD8zShVRF%`CzADu<&8TQ<}{Yw#7=Ta0jRZ-!175^ zE|%Yqj0()dsJUQwtyHbM>YWJTdo-nYPq{V@2u{2rZ-V{_3uOGnytoS#mfg(Gb12Qt zBjqy)biX}_4CqHHCGhvwDEb=(rNCtOI_oYMh6eiAkHTd1ILOy~w`77G#Vv%C>flaR z%^rGZjRDVuV4vs06dxl4yvEq+BYj8ol~IXaJC1ge1{qVlloodZ0N*NXf7U}_>+LDA zTP)!$;&2JCx#LiS@;GiPE_a*oyKV5h&3h5kU6I66UzEH)32I7VSM_+_!3oD-U+_KT z?egR=q$2V9WG$3{EiVgv1|-N&J;eU!XHHBxtdNn%{^7x`j{sS8R+~liit`!or`X>gmN+VOd=eWj#u#d*o+`k zUK04Z8JjQ?Yxp`;N5wO7$8-|gy_GppT;z66=w$L|iWPq-ncJd>39_{^0J1g7(mS0# z=1jpKJ%~RFlV531>9+3`v$4J5UZTFM@5e}|wuDm(?CXv{gf&1|Iv7GnjIfnpOn*2g z^Byt?;14Wsd-^Hd#=6BHVb~{AmE`NlM5r^eX|Ok-M;|tWFdh~`j5`(GF#*~n#UM-r ziGmL2WZbC?13TBm_BppVTH*_$lNjj@fa6Ce^MedfMt1l!J>Y+(Z+RXk`;xg-ME_@- zqBNUZA0m<6>SUNX!MIEh!sJgl7E|X~Xu+k$Aaxb|l#1cnA_N|~6nC%-Fgr{C1lF`Q z!Nxaet0ZJJ!d$cg-BDGig9n5NT!_YERH3W@A!_$F?0?k*xu%Hh8!eYxN_clVxll<@ z%zrLhjA^JZrlC%LPx_D-E_5ByhZq*huRvgnU6HyDlr(^Jd4Rl)I$8G=eV< zT=j9v!7>b5OnvyE%SV|U3o>hrA=l}tRss|CQg9++KqR(g2`Zv)$&Ea7qV-RN6H(zD z?g_NkvQ@iGF-o7opWxt6)XIAj)7lg%R`IYqcfPDd3!7OxVg#7pjuHTQ4TcIPkyHAh z8lFILorcM^O^nuFwEVXm#0oW4-jA8AF$gn~mw;~mfz|_aK!|DOyzLke5KjU^)Vn21 zfe;}gq|QqlTBSy%?}u5SI_wgwlZz zXfOQfTi$UKIkx3G7VCDO5GS@~!dX%aIs$(3k1?1>c*^A4wXqq`U}yL<&JOm;r@en@ zpWHc$yRt%zu73bsKjfsuf=-A4GE{w#?NwnO?E9QS_y+2+4Lk*+RD<<5!7{CuKmGkf z2Y~=;S@H@)TmUy>n;3{wbUX|e0frDLx=wooN}gepa#qvgYPwtaj1vw(^PVRb1jkkV zl-<&@_MUh`Zh9@Ypc$~Bi?uut6KGcmM=XCXUhI^RQ1x9)@|tmTwH-r7wqQn1DpI=f z(zYVO3I(sg*NyCT9nAJ$TF<<|pNYbr$pij^NqA>mU{zc9?vjjl+#Oemvs%w&;?IQR z&%ivlB`MvO+KaGxzDU6uKLSpvUorqX=s6Up?pJIx5L0LN-{AC}exHeag``2lJzk~M z9^|B5^Qa-1oMTEk!MY^)m4E}9-I94C585HQoyAUD^myV-zN}Kq+G|fW0jB!0-YJV- z#pIhKqcRmow_I|XYZI5$4dc4%Pblsku3}1%2*>dsMS2?+VvDtf?L^5!wG4@;QdquV zcgSFl`beis>N)OQa?0$ zYTE!;Jp8=WL`;D!0o-qj@HTviP3&N0KX+!_sXbcUd4+Sw1LMv%lsk7)x-ChAvJ8Pw zGWASZlFKvJa;HKN4Dj>QtFbZP;6&zNYuE-BR5)V42!^dOb?i+Jw^c4#YMQ z0&G6=MwW}M8ihbf<|);InkJM27#Ceq0k)(#d<(XsxwJS7WK^SnFsi(xhiPI0p;92R zL^v{$7B}RjxepT)hZ1l_! z0s~qdMrP`A(<8KSNfw?59k5!HXj)RH`a-kbNf61(6tZ#le|RFHh$T;<_;r1#m-XsI z5T${9Szj%fPEsCH#3K`IwAs;;*f;NF9mgv*87#qd;UNl>HqheU$i^h=K=we=ph&p? zP71Uhw$uq%M%A_@I1u@SVE``c_SsEN-RVD>8#aC~<@MDUz?rM%UVAVR?%)7`L{umw zniFejr0NTeHXJf+t|Bt!VWce}Nz9wZOqm>SK^Zl-Mzs=DO(a^&`P)I2&R4Vh0iG;s zPq1k_WO|f2DkT{K|*tGyY|?)#^-Cn9o1SjeY)n@Tzu>n>^{^{&j7vE;D!@f zu4Iu$GCU863Y(1W6g>g_9}n&l^|uf*SPv$1t|(G=sk{>!9X~oDOUFZ2rBc=7{<#FK zzO_A>GijB#^%n57kiY~Dbba!TH^*tNzNSVYoAH<7+|&F}#X2o=XuOHZ8jP`l!qle* zI=MJ43h%{h8i7`RfT%DhtO{G?y>ZXHLM7Fn+`O&G=`?Z(iw^iIu3kgqsx{?r)y0K8-Xp}kbNnfy($!Gw4MSC8$W_I?&bG3f1h04r!N z2u$qb&@poZcP7nJc{>b4-NfzF0{D>VfuyI0IUUEWEvyin1#iyqWL1g}xSKp%^;`$J`K|ciV_@(&Pi}qfU0EfD-0VvL8QM!^PFmnGT`4>oe!Q;-(u7E4 z0OkL+aOS|AOBLRG8Za)R`D;|}wYn;KxUCtmykmLX@zEGo302cqEpdss@C;D(m%@I> zZe$4if>^W-{QYswNCXe&U>+p`MxEj0?>! zO$oq-<^xW0jh8d2iLZ5qiWFqkHP2MnTOlq;NGKpbe=X>Z+6O`%zKWmfUH`+ChVJDI z#{7%V6HJP7P^HJp|F?hszjnm?H>=x{g5HjItVPCBNNO{FMjNd$=%Qsq33%(`d zil*_-HZ*=m6;05UX6x{q`vHONbqUgTg^Z@!e+2-JB`TejDjskxuM0L)UL%5IYeh|8a9X)`noGF!e|3 zt5j$qh5&iOG7v?;M<_+2af$#`wIGj5ex^wu{$*U1+|{Zi9Ej^(qWUA{73Uyy$}mbb zd1Glry!&jBVbAfwg7s&5?FW|;e@d0dROlxpcK=U-8UsiApK~TiFM;RrjQb z1xR@ll0qfN2Wr&dRm+UTGfEu|F1tPXyEnI2icBqB{BSN6CUxaf8!kmNRNrKKAWO;= zoF;Q;8vj-g0_u;)byXbDh0YH~d2sD5vPXv7m-BjcHCN3_i>rdSyI4fhrF!<5L$X(x zg`3&+kr#N?SXcE(_Z~!{f3;iq?8Rle_Q{r-x}d^<}wdd2s`Wk0>^aN0Klv_u{$k1o&;Go^=9gCN~@pbR63_su`t0)s;#ZK%0jgi?T6e5ZqkxYn16Unz5cDsmxtuM zzPayvGg5X=mIb*RRaDVwdTV7qQ31v6<4no3@25kbS}29L}jq|D)pJLVy;9S zuyRC%+;(m+y+!+6oAb&ZtsC{EIFpaxO;7O3{)bK1n@F&n6v>E4Dr@p?7}i|ib}~jk z-UckEWj}H`6&beKjFIuTi^kn@4-j+$bO7WuQo@_Y5S&F4 zZcS@WlpmVNoFsKb>oJ&dU*CyOp#3el*;oRXr9*gMd_t?$qn?b`@-IQ)!?^S9VKLhoZn8PEy zh(WHp$P5i4E#>lvd4#_<(kr(}xfEK0K8^m`jFjxBxVrFwfRN7M(r{F$0E`ycu`9^p&8U# zFaEqmOR|2y6!}9Sv;A9VIURSFS3z#q(0o(Y(m$o9tZyH%+NN$hVIoum4GZk`1OgVB z6+Nqa{HcyU9kmj7!|h&?H);YoHDBDW&4~;e)qUenq!}R%s7km%WLeCiq<^uvQ-9=$ z`I^)PzwYWf+-hmvS21qYcb>5Z^i%KiG%j{QK+tU-{NyX`8Ml5bXd`kM!iz44OZHH$#M z7%W^vV^8xwJn+sq$5r)`yRtr7At@$a6ez``67UvG+-@c-^>$OrQes>n)Xun}1p~}! zlH=^an2b9;M@bh!?a-w0??fOJa+S*w?>#SJjLS^%^B$WM$K=h@iMN5`EPtIe^873> zGNtYs!mw@?L@(AOG?x=kr{(gvn&FH}x9G~;AK(e`G@9?_R`v+Jso&a~(%g~6wx(;u zlx2QSWB^_3ERnvR?~Do2Ni7K|FfGsPMp&|Bf#~M?p$U{uc@@MMf?yr4QyFHY5}`Qy z5uq!hNjC@Z_ik>9u7|@6QKXe9B#+XFx0PNEO;|yF6K!IphtTLH(DHKKdln%sPzTb( z;Tzuan!E5ta7_6Os&L@zZubgt(gX8_#h`cxgccb?a0zQxdscZbWo|CP#u@77C|1qX zlx{*k?i+N}!Qa1+ot1NPps5E1ddIpxD^VgGHTHqhA({@QU==kLk5p4ha15o9A`~yj z!-?F#149($tJ&41WYu}G=Q9e%&r@97Y@7+CvY9E>1Iwm<$ zlQt@ViG8M73n5z}^K|TeUm{+!tl|PmbAhAiWF6430H=xfEDMWJ6~WD z%<>7bhMEMr&YxW=HwqWJe(_+PJ@|)KAbD46(`yb{ADmkBMdACR%05{_9gBsZ@1YYC z6cSB*B&y)DlCm062m~^|nd~DLLS%1P`!3;fRLmQ!uvV7@N-2*9n!5hmLJNl0`^zA4 zG!U1&VURX(hn^rave@fmWN1UA)1$;5xUbhL`>8gfW<2!2ady5$QpeDcPmR;pR`8HxPNV*VI;74#!a zF4_5m!Z(4**olCB`jv1Btz`aT#VOoW*WaR@|8&*IB0t^tCbnX)2a)wP3fW|@AlkU# z+Xl}rXU|*hR12C=i6(ZSZW!rQ0X+kK$b0`ZH(vRw)ZV18a&w$8T3Vb4(=22KytRAT zsb>{@If;;aPFMUaP9cAzkR8&h^QeddRZ(?`7cc`9RBSi8>)+ez0uAe&~jzxkEQ|( z;YF1O1T&vl47RcUnk4uB)s_(0eGQ~*;feIFNe`j2=vj$juf(N=)x&9svy)yrgFo%! z&YBi>p~c>4G|N7_1r4D2(1@}dA8ee~U?s}T30jZg~8A%$;H`wsb9 zZTtR0XPCF+TI3$Di@tCBld8ob;;s<^9yaR#d(8P;Qd!Kbj z{AUrw>s!4qwwM<7+KXF+W!qliB|8`Ov#_ist4@b3Ae)52c=!hJ z6!P4TkWoaFJQF;)&|tcKs5h+q;gKXO1g%@-Yi?KNLNtVm{)8zR&f-kOW3|?28VTa=Xpx5)Z-bZq zvY=;viHzs01`=SloAK3b2ZzudlHf5&JZu)P{VhW`Yq!U%80&UW>6}zq9#3?AL z&QrHF`N7(c3*$yL-;5xp1uq=S>oPtCl4PhCegY$8;X^k~2NQ>S;7QP0i`MA0Ev`UW zrc$c_+Q)+mz&0)3U{lrN!Sp~Z9SCp9&f7`B`|iMcLQUjRvdl@#Rg#n`D70j|0bW7^ z@(6FLg2RJoPT3PuR@?BZ_+c$4*R>2Swnd+~3mGeu-D)zZuY{xU2>K(c9Y_vZJdNe= zM*CtT(Na1G5JnO3uyvKwYVDbn`%%l4cp}z@!@#?EwfF$&0D5R3f&FkTN(==4a~57r zj3rEv^I&xWF|5$?^ySL-D0NX8{3=G<4P9v~R~mPWwtQF^v?Jtlf>3`Dk+$kJuN;Ed zXSfQp)H{c(4LFP%jwM>TFv^10DX~$d%GRJrr$V$9^KyU_-X*Hqe>rq{prrx4eFaXk ztq)DG*@kYI5CjdL2Zkjd(ORS0_kewpz{a43CKfxO!heGIfrzy4$LlwLVWgEn%M;pL zLcN2osSZ@ij=aH^>OUX4g$S1D|J{tW{tRl*9+h*?!NYfQ#-U+dJDHq&KP#D1@(XqLm~!9 zEJtANxBse1=s3`Q>NlPTcE1E>Uy|D?c*{~YCt#hCd}wB<`C#tTSB?XpWC>ciyT{Aa z-UYY=w22UZ1p297LoV1i5r-W0e2}?G3h!c-(qYV`@5Cu~2BX+GkQ~^Bzd&gT2`~#F zi*%K5!fx_ZcIAA=^yMKKis$Q{WY}nY=s6wC@}Xk)ss5ntl#ukq+bI(vJ&d+%Vdtz6 zXoi5cvuZ6*E>czo&{jqaFF{{W-V`Ua1k=kzj7tmNMXM4Sxl&p%;kN|SyH62ckegC& zL~dwAQ^;!{(;$O#%PqNH9cIC^JlqA~eVCSD=k~S5+Z=-99Vr6)DCmTxvwYfBd%$pF zJ-(5rn64xOt>;0;20dNYP@Sm_%fek{Bml>j2Vel@1L{Bwn36oaWVJc|U zrYLVOkGe&nW?y7z&ZX)Q>ZUeEI6c%#R7jP`QQbqSV=+2*+w|GQFJ(JdC#8&DN(Oyg z$o6T_+DST|ht|Qy@`b`Rv_aaSrIrB81(bD7a&<6As4A0}B8w~9&(|+@Mqf$!nbGuRGwy%wKzP?gTJJseT8pf#doFIX995w!N;JTd&TYEonH6^Vz~fLR=&V?jJ9>p zBFEdHDS1>_@z_d59EL!EH~;O$96xqOoXnaVTXZ~Qz0*E=4mcV(=dZR&zv6f2Y zfv&^Zf$2~u0h`3{Xfclk#J~d<^@rmiozD%(pwS+FpaP92Bw#I*p{xgPHyuWT1$ETe zl-oyeL2}0VV}!9{lwltT3ou39YX!6eqj^HBp$DqCI95{HQXY_l$*t|@1r6$^?+1IY z&1XW?4zpdx?1h^59B8+2gflqGBMQ2NN_W1@5ljidl|*n6F>O0f9Z@p|y&}}(thB*o z4(%U!14SC>A3&RsP;PdD$JKdYhTz0UEVQop;wspu)BWRd z8Lr1u5hpN)pp9w>M6Iyv0a{)O@*X9)r*y=`+7nGx6L{<;?K|Ryc1>u*w!U#R-uc_P zkX#8%LC>h+WE_u654!2^yIrX9oBpJxdq%4zr71Gxkh%0jP@8#r(9o(NGWAVB@` z2j%lB&W}oVq<|GGYEH1}gf|Hjar4f=@Fcy7$$8tuKC=(k>30Lj^?z+lIE8n@bVv;$ z2t*oIxC}CWK%`PwZy5lzjTu`p{qpI=O<%|!qlW?Un!g_;T+V_#)}x0@;}uW8+4;;d}!__z*(>}4XsT)uD zbw`am)I`uzg6M(7_D;e?f$kFYB@)V0f&c&?Q&~Q(=s`eG>f}y&M^+xLp~_K!t8jp_ z5UE#Eu=Cw?Tn6-&eilK`vl3Ds@_V7_T(*x^7b8G5^aur%QHoV<)0V(p4pe5uUf7|( zVy&MW0Ab^TJ=zlAodS}*B%{^J@mwyR#pZbztcfg2_R~afIXA}_%sFaygIefgewA$j8Hp{EvIiZvc`K=I%!nh?jqiygkHVnR-DxL z2Gb3uk3>t@8u14OL=asrRH3ltCW6Qc3D(xtaO~TA2Q3(FvT<9g-yoSdL9inMJv04gAZYoD-3Z8P6X&Ly~?NX8zgBW*Ts|`Ym{}fO$RCqDULJh0jgpQ0} zUcY5yT_5U=95<8rlsSi(J~tI<)zbAlUwBH+IV3ay@^U*WGT=5 zVOa%~sT*kY4^PP{v0baH;KCYl+S#c$Nd~y%oGlyIH6QQk25)1v?%EloAv;Pb`p37i zf)`KR?Qzro_{p*q2Q4@>9=B*_T?RwE;b#Iei|vBt%bxS1H5^X?zVs&b(*NPAYyAzA zHE^XzK&}dk{m+aoDL{{L^i`P#NHupBDy7kYZ-QyQYnj~8Z;%w%(9*S-PqXP%3jMsGGP6~^LpX?u@m8Y;Un!8=XoSB&SpWxV)@(W{v>JkgLFB&!6bO!G zu6j)@37RhNl+wn*)H^VswA#P-zSRhBXXwqIAlgYd3xya{W}VMu8-X;I&1ZmoibswQrck+ z0eI?Ezq0{cZI$+;S})^h8T>-^7tf)KNK3!o^=Ihy?}olJ&A0z+!YCGhIf9hMou7Kd zF@)fv5NB;Y^L&+KE8&8!v`6`3+RQ}1@vdvO`&GY=;oclEHax$!;^QjOumud!vmMV_ zV_TaU>q%or3XFEa?>wE*XtcWwW)R*7VAfT(Hh6HN42ZR_gz)rJ?}9N#Zn|IXhXK9R4TG1dkllhIu%)Ch$v$0MEs8Kw_Ep9pU>ld9*@uc z+{fqftG~{p^Kj;U&il2!U(53uMM6#KEq%sIgBRG-4{wQs*L~4h`}!mSusg&!l@`%M9zI2K-ZAT!k=F6PM_uKl#p7X8{(!`@vU5up!~tI zR{fzu^!am=KVScUy9$Ndw4tRg7@CcHU6KH!);1nSNOCx>Ss#Jx#HS(lcluXG-@8!< z6m13WuonlEh<8FU_pN?ixdzE3Rt!lRYy8}pR zzT;D(hQ-+u@If&WT*;~j)M2hByUA%$e-YnIeg81F{C!5iY-JX=?`TljwKbGg5Pi`D zv+LNpEuon4+`ZV-8J%{sb2F0X{Ah|O>#>QO;pEBAr3EjwhHt;;@Y`#krVGgDB(0K{ z(5f;|$*hmQd8bmUysDfHv|dB;^6xAR6lJ6rs^7ub@(yGw=N>827x_J;T=xEWcLZbZ@c%H)@8_(;PaFD=JMz`j{cvx#tf8mU%BAc-sPy%2Y0wHMn;dpf+xTq+ z2c6o$-rwSj%_uunuU1i3Rf&rE4R7nKz*3YEmfLL8O9BH>nfY6{?xXI@f`~&^ z`}%L=X6OA?j7&^ab^XR8Y?|-$f-mpX+ssZ4&19Kyhy~vSMN~M!-felH^TID+x0SxV zZ;7iOBZ+qcv-oK-w8z?*#{xMK{|+p^Bu+Yh-CRtfN!^tuZPZywF3+*yV{~8d#^zB) zRTkza{I>}A_Ejn>q75hn7e&T($!<(u$RKYKJLS$i^>k57(_WkKh)L9w=Sxc<2yxfi zXrAg7V`Lw7gnns0>QQIb{X{w~=#)f(*vVssyLK-ef9IsUCBzSxco}D_c^F&8o~}8z zYJww=m6@+T8*Sxk+;Lh3)9<6I?BOP`Z}EV7!%_ZM>Nc&HM{3w_KA3<@5)UpMjXuSO z$G1U{uzT%#E12Kqbqpjanm#lKWn&sd(s*ydJ;J@@+!=tWX*t# zr$KkYsW6+ioJb5|rKo5fM15MXeC&lT)$Dp{w9sBDjkeY}mBGQgD(r~~)iVpT*Lo^K zPk`H_l5J+^XOzA@4dX-T;bH(6+AKJXbno68Lu2DjhAu8xMw*2y^+-F>&dx4Q&tIAn z8oG|rbk76h(p6GVOG~RVKNw#twP{n0`GGdI^o6?03Xb9227BTgL1MvU7e2;JOCuJs zHy7I^%%mdWr)--ZK0Cxl7$$lC=Cq0_9AEVB(}>+2Pfh!OQnx0Nn%aaZmTRT zjvq6pz9#x~;}PveUOJf4fca`E8JQ~9urq^RS6AuAW*MiO&ehg6WH}7kP@|NR_YCiC zy|jc&ngd-kt*ezsef4g32{b9&F@cz410QQKfq_z)T`!C??9BO$?0f@yFqF6(yo4E( zg9U(vT&v3Ar0%niCeS$6Gx9Dlhzz%SyBUYEN^EarFU)2)aNWdcw*eR+BC;O5e}4s7>@6-`Jb`w8s~kf8NJ6c|i!WMa zcBvZeIH#hiX{pL)yWyNW@?6z;znwch+s#yT0~hRC<@f>u z#E}{r^3pyUW&H}F_$mbnE(1xJAK=exmBC7vsWKsF6`Z+Qc~!?Vz_%i z-W%?i9R#VOkjmlYj%xNmN9^-oNa6ha&G~k|e$)JLs4z|mPp%pSfNS^OR^2OqJu5am zlH%hKz-FoVZi5Ib=fUA&XS!(=cLNNF?ikVaMOxW!Ije)cb`2}LQyhy)7Pp-pUs$t0 z1@E_VZF%L2d--_#&ivTE=0mE}rQnm}y;w_4KQ2B%`C&G0@YY`3nLzj0o{{P~#N&?( zl@dh1Q!xZiP5LqmyzYHbJ-uBJO>GNl0-*v2vd$pJ^>E4oC{8~W=k8KTIb}-{d^$Oq z4|IhR44Q!p8`@xUa&ir{j10u#wt~=}nyU#!j|}Uu^rzX`DmJazM-5)>(*;w0kBpq4 zmg{QW5q|c#JLi7imk?R57>fd*tvq~~1G8d((hO1yuSrDZ*Md_YbfhrvONVDqnX!@)X^P#jvcN|m8(m{s2yE_^|ilR z-XYv|SpS*ZF$Y0LzC|4F0#a)un8UXSw!}_S@^#Dw?m3?c1{dg*r!E^3z}!Qjge@(J zJ#WvgQ`lC~dFGaCJVCK^)m^Kkp8DpObo(+P(4gyaaGKn{*W_RT`G^T3RamR}n9~$t z`5BeN*IM|%p++~{fkMCXG;P`XBXpCZ*={q(-3Iy$JPYNs$B?R5A}vS+#T%=1 z8Qcd~FO7A1QHRc-?x7exl^D3Vhp3!J3aKp?9l$ieVzq*TK7n%1_=joh$rp+USLMJZXAtXsGN7LQ=80K>cB{Z}r2L3#f#xBVtN_4;e0m|m=1uhxaI`xpB(3B6 zKrMY?NR(pjiJ;V6tpc@=m4iyjWp=F(2e=p%)ZM9z`<>QPsz<}XVRVDCdHs)oARW7nhV5KleCv;k4tEJ1+)o11><&FTM+TY z)gHYp!y5U_8bAafxISgnp}agtT@O{zt7QP)g|ukcDl3}(=-{+qnyF$bqF`xm72`4` z0rIl@ERg6pX~i`9dtA zelyBc9p4tI!n^x>$8SQ@FCZEZDf%WBpKHkiWoInKWudDAf;)1Y)m;M zY+kjqaDrshUHS%0Pv1aqNRpdPB)@wp%=lE~J9ZGsWKmAx5tP0O95`r_YO{hMuEn&W zgz+WW&B||hLK_gRrln3Lldsn%*o^ghVcrp}jj!}XhOcHk_|SnJuJeMB2=p*->kn~T z%nZFbL=6d1MslChr^faoK}Sa)6{mH^s+CooxVrd95j|DsTZoUu=wuPJp%(Ijg$0&R zOa}@UKlrnzxr|vgsyayXCo7t=D`PNa`azYz!8Sl-=N5e>pwBw`^nnBG*a9aHFLK`F z=9z#cACf6|n`N&f9+`sGd7sL*XT5#Pp@emmg7KeFa@Q>Ln~*5jXBt=j>Xn#gxJ>xw z#3M&eV(|}O$8I`;DsHT)L6mF{oO=I&#TT^q3ItAmei-HN&jZF%kSM&Pxsg$L@Tc>N z%jz2~2O$kR1?h(Cdq*t50P}(CajeCO0VTJrVa0QMe*HCoU8n0rLac?IGdy$%hh&nT zzAi>%6=2<^0giMBq{4=>pe2>|xvzRkf=>T%VDHYACt6%=EwedZ@OR|c%fp@84sF{e zU}$Q(4(?u+e`Zbu*{XU5BCMs}zb}n6H8o}5huIBydhJ4}x1YhRHBBTmd-B9bLM-5% z64ehKTKiGV2$j1N$M$c&T3%$o#`n&`SSc?ZTOA4-7?c{0DLET&ywRWQnwex@Pa-XGV07ZlK39;)cN-n64b^hsah-nS9aKT z4@fhuUs}40TiZgWAXL5e=?y~t@3!vkULod9yS6Z+p`=QR0eE0KC$dfoJq`5DI?4%m z#%l2hqupL933sxWqP*Dx+3)fG;gTOh`%)aAcn6yaT5ucLYPMLf&<$s!eb1wuyy(|5 zIH>Yrd88zcJ*f9j73V5d4=+gIXkk)np9sSQ^B}TisW_a#i z<`UUkSc>ohNaos7>Rm>8!kX;KSwL1?fkWrX{hV~xB*=3jI*9P3JK*Eob#k{-SkI@| zI&>Idi0qU5DD~*(%?dgh?!b!pXoF-X+eKsUrYK0!Q6reeCA5|$qzWkTQpJ7nV4^gr zyt$RH%72!PWkAYGezQ^uBfAlSsg#Ytx;#i?5M6JetYSV*$iIiBY9yC9@=q{{n*MX@ z@Ry^mtO?NazyY$?%06W?h@KhAOrOl~Kx4OT@Px+j0_xm)DvhZT5g24Zz(y88CuI3Y zR9wRNmOZvBU|R$eq=GiVHQbtC%CK(SmnWyyY}goyapMIRK5-tnO0TRqcWEbKk&~N_ z+!q~tPDT;&p=bZ@-9*6amXdWTET+i&gSzH$T|F1KHEkVYsK3%yKW9 zp%ej*rfpm^*mDLlCM+8di`|{5cEvK6*WTw;UWueN*kypIhx#*C4*PIA&-iZM-*rwYHbY_Q+ zz2)05NW3`A?s>zyQa(Hb+6V57Txy^Vir2g4uD#In=Jg5L`MI8qW}+U=RKxjvxers| z3=5QX^5#(o@E_<4h~Y`f{!JvL2%dMuY{pgj46GquS}AjT-UJ_;X~=(vssokCAYT}L zfJH_V9XUPufIJw`%0d`@7r zt~Ab?obQM=uL}wq67ao>2+P7SAf?5GM{2mNdrICfIlATr(ur*wl%e#BwlE1?3^Fi4 zzO^Q56NUM!i#Af?3h2t{yJy8c{W+B*$w+^^!i4bpxUQd7`r zH=dGq*d5_jBQ&xa-x`%G4`f1Kz_Q06zH#g8`XqmzM5=v1UC{*7bt!M(`>COP)&=nH z8{eLaebiW`Zjxi8LD1rk{Y~VUQi@uz_O5g~h5Op#Cx=-sH6Bku#Oa-8`q3ItPm;xm z3Ic8MyfoFJ*QoHMGZ53ftMxCYqr$6K1I2HqQb$2)b76AK1SRG)LyW6^TZ$c0SBm|P zK;5p9p{o13M5098NKfHfPm9$y7M)?hesRYAQ;3ojUcQjhcU?b#d~^8OP)T1(RTUBit*no(d9NIFMg?b2EH&ENB6ftt;8|PDA~b{p`d5+V}0^ z?3f3*V~0EI>T22~uQq?Qz(6pvNLu;~Mv5u3rg4_JW zEn^!5SHR63EfsGd3Oo;7h6SjfkLf`jRQE5E5%xvmVZMsM(@o}2rYo+9P-7kWn03cZ zcdtX5$Gz_0IB;iSe#nv9Xf{fAe1?tLRgop^x0+9V2p!+N)xs#ZDCn+*Iyvu@*am&1 z2A>)~ymw){&TbJKja!y7>RrT{t4mAUJ0+Tyj7BI!HnIjwy|}2An)6|enUhb;n#Pqj zB#M)LI*)p2I6dOYnGO2o9dNo)_lB=bTh~&!;u6u}vPY`kp!UP7iut5QAUvGp+%`A) z4#&8&s~C$%fj6n;LBYGZ-%F8W5-2g7Df?qp4#zQwEQhl<8gGFGmwLPSSjme}^b-#Z z9Mi!d^gA7iz&S0m?((elPx;fsxMT9zmi3wG^>mKT(})+WRCr?cD{{LJw=X|Uq4Idv zp{zoQ82LrPX;JSF(Ym35)j+zKjqS}{NoQ9!AZ(GZ18BdF6Amfh+cDU&d03lMTu@F< zZ&rIaE)k1V0v?PjD>EH5-4RneOpJ|BA(_Cl_D3DiHZMv^k{Y%G53+!h!mW?z`Qg`| zA)Q@z?v#Q%vF#TZn!!Ou^*@*|29&!<-U6hxUhJ68S|J`VH$^lvIexqbgu<4mNu{ML z>3Zj*sP^Ni4`fumevtnZBJeK8N$LHAW*puB<$=OEbG{LoRb0ay(%@<@9F&C8W4PtW z2?kPt!A;Ig343>rl_%QCYgH#3K&N6D7iFPJJ{75Zw)j%{ty{4+hpu4Zr%`7Qu3Yax zK7d&~G8^Iq zWZ-v{lvk{LT~Z!R2rLO3X1o=+yiE%e4JBru_FG$OQjm`VOZ+2y0uL)5a`U^=x*P=e zdDhf%+>;Kvu`yF#olU5HLcpk{@EjcfFfu~t7aOi(wrTP9?;Y<(xUB8z>9GyJz(@3j z0!7AnzbMxC$`l#px*Dfz-Gu1kAPpyo!m! zTvnfP3ZHw3*Lcq{ilDotQr^{-t58zYI|p7}7# zgfeRj3|Vs?PjmFNw6+{QWM#FwBz>a4)9VsDLsNeOV5YTQBDG@iTEkJ`W;pek68%8-Aizp)% zD4R)B<(z9F+H14m+0iCPTkW=70(OLmt#@{^{YQ%uzy`+FEh zf;2`Se*Trs%-PEcPIwfs_;G;@^W32cT3_qn)2|oxBuW<*KJ|Z2=;54xf~w<_$bQdN zDC*r?O1m#{ZV!_{qRoC9n-+qv8=IcO_5teySwfeT;RIYMEvw0NgD6va{N<+vmf=RP zm&Y;q)R&YzWO#xmke@dT7TruRFIxK8tI}E@L^_6ziNa47Ck$Zm9RQRq-+Jjf9(|o(t$aKHStjPje?TGQ~1m5J+b<#3HLxjU=u49c}jS{Hcs%=EzfUZIa2U&bxO;eTybO_sJDKSh`HglP_QID_9tMF6i4Xu2 zpqIfNDp_}iY2|O8c>F|~3R9HiFhK~LAn#{Lb?|B@NmimlxpOp~Xq;+;;~zTEU}|t) z0BPYyO3H<1Q7GR_AG(l2R}>{EDtA4ehhRj%)@C4N+`$!q?z%Vak3{K$iK8mH`7OA~ z88i5(R$Z+L%6fDrp$-XK$DLiGD|tqf7#N6Tysg)Hx`T1&4wcSWbb!YucG&<)@b&2ne#&$>MYsx zUXa%a*XJ4sQNDmO{eZ&f1FsM1?kyr;f$}Ltb z`YurkP!S8Z>d64vV&S47wYS>a?PQ7S4x;)_@KcuNnYWY|5`zH#<@j09TQB;df<7>_ zL_r1Gy3~DkKMi+glKry`gbfm!-E=Mvb%vBx=G3S!x!AL4ft7k;N)v?+9-l&H_se%OyiS zT=mTUU(B6k>$~Xa;MiIK?bq6idOX^+$vFxNq`i@TK%Vay1v2-*c}3`^>L_t( zuKON%abrm7IRqBK7iLj7uBSVcqj}=`y&%%Cxk_TDJX$y9?F!1h6d6hmzbK^uBgvrh z>XcUpBh6gI+iRs1<*WI{Rz7uGg@X_30!&h9Lj0?|Q7>i`)QMU)@8oM44NCUHyHQ&T28Scsl)m6=Se6%e3<6V4(_500-mop22XEcwjpT~s;h+hF%gkdB%f(X`su2a zloYvENeJPx^~jR42LaCg{5b)X^1u&>8=0I>vE(j#bxGo+I(7G#qOsv;;0^YcRd*}G z&l)>AoqPc6I?=)9Ok1#REuP_L^DXT!+A{jSSrm)6fCake$aYDfE(FR^P%?i1*vQ75O@MM~I3sJ-=w>@$|efwII?urvbojn`R>1t1`r z5feF~e5In2IeJdsZmkpkgySV~cvP-5oKdy}UccUHPkJy;SEp@_5WV+KKQ3T)e5Dbu z6A^hOR_!JaiL#j+O4}n3PA@IYx=@Equn#W-C*0XNlcpRs4h|t8})x#9{!A z>m1)re#(81xGTzaX@N)#@(w2SR{0^1iL=xNH`!2&_q?Sy+kSGJ628-{vcPR}G)ukN zR{43qJrUNW!2U3@hH5gPq&nQ~12PUX%kRcadC>DyIuGiJJC+1HkY|RqDuDPKNH2E+ zE>C%>LXE85$j280(yrMtAJ0k}1lp219fPRk!=y&5NjD^KQ=*>#HCV{LeL~N21k}!V zw`e2~EG_wUonSeV>Y`C+0s*Hio4N5_^fc)Q^wdSl0*^a-a4o{!+IIG}jKnq9Em}PKX<%m`#@je&XAINW-ZL0 zxrtV@52!gqYwOD}Zy7|+4cY@yTC*aJ5T?MQtttwov7dN9M1HLxuN2{+2_9cWdzE#x z-cs%?$PQ9l`FTEpse-`IbR@LwbZoskOv*73f6ZNaUo=v;*ATCB;|k%K#swRbaD9fk zPLy3%^#su<_0sB1V6|KTXh3!}=maDRxQjQPy4%Og^h%;N@1&7i-$bsndtIa|Py=J) zFNB7!F*K>Q%dt?6h>3+TK-JX5$SB1I?5^L&nxIBSsMBzn%})1A6T(wN{3>5%89{cm z;WmfW>;pgz%ov=>-+=wFfPQ#|N1N?oohM8=Xt@?5&^>=c9gsTi#9%u$iXfTi6q)yL zJP+m+;Haq?2Wi;jYgzR|IXRcW{rjsyc{8i!;n8K#%`eSxj5Alo6$m&s4bCxKBk-wvBzf^<3?&970;=6Be&QSm5ls zz;SRIO3<95l1mbT)GmDuFt$PO2T>J%MyL69;h|(w>f*LSwtAMaB_No`FJ!sji_n+n{xzd4f7ibM z>0SD%lmA-c|Nqs=<;mUe#<0F<%U5&!KcP3vSX}?CJN(=3`yL4344UA0`8}laKN^nz ziP!!c7x{TU^Y@;rKRTN~_#;hdM12LsAN!>h`JbxeU!aM9+DiUAzU*()o1doqpUTeX z8-IZS{f~rKf161D1$y(-l>hkf@J06j^EU0LDgQ_3|26vgeD$Aqvp-Gw7jgcvu>UmW z|8

hV896O-NejcY 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 diff --git a/tests/geometry_demo/screenshots/geo_02_static_pathfinding.png b/tests/geometry_demo/screenshots/geo_02_static_pathfinding.png index 833bef1ef400c23591d9ffd58422c0023b99dc7d..55d8aeed7d09c144b4c3cf93aa46f62181ab0f0b 100644 GIT binary patch literal 62197 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