UIGridPathfinding: clear and separate A-star and Djikstra path systems
This commit is contained in:
parent
9eacedc624
commit
b32f5af28c
7 changed files with 974 additions and 509 deletions
|
|
@ -20,6 +20,7 @@
|
||||||
#include "PyMusic.h"
|
#include "PyMusic.h"
|
||||||
#include "PyKeyboard.h"
|
#include "PyKeyboard.h"
|
||||||
#include "PyMouse.h"
|
#include "PyMouse.h"
|
||||||
|
#include "UIGridPathfinding.h" // AStarPath and DijkstraMap types
|
||||||
#include "McRogueFaceVersion.h"
|
#include "McRogueFaceVersion.h"
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
#include "ImGuiConsole.h"
|
#include "ImGuiConsole.h"
|
||||||
|
|
@ -410,6 +411,10 @@ PyObject* PyInit_mcrfpy()
|
||||||
/*mouse state (#186)*/
|
/*mouse state (#186)*/
|
||||||
&PyMouseType,
|
&PyMouseType,
|
||||||
|
|
||||||
|
/*pathfinding result types*/
|
||||||
|
&mcrfpydef::PyAStarPathType,
|
||||||
|
&mcrfpydef::PyDijkstraMapType,
|
||||||
|
|
||||||
nullptr};
|
nullptr};
|
||||||
|
|
||||||
// Types that are used internally but NOT exported to module namespace (#189)
|
// Types that are used internally but NOT exported to module namespace (#189)
|
||||||
|
|
@ -422,6 +427,9 @@ PyObject* PyInit_mcrfpy()
|
||||||
&PyUICollectionType, &PyUICollectionIterType,
|
&PyUICollectionType, &PyUICollectionIterType,
|
||||||
&PyUIEntityCollectionType, &PyUIEntityCollectionIterType,
|
&PyUIEntityCollectionType, &PyUIEntityCollectionIterType,
|
||||||
|
|
||||||
|
/*pathfinding iterator - returned by AStarPath.__iter__() but not directly instantiable*/
|
||||||
|
&mcrfpydef::PyAStarPathIterType,
|
||||||
|
|
||||||
nullptr};
|
nullptr};
|
||||||
|
|
||||||
// Set up PyWindowType methods and getsetters before PyType_Ready
|
// Set up PyWindowType methods and getsetters before PyType_Ready
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,13 @@ static UIGrid* get_grid_from_pyobject(PyObject* obj) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Could not find Grid type");
|
PyErr_SetString(PyExc_RuntimeError, "Could not find Grid type");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!PyObject_IsInstance(obj, (PyObject*)grid_type)) {
|
if (!PyObject_IsInstance(obj, (PyObject*)grid_type)) {
|
||||||
Py_DECREF(grid_type);
|
Py_DECREF(grid_type);
|
||||||
PyErr_SetString(PyExc_TypeError, "First argument must be a Grid object");
|
PyErr_SetString(PyExc_TypeError, "First argument must be a Grid object");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_DECREF(grid_type);
|
Py_DECREF(grid_type);
|
||||||
PyUIGridObject* pygrid = (PyUIGridObject*)obj;
|
PyUIGridObject* pygrid = (PyUIGridObject*)obj;
|
||||||
return pygrid->data.get();
|
return pygrid->data.get();
|
||||||
|
|
@ -28,18 +28,18 @@ static PyObject* McRFPy_Libtcod::compute_fov(PyObject* self, PyObject* args) {
|
||||||
int x, y, radius;
|
int x, y, radius;
|
||||||
int light_walls = 1;
|
int light_walls = 1;
|
||||||
int algorithm = FOV_BASIC;
|
int algorithm = FOV_BASIC;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "Oiii|ii", &grid_obj, &x, &y, &radius,
|
if (!PyArg_ParseTuple(args, "Oiii|ii", &grid_obj, &x, &y, &radius,
|
||||||
&light_walls, &algorithm)) {
|
&light_walls, &algorithm)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
UIGrid* grid = get_grid_from_pyobject(grid_obj);
|
UIGrid* grid = get_grid_from_pyobject(grid_obj);
|
||||||
if (!grid) return NULL;
|
if (!grid) return NULL;
|
||||||
|
|
||||||
// Compute FOV using grid's method
|
// Compute FOV using grid's method
|
||||||
grid->computeFOV(x, y, radius, light_walls, (TCOD_fov_algorithm_t)algorithm);
|
grid->computeFOV(x, y, radius, light_walls, (TCOD_fov_algorithm_t)algorithm);
|
||||||
|
|
||||||
// Return list of visible cells
|
// Return list of visible cells
|
||||||
PyObject* visible_list = PyList_New(0);
|
PyObject* visible_list = PyList_New(0);
|
||||||
for (int gy = 0; gy < grid->grid_h; gy++) {
|
for (int gy = 0; gy < grid->grid_h; gy++) {
|
||||||
|
|
@ -51,57 +51,31 @@ static PyObject* McRFPy_Libtcod::compute_fov(PyObject* self, PyObject* args) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return visible_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A* Pathfinding
|
return visible_list;
|
||||||
static PyObject* McRFPy_Libtcod::find_path(PyObject* self, PyObject* args) {
|
|
||||||
PyObject* grid_obj;
|
|
||||||
int x1, y1, x2, y2;
|
|
||||||
float diagonal_cost = 1.41f;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "Oiiii|f", &grid_obj, &x1, &y1, &x2, &y2, &diagonal_cost)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
UIGrid* grid = get_grid_from_pyobject(grid_obj);
|
|
||||||
if (!grid) return NULL;
|
|
||||||
|
|
||||||
// Get path from grid
|
|
||||||
std::vector<std::pair<int, int>> path = grid->findPath(x1, y1, x2, y2, diagonal_cost);
|
|
||||||
|
|
||||||
// Convert to Python list
|
|
||||||
PyObject* path_list = PyList_New(path.size());
|
|
||||||
for (size_t i = 0; i < path.size(); i++) {
|
|
||||||
PyObject* pos = Py_BuildValue("(ii)", path[i].first, path[i].second);
|
|
||||||
PyList_SetItem(path_list, i, pos); // steals reference
|
|
||||||
}
|
|
||||||
|
|
||||||
return path_list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Line drawing algorithm
|
// Line drawing algorithm
|
||||||
static PyObject* McRFPy_Libtcod::line(PyObject* self, PyObject* args) {
|
static PyObject* McRFPy_Libtcod::line(PyObject* self, PyObject* args) {
|
||||||
int x1, y1, x2, y2;
|
int x1, y1, x2, y2;
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "iiii", &x1, &y1, &x2, &y2)) {
|
if (!PyArg_ParseTuple(args, "iiii", &x1, &y1, &x2, &y2)) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use TCOD's line algorithm
|
// Use TCOD's line algorithm
|
||||||
TCODLine::init(x1, y1, x2, y2);
|
TCODLine::init(x1, y1, x2, y2);
|
||||||
|
|
||||||
PyObject* line_list = PyList_New(0);
|
PyObject* line_list = PyList_New(0);
|
||||||
int x, y;
|
int x, y;
|
||||||
|
|
||||||
// Step through line
|
// Step through line
|
||||||
while (!TCODLine::step(&x, &y)) {
|
while (!TCODLine::step(&x, &y)) {
|
||||||
PyObject* pos = Py_BuildValue("(ii)", x, y);
|
PyObject* pos = Py_BuildValue("(ii)", x, y);
|
||||||
PyList_Append(line_list, pos);
|
PyList_Append(line_list, pos);
|
||||||
Py_DECREF(pos);
|
Py_DECREF(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
return line_list;
|
return line_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,80 +86,8 @@ static PyObject* McRFPy_Libtcod::line_iter(PyObject* self, PyObject* args) {
|
||||||
return line(self, args);
|
return line(self, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dijkstra pathfinding
|
// Pathfinding functions removed - use Grid.find_path() and Grid.get_dijkstra_map() instead
|
||||||
static PyObject* McRFPy_Libtcod::dijkstra_new(PyObject* self, PyObject* args) {
|
// These return AStarPath and DijkstraMap objects (see UIGridPathfinding.h)
|
||||||
PyObject* grid_obj;
|
|
||||||
float diagonal_cost = 1.41f;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "O|f", &grid_obj, &diagonal_cost)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
UIGrid* grid = get_grid_from_pyobject(grid_obj);
|
|
||||||
if (!grid) return NULL;
|
|
||||||
|
|
||||||
// For now, just return the grid object since Dijkstra is part of the grid
|
|
||||||
Py_INCREF(grid_obj);
|
|
||||||
return grid_obj;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject* McRFPy_Libtcod::dijkstra_compute(PyObject* self, PyObject* args) {
|
|
||||||
PyObject* grid_obj;
|
|
||||||
int root_x, root_y;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "Oii", &grid_obj, &root_x, &root_y)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
UIGrid* grid = get_grid_from_pyobject(grid_obj);
|
|
||||||
if (!grid) return NULL;
|
|
||||||
|
|
||||||
grid->computeDijkstra(root_x, root_y);
|
|
||||||
Py_RETURN_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject* McRFPy_Libtcod::dijkstra_get_distance(PyObject* self, PyObject* args) {
|
|
||||||
PyObject* grid_obj;
|
|
||||||
int x, y;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "Oii", &grid_obj, &x, &y)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
UIGrid* grid = get_grid_from_pyobject(grid_obj);
|
|
||||||
if (!grid) return NULL;
|
|
||||||
|
|
||||||
float distance = grid->getDijkstraDistance(x, y);
|
|
||||||
if (distance < 0) {
|
|
||||||
Py_RETURN_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return PyFloat_FromDouble(distance);
|
|
||||||
}
|
|
||||||
|
|
||||||
static PyObject* McRFPy_Libtcod::dijkstra_path_to(PyObject* self, PyObject* args) {
|
|
||||||
PyObject* grid_obj;
|
|
||||||
int x, y;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTuple(args, "Oii", &grid_obj, &x, &y)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
UIGrid* grid = get_grid_from_pyobject(grid_obj);
|
|
||||||
if (!grid) return NULL;
|
|
||||||
|
|
||||||
std::vector<std::pair<int, int>> path = grid->getDijkstraPath(x, y);
|
|
||||||
|
|
||||||
PyObject* path_list = PyList_New(path.size());
|
|
||||||
for (size_t i = 0; i < path.size(); i++) {
|
|
||||||
PyObject* pos = Py_BuildValue("(ii)", path[i].first, path[i].second);
|
|
||||||
PyList_SetItem(path_list, i, pos); // steals reference
|
|
||||||
}
|
|
||||||
|
|
||||||
return path_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FOV algorithm constants removed - use mcrfpy.FOV enum instead (#114)
|
|
||||||
|
|
||||||
// Method definitions
|
// Method definitions
|
||||||
static PyMethodDef libtcodMethods[] = {
|
static PyMethodDef libtcodMethods[] = {
|
||||||
|
|
@ -200,18 +102,7 @@ static PyMethodDef libtcodMethods[] = {
|
||||||
" algorithm: FOV algorithm (mcrfpy.FOV.BASIC, mcrfpy.FOV.SHADOW, etc.)\n\n"
|
" algorithm: FOV algorithm (mcrfpy.FOV.BASIC, mcrfpy.FOV.SHADOW, etc.)\n\n"
|
||||||
"Returns:\n"
|
"Returns:\n"
|
||||||
" List of (x, y) tuples for visible cells"},
|
" List of (x, y) tuples for visible cells"},
|
||||||
|
|
||||||
{"find_path", McRFPy_Libtcod::find_path, METH_VARARGS,
|
|
||||||
"find_path(grid, x1, y1, x2, y2, diagonal_cost=1.41)\n\n"
|
|
||||||
"Find shortest path between two points using A*.\n\n"
|
|
||||||
"Args:\n"
|
|
||||||
" grid: Grid object to pathfind on\n"
|
|
||||||
" x1, y1: Starting position\n"
|
|
||||||
" x2, y2: Target position\n"
|
|
||||||
" diagonal_cost: Cost of diagonal movement\n\n"
|
|
||||||
"Returns:\n"
|
|
||||||
" List of (x, y) tuples representing the path, or empty list if no path exists"},
|
|
||||||
|
|
||||||
{"line", McRFPy_Libtcod::line, METH_VARARGS,
|
{"line", McRFPy_Libtcod::line, METH_VARARGS,
|
||||||
"line(x1, y1, x2, y2)\n\n"
|
"line(x1, y1, x2, y2)\n\n"
|
||||||
"Get cells along a line using Bresenham's algorithm.\n\n"
|
"Get cells along a line using Bresenham's algorithm.\n\n"
|
||||||
|
|
@ -220,7 +111,7 @@ static PyMethodDef libtcodMethods[] = {
|
||||||
" x2, y2: Ending position\n\n"
|
" x2, y2: Ending position\n\n"
|
||||||
"Returns:\n"
|
"Returns:\n"
|
||||||
" List of (x, y) tuples along the line"},
|
" List of (x, y) tuples along the line"},
|
||||||
|
|
||||||
{"line_iter", McRFPy_Libtcod::line_iter, METH_VARARGS,
|
{"line_iter", McRFPy_Libtcod::line_iter, METH_VARARGS,
|
||||||
"line_iter(x1, y1, x2, y2)\n\n"
|
"line_iter(x1, y1, x2, y2)\n\n"
|
||||||
"Iterate over cells along a line.\n\n"
|
"Iterate over cells along a line.\n\n"
|
||||||
|
|
@ -229,41 +120,7 @@ static PyMethodDef libtcodMethods[] = {
|
||||||
" x2, y2: Ending position\n\n"
|
" x2, y2: Ending position\n\n"
|
||||||
"Returns:\n"
|
"Returns:\n"
|
||||||
" Iterator of (x, y) tuples along the line"},
|
" Iterator of (x, y) tuples along the line"},
|
||||||
|
|
||||||
{"dijkstra_new", McRFPy_Libtcod::dijkstra_new, METH_VARARGS,
|
|
||||||
"dijkstra_new(grid, diagonal_cost=1.41)\n\n"
|
|
||||||
"Create a Dijkstra pathfinding context for a grid.\n\n"
|
|
||||||
"Args:\n"
|
|
||||||
" grid: Grid object to use for pathfinding\n"
|
|
||||||
" diagonal_cost: Cost of diagonal movement\n\n"
|
|
||||||
"Returns:\n"
|
|
||||||
" Grid object configured for Dijkstra pathfinding"},
|
|
||||||
|
|
||||||
{"dijkstra_compute", McRFPy_Libtcod::dijkstra_compute, METH_VARARGS,
|
|
||||||
"dijkstra_compute(grid, root_x, root_y)\n\n"
|
|
||||||
"Compute Dijkstra distance map from root position.\n\n"
|
|
||||||
"Args:\n"
|
|
||||||
" grid: Grid object with Dijkstra context\n"
|
|
||||||
" root_x, root_y: Root position to compute distances from"},
|
|
||||||
|
|
||||||
{"dijkstra_get_distance", McRFPy_Libtcod::dijkstra_get_distance, METH_VARARGS,
|
|
||||||
"dijkstra_get_distance(grid, x, y)\n\n"
|
|
||||||
"Get distance from root to a position.\n\n"
|
|
||||||
"Args:\n"
|
|
||||||
" grid: Grid object with computed Dijkstra map\n"
|
|
||||||
" x, y: Position to get distance for\n\n"
|
|
||||||
"Returns:\n"
|
|
||||||
" Float distance or None if position is invalid/unreachable"},
|
|
||||||
|
|
||||||
{"dijkstra_path_to", McRFPy_Libtcod::dijkstra_path_to, METH_VARARGS,
|
|
||||||
"dijkstra_path_to(grid, x, y)\n\n"
|
|
||||||
"Get shortest path from position to Dijkstra root.\n\n"
|
|
||||||
"Args:\n"
|
|
||||||
" grid: Grid object with computed Dijkstra map\n"
|
|
||||||
" x, y: Starting position\n\n"
|
|
||||||
"Returns:\n"
|
|
||||||
" List of (x, y) tuples representing the path to root"},
|
|
||||||
|
|
||||||
{NULL, NULL, 0, NULL}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -271,7 +128,7 @@ static PyMethodDef libtcodMethods[] = {
|
||||||
static PyModuleDef libtcodModule = {
|
static PyModuleDef libtcodModule = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
"mcrfpy.libtcod",
|
"mcrfpy.libtcod",
|
||||||
"TCOD-compatible algorithms for field of view, pathfinding, and line drawing.\n\n"
|
"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"
|
"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"
|
"Unlike the original TCOD, these functions work directly with Grid objects.\n\n"
|
||||||
"FOV Algorithms (use mcrfpy.FOV enum):\n"
|
"FOV Algorithms (use mcrfpy.FOV enum):\n"
|
||||||
|
|
@ -281,12 +138,15 @@ static PyModuleDef libtcodModule = {
|
||||||
" mcrfpy.FOV.PERMISSIVE_0 through PERMISSIVE_8 - Permissive variants\n"
|
" mcrfpy.FOV.PERMISSIVE_0 through PERMISSIVE_8 - Permissive variants\n"
|
||||||
" mcrfpy.FOV.RESTRICTIVE - Most restrictive FOV\n"
|
" mcrfpy.FOV.RESTRICTIVE - Most restrictive FOV\n"
|
||||||
" mcrfpy.FOV.SYMMETRIC_SHADOWCAST - Symmetric shadow casting\n\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"
|
"Example:\n"
|
||||||
" import mcrfpy\n"
|
" import mcrfpy\n"
|
||||||
" from mcrfpy import libtcod\n\n"
|
" from mcrfpy import libtcod\n\n"
|
||||||
" grid = mcrfpy.Grid(50, 50)\n"
|
" grid = mcrfpy.Grid(50, 50)\n"
|
||||||
" visible = libtcod.compute_fov(grid, 25, 25, 10)\n"
|
" visible = libtcod.compute_fov(grid, 25, 25, 10)\n"
|
||||||
" path = libtcod.find_path(grid, 0, 0, 49, 49)",
|
" path = grid.find_path((0, 0), (49, 49)) # Returns AStarPath",
|
||||||
-1,
|
-1,
|
||||||
libtcodMethods
|
libtcodMethods
|
||||||
};
|
};
|
||||||
|
|
@ -297,8 +157,8 @@ PyObject* McRFPy_Libtcod::init_libtcod_module() {
|
||||||
if (m == NULL) {
|
if (m == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FOV algorithm constants now provided by mcrfpy.FOV enum (#114)
|
// FOV algorithm constants now provided by mcrfpy.FOV enum (#114)
|
||||||
|
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <libtcod.h>
|
||||||
#include "PyObjectUtils.h"
|
#include "PyObjectUtils.h"
|
||||||
#include "PyVector.h"
|
#include "PyVector.h"
|
||||||
#include "PythonObjectCache.h"
|
#include "PythonObjectCache.h"
|
||||||
|
|
@ -749,39 +750,45 @@ PyObject* UIEntity::path_to(PyUIEntityObject* self, PyObject* args, PyObject* kw
|
||||||
PyErr_SetString(PyExc_ValueError, "Entity must be associated with a grid to compute paths");
|
PyErr_SetString(PyExc_ValueError, "Entity must be associated with a grid to compute paths");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current position
|
// Get current position
|
||||||
int current_x = static_cast<int>(self->data->position.x);
|
int current_x = static_cast<int>(self->data->position.x);
|
||||||
int current_y = static_cast<int>(self->data->position.y);
|
int current_y = static_cast<int>(self->data->position.y);
|
||||||
|
|
||||||
// Validate target position
|
// Validate target position
|
||||||
auto grid = self->data->grid;
|
auto grid = self->data->grid;
|
||||||
if (target_x < 0 || target_x >= grid->grid_w || target_y < 0 || target_y >= grid->grid_h) {
|
if (target_x < 0 || target_x >= grid->grid_w || target_y < 0 || target_y >= grid->grid_h) {
|
||||||
PyErr_Format(PyExc_ValueError, "Target position (%d, %d) is out of grid bounds (0-%d, 0-%d)",
|
PyErr_Format(PyExc_ValueError, "Target position (%d, %d) is out of grid bounds (0-%d, 0-%d)",
|
||||||
target_x, target_y, grid->grid_w - 1, grid->grid_h - 1);
|
target_x, target_y, grid->grid_w - 1, grid->grid_h - 1);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use the grid's Dijkstra implementation
|
// Use A* pathfinding via temporary TCODPath
|
||||||
grid->computeDijkstra(current_x, current_y);
|
TCODPath tcod_path(grid->getTCODMap(), 1.41f);
|
||||||
auto path = grid->getDijkstraPath(target_x, target_y);
|
if (!tcod_path.compute(current_x, current_y, target_x, target_y)) {
|
||||||
|
// No path found - return empty list
|
||||||
|
return PyList_New(0);
|
||||||
|
}
|
||||||
|
|
||||||
// Convert path to Python list of tuples
|
// Convert path to Python list of tuples
|
||||||
PyObject* path_list = PyList_New(path.size());
|
PyObject* path_list = PyList_New(tcod_path.size());
|
||||||
if (!path_list) return PyErr_NoMemory();
|
if (!path_list) return PyErr_NoMemory();
|
||||||
|
|
||||||
for (size_t i = 0; i < path.size(); ++i) {
|
for (int i = 0; i < tcod_path.size(); ++i) {
|
||||||
|
int px, py;
|
||||||
|
tcod_path.get(i, &px, &py);
|
||||||
|
|
||||||
PyObject* coord_tuple = PyTuple_New(2);
|
PyObject* coord_tuple = PyTuple_New(2);
|
||||||
if (!coord_tuple) {
|
if (!coord_tuple) {
|
||||||
Py_DECREF(path_list);
|
Py_DECREF(path_list);
|
||||||
return PyErr_NoMemory();
|
return PyErr_NoMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
PyTuple_SetItem(coord_tuple, 0, PyLong_FromLong(path[i].first));
|
PyTuple_SetItem(coord_tuple, 0, PyLong_FromLong(px));
|
||||||
PyTuple_SetItem(coord_tuple, 1, PyLong_FromLong(path[i].second));
|
PyTuple_SetItem(coord_tuple, 1, PyLong_FromLong(py));
|
||||||
PyList_SetItem(path_list, i, coord_tuple);
|
PyList_SetItem(path_list, i, coord_tuple);
|
||||||
}
|
}
|
||||||
|
|
||||||
return path_list;
|
return path_list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
372
src/UIGrid.cpp
372
src/UIGrid.cpp
|
|
@ -1,4 +1,5 @@
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
|
#include "UIGridPathfinding.h" // New pathfinding API
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PythonObjectCache.h"
|
#include "PythonObjectCache.h"
|
||||||
|
|
@ -17,7 +18,7 @@
|
||||||
|
|
||||||
UIGrid::UIGrid()
|
UIGrid::UIGrid()
|
||||||
: grid_w(0), grid_h(0), zoom(1.0f), center_x(0.0f), center_y(0.0f), ptex(nullptr),
|
: grid_w(0), grid_h(0), zoom(1.0f), center_x(0.0f), center_y(0.0f), ptex(nullptr),
|
||||||
fill_color(8, 8, 8, 255), tcod_map(nullptr), tcod_dijkstra(nullptr), tcod_path(nullptr),
|
fill_color(8, 8, 8, 255), tcod_map(nullptr),
|
||||||
perspective_enabled(false), fov_algorithm(FOV_BASIC), fov_radius(10),
|
perspective_enabled(false), fov_algorithm(FOV_BASIC), fov_radius(10),
|
||||||
use_chunks(false) // Default to omniscient view
|
use_chunks(false) // Default to omniscient view
|
||||||
{
|
{
|
||||||
|
|
@ -49,7 +50,7 @@ UIGrid::UIGrid(int gx, int gy, std::shared_ptr<PyTexture> _ptex, sf::Vector2f _x
|
||||||
: grid_w(gx), grid_h(gy),
|
: grid_w(gx), grid_h(gy),
|
||||||
zoom(1.0f),
|
zoom(1.0f),
|
||||||
ptex(_ptex),
|
ptex(_ptex),
|
||||||
fill_color(8, 8, 8, 255), tcod_map(nullptr), tcod_dijkstra(nullptr), tcod_path(nullptr),
|
fill_color(8, 8, 8, 255), tcod_map(nullptr),
|
||||||
perspective_enabled(false), fov_algorithm(FOV_BASIC), fov_radius(10),
|
perspective_enabled(false), fov_algorithm(FOV_BASIC), fov_radius(10),
|
||||||
use_chunks(gx > CHUNK_THRESHOLD || gy > CHUNK_THRESHOLD) // #123 - Use chunks for large grids
|
use_chunks(gx > CHUNK_THRESHOLD || gy > CHUNK_THRESHOLD) // #123 - Use chunks for large grids
|
||||||
{
|
{
|
||||||
|
|
@ -84,14 +85,10 @@ UIGrid::UIGrid(int gx, int gy, std::shared_ptr<PyTexture> _ptex, sf::Vector2f _x
|
||||||
// textures are upside-down inside renderTexture
|
// textures are upside-down inside renderTexture
|
||||||
output.setTexture(renderTexture.getTexture());
|
output.setTexture(renderTexture.getTexture());
|
||||||
|
|
||||||
// Create TCOD map
|
// Create TCOD map for FOV and as source for pathfinding
|
||||||
tcod_map = new TCODMap(gx, gy);
|
tcod_map = new TCODMap(gx, gy);
|
||||||
|
// Note: DijkstraMap objects are created on-demand via get_dijkstra_map()
|
||||||
// Create TCOD dijkstra pathfinder
|
// A* paths are computed on-demand via find_path()
|
||||||
tcod_dijkstra = new TCODDijkstra(tcod_map);
|
|
||||||
|
|
||||||
// Create TCOD A* pathfinder
|
|
||||||
tcod_path = new TCODPath(tcod_map);
|
|
||||||
|
|
||||||
// #123 - Initialize storage based on grid size
|
// #123 - Initialize storage based on grid size
|
||||||
if (use_chunks) {
|
if (use_chunks) {
|
||||||
|
|
@ -368,14 +365,9 @@ UIGridPoint& UIGrid::at(int x, int y)
|
||||||
|
|
||||||
UIGrid::~UIGrid()
|
UIGrid::~UIGrid()
|
||||||
{
|
{
|
||||||
if (tcod_path) {
|
// Clear Dijkstra maps first (they reference tcod_map)
|
||||||
delete tcod_path;
|
dijkstra_maps.clear();
|
||||||
tcod_path = nullptr;
|
|
||||||
}
|
|
||||||
if (tcod_dijkstra) {
|
|
||||||
delete tcod_dijkstra;
|
|
||||||
tcod_dijkstra = nullptr;
|
|
||||||
}
|
|
||||||
if (tcod_map) {
|
if (tcod_map) {
|
||||||
delete tcod_map;
|
delete tcod_map;
|
||||||
tcod_map = nullptr;
|
tcod_map = nullptr;
|
||||||
|
|
@ -476,98 +468,9 @@ bool UIGrid::isInFOV(int x, int y) const
|
||||||
return tcod_map->isInFov(x, y);
|
return tcod_map->isInFov(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::pair<int, int>> UIGrid::findPath(int x1, int y1, int x2, int y2, float diagonalCost)
|
// Pathfinding methods moved to UIGridPathfinding.cpp
|
||||||
{
|
// - Grid.find_path() returns AStarPath objects
|
||||||
std::vector<std::pair<int, int>> path;
|
// - Grid.get_dijkstra_map() returns DijkstraMap objects (cached)
|
||||||
|
|
||||||
if (!tcod_map || x1 < 0 || x1 >= grid_w || y1 < 0 || y1 >= grid_h ||
|
|
||||||
x2 < 0 || x2 >= grid_w || y2 < 0 || y2 >= grid_h) {
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
TCODPath tcod_path(tcod_map, diagonalCost);
|
|
||||||
if (tcod_path.compute(x1, y1, x2, y2)) {
|
|
||||||
for (int i = 0; i < tcod_path.size(); i++) {
|
|
||||||
int x, y;
|
|
||||||
tcod_path.get(i, &x, &y);
|
|
||||||
path.push_back(std::make_pair(x, y));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
void UIGrid::computeDijkstra(int rootX, int rootY, float diagonalCost)
|
|
||||||
{
|
|
||||||
if (!tcod_map || !tcod_dijkstra || rootX < 0 || rootX >= grid_w || rootY < 0 || rootY >= grid_h) return;
|
|
||||||
|
|
||||||
// Compute the Dijkstra map from the root position
|
|
||||||
tcod_dijkstra->compute(rootX, rootY);
|
|
||||||
}
|
|
||||||
|
|
||||||
float UIGrid::getDijkstraDistance(int x, int y) const
|
|
||||||
{
|
|
||||||
if (!tcod_dijkstra || x < 0 || x >= grid_w || y < 0 || y >= grid_h) {
|
|
||||||
return -1.0f; // Invalid position
|
|
||||||
}
|
|
||||||
|
|
||||||
return tcod_dijkstra->getDistance(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<int, int>> UIGrid::getDijkstraPath(int x, int y) const
|
|
||||||
{
|
|
||||||
std::vector<std::pair<int, int>> path;
|
|
||||||
|
|
||||||
if (!tcod_dijkstra || x < 0 || x >= grid_w || y < 0 || y >= grid_h) {
|
|
||||||
return path; // Empty path for invalid position
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the destination
|
|
||||||
if (tcod_dijkstra->setPath(x, y)) {
|
|
||||||
// Walk the path and collect points
|
|
||||||
int px, py;
|
|
||||||
while (tcod_dijkstra->walk(&px, &py)) {
|
|
||||||
path.push_back(std::make_pair(px, py));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
// A* pathfinding implementation
|
|
||||||
std::vector<std::pair<int, int>> UIGrid::computeAStarPath(int x1, int y1, int x2, int y2, float diagonalCost)
|
|
||||||
{
|
|
||||||
std::vector<std::pair<int, int>> path;
|
|
||||||
|
|
||||||
// Validate inputs
|
|
||||||
if (!tcod_map || !tcod_path ||
|
|
||||||
x1 < 0 || x1 >= grid_w || y1 < 0 || y1 >= grid_h ||
|
|
||||||
x2 < 0 || x2 >= grid_w || y2 < 0 || y2 >= grid_h) {
|
|
||||||
return path; // Return empty path
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set diagonal cost (TCODPath doesn't take it as parameter to compute)
|
|
||||||
// Instead, diagonal cost is set during TCODPath construction
|
|
||||||
// For now, we'll use the default diagonal cost from the constructor
|
|
||||||
|
|
||||||
// Compute the path
|
|
||||||
bool success = tcod_path->compute(x1, y1, x2, y2);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
// Get the computed path
|
|
||||||
int pathSize = tcod_path->size();
|
|
||||||
path.reserve(pathSize);
|
|
||||||
|
|
||||||
// TCOD path includes the starting position, so we start from index 0
|
|
||||||
for (int i = 0; i < pathSize; i++) {
|
|
||||||
int px, py;
|
|
||||||
tcod_path->get(i, &px, &py);
|
|
||||||
path.push_back(std::make_pair(px, py));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 1 implementations
|
// Phase 1 implementations
|
||||||
sf::FloatRect UIGrid::get_bounds() const
|
sf::FloatRect UIGrid::get_bounds() const
|
||||||
|
|
@ -1453,128 +1356,9 @@ PyObject* UIGrid::py_is_in_fov(PyUIGridObject* self, PyObject* args, PyObject* k
|
||||||
return PyBool_FromLong(in_fov);
|
return PyBool_FromLong(in_fov);
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* UIGrid::py_find_path(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
// Old pathfinding Python methods removed - see UIGridPathfinding.cpp for new implementation
|
||||||
{
|
// Grid.find_path() now returns AStarPath objects
|
||||||
static const char* kwlist[] = {"start", "end", "diagonal_cost", NULL};
|
// Grid.get_dijkstra_map() returns DijkstraMap objects (cached by root)
|
||||||
PyObject* start_obj = NULL;
|
|
||||||
PyObject* end_obj = NULL;
|
|
||||||
float diagonal_cost = 1.41f;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|f", const_cast<char**>(kwlist),
|
|
||||||
&start_obj, &end_obj, &diagonal_cost)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int x1, y1, x2, y2;
|
|
||||||
if (!PyPosition_FromObjectInt(start_obj, &x1, &y1)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
if (!PyPosition_FromObjectInt(end_obj, &x2, &y2)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<int, int>> path = self->data->findPath(x1, y1, x2, y2, diagonal_cost);
|
|
||||||
|
|
||||||
PyObject* path_list = PyList_New(path.size());
|
|
||||||
if (!path_list) return NULL;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < path.size(); i++) {
|
|
||||||
PyObject* coord = Py_BuildValue("(ii)", path[i].first, path[i].second);
|
|
||||||
if (!coord) {
|
|
||||||
Py_DECREF(path_list);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
PyList_SET_ITEM(path_list, i, coord);
|
|
||||||
}
|
|
||||||
|
|
||||||
return path_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject* UIGrid::py_compute_dijkstra(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
|
||||||
{
|
|
||||||
static const char* kwlist[] = {"root", "diagonal_cost", NULL};
|
|
||||||
PyObject* root_obj = NULL;
|
|
||||||
float diagonal_cost = 1.41f;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|f", const_cast<char**>(kwlist),
|
|
||||||
&root_obj, &diagonal_cost)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int root_x, root_y;
|
|
||||||
if (!PyPosition_FromObjectInt(root_obj, &root_x, &root_y)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
self->data->computeDijkstra(root_x, root_y, diagonal_cost);
|
|
||||||
Py_RETURN_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject* UIGrid::py_get_dijkstra_distance(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
|
||||||
{
|
|
||||||
int x, y;
|
|
||||||
if (!PyPosition_ParseInt(args, kwds, &x, &y)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
float distance = self->data->getDijkstraDistance(x, y);
|
|
||||||
if (distance < 0) {
|
|
||||||
Py_RETURN_NONE; // Invalid position
|
|
||||||
}
|
|
||||||
|
|
||||||
return PyFloat_FromDouble(distance);
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject* UIGrid::py_get_dijkstra_path(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
|
||||||
{
|
|
||||||
int x, y;
|
|
||||||
if (!PyPosition_ParseInt(args, kwds, &x, &y)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<int, int>> path = self->data->getDijkstraPath(x, y);
|
|
||||||
|
|
||||||
PyObject* path_list = PyList_New(path.size());
|
|
||||||
for (size_t i = 0; i < path.size(); i++) {
|
|
||||||
PyObject* pos = Py_BuildValue("(ii)", path[i].first, path[i].second);
|
|
||||||
PyList_SetItem(path_list, i, pos); // Steals reference
|
|
||||||
}
|
|
||||||
|
|
||||||
return path_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject* UIGrid::py_compute_astar_path(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
|
||||||
{
|
|
||||||
static const char* kwlist[] = {"start", "end", "diagonal_cost", NULL};
|
|
||||||
PyObject* start_obj = NULL;
|
|
||||||
PyObject* end_obj = NULL;
|
|
||||||
float diagonal_cost = 1.41f;
|
|
||||||
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|f", const_cast<char**>(kwlist),
|
|
||||||
&start_obj, &end_obj, &diagonal_cost)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int x1, y1, x2, y2;
|
|
||||||
if (!PyPosition_FromObjectInt(start_obj, &x1, &y1)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
if (!PyPosition_FromObjectInt(end_obj, &x2, &y2)) {
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute A* path
|
|
||||||
std::vector<std::pair<int, int>> path = self->data->computeAStarPath(x1, y1, x2, y2, diagonal_cost);
|
|
||||||
|
|
||||||
// Convert to Python list
|
|
||||||
PyObject* path_list = PyList_New(path.size());
|
|
||||||
for (size_t i = 0; i < path.size(); i++) {
|
|
||||||
PyObject* pos = Py_BuildValue("(ii)", path[i].first, path[i].second);
|
|
||||||
PyList_SetItem(path_list, i, pos); // Steals reference
|
|
||||||
}
|
|
||||||
|
|
||||||
return path_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
// #147 - Layer system Python API
|
// #147 - Layer system Python API
|
||||||
PyObject* UIGrid::py_add_layer(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
PyObject* UIGrid::py_add_layer(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
|
@ -1925,51 +1709,32 @@ PyMethodDef UIGrid::methods[] = {
|
||||||
"Returns:\n"
|
"Returns:\n"
|
||||||
" True if the cell is visible, False otherwise\n\n"
|
" True if the cell is visible, False otherwise\n\n"
|
||||||
"Must call compute_fov() first to calculate visibility."},
|
"Must call compute_fov() first to calculate visibility."},
|
||||||
{"find_path", (PyCFunction)UIGrid::py_find_path, METH_VARARGS | METH_KEYWORDS,
|
{"find_path", (PyCFunction)UIGridPathfinding::Grid_find_path, METH_VARARGS | METH_KEYWORDS,
|
||||||
"find_path(start, end, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\n\n"
|
"find_path(start, end, diagonal_cost: float = 1.41) -> AStarPath | None\n\n"
|
||||||
"Find A* path between two points.\n\n"
|
|
||||||
"Args:\n"
|
|
||||||
" start: Starting position as (x, y) tuple, list, or Vector\n"
|
|
||||||
" end: Target position as (x, y) tuple, list, or Vector\n"
|
|
||||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
|
||||||
"Returns:\n"
|
|
||||||
" List of (x, y) tuples representing the path, empty list if no path exists\n\n"
|
|
||||||
"Uses A* algorithm with walkability from grid cells."},
|
|
||||||
{"compute_dijkstra", (PyCFunction)UIGrid::py_compute_dijkstra, METH_VARARGS | METH_KEYWORDS,
|
|
||||||
"compute_dijkstra(root, diagonal_cost: float = 1.41) -> None\n\n"
|
|
||||||
"Compute Dijkstra map from root position.\n\n"
|
|
||||||
"Args:\n"
|
|
||||||
" root: Root position as (x, y) tuple, list, or Vector\n"
|
|
||||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
|
||||||
"Precomputes distances from all reachable cells to the root.\n"
|
|
||||||
"Use get_dijkstra_distance() and get_dijkstra_path() to query results.\n"
|
|
||||||
"Useful for multiple entities pathfinding to the same target."},
|
|
||||||
{"get_dijkstra_distance", (PyCFunction)UIGrid::py_get_dijkstra_distance, METH_VARARGS | METH_KEYWORDS,
|
|
||||||
"get_dijkstra_distance(pos) -> Optional[float]\n\n"
|
|
||||||
"Get distance from Dijkstra root to position.\n\n"
|
|
||||||
"Args:\n"
|
|
||||||
" pos: Position as (x, y) tuple, list, or Vector\n\n"
|
|
||||||
"Returns:\n"
|
|
||||||
" Distance as float, or None if position is unreachable or invalid\n\n"
|
|
||||||
"Must call compute_dijkstra() first."},
|
|
||||||
{"get_dijkstra_path", (PyCFunction)UIGrid::py_get_dijkstra_path, METH_VARARGS | METH_KEYWORDS,
|
|
||||||
"get_dijkstra_path(pos) -> List[Tuple[int, int]]\n\n"
|
|
||||||
"Get path from position to Dijkstra root.\n\n"
|
|
||||||
"Args:\n"
|
|
||||||
" pos: Position as (x, y) tuple, list, or Vector\n\n"
|
|
||||||
"Returns:\n"
|
|
||||||
" List of (x, y) tuples representing path to root, empty if unreachable\n\n"
|
|
||||||
"Must call compute_dijkstra() first. Path includes start but not root position."},
|
|
||||||
{"compute_astar_path", (PyCFunction)UIGrid::py_compute_astar_path, METH_VARARGS | METH_KEYWORDS,
|
|
||||||
"compute_astar_path(start, end, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\n\n"
|
|
||||||
"Compute A* path between two points.\n\n"
|
"Compute A* path between two points.\n\n"
|
||||||
"Args:\n"
|
"Args:\n"
|
||||||
" start: Starting position as (x, y) tuple, list, or Vector\n"
|
" start: Starting position as Vector, Entity, or (x, y) tuple\n"
|
||||||
" end: Target position as (x, y) tuple, list, or Vector\n"
|
" end: Target position as Vector, Entity, or (x, y) tuple\n"
|
||||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
||||||
"Returns:\n"
|
"Returns:\n"
|
||||||
" List of (x, y) tuples representing the path, empty list if no path exists\n\n"
|
" AStarPath object if path exists, None otherwise.\n\n"
|
||||||
"Alternative A* implementation. Prefer find_path() for consistency."},
|
"The returned AStarPath can be iterated or walked step-by-step."},
|
||||||
|
{"get_dijkstra_map", (PyCFunction)UIGridPathfinding::Grid_get_dijkstra_map, METH_VARARGS | METH_KEYWORDS,
|
||||||
|
"get_dijkstra_map(root, diagonal_cost: float = 1.41) -> DijkstraMap\n\n"
|
||||||
|
"Get or create a Dijkstra distance map for a root position.\n\n"
|
||||||
|
"Args:\n"
|
||||||
|
" root: Root position as Vector, Entity, or (x, y) tuple\n"
|
||||||
|
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
||||||
|
"Returns:\n"
|
||||||
|
" DijkstraMap object for querying distances and paths.\n\n"
|
||||||
|
"Grid caches DijkstraMaps by root position. Multiple requests for the\n"
|
||||||
|
"same root return the same cached map. Call clear_dijkstra_maps() after\n"
|
||||||
|
"changing grid walkability to invalidate the cache."},
|
||||||
|
{"clear_dijkstra_maps", (PyCFunction)UIGridPathfinding::Grid_clear_dijkstra_maps, METH_NOARGS,
|
||||||
|
"clear_dijkstra_maps() -> None\n\n"
|
||||||
|
"Clear all cached Dijkstra maps.\n\n"
|
||||||
|
"Call this after modifying grid cell walkability to ensure pathfinding\n"
|
||||||
|
"uses updated walkability data."},
|
||||||
{"add_layer", (PyCFunction)UIGrid::py_add_layer, METH_VARARGS | METH_KEYWORDS,
|
{"add_layer", (PyCFunction)UIGrid::py_add_layer, METH_VARARGS | METH_KEYWORDS,
|
||||||
"add_layer(type: str, z_index: int = -1, texture: Texture = None) -> ColorLayer | TileLayer"},
|
"add_layer(type: str, z_index: int = -1, texture: Texture = None) -> ColorLayer | TileLayer"},
|
||||||
{"remove_layer", (PyCFunction)UIGrid::py_remove_layer, METH_VARARGS,
|
{"remove_layer", (PyCFunction)UIGrid::py_remove_layer, METH_VARARGS,
|
||||||
|
|
@ -2021,51 +1786,32 @@ PyMethodDef UIGrid_all_methods[] = {
|
||||||
"Returns:\n"
|
"Returns:\n"
|
||||||
" True if the cell is visible, False otherwise\n\n"
|
" True if the cell is visible, False otherwise\n\n"
|
||||||
"Must call compute_fov() first to calculate visibility."},
|
"Must call compute_fov() first to calculate visibility."},
|
||||||
{"find_path", (PyCFunction)UIGrid::py_find_path, METH_VARARGS | METH_KEYWORDS,
|
{"find_path", (PyCFunction)UIGridPathfinding::Grid_find_path, METH_VARARGS | METH_KEYWORDS,
|
||||||
"find_path(start, end, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\n\n"
|
"find_path(start, end, diagonal_cost: float = 1.41) -> AStarPath | None\n\n"
|
||||||
"Find A* path between two points.\n\n"
|
|
||||||
"Args:\n"
|
|
||||||
" start: Starting position as (x, y) tuple, list, or Vector\n"
|
|
||||||
" end: Target position as (x, y) tuple, list, or Vector\n"
|
|
||||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
|
||||||
"Returns:\n"
|
|
||||||
" List of (x, y) tuples representing the path, empty list if no path exists\n\n"
|
|
||||||
"Uses A* algorithm with walkability from grid cells."},
|
|
||||||
{"compute_dijkstra", (PyCFunction)UIGrid::py_compute_dijkstra, METH_VARARGS | METH_KEYWORDS,
|
|
||||||
"compute_dijkstra(root, diagonal_cost: float = 1.41) -> None\n\n"
|
|
||||||
"Compute Dijkstra map from root position.\n\n"
|
|
||||||
"Args:\n"
|
|
||||||
" root: Root position as (x, y) tuple, list, or Vector\n"
|
|
||||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
|
||||||
"Precomputes distances from all reachable cells to the root.\n"
|
|
||||||
"Use get_dijkstra_distance() and get_dijkstra_path() to query results.\n"
|
|
||||||
"Useful for multiple entities pathfinding to the same target."},
|
|
||||||
{"get_dijkstra_distance", (PyCFunction)UIGrid::py_get_dijkstra_distance, METH_VARARGS | METH_KEYWORDS,
|
|
||||||
"get_dijkstra_distance(pos) -> Optional[float]\n\n"
|
|
||||||
"Get distance from Dijkstra root to position.\n\n"
|
|
||||||
"Args:\n"
|
|
||||||
" pos: Position as (x, y) tuple, list, or Vector\n\n"
|
|
||||||
"Returns:\n"
|
|
||||||
" Distance as float, or None if position is unreachable or invalid\n\n"
|
|
||||||
"Must call compute_dijkstra() first."},
|
|
||||||
{"get_dijkstra_path", (PyCFunction)UIGrid::py_get_dijkstra_path, METH_VARARGS | METH_KEYWORDS,
|
|
||||||
"get_dijkstra_path(pos) -> List[Tuple[int, int]]\n\n"
|
|
||||||
"Get path from position to Dijkstra root.\n\n"
|
|
||||||
"Args:\n"
|
|
||||||
" pos: Position as (x, y) tuple, list, or Vector\n\n"
|
|
||||||
"Returns:\n"
|
|
||||||
" List of (x, y) tuples representing path to root, empty if unreachable\n\n"
|
|
||||||
"Must call compute_dijkstra() first. Path includes start but not root position."},
|
|
||||||
{"compute_astar_path", (PyCFunction)UIGrid::py_compute_astar_path, METH_VARARGS | METH_KEYWORDS,
|
|
||||||
"compute_astar_path(start, end, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\n\n"
|
|
||||||
"Compute A* path between two points.\n\n"
|
"Compute A* path between two points.\n\n"
|
||||||
"Args:\n"
|
"Args:\n"
|
||||||
" start: Starting position as (x, y) tuple, list, or Vector\n"
|
" start: Starting position as Vector, Entity, or (x, y) tuple\n"
|
||||||
" end: Target position as (x, y) tuple, list, or Vector\n"
|
" end: Target position as Vector, Entity, or (x, y) tuple\n"
|
||||||
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
||||||
"Returns:\n"
|
"Returns:\n"
|
||||||
" List of (x, y) tuples representing the path, empty list if no path exists\n\n"
|
" AStarPath object if path exists, None otherwise.\n\n"
|
||||||
"Alternative A* implementation. Prefer find_path() for consistency."},
|
"The returned AStarPath can be iterated or walked step-by-step."},
|
||||||
|
{"get_dijkstra_map", (PyCFunction)UIGridPathfinding::Grid_get_dijkstra_map, METH_VARARGS | METH_KEYWORDS,
|
||||||
|
"get_dijkstra_map(root, diagonal_cost: float = 1.41) -> DijkstraMap\n\n"
|
||||||
|
"Get or create a Dijkstra distance map for a root position.\n\n"
|
||||||
|
"Args:\n"
|
||||||
|
" root: Root position as Vector, Entity, or (x, y) tuple\n"
|
||||||
|
" diagonal_cost: Cost of diagonal movement (default: 1.41)\n\n"
|
||||||
|
"Returns:\n"
|
||||||
|
" DijkstraMap object for querying distances and paths.\n\n"
|
||||||
|
"Grid caches DijkstraMaps by root position. Multiple requests for the\n"
|
||||||
|
"same root return the same cached map. Call clear_dijkstra_maps() after\n"
|
||||||
|
"changing grid walkability to invalidate the cache."},
|
||||||
|
{"clear_dijkstra_maps", (PyCFunction)UIGridPathfinding::Grid_clear_dijkstra_maps, METH_NOARGS,
|
||||||
|
"clear_dijkstra_maps() -> None\n\n"
|
||||||
|
"Clear all cached Dijkstra maps.\n\n"
|
||||||
|
"Call this after modifying grid cell walkability to ensure pathfinding\n"
|
||||||
|
"uses updated walkability data."},
|
||||||
{"add_layer", (PyCFunction)UIGrid::py_add_layer, METH_VARARGS | METH_KEYWORDS,
|
{"add_layer", (PyCFunction)UIGrid::py_add_layer, METH_VARARGS | METH_KEYWORDS,
|
||||||
"add_layer(type: str, z_index: int = -1, texture: Texture = None) -> ColorLayer | TileLayer\n\n"
|
"add_layer(type: str, z_index: int = -1, texture: Texture = None) -> ColorLayer | TileLayer\n\n"
|
||||||
"Add a new layer to the grid.\n\n"
|
"Add a new layer to the grid.\n\n"
|
||||||
|
|
|
||||||
36
src/UIGrid.h
36
src/UIGrid.h
|
|
@ -8,6 +8,8 @@
|
||||||
#include <libtcod.h>
|
#include <libtcod.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
#include "PyCallable.h"
|
#include "PyCallable.h"
|
||||||
#include "PyTexture.h"
|
#include "PyTexture.h"
|
||||||
|
|
@ -25,6 +27,9 @@
|
||||||
#include "SpatialHash.h"
|
#include "SpatialHash.h"
|
||||||
#include "UIEntityCollection.h" // EntityCollection types (extracted from UIGrid)
|
#include "UIEntityCollection.h" // EntityCollection types (extracted from UIGrid)
|
||||||
|
|
||||||
|
// Forward declaration for pathfinding
|
||||||
|
class DijkstraMap;
|
||||||
|
|
||||||
class UIGrid: public UIDrawable
|
class UIGrid: public UIDrawable
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
|
|
@ -33,10 +38,13 @@ private:
|
||||||
static constexpr int DEFAULT_CELL_WIDTH = 16;
|
static constexpr int DEFAULT_CELL_WIDTH = 16;
|
||||||
static constexpr int DEFAULT_CELL_HEIGHT = 16;
|
static constexpr int DEFAULT_CELL_HEIGHT = 16;
|
||||||
TCODMap* tcod_map; // TCOD map for FOV and pathfinding
|
TCODMap* tcod_map; // TCOD map for FOV and pathfinding
|
||||||
TCODDijkstra* tcod_dijkstra; // Dijkstra pathfinding
|
|
||||||
TCODPath* tcod_path; // A* pathfinding
|
|
||||||
mutable std::mutex fov_mutex; // Mutex for thread-safe FOV operations
|
mutable std::mutex fov_mutex; // Mutex for thread-safe FOV operations
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Dijkstra map cache - keyed by root position
|
||||||
|
// Public so UIGridPathfinding can access it
|
||||||
|
std::map<std::pair<int,int>, std::shared_ptr<DijkstraMap>> dijkstra_maps;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UIGrid();
|
UIGrid();
|
||||||
//UIGrid(int, int, IndexTexture*, float, float, float, float);
|
//UIGrid(int, int, IndexTexture*, float, float, float, float);
|
||||||
|
|
@ -54,15 +62,12 @@ public:
|
||||||
void syncTCODMapCell(int x, int y); // Sync a single cell to TCOD map
|
void syncTCODMapCell(int x, int y); // Sync a single cell to TCOD map
|
||||||
void computeFOV(int x, int y, int radius, bool light_walls = true, TCOD_fov_algorithm_t algo = FOV_BASIC);
|
void computeFOV(int x, int y, int radius, bool light_walls = true, TCOD_fov_algorithm_t algo = FOV_BASIC);
|
||||||
bool isInFOV(int x, int y) const;
|
bool isInFOV(int x, int y) const;
|
||||||
|
TCODMap* getTCODMap() const { return tcod_map; } // Access for pathfinding
|
||||||
|
|
||||||
// Pathfinding methods
|
// Pathfinding - new API creates AStarPath/DijkstraMap objects
|
||||||
std::vector<std::pair<int, int>> findPath(int x1, int y1, int x2, int y2, float diagonalCost = 1.41f);
|
// See UIGridPathfinding.h for the new pathfinding API
|
||||||
void computeDijkstra(int rootX, int rootY, float diagonalCost = 1.41f);
|
// Grid.find_path() now returns AStarPath objects
|
||||||
float getDijkstraDistance(int x, int y) const;
|
// Grid.get_dijkstra_map() returns DijkstraMap objects (cached by root position)
|
||||||
std::vector<std::pair<int, int>> getDijkstraPath(int x, int y) const;
|
|
||||||
|
|
||||||
// A* pathfinding methods
|
|
||||||
std::vector<std::pair<int, int>> computeAStarPath(int x1, int y1, int x2, int y2, float diagonalCost = 1.41f);
|
|
||||||
|
|
||||||
// Phase 1 virtual method implementations
|
// Phase 1 virtual method implementations
|
||||||
sf::FloatRect get_bounds() const override;
|
sf::FloatRect get_bounds() const override;
|
||||||
|
|
@ -167,11 +172,10 @@ public:
|
||||||
static PyObject* py_at(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
static PyObject* py_at(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||||
static PyObject* py_compute_fov(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
static PyObject* py_compute_fov(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||||
static PyObject* py_is_in_fov(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
static PyObject* py_is_in_fov(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||||
static PyObject* py_find_path(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
// Pathfinding methods moved to UIGridPathfinding.cpp
|
||||||
static PyObject* py_compute_dijkstra(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
// py_find_path -> UIGridPathfinding::Grid_find_path (returns AStarPath)
|
||||||
static PyObject* py_get_dijkstra_distance(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
// py_get_dijkstra_map -> UIGridPathfinding::Grid_get_dijkstra_map (returns DijkstraMap)
|
||||||
static PyObject* py_get_dijkstra_path(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
// py_clear_dijkstra_maps -> UIGridPathfinding::Grid_clear_dijkstra_maps
|
||||||
static PyObject* py_compute_astar_path(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
|
||||||
static PyObject* py_entities_in_radius(PyUIGridObject* self, PyObject* args, PyObject* kwds); // #115
|
static PyObject* py_entities_in_radius(PyUIGridObject* self, PyObject* args, PyObject* kwds); // #115
|
||||||
static PyObject* py_center_camera(PyUIGridObject* self, PyObject* args); // #169
|
static PyObject* py_center_camera(PyUIGridObject* self, PyObject* args); // #169
|
||||||
|
|
||||||
|
|
|
||||||
688
src/UIGridPathfinding.cpp
Normal file
688
src/UIGridPathfinding.cpp
Normal file
|
|
@ -0,0 +1,688 @@
|
||||||
|
#include "UIGridPathfinding.h"
|
||||||
|
#include "UIGrid.h"
|
||||||
|
#include "UIEntity.h"
|
||||||
|
#include "PyVector.h"
|
||||||
|
#include "McRFPy_API.h"
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// DijkstraMap Implementation
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
DijkstraMap::DijkstraMap(TCODMap* map, int root_x, int root_y, float diag_cost)
|
||||||
|
: tcod_map(map)
|
||||||
|
, root(root_x, root_y)
|
||||||
|
, diagonal_cost(diag_cost)
|
||||||
|
{
|
||||||
|
tcod_dijkstra = new TCODDijkstra(tcod_map, diagonal_cost);
|
||||||
|
tcod_dijkstra->compute(root_x, root_y); // Compute immediately at creation
|
||||||
|
}
|
||||||
|
|
||||||
|
DijkstraMap::~DijkstraMap() {
|
||||||
|
if (tcod_dijkstra) {
|
||||||
|
delete tcod_dijkstra;
|
||||||
|
tcod_dijkstra = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float DijkstraMap::getDistance(int x, int y) const {
|
||||||
|
if (!tcod_dijkstra) return -1.0f;
|
||||||
|
return tcod_dijkstra->getDistance(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<sf::Vector2i> DijkstraMap::getPathFrom(int x, int y) const {
|
||||||
|
std::vector<sf::Vector2i> path;
|
||||||
|
if (!tcod_dijkstra) return path;
|
||||||
|
|
||||||
|
if (tcod_dijkstra->setPath(x, y)) {
|
||||||
|
int px, py;
|
||||||
|
while (tcod_dijkstra->walk(&px, &py)) {
|
||||||
|
path.push_back(sf::Vector2i(px, py));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
sf::Vector2i DijkstraMap::stepFrom(int x, int y, bool* valid) const {
|
||||||
|
if (!tcod_dijkstra) {
|
||||||
|
if (valid) *valid = false;
|
||||||
|
return sf::Vector2i(-1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tcod_dijkstra->setPath(x, y)) {
|
||||||
|
if (valid) *valid = false;
|
||||||
|
return sf::Vector2i(-1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int px, py;
|
||||||
|
if (tcod_dijkstra->walk(&px, &py)) {
|
||||||
|
if (valid) *valid = true;
|
||||||
|
return sf::Vector2i(px, py);
|
||||||
|
}
|
||||||
|
|
||||||
|
// At root or no path
|
||||||
|
if (valid) *valid = false;
|
||||||
|
return sf::Vector2i(-1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// Helper Functions
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
bool UIGridPathfinding::ExtractPosition(PyObject* obj, int* x, int* y,
|
||||||
|
UIGrid* expected_grid,
|
||||||
|
const char* arg_name) {
|
||||||
|
// Get types from module to avoid static type instance issues
|
||||||
|
PyObject* entity_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Entity");
|
||||||
|
PyObject* vector_type = PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
|
||||||
|
|
||||||
|
// Check if it's an Entity
|
||||||
|
if (entity_type && PyObject_IsInstance(obj, entity_type)) {
|
||||||
|
Py_DECREF(entity_type);
|
||||||
|
Py_XDECREF(vector_type);
|
||||||
|
auto* entity = (PyUIEntityObject*)obj;
|
||||||
|
if (!entity->data) {
|
||||||
|
PyErr_Format(PyExc_RuntimeError,
|
||||||
|
"%s: Entity has no data", arg_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!entity->data->grid) {
|
||||||
|
PyErr_Format(PyExc_RuntimeError,
|
||||||
|
"%s: Entity is not attached to any grid", arg_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (expected_grid && entity->data->grid.get() != expected_grid) {
|
||||||
|
PyErr_Format(PyExc_RuntimeError,
|
||||||
|
"%s: Entity belongs to a different grid", arg_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
*x = static_cast<int>(entity->data->position.x);
|
||||||
|
*y = static_cast<int>(entity->data->position.y);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Py_XDECREF(entity_type);
|
||||||
|
|
||||||
|
// Check if it's a Vector
|
||||||
|
if (vector_type && PyObject_IsInstance(obj, vector_type)) {
|
||||||
|
Py_DECREF(vector_type);
|
||||||
|
auto* vec = (PyVectorObject*)obj;
|
||||||
|
*x = static_cast<int>(vec->data.x);
|
||||||
|
*y = static_cast<int>(vec->data.y);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
Py_XDECREF(vector_type);
|
||||||
|
|
||||||
|
// Try tuple/list
|
||||||
|
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_DECREF(x_long);
|
||||||
|
Py_DECREF(y_long);
|
||||||
|
}
|
||||||
|
Py_XDECREF(x_long);
|
||||||
|
Py_XDECREF(y_long);
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_XDECREF(x_obj);
|
||||||
|
Py_XDECREF(y_obj);
|
||||||
|
|
||||||
|
if (ok) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyErr_Format(PyExc_TypeError,
|
||||||
|
"%s: expected Vector, Entity, or (x, y) tuple", arg_name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// AStarPath Python Methods
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::AStarPath_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
|
||||||
|
PyAStarPathObject* self = (PyAStarPathObject*)type->tp_alloc(type, 0);
|
||||||
|
if (self) {
|
||||||
|
new (&self->path) std::vector<sf::Vector2i>(); // Placement new
|
||||||
|
self->current_index = 0;
|
||||||
|
self->origin = sf::Vector2i(0, 0);
|
||||||
|
self->destination = sf::Vector2i(0, 0);
|
||||||
|
}
|
||||||
|
return (PyObject*)self;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UIGridPathfinding::AStarPath_init(PyAStarPathObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
// AStarPath should not be created directly from Python
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"AStarPath cannot be instantiated directly. Use Grid.find_path() instead.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIGridPathfinding::AStarPath_dealloc(PyAStarPathObject* self) {
|
||||||
|
self->path.~vector(); // Explicitly destroy
|
||||||
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::AStarPath_repr(PyAStarPathObject* self) {
|
||||||
|
size_t remaining = self->path.size() - self->current_index;
|
||||||
|
return PyUnicode_FromFormat("<AStarPath from (%d,%d) to (%d,%d), %zu steps remaining>",
|
||||||
|
self->origin.x, self->origin.y,
|
||||||
|
self->destination.x, self->destination.y,
|
||||||
|
remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::AStarPath_walk(PyAStarPathObject* self, PyObject* args) {
|
||||||
|
if (self->current_index >= self->path.size()) {
|
||||||
|
PyErr_SetString(PyExc_IndexError, "Path exhausted - no more steps");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sf::Vector2i pos = self->path[self->current_index++];
|
||||||
|
return PyVector(sf::Vector2f(static_cast<float>(pos.x), static_cast<float>(pos.y))).pyObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::AStarPath_peek(PyAStarPathObject* self, PyObject* args) {
|
||||||
|
if (self->current_index >= self->path.size()) {
|
||||||
|
PyErr_SetString(PyExc_IndexError, "Path exhausted - no more steps");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sf::Vector2i pos = self->path[self->current_index];
|
||||||
|
return PyVector(sf::Vector2f(static_cast<float>(pos.x), static_cast<float>(pos.y))).pyObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::AStarPath_get_origin(PyAStarPathObject* self, void* closure) {
|
||||||
|
return PyVector(sf::Vector2f(static_cast<float>(self->origin.x),
|
||||||
|
static_cast<float>(self->origin.y))).pyObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::AStarPath_get_destination(PyAStarPathObject* self, void* closure) {
|
||||||
|
return PyVector(sf::Vector2f(static_cast<float>(self->destination.x),
|
||||||
|
static_cast<float>(self->destination.y))).pyObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::AStarPath_get_remaining(PyAStarPathObject* self, void* closure) {
|
||||||
|
size_t remaining = self->path.size() - self->current_index;
|
||||||
|
return PyLong_FromSize_t(remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
Py_ssize_t UIGridPathfinding::AStarPath_len(PyAStarPathObject* self) {
|
||||||
|
return static_cast<Py_ssize_t>(self->path.size() - self->current_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
int UIGridPathfinding::AStarPath_bool(PyObject* obj) {
|
||||||
|
PyAStarPathObject* self = (PyAStarPathObject*)obj;
|
||||||
|
return self->current_index < self->path.size() ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::AStarPath_iter(PyAStarPathObject* self) {
|
||||||
|
// Create iterator object
|
||||||
|
mcrfpydef::PyAStarPathIterObject* iter = PyObject_New(
|
||||||
|
mcrfpydef::PyAStarPathIterObject, &mcrfpydef::PyAStarPathIterType);
|
||||||
|
if (!iter) return NULL;
|
||||||
|
|
||||||
|
Py_INCREF(self);
|
||||||
|
iter->path = self;
|
||||||
|
iter->iter_index = self->current_index;
|
||||||
|
|
||||||
|
return (PyObject*)iter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterator implementation
|
||||||
|
static void AStarPathIter_dealloc(mcrfpydef::PyAStarPathIterObject* self) {
|
||||||
|
Py_XDECREF(self->path);
|
||||||
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject* AStarPathIter_next(mcrfpydef::PyAStarPathIterObject* self) {
|
||||||
|
if (!self->path || self->iter_index >= self->path->path.size()) {
|
||||||
|
return NULL; // StopIteration
|
||||||
|
}
|
||||||
|
|
||||||
|
sf::Vector2i pos = self->path->path[self->iter_index++];
|
||||||
|
// Note: Iterating is consuming for this iterator
|
||||||
|
self->path->current_index = self->iter_index;
|
||||||
|
|
||||||
|
return PyVector(sf::Vector2f(static_cast<float>(pos.x), static_cast<float>(pos.y))).pyObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
static PyObject* AStarPathIter_iter(mcrfpydef::PyAStarPathIterObject* self) {
|
||||||
|
Py_INCREF(self);
|
||||||
|
return (PyObject*)self;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// DijkstraMap Python Methods
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::DijkstraMap_new(PyTypeObject* type, PyObject* args, PyObject* kwds) {
|
||||||
|
PyDijkstraMapObject* self = (PyDijkstraMapObject*)type->tp_alloc(type, 0);
|
||||||
|
if (self) {
|
||||||
|
new (&self->data) std::shared_ptr<DijkstraMap>();
|
||||||
|
}
|
||||||
|
return (PyObject*)self;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UIGridPathfinding::DijkstraMap_init(PyDijkstraMapObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
PyErr_SetString(PyExc_TypeError,
|
||||||
|
"DijkstraMap cannot be instantiated directly. Use Grid.get_dijkstra_map() instead.");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UIGridPathfinding::DijkstraMap_dealloc(PyDijkstraMapObject* self) {
|
||||||
|
self->data.~shared_ptr();
|
||||||
|
Py_TYPE(self)->tp_free((PyObject*)self);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::DijkstraMap_repr(PyDijkstraMapObject* self) {
|
||||||
|
if (!self->data) {
|
||||||
|
return PyUnicode_FromString("<DijkstraMap (invalid)>");
|
||||||
|
}
|
||||||
|
sf::Vector2i root = self->data->getRoot();
|
||||||
|
return PyUnicode_FromFormat("<DijkstraMap root=(%d,%d)>", root.x, root.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::DijkstraMap_distance(PyDijkstraMapObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
static const char* kwlist[] = {"pos", NULL};
|
||||||
|
PyObject* pos_obj = NULL;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", const_cast<char**>(kwlist), &pos_obj)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self->data) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "DijkstraMap is invalid");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x, y;
|
||||||
|
if (!ExtractPosition(pos_obj, &x, &y, nullptr, "pos")) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
float dist = self->data->getDistance(x, y);
|
||||||
|
if (dist < 0) {
|
||||||
|
Py_RETURN_NONE; // Unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
return PyFloat_FromDouble(dist);
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::DijkstraMap_path_from(PyDijkstraMapObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
static const char* kwlist[] = {"pos", NULL};
|
||||||
|
PyObject* pos_obj = NULL;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", const_cast<char**>(kwlist), &pos_obj)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self->data) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "DijkstraMap is invalid");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x, y;
|
||||||
|
if (!ExtractPosition(pos_obj, &x, &y, nullptr, "pos")) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<sf::Vector2i> path = self->data->getPathFrom(x, y);
|
||||||
|
|
||||||
|
// Create an AStarPath object to return
|
||||||
|
PyAStarPathObject* result = (PyAStarPathObject*)mcrfpydef::PyAStarPathType.tp_alloc(
|
||||||
|
&mcrfpydef::PyAStarPathType, 0);
|
||||||
|
if (!result) return NULL;
|
||||||
|
|
||||||
|
new (&result->path) std::vector<sf::Vector2i>(std::move(path));
|
||||||
|
result->current_index = 0;
|
||||||
|
result->origin = sf::Vector2i(x, y);
|
||||||
|
result->destination = self->data->getRoot();
|
||||||
|
|
||||||
|
return (PyObject*)result;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::DijkstraMap_step_from(PyDijkstraMapObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
static const char* kwlist[] = {"pos", NULL};
|
||||||
|
PyObject* pos_obj = NULL;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O", const_cast<char**>(kwlist), &pos_obj)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self->data) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "DijkstraMap is invalid");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x, y;
|
||||||
|
if (!ExtractPosition(pos_obj, &x, &y, nullptr, "pos")) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool valid = false;
|
||||||
|
sf::Vector2i step = self->data->stepFrom(x, y, &valid);
|
||||||
|
|
||||||
|
if (!valid) {
|
||||||
|
Py_RETURN_NONE; // At root or unreachable
|
||||||
|
}
|
||||||
|
|
||||||
|
return PyVector(sf::Vector2f(static_cast<float>(step.x), static_cast<float>(step.y))).pyObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::DijkstraMap_get_root(PyDijkstraMapObject* self, void* closure) {
|
||||||
|
if (!self->data) {
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
sf::Vector2i root = self->data->getRoot();
|
||||||
|
return PyVector(sf::Vector2f(static_cast<float>(root.x), static_cast<float>(root.y))).pyObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// Grid Factory Methods
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::Grid_find_path(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
static const char* kwlist[] = {"start", "end", "diagonal_cost", NULL};
|
||||||
|
PyObject* start_obj = NULL;
|
||||||
|
PyObject* end_obj = NULL;
|
||||||
|
float diagonal_cost = 1.41f;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|f", const_cast<char**>(kwlist),
|
||||||
|
&start_obj, &end_obj, &diagonal_cost)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self->data) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Grid is invalid");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int x1, y1, x2, y2;
|
||||||
|
if (!ExtractPosition(start_obj, &x1, &y1, self->data.get(), "start")) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (!ExtractPosition(end_obj, &x2, &y2, self->data.get(), "end")) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bounds check
|
||||||
|
if (x1 < 0 || x1 >= self->data->grid_w || y1 < 0 || y1 >= self->data->grid_h ||
|
||||||
|
x2 < 0 || x2 >= self->data->grid_w || y2 < 0 || y2 >= self->data->grid_h) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "Position out of grid bounds");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute path using temporary TCODPath
|
||||||
|
TCODPath tcod_path(self->data->getTCODMap(), diagonal_cost);
|
||||||
|
if (!tcod_path.compute(x1, y1, x2, y2)) {
|
||||||
|
Py_RETURN_NONE; // No path exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create AStarPath result object
|
||||||
|
PyAStarPathObject* result = (PyAStarPathObject*)mcrfpydef::PyAStarPathType.tp_alloc(
|
||||||
|
&mcrfpydef::PyAStarPathType, 0);
|
||||||
|
if (!result) return NULL;
|
||||||
|
|
||||||
|
// Initialize
|
||||||
|
new (&result->path) std::vector<sf::Vector2i>();
|
||||||
|
result->current_index = 0;
|
||||||
|
result->origin = sf::Vector2i(x1, y1);
|
||||||
|
result->destination = sf::Vector2i(x2, y2);
|
||||||
|
|
||||||
|
// Copy path data
|
||||||
|
result->path.reserve(tcod_path.size());
|
||||||
|
for (int i = 0; i < tcod_path.size(); i++) {
|
||||||
|
int px, py;
|
||||||
|
tcod_path.get(i, &px, &py);
|
||||||
|
result->path.push_back(sf::Vector2i(px, py));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (PyObject*)result;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::Grid_get_dijkstra_map(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
|
static const char* kwlist[] = {"root", "diagonal_cost", NULL};
|
||||||
|
PyObject* root_obj = NULL;
|
||||||
|
float diagonal_cost = 1.41f;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|f", const_cast<char**>(kwlist),
|
||||||
|
&root_obj, &diagonal_cost)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!self->data) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Grid is invalid");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int root_x, root_y;
|
||||||
|
if (!ExtractPosition(root_obj, &root_x, &root_y, self->data.get(), "root")) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bounds check
|
||||||
|
if (root_x < 0 || root_x >= self->data->grid_w || root_y < 0 || root_y >= self->data->grid_h) {
|
||||||
|
PyErr_SetString(PyExc_ValueError, "Root position out of grid bounds");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto key = std::make_pair(root_x, root_y);
|
||||||
|
|
||||||
|
// Check cache
|
||||||
|
auto it = self->data->dijkstra_maps.find(key);
|
||||||
|
if (it != self->data->dijkstra_maps.end()) {
|
||||||
|
// Check diagonal cost matches (or we could ignore this)
|
||||||
|
if (std::abs(it->second->getDiagonalCost() - diagonal_cost) < 0.001f) {
|
||||||
|
// Return existing
|
||||||
|
PyDijkstraMapObject* result = (PyDijkstraMapObject*)mcrfpydef::PyDijkstraMapType.tp_alloc(
|
||||||
|
&mcrfpydef::PyDijkstraMapType, 0);
|
||||||
|
if (!result) return NULL;
|
||||||
|
new (&result->data) std::shared_ptr<DijkstraMap>(it->second);
|
||||||
|
return (PyObject*)result;
|
||||||
|
}
|
||||||
|
// Different diagonal cost - remove old one
|
||||||
|
self->data->dijkstra_maps.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new DijkstraMap
|
||||||
|
auto dijkstra = std::make_shared<DijkstraMap>(
|
||||||
|
self->data->getTCODMap(), root_x, root_y, diagonal_cost);
|
||||||
|
|
||||||
|
// Cache it
|
||||||
|
self->data->dijkstra_maps[key] = dijkstra;
|
||||||
|
|
||||||
|
// Return Python wrapper
|
||||||
|
PyDijkstraMapObject* result = (PyDijkstraMapObject*)mcrfpydef::PyDijkstraMapType.tp_alloc(
|
||||||
|
&mcrfpydef::PyDijkstraMapType, 0);
|
||||||
|
if (!result) return NULL;
|
||||||
|
|
||||||
|
new (&result->data) std::shared_ptr<DijkstraMap>(dijkstra);
|
||||||
|
return (PyObject*)result;
|
||||||
|
}
|
||||||
|
|
||||||
|
PyObject* UIGridPathfinding::Grid_clear_dijkstra_maps(PyUIGridObject* self, PyObject* args) {
|
||||||
|
if (!self->data) {
|
||||||
|
PyErr_SetString(PyExc_RuntimeError, "Grid is invalid");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
self->data->dijkstra_maps.clear();
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// Python Type Definitions
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
namespace mcrfpydef {
|
||||||
|
|
||||||
|
// AStarPath methods
|
||||||
|
PyMethodDef PyAStarPath_methods[] = {
|
||||||
|
{"walk", (PyCFunction)UIGridPathfinding::AStarPath_walk, METH_NOARGS,
|
||||||
|
"walk() -> Vector\n\n"
|
||||||
|
"Get and consume next step in the path.\n\n"
|
||||||
|
"Returns:\n"
|
||||||
|
" Next position as Vector.\n\n"
|
||||||
|
"Raises:\n"
|
||||||
|
" IndexError: If path is exhausted."},
|
||||||
|
|
||||||
|
{"peek", (PyCFunction)UIGridPathfinding::AStarPath_peek, METH_NOARGS,
|
||||||
|
"peek() -> Vector\n\n"
|
||||||
|
"See next step without consuming it.\n\n"
|
||||||
|
"Returns:\n"
|
||||||
|
" Next position as Vector.\n\n"
|
||||||
|
"Raises:\n"
|
||||||
|
" IndexError: If path is exhausted."},
|
||||||
|
|
||||||
|
{NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
// AStarPath getsetters
|
||||||
|
PyGetSetDef PyAStarPath_getsetters[] = {
|
||||||
|
{"origin", (getter)UIGridPathfinding::AStarPath_get_origin, NULL,
|
||||||
|
"Starting position of the path (Vector, read-only).", NULL},
|
||||||
|
|
||||||
|
{"destination", (getter)UIGridPathfinding::AStarPath_get_destination, NULL,
|
||||||
|
"Ending position of the path (Vector, read-only).", NULL},
|
||||||
|
|
||||||
|
{"remaining", (getter)UIGridPathfinding::AStarPath_get_remaining, NULL,
|
||||||
|
"Number of steps remaining in the path (int, read-only).", NULL},
|
||||||
|
|
||||||
|
{NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
// AStarPath number methods (for bool)
|
||||||
|
PyNumberMethods PyAStarPath_as_number = {
|
||||||
|
.nb_bool = UIGridPathfinding::AStarPath_bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
// AStarPath sequence methods (for len)
|
||||||
|
PySequenceMethods PyAStarPath_as_sequence = {
|
||||||
|
.sq_length = (lenfunc)UIGridPathfinding::AStarPath_len,
|
||||||
|
};
|
||||||
|
|
||||||
|
// AStarPath type
|
||||||
|
PyTypeObject PyAStarPathType = {
|
||||||
|
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||||
|
.tp_name = "mcrfpy.AStarPath",
|
||||||
|
.tp_basicsize = sizeof(PyAStarPathObject),
|
||||||
|
.tp_itemsize = 0,
|
||||||
|
.tp_dealloc = (destructor)UIGridPathfinding::AStarPath_dealloc,
|
||||||
|
.tp_repr = (reprfunc)UIGridPathfinding::AStarPath_repr,
|
||||||
|
.tp_as_number = &PyAStarPath_as_number,
|
||||||
|
.tp_as_sequence = &PyAStarPath_as_sequence,
|
||||||
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
|
.tp_doc = PyDoc_STR(
|
||||||
|
"A computed A* path result, consumed step by step.\n\n"
|
||||||
|
"Created by Grid.find_path(). Cannot be instantiated directly.\n\n"
|
||||||
|
"Use walk() to get and consume each step, or iterate directly.\n"
|
||||||
|
"Use peek() to see the next step without consuming it.\n"
|
||||||
|
"Use bool(path) or len(path) to check if steps remain.\n\n"
|
||||||
|
"Properties:\n"
|
||||||
|
" origin (Vector): Starting position (read-only)\n"
|
||||||
|
" destination (Vector): Ending position (read-only)\n"
|
||||||
|
" remaining (int): Steps remaining (read-only)\n\n"
|
||||||
|
"Example:\n"
|
||||||
|
" path = grid.find_path(start, end)\n"
|
||||||
|
" if path:\n"
|
||||||
|
" while path:\n"
|
||||||
|
" next_pos = path.walk()\n"
|
||||||
|
" entity.pos = next_pos"),
|
||||||
|
.tp_iter = (getiterfunc)UIGridPathfinding::AStarPath_iter,
|
||||||
|
.tp_methods = PyAStarPath_methods,
|
||||||
|
.tp_getset = PyAStarPath_getsetters,
|
||||||
|
.tp_init = (initproc)UIGridPathfinding::AStarPath_init,
|
||||||
|
.tp_new = UIGridPathfinding::AStarPath_new,
|
||||||
|
};
|
||||||
|
|
||||||
|
// AStarPath iterator type
|
||||||
|
PyTypeObject PyAStarPathIterType = {
|
||||||
|
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||||
|
.tp_name = "mcrfpy.AStarPathIterator",
|
||||||
|
.tp_basicsize = sizeof(PyAStarPathIterObject),
|
||||||
|
.tp_itemsize = 0,
|
||||||
|
.tp_dealloc = (destructor)AStarPathIter_dealloc,
|
||||||
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
|
.tp_iter = (getiterfunc)AStarPathIter_iter,
|
||||||
|
.tp_iternext = (iternextfunc)AStarPathIter_next,
|
||||||
|
};
|
||||||
|
|
||||||
|
// DijkstraMap methods
|
||||||
|
PyMethodDef PyDijkstraMap_methods[] = {
|
||||||
|
{"distance", (PyCFunction)UIGridPathfinding::DijkstraMap_distance, METH_VARARGS | METH_KEYWORDS,
|
||||||
|
"distance(pos) -> float | None\n\n"
|
||||||
|
"Get distance from position to root.\n\n"
|
||||||
|
"Args:\n"
|
||||||
|
" pos: Position as Vector, Entity, or (x, y) tuple.\n\n"
|
||||||
|
"Returns:\n"
|
||||||
|
" Float distance, or None if position is unreachable."},
|
||||||
|
|
||||||
|
{"path_from", (PyCFunction)UIGridPathfinding::DijkstraMap_path_from, METH_VARARGS | METH_KEYWORDS,
|
||||||
|
"path_from(pos) -> AStarPath\n\n"
|
||||||
|
"Get full path from position to root.\n\n"
|
||||||
|
"Args:\n"
|
||||||
|
" pos: Starting position as Vector, Entity, or (x, y) tuple.\n\n"
|
||||||
|
"Returns:\n"
|
||||||
|
" AStarPath from pos toward root."},
|
||||||
|
|
||||||
|
{"step_from", (PyCFunction)UIGridPathfinding::DijkstraMap_step_from, METH_VARARGS | METH_KEYWORDS,
|
||||||
|
"step_from(pos) -> Vector | None\n\n"
|
||||||
|
"Get single step from position toward root.\n\n"
|
||||||
|
"Args:\n"
|
||||||
|
" pos: Current position as Vector, Entity, or (x, y) tuple.\n\n"
|
||||||
|
"Returns:\n"
|
||||||
|
" Next position as Vector, or None if at root or unreachable."},
|
||||||
|
|
||||||
|
{NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
// DijkstraMap getsetters
|
||||||
|
PyGetSetDef PyDijkstraMap_getsetters[] = {
|
||||||
|
{"root", (getter)UIGridPathfinding::DijkstraMap_get_root, NULL,
|
||||||
|
"Root position that distances are measured from (Vector, read-only).", NULL},
|
||||||
|
|
||||||
|
{NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
// DijkstraMap type
|
||||||
|
PyTypeObject PyDijkstraMapType = {
|
||||||
|
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||||
|
.tp_name = "mcrfpy.DijkstraMap",
|
||||||
|
.tp_basicsize = sizeof(PyDijkstraMapObject),
|
||||||
|
.tp_itemsize = 0,
|
||||||
|
.tp_dealloc = (destructor)UIGridPathfinding::DijkstraMap_dealloc,
|
||||||
|
.tp_repr = (reprfunc)UIGridPathfinding::DijkstraMap_repr,
|
||||||
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
|
.tp_doc = PyDoc_STR(
|
||||||
|
"A Dijkstra distance map from a fixed root position.\n\n"
|
||||||
|
"Created by Grid.get_dijkstra_map(). Cannot be instantiated directly.\n\n"
|
||||||
|
"Grid caches these maps - multiple requests for the same root return\n"
|
||||||
|
"the same map. Call Grid.clear_dijkstra_maps() after changing grid\n"
|
||||||
|
"walkability to invalidate the cache.\n\n"
|
||||||
|
"Properties:\n"
|
||||||
|
" root (Vector): Root position (read-only)\n\n"
|
||||||
|
"Methods:\n"
|
||||||
|
" distance(pos) -> float | None: Get distance to root\n"
|
||||||
|
" path_from(pos) -> AStarPath: Get full path to root\n"
|
||||||
|
" step_from(pos) -> Vector | None: Get single step toward root\n\n"
|
||||||
|
"Example:\n"
|
||||||
|
" dijkstra = grid.get_dijkstra_map(player.pos)\n"
|
||||||
|
" for enemy in enemies:\n"
|
||||||
|
" dist = dijkstra.distance(enemy.pos)\n"
|
||||||
|
" if dist and dist < 10:\n"
|
||||||
|
" step = dijkstra.step_from(enemy.pos)\n"
|
||||||
|
" if step:\n"
|
||||||
|
" enemy.pos = step"),
|
||||||
|
.tp_methods = PyDijkstraMap_methods,
|
||||||
|
.tp_getset = PyDijkstraMap_getsetters,
|
||||||
|
.tp_init = (initproc)UIGridPathfinding::DijkstraMap_init,
|
||||||
|
.tp_new = UIGridPathfinding::DijkstraMap_new,
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mcrfpydef
|
||||||
152
src/UIGridPathfinding.h
Normal file
152
src/UIGridPathfinding.h
Normal file
|
|
@ -0,0 +1,152 @@
|
||||||
|
#pragma once
|
||||||
|
#include "Common.h"
|
||||||
|
#include "Python.h"
|
||||||
|
#include "UIBase.h" // For PyUIGridObject typedef
|
||||||
|
#include <libtcod.h>
|
||||||
|
#include <SFML/System/Vector2.hpp>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
|
class UIGrid;
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// AStarPath - A computed A* path result, consumed like an iterator
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
struct PyAStarPathObject {
|
||||||
|
PyObject_HEAD
|
||||||
|
std::vector<sf::Vector2i> path; // Pre-computed path positions
|
||||||
|
size_t current_index; // Next step to return
|
||||||
|
sf::Vector2i origin; // Fixed at creation
|
||||||
|
sf::Vector2i destination; // Fixed at creation
|
||||||
|
};
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// DijkstraMap - A Dijkstra distance field from a fixed root
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
class DijkstraMap {
|
||||||
|
public:
|
||||||
|
DijkstraMap(TCODMap* map, int root_x, int root_y, float diagonal_cost);
|
||||||
|
~DijkstraMap();
|
||||||
|
|
||||||
|
// Non-copyable (owns TCODDijkstra)
|
||||||
|
DijkstraMap(const DijkstraMap&) = delete;
|
||||||
|
DijkstraMap& operator=(const DijkstraMap&) = delete;
|
||||||
|
|
||||||
|
// Queries
|
||||||
|
float getDistance(int x, int y) const;
|
||||||
|
std::vector<sf::Vector2i> getPathFrom(int x, int y) const;
|
||||||
|
sf::Vector2i stepFrom(int x, int y, bool* valid = nullptr) const;
|
||||||
|
|
||||||
|
// Accessors
|
||||||
|
sf::Vector2i getRoot() const { return root; }
|
||||||
|
float getDiagonalCost() const { return diagonal_cost; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
TCODDijkstra* tcod_dijkstra; // Owned by this object
|
||||||
|
TCODMap* tcod_map; // Borrowed from Grid
|
||||||
|
sf::Vector2i root;
|
||||||
|
float diagonal_cost;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PyDijkstraMapObject {
|
||||||
|
PyObject_HEAD
|
||||||
|
std::shared_ptr<DijkstraMap> data; // Shared with Grid's collection
|
||||||
|
};
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// Helper Functions
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
namespace UIGridPathfinding {
|
||||||
|
// Extract grid position from Vector, Entity, or tuple
|
||||||
|
// Sets Python error and returns false on failure
|
||||||
|
// If expected_grid is provided and obj is Entity, validates grid membership
|
||||||
|
bool ExtractPosition(PyObject* obj, int* x, int* y,
|
||||||
|
UIGrid* expected_grid = nullptr,
|
||||||
|
const char* arg_name = "position");
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// AStarPath Python Type Methods
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
PyObject* AStarPath_new(PyTypeObject* type, PyObject* args, PyObject* kwds);
|
||||||
|
int AStarPath_init(PyAStarPathObject* self, PyObject* args, PyObject* kwds);
|
||||||
|
void AStarPath_dealloc(PyAStarPathObject* self);
|
||||||
|
PyObject* AStarPath_repr(PyAStarPathObject* self);
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
PyObject* AStarPath_walk(PyAStarPathObject* self, PyObject* args);
|
||||||
|
PyObject* AStarPath_peek(PyAStarPathObject* self, PyObject* args);
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
PyObject* AStarPath_get_origin(PyAStarPathObject* self, void* closure);
|
||||||
|
PyObject* AStarPath_get_destination(PyAStarPathObject* self, void* closure);
|
||||||
|
PyObject* AStarPath_get_remaining(PyAStarPathObject* self, void* closure);
|
||||||
|
|
||||||
|
// Sequence protocol
|
||||||
|
Py_ssize_t AStarPath_len(PyAStarPathObject* self);
|
||||||
|
int AStarPath_bool(PyObject* self);
|
||||||
|
PyObject* AStarPath_iter(PyAStarPathObject* self);
|
||||||
|
PyObject* AStarPath_iternext(PyAStarPathObject* self);
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// DijkstraMap Python Type Methods
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
PyObject* DijkstraMap_new(PyTypeObject* type, PyObject* args, PyObject* kwds);
|
||||||
|
int DijkstraMap_init(PyDijkstraMapObject* self, PyObject* args, PyObject* kwds);
|
||||||
|
void DijkstraMap_dealloc(PyDijkstraMapObject* self);
|
||||||
|
PyObject* DijkstraMap_repr(PyDijkstraMapObject* self);
|
||||||
|
|
||||||
|
// Methods
|
||||||
|
PyObject* DijkstraMap_distance(PyDijkstraMapObject* self, PyObject* args, PyObject* kwds);
|
||||||
|
PyObject* DijkstraMap_path_from(PyDijkstraMapObject* self, PyObject* args, PyObject* kwds);
|
||||||
|
PyObject* DijkstraMap_step_from(PyDijkstraMapObject* self, PyObject* args, PyObject* kwds);
|
||||||
|
|
||||||
|
// Properties
|
||||||
|
PyObject* DijkstraMap_get_root(PyDijkstraMapObject* self, void* closure);
|
||||||
|
|
||||||
|
//=========================================================================
|
||||||
|
// Grid Factory Methods (called from UIGrid Python bindings)
|
||||||
|
//=========================================================================
|
||||||
|
|
||||||
|
// Grid.find_path() -> AStarPath | None
|
||||||
|
PyObject* Grid_find_path(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||||
|
|
||||||
|
// Grid.get_dijkstra_map() -> DijkstraMap
|
||||||
|
PyObject* Grid_get_dijkstra_map(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||||
|
|
||||||
|
// Grid.clear_dijkstra_maps() -> None
|
||||||
|
PyObject* Grid_clear_dijkstra_maps(PyUIGridObject* self, PyObject* args);
|
||||||
|
}
|
||||||
|
|
||||||
|
//=============================================================================
|
||||||
|
// Python Type Definitions
|
||||||
|
//=============================================================================
|
||||||
|
|
||||||
|
namespace mcrfpydef {
|
||||||
|
|
||||||
|
// AStarPath iterator type
|
||||||
|
struct PyAStarPathIterObject {
|
||||||
|
PyObject_HEAD
|
||||||
|
PyAStarPathObject* path; // Reference to path being iterated
|
||||||
|
size_t iter_index; // Current iteration position
|
||||||
|
};
|
||||||
|
|
||||||
|
extern PyNumberMethods PyAStarPath_as_number;
|
||||||
|
extern PySequenceMethods PyAStarPath_as_sequence;
|
||||||
|
extern PyMethodDef PyAStarPath_methods[];
|
||||||
|
extern PyGetSetDef PyAStarPath_getsetters[];
|
||||||
|
|
||||||
|
extern PyTypeObject PyAStarPathType;
|
||||||
|
extern PyTypeObject PyAStarPathIterType;
|
||||||
|
|
||||||
|
extern PyMethodDef PyDijkstraMap_methods[];
|
||||||
|
extern PyGetSetDef PyDijkstraMap_getsetters[];
|
||||||
|
|
||||||
|
extern PyTypeObject PyDijkstraMapType;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue