Fix #215: Replace mcrfpy.libtcod with mcrfpy.bresenham()
- Remove mcrfpy.libtcod submodule entirely - Add mcrfpy.bresenham(start, end, include_start=True, include_end=True) - Accepts tuples or Vector objects for positions - Returns list of (x, y) tuples along the line - compute_fov() was redundant with Grid.compute_fov() - line() functionality now available as top-level bresenham() Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
257e52327b
commit
4ead2f25fe
4 changed files with 125 additions and 198 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "UIDrawable.h"
|
#include "UIDrawable.h"
|
||||||
#include "McRFPy_Automation.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 "McRFPy_Doc.h"
|
||||||
#include "PyTypeCache.h" // Thread-safe cached Python types
|
#include "PyTypeCache.h" // Thread-safe cached Python types
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
|
|
@ -26,6 +26,7 @@
|
||||||
#include "PyBSP.h" // Procedural generation BSP (#202-206)
|
#include "PyBSP.h" // Procedural generation BSP (#202-206)
|
||||||
#include "PyNoiseSource.h" // Procedural generation noise (#207-208)
|
#include "PyNoiseSource.h" // Procedural generation noise (#207-208)
|
||||||
#include "PyLock.h" // Thread synchronization (#219)
|
#include "PyLock.h" // Thread synchronization (#219)
|
||||||
|
#include "PyVector.h" // For bresenham Vector support (#215)
|
||||||
#include "McRogueFaceVersion.h"
|
#include "McRogueFaceVersion.h"
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
#include "ImGuiConsole.h"
|
#include "ImGuiConsole.h"
|
||||||
|
|
@ -304,6 +305,21 @@ static PyMethodDef mcrfpyMethods[] = {
|
||||||
"Without this, modifying UI from threads may cause visual glitches or crashes.")
|
"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}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -638,16 +654,10 @@ PyObject* PyInit_mcrfpy()
|
||||||
PyObject* sys_modules = PyImport_GetModuleDict();
|
PyObject* sys_modules = PyImport_GetModuleDict();
|
||||||
PyDict_SetItemString(sys_modules, "mcrfpy.automation", automation_module);
|
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
|
// Note: mcrfpy.libtcod submodule removed in #215
|
||||||
PyObject* sys_modules = PyImport_GetModuleDict();
|
// - line() functionality replaced by mcrfpy.bresenham()
|
||||||
PyDict_SetItemString(sys_modules, "mcrfpy.libtcod", libtcod_module);
|
// - compute_fov() redundant with Grid.compute_fov()
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize PyTypeCache for thread-safe type lookups
|
// Initialize PyTypeCache for thread-safe type lookups
|
||||||
// This must be done after all types are added to the module
|
// 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;
|
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<int>(vec->data.x);
|
||||||
|
*y = static_cast<int>(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<char**>(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)
|
// Benchmark logging implementation (#104)
|
||||||
PyObject* McRFPy_API::_startBenchmark(PyObject* self, PyObject* args) {
|
PyObject* McRFPy_API::_startBenchmark(PyObject* self, PyObject* args) {
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,9 @@ public:
|
||||||
// Developer console
|
// Developer console
|
||||||
static PyObject* _setDevConsole(PyObject*, PyObject*);
|
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
|
// Scene lifecycle management for Python Scene objects
|
||||||
static void triggerSceneChange(const std::string& from_scene, const std::string& to_scene);
|
static void triggerSceneChange(const std::string& from_scene, const std::string& to_scene);
|
||||||
static void updatePythonScenes(float dt);
|
static void updatePythonScenes(float dt);
|
||||||
|
|
|
||||||
|
|
@ -1,164 +0,0 @@
|
||||||
#include "McRFPy_Libtcod.h"
|
|
||||||
#include "McRFPy_API.h"
|
|
||||||
#include "UIGrid.h"
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
#pragma once
|
|
||||||
#include "Common.h"
|
|
||||||
#include "Python.h"
|
|
||||||
#include <libtcod.h>
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue