feat: Add GridPoint.entities and GridPointState.point properties
GridPoint.entities (#114): - Returns list of entities at this grid cell position - Enables convenient cell-based entity queries without manual iteration - Example: grid.at(5, 5).entities → [<Entity>, <Entity>] GridPointState.point (#16): - Returns GridPoint if entity has discovered this cell, None otherwise - Respects entity's perspective: undiscovered cells return None - Enables entity.at(x,y).point.walkable style access - Live reference: changes to GridPoint are immediately visible This provides a simpler solution for #16 without the complexity of caching stale GridPoint copies. The visible/discovered flags indicate whether the entity "should" trust the data; Python can implement memory systems if needed. closes #114, closes #16 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a529e5eac3
commit
f33e79a123
|
|
@ -120,9 +120,12 @@ PyObject* UIEntity::at(PyUIEntityObject* self, PyObject* o) {
|
||||||
|
|
||||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState");
|
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState");
|
||||||
auto obj = (PyUIGridPointStateObject*)type->tp_alloc(type, 0);
|
auto obj = (PyUIGridPointStateObject*)type->tp_alloc(type, 0);
|
||||||
|
Py_DECREF(type);
|
||||||
obj->data = &(self->data->gridstate[y * self->data->grid->grid_x + x]);
|
obj->data = &(self->data->gridstate[y * self->data->grid->grid_x + x]);
|
||||||
obj->grid = self->data->grid;
|
obj->grid = self->data->grid;
|
||||||
obj->entity = self->data;
|
obj->entity = self->data;
|
||||||
|
obj->x = x; // #16 - Store position for .point property
|
||||||
|
obj->y = y;
|
||||||
return (PyObject*)obj;
|
return (PyObject*)obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -312,7 +315,7 @@ sf::Vector2i PyObject_to_sfVector2i(PyObject* obj) {
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state) {
|
PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state) {
|
||||||
// Create a new GridPointState Python object
|
// Create a new GridPointState Python object (detached - no grid/entity context)
|
||||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState");
|
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState");
|
||||||
if (!type) {
|
if (!type) {
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
@ -329,6 +332,12 @@ PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state) {
|
||||||
obj->data->visible = state.visible;
|
obj->data->visible = state.visible;
|
||||||
obj->data->discovered = state.discovered;
|
obj->data->discovered = state.discovered;
|
||||||
|
|
||||||
|
// Initialize context fields (detached state has no grid/entity context)
|
||||||
|
obj->grid = nullptr;
|
||||||
|
obj->entity = nullptr;
|
||||||
|
obj->x = -1;
|
||||||
|
obj->y = -1;
|
||||||
|
|
||||||
Py_DECREF(type);
|
Py_DECREF(type);
|
||||||
return (PyObject*)obj;
|
return (PyObject*)obj;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include "UIGridPoint.h"
|
#include "UIGridPoint.h"
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
|
#include "UIEntity.h" // #114 - for GridPoint.entities
|
||||||
#include "GridLayers.h" // #150 - for GridLayerType, ColorLayer, TileLayer
|
#include "GridLayers.h" // #150 - for GridLayerType, ColorLayer, TileLayer
|
||||||
#include <cstring> // #150 - for strcmp
|
#include <cstring> // #150 - for strcmp
|
||||||
|
|
||||||
|
|
@ -90,9 +91,52 @@ int UIGridPoint::set_bool_member(PyUIGridPointObject* self, PyObject* value, voi
|
||||||
|
|
||||||
// #150 - Removed get_int_member/set_int_member - now handled by layers
|
// #150 - Removed get_int_member/set_int_member - now handled by layers
|
||||||
|
|
||||||
|
// #114 - Get list of entities at this grid cell
|
||||||
|
PyObject* UIGridPoint::get_entities(PyUIGridPointObject* self, void* closure) {
|
||||||
|
if (!self->grid) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "GridPoint has no parent grid");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int target_x = self->data->grid_x;
|
||||||
|
int target_y = self->data->grid_y;
|
||||||
|
|
||||||
|
PyObject* list = PyList_New(0);
|
||||||
|
if (!list) return NULL;
|
||||||
|
|
||||||
|
// Iterate through grid's entities and find those at this position
|
||||||
|
for (auto& entity : *(self->grid->entities)) {
|
||||||
|
if (static_cast<int>(entity->position.x) == target_x &&
|
||||||
|
static_cast<int>(entity->position.y) == target_y) {
|
||||||
|
// Create Python Entity object for this entity
|
||||||
|
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity");
|
||||||
|
if (!type) {
|
||||||
|
Py_DECREF(list);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
auto obj = (PyUIEntityObject*)type->tp_alloc(type, 0);
|
||||||
|
Py_DECREF(type);
|
||||||
|
if (!obj) {
|
||||||
|
Py_DECREF(list);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
obj->data = entity;
|
||||||
|
if (PyList_Append(list, (PyObject*)obj) < 0) {
|
||||||
|
Py_DECREF(obj);
|
||||||
|
Py_DECREF(list);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
Py_DECREF(obj); // List now owns the reference
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
PyGetSetDef UIGridPoint::getsetters[] = {
|
PyGetSetDef UIGridPoint::getsetters[] = {
|
||||||
{"walkable", (getter)UIGridPoint::get_bool_member, (setter)UIGridPoint::set_bool_member, "Is the GridPoint walkable", (void*)0},
|
{"walkable", (getter)UIGridPoint::get_bool_member, (setter)UIGridPoint::set_bool_member, "Is the GridPoint walkable", (void*)0},
|
||||||
{"transparent", (getter)UIGridPoint::get_bool_member, (setter)UIGridPoint::set_bool_member, "Is the GridPoint transparent", (void*)1},
|
{"transparent", (getter)UIGridPoint::get_bool_member, (setter)UIGridPoint::set_bool_member, "Is the GridPoint transparent", (void*)1},
|
||||||
|
{"entities", (getter)UIGridPoint::get_entities, NULL, "List of entities at this grid cell (read-only)", NULL},
|
||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -137,9 +181,43 @@ int UIGridPointState::set_bool_member(PyUIGridPointStateObject* self, PyObject*
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #16 - Get GridPoint at this position (None if not discovered)
|
||||||
|
PyObject* UIGridPointState::get_point(PyUIGridPointStateObject* self, void* closure) {
|
||||||
|
// Return None if entity hasn't discovered this cell
|
||||||
|
if (!self->data->discovered) {
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self->grid) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "GridPointState has no parent grid");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the GridPoint at this position
|
||||||
|
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPoint");
|
||||||
|
if (!type) return NULL;
|
||||||
|
|
||||||
|
auto obj = (PyUIGridPointObject*)type->tp_alloc(type, 0);
|
||||||
|
Py_DECREF(type);
|
||||||
|
if (!obj) return NULL;
|
||||||
|
|
||||||
|
// Get the GridPoint from the grid
|
||||||
|
int idx = self->y * self->grid->grid_x + self->x;
|
||||||
|
if (idx < 0 || idx >= static_cast<int>(self->grid->points.size())) {
|
||||||
|
Py_DECREF(obj);
|
||||||
|
PyErr_SetString(PyExc_IndexError, "GridPointState position out of bounds");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
obj->data = &(self->grid->points[idx]);
|
||||||
|
obj->grid = self->grid;
|
||||||
|
return (PyObject*)obj;
|
||||||
|
}
|
||||||
|
|
||||||
PyGetSetDef UIGridPointState::getsetters[] = {
|
PyGetSetDef UIGridPointState::getsetters[] = {
|
||||||
{"visible", (getter)UIGridPointState::get_bool_member, (setter)UIGridPointState::set_bool_member, "Is the GridPointState visible", (void*)0},
|
{"visible", (getter)UIGridPointState::get_bool_member, (setter)UIGridPointState::set_bool_member, "Is the GridPointState visible", (void*)0},
|
||||||
{"discovered", (getter)UIGridPointState::get_bool_member, (setter)UIGridPointState::set_bool_member, "Has the GridPointState been discovered", (void*)1},
|
{"discovered", (getter)UIGridPointState::get_bool_member, (setter)UIGridPointState::set_bool_member, "Has the GridPointState been discovered", (void*)1},
|
||||||
|
{"point", (getter)UIGridPointState::get_point, NULL, "GridPoint at this position (None if not discovered)", NULL},
|
||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -232,7 +310,7 @@ int UIGridPoint::setattro(PyUIGridPointObject* self, PyObject* name, PyObject* v
|
||||||
sf::Color color = PyObject_to_sfColor(value);
|
sf::Color color = PyObject_to_sfColor(value);
|
||||||
if (PyErr_Occurred()) return -1;
|
if (PyErr_Occurred()) return -1;
|
||||||
color_layer->at(x, y) = color;
|
color_layer->at(x, y) = color;
|
||||||
color_layer->markDirty();
|
color_layer->markDirty(x, y); // Mark only the affected chunk
|
||||||
return 0;
|
return 0;
|
||||||
} else if (layer->type == GridLayerType::Tile) {
|
} else if (layer->type == GridLayerType::Tile) {
|
||||||
auto tile_layer = std::static_pointer_cast<TileLayer>(layer);
|
auto tile_layer = std::static_pointer_cast<TileLayer>(layer);
|
||||||
|
|
@ -241,7 +319,7 @@ int UIGridPoint::setattro(PyUIGridPointObject* self, PyObject* name, PyObject* v
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
tile_layer->at(x, y) = PyLong_AsLong(value);
|
tile_layer->at(x, y) = PyLong_AsLong(value);
|
||||||
tile_layer->markDirty();
|
tile_layer->markDirty(x, y); // Mark only the affected chunk
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ typedef struct {
|
||||||
UIGridPointState* data;
|
UIGridPointState* data;
|
||||||
std::shared_ptr<UIGrid> grid;
|
std::shared_ptr<UIGrid> grid;
|
||||||
std::shared_ptr<UIEntity> entity;
|
std::shared_ptr<UIEntity> entity;
|
||||||
|
int x, y; // Position in grid (needed for .point property)
|
||||||
} PyUIGridPointStateObject;
|
} PyUIGridPointStateObject;
|
||||||
|
|
||||||
// UIGridPoint - grid cell data for pathfinding and layer access
|
// UIGridPoint - grid cell data for pathfinding and layer access
|
||||||
|
|
@ -49,6 +50,9 @@ public:
|
||||||
static PyObject* get_bool_member(PyUIGridPointObject* self, void* closure);
|
static PyObject* get_bool_member(PyUIGridPointObject* self, void* closure);
|
||||||
static PyObject* repr(PyUIGridPointObject* self);
|
static PyObject* repr(PyUIGridPointObject* self);
|
||||||
|
|
||||||
|
// #114 - entities property: list of entities at this cell
|
||||||
|
static PyObject* get_entities(PyUIGridPointObject* self, void* closure);
|
||||||
|
|
||||||
// #150 - Dynamic property access for named layers
|
// #150 - Dynamic property access for named layers
|
||||||
static PyObject* getattro(PyUIGridPointObject* self, PyObject* name);
|
static PyObject* getattro(PyUIGridPointObject* self, PyObject* name);
|
||||||
static int setattro(PyUIGridPointObject* self, PyObject* name, PyObject* value);
|
static int setattro(PyUIGridPointObject* self, PyObject* name, PyObject* value);
|
||||||
|
|
@ -64,6 +68,9 @@ public:
|
||||||
static int set_bool_member(PyUIGridPointStateObject* self, PyObject* value, void* closure);
|
static int set_bool_member(PyUIGridPointStateObject* self, PyObject* value, void* closure);
|
||||||
static PyGetSetDef getsetters[];
|
static PyGetSetDef getsetters[];
|
||||||
static PyObject* repr(PyUIGridPointStateObject* self);
|
static PyObject* repr(PyUIGridPointStateObject* self);
|
||||||
|
|
||||||
|
// #16 - point property: access to GridPoint (None if not discovered)
|
||||||
|
static PyObject* get_point(PyUIGridPointStateObject* self, void* closure);
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test GridPoint.entities property (#114)
|
||||||
|
========================================
|
||||||
|
|
||||||
|
Tests the GridPoint.entities property that returns a list of entities
|
||||||
|
at that grid cell position.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import mcrfpy
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def run_tests():
|
||||||
|
"""Run GridPoint.entities tests"""
|
||||||
|
print("=== GridPoint.entities Tests ===\n")
|
||||||
|
|
||||||
|
# Test 1: Basic entity listing
|
||||||
|
print("Test 1: Basic entity listing")
|
||||||
|
grid = mcrfpy.Grid(pos=(0, 0), size=(640, 400), grid_size=(40, 25))
|
||||||
|
|
||||||
|
# Add entities at various positions
|
||||||
|
e1 = mcrfpy.Entity((5, 5))
|
||||||
|
e2 = mcrfpy.Entity((5, 5)) # Same position as e1
|
||||||
|
e3 = mcrfpy.Entity((10, 10))
|
||||||
|
grid.entities.append(e1)
|
||||||
|
grid.entities.append(e2)
|
||||||
|
grid.entities.append(e3)
|
||||||
|
|
||||||
|
# Check entities at (5, 5)
|
||||||
|
pt = grid.at(5, 5)
|
||||||
|
entities_at_5_5 = pt.entities
|
||||||
|
assert len(entities_at_5_5) == 2, f"Expected 2 entities at (5,5), got {len(entities_at_5_5)}"
|
||||||
|
print(f" Found {len(entities_at_5_5)} entities at (5, 5)")
|
||||||
|
|
||||||
|
# Check entities at (10, 10)
|
||||||
|
pt2 = grid.at(10, 10)
|
||||||
|
entities_at_10_10 = pt2.entities
|
||||||
|
assert len(entities_at_10_10) == 1, f"Expected 1 entity at (10,10), got {len(entities_at_10_10)}"
|
||||||
|
print(f" Found {len(entities_at_10_10)} entity at (10, 10)")
|
||||||
|
|
||||||
|
# Check empty cell
|
||||||
|
pt3 = grid.at(0, 0)
|
||||||
|
entities_at_0_0 = pt3.entities
|
||||||
|
assert len(entities_at_0_0) == 0, f"Expected 0 entities at (0,0), got {len(entities_at_0_0)}"
|
||||||
|
print(f" Found {len(entities_at_0_0)} entities at (0, 0)")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Test 2: Entity references are valid
|
||||||
|
print("Test 2: Entity references are valid")
|
||||||
|
for e in pt.entities:
|
||||||
|
assert e.x == 5.0, f"Entity x should be 5.0, got {e.x}"
|
||||||
|
assert e.y == 5.0, f"Entity y should be 5.0, got {e.y}"
|
||||||
|
print(" All entity references have correct positions")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Test 3: Entity movement updates listing
|
||||||
|
print("Test 3: Entity movement updates listing")
|
||||||
|
e1.x = 20
|
||||||
|
e1.y = 20
|
||||||
|
|
||||||
|
# Old position should have one fewer entity
|
||||||
|
entities_at_5_5_after = grid.at(5, 5).entities
|
||||||
|
assert len(entities_at_5_5_after) == 1, f"Expected 1 entity at (5,5) after move, got {len(entities_at_5_5_after)}"
|
||||||
|
print(f" After moving e1: {len(entities_at_5_5_after)} entity at (5, 5)")
|
||||||
|
|
||||||
|
# New position should have the moved entity
|
||||||
|
entities_at_20_20 = grid.at(20, 20).entities
|
||||||
|
assert len(entities_at_20_20) == 1, f"Expected 1 entity at (20,20), got {len(entities_at_20_20)}"
|
||||||
|
print(f" After moving e1: {len(entities_at_20_20)} entity at (20, 20)")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Test 4: Multiple grids are independent
|
||||||
|
print("Test 4: Multiple grids are independent")
|
||||||
|
grid2 = mcrfpy.Grid(pos=(0, 0), size=(640, 400), grid_size=(40, 25))
|
||||||
|
e4 = mcrfpy.Entity((5, 5))
|
||||||
|
grid2.entities.append(e4)
|
||||||
|
|
||||||
|
# Original grid should not see grid2's entity
|
||||||
|
assert len(grid.at(5, 5).entities) == 1, "Original grid should still have 1 entity at (5,5)"
|
||||||
|
assert len(grid2.at(5, 5).entities) == 1, "Second grid should have 1 entity at (5,5)"
|
||||||
|
print(" Grids maintain independent entity lists")
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("=== All GridPoint.entities Tests Passed! ===")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
if run_tests():
|
||||||
|
print("\nPASS")
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print("\nFAIL")
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nFAIL: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
sys.exit(1)
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Test GridPointState.point property (#16)
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
Tests the GridPointState.point property that provides access to the
|
||||||
|
GridPoint data from an entity's perspective.
|
||||||
|
|
||||||
|
Key behavior:
|
||||||
|
- Returns None if the cell has not been discovered by the entity
|
||||||
|
- Returns the GridPoint (live reference) if discovered
|
||||||
|
- Works with the FOV/visibility system
|
||||||
|
"""
|
||||||
|
|
||||||
|
import mcrfpy
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def run_tests():
|
||||||
|
"""Run GridPointState.point tests"""
|
||||||
|
print("=== GridPointState.point Tests ===\n")
|
||||||
|
|
||||||
|
# Test 1: Undiscovered cell returns None
|
||||||
|
print("Test 1: Undiscovered cell returns None")
|
||||||
|
grid = mcrfpy.Grid(pos=(0, 0), size=(640, 400), grid_size=(40, 25))
|
||||||
|
|
||||||
|
# Set up grid
|
||||||
|
for y in range(25):
|
||||||
|
for x in range(40):
|
||||||
|
pt = grid.at(x, y)
|
||||||
|
pt.walkable = True
|
||||||
|
pt.transparent = True
|
||||||
|
|
||||||
|
# Create entity
|
||||||
|
entity = mcrfpy.Entity((5, 5))
|
||||||
|
grid.entities.append(entity)
|
||||||
|
|
||||||
|
# Before update_visibility, nothing is discovered
|
||||||
|
state = entity.at((10, 10))
|
||||||
|
assert state.point is None, f"Expected None for undiscovered cell, got {state.point}"
|
||||||
|
print(" Undiscovered cell returns None")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Test 2: Discovered cell returns GridPoint
|
||||||
|
print("Test 2: Discovered cell returns GridPoint")
|
||||||
|
grid.fov = mcrfpy.FOV.SHADOW
|
||||||
|
grid.fov_radius = 8
|
||||||
|
|
||||||
|
entity.update_visibility()
|
||||||
|
|
||||||
|
# Entity's own position should be discovered
|
||||||
|
state_own = entity.at((5, 5))
|
||||||
|
assert state_own.discovered == True, "Entity's position should be discovered"
|
||||||
|
assert state_own.point is not None, "Discovered cell should return GridPoint"
|
||||||
|
print(f" Entity position: discovered={state_own.discovered}, point={state_own.point}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Test 3: GridPoint access through state
|
||||||
|
print("Test 3: GridPoint properties accessible through state.point")
|
||||||
|
|
||||||
|
# Make a specific cell have known properties
|
||||||
|
grid.at(6, 5).walkable = False
|
||||||
|
grid.at(6, 5).transparent = True
|
||||||
|
|
||||||
|
state_adj = entity.at((6, 5))
|
||||||
|
assert state_adj.point is not None, "Adjacent cell should be discovered"
|
||||||
|
assert state_adj.point.walkable == False, f"Expected walkable=False, got {state_adj.point.walkable}"
|
||||||
|
assert state_adj.point.transparent == True, f"Expected transparent=True, got {state_adj.point.transparent}"
|
||||||
|
print(f" Adjacent cell (6,5): walkable={state_adj.point.walkable}, transparent={state_adj.point.transparent}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Test 4: Far cells remain undiscovered
|
||||||
|
print("Test 4: Cells outside FOV radius remain undiscovered")
|
||||||
|
|
||||||
|
# Cell far from entity (outside radius 8)
|
||||||
|
state_far = entity.at((30, 20))
|
||||||
|
assert state_far.discovered == False, "Far cell should not be discovered"
|
||||||
|
assert state_far.point is None, "Far cell point should be None"
|
||||||
|
print(f" Far cell (30,20): discovered={state_far.discovered}, point={state_far.point}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Test 5: Discovered but not visible cells
|
||||||
|
print("Test 5: Discovered but not currently visible cells")
|
||||||
|
|
||||||
|
# Move entity to discover new area
|
||||||
|
entity.x = 6
|
||||||
|
entity.y = 5
|
||||||
|
entity.update_visibility()
|
||||||
|
|
||||||
|
# Move back - old visible cells should now be discovered but not visible
|
||||||
|
entity.x = 5
|
||||||
|
entity.y = 5
|
||||||
|
entity.update_visibility()
|
||||||
|
|
||||||
|
# A cell that was visible when at (6,5) but might not be visible from (5,5)
|
||||||
|
# Actually with radius 8, most nearby cells will still be visible
|
||||||
|
# Let's check a cell that's on the edge
|
||||||
|
state_edge = entity.at((12, 5)) # 7 cells away, should be visible with radius 8
|
||||||
|
if state_edge.discovered:
|
||||||
|
assert state_edge.point is not None, "Discovered cell should have point access"
|
||||||
|
print(f" Edge cell (12,5): discovered={state_edge.discovered}, visible={state_edge.visible}")
|
||||||
|
print(f" point.walkable={state_edge.point.walkable}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Test 6: GridPoint.entities through state.point
|
||||||
|
print("Test 6: GridPoint.entities accessible through state.point")
|
||||||
|
|
||||||
|
# Add another entity at a visible position
|
||||||
|
e2 = mcrfpy.Entity((7, 5))
|
||||||
|
grid.entities.append(e2)
|
||||||
|
|
||||||
|
state_with_entity = entity.at((7, 5))
|
||||||
|
assert state_with_entity.point is not None, "Cell should be discovered"
|
||||||
|
entities_at_cell = state_with_entity.point.entities
|
||||||
|
assert len(entities_at_cell) == 1, f"Expected 1 entity, got {len(entities_at_cell)}"
|
||||||
|
print(f" Cell (7,5) has {len(entities_at_cell)} entity via state.point.entities")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Test 7: Live reference - changes to GridPoint are reflected
|
||||||
|
print("Test 7: state.point is a live reference")
|
||||||
|
|
||||||
|
# Get state before change
|
||||||
|
state_live = entity.at((8, 5))
|
||||||
|
original_walkable = state_live.point.walkable
|
||||||
|
|
||||||
|
# Change the actual GridPoint
|
||||||
|
grid.at(8, 5).walkable = not original_walkable
|
||||||
|
|
||||||
|
# Check that state.point reflects the change
|
||||||
|
new_walkable = state_live.point.walkable
|
||||||
|
assert new_walkable != original_walkable, "state.point should reflect GridPoint changes"
|
||||||
|
print(f" Changed walkable from {original_walkable} to {new_walkable} - reflected in state.point")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Test 8: Wall blocking visibility
|
||||||
|
print("Test 8: Walls block visibility correctly")
|
||||||
|
|
||||||
|
# Create a wall
|
||||||
|
grid.at(15, 5).transparent = False
|
||||||
|
grid.at(15, 5).walkable = False
|
||||||
|
|
||||||
|
entity.update_visibility()
|
||||||
|
|
||||||
|
# Cell behind wall should not be visible (and possibly not discovered)
|
||||||
|
state_behind = entity.at((20, 5)) # Behind wall at x=15
|
||||||
|
print(f" Cell behind wall (20,5): visible={state_behind.visible}, discovered={state_behind.discovered}")
|
||||||
|
if not state_behind.discovered:
|
||||||
|
assert state_behind.point is None, "Undiscovered cell behind wall should have point=None"
|
||||||
|
print(" Correctly returns None for undiscovered cell behind wall")
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("=== All GridPointState.point Tests Passed! ===")
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Main execution
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
if run_tests():
|
||||||
|
print("\nPASS")
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print("\nFAIL")
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nFAIL: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
sys.exit(1)
|
||||||
Loading…
Reference in New Issue