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_API.h"
|
||||||
#include "McRFPy_Automation.h"
|
#include "McRFPy_Automation.h"
|
||||||
|
#include "McRFPy_Libtcod.h"
|
||||||
#include "platform.h"
|
#include "platform.h"
|
||||||
#include "PyAnimation.h"
|
#include "PyAnimation.h"
|
||||||
#include "PyDrawable.h"
|
#include "PyDrawable.h"
|
||||||
|
@ -12,6 +13,7 @@
|
||||||
#include "PyScene.h"
|
#include "PyScene.h"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <libtcod.h>
|
||||||
|
|
||||||
std::vector<sf::SoundBuffer>* McRFPy_API::soundbuffers = nullptr;
|
std::vector<sf::SoundBuffer>* McRFPy_API::soundbuffers = nullptr;
|
||||||
sf::Music* McRFPy_API::music = 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_font", Py_None);
|
||||||
PyModule_AddObject(m, "default_texture", 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
|
// Add automation submodule
|
||||||
PyObject* automation_module = McRFPy_Automation::init_automation_module();
|
PyObject* automation_module = McRFPy_Automation::init_automation_module();
|
||||||
if (automation_module != NULL) {
|
if (automation_module != NULL) {
|
||||||
|
@ -297,6 +314,16 @@ PyObject* PyInit_mcrfpy()
|
||||||
PyDict_SetItemString(sys_modules, "mcrfpy.automation", automation_module);
|
PyDict_SetItemString(sys_modules, "mcrfpy.automation", automation_module);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add libtcod submodule
|
||||||
|
PyObject* libtcod_module = McRFPy_Libtcod::init_libtcod_module();
|
||||||
|
if (libtcod_module != NULL) {
|
||||||
|
PyModule_AddObject(m, "libtcod", libtcod_module);
|
||||||
|
|
||||||
|
// Also add to sys.modules for proper import behavior
|
||||||
|
PyObject* sys_modules = PyImport_GetModuleDict();
|
||||||
|
PyDict_SetItemString(sys_modules, "mcrfpy.libtcod", libtcod_module);
|
||||||
|
}
|
||||||
|
|
||||||
//McRFPy_API::mcrf_module = m;
|
//McRFPy_API::mcrf_module = m;
|
||||||
return 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;
|
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[] = {
|
PyMethodDef UIEntity::methods[] = {
|
||||||
{"at", (PyCFunction)UIEntity::at, METH_O},
|
{"at", (PyCFunction)UIEntity::at, METH_O},
|
||||||
{"index", (PyCFunction)UIEntity::index, METH_NOARGS, "Return the index of this entity in its grid's entity collection"},
|
{"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"},
|
{"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}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -393,6 +451,7 @@ PyMethodDef UIEntity_all_methods[] = {
|
||||||
{"at", (PyCFunction)UIEntity::at, METH_O},
|
{"at", (PyCFunction)UIEntity::at, METH_O},
|
||||||
{"index", (PyCFunction)UIEntity::index, METH_NOARGS, "Return the index of this entity in its grid's entity collection"},
|
{"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"},
|
{"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
|
{NULL} // Sentinel
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,7 @@ public:
|
||||||
static PyObject* at(PyUIEntityObject* self, PyObject* o);
|
static PyObject* at(PyUIEntityObject* self, PyObject* o);
|
||||||
static PyObject* index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
static PyObject* index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
||||||
static PyObject* die(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 int init(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
|
||||||
|
|
||||||
static PyObject* get_position(PyUIEntityObject* self, void* closure);
|
static PyObject* get_position(PyUIEntityObject* self, void* closure);
|
||||||
|
|
343
src/UIGrid.cpp
343
src/UIGrid.cpp
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
UIGrid::UIGrid()
|
UIGrid::UIGrid()
|
||||||
: grid_x(0), grid_y(0), zoom(1.0f), center_x(0.0f), center_y(0.0f), ptex(nullptr),
|
: 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
|
// Initialize entities list
|
||||||
entities = std::make_shared<std::list<std::shared_ptr<UIEntity>>>();
|
entities = std::make_shared<std::list<std::shared_ptr<UIEntity>>>();
|
||||||
|
@ -27,13 +27,14 @@ UIGrid::UIGrid()
|
||||||
output.setTexture(renderTexture.getTexture());
|
output.setTexture(renderTexture.getTexture());
|
||||||
|
|
||||||
// Points vector starts empty (grid_x * grid_y = 0)
|
// 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)
|
UIGrid::UIGrid(int gx, int gy, std::shared_ptr<PyTexture> _ptex, sf::Vector2f _xy, sf::Vector2f _wh)
|
||||||
: grid_x(gx), grid_y(gy),
|
: grid_x(gx), grid_y(gy),
|
||||||
zoom(1.0f),
|
zoom(1.0f),
|
||||||
ptex(_ptex), points(gx * gy),
|
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
|
// Use texture dimensions if available, otherwise use defaults
|
||||||
int cell_width = _ptex ? _ptex->sprite_width : DEFAULT_CELL_WIDTH;
|
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
|
// textures are upside-down inside renderTexture
|
||||||
output.setTexture(renderTexture.getTexture());
|
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() {}
|
void UIGrid::update() {}
|
||||||
|
@ -234,11 +253,116 @@ UIGridPoint& UIGrid::at(int x, int y)
|
||||||
return points[y * grid_x + x];
|
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()
|
PyObjectsEnum UIGrid::derived_type()
|
||||||
{
|
{
|
||||||
return PyObjectsEnum::UIGRID;
|
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
|
// Phase 1 implementations
|
||||||
sf::FloatRect UIGrid::get_bounds() const
|
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) {
|
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
|
// Default values
|
||||||
int grid_x = 0, grid_y = 0;
|
int grid_x = 0, grid_y = 0;
|
||||||
float x = 0.0f, y = 0.0f, w = 0.0f, h = 0.0f;
|
float x = 0.0f, y = 0.0f, w = 0.0f, h = 0.0f;
|
||||||
PyObject* textureObj = nullptr;
|
PyObject* textureObj = nullptr;
|
||||||
|
|
||||||
// Case 1: Got grid size and position from helpers (tuple format)
|
// Check if first argument is a tuple (for tuple-based initialization)
|
||||||
if (grid_size_result.valid) {
|
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_x = grid_size_result.grid_w;
|
||||||
grid_y = grid_size_result.grid_h;
|
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) {
|
if (pos_result.valid) {
|
||||||
x = pos_result.x;
|
x = pos_result.x;
|
||||||
y = pos_result.y;
|
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) {
|
if (size_result.valid) {
|
||||||
w = size_result.w;
|
w = size_result.w;
|
||||||
h = size_result.h;
|
h = size_result.h;
|
||||||
} else {
|
} else {
|
||||||
// Default size based on grid dimensions and texture
|
// Default size based on grid dimensions
|
||||||
w = grid_x * 16.0f; // Will be recalculated if texture provided
|
w = grid_x * 16.0f;
|
||||||
h = grid_y * 16.0f;
|
h = grid_y * 16.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -380,10 +522,8 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
&textureObj);
|
&textureObj);
|
||||||
Py_DECREF(remaining_args);
|
Py_DECREF(remaining_args);
|
||||||
}
|
}
|
||||||
// Case 2: Traditional format
|
// Traditional format parsing
|
||||||
else {
|
else {
|
||||||
PyErr_Clear(); // Clear any errors from helpers
|
|
||||||
|
|
||||||
static const char* keywords[] = {
|
static const char* keywords[] = {
|
||||||
"grid_x", "grid_y", "texture", "pos", "size", "grid_size", nullptr
|
"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)) {
|
if (PyLong_Check(x_obj) && PyLong_Check(y_obj)) {
|
||||||
grid_x = PyLong_AsLong(x_obj);
|
grid_x = PyLong_AsLong(x_obj);
|
||||||
grid_y = PyLong_AsLong(y_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))) {
|
(PyFloat_Check(y_val) || PyLong_Check(y_val))) {
|
||||||
x = PyFloat_Check(x_val) ? PyFloat_AsDouble(x_val) : PyLong_AsLong(x_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);
|
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))) {
|
(PyFloat_Check(h_val) || PyLong_Check(h_val))) {
|
||||||
w = PyFloat_Check(w_val) ? PyFloat_AsDouble(w_val) : PyLong_AsLong(w_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);
|
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 {
|
} else {
|
||||||
// Default size based on grid
|
// Default size based on grid
|
||||||
|
@ -441,16 +599,19 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// At this point we have x, y, w, h values from either parsing method
|
||||||
|
|
||||||
// Convert PyObject texture to IndexTexture*
|
// Convert PyObject texture to shared_ptr<PyTexture>
|
||||||
// This requires the texture object to have been initialized similar to UISprite's texture handling
|
|
||||||
|
|
||||||
std::shared_ptr<PyTexture> texture_ptr = nullptr;
|
std::shared_ptr<PyTexture> texture_ptr = nullptr;
|
||||||
|
|
||||||
// Allow None for texture - use default texture in that case
|
// Allow None or NULL for texture - use default texture in that case
|
||||||
if (textureObj != Py_None) {
|
if (textureObj && textureObj != Py_None) {
|
||||||
//if (!PyObject_IsInstance(textureObj, (PyObject*)&PyTextureType)) {
|
|
||||||
if (!PyObject_IsInstance(textureObj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) {
|
if (!PyObject_IsInstance(textureObj, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) {
|
||||||
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance or None");
|
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance or None");
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -458,16 +619,12 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
PyTextureObject* pyTexture = reinterpret_cast<PyTextureObject*>(textureObj);
|
PyTextureObject* pyTexture = reinterpret_cast<PyTextureObject*>(textureObj);
|
||||||
texture_ptr = pyTexture->data;
|
texture_ptr = pyTexture->data;
|
||||||
} else {
|
} 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;
|
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
|
// 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;
|
w = grid_x * texture_ptr->sprite_width;
|
||||||
h = grid_y * texture_ptr->sprite_height;
|
h = grid_y * texture_ptr->sprite_height;
|
||||||
}
|
}
|
||||||
|
@ -719,8 +876,124 @@ int UIGrid::set_fill_color(PyUIGridObject* self, PyObject* value, void* closure)
|
||||||
return 0;
|
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[] = {
|
PyMethodDef UIGrid::methods[] = {
|
||||||
{"at", (PyCFunction)UIGrid::py_at, METH_VARARGS | METH_KEYWORDS},
|
{"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}
|
{NULL, NULL, 0, NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -731,6 +1004,18 @@ typedef PyUIGridObject PyObjectType;
|
||||||
PyMethodDef UIGrid_all_methods[] = {
|
PyMethodDef UIGrid_all_methods[] = {
|
||||||
UIDRAWABLE_METHODS,
|
UIDRAWABLE_METHODS,
|
||||||
{"at", (PyCFunction)UIGrid::py_at, METH_VARARGS | METH_KEYWORDS},
|
{"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
|
{NULL} // Sentinel
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
23
src/UIGrid.h
23
src/UIGrid.h
|
@ -5,6 +5,7 @@
|
||||||
#include "IndexTexture.h"
|
#include "IndexTexture.h"
|
||||||
#include "Resources.h"
|
#include "Resources.h"
|
||||||
#include <list>
|
#include <list>
|
||||||
|
#include <libtcod.h>
|
||||||
|
|
||||||
#include "PyCallable.h"
|
#include "PyCallable.h"
|
||||||
#include "PyTexture.h"
|
#include "PyTexture.h"
|
||||||
|
@ -25,10 +26,14 @@ private:
|
||||||
// Default cell dimensions when no texture is provided
|
// Default cell dimensions when no texture is provided
|
||||||
static constexpr int DEFAULT_CELL_WIDTH = 16;
|
static constexpr int DEFAULT_CELL_WIDTH = 16;
|
||||||
static constexpr int DEFAULT_CELL_HEIGHT = 16;
|
static constexpr int DEFAULT_CELL_HEIGHT = 16;
|
||||||
|
TCODMap* tcod_map; // TCOD map for FOV and pathfinding
|
||||||
|
TCODDijkstra* tcod_dijkstra; // Dijkstra pathfinding
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UIGrid();
|
UIGrid();
|
||||||
//UIGrid(int, int, IndexTexture*, float, float, float, float);
|
//UIGrid(int, int, IndexTexture*, float, float, float, float);
|
||||||
UIGrid(int, int, std::shared_ptr<PyTexture>, sf::Vector2f, sf::Vector2f);
|
UIGrid(int, int, std::shared_ptr<PyTexture>, sf::Vector2f, sf::Vector2f);
|
||||||
|
~UIGrid(); // Destructor to clean up TCOD map
|
||||||
void update();
|
void update();
|
||||||
void render(sf::Vector2f, sf::RenderTarget&) override final;
|
void render(sf::Vector2f, sf::RenderTarget&) override final;
|
||||||
UIGridPoint& at(int, int);
|
UIGridPoint& at(int, int);
|
||||||
|
@ -36,6 +41,18 @@ public:
|
||||||
//void setSprite(int);
|
//void setSprite(int);
|
||||||
virtual UIDrawable* click_at(sf::Vector2f point) override final;
|
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
|
// Phase 1 virtual method implementations
|
||||||
sf::FloatRect get_bounds() const override;
|
sf::FloatRect get_bounds() const override;
|
||||||
void move(float dx, float dy) override;
|
void move(float dx, float dy) override;
|
||||||
|
@ -78,6 +95,12 @@ public:
|
||||||
static PyObject* get_fill_color(PyUIGridObject* self, void* closure);
|
static PyObject* get_fill_color(PyUIGridObject* self, void* closure);
|
||||||
static int set_fill_color(PyUIGridObject* self, PyObject* value, 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_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 PyMethodDef methods[];
|
||||||
static PyGetSetDef getsetters[];
|
static PyGetSetDef getsetters[];
|
||||||
static PyObject* get_children(PyUIGridObject* self, void* closure);
|
static PyObject* get_children(PyUIGridObject* self, void* closure);
|
||||||
|
|
|
@ -1,19 +1,51 @@
|
||||||
#include "UIGridPoint.h"
|
#include "UIGridPoint.h"
|
||||||
|
#include "UIGrid.h"
|
||||||
|
|
||||||
UIGridPoint::UIGridPoint()
|
UIGridPoint::UIGridPoint()
|
||||||
: color(1.0f, 1.0f, 1.0f), color_overlay(0.0f, 0.0f, 0.0f), walkable(false), transparent(false),
|
: 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*
|
// Utility function to convert sf::Color to PyObject*
|
||||||
PyObject* sfColor_to_PyObject(sf::Color color) {
|
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);
|
return Py_BuildValue("(iiii)", color.r, color.g, color.b, color.a);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility function to convert PyObject* to sf::Color
|
// Utility function to convert PyObject* to sf::Color
|
||||||
sf::Color PyObject_to_sfColor(PyObject* obj) {
|
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
|
int r, g, b, a = 255; // Default alpha to fully opaque if not specified
|
||||||
if (!PyArg_ParseTuple(obj, "iii|i", &r, &g, &b, &a)) {
|
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(); // Return default color on parse error
|
||||||
}
|
}
|
||||||
return sf::Color(r, g, b, a);
|
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) {
|
int UIGridPoint::set_color(PyUIGridPointObject* self, PyObject* value, void* closure) {
|
||||||
sf::Color color = PyObject_to_sfColor(value);
|
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
|
if (reinterpret_cast<long>(closure) == 0) { // color
|
||||||
self->data->color = color;
|
self->data->color = color;
|
||||||
} else { // color_overlay
|
} 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");
|
PyErr_SetString(PyExc_ValueError, "Expected a boolean value");
|
||||||
return -1;
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,8 @@ public:
|
||||||
sf::Color color, color_overlay;
|
sf::Color color, color_overlay;
|
||||||
bool walkable, transparent;
|
bool walkable, transparent;
|
||||||
int tilesprite, tile_overlay, uisprite;
|
int tilesprite, tile_overlay, uisprite;
|
||||||
|
int grid_x, grid_y; // Position in parent grid
|
||||||
|
UIGrid* parent_grid; // Parent grid reference for TCOD sync
|
||||||
UIGridPoint();
|
UIGridPoint();
|
||||||
|
|
||||||
static int set_int_member(PyUIGridPointObject* self, PyObject* value, void* closure);
|
static int set_int_member(PyUIGridPointObject* self, PyObject* value, void* closure);
|
||||||
|
|
Loading…
Reference in New Issue