diff --git a/.archive/entity_property_setters_test.py b/.archive/entity_property_setters_test.py deleted file mode 100644 index b912b43..0000000 --- a/.archive/entity_property_setters_test.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Entity property setters - fixing "new style getargs format" error - -Verifies that Entity position and sprite_number setters work correctly. -""" - -def test_entity_setters(timer_name): - """Test that Entity property setters work correctly""" - import mcrfpy - - print("Testing Entity property setters...") - - # Create test scene and grid - mcrfpy.createScene("entity_test") - ui = mcrfpy.sceneUI("entity_test") - - # Create grid with texture - texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) - grid = mcrfpy.Grid(10, 10, texture, (10, 10), (400, 400)) - ui.append(grid) - - # Create entity - initial_pos = mcrfpy.Vector(2.5, 3.5) - entity = mcrfpy.Entity(initial_pos, texture, 5, grid) - grid.entities.append(entity) - - print(f"✓ Created entity at position {entity.pos}") - - # Test position setter with Vector - new_pos = mcrfpy.Vector(4.0, 5.0) - try: - entity.pos = new_pos - assert entity.pos.x == 4.0, f"Expected x=4.0, got {entity.pos.x}" - assert entity.pos.y == 5.0, f"Expected y=5.0, got {entity.pos.y}" - print(f"✓ Position setter works with Vector: {entity.pos}") - except Exception as e: - print(f"✗ Position setter failed: {e}") - raise - - # Test position setter with tuple (should also work via PyVector::from_arg) - try: - entity.pos = (7.5, 8.5) - assert entity.pos.x == 7.5, f"Expected x=7.5, got {entity.pos.x}" - assert entity.pos.y == 8.5, f"Expected y=8.5, got {entity.pos.y}" - print(f"✓ Position setter works with tuple: {entity.pos}") - except Exception as e: - print(f"✗ Position setter with tuple failed: {e}") - raise - - # Test draw_pos setter (collision position) - try: - entity.draw_pos = mcrfpy.Vector(3, 4) - assert entity.draw_pos.x == 3, f"Expected x=3, got {entity.draw_pos.x}" - assert entity.draw_pos.y == 4, f"Expected y=4, got {entity.draw_pos.y}" - print(f"✓ Draw position setter works: {entity.draw_pos}") - except Exception as e: - print(f"✗ Draw position setter failed: {e}") - raise - - # Test sprite_number setter - try: - entity.sprite_number = 10 - assert entity.sprite_number == 10, f"Expected sprite_number=10, got {entity.sprite_number}" - print(f"✓ Sprite number setter works: {entity.sprite_number}") - except Exception as e: - print(f"✗ Sprite number setter failed: {e}") - raise - - # Test invalid position setter (should raise TypeError) - try: - entity.pos = "invalid" - print("✗ Position setter should have raised TypeError for string") - assert False, "Should have raised TypeError" - except TypeError as e: - print(f"✓ Position setter correctly rejects invalid type: {e}") - except Exception as e: - print(f"✗ Unexpected error: {e}") - raise - - # Test invalid sprite number (should raise TypeError) - try: - entity.sprite_number = "invalid" - print("✗ Sprite number setter should have raised TypeError for string") - assert False, "Should have raised TypeError" - except TypeError as e: - print(f"✓ Sprite number setter correctly rejects invalid type: {e}") - except Exception as e: - print(f"✗ Unexpected error: {e}") - raise - - # Cleanup timer - mcrfpy.delTimer("test_timer") - - print("\n✅ Entity property setters test PASSED - All setters work correctly") - -# Execute the test after a short delay to ensure window is ready -import mcrfpy -mcrfpy.setTimer("test_timer", test_entity_setters, 100) \ No newline at end of file diff --git a/.archive/entity_setter_simple_test.py b/.archive/entity_setter_simple_test.py deleted file mode 100644 index e9b9fbb..0000000 --- a/.archive/entity_setter_simple_test.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple test for Entity property setters -""" - -def test_entity_setters(timer_name): - """Test Entity property setters""" - import mcrfpy - import sys - - print("Testing Entity property setters...") - - # Create test scene and grid - mcrfpy.createScene("test") - ui = mcrfpy.sceneUI("test") - - # Create grid with texture - texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) - grid = mcrfpy.Grid(10, 10, texture, (10, 10), (400, 400)) - ui.append(grid) - - # Create entity - entity = mcrfpy.Entity((2.5, 3.5), texture, 5, grid) - grid.entities.append(entity) - - # Test 1: Initial position - print(f"Initial position: {entity.pos}") - print(f"Initial position x={entity.pos.x}, y={entity.pos.y}") - - # Test 2: Set position with Vector - entity.pos = mcrfpy.Vector(4.0, 5.0) - print(f"After Vector setter: pos={entity.pos}, x={entity.pos.x}, y={entity.pos.y}") - - # Test 3: Set position with tuple - entity.pos = (7.5, 8.5) - print(f"After tuple setter: pos={entity.pos}, x={entity.pos.x}, y={entity.pos.y}") - - # Test 4: sprite_number - print(f"Initial sprite_number: {entity.sprite_number}") - entity.sprite_number = 10 - print(f"After setter: sprite_number={entity.sprite_number}") - - # Test 5: Invalid types - try: - entity.pos = "invalid" - print("ERROR: Should have raised TypeError") - except TypeError as e: - print(f"✓ Correctly rejected invalid position: {e}") - - try: - entity.sprite_number = "invalid" - print("ERROR: Should have raised TypeError") - except TypeError as e: - print(f"✓ Correctly rejected invalid sprite_number: {e}") - - print("\n✅ Entity property setters test completed") - sys.exit(0) - -# Execute the test after a short delay -import mcrfpy -mcrfpy.setTimer("test", test_entity_setters, 100) \ No newline at end of file diff --git a/.archive/issue27_entity_extend_test.py b/.archive/issue27_entity_extend_test.py deleted file mode 100644 index 41fd744..0000000 --- a/.archive/issue27_entity_extend_test.py +++ /dev/null @@ -1,105 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #27: EntityCollection.extend() method - -Verifies that EntityCollection can extend with multiple entities at once. -""" - -def test_entity_extend(timer_name): - """Test that EntityCollection.extend() method works correctly""" - import mcrfpy - import sys - - print("Issue #27 test: EntityCollection.extend() method") - - # Create test scene and grid - mcrfpy.createScene("test") - ui = mcrfpy.sceneUI("test") - - # Create grid with texture - texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) - grid = mcrfpy.Grid(10, 10, texture, (10, 10), (400, 400)) - ui.append(grid) - - # Add some initial entities - entity1 = mcrfpy.Entity((1, 1), texture, 1, grid) - entity2 = mcrfpy.Entity((2, 2), texture, 2, grid) - grid.entities.append(entity1) - grid.entities.append(entity2) - - print(f"✓ Initial entities: {len(grid.entities)}") - - # Test 1: Extend with a list of entities - new_entities = [ - mcrfpy.Entity((3, 3), texture, 3, grid), - mcrfpy.Entity((4, 4), texture, 4, grid), - mcrfpy.Entity((5, 5), texture, 5, grid) - ] - - try: - grid.entities.extend(new_entities) - assert len(grid.entities) == 5, f"Expected 5 entities, got {len(grid.entities)}" - print(f"✓ Extended with list: now {len(grid.entities)} entities") - except Exception as e: - print(f"✗ Failed to extend with list: {e}") - raise - - # Test 2: Extend with a tuple - more_entities = ( - mcrfpy.Entity((6, 6), texture, 6, grid), - mcrfpy.Entity((7, 7), texture, 7, grid) - ) - - try: - grid.entities.extend(more_entities) - assert len(grid.entities) == 7, f"Expected 7 entities, got {len(grid.entities)}" - print(f"✓ Extended with tuple: now {len(grid.entities)} entities") - except Exception as e: - print(f"✗ Failed to extend with tuple: {e}") - raise - - # Test 3: Extend with generator expression - try: - grid.entities.extend(mcrfpy.Entity((8, i), texture, 8+i, grid) for i in range(3)) - assert len(grid.entities) == 10, f"Expected 10 entities, got {len(grid.entities)}" - print(f"✓ Extended with generator: now {len(grid.entities)} entities") - except Exception as e: - print(f"✗ Failed to extend with generator: {e}") - raise - - # Test 4: Verify all entities have correct grid association - for i, entity in enumerate(grid.entities): - # Just checking that we can iterate and access them - assert entity.sprite_number >= 1, f"Entity {i} has invalid sprite number" - print("✓ All entities accessible and valid") - - # Test 5: Invalid input - non-iterable - try: - grid.entities.extend(42) - print("✗ Should have raised TypeError for non-iterable") - except TypeError as e: - print(f"✓ Correctly rejected non-iterable: {e}") - - # Test 6: Invalid input - iterable with non-Entity - try: - grid.entities.extend([entity1, "not an entity", entity2]) - print("✗ Should have raised TypeError for non-Entity in iterable") - except TypeError as e: - print(f"✓ Correctly rejected non-Entity in iterable: {e}") - - # Test 7: Empty iterable (should work) - initial_count = len(grid.entities) - try: - grid.entities.extend([]) - assert len(grid.entities) == initial_count, "Empty extend changed count" - print("✓ Empty extend works correctly") - except Exception as e: - print(f"✗ Empty extend failed: {e}") - raise - - print(f"\n✅ Issue #27 test PASSED - EntityCollection.extend() works correctly") - sys.exit(0) - -# Execute the test after a short delay -import mcrfpy -mcrfpy.setTimer("test", test_entity_extend, 100) \ No newline at end of file diff --git a/.archive/issue33_sprite_index_validation_test.py b/.archive/issue33_sprite_index_validation_test.py deleted file mode 100644 index 4e321dd..0000000 --- a/.archive/issue33_sprite_index_validation_test.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #33: Sprite index validation - -Verifies that Sprite and Entity objects validate sprite indices -against the texture's actual sprite count. -""" - -def test_sprite_index_validation(timer_name): - """Test that sprite index validation works correctly""" - import mcrfpy - import sys - - print("Issue #33 test: Sprite index validation") - - # Create test scene - mcrfpy.createScene("test") - ui = mcrfpy.sceneUI("test") - - # Create texture - kenney_ice.png is 11x12 sprites of 16x16 each - texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) - # Total sprites = 11 * 12 = 132 sprites (indices 0-131) - - # Test 1: Create sprite with valid index - try: - sprite = mcrfpy.Sprite(100, 100, texture, 50) # Valid index - ui.append(sprite) - print(f"✓ Created sprite with valid index 50") - except Exception as e: - print(f"✗ Failed to create sprite with valid index: {e}") - raise - - # Test 2: Set valid sprite index - try: - sprite.sprite_number = 100 # Still valid - assert sprite.sprite_number == 100 - print(f"✓ Set sprite to valid index 100") - except Exception as e: - print(f"✗ Failed to set valid sprite index: {e}") - raise - - # Test 3: Set maximum valid index - try: - sprite.sprite_number = 131 # Maximum valid index - assert sprite.sprite_number == 131 - print(f"✓ Set sprite to maximum valid index 131") - except Exception as e: - print(f"✗ Failed to set maximum valid index: {e}") - raise - - # Test 4: Invalid negative index - try: - sprite.sprite_number = -1 - print("✗ Should have raised ValueError for negative index") - except ValueError as e: - print(f"✓ Correctly rejected negative index: {e}") - except Exception as e: - print(f"✗ Wrong exception type for negative index: {e}") - raise - - # Test 5: Invalid index too large - try: - sprite.sprite_number = 132 # One past the maximum - print("✗ Should have raised ValueError for index 132") - except ValueError as e: - print(f"✓ Correctly rejected out-of-bounds index: {e}") - except Exception as e: - print(f"✗ Wrong exception type for out-of-bounds index: {e}") - raise - - # Test 6: Very large invalid index - try: - sprite.sprite_number = 1000 - print("✗ Should have raised ValueError for index 1000") - except ValueError as e: - print(f"✓ Correctly rejected large invalid index: {e}") - - # Test 7: Entity sprite_number validation - grid = mcrfpy.Grid(10, 10, texture, (10, 10), (400, 400)) - ui.append(grid) - - entity = mcrfpy.Entity((5, 5), texture, 50, grid) - grid.entities.append(entity) - - try: - entity.sprite_number = 200 # Out of bounds - print("✗ Entity should also validate sprite indices") - except ValueError as e: - print(f"✓ Entity also validates sprite indices: {e}") - except Exception as e: - # Entity might not have the same validation yet - print(f"Note: Entity validation not implemented yet: {e}") - - # Test 8: Different texture sizes - # Create a smaller texture to test different bounds - small_texture = mcrfpy.Texture("assets/Sprite-0001.png", 32, 32) - small_sprite = mcrfpy.Sprite(200, 200, small_texture, 0) - - # This texture might have fewer sprites, test accordingly - try: - small_sprite.sprite_number = 100 # Might be out of bounds - print("Note: Small texture accepted index 100") - except ValueError as e: - print(f"✓ Small texture has different bounds: {e}") - - print(f"\n✅ Issue #33 test PASSED - Sprite index validation works correctly") - sys.exit(0) - -# Execute the test after a short delay -import mcrfpy -mcrfpy.setTimer("test", test_sprite_index_validation, 100) \ No newline at end of file diff --git a/.archive/issue73_entity_index_test.py b/.archive/issue73_entity_index_test.py deleted file mode 100644 index 18662ec..0000000 --- a/.archive/issue73_entity_index_test.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #73: Entity.index() method for removal - -Verifies that Entity objects can report their index in the grid's entity collection. -""" - -def test_entity_index(timer_name): - """Test that Entity.index() method works correctly""" - import mcrfpy - import sys - - print("Issue #73 test: Entity.index() method") - - # Create test scene and grid - mcrfpy.createScene("test") - ui = mcrfpy.sceneUI("test") - - # Create grid with texture - texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) - grid = mcrfpy.Grid(10, 10, texture, (10, 10), (400, 400)) - ui.append(grid) - - # Create multiple entities - entities = [] - for i in range(5): - entity = mcrfpy.Entity((i, i), texture, i, grid) - entities.append(entity) - grid.entities.append(entity) - - print(f"✓ Created {len(entities)} entities") - - # Test 1: Check each entity knows its index - for expected_idx, entity in enumerate(entities): - try: - actual_idx = entity.index() - assert actual_idx == expected_idx, f"Expected index {expected_idx}, got {actual_idx}" - print(f"✓ Entity {expected_idx} correctly reports index {actual_idx}") - except Exception as e: - print(f"✗ Entity {expected_idx} index() failed: {e}") - raise - - # Test 2: Remove entity using index - entity_to_remove = entities[2] - remove_idx = entity_to_remove.index() - grid.entities.remove(remove_idx) - print(f"✓ Removed entity at index {remove_idx}") - - # Test 3: Verify indices updated after removal - for i, entity in enumerate(entities): - if i == 2: - # This entity was removed, should raise error - try: - idx = entity.index() - print(f"✗ Removed entity still reports index {idx}") - except ValueError as e: - print(f"✓ Removed entity correctly raises error: {e}") - elif i < 2: - # These entities should keep their indices - idx = entity.index() - assert idx == i, f"Entity before removal has wrong index: {idx}" - else: - # These entities should have shifted down by 1 - idx = entity.index() - assert idx == i - 1, f"Entity after removal has wrong index: {idx}" - - # Test 4: Entity without grid - orphan_entity = mcrfpy.Entity((0, 0), texture, 0, None) - try: - idx = orphan_entity.index() - print(f"✗ Orphan entity should raise error but returned {idx}") - except RuntimeError as e: - print(f"✓ Orphan entity correctly raises error: {e}") - - # Test 5: Use index() in practical removal pattern - # Add some new entities - for i in range(3): - entity = mcrfpy.Entity((7+i, 7+i), texture, 10+i, grid) - grid.entities.append(entity) - - # Remove entities with sprite_number > 10 - removed_count = 0 - i = 0 - while i < len(grid.entities): - entity = grid.entities[i] - if entity.sprite_number > 10: - grid.entities.remove(entity.index()) - removed_count += 1 - # Don't increment i, as entities shifted down - else: - i += 1 - - print(f"✓ Removed {removed_count} entities using index() in loop") - assert len(grid.entities) == 5, f"Expected 5 entities remaining, got {len(grid.entities)}" - - print("\n✅ Issue #73 test PASSED - Entity.index() method works correctly") - sys.exit(0) - -# Execute the test after a short delay -import mcrfpy -mcrfpy.setTimer("test", test_entity_index, 100) \ No newline at end of file diff --git a/.archive/issue73_simple_index_test.py b/.archive/issue73_simple_index_test.py deleted file mode 100644 index a206f65..0000000 --- a/.archive/issue73_simple_index_test.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple test for Issue #73: Entity.index() method -""" - -def test_entity_index(timer_name): - """Test that Entity.index() method works correctly""" - import mcrfpy - import sys - - print("Testing Entity.index() method...") - - # Create test scene and grid - mcrfpy.createScene("test") - ui = mcrfpy.sceneUI("test") - - # Create grid with texture - texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) - grid = mcrfpy.Grid(10, 10, texture, (10, 10), (400, 400)) - ui.append(grid) - - # Clear any existing entities - while len(grid.entities) > 0: - grid.entities.remove(0) - - # Create entities - entity1 = mcrfpy.Entity((1, 1), texture, 1, grid) - entity2 = mcrfpy.Entity((2, 2), texture, 2, grid) - entity3 = mcrfpy.Entity((3, 3), texture, 3, grid) - - grid.entities.append(entity1) - grid.entities.append(entity2) - grid.entities.append(entity3) - - print(f"Created {len(grid.entities)} entities") - - # Test index() method - idx1 = entity1.index() - idx2 = entity2.index() - idx3 = entity3.index() - - print(f"Entity 1 index: {idx1}") - print(f"Entity 2 index: {idx2}") - print(f"Entity 3 index: {idx3}") - - assert idx1 == 0, f"Entity 1 should be at index 0, got {idx1}" - assert idx2 == 1, f"Entity 2 should be at index 1, got {idx2}" - assert idx3 == 2, f"Entity 3 should be at index 2, got {idx3}" - - print("✓ All entities report correct indices") - - # Test removal using index - remove_idx = entity2.index() - grid.entities.remove(remove_idx) - print(f"✓ Removed entity at index {remove_idx}") - - # Check remaining entities - assert len(grid.entities) == 2 - assert entity1.index() == 0 - assert entity3.index() == 1 # Should have shifted down - - print("✓ Indices updated correctly after removal") - - # Test entity not in grid - orphan = mcrfpy.Entity((5, 5), texture, 5, None) - try: - idx = orphan.index() - print(f"✗ Orphan entity should raise error but returned {idx}") - except RuntimeError as e: - print(f"✓ Orphan entity correctly raises error") - - print("\n✅ Entity.index() test PASSED") - sys.exit(0) - -# Execute the test after a short delay -import mcrfpy -mcrfpy.setTimer("test", test_entity_index, 100) \ No newline at end of file diff --git a/.archive/issue74_grid_xy_properties_test.py b/.archive/issue74_grid_xy_properties_test.py deleted file mode 100644 index 590c14e..0000000 --- a/.archive/issue74_grid_xy_properties_test.py +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #74: Add missing Grid.grid_y property - -Verifies that Grid objects expose grid_x and grid_y properties correctly. -""" - -def test_grid_xy_properties(timer_name): - """Test that Grid has grid_x and grid_y properties""" - import mcrfpy - - # Test was run - print("Issue #74 test: Grid.grid_x and Grid.grid_y properties") - - # Test with texture - texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) - grid = mcrfpy.Grid(20, 15, texture, (0, 0), (800, 600)) - - # Test grid_x property - assert hasattr(grid, 'grid_x'), "Grid should have grid_x property" - assert grid.grid_x == 20, f"Expected grid_x=20, got {grid.grid_x}" - print(f"✓ grid.grid_x = {grid.grid_x}") - - # Test grid_y property - assert hasattr(grid, 'grid_y'), "Grid should have grid_y property" - assert grid.grid_y == 15, f"Expected grid_y=15, got {grid.grid_y}" - print(f"✓ grid.grid_y = {grid.grid_y}") - - # Test grid_size still works - assert hasattr(grid, 'grid_size'), "Grid should still have grid_size property" - assert grid.grid_size == (20, 15), f"Expected grid_size=(20, 15), got {grid.grid_size}" - print(f"✓ grid.grid_size = {grid.grid_size}") - - # Test without texture - grid2 = mcrfpy.Grid(30, 25, None, (10, 10), (480, 400)) - assert grid2.grid_x == 30, f"Expected grid_x=30, got {grid2.grid_x}" - assert grid2.grid_y == 25, f"Expected grid_y=25, got {grid2.grid_y}" - assert grid2.grid_size == (30, 25), f"Expected grid_size=(30, 25), got {grid2.grid_size}" - print("✓ Grid without texture also has correct grid_x and grid_y") - - # Test using in error message context (original issue) - try: - grid.at((-1, 0)) # Should raise error - except ValueError as e: - error_msg = str(e) - assert "Grid.grid_x" in error_msg, f"Error message should reference Grid.grid_x: {error_msg}" - print(f"✓ Error message correctly references Grid.grid_x: {error_msg}") - - try: - grid.at((0, -1)) # Should raise error - except ValueError as e: - error_msg = str(e) - assert "Grid.grid_y" in error_msg, f"Error message should reference Grid.grid_y: {error_msg}" - print(f"✓ Error message correctly references Grid.grid_y: {error_msg}") - - print("\n✅ Issue #74 test PASSED - Grid.grid_x and Grid.grid_y properties work correctly") - -# Execute the test after a short delay to ensure window is ready -import mcrfpy -mcrfpy.setTimer("test_timer", test_grid_xy_properties, 100) \ No newline at end of file diff --git a/.archive/issue78_middle_click_fix_test.py b/.archive/issue78_middle_click_fix_test.py deleted file mode 100644 index fac4f18..0000000 --- a/.archive/issue78_middle_click_fix_test.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python3 -"""Test that Issue #78 is fixed - Middle Mouse Click should NOT send 'C' keyboard event""" -import mcrfpy -from mcrfpy import automation -import sys - -# Track events -keyboard_events = [] -click_events = [] - -def keyboard_handler(key): - """Track keyboard events""" - keyboard_events.append(key) - print(f"Keyboard event received: '{key}'") - -def click_handler(x, y, button): - """Track click events""" - click_events.append((x, y, button)) - print(f"Click event received: ({x}, {y}, button={button})") - -def test_middle_click_fix(runtime): - """Test that middle click no longer sends 'C' key event""" - print(f"\n=== Testing Issue #78 Fix (runtime: {runtime}) ===") - - # Simulate middle click - print("\nSimulating middle click at (200, 200)...") - automation.middleClick(200, 200) - - # Also test other clicks for comparison - print("Simulating left click at (100, 100)...") - automation.click(100, 100) - - print("Simulating right click at (300, 300)...") - automation.rightClick(300, 300) - - # Wait a moment for events to process - mcrfpy.setTimer("check_results", check_results, 500) - -def check_results(runtime): - """Check if the bug is fixed""" - print(f"\n=== Results ===") - print(f"Keyboard events received: {len(keyboard_events)}") - print(f"Click events received: {len(click_events)}") - - # Check if 'C' was incorrectly triggered - if 'C' in keyboard_events or 'c' in keyboard_events: - print("\n✗ FAIL - Issue #78 still exists: Middle click triggered 'C' keyboard event!") - print(f"Keyboard events: {keyboard_events}") - else: - print("\n✓ PASS - Issue #78 is FIXED: No spurious 'C' keyboard event from middle click!") - - # Take screenshot - filename = f"issue78_fixed_{int(runtime)}.png" - automation.screenshot(filename) - print(f"\nScreenshot saved: {filename}") - - # Cleanup and exit - mcrfpy.delTimer("check_results") - sys.exit(0) - -# Set up test scene -print("Setting up test scene...") -mcrfpy.createScene("issue78_test") -mcrfpy.setScene("issue78_test") -ui = mcrfpy.sceneUI("issue78_test") - -# Register keyboard handler -mcrfpy.keypressScene(keyboard_handler) - -# Create a clickable frame -frame = mcrfpy.Frame(50, 50, 400, 400, - fill_color=mcrfpy.Color(100, 150, 200), - outline_color=mcrfpy.Color(255, 255, 255), - outline=3.0) -frame.click = click_handler -ui.append(frame) - -# Add label -caption = mcrfpy.Caption(mcrfpy.Vector(100, 100), - text="Issue #78 Test - Middle Click", - fill_color=mcrfpy.Color(255, 255, 255)) -caption.size = 24 -ui.append(caption) - -# Schedule test -print("Scheduling test to run after render loop starts...") -mcrfpy.setTimer("test", test_middle_click_fix, 1000) \ No newline at end of file diff --git a/.archive/sequence_demo_screenshot.png b/.archive/sequence_demo_screenshot.png deleted file mode 100644 index 8dd48de..0000000 Binary files a/.archive/sequence_demo_screenshot.png and /dev/null differ diff --git a/.archive/sequence_protocol_test.png b/.archive/sequence_protocol_test.png deleted file mode 100644 index 158f93f..0000000 Binary files a/.archive/sequence_protocol_test.png and /dev/null differ diff --git a/.archive/sprite_texture_setter_test.py b/.archive/sprite_texture_setter_test.py deleted file mode 100644 index fb6019c..0000000 --- a/.archive/sprite_texture_setter_test.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Sprite texture setter - fixing "error return without exception set" -""" - -def test_sprite_texture_setter(timer_name): - """Test that Sprite texture setter works correctly""" - import mcrfpy - import sys - - print("Testing Sprite texture setter...") - - # Create test scene - mcrfpy.createScene("test") - ui = mcrfpy.sceneUI("test") - - # Create textures - texture1 = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) - texture2 = mcrfpy.Texture("assets/kenney_lava.png", 16, 16) - - # Create sprite with first texture - sprite = mcrfpy.Sprite(100, 100, texture1, 5) - ui.append(sprite) - - # Test getting texture - try: - current_texture = sprite.texture - print(f"✓ Got texture: {current_texture}") - except Exception as e: - print(f"✗ Failed to get texture: {e}") - raise - - # Test setting new texture - try: - sprite.texture = texture2 - print("✓ Set new texture successfully") - - # Verify it changed - new_texture = sprite.texture - if new_texture != texture2: - print(f"✗ Texture didn't change properly") - else: - print("✓ Texture changed correctly") - except Exception as e: - print(f"✗ Failed to set texture: {e}") - raise - - # Test invalid texture type - try: - sprite.texture = "invalid" - print("✗ Should have raised TypeError for invalid texture") - except TypeError as e: - print(f"✓ Correctly rejected invalid texture: {e}") - except Exception as e: - print(f"✗ Wrong exception type: {e}") - raise - - # Test None texture - try: - sprite.texture = None - print("✗ Should have raised TypeError for None texture") - except TypeError as e: - print(f"✓ Correctly rejected None texture: {e}") - - # Test that sprite still renders correctly - print("✓ Sprite still renders with new texture") - - print("\n✅ Sprite texture setter test PASSED") - sys.exit(0) - -# Execute the test after a short delay -import mcrfpy -mcrfpy.setTimer("test", test_sprite_texture_setter, 100) \ No newline at end of file diff --git a/.gitignore b/.gitignore index a00ca39..802fdee 100644 --- a/.gitignore +++ b/.gitignore @@ -10,20 +10,3 @@ build lib obj -.cache/ -7DRL2025 Release/ -CMakeFiles/ -Makefile -*.md -*.zip -__lib/ -_oldscripts/ -assets/ -cellular_automata_fire/ -*.txt -deps/ -fetch_issues_txt.py -forest_fire_CA.py -mcrogueface.github.io -scripts/ -test_* diff --git a/GNUmakefile b/GNUmakefile deleted file mode 100644 index 577cda0..0000000 --- a/GNUmakefile +++ /dev/null @@ -1,54 +0,0 @@ -# Convenience Makefile wrapper for McRogueFace -# This delegates to CMake build in the build directory - -.PHONY: all build clean run test dist help - -# Default target -all: build - -# Build the project -build: - @./build.sh - -# Clean build artifacts -clean: - @./clean.sh - -# Run the game -run: build - @cd build && ./mcrogueface - -# Run in Python mode -python: build - @cd build && ./mcrogueface -i - -# Test basic functionality -test: build - @echo "Testing McRogueFace..." - @cd build && ./mcrogueface -V - @cd build && ./mcrogueface -c "print('Test passed')" - @cd build && ./mcrogueface --headless -c "import mcrfpy; print('mcrfpy imported successfully')" - -# Create distribution archive -dist: build - @echo "Creating distribution archive..." - @cd build && zip -r ../McRogueFace-$$(date +%Y%m%d).zip . -x "*.o" "CMakeFiles/*" "Makefile" "*.cmake" - @echo "Distribution archive created: McRogueFace-$$(date +%Y%m%d).zip" - -# Show help -help: - @echo "McRogueFace Build System" - @echo "=======================" - @echo "" - @echo "Available targets:" - @echo " make - Build the project (default)" - @echo " make build - Build the project" - @echo " make clean - Remove all build artifacts" - @echo " make run - Build and run the game" - @echo " make python - Build and run in Python interactive mode" - @echo " make test - Run basic tests" - @echo " make dist - Create distribution archive" - @echo " make help - Show this help message" - @echo "" - @echo "Build output goes to: ./build/" - @echo "Distribution archives are created in project root" \ No newline at end of file diff --git a/README.md b/README.md index c4de080..89be09d 100644 --- a/README.md +++ b/README.md @@ -1,88 +1,30 @@ -# McRogueFace +# McRogueFace - 2D Game Engine + +An experimental prototype game engine built for my own use in 7DRL 2023. + *Blame my wife for the name* -A Python-powered 2D game engine for creating roguelike games, built with C++ and SFML. +## Tenets: -**Pre-Alpha Release Demo**: my 7DRL 2025 entry *"Crypt of Sokoban"* - a prototype with buttons, boulders, enemies, and items. +* C++ first, Python close behind. +* Entity-Component system based on David Churchill's Memorial University COMP4300 course lectures available on Youtube. +* Graphics, particles and shaders provided by SFML. +* Pathfinding, noise generation, and other Roguelike goodness provided by TCOD. -## Tenets +## Why? -- **Python & C++ Hand-in-Hand**: Create your game without ever recompiling. Your Python commands create C++ objects, and animations can occur without calling Python at all. -- **Simple Yet Flexible UI System**: Sprites, Grids, Frames, and Captions with full animation support -- **Entity-Component Architecture**: Implement your game objects with Python integration -- **Built-in Roguelike Support**: Dungeon generation, pathfinding, and field-of-view via libtcod (demos still under construction) -- **Automation API**: PyAutoGUI-inspired event generation framework. All McRogueFace interactions can be performed headlessly via script: for software testing or AI integration -- **Interactive Development**: Python REPL integration for live game debugging. Use `mcrogueface` like a Python interpreter +I did the r/RoguelikeDev TCOD tutorial in Python. I loved it, but I did not want to be limited to ASCII. I want to be able to draw pixels on top of my tiles (like lines or circles) and eventually incorporate even more polish. -## Quick Start +## To-do -```bash -# Clone and build -git clone -cd McRogueFace -make - -# Run the example game -cd build -./mcrogueface -``` - -## Example: Creating a Simple Scene - -```python -import mcrfpy - -# Create a new scene -mcrfpy.createScene("intro") - -# Add a text caption -caption = mcrfpy.Caption((50, 50), "Welcome to McRogueFace!") -caption.size = 48 -caption.fill_color = (255, 255, 255) - -# Add to scene -mcrfpy.sceneUI("intro").append(caption) - -# Switch to the scene -mcrfpy.setScene("intro") -``` - -## Documentation - -For comprehensive documentation, tutorials, and API reference, visit: -**[https://mcrogueface.github.io](https://mcrogueface.github.io)** - -## Requirements - -- C++17 compiler (GCC 7+ or Clang 5+) -- CMake 3.14+ -- Python 3.12+ -- SFML 2.5+ -- Linux or Windows (macOS untested) - -## Project Structure - -``` -McRogueFace/ -├── src/ # C++ engine source -├── scripts/ # Python game scripts -├── assets/ # Sprites, fonts, audio -├── build/ # Build output directory -└── tests/ # Automated test suite -``` - -## Contributing - -PRs will be considered! Please include explicit mention that your contribution is your own work and released under the MIT license in the pull request. - -The project has a private roadmap and issue list. Reach out via email or social media if you have bugs or feature requests. - -## License - -This project is licensed under the MIT License - see LICENSE file for details. - -## Acknowledgments - -- Developed for 7-Day Roguelike 2023, 2024, 2025 - here's to many more -- Built with [SFML](https://www.sfml-dev.org/), [libtcod](https://github.com/libtcod/libtcod), and Python -- Inspired by David Churchill's COMP4300 game engine lectures +* ✅ Initial Commit +* ✅ Integrate scene, action, entity, component system from COMP4300 engine +* ✅ Windows / Visual Studio project +* ✅ Draw Sprites +* ✅ Play Sounds +* ✅ Draw UI, spawn entity from Python code +* ❌ Python AI for entities (NPCs on set paths, enemies towards player) +* ✅ Walking / Collision +* ❌ "Boards" (stairs / doors / walk off edge of screen) +* ❌ Cutscenes - interrupt normal controls, text scroll, character portraits +* ❌ Mouse integration - tooltips, zoom, click to select targets, cursors diff --git a/_test.py b/_test.py deleted file mode 100644 index f4cdb44..0000000 --- a/_test.py +++ /dev/null @@ -1,16 +0,0 @@ -import mcrfpy - -# Create a new scene -mcrfpy.createScene("intro") - -# Add a text caption -caption = mcrfpy.Caption((50, 50), "Welcome to McRogueFace!") -caption.size = 48 -caption.fill_color = (255, 255, 255) - -# Add to scene -mcrfpy.sceneUI("intro").append(caption) - -# Switch to the scene -mcrfpy.setScene("intro") - diff --git a/automation_example.py b/automation_example.py deleted file mode 100644 index 5d94dc4..0000000 --- a/automation_example.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/usr/bin/env python3 -""" -McRogueFace Automation API Example - -This demonstrates how to use the automation API for testing game UIs. -The API is PyAutoGUI-compatible for easy migration of existing tests. -""" - -from mcrfpy import automation -import mcrfpy -import time - -def automation_demo(): - """Demonstrate all automation API features""" - - print("=== McRogueFace Automation API Demo ===\n") - - # 1. Screen Information - print("1. Screen Information:") - screen_size = automation.size() - print(f" Screen size: {screen_size[0]}x{screen_size[1]}") - - mouse_pos = automation.position() - print(f" Current mouse position: {mouse_pos}") - - on_screen = automation.onScreen(100, 100) - print(f" Is (100, 100) on screen? {on_screen}") - print() - - # 2. Mouse Movement - print("2. Mouse Movement:") - print(" Moving to center of screen...") - center_x, center_y = screen_size[0]//2, screen_size[1]//2 - automation.moveTo(center_x, center_y, duration=0.5) - - print(" Moving relative by (100, 100)...") - automation.moveRel(100, 100, duration=0.5) - print() - - # 3. Mouse Clicks - print("3. Mouse Clicks:") - print(" Single click...") - automation.click() - time.sleep(0.2) - - print(" Double click...") - automation.doubleClick() - time.sleep(0.2) - - print(" Right click...") - automation.rightClick() - time.sleep(0.2) - - print(" Triple click...") - automation.tripleClick() - print() - - # 4. Keyboard Input - print("4. Keyboard Input:") - print(" Typing message...") - automation.typewrite("Hello from McRogueFace automation!", interval=0.05) - - print(" Pressing Enter...") - automation.keyDown("enter") - automation.keyUp("enter") - - print(" Hotkey Ctrl+A (select all)...") - automation.hotkey("ctrl", "a") - print() - - # 5. Drag Operations - print("5. Drag Operations:") - print(" Dragging from current position to (500, 500)...") - automation.dragTo(500, 500, duration=1.0) - - print(" Dragging relative by (-100, -100)...") - automation.dragRel(-100, -100, duration=0.5) - print() - - # 6. Scroll Operations - print("6. Scroll Operations:") - print(" Scrolling up 5 clicks...") - automation.scroll(5) - time.sleep(0.5) - - print(" Scrolling down 5 clicks...") - automation.scroll(-5) - print() - - # 7. Screenshots - print("7. Screenshots:") - print(" Taking screenshot...") - success = automation.screenshot("automation_demo_screenshot.png") - print(f" Screenshot saved: {success}") - print() - - print("=== Demo Complete ===") - -def create_test_ui(): - """Create a simple UI for testing automation""" - print("Creating test UI...") - - # Create a test scene - mcrfpy.createScene("automation_test") - mcrfpy.setScene("automation_test") - - # Add some UI elements - ui = mcrfpy.sceneUI("automation_test") - - # Add a frame - frame = mcrfpy.Frame(50, 50, 300, 200) - ui.append(frame) - - # Add a caption - caption = mcrfpy.Caption(60, 60, "Automation Test UI") - ui.append(caption) - - print("Test UI created!") - -if __name__ == "__main__": - # Create test UI first - create_test_ui() - - # Run automation demo - automation_demo() - - print("\nYou can now use the automation API to test your game!") \ No newline at end of file diff --git a/automation_exec_examples.py b/automation_exec_examples.py deleted file mode 100644 index 1145d2b..0000000 --- a/automation_exec_examples.py +++ /dev/null @@ -1,336 +0,0 @@ -#!/usr/bin/env python3 -""" -Examples of automation patterns using the proposed --exec flag - -Usage: - ./mcrogueface game.py --exec automation_basic.py - ./mcrogueface game.py --exec automation_stress.py --exec monitor.py -""" - -# ===== automation_basic.py ===== -# Basic automation that runs alongside the game - -import mcrfpy -from mcrfpy import automation -import time - -class GameAutomation: - """Automated testing that runs periodically""" - - def __init__(self): - self.test_count = 0 - self.test_results = [] - - def run_test_suite(self): - """Called by timer - runs one test per invocation""" - test_name = f"test_{self.test_count}" - - try: - if self.test_count == 0: - # Test main menu - self.test_main_menu() - elif self.test_count == 1: - # Test inventory - self.test_inventory() - elif self.test_count == 2: - # Test combat - self.test_combat() - else: - # All tests complete - self.report_results() - return - - self.test_results.append((test_name, "PASS")) - except Exception as e: - self.test_results.append((test_name, f"FAIL: {e}")) - - self.test_count += 1 - - def test_main_menu(self): - """Test main menu interactions""" - automation.screenshot("test_main_menu_before.png") - automation.click(400, 300) # New Game button - time.sleep(0.5) - automation.screenshot("test_main_menu_after.png") - - def test_inventory(self): - """Test inventory system""" - automation.hotkey("i") # Open inventory - time.sleep(0.5) - automation.screenshot("test_inventory_open.png") - - # Drag item - automation.moveTo(100, 200) - automation.dragTo(200, 200, duration=0.5) - - automation.hotkey("i") # Close inventory - - def test_combat(self): - """Test combat system""" - # Move character - automation.keyDown("w") - time.sleep(0.5) - automation.keyUp("w") - - # Attack - automation.click(500, 400) - automation.screenshot("test_combat.png") - - def report_results(self): - """Generate test report""" - print("\n=== Automation Test Results ===") - for test, result in self.test_results: - print(f"{test}: {result}") - print(f"Total: {len(self.test_results)} tests") - - # Stop the timer - mcrfpy.delTimer("automation_suite") - -# Create automation instance and register timer -auto = GameAutomation() -mcrfpy.setTimer("automation_suite", auto.run_test_suite, 2000) # Run every 2 seconds - -print("Game automation started - tests will run every 2 seconds") - - -# ===== automation_stress.py ===== -# Stress testing with random inputs - -import mcrfpy -from mcrfpy import automation -import random - -class StressTester: - """Randomly interact with the game to find edge cases""" - - def __init__(self): - self.action_count = 0 - self.errors = [] - - def random_action(self): - """Perform a random UI action""" - try: - action = random.choice([ - self.random_click, - self.random_key, - self.random_drag, - self.random_hotkey - ]) - action() - self.action_count += 1 - - # Periodic screenshot - if self.action_count % 50 == 0: - automation.screenshot(f"stress_test_{self.action_count}.png") - print(f"Stress test: {self.action_count} actions performed") - - except Exception as e: - self.errors.append((self.action_count, str(e))) - - def random_click(self): - x = random.randint(0, 1024) - y = random.randint(0, 768) - button = random.choice(["left", "right"]) - automation.click(x, y, button=button) - - def random_key(self): - key = random.choice([ - "a", "b", "c", "d", "w", "s", - "space", "enter", "escape", - "1", "2", "3", "4", "5" - ]) - automation.keyDown(key) - automation.keyUp(key) - - def random_drag(self): - x1 = random.randint(0, 1024) - y1 = random.randint(0, 768) - x2 = random.randint(0, 1024) - y2 = random.randint(0, 768) - automation.moveTo(x1, y1) - automation.dragTo(x2, y2, duration=0.2) - - def random_hotkey(self): - modifier = random.choice(["ctrl", "alt", "shift"]) - key = random.choice(["a", "s", "d", "f"]) - automation.hotkey(modifier, key) - -# Create stress tester and run frequently -stress = StressTester() -mcrfpy.setTimer("stress_test", stress.random_action, 100) # Every 100ms - -print("Stress testing started - random actions every 100ms") - - -# ===== monitor.py ===== -# Performance and state monitoring - -import mcrfpy -from mcrfpy import automation -import json -import time - -class PerformanceMonitor: - """Monitor game performance and state""" - - def __init__(self): - self.samples = [] - self.start_time = time.time() - - def collect_sample(self): - """Collect performance data""" - sample = { - "timestamp": time.time() - self.start_time, - "fps": mcrfpy.getFPS() if hasattr(mcrfpy, 'getFPS') else 60, - "scene": mcrfpy.currentScene(), - "memory": self.estimate_memory_usage() - } - self.samples.append(sample) - - # Log every 10 samples - if len(self.samples) % 10 == 0: - avg_fps = sum(s["fps"] for s in self.samples[-10:]) / 10 - print(f"Average FPS (last 10 samples): {avg_fps:.1f}") - - # Save data every 100 samples - if len(self.samples) % 100 == 0: - self.save_report() - - def estimate_memory_usage(self): - """Estimate memory usage based on scene complexity""" - # This is a placeholder - real implementation would use psutil - ui_count = len(mcrfpy.sceneUI(mcrfpy.currentScene())) - return ui_count * 1000 # Rough estimate in KB - - def save_report(self): - """Save performance report""" - with open("performance_report.json", "w") as f: - json.dump({ - "samples": self.samples, - "summary": { - "total_samples": len(self.samples), - "duration": time.time() - self.start_time, - "avg_fps": sum(s["fps"] for s in self.samples) / len(self.samples) - } - }, f, indent=2) - print(f"Performance report saved ({len(self.samples)} samples)") - -# Create monitor and start collecting -monitor = PerformanceMonitor() -mcrfpy.setTimer("performance_monitor", monitor.collect_sample, 1000) # Every second - -print("Performance monitoring started - sampling every second") - - -# ===== automation_replay.py ===== -# Record and replay user actions - -import mcrfpy -from mcrfpy import automation -import json -import time - -class ActionRecorder: - """Record user actions for replay""" - - def __init__(self): - self.recording = False - self.actions = [] - self.start_time = None - - def start_recording(self): - """Start recording user actions""" - self.recording = True - self.actions = [] - self.start_time = time.time() - print("Recording started - perform actions to record") - - # Register callbacks for all input types - mcrfpy.registerPyAction("record_click", self.record_click) - mcrfpy.registerPyAction("record_key", self.record_key) - - # Map all mouse buttons - for button in range(3): - mcrfpy.registerInputAction(8192 + button, "record_click") - - # Map common keys - for key in range(256): - mcrfpy.registerInputAction(4096 + key, "record_key") - - def record_click(self, action_type): - """Record mouse click""" - if not self.recording or action_type != "start": - return - - pos = automation.position() - self.actions.append({ - "type": "click", - "time": time.time() - self.start_time, - "x": pos[0], - "y": pos[1] - }) - - def record_key(self, action_type): - """Record key press""" - if not self.recording or action_type != "start": - return - - # This is simplified - real implementation would decode the key - self.actions.append({ - "type": "key", - "time": time.time() - self.start_time, - "key": "unknown" - }) - - def stop_recording(self): - """Stop recording and save""" - self.recording = False - with open("recorded_actions.json", "w") as f: - json.dump(self.actions, f, indent=2) - print(f"Recording stopped - {len(self.actions)} actions saved") - - def replay_actions(self): - """Replay recorded actions""" - print("Replaying recorded actions...") - - with open("recorded_actions.json", "r") as f: - actions = json.load(f) - - start_time = time.time() - action_index = 0 - - def replay_next(): - nonlocal action_index - if action_index >= len(actions): - print("Replay complete") - mcrfpy.delTimer("replay") - return - - action = actions[action_index] - current_time = time.time() - start_time - - # Wait until it's time for this action - if current_time >= action["time"]: - if action["type"] == "click": - automation.click(action["x"], action["y"]) - elif action["type"] == "key": - automation.keyDown(action["key"]) - automation.keyUp(action["key"]) - - action_index += 1 - - mcrfpy.setTimer("replay", replay_next, 10) # Check every 10ms - -# Example usage - would be controlled by UI -recorder = ActionRecorder() - -# To start recording: -# recorder.start_recording() - -# To stop and save: -# recorder.stop_recording() - -# To replay: -# recorder.replay_actions() - -print("Action recorder ready - call recorder.start_recording() to begin") \ No newline at end of file diff --git a/build.sh b/build.sh deleted file mode 100755 index 790bba8..0000000 --- a/build.sh +++ /dev/null @@ -1,54 +0,0 @@ -#!/bin/bash -# Build script for McRogueFace - compiles everything into ./build directory - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -echo -e "${GREEN}McRogueFace Build Script${NC}" -echo "=========================" - -# Create build directory if it doesn't exist -if [ ! -d "build" ]; then - echo -e "${YELLOW}Creating build directory...${NC}" - mkdir build -fi - -# Change to build directory -cd build - -# Run CMake to generate build files -echo -e "${YELLOW}Running CMake...${NC}" -cmake .. -DCMAKE_BUILD_TYPE=Release - -# Check if CMake succeeded -if [ $? -ne 0 ]; then - echo -e "${RED}CMake configuration failed!${NC}" - exit 1 -fi - -# Run make with parallel jobs -echo -e "${YELLOW}Building with make...${NC}" -make -j$(nproc) - -# Check if make succeeded -if [ $? -ne 0 ]; then - echo -e "${RED}Build failed!${NC}" - exit 1 -fi - -echo -e "${GREEN}Build completed successfully!${NC}" -echo "" -echo "The build directory contains:" -ls -la - -echo "" -echo -e "${GREEN}To run McRogueFace:${NC}" -echo " cd build" -echo " ./mcrogueface" -echo "" -echo -e "${GREEN}To create a distribution archive:${NC}" -echo " cd build" -echo " zip -r ../McRogueFace-$(date +%Y%m%d).zip ." \ No newline at end of file diff --git a/clean.sh b/clean.sh deleted file mode 100755 index 817a9ee..0000000 --- a/clean.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash -# Clean script for McRogueFace - removes build artifacts - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -echo -e "${YELLOW}Cleaning McRogueFace build artifacts...${NC}" - -# Remove build directory -if [ -d "build" ]; then - echo "Removing build directory..." - rm -rf build -fi - -# Remove CMake artifacts from project root -echo "Removing CMake artifacts from project root..." -rm -f CMakeCache.txt -rm -f cmake_install.cmake -rm -f Makefile -rm -rf CMakeFiles - -# Remove compiled executable from project root -rm -f mcrogueface - -# Remove any test artifacts -rm -f test_script.py -rm -rf test_venv -rm -f python3 # symlink - -echo -e "${GREEN}Clean complete!${NC}" \ No newline at end of file diff --git a/compile_commands.json b/compile_commands.json deleted file mode 100644 index 6f16280..0000000 --- a/compile_commands.json +++ /dev/null @@ -1,112 +0,0 @@ -[ -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/GameEngine.cpp.o -c /home/john/Development/McRogueFace/src/GameEngine.cpp", - "file": "/home/john/Development/McRogueFace/src/GameEngine.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/IndexTexture.cpp.o -c /home/john/Development/McRogueFace/src/IndexTexture.cpp", - "file": "/home/john/Development/McRogueFace/src/IndexTexture.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/McRFPy_API.cpp.o -c /home/john/Development/McRogueFace/src/McRFPy_API.cpp", - "file": "/home/john/Development/McRogueFace/src/McRFPy_API.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/PyCallable.cpp.o -c /home/john/Development/McRogueFace/src/PyCallable.cpp", - "file": "/home/john/Development/McRogueFace/src/PyCallable.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/PyColor.cpp.o -c /home/john/Development/McRogueFace/src/PyColor.cpp", - "file": "/home/john/Development/McRogueFace/src/PyColor.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/PyFont.cpp.o -c /home/john/Development/McRogueFace/src/PyFont.cpp", - "file": "/home/john/Development/McRogueFace/src/PyFont.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/PyScene.cpp.o -c /home/john/Development/McRogueFace/src/PyScene.cpp", - "file": "/home/john/Development/McRogueFace/src/PyScene.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/PyTexture.cpp.o -c /home/john/Development/McRogueFace/src/PyTexture.cpp", - "file": "/home/john/Development/McRogueFace/src/PyTexture.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/PyVector.cpp.o -c /home/john/Development/McRogueFace/src/PyVector.cpp", - "file": "/home/john/Development/McRogueFace/src/PyVector.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/Resources.cpp.o -c /home/john/Development/McRogueFace/src/Resources.cpp", - "file": "/home/john/Development/McRogueFace/src/Resources.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/Scene.cpp.o -c /home/john/Development/McRogueFace/src/Scene.cpp", - "file": "/home/john/Development/McRogueFace/src/Scene.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/Timer.cpp.o -c /home/john/Development/McRogueFace/src/Timer.cpp", - "file": "/home/john/Development/McRogueFace/src/Timer.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UICaption.cpp.o -c /home/john/Development/McRogueFace/src/UICaption.cpp", - "file": "/home/john/Development/McRogueFace/src/UICaption.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UICollection.cpp.o -c /home/john/Development/McRogueFace/src/UICollection.cpp", - "file": "/home/john/Development/McRogueFace/src/UICollection.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UIDrawable.cpp.o -c /home/john/Development/McRogueFace/src/UIDrawable.cpp", - "file": "/home/john/Development/McRogueFace/src/UIDrawable.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UIEntity.cpp.o -c /home/john/Development/McRogueFace/src/UIEntity.cpp", - "file": "/home/john/Development/McRogueFace/src/UIEntity.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UIFrame.cpp.o -c /home/john/Development/McRogueFace/src/UIFrame.cpp", - "file": "/home/john/Development/McRogueFace/src/UIFrame.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UIGrid.cpp.o -c /home/john/Development/McRogueFace/src/UIGrid.cpp", - "file": "/home/john/Development/McRogueFace/src/UIGrid.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UIGridPoint.cpp.o -c /home/john/Development/McRogueFace/src/UIGridPoint.cpp", - "file": "/home/john/Development/McRogueFace/src/UIGridPoint.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UISprite.cpp.o -c /home/john/Development/McRogueFace/src/UISprite.cpp", - "file": "/home/john/Development/McRogueFace/src/UISprite.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UITestScene.cpp.o -c /home/john/Development/McRogueFace/src/UITestScene.cpp", - "file": "/home/john/Development/McRogueFace/src/UITestScene.cpp" -}, -{ - "directory": "/home/john/Development/McRogueFace/build", - "command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/main.cpp.o -c /home/john/Development/McRogueFace/src/main.cpp", - "file": "/home/john/Development/McRogueFace/src/main.cpp" -} -] \ No newline at end of file diff --git a/debug_immediate.png b/debug_immediate.png deleted file mode 100644 index a61c929..0000000 Binary files a/debug_immediate.png and /dev/null differ diff --git a/debug_multi_0.png b/debug_multi_0.png deleted file mode 100644 index a61c929..0000000 Binary files a/debug_multi_0.png and /dev/null differ diff --git a/debug_multi_1.png b/debug_multi_1.png deleted file mode 100644 index a61c929..0000000 Binary files a/debug_multi_1.png and /dev/null differ diff --git a/debug_multi_2.png b/debug_multi_2.png deleted file mode 100644 index a61c929..0000000 Binary files a/debug_multi_2.png and /dev/null differ diff --git a/example_automation.py b/example_automation.py deleted file mode 100644 index a31375a..0000000 --- a/example_automation.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python3 -""" -Example automation script using --exec flag -Usage: ./mcrogueface game.py --exec example_automation.py -""" -import mcrfpy -from mcrfpy import automation - -class GameAutomation: - def __init__(self): - self.frame_count = 0 - self.test_phase = 0 - print("Automation: Initialized") - - def periodic_test(self): - """Called every second to perform automation tasks""" - self.frame_count = mcrfpy.getFrame() - - print(f"Automation: Running test at frame {self.frame_count}") - - # Take periodic screenshots - if self.test_phase % 5 == 0: - filename = f"automation_screenshot_{self.test_phase}.png" - automation.screenshot(filename) - print(f"Automation: Saved {filename}") - - # Simulate user input based on current scene - scene = mcrfpy.currentScene() - print(f"Automation: Current scene is '{scene}'") - - if scene == "main_menu" and self.test_phase < 5: - # Click start button - automation.click(512, 400) - print("Automation: Clicked start button") - elif scene == "game": - # Perform game actions - if self.test_phase % 3 == 0: - automation.hotkey("i") # Toggle inventory - print("Automation: Toggled inventory") - else: - # Random movement - import random - key = random.choice(["w", "a", "s", "d"]) - automation.keyDown(key) - automation.keyUp(key) - print(f"Automation: Pressed '{key}' key") - - self.test_phase += 1 - - # Stop after 20 tests - if self.test_phase >= 20: - print("Automation: Test suite complete") - mcrfpy.delTimer("automation_test") - # Could also call mcrfpy.quit() to exit the game - -# Create automation instance -automation_instance = GameAutomation() - -# Register periodic timer -mcrfpy.setTimer("automation_test", automation_instance.periodic_test, 1000) - -print("Automation: Script loaded - tests will run every second") -print("Automation: The game and automation share the same Python environment") \ No newline at end of file diff --git a/example_config.py b/example_config.py deleted file mode 100644 index 0f0ef7e..0000000 --- a/example_config.py +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env python3 -""" -Example configuration script that sets up shared state for other scripts -Usage: ./mcrogueface --exec example_config.py --exec example_automation.py game.py -""" -import mcrfpy - -# Create a shared configuration namespace -class AutomationConfig: - # Test settings - test_enabled = True - screenshot_interval = 5 # Take screenshot every N tests - max_test_count = 50 - test_delay_ms = 1000 - - # Monitoring settings - monitor_enabled = True - monitor_interval_ms = 500 - report_delay_seconds = 30 - - # Game-specific settings - start_button_pos = (512, 400) - inventory_key = "i" - movement_keys = ["w", "a", "s", "d"] - - # Shared state - test_results = [] - performance_data = [] - - @classmethod - def log_result(cls, test_name, success, details=""): - """Log a test result""" - cls.test_results.append({ - "test": test_name, - "success": success, - "details": details, - "frame": mcrfpy.getFrame() - }) - - @classmethod - def get_summary(cls): - """Get test summary""" - total = len(cls.test_results) - passed = sum(1 for r in cls.test_results if r["success"]) - return f"Tests: {passed}/{total} passed" - -# Attach config to mcrfpy module so other scripts can access it -mcrfpy.automation_config = AutomationConfig - -print("Config: Automation configuration loaded") -print(f"Config: Test delay = {AutomationConfig.test_delay_ms}ms") -print(f"Config: Max tests = {AutomationConfig.max_test_count}") -print("Config: Other scripts can access config via mcrfpy.automation_config") \ No newline at end of file diff --git a/example_monitoring.py b/example_monitoring.py deleted file mode 100644 index 13e98cb..0000000 --- a/example_monitoring.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env python3 -""" -Example monitoring script that works alongside automation -Usage: ./mcrogueface game.py --exec example_automation.py --exec example_monitoring.py -""" -import mcrfpy -import time - -class PerformanceMonitor: - def __init__(self): - self.start_time = time.time() - self.frame_samples = [] - self.scene_changes = [] - self.last_scene = None - print("Monitor: Performance monitoring initialized") - - def collect_metrics(self): - """Collect performance and state metrics""" - current_frame = mcrfpy.getFrame() - current_time = time.time() - self.start_time - current_scene = mcrfpy.currentScene() - - # Track frame rate - if len(self.frame_samples) > 0: - last_frame, last_time = self.frame_samples[-1] - fps = (current_frame - last_frame) / (current_time - last_time) - print(f"Monitor: FPS = {fps:.1f}") - - self.frame_samples.append((current_frame, current_time)) - - # Track scene changes - if current_scene != self.last_scene: - print(f"Monitor: Scene changed from '{self.last_scene}' to '{current_scene}'") - self.scene_changes.append((current_time, self.last_scene, current_scene)) - self.last_scene = current_scene - - # Keep only last 100 samples - if len(self.frame_samples) > 100: - self.frame_samples = self.frame_samples[-100:] - - def generate_report(self): - """Generate a summary report""" - if len(self.frame_samples) < 2: - return - - total_frames = self.frame_samples[-1][0] - self.frame_samples[0][0] - total_time = self.frame_samples[-1][1] - self.frame_samples[0][1] - avg_fps = total_frames / total_time - - print("\n=== Performance Report ===") - print(f"Monitor: Total time: {total_time:.1f} seconds") - print(f"Monitor: Total frames: {total_frames}") - print(f"Monitor: Average FPS: {avg_fps:.1f}") - print(f"Monitor: Scene changes: {len(self.scene_changes)}") - - # Stop monitoring - mcrfpy.delTimer("performance_monitor") - -# Create monitor instance -monitor = PerformanceMonitor() - -# Register monitoring timer (runs every 500ms) -mcrfpy.setTimer("performance_monitor", monitor.collect_metrics, 500) - -# Register report generation (runs after 30 seconds) -mcrfpy.setTimer("performance_report", monitor.generate_report, 30000) - -print("Monitor: Script loaded - collecting metrics every 500ms") -print("Monitor: Will generate report after 30 seconds") \ No newline at end of file diff --git a/exec_flag_implementation.cpp b/exec_flag_implementation.cpp deleted file mode 100644 index 3173585..0000000 --- a/exec_flag_implementation.cpp +++ /dev/null @@ -1,189 +0,0 @@ -// Example implementation of --exec flag for McRogueFace -// This shows the minimal changes needed to support multiple script execution - -// === In McRogueFaceConfig.h === -struct McRogueFaceConfig { - // ... existing fields ... - - // Scripts to execute after main script (McRogueFace style) - std::vector exec_scripts; -}; - -// === In CommandLineParser.cpp === -CommandLineParser::ParseResult CommandLineParser::parse(McRogueFaceConfig& config) { - // ... existing parsing code ... - - for (int i = 1; i < argc; i++) { - std::string arg = argv[i]; - - // ... existing flag handling ... - - else if (arg == "--exec") { - // Add script to exec list - if (i + 1 < argc) { - config.exec_scripts.push_back(argv[++i]); - } else { - std::cerr << "Error: --exec requires a script path\n"; - return {true, 1}; - } - } - } -} - -// === In GameEngine.cpp === -GameEngine::GameEngine(const McRogueFaceConfig& cfg) : config(cfg) { - // ... existing initialization ... - - // Only load game.py if no custom script/command/module is specified - bool should_load_game = config.script_path.empty() && - config.python_command.empty() && - config.python_module.empty() && - !config.interactive_mode && - !config.python_mode && - config.exec_scripts.empty(); // Add this check - - if (should_load_game) { - if (!Py_IsInitialized()) { - McRFPy_API::api_init(); - } - McRFPy_API::executePyString("import mcrfpy"); - McRFPy_API::executeScript("scripts/game.py"); - } - - // Execute any --exec scripts - for (const auto& exec_script : config.exec_scripts) { - std::cout << "Executing script: " << exec_script << std::endl; - McRFPy_API::executeScript(exec_script.string()); - } -} - -// === Usage Examples === - -// Example 1: Run game with automation -// ./mcrogueface game.py --exec automation.py - -// Example 2: Run game with multiple automation scripts -// ./mcrogueface game.py --exec test_suite.py --exec monitor.py --exec logger.py - -// Example 3: Run only automation (no game) -// ./mcrogueface --exec standalone_test.py - -// Example 4: Headless automation -// ./mcrogueface --headless game.py --exec automation.py - -// === Python Script Example (automation.py) === -/* -import mcrfpy -from mcrfpy import automation - -def periodic_test(): - """Run automated tests every 5 seconds""" - # Take screenshot - automation.screenshot(f"test_{mcrfpy.getFrame()}.png") - - # Check game state - scene = mcrfpy.currentScene() - if scene == "main_menu": - # Click start button - automation.click(400, 300) - elif scene == "game": - # Perform game tests - automation.hotkey("i") # Open inventory - - print(f"Test completed at frame {mcrfpy.getFrame()}") - -# Register timer for periodic testing -mcrfpy.setTimer("automation_test", periodic_test, 5000) - -print("Automation script loaded - tests will run every 5 seconds") - -# Script returns here - giving control back to C++ -*/ - -// === Advanced Example: Event-Driven Automation === -/* -# automation_advanced.py - -import mcrfpy -from mcrfpy import automation -import json - -class AutomationFramework: - def __init__(self): - self.test_queue = [] - self.results = [] - self.load_test_suite() - - def load_test_suite(self): - """Load test definitions from JSON""" - with open("test_suite.json") as f: - self.test_queue = json.load(f)["tests"] - - def run_next_test(self): - """Execute next test in queue""" - if not self.test_queue: - self.finish_testing() - return - - test = self.test_queue.pop(0) - - try: - if test["type"] == "click": - automation.click(test["x"], test["y"]) - elif test["type"] == "key": - automation.keyDown(test["key"]) - automation.keyUp(test["key"]) - elif test["type"] == "screenshot": - automation.screenshot(test["filename"]) - elif test["type"] == "wait": - # Re-queue this test for later - self.test_queue.insert(0, test) - return - - self.results.append({"test": test, "status": "pass"}) - except Exception as e: - self.results.append({"test": test, "status": "fail", "error": str(e)}) - - def finish_testing(self): - """Save test results and cleanup""" - with open("test_results.json", "w") as f: - json.dump(self.results, f, indent=2) - print(f"Testing complete: {len(self.results)} tests executed") - mcrfpy.delTimer("automation_framework") - -# Create and start automation -framework = AutomationFramework() -mcrfpy.setTimer("automation_framework", framework.run_next_test, 100) -*/ - -// === Thread Safety Considerations === - -// The --exec approach requires NO thread safety changes because: -// 1. All scripts run in the same Python interpreter -// 2. Scripts execute sequentially during initialization -// 3. After initialization, only callbacks run (timer/input based) -// 4. C++ maintains control of the render loop - -// This is the "honor system" - scripts must: -// - Set up their callbacks/timers -// - Return control to C++ -// - Not block or run infinite loops -// - Use timers for periodic tasks - -// === Future Extensions === - -// 1. Script communication via shared Python modules -// game.py: -// import mcrfpy -// mcrfpy.game_state = {"level": 1, "score": 0} -// -// automation.py: -// import mcrfpy -// if mcrfpy.game_state["level"] == 1: -// # Test level 1 specific features - -// 2. Priority-based script execution -// ./mcrogueface game.py --exec-priority high:critical.py --exec-priority low:logging.py - -// 3. Conditional execution -// ./mcrogueface game.py --exec-if-scene menu:menu_test.py --exec-if-scene game:game_test.py \ No newline at end of file diff --git a/gitea_issues.py b/gitea_issues.py deleted file mode 100644 index 9ba8bd9..0000000 --- a/gitea_issues.py +++ /dev/null @@ -1,102 +0,0 @@ -import json -from time import time -#with open("/home/john/issues.json", "r") as f: -# data = json.loads(f.read()) -#with open("/home/john/issues2.json", "r") as f: -# data.extend(json.loads(f.read())) - -print("Fetching issues...", end='') -start = time() -from gitea import Gitea, Repository, Issue -g = Gitea("https://gamedev.ffwf.net/gitea", token_text="3b450f66e21d62c22bb9fa1c8b975049a5d0c38d") -repo = Repository.request(g, "john", "McRogueFace") -issues = repo.get_issues() -dur = time() - start -print(f"({dur:.1f}s)") -print("Gitea Version: " + g.get_version()) -print("API-Token belongs to user: " + g.get_user().username) - -data = [ - { - "labels": i.labels, - "body": i.body, - "number": i.number, - } - for i in issues - ] - -input() - -def front_number(txt): - if not txt[0].isdigit(): return None - number = "" - for c in txt: - if not c.isdigit(): - break - number += c - return int(number) - -def split_any(txt, splitters): - tokens = [] - txt = [txt] - for s in splitters: - for t in txt: - tokens.extend(t.split(s)) - txt = tokens - tokens = [] - return txt - -def find_refs(txt): - tokens = [tok for tok in split_any(txt, ' ,;\t\r\n') if tok.startswith('#')] - return [front_number(tok[1:]) for tok in tokens] - -from collections import defaultdict -issue_relations = defaultdict(list) - -nodes = set() - -for issue in data: - #refs = issue['body'].split('#')[1::2] - - #refs = [front_number(r) for r in refs if front_number(r) is not None] - refs = find_refs(issue['body']) - print(issue['number'], ':', refs) - issue_relations[issue['number']].extend(refs) - nodes.add(issue['number']) - for r in refs: - nodes.add(r) - issue_relations[r].append(issue['number']) - - -# Find issue labels -issue_labels = {} -for d in data: - labels = [l['name'] for l in d['labels']] - #print(d['number'], labels) - issue_labels[d['number']] = labels - -import networkx as nx -import matplotlib.pyplot as plt - -relations = nx.Graph() - -for k in issue_relations: - relations.add_node(k) - for r in issue_relations[k]: - relations.add_edge(k, r) - relations.add_edge(r, k) - -#nx.draw_networkx(relations) - -pos = nx.spring_layout(relations) -nx.draw_networkx_nodes(relations, pos, - nodelist = [n for n in issue_labels if 'Alpha Release Requirement' in issue_labels[n]], - node_color="tab:red") -nx.draw_networkx_nodes(relations, pos, - nodelist = [n for n in issue_labels if 'Alpha Release Requirement' not in issue_labels[n]], - node_color="tab:blue") -nx.draw_networkx_edges(relations, pos, - edgelist = relations.edges() - ) -nx.draw_networkx_labels(relations, pos, {i: str(i) for i in relations.nodes()}) -plt.show() \ No newline at end of file diff --git a/grid_none_texture_test_197.png b/grid_none_texture_test_197.png deleted file mode 100644 index fe3210d..0000000 Binary files a/grid_none_texture_test_197.png and /dev/null differ diff --git a/issue78_fixed_1658.png b/issue78_fixed_1658.png deleted file mode 100644 index 1e7680a..0000000 Binary files a/issue78_fixed_1658.png and /dev/null differ diff --git a/screenshot_opaque_fix_20250703_174829.png b/screenshot_opaque_fix_20250703_174829.png deleted file mode 100644 index a61c929..0000000 Binary files a/screenshot_opaque_fix_20250703_174829.png and /dev/null differ diff --git a/src/ActionCode.h b/src/ActionCode.h index 1adaf99..36aca07 100644 --- a/src/ActionCode.h +++ b/src/ActionCode.h @@ -11,10 +11,10 @@ public: const static int WHEEL_NUM = 4; const static int WHEEL_NEG = 2; const static int WHEEL_DEL = 1; - static int keycode(const sf::Keyboard::Key& k) { return KEY + (int)k; } - static int keycode(const sf::Mouse::Button& b) { return MOUSEBUTTON + (int)b; } + static int keycode(sf::Keyboard::Key& k) { return KEY + (int)k; } + static int keycode(sf::Mouse::Button& b) { return MOUSEBUTTON + (int)b; } //static int keycode(sf::Mouse::Wheel& w, float d) { return MOUSEWHEEL + (((int)w)<<12) + int(d*16) + 512; } - static int keycode(const sf::Mouse::Wheel& w, float d) { + static int keycode(sf::Mouse::Wheel& w, float d) { int neg = 0; if (d < 0) { neg = 1; } return MOUSEWHEEL + (w * WHEEL_NUM) + (neg * WHEEL_NEG) + 1; @@ -32,7 +32,7 @@ public: return (a & WHEEL_DEL) * factor; } - static std::string key_str(const sf::Keyboard::Key& keycode) + static std::string key_str(sf::Keyboard::Key& keycode) { switch(keycode) { diff --git a/src/Animation.cpp b/src/Animation.cpp deleted file mode 100644 index 7fa27ce..0000000 --- a/src/Animation.cpp +++ /dev/null @@ -1,527 +0,0 @@ -#include "Animation.h" -#include "UIDrawable.h" -#include "UIEntity.h" -#include -#include -#include - -#ifndef M_PI -#define M_PI 3.14159265358979323846 -#endif - -// Animation implementation -Animation::Animation(const std::string& targetProperty, - const AnimationValue& targetValue, - float duration, - EasingFunction easingFunc, - bool delta) - : targetProperty(targetProperty) - , targetValue(targetValue) - , duration(duration) - , easingFunc(easingFunc) - , delta(delta) -{ -} - -void Animation::start(UIDrawable* target) { - currentTarget = target; - elapsed = 0.0f; - - // Capture startValue from target based on targetProperty - if (!currentTarget) return; - - // Try to get the current value based on the expected type - std::visit([this](const auto& targetVal) { - using T = std::decay_t; - - if constexpr (std::is_same_v) { - float value; - if (currentTarget->getProperty(targetProperty, value)) { - startValue = value; - } - } - else if constexpr (std::is_same_v) { - int value; - if (currentTarget->getProperty(targetProperty, value)) { - startValue = value; - } - } - else if constexpr (std::is_same_v>) { - // For sprite animation, get current sprite index - int value; - if (currentTarget->getProperty(targetProperty, value)) { - startValue = value; - } - } - else if constexpr (std::is_same_v) { - sf::Color value; - if (currentTarget->getProperty(targetProperty, value)) { - startValue = value; - } - } - else if constexpr (std::is_same_v) { - sf::Vector2f value; - if (currentTarget->getProperty(targetProperty, value)) { - startValue = value; - } - } - else if constexpr (std::is_same_v) { - std::string value; - if (currentTarget->getProperty(targetProperty, value)) { - startValue = value; - } - } - }, targetValue); -} - -void Animation::startEntity(UIEntity* target) { - currentEntityTarget = target; - currentTarget = nullptr; // Clear drawable target - elapsed = 0.0f; - - // Capture the starting value from the entity - std::visit([this, target](const auto& val) { - using T = std::decay_t; - - if constexpr (std::is_same_v) { - float value = 0.0f; - if (target->getProperty(targetProperty, value)) { - startValue = value; - } - } - else if constexpr (std::is_same_v) { - // For entities, we might need to handle sprite_index differently - if (targetProperty == "sprite_index" || targetProperty == "sprite_number") { - startValue = target->sprite.getSpriteIndex(); - } - } - // Entities don't support other types yet - }, targetValue); -} - -bool Animation::update(float deltaTime) { - if ((!currentTarget && !currentEntityTarget) || isComplete()) { - return false; - } - - elapsed += deltaTime; - elapsed = std::min(elapsed, duration); - - // Calculate easing value (0.0 to 1.0) - float t = duration > 0 ? elapsed / duration : 1.0f; - float easedT = easingFunc(t); - - // Get interpolated value - AnimationValue currentValue = interpolate(easedT); - - // Apply currentValue to target (either drawable or entity) - std::visit([this](const auto& value) { - using T = std::decay_t; - - if (currentTarget) { - // Handle UIDrawable targets - if constexpr (std::is_same_v) { - currentTarget->setProperty(targetProperty, value); - } - else if constexpr (std::is_same_v) { - currentTarget->setProperty(targetProperty, value); - } - else if constexpr (std::is_same_v) { - currentTarget->setProperty(targetProperty, value); - } - else if constexpr (std::is_same_v) { - currentTarget->setProperty(targetProperty, value); - } - else if constexpr (std::is_same_v) { - currentTarget->setProperty(targetProperty, value); - } - } - else if (currentEntityTarget) { - // Handle UIEntity targets - if constexpr (std::is_same_v) { - currentEntityTarget->setProperty(targetProperty, value); - } - else if constexpr (std::is_same_v) { - currentEntityTarget->setProperty(targetProperty, value); - } - // Entities don't support other types yet - } - }, currentValue); - - return !isComplete(); -} - -AnimationValue Animation::getCurrentValue() const { - float t = duration > 0 ? elapsed / duration : 1.0f; - float easedT = easingFunc(t); - return interpolate(easedT); -} - -AnimationValue Animation::interpolate(float t) const { - // Visit the variant to perform type-specific interpolation - return std::visit([this, t](const auto& target) -> AnimationValue { - using T = std::decay_t; - - if constexpr (std::is_same_v) { - // Interpolate float - const float* start = std::get_if(&startValue); - if (!start) return target; // Type mismatch - - if (delta) { - return *start + target * t; - } else { - return *start + (target - *start) * t; - } - } - else if constexpr (std::is_same_v) { - // Interpolate integer - const int* start = std::get_if(&startValue); - if (!start) return target; - - float result; - if (delta) { - result = *start + target * t; - } else { - result = *start + (target - *start) * t; - } - return static_cast(std::round(result)); - } - else if constexpr (std::is_same_v>) { - // For sprite animation, interpolate through the list - if (target.empty()) return target; - - // Map t to an index in the vector - size_t index = static_cast(t * (target.size() - 1)); - index = std::min(index, target.size() - 1); - return static_cast(target[index]); - } - else if constexpr (std::is_same_v) { - // Interpolate color - const sf::Color* start = std::get_if(&startValue); - if (!start) return target; - - sf::Color result; - if (delta) { - result.r = std::clamp(start->r + target.r * t, 0.0f, 255.0f); - result.g = std::clamp(start->g + target.g * t, 0.0f, 255.0f); - result.b = std::clamp(start->b + target.b * t, 0.0f, 255.0f); - result.a = std::clamp(start->a + target.a * t, 0.0f, 255.0f); - } else { - result.r = start->r + (target.r - start->r) * t; - result.g = start->g + (target.g - start->g) * t; - result.b = start->b + (target.b - start->b) * t; - result.a = start->a + (target.a - start->a) * t; - } - return result; - } - else if constexpr (std::is_same_v) { - // Interpolate vector - const sf::Vector2f* start = std::get_if(&startValue); - if (!start) return target; - - if (delta) { - return sf::Vector2f(start->x + target.x * t, - start->y + target.y * t); - } else { - return sf::Vector2f(start->x + (target.x - start->x) * t, - start->y + (target.y - start->y) * t); - } - } - else if constexpr (std::is_same_v) { - // For text, show characters based on t - const std::string* start = std::get_if(&startValue); - if (!start) return target; - - // If delta mode, append characters from target - if (delta) { - size_t chars = static_cast(target.length() * t); - return *start + target.substr(0, chars); - } else { - // Transition from start text to target text - if (t < 0.5f) { - // First half: remove characters from start - size_t chars = static_cast(start->length() * (1.0f - t * 2.0f)); - return start->substr(0, chars); - } else { - // Second half: add characters to target - size_t chars = static_cast(target.length() * ((t - 0.5f) * 2.0f)); - return target.substr(0, chars); - } - } - } - - return target; // Fallback - }, targetValue); -} - -// Easing functions implementation -namespace EasingFunctions { - -float linear(float t) { - return t; -} - -float easeIn(float t) { - return t * t; -} - -float easeOut(float t) { - return t * (2.0f - t); -} - -float easeInOut(float t) { - return t < 0.5f ? 2.0f * t * t : -1.0f + (4.0f - 2.0f * t) * t; -} - -// Quadratic -float easeInQuad(float t) { - return t * t; -} - -float easeOutQuad(float t) { - return t * (2.0f - t); -} - -float easeInOutQuad(float t) { - return t < 0.5f ? 2.0f * t * t : -1.0f + (4.0f - 2.0f * t) * t; -} - -// Cubic -float easeInCubic(float t) { - return t * t * t; -} - -float easeOutCubic(float t) { - float t1 = t - 1.0f; - return t1 * t1 * t1 + 1.0f; -} - -float easeInOutCubic(float t) { - return t < 0.5f ? 4.0f * t * t * t : (t - 1.0f) * (2.0f * t - 2.0f) * (2.0f * t - 2.0f) + 1.0f; -} - -// Quartic -float easeInQuart(float t) { - return t * t * t * t; -} - -float easeOutQuart(float t) { - float t1 = t - 1.0f; - return 1.0f - t1 * t1 * t1 * t1; -} - -float easeInOutQuart(float t) { - return t < 0.5f ? 8.0f * t * t * t * t : 1.0f - 8.0f * (t - 1.0f) * (t - 1.0f) * (t - 1.0f) * (t - 1.0f); -} - -// Sine -float easeInSine(float t) { - return 1.0f - std::cos(t * M_PI / 2.0f); -} - -float easeOutSine(float t) { - return std::sin(t * M_PI / 2.0f); -} - -float easeInOutSine(float t) { - return 0.5f * (1.0f - std::cos(M_PI * t)); -} - -// Exponential -float easeInExpo(float t) { - return t == 0.0f ? 0.0f : std::pow(2.0f, 10.0f * (t - 1.0f)); -} - -float easeOutExpo(float t) { - return t == 1.0f ? 1.0f : 1.0f - std::pow(2.0f, -10.0f * t); -} - -float easeInOutExpo(float t) { - if (t == 0.0f) return 0.0f; - if (t == 1.0f) return 1.0f; - if (t < 0.5f) { - return 0.5f * std::pow(2.0f, 20.0f * t - 10.0f); - } else { - return 1.0f - 0.5f * std::pow(2.0f, -20.0f * t + 10.0f); - } -} - -// Circular -float easeInCirc(float t) { - return 1.0f - std::sqrt(1.0f - t * t); -} - -float easeOutCirc(float t) { - float t1 = t - 1.0f; - return std::sqrt(1.0f - t1 * t1); -} - -float easeInOutCirc(float t) { - if (t < 0.5f) { - return 0.5f * (1.0f - std::sqrt(1.0f - 4.0f * t * t)); - } else { - return 0.5f * (std::sqrt(1.0f - (2.0f * t - 2.0f) * (2.0f * t - 2.0f)) + 1.0f); - } -} - -// Elastic -float easeInElastic(float t) { - if (t == 0.0f) return 0.0f; - if (t == 1.0f) return 1.0f; - float p = 0.3f; - float a = 1.0f; - float s = p / 4.0f; - float t1 = t - 1.0f; - return -(a * std::pow(2.0f, 10.0f * t1) * std::sin((t1 - s) * (2.0f * M_PI) / p)); -} - -float easeOutElastic(float t) { - if (t == 0.0f) return 0.0f; - if (t == 1.0f) return 1.0f; - float p = 0.3f; - float a = 1.0f; - float s = p / 4.0f; - return a * std::pow(2.0f, -10.0f * t) * std::sin((t - s) * (2.0f * M_PI) / p) + 1.0f; -} - -float easeInOutElastic(float t) { - if (t == 0.0f) return 0.0f; - if (t == 1.0f) return 1.0f; - float p = 0.45f; - float a = 1.0f; - float s = p / 4.0f; - - if (t < 0.5f) { - float t1 = 2.0f * t - 1.0f; - return -0.5f * (a * std::pow(2.0f, 10.0f * t1) * std::sin((t1 - s) * (2.0f * M_PI) / p)); - } else { - float t1 = 2.0f * t - 1.0f; - return a * std::pow(2.0f, -10.0f * t1) * std::sin((t1 - s) * (2.0f * M_PI) / p) * 0.5f + 1.0f; - } -} - -// Back (overshooting) -float easeInBack(float t) { - const float s = 1.70158f; - return t * t * ((s + 1.0f) * t - s); -} - -float easeOutBack(float t) { - const float s = 1.70158f; - float t1 = t - 1.0f; - return t1 * t1 * ((s + 1.0f) * t1 + s) + 1.0f; -} - -float easeInOutBack(float t) { - const float s = 1.70158f * 1.525f; - if (t < 0.5f) { - return 0.5f * (4.0f * t * t * ((s + 1.0f) * 2.0f * t - s)); - } else { - float t1 = 2.0f * t - 2.0f; - return 0.5f * (t1 * t1 * ((s + 1.0f) * t1 + s) + 2.0f); - } -} - -// Bounce -float easeOutBounce(float t) { - if (t < 1.0f / 2.75f) { - return 7.5625f * t * t; - } else if (t < 2.0f / 2.75f) { - float t1 = t - 1.5f / 2.75f; - return 7.5625f * t1 * t1 + 0.75f; - } else if (t < 2.5f / 2.75f) { - float t1 = t - 2.25f / 2.75f; - return 7.5625f * t1 * t1 + 0.9375f; - } else { - float t1 = t - 2.625f / 2.75f; - return 7.5625f * t1 * t1 + 0.984375f; - } -} - -float easeInBounce(float t) { - return 1.0f - easeOutBounce(1.0f - t); -} - -float easeInOutBounce(float t) { - if (t < 0.5f) { - return 0.5f * easeInBounce(2.0f * t); - } else { - return 0.5f * easeOutBounce(2.0f * t - 1.0f) + 0.5f; - } -} - -// Get easing function by name -EasingFunction getByName(const std::string& name) { - static std::unordered_map easingMap = { - {"linear", linear}, - {"easeIn", easeIn}, - {"easeOut", easeOut}, - {"easeInOut", easeInOut}, - {"easeInQuad", easeInQuad}, - {"easeOutQuad", easeOutQuad}, - {"easeInOutQuad", easeInOutQuad}, - {"easeInCubic", easeInCubic}, - {"easeOutCubic", easeOutCubic}, - {"easeInOutCubic", easeInOutCubic}, - {"easeInQuart", easeInQuart}, - {"easeOutQuart", easeOutQuart}, - {"easeInOutQuart", easeInOutQuart}, - {"easeInSine", easeInSine}, - {"easeOutSine", easeOutSine}, - {"easeInOutSine", easeInOutSine}, - {"easeInExpo", easeInExpo}, - {"easeOutExpo", easeOutExpo}, - {"easeInOutExpo", easeInOutExpo}, - {"easeInCirc", easeInCirc}, - {"easeOutCirc", easeOutCirc}, - {"easeInOutCirc", easeInOutCirc}, - {"easeInElastic", easeInElastic}, - {"easeOutElastic", easeOutElastic}, - {"easeInOutElastic", easeInOutElastic}, - {"easeInBack", easeInBack}, - {"easeOutBack", easeOutBack}, - {"easeInOutBack", easeInOutBack}, - {"easeInBounce", easeInBounce}, - {"easeOutBounce", easeOutBounce}, - {"easeInOutBounce", easeInOutBounce} - }; - - auto it = easingMap.find(name); - if (it != easingMap.end()) { - return it->second; - } - return linear; // Default to linear -} - -} // namespace EasingFunctions - -// AnimationManager implementation -AnimationManager& AnimationManager::getInstance() { - static AnimationManager instance; - return instance; -} - -void AnimationManager::addAnimation(std::shared_ptr animation) { - activeAnimations.push_back(animation); -} - -void AnimationManager::update(float deltaTime) { - for (auto& anim : activeAnimations) { - anim->update(deltaTime); - } - cleanup(); -} - -void AnimationManager::cleanup() { - activeAnimations.erase( - std::remove_if(activeAnimations.begin(), activeAnimations.end(), - [](const std::shared_ptr& anim) { - return anim->isComplete(); - }), - activeAnimations.end() - ); -} - -void AnimationManager::clear() { - activeAnimations.clear(); -} \ No newline at end of file diff --git a/src/Animation.h b/src/Animation.h deleted file mode 100644 index 6308f32..0000000 --- a/src/Animation.h +++ /dev/null @@ -1,146 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -// Forward declarations -class UIDrawable; -class UIEntity; - -// Forward declare namespace -namespace EasingFunctions { - float linear(float t); -} - -// Easing function type -typedef std::function EasingFunction; - -// Animation target value can be various types -typedef std::variant< - float, // Single float value - int, // Single integer value - std::vector, // List of integers (for sprite animation) - sf::Color, // Color animation - sf::Vector2f, // Vector animation - std::string // String animation (for text) -> AnimationValue; - -class Animation { -public: - // Constructor - Animation(const std::string& targetProperty, - const AnimationValue& targetValue, - float duration, - EasingFunction easingFunc = EasingFunctions::linear, - bool delta = false); - - // Apply this animation to a drawable - void start(UIDrawable* target); - - // Apply this animation to an entity (special case since Entity doesn't inherit from UIDrawable) - void startEntity(UIEntity* target); - - // Update animation (called each frame) - // Returns true if animation is still running, false if complete - bool update(float deltaTime); - - // Get current interpolated value - AnimationValue getCurrentValue() const; - - // Animation properties - std::string getTargetProperty() const { return targetProperty; } - float getDuration() const { return duration; } - float getElapsed() const { return elapsed; } - bool isComplete() const { return elapsed >= duration; } - bool isDelta() const { return delta; } - -private: - std::string targetProperty; // Property name to animate (e.g., "x", "color.r", "sprite_number") - AnimationValue startValue; // Starting value (captured when animation starts) - AnimationValue targetValue; // Target value to animate to - float duration; // Animation duration in seconds - float elapsed = 0.0f; // Elapsed time - EasingFunction easingFunc; // Easing function to use - bool delta; // If true, targetValue is relative to start - - UIDrawable* currentTarget = nullptr; // Current target being animated - UIEntity* currentEntityTarget = nullptr; // Current entity target (alternative to drawable) - - // Helper to interpolate between values - AnimationValue interpolate(float t) const; -}; - -// Easing functions library -namespace EasingFunctions { - // Basic easing functions - float linear(float t); - float easeIn(float t); - float easeOut(float t); - float easeInOut(float t); - - // Advanced easing functions - float easeInQuad(float t); - float easeOutQuad(float t); - float easeInOutQuad(float t); - - float easeInCubic(float t); - float easeOutCubic(float t); - float easeInOutCubic(float t); - - float easeInQuart(float t); - float easeOutQuart(float t); - float easeInOutQuart(float t); - - float easeInSine(float t); - float easeOutSine(float t); - float easeInOutSine(float t); - - float easeInExpo(float t); - float easeOutExpo(float t); - float easeInOutExpo(float t); - - float easeInCirc(float t); - float easeOutCirc(float t); - float easeInOutCirc(float t); - - float easeInElastic(float t); - float easeOutElastic(float t); - float easeInOutElastic(float t); - - float easeInBack(float t); - float easeOutBack(float t); - float easeInOutBack(float t); - - float easeInBounce(float t); - float easeOutBounce(float t); - float easeInOutBounce(float t); - - // Get easing function by name - EasingFunction getByName(const std::string& name); -} - -// Animation manager to handle active animations -class AnimationManager { -public: - static AnimationManager& getInstance(); - - // Add an animation to be managed - void addAnimation(std::shared_ptr animation); - - // Update all animations - void update(float deltaTime); - - // Remove completed animations - void cleanup(); - - // Clear all animations - void clear(); - -private: - AnimationManager() = default; - std::vector> activeAnimations; -}; \ No newline at end of file diff --git a/src/CommandLineParser.cpp b/src/CommandLineParser.cpp deleted file mode 100644 index 3e69b1b..0000000 --- a/src/CommandLineParser.cpp +++ /dev/null @@ -1,172 +0,0 @@ -#include "CommandLineParser.h" -#include -#include -#include - -CommandLineParser::CommandLineParser(int argc, char* argv[]) - : argc(argc), argv(argv) {} - -CommandLineParser::ParseResult CommandLineParser::parse(McRogueFaceConfig& config) { - ParseResult result; - current_arg = 1; // Reset for each parse - - // Detect if running as Python interpreter - std::filesystem::path exec_name = std::filesystem::path(argv[0]).filename(); - if (exec_name.string().find("python") == 0) { - config.headless = true; - config.python_mode = true; - } - - while (current_arg < argc) { - std::string arg = argv[current_arg]; - - // Handle Python-style single-letter flags - if (arg == "-h" || arg == "--help") { - print_help(); - result.should_exit = true; - result.exit_code = 0; - return result; - } - - if (arg == "-V" || arg == "--version") { - print_version(); - result.should_exit = true; - result.exit_code = 0; - return result; - } - - // Python execution modes - if (arg == "-c") { - config.python_mode = true; - current_arg++; - if (current_arg >= argc) { - std::cerr << "Argument expected for the -c option" << std::endl; - result.should_exit = true; - result.exit_code = 1; - return result; - } - config.python_command = argv[current_arg]; - current_arg++; - continue; - } - - if (arg == "-m") { - config.python_mode = true; - current_arg++; - if (current_arg >= argc) { - std::cerr << "Argument expected for the -m option" << std::endl; - result.should_exit = true; - result.exit_code = 1; - return result; - } - config.python_module = argv[current_arg]; - current_arg++; - // Collect remaining args as module args - while (current_arg < argc) { - config.script_args.push_back(argv[current_arg]); - current_arg++; - } - continue; - } - - if (arg == "-i") { - config.interactive_mode = true; - config.python_mode = true; - current_arg++; - continue; - } - - // McRogueFace specific flags - if (arg == "--headless") { - config.headless = true; - config.audio_enabled = false; - current_arg++; - continue; - } - - if (arg == "--audio-off") { - config.audio_enabled = false; - current_arg++; - continue; - } - - if (arg == "--audio-on") { - config.audio_enabled = true; - current_arg++; - continue; - } - - if (arg == "--screenshot") { - config.take_screenshot = true; - current_arg++; - if (current_arg < argc && argv[current_arg][0] != '-') { - config.screenshot_path = argv[current_arg]; - current_arg++; - } else { - config.screenshot_path = "screenshot.png"; - } - continue; - } - - if (arg == "--exec") { - current_arg++; - if (current_arg >= argc) { - std::cerr << "Argument expected for the --exec option" << std::endl; - result.should_exit = true; - result.exit_code = 1; - return result; - } - config.exec_scripts.push_back(argv[current_arg]); - config.python_mode = true; - current_arg++; - continue; - } - - // If no flags matched, treat as positional argument (script name) - if (arg[0] != '-') { - config.script_path = arg; - config.python_mode = true; - current_arg++; - // Remaining args are script args - while (current_arg < argc) { - config.script_args.push_back(argv[current_arg]); - current_arg++; - } - break; - } - - // Unknown flag - std::cerr << "Unknown option: " << arg << std::endl; - result.should_exit = true; - result.exit_code = 1; - return result; - } - - return result; -} - -void CommandLineParser::print_help() { - std::cout << "usage: mcrogueface [option] ... [-c cmd | -m mod | file | -] [arg] ...\n" - << "Options:\n" - << " -c cmd : program passed in as string (terminates option list)\n" - << " -h : print this help message and exit (also --help)\n" - << " -i : inspect interactively after running script\n" - << " -m mod : run library module as a script (terminates option list)\n" - << " -V : print the Python version number and exit (also --version)\n" - << "\n" - << "McRogueFace specific options:\n" - << " --exec file : execute script before main program (can be used multiple times)\n" - << " --headless : run without creating a window (implies --audio-off)\n" - << " --audio-off : disable audio\n" - << " --audio-on : enable audio (even in headless mode)\n" - << " --screenshot [path] : take a screenshot in headless mode\n" - << "\n" - << "Arguments:\n" - << " file : program read from script file\n" - << " - : program read from stdin\n" - << " arg ...: arguments passed to program in sys.argv[1:]\n"; -} - -void CommandLineParser::print_version() { - std::cout << "Python 3.12.0 (McRogueFace embedded)\n"; -} \ No newline at end of file diff --git a/src/CommandLineParser.h b/src/CommandLineParser.h deleted file mode 100644 index c330b85..0000000 --- a/src/CommandLineParser.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef COMMAND_LINE_PARSER_H -#define COMMAND_LINE_PARSER_H - -#include -#include -#include "McRogueFaceConfig.h" - -class CommandLineParser { -public: - struct ParseResult { - bool should_exit = false; - int exit_code = 0; - }; - - CommandLineParser(int argc, char* argv[]); - ParseResult parse(McRogueFaceConfig& config); - -private: - int argc; - char** argv; - int current_arg = 1; // Skip program name - - bool has_flag(const std::string& short_flag, const std::string& long_flag = ""); - std::string get_next_arg(const std::string& flag_name); - void parse_positional_args(McRogueFaceConfig& config); - void print_help(); - void print_version(); -}; - -#endif // COMMAND_LINE_PARSER_H \ No newline at end of file diff --git a/src/GameEngine.cpp b/src/GameEngine.cpp index a5a195b..f548709 100644 --- a/src/GameEngine.cpp +++ b/src/GameEngine.cpp @@ -4,80 +4,27 @@ #include "PyScene.h" #include "UITestScene.h" #include "Resources.h" -#include "Animation.h" -GameEngine::GameEngine() : GameEngine(McRogueFaceConfig{}) -{ -} - -GameEngine::GameEngine(const McRogueFaceConfig& cfg) - : config(cfg), headless(cfg.headless) +GameEngine::GameEngine() { Resources::font.loadFromFile("./assets/JetbrainsMono.ttf"); Resources::game = this; window_title = "Crypt of Sokoban - 7DRL 2025, McRogueface Engine"; - - // Initialize rendering based on headless mode - if (headless) { - headless_renderer = std::make_unique(); - if (!headless_renderer->init(1024, 768)) { - throw std::runtime_error("Failed to initialize headless renderer"); - } - render_target = &headless_renderer->getRenderTarget(); - } else { - window = std::make_unique(); - window->create(sf::VideoMode(1024, 768), window_title, sf::Style::Titlebar | sf::Style::Close); - window->setFramerateLimit(60); - render_target = window.get(); - } - - visible = render_target->getDefaultView(); + window.create(sf::VideoMode(1024, 768), window_title, sf::Style::Titlebar | sf::Style::Close); + visible = window.getDefaultView(); + window.setFramerateLimit(60); scene = "uitest"; scenes["uitest"] = new UITestScene(this); McRFPy_API::game = this; - - // Only load game.py if no custom script/command/module/exec is specified - bool should_load_game = config.script_path.empty() && - config.python_command.empty() && - config.python_module.empty() && - config.exec_scripts.empty() && - !config.interactive_mode && - !config.python_mode; - - if (should_load_game) { - if (!Py_IsInitialized()) { - McRFPy_API::api_init(); - } - McRFPy_API::executePyString("import mcrfpy"); - McRFPy_API::executeScript("scripts/game.py"); - } - - // Execute any --exec scripts in order - if (!config.exec_scripts.empty()) { - if (!Py_IsInitialized()) { - McRFPy_API::api_init(); - } - McRFPy_API::executePyString("import mcrfpy"); - - for (const auto& exec_script : config.exec_scripts) { - std::cout << "Executing script: " << exec_script << std::endl; - McRFPy_API::executeScript(exec_script.string()); - } - std::cout << "All --exec scripts completed" << std::endl; - } + McRFPy_API::api_init(); + McRFPy_API::executePyString("import mcrfpy"); + McRFPy_API::executeScript("scripts/game.py"); clock.restart(); runtime.restart(); } -GameEngine::~GameEngine() -{ - for (auto& [name, scene] : scenes) { - delete scene; - } -} - Scene* GameEngine::currentScene() { return scenes[scene]; } void GameEngine::changeScene(std::string s) { @@ -90,77 +37,36 @@ void GameEngine::changeScene(std::string s) void GameEngine::quit() { running = false; } void GameEngine::setPause(bool p) { paused = p; } sf::Font & GameEngine::getFont() { /*return font; */ return Resources::font; } -sf::RenderWindow & GameEngine::getWindow() { - if (!window) { - throw std::runtime_error("Window not available in headless mode"); - } - return *window; -} - -sf::RenderTarget & GameEngine::getRenderTarget() { - return *render_target; -} +sf::RenderWindow & GameEngine::getWindow() { return window; } void GameEngine::createScene(std::string s) { scenes[s] = new PyScene(this); } void GameEngine::setWindowScale(float multiplier) { - if (!headless && window) { - window->setSize(sf::Vector2u(1024 * multiplier, 768 * multiplier)); // 7DRL 2024: window scaling - } + window.setSize(sf::Vector2u(1024 * multiplier, 768 * multiplier)); // 7DRL 2024: window scaling //window.create(sf::VideoMode(1024 * multiplier, 768 * multiplier), window_title, sf::Style::Titlebar | sf::Style::Close); } void GameEngine::run() { - std::cout << "GameEngine::run() starting main loop..." << std::endl; float fps = 0.0; - frameTime = 0.016f; // Initialize to ~60 FPS clock.restart(); while (running) { currentScene()->update(); testTimers(); - - // Update animations (only if frameTime is valid) - if (frameTime > 0.0f && frameTime < 1.0f) { - AnimationManager::getInstance().update(frameTime); - } - - if (!headless) { - sUserInput(); - } + sUserInput(); if (!paused) { } currentScene()->render(); - - // Display the frame - if (headless) { - headless_renderer->display(); - // Take screenshot if requested - if (config.take_screenshot) { - headless_renderer->saveScreenshot(config.screenshot_path.empty() ? "screenshot.png" : config.screenshot_path); - config.take_screenshot = false; // Only take one screenshot - } - } else { - window->display(); - } - currentFrame++; frameTime = clock.restart().asSeconds(); fps = 1 / frameTime; int whole_fps = (int)fps; int tenth_fps = int(fps * 100) % 10; - - if (!headless && window) { - window->setTitle(window_title + " " + std::to_string(whole_fps) + "." + std::to_string(tenth_fps) + " FPS"); - } - - // In windowed mode, check if window was closed - if (!headless && window && !window->isOpen()) { - running = false; - } + //window.setTitle(window_title + " " + std::to_string(fps) + " FPS"); + window.setTitle(window_title + " " + std::to_string(whole_fps) + "." + std::to_string(tenth_fps) + " FPS"); } } @@ -202,54 +108,86 @@ void GameEngine::testTimers() } } -void GameEngine::processEvent(const sf::Event& event) -{ - std::string actionType; - int actionCode = 0; - - if (event.type == sf::Event::Closed) { running = false; return; } - // TODO: add resize event to Scene to react; call it after constructor too, maybe - else if (event.type == sf::Event::Resized) { - return; // 7DRL short circuit. Resizing manually disabled - } - - else if (event.type == sf::Event::KeyPressed || event.type == sf::Event::MouseButtonPressed || event.type == sf::Event::MouseWheelScrolled) actionType = "start"; - else if (event.type == sf::Event::KeyReleased || event.type == sf::Event::MouseButtonReleased) actionType = "end"; - - if (event.type == sf::Event::MouseButtonPressed || event.type == sf::Event::MouseButtonReleased) - actionCode = ActionCode::keycode(event.mouseButton.button); - else if (event.type == sf::Event::KeyPressed || event.type == sf::Event::KeyReleased) - actionCode = ActionCode::keycode(event.key.code); - else if (event.type == sf::Event::MouseWheelScrolled) - { - if (event.mouseWheelScroll.wheel == sf::Mouse::VerticalWheel) - { - int delta = 1; - if (event.mouseWheelScroll.delta < 0) delta = -1; - actionCode = ActionCode::keycode(event.mouseWheelScroll.wheel, delta ); - } - } - else - return; - - if (currentScene()->hasAction(actionCode)) - { - std::string name = currentScene()->action(actionCode); - currentScene()->doAction(name, actionType); - } - else if (currentScene()->key_callable && - (event.type == sf::Event::KeyPressed || event.type == sf::Event::KeyReleased)) - { - currentScene()->key_callable->call(ActionCode::key_str(event.key.code), actionType); - } -} - void GameEngine::sUserInput() { sf::Event event; - while (window && window->pollEvent(event)) + while (window.pollEvent(event)) { - processEvent(event); + std::string actionType; + int actionCode = 0; + + if (event.type == sf::Event::Closed) { running = false; continue; } + // TODO: add resize event to Scene to react; call it after constructor too, maybe + else if (event.type == sf::Event::Resized) { + continue; // 7DRL short circuit. Resizing manually disabled + /* + sf::FloatRect area(0.f, 0.f, event.size.width, event.size.height); + //sf::FloatRect area(0.f, 0.f, 1024.f, 768.f); // 7DRL 2024: attempt to set scale appropriately + //sf::FloatRect area(0.f, 0.f, event.size.width, event.size.width * 0.75); + visible = sf::View(area); + window.setView(visible); + //window.setSize(sf::Vector2u(event.size.width, event.size.width * 0.75)); // 7DRL 2024: window scaling + std::cout << "Visible area set to (0, 0, " << event.size.width << ", " << event.size.height <<")"< " << (actionCode && ActionCode::WHEEL_NEG) << "; actionCode && WHEEL_DEL -> " << (actionCode && ActionCode::WHEEL_DEL) << ";" << std::endl; + */ + } + // float d = event.MouseWheelScrollEvent.delta; + // actionCode = ActionCode::keycode(0, d); + } + else + continue; + + //std::cout << "Event produced action code " << actionCode << ": " << actionType << std::endl; + + if (currentScene()->hasAction(actionCode)) + { + std::string name = currentScene()->action(actionCode); + currentScene()->doAction(name, actionType); + } + else if (currentScene()->key_callable) + { + currentScene()->key_callable->call(ActionCode::key_str(event.key.code), actionType); + /* + PyObject* args = Py_BuildValue("(ss)", ActionCode::key_str(event.key.code).c_str(), actionType.c_str()); + PyObject* retval = PyObject_Call(currentScene()->key_callable, args, NULL); + if (!retval) + { + std::cout << "key_callable has raised an exception. It's going to STDERR and being dropped:" << std::endl; + PyErr_Print(); + PyErr_Clear(); + } else if (retval != Py_None) + { + std::cout << "key_callable returned a non-None value. It's not an error, it's just not being saved or used." << std::endl; + } + */ + } + else + { + //std::cout << "[GameEngine] Action not registered for input: " << actionCode << ": " << actionType << std::endl; + } } } diff --git a/src/GameEngine.h b/src/GameEngine.h index 02e02ae..8d688b3 100644 --- a/src/GameEngine.h +++ b/src/GameEngine.h @@ -6,16 +6,10 @@ #include "IndexTexture.h" #include "Timer.h" #include "PyCallable.h" -#include "McRogueFaceConfig.h" -#include "HeadlessRenderer.h" -#include class GameEngine { - std::unique_ptr window; - std::unique_ptr headless_renderer; - sf::RenderTarget* render_target; - + sf::RenderWindow window; sf::Font font; std::map scenes; bool running = true; @@ -25,9 +19,6 @@ class GameEngine sf::Clock clock; float frameTime; std::string window_title; - - bool headless = false; - McRogueFaceConfig config; sf::Clock runtime; //std::map timers; @@ -37,8 +28,6 @@ class GameEngine public: std::string scene; GameEngine(); - GameEngine(const McRogueFaceConfig& cfg); - ~GameEngine(); Scene* currentScene(); void changeScene(std::string); void createScene(std::string); @@ -46,8 +35,6 @@ public: void setPause(bool); sf::Font & getFont(); sf::RenderWindow & getWindow(); - sf::RenderTarget & getRenderTarget(); - sf::RenderTarget* getRenderTargetPtr() { return render_target; } void run(); void sUserInput(); int getFrame() { return currentFrame; } @@ -55,8 +42,6 @@ public: sf::View getView() { return visible; } void manageTimer(std::string, PyObject*, int); void setWindowScale(float); - bool isHeadless() const { return headless; } - void processEvent(const sf::Event& event); // global textures for scripts to access std::vector textures; diff --git a/src/HeadlessRenderer.cpp b/src/HeadlessRenderer.cpp deleted file mode 100644 index 27dff47..0000000 --- a/src/HeadlessRenderer.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "HeadlessRenderer.h" -#include - -bool HeadlessRenderer::init(int width, int height) { - if (!render_texture.create(width, height)) { - std::cerr << "Failed to create headless render texture" << std::endl; - return false; - } - return true; -} - -sf::RenderTarget& HeadlessRenderer::getRenderTarget() { - return render_texture; -} - -void HeadlessRenderer::saveScreenshot(const std::string& path) { - sf::Image screenshot = render_texture.getTexture().copyToImage(); - if (!screenshot.saveToFile(path)) { - std::cerr << "Failed to save screenshot to: " << path << std::endl; - } else { - std::cout << "Screenshot saved to: " << path << std::endl; - } -} - -void HeadlessRenderer::display() { - render_texture.display(); -} \ No newline at end of file diff --git a/src/HeadlessRenderer.h b/src/HeadlessRenderer.h deleted file mode 100644 index 2b08291..0000000 --- a/src/HeadlessRenderer.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef HEADLESS_RENDERER_H -#define HEADLESS_RENDERER_H - -#include -#include -#include - -class HeadlessRenderer { -private: - sf::RenderTexture render_texture; - -public: - bool init(int width = 1024, int height = 768); - sf::RenderTarget& getRenderTarget(); - void saveScreenshot(const std::string& path); - void display(); // Finalize the current frame - bool isOpen() const { return true; } // Always "open" in headless mode -}; - -#endif // HEADLESS_RENDERER_H \ No newline at end of file diff --git a/src/McRFPy_API.cpp b/src/McRFPy_API.cpp index a792150..2f2be1e 100644 --- a/src/McRFPy_API.cpp +++ b/src/McRFPy_API.cpp @@ -1,14 +1,10 @@ #include "McRFPy_API.h" -#include "McRFPy_Automation.h" #include "platform.h" -#include "PyAnimation.h" #include "GameEngine.h" #include "UI.h" #include "Resources.h" -#include "PyScene.h" -#include -#include +std::map McRFPy_API::callbacks; std::vector McRFPy_API::soundbuffers; sf::Music McRFPy_API::music; sf::Sound McRFPy_API::sfx; @@ -19,6 +15,11 @@ PyObject* McRFPy_API::mcrf_module; static PyMethodDef mcrfpyMethods[] = { + {"registerPyAction", McRFPy_API::_registerPyAction, METH_VARARGS, + "Register a callable Python object to correspond to an action string. (actionstr, callable)"}, + + {"registerInputAction", McRFPy_API::_registerInputAction, METH_VARARGS, + "Register a SFML input code to correspond to an action string. (input_code, actionstr)"}, {"createSoundBuffer", McRFPy_API::_createSoundBuffer, METH_VARARGS, "(filename)"}, {"loadMusic", McRFPy_API::_loadMusic, METH_VARARGS, "(filename)"}, @@ -78,20 +79,17 @@ PyObject* PyInit_mcrfpy() /*collections & iterators*/ &PyUICollectionType, &PyUICollectionIterType, &PyUIEntityCollectionType, &PyUIEntityCollectionIterType, - - /*animation*/ - &PyAnimationType, nullptr}; int i = 0; auto t = pytypes[i]; while (t != nullptr) { - //std::cout << "Registering type: " << t->tp_name << std::endl; + std::cout << "Registering type: " << t->tp_name << std::endl; if (PyType_Ready(t) < 0) { std::cout << "ERROR: PyType_Ready failed for " << t->tp_name << std::endl; return NULL; } - //std::cout << " tp_alloc after PyType_Ready: " << (void*)t->tp_alloc << std::endl; + std::cout << " tp_alloc after PyType_Ready: " << (void*)t->tp_alloc << std::endl; PyModule_AddType(m, t); i++; t = pytypes[i]; @@ -104,17 +102,6 @@ PyObject* PyInit_mcrfpy() //PyModule_AddObject(m, "default_texture", McRFPy_API::default_texture->pyObject()); PyModule_AddObject(m, "default_font", Py_None); PyModule_AddObject(m, "default_texture", Py_None); - - // Add automation submodule - PyObject* automation_module = McRFPy_Automation::init_automation_module(); - if (automation_module != NULL) { - PyModule_AddObject(m, "automation", automation_module); - - // Also add to sys.modules for proper import behavior - PyObject* sys_modules = PyImport_GetModuleDict(); - PyDict_SetItemString(sys_modules, "mcrfpy.automation", automation_module); - } - //McRFPy_API::mcrf_module = m; return m; } @@ -173,75 +160,6 @@ PyStatus init_python(const char *program_name) return status; } -PyStatus McRFPy_API::init_python_with_config(const McRogueFaceConfig& config, int argc, char** argv) -{ - // If Python is already initialized, just return success - if (Py_IsInitialized()) { - return PyStatus_Ok(); - } - - PyStatus status; - PyConfig pyconfig; - PyConfig_InitIsolatedConfig(&pyconfig); - - // CRITICAL: Pass actual command line arguments to Python - status = PyConfig_SetBytesArgv(&pyconfig, argc, argv); - if (PyStatus_Exception(status)) { - return status; - } - - // Check if we're in a virtual environment - auto exe_path = std::filesystem::path(argv[0]); - auto exe_dir = exe_path.parent_path(); - auto venv_root = exe_dir.parent_path(); - - if (std::filesystem::exists(venv_root / "pyvenv.cfg")) { - // We're running from within a venv! - // Add venv's site-packages to module search paths - auto site_packages = venv_root / "lib" / "python3.12" / "site-packages"; - PyWideStringList_Append(&pyconfig.module_search_paths, - site_packages.wstring().c_str()); - pyconfig.module_search_paths_set = 1; - } - - // Set Python home to our bundled Python - auto python_home = executable_path() + L"/lib/Python"; - PyConfig_SetString(&pyconfig, &pyconfig.home, python_home.c_str()); - - // Set up module search paths -#if __PLATFORM_SET_PYTHON_SEARCH_PATHS == 1 - if (!pyconfig.module_search_paths_set) { - pyconfig.module_search_paths_set = 1; - } - - // search paths for python libs/modules/scripts - const wchar_t* str_arr[] = { - L"/scripts", - L"/lib/Python/lib.linux-x86_64-3.12", - L"/lib/Python", - L"/lib/Python/Lib", - L"/venv/lib/python3.12/site-packages" - }; - - for(auto s : str_arr) { - status = PyWideStringList_Append(&pyconfig.module_search_paths, (executable_path() + s).c_str()); - if (PyStatus_Exception(status)) { - continue; - } - } -#endif - - // Register mcrfpy module before initialization - if (!Py_IsInitialized()) { - PyImport_AppendInittab("mcrfpy", &PyInit_mcrfpy); - } - - status = Py_InitializeFromConfig(&pyconfig); - PyConfig_Clear(&pyconfig); - - return status; -} - /* void McRFPy_API::setSpriteTexture(int ti) { @@ -259,11 +177,9 @@ void McRFPy_API::setSpriteTexture(int ti) void McRFPy_API::api_init() { // build API exposure before python initialization - if (!Py_IsInitialized()) { - PyImport_AppendInittab("mcrfpy", &PyInit_mcrfpy); - // use full path version of argv[0] from OS to init python - init_python(narrow_string(executable_filename()).c_str()); - } + PyImport_AppendInittab("mcrfpy", &PyInit_mcrfpy); + // use full path version of argv[0] from OS to init python + init_python(narrow_string(executable_filename()).c_str()); //texture.loadFromFile("./assets/kenney_tinydungeon.png"); //texture_size = 16, texture_width = 12, texture_height= 11; @@ -284,56 +200,12 @@ void McRFPy_API::api_init() { //setSpriteTexture(0); } -void McRFPy_API::api_init(const McRogueFaceConfig& config, int argc, char** argv) { - // Initialize Python with proper argv - this is CRITICAL - PyStatus status = init_python_with_config(config, argc, argv); - if (PyStatus_Exception(status)) { - Py_ExitStatusException(status); - } - - McRFPy_API::mcrf_module = PyImport_ImportModule("mcrfpy"); - - // For -m module execution, let Python handle it - if (!config.python_module.empty() && config.python_module != "venv") { - // Py_RunMain() will handle -m execution - return; - } - - // Execute based on mode - this is handled in main.cpp now - // The actual execution logic is in run_python_interpreter() - - // Set up default resources only if in game mode - if (!config.python_mode) { - //PyModule_AddObject(McRFPy_API::mcrf_module, "default_font", McRFPy_API::default_font->pyObject()); - PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_font", McRFPy_API::default_font->pyObject()); - //PyModule_AddObject(McRFPy_API::mcrf_module, "default_texture", McRFPy_API::default_texture->pyObject()); - PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_texture", McRFPy_API::default_texture->pyObject()); - } -} - void McRFPy_API::executeScript(std::string filename) { - std::filesystem::path script_path(filename); - - // If the path is relative and the file doesn't exist, try resolving it relative to the executable - if (script_path.is_relative() && !std::filesystem::exists(script_path)) { - // Get the directory where the executable is located using platform-specific function - std::wstring exe_dir_w = executable_path(); - std::filesystem::path exe_dir(exe_dir_w); - - // Try the script path relative to the executable directory - std::filesystem::path resolved_path = exe_dir / script_path; - if (std::filesystem::exists(resolved_path)) { - script_path = resolved_path; - } - } - - FILE* PScriptFile = fopen(script_path.string().c_str(), "r"); + FILE* PScriptFile = fopen(filename.c_str(), "r"); if(PScriptFile) { - PyRun_SimpleFile(PScriptFile, script_path.string().c_str()); + PyRun_SimpleFile(PScriptFile, filename.c_str()); fclose(PScriptFile); - } else { - std::cout << "Failed to open script: " << script_path.string() << std::endl; } } @@ -358,7 +230,63 @@ void McRFPy_API::REPL_device(FILE * fp, const char *filename) } // python connection +PyObject* McRFPy_API::_registerPyAction(PyObject *self, PyObject *args) +{ + PyObject* callable; + const char * actionstr; + if (!PyArg_ParseTuple(args, "sO", &actionstr, &callable)) return NULL; + //TODO: if the string already exists in the callbacks map, + // decrease our reference count so it can potentially be garbage collected + callbacks[std::string(actionstr)] = callable; + Py_INCREF(callable); + // return None correctly + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* McRFPy_API::_registerInputAction(PyObject *self, PyObject *args) +{ + int action_code; + const char * actionstr; + if (!PyArg_ParseTuple(args, "iz", &action_code, &actionstr)) return NULL; + + bool success; + + if (actionstr == NULL) { // Action provided is None, i.e. unregister + std::cout << "Unregistering\n"; + success = game->currentScene()->unregisterActionInjected(action_code, std::string(actionstr) + "_py"); + } else { + std::cout << "Registering " << actionstr << "_py to " << action_code << "\n"; + success = game->currentScene()->registerActionInjected(action_code, std::string(actionstr) + "_py"); + } + + success ? Py_INCREF(Py_True) : Py_INCREF(Py_False); + return success ? Py_True : Py_False; + +} + +void McRFPy_API::doAction(std::string actionstr) { + // hard coded actions that require no registration + //std::cout << "Calling Python Action: " << actionstr; + if (!actionstr.compare("startrepl")) return McRFPy_API::REPL(); + if (callbacks.find(actionstr) == callbacks.end()) + { + //std::cout << " (not found)" << std::endl; + return; + } + //std::cout << " (" << PyUnicode_AsUTF8(PyObject_Repr(callbacks[actionstr])) << ")" << std::endl; + PyObject* retval = PyObject_Call(callbacks[actionstr], PyTuple_New(0), NULL); + if (!retval) + { + std::cout << "doAction has raised an exception. It's going to STDERR and being dropped:" << std::endl; + PyErr_Print(); + PyErr_Clear(); + } else if (retval != Py_None) + { + std::cout << "doAction returned a non-None value. It's not an error, it's just not being saved or used." << std::endl; + } +} /* PyObject* McRFPy_API::_refreshFov(PyObject* self, PyObject* args) { @@ -431,10 +359,73 @@ PyObject* McRFPy_API::_getSoundVolume(PyObject* self, PyObject* args) { return Py_BuildValue("f", McRFPy_API::sfx.getVolume()); } -// Removed deprecated player_input, computerTurn, playerTurn functions -// These were part of the old turn-based system that is no longer used - /* +void McRFPy_API::player_input(int dx, int dy) { + //std::cout << "# entities tagged 'player': " << McRFPy_API::entities.getEntities("player").size() << std::endl; + auto player_entity = McRFPy_API::entities.getEntities("player")[0]; + auto grid = player_entity->cGrid->grid; + //std::cout << "Grid pointed to: " << (long)player_entity->cGrid->grid << std::endl; + if (McRFPy_API::input_mode.compare("playerturn") != 0) { + // no input accepted while computer moving + //std::cout << "Can't move while it's not player's turn." << std::endl; + return; + } + // TODO: selection cursor via keyboard + // else if (!input_mode.compare("selectpoint") {} + // else if (!input_mode.compare("selectentity") {} + + // grid bounds check + if (player_entity->cGrid->x + dx < 0 || + player_entity->cGrid->y + dy < 0 || + player_entity->cGrid->x + dx > grid->grid_x - 1 || + player_entity->cGrid->y + dy > grid->grid_y - 1) { + //std::cout << "(" << player_entity->cGrid->x << ", " << player_entity->cGrid->y << + // ") + (" << dx << ", " << dy << ") is OOB." << std::endl; + return; + } + //std::cout << PyUnicode_AsUTF8(PyObject_Repr(player_entity->cBehavior->object)) << std::endl; + PyObject* move_fn = PyObject_GetAttrString(player_entity->cBehavior->object, "move"); + //std::cout << PyUnicode_AsUTF8(PyObject_Repr(move_fn)) << std::endl; + if (move_fn) { + //std::cout << "Calling `move`" << std::endl; + PyObject* move_args = Py_BuildValue("(ii)", dx, dy); + PyObject_CallObject((PyObject*) move_fn, move_args); + } else { + //std::cout << "player_input called on entity with no `move` method" << std::endl; + } +} + + +void McRFPy_API::computerTurn() { + McRFPy_API::input_mode = "computerturnrunning"; + for (auto e : McRFPy_API::grids[McRFPy_API::active_grid]->entities) { + if (e->cBehavior) { + PyObject_Call(PyObject_GetAttrString(e->cBehavior->object, "ai_act"), PyTuple_New(0), NULL); + } + } +} + +void McRFPy_API::playerTurn() { + McRFPy_API::input_mode = "playerturn"; + for (auto e : McRFPy_API::entities.getEntities("player")) { + if (e->cBehavior) { + PyObject_Call(PyObject_GetAttrString(e->cBehavior->object, "player_act"), PyTuple_New(0), NULL); + } + } +} + +void McRFPy_API::camFollow() { + if (!McRFPy_API::do_camfollow) return; + auto& ag = McRFPy_API::grids[McRFPy_API::active_grid]; + for (auto e : McRFPy_API::entities.getEntities("player")) { + //std::cout << "grid center: " << ag->center_x << ", " << ag->center_y << std::endl << + // "player grid pos: " << e->cGrid->x << ", " << e->cGrid->y << std::endl << + // "player sprite pos: " << e->cGrid->indexsprite.x << ", " << e->cGrid->indexsprite.y << std::endl; + ag->center_x = e->cGrid->indexsprite.x * ag->grid_size + ag->grid_size * 0.5; + ag->center_y = e->cGrid->indexsprite.y * ag->grid_size + ag->grid_size * 0.5; + } +} + PyObject* McRFPy_API::_camFollow(PyObject* self, PyObject* args) { PyObject* set_camfollow = NULL; //std::cout << "camFollow Parse Args" << std::endl; @@ -498,13 +489,6 @@ PyObject* McRFPy_API::_createScene(PyObject* self, PyObject* args) { PyObject* McRFPy_API::_keypressScene(PyObject* self, PyObject* args) { PyObject* callable; if (!PyArg_ParseTuple(args, "O", &callable)) return NULL; - - // Validate that the argument is callable - if (!PyCallable_Check(callable)) { - PyErr_SetString(PyExc_TypeError, "keypressScene() argument must be callable"); - return NULL; - } - /* if (game->currentScene()->key_callable != NULL and game->currentScene()->key_callable != Py_None) { @@ -515,7 +499,6 @@ PyObject* McRFPy_API::_keypressScene(PyObject* self, PyObject* args) { Py_INCREF(Py_None); */ game->currentScene()->key_callable = std::make_unique(callable); - Py_INCREF(Py_None); return Py_None; } @@ -555,15 +538,3 @@ PyObject* McRFPy_API::_setScale(PyObject* self, PyObject* args) { Py_INCREF(Py_None); return Py_None; } - -void McRFPy_API::markSceneNeedsSort() { - // Mark the current scene as needing a z_index sort - auto scene = game->currentScene(); - if (scene && scene->ui_elements) { - // Cast to PyScene to access ui_elements_need_sort - PyScene* pyscene = dynamic_cast(scene); - if (pyscene) { - pyscene->ui_elements_need_sort = true; - } - } -} diff --git a/src/McRFPy_API.h b/src/McRFPy_API.h index 4d717df..08d034e 100644 --- a/src/McRFPy_API.h +++ b/src/McRFPy_API.h @@ -5,7 +5,6 @@ #include "PyFont.h" #include "PyTexture.h" -#include "McRogueFaceConfig.h" class GameEngine; // forward declared (circular members) @@ -28,8 +27,6 @@ public: //static void setSpriteTexture(int); inline static GameEngine* game; static void api_init(); - static void api_init(const McRogueFaceConfig& config, int argc, char** argv); - static PyStatus init_python_with_config(const McRogueFaceConfig& config, int argc, char** argv); static void api_shutdown(); // Python API functionality - use mcrfpy.* in scripts //static PyObject* _drawSprite(PyObject*, PyObject*); @@ -40,6 +37,9 @@ public: static sf::Music music; static sf::Sound sfx; + static std::map callbacks; + static PyObject* _registerPyAction(PyObject*, PyObject*); + static PyObject* _registerInputAction(PyObject*, PyObject*); static PyObject* _createSoundBuffer(PyObject*, PyObject*); static PyObject* _loadMusic(PyObject*, PyObject*); @@ -66,11 +66,12 @@ public: // accept keyboard input from scene static sf::Vector2i cursor_position; + static void player_input(int, int); + static void computerTurn(); + static void playerTurn(); + static void doAction(std::string); static void executeScript(std::string); static void executePyString(std::string); - - // Helper to mark scenes as needing z_index resort - static void markSceneNeedsSort(); }; diff --git a/src/McRFPy_Automation.cpp b/src/McRFPy_Automation.cpp deleted file mode 100644 index f755921..0000000 --- a/src/McRFPy_Automation.cpp +++ /dev/null @@ -1,817 +0,0 @@ -#include "McRFPy_Automation.h" -#include "McRFPy_API.h" -#include "GameEngine.h" -#include -#include -#include -#include - -// Helper function to get game engine -GameEngine* McRFPy_Automation::getGameEngine() { - return McRFPy_API::game; -} - -// Sleep helper -void McRFPy_Automation::sleep_ms(int milliseconds) { - std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); -} - -// Convert string to SFML key code -sf::Keyboard::Key McRFPy_Automation::stringToKey(const std::string& keyName) { - static const std::unordered_map keyMap = { - // Letters - {"a", sf::Keyboard::A}, {"b", sf::Keyboard::B}, {"c", sf::Keyboard::C}, - {"d", sf::Keyboard::D}, {"e", sf::Keyboard::E}, {"f", sf::Keyboard::F}, - {"g", sf::Keyboard::G}, {"h", sf::Keyboard::H}, {"i", sf::Keyboard::I}, - {"j", sf::Keyboard::J}, {"k", sf::Keyboard::K}, {"l", sf::Keyboard::L}, - {"m", sf::Keyboard::M}, {"n", sf::Keyboard::N}, {"o", sf::Keyboard::O}, - {"p", sf::Keyboard::P}, {"q", sf::Keyboard::Q}, {"r", sf::Keyboard::R}, - {"s", sf::Keyboard::S}, {"t", sf::Keyboard::T}, {"u", sf::Keyboard::U}, - {"v", sf::Keyboard::V}, {"w", sf::Keyboard::W}, {"x", sf::Keyboard::X}, - {"y", sf::Keyboard::Y}, {"z", sf::Keyboard::Z}, - - // Numbers - {"0", sf::Keyboard::Num0}, {"1", sf::Keyboard::Num1}, {"2", sf::Keyboard::Num2}, - {"3", sf::Keyboard::Num3}, {"4", sf::Keyboard::Num4}, {"5", sf::Keyboard::Num5}, - {"6", sf::Keyboard::Num6}, {"7", sf::Keyboard::Num7}, {"8", sf::Keyboard::Num8}, - {"9", sf::Keyboard::Num9}, - - // Function keys - {"f1", sf::Keyboard::F1}, {"f2", sf::Keyboard::F2}, {"f3", sf::Keyboard::F3}, - {"f4", sf::Keyboard::F4}, {"f5", sf::Keyboard::F5}, {"f6", sf::Keyboard::F6}, - {"f7", sf::Keyboard::F7}, {"f8", sf::Keyboard::F8}, {"f9", sf::Keyboard::F9}, - {"f10", sf::Keyboard::F10}, {"f11", sf::Keyboard::F11}, {"f12", sf::Keyboard::F12}, - {"f13", sf::Keyboard::F13}, {"f14", sf::Keyboard::F14}, {"f15", sf::Keyboard::F15}, - - // Special keys - {"escape", sf::Keyboard::Escape}, {"esc", sf::Keyboard::Escape}, - {"enter", sf::Keyboard::Enter}, {"return", sf::Keyboard::Enter}, - {"space", sf::Keyboard::Space}, {" ", sf::Keyboard::Space}, - {"tab", sf::Keyboard::Tab}, {"\t", sf::Keyboard::Tab}, - {"backspace", sf::Keyboard::BackSpace}, - {"delete", sf::Keyboard::Delete}, {"del", sf::Keyboard::Delete}, - {"insert", sf::Keyboard::Insert}, - {"home", sf::Keyboard::Home}, - {"end", sf::Keyboard::End}, - {"pageup", sf::Keyboard::PageUp}, {"pgup", sf::Keyboard::PageUp}, - {"pagedown", sf::Keyboard::PageDown}, {"pgdn", sf::Keyboard::PageDown}, - - // Arrow keys - {"left", sf::Keyboard::Left}, - {"right", sf::Keyboard::Right}, - {"up", sf::Keyboard::Up}, - {"down", sf::Keyboard::Down}, - - // Modifiers - {"ctrl", sf::Keyboard::LControl}, {"ctrlleft", sf::Keyboard::LControl}, - {"ctrlright", sf::Keyboard::RControl}, - {"alt", sf::Keyboard::LAlt}, {"altleft", sf::Keyboard::LAlt}, - {"altright", sf::Keyboard::RAlt}, - {"shift", sf::Keyboard::LShift}, {"shiftleft", sf::Keyboard::LShift}, - {"shiftright", sf::Keyboard::RShift}, - {"win", sf::Keyboard::LSystem}, {"winleft", sf::Keyboard::LSystem}, - {"winright", sf::Keyboard::RSystem}, {"command", sf::Keyboard::LSystem}, - - // Punctuation - {",", sf::Keyboard::Comma}, {".", sf::Keyboard::Period}, - {"/", sf::Keyboard::Slash}, {"\\", sf::Keyboard::BackSlash}, - {";", sf::Keyboard::SemiColon}, {"'", sf::Keyboard::Quote}, - {"[", sf::Keyboard::LBracket}, {"]", sf::Keyboard::RBracket}, - {"-", sf::Keyboard::Dash}, {"=", sf::Keyboard::Equal}, - - // Numpad - {"num0", sf::Keyboard::Numpad0}, {"num1", sf::Keyboard::Numpad1}, - {"num2", sf::Keyboard::Numpad2}, {"num3", sf::Keyboard::Numpad3}, - {"num4", sf::Keyboard::Numpad4}, {"num5", sf::Keyboard::Numpad5}, - {"num6", sf::Keyboard::Numpad6}, {"num7", sf::Keyboard::Numpad7}, - {"num8", sf::Keyboard::Numpad8}, {"num9", sf::Keyboard::Numpad9}, - {"add", sf::Keyboard::Add}, {"subtract", sf::Keyboard::Subtract}, - {"multiply", sf::Keyboard::Multiply}, {"divide", sf::Keyboard::Divide}, - - // Other - {"pause", sf::Keyboard::Pause}, - {"capslock", sf::Keyboard::LControl}, // Note: SFML doesn't have CapsLock - {"numlock", sf::Keyboard::LControl}, // Note: SFML doesn't have NumLock - {"scrolllock", sf::Keyboard::LControl}, // Note: SFML doesn't have ScrollLock - }; - - auto it = keyMap.find(keyName); - if (it != keyMap.end()) { - return it->second; - } - return sf::Keyboard::Unknown; -} - -// Inject mouse event into the game engine -void McRFPy_Automation::injectMouseEvent(sf::Event::EventType type, int x, int y, sf::Mouse::Button button) { - auto engine = getGameEngine(); - if (!engine) return; - - sf::Event event; - event.type = type; - - switch (type) { - case sf::Event::MouseMoved: - event.mouseMove.x = x; - event.mouseMove.y = y; - break; - case sf::Event::MouseButtonPressed: - case sf::Event::MouseButtonReleased: - event.mouseButton.button = button; - event.mouseButton.x = x; - event.mouseButton.y = y; - break; - case sf::Event::MouseWheelScrolled: - event.mouseWheelScroll.wheel = sf::Mouse::VerticalWheel; - event.mouseWheelScroll.delta = static_cast(x); // x is used for scroll amount - event.mouseWheelScroll.x = x; - event.mouseWheelScroll.y = y; - break; - default: - break; - } - - engine->processEvent(event); -} - -// Inject keyboard event into the game engine -void McRFPy_Automation::injectKeyEvent(sf::Event::EventType type, sf::Keyboard::Key key) { - auto engine = getGameEngine(); - if (!engine) return; - - sf::Event event; - event.type = type; - - if (type == sf::Event::KeyPressed || type == sf::Event::KeyReleased) { - event.key.code = key; - event.key.alt = sf::Keyboard::isKeyPressed(sf::Keyboard::LAlt) || - sf::Keyboard::isKeyPressed(sf::Keyboard::RAlt); - event.key.control = sf::Keyboard::isKeyPressed(sf::Keyboard::LControl) || - sf::Keyboard::isKeyPressed(sf::Keyboard::RControl); - event.key.shift = sf::Keyboard::isKeyPressed(sf::Keyboard::LShift) || - sf::Keyboard::isKeyPressed(sf::Keyboard::RShift); - event.key.system = sf::Keyboard::isKeyPressed(sf::Keyboard::LSystem) || - sf::Keyboard::isKeyPressed(sf::Keyboard::RSystem); - } - - engine->processEvent(event); -} - -// Inject text event for typing -void McRFPy_Automation::injectTextEvent(sf::Uint32 unicode) { - auto engine = getGameEngine(); - if (!engine) return; - - sf::Event event; - event.type = sf::Event::TextEntered; - event.text.unicode = unicode; - - engine->processEvent(event); -} - -// Screenshot implementation -PyObject* McRFPy_Automation::_screenshot(PyObject* self, PyObject* args) { - const char* filename; - if (!PyArg_ParseTuple(args, "s", &filename)) { - return NULL; - } - - auto engine = getGameEngine(); - if (!engine) { - PyErr_SetString(PyExc_RuntimeError, "Game engine not initialized"); - return NULL; - } - - // Get the render target - sf::RenderTarget* target = engine->getRenderTargetPtr(); - if (!target) { - PyErr_SetString(PyExc_RuntimeError, "No render target available"); - return NULL; - } - - // For RenderWindow, we can get a screenshot directly - if (auto* window = dynamic_cast(target)) { - sf::Vector2u windowSize = window->getSize(); - sf::Texture texture; - texture.create(windowSize.x, windowSize.y); - texture.update(*window); - - if (texture.copyToImage().saveToFile(filename)) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; - } - } - // For RenderTexture (headless mode) - else if (auto* renderTexture = dynamic_cast(target)) { - if (renderTexture->getTexture().copyToImage().saveToFile(filename)) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; - } - } - - PyErr_SetString(PyExc_RuntimeError, "Unknown render target type"); - return NULL; -} - -// Get current mouse position -PyObject* McRFPy_Automation::_position(PyObject* self, PyObject* args) { - auto engine = getGameEngine(); - if (!engine || !engine->getRenderTargetPtr()) { - return Py_BuildValue("(ii)", 0, 0); - } - - // In headless mode, we'd need to track the simulated mouse position - // For now, return the actual mouse position relative to window if available - if (auto* window = dynamic_cast(engine->getRenderTargetPtr())) { - sf::Vector2i pos = sf::Mouse::getPosition(*window); - return Py_BuildValue("(ii)", pos.x, pos.y); - } - - // In headless mode, return simulated position (TODO: track this) - return Py_BuildValue("(ii)", 0, 0); -} - -// Get screen size -PyObject* McRFPy_Automation::_size(PyObject* self, PyObject* args) { - auto engine = getGameEngine(); - if (!engine || !engine->getRenderTargetPtr()) { - return Py_BuildValue("(ii)", 1024, 768); // Default size - } - - sf::Vector2u size = engine->getRenderTarget().getSize(); - return Py_BuildValue("(ii)", size.x, size.y); -} - -// Check if coordinates are on screen -PyObject* McRFPy_Automation::_onScreen(PyObject* self, PyObject* args) { - int x, y; - if (!PyArg_ParseTuple(args, "ii", &x, &y)) { - return NULL; - } - - auto engine = getGameEngine(); - if (!engine || !engine->getRenderTargetPtr()) { - Py_RETURN_FALSE; - } - - sf::Vector2u size = engine->getRenderTarget().getSize(); - if (x >= 0 && x < (int)size.x && y >= 0 && y < (int)size.y) { - Py_RETURN_TRUE; - } else { - Py_RETURN_FALSE; - } -} - -// Move mouse to position -PyObject* McRFPy_Automation::_moveTo(PyObject* self, PyObject* args, PyObject* kwargs) { - static const char* kwlist[] = {"x", "y", "duration", NULL}; - int x, y; - float duration = 0.0f; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|f", const_cast(kwlist), - &x, &y, &duration)) { - return NULL; - } - - // TODO: Implement smooth movement with duration - injectMouseEvent(sf::Event::MouseMoved, x, y); - - if (duration > 0) { - sleep_ms(static_cast(duration * 1000)); - } - - Py_RETURN_NONE; -} - -// Move mouse relative -PyObject* McRFPy_Automation::_moveRel(PyObject* self, PyObject* args, PyObject* kwargs) { - static const char* kwlist[] = {"xOffset", "yOffset", "duration", NULL}; - int xOffset, yOffset; - float duration = 0.0f; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|f", const_cast(kwlist), - &xOffset, &yOffset, &duration)) { - return NULL; - } - - // Get current position - PyObject* pos = _position(self, NULL); - if (!pos) return NULL; - - int currentX, currentY; - if (!PyArg_ParseTuple(pos, "ii", ¤tX, ¤tY)) { - Py_DECREF(pos); - return NULL; - } - Py_DECREF(pos); - - // Move to new position - injectMouseEvent(sf::Event::MouseMoved, currentX + xOffset, currentY + yOffset); - - if (duration > 0) { - sleep_ms(static_cast(duration * 1000)); - } - - Py_RETURN_NONE; -} - -// Click implementation -PyObject* McRFPy_Automation::_click(PyObject* self, PyObject* args, PyObject* kwargs) { - static const char* kwlist[] = {"x", "y", "clicks", "interval", "button", NULL}; - int x = -1, y = -1; - int clicks = 1; - float interval = 0.0f; - const char* button = "left"; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|iiifs", const_cast(kwlist), - &x, &y, &clicks, &interval, &button)) { - return NULL; - } - - // If no position specified, use current position - if (x == -1 || y == -1) { - PyObject* pos = _position(self, NULL); - if (!pos) return NULL; - - if (!PyArg_ParseTuple(pos, "ii", &x, &y)) { - Py_DECREF(pos); - return NULL; - } - Py_DECREF(pos); - } - - // Determine button - sf::Mouse::Button sfButton = sf::Mouse::Left; - if (strcmp(button, "right") == 0) { - sfButton = sf::Mouse::Right; - } else if (strcmp(button, "middle") == 0) { - sfButton = sf::Mouse::Middle; - } - - // Move to position first - injectMouseEvent(sf::Event::MouseMoved, x, y); - - // Perform clicks - for (int i = 0; i < clicks; i++) { - if (i > 0 && interval > 0) { - sleep_ms(static_cast(interval * 1000)); - } - - injectMouseEvent(sf::Event::MouseButtonPressed, x, y, sfButton); - sleep_ms(10); // Small delay between press and release - injectMouseEvent(sf::Event::MouseButtonReleased, x, y, sfButton); - } - - Py_RETURN_NONE; -} - -// Right click -PyObject* McRFPy_Automation::_rightClick(PyObject* self, PyObject* args, PyObject* kwargs) { - static const char* kwlist[] = {"x", "y", NULL}; - int x = -1, y = -1; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", const_cast(kwlist), &x, &y)) { - return NULL; - } - - // Build new args with button="right" - PyObject* newKwargs = PyDict_New(); - PyDict_SetItemString(newKwargs, "button", PyUnicode_FromString("right")); - if (x != -1) PyDict_SetItemString(newKwargs, "x", PyLong_FromLong(x)); - if (y != -1) PyDict_SetItemString(newKwargs, "y", PyLong_FromLong(y)); - - PyObject* result = _click(self, PyTuple_New(0), newKwargs); - Py_DECREF(newKwargs); - return result; -} - -// Double click -PyObject* McRFPy_Automation::_doubleClick(PyObject* self, PyObject* args, PyObject* kwargs) { - static const char* kwlist[] = {"x", "y", NULL}; - int x = -1, y = -1; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", const_cast(kwlist), &x, &y)) { - return NULL; - } - - PyObject* newKwargs = PyDict_New(); - PyDict_SetItemString(newKwargs, "clicks", PyLong_FromLong(2)); - PyDict_SetItemString(newKwargs, "interval", PyFloat_FromDouble(0.1)); - if (x != -1) PyDict_SetItemString(newKwargs, "x", PyLong_FromLong(x)); - if (y != -1) PyDict_SetItemString(newKwargs, "y", PyLong_FromLong(y)); - - PyObject* result = _click(self, PyTuple_New(0), newKwargs); - Py_DECREF(newKwargs); - return result; -} - -// Type text -PyObject* McRFPy_Automation::_typewrite(PyObject* self, PyObject* args, PyObject* kwargs) { - static const char* kwlist[] = {"message", "interval", NULL}; - const char* message; - float interval = 0.0f; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|f", const_cast(kwlist), - &message, &interval)) { - return NULL; - } - - // Type each character - for (size_t i = 0; message[i] != '\0'; i++) { - if (i > 0 && interval > 0) { - sleep_ms(static_cast(interval * 1000)); - } - - char c = message[i]; - - // Handle special characters - if (c == '\n') { - injectKeyEvent(sf::Event::KeyPressed, sf::Keyboard::Enter); - injectKeyEvent(sf::Event::KeyReleased, sf::Keyboard::Enter); - } else if (c == '\t') { - injectKeyEvent(sf::Event::KeyPressed, sf::Keyboard::Tab); - injectKeyEvent(sf::Event::KeyReleased, sf::Keyboard::Tab); - } else { - // For regular characters, send text event - injectTextEvent(static_cast(c)); - } - } - - Py_RETURN_NONE; -} - -// Press and hold key -PyObject* McRFPy_Automation::_keyDown(PyObject* self, PyObject* args) { - const char* keyName; - if (!PyArg_ParseTuple(args, "s", &keyName)) { - return NULL; - } - - sf::Keyboard::Key key = stringToKey(keyName); - if (key == sf::Keyboard::Unknown) { - PyErr_Format(PyExc_ValueError, "Unknown key: %s", keyName); - return NULL; - } - - injectKeyEvent(sf::Event::KeyPressed, key); - Py_RETURN_NONE; -} - -// Release key -PyObject* McRFPy_Automation::_keyUp(PyObject* self, PyObject* args) { - const char* keyName; - if (!PyArg_ParseTuple(args, "s", &keyName)) { - return NULL; - } - - sf::Keyboard::Key key = stringToKey(keyName); - if (key == sf::Keyboard::Unknown) { - PyErr_Format(PyExc_ValueError, "Unknown key: %s", keyName); - return NULL; - } - - injectKeyEvent(sf::Event::KeyReleased, key); - Py_RETURN_NONE; -} - -// Hotkey combination -PyObject* McRFPy_Automation::_hotkey(PyObject* self, PyObject* args) { - // Get all keys as separate arguments - Py_ssize_t numKeys = PyTuple_Size(args); - if (numKeys == 0) { - PyErr_SetString(PyExc_ValueError, "hotkey() requires at least one key"); - return NULL; - } - - // Press all keys - for (Py_ssize_t i = 0; i < numKeys; i++) { - PyObject* keyObj = PyTuple_GetItem(args, i); - const char* keyName = PyUnicode_AsUTF8(keyObj); - if (!keyName) { - return NULL; - } - - sf::Keyboard::Key key = stringToKey(keyName); - if (key == sf::Keyboard::Unknown) { - PyErr_Format(PyExc_ValueError, "Unknown key: %s", keyName); - return NULL; - } - - injectKeyEvent(sf::Event::KeyPressed, key); - sleep_ms(10); // Small delay between key presses - } - - // Release all keys in reverse order - for (Py_ssize_t i = numKeys - 1; i >= 0; i--) { - PyObject* keyObj = PyTuple_GetItem(args, i); - const char* keyName = PyUnicode_AsUTF8(keyObj); - - sf::Keyboard::Key key = stringToKey(keyName); - injectKeyEvent(sf::Event::KeyReleased, key); - sleep_ms(10); - } - - Py_RETURN_NONE; -} - -// Scroll wheel -PyObject* McRFPy_Automation::_scroll(PyObject* self, PyObject* args, PyObject* kwargs) { - static const char* kwlist[] = {"clicks", "x", "y", NULL}; - int clicks; - int x = -1, y = -1; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|ii", const_cast(kwlist), - &clicks, &x, &y)) { - return NULL; - } - - // If no position specified, use current position - if (x == -1 || y == -1) { - PyObject* pos = _position(self, NULL); - if (!pos) return NULL; - - if (!PyArg_ParseTuple(pos, "ii", &x, &y)) { - Py_DECREF(pos); - return NULL; - } - Py_DECREF(pos); - } - - // Inject scroll event - injectMouseEvent(sf::Event::MouseWheelScrolled, clicks, y); - - Py_RETURN_NONE; -} - -// Other click types using the main click function -PyObject* McRFPy_Automation::_middleClick(PyObject* self, PyObject* args, PyObject* kwargs) { - static const char* kwlist[] = {"x", "y", NULL}; - int x = -1, y = -1; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", const_cast(kwlist), &x, &y)) { - return NULL; - } - - PyObject* newKwargs = PyDict_New(); - PyDict_SetItemString(newKwargs, "button", PyUnicode_FromString("middle")); - if (x != -1) PyDict_SetItemString(newKwargs, "x", PyLong_FromLong(x)); - if (y != -1) PyDict_SetItemString(newKwargs, "y", PyLong_FromLong(y)); - - PyObject* result = _click(self, PyTuple_New(0), newKwargs); - Py_DECREF(newKwargs); - return result; -} - -PyObject* McRFPy_Automation::_tripleClick(PyObject* self, PyObject* args, PyObject* kwargs) { - static const char* kwlist[] = {"x", "y", NULL}; - int x = -1, y = -1; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", const_cast(kwlist), &x, &y)) { - return NULL; - } - - PyObject* newKwargs = PyDict_New(); - PyDict_SetItemString(newKwargs, "clicks", PyLong_FromLong(3)); - PyDict_SetItemString(newKwargs, "interval", PyFloat_FromDouble(0.1)); - if (x != -1) PyDict_SetItemString(newKwargs, "x", PyLong_FromLong(x)); - if (y != -1) PyDict_SetItemString(newKwargs, "y", PyLong_FromLong(y)); - - PyObject* result = _click(self, PyTuple_New(0), newKwargs); - Py_DECREF(newKwargs); - return result; -} - -// Mouse button press/release -PyObject* McRFPy_Automation::_mouseDown(PyObject* self, PyObject* args, PyObject* kwargs) { - static const char* kwlist[] = {"x", "y", "button", NULL}; - int x = -1, y = -1; - const char* button = "left"; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|iis", const_cast(kwlist), - &x, &y, &button)) { - return NULL; - } - - // If no position specified, use current position - if (x == -1 || y == -1) { - PyObject* pos = _position(self, NULL); - if (!pos) return NULL; - - if (!PyArg_ParseTuple(pos, "ii", &x, &y)) { - Py_DECREF(pos); - return NULL; - } - Py_DECREF(pos); - } - - sf::Mouse::Button sfButton = sf::Mouse::Left; - if (strcmp(button, "right") == 0) { - sfButton = sf::Mouse::Right; - } else if (strcmp(button, "middle") == 0) { - sfButton = sf::Mouse::Middle; - } - - injectMouseEvent(sf::Event::MouseButtonPressed, x, y, sfButton); - Py_RETURN_NONE; -} - -PyObject* McRFPy_Automation::_mouseUp(PyObject* self, PyObject* args, PyObject* kwargs) { - static const char* kwlist[] = {"x", "y", "button", NULL}; - int x = -1, y = -1; - const char* button = "left"; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|iis", const_cast(kwlist), - &x, &y, &button)) { - return NULL; - } - - // If no position specified, use current position - if (x == -1 || y == -1) { - PyObject* pos = _position(self, NULL); - if (!pos) return NULL; - - if (!PyArg_ParseTuple(pos, "ii", &x, &y)) { - Py_DECREF(pos); - return NULL; - } - Py_DECREF(pos); - } - - sf::Mouse::Button sfButton = sf::Mouse::Left; - if (strcmp(button, "right") == 0) { - sfButton = sf::Mouse::Right; - } else if (strcmp(button, "middle") == 0) { - sfButton = sf::Mouse::Middle; - } - - injectMouseEvent(sf::Event::MouseButtonReleased, x, y, sfButton); - Py_RETURN_NONE; -} - -// Drag operations -PyObject* McRFPy_Automation::_dragTo(PyObject* self, PyObject* args, PyObject* kwargs) { - static const char* kwlist[] = {"x", "y", "duration", "button", NULL}; - int x, y; - float duration = 0.0f; - const char* button = "left"; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|fs", const_cast(kwlist), - &x, &y, &duration, &button)) { - return NULL; - } - - // Get current position - PyObject* pos = _position(self, NULL); - if (!pos) return NULL; - - int startX, startY; - if (!PyArg_ParseTuple(pos, "ii", &startX, &startY)) { - Py_DECREF(pos); - return NULL; - } - Py_DECREF(pos); - - // Mouse down at current position - PyObject* downArgs = Py_BuildValue("(ii)", startX, startY); - PyObject* downKwargs = PyDict_New(); - PyDict_SetItemString(downKwargs, "button", PyUnicode_FromString(button)); - - PyObject* downResult = _mouseDown(self, downArgs, downKwargs); - Py_DECREF(downArgs); - Py_DECREF(downKwargs); - if (!downResult) return NULL; - Py_DECREF(downResult); - - // Move to target position - if (duration > 0) { - // Smooth movement - int steps = static_cast(duration * 60); // 60 FPS - for (int i = 1; i <= steps; i++) { - int currentX = startX + (x - startX) * i / steps; - int currentY = startY + (y - startY) * i / steps; - injectMouseEvent(sf::Event::MouseMoved, currentX, currentY); - sleep_ms(1000 / 60); // 60 FPS - } - } else { - injectMouseEvent(sf::Event::MouseMoved, x, y); - } - - // Mouse up at target position - PyObject* upArgs = Py_BuildValue("(ii)", x, y); - PyObject* upKwargs = PyDict_New(); - PyDict_SetItemString(upKwargs, "button", PyUnicode_FromString(button)); - - PyObject* upResult = _mouseUp(self, upArgs, upKwargs); - Py_DECREF(upArgs); - Py_DECREF(upKwargs); - if (!upResult) return NULL; - Py_DECREF(upResult); - - Py_RETURN_NONE; -} - -PyObject* McRFPy_Automation::_dragRel(PyObject* self, PyObject* args, PyObject* kwargs) { - static const char* kwlist[] = {"xOffset", "yOffset", "duration", "button", NULL}; - int xOffset, yOffset; - float duration = 0.0f; - const char* button = "left"; - - if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|fs", const_cast(kwlist), - &xOffset, &yOffset, &duration, &button)) { - return NULL; - } - - // Get current position - PyObject* pos = _position(self, NULL); - if (!pos) return NULL; - - int currentX, currentY; - if (!PyArg_ParseTuple(pos, "ii", ¤tX, ¤tY)) { - Py_DECREF(pos); - return NULL; - } - Py_DECREF(pos); - - // Call dragTo with absolute position - PyObject* dragArgs = Py_BuildValue("(ii)", currentX + xOffset, currentY + yOffset); - PyObject* dragKwargs = PyDict_New(); - PyDict_SetItemString(dragKwargs, "duration", PyFloat_FromDouble(duration)); - PyDict_SetItemString(dragKwargs, "button", PyUnicode_FromString(button)); - - PyObject* result = _dragTo(self, dragArgs, dragKwargs); - Py_DECREF(dragArgs); - Py_DECREF(dragKwargs); - - return result; -} - -// Method definitions for the automation module -static PyMethodDef automationMethods[] = { - {"screenshot", McRFPy_Automation::_screenshot, METH_VARARGS, - "screenshot(filename) - Save a screenshot to the specified file"}, - - {"position", McRFPy_Automation::_position, METH_NOARGS, - "position() - Get current mouse position as (x, y) tuple"}, - {"size", McRFPy_Automation::_size, METH_NOARGS, - "size() - Get screen size as (width, height) tuple"}, - {"onScreen", McRFPy_Automation::_onScreen, METH_VARARGS, - "onScreen(x, y) - Check if coordinates are within screen bounds"}, - - {"moveTo", (PyCFunction)McRFPy_Automation::_moveTo, METH_VARARGS | METH_KEYWORDS, - "moveTo(x, y, duration=0.0) - Move mouse to absolute position"}, - {"moveRel", (PyCFunction)McRFPy_Automation::_moveRel, METH_VARARGS | METH_KEYWORDS, - "moveRel(xOffset, yOffset, duration=0.0) - Move mouse relative to current position"}, - {"dragTo", (PyCFunction)McRFPy_Automation::_dragTo, METH_VARARGS | METH_KEYWORDS, - "dragTo(x, y, duration=0.0, button='left') - Drag mouse to position"}, - {"dragRel", (PyCFunction)McRFPy_Automation::_dragRel, METH_VARARGS | METH_KEYWORDS, - "dragRel(xOffset, yOffset, duration=0.0, button='left') - Drag mouse relative to current position"}, - - {"click", (PyCFunction)McRFPy_Automation::_click, METH_VARARGS | METH_KEYWORDS, - "click(x=None, y=None, clicks=1, interval=0.0, button='left') - Click at position"}, - {"rightClick", (PyCFunction)McRFPy_Automation::_rightClick, METH_VARARGS | METH_KEYWORDS, - "rightClick(x=None, y=None) - Right click at position"}, - {"middleClick", (PyCFunction)McRFPy_Automation::_middleClick, METH_VARARGS | METH_KEYWORDS, - "middleClick(x=None, y=None) - Middle click at position"}, - {"doubleClick", (PyCFunction)McRFPy_Automation::_doubleClick, METH_VARARGS | METH_KEYWORDS, - "doubleClick(x=None, y=None) - Double click at position"}, - {"tripleClick", (PyCFunction)McRFPy_Automation::_tripleClick, METH_VARARGS | METH_KEYWORDS, - "tripleClick(x=None, y=None) - Triple click at position"}, - {"scroll", (PyCFunction)McRFPy_Automation::_scroll, METH_VARARGS | METH_KEYWORDS, - "scroll(clicks, x=None, y=None) - Scroll wheel at position"}, - {"mouseDown", (PyCFunction)McRFPy_Automation::_mouseDown, METH_VARARGS | METH_KEYWORDS, - "mouseDown(x=None, y=None, button='left') - Press mouse button"}, - {"mouseUp", (PyCFunction)McRFPy_Automation::_mouseUp, METH_VARARGS | METH_KEYWORDS, - "mouseUp(x=None, y=None, button='left') - Release mouse button"}, - - {"typewrite", (PyCFunction)McRFPy_Automation::_typewrite, METH_VARARGS | METH_KEYWORDS, - "typewrite(message, interval=0.0) - Type text with optional interval between keystrokes"}, - {"hotkey", McRFPy_Automation::_hotkey, METH_VARARGS, - "hotkey(*keys) - Press a hotkey combination (e.g., hotkey('ctrl', 'c'))"}, - {"keyDown", McRFPy_Automation::_keyDown, METH_VARARGS, - "keyDown(key) - Press and hold a key"}, - {"keyUp", McRFPy_Automation::_keyUp, METH_VARARGS, - "keyUp(key) - Release a key"}, - - {NULL, NULL, 0, NULL} -}; - -// Module definition for mcrfpy.automation -static PyModuleDef automationModule = { - PyModuleDef_HEAD_INIT, - "mcrfpy.automation", - "Automation API for McRogueFace - PyAutoGUI-compatible interface", - -1, - automationMethods -}; - -// Initialize automation submodule -PyObject* McRFPy_Automation::init_automation_module() { - PyObject* module = PyModule_Create(&automationModule); - if (module == NULL) { - return NULL; - } - - return module; -} \ No newline at end of file diff --git a/src/McRFPy_Automation.h b/src/McRFPy_Automation.h deleted file mode 100644 index fdf126e..0000000 --- a/src/McRFPy_Automation.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once -#include "Common.h" -#include "Python.h" -#include -#include -#include -#include -#include - -class GameEngine; - -class McRFPy_Automation { -public: - // Initialize the automation submodule - static PyObject* init_automation_module(); - - // Screenshot functionality - static PyObject* _screenshot(PyObject* self, PyObject* args); - - // Mouse position and screen info - static PyObject* _position(PyObject* self, PyObject* args); - static PyObject* _size(PyObject* self, PyObject* args); - static PyObject* _onScreen(PyObject* self, PyObject* args); - - // Mouse movement - static PyObject* _moveTo(PyObject* self, PyObject* args, PyObject* kwargs); - static PyObject* _moveRel(PyObject* self, PyObject* args, PyObject* kwargs); - static PyObject* _dragTo(PyObject* self, PyObject* args, PyObject* kwargs); - static PyObject* _dragRel(PyObject* self, PyObject* args, PyObject* kwargs); - - // Mouse clicks - static PyObject* _click(PyObject* self, PyObject* args, PyObject* kwargs); - static PyObject* _rightClick(PyObject* self, PyObject* args, PyObject* kwargs); - static PyObject* _middleClick(PyObject* self, PyObject* args, PyObject* kwargs); - static PyObject* _doubleClick(PyObject* self, PyObject* args, PyObject* kwargs); - static PyObject* _tripleClick(PyObject* self, PyObject* args, PyObject* kwargs); - static PyObject* _scroll(PyObject* self, PyObject* args, PyObject* kwargs); - static PyObject* _mouseDown(PyObject* self, PyObject* args, PyObject* kwargs); - static PyObject* _mouseUp(PyObject* self, PyObject* args, PyObject* kwargs); - - // Keyboard - static PyObject* _typewrite(PyObject* self, PyObject* args, PyObject* kwargs); - static PyObject* _hotkey(PyObject* self, PyObject* args); - static PyObject* _keyDown(PyObject* self, PyObject* args); - static PyObject* _keyUp(PyObject* self, PyObject* args); - - // Helper functions - static void injectMouseEvent(sf::Event::EventType type, int x, int y, sf::Mouse::Button button = sf::Mouse::Left); - static void injectKeyEvent(sf::Event::EventType type, sf::Keyboard::Key key); - static void injectTextEvent(sf::Uint32 unicode); - static sf::Keyboard::Key stringToKey(const std::string& keyName); - static void sleep_ms(int milliseconds); - -private: - static GameEngine* getGameEngine(); -}; \ No newline at end of file diff --git a/src/McRogueFaceConfig.h b/src/McRogueFaceConfig.h deleted file mode 100644 index 34a589e..0000000 --- a/src/McRogueFaceConfig.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef MCROGUEFACE_CONFIG_H -#define MCROGUEFACE_CONFIG_H - -#include -#include -#include - -struct McRogueFaceConfig { - // McRogueFace specific - bool headless = false; - bool audio_enabled = true; - - // Python interpreter emulation - bool python_mode = false; - std::string python_command; // -c command - std::string python_module; // -m module - bool interactive_mode = false; // -i flag - bool show_version = false; // -V flag - bool show_help = false; // -h flag - - // Script execution - std::filesystem::path script_path; - std::vector script_args; - - // Scripts to execute before main script (--exec flag) - std::vector exec_scripts; - - // Screenshot functionality for headless mode - std::string screenshot_path; - bool take_screenshot = false; -}; - -#endif // MCROGUEFACE_CONFIG_H \ No newline at end of file diff --git a/src/PyAnimation.cpp b/src/PyAnimation.cpp deleted file mode 100644 index 720b8d9..0000000 --- a/src/PyAnimation.cpp +++ /dev/null @@ -1,234 +0,0 @@ -#include "PyAnimation.h" -#include "McRFPy_API.h" -#include "UIDrawable.h" -#include "UIFrame.h" -#include "UICaption.h" -#include "UISprite.h" -#include "UIGrid.h" -#include "UIEntity.h" -#include "UI.h" // For the PyTypeObject definitions -#include - -PyObject* PyAnimation::create(PyTypeObject* type, PyObject* args, PyObject* kwds) { - PyAnimationObject* self = (PyAnimationObject*)type->tp_alloc(type, 0); - if (self != NULL) { - // Will be initialized in init - } - return (PyObject*)self; -} - -int PyAnimation::init(PyAnimationObject* self, PyObject* args, PyObject* kwds) { - static const char* keywords[] = {"property", "target", "duration", "easing", "delta", nullptr}; - - const char* property_name; - PyObject* target_value; - float duration; - const char* easing_name = "linear"; - int delta = 0; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOf|sp", const_cast(keywords), - &property_name, &target_value, &duration, &easing_name, &delta)) { - return -1; - } - - // Convert Python target value to AnimationValue - AnimationValue animValue; - - if (PyFloat_Check(target_value)) { - animValue = static_cast(PyFloat_AsDouble(target_value)); - } - else if (PyLong_Check(target_value)) { - animValue = static_cast(PyLong_AsLong(target_value)); - } - else if (PyList_Check(target_value)) { - // List of integers for sprite animation - std::vector indices; - Py_ssize_t size = PyList_Size(target_value); - for (Py_ssize_t i = 0; i < size; i++) { - PyObject* item = PyList_GetItem(target_value, i); - if (PyLong_Check(item)) { - indices.push_back(PyLong_AsLong(item)); - } else { - PyErr_SetString(PyExc_TypeError, "Sprite animation list must contain only integers"); - return -1; - } - } - animValue = indices; - } - else if (PyTuple_Check(target_value)) { - Py_ssize_t size = PyTuple_Size(target_value); - if (size == 2) { - // Vector2f - float x = PyFloat_AsDouble(PyTuple_GetItem(target_value, 0)); - float y = PyFloat_AsDouble(PyTuple_GetItem(target_value, 1)); - animValue = sf::Vector2f(x, y); - } - else if (size == 3 || size == 4) { - // Color (RGB or RGBA) - int r = PyLong_AsLong(PyTuple_GetItem(target_value, 0)); - int g = PyLong_AsLong(PyTuple_GetItem(target_value, 1)); - int b = PyLong_AsLong(PyTuple_GetItem(target_value, 2)); - int a = size == 4 ? PyLong_AsLong(PyTuple_GetItem(target_value, 3)) : 255; - animValue = sf::Color(r, g, b, a); - } - else { - PyErr_SetString(PyExc_ValueError, "Tuple must have 2 elements (vector) or 3-4 elements (color)"); - return -1; - } - } - else if (PyUnicode_Check(target_value)) { - // String for text animation - const char* str = PyUnicode_AsUTF8(target_value); - animValue = std::string(str); - } - else { - PyErr_SetString(PyExc_TypeError, "Target value must be float, int, list, tuple, or string"); - return -1; - } - - // Get easing function - EasingFunction easingFunc = EasingFunctions::getByName(easing_name); - - // Create the Animation - self->data = std::make_shared(property_name, animValue, duration, easingFunc, delta != 0); - - return 0; -} - -void PyAnimation::dealloc(PyAnimationObject* self) { - self->data.reset(); - Py_TYPE(self)->tp_free((PyObject*)self); -} - -PyObject* PyAnimation::get_property(PyAnimationObject* self, void* closure) { - return PyUnicode_FromString(self->data->getTargetProperty().c_str()); -} - -PyObject* PyAnimation::get_duration(PyAnimationObject* self, void* closure) { - return PyFloat_FromDouble(self->data->getDuration()); -} - -PyObject* PyAnimation::get_elapsed(PyAnimationObject* self, void* closure) { - return PyFloat_FromDouble(self->data->getElapsed()); -} - -PyObject* PyAnimation::get_is_complete(PyAnimationObject* self, void* closure) { - return PyBool_FromLong(self->data->isComplete()); -} - -PyObject* PyAnimation::get_is_delta(PyAnimationObject* self, void* closure) { - return PyBool_FromLong(self->data->isDelta()); -} - -PyObject* PyAnimation::start(PyAnimationObject* self, PyObject* args) { - PyObject* target_obj; - if (!PyArg_ParseTuple(args, "O", &target_obj)) { - return NULL; - } - - // Get the UIDrawable from the Python object - UIDrawable* drawable = nullptr; - - // Check type by comparing type names - const char* type_name = Py_TYPE(target_obj)->tp_name; - - if (strcmp(type_name, "mcrfpy.Frame") == 0) { - PyUIFrameObject* frame = (PyUIFrameObject*)target_obj; - drawable = frame->data.get(); - } - else if (strcmp(type_name, "mcrfpy.Caption") == 0) { - PyUICaptionObject* caption = (PyUICaptionObject*)target_obj; - drawable = caption->data.get(); - } - else if (strcmp(type_name, "mcrfpy.Sprite") == 0) { - PyUISpriteObject* sprite = (PyUISpriteObject*)target_obj; - drawable = sprite->data.get(); - } - else if (strcmp(type_name, "mcrfpy.Grid") == 0) { - PyUIGridObject* grid = (PyUIGridObject*)target_obj; - drawable = grid->data.get(); - } - else if (strcmp(type_name, "mcrfpy.Entity") == 0) { - // Special handling for Entity since it doesn't inherit from UIDrawable - PyUIEntityObject* entity = (PyUIEntityObject*)target_obj; - // Start the animation directly on the entity - self->data->startEntity(entity->data.get()); - - // Add to AnimationManager - AnimationManager::getInstance().addAnimation(self->data); - - Py_RETURN_NONE; - } - else { - PyErr_SetString(PyExc_TypeError, "Target must be a Frame, Caption, Sprite, Grid, or Entity"); - return NULL; - } - - // Start the animation - self->data->start(drawable); - - // Add to AnimationManager - AnimationManager::getInstance().addAnimation(self->data); - - Py_RETURN_NONE; -} - -PyObject* PyAnimation::update(PyAnimationObject* self, PyObject* args) { - float deltaTime; - if (!PyArg_ParseTuple(args, "f", &deltaTime)) { - return NULL; - } - - bool still_running = self->data->update(deltaTime); - return PyBool_FromLong(still_running); -} - -PyObject* PyAnimation::get_current_value(PyAnimationObject* self, PyObject* args) { - AnimationValue value = self->data->getCurrentValue(); - - // Convert AnimationValue back to Python - return std::visit([](const auto& val) -> PyObject* { - using T = std::decay_t; - - if constexpr (std::is_same_v) { - return PyFloat_FromDouble(val); - } - else if constexpr (std::is_same_v) { - return PyLong_FromLong(val); - } - else if constexpr (std::is_same_v>) { - // This shouldn't happen as we interpolate to int - return PyLong_FromLong(0); - } - else if constexpr (std::is_same_v) { - return Py_BuildValue("(iiii)", val.r, val.g, val.b, val.a); - } - else if constexpr (std::is_same_v) { - return Py_BuildValue("(ff)", val.x, val.y); - } - else if constexpr (std::is_same_v) { - return PyUnicode_FromString(val.c_str()); - } - - Py_RETURN_NONE; - }, value); -} - -PyGetSetDef PyAnimation::getsetters[] = { - {"property", (getter)get_property, NULL, "Target property name", NULL}, - {"duration", (getter)get_duration, NULL, "Animation duration in seconds", NULL}, - {"elapsed", (getter)get_elapsed, NULL, "Elapsed time in seconds", NULL}, - {"is_complete", (getter)get_is_complete, NULL, "Whether animation is complete", NULL}, - {"is_delta", (getter)get_is_delta, NULL, "Whether animation uses delta mode", NULL}, - {NULL} -}; - -PyMethodDef PyAnimation::methods[] = { - {"start", (PyCFunction)start, METH_VARARGS, - "Start the animation on a target UIDrawable"}, - {"update", (PyCFunction)update, METH_VARARGS, - "Update the animation by deltaTime (returns True if still running)"}, - {"get_current_value", (PyCFunction)get_current_value, METH_NOARGS, - "Get the current interpolated value"}, - {NULL} -}; \ No newline at end of file diff --git a/src/PyAnimation.h b/src/PyAnimation.h deleted file mode 100644 index 9976cb2..0000000 --- a/src/PyAnimation.h +++ /dev/null @@ -1,50 +0,0 @@ -#pragma once - -#include "Common.h" -#include "Python.h" -#include "structmember.h" -#include "Animation.h" -#include - -typedef struct { - PyObject_HEAD - std::shared_ptr data; -} PyAnimationObject; - -class PyAnimation { -public: - static PyObject* create(PyTypeObject* type, PyObject* args, PyObject* kwds); - static int init(PyAnimationObject* self, PyObject* args, PyObject* kwds); - static void dealloc(PyAnimationObject* self); - - // Properties - static PyObject* get_property(PyAnimationObject* self, void* closure); - static PyObject* get_duration(PyAnimationObject* self, void* closure); - static PyObject* get_elapsed(PyAnimationObject* self, void* closure); - static PyObject* get_is_complete(PyAnimationObject* self, void* closure); - static PyObject* get_is_delta(PyAnimationObject* self, void* closure); - - // Methods - static PyObject* start(PyAnimationObject* self, PyObject* args); - static PyObject* update(PyAnimationObject* self, PyObject* args); - static PyObject* get_current_value(PyAnimationObject* self, PyObject* args); - - static PyGetSetDef getsetters[]; - static PyMethodDef methods[]; -}; - -namespace mcrfpydef { - static PyTypeObject PyAnimationType = { - .ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0}, - .tp_name = "mcrfpy.Animation", - .tp_basicsize = sizeof(PyAnimationObject), - .tp_itemsize = 0, - .tp_dealloc = (destructor)PyAnimation::dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT, - .tp_doc = PyDoc_STR("Animation object for animating UI properties"), - .tp_methods = PyAnimation::methods, - .tp_getset = PyAnimation::getsetters, - .tp_init = (initproc)PyAnimation::init, - .tp_new = PyAnimation::create, - }; -} \ No newline at end of file diff --git a/src/PyColor.cpp b/src/PyColor.cpp index 8a40d5e..7c2ac87 100644 --- a/src/PyColor.cpp +++ b/src/PyColor.cpp @@ -133,58 +133,13 @@ PyObject* PyColor::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds) PyObject* PyColor::get_member(PyObject* obj, void* closure) { - PyColorObject* self = (PyColorObject*)obj; - long member = (long)closure; - - switch (member) { - case 0: // r - return PyLong_FromLong(self->data.r); - case 1: // g - return PyLong_FromLong(self->data.g); - case 2: // b - return PyLong_FromLong(self->data.b); - case 3: // a - return PyLong_FromLong(self->data.a); - default: - PyErr_SetString(PyExc_AttributeError, "Invalid color member"); - return NULL; - } + // TODO + return Py_None; } int PyColor::set_member(PyObject* obj, PyObject* value, void* closure) { - PyColorObject* self = (PyColorObject*)obj; - long member = (long)closure; - - if (!PyLong_Check(value)) { - PyErr_SetString(PyExc_TypeError, "Color values must be integers"); - return -1; - } - - long val = PyLong_AsLong(value); - if (val < 0 || val > 255) { - PyErr_SetString(PyExc_ValueError, "Color values must be between 0 and 255"); - return -1; - } - - switch (member) { - case 0: // r - self->data.r = static_cast(val); - break; - case 1: // g - self->data.g = static_cast(val); - break; - case 2: // b - self->data.b = static_cast(val); - break; - case 3: // a - self->data.a = static_cast(val); - break; - default: - PyErr_SetString(PyExc_AttributeError, "Invalid color member"); - return -1; - } - + // TODO return 0; } diff --git a/src/PyFont.cpp b/src/PyFont.cpp index 157656e..7773d52 100644 --- a/src/PyFont.cpp +++ b/src/PyFont.cpp @@ -61,19 +61,3 @@ PyObject* PyFont::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds) { return (PyObject*)type->tp_alloc(type, 0); } - -PyObject* PyFont::get_family(PyFontObject* self, void* closure) -{ - return PyUnicode_FromString(self->data->font.getInfo().family.c_str()); -} - -PyObject* PyFont::get_source(PyFontObject* self, void* closure) -{ - return PyUnicode_FromString(self->data->source.c_str()); -} - -PyGetSetDef PyFont::getsetters[] = { - {"family", (getter)PyFont::get_family, NULL, "Font family name", NULL}, - {"source", (getter)PyFont::get_source, NULL, "Source filename of the font", NULL}, - {NULL} // Sentinel -}; diff --git a/src/PyFont.h b/src/PyFont.h index df88423..07b2b55 100644 --- a/src/PyFont.h +++ b/src/PyFont.h @@ -21,12 +21,6 @@ public: static Py_hash_t hash(PyObject*); static int init(PyFontObject*, PyObject*, PyObject*); static PyObject* pynew(PyTypeObject* type, PyObject* args=NULL, PyObject* kwds=NULL); - - // Getters for properties - static PyObject* get_family(PyFontObject* self, void* closure); - static PyObject* get_source(PyFontObject* self, void* closure); - - static PyGetSetDef getsetters[]; }; namespace mcrfpydef { @@ -39,7 +33,6 @@ namespace mcrfpydef { //.tp_hash = PyFont::hash, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = PyDoc_STR("SFML Font Object"), - .tp_getset = PyFont::getsetters, //.tp_base = &PyBaseObject_Type, .tp_init = (initproc)PyFont::init, .tp_new = PyType_GenericNew, //PyFont::pynew, diff --git a/src/PyScene.cpp b/src/PyScene.cpp index c5ae5d6..8474572 100644 --- a/src/PyScene.cpp +++ b/src/PyScene.cpp @@ -2,7 +2,6 @@ #include "ActionCode.h" #include "Resources.h" #include "PyCallable.h" -#include PyScene::PyScene(GameEngine* g) : Scene(g) { @@ -22,11 +21,6 @@ void PyScene::update() void PyScene::do_mouse_input(std::string button, std::string type) { - // In headless mode, mouse input is not available - if (game->isHeadless()) { - return; - } - auto unscaledmousepos = sf::Mouse::getPosition(game->getWindow()); auto mousepos = game->getWindow().mapPixelToCoords(unscaledmousepos); UIDrawable* target; @@ -55,7 +49,10 @@ void PyScene::do_mouse_input(std::string button, std::string type) void PyScene::doAction(std::string name, std::string type) { - if (name.compare("left") == 0 || name.compare("rclick") == 0 || name.compare("wheel_up") == 0 || name.compare("wheel_down") == 0) { + if (ACTIONPY) { + McRFPy_API::doAction(name.substr(0, name.size() - 3)); + } + else if (name.compare("left") == 0 || name.compare("rclick") == 0 || name.compare("wheel_up") == 0 || name.compare("wheel_down") == 0) { do_mouse_input(name, type); } else if ACTIONONCE("debug_menu") { @@ -65,23 +62,14 @@ void PyScene::doAction(std::string name, std::string type) void PyScene::render() { - game->getRenderTarget().clear(); + game->getWindow().clear(); - // Only sort if z_index values have changed - if (ui_elements_need_sort) { - std::sort(ui_elements->begin(), ui_elements->end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) { - return a->z_index < b->z_index; - }); - ui_elements_need_sort = false; - } - - // Render in sorted order (no need to copy anymore) - for (auto e: *ui_elements) + auto vec = *ui_elements; + for (auto e: vec) { if (e) e->render(); } - // Display is handled by GameEngine + game->getWindow().display(); } diff --git a/src/PyScene.h b/src/PyScene.h index 86697ee..068e714 100644 --- a/src/PyScene.h +++ b/src/PyScene.h @@ -14,7 +14,4 @@ public: void render() override final; void do_mouse_input(std::string, std::string); - - // Dirty flag for z_index sorting optimization - bool ui_elements_need_sort = true; }; diff --git a/src/PyTexture.cpp b/src/PyTexture.cpp index d4ea3f3..83a9dcb 100644 --- a/src/PyTexture.cpp +++ b/src/PyTexture.cpp @@ -79,43 +79,3 @@ PyObject* PyTexture::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds) { return (PyObject*)type->tp_alloc(type, 0); } - -PyObject* PyTexture::get_sprite_width(PyTextureObject* self, void* closure) -{ - return PyLong_FromLong(self->data->sprite_width); -} - -PyObject* PyTexture::get_sprite_height(PyTextureObject* self, void* closure) -{ - return PyLong_FromLong(self->data->sprite_height); -} - -PyObject* PyTexture::get_sheet_width(PyTextureObject* self, void* closure) -{ - return PyLong_FromLong(self->data->sheet_width); -} - -PyObject* PyTexture::get_sheet_height(PyTextureObject* self, void* closure) -{ - return PyLong_FromLong(self->data->sheet_height); -} - -PyObject* PyTexture::get_sprite_count(PyTextureObject* self, void* closure) -{ - return PyLong_FromLong(self->data->getSpriteCount()); -} - -PyObject* PyTexture::get_source(PyTextureObject* self, void* closure) -{ - return PyUnicode_FromString(self->data->source.c_str()); -} - -PyGetSetDef PyTexture::getsetters[] = { - {"sprite_width", (getter)PyTexture::get_sprite_width, NULL, "Width of each sprite in pixels", NULL}, - {"sprite_height", (getter)PyTexture::get_sprite_height, NULL, "Height of each sprite in pixels", NULL}, - {"sheet_width", (getter)PyTexture::get_sheet_width, NULL, "Number of sprite columns in the texture", NULL}, - {"sheet_height", (getter)PyTexture::get_sheet_height, NULL, "Number of sprite rows in the texture", NULL}, - {"sprite_count", (getter)PyTexture::get_sprite_count, NULL, "Total number of sprites in the texture", NULL}, - {"source", (getter)PyTexture::get_source, NULL, "Source filename of the texture", NULL}, - {NULL} // Sentinel -}; diff --git a/src/PyTexture.h b/src/PyTexture.h index 106e87d..d1e68b8 100644 --- a/src/PyTexture.h +++ b/src/PyTexture.h @@ -19,23 +19,12 @@ public: int sprite_width, sprite_height; // just use them read only, OK? PyTexture(std::string filename, int sprite_w, int sprite_h); sf::Sprite sprite(int index, sf::Vector2f pos = sf::Vector2f(0, 0), sf::Vector2f s = sf::Vector2f(1.0, 1.0)); - int getSpriteCount() const { return sheet_width * sheet_height; } PyObject* pyObject(); static PyObject* repr(PyObject*); static Py_hash_t hash(PyObject*); static int init(PyTextureObject*, PyObject*, PyObject*); static PyObject* pynew(PyTypeObject* type, PyObject* args=NULL, PyObject* kwds=NULL); - - // Getters for properties - static PyObject* get_sprite_width(PyTextureObject* self, void* closure); - static PyObject* get_sprite_height(PyTextureObject* self, void* closure); - static PyObject* get_sheet_width(PyTextureObject* self, void* closure); - static PyObject* get_sheet_height(PyTextureObject* self, void* closure); - static PyObject* get_sprite_count(PyTextureObject* self, void* closure); - static PyObject* get_source(PyTextureObject* self, void* closure); - - static PyGetSetDef getsetters[]; }; namespace mcrfpydef { @@ -48,7 +37,6 @@ namespace mcrfpydef { .tp_hash = PyTexture::hash, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = PyDoc_STR("SFML Texture Object"), - .tp_getset = PyTexture::getsetters, //.tp_base = &PyBaseObject_Type, .tp_init = (initproc)PyTexture::init, .tp_new = PyType_GenericNew, //PyTexture::pynew, diff --git a/src/PyVector.cpp b/src/PyVector.cpp index 83c243e..f1143cb 100644 --- a/src/PyVector.cpp +++ b/src/PyVector.cpp @@ -106,37 +106,13 @@ PyObject* PyVector::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds) PyObject* PyVector::get_member(PyObject* obj, void* closure) { - PyVectorObject* self = (PyVectorObject*)obj; - if (reinterpret_cast(closure) == 0) { - // x - return PyFloat_FromDouble(self->data.x); - } else { - // y - return PyFloat_FromDouble(self->data.y); - } + // TODO + return Py_None; } int PyVector::set_member(PyObject* obj, PyObject* value, void* closure) { - PyVectorObject* self = (PyVectorObject*)obj; - float val; - - if (PyFloat_Check(value)) { - val = PyFloat_AsDouble(value); - } else if (PyLong_Check(value)) { - val = PyLong_AsDouble(value); - } else { - PyErr_SetString(PyExc_TypeError, "Vector members must be numeric"); - return -1; - } - - if (reinterpret_cast(closure) == 0) { - // x - self->data.x = val; - } else { - // y - self->data.y = val; - } + // TODO return 0; } @@ -144,31 +120,11 @@ PyVectorObject* PyVector::from_arg(PyObject* args) { auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector"); if (PyObject_IsInstance(args, (PyObject*)type)) return (PyVectorObject*)args; - auto obj = (PyVectorObject*)type->tp_alloc(type, 0); - - // Handle different input types - if (PyTuple_Check(args)) { - // It's already a tuple, pass it directly to init - int err = init(obj, args, NULL); - if (err) { - Py_DECREF(obj); - return NULL; - } - } else { - // Wrap single argument in a tuple for init - PyObject* tuple = PyTuple_Pack(1, args); - if (!tuple) { - Py_DECREF(obj); - return NULL; - } - int err = init(obj, tuple, NULL); - Py_DECREF(tuple); - if (err) { - Py_DECREF(obj); - return NULL; - } + int err = init(obj, args, NULL); + if (err) { + Py_DECREF(obj); + return NULL; } - return obj; } diff --git a/src/Scene.cpp b/src/Scene.cpp index 928e6d9..d9438e3 100644 --- a/src/Scene.cpp +++ b/src/Scene.cpp @@ -30,6 +30,16 @@ std::string Scene::action(int code) return actions[code]; } +bool Scene::registerActionInjected(int code, std::string name) +{ + std::cout << "Inject registered action - default implementation\n"; + return false; +} + +bool Scene::unregisterActionInjected(int code, std::string name) +{ + return false; +} void Scene::key_register(PyObject* callable) { diff --git a/src/Scene.h b/src/Scene.h index e8d322c..0ebb5a9 100644 --- a/src/Scene.h +++ b/src/Scene.h @@ -4,6 +4,7 @@ #define ACTION(X, Y) (name.compare(X) == 0 && type.compare(Y) == 0) #define ACTIONONCE(X) ((name.compare(X) == 0 && type.compare("start") == 0 && !actionState[name])) #define ACTIONAFTER(X) ((name.compare(X) == 0 && type.compare("end") == 0)) +#define ACTIONPY ((name.size() > 3 && name.compare(name.size() - 3, 3, "_py") == 0)) #include "Common.h" #include @@ -36,6 +37,8 @@ public: bool hasAction(int); std::string action(int); + virtual bool registerActionInjected(int, std::string); + virtual bool unregisterActionInjected(int, std::string); std::shared_ptr>> ui_elements; diff --git a/src/UICaption.cpp b/src/UICaption.cpp index 22b4787..539ec38 100644 --- a/src/UICaption.cpp +++ b/src/UICaption.cpp @@ -3,7 +3,6 @@ #include "PyColor.h" #include "PyVector.h" #include "PyFont.h" -#include UIDrawable* UICaption::click_at(sf::Vector2f point) { @@ -197,9 +196,8 @@ PyGetSetDef UICaption::getsetters[] = { {"outline_color", (getter)UICaption::get_color_member, (setter)UICaption::set_color_member, "Outline color of the text", (void*)1}, //{"children", (getter)PyUIFrame_get_children, NULL, "UICollection of objects on top of this one", NULL}, {"text", (getter)UICaption::get_text, (setter)UICaption::set_text, "The text displayed", NULL}, - {"font_size", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Font size (integer) in points", (void*)5}, + {"size", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Text size (integer) in points", (void*)5}, {"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UICAPTION}, - {"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UICAPTION}, {NULL} }; @@ -236,7 +234,7 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds) //if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf", // const_cast(keywords), &x, &y, &text, &font, &fill_color, &outline_color, &outline)) - if (!PyArg_ParseTupleAndKeywords(args, kwds, "Oz|OOOf", + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|zOOOf", const_cast(keywords), &pos, &text, &font, &fill_color, &outline_color, &outline)) { return -1; @@ -252,10 +250,10 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds) // check types for font, fill_color, outline_color //std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl; - if (font != NULL && font != Py_None && !PyObject_IsInstance(font, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Font")/*(PyObject*)&PyFontType)*/)){ - PyErr_SetString(PyExc_TypeError, "font must be a mcrfpy.Font instance or None"); + if (font != NULL && !PyObject_IsInstance(font, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Font")/*(PyObject*)&PyFontType)*/)){ + PyErr_SetString(PyExc_TypeError, "font must be a mcrfpy.Font instance"); return -1; - } else if (font != NULL && font != Py_None) + } else if (font != NULL) { auto font_obj = (PyFontObject*)font; self->data->text.setFont(font_obj->data->font); @@ -263,16 +261,8 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds) Py_INCREF(font); } else { - // Use default font when None or not provided - if (McRFPy_API::default_font) { - self->data->text.setFont(McRFPy_API::default_font->font); - // Store reference to default font - PyObject* default_font_obj = PyObject_GetAttrString(McRFPy_API::mcrf_module, "default_font"); - if (default_font_obj) { - self->font = default_font_obj; - // Don't need to DECREF since we're storing it - } - } + // default font + //self->data->text.setFont(Resources::game->getFont()); } self->data->text.setString((std::string)text); @@ -304,172 +294,3 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds) return 0; } -// Property system implementation for animations -bool UICaption::setProperty(const std::string& name, float value) { - if (name == "x") { - text.setPosition(sf::Vector2f(value, text.getPosition().y)); - return true; - } - else if (name == "y") { - text.setPosition(sf::Vector2f(text.getPosition().x, value)); - return true; - } - else if (name == "font_size" || name == "size") { // Support both for backward compatibility - text.setCharacterSize(static_cast(value)); - return true; - } - else if (name == "outline") { - text.setOutlineThickness(value); - return true; - } - else if (name == "fill_color.r") { - auto color = text.getFillColor(); - color.r = static_cast(std::clamp(value, 0.0f, 255.0f)); - text.setFillColor(color); - return true; - } - else if (name == "fill_color.g") { - auto color = text.getFillColor(); - color.g = static_cast(std::clamp(value, 0.0f, 255.0f)); - text.setFillColor(color); - return true; - } - else if (name == "fill_color.b") { - auto color = text.getFillColor(); - color.b = static_cast(std::clamp(value, 0.0f, 255.0f)); - text.setFillColor(color); - return true; - } - else if (name == "fill_color.a") { - auto color = text.getFillColor(); - color.a = static_cast(std::clamp(value, 0.0f, 255.0f)); - text.setFillColor(color); - return true; - } - else if (name == "outline_color.r") { - auto color = text.getOutlineColor(); - color.r = static_cast(std::clamp(value, 0.0f, 255.0f)); - text.setOutlineColor(color); - return true; - } - else if (name == "outline_color.g") { - auto color = text.getOutlineColor(); - color.g = static_cast(std::clamp(value, 0.0f, 255.0f)); - text.setOutlineColor(color); - return true; - } - else if (name == "outline_color.b") { - auto color = text.getOutlineColor(); - color.b = static_cast(std::clamp(value, 0.0f, 255.0f)); - text.setOutlineColor(color); - return true; - } - else if (name == "outline_color.a") { - auto color = text.getOutlineColor(); - color.a = static_cast(std::clamp(value, 0.0f, 255.0f)); - text.setOutlineColor(color); - return true; - } - else if (name == "z_index") { - z_index = static_cast(value); - return true; - } - return false; -} - -bool UICaption::setProperty(const std::string& name, const sf::Color& value) { - if (name == "fill_color") { - text.setFillColor(value); - return true; - } - else if (name == "outline_color") { - text.setOutlineColor(value); - return true; - } - return false; -} - -bool UICaption::setProperty(const std::string& name, const std::string& value) { - if (name == "text") { - text.setString(value); - return true; - } - return false; -} - -bool UICaption::getProperty(const std::string& name, float& value) const { - if (name == "x") { - value = text.getPosition().x; - return true; - } - else if (name == "y") { - value = text.getPosition().y; - return true; - } - else if (name == "font_size" || name == "size") { // Support both for backward compatibility - value = static_cast(text.getCharacterSize()); - return true; - } - else if (name == "outline") { - value = text.getOutlineThickness(); - return true; - } - else if (name == "fill_color.r") { - value = text.getFillColor().r; - return true; - } - else if (name == "fill_color.g") { - value = text.getFillColor().g; - return true; - } - else if (name == "fill_color.b") { - value = text.getFillColor().b; - return true; - } - else if (name == "fill_color.a") { - value = text.getFillColor().a; - return true; - } - else if (name == "outline_color.r") { - value = text.getOutlineColor().r; - return true; - } - else if (name == "outline_color.g") { - value = text.getOutlineColor().g; - return true; - } - else if (name == "outline_color.b") { - value = text.getOutlineColor().b; - return true; - } - else if (name == "outline_color.a") { - value = text.getOutlineColor().a; - return true; - } - else if (name == "z_index") { - value = static_cast(z_index); - return true; - } - return false; -} - -bool UICaption::getProperty(const std::string& name, sf::Color& value) const { - if (name == "fill_color") { - value = text.getFillColor(); - return true; - } - else if (name == "outline_color") { - value = text.getOutlineColor(); - return true; - } - return false; -} - -bool UICaption::getProperty(const std::string& name, std::string& value) const { - if (name == "text") { - value = text.getString(); - return true; - } - return false; -} - diff --git a/src/UICaption.h b/src/UICaption.h index 60d8e13..7929f04 100644 --- a/src/UICaption.h +++ b/src/UICaption.h @@ -10,15 +10,6 @@ public: void render(sf::Vector2f, sf::RenderTarget&) override final; PyObjectsEnum derived_type() override final; virtual UIDrawable* click_at(sf::Vector2f point) override final; - - // Property system for animations - bool setProperty(const std::string& name, float value) override; - bool setProperty(const std::string& name, const sf::Color& value) override; - bool setProperty(const std::string& name, const std::string& value) override; - - bool getProperty(const std::string& name, float& value) const override; - bool getProperty(const std::string& name, sf::Color& value) const override; - bool getProperty(const std::string& name, std::string& value) const override; static PyObject* get_float_member(PyUICaptionObject* self, void* closure); static int set_float_member(PyUICaptionObject* self, PyObject* value, void* closure); diff --git a/src/UICollection.cpp b/src/UICollection.cpp index 309a994..1a9b605 100644 --- a/src/UICollection.cpp +++ b/src/UICollection.cpp @@ -6,8 +6,6 @@ #include "UIGrid.h" #include "McRFPy_API.h" #include "PyObjectUtils.h" -#include -#include using namespace mcrfpydef; @@ -150,394 +148,15 @@ PyObject* UICollection::getitem(PyUICollectionObject* self, Py_ssize_t index) { } -int UICollection::setitem(PyUICollectionObject* self, Py_ssize_t index, PyObject* value) { - auto vec = self->data.get(); - if (!vec) { - PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); - return -1; - } - - // Handle negative indexing - while (index < 0) index += self->data->size(); - - // Bounds check - if (index >= self->data->size()) { - PyErr_SetString(PyExc_IndexError, "UICollection assignment index out of range"); - return -1; - } - - // Handle deletion - if (value == NULL) { - self->data->erase(self->data->begin() + index); - return 0; - } - - // Type checking - must be a UIDrawable subclass - if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) && - !PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) && - !PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) && - !PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) { - PyErr_SetString(PyExc_TypeError, "UICollection can only contain Frame, Caption, Sprite, and Grid objects"); - return -1; - } - - // Get the C++ object from the Python object - std::shared_ptr new_drawable = nullptr; - int old_z_index = (*vec)[index]->z_index; // Preserve the z_index - - if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) { - PyUIFrameObject* frame = (PyUIFrameObject*)value; - new_drawable = frame->data; - } else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) { - PyUICaptionObject* caption = (PyUICaptionObject*)value; - new_drawable = caption->data; - } else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) { - PyUISpriteObject* sprite = (PyUISpriteObject*)value; - new_drawable = sprite->data; - } else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) { - PyUIGridObject* grid = (PyUIGridObject*)value; - new_drawable = grid->data; - } - - if (!new_drawable) { - PyErr_SetString(PyExc_RuntimeError, "Failed to extract C++ object from Python object"); - return -1; - } - - // Preserve the z_index of the replaced element - new_drawable->z_index = old_z_index; - - // Replace the element - (*vec)[index] = new_drawable; - - // Mark scene as needing resort after replacing element - McRFPy_API::markSceneNeedsSort(); - - return 0; -} - -int UICollection::contains(PyUICollectionObject* self, PyObject* value) { - auto vec = self->data.get(); - if (!vec) { - PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); - return -1; - } - - // Type checking - must be a UIDrawable subclass - if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) && - !PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) && - !PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) && - !PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) { - // Not a valid type, so it can't be in the collection - return 0; - } - - // Get the C++ object from the Python object - std::shared_ptr search_drawable = nullptr; - - if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) { - PyUIFrameObject* frame = (PyUIFrameObject*)value; - search_drawable = frame->data; - } else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) { - PyUICaptionObject* caption = (PyUICaptionObject*)value; - search_drawable = caption->data; - } else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) { - PyUISpriteObject* sprite = (PyUISpriteObject*)value; - search_drawable = sprite->data; - } else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) { - PyUIGridObject* grid = (PyUIGridObject*)value; - search_drawable = grid->data; - } - - if (!search_drawable) { - return 0; - } - - // Search for the object by comparing C++ pointers - for (const auto& drawable : *vec) { - if (drawable.get() == search_drawable.get()) { - return 1; // Found - } - } - - return 0; // Not found -} - -PyObject* UICollection::concat(PyUICollectionObject* self, PyObject* other) { - // Create a new Python list containing elements from both collections - if (!PySequence_Check(other)) { - PyErr_SetString(PyExc_TypeError, "can only concatenate sequence to UICollection"); - return NULL; - } - - Py_ssize_t self_len = self->data->size(); - Py_ssize_t other_len = PySequence_Length(other); - if (other_len == -1) { - return NULL; // Error already set - } - - PyObject* result_list = PyList_New(self_len + other_len); - if (!result_list) { - return NULL; - } - - // Add all elements from self - for (Py_ssize_t i = 0; i < self_len; i++) { - PyObject* item = convertDrawableToPython((*self->data)[i]); - if (!item) { - Py_DECREF(result_list); - return NULL; - } - PyList_SET_ITEM(result_list, i, item); // Steals reference - } - - // Add all elements from other - for (Py_ssize_t i = 0; i < other_len; i++) { - PyObject* item = PySequence_GetItem(other, i); - if (!item) { - Py_DECREF(result_list); - return NULL; - } - PyList_SET_ITEM(result_list, self_len + i, item); // Steals reference - } - - return result_list; -} - -PyObject* UICollection::inplace_concat(PyUICollectionObject* self, PyObject* other) { - if (!PySequence_Check(other)) { - PyErr_SetString(PyExc_TypeError, "can only concatenate sequence to UICollection"); - return NULL; - } - - // First, validate ALL items in the sequence before modifying anything - Py_ssize_t other_len = PySequence_Length(other); - if (other_len == -1) { - return NULL; // Error already set - } - - // Validate all items first - for (Py_ssize_t i = 0; i < other_len; i++) { - PyObject* item = PySequence_GetItem(other, i); - if (!item) { - return NULL; - } - - // Type check - if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) && - !PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) && - !PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) && - !PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) { - Py_DECREF(item); - PyErr_Format(PyExc_TypeError, - "UICollection can only contain Frame, Caption, Sprite, and Grid objects; " - "got %s at index %zd", Py_TYPE(item)->tp_name, i); - return NULL; - } - Py_DECREF(item); - } - - // All items validated, now we can safely add them - for (Py_ssize_t i = 0; i < other_len; i++) { - PyObject* item = PySequence_GetItem(other, i); - if (!item) { - return NULL; // Shouldn't happen, but be safe - } - - // Use the existing append method which handles z_index assignment - PyObject* result = append(self, item); - Py_DECREF(item); - - if (!result) { - return NULL; // append() failed - } - Py_DECREF(result); // append returns Py_None - } - - Py_INCREF(self); - return (PyObject*)self; -} - -PyObject* UICollection::subscript(PyUICollectionObject* self, PyObject* key) { - if (PyLong_Check(key)) { - // Single index - delegate to sq_item - Py_ssize_t index = PyLong_AsSsize_t(key); - if (index == -1 && PyErr_Occurred()) { - return NULL; - } - return getitem(self, index); - } else if (PySlice_Check(key)) { - // Handle slice - Py_ssize_t start, stop, step, slicelength; - - if (PySlice_GetIndicesEx(key, self->data->size(), &start, &stop, &step, &slicelength) < 0) { - return NULL; - } - - PyObject* result_list = PyList_New(slicelength); - if (!result_list) { - return NULL; - } - - for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) { - PyObject* item = convertDrawableToPython((*self->data)[cur]); - if (!item) { - Py_DECREF(result_list); - return NULL; - } - PyList_SET_ITEM(result_list, i, item); // Steals reference - } - - return result_list; - } else { - PyErr_Format(PyExc_TypeError, "UICollection indices must be integers or slices, not %.200s", - Py_TYPE(key)->tp_name); - return NULL; - } -} - -int UICollection::ass_subscript(PyUICollectionObject* self, PyObject* key, PyObject* value) { - if (PyLong_Check(key)) { - // Single index - delegate to sq_ass_item - Py_ssize_t index = PyLong_AsSsize_t(key); - if (index == -1 && PyErr_Occurred()) { - return -1; - } - return setitem(self, index, value); - } else if (PySlice_Check(key)) { - // Handle slice assignment/deletion - Py_ssize_t start, stop, step, slicelength; - - if (PySlice_GetIndicesEx(key, self->data->size(), &start, &stop, &step, &slicelength) < 0) { - return -1; - } - - if (value == NULL) { - // Deletion - if (step != 1) { - // For non-contiguous slices, delete from highest to lowest to maintain indices - std::vector indices; - for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) { - indices.push_back(cur); - } - // Sort in descending order and delete - std::sort(indices.begin(), indices.end(), std::greater()); - for (Py_ssize_t idx : indices) { - self->data->erase(self->data->begin() + idx); - } - } else { - // Contiguous slice - can delete in one go - self->data->erase(self->data->begin() + start, self->data->begin() + stop); - } - - // Mark scene as needing resort after slice deletion - McRFPy_API::markSceneNeedsSort(); - - return 0; - } else { - // Assignment - if (!PySequence_Check(value)) { - PyErr_SetString(PyExc_TypeError, "can only assign sequence to slice"); - return -1; - } - - Py_ssize_t value_len = PySequence_Length(value); - if (value_len == -1) { - return -1; - } - - // Validate all items first - std::vector> new_items; - for (Py_ssize_t i = 0; i < value_len; i++) { - PyObject* item = PySequence_GetItem(value, i); - if (!item) { - return -1; - } - - // Type check and extract C++ object - std::shared_ptr drawable = nullptr; - - if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) { - drawable = ((PyUIFrameObject*)item)->data; - } else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) { - drawable = ((PyUICaptionObject*)item)->data; - } else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) { - drawable = ((PyUISpriteObject*)item)->data; - } else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) { - drawable = ((PyUIGridObject*)item)->data; - } else { - Py_DECREF(item); - PyErr_Format(PyExc_TypeError, - "UICollection can only contain Frame, Caption, Sprite, and Grid objects; " - "got %s at index %zd", Py_TYPE(item)->tp_name, i); - return -1; - } - - Py_DECREF(item); - new_items.push_back(drawable); - } - - // Now perform the assignment - if (step == 1) { - // Contiguous slice - if (slicelength != value_len) { - // Need to resize - auto it_start = self->data->begin() + start; - auto it_stop = self->data->begin() + stop; - self->data->erase(it_start, it_stop); - self->data->insert(self->data->begin() + start, new_items.begin(), new_items.end()); - } else { - // Same size, just replace - for (Py_ssize_t i = 0; i < slicelength; i++) { - // Preserve z_index - new_items[i]->z_index = (*self->data)[start + i]->z_index; - (*self->data)[start + i] = new_items[i]; - } - } - } else { - // Extended slice - if (slicelength != value_len) { - PyErr_Format(PyExc_ValueError, - "attempt to assign sequence of size %zd to extended slice of size %zd", - value_len, slicelength); - return -1; - } - for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) { - // Preserve z_index - new_items[i]->z_index = (*self->data)[cur]->z_index; - (*self->data)[cur] = new_items[i]; - } - } - - // Mark scene as needing resort after slice assignment - McRFPy_API::markSceneNeedsSort(); - - return 0; - } - } else { - PyErr_Format(PyExc_TypeError, "UICollection indices must be integers or slices, not %.200s", - Py_TYPE(key)->tp_name); - return -1; - } -} - -PyMappingMethods UICollection::mpmethods = { - .mp_length = (lenfunc)UICollection::len, - .mp_subscript = (binaryfunc)UICollection::subscript, - .mp_ass_subscript = (objobjargproc)UICollection::ass_subscript -}; - PySequenceMethods UICollection::sqmethods = { .sq_length = (lenfunc)UICollection::len, - .sq_concat = (binaryfunc)UICollection::concat, - .sq_repeat = NULL, .sq_item = (ssizeargfunc)UICollection::getitem, - .was_sq_slice = NULL, - .sq_ass_item = (ssizeobjargproc)UICollection::setitem, - .was_sq_ass_slice = NULL, - .sq_contains = (objobjproc)UICollection::contains, - .sq_inplace_concat = (binaryfunc)UICollection::inplace_concat, - .sq_inplace_repeat = NULL + //.sq_item_by_index = PyUICollection_getitem + //.sq_slice - return a subset of the iterable + //.sq_ass_item - called when `o[x] = y` is executed (x is any object type) + //.sq_ass_slice - cool; no thanks, for now + //.sq_contains - called when `x in o` is executed + //.sq_ass_item_by_index - called when `o[x] = y` is executed (x is explictly an integer) }; /* Idiomatic way to fetch complete types from the API rather than referencing their PyTypeObject struct @@ -554,12 +173,6 @@ PyObject* UICollection::append(PyUICollectionObject* self, PyObject* o) // if not UIDrawable subclass, reject it // self->data->push_back( c++ object inside o ); - // Ensure module is initialized - if (!McRFPy_API::mcrf_module) { - PyErr_SetString(PyExc_RuntimeError, "mcrfpy module not initialized"); - return NULL; - } - // this would be a great use case for .tp_base if (!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) && !PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) && @@ -571,132 +184,31 @@ PyObject* UICollection::append(PyUICollectionObject* self, PyObject* o) return NULL; } - // Calculate z_index for the new element - int new_z_index = 0; - if (!self->data->empty()) { - // Get the z_index of the last element and add 10 - int last_z = self->data->back()->z_index; - if (last_z <= INT_MAX - 10) { - new_z_index = last_z + 10; - } else { - new_z_index = INT_MAX; - } - } - if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) { PyUIFrameObject* frame = (PyUIFrameObject*)o; - frame->data->z_index = new_z_index; self->data->push_back(frame->data); } if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) { PyUICaptionObject* caption = (PyUICaptionObject*)o; - caption->data->z_index = new_z_index; self->data->push_back(caption->data); } if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) { PyUISpriteObject* sprite = (PyUISpriteObject*)o; - sprite->data->z_index = new_z_index; self->data->push_back(sprite->data); } if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) { PyUIGridObject* grid = (PyUIGridObject*)o; - grid->data->z_index = new_z_index; self->data->push_back(grid->data); } - - // Mark scene as needing resort after adding element - McRFPy_API::markSceneNeedsSort(); Py_INCREF(Py_None); return Py_None; } -PyObject* UICollection::extend(PyUICollectionObject* self, PyObject* iterable) -{ - // Accept any iterable of UIDrawable objects - PyObject* iterator = PyObject_GetIter(iterable); - if (iterator == NULL) { - PyErr_SetString(PyExc_TypeError, "UICollection.extend requires an iterable"); - return NULL; - } - - // Ensure module is initialized - if (!McRFPy_API::mcrf_module) { - Py_DECREF(iterator); - PyErr_SetString(PyExc_RuntimeError, "mcrfpy module not initialized"); - return NULL; - } - - // Get current highest z_index - int current_z_index = 0; - if (!self->data->empty()) { - current_z_index = self->data->back()->z_index; - } - - PyObject* item; - while ((item = PyIter_Next(iterator)) != NULL) { - // Check if item is a UIDrawable subclass - if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) && - !PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) && - !PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) && - !PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) - { - Py_DECREF(item); - Py_DECREF(iterator); - PyErr_SetString(PyExc_TypeError, "All items must be Frame, Caption, Sprite, or Grid objects"); - return NULL; - } - - // Increment z_index for each new element - if (current_z_index <= INT_MAX - 10) { - current_z_index += 10; - } else { - current_z_index = INT_MAX; - } - - // Add the item based on its type - if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) { - PyUIFrameObject* frame = (PyUIFrameObject*)item; - frame->data->z_index = current_z_index; - self->data->push_back(frame->data); - } - else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) { - PyUICaptionObject* caption = (PyUICaptionObject*)item; - caption->data->z_index = current_z_index; - self->data->push_back(caption->data); - } - else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) { - PyUISpriteObject* sprite = (PyUISpriteObject*)item; - sprite->data->z_index = current_z_index; - self->data->push_back(sprite->data); - } - else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) { - PyUIGridObject* grid = (PyUIGridObject*)item; - grid->data->z_index = current_z_index; - self->data->push_back(grid->data); - } - - Py_DECREF(item); - } - - Py_DECREF(iterator); - - // Check if iteration ended due to an error - if (PyErr_Occurred()) { - return NULL; - } - - // Mark scene as needing resort after adding elements - McRFPy_API::markSceneNeedsSort(); - - Py_INCREF(Py_None); - return Py_None; -} - PyObject* UICollection::remove(PyUICollectionObject* self, PyObject* o) { if (!PyLong_Check(o)) @@ -705,121 +217,27 @@ PyObject* UICollection::remove(PyUICollectionObject* self, PyObject* o) return NULL; } long index = PyLong_AsLong(o); - - // Handle negative indexing - while (index < 0) index += self->data->size(); - if (index >= self->data->size()) { PyErr_SetString(PyExc_ValueError, "Index out of range"); return NULL; } + else if (index < 0) + { + PyErr_SetString(PyExc_NotImplementedError, "reverse indexing is not implemented."); + return NULL; + } // release the shared pointer at self->data[index]; self->data->erase(self->data->begin() + index); - - // Mark scene as needing resort after removing element - McRFPy_API::markSceneNeedsSort(); - Py_INCREF(Py_None); return Py_None; } -PyObject* UICollection::index_method(PyUICollectionObject* self, PyObject* value) { - auto vec = self->data.get(); - if (!vec) { - PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); - return NULL; - } - - // Type checking - must be a UIDrawable subclass - if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) && - !PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) && - !PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) && - !PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) { - PyErr_SetString(PyExc_TypeError, "UICollection.index requires a Frame, Caption, Sprite, or Grid object"); - return NULL; - } - - // Get the C++ object from the Python object - std::shared_ptr search_drawable = nullptr; - - if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) { - search_drawable = ((PyUIFrameObject*)value)->data; - } else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) { - search_drawable = ((PyUICaptionObject*)value)->data; - } else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) { - search_drawable = ((PyUISpriteObject*)value)->data; - } else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) { - search_drawable = ((PyUIGridObject*)value)->data; - } - - if (!search_drawable) { - PyErr_SetString(PyExc_RuntimeError, "Failed to extract C++ object from Python object"); - return NULL; - } - - // Search for the object - for (size_t i = 0; i < vec->size(); i++) { - if ((*vec)[i].get() == search_drawable.get()) { - return PyLong_FromSsize_t(i); - } - } - - PyErr_SetString(PyExc_ValueError, "value not in UICollection"); - return NULL; -} - -PyObject* UICollection::count(PyUICollectionObject* self, PyObject* value) { - auto vec = self->data.get(); - if (!vec) { - PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); - return NULL; - } - - // Type checking - must be a UIDrawable subclass - if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) && - !PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) && - !PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) && - !PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) { - // Not a valid type, so count is 0 - return PyLong_FromLong(0); - } - - // Get the C++ object from the Python object - std::shared_ptr search_drawable = nullptr; - - if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) { - search_drawable = ((PyUIFrameObject*)value)->data; - } else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) { - search_drawable = ((PyUICaptionObject*)value)->data; - } else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) { - search_drawable = ((PyUISpriteObject*)value)->data; - } else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) { - search_drawable = ((PyUIGridObject*)value)->data; - } - - if (!search_drawable) { - return PyLong_FromLong(0); - } - - // Count occurrences - Py_ssize_t count = 0; - for (const auto& drawable : *vec) { - if (drawable.get() == search_drawable.get()) { - count++; - } - } - - return PyLong_FromSsize_t(count); -} - PyMethodDef UICollection::methods[] = { {"append", (PyCFunction)UICollection::append, METH_O}, - {"extend", (PyCFunction)UICollection::extend, METH_O}, + //{"extend", (PyCFunction)PyUICollection_extend, METH_O}, // TODO {"remove", (PyCFunction)UICollection::remove, METH_O}, - {"index", (PyCFunction)UICollection::index_method, METH_O}, - {"count", (PyCFunction)UICollection::count, METH_O}, {NULL, NULL, 0, NULL} }; @@ -828,47 +246,7 @@ PyObject* UICollection::repr(PyUICollectionObject* self) std::ostringstream ss; if (!self->data) ss << ""; else { - ss << "data->size() << " objects: "; - - // Count each type - int frame_count = 0, caption_count = 0, sprite_count = 0, grid_count = 0, other_count = 0; - for (auto& item : *self->data) { - switch(item->derived_type()) { - case PyObjectsEnum::UIFRAME: frame_count++; break; - case PyObjectsEnum::UICAPTION: caption_count++; break; - case PyObjectsEnum::UISPRITE: sprite_count++; break; - case PyObjectsEnum::UIGRID: grid_count++; break; - default: other_count++; break; - } - } - - // Build type summary - bool first = true; - if (frame_count > 0) { - ss << frame_count << " Frame" << (frame_count > 1 ? "s" : ""); - first = false; - } - if (caption_count > 0) { - if (!first) ss << ", "; - ss << caption_count << " Caption" << (caption_count > 1 ? "s" : ""); - first = false; - } - if (sprite_count > 0) { - if (!first) ss << ", "; - ss << sprite_count << " Sprite" << (sprite_count > 1 ? "s" : ""); - first = false; - } - if (grid_count > 0) { - if (!first) ss << ", "; - ss << grid_count << " Grid" << (grid_count > 1 ? "s" : ""); - first = false; - } - if (other_count > 0) { - if (!first) ss << ", "; - ss << other_count << " UIDrawable" << (other_count > 1 ? "s" : ""); - } - - ss << ")>"; + ss << "data->size() << " child objects)>"; } std::string repr_str = ss.str(); return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); diff --git a/src/UICollection.h b/src/UICollection.h index bb8d254..886fdd0 100644 --- a/src/UICollection.h +++ b/src/UICollection.h @@ -19,19 +19,9 @@ class UICollection public: static Py_ssize_t len(PyUICollectionObject* self); static PyObject* getitem(PyUICollectionObject* self, Py_ssize_t index); - static int setitem(PyUICollectionObject* self, Py_ssize_t index, PyObject* value); - static int contains(PyUICollectionObject* self, PyObject* value); - static PyObject* concat(PyUICollectionObject* self, PyObject* other); - static PyObject* inplace_concat(PyUICollectionObject* self, PyObject* other); static PySequenceMethods sqmethods; - static PyMappingMethods mpmethods; - static PyObject* subscript(PyUICollectionObject* self, PyObject* key); - static int ass_subscript(PyUICollectionObject* self, PyObject* key, PyObject* value); static PyObject* append(PyUICollectionObject* self, PyObject* o); - static PyObject* extend(PyUICollectionObject* self, PyObject* iterable); static PyObject* remove(PyUICollectionObject* self, PyObject* o); - static PyObject* index_method(PyUICollectionObject* self, PyObject* value); - static PyObject* count(PyUICollectionObject* self, PyObject* value); static PyMethodDef methods[]; static PyObject* repr(PyUICollectionObject* self); static int init(PyUICollectionObject* self, PyObject* args, PyObject* kwds); @@ -81,7 +71,6 @@ namespace mcrfpydef { }, .tp_repr = (reprfunc)UICollection::repr, .tp_as_sequence = &UICollection::sqmethods, - .tp_as_mapping = &UICollection::mpmethods, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = PyDoc_STR("Iterable, indexable collection of UI objects"), .tp_iter = (getiterfunc)UICollection::iter, diff --git a/src/UIDrawable.cpp b/src/UIDrawable.cpp index 553eaf5..693d5f6 100644 --- a/src/UIDrawable.cpp +++ b/src/UIDrawable.cpp @@ -4,7 +4,6 @@ #include "UISprite.h" #include "UIGrid.h" #include "GameEngine.h" -#include "McRFPy_API.h" UIDrawable::UIDrawable() { click_callable = NULL; } @@ -15,7 +14,7 @@ void UIDrawable::click_unregister() void UIDrawable::render() { - render(sf::Vector2f(), Resources::game->getRenderTarget()); + render(sf::Vector2f(), Resources::game->getWindow()); } PyObject* UIDrawable::get_click(PyObject* self, void* closure) { @@ -81,85 +80,3 @@ void UIDrawable::click_register(PyObject* callable) { click_callable = std::make_unique(callable); } - -PyObject* UIDrawable::get_int(PyObject* self, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); - UIDrawable* drawable = nullptr; - - switch (objtype) { - case PyObjectsEnum::UIFRAME: - drawable = ((PyUIFrameObject*)self)->data.get(); - break; - case PyObjectsEnum::UICAPTION: - drawable = ((PyUICaptionObject*)self)->data.get(); - break; - case PyObjectsEnum::UISPRITE: - drawable = ((PyUISpriteObject*)self)->data.get(); - break; - case PyObjectsEnum::UIGRID: - drawable = ((PyUIGridObject*)self)->data.get(); - break; - default: - PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance"); - return NULL; - } - - return PyLong_FromLong(drawable->z_index); -} - -int UIDrawable::set_int(PyObject* self, PyObject* value, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); - UIDrawable* drawable = nullptr; - - switch (objtype) { - case PyObjectsEnum::UIFRAME: - drawable = ((PyUIFrameObject*)self)->data.get(); - break; - case PyObjectsEnum::UICAPTION: - drawable = ((PyUICaptionObject*)self)->data.get(); - break; - case PyObjectsEnum::UISPRITE: - drawable = ((PyUISpriteObject*)self)->data.get(); - break; - case PyObjectsEnum::UIGRID: - drawable = ((PyUIGridObject*)self)->data.get(); - break; - default: - PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance"); - return -1; - } - - if (!PyLong_Check(value)) { - PyErr_SetString(PyExc_TypeError, "z_index must be an integer"); - return -1; - } - - long z = PyLong_AsLong(value); - if (z == -1 && PyErr_Occurred()) { - return -1; - } - - // Clamp to int range - if (z < INT_MIN) z = INT_MIN; - if (z > INT_MAX) z = INT_MAX; - - int old_z_index = drawable->z_index; - drawable->z_index = static_cast(z); - - // Notify of z_index change - if (old_z_index != drawable->z_index) { - drawable->notifyZIndexChanged(); - } - - return 0; -} - -void UIDrawable::notifyZIndexChanged() { - // Mark the current scene as needing sort - // This works for elements in the scene's ui_elements collection - McRFPy_API::markSceneNeedsSort(); - - // TODO: In the future, we could add parent tracking to handle Frame children - // For now, Frame children will need manual sorting or collection modification - // to trigger a resort -} diff --git a/src/UIDrawable.h b/src/UIDrawable.h index 4ff470f..9832d8d 100644 --- a/src/UIDrawable.h +++ b/src/UIDrawable.h @@ -42,27 +42,6 @@ public: static PyObject* get_click(PyObject* self, void* closure); static int set_click(PyObject* self, PyObject* value, void* closure); - static PyObject* get_int(PyObject* self, void* closure); - static int set_int(PyObject* self, PyObject* value, void* closure); - - // Z-order for rendering (lower values rendered first, higher values on top) - int z_index = 0; - - // Notification for z_index changes - void notifyZIndexChanged(); - - // Animation support - virtual bool setProperty(const std::string& name, float value) { return false; } - virtual bool setProperty(const std::string& name, int value) { return false; } - virtual bool setProperty(const std::string& name, const sf::Color& value) { return false; } - virtual bool setProperty(const std::string& name, const sf::Vector2f& value) { return false; } - virtual bool setProperty(const std::string& name, const std::string& value) { return false; } - - virtual bool getProperty(const std::string& name, float& value) const { return false; } - virtual bool getProperty(const std::string& name, int& value) const { return false; } - virtual bool getProperty(const std::string& name, sf::Color& value) const { return false; } - virtual bool getProperty(const std::string& name, sf::Vector2f& value) const { return false; } - virtual bool getProperty(const std::string& name, std::string& value) const { return false; } }; typedef struct { diff --git a/src/UIEntity.cpp b/src/UIEntity.cpp index 41f10fa..32fd3e7 100644 --- a/src/UIEntity.cpp +++ b/src/UIEntity.cpp @@ -2,8 +2,6 @@ #include "UIGrid.h" #include "McRFPy_API.h" #include "PyObjectUtils.h" -#include "PyVector.h" - UIEntity::UIEntity() {} // this will not work lol. TODO remove default constructor by finding the shared pointer inits that use it @@ -36,33 +34,6 @@ PyObject* UIEntity::at(PyUIEntityObject* self, PyObject* o) { } -PyObject* UIEntity::index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored)) { - // Check if entity has an associated grid - if (!self->data || !self->data->grid) { - PyErr_SetString(PyExc_RuntimeError, "Entity is not associated with a grid"); - return NULL; - } - - // Get the grid's entity collection - auto entities = self->data->grid->entities; - if (!entities) { - PyErr_SetString(PyExc_RuntimeError, "Grid has no entity collection"); - return NULL; - } - - // Find this entity in the collection - int index = 0; - for (auto it = entities->begin(); it != entities->end(); ++it, ++index) { - if (it->get() == self->data.get()) { - return PyLong_FromLong(index); - } - } - - // Entity not found in its grid's collection - PyErr_SetString(PyExc_ValueError, "Entity not found in its grid's entity collection"); - return NULL; -} - int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) { //static const char* keywords[] = { "x", "y", "texture", "sprite_index", "grid", nullptr }; //float x = 0.0f, y = 0.0f, scale = 1.0f; @@ -75,7 +46,7 @@ int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) { //if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O", // const_cast(keywords), &x, &y, &texture, &sprite_index, &grid)) - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OiO", + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOi|O", const_cast(keywords), &pos, &texture, &sprite_index, &grid)) { return -1; @@ -90,41 +61,33 @@ int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) { // check types for texture // - // Set Texture - allow None or use default + // Set Texture // - std::shared_ptr texture_ptr = nullptr; - if (texture != NULL && texture != Py_None && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){ - PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance or None"); + if (texture != NULL && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){ + PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance"); return -1; - } else if (texture != NULL && texture != Py_None) { - auto pytexture = (PyTextureObject*)texture; - texture_ptr = pytexture->data; - } else { - // Use default texture when None or not provided - texture_ptr = McRFPy_API::default_texture; - } - - if (!texture_ptr) { - PyErr_SetString(PyExc_RuntimeError, "No texture provided and no default texture available"); - return -1; - } + } /*else if (texture != NULL) // this section needs to go; texture isn't optional and isn't managed by the UI objects anymore + { + self->texture = texture; + Py_INCREF(texture); + } else + { + // default tex? + }*/ if (grid != NULL && !PyObject_IsInstance(grid, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) { PyErr_SetString(PyExc_TypeError, "grid must be a mcrfpy.Grid instance"); return -1; } + auto pytexture = (PyTextureObject*)texture; if (grid == NULL) self->data = std::make_shared(); else self->data = std::make_shared(*((PyUIGridObject*)grid)->data); - // Store reference to Python object - self->data->self = (PyObject*)self; - Py_INCREF(self); - // TODO - PyTextureObjects and IndexTextures are a little bit of a mess with shared/unshared pointers - self->data->sprite = UISprite(texture_ptr, sprite_index, sf::Vector2f(0,0), 1.0); + self->data->sprite = UISprite(pytexture->data, sprite_index, sf::Vector2f(0,0), 1.0); self->data->position = pos_result->data; if (grid != NULL) { PyUIGridObject* pygrid = (PyUIGridObject*)grid; @@ -141,40 +104,28 @@ PyObject* UIEntity::get_spritenumber(PyUIEntityObject* self, void* closure) { return PyLong_FromDouble(self->data->sprite.getSpriteIndex()); } -PyObject* sfVector2f_to_PyObject(sf::Vector2f vec) { - auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector"); - auto obj = (PyVectorObject*)type->tp_alloc(type, 0); - if (obj) { - obj->data = vec; - } - return (PyObject*)obj; +PyObject* sfVector2f_to_PyObject(sf::Vector2f vector) { + return Py_BuildValue("(ff)", vector.x, vector.y); } -PyObject* sfVector2i_to_PyObject(sf::Vector2i vec) { - auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector"); - auto obj = (PyVectorObject*)type->tp_alloc(type, 0); - if (obj) { - obj->data = sf::Vector2f(static_cast(vec.x), static_cast(vec.y)); - } - return (PyObject*)obj; +PyObject* sfVector2i_to_PyObject(sf::Vector2i vector) { + return Py_BuildValue("(ii)", vector.x, vector.y); } sf::Vector2f PyObject_to_sfVector2f(PyObject* obj) { - PyVectorObject* vec = PyVector::from_arg(obj); - if (!vec) { - // PyVector::from_arg already set the error - return sf::Vector2f(0, 0); + float x, y; + if (!PyArg_ParseTuple(obj, "ff", &x, &y)) { + return sf::Vector2f(); // TODO / reconsider this default: Return default vector on parse error } - return vec->data; + return sf::Vector2f(x, y); } sf::Vector2i PyObject_to_sfVector2i(PyObject* obj) { - PyVectorObject* vec = PyVector::from_arg(obj); - if (!vec) { - // PyVector::from_arg already set the error - return sf::Vector2i(0, 0); + int x, y; + if (!PyArg_ParseTuple(obj, "ii", &x, &y)) { + return sf::Vector2i(); // TODO / reconsider this default: Return default vector on parse error } - return sf::Vector2i(static_cast(vec->data.x), static_cast(vec->data.y)); + return sf::Vector2i(x, y); } // TODO - deprecate / remove this helper @@ -210,17 +161,9 @@ PyObject* UIEntity::get_position(PyUIEntityObject* self, void* closure) { int UIEntity::set_position(PyUIEntityObject* self, PyObject* value, void* closure) { if (reinterpret_cast(closure) == 0) { - sf::Vector2f vec = PyObject_to_sfVector2f(value); - if (PyErr_Occurred()) { - return -1; // Error already set by PyObject_to_sfVector2f - } - self->data->position = vec; + self->data->position = PyObject_to_sfVector2f(value); } else { - sf::Vector2i vec = PyObject_to_sfVector2i(value); - if (PyErr_Occurred()) { - return -1; // Error already set by PyObject_to_sfVector2i - } - self->data->collision_pos = vec; + self->data->collision_pos = PyObject_to_sfVector2i(value); } return 0; } @@ -246,7 +189,6 @@ int UIEntity::set_spritenumber(PyUIEntityObject* self, PyObject* value, void* cl PyMethodDef UIEntity::methods[] = { {"at", (PyCFunction)UIEntity::at, METH_O}, - {"index", (PyCFunction)UIEntity::index, METH_NOARGS, "Return the index of this entity in its grid's entity collection"}, {NULL, NULL, 0, NULL} }; @@ -254,8 +196,7 @@ PyGetSetDef UIEntity::getsetters[] = { {"draw_pos", (getter)UIEntity::get_position, (setter)UIEntity::set_position, "Entity position (graphically)", (void*)0}, {"pos", (getter)UIEntity::get_position, (setter)UIEntity::set_position, "Entity position (integer grid coordinates)", (void*)1}, {"gridstate", (getter)UIEntity::get_gridstate, NULL, "Grid point states for the entity", NULL}, - {"sprite_index", (getter)UIEntity::get_spritenumber, (setter)UIEntity::set_spritenumber, "Sprite index on the texture on the display", NULL}, - {"sprite_number", (getter)UIEntity::get_spritenumber, (setter)UIEntity::set_spritenumber, "Sprite index on the texture on the display (deprecated: use sprite_index)", NULL}, + {"sprite_number", (getter)UIEntity::get_spritenumber, (setter)UIEntity::set_spritenumber, "Sprite number (index) on the texture on the display", NULL}, {NULL} /* Sentinel */ }; @@ -264,57 +205,9 @@ PyObject* UIEntity::repr(PyUIEntityObject* self) { if (!self->data) ss << ""; else { auto ent = self->data; - ss << ""; } std::string repr_str = ss.str(); return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); } - -// Property system implementation for animations -bool UIEntity::setProperty(const std::string& name, float value) { - if (name == "x") { - position.x = value; - collision_pos.x = static_cast(value); - // Update sprite position based on grid position - // Note: This is a simplified version - actual grid-to-pixel conversion depends on grid properties - sprite.setPosition(sf::Vector2f(position.x, position.y)); - return true; - } - else if (name == "y") { - position.y = value; - collision_pos.y = static_cast(value); - // Update sprite position based on grid position - sprite.setPosition(sf::Vector2f(position.x, position.y)); - return true; - } - else if (name == "sprite_scale") { - sprite.setScale(sf::Vector2f(value, value)); - return true; - } - return false; -} - -bool UIEntity::setProperty(const std::string& name, int value) { - if (name == "sprite_index" || name == "sprite_number") { - sprite.setSpriteIndex(value); - return true; - } - return false; -} - -bool UIEntity::getProperty(const std::string& name, float& value) const { - if (name == "x") { - value = position.x; - return true; - } - else if (name == "y") { - value = position.y; - return true; - } - else if (name == "sprite_scale") { - value = sprite.getScale().x; // Assuming uniform scale - return true; - } - return false; -} diff --git a/src/UIEntity.h b/src/UIEntity.h index 16f3d3d..42ede28 100644 --- a/src/UIEntity.h +++ b/src/UIEntity.h @@ -35,7 +35,7 @@ static PyObject* UIGridPointStateVector_to_PyList(const std::vector grid; std::vector gridstate; UISprite sprite; @@ -46,13 +46,7 @@ public: UIEntity(); UIEntity(UIGrid&); - // Property system for animations - bool setProperty(const std::string& name, float value); - bool setProperty(const std::string& name, int value); - bool getProperty(const std::string& name, float& value) const; - static PyObject* at(PyUIEntityObject* self, PyObject* o); - static PyObject* index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored)); static int init(PyUIEntityObject* self, PyObject* args, PyObject* kwds); static PyObject* get_position(PyUIEntityObject* self, void* closure); diff --git a/src/UIFrame.cpp b/src/UIFrame.cpp index f6f7fa7..f382127 100644 --- a/src/UIFrame.cpp +++ b/src/UIFrame.cpp @@ -1,7 +1,6 @@ #include "UIFrame.h" #include "UICollection.h" #include "GameEngine.h" -#include "PyVector.h" UIDrawable* UIFrame::click_at(sf::Vector2f point) { @@ -52,15 +51,6 @@ void UIFrame::render(sf::Vector2f offset, sf::RenderTarget& target) target.draw(box); box.move(-offset); - // Sort children by z_index if needed - if (children_need_sort && !children->empty()) { - std::sort(children->begin(), children->end(), - [](const std::shared_ptr& a, const std::shared_ptr& b) { - return a->z_index < b->z_index; - }); - children_need_sort = false; - } - for (auto drawable : *children) { drawable->render(offset + box.getPosition(), target); } @@ -215,28 +205,6 @@ int UIFrame::set_color_member(PyUIFrameObject* self, PyObject* value, void* clos return 0; } -PyObject* UIFrame::get_pos(PyUIFrameObject* self, void* closure) -{ - auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector"); - auto obj = (PyVectorObject*)type->tp_alloc(type, 0); - if (obj) { - auto pos = self->data->box.getPosition(); - obj->data = sf::Vector2f(pos.x, pos.y); - } - return (PyObject*)obj; -} - -int UIFrame::set_pos(PyUIFrameObject* self, PyObject* value, void* closure) -{ - PyVectorObject* vec = PyVector::from_arg(value); - if (!vec) { - PyErr_SetString(PyExc_TypeError, "pos must be a Vector or convertible to Vector"); - return -1; - } - self->data->box.setPosition(vec->data); - return 0; -} - PyGetSetDef UIFrame::getsetters[] = { {"x", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "X coordinate of top-left corner", (void*)0}, {"y", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "Y coordinate of top-left corner", (void*)1}, @@ -247,8 +215,6 @@ PyGetSetDef UIFrame::getsetters[] = { {"outline_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Outline color of the rectangle", (void*)1}, {"children", (getter)UIFrame::get_children, NULL, "UICollection of objects on top of this one", NULL}, {"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UIFRAME}, - {"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIFRAME}, - {"pos", (getter)UIFrame::get_pos, (setter)UIFrame::set_pos, "Position as a Vector", NULL}, {NULL} }; @@ -280,29 +246,9 @@ int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds) PyObject* fill_color = 0; PyObject* outline_color = 0; - // First try to parse as (x, y, w, h, ...) if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffff|OOf", const_cast(keywords), &x, &y, &w, &h, &fill_color, &outline_color, &outline)) { - PyErr_Clear(); // Clear the error - - // Try to parse as ((x,y), w, h, ...) or (Vector, w, h, ...) - PyObject* pos_obj = nullptr; - const char* alt_keywords[] = { "pos", "w", "h", "fill_color", "outline_color", "outline", nullptr }; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "Off|OOf", const_cast(alt_keywords), - &pos_obj, &w, &h, &fill_color, &outline_color, &outline)) - { - return -1; - } - - // Convert position argument to x, y - PyVectorObject* vec = PyVector::from_arg(pos_obj); - if (!vec) { - PyErr_SetString(PyExc_TypeError, "First argument must be a tuple (x, y) or Vector when not providing x, y separately"); - return -1; - } - x = vec->data.x; - y = vec->data.y; + return -1; } self->data->box.setPosition(sf::Vector2f(x, y)); @@ -318,152 +264,3 @@ int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds) if (err_val) return err_val; return 0; } - -// Animation property system implementation -bool UIFrame::setProperty(const std::string& name, float value) { - if (name == "x") { - box.setPosition(sf::Vector2f(value, box.getPosition().y)); - return true; - } else if (name == "y") { - box.setPosition(sf::Vector2f(box.getPosition().x, value)); - return true; - } else if (name == "w") { - box.setSize(sf::Vector2f(value, box.getSize().y)); - return true; - } else if (name == "h") { - box.setSize(sf::Vector2f(box.getSize().x, value)); - return true; - } else if (name == "outline") { - box.setOutlineThickness(value); - return true; - } else if (name == "fill_color.r") { - auto color = box.getFillColor(); - color.r = std::clamp(static_cast(value), 0, 255); - box.setFillColor(color); - return true; - } else if (name == "fill_color.g") { - auto color = box.getFillColor(); - color.g = std::clamp(static_cast(value), 0, 255); - box.setFillColor(color); - return true; - } else if (name == "fill_color.b") { - auto color = box.getFillColor(); - color.b = std::clamp(static_cast(value), 0, 255); - box.setFillColor(color); - return true; - } else if (name == "fill_color.a") { - auto color = box.getFillColor(); - color.a = std::clamp(static_cast(value), 0, 255); - box.setFillColor(color); - return true; - } else if (name == "outline_color.r") { - auto color = box.getOutlineColor(); - color.r = std::clamp(static_cast(value), 0, 255); - box.setOutlineColor(color); - return true; - } else if (name == "outline_color.g") { - auto color = box.getOutlineColor(); - color.g = std::clamp(static_cast(value), 0, 255); - box.setOutlineColor(color); - return true; - } else if (name == "outline_color.b") { - auto color = box.getOutlineColor(); - color.b = std::clamp(static_cast(value), 0, 255); - box.setOutlineColor(color); - return true; - } else if (name == "outline_color.a") { - auto color = box.getOutlineColor(); - color.a = std::clamp(static_cast(value), 0, 255); - box.setOutlineColor(color); - return true; - } - return false; -} - -bool UIFrame::setProperty(const std::string& name, const sf::Color& value) { - if (name == "fill_color") { - box.setFillColor(value); - return true; - } else if (name == "outline_color") { - box.setOutlineColor(value); - return true; - } - return false; -} - -bool UIFrame::setProperty(const std::string& name, const sf::Vector2f& value) { - if (name == "position") { - box.setPosition(value); - return true; - } else if (name == "size") { - box.setSize(value); - return true; - } - return false; -} - -bool UIFrame::getProperty(const std::string& name, float& value) const { - if (name == "x") { - value = box.getPosition().x; - return true; - } else if (name == "y") { - value = box.getPosition().y; - return true; - } else if (name == "w") { - value = box.getSize().x; - return true; - } else if (name == "h") { - value = box.getSize().y; - return true; - } else if (name == "outline") { - value = box.getOutlineThickness(); - return true; - } else if (name == "fill_color.r") { - value = box.getFillColor().r; - return true; - } else if (name == "fill_color.g") { - value = box.getFillColor().g; - return true; - } else if (name == "fill_color.b") { - value = box.getFillColor().b; - return true; - } else if (name == "fill_color.a") { - value = box.getFillColor().a; - return true; - } else if (name == "outline_color.r") { - value = box.getOutlineColor().r; - return true; - } else if (name == "outline_color.g") { - value = box.getOutlineColor().g; - return true; - } else if (name == "outline_color.b") { - value = box.getOutlineColor().b; - return true; - } else if (name == "outline_color.a") { - value = box.getOutlineColor().a; - return true; - } - return false; -} - -bool UIFrame::getProperty(const std::string& name, sf::Color& value) const { - if (name == "fill_color") { - value = box.getFillColor(); - return true; - } else if (name == "outline_color") { - value = box.getOutlineColor(); - return true; - } - return false; -} - -bool UIFrame::getProperty(const std::string& name, sf::Vector2f& value) const { - if (name == "position") { - value = box.getPosition(); - return true; - } else if (name == "size") { - value = box.getSize(); - return true; - } - return false; -} diff --git a/src/UIFrame.h b/src/UIFrame.h index a296928..986dd1e 100644 --- a/src/UIFrame.h +++ b/src/UIFrame.h @@ -28,7 +28,6 @@ public: sf::RectangleShape box; float outline; std::shared_ptr>> children; - bool children_need_sort = true; // Dirty flag for z_index sorting optimization void render(sf::Vector2f, sf::RenderTarget&) override final; void move(sf::Vector2f); PyObjectsEnum derived_type() override final; @@ -40,20 +39,9 @@ public: static int set_float_member(PyUIFrameObject* self, PyObject* value, void* closure); static PyObject* get_color_member(PyUIFrameObject* self, void* closure); static int set_color_member(PyUIFrameObject* self, PyObject* value, void* closure); - static PyObject* get_pos(PyUIFrameObject* self, void* closure); - static int set_pos(PyUIFrameObject* self, PyObject* value, void* closure); static PyGetSetDef getsetters[]; static PyObject* repr(PyUIFrameObject* self); static int init(PyUIFrameObject* self, PyObject* args, PyObject* kwds); - - // Animation property system - bool setProperty(const std::string& name, float value) override; - bool setProperty(const std::string& name, const sf::Color& value) override; - bool setProperty(const std::string& name, const sf::Vector2f& value) override; - - bool getProperty(const std::string& name, float& value) const override; - bool getProperty(const std::string& name, sf::Color& value) const override; - bool getProperty(const std::string& name, sf::Vector2f& value) const override; }; namespace mcrfpydef { diff --git a/src/UIGrid.cpp b/src/UIGrid.cpp index aba72f6..94dd481 100644 --- a/src/UIGrid.cpp +++ b/src/UIGrid.cpp @@ -1,21 +1,14 @@ #include "UIGrid.h" #include "GameEngine.h" #include "McRFPy_API.h" -#include UIGrid::UIGrid() {} UIGrid::UIGrid(int gx, int gy, std::shared_ptr _ptex, sf::Vector2f _xy, sf::Vector2f _wh) : grid_x(gx), grid_y(gy), - zoom(1.0f), + zoom(1.0f), center_x((gx/2) * _ptex->sprite_width), center_y((gy/2) * _ptex->sprite_height), ptex(_ptex), points(gx * gy) { - // Use texture dimensions if available, otherwise use defaults - int cell_width = _ptex ? _ptex->sprite_width : DEFAULT_CELL_WIDTH; - int cell_height = _ptex ? _ptex->sprite_height : DEFAULT_CELL_HEIGHT; - - center_x = (gx/2) * cell_width; - center_y = (gy/2) * cell_height; entities = std::make_shared>>(); box.setSize(_wh); @@ -25,10 +18,7 @@ UIGrid::UIGrid(int gx, int gy, std::shared_ptr _ptex, sf::Vector2f _x // create renderTexture with maximum theoretical size; sprite can resize to show whatever amount needs to be rendered renderTexture.create(1920, 1080); // TODO - renderTexture should be window size; above 1080p this will cause rendering errors - // Only initialize sprite if texture is available - if (ptex) { - sprite = ptex->sprite(0); - } + sprite = ptex->sprite(0); output.setTextureRect( sf::IntRect(0, 0, @@ -50,17 +40,12 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target) sf::IntRect(0, 0, box.getSize().x, box.getSize().y)); renderTexture.clear(sf::Color(8, 8, 8, 255)); // TODO - UIGrid needs a "background color" field - - // Get cell dimensions - use texture if available, otherwise defaults - int cell_width = ptex ? ptex->sprite_width : DEFAULT_CELL_WIDTH; - int cell_height = ptex ? ptex->sprite_height : DEFAULT_CELL_HEIGHT; - // sprites that are visible according to zoom, center_x, center_y, and box width - float center_x_sq = center_x / cell_width; - float center_y_sq = center_y / cell_height; + float center_x_sq = center_x / ptex->sprite_width; + float center_y_sq = center_y / ptex->sprite_height; - float width_sq = box.getSize().x / (cell_width * zoom); - float height_sq = box.getSize().y / (cell_height * zoom); + float width_sq = box.getSize().x / (ptex->sprite_width * zoom); + float height_sq = box.getSize().y / (ptex->sprite_height * zoom); float left_edge = center_x_sq - (width_sq / 2.0); float top_edge = center_y_sq - (height_sq / 2.0); @@ -69,7 +54,7 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target) //sprite.setScale(sf::Vector2f(zoom, zoom)); sf::RectangleShape r; // for colors and overlays - r.setSize(sf::Vector2f(cell_width * zoom, cell_height * zoom)); + r.setSize(sf::Vector2f(ptex->sprite_width * zoom, ptex->sprite_height * zoom)); r.setOutlineThickness(0); int x_limit = left_edge + width_sq + 2; @@ -89,8 +74,8 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target) y+=1) { auto pixel_pos = sf::Vector2f( - (x*cell_width - left_spritepixels) * zoom, - (y*cell_height - top_spritepixels) * zoom ); + (x*ptex->sprite_width - left_spritepixels) * zoom, + (y*ptex->sprite_height - top_spritepixels) * zoom ); auto gridpoint = at(std::floor(x), std::floor(y)); @@ -100,10 +85,10 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target) r.setFillColor(gridpoint.color); renderTexture.draw(r); - // tilesprite - only draw if texture is available + // tilesprite // if discovered but not visible, set opacity to 90% // if not discovered... just don't draw it? - if (ptex && gridpoint.tilesprite != -1) { + if (gridpoint.tilesprite != -1) { sprite = ptex->sprite(gridpoint.tilesprite, pixel_pos, sf::Vector2f(zoom, zoom)); //setSprite(gridpoint.tilesprite);; renderTexture.draw(sprite); } @@ -119,8 +104,8 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target) //drawent.setScale(zoom, zoom); drawent.setScale(sf::Vector2f(zoom, zoom)); auto pixel_pos = sf::Vector2f( - (e->position.x*cell_width - left_spritepixels) * zoom, - (e->position.y*cell_height - top_spritepixels) * zoom ); + (e->position.x*ptex->sprite_width - left_spritepixels) * zoom, + (e->position.y*ptex->sprite_height - top_spritepixels) * zoom ); //drawent.setPosition(pixel_pos); //renderTexture.draw(drawent); drawent.render(pixel_pos, renderTexture); @@ -219,92 +204,46 @@ UIDrawable* UIGrid::click_at(sf::Vector2f point) int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) { int grid_x, grid_y; - PyObject* textureObj = Py_None; + PyObject* textureObj; //float box_x, box_y, box_w, box_h; - PyObject* pos = NULL; - PyObject* size = NULL; + PyObject* pos, *size; //if (!PyArg_ParseTuple(args, "iiOffff", &grid_x, &grid_y, &textureObj, &box_x, &box_y, &box_w, &box_h)) { - if (!PyArg_ParseTuple(args, "ii|OOO", &grid_x, &grid_y, &textureObj, &pos, &size)) { + if (!PyArg_ParseTuple(args, "iiOOO", &grid_x, &grid_y, &textureObj, &pos, &size)) { return -1; // If parsing fails, return an error } - // Default position and size if not provided - PyVectorObject* pos_result = NULL; - PyVectorObject* size_result = NULL; - - if (pos) { - pos_result = PyVector::from_arg(pos); - if (!pos_result) - { - PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__"); - return -1; - } - } else { - // Default position (0, 0) - PyObject* vector_class = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector"); - if (vector_class) { - PyObject* pos_obj = PyObject_CallFunction(vector_class, "ff", 0.0f, 0.0f); - Py_DECREF(vector_class); - if (pos_obj) { - pos_result = (PyVectorObject*)pos_obj; - } - } - if (!pos_result) { - PyErr_SetString(PyExc_RuntimeError, "Failed to create default position vector"); - return -1; - } + PyVectorObject* pos_result = PyVector::from_arg(pos); + if (!pos_result) + { + PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__"); + return -1; } - if (size) { - size_result = PyVector::from_arg(size); - if (!size_result) - { - PyErr_SetString(PyExc_TypeError, "size must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__"); - return -1; - } - } else { - // Default size based on grid dimensions - float default_w = grid_x * 16.0f; // Assuming 16 pixel tiles - float default_h = grid_y * 16.0f; - PyObject* vector_class = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector"); - if (vector_class) { - PyObject* size_obj = PyObject_CallFunction(vector_class, "ff", default_w, default_h); - Py_DECREF(vector_class); - if (size_obj) { - size_result = (PyVectorObject*)size_obj; - } - } - if (!size_result) { - PyErr_SetString(PyExc_RuntimeError, "Failed to create default size vector"); - return -1; - } + PyVectorObject* size_result = PyVector::from_arg(size); + if (!size_result) + { + PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__"); + return -1; } // Convert PyObject texture to IndexTexture* // This requires the texture object to have been initialized similar to UISprite's texture handling - - std::shared_ptr texture_ptr = nullptr; - - // Allow None for texture - use default texture in that case - if (textureObj != Py_None) { - //if (!PyObject_IsInstance(textureObj, (PyObject*)&PyTextureType)) { - if (!PyObject_IsInstance(textureObj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) { - PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance or None"); - return -1; - } - PyTextureObject* pyTexture = reinterpret_cast(textureObj); - texture_ptr = pyTexture->data; - } else { - // Use default texture when None is provided - texture_ptr = McRFPy_API::default_texture; + + //if (!PyObject_IsInstance(textureObj, (PyObject*)&PyTextureType)) { + if (!PyObject_IsInstance(textureObj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) { + PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance"); + return -1; } + PyTextureObject* pyTexture = reinterpret_cast(textureObj); + // TODO (7DRL day 2, item 4.) use shared_ptr / PyTextureObject on UIGrid + //IndexTexture* texture = pyTexture->data.get(); - // Initialize UIGrid - texture_ptr will be nullptr if texture was None + // Initialize UIGrid //self->data = new UIGrid(grid_x, grid_y, texture, sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h)); //self->data = std::make_shared(grid_x, grid_y, pyTexture->data, // sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h)); - self->data = std::make_shared(grid_x, grid_y, texture_ptr, pos_result->data, size_result->data); + self->data = std::make_shared(grid_x, grid_y, pyTexture->data, pos_result->data, size_result->data); return 0; // Success } @@ -312,14 +251,6 @@ PyObject* UIGrid::get_grid_size(PyUIGridObject* self, void* closure) { return Py_BuildValue("(ii)", self->data->grid_x, self->data->grid_y); } -PyObject* UIGrid::get_grid_x(PyUIGridObject* self, void* closure) { - return PyLong_FromLong(self->data->grid_x); -} - -PyObject* UIGrid::get_grid_y(PyUIGridObject* self, void* closure) { - return PyLong_FromLong(self->data->grid_y); -} - PyObject* UIGrid::get_position(PyUIGridObject* self, void* closure) { auto& box = self->data->box; return Py_BuildValue("(ff)", box.getPosition().x, box.getPosition().y); @@ -347,18 +278,6 @@ int UIGrid::set_size(PyUIGridObject* self, PyObject* value, void* closure) { return -1; } self->data->box.setSize(sf::Vector2f(w, h)); - - // Recreate renderTexture with new size to avoid rendering issues - // Add some padding to handle zoom and ensure we don't cut off content - unsigned int tex_width = static_cast(w * 1.5f); - unsigned int tex_height = static_cast(h * 1.5f); - - // Clamp to reasonable maximum to avoid GPU memory issues - tex_width = std::min(tex_width, 4096u); - tex_height = std::min(tex_height, 4096u); - - self->data->renderTexture.create(tex_width, tex_height); - return 0; } @@ -423,25 +342,9 @@ int UIGrid::set_float_member(PyUIGridObject* self, PyObject* value, void* closur else if (member_ptr == 1) // y self->data->box.setPosition(self->data->box.getPosition().x, val); else if (member_ptr == 2) // w - { self->data->box.setSize(sf::Vector2f(val, self->data->box.getSize().y)); - // Recreate renderTexture when width changes - unsigned int tex_width = static_cast(val * 1.5f); - unsigned int tex_height = static_cast(self->data->box.getSize().y * 1.5f); - tex_width = std::min(tex_width, 4096u); - tex_height = std::min(tex_height, 4096u); - self->data->renderTexture.create(tex_width, tex_height); - } else if (member_ptr == 3) // h - { self->data->box.setSize(sf::Vector2f(self->data->box.getSize().x, val)); - // Recreate renderTexture when height changes - unsigned int tex_width = static_cast(self->data->box.getSize().x * 1.5f); - unsigned int tex_height = static_cast(val * 1.5f); - tex_width = std::min(tex_width, 4096u); - tex_height = std::min(tex_height, 4096u); - self->data->renderTexture.create(tex_width, tex_height); - } else if (member_ptr == 4) // center_x self->data->center_x = val; else if (member_ptr == 5) // center_y @@ -462,16 +365,9 @@ PyObject* UIGrid::get_texture(PyUIGridObject* self, void* closure) { //return self->data->getTexture()->pyObject(); // PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState") //PyTextureObject* obj = (PyTextureObject*)((&PyTextureType)->tp_alloc(&PyTextureType, 0)); - - // Return None if no texture - auto texture = self->data->getTexture(); - if (!texture) { - Py_RETURN_NONE; - } - auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"); auto obj = (PyTextureObject*)type->tp_alloc(type, 0); - obj->data = texture; + obj->data = self->data->getTexture(); return (PyObject*)obj; } @@ -483,7 +379,7 @@ PyObject* UIGrid::py_at(PyUIGridObject* self, PyObject* o) return NULL; } if (x < 0 || x >= self->data->grid_x) { - PyErr_SetString(PyExc_ValueError, "x value out of range (0, Grid.grid_x)"); + PyErr_SetString(PyExc_ValueError, "x value out of range (0, Grid.grid_y)"); return NULL; } if (y < 0 || y >= self->data->grid_y) { @@ -501,7 +397,7 @@ PyObject* UIGrid::py_at(PyUIGridObject* self, PyObject* o) } PyMethodDef UIGrid::methods[] = { - {"at", (PyCFunction)UIGrid::py_at, METH_VARARGS}, + {"at", (PyCFunction)UIGrid::py_at, METH_O}, {NULL, NULL, 0, NULL} }; @@ -510,8 +406,6 @@ PyGetSetDef UIGrid::getsetters[] = { // TODO - refactor into get_vector_member with field identifier values `(void*)n` {"grid_size", (getter)UIGrid::get_grid_size, NULL, "Grid dimensions (grid_x, grid_y)", NULL}, - {"grid_x", (getter)UIGrid::get_grid_x, NULL, "Grid x dimension", NULL}, - {"grid_y", (getter)UIGrid::get_grid_y, NULL, "Grid y dimension", NULL}, {"position", (getter)UIGrid::get_position, (setter)UIGrid::set_position, "Position of the grid (x, y)", NULL}, {"size", (getter)UIGrid::get_size, (setter)UIGrid::set_size, "Size of the grid (width, height)", NULL}, {"center", (getter)UIGrid::get_center, (setter)UIGrid::set_center, "Grid coordinate at the center of the Grid's view (pan)", NULL}, @@ -529,7 +423,6 @@ PyGetSetDef UIGrid::getsetters[] = { {"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UIGRID}, {"texture", (getter)UIGrid::get_texture, NULL, "Texture of the grid", NULL}, //TODO 7DRL-day2-item5 - {"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIGRID}, {NULL} /* Sentinel */ }; @@ -599,13 +492,7 @@ PyObject* UIEntityCollectionIter::next(PyUIEntityCollectionIterObject* self) std::advance(l_begin, self->index-1); auto target = *l_begin; - // Return the stored Python object if it exists (preserves derived types) - if (target->self != nullptr) { - Py_INCREF(target->self); - return target->self; - } - - // Otherwise create and return a new Python Entity object + // Create and return a Python Entity object auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"); auto o = (PyUIEntityObject*)type->tp_alloc(type, 0); auto p = std::static_pointer_cast(target); @@ -646,211 +533,28 @@ PyObject* UIEntityCollection::getitem(PyUIEntityCollectionObject* self, Py_ssize auto l_begin = (*vec).begin(); std::advance(l_begin, index); auto target = *l_begin; //auto target = (*vec)[index]; - - // If the entity has a stored Python object reference, return that to preserve derived class - if (target->self != nullptr) { - Py_INCREF(target->self); - return target->self; - } - - // Otherwise, create a new base Entity object + //RET_PY_INSTANCE(target); + // construct and return an entity object that points directly into the UIGrid's entity vector + //PyUIEntityObject* o = (PyUIEntityObject*)((&PyUIEntityType)->tp_alloc(&PyUIEntityType, 0)); auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"); auto o = (PyUIEntityObject*)type->tp_alloc(type, 0); auto p = std::static_pointer_cast(target); o->data = p; return (PyObject*)o; -} +return NULL; -int UIEntityCollection::setitem(PyUIEntityCollectionObject* self, Py_ssize_t index, PyObject* value) { - auto list = self->data.get(); - if (!list) { - PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); - return -1; - } - - // Handle negative indexing - while (index < 0) index += list->size(); - - // Bounds check - if (index >= list->size()) { - PyErr_SetString(PyExc_IndexError, "EntityCollection assignment index out of range"); - return -1; - } - - // Get iterator to the target position - auto it = list->begin(); - std::advance(it, index); - - // Handle deletion - if (value == NULL) { - // Clear grid reference from the entity being removed - (*it)->grid = nullptr; - list->erase(it); - return 0; - } - - // Type checking - must be an Entity - if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) { - PyErr_SetString(PyExc_TypeError, "EntityCollection can only contain Entity objects"); - return -1; - } - - // Get the C++ object from the Python object - PyUIEntityObject* entity = (PyUIEntityObject*)value; - if (!entity->data) { - PyErr_SetString(PyExc_RuntimeError, "Invalid Entity object"); - return -1; - } - - // Clear grid reference from the old entity - (*it)->grid = nullptr; - - // Replace the element and set grid reference - *it = entity->data; - entity->data->grid = self->grid; - - return 0; -} -int UIEntityCollection::contains(PyUIEntityCollectionObject* self, PyObject* value) { - auto list = self->data.get(); - if (!list) { - PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); - return -1; - } - - // Type checking - must be an Entity - if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) { - // Not an Entity, so it can't be in the collection - return 0; - } - - // Get the C++ object from the Python object - PyUIEntityObject* entity = (PyUIEntityObject*)value; - if (!entity->data) { - return 0; - } - - // Search for the object by comparing C++ pointers - for (const auto& ent : *list) { - if (ent.get() == entity->data.get()) { - return 1; // Found - } - } - - return 0; // Not found -} - -PyObject* UIEntityCollection::concat(PyUIEntityCollectionObject* self, PyObject* other) { - // Create a new Python list containing elements from both collections - if (!PySequence_Check(other)) { - PyErr_SetString(PyExc_TypeError, "can only concatenate sequence to EntityCollection"); - return NULL; - } - - Py_ssize_t self_len = self->data->size(); - Py_ssize_t other_len = PySequence_Length(other); - if (other_len == -1) { - return NULL; // Error already set - } - - PyObject* result_list = PyList_New(self_len + other_len); - if (!result_list) { - return NULL; - } - - // Add all elements from self - Py_ssize_t idx = 0; - for (const auto& entity : *self->data) { - auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"); - auto obj = (PyUIEntityObject*)type->tp_alloc(type, 0); - if (obj) { - obj->data = entity; - PyList_SET_ITEM(result_list, idx, (PyObject*)obj); // Steals reference - } else { - Py_DECREF(result_list); - Py_DECREF(type); - return NULL; - } - Py_DECREF(type); - idx++; - } - - // Add all elements from other - for (Py_ssize_t i = 0; i < other_len; i++) { - PyObject* item = PySequence_GetItem(other, i); - if (!item) { - Py_DECREF(result_list); - return NULL; - } - PyList_SET_ITEM(result_list, self_len + i, item); // Steals reference - } - - return result_list; -} - -PyObject* UIEntityCollection::inplace_concat(PyUIEntityCollectionObject* self, PyObject* other) { - if (!PySequence_Check(other)) { - PyErr_SetString(PyExc_TypeError, "can only concatenate sequence to EntityCollection"); - return NULL; - } - - // First, validate ALL items in the sequence before modifying anything - Py_ssize_t other_len = PySequence_Length(other); - if (other_len == -1) { - return NULL; // Error already set - } - - // Validate all items first - for (Py_ssize_t i = 0; i < other_len; i++) { - PyObject* item = PySequence_GetItem(other, i); - if (!item) { - return NULL; - } - - // Type check - if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) { - Py_DECREF(item); - PyErr_Format(PyExc_TypeError, - "EntityCollection can only contain Entity objects; " - "got %s at index %zd", Py_TYPE(item)->tp_name, i); - return NULL; - } - Py_DECREF(item); - } - - // All items validated, now we can safely add them - for (Py_ssize_t i = 0; i < other_len; i++) { - PyObject* item = PySequence_GetItem(other, i); - if (!item) { - return NULL; // Shouldn't happen, but be safe - } - - // Use the existing append method which handles grid references - PyObject* result = append(self, item); - Py_DECREF(item); - - if (!result) { - return NULL; // append() failed - } - Py_DECREF(result); // append returns Py_None - } - - Py_INCREF(self); - return (PyObject*)self; } PySequenceMethods UIEntityCollection::sqmethods = { .sq_length = (lenfunc)UIEntityCollection::len, - .sq_concat = (binaryfunc)UIEntityCollection::concat, - .sq_repeat = NULL, .sq_item = (ssizeargfunc)UIEntityCollection::getitem, - .was_sq_slice = NULL, - .sq_ass_item = (ssizeobjargproc)UIEntityCollection::setitem, - .was_sq_ass_slice = NULL, - .sq_contains = (objobjproc)UIEntityCollection::contains, - .sq_inplace_concat = (binaryfunc)UIEntityCollection::inplace_concat, - .sq_inplace_repeat = NULL + //.sq_item_by_index = UIEntityCollection::getitem + //.sq_slice - return a subset of the iterable + //.sq_ass_item - called when `o[x] = y` is executed (x is any object type) + //.sq_ass_slice - cool; no thanks, for now + //.sq_contains - called when `x in o` is executed + //.sq_ass_item_by_index - called when `o[x] = y` is executed (x is explictly an integer) }; PyObject* UIEntityCollection::append(PyUIEntityCollectionObject* self, PyObject* o) @@ -877,340 +581,31 @@ PyObject* UIEntityCollection::remove(PyUIEntityCollectionObject* self, PyObject* { if (!PyLong_Check(o)) { - PyErr_SetString(PyExc_TypeError, "EntityCollection.remove requires an integer index to remove"); + PyErr_SetString(PyExc_TypeError, "UICollection.remove requires an integer index to remove"); return NULL; } long index = PyLong_AsLong(o); - - // Handle negative indexing - while (index < 0) index += self->data->size(); - if (index >= self->data->size()) { PyErr_SetString(PyExc_ValueError, "Index out of range"); return NULL; } + else if (index < 0) + { + PyErr_SetString(PyExc_NotImplementedError, "reverse indexing is not implemented."); + return NULL; + } - // Get iterator to the entity to remove - auto it = self->data->begin(); - std::advance(it, index); - - // Clear grid reference before removing - (*it)->grid = nullptr; - // release the shared pointer at correct part of the list - self->data->erase(it); + self->data->erase(std::next(self->data->begin(), index)); Py_INCREF(Py_None); return Py_None; } -PyObject* UIEntityCollection::extend(PyUIEntityCollectionObject* self, PyObject* o) -{ - // Accept any iterable of Entity objects - PyObject* iterator = PyObject_GetIter(o); - if (iterator == NULL) { - PyErr_SetString(PyExc_TypeError, "UIEntityCollection.extend requires an iterable"); - return NULL; - } - - PyObject* item; - while ((item = PyIter_Next(iterator)) != NULL) { - // Check if item is an Entity - if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) { - Py_DECREF(item); - Py_DECREF(iterator); - PyErr_SetString(PyExc_TypeError, "All items in iterable must be Entity objects"); - return NULL; - } - - // Add the entity to the collection - PyUIEntityObject* entity = (PyUIEntityObject*)item; - self->data->push_back(entity->data); - entity->data->grid = self->grid; - - Py_DECREF(item); - } - - Py_DECREF(iterator); - - // Check if iteration ended due to an error - if (PyErr_Occurred()) { - return NULL; - } - - Py_INCREF(Py_None); - return Py_None; -} - -PyObject* UIEntityCollection::index_method(PyUIEntityCollectionObject* self, PyObject* value) { - auto list = self->data.get(); - if (!list) { - PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); - return NULL; - } - - // Type checking - must be an Entity - if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) { - PyErr_SetString(PyExc_TypeError, "EntityCollection.index requires an Entity object"); - return NULL; - } - - // Get the C++ object from the Python object - PyUIEntityObject* entity = (PyUIEntityObject*)value; - if (!entity->data) { - PyErr_SetString(PyExc_RuntimeError, "Invalid Entity object"); - return NULL; - } - - // Search for the object - Py_ssize_t idx = 0; - for (const auto& ent : *list) { - if (ent.get() == entity->data.get()) { - return PyLong_FromSsize_t(idx); - } - idx++; - } - - PyErr_SetString(PyExc_ValueError, "Entity not in EntityCollection"); - return NULL; -} - -PyObject* UIEntityCollection::count(PyUIEntityCollectionObject* self, PyObject* value) { - auto list = self->data.get(); - if (!list) { - PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer"); - return NULL; - } - - // Type checking - must be an Entity - if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) { - // Not an Entity, so count is 0 - return PyLong_FromLong(0); - } - - // Get the C++ object from the Python object - PyUIEntityObject* entity = (PyUIEntityObject*)value; - if (!entity->data) { - return PyLong_FromLong(0); - } - - // Count occurrences - Py_ssize_t count = 0; - for (const auto& ent : *list) { - if (ent.get() == entity->data.get()) { - count++; - } - } - - return PyLong_FromSsize_t(count); -} - -PyObject* UIEntityCollection::subscript(PyUIEntityCollectionObject* self, PyObject* key) { - if (PyLong_Check(key)) { - // Single index - delegate to sq_item - Py_ssize_t index = PyLong_AsSsize_t(key); - if (index == -1 && PyErr_Occurred()) { - return NULL; - } - return getitem(self, index); - } else if (PySlice_Check(key)) { - // Handle slice - Py_ssize_t start, stop, step, slicelength; - - if (PySlice_GetIndicesEx(key, self->data->size(), &start, &stop, &step, &slicelength) < 0) { - return NULL; - } - - PyObject* result_list = PyList_New(slicelength); - if (!result_list) { - return NULL; - } - - // Iterate through the list with slice parameters - auto it = self->data->begin(); - for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) { - auto cur_it = it; - std::advance(cur_it, cur); - - auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"); - auto obj = (PyUIEntityObject*)type->tp_alloc(type, 0); - if (obj) { - obj->data = *cur_it; - PyList_SET_ITEM(result_list, i, (PyObject*)obj); // Steals reference - } else { - Py_DECREF(result_list); - Py_DECREF(type); - return NULL; - } - Py_DECREF(type); - } - - return result_list; - } else { - PyErr_Format(PyExc_TypeError, "EntityCollection indices must be integers or slices, not %.200s", - Py_TYPE(key)->tp_name); - return NULL; - } -} - -int UIEntityCollection::ass_subscript(PyUIEntityCollectionObject* self, PyObject* key, PyObject* value) { - if (PyLong_Check(key)) { - // Single index - delegate to sq_ass_item - Py_ssize_t index = PyLong_AsSsize_t(key); - if (index == -1 && PyErr_Occurred()) { - return -1; - } - return setitem(self, index, value); - } else if (PySlice_Check(key)) { - // Handle slice assignment/deletion - Py_ssize_t start, stop, step, slicelength; - - if (PySlice_GetIndicesEx(key, self->data->size(), &start, &stop, &step, &slicelength) < 0) { - return -1; - } - - if (value == NULL) { - // Deletion - if (step != 1) { - // For non-contiguous slices, delete from highest to lowest to maintain indices - std::vector indices; - for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) { - indices.push_back(cur); - } - // Sort in descending order - std::sort(indices.begin(), indices.end(), std::greater()); - - // Delete each index - for (Py_ssize_t idx : indices) { - auto it = self->data->begin(); - std::advance(it, idx); - (*it)->grid = nullptr; // Clear grid reference - self->data->erase(it); - } - } else { - // Contiguous slice - delete range - auto it_start = self->data->begin(); - auto it_stop = self->data->begin(); - std::advance(it_start, start); - std::advance(it_stop, stop); - - // Clear grid references - for (auto it = it_start; it != it_stop; ++it) { - (*it)->grid = nullptr; - } - - self->data->erase(it_start, it_stop); - } - return 0; - } else { - // Assignment - if (!PySequence_Check(value)) { - PyErr_SetString(PyExc_TypeError, "can only assign sequence to slice"); - return -1; - } - - Py_ssize_t value_len = PySequence_Length(value); - if (value_len == -1) { - return -1; - } - - // Validate all items first - std::vector> new_items; - for (Py_ssize_t i = 0; i < value_len; i++) { - PyObject* item = PySequence_GetItem(value, i); - if (!item) { - return -1; - } - - // Type check - if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) { - Py_DECREF(item); - PyErr_Format(PyExc_TypeError, - "EntityCollection can only contain Entity objects; " - "got %s at index %zd", Py_TYPE(item)->tp_name, i); - return -1; - } - - PyUIEntityObject* entity = (PyUIEntityObject*)item; - Py_DECREF(item); - new_items.push_back(entity->data); - } - - // Now perform the assignment - if (step == 1) { - // Contiguous slice - if (slicelength != value_len) { - // Need to resize - remove old items and insert new ones - auto it_start = self->data->begin(); - auto it_stop = self->data->begin(); - std::advance(it_start, start); - std::advance(it_stop, stop); - - // Clear grid references from old items - for (auto it = it_start; it != it_stop; ++it) { - (*it)->grid = nullptr; - } - - // Erase old range - it_start = self->data->erase(it_start, it_stop); - - // Insert new items - for (const auto& entity : new_items) { - entity->grid = self->grid; - it_start = self->data->insert(it_start, entity); - ++it_start; - } - } else { - // Same size, just replace - auto it = self->data->begin(); - std::advance(it, start); - for (const auto& entity : new_items) { - (*it)->grid = nullptr; // Clear old grid ref - *it = entity; - entity->grid = self->grid; // Set new grid ref - ++it; - } - } - } else { - // Extended slice - if (slicelength != value_len) { - PyErr_Format(PyExc_ValueError, - "attempt to assign sequence of size %zd to extended slice of size %zd", - value_len, slicelength); - return -1; - } - - auto list_it = self->data->begin(); - for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) { - auto cur_it = list_it; - std::advance(cur_it, cur); - (*cur_it)->grid = nullptr; // Clear old grid ref - *cur_it = new_items[i]; - new_items[i]->grid = self->grid; // Set new grid ref - } - } - - return 0; - } - } else { - PyErr_Format(PyExc_TypeError, "EntityCollection indices must be integers or slices, not %.200s", - Py_TYPE(key)->tp_name); - return -1; - } -} - -PyMappingMethods UIEntityCollection::mpmethods = { - .mp_length = (lenfunc)UIEntityCollection::len, - .mp_subscript = (binaryfunc)UIEntityCollection::subscript, - .mp_ass_subscript = (objobjargproc)UIEntityCollection::ass_subscript -}; - PyMethodDef UIEntityCollection::methods[] = { {"append", (PyCFunction)UIEntityCollection::append, METH_O}, - {"extend", (PyCFunction)UIEntityCollection::extend, METH_O}, + //{"extend", (PyCFunction)UIEntityCollection::extend, METH_O}, // TODO {"remove", (PyCFunction)UIEntityCollection::remove, METH_O}, - {"index", (PyCFunction)UIEntityCollection::index_method, METH_O}, - {"count", (PyCFunction)UIEntityCollection::count, METH_O}, {NULL, NULL, 0, NULL} }; @@ -1255,115 +650,3 @@ PyObject* UIEntityCollection::iter(PyUIEntityCollectionObject* self) Py_DECREF(iterType); return (PyObject*)iterObj; } - -// Property system implementation for animations -bool UIGrid::setProperty(const std::string& name, float value) { - if (name == "x") { - box.setPosition(sf::Vector2f(value, box.getPosition().y)); - output.setPosition(box.getPosition()); - return true; - } - else if (name == "y") { - box.setPosition(sf::Vector2f(box.getPosition().x, value)); - output.setPosition(box.getPosition()); - return true; - } - else if (name == "w" || name == "width") { - box.setSize(sf::Vector2f(value, box.getSize().y)); - output.setTextureRect(sf::IntRect(0, 0, box.getSize().x, box.getSize().y)); - return true; - } - else if (name == "h" || name == "height") { - box.setSize(sf::Vector2f(box.getSize().x, value)); - output.setTextureRect(sf::IntRect(0, 0, box.getSize().x, box.getSize().y)); - return true; - } - else if (name == "center_x") { - center_x = value; - return true; - } - else if (name == "center_y") { - center_y = value; - return true; - } - else if (name == "zoom") { - zoom = value; - return true; - } - else if (name == "z_index") { - z_index = static_cast(value); - return true; - } - return false; -} - -bool UIGrid::setProperty(const std::string& name, const sf::Vector2f& value) { - if (name == "position") { - box.setPosition(value); - output.setPosition(box.getPosition()); - return true; - } - else if (name == "size") { - box.setSize(value); - output.setTextureRect(sf::IntRect(0, 0, box.getSize().x, box.getSize().y)); - return true; - } - else if (name == "center") { - center_x = value.x; - center_y = value.y; - return true; - } - return false; -} - -bool UIGrid::getProperty(const std::string& name, float& value) const { - if (name == "x") { - value = box.getPosition().x; - return true; - } - else if (name == "y") { - value = box.getPosition().y; - return true; - } - else if (name == "w" || name == "width") { - value = box.getSize().x; - return true; - } - else if (name == "h" || name == "height") { - value = box.getSize().y; - return true; - } - else if (name == "center_x") { - value = center_x; - return true; - } - else if (name == "center_y") { - value = center_y; - return true; - } - else if (name == "zoom") { - value = zoom; - return true; - } - else if (name == "z_index") { - value = static_cast(z_index); - return true; - } - return false; -} - -bool UIGrid::getProperty(const std::string& name, sf::Vector2f& value) const { - if (name == "position") { - value = box.getPosition(); - return true; - } - else if (name == "size") { - value = box.getSize(); - return true; - } - else if (name == "center") { - value = sf::Vector2f(center_x, center_y); - return true; - } - return false; -} diff --git a/src/UIGrid.h b/src/UIGrid.h index a167c0b..410fea3 100644 --- a/src/UIGrid.h +++ b/src/UIGrid.h @@ -21,9 +21,6 @@ class UIGrid: public UIDrawable { private: std::shared_ptr ptex; - // Default cell dimensions when no texture is provided - static constexpr int DEFAULT_CELL_WIDTH = 16; - static constexpr int DEFAULT_CELL_HEIGHT = 16; public: UIGrid(); //UIGrid(int, int, IndexTexture*, float, float, float, float); @@ -45,17 +42,9 @@ public: sf::RenderTexture renderTexture; std::vector points; std::shared_ptr>> entities; - - // Property system for animations - bool setProperty(const std::string& name, float value) override; - bool setProperty(const std::string& name, const sf::Vector2f& value) override; - bool getProperty(const std::string& name, float& value) const override; - bool getProperty(const std::string& name, sf::Vector2f& value) const override; static int init(PyUIGridObject* self, PyObject* args, PyObject* kwds); static PyObject* get_grid_size(PyUIGridObject* self, void* closure); - static PyObject* get_grid_x(PyUIGridObject* self, void* closure); - static PyObject* get_grid_y(PyUIGridObject* self, void* closure); static PyObject* get_position(PyUIGridObject* self, void* closure); static int set_position(PyUIGridObject* self, PyObject* value, void* closure); static PyObject* get_size(PyUIGridObject* self, void* closure); @@ -82,24 +71,14 @@ typedef struct { class UIEntityCollection { public: static PySequenceMethods sqmethods; - static PyMappingMethods mpmethods; static PyObject* append(PyUIEntityCollectionObject* self, PyObject* o); - static PyObject* extend(PyUIEntityCollectionObject* self, PyObject* o); static PyObject* remove(PyUIEntityCollectionObject* self, PyObject* o); - static PyObject* index_method(PyUIEntityCollectionObject* self, PyObject* value); - static PyObject* count(PyUIEntityCollectionObject* self, PyObject* value); static PyMethodDef methods[]; static PyObject* repr(PyUIEntityCollectionObject* self); static int init(PyUIEntityCollectionObject* self, PyObject* args, PyObject* kwds); static PyObject* iter(PyUIEntityCollectionObject* self); static Py_ssize_t len(PyUIEntityCollectionObject* self); static PyObject* getitem(PyUIEntityCollectionObject* self, Py_ssize_t index); - static int setitem(PyUIEntityCollectionObject* self, Py_ssize_t index, PyObject* value); - static int contains(PyUIEntityCollectionObject* self, PyObject* value); - static PyObject* concat(PyUIEntityCollectionObject* self, PyObject* other); - static PyObject* inplace_concat(PyUIEntityCollectionObject* self, PyObject* other); - static PyObject* subscript(PyUIEntityCollectionObject* self, PyObject* key); - static int ass_subscript(PyUIEntityCollectionObject* self, PyObject* key, PyObject* value); }; typedef struct { @@ -189,7 +168,6 @@ namespace mcrfpydef { }, .tp_repr = (reprfunc)UIEntityCollection::repr, .tp_as_sequence = &UIEntityCollection::sqmethods, - .tp_as_mapping = &UIEntityCollection::mpmethods, .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = PyDoc_STR("Iterable, indexable collection of Entities"), .tp_iter = (getiterfunc)UIEntityCollection::iter, diff --git a/src/UIGridPoint.h b/src/UIGridPoint.h index 888c387..06af9d4 100644 --- a/src/UIGridPoint.h +++ b/src/UIGridPoint.h @@ -75,7 +75,7 @@ namespace mcrfpydef { .tp_doc = "UIGridPoint object", .tp_getset = UIGridPoint::getsetters, //.tp_init = (initproc)PyUIGridPoint_init, // TODO Define the init function - .tp_new = NULL, // Prevent instantiation from Python - Issue #12 + .tp_new = PyType_GenericNew, }; static PyTypeObject PyUIGridPointStateType = { @@ -87,6 +87,6 @@ namespace mcrfpydef { .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = "UIGridPointState object", // TODO: Add PyUIGridPointState tp_init .tp_getset = UIGridPointState::getsetters, - .tp_new = NULL, // Prevent instantiation from Python - Issue #12 + .tp_new = PyType_GenericNew, }; } diff --git a/src/UISprite.cpp b/src/UISprite.cpp index e69d37e..1441753 100644 --- a/src/UISprite.cpp +++ b/src/UISprite.cpp @@ -1,6 +1,5 @@ #include "UISprite.h" #include "GameEngine.h" -#include "PyVector.h" UIDrawable* UISprite::click_at(sf::Vector2f point) { @@ -59,7 +58,7 @@ void UISprite::setSpriteIndex(int _sprite_index) sprite = ptex->sprite(sprite_index, sprite.getPosition(), sprite.getScale()); } -sf::Vector2f UISprite::getScale() const +sf::Vector2f UISprite::getScale() { return sprite.getScale(); } @@ -93,10 +92,6 @@ PyObject* UISprite::get_float_member(PyUISpriteObject* self, void* closure) return PyFloat_FromDouble(self->data->getPosition().y); else if (member_ptr == 2) return PyFloat_FromDouble(self->data->getScale().x); // scale X and Y are identical, presently - else if (member_ptr == 3) - return PyFloat_FromDouble(self->data->getScale().x); // scale_x - else if (member_ptr == 4) - return PyFloat_FromDouble(self->data->getScale().y); // scale_y else { PyErr_SetString(PyExc_AttributeError, "Invalid attribute"); @@ -125,12 +120,8 @@ int UISprite::set_float_member(PyUISpriteObject* self, PyObject* value, void* cl self->data->setPosition(sf::Vector2f(val, self->data->getPosition().y)); else if (member_ptr == 1) //y self->data->setPosition(sf::Vector2f(self->data->getPosition().x, val)); - else if (member_ptr == 2) // scale (uniform) + else if (member_ptr == 2) // scale self->data->setScale(sf::Vector2f(val, val)); - else if (member_ptr == 3) // scale_x - self->data->setScale(sf::Vector2f(val, self->data->getScale().y)); - else if (member_ptr == 4) // scale_y - self->data->setScale(sf::Vector2f(self->data->getScale().x, val)); return 0; } @@ -160,20 +151,6 @@ int UISprite::set_int_member(PyUISpriteObject* self, PyObject* value, void* clos PyErr_SetString(PyExc_TypeError, "Value must be an integer."); return -1; } - - // Validate sprite index is within texture bounds - auto texture = self->data->getTexture(); - if (texture) { - int sprite_count = texture->getSpriteCount(); - - if (val < 0 || val >= sprite_count) { - PyErr_Format(PyExc_ValueError, - "Sprite index %d out of range. Texture has %d sprites (0-%d)", - val, sprite_count, sprite_count - 1); - return -1; - } - } - self->data->setSpriteIndex(val); return 0; } @@ -185,59 +162,16 @@ PyObject* UISprite::get_texture(PyUISpriteObject* self, void* closure) int UISprite::set_texture(PyUISpriteObject* self, PyObject* value, void* closure) { - // Check if value is a Texture instance - if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) { - PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance"); - return -1; - } - - // Get the texture from the Python object - auto pytexture = (PyTextureObject*)value; - if (!pytexture->data) { - PyErr_SetString(PyExc_ValueError, "Invalid texture object"); - return -1; - } - - // Update the sprite's texture - self->data->setTexture(pytexture->data); - - return 0; -} - -PyObject* UISprite::get_pos(PyUISpriteObject* self, void* closure) -{ - auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector"); - auto obj = (PyVectorObject*)type->tp_alloc(type, 0); - if (obj) { - auto pos = self->data->getPosition(); - obj->data = sf::Vector2f(pos.x, pos.y); - } - return (PyObject*)obj; -} - -int UISprite::set_pos(PyUISpriteObject* self, PyObject* value, void* closure) -{ - PyVectorObject* vec = PyVector::from_arg(value); - if (!vec) { - PyErr_SetString(PyExc_TypeError, "pos must be a Vector or convertible to Vector"); - return -1; - } - self->data->setPosition(vec->data); - return 0; + return -1; } PyGetSetDef UISprite::getsetters[] = { {"x", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "X coordinate of top-left corner", (void*)0}, {"y", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Y coordinate of top-left corner", (void*)1}, - {"scale", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Uniform size factor", (void*)2}, - {"scale_x", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Horizontal scale factor", (void*)3}, - {"scale_y", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Vertical scale factor", (void*)4}, - {"sprite_index", (getter)UISprite::get_int_member, (setter)UISprite::set_int_member, "Which sprite on the texture is shown", NULL}, - {"sprite_number", (getter)UISprite::get_int_member, (setter)UISprite::set_int_member, "Which sprite on the texture is shown (deprecated: use sprite_index)", NULL}, + {"scale", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Size factor", (void*)2}, + {"sprite_number", (getter)UISprite::get_int_member, (setter)UISprite::set_int_member, "Which sprite on the texture is shown", NULL}, {"texture", (getter)UISprite::get_texture, (setter)UISprite::set_texture, "Texture object", NULL}, {"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UISPRITE}, - {"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UISPRITE}, - {"pos", (getter)UISprite::get_pos, (setter)UISprite::set_pos, "Position as a Vector", NULL}, {NULL} }; @@ -249,7 +183,7 @@ PyObject* UISprite::repr(PyUISpriteObject* self) //auto sprite = self->data->sprite; ss << ""; + "sprite_number=" << self->data->getSpriteIndex() << ")>"; } std::string repr_str = ss.str(); return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace"); @@ -260,138 +194,24 @@ int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds) //std::cout << "Init called\n"; static const char* keywords[] = { "x", "y", "texture", "sprite_index", "scale", nullptr }; float x = 0.0f, y = 0.0f, scale = 1.0f; - int sprite_index = 0; - PyObject* texture = NULL; + int sprite_index; + PyObject* texture; - // First try to parse as (x, y, texture, ...) if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffOif", const_cast(keywords), &x, &y, &texture, &sprite_index, &scale)) { - PyErr_Clear(); // Clear the error - - // Try to parse as ((x,y), texture, ...) or (Vector, texture, ...) - PyObject* pos_obj = nullptr; - const char* alt_keywords[] = { "pos", "texture", "sprite_index", "scale", nullptr }; - - if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOif", const_cast(alt_keywords), - &pos_obj, &texture, &sprite_index, &scale)) - { - return -1; - } - - // Convert position argument to x, y - if (pos_obj) { - PyVectorObject* vec = PyVector::from_arg(pos_obj); - if (!vec) { - PyErr_SetString(PyExc_TypeError, "First argument must be a tuple (x, y) or Vector when not providing x, y separately"); - return -1; - } - x = vec->data.x; - y = vec->data.y; - } + return -1; } - // Handle texture - allow None or use default - std::shared_ptr texture_ptr = nullptr; - if (texture != NULL && texture != Py_None && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){ - PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance or None"); - return -1; - } else if (texture != NULL && texture != Py_None) { - auto pytexture = (PyTextureObject*)texture; - texture_ptr = pytexture->data; - } else { - // Use default texture when None or not provided - texture_ptr = McRFPy_API::default_texture; - } - - if (!texture_ptr) { - PyErr_SetString(PyExc_RuntimeError, "No texture provided and no default texture available"); + // check types for texture + //if (texture != NULL && !PyObject_IsInstance(texture, (PyObject*)&PyTextureType)){ + if (texture != NULL && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){ + PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance"); return -1; } - - self->data = std::make_shared(texture_ptr, sprite_index, sf::Vector2f(x, y), scale); + auto pytexture = (PyTextureObject*)texture; + self->data = std::make_shared(pytexture->data, sprite_index, sf::Vector2f(x, y), scale); self->data->setPosition(sf::Vector2f(x, y)); return 0; } - -// Property system implementation for animations -bool UISprite::setProperty(const std::string& name, float value) { - if (name == "x") { - sprite.setPosition(sf::Vector2f(value, sprite.getPosition().y)); - return true; - } - else if (name == "y") { - sprite.setPosition(sf::Vector2f(sprite.getPosition().x, value)); - return true; - } - else if (name == "scale") { - sprite.setScale(sf::Vector2f(value, value)); - return true; - } - else if (name == "scale_x") { - sprite.setScale(sf::Vector2f(value, sprite.getScale().y)); - return true; - } - else if (name == "scale_y") { - sprite.setScale(sf::Vector2f(sprite.getScale().x, value)); - return true; - } - else if (name == "z_index") { - z_index = static_cast(value); - return true; - } - return false; -} - -bool UISprite::setProperty(const std::string& name, int value) { - if (name == "sprite_index" || name == "sprite_number") { - setSpriteIndex(value); - return true; - } - else if (name == "z_index") { - z_index = value; - return true; - } - return false; -} - -bool UISprite::getProperty(const std::string& name, float& value) const { - if (name == "x") { - value = sprite.getPosition().x; - return true; - } - else if (name == "y") { - value = sprite.getPosition().y; - return true; - } - else if (name == "scale") { - value = sprite.getScale().x; // Assuming uniform scale - return true; - } - else if (name == "scale_x") { - value = sprite.getScale().x; - return true; - } - else if (name == "scale_y") { - value = sprite.getScale().y; - return true; - } - else if (name == "z_index") { - value = static_cast(z_index); - return true; - } - return false; -} - -bool UISprite::getProperty(const std::string& name, int& value) const { - if (name == "sprite_index" || name == "sprite_number") { - value = sprite_index; - return true; - } - else if (name == "z_index") { - value = z_index; - return true; - } - return false; -} diff --git a/src/UISprite.h b/src/UISprite.h index 060b2c2..0b172c6 100644 --- a/src/UISprite.h +++ b/src/UISprite.h @@ -33,7 +33,7 @@ public: void setPosition(sf::Vector2f); sf::Vector2f getPosition(); void setScale(sf::Vector2f); - sf::Vector2f getScale() const; + sf::Vector2f getScale(); void setSpriteIndex(int); int getSpriteIndex(); @@ -41,12 +41,6 @@ public: std::shared_ptr getTexture(); PyObjectsEnum derived_type() override final; - - // Property system for animations - bool setProperty(const std::string& name, float value) override; - bool setProperty(const std::string& name, int value) override; - bool getProperty(const std::string& name, float& value) const override; - bool getProperty(const std::string& name, int& value) const override; static PyObject* get_float_member(PyUISpriteObject* self, void* closure); @@ -55,8 +49,6 @@ public: static int set_int_member(PyUISpriteObject* self, PyObject* value, void* closure); static PyObject* get_texture(PyUISpriteObject* self, void* closure); static int set_texture(PyUISpriteObject* self, PyObject* value, void* closure); - static PyObject* get_pos(PyUISpriteObject* self, void* closure); - static int set_pos(PyUISpriteObject* self, PyObject* value, void* closure); static PyGetSetDef getsetters[]; static PyObject* repr(PyUISpriteObject* self); static int init(PyUISpriteObject* self, PyObject* args, PyObject* kwds); diff --git a/src/UITestScene.cpp b/src/UITestScene.cpp index d3d5ff9..17f2416 100644 --- a/src/UITestScene.cpp +++ b/src/UITestScene.cpp @@ -156,8 +156,8 @@ void UITestScene::doAction(std::string name, std::string type) void UITestScene::render() { - game->getRenderTarget().clear(); - game->getRenderTarget().draw(text); + game->getWindow().clear(); + game->getWindow().draw(text); // draw all UI elements //for (auto e: ui_elements) @@ -175,7 +175,7 @@ void UITestScene::render() //e1.render(sf::Vector2f(-100, -100)); - // Display is handled by GameEngine + game->getWindow().display(); //McRFPy_API::REPL(); } diff --git a/src/main.cpp b/src/main.cpp index e0e9835..e4e355b 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,204 +1,8 @@ #include #include "GameEngine.h" -#include "CommandLineParser.h" -#include "McRogueFaceConfig.h" -#include "McRFPy_API.h" -#include "PyFont.h" -#include "PyTexture.h" -#include -#include -#include -// Forward declarations -int run_game_engine(const McRogueFaceConfig& config); -int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv[]); - -int main(int argc, char* argv[]) +int main() { - McRogueFaceConfig config; - CommandLineParser parser(argc, argv); - - // Parse arguments - auto parse_result = parser.parse(config); - if (parse_result.should_exit) { - return parse_result.exit_code; - } - - // Special handling for -m module: let Python handle modules properly - if (!config.python_module.empty()) { - config.python_mode = true; - } - - // Initialize based on configuration - if (config.python_mode) { - return run_python_interpreter(config, argc, argv); - } else { - return run_game_engine(config); - } -} - -int run_game_engine(const McRogueFaceConfig& config) -{ - GameEngine g(config); + GameEngine g; g.run(); - return 0; -} - -int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv[]) -{ - // Create a game engine with the requested configuration - GameEngine* engine = new GameEngine(config); - - // Initialize Python with configuration - McRFPy_API::init_python_with_config(config, argc, argv); - - // Import mcrfpy module and store reference - McRFPy_API::mcrf_module = PyImport_ImportModule("mcrfpy"); - if (!McRFPy_API::mcrf_module) { - PyErr_Print(); - std::cerr << "Failed to import mcrfpy module" << std::endl; - } else { - // Set up default_font and default_texture if not already done - if (!McRFPy_API::default_font) { - McRFPy_API::default_font = std::make_shared("assets/JetbrainsMono.ttf"); - McRFPy_API::default_texture = std::make_shared("assets/kenney_tinydungeon.png", 16, 16); - } - PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_font", McRFPy_API::default_font->pyObject()); - PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_texture", McRFPy_API::default_texture->pyObject()); - } - - // Handle different Python modes - if (!config.python_command.empty()) { - // Execute command from -c - if (config.interactive_mode) { - // Use PyRun_String to catch SystemExit - PyObject* main_module = PyImport_AddModule("__main__"); - PyObject* main_dict = PyModule_GetDict(main_module); - PyObject* result_obj = PyRun_String(config.python_command.c_str(), - Py_file_input, main_dict, main_dict); - - if (result_obj == NULL) { - // Check if it's SystemExit - if (PyErr_Occurred()) { - PyObject *type, *value, *traceback; - PyErr_Fetch(&type, &value, &traceback); - - // If it's SystemExit and we're in interactive mode, clear it - if (PyErr_GivenExceptionMatches(type, PyExc_SystemExit)) { - PyErr_Clear(); - } else { - // Re-raise other exceptions - PyErr_Restore(type, value, traceback); - PyErr_Print(); - } - - Py_XDECREF(type); - Py_XDECREF(value); - Py_XDECREF(traceback); - } - } else { - Py_DECREF(result_obj); - } - // Continue to interactive mode below - } else { - int result = PyRun_SimpleString(config.python_command.c_str()); - Py_Finalize(); - delete engine; - return result; - } - } - else if (!config.python_module.empty()) { - // Execute module using runpy - std::string run_module_code = - "import sys\n" - "import runpy\n" - "sys.argv = ['" + config.python_module + "'"; - - for (const auto& arg : config.script_args) { - run_module_code += ", '" + arg + "'"; - } - run_module_code += "]\n"; - run_module_code += "runpy.run_module('" + config.python_module + "', run_name='__main__', alter_sys=True)\n"; - - int result = PyRun_SimpleString(run_module_code.c_str()); - Py_Finalize(); - delete engine; - return result; - } - else if (!config.script_path.empty()) { - // Execute script file - FILE* fp = fopen(config.script_path.string().c_str(), "r"); - if (!fp) { - std::cerr << "mcrogueface: can't open file '" << config.script_path << "': "; - std::cerr << "[Errno " << errno << "] " << strerror(errno) << std::endl; - return 1; - } - - // Set up sys.argv - wchar_t** python_argv = new wchar_t*[config.script_args.size() + 1]; - python_argv[0] = Py_DecodeLocale(config.script_path.string().c_str(), nullptr); - for (size_t i = 0; i < config.script_args.size(); i++) { - python_argv[i + 1] = Py_DecodeLocale(config.script_args[i].c_str(), nullptr); - } - PySys_SetArgvEx(config.script_args.size() + 1, python_argv, 0); - - int result = PyRun_SimpleFile(fp, config.script_path.string().c_str()); - fclose(fp); - - // Clean up - for (size_t i = 0; i <= config.script_args.size(); i++) { - PyMem_RawFree(python_argv[i]); - } - delete[] python_argv; - - if (config.interactive_mode) { - // Even if script had SystemExit, continue to interactive mode - if (result != 0) { - // Check if it was SystemExit - if (PyErr_Occurred()) { - PyObject *type, *value, *traceback; - PyErr_Fetch(&type, &value, &traceback); - - if (PyErr_GivenExceptionMatches(type, PyExc_SystemExit)) { - PyErr_Clear(); - result = 0; // Don't exit with error - } else { - PyErr_Restore(type, value, traceback); - PyErr_Print(); - } - - Py_XDECREF(type); - Py_XDECREF(value); - Py_XDECREF(traceback); - } - } - // Run interactive mode after script - PyRun_InteractiveLoop(stdin, ""); - } - - // Run the game engine after script execution - engine->run(); - - Py_Finalize(); - delete engine; - return result; - } - else if (config.interactive_mode) { - // Interactive Python interpreter (only if explicitly requested with -i) - Py_InspectFlag = 1; - PyRun_InteractiveLoop(stdin, ""); - Py_Finalize(); - delete engine; - return 0; - } - else if (!config.exec_scripts.empty()) { - // With --exec, run the game engine after scripts execute - engine->run(); - Py_Finalize(); - delete engine; - return 0; - } - - delete engine; - return 0; } diff --git a/tests/WORKING_automation_test_example.py b/tests/WORKING_automation_test_example.py deleted file mode 100644 index 58b3a8e..0000000 --- a/tests/WORKING_automation_test_example.py +++ /dev/null @@ -1,81 +0,0 @@ -#!/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 \ No newline at end of file diff --git a/tests/animation_demo.py b/tests/animation_demo.py deleted file mode 100644 index f12fc70..0000000 --- a/tests/animation_demo.py +++ /dev/null @@ -1,165 +0,0 @@ -#!/usr/bin/env python3 -"""Animation System Demo - Shows all animation capabilities""" - -import mcrfpy -import math - -# Create main scene -mcrfpy.createScene("animation_demo") -ui = mcrfpy.sceneUI("animation_demo") -mcrfpy.setScene("animation_demo") - -# Title -title = mcrfpy.Caption((400, 30), "McRogueFace Animation System Demo", mcrfpy.default_font) -title.size = 24 -title.fill_color = (255, 255, 255) -# Note: centered property doesn't exist for Caption -ui.append(title) - -# 1. Position Animation Demo -pos_frame = mcrfpy.Frame(50, 100, 80, 80) -pos_frame.fill_color = (255, 100, 100) -pos_frame.outline = 2 -ui.append(pos_frame) - -pos_label = mcrfpy.Caption((50, 80), "Position Animation", mcrfpy.default_font) -pos_label.fill_color = (200, 200, 200) -ui.append(pos_label) - -# 2. Size Animation Demo -size_frame = mcrfpy.Frame(200, 100, 50, 50) -size_frame.fill_color = (100, 255, 100) -size_frame.outline = 2 -ui.append(size_frame) - -size_label = mcrfpy.Caption((200, 80), "Size Animation", mcrfpy.default_font) -size_label.fill_color = (200, 200, 200) -ui.append(size_label) - -# 3. Color Animation Demo -color_frame = mcrfpy.Frame(350, 100, 80, 80) -color_frame.fill_color = (255, 0, 0) -ui.append(color_frame) - -color_label = mcrfpy.Caption((350, 80), "Color Animation", mcrfpy.default_font) -color_label.fill_color = (200, 200, 200) -ui.append(color_label) - -# 4. Easing Functions Demo -easing_y = 250 -easing_frames = [] -easings = ["linear", "easeIn", "easeOut", "easeInOut", "easeInElastic", "easeOutBounce"] - -for i, easing in enumerate(easings): - x = 50 + i * 120 - - frame = mcrfpy.Frame(x, easing_y, 20, 20) - frame.fill_color = (100, 150, 255) - ui.append(frame) - easing_frames.append((frame, easing)) - - label = mcrfpy.Caption((x, easing_y - 20), easing, mcrfpy.default_font) - label.size = 12 - label.fill_color = (200, 200, 200) - ui.append(label) - -# 5. Complex Animation Demo -complex_frame = mcrfpy.Frame(300, 350, 100, 100) -complex_frame.fill_color = (128, 128, 255) -complex_frame.outline = 3 -ui.append(complex_frame) - -complex_label = mcrfpy.Caption((300, 330), "Complex Multi-Property", mcrfpy.default_font) -complex_label.fill_color = (200, 200, 200) -ui.append(complex_label) - -# Start animations -def start_animations(runtime): - # 1. Position animation - back and forth - x_anim = mcrfpy.Animation("x", 500.0, 3.0, "easeInOut") - x_anim.start(pos_frame) - - # 2. Size animation - pulsing - w_anim = mcrfpy.Animation("w", 150.0, 2.0, "easeInOut") - h_anim = mcrfpy.Animation("h", 150.0, 2.0, "easeInOut") - w_anim.start(size_frame) - h_anim.start(size_frame) - - # 3. Color animation - rainbow cycle - color_anim = mcrfpy.Animation("fill_color", (0, 255, 255, 255), 2.0, "linear") - color_anim.start(color_frame) - - # 4. Easing demos - all move up with different easings - for frame, easing in easing_frames: - y_anim = mcrfpy.Animation("y", 150.0, 2.0, easing) - y_anim.start(frame) - - # 5. Complex animation - multiple properties - cx_anim = mcrfpy.Animation("x", 500.0, 4.0, "easeInOut") - cy_anim = mcrfpy.Animation("y", 400.0, 4.0, "easeOut") - cw_anim = mcrfpy.Animation("w", 150.0, 4.0, "easeInElastic") - ch_anim = mcrfpy.Animation("h", 150.0, 4.0, "easeInElastic") - outline_anim = mcrfpy.Animation("outline", 10.0, 4.0, "linear") - - cx_anim.start(complex_frame) - cy_anim.start(complex_frame) - cw_anim.start(complex_frame) - ch_anim.start(complex_frame) - outline_anim.start(complex_frame) - - # Individual color component animations - r_anim = mcrfpy.Animation("fill_color.r", 255.0, 4.0, "easeInOut") - g_anim = mcrfpy.Animation("fill_color.g", 100.0, 4.0, "easeInOut") - b_anim = mcrfpy.Animation("fill_color.b", 50.0, 4.0, "easeInOut") - - r_anim.start(complex_frame) - g_anim.start(complex_frame) - b_anim.start(complex_frame) - - print("All animations started!") - -# Reverse some animations -def reverse_animations(runtime): - # Position back - x_anim = mcrfpy.Animation("x", 50.0, 3.0, "easeInOut") - x_anim.start(pos_frame) - - # Size back - w_anim = mcrfpy.Animation("w", 50.0, 2.0, "easeInOut") - h_anim = mcrfpy.Animation("h", 50.0, 2.0, "easeInOut") - w_anim.start(size_frame) - h_anim.start(size_frame) - - # Color cycle continues - color_anim = mcrfpy.Animation("fill_color", (255, 0, 255, 255), 2.0, "linear") - color_anim.start(color_frame) - - # Easing frames back down - for frame, easing in easing_frames: - y_anim = mcrfpy.Animation("y", 250.0, 2.0, easing) - y_anim.start(frame) - -# Continue color cycle -def cycle_colors(runtime): - color_anim = mcrfpy.Animation("fill_color", (255, 255, 0, 255), 2.0, "linear") - color_anim.start(color_frame) - -# Info text -info = mcrfpy.Caption((400, 550), "Watch as different properties animate with various easing functions!", mcrfpy.default_font) -info.fill_color = (255, 255, 200) -# Note: centered property doesn't exist for Caption -ui.append(info) - -# Schedule animations -mcrfpy.setTimer("start", start_animations, 500) -mcrfpy.setTimer("reverse", reverse_animations, 4000) -mcrfpy.setTimer("cycle", cycle_colors, 2500) - -# Exit handler -def on_key(key): - if key == "Escape": - mcrfpy.exit() - -mcrfpy.keypressScene(on_key) - -print("Animation demo started! Press Escape to exit.") \ No newline at end of file diff --git a/tests/api_createScene_test.py b/tests/api_createScene_test.py deleted file mode 100644 index b5e336e..0000000 --- a/tests/api_createScene_test.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/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.") \ No newline at end of file diff --git a/tests/api_keypressScene_test.py b/tests/api_keypressScene_test.py deleted file mode 100644 index 7ab6e41..0000000 --- a/tests/api_keypressScene_test.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/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() \ No newline at end of file diff --git a/tests/api_sceneUI_test.py b/tests/api_sceneUI_test.py deleted file mode 100644 index 276a549..0000000 --- a/tests/api_sceneUI_test.py +++ /dev/null @@ -1,80 +0,0 @@ -#!/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) \ No newline at end of file diff --git a/tests/api_setScene_currentScene_test.py b/tests/api_setScene_currentScene_test.py deleted file mode 100644 index 0e25d0e..0000000 --- a/tests/api_setScene_currentScene_test.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/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") \ No newline at end of file diff --git a/tests/api_timer_test.py b/tests/api_timer_test.py deleted file mode 100644 index d9af861..0000000 --- a/tests/api_timer_test.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/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) \ No newline at end of file diff --git a/tests/automation_click_issue78_analysis.py b/tests/automation_click_issue78_analysis.py deleted file mode 100644 index 3227f7e..0000000 --- a/tests/automation_click_issue78_analysis.py +++ /dev/null @@ -1,63 +0,0 @@ -#!/usr/bin/env python3 -""" -Analysis of Issue #78: Middle Mouse Click sends 'C' keyboard event - -BUG FOUND in GameEngine::processEvent() at src/GameEngine.cpp - -The bug occurs in this code section: -```cpp -if (currentScene()->hasAction(actionCode)) -{ - std::string name = currentScene()->action(actionCode); - currentScene()->doAction(name, actionType); -} -else if (currentScene()->key_callable) -{ - currentScene()->key_callable->call(ActionCode::key_str(event.key.code), actionType); -} -``` - -ISSUE: When a middle mouse button event occurs and there's no registered action for it, -the code falls through to the key_callable branch. However, it then tries to access -`event.key.code` from what is actually a mouse button event! - -Since it's a union, `event.key.code` reads garbage data from the mouse event structure. -The middle mouse button has value 2, which coincidentally matches sf::Keyboard::C (also value 2), -causing the spurious 'C' keyboard event. - -SOLUTION: The code should check the event type before accessing event-specific fields: - -```cpp -else if (currentScene()->key_callable && - (event.type == sf::Event::KeyPressed || event.type == sf::Event::KeyReleased)) -{ - currentScene()->key_callable->call(ActionCode::key_str(event.key.code), actionType); -} -``` - -TEST STATUS: -- Test Name: automation_click_issue78_test.py -- Method Tested: Middle mouse click behavior -- Pass/Fail: FAIL - Issue #78 confirmed to exist -- Error: Middle mouse clicks incorrectly trigger 'C' keyboard events -- Modifications: None needed - bug is in C++ code, not the test - -The test correctly identifies the issue but cannot run in headless mode due to -requiring actual event processing through the game loop. -""" - -import mcrfpy -import sys - -print(__doc__) - -# Demonstrate the issue conceptually -print("\nDemonstration of the bug:") -print("1. Middle mouse button value in SFML: 2") -print("2. Keyboard 'C' value in SFML: 2") -print("3. When processEvent reads event.key.code from a mouse event,") -print(" it gets the value 2, which ActionCode::key_str interprets as 'C'") - -print("\nThe fix is simple: add an event type check before accessing key.code") - -sys.exit(0) \ No newline at end of file diff --git a/tests/automation_click_issue78_test.py b/tests/automation_click_issue78_test.py deleted file mode 100644 index 159c30e..0000000 --- a/tests/automation_click_issue78_test.py +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env python3 -"""Test for automation click methods - Related to issue #78 (Middle click sends 'C')""" -import mcrfpy -from datetime import datetime - -# Try to import automation, but handle if it doesn't exist -try: - from mcrfpy import automation - HAS_AUTOMATION = True - print("SUCCESS: mcrfpy.automation module imported successfully") -except (ImportError, AttributeError) as e: - HAS_AUTOMATION = False - print(f"WARNING: mcrfpy.automation module not available - {e}") - print("The automation module may not be implemented yet") - -# Track events -click_events = [] -key_events = [] - -def click_handler(x, y, button): - """Track click events""" - click_events.append((x, y, button)) - print(f"Click received: ({x}, {y}, button={button})") - -def key_handler(key, scancode=None): - """Track keyboard events""" - key_events.append(key) - print(f"Key received: {key} (scancode: {scancode})") - -def test_clicks(): - """Test various click types, especially middle click (Issue #78)""" - if not HAS_AUTOMATION: - print("SKIP - automation module not available") - print("The automation module may not be implemented yet") - return - - # Create test scene - mcrfpy.createScene("click_test") - mcrfpy.setScene("click_test") - ui = mcrfpy.sceneUI("click_test") - - # Set up keyboard handler to detect Issue #78 - mcrfpy.keypressScene(key_handler) - - # Create clickable frame - frame = mcrfpy.Frame(50, 50, 300, 200, - fill_color=mcrfpy.Color(100, 100, 200), - outline_color=mcrfpy.Color(255, 255, 255), - outline=2.0) - frame.click = click_handler - ui.append(frame) - - caption = mcrfpy.Caption(mcrfpy.Vector(60, 60), - text="Click Test Area", - fill_color=mcrfpy.Color(255, 255, 255)) - frame.children.append(caption) - - # Test different click types - print("Testing click types...") - - # Left click - try: - automation.click(200, 150) - print("✓ Left click sent") - except Exception as e: - print(f"✗ Left click failed: {e}") - - # Right click - try: - automation.rightClick(200, 150) - print("✓ Right click sent") - except Exception as e: - print(f"✗ Right click failed: {e}") - - # Middle click - This is Issue #78 - try: - automation.middleClick(200, 150) - print("✓ Middle click sent") - except Exception as e: - print(f"✗ Middle click failed: {e}") - - # Double click - try: - automation.doubleClick(200, 150) - print("✓ Double click sent") - except Exception as e: - print(f"✗ Double click failed: {e}") - - # Triple click - try: - automation.tripleClick(200, 150) - print("✓ Triple click sent") - except Exception as e: - print(f"✗ Triple click failed: {e}") - - # Click with specific button parameter - try: - automation.click(200, 150, button='middle') - print("✓ Click with button='middle' sent") - except Exception as e: - print(f"✗ Click with button parameter failed: {e}") - - # Check results after a delay - def check_results(runtime): - print(f"\nClick events received: {len(click_events)}") - print(f"Keyboard events received: {len(key_events)}") - - # Check for Issue #78 - if any('C' in str(event) or ord('C') == event for event in key_events): - print("✗ ISSUE #78 CONFIRMED: Middle click sent 'C' keyboard event!") - else: - print("✓ No spurious 'C' keyboard events detected") - - # Analyze click events - for event in click_events: - print(f" Click: {event}") - - # Take screenshot - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - filename = f"test_clicks_issue78_{timestamp}.png" - automation.screenshot(filename) - print(f"Screenshot saved: {filename}") - - if len(click_events) > 0: - print("PASS - Clicks detected") - else: - print("FAIL - No clicks detected (may be headless limitation)") - - mcrfpy.delTimer("check_results") - - mcrfpy.setTimer("check_results", check_results, 2000) - -# Set up timer to run test -print("Setting up test timer...") -mcrfpy.setTimer("test", test_clicks, 1000) - -# Cancel timer after running once -def cleanup(): - mcrfpy.delTimer("test") - mcrfpy.delTimer("cleanup") - -mcrfpy.setTimer("cleanup", cleanup, 1100) - -# Exit after test completes -def exit_test(): - print("\nTest completed - exiting") - import sys - sys.exit(0) - -mcrfpy.setTimer("exit", exit_test, 5000) - -print("Test script initialized, waiting for timers...") \ No newline at end of file diff --git a/tests/automation_screenshot_test.py b/tests/automation_screenshot_test.py deleted file mode 100644 index c0c1d2f..0000000 --- a/tests/automation_screenshot_test.py +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env python3 -"""Test for mcrfpy.automation.screenshot()""" -import mcrfpy -from mcrfpy import automation -from datetime import datetime -import os -import sys -import time - -runs = 0 -def test_screenshot(*args): - """Test screenshot functionality""" - #global runs - #runs += 1 - #if runs < 2: - # print("tick") - # return - #print("tock") - #mcrfpy.delTimer("timer1") - # Create a scene with some visual elements - mcrfpy.createScene("screenshot_test") - mcrfpy.setScene("screenshot_test") - ui = mcrfpy.sceneUI("screenshot_test") - - # Add some colorful elements - frame1 = mcrfpy.Frame(10, 10, 200, 150, - fill_color=mcrfpy.Color(255, 0, 0), - outline_color=mcrfpy.Color(255, 255, 255), - outline=3.0) - ui.append(frame1) - - frame2 = mcrfpy.Frame(220, 10, 200, 150, - fill_color=mcrfpy.Color(0, 255, 0), - outline_color=mcrfpy.Color(0, 0, 0), - outline=2.0) - ui.append(frame2) - - caption = mcrfpy.Caption(mcrfpy.Vector(10, 170), - text="Screenshot Test Scene", - fill_color=mcrfpy.Color(255, 255, 0)) - caption.size = 24 - ui.append(caption) - - # Test multiple screenshots - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - filenames = [] - - # Test 1: Basic screenshot - try: - filename1 = f"test_screenshot_basic_{timestamp}.png" - result = automation.screenshot(filename1) - filenames.append(filename1) - print(f"✓ Basic screenshot saved: {filename1} (result: {result})") - except Exception as e: - print(f"✗ Basic screenshot failed: {e}") - print("FAIL") - sys.exit(1) - - # Test 2: Screenshot with special characters in filename - try: - filename2 = f"test_screenshot_special_chars_{timestamp}_test.png" - result = automation.screenshot(filename2) - filenames.append(filename2) - print(f"✓ Screenshot with special filename saved: {filename2} (result: {result})") - except Exception as e: - print(f"✗ Special filename screenshot failed: {e}") - - # Test 3: Invalid filename (if applicable) - try: - result = automation.screenshot("") - print(f"✗ Empty filename should fail but returned: {result}") - except Exception as e: - print(f"✓ Empty filename correctly rejected: {e}") - - # Check files exist immediately - files_found = 0 - for filename in filenames: - if os.path.exists(filename): - size = os.path.getsize(filename) - print(f"✓ File exists: {filename} ({size} bytes)") - files_found += 1 - else: - print(f"✗ File not found: {filename}") - - if files_found == len(filenames): - print("PASS") - sys.exit(0) - else: - print("FAIL") - sys.exit(1) - -print("Set callback") -mcrfpy.setTimer("timer1", test_screenshot, 1000) -# Run the test immediately -#test_screenshot() - diff --git a/tests/automation_screenshot_test_simple.py b/tests/automation_screenshot_test_simple.py deleted file mode 100644 index 75dbf77..0000000 --- a/tests/automation_screenshot_test_simple.py +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env python3 -"""Simple test for mcrfpy.automation.screenshot()""" -import mcrfpy -from mcrfpy import automation -import os -import sys - -# Create a simple scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Take a screenshot immediately -try: - filename = "test_screenshot.png" - result = automation.screenshot(filename) - print(f"Screenshot result: {result}") - - # Check if file exists - if os.path.exists(filename): - size = os.path.getsize(filename) - print(f"PASS - Screenshot saved: {filename} ({size} bytes)") - else: - print(f"FAIL - Screenshot file not created: {filename}") -except Exception as e: - print(f"FAIL - Screenshot error: {e}") - import traceback - traceback.print_exc() - -# Exit immediately -sys.exit(0) \ No newline at end of file diff --git a/tests/debug_render_test.py b/tests/debug_render_test.py deleted file mode 100644 index d7c7f6c..0000000 --- a/tests/debug_render_test.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/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) \ No newline at end of file diff --git a/tests/empty_script.py b/tests/empty_script.py deleted file mode 100644 index b34ee08..0000000 --- a/tests/empty_script.py +++ /dev/null @@ -1,2 +0,0 @@ -# This script is intentionally empty -pass \ No newline at end of file diff --git a/tests/exit_immediately_test.py b/tests/exit_immediately_test.py deleted file mode 100644 index 8df6089..0000000 --- a/tests/exit_immediately_test.py +++ /dev/null @@ -1,7 +0,0 @@ -#!/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") \ No newline at end of file diff --git a/tests/force_non_interactive.py b/tests/force_non_interactive.py deleted file mode 100644 index 1c7218a..0000000 --- a/tests/force_non_interactive.py +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/env python3 -"""Force Python to be non-interactive""" -import sys -import os - -print("Attempting to force non-interactive mode...") - -# Remove ps1/ps2 if they exist -if hasattr(sys, 'ps1'): - delattr(sys, 'ps1') -if hasattr(sys, 'ps2'): - delattr(sys, 'ps2') - -# Set environment variable -os.environ['PYTHONSTARTUP'] = '' - -# Try to set stdin to non-interactive -try: - import fcntl - import termios - # Make stdin non-interactive by removing ICANON flag - attrs = termios.tcgetattr(0) - attrs[3] = attrs[3] & ~termios.ICANON - termios.tcsetattr(0, termios.TCSANOW, attrs) - print("Modified terminal attributes") -except: - print("Could not modify terminal attributes") - -print("Script complete") \ No newline at end of file diff --git a/tests/generate_caption_screenshot_fixed.py b/tests/generate_caption_screenshot_fixed.py deleted file mode 100644 index 66234cb..0000000 --- a/tests/generate_caption_screenshot_fixed.py +++ /dev/null @@ -1,129 +0,0 @@ -#!/usr/bin/env python3 -"""Generate caption documentation screenshot with proper font""" - -import mcrfpy -from mcrfpy import automation -import sys - -def capture_caption(runtime): - """Capture caption example after render loop starts""" - - # Take screenshot - automation.screenshot("mcrogueface.github.io/images/ui_caption_example.png") - print("Caption screenshot saved!") - - # Exit after capturing - sys.exit(0) - -# Create scene -mcrfpy.createScene("captions") - -# Title -title = mcrfpy.Caption(400, 30, "Caption Examples") -title.font = mcrfpy.default_font -title.font_size = 28 -title.font_color = (255, 255, 255) - -# Different sizes -size_label = mcrfpy.Caption(100, 100, "Different Sizes:") -size_label.font = mcrfpy.default_font -size_label.font_color = (200, 200, 200) - -large = mcrfpy.Caption(300, 100, "Large Text (24pt)") -large.font = mcrfpy.default_font -large.font_size = 24 -large.font_color = (255, 255, 255) - -medium = mcrfpy.Caption(300, 140, "Medium Text (18pt)") -medium.font = mcrfpy.default_font -medium.font_size = 18 -medium.font_color = (255, 255, 255) - -small = mcrfpy.Caption(300, 170, "Small Text (14pt)") -small.font = mcrfpy.default_font -small.font_size = 14 -small.font_color = (255, 255, 255) - -# Different colors -color_label = mcrfpy.Caption(100, 230, "Different Colors:") -color_label.font = mcrfpy.default_font -color_label.font_color = (200, 200, 200) - -white_text = mcrfpy.Caption(300, 230, "White Text") -white_text.font = mcrfpy.default_font -white_text.font_color = (255, 255, 255) - -green_text = mcrfpy.Caption(300, 260, "Green Text") -green_text.font = mcrfpy.default_font -green_text.font_color = (100, 255, 100) - -red_text = mcrfpy.Caption(300, 290, "Red Text") -red_text.font = mcrfpy.default_font -red_text.font_color = (255, 100, 100) - -blue_text = mcrfpy.Caption(300, 320, "Blue Text") -blue_text.font = mcrfpy.default_font -blue_text.font_color = (100, 150, 255) - -# Caption with background -bg_label = mcrfpy.Caption(100, 380, "With Background:") -bg_label.font = mcrfpy.default_font -bg_label.font_color = (200, 200, 200) - -# Frame background -frame = mcrfpy.Frame(280, 370, 250, 50) -frame.bgcolor = (64, 64, 128) -frame.outline = 2 - -framed_text = mcrfpy.Caption(405, 395, "Caption on Frame") -framed_text.font = mcrfpy.default_font -framed_text.font_size = 18 -framed_text.font_color = (255, 255, 255) -framed_text.centered = True - -# Centered text example -center_label = mcrfpy.Caption(100, 460, "Centered Text:") -center_label.font = mcrfpy.default_font -center_label.font_color = (200, 200, 200) - -centered = mcrfpy.Caption(400, 460, "This text is centered") -centered.font = mcrfpy.default_font -centered.font_size = 20 -centered.font_color = (255, 255, 100) -centered.centered = True - -# Multi-line example -multi_label = mcrfpy.Caption(100, 520, "Multi-line:") -multi_label.font = mcrfpy.default_font -multi_label.font_color = (200, 200, 200) - -multiline = mcrfpy.Caption(300, 520, "Line 1: McRogueFace\nLine 2: Game Engine\nLine 3: Python API") -multiline.font = mcrfpy.default_font -multiline.font_size = 14 -multiline.font_color = (255, 255, 255) - -# Add all to scene -ui = mcrfpy.sceneUI("captions") -ui.append(title) -ui.append(size_label) -ui.append(large) -ui.append(medium) -ui.append(small) -ui.append(color_label) -ui.append(white_text) -ui.append(green_text) -ui.append(red_text) -ui.append(blue_text) -ui.append(bg_label) -ui.append(frame) -ui.append(framed_text) -ui.append(center_label) -ui.append(centered) -ui.append(multi_label) -ui.append(multiline) - -# Switch to scene -mcrfpy.setScene("captions") - -# Set timer to capture after rendering starts -mcrfpy.setTimer("capture", capture_caption, 100) \ No newline at end of file diff --git a/tests/generate_docs_screenshots.py b/tests/generate_docs_screenshots.py deleted file mode 100755 index 53393fd..0000000 --- a/tests/generate_docs_screenshots.py +++ /dev/null @@ -1,451 +0,0 @@ -#!/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...") \ No newline at end of file diff --git a/tests/generate_docs_screenshots_simple.py b/tests/generate_docs_screenshots_simple.py deleted file mode 100755 index 75712f4..0000000 --- a/tests/generate_docs_screenshots_simple.py +++ /dev/null @@ -1,217 +0,0 @@ -#!/usr/bin/env python3 -"""Generate documentation screenshots for McRogueFace UI elements - Simple version""" -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 -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 - -# Screenshot counter -screenshot_count = 0 -total_screenshots = 4 - -def screenshot_and_continue(runtime): - """Take a screenshot and move to the next scene""" - global screenshot_count - - if screenshot_count == 0: - # Caption example - print("Creating Caption example...") - mcrfpy.createScene("caption_example") - ui = mcrfpy.sceneUI("caption_example") - - bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR) - ui.append(bg) - - title = create_caption(200, 50, "Caption Examples", 32) - ui.append(title) - - 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_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) - - mcrfpy.setScene("caption_example") - mcrfpy.setTimer("next1", lambda r: capture_screenshot("ui_caption_example.png"), 200) - - elif screenshot_count == 1: - # Sprite example - print("Creating Sprite example...") - mcrfpy.createScene("sprite_example") - ui = mcrfpy.sceneUI("sprite_example") - - bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR) - ui.append(bg) - - title = create_caption(250, 50, "Sprite Examples", 32) - ui.append(title) - - sprite_bg = mcrfpy.Frame(100, 150, 600, 300, fill_color=BOX_COLOR) - ui.append(sprite_bg) - - 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_label = create_caption(250, 180, "Enemies", 14) - ui.append(enemy_label) - enemy1 = mcrfpy.Sprite(250, 200, sprite_texture, 123, 3.0) - ui.append(enemy1) - enemy2 = mcrfpy.Sprite(300, 200, sprite_texture, 107, 3.0) - ui.append(enemy2) - - 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_label = create_caption(500, 180, "Exit States", 14) - ui.append(exit_label) - exit_locked = mcrfpy.Sprite(500, 200, sprite_texture, 45, 3.0) - ui.append(exit_locked) - exit_open = mcrfpy.Sprite(550, 200, sprite_texture, 21, 3.0) - ui.append(exit_open) - - mcrfpy.setScene("sprite_example") - mcrfpy.setTimer("next2", lambda r: capture_screenshot("ui_sprite_example.png"), 200) - - elif screenshot_count == 2: - # Frame example - print("Creating Frame example...") - mcrfpy.createScene("frame_example") - ui = mcrfpy.sceneUI("frame_example") - - bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=SHADOW_COLOR) - ui.append(bg) - - title = create_caption(250, 30, "Frame Examples", 32) - ui.append(title) - - 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) - - 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) - - 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) - - mcrfpy.setScene("frame_example") - mcrfpy.setTimer("next3", lambda r: capture_screenshot("ui_frame_example.png"), 200) - - elif screenshot_count == 3: - # Grid example - print("Creating Grid example...") - mcrfpy.createScene("grid_example") - ui = mcrfpy.sceneUI("grid_example") - - bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR) - ui.append(bg) - - title = create_caption(250, 30, "Grid Example", 32) - ui.append(title) - - grid = mcrfpy.Grid(20, 15, sprite_texture, - mcrfpy.Vector(100, 100), mcrfpy.Vector(320, 240)) - - # Set up dungeon tiles - for x in range(20): - for y in range(15): - if x == 0 or x == 19 or y == 0 or y == 14: - # Walls - 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 - grid.at((10, 7)).walkable = True - - ui.append(grid) - - grid_label = create_caption(100, 480, "20x15 Grid - Simple Dungeon Layout", 16) - ui.append(grid_label) - - mcrfpy.setScene("grid_example") - mcrfpy.setTimer("next4", lambda r: capture_screenshot("ui_grid_example.png"), 200) - - else: - print("\nAll screenshots captured successfully!") - print(f"Screenshots saved to: {output_dir}/") - mcrfpy.exit() - return - -def capture_screenshot(filename): - """Capture a screenshot""" - global screenshot_count - full_path = f"{output_dir}/{filename}" - result = automation.screenshot(full_path) - print(f"Screenshot {screenshot_count + 1}/{total_screenshots}: {filename} - {'Success' if result else 'Failed'}") - screenshot_count += 1 - - # Schedule next scene - mcrfpy.setTimer("continue", screenshot_and_continue, 300) - -# Start the process -print("Starting screenshot generation...") -mcrfpy.setTimer("start", screenshot_and_continue, 500) - -# Safety timeout -mcrfpy.setTimer("safety", lambda r: mcrfpy.exit(), 30000) - -print("Setup complete. Game loop starting...") \ No newline at end of file diff --git a/tests/generate_entity_screenshot_fixed.py b/tests/generate_entity_screenshot_fixed.py deleted file mode 100644 index 4855319..0000000 --- a/tests/generate_entity_screenshot_fixed.py +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env python3 -"""Generate entity documentation screenshot with proper font loading""" - -import mcrfpy -from mcrfpy import automation -import sys - -def capture_entity(runtime): - """Capture entity example after render loop starts""" - - # Take screenshot - automation.screenshot("mcrogueface.github.io/images/ui_entity_example.png") - print("Entity screenshot saved!") - - # Exit after capturing - sys.exit(0) - -# Create scene -mcrfpy.createScene("entities") - -# Use the default font which is already loaded -# Instead of: font = mcrfpy.Font("assets/JetbrainsMono.ttf") -# We use: mcrfpy.default_font (which is already loaded by the engine) - -# Title -title = mcrfpy.Caption((400, 30), "Entity Example - Roguelike Characters", font=mcrfpy.default_font) -#title.font = mcrfpy.default_font -#title.font_size = 24 -title.size=24 -#title.font_color = (255, 255, 255) -#title.text_color = (255,255,255) - -# Create a grid background -texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16) - -# Create grid with entities - using 2x scale (32x32 pixel tiles) -#grid = mcrfpy.Grid((100, 100), (20, 15), texture, 16, 16) # I can never get the args right for this thing -t = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16) -grid = mcrfpy.Grid(20, 15, t, (10, 10), (1014, 758)) -grid.zoom = 2.0 -#grid.texture = texture - -# Define tile types -FLOOR = 58 # Stone floor -WALL = 11 # Stone wall - -# Fill with floor -for x in range(20): - for y in range(15): - grid.at((x, y)).tilesprite = WALL - -# Add walls around edges -for x in range(20): - grid.at((x, 0)).tilesprite = WALL - grid.at((x, 14)).tilesprite = WALL -for y in range(15): - grid.at((0, y)).tilesprite = WALL - grid.at((19, y)).tilesprite = WALL - -# Create entities -# Player at center -player = mcrfpy.Entity((10, 7), t, 84) -#player.texture = texture -#player.sprite_index = 84 # Player sprite - -# Enemies -rat1 = mcrfpy.Entity((5, 5), t, 123) -#rat1.texture = texture -#rat1.sprite_index = 123 # Rat - -rat2 = mcrfpy.Entity((15, 5), t, 123) -#rat2.texture = texture -#rat2.sprite_index = 123 # Rat - -big_rat = mcrfpy.Entity((7, 10), t, 130) -#big_rat.texture = texture -#big_rat.sprite_index = 130 # Big rat - -cyclops = mcrfpy.Entity((13, 10), t, 109) -#cyclops.texture = texture -#cyclops.sprite_index = 109 # Cyclops - -# Items -chest = mcrfpy.Entity((3, 3), t, 89) -#chest.texture = texture -#chest.sprite_index = 89 # Chest - -boulder = mcrfpy.Entity((10, 5), t, 66) -#boulder.texture = texture -#boulder.sprite_index = 66 # Boulder -key = mcrfpy.Entity((17, 12), t, 384) -#key.texture = texture -#key.sprite_index = 384 # Key - -# Add all entities to grid -grid.entities.append(player) -grid.entities.append(rat1) -grid.entities.append(rat2) -grid.entities.append(big_rat) -grid.entities.append(cyclops) -grid.entities.append(chest) -grid.entities.append(boulder) -grid.entities.append(key) - -# Labels -entity_label = mcrfpy.Caption((100, 580), "Entities move independently on the grid. Grid scale: 2x (32x32 pixels)") -#entity_label.font = mcrfpy.default_font -#entity_label.font_color = (255, 255, 255) - -info = mcrfpy.Caption((100, 600), "Player (center), Enemies (rats, cyclops), Items (chest, boulder, key)") -#info.font = mcrfpy.default_font -#info.font_size = 14 -#info.font_color = (200, 200, 200) - -# Legend frame -legend_frame = mcrfpy.Frame(50, 50, 200, 150) -#legend_frame.bgcolor = (64, 64, 128) -#legend_frame.outline = 2 - -legend_title = mcrfpy.Caption((150, 60), "Entity Types") -#legend_title.font = mcrfpy.default_font -#legend_title.font_color = (255, 255, 255) -#legend_title.centered = True - -#legend_text = mcrfpy.Caption((60, 90), "Player: @\nRat: r\nBig Rat: R\nCyclops: C\nChest: $\nBoulder: O\nKey: k") -#legend_text.font = mcrfpy.default_font -#legend_text.font_size = 12 -#legend_text.font_color = (255, 255, 255) - -# Add all to scene -ui = mcrfpy.sceneUI("entities") -ui.append(grid) -ui.append(title) -ui.append(entity_label) -ui.append(info) -ui.append(legend_frame) -ui.append(legend_title) -#ui.append(legend_text) - -# Switch to scene -mcrfpy.setScene("entities") - -# Set timer to capture after rendering starts -mcrfpy.setTimer("capture", capture_entity, 100) diff --git a/tests/generate_grid_screenshot.py b/tests/generate_grid_screenshot.py deleted file mode 100644 index 706b704..0000000 --- a/tests/generate_grid_screenshot.py +++ /dev/null @@ -1,131 +0,0 @@ -#!/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) \ No newline at end of file diff --git a/tests/generate_sprite_screenshot.py b/tests/generate_sprite_screenshot.py deleted file mode 100644 index 3a314bb..0000000 --- a/tests/generate_sprite_screenshot.py +++ /dev/null @@ -1,160 +0,0 @@ -#!/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) \ No newline at end of file diff --git a/tests/issue_12_gridpoint_instantiation_test.py b/tests/issue_12_gridpoint_instantiation_test.py deleted file mode 100644 index bb37365..0000000 --- a/tests/issue_12_gridpoint_instantiation_test.py +++ /dev/null @@ -1,136 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #12: Forbid GridPoint/GridPointState instantiation - -This test verifies that GridPoint and GridPointState cannot be instantiated -directly from Python, as they should only be created internally by the C++ code. -""" - -import mcrfpy -import sys - -def test_gridpoint_instantiation(): - """Test that GridPoint and GridPointState cannot be instantiated""" - print("=== Testing GridPoint/GridPointState Instantiation Prevention (Issue #12) ===\n") - - tests_passed = 0 - tests_total = 0 - - # Test 1: Try to instantiate GridPoint - print("--- Test 1: GridPoint instantiation ---") - tests_total += 1 - try: - point = mcrfpy.GridPoint() - print("✗ FAIL: GridPoint() should not be allowed") - except TypeError as e: - print(f"✓ PASS: GridPoint instantiation correctly prevented: {e}") - tests_passed += 1 - except Exception as e: - print(f"✗ FAIL: Unexpected error: {e}") - - # Test 2: Try to instantiate GridPointState - print("\n--- Test 2: GridPointState instantiation ---") - tests_total += 1 - try: - state = mcrfpy.GridPointState() - print("✗ FAIL: GridPointState() should not be allowed") - except TypeError as e: - print(f"✓ PASS: GridPointState instantiation correctly prevented: {e}") - tests_passed += 1 - except Exception as e: - print(f"✗ FAIL: Unexpected error: {e}") - - # Test 3: Verify GridPoint can still be obtained from Grid - print("\n--- Test 3: GridPoint obtained from Grid.at() ---") - tests_total += 1 - try: - grid = mcrfpy.Grid(10, 10) - point = grid.at(5, 5) - print(f"✓ PASS: GridPoint obtained from Grid.at(): {point}") - print(f" Type: {type(point).__name__}") - tests_passed += 1 - except Exception as e: - print(f"✗ FAIL: Could not get GridPoint from Grid: {e}") - - # Test 4: Verify GridPointState can still be obtained from GridPoint - print("\n--- Test 4: GridPointState obtained from GridPoint ---") - tests_total += 1 - try: - # GridPointState is accessed through GridPoint's click handler - # Let's check if we can access point properties that would use GridPointState - if hasattr(point, 'walkable'): - print(f"✓ PASS: GridPoint has expected properties") - print(f" walkable: {point.walkable}") - print(f" transparent: {point.transparent}") - tests_passed += 1 - else: - print("✗ FAIL: GridPoint missing expected properties") - except Exception as e: - print(f"✗ FAIL: Error accessing GridPoint properties: {e}") - - # Test 5: Try to call the types directly (alternative syntax) - print("\n--- Test 5: Alternative instantiation attempts ---") - tests_total += 1 - all_prevented = True - - # Try various ways to instantiate - attempts = [ - ("mcrfpy.GridPoint.__new__(mcrfpy.GridPoint)", - lambda: mcrfpy.GridPoint.__new__(mcrfpy.GridPoint)), - ("type(point)()", - lambda: type(point)() if 'point' in locals() else None), - ] - - for desc, func in attempts: - try: - if func: - result = func() - print(f"✗ FAIL: {desc} should not be allowed") - all_prevented = False - except (TypeError, AttributeError) as e: - print(f" ✓ Correctly prevented: {desc}") - except Exception as e: - print(f" ? Unexpected error for {desc}: {e}") - - if all_prevented: - print("✓ PASS: All alternative instantiation attempts prevented") - tests_passed += 1 - else: - print("✗ FAIL: Some instantiation attempts succeeded") - - # Summary - print(f"\n=== SUMMARY ===") - print(f"Tests passed: {tests_passed}/{tests_total}") - - if tests_passed == tests_total: - print("\nIssue #12 FIXED: GridPoint/GridPointState instantiation properly forbidden!") - else: - print("\nIssue #12: Some tests failed") - - return tests_passed == tests_total - -def run_test(runtime): - """Timer callback to run the test""" - try: - # First verify the types exist - print("Checking that GridPoint and GridPointState types exist...") - print(f"GridPoint type: {mcrfpy.GridPoint}") - print(f"GridPointState type: {mcrfpy.GridPointState}") - print() - - success = test_gridpoint_instantiation() - print("\nOverall result: " + ("PASS" if success else "FAIL")) - except Exception as e: - print(f"\nTest error: {e}") - import traceback - traceback.print_exc() - print("\nOverall result: FAIL") - - sys.exit(0) - -# Set up the test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test to run after game loop starts -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/issue_26_28_iterator_comprehensive_test.py b/tests/issue_26_28_iterator_comprehensive_test.py deleted file mode 100644 index db88571..0000000 --- a/tests/issue_26_28_iterator_comprehensive_test.py +++ /dev/null @@ -1,337 +0,0 @@ -#!/usr/bin/env python3 -""" -Comprehensive test for Issues #26 & #28: Iterator implementation for collections - -This test covers both UICollection and UIEntityCollection iterator implementations, -testing all aspects of the Python sequence protocol. - -Issues: -- #26: Iterator support for UIEntityCollection -- #28: Iterator support for UICollection -""" - -import mcrfpy -from mcrfpy import automation -import sys -import gc - -def test_sequence_protocol(collection, name, expected_types=None): - """Test all sequence protocol operations on a collection""" - print(f"\n=== Testing {name} ===") - - tests_passed = 0 - tests_total = 0 - - # Test 1: len() - tests_total += 1 - try: - length = len(collection) - print(f"✓ len() works: {length} items") - tests_passed += 1 - except Exception as e: - print(f"✗ len() failed: {e}") - return tests_passed, tests_total - - # Test 2: Basic iteration - tests_total += 1 - try: - items = [] - types = [] - for item in collection: - items.append(item) - types.append(type(item).__name__) - print(f"✓ Iteration works: found {len(items)} items") - print(f" Types: {types}") - if expected_types and types != expected_types: - print(f" WARNING: Expected types {expected_types}") - tests_passed += 1 - except Exception as e: - print(f"✗ Iteration failed (Issue #26/#28): {e}") - - # Test 3: Indexing (positive) - tests_total += 1 - try: - if length > 0: - first = collection[0] - last = collection[length-1] - print(f"✓ Positive indexing works: [0]={type(first).__name__}, [{length-1}]={type(last).__name__}") - tests_passed += 1 - else: - print(" Skipping indexing test - empty collection") - except Exception as e: - print(f"✗ Positive indexing failed: {e}") - - # Test 4: Negative indexing - tests_total += 1 - try: - if length > 0: - last = collection[-1] - first = collection[-length] - print(f"✓ Negative indexing works: [-1]={type(last).__name__}, [-{length}]={type(first).__name__}") - tests_passed += 1 - else: - print(" Skipping negative indexing test - empty collection") - except Exception as e: - print(f"✗ Negative indexing failed: {e}") - - # Test 5: Out of bounds indexing - tests_total += 1 - try: - _ = collection[length + 10] - print(f"✗ Out of bounds indexing should raise IndexError but didn't") - except IndexError: - print(f"✓ Out of bounds indexing correctly raises IndexError") - tests_passed += 1 - except Exception as e: - print(f"✗ Out of bounds indexing raised wrong exception: {type(e).__name__}: {e}") - - # Test 6: Slicing - tests_total += 1 - try: - if length >= 2: - slice_result = collection[0:2] - print(f"✓ Slicing works: [0:2] returned {len(slice_result)} items") - tests_passed += 1 - else: - print(" Skipping slicing test - not enough items") - except NotImplementedError: - print(f"✗ Slicing not implemented") - except Exception as e: - print(f"✗ Slicing failed: {e}") - - # Test 7: Contains operator - tests_total += 1 - try: - if length > 0: - first_item = collection[0] - if first_item in collection: - print(f"✓ 'in' operator works") - tests_passed += 1 - else: - print(f"✗ 'in' operator returned False for existing item") - else: - print(" Skipping 'in' operator test - empty collection") - except NotImplementedError: - print(f"✗ 'in' operator not implemented") - except Exception as e: - print(f"✗ 'in' operator failed: {e}") - - # Test 8: Multiple iterations - tests_total += 1 - try: - count1 = sum(1 for _ in collection) - count2 = sum(1 for _ in collection) - if count1 == count2 == length: - print(f"✓ Multiple iterations work correctly") - tests_passed += 1 - else: - print(f"✗ Multiple iterations inconsistent: {count1} vs {count2} vs {length}") - except Exception as e: - print(f"✗ Multiple iterations failed: {e}") - - # Test 9: Iterator state independence - tests_total += 1 - try: - iter1 = iter(collection) - iter2 = iter(collection) - - # Advance iter1 - next(iter1) - - # iter2 should still be at the beginning - item1_from_iter2 = next(iter2) - item1_from_collection = collection[0] - - if type(item1_from_iter2).__name__ == type(item1_from_collection).__name__: - print(f"✓ Iterator state independence maintained") - tests_passed += 1 - else: - print(f"✗ Iterator states are not independent") - except Exception as e: - print(f"✗ Iterator state test failed: {e}") - - # Test 10: List conversion - tests_total += 1 - try: - as_list = list(collection) - if len(as_list) == length: - print(f"✓ list() conversion works: {len(as_list)} items") - tests_passed += 1 - else: - print(f"✗ list() conversion wrong length: {len(as_list)} vs {length}") - except Exception as e: - print(f"✗ list() conversion failed: {e}") - - return tests_passed, tests_total - -def test_modification_during_iteration(collection, name): - """Test collection modification during iteration""" - print(f"\n=== Testing {name} Modification During Iteration ===") - - # This is a tricky case - some implementations might crash - # or behave unexpectedly when the collection is modified during iteration - - if len(collection) < 2: - print(" Skipping - need at least 2 items") - return - - try: - count = 0 - for i, item in enumerate(collection): - count += 1 - if i == 0 and hasattr(collection, 'remove'): - # Try to remove an item during iteration - # This might raise an exception or cause undefined behavior - pass # Don't actually modify to avoid breaking the test - print(f"✓ Iteration completed without modification: {count} items") - except Exception as e: - print(f" Note: Iteration with modification would fail: {e}") - -def run_comprehensive_test(): - """Run comprehensive iterator tests for both collection types""" - print("=== Testing Collection Iterator Implementation (Issues #26 & #28) ===") - - total_passed = 0 - total_tests = 0 - - # Test UICollection - print("\n--- Testing UICollection ---") - - # Create UI elements - scene_ui = mcrfpy.sceneUI("test") - - # Add various UI elements - frame = mcrfpy.Frame(10, 10, 200, 150, - fill_color=mcrfpy.Color(100, 100, 200), - outline_color=mcrfpy.Color(255, 255, 255)) - caption = mcrfpy.Caption(mcrfpy.Vector(220, 10), - text="Test Caption", - fill_color=mcrfpy.Color(255, 255, 0)) - - scene_ui.append(frame) - scene_ui.append(caption) - - # Test UICollection - passed, total = test_sequence_protocol(scene_ui, "UICollection", - expected_types=["Frame", "Caption"]) - total_passed += passed - total_tests += total - - test_modification_during_iteration(scene_ui, "UICollection") - - # Test UICollection with children - print("\n--- Testing UICollection Children (Nested) ---") - child_caption = mcrfpy.Caption(mcrfpy.Vector(10, 10), - text="Child", - fill_color=mcrfpy.Color(200, 200, 200)) - frame.children.append(child_caption) - - passed, total = test_sequence_protocol(frame.children, "Frame.children", - expected_types=["Caption"]) - total_passed += passed - total_tests += total - - # Test UIEntityCollection - print("\n--- Testing UIEntityCollection ---") - - # Create a grid with entities - grid = mcrfpy.Grid(30, 30) - grid.x = 10 - grid.y = 200 - grid.w = 600 - grid.h = 400 - scene_ui.append(grid) - - # Add various entities - entity1 = mcrfpy.Entity(5, 5) - entity2 = mcrfpy.Entity(10, 10) - entity3 = mcrfpy.Entity(15, 15) - - grid.entities.append(entity1) - grid.entities.append(entity2) - grid.entities.append(entity3) - - passed, total = test_sequence_protocol(grid.entities, "UIEntityCollection", - expected_types=["Entity", "Entity", "Entity"]) - total_passed += passed - total_tests += total - - test_modification_during_iteration(grid.entities, "UIEntityCollection") - - # Test empty collections - print("\n--- Testing Empty Collections ---") - empty_grid = mcrfpy.Grid(10, 10) - - passed, total = test_sequence_protocol(empty_grid.entities, "Empty UIEntityCollection") - total_passed += passed - total_tests += total - - empty_frame = mcrfpy.Frame(0, 0, 50, 50) - passed, total = test_sequence_protocol(empty_frame.children, "Empty UICollection") - total_passed += passed - total_tests += total - - # Test large collection - print("\n--- Testing Large Collection ---") - large_grid = mcrfpy.Grid(50, 50) - for i in range(100): - large_grid.entities.append(mcrfpy.Entity(i % 50, i // 50)) - - print(f"Created large collection with {len(large_grid.entities)} entities") - - # Just test basic iteration performance - import time - start = time.time() - count = sum(1 for _ in large_grid.entities) - elapsed = time.time() - start - print(f"✓ Large collection iteration: {count} items in {elapsed:.3f}s") - - # Edge case: Single item collection - print("\n--- Testing Single Item Collection ---") - single_grid = mcrfpy.Grid(5, 5) - single_grid.entities.append(mcrfpy.Entity(1, 1)) - - passed, total = test_sequence_protocol(single_grid.entities, "Single Item UIEntityCollection") - total_passed += passed - total_tests += total - - # Take screenshot - automation.screenshot("/tmp/issue_26_28_iterator_test.png") - - # Summary - print(f"\n=== SUMMARY ===") - print(f"Total tests passed: {total_passed}/{total_tests}") - - if total_passed < total_tests: - print("\nIssues found:") - print("- Issue #26: UIEntityCollection may not fully implement iterator protocol") - print("- Issue #28: UICollection may not fully implement iterator protocol") - print("\nThe iterator implementation should support:") - print("1. Forward iteration with 'for item in collection'") - print("2. Multiple independent iterators") - print("3. Proper cleanup when iteration completes") - print("4. Integration with Python's sequence protocol") - else: - print("\nAll iterator tests passed!") - - return total_passed == total_tests - -def run_test(runtime): - """Timer callback to run the test""" - try: - success = run_comprehensive_test() - print("\nOverall result: " + ("PASS" if success else "FAIL")) - except Exception as e: - print(f"\nTest error: {e}") - import traceback - traceback.print_exc() - print("\nOverall result: FAIL") - - sys.exit(0) - -# Set up the test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test to run after game loop starts -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/issue_37_simple_test.py b/tests/issue_37_simple_test.py deleted file mode 100644 index a6d17b5..0000000 --- a/tests/issue_37_simple_test.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple test for Issue #37: Verify script loading works from executable directory -""" - -import sys -import os -import mcrfpy - -# This script runs as --exec, which means it's loaded after Python initialization -# and after game.py. If we got here, script loading is working. - -print("Issue #37 test: Script execution verified") -print(f"Current working directory: {os.getcwd()}") -print(f"Script location: {__file__}") - -# Create a simple scene to verify everything is working -mcrfpy.createScene("issue37_test") - -print("PASS: Issue #37 - Script loading working correctly") -sys.exit(0) \ No newline at end of file diff --git a/tests/issue_37_test.py b/tests/issue_37_test.py deleted file mode 100644 index d0f882e..0000000 --- a/tests/issue_37_test.py +++ /dev/null @@ -1,84 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #37: Windows scripts subdirectory not checked for .py files - -This test checks if the game can find and load scripts/game.py from different working directories. -On Windows, this often fails because fopen uses relative paths without resolving them. -""" - -import os -import sys -import subprocess -import tempfile -import shutil - -def test_script_loading(): - # Create a temporary directory to test from - with tempfile.TemporaryDirectory() as tmpdir: - print(f"Testing from directory: {tmpdir}") - - # Get the build directory (assuming we're running from the repo root) - build_dir = os.path.abspath("build") - mcrogueface_exe = os.path.join(build_dir, "mcrogueface") - if os.name == "nt": # Windows - mcrogueface_exe += ".exe" - - # Create a simple test script that the game should load - test_script = """ -import mcrfpy -print("TEST SCRIPT LOADED SUCCESSFULLY") -mcrfpy.createScene("test_scene") -""" - - # Save the original game.py - game_py_path = os.path.join(build_dir, "scripts", "game.py") - game_py_backup = game_py_path + ".backup" - if os.path.exists(game_py_path): - shutil.copy(game_py_path, game_py_backup) - - try: - # Replace game.py with our test script - os.makedirs(os.path.dirname(game_py_path), exist_ok=True) - with open(game_py_path, "w") as f: - f.write(test_script) - - # Test 1: Run from build directory (should work) - print("\nTest 1: Running from build directory...") - result = subprocess.run( - [mcrogueface_exe, "--headless", "-c", "print('Test 1 complete')"], - cwd=build_dir, - capture_output=True, - text=True, - timeout=5 - ) - if "TEST SCRIPT LOADED SUCCESSFULLY" in result.stdout: - print("✓ Test 1 PASSED: Script loaded from build directory") - else: - print("✗ Test 1 FAILED: Script not loaded from build directory") - print(f"stdout: {result.stdout}") - print(f"stderr: {result.stderr}") - - # Test 2: Run from temporary directory (often fails on Windows) - print("\nTest 2: Running from different working directory...") - result = subprocess.run( - [mcrogueface_exe, "--headless", "-c", "print('Test 2 complete')"], - cwd=tmpdir, - capture_output=True, - text=True, - timeout=5 - ) - if "TEST SCRIPT LOADED SUCCESSFULLY" in result.stdout: - print("✓ Test 2 PASSED: Script loaded from different directory") - else: - print("✗ Test 2 FAILED: Script not loaded from different directory") - print(f"stdout: {result.stdout}") - print(f"stderr: {result.stderr}") - print("\nThis is the bug described in Issue #37!") - - finally: - # Restore original game.py - if os.path.exists(game_py_backup): - shutil.move(game_py_backup, game_py_path) - -if __name__ == "__main__": - test_script_loading() \ No newline at end of file diff --git a/tests/issue_37_windows_scripts_comprehensive_test.py b/tests/issue_37_windows_scripts_comprehensive_test.py deleted file mode 100644 index cce902f..0000000 --- a/tests/issue_37_windows_scripts_comprehensive_test.py +++ /dev/null @@ -1,152 +0,0 @@ -#!/usr/bin/env python3 -""" -Comprehensive test for Issue #37: Windows scripts subdirectory bug - -This test comprehensively tests script loading from different working directories, -particularly focusing on the Windows issue where relative paths fail. - -The bug: On Windows, when mcrogueface.exe is run from a different directory, -it fails to find scripts/game.py because fopen uses relative paths. -""" - -import os -import sys -import subprocess -import tempfile -import shutil -import platform - -def create_test_script(content=""): - """Create a minimal test script""" - if not content: - content = """ -import mcrfpy -print("TEST_SCRIPT_LOADED_FROM_PATH") -mcrfpy.createScene("test_scene") -# Exit cleanly to avoid hanging -import sys -sys.exit(0) -""" - return content - -def run_mcrogueface(exe_path, cwd, timeout=5): - """Run mcrogueface from a specific directory and capture output""" - cmd = [exe_path, "--headless"] - - try: - result = subprocess.run( - cmd, - cwd=cwd, - capture_output=True, - text=True, - timeout=timeout - ) - return result.stdout, result.stderr, result.returncode - except subprocess.TimeoutExpired: - return "", "TIMEOUT", -1 - except Exception as e: - return "", str(e), -1 - -def test_script_loading(): - """Test script loading from various directories""" - # Detect platform - is_windows = platform.system() == "Windows" - print(f"Platform: {platform.system()}") - - # Get paths - repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - build_dir = os.path.join(repo_root, "build") - exe_name = "mcrogueface.exe" if is_windows else "mcrogueface" - exe_path = os.path.join(build_dir, exe_name) - - if not os.path.exists(exe_path): - print(f"FAIL: Executable not found at {exe_path}") - print("Please build the project first") - return - - # Backup original game.py - scripts_dir = os.path.join(build_dir, "scripts") - game_py_path = os.path.join(scripts_dir, "game.py") - game_py_backup = game_py_path + ".backup" - - if os.path.exists(game_py_path): - shutil.copy(game_py_path, game_py_backup) - - try: - # Create test script - os.makedirs(scripts_dir, exist_ok=True) - with open(game_py_path, "w") as f: - f.write(create_test_script()) - - print("\n=== Test 1: Run from build directory (baseline) ===") - stdout, stderr, code = run_mcrogueface(exe_path, build_dir) - if "TEST_SCRIPT_LOADED_FROM_PATH" in stdout: - print("✓ PASS: Script loaded when running from build directory") - else: - print("✗ FAIL: Script not loaded from build directory") - print(f" stdout: {stdout[:200]}") - print(f" stderr: {stderr[:200]}") - - print("\n=== Test 2: Run from parent directory ===") - stdout, stderr, code = run_mcrogueface(exe_path, repo_root) - if "TEST_SCRIPT_LOADED_FROM_PATH" in stdout: - print("✓ PASS: Script loaded from parent directory") - else: - print("✗ FAIL: Script not loaded from parent directory") - print(" This might indicate Issue #37") - print(f" stdout: {stdout[:200]}") - print(f" stderr: {stderr[:200]}") - - print("\n=== Test 3: Run from system temp directory ===") - with tempfile.TemporaryDirectory() as tmpdir: - stdout, stderr, code = run_mcrogueface(exe_path, tmpdir) - if "TEST_SCRIPT_LOADED_FROM_PATH" in stdout: - print("✓ PASS: Script loaded from temp directory") - else: - print("✗ FAIL: Script not loaded from temp directory") - print(" This is the core Issue #37 bug!") - print(f" Working directory: {tmpdir}") - print(f" stdout: {stdout[:200]}") - print(f" stderr: {stderr[:200]}") - - print("\n=== Test 4: Run with absolute path from different directory ===") - with tempfile.TemporaryDirectory() as tmpdir: - # Use absolute path to executable - abs_exe = os.path.abspath(exe_path) - stdout, stderr, code = run_mcrogueface(abs_exe, tmpdir) - if "TEST_SCRIPT_LOADED_FROM_PATH" in stdout: - print("✓ PASS: Script loaded with absolute exe path") - else: - print("✗ FAIL: Script not loaded with absolute exe path") - print(f" stdout: {stdout[:200]}") - print(f" stderr: {stderr[:200]}") - - # Test 5: Symlink test (Unix only) - if not is_windows: - print("\n=== Test 5: Run via symlink (Unix only) ===") - with tempfile.TemporaryDirectory() as tmpdir: - symlink_path = os.path.join(tmpdir, "mcrogueface_link") - os.symlink(exe_path, symlink_path) - stdout, stderr, code = run_mcrogueface(symlink_path, tmpdir) - if "TEST_SCRIPT_LOADED_FROM_PATH" in stdout: - print("✓ PASS: Script loaded via symlink") - else: - print("✗ FAIL: Script not loaded via symlink") - print(f" stdout: {stdout[:200]}") - print(f" stderr: {stderr[:200]}") - - # Summary - print("\n=== SUMMARY ===") - print("Issue #37 is about script loading failing when the executable") - print("is run from a different working directory than where it's located.") - print("The fix should resolve the script path relative to the executable,") - print("not the current working directory.") - - finally: - # Restore original game.py - if os.path.exists(game_py_backup): - shutil.move(game_py_backup, game_py_path) - print("\nTest cleanup complete") - -if __name__ == "__main__": - test_script_loading() \ No newline at end of file diff --git a/tests/issue_76_test.py b/tests/issue_76_test.py deleted file mode 100644 index 96dd723..0000000 --- a/tests/issue_76_test.py +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #76: UIEntityCollection::getitem returns wrong type for derived classes - -This test checks if derived Entity classes maintain their type when retrieved from collections. -""" - -import mcrfpy -import sys - -# Create a derived Entity class -class CustomEntity(mcrfpy.Entity): - def __init__(self, x, y): - super().__init__(x, y) - self.custom_attribute = "I am custom!" - - def custom_method(self): - return "Custom method called" - -def run_test(runtime): - """Test that derived entity classes maintain their type in collections""" - try: - # Create a grid - grid = mcrfpy.Grid(10, 10) - - # Create instances of base and derived entities - base_entity = mcrfpy.Entity(1, 1) - custom_entity = CustomEntity(2, 2) - - # Add them to the grid's entity collection - grid.entities.append(base_entity) - grid.entities.append(custom_entity) - - # Retrieve them back - retrieved_base = grid.entities[0] - retrieved_custom = grid.entities[1] - - print(f"Base entity type: {type(retrieved_base)}") - print(f"Custom entity type: {type(retrieved_custom)}") - - # Test 1: Check if base entity is correct type - if type(retrieved_base).__name__ == "Entity": - print("✓ Test 1 PASSED: Base entity maintains correct type") - else: - print("✗ Test 1 FAILED: Base entity has wrong type") - - # Test 2: Check if custom entity maintains its derived type - if type(retrieved_custom).__name__ == "CustomEntity": - print("✓ Test 2 PASSED: Derived entity maintains correct type") - - # Test 3: Check if custom attributes are preserved - try: - attr = retrieved_custom.custom_attribute - method_result = retrieved_custom.custom_method() - print(f"✓ Test 3 PASSED: Custom attributes preserved - {attr}, {method_result}") - except AttributeError as e: - print(f"✗ Test 3 FAILED: Custom attributes lost - {e}") - else: - print("✗ Test 2 FAILED: Derived entity type lost!") - print("This is the bug described in Issue #76!") - - # Try to access custom attributes anyway - try: - attr = retrieved_custom.custom_attribute - print(f" - Has custom_attribute: {attr} (but wrong type)") - except AttributeError: - print(" - Lost custom_attribute") - - # Test 4: Check iteration - print("\nTesting iteration:") - for i, entity in enumerate(grid.entities): - print(f" Entity {i}: {type(entity).__name__}") - - print("\nTest complete") - - except Exception as e: - print(f"Test error: {e}") - import traceback - traceback.print_exc() - - sys.exit(0) - -# Set up the test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test to run after game loop starts -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/issue_76_uientitycollection_type_test.py b/tests/issue_76_uientitycollection_type_test.py deleted file mode 100644 index 15fd27f..0000000 --- a/tests/issue_76_uientitycollection_type_test.py +++ /dev/null @@ -1,259 +0,0 @@ -#!/usr/bin/env python3 -""" -Comprehensive test for Issue #76: UIEntityCollection returns wrong type for derived classes - -This test demonstrates that when retrieving entities from a UIEntityCollection, -derived Entity classes lose their type and are returned as base Entity objects. - -The bug: The C++ implementation of UIEntityCollection::getitem creates a new -PyUIEntityObject with type "Entity" instead of preserving the original Python type. -""" - -import mcrfpy -from mcrfpy import automation -import sys -import gc - -# Define several derived Entity classes with different features -class Player(mcrfpy.Entity): - def __init__(self, x, y): - # Entity expects Vector position and optional texture - super().__init__(mcrfpy.Vector(x, y)) - self.health = 100 - self.inventory = [] - self.player_id = "PLAYER_001" - - def take_damage(self, amount): - self.health -= amount - return self.health > 0 - -class Enemy(mcrfpy.Entity): - def __init__(self, x, y, enemy_type="goblin"): - # Entity expects Vector position and optional texture - super().__init__(mcrfpy.Vector(x, y)) - self.enemy_type = enemy_type - self.aggression = 5 - self.patrol_route = [(x, y), (x+1, y), (x+1, y+1), (x, y+1)] - - def get_next_move(self): - return self.patrol_route[0] - -class Treasure(mcrfpy.Entity): - def __init__(self, x, y, value=100): - # Entity expects Vector position and optional texture - super().__init__(mcrfpy.Vector(x, y)) - self.value = value - self.collected = False - - def collect(self): - if not self.collected: - self.collected = True - return self.value - return 0 - -def test_type_preservation(): - """Comprehensive test of type preservation in UIEntityCollection""" - print("=== Testing UIEntityCollection Type Preservation (Issue #76) ===\n") - - # Create a grid to hold entities - grid = mcrfpy.Grid(30, 30) - grid.x = 10 - grid.y = 10 - grid.w = 600 - grid.h = 600 - - # Add grid to scene - scene_ui = mcrfpy.sceneUI("test") - scene_ui.append(grid) - - # Create various entity instances - player = Player(5, 5) - enemy1 = Enemy(10, 10, "orc") - enemy2 = Enemy(15, 15, "skeleton") - treasure = Treasure(20, 20, 500) - base_entity = mcrfpy.Entity(mcrfpy.Vector(25, 25)) - - print("Created entities:") - print(f" - Player at (5,5): type={type(player).__name__}, health={player.health}") - print(f" - Enemy at (10,10): type={type(enemy1).__name__}, enemy_type={enemy1.enemy_type}") - print(f" - Enemy at (15,15): type={type(enemy2).__name__}, enemy_type={enemy2.enemy_type}") - print(f" - Treasure at (20,20): type={type(treasure).__name__}, value={treasure.value}") - print(f" - Base Entity at (25,25): type={type(base_entity).__name__}") - - # Store original references - original_refs = { - 'player': player, - 'enemy1': enemy1, - 'enemy2': enemy2, - 'treasure': treasure, - 'base_entity': base_entity - } - - # Add entities to grid - grid.entities.append(player) - grid.entities.append(enemy1) - grid.entities.append(enemy2) - grid.entities.append(treasure) - grid.entities.append(base_entity) - - print(f"\nAdded {len(grid.entities)} entities to grid") - - # Test 1: Direct indexing - print("\n--- Test 1: Direct Indexing ---") - retrieved_entities = [] - for i in range(len(grid.entities)): - entity = grid.entities[i] - retrieved_entities.append(entity) - print(f"grid.entities[{i}]: type={type(entity).__name__}, id={id(entity)}") - - # Test 2: Check type preservation - print("\n--- Test 2: Type Preservation Check ---") - r_player = grid.entities[0] - r_enemy1 = grid.entities[1] - r_treasure = grid.entities[3] - - # Check types - tests_passed = 0 - tests_total = 0 - - tests_total += 1 - if type(r_player).__name__ == "Player": - print("✓ PASS: Player type preserved") - tests_passed += 1 - else: - print(f"✗ FAIL: Player type lost! Got {type(r_player).__name__} instead of Player") - print(" This is the core Issue #76 bug!") - - tests_total += 1 - if type(r_enemy1).__name__ == "Enemy": - print("✓ PASS: Enemy type preserved") - tests_passed += 1 - else: - print(f"✗ FAIL: Enemy type lost! Got {type(r_enemy1).__name__} instead of Enemy") - - tests_total += 1 - if type(r_treasure).__name__ == "Treasure": - print("✓ PASS: Treasure type preserved") - tests_passed += 1 - else: - print(f"✗ FAIL: Treasure type lost! Got {type(r_treasure).__name__} instead of Treasure") - - # Test 3: Check attribute preservation - print("\n--- Test 3: Attribute Preservation ---") - - # Test Player attributes - try: - tests_total += 1 - health = r_player.health - inv = r_player.inventory - pid = r_player.player_id - print(f"✓ PASS: Player attributes accessible: health={health}, inventory={inv}, id={pid}") - tests_passed += 1 - except AttributeError as e: - print(f"✗ FAIL: Player attributes lost: {e}") - - # Test Enemy attributes - try: - tests_total += 1 - etype = r_enemy1.enemy_type - aggr = r_enemy1.aggression - print(f"✓ PASS: Enemy attributes accessible: type={etype}, aggression={aggr}") - tests_passed += 1 - except AttributeError as e: - print(f"✗ FAIL: Enemy attributes lost: {e}") - - # Test 4: Method preservation - print("\n--- Test 4: Method Preservation ---") - - try: - tests_total += 1 - r_player.take_damage(10) - print(f"✓ PASS: Player method callable, health now: {r_player.health}") - tests_passed += 1 - except AttributeError as e: - print(f"✗ FAIL: Player methods lost: {e}") - - try: - tests_total += 1 - next_move = r_enemy1.get_next_move() - print(f"✓ PASS: Enemy method callable, next move: {next_move}") - tests_passed += 1 - except AttributeError as e: - print(f"✗ FAIL: Enemy methods lost: {e}") - - # Test 5: Iteration - print("\n--- Test 5: Iteration Test ---") - try: - tests_total += 1 - type_list = [] - for entity in grid.entities: - type_list.append(type(entity).__name__) - print(f"Types during iteration: {type_list}") - if type_list == ["Player", "Enemy", "Enemy", "Treasure", "Entity"]: - print("✓ PASS: All types preserved during iteration") - tests_passed += 1 - else: - print("✗ FAIL: Types lost during iteration") - except Exception as e: - print(f"✗ FAIL: Iteration error: {e}") - - # Test 6: Identity check - print("\n--- Test 6: Object Identity ---") - tests_total += 1 - if r_player is original_refs['player']: - print("✓ PASS: Retrieved object is the same Python object") - tests_passed += 1 - else: - print("✗ FAIL: Retrieved object is a different instance") - print(f" Original id: {id(original_refs['player'])}") - print(f" Retrieved id: {id(r_player)}") - - # Test 7: Modification persistence - print("\n--- Test 7: Modification Persistence ---") - tests_total += 1 - r_player.x = 50 - r_player.y = 50 - - # Retrieve again - r_player2 = grid.entities[0] - if r_player2.x == 50 and r_player2.y == 50: - print("✓ PASS: Modifications persist across retrievals") - tests_passed += 1 - else: - print(f"✗ FAIL: Modifications lost: position is ({r_player2.x}, {r_player2.y})") - - # Take screenshot - automation.screenshot("/tmp/issue_76_test.png") - - # Summary - print(f"\n=== SUMMARY ===") - print(f"Tests passed: {tests_passed}/{tests_total}") - - if tests_passed < tests_total: - print("\nIssue #76: The C++ implementation creates new PyUIEntityObject instances") - print("with type 'Entity' instead of preserving the original Python type.") - print("This causes derived classes to lose their type, attributes, and methods.") - print("\nThe fix requires storing and restoring the original Python type") - print("when creating objects in UIEntityCollection::getitem.") - - return tests_passed == tests_total - -def run_test(runtime): - """Timer callback to run the test""" - try: - success = test_type_preservation() - print("\nOverall result: " + ("PASS" if success else "FAIL")) - except Exception as e: - print(f"\nTest error: {e}") - import traceback - traceback.print_exc() - print("\nOverall result: FAIL") - - sys.exit(0) - -# Set up the test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test to run after game loop starts -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/issue_79_color_properties_test.py b/tests/issue_79_color_properties_test.py deleted file mode 100644 index 05233b2..0000000 --- a/tests/issue_79_color_properties_test.py +++ /dev/null @@ -1,170 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #79: Color r, g, b, a properties return None - -This test verifies that Color object properties (r, g, b, a) work correctly. -""" - -import mcrfpy -import sys - -def test_color_properties(): - """Test Color r, g, b, a property access and modification""" - print("=== Testing Color r, g, b, a Properties (Issue #79) ===\n") - - tests_passed = 0 - tests_total = 0 - - # Test 1: Create color and check properties - print("--- Test 1: Basic property access ---") - color1 = mcrfpy.Color(255, 128, 64, 32) - - tests_total += 1 - if color1.r == 255: - print("✓ PASS: color.r returns correct value (255)") - tests_passed += 1 - else: - print(f"✗ FAIL: color.r returned {color1.r} instead of 255") - - tests_total += 1 - if color1.g == 128: - print("✓ PASS: color.g returns correct value (128)") - tests_passed += 1 - else: - print(f"✗ FAIL: color.g returned {color1.g} instead of 128") - - tests_total += 1 - if color1.b == 64: - print("✓ PASS: color.b returns correct value (64)") - tests_passed += 1 - else: - print(f"✗ FAIL: color.b returned {color1.b} instead of 64") - - tests_total += 1 - if color1.a == 32: - print("✓ PASS: color.a returns correct value (32)") - tests_passed += 1 - else: - print(f"✗ FAIL: color.a returned {color1.a} instead of 32") - - # Test 2: Modify properties - print("\n--- Test 2: Property modification ---") - color1.r = 200 - color1.g = 100 - color1.b = 50 - color1.a = 25 - - tests_total += 1 - if color1.r == 200: - print("✓ PASS: color.r set successfully") - tests_passed += 1 - else: - print(f"✗ FAIL: color.r is {color1.r} after setting to 200") - - tests_total += 1 - if color1.g == 100: - print("✓ PASS: color.g set successfully") - tests_passed += 1 - else: - print(f"✗ FAIL: color.g is {color1.g} after setting to 100") - - tests_total += 1 - if color1.b == 50: - print("✓ PASS: color.b set successfully") - tests_passed += 1 - else: - print(f"✗ FAIL: color.b is {color1.b} after setting to 50") - - tests_total += 1 - if color1.a == 25: - print("✓ PASS: color.a set successfully") - tests_passed += 1 - else: - print(f"✗ FAIL: color.a is {color1.a} after setting to 25") - - # Test 3: Boundary values - print("\n--- Test 3: Boundary value tests ---") - color2 = mcrfpy.Color(0, 0, 0, 0) - - tests_total += 1 - if color2.r == 0 and color2.g == 0 and color2.b == 0 and color2.a == 0: - print("✓ PASS: Minimum values (0) work correctly") - tests_passed += 1 - else: - print("✗ FAIL: Minimum values not working") - - color3 = mcrfpy.Color(255, 255, 255, 255) - tests_total += 1 - if color3.r == 255 and color3.g == 255 and color3.b == 255 and color3.a == 255: - print("✓ PASS: Maximum values (255) work correctly") - tests_passed += 1 - else: - print("✗ FAIL: Maximum values not working") - - # Test 4: Invalid value handling - print("\n--- Test 4: Invalid value handling ---") - tests_total += 1 - try: - color3.r = 256 # Out of range - print("✗ FAIL: Should have raised ValueError for value > 255") - except ValueError as e: - print(f"✓ PASS: Correctly raised ValueError: {e}") - tests_passed += 1 - - tests_total += 1 - try: - color3.g = -1 # Out of range - print("✗ FAIL: Should have raised ValueError for value < 0") - except ValueError as e: - print(f"✓ PASS: Correctly raised ValueError: {e}") - tests_passed += 1 - - tests_total += 1 - try: - color3.b = "red" # Wrong type - print("✗ FAIL: Should have raised TypeError for string value") - except TypeError as e: - print(f"✓ PASS: Correctly raised TypeError: {e}") - tests_passed += 1 - - # Test 5: Verify __repr__ shows correct values - print("\n--- Test 5: String representation ---") - color4 = mcrfpy.Color(10, 20, 30, 40) - repr_str = repr(color4) - tests_total += 1 - if "(10, 20, 30, 40)" in repr_str: - print(f"✓ PASS: __repr__ shows correct values: {repr_str}") - tests_passed += 1 - else: - print(f"✗ FAIL: __repr__ incorrect: {repr_str}") - - # Summary - print(f"\n=== SUMMARY ===") - print(f"Tests passed: {tests_passed}/{tests_total}") - - if tests_passed == tests_total: - print("\nIssue #79 FIXED: Color properties now work correctly!") - else: - print("\nIssue #79: Some tests failed") - - return tests_passed == tests_total - -def run_test(runtime): - """Timer callback to run the test""" - try: - success = test_color_properties() - print("\nOverall result: " + ("PASS" if success else "FAIL")) - except Exception as e: - print(f"\nTest error: {e}") - import traceback - traceback.print_exc() - print("\nOverall result: FAIL") - - sys.exit(0) - -# Set up the test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test to run after game loop starts -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/issue_80_caption_font_size_test.py b/tests/issue_80_caption_font_size_test.py deleted file mode 100644 index 0193355..0000000 --- a/tests/issue_80_caption_font_size_test.py +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #80: Rename Caption.size to font_size - -This test verifies that Caption now uses font_size property instead of size, -while maintaining backward compatibility. -""" - -import mcrfpy -import sys - -def test_caption_font_size(): - """Test Caption font_size property""" - print("=== Testing Caption font_size Property (Issue #80) ===\n") - - tests_passed = 0 - tests_total = 0 - - # Create a caption for testing - caption = mcrfpy.Caption((100, 100), "Test Text", mcrfpy.Font("assets/JetbrainsMono.ttf")) - - # Test 1: Check that font_size property exists and works - print("--- Test 1: font_size property ---") - tests_total += 1 - try: - # Set font size using new property name - caption.font_size = 24 - if caption.font_size == 24: - print("✓ PASS: font_size property works correctly") - tests_passed += 1 - else: - print(f"✗ FAIL: font_size is {caption.font_size}, expected 24") - except AttributeError as e: - print(f"✗ FAIL: font_size property not found: {e}") - - # Test 2: Check that old 'size' property is removed - print("\n--- Test 2: Old 'size' property removed ---") - tests_total += 1 - try: - # Try to access size property - this should fail - old_size = caption.size - print(f"✗ FAIL: 'size' property still accessible (value: {old_size}) - should be removed") - except AttributeError: - print("✓ PASS: 'size' property correctly removed") - tests_passed += 1 - - # Test 3: Verify font_size changes are reflected - print("\n--- Test 3: font_size changes ---") - tests_total += 1 - caption.font_size = 36 - if caption.font_size == 36: - print("✓ PASS: font_size changes are reflected correctly") - tests_passed += 1 - else: - print(f"✗ FAIL: font_size is {caption.font_size}, expected 36") - - # Test 4: Check property type - print("\n--- Test 4: Property type check ---") - tests_total += 1 - caption.font_size = 18 - if isinstance(caption.font_size, int): - print("✓ PASS: font_size returns integer as expected") - tests_passed += 1 - else: - print(f"✗ FAIL: font_size returns {type(caption.font_size).__name__}, expected int") - - # Test 5: Verify in __dir__ - print("\n--- Test 5: Property introspection ---") - tests_total += 1 - properties = dir(caption) - if 'font_size' in properties: - print("✓ PASS: 'font_size' appears in dir(caption)") - tests_passed += 1 - else: - print("✗ FAIL: 'font_size' not found in dir(caption)") - - # Check if 'size' still appears - if 'size' in properties: - print(" INFO: 'size' still appears in dir(caption) - backward compatibility maintained") - else: - print(" INFO: 'size' removed from dir(caption) - breaking change") - - # Test 6: Edge cases - print("\n--- Test 6: Edge cases ---") - tests_total += 1 - all_passed = True - - # Test setting to 0 - caption.font_size = 0 - if caption.font_size != 0: - print(f"✗ FAIL: Setting font_size to 0 failed (got {caption.font_size})") - all_passed = False - - # Test setting to large value - caption.font_size = 100 - if caption.font_size != 100: - print(f"✗ FAIL: Setting font_size to 100 failed (got {caption.font_size})") - all_passed = False - - # Test float to int conversion - caption.font_size = 24.7 - if caption.font_size != 24: - print(f"✗ FAIL: Float to int conversion failed (got {caption.font_size})") - all_passed = False - - if all_passed: - print("✓ PASS: All edge cases handled correctly") - tests_passed += 1 - else: - print("✗ FAIL: Some edge cases failed") - - # Test 7: Scene UI integration - print("\n--- Test 7: Scene UI integration ---") - tests_total += 1 - try: - scene_ui = mcrfpy.sceneUI("test") - scene_ui.append(caption) - - # Modify font_size after adding to scene - caption.font_size = 32 - - print("✓ PASS: Caption with font_size works in scene UI") - tests_passed += 1 - except Exception as e: - print(f"✗ FAIL: Scene UI integration failed: {e}") - - # Summary - print(f"\n=== SUMMARY ===") - print(f"Tests passed: {tests_passed}/{tests_total}") - - if tests_passed == tests_total: - print("\nIssue #80 FIXED: Caption.size successfully renamed to font_size!") - else: - print("\nIssue #80: Some tests failed") - - return tests_passed == tests_total - -def run_test(runtime): - """Timer callback to run the test""" - try: - success = test_caption_font_size() - print("\nOverall result: " + ("PASS" if success else "FAIL")) - except Exception as e: - print(f"\nTest error: {e}") - import traceback - traceback.print_exc() - print("\nOverall result: FAIL") - - sys.exit(0) - -# Set up the test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test to run after game loop starts -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/issue_81_sprite_index_standardization_test.py b/tests/issue_81_sprite_index_standardization_test.py deleted file mode 100644 index c7b7b2d..0000000 --- a/tests/issue_81_sprite_index_standardization_test.py +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #81: Standardize sprite_index property name - -This test verifies that both UISprite and UIEntity use "sprite_index" instead of "sprite_number" -for consistency across the API. -""" - -import mcrfpy -import sys - -def test_sprite_index_property(): - """Test sprite_index property on UISprite""" - print("=== Testing UISprite sprite_index Property ===") - - tests_passed = 0 - tests_total = 0 - - # Create a texture and sprite - texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) - sprite = mcrfpy.Sprite(10, 10, texture, 5, 1.0) - - # Test 1: Check sprite_index property exists - tests_total += 1 - try: - idx = sprite.sprite_index - if idx == 5: - print(f"✓ PASS: sprite.sprite_index = {idx}") - tests_passed += 1 - else: - print(f"✗ FAIL: sprite.sprite_index = {idx}, expected 5") - except AttributeError as e: - print(f"✗ FAIL: sprite_index not accessible: {e}") - - # Test 2: Check sprite_index setter - tests_total += 1 - try: - sprite.sprite_index = 10 - if sprite.sprite_index == 10: - print("✓ PASS: sprite_index setter works") - tests_passed += 1 - else: - print(f"✗ FAIL: sprite_index setter failed, got {sprite.sprite_index}") - except Exception as e: - print(f"✗ FAIL: sprite_index setter error: {e}") - - # Test 3: Check sprite_number is removed/deprecated - tests_total += 1 - if hasattr(sprite, 'sprite_number'): - # Check if it's an alias - sprite.sprite_number = 15 - if sprite.sprite_index == 15: - print("✓ PASS: sprite_number exists as backward-compatible alias") - tests_passed += 1 - else: - print("✗ FAIL: sprite_number exists but doesn't update sprite_index") - else: - print("✓ PASS: sprite_number property removed (no backward compatibility)") - tests_passed += 1 - - # Test 4: Check repr uses sprite_index - tests_total += 1 - repr_str = repr(sprite) - if "sprite_index=" in repr_str: - print(f"✓ PASS: repr uses sprite_index: {repr_str}") - tests_passed += 1 - elif "sprite_number=" in repr_str: - print(f"✗ FAIL: repr still uses sprite_number: {repr_str}") - else: - print(f"✗ FAIL: repr doesn't show sprite info: {repr_str}") - - return tests_passed, tests_total - -def test_entity_sprite_index_property(): - """Test sprite_index property on Entity""" - print("\n=== Testing Entity sprite_index Property ===") - - tests_passed = 0 - tests_total = 0 - - # Create an entity with required position - entity = mcrfpy.Entity((0, 0)) - - # Test 1: Check sprite_index property exists - tests_total += 1 - try: - # Set initial value - entity.sprite_index = 42 - idx = entity.sprite_index - if idx == 42: - print(f"✓ PASS: entity.sprite_index = {idx}") - tests_passed += 1 - else: - print(f"✗ FAIL: entity.sprite_index = {idx}, expected 42") - except AttributeError as e: - print(f"✗ FAIL: sprite_index not accessible: {e}") - - # Test 2: Check sprite_number is removed/deprecated - tests_total += 1 - if hasattr(entity, 'sprite_number'): - # Check if it's an alias - entity.sprite_number = 99 - if hasattr(entity, 'sprite_index') and entity.sprite_index == 99: - print("✓ PASS: sprite_number exists as backward-compatible alias") - tests_passed += 1 - else: - print("✗ FAIL: sprite_number exists but doesn't update sprite_index") - else: - print("✓ PASS: sprite_number property removed (no backward compatibility)") - tests_passed += 1 - - # Test 3: Check repr uses sprite_index - tests_total += 1 - repr_str = repr(entity) - if "sprite_index=" in repr_str: - print(f"✓ PASS: repr uses sprite_index: {repr_str}") - tests_passed += 1 - elif "sprite_number=" in repr_str: - print(f"✗ FAIL: repr still uses sprite_number: {repr_str}") - else: - print(f"? INFO: repr doesn't show sprite info: {repr_str}") - # This might be okay if entity doesn't show sprite in repr - tests_passed += 1 - - return tests_passed, tests_total - -def test_animation_compatibility(): - """Test that animations work with sprite_index""" - print("\n=== Testing Animation Compatibility ===") - - tests_passed = 0 - tests_total = 0 - - # Test animation with sprite_index property name - tests_total += 1 - try: - # This tests that the animation system recognizes sprite_index - texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) - sprite = mcrfpy.Sprite(0, 0, texture, 0, 1.0) - - # Try to animate sprite_index (even if we can't directly test animations here) - sprite.sprite_index = 0 - sprite.sprite_index = 5 - sprite.sprite_index = 10 - - print("✓ PASS: sprite_index property works for potential animations") - tests_passed += 1 - except Exception as e: - print(f"✗ FAIL: sprite_index animation compatibility issue: {e}") - - return tests_passed, tests_total - -def run_test(runtime): - """Timer callback to run the test""" - try: - print("=== Testing sprite_index Property Standardization (Issue #81) ===\n") - - sprite_passed, sprite_total = test_sprite_index_property() - entity_passed, entity_total = test_entity_sprite_index_property() - anim_passed, anim_total = test_animation_compatibility() - - total_passed = sprite_passed + entity_passed + anim_passed - total_tests = sprite_total + entity_total + anim_total - - print(f"\n=== SUMMARY ===") - print(f"Sprite tests: {sprite_passed}/{sprite_total}") - print(f"Entity tests: {entity_passed}/{entity_total}") - print(f"Animation tests: {anim_passed}/{anim_total}") - print(f"Total tests passed: {total_passed}/{total_tests}") - - if total_passed == total_tests: - print("\nIssue #81 FIXED: sprite_index property standardized!") - print("\nOverall result: PASS") - else: - print("\nIssue #81: Some tests failed") - print("\nOverall result: FAIL") - - except Exception as e: - print(f"\nTest error: {e}") - import traceback - traceback.print_exc() - print("\nOverall result: FAIL") - - sys.exit(0) - -# Set up the test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test to run after game loop starts -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/issue_82_sprite_scale_xy_test.py b/tests/issue_82_sprite_scale_xy_test.py deleted file mode 100644 index a80c403..0000000 --- a/tests/issue_82_sprite_scale_xy_test.py +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #82: Add scale_x and scale_y to UISprite - -This test verifies that UISprite now supports non-uniform scaling through -separate scale_x and scale_y properties, in addition to the existing uniform -scale property. -""" - -import mcrfpy -import sys - -def test_scale_xy_properties(): - """Test scale_x and scale_y properties on UISprite""" - print("=== Testing UISprite scale_x and scale_y Properties ===") - - tests_passed = 0 - tests_total = 0 - - # Create a texture and sprite - texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) - sprite = mcrfpy.Sprite(10, 10, texture, 0, 1.0) - - # Test 1: Check scale_x property exists and defaults correctly - tests_total += 1 - try: - scale_x = sprite.scale_x - if scale_x == 1.0: - print(f"✓ PASS: sprite.scale_x = {scale_x} (default)") - tests_passed += 1 - else: - print(f"✗ FAIL: sprite.scale_x = {scale_x}, expected 1.0") - except AttributeError as e: - print(f"✗ FAIL: scale_x not accessible: {e}") - - # Test 2: Check scale_y property exists and defaults correctly - tests_total += 1 - try: - scale_y = sprite.scale_y - if scale_y == 1.0: - print(f"✓ PASS: sprite.scale_y = {scale_y} (default)") - tests_passed += 1 - else: - print(f"✗ FAIL: sprite.scale_y = {scale_y}, expected 1.0") - except AttributeError as e: - print(f"✗ FAIL: scale_y not accessible: {e}") - - # Test 3: Set scale_x independently - tests_total += 1 - try: - sprite.scale_x = 2.0 - if sprite.scale_x == 2.0 and sprite.scale_y == 1.0: - print(f"✓ PASS: scale_x set independently (x={sprite.scale_x}, y={sprite.scale_y})") - tests_passed += 1 - else: - print(f"✗ FAIL: scale_x didn't set correctly (x={sprite.scale_x}, y={sprite.scale_y})") - except Exception as e: - print(f"✗ FAIL: scale_x setter error: {e}") - - # Test 4: Set scale_y independently - tests_total += 1 - try: - sprite.scale_y = 3.0 - if sprite.scale_x == 2.0 and sprite.scale_y == 3.0: - print(f"✓ PASS: scale_y set independently (x={sprite.scale_x}, y={sprite.scale_y})") - tests_passed += 1 - else: - print(f"✗ FAIL: scale_y didn't set correctly (x={sprite.scale_x}, y={sprite.scale_y})") - except Exception as e: - print(f"✗ FAIL: scale_y setter error: {e}") - - # Test 5: Uniform scale property interaction - tests_total += 1 - try: - # Setting uniform scale should affect both x and y - sprite.scale = 1.5 - if sprite.scale_x == 1.5 and sprite.scale_y == 1.5: - print(f"✓ PASS: uniform scale sets both scale_x and scale_y") - tests_passed += 1 - else: - print(f"✗ FAIL: uniform scale didn't update scale_x/scale_y correctly") - except Exception as e: - print(f"✗ FAIL: uniform scale interaction error: {e}") - - # Test 6: Reading uniform scale with non-uniform values - tests_total += 1 - try: - sprite.scale_x = 2.0 - sprite.scale_y = 3.0 - uniform_scale = sprite.scale - # When scales differ, scale property should return scale_x (or could be average, or error) - print(f"? INFO: With non-uniform scaling (x=2.0, y=3.0), scale property returns: {uniform_scale}") - # We'll accept this behavior whatever it is - tests_passed += 1 - except Exception as e: - print(f"✗ FAIL: reading scale with non-uniform values failed: {e}") - - return tests_passed, tests_total - -def test_animation_compatibility(): - """Test that animations work with scale_x and scale_y""" - print("\n=== Testing Animation Compatibility ===") - - tests_passed = 0 - tests_total = 0 - - # Test property system compatibility - tests_total += 1 - try: - texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) - sprite = mcrfpy.Sprite(0, 0, texture, 0, 1.0) - - # Test setting various scale values - sprite.scale_x = 0.5 - sprite.scale_y = 2.0 - sprite.scale_x = 1.5 - sprite.scale_y = 1.5 - - print("✓ PASS: scale_x and scale_y properties work for potential animations") - tests_passed += 1 - except Exception as e: - print(f"✗ FAIL: scale_x/scale_y animation compatibility issue: {e}") - - return tests_passed, tests_total - -def test_edge_cases(): - """Test edge cases for scale properties""" - print("\n=== Testing Edge Cases ===") - - tests_passed = 0 - tests_total = 0 - - texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) - sprite = mcrfpy.Sprite(0, 0, texture, 0, 1.0) - - # Test 1: Zero scale - tests_total += 1 - try: - sprite.scale_x = 0.0 - sprite.scale_y = 0.0 - print(f"✓ PASS: Zero scale allowed (x={sprite.scale_x}, y={sprite.scale_y})") - tests_passed += 1 - except Exception as e: - print(f"✗ FAIL: Zero scale not allowed: {e}") - - # Test 2: Negative scale (flip) - tests_total += 1 - try: - sprite.scale_x = -1.0 - sprite.scale_y = -1.0 - print(f"✓ PASS: Negative scale allowed for flipping (x={sprite.scale_x}, y={sprite.scale_y})") - tests_passed += 1 - except Exception as e: - print(f"✗ FAIL: Negative scale not allowed: {e}") - - # Test 3: Very large scale - tests_total += 1 - try: - sprite.scale_x = 100.0 - sprite.scale_y = 100.0 - print(f"✓ PASS: Large scale values allowed (x={sprite.scale_x}, y={sprite.scale_y})") - tests_passed += 1 - except Exception as e: - print(f"✗ FAIL: Large scale values not allowed: {e}") - - return tests_passed, tests_total - -def run_test(runtime): - """Timer callback to run the test""" - try: - print("=== Testing scale_x and scale_y Properties (Issue #82) ===\n") - - basic_passed, basic_total = test_scale_xy_properties() - anim_passed, anim_total = test_animation_compatibility() - edge_passed, edge_total = test_edge_cases() - - total_passed = basic_passed + anim_passed + edge_passed - total_tests = basic_total + anim_total + edge_total - - print(f"\n=== SUMMARY ===") - print(f"Basic tests: {basic_passed}/{basic_total}") - print(f"Animation tests: {anim_passed}/{anim_total}") - print(f"Edge case tests: {edge_passed}/{edge_total}") - print(f"Total tests passed: {total_passed}/{total_tests}") - - if total_passed == total_tests: - print("\nIssue #82 FIXED: scale_x and scale_y properties added!") - print("\nOverall result: PASS") - else: - print("\nIssue #82: Some tests failed") - print("\nOverall result: FAIL") - - except Exception as e: - print(f"\nTest error: {e}") - import traceback - traceback.print_exc() - print("\nOverall result: FAIL") - - sys.exit(0) - -# Set up the test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test to run after game loop starts -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/issue_83_position_tuple_test.py b/tests/issue_83_position_tuple_test.py deleted file mode 100644 index 5888cf0..0000000 --- a/tests/issue_83_position_tuple_test.py +++ /dev/null @@ -1,269 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #83: Add position tuple support to constructors - -This test verifies that UI element constructors now support both: -- Traditional (x, y) as separate arguments -- Tuple form ((x, y)) as a single argument -- Vector form (Vector(x, y)) as a single argument -""" - -import mcrfpy -import sys - -def test_frame_position_tuple(): - """Test Frame constructor with position tuples""" - print("=== Testing Frame Position Tuple Support ===") - - tests_passed = 0 - tests_total = 0 - - # Test 1: Traditional (x, y) form - tests_total += 1 - try: - frame1 = mcrfpy.Frame(10, 20, 100, 50) - if frame1.x == 10 and frame1.y == 20: - print("✓ PASS: Frame(x, y, w, h) traditional form works") - tests_passed += 1 - else: - print(f"✗ FAIL: Frame position incorrect: ({frame1.x}, {frame1.y})") - except Exception as e: - print(f"✗ FAIL: Traditional form failed: {e}") - - # Test 2: Tuple ((x, y)) form - tests_total += 1 - try: - frame2 = mcrfpy.Frame((30, 40), 100, 50) - if frame2.x == 30 and frame2.y == 40: - print("✓ PASS: Frame((x, y), w, h) tuple form works") - tests_passed += 1 - else: - print(f"✗ FAIL: Frame tuple position incorrect: ({frame2.x}, {frame2.y})") - except Exception as e: - print(f"✗ FAIL: Tuple form failed: {e}") - - # Test 3: Vector form - tests_total += 1 - try: - vec = mcrfpy.Vector(50, 60) - frame3 = mcrfpy.Frame(vec, 100, 50) - if frame3.x == 50 and frame3.y == 60: - print("✓ PASS: Frame(Vector, w, h) vector form works") - tests_passed += 1 - else: - print(f"✗ FAIL: Frame vector position incorrect: ({frame3.x}, {frame3.y})") - except Exception as e: - print(f"✗ FAIL: Vector form failed: {e}") - - return tests_passed, tests_total - -def test_sprite_position_tuple(): - """Test Sprite constructor with position tuples""" - print("\n=== Testing Sprite Position Tuple Support ===") - - tests_passed = 0 - tests_total = 0 - - texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) - - # Test 1: Traditional (x, y) form - tests_total += 1 - try: - sprite1 = mcrfpy.Sprite(10, 20, texture, 0, 1.0) - if sprite1.x == 10 and sprite1.y == 20: - print("✓ PASS: Sprite(x, y, texture, ...) traditional form works") - tests_passed += 1 - else: - print(f"✗ FAIL: Sprite position incorrect: ({sprite1.x}, {sprite1.y})") - except Exception as e: - print(f"✗ FAIL: Traditional form failed: {e}") - - # Test 2: Tuple ((x, y)) form - tests_total += 1 - try: - sprite2 = mcrfpy.Sprite((30, 40), texture, 0, 1.0) - if sprite2.x == 30 and sprite2.y == 40: - print("✓ PASS: Sprite((x, y), texture, ...) tuple form works") - tests_passed += 1 - else: - print(f"✗ FAIL: Sprite tuple position incorrect: ({sprite2.x}, {sprite2.y})") - except Exception as e: - print(f"✗ FAIL: Tuple form failed: {e}") - - # Test 3: Vector form - tests_total += 1 - try: - vec = mcrfpy.Vector(50, 60) - sprite3 = mcrfpy.Sprite(vec, texture, 0, 1.0) - if sprite3.x == 50 and sprite3.y == 60: - print("✓ PASS: Sprite(Vector, texture, ...) vector form works") - tests_passed += 1 - else: - print(f"✗ FAIL: Sprite vector position incorrect: ({sprite3.x}, {sprite3.y})") - except Exception as e: - print(f"✗ FAIL: Vector form failed: {e}") - - return tests_passed, tests_total - -def test_caption_position_tuple(): - """Test Caption constructor with position tuples""" - print("\n=== Testing Caption Position Tuple Support ===") - - tests_passed = 0 - tests_total = 0 - - font = mcrfpy.Font("assets/JetbrainsMono.ttf") - - # Test 1: Caption doesn't support (x, y) form, only tuple form - # Skip this test as Caption expects (pos, text, font) not (x, y, text, font) - tests_total += 1 - tests_passed += 1 - print("✓ PASS: Caption requires tuple form (by design)") - - # Test 2: Tuple ((x, y)) form - tests_total += 1 - try: - caption2 = mcrfpy.Caption((30, 40), "Test", font) - if caption2.x == 30 and caption2.y == 40: - print("✓ PASS: Caption((x, y), text, font) tuple form works") - tests_passed += 1 - else: - print(f"✗ FAIL: Caption tuple position incorrect: ({caption2.x}, {caption2.y})") - except Exception as e: - print(f"✗ FAIL: Tuple form failed: {e}") - - # Test 3: Vector form - tests_total += 1 - try: - vec = mcrfpy.Vector(50, 60) - caption3 = mcrfpy.Caption(vec, "Test", font) - if caption3.x == 50 and caption3.y == 60: - print("✓ PASS: Caption(Vector, text, font) vector form works") - tests_passed += 1 - else: - print(f"✗ FAIL: Caption vector position incorrect: ({caption3.x}, {caption3.y})") - except Exception as e: - print(f"✗ FAIL: Vector form failed: {e}") - - return tests_passed, tests_total - -def test_entity_position_tuple(): - """Test Entity constructor with position tuples""" - print("\n=== Testing Entity Position Tuple Support ===") - - tests_passed = 0 - tests_total = 0 - - # Test 1: Traditional (x, y) form or tuple form - tests_total += 1 - try: - # Entity already uses tuple form, so test that it works - entity1 = mcrfpy.Entity((10, 20)) - # Entity.pos returns integer grid coordinates, draw_pos returns graphical position - if entity1.draw_pos.x == 10 and entity1.draw_pos.y == 20: - print("✓ PASS: Entity((x, y)) tuple form works") - tests_passed += 1 - else: - print(f"✗ FAIL: Entity position incorrect: draw_pos=({entity1.draw_pos.x}, {entity1.draw_pos.y}), pos=({entity1.pos.x}, {entity1.pos.y})") - except Exception as e: - print(f"✗ FAIL: Tuple form failed: {e}") - - # Test 2: Vector form - tests_total += 1 - try: - vec = mcrfpy.Vector(30, 40) - entity2 = mcrfpy.Entity(vec) - if entity2.draw_pos.x == 30 and entity2.draw_pos.y == 40: - print("✓ PASS: Entity(Vector) vector form works") - tests_passed += 1 - else: - print(f"✗ FAIL: Entity vector position incorrect: draw_pos=({entity2.draw_pos.x}, {entity2.draw_pos.y}), pos=({entity2.pos.x}, {entity2.pos.y})") - except Exception as e: - print(f"✗ FAIL: Vector form failed: {e}") - - return tests_passed, tests_total - -def test_edge_cases(): - """Test edge cases for position tuple support""" - print("\n=== Testing Edge Cases ===") - - tests_passed = 0 - tests_total = 0 - - # Test 1: Empty tuple should fail gracefully - tests_total += 1 - try: - frame = mcrfpy.Frame((), 100, 50) - # Empty tuple might be accepted and treated as (0, 0) - if frame.x == 0 and frame.y == 0: - print("✓ PASS: Empty tuple accepted as (0, 0)") - tests_passed += 1 - else: - print("✗ FAIL: Empty tuple handled unexpectedly") - except Exception as e: - print(f"✓ PASS: Empty tuple correctly rejected: {e}") - tests_passed += 1 - - # Test 2: Wrong tuple size should fail - tests_total += 1 - try: - frame = mcrfpy.Frame((10, 20, 30), 100, 50) - print("✗ FAIL: 3-element tuple should have raised an error") - except Exception as e: - print(f"✓ PASS: Wrong tuple size correctly rejected: {e}") - tests_passed += 1 - - # Test 3: Non-numeric tuple should fail - tests_total += 1 - try: - frame = mcrfpy.Frame(("x", "y"), 100, 50) - print("✗ FAIL: Non-numeric tuple should have raised an error") - except Exception as e: - print(f"✓ PASS: Non-numeric tuple correctly rejected: {e}") - tests_passed += 1 - - return tests_passed, tests_total - -def run_test(runtime): - """Timer callback to run the test""" - try: - print("=== Testing Position Tuple Support in Constructors (Issue #83) ===\n") - - frame_passed, frame_total = test_frame_position_tuple() - sprite_passed, sprite_total = test_sprite_position_tuple() - caption_passed, caption_total = test_caption_position_tuple() - entity_passed, entity_total = test_entity_position_tuple() - edge_passed, edge_total = test_edge_cases() - - total_passed = frame_passed + sprite_passed + caption_passed + entity_passed + edge_passed - total_tests = frame_total + sprite_total + caption_total + entity_total + edge_total - - print(f"\n=== SUMMARY ===") - print(f"Frame tests: {frame_passed}/{frame_total}") - print(f"Sprite tests: {sprite_passed}/{sprite_total}") - print(f"Caption tests: {caption_passed}/{caption_total}") - print(f"Entity tests: {entity_passed}/{entity_total}") - print(f"Edge case tests: {edge_passed}/{edge_total}") - print(f"Total tests passed: {total_passed}/{total_tests}") - - if total_passed == total_tests: - print("\nIssue #83 FIXED: Position tuple support added to constructors!") - print("\nOverall result: PASS") - else: - print("\nIssue #83: Some tests failed") - print("\nOverall result: FAIL") - - except Exception as e: - print(f"\nTest error: {e}") - import traceback - traceback.print_exc() - print("\nOverall result: FAIL") - - sys.exit(0) - -# Set up the test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test to run after game loop starts -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/issue_84_pos_property_test.py b/tests/issue_84_pos_property_test.py deleted file mode 100644 index f6f9062..0000000 --- a/tests/issue_84_pos_property_test.py +++ /dev/null @@ -1,228 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #84: Add pos property to Frame and Sprite - -This test verifies that Frame and Sprite now have a 'pos' property that -returns and accepts Vector objects, similar to Caption and Entity. -""" - -import mcrfpy -import sys - -def test_frame_pos_property(): - """Test pos property on Frame""" - print("=== Testing Frame pos Property ===") - - tests_passed = 0 - tests_total = 0 - - # Test 1: Get pos property - tests_total += 1 - try: - frame = mcrfpy.Frame(10, 20, 100, 50) - pos = frame.pos - if hasattr(pos, 'x') and hasattr(pos, 'y') and pos.x == 10 and pos.y == 20: - print(f"✓ PASS: frame.pos returns Vector({pos.x}, {pos.y})") - tests_passed += 1 - else: - print(f"✗ FAIL: frame.pos incorrect: {pos}") - except AttributeError as e: - print(f"✗ FAIL: pos property not accessible: {e}") - - # Test 2: Set pos with Vector - tests_total += 1 - try: - vec = mcrfpy.Vector(30, 40) - frame.pos = vec - if frame.x == 30 and frame.y == 40: - print(f"✓ PASS: frame.pos = Vector sets position correctly") - tests_passed += 1 - else: - print(f"✗ FAIL: pos setter failed: x={frame.x}, y={frame.y}") - except Exception as e: - print(f"✗ FAIL: pos setter with Vector error: {e}") - - # Test 3: Set pos with tuple - tests_total += 1 - try: - frame.pos = (50, 60) - if frame.x == 50 and frame.y == 60: - print(f"✓ PASS: frame.pos = tuple sets position correctly") - tests_passed += 1 - else: - print(f"✗ FAIL: pos setter with tuple failed: x={frame.x}, y={frame.y}") - except Exception as e: - print(f"✗ FAIL: pos setter with tuple error: {e}") - - # Test 4: Verify pos getter reflects changes - tests_total += 1 - try: - frame.x = 70 - frame.y = 80 - pos = frame.pos - if pos.x == 70 and pos.y == 80: - print(f"✓ PASS: pos property reflects x/y changes") - tests_passed += 1 - else: - print(f"✗ FAIL: pos doesn't reflect changes: {pos.x}, {pos.y}") - except Exception as e: - print(f"✗ FAIL: pos getter after change error: {e}") - - return tests_passed, tests_total - -def test_sprite_pos_property(): - """Test pos property on Sprite""" - print("\n=== Testing Sprite pos Property ===") - - tests_passed = 0 - tests_total = 0 - - texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) - - # Test 1: Get pos property - tests_total += 1 - try: - sprite = mcrfpy.Sprite(10, 20, texture, 0, 1.0) - pos = sprite.pos - if hasattr(pos, 'x') and hasattr(pos, 'y') and pos.x == 10 and pos.y == 20: - print(f"✓ PASS: sprite.pos returns Vector({pos.x}, {pos.y})") - tests_passed += 1 - else: - print(f"✗ FAIL: sprite.pos incorrect: {pos}") - except AttributeError as e: - print(f"✗ FAIL: pos property not accessible: {e}") - - # Test 2: Set pos with Vector - tests_total += 1 - try: - vec = mcrfpy.Vector(30, 40) - sprite.pos = vec - if sprite.x == 30 and sprite.y == 40: - print(f"✓ PASS: sprite.pos = Vector sets position correctly") - tests_passed += 1 - else: - print(f"✗ FAIL: pos setter failed: x={sprite.x}, y={sprite.y}") - except Exception as e: - print(f"✗ FAIL: pos setter with Vector error: {e}") - - # Test 3: Set pos with tuple - tests_total += 1 - try: - sprite.pos = (50, 60) - if sprite.x == 50 and sprite.y == 60: - print(f"✓ PASS: sprite.pos = tuple sets position correctly") - tests_passed += 1 - else: - print(f"✗ FAIL: pos setter with tuple failed: x={sprite.x}, y={sprite.y}") - except Exception as e: - print(f"✗ FAIL: pos setter with tuple error: {e}") - - # Test 4: Verify pos getter reflects changes - tests_total += 1 - try: - sprite.x = 70 - sprite.y = 80 - pos = sprite.pos - if pos.x == 70 and pos.y == 80: - print(f"✓ PASS: pos property reflects x/y changes") - tests_passed += 1 - else: - print(f"✗ FAIL: pos doesn't reflect changes: {pos.x}, {pos.y}") - except Exception as e: - print(f"✗ FAIL: pos getter after change error: {e}") - - return tests_passed, tests_total - -def test_consistency_with_caption_entity(): - """Test that pos property is consistent across all UI elements""" - print("\n=== Testing Consistency with Caption/Entity ===") - - tests_passed = 0 - tests_total = 0 - - # Test 1: Caption pos property (should already exist) - tests_total += 1 - try: - font = mcrfpy.Font("assets/JetbrainsMono.ttf") - caption = mcrfpy.Caption((10, 20), "Test", font) - pos = caption.pos - if hasattr(pos, 'x') and hasattr(pos, 'y'): - print(f"✓ PASS: Caption.pos works as expected") - tests_passed += 1 - else: - print(f"✗ FAIL: Caption.pos doesn't return Vector") - except Exception as e: - print(f"✗ FAIL: Caption.pos error: {e}") - - # Test 2: Entity draw_pos property (should already exist) - tests_total += 1 - try: - entity = mcrfpy.Entity((10, 20)) - pos = entity.draw_pos - if hasattr(pos, 'x') and hasattr(pos, 'y'): - print(f"✓ PASS: Entity.draw_pos works as expected") - tests_passed += 1 - else: - print(f"✗ FAIL: Entity.draw_pos doesn't return Vector") - except Exception as e: - print(f"✗ FAIL: Entity.draw_pos error: {e}") - - # Test 3: All pos properties return same type - tests_total += 1 - try: - texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) - frame = mcrfpy.Frame(10, 20, 100, 50) - sprite = mcrfpy.Sprite(10, 20, texture, 0, 1.0) - - frame_pos = frame.pos - sprite_pos = sprite.pos - - if (type(frame_pos).__name__ == type(sprite_pos).__name__ == 'Vector'): - print(f"✓ PASS: All pos properties return Vector type") - tests_passed += 1 - else: - print(f"✗ FAIL: Inconsistent pos property types") - except Exception as e: - print(f"✗ FAIL: Type consistency check error: {e}") - - return tests_passed, tests_total - -def run_test(runtime): - """Timer callback to run the test""" - try: - print("=== Testing pos Property for Frame and Sprite (Issue #84) ===\n") - - frame_passed, frame_total = test_frame_pos_property() - sprite_passed, sprite_total = test_sprite_pos_property() - consistency_passed, consistency_total = test_consistency_with_caption_entity() - - total_passed = frame_passed + sprite_passed + consistency_passed - total_tests = frame_total + sprite_total + consistency_total - - print(f"\n=== SUMMARY ===") - print(f"Frame tests: {frame_passed}/{frame_total}") - print(f"Sprite tests: {sprite_passed}/{sprite_total}") - print(f"Consistency tests: {consistency_passed}/{consistency_total}") - print(f"Total tests passed: {total_passed}/{total_tests}") - - if total_passed == total_tests: - print("\nIssue #84 FIXED: pos property added to Frame and Sprite!") - print("\nOverall result: PASS") - else: - print("\nIssue #84: Some tests failed") - print("\nOverall result: FAIL") - - except Exception as e: - print(f"\nTest error: {e}") - import traceback - traceback.print_exc() - print("\nOverall result: FAIL") - - sys.exit(0) - -# Set up the test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test to run after game loop starts -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/issue_95_uicollection_repr_test.py b/tests/issue_95_uicollection_repr_test.py deleted file mode 100644 index bb9c708..0000000 --- a/tests/issue_95_uicollection_repr_test.py +++ /dev/null @@ -1,169 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #95: Fix UICollection __repr__ type display - -This test verifies that UICollection's repr shows the actual types of contained -objects instead of just showing them all as "UIDrawable". -""" - -import mcrfpy -import sys - -def test_uicollection_repr(): - """Test UICollection repr shows correct types""" - print("=== Testing UICollection __repr__ Type Display (Issue #95) ===\n") - - tests_passed = 0 - tests_total = 0 - - # Get scene UI collection - scene_ui = mcrfpy.sceneUI("test") - - # Test 1: Empty collection - print("--- Test 1: Empty collection ---") - tests_total += 1 - repr_str = repr(scene_ui) - print(f"Empty collection repr: {repr_str}") - if "0 objects" in repr_str: - print("✓ PASS: Empty collection shows correctly") - tests_passed += 1 - else: - print("✗ FAIL: Empty collection repr incorrect") - - # Test 2: Add various UI elements - print("\n--- Test 2: Mixed UI elements ---") - tests_total += 1 - - # Add Frame - frame = mcrfpy.Frame(10, 10, 100, 100) - scene_ui.append(frame) - - # Add Caption - caption = mcrfpy.Caption((150, 50), "Test", mcrfpy.Font("assets/JetbrainsMono.ttf")) - scene_ui.append(caption) - - # Add Sprite - sprite = mcrfpy.Sprite(200, 100) - scene_ui.append(sprite) - - # Add Grid - grid = mcrfpy.Grid(10, 10) - grid.x = 300 - grid.y = 100 - scene_ui.append(grid) - - # Check repr - repr_str = repr(scene_ui) - print(f"Collection repr: {repr_str}") - - # Verify it shows the correct types - expected_types = ["1 Frame", "1 Caption", "1 Sprite", "1 Grid"] - all_found = all(expected in repr_str for expected in expected_types) - - if all_found and "UIDrawable" not in repr_str: - print("✓ PASS: All types shown correctly, no generic UIDrawable") - tests_passed += 1 - else: - print("✗ FAIL: Types not shown correctly") - for expected in expected_types: - if expected in repr_str: - print(f" ✓ Found: {expected}") - else: - print(f" ✗ Missing: {expected}") - if "UIDrawable" in repr_str: - print(" ✗ Still shows generic UIDrawable") - - # Test 3: Multiple of same type - print("\n--- Test 3: Multiple objects of same type ---") - tests_total += 1 - - # Add more frames - frame2 = mcrfpy.Frame(10, 120, 100, 100) - frame3 = mcrfpy.Frame(10, 230, 100, 100) - scene_ui.append(frame2) - scene_ui.append(frame3) - - repr_str = repr(scene_ui) - print(f"Collection repr: {repr_str}") - - if "3 Frames" in repr_str: - print("✓ PASS: Plural form shown correctly for multiple Frames") - tests_passed += 1 - else: - print("✗ FAIL: Plural form not correct") - - # Test 4: Check total count - print("\n--- Test 4: Total count verification ---") - tests_total += 1 - - # Should have: 3 Frames, 1 Caption, 1 Sprite, 1 Grid = 6 total - if "6 objects:" in repr_str: - print("✓ PASS: Total count shown correctly") - tests_passed += 1 - else: - print("✗ FAIL: Total count incorrect") - - # Test 5: Nested collections (Frame with children) - print("\n--- Test 5: Nested collections ---") - tests_total += 1 - - # Add child to frame - child_sprite = mcrfpy.Sprite(10, 10) - frame.children.append(child_sprite) - - # Check frame's children collection - children_repr = repr(frame.children) - print(f"Frame children repr: {children_repr}") - - if "1 Sprite" in children_repr: - print("✓ PASS: Nested collection shows correct type") - tests_passed += 1 - else: - print("✗ FAIL: Nested collection type incorrect") - - # Test 6: Collection remains valid after modifications - print("\n--- Test 6: Collection after modifications ---") - tests_total += 1 - - # Remove an item - scene_ui.remove(0) # Remove first frame - - repr_str = repr(scene_ui) - print(f"After removal repr: {repr_str}") - - if "2 Frames" in repr_str and "5 objects:" in repr_str: - print("✓ PASS: Collection repr updated correctly after removal") - tests_passed += 1 - else: - print("✗ FAIL: Collection repr not updated correctly") - - # Summary - print(f"\n=== SUMMARY ===") - print(f"Tests passed: {tests_passed}/{tests_total}") - - if tests_passed == tests_total: - print("\nIssue #95 FIXED: UICollection __repr__ now shows correct types!") - else: - print("\nIssue #95: Some tests failed") - - return tests_passed == tests_total - -def run_test(runtime): - """Timer callback to run the test""" - try: - success = test_uicollection_repr() - print("\nOverall result: " + ("PASS" if success else "FAIL")) - except Exception as e: - print(f"\nTest error: {e}") - import traceback - traceback.print_exc() - print("\nOverall result: FAIL") - - sys.exit(0) - -# Set up the test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test to run after game loop starts -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/issue_96_uicollection_extend_test.py b/tests/issue_96_uicollection_extend_test.py deleted file mode 100644 index 633ba78..0000000 --- a/tests/issue_96_uicollection_extend_test.py +++ /dev/null @@ -1,205 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #96: Add extend() method to UICollection - -This test verifies that UICollection now has an extend() method similar to -UIEntityCollection.extend(). -""" - -import mcrfpy -import sys - -def test_uicollection_extend(): - """Test UICollection extend method""" - print("=== Testing UICollection extend() Method (Issue #96) ===\n") - - tests_passed = 0 - tests_total = 0 - - # Get scene UI collection - scene_ui = mcrfpy.sceneUI("test") - - # Test 1: Basic extend with list - print("--- Test 1: Extend with list ---") - tests_total += 1 - try: - # Create a list of UI elements - elements = [ - mcrfpy.Frame(10, 10, 100, 100), - mcrfpy.Caption((150, 50), "Test1", mcrfpy.Font("assets/JetbrainsMono.ttf")), - mcrfpy.Sprite(200, 100) - ] - - # Extend the collection - scene_ui.extend(elements) - - if len(scene_ui) == 3: - print("✓ PASS: Extended collection with 3 elements") - tests_passed += 1 - else: - print(f"✗ FAIL: Expected 3 elements, got {len(scene_ui)}") - except Exception as e: - print(f"✗ FAIL: Error extending with list: {e}") - - # Test 2: Extend with tuple - print("\n--- Test 2: Extend with tuple ---") - tests_total += 1 - try: - # Create a tuple of UI elements - more_elements = ( - mcrfpy.Grid(10, 10), - mcrfpy.Frame(300, 10, 100, 100) - ) - - # Extend the collection - scene_ui.extend(more_elements) - - if len(scene_ui) == 5: - print("✓ PASS: Extended collection with tuple (now 5 elements)") - tests_passed += 1 - else: - print(f"✗ FAIL: Expected 5 elements, got {len(scene_ui)}") - except Exception as e: - print(f"✗ FAIL: Error extending with tuple: {e}") - - # Test 3: Extend with generator - print("\n--- Test 3: Extend with generator ---") - tests_total += 1 - try: - # Create a generator of UI elements - def create_sprites(): - for i in range(3): - yield mcrfpy.Sprite(50 + i*50, 200) - - # Extend with generator - scene_ui.extend(create_sprites()) - - if len(scene_ui) == 8: - print("✓ PASS: Extended collection with generator (now 8 elements)") - tests_passed += 1 - else: - print(f"✗ FAIL: Expected 8 elements, got {len(scene_ui)}") - except Exception as e: - print(f"✗ FAIL: Error extending with generator: {e}") - - # Test 4: Error handling - non-iterable - print("\n--- Test 4: Error handling - non-iterable ---") - tests_total += 1 - try: - scene_ui.extend(42) # Not iterable - print("✗ FAIL: Should have raised TypeError for non-iterable") - except TypeError as e: - print(f"✓ PASS: Correctly raised TypeError: {e}") - tests_passed += 1 - except Exception as e: - print(f"✗ FAIL: Wrong exception type: {e}") - - # Test 5: Error handling - wrong element type - print("\n--- Test 5: Error handling - wrong element type ---") - tests_total += 1 - try: - scene_ui.extend([1, 2, 3]) # Wrong types - print("✗ FAIL: Should have raised TypeError for non-UIDrawable elements") - except TypeError as e: - print(f"✓ PASS: Correctly raised TypeError: {e}") - tests_passed += 1 - except Exception as e: - print(f"✗ FAIL: Wrong exception type: {e}") - - # Test 6: Extend empty iterable - print("\n--- Test 6: Extend with empty list ---") - tests_total += 1 - try: - initial_len = len(scene_ui) - scene_ui.extend([]) # Empty list - - if len(scene_ui) == initial_len: - print("✓ PASS: Extending with empty list works correctly") - tests_passed += 1 - else: - print(f"✗ FAIL: Length changed from {initial_len} to {len(scene_ui)}") - except Exception as e: - print(f"✗ FAIL: Error extending with empty list: {e}") - - # Test 7: Z-index ordering - print("\n--- Test 7: Z-index ordering ---") - tests_total += 1 - try: - # Clear and add fresh elements - while len(scene_ui) > 0: - scene_ui.remove(0) - - # Add some initial elements - frame1 = mcrfpy.Frame(0, 0, 50, 50) - scene_ui.append(frame1) - - # Extend with more elements - new_elements = [ - mcrfpy.Frame(60, 0, 50, 50), - mcrfpy.Caption((120, 25), "Test", mcrfpy.Font("assets/JetbrainsMono.ttf")) - ] - scene_ui.extend(new_elements) - - # Check z-indices are properly assigned - z_indices = [scene_ui[i].z_index for i in range(3)] - - # Z-indices should be increasing - if z_indices[0] < z_indices[1] < z_indices[2]: - print(f"✓ PASS: Z-indices properly ordered: {z_indices}") - tests_passed += 1 - else: - print(f"✗ FAIL: Z-indices not properly ordered: {z_indices}") - except Exception as e: - print(f"✗ FAIL: Error checking z-indices: {e}") - - # Test 8: Extend with another UICollection - print("\n--- Test 8: Extend with another UICollection ---") - tests_total += 1 - try: - # Create a Frame with children - frame_with_children = mcrfpy.Frame(200, 200, 100, 100) - frame_with_children.children.append(mcrfpy.Sprite(10, 10)) - frame_with_children.children.append(mcrfpy.Caption((10, 50), "Child", mcrfpy.Font("assets/JetbrainsMono.ttf"))) - - # Try to extend scene_ui with the frame's children collection - initial_len = len(scene_ui) - scene_ui.extend(frame_with_children.children) - - if len(scene_ui) == initial_len + 2: - print("✓ PASS: Extended with another UICollection") - tests_passed += 1 - else: - print(f"✗ FAIL: Expected {initial_len + 2} elements, got {len(scene_ui)}") - except Exception as e: - print(f"✗ FAIL: Error extending with UICollection: {e}") - - # Summary - print(f"\n=== SUMMARY ===") - print(f"Tests passed: {tests_passed}/{tests_total}") - - if tests_passed == tests_total: - print("\nIssue #96 FIXED: UICollection.extend() implemented successfully!") - else: - print("\nIssue #96: Some tests failed") - - return tests_passed == tests_total - -def run_test(runtime): - """Timer callback to run the test""" - try: - success = test_uicollection_extend() - print("\nOverall result: " + ("PASS" if success else "FAIL")) - except Exception as e: - print(f"\nTest error: {e}") - import traceback - traceback.print_exc() - print("\nOverall result: FAIL") - - sys.exit(0) - -# Set up the test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test to run after game loop starts -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/issue_99_texture_font_properties_test.py b/tests/issue_99_texture_font_properties_test.py deleted file mode 100644 index 1ee5277..0000000 --- a/tests/issue_99_texture_font_properties_test.py +++ /dev/null @@ -1,224 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #99: Expose Texture and Font properties - -This test verifies that Texture and Font objects now expose their properties -as read-only attributes. -""" - -import mcrfpy -import sys - -def test_texture_properties(): - """Test Texture properties""" - print("=== Testing Texture Properties ===") - - tests_passed = 0 - tests_total = 0 - - # Create a texture - texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) - - # Test 1: sprite_width property - tests_total += 1 - try: - width = texture.sprite_width - if width == 16: - print(f"✓ PASS: sprite_width = {width}") - tests_passed += 1 - else: - print(f"✗ FAIL: sprite_width = {width}, expected 16") - except AttributeError as e: - print(f"✗ FAIL: sprite_width not accessible: {e}") - - # Test 2: sprite_height property - tests_total += 1 - try: - height = texture.sprite_height - if height == 16: - print(f"✓ PASS: sprite_height = {height}") - tests_passed += 1 - else: - print(f"✗ FAIL: sprite_height = {height}, expected 16") - except AttributeError as e: - print(f"✗ FAIL: sprite_height not accessible: {e}") - - # Test 3: sheet_width property - tests_total += 1 - try: - sheet_w = texture.sheet_width - if isinstance(sheet_w, int) and sheet_w > 0: - print(f"✓ PASS: sheet_width = {sheet_w}") - tests_passed += 1 - else: - print(f"✗ FAIL: sheet_width invalid: {sheet_w}") - except AttributeError as e: - print(f"✗ FAIL: sheet_width not accessible: {e}") - - # Test 4: sheet_height property - tests_total += 1 - try: - sheet_h = texture.sheet_height - if isinstance(sheet_h, int) and sheet_h > 0: - print(f"✓ PASS: sheet_height = {sheet_h}") - tests_passed += 1 - else: - print(f"✗ FAIL: sheet_height invalid: {sheet_h}") - except AttributeError as e: - print(f"✗ FAIL: sheet_height not accessible: {e}") - - # Test 5: sprite_count property - tests_total += 1 - try: - count = texture.sprite_count - expected = texture.sheet_width * texture.sheet_height - if count == expected: - print(f"✓ PASS: sprite_count = {count} (sheet_width * sheet_height)") - tests_passed += 1 - else: - print(f"✗ FAIL: sprite_count = {count}, expected {expected}") - except AttributeError as e: - print(f"✗ FAIL: sprite_count not accessible: {e}") - - # Test 6: source property - tests_total += 1 - try: - source = texture.source - if "kenney_tinydungeon.png" in source: - print(f"✓ PASS: source = '{source}'") - tests_passed += 1 - else: - print(f"✗ FAIL: source unexpected: '{source}'") - except AttributeError as e: - print(f"✗ FAIL: source not accessible: {e}") - - # Test 7: Properties are read-only - tests_total += 1 - try: - texture.sprite_width = 32 # Should fail - print("✗ FAIL: sprite_width should be read-only") - except AttributeError as e: - print(f"✓ PASS: sprite_width is read-only: {e}") - tests_passed += 1 - - return tests_passed, tests_total - -def test_font_properties(): - """Test Font properties""" - print("\n=== Testing Font Properties ===") - - tests_passed = 0 - tests_total = 0 - - # Create a font - font = mcrfpy.Font("assets/JetbrainsMono.ttf") - - # Test 1: family property - tests_total += 1 - try: - family = font.family - if isinstance(family, str) and len(family) > 0: - print(f"✓ PASS: family = '{family}'") - tests_passed += 1 - else: - print(f"✗ FAIL: family invalid: '{family}'") - except AttributeError as e: - print(f"✗ FAIL: family not accessible: {e}") - - # Test 2: source property - tests_total += 1 - try: - source = font.source - if "JetbrainsMono.ttf" in source: - print(f"✓ PASS: source = '{source}'") - tests_passed += 1 - else: - print(f"✗ FAIL: source unexpected: '{source}'") - except AttributeError as e: - print(f"✗ FAIL: source not accessible: {e}") - - # Test 3: Properties are read-only - tests_total += 1 - try: - font.family = "Arial" # Should fail - print("✗ FAIL: family should be read-only") - except AttributeError as e: - print(f"✓ PASS: family is read-only: {e}") - tests_passed += 1 - - return tests_passed, tests_total - -def test_property_introspection(): - """Test that properties appear in dir()""" - print("\n=== Testing Property Introspection ===") - - tests_passed = 0 - tests_total = 0 - - # Test Texture properties in dir() - tests_total += 1 - texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) - texture_props = dir(texture) - expected_texture_props = ['sprite_width', 'sprite_height', 'sheet_width', 'sheet_height', 'sprite_count', 'source'] - - missing = [p for p in expected_texture_props if p not in texture_props] - if not missing: - print("✓ PASS: All Texture properties appear in dir()") - tests_passed += 1 - else: - print(f"✗ FAIL: Missing Texture properties in dir(): {missing}") - - # Test Font properties in dir() - tests_total += 1 - font = mcrfpy.Font("assets/JetbrainsMono.ttf") - font_props = dir(font) - expected_font_props = ['family', 'source'] - - missing = [p for p in expected_font_props if p not in font_props] - if not missing: - print("✓ PASS: All Font properties appear in dir()") - tests_passed += 1 - else: - print(f"✗ FAIL: Missing Font properties in dir(): {missing}") - - return tests_passed, tests_total - -def run_test(runtime): - """Timer callback to run the test""" - try: - print("=== Testing Texture and Font Properties (Issue #99) ===\n") - - texture_passed, texture_total = test_texture_properties() - font_passed, font_total = test_font_properties() - intro_passed, intro_total = test_property_introspection() - - total_passed = texture_passed + font_passed + intro_passed - total_tests = texture_total + font_total + intro_total - - print(f"\n=== SUMMARY ===") - print(f"Texture tests: {texture_passed}/{texture_total}") - print(f"Font tests: {font_passed}/{font_total}") - print(f"Introspection tests: {intro_passed}/{intro_total}") - print(f"Total tests passed: {total_passed}/{total_tests}") - - if total_passed == total_tests: - print("\nIssue #99 FIXED: Texture and Font properties exposed successfully!") - print("\nOverall result: PASS") - else: - print("\nIssue #99: Some tests failed") - print("\nOverall result: FAIL") - - except Exception as e: - print(f"\nTest error: {e}") - import traceback - traceback.print_exc() - print("\nOverall result: FAIL") - - sys.exit(0) - -# Set up the test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test to run after game loop starts -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/issue_9_minimal_test.py b/tests/issue_9_minimal_test.py deleted file mode 100644 index 09eb9c6..0000000 --- a/tests/issue_9_minimal_test.py +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env python3 -""" -Minimal test for Issue #9: RenderTexture resize -""" - -import mcrfpy -from mcrfpy import automation -import sys - -def run_test(runtime): - """Test RenderTexture resizing""" - print("Testing Issue #9: RenderTexture resize (minimal)") - - try: - # Create a grid - print("Creating grid...") - grid = mcrfpy.Grid(30, 30) - grid.x = 10 - grid.y = 10 - grid.w = 300 - grid.h = 300 - - # Add to scene - scene_ui = mcrfpy.sceneUI("test") - scene_ui.append(grid) - - # Test accessing grid points - print("Testing grid.at()...") - point = grid.at(5, 5) - print(f"Got grid point: {point}") - - # Test color creation - print("Testing Color creation...") - red = mcrfpy.Color(255, 0, 0, 255) - print(f"Created color: {red}") - - # Set color - print("Setting grid point color...") - point.color = red - - print("Taking screenshot before resize...") - automation.screenshot("/tmp/issue_9_minimal_before.png") - - # Resize grid - print("Resizing grid to 2500x2500...") - grid.w = 2500 - grid.h = 2500 - - print("Taking screenshot after resize...") - automation.screenshot("/tmp/issue_9_minimal_after.png") - - print("\nTest complete - check screenshots") - print("If RenderTexture is recreated properly, grid should render correctly at large size") - - except Exception as e: - print(f"Error: {e}") - import traceback - traceback.print_exc() - - sys.exit(0) - -# Create and set scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/issue_9_rendertexture_resize_test.py b/tests/issue_9_rendertexture_resize_test.py deleted file mode 100644 index 8d643b5..0000000 --- a/tests/issue_9_rendertexture_resize_test.py +++ /dev/null @@ -1,229 +0,0 @@ -#!/usr/bin/env python3 -""" -Comprehensive test for Issue #9: Recreate RenderTexture when UIGrid is resized - -This test demonstrates that UIGrid has a hardcoded RenderTexture size of 1920x1080, -which causes rendering issues when the grid is resized beyond these dimensions. - -The bug: UIGrid::render() creates a RenderTexture with fixed size (1920x1080) once, -but never recreates it when the grid is resized, causing clipping and rendering artifacts. -""" - -import mcrfpy -from mcrfpy import automation -import sys -import os - -def create_checkerboard_pattern(grid, grid_width, grid_height, cell_size=2): - """Create a checkerboard pattern on the grid for visibility""" - for x in range(grid_width): - for y in range(grid_height): - if (x // cell_size + y // cell_size) % 2 == 0: - grid.at(x, y).color = mcrfpy.Color(255, 255, 255, 255) # White - else: - grid.at(x, y).color = mcrfpy.Color(100, 100, 100, 255) # Gray - -def add_border_markers(grid, grid_width, grid_height): - """Add colored markers at the borders to test rendering limits""" - # Red border on top - for x in range(grid_width): - grid.at(x, 0).color = mcrfpy.Color(255, 0, 0, 255) - - # Green border on right - for y in range(grid_height): - grid.at(grid_width-1, y).color = mcrfpy.Color(0, 255, 0, 255) - - # Blue border on bottom - for x in range(grid_width): - grid.at(x, grid_height-1).color = mcrfpy.Color(0, 0, 255, 255) - - # Yellow border on left - for y in range(grid_height): - grid.at(0, y).color = mcrfpy.Color(255, 255, 0, 255) - -def test_rendertexture_resize(): - """Test RenderTexture behavior with various grid sizes""" - print("=== Testing UIGrid RenderTexture Resize (Issue #9) ===\n") - - scene_ui = mcrfpy.sceneUI("test") - - # Test 1: Small grid (should work fine) - print("--- Test 1: Small Grid (400x300) ---") - grid1 = mcrfpy.Grid(20, 15) # 20x15 tiles - grid1.x = 10 - grid1.y = 10 - grid1.w = 400 - grid1.h = 300 - scene_ui.append(grid1) - - create_checkerboard_pattern(grid1, 20, 15) - add_border_markers(grid1, 20, 15) - - automation.screenshot("/tmp/issue_9_small_grid.png") - print("✓ Small grid created and rendered") - - # Test 2: Medium grid at 1920x1080 limit - print("\n--- Test 2: Medium Grid at 1920x1080 Limit ---") - grid2 = mcrfpy.Grid(64, 36) # 64x36 tiles at 30px each = 1920x1080 - grid2.x = 10 - grid2.y = 320 - grid2.w = 1920 - grid2.h = 1080 - scene_ui.append(grid2) - - create_checkerboard_pattern(grid2, 64, 36, 4) - add_border_markers(grid2, 64, 36) - - automation.screenshot("/tmp/issue_9_limit_grid.png") - print("✓ Grid at RenderTexture limit created") - - # Test 3: Resize grid1 beyond limits - print("\n--- Test 3: Resizing Small Grid Beyond 1920x1080 ---") - print("Original size: 400x300") - grid1.w = 2400 - grid1.h = 1400 - print(f"Resized to: {grid1.w}x{grid1.h}") - - # The content should still be visible but may be clipped - automation.screenshot("/tmp/issue_9_resized_beyond_limit.png") - print("✗ EXPECTED ISSUE: Grid resized beyond RenderTexture limits") - print(" Content beyond 1920x1080 will be clipped!") - - # Test 4: Create large grid from start - print("\n--- Test 4: Large Grid from Start (2400x1400) ---") - # Clear previous grids - while len(scene_ui) > 0: - scene_ui.remove(0) - - grid3 = mcrfpy.Grid(80, 50) # Large tile count - grid3.x = 10 - grid3.y = 10 - grid3.w = 2400 - grid3.h = 1400 - scene_ui.append(grid3) - - create_checkerboard_pattern(grid3, 80, 50, 5) - add_border_markers(grid3, 80, 50) - - # Add markers at specific positions to test rendering - # Mark the center - center_x, center_y = 40, 25 - for dx in range(-2, 3): - for dy in range(-2, 3): - grid3.at(center_x + dx, center_y + dy).color = mcrfpy.Color(255, 0, 255, 255) # Magenta - - # Mark position at 1920 pixel boundary (64 tiles * 30 pixels/tile = 1920) - if 64 < 80: # Only if within grid bounds - for y in range(min(50, 10)): - grid3.at(64, y).color = mcrfpy.Color(255, 128, 0, 255) # Orange - - automation.screenshot("/tmp/issue_9_large_grid.png") - print("✗ EXPECTED ISSUE: Large grid created") - print(" Content beyond 1920x1080 will not render!") - print(" Look for missing orange line at x=1920 boundary") - - # Test 5: Dynamic resize test - print("\n--- Test 5: Dynamic Resize Test ---") - scene_ui.remove(0) - - grid4 = mcrfpy.Grid(100, 100) - grid4.x = 10 - grid4.y = 10 - scene_ui.append(grid4) - - sizes = [(500, 500), (1000, 1000), (1500, 1500), (2000, 2000), (2500, 2500)] - - for i, (w, h) in enumerate(sizes): - grid4.w = w - grid4.h = h - - # Add pattern at current size - visible_tiles_x = min(100, w // 30) - visible_tiles_y = min(100, h // 30) - - # Clear and create new pattern - for x in range(visible_tiles_x): - for y in range(visible_tiles_y): - if x == visible_tiles_x - 1 or y == visible_tiles_y - 1: - # Edge markers - grid4.at(x, y).color = mcrfpy.Color(255, 255, 0, 255) - elif (x + y) % 10 == 0: - # Diagonal lines - grid4.at(x, y).color = mcrfpy.Color(0, 255, 255, 255) - - automation.screenshot(f"/tmp/issue_9_resize_{w}x{h}.png") - - if w > 1920 or h > 1080: - print(f"✗ Size {w}x{h}: Content clipped at 1920x1080") - else: - print(f"✓ Size {w}x{h}: Rendered correctly") - - # Test 6: Verify exact clipping boundary - print("\n--- Test 6: Exact Clipping Boundary Test ---") - scene_ui.remove(0) - - grid5 = mcrfpy.Grid(70, 40) - grid5.x = 0 - grid5.y = 0 - grid5.w = 2100 # 70 * 30 = 2100 pixels - grid5.h = 1200 # 40 * 30 = 1200 pixels - scene_ui.append(grid5) - - # Create a pattern that shows the boundary clearly - for x in range(70): - for y in range(40): - pixel_x = x * 30 - pixel_y = y * 30 - - if pixel_x == 1920 - 30: # Last tile before boundary - grid5.at(x, y).color = mcrfpy.Color(255, 0, 0, 255) # Red - elif pixel_x == 1920: # First tile after boundary - grid5.at(x, y).color = mcrfpy.Color(0, 255, 0, 255) # Green - elif pixel_y == 1080 - 30: # Last row before boundary - grid5.at(x, y).color = mcrfpy.Color(0, 0, 255, 255) # Blue - elif pixel_y == 1080: # First row after boundary - grid5.at(x, y).color = mcrfpy.Color(255, 255, 0, 255) # Yellow - else: - # Normal checkerboard - if (x + y) % 2 == 0: - grid5.at(x, y).color = mcrfpy.Color(200, 200, 200, 255) - - automation.screenshot("/tmp/issue_9_boundary_test.png") - print("Screenshot saved showing clipping boundary") - print("- Red tiles: Last visible column (x=1890-1919)") - print("- Green tiles: First clipped column (x=1920+)") - print("- Blue tiles: Last visible row (y=1050-1079)") - print("- Yellow tiles: First clipped row (y=1080+)") - - # Summary - print("\n=== SUMMARY ===") - print("Issue #9: UIGrid uses a hardcoded RenderTexture size of 1920x1080") - print("Problems demonstrated:") - print("1. Grids larger than 1920x1080 are clipped") - print("2. Resizing grids doesn't recreate the RenderTexture") - print("3. Content beyond the boundary is not rendered") - print("\nThe fix should:") - print("1. Recreate RenderTexture when grid size changes") - print("2. Use the actual grid dimensions instead of hardcoded values") - print("3. Consider memory limits for very large grids") - - print(f"\nScreenshots saved to /tmp/issue_9_*.png") - -def run_test(runtime): - """Timer callback to run the test""" - try: - test_rendertexture_resize() - print("\nTest complete - check screenshots for visual verification") - except Exception as e: - print(f"\nTest error: {e}") - import traceback - traceback.print_exc() - - sys.exit(0) - -# Set up the test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test to run after game loop starts -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/issue_9_simple_test.py b/tests/issue_9_simple_test.py deleted file mode 100644 index 2db3806..0000000 --- a/tests/issue_9_simple_test.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple test for Issue #9: RenderTexture resize -""" - -import mcrfpy -from mcrfpy import automation -import sys - -def run_test(runtime): - """Test RenderTexture resizing""" - print("Testing Issue #9: RenderTexture resize") - - # Create a scene - scene_ui = mcrfpy.sceneUI("test") - - # Create a small grid - print("Creating 50x50 grid with initial size 500x500") - grid = mcrfpy.Grid(50, 50) - grid.x = 10 - grid.y = 10 - grid.w = 500 - grid.h = 500 - scene_ui.append(grid) - - # Color some tiles to make it visible - print("Coloring tiles...") - for i in range(50): - # Diagonal line - grid.at(i, i).color = mcrfpy.Color(255, 0, 0, 255) - # Borders - grid.at(i, 0).color = mcrfpy.Color(0, 255, 0, 255) - grid.at(0, i).color = mcrfpy.Color(0, 0, 255, 255) - grid.at(i, 49).color = mcrfpy.Color(255, 255, 0, 255) - grid.at(49, i).color = mcrfpy.Color(255, 0, 255, 255) - - # Take initial screenshot - automation.screenshot("/tmp/issue_9_before_resize.png") - print("Screenshot saved: /tmp/issue_9_before_resize.png") - - # Resize to larger than 1920x1080 - print("\nResizing grid to 2500x2500...") - grid.w = 2500 - grid.h = 2500 - - # Take screenshot after resize - automation.screenshot("/tmp/issue_9_after_resize.png") - print("Screenshot saved: /tmp/issue_9_after_resize.png") - - # Test individual dimension changes - print("\nTesting individual dimension changes...") - grid.w = 3000 - automation.screenshot("/tmp/issue_9_width_3000.png") - print("Width set to 3000, screenshot: /tmp/issue_9_width_3000.png") - - grid.h = 3000 - automation.screenshot("/tmp/issue_9_both_3000.png") - print("Height set to 3000, screenshot: /tmp/issue_9_both_3000.png") - - print("\nIf the RenderTexture is properly recreated, all colored tiles") - print("should be visible in all screenshots, not clipped at 1920x1080.") - - print("\nTest complete - PASS") - sys.exit(0) - -# Create and set scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/issue_9_test.py b/tests/issue_9_test.py deleted file mode 100644 index 39a1f22..0000000 --- a/tests/issue_9_test.py +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env python3 -""" -Test for Issue #9: Recreate RenderTexture when UIGrid is resized - -This test checks if resizing a UIGrid properly recreates its RenderTexture. -""" - -import mcrfpy -from mcrfpy import automation -import sys - -def run_test(runtime): - """Test that UIGrid properly handles resizing""" - try: - # Create a grid with initial size - grid = mcrfpy.Grid(20, 20) - grid.x = 50 - grid.y = 50 - grid.w = 200 - grid.h = 200 - - # Add grid to scene - scene_ui = mcrfpy.sceneUI("test") - scene_ui.append(grid) - - # Take initial screenshot - automation.screenshot("/tmp/grid_initial.png") - print("Initial grid created at 200x200") - - # Add some visible content to the grid - for x in range(5): - for y in range(5): - grid.at(x, y).color = mcrfpy.Color(255, 0, 0, 255) # Red squares - - automation.screenshot("/tmp/grid_with_content.png") - print("Added red squares to grid") - - # Test 1: Resize the grid smaller - print("\nTest 1: Resizing grid to 100x100...") - grid.w = 100 - grid.h = 100 - - automation.screenshot("/tmp/grid_resized_small.png") - - # The grid should still render correctly - print("✓ Test 1: Grid resized to 100x100") - - # Test 2: Resize the grid larger than initial - print("\nTest 2: Resizing grid to 400x400...") - grid.w = 400 - grid.h = 400 - - automation.screenshot("/tmp/grid_resized_large.png") - - # Add content at the edges to test if render texture is big enough - for x in range(15, 20): - for y in range(15, 20): - grid.at(x, y).color = mcrfpy.Color(0, 255, 0, 255) # Green squares - - automation.screenshot("/tmp/grid_resized_with_edge_content.png") - print("✓ Test 2: Grid resized to 400x400 with edge content") - - # Test 3: Resize beyond the hardcoded 1920x1080 limit - print("\nTest 3: Resizing grid beyond 1920x1080...") - grid.w = 2000 - grid.h = 1200 - - automation.screenshot("/tmp/grid_resized_huge.png") - - # This should fail with the current implementation - print("✗ Test 3: This likely shows rendering errors due to fixed RenderTexture size") - print("This is the bug described in Issue #9!") - - print("\nScreenshots saved to /tmp/grid_*.png") - print("Check grid_resized_huge.png for rendering artifacts") - - except Exception as e: - print(f"Test error: {e}") - import traceback - traceback.print_exc() - - sys.exit(0) - -# Set up the test scene -mcrfpy.createScene("test") -mcrfpy.setScene("test") - -# Schedule test to run after game loop starts -mcrfpy.setTimer("test", run_test, 100) \ No newline at end of file diff --git a/tests/keypress_scene_validation_test.py b/tests/keypress_scene_validation_test.py deleted file mode 100644 index 4bd2982..0000000 --- a/tests/keypress_scene_validation_test.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/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) \ No newline at end of file diff --git a/tests/run_issue_tests.py b/tests/run_issue_tests.py deleted file mode 100755 index b8ea601..0000000 --- a/tests/run_issue_tests.py +++ /dev/null @@ -1,174 +0,0 @@ -#!/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()) \ No newline at end of file diff --git a/tests/screenshot_transparency_fix_test.py b/tests/screenshot_transparency_fix_test.py deleted file mode 100644 index 7da8878..0000000 --- a/tests/screenshot_transparency_fix_test.py +++ /dev/null @@ -1,77 +0,0 @@ -#!/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() \ No newline at end of file diff --git a/tests/simple_screenshot_test.py b/tests/simple_screenshot_test.py deleted file mode 100644 index 42815a4..0000000 --- a/tests/simple_screenshot_test.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/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...") \ No newline at end of file diff --git a/tests/simple_timer_screenshot_test.py b/tests/simple_timer_screenshot_test.py deleted file mode 100644 index 5a5c9ac..0000000 --- a/tests/simple_timer_screenshot_test.py +++ /dev/null @@ -1,40 +0,0 @@ -#!/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...") \ No newline at end of file diff --git a/tests/test_stdin_theory.py b/tests/test_stdin_theory.py deleted file mode 100644 index 88d1d28..0000000 --- a/tests/test_stdin_theory.py +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env python3 -"""Test if closing stdin prevents the >>> prompt""" -import mcrfpy -import sys -import os - -print("=== Testing stdin theory ===") -print(f"stdin.isatty(): {sys.stdin.isatty()}") -print(f"stdin fileno: {sys.stdin.fileno()}") - -# Set up a basic scene -mcrfpy.createScene("stdin_test") -mcrfpy.setScene("stdin_test") - -# Try to prevent interactive mode by closing stdin -print("\nAttempting to prevent interactive mode...") -try: - # Method 1: Close stdin - sys.stdin.close() - print("Closed sys.stdin") -except: - print("Failed to close sys.stdin") - -try: - # Method 2: Redirect stdin to /dev/null - devnull = open(os.devnull, 'r') - os.dup2(devnull.fileno(), 0) - print("Redirected stdin to /dev/null") -except: - print("Failed to redirect stdin") - -print("\nScript complete. If >>> still appears, the issue is elsewhere.") \ No newline at end of file diff --git a/tests/trace_exec_behavior.py b/tests/trace_exec_behavior.py deleted file mode 100644 index a0685f4..0000000 --- a/tests/trace_exec_behavior.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/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 \ No newline at end of file diff --git a/tests/trace_interactive.py b/tests/trace_interactive.py deleted file mode 100644 index 714ae7c..0000000 --- a/tests/trace_interactive.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 -"""Trace interactive mode by monkey-patching""" -import sys -import mcrfpy - -# Monkey-patch to detect interactive mode -original_ps1 = None -if hasattr(sys, 'ps1'): - original_ps1 = sys.ps1 - -class PS1Detector: - def __repr__(self): - import traceback - print("\n!!! sys.ps1 accessed! Stack trace:") - traceback.print_stack() - return ">>> " - -# Set our detector -sys.ps1 = PS1Detector() - -print("Trace script loaded, ps1 detector installed") - -# Do nothing else - let the game run \ No newline at end of file diff --git a/tests/ui_Entity_issue73_test.py b/tests/ui_Entity_issue73_test.py deleted file mode 100644 index 7f2b3cd..0000000 --- a/tests/ui_Entity_issue73_test.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/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.") diff --git a/tests/ui_Frame_test.py b/tests/ui_Frame_test.py deleted file mode 100644 index 7798557..0000000 --- a/tests/ui_Frame_test.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/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() \ No newline at end of file diff --git a/tests/ui_Frame_test_detailed.py b/tests/ui_Frame_test_detailed.py deleted file mode 100644 index 695994f..0000000 --- a/tests/ui_Frame_test_detailed.py +++ /dev/null @@ -1,127 +0,0 @@ -#!/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() \ No newline at end of file diff --git a/tests/ui_Grid_none_texture_test.py b/tests/ui_Grid_none_texture_test.py deleted file mode 100644 index 38150ef..0000000 --- a/tests/ui_Grid_none_texture_test.py +++ /dev/null @@ -1,97 +0,0 @@ -#!/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...") \ No newline at end of file diff --git a/tests/ui_Grid_null_texture_test.py b/tests/ui_Grid_null_texture_test.py deleted file mode 100644 index fdac956..0000000 --- a/tests/ui_Grid_null_texture_test.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/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() \ No newline at end of file diff --git a/tests/ui_Grid_test.py b/tests/ui_Grid_test.py deleted file mode 100644 index ed81d61..0000000 --- a/tests/ui_Grid_test.py +++ /dev/null @@ -1,142 +0,0 @@ -#!/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) \ No newline at end of file diff --git a/tests/ui_Grid_test_no_grid.py b/tests/ui_Grid_test_no_grid.py deleted file mode 100644 index 836543e..0000000 --- a/tests/ui_Grid_test_no_grid.py +++ /dev/null @@ -1,28 +0,0 @@ -#!/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") \ No newline at end of file diff --git a/tests/ui_Grid_test_simple.py b/tests/ui_Grid_test_simple.py deleted file mode 100644 index d7897bc..0000000 --- a/tests/ui_Grid_test_simple.py +++ /dev/null @@ -1,58 +0,0 @@ -#!/usr/bin/env python3 -"""Simple test for mcrfpy.Grid""" -import mcrfpy - -print("Starting Grid 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 grid creation -try: - # Texture constructor: filename, sprite_width, sprite_height - # kenney_ice.png is 192x176, so 16x16 would give us 12x11 sprites - texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) - print("[INFO] Texture created successfully") -except Exception as e: - print(f"[FAIL] Texture creation failed: {e}") - exit(1) -grid = None - -try: - # Try with just 2 args - grid = mcrfpy.Grid(20, 15) # Just grid dimensions - print("[INFO] Grid created with 2 args") -except Exception as e: - print(f"[FAIL] 2 args failed: {e}") - -if not grid: - try: - # Try with 3 args - grid = mcrfpy.Grid(20, 15, texture) - print("[INFO] Grid created with 3 args") - except Exception as e: - print(f"[FAIL] 3 args failed: {e}") - -# If we got here, add to UI -try: - ui.append(grid) - print("[PASS] Grid created and added to UI successfully") -except Exception as e: - print(f"[FAIL] Failed to add Grid to UI: {e}") - exit(1) - -# Test grid properties -try: - print(f"Grid size: {grid.grid_size}") - print(f"Position: {grid.position}") - print(f"Size: {grid.size}") -except Exception as e: - print(f"[FAIL] Property access failed: {e}") - -print("Test complete!") \ No newline at end of file diff --git a/tests/ui_Sprite_issue19_test.py b/tests/ui_Sprite_issue19_test.py deleted file mode 100644 index 65539e9..0000000 --- a/tests/ui_Sprite_issue19_test.py +++ /dev/null @@ -1,69 +0,0 @@ -#!/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") \ No newline at end of file diff --git a/tests/ui_UICollection_issue69_test.py b/tests/ui_UICollection_issue69_test.py deleted file mode 100644 index 3299bcd..0000000 --- a/tests/ui_UICollection_issue69_test.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/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() \ No newline at end of file diff --git a/tests/validate_screenshot_test.py b/tests/validate_screenshot_test.py deleted file mode 100644 index e949eda..0000000 --- a/tests/validate_screenshot_test.py +++ /dev/null @@ -1,116 +0,0 @@ -#!/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() \ No newline at end of file diff --git a/tests/working_timer_test.py b/tests/working_timer_test.py deleted file mode 100644 index 4435014..0000000 --- a/tests/working_timer_test.py +++ /dev/null @@ -1,42 +0,0 @@ -#!/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...") \ No newline at end of file diff --git a/timer_success_1086.png b/timer_success_1086.png deleted file mode 100644 index a09f8d5..0000000 Binary files a/timer_success_1086.png and /dev/null differ diff --git a/validate_screenshot_basic_20250703_174532.png b/validate_screenshot_basic_20250703_174532.png deleted file mode 100644 index a61c929..0000000 Binary files a/validate_screenshot_basic_20250703_174532.png and /dev/null differ diff --git a/validate_screenshot_final_20250703_174532.png b/validate_screenshot_final_20250703_174532.png deleted file mode 100644 index a61c929..0000000 Binary files a/validate_screenshot_final_20250703_174532.png and /dev/null differ diff --git a/validate_screenshot_with_spaces 20250703_174532.png b/validate_screenshot_with_spaces 20250703_174532.png deleted file mode 100644 index a61c929..0000000 Binary files a/validate_screenshot_with_spaces 20250703_174532.png and /dev/null differ