McRogueFace/tests/geometry_demo/screens/pathfinding_static_demo.py

293 lines
10 KiB
Python

"""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))