feat(entity): implement path_to() method for entity pathfinding
- Add path_to(target_x, target_y) method to UIEntity class - Uses existing Dijkstra pathfinding implementation from UIGrid - Returns list of (x, y) coordinate tuples for complete path - Supports both positional and keyword argument formats - Proper error handling for out-of-bounds and no-grid scenarios - Comprehensive test suite covering normal and edge cases Part of TCOD integration sprint - gives entities immediate pathfinding capabilities. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
1a3e308c77
commit
7ee0a08662
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
|
@ -0,0 +1,342 @@
|
|||
/**
|
||||
* Example implementation demonstrating the proposed visibility tracking system
|
||||
* This shows how UIGridPoint, UIGridPointState, and libtcod maps work together
|
||||
*/
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
// Forward declarations
|
||||
class UIGrid;
|
||||
class UIEntity;
|
||||
class TCODMap;
|
||||
|
||||
/**
|
||||
* UIGridPoint - The "ground truth" of a grid cell
|
||||
* This represents the actual state of the world
|
||||
*/
|
||||
class UIGridPoint {
|
||||
public:
|
||||
// Core properties
|
||||
bool walkable = true; // Can entities move through this cell?
|
||||
bool transparent = true; // Does this cell block line of sight?
|
||||
int tilesprite = 0; // What tile to render
|
||||
|
||||
// Visual properties
|
||||
sf::Color color;
|
||||
sf::Color color_overlay;
|
||||
|
||||
// Grid position
|
||||
int grid_x, grid_y;
|
||||
UIGrid* parent_grid;
|
||||
|
||||
// When these change, sync with TCOD map
|
||||
void setWalkable(bool value) {
|
||||
walkable = value;
|
||||
if (parent_grid) syncTCODMapCell();
|
||||
}
|
||||
|
||||
void setTransparent(bool value) {
|
||||
transparent = value;
|
||||
if (parent_grid) syncTCODMapCell();
|
||||
}
|
||||
|
||||
private:
|
||||
void syncTCODMapCell(); // Update TCOD map when properties change
|
||||
};
|
||||
|
||||
/**
|
||||
* UIGridPointState - What an entity knows about a grid cell
|
||||
* Each entity maintains one of these for each cell it has encountered
|
||||
*/
|
||||
class UIGridPointState {
|
||||
public:
|
||||
// Visibility state
|
||||
bool visible = false; // Currently in entity's FOV?
|
||||
bool discovered = false; // Has entity ever seen this cell?
|
||||
|
||||
// When the entity last saw this cell (for fog of war effects)
|
||||
int last_seen_turn = -1;
|
||||
|
||||
// What the entity remembers about this cell
|
||||
// (may be outdated if cell changed after entity saw it)
|
||||
bool remembered_walkable = true;
|
||||
bool remembered_transparent = true;
|
||||
int remembered_tilesprite = 0;
|
||||
|
||||
// Update remembered state from actual grid point
|
||||
void updateFromTruth(const UIGridPoint& truth, int current_turn) {
|
||||
if (visible) {
|
||||
discovered = true;
|
||||
last_seen_turn = current_turn;
|
||||
remembered_walkable = truth.walkable;
|
||||
remembered_transparent = truth.transparent;
|
||||
remembered_tilesprite = truth.tilesprite;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* EntityGridKnowledge - Manages an entity's knowledge across multiple grids
|
||||
* This allows entities to remember explored areas even when changing levels
|
||||
*/
|
||||
class EntityGridKnowledge {
|
||||
private:
|
||||
// Map from grid ID to the entity's knowledge of that grid
|
||||
std::unordered_map<std::string, std::vector<UIGridPointState>> grid_knowledge;
|
||||
|
||||
public:
|
||||
// Get or create knowledge vector for a specific grid
|
||||
std::vector<UIGridPointState>& getGridKnowledge(const std::string& grid_id, int grid_size) {
|
||||
auto& knowledge = grid_knowledge[grid_id];
|
||||
if (knowledge.empty()) {
|
||||
knowledge.resize(grid_size);
|
||||
}
|
||||
return knowledge;
|
||||
}
|
||||
|
||||
// Check if entity has visited this grid before
|
||||
bool hasGridKnowledge(const std::string& grid_id) const {
|
||||
return grid_knowledge.find(grid_id) != grid_knowledge.end();
|
||||
}
|
||||
|
||||
// Clear knowledge of a specific grid (e.g., for memory-wiping effects)
|
||||
void forgetGrid(const std::string& grid_id) {
|
||||
grid_knowledge.erase(grid_id);
|
||||
}
|
||||
|
||||
// Get total number of grids this entity knows about
|
||||
size_t getKnownGridCount() const {
|
||||
return grid_knowledge.size();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Enhanced UIEntity with visibility tracking
|
||||
*/
|
||||
class UIEntity {
|
||||
private:
|
||||
// Entity properties
|
||||
float x, y; // Position
|
||||
UIGrid* current_grid; // Current grid entity is on
|
||||
EntityGridKnowledge knowledge; // Multi-grid knowledge storage
|
||||
int sight_radius = 10; // How far entity can see
|
||||
bool omniscient = false; // Does entity know everything?
|
||||
|
||||
public:
|
||||
// Update entity's FOV and visibility knowledge
|
||||
void updateFOV(int radius = -1) {
|
||||
if (!current_grid) return;
|
||||
if (radius < 0) radius = sight_radius;
|
||||
|
||||
// Get entity's knowledge of current grid
|
||||
auto& grid_knowledge = knowledge.getGridKnowledge(
|
||||
current_grid->getGridId(),
|
||||
current_grid->getGridSize()
|
||||
);
|
||||
|
||||
// Reset visibility for all cells
|
||||
for (auto& cell_knowledge : grid_knowledge) {
|
||||
cell_knowledge.visible = false;
|
||||
}
|
||||
|
||||
if (omniscient) {
|
||||
// Omniscient entities see everything
|
||||
for (int i = 0; i < grid_knowledge.size(); i++) {
|
||||
grid_knowledge[i].visible = true;
|
||||
grid_knowledge[i].discovered = true;
|
||||
grid_knowledge[i].updateFromTruth(
|
||||
current_grid->getPointAt(i),
|
||||
current_grid->getCurrentTurn()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Normal FOV calculation using TCOD
|
||||
current_grid->computeFOVForEntity(this, (int)x, (int)y, radius);
|
||||
|
||||
// Update visibility states based on TCOD FOV results
|
||||
for (int gy = 0; gy < current_grid->getHeight(); gy++) {
|
||||
for (int gx = 0; gx < current_grid->getWidth(); gx++) {
|
||||
int idx = gy * current_grid->getWidth() + gx;
|
||||
|
||||
if (current_grid->isCellInFOV(gx, gy)) {
|
||||
grid_knowledge[idx].visible = true;
|
||||
grid_knowledge[idx].updateFromTruth(
|
||||
current_grid->getPointAt(idx),
|
||||
current_grid->getCurrentTurn()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if entity can see a specific position
|
||||
bool canSeePosition(int gx, int gy) const {
|
||||
if (!current_grid) return false;
|
||||
|
||||
auto& grid_knowledge = const_cast<EntityGridKnowledge&>(knowledge).getGridKnowledge(
|
||||
current_grid->getGridId(),
|
||||
current_grid->getGridSize()
|
||||
);
|
||||
|
||||
int idx = gy * current_grid->getWidth() + gx;
|
||||
return idx >= 0 && idx < grid_knowledge.size() && grid_knowledge[idx].visible;
|
||||
}
|
||||
|
||||
// Check if entity has ever discovered a position
|
||||
bool hasDiscoveredPosition(int gx, int gy) const {
|
||||
if (!current_grid) return false;
|
||||
|
||||
auto& grid_knowledge = const_cast<EntityGridKnowledge&>(knowledge).getGridKnowledge(
|
||||
current_grid->getGridId(),
|
||||
current_grid->getGridSize()
|
||||
);
|
||||
|
||||
int idx = gy * current_grid->getWidth() + gx;
|
||||
return idx >= 0 && idx < grid_knowledge.size() && grid_knowledge[idx].discovered;
|
||||
}
|
||||
|
||||
// Find path using only discovered/remembered terrain
|
||||
std::vector<std::pair<int, int>> findKnownPath(int dest_x, int dest_y) {
|
||||
if (!current_grid) return {};
|
||||
|
||||
// Create a TCOD map based on entity's knowledge
|
||||
auto knowledge_map = current_grid->createKnowledgeMapForEntity(this);
|
||||
|
||||
// Use A* on the knowledge map
|
||||
auto path = knowledge_map->computePath((int)x, (int)y, dest_x, dest_y);
|
||||
|
||||
delete knowledge_map;
|
||||
return path;
|
||||
}
|
||||
|
||||
// Move to a new grid, preserving knowledge of the old one
|
||||
void moveToGrid(UIGrid* new_grid) {
|
||||
if (current_grid) {
|
||||
// Knowledge is automatically preserved in the knowledge map
|
||||
current_grid->removeEntity(this);
|
||||
}
|
||||
|
||||
current_grid = new_grid;
|
||||
if (new_grid) {
|
||||
new_grid->addEntity(this);
|
||||
// If we've been here before, we still remember it
|
||||
updateFOV();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Example use cases
|
||||
*/
|
||||
|
||||
// Use Case 1: Player exploring a dungeon
|
||||
void playerExploration() {
|
||||
auto player = std::make_shared<UIEntity>();
|
||||
auto dungeon_level1 = std::make_shared<UIGrid>("dungeon_level_1", 50, 50);
|
||||
|
||||
// Player starts with no knowledge
|
||||
player->moveToGrid(dungeon_level1.get());
|
||||
player->updateFOV(10); // Can see 10 tiles in each direction
|
||||
|
||||
// Only render what player can see
|
||||
dungeon_level1->renderWithEntityPerspective(player.get());
|
||||
|
||||
// Player tries to path to unexplored area
|
||||
auto path = player->findKnownPath(45, 45);
|
||||
if (path.empty()) {
|
||||
// "You haven't explored that area yet!"
|
||||
}
|
||||
}
|
||||
|
||||
// Use Case 2: Entity with perfect knowledge
|
||||
void omniscientEntity() {
|
||||
auto guardian = std::make_shared<UIEntity>();
|
||||
guardian->setOmniscient(true); // Knows everything about any grid it enters
|
||||
|
||||
auto temple = std::make_shared<UIGrid>("temple", 30, 30);
|
||||
guardian->moveToGrid(temple.get());
|
||||
|
||||
// Guardian immediately knows entire layout
|
||||
auto path = guardian->findKnownPath(29, 29); // Can path anywhere
|
||||
}
|
||||
|
||||
// Use Case 3: Entity returning to previously explored area
|
||||
void returningToArea() {
|
||||
auto scout = std::make_shared<UIEntity>();
|
||||
auto forest = std::make_shared<UIGrid>("forest", 40, 40);
|
||||
auto cave = std::make_shared<UIGrid>("cave", 20, 20);
|
||||
|
||||
// Scout explores forest
|
||||
scout->moveToGrid(forest.get());
|
||||
scout->updateFOV(15);
|
||||
// ... scout moves around, discovering ~50% of forest ...
|
||||
|
||||
// Scout enters cave
|
||||
scout->moveToGrid(cave.get());
|
||||
scout->updateFOV(8); // Darker in cave, reduced vision
|
||||
|
||||
// Later, scout returns to forest
|
||||
scout->moveToGrid(forest.get());
|
||||
// Scout still remembers the areas previously explored!
|
||||
// Can immediately path through known areas
|
||||
auto path = scout->findKnownPath(10, 10); // Works if area was explored before
|
||||
}
|
||||
|
||||
// Use Case 4: Fog of war - remembered vs current state
|
||||
void fogOfWar() {
|
||||
auto player = std::make_shared<UIEntity>();
|
||||
auto dungeon = std::make_shared<UIGrid>("dungeon", 50, 50);
|
||||
|
||||
player->moveToGrid(dungeon.get());
|
||||
player->setPosition(25, 25);
|
||||
player->updateFOV(10);
|
||||
|
||||
// Player sees a door at (30, 25) - it's open
|
||||
auto& door_point = dungeon->at(30, 25);
|
||||
door_point.walkable = true;
|
||||
door_point.transparent = true;
|
||||
|
||||
// Player moves away
|
||||
player->setPosition(10, 10);
|
||||
player->updateFOV(10);
|
||||
|
||||
// While player is gone, door closes
|
||||
door_point.walkable = false;
|
||||
door_point.transparent = false;
|
||||
|
||||
// Player's memory still thinks door is open
|
||||
auto& player_knowledge = player->getKnowledgeAt(30, 25);
|
||||
// player_knowledge.remembered_walkable is still true!
|
||||
|
||||
// Player tries to path through the door based on memory
|
||||
auto path = player->findKnownPath(35, 25);
|
||||
// Path planning succeeds based on remembered state
|
||||
|
||||
// But when player gets close enough to see it again...
|
||||
player->setPosition(25, 25);
|
||||
player->updateFOV(10);
|
||||
// Knowledge updates - door is actually closed!
|
||||
}
|
||||
|
||||
/**
|
||||
* Proper use of each component:
|
||||
*
|
||||
* UIGridPoint:
|
||||
* - Stores the actual, current state of the world
|
||||
* - Used by the game logic to determine what really happens
|
||||
* - Syncs with TCOD map for consistent pathfinding/FOV
|
||||
*
|
||||
* UIGridPointState:
|
||||
* - Stores what an entity believes/remembers about a cell
|
||||
* - May be outdated if world changed since last seen
|
||||
* - Used for rendering fog of war and entity decision-making
|
||||
*
|
||||
* TCOD Map:
|
||||
* - Provides efficient FOV and pathfinding algorithms
|
||||
* - Can be created from either ground truth or entity knowledge
|
||||
* - Multiple maps can exist (one for truth, one per entity for knowledge-based pathfinding)
|
||||
*/
|
|
@ -1,5 +1,6 @@
|
|||
#include "McRFPy_API.h"
|
||||
#include "McRFPy_Automation.h"
|
||||
#include "McRFPy_Libtcod.h"
|
||||
#include "platform.h"
|
||||
#include "PyAnimation.h"
|
||||
#include "PyDrawable.h"
|
||||
|
@ -12,6 +13,7 @@
|
|||
#include "PyScene.h"
|
||||
#include <filesystem>
|
||||
#include <cstring>
|
||||
#include <libtcod.h>
|
||||
|
||||
std::vector<sf::SoundBuffer>* McRFPy_API::soundbuffers = nullptr;
|
||||
sf::Music* McRFPy_API::music = nullptr;
|
||||
|
@ -287,6 +289,21 @@ PyObject* PyInit_mcrfpy()
|
|||
PyModule_AddObject(m, "default_font", Py_None);
|
||||
PyModule_AddObject(m, "default_texture", Py_None);
|
||||
|
||||
// Add TCOD FOV algorithm constants
|
||||
PyModule_AddIntConstant(m, "FOV_BASIC", FOV_BASIC);
|
||||
PyModule_AddIntConstant(m, "FOV_DIAMOND", FOV_DIAMOND);
|
||||
PyModule_AddIntConstant(m, "FOV_SHADOW", FOV_SHADOW);
|
||||
PyModule_AddIntConstant(m, "FOV_PERMISSIVE_0", FOV_PERMISSIVE_0);
|
||||
PyModule_AddIntConstant(m, "FOV_PERMISSIVE_1", FOV_PERMISSIVE_1);
|
||||
PyModule_AddIntConstant(m, "FOV_PERMISSIVE_2", FOV_PERMISSIVE_2);
|
||||
PyModule_AddIntConstant(m, "FOV_PERMISSIVE_3", FOV_PERMISSIVE_3);
|
||||
PyModule_AddIntConstant(m, "FOV_PERMISSIVE_4", FOV_PERMISSIVE_4);
|
||||
PyModule_AddIntConstant(m, "FOV_PERMISSIVE_5", FOV_PERMISSIVE_5);
|
||||
PyModule_AddIntConstant(m, "FOV_PERMISSIVE_6", FOV_PERMISSIVE_6);
|
||||
PyModule_AddIntConstant(m, "FOV_PERMISSIVE_7", FOV_PERMISSIVE_7);
|
||||
PyModule_AddIntConstant(m, "FOV_PERMISSIVE_8", FOV_PERMISSIVE_8);
|
||||
PyModule_AddIntConstant(m, "FOV_RESTRICTIVE", FOV_RESTRICTIVE);
|
||||
|
||||
// Add automation submodule
|
||||
PyObject* automation_module = McRFPy_Automation::init_automation_module();
|
||||
if (automation_module != NULL) {
|
||||
|
@ -297,6 +314,16 @@ PyObject* PyInit_mcrfpy()
|
|||
PyDict_SetItemString(sys_modules, "mcrfpy.automation", automation_module);
|
||||
}
|
||||
|
||||
// Add libtcod submodule
|
||||
PyObject* libtcod_module = McRFPy_Libtcod::init_libtcod_module();
|
||||
if (libtcod_module != NULL) {
|
||||
PyModule_AddObject(m, "libtcod", libtcod_module);
|
||||
|
||||
// Also add to sys.modules for proper import behavior
|
||||
PyObject* sys_modules = PyImport_GetModuleDict();
|
||||
PyDict_SetItemString(sys_modules, "mcrfpy.libtcod", libtcod_module);
|
||||
}
|
||||
|
||||
//McRFPy_API::mcrf_module = m;
|
||||
return m;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,324 @@
|
|||
#include "McRFPy_Libtcod.h"
|
||||
#include "McRFPy_API.h"
|
||||
#include "UIGrid.h"
|
||||
#include <vector>
|
||||
|
||||
// Helper function to get UIGrid from Python object
|
||||
static UIGrid* get_grid_from_pyobject(PyObject* obj) {
|
||||
auto grid_type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid");
|
||||
if (!grid_type) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Could not find Grid type");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!PyObject_IsInstance(obj, (PyObject*)grid_type)) {
|
||||
Py_DECREF(grid_type);
|
||||
PyErr_SetString(PyExc_TypeError, "First argument must be a Grid object");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Py_DECREF(grid_type);
|
||||
PyUIGridObject* pygrid = (PyUIGridObject*)obj;
|
||||
return pygrid->data.get();
|
||||
}
|
||||
|
||||
// Field of View computation
|
||||
static PyObject* McRFPy_Libtcod::compute_fov(PyObject* self, PyObject* args) {
|
||||
PyObject* grid_obj;
|
||||
int x, y, radius;
|
||||
int light_walls = 1;
|
||||
int algorithm = FOV_BASIC;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "Oiii|ii", &grid_obj, &x, &y, &radius,
|
||||
&light_walls, &algorithm)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
UIGrid* grid = get_grid_from_pyobject(grid_obj);
|
||||
if (!grid) return NULL;
|
||||
|
||||
// Compute FOV using grid's method
|
||||
grid->computeFOV(x, y, radius, light_walls, (TCOD_fov_algorithm_t)algorithm);
|
||||
|
||||
// Return list of visible cells
|
||||
PyObject* visible_list = PyList_New(0);
|
||||
for (int gy = 0; gy < grid->grid_y; gy++) {
|
||||
for (int gx = 0; gx < grid->grid_x; gx++) {
|
||||
if (grid->isInFOV(gx, gy)) {
|
||||
PyObject* pos = Py_BuildValue("(ii)", gx, gy);
|
||||
PyList_Append(visible_list, pos);
|
||||
Py_DECREF(pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
if (!PyArg_ParseTuple(args, "iiii", &x1, &y1, &x2, &y2)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Use TCOD's line algorithm
|
||||
TCODLine::init(x1, y1, x2, y2);
|
||||
|
||||
PyObject* line_list = PyList_New(0);
|
||||
int x, y;
|
||||
|
||||
// Step through line
|
||||
while (!TCODLine::step(&x, &y)) {
|
||||
PyObject* pos = Py_BuildValue("(ii)", x, y);
|
||||
PyList_Append(line_list, pos);
|
||||
Py_DECREF(pos);
|
||||
}
|
||||
|
||||
return line_list;
|
||||
}
|
||||
|
||||
// Line iterator (generator-like function)
|
||||
static PyObject* McRFPy_Libtcod::line_iter(PyObject* self, PyObject* args) {
|
||||
// For simplicity, just call line() for now
|
||||
// A proper implementation would create an iterator object
|
||||
return line(self, args);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Add FOV algorithm constants to module
|
||||
static PyObject* McRFPy_Libtcod::add_fov_constants(PyObject* module) {
|
||||
// FOV algorithms
|
||||
PyModule_AddIntConstant(module, "FOV_BASIC", FOV_BASIC);
|
||||
PyModule_AddIntConstant(module, "FOV_DIAMOND", FOV_DIAMOND);
|
||||
PyModule_AddIntConstant(module, "FOV_SHADOW", FOV_SHADOW);
|
||||
PyModule_AddIntConstant(module, "FOV_PERMISSIVE_0", FOV_PERMISSIVE_0);
|
||||
PyModule_AddIntConstant(module, "FOV_PERMISSIVE_1", FOV_PERMISSIVE_1);
|
||||
PyModule_AddIntConstant(module, "FOV_PERMISSIVE_2", FOV_PERMISSIVE_2);
|
||||
PyModule_AddIntConstant(module, "FOV_PERMISSIVE_3", FOV_PERMISSIVE_3);
|
||||
PyModule_AddIntConstant(module, "FOV_PERMISSIVE_4", FOV_PERMISSIVE_4);
|
||||
PyModule_AddIntConstant(module, "FOV_PERMISSIVE_5", FOV_PERMISSIVE_5);
|
||||
PyModule_AddIntConstant(module, "FOV_PERMISSIVE_6", FOV_PERMISSIVE_6);
|
||||
PyModule_AddIntConstant(module, "FOV_PERMISSIVE_7", FOV_PERMISSIVE_7);
|
||||
PyModule_AddIntConstant(module, "FOV_PERMISSIVE_8", FOV_PERMISSIVE_8);
|
||||
PyModule_AddIntConstant(module, "FOV_RESTRICTIVE", FOV_RESTRICTIVE);
|
||||
PyModule_AddIntConstant(module, "FOV_SYMMETRIC_SHADOWCAST", FOV_SYMMETRIC_SHADOWCAST);
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
// Method definitions
|
||||
static PyMethodDef libtcodMethods[] = {
|
||||
{"compute_fov", McRFPy_Libtcod::compute_fov, METH_VARARGS,
|
||||
"compute_fov(grid, x, y, radius, light_walls=True, algorithm=FOV_BASIC)\n\n"
|
||||
"Compute field of view from a position.\n\n"
|
||||
"Args:\n"
|
||||
" grid: Grid object to compute FOV on\n"
|
||||
" x, y: Origin position\n"
|
||||
" radius: Maximum sight radius\n"
|
||||
" light_walls: Whether walls are lit when in FOV\n"
|
||||
" algorithm: FOV algorithm to use (FOV_BASIC, FOV_SHADOW, etc.)\n\n"
|
||||
"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"
|
||||
"Args:\n"
|
||||
" x1, y1: Starting position\n"
|
||||
" x2, y2: Ending position\n\n"
|
||||
"Returns:\n"
|
||||
" List of (x, y) tuples along the line"},
|
||||
|
||||
{"line_iter", McRFPy_Libtcod::line_iter, METH_VARARGS,
|
||||
"line_iter(x1, y1, x2, y2)\n\n"
|
||||
"Iterate over cells along a line.\n\n"
|
||||
"Args:\n"
|
||||
" x1, y1: Starting position\n"
|
||||
" x2, y2: Ending position\n\n"
|
||||
"Returns:\n"
|
||||
" Iterator of (x, y) tuples along the line"},
|
||||
|
||||
{"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}
|
||||
};
|
||||
|
||||
// Module definition
|
||||
static PyModuleDef libtcodModule = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"mcrfpy.libtcod",
|
||||
"TCOD-compatible algorithms for field of view, pathfinding, 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:\n"
|
||||
" FOV_BASIC - Basic circular FOV\n"
|
||||
" FOV_SHADOW - Shadow casting (recommended)\n"
|
||||
" FOV_DIAMOND - Diamond-shaped FOV\n"
|
||||
" FOV_PERMISSIVE_0 through FOV_PERMISSIVE_8 - Permissive variants\n"
|
||||
" FOV_RESTRICTIVE - Most restrictive FOV\n"
|
||||
" FOV_SYMMETRIC_SHADOWCAST - Symmetric shadow casting\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)",
|
||||
-1,
|
||||
libtcodMethods
|
||||
};
|
||||
|
||||
// Module initialization
|
||||
PyObject* McRFPy_Libtcod::init_libtcod_module() {
|
||||
PyObject* m = PyModule_Create(&libtcodModule);
|
||||
if (m == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Add FOV algorithm constants
|
||||
add_fov_constants(m);
|
||||
|
||||
return m;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
#include "Common.h"
|
||||
#include "Python.h"
|
||||
#include <libtcod.h>
|
||||
|
||||
namespace McRFPy_Libtcod
|
||||
{
|
||||
// Field of View algorithms
|
||||
static PyObject* compute_fov(PyObject* self, PyObject* args);
|
||||
|
||||
// Pathfinding
|
||||
static PyObject* find_path(PyObject* self, PyObject* args);
|
||||
static PyObject* dijkstra_new(PyObject* self, PyObject* args);
|
||||
static PyObject* dijkstra_compute(PyObject* self, PyObject* args);
|
||||
static PyObject* dijkstra_get_distance(PyObject* self, PyObject* args);
|
||||
static PyObject* dijkstra_path_to(PyObject* self, PyObject* args);
|
||||
|
||||
// Line algorithms
|
||||
static PyObject* line(PyObject* self, PyObject* args);
|
||||
static PyObject* line_iter(PyObject* self, PyObject* args);
|
||||
|
||||
// FOV algorithm constants
|
||||
static PyObject* add_fov_constants(PyObject* module);
|
||||
|
||||
// Module initialization
|
||||
PyObject* init_libtcod_module();
|
||||
}
|
|
@ -377,10 +377,68 @@ PyObject* UIEntity::die(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored))
|
|||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject* UIEntity::path_to(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
|
||||
static const char* keywords[] = {"target_x", "target_y", "x", "y", nullptr};
|
||||
int target_x = -1, target_y = -1;
|
||||
|
||||
// Parse arguments - support both target_x/target_y and x/y parameter names
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii", const_cast<char**>(keywords),
|
||||
&target_x, &target_y)) {
|
||||
PyErr_Clear();
|
||||
// Try alternative parameter names
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|iiii", const_cast<char**>(keywords),
|
||||
&target_x, &target_y, &target_x, &target_y)) {
|
||||
PyErr_SetString(PyExc_TypeError, "path_to() requires target_x and target_y integer arguments");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if entity has a grid
|
||||
if (!self->data || !self->data->grid) {
|
||||
PyErr_SetString(PyExc_ValueError, "Entity must be associated with a grid to compute paths");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Get current position
|
||||
int current_x = static_cast<int>(self->data->position.x);
|
||||
int current_y = static_cast<int>(self->data->position.y);
|
||||
|
||||
// Validate target position
|
||||
auto grid = self->data->grid;
|
||||
if (target_x < 0 || target_x >= grid->grid_x || target_y < 0 || target_y >= grid->grid_y) {
|
||||
PyErr_Format(PyExc_ValueError, "Target position (%d, %d) is out of grid bounds (0-%d, 0-%d)",
|
||||
target_x, target_y, grid->grid_x - 1, grid->grid_y - 1);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Use the grid's Dijkstra implementation
|
||||
grid->computeDijkstra(current_x, current_y);
|
||||
auto path = grid->getDijkstraPath(target_x, target_y);
|
||||
|
||||
// Convert path to Python list of tuples
|
||||
PyObject* path_list = PyList_New(path.size());
|
||||
if (!path_list) return PyErr_NoMemory();
|
||||
|
||||
for (size_t i = 0; i < path.size(); ++i) {
|
||||
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));
|
||||
PyList_SetItem(path_list, i, coord_tuple);
|
||||
}
|
||||
|
||||
return path_list;
|
||||
}
|
||||
|
||||
PyMethodDef UIEntity::methods[] = {
|
||||
{"at", (PyCFunction)UIEntity::at, METH_O},
|
||||
{"index", (PyCFunction)UIEntity::index, METH_NOARGS, "Return the index of this entity in its grid's entity collection"},
|
||||
{"die", (PyCFunction)UIEntity::die, METH_NOARGS, "Remove this entity from its grid"},
|
||||
{"path_to", (PyCFunction)UIEntity::path_to, METH_VARARGS | METH_KEYWORDS, "Find path from entity to target position using Dijkstra pathfinding"},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
|
@ -393,6 +451,7 @@ PyMethodDef UIEntity_all_methods[] = {
|
|||
{"at", (PyCFunction)UIEntity::at, METH_O},
|
||||
{"index", (PyCFunction)UIEntity::index, METH_NOARGS, "Return the index of this entity in its grid's entity collection"},
|
||||
{"die", (PyCFunction)UIEntity::die, METH_NOARGS, "Remove this entity from its grid"},
|
||||
{"path_to", (PyCFunction)UIEntity::path_to, METH_VARARGS | METH_KEYWORDS, "Find path from entity to target position using Dijkstra pathfinding"},
|
||||
{NULL} // Sentinel
|
||||
};
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ public:
|
|||
static PyObject* at(PyUIEntityObject* self, PyObject* o);
|
||||
static PyObject* index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
||||
static PyObject* die(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
||||
static PyObject* path_to(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
|
||||
static int init(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
|
||||
|
||||
static PyObject* get_position(PyUIEntityObject* self, void* closure);
|
||||
|
|
343
src/UIGrid.cpp
343
src/UIGrid.cpp
|
@ -7,7 +7,7 @@
|
|||
|
||||
UIGrid::UIGrid()
|
||||
: grid_x(0), grid_y(0), zoom(1.0f), center_x(0.0f), center_y(0.0f), ptex(nullptr),
|
||||
fill_color(8, 8, 8, 255) // Default dark gray background
|
||||
fill_color(8, 8, 8, 255), tcod_map(nullptr), tcod_dijkstra(nullptr) // Default dark gray background
|
||||
{
|
||||
// Initialize entities list
|
||||
entities = std::make_shared<std::list<std::shared_ptr<UIEntity>>>();
|
||||
|
@ -27,13 +27,14 @@ UIGrid::UIGrid()
|
|||
output.setTexture(renderTexture.getTexture());
|
||||
|
||||
// Points vector starts empty (grid_x * grid_y = 0)
|
||||
// TCOD map will be created when grid is resized
|
||||
}
|
||||
|
||||
UIGrid::UIGrid(int gx, int gy, std::shared_ptr<PyTexture> _ptex, sf::Vector2f _xy, sf::Vector2f _wh)
|
||||
: grid_x(gx), grid_y(gy),
|
||||
zoom(1.0f),
|
||||
ptex(_ptex), points(gx * gy),
|
||||
fill_color(8, 8, 8, 255) // Default dark gray background
|
||||
fill_color(8, 8, 8, 255), tcod_map(nullptr), tcod_dijkstra(nullptr) // Default dark gray background
|
||||
{
|
||||
// Use texture dimensions if available, otherwise use defaults
|
||||
int cell_width = _ptex ? _ptex->sprite_width : DEFAULT_CELL_WIDTH;
|
||||
|
@ -63,6 +64,24 @@ 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
|
||||
tcod_map = new TCODMap(gx, gy);
|
||||
|
||||
// Create TCOD dijkstra pathfinder
|
||||
tcod_dijkstra = new TCODDijkstra(tcod_map);
|
||||
|
||||
// Initialize grid points with parent reference
|
||||
for (int y = 0; y < gy; y++) {
|
||||
for (int x = 0; x < gx; x++) {
|
||||
int idx = y * gx + x;
|
||||
points[idx].grid_x = x;
|
||||
points[idx].grid_y = y;
|
||||
points[idx].parent_grid = this;
|
||||
}
|
||||
}
|
||||
|
||||
// Initial sync of TCOD map
|
||||
syncTCODMap();
|
||||
}
|
||||
|
||||
void UIGrid::update() {}
|
||||
|
@ -234,11 +253,116 @@ UIGridPoint& UIGrid::at(int x, int y)
|
|||
return points[y * grid_x + x];
|
||||
}
|
||||
|
||||
UIGrid::~UIGrid()
|
||||
{
|
||||
if (tcod_dijkstra) {
|
||||
delete tcod_dijkstra;
|
||||
tcod_dijkstra = nullptr;
|
||||
}
|
||||
if (tcod_map) {
|
||||
delete tcod_map;
|
||||
tcod_map = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
PyObjectsEnum UIGrid::derived_type()
|
||||
{
|
||||
return PyObjectsEnum::UIGRID;
|
||||
}
|
||||
|
||||
// TCOD integration methods
|
||||
void UIGrid::syncTCODMap()
|
||||
{
|
||||
if (!tcod_map) return;
|
||||
|
||||
for (int y = 0; y < grid_y; y++) {
|
||||
for (int x = 0; x < grid_x; x++) {
|
||||
const UIGridPoint& point = at(x, y);
|
||||
tcod_map->setProperties(x, y, point.transparent, point.walkable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UIGrid::syncTCODMapCell(int x, int y)
|
||||
{
|
||||
if (!tcod_map || x < 0 || x >= grid_x || y < 0 || y >= grid_y) return;
|
||||
|
||||
const UIGridPoint& point = at(x, y);
|
||||
tcod_map->setProperties(x, y, point.transparent, point.walkable);
|
||||
}
|
||||
|
||||
void UIGrid::computeFOV(int x, int y, int radius, bool light_walls, TCOD_fov_algorithm_t algo)
|
||||
{
|
||||
if (!tcod_map || x < 0 || x >= grid_x || y < 0 || y >= grid_y) return;
|
||||
|
||||
tcod_map->computeFov(x, y, radius, light_walls, algo);
|
||||
}
|
||||
|
||||
bool UIGrid::isInFOV(int x, int y) const
|
||||
{
|
||||
if (!tcod_map || x < 0 || x >= grid_x || y < 0 || y >= grid_y) return false;
|
||||
|
||||
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_x || y1 < 0 || y1 >= grid_y ||
|
||||
x2 < 0 || x2 >= grid_x || y2 < 0 || y2 >= grid_y) {
|
||||
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_x || rootY < 0 || rootY >= grid_y) 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_x || y < 0 || y >= grid_y) {
|
||||
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_x || y < 0 || y >= grid_y) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Phase 1 implementations
|
||||
sf::FloatRect UIGrid::get_bounds() const
|
||||
{
|
||||
|
@ -338,35 +462,53 @@ UIDrawable* UIGrid::click_at(sf::Vector2f point)
|
|||
|
||||
|
||||
int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||
// Try parsing with PyArgHelpers
|
||||
int arg_idx = 0;
|
||||
auto grid_size_result = PyArgHelpers::parseGridSize(args, kwds, &arg_idx);
|
||||
auto pos_result = PyArgHelpers::parsePosition(args, kwds, &arg_idx);
|
||||
auto size_result = PyArgHelpers::parseSize(args, kwds, &arg_idx);
|
||||
|
||||
// Default values
|
||||
int grid_x = 0, grid_y = 0;
|
||||
float x = 0.0f, y = 0.0f, w = 0.0f, h = 0.0f;
|
||||
PyObject* textureObj = nullptr;
|
||||
|
||||
// Case 1: Got grid size and position from helpers (tuple format)
|
||||
if (grid_size_result.valid) {
|
||||
// Check if first argument is a tuple (for tuple-based initialization)
|
||||
bool has_tuple_first_arg = false;
|
||||
if (args && PyTuple_Size(args) > 0) {
|
||||
PyObject* first_arg = PyTuple_GetItem(args, 0);
|
||||
if (PyTuple_Check(first_arg)) {
|
||||
has_tuple_first_arg = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Try tuple-based parsing if we have a tuple as first argument
|
||||
if (has_tuple_first_arg) {
|
||||
int arg_idx = 0;
|
||||
auto grid_size_result = PyArgHelpers::parseGridSize(args, kwds, &arg_idx);
|
||||
|
||||
// If grid size parsing failed with an error, report it
|
||||
if (!grid_size_result.valid) {
|
||||
if (grid_size_result.error) {
|
||||
PyErr_SetString(PyExc_TypeError, grid_size_result.error);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "Invalid grid size tuple");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We got a valid grid size
|
||||
grid_x = grid_size_result.grid_w;
|
||||
grid_y = grid_size_result.grid_h;
|
||||
|
||||
// Set position if we got it
|
||||
// Try to parse position and size
|
||||
auto pos_result = PyArgHelpers::parsePosition(args, kwds, &arg_idx);
|
||||
if (pos_result.valid) {
|
||||
x = pos_result.x;
|
||||
y = pos_result.y;
|
||||
}
|
||||
|
||||
// Set size if we got it, otherwise calculate default
|
||||
auto size_result = PyArgHelpers::parseSize(args, kwds, &arg_idx);
|
||||
if (size_result.valid) {
|
||||
w = size_result.w;
|
||||
h = size_result.h;
|
||||
} else {
|
||||
// Default size based on grid dimensions and texture
|
||||
w = grid_x * 16.0f; // Will be recalculated if texture provided
|
||||
// Default size based on grid dimensions
|
||||
w = grid_x * 16.0f;
|
||||
h = grid_y * 16.0f;
|
||||
}
|
||||
|
||||
|
@ -380,10 +522,8 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
|||
&textureObj);
|
||||
Py_DECREF(remaining_args);
|
||||
}
|
||||
// Case 2: Traditional format
|
||||
// Traditional format parsing
|
||||
else {
|
||||
PyErr_Clear(); // Clear any errors from helpers
|
||||
|
||||
static const char* keywords[] = {
|
||||
"grid_x", "grid_y", "texture", "pos", "size", "grid_size", nullptr
|
||||
};
|
||||
|
@ -406,7 +546,13 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
|||
if (PyLong_Check(x_obj) && PyLong_Check(y_obj)) {
|
||||
grid_x = PyLong_AsLong(x_obj);
|
||||
grid_y = PyLong_AsLong(y_obj);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "grid_size must contain integers");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "grid_size must be a tuple of two integers");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -419,7 +565,13 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
|||
(PyFloat_Check(y_val) || PyLong_Check(y_val))) {
|
||||
x = PyFloat_Check(x_val) ? PyFloat_AsDouble(x_val) : PyLong_AsLong(x_val);
|
||||
y = PyFloat_Check(y_val) ? PyFloat_AsDouble(y_val) : PyLong_AsLong(y_val);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "pos must contain numbers");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "pos must be a tuple of two numbers");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -432,7 +584,13 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
|||
(PyFloat_Check(h_val) || PyLong_Check(h_val))) {
|
||||
w = PyFloat_Check(w_val) ? PyFloat_AsDouble(w_val) : PyLong_AsLong(w_val);
|
||||
h = PyFloat_Check(h_val) ? PyFloat_AsDouble(h_val) : PyLong_AsLong(h_val);
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "size must contain numbers");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
PyErr_SetString(PyExc_TypeError, "size must be a tuple of two numbers");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
// Default size based on grid
|
||||
|
@ -440,17 +598,20 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
|||
h = grid_y * 16.0f;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate grid dimensions
|
||||
if (grid_x <= 0 || grid_y <= 0) {
|
||||
PyErr_SetString(PyExc_ValueError, "Grid dimensions must be positive integers");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// At this point we have x, y, w, h values from either parsing method
|
||||
|
||||
// Convert PyObject texture to IndexTexture*
|
||||
// This requires the texture object to have been initialized similar to UISprite's texture handling
|
||||
|
||||
// Convert PyObject texture to shared_ptr<PyTexture>
|
||||
std::shared_ptr<PyTexture> texture_ptr = nullptr;
|
||||
|
||||
// Allow None for texture - use default texture in that case
|
||||
if (textureObj != Py_None) {
|
||||
//if (!PyObject_IsInstance(textureObj, (PyObject*)&PyTextureType)) {
|
||||
// Allow None or NULL for texture - use default texture in that case
|
||||
if (textureObj && textureObj != Py_None) {
|
||||
if (!PyObject_IsInstance(textureObj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) {
|
||||
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance or None");
|
||||
return -1;
|
||||
|
@ -458,16 +619,12 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
|||
PyTextureObject* pyTexture = reinterpret_cast<PyTextureObject*>(textureObj);
|
||||
texture_ptr = pyTexture->data;
|
||||
} else {
|
||||
// Use default texture when None is provided
|
||||
// Use default texture when None is provided or texture not specified
|
||||
texture_ptr = McRFPy_API::default_texture;
|
||||
}
|
||||
|
||||
// Initialize UIGrid - texture_ptr will be nullptr if texture was None
|
||||
//self->data = new UIGrid(grid_x, grid_y, texture, sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h));
|
||||
//self->data = std::make_shared<UIGrid>(grid_x, grid_y, pyTexture->data,
|
||||
// sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h));
|
||||
// Adjust size based on texture if available and size not explicitly set
|
||||
if (!size_result.valid && texture_ptr) {
|
||||
if (texture_ptr && w == grid_x * 16.0f && h == grid_y * 16.0f) {
|
||||
w = grid_x * texture_ptr->sprite_width;
|
||||
h = grid_y * texture_ptr->sprite_height;
|
||||
}
|
||||
|
@ -719,8 +876,124 @@ int UIGrid::set_fill_color(PyUIGridObject* self, PyObject* value, void* closure)
|
|||
return 0;
|
||||
}
|
||||
|
||||
// Python API implementations for TCOD functionality
|
||||
PyObject* UIGrid::py_compute_fov(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
static char* kwlist[] = {"x", "y", "radius", "light_walls", "algorithm", NULL};
|
||||
int x, y, radius = 0;
|
||||
int light_walls = 1;
|
||||
int algorithm = FOV_BASIC;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii|ipi", kwlist,
|
||||
&x, &y, &radius, &light_walls, &algorithm)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->data->computeFOV(x, y, radius, light_walls, (TCOD_fov_algorithm_t)algorithm);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject* UIGrid::py_is_in_fov(PyUIGridObject* self, PyObject* args)
|
||||
{
|
||||
int x, y;
|
||||
if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool in_fov = self->data->isInFOV(x, y);
|
||||
return PyBool_FromLong(in_fov);
|
||||
}
|
||||
|
||||
PyObject* UIGrid::py_find_path(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
static char* kwlist[] = {"x1", "y1", "x2", "y2", "diagonal_cost", NULL};
|
||||
int x1, y1, x2, y2;
|
||||
float diagonal_cost = 1.41f;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "iiii|f", kwlist,
|
||||
&x1, &y1, &x2, &y2, &diagonal_cost)) {
|
||||
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 char* kwlist[] = {"root_x", "root_y", "diagonal_cost", NULL};
|
||||
int root_x, root_y;
|
||||
float diagonal_cost = 1.41f;
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "ii|f", kwlist,
|
||||
&root_x, &root_y, &diagonal_cost)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
self->data->computeDijkstra(root_x, root_y, diagonal_cost);
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyObject* UIGrid::py_get_dijkstra_distance(PyUIGridObject* self, PyObject* args)
|
||||
{
|
||||
int x, y;
|
||||
if (!PyArg_ParseTuple(args, "ii", &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)
|
||||
{
|
||||
int x, y;
|
||||
if (!PyArg_ParseTuple(args, "ii", &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;
|
||||
}
|
||||
|
||||
PyMethodDef UIGrid::methods[] = {
|
||||
{"at", (PyCFunction)UIGrid::py_at, METH_VARARGS | METH_KEYWORDS},
|
||||
{"compute_fov", (PyCFunction)UIGrid::py_compute_fov, METH_VARARGS | METH_KEYWORDS,
|
||||
"Compute field of view from a position. Args: x, y, radius=0, light_walls=True, algorithm=FOV_BASIC"},
|
||||
{"is_in_fov", (PyCFunction)UIGrid::py_is_in_fov, METH_VARARGS,
|
||||
"Check if a cell is in the field of view. Args: x, y"},
|
||||
{"find_path", (PyCFunction)UIGrid::py_find_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"Find A* path between two points. Args: x1, y1, x2, y2, diagonal_cost=1.41"},
|
||||
{"compute_dijkstra", (PyCFunction)UIGrid::py_compute_dijkstra, METH_VARARGS | METH_KEYWORDS,
|
||||
"Compute Dijkstra map from root position. Args: root_x, root_y, diagonal_cost=1.41"},
|
||||
{"get_dijkstra_distance", (PyCFunction)UIGrid::py_get_dijkstra_distance, METH_VARARGS,
|
||||
"Get distance from Dijkstra root to position. Args: x, y. Returns float or None if invalid."},
|
||||
{"get_dijkstra_path", (PyCFunction)UIGrid::py_get_dijkstra_path, METH_VARARGS,
|
||||
"Get path from position to Dijkstra root. Args: x, y. Returns list of (x,y) tuples."},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
|
@ -731,6 +1004,18 @@ typedef PyUIGridObject PyObjectType;
|
|||
PyMethodDef UIGrid_all_methods[] = {
|
||||
UIDRAWABLE_METHODS,
|
||||
{"at", (PyCFunction)UIGrid::py_at, METH_VARARGS | METH_KEYWORDS},
|
||||
{"compute_fov", (PyCFunction)UIGrid::py_compute_fov, METH_VARARGS | METH_KEYWORDS,
|
||||
"Compute field of view from a position. Args: x, y, radius=0, light_walls=True, algorithm=FOV_BASIC"},
|
||||
{"is_in_fov", (PyCFunction)UIGrid::py_is_in_fov, METH_VARARGS,
|
||||
"Check if a cell is in the field of view. Args: x, y"},
|
||||
{"find_path", (PyCFunction)UIGrid::py_find_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"Find A* path between two points. Args: x1, y1, x2, y2, diagonal_cost=1.41"},
|
||||
{"compute_dijkstra", (PyCFunction)UIGrid::py_compute_dijkstra, METH_VARARGS | METH_KEYWORDS,
|
||||
"Compute Dijkstra map from root position. Args: root_x, root_y, diagonal_cost=1.41"},
|
||||
{"get_dijkstra_distance", (PyCFunction)UIGrid::py_get_dijkstra_distance, METH_VARARGS,
|
||||
"Get distance from Dijkstra root to position. Args: x, y. Returns float or None if invalid."},
|
||||
{"get_dijkstra_path", (PyCFunction)UIGrid::py_get_dijkstra_path, METH_VARARGS,
|
||||
"Get path from position to Dijkstra root. Args: x, y. Returns list of (x,y) tuples."},
|
||||
{NULL} // Sentinel
|
||||
};
|
||||
|
||||
|
|
23
src/UIGrid.h
23
src/UIGrid.h
|
@ -5,6 +5,7 @@
|
|||
#include "IndexTexture.h"
|
||||
#include "Resources.h"
|
||||
#include <list>
|
||||
#include <libtcod.h>
|
||||
|
||||
#include "PyCallable.h"
|
||||
#include "PyTexture.h"
|
||||
|
@ -25,10 +26,14 @@ private:
|
|||
// Default cell dimensions when no texture is provided
|
||||
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
|
||||
|
||||
public:
|
||||
UIGrid();
|
||||
//UIGrid(int, int, IndexTexture*, float, float, float, float);
|
||||
UIGrid(int, int, std::shared_ptr<PyTexture>, sf::Vector2f, sf::Vector2f);
|
||||
~UIGrid(); // Destructor to clean up TCOD map
|
||||
void update();
|
||||
void render(sf::Vector2f, sf::RenderTarget&) override final;
|
||||
UIGridPoint& at(int, int);
|
||||
|
@ -36,6 +41,18 @@ public:
|
|||
//void setSprite(int);
|
||||
virtual UIDrawable* click_at(sf::Vector2f point) override final;
|
||||
|
||||
// TCOD integration methods
|
||||
void syncTCODMap(); // Sync entire map with current grid state
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
// Phase 1 virtual method implementations
|
||||
sf::FloatRect get_bounds() const override;
|
||||
void move(float dx, float dy) override;
|
||||
|
@ -78,6 +95,12 @@ public:
|
|||
static PyObject* get_fill_color(PyUIGridObject* self, void* closure);
|
||||
static int set_fill_color(PyUIGridObject* self, PyObject* value, void* closure);
|
||||
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);
|
||||
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);
|
||||
static PyObject* py_get_dijkstra_path(PyUIGridObject* self, PyObject* args);
|
||||
static PyMethodDef methods[];
|
||||
static PyGetSetDef getsetters[];
|
||||
static PyObject* get_children(PyUIGridObject* self, void* closure);
|
||||
|
|
|
@ -1,19 +1,51 @@
|
|||
#include "UIGridPoint.h"
|
||||
#include "UIGrid.h"
|
||||
|
||||
UIGridPoint::UIGridPoint()
|
||||
: color(1.0f, 1.0f, 1.0f), color_overlay(0.0f, 0.0f, 0.0f), walkable(false), transparent(false),
|
||||
tilesprite(-1), tile_overlay(-1), uisprite(-1)
|
||||
tilesprite(-1), tile_overlay(-1), uisprite(-1), grid_x(-1), grid_y(-1), parent_grid(nullptr)
|
||||
{}
|
||||
|
||||
// Utility function to convert sf::Color to PyObject*
|
||||
PyObject* sfColor_to_PyObject(sf::Color color) {
|
||||
// For now, keep returning tuples to avoid breaking existing code
|
||||
return Py_BuildValue("(iiii)", color.r, color.g, color.b, color.a);
|
||||
}
|
||||
|
||||
// Utility function to convert PyObject* to sf::Color
|
||||
sf::Color PyObject_to_sfColor(PyObject* obj) {
|
||||
// Get the mcrfpy module and Color type
|
||||
PyObject* module = PyImport_ImportModule("mcrfpy");
|
||||
if (!module) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to import mcrfpy module");
|
||||
return sf::Color();
|
||||
}
|
||||
|
||||
PyObject* color_type = PyObject_GetAttrString(module, "Color");
|
||||
Py_DECREF(module);
|
||||
|
||||
if (!color_type) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "Failed to get Color type from mcrfpy module");
|
||||
return sf::Color();
|
||||
}
|
||||
|
||||
// Check if it's a mcrfpy.Color object
|
||||
int is_color = PyObject_IsInstance(obj, color_type);
|
||||
Py_DECREF(color_type);
|
||||
|
||||
if (is_color == 1) {
|
||||
PyColorObject* color_obj = (PyColorObject*)obj;
|
||||
return color_obj->data;
|
||||
} else if (is_color == -1) {
|
||||
// Error occurred in PyObject_IsInstance
|
||||
return sf::Color();
|
||||
}
|
||||
|
||||
// Otherwise try to parse as tuple
|
||||
int r, g, b, a = 255; // Default alpha to fully opaque if not specified
|
||||
if (!PyArg_ParseTuple(obj, "iii|i", &r, &g, &b, &a)) {
|
||||
PyErr_Clear(); // Clear the error from failed tuple parsing
|
||||
PyErr_SetString(PyExc_TypeError, "color must be a Color object or a tuple of (r, g, b[, a])");
|
||||
return sf::Color(); // Return default color on parse error
|
||||
}
|
||||
return sf::Color(r, g, b, a);
|
||||
|
@ -29,6 +61,11 @@ PyObject* UIGridPoint::get_color(PyUIGridPointObject* self, void* closure) {
|
|||
|
||||
int UIGridPoint::set_color(PyUIGridPointObject* self, PyObject* value, void* closure) {
|
||||
sf::Color color = PyObject_to_sfColor(value);
|
||||
// Check if an error occurred during conversion
|
||||
if (PyErr_Occurred()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (reinterpret_cast<long>(closure) == 0) { // color
|
||||
self->data->color = color;
|
||||
} else { // color_overlay
|
||||
|
@ -62,6 +99,12 @@ int UIGridPoint::set_bool_member(PyUIGridPointObject* self, PyObject* value, voi
|
|||
PyErr_SetString(PyExc_ValueError, "Expected a boolean value");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Sync with TCOD map if parent grid exists
|
||||
if (self->data->parent_grid && self->data->grid_x >= 0 && self->data->grid_y >= 0) {
|
||||
self->data->parent_grid->syncTCODMapCell(self->data->grid_x, self->data->grid_y);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -40,6 +40,8 @@ public:
|
|||
sf::Color color, color_overlay;
|
||||
bool walkable, transparent;
|
||||
int tilesprite, tile_overlay, uisprite;
|
||||
int grid_x, grid_y; // Position in parent grid
|
||||
UIGrid* parent_grid; // Parent grid reference for TCOD sync
|
||||
UIGridPoint();
|
||||
|
||||
static int set_int_member(PyUIGridPointObject* self, PyObject* value, void* closure);
|
||||
|
|
Loading…
Reference in New Issue