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:
parent
1a143982e1
commit
f4343e1e82
163 changed files with 12812 additions and 5441 deletions
215
tests/constructor_audit.py
Normal file
215
tests/constructor_audit.py
Normal 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)
|
||||
30
tests/count_format_string.py
Normal file
30
tests/count_format_string.py
Normal 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}")
|
||||
81
tests/demo_animation_callback_usage.py
Normal file
81
tests/demo_animation_callback_usage.py
Normal 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()
|
||||
146
tests/demos/animation_demo_safe.py
Normal file
146
tests/demos/animation_demo_safe.py
Normal 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)
|
||||
616
tests/demos/animation_sizzle_reel.py
Normal file
616
tests/demos/animation_sizzle_reel.py
Normal 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.")
|
||||
227
tests/demos/animation_sizzle_reel_fixed.py
Normal file
227
tests/demos/animation_sizzle_reel_fixed.py
Normal 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)
|
||||
307
tests/demos/animation_sizzle_reel_v2.py
Normal file
307
tests/demos/animation_sizzle_reel_v2.py
Normal 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)
|
||||
316
tests/demos/animation_sizzle_reel_working.py
Normal file
316
tests/demos/animation_sizzle_reel_working.py
Normal 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))
|
||||
207
tests/demos/api_demo_final.py
Normal file
207
tests/demos/api_demo_final.py
Normal 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()
|
||||
99
tests/demos/debug_astar_demo.py
Normal file
99
tests/demos/debug_astar_demo.py
Normal 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)
|
||||
137
tests/demos/dijkstra_demo_working.py
Normal file
137
tests/demos/dijkstra_demo_working.py
Normal 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.")
|
||||
306
tests/demos/exhaustive_api_demo_fixed.py
Normal file
306
tests/demos/exhaustive_api_demo_fixed.py
Normal 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()
|
||||
391
tests/demos/path_vision_sizzle_reel.py
Normal file
391
tests/demos/path_vision_sizzle_reel.py
Normal 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")
|
||||
377
tests/demos/pathfinding_showcase.py
Normal file
377
tests/demos/pathfinding_showcase.py
Normal 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.")
|
||||
226
tests/demos/simple_text_input.py
Normal file
226
tests/demos/simple_text_input.py
Normal 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()
|
||||
190
tests/demos/sizzle_reel_final.py
Normal file
190
tests/demos/sizzle_reel_final.py
Normal 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)
|
||||
193
tests/demos/sizzle_reel_final_fixed.py
Normal file
193
tests/demos/sizzle_reel_final_fixed.py
Normal 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)
|
||||
149
tests/demos/text_input_demo.py
Normal file
149
tests/demos/text_input_demo.py
Normal 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()
|
||||
320
tests/demos/text_input_standalone.py
Normal file
320
tests/demos/text_input_standalone.py
Normal 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()
|
||||
320
tests/demos/text_input_widget.py
Normal file
320
tests/demos/text_input_widget.py
Normal 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()
|
||||
235
tests/integration/astar_vs_dijkstra.py
Normal file
235
tests/integration/astar_vs_dijkstra.py
Normal 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!")
|
||||
59
tests/integration/debug_visibility.py
Normal file
59
tests/integration/debug_visibility.py
Normal 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)
|
||||
234
tests/integration/dijkstra_all_paths.py
Normal file
234
tests/integration/dijkstra_all_paths.py
Normal 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")
|
||||
236
tests/integration/dijkstra_cycle_paths.py
Normal file
236
tests/integration/dijkstra_cycle_paths.py
Normal 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")
|
||||
161
tests/integration/dijkstra_debug.py
Normal file
161
tests/integration/dijkstra_debug.py
Normal 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.")
|
||||
244
tests/integration/dijkstra_interactive.py
Normal file
244
tests/integration/dijkstra_interactive.py
Normal 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)})")
|
||||
344
tests/integration/dijkstra_interactive_enhanced.py
Normal file
344
tests/integration/dijkstra_interactive_enhanced.py
Normal 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)})")
|
||||
146
tests/integration/dijkstra_test.py
Normal file
146
tests/integration/dijkstra_test.py
Normal 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...")
|
||||
201
tests/integration/interactive_visibility.py
Normal file
201
tests/integration/interactive_visibility.py
Normal 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!")
|
||||
46
tests/integration/simple_interactive_visibility.py
Normal file
46
tests/integration/simple_interactive_visibility.py
Normal 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!")
|
||||
39
tests/integration/simple_visibility_test.py
Normal file
39
tests/integration/simple_visibility_test.py
Normal 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)
|
||||
|
|
@ -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.")
|
||||
101
tests/unified_click_example.cpp
Normal file
101
tests/unified_click_example.cpp
Normal 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;
|
||||
}
|
||||
};
|
||||
4
tests/unit/check_entity_attrs.py
Normal file
4
tests/unit/check_entity_attrs.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
import mcrfpy
|
||||
e = mcrfpy.Entity(0, 0)
|
||||
print("Entity attributes:", dir(e))
|
||||
print("\nEntity repr:", repr(e))
|
||||
80
tests/unit/debug_empty_paths.py
Normal file
80
tests/unit/debug_empty_paths.py
Normal 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...")
|
||||
100
tests/unit/grid_at_argument_test.py
Normal file
100
tests/unit/grid_at_argument_test.py
Normal 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue