- Add Behavior enum (IDLE..FLEE, 11 values) and Trigger enum (DONE, BLOCKED, TARGET) as runtime IntEnum classes (closes #297, closes #298) - Add entity label system: labels property (frozenset), add_label(), remove_label(), has_label(), constructor kwarg (closes #296) - Add cell_pos integer logical position decoupled from float draw_pos; grid_pos now aliases cell_pos; SpatialHash::updateCell() for cell-based bucket management; FOV/visibility uses cell_position (closes #295) - Add step callback and default_behavior properties to Entity for grid.step() turn management (closes #299) - Update updateVisibility, visible_entities, ColorLayer::updatePerspective to use cell_position instead of float position BREAKING: grid_pos no longer derives from float x/y position. Use cell_pos/grid_pos for logical position, draw_pos for render position. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
183 lines
6.7 KiB
Python
183 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Test for issue #176: Entity position naming consistency.
|
|
|
|
Tests the new Entity position properties:
|
|
- pos, x, y: pixel coordinates (requires grid attachment)
|
|
- grid_pos, grid_x, grid_y: integer tile coordinates
|
|
- draw_pos: fractional tile coordinates for animation
|
|
"""
|
|
import mcrfpy
|
|
import sys
|
|
|
|
def test_entity_positions():
|
|
"""Test Entity position properties with grid attachment."""
|
|
errors = []
|
|
|
|
# Create a texture with 16x16 sprites (standard tile size)
|
|
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
|
|
|
# Create a grid (10x10 tiles, 16x16 pixels each)
|
|
grid = mcrfpy.Grid(grid_size=(10, 10), texture=texture, pos=(0, 0), size=(160, 160))
|
|
|
|
# Create entity at tile position (3, 5)
|
|
entity = mcrfpy.Entity(grid_pos=(3, 5), texture=texture, grid=grid)
|
|
|
|
# Test 1: grid_pos should return integer tile coordinates
|
|
gpos = entity.grid_pos
|
|
if gpos.x != 3 or gpos.y != 5:
|
|
errors.append(f"grid_pos: expected (3, 5), got ({gpos.x}, {gpos.y})")
|
|
|
|
# Test 2: grid_x and grid_y should return integers
|
|
if entity.grid_x != 3:
|
|
errors.append(f"grid_x: expected 3, got {entity.grid_x}")
|
|
if entity.grid_y != 5:
|
|
errors.append(f"grid_y: expected 5, got {entity.grid_y}")
|
|
|
|
# Test 3: draw_pos should return float tile coordinates
|
|
dpos = entity.draw_pos
|
|
if abs(dpos.x - 3.0) > 0.001 or abs(dpos.y - 5.0) > 0.001:
|
|
errors.append(f"draw_pos: expected (3.0, 5.0), got ({dpos.x}, {dpos.y})")
|
|
|
|
# Test 4: pos should return pixel coordinates (tile * tile_size)
|
|
# With 16x16 tiles: (3, 5) tiles = (48, 80) pixels
|
|
ppos = entity.pos
|
|
if abs(ppos.x - 48.0) > 0.001 or abs(ppos.y - 80.0) > 0.001:
|
|
errors.append(f"pos: expected (48.0, 80.0), got ({ppos.x}, {ppos.y})")
|
|
|
|
# Test 5: x and y should return pixel coordinates
|
|
if abs(entity.x - 48.0) > 0.001:
|
|
errors.append(f"x: expected 48.0, got {entity.x}")
|
|
if abs(entity.y - 80.0) > 0.001:
|
|
errors.append(f"y: expected 80.0, got {entity.y}")
|
|
|
|
# Test 6: Setting grid_x/grid_y should update cell position (#295: decoupled from pixel pos)
|
|
entity.grid_x = 7
|
|
entity.grid_y = 2
|
|
if entity.grid_x != 7 or entity.grid_y != 2:
|
|
errors.append(f"After setting grid_x/y: expected (7, 2), got ({entity.grid_x}, {entity.grid_y})")
|
|
# #295: cell_pos (grid_x/y) is decoupled from pixel pos - pixel pos NOT updated
|
|
# Pixel pos should remain at the draw_pos * tile_size (3*16=48, 5*16=80 from earlier)
|
|
if abs(entity.x - 48.0) > 0.001 or abs(entity.y - 80.0) > 0.001:
|
|
errors.append(f"After grid_x/y set, pixel pos should be unchanged: expected (48, 80), got ({entity.x}, {entity.y})")
|
|
|
|
# Test 7: Setting pos (pixels) should update draw_pos but NOT grid_pos (#295)
|
|
entity.pos = mcrfpy.Vector(64, 96) # (64, 96) / 16 = (4, 6) tiles
|
|
if abs(entity.draw_pos.x - 4.0) > 0.001 or abs(entity.draw_pos.y - 6.0) > 0.001:
|
|
errors.append(f"After setting pos, draw_pos: expected (4, 6), got ({entity.draw_pos.x}, {entity.draw_pos.y})")
|
|
# #295: grid_pos is cell_pos, not derived from float position - should be (7, 2) from above
|
|
if entity.grid_x != 7 or entity.grid_y != 2:
|
|
errors.append(f"After setting pos, grid_x/y should be unchanged: expected (7, 2), got ({entity.grid_x}, {entity.grid_y})")
|
|
|
|
# Test 8: repr should show position info
|
|
repr_str = repr(entity)
|
|
if "draw_pos=" not in repr_str:
|
|
errors.append(f"repr should contain draw_pos: {repr_str}")
|
|
|
|
return errors
|
|
|
|
|
|
def test_entity_without_grid():
|
|
"""Test that pixel positions require grid attachment."""
|
|
errors = []
|
|
|
|
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
|
entity = mcrfpy.Entity(grid_pos=(3, 5), texture=texture) # No grid
|
|
|
|
# grid_pos should work without grid
|
|
if entity.grid_x != 3 or entity.grid_y != 5:
|
|
errors.append(f"grid_x/y without grid: expected (3, 5), got ({entity.grid_x}, {entity.grid_y})")
|
|
|
|
# pos should raise RuntimeError without grid
|
|
try:
|
|
_ = entity.pos
|
|
errors.append("entity.pos should raise RuntimeError without grid")
|
|
except RuntimeError as e:
|
|
if "not attached to a Grid" not in str(e):
|
|
errors.append(f"Wrong error message for pos: {e}")
|
|
|
|
# x should raise RuntimeError without grid
|
|
try:
|
|
_ = entity.x
|
|
errors.append("entity.x should raise RuntimeError without grid")
|
|
except RuntimeError as e:
|
|
if "not attached to a Grid" not in str(e):
|
|
errors.append(f"Wrong error message for x: {e}")
|
|
|
|
# Setting pos should raise RuntimeError without grid
|
|
try:
|
|
entity.pos = mcrfpy.Vector(100, 100)
|
|
errors.append("setting entity.pos should raise RuntimeError without grid")
|
|
except RuntimeError as e:
|
|
if "not attached to a Grid" not in str(e):
|
|
errors.append(f"Wrong error message for setting pos: {e}")
|
|
|
|
return errors
|
|
|
|
|
|
def test_animation_properties():
|
|
"""Test that animation properties work correctly."""
|
|
errors = []
|
|
|
|
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
|
grid = mcrfpy.Grid(grid_size=(10, 10), texture=texture, pos=(0, 0), size=(160, 160))
|
|
entity = mcrfpy.Entity(grid_pos=(0, 0), texture=texture, grid=grid)
|
|
|
|
# Test draw_x/draw_y animation properties exist
|
|
try:
|
|
# hasProperty should accept draw_x and draw_y
|
|
# We can't call hasProperty directly, but we can try to animate
|
|
# and check if it raises ValueError for invalid property
|
|
pass # Animation tested implicitly through animate() error handling
|
|
except Exception as e:
|
|
errors.append(f"Animation property test failed: {e}")
|
|
|
|
return errors
|
|
|
|
|
|
def main():
|
|
print("Testing issue #176: Entity position naming consistency")
|
|
print("=" * 60)
|
|
|
|
all_errors = []
|
|
|
|
# Test 1: Entity with grid
|
|
print("\n1. Testing entity positions with grid attachment...")
|
|
errors = test_entity_positions()
|
|
if errors:
|
|
for e in errors:
|
|
print(f" FAIL: {e}")
|
|
all_errors.extend(errors)
|
|
else:
|
|
print(" PASS")
|
|
|
|
# Test 2: Entity without grid
|
|
print("\n2. Testing entity positions without grid...")
|
|
errors = test_entity_without_grid()
|
|
if errors:
|
|
for e in errors:
|
|
print(f" FAIL: {e}")
|
|
all_errors.extend(errors)
|
|
else:
|
|
print(" PASS")
|
|
|
|
# Test 3: Animation properties
|
|
print("\n3. Testing animation properties...")
|
|
errors = test_animation_properties()
|
|
if errors:
|
|
for e in errors:
|
|
print(f" FAIL: {e}")
|
|
all_errors.extend(errors)
|
|
else:
|
|
print(" PASS")
|
|
|
|
print("\n" + "=" * 60)
|
|
if all_errors:
|
|
print(f"FAILED: {len(all_errors)} error(s)")
|
|
sys.exit(1)
|
|
else:
|
|
print("All tests passed!")
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|