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 "PyKeyboard.h"
|
||||
#include "PyMouse.h"
|
||||
#include "UIGridPathfinding.h" // AStarPath and DijkstraMap types
|
||||
#include "McRogueFaceVersion.h"
|
||||
#include "GameEngine.h"
|
||||
#include "ImGuiConsole.h"
|
||||
|
|
@ -410,6 +411,10 @@ PyObject* PyInit_mcrfpy()
|
|||
/*mouse state (#186)*/
|
||||
&PyMouseType,
|
||||
|
||||
/*pathfinding result types*/
|
||||
&mcrfpydef::PyAStarPathType,
|
||||
&mcrfpydef::PyDijkstraMapType,
|
||||
|
||||
nullptr};
|
||||
|
||||
// Types that are used internally but NOT exported to module namespace (#189)
|
||||
|
|
@ -422,6 +427,9 @@ PyObject* PyInit_mcrfpy()
|
|||
&PyUICollectionType, &PyUICollectionIterType,
|
||||
&PyUIEntityCollectionType, &PyUIEntityCollectionIterType,
|
||||
|
||||
/*pathfinding iterator - returned by AStarPath.__iter__() but not directly instantiable*/
|
||||
&mcrfpydef::PyAStarPathIterType,
|
||||
|
||||
nullptr};
|
||||
|
||||
// Set up PyWindowType methods and getsetters before PyType_Ready
|
||||
|
|
|
|||
|
|
@ -55,32 +55,6 @@ static PyObject* McRFPy_Libtcod::compute_fov(PyObject* self, PyObject* args) {
|
|||
return visible_list;
|
||||
}
|
||||
|
||||
// A* Pathfinding
|
||||
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
|
||||
static PyObject* McRFPy_Libtcod::line(PyObject* self, PyObject* args) {
|
||||
int x1, y1, x2, y2;
|
||||
|
|
@ -112,80 +86,8 @@ static PyObject* McRFPy_Libtcod::line_iter(PyObject* self, PyObject* args) {
|
|||
return line(self, args);
|
||||
}
|
||||
|
||||
// Dijkstra pathfinding
|
||||
static PyObject* McRFPy_Libtcod::dijkstra_new(PyObject* self, PyObject* args) {
|
||||
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)
|
||||
// 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[] = {
|
||||
|
|
@ -201,17 +103,6 @@ static PyMethodDef libtcodMethods[] = {
|
|||
"Returns:\n"
|
||||
" 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(x1, y1, x2, y2)\n\n"
|
||||
"Get cells along a line using Bresenham's algorithm.\n\n"
|
||||
|
|
@ -230,40 +121,6 @@ static PyMethodDef libtcodMethods[] = {
|
|||
"Returns:\n"
|
||||
" 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}
|
||||
};
|
||||
|
||||
|
|
@ -271,7 +128,7 @@ static PyMethodDef libtcodMethods[] = {
|
|||
static PyModuleDef libtcodModule = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"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"
|
||||
"Unlike the original TCOD, these functions work directly with Grid objects.\n\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.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 = libtcod.find_path(grid, 0, 0, 49, 49)",
|
||||
" path = grid.find_path((0, 0), (49, 49)) # Returns AStarPath",
|
||||
-1,
|
||||
libtcodMethods
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
#include "McRFPy_API.h"
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <libtcod.h>
|
||||
#include "PyObjectUtils.h"
|
||||
#include "PyVector.h"
|
||||
#include "PythonObjectCache.h"
|
||||
|
|
@ -762,23 +763,29 @@ PyObject* UIEntity::path_to(PyUIEntityObject* self, PyObject* args, PyObject* kw
|
|||
return NULL;
|
||||
}
|
||||
|
||||
// Use the grid's Dijkstra implementation
|
||||
grid->computeDijkstra(current_x, current_y);
|
||||
auto path = grid->getDijkstraPath(target_x, target_y);
|
||||
// Use A* pathfinding via temporary TCODPath
|
||||
TCODPath tcod_path(grid->getTCODMap(), 1.41f);
|
||||
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
|
||||
PyObject* path_list = PyList_New(path.size());
|
||||
PyObject* path_list = PyList_New(tcod_path.size());
|
||||
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);
|
||||
if (!coord_tuple) {
|
||||
Py_DECREF(path_list);
|
||||
return PyErr_NoMemory();
|
||||
}
|
||||
|
||||
PyTuple_SetItem(coord_tuple, 0, PyLong_FromLong(path[i].first));
|
||||
PyTuple_SetItem(coord_tuple, 1, PyLong_FromLong(path[i].second));
|
||||
PyTuple_SetItem(coord_tuple, 0, PyLong_FromLong(px));
|
||||
PyTuple_SetItem(coord_tuple, 1, PyLong_FromLong(py));
|
||||
PyList_SetItem(path_list, i, coord_tuple);
|
||||
}
|
||||
|
||||
|
|
|
|||
372
src/UIGrid.cpp
372
src/UIGrid.cpp
|
|
@ -1,4 +1,5 @@
|
|||
#include "UIGrid.h"
|
||||
#include "UIGridPathfinding.h" // New pathfinding API
|
||||
#include "GameEngine.h"
|
||||
#include "McRFPy_API.h"
|
||||
#include "PythonObjectCache.h"
|
||||
|
|
@ -17,7 +18,7 @@
|
|||
|
||||
UIGrid::UIGrid()
|
||||
: 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),
|
||||
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),
|
||||
zoom(1.0f),
|
||||
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),
|
||||
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
|
||||
output.setTexture(renderTexture.getTexture());
|
||||
|
||||
// Create TCOD map
|
||||
// Create TCOD map for FOV and as source for pathfinding
|
||||
tcod_map = new TCODMap(gx, gy);
|
||||
|
||||
// Create TCOD dijkstra pathfinder
|
||||
tcod_dijkstra = new TCODDijkstra(tcod_map);
|
||||
|
||||
// Create TCOD A* pathfinder
|
||||
tcod_path = new TCODPath(tcod_map);
|
||||
// Note: DijkstraMap objects are created on-demand via get_dijkstra_map()
|
||||
// A* paths are computed on-demand via find_path()
|
||||
|
||||
// #123 - Initialize storage based on grid size
|
||||
if (use_chunks) {
|
||||
|
|
@ -368,14 +365,9 @@ UIGridPoint& UIGrid::at(int x, int y)
|
|||
|
||||
UIGrid::~UIGrid()
|
||||
{
|
||||
if (tcod_path) {
|
||||
delete tcod_path;
|
||||
tcod_path = nullptr;
|
||||
}
|
||||
if (tcod_dijkstra) {
|
||||
delete tcod_dijkstra;
|
||||
tcod_dijkstra = nullptr;
|
||||
}
|
||||
// Clear Dijkstra maps first (they reference tcod_map)
|
||||
dijkstra_maps.clear();
|
||||
|
||||
if (tcod_map) {
|
||||
delete tcod_map;
|
||||
tcod_map = nullptr;
|
||||
|
|
@ -476,98 +468,9 @@ bool UIGrid::isInFOV(int x, int y) const
|
|||
return tcod_map->isInFov(x, y);
|
||||
}
|
||||
|
||||
std::vector<std::pair<int, int>> UIGrid::findPath(int x1, int y1, int x2, int y2, float diagonalCost)
|
||||
{
|
||||
std::vector<std::pair<int, int>> path;
|
||||
|
||||
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;
|
||||
}
|
||||
// Pathfinding methods moved to UIGridPathfinding.cpp
|
||||
// - Grid.find_path() returns AStarPath objects
|
||||
// - Grid.get_dijkstra_map() returns DijkstraMap objects (cached)
|
||||
|
||||
// Phase 1 implementations
|
||||
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);
|
||||
}
|
||||
|
||||
PyObject* UIGrid::py_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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
// Old pathfinding Python methods removed - see UIGridPathfinding.cpp for new implementation
|
||||
// Grid.find_path() now returns AStarPath objects
|
||||
// Grid.get_dijkstra_map() returns DijkstraMap objects (cached by root)
|
||||
|
||||
// #147 - Layer system Python API
|
||||
PyObject* UIGrid::py_add_layer(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||
|
|
@ -1925,51 +1709,32 @@ PyMethodDef UIGrid::methods[] = {
|
|||
"Returns:\n"
|
||||
" True if the cell is visible, False otherwise\n\n"
|
||||
"Must call compute_fov() first to calculate visibility."},
|
||||
{"find_path", (PyCFunction)UIGrid::py_find_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"find_path(start, end, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\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"
|
||||
{"find_path", (PyCFunction)UIGridPathfinding::Grid_find_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"find_path(start, end, diagonal_cost: float = 1.41) -> AStarPath | None\n\n"
|
||||
"Compute 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"
|
||||
" start: Starting position as Vector, Entity, or (x, y) tuple\n"
|
||||
" end: Target position as Vector, Entity, or (x, y) tuple\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"
|
||||
"Alternative A* implementation. Prefer find_path() for consistency."},
|
||||
" AStarPath object if path exists, None otherwise.\n\n"
|
||||
"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(type: str, z_index: int = -1, texture: Texture = None) -> ColorLayer | TileLayer"},
|
||||
{"remove_layer", (PyCFunction)UIGrid::py_remove_layer, METH_VARARGS,
|
||||
|
|
@ -2021,51 +1786,32 @@ PyMethodDef UIGrid_all_methods[] = {
|
|||
"Returns:\n"
|
||||
" True if the cell is visible, False otherwise\n\n"
|
||||
"Must call compute_fov() first to calculate visibility."},
|
||||
{"find_path", (PyCFunction)UIGrid::py_find_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"find_path(start, end, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]\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"
|
||||
{"find_path", (PyCFunction)UIGridPathfinding::Grid_find_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"find_path(start, end, diagonal_cost: float = 1.41) -> AStarPath | None\n\n"
|
||||
"Compute 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"
|
||||
" start: Starting position as Vector, Entity, or (x, y) tuple\n"
|
||||
" end: Target position as Vector, Entity, or (x, y) tuple\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"
|
||||
"Alternative A* implementation. Prefer find_path() for consistency."},
|
||||
" AStarPath object if path exists, None otherwise.\n\n"
|
||||
"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(type: str, z_index: int = -1, texture: Texture = None) -> ColorLayer | TileLayer\n\n"
|
||||
"Add a new layer to the grid.\n\n"
|
||||
|
|
|
|||
34
src/UIGrid.h
34
src/UIGrid.h
|
|
@ -8,6 +8,8 @@
|
|||
#include <libtcod.h>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
#include "PyCallable.h"
|
||||
#include "PyTexture.h"
|
||||
|
|
@ -25,6 +27,9 @@
|
|||
#include "SpatialHash.h"
|
||||
#include "UIEntityCollection.h" // EntityCollection types (extracted from UIGrid)
|
||||
|
||||
// Forward declaration for pathfinding
|
||||
class DijkstraMap;
|
||||
|
||||
class UIGrid: public UIDrawable
|
||||
{
|
||||
private:
|
||||
|
|
@ -33,10 +38,13 @@ private:
|
|||
static constexpr int DEFAULT_CELL_WIDTH = 16;
|
||||
static constexpr int DEFAULT_CELL_HEIGHT = 16;
|
||||
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
|
||||
|
||||
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:
|
||||
UIGrid();
|
||||
//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 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;
|
||||
TCODMap* getTCODMap() const { return tcod_map; } // Access for pathfinding
|
||||
|
||||
// Pathfinding methods
|
||||
std::vector<std::pair<int, int>> findPath(int x1, int y1, int x2, int y2, float diagonalCost = 1.41f);
|
||||
void computeDijkstra(int rootX, int rootY, float diagonalCost = 1.41f);
|
||||
float getDijkstraDistance(int x, int y) const;
|
||||
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);
|
||||
// Pathfinding - new API creates AStarPath/DijkstraMap objects
|
||||
// See UIGridPathfinding.h for the new pathfinding API
|
||||
// Grid.find_path() now returns AStarPath objects
|
||||
// Grid.get_dijkstra_map() returns DijkstraMap objects (cached by root position)
|
||||
|
||||
// Phase 1 virtual method implementations
|
||||
sf::FloatRect get_bounds() const override;
|
||||
|
|
@ -167,11 +172,10 @@ public:
|
|||
static PyObject* py_at(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_find_path(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* py_compute_dijkstra(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* py_get_dijkstra_distance(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* py_get_dijkstra_path(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyObject* py_compute_astar_path(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||
// Pathfinding methods moved to UIGridPathfinding.cpp
|
||||
// py_find_path -> UIGridPathfinding::Grid_find_path (returns AStarPath)
|
||||
// py_get_dijkstra_map -> UIGridPathfinding::Grid_get_dijkstra_map (returns DijkstraMap)
|
||||
// py_clear_dijkstra_maps -> UIGridPathfinding::Grid_clear_dijkstra_maps
|
||||
static PyObject* py_entities_in_radius(PyUIGridObject* self, PyObject* args, PyObject* kwds); // #115
|
||||
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