Compare commits
No commits in common. "64ffe1f6999c44414a6429abbeb9e5c0d63551c2" and "192d1ae1dd1445c1795637cc0d3adea88b9cdc2b" have entirely different histories.
64ffe1f699
...
192d1ae1dd
|
@ -9,7 +9,6 @@ obj
|
|||
build
|
||||
lib
|
||||
obj
|
||||
__pycache__
|
||||
|
||||
.cache/
|
||||
7DRL2025 Release/
|
||||
|
@ -28,5 +27,3 @@ forest_fire_CA.py
|
|||
mcrogueface.github.io
|
||||
scripts/
|
||||
test_*
|
||||
|
||||
tcod_reference
|
||||
|
|
61
ROADMAP.md
61
ROADMAP.md
|
@ -22,19 +22,16 @@
|
|||
- Mass entity choreography (100+ entities)
|
||||
- Performance stress test with 1000+ entities
|
||||
|
||||
#### 2. TCOD Integration Sprint ✅ COMPLETE!
|
||||
- [x] **UIGrid TCOD Integration** (8 hours) ✅ COMPLETED!
|
||||
- ✅ Add TCODMap* to UIGrid constructor with proper lifecycle
|
||||
- ✅ Implement complete Dijkstra pathfinding system
|
||||
- ✅ Create mcrfpy.libtcod submodule with Python bindings
|
||||
- ✅ Fix critical PyArg bug preventing Color object assignments
|
||||
- ✅ Implement FOV with perspective rendering
|
||||
- [ ] Add batch operations for NumPy-style access (deferred)
|
||||
- [ ] Create CellView for ergonomic .at((x,y)) access (deferred)
|
||||
- [x] **UIEntity Pathfinding** (4 hours) ✅ COMPLETED!
|
||||
- ✅ Implement Dijkstra maps for multiple targets in UIGrid
|
||||
- ✅ Add path_to(target) method using A* to UIEntity
|
||||
- ✅ Cache paths in UIEntity for performance
|
||||
#### 2. TCOD Integration Sprint
|
||||
- [ ] **UIGrid TCOD Integration** (8 hours)
|
||||
- Add TCODMap* to UIGrid constructor
|
||||
- Implement mcrfpy.libtcod.compute_fov()
|
||||
- Add batch operations for NumPy-style access
|
||||
- Create CellView for ergonomic .at((x,y)) access
|
||||
- [ ] **UIEntity Pathfinding** (4 hours)
|
||||
- Add path_to(target) method using A*
|
||||
- Implement Dijkstra maps for multiple targets
|
||||
- Cache paths in UIEntity for performance
|
||||
|
||||
#### 3. Performance Critical Path
|
||||
- [ ] **Implement SpatialHash** for 10,000+ entities (2 hours)
|
||||
|
@ -124,44 +121,6 @@ entity.can_see(other_entity) # FOV check
|
|||
|
||||
## Recent Achievements
|
||||
|
||||
### 2025-07-10: Complete FOV, A* Pathfinding & GUI Text Widgets! 👁️🗺️⌨️
|
||||
**Engine Feature Sprint - Major Capabilities Added**
|
||||
- ✅ Complete FOV (Field of View) system with perspective rendering
|
||||
- UIGrid.perspective property controls which entity's view to render
|
||||
- Three-layer overlay system: unexplored (black), explored (dark), visible (normal)
|
||||
- Per-entity visibility state tracking with UIGridPointState
|
||||
- Perfect knowledge updates - only explored areas persist
|
||||
- ✅ A* Pathfinding implementation
|
||||
- Entity.path_to(x, y) method for direct pathfinding
|
||||
- UIGrid compute_astar() and get_astar_path() methods
|
||||
- Path caching in entities for performance
|
||||
- Complete test suite comparing A* vs Dijkstra performance
|
||||
- ✅ GUI Text Input Widget System
|
||||
- Full-featured TextInputWidget class with cursor, selection, scrolling
|
||||
- Improved widget with proper text rendering and multi-line support
|
||||
- Example showcase demonstrating multiple input fields
|
||||
- Foundation for in-game consoles, chat systems, and text entry
|
||||
- ✅ Sizzle Reel Demos
|
||||
- path_vision_sizzle_reel.py combines pathfinding with FOV
|
||||
- Interactive visibility demos showing real-time FOV updates
|
||||
- Performance demonstrations with multiple entities
|
||||
|
||||
### 2025-07-09: Dijkstra Pathfinding & Critical Bug Fix! 🗺️
|
||||
**TCOD Integration Sprint - Major Progress**
|
||||
- ✅ Complete Dijkstra pathfinding implementation in UIGrid
|
||||
- compute_dijkstra(), get_dijkstra_distance(), get_dijkstra_path() methods
|
||||
- Full TCODMap and TCODDijkstra integration with proper memory management
|
||||
- Comprehensive test suite with both headless and interactive demos
|
||||
- ✅ **CRITICAL FIX**: PyArg bug in UIGridPoint color setter
|
||||
- Now supports both mcrfpy.Color objects and (r,g,b,a) tuples
|
||||
- Eliminated mysterious "SystemError: new style getargs format" crashes
|
||||
- Proper error handling and exception propagation
|
||||
- ✅ mcrfpy.libtcod submodule with Python bindings
|
||||
- dijkstra_compute(), dijkstra_get_distance(), dijkstra_get_path()
|
||||
- line() function for corridor generation
|
||||
- Foundation ready for FOV implementation
|
||||
- ✅ Test consolidation: 6 broken demos → 2 clean, working versions
|
||||
|
||||
### 2025-07-08: PyArgHelpers Infrastructure Complete! 🔧
|
||||
**Standardized Python API Argument Parsing**
|
||||
- Unified position handling: (x, y) tuples or separate x, y args
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 35 KiB |
|
@ -1,342 +0,0 @@
|
|||
/**
|
||||
* 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,33 +0,0 @@
|
|||
import mcrfpy
|
||||
|
||||
# Create a new scene called "hello"
|
||||
mcrfpy.createScene("hello")
|
||||
|
||||
# Switch to our new scene
|
||||
mcrfpy.setScene("hello")
|
||||
|
||||
# Get the UI container for our scene
|
||||
ui = mcrfpy.sceneUI("hello")
|
||||
|
||||
# Create a text caption
|
||||
caption = mcrfpy.Caption("Hello Roguelike!", 400, 300)
|
||||
caption.font_size = 32
|
||||
caption.fill_color = mcrfpy.Color(255, 255, 255) # White text
|
||||
|
||||
# Add the caption to our scene
|
||||
ui.append(caption)
|
||||
|
||||
# Create a smaller instruction caption
|
||||
instruction = mcrfpy.Caption("Press ESC to exit", 400, 350)
|
||||
instruction.font_size = 16
|
||||
instruction.fill_color = mcrfpy.Color(200, 200, 200) # Light gray
|
||||
ui.append(instruction)
|
||||
|
||||
# Set up a simple key handler
|
||||
def handle_keys(key, state):
|
||||
if state == "start" and key == "Escape":
|
||||
mcrfpy.setScene(None) # This exits the game
|
||||
|
||||
mcrfpy.keypressScene(handle_keys)
|
||||
|
||||
print("Hello Roguelike is running!")
|
|
@ -1,55 +0,0 @@
|
|||
import mcrfpy
|
||||
|
||||
# Create our test scene
|
||||
mcrfpy.createScene("test")
|
||||
mcrfpy.setScene("test")
|
||||
ui = mcrfpy.sceneUI("test")
|
||||
|
||||
# Create a background frame
|
||||
background = mcrfpy.Frame(0, 0, 1024, 768)
|
||||
background.fill_color = mcrfpy.Color(20, 20, 30) # Dark blue-gray
|
||||
ui.append(background)
|
||||
|
||||
# Title text
|
||||
title = mcrfpy.Caption("McRogueFace Setup Test", 512, 100)
|
||||
title.font_size = 36
|
||||
title.fill_color = mcrfpy.Color(255, 255, 100) # Yellow
|
||||
ui.append(title)
|
||||
|
||||
# Status text that will update
|
||||
status_text = mcrfpy.Caption("Press any key to test input...", 512, 300)
|
||||
status_text.font_size = 20
|
||||
status_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(status_text)
|
||||
|
||||
# Instructions
|
||||
instructions = [
|
||||
"Arrow Keys: Test movement input",
|
||||
"Space: Test action input",
|
||||
"Mouse Click: Test mouse input",
|
||||
"ESC: Exit"
|
||||
]
|
||||
|
||||
y_offset = 400
|
||||
for instruction in instructions:
|
||||
inst_caption = mcrfpy.Caption(instruction, 512, y_offset)
|
||||
inst_caption.font_size = 16
|
||||
inst_caption.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(inst_caption)
|
||||
y_offset += 30
|
||||
|
||||
# Input handler
|
||||
def handle_input(key, state):
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
if key == "Escape":
|
||||
mcrfpy.setScene(None)
|
||||
else:
|
||||
status_text.text = f"You pressed: {key}"
|
||||
status_text.fill_color = mcrfpy.Color(100, 255, 100) # Green
|
||||
|
||||
# Set up input handling
|
||||
mcrfpy.keypressScene(handle_input)
|
||||
|
||||
print("Setup test is running! Try pressing different keys.")
|
|
@ -1,162 +0,0 @@
|
|||
import mcrfpy
|
||||
|
||||
# Window configuration
|
||||
mcrfpy.createScene("game")
|
||||
mcrfpy.setScene("game")
|
||||
|
||||
window = mcrfpy.Window.get()
|
||||
window.title = "McRogueFace Roguelike - Part 1"
|
||||
|
||||
# Get the UI container for our scene
|
||||
ui = mcrfpy.sceneUI("game")
|
||||
|
||||
# Create a dark background
|
||||
background = mcrfpy.Frame(0, 0, 1024, 768)
|
||||
background.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
ui.append(background)
|
||||
|
||||
# Load the ASCII tileset
|
||||
tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16)
|
||||
|
||||
# Create the game grid
|
||||
GRID_WIDTH = 50
|
||||
GRID_HEIGHT = 30
|
||||
|
||||
grid = mcrfpy.Grid(grid_x=GRID_WIDTH, grid_y=GRID_HEIGHT, texture=tileset)
|
||||
grid.position = (100, 100)
|
||||
grid.size = (800, 480)
|
||||
ui.append(grid)
|
||||
|
||||
def create_room():
|
||||
"""Create a room with walls around the edges"""
|
||||
# Fill everything with floor tiles first
|
||||
for y in range(GRID_HEIGHT):
|
||||
for x in range(GRID_WIDTH):
|
||||
cell = grid.at(x, y)
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.sprite_index = 46 # '.' character
|
||||
cell.color = mcrfpy.Color(50, 50, 50) # Dark gray floor
|
||||
|
||||
# Create walls around the edges
|
||||
for x in range(GRID_WIDTH):
|
||||
# Top wall
|
||||
cell = grid.at(x, 0)
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.sprite_index = 35 # '#' character
|
||||
cell.color = mcrfpy.Color(100, 100, 100) # Gray walls
|
||||
|
||||
# Bottom wall
|
||||
cell = grid.at(x, GRID_HEIGHT - 1)
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.sprite_index = 35 # '#' character
|
||||
cell.color = mcrfpy.Color(100, 100, 100)
|
||||
|
||||
for y in range(GRID_HEIGHT):
|
||||
# Left wall
|
||||
cell = grid.at(0, y)
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.sprite_index = 35 # '#' character
|
||||
cell.color = mcrfpy.Color(100, 100, 100)
|
||||
|
||||
# Right wall
|
||||
cell = grid.at(GRID_WIDTH - 1, y)
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.sprite_index = 35 # '#' character
|
||||
cell.color = mcrfpy.Color(100, 100, 100)
|
||||
|
||||
# Create the room
|
||||
create_room()
|
||||
|
||||
# Create the player entity
|
||||
player = mcrfpy.Entity(x=GRID_WIDTH // 2, y=GRID_HEIGHT // 2, grid=grid)
|
||||
player.sprite_index = 64 # '@' character
|
||||
player.color = mcrfpy.Color(255, 255, 255) # White
|
||||
|
||||
def move_player(dx, dy):
|
||||
"""Move the player if the destination is walkable"""
|
||||
# Calculate new position
|
||||
new_x = player.x + dx
|
||||
new_y = player.y + dy
|
||||
|
||||
# Check bounds
|
||||
if new_x < 0 or new_x >= GRID_WIDTH or new_y < 0 or new_y >= GRID_HEIGHT:
|
||||
return
|
||||
|
||||
# Check if the destination is walkable
|
||||
destination = grid.at(new_x, new_y)
|
||||
if destination.walkable:
|
||||
# Move the player
|
||||
player.x = new_x
|
||||
player.y = new_y
|
||||
|
||||
def handle_input(key, state):
|
||||
"""Handle keyboard input for player movement"""
|
||||
# Only process key presses, not releases
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
# Movement deltas
|
||||
dx, dy = 0, 0
|
||||
|
||||
# Arrow keys
|
||||
if key == "Up":
|
||||
dy = -1
|
||||
elif key == "Down":
|
||||
dy = 1
|
||||
elif key == "Left":
|
||||
dx = -1
|
||||
elif key == "Right":
|
||||
dx = 1
|
||||
|
||||
# Numpad movement (for true roguelike feel!)
|
||||
elif key == "Num7": # Northwest
|
||||
dx, dy = -1, -1
|
||||
elif key == "Num8": # North
|
||||
dy = -1
|
||||
elif key == "Num9": # Northeast
|
||||
dx, dy = 1, -1
|
||||
elif key == "Num4": # West
|
||||
dx = -1
|
||||
elif key == "Num6": # East
|
||||
dx = 1
|
||||
elif key == "Num1": # Southwest
|
||||
dx, dy = -1, 1
|
||||
elif key == "Num2": # South
|
||||
dy = 1
|
||||
elif key == "Num3": # Southeast
|
||||
dx, dy = 1, 1
|
||||
|
||||
# Escape to quit
|
||||
elif key == "Escape":
|
||||
mcrfpy.setScene(None)
|
||||
return
|
||||
|
||||
# If there's movement, try to move the player
|
||||
if dx != 0 or dy != 0:
|
||||
move_player(dx, dy)
|
||||
|
||||
# Register the input handler
|
||||
mcrfpy.keypressScene(handle_input)
|
||||
|
||||
# Add UI elements
|
||||
title = mcrfpy.Caption("McRogueFace Roguelike", 512, 30)
|
||||
title.font_size = 24
|
||||
title.fill_color = mcrfpy.Color(255, 255, 100)
|
||||
ui.append(title)
|
||||
|
||||
instructions = mcrfpy.Caption("Arrow Keys or Numpad to move, ESC to quit", 512, 60)
|
||||
instructions.font_size = 16
|
||||
instructions.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(instructions)
|
||||
|
||||
status = mcrfpy.Caption("@ You", 100, 600)
|
||||
status.font_size = 18
|
||||
status.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(status)
|
||||
|
||||
print("Part 1: The @ symbol moves!")
|
|
@ -1,217 +0,0 @@
|
|||
import mcrfpy
|
||||
|
||||
class GameObject:
|
||||
"""Base class for all game objects (player, monsters, items)"""
|
||||
|
||||
def __init__(self, x, y, sprite_index, color, name, blocks=False):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.sprite_index = sprite_index
|
||||
self.color = color
|
||||
self.name = name
|
||||
self.blocks = blocks
|
||||
self._entity = None
|
||||
self.grid = None
|
||||
|
||||
def attach_to_grid(self, grid):
|
||||
"""Attach this game object to a McRogueFace grid"""
|
||||
self.grid = grid
|
||||
self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid)
|
||||
self._entity.sprite_index = self.sprite_index
|
||||
self._entity.color = mcrfpy.Color(*self.color)
|
||||
|
||||
def move(self, dx, dy):
|
||||
"""Move by the given amount if possible"""
|
||||
if not self.grid:
|
||||
return
|
||||
|
||||
new_x = self.x + dx
|
||||
new_y = self.y + dy
|
||||
|
||||
self.x = new_x
|
||||
self.y = new_y
|
||||
|
||||
if self._entity:
|
||||
self._entity.x = new_x
|
||||
self._entity.y = new_y
|
||||
|
||||
class GameMap:
|
||||
"""Manages the game world"""
|
||||
|
||||
def __init__(self, width, height):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.grid = None
|
||||
self.entities = []
|
||||
|
||||
def create_grid(self, tileset):
|
||||
"""Create the McRogueFace grid"""
|
||||
self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset)
|
||||
self.grid.position = (100, 100)
|
||||
self.grid.size = (800, 480)
|
||||
self.fill_with_walls()
|
||||
return self.grid
|
||||
|
||||
def fill_with_walls(self):
|
||||
"""Fill the entire map with wall tiles"""
|
||||
for y in range(self.height):
|
||||
for x in range(self.width):
|
||||
self.set_tile(x, y, walkable=False, transparent=False,
|
||||
sprite_index=35, color=(100, 100, 100))
|
||||
|
||||
def set_tile(self, x, y, walkable, transparent, sprite_index, color):
|
||||
"""Set properties for a specific tile"""
|
||||
if 0 <= x < self.width and 0 <= y < self.height:
|
||||
cell = self.grid.at(x, y)
|
||||
cell.walkable = walkable
|
||||
cell.transparent = transparent
|
||||
cell.sprite_index = sprite_index
|
||||
cell.color = mcrfpy.Color(*color)
|
||||
|
||||
def create_room(self, x1, y1, x2, y2):
|
||||
"""Carve out a room in the map"""
|
||||
x1, x2 = min(x1, x2), max(x1, x2)
|
||||
y1, y2 = min(y1, y2), max(y1, y2)
|
||||
|
||||
for y in range(y1, y2 + 1):
|
||||
for x in range(x1, x2 + 1):
|
||||
self.set_tile(x, y, walkable=True, transparent=True,
|
||||
sprite_index=46, color=(50, 50, 50))
|
||||
|
||||
def create_tunnel_h(self, x1, x2, y):
|
||||
"""Create a horizontal tunnel"""
|
||||
for x in range(min(x1, x2), max(x1, x2) + 1):
|
||||
self.set_tile(x, y, walkable=True, transparent=True,
|
||||
sprite_index=46, color=(50, 50, 50))
|
||||
|
||||
def create_tunnel_v(self, y1, y2, x):
|
||||
"""Create a vertical tunnel"""
|
||||
for y in range(min(y1, y2), max(y1, y2) + 1):
|
||||
self.set_tile(x, y, walkable=True, transparent=True,
|
||||
sprite_index=46, color=(50, 50, 50))
|
||||
|
||||
def is_blocked(self, x, y):
|
||||
"""Check if a tile blocks movement"""
|
||||
if x < 0 or x >= self.width or y < 0 or y >= self.height:
|
||||
return True
|
||||
|
||||
if not self.grid.at(x, y).walkable:
|
||||
return True
|
||||
|
||||
for entity in self.entities:
|
||||
if entity.blocks and entity.x == x and entity.y == y:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def add_entity(self, entity):
|
||||
"""Add a GameObject to the map"""
|
||||
self.entities.append(entity)
|
||||
entity.attach_to_grid(self.grid)
|
||||
|
||||
def get_blocking_entity_at(self, x, y):
|
||||
"""Return any blocking entity at the given position"""
|
||||
for entity in self.entities:
|
||||
if entity.blocks and entity.x == x and entity.y == y:
|
||||
return entity
|
||||
return None
|
||||
|
||||
class Engine:
|
||||
"""Main game engine that manages game state"""
|
||||
|
||||
def __init__(self):
|
||||
self.game_map = None
|
||||
self.player = None
|
||||
self.entities = []
|
||||
|
||||
mcrfpy.createScene("game")
|
||||
mcrfpy.setScene("game")
|
||||
|
||||
window = mcrfpy.Window.get()
|
||||
window.title = "McRogueFace Roguelike - Part 2"
|
||||
|
||||
self.ui = mcrfpy.sceneUI("game")
|
||||
|
||||
background = mcrfpy.Frame(0, 0, 1024, 768)
|
||||
background.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
self.ui.append(background)
|
||||
|
||||
self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16)
|
||||
|
||||
self.setup_game()
|
||||
self.setup_input()
|
||||
self.setup_ui()
|
||||
|
||||
def setup_game(self):
|
||||
"""Initialize the game world"""
|
||||
self.game_map = GameMap(50, 30)
|
||||
grid = self.game_map.create_grid(self.tileset)
|
||||
self.ui.append(grid)
|
||||
|
||||
self.game_map.create_room(10, 10, 20, 20)
|
||||
self.game_map.create_room(30, 15, 40, 25)
|
||||
self.game_map.create_room(15, 22, 25, 28)
|
||||
|
||||
self.game_map.create_tunnel_h(20, 30, 15)
|
||||
self.game_map.create_tunnel_v(20, 22, 20)
|
||||
|
||||
self.player = GameObject(15, 15, 64, (255, 255, 255), "Player", blocks=True)
|
||||
self.game_map.add_entity(self.player)
|
||||
|
||||
npc = GameObject(35, 20, 64, (255, 255, 0), "NPC", blocks=True)
|
||||
self.game_map.add_entity(npc)
|
||||
self.entities.append(npc)
|
||||
|
||||
potion = GameObject(12, 12, 33, (255, 0, 255), "Potion", blocks=False)
|
||||
self.game_map.add_entity(potion)
|
||||
self.entities.append(potion)
|
||||
|
||||
def handle_movement(self, dx, dy):
|
||||
"""Handle player movement"""
|
||||
new_x = self.player.x + dx
|
||||
new_y = self.player.y + dy
|
||||
|
||||
if not self.game_map.is_blocked(new_x, new_y):
|
||||
self.player.move(dx, dy)
|
||||
else:
|
||||
target = self.game_map.get_blocking_entity_at(new_x, new_y)
|
||||
if target:
|
||||
print(f"You bump into the {target.name}!")
|
||||
|
||||
def setup_input(self):
|
||||
"""Setup keyboard input handling"""
|
||||
def handle_keys(key, state):
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
movement = {
|
||||
"Up": (0, -1), "Down": (0, 1),
|
||||
"Left": (-1, 0), "Right": (1, 0),
|
||||
"Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1),
|
||||
"Num4": (-1, 0), "Num6": (1, 0),
|
||||
"Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1),
|
||||
}
|
||||
|
||||
if key in movement:
|
||||
dx, dy = movement[key]
|
||||
self.handle_movement(dx, dy)
|
||||
elif key == "Escape":
|
||||
mcrfpy.setScene(None)
|
||||
|
||||
mcrfpy.keypressScene(handle_keys)
|
||||
|
||||
def setup_ui(self):
|
||||
"""Setup UI elements"""
|
||||
title = mcrfpy.Caption("McRogueFace Roguelike - Part 2", 512, 30)
|
||||
title.font_size = 24
|
||||
title.fill_color = mcrfpy.Color(255, 255, 100)
|
||||
self.ui.append(title)
|
||||
|
||||
instructions = mcrfpy.Caption("Explore the dungeon! ESC to quit", 512, 60)
|
||||
instructions.font_size = 16
|
||||
instructions.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
self.ui.append(instructions)
|
||||
|
||||
# Create and run the game
|
||||
engine = Engine()
|
||||
print("Part 2: Entities and Maps!")
|
|
@ -1,312 +0,0 @@
|
|||
import mcrfpy
|
||||
import random
|
||||
|
||||
class GameObject:
|
||||
"""Base class for all game objects"""
|
||||
def __init__(self, x, y, sprite_index, color, name, blocks=False):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.sprite_index = sprite_index
|
||||
self.color = color
|
||||
self.name = name
|
||||
self.blocks = blocks
|
||||
self._entity = None
|
||||
self.grid = None
|
||||
|
||||
def attach_to_grid(self, grid):
|
||||
"""Attach this game object to a McRogueFace grid"""
|
||||
self.grid = grid
|
||||
self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid)
|
||||
self._entity.sprite_index = self.sprite_index
|
||||
self._entity.color = mcrfpy.Color(*self.color)
|
||||
|
||||
def move(self, dx, dy):
|
||||
"""Move by the given amount"""
|
||||
if not self.grid:
|
||||
return
|
||||
self.x += dx
|
||||
self.y += dy
|
||||
if self._entity:
|
||||
self._entity.x = self.x
|
||||
self._entity.y = self.y
|
||||
|
||||
class RectangularRoom:
|
||||
"""A rectangular room with its position and size"""
|
||||
|
||||
def __init__(self, x, y, width, height):
|
||||
self.x1 = x
|
||||
self.y1 = y
|
||||
self.x2 = x + width
|
||||
self.y2 = y + height
|
||||
|
||||
@property
|
||||
def center(self):
|
||||
"""Return the center coordinates of the room"""
|
||||
center_x = (self.x1 + self.x2) // 2
|
||||
center_y = (self.y1 + self.y2) // 2
|
||||
return center_x, center_y
|
||||
|
||||
@property
|
||||
def inner(self):
|
||||
"""Return the inner area of the room"""
|
||||
return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1
|
||||
|
||||
def intersects(self, other):
|
||||
"""Return True if this room overlaps with another"""
|
||||
return (
|
||||
self.x1 <= other.x2
|
||||
and self.x2 >= other.x1
|
||||
and self.y1 <= other.y2
|
||||
and self.y2 >= other.y1
|
||||
)
|
||||
|
||||
def tunnel_between(start, end):
|
||||
"""Return an L-shaped tunnel between two points"""
|
||||
x1, y1 = start
|
||||
x2, y2 = end
|
||||
|
||||
if random.random() < 0.5:
|
||||
corner_x = x2
|
||||
corner_y = y1
|
||||
else:
|
||||
corner_x = x1
|
||||
corner_y = y2
|
||||
|
||||
# Generate the coordinates
|
||||
for x in range(min(x1, corner_x), max(x1, corner_x) + 1):
|
||||
yield x, y1
|
||||
for y in range(min(y1, corner_y), max(y1, corner_y) + 1):
|
||||
yield corner_x, y
|
||||
for x in range(min(corner_x, x2), max(corner_x, x2) + 1):
|
||||
yield x, corner_y
|
||||
for y in range(min(corner_y, y2), max(corner_y, y2) + 1):
|
||||
yield x2, y
|
||||
|
||||
class GameMap:
|
||||
"""Manages the game world"""
|
||||
|
||||
def __init__(self, width, height):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.grid = None
|
||||
self.entities = []
|
||||
self.rooms = []
|
||||
|
||||
def create_grid(self, tileset):
|
||||
"""Create the McRogueFace grid"""
|
||||
self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset)
|
||||
self.grid.position = (100, 100)
|
||||
self.grid.size = (800, 480)
|
||||
return self.grid
|
||||
|
||||
def fill_with_walls(self):
|
||||
"""Fill the entire map with wall tiles"""
|
||||
for y in range(self.height):
|
||||
for x in range(self.width):
|
||||
self.set_tile(x, y, walkable=False, transparent=False,
|
||||
sprite_index=35, color=(100, 100, 100))
|
||||
|
||||
def set_tile(self, x, y, walkable, transparent, sprite_index, color):
|
||||
"""Set properties for a specific tile"""
|
||||
if 0 <= x < self.width and 0 <= y < self.height:
|
||||
cell = self.grid.at(x, y)
|
||||
cell.walkable = walkable
|
||||
cell.transparent = transparent
|
||||
cell.sprite_index = sprite_index
|
||||
cell.color = mcrfpy.Color(*color)
|
||||
|
||||
def generate_dungeon(self, max_rooms, room_min_size, room_max_size, player):
|
||||
"""Generate a new dungeon map"""
|
||||
self.fill_with_walls()
|
||||
|
||||
for r in range(max_rooms):
|
||||
room_width = random.randint(room_min_size, room_max_size)
|
||||
room_height = random.randint(room_min_size, room_max_size)
|
||||
|
||||
x = random.randint(0, self.width - room_width - 1)
|
||||
y = random.randint(0, self.height - room_height - 1)
|
||||
|
||||
new_room = RectangularRoom(x, y, room_width, room_height)
|
||||
|
||||
if any(new_room.intersects(other_room) for other_room in self.rooms):
|
||||
continue
|
||||
|
||||
self.carve_room(new_room)
|
||||
|
||||
if len(self.rooms) == 0:
|
||||
player.x, player.y = new_room.center
|
||||
if player._entity:
|
||||
player._entity.x, player._entity.y = new_room.center
|
||||
else:
|
||||
self.carve_tunnel(self.rooms[-1].center, new_room.center)
|
||||
|
||||
self.rooms.append(new_room)
|
||||
|
||||
def carve_room(self, room):
|
||||
"""Carve out a room"""
|
||||
inner_x1, inner_y1, inner_x2, inner_y2 = room.inner
|
||||
|
||||
for y in range(inner_y1, inner_y2):
|
||||
for x in range(inner_x1, inner_x2):
|
||||
self.set_tile(x, y, walkable=True, transparent=True,
|
||||
sprite_index=46, color=(50, 50, 50))
|
||||
|
||||
def carve_tunnel(self, start, end):
|
||||
"""Carve a tunnel between two points"""
|
||||
for x, y in tunnel_between(start, end):
|
||||
self.set_tile(x, y, walkable=True, transparent=True,
|
||||
sprite_index=46, color=(30, 30, 40))
|
||||
|
||||
def is_blocked(self, x, y):
|
||||
"""Check if a tile blocks movement"""
|
||||
if x < 0 or x >= self.width or y < 0 or y >= self.height:
|
||||
return True
|
||||
if not self.grid.at(x, y).walkable:
|
||||
return True
|
||||
for entity in self.entities:
|
||||
if entity.blocks and entity.x == x and entity.y == y:
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_entity(self, entity):
|
||||
"""Add a GameObject to the map"""
|
||||
self.entities.append(entity)
|
||||
entity.attach_to_grid(self.grid)
|
||||
|
||||
class Engine:
|
||||
"""Main game engine"""
|
||||
|
||||
def __init__(self):
|
||||
self.game_map = None
|
||||
self.player = None
|
||||
self.entities = []
|
||||
|
||||
mcrfpy.createScene("game")
|
||||
mcrfpy.setScene("game")
|
||||
|
||||
window = mcrfpy.Window.get()
|
||||
window.title = "McRogueFace Roguelike - Part 3"
|
||||
|
||||
self.ui = mcrfpy.sceneUI("game")
|
||||
|
||||
background = mcrfpy.Frame(0, 0, 1024, 768)
|
||||
background.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
self.ui.append(background)
|
||||
|
||||
self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16)
|
||||
|
||||
self.setup_game()
|
||||
self.setup_input()
|
||||
self.setup_ui()
|
||||
|
||||
def setup_game(self):
|
||||
"""Initialize the game world"""
|
||||
self.game_map = GameMap(80, 45)
|
||||
grid = self.game_map.create_grid(self.tileset)
|
||||
self.ui.append(grid)
|
||||
|
||||
# Create player (before dungeon generation)
|
||||
self.player = GameObject(0, 0, 64, (255, 255, 255), "Player", blocks=True)
|
||||
|
||||
# Generate the dungeon
|
||||
self.game_map.generate_dungeon(
|
||||
max_rooms=30,
|
||||
room_min_size=6,
|
||||
room_max_size=10,
|
||||
player=self.player
|
||||
)
|
||||
|
||||
# Add player to map
|
||||
self.game_map.add_entity(self.player)
|
||||
|
||||
# Add some monsters in random rooms
|
||||
for i in range(5):
|
||||
if i < len(self.game_map.rooms) - 1: # Don't spawn in first room
|
||||
room = self.game_map.rooms[i + 1]
|
||||
x, y = room.center
|
||||
|
||||
# Create an orc
|
||||
orc = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True)
|
||||
self.game_map.add_entity(orc)
|
||||
self.entities.append(orc)
|
||||
|
||||
def handle_movement(self, dx, dy):
|
||||
"""Handle player movement"""
|
||||
new_x = self.player.x + dx
|
||||
new_y = self.player.y + dy
|
||||
|
||||
if not self.game_map.is_blocked(new_x, new_y):
|
||||
self.player.move(dx, dy)
|
||||
|
||||
def setup_input(self):
|
||||
"""Setup keyboard input handling"""
|
||||
def handle_keys(key, state):
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
movement = {
|
||||
"Up": (0, -1), "Down": (0, 1),
|
||||
"Left": (-1, 0), "Right": (1, 0),
|
||||
"Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1),
|
||||
"Num4": (-1, 0), "Num6": (1, 0),
|
||||
"Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1),
|
||||
}
|
||||
|
||||
if key in movement:
|
||||
dx, dy = movement[key]
|
||||
self.handle_movement(dx, dy)
|
||||
elif key == "Escape":
|
||||
mcrfpy.setScene(None)
|
||||
elif key == "Space":
|
||||
# Regenerate the dungeon
|
||||
self.regenerate_dungeon()
|
||||
|
||||
mcrfpy.keypressScene(handle_keys)
|
||||
|
||||
def regenerate_dungeon(self):
|
||||
"""Generate a new dungeon"""
|
||||
# Clear existing entities
|
||||
self.game_map.entities.clear()
|
||||
self.game_map.rooms.clear()
|
||||
self.entities.clear()
|
||||
|
||||
# Clear the entity list in the grid
|
||||
if self.game_map.grid:
|
||||
self.game_map.grid.entities.clear()
|
||||
|
||||
# Regenerate
|
||||
self.game_map.generate_dungeon(
|
||||
max_rooms=30,
|
||||
room_min_size=6,
|
||||
room_max_size=10,
|
||||
player=self.player
|
||||
)
|
||||
|
||||
# Re-add player
|
||||
self.game_map.add_entity(self.player)
|
||||
|
||||
# Add new monsters
|
||||
for i in range(5):
|
||||
if i < len(self.game_map.rooms) - 1:
|
||||
room = self.game_map.rooms[i + 1]
|
||||
x, y = room.center
|
||||
orc = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True)
|
||||
self.game_map.add_entity(orc)
|
||||
self.entities.append(orc)
|
||||
|
||||
def setup_ui(self):
|
||||
"""Setup UI elements"""
|
||||
title = mcrfpy.Caption("Procedural Dungeon Generation", 512, 30)
|
||||
title.font_size = 24
|
||||
title.fill_color = mcrfpy.Color(255, 255, 100)
|
||||
self.ui.append(title)
|
||||
|
||||
instructions = mcrfpy.Caption("Arrow keys to move, SPACE to regenerate, ESC to quit", 512, 60)
|
||||
instructions.font_size = 16
|
||||
instructions.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
self.ui.append(instructions)
|
||||
|
||||
# Create and run the game
|
||||
engine = Engine()
|
||||
print("Part 3: Procedural Dungeon Generation!")
|
||||
print("Press SPACE to generate a new dungeon")
|
|
@ -1,334 +0,0 @@
|
|||
import mcrfpy
|
||||
import random
|
||||
|
||||
# Color configurations for visibility
|
||||
COLORS_VISIBLE = {
|
||||
'wall': (100, 100, 100),
|
||||
'floor': (50, 50, 50),
|
||||
'tunnel': (30, 30, 40),
|
||||
}
|
||||
|
||||
class GameObject:
|
||||
"""Base class for all game objects"""
|
||||
def __init__(self, x, y, sprite_index, color, name, blocks=False):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.sprite_index = sprite_index
|
||||
self.color = color
|
||||
self.name = name
|
||||
self.blocks = blocks
|
||||
self._entity = None
|
||||
self.grid = None
|
||||
|
||||
def attach_to_grid(self, grid):
|
||||
"""Attach this game object to a McRogueFace grid"""
|
||||
self.grid = grid
|
||||
self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid)
|
||||
self._entity.sprite_index = self.sprite_index
|
||||
self._entity.color = mcrfpy.Color(*self.color)
|
||||
|
||||
def move(self, dx, dy):
|
||||
"""Move by the given amount"""
|
||||
if not self.grid:
|
||||
return
|
||||
self.x += dx
|
||||
self.y += dy
|
||||
if self._entity:
|
||||
self._entity.x = self.x
|
||||
self._entity.y = self.y
|
||||
# Update FOV when player moves
|
||||
if self.name == "Player":
|
||||
self.update_fov()
|
||||
|
||||
def update_fov(self):
|
||||
"""Update field of view from this entity's position"""
|
||||
if self._entity and self.grid:
|
||||
self._entity.update_fov(radius=8)
|
||||
|
||||
class RectangularRoom:
|
||||
"""A rectangular room with its position and size"""
|
||||
|
||||
def __init__(self, x, y, width, height):
|
||||
self.x1 = x
|
||||
self.y1 = y
|
||||
self.x2 = x + width
|
||||
self.y2 = y + height
|
||||
|
||||
@property
|
||||
def center(self):
|
||||
center_x = (self.x1 + self.x2) // 2
|
||||
center_y = (self.y1 + self.y2) // 2
|
||||
return center_x, center_y
|
||||
|
||||
@property
|
||||
def inner(self):
|
||||
return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1
|
||||
|
||||
def intersects(self, other):
|
||||
return (
|
||||
self.x1 <= other.x2
|
||||
and self.x2 >= other.x1
|
||||
and self.y1 <= other.y2
|
||||
and self.y2 >= other.y1
|
||||
)
|
||||
|
||||
def tunnel_between(start, end):
|
||||
"""Return an L-shaped tunnel between two points"""
|
||||
x1, y1 = start
|
||||
x2, y2 = end
|
||||
|
||||
if random.random() < 0.5:
|
||||
corner_x = x2
|
||||
corner_y = y1
|
||||
else:
|
||||
corner_x = x1
|
||||
corner_y = y2
|
||||
|
||||
for x in range(min(x1, corner_x), max(x1, corner_x) + 1):
|
||||
yield x, y1
|
||||
for y in range(min(y1, corner_y), max(y1, corner_y) + 1):
|
||||
yield corner_x, y
|
||||
for x in range(min(corner_x, x2), max(corner_x, x2) + 1):
|
||||
yield x, corner_y
|
||||
for y in range(min(corner_y, y2), max(corner_y, y2) + 1):
|
||||
yield x2, y
|
||||
|
||||
class GameMap:
|
||||
"""Manages the game world"""
|
||||
|
||||
def __init__(self, width, height):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.grid = None
|
||||
self.entities = []
|
||||
self.rooms = []
|
||||
|
||||
def create_grid(self, tileset):
|
||||
"""Create the McRogueFace grid"""
|
||||
self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset)
|
||||
self.grid.position = (100, 100)
|
||||
self.grid.size = (800, 480)
|
||||
|
||||
# Enable perspective rendering (0 = first entity = player)
|
||||
self.grid.perspective = 0
|
||||
|
||||
return self.grid
|
||||
|
||||
def fill_with_walls(self):
|
||||
"""Fill the entire map with wall tiles"""
|
||||
for y in range(self.height):
|
||||
for x in range(self.width):
|
||||
self.set_tile(x, y, walkable=False, transparent=False,
|
||||
sprite_index=35, tile_type='wall')
|
||||
|
||||
def set_tile(self, x, y, walkable, transparent, sprite_index, tile_type):
|
||||
"""Set properties for a specific tile"""
|
||||
if 0 <= x < self.width and 0 <= y < self.height:
|
||||
cell = self.grid.at(x, y)
|
||||
cell.walkable = walkable
|
||||
cell.transparent = transparent
|
||||
cell.sprite_index = sprite_index
|
||||
cell.color = mcrfpy.Color(*COLORS_VISIBLE[tile_type])
|
||||
|
||||
def generate_dungeon(self, max_rooms, room_min_size, room_max_size, player):
|
||||
"""Generate a new dungeon map"""
|
||||
self.fill_with_walls()
|
||||
|
||||
for r in range(max_rooms):
|
||||
room_width = random.randint(room_min_size, room_max_size)
|
||||
room_height = random.randint(room_min_size, room_max_size)
|
||||
|
||||
x = random.randint(0, self.width - room_width - 1)
|
||||
y = random.randint(0, self.height - room_height - 1)
|
||||
|
||||
new_room = RectangularRoom(x, y, room_width, room_height)
|
||||
|
||||
if any(new_room.intersects(other_room) for other_room in self.rooms):
|
||||
continue
|
||||
|
||||
self.carve_room(new_room)
|
||||
|
||||
if len(self.rooms) == 0:
|
||||
player.x, player.y = new_room.center
|
||||
if player._entity:
|
||||
player._entity.x, player._entity.y = new_room.center
|
||||
else:
|
||||
self.carve_tunnel(self.rooms[-1].center, new_room.center)
|
||||
|
||||
self.rooms.append(new_room)
|
||||
|
||||
def carve_room(self, room):
|
||||
"""Carve out a room"""
|
||||
inner_x1, inner_y1, inner_x2, inner_y2 = room.inner
|
||||
|
||||
for y in range(inner_y1, inner_y2):
|
||||
for x in range(inner_x1, inner_x2):
|
||||
self.set_tile(x, y, walkable=True, transparent=True,
|
||||
sprite_index=46, tile_type='floor')
|
||||
|
||||
def carve_tunnel(self, start, end):
|
||||
"""Carve a tunnel between two points"""
|
||||
for x, y in tunnel_between(start, end):
|
||||
self.set_tile(x, y, walkable=True, transparent=True,
|
||||
sprite_index=46, tile_type='tunnel')
|
||||
|
||||
def is_blocked(self, x, y):
|
||||
"""Check if a tile blocks movement"""
|
||||
if x < 0 or x >= self.width or y < 0 or y >= self.height:
|
||||
return True
|
||||
if not self.grid.at(x, y).walkable:
|
||||
return True
|
||||
for entity in self.entities:
|
||||
if entity.blocks and entity.x == x and entity.y == y:
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_entity(self, entity):
|
||||
"""Add a GameObject to the map"""
|
||||
self.entities.append(entity)
|
||||
entity.attach_to_grid(self.grid)
|
||||
|
||||
class Engine:
|
||||
"""Main game engine"""
|
||||
|
||||
def __init__(self):
|
||||
self.game_map = None
|
||||
self.player = None
|
||||
self.entities = []
|
||||
self.fov_radius = 8
|
||||
|
||||
mcrfpy.createScene("game")
|
||||
mcrfpy.setScene("game")
|
||||
|
||||
window = mcrfpy.Window.get()
|
||||
window.title = "McRogueFace Roguelike - Part 4"
|
||||
|
||||
self.ui = mcrfpy.sceneUI("game")
|
||||
|
||||
background = mcrfpy.Frame(0, 0, 1024, 768)
|
||||
background.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
self.ui.append(background)
|
||||
|
||||
self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16)
|
||||
|
||||
self.setup_game()
|
||||
self.setup_input()
|
||||
self.setup_ui()
|
||||
|
||||
def setup_game(self):
|
||||
"""Initialize the game world"""
|
||||
self.game_map = GameMap(80, 45)
|
||||
grid = self.game_map.create_grid(self.tileset)
|
||||
self.ui.append(grid)
|
||||
|
||||
# Create player
|
||||
self.player = GameObject(0, 0, 64, (255, 255, 255), "Player", blocks=True)
|
||||
|
||||
# Generate the dungeon
|
||||
self.game_map.generate_dungeon(
|
||||
max_rooms=30,
|
||||
room_min_size=6,
|
||||
room_max_size=10,
|
||||
player=self.player
|
||||
)
|
||||
|
||||
# Add player to map
|
||||
self.game_map.add_entity(self.player)
|
||||
|
||||
# Add monsters in random rooms
|
||||
for i in range(10):
|
||||
if i < len(self.game_map.rooms) - 1:
|
||||
room = self.game_map.rooms[i + 1]
|
||||
x, y = room.center
|
||||
|
||||
# Randomly offset from center
|
||||
x += random.randint(-2, 2)
|
||||
y += random.randint(-2, 2)
|
||||
|
||||
# Make sure position is walkable
|
||||
if self.game_map.grid.at(x, y).walkable:
|
||||
if i % 2 == 0:
|
||||
# Create an orc
|
||||
orc = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True)
|
||||
self.game_map.add_entity(orc)
|
||||
self.entities.append(orc)
|
||||
else:
|
||||
# Create a troll
|
||||
troll = GameObject(x, y, 84, (0, 127, 0), "Troll", blocks=True)
|
||||
self.game_map.add_entity(troll)
|
||||
self.entities.append(troll)
|
||||
|
||||
# Initial FOV calculation
|
||||
self.player.update_fov()
|
||||
|
||||
def handle_movement(self, dx, dy):
|
||||
"""Handle player movement"""
|
||||
new_x = self.player.x + dx
|
||||
new_y = self.player.y + dy
|
||||
|
||||
if not self.game_map.is_blocked(new_x, new_y):
|
||||
self.player.move(dx, dy)
|
||||
|
||||
def setup_input(self):
|
||||
"""Setup keyboard input handling"""
|
||||
def handle_keys(key, state):
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
movement = {
|
||||
"Up": (0, -1), "Down": (0, 1),
|
||||
"Left": (-1, 0), "Right": (1, 0),
|
||||
"Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1),
|
||||
"Num4": (-1, 0), "Num6": (1, 0),
|
||||
"Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1),
|
||||
}
|
||||
|
||||
if key in movement:
|
||||
dx, dy = movement[key]
|
||||
self.handle_movement(dx, dy)
|
||||
elif key == "Escape":
|
||||
mcrfpy.setScene(None)
|
||||
elif key == "v":
|
||||
# Toggle FOV on/off
|
||||
if self.game_map.grid.perspective == 0:
|
||||
self.game_map.grid.perspective = -1 # Omniscient
|
||||
print("FOV disabled - omniscient view")
|
||||
else:
|
||||
self.game_map.grid.perspective = 0 # Player perspective
|
||||
print("FOV enabled - player perspective")
|
||||
elif key == "Plus" or key == "Equals":
|
||||
# Increase FOV radius
|
||||
self.fov_radius = min(self.fov_radius + 1, 20)
|
||||
self.player._entity.update_fov(radius=self.fov_radius)
|
||||
print(f"FOV radius: {self.fov_radius}")
|
||||
elif key == "Minus":
|
||||
# Decrease FOV radius
|
||||
self.fov_radius = max(self.fov_radius - 1, 3)
|
||||
self.player._entity.update_fov(radius=self.fov_radius)
|
||||
print(f"FOV radius: {self.fov_radius}")
|
||||
|
||||
mcrfpy.keypressScene(handle_keys)
|
||||
|
||||
def setup_ui(self):
|
||||
"""Setup UI elements"""
|
||||
title = mcrfpy.Caption("Field of View", 512, 30)
|
||||
title.font_size = 24
|
||||
title.fill_color = mcrfpy.Color(255, 255, 100)
|
||||
self.ui.append(title)
|
||||
|
||||
instructions = mcrfpy.Caption("Arrow keys to move | V to toggle FOV | +/- to adjust radius | ESC to quit", 512, 60)
|
||||
instructions.font_size = 16
|
||||
instructions.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
self.ui.append(instructions)
|
||||
|
||||
# FOV indicator
|
||||
self.fov_text = mcrfpy.Caption(f"FOV Radius: {self.fov_radius}", 900, 100)
|
||||
self.fov_text.font_size = 14
|
||||
self.fov_text.fill_color = mcrfpy.Color(150, 200, 255)
|
||||
self.ui.append(self.fov_text)
|
||||
|
||||
# Create and run the game
|
||||
engine = Engine()
|
||||
print("Part 4: Field of View!")
|
||||
print("Press V to toggle FOV on/off")
|
||||
print("Press +/- to adjust FOV radius")
|
|
@ -1,388 +0,0 @@
|
|||
import mcrfpy
|
||||
import random
|
||||
|
||||
# Color configurations
|
||||
COLORS_VISIBLE = {
|
||||
'wall': (100, 100, 100),
|
||||
'floor': (50, 50, 50),
|
||||
'tunnel': (30, 30, 40),
|
||||
}
|
||||
|
||||
# Actions
|
||||
class Action:
|
||||
"""Base class for all actions"""
|
||||
pass
|
||||
|
||||
class MovementAction(Action):
|
||||
"""Action for moving an entity"""
|
||||
def __init__(self, dx, dy):
|
||||
self.dx = dx
|
||||
self.dy = dy
|
||||
|
||||
class WaitAction(Action):
|
||||
"""Action for waiting/skipping turn"""
|
||||
pass
|
||||
|
||||
class GameObject:
|
||||
"""Base class for all game objects"""
|
||||
def __init__(self, x, y, sprite_index, color, name, blocks=False):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.sprite_index = sprite_index
|
||||
self.color = color
|
||||
self.name = name
|
||||
self.blocks = blocks
|
||||
self._entity = None
|
||||
self.grid = None
|
||||
|
||||
def attach_to_grid(self, grid):
|
||||
"""Attach this game object to a McRogueFace grid"""
|
||||
self.grid = grid
|
||||
self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid)
|
||||
self._entity.sprite_index = self.sprite_index
|
||||
self._entity.color = mcrfpy.Color(*self.color)
|
||||
|
||||
def move(self, dx, dy):
|
||||
"""Move by the given amount"""
|
||||
if not self.grid:
|
||||
return
|
||||
self.x += dx
|
||||
self.y += dy
|
||||
if self._entity:
|
||||
self._entity.x = self.x
|
||||
self._entity.y = self.y
|
||||
# Update FOV when player moves
|
||||
if self.name == "Player":
|
||||
self.update_fov()
|
||||
|
||||
def update_fov(self):
|
||||
"""Update field of view from this entity's position"""
|
||||
if self._entity and self.grid:
|
||||
self._entity.update_fov(radius=8)
|
||||
|
||||
class RectangularRoom:
|
||||
"""A rectangular room with its position and size"""
|
||||
|
||||
def __init__(self, x, y, width, height):
|
||||
self.x1 = x
|
||||
self.y1 = y
|
||||
self.x2 = x + width
|
||||
self.y2 = y + height
|
||||
|
||||
@property
|
||||
def center(self):
|
||||
center_x = (self.x1 + self.x2) // 2
|
||||
center_y = (self.y1 + self.y2) // 2
|
||||
return center_x, center_y
|
||||
|
||||
@property
|
||||
def inner(self):
|
||||
return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1
|
||||
|
||||
def intersects(self, other):
|
||||
return (
|
||||
self.x1 <= other.x2
|
||||
and self.x2 >= other.x1
|
||||
and self.y1 <= other.y2
|
||||
and self.y2 >= other.y1
|
||||
)
|
||||
|
||||
def tunnel_between(start, end):
|
||||
"""Return an L-shaped tunnel between two points"""
|
||||
x1, y1 = start
|
||||
x2, y2 = end
|
||||
|
||||
if random.random() < 0.5:
|
||||
corner_x = x2
|
||||
corner_y = y1
|
||||
else:
|
||||
corner_x = x1
|
||||
corner_y = y2
|
||||
|
||||
for x in range(min(x1, corner_x), max(x1, corner_x) + 1):
|
||||
yield x, y1
|
||||
for y in range(min(y1, corner_y), max(y1, corner_y) + 1):
|
||||
yield corner_x, y
|
||||
for x in range(min(corner_x, x2), max(corner_x, x2) + 1):
|
||||
yield x, corner_y
|
||||
for y in range(min(corner_y, y2), max(corner_y, y2) + 1):
|
||||
yield x2, y
|
||||
|
||||
def spawn_enemies_in_room(room, game_map, max_enemies=2):
|
||||
"""Spawn between 0 and max_enemies in a room"""
|
||||
number_of_enemies = random.randint(0, max_enemies)
|
||||
|
||||
enemies_spawned = []
|
||||
|
||||
for i in range(number_of_enemies):
|
||||
# Try to find a valid position
|
||||
attempts = 10
|
||||
while attempts > 0:
|
||||
# Random position within room bounds
|
||||
x = random.randint(room.x1 + 1, room.x2 - 1)
|
||||
y = random.randint(room.y1 + 1, room.y2 - 1)
|
||||
|
||||
# Check if position is valid
|
||||
if not game_map.is_blocked(x, y):
|
||||
# 80% chance for orc, 20% for troll
|
||||
if random.random() < 0.8:
|
||||
enemy = GameObject(x, y, 111, (63, 127, 63), "Orc", blocks=True)
|
||||
else:
|
||||
enemy = GameObject(x, y, 84, (0, 127, 0), "Troll", blocks=True)
|
||||
|
||||
game_map.add_entity(enemy)
|
||||
enemies_spawned.append(enemy)
|
||||
break
|
||||
|
||||
attempts -= 1
|
||||
|
||||
return enemies_spawned
|
||||
|
||||
class GameMap:
|
||||
"""Manages the game world"""
|
||||
|
||||
def __init__(self, width, height):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.grid = None
|
||||
self.entities = []
|
||||
self.rooms = []
|
||||
|
||||
def create_grid(self, tileset):
|
||||
"""Create the McRogueFace grid"""
|
||||
self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset)
|
||||
self.grid.position = (100, 100)
|
||||
self.grid.size = (800, 480)
|
||||
|
||||
# Enable perspective rendering
|
||||
self.grid.perspective = 0
|
||||
|
||||
return self.grid
|
||||
|
||||
def fill_with_walls(self):
|
||||
"""Fill the entire map with wall tiles"""
|
||||
for y in range(self.height):
|
||||
for x in range(self.width):
|
||||
self.set_tile(x, y, walkable=False, transparent=False,
|
||||
sprite_index=35, tile_type='wall')
|
||||
|
||||
def set_tile(self, x, y, walkable, transparent, sprite_index, tile_type):
|
||||
"""Set properties for a specific tile"""
|
||||
if 0 <= x < self.width and 0 <= y < self.height:
|
||||
cell = self.grid.at(x, y)
|
||||
cell.walkable = walkable
|
||||
cell.transparent = transparent
|
||||
cell.sprite_index = sprite_index
|
||||
cell.color = mcrfpy.Color(*COLORS_VISIBLE[tile_type])
|
||||
|
||||
def generate_dungeon(self, max_rooms, room_min_size, room_max_size, player, max_enemies_per_room):
|
||||
"""Generate a new dungeon map"""
|
||||
self.fill_with_walls()
|
||||
|
||||
for r in range(max_rooms):
|
||||
room_width = random.randint(room_min_size, room_max_size)
|
||||
room_height = random.randint(room_min_size, room_max_size)
|
||||
|
||||
x = random.randint(0, self.width - room_width - 1)
|
||||
y = random.randint(0, self.height - room_height - 1)
|
||||
|
||||
new_room = RectangularRoom(x, y, room_width, room_height)
|
||||
|
||||
if any(new_room.intersects(other_room) for other_room in self.rooms):
|
||||
continue
|
||||
|
||||
self.carve_room(new_room)
|
||||
|
||||
if len(self.rooms) == 0:
|
||||
# First room - place player
|
||||
player.x, player.y = new_room.center
|
||||
if player._entity:
|
||||
player._entity.x, player._entity.y = new_room.center
|
||||
else:
|
||||
# All other rooms - add tunnel and enemies
|
||||
self.carve_tunnel(self.rooms[-1].center, new_room.center)
|
||||
spawn_enemies_in_room(new_room, self, max_enemies_per_room)
|
||||
|
||||
self.rooms.append(new_room)
|
||||
|
||||
def carve_room(self, room):
|
||||
"""Carve out a room"""
|
||||
inner_x1, inner_y1, inner_x2, inner_y2 = room.inner
|
||||
|
||||
for y in range(inner_y1, inner_y2):
|
||||
for x in range(inner_x1, inner_x2):
|
||||
self.set_tile(x, y, walkable=True, transparent=True,
|
||||
sprite_index=46, tile_type='floor')
|
||||
|
||||
def carve_tunnel(self, start, end):
|
||||
"""Carve a tunnel between two points"""
|
||||
for x, y in tunnel_between(start, end):
|
||||
self.set_tile(x, y, walkable=True, transparent=True,
|
||||
sprite_index=46, tile_type='tunnel')
|
||||
|
||||
def get_blocking_entity_at(self, x, y):
|
||||
"""Return any blocking entity at the given position"""
|
||||
for entity in self.entities:
|
||||
if entity.blocks and entity.x == x and entity.y == y:
|
||||
return entity
|
||||
return None
|
||||
|
||||
def is_blocked(self, x, y):
|
||||
"""Check if a tile blocks movement"""
|
||||
if x < 0 or x >= self.width or y < 0 or y >= self.height:
|
||||
return True
|
||||
|
||||
if not self.grid.at(x, y).walkable:
|
||||
return True
|
||||
|
||||
if self.get_blocking_entity_at(x, y):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def add_entity(self, entity):
|
||||
"""Add a GameObject to the map"""
|
||||
self.entities.append(entity)
|
||||
entity.attach_to_grid(self.grid)
|
||||
|
||||
class Engine:
|
||||
"""Main game engine"""
|
||||
|
||||
def __init__(self):
|
||||
self.game_map = None
|
||||
self.player = None
|
||||
self.entities = []
|
||||
|
||||
mcrfpy.createScene("game")
|
||||
mcrfpy.setScene("game")
|
||||
|
||||
window = mcrfpy.Window.get()
|
||||
window.title = "McRogueFace Roguelike - Part 5"
|
||||
|
||||
self.ui = mcrfpy.sceneUI("game")
|
||||
|
||||
background = mcrfpy.Frame(0, 0, 1024, 768)
|
||||
background.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
self.ui.append(background)
|
||||
|
||||
self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16)
|
||||
|
||||
self.setup_game()
|
||||
self.setup_input()
|
||||
self.setup_ui()
|
||||
|
||||
def setup_game(self):
|
||||
"""Initialize the game world"""
|
||||
self.game_map = GameMap(80, 45)
|
||||
grid = self.game_map.create_grid(self.tileset)
|
||||
self.ui.append(grid)
|
||||
|
||||
# Create player
|
||||
self.player = GameObject(0, 0, 64, (255, 255, 255), "Player", blocks=True)
|
||||
|
||||
# Generate the dungeon
|
||||
self.game_map.generate_dungeon(
|
||||
max_rooms=30,
|
||||
room_min_size=6,
|
||||
room_max_size=10,
|
||||
player=self.player,
|
||||
max_enemies_per_room=2
|
||||
)
|
||||
|
||||
# Add player to map
|
||||
self.game_map.add_entity(self.player)
|
||||
|
||||
# Store reference to all entities
|
||||
self.entities = [e for e in self.game_map.entities if e != self.player]
|
||||
|
||||
# Initial FOV calculation
|
||||
self.player.update_fov()
|
||||
|
||||
def handle_player_turn(self, action):
|
||||
"""Process the player's action"""
|
||||
if isinstance(action, MovementAction):
|
||||
dest_x = self.player.x + action.dx
|
||||
dest_y = self.player.y + action.dy
|
||||
|
||||
# Check what's at the destination
|
||||
target = self.game_map.get_blocking_entity_at(dest_x, dest_y)
|
||||
|
||||
if target:
|
||||
# We bumped into something!
|
||||
print(f"You kick the {target.name} in the shins, much to its annoyance!")
|
||||
self.status_text.text = f"You kick the {target.name}!"
|
||||
elif not self.game_map.is_blocked(dest_x, dest_y):
|
||||
# Move the player
|
||||
self.player.move(action.dx, action.dy)
|
||||
self.status_text.text = ""
|
||||
else:
|
||||
# Bumped into a wall
|
||||
self.status_text.text = "Blocked!"
|
||||
|
||||
elif isinstance(action, WaitAction):
|
||||
self.status_text.text = "You wait..."
|
||||
|
||||
def setup_input(self):
|
||||
"""Setup keyboard input handling"""
|
||||
def handle_keys(key, state):
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
action = None
|
||||
|
||||
# Movement keys
|
||||
movement = {
|
||||
"Up": (0, -1), "Down": (0, 1),
|
||||
"Left": (-1, 0), "Right": (1, 0),
|
||||
"Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1),
|
||||
"Num4": (-1, 0), "Num5": (0, 0), "Num6": (1, 0),
|
||||
"Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1),
|
||||
}
|
||||
|
||||
if key in movement:
|
||||
dx, dy = movement[key]
|
||||
if dx == 0 and dy == 0:
|
||||
action = WaitAction()
|
||||
else:
|
||||
action = MovementAction(dx, dy)
|
||||
elif key == "Period":
|
||||
action = WaitAction()
|
||||
elif key == "Escape":
|
||||
mcrfpy.setScene(None)
|
||||
return
|
||||
|
||||
# Process the action
|
||||
if action:
|
||||
self.handle_player_turn(action)
|
||||
|
||||
mcrfpy.keypressScene(handle_keys)
|
||||
|
||||
def setup_ui(self):
|
||||
"""Setup UI elements"""
|
||||
title = mcrfpy.Caption("Placing Enemies", 512, 30)
|
||||
title.font_size = 24
|
||||
title.fill_color = mcrfpy.Color(255, 255, 100)
|
||||
self.ui.append(title)
|
||||
|
||||
instructions = mcrfpy.Caption("Arrow keys to move | . to wait | Bump into enemies! | ESC to quit", 512, 60)
|
||||
instructions.font_size = 16
|
||||
instructions.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
self.ui.append(instructions)
|
||||
|
||||
# Status text
|
||||
self.status_text = mcrfpy.Caption("", 512, 600)
|
||||
self.status_text.font_size = 18
|
||||
self.status_text.fill_color = mcrfpy.Color(255, 200, 200)
|
||||
self.ui.append(self.status_text)
|
||||
|
||||
# Entity count
|
||||
entity_count = len(self.entities)
|
||||
count_text = mcrfpy.Caption(f"Enemies: {entity_count}", 900, 100)
|
||||
count_text.font_size = 14
|
||||
count_text.fill_color = mcrfpy.Color(150, 150, 255)
|
||||
self.ui.append(count_text)
|
||||
|
||||
# Create and run the game
|
||||
engine = Engine()
|
||||
print("Part 5: Placing Enemies!")
|
||||
print("Try bumping into enemies - combat coming in Part 6!")
|
|
@ -1,568 +0,0 @@
|
|||
import mcrfpy
|
||||
import random
|
||||
|
||||
# Color configurations
|
||||
COLORS_VISIBLE = {
|
||||
'wall': (100, 100, 100),
|
||||
'floor': (50, 50, 50),
|
||||
'tunnel': (30, 30, 40),
|
||||
}
|
||||
|
||||
# Message colors
|
||||
COLOR_PLAYER_ATK = (230, 230, 230)
|
||||
COLOR_ENEMY_ATK = (255, 200, 200)
|
||||
COLOR_PLAYER_DIE = (255, 100, 100)
|
||||
COLOR_ENEMY_DIE = (255, 165, 0)
|
||||
|
||||
# Actions
|
||||
class Action:
|
||||
"""Base class for all actions"""
|
||||
pass
|
||||
|
||||
class MovementAction(Action):
|
||||
"""Action for moving an entity"""
|
||||
def __init__(self, dx, dy):
|
||||
self.dx = dx
|
||||
self.dy = dy
|
||||
|
||||
class MeleeAction(Action):
|
||||
"""Action for melee attacks"""
|
||||
def __init__(self, attacker, target):
|
||||
self.attacker = attacker
|
||||
self.target = target
|
||||
|
||||
def perform(self):
|
||||
"""Execute the attack"""
|
||||
if not self.target.is_alive:
|
||||
return None
|
||||
|
||||
damage = self.attacker.power - self.target.defense
|
||||
|
||||
if damage > 0:
|
||||
attack_desc = f"{self.attacker.name} attacks {self.target.name} for {damage} damage!"
|
||||
self.target.take_damage(damage)
|
||||
|
||||
# Choose color based on attacker
|
||||
if self.attacker.name == "Player":
|
||||
color = COLOR_PLAYER_ATK
|
||||
else:
|
||||
color = COLOR_ENEMY_ATK
|
||||
|
||||
return attack_desc, color
|
||||
else:
|
||||
attack_desc = f"{self.attacker.name} attacks {self.target.name} but does no damage."
|
||||
return attack_desc, (150, 150, 150)
|
||||
|
||||
class WaitAction(Action):
|
||||
"""Action for waiting/skipping turn"""
|
||||
pass
|
||||
|
||||
class GameObject:
|
||||
"""Base class for all game objects"""
|
||||
def __init__(self, x, y, sprite_index, color, name,
|
||||
blocks=False, hp=0, defense=0, power=0):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.sprite_index = sprite_index
|
||||
self.color = color
|
||||
self.name = name
|
||||
self.blocks = blocks
|
||||
self._entity = None
|
||||
self.grid = None
|
||||
|
||||
# Combat stats
|
||||
self.max_hp = hp
|
||||
self.hp = hp
|
||||
self.defense = defense
|
||||
self.power = power
|
||||
|
||||
@property
|
||||
def is_alive(self):
|
||||
"""Returns True if this entity can act"""
|
||||
return self.hp > 0
|
||||
|
||||
def attach_to_grid(self, grid):
|
||||
"""Attach this game object to a McRogueFace grid"""
|
||||
self.grid = grid
|
||||
self._entity = mcrfpy.Entity(x=self.x, y=self.y, grid=grid)
|
||||
self._entity.sprite_index = self.sprite_index
|
||||
self._entity.color = mcrfpy.Color(*self.color)
|
||||
|
||||
def move(self, dx, dy):
|
||||
"""Move by the given amount"""
|
||||
if not self.grid:
|
||||
return
|
||||
self.x += dx
|
||||
self.y += dy
|
||||
if self._entity:
|
||||
self._entity.x = self.x
|
||||
self._entity.y = self.y
|
||||
# Update FOV when player moves
|
||||
if self.name == "Player":
|
||||
self.update_fov()
|
||||
|
||||
def update_fov(self):
|
||||
"""Update field of view from this entity's position"""
|
||||
if self._entity and self.grid:
|
||||
self._entity.update_fov(radius=8)
|
||||
|
||||
def take_damage(self, amount):
|
||||
"""Apply damage to this entity"""
|
||||
self.hp -= amount
|
||||
|
||||
# Check for death
|
||||
if self.hp <= 0:
|
||||
self.die()
|
||||
|
||||
def die(self):
|
||||
"""Handle entity death"""
|
||||
if self.name == "Player":
|
||||
# Player death
|
||||
self.sprite_index = 64 # Stay as @
|
||||
self.color = (127, 0, 0) # Dark red
|
||||
if self._entity:
|
||||
self._entity.color = mcrfpy.Color(127, 0, 0)
|
||||
else:
|
||||
# Enemy death
|
||||
self.sprite_index = 37 # % character for corpse
|
||||
self.color = (127, 0, 0) # Dark red
|
||||
self.blocks = False # Corpses don't block
|
||||
self.name = f"remains of {self.name}"
|
||||
|
||||
if self._entity:
|
||||
self._entity.sprite_index = 37
|
||||
self._entity.color = mcrfpy.Color(127, 0, 0)
|
||||
|
||||
# Entity factories
|
||||
def create_player(x, y):
|
||||
"""Create the player entity"""
|
||||
return GameObject(
|
||||
x=x, y=y,
|
||||
sprite_index=64, # @
|
||||
color=(255, 255, 255),
|
||||
name="Player",
|
||||
blocks=True,
|
||||
hp=30,
|
||||
defense=2,
|
||||
power=5
|
||||
)
|
||||
|
||||
def create_orc(x, y):
|
||||
"""Create an orc enemy"""
|
||||
return GameObject(
|
||||
x=x, y=y,
|
||||
sprite_index=111, # o
|
||||
color=(63, 127, 63),
|
||||
name="Orc",
|
||||
blocks=True,
|
||||
hp=10,
|
||||
defense=0,
|
||||
power=3
|
||||
)
|
||||
|
||||
def create_troll(x, y):
|
||||
"""Create a troll enemy"""
|
||||
return GameObject(
|
||||
x=x, y=y,
|
||||
sprite_index=84, # T
|
||||
color=(0, 127, 0),
|
||||
name="Troll",
|
||||
blocks=True,
|
||||
hp=16,
|
||||
defense=1,
|
||||
power=4
|
||||
)
|
||||
|
||||
class RectangularRoom:
|
||||
"""A rectangular room with its position and size"""
|
||||
|
||||
def __init__(self, x, y, width, height):
|
||||
self.x1 = x
|
||||
self.y1 = y
|
||||
self.x2 = x + width
|
||||
self.y2 = y + height
|
||||
|
||||
@property
|
||||
def center(self):
|
||||
center_x = (self.x1 + self.x2) // 2
|
||||
center_y = (self.y1 + self.y2) // 2
|
||||
return center_x, center_y
|
||||
|
||||
@property
|
||||
def inner(self):
|
||||
return self.x1 + 1, self.y1 + 1, self.x2 - 1, self.y2 - 1
|
||||
|
||||
def intersects(self, other):
|
||||
return (
|
||||
self.x1 <= other.x2
|
||||
and self.x2 >= other.x1
|
||||
and self.y1 <= other.y2
|
||||
and self.y2 >= other.y1
|
||||
)
|
||||
|
||||
def tunnel_between(start, end):
|
||||
"""Return an L-shaped tunnel between two points"""
|
||||
x1, y1 = start
|
||||
x2, y2 = end
|
||||
|
||||
if random.random() < 0.5:
|
||||
corner_x = x2
|
||||
corner_y = y1
|
||||
else:
|
||||
corner_x = x1
|
||||
corner_y = y2
|
||||
|
||||
for x in range(min(x1, corner_x), max(x1, corner_x) + 1):
|
||||
yield x, y1
|
||||
for y in range(min(y1, corner_y), max(y1, corner_y) + 1):
|
||||
yield corner_x, y
|
||||
for x in range(min(corner_x, x2), max(corner_x, x2) + 1):
|
||||
yield x, corner_y
|
||||
for y in range(min(corner_y, y2), max(corner_y, y2) + 1):
|
||||
yield x2, y
|
||||
|
||||
def spawn_enemies_in_room(room, game_map, max_enemies=2):
|
||||
"""Spawn between 0 and max_enemies in a room"""
|
||||
number_of_enemies = random.randint(0, max_enemies)
|
||||
|
||||
enemies_spawned = []
|
||||
|
||||
for i in range(number_of_enemies):
|
||||
attempts = 10
|
||||
while attempts > 0:
|
||||
x = random.randint(room.x1 + 1, room.x2 - 1)
|
||||
y = random.randint(room.y1 + 1, room.y2 - 1)
|
||||
|
||||
if not game_map.is_blocked(x, y):
|
||||
# 80% chance for orc, 20% for troll
|
||||
if random.random() < 0.8:
|
||||
enemy = create_orc(x, y)
|
||||
else:
|
||||
enemy = create_troll(x, y)
|
||||
|
||||
game_map.add_entity(enemy)
|
||||
enemies_spawned.append(enemy)
|
||||
break
|
||||
|
||||
attempts -= 1
|
||||
|
||||
return enemies_spawned
|
||||
|
||||
class GameMap:
|
||||
"""Manages the game world"""
|
||||
|
||||
def __init__(self, width, height):
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.grid = None
|
||||
self.entities = []
|
||||
self.rooms = []
|
||||
|
||||
def create_grid(self, tileset):
|
||||
"""Create the McRogueFace grid"""
|
||||
self.grid = mcrfpy.Grid(grid_x=self.width, grid_y=self.height, texture=tileset)
|
||||
self.grid.position = (100, 100)
|
||||
self.grid.size = (800, 480)
|
||||
|
||||
# Enable perspective rendering
|
||||
self.grid.perspective = 0
|
||||
|
||||
return self.grid
|
||||
|
||||
def fill_with_walls(self):
|
||||
"""Fill the entire map with wall tiles"""
|
||||
for y in range(self.height):
|
||||
for x in range(self.width):
|
||||
self.set_tile(x, y, walkable=False, transparent=False,
|
||||
sprite_index=35, tile_type='wall')
|
||||
|
||||
def set_tile(self, x, y, walkable, transparent, sprite_index, tile_type):
|
||||
"""Set properties for a specific tile"""
|
||||
if 0 <= x < self.width and 0 <= y < self.height:
|
||||
cell = self.grid.at(x, y)
|
||||
cell.walkable = walkable
|
||||
cell.transparent = transparent
|
||||
cell.sprite_index = sprite_index
|
||||
cell.color = mcrfpy.Color(*COLORS_VISIBLE[tile_type])
|
||||
|
||||
def generate_dungeon(self, max_rooms, room_min_size, room_max_size, player, max_enemies_per_room):
|
||||
"""Generate a new dungeon map"""
|
||||
self.fill_with_walls()
|
||||
|
||||
for r in range(max_rooms):
|
||||
room_width = random.randint(room_min_size, room_max_size)
|
||||
room_height = random.randint(room_min_size, room_max_size)
|
||||
|
||||
x = random.randint(0, self.width - room_width - 1)
|
||||
y = random.randint(0, self.height - room_height - 1)
|
||||
|
||||
new_room = RectangularRoom(x, y, room_width, room_height)
|
||||
|
||||
if any(new_room.intersects(other_room) for other_room in self.rooms):
|
||||
continue
|
||||
|
||||
self.carve_room(new_room)
|
||||
|
||||
if len(self.rooms) == 0:
|
||||
# First room - place player
|
||||
player.x, player.y = new_room.center
|
||||
if player._entity:
|
||||
player._entity.x, player._entity.y = new_room.center
|
||||
else:
|
||||
# All other rooms - add tunnel and enemies
|
||||
self.carve_tunnel(self.rooms[-1].center, new_room.center)
|
||||
spawn_enemies_in_room(new_room, self, max_enemies_per_room)
|
||||
|
||||
self.rooms.append(new_room)
|
||||
|
||||
def carve_room(self, room):
|
||||
"""Carve out a room"""
|
||||
inner_x1, inner_y1, inner_x2, inner_y2 = room.inner
|
||||
|
||||
for y in range(inner_y1, inner_y2):
|
||||
for x in range(inner_x1, inner_x2):
|
||||
self.set_tile(x, y, walkable=True, transparent=True,
|
||||
sprite_index=46, tile_type='floor')
|
||||
|
||||
def carve_tunnel(self, start, end):
|
||||
"""Carve a tunnel between two points"""
|
||||
for x, y in tunnel_between(start, end):
|
||||
self.set_tile(x, y, walkable=True, transparent=True,
|
||||
sprite_index=46, tile_type='tunnel')
|
||||
|
||||
def get_blocking_entity_at(self, x, y):
|
||||
"""Return any blocking entity at the given position"""
|
||||
for entity in self.entities:
|
||||
if entity.blocks and entity.x == x and entity.y == y:
|
||||
return entity
|
||||
return None
|
||||
|
||||
def is_blocked(self, x, y):
|
||||
"""Check if a tile blocks movement"""
|
||||
if x < 0 or x >= self.width or y < 0 or y >= self.height:
|
||||
return True
|
||||
|
||||
if not self.grid.at(x, y).walkable:
|
||||
return True
|
||||
|
||||
if self.get_blocking_entity_at(x, y):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def add_entity(self, entity):
|
||||
"""Add a GameObject to the map"""
|
||||
self.entities.append(entity)
|
||||
entity.attach_to_grid(self.grid)
|
||||
|
||||
class Engine:
|
||||
"""Main game engine"""
|
||||
|
||||
def __init__(self):
|
||||
self.game_map = None
|
||||
self.player = None
|
||||
self.entities = []
|
||||
self.messages = [] # Simple message log
|
||||
self.max_messages = 5
|
||||
|
||||
mcrfpy.createScene("game")
|
||||
mcrfpy.setScene("game")
|
||||
|
||||
window = mcrfpy.Window.get()
|
||||
window.title = "McRogueFace Roguelike - Part 6"
|
||||
|
||||
self.ui = mcrfpy.sceneUI("game")
|
||||
|
||||
background = mcrfpy.Frame(0, 0, 1024, 768)
|
||||
background.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
self.ui.append(background)
|
||||
|
||||
self.tileset = mcrfpy.Texture("assets/sprites/ascii_tileset.png", 16, 16)
|
||||
|
||||
self.setup_game()
|
||||
self.setup_input()
|
||||
self.setup_ui()
|
||||
|
||||
def add_message(self, text, color=(255, 255, 255)):
|
||||
"""Add a message to the log"""
|
||||
self.messages.append((text, color))
|
||||
if len(self.messages) > self.max_messages:
|
||||
self.messages.pop(0)
|
||||
self.update_message_display()
|
||||
|
||||
def update_message_display(self):
|
||||
"""Update the message display"""
|
||||
# Clear old messages
|
||||
for caption in self.message_captions:
|
||||
# Remove from UI (McRogueFace doesn't have remove, so we hide it)
|
||||
caption.text = ""
|
||||
|
||||
# Display current messages
|
||||
for i, (text, color) in enumerate(self.messages):
|
||||
if i < len(self.message_captions):
|
||||
self.message_captions[i].text = text
|
||||
self.message_captions[i].fill_color = mcrfpy.Color(*color)
|
||||
|
||||
def setup_game(self):
|
||||
"""Initialize the game world"""
|
||||
self.game_map = GameMap(80, 45)
|
||||
grid = self.game_map.create_grid(self.tileset)
|
||||
self.ui.append(grid)
|
||||
|
||||
# Create player
|
||||
self.player = create_player(0, 0)
|
||||
|
||||
# Generate the dungeon
|
||||
self.game_map.generate_dungeon(
|
||||
max_rooms=30,
|
||||
room_min_size=6,
|
||||
room_max_size=10,
|
||||
player=self.player,
|
||||
max_enemies_per_room=2
|
||||
)
|
||||
|
||||
# Add player to map
|
||||
self.game_map.add_entity(self.player)
|
||||
|
||||
# Store reference to all entities
|
||||
self.entities = [e for e in self.game_map.entities if e != self.player]
|
||||
|
||||
# Initial FOV calculation
|
||||
self.player.update_fov()
|
||||
|
||||
# Welcome message
|
||||
self.add_message("Welcome to the dungeon!", (100, 100, 255))
|
||||
|
||||
def handle_player_turn(self, action):
|
||||
"""Process the player's action"""
|
||||
if not self.player.is_alive:
|
||||
return
|
||||
|
||||
if isinstance(action, MovementAction):
|
||||
dest_x = self.player.x + action.dx
|
||||
dest_y = self.player.y + action.dy
|
||||
|
||||
# Check what's at the destination
|
||||
target = self.game_map.get_blocking_entity_at(dest_x, dest_y)
|
||||
|
||||
if target:
|
||||
# Attack!
|
||||
attack = MeleeAction(self.player, target)
|
||||
result = attack.perform()
|
||||
if result:
|
||||
text, color = result
|
||||
self.add_message(text, color)
|
||||
|
||||
# Check if target died
|
||||
if not target.is_alive:
|
||||
death_msg = f"The {target.name.replace('remains of ', '')} is dead!"
|
||||
self.add_message(death_msg, COLOR_ENEMY_DIE)
|
||||
|
||||
elif not self.game_map.is_blocked(dest_x, dest_y):
|
||||
# Move the player
|
||||
self.player.move(action.dx, action.dy)
|
||||
|
||||
elif isinstance(action, WaitAction):
|
||||
pass # Do nothing
|
||||
|
||||
# Enemy turns
|
||||
self.handle_enemy_turns()
|
||||
|
||||
def handle_enemy_turns(self):
|
||||
"""Let all enemies take their turn"""
|
||||
for entity in self.entities:
|
||||
if entity.is_alive:
|
||||
# Simple AI: if player is adjacent, attack. Otherwise, do nothing.
|
||||
dx = entity.x - self.player.x
|
||||
dy = entity.y - self.player.y
|
||||
distance = abs(dx) + abs(dy)
|
||||
|
||||
if distance == 1: # Adjacent to player
|
||||
attack = MeleeAction(entity, self.player)
|
||||
result = attack.perform()
|
||||
if result:
|
||||
text, color = result
|
||||
self.add_message(text, color)
|
||||
|
||||
# Check if player died
|
||||
if not self.player.is_alive:
|
||||
self.add_message("You have died!", COLOR_PLAYER_DIE)
|
||||
|
||||
def setup_input(self):
|
||||
"""Setup keyboard input handling"""
|
||||
def handle_keys(key, state):
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
action = None
|
||||
|
||||
# Movement keys
|
||||
movement = {
|
||||
"Up": (0, -1), "Down": (0, 1),
|
||||
"Left": (-1, 0), "Right": (1, 0),
|
||||
"Num7": (-1, -1), "Num8": (0, -1), "Num9": (1, -1),
|
||||
"Num4": (-1, 0), "Num5": (0, 0), "Num6": (1, 0),
|
||||
"Num1": (-1, 1), "Num2": (0, 1), "Num3": (1, 1),
|
||||
}
|
||||
|
||||
if key in movement:
|
||||
dx, dy = movement[key]
|
||||
if dx == 0 and dy == 0:
|
||||
action = WaitAction()
|
||||
else:
|
||||
action = MovementAction(dx, dy)
|
||||
elif key == "Period":
|
||||
action = WaitAction()
|
||||
elif key == "Escape":
|
||||
mcrfpy.setScene(None)
|
||||
return
|
||||
|
||||
# Process the action
|
||||
if action:
|
||||
self.handle_player_turn(action)
|
||||
|
||||
mcrfpy.keypressScene(handle_keys)
|
||||
|
||||
def setup_ui(self):
|
||||
"""Setup UI elements"""
|
||||
title = mcrfpy.Caption("Combat System", 512, 30)
|
||||
title.font_size = 24
|
||||
title.fill_color = mcrfpy.Color(255, 255, 100)
|
||||
self.ui.append(title)
|
||||
|
||||
instructions = mcrfpy.Caption("Attack enemies by bumping into them!", 512, 60)
|
||||
instructions.font_size = 16
|
||||
instructions.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
self.ui.append(instructions)
|
||||
|
||||
# Player stats
|
||||
self.hp_text = mcrfpy.Caption(f"HP: {self.player.hp}/{self.player.max_hp}", 50, 100)
|
||||
self.hp_text.font_size = 18
|
||||
self.hp_text.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
self.ui.append(self.hp_text)
|
||||
|
||||
# Message log
|
||||
self.message_captions = []
|
||||
for i in range(self.max_messages):
|
||||
caption = mcrfpy.Caption("", 50, 620 + i * 20)
|
||||
caption.font_size = 14
|
||||
caption.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
self.ui.append(caption)
|
||||
self.message_captions.append(caption)
|
||||
|
||||
# Timer to update HP display
|
||||
def update_stats(dt):
|
||||
self.hp_text.text = f"HP: {self.player.hp}/{self.player.max_hp}"
|
||||
if self.player.hp <= 0:
|
||||
self.hp_text.fill_color = mcrfpy.Color(127, 0, 0)
|
||||
elif self.player.hp < self.player.max_hp // 3:
|
||||
self.hp_text.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
else:
|
||||
self.hp_text.fill_color = mcrfpy.Color(0, 255, 0)
|
||||
|
||||
mcrfpy.setTimer("update_stats", update_stats, 100)
|
||||
|
||||
# Create and run the game
|
||||
engine = Engine()
|
||||
print("Part 6: Combat System!")
|
||||
print("Attack enemies to defeat them, but watch your HP!")
|
|
@ -5,7 +5,6 @@
|
|||
#include "UITestScene.h"
|
||||
#include "Resources.h"
|
||||
#include "Animation.h"
|
||||
#include <cmath>
|
||||
|
||||
GameEngine::GameEngine() : GameEngine(McRogueFaceConfig{})
|
||||
{
|
||||
|
@ -36,8 +35,7 @@ GameEngine::GameEngine(const McRogueFaceConfig& cfg)
|
|||
|
||||
// Initialize the game view
|
||||
gameView.setSize(static_cast<float>(gameResolution.x), static_cast<float>(gameResolution.y));
|
||||
// Use integer center coordinates for pixel-perfect rendering
|
||||
gameView.setCenter(std::floor(gameResolution.x / 2.0f), std::floor(gameResolution.y / 2.0f));
|
||||
gameView.setCenter(gameResolution.x / 2.0f, gameResolution.y / 2.0f);
|
||||
updateViewport();
|
||||
scene = "uitest";
|
||||
scenes["uitest"] = new UITestScene(this);
|
||||
|
@ -419,8 +417,7 @@ void GameEngine::setFramerateLimit(unsigned int limit)
|
|||
void GameEngine::setGameResolution(unsigned int width, unsigned int height) {
|
||||
gameResolution = sf::Vector2u(width, height);
|
||||
gameView.setSize(static_cast<float>(width), static_cast<float>(height));
|
||||
// Use integer center coordinates for pixel-perfect rendering
|
||||
gameView.setCenter(std::floor(width / 2.0f), std::floor(height / 2.0f));
|
||||
gameView.setCenter(width / 2.0f, height / 2.0f);
|
||||
updateViewport();
|
||||
}
|
||||
|
||||
|
@ -449,9 +446,8 @@ void GameEngine::updateViewport() {
|
|||
float viewportWidth = std::min(static_cast<float>(gameResolution.x), static_cast<float>(windowSize.x));
|
||||
float viewportHeight = std::min(static_cast<float>(gameResolution.y), static_cast<float>(windowSize.y));
|
||||
|
||||
// Floor offsets to ensure integer pixel alignment
|
||||
float offsetX = std::floor((windowSize.x - viewportWidth) / 2.0f);
|
||||
float offsetY = std::floor((windowSize.y - viewportHeight) / 2.0f);
|
||||
float offsetX = (windowSize.x - viewportWidth) / 2.0f;
|
||||
float offsetY = (windowSize.y - viewportHeight) / 2.0f;
|
||||
|
||||
gameView.setViewport(sf::FloatRect(
|
||||
offsetX / windowSize.x,
|
||||
|
@ -478,21 +474,13 @@ void GameEngine::updateViewport() {
|
|||
|
||||
if (windowAspect > gameAspect) {
|
||||
// Window is wider - black bars on sides
|
||||
// Calculate viewport size in pixels and floor for pixel-perfect scaling
|
||||
float pixelHeight = static_cast<float>(windowSize.y);
|
||||
float pixelWidth = std::floor(pixelHeight * gameAspect);
|
||||
|
||||
viewportHeight = 1.0f;
|
||||
viewportWidth = pixelWidth / windowSize.x;
|
||||
viewportWidth = gameAspect / windowAspect;
|
||||
offsetX = (1.0f - viewportWidth) / 2.0f;
|
||||
} else {
|
||||
// Window is taller - black bars on top/bottom
|
||||
// Calculate viewport size in pixels and floor for pixel-perfect scaling
|
||||
float pixelWidth = static_cast<float>(windowSize.x);
|
||||
float pixelHeight = std::floor(pixelWidth / gameAspect);
|
||||
|
||||
viewportWidth = 1.0f;
|
||||
viewportHeight = pixelHeight / windowSize.y;
|
||||
viewportHeight = windowAspect / gameAspect;
|
||||
offsetY = (1.0f - viewportHeight) / 2.0f;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
#include "McRFPy_API.h"
|
||||
#include "McRFPy_Automation.h"
|
||||
#include "McRFPy_Libtcod.h"
|
||||
#include "platform.h"
|
||||
#include "PyAnimation.h"
|
||||
#include "PyDrawable.h"
|
||||
|
@ -13,7 +12,6 @@
|
|||
#include "PyScene.h"
|
||||
#include <filesystem>
|
||||
#include <cstring>
|
||||
#include <libtcod.h>
|
||||
|
||||
std::vector<sf::SoundBuffer>* McRFPy_API::soundbuffers = nullptr;
|
||||
sf::Music* McRFPy_API::music = nullptr;
|
||||
|
@ -289,21 +287,6 @@ 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) {
|
||||
|
@ -314,16 +297,6 @@ 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;
|
||||
}
|
||||
|
|
|
@ -1,324 +0,0 @@
|
|||
#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;
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
#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();
|
||||
}
|
|
@ -2,15 +2,10 @@
|
|||
#include "McRFPy_API.h"
|
||||
|
||||
PyTexture::PyTexture(std::string filename, int sprite_w, int sprite_h)
|
||||
: source(filename), sprite_width(sprite_w), sprite_height(sprite_h), sheet_width(0), sheet_height(0)
|
||||
: source(filename), sprite_width(sprite_w), sprite_height(sprite_h)
|
||||
{
|
||||
texture = sf::Texture();
|
||||
if (!texture.loadFromFile(source)) {
|
||||
// Failed to load texture - leave sheet dimensions as 0
|
||||
// This will be checked in init()
|
||||
return;
|
||||
}
|
||||
texture.setSmooth(false); // Disable smoothing for pixel art
|
||||
texture.loadFromFile(source);
|
||||
auto size = texture.getSize();
|
||||
sheet_width = (size.x / sprite_width);
|
||||
sheet_height = (size.y / sprite_height);
|
||||
|
@ -23,12 +18,6 @@ PyTexture::PyTexture(std::string filename, int sprite_w, int sprite_h)
|
|||
|
||||
sf::Sprite PyTexture::sprite(int index, sf::Vector2f pos, sf::Vector2f s)
|
||||
{
|
||||
// Protect against division by zero if texture failed to load
|
||||
if (sheet_width == 0 || sheet_height == 0) {
|
||||
// Return an empty sprite
|
||||
return sf::Sprite();
|
||||
}
|
||||
|
||||
int tx = index % sheet_width, ty = index / sheet_width;
|
||||
auto ir = sf::IntRect(tx * sprite_width, ty * sprite_height, sprite_width, sprite_height);
|
||||
auto sprite = sf::Sprite(texture, ir);
|
||||
|
@ -82,16 +71,7 @@ int PyTexture::init(PyTextureObject* self, PyObject* args, PyObject* kwds)
|
|||
int sprite_width, sprite_height;
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sii", const_cast<char**>(keywords), &filename, &sprite_width, &sprite_height))
|
||||
return -1;
|
||||
|
||||
// Create the texture object
|
||||
self->data = std::make_shared<PyTexture>(filename, sprite_width, sprite_height);
|
||||
|
||||
// Check if the texture failed to load (sheet dimensions will be 0)
|
||||
if (self->data->sheet_width == 0 || self->data->sheet_height == 0) {
|
||||
PyErr_Format(PyExc_IOError, "Failed to load texture from file: %s", filename);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
172
src/UIEntity.cpp
172
src/UIEntity.cpp
|
@ -9,52 +9,16 @@
|
|||
#include "UIEntityPyMethods.h"
|
||||
|
||||
|
||||
|
||||
UIEntity::UIEntity()
|
||||
: self(nullptr), grid(nullptr), position(0.0f, 0.0f)
|
||||
{
|
||||
// Initialize sprite with safe defaults (sprite has its own safe constructor now)
|
||||
// gridstate vector starts empty - will be lazily initialized when needed
|
||||
// gridstate vector starts empty since we don't know grid dimensions
|
||||
}
|
||||
|
||||
// Removed UIEntity(UIGrid&) constructor - using lazy initialization instead
|
||||
|
||||
void UIEntity::updateVisibility()
|
||||
UIEntity::UIEntity(UIGrid& grid)
|
||||
: gridstate(grid.grid_x * grid.grid_y)
|
||||
{
|
||||
if (!grid) return;
|
||||
|
||||
// Lazy initialize gridstate if needed
|
||||
if (gridstate.size() == 0) {
|
||||
gridstate.resize(grid->grid_x * grid->grid_y);
|
||||
// Initialize all cells as not visible/discovered
|
||||
for (auto& state : gridstate) {
|
||||
state.visible = false;
|
||||
state.discovered = false;
|
||||
}
|
||||
}
|
||||
|
||||
// First, mark all cells as not visible
|
||||
for (auto& state : gridstate) {
|
||||
state.visible = false;
|
||||
}
|
||||
|
||||
// Compute FOV from entity's position
|
||||
int x = static_cast<int>(position.x);
|
||||
int y = static_cast<int>(position.y);
|
||||
|
||||
// Use default FOV radius of 10 (can be made configurable later)
|
||||
grid->computeFOV(x, y, 10);
|
||||
|
||||
// Update visible cells based on FOV computation
|
||||
for (int gy = 0; gy < grid->grid_y; gy++) {
|
||||
for (int gx = 0; gx < grid->grid_x; gx++) {
|
||||
int idx = gy * grid->grid_x + gx;
|
||||
if (grid->isInFOV(gx, gy)) {
|
||||
gridstate[idx].visible = true;
|
||||
gridstate[idx].discovered = true; // Once seen, always discovered
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PyObject* UIEntity::at(PyUIEntityObject* self, PyObject* o) {
|
||||
|
@ -68,29 +32,17 @@ PyObject* UIEntity::at(PyUIEntityObject* self, PyObject* o) {
|
|||
PyErr_SetString(PyExc_ValueError, "Entity cannot access surroundings because it is not associated with a grid");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Lazy initialize gridstate if needed
|
||||
if (self->data->gridstate.size() == 0) {
|
||||
self->data->gridstate.resize(self->data->grid->grid_x * self->data->grid->grid_y);
|
||||
// Initialize all cells as not visible/discovered
|
||||
for (auto& state : self->data->gridstate) {
|
||||
state.visible = false;
|
||||
state.discovered = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Bounds check
|
||||
if (x < 0 || x >= self->data->grid->grid_x || y < 0 || y >= self->data->grid->grid_y) {
|
||||
PyErr_Format(PyExc_IndexError, "Grid coordinates (%d, %d) out of bounds", x, y);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
PyUIGridPointStateObject* obj = (PyUIGridPointStateObject*)((&mcrfpydef::PyUIGridPointStateType)->tp_alloc(&mcrfpydef::PyUIGridPointStateType, 0));
|
||||
*/
|
||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState");
|
||||
auto obj = (PyUIGridPointStateObject*)type->tp_alloc(type, 0);
|
||||
obj->data = &(self->data->gridstate[y * self->data->grid->grid_x + x]);
|
||||
//auto target = std::static_pointer_cast<UIEntity>(target);
|
||||
obj->data = &(self->data->gridstate[y + self->data->grid->grid_x * x]);
|
||||
obj->grid = self->data->grid;
|
||||
obj->entity = self->data;
|
||||
return (PyObject*)obj;
|
||||
|
||||
}
|
||||
|
||||
PyObject* UIEntity::index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored)) {
|
||||
|
@ -214,8 +166,10 @@ int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
|
|||
return -1;
|
||||
}
|
||||
|
||||
// Always use default constructor for lazy initialization
|
||||
self->data = std::make_shared<UIEntity>();
|
||||
if (grid_obj == NULL)
|
||||
self->data = std::make_shared<UIEntity>();
|
||||
else
|
||||
self->data = std::make_shared<UIEntity>(*((PyUIGridObject*)grid_obj)->data);
|
||||
|
||||
// Store reference to Python object
|
||||
self->data->self = (PyObject*)self;
|
||||
|
@ -237,9 +191,6 @@ int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
|
|||
self->data->grid = pygrid->data;
|
||||
// todone - on creation of Entity with Grid assignment, also append it to the entity list
|
||||
pygrid->data->entities->push_back(self->data);
|
||||
|
||||
// Don't initialize gridstate here - lazy initialization to support large numbers of entities
|
||||
// gridstate will be initialized when visibility is updated or accessed
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
@ -286,26 +237,11 @@ sf::Vector2i PyObject_to_sfVector2i(PyObject* obj) {
|
|||
return sf::Vector2i(static_cast<int>(vec->data.x), static_cast<int>(vec->data.y));
|
||||
}
|
||||
|
||||
// TODO - deprecate / remove this helper
|
||||
PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state) {
|
||||
// Create a new GridPointState Python object
|
||||
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState");
|
||||
if (!type) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
auto obj = (PyUIGridPointStateObject*)type->tp_alloc(type, 0);
|
||||
if (!obj) {
|
||||
Py_DECREF(type);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Allocate new data and copy values
|
||||
obj->data = new UIGridPointState();
|
||||
obj->data->visible = state.visible;
|
||||
obj->data->discovered = state.discovered;
|
||||
|
||||
Py_DECREF(type);
|
||||
return (PyObject*)obj;
|
||||
// This function is incomplete - it creates an empty object without setting state data
|
||||
// Should use PyObjectUtils::createGridPointState() instead
|
||||
return PyObjectUtils::createPyObjectGeneric("GridPointState");
|
||||
}
|
||||
|
||||
PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>& vec) {
|
||||
|
@ -441,75 +377,10 @@ 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;
|
||||
}
|
||||
|
||||
PyObject* UIEntity::update_visibility(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored))
|
||||
{
|
||||
self->data->updateVisibility();
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
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"},
|
||||
{"update_visibility", (PyCFunction)UIEntity::update_visibility, METH_NOARGS, "Update entity's visibility state based on current FOV"},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
|
@ -522,8 +393,6 @@ 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"},
|
||||
{"update_visibility", (PyCFunction)UIEntity::update_visibility, METH_NOARGS, "Update entity's visibility state based on current FOV"},
|
||||
{NULL} // Sentinel
|
||||
};
|
||||
|
||||
|
@ -557,12 +426,15 @@ PyObject* UIEntity::repr(PyUIEntityObject* self) {
|
|||
bool UIEntity::setProperty(const std::string& name, float value) {
|
||||
if (name == "x") {
|
||||
position.x = value;
|
||||
// Don't update sprite position here - UIGrid::render() handles the pixel positioning
|
||||
// Update sprite position based on grid position
|
||||
// Note: This is a simplified version - actual grid-to-pixel conversion depends on grid properties
|
||||
sprite.setPosition(sf::Vector2f(position.x, position.y));
|
||||
return true;
|
||||
}
|
||||
else if (name == "y") {
|
||||
position.y = value;
|
||||
// Don't update sprite position here - UIGrid::render() handles the pixel positioning
|
||||
// Update sprite position based on grid position
|
||||
sprite.setPosition(sf::Vector2f(position.x, position.y));
|
||||
return true;
|
||||
}
|
||||
else if (name == "sprite_scale") {
|
||||
|
|
|
@ -27,10 +27,10 @@ class UIGrid;
|
|||
//} PyUIEntityObject;
|
||||
|
||||
// helper methods with no namespace requirement
|
||||
PyObject* sfVector2f_to_PyObject(sf::Vector2f vector);
|
||||
sf::Vector2f PyObject_to_sfVector2f(PyObject* obj);
|
||||
PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state);
|
||||
PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>& vec);
|
||||
static PyObject* sfVector2f_to_PyObject(sf::Vector2f vector);
|
||||
static sf::Vector2f PyObject_to_sfVector2f(PyObject* obj);
|
||||
static PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state);
|
||||
static PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>& vec);
|
||||
|
||||
// TODO: make UIEntity a drawable
|
||||
class UIEntity//: public UIDrawable
|
||||
|
@ -44,9 +44,7 @@ public:
|
|||
//void render(sf::Vector2f); //override final;
|
||||
|
||||
UIEntity();
|
||||
|
||||
// Visibility methods
|
||||
void updateVisibility(); // Update gridstate from current FOV
|
||||
UIEntity(UIGrid&);
|
||||
|
||||
// Property system for animations
|
||||
bool setProperty(const std::string& name, float value);
|
||||
|
@ -61,8 +59,6 @@ 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 PyObject* update_visibility(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
|
||||
static int init(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
|
||||
|
||||
static PyObject* get_position(PyUIEntityObject* self, void* closure);
|
||||
|
|
529
src/UIGrid.cpp
529
src/UIGrid.cpp
|
@ -7,8 +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), tcod_map(nullptr), tcod_dijkstra(nullptr), tcod_path(nullptr),
|
||||
perspective(-1) // Default to omniscient view
|
||||
fill_color(8, 8, 8, 255) // Default dark gray background
|
||||
{
|
||||
// Initialize entities list
|
||||
entities = std::make_shared<std::list<std::shared_ptr<UIEntity>>>();
|
||||
|
@ -28,15 +27,13 @@ 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), tcod_map(nullptr), tcod_dijkstra(nullptr), tcod_path(nullptr),
|
||||
perspective(-1) // Default to omniscient view
|
||||
fill_color(8, 8, 8, 255) // Default dark gray background
|
||||
{
|
||||
// Use texture dimensions if available, otherwise use defaults
|
||||
int cell_width = _ptex ? _ptex->sprite_width : DEFAULT_CELL_WIDTH;
|
||||
|
@ -66,27 +63,6 @@ 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);
|
||||
|
||||
// Create TCOD A* pathfinder
|
||||
tcod_path = new TCODPath(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() {}
|
||||
|
@ -188,55 +164,43 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target)
|
|||
}
|
||||
|
||||
|
||||
// top layer - opacity for discovered / visible status based on perspective
|
||||
// Only render visibility overlay if perspective is set (not omniscient)
|
||||
if (perspective >= 0 && perspective < static_cast<int>(entities->size())) {
|
||||
// Get the entity whose perspective we're using
|
||||
auto it = entities->begin();
|
||||
std::advance(it, perspective);
|
||||
auto& entity = *it;
|
||||
|
||||
// Create rectangle for overlays
|
||||
sf::RectangleShape overlay;
|
||||
overlay.setSize(sf::Vector2f(cell_width * zoom, cell_height * zoom));
|
||||
|
||||
for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0);
|
||||
x < x_limit;
|
||||
x+=1)
|
||||
// top layer - opacity for discovered / visible status (debug, basically)
|
||||
/* // Disabled until I attach a "perspective"
|
||||
for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0);
|
||||
x < x_limit; //x < view_width;
|
||||
x+=1)
|
||||
{
|
||||
//for (float y = (top_edge >= 0 ? top_edge : 0);
|
||||
for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0);
|
||||
y < y_limit; //y < view_height;
|
||||
y+=1)
|
||||
{
|
||||
for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0);
|
||||
y < y_limit;
|
||||
y+=1)
|
||||
{
|
||||
// Skip out-of-bounds cells
|
||||
if (x < 0 || x >= grid_x || y < 0 || y >= grid_y) continue;
|
||||
|
||||
auto pixel_pos = sf::Vector2f(
|
||||
(x*cell_width - left_spritepixels) * zoom,
|
||||
(y*cell_height - top_spritepixels) * zoom );
|
||||
auto pixel_pos = sf::Vector2f(
|
||||
(x*itex->grid_size - left_spritepixels) * zoom,
|
||||
(y*itex->grid_size - top_spritepixels) * zoom );
|
||||
|
||||
// Get visibility state from entity's perspective
|
||||
int idx = y * grid_x + x;
|
||||
if (idx >= 0 && idx < static_cast<int>(entity->gridstate.size())) {
|
||||
const auto& state = entity->gridstate[idx];
|
||||
auto gridpoint = at(std::floor(x), std::floor(y));
|
||||
|
||||
overlay.setPosition(pixel_pos);
|
||||
sprite.setPosition(pixel_pos);
|
||||
|
||||
// Three overlay colors as specified:
|
||||
if (!state.discovered) {
|
||||
// Never seen - black
|
||||
overlay.setFillColor(sf::Color(0, 0, 0, 255));
|
||||
renderTexture.draw(overlay);
|
||||
} else if (!state.visible) {
|
||||
// Discovered but not currently visible - dark gray
|
||||
overlay.setFillColor(sf::Color(32, 32, 40, 192));
|
||||
renderTexture.draw(overlay);
|
||||
}
|
||||
// If visible and discovered, no overlay (fully visible)
|
||||
}
|
||||
r.setPosition(pixel_pos);
|
||||
|
||||
// visible & discovered layers for testing purposes
|
||||
if (!gridpoint.discovered) {
|
||||
r.setFillColor(sf::Color(16, 16, 20, 192)); // 255 opacity for actual blackout
|
||||
renderTexture.draw(r);
|
||||
} else if (!gridpoint.visible) {
|
||||
r.setFillColor(sf::Color(32, 32, 40, 128));
|
||||
renderTexture.draw(r);
|
||||
}
|
||||
|
||||
// overlay
|
||||
|
||||
// uisprite
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// grid lines for testing & validation
|
||||
/*
|
||||
|
@ -270,155 +234,11 @@ UIGridPoint& UIGrid::at(int x, int y)
|
|||
return points[y * grid_x + x];
|
||||
}
|
||||
|
||||
UIGrid::~UIGrid()
|
||||
{
|
||||
if (tcod_path) {
|
||||
delete tcod_path;
|
||||
tcod_path = nullptr;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
// A* pathfinding implementation
|
||||
std::vector<std::pair<int, int>> UIGrid::computeAStarPath(int x1, int y1, int x2, int y2, float diagonalCost)
|
||||
{
|
||||
std::vector<std::pair<int, int>> path;
|
||||
|
||||
// Validate inputs
|
||||
if (!tcod_map || !tcod_path ||
|
||||
x1 < 0 || x1 >= grid_x || y1 < 0 || y1 >= grid_y ||
|
||||
x2 < 0 || x2 >= grid_x || y2 < 0 || y2 >= grid_y) {
|
||||
return path; // Return empty path
|
||||
}
|
||||
|
||||
// Set diagonal cost (TCODPath doesn't take it as parameter to compute)
|
||||
// Instead, diagonal cost is set during TCODPath construction
|
||||
// For now, we'll use the default diagonal cost from the constructor
|
||||
|
||||
// Compute the path
|
||||
bool success = tcod_path->compute(x1, y1, x2, y2);
|
||||
|
||||
if (success) {
|
||||
// Get the computed path
|
||||
int pathSize = tcod_path->size();
|
||||
path.reserve(pathSize);
|
||||
|
||||
// TCOD path includes the starting position, so we start from index 0
|
||||
for (int i = 0; i < pathSize; i++) {
|
||||
int px, py;
|
||||
tcod_path->get(i, &px, &py);
|
||||
path.push_back(std::make_pair(px, py));
|
||||
}
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
// Phase 1 implementations
|
||||
sf::FloatRect UIGrid::get_bounds() const
|
||||
{
|
||||
|
@ -518,53 +338,35 @@ 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;
|
||||
|
||||
// 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
|
||||
// Case 1: Got grid size and position from helpers (tuple format)
|
||||
if (grid_size_result.valid) {
|
||||
grid_x = grid_size_result.grid_w;
|
||||
grid_y = grid_size_result.grid_h;
|
||||
|
||||
// Try to parse position and size
|
||||
auto pos_result = PyArgHelpers::parsePosition(args, kwds, &arg_idx);
|
||||
// Set position if we got it
|
||||
if (pos_result.valid) {
|
||||
x = pos_result.x;
|
||||
y = pos_result.y;
|
||||
}
|
||||
|
||||
auto size_result = PyArgHelpers::parseSize(args, kwds, &arg_idx);
|
||||
// Set size if we got it, otherwise calculate default
|
||||
if (size_result.valid) {
|
||||
w = size_result.w;
|
||||
h = size_result.h;
|
||||
} else {
|
||||
// Default size based on grid dimensions
|
||||
w = grid_x * 16.0f;
|
||||
// Default size based on grid dimensions and texture
|
||||
w = grid_x * 16.0f; // Will be recalculated if texture provided
|
||||
h = grid_y * 16.0f;
|
||||
}
|
||||
|
||||
|
@ -578,8 +380,10 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
|||
&textureObj);
|
||||
Py_DECREF(remaining_args);
|
||||
}
|
||||
// Traditional format parsing
|
||||
// Case 2: Traditional format
|
||||
else {
|
||||
PyErr_Clear(); // Clear any errors from helpers
|
||||
|
||||
static const char* keywords[] = {
|
||||
"grid_x", "grid_y", "texture", "pos", "size", "grid_size", nullptr
|
||||
};
|
||||
|
@ -602,13 +406,7 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -621,13 +419,7 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -640,13 +432,7 @@ 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
|
||||
|
@ -655,19 +441,16 @@ 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
|
||||
|
||||
// Convert PyObject texture to shared_ptr<PyTexture>
|
||||
// Convert PyObject texture to IndexTexture*
|
||||
// This requires the texture object to have been initialized similar to UISprite's texture handling
|
||||
|
||||
std::shared_ptr<PyTexture> texture_ptr = nullptr;
|
||||
|
||||
// Allow None or NULL for texture - use default texture in that case
|
||||
if (textureObj && textureObj != Py_None) {
|
||||
// Allow None for texture - use default texture in that case
|
||||
if (textureObj != Py_None) {
|
||||
//if (!PyObject_IsInstance(textureObj, (PyObject*)&PyTextureType)) {
|
||||
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;
|
||||
|
@ -675,12 +458,16 @@ 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 or texture not specified
|
||||
// Use default texture when None is provided
|
||||
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 (texture_ptr && w == grid_x * 16.0f && h == grid_y * 16.0f) {
|
||||
if (!size_result.valid && texture_ptr) {
|
||||
w = grid_x * texture_ptr->sprite_width;
|
||||
h = grid_y * texture_ptr->sprite_height;
|
||||
}
|
||||
|
@ -932,183 +719,8 @@ int UIGrid::set_fill_color(PyUIGridObject* self, PyObject* value, void* closure)
|
|||
return 0;
|
||||
}
|
||||
|
||||
PyObject* UIGrid::get_perspective(PyUIGridObject* self, void* closure)
|
||||
{
|
||||
return PyLong_FromLong(self->data->perspective);
|
||||
}
|
||||
|
||||
int UIGrid::set_perspective(PyUIGridObject* self, PyObject* value, void* closure)
|
||||
{
|
||||
long perspective = PyLong_AsLong(value);
|
||||
if (PyErr_Occurred()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Validate perspective (-1 for omniscient, or valid entity index)
|
||||
if (perspective < -1) {
|
||||
PyErr_SetString(PyExc_ValueError, "perspective must be -1 (omniscient) or a valid entity index");
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if entity index is valid (if not omniscient)
|
||||
if (perspective >= 0 && self->data->entities) {
|
||||
int entity_count = self->data->entities->size();
|
||||
if (perspective >= entity_count) {
|
||||
PyErr_Format(PyExc_IndexError, "perspective index %ld out of range (grid has %d entities)",
|
||||
perspective, entity_count);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
self->data->perspective = perspective;
|
||||
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;
|
||||
}
|
||||
|
||||
PyObject* UIGrid::py_compute_astar_path(PyUIGridObject* self, PyObject* args, PyObject* kwds)
|
||||
{
|
||||
int x1, y1, x2, y2;
|
||||
float diagonal_cost = 1.41f;
|
||||
|
||||
static char* kwlist[] = {"x1", "y1", "x2", "y2", "diagonal_cost", NULL};
|
||||
|
||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "iiii|f", kwlist,
|
||||
&x1, &y1, &x2, &y2, &diagonal_cost)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Compute A* path
|
||||
std::vector<std::pair<int, int>> path = self->data->computeAStarPath(x1, y1, x2, y2, diagonal_cost);
|
||||
|
||||
// Convert to Python list
|
||||
PyObject* path_list = PyList_New(path.size());
|
||||
for (size_t i = 0; i < path.size(); i++) {
|
||||
PyObject* pos = Py_BuildValue("(ii)", path[i].first, path[i].second);
|
||||
PyList_SetItem(path_list, i, pos); // Steals reference
|
||||
}
|
||||
|
||||
return path_list;
|
||||
}
|
||||
|
||||
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."},
|
||||
{"compute_astar_path", (PyCFunction)UIGrid::py_compute_astar_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"Compute A* path between two points. Args: x1, y1, x2, y2, diagonal_cost=1.41. Returns list of (x,y) tuples. Note: diagonal_cost is currently ignored (uses default 1.41)."},
|
||||
{NULL, NULL, 0, NULL}
|
||||
};
|
||||
|
||||
|
@ -1119,20 +731,6 @@ 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."},
|
||||
{"compute_astar_path", (PyCFunction)UIGrid::py_compute_astar_path, METH_VARARGS | METH_KEYWORDS,
|
||||
"Compute A* path between two points. Args: x1, y1, x2, y2, diagonal_cost=1.41. Returns list of (x,y) tuples. Note: diagonal_cost is currently ignored (uses default 1.41)."},
|
||||
{NULL} // Sentinel
|
||||
};
|
||||
|
||||
|
@ -1161,7 +759,6 @@ PyGetSetDef UIGrid::getsetters[] = {
|
|||
|
||||
{"texture", (getter)UIGrid::get_texture, NULL, "Texture of the grid", NULL}, //TODO 7DRL-day2-item5
|
||||
{"fill_color", (getter)UIGrid::get_fill_color, (setter)UIGrid::set_fill_color, "Background fill color of the grid", NULL},
|
||||
{"perspective", (getter)UIGrid::get_perspective, (setter)UIGrid::set_perspective, "Entity perspective index (-1 for omniscient view)", NULL},
|
||||
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIGRID},
|
||||
{"name", (getter)UIDrawable::get_name, (setter)UIDrawable::set_name, "Name for finding elements", (void*)PyObjectsEnum::UIGRID},
|
||||
UIDRAWABLE_GETSETTERS,
|
||||
|
@ -1505,16 +1102,6 @@ PyObject* UIEntityCollection::append(PyUIEntityCollectionObject* self, PyObject*
|
|||
self->data->push_back(entity->data);
|
||||
entity->data->grid = self->grid;
|
||||
|
||||
// Initialize gridstate if not already done
|
||||
if (entity->data->gridstate.size() == 0 && self->grid) {
|
||||
entity->data->gridstate.resize(self->grid->grid_x * self->grid->grid_y);
|
||||
// Initialize all cells as not visible/discovered
|
||||
for (auto& state : entity->data->gridstate) {
|
||||
state.visible = false;
|
||||
state.discovered = false;
|
||||
}
|
||||
}
|
||||
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
|
33
src/UIGrid.h
33
src/UIGrid.h
|
@ -5,7 +5,6 @@
|
|||
#include "IndexTexture.h"
|
||||
#include "Resources.h"
|
||||
#include <list>
|
||||
#include <libtcod.h>
|
||||
|
||||
#include "PyCallable.h"
|
||||
#include "PyTexture.h"
|
||||
|
@ -26,15 +25,10 @@ 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
|
||||
TCODPath* tcod_path; // A* 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);
|
||||
|
@ -42,21 +36,6 @@ 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;
|
||||
|
||||
// A* pathfinding methods
|
||||
std::vector<std::pair<int, int>> computeAStarPath(int x1, int y1, int x2, int y2, float diagonalCost = 1.41f);
|
||||
|
||||
// Phase 1 virtual method implementations
|
||||
sf::FloatRect get_bounds() const override;
|
||||
void move(float dx, float dy) override;
|
||||
|
@ -77,9 +56,6 @@ public:
|
|||
// Background rendering
|
||||
sf::Color fill_color;
|
||||
|
||||
// Perspective system - which entity's view to render (-1 = omniscient/default)
|
||||
int perspective;
|
||||
|
||||
// Property system for animations
|
||||
bool setProperty(const std::string& name, float value) override;
|
||||
bool setProperty(const std::string& name, const sf::Vector2f& value) override;
|
||||
|
@ -101,16 +77,7 @@ public:
|
|||
static PyObject* get_texture(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 PyObject* get_perspective(PyUIGridObject* self, void* closure);
|
||||
static int set_perspective(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 PyObject* py_compute_astar_path(PyUIGridObject* self, PyObject* args, PyObject* kwds);
|
||||
static PyMethodDef methods[];
|
||||
static PyGetSetDef getsetters[];
|
||||
static PyObject* get_children(PyUIGridObject* self, void* closure);
|
||||
|
|
|
@ -1,51 +1,19 @@
|
|||
#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), grid_x(-1), grid_y(-1), parent_grid(nullptr)
|
||||
tilesprite(-1), tile_overlay(-1), uisprite(-1)
|
||||
{}
|
||||
|
||||
// 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);
|
||||
|
@ -61,11 +29,6 @@ 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
|
||||
|
@ -99,12 +62,6 @@ 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,8 +40,6 @@ 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);
|
||||
|
|
|
@ -121,7 +121,7 @@ UITestScene::UITestScene(GameEngine* g) : Scene(g)
|
|||
//UIEntity test:
|
||||
// asdf
|
||||
// TODO - reimplement UISprite style rendering within UIEntity class. Entities don't have a screen pixel position, they have a grid position, and grid sets zoom when rendering them.
|
||||
auto e5a = std::make_shared<UIEntity>(); // Default constructor - lazy initialization
|
||||
auto e5a = std::make_shared<UIEntity>(*e5); // this basic constructor sucks: sprite position + zoom are irrelevant for UIEntity.
|
||||
e5a->grid = e5;
|
||||
//auto e5as = UISprite(indextex, 85, sf::Vector2f(0, 0), 1.0);
|
||||
//e5a->sprite = e5as; // will copy constructor even exist for UISprite...?
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
from text_input_widget_improved import FocusManager, TextInput
|
||||
|
||||
# Create focus manager
|
||||
focus_mgr = FocusManager()
|
||||
|
||||
# Create input field
|
||||
name_input = TextInput(
|
||||
x=50, y=100,
|
||||
width=300,
|
||||
label="Name:",
|
||||
placeholder="Enter your name",
|
||||
on_change=lambda text: print(f"Name changed to: {text}")
|
||||
)
|
||||
|
||||
tags_input = TextInput(
|
||||
x=50, y=160,
|
||||
width=300,
|
||||
label="Tags:",
|
||||
placeholder="door,chest,floor,wall",
|
||||
on_change=lambda text: print(f"Text: {text}")
|
||||
)
|
||||
|
||||
# Register with focus manager
|
||||
name_input._focus_manager = focus_mgr
|
||||
focus_mgr.register(name_input)
|
||||
|
||||
|
||||
# Create demo scene
|
||||
import mcrfpy
|
||||
|
||||
mcrfpy.createScene("text_example")
|
||||
mcrfpy.setScene("text_example")
|
||||
|
||||
ui = mcrfpy.sceneUI("text_example")
|
||||
# Add to scene
|
||||
#ui.append(name_input) # don't do this, only the internal Frame class can go into the UI; have to manage derived objects "carefully" (McRogueFace alpha anti-feature)
|
||||
name_input.add_to_scene(ui)
|
||||
tags_input.add_to_scene(ui)
|
||||
|
||||
# Handle keyboard events
|
||||
def handle_keys(key, state):
|
||||
if not focus_mgr.handle_key(key, state):
|
||||
if key == "Tab" and state == "start":
|
||||
focus_mgr.focus_next()
|
||||
|
||||
# McRogueFace alpha anti-feature: only the active scene can be given a keypress callback
|
||||
mcrfpy.keypressScene(handle_keys)
|
||||
|
|
@ -1,201 +0,0 @@
|
|||
"""
|
||||
Text Input Widget System for McRogueFace
|
||||
A reusable module for text input fields with focus management
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
|
||||
|
||||
class FocusManager:
|
||||
"""Manages focus across multiple widgets"""
|
||||
def __init__(self):
|
||||
self.widgets = []
|
||||
self.focused_widget = None
|
||||
self.focus_index = -1
|
||||
|
||||
def register(self, widget):
|
||||
"""Register a widget"""
|
||||
self.widgets.append(widget)
|
||||
if self.focused_widget is None:
|
||||
self.focus(widget)
|
||||
|
||||
def focus(self, widget):
|
||||
"""Set focus to widget"""
|
||||
if self.focused_widget:
|
||||
self.focused_widget.on_blur()
|
||||
|
||||
self.focused_widget = widget
|
||||
self.focus_index = self.widgets.index(widget) if widget in self.widgets else -1
|
||||
|
||||
if widget:
|
||||
widget.on_focus()
|
||||
|
||||
def focus_next(self):
|
||||
"""Focus next widget"""
|
||||
if not self.widgets:
|
||||
return
|
||||
self.focus_index = (self.focus_index + 1) % len(self.widgets)
|
||||
self.focus(self.widgets[self.focus_index])
|
||||
|
||||
def focus_prev(self):
|
||||
"""Focus previous widget"""
|
||||
if not self.widgets:
|
||||
return
|
||||
self.focus_index = (self.focus_index - 1) % len(self.widgets)
|
||||
self.focus(self.widgets[self.focus_index])
|
||||
|
||||
def handle_key(self, key):
|
||||
"""Send key to focused widget"""
|
||||
if self.focused_widget:
|
||||
return self.focused_widget.handle_key(key)
|
||||
return False
|
||||
|
||||
|
||||
class TextInput:
|
||||
"""Text input field widget"""
|
||||
def __init__(self, x, y, width, height=24, label="", placeholder="", on_change=None):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.label = label
|
||||
self.placeholder = placeholder
|
||||
self.on_change = on_change
|
||||
|
||||
# Text state
|
||||
self.text = ""
|
||||
self.cursor_pos = 0
|
||||
self.focused = False
|
||||
|
||||
# Visual elements
|
||||
self._create_ui()
|
||||
|
||||
def _create_ui(self):
|
||||
"""Create UI components"""
|
||||
# Background frame
|
||||
self.frame = mcrfpy.Frame(self.x, self.y, self.width, self.height)
|
||||
self.frame.fill_color = (255, 255, 255, 255)
|
||||
self.frame.outline_color = (128, 128, 128, 255)
|
||||
self.frame.outline = 2
|
||||
|
||||
# Label (above input)
|
||||
if self.label:
|
||||
self.label_text = mcrfpy.Caption(self.label, self.x, self.y - 20)
|
||||
self.label_text.fill_color = (255, 255, 255, 255)
|
||||
|
||||
# Text content
|
||||
self.text_display = mcrfpy.Caption("", self.x + 4, self.y + 4)
|
||||
self.text_display.fill_color = (0, 0, 0, 255)
|
||||
|
||||
# Placeholder text
|
||||
if self.placeholder:
|
||||
self.placeholder_text = mcrfpy.Caption(self.placeholder, self.x + 4, self.y + 4)
|
||||
self.placeholder_text.fill_color = (180, 180, 180, 255)
|
||||
|
||||
# Cursor
|
||||
self.cursor = mcrfpy.Frame(self.x + 4, self.y + 4, 2, self.height - 8)
|
||||
self.cursor.fill_color = (0, 0, 0, 255)
|
||||
self.cursor.visible = False
|
||||
|
||||
# Click handler
|
||||
self.frame.click = self._on_click
|
||||
|
||||
def _on_click(self, x, y, button, state):
|
||||
"""Handle mouse clicks"""
|
||||
print(self, x, y, button, state)
|
||||
if button == "left" and hasattr(self, '_focus_manager'):
|
||||
self._focus_manager.focus(self)
|
||||
|
||||
def on_focus(self):
|
||||
"""Called when focused"""
|
||||
self.focused = True
|
||||
self.frame.outline_color = (0, 120, 255, 255)
|
||||
self.frame.outline = 3
|
||||
self.cursor.visible = True
|
||||
self._update_display()
|
||||
|
||||
def on_blur(self):
|
||||
"""Called when focus lost"""
|
||||
self.focused = False
|
||||
self.frame.outline_color = (128, 128, 128, 255)
|
||||
self.frame.outline = 2
|
||||
self.cursor.visible = False
|
||||
self._update_display()
|
||||
|
||||
def handle_key(self, key):
|
||||
"""Process keyboard input"""
|
||||
if not self.focused:
|
||||
return False
|
||||
|
||||
old_text = self.text
|
||||
handled = True
|
||||
|
||||
# Navigation and editing keys
|
||||
if key == "BackSpace":
|
||||
if self.cursor_pos > 0:
|
||||
self.text = self.text[:self.cursor_pos-1] + self.text[self.cursor_pos:]
|
||||
self.cursor_pos -= 1
|
||||
elif key == "Delete":
|
||||
if self.cursor_pos < len(self.text):
|
||||
self.text = self.text[:self.cursor_pos] + self.text[self.cursor_pos+1:]
|
||||
elif key == "Left":
|
||||
self.cursor_pos = max(0, self.cursor_pos - 1)
|
||||
elif key == "Right":
|
||||
self.cursor_pos = min(len(self.text), self.cursor_pos + 1)
|
||||
elif key == "Home":
|
||||
self.cursor_pos = 0
|
||||
elif key == "End":
|
||||
self.cursor_pos = len(self.text)
|
||||
elif key in ("Tab", "Return"):
|
||||
handled = False # Let parent handle
|
||||
elif len(key) == 1 and key.isprintable():
|
||||
self.text = self.text[:self.cursor_pos] + key + self.text[self.cursor_pos:]
|
||||
self.cursor_pos += 1
|
||||
else:
|
||||
handled = False
|
||||
|
||||
# Update if changed
|
||||
if old_text != self.text:
|
||||
self._update_display()
|
||||
if self.on_change:
|
||||
self.on_change(self.text)
|
||||
elif handled:
|
||||
self._update_cursor()
|
||||
|
||||
return handled
|
||||
|
||||
def _update_display(self):
|
||||
"""Update visual state"""
|
||||
# Show/hide placeholder
|
||||
if hasattr(self, 'placeholder_text'):
|
||||
self.placeholder_text.visible = (self.text == "" and not self.focused)
|
||||
|
||||
# Update text
|
||||
self.text_display.text = self.text
|
||||
self._update_cursor()
|
||||
|
||||
def _update_cursor(self):
|
||||
"""Update cursor position"""
|
||||
if self.focused:
|
||||
# Estimate position (10 pixels per character)
|
||||
self.cursor.x = self.x + 4 + (self.cursor_pos * 10)
|
||||
|
||||
def set_text(self, text):
|
||||
"""Set text programmatically"""
|
||||
self.text = text
|
||||
self.cursor_pos = len(text)
|
||||
self._update_display()
|
||||
|
||||
def get_text(self):
|
||||
"""Get current text"""
|
||||
return self.text
|
||||
|
||||
def add_to_scene(self, scene):
|
||||
"""Add all components to scene"""
|
||||
scene.append(self.frame)
|
||||
if hasattr(self, 'label_text'):
|
||||
scene.append(self.label_text)
|
||||
if hasattr(self, 'placeholder_text'):
|
||||
scene.append(self.placeholder_text)
|
||||
scene.append(self.text_display)
|
||||
scene.append(self.cursor)
|
|
@ -1,265 +0,0 @@
|
|||
"""
|
||||
Improved Text Input Widget System for McRogueFace
|
||||
Uses proper parent-child frame structure and handles keyboard input correctly
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
|
||||
|
||||
class FocusManager:
|
||||
"""Manages focus across multiple widgets"""
|
||||
def __init__(self):
|
||||
self.widgets = []
|
||||
self.focused_widget = None
|
||||
self.focus_index = -1
|
||||
# Global keyboard state
|
||||
self.shift_pressed = False
|
||||
self.caps_lock = False
|
||||
|
||||
def register(self, widget):
|
||||
"""Register a widget"""
|
||||
self.widgets.append(widget)
|
||||
if self.focused_widget is None:
|
||||
self.focus(widget)
|
||||
|
||||
def focus(self, widget):
|
||||
"""Set focus to widget"""
|
||||
if self.focused_widget:
|
||||
self.focused_widget.on_blur()
|
||||
|
||||
self.focused_widget = widget
|
||||
self.focus_index = self.widgets.index(widget) if widget in self.widgets else -1
|
||||
|
||||
if widget:
|
||||
widget.on_focus()
|
||||
|
||||
def focus_next(self):
|
||||
"""Focus next widget"""
|
||||
if not self.widgets:
|
||||
return
|
||||
self.focus_index = (self.focus_index + 1) % len(self.widgets)
|
||||
self.focus(self.widgets[self.focus_index])
|
||||
|
||||
def focus_prev(self):
|
||||
"""Focus previous widget"""
|
||||
if not self.widgets:
|
||||
return
|
||||
self.focus_index = (self.focus_index - 1) % len(self.widgets)
|
||||
self.focus(self.widgets[self.focus_index])
|
||||
|
||||
def handle_key(self, key, state):
|
||||
"""Send key to focused widget"""
|
||||
# Track shift state
|
||||
if key == "LShift" or key == "RShift":
|
||||
self.shift_pressed = True
|
||||
return True
|
||||
elif key == "start": # Key release for shift
|
||||
self.shift_pressed = False
|
||||
return True
|
||||
elif key == "CapsLock":
|
||||
self.caps_lock = not self.caps_lock
|
||||
return True
|
||||
|
||||
if self.focused_widget:
|
||||
return self.focused_widget.handle_key(key, self.shift_pressed, self.caps_lock)
|
||||
return False
|
||||
|
||||
|
||||
class TextInput:
|
||||
"""Text input field widget with proper parent-child structure"""
|
||||
def __init__(self, x, y, width, height=24, label="", placeholder="", on_change=None):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.label = label
|
||||
self.placeholder = placeholder
|
||||
self.on_change = on_change
|
||||
|
||||
# Text state
|
||||
self.text = ""
|
||||
self.cursor_pos = 0
|
||||
self.focused = False
|
||||
|
||||
# Create the widget structure
|
||||
self._create_ui()
|
||||
|
||||
def _create_ui(self):
|
||||
"""Create UI components with proper parent-child structure"""
|
||||
# Parent frame that contains everything
|
||||
self.parent_frame = mcrfpy.Frame(self.x, self.y - (20 if self.label else 0),
|
||||
self.width, self.height + (20 if self.label else 0))
|
||||
self.parent_frame.fill_color = (0, 0, 0, 0) # Transparent parent
|
||||
|
||||
# Input frame (relative to parent)
|
||||
self.frame = mcrfpy.Frame(0, 20 if self.label else 0, self.width, self.height)
|
||||
self.frame.fill_color = (255, 255, 255, 255)
|
||||
self.frame.outline_color = (128, 128, 128, 255)
|
||||
self.frame.outline = 2
|
||||
|
||||
# Label (relative to parent)
|
||||
if self.label:
|
||||
self.label_text = mcrfpy.Caption(self.label, 0, 0)
|
||||
self.label_text.fill_color = (255, 255, 255, 255)
|
||||
self.parent_frame.children.append(self.label_text)
|
||||
|
||||
# Text content (relative to input frame)
|
||||
self.text_display = mcrfpy.Caption("", 4, 4)
|
||||
self.text_display.fill_color = (0, 0, 0, 255)
|
||||
|
||||
# Placeholder text (relative to input frame)
|
||||
if self.placeholder:
|
||||
self.placeholder_text = mcrfpy.Caption(self.placeholder, 4, 4)
|
||||
self.placeholder_text.fill_color = (180, 180, 180, 255)
|
||||
self.frame.children.append(self.placeholder_text)
|
||||
|
||||
# Cursor (relative to input frame)
|
||||
# Experiment: replacing cursor frame with an inline text character
|
||||
#self.cursor = mcrfpy.Frame(4, 4, 2, self.height - 8)
|
||||
#self.cursor.fill_color = (0, 0, 0, 255)
|
||||
#self.cursor.visible = False
|
||||
|
||||
# Add children to input frame
|
||||
self.frame.children.append(self.text_display)
|
||||
#self.frame.children.append(self.cursor)
|
||||
|
||||
# Add input frame to parent
|
||||
self.parent_frame.children.append(self.frame)
|
||||
|
||||
# Click handler on the input frame
|
||||
self.frame.click = self._on_click
|
||||
|
||||
def _on_click(self, x, y, button, state):
|
||||
"""Handle mouse clicks"""
|
||||
print(f"{x=} {y=} {button=} {state=}")
|
||||
if button == "left" and hasattr(self, '_focus_manager'):
|
||||
self._focus_manager.focus(self)
|
||||
|
||||
def on_focus(self):
|
||||
"""Called when focused"""
|
||||
self.focused = True
|
||||
self.frame.outline_color = (0, 120, 255, 255)
|
||||
self.frame.outline = 3
|
||||
#self.cursor.visible = True
|
||||
self._update_display()
|
||||
|
||||
def on_blur(self):
|
||||
"""Called when focus lost"""
|
||||
self.focused = False
|
||||
self.frame.outline_color = (128, 128, 128, 255)
|
||||
self.frame.outline = 2
|
||||
#self.cursor.visible = False
|
||||
self._update_display()
|
||||
|
||||
def handle_key(self, key, shift_pressed, caps_lock):
|
||||
"""Process keyboard input with shift state"""
|
||||
if not self.focused:
|
||||
return False
|
||||
|
||||
old_text = self.text
|
||||
handled = True
|
||||
|
||||
# Special key mappings for shifted characters
|
||||
shift_map = {
|
||||
"1": "!", "2": "@", "3": "#", "4": "$", "5": "%",
|
||||
"6": "^", "7": "&", "8": "*", "9": "(", "0": ")",
|
||||
"-": "_", "=": "+", "[": "{", "]": "}", "\\": "|",
|
||||
";": ":", "'": '"', ",": "<", ".": ">", "/": "?",
|
||||
"`": "~"
|
||||
}
|
||||
|
||||
# Navigation and editing keys
|
||||
if key == "BackSpace":
|
||||
if self.cursor_pos > 0:
|
||||
self.text = self.text[:self.cursor_pos-1] + self.text[self.cursor_pos:]
|
||||
self.cursor_pos -= 1
|
||||
elif key == "Delete":
|
||||
if self.cursor_pos < len(self.text):
|
||||
self.text = self.text[:self.cursor_pos] + self.text[self.cursor_pos+1:]
|
||||
elif key == "Left":
|
||||
self.cursor_pos = max(0, self.cursor_pos - 1)
|
||||
elif key == "Right":
|
||||
self.cursor_pos = min(len(self.text), self.cursor_pos + 1)
|
||||
elif key == "Home":
|
||||
self.cursor_pos = 0
|
||||
elif key == "End":
|
||||
self.cursor_pos = len(self.text)
|
||||
elif key == "Space":
|
||||
self._insert_at_cursor(" ")
|
||||
elif key in ("Tab", "Return"):
|
||||
handled = False # Let parent handle
|
||||
# Handle number keys with "Num" prefix
|
||||
elif key.startswith("Num") and len(key) == 4:
|
||||
num = key[3] # Get the digit after "Num"
|
||||
if shift_pressed and num in shift_map:
|
||||
self._insert_at_cursor(shift_map[num])
|
||||
else:
|
||||
self._insert_at_cursor(num)
|
||||
# Handle single character keys
|
||||
elif len(key) == 1:
|
||||
char = key
|
||||
# Apply shift transformations
|
||||
if shift_pressed:
|
||||
if char in shift_map:
|
||||
char = shift_map[char]
|
||||
elif char.isalpha():
|
||||
char = char.upper()
|
||||
else:
|
||||
# Apply caps lock for letters
|
||||
if char.isalpha():
|
||||
if caps_lock:
|
||||
char = char.upper()
|
||||
else:
|
||||
char = char.lower()
|
||||
self._insert_at_cursor(char)
|
||||
else:
|
||||
# Unhandled key - print for debugging
|
||||
print(f"[TextInput] Unhandled key: '{key}' (shift={shift_pressed}, caps={caps_lock})")
|
||||
handled = False
|
||||
|
||||
# Update if changed
|
||||
if old_text != self.text:
|
||||
self._update_display()
|
||||
if self.on_change:
|
||||
self.on_change(self.text)
|
||||
elif handled:
|
||||
self._update_cursor()
|
||||
|
||||
return handled
|
||||
|
||||
def _insert_at_cursor(self, char):
|
||||
"""Insert a character at the cursor position"""
|
||||
self.text = self.text[:self.cursor_pos] + char + self.text[self.cursor_pos:]
|
||||
self.cursor_pos += 1
|
||||
|
||||
def _update_display(self):
|
||||
"""Update visual state"""
|
||||
# Show/hide placeholder
|
||||
if hasattr(self, 'placeholder_text'):
|
||||
self.placeholder_text.visible = (self.text == "" and not self.focused)
|
||||
|
||||
# Update text
|
||||
self.text_display.text = self.text[:self.cursor_pos] + "|" + self.text[self.cursor_pos:]
|
||||
self._update_cursor()
|
||||
|
||||
def _update_cursor(self):
|
||||
"""Update cursor position"""
|
||||
if self.focused:
|
||||
# Estimate position (10 pixels per character)
|
||||
#self.cursor.x = 4 + (self.cursor_pos * 10)
|
||||
self.text_display.text = self.text[:self.cursor_pos] + "|" + self.text[self.cursor_pos:]
|
||||
pass
|
||||
|
||||
def set_text(self, text):
|
||||
"""Set text programmatically"""
|
||||
self.text = text
|
||||
self.cursor_pos = len(text)
|
||||
self._update_display()
|
||||
|
||||
def get_text(self):
|
||||
"""Get current text"""
|
||||
return self.text
|
||||
|
||||
def add_to_scene(self, scene):
|
||||
"""Add only the parent frame to scene"""
|
||||
scene.append(self.parent_frame)
|
|
@ -1,208 +1,165 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Animation Demo: Grid Center & Entity Movement
|
||||
=============================================
|
||||
|
||||
Demonstrates:
|
||||
- Animated grid centering following entity
|
||||
- Smooth entity movement along paths
|
||||
- Perspective shifts with zoom transitions
|
||||
- Field of view updates
|
||||
"""
|
||||
"""Animation System Demo - Shows all animation capabilities"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
import math
|
||||
|
||||
# Setup scene
|
||||
mcrfpy.createScene("anim_demo")
|
||||
# Create main scene
|
||||
mcrfpy.createScene("animation_demo")
|
||||
ui = mcrfpy.sceneUI("animation_demo")
|
||||
mcrfpy.setScene("animation_demo")
|
||||
|
||||
# Create grid
|
||||
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
|
||||
grid.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
|
||||
# Simple map
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
cell = grid.at(x, y)
|
||||
# Create walls around edges and some obstacles
|
||||
if x == 0 or x == 29 or y == 0 or y == 19:
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.color = mcrfpy.Color(40, 30, 30)
|
||||
elif (x == 10 and 5 <= y <= 15) or (y == 10 and 5 <= x <= 25):
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.color = mcrfpy.Color(60, 40, 40)
|
||||
else:
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.color = mcrfpy.Color(80, 80, 100)
|
||||
|
||||
# Create entities
|
||||
player = mcrfpy.Entity(5, 5, grid=grid)
|
||||
player.sprite_index = 64 # @
|
||||
|
||||
enemy = mcrfpy.Entity(25, 15, grid=grid)
|
||||
enemy.sprite_index = 69 # E
|
||||
|
||||
# Update visibility
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
|
||||
# UI setup
|
||||
ui = mcrfpy.sceneUI("anim_demo")
|
||||
ui.append(grid)
|
||||
grid.position = (100, 100)
|
||||
grid.size = (600, 400)
|
||||
|
||||
title = mcrfpy.Caption("Animation Demo - Grid Center & Entity Movement", 200, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
# Title
|
||||
title = mcrfpy.Caption((400, 30), "McRogueFace Animation System Demo", mcrfpy.default_font)
|
||||
title.size = 24
|
||||
title.fill_color = (255, 255, 255)
|
||||
# Note: centered property doesn't exist for Caption
|
||||
ui.append(title)
|
||||
|
||||
status = mcrfpy.Caption("Press 1: Move Player | 2: Move Enemy | 3: Perspective Shift | Q: Quit", 100, 50)
|
||||
status.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(status)
|
||||
# 1. Position Animation Demo
|
||||
pos_frame = mcrfpy.Frame(50, 100, 80, 80)
|
||||
pos_frame.fill_color = (255, 100, 100)
|
||||
pos_frame.outline = 2
|
||||
ui.append(pos_frame)
|
||||
|
||||
info = mcrfpy.Caption("Perspective: Player", 500, 70)
|
||||
info.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
pos_label = mcrfpy.Caption((50, 80), "Position Animation", mcrfpy.default_font)
|
||||
pos_label.fill_color = (200, 200, 200)
|
||||
ui.append(pos_label)
|
||||
|
||||
# 2. Size Animation Demo
|
||||
size_frame = mcrfpy.Frame(200, 100, 50, 50)
|
||||
size_frame.fill_color = (100, 255, 100)
|
||||
size_frame.outline = 2
|
||||
ui.append(size_frame)
|
||||
|
||||
size_label = mcrfpy.Caption((200, 80), "Size Animation", mcrfpy.default_font)
|
||||
size_label.fill_color = (200, 200, 200)
|
||||
ui.append(size_label)
|
||||
|
||||
# 3. Color Animation Demo
|
||||
color_frame = mcrfpy.Frame(350, 100, 80, 80)
|
||||
color_frame.fill_color = (255, 0, 0)
|
||||
ui.append(color_frame)
|
||||
|
||||
color_label = mcrfpy.Caption((350, 80), "Color Animation", mcrfpy.default_font)
|
||||
color_label.fill_color = (200, 200, 200)
|
||||
ui.append(color_label)
|
||||
|
||||
# 4. Easing Functions Demo
|
||||
easing_y = 250
|
||||
easing_frames = []
|
||||
easings = ["linear", "easeIn", "easeOut", "easeInOut", "easeInElastic", "easeOutBounce"]
|
||||
|
||||
for i, easing in enumerate(easings):
|
||||
x = 50 + i * 120
|
||||
|
||||
frame = mcrfpy.Frame(x, easing_y, 20, 20)
|
||||
frame.fill_color = (100, 150, 255)
|
||||
ui.append(frame)
|
||||
easing_frames.append((frame, easing))
|
||||
|
||||
label = mcrfpy.Caption((x, easing_y - 20), easing, mcrfpy.default_font)
|
||||
label.size = 12
|
||||
label.fill_color = (200, 200, 200)
|
||||
ui.append(label)
|
||||
|
||||
# 5. Complex Animation Demo
|
||||
complex_frame = mcrfpy.Frame(300, 350, 100, 100)
|
||||
complex_frame.fill_color = (128, 128, 255)
|
||||
complex_frame.outline = 3
|
||||
ui.append(complex_frame)
|
||||
|
||||
complex_label = mcrfpy.Caption((300, 330), "Complex Multi-Property", mcrfpy.default_font)
|
||||
complex_label.fill_color = (200, 200, 200)
|
||||
ui.append(complex_label)
|
||||
|
||||
# Start animations
|
||||
def start_animations(runtime):
|
||||
# 1. Position animation - back and forth
|
||||
x_anim = mcrfpy.Animation("x", 500.0, 3.0, "easeInOut")
|
||||
x_anim.start(pos_frame)
|
||||
|
||||
# 2. Size animation - pulsing
|
||||
w_anim = mcrfpy.Animation("w", 150.0, 2.0, "easeInOut")
|
||||
h_anim = mcrfpy.Animation("h", 150.0, 2.0, "easeInOut")
|
||||
w_anim.start(size_frame)
|
||||
h_anim.start(size_frame)
|
||||
|
||||
# 3. Color animation - rainbow cycle
|
||||
color_anim = mcrfpy.Animation("fill_color", (0, 255, 255, 255), 2.0, "linear")
|
||||
color_anim.start(color_frame)
|
||||
|
||||
# 4. Easing demos - all move up with different easings
|
||||
for frame, easing in easing_frames:
|
||||
y_anim = mcrfpy.Animation("y", 150.0, 2.0, easing)
|
||||
y_anim.start(frame)
|
||||
|
||||
# 5. Complex animation - multiple properties
|
||||
cx_anim = mcrfpy.Animation("x", 500.0, 4.0, "easeInOut")
|
||||
cy_anim = mcrfpy.Animation("y", 400.0, 4.0, "easeOut")
|
||||
cw_anim = mcrfpy.Animation("w", 150.0, 4.0, "easeInElastic")
|
||||
ch_anim = mcrfpy.Animation("h", 150.0, 4.0, "easeInElastic")
|
||||
outline_anim = mcrfpy.Animation("outline", 10.0, 4.0, "linear")
|
||||
|
||||
cx_anim.start(complex_frame)
|
||||
cy_anim.start(complex_frame)
|
||||
cw_anim.start(complex_frame)
|
||||
ch_anim.start(complex_frame)
|
||||
outline_anim.start(complex_frame)
|
||||
|
||||
# Individual color component animations
|
||||
r_anim = mcrfpy.Animation("fill_color.r", 255.0, 4.0, "easeInOut")
|
||||
g_anim = mcrfpy.Animation("fill_color.g", 100.0, 4.0, "easeInOut")
|
||||
b_anim = mcrfpy.Animation("fill_color.b", 50.0, 4.0, "easeInOut")
|
||||
|
||||
r_anim.start(complex_frame)
|
||||
g_anim.start(complex_frame)
|
||||
b_anim.start(complex_frame)
|
||||
|
||||
print("All animations started!")
|
||||
|
||||
# Reverse some animations
|
||||
def reverse_animations(runtime):
|
||||
# Position back
|
||||
x_anim = mcrfpy.Animation("x", 50.0, 3.0, "easeInOut")
|
||||
x_anim.start(pos_frame)
|
||||
|
||||
# Size back
|
||||
w_anim = mcrfpy.Animation("w", 50.0, 2.0, "easeInOut")
|
||||
h_anim = mcrfpy.Animation("h", 50.0, 2.0, "easeInOut")
|
||||
w_anim.start(size_frame)
|
||||
h_anim.start(size_frame)
|
||||
|
||||
# Color cycle continues
|
||||
color_anim = mcrfpy.Animation("fill_color", (255, 0, 255, 255), 2.0, "linear")
|
||||
color_anim.start(color_frame)
|
||||
|
||||
# Easing frames back down
|
||||
for frame, easing in easing_frames:
|
||||
y_anim = mcrfpy.Animation("y", 250.0, 2.0, easing)
|
||||
y_anim.start(frame)
|
||||
|
||||
# Continue color cycle
|
||||
def cycle_colors(runtime):
|
||||
color_anim = mcrfpy.Animation("fill_color", (255, 255, 0, 255), 2.0, "linear")
|
||||
color_anim.start(color_frame)
|
||||
|
||||
# Info text
|
||||
info = mcrfpy.Caption((400, 550), "Watch as different properties animate with various easing functions!", mcrfpy.default_font)
|
||||
info.fill_color = (255, 255, 200)
|
||||
# Note: centered property doesn't exist for Caption
|
||||
ui.append(info)
|
||||
|
||||
# Movement functions
|
||||
def move_player_demo():
|
||||
"""Demo player movement with camera follow"""
|
||||
# Calculate path to a destination
|
||||
path = player.path_to(20, 10)
|
||||
if not path:
|
||||
status.text = "No path available!"
|
||||
return
|
||||
# Schedule animations
|
||||
mcrfpy.setTimer("start", start_animations, 500)
|
||||
mcrfpy.setTimer("reverse", reverse_animations, 4000)
|
||||
mcrfpy.setTimer("cycle", cycle_colors, 2500)
|
||||
|
||||
status.text = f"Moving player along {len(path)} steps..."
|
||||
# Exit handler
|
||||
def on_key(key):
|
||||
if key == "Escape":
|
||||
mcrfpy.exit()
|
||||
|
||||
# Animate along path
|
||||
for i, (x, y) in enumerate(path[:5]): # First 5 steps
|
||||
delay = i * 500 # 500ms between steps
|
||||
mcrfpy.keypressScene(on_key)
|
||||
|
||||
# Schedule movement
|
||||
def move_step(dt, px=x, py=y):
|
||||
# Animate entity position
|
||||
anim_x = mcrfpy.Animation("x", float(px), 0.4, "easeInOut")
|
||||
anim_y = mcrfpy.Animation("y", float(py), 0.4, "easeInOut")
|
||||
anim_x.start(player)
|
||||
anim_y.start(player)
|
||||
|
||||
# Update visibility
|
||||
player.update_visibility()
|
||||
|
||||
# Animate camera to follow
|
||||
center_x = px * 16 # Assuming 16x16 tiles
|
||||
center_y = py * 16
|
||||
cam_anim = mcrfpy.Animation("center", (center_x, center_y), 0.4, "easeOut")
|
||||
cam_anim.start(grid)
|
||||
|
||||
mcrfpy.setTimer(f"player_move_{i}", move_step, delay)
|
||||
|
||||
def move_enemy_demo():
|
||||
"""Demo enemy movement"""
|
||||
# Calculate path
|
||||
path = enemy.path_to(10, 5)
|
||||
if not path:
|
||||
status.text = "Enemy has no path!"
|
||||
return
|
||||
|
||||
status.text = f"Moving enemy along {len(path)} steps..."
|
||||
|
||||
# Animate along path
|
||||
for i, (x, y) in enumerate(path[:5]): # First 5 steps
|
||||
delay = i * 500
|
||||
|
||||
def move_step(dt, ex=x, ey=y):
|
||||
anim_x = mcrfpy.Animation("x", float(ex), 0.4, "easeInOut")
|
||||
anim_y = mcrfpy.Animation("y", float(ey), 0.4, "easeInOut")
|
||||
anim_x.start(enemy)
|
||||
anim_y.start(enemy)
|
||||
enemy.update_visibility()
|
||||
|
||||
# If following enemy, update camera
|
||||
if grid.perspective == 1:
|
||||
center_x = ex * 16
|
||||
center_y = ey * 16
|
||||
cam_anim = mcrfpy.Animation("center", (center_x, center_y), 0.4, "easeOut")
|
||||
cam_anim.start(grid)
|
||||
|
||||
mcrfpy.setTimer(f"enemy_move_{i}", move_step, delay)
|
||||
|
||||
def perspective_shift_demo():
|
||||
"""Demo dramatic perspective shift"""
|
||||
status.text = "Perspective shift in progress..."
|
||||
|
||||
# Phase 1: Zoom out
|
||||
zoom_out = mcrfpy.Animation("zoom", 0.5, 1.5, "easeInExpo")
|
||||
zoom_out.start(grid)
|
||||
|
||||
# Phase 2: Switch perspective at peak
|
||||
def switch_perspective(dt):
|
||||
if grid.perspective == 0:
|
||||
grid.perspective = 1
|
||||
info.text = "Perspective: Enemy"
|
||||
info.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
target = enemy
|
||||
else:
|
||||
grid.perspective = 0
|
||||
info.text = "Perspective: Player"
|
||||
info.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
target = player
|
||||
|
||||
# Update camera to new target
|
||||
center_x = target.x * 16
|
||||
center_y = target.y * 16
|
||||
cam_anim = mcrfpy.Animation("center", (center_x, center_y), 0.5, "linear")
|
||||
cam_anim.start(grid)
|
||||
|
||||
mcrfpy.setTimer("switch_persp", switch_perspective, 1600)
|
||||
|
||||
# Phase 3: Zoom back in
|
||||
def zoom_in(dt):
|
||||
zoom_in_anim = mcrfpy.Animation("zoom", 1.0, 1.5, "easeOutExpo")
|
||||
zoom_in_anim.start(grid)
|
||||
status.text = "Perspective shift complete!"
|
||||
|
||||
mcrfpy.setTimer("zoom_in", zoom_in, 2100)
|
||||
|
||||
# Input handler
|
||||
def handle_input(key, state):
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
if key == "q":
|
||||
print("Exiting demo...")
|
||||
sys.exit(0)
|
||||
elif key == "1":
|
||||
move_player_demo()
|
||||
elif key == "2":
|
||||
move_enemy_demo()
|
||||
elif key == "3":
|
||||
perspective_shift_demo()
|
||||
|
||||
# Set scene
|
||||
mcrfpy.setScene("anim_demo")
|
||||
mcrfpy.keypressScene(handle_input)
|
||||
|
||||
# Initial setup
|
||||
grid.perspective = 0
|
||||
grid.zoom = 1.0
|
||||
|
||||
# Center on player initially
|
||||
center_x = player.x * 16
|
||||
center_y = player.y * 16
|
||||
initial_cam = mcrfpy.Animation("center", (center_x, center_y), 0.5, "easeOut")
|
||||
initial_cam.start(grid)
|
||||
|
||||
print("Animation Demo Started!")
|
||||
print("======================")
|
||||
print("Press 1: Animate player movement with camera follow")
|
||||
print("Press 2: Animate enemy movement")
|
||||
print("Press 3: Dramatic perspective shift with zoom")
|
||||
print("Press Q: Quit")
|
||||
print()
|
||||
print("Watch how the grid center smoothly follows entities")
|
||||
print("and how perspective shifts create cinematic effects!")
|
||||
print("Animation demo started! Press Escape to exit.")
|
|
@ -1,235 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
A* vs Dijkstra Visual Comparison
|
||||
=================================
|
||||
|
||||
Shows the difference between A* (single target) and Dijkstra (multi-target).
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Colors
|
||||
WALL_COLOR = mcrfpy.Color(40, 20, 20)
|
||||
FLOOR_COLOR = mcrfpy.Color(60, 60, 80)
|
||||
ASTAR_COLOR = mcrfpy.Color(0, 255, 0) # Green for A*
|
||||
DIJKSTRA_COLOR = mcrfpy.Color(0, 150, 255) # Blue for Dijkstra
|
||||
START_COLOR = mcrfpy.Color(255, 100, 100) # Red for start
|
||||
END_COLOR = mcrfpy.Color(255, 255, 100) # Yellow for end
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
mode = "ASTAR"
|
||||
start_pos = (5, 10)
|
||||
end_pos = (27, 10) # Changed from 25 to 27 to avoid the wall
|
||||
|
||||
def create_map():
|
||||
"""Create a map with obstacles to show pathfinding differences"""
|
||||
global grid
|
||||
|
||||
mcrfpy.createScene("pathfinding_comparison")
|
||||
|
||||
# Create grid
|
||||
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
|
||||
# Initialize all as floor
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
grid.at(x, y).walkable = True
|
||||
grid.at(x, y).color = FLOOR_COLOR
|
||||
|
||||
# Create obstacles that make A* and Dijkstra differ
|
||||
obstacles = [
|
||||
# Vertical wall with gaps
|
||||
[(15, y) for y in range(3, 17) if y not in [8, 12]],
|
||||
# Horizontal walls
|
||||
[(x, 5) for x in range(10, 20)],
|
||||
[(x, 15) for x in range(10, 20)],
|
||||
# Maze-like structure
|
||||
[(x, 10) for x in range(20, 25)],
|
||||
[(25, y) for y in range(5, 15)],
|
||||
]
|
||||
|
||||
for obstacle_group in obstacles:
|
||||
for x, y in obstacle_group:
|
||||
grid.at(x, y).walkable = False
|
||||
grid.at(x, y).color = WALL_COLOR
|
||||
|
||||
# Mark start and end
|
||||
grid.at(start_pos[0], start_pos[1]).color = START_COLOR
|
||||
grid.at(end_pos[0], end_pos[1]).color = END_COLOR
|
||||
|
||||
def clear_paths():
|
||||
"""Clear path highlighting"""
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
cell = grid.at(x, y)
|
||||
if cell.walkable:
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
# Restore start and end colors
|
||||
grid.at(start_pos[0], start_pos[1]).color = START_COLOR
|
||||
grid.at(end_pos[0], end_pos[1]).color = END_COLOR
|
||||
|
||||
def show_astar():
|
||||
"""Show A* path"""
|
||||
clear_paths()
|
||||
|
||||
# Compute A* path
|
||||
path = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
|
||||
|
||||
# Color the path
|
||||
for i, (x, y) in enumerate(path):
|
||||
if (x, y) != start_pos and (x, y) != end_pos:
|
||||
grid.at(x, y).color = ASTAR_COLOR
|
||||
|
||||
status_text.text = f"A* Path: {len(path)} steps (optimized for single target)"
|
||||
status_text.fill_color = ASTAR_COLOR
|
||||
|
||||
def show_dijkstra():
|
||||
"""Show Dijkstra exploration"""
|
||||
clear_paths()
|
||||
|
||||
# Compute Dijkstra from start
|
||||
grid.compute_dijkstra(start_pos[0], start_pos[1])
|
||||
|
||||
# Color cells by distance (showing exploration)
|
||||
max_dist = 40.0
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
if grid.at(x, y).walkable:
|
||||
dist = grid.get_dijkstra_distance(x, y)
|
||||
if dist is not None and dist < max_dist:
|
||||
# Color based on distance
|
||||
intensity = int(255 * (1 - dist / max_dist))
|
||||
grid.at(x, y).color = mcrfpy.Color(0, intensity // 2, intensity)
|
||||
|
||||
# Get the actual path
|
||||
path = grid.get_dijkstra_path(end_pos[0], end_pos[1])
|
||||
|
||||
# Highlight the actual path more brightly
|
||||
for x, y in path:
|
||||
if (x, y) != start_pos and (x, y) != end_pos:
|
||||
grid.at(x, y).color = DIJKSTRA_COLOR
|
||||
|
||||
# Restore start and end
|
||||
grid.at(start_pos[0], start_pos[1]).color = START_COLOR
|
||||
grid.at(end_pos[0], end_pos[1]).color = END_COLOR
|
||||
|
||||
status_text.text = f"Dijkstra: {len(path)} steps (explores all directions)"
|
||||
status_text.fill_color = DIJKSTRA_COLOR
|
||||
|
||||
def show_both():
|
||||
"""Show both paths overlaid"""
|
||||
clear_paths()
|
||||
|
||||
# Get both paths
|
||||
astar_path = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
|
||||
grid.compute_dijkstra(start_pos[0], start_pos[1])
|
||||
dijkstra_path = grid.get_dijkstra_path(end_pos[0], end_pos[1])
|
||||
|
||||
print(astar_path, dijkstra_path)
|
||||
|
||||
# Color Dijkstra path first (blue)
|
||||
for x, y in dijkstra_path:
|
||||
if (x, y) != start_pos and (x, y) != end_pos:
|
||||
grid.at(x, y).color = DIJKSTRA_COLOR
|
||||
|
||||
# Then A* path (green) - will overwrite shared cells
|
||||
for x, y in astar_path:
|
||||
if (x, y) != start_pos and (x, y) != end_pos:
|
||||
grid.at(x, y).color = ASTAR_COLOR
|
||||
|
||||
# Mark differences
|
||||
different_cells = []
|
||||
for cell in dijkstra_path:
|
||||
if cell not in astar_path:
|
||||
different_cells.append(cell)
|
||||
|
||||
status_text.text = f"Both paths: A*={len(astar_path)} steps, Dijkstra={len(dijkstra_path)} steps"
|
||||
if different_cells:
|
||||
info_text.text = f"Paths differ at {len(different_cells)} cells"
|
||||
else:
|
||||
info_text.text = "Paths are identical"
|
||||
|
||||
def handle_keypress(key_str, state):
|
||||
"""Handle keyboard input"""
|
||||
global mode
|
||||
if state == "end": return
|
||||
print(key_str)
|
||||
if key_str == "Esc" or key_str == "Q":
|
||||
print("\nExiting...")
|
||||
sys.exit(0)
|
||||
elif key_str == "A" or key_str == "1":
|
||||
mode = "ASTAR"
|
||||
show_astar()
|
||||
elif key_str == "D" or key_str == "2":
|
||||
mode = "DIJKSTRA"
|
||||
show_dijkstra()
|
||||
elif key_str == "B" or key_str == "3":
|
||||
mode = "BOTH"
|
||||
show_both()
|
||||
elif key_str == "Space":
|
||||
# Refresh current mode
|
||||
if mode == "ASTAR":
|
||||
show_astar()
|
||||
elif mode == "DIJKSTRA":
|
||||
show_dijkstra()
|
||||
else:
|
||||
show_both()
|
||||
|
||||
# Create the demo
|
||||
print("A* vs Dijkstra Pathfinding Comparison")
|
||||
print("=====================================")
|
||||
print("Controls:")
|
||||
print(" A or 1 - Show A* path (green)")
|
||||
print(" D or 2 - Show Dijkstra (blue gradient)")
|
||||
print(" B or 3 - Show both paths")
|
||||
print(" Q/ESC - Quit")
|
||||
print()
|
||||
print("A* is optimized for single-target pathfinding")
|
||||
print("Dijkstra explores in all directions (good for multiple targets)")
|
||||
|
||||
create_map()
|
||||
|
||||
# Set up UI
|
||||
ui = mcrfpy.sceneUI("pathfinding_comparison")
|
||||
ui.append(grid)
|
||||
|
||||
# Scale and position
|
||||
grid.size = (600, 400) # 30*20, 20*20
|
||||
grid.position = (100, 100)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption("A* vs Dijkstra Pathfinding", 250, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Add status
|
||||
status_text = mcrfpy.Caption("Press A for A*, D for Dijkstra, B for Both", 100, 60)
|
||||
status_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(status_text)
|
||||
|
||||
# Add info
|
||||
info_text = mcrfpy.Caption("", 100, 520)
|
||||
info_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(info_text)
|
||||
|
||||
# Add legend
|
||||
legend1 = mcrfpy.Caption("Red=Start, Yellow=End, Green=A*, Blue=Dijkstra", 100, 540)
|
||||
legend1.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend1)
|
||||
|
||||
legend2 = mcrfpy.Caption("Dark=Walls, Light=Floor", 100, 560)
|
||||
legend2.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend2)
|
||||
|
||||
# Set scene and input
|
||||
mcrfpy.setScene("pathfinding_comparison")
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
|
||||
# Show initial A* path
|
||||
show_astar()
|
||||
|
||||
print("\nDemo ready!")
|
|
@ -1,4 +0,0 @@
|
|||
import mcrfpy
|
||||
e = mcrfpy.Entity(0, 0)
|
||||
print("Entity attributes:", dir(e))
|
||||
print("\nEntity repr:", repr(e))
|
|
@ -1,99 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Debug the astar_vs_dijkstra demo issue"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Same setup as the demo
|
||||
start_pos = (5, 10)
|
||||
end_pos = (25, 10)
|
||||
|
||||
print("Debugging A* vs Dijkstra demo...")
|
||||
print(f"Start: {start_pos}, End: {end_pos}")
|
||||
|
||||
# Create scene and grid
|
||||
mcrfpy.createScene("debug")
|
||||
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
|
||||
|
||||
# Initialize all as floor
|
||||
print("\nInitializing 30x20 grid...")
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
grid.at(x, y).walkable = True
|
||||
|
||||
# Test path before obstacles
|
||||
print("\nTest 1: Path with no obstacles")
|
||||
path1 = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
|
||||
print(f" Path: {path1[:5]}...{path1[-3:] if len(path1) > 5 else ''}")
|
||||
print(f" Length: {len(path1)}")
|
||||
|
||||
# Add obstacles from the demo
|
||||
obstacles = [
|
||||
# Vertical wall with gaps
|
||||
[(15, y) for y in range(3, 17) if y not in [8, 12]],
|
||||
# Horizontal walls
|
||||
[(x, 5) for x in range(10, 20)],
|
||||
[(x, 15) for x in range(10, 20)],
|
||||
# Maze-like structure
|
||||
[(x, 10) for x in range(20, 25)],
|
||||
[(25, y) for y in range(5, 15)],
|
||||
]
|
||||
|
||||
print("\nAdding obstacles...")
|
||||
wall_count = 0
|
||||
for obstacle_group in obstacles:
|
||||
for x, y in obstacle_group:
|
||||
grid.at(x, y).walkable = False
|
||||
wall_count += 1
|
||||
if wall_count <= 5:
|
||||
print(f" Wall at ({x}, {y})")
|
||||
|
||||
print(f" Total walls added: {wall_count}")
|
||||
|
||||
# Check specific cells
|
||||
print(f"\nChecking key positions:")
|
||||
print(f" Start ({start_pos[0]}, {start_pos[1]}): walkable={grid.at(start_pos[0], start_pos[1]).walkable}")
|
||||
print(f" End ({end_pos[0]}, {end_pos[1]}): walkable={grid.at(end_pos[0], end_pos[1]).walkable}")
|
||||
|
||||
# Check if path is blocked
|
||||
print(f"\nChecking horizontal line at y=10:")
|
||||
blocked_x = []
|
||||
for x in range(30):
|
||||
if not grid.at(x, 10).walkable:
|
||||
blocked_x.append(x)
|
||||
|
||||
print(f" Blocked x positions: {blocked_x}")
|
||||
|
||||
# Test path with obstacles
|
||||
print("\nTest 2: Path with obstacles")
|
||||
path2 = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
|
||||
print(f" Path: {path2}")
|
||||
print(f" Length: {len(path2)}")
|
||||
|
||||
# Check if there's any path at all
|
||||
if not path2:
|
||||
print("\n No path found! Checking why...")
|
||||
|
||||
# Check if we can reach the vertical wall gap
|
||||
print("\n Testing path to wall gap at (15, 8):")
|
||||
path_to_gap = grid.compute_astar_path(start_pos[0], start_pos[1], 15, 8)
|
||||
print(f" Path to gap: {path_to_gap}")
|
||||
|
||||
# Check from gap to end
|
||||
print("\n Testing path from gap (15, 8) to end:")
|
||||
path_from_gap = grid.compute_astar_path(15, 8, end_pos[0], end_pos[1])
|
||||
print(f" Path from gap: {path_from_gap}")
|
||||
|
||||
# Check walls more carefully
|
||||
print("\nDetailed wall analysis:")
|
||||
print(" Walls at x=25 (blocking end?):")
|
||||
for y in range(5, 15):
|
||||
print(f" ({25}, {y}): walkable={grid.at(25, y).walkable}")
|
||||
|
||||
def timer_cb(dt):
|
||||
sys.exit(0)
|
||||
|
||||
ui = mcrfpy.sceneUI("debug")
|
||||
ui.append(grid)
|
||||
mcrfpy.setScene("debug")
|
||||
mcrfpy.setTimer("exit", timer_cb, 100)
|
|
@ -1,80 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Debug empty paths issue"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
print("Debugging empty paths...")
|
||||
|
||||
# Create scene and grid
|
||||
mcrfpy.createScene("debug")
|
||||
grid = mcrfpy.Grid(grid_x=10, grid_y=10)
|
||||
|
||||
# Initialize grid - all walkable
|
||||
print("\nInitializing grid...")
|
||||
for y in range(10):
|
||||
for x in range(10):
|
||||
grid.at(x, y).walkable = True
|
||||
|
||||
# Test simple path
|
||||
print("\nTest 1: Simple path from (0,0) to (5,5)")
|
||||
path = grid.compute_astar_path(0, 0, 5, 5)
|
||||
print(f" A* path: {path}")
|
||||
print(f" Path length: {len(path)}")
|
||||
|
||||
# Test with Dijkstra
|
||||
print("\nTest 2: Same path with Dijkstra")
|
||||
grid.compute_dijkstra(0, 0)
|
||||
dpath = grid.get_dijkstra_path(5, 5)
|
||||
print(f" Dijkstra path: {dpath}")
|
||||
print(f" Path length: {len(dpath)}")
|
||||
|
||||
# Check if grid is properly initialized
|
||||
print("\nTest 3: Checking grid cells")
|
||||
for y in range(3):
|
||||
for x in range(3):
|
||||
cell = grid.at(x, y)
|
||||
print(f" Cell ({x},{y}): walkable={cell.walkable}")
|
||||
|
||||
# Test with walls
|
||||
print("\nTest 4: Path with wall")
|
||||
grid.at(2, 2).walkable = False
|
||||
grid.at(3, 2).walkable = False
|
||||
grid.at(4, 2).walkable = False
|
||||
print(" Added wall at y=2, x=2,3,4")
|
||||
|
||||
path2 = grid.compute_astar_path(0, 0, 5, 5)
|
||||
print(f" A* path with wall: {path2}")
|
||||
print(f" Path length: {len(path2)}")
|
||||
|
||||
# Test invalid paths
|
||||
print("\nTest 5: Path to blocked cell")
|
||||
grid.at(9, 9).walkable = False
|
||||
path3 = grid.compute_astar_path(0, 0, 9, 9)
|
||||
print(f" Path to blocked cell: {path3}")
|
||||
|
||||
# Check TCOD map sync
|
||||
print("\nTest 6: Verify TCOD map is synced")
|
||||
# Try to force a sync
|
||||
print(" Checking if syncTCODMap exists...")
|
||||
if hasattr(grid, 'sync_tcod_map'):
|
||||
print(" Calling sync_tcod_map()")
|
||||
grid.sync_tcod_map()
|
||||
else:
|
||||
print(" No sync_tcod_map method found")
|
||||
|
||||
# Try path again
|
||||
print("\nTest 7: Path after potential sync")
|
||||
path4 = grid.compute_astar_path(0, 0, 5, 5)
|
||||
print(f" A* path: {path4}")
|
||||
|
||||
def timer_cb(dt):
|
||||
sys.exit(0)
|
||||
|
||||
# Quick UI setup
|
||||
ui = mcrfpy.sceneUI("debug")
|
||||
ui.append(grid)
|
||||
mcrfpy.setScene("debug")
|
||||
mcrfpy.setTimer("exit", timer_cb, 100)
|
||||
|
||||
print("\nStarting timer...")
|
|
@ -1,59 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Debug visibility crash"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
print("Debug visibility...")
|
||||
|
||||
# Create scene and grid
|
||||
mcrfpy.createScene("debug")
|
||||
grid = mcrfpy.Grid(grid_x=5, grid_y=5)
|
||||
|
||||
# Initialize grid
|
||||
print("Initializing grid...")
|
||||
for y in range(5):
|
||||
for x in range(5):
|
||||
cell = grid.at(x, y)
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
|
||||
# Create entity
|
||||
print("Creating entity...")
|
||||
entity = mcrfpy.Entity(2, 2)
|
||||
entity.sprite_index = 64
|
||||
grid.entities.append(entity)
|
||||
print(f"Entity at ({entity.x}, {entity.y})")
|
||||
|
||||
# Check gridstate
|
||||
print(f"\nGridstate length: {len(entity.gridstate)}")
|
||||
print(f"Expected: {5 * 5}")
|
||||
|
||||
# Try to access gridstate
|
||||
print("\nChecking gridstate access...")
|
||||
try:
|
||||
if len(entity.gridstate) > 0:
|
||||
state = entity.gridstate[0]
|
||||
print(f"First state: visible={state.visible}, discovered={state.discovered}")
|
||||
except Exception as e:
|
||||
print(f"Error accessing gridstate: {e}")
|
||||
|
||||
# Try update_visibility
|
||||
print("\nTrying update_visibility...")
|
||||
try:
|
||||
entity.update_visibility()
|
||||
print("update_visibility succeeded")
|
||||
except Exception as e:
|
||||
print(f"Error in update_visibility: {e}")
|
||||
|
||||
# Try perspective
|
||||
print("\nTesting perspective...")
|
||||
print(f"Initial perspective: {grid.perspective}")
|
||||
try:
|
||||
grid.perspective = 0
|
||||
print(f"Set perspective to 0: {grid.perspective}")
|
||||
except Exception as e:
|
||||
print(f"Error setting perspective: {e}")
|
||||
|
||||
print("\nTest complete")
|
||||
sys.exit(0)
|
|
@ -1,234 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Dijkstra Demo - Shows ALL Path Combinations (Including Invalid)
|
||||
===============================================================
|
||||
|
||||
Cycles through every possible entity pair to demonstrate both
|
||||
valid paths and properly handled invalid paths (empty lists).
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# High contrast colors
|
||||
WALL_COLOR = mcrfpy.Color(40, 20, 20) # Very dark red/brown
|
||||
FLOOR_COLOR = mcrfpy.Color(60, 60, 80) # Dark blue-gray
|
||||
PATH_COLOR = mcrfpy.Color(0, 255, 0) # Bright green
|
||||
START_COLOR = mcrfpy.Color(255, 100, 100) # Light red
|
||||
END_COLOR = mcrfpy.Color(100, 100, 255) # Light blue
|
||||
NO_PATH_COLOR = mcrfpy.Color(255, 0, 0) # Pure red for unreachable
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
entities = []
|
||||
current_combo_index = 0
|
||||
all_combinations = [] # All possible pairs
|
||||
current_path = []
|
||||
|
||||
def create_map():
|
||||
"""Create the map with entities"""
|
||||
global grid, entities, all_combinations
|
||||
|
||||
mcrfpy.createScene("dijkstra_all")
|
||||
|
||||
# Create grid
|
||||
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
|
||||
# Map layout - Entity 1 is intentionally trapped!
|
||||
map_layout = [
|
||||
"..............", # Row 0
|
||||
"..W.....WWWW..", # Row 1
|
||||
"..W.W...W.EW..", # Row 2 - Entity 1 TRAPPED at (10,2)
|
||||
"..W.....W..W..", # Row 3
|
||||
"..W...E.WWWW..", # Row 4 - Entity 2 at (6,4)
|
||||
"E.W...........", # Row 5 - Entity 3 at (0,5)
|
||||
"..W...........", # Row 6
|
||||
"..W...........", # Row 7
|
||||
"..W.WWW.......", # Row 8
|
||||
"..............", # Row 9
|
||||
]
|
||||
|
||||
# Create the map
|
||||
entity_positions = []
|
||||
for y, row in enumerate(map_layout):
|
||||
for x, char in enumerate(row):
|
||||
cell = grid.at(x, y)
|
||||
|
||||
if char == 'W':
|
||||
cell.walkable = False
|
||||
cell.color = WALL_COLOR
|
||||
else:
|
||||
cell.walkable = True
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
if char == 'E':
|
||||
entity_positions.append((x, y))
|
||||
|
||||
# Create entities
|
||||
entities = []
|
||||
for i, (x, y) in enumerate(entity_positions):
|
||||
entity = mcrfpy.Entity(x, y)
|
||||
entity.sprite_index = 49 + i # '1', '2', '3'
|
||||
grid.entities.append(entity)
|
||||
entities.append(entity)
|
||||
|
||||
print("Map Analysis:")
|
||||
print("=============")
|
||||
for i, (x, y) in enumerate(entity_positions):
|
||||
print(f"Entity {i+1} at ({x}, {y})")
|
||||
|
||||
# Generate ALL combinations (including invalid ones)
|
||||
all_combinations = []
|
||||
for i in range(len(entities)):
|
||||
for j in range(len(entities)):
|
||||
if i != j: # Skip self-paths
|
||||
all_combinations.append((i, j))
|
||||
|
||||
print(f"\nTotal path combinations to test: {len(all_combinations)}")
|
||||
|
||||
def clear_path_colors():
|
||||
"""Reset all floor tiles to original color"""
|
||||
global current_path
|
||||
|
||||
for y in range(grid.grid_y):
|
||||
for x in range(grid.grid_x):
|
||||
cell = grid.at(x, y)
|
||||
if cell.walkable:
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
current_path = []
|
||||
|
||||
def show_combination(index):
|
||||
"""Show a specific path combination (valid or invalid)"""
|
||||
global current_combo_index, current_path
|
||||
|
||||
current_combo_index = index % len(all_combinations)
|
||||
from_idx, to_idx = all_combinations[current_combo_index]
|
||||
|
||||
# Clear previous path
|
||||
clear_path_colors()
|
||||
|
||||
# Get entities
|
||||
e_from = entities[from_idx]
|
||||
e_to = entities[to_idx]
|
||||
|
||||
# Calculate path
|
||||
path = e_from.path_to(int(e_to.x), int(e_to.y))
|
||||
current_path = path if path else []
|
||||
|
||||
# Always color start and end positions
|
||||
grid.at(int(e_from.x), int(e_from.y)).color = START_COLOR
|
||||
grid.at(int(e_to.x), int(e_to.y)).color = NO_PATH_COLOR if not path else END_COLOR
|
||||
|
||||
# Color the path if it exists
|
||||
if path:
|
||||
# Color intermediate steps
|
||||
for i, (x, y) in enumerate(path):
|
||||
if i > 0 and i < len(path) - 1:
|
||||
grid.at(x, y).color = PATH_COLOR
|
||||
|
||||
status_text.text = f"Path {current_combo_index + 1}/{len(all_combinations)}: Entity {from_idx+1} → Entity {to_idx+1} = {len(path)} steps"
|
||||
status_text.fill_color = mcrfpy.Color(100, 255, 100) # Green for valid
|
||||
|
||||
# Show path steps
|
||||
path_display = []
|
||||
for i, (x, y) in enumerate(path[:5]):
|
||||
path_display.append(f"({x},{y})")
|
||||
if len(path) > 5:
|
||||
path_display.append("...")
|
||||
path_text.text = "Path: " + " → ".join(path_display)
|
||||
else:
|
||||
status_text.text = f"Path {current_combo_index + 1}/{len(all_combinations)}: Entity {from_idx+1} → Entity {to_idx+1} = NO PATH!"
|
||||
status_text.fill_color = mcrfpy.Color(255, 100, 100) # Red for invalid
|
||||
path_text.text = "Path: [] (No valid path exists)"
|
||||
|
||||
# Update info
|
||||
info_text.text = f"From: Entity {from_idx+1} at ({int(e_from.x)}, {int(e_from.y)}) | To: Entity {to_idx+1} at ({int(e_to.x)}, {int(e_to.y)})"
|
||||
|
||||
def handle_keypress(key_str, state):
|
||||
"""Handle keyboard input"""
|
||||
global current_combo_index
|
||||
if state == "end": return
|
||||
|
||||
if key_str == "Esc" or key_str == "Q":
|
||||
print("\nExiting...")
|
||||
sys.exit(0)
|
||||
elif key_str == "Space" or key_str == "N":
|
||||
show_combination(current_combo_index + 1)
|
||||
elif key_str == "P":
|
||||
show_combination(current_combo_index - 1)
|
||||
elif key_str == "R":
|
||||
show_combination(current_combo_index)
|
||||
elif key_str in "123456":
|
||||
combo_num = int(key_str) - 1 # 0-based index
|
||||
if combo_num < len(all_combinations):
|
||||
show_combination(combo_num)
|
||||
|
||||
# Create the demo
|
||||
print("Dijkstra All Paths Demo")
|
||||
print("=======================")
|
||||
print("Shows ALL path combinations including invalid ones")
|
||||
print("Entity 1 is trapped - paths to/from it will be empty!")
|
||||
print()
|
||||
|
||||
create_map()
|
||||
|
||||
# Set up UI
|
||||
ui = mcrfpy.sceneUI("dijkstra_all")
|
||||
ui.append(grid)
|
||||
|
||||
# Scale and position
|
||||
grid.size = (560, 400)
|
||||
grid.position = (120, 100)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption("Dijkstra - All Paths (Valid & Invalid)", 200, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Add status (will change color based on validity)
|
||||
status_text = mcrfpy.Caption("Ready", 120, 60)
|
||||
status_text.fill_color = mcrfpy.Color(255, 255, 100)
|
||||
ui.append(status_text)
|
||||
|
||||
# Add info
|
||||
info_text = mcrfpy.Caption("", 120, 80)
|
||||
info_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(info_text)
|
||||
|
||||
# Add path display
|
||||
path_text = mcrfpy.Caption("Path: None", 120, 520)
|
||||
path_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(path_text)
|
||||
|
||||
# Add controls
|
||||
controls = mcrfpy.Caption("SPACE/N=Next, P=Previous, 1-6=Jump to path, Q=Quit", 120, 540)
|
||||
controls.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(controls)
|
||||
|
||||
# Add legend
|
||||
legend = mcrfpy.Caption("Red Start→Blue End (valid) | Red Start→Red End (invalid)", 120, 560)
|
||||
legend.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend)
|
||||
|
||||
# Expected results info
|
||||
expected = mcrfpy.Caption("Entity 1 is trapped: paths 1→2, 1→3, 2→1, 3→1 will fail", 120, 580)
|
||||
expected.fill_color = mcrfpy.Color(255, 150, 150)
|
||||
ui.append(expected)
|
||||
|
||||
# Set scene first, then set up input handler
|
||||
mcrfpy.setScene("dijkstra_all")
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
|
||||
# Show first combination
|
||||
show_combination(0)
|
||||
|
||||
print("\nDemo ready!")
|
||||
print("Expected results:")
|
||||
print(" Path 1: Entity 1→2 = NO PATH (Entity 1 is trapped)")
|
||||
print(" Path 2: Entity 1→3 = NO PATH (Entity 1 is trapped)")
|
||||
print(" Path 3: Entity 2→1 = NO PATH (Entity 1 is trapped)")
|
||||
print(" Path 4: Entity 2→3 = Valid path")
|
||||
print(" Path 5: Entity 3→1 = NO PATH (Entity 1 is trapped)")
|
||||
print(" Path 6: Entity 3→2 = Valid path")
|
|
@ -1,236 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Dijkstra Demo - Cycles Through Different Path Combinations
|
||||
==========================================================
|
||||
|
||||
Shows paths between different entity pairs, skipping impossible paths.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# High contrast colors
|
||||
WALL_COLOR = mcrfpy.Color(40, 20, 20) # Very dark red/brown
|
||||
FLOOR_COLOR = mcrfpy.Color(60, 60, 80) # Dark blue-gray
|
||||
PATH_COLOR = mcrfpy.Color(0, 255, 0) # Bright green
|
||||
START_COLOR = mcrfpy.Color(255, 100, 100) # Light red
|
||||
END_COLOR = mcrfpy.Color(100, 100, 255) # Light blue
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
entities = []
|
||||
current_path_index = 0
|
||||
path_combinations = []
|
||||
current_path = []
|
||||
|
||||
def create_map():
|
||||
"""Create the map with entities"""
|
||||
global grid, entities
|
||||
|
||||
mcrfpy.createScene("dijkstra_cycle")
|
||||
|
||||
# Create grid
|
||||
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
|
||||
# Map layout
|
||||
map_layout = [
|
||||
"..............", # Row 0
|
||||
"..W.....WWWW..", # Row 1
|
||||
"..W.W...W.EW..", # Row 2 - Entity 1 at (10,2) is TRAPPED!
|
||||
"..W.....W..W..", # Row 3
|
||||
"..W...E.WWWW..", # Row 4 - Entity 2 at (6,4)
|
||||
"E.W...........", # Row 5 - Entity 3 at (0,5)
|
||||
"..W...........", # Row 6
|
||||
"..W...........", # Row 7
|
||||
"..W.WWW.......", # Row 8
|
||||
"..............", # Row 9
|
||||
]
|
||||
|
||||
# Create the map
|
||||
entity_positions = []
|
||||
for y, row in enumerate(map_layout):
|
||||
for x, char in enumerate(row):
|
||||
cell = grid.at(x, y)
|
||||
|
||||
if char == 'W':
|
||||
cell.walkable = False
|
||||
cell.color = WALL_COLOR
|
||||
else:
|
||||
cell.walkable = True
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
if char == 'E':
|
||||
entity_positions.append((x, y))
|
||||
|
||||
# Create entities
|
||||
entities = []
|
||||
for i, (x, y) in enumerate(entity_positions):
|
||||
entity = mcrfpy.Entity(x, y)
|
||||
entity.sprite_index = 49 + i # '1', '2', '3'
|
||||
grid.entities.append(entity)
|
||||
entities.append(entity)
|
||||
|
||||
print("Entities created:")
|
||||
for i, (x, y) in enumerate(entity_positions):
|
||||
print(f" Entity {i+1} at ({x}, {y})")
|
||||
|
||||
# Check which entity is trapped
|
||||
print("\nChecking accessibility:")
|
||||
for i, e in enumerate(entities):
|
||||
# Try to path to each other entity
|
||||
can_reach = []
|
||||
for j, other in enumerate(entities):
|
||||
if i != j:
|
||||
path = e.path_to(int(other.x), int(other.y))
|
||||
if path:
|
||||
can_reach.append(j+1)
|
||||
|
||||
if not can_reach:
|
||||
print(f" Entity {i+1} at ({int(e.x)}, {int(e.y)}) is TRAPPED!")
|
||||
else:
|
||||
print(f" Entity {i+1} can reach entities: {can_reach}")
|
||||
|
||||
# Generate valid path combinations (excluding trapped entity)
|
||||
global path_combinations
|
||||
path_combinations = []
|
||||
|
||||
# Only paths between entities 2 and 3 (indices 1 and 2) will work
|
||||
# since entity 1 (index 0) is trapped
|
||||
if len(entities) >= 3:
|
||||
# Entity 2 to Entity 3
|
||||
path = entities[1].path_to(int(entities[2].x), int(entities[2].y))
|
||||
if path:
|
||||
path_combinations.append((1, 2, path))
|
||||
|
||||
# Entity 3 to Entity 2
|
||||
path = entities[2].path_to(int(entities[1].x), int(entities[1].y))
|
||||
if path:
|
||||
path_combinations.append((2, 1, path))
|
||||
|
||||
print(f"\nFound {len(path_combinations)} valid paths")
|
||||
|
||||
def clear_path_colors():
|
||||
"""Reset all floor tiles to original color"""
|
||||
global current_path
|
||||
|
||||
for y in range(grid.grid_y):
|
||||
for x in range(grid.grid_x):
|
||||
cell = grid.at(x, y)
|
||||
if cell.walkable:
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
current_path = []
|
||||
|
||||
def show_path(index):
|
||||
"""Show a specific path combination"""
|
||||
global current_path_index, current_path
|
||||
|
||||
if not path_combinations:
|
||||
status_text.text = "No valid paths available (Entity 1 is trapped!)"
|
||||
return
|
||||
|
||||
current_path_index = index % len(path_combinations)
|
||||
from_idx, to_idx, path = path_combinations[current_path_index]
|
||||
|
||||
# Clear previous path
|
||||
clear_path_colors()
|
||||
|
||||
# Get entities
|
||||
e_from = entities[from_idx]
|
||||
e_to = entities[to_idx]
|
||||
|
||||
# Color the path
|
||||
current_path = path
|
||||
if path:
|
||||
# Color start and end
|
||||
grid.at(int(e_from.x), int(e_from.y)).color = START_COLOR
|
||||
grid.at(int(e_to.x), int(e_to.y)).color = END_COLOR
|
||||
|
||||
# Color intermediate steps
|
||||
for i, (x, y) in enumerate(path):
|
||||
if i > 0 and i < len(path) - 1:
|
||||
grid.at(x, y).color = PATH_COLOR
|
||||
|
||||
# Update status
|
||||
status_text.text = f"Path {current_path_index + 1}/{len(path_combinations)}: Entity {from_idx+1} → Entity {to_idx+1} ({len(path)} steps)"
|
||||
|
||||
# Update path display
|
||||
path_display = []
|
||||
for i, (x, y) in enumerate(path[:5]): # Show first 5 steps
|
||||
path_display.append(f"({x},{y})")
|
||||
if len(path) > 5:
|
||||
path_display.append("...")
|
||||
path_text.text = "Path: " + " → ".join(path_display) if path_display else "Path: None"
|
||||
|
||||
def handle_keypress(key_str, state):
|
||||
"""Handle keyboard input"""
|
||||
global current_path_index
|
||||
if state == "end": return
|
||||
if key_str == "Esc":
|
||||
print("\nExiting...")
|
||||
sys.exit(0)
|
||||
elif key_str == "N" or key_str == "Space":
|
||||
show_path(current_path_index + 1)
|
||||
elif key_str == "P":
|
||||
show_path(current_path_index - 1)
|
||||
elif key_str == "R":
|
||||
show_path(current_path_index)
|
||||
|
||||
# Create the demo
|
||||
print("Dijkstra Path Cycling Demo")
|
||||
print("==========================")
|
||||
print("Note: Entity 1 is trapped by walls!")
|
||||
print()
|
||||
|
||||
create_map()
|
||||
|
||||
# Set up UI
|
||||
ui = mcrfpy.sceneUI("dijkstra_cycle")
|
||||
ui.append(grid)
|
||||
|
||||
# Scale and position
|
||||
grid.size = (560, 400)
|
||||
grid.position = (120, 100)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption("Dijkstra Pathfinding - Cycle Paths", 200, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Add status
|
||||
status_text = mcrfpy.Caption("Press SPACE to cycle paths", 120, 60)
|
||||
status_text.fill_color = mcrfpy.Color(255, 255, 100)
|
||||
ui.append(status_text)
|
||||
|
||||
# Add path display
|
||||
path_text = mcrfpy.Caption("Path: None", 120, 520)
|
||||
path_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(path_text)
|
||||
|
||||
# Add controls
|
||||
controls = mcrfpy.Caption("SPACE/N=Next, P=Previous, R=Refresh, Q=Quit", 120, 540)
|
||||
controls.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(controls)
|
||||
|
||||
# Add legend
|
||||
legend = mcrfpy.Caption("Red=Start, Blue=End, Green=Path, Dark=Wall", 120, 560)
|
||||
legend.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend)
|
||||
|
||||
# Show first valid path
|
||||
mcrfpy.setScene("dijkstra_cycle")
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
|
||||
# Display initial path
|
||||
if path_combinations:
|
||||
show_path(0)
|
||||
else:
|
||||
status_text.text = "No valid paths! Entity 1 is trapped!"
|
||||
|
||||
print("\nDemo ready!")
|
||||
print("Controls:")
|
||||
print(" SPACE or N - Next path")
|
||||
print(" P - Previous path")
|
||||
print(" R - Refresh current path")
|
||||
print(" Q - Quit")
|
|
@ -1,161 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Debug version of Dijkstra pathfinding to diagnose visualization issues
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Colors
|
||||
WALL_COLOR = mcrfpy.Color(60, 30, 30)
|
||||
FLOOR_COLOR = mcrfpy.Color(200, 200, 220)
|
||||
PATH_COLOR = mcrfpy.Color(200, 250, 220)
|
||||
ENTITY_COLORS = [
|
||||
mcrfpy.Color(255, 100, 100), # Entity 1 - Red
|
||||
mcrfpy.Color(100, 255, 100), # Entity 2 - Green
|
||||
mcrfpy.Color(100, 100, 255), # Entity 3 - Blue
|
||||
]
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
entities = []
|
||||
first_point = None
|
||||
second_point = None
|
||||
|
||||
def create_simple_map():
|
||||
"""Create a simple test map"""
|
||||
global grid, entities
|
||||
|
||||
mcrfpy.createScene("dijkstra_debug")
|
||||
|
||||
# Small grid for easy debugging
|
||||
grid = mcrfpy.Grid(grid_x=10, grid_y=10)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
|
||||
print("Initializing 10x10 grid...")
|
||||
|
||||
# Initialize all as floor
|
||||
for y in range(10):
|
||||
for x in range(10):
|
||||
grid.at(x, y).walkable = True
|
||||
grid.at(x, y).transparent = True
|
||||
grid.at(x, y).color = FLOOR_COLOR
|
||||
|
||||
# Add a simple wall
|
||||
print("Adding walls at:")
|
||||
walls = [(5, 2), (5, 3), (5, 4), (5, 5), (5, 6)]
|
||||
for x, y in walls:
|
||||
print(f" Wall at ({x}, {y})")
|
||||
grid.at(x, y).walkable = False
|
||||
grid.at(x, y).color = WALL_COLOR
|
||||
|
||||
# Create 3 entities
|
||||
entity_positions = [(2, 5), (8, 5), (5, 8)]
|
||||
entities = []
|
||||
|
||||
print("\nCreating entities at:")
|
||||
for i, (x, y) in enumerate(entity_positions):
|
||||
print(f" Entity {i+1} at ({x}, {y})")
|
||||
entity = mcrfpy.Entity(x, y)
|
||||
entity.sprite_index = 49 + i # '1', '2', '3'
|
||||
grid.entities.append(entity)
|
||||
entities.append(entity)
|
||||
|
||||
return grid
|
||||
|
||||
def test_path_highlighting():
|
||||
"""Test path highlighting with debug output"""
|
||||
print("\n" + "="*50)
|
||||
print("Testing path highlighting...")
|
||||
|
||||
# Select first two entities
|
||||
e1 = entities[0]
|
||||
e2 = entities[1]
|
||||
|
||||
print(f"\nEntity 1 position: ({e1.x}, {e1.y})")
|
||||
print(f"Entity 2 position: ({e2.x}, {e2.y})")
|
||||
|
||||
# Use entity.path_to()
|
||||
print("\nCalling entity.path_to()...")
|
||||
path = e1.path_to(int(e2.x), int(e2.y))
|
||||
|
||||
print(f"Path returned: {path}")
|
||||
print(f"Path length: {len(path)} steps")
|
||||
|
||||
if path:
|
||||
print("\nHighlighting path cells:")
|
||||
for i, (x, y) in enumerate(path):
|
||||
print(f" Step {i}: ({x}, {y})")
|
||||
# Get current color for debugging
|
||||
cell = grid.at(x, y)
|
||||
old_color = (cell.color.r, cell.color.g, cell.color.b)
|
||||
|
||||
# Set new color
|
||||
cell.color = PATH_COLOR
|
||||
new_color = (cell.color.r, cell.color.g, cell.color.b)
|
||||
|
||||
print(f" Color changed from {old_color} to {new_color}")
|
||||
print(f" Walkable: {cell.walkable}")
|
||||
|
||||
# Also test grid's Dijkstra methods
|
||||
print("\n" + "-"*30)
|
||||
print("Testing grid Dijkstra methods...")
|
||||
|
||||
grid.compute_dijkstra(int(e1.x), int(e1.y))
|
||||
grid_path = grid.get_dijkstra_path(int(e2.x), int(e2.y))
|
||||
distance = grid.get_dijkstra_distance(int(e2.x), int(e2.y))
|
||||
|
||||
print(f"Grid path: {grid_path}")
|
||||
print(f"Grid distance: {distance}")
|
||||
|
||||
# Verify colors were set
|
||||
print("\nVerifying cell colors after highlighting:")
|
||||
for x, y in path[:3]: # Check first 3 cells
|
||||
cell = grid.at(x, y)
|
||||
color = (cell.color.r, cell.color.g, cell.color.b)
|
||||
expected = (PATH_COLOR.r, PATH_COLOR.g, PATH_COLOR.b)
|
||||
match = color == expected
|
||||
print(f" Cell ({x}, {y}): color={color}, expected={expected}, match={match}")
|
||||
|
||||
def handle_keypress(scene_name, keycode):
|
||||
"""Simple keypress handler"""
|
||||
if keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
|
||||
print("\nExiting debug...")
|
||||
sys.exit(0)
|
||||
elif keycode == 32: # Space
|
||||
print("\nSpace pressed - retesting path highlighting...")
|
||||
test_path_highlighting()
|
||||
|
||||
# Create the map
|
||||
print("Dijkstra Debug Test")
|
||||
print("===================")
|
||||
grid = create_simple_map()
|
||||
|
||||
# Initial path test
|
||||
test_path_highlighting()
|
||||
|
||||
# Set up UI
|
||||
ui = mcrfpy.sceneUI("dijkstra_debug")
|
||||
ui.append(grid)
|
||||
|
||||
# Position and scale
|
||||
grid.position = (50, 50)
|
||||
grid.size = (400, 400) # 10*40
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption("Dijkstra Debug - Press SPACE to retest, Q to quit", 50, 10)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Add debug info
|
||||
info = mcrfpy.Caption("Check console for debug output", 50, 470)
|
||||
info.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(info)
|
||||
|
||||
# Set up scene
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
mcrfpy.setScene("dijkstra_debug")
|
||||
|
||||
print("\nScene ready. The path should be highlighted in cyan.")
|
||||
print("If you don't see the path, there may be a rendering issue.")
|
||||
print("Press SPACE to retest, Q to quit.")
|
|
@ -1,137 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Working Dijkstra Demo with Clear Visual Feedback
|
||||
================================================
|
||||
|
||||
This demo shows pathfinding with high-contrast colors.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# High contrast colors
|
||||
WALL_COLOR = mcrfpy.Color(40, 20, 20) # Very dark red/brown for walls
|
||||
FLOOR_COLOR = mcrfpy.Color(60, 60, 80) # Dark blue-gray for floors
|
||||
PATH_COLOR = mcrfpy.Color(0, 255, 0) # Pure green for paths
|
||||
START_COLOR = mcrfpy.Color(255, 0, 0) # Red for start
|
||||
END_COLOR = mcrfpy.Color(0, 0, 255) # Blue for end
|
||||
|
||||
print("Dijkstra Demo - High Contrast")
|
||||
print("==============================")
|
||||
|
||||
# Create scene
|
||||
mcrfpy.createScene("dijkstra_demo")
|
||||
|
||||
# Create grid with exact layout from user
|
||||
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
|
||||
# Map layout
|
||||
map_layout = [
|
||||
"..............", # Row 0
|
||||
"..W.....WWWW..", # Row 1
|
||||
"..W.W...W.EW..", # Row 2
|
||||
"..W.....W..W..", # Row 3
|
||||
"..W...E.WWWW..", # Row 4
|
||||
"E.W...........", # Row 5
|
||||
"..W...........", # Row 6
|
||||
"..W...........", # Row 7
|
||||
"..W.WWW.......", # Row 8
|
||||
"..............", # Row 9
|
||||
]
|
||||
|
||||
# Create the map
|
||||
entity_positions = []
|
||||
for y, row in enumerate(map_layout):
|
||||
for x, char in enumerate(row):
|
||||
cell = grid.at(x, y)
|
||||
|
||||
if char == 'W':
|
||||
cell.walkable = False
|
||||
cell.color = WALL_COLOR
|
||||
else:
|
||||
cell.walkable = True
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
if char == 'E':
|
||||
entity_positions.append((x, y))
|
||||
|
||||
print(f"Map created: {grid.grid_x}x{grid.grid_y}")
|
||||
print(f"Entity positions: {entity_positions}")
|
||||
|
||||
# Create entities
|
||||
entities = []
|
||||
for i, (x, y) in enumerate(entity_positions):
|
||||
entity = mcrfpy.Entity(x, y)
|
||||
entity.sprite_index = 49 + i # '1', '2', '3'
|
||||
grid.entities.append(entity)
|
||||
entities.append(entity)
|
||||
print(f"Entity {i+1} at ({x}, {y})")
|
||||
|
||||
# Highlight a path immediately
|
||||
if len(entities) >= 2:
|
||||
e1, e2 = entities[0], entities[1]
|
||||
print(f"\nCalculating path from Entity 1 ({e1.x}, {e1.y}) to Entity 2 ({e2.x}, {e2.y})...")
|
||||
|
||||
path = e1.path_to(int(e2.x), int(e2.y))
|
||||
print(f"Path found: {path}")
|
||||
print(f"Path length: {len(path)} steps")
|
||||
|
||||
if path:
|
||||
print("\nHighlighting path in bright green...")
|
||||
# Color start and end specially
|
||||
grid.at(int(e1.x), int(e1.y)).color = START_COLOR
|
||||
grid.at(int(e2.x), int(e2.y)).color = END_COLOR
|
||||
|
||||
# Color the path
|
||||
for i, (x, y) in enumerate(path):
|
||||
if i > 0 and i < len(path) - 1: # Skip start and end
|
||||
grid.at(x, y).color = PATH_COLOR
|
||||
print(f" Colored ({x}, {y}) green")
|
||||
|
||||
# Keypress handler
|
||||
def handle_keypress(scene_name, keycode):
|
||||
if keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
|
||||
print("\nExiting...")
|
||||
sys.exit(0)
|
||||
elif keycode == 32: # Space
|
||||
print("\nRefreshing path colors...")
|
||||
# Re-color the path to ensure it's visible
|
||||
if len(entities) >= 2 and path:
|
||||
for x, y in path[1:-1]:
|
||||
grid.at(x, y).color = PATH_COLOR
|
||||
|
||||
# Set up UI
|
||||
ui = mcrfpy.sceneUI("dijkstra_demo")
|
||||
ui.append(grid)
|
||||
|
||||
# Scale grid
|
||||
grid.size = (560, 400) # 14*40, 10*40
|
||||
grid.position = (120, 100)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption("Dijkstra Pathfinding - High Contrast", 200, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Add legend
|
||||
legend1 = mcrfpy.Caption("Red=Start, Blue=End, Green=Path", 120, 520)
|
||||
legend1.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(legend1)
|
||||
|
||||
legend2 = mcrfpy.Caption("Press Q to quit, SPACE to refresh", 120, 540)
|
||||
legend2.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend2)
|
||||
|
||||
# Entity info
|
||||
info = mcrfpy.Caption(f"Path: Entity 1 to 2 = {len(path) if 'path' in locals() else 0} steps", 120, 60)
|
||||
info.fill_color = mcrfpy.Color(255, 255, 100)
|
||||
ui.append(info)
|
||||
|
||||
# Set up input
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
mcrfpy.setScene("dijkstra_demo")
|
||||
|
||||
print("\nDemo ready! The path should be clearly visible in bright green.")
|
||||
print("Red = Start, Blue = End, Green = Path")
|
||||
print("Press SPACE to refresh colors if needed.")
|
|
@ -1,244 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Dijkstra Pathfinding Interactive Demo
|
||||
=====================================
|
||||
|
||||
Interactive visualization showing Dijkstra pathfinding between entities.
|
||||
|
||||
Controls:
|
||||
- Press 1/2/3 to select the first entity
|
||||
- Press A/B/C to select the second entity
|
||||
- Space to clear selection
|
||||
- Q or ESC to quit
|
||||
|
||||
The path between selected entities is automatically highlighted.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Colors - using more distinct values
|
||||
WALL_COLOR = mcrfpy.Color(60, 30, 30)
|
||||
FLOOR_COLOR = mcrfpy.Color(100, 100, 120) # Darker floor for better contrast
|
||||
PATH_COLOR = mcrfpy.Color(50, 255, 50) # Bright green for path
|
||||
ENTITY_COLORS = [
|
||||
mcrfpy.Color(255, 100, 100), # Entity 1 - Red
|
||||
mcrfpy.Color(100, 255, 100), # Entity 2 - Green
|
||||
mcrfpy.Color(100, 100, 255), # Entity 3 - Blue
|
||||
]
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
entities = []
|
||||
first_point = None
|
||||
second_point = None
|
||||
|
||||
def create_map():
|
||||
"""Create the interactive map with the layout specified by the user"""
|
||||
global grid, entities
|
||||
|
||||
mcrfpy.createScene("dijkstra_interactive")
|
||||
|
||||
# Create grid - 14x10 as specified
|
||||
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
|
||||
# Define the map layout from user's specification
|
||||
# . = floor, W = wall, E = entity position
|
||||
map_layout = [
|
||||
"..............", # Row 0
|
||||
"..W.....WWWW..", # Row 1
|
||||
"..W.W...W.EW..", # Row 2
|
||||
"..W.....W..W..", # Row 3
|
||||
"..W...E.WWWW..", # Row 4
|
||||
"E.W...........", # Row 5
|
||||
"..W...........", # Row 6
|
||||
"..W...........", # Row 7
|
||||
"..W.WWW.......", # Row 8
|
||||
"..............", # Row 9
|
||||
]
|
||||
|
||||
# Create the map
|
||||
entity_positions = []
|
||||
for y, row in enumerate(map_layout):
|
||||
for x, char in enumerate(row):
|
||||
cell = grid.at(x, y)
|
||||
|
||||
if char == 'W':
|
||||
# Wall
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.color = WALL_COLOR
|
||||
else:
|
||||
# Floor
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
if char == 'E':
|
||||
# Entity position
|
||||
entity_positions.append((x, y))
|
||||
|
||||
# Create entities at marked positions
|
||||
entities = []
|
||||
for i, (x, y) in enumerate(entity_positions):
|
||||
entity = mcrfpy.Entity(x, y)
|
||||
entity.sprite_index = 49 + i # '1', '2', '3'
|
||||
grid.entities.append(entity)
|
||||
entities.append(entity)
|
||||
|
||||
return grid
|
||||
|
||||
def clear_path_highlight():
|
||||
"""Clear any existing path highlighting"""
|
||||
# Reset all floor tiles to original color
|
||||
for y in range(grid.grid_y):
|
||||
for x in range(grid.grid_x):
|
||||
cell = grid.at(x, y)
|
||||
if cell.walkable:
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
def highlight_path():
|
||||
"""Highlight the path between selected entities"""
|
||||
if first_point is None or second_point is None:
|
||||
return
|
||||
|
||||
# Clear previous highlighting
|
||||
clear_path_highlight()
|
||||
|
||||
# Get entities
|
||||
entity1 = entities[first_point]
|
||||
entity2 = entities[second_point]
|
||||
|
||||
# Compute Dijkstra from first entity
|
||||
grid.compute_dijkstra(int(entity1.x), int(entity1.y))
|
||||
|
||||
# Get path to second entity
|
||||
path = grid.get_dijkstra_path(int(entity2.x), int(entity2.y))
|
||||
|
||||
if path:
|
||||
# Highlight the path
|
||||
for x, y in path:
|
||||
cell = grid.at(x, y)
|
||||
if cell.walkable:
|
||||
cell.color = PATH_COLOR
|
||||
|
||||
# Also highlight start and end with entity colors
|
||||
grid.at(int(entity1.x), int(entity1.y)).color = ENTITY_COLORS[first_point]
|
||||
grid.at(int(entity2.x), int(entity2.y)).color = ENTITY_COLORS[second_point]
|
||||
|
||||
# Update info
|
||||
distance = grid.get_dijkstra_distance(int(entity2.x), int(entity2.y))
|
||||
info_text.text = f"Path: Entity {first_point+1} to Entity {second_point+1} - {len(path)} steps, {distance:.1f} units"
|
||||
else:
|
||||
info_text.text = f"No path between Entity {first_point+1} and Entity {second_point+1}"
|
||||
|
||||
def handle_keypress(scene_name, keycode):
|
||||
"""Handle keyboard input"""
|
||||
global first_point, second_point
|
||||
|
||||
# Number keys for first entity
|
||||
if keycode == 49: # '1'
|
||||
first_point = 0
|
||||
status_text.text = f"First: Entity 1 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
|
||||
highlight_path()
|
||||
elif keycode == 50: # '2'
|
||||
first_point = 1
|
||||
status_text.text = f"First: Entity 2 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
|
||||
highlight_path()
|
||||
elif keycode == 51: # '3'
|
||||
first_point = 2
|
||||
status_text.text = f"First: Entity 3 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
|
||||
highlight_path()
|
||||
|
||||
# Letter keys for second entity
|
||||
elif keycode == 65 or keycode == 97: # 'A' or 'a'
|
||||
second_point = 0
|
||||
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 1"
|
||||
highlight_path()
|
||||
elif keycode == 66 or keycode == 98: # 'B' or 'b'
|
||||
second_point = 1
|
||||
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 2"
|
||||
highlight_path()
|
||||
elif keycode == 67 or keycode == 99: # 'C' or 'c'
|
||||
second_point = 2
|
||||
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 3"
|
||||
highlight_path()
|
||||
|
||||
# Clear selection
|
||||
elif keycode == 32: # Space
|
||||
first_point = None
|
||||
second_point = None
|
||||
clear_path_highlight()
|
||||
status_text.text = "Press 1/2/3 for first entity, A/B/C for second"
|
||||
info_text.text = "Space to clear, Q to quit"
|
||||
|
||||
# Quit
|
||||
elif keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
|
||||
print("\nExiting Dijkstra interactive demo...")
|
||||
sys.exit(0)
|
||||
|
||||
# Create the visualization
|
||||
print("Dijkstra Pathfinding Interactive Demo")
|
||||
print("=====================================")
|
||||
print("Controls:")
|
||||
print(" 1/2/3 - Select first entity")
|
||||
print(" A/B/C - Select second entity")
|
||||
print(" Space - Clear selection")
|
||||
print(" Q/ESC - Quit")
|
||||
|
||||
# Create map
|
||||
grid = create_map()
|
||||
|
||||
# Set up UI
|
||||
ui = mcrfpy.sceneUI("dijkstra_interactive")
|
||||
ui.append(grid)
|
||||
|
||||
# Scale and position grid for better visibility
|
||||
grid.size = (560, 400) # 14*40, 10*40
|
||||
grid.position = (120, 60)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption("Dijkstra Pathfinding Interactive", 250, 10)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Add status text
|
||||
status_text = mcrfpy.Caption("Press 1/2/3 for first entity, A/B/C for second", 120, 480)
|
||||
status_text.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(status_text)
|
||||
|
||||
# Add info text
|
||||
info_text = mcrfpy.Caption("Space to clear, Q to quit", 120, 500)
|
||||
info_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(info_text)
|
||||
|
||||
# Add legend
|
||||
legend1 = mcrfpy.Caption("Entities: 1=Red 2=Green 3=Blue", 120, 540)
|
||||
legend1.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend1)
|
||||
|
||||
legend2 = mcrfpy.Caption("Colors: Dark=Wall Light=Floor Cyan=Path", 120, 560)
|
||||
legend2.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend2)
|
||||
|
||||
# Mark entity positions with colored indicators
|
||||
for i, entity in enumerate(entities):
|
||||
marker = mcrfpy.Caption(str(i+1),
|
||||
120 + int(entity.x) * 40 + 15,
|
||||
60 + int(entity.y) * 40 + 10)
|
||||
marker.fill_color = ENTITY_COLORS[i]
|
||||
marker.outline = 1
|
||||
marker.outline_color = mcrfpy.Color(0, 0, 0)
|
||||
ui.append(marker)
|
||||
|
||||
# Set up input handling
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
|
||||
# Show the scene
|
||||
mcrfpy.setScene("dijkstra_interactive")
|
||||
|
||||
print("\nVisualization ready!")
|
||||
print("Entities are at:")
|
||||
for i, entity in enumerate(entities):
|
||||
print(f" Entity {i+1}: ({int(entity.x)}, {int(entity.y)})")
|
|
@ -1,344 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Enhanced Dijkstra Pathfinding Interactive Demo
|
||||
==============================================
|
||||
|
||||
Interactive visualization with entity pathfinding animations.
|
||||
|
||||
Controls:
|
||||
- Press 1/2/3 to select the first entity
|
||||
- Press A/B/C to select the second entity
|
||||
- Space to clear selection
|
||||
- M to make selected entity move along path
|
||||
- P to pause/resume animation
|
||||
- R to reset entity positions
|
||||
- Q or ESC to quit
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
import math
|
||||
|
||||
# Colors
|
||||
WALL_COLOR = mcrfpy.Color(60, 30, 30)
|
||||
FLOOR_COLOR = mcrfpy.Color(200, 200, 220)
|
||||
PATH_COLOR = mcrfpy.Color(200, 250, 220)
|
||||
VISITED_COLOR = mcrfpy.Color(180, 230, 200)
|
||||
ENTITY_COLORS = [
|
||||
mcrfpy.Color(255, 100, 100), # Entity 1 - Red
|
||||
mcrfpy.Color(100, 255, 100), # Entity 2 - Green
|
||||
mcrfpy.Color(100, 100, 255), # Entity 3 - Blue
|
||||
]
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
entities = []
|
||||
first_point = None
|
||||
second_point = None
|
||||
current_path = []
|
||||
animating = False
|
||||
animation_progress = 0.0
|
||||
animation_speed = 2.0 # cells per second
|
||||
original_positions = [] # Store original entity positions
|
||||
|
||||
def create_map():
|
||||
"""Create the interactive map with the layout specified by the user"""
|
||||
global grid, entities, original_positions
|
||||
|
||||
mcrfpy.createScene("dijkstra_enhanced")
|
||||
|
||||
# Create grid - 14x10 as specified
|
||||
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
|
||||
# Define the map layout from user's specification
|
||||
# . = floor, W = wall, E = entity position
|
||||
map_layout = [
|
||||
"..............", # Row 0
|
||||
"..W.....WWWW..", # Row 1
|
||||
"..W.W...W.EW..", # Row 2
|
||||
"..W.....W..W..", # Row 3
|
||||
"..W...E.WWWW..", # Row 4
|
||||
"E.W...........", # Row 5
|
||||
"..W...........", # Row 6
|
||||
"..W...........", # Row 7
|
||||
"..W.WWW.......", # Row 8
|
||||
"..............", # Row 9
|
||||
]
|
||||
|
||||
# Create the map
|
||||
entity_positions = []
|
||||
for y, row in enumerate(map_layout):
|
||||
for x, char in enumerate(row):
|
||||
cell = grid.at(x, y)
|
||||
|
||||
if char == 'W':
|
||||
# Wall
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.color = WALL_COLOR
|
||||
else:
|
||||
# Floor
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
if char == 'E':
|
||||
# Entity position
|
||||
entity_positions.append((x, y))
|
||||
|
||||
# Create entities at marked positions
|
||||
entities = []
|
||||
original_positions = []
|
||||
for i, (x, y) in enumerate(entity_positions):
|
||||
entity = mcrfpy.Entity(x, y)
|
||||
entity.sprite_index = 49 + i # '1', '2', '3'
|
||||
grid.entities.append(entity)
|
||||
entities.append(entity)
|
||||
original_positions.append((x, y))
|
||||
|
||||
return grid
|
||||
|
||||
def clear_path_highlight():
|
||||
"""Clear any existing path highlighting"""
|
||||
global current_path
|
||||
|
||||
# Reset all floor tiles to original color
|
||||
for y in range(grid.grid_y):
|
||||
for x in range(grid.grid_x):
|
||||
cell = grid.at(x, y)
|
||||
if cell.walkable:
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
current_path = []
|
||||
|
||||
def highlight_path():
|
||||
"""Highlight the path between selected entities using entity.path_to()"""
|
||||
global current_path
|
||||
|
||||
if first_point is None or second_point is None:
|
||||
return
|
||||
|
||||
# Clear previous highlighting
|
||||
clear_path_highlight()
|
||||
|
||||
# Get entities
|
||||
entity1 = entities[first_point]
|
||||
entity2 = entities[second_point]
|
||||
|
||||
# Use the new path_to method!
|
||||
path = entity1.path_to(int(entity2.x), int(entity2.y))
|
||||
|
||||
if path:
|
||||
current_path = path
|
||||
|
||||
# Highlight the path
|
||||
for i, (x, y) in enumerate(path):
|
||||
cell = grid.at(x, y)
|
||||
if cell.walkable:
|
||||
# Use gradient for path visualization
|
||||
if i < len(path) - 1:
|
||||
cell.color = PATH_COLOR
|
||||
else:
|
||||
cell.color = VISITED_COLOR
|
||||
|
||||
# Highlight start and end with entity colors
|
||||
grid.at(int(entity1.x), int(entity1.y)).color = ENTITY_COLORS[first_point]
|
||||
grid.at(int(entity2.x), int(entity2.y)).color = ENTITY_COLORS[second_point]
|
||||
|
||||
# Update info
|
||||
info_text.text = f"Path: Entity {first_point+1} to Entity {second_point+1} - {len(path)} steps"
|
||||
else:
|
||||
info_text.text = f"No path between Entity {first_point+1} and Entity {second_point+1}"
|
||||
current_path = []
|
||||
|
||||
def animate_movement(dt):
|
||||
"""Animate entity movement along path"""
|
||||
global animation_progress, animating, current_path
|
||||
|
||||
if not animating or not current_path or first_point is None:
|
||||
return
|
||||
|
||||
entity = entities[first_point]
|
||||
|
||||
# Update animation progress
|
||||
animation_progress += animation_speed * dt
|
||||
|
||||
# Calculate current position along path
|
||||
path_index = int(animation_progress)
|
||||
|
||||
if path_index >= len(current_path):
|
||||
# Animation complete
|
||||
animating = False
|
||||
animation_progress = 0.0
|
||||
# Snap to final position
|
||||
if current_path:
|
||||
final_x, final_y = current_path[-1]
|
||||
entity.x = float(final_x)
|
||||
entity.y = float(final_y)
|
||||
return
|
||||
|
||||
# Interpolate between path points
|
||||
if path_index < len(current_path) - 1:
|
||||
curr_x, curr_y = current_path[path_index]
|
||||
next_x, next_y = current_path[path_index + 1]
|
||||
|
||||
# Calculate interpolation factor
|
||||
t = animation_progress - path_index
|
||||
|
||||
# Smooth interpolation
|
||||
entity.x = curr_x + (next_x - curr_x) * t
|
||||
entity.y = curr_y + (next_y - curr_y) * t
|
||||
else:
|
||||
# At last point
|
||||
entity.x, entity.y = current_path[path_index]
|
||||
|
||||
def handle_keypress(scene_name, keycode):
|
||||
"""Handle keyboard input"""
|
||||
global first_point, second_point, animating, animation_progress
|
||||
|
||||
# Number keys for first entity
|
||||
if keycode == 49: # '1'
|
||||
first_point = 0
|
||||
status_text.text = f"First: Entity 1 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
|
||||
highlight_path()
|
||||
elif keycode == 50: # '2'
|
||||
first_point = 1
|
||||
status_text.text = f"First: Entity 2 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
|
||||
highlight_path()
|
||||
elif keycode == 51: # '3'
|
||||
first_point = 2
|
||||
status_text.text = f"First: Entity 3 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
|
||||
highlight_path()
|
||||
|
||||
# Letter keys for second entity
|
||||
elif keycode == 65 or keycode == 97: # 'A' or 'a'
|
||||
second_point = 0
|
||||
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 1"
|
||||
highlight_path()
|
||||
elif keycode == 66 or keycode == 98: # 'B' or 'b'
|
||||
second_point = 1
|
||||
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 2"
|
||||
highlight_path()
|
||||
elif keycode == 67 or keycode == 99: # 'C' or 'c'
|
||||
second_point = 2
|
||||
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 3"
|
||||
highlight_path()
|
||||
|
||||
# Movement control
|
||||
elif keycode == 77 or keycode == 109: # 'M' or 'm'
|
||||
if current_path and first_point is not None:
|
||||
animating = True
|
||||
animation_progress = 0.0
|
||||
control_text.text = "Animation: MOVING (press P to pause)"
|
||||
|
||||
# Pause/Resume
|
||||
elif keycode == 80 or keycode == 112: # 'P' or 'p'
|
||||
animating = not animating
|
||||
control_text.text = f"Animation: {'MOVING' if animating else 'PAUSED'} (press P to {'pause' if animating else 'resume'})"
|
||||
|
||||
# Reset positions
|
||||
elif keycode == 82 or keycode == 114: # 'R' or 'r'
|
||||
animating = False
|
||||
animation_progress = 0.0
|
||||
for i, entity in enumerate(entities):
|
||||
entity.x, entity.y = original_positions[i]
|
||||
control_text.text = "Entities reset to original positions"
|
||||
highlight_path() # Re-highlight path after reset
|
||||
|
||||
# Clear selection
|
||||
elif keycode == 32: # Space
|
||||
first_point = None
|
||||
second_point = None
|
||||
animating = False
|
||||
animation_progress = 0.0
|
||||
clear_path_highlight()
|
||||
status_text.text = "Press 1/2/3 for first entity, A/B/C for second"
|
||||
info_text.text = "Space to clear, Q to quit"
|
||||
control_text.text = "Press M to move, P to pause, R to reset"
|
||||
|
||||
# Quit
|
||||
elif keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
|
||||
print("\nExiting enhanced Dijkstra demo...")
|
||||
sys.exit(0)
|
||||
|
||||
# Timer callback for animation
|
||||
def update_animation(dt):
|
||||
"""Update animation state"""
|
||||
animate_movement(dt / 1000.0) # Convert ms to seconds
|
||||
|
||||
# Create the visualization
|
||||
print("Enhanced Dijkstra Pathfinding Demo")
|
||||
print("==================================")
|
||||
print("Controls:")
|
||||
print(" 1/2/3 - Select first entity")
|
||||
print(" A/B/C - Select second entity")
|
||||
print(" M - Move first entity along path")
|
||||
print(" P - Pause/Resume animation")
|
||||
print(" R - Reset entity positions")
|
||||
print(" Space - Clear selection")
|
||||
print(" Q/ESC - Quit")
|
||||
|
||||
# Create map
|
||||
grid = create_map()
|
||||
|
||||
# Set up UI
|
||||
ui = mcrfpy.sceneUI("dijkstra_enhanced")
|
||||
ui.append(grid)
|
||||
|
||||
# Scale and position grid for better visibility
|
||||
grid.size = (560, 400) # 14*40, 10*40
|
||||
grid.position = (120, 60)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption("Enhanced Dijkstra Pathfinding", 250, 10)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Add status text
|
||||
status_text = mcrfpy.Caption("Press 1/2/3 for first entity, A/B/C for second", 120, 480)
|
||||
status_text.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(status_text)
|
||||
|
||||
# Add info text
|
||||
info_text = mcrfpy.Caption("Space to clear, Q to quit", 120, 500)
|
||||
info_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(info_text)
|
||||
|
||||
# Add control text
|
||||
control_text = mcrfpy.Caption("Press M to move, P to pause, R to reset", 120, 520)
|
||||
control_text.fill_color = mcrfpy.Color(150, 200, 150)
|
||||
ui.append(control_text)
|
||||
|
||||
# Add legend
|
||||
legend1 = mcrfpy.Caption("Entities: 1=Red 2=Green 3=Blue", 120, 560)
|
||||
legend1.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend1)
|
||||
|
||||
legend2 = mcrfpy.Caption("Colors: Dark=Wall Light=Floor Cyan=Path", 120, 580)
|
||||
legend2.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend2)
|
||||
|
||||
# Mark entity positions with colored indicators
|
||||
for i, entity in enumerate(entities):
|
||||
marker = mcrfpy.Caption(str(i+1),
|
||||
120 + int(entity.x) * 40 + 15,
|
||||
60 + int(entity.y) * 40 + 10)
|
||||
marker.fill_color = ENTITY_COLORS[i]
|
||||
marker.outline = 1
|
||||
marker.outline_color = mcrfpy.Color(0, 0, 0)
|
||||
ui.append(marker)
|
||||
|
||||
# Set up input handling
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
|
||||
# Set up animation timer (60 FPS)
|
||||
mcrfpy.setTimer("animation", update_animation, 16)
|
||||
|
||||
# Show the scene
|
||||
mcrfpy.setScene("dijkstra_enhanced")
|
||||
|
||||
print("\nVisualization ready!")
|
||||
print("Entities are at:")
|
||||
for i, entity in enumerate(entities):
|
||||
print(f" Entity {i+1}: ({int(entity.x)}, {int(entity.y)})")
|
|
@ -1,146 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Dijkstra Pathfinding Test - Headless
|
||||
====================================
|
||||
|
||||
Tests all Dijkstra functionality and generates a screenshot.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
|
||||
def create_test_map():
|
||||
"""Create a test map with obstacles"""
|
||||
mcrfpy.createScene("dijkstra_test")
|
||||
|
||||
# Create grid
|
||||
grid = mcrfpy.Grid(grid_x=20, grid_y=12)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
|
||||
# Initialize all cells as walkable floor
|
||||
for y in range(12):
|
||||
for x in range(20):
|
||||
grid.at(x, y).walkable = True
|
||||
grid.at(x, y).transparent = True
|
||||
grid.at(x, y).color = mcrfpy.Color(200, 200, 220)
|
||||
|
||||
# Add walls to create interesting paths
|
||||
walls = [
|
||||
# Vertical wall in the middle
|
||||
(10, 1), (10, 2), (10, 3), (10, 4), (10, 5), (10, 6), (10, 7), (10, 8),
|
||||
# Horizontal walls
|
||||
(2, 6), (3, 6), (4, 6), (5, 6), (6, 6),
|
||||
(14, 6), (15, 6), (16, 6), (17, 6),
|
||||
# Some scattered obstacles
|
||||
(5, 2), (15, 2), (5, 9), (15, 9)
|
||||
]
|
||||
|
||||
for x, y in walls:
|
||||
grid.at(x, y).walkable = False
|
||||
grid.at(x, y).color = mcrfpy.Color(60, 30, 30)
|
||||
|
||||
# Place test entities
|
||||
entities = []
|
||||
positions = [(2, 2), (17, 2), (9, 10)]
|
||||
colors = [
|
||||
mcrfpy.Color(255, 100, 100), # Red
|
||||
mcrfpy.Color(100, 255, 100), # Green
|
||||
mcrfpy.Color(100, 100, 255) # Blue
|
||||
]
|
||||
|
||||
for i, (x, y) in enumerate(positions):
|
||||
entity = mcrfpy.Entity(x, y)
|
||||
entity.sprite_index = 49 + i # '1', '2', '3'
|
||||
grid.entities.append(entity)
|
||||
entities.append(entity)
|
||||
# Mark entity positions
|
||||
grid.at(x, y).color = colors[i]
|
||||
|
||||
return grid, entities
|
||||
|
||||
def test_dijkstra(grid, entities):
|
||||
"""Test Dijkstra pathfinding between all entity pairs"""
|
||||
results = []
|
||||
|
||||
for i in range(len(entities)):
|
||||
for j in range(len(entities)):
|
||||
if i != j:
|
||||
# Compute Dijkstra from entity i
|
||||
e1 = entities[i]
|
||||
e2 = entities[j]
|
||||
grid.compute_dijkstra(int(e1.x), int(e1.y))
|
||||
|
||||
# Get distance and path to entity j
|
||||
distance = grid.get_dijkstra_distance(int(e2.x), int(e2.y))
|
||||
path = grid.get_dijkstra_path(int(e2.x), int(e2.y))
|
||||
|
||||
if path:
|
||||
results.append(f"Path {i+1}→{j+1}: {len(path)} steps, {distance:.1f} units")
|
||||
|
||||
# Color one interesting path
|
||||
if i == 0 and j == 2: # Path from 1 to 3
|
||||
for x, y in path[1:-1]: # Skip endpoints
|
||||
if grid.at(x, y).walkable:
|
||||
grid.at(x, y).color = mcrfpy.Color(200, 250, 220)
|
||||
else:
|
||||
results.append(f"Path {i+1}→{j+1}: No path found!")
|
||||
|
||||
return results
|
||||
|
||||
def run_test(runtime):
|
||||
"""Timer callback to run tests and take screenshot"""
|
||||
# Run pathfinding tests
|
||||
results = test_dijkstra(grid, entities)
|
||||
|
||||
# Update display with results
|
||||
y_pos = 380
|
||||
for result in results:
|
||||
caption = mcrfpy.Caption(result, 50, y_pos)
|
||||
caption.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(caption)
|
||||
y_pos += 20
|
||||
|
||||
# Take screenshot
|
||||
mcrfpy.setTimer("screenshot", lambda rt: take_screenshot(), 500)
|
||||
|
||||
def take_screenshot():
|
||||
"""Take screenshot and exit"""
|
||||
try:
|
||||
automation.screenshot("dijkstra_test.png")
|
||||
print("Screenshot saved: dijkstra_test.png")
|
||||
except Exception as e:
|
||||
print(f"Screenshot failed: {e}")
|
||||
|
||||
# Exit
|
||||
sys.exit(0)
|
||||
|
||||
# Create test map
|
||||
print("Creating Dijkstra pathfinding test...")
|
||||
grid, entities = create_test_map()
|
||||
|
||||
# Set up UI
|
||||
ui = mcrfpy.sceneUI("dijkstra_test")
|
||||
ui.append(grid)
|
||||
|
||||
# Position and scale grid
|
||||
grid.position = (50, 50)
|
||||
grid.size = (500, 300)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption("Dijkstra Pathfinding Test", 200, 10)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Add legend
|
||||
legend = mcrfpy.Caption("Red=Entity1 Green=Entity2 Blue=Entity3 Cyan=Path 1→3", 50, 360)
|
||||
legend.fill_color = mcrfpy.Color(180, 180, 180)
|
||||
ui.append(legend)
|
||||
|
||||
# Set scene
|
||||
mcrfpy.setScene("dijkstra_test")
|
||||
|
||||
# Run test after scene loads
|
||||
mcrfpy.setTimer("test", run_test, 100)
|
||||
|
||||
print("Running Dijkstra tests...")
|
|
@ -1,201 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Interactive Visibility Demo
|
||||
==========================
|
||||
|
||||
Controls:
|
||||
- WASD: Move the player (green @)
|
||||
- Arrow keys: Move enemy (red E)
|
||||
- Tab: Cycle perspective (Omniscient → Player → Enemy → Omniscient)
|
||||
- Space: Update visibility for current entity
|
||||
- R: Reset positions
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Create scene and grid
|
||||
mcrfpy.createScene("visibility_demo")
|
||||
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
|
||||
grid.fill_color = mcrfpy.Color(20, 20, 30) # Dark background
|
||||
|
||||
# Initialize grid - all walkable and transparent
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
cell = grid.at(x, y)
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.color = mcrfpy.Color(100, 100, 120) # Floor color
|
||||
|
||||
# Create walls
|
||||
walls = [
|
||||
# Central cross
|
||||
[(15, y) for y in range(8, 12)],
|
||||
[(x, 10) for x in range(13, 18)],
|
||||
|
||||
# Rooms
|
||||
# Top-left room
|
||||
[(x, 5) for x in range(2, 8)] + [(8, y) for y in range(2, 6)],
|
||||
[(2, y) for y in range(2, 6)] + [(x, 2) for x in range(2, 8)],
|
||||
|
||||
# Top-right room
|
||||
[(x, 5) for x in range(22, 28)] + [(22, y) for y in range(2, 6)],
|
||||
[(28, y) for y in range(2, 6)] + [(x, 2) for x in range(22, 28)],
|
||||
|
||||
# Bottom-left room
|
||||
[(x, 15) for x in range(2, 8)] + [(8, y) for y in range(15, 18)],
|
||||
[(2, y) for y in range(15, 18)] + [(x, 18) for x in range(2, 8)],
|
||||
|
||||
# Bottom-right room
|
||||
[(x, 15) for x in range(22, 28)] + [(22, y) for y in range(15, 18)],
|
||||
[(28, y) for y in range(15, 18)] + [(x, 18) for x in range(22, 28)],
|
||||
]
|
||||
|
||||
for wall_group in walls:
|
||||
for x, y in wall_group:
|
||||
if 0 <= x < 30 and 0 <= y < 20:
|
||||
cell = grid.at(x, y)
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.color = mcrfpy.Color(40, 20, 20) # Wall color
|
||||
|
||||
# Create entities
|
||||
player = mcrfpy.Entity(5, 10, grid=grid)
|
||||
player.sprite_index = 64 # @
|
||||
enemy = mcrfpy.Entity(25, 10, grid=grid)
|
||||
enemy.sprite_index = 69 # E
|
||||
|
||||
# Update initial visibility
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
|
||||
# Global state
|
||||
current_perspective = -1
|
||||
perspective_names = ["Omniscient", "Player", "Enemy"]
|
||||
|
||||
# UI Setup
|
||||
ui = mcrfpy.sceneUI("visibility_demo")
|
||||
ui.append(grid)
|
||||
grid.position = (50, 100)
|
||||
grid.size = (900, 600) # 30*30, 20*30
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption("Interactive Visibility Demo", 350, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Info displays
|
||||
perspective_label = mcrfpy.Caption("Perspective: Omniscient", 50, 50)
|
||||
perspective_label.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(perspective_label)
|
||||
|
||||
controls = mcrfpy.Caption("WASD: Move player | Arrows: Move enemy | Tab: Cycle perspective | Space: Update visibility | R: Reset", 50, 730)
|
||||
controls.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(controls)
|
||||
|
||||
player_info = mcrfpy.Caption("Player: (5, 10)", 700, 50)
|
||||
player_info.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
ui.append(player_info)
|
||||
|
||||
enemy_info = mcrfpy.Caption("Enemy: (25, 10)", 700, 70)
|
||||
enemy_info.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
ui.append(enemy_info)
|
||||
|
||||
# Helper functions
|
||||
def move_entity(entity, dx, dy):
|
||||
"""Move entity if target is walkable"""
|
||||
new_x = int(entity.x + dx)
|
||||
new_y = int(entity.y + dy)
|
||||
|
||||
if 0 <= new_x < 30 and 0 <= new_y < 20:
|
||||
cell = grid.at(new_x, new_y)
|
||||
if cell.walkable:
|
||||
entity.x = new_x
|
||||
entity.y = new_y
|
||||
entity.update_visibility()
|
||||
return True
|
||||
return False
|
||||
|
||||
def update_info():
|
||||
"""Update info displays"""
|
||||
player_info.text = f"Player: ({int(player.x)}, {int(player.y)})"
|
||||
enemy_info.text = f"Enemy: ({int(enemy.x)}, {int(enemy.y)})"
|
||||
|
||||
def cycle_perspective():
|
||||
"""Cycle through perspectives"""
|
||||
global current_perspective
|
||||
|
||||
# Cycle: -1 → 0 → 1 → -1
|
||||
current_perspective = (current_perspective + 2) % 3 - 1
|
||||
|
||||
grid.perspective = current_perspective
|
||||
name = perspective_names[current_perspective + 1]
|
||||
perspective_label.text = f"Perspective: {name}"
|
||||
|
||||
# Key handlers
|
||||
def handle_keys(key, state):
|
||||
"""Handle keyboard input"""
|
||||
if state == "end": return
|
||||
key = key.lower()
|
||||
# Player movement (WASD)
|
||||
if key == "w":
|
||||
move_entity(player, 0, -1)
|
||||
elif key == "s":
|
||||
move_entity(player, 0, 1)
|
||||
elif key == "a":
|
||||
move_entity(player, -1, 0)
|
||||
elif key == "d":
|
||||
move_entity(player, 1, 0)
|
||||
|
||||
# Enemy movement (Arrows)
|
||||
elif key == "up":
|
||||
move_entity(enemy, 0, -1)
|
||||
elif key == "down":
|
||||
move_entity(enemy, 0, 1)
|
||||
elif key == "left":
|
||||
move_entity(enemy, -1, 0)
|
||||
elif key == "right":
|
||||
move_entity(enemy, 1, 0)
|
||||
|
||||
# Tab to cycle perspective
|
||||
elif key == "tab":
|
||||
cycle_perspective()
|
||||
|
||||
# Space to update visibility
|
||||
elif key == "space":
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
print("Updated visibility for both entities")
|
||||
|
||||
# R to reset
|
||||
elif key == "r":
|
||||
player.x, player.y = 5, 10
|
||||
enemy.x, enemy.y = 25, 10
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
update_info()
|
||||
print("Reset positions")
|
||||
|
||||
# Q to quit
|
||||
elif key == "q":
|
||||
print("Exiting...")
|
||||
sys.exit(0)
|
||||
|
||||
update_info()
|
||||
|
||||
# Set scene first
|
||||
mcrfpy.setScene("visibility_demo")
|
||||
|
||||
# Register key handler (operates on current scene)
|
||||
mcrfpy.keypressScene(handle_keys)
|
||||
|
||||
print("Interactive Visibility Demo")
|
||||
print("===========================")
|
||||
print("WASD: Move player (green @)")
|
||||
print("Arrows: Move enemy (red E)")
|
||||
print("Tab: Cycle perspective")
|
||||
print("Space: Update visibility")
|
||||
print("R: Reset positions")
|
||||
print("Q: Quit")
|
||||
print("\nCurrent perspective: Omniscient (shows all)")
|
||||
print("Try moving entities and switching perspectives!")
|
|
@ -1,375 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Path & Vision Sizzle Reel (Fixed)
|
||||
=================================
|
||||
|
||||
Fixed version with proper animation chaining to prevent glitches.
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
class PathAnimator:
|
||||
"""Handles step-by-step animation with proper completion tracking"""
|
||||
|
||||
def __init__(self, entity, name="animator"):
|
||||
self.entity = entity
|
||||
self.name = name
|
||||
self.path = []
|
||||
self.current_index = 0
|
||||
self.step_duration = 0.4
|
||||
self.animating = False
|
||||
self.on_step = None
|
||||
self.on_complete = None
|
||||
|
||||
def set_path(self, path):
|
||||
"""Set the path to animate along"""
|
||||
self.path = path
|
||||
self.current_index = 0
|
||||
|
||||
def start(self):
|
||||
"""Start animating"""
|
||||
if not self.path:
|
||||
return
|
||||
|
||||
self.animating = True
|
||||
self.current_index = 0
|
||||
self._move_to_next()
|
||||
|
||||
def stop(self):
|
||||
"""Stop animating"""
|
||||
self.animating = False
|
||||
mcrfpy.delTimer(f"{self.name}_check")
|
||||
|
||||
def _move_to_next(self):
|
||||
"""Move to next position in path"""
|
||||
if not self.animating or self.current_index >= len(self.path):
|
||||
self.animating = False
|
||||
if self.on_complete:
|
||||
self.on_complete()
|
||||
return
|
||||
|
||||
# Get next position
|
||||
x, y = self.path[self.current_index]
|
||||
|
||||
# Create animations
|
||||
anim_x = mcrfpy.Animation("x", float(x), self.step_duration, "easeInOut")
|
||||
anim_y = mcrfpy.Animation("y", float(y), self.step_duration, "easeInOut")
|
||||
|
||||
anim_x.start(self.entity)
|
||||
anim_y.start(self.entity)
|
||||
|
||||
# Update visibility
|
||||
self.entity.update_visibility()
|
||||
|
||||
# Callback for each step
|
||||
if self.on_step:
|
||||
self.on_step(self.current_index, x, y)
|
||||
|
||||
# Schedule next move
|
||||
delay = int(self.step_duration * 1000) + 50 # Add small buffer
|
||||
mcrfpy.setTimer(f"{self.name}_next", self._handle_next, delay)
|
||||
|
||||
def _handle_next(self, dt):
|
||||
"""Timer callback to move to next position"""
|
||||
self.current_index += 1
|
||||
mcrfpy.delTimer(f"{self.name}_next")
|
||||
self._move_to_next()
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
player = None
|
||||
enemy = None
|
||||
player_animator = None
|
||||
enemy_animator = None
|
||||
demo_phase = 0
|
||||
|
||||
def create_scene():
|
||||
"""Create the demo environment"""
|
||||
global grid, player, enemy
|
||||
|
||||
mcrfpy.createScene("fixed_demo")
|
||||
|
||||
# Create grid
|
||||
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
|
||||
grid.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
|
||||
# Simple dungeon layout
|
||||
map_layout = [
|
||||
"##############################",
|
||||
"#......#########.....#########",
|
||||
"#......#########.....#########",
|
||||
"#......#.........#...#########",
|
||||
"#......#.........#...#########",
|
||||
"####.###.........#.###########",
|
||||
"####.............#.###########",
|
||||
"####.............#.###########",
|
||||
"####.###.........#.###########",
|
||||
"#......#.........#...#########",
|
||||
"#......#.........#...#########",
|
||||
"#......#########.#...........#",
|
||||
"#......#########.#...........#",
|
||||
"#......#########.#...........#",
|
||||
"#......#########.#############",
|
||||
"####.###########.............#",
|
||||
"####.........................#",
|
||||
"####.###########.............#",
|
||||
"#......#########.............#",
|
||||
"##############################",
|
||||
]
|
||||
|
||||
# Build map
|
||||
for y, row in enumerate(map_layout):
|
||||
for x, char in enumerate(row):
|
||||
cell = grid.at(x, y)
|
||||
if char == '#':
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.color = mcrfpy.Color(40, 30, 30)
|
||||
else:
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.color = mcrfpy.Color(80, 80, 100)
|
||||
|
||||
# Create entities
|
||||
player = mcrfpy.Entity(3, 3, grid=grid)
|
||||
player.sprite_index = 64 # @
|
||||
|
||||
enemy = mcrfpy.Entity(26, 16, grid=grid)
|
||||
enemy.sprite_index = 69 # E
|
||||
|
||||
# Initial visibility
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
|
||||
# Set initial perspective
|
||||
grid.perspective = 0
|
||||
|
||||
def setup_ui():
|
||||
"""Create UI elements"""
|
||||
ui = mcrfpy.sceneUI("fixed_demo")
|
||||
ui.append(grid)
|
||||
|
||||
grid.position = (50, 80)
|
||||
grid.size = (700, 500)
|
||||
|
||||
title = mcrfpy.Caption("Path & Vision Demo (Fixed)", 300, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
global status_text, perspective_text
|
||||
status_text = mcrfpy.Caption("Initializing...", 50, 50)
|
||||
status_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(status_text)
|
||||
|
||||
perspective_text = mcrfpy.Caption("Perspective: Player", 550, 50)
|
||||
perspective_text.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
ui.append(perspective_text)
|
||||
|
||||
controls = mcrfpy.Caption("Space: Start/Pause | R: Restart | Q: Quit", 250, 600)
|
||||
controls.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(controls)
|
||||
|
||||
def update_camera_smooth(target, duration=0.3):
|
||||
"""Smoothly move camera to entity"""
|
||||
center_x = target.x * 23 # Approximate pixel size
|
||||
center_y = target.y * 23
|
||||
|
||||
cam_anim = mcrfpy.Animation("center", (center_x, center_y), duration, "easeOut")
|
||||
cam_anim.start(grid)
|
||||
|
||||
def start_demo():
|
||||
"""Start the demo sequence"""
|
||||
global demo_phase, player_animator, enemy_animator
|
||||
|
||||
demo_phase = 1
|
||||
status_text.text = "Phase 1: Player movement with camera follow"
|
||||
|
||||
# Player path
|
||||
player_path = [
|
||||
(3, 3), (3, 6), (4, 6), (7, 6), (7, 8),
|
||||
(10, 8), (13, 8), (16, 8), (16, 10),
|
||||
(16, 13), (16, 16), (20, 16), (24, 16)
|
||||
]
|
||||
|
||||
# Setup player animator
|
||||
player_animator = PathAnimator(player, "player")
|
||||
player_animator.set_path(player_path)
|
||||
player_animator.step_duration = 0.5
|
||||
|
||||
def on_player_step(index, x, y):
|
||||
"""Called for each player step"""
|
||||
status_text.text = f"Player step {index+1}/{len(player_path)}"
|
||||
if grid.perspective == 0:
|
||||
update_camera_smooth(player, 0.4)
|
||||
|
||||
def on_player_complete():
|
||||
"""Called when player path is complete"""
|
||||
start_phase_2()
|
||||
|
||||
player_animator.on_step = on_player_step
|
||||
player_animator.on_complete = on_player_complete
|
||||
player_animator.start()
|
||||
|
||||
def start_phase_2():
|
||||
"""Start enemy movement phase"""
|
||||
global demo_phase
|
||||
|
||||
demo_phase = 2
|
||||
status_text.text = "Phase 2: Enemy movement (may enter player's view)"
|
||||
|
||||
# Enemy path
|
||||
enemy_path = [
|
||||
(26, 16), (22, 16), (18, 16), (16, 16),
|
||||
(16, 13), (16, 10), (16, 8), (13, 8),
|
||||
(10, 8), (7, 8), (7, 6), (4, 6)
|
||||
]
|
||||
|
||||
# Setup enemy animator
|
||||
enemy_animator.set_path(enemy_path)
|
||||
enemy_animator.step_duration = 0.4
|
||||
|
||||
def on_enemy_step(index, x, y):
|
||||
"""Check if enemy is visible to player"""
|
||||
if grid.perspective == 0:
|
||||
# Check if enemy is in player's view
|
||||
enemy_idx = int(y) * grid.grid_x + int(x)
|
||||
if enemy_idx < len(player.gridstate) and player.gridstate[enemy_idx].visible:
|
||||
status_text.text = "Enemy spotted in player's view!"
|
||||
|
||||
def on_enemy_complete():
|
||||
"""Start perspective transition"""
|
||||
start_phase_3()
|
||||
|
||||
enemy_animator.on_step = on_enemy_step
|
||||
enemy_animator.on_complete = on_enemy_complete
|
||||
enemy_animator.start()
|
||||
|
||||
def start_phase_3():
|
||||
"""Dramatic perspective shift"""
|
||||
global demo_phase
|
||||
|
||||
demo_phase = 3
|
||||
status_text.text = "Phase 3: Perspective shift..."
|
||||
|
||||
# Stop any ongoing animations
|
||||
player_animator.stop()
|
||||
enemy_animator.stop()
|
||||
|
||||
# Zoom out
|
||||
zoom_out = mcrfpy.Animation("zoom", 0.6, 2.0, "easeInExpo")
|
||||
zoom_out.start(grid)
|
||||
|
||||
# Schedule perspective switch
|
||||
mcrfpy.setTimer("switch_persp", switch_perspective, 2100)
|
||||
|
||||
def switch_perspective(dt):
|
||||
"""Switch to enemy perspective"""
|
||||
grid.perspective = 1
|
||||
perspective_text.text = "Perspective: Enemy"
|
||||
perspective_text.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
|
||||
# Update camera
|
||||
update_camera_smooth(enemy, 0.5)
|
||||
|
||||
# Zoom back in
|
||||
zoom_in = mcrfpy.Animation("zoom", 1.0, 2.0, "easeOutExpo")
|
||||
zoom_in.start(grid)
|
||||
|
||||
status_text.text = "Now following enemy perspective"
|
||||
|
||||
# Clean up timer
|
||||
mcrfpy.delTimer("switch_persp")
|
||||
|
||||
# Continue enemy movement after transition
|
||||
mcrfpy.setTimer("continue_enemy", continue_enemy_movement, 2500)
|
||||
|
||||
def continue_enemy_movement(dt):
|
||||
"""Continue enemy movement after perspective shift"""
|
||||
mcrfpy.delTimer("continue_enemy")
|
||||
|
||||
# Continue path
|
||||
enemy_path_2 = [
|
||||
(4, 6), (3, 6), (3, 3), (3, 2), (3, 1)
|
||||
]
|
||||
|
||||
enemy_animator.set_path(enemy_path_2)
|
||||
|
||||
def on_step(index, x, y):
|
||||
update_camera_smooth(enemy, 0.4)
|
||||
status_text.text = f"Following enemy: step {index+1}"
|
||||
|
||||
def on_complete():
|
||||
status_text.text = "Demo complete! Press R to restart"
|
||||
|
||||
enemy_animator.on_step = on_step
|
||||
enemy_animator.on_complete = on_complete
|
||||
enemy_animator.start()
|
||||
|
||||
# Control state
|
||||
running = False
|
||||
|
||||
def handle_keys(key, state):
|
||||
"""Handle keyboard input"""
|
||||
global running
|
||||
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
key = key.lower()
|
||||
|
||||
if key == "q":
|
||||
sys.exit(0)
|
||||
elif key == "space":
|
||||
if not running:
|
||||
running = True
|
||||
start_demo()
|
||||
else:
|
||||
running = False
|
||||
player_animator.stop()
|
||||
enemy_animator.stop()
|
||||
status_text.text = "Paused"
|
||||
elif key == "r":
|
||||
# Reset everything
|
||||
player.x, player.y = 3, 3
|
||||
enemy.x, enemy.y = 26, 16
|
||||
grid.perspective = 0
|
||||
perspective_text.text = "Perspective: Player"
|
||||
perspective_text.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
grid.zoom = 1.0
|
||||
update_camera_smooth(player, 0.5)
|
||||
|
||||
if running:
|
||||
player_animator.stop()
|
||||
enemy_animator.stop()
|
||||
running = False
|
||||
|
||||
status_text.text = "Reset - Press SPACE to start"
|
||||
|
||||
# Initialize
|
||||
create_scene()
|
||||
setup_ui()
|
||||
|
||||
# Setup animators
|
||||
player_animator = PathAnimator(player, "player")
|
||||
enemy_animator = PathAnimator(enemy, "enemy")
|
||||
|
||||
# Set scene
|
||||
mcrfpy.setScene("fixed_demo")
|
||||
mcrfpy.keypressScene(handle_keys)
|
||||
|
||||
# Initial camera
|
||||
grid.zoom = 1.0
|
||||
update_camera_smooth(player, 0.5)
|
||||
|
||||
print("Path & Vision Demo (Fixed)")
|
||||
print("==========================")
|
||||
print("This version properly chains animations to prevent glitches.")
|
||||
print()
|
||||
print("The demo will:")
|
||||
print("1. Move player with camera following")
|
||||
print("2. Move enemy (may enter player's view)")
|
||||
print("3. Dramatic perspective shift to enemy")
|
||||
print("4. Continue following enemy")
|
||||
print()
|
||||
print("Press SPACE to start, Q to quit")
|
|
@ -1,391 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Path & Vision Sizzle Reel
|
||||
=========================
|
||||
|
||||
A choreographed demo showing:
|
||||
- Smooth entity movement along paths
|
||||
- Camera following with grid center animation
|
||||
- Field of view updates as entities move
|
||||
- Dramatic perspective transitions with zoom effects
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Colors
|
||||
WALL_COLOR = mcrfpy.Color(40, 30, 30)
|
||||
FLOOR_COLOR = mcrfpy.Color(80, 80, 100)
|
||||
PATH_COLOR = mcrfpy.Color(120, 120, 180)
|
||||
DARK_FLOOR = mcrfpy.Color(40, 40, 50)
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
player = None
|
||||
enemy = None
|
||||
sequence_step = 0
|
||||
player_path = []
|
||||
enemy_path = []
|
||||
player_path_index = 0
|
||||
enemy_path_index = 0
|
||||
|
||||
def create_scene():
|
||||
"""Create the demo environment"""
|
||||
global grid, player, enemy
|
||||
|
||||
mcrfpy.createScene("path_vision_demo")
|
||||
|
||||
# Create larger grid for more dramatic movement
|
||||
grid = mcrfpy.Grid(grid_x=40, grid_y=25)
|
||||
grid.fill_color = mcrfpy.Color(20, 20, 30)
|
||||
|
||||
# Map layout - interconnected rooms with corridors
|
||||
map_layout = [
|
||||
"########################################", # 0
|
||||
"#......##########......################", # 1
|
||||
"#......##########......################", # 2
|
||||
"#......##########......################", # 3
|
||||
"#......#.........#.....################", # 4
|
||||
"#......#.........#.....################", # 5
|
||||
"####.###.........####.#################", # 6
|
||||
"####.....................##############", # 7
|
||||
"####.....................##############", # 8
|
||||
"####.###.........####.#################", # 9
|
||||
"#......#.........#.....################", # 10
|
||||
"#......#.........#.....################", # 11
|
||||
"#......#.........#.....################", # 12
|
||||
"#......###.....###.....################", # 13
|
||||
"#......###.....###.....################", # 14
|
||||
"#......###.....###.....#########......#", # 15
|
||||
"#......###.....###.....#########......#", # 16
|
||||
"#......###.....###.....#########......#", # 17
|
||||
"#####.############.#############......#", # 18
|
||||
"#####...........................#.....#", # 19
|
||||
"#####...........................#.....#", # 20
|
||||
"#####.############.#############......#", # 21
|
||||
"#......###########.##########.........#", # 22
|
||||
"#......###########.##########.........#", # 23
|
||||
"########################################", # 24
|
||||
]
|
||||
|
||||
# Build the map
|
||||
for y, row in enumerate(map_layout):
|
||||
for x, char in enumerate(row):
|
||||
cell = grid.at(x, y)
|
||||
if char == '#':
|
||||
cell.walkable = False
|
||||
cell.transparent = False
|
||||
cell.color = WALL_COLOR
|
||||
else:
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.color = FLOOR_COLOR
|
||||
|
||||
# Create player in top-left room
|
||||
player = mcrfpy.Entity(3, 3, grid=grid)
|
||||
player.sprite_index = 64 # @
|
||||
|
||||
# Create enemy in bottom-right area
|
||||
enemy = mcrfpy.Entity(35, 20, grid=grid)
|
||||
enemy.sprite_index = 69 # E
|
||||
|
||||
# Initial visibility
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
|
||||
# Set initial perspective to player
|
||||
grid.perspective = 0
|
||||
|
||||
def setup_paths():
|
||||
"""Define the paths for entities"""
|
||||
global player_path, enemy_path
|
||||
|
||||
# Player path: Top-left room → corridor → middle room
|
||||
player_waypoints = [
|
||||
(3, 3), # Start
|
||||
(3, 8), # Move down
|
||||
(7, 8), # Enter corridor
|
||||
(16, 8), # Through corridor
|
||||
(16, 12), # Enter middle room
|
||||
(12, 12), # Move in room
|
||||
(12, 16), # Move down
|
||||
(16, 16), # Move right
|
||||
(16, 19), # Exit room
|
||||
(25, 19), # Move right
|
||||
(30, 19), # Continue
|
||||
(35, 19), # Near enemy start
|
||||
]
|
||||
|
||||
# Enemy path: Bottom-right → around → approach player area
|
||||
enemy_waypoints = [
|
||||
(35, 20), # Start
|
||||
(30, 20), # Move left
|
||||
(25, 20), # Continue
|
||||
(20, 20), # Continue
|
||||
(16, 20), # Corridor junction
|
||||
(16, 16), # Move up (might see player)
|
||||
(16, 12), # Continue up
|
||||
(16, 8), # Top corridor
|
||||
(10, 8), # Move left
|
||||
(7, 8), # Continue
|
||||
(3, 8), # Player's area
|
||||
(3, 12), # Move down
|
||||
]
|
||||
|
||||
# Calculate full paths using pathfinding
|
||||
player_path = []
|
||||
for i in range(len(player_waypoints) - 1):
|
||||
x1, y1 = player_waypoints[i]
|
||||
x2, y2 = player_waypoints[i + 1]
|
||||
|
||||
# Use grid's A* pathfinding
|
||||
segment = grid.compute_astar_path(x1, y1, x2, y2)
|
||||
if segment:
|
||||
# Add segment (avoiding duplicates)
|
||||
if not player_path or segment[0] != player_path[-1]:
|
||||
player_path.extend(segment)
|
||||
else:
|
||||
player_path.extend(segment[1:])
|
||||
|
||||
enemy_path = []
|
||||
for i in range(len(enemy_waypoints) - 1):
|
||||
x1, y1 = enemy_waypoints[i]
|
||||
x2, y2 = enemy_waypoints[i + 1]
|
||||
|
||||
segment = grid.compute_astar_path(x1, y1, x2, y2)
|
||||
if segment:
|
||||
if not enemy_path or segment[0] != enemy_path[-1]:
|
||||
enemy_path.extend(segment)
|
||||
else:
|
||||
enemy_path.extend(segment[1:])
|
||||
|
||||
print(f"Player path: {len(player_path)} steps")
|
||||
print(f"Enemy path: {len(enemy_path)} steps")
|
||||
|
||||
def setup_ui():
|
||||
"""Create UI elements"""
|
||||
ui = mcrfpy.sceneUI("path_vision_demo")
|
||||
ui.append(grid)
|
||||
|
||||
# Position and size grid
|
||||
grid.position = (50, 80)
|
||||
grid.size = (700, 500) # Adjust based on zoom
|
||||
|
||||
# Title
|
||||
title = mcrfpy.Caption("Path & Vision Sizzle Reel", 300, 20)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Status
|
||||
global status_text, perspective_text
|
||||
status_text = mcrfpy.Caption("Starting demo...", 50, 50)
|
||||
status_text.fill_color = mcrfpy.Color(200, 200, 200)
|
||||
ui.append(status_text)
|
||||
|
||||
perspective_text = mcrfpy.Caption("Perspective: Player", 550, 50)
|
||||
perspective_text.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
ui.append(perspective_text)
|
||||
|
||||
# Controls
|
||||
controls = mcrfpy.Caption("Space: Pause/Resume | R: Restart | Q: Quit", 250, 600)
|
||||
controls.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(controls)
|
||||
|
||||
# Animation control
|
||||
paused = False
|
||||
move_timer = 0
|
||||
zoom_transition = False
|
||||
|
||||
def move_entity_smooth(entity, target_x, target_y, duration=0.3):
|
||||
"""Smoothly animate entity to position"""
|
||||
# Create position animation
|
||||
anim_x = mcrfpy.Animation("x", float(target_x), duration, "easeInOut")
|
||||
anim_y = mcrfpy.Animation("y", float(target_y), duration, "easeInOut")
|
||||
|
||||
anim_x.start(entity)
|
||||
anim_y.start(entity)
|
||||
|
||||
def update_camera_smooth(center_x, center_y, duration=0.3):
|
||||
"""Smoothly move camera center"""
|
||||
# Convert grid coords to pixel coords (assuming 16x16 tiles)
|
||||
pixel_x = center_x * 16
|
||||
pixel_y = center_y * 16
|
||||
|
||||
anim = mcrfpy.Animation("center", (pixel_x, pixel_y), duration, "easeOut")
|
||||
anim.start(grid)
|
||||
|
||||
def start_perspective_transition():
|
||||
"""Begin the dramatic perspective shift"""
|
||||
global zoom_transition, sequence_step
|
||||
zoom_transition = True
|
||||
sequence_step = 100 # Special sequence number
|
||||
|
||||
status_text.text = "Perspective shift: Zooming out..."
|
||||
|
||||
# Zoom out with elastic easing
|
||||
zoom_out = mcrfpy.Animation("zoom", 0.5, 2.0, "easeInExpo")
|
||||
zoom_out.start(grid)
|
||||
|
||||
# Schedule the perspective switch
|
||||
mcrfpy.setTimer("switch_perspective", switch_perspective, 2100)
|
||||
|
||||
def switch_perspective(dt):
|
||||
"""Switch perspective at the peak of zoom"""
|
||||
global sequence_step
|
||||
|
||||
# Switch to enemy perspective
|
||||
grid.perspective = 1
|
||||
perspective_text.text = "Perspective: Enemy"
|
||||
perspective_text.fill_color = mcrfpy.Color(255, 100, 100)
|
||||
|
||||
status_text.text = "Perspective shift: Following enemy..."
|
||||
|
||||
# Update camera to enemy position
|
||||
update_camera_smooth(enemy.x, enemy.y, 0.1)
|
||||
|
||||
# Zoom back in
|
||||
zoom_in = mcrfpy.Animation("zoom", 1.2, 2.0, "easeOutExpo")
|
||||
zoom_in.start(grid)
|
||||
|
||||
# Resume sequence
|
||||
mcrfpy.setTimer("resume_enemy", resume_enemy_sequence, 2100)
|
||||
|
||||
# Cancel this timer
|
||||
mcrfpy.delTimer("switch_perspective")
|
||||
|
||||
def resume_enemy_sequence(dt):
|
||||
"""Resume following enemy after perspective shift"""
|
||||
global sequence_step, zoom_transition
|
||||
zoom_transition = False
|
||||
sequence_step = 101 # Continue with enemy movement
|
||||
mcrfpy.delTimer("resume_enemy")
|
||||
|
||||
def sequence_tick(dt):
|
||||
"""Main sequence controller"""
|
||||
global sequence_step, player_path_index, enemy_path_index, move_timer
|
||||
|
||||
if paused or zoom_transition:
|
||||
return
|
||||
|
||||
move_timer += dt
|
||||
if move_timer < 400: # Move every 400ms
|
||||
return
|
||||
move_timer = 0
|
||||
|
||||
if sequence_step < 50:
|
||||
# Phase 1: Follow player movement
|
||||
if player_path_index < len(player_path):
|
||||
x, y = player_path[player_path_index]
|
||||
move_entity_smooth(player, x, y)
|
||||
player.update_visibility()
|
||||
|
||||
# Camera follows player
|
||||
if grid.perspective == 0:
|
||||
update_camera_smooth(player.x, player.y)
|
||||
|
||||
player_path_index += 1
|
||||
status_text.text = f"Player moving... Step {player_path_index}/{len(player_path)}"
|
||||
|
||||
# Start enemy movement after player has moved a bit
|
||||
if player_path_index == 10:
|
||||
sequence_step = 1 # Enable enemy movement
|
||||
else:
|
||||
# Player reached destination, start perspective transition
|
||||
start_perspective_transition()
|
||||
|
||||
if sequence_step >= 1 and sequence_step < 50:
|
||||
# Phase 2: Enemy movement (concurrent with player)
|
||||
if enemy_path_index < len(enemy_path):
|
||||
x, y = enemy_path[enemy_path_index]
|
||||
move_entity_smooth(enemy, x, y)
|
||||
enemy.update_visibility()
|
||||
|
||||
# Check if enemy is visible to player
|
||||
if grid.perspective == 0:
|
||||
enemy_cell_idx = int(enemy.y) * grid.grid_x + int(enemy.x)
|
||||
if enemy_cell_idx < len(player.gridstate) and player.gridstate[enemy_cell_idx].visible:
|
||||
status_text.text = "Enemy spotted!"
|
||||
|
||||
enemy_path_index += 1
|
||||
|
||||
elif sequence_step == 101:
|
||||
# Phase 3: Continue following enemy after perspective shift
|
||||
if enemy_path_index < len(enemy_path):
|
||||
x, y = enemy_path[enemy_path_index]
|
||||
move_entity_smooth(enemy, x, y)
|
||||
enemy.update_visibility()
|
||||
|
||||
# Camera follows enemy
|
||||
update_camera_smooth(enemy.x, enemy.y)
|
||||
|
||||
enemy_path_index += 1
|
||||
status_text.text = f"Following enemy... Step {enemy_path_index}/{len(enemy_path)}"
|
||||
else:
|
||||
status_text.text = "Demo complete! Press R to restart"
|
||||
sequence_step = 200 # Done
|
||||
|
||||
def handle_keys(key, state):
|
||||
"""Handle keyboard input"""
|
||||
global paused, sequence_step, player_path_index, enemy_path_index, move_timer
|
||||
key = key.lower()
|
||||
if state != "start":
|
||||
return
|
||||
|
||||
if key == "q":
|
||||
print("Exiting sizzle reel...")
|
||||
sys.exit(0)
|
||||
elif key == "space":
|
||||
paused = not paused
|
||||
status_text.text = "PAUSED" if paused else "Running..."
|
||||
elif key == "r":
|
||||
# Reset everything
|
||||
player.x, player.y = 3, 3
|
||||
enemy.x, enemy.y = 35, 20
|
||||
player.update_visibility()
|
||||
enemy.update_visibility()
|
||||
grid.perspective = 0
|
||||
perspective_text.text = "Perspective: Player"
|
||||
perspective_text.fill_color = mcrfpy.Color(100, 255, 100)
|
||||
sequence_step = 0
|
||||
player_path_index = 0
|
||||
enemy_path_index = 0
|
||||
move_timer = 0
|
||||
update_camera_smooth(player.x, player.y, 0.5)
|
||||
|
||||
# Reset zoom
|
||||
zoom_reset = mcrfpy.Animation("zoom", 1.2, 0.5, "easeOut")
|
||||
zoom_reset.start(grid)
|
||||
|
||||
status_text.text = "Demo restarted!"
|
||||
|
||||
# Initialize everything
|
||||
print("Path & Vision Sizzle Reel")
|
||||
print("=========================")
|
||||
print("Demonstrating:")
|
||||
print("- Smooth entity movement along calculated paths")
|
||||
print("- Camera following with animated grid centering")
|
||||
print("- Field of view updates as entities move")
|
||||
print("- Dramatic perspective transitions with zoom effects")
|
||||
print()
|
||||
|
||||
create_scene()
|
||||
setup_paths()
|
||||
setup_ui()
|
||||
|
||||
# Set scene and input
|
||||
mcrfpy.setScene("path_vision_demo")
|
||||
mcrfpy.keypressScene(handle_keys)
|
||||
|
||||
# Initial camera setup
|
||||
grid.zoom = 1.2
|
||||
update_camera_smooth(player.x, player.y, 0.1)
|
||||
|
||||
# Start the sequence
|
||||
mcrfpy.setTimer("sequence", sequence_tick, 50) # Tick every 50ms
|
||||
|
||||
print("Demo started!")
|
||||
print("- Player (@) will navigate through rooms")
|
||||
print("- Enemy (E) will move on a different path")
|
||||
print("- Watch for the dramatic perspective shift!")
|
||||
print()
|
||||
print("Controls: Space=Pause, R=Restart, Q=Quit")
|
|
@ -1,373 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Pathfinding Showcase Demo
|
||||
=========================
|
||||
|
||||
Demonstrates various pathfinding scenarios with multiple entities.
|
||||
|
||||
Features:
|
||||
- Multiple entities pathfinding simultaneously
|
||||
- Chase mode: entities pursue targets
|
||||
- Flee mode: entities avoid threats
|
||||
- Patrol mode: entities follow waypoints
|
||||
- Visual debugging: show Dijkstra distance field
|
||||
"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
import random
|
||||
|
||||
# Colors
|
||||
WALL_COLOR = mcrfpy.Color(40, 40, 40)
|
||||
FLOOR_COLOR = mcrfpy.Color(220, 220, 240)
|
||||
PATH_COLOR = mcrfpy.Color(180, 250, 180)
|
||||
THREAT_COLOR = mcrfpy.Color(255, 100, 100)
|
||||
GOAL_COLOR = mcrfpy.Color(100, 255, 100)
|
||||
DIJKSTRA_COLORS = [
|
||||
mcrfpy.Color(50, 50, 100), # Far
|
||||
mcrfpy.Color(70, 70, 150),
|
||||
mcrfpy.Color(90, 90, 200),
|
||||
mcrfpy.Color(110, 110, 250),
|
||||
mcrfpy.Color(150, 150, 255),
|
||||
mcrfpy.Color(200, 200, 255), # Near
|
||||
]
|
||||
|
||||
# Entity types
|
||||
PLAYER = 64 # @
|
||||
ENEMY = 69 # E
|
||||
TREASURE = 36 # $
|
||||
PATROL = 80 # P
|
||||
|
||||
# Global state
|
||||
grid = None
|
||||
player = None
|
||||
enemies = []
|
||||
treasures = []
|
||||
patrol_entities = []
|
||||
mode = "CHASE"
|
||||
show_dijkstra = False
|
||||
animation_speed = 3.0
|
||||
|
||||
def create_dungeon():
|
||||
"""Create a dungeon-like map"""
|
||||
global grid
|
||||
|
||||
mcrfpy.createScene("pathfinding_showcase")
|
||||
|
||||
# Create larger grid for showcase
|
||||
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
|
||||
grid.fill_color = mcrfpy.Color(0, 0, 0)
|
||||
|
||||
# Initialize all as floor
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
grid.at(x, y).walkable = True
|
||||
grid.at(x, y).transparent = True
|
||||
grid.at(x, y).color = FLOOR_COLOR
|
||||
|
||||
# Create rooms and corridors
|
||||
rooms = [
|
||||
(2, 2, 8, 6), # Top-left room
|
||||
(20, 2, 8, 6), # Top-right room
|
||||
(11, 8, 8, 6), # Center room
|
||||
(2, 14, 8, 5), # Bottom-left room
|
||||
(20, 14, 8, 5), # Bottom-right room
|
||||
]
|
||||
|
||||
# Create room walls
|
||||
for rx, ry, rw, rh in rooms:
|
||||
# Top and bottom walls
|
||||
for x in range(rx, rx + rw):
|
||||
if 0 <= x < 30:
|
||||
grid.at(x, ry).walkable = False
|
||||
grid.at(x, ry).color = WALL_COLOR
|
||||
grid.at(x, ry + rh - 1).walkable = False
|
||||
grid.at(x, ry + rh - 1).color = WALL_COLOR
|
||||
|
||||
# Left and right walls
|
||||
for y in range(ry, ry + rh):
|
||||
if 0 <= y < 20:
|
||||
grid.at(rx, y).walkable = False
|
||||
grid.at(rx, y).color = WALL_COLOR
|
||||
grid.at(rx + rw - 1, y).walkable = False
|
||||
grid.at(rx + rw - 1, y).color = WALL_COLOR
|
||||
|
||||
# Create doorways
|
||||
doorways = [
|
||||
(6, 2), (24, 2), # Top room doors
|
||||
(6, 7), (24, 7), # Top room doors bottom
|
||||
(15, 8), (15, 13), # Center room doors
|
||||
(6, 14), (24, 14), # Bottom room doors
|
||||
(11, 11), (18, 11), # Center room side doors
|
||||
]
|
||||
|
||||
for x, y in doorways:
|
||||
if 0 <= x < 30 and 0 <= y < 20:
|
||||
grid.at(x, y).walkable = True
|
||||
grid.at(x, y).color = FLOOR_COLOR
|
||||
|
||||
# Add some corridors
|
||||
# Horizontal corridors
|
||||
for x in range(10, 20):
|
||||
grid.at(x, 5).walkable = True
|
||||
grid.at(x, 5).color = FLOOR_COLOR
|
||||
grid.at(x, 16).walkable = True
|
||||
grid.at(x, 16).color = FLOOR_COLOR
|
||||
|
||||
# Vertical corridors
|
||||
for y in range(5, 17):
|
||||
grid.at(10, y).walkable = True
|
||||
grid.at(10, y).color = FLOOR_COLOR
|
||||
grid.at(19, y).walkable = True
|
||||
grid.at(19, y).color = FLOOR_COLOR
|
||||
|
||||
def spawn_entities():
|
||||
"""Spawn various entity types"""
|
||||
global player, enemies, treasures, patrol_entities
|
||||
|
||||
# Clear existing entities
|
||||
grid.entities.clear()
|
||||
enemies = []
|
||||
treasures = []
|
||||
patrol_entities = []
|
||||
|
||||
# Spawn player in center room
|
||||
player = mcrfpy.Entity(15, 11)
|
||||
player.sprite_index = PLAYER
|
||||
grid.entities.append(player)
|
||||
|
||||
# Spawn enemies in corners
|
||||
enemy_positions = [(4, 4), (24, 4), (4, 16), (24, 16)]
|
||||
for x, y in enemy_positions:
|
||||
enemy = mcrfpy.Entity(x, y)
|
||||
enemy.sprite_index = ENEMY
|
||||
grid.entities.append(enemy)
|
||||
enemies.append(enemy)
|
||||
|
||||
# Spawn treasures
|
||||
treasure_positions = [(6, 5), (24, 5), (15, 10)]
|
||||
for x, y in treasure_positions:
|
||||
treasure = mcrfpy.Entity(x, y)
|
||||
treasure.sprite_index = TREASURE
|
||||
grid.entities.append(treasure)
|
||||
treasures.append(treasure)
|
||||
|
||||
# Spawn patrol entities
|
||||
patrol = mcrfpy.Entity(10, 10)
|
||||
patrol.sprite_index = PATROL
|
||||
patrol.waypoints = [(10, 10), (19, 10), (19, 16), (10, 16)] # Square patrol
|
||||
patrol.waypoint_index = 0
|
||||
grid.entities.append(patrol)
|
||||
patrol_entities.append(patrol)
|
||||
|
||||
def visualize_dijkstra(target_x, target_y):
|
||||
"""Visualize Dijkstra distance field"""
|
||||
if not show_dijkstra:
|
||||
return
|
||||
|
||||
# Compute Dijkstra from target
|
||||
grid.compute_dijkstra(target_x, target_y)
|
||||
|
||||
# Color tiles based on distance
|
||||
max_dist = 30.0
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
if grid.at(x, y).walkable:
|
||||
dist = grid.get_dijkstra_distance(x, y)
|
||||
if dist is not None and dist < max_dist:
|
||||
# Map distance to color index
|
||||
color_idx = int((dist / max_dist) * len(DIJKSTRA_COLORS))
|
||||
color_idx = min(color_idx, len(DIJKSTRA_COLORS) - 1)
|
||||
grid.at(x, y).color = DIJKSTRA_COLORS[color_idx]
|
||||
|
||||
def move_enemies(dt):
|
||||
"""Move enemies based on current mode"""
|
||||
if mode == "CHASE":
|
||||
# Enemies chase player
|
||||
for enemy in enemies:
|
||||
path = enemy.path_to(int(player.x), int(player.y))
|
||||
if path and len(path) > 1: # Don't move onto player
|
||||
# Move towards player
|
||||
next_x, next_y = path[1]
|
||||
# Smooth movement
|
||||
dx = next_x - enemy.x
|
||||
dy = next_y - enemy.y
|
||||
enemy.x += dx * dt * animation_speed
|
||||
enemy.y += dy * dt * animation_speed
|
||||
|
||||
elif mode == "FLEE":
|
||||
# Enemies flee from player
|
||||
for enemy in enemies:
|
||||
# Compute opposite direction
|
||||
dx = enemy.x - player.x
|
||||
dy = enemy.y - player.y
|
||||
|
||||
# Find safe spot in that direction
|
||||
target_x = int(enemy.x + dx * 2)
|
||||
target_y = int(enemy.y + dy * 2)
|
||||
|
||||
# Clamp to grid
|
||||
target_x = max(0, min(29, target_x))
|
||||
target_y = max(0, min(19, target_y))
|
||||
|
||||
path = enemy.path_to(target_x, target_y)
|
||||
if path and len(path) > 0:
|
||||
next_x, next_y = path[0]
|
||||
# Move away from player
|
||||
dx = next_x - enemy.x
|
||||
dy = next_y - enemy.y
|
||||
enemy.x += dx * dt * animation_speed
|
||||
enemy.y += dy * dt * animation_speed
|
||||
|
||||
def move_patrols(dt):
|
||||
"""Move patrol entities along waypoints"""
|
||||
for patrol in patrol_entities:
|
||||
if not hasattr(patrol, 'waypoints'):
|
||||
continue
|
||||
|
||||
# Get current waypoint
|
||||
target_x, target_y = patrol.waypoints[patrol.waypoint_index]
|
||||
|
||||
# Check if reached waypoint
|
||||
dist = abs(patrol.x - target_x) + abs(patrol.y - target_y)
|
||||
if dist < 0.5:
|
||||
# Move to next waypoint
|
||||
patrol.waypoint_index = (patrol.waypoint_index + 1) % len(patrol.waypoints)
|
||||
target_x, target_y = patrol.waypoints[patrol.waypoint_index]
|
||||
|
||||
# Path to waypoint
|
||||
path = patrol.path_to(target_x, target_y)
|
||||
if path and len(path) > 0:
|
||||
next_x, next_y = path[0]
|
||||
dx = next_x - patrol.x
|
||||
dy = next_y - patrol.y
|
||||
patrol.x += dx * dt * animation_speed * 0.5 # Slower patrol speed
|
||||
patrol.y += dy * dt * animation_speed * 0.5
|
||||
|
||||
def update_entities(dt):
|
||||
"""Update all entity movements"""
|
||||
move_enemies(dt / 1000.0) # Convert to seconds
|
||||
move_patrols(dt / 1000.0)
|
||||
|
||||
# Update Dijkstra visualization
|
||||
if show_dijkstra and player:
|
||||
visualize_dijkstra(int(player.x), int(player.y))
|
||||
|
||||
def handle_keypress(scene_name, keycode):
|
||||
"""Handle keyboard input"""
|
||||
global mode, show_dijkstra, player
|
||||
|
||||
# Mode switching
|
||||
if keycode == 49: # '1'
|
||||
mode = "CHASE"
|
||||
mode_text.text = "Mode: CHASE - Enemies pursue player"
|
||||
clear_colors()
|
||||
elif keycode == 50: # '2'
|
||||
mode = "FLEE"
|
||||
mode_text.text = "Mode: FLEE - Enemies avoid player"
|
||||
clear_colors()
|
||||
elif keycode == 51: # '3'
|
||||
mode = "PATROL"
|
||||
mode_text.text = "Mode: PATROL - Entities follow waypoints"
|
||||
clear_colors()
|
||||
|
||||
# Toggle Dijkstra visualization
|
||||
elif keycode == 68 or keycode == 100: # 'D' or 'd'
|
||||
show_dijkstra = not show_dijkstra
|
||||
debug_text.text = f"Dijkstra Debug: {'ON' if show_dijkstra else 'OFF'}"
|
||||
if not show_dijkstra:
|
||||
clear_colors()
|
||||
|
||||
# Move player with arrow keys or WASD
|
||||
elif keycode in [87, 119]: # W/w - Up
|
||||
if player.y > 0:
|
||||
path = player.path_to(int(player.x), int(player.y) - 1)
|
||||
if path:
|
||||
player.y -= 1
|
||||
elif keycode in [83, 115]: # S/s - Down
|
||||
if player.y < 19:
|
||||
path = player.path_to(int(player.x), int(player.y) + 1)
|
||||
if path:
|
||||
player.y += 1
|
||||
elif keycode in [65, 97]: # A/a - Left
|
||||
if player.x > 0:
|
||||
path = player.path_to(int(player.x) - 1, int(player.y))
|
||||
if path:
|
||||
player.x -= 1
|
||||
elif keycode in [68, 100]: # D/d - Right
|
||||
if player.x < 29:
|
||||
path = player.path_to(int(player.x) + 1, int(player.y))
|
||||
if path:
|
||||
player.x += 1
|
||||
|
||||
# Reset
|
||||
elif keycode == 82 or keycode == 114: # 'R' or 'r'
|
||||
spawn_entities()
|
||||
clear_colors()
|
||||
|
||||
# Quit
|
||||
elif keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
|
||||
print("\nExiting pathfinding showcase...")
|
||||
sys.exit(0)
|
||||
|
||||
def clear_colors():
|
||||
"""Reset floor colors"""
|
||||
for y in range(20):
|
||||
for x in range(30):
|
||||
if grid.at(x, y).walkable:
|
||||
grid.at(x, y).color = FLOOR_COLOR
|
||||
|
||||
# Create the showcase
|
||||
print("Pathfinding Showcase Demo")
|
||||
print("=========================")
|
||||
print("Controls:")
|
||||
print(" WASD - Move player")
|
||||
print(" 1 - Chase mode (enemies pursue)")
|
||||
print(" 2 - Flee mode (enemies avoid)")
|
||||
print(" 3 - Patrol mode")
|
||||
print(" D - Toggle Dijkstra visualization")
|
||||
print(" R - Reset entities")
|
||||
print(" Q/ESC - Quit")
|
||||
|
||||
# Create dungeon
|
||||
create_dungeon()
|
||||
spawn_entities()
|
||||
|
||||
# Set up UI
|
||||
ui = mcrfpy.sceneUI("pathfinding_showcase")
|
||||
ui.append(grid)
|
||||
|
||||
# Scale and position
|
||||
grid.size = (750, 500) # 30*25, 20*25
|
||||
grid.position = (25, 60)
|
||||
|
||||
# Add title
|
||||
title = mcrfpy.Caption("Pathfinding Showcase", 300, 10)
|
||||
title.fill_color = mcrfpy.Color(255, 255, 255)
|
||||
ui.append(title)
|
||||
|
||||
# Add mode text
|
||||
mode_text = mcrfpy.Caption("Mode: CHASE - Enemies pursue player", 25, 580)
|
||||
mode_text.fill_color = mcrfpy.Color(255, 255, 200)
|
||||
ui.append(mode_text)
|
||||
|
||||
# Add debug text
|
||||
debug_text = mcrfpy.Caption("Dijkstra Debug: OFF", 25, 600)
|
||||
debug_text.fill_color = mcrfpy.Color(200, 200, 255)
|
||||
ui.append(debug_text)
|
||||
|
||||
# Add legend
|
||||
legend = mcrfpy.Caption("@ Player E Enemy $ Treasure P Patrol", 25, 620)
|
||||
legend.fill_color = mcrfpy.Color(150, 150, 150)
|
||||
ui.append(legend)
|
||||
|
||||
# Set up input handling
|
||||
mcrfpy.keypressScene(handle_keypress)
|
||||
|
||||
# Set up animation timer
|
||||
mcrfpy.setTimer("entities", update_entities, 16) # 60 FPS
|
||||
|
||||
# Show scene
|
||||
mcrfpy.setScene("pathfinding_showcase")
|
||||
|
||||
print("\nShowcase ready! Move with WASD and watch entities react.")
|
|
@ -1,46 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Simple interactive visibility test"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
# Create scene and grid
|
||||
print("Creating scene...")
|
||||
mcrfpy.createScene("vis_test")
|
||||
|
||||
print("Creating grid...")
|
||||
grid = mcrfpy.Grid(grid_x=10, grid_y=10)
|
||||
|
||||
# Initialize grid
|
||||
print("Initializing grid...")
|
||||
for y in range(10):
|
||||
for x in range(10):
|
||||
cell = grid.at(x, y)
|
||||
cell.walkable = True
|
||||
cell.transparent = True
|
||||
cell.color = mcrfpy.Color(100, 100, 120)
|
||||
|
||||
# Create entity
|
||||
print("Creating entity...")
|
||||
entity = mcrfpy.Entity(5, 5, grid=grid)
|
||||
entity.sprite_index = 64
|
||||
|
||||
print("Updating visibility...")
|
||||
entity.update_visibility()
|
||||
|
||||
# Set up UI
|
||||
print("Setting up UI...")
|
||||
ui = mcrfpy.sceneUI("vis_test")
|
||||
ui.append(grid)
|
||||
grid.position = (50, 50)
|
||||
grid.size = (300, 300)
|
||||
|
||||
# Test perspective
|
||||
print("Testing perspective...")
|
||||
grid.perspective = -1 # Omniscient
|
||||
print(f"Perspective set to: {grid.perspective}")
|
||||
|
||||
print("Setting scene...")
|
||||
mcrfpy.setScene("vis_test")
|
||||
|
||||
print("Ready!")
|
|
@ -1,39 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Simple visibility test without entity append"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
print("Simple visibility test...")
|
||||
|
||||
# Create scene and grid
|
||||
mcrfpy.createScene("simple")
|
||||
print("Scene created")
|
||||
|
||||
grid = mcrfpy.Grid(grid_x=5, grid_y=5)
|
||||
print("Grid created")
|
||||
|
||||
# Create entity without appending
|
||||
entity = mcrfpy.Entity(2, 2, grid=grid)
|
||||
print(f"Entity created at ({entity.x}, {entity.y})")
|
||||
|
||||
# Check if gridstate is initialized
|
||||
print(f"Gridstate length: {len(entity.gridstate)}")
|
||||
|
||||
# Try to access at method
|
||||
try:
|
||||
state = entity.at(0, 0)
|
||||
print(f"at(0,0) returned: {state}")
|
||||
print(f"visible: {state.visible}, discovered: {state.discovered}")
|
||||
except Exception as e:
|
||||
print(f"Error in at(): {e}")
|
||||
|
||||
# Try update_visibility
|
||||
try:
|
||||
entity.update_visibility()
|
||||
print("update_visibility() succeeded")
|
||||
except Exception as e:
|
||||
print(f"Error in update_visibility(): {e}")
|
||||
|
||||
print("Test complete")
|
||||
sys.exit(0)
|
|
@ -1,60 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test pathfinding integration with demos"""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
print("Testing pathfinding integration...")
|
||||
print("=" * 50)
|
||||
|
||||
# Create scene and grid
|
||||
mcrfpy.createScene("test")
|
||||
grid = mcrfpy.Grid(grid_x=10, grid_y=10)
|
||||
|
||||
# Initialize grid
|
||||
for y in range(10):
|
||||
for x in range(10):
|
||||
grid.at(x, y).walkable = True
|
||||
|
||||
# Add some walls
|
||||
for i in range(5):
|
||||
grid.at(5, i + 2).walkable = False
|
||||
|
||||
# Create entities
|
||||
e1 = mcrfpy.Entity(2, 5)
|
||||
e2 = mcrfpy.Entity(8, 5)
|
||||
grid.entities.append(e1)
|
||||
grid.entities.append(e2)
|
||||
|
||||
# Test pathfinding between entities
|
||||
print(f"Entity 1 at ({e1.x}, {e1.y})")
|
||||
print(f"Entity 2 at ({e2.x}, {e2.y})")
|
||||
|
||||
# Entity 1 finds path to Entity 2
|
||||
path = e1.path_to(int(e2.x), int(e2.y))
|
||||
print(f"\nPath from E1 to E2: {path}")
|
||||
print(f"Path length: {len(path)} steps")
|
||||
|
||||
# Test movement simulation
|
||||
if path and len(path) > 1:
|
||||
print("\nSimulating movement along path:")
|
||||
for i, (x, y) in enumerate(path[:5]): # Show first 5 steps
|
||||
print(f" Step {i}: Move to ({x}, {y})")
|
||||
|
||||
# Test path in reverse
|
||||
path_reverse = e2.path_to(int(e1.x), int(e1.y))
|
||||
print(f"\nPath from E2 to E1: {path_reverse}")
|
||||
print(f"Reverse path length: {len(path_reverse)} steps")
|
||||
|
||||
print("\n✓ Pathfinding integration working correctly!")
|
||||
print("Enhanced demos are ready for interactive use.")
|
||||
|
||||
# Quick animation test
|
||||
def test_timer(dt):
|
||||
print(f"Timer callback received: dt={dt}ms")
|
||||
sys.exit(0)
|
||||
|
||||
# Set a quick timer to test animation system
|
||||
mcrfpy.setTimer("test", test_timer, 100)
|
||||
|
||||
print("\nTesting timer system for animations...")
|
Loading…
Reference in New Issue