Compare commits
54 commits
master
...
alpha_stre
| Author | SHA1 | Date | |
|---|---|---|---|
| 64ffe1f699 | |||
| 9be3161a24 | |||
| d13153ddb4 | |||
| 051a2ca951 | |||
| 7ee0a08662 | |||
| 1a3e308c77 | |||
| c1e02070f4 | |||
| 192d1ae1dd | |||
| 4e94d1d79e | |||
| 1e67541c29 | |||
| edb7967080 | |||
| 1e65d50c82 | |||
| e34d4f967e | |||
| d0e02d5b83 | |||
| 692ef0f6ad | |||
| 6d7fab5f31 | |||
| d51bed623a | |||
| 94a0282f9f | |||
| cf67c995f6 | |||
| 1d90cdab1d | |||
| e1c6c53157 | |||
| 5d24ba6a85 | |||
| c4b4f12758 | |||
| 419f7d716a | |||
| 7c87b5a092 | |||
| e2696e60df | |||
| 5a49cb7b6d | |||
| 93256b96c6 | |||
| 967ebcf478 | |||
| 5e4224a4f8 | |||
| ff7cf25806 | |||
| 4b2ad0ff18 | |||
| eaeef1a889 | |||
| f76a26c120 | |||
| 193294d3a7 | |||
| f23aa784f2 | |||
| 1c7195a748 | |||
| edfe3ba184 | |||
| 97067a104e | |||
| ee6550bf63 | |||
| cc9b5c8f88 | |||
| 27db9a4184 | |||
| 1aa35202e1 | |||
| b390a087bc | |||
| 0f518127ec | |||
| 75f75d250f | |||
| c48c91e5d7 | |||
| fe5976c425 | |||
| 61a05dd6ba | |||
| c0270c9b32 | |||
| da7180f5ed | |||
| f1b354e47d | |||
| a88ce0e259 | |||
| 5b6b0cc8ff |
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 31 KiB |
BIN
.archive/caption_opacity_25.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
.archive/caption_opacity_50.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
.archive/caption_visible.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
.archive/debug_immediate.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
.archive/debug_multi_0.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
.archive/debug_multi_1.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
.archive/debug_multi_2.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
99
.archive/entity_property_setters_test.py
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
#!/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)
|
||||
61
.archive/entity_setter_simple_test.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#!/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)
|
||||
BIN
.archive/grid_none_texture_test_197.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
105
.archive/issue27_entity_extend_test.py
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
#!/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)
|
||||
111
.archive/issue33_sprite_index_validation_test.py
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#!/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)
|
||||
101
.archive/issue73_entity_index_test.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
#!/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)
|
||||
77
.archive/issue73_simple_index_test.py
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#!/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)
|
||||
60
.archive/issue74_grid_xy_properties_test.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#!/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)
|
||||
BIN
.archive/issue78_fixed_1658.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
87
.archive/issue78_middle_click_fix_test.py
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
#!/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)
|
||||
BIN
.archive/screenshot_opaque_fix_20250703_174829.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
.archive/sequence_demo_screenshot.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
.archive/sequence_protocol_test.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
73
.archive/sprite_texture_setter_test.py
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
#!/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)
|
||||
BIN
.archive/timer_success_1086.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
.archive/validate_screenshot_basic_20250703_174532.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
.archive/validate_screenshot_final_20250703_174532.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
.archive/validate_screenshot_with_spaces 20250703_174532.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
|
|
@ -1,123 +0,0 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
build-essential cmake git \
|
||||
zlib1g-dev libx11-dev libxrandr-dev libxcursor-dev \
|
||||
libfreetype-dev libudev-dev libvorbis-dev libflac-dev \
|
||||
libgl-dev libopenal-dev
|
||||
|
||||
- name: Check for pre-built libraries
|
||||
run: |
|
||||
if [ ! -d "__lib" ]; then
|
||||
echo "::error::__lib/ directory not found. Pre-built libraries must be available on the runner."
|
||||
echo "See BUILD_FROM_SOURCE.md for instructions on building dependencies."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build (Release)
|
||||
run: make linux
|
||||
|
||||
- name: Run tests (Release)
|
||||
run: cd tests && python3 run_tests.py -v
|
||||
|
||||
debug-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
build-essential cmake git \
|
||||
zlib1g-dev libx11-dev libxrandr-dev libxcursor-dev \
|
||||
libfreetype-dev libudev-dev libvorbis-dev libflac-dev \
|
||||
libgl-dev libopenal-dev
|
||||
|
||||
- name: Check for debug libraries
|
||||
run: |
|
||||
if [ ! -d "__lib_debug" ]; then
|
||||
echo "::error::__lib_debug/ directory not found. Build debug Python first: tools/build_debug_python.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build and test (debug Python)
|
||||
run: make debug-test
|
||||
|
||||
asan-test:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
build-essential cmake git \
|
||||
zlib1g-dev libx11-dev libxrandr-dev libxcursor-dev \
|
||||
libfreetype-dev libudev-dev libvorbis-dev libflac-dev \
|
||||
libgl-dev libopenal-dev
|
||||
|
||||
- name: Check for debug libraries
|
||||
run: |
|
||||
if [ ! -d "__lib_debug" ]; then
|
||||
echo "::error::__lib_debug/ directory not found. Build debug Python first: tools/build_debug_python.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build and test (ASan + UBSan)
|
||||
run: make asan-test
|
||||
|
||||
valgrind-test:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
build-essential cmake git valgrind \
|
||||
zlib1g-dev libx11-dev libxrandr-dev libxcursor-dev \
|
||||
libfreetype-dev libudev-dev libvorbis-dev libflac-dev \
|
||||
libgl-dev libopenal-dev
|
||||
|
||||
- name: Check for debug libraries
|
||||
run: |
|
||||
if [ ! -d "__lib_debug" ]; then
|
||||
echo "::error::__lib_debug/ directory not found. Build debug Python first: tools/build_debug_python.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Build and test (Valgrind memcheck)
|
||||
run: make valgrind-test
|
||||
timeout-minutes: 30
|
||||
69
.gitignore
vendored
|
|
@ -7,83 +7,26 @@ PCbuild
|
|||
.vs
|
||||
obj
|
||||
build
|
||||
/lib
|
||||
lib
|
||||
obj
|
||||
__pycache__
|
||||
|
||||
# unimportant files that won't pass clean dir check
|
||||
build*
|
||||
docs
|
||||
.claude
|
||||
my_games
|
||||
|
||||
# images are produced by many tests
|
||||
*.png
|
||||
|
||||
# generated snippet preview screenshots (#381); consumed/committed by the doc-site repo
|
||||
snippet-shots/
|
||||
|
||||
# WASM stdlib for Emscripten build
|
||||
!wasm_stdlib/
|
||||
|
||||
.cache/
|
||||
7DRL2025 Release/
|
||||
CMakeFiles/
|
||||
Makefile
|
||||
*.md
|
||||
*.zip
|
||||
__lib/
|
||||
__lib_debug/
|
||||
__lib_windows/
|
||||
build-windows/
|
||||
build_windows/
|
||||
_oldscripts/
|
||||
|
||||
# Audit tooling virtualenv (tools/audit_pymethoddef.py)
|
||||
.venv-audit/
|
||||
assets/
|
||||
cellular_automata_fire/
|
||||
*.txt
|
||||
deps/
|
||||
fetch_issues_txt.py
|
||||
forest_fire_CA.py
|
||||
mcrogueface.github.io
|
||||
scripts/
|
||||
test_*
|
||||
|
||||
tcod_reference
|
||||
.archive
|
||||
.mcp.json
|
||||
dist/
|
||||
|
||||
# Keep important documentation and tests
|
||||
!CLAUDE.md
|
||||
!README.md
|
||||
!tests/
|
||||
|
||||
# Fuzzing build artifacts and runtime data (build-fuzz matched by build* above)
|
||||
tests/fuzz/corpora/
|
||||
tests/fuzz/crashes/
|
||||
|
||||
# >>> generated-docs (untrack via tools/untrack_generated_docs.sh) >>>
|
||||
# Rendered/generated documentation is regenerated by the pre-commit hook and
|
||||
# shipped inside release artifacts -- it no longer lives in git. The compact
|
||||
# api/manifest.json is the exception: it STAYS committed (refreshed by the hook).
|
||||
# Personal-notes docs (plans, triage, research) are also untracked here.
|
||||
# tools/untrack_generated_docs.sh reads exactly this block to migrate the
|
||||
# currently-tracked copies out of the index.
|
||||
docs/API_REFERENCE_DYNAMIC.md
|
||||
docs/api_reference_dynamic.html
|
||||
docs/mcrfpy.3
|
||||
stubs/mcrfpy.pyi
|
||||
docs/generated/
|
||||
docs/API_REFERENCE_COMPLETE.md
|
||||
docs/api_reference_complete.html
|
||||
docs/VISION.md
|
||||
docs/GAMES.md
|
||||
docs/plan-*.md
|
||||
docs/api-audit-*.md
|
||||
docs/ISSUE_TRIAGE_*.md
|
||||
docs/GRID_ENTITY_OVERHAUL_ROADMAP.md
|
||||
docs/sprint-*.md
|
||||
docs/EMSCRIPTEN_RESEARCH.md
|
||||
docs/WASM_TROUBLESHOOTING.md
|
||||
docs/MCROGUFACE_LITE_PICOCALC_RESEARCH.md
|
||||
docs/3D_SYSTEM_GUIDE.md
|
||||
docs/PROCEDURAL_GENERATION_SPEC.md
|
||||
# <<< generated-docs <<<
|
||||
|
|
|
|||
13
.gitmodules
vendored
|
|
@ -10,13 +10,6 @@
|
|||
[submodule "modules/SFML"]
|
||||
path = modules/SFML
|
||||
url = git@github.com:SFML/SFML.git
|
||||
[submodule "modules/libtcod-headless"]
|
||||
path = modules/libtcod-headless
|
||||
url = git@github.com:jmccardle/libtcod-headless.git
|
||||
branch = 2.2.1-headless
|
||||
[submodule "modules/RapidXML"]
|
||||
path = modules/RapidXML
|
||||
url = https://github.com/Fe-Bell/RapidXML
|
||||
[submodule "modules/json"]
|
||||
path = modules/json
|
||||
url = git@github.com:nlohmann/json.git
|
||||
[submodule "modules/libtcod"]
|
||||
path = modules/libtcod
|
||||
url = git@github.com:libtcod/libtcod.git
|
||||
|
|
|
|||
1093
ALPHA_STREAMLINE_WORKLOG.md
Normal file
|
|
@ -1,306 +0,0 @@
|
|||
# Building McRogueFace from Source
|
||||
|
||||
This document describes how to build McRogueFace from a fresh clone.
|
||||
|
||||
## Build Options
|
||||
|
||||
There are two ways to build McRogueFace:
|
||||
|
||||
1. **Quick Build** (recommended): Use pre-built dependency libraries from a `build_deps` archive
|
||||
2. **Full Build**: Compile all dependencies from submodules
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### System Dependencies
|
||||
|
||||
Install these packages before building:
|
||||
|
||||
```bash
|
||||
# Debian/Ubuntu
|
||||
sudo apt install \
|
||||
build-essential \
|
||||
cmake \
|
||||
git \
|
||||
zlib1g-dev \
|
||||
libx11-dev \
|
||||
libxrandr-dev \
|
||||
libxcursor-dev \
|
||||
libfreetype-dev \
|
||||
libudev-dev \
|
||||
libvorbis-dev \
|
||||
libflac-dev \
|
||||
libgl-dev \
|
||||
libopenal-dev
|
||||
```
|
||||
|
||||
**Note:** SDL is NOT required - McRogueFace uses libtcod-headless which has no SDL dependency.
|
||||
|
||||
---
|
||||
|
||||
## Option 1: Quick Build (Using Pre-built Dependencies)
|
||||
|
||||
If you have a `build_deps.tar.gz` or `build_deps.zip` archive:
|
||||
|
||||
```bash
|
||||
# Clone McRogueFace (no submodules needed)
|
||||
git clone <repository-url> McRogueFace
|
||||
cd McRogueFace
|
||||
|
||||
# Extract pre-built dependencies
|
||||
tar -xzf /path/to/build_deps.tar.gz
|
||||
# Or for zip: unzip /path/to/build_deps.zip
|
||||
|
||||
# Build McRogueFace
|
||||
mkdir -p build && cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
make -j$(nproc)
|
||||
|
||||
# Run
|
||||
./mcrogueface
|
||||
```
|
||||
|
||||
The `build_deps` archive contains:
|
||||
- `__lib/` - Pre-built shared libraries (Python, SFML, libtcod-headless)
|
||||
- `deps/` - Header symlinks for compilation
|
||||
|
||||
**Total build time: ~30 seconds**
|
||||
|
||||
---
|
||||
|
||||
## Option 2: Full Build (Compiling All Dependencies)
|
||||
|
||||
### 1. Clone with Submodules
|
||||
|
||||
```bash
|
||||
git clone --recursive <repository-url> McRogueFace
|
||||
cd McRogueFace
|
||||
```
|
||||
|
||||
If submodules weren't cloned:
|
||||
```bash
|
||||
git submodule update --init --recursive
|
||||
```
|
||||
|
||||
**Note:** imgui/imgui-sfml submodules may fail - this is fine, they're not used.
|
||||
|
||||
### 2. Create Dependency Symlinks
|
||||
|
||||
```bash
|
||||
cd deps
|
||||
ln -sf ../modules/cpython cpython
|
||||
ln -sf ../modules/libtcod-headless/src/libtcod libtcod
|
||||
ln -sf ../modules/cpython/Include Python
|
||||
ln -sf ../modules/SFML/include/SFML SFML
|
||||
cd ..
|
||||
```
|
||||
|
||||
### 3. Build libtcod-headless
|
||||
|
||||
libtcod-headless is our SDL-free fork with vendored dependencies:
|
||||
|
||||
```bash
|
||||
cd modules/libtcod-headless
|
||||
mkdir build && cd build
|
||||
|
||||
cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=ON
|
||||
|
||||
make -j$(nproc)
|
||||
cd ../../..
|
||||
```
|
||||
|
||||
That's it! No special flags needed - libtcod-headless defaults to:
|
||||
- `LIBTCOD_SDL3=disable` (no SDL dependency)
|
||||
- Vendored lodepng, utf8proc, stb
|
||||
|
||||
### 4. Build Python 3.12
|
||||
|
||||
```bash
|
||||
cd modules/cpython
|
||||
./configure --enable-shared
|
||||
make -j$(nproc)
|
||||
cd ../..
|
||||
```
|
||||
|
||||
### 5. Build SFML 2.6
|
||||
|
||||
```bash
|
||||
cd modules/SFML
|
||||
mkdir build && cd build
|
||||
|
||||
cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_SHARED_LIBS=ON
|
||||
|
||||
make -j$(nproc)
|
||||
cd ../../..
|
||||
```
|
||||
|
||||
### 6. Copy Libraries
|
||||
|
||||
```bash
|
||||
mkdir -p __lib
|
||||
|
||||
# Python
|
||||
cp modules/cpython/libpython3.12.so* __lib/
|
||||
|
||||
# SFML
|
||||
cp modules/SFML/build/lib/libsfml-*.so* __lib/
|
||||
|
||||
# libtcod-headless
|
||||
cp modules/libtcod-headless/build/bin/libtcod.so* __lib/
|
||||
|
||||
# Python standard library
|
||||
cp -r modules/cpython/Lib __lib/Python
|
||||
```
|
||||
|
||||
### 7. Build McRogueFace
|
||||
|
||||
```bash
|
||||
mkdir -p build && cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
make -j$(nproc)
|
||||
```
|
||||
|
||||
### 8. Run
|
||||
|
||||
```bash
|
||||
./mcrogueface
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Submodule Versions
|
||||
|
||||
| Submodule | Version | Notes |
|
||||
|-----------|---------|-------|
|
||||
| SFML | 2.6.1 | Graphics, audio, windowing |
|
||||
| cpython | 3.12.2 | Embedded Python interpreter |
|
||||
| libtcod-headless | 2.2.1 | SDL-free fork for FOV, pathfinding |
|
||||
|
||||
---
|
||||
|
||||
## Creating a build_deps Archive
|
||||
|
||||
To create a `build_deps` archive for distribution:
|
||||
|
||||
```bash
|
||||
cd McRogueFace
|
||||
|
||||
# Create archive directory
|
||||
mkdir -p build_deps_staging
|
||||
|
||||
# Copy libraries
|
||||
cp -r __lib build_deps_staging/
|
||||
|
||||
# Copy/create deps symlinks as actual directories with only needed headers
|
||||
mkdir -p build_deps_staging/deps
|
||||
cp -rL deps/libtcod build_deps_staging/deps/ # Follow symlink
|
||||
cp -rL deps/Python build_deps_staging/deps/
|
||||
cp -rL deps/SFML build_deps_staging/deps/
|
||||
cp -r deps/platform build_deps_staging/deps/
|
||||
|
||||
# Create archives
|
||||
cd build_deps_staging
|
||||
tar -czf ../build_deps.tar.gz __lib deps
|
||||
zip -r ../build_deps.zip __lib deps
|
||||
cd ..
|
||||
|
||||
# Cleanup
|
||||
rm -rf build_deps_staging
|
||||
```
|
||||
|
||||
The resulting archive can be distributed alongside releases for users who want to build McRogueFace without compiling dependencies.
|
||||
|
||||
**Archive contents:**
|
||||
```
|
||||
build_deps.tar.gz
|
||||
├── __lib/
|
||||
│ ├── libpython3.12.so*
|
||||
│ ├── libsfml-*.so*
|
||||
│ ├── libtcod.so*
|
||||
│ └── Python/ # Python standard library
|
||||
└── deps/
|
||||
├── libtcod/ # libtcod headers
|
||||
├── Python/ # Python headers
|
||||
├── SFML/ # SFML headers
|
||||
└── platform/ # Platform-specific configs
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verify the Build
|
||||
|
||||
```bash
|
||||
cd build
|
||||
|
||||
# Check version
|
||||
./mcrogueface --version
|
||||
|
||||
# Test headless mode
|
||||
./mcrogueface --headless -c "import mcrfpy; print('Success')"
|
||||
|
||||
# Verify no SDL dependencies
|
||||
ldd mcrogueface | grep -i sdl # Should output nothing
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### OpenAL not found
|
||||
```bash
|
||||
sudo apt install libopenal-dev
|
||||
```
|
||||
|
||||
### FreeType not found
|
||||
```bash
|
||||
sudo apt install libfreetype-dev
|
||||
```
|
||||
|
||||
### X11/Xrandr not found
|
||||
```bash
|
||||
sudo apt install libx11-dev libxrandr-dev
|
||||
```
|
||||
|
||||
### Python standard library missing
|
||||
Ensure `__lib/Python` contains the standard library:
|
||||
```bash
|
||||
ls __lib/Python/os.py # Should exist
|
||||
```
|
||||
|
||||
### libtcod symbols not found
|
||||
Ensure libtcod.so is in `__lib/` with correct version:
|
||||
```bash
|
||||
ls -la __lib/libtcod.so*
|
||||
# Should show libtcod.so -> libtcod.so.2 -> libtcod.so.2.2.1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Build Times (approximate)
|
||||
|
||||
On a typical 4-core system:
|
||||
|
||||
| Component | Time |
|
||||
|-----------|------|
|
||||
| libtcod-headless | ~30 seconds |
|
||||
| Python 3.12 | ~3-5 minutes |
|
||||
| SFML 2.6 | ~1 minute |
|
||||
| McRogueFace | ~30 seconds |
|
||||
| **Full build total** | **~5-7 minutes** |
|
||||
| **Quick build (pre-built deps)** | **~30 seconds** |
|
||||
|
||||
---
|
||||
|
||||
## Runtime Dependencies
|
||||
|
||||
The built executable requires these system libraries:
|
||||
- `libz.so.1` (zlib)
|
||||
- `libopenal.so.1` (OpenAL)
|
||||
- `libX11.so.6`, `libXrandr.so.2` (X11)
|
||||
- `libfreetype.so.6` (FreeType)
|
||||
- `libGL.so.1` (OpenGL)
|
||||
|
||||
All other dependencies (Python, SFML, libtcod) are bundled in `lib/`.
|
||||
594
CMakeLists.txt
|
|
@ -8,527 +8,49 @@ project(McRogueFace)
|
|||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||
|
||||
# Headless build option (no SFML, no graphics - for server/testing/Emscripten prep)
|
||||
option(MCRF_HEADLESS "Build without graphics dependencies (SFML, ImGui)" OFF)
|
||||
|
||||
# SDL2 backend option (SDL2 + OpenGL ES 2 - for Emscripten/WebGL, Android, cross-platform)
|
||||
option(MCRF_SDL2 "Build with SDL2+OpenGL ES 2 backend instead of SFML" OFF)
|
||||
|
||||
# Playground mode - minimal scripts for web playground (REPL-focused)
|
||||
option(MCRF_PLAYGROUND "Build with minimal playground scripts instead of full game" OFF)
|
||||
|
||||
# Demo mode - self-contained demo game for web showcase
|
||||
option(MCRF_DEMO "Build with demo scripts (web showcase)" OFF)
|
||||
|
||||
# Game shell mode - fullscreen canvas, no REPL chrome (for itch.io / standalone web games)
|
||||
option(MCRF_GAME_SHELL "Use minimal game-only HTML shell (no REPL)" OFF)
|
||||
|
||||
# Debug/sanitizer build options
|
||||
option(MCRF_SANITIZE_ADDRESS "Build with AddressSanitizer" OFF)
|
||||
option(MCRF_SANITIZE_UNDEFINED "Build with UBSan" OFF)
|
||||
option(MCRF_SANITIZE_THREAD "Build with ThreadSanitizer" OFF)
|
||||
option(MCRF_DEBUG_PYTHON "Link against debug CPython from __lib_debug/" OFF)
|
||||
option(MCRF_FREE_THREADED_PYTHON "Link against free-threaded CPython (python3.14t)" OFF)
|
||||
option(MCRF_WASM_DEBUG "Build WASM with DWARF debug info and source maps" OFF)
|
||||
option(MCRF_FUZZER "Build with libFuzzer coverage instrumentation for atheris" OFF)
|
||||
option(MCRF_PROFILE "Keep frame pointers for perf/Callgrind profiling (use with RelWithDebInfo)" OFF)
|
||||
|
||||
# Validate mutually exclusive sanitizers
|
||||
if(MCRF_SANITIZE_ADDRESS AND MCRF_SANITIZE_THREAD)
|
||||
message(FATAL_ERROR "ASan and TSan are mutually exclusive. Use one or the other.")
|
||||
endif()
|
||||
|
||||
# Validate debug Python library exists when requested
|
||||
if(MCRF_DEBUG_PYTHON)
|
||||
if(NOT EXISTS "${CMAKE_SOURCE_DIR}/__lib_debug/libpython3.14.so.1.0")
|
||||
message(FATAL_ERROR
|
||||
"__lib_debug/libpython3.14.so.1.0 not found.\n"
|
||||
"Build it first: tools/build_debug_python.sh")
|
||||
endif()
|
||||
message(STATUS "Using debug CPython from __lib_debug/")
|
||||
endif()
|
||||
|
||||
# Emscripten builds: use SDL2 if specified, otherwise fall back to headless
|
||||
if(EMSCRIPTEN)
|
||||
if(MCRF_SDL2)
|
||||
message(STATUS "Emscripten detected - using SDL2 backend")
|
||||
set(MCRF_HEADLESS OFF)
|
||||
else()
|
||||
set(MCRF_HEADLESS ON)
|
||||
message(STATUS "Emscripten detected - forcing HEADLESS mode (use -DMCRF_SDL2=ON for graphics)")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(MCRF_SDL2)
|
||||
message(STATUS "Building with SDL2 backend - SDL2+OpenGL ES 2")
|
||||
endif()
|
||||
|
||||
if(MCRF_PLAYGROUND)
|
||||
message(STATUS "Building in PLAYGROUND mode - minimal scripts for web REPL")
|
||||
endif()
|
||||
|
||||
if(MCRF_HEADLESS)
|
||||
message(STATUS "Building in HEADLESS mode - no SFML/ImGui dependencies")
|
||||
endif()
|
||||
|
||||
# Detect cross-compilation for Windows (MinGW)
|
||||
if(CMAKE_CROSSCOMPILING AND WIN32)
|
||||
set(MCRF_CROSS_WINDOWS TRUE)
|
||||
message(STATUS "Cross-compiling for Windows using MinGW")
|
||||
endif()
|
||||
|
||||
# Add include directories
|
||||
#include_directories(${CMAKE_SOURCE_DIR}/deps_linux)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps)
|
||||
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/deps/libtcod)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src/3d)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src/platform)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src/tiled)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src/ldtk)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/src/audio)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/modules/RapidXML)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/modules/json/single_include)
|
||||
#include_directories(${CMAKE_SOURCE_DIR}/deps_linux/Python-3.11.1)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/libtcod)
|
||||
|
||||
# Python includes: use different paths for Windows vs Linux vs Emscripten
|
||||
if(EMSCRIPTEN)
|
||||
# Emscripten build: use Python headers compiled for wasm32-emscripten
|
||||
# The pyconfig.h from cross-build has correct LONG_BIT and other settings
|
||||
set(PYTHON_WASM_BUILD "${CMAKE_SOURCE_DIR}/deps/cpython/cross-build/wasm32-emscripten/build/python")
|
||||
# Force-include wasm pyconfig.h BEFORE anything else to set correct platform defines
|
||||
add_compile_options(-include ${PYTHON_WASM_BUILD}/pyconfig.h)
|
||||
# Override LONG_BIT - Emscripten's limits.h incorrectly defines it as 64 for wasm32
|
||||
add_compile_definitions(LONG_BIT=32)
|
||||
# Include wasm build directory FIRST so its pyconfig.h is found by #include "pyconfig.h"
|
||||
include_directories(BEFORE ${PYTHON_WASM_BUILD})
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython/Include)
|
||||
message(STATUS "Using Emscripten Python from: ${PYTHON_WASM_BUILD}")
|
||||
elseif(MCRF_CROSS_WINDOWS)
|
||||
# Windows cross-compilation: use cpython headers with PC/pyconfig.h
|
||||
# Problem: Python.h uses #include "pyconfig.h" which finds Include/pyconfig.h (Linux) first
|
||||
# Solution: Use -include to force Windows pyconfig.h to be included first
|
||||
# This defines MS_WINDOWS before Python.h is processed, ensuring correct struct layouts
|
||||
add_compile_options(-include ${CMAKE_SOURCE_DIR}/deps/cpython/PC/pyconfig.h)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython/Include)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython/PC) # For other Windows-specific headers
|
||||
# Also include SFML and libtcod Windows headers
|
||||
include_directories(${CMAKE_SOURCE_DIR}/__lib_windows/sfml/include)
|
||||
include_directories(SYSTEM ${CMAKE_SOURCE_DIR}/__lib_windows/libtcod/include)
|
||||
else()
|
||||
# Native builds (Linux/Windows): use existing Python setup
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/Python)
|
||||
endif()
|
||||
|
||||
# ImGui and ImGui-SFML include directories (not needed in headless or SDL2 mode)
|
||||
# SDL2 builds will use ImGui with SDL2 backend later; for now, no ImGui
|
||||
if(NOT MCRF_HEADLESS AND NOT MCRF_SDL2)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/modules/imgui)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/modules/imgui-sfml)
|
||||
|
||||
# ImGui source files
|
||||
set(IMGUI_SOURCES
|
||||
${CMAKE_SOURCE_DIR}/modules/imgui/imgui.cpp
|
||||
${CMAKE_SOURCE_DIR}/modules/imgui/imgui_draw.cpp
|
||||
${CMAKE_SOURCE_DIR}/modules/imgui/imgui_tables.cpp
|
||||
${CMAKE_SOURCE_DIR}/modules/imgui/imgui_widgets.cpp
|
||||
${CMAKE_SOURCE_DIR}/modules/imgui-sfml/imgui-SFML.cpp
|
||||
)
|
||||
endif()
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/Python)
|
||||
|
||||
# Collect all the source files
|
||||
file(GLOB_RECURSE SOURCES "src/*.cpp")
|
||||
|
||||
# Add ImGui sources to the build (only if using SFML)
|
||||
if(NOT MCRF_HEADLESS AND NOT MCRF_SDL2)
|
||||
list(APPEND SOURCES ${IMGUI_SOURCES})
|
||||
# Add GLAD for OpenGL function loading (needed for 3D rendering on SFML)
|
||||
list(APPEND SOURCES "${CMAKE_SOURCE_DIR}/src/3d/glad.c")
|
||||
endif()
|
||||
|
||||
# Find OpenGL (required by ImGui-SFML) - not needed in headless mode
|
||||
# SDL2 builds handle OpenGL ES 2 differently (via SDL2 or Emscripten)
|
||||
if(NOT MCRF_HEADLESS AND NOT MCRF_SDL2)
|
||||
if(MCRF_CROSS_WINDOWS)
|
||||
# For cross-compilation, OpenGL is provided by MinGW
|
||||
set(OPENGL_LIBRARIES opengl32)
|
||||
else()
|
||||
find_package(OpenGL REQUIRED)
|
||||
set(OPENGL_LIBRARIES OpenGL::GL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Create a list of libraries to link against
|
||||
if(EMSCRIPTEN)
|
||||
# Emscripten build: link against WASM-compiled Python and libtcod
|
||||
set(PYTHON_WASM_BUILD "${CMAKE_SOURCE_DIR}/deps/cpython/cross-build/wasm32-emscripten/build/python")
|
||||
set(PYTHON_WASM_PREFIX "${CMAKE_SOURCE_DIR}/deps/cpython/cross-build/wasm32-emscripten/prefix")
|
||||
set(LIBTCOD_WASM_BUILD "${CMAKE_SOURCE_DIR}/modules/libtcod-headless/build-emscripten")
|
||||
# Collect HACL crypto object files (not included in libpython3.14.a)
|
||||
file(GLOB PYTHON_HACL_OBJECTS "${PYTHON_WASM_BUILD}/Modules/_hacl/*.o")
|
||||
set(LINK_LIBS
|
||||
${PYTHON_WASM_BUILD}/libpython3.14.a
|
||||
${PYTHON_HACL_OBJECTS}
|
||||
${PYTHON_WASM_BUILD}/Modules/expat/libexpat.a
|
||||
${PYTHON_WASM_PREFIX}/lib/libmpdec.a
|
||||
${PYTHON_WASM_PREFIX}/lib/libffi.a
|
||||
${LIBTCOD_WASM_BUILD}/libtcod.a
|
||||
${LIBTCOD_WASM_BUILD}/_deps/lodepng-c-build/liblodepng-c.a
|
||||
${LIBTCOD_WASM_BUILD}/_deps/utf8proc-build/libutf8proc.a)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux) # Use Linux platform stubs for now
|
||||
# For SDL2 builds, add stb headers for image/font loading
|
||||
if(MCRF_SDL2)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/stb)
|
||||
endif()
|
||||
message(STATUS "Linking Emscripten Python: ${PYTHON_WASM_BUILD}/libpython3.14.a")
|
||||
message(STATUS "Linking Emscripten libtcod: ${LIBTCOD_WASM_BUILD}/libtcod.a")
|
||||
elseif(MCRF_SDL2)
|
||||
# SDL2 build (non-Emscripten): link against SDL2 and system libraries
|
||||
# Note: For desktop SDL2 builds in the future
|
||||
find_package(SDL2 REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
set(LINK_LIBS
|
||||
SDL2::SDL2
|
||||
OpenGL::GL
|
||||
tcod
|
||||
python3.14
|
||||
m dl util pthread)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/stb) # stb_image.h, stb_truetype.h
|
||||
link_directories(${CMAKE_SOURCE_DIR}/__lib)
|
||||
message(STATUS "Building with SDL2 backend (desktop)")
|
||||
elseif(MCRF_HEADLESS)
|
||||
# Headless build: no SFML, no OpenGL
|
||||
if(WIN32 OR MCRF_CROSS_WINDOWS)
|
||||
set(LINK_LIBS
|
||||
libtcod
|
||||
python314)
|
||||
if(MCRF_CROSS_WINDOWS)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/windows)
|
||||
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows/libtcod/lib)
|
||||
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows)
|
||||
else()
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/windows)
|
||||
link_directories(${CMAKE_SOURCE_DIR}/__lib)
|
||||
endif()
|
||||
else()
|
||||
# Unix/Linux headless build
|
||||
if(MCRF_FREE_THREADED_PYTHON)
|
||||
set(PYTHON_LIB python3.14t)
|
||||
elseif(MCRF_DEBUG_PYTHON)
|
||||
set(PYTHON_LIB python3.14d)
|
||||
else()
|
||||
set(PYTHON_LIB python3.14)
|
||||
endif()
|
||||
set(LINK_LIBS
|
||||
tcod
|
||||
${PYTHON_LIB}
|
||||
m dl util pthread)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux)
|
||||
if(MCRF_DEBUG_PYTHON OR MCRF_FREE_THREADED_PYTHON)
|
||||
link_directories(${CMAKE_SOURCE_DIR}/__lib_debug)
|
||||
endif()
|
||||
link_directories(${CMAKE_SOURCE_DIR}/__lib)
|
||||
endif()
|
||||
elseif(MCRF_CROSS_WINDOWS)
|
||||
# MinGW cross-compilation: use full library names
|
||||
set(LINK_LIBS
|
||||
sfml-graphics
|
||||
sfml-window
|
||||
sfml-system
|
||||
sfml-audio
|
||||
libtcod
|
||||
python314
|
||||
${OPENGL_LIBRARIES})
|
||||
|
||||
# Add Windows system libraries needed by SFML and MinGW
|
||||
list(APPEND LINK_LIBS
|
||||
winmm # Windows multimedia (for audio)
|
||||
gdi32 # Graphics Device Interface
|
||||
ws2_32 # Winsock (networking, used by some deps)
|
||||
ole32 # OLE support
|
||||
oleaut32 # OLE automation
|
||||
uuid # UUID library
|
||||
comdlg32 # Common dialogs
|
||||
imm32 # Input Method Manager
|
||||
version # Version info
|
||||
)
|
||||
set(LINK_LIBS
|
||||
m
|
||||
dl
|
||||
util
|
||||
pthread
|
||||
python3.12
|
||||
sfml-graphics
|
||||
sfml-window
|
||||
sfml-system
|
||||
sfml-audio
|
||||
tcod)
|
||||
|
||||
# On Windows, add any additional libs and include directories
|
||||
if(WIN32)
|
||||
# Add the necessary Windows-specific libraries and include directories
|
||||
# include_directories(path_to_additional_includes)
|
||||
# link_directories(path_to_additional_libs)
|
||||
# list(APPEND LINK_LIBS additional_windows_libs)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/windows)
|
||||
|
||||
# Link directories for cross-compiled Windows libs
|
||||
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows/sfml/lib)
|
||||
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows/libtcod/lib)
|
||||
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows)
|
||||
elseif(WIN32)
|
||||
# Native Windows build (MSVC)
|
||||
set(LINK_LIBS
|
||||
sfml-graphics
|
||||
sfml-window
|
||||
sfml-system
|
||||
sfml-audio
|
||||
tcod
|
||||
python314
|
||||
${OPENGL_LIBRARIES})
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/windows)
|
||||
link_directories(${CMAKE_SOURCE_DIR}/__lib)
|
||||
else()
|
||||
# Unix/Linux build
|
||||
if(MCRF_FREE_THREADED_PYTHON)
|
||||
set(PYTHON_LIB python3.14t)
|
||||
elseif(MCRF_DEBUG_PYTHON)
|
||||
set(PYTHON_LIB python3.14d)
|
||||
else()
|
||||
set(PYTHON_LIB python3.14)
|
||||
endif()
|
||||
set(LINK_LIBS
|
||||
sfml-graphics
|
||||
sfml-window
|
||||
sfml-system
|
||||
sfml-audio
|
||||
tcod
|
||||
${PYTHON_LIB}
|
||||
m dl util pthread
|
||||
${OPENGL_LIBRARIES})
|
||||
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux)
|
||||
if(MCRF_DEBUG_PYTHON OR MCRF_FREE_THREADED_PYTHON)
|
||||
link_directories(${CMAKE_SOURCE_DIR}/__lib_debug)
|
||||
endif()
|
||||
link_directories(${CMAKE_SOURCE_DIR}/__lib)
|
||||
endif()
|
||||
|
||||
# Add the directory where the linker should look for the libraries
|
||||
#link_directories(${CMAKE_SOURCE_DIR}/deps_linux)
|
||||
link_directories(${CMAKE_SOURCE_DIR}/__lib)
|
||||
|
||||
# Define the executable target before linking libraries
|
||||
add_executable(mcrogueface ${SOURCES})
|
||||
|
||||
# Define NO_SDL for libtcod-headless headers (excludes SDL-dependent code)
|
||||
# We ALWAYS need this because libtcod headers expect SDL3, not SDL2
|
||||
# Our SDL2 backend is separate from libtcod's SDL3 renderer
|
||||
target_compile_definitions(mcrogueface PRIVATE NO_SDL)
|
||||
|
||||
# Sanitizer instrumentation — applied to mcrogueface target only (not imported libs)
|
||||
if(MCRF_SANITIZE_ADDRESS)
|
||||
message(STATUS "AddressSanitizer enabled")
|
||||
target_compile_options(mcrogueface PRIVATE
|
||||
-fsanitize=address -fno-omit-frame-pointer -g -O1)
|
||||
target_link_options(mcrogueface PRIVATE
|
||||
-fsanitize=address)
|
||||
endif()
|
||||
|
||||
if(MCRF_SANITIZE_UNDEFINED)
|
||||
message(STATUS "UndefinedBehaviorSanitizer enabled")
|
||||
# -fno-sanitize=function is Clang-only; -fno-sanitize=vptr avoids CPython false positives
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
set(UBSAN_EXCLUSIONS -fno-sanitize=function,vptr)
|
||||
else()
|
||||
set(UBSAN_EXCLUSIONS -fno-sanitize=vptr)
|
||||
endif()
|
||||
target_compile_options(mcrogueface PRIVATE
|
||||
-fsanitize=undefined ${UBSAN_EXCLUSIONS} -g -O1)
|
||||
target_link_options(mcrogueface PRIVATE
|
||||
-fsanitize=undefined ${UBSAN_EXCLUSIONS})
|
||||
endif()
|
||||
|
||||
if(MCRF_SANITIZE_THREAD)
|
||||
message(STATUS "ThreadSanitizer enabled")
|
||||
target_compile_options(mcrogueface PRIVATE
|
||||
-fsanitize=thread -g -O1)
|
||||
target_link_options(mcrogueface PRIVATE
|
||||
-fsanitize=thread)
|
||||
endif()
|
||||
|
||||
# Profiling instrumentation (#345) — retain frame pointers so perf --call-graph fp
|
||||
# and Callgrind can unwind through optimized code. Optimization/-g come from the
|
||||
# RelWithDebInfo build type; this only adds the frame pointer. Binary is NOT stripped.
|
||||
if(MCRF_PROFILE)
|
||||
message(STATUS "Profiling build: frame pointers retained (perf/Callgrind)")
|
||||
target_compile_options(mcrogueface PRIVATE -fno-omit-frame-pointer)
|
||||
endif()
|
||||
|
||||
if(MCRF_FUZZER)
|
||||
if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||
message(FATAL_ERROR "MCRF_FUZZER=ON requires Clang. Invoke with CC=clang-18 CXX=clang++-18.")
|
||||
endif()
|
||||
message(STATUS "Building mcrfpy_fuzz harness (libFuzzer + ASan + UBSan)")
|
||||
|
||||
set(MCRF_FUZZ_SOURCES ${SOURCES})
|
||||
list(REMOVE_ITEM MCRF_FUZZ_SOURCES ${CMAKE_SOURCE_DIR}/src/main.cpp)
|
||||
list(APPEND MCRF_FUZZ_SOURCES ${CMAKE_SOURCE_DIR}/tests/fuzz/fuzz_common.cpp)
|
||||
|
||||
add_executable(mcrfpy_fuzz ${MCRF_FUZZ_SOURCES})
|
||||
target_compile_definitions(mcrfpy_fuzz PRIVATE NO_SDL MCRF_FUZZ_HARNESS)
|
||||
if(MCRF_DEBUG_PYTHON OR MCRF_FREE_THREADED_PYTHON)
|
||||
target_compile_definitions(mcrfpy_fuzz PRIVATE Py_DEBUG)
|
||||
endif()
|
||||
if(MCRF_FREE_THREADED_PYTHON)
|
||||
target_compile_definitions(mcrfpy_fuzz PRIVATE Py_GIL_DISABLED)
|
||||
endif()
|
||||
if(MCRF_HEADLESS)
|
||||
target_compile_definitions(mcrfpy_fuzz PRIVATE MCRF_HEADLESS)
|
||||
endif()
|
||||
if(MCRF_SDL2)
|
||||
target_compile_definitions(mcrfpy_fuzz PRIVATE MCRF_SDL2)
|
||||
endif()
|
||||
target_include_directories(mcrfpy_fuzz PRIVATE
|
||||
${CMAKE_SOURCE_DIR}/src
|
||||
${CMAKE_SOURCE_DIR}/tests/fuzz)
|
||||
target_compile_options(mcrfpy_fuzz PRIVATE
|
||||
-fsanitize=fuzzer-no-link,address,undefined
|
||||
-fno-sanitize=function,vptr
|
||||
-fno-omit-frame-pointer -g -O1)
|
||||
target_link_options(mcrfpy_fuzz PRIVATE
|
||||
-fsanitize=fuzzer,address,undefined
|
||||
-fno-sanitize=function,vptr)
|
||||
target_link_libraries(mcrfpy_fuzz ${LINK_LIBS})
|
||||
|
||||
# Copy Python runtime + assets next to mcrfpy_fuzz so the embedded
|
||||
# interpreter finds the stdlib and default_font/default_texture load.
|
||||
add_custom_command(TARGET mcrfpy_fuzz POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrfpy_fuzz>/lib
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/assets $<TARGET_FILE_DIR:mcrfpy_fuzz>/assets)
|
||||
if(MCRF_DEBUG_PYTHON)
|
||||
add_custom_command(TARGET mcrfpy_fuzz POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_SOURCE_DIR}/__lib_debug/libpython3.14.so.1.0
|
||||
$<TARGET_FILE_DIR:mcrfpy_fuzz>/lib/libpython3.14.so.1.0
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_SOURCE_DIR}/__lib_debug/libpython3.14.so.1.0
|
||||
$<TARGET_FILE_DIR:mcrfpy_fuzz>/lib/libpython3.14d.so.1.0
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink
|
||||
libpython3.14d.so.1.0
|
||||
$<TARGET_FILE_DIR:mcrfpy_fuzz>/lib/libpython3.14d.so)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Enable Py_DEBUG when linking against debug CPython (matches pydebug ABI)
|
||||
if(MCRF_DEBUG_PYTHON OR MCRF_FREE_THREADED_PYTHON)
|
||||
target_compile_definitions(mcrogueface PRIVATE Py_DEBUG)
|
||||
endif()
|
||||
|
||||
# Enable Py_GIL_DISABLED for free-threaded CPython (no-GIL build)
|
||||
if(MCRF_FREE_THREADED_PYTHON)
|
||||
target_compile_definitions(mcrogueface PRIVATE Py_GIL_DISABLED)
|
||||
endif()
|
||||
|
||||
# Define MCRF_HEADLESS for headless builds (excludes SFML/ImGui code)
|
||||
if(MCRF_HEADLESS)
|
||||
target_compile_definitions(mcrogueface PRIVATE MCRF_HEADLESS)
|
||||
endif()
|
||||
|
||||
# Define MCRF_SDL2 for SDL2 builds (uses SDL2+OpenGL ES 2 instead of SFML)
|
||||
if(MCRF_SDL2)
|
||||
target_compile_definitions(mcrogueface PRIVATE MCRF_SDL2)
|
||||
endif()
|
||||
|
||||
# Asset/script directories for WASM preloading (game projects override these)
|
||||
set(MCRF_ASSETS_DIR "${CMAKE_SOURCE_DIR}/assets" CACHE PATH "Assets directory for WASM preloading")
|
||||
set(MCRF_SCRIPTS_DIR "${CMAKE_SOURCE_DIR}/src/scripts" CACHE PATH "Scripts directory for WASM preloading")
|
||||
set(MCRF_SCRIPTS_PLAYGROUND_DIR "${CMAKE_SOURCE_DIR}/src/scripts_playground" CACHE PATH "Playground scripts for WASM")
|
||||
set(MCRF_SCRIPTS_DEMO_DIR "${CMAKE_SOURCE_DIR}/src/scripts_demo" CACHE PATH "Demo scripts for WASM showcase")
|
||||
|
||||
# Emscripten-specific link options (use ports for zlib, bzip2, sqlite3)
|
||||
if(EMSCRIPTEN)
|
||||
# Base Emscripten options
|
||||
set(EMSCRIPTEN_LINK_OPTIONS
|
||||
-sUSE_ZLIB=1
|
||||
-sUSE_BZIP2=1
|
||||
-sUSE_SQLITE3=1
|
||||
-sALLOW_MEMORY_GROWTH=1
|
||||
-sSTACK_SIZE=2097152
|
||||
-sEXPORTED_RUNTIME_METHODS=ccall,cwrap,FS,IDBFS
|
||||
-sEXPORTED_FUNCTIONS=_main,_run_python_string,_run_python_string_with_output,_reset_python_environment,_notify_canvas_resize,_sync_storage
|
||||
-lidbfs.js
|
||||
-sASSERTIONS=2
|
||||
-sSTACK_OVERFLOW_CHECK=2
|
||||
-fexceptions
|
||||
-sNO_DISABLE_EXCEPTION_CATCHING
|
||||
# Disable features that require dynamic linking support
|
||||
-sERROR_ON_UNDEFINED_SYMBOLS=0
|
||||
-sALLOW_UNIMPLEMENTED_SYSCALLS=1
|
||||
# Preload Python stdlib into virtual filesystem at /lib/python3.14
|
||||
--preload-file=${CMAKE_SOURCE_DIR}/wasm_stdlib/lib@/lib
|
||||
# Preload game scripts into /scripts (playground, demo, or full game)
|
||||
--preload-file=$<IF:$<BOOL:${MCRF_PLAYGROUND}>,${MCRF_SCRIPTS_PLAYGROUND_DIR},$<IF:$<BOOL:${MCRF_DEMO}>,${MCRF_SCRIPTS_DEMO_DIR},${MCRF_SCRIPTS_DIR}>>@/scripts
|
||||
# Preload assets
|
||||
--preload-file=${MCRF_ASSETS_DIR}@/assets
|
||||
# Use custom HTML shell - game shell (fullscreen) or playground shell (REPL)
|
||||
--shell-file=${CMAKE_SOURCE_DIR}/src/$<IF:$<BOOL:${MCRF_GAME_SHELL}>,shell_game.html,shell.html>
|
||||
# Pre-JS to fix browser zoom causing undefined values in events
|
||||
--pre-js=${CMAKE_SOURCE_DIR}/src/emscripten_pre.js
|
||||
)
|
||||
|
||||
# Add SDL2 options if using SDL2 backend
|
||||
if(MCRF_SDL2)
|
||||
list(APPEND EMSCRIPTEN_LINK_OPTIONS
|
||||
-sUSE_SDL=2
|
||||
-sUSE_SDL_MIXER=2
|
||||
-sFULL_ES2=1
|
||||
-sMIN_WEBGL_VERSION=2
|
||||
-sMAX_WEBGL_VERSION=2
|
||||
-sUSE_FREETYPE=1
|
||||
)
|
||||
# SDL2, SDL2_mixer, and FreeType flags are also needed at compile time for headers
|
||||
target_compile_options(mcrogueface PRIVATE
|
||||
-sUSE_SDL=2
|
||||
-sUSE_SDL_MIXER=2
|
||||
-sUSE_FREETYPE=1
|
||||
)
|
||||
message(STATUS "Emscripten SDL2 options enabled: -sUSE_SDL=2 -sUSE_SDL_MIXER=2 -sFULL_ES2=1 -sUSE_FREETYPE=1")
|
||||
endif()
|
||||
|
||||
# WASM debug builds: DWARF symbols, source maps, symbol map for stack traces
|
||||
if(MCRF_WASM_DEBUG)
|
||||
list(APPEND EMSCRIPTEN_LINK_OPTIONS
|
||||
-g4
|
||||
-gsource-map
|
||||
--emit-symbol-map
|
||||
)
|
||||
target_compile_options(mcrogueface PRIVATE -g4)
|
||||
message(STATUS "Emscripten debug enabled: DWARF (-g4), source maps, symbol map")
|
||||
endif()
|
||||
|
||||
target_link_options(mcrogueface PRIVATE ${EMSCRIPTEN_LINK_OPTIONS})
|
||||
|
||||
# Output as HTML to use the shell file
|
||||
set_target_properties(mcrogueface PROPERTIES SUFFIX ".html")
|
||||
|
||||
# Set Python home for the embedded interpreter
|
||||
target_compile_definitions(mcrogueface PRIVATE
|
||||
MCRF_WASM_PYTHON_HOME="/lib/python3.14"
|
||||
)
|
||||
endif()
|
||||
|
||||
# On Windows, define Py_ENABLE_SHARED for proper Python DLL imports
|
||||
# Py_PYCONFIG_H prevents Include/pyconfig.h (Linux config) from being included
|
||||
# (PC/pyconfig.h already defines HAVE_DECLSPEC_DLL and MS_WINDOWS)
|
||||
if(WIN32 OR MCRF_CROSS_WINDOWS)
|
||||
target_compile_definitions(mcrogueface PRIVATE Py_ENABLE_SHARED Py_PYCONFIG_H)
|
||||
endif()
|
||||
|
||||
# On Windows, set subsystem to WINDOWS to hide console (release builds only)
|
||||
# Use -DMCRF_WINDOWS_CONSOLE=ON for debug builds with console output
|
||||
option(MCRF_WINDOWS_CONSOLE "Keep console window visible for debugging" OFF)
|
||||
|
||||
if(WIN32 AND NOT MCRF_CROSS_WINDOWS)
|
||||
# MSVC-specific flags
|
||||
if(NOT MCRF_WINDOWS_CONSOLE)
|
||||
set_target_properties(mcrogueface PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
LINK_FLAGS "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")
|
||||
endif()
|
||||
elseif(MCRF_CROSS_WINDOWS)
|
||||
# MinGW cross-compilation
|
||||
if(NOT MCRF_WINDOWS_CONSOLE)
|
||||
# Release: use -mwindows to hide console
|
||||
set_target_properties(mcrogueface PROPERTIES
|
||||
WIN32_EXECUTABLE TRUE
|
||||
LINK_FLAGS "-mwindows")
|
||||
else()
|
||||
# Debug: keep console for stdout/stderr output
|
||||
message(STATUS "Windows console enabled for debugging")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Now the linker will find the libraries in the specified directory
|
||||
target_link_libraries(mcrogueface ${LINK_LIBS})
|
||||
|
||||
|
|
@ -543,63 +65,11 @@ add_custom_command(TARGET mcrogueface POST_BUILD
|
|||
${CMAKE_SOURCE_DIR}/src/scripts $<TARGET_FILE_DIR:mcrogueface>/scripts)
|
||||
|
||||
# Copy Python standard library to build directory
|
||||
if(MCRF_DEBUG_PYTHON)
|
||||
# Copy all libs first (SFML, libtcod, Python stdlib), then overwrite with debug Python
|
||||
# The debug lib has SONAME libpython3.14d.so.1.0, so we need both names
|
||||
add_custom_command(TARGET mcrogueface POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrogueface>/lib
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_SOURCE_DIR}/__lib_debug/libpython3.14.so.1.0
|
||||
$<TARGET_FILE_DIR:mcrogueface>/lib/libpython3.14.so.1.0
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_SOURCE_DIR}/__lib_debug/libpython3.14.so.1.0
|
||||
$<TARGET_FILE_DIR:mcrogueface>/lib/libpython3.14d.so.1.0
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink
|
||||
libpython3.14d.so.1.0
|
||||
$<TARGET_FILE_DIR:mcrogueface>/lib/libpython3.14d.so)
|
||||
else()
|
||||
add_custom_command(TARGET mcrogueface POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrogueface>/lib)
|
||||
endif()
|
||||
add_custom_command(TARGET mcrogueface POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrogueface>/lib)
|
||||
|
||||
# On Windows, copy DLLs to executable directory
|
||||
if(MCRF_CROSS_WINDOWS)
|
||||
# Cross-compilation: copy DLLs from __lib_windows
|
||||
add_custom_command(TARGET mcrogueface POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/__lib_windows/sfml/bin $<TARGET_FILE_DIR:mcrogueface>
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/__lib_windows/libtcod/bin $<TARGET_FILE_DIR:mcrogueface>
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_SOURCE_DIR}/__lib_windows/python314.dll $<TARGET_FILE_DIR:mcrogueface>
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_SOURCE_DIR}/__lib_windows/python3.dll $<TARGET_FILE_DIR:mcrogueface>
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_SOURCE_DIR}/__lib_windows/vcruntime140.dll $<TARGET_FILE_DIR:mcrogueface>
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_SOURCE_DIR}/__lib_windows/vcruntime140_1.dll $<TARGET_FILE_DIR:mcrogueface>
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
/usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll $<TARGET_FILE_DIR:mcrogueface>
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Copied Windows DLLs to executable directory")
|
||||
|
||||
# Copy Python standard library zip
|
||||
add_custom_command(TARGET mcrogueface POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_SOURCE_DIR}/__lib_windows/python314.zip $<TARGET_FILE_DIR:mcrogueface>
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Copied Python stdlib")
|
||||
elseif(WIN32)
|
||||
# Native Windows build: copy DLLs from __lib
|
||||
add_custom_command(TARGET mcrogueface POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrogueface>
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "Copied DLLs to executable directory")
|
||||
endif()
|
||||
|
||||
# rpath for including shared libraries (Linux/Unix only)
|
||||
if(NOT WIN32)
|
||||
set_target_properties(mcrogueface PROPERTIES
|
||||
INSTALL_RPATH "$ORIGIN/./lib")
|
||||
endif()
|
||||
# rpath for including shared libraries
|
||||
set_target_properties(mcrogueface PROPERTIES
|
||||
INSTALL_RPATH "$ORIGIN/./lib")
|
||||
|
||||
|
|
|
|||
54
GNUmakefile
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# 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"
|
||||
590
Makefile
|
|
@ -1,590 +0,0 @@
|
|||
# McRogueFace Build Makefile
|
||||
# Usage:
|
||||
# make - Build for Linux (default)
|
||||
# make windows - Cross-compile for Windows using MinGW (release)
|
||||
# make windows-debug - Cross-compile for Windows with console & debug symbols
|
||||
# make clean - Clean Linux build
|
||||
# make clean-windows - Clean Windows build
|
||||
# make run - Run the Linux build
|
||||
#
|
||||
# Profiling (see docs/profiling.md):
|
||||
# make profile - RelWithDebInfo + frame pointers build in build-profile/
|
||||
# make callgrind SCRIPT=tests/benchmarks/foo.py - Callgrind a headless benchmark
|
||||
#
|
||||
# WebAssembly / Emscripten:
|
||||
# make wasm - Build full game for web WITH the REPL console (dev/debug build)
|
||||
# make wasm-game - Build the clean full game for web (fullscreen, no REPL) -- shipped
|
||||
# make playground - Build minimal playground for web REPL
|
||||
# make serve - Serve the clean game (build-wasm-game) locally on port 8080
|
||||
# make serve-game - Serve the clean game (build-wasm-game) locally on port 8080
|
||||
# make serve-dev - Serve the wasm dev build with REPL console (build-emscripten)
|
||||
# make clean-wasm - Clean Emscripten builds
|
||||
#
|
||||
# Packaging:
|
||||
# make package-windows-light - Windows with minimal stdlib (~5 MB)
|
||||
# make package-windows-full - Windows with full stdlib (~15 MB)
|
||||
# make package-linux-light - Linux with minimal stdlib
|
||||
# make package-linux-full - Linux with full stdlib
|
||||
# make package-all - All platform/preset combinations
|
||||
#
|
||||
# Release:
|
||||
# make version-bump NEXT_VERSION=x.y.z-suffix
|
||||
# Tags HEAD with current version, builds all packages, bumps to NEXT_VERSION
|
||||
|
||||
.PHONY: all linux windows windows-debug clean clean-windows clean-dist run
|
||||
.PHONY: wasm wasm-game wasm-debug playground playground-debug serve serve-game serve-dev serve-playground clean-wasm
|
||||
.PHONY: package-windows-light package-windows-full package-linux-light package-linux-full package-all
|
||||
.PHONY: version-bump
|
||||
.PHONY: debug debug-test asan asan-test tsan tsan-test valgrind-test massif-test analyze clean-debug
|
||||
.PHONY: profile callgrind clean-profile
|
||||
.PHONY: install-hooks
|
||||
|
||||
# Number of parallel jobs for compilation
|
||||
JOBS := $(shell nproc 2>/dev/null || echo 4)
|
||||
|
||||
all: linux
|
||||
|
||||
linux:
|
||||
@echo "Building McRogueFace for Linux..."
|
||||
@mkdir -p build
|
||||
@cd build && cmake .. -DCMAKE_BUILD_TYPE=Release && make -j$(JOBS)
|
||||
@echo "Build complete! Run with: ./build/mcrogueface"
|
||||
|
||||
windows:
|
||||
@echo "Cross-compiling McRogueFace for Windows..."
|
||||
@mkdir -p build-windows
|
||||
@cd build-windows && cmake .. \
|
||||
-DCMAKE_TOOLCHAIN_FILE=../cmake/toolchains/mingw-w64-x86_64.cmake \
|
||||
-DCMAKE_BUILD_TYPE=Release && make -j$(JOBS)
|
||||
@echo "Windows build complete! Output: build-windows/mcrogueface.exe"
|
||||
|
||||
windows-debug:
|
||||
@echo "Cross-compiling McRogueFace for Windows (debug with console)..."
|
||||
@mkdir -p build-windows-debug
|
||||
@cd build-windows-debug && cmake .. \
|
||||
-DCMAKE_TOOLCHAIN_FILE=../cmake/toolchains/mingw-w64-x86_64.cmake \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DMCRF_WINDOWS_CONSOLE=ON && make -j$(JOBS)
|
||||
@echo "Windows debug build complete! Output: build-windows-debug/mcrogueface.exe"
|
||||
@echo "Run from cmd.exe to see console output"
|
||||
|
||||
clean:
|
||||
@echo "Cleaning Linux build..."
|
||||
@rm -rf build
|
||||
|
||||
clean-windows:
|
||||
@echo "Cleaning Windows builds..."
|
||||
@rm -rf build-windows build-windows-debug
|
||||
|
||||
clean-dist:
|
||||
@echo "Cleaning distribution packages..."
|
||||
@rm -rf dist
|
||||
|
||||
clean-all: clean clean-windows clean-wasm clean-debug clean-profile clean-dist
|
||||
@echo "All builds and packages cleaned."
|
||||
|
||||
run: linux
|
||||
@cd build && ./mcrogueface
|
||||
|
||||
# Install git hooks by symlinking tools/hooks/* into .git/hooks/ (idempotent).
|
||||
install-hooks:
|
||||
@hooks_dir="$$(git rev-parse --git-path hooks)"; \
|
||||
mkdir -p "$$hooks_dir"; \
|
||||
for hook in tools/hooks/*; do \
|
||||
[ -e "$$hook" ] || continue; \
|
||||
name="$$(basename "$$hook")"; \
|
||||
ln -sf "$$(pwd)/$$hook" "$$hooks_dir/$$name"; \
|
||||
echo "Linked $$hooks_dir/$$name -> $$hook"; \
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Documentation / release
|
||||
#
|
||||
# Every piece of the docs pipeline already existed; nothing chained them, so the
|
||||
# site drifted 26 engine commits behind without anything noticing. These targets
|
||||
# are the chain.
|
||||
#
|
||||
# SITE_DIR is the mcrogueface.github.io checkout. Override if yours lives elsewhere:
|
||||
# make release-docs SITE_DIR=/path/to/mcrogueface.github.io
|
||||
# ---------------------------------------------------------------------------
|
||||
SITE_DIR ?= $(CURDIR)/../mcrogueface.github.io
|
||||
|
||||
# Two DIFFERENT refs, easily conflated:
|
||||
#
|
||||
# BASE_REF the PREVIOUS release. The API delta is measured against it, to answer
|
||||
# "what changed, and which pages does that oblige us to revisit?"
|
||||
# RELEASE_REF the tag being CUT. The site pins its source links to it, which is what
|
||||
# freezes this version into the site's history.
|
||||
#
|
||||
# When cutting a release, set both:
|
||||
# make release-docs BASE_REF=0.2.8 RELEASE_REF=0.2.9
|
||||
#
|
||||
# Note: BASE_REF must carry api/manifest.json. The manifest infrastructure landed in
|
||||
# 54624b3, so tags older than that (0.2.8 included) have no baseline to diff against --
|
||||
# api_delta will say so rather than inventing one.
|
||||
BASE_REF ?= $(shell git describe --tags --abbrev=0 2>/dev/null)
|
||||
RELEASE_REF ?= $(shell git describe --tags --always --abbrev=0 2>/dev/null)
|
||||
|
||||
# Regenerate everything derived from the compiled module: man page, API reference
|
||||
# (HTML + Markdown), type stubs, and the tracked api/manifest.json.
|
||||
docs: linux
|
||||
@./tools/generate_all_docs.sh
|
||||
@./build/mcrogueface --headless --exec ../tools/generate_api_manifest.py
|
||||
|
||||
# Re-run every docs snippet and stamp its `# mcrf:` header with what actually
|
||||
# happened (status from the run, verified from the engine, objects from the source).
|
||||
stamp-snippets: linux
|
||||
@python3 tools/stamp_snippets.py
|
||||
|
||||
# CI gate: fail if any snippet is broken or its stamp is stale. Writes nothing.
|
||||
check-snippets: linux
|
||||
@python3 tools/stamp_snippets.py --check
|
||||
|
||||
# Render a deterministic preview PNG for every snippet into snippet-shots/ (gitignored;
|
||||
# the images belong in the doc-site repo, which pulls them from here -- see #381). The
|
||||
# images are a visual-regression oracle: a changed PNG means behaviour changed.
|
||||
snippet-shots: linux
|
||||
@python3 tools/generate_snippet_shots.py
|
||||
|
||||
# What changed in the API since the last release, and which site pages that
|
||||
# obligates you to update (resolved via each page's `mcrf.objects` frontmatter).
|
||||
api-delta:
|
||||
@python3 tools/api_delta.py $(BASE_REF) . --site-dir $(SITE_DIR) --format md
|
||||
|
||||
# The full release documentation pass. Run this when cutting a tag: it refreshes
|
||||
# everything the engine derives, proves the published samples still run, rebuilds
|
||||
# the site's generated reference + snippet library against this engine, and prints
|
||||
# the checklist of hand-written pages the API change obligates you to revisit.
|
||||
#
|
||||
# Deliberately NOT automatic: it does not commit, and it does not touch the
|
||||
# hand-written pages. It tells you what it found and leaves the judgment to you.
|
||||
release-docs: docs stamp-snippets snippet-shots
|
||||
@echo ""
|
||||
@echo "=== Regenerating the site against this engine (SITE_DIR=$(SITE_DIR)) ==="
|
||||
@test -d "$(SITE_DIR)" || { echo "SITE_DIR not found: $(SITE_DIR)"; exit 1; }
|
||||
@cd "$(SITE_DIR)" && python3 tools/build_reference.py
|
||||
@cd "$(SITE_DIR)" && python3 tools/build_library.py --ref $(RELEASE_REF)
|
||||
@echo ""
|
||||
@echo "=== API delta $(BASE_REF) -> now, and the pages it obligates ==="
|
||||
@python3 tools/api_delta.py $(BASE_REF) . --site-dir $(SITE_DIR) --format md
|
||||
@echo ""
|
||||
@echo "Site pinned to RELEASE_REF=$(RELEASE_REF); delta measured from BASE_REF=$(BASE_REF)."
|
||||
@echo "Next: review the delta above, update the hand-written pages it names,"
|
||||
@echo " then commit both repos. For a Gitea checklist issue instead, run:"
|
||||
@echo " python3 tools/api_delta.py $(BASE_REF) . --site-dir $(SITE_DIR) --format gitea"
|
||||
|
||||
.PHONY: docs stamp-snippets check-snippets snippet-shots api-delta release-docs
|
||||
|
||||
# Debug and sanitizer targets
|
||||
debug:
|
||||
@echo "Building McRogueFace with debug Python (pydebug assertions)..."
|
||||
@mkdir -p build-debug
|
||||
@cd build-debug && cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DMCRF_DEBUG_PYTHON=ON && make -j$(JOBS)
|
||||
@echo "Debug build complete! Output: build-debug/mcrogueface"
|
||||
|
||||
debug-test: debug
|
||||
@echo "Running test suite with debug Python..."
|
||||
cd tests && MCRF_BUILD_DIR=../build-debug \
|
||||
MCRF_LIB_DIR=../__lib_debug \
|
||||
python3 run_tests.py -v
|
||||
|
||||
# Profiling build (#345): RelWithDebInfo (-O2 -g) + frame pointers, self-contained
|
||||
# in build-profile/ (lib/assets/scripts copied like the normal build). Suited to
|
||||
# both Callgrind (deterministic, no perms) and perf (--call-graph fp).
|
||||
profile:
|
||||
@echo "Building McRogueFace for profiling (RelWithDebInfo + frame pointers)..."
|
||||
@mkdir -p build-profile
|
||||
@cd build-profile && cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
||||
-DMCRF_PROFILE=ON && make -j$(JOBS)
|
||||
@echo "Profile build complete! Binary: build-profile/mcrogueface"
|
||||
|
||||
# Callgrind a headless benchmark script. Deterministic, exact instruction counts,
|
||||
# no special permissions. Usage: make callgrind SCRIPT=tests/benchmarks/foo.py
|
||||
# Output: callgrind.out (feed to callgrind_annotate).
|
||||
SCRIPT ?= tests/benchmarks/issue_331_property_read_bench.py
|
||||
callgrind: profile
|
||||
@echo "Running Callgrind on $(SCRIPT)..."
|
||||
@cd build-profile && valgrind --tool=callgrind \
|
||||
--callgrind-out-file=callgrind.out \
|
||||
./mcrogueface --headless --exec ../$(SCRIPT)
|
||||
@echo "Done. Annotate with: callgrind_annotate build-profile/callgrind.out | head -60"
|
||||
|
||||
clean-profile:
|
||||
@echo "Cleaning profile build..."
|
||||
@rm -rf build-profile
|
||||
|
||||
asan:
|
||||
@echo "Building McRogueFace with ASan + UBSan..."
|
||||
@mkdir -p build-asan
|
||||
@cd build-asan && cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DMCRF_DEBUG_PYTHON=ON \
|
||||
-DMCRF_SANITIZE_ADDRESS=ON \
|
||||
-DMCRF_SANITIZE_UNDEFINED=ON && make -j$(JOBS)
|
||||
@echo "ASan build complete! Output: build-asan/mcrogueface"
|
||||
|
||||
asan-test: asan
|
||||
@echo "Running test suite under ASan + UBSan..."
|
||||
cd tests && MCRF_BUILD_DIR=../build-asan \
|
||||
MCRF_LIB_DIR=../__lib_debug \
|
||||
PYTHONMALLOC=malloc \
|
||||
ASAN_OPTIONS="detect_leaks=1:halt_on_error=1:print_summary=1" \
|
||||
LSAN_OPTIONS="suppressions=$(CURDIR)/sanitizers/asan.supp" \
|
||||
UBSAN_OPTIONS="print_stacktrace=1:halt_on_error=1" \
|
||||
python3 run_tests.py -v --sanitizer
|
||||
|
||||
# Fuzzing targets (clang-18 + libFuzzer + ASan + UBSan).
|
||||
# Design: ONE instrumented executable `mcrfpy_fuzz` that embeds CPython,
|
||||
# registers the mcrfpy module, and dispatches each libFuzzer iteration to
|
||||
# a Python `fuzz_one_input(data)` function loaded from the script named by
|
||||
# the MCRF_FUZZ_TARGET env var. libFuzzer instruments the C++ engine code
|
||||
# where all the #258-#278 bugs live. No atheris dependency.
|
||||
FUZZ_TARGETS := grid_entity property_types anim_timer_scene maps_procgen fov pathfinding_behavior audio_dsp import_parsers texture_factory shader_bindings
|
||||
FUZZ_SECONDS ?= 30
|
||||
|
||||
# Shared env for running the fuzz binary. PYTHONHOME points at the build-fuzz
|
||||
# copy of the bundled stdlib (post-build copied into build-fuzz/lib/).
|
||||
# ASAN_OPTIONS: leak detection disabled because libFuzzer intentionally holds
|
||||
# inputs for its corpus; abort_on_error ensures crashes are loud and repro-able.
|
||||
define FUZZ_ENV
|
||||
MCRF_LIB_DIR=../__lib_debug \
|
||||
PYTHONMALLOC=malloc \
|
||||
PYTHONHOME=../__lib/Python \
|
||||
ASAN_OPTIONS="detect_leaks=0:halt_on_error=1:abort_on_error=1:print_stacktrace=1" \
|
||||
UBSAN_OPTIONS="print_stacktrace=1:halt_on_error=1"
|
||||
endef
|
||||
|
||||
fuzz-build:
|
||||
@echo "Building mcrfpy_fuzz with libFuzzer + ASan (clang-18)..."
|
||||
@mkdir -p build-fuzz
|
||||
@cd build-fuzz && CC=clang-18 CXX=clang++-18 cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DMCRF_DEBUG_PYTHON=ON \
|
||||
-DMCRF_SANITIZE_ADDRESS=ON \
|
||||
-DMCRF_SANITIZE_UNDEFINED=ON \
|
||||
-DMCRF_FUZZER=ON \
|
||||
-DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld && make -j$(JOBS) mcrfpy_fuzz
|
||||
@echo "Fuzz build complete! Output: build-fuzz/mcrfpy_fuzz"
|
||||
|
||||
fuzz: fuzz-build
|
||||
@for t in $(FUZZ_TARGETS); do \
|
||||
if [ ! -f tests/fuzz/fuzz_$$t.py ]; then \
|
||||
echo "SKIP: tests/fuzz/fuzz_$$t.py does not exist yet"; \
|
||||
continue; \
|
||||
fi; \
|
||||
echo "=== fuzzing $$t for $(FUZZ_SECONDS)s ==="; \
|
||||
mkdir -p tests/fuzz/corpora/$$t tests/fuzz/crashes; \
|
||||
( cd build-fuzz && $(FUZZ_ENV) MCRF_FUZZ_TARGET=$$t \
|
||||
./mcrfpy_fuzz \
|
||||
-max_total_time=$(FUZZ_SECONDS) \
|
||||
-artifact_prefix=../tests/fuzz/crashes/$$t- \
|
||||
../tests/fuzz/corpora/$$t ../tests/fuzz/seeds/$$t ) || exit 1; \
|
||||
done
|
||||
|
||||
fuzz-long: fuzz-build
|
||||
@test -n "$(TARGET)" || (echo "Usage: make fuzz-long TARGET=<name> SECONDS=<n>"; exit 1)
|
||||
@test -f tests/fuzz/fuzz_$(TARGET).py || (echo "No target: tests/fuzz/fuzz_$(TARGET).py"; exit 1)
|
||||
@mkdir -p tests/fuzz/corpora/$(TARGET) tests/fuzz/crashes
|
||||
@( cd build-fuzz && $(FUZZ_ENV) MCRF_FUZZ_TARGET=$(TARGET) \
|
||||
./mcrfpy_fuzz \
|
||||
-max_total_time=$(or $(SECONDS),3600) \
|
||||
-artifact_prefix=../tests/fuzz/crashes/$(TARGET)- \
|
||||
../tests/fuzz/corpora/$(TARGET) ../tests/fuzz/seeds/$(TARGET) )
|
||||
|
||||
fuzz-repro:
|
||||
@test -n "$(TARGET)" || (echo "Usage: make fuzz-repro TARGET=<name> CRASH=<path>"; exit 1)
|
||||
@test -n "$(CRASH)" || (echo "Usage: make fuzz-repro TARGET=<name> CRASH=<path>"; exit 1)
|
||||
@( cd build-fuzz && $(FUZZ_ENV) MCRF_FUZZ_TARGET=$(TARGET) \
|
||||
./mcrfpy_fuzz ../$(CRASH) )
|
||||
|
||||
clean-fuzz:
|
||||
@echo "Cleaning fuzz build and corpora..."
|
||||
@rm -rf build-fuzz tests/fuzz/corpora tests/fuzz/crashes
|
||||
|
||||
tsan:
|
||||
@echo "Building McRogueFace with TSan + free-threaded Python..."
|
||||
@echo "NOTE: Requires free-threaded debug Python built with:"
|
||||
@echo " tools/build_debug_python.sh --tsan"
|
||||
@mkdir -p build-tsan
|
||||
@cd build-tsan && cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DMCRF_FREE_THREADED_PYTHON=ON \
|
||||
-DMCRF_SANITIZE_THREAD=ON && make -j$(JOBS)
|
||||
@echo "TSan build complete! Output: build-tsan/mcrogueface"
|
||||
|
||||
tsan-test: tsan
|
||||
@echo "Running test suite under TSan..."
|
||||
cd tests && MCRF_BUILD_DIR=../build-tsan \
|
||||
MCRF_LIB_DIR=../__lib_debug \
|
||||
TSAN_OPTIONS="halt_on_error=1:second_deadlock_stack=1" \
|
||||
python3 run_tests.py -v --sanitizer
|
||||
|
||||
valgrind-test: debug
|
||||
@echo "Running test suite under Valgrind memcheck..."
|
||||
cd tests && MCRF_BUILD_DIR=../build-debug \
|
||||
MCRF_LIB_DIR=../__lib_debug \
|
||||
MCRF_TIMEOUT_MULTIPLIER=50 \
|
||||
PYTHONMALLOC=malloc \
|
||||
python3 run_tests.py -v --valgrind
|
||||
|
||||
massif-test: debug
|
||||
@echo "Running heap profiling under Valgrind Massif..."
|
||||
@mkdir -p build-debug
|
||||
cd build-debug && valgrind --tool=massif \
|
||||
--massif-out-file=massif.out \
|
||||
--pages-as-heap=no \
|
||||
--detailed-freq=10 \
|
||||
--max-snapshots=100 \
|
||||
./mcrogueface --headless --exec ../tests/benchmarks/stress_test_suite.py
|
||||
@echo "Massif output: build-debug/massif.out"
|
||||
@echo "View with: ms_print build-debug/massif.out"
|
||||
|
||||
analyze:
|
||||
@echo "Running cppcheck static analysis..."
|
||||
cppcheck --enable=warning,performance,portability \
|
||||
--suppress=missingIncludeSystem \
|
||||
--suppress=unusedFunction \
|
||||
--suppress=noExplicitConstructor \
|
||||
--suppress=missingOverride \
|
||||
--inline-suppr \
|
||||
-I src/ -I deps/ -I deps/cpython -I deps/Python \
|
||||
-I src/platform -I src/3d -I src/tiled -I src/ldtk -I src/audio \
|
||||
--std=c++20 \
|
||||
--quiet \
|
||||
src/ 2>&1
|
||||
@echo "Static analysis complete."
|
||||
|
||||
clean-debug:
|
||||
@echo "Cleaning debug/sanitizer builds..."
|
||||
@rm -rf build-debug build-asan build-tsan build-fuzz
|
||||
|
||||
# Packaging targets using tools/package.sh
|
||||
package-windows-light: windows
|
||||
@./tools/package.sh windows light
|
||||
|
||||
package-windows-full: windows
|
||||
@./tools/package.sh windows full
|
||||
|
||||
package-linux-light: linux
|
||||
@./tools/package.sh linux light
|
||||
|
||||
package-linux-full: linux
|
||||
@./tools/package.sh linux full
|
||||
|
||||
package-all: windows linux
|
||||
@./tools/package.sh all
|
||||
|
||||
# Legacy target for backwards compatibility
|
||||
package-windows: package-windows-full
|
||||
|
||||
# Emscripten / WebAssembly targets
|
||||
# Requires: source ~/emsdk/emsdk_env.sh (or wherever your emsdk is installed)
|
||||
#
|
||||
# For iterative development, configure once then rebuild:
|
||||
# source ~/emsdk/emsdk_env.sh && emmake make -C build-emscripten
|
||||
#
|
||||
wasm:
|
||||
@if ! command -v emcmake >/dev/null 2>&1; then \
|
||||
echo "Error: emcmake not found. Run 'source ~/emsdk/emsdk_env.sh' first."; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ ! -f build-emscripten/Makefile ]; then \
|
||||
echo "Configuring WebAssembly build (full game)..."; \
|
||||
mkdir -p build-emscripten; \
|
||||
cd build-emscripten && emcmake cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DMCRF_SDL2=ON; \
|
||||
fi
|
||||
@echo "Building McRogueFace for WebAssembly..."
|
||||
@emmake make -C build-emscripten -j$(JOBS)
|
||||
@echo "WebAssembly dev build complete (game + REPL console)! Files in build-emscripten/"
|
||||
@echo "Run 'make serve-dev' to test locally. For the clean shipped game, use 'make wasm-game' + 'make serve'."
|
||||
|
||||
playground:
|
||||
@if ! command -v emcmake >/dev/null 2>&1; then \
|
||||
echo "Error: emcmake not found. Run 'source ~/emsdk/emsdk_env.sh' first."; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ ! -f build-playground/Makefile ]; then \
|
||||
echo "Configuring Playground build..."; \
|
||||
mkdir -p build-playground; \
|
||||
cd build-playground && emcmake cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DMCRF_SDL2=ON \
|
||||
-DMCRF_PLAYGROUND=ON; \
|
||||
fi
|
||||
@echo "Building McRogueFace Playground for WebAssembly..."
|
||||
@emmake make -C build-playground -j$(JOBS)
|
||||
@echo "Playground build complete! Files in build-playground/"
|
||||
@echo "Run 'make serve-playground' to test locally"
|
||||
|
||||
serve:
|
||||
@echo "Serving the clean game build (build-wasm-game) at http://localhost:8080"
|
||||
@echo "(Run 'make wasm-game' first if it is not built. For the REPL dev build, use 'make serve-dev'.)"
|
||||
@echo "Press Ctrl+C to stop"
|
||||
@cd build-wasm-game && python3 -m http.server 8080
|
||||
|
||||
serve-dev:
|
||||
@echo "Serving the wasm DEV build with REPL console (build-emscripten) at http://localhost:8080"
|
||||
@echo "(Run 'make wasm' first if it is not built.)"
|
||||
@echo "Press Ctrl+C to stop"
|
||||
@cd build-emscripten && python3 -m http.server 8080
|
||||
|
||||
serve-playground:
|
||||
@echo "Serving Playground build at http://localhost:8080"
|
||||
@echo "Press Ctrl+C to stop"
|
||||
@cd build-playground && python3 -m http.server 8080
|
||||
|
||||
wasm-game:
|
||||
@if ! command -v emcmake >/dev/null 2>&1; then \
|
||||
echo "Error: emcmake not found. Run 'source ~/emsdk/emsdk_env.sh' first."; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ ! -f build-wasm-game/Makefile ]; then \
|
||||
echo "Configuring WebAssembly game build (fullscreen, no REPL)..."; \
|
||||
mkdir -p build-wasm-game; \
|
||||
cd build-wasm-game && emcmake cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DMCRF_SDL2=ON \
|
||||
-DMCRF_GAME_SHELL=ON; \
|
||||
fi
|
||||
@echo "Building McRogueFace game for WebAssembly..."
|
||||
@emmake make -C build-wasm-game -j$(JOBS)
|
||||
@echo "Clean game build complete (no REPL console)! Files in build-wasm-game/"
|
||||
@echo "Run 'make serve' (or 'make serve-game') to test locally"
|
||||
|
||||
serve-game:
|
||||
@echo "Serving game build at http://localhost:8080"
|
||||
@echo "Press Ctrl+C to stop"
|
||||
@cd build-wasm-game && python3 -m http.server 8080
|
||||
|
||||
wasm-demo:
|
||||
@if ! command -v emcmake >/dev/null 2>&1; then \
|
||||
echo "Error: emcmake not found. Run 'source ~/emsdk/emsdk_env.sh' first."; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ ! -f build-wasm-demo/Makefile ]; then \
|
||||
echo "Configuring WebAssembly demo build..."; \
|
||||
mkdir -p build-wasm-demo; \
|
||||
cd build-wasm-demo && emcmake cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DMCRF_SDL2=ON \
|
||||
-DMCRF_DEMO=ON \
|
||||
-DMCRF_GAME_SHELL=ON; \
|
||||
fi
|
||||
@echo "Building McRogueFace demo for WebAssembly..."
|
||||
@emmake make -C build-wasm-demo -j$(JOBS)
|
||||
@cp web/index.html build-wasm-demo/index.html
|
||||
@echo "Demo build complete! Files in build-wasm-demo/"
|
||||
@echo "Run 'make serve-demo' to test locally"
|
||||
|
||||
serve-demo:
|
||||
@echo "Serving demo build at http://localhost:8080"
|
||||
@echo "Press Ctrl+C to stop"
|
||||
@cd build-wasm-demo && python3 -m http.server 8080
|
||||
|
||||
clean-wasm:
|
||||
@echo "Cleaning Emscripten builds..."
|
||||
@rm -rf build-emscripten build-playground build-wasm-game build-wasm-demo build-wasm-debug build-playground-debug
|
||||
|
||||
wasm-debug:
|
||||
@if ! command -v emcmake >/dev/null 2>&1; then \
|
||||
echo "Error: emcmake not found. Run 'source ~/emsdk/emsdk_env.sh' first."; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ ! -f build-wasm-debug/Makefile ]; then \
|
||||
echo "Configuring WebAssembly debug build (DWARF + source maps)..."; \
|
||||
mkdir -p build-wasm-debug; \
|
||||
cd build-wasm-debug && emcmake cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DMCRF_SDL2=ON \
|
||||
-DMCRF_WASM_DEBUG=ON; \
|
||||
fi
|
||||
@echo "Building McRogueFace for WebAssembly (debug)..."
|
||||
@emmake make -C build-wasm-debug -j$(JOBS)
|
||||
@echo "Debug WASM build complete! Files in build-wasm-debug/"
|
||||
@echo "Debug artifacts: .wasm.map (source map), .symbols (symbol map)"
|
||||
@echo "Run 'make serve-wasm-debug' to test locally"
|
||||
|
||||
serve-wasm-debug:
|
||||
@echo "Serving debug WASM build at http://localhost:8080"
|
||||
@echo "Press Ctrl+C to stop"
|
||||
@cd build-wasm-debug && python3 -m http.server 8080
|
||||
|
||||
playground-debug:
|
||||
@if ! command -v emcmake >/dev/null 2>&1; then \
|
||||
echo "Error: emcmake not found. Run 'source ~/emsdk/emsdk_env.sh' first."; \
|
||||
exit 1; \
|
||||
fi
|
||||
@if [ ! -f build-playground-debug/Makefile ]; then \
|
||||
echo "Configuring Playground debug build (DWARF + source maps)..."; \
|
||||
mkdir -p build-playground-debug; \
|
||||
cd build-playground-debug && emcmake cmake .. \
|
||||
-DCMAKE_BUILD_TYPE=Debug \
|
||||
-DMCRF_SDL2=ON \
|
||||
-DMCRF_PLAYGROUND=ON \
|
||||
-DMCRF_WASM_DEBUG=ON; \
|
||||
fi
|
||||
@echo "Building McRogueFace Playground for WebAssembly (debug)..."
|
||||
@emmake make -C build-playground-debug -j$(JOBS)
|
||||
@echo "Playground debug build complete! Files in build-playground-debug/"
|
||||
@echo "Run 'make serve-playground-debug' to test locally"
|
||||
|
||||
serve-playground-debug:
|
||||
@echo "Serving debug Playground build at http://localhost:8080"
|
||||
@echo "Press Ctrl+C to stop"
|
||||
@cd build-playground-debug && python3 -m http.server 8080
|
||||
|
||||
# Current version extracted from source
|
||||
CURRENT_VERSION := $(shell grep 'MCRFPY_VERSION' src/McRogueFaceVersion.h | sed 's/.*"\(.*\)"/\1/')
|
||||
|
||||
# Release workflow: tag current version, build all packages, bump to next version
|
||||
# Usage: make version-bump NEXT_VERSION=0.2.6-prerelease-7drl2026
|
||||
version-bump:
|
||||
ifndef NEXT_VERSION
|
||||
$(error Usage: make version-bump NEXT_VERSION=x.y.z-suffix)
|
||||
endif
|
||||
@if ! command -v emcmake >/dev/null 2>&1; then \
|
||||
echo "Error: emcmake not found. Run 'source ~/emsdk/emsdk_env.sh' first."; \
|
||||
exit 1; \
|
||||
fi
|
||||
# git status (clean working dir check), but ignore modules/, because building submodules dirties their subdirs
|
||||
@if [ -n "$$(git status --porcelain | grep -v modules)" ]; then \
|
||||
echo "Error: Working tree is not clean. Commit or stash changes first."; \
|
||||
exit 1; \
|
||||
fi
|
||||
@echo "=== Releasing $(CURRENT_VERSION) ==="
|
||||
@# Idempotent tag: ok if it already points at HEAD (resuming partial run)
|
||||
@if git rev-parse "$(CURRENT_VERSION)" >/dev/null 2>&1; then \
|
||||
TAG_COMMIT=$$(git rev-parse "$(CURRENT_VERSION)^{}"); \
|
||||
HEAD_COMMIT=$$(git rev-parse HEAD); \
|
||||
if [ "$$TAG_COMMIT" != "$$HEAD_COMMIT" ]; then \
|
||||
echo "Error: Tag $(CURRENT_VERSION) already exists but points to a different commit."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
echo "Tag $(CURRENT_VERSION) already exists at HEAD (resuming)."; \
|
||||
else \
|
||||
git tag "$(CURRENT_VERSION)"; \
|
||||
fi
|
||||
$(MAKE) package-linux-full
|
||||
$(MAKE) package-windows-full
|
||||
$(MAKE) wasm-game
|
||||
@echo "Packaging WASM build (clean game, no REPL console)..."
|
||||
@mkdir -p dist
|
||||
cd build-wasm-game && zip -r ../dist/McRogueFace-$(CURRENT_VERSION)-WASM.zip \
|
||||
mcrogueface.html mcrogueface.js mcrogueface.wasm mcrogueface.data
|
||||
@echo ""
|
||||
@echo "Bumping version: $(CURRENT_VERSION) -> $(NEXT_VERSION)"
|
||||
@sed -i 's|MCRFPY_VERSION "$(CURRENT_VERSION)"|MCRFPY_VERSION "$(NEXT_VERSION)"|' src/McRogueFaceVersion.h
|
||||
@TAGGED_HASH=$$(git rev-parse --short HEAD); \
|
||||
git add src/McRogueFaceVersion.h && \
|
||||
git commit -m "Version bump: $(CURRENT_VERSION) ($$TAGGED_HASH) -> $(NEXT_VERSION)"
|
||||
@echo ""
|
||||
@echo "=== Release $(CURRENT_VERSION) complete ==="
|
||||
@echo "Tag: $(CURRENT_VERSION)"
|
||||
@echo "Next: $(NEXT_VERSION)"
|
||||
@echo "Packages:"
|
||||
@ls -lh dist/*$(CURRENT_VERSION)* 2>/dev/null
|
||||
93
PHASE_1_2_3_COMPLETION_SUMMARY.md
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# Phase 1-3 Completion Summary
|
||||
|
||||
## Overview
|
||||
Successfully completed all tasks in Phases 1, 2, and 3 of the alpha_streamline_2 branch. This represents a major architectural improvement to McRogueFace's Python API, making it more consistent, safer, and feature-rich.
|
||||
|
||||
## Phase 1: Architecture Stabilization (Completed)
|
||||
- ✅ #7 - Audited and fixed unsafe constructors across all UI classes
|
||||
- ✅ #71 - Implemented _Drawable base class properties at C++ level
|
||||
- ✅ #87 - Added visible property for show/hide functionality
|
||||
- ✅ #88 - Added opacity property for transparency control
|
||||
- ✅ #89 - Added get_bounds() method returning (x, y, width, height)
|
||||
- ✅ #98 - Added move()/resize() methods for dynamic UI manipulation
|
||||
|
||||
## Phase 2: API Enhancements (Completed)
|
||||
- ✅ #101 - Standardized default positions (all UI elements default to 0,0)
|
||||
- ✅ #38 - Frame accepts children parameter in constructor
|
||||
- ✅ #42 - All UI elements accept click handler in __init__
|
||||
- ✅ #90 - Grid accepts size as tuple: Grid((20, 15))
|
||||
- ✅ #19 - Sprite texture swapping via texture property
|
||||
- ✅ #52 - Grid rendering skips out-of-bounds entities
|
||||
|
||||
## Phase 3: Game-Ready Features (Completed)
|
||||
- ✅ #30 - Entity.die() method for proper cleanup
|
||||
- ✅ #93 - Vector arithmetic operators (+, -, *, /, ==, bool, abs, neg)
|
||||
- ✅ #94 - Color helper methods (from_hex, to_hex, lerp)
|
||||
- ✅ #103 - Timer objects with pause/resume/cancel functionality
|
||||
|
||||
## Additional Improvements
|
||||
- ✅ Standardized position arguments across all UI classes
|
||||
- Created PyPositionHelper for consistent argument parsing
|
||||
- All classes now accept: (x, y), pos=(x,y), x=x, y=y formats
|
||||
- ✅ Fixed UTF-8 encoding configuration for Python output
|
||||
- Configured PyConfig.stdio_encoding during initialization
|
||||
- Resolved unicode character printing issues
|
||||
|
||||
## Technical Achievements
|
||||
|
||||
### Architecture
|
||||
- Safe two-phase initialization for all Python objects
|
||||
- Consistent constructor patterns across UI hierarchy
|
||||
- Proper shared_ptr lifetime management
|
||||
- Clean separation between C++ implementation and Python API
|
||||
|
||||
### API Consistency
|
||||
- All UI elements follow same initialization patterns
|
||||
- Position arguments work uniformly across all classes
|
||||
- Properties accessible via standard Python attribute access
|
||||
- Methods follow Python naming conventions
|
||||
|
||||
### Developer Experience
|
||||
- Intuitive object construction with sensible defaults
|
||||
- Flexible argument formats reduce boilerplate
|
||||
- Clear error messages for invalid inputs
|
||||
- Comprehensive test coverage for all features
|
||||
|
||||
## Impact on Game Development
|
||||
|
||||
### Before
|
||||
```python
|
||||
# Inconsistent, error-prone API
|
||||
frame = mcrfpy.Frame()
|
||||
frame.x = 100 # Had to set position after creation
|
||||
frame.y = 50
|
||||
caption = mcrfpy.Caption(mcrfpy.default_font, "Hello", 20, 20) # Different argument order
|
||||
grid = mcrfpy.Grid(10, 10, 32, 32, 0, 0) # Confusing parameter order
|
||||
```
|
||||
|
||||
### After
|
||||
```python
|
||||
# Clean, consistent API
|
||||
frame = mcrfpy.Frame(x=100, y=50, children=[
|
||||
mcrfpy.Caption("Hello", pos=(20, 20)),
|
||||
mcrfpy.Sprite("icon.png", (10, 10))
|
||||
])
|
||||
grid = mcrfpy.Grid(size=(10, 10), pos=(0, 0))
|
||||
|
||||
# Advanced features
|
||||
timer = mcrfpy.Timer("animation", update_frame, 16)
|
||||
timer.pause() # Pause during menu
|
||||
timer.resume() # Resume when gameplay continues
|
||||
|
||||
player.move(velocity * delta_time) # Vector math works naturally
|
||||
ui_theme = mcrfpy.Color.from_hex("#2D3436")
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
With Phases 1-3 complete, the codebase is ready for:
|
||||
- Phase 4: Event System & Animations (advanced interactivity)
|
||||
- Phase 5: Scene Management (transitions, lifecycle)
|
||||
- Phase 6: Audio System (procedural generation, effects)
|
||||
- Phase 7: Optimization (sprite batching, profiling)
|
||||
|
||||
The foundation is now solid for building sophisticated roguelike games with McRogueFace.
|
||||
222
README.md
|
|
@ -3,221 +3,79 @@
|
|||
|
||||
A Python-powered 2D game engine for creating roguelike games, built with C++ and SFML.
|
||||
|
||||
* Core roguelike logic from libtcod: field of view, pathfinding
|
||||
* Animate sprites with multiple frames. Smooth transitions for positions, sizes, zoom, and camera
|
||||
* Simple GUI element system allows keyboard and mouse input, composition
|
||||
* No compilation or installation necessary. The runtime is a full Python environment; "Zip And Ship"
|
||||
**Pre-Alpha Release Demo**: my 7DRL 2025 entry *"Crypt of Sokoban"* - a prototype with buttons, boulders, enemies, and items.
|
||||
|
||||
📖 **[Full Documentation & Tutorials](https://mcrogueface.github.io/)** - Quickstart guide, API reference, and cookbook
|
||||
## Tenets
|
||||
|
||||
- **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
|
||||
|
||||
## Quick Start
|
||||
|
||||
**Download** the [latest release](https://github.com/jmccardle/McRogueFace/releases/latest):
|
||||
- **Windows**: `McRogueFace-*-Win.zip`
|
||||
- **Linux**: `McRogueFace-*-Linux.tar.bz2`
|
||||
```bash
|
||||
# Clone and build
|
||||
git clone <wherever you found this repo>
|
||||
cd McRogueFace
|
||||
make
|
||||
|
||||
Extract and run `mcrogueface` (or `mcrogueface.exe` on Windows) to see the demo game.
|
||||
# Run the example game
|
||||
cd build
|
||||
./mcrogueface
|
||||
```
|
||||
|
||||
### Your First Game
|
||||
|
||||
Create `scripts/game.py` (or edit the existing one):
|
||||
## Example: Creating a Simple Scene
|
||||
|
||||
```python
|
||||
import mcrfpy
|
||||
|
||||
# Create and activate a scene
|
||||
scene = mcrfpy.Scene("game")
|
||||
scene.activate()
|
||||
# Create a new scene
|
||||
mcrfpy.createScene("intro")
|
||||
|
||||
# Load a sprite sheet
|
||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
||||
# Add a text caption
|
||||
caption = mcrfpy.Caption((50, 50), "Welcome to McRogueFace!")
|
||||
caption.size = 48
|
||||
caption.fill_color = (255, 255, 255)
|
||||
|
||||
# Create a tile grid
|
||||
grid = mcrfpy.Grid(grid_size=(20, 15), texture=texture, pos=(50, 50), size=(640, 480))
|
||||
grid.zoom = 2.0
|
||||
scene.children.append(grid)
|
||||
# Add to scene
|
||||
mcrfpy.sceneUI("intro").append(caption)
|
||||
|
||||
# Add a player entity
|
||||
player = mcrfpy.Entity(pos=(10, 7), texture=texture, sprite_index=84)
|
||||
grid.entities.append(player)
|
||||
|
||||
# Handle keyboard input
|
||||
def on_key(key, state):
|
||||
if state != "start":
|
||||
return
|
||||
x, y = int(player.x), int(player.y)
|
||||
if key == "W": y -= 1
|
||||
elif key == "S": y += 1
|
||||
elif key == "A": x -= 1
|
||||
elif key == "D": x += 1
|
||||
player.x, player.y = x, y
|
||||
|
||||
scene.on_key = on_key
|
||||
```
|
||||
|
||||
Run `mcrogueface` and you have a movable character!
|
||||
|
||||
### Visual Framework
|
||||
|
||||
- **Sprite**: Single image or sprite from a shared sheet
|
||||
- **Caption**: Text rendering with fonts
|
||||
- **Frame**: Container rectangle for composing UIs
|
||||
- **Grid**: 2D tile array with zoom and camera control
|
||||
- **Entity**: Grid-based game object with sprite and pathfinding
|
||||
- **Animation**: Interpolate any property over time with easing
|
||||
|
||||
## Building from Source
|
||||
|
||||
For most users, pre-built releases are available. If you need to build from source:
|
||||
|
||||
### Quick Build (with pre-built dependencies)
|
||||
|
||||
Download `build_deps.tar.gz` from the releases page, then:
|
||||
|
||||
```bash
|
||||
git clone <repository-url> McRogueFace
|
||||
cd McRogueFace
|
||||
tar -xzf /path/to/build_deps.tar.gz
|
||||
mkdir build && cd build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release
|
||||
make -j$(nproc)
|
||||
```
|
||||
|
||||
### Full Build (compiling all dependencies)
|
||||
|
||||
```bash
|
||||
git clone --recursive <repository-url> McRogueFace
|
||||
cd McRogueFace
|
||||
# See BUILD_FROM_SOURCE.md for complete instructions
|
||||
```
|
||||
|
||||
**[BUILD_FROM_SOURCE.md](BUILD_FROM_SOURCE.md)** - Complete build guide including:
|
||||
- System dependency installation
|
||||
- Compiling SFML, Python, and libtcod-headless from source
|
||||
- Creating `build_deps` archives for distribution
|
||||
- Troubleshooting common build issues
|
||||
|
||||
### System Requirements
|
||||
|
||||
- **Linux**: Debian/Ubuntu tested; other distros should work
|
||||
- **Windows**: Supported (see build guide for details)
|
||||
- **macOS**: Untested
|
||||
|
||||
## Example: Main Menu with Buttons
|
||||
|
||||
```python
|
||||
import mcrfpy
|
||||
|
||||
# Create a scene
|
||||
scene = mcrfpy.Scene("menu")
|
||||
|
||||
# Add a background frame
|
||||
bg = mcrfpy.Frame(pos=(0, 0), size=(1024, 768),
|
||||
fill_color=mcrfpy.Color(20, 20, 40))
|
||||
scene.children.append(bg)
|
||||
|
||||
# Add a title
|
||||
title = mcrfpy.Caption(pos=(312, 100), text="My Roguelike",
|
||||
fill_color=mcrfpy.Color(255, 255, 100))
|
||||
title.font_size = 48
|
||||
scene.children.append(title)
|
||||
|
||||
# Create a button
|
||||
button = mcrfpy.Frame(pos=(362, 300), size=(300, 80),
|
||||
fill_color=mcrfpy.Color(50, 150, 50))
|
||||
button_text = mcrfpy.Caption(pos=(90, 25), text="Start Game")
|
||||
button.children.append(button_text)
|
||||
|
||||
def on_click(x, y, btn):
|
||||
print("Game starting!")
|
||||
|
||||
button.on_click = on_click
|
||||
scene.children.append(button)
|
||||
|
||||
scene.activate()
|
||||
# Switch to the scene
|
||||
mcrfpy.setScene("intro")
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
### 📚 Developer Documentation
|
||||
For comprehensive documentation, tutorials, and API reference, visit:
|
||||
**[https://mcrogueface.github.io](https://mcrogueface.github.io)**
|
||||
|
||||
For comprehensive documentation about systems, architecture, and development workflows:
|
||||
|
||||
**[Project Wiki](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki)**
|
||||
|
||||
Key wiki pages:
|
||||
|
||||
- **[Home](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Home)** - Documentation hub with multiple entry points
|
||||
- **[Grid System](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Grid-System)** - Three-layer grid architecture
|
||||
- **[Python Binding System](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Python-Binding-System)** - C++/Python integration
|
||||
- **[Performance and Profiling](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Performance-and-Profiling)** - Optimization tools
|
||||
- **[Adding Python Bindings](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Adding-Python-Bindings)** - Step-by-step binding guide
|
||||
- **[Issue Roadmap](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Issue-Roadmap)** - All open issues organized by system
|
||||
|
||||
### 📖 Development Guides
|
||||
|
||||
In the repository root:
|
||||
|
||||
- **[CLAUDE.md](CLAUDE.md)** - Build instructions, testing guidelines, common tasks
|
||||
- **[ROADMAP.md](ROADMAP.md)** - Strategic vision and development phases
|
||||
- **[docs/api-stability.md](docs/api-stability.md)** - 1.0 compatibility policy (value semantics, bulk-edit convention, subinterpreter exclusion)
|
||||
- **[docs/threading-model.md](docs/threading-model.md)** - Threading contract for off-main-thread access via `mcrfpy.lock()`
|
||||
- **[roguelike_tutorial/](roguelike_tutorial/)** - Complete roguelike tutorial implementations
|
||||
|
||||
> **Note:** Running mcrfpy in a Python subinterpreter is unsupported in 1.x; the module declares `m_size = -1` and will refuse or misbehave. See [#220](https://dev.ffwf.net/forgejo/john/McRogueFace/issues/220).
|
||||
|
||||
## Build Requirements
|
||||
## Requirements
|
||||
|
||||
- C++17 compiler (GCC 7+ or Clang 5+)
|
||||
- CMake 3.14+
|
||||
- Python 3.14 (embedded)
|
||||
- SFML 2.6
|
||||
- Python 3.12+
|
||||
- SFML 2.5+
|
||||
- Linux or Windows (macOS untested)
|
||||
|
||||
See [BUILD_FROM_SOURCE.md](BUILD_FROM_SOURCE.md) for detailed compilation instructions.
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
McRogueFace/
|
||||
├── assets/ # Sprites, fonts, audio
|
||||
├── build/ # Build output: this is what you distribute
|
||||
│ ├── assets/ # (copied from assets/)
|
||||
│ ├── scripts/ # (copied from src/scripts/)
|
||||
│ └── lib/ # Python stdlib and extension modules
|
||||
├── docs/ # Generated HTML, markdown API docs
|
||||
├── src/ # C++ engine source
|
||||
│ └── scripts/ # Python game scripts
|
||||
├── stubs/ # .pyi type stubs for IDE integration
|
||||
├── tests/ # Automated test suite
|
||||
└── tools/ # Documentation generation scripts
|
||||
├── scripts/ # Python game scripts
|
||||
├── assets/ # Sprites, fonts, audio
|
||||
├── build/ # Build output directory
|
||||
└── tests/ # Automated test suite
|
||||
```
|
||||
|
||||
If you are building McRogueFace to implement game logic or scene configuration in C++, you'll have to compile the project.
|
||||
|
||||
If you are writing a game in Python using McRogueFace, you only need to rename and zip/distribute the `build` directory.
|
||||
|
||||
## Philosophy
|
||||
|
||||
- **C++ every frame, Python every tick**: All rendering data is handled in C++. Structure your UI and program animations in Python, and they are rendered without Python. All game logic can be written in Python.
|
||||
- **No Compiling Required; Zip And Ship**: Implement your game objects with Python, zip up McRogueFace with your "game.py" to ship
|
||||
- **Built-in Roguelike Support**: Dungeon generation, pathfinding, and field-of-view via libtcod
|
||||
- **Hands-Off Testing**: 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
|
||||
|
||||
## 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.
|
||||
|
||||
### Issue Tracking
|
||||
|
||||
The project uses [Forgejo Issues](https://dev.ffwf.net/forgejo/john/McRogueFace/issues) for task tracking and bug reports. Issues are organized with labels:
|
||||
|
||||
- **System labels** (grid, animation, python-binding, etc.) - identify which codebase area
|
||||
- **Priority labels** (tier1-active, tier2-foundation, tier3-future) - development timeline
|
||||
- **Type labels** (Major Feature, Minor Feature, Bugfix, etc.) - effort and scope
|
||||
|
||||
See the [Issue Roadmap](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Issue-Roadmap) on the wiki for organized view of all open tasks.
|
||||
The project has a private roadmap and issue list. Reach out via email or social media if you have bugs or feature requests.
|
||||
|
||||
## License
|
||||
|
||||
|
|
@ -225,6 +83,6 @@ This project is licensed under the MIT License - see LICENSE file for details.
|
|||
|
||||
## Acknowledgments
|
||||
|
||||
- Developed for 7-Day Roguelike 2023, 2024, 2025, 2026 - here's to many more
|
||||
- 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
|
||||
|
|
|
|||
167
RENDERTEXTURE_DESIGN.md
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
# RenderTexture Overhaul Design Document
|
||||
|
||||
## Overview
|
||||
|
||||
This document outlines the design for implementing RenderTexture support across all UIDrawable classes in McRogueFace. This is Issue #6 and represents a major architectural change to the rendering system.
|
||||
|
||||
## Goals
|
||||
|
||||
1. **Automatic Clipping**: Children rendered outside parent bounds should be clipped
|
||||
2. **Off-screen Rendering**: Enable post-processing effects and complex compositing
|
||||
3. **Performance**: Cache static content, only re-render when changed
|
||||
4. **Backward Compatibility**: Existing code should continue to work
|
||||
|
||||
## Current State
|
||||
|
||||
### Classes Already Using RenderTexture:
|
||||
- **UIGrid**: Uses a 1920x1080 RenderTexture for compositing grid view
|
||||
- **SceneTransition**: Uses two 1024x768 RenderTextures for transitions
|
||||
- **HeadlessRenderer**: Uses RenderTexture for headless mode
|
||||
|
||||
### Classes Using Direct Rendering:
|
||||
- **UIFrame**: Renders box and children directly
|
||||
- **UICaption**: Renders text directly
|
||||
- **UISprite**: Renders sprite directly
|
||||
|
||||
## Design Decisions
|
||||
|
||||
### 1. Opt-in Architecture
|
||||
|
||||
Not all UIDrawables need RenderTextures. We'll use an opt-in approach:
|
||||
|
||||
```cpp
|
||||
class UIDrawable {
|
||||
protected:
|
||||
// RenderTexture support (opt-in)
|
||||
std::unique_ptr<sf::RenderTexture> render_texture;
|
||||
sf::Sprite render_sprite;
|
||||
bool use_render_texture = false;
|
||||
bool render_dirty = true;
|
||||
|
||||
// Enable RenderTexture for this drawable
|
||||
void enableRenderTexture(unsigned int width, unsigned int height);
|
||||
void updateRenderTexture();
|
||||
};
|
||||
```
|
||||
|
||||
### 2. When to Use RenderTexture
|
||||
|
||||
RenderTextures will be enabled for:
|
||||
1. **UIFrame with clipping enabled** (new property: `clip_children = true`)
|
||||
2. **UIDrawables with effects** (future: shaders, blend modes)
|
||||
3. **Complex composites** (many children that rarely change)
|
||||
|
||||
### 3. Render Flow
|
||||
|
||||
```
|
||||
Standard Flow:
|
||||
render() → render directly to target
|
||||
|
||||
RenderTexture Flow:
|
||||
render() → if dirty → clear RT → render to RT → dirty = false
|
||||
→ draw RT sprite to target
|
||||
```
|
||||
|
||||
### 4. Dirty Flag Management
|
||||
|
||||
Mark as dirty when:
|
||||
- Properties change (position, size, color, etc.)
|
||||
- Children added/removed
|
||||
- Child marked as dirty (propagate up)
|
||||
- Animation frame
|
||||
|
||||
### 5. Size Management
|
||||
|
||||
RenderTexture size options:
|
||||
1. **Fixed Size**: Set at creation (current UIGrid approach)
|
||||
2. **Dynamic Size**: Match bounds, recreate on resize
|
||||
3. **Pooled Sizes**: Use standard sizes from pool
|
||||
|
||||
We'll use **Dynamic Size** with lazy creation.
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Base Infrastructure (This PR)
|
||||
1. Add RenderTexture members to UIDrawable
|
||||
2. Add `enableRenderTexture()` method
|
||||
3. Implement dirty flag system
|
||||
4. Add `clip_children` property to UIFrame
|
||||
|
||||
### Phase 2: UIFrame Implementation
|
||||
1. Update UIFrame::render() to use RenderTexture when clipping
|
||||
2. Test with nested frames
|
||||
3. Verify clipping works correctly
|
||||
|
||||
### Phase 3: Performance Optimization
|
||||
1. Implement texture pooling
|
||||
2. Add dirty flag propagation
|
||||
3. Profile and optimize
|
||||
|
||||
### Phase 4: Extended Features
|
||||
1. Blur/glow effects using RenderTexture
|
||||
2. Viewport-based rendering (#8)
|
||||
3. Screenshot improvements
|
||||
|
||||
## API Changes
|
||||
|
||||
### Python API:
|
||||
```python
|
||||
# Enable clipping on frames
|
||||
frame.clip_children = True # New property
|
||||
|
||||
# Future: effects
|
||||
frame.blur_amount = 5.0
|
||||
sprite.glow_color = Color(255, 200, 100)
|
||||
```
|
||||
|
||||
### C++ API:
|
||||
```cpp
|
||||
// Enable RenderTexture
|
||||
frame->enableRenderTexture(width, height);
|
||||
frame->setClipChildren(true);
|
||||
|
||||
// Mark dirty
|
||||
frame->markDirty();
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
1. **Memory**: Each RenderTexture uses GPU memory (width * height * 4 bytes)
|
||||
2. **Creation Cost**: Creating RenderTextures is expensive, use pooling
|
||||
3. **Clear Cost**: Clearing large RenderTextures each frame is costly
|
||||
4. **Bandwidth**: Drawing to RenderTexture then to screen doubles bandwidth
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
1. All existing code continues to work (direct rendering by default)
|
||||
2. Gradually enable RenderTexture for specific use cases
|
||||
3. Profile before/after to ensure performance gains
|
||||
4. Document best practices
|
||||
|
||||
## Risks and Mitigation
|
||||
|
||||
| Risk | Mitigation |
|
||||
|------|------------|
|
||||
| Performance regression | Opt-in design, profile extensively |
|
||||
| Memory usage increase | Texture pooling, size limits |
|
||||
| Complexity increase | Clear documentation, examples |
|
||||
| Integration issues | Extensive testing with SceneTransition |
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. ✓ Frames can clip children to bounds
|
||||
2. ✓ No performance regression for direct rendering
|
||||
3. ✓ Scene transitions continue to work
|
||||
4. ✓ Memory usage is reasonable
|
||||
5. ✓ API is intuitive and documented
|
||||
|
||||
## Future Extensions
|
||||
|
||||
1. **Shader Support** (#106): RenderTextures enable post-processing shaders
|
||||
2. **Particle Systems** (#107): Render particles to texture for effects
|
||||
3. **Caching**: Static UI elements cached in RenderTextures
|
||||
4. **Resolution Independence**: RenderTextures for DPI scaling
|
||||
|
||||
## Conclusion
|
||||
|
||||
This design provides a foundation for professional rendering capabilities while maintaining backward compatibility and performance. The opt-in approach allows gradual adoption and testing.
|
||||
871
ROADMAP.md
|
|
@ -1,154 +1,803 @@
|
|||
# McRogueFace - Development Roadmap
|
||||
|
||||
**Version**: 0.2.8 | **Era**: McRogueFace (2D roguelikes) -- on the road to 1.0
|
||||
## 🚨 URGENT PRIORITIES - July 9, 2025 🚨
|
||||
|
||||
For detailed architecture, philosophy, and decision framework, see the [Strategic Direction](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Strategic-Direction) wiki page. For per-issue tracking, see the [Issue Roadmap](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Issue-Roadmap).
|
||||
### IMMEDIATE ACTION REQUIRED (Next 48 Hours)
|
||||
|
||||
**CRITICAL DEADLINE**: RoguelikeDev Tutorial Event starts July 15 - Need to advertise by July 11!
|
||||
|
||||
#### 1. Tutorial Emergency Plan (2 DAYS)
|
||||
- [ ] **Day 1 (July 9)**: Parts 1-2 (Setup, Moving @, Drawing Map, Entities)
|
||||
- [ ] **Day 2 (July 10)**: Parts 3-4 (FOV, Combat/AI)
|
||||
- [ ] **July 11**: Announce on r/roguelikedev with 4 completed parts
|
||||
- [ ] **July 12-14**: Complete remaining 10 parts before event starts
|
||||
|
||||
#### 1b. Sizzle Reel Demo (URGENT)
|
||||
- [ ] **Expand animation_sizzle_reel_working.py** with Grid/Entity demos:
|
||||
- Grid scrolling and zooming animations
|
||||
- Entity movement patterns (patrol, chase, flee)
|
||||
- Particle effects using entity spawning
|
||||
- Tile animation demonstrations
|
||||
- Color cycling and transparency effects
|
||||
- Mass entity choreography (100+ entities)
|
||||
- Performance stress test with 1000+ entities
|
||||
|
||||
#### 2. TCOD Integration Sprint ✅ COMPLETE!
|
||||
- [x] **UIGrid TCOD Integration** (8 hours) ✅ COMPLETED!
|
||||
- ✅ Add TCODMap* to UIGrid constructor with proper lifecycle
|
||||
- ✅ Implement complete Dijkstra pathfinding system
|
||||
- ✅ Create mcrfpy.libtcod submodule with Python bindings
|
||||
- ✅ Fix critical PyArg bug preventing Color object assignments
|
||||
- ✅ Implement FOV with perspective rendering
|
||||
- [ ] Add batch operations for NumPy-style access (deferred)
|
||||
- [ ] Create CellView for ergonomic .at((x,y)) access (deferred)
|
||||
- [x] **UIEntity Pathfinding** (4 hours) ✅ COMPLETED!
|
||||
- ✅ Implement Dijkstra maps for multiple targets in UIGrid
|
||||
- ✅ Add path_to(target) method using A* to UIEntity
|
||||
- ✅ Cache paths in UIEntity for performance
|
||||
|
||||
#### 3. Performance Critical Path
|
||||
- [ ] **Implement SpatialHash** for 10,000+ entities (2 hours)
|
||||
- [ ] **Add dirty flag system** to UIGrid (1 hour)
|
||||
- [ ] **Batch update context managers** (2 hours)
|
||||
- [ ] **Memory pool for entities** (2 hours)
|
||||
|
||||
#### 4. Bug Fixing Pipeline
|
||||
- [ ] Set up GitHub Issues automation
|
||||
- [ ] Create test for each bug before fixing
|
||||
- [ ] Track: Memory leaks, Segfaults, Python/C++ boundary errors
|
||||
|
||||
---
|
||||
|
||||
## What Has Shipped
|
||||
## 🎯 STRATEGIC ARCHITECTURE VISION
|
||||
|
||||
**Alpha 0.1** (2024) -- First complete release. Milestone: all datatypes behaving.
|
||||
### Three-Layer Grid Architecture (From Compass Research)
|
||||
Following successful roguelike patterns (Caves of Qud, Cogmind, DCSS):
|
||||
|
||||
**0.2 series** (Jan-Mar 2026) -- Weekly updates to GitHub. Key additions:
|
||||
- 3D/Voxel pipeline (experimental): Viewport3D, Camera3D, Entity3D, VoxelGrid with greedy meshing and serialization
|
||||
- Procedural generation: HeightMap, BSP, NoiseSource, DiscreteMap
|
||||
- Tiled and LDtk import with Wang tile / AutoRule resolution
|
||||
- Emscripten/SDL2 backend for WebAssembly deployment
|
||||
- Animation callbacks, mouse event system, grid cell callbacks
|
||||
- Multi-layer grid system with chunk-based rendering and dirty-flag caching
|
||||
- Documentation macro system with auto-generated API docs, man pages, and type stubs
|
||||
- Windows cross-compilation, mobile-ish WASM support, SDL2_mixer audio
|
||||
- Behavior/Trigger turn manager: `grid.step()`, entity labels, `cell_pos`, Dijkstra-backed pathfinding (#295-#303)
|
||||
1. **Visual Layer** (UIGridPoint) - Sprites, colors, animations
|
||||
2. **World State Layer** (TCODMap) - Walkability, transparency, physics
|
||||
3. **Entity Perspective Layer** (UIGridPointState) - Per-entity FOV, knowledge
|
||||
|
||||
**Proving grounds**: Crypt of Sokoban (7DRL 2025), then 7DRL 2026 -- both shipped on the same engine. The 2026 jam surfaced hotfix-worthy issues (SDL key scancodes, composite textures) that have since landed on master.
|
||||
### Performance Architecture (Critical for 1000x1000 maps)
|
||||
- **Spatial Hashing** for entity queries (not quadtrees!)
|
||||
- **Batch Operations** with context managers (10-100x speedup)
|
||||
- **Memory Pooling** for entities and components
|
||||
- **Dirty Flag System** to avoid unnecessary updates
|
||||
- **Zero-Copy NumPy Integration** via buffer protocol
|
||||
|
||||
### Key Insight from Research
|
||||
"Minimizing Python/C++ boundary crossings matters more than individual function complexity"
|
||||
- Batch everything possible
|
||||
- Use context managers for logical operations
|
||||
- Expose arrays, not individual cells
|
||||
- Profile and optimize hot paths only
|
||||
|
||||
---
|
||||
|
||||
## Current Focus: API Freeze + Memory Safety Sweep
|
||||
## Project Status: 🎉 ALPHA 0.1 RELEASE! 🎉
|
||||
|
||||
7DRL 2026 is behind us (Feb 28 -- Mar 8). The engine has two concurrent tracks to 1.0:
|
||||
|
||||
### Track 1: API Freeze
|
||||
The process is underway. Closed in this pass: camelCase module functions (#304), deprecated `sprite_number` (#305), legacy string enum comparisons (#306), `Color.__eq__`/`__ne__` (#307), `Grid.position` alias (#308). The freeze decisions are now locked behind a public API-surface snapshot regression test (#314), so accidental signature drift fails CI.
|
||||
|
||||
Remaining freeze work:
|
||||
1. Catalog every public Python class, method, and property -- audit against `stubs/mcrfpy.pyi` and generated docs (snapshot test now enforces the catalog)
|
||||
2. Identify any last naming/signature/default changes before committing
|
||||
3. Final breaking-change pass, bundled
|
||||
4. Document the stable API as the contract
|
||||
5. Experimental modules (3D/Voxel) stay out of the freeze with an `experimental` label
|
||||
|
||||
### Track 2: Fuzz-Driven Bug Sweep
|
||||
The libFuzzer+ASan harness (#283) has nine work tranches merged: build plumbing (W1), native harness (W2/W3), then six targeted fuzzers under `tests/fuzz/`:
|
||||
- `fuzz_grid_entity` -- EntityCollection lifetime (W4, fixed #258-#263, #273, #274)
|
||||
- `fuzz_property_types` -- refcount / type confusion (W5, fixed #267, #268, #272)
|
||||
- `fuzz_anim_timer_scene` -- animation/timer/scene lifecycles (W6)
|
||||
- `fuzz_fov` -- compute_fov parameters (W8, fixed #310)
|
||||
- `fuzz_maps_procgen` -- HeightMap/DiscreteMap interfaces (W7)
|
||||
- `fuzz_pathfinding_behavior` -- Dijkstra + turn manager (W9, fixed #311)
|
||||
|
||||
Coverage extension (#312) added four more: `fuzz_audio_dsp` (SoundBuffer DSP), `fuzz_import_parsers` (Tiled/LDtk file parsers), `fuzz_texture_factory` (byte ingestion), `fuzz_shader_bindings` (uniform-binding lifetime), plus Tier C surface folded into the existing targets. That run found five new bugs: #321 (HIGH -- ColorLayer.draw_fov bad-free), #322 (WangSet.terrain_enum error-pending abort), #323/#324/#325 (float→int UB in pitch_shift/hsl_shift/Vector) -- all five now fixed and merged, see Recently Shipped.
|
||||
|
||||
### Recently Shipped (April 2026)
|
||||
- **#294** -- `entity.perspective_map` replaces flat `vector<UIGridPointState>` with a 3-state DiscreteMap (UNKNOWN/DISCOVERED/VISIBLE). Per-entity FOV memory is now serializable, swappable, and structurally enforces visible-as-subset-of-discovered.
|
||||
- **#315** -- Pathfinding API extended with built-in heuristics (Euclidean/Manhattan/Chebyshev/Diagonal/Zero), multi-root Dijkstra, FLEE primitives (invert + descent), and an interactive demo. EntityBehavior SEEK/FLEE refactored to a `PathProvider` strategy.
|
||||
- **Phase 5.2** -- six performance benchmark scripts under `tests/benchmarks/` covering grid.step(), FOV writeback cost, spatial hash vs. O(n), pathfinding with collision labels, multi-GridView render, and Dijkstra variants. Baselines under `tests/benchmarks/baseline/phase5_2/`.
|
||||
- **Phase 5.3** -- documentation regenerated; `tools/generate_stubs_v2.py` rewritten as introspection-based so it can no longer drift from the C++ source.
|
||||
|
||||
### Recently Shipped (July 2026)
|
||||
- **Grid input revival + the GridData/GridView type split (#355, #357-#368, #370, #371)** -- merged to master as `36e74e0`; 15 issues closed. Grid input had been dead since the #252 GridView unification: cell callbacks never fired, grid children were unclickable, cell hover was stuck. The cause was structural -- `UIGrid` inherited *both* `UIDrawable` and `GridData`, so every map secretly carried a second camera and RenderTexture that nothing on screen corresponded to, and input, children, and dirty propagation all dead-ended in it. **Input fix (#355, #357, #358, #360, #362, #363, #365, #366)**: cell callbacks and hit-testing move to `UIGridView` -- input belongs to the *camera*, not the data, so two views over one map track hover independently; a `weak_ptr` view registry replaces the single `owning_view` (N views, one map); `find()`/`findAll()` descend into grids again; the ImGui scene explorer can expand them; hover-exit fires when the cursor leaves the window; `UICollection.repr` names Grids instead of `"UIDrawable"`. **Type split (#359, #361, #364, #370, #371)**: `UIGrid` is **deleted, not demoted**. `mcrfpy.GridData` is now public and standalone-constructible -- a map with no position, size, camera, or `render()`; only `UIGridView` is a `UIDrawable`. `mcrfpy.Grid` is an *alias bound to the same type object* as `GridView` (not a subclass -- `isinstance` works both ways, no MRO to explain). Overlay children belong to the view; entities belong to the map. Breaking change: `entity.grid` now returns a `GridData` (the setter still accepts a view). The split flushed out four latent bugs, each verified against unmodified master before being fixed: **#370** `Grid.center_camera()` and `Grid.size = ...` were silent **no-ops** (writing to the ghost camera nothing rendered); **#371** the shader uniform binder did type-confused pointer arithmetic, reading a `GridView` wrapper as a `_GridData` wrapper -- which "worked" only because both wrappers had identical layout and both C++ classes happened to put `UIDrawable` at offset 0; **#368** `markContentDirty()`'s walk up the parent chain was gated on a flag with exactly one `clearDirty()` call site in the whole engine, so the guard was permanently false and content invalidation propagated **nowhere** -- `Frame(cache_subtree=True)` silently froze its subtree's content (text never updated, colours never changed; only movement survived, being already on an unconditional path). The walk is now unconditional; entity movement, the hot path, is unaffected (164 ns/move). **#367**: hover state is cleared on focus loss. Suite 329/329. Riding along on the same branch: generated docs are **untracked** (regenerated by `tools/hooks/pre-commit`, installed via `make install-hooks`, and shipped inside release artifacts), with a compact `api/manifest.json` -- signatures + docstring hashes + carried-forward `since`/`modified` lifecycle metadata -- as the one committed exception, so API drift stays diffable by ref via `tools/api_delta.py REF1 REF2` without rebuilding at old commits.
|
||||
- **Grid render/memory sprint (#351, #334, #338, #332, #335; safety #353/#354)** -- a batch review closed dead/unbottlenecked issues (#117 entity pool, #145 TexturePool -- no demonstrated allocation gate; reopen after profiling) and shipped six grid-layer changes. **#351**: `UIGridView::render()` had *no* dirty check post-#252 and cleared+redrew every frame -- added a `GridData::content_generation` counter (bumped at every data/layer/entity mutation choke point) so an idle, non-perspective, childless view re-blits its cached texture instead of re-rendering; fixed a latent bug where Python entity `set_position`/`set_spritenumber` updated the spatial hash but never invalidated render (masked by the unconditional redraw). A follow-on regression (**6b99a1b**): the C++ turn manager `py_step` mutates entity cells directly, bypassing the setters -- it now calls `GridData::markCompositeDirty()` once per step (must qualify `GridData::`; `using UIDrawable::markCompositeDirty` shadows the data-layer override). **#334**: `DiscreteMap` exposes its `uint8_t` plane via the Python buffer protocol (`tp_as_buffer`, 2D zero-copy numpy/memoryview views). **#332**: `GridData` migrated from per-cell `UIGridPoint` structs (24 B for 2 B payload) + the whole chunk manager to two dense `std::vector<uint8_t>` planes (walkable/transparent) -- deleted `GridChunk`/`ChunkManager`/`CHUNK_THRESHOLD`, stripped `UIGridPoint` to a Python-accessor namespace, net **-257 lines**; pathfinding's `isCellWalkable`/`cellWalkable` were the critical non-obvious callers. **#335**: `ColorLayer`/`TileLayer` get zero-copy numpy views through a new `with layer.edit() as view:` context manager (implements the #328 convention; `__exit__` marks the layer dirty) -- `(h,w,4)` uint8 and `(h,w)` int32. **#338** (issue left open): safe subset only -- `rotationTexture` is now lazily allocated (`unique_ptr`) on Grid/GridView; deferred the `render_sprite` relocation as not worth the churn. Validation was delivered via **The Crucible** (**#354**, `tests/benchmarks/crucible.py`) -- a headless, deterministic, wall-clock A/B harness that safely replaces the windowed Gauntlet (which OOM-hard-locked the desktop during grid ramp). **#353** hardened the Gauntlet with `predict_bytes`/`max_load` refusal + an `RLIMIT_AS` backstop + RSS watchdog. Crucible A/B, current master vs the 0.2.8 dist artifact (both headless): grid_alloc **-58.9%**, layer_fill **-56.1%**, entity_churn **-43.0%**, grid_fill **-25.4%**, fov_storm/step_swarm/path_queries single-digit; **geomean 0.673 = ~32.7% faster overall**, peak RSS ~103 vs ~119 MB -- the big wins track #332 SoA + #329 indexing. Suite 314/314. Remaining in the thread: **#333** (lazy TCOD map rebuild, unblocked by #332), **#352** (perspective-grid early-out), and the deferred #338 relocation.
|
||||
- **Native profiling rig + hot-path perf batch (#345, #331, #342/#343/#344/#348)** -- a Callgrind/perf profiling workflow (`make profile` -> `build-profile/` RelWithDebInfo + frame pointers; `make callgrind SCRIPT=...`; `docs/profiling.md`) landed as **#345**, then drove five measured fixes. **#331**: hot property getters stop re-importing `mcrfpy` per call. **#348** (found *by* Callgrind, not by guessing): `UIGridView::get_grid` re-allocated a throwaway Grid wrapper + weakref every call at 0% cache hit -- now holds one persistent strong ref (`get_grid` inclusive **72.6M -> 2.1M Ir, -97%**). **#342**: scalar-float animation fast path bypasses two per-frame `std::variant` visits (`Animation::update` **-18%**, variant machinery **473M -> 99M Ir**) -- the profiler *disproved* the guessed strcmp-cascade and weak_ptr-lock hypotheses and found the real cost. **#344**: memoized Key/InputState enum members instead of rebuilding via `EnumMeta.__call__` per event (enum-ctor **19.18M -> 0.228M Ir, -99%**; wall-clock -31%). **#343**: skip the per-frame `GetAttrString("update")` for non-subclassed scenes (**4,559 -> 3 Ir/frame** on the real `doFrame` loop; measuring it required bypassing headless `step()`, which doesn't call `updatePythonScenes()` -- filed as **#350**). Harness total instructions **3,522.9M -> 3,211.5M (-8.8%)**; suite 307/307 with new regression tests `issue_34{2,4,8}_*`. Durable sprint doc with A/B numbers at `docs/sprint-perf-342-348.md`. Spun out **#349** (hybrid declarative scene serialization, Major Feature, tier2) from the finding that `automation.screenshot()` PNG encode is ~96% of a render-profile's instructions.
|
||||
- **Tier1 memory-model batch resolved** -- all five pre-1.0 freeze decisions from the 2026-07-02 review are closed on master. **#326**: Color/Vector are value types forever (copies at every property boundary; write-through proxies rejected); **#328**: `with layer.edit() as view:` is the single bulk-edit convention for future buffer/numpy APIs (conservative invalidation on `__exit__`; unblocks #335); **#327**: threading contract documented -- `docs/threading-model.md` + normative `mcrfpy.lock()` docstring ("off-main-thread access outside the lock is undefined"); **#330**: subinterpreters explicitly excluded from 1.x compatibility; **#329**: `grid.entities` re-backed by `std::vector` -- indexing is O(1) (5000-entity indexed sweep: 49.7 ms -> 0.5 ms), iterator is index-based with the same mutation-guard semantics, suite 304/304. #326/#328/#330 are recorded in the new `docs/api-stability.md` compatibility policy, enforced by the api-surface snapshot test.
|
||||
- **#340 The Gauntlet** -- interactive on-screen stress benchmark (`tests/benchmarks/gauntlet/`). Six trials (ENTITY SWARM, ANIMATION STORM, GRID TITAN, PATHFINDER RUSH, UI AVALANCHE, SIGHTLINE SIEGE) ramp load geometrically until p95 frame time breaks the 16.67 ms budget; live HUD with frame-time sparkline; menu + results screens diff against a committed JSON baseline with letter grades and a geometric-mean GAUNTLET SCORE. First real baseline captured windowed on 0.2.8 (e.g. 10,555 entities, 450 pathfinding queries/tick, 4,123 UI elements at budget). Caveat: desktop noise made callback-heavy trials vary run-to-run -- recapture on a quiet system for a canonical baseline. Found #341 (get_metrics draw_calls/render counters read 0).
|
||||
|
||||
### Recently Shipped (June 2026)
|
||||
- **0.2.8 release** -- Version bumped to `0.2.8` (dropped the in-development `-7DRL-2026` suffix) and cut for distribution: `dist/McRogueFace-0.2.8-{Linux-full.tar.gz,Windows-full.zip,WASM.zip}`. Shipped libtcod (Linux `.so` / Windows `.dll` / Emscripten `.a`) rebuilt from submodule 79abc66 so release binaries carry the #321 FOV fix; the Linux build is `SDL3=OFF` with vendored utf8proc to stay self-contained (only libz + system libs), matching prior releases. Also fixed a pre-existing `tools/package.sh` bug that affected 0.2.7 too: the Linux package shipped the bare `libtcod.so` instead of its DT_NEEDED SONAME `libtcod.so.2` (+ compat symlink, mirroring the SFML packaging), so the runtime loader couldn't resolve it on a clean machine -- an absolute dev RUNPATH had masked this locally. No Gitea issue tracks this release (declined). Local commit `cf844f4`, pushed to origin/master; the `0.2.8` tag exists locally but has not been pushed yet.
|
||||
- **#321** (HIGH) -- Fixed the `ColorLayer.draw_fov` heap-buffer-overflow by bumping `libtcod-headless` to 79abc66, pulling in upstream FOV overflow fixes (root cause: off-by-one in `view_array_insert` overflowing `active_views` in `fov_permissive2.c`; the "bad-free in ~GridData" of the issue title is the downstream symptom). Verified by A/B replay of the #312 `fuzz_fov` crash corpus under clang-18 ASan -- pre-fix (8835239) inputs abort with the overflow inside `GridData::computeFOV`, post-fix (79abc66) all clean plus a 45s/21,952-run fuzz smoke. (The gitignored shipped `__lib/libtcod.so` needed rebuilding from this commit for release binaries to carry the fix -- done for 0.2.8, see above.)
|
||||
- **#322-#325** (fuzz-surfaced safety batch) -- the four remaining #312 fuzz bugs fixed and regression-tested. **#322**: `WangSet.terrain_enum` (and the parallel `AutoRuleSet.terrain_enum`) ignored Python C-API return codes, so an invalid-UTF-8 import name left an exception pending and the next `PyObject_Call` tripped a `_PyErr_Occurred` assertion/abort; every C-API return is now checked and the real exception propagated. **#323/#324/#325**: NaN/inf (or out-of-long-range) floats reached unchecked float->integer casts -- `pitch_shift` factor (`AudioEffects.cpp:27`, nan->unsigned long), `hsl_shift` shifts (`PyTexture.cpp:458`, nan->unsigned char), and `Vector.int` components (`PyVector.cpp:610`, inf->long), all undefined behavior; each now validates finiteness/range at the binding boundary and raises ValueError/OverflowError. Verified by A/B replay of the #312 crash corpus under clang-18 UBSan/ASan: all four inputs abort pre-fix at the exact cited lines and run clean post-fix, with four new regression tests (`tests/regression/issue_322..325_*`).
|
||||
- **#312** -- Fuzz coverage extended to the remaining public API surface. Four new libFuzzer targets (`fuzz_audio_dsp`, `fuzz_import_parsers`, `fuzz_texture_factory`, `fuzz_shader_bindings`) cover the Tier A/B gaps (external file parsers, audio DSP math, raw-byte texture ingestion, shader uniform-binding lifetime); Tier C surface (Line/Circle/Arc, `Scene.children` collections, `find`/`find_all`/`bresenham`/`lock`, grid spatial queries, GridPoint dynamic attrs, `Grid.find_path`+AStarPath, ColorLayer perspective/`draw_fov`, layer `apply_*`) folded into the five existing targets. Each new target is signature-validated against the live API and seeded from real fixtures. The campaign immediately found **five new bugs** -- filed #321-#325 (no fixes this round; targets only). A pre-existing infra fix rode along: `tools/build_debug_libs.sh` flag-quoting bug that broke instrumented debug-lib rebuilds. The benchmark triplet is deliberately excluded from fuzzing (`end_benchmark()` writes a file per call).
|
||||
- **#320** -- `Caption` constructor positional signature now matches its frozen docstring. The docstring advertised `Caption(pos, font, text, ...)` (parallel to `Sprite`/`Entity`, whose 2nd positional is the resource), but the implementation laid its two positional slots out as `(pos, text)` with `font` keyword-only, so `Caption((x,y), None, "text")` raised `TypeError`. Fixed `UICaption::init` to `(pos, font, text)` positional-or-keyword. Audited zero live callers of the old `(pos, text)` 2-positional form. Also added the matching read-only `Caption.font` getter (the class docstring listed `font` as an attribute but no getter existed; it now reflects the supplied or engine-default font). Also rewrote two stale unit tests (`test_animation_raii`, `test_animation_property_locking`) that called the removed `mcrfpy.Animation(...)` constructor to use `drawable.animate(...)` -- preserving the suite's only `conflict_mode` (#120) coverage and the weak-target RAII checks. Suite now 297/297.
|
||||
- **#317 / #318 / #319** -- The three code-level bugs surfaced by the #314 docstring-accuracy verify pass, fixed together. #317: `automation.scroll()` dropped the x of its position argument (the scroll delta now has its own `injectMouseEvent` parameter, so the real x/y is forwarded). #318: `GridView.texture` always returned `None` (a TODO stub) -- it now returns a `Texture` wrapper (and since `mcrfpy.Grid`/`mcrfpy.GridView` are one type post-#252, both names benefit). #319: `Entity.visible_entities(radius=None)` raised `TypeError` (the `i` format code rejects `None`) -- radius is now parsed as an object so `None`/omitted/`-1` mean "grid default". Regression tests for each; api-surface snapshot re-baselined and docs/stubs regenerated.
|
||||
- **#316** -- Sparse (windowed) perspective writeback in `UIEntity::updateVisibility`. The demote+promote passes are now clipped to an AABB sized to `fov_radius` (with a `prev_fov` window cache so a moving entity leaves no trailing "ghost vision"), replacing two full-`W*H` walks per entity. The Phase 5.2 benchmark's flat ~25-36 ms/entity writeback overhead on a 1000x1000 grid collapses to single-digit microseconds (384x-6577x on the cheap algorithms; lost in timing noise on the rest). Adversarial verify caught a regression the happy-path test missed -- externally-assigned maps (the documented `from_bytes` load/resume path) need a one-shot full demote (`perspective_full_demote_pending`) since `prev_fov` only bounds engine-promoted cells; fixed and locked with a 7-section regression test.
|
||||
- **#313** -- `UIEntity::grid` migrated from `shared_ptr<UIGrid>` to `shared_ptr<GridData>` (post-#252 refactor cleanup), adding a new public `entity.texture` read/write property. Merged to master.
|
||||
- **#314** -- API audit follow-through complete. (1) Snapshot lock: a public API-surface regression test (`tests/unit/api_surface_snapshot_test.py`) enshrines the frozen contract. (2) **F15**: all 289 raw docstring slots across the 20 frozen binding files converted to `MCRF_*` macros (frozen surface 100% compliant), driven by two one-agent-per-file workflows with build/doc gates and an adversarial signature-accuracy verify pass. Property types now resolve to real types (not `Any`) and read-only flags are correct. (3) A strict frozen-docstring gate (`tools/check_frozen_docstrings.sh`, wired into `generate_all_docs.sh`) locks it against regression. Breaking-change findings (F1/F4/F6/F11/F13) closed earlier; F7/F8/F10 deferred as non-1.0. Code-level bugs surfaced by the verify pass filed as #317/#318/#319.
|
||||
|
||||
### Active Follow-Ups
|
||||
- The memory-model review (#326-#338) is nearly closed out: the five tier1 freeze decisions (#326-#330), #331 (hot-getter fast path), #332 (GridData SoA), #333 (lazy TCOD map rebuild), #334 (DiscreteMap buffer protocol), and #335 (numpy layer views) are all **resolved** on master. Still open: **#338** (per-instance memory diet -- shipped as a safe subset, the `render_sprite` relocation deferred), **#336** (free-threading hardening), **#337** (numpy availability strategy), and **#352** (perspective-grid render early-out).
|
||||
- The grid type-split bugs and the profiling/Gauntlet testability gaps are now **closed**: #369 (`.parent` identity churn), #372 (rotted `tests/demo/`, superseded by #374), #356 (doc/stub generators miss module-level dynamic attributes), #341 (get_metrics render counters read 0), and #350 (headless `step()` bypasses `updatePythonScenes()`).
|
||||
- #349 (declarative scene serialization) is an open tier2 design proposal.
|
||||
- Gauntlet baseline should be recaptured on a quiet system (`tests/benchmarks/gauntlet/run_gauntlet.py`) -- the committed first baseline is real but desktop-noisy for the callback-heavy trials. (#341, render counters read 0 in get_metrics, which blocked richer per-subsystem HUD attribution, is now closed.)
|
||||
- 0.2.8 is released and published: commit `cf844f4` and the `0.2.8` tag are both pushed, and the distribution artifacts are uploaded. Master has since advanced past `36e74e0` (grid input revival + type split) through the July perf/grid sprints and the docs-as-tests thread; the `closes #...` commit trailers take their issues down on merge.
|
||||
|
||||
### Other Post-7DRL Priorities
|
||||
- Progress on the r/roguelikedev tutorial series (#167)
|
||||
- Better pip/virtualenv integration for adding packages to McRogueFace's embedded interpreter
|
||||
**Current State**: Documentation system complete, TCOD integration urgent
|
||||
**Latest Update**: Completed Phase 7 documentation infrastructure (2025-07-08)
|
||||
**Branch**: alpha_streamline_2
|
||||
**Open Issues**: ~46 remaining + URGENT TCOD/Tutorial work
|
||||
|
||||
---
|
||||
|
||||
## Engine Eras
|
||||
## 📋 TCOD Integration Implementation Details
|
||||
|
||||
One engine, accumulating capabilities. Nothing is thrown away.
|
||||
### Phase 1: Core UIGrid Integration (Day 1 Morning)
|
||||
```cpp
|
||||
// UIGrid.h additions
|
||||
class UIGrid : public UIDrawable {
|
||||
private:
|
||||
TCODMap* world_state; // Add TCOD map
|
||||
std::unordered_map<int, UIGridPointState*> entity_perspectives;
|
||||
bool batch_mode = false;
|
||||
std::vector<CellUpdate> pending_updates;
|
||||
```
|
||||
|
||||
| Era | Focus | Status |
|
||||
|-----|-------|--------|
|
||||
| **McRogueFace** | 2D tiles, roguelike systems, procgen | Active -- approaching 1.0 |
|
||||
| **McVectorFace** | Sparse grids, vector graphics, physics | Planned |
|
||||
| **McVoxelFace** | Voxel terrain, 3D gameplay | Proof-of-concept complete |
|
||||
### Phase 2: Python Bindings (Day 1 Afternoon)
|
||||
```python
|
||||
# New API surface
|
||||
grid = mcrfpy.Grid(100, 100)
|
||||
grid.compute_fov(player.x, player.y, radius=10) # Returns visible cells
|
||||
grid.at((x, y)).walkable = False # Ergonomic access
|
||||
with grid.batch_update(): # Context manager for performance
|
||||
# All updates batched
|
||||
```
|
||||
|
||||
### Phase 3: Entity Integration (Day 2 Morning)
|
||||
```python
|
||||
# UIEntity additions
|
||||
entity.path_to(target_x, target_y) # A* pathfinding
|
||||
entity.flee_from(threat) # Dijkstra map
|
||||
entity.can_see(other_entity) # FOV check
|
||||
```
|
||||
|
||||
### Critical Success Factors:
|
||||
1. **Batch everything** - Never update single cells in loops
|
||||
2. **Lazy evaluation** - Only compute FOV for entities that need it
|
||||
3. **Sparse storage** - Don't store full grids per entity
|
||||
4. **Profile early** - Find the 20% of code taking 80% of time
|
||||
|
||||
---
|
||||
|
||||
## 3D/Voxel Pipeline (Experimental)
|
||||
## Recent Achievements
|
||||
|
||||
The 3D pipeline is proof-of-concept scouting for the McVoxelFace era. It works and is tested but is explicitly **not** part of the 1.0 API freeze.
|
||||
### 2025-07-10: Complete FOV, A* Pathfinding & GUI Text Widgets! 👁️🗺️⌨️
|
||||
**Engine Feature Sprint - Major Capabilities Added**
|
||||
- ✅ Complete FOV (Field of View) system with perspective rendering
|
||||
- UIGrid.perspective property controls which entity's view to render
|
||||
- Three-layer overlay system: unexplored (black), explored (dark), visible (normal)
|
||||
- Per-entity visibility state tracking with UIGridPointState
|
||||
- Perfect knowledge updates - only explored areas persist
|
||||
- ✅ A* Pathfinding implementation
|
||||
- Entity.path_to(x, y) method for direct pathfinding
|
||||
- UIGrid compute_astar() and get_astar_path() methods
|
||||
- Path caching in entities for performance
|
||||
- Complete test suite comparing A* vs Dijkstra performance
|
||||
- ✅ GUI Text Input Widget System
|
||||
- Full-featured TextInputWidget class with cursor, selection, scrolling
|
||||
- Improved widget with proper text rendering and multi-line support
|
||||
- Example showcase demonstrating multiple input fields
|
||||
- Foundation for in-game consoles, chat systems, and text entry
|
||||
- ✅ Sizzle Reel Demos
|
||||
- path_vision_sizzle_reel.py combines pathfinding with FOV
|
||||
- Interactive visibility demos showing real-time FOV updates
|
||||
- Performance demonstrations with multiple entities
|
||||
|
||||
**What exists**: Viewport3D, Camera3D, Entity3D, MeshLayer, Model3D (glTF), Billboard, Shader3D, VoxelGrid with greedy meshing, face culling, RLE serialization, and navigation projection.
|
||||
### 2025-07-09: Dijkstra Pathfinding & Critical Bug Fix! 🗺️
|
||||
**TCOD Integration Sprint - Major Progress**
|
||||
- ✅ Complete Dijkstra pathfinding implementation in UIGrid
|
||||
- compute_dijkstra(), get_dijkstra_distance(), get_dijkstra_path() methods
|
||||
- Full TCODMap and TCODDijkstra integration with proper memory management
|
||||
- Comprehensive test suite with both headless and interactive demos
|
||||
- ✅ **CRITICAL FIX**: PyArg bug in UIGridPoint color setter
|
||||
- Now supports both mcrfpy.Color objects and (r,g,b,a) tuples
|
||||
- Eliminated mysterious "SystemError: new style getargs format" crashes
|
||||
- Proper error handling and exception propagation
|
||||
- ✅ mcrfpy.libtcod submodule with Python bindings
|
||||
- dijkstra_compute(), dijkstra_get_distance(), dijkstra_get_path()
|
||||
- line() function for corridor generation
|
||||
- Foundation ready for FOV implementation
|
||||
- ✅ Test consolidation: 6 broken demos → 2 clean, working versions
|
||||
|
||||
**Known gaps**: Some Entity3D collection methods, animation stubs, shader pipeline incomplete.
|
||||
### 2025-07-08: PyArgHelpers Infrastructure Complete! 🔧
|
||||
**Standardized Python API Argument Parsing**
|
||||
- Unified position handling: (x, y) tuples or separate x, y args
|
||||
- Consistent size parsing: (w, h) tuples or width, height args
|
||||
- Grid-specific helpers for tile-based positioning
|
||||
- Proper conflict detection between positional and keyword args
|
||||
- All UI components migrated: Frame, Caption, Sprite, Grid, Entity
|
||||
- Improved error messages: "Value must be a number (int or float)"
|
||||
- Foundation for Phase 7 documentation efforts
|
||||
|
||||
**Maturity track**: These modules will mature on their own timeline, driven by games that need 3D. They won't block 2D stability.
|
||||
### 2025-07-05: ALPHA 0.1 ACHIEVED! 🎊🍾
|
||||
**All Alpha Blockers Resolved!**
|
||||
- Z-order rendering with performance optimization (Issue #63)
|
||||
- Python Sequence Protocol for collections (Issue #69)
|
||||
- Comprehensive Animation System (Issue #59)
|
||||
- Moved RenderTexture to Beta (not needed for Alpha)
|
||||
- **McRogueFace is ready for Alpha release!**
|
||||
|
||||
### 2025-07-05: Z-order Rendering Complete! 🎉
|
||||
**Issue #63 Resolved**: Consistent z-order rendering with performance optimization
|
||||
- Dirty flag pattern prevents unnecessary per-frame sorting
|
||||
- Lazy sorting for both Scene elements and Frame children
|
||||
- Frame children now respect z_index (fixed inconsistency)
|
||||
- Automatic dirty marking on z_index changes and collection modifications
|
||||
- Performance: O(1) check for static scenes vs O(n log n) every frame
|
||||
|
||||
### 2025-07-05: Python Sequence Protocol Complete! 🎉
|
||||
**Issue #69 Resolved**: Full sequence protocol implementation for collections
|
||||
- Complete __setitem__, __delitem__, __contains__ support
|
||||
- Slice operations with extended slice support (step != 1)
|
||||
- Concatenation (+) and in-place concatenation (+=) with validation
|
||||
- Negative indexing throughout, index() and count() methods
|
||||
- Type safety: UICollection (Frame/Caption/Sprite/Grid), EntityCollection (Entity only)
|
||||
- Default value support: None for texture/font parameters uses engine defaults
|
||||
|
||||
### 2025-07-05: Animation System Complete! 🎉
|
||||
**Issue #59 Resolved**: Comprehensive animation system with 30+ easing functions
|
||||
- Property-based animations for all UI classes (Frame, Caption, Sprite, Grid, Entity)
|
||||
- Individual color component animation (r/g/b/a)
|
||||
- Sprite sequence animation and text typewriter effects
|
||||
- Pure C++ execution without Python callbacks
|
||||
- Delta animation support for relative values
|
||||
|
||||
### 2025-01-03: Major Stability Update
|
||||
**Major Cleanup**: Removed deprecated registerPyAction system (-180 lines)
|
||||
**Bug Fixes**: 12 critical issues including Grid segfault, Issue #78 (middle click), Entity setters
|
||||
**New Features**: Entity.index() (#73), EntityCollection.extend() (#27), Sprite validation (#33)
|
||||
**Test Coverage**: Comprehensive test suite with timer callback pattern established
|
||||
|
||||
---
|
||||
|
||||
## Future Directions
|
||||
## 🔧 CURRENT WORK: Alpha Streamline 2 - Major Architecture Improvements
|
||||
|
||||
These are ideas on the horizon -- not yet concrete enough for issues, but worth capturing.
|
||||
### Recent Completions:
|
||||
- ✅ **Phase 1-4 Complete** - Foundation, API Polish, Entity Lifecycle, Visibility/Performance
|
||||
- ✅ **Phase 5 Complete** - Window/Scene Architecture fully implemented!
|
||||
- Window singleton with properties (#34)
|
||||
- OOP Scene support with lifecycle methods (#61)
|
||||
- Window resize events (#1)
|
||||
- Scene transitions with animations (#105)
|
||||
- ✅ **Phase 6 Complete** - Rendering Revolution achieved!
|
||||
- Grid background colors (#50) ✅
|
||||
- RenderTexture overhaul (#6) ✅
|
||||
- UIFrame clipping support ✅
|
||||
- Viewport-based rendering (#8) ✅
|
||||
|
||||
### McRogueFace Lite
|
||||
A spiritual port to MicroPython targeting the PicoCalc and other microcontrollers. Could provide a migration path to retro ROMs or compete in the Pico-8 space. The core idea: strip McRogueFace down to its essential tile/entity/scene model and run it on constrained hardware.
|
||||
### Active Development:
|
||||
- **Branch**: alpha_streamline_2
|
||||
- **Current Phase**: Phase 7 - Documentation & Distribution
|
||||
- **Achievement**: PyArgHelpers infrastructure complete - standardized Python API
|
||||
- **Strategic Vision**: See STRATEGIC_VISION.md for platform roadmap
|
||||
- **Latest**: All UI components now use consistent argument parsing patterns!
|
||||
|
||||
### McVectorFace Era
|
||||
The next major capability expansion. Sparse grid layers, a polygon/shape rendering class, and eventually physics integration. This would support games that aren't purely tile-based -- top-down action, strategy maps with irregular regions, or hybrid tile+vector visuals. See the [Strategic Direction](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Strategic-Direction) wiki for the full era model.
|
||||
### 🏗️ Architectural Dependencies Map
|
||||
|
||||
### McRogueFace Standard Library
|
||||
A built-in collection of reusable GUI widgets and game UI patterns: menus, dialogs, inventory screens, stat bars, text input fields, scrollable lists. These would ship with the engine as importable Python modules, saving every game from reimplementing the same UI primitives. Think of it as `mcrfpy.widgets` -- batteries included.
|
||||
```
|
||||
Foundation Layer:
|
||||
├── #71 Base Class (_Drawable)
|
||||
│ ├── #10 Visibility System (needs AABB from base)
|
||||
│ ├── #87 visible property
|
||||
│ └── #88 opacity property
|
||||
│
|
||||
├── #7 Safe Constructors (affects all classes)
|
||||
│ └── Blocks any new class creation until resolved
|
||||
│
|
||||
└── #30 Entity/Grid Integration (lifecycle management)
|
||||
└── Enables reliable entity management
|
||||
|
||||
### Pip/Virtualenv Integration
|
||||
Rather than inverting the architecture to make McRogueFace a pip-installable package, the nearer-term goal is better integration in the other direction: making it easy to install and use third-party Python packages within McRogueFace's embedded interpreter. This could mean virtualenv awareness, a `mcrf install` command, or bundling pip itself.
|
||||
Window/Scene Layer:
|
||||
├── #34 Window Object
|
||||
│ ├── #61 Scene Object (depends on Window)
|
||||
│ ├── #14 SFML Exposure (helps implement Window)
|
||||
│ └── Future: Multi-window support
|
||||
|
||||
Rendering Layer:
|
||||
└── #6 RenderTexture Overhaul
|
||||
├── Enables clipping
|
||||
├── Off-screen rendering
|
||||
└── Post-processing effects
|
||||
```
|
||||
|
||||
## 🚀 Alpha Streamline 2 - Comprehensive Phase Plan
|
||||
|
||||
### Phase 1: Foundation Stabilization (1-2 weeks)
|
||||
**Goal**: Safe, predictable base for all future work
|
||||
```
|
||||
1. #7 - Audit and fix unsafe constructors (CRITICAL - do first!)
|
||||
- Find all manually implemented no-arg constructors
|
||||
- Verify map compatibility requirements
|
||||
- Make pointer-safe or remove
|
||||
|
||||
2. #71 - _Drawable base class implementation
|
||||
- Common properties: x, y, w, h, visible, opacity
|
||||
- Virtual methods: get_bounds(), render()
|
||||
- Proper Python inheritance setup
|
||||
|
||||
3. #87 - visible property
|
||||
- Add to base class
|
||||
- Update all render methods to check
|
||||
|
||||
4. #88 - opacity property (depends on #87)
|
||||
- 0.0-1.0 float range
|
||||
- Apply in render methods
|
||||
|
||||
5. #89 - get_bounds() method
|
||||
- Virtual method returning (x, y, w, h)
|
||||
- Override in each UI class
|
||||
|
||||
6. #98 - move()/resize() convenience methods
|
||||
- move(dx, dy) - relative movement
|
||||
- resize(w, h) - absolute sizing
|
||||
```
|
||||
*Rationale*: Can't build on unsafe foundations. Base class enables all UI improvements.
|
||||
|
||||
### Phase 2: Constructor & API Polish (1 week)
|
||||
**Goal**: Pythonic, intuitive API
|
||||
```
|
||||
1. #101 - Standardize (0,0) defaults for all positions
|
||||
2. #38 - Frame children parameter: Frame(children=[...])
|
||||
3. #42 - Click handler in __init__: Button(click=callback)
|
||||
4. #90 - Grid size tuple: Grid(grid_size=(10, 10))
|
||||
5. #19 - Sprite texture swapping: sprite.texture = new_texture
|
||||
6. #52 - Grid skip out-of-bounds entities (performance)
|
||||
```
|
||||
*Rationale*: Quick wins that make the API more pleasant before bigger changes.
|
||||
|
||||
### Phase 3: Entity Lifecycle Management (1 week)
|
||||
**Goal**: Bulletproof entity/grid relationships
|
||||
```
|
||||
1. #30 - Entity.die() and grid association
|
||||
- Grid.entities.append(e) sets e.grid = self
|
||||
- Grid.entities.remove(e) sets e.grid = None
|
||||
- Entity.die() calls self.grid.remove(self)
|
||||
- Entity can only be in 0 or 1 grid
|
||||
|
||||
2. #93 - Vector arithmetic methods
|
||||
- add, subtract, multiply, divide
|
||||
- distance, normalize, dot product
|
||||
|
||||
3. #94 - Color helper methods
|
||||
- from_hex("#FF0000"), to_hex()
|
||||
- lerp(other_color, t) for interpolation
|
||||
|
||||
4. #103 - Timer objects
|
||||
timer = mcrfpy.Timer("my_timer", callback, 1000)
|
||||
timer.pause()
|
||||
timer.resume()
|
||||
timer.cancel()
|
||||
```
|
||||
*Rationale*: Games need reliable entity management. Timer objects enable entity AI.
|
||||
|
||||
### Phase 4: Visibility & Performance (1-2 weeks)
|
||||
**Goal**: Only render/process what's needed
|
||||
```
|
||||
1. #10 - [UNSCHEDULED] Full visibility system with AABB
|
||||
- Postponed: UIDrawables can exist in multiple collections
|
||||
- Cannot reliably determine screen position due to multiple render contexts
|
||||
- Needs architectural solution for parent-child relationships
|
||||
|
||||
2. #52 - Grid culling (COMPLETED in Phase 2)
|
||||
|
||||
3. #39/40/41 - Name system for finding elements
|
||||
- name="button1" property on all UIDrawables
|
||||
- only_one=True for unique names
|
||||
- scene.find("button1") returns element
|
||||
- collection.find("enemy*") returns list
|
||||
|
||||
4. #104 - Basic profiling/metrics
|
||||
- Frame time tracking
|
||||
- Draw call counting
|
||||
- Python vs C++ time split
|
||||
```
|
||||
*Rationale*: Performance is feature. Finding elements by name is huge QoL.
|
||||
|
||||
### Phase 5: Window/Scene Architecture ✅ COMPLETE! (2025-07-06)
|
||||
**Goal**: Modern, flexible architecture
|
||||
```
|
||||
1. ✅ #34 - Window object (singleton first)
|
||||
window = mcrfpy.Window.get()
|
||||
window.resolution = (1920, 1080)
|
||||
window.fullscreen = True
|
||||
window.vsync = True
|
||||
|
||||
2. ✅ #1 - Window resize events
|
||||
scene.on_resize(self, width, height) callback implemented
|
||||
|
||||
3. ✅ #61 - Scene object (OOP scenes)
|
||||
class MenuScene(mcrfpy.Scene):
|
||||
def on_keypress(self, key, state):
|
||||
# handle input
|
||||
def on_enter(self):
|
||||
# setup UI
|
||||
def on_exit(self):
|
||||
# cleanup
|
||||
def update(self, dt):
|
||||
# frame update
|
||||
|
||||
4. ✅ #14 - SFML exposure research
|
||||
- Completed comprehensive analysis
|
||||
- Recommendation: Direct integration as mcrfpy.sfml
|
||||
- SFML 3.0 migration deferred to late 2025
|
||||
|
||||
5. ✅ #105 - Scene transitions
|
||||
mcrfpy.setScene("menu", "fade", 1.0)
|
||||
# Supports: fade, slide_left, slide_right, slide_up, slide_down
|
||||
```
|
||||
*Result*: Entire window/scene system modernized with OOP design!
|
||||
|
||||
### Phase 6: Rendering Revolution (3-4 weeks) ✅ COMPLETE!
|
||||
**Goal**: Professional rendering capabilities
|
||||
```
|
||||
1. ✅ #50 - Grid background colors [COMPLETED]
|
||||
grid.background_color = mcrfpy.Color(50, 50, 50)
|
||||
- Added background_color property with animation support
|
||||
- Default dark gray background (8, 8, 8, 255)
|
||||
|
||||
2. ✅ #6 - RenderTexture overhaul [COMPLETED]
|
||||
✅ Base infrastructure in UIDrawable
|
||||
✅ UIFrame clip_children property
|
||||
✅ Dirty flag optimization system
|
||||
✅ Nested clipping support
|
||||
✅ UIGrid already has appropriate RenderTexture implementation
|
||||
❌ UICaption/UISprite clipping not needed (no children)
|
||||
|
||||
3. ✅ #8 - Viewport-based rendering [COMPLETED]
|
||||
- Fixed game resolution (window.game_resolution)
|
||||
- Three scaling modes: "center", "stretch", "fit"
|
||||
- Window to game coordinate transformation
|
||||
- Mouse input properly scaled with windowToGameCoords()
|
||||
- Python API fully integrated
|
||||
- Tests: test_viewport_simple.py, test_viewport_visual.py, test_viewport_scaling.py
|
||||
|
||||
4. #106 - Shader support [DEFERRED TO POST-PHASE 7]
|
||||
sprite.shader = mcrfpy.Shader.load("glow.frag")
|
||||
frame.shader_params = {"intensity": 0.5}
|
||||
|
||||
5. #107 - Particle system [DEFERRED TO POST-PHASE 7]
|
||||
emitter = mcrfpy.ParticleEmitter()
|
||||
emitter.texture = spark_texture
|
||||
emitter.emission_rate = 100
|
||||
emitter.lifetime = (0.5, 2.0)
|
||||
```
|
||||
|
||||
**Phase 6 Achievement Summary**:
|
||||
- Grid backgrounds (#50) ✅ - Customizable background colors with animation
|
||||
- RenderTexture overhaul (#6) ✅ - UIFrame clipping with opt-in architecture
|
||||
- Viewport rendering (#8) ✅ - Three scaling modes with coordinate transformation
|
||||
- UIGrid already had optimal RenderTexture implementation for its use case
|
||||
- UICaption/UISprite clipping unnecessary (no children to clip)
|
||||
- Performance optimized with dirty flag system
|
||||
- Backward compatibility preserved throughout
|
||||
- Effects/Shader/Particle systems deferred for focused delivery
|
||||
|
||||
*Rationale*: This unlocks professional visual effects but is complex.
|
||||
|
||||
### Phase 7: Documentation & Distribution (1-2 weeks)
|
||||
**Goal**: Ready for the world
|
||||
```
|
||||
1. ✅ #85 - Replace all "docstring" placeholders [COMPLETED 2025-07-08]
|
||||
2. ✅ #86 - Add parameter documentation [COMPLETED 2025-07-08]
|
||||
3. ✅ #108 - Generate .pyi type stubs for IDE support [COMPLETED 2025-07-08]
|
||||
4. ❌ #70 - PyPI wheel preparation [CANCELLED - Architectural mismatch]
|
||||
5. API reference generator tool
|
||||
```
|
||||
|
||||
## 📋 Critical Path & Parallel Tracks
|
||||
|
||||
### 🔴 **Critical Path** (Must do in order)
|
||||
**Safe Constructors (#7)** → **Base Class (#71)** → **Visibility (#10)** → **Window (#34)** → **Scene (#61)**
|
||||
|
||||
### 🟡 **Parallel Tracks** (Can be done alongside critical path)
|
||||
|
||||
**Track A: Entity Systems**
|
||||
- Entity/Grid integration (#30)
|
||||
- Timer objects (#103)
|
||||
- Vector/Color helpers (#93, #94)
|
||||
|
||||
**Track B: API Polish**
|
||||
- Constructor improvements (#101, #38, #42, #90)
|
||||
- Sprite texture swap (#19)
|
||||
- Name/search system (#39/40/41)
|
||||
|
||||
**Track C: Performance**
|
||||
- Grid culling (#52)
|
||||
- Visibility culling (part of #10)
|
||||
- Profiling tools (#104)
|
||||
|
||||
### 💎 **Quick Wins to Sprinkle Throughout**
|
||||
1. Color helpers (#94) - 1 hour
|
||||
2. Vector methods (#93) - 1 hour
|
||||
3. Grid backgrounds (#50) - 30 minutes
|
||||
4. Default positions (#101) - 30 minutes
|
||||
|
||||
### 🎯 **Recommended Execution Order**
|
||||
|
||||
**Week 1-2**: Foundation (Critical constructors + base class)
|
||||
**Week 3**: Entity lifecycle + API polish
|
||||
**Week 4**: Visibility system + performance
|
||||
**Week 5-6**: Window/Scene architecture
|
||||
**Week 7-9**: Rendering revolution (or defer to gamma)
|
||||
**Week 10**: Documentation + release prep
|
||||
|
||||
### 🆕 **New Issues to Create/Track**
|
||||
|
||||
1. [x] **Timer Objects** - Pythonic timer management (#103) - *Completed Phase 3*
|
||||
2. [ ] **Event System Enhancement** - Mouse enter/leave, drag, right-click
|
||||
3. [ ] **Resource Manager** - Centralized asset loading
|
||||
4. [ ] **Serialization System** - Save/load game state
|
||||
5. [x] **Scene Transitions** - Fade, slide, custom effects (#105) - *Completed Phase 5*
|
||||
6. [x] **Profiling Tools** - Performance metrics (#104) - *Completed Phase 4*
|
||||
7. [ ] **Particle System** - Visual effects framework (#107)
|
||||
8. [ ] **Shader Support** - Custom rendering effects (#106)
|
||||
|
||||
---
|
||||
|
||||
## Open Issues by Area
|
||||
## 📋 Phase 6 Implementation Strategy
|
||||
|
||||
26 open issues across the tracker. Key groupings:
|
||||
### RenderTexture Overhaul (#6) - Technical Approach
|
||||
|
||||
- **Docs-as-tests fallout** (#380, #374) -- shipped `templates/*/` example programs are gated by nothing; 19 orphan scripts under `tests/demo/screens/` need adoption or retirement. Both spun out of the July snippets-as-tests thread (the docs site's code samples are now in the gated suite). The earlier bug batch (#369 `.parent` identity, #372 rotted `tests/demo/`, #356 doc generators miss module attrs, #341 get_metrics counters, #350 headless `step()` bypass) is **closed**.
|
||||
- **Memory-model review, July 2026** (#336, #337, #338 remaining; #326-#335 resolved) -- per-instance memory diet, free-threading hardening, numpy availability strategy
|
||||
- **Grid / rendering** (#352, #152, #67, #124, #107, #347) -- perspective-grid render early-out; sparse layers; infinite worlds; grid point animation; particle system; SFML vs SDL2/WebGL renderer parity
|
||||
- **Design proposals** (#349) -- hybrid declarative scene serialization (test oracle + save/load)
|
||||
- **Demos / tutorials** (#167, #248, #154, #156, #55) -- r/roguelikedev series, Crypt of Sokoban remaster, LLM agent simulations
|
||||
- **Platform/distribution** (#70, #54, #62, #53) -- Packaging without the embedded interpreter, Jupyter, multiple windows, input methods
|
||||
- **WASM tooling** (#239, #346) -- Automated browser testing; web-build profiling docs (sibling to #345)
|
||||
- **Deferred** (#220, #46, #45) -- Subinterpreter support / tests, accessibility modes
|
||||
**Current State**:
|
||||
- UIGrid already uses RenderTexture for entity rendering
|
||||
- Scene transitions use RenderTextures for smooth animations
|
||||
- Direct rendering to window for Frame, Caption, Sprite
|
||||
|
||||
See the [Forgejo issue tracker](https://dev.ffwf.net/forgejo/john/McRogueFace/issues) for current status.
|
||||
**Implementation Plan**:
|
||||
1. **Base Infrastructure**:
|
||||
- Add `sf::RenderTexture* target` to UIDrawable base
|
||||
- Modify `render()` to check if target exists
|
||||
- If target: render to texture, then draw texture to parent
|
||||
- If no target: render directly (backward compatible)
|
||||
|
||||
2. **Clipping Support**:
|
||||
- Frame enforces bounds on children via RenderTexture
|
||||
- Children outside bounds are automatically clipped
|
||||
- Nested frames create render texture hierarchy
|
||||
|
||||
3. **Performance Optimization**:
|
||||
- Lazy RenderTexture creation (only when needed)
|
||||
- Dirty flag system (only re-render when changed)
|
||||
- Texture pooling for commonly used sizes
|
||||
|
||||
4. **Integration Points**:
|
||||
- Scene transitions already working with RenderTextures
|
||||
- UIGrid can be reference implementation
|
||||
- Test with deeply nested UI structures
|
||||
|
||||
**Quick Wins Before Core Work**:
|
||||
1. **Grid Background (#50)** - 30 min implementation
|
||||
- Add `background_color` and `background_texture` properties
|
||||
- Render before entities in UIGrid::render()
|
||||
- Good warm-up before tackling RenderTexture
|
||||
|
||||
2. **Research Tasks**:
|
||||
- Study UIGrid's current RenderTexture usage
|
||||
- Profile scene transition performance
|
||||
- Identify potential texture size limits
|
||||
|
||||
---
|
||||
|
||||
## Resources
|
||||
## 🚀 NEXT PHASE: Beta Features & Polish
|
||||
|
||||
- **Issue Tracker**: [Forgejo Issues](https://dev.ffwf.net/forgejo/john/McRogueFace/issues)
|
||||
- **Wiki**: [Strategic Direction](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Strategic-Direction), [Issue Roadmap](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Issue-Roadmap), [Development Workflow](https://dev.ffwf.net/forgejo/john/McRogueFace/wiki/Development-Workflow)
|
||||
- **Build Guide**: See `CLAUDE.md` for build instructions
|
||||
- **Tutorial**: `roguelike_tutorial/` for implementation examples
|
||||
### Alpha Complete! Moving to Beta Priorities:
|
||||
1. ~~**#69** - Python Sequence Protocol for collections~~ - *Completed! (2025-07-05)*
|
||||
2. ~~**#63** - Z-order rendering for UIDrawables~~ - *Completed! (2025-07-05)*
|
||||
3. ~~**#59** - Animation system~~ - *Completed! (2025-07-05)*
|
||||
4. **#6** - RenderTexture concept - *Extensive Overhaul*
|
||||
5. ~~**#47** - New README.md for Alpha release~~ - *Completed*
|
||||
- [x] **#78** - Middle Mouse Click sends "C" keyboard event - *Fixed*
|
||||
- [x] **#77** - Fix error message copy/paste bug - *Fixed*
|
||||
- [x] **#74** - Add missing `Grid.grid_y` property - *Fixed*
|
||||
- [ ] **#37** - Fix Windows build module import from "scripts" directory - *Isolated Fix*
|
||||
Issue #37 is **on hold** until we have a Windows build environment available. I actually suspect this is already fixed by the updates to the makefile, anyway.
|
||||
- [x] **Entity Property Setters** - Fix "new style getargs format" error - *Fixed*
|
||||
- [x] **Sprite Texture Setter** - Fix "error return without exception set" - *Fixed*
|
||||
- [x] **keypressScene() Validation** - Add proper error handling - *Fixed*
|
||||
|
||||
### 🔄 Complete Iterator System
|
||||
**Status**: Core iterators complete (#72 closed), Grid point iterators still pending
|
||||
|
||||
- [ ] **Grid Point Iterator Implementation** - Complete the remaining grid iteration work
|
||||
- [x] **#73** - Add `entity.index()` method for collection removal - *Fixed*
|
||||
- [x] **#69** ⚠️ **Alpha Blocker** - Refactor all collections to use Python Sequence Protocol - *Completed! (2025-07-05)*
|
||||
|
||||
**Dependencies**: Grid point iterators → #73 entity.index() → #69 Sequence Protocol overhaul
|
||||
|
||||
---
|
||||
|
||||
## 🗂 ISSUE TRIAGE BY SYSTEM (78 Total Issues)
|
||||
|
||||
### 🎮 Core Engine Systems
|
||||
|
||||
#### Iterator/Collection System (2 issues)
|
||||
- [x] **#73** - Entity index() method for removal - *Fixed*
|
||||
- [x] **#69** ⚠️ **Alpha Blocker** - Sequence Protocol refactor - *Completed! (2025-07-05)*
|
||||
|
||||
#### Python/C++ Integration (7 issues)
|
||||
- [x] **#76** - UIEntity derived type preservation in collections - *Multiple Integrations*
|
||||
- [ ] **#71** - Drawable base class hierarchy - *Extensive Overhaul*
|
||||
- [ ] **#70** - PyPI wheel distribution - *Extensive Overhaul*
|
||||
- [~] **#32** - Executable behave like `python` command - *Extensive Overhaul* *(90% Complete: -h, -V, -c, -m, -i, script execution, sys.argv, --exec all implemented. Only stdin (-) support missing)*
|
||||
- [ ] **#35** - TCOD as built-in module - *Extensive Overhaul*
|
||||
- [~] **#14** - Expose SFML as built-in module - *Research Complete, Implementation Pending*
|
||||
- [ ] **#46** - Subinterpreter threading tests - *Multiple Integrations*
|
||||
|
||||
#### UI/Rendering System (12 issues)
|
||||
- [x] **#63** ⚠️ **Alpha Blocker** - Z-order for UIDrawables - *Multiple Integrations*
|
||||
- [x] **#59** ⚠️ **Alpha Blocker** - Animation system - *Completed! (2025-07-05)*
|
||||
- [ ] **#6** ⚠️ **Alpha Blocker** - RenderTexture for all UIDrawables - *Extensive Overhaul*
|
||||
- [ ] **#10** - UIDrawable visibility/AABB system - *Extensive Overhaul*
|
||||
- [ ] **#8** - UIGrid RenderTexture viewport sizing - *Multiple Integrations*
|
||||
- [x] **#9** - UIGrid RenderTexture resize handling - *Multiple Integrations*
|
||||
- [ ] **#52** - UIGrid skip out-of-bounds entities - *Isolated Fix*
|
||||
- [ ] **#50** - UIGrid background color field - *Isolated Fix*
|
||||
- [ ] **#19** - Sprite get/set texture methods - *Multiple Integrations*
|
||||
- [ ] **#17** - Move UISprite position into sf::Sprite - *Isolated Fix*
|
||||
- [x] **#33** - Sprite index validation against texture range - *Fixed*
|
||||
|
||||
#### Grid/Entity System (6 issues)
|
||||
- [ ] **#30** - Entity/Grid association management (.die() method) - *Extensive Overhaul*
|
||||
- [ ] **#16** - Grid strict mode for entity knowledge/visibility - *Extensive Overhaul*
|
||||
- [ ] **#67** - Grid stitching for infinite worlds - *Extensive Overhaul*
|
||||
- [ ] **#15** - UIGridPointState cleanup and standardization - *Multiple Integrations*
|
||||
- [ ] **#20** - UIGrid get_grid_size standardization - *Multiple Integrations*
|
||||
- [x] **#12** - GridPoint/GridPointState forbid direct init - *Isolated Fix*
|
||||
|
||||
#### Scene/Window Management (5 issues)
|
||||
- [x] **#61** - Scene object encapsulating key callbacks - *Completed Phase 5*
|
||||
- [x] **#34** - Window object for resolution/scaling - *Completed Phase 5*
|
||||
- [ ] **#62** - Multiple windows support - *Extensive Overhaul*
|
||||
- [ ] **#49** - Window resolution & viewport controls - *Multiple Integrations*
|
||||
- [x] **#1** - Scene resize event handling - *Completed Phase 5*
|
||||
|
||||
### 🔧 Quality of Life Features
|
||||
|
||||
#### UI Enhancement Features (8 issues)
|
||||
- [ ] **#39** - Name field on UIDrawables - *Multiple Integrations*
|
||||
- [ ] **#40** - `only_one` arg for unique naming - *Multiple Integrations*
|
||||
- [ ] **#41** - `.find(name)` method for collections - *Multiple Integrations*
|
||||
- [ ] **#38** - `children` arg for Frame initialization - *Isolated Fix*
|
||||
- [ ] **#42** - Click callback arg for UIDrawable init - *Isolated Fix*
|
||||
- [x] **#27** - UIEntityCollection.extend() method - *Fixed*
|
||||
- [ ] **#28** - UICollectionIter for scene ui iteration - *Isolated Fix*
|
||||
- [ ] **#26** - UIEntityCollectionIter implementation - *Isolated Fix*
|
||||
|
||||
### 🧹 Refactoring & Cleanup
|
||||
|
||||
#### Code Cleanup (7 issues)
|
||||
- [x] **#3** ⚠️ **Alpha Blocker** - Remove `McRFPy_API::player_input` - *Completed*
|
||||
- [x] **#2** ⚠️ **Alpha Blocker** - Review `registerPyAction` necessity - *Completed*
|
||||
- [ ] **#7** - Remove unsafe no-argument constructors - *Multiple Integrations*
|
||||
- [ ] **#21** - PyUIGrid dealloc cleanup - *Isolated Fix*
|
||||
- [ ] **#75** - REPL thread separation from SFML window - *Multiple Integrations*
|
||||
|
||||
### 📚 Demo & Documentation
|
||||
|
||||
#### Documentation (2 issues)
|
||||
- [x] **#47** ⚠️ **Alpha Blocker** - Alpha release README.md - *Isolated Fix*
|
||||
- [ ] **#48** - Dependency compilation documentation - *Isolated Fix*
|
||||
|
||||
#### Demo Projects (6 issues)
|
||||
- [ ] **#54** - Jupyter notebook integration demo - *Multiple Integrations*
|
||||
- [ ] **#55** - Hunt the Wumpus AI demo - *Multiple Integrations*
|
||||
- [ ] **#53** - Web interface input demo - *Multiple Integrations* *(New automation API could help)*
|
||||
- [ ] **#45** - Accessibility mode demos - *Multiple Integrations* *(New automation API could help test)*
|
||||
- [ ] **#36** - Dear ImGui integration tests - *Extensive Overhaul*
|
||||
- [ ] **#65** - Python Explorer scene (replaces uitest) - *Extensive Overhaul*
|
||||
|
||||
---
|
||||
|
||||
## 🎮 STRATEGIC DIRECTION
|
||||
|
||||
### Engine Philosophy Maintained
|
||||
- **C++ First**: Performance-critical code stays in C++
|
||||
- **Python Close Behind**: Rich scripting without frame-rate impact
|
||||
- **Game-Ready**: Each improvement should benefit actual game development
|
||||
|
||||
### Architecture Goals
|
||||
1. **Clean Inheritance**: Drawable → UI components, proper type preservation
|
||||
2. **Collection Consistency**: Uniform iteration, indexing, and search patterns
|
||||
3. **Resource Management**: RAII everywhere, proper lifecycle handling
|
||||
4. **Multi-Platform**: Windows/Linux feature parity maintained
|
||||
|
||||
---
|
||||
|
||||
## 📚 REFERENCES & CONTEXT
|
||||
|
||||
**Issue Dependencies** (Key Chains):
|
||||
- Iterator System: Grid points → #73 → #69 (Alpha Blocker)
|
||||
- UI Hierarchy: #71 → #63 (Alpha Blocker)
|
||||
- Rendering: #6 (Alpha Blocker) → #8, #9 → #10
|
||||
- Entity System: #30 → #16 → #67
|
||||
- Window Management: #34 → #49, #61 → #62
|
||||
|
||||
**Commit References**:
|
||||
- 167636c: Iterator improvements (UICollection/UIEntityCollection complete)
|
||||
- Recent work: 7DRL 2025 completion, RPATH updates, console improvements
|
||||
|
||||
**Architecture Files**:
|
||||
- Iterator patterns: src/UICollection.cpp, src/UIGrid.cpp
|
||||
- Python integration: src/McRFPy_API.cpp, src/PyObjectUtils.h
|
||||
- Game implementation: src/scripts/ (Crypt of Sokoban complete game)
|
||||
|
||||
---
|
||||
|
||||
## 🔮 FUTURE VISION: Pure Python Extension Architecture
|
||||
|
||||
### Concept: McRogueFace as a Traditional Python Package
|
||||
**Status**: Unscheduled - Long-term vision
|
||||
**Complexity**: Major architectural overhaul
|
||||
|
||||
Instead of being a C++ application that embeds Python, McRogueFace could be redesigned as a pure Python extension module that can be installed via `pip install mcrogueface`.
|
||||
|
||||
### Technical Approach
|
||||
1. **Separate Core Engine from Python Embedding**
|
||||
- Extract SFML rendering, audio, and input into C++ extension modules
|
||||
- Remove embedded CPython interpreter
|
||||
- Use Python's C API to expose functionality
|
||||
|
||||
2. **Module Structure**
|
||||
```
|
||||
mcrfpy/
|
||||
├── __init__.py # Pure Python coordinator
|
||||
├── _core.so # C++ rendering/game loop extension
|
||||
├── _sfml.so # SFML bindings
|
||||
├── _audio.so # Audio system bindings
|
||||
└── engine.py # Python game engine logic
|
||||
```
|
||||
|
||||
3. **Inverted Control Flow**
|
||||
- Python drives the main loop instead of C++
|
||||
- C++ extensions handle performance-critical operations
|
||||
- Python manages game logic, scenes, and entity systems
|
||||
|
||||
### Benefits
|
||||
- **Standard Python Packaging**: `pip install mcrogueface`
|
||||
- **Virtual Environment Support**: Works with venv, conda, poetry
|
||||
- **Better IDE Integration**: Standard Python development workflow
|
||||
- **Easier Testing**: Use pytest, standard Python testing tools
|
||||
- **Cross-Python Compatibility**: Support multiple Python versions
|
||||
- **Modular Architecture**: Users can import only what they need
|
||||
|
||||
### Challenges
|
||||
- **Major Refactoring**: Complete restructure of codebase
|
||||
- **Performance Considerations**: Python-driven main loop overhead
|
||||
- **Build Complexity**: Multiple extension modules to compile
|
||||
- **Platform Support**: Need wheels for many platform/Python combinations
|
||||
- **API Stability**: Would need careful design to maintain compatibility
|
||||
|
||||
### Implementation Phases (If Pursued)
|
||||
1. **Proof of Concept**: Simple SFML binding as Python extension
|
||||
2. **Core Extraction**: Separate rendering from Python embedding
|
||||
3. **Module Design**: Define clean API boundaries
|
||||
4. **Incremental Migration**: Move systems one at a time
|
||||
5. **Compatibility Layer**: Support existing games during transition
|
||||
|
||||
### Example Usage (Future Vision)
|
||||
```python
|
||||
import mcrfpy
|
||||
from mcrfpy import Scene, Frame, Sprite, Grid
|
||||
|
||||
# Create game directly in Python
|
||||
game = mcrfpy.Game(width=1024, height=768)
|
||||
|
||||
# Define scenes using Python classes
|
||||
class MainMenu(Scene):
|
||||
def on_enter(self):
|
||||
self.ui.append(Frame(100, 100, 200, 50))
|
||||
self.ui.append(Sprite("logo.png", x=400, y=100))
|
||||
|
||||
def on_keypress(self, key, pressed):
|
||||
if key == "ENTER" and pressed:
|
||||
self.game.set_scene("game")
|
||||
|
||||
# Run the game
|
||||
game.add_scene("menu", MainMenu())
|
||||
game.run()
|
||||
```
|
||||
|
||||
This architecture would make McRogueFace a first-class Python citizen, following standard Python packaging conventions while maintaining high performance through C++ extensions.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 IMMEDIATE NEXT STEPS (Priority Order)
|
||||
|
||||
### Today (July 9) - EXECUTE NOW:
|
||||
1. **Start Tutorial Part 1** - Basic setup and @ movement (2 hours)
|
||||
2. **Implement UIGrid.at((x,y))** - CellView pattern (1 hour)
|
||||
3. **Create Grid demo** for sizzle reel (1 hour)
|
||||
4. **Fix any blocking bugs** discovered during tutorial writing
|
||||
|
||||
### Tomorrow (July 10) - CRITICAL PATH:
|
||||
1. **Tutorial Parts 2-4** - Map drawing, entities, FOV, combat
|
||||
2. **Implement compute_fov()** in UIGrid
|
||||
3. **Add batch_update context manager**
|
||||
4. **Expand sizzle reel** with entity choreography
|
||||
|
||||
### July 11 - ANNOUNCEMENT DAY:
|
||||
1. **Polish 4 tutorial parts**
|
||||
2. **Create announcement post** for r/roguelikedev
|
||||
3. **Record sizzle reel video**
|
||||
4. **Submit announcement** by end of day
|
||||
|
||||
### Architecture Decision Log:
|
||||
- **DECIDED**: Use three-layer architecture (visual/world/perspective)
|
||||
- **DECIDED**: Spatial hashing over quadtrees for entities
|
||||
- **DECIDED**: Batch operations are mandatory, not optional
|
||||
- **DECIDED**: TCOD integration as mcrfpy.libtcod submodule
|
||||
- **DECIDED**: Tutorial must showcase McRogueFace strengths, not mimic TCOD
|
||||
|
||||
### Risk Mitigation:
|
||||
- **If TCOD integration delays**: Use pure Python FOV for tutorial
|
||||
- **If performance issues**: Focus on <100x100 maps for demos
|
||||
- **If tutorial incomplete**: Ship with 4 solid parts + roadmap
|
||||
- **If bugs block progress**: Document as "known issues" and continue
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2025-07-09 (URGENT SPRINT MODE)*
|
||||
*Next Review: July 11 after announcement*
|
||||
|
|
|
|||
257
SFML_3_MIGRATION_RESEARCH.md
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
# SFML 3.0 Migration Research for McRogueFace
|
||||
|
||||
## Executive Summary
|
||||
|
||||
SFML 3.0 was released on December 21, 2024, marking the first major version in 12 years. While it offers significant improvements in type safety, modern C++ features, and API consistency, migrating McRogueFace would require substantial effort. Given our plans for `mcrfpy.sfml`, I recommend **deferring migration to SFML 3.0** until after implementing the initial `mcrfpy.sfml` module with SFML 2.6.1.
|
||||
|
||||
## SFML 3.0 Overview
|
||||
|
||||
### Release Highlights
|
||||
- **Release Date**: December 21, 2024
|
||||
- **Development**: 3 years, 1,100+ commits, 41 new contributors
|
||||
- **Major Feature**: C++17 support (now required)
|
||||
- **Audio Backend**: Replaced OpenAL with miniaudio
|
||||
- **Test Coverage**: Expanded to 57%
|
||||
- **New Features**: Scissor and stencil testing
|
||||
|
||||
### Key Breaking Changes
|
||||
|
||||
#### 1. C++ Standard Requirements
|
||||
- **Minimum**: C++17 (was C++03)
|
||||
- **Compilers**: MSVC 16 (VS 2019), GCC 9, Clang 9, AppleClang 12
|
||||
|
||||
#### 2. Event System Overhaul
|
||||
```cpp
|
||||
// SFML 2.x
|
||||
sf::Event event;
|
||||
while (window.pollEvent(event)) {
|
||||
switch (event.type) {
|
||||
case sf::Event::Closed:
|
||||
window.close();
|
||||
break;
|
||||
case sf::Event::KeyPressed:
|
||||
handleKey(event.key.code);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// SFML 3.0
|
||||
while (const std::optional event = window.pollEvent()) {
|
||||
if (event->is<sf::Event::Closed>()) {
|
||||
window.close();
|
||||
}
|
||||
else if (const auto* keyPressed = event->getIf<sf::Event::KeyPressed>()) {
|
||||
handleKey(keyPressed->code);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Scoped Enumerations
|
||||
```cpp
|
||||
// SFML 2.x
|
||||
sf::Keyboard::A
|
||||
sf::Mouse::Left
|
||||
|
||||
// SFML 3.0
|
||||
sf::Keyboard::Key::A
|
||||
sf::Mouse::Button::Left
|
||||
```
|
||||
|
||||
#### 4. Resource Loading
|
||||
```cpp
|
||||
// SFML 2.x
|
||||
sf::Texture texture;
|
||||
if (!texture.loadFromFile("image.png")) {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// SFML 3.0
|
||||
try {
|
||||
sf::Texture texture("image.png");
|
||||
} catch (const std::exception& e) {
|
||||
// Handle error
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. Geometry Changes
|
||||
```cpp
|
||||
// SFML 2.x
|
||||
sf::FloatRect rect(left, top, width, height);
|
||||
|
||||
// SFML 3.0
|
||||
sf::FloatRect rect({left, top}, {width, height});
|
||||
// Now uses position and size vectors
|
||||
```
|
||||
|
||||
#### 6. CMake Changes
|
||||
```cmake
|
||||
# SFML 2.x
|
||||
find_package(SFML 2.6 COMPONENTS graphics window system audio REQUIRED)
|
||||
target_link_libraries(app sfml-graphics sfml-window sfml-system sfml-audio)
|
||||
|
||||
# SFML 3.0
|
||||
find_package(SFML 3.0 COMPONENTS Graphics Window System Audio REQUIRED)
|
||||
target_link_libraries(app SFML::Graphics SFML::Window SFML::System SFML::Audio)
|
||||
```
|
||||
|
||||
## McRogueFace SFML Usage Analysis
|
||||
|
||||
### Current Usage Statistics
|
||||
- **SFML Version**: 2.6.1
|
||||
- **Integration Level**: Moderate to Heavy
|
||||
- **Affected Files**: ~40+ source files
|
||||
|
||||
### Major Areas Requiring Changes
|
||||
|
||||
#### 1. Event Handling (High Impact)
|
||||
- **Files**: `GameEngine.cpp`, `PyScene.cpp`
|
||||
- **Changes**: Complete rewrite of event loops
|
||||
- **Effort**: High
|
||||
|
||||
#### 2. Enumerations (Medium Impact)
|
||||
- **Files**: `ActionCode.h`, all input handling
|
||||
- **Changes**: Update all keyboard/mouse enum references
|
||||
- **Effort**: Medium (mostly find/replace)
|
||||
|
||||
#### 3. Resource Loading (Medium Impact)
|
||||
- **Files**: `PyTexture.cpp`, `PyFont.cpp`, `McRFPy_API.cpp`
|
||||
- **Changes**: Constructor-based loading with exception handling
|
||||
- **Effort**: Medium
|
||||
|
||||
#### 4. Geometry (Low Impact)
|
||||
- **Files**: Various UI classes
|
||||
- **Changes**: Update Rect construction
|
||||
- **Effort**: Low
|
||||
|
||||
#### 5. CMake Build System (Low Impact)
|
||||
- **Files**: `CMakeLists.txt`
|
||||
- **Changes**: Update find_package and target names
|
||||
- **Effort**: Low
|
||||
|
||||
### Code Examples from McRogueFace
|
||||
|
||||
#### Current Event Loop (GameEngine.cpp)
|
||||
```cpp
|
||||
sf::Event event;
|
||||
while (window && window->pollEvent(event)) {
|
||||
processEvent(event);
|
||||
if (event.type == sf::Event::Closed) {
|
||||
running = false;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Current Key Mapping (ActionCode.h)
|
||||
```cpp
|
||||
{sf::Keyboard::Key::A, KEY_A},
|
||||
{sf::Keyboard::Key::Left, KEY_LEFT},
|
||||
{sf::Mouse::Left, MOUSEBUTTON_LEFT}
|
||||
```
|
||||
|
||||
## Impact on mcrfpy.sfml Module Plans
|
||||
|
||||
### Option 1: Implement with SFML 2.6.1 First (Recommended)
|
||||
**Pros**:
|
||||
- Faster initial implementation
|
||||
- Stable, well-tested SFML version
|
||||
- Can provide value immediately
|
||||
- Migration can be done later
|
||||
|
||||
**Cons**:
|
||||
- Will require migration work later
|
||||
- API might need changes for SFML 3.0
|
||||
|
||||
### Option 2: Wait and Implement with SFML 3.0
|
||||
**Pros**:
|
||||
- Future-proof implementation
|
||||
- Modern C++ features
|
||||
- No migration needed later
|
||||
|
||||
**Cons**:
|
||||
- Delays `mcrfpy.sfml` implementation
|
||||
- SFML 3.0 is very new (potential bugs)
|
||||
- Less documentation/examples available
|
||||
|
||||
### Option 3: Dual Support
|
||||
**Pros**:
|
||||
- Maximum flexibility
|
||||
- Gradual migration path
|
||||
|
||||
**Cons**:
|
||||
- Significant additional complexity
|
||||
- Maintenance burden
|
||||
- Conditional compilation complexity
|
||||
|
||||
## Migration Strategy Recommendation
|
||||
|
||||
### Phase 1: Current State (Now)
|
||||
1. Continue with SFML 2.6.1
|
||||
2. Implement `mcrfpy.sfml` module as planned
|
||||
3. Design module API to minimize future breaking changes
|
||||
|
||||
### Phase 2: Preparation (3-6 months)
|
||||
1. Monitor SFML 3.0 stability and adoption
|
||||
2. Create migration branch for testing
|
||||
3. Update development environment to C++17
|
||||
|
||||
### Phase 3: Migration (6-12 months)
|
||||
1. Migrate McRogueFace core to SFML 3.0
|
||||
2. Update `mcrfpy.sfml` to match
|
||||
3. Provide migration guide for users
|
||||
|
||||
### Phase 4: Deprecation (12-18 months)
|
||||
1. Deprecate SFML 2.6.1 support
|
||||
2. Focus on SFML 3.0 features
|
||||
|
||||
## Specific Migration Tasks
|
||||
|
||||
### Prerequisites
|
||||
- [ ] Update to C++17 compatible compiler
|
||||
- [ ] Update CMake to 3.16+
|
||||
- [ ] Review all SFML usage locations
|
||||
|
||||
### Core Changes
|
||||
- [ ] Rewrite all event handling loops
|
||||
- [ ] Update all enum references
|
||||
- [ ] Convert resource loading to constructors
|
||||
- [ ] Update geometry construction
|
||||
- [ ] Update CMake configuration
|
||||
|
||||
### mcrfpy.sfml Considerations
|
||||
- [ ] Design API to be version-agnostic where possible
|
||||
- [ ] Use abstraction layer for version-specific code
|
||||
- [ ] Document version requirements clearly
|
||||
|
||||
## Risk Assessment
|
||||
|
||||
### High Risk Areas
|
||||
1. **Event System**: Complete paradigm shift
|
||||
2. **Exception Handling**: New resource loading model
|
||||
3. **Third-party Dependencies**: May not support SFML 3.0 yet
|
||||
|
||||
### Medium Risk Areas
|
||||
1. **Performance**: New implementations may differ
|
||||
2. **Platform Support**: New version may have issues
|
||||
3. **Documentation**: Less community knowledge
|
||||
|
||||
### Low Risk Areas
|
||||
1. **Basic Rendering**: Core concepts unchanged
|
||||
2. **CMake**: Straightforward updates
|
||||
3. **Enums**: Mechanical changes
|
||||
|
||||
## Conclusion
|
||||
|
||||
While SFML 3.0 offers significant improvements, the migration effort is substantial. Given that:
|
||||
|
||||
1. SFML 3.0 is very new (released December 2024)
|
||||
2. McRogueFace has heavy SFML integration
|
||||
3. We plan to implement `mcrfpy.sfml` soon
|
||||
4. The event system requires complete rewriting
|
||||
|
||||
**I recommend deferring SFML 3.0 migration** until after successfully implementing `mcrfpy.sfml` with SFML 2.6.1. This allows us to:
|
||||
- Deliver value sooner with `mcrfpy.sfml`
|
||||
- Learn from early adopters of SFML 3.0
|
||||
- Design our module API with migration in mind
|
||||
- Migrate when SFML 3.0 is more mature
|
||||
|
||||
The migration should be revisited in 6-12 months when SFML 3.0 has proven stability and wider adoption.
|
||||
200
SFML_EXPOSURE_RESEARCH.md
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
# SFML Exposure Research (#14)
|
||||
|
||||
## Executive Summary
|
||||
|
||||
After thorough research, I recommend **Option 3: Direct Integration** - implementing our own `mcrfpy.sfml` module with API compatibility to existing python-sfml bindings. This approach gives us full control while maintaining familiarity for developers who have used python-sfml.
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
### McRogueFace SFML Usage
|
||||
|
||||
**Version**: SFML 2.6.1 (confirmed in `modules/SFML/include/SFML/Config.hpp`)
|
||||
|
||||
**Integration Level**: Moderate to Heavy
|
||||
- SFML types appear in most header files
|
||||
- Core rendering depends on `sf::RenderTarget`
|
||||
- Event system uses `sf::Event` directly
|
||||
- Input mapping uses SFML enums
|
||||
|
||||
**SFML Modules Used**:
|
||||
- Graphics (sprites, textures, fonts, shapes)
|
||||
- Window (events, keyboard, mouse)
|
||||
- System (vectors, time, clocks)
|
||||
- Audio (sound effects, music)
|
||||
|
||||
**Already Exposed to Python**:
|
||||
- `mcrfpy.Color` → `sf::Color`
|
||||
- `mcrfpy.Vector` → `sf::Vector2f`
|
||||
- `mcrfpy.Font` → `sf::Font`
|
||||
- `mcrfpy.Texture` → `sf::Texture`
|
||||
|
||||
### Python-SFML Status
|
||||
|
||||
**Official python-sfml (pysfml)**:
|
||||
- Last version: 2.3.2 (supports SFML 2.3.2)
|
||||
- Last meaningful update: ~2019
|
||||
- Not compatible with SFML 2.6.1
|
||||
- Project appears abandoned (domain redirects elsewhere)
|
||||
- GitHub repo has 43 forks but no active maintained fork
|
||||
|
||||
**Alternatives**:
|
||||
- No other major Python SFML bindings found
|
||||
- Most alternatives were archived by 2021
|
||||
|
||||
## Option Analysis
|
||||
|
||||
### Option 1: Use Existing python-sfml
|
||||
**Pros**:
|
||||
- No development work needed
|
||||
- Established API
|
||||
|
||||
**Cons**:
|
||||
- Incompatible with SFML 2.6.1
|
||||
- Would require downgrading to SFML 2.3.2
|
||||
- Abandoned project (security/bug risks)
|
||||
- Installation issues reported
|
||||
|
||||
**Verdict**: Not viable due to version incompatibility and abandonment
|
||||
|
||||
### Option 2: Fork and Update python-sfml
|
||||
**Pros**:
|
||||
- Leverage existing codebase
|
||||
- Maintain API compatibility
|
||||
|
||||
**Cons**:
|
||||
- Significant work to update from 2.3.2 to 2.6.1
|
||||
- Cython complexity
|
||||
- Maintenance burden of external codebase
|
||||
- Still requires users to pip install separately
|
||||
|
||||
**Verdict**: High effort with limited benefit
|
||||
|
||||
### Option 3: Direct Integration (Recommended)
|
||||
**Pros**:
|
||||
- Full control over implementation
|
||||
- Tight integration with McRogueFace
|
||||
- No external dependencies
|
||||
- Can expose exactly what we need
|
||||
- Built-in module (no pip install)
|
||||
- Can maintain API compatibility with python-sfml
|
||||
|
||||
**Cons**:
|
||||
- Development effort required
|
||||
- Need to maintain bindings
|
||||
|
||||
**Verdict**: Best long-term solution
|
||||
|
||||
## Implementation Plan for Direct Integration
|
||||
|
||||
### 1. Module Structure
|
||||
```python
|
||||
# Built-in module: mcrfpy.sfml
|
||||
import mcrfpy.sfml as sf
|
||||
|
||||
# Maintain compatibility with python-sfml API
|
||||
window = sf.RenderWindow(sf.VideoMode(800, 600), "My Window")
|
||||
sprite = sf.Sprite()
|
||||
texture = sf.Texture()
|
||||
```
|
||||
|
||||
### 2. Priority Classes to Expose
|
||||
|
||||
**Phase 1 - Core Types** (Already partially done):
|
||||
- [x] `sf::Vector2f`, `sf::Vector2i`
|
||||
- [x] `sf::Color`
|
||||
- [ ] `sf::Rect` (FloatRect, IntRect)
|
||||
- [ ] `sf::VideoMode`
|
||||
- [ ] `sf::Time`, `sf::Clock`
|
||||
|
||||
**Phase 2 - Graphics**:
|
||||
- [x] `sf::Texture` (partial)
|
||||
- [x] `sf::Font` (partial)
|
||||
- [ ] `sf::Sprite` (full exposure)
|
||||
- [ ] `sf::Text`
|
||||
- [ ] `sf::Shape` hierarchy
|
||||
- [ ] `sf::View`
|
||||
- [ ] `sf::RenderWindow` (carefully managed)
|
||||
|
||||
**Phase 3 - Window/Input**:
|
||||
- [ ] `sf::Event` and event types
|
||||
- [ ] `sf::Keyboard` enums
|
||||
- [ ] `sf::Mouse` enums
|
||||
- [ ] `sf::Joystick`
|
||||
|
||||
**Phase 4 - Audio** (lower priority):
|
||||
- [ ] `sf::SoundBuffer`
|
||||
- [ ] `sf::Sound`
|
||||
- [ ] `sf::Music`
|
||||
|
||||
### 3. Design Principles
|
||||
|
||||
1. **API Compatibility**: Match python-sfml's API where possible
|
||||
2. **Memory Safety**: Use shared_ptr for resource management
|
||||
3. **Thread Safety**: Consider GIL implications
|
||||
4. **Integration**: Allow mixing with existing mcrfpy types
|
||||
5. **Documentation**: Comprehensive docstrings
|
||||
|
||||
### 4. Technical Considerations
|
||||
|
||||
**Resource Sharing**:
|
||||
- McRogueFace already manages SFML resources
|
||||
- Need to share textures/fonts between mcrfpy and sfml modules
|
||||
- Use the same underlying SFML objects
|
||||
|
||||
**Window Management**:
|
||||
- McRogueFace owns the main window
|
||||
- Expose read-only access or controlled modification
|
||||
- Prevent users from closing/destroying the game window
|
||||
|
||||
**Event Handling**:
|
||||
- Game engine processes events in main loop
|
||||
- Need mechanism to expose events to Python safely
|
||||
- Consider callback system or event queue
|
||||
|
||||
### 5. Implementation Phases
|
||||
|
||||
**Phase 1** (1-2 weeks):
|
||||
- Create `mcrfpy.sfml` module structure
|
||||
- Implement basic types (Vector, Color, Rect)
|
||||
- Add comprehensive tests
|
||||
|
||||
**Phase 2** (2-3 weeks):
|
||||
- Expose graphics classes
|
||||
- Implement resource sharing with mcrfpy
|
||||
- Create example scripts
|
||||
|
||||
**Phase 3** (2-3 weeks):
|
||||
- Add window/input functionality
|
||||
- Integrate with game event loop
|
||||
- Performance optimization
|
||||
|
||||
**Phase 4** (1 week):
|
||||
- Audio support
|
||||
- Documentation
|
||||
- PyPI packaging of mcrfpy.sfml separately
|
||||
|
||||
## Benefits of Direct Integration
|
||||
|
||||
1. **No Version Conflicts**: Always in sync with our SFML version
|
||||
2. **Better Performance**: Direct C++ bindings without Cython overhead
|
||||
3. **Selective Exposure**: Only expose what makes sense for game scripting
|
||||
4. **Integrated Documentation**: Part of McRogueFace docs
|
||||
5. **Future-Proof**: We control the implementation
|
||||
|
||||
## Migration Path for Users
|
||||
|
||||
Users familiar with python-sfml can easily migrate:
|
||||
```python
|
||||
# Old python-sfml code
|
||||
import sfml as sf
|
||||
|
||||
# New McRogueFace code
|
||||
import mcrfpy.sfml as sf
|
||||
# Most code remains the same!
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
Direct integration as `mcrfpy.sfml` provides the best balance of control, compatibility, and user experience. While it requires development effort, it ensures long-term maintainability and tight integration with McRogueFace's architecture.
|
||||
|
||||
The abandoned state of python-sfml actually presents an opportunity: we can provide a modern, maintained SFML binding for Python as part of McRogueFace, potentially attracting users who need SFML 2.6+ support.
|
||||
226
STRATEGIC_VISION.md
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
# McRogueFace Strategic Vision: Beyond Alpha
|
||||
|
||||
## 🎯 Three Transformative Directions
|
||||
|
||||
### 1. **The Roguelike Operating System** 🖥️
|
||||
|
||||
Transform McRogueFace into a platform where games are apps:
|
||||
|
||||
#### Core Platform Features
|
||||
- **Game Package Manager**: `mcrf install dungeon-crawler`
|
||||
- **Hot-swappable Game Modules**: Switch between games without restarting
|
||||
- **Shared Asset Library**: Common sprites, sounds, and UI components
|
||||
- **Cross-Game Saves**: Universal character/inventory system
|
||||
- **Multi-Game Sessions**: Run multiple roguelikes simultaneously in tabs
|
||||
|
||||
#### Technical Implementation
|
||||
```python
|
||||
# Future API Example
|
||||
import mcrfpy.platform as platform
|
||||
|
||||
# Install and launch games
|
||||
platform.install("nethack-remake")
|
||||
platform.install("pixel-dungeon-port")
|
||||
|
||||
# Create multi-game session
|
||||
session = platform.MultiGameSession()
|
||||
session.add_tab("nethack-remake", save_file="warrior_lvl_15.sav")
|
||||
session.add_tab("pixel-dungeon-port", new_game=True)
|
||||
session.run()
|
||||
```
|
||||
|
||||
### 2. **AI-Native Game Development** 🤖
|
||||
|
||||
Position McRogueFace as the first **AI-first roguelike engine**:
|
||||
|
||||
#### Integrated AI Features
|
||||
- **GPT-Powered NPCs**: Dynamic dialogue and quest generation
|
||||
- **Procedural Content via LLMs**: Describe a dungeon, AI generates it
|
||||
- **AI Dungeon Master**: Adaptive difficulty and narrative
|
||||
- **Code Assistant Integration**: Built-in AI helps write game logic
|
||||
|
||||
#### Revolutionary Possibilities
|
||||
```python
|
||||
# AI-Assisted Game Creation
|
||||
from mcrfpy import ai_tools
|
||||
|
||||
# Natural language level design
|
||||
dungeon = ai_tools.generate_dungeon("""
|
||||
Create a haunted library with 3 floors.
|
||||
First floor: Reading rooms with ghost librarians
|
||||
Second floor: Restricted section with magical traps
|
||||
Third floor: Ancient archive with boss encounter
|
||||
""")
|
||||
|
||||
# AI-driven NPCs
|
||||
npc = ai_tools.create_npc(
|
||||
personality="Grumpy dwarf merchant who secretly loves poetry",
|
||||
knowledge=["local rumors", "item prices", "hidden treasures"],
|
||||
dynamic_dialogue=True
|
||||
)
|
||||
```
|
||||
|
||||
### 3. **Web-Native Multiplayer Platform** 🌐
|
||||
|
||||
Make McRogueFace the **Discord of Roguelikes**:
|
||||
|
||||
#### Multiplayer Revolution
|
||||
- **Seamless Co-op**: Drop-in/drop-out multiplayer
|
||||
- **Competitive Modes**: Racing, PvP arenas, daily challenges
|
||||
- **Spectator System**: Watch and learn from others
|
||||
- **Cloud Saves**: Play anywhere, sync everywhere
|
||||
- **Social Features**: Guilds, tournaments, leaderboards
|
||||
|
||||
#### WebAssembly Future
|
||||
```python
|
||||
# Future Web API
|
||||
import mcrfpy.web as web
|
||||
|
||||
# Host a game room
|
||||
room = web.create_room("Epic Dungeon Run", max_players=4)
|
||||
room.set_rules(friendly_fire=False, shared_loot=True)
|
||||
room.open_to_public()
|
||||
|
||||
# Stream gameplay
|
||||
stream = web.GameStream(room)
|
||||
stream.to_twitch(channel="awesome_roguelike")
|
||||
```
|
||||
|
||||
## 🏗️ Architecture Evolution Roadmap
|
||||
|
||||
### Phase 1: Beta Foundation (3-4 months)
|
||||
**Focus**: Stability and Polish
|
||||
- Complete RenderTexture system (#6)
|
||||
- Implement save/load system
|
||||
- Add audio mixing and 3D sound
|
||||
- Create plugin architecture
|
||||
- **Deliverable**: Beta release with plugin support
|
||||
|
||||
### Phase 2: Platform Infrastructure (6-8 months)
|
||||
**Focus**: Multi-game Support
|
||||
- Game package format specification
|
||||
- Resource sharing system
|
||||
- Inter-game communication API
|
||||
- Cloud save infrastructure
|
||||
- **Deliverable**: McRogueFace Platform 1.0
|
||||
|
||||
### Phase 3: AI Integration (8-12 months)
|
||||
**Focus**: AI-Native Features
|
||||
- LLM integration framework
|
||||
- Procedural content pipelines
|
||||
- Natural language game scripting
|
||||
- AI behavior trees
|
||||
- **Deliverable**: McRogueFace AI Studio
|
||||
|
||||
### Phase 4: Web Deployment (12-18 months)
|
||||
**Focus**: Browser-based Gaming
|
||||
- WebAssembly compilation
|
||||
- WebRTC multiplayer
|
||||
- Cloud computation for AI
|
||||
- Mobile touch controls
|
||||
- **Deliverable**: play.mcrogueface.com
|
||||
|
||||
## 🎮 Killer App Ideas
|
||||
|
||||
### 1. **Roguelike Maker** (Like Mario Maker)
|
||||
- Visual dungeon editor
|
||||
- Share levels online
|
||||
- Play-test with AI
|
||||
- Community ratings
|
||||
|
||||
### 2. **The Infinite Dungeon**
|
||||
- Persistent world all players explore
|
||||
- Procedurally expands based on player actions
|
||||
- AI Dungeon Master creates personalized quests
|
||||
- Cross-platform play
|
||||
|
||||
### 3. **Roguelike Battle Royale**
|
||||
- 100 players start in connected dungeons
|
||||
- Dungeons collapse, forcing encounters
|
||||
- Last adventurer standing wins
|
||||
- AI-generated commentary
|
||||
|
||||
## 🛠️ Technical Innovations to Pursue
|
||||
|
||||
### 1. **Temporal Debugging**
|
||||
- Rewind game state
|
||||
- Fork timelines for "what-if" scenarios
|
||||
- Visual debugging of entity histories
|
||||
|
||||
### 2. **Neural Tileset Generation**
|
||||
- Train on existing tilesets
|
||||
- Generate infinite variations
|
||||
- Style transfer between games
|
||||
|
||||
### 3. **Quantum Roguelike Mechanics**
|
||||
- Superposition states for entities
|
||||
- Probability-based combat
|
||||
- Observer-effect puzzles
|
||||
|
||||
## 🌍 Community Building Strategy
|
||||
|
||||
### 1. **Education First**
|
||||
- University partnerships
|
||||
- Free curriculum: "Learn Python with Roguelikes"
|
||||
- Summer of Code participation
|
||||
- Student game jams
|
||||
|
||||
### 2. **Open Core Model**
|
||||
- Core engine: MIT licensed
|
||||
- Premium platforms: Cloud, AI, multiplayer
|
||||
- Revenue sharing for content creators
|
||||
- Sponsored tournaments
|
||||
|
||||
### 3. **Developer Ecosystem**
|
||||
- Comprehensive API documentation
|
||||
- Example games and tutorials
|
||||
- Asset marketplace
|
||||
- GitHub integration for mods
|
||||
|
||||
## 🎯 Success Metrics
|
||||
|
||||
### Year 1 Goals
|
||||
- 1,000+ games created on platform
|
||||
- 10,000+ monthly active developers
|
||||
- 3 AAA-quality showcase games
|
||||
- University curriculum adoption
|
||||
|
||||
### Year 2 Goals
|
||||
- 100,000+ monthly active players
|
||||
- $1M in platform transactions
|
||||
- Major game studio partnership
|
||||
- Native VR support
|
||||
|
||||
### Year 3 Goals
|
||||
- #1 roguelike development platform
|
||||
- IPO or acquisition readiness
|
||||
- 1M+ monthly active players
|
||||
- Industry standard for roguelikes
|
||||
|
||||
## 🚀 Next Immediate Actions
|
||||
|
||||
1. **Finish Beta Polish**
|
||||
- Merge alpha_streamline_2 → master
|
||||
- Complete RenderTexture (#6)
|
||||
- Implement basic save/load
|
||||
|
||||
2. **Build Community**
|
||||
- Launch Discord server
|
||||
- Create YouTube tutorials
|
||||
- Host first game jam
|
||||
|
||||
3. **Prototype AI Features**
|
||||
- Simple GPT integration
|
||||
- Procedural room descriptions
|
||||
- Dynamic NPC dialogue
|
||||
|
||||
4. **Plan Platform Architecture**
|
||||
- Design plugin system
|
||||
- Spec game package format
|
||||
- Cloud infrastructure research
|
||||
|
||||
---
|
||||
|
||||
*"McRogueFace: Not just an engine, but a universe of infinite dungeons."*
|
||||
|
||||
Remember: The best platforms create possibilities their creators never imagined. Build for the community you want to see, and they will create wonders.
|
||||
16
_test.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
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")
|
||||
|
||||
BIN
assets/48px_ui_icons-KenneyNL.png
Normal file
|
After Width: | Height: | Size: 181 KiB |
BIN
assets/Sprite-0001.ase
Normal file
BIN
assets/Sprite-0001.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
assets/alives_other.png
Normal file
|
After Width: | Height: | Size: 202 KiB |
BIN
assets/boom.wav
Normal file
BIN
assets/custom_player.png
Normal file
|
After Width: | Height: | Size: 8.4 KiB |
BIN
assets/gamescale_buildings.png
Normal file
|
After Width: | Height: | Size: 33 KiB |
BIN
assets/gamescale_decor.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
assets/kenney_TD_MR_IP.png
Normal file
|
After Width: | Height: | Size: 674 KiB |
BIN
assets/sfx/splat1.ogg
Normal file
BIN
assets/sfx/splat2.ogg
Normal file
BIN
assets/sfx/splat3.ogg
Normal file
BIN
assets/sfx/splat4.ogg
Normal file
BIN
assets/sfx/splat5.ogg
Normal file
BIN
assets/sfx/splat6.ogg
Normal file
BIN
assets/sfx/splat7.ogg
Normal file
BIN
assets/sfx/splat8.ogg
Normal file
BIN
assets/sfx/splat9.ogg
Normal file
BIN
assets/temp_logo.png
Normal file
|
After Width: | Height: | Size: 3 MiB |
BIN
assets/terrain.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
assets/terrain_alpha.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
assets/test_portraits.ase
Normal file
BIN
assets/test_portraits.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
127
automation_example.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
#!/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!")
|
||||
336
automation_exec_examples.py
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
#!/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")
|
||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 39 KiB |
|
|
@ -1,36 +0,0 @@
|
|||
@echo off
|
||||
REM Windows build script for McRogueFace
|
||||
REM Run this over SSH without Visual Studio GUI
|
||||
|
||||
echo Building McRogueFace for Windows...
|
||||
|
||||
REM Clean previous build
|
||||
if exist build_win rmdir /s /q build_win
|
||||
mkdir build_win
|
||||
cd build_win
|
||||
|
||||
REM Generate Visual Studio project files with CMake
|
||||
REM Use -G to specify generator, -A for architecture
|
||||
REM Visual Studio 2022 = "Visual Studio 17 2022"
|
||||
REM Visual Studio 2019 = "Visual Studio 16 2019"
|
||||
cmake -G "Visual Studio 17 2022" -A x64 ..
|
||||
if errorlevel 1 (
|
||||
echo CMake configuration failed!
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Build using MSBuild (comes with Visual Studio)
|
||||
REM You can also use cmake --build . --config Release
|
||||
msbuild McRogueFace.sln /p:Configuration=Release /p:Platform=x64 /m
|
||||
if errorlevel 1 (
|
||||
echo Build failed!
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo Build completed successfully!
|
||||
echo Executable location: build_win\Release\mcrogueface.exe
|
||||
|
||||
REM Alternative: Using cmake to build (works with any generator)
|
||||
REM cmake --build . --config Release --parallel
|
||||
|
||||
cd ..
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
@echo off
|
||||
REM Windows build script using cmake --build (generator-agnostic)
|
||||
REM This version works with any CMake generator
|
||||
|
||||
echo Building McRogueFace for Windows using CMake...
|
||||
|
||||
REM Set build directory
|
||||
set BUILD_DIR=build_win
|
||||
set CONFIG=Release
|
||||
|
||||
REM Clean previous build
|
||||
if exist %BUILD_DIR% rmdir /s /q %BUILD_DIR%
|
||||
mkdir %BUILD_DIR%
|
||||
cd %BUILD_DIR%
|
||||
|
||||
REM Configure with CMake
|
||||
REM You can change the generator here if needed:
|
||||
REM -G "Visual Studio 17 2022" (VS 2022)
|
||||
REM -G "Visual Studio 16 2019" (VS 2019)
|
||||
REM -G "MinGW Makefiles" (MinGW)
|
||||
REM -G "Ninja" (Ninja build system)
|
||||
cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=%CONFIG% ..
|
||||
if errorlevel 1 (
|
||||
echo CMake configuration failed!
|
||||
cd ..
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
REM Build using cmake (works with any generator)
|
||||
cmake --build . --config %CONFIG% --parallel
|
||||
if errorlevel 1 (
|
||||
echo Build failed!
|
||||
cd ..
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
echo.
|
||||
echo Build completed successfully!
|
||||
echo Executable: %BUILD_DIR%\%CONFIG%\mcrogueface.exe
|
||||
echo.
|
||||
|
||||
cd ..
|
||||
BIN
caption_invisible.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
caption_moved.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
caption_opacity_0.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
caption_opacity_25.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
caption_opacity_50.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
caption_visible.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
33
clean.sh
Executable file
|
|
@ -0,0 +1,33 @@
|
|||
#!/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}"
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
# CMake toolchain file for cross-compiling to Windows using MinGW-w64
|
||||
# Usage: cmake -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64-x86_64.cmake ..
|
||||
|
||||
set(CMAKE_SYSTEM_NAME Windows)
|
||||
set(CMAKE_SYSTEM_PROCESSOR x86_64)
|
||||
|
||||
# Specify the cross-compiler (use posix variant for std::mutex support)
|
||||
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc-posix)
|
||||
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++-posix)
|
||||
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
|
||||
|
||||
# Target environment location
|
||||
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
|
||||
|
||||
# Add MinGW system include directories for Windows headers
|
||||
include_directories(SYSTEM /usr/x86_64-w64-mingw32/include)
|
||||
|
||||
# Adjust search behavior
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
||||
|
||||
# Static linking of libgcc and libstdc++ to avoid runtime dependency issues
|
||||
# Enable auto-import for Python DLL data symbols
|
||||
set(CMAKE_EXE_LINKER_FLAGS_INIT "-static-libgcc -static-libstdc++ -Wl,--enable-auto-import")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-static-libgcc -static-libstdc++ -Wl,--enable-auto-import")
|
||||
|
||||
# Windows-specific defines
|
||||
add_definitions(-DWIN32 -D_WIN32 -D_WINDOWS)
|
||||
add_definitions(-DMINGW_HAS_SECURE_API)
|
||||
|
||||
# Disable console window for GUI applications (optional, can be overridden)
|
||||
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -mwindows")
|
||||
80
compare_html_docs.py
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Compare the original and improved HTML documentation."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
def compare_docs():
|
||||
"""Show key differences between the two HTML versions."""
|
||||
|
||||
print("HTML Documentation Improvements")
|
||||
print("=" * 50)
|
||||
|
||||
# Read both files
|
||||
original = Path("docs/api_reference.html")
|
||||
improved = Path("docs/api_reference_improved.html")
|
||||
|
||||
if not original.exists() or not improved.exists():
|
||||
print("Error: Documentation files not found")
|
||||
return
|
||||
|
||||
with open(original, 'r') as f:
|
||||
orig_content = f.read()
|
||||
|
||||
with open(improved, 'r') as f:
|
||||
imp_content = f.read()
|
||||
|
||||
print("\n📊 File Size Comparison:")
|
||||
print(f" Original: {len(orig_content):,} bytes")
|
||||
print(f" Improved: {len(imp_content):,} bytes")
|
||||
|
||||
print("\n✅ Key Improvements:")
|
||||
|
||||
# Check newline handling
|
||||
if '\\n' in orig_content and '\\n' not in imp_content:
|
||||
print(" • Fixed literal \\n in documentation text")
|
||||
|
||||
# Check table of contents
|
||||
if '[Classes](#classes)' in orig_content and '<a href="#classes">Classes</a>' in imp_content:
|
||||
print(" • Converted markdown links to proper HTML anchors")
|
||||
|
||||
# Check headings
|
||||
if '<h4>Args:</h4>' not in imp_content and '<strong>Arguments:</strong>' in imp_content:
|
||||
print(" • Fixed Args/Attributes formatting (no longer H4 headings)")
|
||||
|
||||
# Check method descriptions
|
||||
orig_count = orig_content.count('`Get bounding box')
|
||||
imp_count = imp_content.count('get_bounds(...)')
|
||||
if orig_count > imp_count:
|
||||
print(f" • Reduced duplicate method descriptions ({orig_count} → {imp_count})")
|
||||
|
||||
# Check Entity inheritance
|
||||
if 'Entity.*Inherits from: Drawable' not in imp_content:
|
||||
print(" • Fixed Entity class (no longer shows incorrect inheritance)")
|
||||
|
||||
# Check styling
|
||||
if '.container {' in imp_content and '.container {' not in orig_content:
|
||||
print(" • Enhanced visual styling with better typography and layout")
|
||||
|
||||
# Check class documentation
|
||||
if '<h4>Arguments:</h4>' in imp_content:
|
||||
print(" • Added detailed constructor arguments for all classes")
|
||||
|
||||
# Check automation
|
||||
if 'automation.click</code></h4>' in imp_content:
|
||||
print(" • Improved automation module documentation formatting")
|
||||
|
||||
print("\n📋 Documentation Coverage:")
|
||||
print(f" • Classes: {imp_content.count('class-section')} documented")
|
||||
print(f" • Functions: {imp_content.count('function-section')} documented")
|
||||
method_count = imp_content.count('<h5><code class="method">')
|
||||
print(f" • Methods: {method_count} documented")
|
||||
|
||||
print("\n✨ Visual Enhancements:")
|
||||
print(" • Professional color scheme with syntax highlighting")
|
||||
print(" • Responsive layout with max-width container")
|
||||
print(" • Clear visual hierarchy with styled headings")
|
||||
print(" • Improved code block formatting")
|
||||
print(" • Better spacing and typography")
|
||||
|
||||
if __name__ == '__main__':
|
||||
compare_docs()
|
||||
157
css_colors.txt
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
aqua #00FFFF
|
||||
black #000000
|
||||
blue #0000FF
|
||||
fuchsia #FF00FF
|
||||
gray #808080
|
||||
green #008000
|
||||
lime #00FF00
|
||||
maroon #800000
|
||||
navy #000080
|
||||
olive #808000
|
||||
purple #800080
|
||||
red #FF0000
|
||||
silver #C0C0C0
|
||||
teal #008080
|
||||
white #FFFFFF
|
||||
yellow #FFFF00
|
||||
aliceblue #F0F8FF
|
||||
antiquewhite #FAEBD7
|
||||
aqua #00FFFF
|
||||
aquamarine #7FFFD4
|
||||
azure #F0FFFF
|
||||
beige #F5F5DC
|
||||
bisque #FFE4C4
|
||||
black #000000
|
||||
blanchedalmond #FFEBCD
|
||||
blue #0000FF
|
||||
blueviolet #8A2BE2
|
||||
brown #A52A2A
|
||||
burlywood #DEB887
|
||||
cadetblue #5F9EA0
|
||||
chartreuse #7FFF00
|
||||
chocolate #D2691E
|
||||
coral #FF7F50
|
||||
cornflowerblue #6495ED
|
||||
cornsilk #FFF8DC
|
||||
crimson #DC143C
|
||||
cyan #00FFFF
|
||||
darkblue #00008B
|
||||
darkcyan #008B8B
|
||||
darkgoldenrod #B8860B
|
||||
darkgray #A9A9A9
|
||||
darkgreen #006400
|
||||
darkkhaki #BDB76B
|
||||
darkmagenta #8B008B
|
||||
darkolivegreen #556B2F
|
||||
darkorange #FF8C00
|
||||
darkorchid #9932CC
|
||||
darkred #8B0000
|
||||
darksalmon #E9967A
|
||||
darkseagreen #8FBC8F
|
||||
darkslateblue #483D8B
|
||||
darkslategray #2F4F4F
|
||||
darkturquoise #00CED1
|
||||
darkviolet #9400D3
|
||||
deeppink #FF1493
|
||||
deepskyblue #00BFFF
|
||||
dimgray #696969
|
||||
dodgerblue #1E90FF
|
||||
firebrick #B22222
|
||||
floralwhite #FFFAF0
|
||||
forestgreen #228B22
|
||||
fuchsia #FF00FF
|
||||
gainsboro #DCDCDC
|
||||
ghostwhite #F8F8FF
|
||||
gold #FFD700
|
||||
goldenrod #DAA520
|
||||
gray #7F7F7F
|
||||
green #008000
|
||||
greenyellow #ADFF2F
|
||||
honeydew #F0FFF0
|
||||
hotpink #FF69B4
|
||||
indianred #CD5C5C
|
||||
indigo #4B0082
|
||||
ivory #FFFFF0
|
||||
khaki #F0E68C
|
||||
lavender #E6E6FA
|
||||
lavenderblush #FFF0F5
|
||||
lawngreen #7CFC00
|
||||
lemonchiffon #FFFACD
|
||||
lightblue #ADD8E6
|
||||
lightcoral #F08080
|
||||
lightcyan #E0FFFF
|
||||
lightgoldenrodyellow #FAFAD2
|
||||
lightgreen #90EE90
|
||||
lightgrey #D3D3D3
|
||||
lightpink #FFB6C1
|
||||
lightsalmon #FFA07A
|
||||
lightseagreen #20B2AA
|
||||
lightskyblue #87CEFA
|
||||
lightslategray #778899
|
||||
lightsteelblue #B0C4DE
|
||||
lightyellow #FFFFE0
|
||||
lime #00FF00
|
||||
limegreen #32CD32
|
||||
linen #FAF0E6
|
||||
magenta #FF00FF
|
||||
maroon #800000
|
||||
mediumaquamarine #66CDAA
|
||||
mediumblue #0000CD
|
||||
mediumorchid #BA55D3
|
||||
mediumpurple #9370DB
|
||||
mediumseagreen #3CB371
|
||||
mediumslateblue #7B68EE
|
||||
mediumspringgreen #00FA9A
|
||||
mediumturquoise #48D1CC
|
||||
mediumvioletred #C71585
|
||||
midnightblue #191970
|
||||
mintcream #F5FFFA
|
||||
mistyrose #FFE4E1
|
||||
moccasin #FFE4B5
|
||||
navajowhite #FFDEAD
|
||||
navy #000080
|
||||
navyblue #9FAFDF
|
||||
oldlace #FDF5E6
|
||||
olive #808000
|
||||
olivedrab #6B8E23
|
||||
orange #FFA500
|
||||
orangered #FF4500
|
||||
orchid #DA70D6
|
||||
palegoldenrod #EEE8AA
|
||||
palegreen #98FB98
|
||||
paleturquoise #AFEEEE
|
||||
palevioletred #DB7093
|
||||
papayawhip #FFEFD5
|
||||
peachpuff #FFDAB9
|
||||
peru #CD853F
|
||||
pink #FFC0CB
|
||||
plum #DDA0DD
|
||||
powderblue #B0E0E6
|
||||
purple #800080
|
||||
red #FF0000
|
||||
rosybrown #BC8F8F
|
||||
royalblue #4169E1
|
||||
saddlebrown #8B4513
|
||||
salmon #FA8072
|
||||
sandybrown #FA8072
|
||||
seagreen #2E8B57
|
||||
seashell #FFF5EE
|
||||
sienna #A0522D
|
||||
silver #C0C0C0
|
||||
skyblue #87CEEB
|
||||
slateblue #6A5ACD
|
||||
slategray #708090
|
||||
snow #FFFAFA
|
||||
springgreen #00FF7F
|
||||
steelblue #4682B4
|
||||
tan #D2B48C
|
||||
teal #008080
|
||||
thistle #D8BFD8
|
||||
tomato #FF6347
|
||||
turquoise #40E0D0
|
||||
violet #EE82EE
|
||||
wheat #F5DEB3
|
||||
white #FFFFFF
|
||||
whitesmoke #F5F5F5
|
||||
yellow #FFFF00
|
||||
yellowgreen #9ACD32
|
||||
54
deps/platform/linux/platform.h
vendored
|
|
@ -1,54 +1,6 @@
|
|||
#ifndef __PLATFORM
|
||||
#define __PLATFORM
|
||||
#define __PLATFORM_SET_PYTHON_SEARCH_PATHS 1
|
||||
|
||||
#ifdef __EMSCRIPTEN__
|
||||
// WASM/Emscripten platform - no /proc filesystem, limited std::filesystem support
|
||||
|
||||
std::wstring executable_path()
|
||||
{
|
||||
// In WASM, the executable is at the root of the virtual filesystem
|
||||
return L"/";
|
||||
}
|
||||
|
||||
std::wstring executable_filename()
|
||||
{
|
||||
// In WASM, we use a fixed executable name
|
||||
return L"/mcrogueface";
|
||||
}
|
||||
|
||||
std::wstring working_path()
|
||||
{
|
||||
// In WASM, working directory is root of virtual filesystem
|
||||
return L"/";
|
||||
}
|
||||
|
||||
std::string narrow_string(std::wstring convertme)
|
||||
{
|
||||
// Simple conversion for ASCII/UTF-8 compatible strings
|
||||
std::string result;
|
||||
result.reserve(convertme.size());
|
||||
for (wchar_t wc : convertme) {
|
||||
if (wc < 128) {
|
||||
result.push_back(static_cast<char>(wc));
|
||||
} else {
|
||||
// For non-ASCII, use a simple UTF-8 encoding
|
||||
if (wc < 0x800) {
|
||||
result.push_back(static_cast<char>(0xC0 | (wc >> 6)));
|
||||
result.push_back(static_cast<char>(0x80 | (wc & 0x3F)));
|
||||
} else {
|
||||
result.push_back(static_cast<char>(0xE0 | (wc >> 12)));
|
||||
result.push_back(static_cast<char>(0x80 | ((wc >> 6) & 0x3F)));
|
||||
result.push_back(static_cast<char>(0x80 | (wc & 0x3F)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
#else
|
||||
// Native Linux platform
|
||||
|
||||
std::wstring executable_path()
|
||||
{
|
||||
/*
|
||||
|
|
@ -60,7 +12,7 @@ std::wstring executable_path()
|
|||
return exec_path.wstring();
|
||||
//size_t path_index = exec_path.find_last_of('/');
|
||||
//return exec_path.substr(0, path_index);
|
||||
|
||||
|
||||
}
|
||||
|
||||
std::wstring executable_filename()
|
||||
|
|
@ -85,6 +37,4 @@ std::string narrow_string(std::wstring convertme)
|
|||
return converter.to_bytes(convertme);
|
||||
}
|
||||
|
||||
#endif // __EMSCRIPTEN__
|
||||
|
||||
#endif // __PLATFORM
|
||||
#endif
|
||||
|
|
|
|||
8
deps/platform/windows/platform.h
vendored
|
|
@ -1,12 +1,12 @@
|
|||
#ifndef __PLATFORM
|
||||
#define __PLATFORM
|
||||
#define __PLATFORM_SET_PYTHON_SEARCH_PATHS 1
|
||||
#include <windows.h>
|
||||
#define __PLATFORM_SET_PYTHON_SEARCH_PATHS 0
|
||||
#include <Windows.h>
|
||||
|
||||
std::wstring executable_path()
|
||||
{
|
||||
wchar_t buffer[MAX_PATH];
|
||||
GetModuleFileNameW(NULL, buffer, MAX_PATH); // Use explicit Unicode version
|
||||
GetModuleFileName(NULL, buffer, MAX_PATH);
|
||||
std::wstring exec_path = buffer;
|
||||
size_t path_index = exec_path.find_last_of(L"\\/");
|
||||
return exec_path.substr(0, path_index);
|
||||
|
|
@ -15,7 +15,7 @@ std::wstring executable_path()
|
|||
std::wstring executable_filename()
|
||||
{
|
||||
wchar_t buffer[MAX_PATH];
|
||||
GetModuleFileNameW(NULL, buffer, MAX_PATH); // Use explicit Unicode version
|
||||
GetModuleFileName(NULL, buffer, MAX_PATH);
|
||||
std::wstring exec_path = buffer;
|
||||
return exec_path;
|
||||
}
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 35 KiB |
852
docs/api_reference.html
Normal file
|
|
@ -0,0 +1,852 @@
|
|||
<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta charset="UTF-8">
|
||||
<title>McRogueFace API Reference</title>
|
||||
<style>
|
||||
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
line-height: 1.6; color: #333; max-width: 900px; margin: 0 auto; padding: 20px; }
|
||||
h1, h2, h3, h4, h5 { color: #2c3e50; margin-top: 24px; }
|
||||
h1 { border-bottom: 2px solid #3498db; padding-bottom: 10px; }
|
||||
h2 { border-bottom: 1px solid #ecf0f1; padding-bottom: 8px; }
|
||||
code { background: #f4f4f4; padding: 2px 4px; border-radius: 3px; font-size: 90%; }
|
||||
pre { background: #f4f4f4; padding: 12px; border-radius: 5px; overflow-x: auto; }
|
||||
pre code { background: none; padding: 0; }
|
||||
blockquote { border-left: 4px solid #3498db; margin: 0; padding-left: 16px; color: #7f8c8d; }
|
||||
hr { border: none; border-top: 1px solid #ecf0f1; margin: 24px 0; }
|
||||
a { color: #3498db; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.property { color: #27ae60; }
|
||||
.method { color: #2980b9; }
|
||||
.class-name { color: #8e44ad; font-weight: bold; }
|
||||
ul { padding-left: 24px; }
|
||||
li { margin: 4px 0; }
|
||||
|
||||
</style>
|
||||
</head><body>
|
||||
<h1>McRogueFace API Reference</h1>
|
||||
|
||||
<em>Generated on 2025-07-08 10:11:22</em>
|
||||
|
||||
<h2>Overview</h2>
|
||||
|
||||
<p>McRogueFace Python API\n\nCore game engine interface for creating roguelike games with Python.\n\nThis module provides:\n- Scene management (createScene, setScene, currentScene)\n- UI components (Frame, Caption, Sprite, Grid)\n- Entity system for game objects\n- Audio playback (sound effects and music)\n- Timer system for scheduled events\n- Input handling\n- Performance metrics\n\nExample:\n import mcrfpy\n \n # Create a new scene\n mcrfpy.createScene('game')\n mcrfpy.setScene('game')\n \n # Add UI elements\n frame = mcrfpy.Frame(10, 10, 200, 100)\n caption = mcrfpy.Caption('Hello World', 50, 50)\n mcrfpy.sceneUI().extend([frame, caption])\n</p>
|
||||
|
||||
<h2>Table of Contents</h2>
|
||||
|
||||
<ul>
|
||||
<li>[Classes](#classes)</li>
|
||||
<li>[Functions](#functions)</li>
|
||||
<li>[Automation Module](#automation-module)</li>
|
||||
</ul>
|
||||
|
||||
<h2>Classes</h2>
|
||||
|
||||
<h3>UI Components</h3>
|
||||
|
||||
<h3>class `Caption`</h3>
|
||||
<em>Inherits from: Drawable</em>
|
||||
|
||||
<pre><code class="language-python">
|
||||
Caption(text='', x=0, y=0, font=None, fill_color=None, outline_color=None, outline=0, click=None)
|
||||
</code></pre>
|
||||
|
||||
<p>A text display UI element with customizable font and styling.</p>
|
||||
|
||||
<p>Args:</p>
|
||||
<p>text (str): The text content to display. Default: ''</p>
|
||||
<p>x (float): X position in pixels. Default: 0</p>
|
||||
<p>y (float): Y position in pixels. Default: 0</p>
|
||||
<p>font (Font): Font object for text rendering. Default: engine default font</p>
|
||||
<p>fill_color (Color): Text fill color. Default: (255, 255, 255, 255)</p>
|
||||
<p>outline_color (Color): Text outline color. Default: (0, 0, 0, 255)</p>
|
||||
<p>outline (float): Text outline thickness. Default: 0</p>
|
||||
<p>click (callable): Click event handler. Default: None</p>
|
||||
|
||||
<p>Attributes:</p>
|
||||
<p>text (str): The displayed text content</p>
|
||||
<p>x, y (float): Position in pixels</p>
|
||||
<p>font (Font): Font used for rendering</p>
|
||||
<p>fill_color, outline_color (Color): Text appearance</p>
|
||||
<p>outline (float): Outline thickness</p>
|
||||
<p>click (callable): Click event handler</p>
|
||||
<p>visible (bool): Visibility state</p>
|
||||
<p>z_index (int): Rendering order</p>
|
||||
<p>w, h (float): Read-only computed size based on text and font</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`Get bounding box as (x, y, width, height)`</h5>
|
||||
<p>Get bounding box as (x, y, width, height)</p>
|
||||
|
||||
<h5>`Move by relative offset (dx, dy)`</h5>
|
||||
<p>Move by relative offset (dx, dy)</p>
|
||||
|
||||
<h5>`Resize to new dimensions (width, height)`</h5>
|
||||
<p>Resize to new dimensions (width, height)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Entity`</h3>
|
||||
<em>Inherits from: Drawable</em>
|
||||
|
||||
<p>UIEntity objects</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`at(...)`</h5>
|
||||
|
||||
<h5>`die(...)`</h5>
|
||||
<p>Remove this entity from its grid</p>
|
||||
|
||||
<h5>`Get bounding box as (x, y, width, height)`</h5>
|
||||
<p>Get bounding box as (x, y, width, height)</p>
|
||||
|
||||
<h5>`index(...)`</h5>
|
||||
|
||||
<h5>`Move by relative offset (dx, dy)`</h5>
|
||||
<p>Move by relative offset (dx, dy)</p>
|
||||
|
||||
<h5>`Resize to new dimensions (width, height)`</h5>
|
||||
<p>Resize to new dimensions (width, height)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Frame`</h3>
|
||||
<em>Inherits from: Drawable</em>
|
||||
|
||||
<pre><code class="language-python">
|
||||
Frame(x=0, y=0, w=0, h=0, fill_color=None, outline_color=None, outline=0, click=None, children=None)
|
||||
</code></pre>
|
||||
|
||||
<p>A rectangular frame UI element that can contain other drawable elements.</p>
|
||||
|
||||
<p>Args:</p>
|
||||
<p>x (float): X position in pixels. Default: 0</p>
|
||||
<p>y (float): Y position in pixels. Default: 0</p>
|
||||
<p>w (float): Width in pixels. Default: 0</p>
|
||||
<p>h (float): Height in pixels. Default: 0</p>
|
||||
<p>fill_color (Color): Background fill color. Default: (0, 0, 0, 128)</p>
|
||||
<p>outline_color (Color): Border outline color. Default: (255, 255, 255, 255)</p>
|
||||
<p>outline (float): Border outline thickness. Default: 0</p>
|
||||
<p>click (callable): Click event handler. Default: None</p>
|
||||
<p>children (list): Initial list of child drawable elements. Default: None</p>
|
||||
|
||||
<p>Attributes:</p>
|
||||
<p>x, y (float): Position in pixels</p>
|
||||
<p>w, h (float): Size in pixels</p>
|
||||
<p>fill_color, outline_color (Color): Visual appearance</p>
|
||||
<p>outline (float): Border thickness</p>
|
||||
<p>click (callable): Click event handler</p>
|
||||
<p>children (list): Collection of child drawable elements</p>
|
||||
<p>visible (bool): Visibility state</p>
|
||||
<p>z_index (int): Rendering order</p>
|
||||
<p>clip_children (bool): Whether to clip children to frame bounds</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`Get bounding box as (x, y, width, height)`</h5>
|
||||
<p>Get bounding box as (x, y, width, height)</p>
|
||||
|
||||
<h5>`Move by relative offset (dx, dy)`</h5>
|
||||
<p>Move by relative offset (dx, dy)</p>
|
||||
|
||||
<h5>`Resize to new dimensions (width, height)`</h5>
|
||||
<p>Resize to new dimensions (width, height)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Grid`</h3>
|
||||
<em>Inherits from: Drawable</em>
|
||||
|
||||
<pre><code class="language-python">
|
||||
Grid(x=0, y=0, grid_size=(20, 20), texture=None, tile_width=16, tile_height=16, scale=1.0, click=None)
|
||||
</code></pre>
|
||||
|
||||
<p>A grid-based tilemap UI element for rendering tile-based levels and game worlds.</p>
|
||||
|
||||
<p>Args:</p>
|
||||
<p>x (float): X position in pixels. Default: 0</p>
|
||||
<p>y (float): Y position in pixels. Default: 0</p>
|
||||
<p>grid_size (tuple): Grid dimensions as (width, height) in tiles. Default: (20, 20)</p>
|
||||
<p>texture (Texture): Texture atlas containing tile sprites. Default: None</p>
|
||||
<p>tile_width (int): Width of each tile in pixels. Default: 16</p>
|
||||
<p>tile_height (int): Height of each tile in pixels. Default: 16</p>
|
||||
<p>scale (float): Grid scaling factor. Default: 1.0</p>
|
||||
<p>click (callable): Click event handler. Default: None</p>
|
||||
|
||||
<p>Attributes:</p>
|
||||
<p>x, y (float): Position in pixels</p>
|
||||
<p>grid_size (tuple): Grid dimensions (width, height) in tiles</p>
|
||||
<p>tile_width, tile_height (int): Tile dimensions in pixels</p>
|
||||
<p>texture (Texture): Tile texture atlas</p>
|
||||
<p>scale (float): Scale multiplier</p>
|
||||
<p>points (list): 2D array of GridPoint objects for tile data</p>
|
||||
<p>entities (list): Collection of Entity objects in the grid</p>
|
||||
<p>background_color (Color): Grid background color</p>
|
||||
<p>click (callable): Click event handler</p>
|
||||
<p>visible (bool): Visibility state</p>
|
||||
<p>z_index (int): Rendering order</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`at(...)`</h5>
|
||||
|
||||
<h5>`Get bounding box as (x, y, width, height)`</h5>
|
||||
<p>Get bounding box as (x, y, width, height)</p>
|
||||
|
||||
<h5>`Move by relative offset (dx, dy)`</h5>
|
||||
<p>Move by relative offset (dx, dy)</p>
|
||||
|
||||
<h5>`Resize to new dimensions (width, height)`</h5>
|
||||
<p>Resize to new dimensions (width, height)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Sprite`</h3>
|
||||
<em>Inherits from: Drawable</em>
|
||||
|
||||
<pre><code class="language-python">
|
||||
Sprite(x=0, y=0, texture=None, sprite_index=0, scale=1.0, click=None)
|
||||
</code></pre>
|
||||
|
||||
<p>A sprite UI element that displays a texture or portion of a texture atlas.</p>
|
||||
|
||||
<p>Args:</p>
|
||||
<p>x (float): X position in pixels. Default: 0</p>
|
||||
<p>y (float): Y position in pixels. Default: 0</p>
|
||||
<p>texture (Texture): Texture object to display. Default: None</p>
|
||||
<p>sprite_index (int): Index into texture atlas (if applicable). Default: 0</p>
|
||||
<p>scale (float): Sprite scaling factor. Default: 1.0</p>
|
||||
<p>click (callable): Click event handler. Default: None</p>
|
||||
|
||||
<p>Attributes:</p>
|
||||
<p>x, y (float): Position in pixels</p>
|
||||
<p>texture (Texture): The texture being displayed</p>
|
||||
<p>sprite_index (int): Current sprite index in texture atlas</p>
|
||||
<p>scale (float): Scale multiplier</p>
|
||||
<p>click (callable): Click event handler</p>
|
||||
<p>visible (bool): Visibility state</p>
|
||||
<p>z_index (int): Rendering order</p>
|
||||
<p>w, h (float): Read-only computed size based on texture and scale</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`Get bounding box as (x, y, width, height)`</h5>
|
||||
<p>Get bounding box as (x, y, width, height)</p>
|
||||
|
||||
<h5>`Move by relative offset (dx, dy)`</h5>
|
||||
<p>Move by relative offset (dx, dy)</p>
|
||||
|
||||
<h5>`Resize to new dimensions (width, height)`</h5>
|
||||
<p>Resize to new dimensions (width, height)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>Collections</h3>
|
||||
|
||||
<h3>class `EntityCollection`</h3>
|
||||
|
||||
<p>Iterable, indexable collection of Entities</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`append(...)`</h5>
|
||||
|
||||
<h5>`count(...)`</h5>
|
||||
|
||||
<h5>`extend(...)`</h5>
|
||||
|
||||
<h5>`index(...)`</h5>
|
||||
|
||||
<h5>`remove(...)`</h5>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `UICollection`</h3>
|
||||
|
||||
<p>Iterable, indexable collection of UI objects</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`append(...)`</h5>
|
||||
|
||||
<h5>`count(...)`</h5>
|
||||
|
||||
<h5>`extend(...)`</h5>
|
||||
|
||||
<h5>`index(...)`</h5>
|
||||
|
||||
<h5>`remove(...)`</h5>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `UICollectionIter`</h3>
|
||||
|
||||
<p>Iterator for a collection of UI objects</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `UIEntityCollectionIter`</h3>
|
||||
|
||||
<p>Iterator for a collection of UI objects</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>System Types</h3>
|
||||
|
||||
<h3>class `Color`</h3>
|
||||
|
||||
<p>SFML Color Object</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`Create Color from hex string (e.g., '#FF0000' or 'FF0000')`</h5>
|
||||
<p>Create Color from hex string (e.g., '#FF0000' or 'FF0000')</p>
|
||||
|
||||
<h5>`lerp(...)`</h5>
|
||||
<p>Linearly interpolate between this color and another</p>
|
||||
|
||||
<h5>`to_hex(...)`</h5>
|
||||
<p>Convert Color to hex string</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Font`</h3>
|
||||
|
||||
<p>SFML Font Object</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Texture`</h3>
|
||||
|
||||
<p>SFML Texture Object</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Vector`</h3>
|
||||
|
||||
<p>SFML Vector Object</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`angle(...)`</h5>
|
||||
|
||||
<h5>`copy(...)`</h5>
|
||||
|
||||
<h5>`distance_to(...)`</h5>
|
||||
<p>Return the distance to another vector</p>
|
||||
|
||||
<h5>`dot(...)`</h5>
|
||||
|
||||
<h5>`magnitude(...)`</h5>
|
||||
<p>Return the length of the vector</p>
|
||||
|
||||
<h5>`magnitude_squared(...)`</h5>
|
||||
<p>Return the squared length of the vector</p>
|
||||
|
||||
<h5>`normalize(...)`</h5>
|
||||
<p>Return a unit vector in the same direction</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>Other Classes</h3>
|
||||
|
||||
<h3>class `Animation`</h3>
|
||||
|
||||
<p>Animation object for animating UI properties</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`get_current_value(...)`</h5>
|
||||
<p>Get the current interpolated value</p>
|
||||
|
||||
<h5>`start(...)`</h5>
|
||||
<p>Start the animation on a target UIDrawable</p>
|
||||
|
||||
<h5>`Update the animation by deltaTime (returns True if still running)`</h5>
|
||||
<p>Update the animation by deltaTime (returns True if still running)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Drawable`</h3>
|
||||
|
||||
<p>Base class for all drawable UI elements</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`Get bounding box as (x, y, width, height)`</h5>
|
||||
<p>Get bounding box as (x, y, width, height)</p>
|
||||
|
||||
<h5>`Move by relative offset (dx, dy)`</h5>
|
||||
<p>Move by relative offset (dx, dy)</p>
|
||||
|
||||
<h5>`Resize to new dimensions (width, height)`</h5>
|
||||
<p>Resize to new dimensions (width, height)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `GridPoint`</h3>
|
||||
|
||||
<p>UIGridPoint object</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `GridPointState`</h3>
|
||||
|
||||
<p>UIGridPointState object</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Scene`</h3>
|
||||
|
||||
<p>Base class for object-oriented scenes</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`activate(...)`</h5>
|
||||
<p>Make this the active scene</p>
|
||||
|
||||
<h5>`get_ui(...)`</h5>
|
||||
<p>Get the UI element collection for this scene</p>
|
||||
|
||||
<h5>`Register a keyboard handler function (alternative to overriding on_keypress)`</h5>
|
||||
<p>Register a keyboard handler function (alternative to overriding on_keypress)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Timer`</h3>
|
||||
|
||||
<p>Timer object for scheduled callbacks</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`cancel(...)`</h5>
|
||||
<p>Cancel the timer and remove it from the system</p>
|
||||
|
||||
<h5>`pause(...)`</h5>
|
||||
<p>Pause the timer</p>
|
||||
|
||||
<h5>`restart(...)`</h5>
|
||||
<p>Restart the timer from the current time</p>
|
||||
|
||||
<h5>`resume(...)`</h5>
|
||||
<p>Resume a paused timer</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Window`</h3>
|
||||
|
||||
<p>Window singleton for accessing and modifying the game window properties</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`center(...)`</h5>
|
||||
<p>Center the window on the screen</p>
|
||||
|
||||
<h5>`get(...)`</h5>
|
||||
<p>Get the Window singleton instance</p>
|
||||
|
||||
<h5>`screenshot(...)`</h5>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Functions</h2>
|
||||
|
||||
<h3>Scene Management</h3>
|
||||
|
||||
<h3>`createScene(name: str)`</h3>
|
||||
|
||||
|
||||
<p>Create a new empty scene.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>name: Unique name for the new scene</p>
|
||||
|
||||
<em>*Raises:*</em>
|
||||
<p>ValueError: If a scene with this name already exists</p>
|
||||
|
||||
<em>*Note:*</em>
|
||||
<p>The scene is created but not made active. Use setScene() to switch to it.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`currentScene()`</h3>
|
||||
|
||||
|
||||
<p>Get the name of the currently active scene.</p>
|
||||
|
||||
<em>*Returns:*</em>
|
||||
<p>str: Name of the current scene</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`keypressScene(handler: callable)`</h3>
|
||||
|
||||
|
||||
<p>Set the keyboard event handler for the current scene.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>handler: Callable that receives (key_name: str, is_pressed: bool)</p>
|
||||
|
||||
<em>*Example:*</em>
|
||||
<p>def on_key(key, pressed):</p>
|
||||
<p>if key == 'A' and pressed:</p>
|
||||
<p>print('A key pressed')</p>
|
||||
<p>mcrfpy.keypressScene(on_key)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`sceneUI(scene: str = None)`</h3>
|
||||
|
||||
|
||||
<p>Get all UI elements for a scene.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>scene: Scene name. If None, uses current scene</p>
|
||||
|
||||
<em>*Returns:*</em>
|
||||
<p>list: All UI elements (Frame, Caption, Sprite, Grid) in the scene</p>
|
||||
|
||||
<em>*Raises:*</em>
|
||||
<p>KeyError: If the specified scene doesn't exist</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`setScene(scene: str, transition: str = None, duration: float = 0.0)`</h3>
|
||||
|
||||
|
||||
<p>Switch to a different scene with optional transition effect.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>scene: Name of the scene to switch to</p>
|
||||
<p>transition: Transition type ('fade', 'slide_left', 'slide_right', 'slide_up', 'slide_down')</p>
|
||||
<p>duration: Transition duration in seconds (default: 0.0 for instant)</p>
|
||||
|
||||
<em>*Raises:*</em>
|
||||
<p>KeyError: If the scene doesn't exist</p>
|
||||
<p>ValueError: If the transition type is invalid</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>Audio</h3>
|
||||
|
||||
<h3>`createSoundBuffer(filename: str)`</h3>
|
||||
|
||||
|
||||
<p>Load a sound effect from a file and return its buffer ID.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>filename: Path to the sound file (WAV, OGG, FLAC)</p>
|
||||
|
||||
<em>*Returns:*</em>
|
||||
<p>int: Buffer ID for use with playSound()</p>
|
||||
|
||||
<em>*Raises:*</em>
|
||||
<p>RuntimeError: If the file cannot be loaded</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`getMusicVolume()`</h3>
|
||||
|
||||
|
||||
<p>Get the current music volume level.</p>
|
||||
|
||||
<em>*Returns:*</em>
|
||||
<p>int: Current volume (0-100)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`getSoundVolume()`</h3>
|
||||
|
||||
|
||||
<p>Get the current sound effects volume level.</p>
|
||||
|
||||
<em>*Returns:*</em>
|
||||
<p>int: Current volume (0-100)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`loadMusic(filename: str)`</h3>
|
||||
|
||||
|
||||
<p>Load and immediately play background music from a file.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>filename: Path to the music file (WAV, OGG, FLAC)</p>
|
||||
|
||||
<em>*Note:*</em>
|
||||
<p>Only one music track can play at a time. Loading new music stops the current track.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`playSound(buffer_id: int)`</h3>
|
||||
|
||||
|
||||
<p>Play a sound effect using a previously loaded buffer.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>buffer_id: Sound buffer ID returned by createSoundBuffer()</p>
|
||||
|
||||
<em>*Raises:*</em>
|
||||
<p>RuntimeError: If the buffer ID is invalid</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`setMusicVolume(volume: int)`</h3>
|
||||
|
||||
|
||||
<p>Set the global music volume.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>volume: Volume level from 0 (silent) to 100 (full volume)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`setSoundVolume(volume: int)`</h3>
|
||||
|
||||
|
||||
<p>Set the global sound effects volume.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>volume: Volume level from 0 (silent) to 100 (full volume)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>UI Utilities</h3>
|
||||
|
||||
<h3>`find(name: str, scene: str = None)`</h3>
|
||||
|
||||
|
||||
<p>Find the first UI element with the specified name.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>name: Exact name to search for</p>
|
||||
<p>scene: Scene to search in (default: current scene)</p>
|
||||
|
||||
<em>*Returns:*</em>
|
||||
<p>Frame, Caption, Sprite, Grid, or Entity if found; None otherwise</p>
|
||||
|
||||
<em>*Note:*</em>
|
||||
<p>Searches scene UI elements and entities within grids.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`findAll(pattern: str, scene: str = None)`</h3>
|
||||
|
||||
|
||||
<p>Find all UI elements matching a name pattern.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>pattern: Name pattern with optional wildcards (* matches any characters)</p>
|
||||
<p>scene: Scene to search in (default: current scene)</p>
|
||||
|
||||
<em>*Returns:*</em>
|
||||
<p>list: All matching UI elements and entities</p>
|
||||
|
||||
<em>*Example:*</em>
|
||||
<p>findAll('enemy*') # Find all elements starting with 'enemy'</p>
|
||||
<p>findAll('*_button') # Find all elements ending with '_button'</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>System</h3>
|
||||
|
||||
<h3>`delTimer(name: str)`</h3>
|
||||
|
||||
|
||||
<p>Stop and remove a timer.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>name: Timer identifier to remove</p>
|
||||
|
||||
<em>*Note:*</em>
|
||||
<p>No error is raised if the timer doesn't exist.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`exit()`</h3>
|
||||
|
||||
|
||||
<p>Cleanly shut down the game engine and exit the application.</p>
|
||||
|
||||
<em>*Note:*</em>
|
||||
<p>This immediately closes the window and terminates the program.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`getMetrics()`</h3>
|
||||
|
||||
|
||||
<p>Get current performance metrics.</p>
|
||||
|
||||
<em>*Returns:*</em>
|
||||
<p>dict: Performance data with keys:</p>
|
||||
<ul>
|
||||
<li>frame_time: Last frame duration in seconds</li>
|
||||
<li>avg_frame_time: Average frame time</li>
|
||||
<li>fps: Frames per second</li>
|
||||
<li>draw_calls: Number of draw calls</li>
|
||||
<li>ui_elements: Total UI element count</li>
|
||||
<li>visible_elements: Visible element count</li>
|
||||
<li>current_frame: Frame counter</li>
|
||||
<li>runtime: Total runtime in seconds</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`setScale(multiplier: float)`</h3>
|
||||
|
||||
|
||||
<p>Scale the game window size.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>multiplier: Scale factor (e.g., 2.0 for double size)</p>
|
||||
|
||||
<em>*Note:*</em>
|
||||
<p>The internal resolution remains 1024x768, but the window is scaled.</p>
|
||||
<p>This is deprecated - use Window.resolution instead.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`setTimer(name: str, handler: callable, interval: int)`</h3>
|
||||
|
||||
|
||||
<p>Create or update a recurring timer.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>name: Unique identifier for the timer</p>
|
||||
<p>handler: Function called with (runtime: float) parameter</p>
|
||||
<p>interval: Time between calls in milliseconds</p>
|
||||
|
||||
<em>*Note:*</em>
|
||||
<p>If a timer with this name exists, it will be replaced.</p>
|
||||
<p>The handler receives the total runtime in seconds as its argument.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Automation Module</h2>
|
||||
|
||||
<p>The <code>mcrfpy.automation</code> module provides testing and automation capabilities.</p>
|
||||
|
||||
<h3>`automation.click(x=None, y=None, clicks=1, interval=0.0, button='left') - Click at position`</h3>
|
||||
|
||||
<p>click(x=None, y=None, clicks=1, interval=0.0, button='left') - Click at position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.doubleClick(x=None, y=None) - Double click at position`</h3>
|
||||
|
||||
<p>doubleClick(x=None, y=None) - Double click at position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.dragRel(xOffset, yOffset, duration=0.0, button='left') - Drag mouse relative to current position`</h3>
|
||||
|
||||
<p>dragRel(xOffset, yOffset, duration=0.0, button='left') - Drag mouse relative to current position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.dragTo(x, y, duration=0.0, button='left') - Drag mouse to position`</h3>
|
||||
|
||||
<p>dragTo(x, y, duration=0.0, button='left') - Drag mouse to position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.hotkey(*keys) - Press a hotkey combination (e.g., hotkey('ctrl', 'c'))`</h3>
|
||||
|
||||
<p>hotkey(*keys) - Press a hotkey combination (e.g., hotkey('ctrl', 'c'))</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.keyDown(key) - Press and hold a key`</h3>
|
||||
|
||||
<p>keyDown(key) - Press and hold a key</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.keyUp(key) - Release a key`</h3>
|
||||
|
||||
<p>keyUp(key) - Release a key</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.middleClick(x=None, y=None) - Middle click at position`</h3>
|
||||
|
||||
<p>middleClick(x=None, y=None) - Middle click at position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.mouseDown(x=None, y=None, button='left') - Press mouse button`</h3>
|
||||
|
||||
<p>mouseDown(x=None, y=None, button='left') - Press mouse button</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.mouseUp(x=None, y=None, button='left') - Release mouse button`</h3>
|
||||
|
||||
<p>mouseUp(x=None, y=None, button='left') - Release mouse button</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.moveRel(xOffset, yOffset, duration=0.0) - Move mouse relative to current position`</h3>
|
||||
|
||||
<p>moveRel(xOffset, yOffset, duration=0.0) - Move mouse relative to current position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.moveTo(x, y, duration=0.0) - Move mouse to absolute position`</h3>
|
||||
|
||||
<p>moveTo(x, y, duration=0.0) - Move mouse to absolute position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.onScreen(x, y) - Check if coordinates are within screen bounds`</h3>
|
||||
|
||||
<p>onScreen(x, y) - Check if coordinates are within screen bounds</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.position() - Get current mouse position as (x, y) tuple`</h3>
|
||||
|
||||
<p>position() - Get current mouse position as (x, y) tuple</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.rightClick(x=None, y=None) - Right click at position`</h3>
|
||||
|
||||
<p>rightClick(x=None, y=None) - Right click at position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.screenshot(filename) - Save a screenshot to the specified file`</h3>
|
||||
|
||||
<p>screenshot(filename) - Save a screenshot to the specified file</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.scroll(clicks, x=None, y=None) - Scroll wheel at position`</h3>
|
||||
|
||||
<p>scroll(clicks, x=None, y=None) - Scroll wheel at position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.size() - Get screen size as (width, height) tuple`</h3>
|
||||
|
||||
<p>size() - Get screen size as (width, height) tuple</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.tripleClick(x=None, y=None) - Triple click at position`</h3>
|
||||
|
||||
<p>tripleClick(x=None, y=None) - Triple click at position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.typewrite(message, interval=0.0) - Type text with optional interval between keystrokes`</h3>
|
||||
|
||||
<p>typewrite(message, interval=0.0) - Type text with optional interval between keystrokes</p>
|
||||
|
||||
<hr>
|
||||
|
||||
</body></html>
|
||||
1602
docs/api_reference_complete.html
Normal file
1773
docs/api_reference_improved.html
Normal file
|
|
@ -1,13 +0,0 @@
|
|||
"""McRogueFace - Animated Movement (basic)
|
||||
|
||||
Documentation: https://mcrogueface.github.io/cookbook/combat_animated_movement
|
||||
Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_animated_movement_basic.py
|
||||
|
||||
This code is extracted from the McRogueFace documentation and can be
|
||||
run directly with: ./mcrogueface path/to/this/file.py
|
||||
"""
|
||||
|
||||
if new_x != current_x:
|
||||
anim = mcrfpy.Animation("x", float(new_x), duration, "easeInOut", callback=done)
|
||||
else:
|
||||
anim = mcrfpy.Animation("y", float(new_y), duration, "easeInOut", callback=done)
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
"""McRogueFace - Animated Movement (basic_2)
|
||||
|
||||
Documentation: https://mcrogueface.github.io/cookbook/combat_animated_movement
|
||||
Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_animated_movement_basic_2.py
|
||||
|
||||
This code is extracted from the McRogueFace documentation and can be
|
||||
run directly with: ./mcrogueface path/to/this/file.py
|
||||
"""
|
||||
|
||||
current_anim = mcrfpy.Animation("x", 100.0, 0.5, "linear")
|
||||
current_anim.start(entity)
|
||||
# Later: current_anim = None # Let it complete or create new one
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
"""McRogueFace - Basic Enemy AI (basic)
|
||||
|
||||
Documentation: https://mcrogueface.github.io/cookbook/combat_enemy_ai
|
||||
Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_enemy_ai_basic.py
|
||||
|
||||
This code is extracted from the McRogueFace documentation and can be
|
||||
run directly with: ./mcrogueface path/to/this/file.py
|
||||
"""
|
||||
|
||||
import random
|
||||
|
||||
def wander(enemy, grid):
|
||||
"""Move randomly to an adjacent walkable tile."""
|
||||
ex, ey = int(enemy.x), int(enemy.y)
|
||||
|
||||
# Get valid adjacent tiles
|
||||
directions = [(0, -1), (0, 1), (-1, 0), (1, 0)]
|
||||
random.shuffle(directions)
|
||||
|
||||
for dx, dy in directions:
|
||||
new_x, new_y = ex + dx, ey + dy
|
||||
|
||||
if is_walkable(grid, new_x, new_y) and not is_occupied(new_x, new_y):
|
||||
enemy.x = new_x
|
||||
enemy.y = new_y
|
||||
return
|
||||
|
||||
# No valid moves - stay in place
|
||||
|
||||
def is_walkable(grid, x, y):
|
||||
"""Check if a tile can be walked on."""
|
||||
grid_w, grid_h = grid.grid_size
|
||||
if x < 0 or x >= grid_w or y < 0 or y >= grid_h:
|
||||
return False
|
||||
return grid.at(x, y).walkable
|
||||
|
||||
def is_occupied(x, y, entities=None):
|
||||
"""Check if a tile is occupied by another entity."""
|
||||
if entities is None:
|
||||
return False
|
||||
|
||||
for entity in entities:
|
||||
if int(entity.x) == x and int(entity.y) == y:
|
||||
return True
|
||||
return False
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
"""McRogueFace - Basic Enemy AI (multi)
|
||||
|
||||
Documentation: https://mcrogueface.github.io/cookbook/combat_enemy_ai
|
||||
Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_enemy_ai_multi.py
|
||||
|
||||
This code is extracted from the McRogueFace documentation and can be
|
||||
run directly with: ./mcrogueface path/to/this/file.py
|
||||
"""
|
||||
|
||||
# Filter to cardinal directions only
|
||||
path = [p for p in path if abs(p[0] - ex) + abs(p[1] - ey) == 1]
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
"""McRogueFace - Basic Enemy AI (multi_2)
|
||||
|
||||
Documentation: https://mcrogueface.github.io/cookbook/combat_enemy_ai
|
||||
Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_enemy_ai_multi_2.py
|
||||
|
||||
This code is extracted from the McRogueFace documentation and can be
|
||||
run directly with: ./mcrogueface path/to/this/file.py
|
||||
"""
|
||||
|
||||
def alert_nearby(x, y, radius, enemies):
|
||||
for enemy in enemies:
|
||||
dist = abs(enemy.entity.x - x) + abs(enemy.entity.y - y)
|
||||
if dist <= radius and hasattr(enemy.ai, 'alert'):
|
||||
enemy.ai.alert = True
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
"""McRogueFace - Melee Combat System (basic)
|
||||
|
||||
Documentation: https://mcrogueface.github.io/cookbook/combat_melee
|
||||
Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_melee_basic.py
|
||||
|
||||
This code is extracted from the McRogueFace documentation and can be
|
||||
run directly with: ./mcrogueface path/to/this/file.py
|
||||
"""
|
||||
|
||||
class CombatLog:
|
||||
"""Scrolling combat message log."""
|
||||
|
||||
def __init__(self, x, y, width, height, max_messages=10):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.max_messages = max_messages
|
||||
self.messages = []
|
||||
self.captions = []
|
||||
|
||||
ui = mcrfpy.sceneUI(mcrfpy.currentScene())
|
||||
|
||||
# Background
|
||||
self.frame = mcrfpy.Frame(x, y, width, height)
|
||||
self.frame.fill_color = mcrfpy.Color(0, 0, 0, 180)
|
||||
ui.append(self.frame)
|
||||
|
||||
def add_message(self, text, color=None):
|
||||
"""Add a message to the log."""
|
||||
if color is None:
|
||||
color = mcrfpy.Color(200, 200, 200)
|
||||
|
||||
self.messages.append((text, color))
|
||||
|
||||
# Keep only recent messages
|
||||
if len(self.messages) > self.max_messages:
|
||||
self.messages.pop(0)
|
||||
|
||||
self._refresh_display()
|
||||
|
||||
def _refresh_display(self):
|
||||
"""Redraw all messages."""
|
||||
ui = mcrfpy.sceneUI(mcrfpy.currentScene())
|
||||
|
||||
# Remove old captions
|
||||
for caption in self.captions:
|
||||
try:
|
||||
ui.remove(caption)
|
||||
except:
|
||||
pass
|
||||
self.captions.clear()
|
||||
|
||||
# Create new captions
|
||||
line_height = 18
|
||||
for i, (text, color) in enumerate(self.messages):
|
||||
caption = mcrfpy.Caption(text, self.x + 5, self.y + 5 + i * line_height)
|
||||
caption.fill_color = color
|
||||
ui.append(caption)
|
||||
self.captions.append(caption)
|
||||
|
||||
def log_attack(self, attacker_name, defender_name, damage, killed=False, critical=False):
|
||||
"""Log an attack event."""
|
||||
if critical:
|
||||
text = f"{attacker_name} CRITS {defender_name} for {damage}!"
|
||||
color = mcrfpy.Color(255, 255, 0)
|
||||
else:
|
||||
text = f"{attacker_name} hits {defender_name} for {damage}."
|
||||
color = mcrfpy.Color(200, 200, 200)
|
||||
|
||||
self.add_message(text, color)
|
||||
|
||||
if killed:
|
||||
self.add_message(f"{defender_name} is defeated!", mcrfpy.Color(255, 100, 100))
|
||||
|
||||
|
||||
# Global combat log
|
||||
combat_log = None
|
||||
|
||||
def init_combat_log():
|
||||
global combat_log
|
||||
combat_log = CombatLog(10, 500, 400, 200)
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
"""McRogueFace - Melee Combat System (complete)
|
||||
|
||||
Documentation: https://mcrogueface.github.io/cookbook/combat_melee
|
||||
Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_melee_complete.py
|
||||
|
||||
This code is extracted from the McRogueFace documentation and can be
|
||||
run directly with: ./mcrogueface path/to/this/file.py
|
||||
"""
|
||||
|
||||
def die_with_animation(entity):
|
||||
# Play death animation
|
||||
anim = mcrfpy.Animation("opacity", 0.0, 0.5, "linear")
|
||||
anim.start(entity)
|
||||
# Remove after animation
|
||||
mcrfpy.setTimer("remove", lambda dt: remove_entity(entity), 500)
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
"""McRogueFace - Melee Combat System (complete_2)
|
||||
|
||||
Documentation: https://mcrogueface.github.io/cookbook/combat_melee
|
||||
Repository: https://github.com/jmccardle/McRogueFace/blob/master/docs/cookbook/combat/combat_melee_complete_2.py
|
||||
|
||||
This code is extracted from the McRogueFace documentation and can be
|
||||
run directly with: ./mcrogueface path/to/this/file.py
|
||||
"""
|
||||
|
||||
@dataclass
|
||||
class AdvancedFighter(Fighter):
|
||||
fire_resist: float = 0.0
|
||||
ice_resist: float = 0.0
|
||||
physical_resist: float = 0.0
|
||||