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

215
tests/constructor_audit.py Normal file
View file

@ -0,0 +1,215 @@
#!/usr/bin/env python3
"""Audit current constructor argument handling for all UI classes"""
import mcrfpy
import sys
def audit_constructors():
"""Test current state of all UI constructors"""
print("=== CONSTRUCTOR AUDIT ===\n")
# Create test scene and texture
mcrfpy.createScene("audit")
texture = mcrfpy.Texture("assets/test_portraits.png", 32, 32)
# Test Frame
print("1. Frame Constructor Tests:")
print("-" * 40)
# No args
try:
f = mcrfpy.Frame()
print("✓ Frame() - works")
except Exception as e:
print(f"✗ Frame() - {e}")
# Traditional 4 args (x, y, w, h)
try:
f = mcrfpy.Frame(10, 20, 100, 50)
print("✓ Frame(10, 20, 100, 50) - works")
except Exception as e:
print(f"✗ Frame(10, 20, 100, 50) - {e}")
# Tuple pos + size
try:
f = mcrfpy.Frame((10, 20), (100, 50))
print("✓ Frame((10, 20), (100, 50)) - works")
except Exception as e:
print(f"✗ Frame((10, 20), (100, 50)) - {e}")
# Keywords
try:
f = mcrfpy.Frame(pos=(10, 20), size=(100, 50))
print("✓ Frame(pos=(10, 20), size=(100, 50)) - works")
except Exception as e:
print(f"✗ Frame(pos=(10, 20), size=(100, 50)) - {e}")
# Test Grid
print("\n2. Grid Constructor Tests:")
print("-" * 40)
# No args
try:
g = mcrfpy.Grid()
print("✓ Grid() - works")
except Exception as e:
print(f"✗ Grid() - {e}")
# Grid size only
try:
g = mcrfpy.Grid((10, 10))
print("✓ Grid((10, 10)) - works")
except Exception as e:
print(f"✗ Grid((10, 10)) - {e}")
# Grid size + texture
try:
g = mcrfpy.Grid((10, 10), texture)
print("✓ Grid((10, 10), texture) - works")
except Exception as e:
print(f"✗ Grid((10, 10), texture) - {e}")
# Full positional (expected: pos, size, grid_size, texture)
try:
g = mcrfpy.Grid((0, 0), (320, 320), (10, 10), texture)
print("✓ Grid((0, 0), (320, 320), (10, 10), texture) - works")
except Exception as e:
print(f"✗ Grid((0, 0), (320, 320), (10, 10), texture) - {e}")
# Keywords
try:
g = mcrfpy.Grid(pos=(0, 0), size=(320, 320), grid_size=(10, 10), texture=texture)
print("✓ Grid(pos=..., size=..., grid_size=..., texture=...) - works")
except Exception as e:
print(f"✗ Grid(pos=..., size=..., grid_size=..., texture=...) - {e}")
# Test Sprite
print("\n3. Sprite Constructor Tests:")
print("-" * 40)
# No args
try:
s = mcrfpy.Sprite()
print("✓ Sprite() - works")
except Exception as e:
print(f"✗ Sprite() - {e}")
# Position only
try:
s = mcrfpy.Sprite((10, 20))
print("✓ Sprite((10, 20)) - works")
except Exception as e:
print(f"✗ Sprite((10, 20)) - {e}")
# Position + texture
try:
s = mcrfpy.Sprite((10, 20), texture)
print("✓ Sprite((10, 20), texture) - works")
except Exception as e:
print(f"✗ Sprite((10, 20), texture) - {e}")
# Position + texture + sprite_index
try:
s = mcrfpy.Sprite((10, 20), texture, 5)
print("✓ Sprite((10, 20), texture, 5) - works")
except Exception as e:
print(f"✗ Sprite((10, 20), texture, 5) - {e}")
# Keywords
try:
s = mcrfpy.Sprite(pos=(10, 20), texture=texture, sprite_index=5)
print("✓ Sprite(pos=..., texture=..., sprite_index=...) - works")
except Exception as e:
print(f"✗ Sprite(pos=..., texture=..., sprite_index=...) - {e}")
# Test Caption
print("\n4. Caption Constructor Tests:")
print("-" * 40)
# No args
try:
c = mcrfpy.Caption()
print("✓ Caption() - works")
except Exception as e:
print(f"✗ Caption() - {e}")
# Text only
try:
c = mcrfpy.Caption("Hello")
print("✓ Caption('Hello') - works")
except Exception as e:
print(f"✗ Caption('Hello') - {e}")
# Position + text (expected order: pos, font, text)
try:
c = mcrfpy.Caption((10, 20), "Hello")
print("✓ Caption((10, 20), 'Hello') - works")
except Exception as e:
print(f"✗ Caption((10, 20), 'Hello') - {e}")
# Position + font + text
try:
c = mcrfpy.Caption((10, 20), 16, "Hello")
print("✓ Caption((10, 20), 16, 'Hello') - works")
except Exception as e:
print(f"✗ Caption((10, 20), 16, 'Hello') - {e}")
# Keywords
try:
c = mcrfpy.Caption(pos=(10, 20), font=16, text="Hello")
print("✓ Caption(pos=..., font=..., text=...) - works")
except Exception as e:
print(f"✗ Caption(pos=..., font=..., text=...) - {e}")
# Test Entity
print("\n5. Entity Constructor Tests:")
print("-" * 40)
# No args
try:
e = mcrfpy.Entity()
print("✓ Entity() - works")
except Exception as e:
print(f"✗ Entity() - {e}")
# Grid position only
try:
e = mcrfpy.Entity((5.0, 6.0))
print("✓ Entity((5.0, 6.0)) - works")
except Exception as e:
print(f"✗ Entity((5.0, 6.0)) - {e}")
# Grid position + texture
try:
e = mcrfpy.Entity((5.0, 6.0), texture)
print("✓ Entity((5.0, 6.0), texture) - works")
except Exception as e:
print(f"✗ Entity((5.0, 6.0), texture) - {e}")
# Grid position + texture + sprite_index
try:
e = mcrfpy.Entity((5.0, 6.0), texture, 3)
print("✓ Entity((5.0, 6.0), texture, 3) - works")
except Exception as e:
print(f"✗ Entity((5.0, 6.0), texture, 3) - {e}")
# Keywords
try:
e = mcrfpy.Entity(grid_pos=(5.0, 6.0), texture=texture, sprite_index=3)
print("✓ Entity(grid_pos=..., texture=..., sprite_index=...) - works")
except Exception as e:
print(f"✗ Entity(grid_pos=..., texture=..., sprite_index=...) - {e}")
print("\n=== AUDIT COMPLETE ===")
# Run audit
try:
audit_constructors()
print("\nPASS")
sys.exit(0)
except Exception as e:
print(f"\nFAIL: {e}")
import traceback
traceback.print_exc()
sys.exit(1)

View file

@ -0,0 +1,30 @@
#!/usr/bin/env python3
# Count format string characters
fmt = "|OOOOfOOifizfffi"
print(f"Format string: {fmt}")
# Remove the | prefix
fmt_chars = fmt[1:]
print(f"Format chars after |: {fmt_chars}")
print(f"Length: {len(fmt_chars)}")
# Count each type
o_count = fmt_chars.count('O')
f_count = fmt_chars.count('f')
i_count = fmt_chars.count('i')
z_count = fmt_chars.count('z')
s_count = fmt_chars.count('s')
print(f"\nCounts:")
print(f"O (objects): {o_count}")
print(f"f (floats): {f_count}")
print(f"i (ints): {i_count}")
print(f"z (strings): {z_count}")
print(f"s (strings): {s_count}")
print(f"Total: {o_count + f_count + i_count + z_count + s_count}")
# List out each position
print("\nPosition by position:")
for i, c in enumerate(fmt_chars):
print(f"{i+1}: {c}")

View file

@ -0,0 +1,81 @@
#!/usr/bin/env python3
"""
Demonstration of animation callbacks solving race conditions.
Shows how callbacks enable direct causality for game state changes.
"""
import mcrfpy
# Game state
player_moving = False
move_queue = []
def movement_complete(anim, target):
"""Called when player movement animation completes"""
global player_moving, move_queue
print("Movement animation completed!")
player_moving = False
# Process next move if queued
if move_queue:
next_pos = move_queue.pop(0)
move_player_to(next_pos)
else:
print("Player is now idle and ready for input")
def move_player_to(new_pos):
"""Move player with animation and proper state management"""
global player_moving
if player_moving:
print(f"Queueing move to {new_pos}")
move_queue.append(new_pos)
return
player_moving = True
print(f"Moving player to {new_pos}")
# Get player entity (placeholder for demo)
ui = mcrfpy.sceneUI("game")
player = ui[0] # Assume first element is player
# Animate movement with callback
x, y = new_pos
anim_x = mcrfpy.Animation("x", float(x), 0.5, "easeInOutQuad", callback=movement_complete)
anim_y = mcrfpy.Animation("y", float(y), 0.5, "easeInOutQuad")
anim_x.start(player)
anim_y.start(player)
def setup_demo():
"""Set up the demo scene"""
# Create scene
mcrfpy.createScene("game")
mcrfpy.setScene("game")
# Create player sprite
player = mcrfpy.Frame((100, 100), (32, 32), fill_color=(0, 255, 0))
ui = mcrfpy.sceneUI("game")
ui.append(player)
print("Demo: Animation callbacks for movement queue")
print("=" * 40)
# Simulate rapid movement commands
mcrfpy.setTimer("move1", lambda r: move_player_to((200, 100)), 100)
mcrfpy.setTimer("move2", lambda r: move_player_to((200, 200)), 200) # Will be queued
mcrfpy.setTimer("move3", lambda r: move_player_to((100, 200)), 300) # Will be queued
# Exit after demo
mcrfpy.setTimer("exit", lambda r: exit_demo(), 3000)
def exit_demo():
"""Exit the demo"""
print("\nDemo completed successfully!")
print("Callbacks ensure proper movement sequencing without race conditions")
import sys
sys.exit(0)
# Run the demo
setup_demo()

View file

@ -0,0 +1,146 @@
#!/usr/bin/env python3
"""
McRogueFace Animation Demo - Safe Version
=========================================
A safer, simpler version that demonstrates animations without crashes.
"""
import mcrfpy
import sys
# Configuration
DEMO_DURATION = 4.0
# Track state
current_demo = 0
subtitle = None
demo_items = []
def create_scene():
"""Create the demo scene"""
mcrfpy.createScene("demo")
mcrfpy.setScene("demo")
ui = mcrfpy.sceneUI("demo")
# Title
title = mcrfpy.Caption("Animation Demo", 500, 20)
title.fill_color = mcrfpy.Color(255, 255, 0)
title.outline = 2
ui.append(title)
# Subtitle
global subtitle
subtitle = mcrfpy.Caption("Starting...", 450, 60)
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(subtitle)
def clear_demo_items():
"""Clear demo items from scene"""
global demo_items
ui = mcrfpy.sceneUI("demo")
# Remove demo items by tracking what we added
for item in demo_items:
try:
# Find index of item
for i in range(len(ui)):
if i >= 2: # Skip title and subtitle
ui.remove(i)
break
except:
pass
demo_items = []
def demo1_basic():
"""Basic frame animations"""
global demo_items
clear_demo_items()
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 1: Basic Frame Animations"
# Create frame
f = mcrfpy.Frame(100, 150, 200, 100)
f.fill_color = mcrfpy.Color(50, 50, 150)
f.outline = 3
ui.append(f)
demo_items.append(f)
# Simple animations
mcrfpy.Animation("x", 600.0, 2.0, "easeInOut").start(f)
mcrfpy.Animation("w", 300.0, 2.0, "easeInOut").start(f)
mcrfpy.Animation("fill_color", (255, 100, 50, 200), 3.0, "linear").start(f)
def demo2_caption():
"""Caption animations"""
global demo_items
clear_demo_items()
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 2: Caption Animations"
# Moving caption
c1 = mcrfpy.Caption("Moving Text!", 100, 200)
c1.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(c1)
demo_items.append(c1)
mcrfpy.Animation("x", 700.0, 3.0, "easeOutBounce").start(c1)
# Typewriter
c2 = mcrfpy.Caption("", 100, 300)
c2.fill_color = mcrfpy.Color(0, 255, 255)
ui.append(c2)
demo_items.append(c2)
mcrfpy.Animation("text", "Typewriter effect...", 3.0, "linear").start(c2)
def demo3_multiple():
"""Multiple animations"""
global demo_items
clear_demo_items()
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 3: Multiple Animations"
# Create several frames
for i in range(5):
f = mcrfpy.Frame(100 + i * 120, 200, 80, 80)
f.fill_color = mcrfpy.Color(50 + i * 40, 100, 200 - i * 30)
ui.append(f)
demo_items.append(f)
# Animate each differently
target_y = 350 + i * 20
mcrfpy.Animation("y", float(target_y), 2.0, "easeInOut").start(f)
mcrfpy.Animation("opacity", 0.5, 3.0, "easeInOut").start(f)
def run_next_demo(runtime):
"""Run the next demo"""
global current_demo
demos = [demo1_basic, demo2_caption, demo3_multiple]
if current_demo < len(demos):
demos[current_demo]()
current_demo += 1
if current_demo < len(demos):
mcrfpy.setTimer("next", run_next_demo, int(DEMO_DURATION * 1000))
else:
subtitle.text = "Demo Complete!"
# Exit after a delay
def exit_program(rt):
print("Demo finished successfully!")
sys.exit(0)
mcrfpy.setTimer("exit", exit_program, 2000)
# Initialize
print("Starting Safe Animation Demo...")
create_scene()
# Start demos
mcrfpy.setTimer("start", run_next_demo, 500)

View file

@ -0,0 +1,616 @@
#!/usr/bin/env python3
"""
McRogueFace Animation Sizzle Reel
=================================
This script demonstrates EVERY animation type on EVERY UI object type.
It showcases all 30 easing functions, all animatable properties, and
special animation modes (delta, sprite sequences, text effects).
The script creates a comprehensive visual demonstration of the animation
system's capabilities, cycling through different objects and effects.
Author: Claude
Purpose: Complete animation system demonstration
"""
import mcrfpy
from mcrfpy import Color, Frame, Caption, Sprite, Grid, Entity, Texture, Animation
import sys
import math
# Configuration
SCENE_WIDTH = 1280
SCENE_HEIGHT = 720
DEMO_DURATION = 5.0 # Duration for each demo section
# All available easing functions
EASING_FUNCTIONS = [
"linear", "easeIn", "easeOut", "easeInOut",
"easeInQuad", "easeOutQuad", "easeInOutQuad",
"easeInCubic", "easeOutCubic", "easeInOutCubic",
"easeInQuart", "easeOutQuart", "easeInOutQuart",
"easeInSine", "easeOutSine", "easeInOutSine",
"easeInExpo", "easeOutExpo", "easeInOutExpo",
"easeInCirc", "easeOutCirc", "easeInOutCirc",
"easeInElastic", "easeOutElastic", "easeInOutElastic",
"easeInBack", "easeOutBack", "easeInOutBack",
"easeInBounce", "easeOutBounce", "easeInOutBounce"
]
# Track current demo state
current_demo = 0
demo_start_time = 0
demos = []
# Handle ESC key to exit
def handle_keypress(scene_name, keycode):
if keycode == 256: # ESC key
print("Exiting animation sizzle reel...")
sys.exit(0)
def create_demo_scene():
"""Create the main demo scene with title"""
mcrfpy.createScene("sizzle_reel")
mcrfpy.setScene("sizzle_reel")
mcrfpy.keypressScene(handle_keypress)
ui = mcrfpy.sceneUI("sizzle_reel")
# Title caption
title = Caption("McRogueFace Animation Sizzle Reel",
SCENE_WIDTH/2 - 200, 20)
title.fill_color = Color(255, 255, 0)
title.outline = 2
title.outline_color = Color(0, 0, 0)
ui.append(title)
# Subtitle showing current demo
global subtitle
subtitle = Caption("Initializing...",
SCENE_WIDTH/2 - 150, 60)
subtitle.fill_color = Color(200, 200, 200)
ui.append(subtitle)
return ui
def demo_frame_basic_animations(ui):
"""Demo 1: Basic frame animations - position, size, colors"""
subtitle.text = "Demo 1: Frame Basic Animations (Position, Size, Colors)"
# Create test frame
frame = Frame(100, 150, 200, 100)
frame.fill_color = Color(50, 50, 150)
frame.outline = 3
frame.outline_color = Color(255, 255, 255)
ui.append(frame)
# Position animations with different easings
x_anim = Animation("x", 800.0, 2.0, "easeInOutBack")
y_anim = Animation("y", 400.0, 2.0, "easeInOutElastic")
x_anim.start(frame)
y_anim.start(frame)
# Size animations
w_anim = Animation("w", 400.0, 3.0, "easeInOutCubic")
h_anim = Animation("h", 200.0, 3.0, "easeInOutCubic")
w_anim.start(frame)
h_anim.start(frame)
# Color animations - use tuples instead of Color objects
fill_anim = Animation("fill_color", (255, 100, 50, 200), 4.0, "easeInOutSine")
outline_anim = Animation("outline_color", (0, 255, 255, 255), 4.0, "easeOutBounce")
fill_anim.start(frame)
outline_anim.start(frame)
# Outline thickness animation
thickness_anim = Animation("outline", 10.0, 4.5, "easeInOutQuad")
thickness_anim.start(frame)
return frame
def demo_frame_opacity_zindex(ui):
"""Demo 2: Frame opacity and z-index animations"""
subtitle.text = "Demo 2: Frame Opacity & Z-Index Animations"
frames = []
colors = [
Color(255, 0, 0, 200),
Color(0, 255, 0, 200),
Color(0, 0, 255, 200),
Color(255, 255, 0, 200)
]
# Create overlapping frames
for i in range(4):
frame = Frame(200 + i*80, 200 + i*40, 200, 150)
frame.fill_color = colors[i]
frame.outline = 2
frame.z_index = i
ui.append(frame)
frames.append(frame)
# Animate opacity in waves
opacity_anim = Animation("opacity", 0.3, 2.0, "easeInOutSine")
opacity_anim.start(frame)
# Reverse opacity animation
opacity_back = Animation("opacity", 1.0, 2.0, "easeInOutSine", delta=False)
mcrfpy.setTimer(f"opacity_back_{i}", lambda t, f=frame, a=opacity_back: a.start(f), 2000)
# Z-index shuffle animation
z_anim = Animation("z_index", (i + 2) % 4, 3.0, "linear")
z_anim.start(frame)
return frames
def demo_caption_animations(ui):
"""Demo 3: Caption text animations and effects"""
subtitle.text = "Demo 3: Caption Animations (Text, Color, Position)"
# Basic caption with position animation
caption1 = Caption("Moving Text!", 100, 200)
caption1.fill_color = Color(255, 255, 255)
caption1.outline = 1
ui.append(caption1)
# Animate across screen with bounce
x_anim = Animation("x", 900.0, 3.0, "easeOutBounce")
x_anim.start(caption1)
# Color cycling caption
caption2 = Caption("Rainbow Colors", 400, 300)
caption2.outline = 2
ui.append(caption2)
# Cycle through colors - use tuples
color_anim1 = Animation("fill_color", (255, 0, 0, 255), 1.0, "linear")
color_anim2 = Animation("fill_color", (0, 255, 0, 255), 1.0, "linear")
color_anim3 = Animation("fill_color", (0, 0, 255, 255), 1.0, "linear")
color_anim4 = Animation("fill_color", (255, 255, 255, 255), 1.0, "linear")
color_anim1.start(caption2)
mcrfpy.setTimer("color2", lambda t: color_anim2.start(caption2), 1000)
mcrfpy.setTimer("color3", lambda t: color_anim3.start(caption2), 2000)
mcrfpy.setTimer("color4", lambda t: color_anim4.start(caption2), 3000)
# Typewriter effect caption
caption3 = Caption("", 100, 400)
caption3.fill_color = Color(0, 255, 255)
ui.append(caption3)
typewriter = Animation("text", "This text appears one character at a time...", 3.0, "linear")
typewriter.start(caption3)
# Size animation caption
caption4 = Caption("Growing Text", 400, 500)
caption4.fill_color = Color(255, 200, 0)
ui.append(caption4)
# Note: size animation would require font size property support
# For now, animate position to simulate growth
scale_sim = Animation("y", 480.0, 2.0, "easeInOutElastic")
scale_sim.start(caption4)
return [caption1, caption2, caption3, caption4]
def demo_sprite_animations(ui):
"""Demo 4: Sprite animations including sprite sequences"""
subtitle.text = "Demo 4: Sprite Animations (Position, Scale, Sprite Sequences)"
# Load a test texture (you'll need to adjust path)
try:
texture = Texture("assets/sprites/player.png", grid_size=(32, 32))
except:
# Fallback if texture not found
texture = None
if texture:
# Basic sprite with position animation
sprite1 = Sprite(100, 200, texture, sprite_index=0)
sprite1.scale = 2.0
ui.append(sprite1)
# Circular motion using sin/cos animations
# We'll use delta mode to create circular motion
x_circle = Animation("x", 300.0, 4.0, "easeInOutSine")
y_circle = Animation("y", 300.0, 4.0, "easeInOutCubic")
x_circle.start(sprite1)
y_circle.start(sprite1)
# Sprite sequence animation (walking cycle)
sprite2 = Sprite(500, 300, texture, sprite_index=0)
sprite2.scale = 3.0
ui.append(sprite2)
# Animate through sprite indices for animation
walk_cycle = Animation("sprite_index", [0, 1, 2, 3, 2, 1], 2.0, "linear")
walk_cycle.start(sprite2)
# Scale pulsing sprite
sprite3 = Sprite(800, 400, texture, sprite_index=4)
ui.append(sprite3)
# Note: scale animation would need to be supported
# For now use position to simulate
pulse_y = Animation("y", 380.0, 0.5, "easeInOutSine")
pulse_y.start(sprite3)
# Z-index animation for layering
sprite3_z = Animation("z_index", 10, 2.0, "linear")
sprite3_z.start(sprite3)
return [sprite1, sprite2, sprite3]
else:
# Create placeholder caption if no texture
no_texture = Caption("(Sprite demo requires texture file)", 400, 350)
no_texture.fill_color = Color(255, 100, 100)
ui.append(no_texture)
return [no_texture]
def demo_grid_animations(ui):
"""Demo 5: Grid animations (position, camera, zoom)"""
subtitle.text = "Demo 5: Grid Animations (Position, Camera Effects)"
# Create a grid
try:
texture = Texture("assets/sprites/tiles.png", grid_size=(16, 16))
except:
texture = None
# Grid constructor: Grid(grid_x, grid_y, texture, position, size)
# Note: tile dimensions are determined by texture's grid_size
grid = Grid(20, 15, texture, (100, 150), (480, 360)) # 20x24, 15x24
grid.fill_color = Color(20, 20, 40)
ui.append(grid)
# Fill with some test pattern
for y in range(15):
for x in range(20):
point = grid.at(x, y)
point.tilesprite = (x + y) % 4
point.walkable = ((x + y) % 3) != 0
if not point.walkable:
point.color = Color(100, 50, 50, 128)
# Animate grid position
grid_x = Animation("x", 400.0, 3.0, "easeInOutBack")
grid_x.start(grid)
# Camera pan animation (if supported)
# center_x = Animation("center", (10.0, 7.5), 4.0, "easeInOutCubic")
# center_x.start(grid)
# Create entities in the grid
if texture:
entity1 = Entity((5.0, 5.0), texture, 8) # position tuple, texture, sprite_index
entity1.scale = 1.5
grid.entities.append(entity1)
# Animate entity movement
entity_pos = Animation("position", (15.0, 10.0), 3.0, "easeInOutQuad")
entity_pos.start(entity1)
# Create patrolling entity
entity2 = Entity((10.0, 2.0), texture, 12) # position tuple, texture, sprite_index
grid.entities.append(entity2)
# Animate sprite changes
entity2_sprite = Animation("sprite_index", [12, 13, 14, 15, 14, 13], 2.0, "linear")
entity2_sprite.start(entity2)
return grid
def demo_complex_combinations(ui):
"""Demo 6: Complex multi-property animations"""
subtitle.text = "Demo 6: Complex Multi-Property Animations"
# Create a complex UI composition
main_frame = Frame(200, 200, 400, 300)
main_frame.fill_color = Color(30, 30, 60, 200)
main_frame.outline = 2
ui.append(main_frame)
# Child elements
title = Caption("Multi-Animation Demo", 20, 20)
title.fill_color = Color(255, 255, 255)
main_frame.children.append(title)
# Animate everything at once
# Frame animations
frame_x = Animation("x", 600.0, 3.0, "easeInOutElastic")
frame_w = Animation("w", 300.0, 2.5, "easeOutBack")
frame_fill = Animation("fill_color", (60, 30, 90, 220), 4.0, "easeInOutSine")
frame_outline = Animation("outline", 8.0, 3.0, "easeInOutQuad")
frame_x.start(main_frame)
frame_w.start(main_frame)
frame_fill.start(main_frame)
frame_outline.start(main_frame)
# Title animations
title_color = Animation("fill_color", (255, 200, 0, 255), 2.0, "easeOutBounce")
title_color.start(title)
# Add animated sub-frames
for i in range(3):
sub_frame = Frame(50 + i * 100, 100, 80, 80)
sub_frame.fill_color = Color(100 + i*50, 50, 200 - i*50, 180)
main_frame.children.append(sub_frame)
# Rotate positions using delta animations
sub_y = Animation("y", 50.0, 2.0, "easeInOutSine", delta=True)
sub_y.start(sub_frame)
return main_frame
def demo_easing_showcase(ui):
"""Demo 7: Showcase all 30 easing functions"""
subtitle.text = "Demo 7: All 30 Easing Functions Showcase"
# Create small frames for each easing function
frames_per_row = 6
frame_size = 180
spacing = 10
for i, easing in enumerate(EASING_FUNCTIONS[:12]): # First 12 easings
row = i // frames_per_row
col = i % frames_per_row
x = 50 + col * (frame_size + spacing)
y = 150 + row * (60 + spacing)
# Create indicator frame
frame = Frame(x, y, 20, 20)
frame.fill_color = Color(100, 200, 255)
frame.outline = 1
ui.append(frame)
# Label
label = Caption(easing, x, y - 20)
label.fill_color = Color(200, 200, 200)
ui.append(label)
# Animate using this easing
move_anim = Animation("x", x + frame_size - 20, 3.0, easing)
move_anim.start(frame)
# Continue with remaining easings after a delay
def show_more_easings(runtime):
for j, easing in enumerate(EASING_FUNCTIONS[12:24]): # Next 12
row = j // frames_per_row + 2
col = j % frames_per_row
x = 50 + col * (frame_size + spacing)
y = 150 + row * (60 + spacing)
frame2 = Frame(x, y, 20, 20)
frame2.fill_color = Color(255, 150, 100)
frame2.outline = 1
ui.append(frame2)
label2 = Caption(easing, x, y - 20)
label2.fill_color = Color(200, 200, 200)
ui.append(label2)
move_anim2 = Animation("x", x + frame_size - 20, 3.0, easing)
move_anim2.start(frame2)
mcrfpy.setTimer("more_easings", show_more_easings, 1000)
# Show final easings
def show_final_easings(runtime):
for k, easing in enumerate(EASING_FUNCTIONS[24:]): # Last 6
row = k // frames_per_row + 4
col = k % frames_per_row
x = 50 + col * (frame_size + spacing)
y = 150 + row * (60 + spacing)
frame3 = Frame(x, y, 20, 20)
frame3.fill_color = Color(150, 255, 150)
frame3.outline = 1
ui.append(frame3)
label3 = Caption(easing, x, y - 20)
label3.fill_color = Color(200, 200, 200)
ui.append(label3)
move_anim3 = Animation("x", x + frame_size - 20, 3.0, easing)
move_anim3.start(frame3)
mcrfpy.setTimer("final_easings", show_final_easings, 2000)
def demo_delta_animations(ui):
"""Demo 8: Delta mode animations (relative movements)"""
subtitle.text = "Demo 8: Delta Mode Animations (Relative Movements)"
# Create objects that will move relative to their position
frames = []
start_positions = [(100, 200), (300, 200), (500, 200), (700, 200)]
colors = [Color(255, 100, 100), Color(100, 255, 100),
Color(100, 100, 255), Color(255, 255, 100)]
for i, (x, y) in enumerate(start_positions):
frame = Frame(x, y, 80, 80)
frame.fill_color = colors[i]
frame.outline = 2
ui.append(frame)
frames.append(frame)
# Delta animations - move relative to current position
# Each frame moves by different amounts
dx = (i + 1) * 50
dy = math.sin(i) * 100
x_delta = Animation("x", dx, 2.0, "easeInOutBack", delta=True)
y_delta = Animation("y", dy, 2.0, "easeInOutElastic", delta=True)
x_delta.start(frame)
y_delta.start(frame)
# Create caption showing delta mode
delta_label = Caption("Delta mode: Relative animations from current position", 200, 400)
delta_label.fill_color = Color(255, 255, 255)
ui.append(delta_label)
# Animate the label with delta mode text append
text_delta = Animation("text", " - ANIMATED!", 2.0, "linear", delta=True)
text_delta.start(delta_label)
return frames
def demo_color_component_animations(ui):
"""Demo 9: Individual color channel animations"""
subtitle.text = "Demo 9: Color Component Animations (R, G, B, A channels)"
# Create frames to demonstrate individual color channel animations
base_frame = Frame(300, 200, 600, 300)
base_frame.fill_color = Color(128, 128, 128, 255)
base_frame.outline = 3
ui.append(base_frame)
# Labels for each channel
labels = ["Red", "Green", "Blue", "Alpha"]
positions = [(50, 50), (200, 50), (350, 50), (500, 50)]
for i, (label_text, (x, y)) in enumerate(zip(labels, positions)):
# Create label
label = Caption(label_text, x, y - 30)
label.fill_color = Color(255, 255, 255)
base_frame.children.append(label)
# Create demo frame for this channel
demo_frame = Frame(x, y, 100, 100)
demo_frame.fill_color = Color(100, 100, 100, 200)
demo_frame.outline = 2
base_frame.children.append(demo_frame)
# Animate individual color channel
if i == 0: # Red
r_anim = Animation("fill_color.r", 255, 3.0, "easeInOutSine")
r_anim.start(demo_frame)
elif i == 1: # Green
g_anim = Animation("fill_color.g", 255, 3.0, "easeInOutSine")
g_anim.start(demo_frame)
elif i == 2: # Blue
b_anim = Animation("fill_color.b", 255, 3.0, "easeInOutSine")
b_anim.start(demo_frame)
else: # Alpha
a_anim = Animation("fill_color.a", 50, 3.0, "easeInOutSine")
a_anim.start(demo_frame)
# Animate main frame outline color components in sequence
outline_r = Animation("outline_color.r", 255, 1.0, "linear")
outline_g = Animation("outline_color.g", 255, 1.0, "linear")
outline_b = Animation("outline_color.b", 0, 1.0, "linear")
outline_r.start(base_frame)
mcrfpy.setTimer("outline_g", lambda t: outline_g.start(base_frame), 1000)
mcrfpy.setTimer("outline_b", lambda t: outline_b.start(base_frame), 2000)
return base_frame
def demo_performance_stress_test(ui):
"""Demo 10: Performance test with many simultaneous animations"""
subtitle.text = "Demo 10: Performance Stress Test (100+ Simultaneous Animations)"
# Create many small objects with different animations
num_objects = 100
for i in range(num_objects):
# Random starting position
x = 100 + (i % 20) * 50
y = 150 + (i // 20) * 50
# Create small frame
size = 20 + (i % 3) * 10
frame = Frame(x, y, size, size)
# Random color
r = (i * 37) % 256
g = (i * 73) % 256
b = (i * 113) % 256
frame.fill_color = Color(r, g, b, 200)
frame.outline = 1
ui.append(frame)
# Random animation properties
target_x = 100 + (i % 15) * 70
target_y = 150 + (i // 15) * 70
duration = 2.0 + (i % 30) * 0.1
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
# Start multiple animations per object
x_anim = Animation("x", target_x, duration, easing)
y_anim = Animation("y", target_y, duration, easing)
opacity_anim = Animation("opacity", 0.3 + (i % 7) * 0.1, duration, "easeInOutSine")
x_anim.start(frame)
y_anim.start(frame)
opacity_anim.start(frame)
# Performance counter
perf_caption = Caption(f"Animating {num_objects * 3} properties simultaneously", 400, 600)
perf_caption.fill_color = Color(255, 255, 0)
ui.append(perf_caption)
def next_demo(runtime):
"""Cycle to the next demo"""
global current_demo, demo_start_time
# Clear the UI except title and subtitle
ui = mcrfpy.sceneUI("sizzle_reel")
# Keep only the first two elements (title and subtitle)
while len(ui) > 2:
# Remove from the end to avoid index issues
ui.remove(len(ui) - 1)
# Run the next demo
if current_demo < len(demos):
demos[current_demo](ui)
current_demo += 1
# Schedule next demo
if current_demo < len(demos):
mcrfpy.setTimer("next_demo", next_demo, int(DEMO_DURATION * 1000))
else:
# All demos complete
subtitle.text = "Animation Showcase Complete! Press ESC to exit."
complete = Caption("All animation types demonstrated!", 400, 350)
complete.fill_color = Color(0, 255, 0)
complete.outline = 2
ui.append(complete)
def run_sizzle_reel(runtime):
"""Main entry point - start the demo sequence"""
global demos
# List of all demo functions
demos = [
demo_frame_basic_animations,
demo_frame_opacity_zindex,
demo_caption_animations,
demo_sprite_animations,
demo_grid_animations,
demo_complex_combinations,
demo_easing_showcase,
demo_delta_animations,
demo_color_component_animations,
demo_performance_stress_test
]
# Start the first demo
next_demo(runtime)
# Initialize scene
ui = create_demo_scene()
# Start the sizzle reel after a short delay
mcrfpy.setTimer("start_sizzle", run_sizzle_reel, 500)
print("Starting McRogueFace Animation Sizzle Reel...")
print("This will demonstrate ALL animation types on ALL objects.")
print("Press ESC at any time to exit.")

View file

@ -0,0 +1,227 @@
#!/usr/bin/env python3
"""
McRogueFace Animation Sizzle Reel (Fixed)
=========================================
This script demonstrates EVERY animation type on EVERY UI object type.
Fixed version that works properly with the game loop.
"""
import mcrfpy
# Configuration
SCENE_WIDTH = 1280
SCENE_HEIGHT = 720
DEMO_DURATION = 5.0 # Duration for each demo section
# All available easing functions
EASING_FUNCTIONS = [
"linear", "easeIn", "easeOut", "easeInOut",
"easeInQuad", "easeOutQuad", "easeInOutQuad",
"easeInCubic", "easeOutCubic", "easeInOutCubic",
"easeInQuart", "easeOutQuart", "easeInOutQuart",
"easeInSine", "easeOutSine", "easeInOutSine",
"easeInExpo", "easeOutExpo", "easeInOutExpo",
"easeInCirc", "easeOutCirc", "easeInOutCirc",
"easeInElastic", "easeOutElastic", "easeInOutElastic",
"easeInBack", "easeOutBack", "easeInOutBack",
"easeInBounce", "easeOutBounce", "easeInOutBounce"
]
# Track current demo state
current_demo = 0
subtitle = None
def create_demo_scene():
"""Create the main demo scene with title"""
mcrfpy.createScene("sizzle_reel")
mcrfpy.setScene("sizzle_reel")
ui = mcrfpy.sceneUI("sizzle_reel")
# Title caption
title = mcrfpy.Caption("McRogueFace Animation Sizzle Reel",
SCENE_WIDTH/2 - 200, 20)
title.fill_color = mcrfpy.Color(255, 255, 0)
title.outline = 2
title.outline_color = mcrfpy.Color(0, 0, 0)
ui.append(title)
# Subtitle showing current demo
global subtitle
subtitle = mcrfpy.Caption("Initializing...",
SCENE_WIDTH/2 - 150, 60)
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(subtitle)
return ui
def demo_frame_basic_animations():
"""Demo 1: Basic frame animations - position, size, colors"""
ui = mcrfpy.sceneUI("sizzle_reel")
subtitle.text = "Demo 1: Frame Basic Animations (Position, Size, Colors)"
# Create test frame
frame = mcrfpy.Frame(100, 150, 200, 100)
frame.fill_color = mcrfpy.Color(50, 50, 150)
frame.outline = 3
frame.outline_color = mcrfpy.Color(255, 255, 255)
ui.append(frame)
# Position animations with different easings
x_anim = mcrfpy.Animation("x", 800.0, 2.0, "easeInOutBack")
y_anim = mcrfpy.Animation("y", 400.0, 2.0, "easeInOutElastic")
x_anim.start(frame)
y_anim.start(frame)
# Size animations
w_anim = mcrfpy.Animation("w", 400.0, 3.0, "easeInOutCubic")
h_anim = mcrfpy.Animation("h", 200.0, 3.0, "easeInOutCubic")
w_anim.start(frame)
h_anim.start(frame)
# Color animations
fill_anim = mcrfpy.Animation("fill_color", mcrfpy.Color(255, 100, 50, 200), 4.0, "easeInOutSine")
outline_anim = mcrfpy.Animation("outline_color", mcrfpy.Color(0, 255, 255), 4.0, "easeOutBounce")
fill_anim.start(frame)
outline_anim.start(frame)
# Outline thickness animation
thickness_anim = mcrfpy.Animation("outline", 10.0, 4.5, "easeInOutQuad")
thickness_anim.start(frame)
def demo_caption_animations():
"""Demo 2: Caption text animations and effects"""
ui = mcrfpy.sceneUI("sizzle_reel")
subtitle.text = "Demo 2: Caption Animations (Text, Color, Position)"
# Basic caption with position animation
caption1 = mcrfpy.Caption("Moving Text!", 100, 200)
caption1.fill_color = mcrfpy.Color(255, 255, 255)
caption1.outline = 1
ui.append(caption1)
# Animate across screen with bounce
x_anim = mcrfpy.Animation("x", 900.0, 3.0, "easeOutBounce")
x_anim.start(caption1)
# Color cycling caption
caption2 = mcrfpy.Caption("Rainbow Colors", 400, 300)
caption2.outline = 2
ui.append(caption2)
# Cycle through colors
color_anim1 = mcrfpy.Animation("fill_color", mcrfpy.Color(255, 0, 0), 1.0, "linear")
color_anim1.start(caption2)
# Typewriter effect caption
caption3 = mcrfpy.Caption("", 100, 400)
caption3.fill_color = mcrfpy.Color(0, 255, 255)
ui.append(caption3)
typewriter = mcrfpy.Animation("text", "This text appears one character at a time...", 3.0, "linear")
typewriter.start(caption3)
def demo_sprite_animations():
"""Demo 3: Sprite animations (if texture available)"""
ui = mcrfpy.sceneUI("sizzle_reel")
subtitle.text = "Demo 3: Sprite Animations"
# Create placeholder caption since texture might not exist
no_texture = mcrfpy.Caption("(Sprite demo - textures may not be loaded)", 400, 350)
no_texture.fill_color = mcrfpy.Color(255, 100, 100)
ui.append(no_texture)
def demo_performance_stress_test():
"""Demo 4: Performance test with many simultaneous animations"""
ui = mcrfpy.sceneUI("sizzle_reel")
subtitle.text = "Demo 4: Performance Test (50+ Simultaneous Animations)"
# Create many small objects with different animations
num_objects = 50
for i in range(num_objects):
# Random starting position
x = 100 + (i % 10) * 100
y = 150 + (i // 10) * 80
# Create small frame
size = 20 + (i % 3) * 10
frame = mcrfpy.Frame(x, y, size, size)
# Random color
r = (i * 37) % 256
g = (i * 73) % 256
b = (i * 113) % 256
frame.fill_color = mcrfpy.Color(r, g, b, 200)
frame.outline = 1
ui.append(frame)
# Random animation properties
target_x = 100 + (i % 8) * 120
target_y = 150 + (i // 8) * 100
duration = 2.0 + (i % 30) * 0.1
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
# Start multiple animations per object
x_anim = mcrfpy.Animation("x", float(target_x), duration, easing)
y_anim = mcrfpy.Animation("y", float(target_y), duration, easing)
opacity_anim = mcrfpy.Animation("opacity", 0.3 + (i % 7) * 0.1, duration, "easeInOutSine")
x_anim.start(frame)
y_anim.start(frame)
opacity_anim.start(frame)
# Performance counter
perf_caption = mcrfpy.Caption(f"Animating {num_objects * 3} properties simultaneously", 400, 600)
perf_caption.fill_color = mcrfpy.Color(255, 255, 0)
ui.append(perf_caption)
def clear_scene():
"""Clear the scene except title and subtitle"""
ui = mcrfpy.sceneUI("sizzle_reel")
# Keep only the first two elements (title and subtitle)
while len(ui) > 2:
ui.remove(2)
def run_demo_sequence(runtime):
"""Run through all demos"""
global current_demo
# Clear previous demo
clear_scene()
# Demo list
demos = [
demo_frame_basic_animations,
demo_caption_animations,
demo_sprite_animations,
demo_performance_stress_test
]
if current_demo < len(demos):
# Run current demo
demos[current_demo]()
current_demo += 1
# Schedule next demo
if current_demo < len(demos):
mcrfpy.setTimer("next_demo", run_demo_sequence, int(DEMO_DURATION * 1000))
else:
# All demos complete
subtitle.text = "Animation Showcase Complete!"
complete = mcrfpy.Caption("All animation types demonstrated!", 400, 350)
complete.fill_color = mcrfpy.Color(0, 255, 0)
complete.outline = 2
ui = mcrfpy.sceneUI("sizzle_reel")
ui.append(complete)
# Initialize scene
print("Starting McRogueFace Animation Sizzle Reel...")
print("This will demonstrate animation types on various objects.")
ui = create_demo_scene()
# Start the demo sequence after a short delay
mcrfpy.setTimer("start_demos", run_demo_sequence, 500)

View file

@ -0,0 +1,307 @@
#!/usr/bin/env python3
"""
McRogueFace Animation Sizzle Reel v2
====================================
Fixed version with proper API usage for animations and collections.
"""
import mcrfpy
# Configuration
SCENE_WIDTH = 1280
SCENE_HEIGHT = 720
DEMO_DURATION = 5.0 # Duration for each demo section
# All available easing functions
EASING_FUNCTIONS = [
"linear", "easeIn", "easeOut", "easeInOut",
"easeInQuad", "easeOutQuad", "easeInOutQuad",
"easeInCubic", "easeOutCubic", "easeInOutCubic",
"easeInQuart", "easeOutQuart", "easeInOutQuart",
"easeInSine", "easeOutSine", "easeInOutSine",
"easeInExpo", "easeOutExpo", "easeInOutExpo",
"easeInCirc", "easeOutCirc", "easeInOutCirc",
"easeInElastic", "easeOutElastic", "easeInOutElastic",
"easeInBack", "easeOutBack", "easeInOutBack",
"easeInBounce", "easeOutBounce", "easeInOutBounce"
]
# Track current demo state
current_demo = 0
subtitle = None
demo_objects = [] # Track objects from current demo
def create_demo_scene():
"""Create the main demo scene with title"""
mcrfpy.createScene("sizzle_reel")
mcrfpy.setScene("sizzle_reel")
ui = mcrfpy.sceneUI("sizzle_reel")
# Title caption
title = mcrfpy.Caption("McRogueFace Animation Sizzle Reel",
SCENE_WIDTH/2 - 200, 20)
title.fill_color = mcrfpy.Color(255, 255, 0)
title.outline = 2
title.outline_color = mcrfpy.Color(0, 0, 0)
ui.append(title)
# Subtitle showing current demo
global subtitle
subtitle = mcrfpy.Caption("Initializing...",
SCENE_WIDTH/2 - 150, 60)
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(subtitle)
return ui
def demo_frame_basic_animations():
"""Demo 1: Basic frame animations - position, size, colors"""
global demo_objects
demo_objects = []
ui = mcrfpy.sceneUI("sizzle_reel")
subtitle.text = "Demo 1: Frame Basic Animations (Position, Size, Colors)"
# Create test frame
frame = mcrfpy.Frame(100, 150, 200, 100)
frame.fill_color = mcrfpy.Color(50, 50, 150)
frame.outline = 3
frame.outline_color = mcrfpy.Color(255, 255, 255)
ui.append(frame)
demo_objects.append(frame)
# Position animations with different easings
x_anim = mcrfpy.Animation("x", 800.0, 2.0, "easeInOutBack")
y_anim = mcrfpy.Animation("y", 400.0, 2.0, "easeInOutElastic")
x_anim.start(frame)
y_anim.start(frame)
# Size animations
w_anim = mcrfpy.Animation("w", 400.0, 3.0, "easeInOutCubic")
h_anim = mcrfpy.Animation("h", 200.0, 3.0, "easeInOutCubic")
w_anim.start(frame)
h_anim.start(frame)
# Color animations - use tuples instead of Color objects
fill_anim = mcrfpy.Animation("fill_color", (255, 100, 50, 200), 4.0, "easeInOutSine")
outline_anim = mcrfpy.Animation("outline_color", (0, 255, 255, 255), 4.0, "easeOutBounce")
fill_anim.start(frame)
outline_anim.start(frame)
# Outline thickness animation
thickness_anim = mcrfpy.Animation("outline", 10.0, 4.5, "easeInOutQuad")
thickness_anim.start(frame)
def demo_caption_animations():
"""Demo 2: Caption text animations and effects"""
global demo_objects
demo_objects = []
ui = mcrfpy.sceneUI("sizzle_reel")
subtitle.text = "Demo 2: Caption Animations (Text, Color, Position)"
# Basic caption with position animation
caption1 = mcrfpy.Caption("Moving Text!", 100, 200)
caption1.fill_color = mcrfpy.Color(255, 255, 255)
caption1.outline = 1
ui.append(caption1)
demo_objects.append(caption1)
# Animate across screen with bounce
x_anim = mcrfpy.Animation("x", 900.0, 3.0, "easeOutBounce")
x_anim.start(caption1)
# Color cycling caption
caption2 = mcrfpy.Caption("Rainbow Colors", 400, 300)
caption2.outline = 2
ui.append(caption2)
demo_objects.append(caption2)
# Cycle through colors using tuples
color_anim1 = mcrfpy.Animation("fill_color", (255, 0, 0, 255), 1.0, "linear")
color_anim1.start(caption2)
# Schedule color changes
def change_to_green(rt):
color_anim2 = mcrfpy.Animation("fill_color", (0, 255, 0, 255), 1.0, "linear")
color_anim2.start(caption2)
def change_to_blue(rt):
color_anim3 = mcrfpy.Animation("fill_color", (0, 0, 255, 255), 1.0, "linear")
color_anim3.start(caption2)
def change_to_white(rt):
color_anim4 = mcrfpy.Animation("fill_color", (255, 255, 255, 255), 1.0, "linear")
color_anim4.start(caption2)
mcrfpy.setTimer("color2", change_to_green, 1000)
mcrfpy.setTimer("color3", change_to_blue, 2000)
mcrfpy.setTimer("color4", change_to_white, 3000)
# Typewriter effect caption
caption3 = mcrfpy.Caption("", 100, 400)
caption3.fill_color = mcrfpy.Color(0, 255, 255)
ui.append(caption3)
demo_objects.append(caption3)
typewriter = mcrfpy.Animation("text", "This text appears one character at a time...", 3.0, "linear")
typewriter.start(caption3)
def demo_easing_showcase():
"""Demo 3: Showcase different easing functions"""
global demo_objects
demo_objects = []
ui = mcrfpy.sceneUI("sizzle_reel")
subtitle.text = "Demo 3: Easing Functions Showcase"
# Create small frames for each easing function
frames_per_row = 6
frame_width = 180
spacing = 10
# Show first 12 easings
for i, easing in enumerate(EASING_FUNCTIONS[:12]):
row = i // frames_per_row
col = i % frames_per_row
x = 50 + col * (frame_width + spacing)
y = 150 + row * (80 + spacing)
# Create indicator frame
frame = mcrfpy.Frame(x, y, 20, 20)
frame.fill_color = mcrfpy.Color(100, 200, 255)
frame.outline = 1
ui.append(frame)
demo_objects.append(frame)
# Label
label = mcrfpy.Caption(easing[:8], x, y - 20) # Truncate long names
label.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(label)
demo_objects.append(label)
# Animate using this easing
move_anim = mcrfpy.Animation("x", float(x + frame_width - 20), 3.0, easing)
move_anim.start(frame)
def demo_performance_stress_test():
"""Demo 4: Performance test with many simultaneous animations"""
global demo_objects
demo_objects = []
ui = mcrfpy.sceneUI("sizzle_reel")
subtitle.text = "Demo 4: Performance Test (50+ Simultaneous Animations)"
# Create many small objects with different animations
num_objects = 50
for i in range(num_objects):
# Starting position
x = 100 + (i % 10) * 100
y = 150 + (i // 10) * 80
# Create small frame
size = 20 + (i % 3) * 10
frame = mcrfpy.Frame(x, y, size, size)
# Random color
r = (i * 37) % 256
g = (i * 73) % 256
b = (i * 113) % 256
frame.fill_color = mcrfpy.Color(r, g, b, 200)
frame.outline = 1
ui.append(frame)
demo_objects.append(frame)
# Random animation properties
target_x = 100 + (i % 8) * 120
target_y = 150 + (i // 8) * 100
duration = 2.0 + (i % 30) * 0.1
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
# Start multiple animations per object
x_anim = mcrfpy.Animation("x", float(target_x), duration, easing)
y_anim = mcrfpy.Animation("y", float(target_y), duration, easing)
opacity_anim = mcrfpy.Animation("opacity", 0.3 + (i % 7) * 0.1, duration, "easeInOutSine")
x_anim.start(frame)
y_anim.start(frame)
opacity_anim.start(frame)
# Performance counter
perf_caption = mcrfpy.Caption(f"Animating {num_objects * 3} properties simultaneously", 350, 600)
perf_caption.fill_color = mcrfpy.Color(255, 255, 0)
ui.append(perf_caption)
demo_objects.append(perf_caption)
def clear_scene():
"""Clear the scene except title and subtitle"""
global demo_objects
ui = mcrfpy.sceneUI("sizzle_reel")
# Remove all demo objects
for obj in demo_objects:
try:
# Find index of object
for i in range(len(ui)):
if ui[i] is obj:
ui.remove(ui[i])
break
except:
pass # Object might already be removed
demo_objects = []
# Clean up any timers
for timer_name in ["color2", "color3", "color4"]:
try:
mcrfpy.delTimer(timer_name)
except:
pass
def run_demo_sequence(runtime):
"""Run through all demos"""
global current_demo
# Clear previous demo
clear_scene()
# Demo list
demos = [
demo_frame_basic_animations,
demo_caption_animations,
demo_easing_showcase,
demo_performance_stress_test
]
if current_demo < len(demos):
# Run current demo
demos[current_demo]()
current_demo += 1
# Schedule next demo
if current_demo < len(demos):
mcrfpy.setTimer("next_demo", run_demo_sequence, int(DEMO_DURATION * 1000))
else:
# Final demo completed
def show_complete(rt):
subtitle.text = "Animation Showcase Complete!"
complete = mcrfpy.Caption("All animation types demonstrated!", 400, 350)
complete.fill_color = mcrfpy.Color(0, 255, 0)
complete.outline = 2
ui = mcrfpy.sceneUI("sizzle_reel")
ui.append(complete)
mcrfpy.setTimer("complete", show_complete, 3000)
# Initialize scene
print("Starting McRogueFace Animation Sizzle Reel v2...")
print("This will demonstrate animation types on various objects.")
ui = create_demo_scene()
# Start the demo sequence after a short delay
mcrfpy.setTimer("start_demos", run_demo_sequence, 500)

View file

@ -0,0 +1,316 @@
#!/usr/bin/env python3
"""
McRogueFace Animation Sizzle Reel - Working Version
===================================================
Complete demonstration of all animation capabilities.
Fixed to work properly with the API.
"""
import mcrfpy
import sys
import math
# Configuration
DEMO_DURATION = 7.0 # Duration for each demo
# All available easing functions
EASING_FUNCTIONS = [
"linear", "easeIn", "easeOut", "easeInOut",
"easeInQuad", "easeOutQuad", "easeInOutQuad",
"easeInCubic", "easeOutCubic", "easeInOutCubic",
"easeInQuart", "easeOutQuart", "easeInOutQuart",
"easeInSine", "easeOutSine", "easeInOutSine",
"easeInExpo", "easeOutExpo", "easeInOutExpo",
"easeInCirc", "easeOutCirc", "easeInOutCirc",
"easeInElastic", "easeOutElastic", "easeInOutElastic",
"easeInBack", "easeOutBack", "easeInOutBack",
"easeInBounce", "easeOutBounce", "easeInOutBounce"
]
# Track state
current_demo = 0
subtitle = None
demo_objects = []
def create_scene():
"""Create the demo scene with title"""
mcrfpy.createScene("sizzle")
mcrfpy.setScene("sizzle")
ui = mcrfpy.sceneUI("sizzle")
# Title
title = mcrfpy.Caption("McRogueFace Animation Sizzle Reel", 340, 20)
title.fill_color = mcrfpy.Color(255, 255, 0)
title.outline = 2
title.outline_color = mcrfpy.Color(0, 0, 0)
ui.append(title)
# Subtitle
global subtitle
subtitle = mcrfpy.Caption("Initializing...", 400, 60)
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(subtitle)
def clear_demo():
"""Clear demo objects"""
global demo_objects
ui = mcrfpy.sceneUI("sizzle")
# Remove items starting from the end
# Skip first 2 (title and subtitle)
while len(ui) > 2:
ui.remove(len(ui) - 1)
demo_objects = []
def demo1_frame_basics():
"""Demo 1: Basic frame animations"""
clear_demo()
print("demo1")
subtitle.text = "Demo 1: Frame Animations (Position, Size, Color)"
ui = mcrfpy.sceneUI("sizzle")
# Create frame
frame = mcrfpy.Frame(100, 150, 200, 100)
frame.fill_color = mcrfpy.Color(50, 50, 150)
frame.outline = 3
frame.outline_color = mcrfpy.Color(255, 255, 255)
ui.append(frame)
# Animate properties
mcrfpy.Animation("x", 700.0, 2.5, "easeInOutBack").start(frame)
mcrfpy.Animation("y", 350.0, 2.5, "easeInOutElastic").start(frame)
mcrfpy.Animation("w", 350.0, 3.0, "easeInOutCubic").start(frame)
mcrfpy.Animation("h", 180.0, 3.0, "easeInOutCubic").start(frame)
mcrfpy.Animation("fill_color", (255, 100, 50, 200), 4.0, "easeInOutSine").start(frame)
mcrfpy.Animation("outline_color", (0, 255, 255, 255), 4.0, "easeOutBounce").start(frame)
mcrfpy.Animation("outline", 8.0, 4.0, "easeInOutQuad").start(frame)
def demo2_opacity_zindex():
"""Demo 2: Opacity and z-index animations"""
clear_demo()
print("demo2")
subtitle.text = "Demo 2: Opacity & Z-Index Animations"
ui = mcrfpy.sceneUI("sizzle")
# Create overlapping frames
colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0)]
for i in range(4):
frame = mcrfpy.Frame(200 + i*80, 200 + i*40, 200, 150)
frame.fill_color = mcrfpy.Color(colors[i][0], colors[i][1], colors[i][2], 200)
frame.outline = 2
frame.z_index = i
ui.append(frame)
# Animate opacity
mcrfpy.Animation("opacity", 0.3, 2.0, "easeInOutSine").start(frame)
# Schedule opacity return
def return_opacity(rt):
for i in range(4):
mcrfpy.Animation("opacity", 1.0, 2.0, "easeInOutSine").start(ui[i])
mcrfpy.setTimer(f"opacity_{i}", return_opacity, 2100)
def demo3_captions():
"""Demo 3: Caption animations"""
clear_demo()
print("demo3")
subtitle.text = "Demo 3: Caption Animations"
ui = mcrfpy.sceneUI("sizzle")
# Moving caption
c1 = mcrfpy.Caption("Bouncing Text!", 100, 200)
c1.fill_color = mcrfpy.Color(255, 255, 255)
c1.outline = 1
ui.append(c1)
mcrfpy.Animation("x", 800.0, 3.0, "easeOutBounce").start(c1)
# Color cycling caption
c2 = mcrfpy.Caption("Color Cycle", 400, 300)
c2.outline = 2
ui.append(c2)
# Animate through colors
def cycle_colors():
anim = mcrfpy.Animation("fill_color", (255, 0, 0, 255), 0.5, "linear")
anim.start(c2)
def to_green(rt):
mcrfpy.Animation("fill_color", (0, 255, 0, 255), 0.5, "linear").start(c2)
def to_blue(rt):
mcrfpy.Animation("fill_color", (0, 0, 255, 255), 0.5, "linear").start(c2)
def to_white(rt):
mcrfpy.Animation("fill_color", (255, 255, 255, 255), 0.5, "linear").start(c2)
mcrfpy.setTimer("c_green", to_green, 600)
mcrfpy.setTimer("c_blue", to_blue, 1200)
mcrfpy.setTimer("c_white", to_white, 1800)
cycle_colors()
# Typewriter effect
c3 = mcrfpy.Caption("", 100, 400)
c3.fill_color = mcrfpy.Color(0, 255, 255)
ui.append(c3)
mcrfpy.Animation("text", "This text appears one character at a time...", 3.0, "linear").start(c3)
def demo4_easing_showcase():
"""Demo 4: Showcase easing functions"""
clear_demo()
print("demo4")
subtitle.text = "Demo 4: 30 Easing Functions"
ui = mcrfpy.sceneUI("sizzle")
# Show first 15 easings
for i in range(15):
row = i // 5
col = i % 5
x = 80 + col * 180
y = 150 + row * 120
# Create frame
f = mcrfpy.Frame(x, y, 20, 20)
f.fill_color = mcrfpy.Color(100, 150, 255)
f.outline = 1
ui.append(f)
# Label
label = mcrfpy.Caption(EASING_FUNCTIONS[i][:10], x, y - 20)
label.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(label)
# Animate with this easing
mcrfpy.Animation("x", float(x + 140), 3.0, EASING_FUNCTIONS[i]).start(f)
def demo5_performance():
"""Demo 5: Many simultaneous animations"""
clear_demo()
print("demo5")
subtitle.text = "Demo 5: 50+ Simultaneous Animations"
ui = mcrfpy.sceneUI("sizzle")
# Create many animated objects
for i in range(50):
print(f"{i}...",end='',flush=True)
x = 100 + (i % 10) * 90
y = 120 + (i // 10) * 80
f = mcrfpy.Frame(x, y, 25, 25)
r = (i * 37) % 256
g = (i * 73) % 256
b = (i * 113) % 256
f.fill_color = (r, g, b, 200) #mcrfpy.Color(r, g, b, 200)
f.outline = 1
ui.append(f)
# Random animations
target_x = 150 + (i % 8) * 100
target_y = 150 + (i // 8) * 85
duration = 2.0 + (i % 30) * 0.1
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
mcrfpy.Animation("x", float(target_x), duration, easing).start(f)
mcrfpy.Animation("y", float(target_y), duration, easing).start(f)
mcrfpy.Animation("opacity", 0.3 + (i % 7) * 0.1, 2.5, "easeInOutSine").start(f)
def demo6_delta_mode():
"""Demo 6: Delta mode animations"""
clear_demo()
print("demo6")
subtitle.text = "Demo 6: Delta Mode (Relative Movement)"
ui = mcrfpy.sceneUI("sizzle")
# Create frames that move relative to position
positions = [(100, 300), (300, 300), (500, 300), (700, 300)]
colors = [(255, 100, 100), (100, 255, 100), (100, 100, 255), (255, 255, 100)]
for i, ((x, y), color) in enumerate(zip(positions, colors)):
f = mcrfpy.Frame(x, y, 60, 60)
f.fill_color = mcrfpy.Color(color[0], color[1], color[2])
f.outline = 2
ui.append(f)
# Delta animations - move by amount, not to position
dx = (i + 1) * 30
dy = math.sin(i * 0.5) * 50
mcrfpy.Animation("x", float(dx), 2.0, "easeInOutBack", delta=True).start(f)
mcrfpy.Animation("y", float(dy), 2.0, "easeInOutElastic", delta=True).start(f)
# Caption explaining delta mode
info = mcrfpy.Caption("Delta mode: animations move BY amount, not TO position", 200, 450)
info.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(info)
def run_next_demo(runtime):
"""Run the next demo in sequence"""
global current_demo
demos = [
demo1_frame_basics,
demo2_opacity_zindex,
demo3_captions,
demo4_easing_showcase,
demo5_performance,
demo6_delta_mode
]
if current_demo < len(demos):
# Clean up timers from previous demo
for timer in ["opacity_0", "opacity_1", "opacity_2", "opacity_3",
"c_green", "c_blue", "c_white"]:
try:
mcrfpy.delTimer(timer)
except:
pass
# Run next demo
print(f"Run next: {current_demo}")
demos[current_demo]()
current_demo += 1
# Schedule next demo
if current_demo < len(demos):
#mcrfpy.setTimer("next_demo", run_next_demo, int(DEMO_DURATION * 1000))
pass
else:
current_demo = 0
# All done
#subtitle.text = "Animation Showcase Complete!"
#complete = mcrfpy.Caption("All animations demonstrated successfully!", 350, 350)
#complete.fill_color = mcrfpy.Color(0, 255, 0)
#complete.outline = 2
#ui = mcrfpy.sceneUI("sizzle")
#ui.append(complete)
#
## Exit after delay
#def exit_program(rt):
# print("\nSizzle reel completed successfully!")
# sys.exit(0)
#mcrfpy.setTimer("exit", exit_program, 3000)
# Handle ESC key
def handle_keypress(scene_name, keycode):
if keycode == 256: # ESC
print("\nExiting...")
sys.exit(0)
# Initialize
print("Starting McRogueFace Animation Sizzle Reel...")
print("This demonstrates all animation capabilities.")
print("Press ESC to exit at any time.")
create_scene()
mcrfpy.keypressScene(handle_keypress)
# Start the show
mcrfpy.setTimer("start", run_next_demo, int(DEMO_DURATION * 1000))

View file

@ -0,0 +1,207 @@
#!/usr/bin/env python3
"""
McRogueFace API Demo - Final Version
====================================
Complete API demonstration with proper error handling.
Tests all constructors and methods systematically.
"""
import mcrfpy
import sys
def print_section(title):
"""Print a section header"""
print("\n" + "="*60)
print(f" {title}")
print("="*60)
def print_test(name, success=True):
"""Print test result"""
status = "" if success else ""
print(f" {status} {name}")
def test_colors():
"""Test Color API"""
print_section("COLOR TESTS")
try:
# Basic constructors
c1 = mcrfpy.Color(255, 0, 0) # RGB
print_test(f"Color(255,0,0) = ({c1.r},{c1.g},{c1.b},{c1.a})")
c2 = mcrfpy.Color(100, 150, 200, 128) # RGBA
print_test(f"Color(100,150,200,128) = ({c2.r},{c2.g},{c2.b},{c2.a})")
# Property modification
c1.r = 128
c1.g = 128
c1.b = 128
c1.a = 200
print_test(f"Modified color = ({c1.r},{c1.g},{c1.b},{c1.a})")
except Exception as e:
print_test(f"Color test failed: {e}", False)
def test_frames():
"""Test Frame API"""
print_section("FRAME TESTS")
# Create scene
mcrfpy.createScene("test")
mcrfpy.setScene("test")
ui = mcrfpy.sceneUI("test")
try:
# Constructors
f1 = mcrfpy.Frame()
print_test(f"Frame() at ({f1.x},{f1.y}) size ({f1.w},{f1.h})")
f2 = mcrfpy.Frame(100, 50)
print_test(f"Frame(100,50) at ({f2.x},{f2.y})")
f3 = mcrfpy.Frame(200, 100, 150, 75)
print_test(f"Frame(200,100,150,75) size ({f3.w},{f3.h})")
# Properties
f3.fill_color = mcrfpy.Color(100, 100, 200)
f3.outline = 3
f3.outline_color = mcrfpy.Color(255, 255, 0)
f3.opacity = 0.8
f3.visible = True
f3.z_index = 5
print_test(f"Frame properties set")
# Add to scene
ui.append(f3)
print_test(f"Frame added to scene")
# Children
child = mcrfpy.Frame(10, 10, 50, 50)
f3.children.append(child)
print_test(f"Child added, count = {len(f3.children)}")
except Exception as e:
print_test(f"Frame test failed: {e}", False)
def test_captions():
"""Test Caption API"""
print_section("CAPTION TESTS")
ui = mcrfpy.sceneUI("test")
try:
# Constructors
c1 = mcrfpy.Caption()
print_test(f"Caption() text='{c1.text}'")
c2 = mcrfpy.Caption("Hello World")
print_test(f"Caption('Hello World') at ({c2.x},{c2.y})")
c3 = mcrfpy.Caption("Test", 300, 200)
print_test(f"Caption with position at ({c3.x},{c3.y})")
# Properties
c3.text = "Modified"
c3.fill_color = mcrfpy.Color(255, 255, 0)
c3.outline = 2
c3.outline_color = mcrfpy.Color(0, 0, 0)
print_test(f"Caption text='{c3.text}'")
ui.append(c3)
print_test("Caption added to scene")
except Exception as e:
print_test(f"Caption test failed: {e}", False)
def test_animations():
"""Test Animation API"""
print_section("ANIMATION TESTS")
ui = mcrfpy.sceneUI("test")
try:
# Create target
frame = mcrfpy.Frame(50, 50, 100, 100)
frame.fill_color = mcrfpy.Color(100, 100, 100)
ui.append(frame)
# Basic animations
a1 = mcrfpy.Animation("x", 300.0, 2.0)
print_test("Animation created (position)")
a2 = mcrfpy.Animation("opacity", 0.5, 1.5, "easeInOut")
print_test("Animation with easing")
a3 = mcrfpy.Animation("fill_color", (255, 0, 0, 255), 2.0)
print_test("Color animation (tuple)")
# Start animations
a1.start(frame)
a2.start(frame)
a3.start(frame)
print_test("Animations started")
# Check properties
print_test(f"Duration = {a1.duration}")
print_test(f"Elapsed = {a1.elapsed}")
print_test(f"Complete = {a1.is_complete}")
except Exception as e:
print_test(f"Animation test failed: {e}", False)
def test_collections():
"""Test collection operations"""
print_section("COLLECTION TESTS")
ui = mcrfpy.sceneUI("test")
try:
# Clear scene
while len(ui) > 0:
ui.remove(ui[len(ui)-1])
print_test(f"Scene cleared, length = {len(ui)}")
# Add items
for i in range(5):
f = mcrfpy.Frame(i*100, 50, 80, 80)
ui.append(f)
print_test(f"Added 5 frames, length = {len(ui)}")
# Access
first = ui[0]
print_test(f"Accessed ui[0] at ({first.x},{first.y})")
# Iteration
count = sum(1 for _ in ui)
print_test(f"Iteration count = {count}")
except Exception as e:
print_test(f"Collection test failed: {e}", False)
def run_tests():
"""Run all tests"""
print("\n" + "="*60)
print(" McRogueFace API Test Suite")
print("="*60)
test_colors()
test_frames()
test_captions()
test_animations()
test_collections()
print("\n" + "="*60)
print(" Tests Complete")
print("="*60)
# Exit after delay
def exit_program(runtime):
print("\nExiting...")
sys.exit(0)
mcrfpy.setTimer("exit", exit_program, 3000)
# Run tests
print("Starting API tests...")
run_tests()

View file

@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""Debug the astar_vs_dijkstra demo issue"""
import mcrfpy
import sys
# Same setup as the demo
start_pos = (5, 10)
end_pos = (25, 10)
print("Debugging A* vs Dijkstra demo...")
print(f"Start: {start_pos}, End: {end_pos}")
# Create scene and grid
mcrfpy.createScene("debug")
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
# Initialize all as floor
print("\nInitializing 30x20 grid...")
for y in range(20):
for x in range(30):
grid.at(x, y).walkable = True
# Test path before obstacles
print("\nTest 1: Path with no obstacles")
path1 = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
print(f" Path: {path1[:5]}...{path1[-3:] if len(path1) > 5 else ''}")
print(f" Length: {len(path1)}")
# Add obstacles from the demo
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)],
]
print("\nAdding obstacles...")
wall_count = 0
for obstacle_group in obstacles:
for x, y in obstacle_group:
grid.at(x, y).walkable = False
wall_count += 1
if wall_count <= 5:
print(f" Wall at ({x}, {y})")
print(f" Total walls added: {wall_count}")
# Check specific cells
print(f"\nChecking key positions:")
print(f" Start ({start_pos[0]}, {start_pos[1]}): walkable={grid.at(start_pos[0], start_pos[1]).walkable}")
print(f" End ({end_pos[0]}, {end_pos[1]}): walkable={grid.at(end_pos[0], end_pos[1]).walkable}")
# Check if path is blocked
print(f"\nChecking horizontal line at y=10:")
blocked_x = []
for x in range(30):
if not grid.at(x, 10).walkable:
blocked_x.append(x)
print(f" Blocked x positions: {blocked_x}")
# Test path with obstacles
print("\nTest 2: Path with obstacles")
path2 = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
print(f" Path: {path2}")
print(f" Length: {len(path2)}")
# Check if there's any path at all
if not path2:
print("\n No path found! Checking why...")
# Check if we can reach the vertical wall gap
print("\n Testing path to wall gap at (15, 8):")
path_to_gap = grid.compute_astar_path(start_pos[0], start_pos[1], 15, 8)
print(f" Path to gap: {path_to_gap}")
# Check from gap to end
print("\n Testing path from gap (15, 8) to end:")
path_from_gap = grid.compute_astar_path(15, 8, end_pos[0], end_pos[1])
print(f" Path from gap: {path_from_gap}")
# Check walls more carefully
print("\nDetailed wall analysis:")
print(" Walls at x=25 (blocking end?):")
for y in range(5, 15):
print(f" ({25}, {y}): walkable={grid.at(25, y).walkable}")
def timer_cb(dt):
sys.exit(0)
ui = mcrfpy.sceneUI("debug")
ui.append(grid)
mcrfpy.setScene("debug")
mcrfpy.setTimer("exit", timer_cb, 100)

View file

@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""
Working Dijkstra Demo with Clear Visual Feedback
================================================
This demo shows pathfinding with high-contrast colors.
"""
import mcrfpy
import sys
# High contrast colors
WALL_COLOR = mcrfpy.Color(40, 20, 20) # Very dark red/brown for walls
FLOOR_COLOR = mcrfpy.Color(60, 60, 80) # Dark blue-gray for floors
PATH_COLOR = mcrfpy.Color(0, 255, 0) # Pure green for paths
START_COLOR = mcrfpy.Color(255, 0, 0) # Red for start
END_COLOR = mcrfpy.Color(0, 0, 255) # Blue for end
print("Dijkstra Demo - High Contrast")
print("==============================")
# Create scene
mcrfpy.createScene("dijkstra_demo")
# Create grid with exact layout from user
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
"..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':
cell.walkable = False
cell.color = WALL_COLOR
else:
cell.walkable = True
cell.color = FLOOR_COLOR
if char == 'E':
entity_positions.append((x, y))
print(f"Map created: {grid.grid_x}x{grid.grid_y}")
print(f"Entity positions: {entity_positions}")
# 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(f"Entity {i+1} at ({x}, {y})")
# Highlight a path immediately
if len(entities) >= 2:
e1, e2 = entities[0], entities[1]
print(f"\nCalculating path from Entity 1 ({e1.x}, {e1.y}) to Entity 2 ({e2.x}, {e2.y})...")
path = e1.path_to(int(e2.x), int(e2.y))
print(f"Path found: {path}")
print(f"Path length: {len(path)} steps")
if path:
print("\nHighlighting path in bright green...")
# Color start and end specially
grid.at(int(e1.x), int(e1.y)).color = START_COLOR
grid.at(int(e2.x), int(e2.y)).color = END_COLOR
# Color the path
for i, (x, y) in enumerate(path):
if i > 0 and i < len(path) - 1: # Skip start and end
grid.at(x, y).color = PATH_COLOR
print(f" Colored ({x}, {y}) green")
# Keypress handler
def handle_keypress(scene_name, keycode):
if keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
print("\nExiting...")
sys.exit(0)
elif keycode == 32: # Space
print("\nRefreshing path colors...")
# Re-color the path to ensure it's visible
if len(entities) >= 2 and path:
for x, y in path[1:-1]:
grid.at(x, y).color = PATH_COLOR
# Set up UI
ui = mcrfpy.sceneUI("dijkstra_demo")
ui.append(grid)
# Scale grid
grid.size = (560, 400) # 14*40, 10*40
grid.position = (120, 100)
# Add title
title = mcrfpy.Caption("Dijkstra Pathfinding - High Contrast", 200, 20)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add legend
legend1 = mcrfpy.Caption("Red=Start, Blue=End, Green=Path", 120, 520)
legend1.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(legend1)
legend2 = mcrfpy.Caption("Press Q to quit, SPACE to refresh", 120, 540)
legend2.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend2)
# Entity info
info = mcrfpy.Caption(f"Path: Entity 1 to 2 = {len(path) if 'path' in locals() else 0} steps", 120, 60)
info.fill_color = mcrfpy.Color(255, 255, 100)
ui.append(info)
# Set up input
mcrfpy.keypressScene(handle_keypress)
mcrfpy.setScene("dijkstra_demo")
print("\nDemo ready! The path should be clearly visible in bright green.")
print("Red = Start, Blue = End, Green = Path")
print("Press SPACE to refresh colors if needed.")

View file

@ -0,0 +1,306 @@
#!/usr/bin/env python3
"""
McRogueFace Exhaustive API Demo (Fixed)
=======================================
Fixed version that properly exits after tests complete.
"""
import mcrfpy
import sys
# Test configuration
VERBOSE = True # Print detailed information about each test
def print_section(title):
"""Print a section header"""
print("\n" + "="*60)
print(f" {title}")
print("="*60)
def print_test(test_name, success=True):
"""Print test result"""
status = "✓ PASS" if success else "✗ FAIL"
print(f" {status} - {test_name}")
def test_color_api():
"""Test all Color constructors and methods"""
print_section("COLOR API TESTS")
# Constructor variants
print("\n Constructors:")
# Empty constructor (defaults to white)
c1 = mcrfpy.Color()
print_test(f"Color() = ({c1.r}, {c1.g}, {c1.b}, {c1.a})")
# Single value (grayscale)
c2 = mcrfpy.Color(128)
print_test(f"Color(128) = ({c2.r}, {c2.g}, {c2.b}, {c2.a})")
# RGB only (alpha defaults to 255)
c3 = mcrfpy.Color(255, 128, 0)
print_test(f"Color(255, 128, 0) = ({c3.r}, {c3.g}, {c3.b}, {c3.a})")
# Full RGBA
c4 = mcrfpy.Color(100, 150, 200, 128)
print_test(f"Color(100, 150, 200, 128) = ({c4.r}, {c4.g}, {c4.b}, {c4.a})")
# Property access
print("\n Properties:")
c = mcrfpy.Color(10, 20, 30, 40)
print_test(f"Initial: r={c.r}, g={c.g}, b={c.b}, a={c.a}")
c.r = 200
c.g = 150
c.b = 100
c.a = 255
print_test(f"After modification: r={c.r}, g={c.g}, b={c.b}, a={c.a}")
return True
def test_frame_api():
"""Test all Frame constructors and methods"""
print_section("FRAME API TESTS")
# Create a test scene
mcrfpy.createScene("api_test")
mcrfpy.setScene("api_test")
ui = mcrfpy.sceneUI("api_test")
# Constructor variants
print("\n Constructors:")
# Empty constructor
f1 = mcrfpy.Frame()
print_test(f"Frame() - pos=({f1.x}, {f1.y}), size=({f1.w}, {f1.h})")
ui.append(f1)
# Position only
f2 = mcrfpy.Frame(100, 50)
print_test(f"Frame(100, 50) - pos=({f2.x}, {f2.y}), size=({f2.w}, {f2.h})")
ui.append(f2)
# Position and size
f3 = mcrfpy.Frame(200, 100, 150, 75)
print_test(f"Frame(200, 100, 150, 75) - pos=({f3.x}, {f3.y}), size=({f3.w}, {f3.h})")
ui.append(f3)
# Full constructor
f4 = mcrfpy.Frame(300, 200, 200, 100,
fill_color=mcrfpy.Color(100, 100, 200),
outline_color=mcrfpy.Color(255, 255, 0),
outline=3)
print_test("Frame with all parameters")
ui.append(f4)
# Properties
print("\n Properties:")
# Position and size
f = mcrfpy.Frame(10, 20, 30, 40)
print_test(f"Initial: x={f.x}, y={f.y}, w={f.w}, h={f.h}")
f.x = 50
f.y = 60
f.w = 70
f.h = 80
print_test(f"Modified: x={f.x}, y={f.y}, w={f.w}, h={f.h}")
# Colors
f.fill_color = mcrfpy.Color(255, 0, 0, 128)
f.outline_color = mcrfpy.Color(0, 255, 0)
f.outline = 5.0
print_test(f"Colors set, outline={f.outline}")
# Visibility and opacity
f.visible = False
f.opacity = 0.5
print_test(f"visible={f.visible}, opacity={f.opacity}")
f.visible = True # Reset
# Z-index
f.z_index = 10
print_test(f"z_index={f.z_index}")
# Children collection
child1 = mcrfpy.Frame(5, 5, 20, 20)
child2 = mcrfpy.Frame(30, 5, 20, 20)
f.children.append(child1)
f.children.append(child2)
print_test(f"children.count = {len(f.children)}")
return True
def test_caption_api():
"""Test all Caption constructors and methods"""
print_section("CAPTION API TESTS")
ui = mcrfpy.sceneUI("api_test")
# Constructor variants
print("\n Constructors:")
# Empty constructor
c1 = mcrfpy.Caption()
print_test(f"Caption() - text='{c1.text}', pos=({c1.x}, {c1.y})")
ui.append(c1)
# Text only
c2 = mcrfpy.Caption("Hello World")
print_test(f"Caption('Hello World') - pos=({c2.x}, {c2.y})")
ui.append(c2)
# Text and position
c3 = mcrfpy.Caption("Positioned Text", 100, 50)
print_test(f"Caption('Positioned Text', 100, 50)")
ui.append(c3)
# Full constructor
c5 = mcrfpy.Caption("Styled Text", 300, 150,
fill_color=mcrfpy.Color(255, 255, 0),
outline_color=mcrfpy.Color(255, 0, 0),
outline=2)
print_test("Caption with all style parameters")
ui.append(c5)
# Properties
print("\n Properties:")
c = mcrfpy.Caption("Test Caption", 10, 20)
# Text
c.text = "Modified Text"
print_test(f"text = '{c.text}'")
# Position
c.x = 50
c.y = 60
print_test(f"position = ({c.x}, {c.y})")
# Colors and style
c.fill_color = mcrfpy.Color(0, 255, 255)
c.outline_color = mcrfpy.Color(255, 0, 255)
c.outline = 3.0
print_test("Colors and outline set")
# Size (read-only, computed from text)
print_test(f"size (computed) = ({c.w}, {c.h})")
return True
def test_animation_api():
"""Test Animation class API"""
print_section("ANIMATION API TESTS")
ui = mcrfpy.sceneUI("api_test")
print("\n Animation Constructors:")
# Basic animation
anim1 = mcrfpy.Animation("x", 100.0, 2.0)
print_test("Animation('x', 100.0, 2.0)")
# With easing
anim2 = mcrfpy.Animation("y", 200.0, 3.0, "easeInOut")
print_test("Animation with easing='easeInOut'")
# Delta mode
anim3 = mcrfpy.Animation("w", 50.0, 1.5, "linear", delta=True)
print_test("Animation with delta=True")
# Color animation (as tuple)
anim4 = mcrfpy.Animation("fill_color", (255, 0, 0, 255), 2.0)
print_test("Animation with Color tuple target")
# Vector animation
anim5 = mcrfpy.Animation("position", (10.0, 20.0), 2.5, "easeOutBounce")
print_test("Animation with position tuple")
# Sprite sequence
anim6 = mcrfpy.Animation("sprite_index", [0, 1, 2, 3, 2, 1], 2.0)
print_test("Animation with sprite sequence")
# Properties
print("\n Animation Properties:")
# Check properties
print_test(f"property = '{anim1.property}'")
print_test(f"duration = {anim1.duration}")
print_test(f"elapsed = {anim1.elapsed}")
print_test(f"is_complete = {anim1.is_complete}")
print_test(f"is_delta = {anim3.is_delta}")
# Methods
print("\n Animation Methods:")
# Create test frame
frame = mcrfpy.Frame(50, 50, 100, 100)
frame.fill_color = mcrfpy.Color(100, 100, 100)
ui.append(frame)
# Start animation
anim1.start(frame)
print_test("start() called on frame")
# Test some easing functions
print("\n Sample Easing Functions:")
easings = ["linear", "easeIn", "easeOut", "easeInOut", "easeInBounce", "easeOutElastic"]
for easing in easings:
try:
test_anim = mcrfpy.Animation("x", 100.0, 1.0, easing)
print_test(f"Easing '{easing}'")
except:
print_test(f"Easing '{easing}' failed", False)
return True
def run_all_tests():
"""Run all API tests"""
print("\n" + "="*60)
print(" McRogueFace Exhaustive API Test Suite (Fixed)")
print(" Testing constructors and methods...")
print("="*60)
# Run each test category
test_functions = [
test_color_api,
test_frame_api,
test_caption_api,
test_animation_api
]
passed = 0
failed = 0
for test_func in test_functions:
try:
if test_func():
passed += 1
else:
failed += 1
except Exception as e:
print(f"\n ERROR in {test_func.__name__}: {e}")
failed += 1
# Summary
print("\n" + "="*60)
print(f" TEST SUMMARY: {passed} passed, {failed} failed")
print("="*60)
print("\n Visual elements are displayed in the 'api_test' scene.")
print(" The test is complete.")
# Exit after a short delay to allow output to be seen
def exit_test(runtime):
print("\nExiting API test suite...")
sys.exit(0)
mcrfpy.setTimer("exit", exit_test, 2000)
# Run the tests immediately
print("Starting McRogueFace Exhaustive API Demo (Fixed)...")
print("This will test constructors and methods.")
run_all_tests()

View file

@ -0,0 +1,391 @@
#!/usr/bin/env python3
"""
Path & Vision Sizzle Reel
=========================
A choreographed demo showing:
- Smooth entity movement along paths
- Camera following with grid center animation
- Field of view updates as entities move
- Dramatic perspective transitions with zoom effects
"""
import mcrfpy
import sys
# Colors
WALL_COLOR = mcrfpy.Color(40, 30, 30)
FLOOR_COLOR = mcrfpy.Color(80, 80, 100)
PATH_COLOR = mcrfpy.Color(120, 120, 180)
DARK_FLOOR = mcrfpy.Color(40, 40, 50)
# Global state
grid = None
player = None
enemy = None
sequence_step = 0
player_path = []
enemy_path = []
player_path_index = 0
enemy_path_index = 0
def create_scene():
"""Create the demo environment"""
global grid, player, enemy
mcrfpy.createScene("path_vision_demo")
# Create larger grid for more dramatic movement
grid = mcrfpy.Grid(grid_x=40, grid_y=25)
grid.fill_color = mcrfpy.Color(20, 20, 30)
# Map layout - interconnected rooms with corridors
map_layout = [
"########################################", # 0
"#......##########......################", # 1
"#......##########......################", # 2
"#......##########......################", # 3
"#......#.........#.....################", # 4
"#......#.........#.....################", # 5
"####.###.........####.#################", # 6
"####.....................##############", # 7
"####.....................##############", # 8
"####.###.........####.#################", # 9
"#......#.........#.....################", # 10
"#......#.........#.....################", # 11
"#......#.........#.....################", # 12
"#......###.....###.....################", # 13
"#......###.....###.....################", # 14
"#......###.....###.....#########......#", # 15
"#......###.....###.....#########......#", # 16
"#......###.....###.....#########......#", # 17
"#####.############.#############......#", # 18
"#####...........................#.....#", # 19
"#####...........................#.....#", # 20
"#####.############.#############......#", # 21
"#......###########.##########.........#", # 22
"#......###########.##########.........#", # 23
"########################################", # 24
]
# Build the map
for y, row in enumerate(map_layout):
for x, char in enumerate(row):
cell = grid.at(x, y)
if char == '#':
cell.walkable = False
cell.transparent = False
cell.color = WALL_COLOR
else:
cell.walkable = True
cell.transparent = True
cell.color = FLOOR_COLOR
# Create player in top-left room
player = mcrfpy.Entity(3, 3, grid=grid)
player.sprite_index = 64 # @
# Create enemy in bottom-right area
enemy = mcrfpy.Entity(35, 20, grid=grid)
enemy.sprite_index = 69 # E
# Initial visibility
player.update_visibility()
enemy.update_visibility()
# Set initial perspective to player
grid.perspective = 0
def setup_paths():
"""Define the paths for entities"""
global player_path, enemy_path
# Player path: Top-left room → corridor → middle room
player_waypoints = [
(3, 3), # Start
(3, 8), # Move down
(7, 8), # Enter corridor
(16, 8), # Through corridor
(16, 12), # Enter middle room
(12, 12), # Move in room
(12, 16), # Move down
(16, 16), # Move right
(16, 19), # Exit room
(25, 19), # Move right
(30, 19), # Continue
(35, 19), # Near enemy start
]
# Enemy path: Bottom-right → around → approach player area
enemy_waypoints = [
(35, 20), # Start
(30, 20), # Move left
(25, 20), # Continue
(20, 20), # Continue
(16, 20), # Corridor junction
(16, 16), # Move up (might see player)
(16, 12), # Continue up
(16, 8), # Top corridor
(10, 8), # Move left
(7, 8), # Continue
(3, 8), # Player's area
(3, 12), # Move down
]
# Calculate full paths using pathfinding
player_path = []
for i in range(len(player_waypoints) - 1):
x1, y1 = player_waypoints[i]
x2, y2 = player_waypoints[i + 1]
# Use grid's A* pathfinding
segment = grid.compute_astar_path(x1, y1, x2, y2)
if segment:
# Add segment (avoiding duplicates)
if not player_path or segment[0] != player_path[-1]:
player_path.extend(segment)
else:
player_path.extend(segment[1:])
enemy_path = []
for i in range(len(enemy_waypoints) - 1):
x1, y1 = enemy_waypoints[i]
x2, y2 = enemy_waypoints[i + 1]
segment = grid.compute_astar_path(x1, y1, x2, y2)
if segment:
if not enemy_path or segment[0] != enemy_path[-1]:
enemy_path.extend(segment)
else:
enemy_path.extend(segment[1:])
print(f"Player path: {len(player_path)} steps")
print(f"Enemy path: {len(enemy_path)} steps")
def setup_ui():
"""Create UI elements"""
ui = mcrfpy.sceneUI("path_vision_demo")
ui.append(grid)
# Position and size grid
grid.position = (50, 80)
grid.size = (700, 500) # Adjust based on zoom
# Title
title = mcrfpy.Caption("Path & Vision Sizzle Reel", 300, 20)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Status
global status_text, perspective_text
status_text = mcrfpy.Caption("Starting demo...", 50, 50)
status_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(status_text)
perspective_text = mcrfpy.Caption("Perspective: Player", 550, 50)
perspective_text.fill_color = mcrfpy.Color(100, 255, 100)
ui.append(perspective_text)
# Controls
controls = mcrfpy.Caption("Space: Pause/Resume | R: Restart | Q: Quit", 250, 600)
controls.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(controls)
# Animation control
paused = False
move_timer = 0
zoom_transition = False
def move_entity_smooth(entity, target_x, target_y, duration=0.3):
"""Smoothly animate entity to position"""
# Create position animation
anim_x = mcrfpy.Animation("x", float(target_x), duration, "easeInOut")
anim_y = mcrfpy.Animation("y", float(target_y), duration, "easeInOut")
anim_x.start(entity)
anim_y.start(entity)
def update_camera_smooth(center_x, center_y, duration=0.3):
"""Smoothly move camera center"""
# Convert grid coords to pixel coords (assuming 16x16 tiles)
pixel_x = center_x * 16
pixel_y = center_y * 16
anim = mcrfpy.Animation("center", (pixel_x, pixel_y), duration, "easeOut")
anim.start(grid)
def start_perspective_transition():
"""Begin the dramatic perspective shift"""
global zoom_transition, sequence_step
zoom_transition = True
sequence_step = 100 # Special sequence number
status_text.text = "Perspective shift: Zooming out..."
# Zoom out with elastic easing
zoom_out = mcrfpy.Animation("zoom", 0.5, 2.0, "easeInExpo")
zoom_out.start(grid)
# Schedule the perspective switch
mcrfpy.setTimer("switch_perspective", switch_perspective, 2100)
def switch_perspective(dt):
"""Switch perspective at the peak of zoom"""
global sequence_step
# Switch to enemy perspective
grid.perspective = 1
perspective_text.text = "Perspective: Enemy"
perspective_text.fill_color = mcrfpy.Color(255, 100, 100)
status_text.text = "Perspective shift: Following enemy..."
# Update camera to enemy position
update_camera_smooth(enemy.x, enemy.y, 0.1)
# Zoom back in
zoom_in = mcrfpy.Animation("zoom", 1.2, 2.0, "easeOutExpo")
zoom_in.start(grid)
# Resume sequence
mcrfpy.setTimer("resume_enemy", resume_enemy_sequence, 2100)
# Cancel this timer
mcrfpy.delTimer("switch_perspective")
def resume_enemy_sequence(dt):
"""Resume following enemy after perspective shift"""
global sequence_step, zoom_transition
zoom_transition = False
sequence_step = 101 # Continue with enemy movement
mcrfpy.delTimer("resume_enemy")
def sequence_tick(dt):
"""Main sequence controller"""
global sequence_step, player_path_index, enemy_path_index, move_timer
if paused or zoom_transition:
return
move_timer += dt
if move_timer < 400: # Move every 400ms
return
move_timer = 0
if sequence_step < 50:
# Phase 1: Follow player movement
if player_path_index < len(player_path):
x, y = player_path[player_path_index]
move_entity_smooth(player, x, y)
player.update_visibility()
# Camera follows player
if grid.perspective == 0:
update_camera_smooth(player.x, player.y)
player_path_index += 1
status_text.text = f"Player moving... Step {player_path_index}/{len(player_path)}"
# Start enemy movement after player has moved a bit
if player_path_index == 10:
sequence_step = 1 # Enable enemy movement
else:
# Player reached destination, start perspective transition
start_perspective_transition()
if sequence_step >= 1 and sequence_step < 50:
# Phase 2: Enemy movement (concurrent with player)
if enemy_path_index < len(enemy_path):
x, y = enemy_path[enemy_path_index]
move_entity_smooth(enemy, x, y)
enemy.update_visibility()
# Check if enemy is visible to player
if grid.perspective == 0:
enemy_cell_idx = int(enemy.y) * grid.grid_x + int(enemy.x)
if enemy_cell_idx < len(player.gridstate) and player.gridstate[enemy_cell_idx].visible:
status_text.text = "Enemy spotted!"
enemy_path_index += 1
elif sequence_step == 101:
# Phase 3: Continue following enemy after perspective shift
if enemy_path_index < len(enemy_path):
x, y = enemy_path[enemy_path_index]
move_entity_smooth(enemy, x, y)
enemy.update_visibility()
# Camera follows enemy
update_camera_smooth(enemy.x, enemy.y)
enemy_path_index += 1
status_text.text = f"Following enemy... Step {enemy_path_index}/{len(enemy_path)}"
else:
status_text.text = "Demo complete! Press R to restart"
sequence_step = 200 # Done
def handle_keys(key, state):
"""Handle keyboard input"""
global paused, sequence_step, player_path_index, enemy_path_index, move_timer
key = key.lower()
if state != "start":
return
if key == "q":
print("Exiting sizzle reel...")
sys.exit(0)
elif key == "space":
paused = not paused
status_text.text = "PAUSED" if paused else "Running..."
elif key == "r":
# Reset everything
player.x, player.y = 3, 3
enemy.x, enemy.y = 35, 20
player.update_visibility()
enemy.update_visibility()
grid.perspective = 0
perspective_text.text = "Perspective: Player"
perspective_text.fill_color = mcrfpy.Color(100, 255, 100)
sequence_step = 0
player_path_index = 0
enemy_path_index = 0
move_timer = 0
update_camera_smooth(player.x, player.y, 0.5)
# Reset zoom
zoom_reset = mcrfpy.Animation("zoom", 1.2, 0.5, "easeOut")
zoom_reset.start(grid)
status_text.text = "Demo restarted!"
# Initialize everything
print("Path & Vision Sizzle Reel")
print("=========================")
print("Demonstrating:")
print("- Smooth entity movement along calculated paths")
print("- Camera following with animated grid centering")
print("- Field of view updates as entities move")
print("- Dramatic perspective transitions with zoom effects")
print()
create_scene()
setup_paths()
setup_ui()
# Set scene and input
mcrfpy.setScene("path_vision_demo")
mcrfpy.keypressScene(handle_keys)
# Initial camera setup
grid.zoom = 1.2
update_camera_smooth(player.x, player.y, 0.1)
# Start the sequence
mcrfpy.setTimer("sequence", sequence_tick, 50) # Tick every 50ms
print("Demo started!")
print("- Player (@) will navigate through rooms")
print("- Enemy (E) will move on a different path")
print("- Watch for the dramatic perspective shift!")
print()
print("Controls: Space=Pause, R=Restart, Q=Quit")

View file

@ -0,0 +1,377 @@
#!/usr/bin/env python3
"""
Pathfinding Showcase Demo
=========================
Demonstrates various pathfinding scenarios with multiple entities.
Features:
- Multiple entities pathfinding simultaneously
- Chase mode: entities pursue targets
- Flee mode: entities avoid threats
- Patrol mode: entities follow waypoints
- Visual debugging: show Dijkstra distance field
"""
import mcrfpy
import sys
import random
# Colors
WALL_COLOR = mcrfpy.Color(40, 40, 40)
FLOOR_COLOR = mcrfpy.Color(220, 220, 240)
PATH_COLOR = mcrfpy.Color(180, 250, 180)
THREAT_COLOR = mcrfpy.Color(255, 100, 100)
GOAL_COLOR = mcrfpy.Color(100, 255, 100)
DIJKSTRA_COLORS = [
mcrfpy.Color(50, 50, 100), # Far
mcrfpy.Color(70, 70, 150),
mcrfpy.Color(90, 90, 200),
mcrfpy.Color(110, 110, 250),
mcrfpy.Color(150, 150, 255),
mcrfpy.Color(200, 200, 255), # Near
]
# Entity types
PLAYER = 64 # @
ENEMY = 69 # E
TREASURE = 36 # $
PATROL = 80 # P
# Global state
grid = None
player = None
enemies = []
treasures = []
patrol_entities = []
mode = "CHASE"
show_dijkstra = False
animation_speed = 3.0
# Track waypoints separately since Entity doesn't have custom attributes
entity_waypoints = {} # entity -> [(x, y), ...]
entity_waypoint_indices = {} # entity -> current index
def create_dungeon():
"""Create a dungeon-like map"""
global grid
mcrfpy.createScene("pathfinding_showcase")
# Create larger grid for showcase
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).transparent = True
grid.at(x, y).color = FLOOR_COLOR
# Create rooms and corridors
rooms = [
(2, 2, 8, 6), # Top-left room
(20, 2, 8, 6), # Top-right room
(11, 8, 8, 6), # Center room
(2, 14, 8, 5), # Bottom-left room
(20, 14, 8, 5), # Bottom-right room
]
# Create room walls
for rx, ry, rw, rh in rooms:
# Top and bottom walls
for x in range(rx, rx + rw):
if 0 <= x < 30:
grid.at(x, ry).walkable = False
grid.at(x, ry).color = WALL_COLOR
grid.at(x, ry + rh - 1).walkable = False
grid.at(x, ry + rh - 1).color = WALL_COLOR
# Left and right walls
for y in range(ry, ry + rh):
if 0 <= y < 20:
grid.at(rx, y).walkable = False
grid.at(rx, y).color = WALL_COLOR
grid.at(rx + rw - 1, y).walkable = False
grid.at(rx + rw - 1, y).color = WALL_COLOR
# Create doorways
doorways = [
(6, 2), (24, 2), # Top room doors
(6, 7), (24, 7), # Top room doors bottom
(15, 8), (15, 13), # Center room doors
(6, 14), (24, 14), # Bottom room doors
(11, 11), (18, 11), # Center room side doors
]
for x, y in doorways:
if 0 <= x < 30 and 0 <= y < 20:
grid.at(x, y).walkable = True
grid.at(x, y).color = FLOOR_COLOR
# Add some corridors
# Horizontal corridors
for x in range(10, 20):
grid.at(x, 5).walkable = True
grid.at(x, 5).color = FLOOR_COLOR
grid.at(x, 16).walkable = True
grid.at(x, 16).color = FLOOR_COLOR
# Vertical corridors
for y in range(5, 17):
grid.at(10, y).walkable = True
grid.at(10, y).color = FLOOR_COLOR
grid.at(19, y).walkable = True
grid.at(19, y).color = FLOOR_COLOR
def spawn_entities():
"""Spawn various entity types"""
global player, enemies, treasures, patrol_entities
# Clear existing entities
#grid.entities.clear()
enemies = []
treasures = []
patrol_entities = []
# Spawn player in center room
player = mcrfpy.Entity((15, 11), mcrfpy.default_texture, PLAYER)
grid.entities.append(player)
# Spawn enemies in corners
enemy_positions = [(4, 4), (24, 4), (4, 16), (24, 16)]
for x, y in enemy_positions:
enemy = mcrfpy.Entity((x, y), mcrfpy.default_texture, ENEMY)
grid.entities.append(enemy)
enemies.append(enemy)
# Spawn treasures
treasure_positions = [(6, 5), (24, 5), (15, 10)]
for x, y in treasure_positions:
treasure = mcrfpy.Entity((x, y), mcrfpy.default_texture, TREASURE)
grid.entities.append(treasure)
treasures.append(treasure)
# Spawn patrol entities
patrol = mcrfpy.Entity((10, 10), mcrfpy.default_texture, PATROL)
# Store waypoints separately since Entity doesn't support custom attributes
entity_waypoints[patrol] = [(10, 10), (19, 10), (19, 16), (10, 16)] # Square patrol
entity_waypoint_indices[patrol] = 0
grid.entities.append(patrol)
patrol_entities.append(patrol)
def visualize_dijkstra(target_x, target_y):
"""Visualize Dijkstra distance field"""
if not show_dijkstra:
return
# Compute Dijkstra from target
grid.compute_dijkstra(target_x, target_y)
# Color tiles based on distance
max_dist = 30.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:
# Map distance to color index
color_idx = int((dist / max_dist) * len(DIJKSTRA_COLORS))
color_idx = min(color_idx, len(DIJKSTRA_COLORS) - 1)
grid.at(x, y).color = DIJKSTRA_COLORS[color_idx]
def move_enemies(dt):
"""Move enemies based on current mode"""
if mode == "CHASE":
# Enemies chase player
for enemy in enemies:
path = enemy.path_to(int(player.x), int(player.y))
if path and len(path) > 1: # Don't move onto player
# Move towards player
next_x, next_y = path[1]
# Smooth movement
dx = next_x - enemy.x
dy = next_y - enemy.y
enemy.x += dx * dt * animation_speed
enemy.y += dy * dt * animation_speed
elif mode == "FLEE":
# Enemies flee from player
for enemy in enemies:
# Compute opposite direction
dx = enemy.x - player.x
dy = enemy.y - player.y
# Find safe spot in that direction
target_x = int(enemy.x + dx * 2)
target_y = int(enemy.y + dy * 2)
# Clamp to grid
target_x = max(0, min(29, target_x))
target_y = max(0, min(19, target_y))
path = enemy.path_to(target_x, target_y)
if path and len(path) > 0:
next_x, next_y = path[0]
# Move away from player
dx = next_x - enemy.x
dy = next_y - enemy.y
enemy.x += dx * dt * animation_speed
enemy.y += dy * dt * animation_speed
def move_patrols(dt):
"""Move patrol entities along waypoints"""
for patrol in patrol_entities:
if patrol not in entity_waypoints:
continue
# Get current waypoint
waypoints = entity_waypoints[patrol]
waypoint_index = entity_waypoint_indices[patrol]
target_x, target_y = waypoints[waypoint_index]
# Check if reached waypoint
dist = abs(patrol.x - target_x) + abs(patrol.y - target_y)
if dist < 0.5:
# Move to next waypoint
entity_waypoint_indices[patrol] = (waypoint_index + 1) % len(waypoints)
waypoint_index = entity_waypoint_indices[patrol]
target_x, target_y = waypoints[waypoint_index]
# Path to waypoint
path = patrol.path_to(target_x, target_y)
if path and len(path) > 0:
next_x, next_y = path[0]
dx = next_x - patrol.x
dy = next_y - patrol.y
patrol.x += dx * dt * animation_speed * 0.5 # Slower patrol speed
patrol.y += dy * dt * animation_speed * 0.5
def update_entities(dt):
"""Update all entity movements"""
move_enemies(dt / 1000.0) # Convert to seconds
move_patrols(dt / 1000.0)
# Update Dijkstra visualization
if show_dijkstra and player:
visualize_dijkstra(int(player.x), int(player.y))
def handle_keypress(scene_name, keycode):
"""Handle keyboard input"""
global mode, show_dijkstra, player
# Mode switching
if keycode == 49: # '1'
mode = "CHASE"
mode_text.text = "Mode: CHASE - Enemies pursue player"
clear_colors()
elif keycode == 50: # '2'
mode = "FLEE"
mode_text.text = "Mode: FLEE - Enemies avoid player"
clear_colors()
elif keycode == 51: # '3'
mode = "PATROL"
mode_text.text = "Mode: PATROL - Entities follow waypoints"
clear_colors()
# Toggle Dijkstra visualization
elif keycode == 68 or keycode == 100: # 'D' or 'd'
show_dijkstra = not show_dijkstra
debug_text.text = f"Dijkstra Debug: {'ON' if show_dijkstra else 'OFF'}"
if not show_dijkstra:
clear_colors()
# Move player with arrow keys or WASD
elif keycode in [87, 119]: # W/w - Up
if player.y > 0:
path = player.path_to(int(player.x), int(player.y) - 1)
if path:
player.y -= 1
elif keycode in [83, 115]: # S/s - Down
if player.y < 19:
path = player.path_to(int(player.x), int(player.y) + 1)
if path:
player.y += 1
elif keycode in [65, 97]: # A/a - Left
if player.x > 0:
path = player.path_to(int(player.x) - 1, int(player.y))
if path:
player.x -= 1
elif keycode in [68, 100]: # D/d - Right
if player.x < 29:
path = player.path_to(int(player.x) + 1, int(player.y))
if path:
player.x += 1
# Reset
elif keycode == 82 or keycode == 114: # 'R' or 'r'
spawn_entities()
clear_colors()
# Quit
elif keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
print("\nExiting pathfinding showcase...")
sys.exit(0)
def clear_colors():
"""Reset floor colors"""
for y in range(20):
for x in range(30):
if grid.at(x, y).walkable:
grid.at(x, y).color = FLOOR_COLOR
# Create the showcase
print("Pathfinding Showcase Demo")
print("=========================")
print("Controls:")
print(" WASD - Move player")
print(" 1 - Chase mode (enemies pursue)")
print(" 2 - Flee mode (enemies avoid)")
print(" 3 - Patrol mode")
print(" D - Toggle Dijkstra visualization")
print(" R - Reset entities")
print(" Q/ESC - Quit")
# Create dungeon
create_dungeon()
spawn_entities()
# Set up UI
ui = mcrfpy.sceneUI("pathfinding_showcase")
ui.append(grid)
# Scale and position
grid.size = (750, 500) # 30*25, 20*25
grid.position = (25, 60)
# Add title
title = mcrfpy.Caption("Pathfinding Showcase", 300, 10)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add mode text
mode_text = mcrfpy.Caption("Mode: CHASE - Enemies pursue player", 25, 580)
mode_text.fill_color = mcrfpy.Color(255, 255, 200)
ui.append(mode_text)
# Add debug text
debug_text = mcrfpy.Caption("Dijkstra Debug: OFF", 25, 600)
debug_text.fill_color = mcrfpy.Color(200, 200, 255)
ui.append(debug_text)
# Add legend
legend = mcrfpy.Caption("@ Player E Enemy $ Treasure P Patrol", 25, 620)
legend.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend)
# Set up input handling
mcrfpy.keypressScene(handle_keypress)
# Set up animation timer
mcrfpy.setTimer("entities", update_entities, 16) # 60 FPS
# Show scene
mcrfpy.setScene("pathfinding_showcase")
print("\nShowcase ready! Move with WASD and watch entities react.")

View file

@ -0,0 +1,226 @@
#!/usr/bin/env python3
"""
Simple Text Input Widget for McRogueFace
Minimal implementation focusing on core functionality
"""
import mcrfpy
import sys
class TextInput:
"""Simple text input widget"""
def __init__(self, x, y, width, label=""):
self.x = x
self.y = y
self.width = width
self.label = label
self.text = ""
self.cursor_pos = 0
self.focused = False
# Create UI elements
self.frame = mcrfpy.Frame(self.x, self.y, self.width, 24)
self.frame.fill_color = (255, 255, 255, 255)
self.frame.outline_color = (128, 128, 128, 255)
self.frame.outline = 2
# Label
if self.label:
self.label_caption = mcrfpy.Caption(self.label, self.x, self.y - 20)
self.label_caption.fill_color = (255, 255, 255, 255)
# Text display
self.text_caption = mcrfpy.Caption("", self.x + 4, self.y + 4)
self.text_caption.fill_color = (0, 0, 0, 255)
# Cursor (a simple vertical line using a frame)
self.cursor = mcrfpy.Frame(self.x + 4, self.y + 4, 2, 16)
self.cursor.fill_color = (0, 0, 0, 255)
self.cursor.visible = False
# Click handler
self.frame.click = self._on_click
def _on_click(self, x, y, button):
"""Handle clicks"""
if button == 1: # Left click
# Request focus
global current_focus
if current_focus and current_focus != self:
current_focus.blur()
current_focus = self
self.focus()
def focus(self):
"""Give focus to this input"""
self.focused = True
self.frame.outline_color = (0, 120, 255, 255)
self.frame.outline = 3
self.cursor.visible = True
self._update_cursor()
def blur(self):
"""Remove focus"""
self.focused = False
self.frame.outline_color = (128, 128, 128, 255)
self.frame.outline = 2
self.cursor.visible = False
def handle_key(self, key):
"""Process keyboard input"""
if not self.focused:
return False
if key == "BackSpace":
if self.cursor_pos > 0:
self.text = self.text[:self.cursor_pos-1] + self.text[self.cursor_pos:]
self.cursor_pos -= 1
elif key == "Delete":
if self.cursor_pos < len(self.text):
self.text = self.text[:self.cursor_pos] + self.text[self.cursor_pos+1:]
elif key == "Left":
self.cursor_pos = max(0, self.cursor_pos - 1)
elif key == "Right":
self.cursor_pos = min(len(self.text), self.cursor_pos + 1)
elif key == "Home":
self.cursor_pos = 0
elif key == "End":
self.cursor_pos = len(self.text)
elif len(key) == 1 and key.isprintable():
self.text = self.text[:self.cursor_pos] + key + self.text[self.cursor_pos:]
self.cursor_pos += 1
else:
return False
self._update_display()
return True
def _update_display(self):
"""Update text display"""
self.text_caption.text = self.text
self._update_cursor()
def _update_cursor(self):
"""Update cursor position"""
if self.focused:
# Estimate character width (roughly 10 pixels per char)
self.cursor.x = self.x + 4 + (self.cursor_pos * 10)
def add_to_scene(self, scene):
"""Add all components to scene"""
scene.append(self.frame)
if hasattr(self, 'label_caption'):
scene.append(self.label_caption)
scene.append(self.text_caption)
scene.append(self.cursor)
# Global focus tracking
current_focus = None
text_inputs = []
def demo_test(timer_name):
"""Run automated demo after scene loads"""
print("\n=== Text Input Widget Demo ===")
# Test typing in first field
print("Testing first input field...")
text_inputs[0].focus()
for char in "Hello":
text_inputs[0].handle_key(char)
print(f"First field contains: '{text_inputs[0].text}'")
# Test second field
print("\nTesting second input field...")
text_inputs[1].focus()
for char in "World":
text_inputs[1].handle_key(char)
print(f"Second field contains: '{text_inputs[1].text}'")
# Test text operations
print("\nTesting cursor movement and deletion...")
text_inputs[1].handle_key("Home")
text_inputs[1].handle_key("Delete")
print(f"After delete at start: '{text_inputs[1].text}'")
text_inputs[1].handle_key("End")
text_inputs[1].handle_key("BackSpace")
print(f"After backspace at end: '{text_inputs[1].text}'")
print("\n=== Demo Complete! ===")
print("Text input widget is working successfully!")
print("Features demonstrated:")
print(" - Text entry")
print(" - Focus management (blue outline)")
print(" - Cursor positioning")
print(" - Delete/Backspace operations")
sys.exit(0)
def create_scene():
"""Create the demo scene"""
global text_inputs
mcrfpy.createScene("demo")
scene = mcrfpy.sceneUI("demo")
# Background
bg = mcrfpy.Frame(0, 0, 800, 600)
bg.fill_color = (40, 40, 40, 255)
scene.append(bg)
# Title
title = mcrfpy.Caption("Text Input Widget Demo", 10, 10)
title.fill_color = (255, 255, 255, 255)
scene.append(title)
# Create input fields
input1 = TextInput(50, 100, 300, "Name:")
input1.add_to_scene(scene)
text_inputs.append(input1)
input2 = TextInput(50, 160, 300, "Email:")
input2.add_to_scene(scene)
text_inputs.append(input2)
input3 = TextInput(50, 220, 400, "Comment:")
input3.add_to_scene(scene)
text_inputs.append(input3)
# Status text
status = mcrfpy.Caption("Click to focus, type to enter text", 50, 280)
status.fill_color = (200, 200, 200, 255)
scene.append(status)
# Keyboard handler
def handle_keys(scene_name, key):
global current_focus, text_inputs
# Tab to switch fields
if key == "Tab" and current_focus:
idx = text_inputs.index(current_focus)
next_idx = (idx + 1) % len(text_inputs)
text_inputs[next_idx]._on_click(0, 0, 1)
else:
# Pass to focused input
if current_focus:
current_focus.handle_key(key)
# Update status
texts = [inp.text for inp in text_inputs]
status.text = f"Values: {texts[0]} | {texts[1]} | {texts[2]}"
mcrfpy.keypressScene("demo", handle_keys)
mcrfpy.setScene("demo")
# Schedule test
mcrfpy.setTimer("test", demo_test, 500)
if __name__ == "__main__":
print("Starting simple text input demo...")
create_scene()

View file

@ -0,0 +1,190 @@
#!/usr/bin/env python3
"""
McRogueFace Animation Sizzle Reel - Final Version
=================================================
Complete demonstration of all animation capabilities.
This version works properly with the game loop and avoids API issues.
WARNING: This demo causes a segmentation fault due to a bug in the
AnimationManager. When UI elements with active animations are removed
from the scene, the AnimationManager crashes when trying to update them.
Use sizzle_reel_final_fixed.py instead, which works around this issue
by hiding objects off-screen instead of removing them.
"""
import mcrfpy
# Configuration
DEMO_DURATION = 6.0 # Duration for each demo
# All available easing functions
EASING_FUNCTIONS = [
"linear", "easeIn", "easeOut", "easeInOut",
"easeInQuad", "easeOutQuad", "easeInOutQuad",
"easeInCubic", "easeOutCubic", "easeInOutCubic",
"easeInQuart", "easeOutQuart", "easeInOutQuart",
"easeInSine", "easeOutSine", "easeInOutSine",
"easeInExpo", "easeOutExpo", "easeInOutExpo",
"easeInCirc", "easeOutCirc", "easeInOutCirc",
"easeInElastic", "easeOutElastic", "easeInOutElastic",
"easeInBack", "easeOutBack", "easeInOutBack",
"easeInBounce", "easeOutBounce", "easeInOutBounce"
]
# Track demo state
current_demo = 0
subtitle = None
def create_scene():
"""Create the demo scene"""
mcrfpy.createScene("demo")
mcrfpy.setScene("demo")
ui = mcrfpy.sceneUI("demo")
# Title
title = mcrfpy.Caption("Animation Sizzle Reel", 500, 20)
title.fill_color = mcrfpy.Color(255, 255, 0)
title.outline = 2
title.font_size = 28
ui.append(title)
# Subtitle
global subtitle
subtitle = mcrfpy.Caption("Starting...", 450, 60)
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(subtitle)
return ui
def demo1_frame_animations():
"""Frame position, size, and color animations"""
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 1: Frame Animations"
# Create frame
f = mcrfpy.Frame(100, 150, 200, 100)
f.fill_color = mcrfpy.Color(50, 50, 150)
f.outline = 3
f.outline_color = mcrfpy.Color(255, 255, 255)
ui.append(f)
# Animate properties
mcrfpy.Animation("x", 600.0, 2.0, "easeInOutBack").start(f)
mcrfpy.Animation("y", 300.0, 2.0, "easeInOutElastic").start(f)
mcrfpy.Animation("w", 300.0, 2.5, "easeInOutCubic").start(f)
mcrfpy.Animation("h", 150.0, 2.5, "easeInOutCubic").start(f)
mcrfpy.Animation("fill_color", (255, 100, 50, 200), 3.0, "easeInOutSine").start(f)
mcrfpy.Animation("outline", 8.0, 3.0, "easeInOutQuad").start(f)
def demo2_caption_animations():
"""Caption movement and text effects"""
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 2: Caption Animations"
# Moving caption
c1 = mcrfpy.Caption("Bouncing Text!", 100, 200)
c1.fill_color = mcrfpy.Color(255, 255, 255)
c1.font_size = 28
ui.append(c1)
mcrfpy.Animation("x", 800.0, 3.0, "easeOutBounce").start(c1)
# Color cycling
c2 = mcrfpy.Caption("Color Cycle", 400, 300)
c2.outline = 2
c2.font_size = 28
ui.append(c2)
mcrfpy.Animation("fill_color", (255, 0, 0, 255), 1.0, "linear").start(c2)
# Typewriter effect
c3 = mcrfpy.Caption("", 100, 400)
c3.fill_color = mcrfpy.Color(0, 255, 255)
c3.font_size = 28
ui.append(c3)
mcrfpy.Animation("text", "Typewriter effect animation...", 3.0, "linear").start(c3)
def demo3_easing_showcase():
"""Show all 30 easing functions"""
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 3: All 30 Easing Functions"
# Create a small frame for each easing
for i, easing in enumerate(EASING_FUNCTIONS[:15]): # First 15
row = i // 5
col = i % 5
x = 100 + col * 200
y = 150 + row * 100
# Frame
f = mcrfpy.Frame(x, y, 20, 20)
f.fill_color = mcrfpy.Color(100, 150, 255)
ui.append(f)
# Label
label = mcrfpy.Caption(easing[:10], x, y - 20)
label.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(label)
# Animate with this easing
mcrfpy.Animation("x", float(x + 150), 3.0, easing).start(f)
def demo4_performance():
"""Many simultaneous animations"""
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 4: 50+ Simultaneous Animations"
for i in range(50):
x = 100 + (i % 10) * 100
y = 150 + (i // 10) * 100
f = mcrfpy.Frame(x, y, 30, 30)
f.fill_color = mcrfpy.Color((i*37)%256, (i*73)%256, (i*113)%256)
ui.append(f)
# Animate to random position
target_x = 150 + (i % 8) * 110
target_y = 200 + (i // 8) * 90
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
mcrfpy.Animation("x", float(target_x), 2.5, easing).start(f)
mcrfpy.Animation("y", float(target_y), 2.5, easing).start(f)
mcrfpy.Animation("opacity", 0.3 + (i%7)*0.1, 2.0, "easeInOutSine").start(f)
def clear_demo_objects():
"""Clear scene except title and subtitle"""
ui = mcrfpy.sceneUI("demo")
# Keep removing items after the first 2 (title and subtitle)
while len(ui) > 2:
# Remove the last item
ui.remove(len(ui)-1)
def next_demo(runtime):
"""Run the next demo"""
global current_demo
clear_demo_objects()
demos = [
demo1_frame_animations,
demo2_caption_animations,
demo3_easing_showcase,
demo4_performance
]
if current_demo < len(demos):
demos[current_demo]()
current_demo += 1
if current_demo < len(demos):
#mcrfpy.setTimer("next", next_demo, int(DEMO_DURATION * 1000))
pass
else:
subtitle.text = "Demo Complete!"
# Initialize
print("Starting Animation Sizzle Reel...")
create_scene()
mcrfpy.setTimer("start", next_demo, int(DEMO_DURATION * 1000))
next_demo(0)

View file

@ -0,0 +1,193 @@
#!/usr/bin/env python3
"""
McRogueFace Animation Sizzle Reel - Fixed Version
=================================================
This version works around the animation crash by:
1. Using shorter demo durations to ensure animations complete before clearing
2. Adding a delay before clearing to let animations finish
3. Not removing objects, just hiding them off-screen instead
"""
import mcrfpy
# Configuration
DEMO_DURATION = 3.5 # Slightly shorter to ensure animations complete
CLEAR_DELAY = 0.5 # Extra delay before clearing
# All available easing functions
EASING_FUNCTIONS = [
"linear", "easeIn", "easeOut", "easeInOut",
"easeInQuad", "easeOutQuad", "easeInOutQuad",
"easeInCubic", "easeOutCubic", "easeInOutCubic",
"easeInQuart", "easeOutQuart", "easeInOutQuart",
"easeInSine", "easeOutSine", "easeInOutSine",
"easeInExpo", "easeOutExpo", "easeInOutExpo",
"easeInCirc", "easeOutCirc", "easeInOutCirc",
"easeInElastic", "easeOutElastic", "easeInOutElastic",
"easeInBack", "easeOutBack", "easeInOutBack",
"easeInBounce", "easeOutBounce", "easeInOutBounce"
]
# Track demo state
current_demo = 0
subtitle = None
demo_objects = [] # Track objects to hide instead of remove
def create_scene():
"""Create the demo scene"""
mcrfpy.createScene("demo")
mcrfpy.setScene("demo")
ui = mcrfpy.sceneUI("demo")
# Title
title = mcrfpy.Caption("Animation Sizzle Reel", 500, 20)
title.fill_color = mcrfpy.Color(255, 255, 0)
title.outline = 2
ui.append(title)
# Subtitle
global subtitle
subtitle = mcrfpy.Caption("Starting...", 450, 60)
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(subtitle)
return ui
def hide_demo_objects():
"""Hide demo objects by moving them off-screen instead of removing"""
global demo_objects
# Move all demo objects far off-screen
for obj in demo_objects:
obj.x = -1000
obj.y = -1000
demo_objects = []
def demo1_frame_animations():
"""Frame position, size, and color animations"""
global demo_objects
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 1: Frame Animations"
# Create frame
f = mcrfpy.Frame(100, 150, 200, 100)
f.fill_color = mcrfpy.Color(50, 50, 150)
f.outline = 3
f.outline_color = mcrfpy.Color(255, 255, 255)
ui.append(f)
demo_objects.append(f)
# Animate properties with shorter durations
mcrfpy.Animation("x", 600.0, 2.0, "easeInOutBack").start(f)
mcrfpy.Animation("y", 300.0, 2.0, "easeInOutElastic").start(f)
mcrfpy.Animation("w", 300.0, 2.5, "easeInOutCubic").start(f)
mcrfpy.Animation("h", 150.0, 2.5, "easeInOutCubic").start(f)
mcrfpy.Animation("fill_color", (255, 100, 50, 200), 3.0, "easeInOutSine").start(f)
mcrfpy.Animation("outline", 8.0, 3.0, "easeInOutQuad").start(f)
def demo2_caption_animations():
"""Caption movement and text effects"""
global demo_objects
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 2: Caption Animations"
# Moving caption
c1 = mcrfpy.Caption("Bouncing Text!", 100, 200)
c1.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(c1)
demo_objects.append(c1)
mcrfpy.Animation("x", 800.0, 3.0, "easeOutBounce").start(c1)
# Color cycling
c2 = mcrfpy.Caption("Color Cycle", 400, 300)
c2.outline = 2
ui.append(c2)
demo_objects.append(c2)
mcrfpy.Animation("fill_color", (255, 0, 0, 255), 1.0, "linear").start(c2)
# Static text (no typewriter effect to avoid issues)
c3 = mcrfpy.Caption("Animation Demo", 100, 400)
c3.fill_color = mcrfpy.Color(0, 255, 255)
ui.append(c3)
demo_objects.append(c3)
def demo3_easing_showcase():
"""Show all 30 easing functions"""
global demo_objects
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 3: All 30 Easing Functions"
# Create a small frame for each easing
for i, easing in enumerate(EASING_FUNCTIONS[:15]): # First 15
row = i // 5
col = i % 5
x = 100 + col * 200
y = 150 + row * 100
# Frame
f = mcrfpy.Frame(x, y, 20, 20)
f.fill_color = mcrfpy.Color(100, 150, 255)
ui.append(f)
demo_objects.append(f)
# Label
label = mcrfpy.Caption(easing[:10], x, y - 20)
label.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(label)
demo_objects.append(label)
# Animate with this easing
mcrfpy.Animation("x", float(x + 150), 3.0, easing).start(f)
def demo4_performance():
"""Many simultaneous animations"""
global demo_objects
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 4: 50+ Simultaneous Animations"
for i in range(50):
x = 100 + (i % 10) * 80
y = 150 + (i // 10) * 80
f = mcrfpy.Frame(x, y, 30, 30)
f.fill_color = mcrfpy.Color((i*37)%256, (i*73)%256, (i*113)%256)
ui.append(f)
demo_objects.append(f)
# Animate to random position
target_x = 150 + (i % 8) * 90
target_y = 200 + (i // 8) * 70
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
mcrfpy.Animation("x", float(target_x), 2.5, easing).start(f)
mcrfpy.Animation("y", float(target_y), 2.5, easing).start(f)
def next_demo(runtime):
"""Run the next demo with proper cleanup"""
global current_demo
# First hide old objects
hide_demo_objects()
demos = [
demo1_frame_animations,
demo2_caption_animations,
demo3_easing_showcase,
demo4_performance
]
if current_demo < len(demos):
demos[current_demo]()
current_demo += 1
if current_demo < len(demos):
mcrfpy.setTimer("next", next_demo, int(DEMO_DURATION * 1000))
else:
subtitle.text = "Demo Complete!"
mcrfpy.setTimer("exit", lambda t: mcrfpy.exit(), 2000)
# Initialize
print("Starting Animation Sizzle Reel (Fixed)...")
create_scene()
mcrfpy.setTimer("start", next_demo, 500)

View file

@ -0,0 +1,149 @@
#!/usr/bin/env python3
"""
Text Input Demo with Auto-Test
Demonstrates the text input widget system with automated testing
"""
import mcrfpy
from mcrfpy import automation
import sys
from text_input_widget import FocusManager, TextInput
def test_text_input(timer_name):
"""Automated test that runs after scene is loaded"""
print("Testing text input widget system...")
# Take a screenshot of the initial state
automation.screenshot("text_input_initial.png")
# Simulate typing in the first field
print("Clicking on first field...")
automation.click(200, 130) # Click on name field
# Type some text
for char in "John Doe":
mcrfpy.keypressScene("text_input_demo", char)
# Tab to next field
mcrfpy.keypressScene("text_input_demo", "Tab")
# Type email
for char in "john@example.com":
mcrfpy.keypressScene("text_input_demo", char)
# Tab to comment field
mcrfpy.keypressScene("text_input_demo", "Tab")
# Type comment
for char in "Testing the widget!":
mcrfpy.keypressScene("text_input_demo", char)
# Take final screenshot
automation.screenshot("text_input_filled.png")
print("Text input test complete!")
print("Screenshots saved: text_input_initial.png, text_input_filled.png")
# Exit after test
sys.exit(0)
def create_demo():
"""Create a demo scene with multiple text input fields"""
mcrfpy.createScene("text_input_demo")
scene = mcrfpy.sceneUI("text_input_demo")
# Create background
bg = mcrfpy.Frame(0, 0, 800, 600)
bg.fill_color = (40, 40, 40, 255)
scene.append(bg)
# Title
title = mcrfpy.Caption(10, 10, "Text Input Widget Demo - Auto Test")
title.color = (255, 255, 255, 255)
scene.append(title)
# Instructions
instructions = mcrfpy.Caption(10, 50, "This will automatically test the text input system")
instructions.color = (200, 200, 200, 255)
scene.append(instructions)
# Create focus manager
focus_manager = FocusManager()
# Create text input fields
fields = []
# Name field
name_input = TextInput(50, 120, 300, "Name:", 16)
name_input._focus_manager = focus_manager
focus_manager.register(name_input)
scene.append(name_input.frame)
if hasattr(name_input, 'label_text'):
scene.append(name_input.label_text)
scene.append(name_input.text_display)
scene.append(name_input.cursor)
fields.append(name_input)
# Email field
email_input = TextInput(50, 180, 300, "Email:", 16)
email_input._focus_manager = focus_manager
focus_manager.register(email_input)
scene.append(email_input.frame)
if hasattr(email_input, 'label_text'):
scene.append(email_input.label_text)
scene.append(email_input.text_display)
scene.append(email_input.cursor)
fields.append(email_input)
# Comment field
comment_input = TextInput(50, 240, 400, "Comment:", 16)
comment_input._focus_manager = focus_manager
focus_manager.register(comment_input)
scene.append(comment_input.frame)
if hasattr(comment_input, 'label_text'):
scene.append(comment_input.label_text)
scene.append(comment_input.text_display)
scene.append(comment_input.cursor)
fields.append(comment_input)
# Result display
result_text = mcrfpy.Caption(50, 320, "Values will appear here as you type...")
result_text.color = (150, 255, 150, 255)
scene.append(result_text)
def update_result(*args):
"""Update the result display with current field values"""
name = fields[0].get_text()
email = fields[1].get_text()
comment = fields[2].get_text()
result_text.text = f"Name: {name} | Email: {email} | Comment: {comment}"
# Set change handlers
for field in fields:
field.on_change = update_result
# Keyboard handler
def handle_keys(scene_name, key):
"""Global keyboard handler"""
# Let focus manager handle the key first
if not focus_manager.handle_key(key):
# Handle focus switching
if key == "Tab":
focus_manager.focus_next()
elif key == "Escape":
print("Demo terminated by user")
sys.exit(0)
mcrfpy.keypressScene("text_input_demo", handle_keys)
# Set the scene
mcrfpy.setScene("text_input_demo")
# Schedule the automated test
mcrfpy.setTimer("test", test_text_input, 500) # Run test after 500ms
if __name__ == "__main__":
create_demo()

View file

@ -0,0 +1,320 @@
#!/usr/bin/env python3
"""
Standalone Text Input Widget System for McRogueFace
Complete implementation with demo and automated test
"""
import mcrfpy
import sys
class FocusManager:
"""Manages focus state across multiple widgets"""
def __init__(self):
self.widgets = []
self.focused_widget = None
self.focus_index = -1
def register(self, widget):
"""Register a widget with the focus manager"""
self.widgets.append(widget)
if self.focused_widget is None:
self.focus(widget)
def focus(self, widget):
"""Set focus to a specific widget"""
if self.focused_widget:
self.focused_widget.on_blur()
self.focused_widget = widget
self.focus_index = self.widgets.index(widget) if widget in self.widgets else -1
if widget:
widget.on_focus()
def focus_next(self):
"""Focus the next widget in the list"""
if not self.widgets:
return
self.focus_index = (self.focus_index + 1) % len(self.widgets)
self.focus(self.widgets[self.focus_index])
def handle_key(self, key):
"""Route key events to focused widget. Returns True if handled."""
if self.focused_widget:
return self.focused_widget.handle_key(key)
return False
class TextInput:
"""A text input widget with cursor support"""
def __init__(self, x, y, width, label="", font_size=16):
self.x = x
self.y = y
self.width = width
self.label = label
self.font_size = font_size
# Text state
self.text = ""
self.cursor_pos = 0
# Visual state
self.focused = False
# Create UI elements
self._create_ui()
def _create_ui(self):
"""Create the visual components"""
# Background frame
self.frame = mcrfpy.Frame(self.x, self.y, self.width, self.font_size + 8)
self.frame.outline = 2
self.frame.fill_color = (255, 255, 255, 255)
self.frame.outline_color = (128, 128, 128, 255)
# Label (if provided)
if self.label:
self.label_text = mcrfpy.Caption(
self.x - 5,
self.y - self.font_size - 5,
self.label
)
self.label_text.color = (255, 255, 255, 255)
# Text display
self.text_display = mcrfpy.Caption(
self.x + 4,
self.y + 4,
""
)
self.text_display.color = (0, 0, 0, 255)
# Cursor (using a thin frame)
self.cursor = mcrfpy.Frame(
self.x + 4,
self.y + 4,
2,
self.font_size
)
self.cursor.fill_color = (0, 0, 0, 255)
self.cursor.visible = False
# Click handler
self.frame.click = self._on_click
def _on_click(self, x, y, button):
"""Handle mouse clicks on the input field"""
if button == 1: # Left click
if hasattr(self, '_focus_manager'):
self._focus_manager.focus(self)
def on_focus(self):
"""Called when this widget receives focus"""
self.focused = True
self.frame.outline_color = (0, 120, 255, 255)
self.frame.outline = 3
self.cursor.visible = True
self._update_cursor_position()
def on_blur(self):
"""Called when this widget loses focus"""
self.focused = False
self.frame.outline_color = (128, 128, 128, 255)
self.frame.outline = 2
self.cursor.visible = False
def handle_key(self, key):
"""Handle keyboard input. Returns True if key was handled."""
if not self.focused:
return False
handled = True
# Special keys
if key == "BackSpace":
if self.cursor_pos > 0:
self.text = self.text[:self.cursor_pos-1] + self.text[self.cursor_pos:]
self.cursor_pos -= 1
elif key == "Delete":
if self.cursor_pos < len(self.text):
self.text = self.text[:self.cursor_pos] + self.text[self.cursor_pos+1:]
elif key == "Left":
self.cursor_pos = max(0, self.cursor_pos - 1)
elif key == "Right":
self.cursor_pos = min(len(self.text), self.cursor_pos + 1)
elif key == "Home":
self.cursor_pos = 0
elif key == "End":
self.cursor_pos = len(self.text)
elif key == "Tab":
handled = False # Let focus manager handle
elif len(key) == 1 and key.isprintable():
# Regular character input
self.text = self.text[:self.cursor_pos] + key + self.text[self.cursor_pos:]
self.cursor_pos += 1
else:
handled = False
# Update display
self._update_display()
return handled
def _update_display(self):
"""Update the text display and cursor position"""
self.text_display.text = self.text
self._update_cursor_position()
def _update_cursor_position(self):
"""Update cursor visual position based on text position"""
if not self.focused:
return
# Simple character width estimation (monospace assumption)
char_width = self.font_size * 0.6
cursor_x = self.x + 4 + int(self.cursor_pos * char_width)
self.cursor.x = cursor_x
def get_text(self):
"""Get the current text content"""
return self.text
def add_to_scene(self, scene):
"""Add all components to a scene"""
scene.append(self.frame)
if hasattr(self, 'label_text'):
scene.append(self.label_text)
scene.append(self.text_display)
scene.append(self.cursor)
def run_automated_test(timer_name):
"""Automated test that demonstrates the text input functionality"""
print("\n=== Running Text Input Widget Test ===")
# Take initial screenshot
if hasattr(mcrfpy, 'automation'):
mcrfpy.automation.screenshot("text_input_test_1_initial.png")
print("Screenshot 1: Initial state saved")
# Simulate some typing
print("Simulating keyboard input...")
# The scene's keyboard handler will process these
test_sequence = [
("H", "Typing 'H'"),
("e", "Typing 'e'"),
("l", "Typing 'l'"),
("l", "Typing 'l'"),
("o", "Typing 'o'"),
("Tab", "Switching to next field"),
("T", "Typing 'T'"),
("e", "Typing 'e'"),
("s", "Typing 's'"),
("t", "Typing 't'"),
("Tab", "Switching to comment field"),
("W", "Typing 'W'"),
("o", "Typing 'o'"),
("r", "Typing 'r'"),
("k", "Typing 'k'"),
("s", "Typing 's'"),
("!", "Typing '!'"),
]
# Process each key
for key, desc in test_sequence:
print(f" - {desc}")
# Trigger the scene's keyboard handler
if hasattr(mcrfpy, '_scene_key_handler'):
mcrfpy._scene_key_handler("text_input_demo", key)
# Take final screenshot
if hasattr(mcrfpy, 'automation'):
mcrfpy.automation.screenshot("text_input_test_2_filled.png")
print("Screenshot 2: Filled state saved")
print("\n=== Text Input Test Complete! ===")
print("The text input widget system is working correctly.")
print("Features demonstrated:")
print(" - Focus management (blue outline on focused field)")
print(" - Text entry with cursor")
print(" - Tab navigation between fields")
print(" - Visual feedback")
# Exit successfully
sys.exit(0)
def create_demo():
"""Create the demo scene"""
mcrfpy.createScene("text_input_demo")
scene = mcrfpy.sceneUI("text_input_demo")
# Create background
bg = mcrfpy.Frame(0, 0, 800, 600)
bg.fill_color = (40, 40, 40, 255)
scene.append(bg)
# Title
title = mcrfpy.Caption(10, 10, "Text Input Widget System")
title.color = (255, 255, 255, 255)
scene.append(title)
# Instructions
info = mcrfpy.Caption(10, 50, "Click to focus | Tab to switch fields | Type to enter text")
info.color = (200, 200, 200, 255)
scene.append(info)
# Create focus manager
focus_manager = FocusManager()
# Create text inputs
name_input = TextInput(50, 120, 300, "Name:", 16)
name_input._focus_manager = focus_manager
focus_manager.register(name_input)
name_input.add_to_scene(scene)
email_input = TextInput(50, 180, 300, "Email:", 16)
email_input._focus_manager = focus_manager
focus_manager.register(email_input)
email_input.add_to_scene(scene)
comment_input = TextInput(50, 240, 400, "Comment:", 16)
comment_input._focus_manager = focus_manager
focus_manager.register(comment_input)
comment_input.add_to_scene(scene)
# Status display
status = mcrfpy.Caption(50, 320, "Ready for input...")
status.color = (150, 255, 150, 255)
scene.append(status)
# Store references for the keyboard handler
widgets = [name_input, email_input, comment_input]
# Keyboard handler
def handle_keys(scene_name, key):
"""Global keyboard handler"""
if not focus_manager.handle_key(key):
if key == "Tab":
focus_manager.focus_next()
# Update status
texts = [w.get_text() for w in widgets]
status.text = f"Name: '{texts[0]}' | Email: '{texts[1]}' | Comment: '{texts[2]}'"
# Store handler reference for test
mcrfpy._scene_key_handler = handle_keys
mcrfpy.keypressScene("text_input_demo", handle_keys)
mcrfpy.setScene("text_input_demo")
# Schedule automated test
mcrfpy.setTimer("test", run_automated_test, 1000) # Run after 1 second
if __name__ == "__main__":
print("Starting Text Input Widget Demo...")
create_demo()

View file

@ -0,0 +1,320 @@
#!/usr/bin/env python3
"""
Text Input Widget System for McRogueFace
A pure Python implementation of focusable text input fields
"""
import mcrfpy
import sys
from dataclasses import dataclass
from typing import Optional, List, Callable
class FocusManager:
"""Manages focus state across multiple widgets"""
def __init__(self):
self.widgets: List['TextInput'] = []
self.focused_widget: Optional['TextInput'] = None
self.focus_index: int = -1
def register(self, widget: 'TextInput'):
"""Register a widget with the focus manager"""
self.widgets.append(widget)
if self.focused_widget is None:
self.focus(widget)
def focus(self, widget: 'TextInput'):
"""Set focus to a specific widget"""
if self.focused_widget:
self.focused_widget.on_blur()
self.focused_widget = widget
self.focus_index = self.widgets.index(widget) if widget in self.widgets else -1
if widget:
widget.on_focus()
def focus_next(self):
"""Focus the next widget in the list"""
if not self.widgets:
return
self.focus_index = (self.focus_index + 1) % len(self.widgets)
self.focus(self.widgets[self.focus_index])
def focus_prev(self):
"""Focus the previous widget in the list"""
if not self.widgets:
return
self.focus_index = (self.focus_index - 1) % len(self.widgets)
self.focus(self.widgets[self.focus_index])
def handle_key(self, key: str) -> bool:
"""Route key events to focused widget. Returns True if handled."""
if self.focused_widget:
return self.focused_widget.handle_key(key)
return False
class TextInput:
"""A text input widget with cursor and selection support"""
def __init__(self, x: int, y: int, width: int = 200, label: str = "",
font_size: int = 16, on_change: Optional[Callable] = None):
self.x = x
self.y = y
self.width = width
self.label = label
self.font_size = font_size
self.on_change = on_change
# Text state
self.text = ""
self.cursor_pos = 0
self.selection_start = -1
self.selection_end = -1
# Visual state
self.focused = False
self.cursor_visible = True
self.cursor_blink_timer = 0
# Create UI elements
self._create_ui()
def _create_ui(self):
"""Create the visual components"""
# Background frame
self.frame = mcrfpy.Frame(self.x, self.y, self.width, self.font_size + 8)
self.frame.outline = 2
self.frame.fill_color = (255, 255, 255, 255)
self.frame.outline_color = (128, 128, 128, 255)
# Label (if provided)
if self.label:
self.label_text = mcrfpy.Caption(
self.x - 5,
self.y - self.font_size - 5,
self.label
)
self.label_text.color = (255, 255, 255, 255)
# Text display
self.text_display = mcrfpy.Caption(
self.x + 4,
self.y + 4,
""
)
self.text_display.color = (0, 0, 0, 255)
# Cursor (using a thin frame)
self.cursor = mcrfpy.Frame(
self.x + 4,
self.y + 4,
2,
self.font_size
)
self.cursor.fill_color = (0, 0, 0, 255)
self.cursor.visible = False
# Click handler
self.frame.click = self._on_click
def _on_click(self, x: int, y: int, button: int):
"""Handle mouse clicks on the input field"""
if button == 1: # Left click
# Request focus through the focus manager
if hasattr(self, '_focus_manager'):
self._focus_manager.focus(self)
def on_focus(self):
"""Called when this widget receives focus"""
self.focused = True
self.frame.outline_color = (0, 120, 255, 255)
self.frame.outline = 3
self.cursor.visible = True
self._update_cursor_position()
def on_blur(self):
"""Called when this widget loses focus"""
self.focused = False
self.frame.outline_color = (128, 128, 128, 255)
self.frame.outline = 2
self.cursor.visible = False
def handle_key(self, key: str) -> bool:
"""Handle keyboard input. Returns True if key was handled."""
if not self.focused:
return False
handled = True
old_text = self.text
# Special keys
if key == "BackSpace":
if self.cursor_pos > 0:
self.text = self.text[:self.cursor_pos-1] + self.text[self.cursor_pos:]
self.cursor_pos -= 1
elif key == "Delete":
if self.cursor_pos < len(self.text):
self.text = self.text[:self.cursor_pos] + self.text[self.cursor_pos+1:]
elif key == "Left":
self.cursor_pos = max(0, self.cursor_pos - 1)
elif key == "Right":
self.cursor_pos = min(len(self.text), self.cursor_pos + 1)
elif key == "Home":
self.cursor_pos = 0
elif key == "End":
self.cursor_pos = len(self.text)
elif key == "Return":
handled = False # Let parent handle submit
elif key == "Tab":
handled = False # Let focus manager handle
elif len(key) == 1 and key.isprintable():
# Regular character input
self.text = self.text[:self.cursor_pos] + key + self.text[self.cursor_pos:]
self.cursor_pos += 1
else:
handled = False
# Update display
if old_text != self.text:
self._update_display()
if self.on_change:
self.on_change(self.text)
else:
self._update_cursor_position()
return handled
def _update_display(self):
"""Update the text display and cursor position"""
self.text_display.text = self.text
self._update_cursor_position()
def _update_cursor_position(self):
"""Update cursor visual position based on text position"""
if not self.focused:
return
# Simple character width estimation (monospace assumption)
char_width = self.font_size * 0.6
cursor_x = self.x + 4 + int(self.cursor_pos * char_width)
self.cursor.x = cursor_x
def set_text(self, text: str):
"""Set the text content"""
self.text = text
self.cursor_pos = len(text)
self._update_display()
def get_text(self) -> str:
"""Get the current text content"""
return self.text
# Demo application
def create_demo():
"""Create a demo scene with multiple text input fields"""
mcrfpy.createScene("text_input_demo")
scene = mcrfpy.sceneUI("text_input_demo")
# Create background
bg = mcrfpy.Frame(0, 0, 800, 600)
bg.fill_color = (40, 40, 40, 255)
scene.append(bg)
# Title
title = mcrfpy.Caption(10, 10, "Text Input Widget Demo")
title.color = (255, 255, 255, 255)
scene.append(title)
# Instructions
instructions = mcrfpy.Caption(10, 50, "Click to focus, Tab to switch fields, Type to enter text")
instructions.color = (200, 200, 200, 255)
scene.append(instructions)
# Create focus manager
focus_manager = FocusManager()
# Create text input fields
fields = []
# Name field
name_input = TextInput(50, 120, 300, "Name:", 16)
name_input._focus_manager = focus_manager
focus_manager.register(name_input)
scene.append(name_input.frame)
if hasattr(name_input, 'label_text'):
scene.append(name_input.label_text)
scene.append(name_input.text_display)
scene.append(name_input.cursor)
fields.append(name_input)
# Email field
email_input = TextInput(50, 180, 300, "Email:", 16)
email_input._focus_manager = focus_manager
focus_manager.register(email_input)
scene.append(email_input.frame)
if hasattr(email_input, 'label_text'):
scene.append(email_input.label_text)
scene.append(email_input.text_display)
scene.append(email_input.cursor)
fields.append(email_input)
# Comment field
comment_input = TextInput(50, 240, 400, "Comment:", 16)
comment_input._focus_manager = focus_manager
focus_manager.register(comment_input)
scene.append(comment_input.frame)
if hasattr(comment_input, 'label_text'):
scene.append(comment_input.label_text)
scene.append(comment_input.text_display)
scene.append(comment_input.cursor)
fields.append(comment_input)
# Result display
result_text = mcrfpy.Caption(50, 320, "Type in the fields above...")
result_text.color = (150, 255, 150, 255)
scene.append(result_text)
def update_result(*args):
"""Update the result display with current field values"""
name = fields[0].get_text()
email = fields[1].get_text()
comment = fields[2].get_text()
result_text.text = f"Name: {name} | Email: {email} | Comment: {comment}"
# Set change handlers
for field in fields:
field.on_change = update_result
# Keyboard handler
def handle_keys(scene_name, key):
"""Global keyboard handler"""
# Let focus manager handle the key first
if not focus_manager.handle_key(key):
# Handle focus switching
if key == "Tab":
focus_manager.focus_next()
elif key == "Escape":
print("Demo complete!")
sys.exit(0)
mcrfpy.keypressScene("text_input_demo", handle_keys)
# Set the scene
mcrfpy.setScene("text_input_demo")
# Add a timer for cursor blinking (optional enhancement)
def blink_cursor(timer_name):
"""Blink the cursor for the focused widget"""
if focus_manager.focused_widget and focus_manager.focused_widget.focused:
cursor = focus_manager.focused_widget.cursor
cursor.visible = not cursor.visible
mcrfpy.setTimer("cursor_blink", blink_cursor, 500) # Blink every 500ms
if __name__ == "__main__":
create_demo()

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

@ -1,32 +0,0 @@
#!/usr/bin/env python3
"""Test if closing stdin prevents the >>> prompt"""
import mcrfpy
import sys
import os
print("=== Testing stdin theory ===")
print(f"stdin.isatty(): {sys.stdin.isatty()}")
print(f"stdin fileno: {sys.stdin.fileno()}")
# Set up a basic scene
mcrfpy.createScene("stdin_test")
mcrfpy.setScene("stdin_test")
# Try to prevent interactive mode by closing stdin
print("\nAttempting to prevent interactive mode...")
try:
# Method 1: Close stdin
sys.stdin.close()
print("Closed sys.stdin")
except:
print("Failed to close sys.stdin")
try:
# Method 2: Redirect stdin to /dev/null
devnull = open(os.devnull, 'r')
os.dup2(devnull.fileno(), 0)
print("Redirected stdin to /dev/null")
except:
print("Failed to redirect stdin")
print("\nScript complete. If >>> still appears, the issue is elsewhere.")

View file

@ -0,0 +1,101 @@
// Example of how UIFrame would implement unified click handling
//
// Click Priority Example:
// - Dialog Frame (has click handler to drag window)
// - Title Caption (no click handler)
// - Button Frame (has click handler)
// - Button Caption "OK" (no click handler)
// - Close X Sprite (has click handler)
//
// Clicking on:
// - "OK" text -> Button Frame gets the click (deepest parent with handler)
// - Close X -> Close sprite gets the click
// - Title bar -> Dialog Frame gets the click (no child has handler there)
// - Outside dialog -> nullptr (bounds check fails)
class UIFrame : public UIDrawable, protected RectangularContainer {
private:
// Implementation of container interface
sf::Vector2f toChildCoordinates(sf::Vector2f localPoint, int childIndex) const override {
// Children use same coordinate system as frame's local coordinates
return localPoint;
}
UIDrawable* getClickHandler() override {
return click_callable ? this : nullptr;
}
std::vector<UIDrawable*> getClickableChildren() override {
std::vector<UIDrawable*> result;
for (auto& child : *children) {
result.push_back(child.get());
}
return result;
}
public:
UIDrawable* click_at(sf::Vector2f point) override {
// Update bounds from box
bounds = sf::FloatRect(box.getPosition().x, box.getPosition().y,
box.getSize().x, box.getSize().y);
// Use unified handler
return handleClick(point);
}
};
// Example for UIGrid with entity coordinate transformation
class UIGrid : public UIDrawable, protected RectangularContainer {
private:
sf::Vector2f toChildCoordinates(sf::Vector2f localPoint, int childIndex) const override {
// For entities, we need to transform from pixel coordinates to grid coordinates
// This is where the grid's special coordinate system is handled
// Assuming entity positions are in grid cells, not pixels
// We pass pixel coordinates relative to the grid's rendering area
return localPoint; // Entities will handle their own sprite positioning
}
std::vector<UIDrawable*> getClickableChildren() override {
std::vector<UIDrawable*> result;
// Only check entities that are visible on screen
float left_edge = center_x - (box.getSize().x / 2.0f) / (grid_size * zoom);
float top_edge = center_y - (box.getSize().y / 2.0f) / (grid_size * zoom);
float right_edge = left_edge + (box.getSize().x / (grid_size * zoom));
float bottom_edge = top_edge + (box.getSize().y / (grid_size * zoom));
for (auto& entity : entities) {
// Check if entity is within visible bounds
if (entity->position.x >= left_edge - 1 && entity->position.x < right_edge + 1 &&
entity->position.y >= top_edge - 1 && entity->position.y < bottom_edge + 1) {
result.push_back(&entity->sprite);
}
}
return result;
}
};
// For Scene, which has no coordinate transformation
class PyScene : protected UIContainerBase {
private:
sf::Vector2f toLocalCoordinates(sf::Vector2f point) const override {
// Scene uses window coordinates directly
return point;
}
sf::Vector2f toChildCoordinates(sf::Vector2f localPoint, int childIndex) const override {
// Top-level drawables use window coordinates
return localPoint;
}
bool containsPoint(sf::Vector2f localPoint) const override {
// Scene contains all points (full window)
return true;
}
UIDrawable* getClickHandler() override {
// Scene itself doesn't handle clicks
return nullptr;
}
};

View file

@ -0,0 +1,4 @@
import mcrfpy
e = mcrfpy.Entity(0, 0)
print("Entity attributes:", dir(e))
print("\nEntity repr:", repr(e))

View file

@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""Debug empty paths issue"""
import mcrfpy
import sys
print("Debugging empty paths...")
# Create scene and grid
mcrfpy.createScene("debug")
grid = mcrfpy.Grid(grid_x=10, grid_y=10)
# Initialize grid - all walkable
print("\nInitializing grid...")
for y in range(10):
for x in range(10):
grid.at(x, y).walkable = True
# Test simple path
print("\nTest 1: Simple path from (0,0) to (5,5)")
path = grid.compute_astar_path(0, 0, 5, 5)
print(f" A* path: {path}")
print(f" Path length: {len(path)}")
# Test with Dijkstra
print("\nTest 2: Same path with Dijkstra")
grid.compute_dijkstra(0, 0)
dpath = grid.get_dijkstra_path(5, 5)
print(f" Dijkstra path: {dpath}")
print(f" Path length: {len(dpath)}")
# Check if grid is properly initialized
print("\nTest 3: Checking grid cells")
for y in range(3):
for x in range(3):
cell = grid.at(x, y)
print(f" Cell ({x},{y}): walkable={cell.walkable}")
# Test with walls
print("\nTest 4: Path with wall")
grid.at(2, 2).walkable = False
grid.at(3, 2).walkable = False
grid.at(4, 2).walkable = False
print(" Added wall at y=2, x=2,3,4")
path2 = grid.compute_astar_path(0, 0, 5, 5)
print(f" A* path with wall: {path2}")
print(f" Path length: {len(path2)}")
# Test invalid paths
print("\nTest 5: Path to blocked cell")
grid.at(9, 9).walkable = False
path3 = grid.compute_astar_path(0, 0, 9, 9)
print(f" Path to blocked cell: {path3}")
# Check TCOD map sync
print("\nTest 6: Verify TCOD map is synced")
# Try to force a sync
print(" Checking if syncTCODMap exists...")
if hasattr(grid, 'sync_tcod_map'):
print(" Calling sync_tcod_map()")
grid.sync_tcod_map()
else:
print(" No sync_tcod_map method found")
# Try path again
print("\nTest 7: Path after potential sync")
path4 = grid.compute_astar_path(0, 0, 5, 5)
print(f" A* path: {path4}")
def timer_cb(dt):
sys.exit(0)
# Quick UI setup
ui = mcrfpy.sceneUI("debug")
ui.append(grid)
mcrfpy.setScene("debug")
mcrfpy.setTimer("exit", timer_cb, 100)
print("\nStarting timer...")

View file

@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""Test Grid.at() method with various argument formats"""
import mcrfpy
import sys
def test_grid_at_arguments():
"""Test that Grid.at() accepts all required argument formats"""
print("Testing Grid.at() argument formats...")
# Create a test scene
mcrfpy.createScene("test")
# Create a grid
grid = mcrfpy.Grid(10, 10)
ui = mcrfpy.sceneUI("test")
ui.append(grid)
success_count = 0
total_tests = 4
# Test 1: Two positional arguments (x, y)
try:
point1 = grid.at(5, 5)
print("✓ Test 1 PASSED: grid.at(5, 5)")
success_count += 1
except Exception as e:
print(f"✗ Test 1 FAILED: grid.at(5, 5) - {e}")
# Test 2: Single tuple argument (x, y)
try:
point2 = grid.at((3, 3))
print("✓ Test 2 PASSED: grid.at((3, 3))")
success_count += 1
except Exception as e:
print(f"✗ Test 2 FAILED: grid.at((3, 3)) - {e}")
# Test 3: Keyword arguments x=x, y=y
try:
point3 = grid.at(x=7, y=2)
print("✓ Test 3 PASSED: grid.at(x=7, y=2)")
success_count += 1
except Exception as e:
print(f"✗ Test 3 FAILED: grid.at(x=7, y=2) - {e}")
# Test 4: pos keyword argument pos=(x, y)
try:
point4 = grid.at(pos=(1, 8))
print("✓ Test 4 PASSED: grid.at(pos=(1, 8))")
success_count += 1
except Exception as e:
print(f"✗ Test 4 FAILED: grid.at(pos=(1, 8)) - {e}")
# Test error cases
print("\nTesting error cases...")
# Test 5: Invalid - mixing pos with x/y
try:
grid.at(x=1, pos=(2, 2))
print("✗ Test 5 FAILED: Should have raised error for mixing pos and x/y")
except TypeError as e:
print(f"✓ Test 5 PASSED: Correctly rejected mixing pos and x/y - {e}")
# Test 6: Invalid - out of range
try:
grid.at(15, 15)
print("✗ Test 6 FAILED: Should have raised error for out of range")
except ValueError as e:
print(f"✓ Test 6 PASSED: Correctly rejected out of range - {e}")
# Test 7: Verify all points are valid GridPoint objects
try:
# Check that we can set walkable on all returned points
if 'point1' in locals():
point1.walkable = True
if 'point2' in locals():
point2.walkable = False
if 'point3' in locals():
point3.color = mcrfpy.Color(255, 0, 0)
if 'point4' in locals():
point4.tilesprite = 5
print("✓ All returned GridPoint objects are valid")
except Exception as e:
print(f"✗ GridPoint objects validation failed: {e}")
print(f"\nSummary: {success_count}/{total_tests} tests passed")
if success_count == total_tests:
print("ALL TESTS PASSED!")
sys.exit(0)
else:
print("SOME TESTS FAILED!")
sys.exit(1)
# Run timer callback to execute tests after render loop starts
def run_test(elapsed):
test_grid_at_arguments()
# Set a timer to run the test
mcrfpy.setTimer("test", run_test, 100)