fix: Refine geometry demos for 1024x768 and fix animations

- 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 <noreply@anthropic.com>
This commit is contained in:
John McCardle 2025-11-26 04:54:13 -05:00
parent 576481957a
commit 51e96c0c6b
12 changed files with 497 additions and 337 deletions

View File

@ -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":

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB