Squashed commit of the following: [alpha_presentable]

Author: John McCardle <mccardle.john@gmail.com>
Co-Authored-By: Claude <noreply@anthropic.com>

commit dc47f2474c7b2642d368f9772894aed857527807
    the UIEntity rant

commit 673ca8e1b089ea670257fc04ae1a676ed95a40ed
    I forget when these tests were written, but I want them in the squash merge

commit 70c71565c684fa96e222179271ecb13a156d80ad
    Fix UI object segfault by switching from managed to manual weakref management

    The UI types (Frame, Caption, Sprite, Grid, Entity) were using
    Py_TPFLAGS_MANAGED_WEAKREF while also trying to manually create weakrefs
    for the PythonObjectCache. This is fundamentally incompatible - when
    Python manages weakrefs internally, PyWeakref_NewRef() cannot access the
    weakref list properly, causing segfaults.

    Changed all UI types to use manual weakref management (like PyTimer):
    - Restored weakreflist field in all UI type structures
    - Removed Py_TPFLAGS_MANAGED_WEAKREF from all UI type flags
    - Added tp_weaklistoffset for all UI types in module initialization
    - Initialize weakreflist=NULL in tp_new and init methods
    - Call PyObject_ClearWeakRefs() in dealloc functions

    This allows the PythonObjectCache to continue working correctly,
    maintaining Python object identity for C++ objects across the boundary.

    Fixes segfault when creating UI objects (e.g., Caption, Grid) that was
    preventing tutorial scripts from running.

This is the bulk of the required behavior for Issue #126.
that issure isn't ready for closure yet; several other sub-issues left.
    closes #110
    mention issue #109 - resolves some __init__ related nuisances

commit 3dce3ec539ae99e32d869007bf3f49d03e4e2f89
    Refactor timer system for cleaner architecture and enhanced functionality

    Major improvements to the timer system:
    - Unified all timer logic in the Timer class (C++)
    - Removed PyTimerCallable subclass, now using PyCallable directly
    - Timer objects are now passed to callbacks as first argument
    - Added 'once' parameter for one-shot timers that auto-stop
    - Implemented proper PythonObjectCache integration with weakref support

    API enhancements:
    - New callback signature: callback(timer, runtime) instead of just (runtime)
    - Timer objects expose: name, interval, remaining, paused, active, once properties
    - Methods: pause(), resume(), cancel(), restart()
    - Comprehensive documentation with examples
    - Enhanced repr showing timer state (active/paused/once/remaining time)

    This cleanup follows the UIEntity/PyUIEntity pattern and makes the timer
    system more Pythonic while maintaining backward compatibility through
    the legacy setTimer/delTimer API.

    closes #121

commit 145834cfc31b8dabc4cb3591b9cb4ed99fc8b964
    Implement Python object cache to preserve derived types in collections

    Add a global cache system that maintains weak references to Python objects,
    ensuring that derived Python classes maintain their identity when stored in
    and retrieved from C++ collections.

    Key changes:
    - Add PythonObjectCache singleton with serial number system
    - Each cacheable object (UIDrawable, UIEntity, Timer, Animation) gets unique ID
    - Cache stores weak references to prevent circular reference memory leaks
    - Update all UI type definitions to support weak references (Py_TPFLAGS_MANAGED_WEAKREF)
    - Enable subclassing for all UI types (Py_TPFLAGS_BASETYPE)
    - Collections check cache before creating new Python wrappers
    - Register objects in cache during __init__ methods
    - Clean up cache entries in C++ destructors

    This ensures that Python code like:
    ```python
    class MyFrame(mcrfpy.Frame):
        def __init__(self):
            super().__init__()
            self.custom_data = "preserved"

    frame = MyFrame()
    scene.ui.append(frame)
    retrieved = scene.ui[0]  # Same MyFrame instance with custom_data intact
    ```

    Works correctly, with retrieved maintaining the derived type and custom attributes.

    Closes #112

commit c5e7e8e298
    Update test demos for new Python API and entity system

    - Update all text input demos to use new Entity constructor signature
    - Fix pathfinding showcase to work with new entity position handling
    - Remove entity_waypoints tracking in favor of simplified movement
    - Delete obsolete exhaustive_api_demo.py (superseded by newer demos)
    - Adjust entity creation calls to match Entity((x, y), texture, sprite_index) pattern

commit 6d29652ae7
    Update animation demo suite with crash fixes and improvements

    - Add warnings about AnimationManager segfault bug in sizzle_reel_final.py
    - Create sizzle_reel_final_fixed.py that works around the crash by hiding objects instead of removing them
    - Increase font sizes for better visibility in demos
    - Extend demo durations for better showcase of animations
    - Remove debug prints from animation_sizzle_reel_working.py
    - Minor cleanup and improvements to all animation demos

commit a010e5fa96
    Update game scripts for new Python API

    - Convert entity position access from tuple to x/y properties
    - Update caption size property to font_size
    - Fix grid boundary checks to use grid_size instead of exceptions
    - Clean up demo timer on menu exit to prevent callbacks

    These changes adapt the game scripts to work with the new standardized
    Python API constructors and property names.

commit 9c8d6c4591
    Fix click event z-order handling in PyScene

    Changed click detection to properly respect z-index by:
    - Sorting ui_elements in-place when needed (same as render order)
    - Using reverse iterators to check highest z-index elements first
    - This ensures top-most elements receive clicks before lower ones

commit dcd1b0ca33
    Add roguelike tutorial implementation files

    Implement Parts 0-2 of the classic roguelike tutorial adapted for McRogueFace:
    - Part 0: Basic grid setup and tile rendering
    - Part 1: Drawing '@' symbol and basic movement
    - Part 1b: Variant with sprite-based player
    - Part 2: Entity system and NPC implementation with three movement variants:
      - part_2.py: Standard implementation
      - part_2-naive.py: Naive movement approach
      - part_2-onemovequeued.py: Queued movement system

    Includes tutorial assets:
    - tutorial2.png: Tileset for dungeon tiles
    - tutorial_hero.png: Player sprite sheet

commit 6813fb5129
    Standardize Python API constructors and remove PyArgHelpers

    - Remove PyArgHelpers.h and all macro-based argument parsing
    - Convert all UI class constructors to use PyArg_ParseTupleAndKeywords
    - Standardize constructor signatures across UICaption, UIEntity, UIFrame, UIGrid, and UISprite
    - Replace PYARGHELPER_SINGLE/MULTI macros with explicit argument parsing
    - Improve error messages and argument validation
    - Maintain backward compatibility with existing Python code

    This change improves code maintainability and consistency across the Python API.

commit 6f67fbb51e
    Fix animation callback crashes from iterator invalidation (#119)

    Resolved segfaults caused by creating new animations from within
    animation callbacks. The issue was iterator invalidation in
    AnimationManager::update() when callbacks modified the active
    animations vector.

    Changes:
    - Add deferred animation queue to AnimationManager
    - New animations created during update are queued and added after
    - Set isUpdating flag to track when in update loop
    - Properly handle Animation destructor during callback execution
    - Add clearCallback() method for safe cleanup scenarios

    This fixes the "free(): invalid pointer" and "malloc(): unaligned
    fastbin chunk detected" errors that occurred with rapid animation
    creation in callbacks.

commit eb88c7b3aa
    Add animation completion callbacks (#119)

    Implement callbacks that fire when animations complete, enabling direct
    causality between animation end and game state changes. This eliminates
    race conditions from parallel timer workarounds.

    - Add optional callback parameter to Animation constructor
    - Callbacks execute synchronously when animation completes
    - Proper Python reference counting with GIL safety
    - Callbacks receive (anim, target) parameters (currently None)
    - Exception handling prevents crashes from Python errors

    Example usage:
    ```python
    def on_complete(anim, target):
        player_moving = False

    anim = mcrfpy.Animation("x", 300.0, 1.0, "easeOut", callback=on_complete)
    anim.start(player)
    ```

    closes #119

commit 9fb428dd01
    Update ROADMAP with GitHub issue numbers (#111-#125)

    Added issue numbers from GitHub tracker to roadmap items:
    - #111: Grid Click Events Broken in Headless
    - #112: Object Splitting Bug (Python type preservation)
    - #113: Batch Operations for Grid
    - #114: CellView API
    - #115: SpatialHash Implementation
    - #116: Dirty Flag System
    - #117: Memory Pool for Entities
    - #118: Scene as Drawable
    - #119: Animation Completion Callbacks
    - #120: Animation Property Locking
    - #121: Timer Object System
    - #122: Parent-Child UI System
    - #123: Grid Subgrid System
    - #124: Grid Point Animation
    - #125: GitHub Issues Automation

    Also updated existing references:
    - #101/#110: Constructor standardization
    - #109: Vector class indexing

    Note: Tutorial-specific items and Python-implementable features
    (input queue, collision reservation) are not tracked as engine issues.

commit 062e4dadc4
    Fix animation segfaults with RAII weak_ptr implementation

    Resolved two critical segmentation faults in AnimationManager:
    1. Race condition when creating multiple animations in timer callbacks
    2. Exit crash when animations outlive their target objects

    Changes:
    - Replace raw pointers with std::weak_ptr for automatic target invalidation
    - Add Animation::complete() to jump animations to final value
    - Add Animation::hasValidTarget() to check if target still exists
    - Update AnimationManager to auto-remove invalid animations
    - Add AnimationManager::clear() call to GameEngine::cleanup()
    - Update Python bindings to pass shared_ptr instead of raw pointers

    This ensures animations can never reference destroyed objects, following
    proper RAII principles. Tested with sizzle_reel_final.py and stress
    tests creating/destroying hundreds of animated objects.

commit 98fc49a978
    Directory structure cleanup and organization overhaul
This commit is contained in:
John McCardle 2025-07-15 21:30:49 -04:00
commit f4343e1e82
163 changed files with 12812 additions and 5441 deletions

View file

@ -0,0 +1,235 @@
#!/usr/bin/env python3
"""
A* vs Dijkstra Visual Comparison
=================================
Shows the difference between A* (single target) and Dijkstra (multi-target).
"""
import mcrfpy
import sys
# Colors
WALL_COLOR = mcrfpy.Color(40, 20, 20)
FLOOR_COLOR = mcrfpy.Color(60, 60, 80)
ASTAR_COLOR = mcrfpy.Color(0, 255, 0) # Green for A*
DIJKSTRA_COLOR = mcrfpy.Color(0, 150, 255) # Blue for Dijkstra
START_COLOR = mcrfpy.Color(255, 100, 100) # Red for start
END_COLOR = mcrfpy.Color(255, 255, 100) # Yellow for end
# Global state
grid = None
mode = "ASTAR"
start_pos = (5, 10)
end_pos = (27, 10) # Changed from 25 to 27 to avoid the wall
def create_map():
"""Create a map with obstacles to show pathfinding differences"""
global grid
mcrfpy.createScene("pathfinding_comparison")
# Create grid
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
grid.fill_color = mcrfpy.Color(0, 0, 0)
# Initialize all as floor
for y in range(20):
for x in range(30):
grid.at(x, y).walkable = True
grid.at(x, y).color = FLOOR_COLOR
# Create obstacles that make A* and Dijkstra differ
obstacles = [
# Vertical wall with gaps
[(15, y) for y in range(3, 17) if y not in [8, 12]],
# Horizontal walls
[(x, 5) for x in range(10, 20)],
[(x, 15) for x in range(10, 20)],
# Maze-like structure
[(x, 10) for x in range(20, 25)],
[(25, y) for y in range(5, 15)],
]
for obstacle_group in obstacles:
for x, y in obstacle_group:
grid.at(x, y).walkable = False
grid.at(x, y).color = WALL_COLOR
# Mark start and end
grid.at(start_pos[0], start_pos[1]).color = START_COLOR
grid.at(end_pos[0], end_pos[1]).color = END_COLOR
def clear_paths():
"""Clear path highlighting"""
for y in range(20):
for x in range(30):
cell = grid.at(x, y)
if cell.walkable:
cell.color = FLOOR_COLOR
# Restore start and end colors
grid.at(start_pos[0], start_pos[1]).color = START_COLOR
grid.at(end_pos[0], end_pos[1]).color = END_COLOR
def show_astar():
"""Show A* path"""
clear_paths()
# Compute A* path
path = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
# Color the path
for i, (x, y) in enumerate(path):
if (x, y) != start_pos and (x, y) != end_pos:
grid.at(x, y).color = ASTAR_COLOR
status_text.text = f"A* Path: {len(path)} steps (optimized for single target)"
status_text.fill_color = ASTAR_COLOR
def show_dijkstra():
"""Show Dijkstra exploration"""
clear_paths()
# Compute Dijkstra from start
grid.compute_dijkstra(start_pos[0], start_pos[1])
# Color cells by distance (showing exploration)
max_dist = 40.0
for y in range(20):
for x in range(30):
if grid.at(x, y).walkable:
dist = grid.get_dijkstra_distance(x, y)
if dist is not None and dist < max_dist:
# Color based on distance
intensity = int(255 * (1 - dist / max_dist))
grid.at(x, y).color = mcrfpy.Color(0, intensity // 2, intensity)
# Get the actual path
path = grid.get_dijkstra_path(end_pos[0], end_pos[1])
# Highlight the actual path more brightly
for x, y in path:
if (x, y) != start_pos and (x, y) != end_pos:
grid.at(x, y).color = DIJKSTRA_COLOR
# Restore start and end
grid.at(start_pos[0], start_pos[1]).color = START_COLOR
grid.at(end_pos[0], end_pos[1]).color = END_COLOR
status_text.text = f"Dijkstra: {len(path)} steps (explores all directions)"
status_text.fill_color = DIJKSTRA_COLOR
def show_both():
"""Show both paths overlaid"""
clear_paths()
# Get both paths
astar_path = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
grid.compute_dijkstra(start_pos[0], start_pos[1])
dijkstra_path = grid.get_dijkstra_path(end_pos[0], end_pos[1])
print(astar_path, dijkstra_path)
# Color Dijkstra path first (blue)
for x, y in dijkstra_path:
if (x, y) != start_pos and (x, y) != end_pos:
grid.at(x, y).color = DIJKSTRA_COLOR
# Then A* path (green) - will overwrite shared cells
for x, y in astar_path:
if (x, y) != start_pos and (x, y) != end_pos:
grid.at(x, y).color = ASTAR_COLOR
# Mark differences
different_cells = []
for cell in dijkstra_path:
if cell not in astar_path:
different_cells.append(cell)
status_text.text = f"Both paths: A*={len(astar_path)} steps, Dijkstra={len(dijkstra_path)} steps"
if different_cells:
info_text.text = f"Paths differ at {len(different_cells)} cells"
else:
info_text.text = "Paths are identical"
def handle_keypress(key_str, state):
"""Handle keyboard input"""
global mode
if state == "end": return
print(key_str)
if key_str == "Esc" or key_str == "Q":
print("\nExiting...")
sys.exit(0)
elif key_str == "A" or key_str == "1":
mode = "ASTAR"
show_astar()
elif key_str == "D" or key_str == "2":
mode = "DIJKSTRA"
show_dijkstra()
elif key_str == "B" or key_str == "3":
mode = "BOTH"
show_both()
elif key_str == "Space":
# Refresh current mode
if mode == "ASTAR":
show_astar()
elif mode == "DIJKSTRA":
show_dijkstra()
else:
show_both()
# Create the demo
print("A* vs Dijkstra Pathfinding Comparison")
print("=====================================")
print("Controls:")
print(" A or 1 - Show A* path (green)")
print(" D or 2 - Show Dijkstra (blue gradient)")
print(" B or 3 - Show both paths")
print(" Q/ESC - Quit")
print()
print("A* is optimized for single-target pathfinding")
print("Dijkstra explores in all directions (good for multiple targets)")
create_map()
# Set up UI
ui = mcrfpy.sceneUI("pathfinding_comparison")
ui.append(grid)
# Scale and position
grid.size = (600, 400) # 30*20, 20*20
grid.position = (100, 100)
# Add title
title = mcrfpy.Caption("A* vs Dijkstra Pathfinding", 250, 20)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add status
status_text = mcrfpy.Caption("Press A for A*, D for Dijkstra, B for Both", 100, 60)
status_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(status_text)
# Add info
info_text = mcrfpy.Caption("", 100, 520)
info_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(info_text)
# Add legend
legend1 = mcrfpy.Caption("Red=Start, Yellow=End, Green=A*, Blue=Dijkstra", 100, 540)
legend1.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend1)
legend2 = mcrfpy.Caption("Dark=Walls, Light=Floor", 100, 560)
legend2.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend2)
# Set scene and input
mcrfpy.setScene("pathfinding_comparison")
mcrfpy.keypressScene(handle_keypress)
# Show initial A* path
show_astar()
print("\nDemo ready!")

View file

@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""Debug visibility crash"""
import mcrfpy
import sys
print("Debug visibility...")
# Create scene and grid
mcrfpy.createScene("debug")
grid = mcrfpy.Grid(grid_x=5, grid_y=5)
# Initialize grid
print("Initializing grid...")
for y in range(5):
for x in range(5):
cell = grid.at(x, y)
cell.walkable = True
cell.transparent = True
# Create entity
print("Creating entity...")
entity = mcrfpy.Entity(2, 2)
entity.sprite_index = 64
grid.entities.append(entity)
print(f"Entity at ({entity.x}, {entity.y})")
# Check gridstate
print(f"\nGridstate length: {len(entity.gridstate)}")
print(f"Expected: {5 * 5}")
# Try to access gridstate
print("\nChecking gridstate access...")
try:
if len(entity.gridstate) > 0:
state = entity.gridstate[0]
print(f"First state: visible={state.visible}, discovered={state.discovered}")
except Exception as e:
print(f"Error accessing gridstate: {e}")
# Try update_visibility
print("\nTrying update_visibility...")
try:
entity.update_visibility()
print("update_visibility succeeded")
except Exception as e:
print(f"Error in update_visibility: {e}")
# Try perspective
print("\nTesting perspective...")
print(f"Initial perspective: {grid.perspective}")
try:
grid.perspective = 0
print(f"Set perspective to 0: {grid.perspective}")
except Exception as e:
print(f"Error setting perspective: {e}")
print("\nTest complete")
sys.exit(0)

View file

@ -0,0 +1,234 @@
#!/usr/bin/env python3
"""
Dijkstra Demo - Shows ALL Path Combinations (Including Invalid)
===============================================================
Cycles through every possible entity pair to demonstrate both
valid paths and properly handled invalid paths (empty lists).
"""
import mcrfpy
import sys
# High contrast colors
WALL_COLOR = mcrfpy.Color(40, 20, 20) # Very dark red/brown
FLOOR_COLOR = mcrfpy.Color(60, 60, 80) # Dark blue-gray
PATH_COLOR = mcrfpy.Color(0, 255, 0) # Bright green
START_COLOR = mcrfpy.Color(255, 100, 100) # Light red
END_COLOR = mcrfpy.Color(100, 100, 255) # Light blue
NO_PATH_COLOR = mcrfpy.Color(255, 0, 0) # Pure red for unreachable
# Global state
grid = None
entities = []
current_combo_index = 0
all_combinations = [] # All possible pairs
current_path = []
def create_map():
"""Create the map with entities"""
global grid, entities, all_combinations
mcrfpy.createScene("dijkstra_all")
# Create grid
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
grid.fill_color = mcrfpy.Color(0, 0, 0)
# Map layout - Entity 1 is intentionally trapped!
map_layout = [
"..............", # Row 0
"..W.....WWWW..", # Row 1
"..W.W...W.EW..", # Row 2 - Entity 1 TRAPPED at (10,2)
"..W.....W..W..", # Row 3
"..W...E.WWWW..", # Row 4 - Entity 2 at (6,4)
"E.W...........", # Row 5 - Entity 3 at (0,5)
"..W...........", # Row 6
"..W...........", # Row 7
"..W.WWW.......", # Row 8
"..............", # Row 9
]
# Create the map
entity_positions = []
for y, row in enumerate(map_layout):
for x, char in enumerate(row):
cell = grid.at(x, y)
if char == 'W':
cell.walkable = False
cell.color = WALL_COLOR
else:
cell.walkable = True
cell.color = FLOOR_COLOR
if char == 'E':
entity_positions.append((x, y))
# Create entities
entities = []
for i, (x, y) in enumerate(entity_positions):
entity = mcrfpy.Entity(x, y)
entity.sprite_index = 49 + i # '1', '2', '3'
grid.entities.append(entity)
entities.append(entity)
print("Map Analysis:")
print("=============")
for i, (x, y) in enumerate(entity_positions):
print(f"Entity {i+1} at ({x}, {y})")
# Generate ALL combinations (including invalid ones)
all_combinations = []
for i in range(len(entities)):
for j in range(len(entities)):
if i != j: # Skip self-paths
all_combinations.append((i, j))
print(f"\nTotal path combinations to test: {len(all_combinations)}")
def clear_path_colors():
"""Reset all floor tiles to original color"""
global current_path
for y in range(grid.grid_y):
for x in range(grid.grid_x):
cell = grid.at(x, y)
if cell.walkable:
cell.color = FLOOR_COLOR
current_path = []
def show_combination(index):
"""Show a specific path combination (valid or invalid)"""
global current_combo_index, current_path
current_combo_index = index % len(all_combinations)
from_idx, to_idx = all_combinations[current_combo_index]
# Clear previous path
clear_path_colors()
# Get entities
e_from = entities[from_idx]
e_to = entities[to_idx]
# Calculate path
path = e_from.path_to(int(e_to.x), int(e_to.y))
current_path = path if path else []
# Always color start and end positions
grid.at(int(e_from.x), int(e_from.y)).color = START_COLOR
grid.at(int(e_to.x), int(e_to.y)).color = NO_PATH_COLOR if not path else END_COLOR
# Color the path if it exists
if path:
# Color intermediate steps
for i, (x, y) in enumerate(path):
if i > 0 and i < len(path) - 1:
grid.at(x, y).color = PATH_COLOR
status_text.text = f"Path {current_combo_index + 1}/{len(all_combinations)}: Entity {from_idx+1} → Entity {to_idx+1} = {len(path)} steps"
status_text.fill_color = mcrfpy.Color(100, 255, 100) # Green for valid
# Show path steps
path_display = []
for i, (x, y) in enumerate(path[:5]):
path_display.append(f"({x},{y})")
if len(path) > 5:
path_display.append("...")
path_text.text = "Path: " + "".join(path_display)
else:
status_text.text = f"Path {current_combo_index + 1}/{len(all_combinations)}: Entity {from_idx+1} → Entity {to_idx+1} = NO PATH!"
status_text.fill_color = mcrfpy.Color(255, 100, 100) # Red for invalid
path_text.text = "Path: [] (No valid path exists)"
# Update info
info_text.text = f"From: Entity {from_idx+1} at ({int(e_from.x)}, {int(e_from.y)}) | To: Entity {to_idx+1} at ({int(e_to.x)}, {int(e_to.y)})"
def handle_keypress(key_str, state):
"""Handle keyboard input"""
global current_combo_index
if state == "end": return
if key_str == "Esc" or key_str == "Q":
print("\nExiting...")
sys.exit(0)
elif key_str == "Space" or key_str == "N":
show_combination(current_combo_index + 1)
elif key_str == "P":
show_combination(current_combo_index - 1)
elif key_str == "R":
show_combination(current_combo_index)
elif key_str in "123456":
combo_num = int(key_str) - 1 # 0-based index
if combo_num < len(all_combinations):
show_combination(combo_num)
# Create the demo
print("Dijkstra All Paths Demo")
print("=======================")
print("Shows ALL path combinations including invalid ones")
print("Entity 1 is trapped - paths to/from it will be empty!")
print()
create_map()
# Set up UI
ui = mcrfpy.sceneUI("dijkstra_all")
ui.append(grid)
# Scale and position
grid.size = (560, 400)
grid.position = (120, 100)
# Add title
title = mcrfpy.Caption("Dijkstra - All Paths (Valid & Invalid)", 200, 20)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add status (will change color based on validity)
status_text = mcrfpy.Caption("Ready", 120, 60)
status_text.fill_color = mcrfpy.Color(255, 255, 100)
ui.append(status_text)
# Add info
info_text = mcrfpy.Caption("", 120, 80)
info_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(info_text)
# Add path display
path_text = mcrfpy.Caption("Path: None", 120, 520)
path_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(path_text)
# Add controls
controls = mcrfpy.Caption("SPACE/N=Next, P=Previous, 1-6=Jump to path, Q=Quit", 120, 540)
controls.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(controls)
# Add legend
legend = mcrfpy.Caption("Red Start→Blue End (valid) | Red Start→Red End (invalid)", 120, 560)
legend.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend)
# Expected results info
expected = mcrfpy.Caption("Entity 1 is trapped: paths 1→2, 1→3, 2→1, 3→1 will fail", 120, 580)
expected.fill_color = mcrfpy.Color(255, 150, 150)
ui.append(expected)
# Set scene first, then set up input handler
mcrfpy.setScene("dijkstra_all")
mcrfpy.keypressScene(handle_keypress)
# Show first combination
show_combination(0)
print("\nDemo ready!")
print("Expected results:")
print(" Path 1: Entity 1→2 = NO PATH (Entity 1 is trapped)")
print(" Path 2: Entity 1→3 = NO PATH (Entity 1 is trapped)")
print(" Path 3: Entity 2→1 = NO PATH (Entity 1 is trapped)")
print(" Path 4: Entity 2→3 = Valid path")
print(" Path 5: Entity 3→1 = NO PATH (Entity 1 is trapped)")
print(" Path 6: Entity 3→2 = Valid path")

View file

@ -0,0 +1,236 @@
#!/usr/bin/env python3
"""
Dijkstra Demo - Cycles Through Different Path Combinations
==========================================================
Shows paths between different entity pairs, skipping impossible paths.
"""
import mcrfpy
import sys
# High contrast colors
WALL_COLOR = mcrfpy.Color(40, 20, 20) # Very dark red/brown
FLOOR_COLOR = mcrfpy.Color(60, 60, 80) # Dark blue-gray
PATH_COLOR = mcrfpy.Color(0, 255, 0) # Bright green
START_COLOR = mcrfpy.Color(255, 100, 100) # Light red
END_COLOR = mcrfpy.Color(100, 100, 255) # Light blue
# Global state
grid = None
entities = []
current_path_index = 0
path_combinations = []
current_path = []
def create_map():
"""Create the map with entities"""
global grid, entities
mcrfpy.createScene("dijkstra_cycle")
# Create grid
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
grid.fill_color = mcrfpy.Color(0, 0, 0)
# Map layout
map_layout = [
"..............", # Row 0
"..W.....WWWW..", # Row 1
"..W.W...W.EW..", # Row 2 - Entity 1 at (10,2) is TRAPPED!
"..W.....W..W..", # Row 3
"..W...E.WWWW..", # Row 4 - Entity 2 at (6,4)
"E.W...........", # Row 5 - Entity 3 at (0,5)
"..W...........", # Row 6
"..W...........", # Row 7
"..W.WWW.......", # Row 8
"..............", # Row 9
]
# Create the map
entity_positions = []
for y, row in enumerate(map_layout):
for x, char in enumerate(row):
cell = grid.at(x, y)
if char == 'W':
cell.walkable = False
cell.color = WALL_COLOR
else:
cell.walkable = True
cell.color = FLOOR_COLOR
if char == 'E':
entity_positions.append((x, y))
# Create entities
entities = []
for i, (x, y) in enumerate(entity_positions):
entity = mcrfpy.Entity(x, y)
entity.sprite_index = 49 + i # '1', '2', '3'
grid.entities.append(entity)
entities.append(entity)
print("Entities created:")
for i, (x, y) in enumerate(entity_positions):
print(f" Entity {i+1} at ({x}, {y})")
# Check which entity is trapped
print("\nChecking accessibility:")
for i, e in enumerate(entities):
# Try to path to each other entity
can_reach = []
for j, other in enumerate(entities):
if i != j:
path = e.path_to(int(other.x), int(other.y))
if path:
can_reach.append(j+1)
if not can_reach:
print(f" Entity {i+1} at ({int(e.x)}, {int(e.y)}) is TRAPPED!")
else:
print(f" Entity {i+1} can reach entities: {can_reach}")
# Generate valid path combinations (excluding trapped entity)
global path_combinations
path_combinations = []
# Only paths between entities 2 and 3 (indices 1 and 2) will work
# since entity 1 (index 0) is trapped
if len(entities) >= 3:
# Entity 2 to Entity 3
path = entities[1].path_to(int(entities[2].x), int(entities[2].y))
if path:
path_combinations.append((1, 2, path))
# Entity 3 to Entity 2
path = entities[2].path_to(int(entities[1].x), int(entities[1].y))
if path:
path_combinations.append((2, 1, path))
print(f"\nFound {len(path_combinations)} valid paths")
def clear_path_colors():
"""Reset all floor tiles to original color"""
global current_path
for y in range(grid.grid_y):
for x in range(grid.grid_x):
cell = grid.at(x, y)
if cell.walkable:
cell.color = FLOOR_COLOR
current_path = []
def show_path(index):
"""Show a specific path combination"""
global current_path_index, current_path
if not path_combinations:
status_text.text = "No valid paths available (Entity 1 is trapped!)"
return
current_path_index = index % len(path_combinations)
from_idx, to_idx, path = path_combinations[current_path_index]
# Clear previous path
clear_path_colors()
# Get entities
e_from = entities[from_idx]
e_to = entities[to_idx]
# Color the path
current_path = path
if path:
# Color start and end
grid.at(int(e_from.x), int(e_from.y)).color = START_COLOR
grid.at(int(e_to.x), int(e_to.y)).color = END_COLOR
# Color intermediate steps
for i, (x, y) in enumerate(path):
if i > 0 and i < len(path) - 1:
grid.at(x, y).color = PATH_COLOR
# Update status
status_text.text = f"Path {current_path_index + 1}/{len(path_combinations)}: Entity {from_idx+1} → Entity {to_idx+1} ({len(path)} steps)"
# Update path display
path_display = []
for i, (x, y) in enumerate(path[:5]): # Show first 5 steps
path_display.append(f"({x},{y})")
if len(path) > 5:
path_display.append("...")
path_text.text = "Path: " + "".join(path_display) if path_display else "Path: None"
def handle_keypress(key_str, state):
"""Handle keyboard input"""
global current_path_index
if state == "end": return
if key_str == "Esc":
print("\nExiting...")
sys.exit(0)
elif key_str == "N" or key_str == "Space":
show_path(current_path_index + 1)
elif key_str == "P":
show_path(current_path_index - 1)
elif key_str == "R":
show_path(current_path_index)
# Create the demo
print("Dijkstra Path Cycling Demo")
print("==========================")
print("Note: Entity 1 is trapped by walls!")
print()
create_map()
# Set up UI
ui = mcrfpy.sceneUI("dijkstra_cycle")
ui.append(grid)
# Scale and position
grid.size = (560, 400)
grid.position = (120, 100)
# Add title
title = mcrfpy.Caption("Dijkstra Pathfinding - Cycle Paths", 200, 20)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add status
status_text = mcrfpy.Caption("Press SPACE to cycle paths", 120, 60)
status_text.fill_color = mcrfpy.Color(255, 255, 100)
ui.append(status_text)
# Add path display
path_text = mcrfpy.Caption("Path: None", 120, 520)
path_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(path_text)
# Add controls
controls = mcrfpy.Caption("SPACE/N=Next, P=Previous, R=Refresh, Q=Quit", 120, 540)
controls.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(controls)
# Add legend
legend = mcrfpy.Caption("Red=Start, Blue=End, Green=Path, Dark=Wall", 120, 560)
legend.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend)
# Show first valid path
mcrfpy.setScene("dijkstra_cycle")
mcrfpy.keypressScene(handle_keypress)
# Display initial path
if path_combinations:
show_path(0)
else:
status_text.text = "No valid paths! Entity 1 is trapped!"
print("\nDemo ready!")
print("Controls:")
print(" SPACE or N - Next path")
print(" P - Previous path")
print(" R - Refresh current path")
print(" Q - Quit")

View file

@ -0,0 +1,161 @@
#!/usr/bin/env python3
"""
Debug version of Dijkstra pathfinding to diagnose visualization issues
"""
import mcrfpy
import sys
# Colors
WALL_COLOR = mcrfpy.Color(60, 30, 30)
FLOOR_COLOR = mcrfpy.Color(200, 200, 220)
PATH_COLOR = mcrfpy.Color(200, 250, 220)
ENTITY_COLORS = [
mcrfpy.Color(255, 100, 100), # Entity 1 - Red
mcrfpy.Color(100, 255, 100), # Entity 2 - Green
mcrfpy.Color(100, 100, 255), # Entity 3 - Blue
]
# Global state
grid = None
entities = []
first_point = None
second_point = None
def create_simple_map():
"""Create a simple test map"""
global grid, entities
mcrfpy.createScene("dijkstra_debug")
# Small grid for easy debugging
grid = mcrfpy.Grid(grid_x=10, grid_y=10)
grid.fill_color = mcrfpy.Color(0, 0, 0)
print("Initializing 10x10 grid...")
# Initialize all as floor
for y in range(10):
for x in range(10):
grid.at(x, y).walkable = True
grid.at(x, y).transparent = True
grid.at(x, y).color = FLOOR_COLOR
# Add a simple wall
print("Adding walls at:")
walls = [(5, 2), (5, 3), (5, 4), (5, 5), (5, 6)]
for x, y in walls:
print(f" Wall at ({x}, {y})")
grid.at(x, y).walkable = False
grid.at(x, y).color = WALL_COLOR
# Create 3 entities
entity_positions = [(2, 5), (8, 5), (5, 8)]
entities = []
print("\nCreating entities at:")
for i, (x, y) in enumerate(entity_positions):
print(f" Entity {i+1} at ({x}, {y})")
entity = mcrfpy.Entity(x, y)
entity.sprite_index = 49 + i # '1', '2', '3'
grid.entities.append(entity)
entities.append(entity)
return grid
def test_path_highlighting():
"""Test path highlighting with debug output"""
print("\n" + "="*50)
print("Testing path highlighting...")
# Select first two entities
e1 = entities[0]
e2 = entities[1]
print(f"\nEntity 1 position: ({e1.x}, {e1.y})")
print(f"Entity 2 position: ({e2.x}, {e2.y})")
# Use entity.path_to()
print("\nCalling entity.path_to()...")
path = e1.path_to(int(e2.x), int(e2.y))
print(f"Path returned: {path}")
print(f"Path length: {len(path)} steps")
if path:
print("\nHighlighting path cells:")
for i, (x, y) in enumerate(path):
print(f" Step {i}: ({x}, {y})")
# Get current color for debugging
cell = grid.at(x, y)
old_color = (cell.color.r, cell.color.g, cell.color.b)
# Set new color
cell.color = PATH_COLOR
new_color = (cell.color.r, cell.color.g, cell.color.b)
print(f" Color changed from {old_color} to {new_color}")
print(f" Walkable: {cell.walkable}")
# Also test grid's Dijkstra methods
print("\n" + "-"*30)
print("Testing grid Dijkstra methods...")
grid.compute_dijkstra(int(e1.x), int(e1.y))
grid_path = grid.get_dijkstra_path(int(e2.x), int(e2.y))
distance = grid.get_dijkstra_distance(int(e2.x), int(e2.y))
print(f"Grid path: {grid_path}")
print(f"Grid distance: {distance}")
# Verify colors were set
print("\nVerifying cell colors after highlighting:")
for x, y in path[:3]: # Check first 3 cells
cell = grid.at(x, y)
color = (cell.color.r, cell.color.g, cell.color.b)
expected = (PATH_COLOR.r, PATH_COLOR.g, PATH_COLOR.b)
match = color == expected
print(f" Cell ({x}, {y}): color={color}, expected={expected}, match={match}")
def handle_keypress(scene_name, keycode):
"""Simple keypress handler"""
if keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
print("\nExiting debug...")
sys.exit(0)
elif keycode == 32: # Space
print("\nSpace pressed - retesting path highlighting...")
test_path_highlighting()
# Create the map
print("Dijkstra Debug Test")
print("===================")
grid = create_simple_map()
# Initial path test
test_path_highlighting()
# Set up UI
ui = mcrfpy.sceneUI("dijkstra_debug")
ui.append(grid)
# Position and scale
grid.position = (50, 50)
grid.size = (400, 400) # 10*40
# Add title
title = mcrfpy.Caption("Dijkstra Debug - Press SPACE to retest, Q to quit", 50, 10)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add debug info
info = mcrfpy.Caption("Check console for debug output", 50, 470)
info.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(info)
# Set up scene
mcrfpy.keypressScene(handle_keypress)
mcrfpy.setScene("dijkstra_debug")
print("\nScene ready. The path should be highlighted in cyan.")
print("If you don't see the path, there may be a rendering issue.")
print("Press SPACE to retest, Q to quit.")

View file

@ -0,0 +1,244 @@
#!/usr/bin/env python3
"""
Dijkstra Pathfinding Interactive Demo
=====================================
Interactive visualization showing Dijkstra pathfinding between entities.
Controls:
- Press 1/2/3 to select the first entity
- Press A/B/C to select the second entity
- Space to clear selection
- Q or ESC to quit
The path between selected entities is automatically highlighted.
"""
import mcrfpy
import sys
# Colors - using more distinct values
WALL_COLOR = mcrfpy.Color(60, 30, 30)
FLOOR_COLOR = mcrfpy.Color(100, 100, 120) # Darker floor for better contrast
PATH_COLOR = mcrfpy.Color(50, 255, 50) # Bright green for path
ENTITY_COLORS = [
mcrfpy.Color(255, 100, 100), # Entity 1 - Red
mcrfpy.Color(100, 255, 100), # Entity 2 - Green
mcrfpy.Color(100, 100, 255), # Entity 3 - Blue
]
# Global state
grid = None
entities = []
first_point = None
second_point = None
def create_map():
"""Create the interactive map with the layout specified by the user"""
global grid, entities
mcrfpy.createScene("dijkstra_interactive")
# Create grid - 14x10 as specified
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
grid.fill_color = mcrfpy.Color(0, 0, 0)
# Define the map layout from user's specification
# . = floor, W = wall, E = entity position
map_layout = [
"..............", # Row 0
"..W.....WWWW..", # Row 1
"..W.W...W.EW..", # Row 2
"..W.....W..W..", # Row 3
"..W...E.WWWW..", # Row 4
"E.W...........", # Row 5
"..W...........", # Row 6
"..W...........", # Row 7
"..W.WWW.......", # Row 8
"..............", # Row 9
]
# Create the map
entity_positions = []
for y, row in enumerate(map_layout):
for x, char in enumerate(row):
cell = grid.at(x, y)
if char == 'W':
# Wall
cell.walkable = False
cell.transparent = False
cell.color = WALL_COLOR
else:
# Floor
cell.walkable = True
cell.transparent = True
cell.color = FLOOR_COLOR
if char == 'E':
# Entity position
entity_positions.append((x, y))
# Create entities at marked positions
entities = []
for i, (x, y) in enumerate(entity_positions):
entity = mcrfpy.Entity(x, y)
entity.sprite_index = 49 + i # '1', '2', '3'
grid.entities.append(entity)
entities.append(entity)
return grid
def clear_path_highlight():
"""Clear any existing path highlighting"""
# Reset all floor tiles to original color
for y in range(grid.grid_y):
for x in range(grid.grid_x):
cell = grid.at(x, y)
if cell.walkable:
cell.color = FLOOR_COLOR
def highlight_path():
"""Highlight the path between selected entities"""
if first_point is None or second_point is None:
return
# Clear previous highlighting
clear_path_highlight()
# Get entities
entity1 = entities[first_point]
entity2 = entities[second_point]
# Compute Dijkstra from first entity
grid.compute_dijkstra(int(entity1.x), int(entity1.y))
# Get path to second entity
path = grid.get_dijkstra_path(int(entity2.x), int(entity2.y))
if path:
# Highlight the path
for x, y in path:
cell = grid.at(x, y)
if cell.walkable:
cell.color = PATH_COLOR
# Also highlight start and end with entity colors
grid.at(int(entity1.x), int(entity1.y)).color = ENTITY_COLORS[first_point]
grid.at(int(entity2.x), int(entity2.y)).color = ENTITY_COLORS[second_point]
# Update info
distance = grid.get_dijkstra_distance(int(entity2.x), int(entity2.y))
info_text.text = f"Path: Entity {first_point+1} to Entity {second_point+1} - {len(path)} steps, {distance:.1f} units"
else:
info_text.text = f"No path between Entity {first_point+1} and Entity {second_point+1}"
def handle_keypress(scene_name, keycode):
"""Handle keyboard input"""
global first_point, second_point
# Number keys for first entity
if keycode == 49: # '1'
first_point = 0
status_text.text = f"First: Entity 1 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
highlight_path()
elif keycode == 50: # '2'
first_point = 1
status_text.text = f"First: Entity 2 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
highlight_path()
elif keycode == 51: # '3'
first_point = 2
status_text.text = f"First: Entity 3 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
highlight_path()
# Letter keys for second entity
elif keycode == 65 or keycode == 97: # 'A' or 'a'
second_point = 0
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 1"
highlight_path()
elif keycode == 66 or keycode == 98: # 'B' or 'b'
second_point = 1
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 2"
highlight_path()
elif keycode == 67 or keycode == 99: # 'C' or 'c'
second_point = 2
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 3"
highlight_path()
# Clear selection
elif keycode == 32: # Space
first_point = None
second_point = None
clear_path_highlight()
status_text.text = "Press 1/2/3 for first entity, A/B/C for second"
info_text.text = "Space to clear, Q to quit"
# Quit
elif keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
print("\nExiting Dijkstra interactive demo...")
sys.exit(0)
# Create the visualization
print("Dijkstra Pathfinding Interactive Demo")
print("=====================================")
print("Controls:")
print(" 1/2/3 - Select first entity")
print(" A/B/C - Select second entity")
print(" Space - Clear selection")
print(" Q/ESC - Quit")
# Create map
grid = create_map()
# Set up UI
ui = mcrfpy.sceneUI("dijkstra_interactive")
ui.append(grid)
# Scale and position grid for better visibility
grid.size = (560, 400) # 14*40, 10*40
grid.position = (120, 60)
# Add title
title = mcrfpy.Caption("Dijkstra Pathfinding Interactive", 250, 10)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add status text
status_text = mcrfpy.Caption("Press 1/2/3 for first entity, A/B/C for second", 120, 480)
status_text.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(status_text)
# Add info text
info_text = mcrfpy.Caption("Space to clear, Q to quit", 120, 500)
info_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(info_text)
# Add legend
legend1 = mcrfpy.Caption("Entities: 1=Red 2=Green 3=Blue", 120, 540)
legend1.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend1)
legend2 = mcrfpy.Caption("Colors: Dark=Wall Light=Floor Cyan=Path", 120, 560)
legend2.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend2)
# Mark entity positions with colored indicators
for i, entity in enumerate(entities):
marker = mcrfpy.Caption(str(i+1),
120 + int(entity.x) * 40 + 15,
60 + int(entity.y) * 40 + 10)
marker.fill_color = ENTITY_COLORS[i]
marker.outline = 1
marker.outline_color = mcrfpy.Color(0, 0, 0)
ui.append(marker)
# Set up input handling
mcrfpy.keypressScene(handle_keypress)
# Show the scene
mcrfpy.setScene("dijkstra_interactive")
print("\nVisualization ready!")
print("Entities are at:")
for i, entity in enumerate(entities):
print(f" Entity {i+1}: ({int(entity.x)}, {int(entity.y)})")

View file

@ -0,0 +1,344 @@
#!/usr/bin/env python3
"""
Enhanced Dijkstra Pathfinding Interactive Demo
==============================================
Interactive visualization with entity pathfinding animations.
Controls:
- Press 1/2/3 to select the first entity
- Press A/B/C to select the second entity
- Space to clear selection
- M to make selected entity move along path
- P to pause/resume animation
- R to reset entity positions
- Q or ESC to quit
"""
import mcrfpy
import sys
import math
# Colors
WALL_COLOR = mcrfpy.Color(60, 30, 30)
FLOOR_COLOR = mcrfpy.Color(200, 200, 220)
PATH_COLOR = mcrfpy.Color(200, 250, 220)
VISITED_COLOR = mcrfpy.Color(180, 230, 200)
ENTITY_COLORS = [
mcrfpy.Color(255, 100, 100), # Entity 1 - Red
mcrfpy.Color(100, 255, 100), # Entity 2 - Green
mcrfpy.Color(100, 100, 255), # Entity 3 - Blue
]
# Global state
grid = None
entities = []
first_point = None
second_point = None
current_path = []
animating = False
animation_progress = 0.0
animation_speed = 2.0 # cells per second
original_positions = [] # Store original entity positions
def create_map():
"""Create the interactive map with the layout specified by the user"""
global grid, entities, original_positions
mcrfpy.createScene("dijkstra_enhanced")
# Create grid - 14x10 as specified
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
grid.fill_color = mcrfpy.Color(0, 0, 0)
# Define the map layout from user's specification
# . = floor, W = wall, E = entity position
map_layout = [
"..............", # Row 0
"..W.....WWWW..", # Row 1
"..W.W...W.EW..", # Row 2
"..W.....W..W..", # Row 3
"..W...E.WWWW..", # Row 4
"E.W...........", # Row 5
"..W...........", # Row 6
"..W...........", # Row 7
"..W.WWW.......", # Row 8
"..............", # Row 9
]
# Create the map
entity_positions = []
for y, row in enumerate(map_layout):
for x, char in enumerate(row):
cell = grid.at(x, y)
if char == 'W':
# Wall
cell.walkable = False
cell.transparent = False
cell.color = WALL_COLOR
else:
# Floor
cell.walkable = True
cell.transparent = True
cell.color = FLOOR_COLOR
if char == 'E':
# Entity position
entity_positions.append((x, y))
# Create entities at marked positions
entities = []
original_positions = []
for i, (x, y) in enumerate(entity_positions):
entity = mcrfpy.Entity(x, y)
entity.sprite_index = 49 + i # '1', '2', '3'
grid.entities.append(entity)
entities.append(entity)
original_positions.append((x, y))
return grid
def clear_path_highlight():
"""Clear any existing path highlighting"""
global current_path
# Reset all floor tiles to original color
for y in range(grid.grid_y):
for x in range(grid.grid_x):
cell = grid.at(x, y)
if cell.walkable:
cell.color = FLOOR_COLOR
current_path = []
def highlight_path():
"""Highlight the path between selected entities using entity.path_to()"""
global current_path
if first_point is None or second_point is None:
return
# Clear previous highlighting
clear_path_highlight()
# Get entities
entity1 = entities[first_point]
entity2 = entities[second_point]
# Use the new path_to method!
path = entity1.path_to(int(entity2.x), int(entity2.y))
if path:
current_path = path
# Highlight the path
for i, (x, y) in enumerate(path):
cell = grid.at(x, y)
if cell.walkable:
# Use gradient for path visualization
if i < len(path) - 1:
cell.color = PATH_COLOR
else:
cell.color = VISITED_COLOR
# Highlight start and end with entity colors
grid.at(int(entity1.x), int(entity1.y)).color = ENTITY_COLORS[first_point]
grid.at(int(entity2.x), int(entity2.y)).color = ENTITY_COLORS[second_point]
# Update info
info_text.text = f"Path: Entity {first_point+1} to Entity {second_point+1} - {len(path)} steps"
else:
info_text.text = f"No path between Entity {first_point+1} and Entity {second_point+1}"
current_path = []
def animate_movement(dt):
"""Animate entity movement along path"""
global animation_progress, animating, current_path
if not animating or not current_path or first_point is None:
return
entity = entities[first_point]
# Update animation progress
animation_progress += animation_speed * dt
# Calculate current position along path
path_index = int(animation_progress)
if path_index >= len(current_path):
# Animation complete
animating = False
animation_progress = 0.0
# Snap to final position
if current_path:
final_x, final_y = current_path[-1]
entity.x = float(final_x)
entity.y = float(final_y)
return
# Interpolate between path points
if path_index < len(current_path) - 1:
curr_x, curr_y = current_path[path_index]
next_x, next_y = current_path[path_index + 1]
# Calculate interpolation factor
t = animation_progress - path_index
# Smooth interpolation
entity.x = curr_x + (next_x - curr_x) * t
entity.y = curr_y + (next_y - curr_y) * t
else:
# At last point
entity.x, entity.y = current_path[path_index]
def handle_keypress(scene_name, keycode):
"""Handle keyboard input"""
global first_point, second_point, animating, animation_progress
# Number keys for first entity
if keycode == 49: # '1'
first_point = 0
status_text.text = f"First: Entity 1 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
highlight_path()
elif keycode == 50: # '2'
first_point = 1
status_text.text = f"First: Entity 2 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
highlight_path()
elif keycode == 51: # '3'
first_point = 2
status_text.text = f"First: Entity 3 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
highlight_path()
# Letter keys for second entity
elif keycode == 65 or keycode == 97: # 'A' or 'a'
second_point = 0
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 1"
highlight_path()
elif keycode == 66 or keycode == 98: # 'B' or 'b'
second_point = 1
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 2"
highlight_path()
elif keycode == 67 or keycode == 99: # 'C' or 'c'
second_point = 2
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 3"
highlight_path()
# Movement control
elif keycode == 77 or keycode == 109: # 'M' or 'm'
if current_path and first_point is not None:
animating = True
animation_progress = 0.0
control_text.text = "Animation: MOVING (press P to pause)"
# Pause/Resume
elif keycode == 80 or keycode == 112: # 'P' or 'p'
animating = not animating
control_text.text = f"Animation: {'MOVING' if animating else 'PAUSED'} (press P to {'pause' if animating else 'resume'})"
# Reset positions
elif keycode == 82 or keycode == 114: # 'R' or 'r'
animating = False
animation_progress = 0.0
for i, entity in enumerate(entities):
entity.x, entity.y = original_positions[i]
control_text.text = "Entities reset to original positions"
highlight_path() # Re-highlight path after reset
# Clear selection
elif keycode == 32: # Space
first_point = None
second_point = None
animating = False
animation_progress = 0.0
clear_path_highlight()
status_text.text = "Press 1/2/3 for first entity, A/B/C for second"
info_text.text = "Space to clear, Q to quit"
control_text.text = "Press M to move, P to pause, R to reset"
# Quit
elif keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
print("\nExiting enhanced Dijkstra demo...")
sys.exit(0)
# Timer callback for animation
def update_animation(dt):
"""Update animation state"""
animate_movement(dt / 1000.0) # Convert ms to seconds
# Create the visualization
print("Enhanced Dijkstra Pathfinding Demo")
print("==================================")
print("Controls:")
print(" 1/2/3 - Select first entity")
print(" A/B/C - Select second entity")
print(" M - Move first entity along path")
print(" P - Pause/Resume animation")
print(" R - Reset entity positions")
print(" Space - Clear selection")
print(" Q/ESC - Quit")
# Create map
grid = create_map()
# Set up UI
ui = mcrfpy.sceneUI("dijkstra_enhanced")
ui.append(grid)
# Scale and position grid for better visibility
grid.size = (560, 400) # 14*40, 10*40
grid.position = (120, 60)
# Add title
title = mcrfpy.Caption("Enhanced Dijkstra Pathfinding", 250, 10)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add status text
status_text = mcrfpy.Caption("Press 1/2/3 for first entity, A/B/C for second", 120, 480)
status_text.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(status_text)
# Add info text
info_text = mcrfpy.Caption("Space to clear, Q to quit", 120, 500)
info_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(info_text)
# Add control text
control_text = mcrfpy.Caption("Press M to move, P to pause, R to reset", 120, 520)
control_text.fill_color = mcrfpy.Color(150, 200, 150)
ui.append(control_text)
# Add legend
legend1 = mcrfpy.Caption("Entities: 1=Red 2=Green 3=Blue", 120, 560)
legend1.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend1)
legend2 = mcrfpy.Caption("Colors: Dark=Wall Light=Floor Cyan=Path", 120, 580)
legend2.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend2)
# Mark entity positions with colored indicators
for i, entity in enumerate(entities):
marker = mcrfpy.Caption(str(i+1),
120 + int(entity.x) * 40 + 15,
60 + int(entity.y) * 40 + 10)
marker.fill_color = ENTITY_COLORS[i]
marker.outline = 1
marker.outline_color = mcrfpy.Color(0, 0, 0)
ui.append(marker)
# Set up input handling
mcrfpy.keypressScene(handle_keypress)
# Set up animation timer (60 FPS)
mcrfpy.setTimer("animation", update_animation, 16)
# Show the scene
mcrfpy.setScene("dijkstra_enhanced")
print("\nVisualization ready!")
print("Entities are at:")
for i, entity in enumerate(entities):
print(f" Entity {i+1}: ({int(entity.x)}, {int(entity.y)})")

View file

@ -0,0 +1,146 @@
#!/usr/bin/env python3
"""
Dijkstra Pathfinding Test - Headless
====================================
Tests all Dijkstra functionality and generates a screenshot.
"""
import mcrfpy
from mcrfpy import automation
import sys
def create_test_map():
"""Create a test map with obstacles"""
mcrfpy.createScene("dijkstra_test")
# Create grid
grid = mcrfpy.Grid(grid_x=20, grid_y=12)
grid.fill_color = mcrfpy.Color(0, 0, 0)
# Initialize all cells as walkable floor
for y in range(12):
for x in range(20):
grid.at(x, y).walkable = True
grid.at(x, y).transparent = True
grid.at(x, y).color = mcrfpy.Color(200, 200, 220)
# Add walls to create interesting paths
walls = [
# Vertical wall in the middle
(10, 1), (10, 2), (10, 3), (10, 4), (10, 5), (10, 6), (10, 7), (10, 8),
# Horizontal walls
(2, 6), (3, 6), (4, 6), (5, 6), (6, 6),
(14, 6), (15, 6), (16, 6), (17, 6),
# Some scattered obstacles
(5, 2), (15, 2), (5, 9), (15, 9)
]
for x, y in walls:
grid.at(x, y).walkable = False
grid.at(x, y).color = mcrfpy.Color(60, 30, 30)
# Place test entities
entities = []
positions = [(2, 2), (17, 2), (9, 10)]
colors = [
mcrfpy.Color(255, 100, 100), # Red
mcrfpy.Color(100, 255, 100), # Green
mcrfpy.Color(100, 100, 255) # Blue
]
for i, (x, y) in enumerate(positions):
entity = mcrfpy.Entity(x, y)
entity.sprite_index = 49 + i # '1', '2', '3'
grid.entities.append(entity)
entities.append(entity)
# Mark entity positions
grid.at(x, y).color = colors[i]
return grid, entities
def test_dijkstra(grid, entities):
"""Test Dijkstra pathfinding between all entity pairs"""
results = []
for i in range(len(entities)):
for j in range(len(entities)):
if i != j:
# Compute Dijkstra from entity i
e1 = entities[i]
e2 = entities[j]
grid.compute_dijkstra(int(e1.x), int(e1.y))
# Get distance and path to entity j
distance = grid.get_dijkstra_distance(int(e2.x), int(e2.y))
path = grid.get_dijkstra_path(int(e2.x), int(e2.y))
if path:
results.append(f"Path {i+1}{j+1}: {len(path)} steps, {distance:.1f} units")
# Color one interesting path
if i == 0 and j == 2: # Path from 1 to 3
for x, y in path[1:-1]: # Skip endpoints
if grid.at(x, y).walkable:
grid.at(x, y).color = mcrfpy.Color(200, 250, 220)
else:
results.append(f"Path {i+1}{j+1}: No path found!")
return results
def run_test(runtime):
"""Timer callback to run tests and take screenshot"""
# Run pathfinding tests
results = test_dijkstra(grid, entities)
# Update display with results
y_pos = 380
for result in results:
caption = mcrfpy.Caption(result, 50, y_pos)
caption.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(caption)
y_pos += 20
# Take screenshot
mcrfpy.setTimer("screenshot", lambda rt: take_screenshot(), 500)
def take_screenshot():
"""Take screenshot and exit"""
try:
automation.screenshot("dijkstra_test.png")
print("Screenshot saved: dijkstra_test.png")
except Exception as e:
print(f"Screenshot failed: {e}")
# Exit
sys.exit(0)
# Create test map
print("Creating Dijkstra pathfinding test...")
grid, entities = create_test_map()
# Set up UI
ui = mcrfpy.sceneUI("dijkstra_test")
ui.append(grid)
# Position and scale grid
grid.position = (50, 50)
grid.size = (500, 300)
# Add title
title = mcrfpy.Caption("Dijkstra Pathfinding Test", 200, 10)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add legend
legend = mcrfpy.Caption("Red=Entity1 Green=Entity2 Blue=Entity3 Cyan=Path 1→3", 50, 360)
legend.fill_color = mcrfpy.Color(180, 180, 180)
ui.append(legend)
# Set scene
mcrfpy.setScene("dijkstra_test")
# Run test after scene loads
mcrfpy.setTimer("test", run_test, 100)
print("Running Dijkstra tests...")

View file

@ -0,0 +1,29 @@
#!/usr/bin/env python3
"""Force Python to be non-interactive"""
import sys
import os
print("Attempting to force non-interactive mode...")
# Remove ps1/ps2 if they exist
if hasattr(sys, 'ps1'):
delattr(sys, 'ps1')
if hasattr(sys, 'ps2'):
delattr(sys, 'ps2')
# Set environment variable
os.environ['PYTHONSTARTUP'] = ''
# Try to set stdin to non-interactive
try:
import fcntl
import termios
# Make stdin non-interactive by removing ICANON flag
attrs = termios.tcgetattr(0)
attrs[3] = attrs[3] & ~termios.ICANON
termios.tcsetattr(0, termios.TCSANOW, attrs)
print("Modified terminal attributes")
except:
print("Could not modify terminal attributes")
print("Script complete")

View file

@ -0,0 +1,201 @@
#!/usr/bin/env python3
"""
Interactive Visibility Demo
==========================
Controls:
- WASD: Move the player (green @)
- Arrow keys: Move enemy (red E)
- Tab: Cycle perspective (Omniscient Player Enemy Omniscient)
- Space: Update visibility for current entity
- R: Reset positions
"""
import mcrfpy
import sys
# Create scene and grid
mcrfpy.createScene("visibility_demo")
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
grid.fill_color = mcrfpy.Color(20, 20, 30) # Dark background
# Initialize grid - all walkable and transparent
for y in range(20):
for x in range(30):
cell = grid.at(x, y)
cell.walkable = True
cell.transparent = True
cell.color = mcrfpy.Color(100, 100, 120) # Floor color
# Create walls
walls = [
# Central cross
[(15, y) for y in range(8, 12)],
[(x, 10) for x in range(13, 18)],
# Rooms
# Top-left room
[(x, 5) for x in range(2, 8)] + [(8, y) for y in range(2, 6)],
[(2, y) for y in range(2, 6)] + [(x, 2) for x in range(2, 8)],
# Top-right room
[(x, 5) for x in range(22, 28)] + [(22, y) for y in range(2, 6)],
[(28, y) for y in range(2, 6)] + [(x, 2) for x in range(22, 28)],
# Bottom-left room
[(x, 15) for x in range(2, 8)] + [(8, y) for y in range(15, 18)],
[(2, y) for y in range(15, 18)] + [(x, 18) for x in range(2, 8)],
# Bottom-right room
[(x, 15) for x in range(22, 28)] + [(22, y) for y in range(15, 18)],
[(28, y) for y in range(15, 18)] + [(x, 18) for x in range(22, 28)],
]
for wall_group in walls:
for x, y in wall_group:
if 0 <= x < 30 and 0 <= y < 20:
cell = grid.at(x, y)
cell.walkable = False
cell.transparent = False
cell.color = mcrfpy.Color(40, 20, 20) # Wall color
# Create entities
player = mcrfpy.Entity(5, 10, grid=grid)
player.sprite_index = 64 # @
enemy = mcrfpy.Entity(25, 10, grid=grid)
enemy.sprite_index = 69 # E
# Update initial visibility
player.update_visibility()
enemy.update_visibility()
# Global state
current_perspective = -1
perspective_names = ["Omniscient", "Player", "Enemy"]
# UI Setup
ui = mcrfpy.sceneUI("visibility_demo")
ui.append(grid)
grid.position = (50, 100)
grid.size = (900, 600) # 30*30, 20*30
# Title
title = mcrfpy.Caption("Interactive Visibility Demo", 350, 20)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Info displays
perspective_label = mcrfpy.Caption("Perspective: Omniscient", 50, 50)
perspective_label.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(perspective_label)
controls = mcrfpy.Caption("WASD: Move player | Arrows: Move enemy | Tab: Cycle perspective | Space: Update visibility | R: Reset", 50, 730)
controls.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(controls)
player_info = mcrfpy.Caption("Player: (5, 10)", 700, 50)
player_info.fill_color = mcrfpy.Color(100, 255, 100)
ui.append(player_info)
enemy_info = mcrfpy.Caption("Enemy: (25, 10)", 700, 70)
enemy_info.fill_color = mcrfpy.Color(255, 100, 100)
ui.append(enemy_info)
# Helper functions
def move_entity(entity, dx, dy):
"""Move entity if target is walkable"""
new_x = int(entity.x + dx)
new_y = int(entity.y + dy)
if 0 <= new_x < 30 and 0 <= new_y < 20:
cell = grid.at(new_x, new_y)
if cell.walkable:
entity.x = new_x
entity.y = new_y
entity.update_visibility()
return True
return False
def update_info():
"""Update info displays"""
player_info.text = f"Player: ({int(player.x)}, {int(player.y)})"
enemy_info.text = f"Enemy: ({int(enemy.x)}, {int(enemy.y)})"
def cycle_perspective():
"""Cycle through perspectives"""
global current_perspective
# Cycle: -1 → 0 → 1 → -1
current_perspective = (current_perspective + 2) % 3 - 1
grid.perspective = current_perspective
name = perspective_names[current_perspective + 1]
perspective_label.text = f"Perspective: {name}"
# Key handlers
def handle_keys(key, state):
"""Handle keyboard input"""
if state == "end": return
key = key.lower()
# Player movement (WASD)
if key == "w":
move_entity(player, 0, -1)
elif key == "s":
move_entity(player, 0, 1)
elif key == "a":
move_entity(player, -1, 0)
elif key == "d":
move_entity(player, 1, 0)
# Enemy movement (Arrows)
elif key == "up":
move_entity(enemy, 0, -1)
elif key == "down":
move_entity(enemy, 0, 1)
elif key == "left":
move_entity(enemy, -1, 0)
elif key == "right":
move_entity(enemy, 1, 0)
# Tab to cycle perspective
elif key == "tab":
cycle_perspective()
# Space to update visibility
elif key == "space":
player.update_visibility()
enemy.update_visibility()
print("Updated visibility for both entities")
# R to reset
elif key == "r":
player.x, player.y = 5, 10
enemy.x, enemy.y = 25, 10
player.update_visibility()
enemy.update_visibility()
update_info()
print("Reset positions")
# Q to quit
elif key == "q":
print("Exiting...")
sys.exit(0)
update_info()
# Set scene first
mcrfpy.setScene("visibility_demo")
# Register key handler (operates on current scene)
mcrfpy.keypressScene(handle_keys)
print("Interactive Visibility Demo")
print("===========================")
print("WASD: Move player (green @)")
print("Arrows: Move enemy (red E)")
print("Tab: Cycle perspective")
print("Space: Update visibility")
print("R: Reset positions")
print("Q: Quit")
print("\nCurrent perspective: Omniscient (shows all)")
print("Try moving entities and switching perspectives!")

View file

@ -0,0 +1,46 @@
#!/usr/bin/env python3
"""Simple interactive visibility test"""
import mcrfpy
import sys
# Create scene and grid
print("Creating scene...")
mcrfpy.createScene("vis_test")
print("Creating grid...")
grid = mcrfpy.Grid(grid_x=10, grid_y=10)
# Initialize grid
print("Initializing grid...")
for y in range(10):
for x in range(10):
cell = grid.at(x, y)
cell.walkable = True
cell.transparent = True
cell.color = mcrfpy.Color(100, 100, 120)
# Create entity
print("Creating entity...")
entity = mcrfpy.Entity(5, 5, grid=grid)
entity.sprite_index = 64
print("Updating visibility...")
entity.update_visibility()
# Set up UI
print("Setting up UI...")
ui = mcrfpy.sceneUI("vis_test")
ui.append(grid)
grid.position = (50, 50)
grid.size = (300, 300)
# Test perspective
print("Testing perspective...")
grid.perspective = -1 # Omniscient
print(f"Perspective set to: {grid.perspective}")
print("Setting scene...")
mcrfpy.setScene("vis_test")
print("Ready!")

View file

@ -0,0 +1,39 @@
#!/usr/bin/env python3
"""Simple visibility test without entity append"""
import mcrfpy
import sys
print("Simple visibility test...")
# Create scene and grid
mcrfpy.createScene("simple")
print("Scene created")
grid = mcrfpy.Grid(grid_x=5, grid_y=5)
print("Grid created")
# Create entity without appending
entity = mcrfpy.Entity(2, 2, grid=grid)
print(f"Entity created at ({entity.x}, {entity.y})")
# Check if gridstate is initialized
print(f"Gridstate length: {len(entity.gridstate)}")
# Try to access at method
try:
state = entity.at(0, 0)
print(f"at(0,0) returned: {state}")
print(f"visible: {state.visible}, discovered: {state.discovered}")
except Exception as e:
print(f"Error in at(): {e}")
# Try update_visibility
try:
entity.update_visibility()
print("update_visibility() succeeded")
except Exception as e:
print(f"Error in update_visibility(): {e}")
print("Test complete")
sys.exit(0)

View file

@ -0,0 +1,23 @@
#!/usr/bin/env python3
"""Trace interactive mode by monkey-patching"""
import sys
import mcrfpy
# Monkey-patch to detect interactive mode
original_ps1 = None
if hasattr(sys, 'ps1'):
original_ps1 = sys.ps1
class PS1Detector:
def __repr__(self):
import traceback
print("\n!!! sys.ps1 accessed! Stack trace:")
traceback.print_stack()
return ">>> "
# Set our detector
sys.ps1 = PS1Detector()
print("Trace script loaded, ps1 detector installed")
# Do nothing else - let the game run