diff --git a/quick_extend_test.py b/quick_extend_test.py new file mode 100644 index 0000000..b99ef89 --- /dev/null +++ b/quick_extend_test.py @@ -0,0 +1,20 @@ +import mcrfpy + +# Create grid +t = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) +g = mcrfpy.Grid(5, 5, t, (0, 0), (100, 100)) + +# Create some entities +entities = [mcrfpy.Entity((i, i), t, i, g) for i in range(3)] + +# Test extend +print(f"Initial entities: {len(g.entities)}") +g.entities.extend(entities) +print(f"After extend: {len(g.entities)}") + +# Test with tuple +more = (mcrfpy.Entity((3, 3), t, 3, g), mcrfpy.Entity((4, 4), t, 4, g)) +g.entities.extend(more) +print(f"After second extend: {len(g.entities)}") + +print("✓ EntityCollection.extend() works!") \ No newline at end of file diff --git a/src/UIGrid.cpp b/src/UIGrid.cpp index 0d08328..7a2f9ed 100644 --- a/src/UIGrid.cpp +++ b/src/UIGrid.cpp @@ -637,9 +637,47 @@ PyObject* UIEntityCollection::remove(PyUIEntityCollectionObject* self, PyObject* return Py_None; } +PyObject* UIEntityCollection::extend(PyUIEntityCollectionObject* self, PyObject* o) +{ + // Accept any iterable of Entity objects + PyObject* iterator = PyObject_GetIter(o); + if (iterator == NULL) { + PyErr_SetString(PyExc_TypeError, "UIEntityCollection.extend requires an iterable"); + return NULL; + } + + PyObject* item; + while ((item = PyIter_Next(iterator)) != NULL) { + // Check if item is an Entity + if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity"))) { + Py_DECREF(item); + Py_DECREF(iterator); + PyErr_SetString(PyExc_TypeError, "All items in iterable must be Entity objects"); + return NULL; + } + + // Add the entity to the collection + PyUIEntityObject* entity = (PyUIEntityObject*)item; + self->data->push_back(entity->data); + entity->data->grid = self->grid; + + Py_DECREF(item); + } + + Py_DECREF(iterator); + + // Check if iteration ended due to an error + if (PyErr_Occurred()) { + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + PyMethodDef UIEntityCollection::methods[] = { {"append", (PyCFunction)UIEntityCollection::append, METH_O}, - //{"extend", (PyCFunction)UIEntityCollection::extend, METH_O}, // TODO + {"extend", (PyCFunction)UIEntityCollection::extend, METH_O}, {"remove", (PyCFunction)UIEntityCollection::remove, METH_O}, {NULL, NULL, 0, NULL} }; diff --git a/src/UIGrid.h b/src/UIGrid.h index 0e041a2..1e3f2aa 100644 --- a/src/UIGrid.h +++ b/src/UIGrid.h @@ -77,6 +77,7 @@ class UIEntityCollection { public: static PySequenceMethods sqmethods; static PyObject* append(PyUIEntityCollectionObject* self, PyObject* o); + static PyObject* extend(PyUIEntityCollectionObject* self, PyObject* o); static PyObject* remove(PyUIEntityCollectionObject* self, PyObject* o); static PyMethodDef methods[]; static PyObject* repr(PyUIEntityCollectionObject* self); diff --git a/tests/issue27_entity_extend_test.py b/tests/issue27_entity_extend_test.py new file mode 100644 index 0000000..41fd744 --- /dev/null +++ b/tests/issue27_entity_extend_test.py @@ -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) \ No newline at end of file