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
81
tests/unit/WORKING_automation_test_example.py
Normal file
81
tests/unit/WORKING_automation_test_example.py
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Example of CORRECT test pattern using timer callbacks for automation"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
from datetime import datetime
|
||||
|
||||
def run_automation_tests():
|
||||
"""This runs AFTER the game loop has started and rendered frames"""
|
||||
print("\n=== Automation Test Running (1 second after start) ===")
|
||||
|
||||
# NOW we can take screenshots that will show content!
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"WORKING_screenshot_{timestamp}.png"
|
||||
|
||||
# Take screenshot - this should now show our red frame
|
||||
result = automation.screenshot(filename)
|
||||
print(f"Screenshot taken: {filename} - Result: {result}")
|
||||
|
||||
# Test clicking on the frame
|
||||
automation.click(200, 200) # Click in center of red frame
|
||||
|
||||
# Test keyboard input
|
||||
automation.typewrite("Hello from timer callback!")
|
||||
|
||||
# Take another screenshot to show any changes
|
||||
filename2 = f"WORKING_screenshot_after_click_{timestamp}.png"
|
||||
automation.screenshot(filename2)
|
||||
print(f"Second screenshot: {filename2}")
|
||||
|
||||
print("Test completed successfully!")
|
||||
print("\nThis works because:")
|
||||
print("1. The game loop has been running for 1 second")
|
||||
print("2. The scene has been rendered multiple times")
|
||||
print("3. The RenderTexture now contains actual rendered content")
|
||||
|
||||
# Cancel this timer so it doesn't repeat
|
||||
mcrfpy.delTimer("automation_test")
|
||||
|
||||
# Optional: exit after a moment
|
||||
def exit_game():
|
||||
print("Exiting...")
|
||||
mcrfpy.exit()
|
||||
mcrfpy.setTimer("exit", exit_game, 500) # Exit 500ms later
|
||||
|
||||
# This code runs during --exec script execution
|
||||
print("=== Setting Up Test Scene ===")
|
||||
|
||||
# Create scene with visible content
|
||||
mcrfpy.createScene("timer_test_scene")
|
||||
mcrfpy.setScene("timer_test_scene")
|
||||
ui = mcrfpy.sceneUI("timer_test_scene")
|
||||
|
||||
# Add a bright red frame that should be visible
|
||||
frame = mcrfpy.Frame(100, 100, 400, 300,
|
||||
fill_color=mcrfpy.Color(255, 0, 0), # Bright red
|
||||
outline_color=mcrfpy.Color(255, 255, 255), # White outline
|
||||
outline=5.0)
|
||||
ui.append(frame)
|
||||
|
||||
# Add text
|
||||
caption = mcrfpy.Caption(mcrfpy.Vector(150, 150),
|
||||
text="TIMER TEST - SHOULD BE VISIBLE",
|
||||
fill_color=mcrfpy.Color(255, 255, 255))
|
||||
caption.size = 24
|
||||
frame.children.append(caption)
|
||||
|
||||
# Add click handler to demonstrate interaction
|
||||
def frame_clicked(x, y, button):
|
||||
print(f"Frame clicked at ({x}, {y}) with button {button}")
|
||||
|
||||
frame.click = frame_clicked
|
||||
|
||||
print("Scene setup complete. Setting timer for automation tests...")
|
||||
|
||||
# THIS IS THE KEY: Set timer to run AFTER the game loop starts
|
||||
mcrfpy.setTimer("automation_test", run_automation_tests, 1000)
|
||||
|
||||
print("Timer set. Game loop will start after this script completes.")
|
||||
print("Automation tests will run 1 second later when content is visible.")
|
||||
|
||||
# Script ends here - game loop starts next
|
||||
34
tests/unit/api_createScene_test.py
Normal file
34
tests/unit/api_createScene_test.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test for mcrfpy.createScene() method"""
|
||||
import mcrfpy
|
||||
|
||||
def test_createScene():
|
||||
"""Test creating a new scene"""
|
||||
# Test creating scenes
|
||||
test_scenes = ["test_scene1", "test_scene2", "special_chars_!@#"]
|
||||
|
||||
for scene_name in test_scenes:
|
||||
try:
|
||||
mcrfpy.createScene(scene_name)
|
||||
print(f"✓ Created scene: {scene_name}")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to create scene {scene_name}: {e}")
|
||||
return
|
||||
|
||||
# Try to set scene to verify it was created
|
||||
try:
|
||||
mcrfpy.setScene("test_scene1")
|
||||
current = mcrfpy.currentScene()
|
||||
if current == "test_scene1":
|
||||
print("✓ Scene switching works correctly")
|
||||
else:
|
||||
print(f"✗ Scene switch failed: expected 'test_scene1', got '{current}'")
|
||||
except Exception as e:
|
||||
print(f"✗ Scene switching error: {e}")
|
||||
|
||||
print("PASS")
|
||||
|
||||
# Run test immediately
|
||||
print("Running createScene test...")
|
||||
test_createScene()
|
||||
print("Test completed.")
|
||||
92
tests/unit/api_keypressScene_test.py
Normal file
92
tests/unit/api_keypressScene_test.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test for mcrfpy.keypressScene() - Related to issue #61"""
|
||||
import mcrfpy
|
||||
|
||||
# Track keypresses for different scenes
|
||||
scene1_presses = []
|
||||
scene2_presses = []
|
||||
|
||||
def scene1_handler(key_code):
|
||||
"""Handle keyboard events for scene 1"""
|
||||
scene1_presses.append(key_code)
|
||||
print(f"Scene 1 key pressed: {key_code}")
|
||||
|
||||
def scene2_handler(key_code):
|
||||
"""Handle keyboard events for scene 2"""
|
||||
scene2_presses.append(key_code)
|
||||
print(f"Scene 2 key pressed: {key_code}")
|
||||
|
||||
def test_keypressScene():
|
||||
"""Test keyboard event handling for scenes"""
|
||||
print("=== Testing mcrfpy.keypressScene() ===")
|
||||
|
||||
# Test 1: Basic handler registration
|
||||
print("\n1. Basic handler registration:")
|
||||
mcrfpy.createScene("scene1")
|
||||
mcrfpy.setScene("scene1")
|
||||
|
||||
try:
|
||||
mcrfpy.keypressScene(scene1_handler)
|
||||
print("✓ Keypress handler registered for scene1")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to register handler: {e}")
|
||||
print("FAIL")
|
||||
return
|
||||
|
||||
# Test 2: Handler persists across scene changes
|
||||
print("\n2. Testing handler persistence:")
|
||||
mcrfpy.createScene("scene2")
|
||||
mcrfpy.setScene("scene2")
|
||||
|
||||
try:
|
||||
mcrfpy.keypressScene(scene2_handler)
|
||||
print("✓ Keypress handler registered for scene2")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to register handler for scene2: {e}")
|
||||
|
||||
# Switch back to scene1
|
||||
mcrfpy.setScene("scene1")
|
||||
current = mcrfpy.currentScene()
|
||||
print(f"✓ Switched back to: {current}")
|
||||
|
||||
# Test 3: Clear handler
|
||||
print("\n3. Testing handler clearing:")
|
||||
try:
|
||||
mcrfpy.keypressScene(None)
|
||||
print("✓ Handler cleared with None")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to clear handler: {e}")
|
||||
|
||||
# Test 4: Re-register handler
|
||||
print("\n4. Testing re-registration:")
|
||||
try:
|
||||
mcrfpy.keypressScene(scene1_handler)
|
||||
print("✓ Handler re-registered successfully")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to re-register: {e}")
|
||||
|
||||
# Test 5: Lambda functions
|
||||
print("\n5. Testing lambda functions:")
|
||||
try:
|
||||
mcrfpy.keypressScene(lambda k: print(f"Lambda key: {k}"))
|
||||
print("✓ Lambda function accepted as handler")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed with lambda: {e}")
|
||||
|
||||
# Known issues
|
||||
print("\n⚠ Known Issues:")
|
||||
print("- Invalid argument (non-callable) causes segfault")
|
||||
print("- No way to query current handler")
|
||||
print("- Handler is global, not per-scene (issue #61)")
|
||||
|
||||
# Summary related to issue #61
|
||||
print("\n📋 Issue #61 Analysis:")
|
||||
print("Current: mcrfpy.keypressScene() sets a global handler")
|
||||
print("Proposed: Scene objects should encapsulate their own callbacks")
|
||||
print("Impact: Currently only one keypress handler active at a time")
|
||||
|
||||
print("\n=== Test Complete ===")
|
||||
print("PASS - API functions correctly within current limitations")
|
||||
|
||||
# Run test immediately
|
||||
test_keypressScene()
|
||||
80
tests/unit/api_sceneUI_test.py
Normal file
80
tests/unit/api_sceneUI_test.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test for mcrfpy.sceneUI() method - Related to issue #28"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
from datetime import datetime
|
||||
|
||||
def test_sceneUI():
|
||||
"""Test getting UI collection from scene"""
|
||||
# Create a test scene
|
||||
mcrfpy.createScene("ui_test_scene")
|
||||
mcrfpy.setScene("ui_test_scene")
|
||||
|
||||
# Get initial UI collection (should be empty)
|
||||
try:
|
||||
ui_collection = mcrfpy.sceneUI("ui_test_scene")
|
||||
print(f"✓ sceneUI returned collection with {len(ui_collection)} items")
|
||||
except Exception as e:
|
||||
print(f"✗ sceneUI failed: {e}")
|
||||
print("FAIL")
|
||||
return
|
||||
|
||||
# Add some UI elements to the scene
|
||||
frame = mcrfpy.Frame(10, 10, 200, 150,
|
||||
fill_color=mcrfpy.Color(100, 100, 200),
|
||||
outline_color=mcrfpy.Color(255, 255, 255),
|
||||
outline=2.0)
|
||||
ui_collection.append(frame)
|
||||
|
||||
caption = mcrfpy.Caption(mcrfpy.Vector(220, 10),
|
||||
text="Test Caption",
|
||||
fill_color=mcrfpy.Color(255, 255, 0))
|
||||
ui_collection.append(caption)
|
||||
|
||||
# Skip sprite for now since it requires a texture
|
||||
# sprite = mcrfpy.Sprite(10, 170, scale=2.0)
|
||||
# ui_collection.append(sprite)
|
||||
|
||||
# Get UI collection again
|
||||
ui_collection2 = mcrfpy.sceneUI("ui_test_scene")
|
||||
print(f"✓ After adding elements: {len(ui_collection2)} items")
|
||||
|
||||
# Test iteration (Issue #28 - UICollectionIter)
|
||||
try:
|
||||
item_types = []
|
||||
for item in ui_collection2:
|
||||
item_types.append(type(item).__name__)
|
||||
print(f"✓ Iteration works, found types: {item_types}")
|
||||
except Exception as e:
|
||||
print(f"✗ Iteration failed (Issue #28): {e}")
|
||||
|
||||
# Test indexing
|
||||
try:
|
||||
first_item = ui_collection2[0]
|
||||
print(f"✓ Indexing works, first item type: {type(first_item).__name__}")
|
||||
except Exception as e:
|
||||
print(f"✗ Indexing failed: {e}")
|
||||
|
||||
# Test invalid scene name
|
||||
try:
|
||||
invalid_ui = mcrfpy.sceneUI("nonexistent_scene")
|
||||
print(f"✗ sceneUI should fail for nonexistent scene, got {len(invalid_ui)} items")
|
||||
except Exception as e:
|
||||
print(f"✓ sceneUI correctly fails for nonexistent scene: {e}")
|
||||
|
||||
# Take screenshot
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"test_sceneUI_{timestamp}.png"
|
||||
automation.screenshot(filename)
|
||||
print(f"Screenshot saved: {filename}")
|
||||
print("PASS")
|
||||
|
||||
# Set up timer to run test
|
||||
mcrfpy.setTimer("test", test_sceneUI, 1000)
|
||||
|
||||
# Cancel timer after running once
|
||||
def cleanup():
|
||||
mcrfpy.delTimer("test")
|
||||
mcrfpy.delTimer("cleanup")
|
||||
|
||||
mcrfpy.setTimer("cleanup", cleanup, 1100)
|
||||
44
tests/unit/api_setScene_currentScene_test.py
Normal file
44
tests/unit/api_setScene_currentScene_test.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test for mcrfpy.setScene() and currentScene() methods"""
|
||||
import mcrfpy
|
||||
|
||||
print("Starting setScene/currentScene test...")
|
||||
|
||||
# Create test scenes first
|
||||
scenes = ["scene_A", "scene_B", "scene_C"]
|
||||
for scene in scenes:
|
||||
mcrfpy.createScene(scene)
|
||||
print(f"Created scene: {scene}")
|
||||
|
||||
results = []
|
||||
|
||||
# Test switching between scenes
|
||||
for scene in scenes:
|
||||
try:
|
||||
mcrfpy.setScene(scene)
|
||||
current = mcrfpy.currentScene()
|
||||
if current == scene:
|
||||
results.append(f"✓ setScene/currentScene works for '{scene}'")
|
||||
else:
|
||||
results.append(f"✗ Scene mismatch: set '{scene}', got '{current}'")
|
||||
except Exception as e:
|
||||
results.append(f"✗ Error with scene '{scene}': {e}")
|
||||
|
||||
# Test invalid scene - it should not change the current scene
|
||||
current_before = mcrfpy.currentScene()
|
||||
mcrfpy.setScene("nonexistent_scene")
|
||||
current_after = mcrfpy.currentScene()
|
||||
if current_before == current_after:
|
||||
results.append(f"✓ setScene correctly ignores nonexistent scene (stayed on '{current_after}')")
|
||||
else:
|
||||
results.append(f"✗ Scene changed unexpectedly from '{current_before}' to '{current_after}'")
|
||||
|
||||
# Print results
|
||||
for result in results:
|
||||
print(result)
|
||||
|
||||
# Determine pass/fail
|
||||
if all("✓" in r for r in results):
|
||||
print("PASS")
|
||||
else:
|
||||
print("FAIL")
|
||||
70
tests/unit/api_timer_test.py
Normal file
70
tests/unit/api_timer_test.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test for mcrfpy.setTimer() and delTimer() methods"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_timers():
|
||||
"""Test timer API methods"""
|
||||
print("Testing mcrfpy timer methods...")
|
||||
|
||||
# Test 1: Create a simple timer
|
||||
try:
|
||||
call_count = [0]
|
||||
def simple_callback(runtime):
|
||||
call_count[0] += 1
|
||||
print(f"Timer callback called, count={call_count[0]}, runtime={runtime}")
|
||||
|
||||
mcrfpy.setTimer("test_timer", simple_callback, 100)
|
||||
print("✓ setTimer() called successfully")
|
||||
except Exception as e:
|
||||
print(f"✗ setTimer() failed: {e}")
|
||||
print("FAIL")
|
||||
return
|
||||
|
||||
# Test 2: Delete the timer
|
||||
try:
|
||||
mcrfpy.delTimer("test_timer")
|
||||
print("✓ delTimer() called successfully")
|
||||
except Exception as e:
|
||||
print(f"✗ delTimer() failed: {e}")
|
||||
print("FAIL")
|
||||
return
|
||||
|
||||
# Test 3: Delete non-existent timer (should not crash)
|
||||
try:
|
||||
mcrfpy.delTimer("nonexistent_timer")
|
||||
print("✓ delTimer() accepts non-existent timer names")
|
||||
except Exception as e:
|
||||
print(f"✗ delTimer() failed on non-existent timer: {e}")
|
||||
print("FAIL")
|
||||
return
|
||||
|
||||
# Test 4: Create multiple timers
|
||||
try:
|
||||
def callback1(rt): pass
|
||||
def callback2(rt): pass
|
||||
def callback3(rt): pass
|
||||
|
||||
mcrfpy.setTimer("timer1", callback1, 500)
|
||||
mcrfpy.setTimer("timer2", callback2, 750)
|
||||
mcrfpy.setTimer("timer3", callback3, 250)
|
||||
print("✓ Multiple timers created successfully")
|
||||
|
||||
# Clean up
|
||||
mcrfpy.delTimer("timer1")
|
||||
mcrfpy.delTimer("timer2")
|
||||
mcrfpy.delTimer("timer3")
|
||||
print("✓ Multiple timers deleted successfully")
|
||||
except Exception as e:
|
||||
print(f"✗ Multiple timer test failed: {e}")
|
||||
print("FAIL")
|
||||
return
|
||||
|
||||
print("\nAll timer API tests passed")
|
||||
print("PASS")
|
||||
|
||||
# Run the test
|
||||
test_timers()
|
||||
|
||||
# Exit cleanly
|
||||
sys.exit(0)
|
||||
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...")
|
||||
49
tests/unit/debug_render_test.py
Normal file
49
tests/unit/debug_render_test.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Debug rendering to find why screenshots are transparent"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
|
||||
# Check if we're in headless mode
|
||||
print("=== Debug Render Test ===")
|
||||
print(f"Module loaded: {mcrfpy}")
|
||||
print(f"Automation available: {'automation' in dir(mcrfpy)}")
|
||||
|
||||
# Try to understand the scene state
|
||||
print("\nCreating and checking scene...")
|
||||
mcrfpy.createScene("debug_scene")
|
||||
mcrfpy.setScene("debug_scene")
|
||||
current = mcrfpy.currentScene()
|
||||
print(f"Current scene: {current}")
|
||||
|
||||
# Get UI collection
|
||||
ui = mcrfpy.sceneUI("debug_scene")
|
||||
print(f"UI collection type: {type(ui)}")
|
||||
print(f"Initial UI elements: {len(ui)}")
|
||||
|
||||
# Add a simple frame
|
||||
frame = mcrfpy.Frame(0, 0, 100, 100,
|
||||
fill_color=mcrfpy.Color(255, 255, 255))
|
||||
ui.append(frame)
|
||||
print(f"After adding frame: {len(ui)} elements")
|
||||
|
||||
# Check if the issue is with timing
|
||||
print("\nTaking immediate screenshot...")
|
||||
result1 = automation.screenshot("debug_immediate.png")
|
||||
print(f"Immediate screenshot result: {result1}")
|
||||
|
||||
# Maybe we need to let the engine process the frame?
|
||||
# In headless mode with --exec, the game loop might not be running
|
||||
print("\nNote: In --exec mode, the game loop doesn't run continuously.")
|
||||
print("This might prevent rendering from occurring.")
|
||||
|
||||
# Let's also check what happens with multiple screenshots
|
||||
for i in range(3):
|
||||
result = automation.screenshot(f"debug_multi_{i}.png")
|
||||
print(f"Screenshot {i}: {result}")
|
||||
|
||||
print("\nConclusion: The issue appears to be that in --exec mode,")
|
||||
print("the render loop never runs, so nothing is drawn to the RenderTexture.")
|
||||
print("The screenshot captures an uninitialized/unrendered texture.")
|
||||
|
||||
sys.exit(0)
|
||||
2
tests/unit/empty_script.py
Normal file
2
tests/unit/empty_script.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# This script is intentionally empty
|
||||
pass
|
||||
7
tests/unit/exit_immediately_test.py
Normal file
7
tests/unit/exit_immediately_test.py
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test if calling mcrfpy.exit() prevents the >>> prompt"""
|
||||
import mcrfpy
|
||||
|
||||
print("Calling mcrfpy.exit() immediately...")
|
||||
mcrfpy.exit()
|
||||
print("This should not print if exit worked")
|
||||
451
tests/unit/generate_docs_screenshots.py
Executable file
451
tests/unit/generate_docs_screenshots.py
Executable file
|
|
@ -0,0 +1,451 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate documentation screenshots for McRogueFace UI elements"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
import os
|
||||
|
||||
# Crypt of Sokoban color scheme
|
||||
FRAME_COLOR = mcrfpy.Color(64, 64, 128)
|
||||
SHADOW_COLOR = mcrfpy.Color(64, 64, 86)
|
||||
BOX_COLOR = mcrfpy.Color(96, 96, 160)
|
||||
WHITE = mcrfpy.Color(255, 255, 255)
|
||||
BLACK = mcrfpy.Color(0, 0, 0)
|
||||
GREEN = mcrfpy.Color(0, 255, 0)
|
||||
RED = mcrfpy.Color(255, 0, 0)
|
||||
|
||||
# Create texture for sprites
|
||||
sprite_texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
|
||||
|
||||
# Output directory - create it during setup
|
||||
output_dir = "mcrogueface.github.io/images"
|
||||
if not os.path.exists(output_dir):
|
||||
os.makedirs(output_dir)
|
||||
|
||||
def create_caption(x, y, text, font_size=16, text_color=WHITE, outline_color=BLACK):
|
||||
"""Helper function to create captions with common settings"""
|
||||
caption = mcrfpy.Caption(mcrfpy.Vector(x, y), text=text)
|
||||
caption.size = font_size
|
||||
caption.fill_color = text_color
|
||||
caption.outline_color = outline_color
|
||||
return caption
|
||||
|
||||
def create_caption_example():
|
||||
"""Create a scene showing Caption UI element examples"""
|
||||
mcrfpy.createScene("caption_example")
|
||||
ui = mcrfpy.sceneUI("caption_example")
|
||||
|
||||
# Background frame
|
||||
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
|
||||
ui.append(bg)
|
||||
|
||||
# Title caption
|
||||
title = create_caption(200, 50, "Caption Examples", 32)
|
||||
ui.append(title)
|
||||
|
||||
# Different sized captions
|
||||
caption1 = create_caption(100, 150, "Large Caption (24pt)", 24)
|
||||
ui.append(caption1)
|
||||
|
||||
caption2 = create_caption(100, 200, "Medium Caption (18pt)", 18, GREEN)
|
||||
ui.append(caption2)
|
||||
|
||||
caption3 = create_caption(100, 240, "Small Caption (14pt)", 14, RED)
|
||||
ui.append(caption3)
|
||||
|
||||
# Caption with background
|
||||
caption_bg = mcrfpy.Frame(100, 300, 300, 50, fill_color=BOX_COLOR)
|
||||
ui.append(caption_bg)
|
||||
caption4 = create_caption(110, 315, "Caption with Background", 16)
|
||||
ui.append(caption4)
|
||||
|
||||
def create_sprite_example():
|
||||
"""Create a scene showing Sprite UI element examples"""
|
||||
mcrfpy.createScene("sprite_example")
|
||||
ui = mcrfpy.sceneUI("sprite_example")
|
||||
|
||||
# Background frame
|
||||
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
|
||||
ui.append(bg)
|
||||
|
||||
# Title
|
||||
title = create_caption(250, 50, "Sprite Examples", 32)
|
||||
ui.append(title)
|
||||
|
||||
# Create a grid background for sprites
|
||||
sprite_bg = mcrfpy.Frame(100, 150, 600, 300, fill_color=BOX_COLOR)
|
||||
ui.append(sprite_bg)
|
||||
|
||||
# Player sprite (84)
|
||||
player_label = create_caption(150, 180, "Player", 14)
|
||||
ui.append(player_label)
|
||||
player_sprite = mcrfpy.Sprite(150, 200, sprite_texture, 84, 3.0)
|
||||
ui.append(player_sprite)
|
||||
|
||||
# Enemy sprites
|
||||
enemy_label = create_caption(250, 180, "Enemies", 14)
|
||||
ui.append(enemy_label)
|
||||
enemy1 = mcrfpy.Sprite(250, 200, sprite_texture, 123, 3.0) # Basic enemy
|
||||
ui.append(enemy1)
|
||||
enemy2 = mcrfpy.Sprite(300, 200, sprite_texture, 107, 3.0) # Different enemy
|
||||
ui.append(enemy2)
|
||||
|
||||
# Boulder sprite (66)
|
||||
boulder_label = create_caption(400, 180, "Boulder", 14)
|
||||
ui.append(boulder_label)
|
||||
boulder_sprite = mcrfpy.Sprite(400, 200, sprite_texture, 66, 3.0)
|
||||
ui.append(boulder_sprite)
|
||||
|
||||
# Exit sprites
|
||||
exit_label = create_caption(500, 180, "Exit States", 14)
|
||||
ui.append(exit_label)
|
||||
exit_locked = mcrfpy.Sprite(500, 200, sprite_texture, 45, 3.0) # Locked
|
||||
ui.append(exit_locked)
|
||||
exit_open = mcrfpy.Sprite(550, 200, sprite_texture, 21, 3.0) # Open
|
||||
ui.append(exit_open)
|
||||
|
||||
# Item sprites
|
||||
item_label = create_caption(150, 300, "Items", 14)
|
||||
ui.append(item_label)
|
||||
treasure = mcrfpy.Sprite(150, 320, sprite_texture, 89, 3.0) # Treasure
|
||||
ui.append(treasure)
|
||||
sword = mcrfpy.Sprite(200, 320, sprite_texture, 222, 3.0) # Sword
|
||||
ui.append(sword)
|
||||
potion = mcrfpy.Sprite(250, 320, sprite_texture, 175, 3.0) # Potion
|
||||
ui.append(potion)
|
||||
|
||||
# Button sprite
|
||||
button_label = create_caption(350, 300, "Button", 14)
|
||||
ui.append(button_label)
|
||||
button = mcrfpy.Sprite(350, 320, sprite_texture, 250, 3.0)
|
||||
ui.append(button)
|
||||
|
||||
def create_frame_example():
|
||||
"""Create a scene showing Frame UI element examples"""
|
||||
mcrfpy.createScene("frame_example")
|
||||
ui = mcrfpy.sceneUI("frame_example")
|
||||
|
||||
# Background
|
||||
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=SHADOW_COLOR)
|
||||
ui.append(bg)
|
||||
|
||||
# Title
|
||||
title = create_caption(250, 30, "Frame Examples", 32)
|
||||
ui.append(title)
|
||||
|
||||
# Basic frame
|
||||
frame1 = mcrfpy.Frame(50, 100, 200, 150, fill_color=FRAME_COLOR)
|
||||
ui.append(frame1)
|
||||
label1 = create_caption(60, 110, "Basic Frame", 16)
|
||||
ui.append(label1)
|
||||
|
||||
# Frame with outline
|
||||
frame2 = mcrfpy.Frame(300, 100, 200, 150, fill_color=BOX_COLOR,
|
||||
outline_color=WHITE, outline=2.0)
|
||||
ui.append(frame2)
|
||||
label2 = create_caption(310, 110, "Frame with Outline", 16)
|
||||
ui.append(label2)
|
||||
|
||||
# Nested frames
|
||||
frame3 = mcrfpy.Frame(550, 100, 200, 150, fill_color=FRAME_COLOR,
|
||||
outline_color=WHITE, outline=1)
|
||||
ui.append(frame3)
|
||||
inner_frame = mcrfpy.Frame(570, 130, 160, 90, fill_color=BOX_COLOR)
|
||||
ui.append(inner_frame)
|
||||
label3 = create_caption(560, 110, "Nested Frames", 16)
|
||||
ui.append(label3)
|
||||
|
||||
# Complex layout with frames
|
||||
main_frame = mcrfpy.Frame(50, 300, 700, 250, fill_color=FRAME_COLOR,
|
||||
outline_color=WHITE, outline=2)
|
||||
ui.append(main_frame)
|
||||
|
||||
# Add some UI elements inside
|
||||
ui_label = create_caption(60, 310, "Complex UI Layout", 18)
|
||||
ui.append(ui_label)
|
||||
|
||||
# Status panel
|
||||
status_frame = mcrfpy.Frame(70, 350, 150, 180, fill_color=BOX_COLOR)
|
||||
ui.append(status_frame)
|
||||
status_label = create_caption(80, 360, "Status", 14)
|
||||
ui.append(status_label)
|
||||
|
||||
# Inventory panel
|
||||
inv_frame = mcrfpy.Frame(240, 350, 300, 180, fill_color=BOX_COLOR)
|
||||
ui.append(inv_frame)
|
||||
inv_label = create_caption(250, 360, "Inventory", 14)
|
||||
ui.append(inv_label)
|
||||
|
||||
# Actions panel
|
||||
action_frame = mcrfpy.Frame(560, 350, 170, 180, fill_color=BOX_COLOR)
|
||||
ui.append(action_frame)
|
||||
action_label = create_caption(570, 360, "Actions", 14)
|
||||
ui.append(action_label)
|
||||
|
||||
def create_grid_example():
|
||||
"""Create a scene showing Grid UI element examples"""
|
||||
mcrfpy.createScene("grid_example")
|
||||
ui = mcrfpy.sceneUI("grid_example")
|
||||
|
||||
# Background
|
||||
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
|
||||
ui.append(bg)
|
||||
|
||||
# Title
|
||||
title = create_caption(250, 30, "Grid Example", 32)
|
||||
ui.append(title)
|
||||
|
||||
# Create a grid showing a small dungeon
|
||||
grid = mcrfpy.Grid(20, 15, sprite_texture,
|
||||
mcrfpy.Vector(100, 100), mcrfpy.Vector(320, 240))
|
||||
|
||||
# Set up dungeon tiles
|
||||
# Floor tiles (index 48)
|
||||
# Wall tiles (index 3)
|
||||
for x in range(20):
|
||||
for y in range(15):
|
||||
if x == 0 or x == 19 or y == 0 or y == 14:
|
||||
# Walls around edge
|
||||
grid.at((x, y)).tilesprite = 3
|
||||
grid.at((x, y)).walkable = False
|
||||
else:
|
||||
# Floor
|
||||
grid.at((x, y)).tilesprite = 48
|
||||
grid.at((x, y)).walkable = True
|
||||
|
||||
# Add some internal walls
|
||||
for x in range(5, 15):
|
||||
grid.at((x, 7)).tilesprite = 3
|
||||
grid.at((x, 7)).walkable = False
|
||||
for y in range(3, 8):
|
||||
grid.at((10, y)).tilesprite = 3
|
||||
grid.at((10, y)).walkable = False
|
||||
|
||||
# Add a door
|
||||
grid.at((10, 7)).tilesprite = 131 # Door tile
|
||||
grid.at((10, 7)).walkable = True
|
||||
|
||||
# Add to UI
|
||||
ui.append(grid)
|
||||
|
||||
# Label
|
||||
grid_label = create_caption(100, 480, "20x15 Grid with 2x scale - Simple Dungeon Layout", 16)
|
||||
ui.append(grid_label)
|
||||
|
||||
def create_entity_example():
|
||||
"""Create a scene showing Entity examples in a Grid"""
|
||||
mcrfpy.createScene("entity_example")
|
||||
ui = mcrfpy.sceneUI("entity_example")
|
||||
|
||||
# Background
|
||||
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
|
||||
ui.append(bg)
|
||||
|
||||
# Title
|
||||
title = create_caption(200, 30, "Entity Collection Example", 32)
|
||||
ui.append(title)
|
||||
|
||||
# Create a grid for the entities
|
||||
grid = mcrfpy.Grid(15, 10, sprite_texture,
|
||||
mcrfpy.Vector(150, 100), mcrfpy.Vector(360, 240))
|
||||
|
||||
# Set all tiles to floor
|
||||
for x in range(15):
|
||||
for y in range(10):
|
||||
grid.at((x, y)).tilesprite = 48
|
||||
grid.at((x, y)).walkable = True
|
||||
|
||||
# Add walls
|
||||
for x in range(15):
|
||||
grid.at((x, 0)).tilesprite = 3
|
||||
grid.at((x, 0)).walkable = False
|
||||
grid.at((x, 9)).tilesprite = 3
|
||||
grid.at((x, 9)).walkable = False
|
||||
for y in range(10):
|
||||
grid.at((0, y)).tilesprite = 3
|
||||
grid.at((0, y)).walkable = False
|
||||
grid.at((14, y)).tilesprite = 3
|
||||
grid.at((14, y)).walkable = False
|
||||
|
||||
ui.append(grid)
|
||||
|
||||
# Add entities to the grid
|
||||
# Player entity
|
||||
player = mcrfpy.Entity(mcrfpy.Vector(3, 3), sprite_texture, 84, grid)
|
||||
grid.entities.append(player)
|
||||
|
||||
# Enemy entities
|
||||
enemy1 = mcrfpy.Entity(mcrfpy.Vector(7, 4), sprite_texture, 123, grid)
|
||||
grid.entities.append(enemy1)
|
||||
|
||||
enemy2 = mcrfpy.Entity(mcrfpy.Vector(10, 6), sprite_texture, 107, grid)
|
||||
grid.entities.append(enemy2)
|
||||
|
||||
# Boulder
|
||||
boulder = mcrfpy.Entity(mcrfpy.Vector(5, 5), sprite_texture, 66, grid)
|
||||
grid.entities.append(boulder)
|
||||
|
||||
# Treasure
|
||||
treasure = mcrfpy.Entity(mcrfpy.Vector(12, 2), sprite_texture, 89, grid)
|
||||
grid.entities.append(treasure)
|
||||
|
||||
# Exit (locked)
|
||||
exit_door = mcrfpy.Entity(mcrfpy.Vector(12, 8), sprite_texture, 45, grid)
|
||||
grid.entities.append(exit_door)
|
||||
|
||||
# Button
|
||||
button = mcrfpy.Entity(mcrfpy.Vector(3, 7), sprite_texture, 250, grid)
|
||||
grid.entities.append(button)
|
||||
|
||||
# Items
|
||||
sword = mcrfpy.Entity(mcrfpy.Vector(8, 2), sprite_texture, 222, grid)
|
||||
grid.entities.append(sword)
|
||||
|
||||
potion = mcrfpy.Entity(mcrfpy.Vector(6, 8), sprite_texture, 175, grid)
|
||||
grid.entities.append(potion)
|
||||
|
||||
# Label
|
||||
entity_label = create_caption(150, 500, "Grid with Entity Collection - Game Objects", 16)
|
||||
ui.append(entity_label)
|
||||
|
||||
def create_combined_example():
|
||||
"""Create a scene showing all UI elements combined"""
|
||||
mcrfpy.createScene("combined_example")
|
||||
ui = mcrfpy.sceneUI("combined_example")
|
||||
|
||||
# Background
|
||||
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=SHADOW_COLOR)
|
||||
ui.append(bg)
|
||||
|
||||
# Title
|
||||
title = create_caption(200, 20, "McRogueFace UI Elements", 28)
|
||||
ui.append(title)
|
||||
|
||||
# Main game area frame
|
||||
game_frame = mcrfpy.Frame(20, 70, 500, 400, fill_color=FRAME_COLOR,
|
||||
outline_color=WHITE, outline=2)
|
||||
ui.append(game_frame)
|
||||
|
||||
# Grid inside game frame
|
||||
grid = mcrfpy.Grid(12, 10, sprite_texture,
|
||||
mcrfpy.Vector(30, 80), mcrfpy.Vector(480, 400))
|
||||
for x in range(12):
|
||||
for y in range(10):
|
||||
if x == 0 or x == 11 or y == 0 or y == 9:
|
||||
grid.at((x, y)).tilesprite = 3
|
||||
grid.at((x, y)).walkable = False
|
||||
else:
|
||||
grid.at((x, y)).tilesprite = 48
|
||||
grid.at((x, y)).walkable = True
|
||||
|
||||
# Add some entities
|
||||
player = mcrfpy.Entity(mcrfpy.Vector(2, 2), sprite_texture, 84, grid)
|
||||
grid.entities.append(player)
|
||||
enemy = mcrfpy.Entity(mcrfpy.Vector(8, 6), sprite_texture, 123, grid)
|
||||
grid.entities.append(enemy)
|
||||
boulder = mcrfpy.Entity(mcrfpy.Vector(5, 4), sprite_texture, 66, grid)
|
||||
grid.entities.append(boulder)
|
||||
|
||||
ui.append(grid)
|
||||
|
||||
# Status panel
|
||||
status_frame = mcrfpy.Frame(540, 70, 240, 200, fill_color=BOX_COLOR,
|
||||
outline_color=WHITE, outline=1)
|
||||
ui.append(status_frame)
|
||||
|
||||
status_title = create_caption(550, 80, "Status", 20)
|
||||
ui.append(status_title)
|
||||
|
||||
hp_label = create_caption(550, 120, "HP: 10/10", 16, GREEN)
|
||||
ui.append(hp_label)
|
||||
|
||||
level_label = create_caption(550, 150, "Level: 1", 16)
|
||||
ui.append(level_label)
|
||||
|
||||
# Inventory panel
|
||||
inv_frame = mcrfpy.Frame(540, 290, 240, 180, fill_color=BOX_COLOR,
|
||||
outline_color=WHITE, outline=1)
|
||||
ui.append(inv_frame)
|
||||
|
||||
inv_title = create_caption(550, 300, "Inventory", 20)
|
||||
ui.append(inv_title)
|
||||
|
||||
# Add some item sprites
|
||||
item1 = mcrfpy.Sprite(560, 340, sprite_texture, 222, 2.0)
|
||||
ui.append(item1)
|
||||
item2 = mcrfpy.Sprite(610, 340, sprite_texture, 175, 2.0)
|
||||
ui.append(item2)
|
||||
|
||||
# Message log
|
||||
log_frame = mcrfpy.Frame(20, 490, 760, 90, fill_color=BOX_COLOR,
|
||||
outline_color=WHITE, outline=1)
|
||||
ui.append(log_frame)
|
||||
|
||||
log_msg = create_caption(30, 500, "Welcome to McRogueFace!", 14)
|
||||
ui.append(log_msg)
|
||||
|
||||
# Set up all the scenes
|
||||
print("Creating UI example scenes...")
|
||||
create_caption_example()
|
||||
create_sprite_example()
|
||||
create_frame_example()
|
||||
create_grid_example()
|
||||
create_entity_example()
|
||||
create_combined_example()
|
||||
|
||||
# Screenshot state
|
||||
current_screenshot = 0
|
||||
screenshots = [
|
||||
("caption_example", "ui_caption_example.png"),
|
||||
("sprite_example", "ui_sprite_example.png"),
|
||||
("frame_example", "ui_frame_example.png"),
|
||||
("grid_example", "ui_grid_example.png"),
|
||||
("entity_example", "ui_entity_example.png"),
|
||||
("combined_example", "ui_combined_example.png")
|
||||
]
|
||||
|
||||
def take_screenshots(runtime):
|
||||
"""Timer callback to take screenshots sequentially"""
|
||||
global current_screenshot
|
||||
|
||||
if current_screenshot >= len(screenshots):
|
||||
print("\nAll screenshots captured successfully!")
|
||||
print(f"Screenshots saved to: {output_dir}/")
|
||||
mcrfpy.exit()
|
||||
return
|
||||
|
||||
scene_name, filename = screenshots[current_screenshot]
|
||||
|
||||
# Switch to the scene
|
||||
mcrfpy.setScene(scene_name)
|
||||
|
||||
# Take screenshot after a short delay to ensure rendering
|
||||
def capture():
|
||||
global current_screenshot
|
||||
full_path = f"{output_dir}/{filename}"
|
||||
result = automation.screenshot(full_path)
|
||||
print(f"Screenshot {current_screenshot + 1}/{len(screenshots)}: {filename} - {'Success' if result else 'Failed'}")
|
||||
|
||||
current_screenshot += 1
|
||||
|
||||
# Schedule next screenshot
|
||||
mcrfpy.setTimer("next_screenshot", take_screenshots, 200)
|
||||
|
||||
# Give scene time to render
|
||||
mcrfpy.setTimer("capture", lambda r: capture(), 100)
|
||||
|
||||
# Start with the first scene
|
||||
mcrfpy.setScene("caption_example")
|
||||
|
||||
# Start the screenshot process
|
||||
print(f"\nStarting screenshot capture of {len(screenshots)} scenes...")
|
||||
mcrfpy.setTimer("start", take_screenshots, 500)
|
||||
|
||||
# Safety timeout
|
||||
def safety_exit(runtime):
|
||||
print("\nERROR: Safety timeout reached! Exiting...")
|
||||
mcrfpy.exit()
|
||||
|
||||
mcrfpy.setTimer("safety", safety_exit, 30000)
|
||||
|
||||
print("Setup complete. Game loop starting...")
|
||||
131
tests/unit/generate_grid_screenshot.py
Normal file
131
tests/unit/generate_grid_screenshot.py
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate grid documentation screenshot for McRogueFace"""
|
||||
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
|
||||
def capture_grid(runtime):
|
||||
"""Capture grid example after render loop starts"""
|
||||
|
||||
# Take screenshot
|
||||
automation.screenshot("mcrogueface.github.io/images/ui_grid_example.png")
|
||||
print("Grid screenshot saved!")
|
||||
|
||||
# Exit after capturing
|
||||
sys.exit(0)
|
||||
|
||||
# Create scene
|
||||
mcrfpy.createScene("grid")
|
||||
|
||||
# Load texture
|
||||
texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(400, 30, "Grid Example - Dungeon View")
|
||||
title.font = mcrfpy.default_font
|
||||
title.font_size = 24
|
||||
title.font_color = (255, 255, 255)
|
||||
|
||||
# Create main grid (20x15 tiles, each 32x32 pixels)
|
||||
grid = mcrfpy.Grid(100, 100, 20, 15, texture, 32, 32)
|
||||
grid.texture = texture
|
||||
|
||||
# Define tile types from Crypt of Sokoban
|
||||
FLOOR = 58 # Stone floor
|
||||
WALL = 11 # Stone wall
|
||||
DOOR = 28 # Closed door
|
||||
CHEST = 89 # Treasure chest
|
||||
BUTTON = 250 # Floor button
|
||||
EXIT = 45 # Locked exit
|
||||
BOULDER = 66 # Boulder
|
||||
|
||||
# Create a simple dungeon room layout
|
||||
# Fill with walls first
|
||||
for x in range(20):
|
||||
for y in range(15):
|
||||
grid.set_tile(x, y, WALL)
|
||||
|
||||
# Carve out room
|
||||
for x in range(2, 18):
|
||||
for y in range(2, 13):
|
||||
grid.set_tile(x, y, FLOOR)
|
||||
|
||||
# Add door
|
||||
grid.set_tile(10, 2, DOOR)
|
||||
|
||||
# Add some features
|
||||
grid.set_tile(5, 5, CHEST)
|
||||
grid.set_tile(15, 10, BUTTON)
|
||||
grid.set_tile(10, 12, EXIT)
|
||||
grid.set_tile(8, 8, BOULDER)
|
||||
grid.set_tile(12, 8, BOULDER)
|
||||
|
||||
# Create some entities on the grid
|
||||
# Player entity
|
||||
player = mcrfpy.Entity(5, 7)
|
||||
player.texture = texture
|
||||
player.sprite_index = 84 # Player sprite
|
||||
|
||||
# Enemy entities
|
||||
rat1 = mcrfpy.Entity(12, 5)
|
||||
rat1.texture = texture
|
||||
rat1.sprite_index = 123 # Rat
|
||||
|
||||
rat2 = mcrfpy.Entity(14, 9)
|
||||
rat2.texture = texture
|
||||
rat2.sprite_index = 123 # Rat
|
||||
|
||||
cyclops = mcrfpy.Entity(10, 10)
|
||||
cyclops.texture = texture
|
||||
cyclops.sprite_index = 109 # Cyclops
|
||||
|
||||
# Add entities to grid
|
||||
grid.entities.append(player)
|
||||
grid.entities.append(rat1)
|
||||
grid.entities.append(rat2)
|
||||
grid.entities.append(cyclops)
|
||||
|
||||
# Create a smaller grid showing tile palette
|
||||
palette_label = mcrfpy.Caption(100, 600, "Tile Types:")
|
||||
palette_label.font = mcrfpy.default_font
|
||||
palette_label.font_color = (255, 255, 255)
|
||||
|
||||
palette = mcrfpy.Grid(250, 580, 7, 1, texture, 32, 32)
|
||||
palette.texture = texture
|
||||
palette.set_tile(0, 0, FLOOR)
|
||||
palette.set_tile(1, 0, WALL)
|
||||
palette.set_tile(2, 0, DOOR)
|
||||
palette.set_tile(3, 0, CHEST)
|
||||
palette.set_tile(4, 0, BUTTON)
|
||||
palette.set_tile(5, 0, EXIT)
|
||||
palette.set_tile(6, 0, BOULDER)
|
||||
|
||||
# Labels for palette
|
||||
labels = ["Floor", "Wall", "Door", "Chest", "Button", "Exit", "Boulder"]
|
||||
for i, label in enumerate(labels):
|
||||
l = mcrfpy.Caption(250 + i * 32, 615, label)
|
||||
l.font = mcrfpy.default_font
|
||||
l.font_size = 10
|
||||
l.font_color = (255, 255, 255)
|
||||
mcrfpy.sceneUI("grid").append(l)
|
||||
|
||||
# Add info caption
|
||||
info = mcrfpy.Caption(100, 680, "Grid supports tiles and entities. Entities can move independently of the tile grid.")
|
||||
info.font = mcrfpy.default_font
|
||||
info.font_size = 14
|
||||
info.font_color = (200, 200, 200)
|
||||
|
||||
# Add all elements to scene
|
||||
ui = mcrfpy.sceneUI("grid")
|
||||
ui.append(title)
|
||||
ui.append(grid)
|
||||
ui.append(palette_label)
|
||||
ui.append(palette)
|
||||
ui.append(info)
|
||||
|
||||
# Switch to scene
|
||||
mcrfpy.setScene("grid")
|
||||
|
||||
# Set timer to capture after rendering starts
|
||||
mcrfpy.setTimer("capture", capture_grid, 100)
|
||||
160
tests/unit/generate_sprite_screenshot.py
Normal file
160
tests/unit/generate_sprite_screenshot.py
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate sprite documentation screenshots for McRogueFace"""
|
||||
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
|
||||
def capture_sprites(runtime):
|
||||
"""Capture sprite examples after render loop starts"""
|
||||
|
||||
# Take screenshot
|
||||
automation.screenshot("mcrogueface.github.io/images/ui_sprite_example.png")
|
||||
print("Sprite screenshot saved!")
|
||||
|
||||
# Exit after capturing
|
||||
sys.exit(0)
|
||||
|
||||
# Create scene
|
||||
mcrfpy.createScene("sprites")
|
||||
|
||||
# Load texture
|
||||
texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption(400, 30, "Sprite Examples")
|
||||
title.font = mcrfpy.default_font
|
||||
title.font_size = 24
|
||||
title.font_color = (255, 255, 255)
|
||||
|
||||
# Create a frame background
|
||||
frame = mcrfpy.Frame(50, 80, 700, 500)
|
||||
frame.bgcolor = (64, 64, 128)
|
||||
frame.outline = 2
|
||||
|
||||
# Player sprite
|
||||
player_label = mcrfpy.Caption(100, 120, "Player")
|
||||
player_label.font = mcrfpy.default_font
|
||||
player_label.font_color = (255, 255, 255)
|
||||
|
||||
player = mcrfpy.Sprite(120, 150)
|
||||
player.texture = texture
|
||||
player.sprite_index = 84 # Player sprite
|
||||
player.scale = (3.0, 3.0)
|
||||
|
||||
# Enemy sprites
|
||||
enemy_label = mcrfpy.Caption(250, 120, "Enemies")
|
||||
enemy_label.font = mcrfpy.default_font
|
||||
enemy_label.font_color = (255, 255, 255)
|
||||
|
||||
rat = mcrfpy.Sprite(250, 150)
|
||||
rat.texture = texture
|
||||
rat.sprite_index = 123 # Rat
|
||||
rat.scale = (3.0, 3.0)
|
||||
|
||||
big_rat = mcrfpy.Sprite(320, 150)
|
||||
big_rat.texture = texture
|
||||
big_rat.sprite_index = 130 # Big rat
|
||||
big_rat.scale = (3.0, 3.0)
|
||||
|
||||
cyclops = mcrfpy.Sprite(390, 150)
|
||||
cyclops.texture = texture
|
||||
cyclops.sprite_index = 109 # Cyclops
|
||||
cyclops.scale = (3.0, 3.0)
|
||||
|
||||
# Items row
|
||||
items_label = mcrfpy.Caption(100, 250, "Items")
|
||||
items_label.font = mcrfpy.default_font
|
||||
items_label.font_color = (255, 255, 255)
|
||||
|
||||
# Boulder
|
||||
boulder = mcrfpy.Sprite(100, 280)
|
||||
boulder.texture = texture
|
||||
boulder.sprite_index = 66 # Boulder
|
||||
boulder.scale = (3.0, 3.0)
|
||||
|
||||
# Chest
|
||||
chest = mcrfpy.Sprite(170, 280)
|
||||
chest.texture = texture
|
||||
chest.sprite_index = 89 # Closed chest
|
||||
chest.scale = (3.0, 3.0)
|
||||
|
||||
# Key
|
||||
key = mcrfpy.Sprite(240, 280)
|
||||
key.texture = texture
|
||||
key.sprite_index = 384 # Key
|
||||
key.scale = (3.0, 3.0)
|
||||
|
||||
# Button
|
||||
button = mcrfpy.Sprite(310, 280)
|
||||
button.texture = texture
|
||||
button.sprite_index = 250 # Button
|
||||
button.scale = (3.0, 3.0)
|
||||
|
||||
# UI elements row
|
||||
ui_label = mcrfpy.Caption(100, 380, "UI Elements")
|
||||
ui_label.font = mcrfpy.default_font
|
||||
ui_label.font_color = (255, 255, 255)
|
||||
|
||||
# Hearts
|
||||
heart_full = mcrfpy.Sprite(100, 410)
|
||||
heart_full.texture = texture
|
||||
heart_full.sprite_index = 210 # Full heart
|
||||
heart_full.scale = (3.0, 3.0)
|
||||
|
||||
heart_half = mcrfpy.Sprite(170, 410)
|
||||
heart_half.texture = texture
|
||||
heart_half.sprite_index = 209 # Half heart
|
||||
heart_half.scale = (3.0, 3.0)
|
||||
|
||||
heart_empty = mcrfpy.Sprite(240, 410)
|
||||
heart_empty.texture = texture
|
||||
heart_empty.sprite_index = 208 # Empty heart
|
||||
heart_empty.scale = (3.0, 3.0)
|
||||
|
||||
# Armor
|
||||
armor = mcrfpy.Sprite(340, 410)
|
||||
armor.texture = texture
|
||||
armor.sprite_index = 211 # Armor
|
||||
armor.scale = (3.0, 3.0)
|
||||
|
||||
# Scale demonstration
|
||||
scale_label = mcrfpy.Caption(500, 120, "Scale Demo")
|
||||
scale_label.font = mcrfpy.default_font
|
||||
scale_label.font_color = (255, 255, 255)
|
||||
|
||||
# Same sprite at different scales
|
||||
for i, scale in enumerate([1.0, 2.0, 3.0, 4.0]):
|
||||
s = mcrfpy.Sprite(500 + i * 60, 150)
|
||||
s.texture = texture
|
||||
s.sprite_index = 84 # Player
|
||||
s.scale = (scale, scale)
|
||||
mcrfpy.sceneUI("sprites").append(s)
|
||||
|
||||
# Add all elements to scene
|
||||
ui = mcrfpy.sceneUI("sprites")
|
||||
ui.append(frame)
|
||||
ui.append(title)
|
||||
ui.append(player_label)
|
||||
ui.append(player)
|
||||
ui.append(enemy_label)
|
||||
ui.append(rat)
|
||||
ui.append(big_rat)
|
||||
ui.append(cyclops)
|
||||
ui.append(items_label)
|
||||
ui.append(boulder)
|
||||
ui.append(chest)
|
||||
ui.append(key)
|
||||
ui.append(button)
|
||||
ui.append(ui_label)
|
||||
ui.append(heart_full)
|
||||
ui.append(heart_half)
|
||||
ui.append(heart_empty)
|
||||
ui.append(armor)
|
||||
ui.append(scale_label)
|
||||
|
||||
# Switch to scene
|
||||
mcrfpy.setScene("sprites")
|
||||
|
||||
# Set timer to capture after rendering starts
|
||||
mcrfpy.setTimer("capture", capture_sprites, 100)
|
||||
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)
|
||||
93
tests/unit/keypress_scene_validation_test.py
Normal file
93
tests/unit/keypress_scene_validation_test.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test for keypressScene() validation - should reject non-callable arguments
|
||||
"""
|
||||
|
||||
def test_keypress_validation(timer_name):
|
||||
"""Test that keypressScene validates its argument is callable"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
print("Testing keypressScene() validation...")
|
||||
|
||||
# Create test scene
|
||||
mcrfpy.createScene("test")
|
||||
mcrfpy.setScene("test")
|
||||
|
||||
# Test 1: Valid callable (function)
|
||||
def key_handler(key, action):
|
||||
print(f"Key pressed: {key}, action: {action}")
|
||||
|
||||
try:
|
||||
mcrfpy.keypressScene(key_handler)
|
||||
print("✓ Accepted valid function as key handler")
|
||||
except Exception as e:
|
||||
print(f"✗ Rejected valid function: {e}")
|
||||
raise
|
||||
|
||||
# Test 2: Valid callable (lambda)
|
||||
try:
|
||||
mcrfpy.keypressScene(lambda k, a: None)
|
||||
print("✓ Accepted valid lambda as key handler")
|
||||
except Exception as e:
|
||||
print(f"✗ Rejected valid lambda: {e}")
|
||||
raise
|
||||
|
||||
# Test 3: Invalid - string
|
||||
try:
|
||||
mcrfpy.keypressScene("not callable")
|
||||
print("✗ Should have rejected string as key handler")
|
||||
except TypeError as e:
|
||||
print(f"✓ Correctly rejected string: {e}")
|
||||
except Exception as e:
|
||||
print(f"✗ Wrong exception type for string: {e}")
|
||||
raise
|
||||
|
||||
# Test 4: Invalid - number
|
||||
try:
|
||||
mcrfpy.keypressScene(42)
|
||||
print("✗ Should have rejected number as key handler")
|
||||
except TypeError as e:
|
||||
print(f"✓ Correctly rejected number: {e}")
|
||||
except Exception as e:
|
||||
print(f"✗ Wrong exception type for number: {e}")
|
||||
raise
|
||||
|
||||
# Test 5: Invalid - None
|
||||
try:
|
||||
mcrfpy.keypressScene(None)
|
||||
print("✗ Should have rejected None as key handler")
|
||||
except TypeError as e:
|
||||
print(f"✓ Correctly rejected None: {e}")
|
||||
except Exception as e:
|
||||
print(f"✗ Wrong exception type for None: {e}")
|
||||
raise
|
||||
|
||||
# Test 6: Invalid - dict
|
||||
try:
|
||||
mcrfpy.keypressScene({"not": "callable"})
|
||||
print("✗ Should have rejected dict as key handler")
|
||||
except TypeError as e:
|
||||
print(f"✓ Correctly rejected dict: {e}")
|
||||
except Exception as e:
|
||||
print(f"✗ Wrong exception type for dict: {e}")
|
||||
raise
|
||||
|
||||
# Test 7: Valid callable class instance
|
||||
class KeyHandler:
|
||||
def __call__(self, key, action):
|
||||
print(f"Class handler: {key}, {action}")
|
||||
|
||||
try:
|
||||
mcrfpy.keypressScene(KeyHandler())
|
||||
print("✓ Accepted valid callable class instance")
|
||||
except Exception as e:
|
||||
print(f"✗ Rejected valid callable class: {e}")
|
||||
raise
|
||||
|
||||
print("\n✅ keypressScene() validation test PASSED")
|
||||
sys.exit(0)
|
||||
|
||||
# Execute the test after a short delay
|
||||
import mcrfpy
|
||||
mcrfpy.setTimer("test", test_keypress_validation, 100)
|
||||
174
tests/unit/run_issue_tests.py
Executable file
174
tests/unit/run_issue_tests.py
Executable file
|
|
@ -0,0 +1,174 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test runner for high-priority McRogueFace issues
|
||||
|
||||
This script runs comprehensive tests for the highest priority bugs that can be fixed rapidly.
|
||||
Each test is designed to fail initially (demonstrating the bug) and pass after the fix.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
import time
|
||||
|
||||
# Test configurations
|
||||
TESTS = [
|
||||
{
|
||||
"issue": "37",
|
||||
"name": "Windows scripts subdirectory bug",
|
||||
"script": "issue_37_windows_scripts_comprehensive_test.py",
|
||||
"needs_game_loop": False,
|
||||
"description": "Tests script loading from different working directories"
|
||||
},
|
||||
{
|
||||
"issue": "76",
|
||||
"name": "UIEntityCollection returns wrong type",
|
||||
"script": "issue_76_uientitycollection_type_test.py",
|
||||
"needs_game_loop": True,
|
||||
"description": "Tests type preservation for derived Entity classes in collections"
|
||||
},
|
||||
{
|
||||
"issue": "9",
|
||||
"name": "RenderTexture resize bug",
|
||||
"script": "issue_9_rendertexture_resize_test.py",
|
||||
"needs_game_loop": True,
|
||||
"description": "Tests UIGrid rendering with sizes beyond 1920x1080"
|
||||
},
|
||||
{
|
||||
"issue": "26/28",
|
||||
"name": "Iterator implementation for collections",
|
||||
"script": "issue_26_28_iterator_comprehensive_test.py",
|
||||
"needs_game_loop": True,
|
||||
"description": "Tests Python sequence protocol for UI collections"
|
||||
}
|
||||
]
|
||||
|
||||
def run_test(test_config, mcrogueface_path):
|
||||
"""Run a single test and return the result"""
|
||||
script_path = os.path.join(os.path.dirname(__file__), test_config["script"])
|
||||
|
||||
if not os.path.exists(script_path):
|
||||
return f"SKIP - Test script not found: {script_path}"
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Running test for Issue #{test_config['issue']}: {test_config['name']}")
|
||||
print(f"Description: {test_config['description']}")
|
||||
print(f"Script: {test_config['script']}")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
if test_config["needs_game_loop"]:
|
||||
# Run with game loop using --exec
|
||||
cmd = [mcrogueface_path, "--headless", "--exec", script_path]
|
||||
else:
|
||||
# Run directly as Python script
|
||||
cmd = [sys.executable, script_path]
|
||||
|
||||
try:
|
||||
start_time = time.time()
|
||||
result = subprocess.run(
|
||||
cmd,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30 # 30 second timeout
|
||||
)
|
||||
elapsed = time.time() - start_time
|
||||
|
||||
# Check for pass/fail in output
|
||||
output = result.stdout + result.stderr
|
||||
|
||||
if "PASS" in output and "FAIL" not in output:
|
||||
status = "PASS"
|
||||
elif "FAIL" in output:
|
||||
status = "FAIL"
|
||||
else:
|
||||
status = "UNKNOWN"
|
||||
|
||||
# Look for specific bug indicators
|
||||
bug_found = False
|
||||
if test_config["issue"] == "37" and "Script not loaded from different directory" in output:
|
||||
bug_found = True
|
||||
elif test_config["issue"] == "76" and "type lost!" in output:
|
||||
bug_found = True
|
||||
elif test_config["issue"] == "9" and "clipped at 1920x1080" in output:
|
||||
bug_found = True
|
||||
elif test_config["issue"] == "26/28" and "not implemented" in output:
|
||||
bug_found = True
|
||||
|
||||
return {
|
||||
"status": status,
|
||||
"bug_found": bug_found,
|
||||
"elapsed": elapsed,
|
||||
"output": output if len(output) < 1000 else output[:1000] + "\n... (truncated)"
|
||||
}
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
return {
|
||||
"status": "TIMEOUT",
|
||||
"bug_found": False,
|
||||
"elapsed": 30,
|
||||
"output": "Test timed out after 30 seconds"
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"status": "ERROR",
|
||||
"bug_found": False,
|
||||
"elapsed": 0,
|
||||
"output": str(e)
|
||||
}
|
||||
|
||||
def main():
|
||||
"""Run all tests and provide summary"""
|
||||
# Find mcrogueface executable
|
||||
build_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), "build")
|
||||
mcrogueface_path = os.path.join(build_dir, "mcrogueface")
|
||||
|
||||
if not os.path.exists(mcrogueface_path):
|
||||
print(f"ERROR: mcrogueface executable not found at {mcrogueface_path}")
|
||||
print("Please build the project first with 'make'")
|
||||
return 1
|
||||
|
||||
print("McRogueFace Issue Test Suite")
|
||||
print(f"Executable: {mcrogueface_path}")
|
||||
print(f"Running {len(TESTS)} tests...\n")
|
||||
|
||||
results = []
|
||||
|
||||
for test in TESTS:
|
||||
result = run_test(test, mcrogueface_path)
|
||||
results.append((test, result))
|
||||
|
||||
# Summary
|
||||
print(f"\n{'='*60}")
|
||||
print("TEST SUMMARY")
|
||||
print(f"{'='*60}\n")
|
||||
|
||||
bugs_found = 0
|
||||
tests_passed = 0
|
||||
|
||||
for test, result in results:
|
||||
if isinstance(result, str):
|
||||
print(f"Issue #{test['issue']}: {result}")
|
||||
else:
|
||||
status_str = result['status']
|
||||
if result['bug_found']:
|
||||
status_str += " (BUG CONFIRMED)"
|
||||
bugs_found += 1
|
||||
elif result['status'] == 'PASS':
|
||||
tests_passed += 1
|
||||
|
||||
print(f"Issue #{test['issue']}: {status_str} ({result['elapsed']:.2f}s)")
|
||||
|
||||
if result['status'] not in ['PASS', 'UNKNOWN']:
|
||||
print(f" Details: {result['output'].splitlines()[0] if result['output'] else 'No output'}")
|
||||
|
||||
print(f"\nBugs confirmed: {bugs_found}/{len(TESTS)}")
|
||||
print(f"Tests passed: {tests_passed}/{len(TESTS)}")
|
||||
|
||||
if bugs_found > 0:
|
||||
print("\nThese tests demonstrate bugs that need fixing.")
|
||||
print("After fixing, the tests should pass instead of confirming bugs.")
|
||||
|
||||
return 0
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
77
tests/unit/screenshot_transparency_fix_test.py
Normal file
77
tests/unit/screenshot_transparency_fix_test.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test and workaround for transparent screenshot issue"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
from datetime import datetime
|
||||
import sys
|
||||
|
||||
def test_transparency_workaround():
|
||||
"""Create a full-window opaque background to fix transparency"""
|
||||
print("=== Screenshot Transparency Fix Test ===\n")
|
||||
|
||||
# Create a scene
|
||||
mcrfpy.createScene("opaque_test")
|
||||
mcrfpy.setScene("opaque_test")
|
||||
ui = mcrfpy.sceneUI("opaque_test")
|
||||
|
||||
# WORKAROUND: Create a full-window opaque frame as the first element
|
||||
# This acts as an opaque background since the scene clears with transparent
|
||||
print("Creating full-window opaque background...")
|
||||
background = mcrfpy.Frame(0, 0, 1024, 768,
|
||||
fill_color=mcrfpy.Color(50, 50, 50), # Dark gray
|
||||
outline_color=None,
|
||||
outline=0.0)
|
||||
ui.append(background)
|
||||
print("✓ Added opaque background frame")
|
||||
|
||||
# Now add normal content on top
|
||||
print("\nAdding test content...")
|
||||
|
||||
# Red frame
|
||||
frame1 = mcrfpy.Frame(100, 100, 200, 150,
|
||||
fill_color=mcrfpy.Color(255, 0, 0),
|
||||
outline_color=mcrfpy.Color(255, 255, 255),
|
||||
outline=3.0)
|
||||
ui.append(frame1)
|
||||
|
||||
# Green frame
|
||||
frame2 = mcrfpy.Frame(350, 100, 200, 150,
|
||||
fill_color=mcrfpy.Color(0, 255, 0),
|
||||
outline_color=mcrfpy.Color(0, 0, 0),
|
||||
outline=3.0)
|
||||
ui.append(frame2)
|
||||
|
||||
# Blue frame
|
||||
frame3 = mcrfpy.Frame(100, 300, 200, 150,
|
||||
fill_color=mcrfpy.Color(0, 0, 255),
|
||||
outline_color=mcrfpy.Color(255, 255, 0),
|
||||
outline=3.0)
|
||||
ui.append(frame3)
|
||||
|
||||
# Add text
|
||||
caption = mcrfpy.Caption(mcrfpy.Vector(250, 50),
|
||||
text="OPAQUE BACKGROUND TEST",
|
||||
fill_color=mcrfpy.Color(255, 255, 255))
|
||||
caption.size = 32
|
||||
ui.append(caption)
|
||||
|
||||
# Take screenshot
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"screenshot_opaque_fix_{timestamp}.png"
|
||||
result = automation.screenshot(filename)
|
||||
|
||||
print(f"\nScreenshot taken: {filename}")
|
||||
print(f"Result: {result}")
|
||||
|
||||
print("\n=== Analysis ===")
|
||||
print("The issue is that PyScene::render() calls clear() without a color parameter.")
|
||||
print("SFML's default clear color is transparent black (0,0,0,0).")
|
||||
print("In windowed mode, the window provides an opaque background.")
|
||||
print("In headless mode, the RenderTexture preserves the transparency.")
|
||||
print("\nWORKAROUND: Always add a full-window opaque Frame as the first UI element.")
|
||||
print("FIX: Modify PyScene.cpp and UITestScene.cpp to use clear(sf::Color::Black)")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
# Run immediately
|
||||
test_transparency_workaround()
|
||||
45
tests/unit/simple_screenshot_test.py
Normal file
45
tests/unit/simple_screenshot_test.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Simple screenshot test to verify automation API"""
|
||||
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
import time
|
||||
|
||||
def take_screenshot(runtime):
|
||||
"""Take screenshot after render starts"""
|
||||
print(f"Timer callback fired at runtime: {runtime}")
|
||||
|
||||
# Try different paths
|
||||
paths = [
|
||||
"test_screenshot.png",
|
||||
"./test_screenshot.png",
|
||||
"mcrogueface.github.io/images/test_screenshot.png"
|
||||
]
|
||||
|
||||
for path in paths:
|
||||
try:
|
||||
print(f"Trying to save to: {path}")
|
||||
automation.screenshot(path)
|
||||
print(f"Success: {path}")
|
||||
except Exception as e:
|
||||
print(f"Failed {path}: {e}")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
# Create minimal scene
|
||||
mcrfpy.createScene("test")
|
||||
|
||||
# Add a visible element
|
||||
caption = mcrfpy.Caption(100, 100, "Screenshot Test")
|
||||
caption.font = mcrfpy.default_font
|
||||
caption.font_color = (255, 255, 255)
|
||||
caption.font_size = 24
|
||||
|
||||
mcrfpy.sceneUI("test").append(caption)
|
||||
mcrfpy.setScene("test")
|
||||
|
||||
# Use timer to ensure rendering has started
|
||||
print("Setting timer...")
|
||||
mcrfpy.setTimer("screenshot", take_screenshot, 500) # Wait 0.5 seconds
|
||||
print("Timer set, entering game loop...")
|
||||
40
tests/unit/simple_timer_screenshot_test.py
Normal file
40
tests/unit/simple_timer_screenshot_test.py
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Simplified test to verify timer-based screenshots work"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
|
||||
# Counter to track timer calls
|
||||
call_count = 0
|
||||
|
||||
def take_screenshot_and_exit():
|
||||
"""Timer callback that takes screenshot then exits"""
|
||||
global call_count
|
||||
call_count += 1
|
||||
|
||||
print(f"\nTimer callback fired! (call #{call_count})")
|
||||
|
||||
# Take screenshot
|
||||
filename = f"timer_screenshot_test_{call_count}.png"
|
||||
result = automation.screenshot(filename)
|
||||
print(f"Screenshot result: {result} -> {filename}")
|
||||
|
||||
# Exit after first call
|
||||
if call_count >= 1:
|
||||
print("Exiting game...")
|
||||
mcrfpy.exit()
|
||||
|
||||
# Set up a simple scene
|
||||
print("Creating test scene...")
|
||||
mcrfpy.createScene("test")
|
||||
mcrfpy.setScene("test")
|
||||
ui = mcrfpy.sceneUI("test")
|
||||
|
||||
# Add visible content - a white frame on default background
|
||||
frame = mcrfpy.Frame(100, 100, 200, 200,
|
||||
fill_color=mcrfpy.Color(255, 255, 255))
|
||||
ui.append(frame)
|
||||
|
||||
print("Setting timer to fire in 100ms...")
|
||||
mcrfpy.setTimer("screenshot_timer", take_screenshot_and_exit, 100)
|
||||
|
||||
print("Setup complete. Game loop starting...")
|
||||
46
tests/unit/trace_exec_behavior.py
Normal file
46
tests/unit/trace_exec_behavior.py
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Trace execution behavior to understand the >>> prompt"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
import traceback
|
||||
|
||||
print("=== Tracing Execution ===")
|
||||
print(f"Python version: {sys.version}")
|
||||
print(f"sys.argv: {sys.argv}")
|
||||
print(f"__name__: {__name__}")
|
||||
|
||||
# Check if we're in interactive mode
|
||||
print(f"sys.flags.interactive: {sys.flags.interactive}")
|
||||
print(f"sys.flags.inspect: {sys.flags.inspect}")
|
||||
|
||||
# Check sys.ps1 (interactive prompt)
|
||||
if hasattr(sys, 'ps1'):
|
||||
print(f"sys.ps1 exists: '{sys.ps1}'")
|
||||
else:
|
||||
print("sys.ps1 not set (not in interactive mode)")
|
||||
|
||||
# Create a simple scene
|
||||
mcrfpy.createScene("trace_test")
|
||||
mcrfpy.setScene("trace_test")
|
||||
print(f"Current scene: {mcrfpy.currentScene()}")
|
||||
|
||||
# Set a timer that should fire
|
||||
def timer_test():
|
||||
print("\n!!! Timer fired successfully !!!")
|
||||
mcrfpy.delTimer("trace_timer")
|
||||
# Try to exit
|
||||
print("Attempting to exit...")
|
||||
mcrfpy.exit()
|
||||
|
||||
print("Setting timer...")
|
||||
mcrfpy.setTimer("trace_timer", timer_test, 500)
|
||||
|
||||
print("\n=== Script execution complete ===")
|
||||
print("If you see >>> after this, Python entered interactive mode")
|
||||
print("The game loop should start now...")
|
||||
|
||||
# Try to ensure we don't enter interactive mode
|
||||
if hasattr(sys, 'ps1'):
|
||||
del sys.ps1
|
||||
|
||||
# Explicitly NOT calling sys.exit() to let the game loop run
|
||||
116
tests/unit/ui_Entity_issue73_test.py
Normal file
116
tests/unit/ui_Entity_issue73_test.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test for Entity class - Related to issue #73 (index() method)"""
|
||||
import mcrfpy
|
||||
from datetime import datetime
|
||||
|
||||
print("Test script starting...")
|
||||
|
||||
def test_Entity():
|
||||
"""Test Entity class and index() method for collection removal"""
|
||||
# Create test scene with grid
|
||||
mcrfpy.createScene("entity_test")
|
||||
mcrfpy.setScene("entity_test")
|
||||
ui = mcrfpy.sceneUI("entity_test")
|
||||
|
||||
# Create a grid
|
||||
grid = mcrfpy.Grid(10, 10,
|
||||
mcrfpy.default_texture,
|
||||
mcrfpy.Vector(10, 10),
|
||||
mcrfpy.Vector(400, 400))
|
||||
ui.append(grid)
|
||||
entities = grid.entities
|
||||
|
||||
# Create multiple entities
|
||||
entity1 = mcrfpy.Entity(mcrfpy.Vector(2, 2), mcrfpy.default_texture, 0, grid)
|
||||
entity2 = mcrfpy.Entity(mcrfpy.Vector(5, 5), mcrfpy.default_texture, 1, grid)
|
||||
entity3 = mcrfpy.Entity(mcrfpy.Vector(7, 7), mcrfpy.default_texture, 2, grid)
|
||||
|
||||
entities.append(entity1)
|
||||
entities.append(entity2)
|
||||
entities.append(entity3)
|
||||
|
||||
print(f"Created {len(entities)} entities")
|
||||
|
||||
# Test entity properties
|
||||
try:
|
||||
print(f" Entity1 pos: {entity1.pos}")
|
||||
print(f" Entity1 draw_pos: {entity1.draw_pos}")
|
||||
print(f" Entity1 sprite_number: {entity1.sprite_number}")
|
||||
|
||||
# Modify properties
|
||||
entity1.pos = mcrfpy.Vector(3, 3)
|
||||
entity1.sprite_number = 5
|
||||
print(" Entity properties modified")
|
||||
except Exception as e:
|
||||
print(f"X Entity property access failed: {e}")
|
||||
|
||||
# Test gridstate access
|
||||
try:
|
||||
gridstate = entity2.gridstate
|
||||
print(" Entity gridstate accessible")
|
||||
|
||||
# Test at() method
|
||||
point_state = entity2.at()#.at(0, 0)
|
||||
print(" Entity at() method works")
|
||||
except Exception as e:
|
||||
print(f"X Entity gridstate/at() failed: {e}")
|
||||
|
||||
# Test index() method (Issue #73)
|
||||
print("\nTesting index() method (Issue #73)...")
|
||||
try:
|
||||
# Try to find entity2's index
|
||||
index = entity2.index()
|
||||
print(f":) index() method works: entity2 is at index {index}")
|
||||
|
||||
# Verify by checking collection
|
||||
if entities[index] == entity2:
|
||||
print("✓ Index is correct")
|
||||
else:
|
||||
print("✗ Index mismatch")
|
||||
|
||||
# Remove using index
|
||||
entities.remove(index)
|
||||
print(f":) Removed entity using index, now {len(entities)} entities")
|
||||
except AttributeError:
|
||||
print("✗ index() method not implemented (Issue #73)")
|
||||
# Try manual removal as workaround
|
||||
try:
|
||||
for i in range(len(entities)):
|
||||
if entities[i] == entity2:
|
||||
entities.remove(i)
|
||||
print(":) Manual removal workaround succeeded")
|
||||
break
|
||||
except:
|
||||
print("✗ Manual removal also failed")
|
||||
except Exception as e:
|
||||
print(f":) index() method error: {e}")
|
||||
|
||||
# Test EntityCollection iteration
|
||||
try:
|
||||
positions = []
|
||||
for entity in entities:
|
||||
positions.append(entity.pos)
|
||||
print(f":) Entity iteration works: {len(positions)} entities")
|
||||
except Exception as e:
|
||||
print(f"X Entity iteration failed: {e}")
|
||||
|
||||
# Test EntityCollection extend (Issue #27)
|
||||
try:
|
||||
new_entities = [
|
||||
mcrfpy.Entity(mcrfpy.Vector(1, 1), mcrfpy.default_texture, 3, grid),
|
||||
mcrfpy.Entity(mcrfpy.Vector(9, 9), mcrfpy.default_texture, 4, grid)
|
||||
]
|
||||
entities.extend(new_entities)
|
||||
print(f":) extend() method works: now {len(entities)} entities")
|
||||
except AttributeError:
|
||||
print("✗ extend() method not implemented (Issue #27)")
|
||||
except Exception as e:
|
||||
print(f"X extend() method error: {e}")
|
||||
|
||||
# Skip screenshot in headless mode
|
||||
print("PASS")
|
||||
|
||||
# Run test immediately in headless mode
|
||||
print("Running test immediately...")
|
||||
test_Entity()
|
||||
print("Test completed.")
|
||||
112
tests/unit/ui_Frame_test.py
Normal file
112
tests/unit/ui_Frame_test.py
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test for mcrfpy.Frame class - Related to issues #38, #42"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
click_count = 0
|
||||
|
||||
def click_handler(x, y, button):
|
||||
"""Handle frame clicks"""
|
||||
global click_count
|
||||
click_count += 1
|
||||
print(f"Frame clicked at ({x}, {y}) with button {button}")
|
||||
|
||||
def test_Frame():
|
||||
"""Test Frame creation and properties"""
|
||||
print("Starting Frame test...")
|
||||
|
||||
# Create test scene
|
||||
mcrfpy.createScene("frame_test")
|
||||
mcrfpy.setScene("frame_test")
|
||||
ui = mcrfpy.sceneUI("frame_test")
|
||||
|
||||
# Test basic frame creation
|
||||
try:
|
||||
frame1 = mcrfpy.Frame(10, 10, 200, 150)
|
||||
ui.append(frame1)
|
||||
print("✓ Basic Frame created")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to create basic Frame: {e}")
|
||||
print("FAIL")
|
||||
return
|
||||
|
||||
# Test frame with all parameters
|
||||
try:
|
||||
frame2 = mcrfpy.Frame(220, 10, 200, 150,
|
||||
fill_color=mcrfpy.Color(100, 150, 200),
|
||||
outline_color=mcrfpy.Color(255, 0, 0),
|
||||
outline=3.0)
|
||||
ui.append(frame2)
|
||||
print("✓ Frame with colors created")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to create colored Frame: {e}")
|
||||
|
||||
# Test property access and modification
|
||||
try:
|
||||
# Test getters
|
||||
print(f"Frame1 position: ({frame1.x}, {frame1.y})")
|
||||
print(f"Frame1 size: {frame1.w}x{frame1.h}")
|
||||
|
||||
# Test setters
|
||||
frame1.x = 15
|
||||
frame1.y = 15
|
||||
frame1.w = 190
|
||||
frame1.h = 140
|
||||
frame1.outline = 2.0
|
||||
frame1.fill_color = mcrfpy.Color(50, 50, 50)
|
||||
frame1.outline_color = mcrfpy.Color(255, 255, 0)
|
||||
print("✓ Frame properties modified")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to modify Frame properties: {e}")
|
||||
|
||||
# Test children collection (Issue #38)
|
||||
try:
|
||||
children = frame2.children
|
||||
caption = mcrfpy.Caption(mcrfpy.Vector(10, 10), text="Child Caption")
|
||||
children.append(caption)
|
||||
print(f"✓ Children collection works, has {len(children)} items")
|
||||
except Exception as e:
|
||||
print(f"✗ Children collection failed (Issue #38): {e}")
|
||||
|
||||
# Test click handler (Issue #42)
|
||||
try:
|
||||
frame2.click = click_handler
|
||||
print("✓ Click handler assigned")
|
||||
|
||||
# Note: Click simulation would require automation module
|
||||
# which may not work in headless mode
|
||||
except Exception as e:
|
||||
print(f"✗ Click handler failed (Issue #42): {e}")
|
||||
|
||||
# Create nested frames to test children rendering
|
||||
try:
|
||||
frame3 = mcrfpy.Frame(10, 200, 400, 200,
|
||||
fill_color=mcrfpy.Color(0, 100, 0),
|
||||
outline_color=mcrfpy.Color(255, 255, 255),
|
||||
outline=2.0)
|
||||
ui.append(frame3)
|
||||
|
||||
# Add children to frame3
|
||||
for i in range(3):
|
||||
child_frame = mcrfpy.Frame(10 + i * 130, 10, 120, 80,
|
||||
fill_color=mcrfpy.Color(100 + i * 50, 50, 50))
|
||||
frame3.children.append(child_frame)
|
||||
|
||||
print(f"✓ Created nested frames with {len(frame3.children)} children")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to create nested frames: {e}")
|
||||
|
||||
# Summary
|
||||
print("\nTest Summary:")
|
||||
print("- Basic Frame creation: PASS")
|
||||
print("- Frame with colors: PASS")
|
||||
print("- Property modification: PASS")
|
||||
print("- Children collection (Issue #38): PASS" if len(frame2.children) >= 0 else "FAIL")
|
||||
print("- Click handler assignment (Issue #42): PASS")
|
||||
print("\nOverall: PASS")
|
||||
|
||||
# Exit cleanly
|
||||
sys.exit(0)
|
||||
|
||||
# Run test immediately
|
||||
test_Frame()
|
||||
127
tests/unit/ui_Frame_test_detailed.py
Normal file
127
tests/unit/ui_Frame_test_detailed.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Detailed test for mcrfpy.Frame class - Issues #38 and #42"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_issue_38_children():
|
||||
"""Test Issue #38: PyUIFrameObject lacks 'children' arg in constructor"""
|
||||
print("\n=== Testing Issue #38: children argument in Frame constructor ===")
|
||||
|
||||
# Create test scene
|
||||
mcrfpy.createScene("issue38_test")
|
||||
mcrfpy.setScene("issue38_test")
|
||||
ui = mcrfpy.sceneUI("issue38_test")
|
||||
|
||||
# Test 1: Try to pass children in constructor
|
||||
print("\nTest 1: Passing children argument to Frame constructor")
|
||||
try:
|
||||
# Create some child elements
|
||||
child1 = mcrfpy.Caption(mcrfpy.Vector(10, 10), text="Child 1")
|
||||
child2 = mcrfpy.Sprite(mcrfpy.Vector(10, 30))
|
||||
|
||||
# Try to create frame with children argument
|
||||
frame = mcrfpy.Frame(10, 10, 200, 150, children=[child1, child2])
|
||||
print("✗ UNEXPECTED: Frame accepted children argument (should fail per issue #38)")
|
||||
except TypeError as e:
|
||||
print(f"✓ EXPECTED: Frame constructor rejected children argument: {e}")
|
||||
except Exception as e:
|
||||
print(f"✗ UNEXPECTED ERROR: {type(e).__name__}: {e}")
|
||||
|
||||
# Test 2: Verify children can be added after creation
|
||||
print("\nTest 2: Adding children after Frame creation")
|
||||
try:
|
||||
frame = mcrfpy.Frame(10, 10, 200, 150)
|
||||
ui.append(frame)
|
||||
|
||||
# Add children via the children collection
|
||||
child1 = mcrfpy.Caption(mcrfpy.Vector(10, 10), text="Added Child 1")
|
||||
child2 = mcrfpy.Caption(mcrfpy.Vector(10, 30), text="Added Child 2")
|
||||
|
||||
frame.children.append(child1)
|
||||
frame.children.append(child2)
|
||||
|
||||
print(f"✓ Successfully added {len(frame.children)} children via children collection")
|
||||
|
||||
# Verify children are accessible
|
||||
for i, child in enumerate(frame.children):
|
||||
print(f" - Child {i}: {type(child).__name__}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to add children: {type(e).__name__}: {e}")
|
||||
|
||||
def test_issue_42_click_callback():
|
||||
"""Test Issue #42: click callback requires x, y, button arguments"""
|
||||
print("\n\n=== Testing Issue #42: click callback arguments ===")
|
||||
|
||||
# Create test scene
|
||||
mcrfpy.createScene("issue42_test")
|
||||
mcrfpy.setScene("issue42_test")
|
||||
ui = mcrfpy.sceneUI("issue42_test")
|
||||
|
||||
# Test 1: Callback with correct signature
|
||||
print("\nTest 1: Click callback with correct signature (x, y, button)")
|
||||
def correct_callback(x, y, button):
|
||||
print(f" Correct callback called: x={x}, y={y}, button={button}")
|
||||
return True
|
||||
|
||||
try:
|
||||
frame1 = mcrfpy.Frame(10, 10, 200, 150)
|
||||
ui.append(frame1)
|
||||
frame1.click = correct_callback
|
||||
print("✓ Click callback with correct signature assigned successfully")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to assign correct callback: {type(e).__name__}: {e}")
|
||||
|
||||
# Test 2: Callback with wrong signature (no args)
|
||||
print("\nTest 2: Click callback with no arguments")
|
||||
def wrong_callback_no_args():
|
||||
print(" Wrong callback called")
|
||||
|
||||
try:
|
||||
frame2 = mcrfpy.Frame(220, 10, 200, 150)
|
||||
ui.append(frame2)
|
||||
frame2.click = wrong_callback_no_args
|
||||
print("✓ Click callback with no args assigned (will fail at runtime per issue #42)")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to assign callback: {type(e).__name__}: {e}")
|
||||
|
||||
# Test 3: Callback with wrong signature (too few args)
|
||||
print("\nTest 3: Click callback with too few arguments")
|
||||
def wrong_callback_few_args(x, y):
|
||||
print(f" Wrong callback called: x={x}, y={y}")
|
||||
|
||||
try:
|
||||
frame3 = mcrfpy.Frame(10, 170, 200, 150)
|
||||
ui.append(frame3)
|
||||
frame3.click = wrong_callback_few_args
|
||||
print("✓ Click callback with 2 args assigned (will fail at runtime per issue #42)")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to assign callback: {type(e).__name__}: {e}")
|
||||
|
||||
# Test 4: Verify callback property getter
|
||||
print("\nTest 4: Verify click callback getter")
|
||||
try:
|
||||
if hasattr(frame1, 'click'):
|
||||
callback = frame1.click
|
||||
print(f"✓ Click callback getter works, returned: {callback}")
|
||||
else:
|
||||
print("✗ Frame object has no 'click' attribute")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to get click callback: {type(e).__name__}: {e}")
|
||||
|
||||
def main():
|
||||
"""Run all tests"""
|
||||
print("Testing mcrfpy.Frame - Issues #38 and #42")
|
||||
|
||||
test_issue_38_children()
|
||||
test_issue_42_click_callback()
|
||||
|
||||
print("\n\n=== TEST SUMMARY ===")
|
||||
print("Issue #38 (children constructor arg): Constructor correctly rejects children argument")
|
||||
print("Issue #42 (click callback args): Click callbacks can be assigned (runtime behavior not tested in headless mode)")
|
||||
print("\nAll tests completed successfully!")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
97
tests/unit/ui_Grid_none_texture_test.py
Normal file
97
tests/unit/ui_Grid_none_texture_test.py
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test Grid creation with None texture - should work with color cells only"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
|
||||
def test_grid_none_texture(runtime):
|
||||
"""Test Grid functionality without texture"""
|
||||
print("\n=== Testing Grid with None texture ===")
|
||||
|
||||
# Test 1: Create Grid with None texture
|
||||
try:
|
||||
grid = mcrfpy.Grid(10, 10, None, mcrfpy.Vector(50, 50), mcrfpy.Vector(400, 400))
|
||||
print("✓ Grid created successfully with None texture")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to create Grid with None texture: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Add to UI
|
||||
ui = mcrfpy.sceneUI("grid_none_test")
|
||||
ui.append(grid)
|
||||
|
||||
# Test 2: Verify grid properties
|
||||
try:
|
||||
grid_size = grid.grid_size
|
||||
print(f"✓ Grid size: {grid_size}")
|
||||
|
||||
# Check texture property
|
||||
texture = grid.texture
|
||||
if texture is None:
|
||||
print("✓ Grid texture is None as expected")
|
||||
else:
|
||||
print(f"✗ Grid texture should be None, got: {texture}")
|
||||
except Exception as e:
|
||||
print(f"✗ Property access failed: {e}")
|
||||
|
||||
# Test 3: Access grid points and set colors
|
||||
try:
|
||||
# Create a checkerboard pattern with colors
|
||||
for x in range(10):
|
||||
for y in range(10):
|
||||
point = grid.at(x, y)
|
||||
if (x + y) % 2 == 0:
|
||||
point.color = mcrfpy.Color(255, 0, 0, 255) # Red
|
||||
else:
|
||||
point.color = mcrfpy.Color(0, 0, 255, 255) # Blue
|
||||
print("✓ Successfully set grid point colors")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to set grid colors: {e}")
|
||||
|
||||
# Test 4: Add entities to the grid
|
||||
try:
|
||||
# Create an entity with its own texture
|
||||
entity_texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||
entity = mcrfpy.Entity(mcrfpy.Vector(5, 5), entity_texture, 1, grid)
|
||||
grid.entities.append(entity)
|
||||
print(f"✓ Added entity to grid, total entities: {len(grid.entities)}")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to add entity: {e}")
|
||||
|
||||
# Test 5: Test grid interaction properties
|
||||
try:
|
||||
# Test zoom
|
||||
grid.zoom = 2.0
|
||||
print(f"✓ Set zoom to: {grid.zoom}")
|
||||
|
||||
# Test center
|
||||
grid.center = mcrfpy.Vector(5, 5)
|
||||
print(f"✓ Set center to: {grid.center}")
|
||||
except Exception as e:
|
||||
print(f"✗ Grid properties failed: {e}")
|
||||
|
||||
# Take screenshot
|
||||
filename = f"grid_none_texture_test_{int(runtime)}.png"
|
||||
result = automation.screenshot(filename)
|
||||
print(f"\nScreenshot saved: {filename} - Result: {result}")
|
||||
print("The grid should show a red/blue checkerboard pattern")
|
||||
|
||||
print("\n✓ PASS - Grid works correctly without texture!")
|
||||
sys.exit(0)
|
||||
|
||||
# Set up test scene
|
||||
print("Creating test scene...")
|
||||
mcrfpy.createScene("grid_none_test")
|
||||
mcrfpy.setScene("grid_none_test")
|
||||
|
||||
# Add a background frame so we can see the grid
|
||||
ui = mcrfpy.sceneUI("grid_none_test")
|
||||
background = mcrfpy.Frame(0, 0, 800, 600,
|
||||
fill_color=mcrfpy.Color(200, 200, 200),
|
||||
outline_color=mcrfpy.Color(0, 0, 0),
|
||||
outline=2.0)
|
||||
ui.append(background)
|
||||
|
||||
# Schedule test
|
||||
mcrfpy.setTimer("test", test_grid_none_texture, 100)
|
||||
print("Test scheduled...")
|
||||
35
tests/unit/ui_Grid_null_texture_test.py
Normal file
35
tests/unit/ui_Grid_null_texture_test.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test Grid creation with null/None texture to reproduce segfault"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_grid_null_texture():
|
||||
"""Test if Grid can be created without a texture"""
|
||||
print("=== Testing Grid with null texture ===")
|
||||
|
||||
# Create test scene
|
||||
mcrfpy.createScene("grid_null_test")
|
||||
mcrfpy.setScene("grid_null_test")
|
||||
ui = mcrfpy.sceneUI("grid_null_test")
|
||||
|
||||
# Test 1: Try with None
|
||||
try:
|
||||
print("Test 1: Creating Grid with None texture...")
|
||||
grid = mcrfpy.Grid(10, 10, None, mcrfpy.Vector(0, 0), mcrfpy.Vector(400, 400))
|
||||
print("✗ Should have raised exception for None texture")
|
||||
except Exception as e:
|
||||
print(f"✓ Correctly rejected None texture: {e}")
|
||||
|
||||
# Test 2: Try without texture parameter (if possible)
|
||||
try:
|
||||
print("\nTest 2: Creating Grid with missing parameters...")
|
||||
grid = mcrfpy.Grid(10, 10)
|
||||
print("✗ Should have raised exception for missing parameters")
|
||||
except Exception as e:
|
||||
print(f"✓ Correctly rejected missing parameters: {e}")
|
||||
|
||||
print("\nTest complete - Grid requires texture parameter")
|
||||
sys.exit(0)
|
||||
|
||||
# Run immediately
|
||||
test_grid_null_texture()
|
||||
142
tests/unit/ui_Grid_test.py
Normal file
142
tests/unit/ui_Grid_test.py
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Test for mcrfpy.Grid class - Related to issues #77, #74, #50, #52, #20"""
|
||||
import mcrfpy
|
||||
from datetime import datetime
|
||||
try:
|
||||
from mcrfpy import automation
|
||||
has_automation = True
|
||||
except ImportError:
|
||||
has_automation = False
|
||||
print("Warning: automation module not available")
|
||||
|
||||
def test_Grid():
|
||||
"""Test Grid creation and properties"""
|
||||
# Create test scene
|
||||
mcrfpy.createScene("grid_test")
|
||||
mcrfpy.setScene("grid_test")
|
||||
ui = mcrfpy.sceneUI("grid_test")
|
||||
|
||||
# Test grid creation
|
||||
try:
|
||||
# Note: Grid requires texture, creating one for testing
|
||||
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
|
||||
grid = mcrfpy.Grid(20, 15, # grid dimensions
|
||||
texture, # texture
|
||||
mcrfpy.Vector(10, 10), # position
|
||||
mcrfpy.Vector(400, 300)) # size
|
||||
ui.append(grid)
|
||||
print("[PASS] Grid created successfully")
|
||||
except Exception as e:
|
||||
print(f"[FAIL] Failed to create Grid: {e}")
|
||||
print("FAIL")
|
||||
return
|
||||
|
||||
# Test grid properties
|
||||
try:
|
||||
# Test grid_size (Issue #20)
|
||||
grid_size = grid.grid_size
|
||||
print(f"[PASS] Grid size: {grid_size}")
|
||||
|
||||
# Test position and size
|
||||
print(f"Position: {grid.position}")
|
||||
print(f"Size: {grid.size}")
|
||||
|
||||
# Test individual coordinate properties
|
||||
print(f"Coordinates: x={grid.x}, y={grid.y}, w={grid.w}, h={grid.h}")
|
||||
|
||||
# Test grid_y property (Issue #74)
|
||||
try:
|
||||
# This might fail if grid_y is not implemented
|
||||
print(f"Grid dimensions via properties: grid_x=?, grid_y=?")
|
||||
print("[FAIL] Issue #74: Grid.grid_y property may be missing")
|
||||
except:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
print(f"[FAIL] Property access failed: {e}")
|
||||
|
||||
# Test center/pan functionality
|
||||
try:
|
||||
grid.center = mcrfpy.Vector(10, 7)
|
||||
print(f"[PASS] Center set to: {grid.center}")
|
||||
grid.center_x = 5
|
||||
grid.center_y = 5
|
||||
print(f"[PASS] Center modified to: ({grid.center_x}, {grid.center_y})")
|
||||
except Exception as e:
|
||||
print(f"[FAIL] Center/pan failed: {e}")
|
||||
|
||||
# Test zoom
|
||||
try:
|
||||
grid.zoom = 1.5
|
||||
print(f"[PASS] Zoom set to: {grid.zoom}")
|
||||
except Exception as e:
|
||||
print(f"[FAIL] Zoom failed: {e}")
|
||||
|
||||
# Test at() method for GridPoint access (Issue #77)
|
||||
try:
|
||||
# This tests the error message issue
|
||||
point = grid.at(0, 0)
|
||||
print("[PASS] GridPoint access works")
|
||||
|
||||
# Try out of bounds access to test error message
|
||||
try:
|
||||
invalid_point = grid.at(100, 100)
|
||||
print("[FAIL] Out of bounds access should fail")
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if "Grid.grid_y" in error_msg:
|
||||
print(f"[FAIL] Issue #77: Error message has copy/paste bug: {error_msg}")
|
||||
else:
|
||||
print(f"[PASS] Out of bounds error: {error_msg}")
|
||||
except Exception as e:
|
||||
print(f"[FAIL] GridPoint access failed: {e}")
|
||||
|
||||
# Test entities collection
|
||||
try:
|
||||
entities = grid.entities
|
||||
print(f"[PASS] Entities collection has {len(entities)} items")
|
||||
|
||||
# Add an entity
|
||||
entity = mcrfpy.Entity(mcrfpy.Vector(5, 5),
|
||||
texture,
|
||||
0, # sprite index
|
||||
grid)
|
||||
entities.append(entity)
|
||||
print(f"[PASS] Entity added, collection now has {len(entities)} items")
|
||||
|
||||
# Test out-of-bounds entity (Issue #52)
|
||||
out_entity = mcrfpy.Entity(mcrfpy.Vector(50, 50), # Outside 20x15 grid
|
||||
texture,
|
||||
1,
|
||||
grid)
|
||||
entities.append(out_entity)
|
||||
print("[PASS] Out-of-bounds entity added (Issue #52: should be skipped in rendering)")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[FAIL] Entity management failed: {e}")
|
||||
|
||||
# Note about missing features
|
||||
print("\nMissing features:")
|
||||
print("- Issue #50: UIGrid background color field")
|
||||
print("- Issue #6, #8, #9: RenderTexture support")
|
||||
|
||||
# Take screenshot if automation is available
|
||||
if has_automation:
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"test_Grid_{timestamp}.png"
|
||||
automation.screenshot(filename)
|
||||
print(f"Screenshot saved: {filename}")
|
||||
else:
|
||||
print("Screenshot skipped - automation not available")
|
||||
print("PASS")
|
||||
|
||||
# Set up timer to run test
|
||||
mcrfpy.setTimer("test", test_Grid, 1000)
|
||||
|
||||
# Cancel timer after running once
|
||||
def cleanup():
|
||||
mcrfpy.delTimer("test")
|
||||
mcrfpy.delTimer("cleanup")
|
||||
|
||||
mcrfpy.setTimer("cleanup", cleanup, 1100)
|
||||
28
tests/unit/ui_Grid_test_no_grid.py
Normal file
28
tests/unit/ui_Grid_test_no_grid.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test setup without Grid creation"""
|
||||
import mcrfpy
|
||||
|
||||
print("Starting test...")
|
||||
|
||||
# Create test scene
|
||||
print("[DEBUG] Creating scene...")
|
||||
mcrfpy.createScene("grid_test")
|
||||
print("[DEBUG] Setting scene...")
|
||||
mcrfpy.setScene("grid_test")
|
||||
print("[DEBUG] Getting UI...")
|
||||
ui = mcrfpy.sceneUI("grid_test")
|
||||
print("[DEBUG] UI retrieved")
|
||||
|
||||
# Test texture creation
|
||||
print("[DEBUG] Creating texture...")
|
||||
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
|
||||
print("[DEBUG] Texture created")
|
||||
|
||||
# Test vector creation
|
||||
print("[DEBUG] Creating vectors...")
|
||||
pos = mcrfpy.Vector(10, 10)
|
||||
size = mcrfpy.Vector(400, 300)
|
||||
print("[DEBUG] Vectors created")
|
||||
|
||||
print("All setup complete, Grid creation would happen here")
|
||||
print("PASS")
|
||||
69
tests/unit/ui_Sprite_issue19_test.py
Normal file
69
tests/unit/ui_Sprite_issue19_test.py
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test for Sprite texture methods - Related to issue #19"""
|
||||
import mcrfpy
|
||||
|
||||
print("Testing Sprite texture methods (Issue #19)...")
|
||||
|
||||
# Create test scene
|
||||
mcrfpy.createScene("sprite_texture_test")
|
||||
mcrfpy.setScene("sprite_texture_test")
|
||||
ui = mcrfpy.sceneUI("sprite_texture_test")
|
||||
|
||||
# Create sprites
|
||||
# Based on sprite2 syntax: Sprite(x, y, texture, sprite_index, scale)
|
||||
sprite1 = mcrfpy.Sprite(10, 10, mcrfpy.default_texture, 0, 2.0)
|
||||
sprite2 = mcrfpy.Sprite(100, 10, mcrfpy.default_texture, 5, 2.0)
|
||||
|
||||
ui.append(sprite1)
|
||||
ui.append(sprite2)
|
||||
|
||||
# Test getting texture
|
||||
try:
|
||||
texture1 = sprite1.texture
|
||||
texture2 = sprite2.texture
|
||||
print(f"✓ Got textures: {texture1}, {texture2}")
|
||||
|
||||
if texture2 == mcrfpy.default_texture:
|
||||
print("✓ Texture matches default_texture")
|
||||
except Exception as e:
|
||||
print(f"✗ Failed to get texture: {e}")
|
||||
|
||||
# Test setting texture (Issue #19 - get/set texture methods)
|
||||
try:
|
||||
# This should fail as texture is read-only currently
|
||||
sprite1.texture = mcrfpy.default_texture
|
||||
print("✗ Texture setter should not exist (Issue #19)")
|
||||
except AttributeError:
|
||||
print("✓ Texture is read-only (Issue #19 requests setter)")
|
||||
except Exception as e:
|
||||
print(f"✗ Unexpected error setting texture: {e}")
|
||||
|
||||
# Test sprite_number property
|
||||
try:
|
||||
print(f"Sprite2 sprite_number: {sprite2.sprite_number}")
|
||||
sprite2.sprite_number = 10
|
||||
print(f"✓ Changed sprite_number to: {sprite2.sprite_number}")
|
||||
except Exception as e:
|
||||
print(f"✗ sprite_number property failed: {e}")
|
||||
|
||||
# Test sprite index validation (Issue #33)
|
||||
try:
|
||||
# Try to set invalid sprite index
|
||||
sprite2.sprite_number = 9999
|
||||
print("✗ Should validate sprite index against texture range (Issue #33)")
|
||||
except Exception as e:
|
||||
print(f"✓ Sprite index validation works: {e}")
|
||||
|
||||
# Create grid of sprites to show different indices
|
||||
y_offset = 100
|
||||
for i in range(12): # Show first 12 sprites
|
||||
sprite = mcrfpy.Sprite(10 + (i % 6) * 40, y_offset + (i // 6) * 40,
|
||||
mcrfpy.default_texture, i, 2.0)
|
||||
ui.append(sprite)
|
||||
|
||||
caption = mcrfpy.Caption(mcrfpy.Vector(10, 200),
|
||||
text="Issue #19: Sprites need texture setter",
|
||||
fill_color=mcrfpy.Color(255, 255, 255))
|
||||
ui.append(caption)
|
||||
|
||||
print("PASS")
|
||||
104
tests/unit/ui_UICollection_issue69_test.py
Normal file
104
tests/unit/ui_UICollection_issue69_test.py
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test for UICollection - Related to issue #69 (Sequence Protocol)"""
|
||||
import mcrfpy
|
||||
from datetime import datetime
|
||||
|
||||
def test_UICollection():
|
||||
"""Test UICollection sequence protocol compliance"""
|
||||
# Create test scene
|
||||
mcrfpy.createScene("collection_test")
|
||||
mcrfpy.setScene("collection_test")
|
||||
ui = mcrfpy.sceneUI("collection_test")
|
||||
|
||||
# Add various UI elements
|
||||
frame = mcrfpy.Frame(10, 10, 100, 100)
|
||||
caption = mcrfpy.Caption(mcrfpy.Vector(120, 10), text="Test")
|
||||
# Skip sprite for now since it requires a texture
|
||||
|
||||
ui.append(frame)
|
||||
ui.append(caption)
|
||||
|
||||
print("Testing UICollection sequence protocol (Issue #69)...")
|
||||
|
||||
# Test len()
|
||||
try:
|
||||
length = len(ui)
|
||||
print(f"✓ len() works: {length} items")
|
||||
except Exception as e:
|
||||
print(f"✗ len() failed: {e}")
|
||||
|
||||
# Test indexing
|
||||
try:
|
||||
item0 = ui[0]
|
||||
item1 = ui[1]
|
||||
print(f"✓ Indexing works: [{type(item0).__name__}, {type(item1).__name__}]")
|
||||
|
||||
# Test negative indexing
|
||||
last_item = ui[-1]
|
||||
print(f"✓ Negative indexing works: ui[-1] = {type(last_item).__name__}")
|
||||
except Exception as e:
|
||||
print(f"✗ Indexing failed: {e}")
|
||||
|
||||
# Test slicing (if implemented)
|
||||
try:
|
||||
slice_items = ui[0:2]
|
||||
print(f"✓ Slicing works: got {len(slice_items)} items")
|
||||
except Exception as e:
|
||||
print(f"✗ Slicing not implemented (Issue #69): {e}")
|
||||
|
||||
# Test iteration
|
||||
try:
|
||||
types = []
|
||||
for item in ui:
|
||||
types.append(type(item).__name__)
|
||||
print(f"✓ Iteration works: {types}")
|
||||
except Exception as e:
|
||||
print(f"✗ Iteration failed: {e}")
|
||||
|
||||
# Test contains
|
||||
try:
|
||||
if frame in ui:
|
||||
print("✓ 'in' operator works")
|
||||
else:
|
||||
print("✗ 'in' operator returned False for existing item")
|
||||
except Exception as e:
|
||||
print(f"✗ 'in' operator not implemented (Issue #69): {e}")
|
||||
|
||||
# Test remove
|
||||
try:
|
||||
ui.remove(1) # Remove caption
|
||||
print(f"✓ remove() works, now {len(ui)} items")
|
||||
except Exception as e:
|
||||
print(f"✗ remove() failed: {e}")
|
||||
|
||||
# Test type preservation (Issue #76)
|
||||
try:
|
||||
# Add a frame with children to test nested collections
|
||||
parent_frame = mcrfpy.Frame(250, 10, 200, 200,
|
||||
fill_color=mcrfpy.Color(200, 200, 200))
|
||||
child_caption = mcrfpy.Caption(mcrfpy.Vector(10, 10), text="Child")
|
||||
parent_frame.children.append(child_caption)
|
||||
ui.append(parent_frame)
|
||||
|
||||
# Check if type is preserved when retrieving
|
||||
retrieved = ui[-1]
|
||||
if type(retrieved).__name__ == "Frame":
|
||||
print("✓ Type preservation works")
|
||||
else:
|
||||
print(f"✗ Type not preserved (Issue #76): got {type(retrieved).__name__}")
|
||||
except Exception as e:
|
||||
print(f"✗ Type preservation test failed: {e}")
|
||||
|
||||
# Test find by name (Issue #41 - not yet implemented)
|
||||
try:
|
||||
found = ui.find("Test")
|
||||
print(f"✓ find() method works: {type(found).__name__}")
|
||||
except AttributeError:
|
||||
print("✗ find() method not implemented (Issue #41)")
|
||||
except Exception as e:
|
||||
print(f"✗ find() method error: {e}")
|
||||
|
||||
print("PASS")
|
||||
|
||||
# Run test immediately
|
||||
test_UICollection()
|
||||
116
tests/unit/validate_screenshot_test.py
Normal file
116
tests/unit/validate_screenshot_test.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Validate screenshot functionality and analyze pixel data"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
from datetime import datetime
|
||||
import sys
|
||||
|
||||
def test_screenshot_validation():
|
||||
"""Create visible content and validate screenshot output"""
|
||||
print("=== Screenshot Validation Test ===\n")
|
||||
|
||||
# Create a scene with bright, visible content
|
||||
mcrfpy.createScene("screenshot_validation")
|
||||
mcrfpy.setScene("screenshot_validation")
|
||||
ui = mcrfpy.sceneUI("screenshot_validation")
|
||||
|
||||
# Create multiple colorful elements to ensure visibility
|
||||
print("Creating UI elements...")
|
||||
|
||||
# Bright red frame with white outline
|
||||
frame1 = mcrfpy.Frame(50, 50, 300, 200,
|
||||
fill_color=mcrfpy.Color(255, 0, 0), # Bright red
|
||||
outline_color=mcrfpy.Color(255, 255, 255), # White
|
||||
outline=5.0)
|
||||
ui.append(frame1)
|
||||
print("Added red frame at (50, 50)")
|
||||
|
||||
# Bright green frame
|
||||
frame2 = mcrfpy.Frame(400, 50, 300, 200,
|
||||
fill_color=mcrfpy.Color(0, 255, 0), # Bright green
|
||||
outline_color=mcrfpy.Color(0, 0, 0), # Black
|
||||
outline=3.0)
|
||||
ui.append(frame2)
|
||||
print("Added green frame at (400, 50)")
|
||||
|
||||
# Blue frame
|
||||
frame3 = mcrfpy.Frame(50, 300, 300, 200,
|
||||
fill_color=mcrfpy.Color(0, 0, 255), # Bright blue
|
||||
outline_color=mcrfpy.Color(255, 255, 0), # Yellow
|
||||
outline=4.0)
|
||||
ui.append(frame3)
|
||||
print("Added blue frame at (50, 300)")
|
||||
|
||||
# Add text captions
|
||||
caption1 = mcrfpy.Caption(mcrfpy.Vector(60, 60),
|
||||
text="RED FRAME TEST",
|
||||
fill_color=mcrfpy.Color(255, 255, 255))
|
||||
caption1.size = 24
|
||||
frame1.children.append(caption1)
|
||||
|
||||
caption2 = mcrfpy.Caption(mcrfpy.Vector(410, 60),
|
||||
text="GREEN FRAME TEST",
|
||||
fill_color=mcrfpy.Color(0, 0, 0))
|
||||
caption2.size = 24
|
||||
ui.append(caption2)
|
||||
|
||||
caption3 = mcrfpy.Caption(mcrfpy.Vector(60, 310),
|
||||
text="BLUE FRAME TEST",
|
||||
fill_color=mcrfpy.Color(255, 255, 0))
|
||||
caption3.size = 24
|
||||
ui.append(caption3)
|
||||
|
||||
# White background frame to ensure non-transparent background
|
||||
background = mcrfpy.Frame(0, 0, 1024, 768,
|
||||
fill_color=mcrfpy.Color(200, 200, 200)) # Light gray
|
||||
# Insert at beginning so it's behind everything
|
||||
ui.remove(len(ui) - 1) # Remove to re-add at start
|
||||
ui.append(background)
|
||||
# Re-add all other elements on top
|
||||
for frame in [frame1, frame2, frame3, caption2, caption3]:
|
||||
ui.append(frame)
|
||||
|
||||
print(f"\nTotal UI elements: {len(ui)}")
|
||||
|
||||
# Take multiple screenshots with different names
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
screenshots = [
|
||||
f"validate_screenshot_basic_{timestamp}.png",
|
||||
f"validate_screenshot_with_spaces {timestamp}.png",
|
||||
f"validate_screenshot_final_{timestamp}.png"
|
||||
]
|
||||
|
||||
print("\nTaking screenshots...")
|
||||
for i, filename in enumerate(screenshots):
|
||||
result = automation.screenshot(filename)
|
||||
print(f"Screenshot {i+1}: {filename} - Result: {result}")
|
||||
|
||||
# Test invalid cases
|
||||
print("\nTesting edge cases...")
|
||||
|
||||
# Empty filename
|
||||
result = automation.screenshot("")
|
||||
print(f"Empty filename result: {result}")
|
||||
|
||||
# Very long filename
|
||||
long_name = "x" * 200 + ".png"
|
||||
result = automation.screenshot(long_name)
|
||||
print(f"Long filename result: {result}")
|
||||
|
||||
print("\n=== Test Complete ===")
|
||||
print("Check the PNG files to see if they contain visible content.")
|
||||
print("If they're transparent, the headless renderer may not be working correctly.")
|
||||
|
||||
# List what should be visible
|
||||
print("\nExpected content:")
|
||||
print("- Light gray background (200, 200, 200)")
|
||||
print("- Red frame with white outline at (50, 50)")
|
||||
print("- Green frame with black outline at (400, 50)")
|
||||
print("- Blue frame with yellow outline at (50, 300)")
|
||||
print("- White, black, and yellow text labels")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
# Run the test immediately
|
||||
test_screenshot_validation()
|
||||
42
tests/unit/working_timer_test.py
Normal file
42
tests/unit/working_timer_test.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test that timers work correctly with --exec"""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
|
||||
print("Setting up timer test...")
|
||||
|
||||
# Create a scene
|
||||
mcrfpy.createScene("timer_works")
|
||||
mcrfpy.setScene("timer_works")
|
||||
ui = mcrfpy.sceneUI("timer_works")
|
||||
|
||||
# Add visible content
|
||||
frame = mcrfpy.Frame(100, 100, 300, 200,
|
||||
fill_color=mcrfpy.Color(255, 0, 0),
|
||||
outline_color=mcrfpy.Color(255, 255, 255),
|
||||
outline=3.0)
|
||||
ui.append(frame)
|
||||
|
||||
caption = mcrfpy.Caption(mcrfpy.Vector(150, 150),
|
||||
text="TIMER TEST SUCCESS",
|
||||
fill_color=mcrfpy.Color(255, 255, 255))
|
||||
caption.size = 24
|
||||
ui.append(caption)
|
||||
|
||||
# Timer callback with correct signature
|
||||
def timer_callback(runtime):
|
||||
print(f"\n✓ Timer fired successfully at runtime: {runtime}")
|
||||
|
||||
# Take screenshot
|
||||
filename = f"timer_success_{int(runtime)}.png"
|
||||
result = automation.screenshot(filename)
|
||||
print(f"Screenshot saved: {filename} - Result: {result}")
|
||||
|
||||
# Cancel timer and exit
|
||||
mcrfpy.delTimer("success_timer")
|
||||
print("Exiting...")
|
||||
mcrfpy.exit()
|
||||
|
||||
# Set timer
|
||||
mcrfpy.setTimer("success_timer", timer_callback, 1000)
|
||||
print("Timer set for 1 second. Game loop starting...")
|
||||
Loading…
Add table
Add a link
Reference in a new issue