McRogueFace/docs/visibility_tracking_example...

342 lines
11 KiB
C++

/**
* 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)
*/