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
11 changed files with 1163 additions and 30 deletions
342
docs/visibility_tracking_example.cpp
Normal file
342
docs/visibility_tracking_example.cpp
Normal file
|
|
@ -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)
|
||||
*/
|
||||
Loading…
Add table
Add a link
Reference in a new issue