diff --git a/src/McRFPy_API.cpp b/src/McRFPy_API.cpp index afda758..36d5aff 100644 --- a/src/McRFPy_API.cpp +++ b/src/McRFPy_API.cpp @@ -1,7 +1,7 @@ #include "McRFPy_API.h" #include "UIDrawable.h" #include "McRFPy_Automation.h" -#include "McRFPy_Libtcod.h" +// Note: McRFPy_Libtcod.h removed in #215 - functionality moved to mcrfpy.bresenham() #include "McRFPy_Doc.h" #include "PyTypeCache.h" // Thread-safe cached Python types #include "platform.h" @@ -26,6 +26,7 @@ #include "PyBSP.h" // Procedural generation BSP (#202-206) #include "PyNoiseSource.h" // Procedural generation noise (#207-208) #include "PyLock.h" // Thread synchronization (#219) +#include "PyVector.h" // For bresenham Vector support (#215) #include "McRogueFaceVersion.h" #include "GameEngine.h" #include "ImGuiConsole.h" @@ -304,6 +305,21 @@ static PyMethodDef mcrfpyMethods[] = { "Without this, modifying UI from threads may cause visual glitches or crashes.") )}, + // #215: Bresenham line algorithm (replaces mcrfpy.libtcod.line) + {"bresenham", (PyCFunction)McRFPy_API::_bresenham, METH_VARARGS | METH_KEYWORDS, + MCRF_FUNCTION(bresenham, + MCRF_SIG("(start, end, *, include_start=True, include_end=True)", "list[tuple[int, int]]"), + MCRF_DESC("Compute grid cells along a line using Bresenham's algorithm."), + MCRF_ARGS_START + MCRF_ARG("start", "(x, y) tuple or Vector - starting point") + MCRF_ARG("end", "(x, y) tuple or Vector - ending point") + MCRF_ARG("include_start", "Include the starting point in results (default: True)") + MCRF_ARG("include_end", "Include the ending point in results (default: True)") + MCRF_RETURNS("list[tuple[int, int]]: List of (x, y) grid coordinates along the line") + MCRF_NOTE("Useful for line-of-sight checks, projectile paths, and drawing lines on grids. " + "The algorithm ensures minimal grid traversal between two points.") + )}, + {NULL, NULL, 0, NULL} }; @@ -638,16 +654,10 @@ PyObject* PyInit_mcrfpy() PyObject* sys_modules = PyImport_GetModuleDict(); PyDict_SetItemString(sys_modules, "mcrfpy.automation", automation_module); } - - // Add libtcod submodule - PyObject* libtcod_module = McRFPy_Libtcod::init_libtcod_module(); - if (libtcod_module != NULL) { - PyModule_AddObject(m, "libtcod", libtcod_module); - // Also add to sys.modules for proper import behavior - PyObject* sys_modules = PyImport_GetModuleDict(); - PyDict_SetItemString(sys_modules, "mcrfpy.libtcod", libtcod_module); - } + // Note: mcrfpy.libtcod submodule removed in #215 + // - line() functionality replaced by mcrfpy.bresenham() + // - compute_fov() redundant with Grid.compute_fov() // Initialize PyTypeCache for thread-safe type lookups // This must be done after all types are added to the module @@ -1524,6 +1534,108 @@ PyObject* McRFPy_API::_setDevConsole(PyObject* self, PyObject* args) { Py_RETURN_NONE; } +// #215: Bresenham line algorithm implementation +// Helper to extract (x, y) from tuple, list, or Vector +static bool extract_point(PyObject* obj, int* x, int* y, const char* arg_name) { + // Try tuple/list first + if (PySequence_Check(obj) && PySequence_Size(obj) == 2) { + PyObject* x_obj = PySequence_GetItem(obj, 0); + PyObject* y_obj = PySequence_GetItem(obj, 1); + + bool ok = false; + if (x_obj && y_obj && PyNumber_Check(x_obj) && PyNumber_Check(y_obj)) { + PyObject* x_long = PyNumber_Long(x_obj); + PyObject* y_long = PyNumber_Long(y_obj); + if (x_long && y_long) { + *x = PyLong_AsLong(x_long); + *y = PyLong_AsLong(y_long); + ok = !PyErr_Occurred(); + } + Py_XDECREF(x_long); + Py_XDECREF(y_long); + } + + Py_XDECREF(x_obj); + Py_XDECREF(y_obj); + + if (ok) return true; + } + + // Try Vector type + PyObject* vector_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector"); + if (vector_type && PyObject_IsInstance(obj, vector_type)) { + Py_DECREF(vector_type); + PyVectorObject* vec = (PyVectorObject*)obj; + *x = static_cast(vec->data.x); + *y = static_cast(vec->data.y); + return true; + } + Py_XDECREF(vector_type); + + PyErr_Format(PyExc_TypeError, + "%s: expected (x, y) tuple or Vector, got %s", + arg_name, Py_TYPE(obj)->tp_name); + return false; +} + +PyObject* McRFPy_API::_bresenham(PyObject* self, PyObject* args, PyObject* kwargs) { + static const char* kwlist[] = {"start", "end", "include_start", "include_end", NULL}; + PyObject* start_obj = NULL; + PyObject* end_obj = NULL; + int include_start = 1; // Default: True + int include_end = 1; // Default: True + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "OO|pp", + const_cast(kwlist), + &start_obj, &end_obj, + &include_start, &include_end)) { + return NULL; + } + + int x1, y1, x2, y2; + if (!extract_point(start_obj, &x1, &y1, "start")) return NULL; + if (!extract_point(end_obj, &x2, &y2, "end")) return NULL; + + // Build result list using TCOD's Bresenham implementation + PyObject* result = PyList_New(0); + if (!result) return NULL; + + // Add start point if requested + if (include_start) { + PyObject* pos = Py_BuildValue("(ii)", x1, y1); + if (!pos) { Py_DECREF(result); return NULL; } + PyList_Append(result, pos); + Py_DECREF(pos); + } + + // Use TCOD's line algorithm for intermediate points + TCODLine::init(x1, y1, x2, y2); + int x, y; + + // Step through the line - TCODLine::step returns false until reaching endpoint + while (!TCODLine::step(&x, &y)) { + // Skip start point (already handled above if include_start) + if (x == x1 && y == y1) continue; + // Skip end point (handle below based on include_end) + if (x == x2 && y == y2) continue; + + PyObject* pos = Py_BuildValue("(ii)", x, y); + if (!pos) { Py_DECREF(result); return NULL; } + PyList_Append(result, pos); + Py_DECREF(pos); + } + + // Add end point if requested (and it's different from start) + if (include_end && (x1 != x2 || y1 != y2)) { + PyObject* pos = Py_BuildValue("(ii)", x2, y2); + if (!pos) { Py_DECREF(result); return NULL; } + PyList_Append(result, pos); + Py_DECREF(pos); + } + + return result; +} + // Benchmark logging implementation (#104) PyObject* McRFPy_API::_startBenchmark(PyObject* self, PyObject* args) { try { diff --git a/src/McRFPy_API.h b/src/McRFPy_API.h index d5587f3..b8276d4 100644 --- a/src/McRFPy_API.h +++ b/src/McRFPy_API.h @@ -76,6 +76,9 @@ public: // Developer console static PyObject* _setDevConsole(PyObject*, PyObject*); + // #215: Bresenham line algorithm (replaces mcrfpy.libtcod.line) + static PyObject* _bresenham(PyObject*, PyObject*, PyObject*); + // Scene lifecycle management for Python Scene objects static void triggerSceneChange(const std::string& from_scene, const std::string& to_scene); static void updatePythonScenes(float dt); diff --git a/src/McRFPy_Libtcod.cpp b/src/McRFPy_Libtcod.cpp deleted file mode 100644 index 71b20df..0000000 --- a/src/McRFPy_Libtcod.cpp +++ /dev/null @@ -1,164 +0,0 @@ -#include "McRFPy_Libtcod.h" -#include "McRFPy_API.h" -#include "UIGrid.h" -#include - -// Helper function to get UIGrid from Python object -static UIGrid* get_grid_from_pyobject(PyObject* obj) { - auto grid_type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"); - if (!grid_type) { - PyErr_SetString(PyExc_RuntimeError, "Could not find Grid type"); - return nullptr; - } - - if (!PyObject_IsInstance(obj, (PyObject*)grid_type)) { - Py_DECREF(grid_type); - PyErr_SetString(PyExc_TypeError, "First argument must be a Grid object"); - return nullptr; - } - - Py_DECREF(grid_type); - PyUIGridObject* pygrid = (PyUIGridObject*)obj; - return pygrid->data.get(); -} - -// Field of View computation -static PyObject* McRFPy_Libtcod::compute_fov(PyObject* self, PyObject* args) { - PyObject* grid_obj; - int x, y, radius; - int light_walls = 1; - int algorithm = FOV_BASIC; - - if (!PyArg_ParseTuple(args, "Oiii|ii", &grid_obj, &x, &y, &radius, - &light_walls, &algorithm)) { - return NULL; - } - - UIGrid* grid = get_grid_from_pyobject(grid_obj); - if (!grid) return NULL; - - // Compute FOV using grid's method - grid->computeFOV(x, y, radius, light_walls, (TCOD_fov_algorithm_t)algorithm); - - // Return list of visible cells - PyObject* visible_list = PyList_New(0); - for (int gy = 0; gy < grid->grid_h; gy++) { - for (int gx = 0; gx < grid->grid_w; gx++) { - if (grid->isInFOV(gx, gy)) { - PyObject* pos = Py_BuildValue("(ii)", gx, gy); - PyList_Append(visible_list, pos); - Py_DECREF(pos); - } - } - } - - return visible_list; -} - -// Line drawing algorithm -static PyObject* McRFPy_Libtcod::line(PyObject* self, PyObject* args) { - int x1, y1, x2, y2; - - if (!PyArg_ParseTuple(args, "iiii", &x1, &y1, &x2, &y2)) { - return NULL; - } - - // Use TCOD's line algorithm - TCODLine::init(x1, y1, x2, y2); - - PyObject* line_list = PyList_New(0); - int x, y; - - // Step through line - while (!TCODLine::step(&x, &y)) { - PyObject* pos = Py_BuildValue("(ii)", x, y); - PyList_Append(line_list, pos); - Py_DECREF(pos); - } - - return line_list; -} - -// Line iterator (generator-like function) -static PyObject* McRFPy_Libtcod::line_iter(PyObject* self, PyObject* args) { - // For simplicity, just call line() for now - // A proper implementation would create an iterator object - return line(self, args); -} - -// Pathfinding functions removed - use Grid.find_path() and Grid.get_dijkstra_map() instead -// These return AStarPath and DijkstraMap objects (see UIGridPathfinding.h) - -// Method definitions -static PyMethodDef libtcodMethods[] = { - {"compute_fov", McRFPy_Libtcod::compute_fov, METH_VARARGS, - "compute_fov(grid, x, y, radius, light_walls=True, algorithm=mcrfpy.FOV.BASIC)\n\n" - "Compute field of view from a position.\n\n" - "Args:\n" - " grid: Grid object to compute FOV on\n" - " x, y: Origin position\n" - " radius: Maximum sight radius\n" - " light_walls: Whether walls are lit when in FOV\n" - " algorithm: FOV algorithm (mcrfpy.FOV.BASIC, mcrfpy.FOV.SHADOW, etc.)\n\n" - "Returns:\n" - " List of (x, y) tuples for visible cells"}, - - {"line", McRFPy_Libtcod::line, METH_VARARGS, - "line(x1, y1, x2, y2)\n\n" - "Get cells along a line using Bresenham's algorithm.\n\n" - "Args:\n" - " x1, y1: Starting position\n" - " x2, y2: Ending position\n\n" - "Returns:\n" - " List of (x, y) tuples along the line"}, - - {"line_iter", McRFPy_Libtcod::line_iter, METH_VARARGS, - "line_iter(x1, y1, x2, y2)\n\n" - "Iterate over cells along a line.\n\n" - "Args:\n" - " x1, y1: Starting position\n" - " x2, y2: Ending position\n\n" - "Returns:\n" - " Iterator of (x, y) tuples along the line"}, - - {NULL, NULL, 0, NULL} -}; - -// Module definition -static PyModuleDef libtcodModule = { - PyModuleDef_HEAD_INIT, - "mcrfpy.libtcod", - "TCOD-compatible algorithms for field of view and line drawing.\n\n" - "This module provides access to TCOD's algorithms integrated with McRogueFace grids.\n" - "Unlike the original TCOD, these functions work directly with Grid objects.\n\n" - "FOV Algorithms (use mcrfpy.FOV enum):\n" - " mcrfpy.FOV.BASIC - Basic circular FOV\n" - " mcrfpy.FOV.SHADOW - Shadow casting (recommended)\n" - " mcrfpy.FOV.DIAMOND - Diamond-shaped FOV\n" - " mcrfpy.FOV.PERMISSIVE_0 through PERMISSIVE_8 - Permissive variants\n" - " mcrfpy.FOV.RESTRICTIVE - Most restrictive FOV\n" - " mcrfpy.FOV.SYMMETRIC_SHADOWCAST - Symmetric shadow casting\n\n" - "Pathfinding:\n" - " Use Grid.find_path() for A* pathfinding (returns AStarPath objects)\n" - " Use Grid.get_dijkstra_map() for Dijkstra pathfinding (returns DijkstraMap objects)\n\n" - "Example:\n" - " import mcrfpy\n" - " from mcrfpy import libtcod\n\n" - " grid = mcrfpy.Grid(50, 50)\n" - " visible = libtcod.compute_fov(grid, 25, 25, 10)\n" - " path = grid.find_path((0, 0), (49, 49)) # Returns AStarPath", - -1, - libtcodMethods -}; - -// Module initialization -PyObject* McRFPy_Libtcod::init_libtcod_module() { - PyObject* m = PyModule_Create(&libtcodModule); - if (m == NULL) { - return NULL; - } - - // FOV algorithm constants now provided by mcrfpy.FOV enum (#114) - - return m; -} diff --git a/src/McRFPy_Libtcod.h b/src/McRFPy_Libtcod.h deleted file mode 100644 index dd57ad6..0000000 --- a/src/McRFPy_Libtcod.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once -#include "Common.h" -#include "Python.h" -#include - -namespace McRFPy_Libtcod -{ - // Field of View algorithms - static PyObject* compute_fov(PyObject* self, PyObject* args); - - // Pathfinding - static PyObject* find_path(PyObject* self, PyObject* args); - static PyObject* dijkstra_new(PyObject* self, PyObject* args); - static PyObject* dijkstra_compute(PyObject* self, PyObject* args); - static PyObject* dijkstra_get_distance(PyObject* self, PyObject* args); - static PyObject* dijkstra_path_to(PyObject* self, PyObject* args); - - // Line algorithms - static PyObject* line(PyObject* self, PyObject* args); - static PyObject* line_iter(PyObject* self, PyObject* args); - - // Module initialization - PyObject* init_libtcod_module(); -} \ No newline at end of file