Compare commits
No commits in common. "192d1ae1dd1445c1795637cc0d3adea88b9cdc2b" and "93256b96c6551c63dd575f3cd9e888fccf654b57" have entirely different histories.
192d1ae1dd
...
93256b96c6
304
ROADMAP.md
|
@ -1,136 +1,16 @@
|
|||
# McRogueFace - Development Roadmap
|
||||
|
||||
## 🚨 URGENT PRIORITIES - July 9, 2025 🚨
|
||||
|
||||
### IMMEDIATE ACTION REQUIRED (Next 48 Hours)
|
||||
|
||||
**CRITICAL DEADLINE**: RoguelikeDev Tutorial Event starts July 15 - Need to advertise by July 11!
|
||||
|
||||
#### 1. Tutorial Emergency Plan (2 DAYS)
|
||||
- [ ] **Day 1 (July 9)**: Parts 1-2 (Setup, Moving @, Drawing Map, Entities)
|
||||
- [ ] **Day 2 (July 10)**: Parts 3-4 (FOV, Combat/AI)
|
||||
- [ ] **July 11**: Announce on r/roguelikedev with 4 completed parts
|
||||
- [ ] **July 12-14**: Complete remaining 10 parts before event starts
|
||||
|
||||
#### 1b. Sizzle Reel Demo (URGENT)
|
||||
- [ ] **Expand animation_sizzle_reel_working.py** with Grid/Entity demos:
|
||||
- Grid scrolling and zooming animations
|
||||
- Entity movement patterns (patrol, chase, flee)
|
||||
- Particle effects using entity spawning
|
||||
- Tile animation demonstrations
|
||||
- Color cycling and transparency effects
|
||||
- Mass entity choreography (100+ entities)
|
||||
- Performance stress test with 1000+ entities
|
||||
|
||||
#### 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)
|
||||
- [ ] **Add dirty flag system** to UIGrid (1 hour)
|
||||
- [ ] **Batch update context managers** (2 hours)
|
||||
- [ ] **Memory pool for entities** (2 hours)
|
||||
|
||||
#### 4. Bug Fixing Pipeline
|
||||
- [ ] Set up GitHub Issues automation
|
||||
- [ ] Create test for each bug before fixing
|
||||
- [ ] Track: Memory leaks, Segfaults, Python/C++ boundary errors
|
||||
|
||||
---
|
||||
|
||||
## 🎯 STRATEGIC ARCHITECTURE VISION
|
||||
|
||||
### Three-Layer Grid Architecture (From Compass Research)
|
||||
Following successful roguelike patterns (Caves of Qud, Cogmind, DCSS):
|
||||
|
||||
1. **Visual Layer** (UIGridPoint) - Sprites, colors, animations
|
||||
2. **World State Layer** (TCODMap) - Walkability, transparency, physics
|
||||
3. **Entity Perspective Layer** (UIGridPointState) - Per-entity FOV, knowledge
|
||||
|
||||
### Performance Architecture (Critical for 1000x1000 maps)
|
||||
- **Spatial Hashing** for entity queries (not quadtrees!)
|
||||
- **Batch Operations** with context managers (10-100x speedup)
|
||||
- **Memory Pooling** for entities and components
|
||||
- **Dirty Flag System** to avoid unnecessary updates
|
||||
- **Zero-Copy NumPy Integration** via buffer protocol
|
||||
|
||||
### Key Insight from Research
|
||||
"Minimizing Python/C++ boundary crossings matters more than individual function complexity"
|
||||
- Batch everything possible
|
||||
- Use context managers for logical operations
|
||||
- Expose arrays, not individual cells
|
||||
- Profile and optimize hot paths only
|
||||
|
||||
---
|
||||
|
||||
## Project Status: 🎉 ALPHA 0.1 RELEASE! 🎉
|
||||
|
||||
**Current State**: Documentation system complete, TCOD integration urgent
|
||||
**Latest Update**: Completed Phase 7 documentation infrastructure (2025-07-08)
|
||||
**Branch**: alpha_streamline_2
|
||||
**Open Issues**: ~46 remaining + URGENT TCOD/Tutorial work
|
||||
|
||||
---
|
||||
|
||||
## 📋 TCOD Integration Implementation Details
|
||||
|
||||
### Phase 1: Core UIGrid Integration (Day 1 Morning)
|
||||
```cpp
|
||||
// UIGrid.h additions
|
||||
class UIGrid : public UIDrawable {
|
||||
private:
|
||||
TCODMap* world_state; // Add TCOD map
|
||||
std::unordered_map<int, UIGridPointState*> entity_perspectives;
|
||||
bool batch_mode = false;
|
||||
std::vector<CellUpdate> pending_updates;
|
||||
```
|
||||
|
||||
### Phase 2: Python Bindings (Day 1 Afternoon)
|
||||
```python
|
||||
# New API surface
|
||||
grid = mcrfpy.Grid(100, 100)
|
||||
grid.compute_fov(player.x, player.y, radius=10) # Returns visible cells
|
||||
grid.at((x, y)).walkable = False # Ergonomic access
|
||||
with grid.batch_update(): # Context manager for performance
|
||||
# All updates batched
|
||||
```
|
||||
|
||||
### Phase 3: Entity Integration (Day 2 Morning)
|
||||
```python
|
||||
# UIEntity additions
|
||||
entity.path_to(target_x, target_y) # A* pathfinding
|
||||
entity.flee_from(threat) # Dijkstra map
|
||||
entity.can_see(other_entity) # FOV check
|
||||
```
|
||||
|
||||
### Critical Success Factors:
|
||||
1. **Batch everything** - Never update single cells in loops
|
||||
2. **Lazy evaluation** - Only compute FOV for entities that need it
|
||||
3. **Sparse storage** - Don't store full grids per entity
|
||||
4. **Profile early** - Find the 20% of code taking 80% of time
|
||||
**Current State**: Alpha release achieved! All critical blockers resolved!
|
||||
**Latest Update**: Moved RenderTexture (#6) to Beta - Alpha is READY! (2025-07-05)
|
||||
**Branch**: interpreter_mode (ready for alpha release merge)
|
||||
**Open Issues**: ~46 remaining (non-blocking quality-of-life improvements)
|
||||
|
||||
---
|
||||
|
||||
## Recent Achievements
|
||||
|
||||
### 2025-07-08: PyArgHelpers Infrastructure Complete! 🔧
|
||||
**Standardized Python API Argument Parsing**
|
||||
- Unified position handling: (x, y) tuples or separate x, y args
|
||||
- Consistent size parsing: (w, h) tuples or width, height args
|
||||
- Grid-specific helpers for tile-based positioning
|
||||
- Proper conflict detection between positional and keyword args
|
||||
- All UI components migrated: Frame, Caption, Sprite, Grid, Entity
|
||||
- Improved error messages: "Value must be a number (int or float)"
|
||||
- Foundation for Phase 7 documentation efforts
|
||||
|
||||
### 2025-07-05: ALPHA 0.1 ACHIEVED! 🎊🍾
|
||||
**All Alpha Blockers Resolved!**
|
||||
- Z-order rendering with performance optimization (Issue #63)
|
||||
|
@ -181,18 +61,17 @@ entity.can_see(other_entity) # FOV check
|
|||
- OOP Scene support with lifecycle methods (#61)
|
||||
- Window resize events (#1)
|
||||
- Scene transitions with animations (#105)
|
||||
- ✅ **Phase 6 Complete** - Rendering Revolution achieved!
|
||||
- 🚧 **Phase 6 Started** - Rendering Revolution in progress!
|
||||
- Grid background colors (#50) ✅
|
||||
- RenderTexture overhaul (#6) ✅
|
||||
- RenderTexture base infrastructure ✅
|
||||
- UIFrame clipping support ✅
|
||||
- Viewport-based rendering (#8) ✅
|
||||
|
||||
### Active Development:
|
||||
- **Branch**: alpha_streamline_2
|
||||
- **Current Phase**: Phase 7 - Documentation & Distribution
|
||||
- **Achievement**: PyArgHelpers infrastructure complete - standardized Python API
|
||||
- **Current Phase**: Phase 6 - Rendering Revolution (IN PROGRESS)
|
||||
- **Timeline**: 3-4 weeks for Phase 6 implementation
|
||||
- **Strategic Vision**: See STRATEGIC_VISION.md for platform roadmap
|
||||
- **Latest**: All UI components now use consistent argument parsing patterns!
|
||||
- **Latest**: RenderTexture base infrastructure complete, UIFrame clipping working!
|
||||
|
||||
### 🏗️ Architectural Dependencies Map
|
||||
|
||||
|
@ -349,7 +228,7 @@ Rendering Layer:
|
|||
```
|
||||
*Result*: Entire window/scene system modernized with OOP design!
|
||||
|
||||
### Phase 6: Rendering Revolution (3-4 weeks) ✅ COMPLETE!
|
||||
### Phase 6: Rendering Revolution (3-4 weeks) 🚧 IN PROGRESS!
|
||||
**Goal**: Professional rendering capabilities
|
||||
```
|
||||
1. ✅ #50 - Grid background colors [COMPLETED]
|
||||
|
@ -357,52 +236,49 @@ Rendering Layer:
|
|||
- Added background_color property with animation support
|
||||
- Default dark gray background (8, 8, 8, 255)
|
||||
|
||||
2. ✅ #6 - RenderTexture overhaul [COMPLETED]
|
||||
2. 🚧 #6 - RenderTexture overhaul [PARTIALLY COMPLETE]
|
||||
✅ Base infrastructure in UIDrawable
|
||||
✅ UIFrame clip_children property
|
||||
✅ Dirty flag optimization system
|
||||
✅ Nested clipping support
|
||||
✅ UIGrid already has appropriate RenderTexture implementation
|
||||
❌ UICaption/UISprite clipping not needed (no children)
|
||||
⏳ Extend to other UI classes
|
||||
⏳ Effects (blur, glow, etc.)
|
||||
|
||||
3. ✅ #8 - Viewport-based rendering [COMPLETED]
|
||||
- Fixed game resolution (window.game_resolution)
|
||||
- Three scaling modes: "center", "stretch", "fit"
|
||||
- Window to game coordinate transformation
|
||||
- Mouse input properly scaled with windowToGameCoords()
|
||||
- Python API fully integrated
|
||||
- Tests: test_viewport_simple.py, test_viewport_visual.py, test_viewport_scaling.py
|
||||
3. #8 - Viewport-based rendering [NEXT PRIORITY]
|
||||
- RenderTexture matches viewport
|
||||
- Proper scaling/letterboxing
|
||||
- Coordinate system transformations
|
||||
|
||||
4. #106 - Shader support [DEFERRED TO POST-PHASE 7]
|
||||
4. #106 - Shader support [STRETCH GOAL]
|
||||
sprite.shader = mcrfpy.Shader.load("glow.frag")
|
||||
frame.shader_params = {"intensity": 0.5}
|
||||
|
||||
5. #107 - Particle system [DEFERRED TO POST-PHASE 7]
|
||||
5. #107 - Particle system [STRETCH GOAL]
|
||||
emitter = mcrfpy.ParticleEmitter()
|
||||
emitter.texture = spark_texture
|
||||
emitter.emission_rate = 100
|
||||
emitter.lifetime = (0.5, 2.0)
|
||||
```
|
||||
|
||||
**Phase 6 Achievement Summary**:
|
||||
- Grid backgrounds (#50) ✅ - Customizable background colors with animation
|
||||
- RenderTexture overhaul (#6) ✅ - UIFrame clipping with opt-in architecture
|
||||
- Viewport rendering (#8) ✅ - Three scaling modes with coordinate transformation
|
||||
- UIGrid already had optimal RenderTexture implementation for its use case
|
||||
- UICaption/UISprite clipping unnecessary (no children to clip)
|
||||
- Performance optimized with dirty flag system
|
||||
- Backward compatibility preserved throughout
|
||||
- Effects/Shader/Particle systems deferred for focused delivery
|
||||
**Phase 6 Technical Notes**:
|
||||
- RenderTexture is the foundation - everything else depends on it
|
||||
- Grid backgrounds (#50) ✅ completed as warm-up task
|
||||
- RenderTexture implementation uses opt-in architecture to preserve backward compatibility
|
||||
- Dirty flag system crucial for performance - only re-render when properties change
|
||||
- Nested clipping works correctly with proper coordinate transformations
|
||||
- Scene transitions already use RenderTextures - good integration test
|
||||
- Next: Viewport rendering (#8) will build on RenderTexture foundation
|
||||
- Shader/Particle systems might be deferred to Phase 7 or Gamma
|
||||
|
||||
*Rationale*: This unlocks professional visual effects but is complex.
|
||||
|
||||
### Phase 7: Documentation & Distribution (1-2 weeks)
|
||||
**Goal**: Ready for the world
|
||||
```
|
||||
1. ✅ #85 - Replace all "docstring" placeholders [COMPLETED 2025-07-08]
|
||||
2. ✅ #86 - Add parameter documentation [COMPLETED 2025-07-08]
|
||||
3. ✅ #108 - Generate .pyi type stubs for IDE support [COMPLETED 2025-07-08]
|
||||
4. ❌ #70 - PyPI wheel preparation [CANCELLED - Architectural mismatch]
|
||||
1. #85 - Replace all "docstring" placeholders
|
||||
2. #86 - Add parameter documentation
|
||||
3. #108 - Generate .pyi type stubs for IDE support
|
||||
4. #70 - PyPI wheel preparation
|
||||
5. API reference generator tool
|
||||
```
|
||||
|
||||
|
@ -645,118 +521,4 @@ Rendering Layer:
|
|||
|
||||
---
|
||||
|
||||
## 🔮 FUTURE VISION: Pure Python Extension Architecture
|
||||
|
||||
### Concept: McRogueFace as a Traditional Python Package
|
||||
**Status**: Unscheduled - Long-term vision
|
||||
**Complexity**: Major architectural overhaul
|
||||
|
||||
Instead of being a C++ application that embeds Python, McRogueFace could be redesigned as a pure Python extension module that can be installed via `pip install mcrogueface`.
|
||||
|
||||
### Technical Approach
|
||||
1. **Separate Core Engine from Python Embedding**
|
||||
- Extract SFML rendering, audio, and input into C++ extension modules
|
||||
- Remove embedded CPython interpreter
|
||||
- Use Python's C API to expose functionality
|
||||
|
||||
2. **Module Structure**
|
||||
```
|
||||
mcrfpy/
|
||||
├── __init__.py # Pure Python coordinator
|
||||
├── _core.so # C++ rendering/game loop extension
|
||||
├── _sfml.so # SFML bindings
|
||||
├── _audio.so # Audio system bindings
|
||||
└── engine.py # Python game engine logic
|
||||
```
|
||||
|
||||
3. **Inverted Control Flow**
|
||||
- Python drives the main loop instead of C++
|
||||
- C++ extensions handle performance-critical operations
|
||||
- Python manages game logic, scenes, and entity systems
|
||||
|
||||
### Benefits
|
||||
- **Standard Python Packaging**: `pip install mcrogueface`
|
||||
- **Virtual Environment Support**: Works with venv, conda, poetry
|
||||
- **Better IDE Integration**: Standard Python development workflow
|
||||
- **Easier Testing**: Use pytest, standard Python testing tools
|
||||
- **Cross-Python Compatibility**: Support multiple Python versions
|
||||
- **Modular Architecture**: Users can import only what they need
|
||||
|
||||
### Challenges
|
||||
- **Major Refactoring**: Complete restructure of codebase
|
||||
- **Performance Considerations**: Python-driven main loop overhead
|
||||
- **Build Complexity**: Multiple extension modules to compile
|
||||
- **Platform Support**: Need wheels for many platform/Python combinations
|
||||
- **API Stability**: Would need careful design to maintain compatibility
|
||||
|
||||
### Implementation Phases (If Pursued)
|
||||
1. **Proof of Concept**: Simple SFML binding as Python extension
|
||||
2. **Core Extraction**: Separate rendering from Python embedding
|
||||
3. **Module Design**: Define clean API boundaries
|
||||
4. **Incremental Migration**: Move systems one at a time
|
||||
5. **Compatibility Layer**: Support existing games during transition
|
||||
|
||||
### Example Usage (Future Vision)
|
||||
```python
|
||||
import mcrfpy
|
||||
from mcrfpy import Scene, Frame, Sprite, Grid
|
||||
|
||||
# Create game directly in Python
|
||||
game = mcrfpy.Game(width=1024, height=768)
|
||||
|
||||
# Define scenes using Python classes
|
||||
class MainMenu(Scene):
|
||||
def on_enter(self):
|
||||
self.ui.append(Frame(100, 100, 200, 50))
|
||||
self.ui.append(Sprite("logo.png", x=400, y=100))
|
||||
|
||||
def on_keypress(self, key, pressed):
|
||||
if key == "ENTER" and pressed:
|
||||
self.game.set_scene("game")
|
||||
|
||||
# Run the game
|
||||
game.add_scene("menu", MainMenu())
|
||||
game.run()
|
||||
```
|
||||
|
||||
This architecture would make McRogueFace a first-class Python citizen, following standard Python packaging conventions while maintaining high performance through C++ extensions.
|
||||
|
||||
---
|
||||
|
||||
## 🚀 IMMEDIATE NEXT STEPS (Priority Order)
|
||||
|
||||
### Today (July 9) - EXECUTE NOW:
|
||||
1. **Start Tutorial Part 1** - Basic setup and @ movement (2 hours)
|
||||
2. **Implement UIGrid.at((x,y))** - CellView pattern (1 hour)
|
||||
3. **Create Grid demo** for sizzle reel (1 hour)
|
||||
4. **Fix any blocking bugs** discovered during tutorial writing
|
||||
|
||||
### Tomorrow (July 10) - CRITICAL PATH:
|
||||
1. **Tutorial Parts 2-4** - Map drawing, entities, FOV, combat
|
||||
2. **Implement compute_fov()** in UIGrid
|
||||
3. **Add batch_update context manager**
|
||||
4. **Expand sizzle reel** with entity choreography
|
||||
|
||||
### July 11 - ANNOUNCEMENT DAY:
|
||||
1. **Polish 4 tutorial parts**
|
||||
2. **Create announcement post** for r/roguelikedev
|
||||
3. **Record sizzle reel video**
|
||||
4. **Submit announcement** by end of day
|
||||
|
||||
### Architecture Decision Log:
|
||||
- **DECIDED**: Use three-layer architecture (visual/world/perspective)
|
||||
- **DECIDED**: Spatial hashing over quadtrees for entities
|
||||
- **DECIDED**: Batch operations are mandatory, not optional
|
||||
- **DECIDED**: TCOD integration as mcrfpy.libtcod submodule
|
||||
- **DECIDED**: Tutorial must showcase McRogueFace strengths, not mimic TCOD
|
||||
|
||||
### Risk Mitigation:
|
||||
- **If TCOD integration delays**: Use pure Python FOV for tutorial
|
||||
- **If performance issues**: Focus on <100x100 maps for demos
|
||||
- **If tutorial incomplete**: Ship with 4 solid parts + roadmap
|
||||
- **If bugs block progress**: Document as "known issues" and continue
|
||||
|
||||
---
|
||||
|
||||
*Last Updated: 2025-07-09 (URGENT SPRINT MODE)*
|
||||
*Next Review: July 11 after announcement*
|
||||
*Last Updated: 2025-07-05*
|
||||
|
|
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 39 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 31 KiB |
|
@ -1,80 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Compare the original and improved HTML documentation."""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
def compare_docs():
|
||||
"""Show key differences between the two HTML versions."""
|
||||
|
||||
print("HTML Documentation Improvements")
|
||||
print("=" * 50)
|
||||
|
||||
# Read both files
|
||||
original = Path("docs/api_reference.html")
|
||||
improved = Path("docs/api_reference_improved.html")
|
||||
|
||||
if not original.exists() or not improved.exists():
|
||||
print("Error: Documentation files not found")
|
||||
return
|
||||
|
||||
with open(original, 'r') as f:
|
||||
orig_content = f.read()
|
||||
|
||||
with open(improved, 'r') as f:
|
||||
imp_content = f.read()
|
||||
|
||||
print("\n📊 File Size Comparison:")
|
||||
print(f" Original: {len(orig_content):,} bytes")
|
||||
print(f" Improved: {len(imp_content):,} bytes")
|
||||
|
||||
print("\n✅ Key Improvements:")
|
||||
|
||||
# Check newline handling
|
||||
if '\\n' in orig_content and '\\n' not in imp_content:
|
||||
print(" • Fixed literal \\n in documentation text")
|
||||
|
||||
# Check table of contents
|
||||
if '[Classes](#classes)' in orig_content and '<a href="#classes">Classes</a>' in imp_content:
|
||||
print(" • Converted markdown links to proper HTML anchors")
|
||||
|
||||
# Check headings
|
||||
if '<h4>Args:</h4>' not in imp_content and '<strong>Arguments:</strong>' in imp_content:
|
||||
print(" • Fixed Args/Attributes formatting (no longer H4 headings)")
|
||||
|
||||
# Check method descriptions
|
||||
orig_count = orig_content.count('`Get bounding box')
|
||||
imp_count = imp_content.count('get_bounds(...)')
|
||||
if orig_count > imp_count:
|
||||
print(f" • Reduced duplicate method descriptions ({orig_count} → {imp_count})")
|
||||
|
||||
# Check Entity inheritance
|
||||
if 'Entity.*Inherits from: Drawable' not in imp_content:
|
||||
print(" • Fixed Entity class (no longer shows incorrect inheritance)")
|
||||
|
||||
# Check styling
|
||||
if '.container {' in imp_content and '.container {' not in orig_content:
|
||||
print(" • Enhanced visual styling with better typography and layout")
|
||||
|
||||
# Check class documentation
|
||||
if '<h4>Arguments:</h4>' in imp_content:
|
||||
print(" • Added detailed constructor arguments for all classes")
|
||||
|
||||
# Check automation
|
||||
if 'automation.click</code></h4>' in imp_content:
|
||||
print(" • Improved automation module documentation formatting")
|
||||
|
||||
print("\n📋 Documentation Coverage:")
|
||||
print(f" • Classes: {imp_content.count('class-section')} documented")
|
||||
print(f" • Functions: {imp_content.count('function-section')} documented")
|
||||
method_count = imp_content.count('<h5><code class="method">')
|
||||
print(f" • Methods: {method_count} documented")
|
||||
|
||||
print("\n✨ Visual Enhancements:")
|
||||
print(" • Professional color scheme with syntax highlighting")
|
||||
print(" • Responsive layout with max-width container")
|
||||
print(" • Clear visual hierarchy with styled headings")
|
||||
print(" • Improved code block formatting")
|
||||
print(" • Better spacing and typography")
|
||||
|
||||
if __name__ == '__main__':
|
||||
compare_docs()
|
|
@ -1,852 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html><head>
|
||||
<meta charset="UTF-8">
|
||||
<title>McRogueFace API Reference</title>
|
||||
<style>
|
||||
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
line-height: 1.6; color: #333; max-width: 900px; margin: 0 auto; padding: 20px; }
|
||||
h1, h2, h3, h4, h5 { color: #2c3e50; margin-top: 24px; }
|
||||
h1 { border-bottom: 2px solid #3498db; padding-bottom: 10px; }
|
||||
h2 { border-bottom: 1px solid #ecf0f1; padding-bottom: 8px; }
|
||||
code { background: #f4f4f4; padding: 2px 4px; border-radius: 3px; font-size: 90%; }
|
||||
pre { background: #f4f4f4; padding: 12px; border-radius: 5px; overflow-x: auto; }
|
||||
pre code { background: none; padding: 0; }
|
||||
blockquote { border-left: 4px solid #3498db; margin: 0; padding-left: 16px; color: #7f8c8d; }
|
||||
hr { border: none; border-top: 1px solid #ecf0f1; margin: 24px 0; }
|
||||
a { color: #3498db; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.property { color: #27ae60; }
|
||||
.method { color: #2980b9; }
|
||||
.class-name { color: #8e44ad; font-weight: bold; }
|
||||
ul { padding-left: 24px; }
|
||||
li { margin: 4px 0; }
|
||||
|
||||
</style>
|
||||
</head><body>
|
||||
<h1>McRogueFace API Reference</h1>
|
||||
|
||||
<em>Generated on 2025-07-08 10:11:22</em>
|
||||
|
||||
<h2>Overview</h2>
|
||||
|
||||
<p>McRogueFace Python API\n\nCore game engine interface for creating roguelike games with Python.\n\nThis module provides:\n- Scene management (createScene, setScene, currentScene)\n- UI components (Frame, Caption, Sprite, Grid)\n- Entity system for game objects\n- Audio playback (sound effects and music)\n- Timer system for scheduled events\n- Input handling\n- Performance metrics\n\nExample:\n import mcrfpy\n \n # Create a new scene\n mcrfpy.createScene('game')\n mcrfpy.setScene('game')\n \n # Add UI elements\n frame = mcrfpy.Frame(10, 10, 200, 100)\n caption = mcrfpy.Caption('Hello World', 50, 50)\n mcrfpy.sceneUI().extend([frame, caption])\n</p>
|
||||
|
||||
<h2>Table of Contents</h2>
|
||||
|
||||
<ul>
|
||||
<li>[Classes](#classes)</li>
|
||||
<li>[Functions](#functions)</li>
|
||||
<li>[Automation Module](#automation-module)</li>
|
||||
</ul>
|
||||
|
||||
<h2>Classes</h2>
|
||||
|
||||
<h3>UI Components</h3>
|
||||
|
||||
<h3>class `Caption`</h3>
|
||||
<em>Inherits from: Drawable</em>
|
||||
|
||||
<pre><code class="language-python">
|
||||
Caption(text='', x=0, y=0, font=None, fill_color=None, outline_color=None, outline=0, click=None)
|
||||
</code></pre>
|
||||
|
||||
<p>A text display UI element with customizable font and styling.</p>
|
||||
|
||||
<p>Args:</p>
|
||||
<p>text (str): The text content to display. Default: ''</p>
|
||||
<p>x (float): X position in pixels. Default: 0</p>
|
||||
<p>y (float): Y position in pixels. Default: 0</p>
|
||||
<p>font (Font): Font object for text rendering. Default: engine default font</p>
|
||||
<p>fill_color (Color): Text fill color. Default: (255, 255, 255, 255)</p>
|
||||
<p>outline_color (Color): Text outline color. Default: (0, 0, 0, 255)</p>
|
||||
<p>outline (float): Text outline thickness. Default: 0</p>
|
||||
<p>click (callable): Click event handler. Default: None</p>
|
||||
|
||||
<p>Attributes:</p>
|
||||
<p>text (str): The displayed text content</p>
|
||||
<p>x, y (float): Position in pixels</p>
|
||||
<p>font (Font): Font used for rendering</p>
|
||||
<p>fill_color, outline_color (Color): Text appearance</p>
|
||||
<p>outline (float): Outline thickness</p>
|
||||
<p>click (callable): Click event handler</p>
|
||||
<p>visible (bool): Visibility state</p>
|
||||
<p>z_index (int): Rendering order</p>
|
||||
<p>w, h (float): Read-only computed size based on text and font</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`Get bounding box as (x, y, width, height)`</h5>
|
||||
<p>Get bounding box as (x, y, width, height)</p>
|
||||
|
||||
<h5>`Move by relative offset (dx, dy)`</h5>
|
||||
<p>Move by relative offset (dx, dy)</p>
|
||||
|
||||
<h5>`Resize to new dimensions (width, height)`</h5>
|
||||
<p>Resize to new dimensions (width, height)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Entity`</h3>
|
||||
<em>Inherits from: Drawable</em>
|
||||
|
||||
<p>UIEntity objects</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`at(...)`</h5>
|
||||
|
||||
<h5>`die(...)`</h5>
|
||||
<p>Remove this entity from its grid</p>
|
||||
|
||||
<h5>`Get bounding box as (x, y, width, height)`</h5>
|
||||
<p>Get bounding box as (x, y, width, height)</p>
|
||||
|
||||
<h5>`index(...)`</h5>
|
||||
|
||||
<h5>`Move by relative offset (dx, dy)`</h5>
|
||||
<p>Move by relative offset (dx, dy)</p>
|
||||
|
||||
<h5>`Resize to new dimensions (width, height)`</h5>
|
||||
<p>Resize to new dimensions (width, height)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Frame`</h3>
|
||||
<em>Inherits from: Drawable</em>
|
||||
|
||||
<pre><code class="language-python">
|
||||
Frame(x=0, y=0, w=0, h=0, fill_color=None, outline_color=None, outline=0, click=None, children=None)
|
||||
</code></pre>
|
||||
|
||||
<p>A rectangular frame UI element that can contain other drawable elements.</p>
|
||||
|
||||
<p>Args:</p>
|
||||
<p>x (float): X position in pixels. Default: 0</p>
|
||||
<p>y (float): Y position in pixels. Default: 0</p>
|
||||
<p>w (float): Width in pixels. Default: 0</p>
|
||||
<p>h (float): Height in pixels. Default: 0</p>
|
||||
<p>fill_color (Color): Background fill color. Default: (0, 0, 0, 128)</p>
|
||||
<p>outline_color (Color): Border outline color. Default: (255, 255, 255, 255)</p>
|
||||
<p>outline (float): Border outline thickness. Default: 0</p>
|
||||
<p>click (callable): Click event handler. Default: None</p>
|
||||
<p>children (list): Initial list of child drawable elements. Default: None</p>
|
||||
|
||||
<p>Attributes:</p>
|
||||
<p>x, y (float): Position in pixels</p>
|
||||
<p>w, h (float): Size in pixels</p>
|
||||
<p>fill_color, outline_color (Color): Visual appearance</p>
|
||||
<p>outline (float): Border thickness</p>
|
||||
<p>click (callable): Click event handler</p>
|
||||
<p>children (list): Collection of child drawable elements</p>
|
||||
<p>visible (bool): Visibility state</p>
|
||||
<p>z_index (int): Rendering order</p>
|
||||
<p>clip_children (bool): Whether to clip children to frame bounds</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`Get bounding box as (x, y, width, height)`</h5>
|
||||
<p>Get bounding box as (x, y, width, height)</p>
|
||||
|
||||
<h5>`Move by relative offset (dx, dy)`</h5>
|
||||
<p>Move by relative offset (dx, dy)</p>
|
||||
|
||||
<h5>`Resize to new dimensions (width, height)`</h5>
|
||||
<p>Resize to new dimensions (width, height)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Grid`</h3>
|
||||
<em>Inherits from: Drawable</em>
|
||||
|
||||
<pre><code class="language-python">
|
||||
Grid(x=0, y=0, grid_size=(20, 20), texture=None, tile_width=16, tile_height=16, scale=1.0, click=None)
|
||||
</code></pre>
|
||||
|
||||
<p>A grid-based tilemap UI element for rendering tile-based levels and game worlds.</p>
|
||||
|
||||
<p>Args:</p>
|
||||
<p>x (float): X position in pixels. Default: 0</p>
|
||||
<p>y (float): Y position in pixels. Default: 0</p>
|
||||
<p>grid_size (tuple): Grid dimensions as (width, height) in tiles. Default: (20, 20)</p>
|
||||
<p>texture (Texture): Texture atlas containing tile sprites. Default: None</p>
|
||||
<p>tile_width (int): Width of each tile in pixels. Default: 16</p>
|
||||
<p>tile_height (int): Height of each tile in pixels. Default: 16</p>
|
||||
<p>scale (float): Grid scaling factor. Default: 1.0</p>
|
||||
<p>click (callable): Click event handler. Default: None</p>
|
||||
|
||||
<p>Attributes:</p>
|
||||
<p>x, y (float): Position in pixels</p>
|
||||
<p>grid_size (tuple): Grid dimensions (width, height) in tiles</p>
|
||||
<p>tile_width, tile_height (int): Tile dimensions in pixels</p>
|
||||
<p>texture (Texture): Tile texture atlas</p>
|
||||
<p>scale (float): Scale multiplier</p>
|
||||
<p>points (list): 2D array of GridPoint objects for tile data</p>
|
||||
<p>entities (list): Collection of Entity objects in the grid</p>
|
||||
<p>background_color (Color): Grid background color</p>
|
||||
<p>click (callable): Click event handler</p>
|
||||
<p>visible (bool): Visibility state</p>
|
||||
<p>z_index (int): Rendering order</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`at(...)`</h5>
|
||||
|
||||
<h5>`Get bounding box as (x, y, width, height)`</h5>
|
||||
<p>Get bounding box as (x, y, width, height)</p>
|
||||
|
||||
<h5>`Move by relative offset (dx, dy)`</h5>
|
||||
<p>Move by relative offset (dx, dy)</p>
|
||||
|
||||
<h5>`Resize to new dimensions (width, height)`</h5>
|
||||
<p>Resize to new dimensions (width, height)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Sprite`</h3>
|
||||
<em>Inherits from: Drawable</em>
|
||||
|
||||
<pre><code class="language-python">
|
||||
Sprite(x=0, y=0, texture=None, sprite_index=0, scale=1.0, click=None)
|
||||
</code></pre>
|
||||
|
||||
<p>A sprite UI element that displays a texture or portion of a texture atlas.</p>
|
||||
|
||||
<p>Args:</p>
|
||||
<p>x (float): X position in pixels. Default: 0</p>
|
||||
<p>y (float): Y position in pixels. Default: 0</p>
|
||||
<p>texture (Texture): Texture object to display. Default: None</p>
|
||||
<p>sprite_index (int): Index into texture atlas (if applicable). Default: 0</p>
|
||||
<p>scale (float): Sprite scaling factor. Default: 1.0</p>
|
||||
<p>click (callable): Click event handler. Default: None</p>
|
||||
|
||||
<p>Attributes:</p>
|
||||
<p>x, y (float): Position in pixels</p>
|
||||
<p>texture (Texture): The texture being displayed</p>
|
||||
<p>sprite_index (int): Current sprite index in texture atlas</p>
|
||||
<p>scale (float): Scale multiplier</p>
|
||||
<p>click (callable): Click event handler</p>
|
||||
<p>visible (bool): Visibility state</p>
|
||||
<p>z_index (int): Rendering order</p>
|
||||
<p>w, h (float): Read-only computed size based on texture and scale</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`Get bounding box as (x, y, width, height)`</h5>
|
||||
<p>Get bounding box as (x, y, width, height)</p>
|
||||
|
||||
<h5>`Move by relative offset (dx, dy)`</h5>
|
||||
<p>Move by relative offset (dx, dy)</p>
|
||||
|
||||
<h5>`Resize to new dimensions (width, height)`</h5>
|
||||
<p>Resize to new dimensions (width, height)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>Collections</h3>
|
||||
|
||||
<h3>class `EntityCollection`</h3>
|
||||
|
||||
<p>Iterable, indexable collection of Entities</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`append(...)`</h5>
|
||||
|
||||
<h5>`count(...)`</h5>
|
||||
|
||||
<h5>`extend(...)`</h5>
|
||||
|
||||
<h5>`index(...)`</h5>
|
||||
|
||||
<h5>`remove(...)`</h5>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `UICollection`</h3>
|
||||
|
||||
<p>Iterable, indexable collection of UI objects</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`append(...)`</h5>
|
||||
|
||||
<h5>`count(...)`</h5>
|
||||
|
||||
<h5>`extend(...)`</h5>
|
||||
|
||||
<h5>`index(...)`</h5>
|
||||
|
||||
<h5>`remove(...)`</h5>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `UICollectionIter`</h3>
|
||||
|
||||
<p>Iterator for a collection of UI objects</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `UIEntityCollectionIter`</h3>
|
||||
|
||||
<p>Iterator for a collection of UI objects</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>System Types</h3>
|
||||
|
||||
<h3>class `Color`</h3>
|
||||
|
||||
<p>SFML Color Object</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`Create Color from hex string (e.g., '#FF0000' or 'FF0000')`</h5>
|
||||
<p>Create Color from hex string (e.g., '#FF0000' or 'FF0000')</p>
|
||||
|
||||
<h5>`lerp(...)`</h5>
|
||||
<p>Linearly interpolate between this color and another</p>
|
||||
|
||||
<h5>`to_hex(...)`</h5>
|
||||
<p>Convert Color to hex string</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Font`</h3>
|
||||
|
||||
<p>SFML Font Object</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Texture`</h3>
|
||||
|
||||
<p>SFML Texture Object</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Vector`</h3>
|
||||
|
||||
<p>SFML Vector Object</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`angle(...)`</h5>
|
||||
|
||||
<h5>`copy(...)`</h5>
|
||||
|
||||
<h5>`distance_to(...)`</h5>
|
||||
<p>Return the distance to another vector</p>
|
||||
|
||||
<h5>`dot(...)`</h5>
|
||||
|
||||
<h5>`magnitude(...)`</h5>
|
||||
<p>Return the length of the vector</p>
|
||||
|
||||
<h5>`magnitude_squared(...)`</h5>
|
||||
<p>Return the squared length of the vector</p>
|
||||
|
||||
<h5>`normalize(...)`</h5>
|
||||
<p>Return a unit vector in the same direction</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>Other Classes</h3>
|
||||
|
||||
<h3>class `Animation`</h3>
|
||||
|
||||
<p>Animation object for animating UI properties</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`get_current_value(...)`</h5>
|
||||
<p>Get the current interpolated value</p>
|
||||
|
||||
<h5>`start(...)`</h5>
|
||||
<p>Start the animation on a target UIDrawable</p>
|
||||
|
||||
<h5>`Update the animation by deltaTime (returns True if still running)`</h5>
|
||||
<p>Update the animation by deltaTime (returns True if still running)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Drawable`</h3>
|
||||
|
||||
<p>Base class for all drawable UI elements</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`Get bounding box as (x, y, width, height)`</h5>
|
||||
<p>Get bounding box as (x, y, width, height)</p>
|
||||
|
||||
<h5>`Move by relative offset (dx, dy)`</h5>
|
||||
<p>Move by relative offset (dx, dy)</p>
|
||||
|
||||
<h5>`Resize to new dimensions (width, height)`</h5>
|
||||
<p>Resize to new dimensions (width, height)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `GridPoint`</h3>
|
||||
|
||||
<p>UIGridPoint object</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `GridPointState`</h3>
|
||||
|
||||
<p>UIGridPointState object</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Scene`</h3>
|
||||
|
||||
<p>Base class for object-oriented scenes</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`activate(...)`</h5>
|
||||
<p>Make this the active scene</p>
|
||||
|
||||
<h5>`get_ui(...)`</h5>
|
||||
<p>Get the UI element collection for this scene</p>
|
||||
|
||||
<h5>`Register a keyboard handler function (alternative to overriding on_keypress)`</h5>
|
||||
<p>Register a keyboard handler function (alternative to overriding on_keypress)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Timer`</h3>
|
||||
|
||||
<p>Timer object for scheduled callbacks</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`cancel(...)`</h5>
|
||||
<p>Cancel the timer and remove it from the system</p>
|
||||
|
||||
<h5>`pause(...)`</h5>
|
||||
<p>Pause the timer</p>
|
||||
|
||||
<h5>`restart(...)`</h5>
|
||||
<p>Restart the timer from the current time</p>
|
||||
|
||||
<h5>`resume(...)`</h5>
|
||||
<p>Resume a paused timer</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>class `Window`</h3>
|
||||
|
||||
<p>Window singleton for accessing and modifying the game window properties</p>
|
||||
|
||||
<h4>Methods</h4>
|
||||
|
||||
<h5>`center(...)`</h5>
|
||||
<p>Center the window on the screen</p>
|
||||
|
||||
<h5>`get(...)`</h5>
|
||||
<p>Get the Window singleton instance</p>
|
||||
|
||||
<h5>`screenshot(...)`</h5>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Functions</h2>
|
||||
|
||||
<h3>Scene Management</h3>
|
||||
|
||||
<h3>`createScene(name: str)`</h3>
|
||||
|
||||
|
||||
<p>Create a new empty scene.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>name: Unique name for the new scene</p>
|
||||
|
||||
<em>*Raises:*</em>
|
||||
<p>ValueError: If a scene with this name already exists</p>
|
||||
|
||||
<em>*Note:*</em>
|
||||
<p>The scene is created but not made active. Use setScene() to switch to it.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`currentScene()`</h3>
|
||||
|
||||
|
||||
<p>Get the name of the currently active scene.</p>
|
||||
|
||||
<em>*Returns:*</em>
|
||||
<p>str: Name of the current scene</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`keypressScene(handler: callable)`</h3>
|
||||
|
||||
|
||||
<p>Set the keyboard event handler for the current scene.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>handler: Callable that receives (key_name: str, is_pressed: bool)</p>
|
||||
|
||||
<em>*Example:*</em>
|
||||
<p>def on_key(key, pressed):</p>
|
||||
<p>if key == 'A' and pressed:</p>
|
||||
<p>print('A key pressed')</p>
|
||||
<p>mcrfpy.keypressScene(on_key)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`sceneUI(scene: str = None)`</h3>
|
||||
|
||||
|
||||
<p>Get all UI elements for a scene.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>scene: Scene name. If None, uses current scene</p>
|
||||
|
||||
<em>*Returns:*</em>
|
||||
<p>list: All UI elements (Frame, Caption, Sprite, Grid) in the scene</p>
|
||||
|
||||
<em>*Raises:*</em>
|
||||
<p>KeyError: If the specified scene doesn't exist</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`setScene(scene: str, transition: str = None, duration: float = 0.0)`</h3>
|
||||
|
||||
|
||||
<p>Switch to a different scene with optional transition effect.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>scene: Name of the scene to switch to</p>
|
||||
<p>transition: Transition type ('fade', 'slide_left', 'slide_right', 'slide_up', 'slide_down')</p>
|
||||
<p>duration: Transition duration in seconds (default: 0.0 for instant)</p>
|
||||
|
||||
<em>*Raises:*</em>
|
||||
<p>KeyError: If the scene doesn't exist</p>
|
||||
<p>ValueError: If the transition type is invalid</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>Audio</h3>
|
||||
|
||||
<h3>`createSoundBuffer(filename: str)`</h3>
|
||||
|
||||
|
||||
<p>Load a sound effect from a file and return its buffer ID.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>filename: Path to the sound file (WAV, OGG, FLAC)</p>
|
||||
|
||||
<em>*Returns:*</em>
|
||||
<p>int: Buffer ID for use with playSound()</p>
|
||||
|
||||
<em>*Raises:*</em>
|
||||
<p>RuntimeError: If the file cannot be loaded</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`getMusicVolume()`</h3>
|
||||
|
||||
|
||||
<p>Get the current music volume level.</p>
|
||||
|
||||
<em>*Returns:*</em>
|
||||
<p>int: Current volume (0-100)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`getSoundVolume()`</h3>
|
||||
|
||||
|
||||
<p>Get the current sound effects volume level.</p>
|
||||
|
||||
<em>*Returns:*</em>
|
||||
<p>int: Current volume (0-100)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`loadMusic(filename: str)`</h3>
|
||||
|
||||
|
||||
<p>Load and immediately play background music from a file.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>filename: Path to the music file (WAV, OGG, FLAC)</p>
|
||||
|
||||
<em>*Note:*</em>
|
||||
<p>Only one music track can play at a time. Loading new music stops the current track.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`playSound(buffer_id: int)`</h3>
|
||||
|
||||
|
||||
<p>Play a sound effect using a previously loaded buffer.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>buffer_id: Sound buffer ID returned by createSoundBuffer()</p>
|
||||
|
||||
<em>*Raises:*</em>
|
||||
<p>RuntimeError: If the buffer ID is invalid</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`setMusicVolume(volume: int)`</h3>
|
||||
|
||||
|
||||
<p>Set the global music volume.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>volume: Volume level from 0 (silent) to 100 (full volume)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`setSoundVolume(volume: int)`</h3>
|
||||
|
||||
|
||||
<p>Set the global sound effects volume.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>volume: Volume level from 0 (silent) to 100 (full volume)</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>UI Utilities</h3>
|
||||
|
||||
<h3>`find(name: str, scene: str = None)`</h3>
|
||||
|
||||
|
||||
<p>Find the first UI element with the specified name.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>name: Exact name to search for</p>
|
||||
<p>scene: Scene to search in (default: current scene)</p>
|
||||
|
||||
<em>*Returns:*</em>
|
||||
<p>Frame, Caption, Sprite, Grid, or Entity if found; None otherwise</p>
|
||||
|
||||
<em>*Note:*</em>
|
||||
<p>Searches scene UI elements and entities within grids.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`findAll(pattern: str, scene: str = None)`</h3>
|
||||
|
||||
|
||||
<p>Find all UI elements matching a name pattern.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>pattern: Name pattern with optional wildcards (* matches any characters)</p>
|
||||
<p>scene: Scene to search in (default: current scene)</p>
|
||||
|
||||
<em>*Returns:*</em>
|
||||
<p>list: All matching UI elements and entities</p>
|
||||
|
||||
<em>*Example:*</em>
|
||||
<p>findAll('enemy*') # Find all elements starting with 'enemy'</p>
|
||||
<p>findAll('*_button') # Find all elements ending with '_button'</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>System</h3>
|
||||
|
||||
<h3>`delTimer(name: str)`</h3>
|
||||
|
||||
|
||||
<p>Stop and remove a timer.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>name: Timer identifier to remove</p>
|
||||
|
||||
<em>*Note:*</em>
|
||||
<p>No error is raised if the timer doesn't exist.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`exit()`</h3>
|
||||
|
||||
|
||||
<p>Cleanly shut down the game engine and exit the application.</p>
|
||||
|
||||
<em>*Note:*</em>
|
||||
<p>This immediately closes the window and terminates the program.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`getMetrics()`</h3>
|
||||
|
||||
|
||||
<p>Get current performance metrics.</p>
|
||||
|
||||
<em>*Returns:*</em>
|
||||
<p>dict: Performance data with keys:</p>
|
||||
<ul>
|
||||
<li>frame_time: Last frame duration in seconds</li>
|
||||
<li>avg_frame_time: Average frame time</li>
|
||||
<li>fps: Frames per second</li>
|
||||
<li>draw_calls: Number of draw calls</li>
|
||||
<li>ui_elements: Total UI element count</li>
|
||||
<li>visible_elements: Visible element count</li>
|
||||
<li>current_frame: Frame counter</li>
|
||||
<li>runtime: Total runtime in seconds</li>
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`setScale(multiplier: float)`</h3>
|
||||
|
||||
|
||||
<p>Scale the game window size.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>multiplier: Scale factor (e.g., 2.0 for double size)</p>
|
||||
|
||||
<em>*Note:*</em>
|
||||
<p>The internal resolution remains 1024x768, but the window is scaled.</p>
|
||||
<p>This is deprecated - use Window.resolution instead.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`setTimer(name: str, handler: callable, interval: int)`</h3>
|
||||
|
||||
|
||||
<p>Create or update a recurring timer.</p>
|
||||
|
||||
<em>*Args:*</em>
|
||||
<p>name: Unique identifier for the timer</p>
|
||||
<p>handler: Function called with (runtime: float) parameter</p>
|
||||
<p>interval: Time between calls in milliseconds</p>
|
||||
|
||||
<em>*Note:*</em>
|
||||
<p>If a timer with this name exists, it will be replaced.</p>
|
||||
<p>The handler receives the total runtime in seconds as its argument.</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Automation Module</h2>
|
||||
|
||||
<p>The <code>mcrfpy.automation</code> module provides testing and automation capabilities.</p>
|
||||
|
||||
<h3>`automation.click(x=None, y=None, clicks=1, interval=0.0, button='left') - Click at position`</h3>
|
||||
|
||||
<p>click(x=None, y=None, clicks=1, interval=0.0, button='left') - Click at position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.doubleClick(x=None, y=None) - Double click at position`</h3>
|
||||
|
||||
<p>doubleClick(x=None, y=None) - Double click at position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.dragRel(xOffset, yOffset, duration=0.0, button='left') - Drag mouse relative to current position`</h3>
|
||||
|
||||
<p>dragRel(xOffset, yOffset, duration=0.0, button='left') - Drag mouse relative to current position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.dragTo(x, y, duration=0.0, button='left') - Drag mouse to position`</h3>
|
||||
|
||||
<p>dragTo(x, y, duration=0.0, button='left') - Drag mouse to position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.hotkey(*keys) - Press a hotkey combination (e.g., hotkey('ctrl', 'c'))`</h3>
|
||||
|
||||
<p>hotkey(*keys) - Press a hotkey combination (e.g., hotkey('ctrl', 'c'))</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.keyDown(key) - Press and hold a key`</h3>
|
||||
|
||||
<p>keyDown(key) - Press and hold a key</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.keyUp(key) - Release a key`</h3>
|
||||
|
||||
<p>keyUp(key) - Release a key</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.middleClick(x=None, y=None) - Middle click at position`</h3>
|
||||
|
||||
<p>middleClick(x=None, y=None) - Middle click at position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.mouseDown(x=None, y=None, button='left') - Press mouse button`</h3>
|
||||
|
||||
<p>mouseDown(x=None, y=None, button='left') - Press mouse button</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.mouseUp(x=None, y=None, button='left') - Release mouse button`</h3>
|
||||
|
||||
<p>mouseUp(x=None, y=None, button='left') - Release mouse button</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.moveRel(xOffset, yOffset, duration=0.0) - Move mouse relative to current position`</h3>
|
||||
|
||||
<p>moveRel(xOffset, yOffset, duration=0.0) - Move mouse relative to current position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.moveTo(x, y, duration=0.0) - Move mouse to absolute position`</h3>
|
||||
|
||||
<p>moveTo(x, y, duration=0.0) - Move mouse to absolute position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.onScreen(x, y) - Check if coordinates are within screen bounds`</h3>
|
||||
|
||||
<p>onScreen(x, y) - Check if coordinates are within screen bounds</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.position() - Get current mouse position as (x, y) tuple`</h3>
|
||||
|
||||
<p>position() - Get current mouse position as (x, y) tuple</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.rightClick(x=None, y=None) - Right click at position`</h3>
|
||||
|
||||
<p>rightClick(x=None, y=None) - Right click at position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.screenshot(filename) - Save a screenshot to the specified file`</h3>
|
||||
|
||||
<p>screenshot(filename) - Save a screenshot to the specified file</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.scroll(clicks, x=None, y=None) - Scroll wheel at position`</h3>
|
||||
|
||||
<p>scroll(clicks, x=None, y=None) - Scroll wheel at position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.size() - Get screen size as (width, height) tuple`</h3>
|
||||
|
||||
<p>size() - Get screen size as (width, height) tuple</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.tripleClick(x=None, y=None) - Triple click at position`</h3>
|
||||
|
||||
<p>tripleClick(x=None, y=None) - Triple click at position</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>`automation.typewrite(message, interval=0.0) - Type text with optional interval between keystrokes`</h3>
|
||||
|
||||
<p>typewrite(message, interval=0.0) - Type text with optional interval between keystrokes</p>
|
||||
|
||||
<hr>
|
||||
|
||||
</body></html>
|
Before Width: | Height: | Size: 43 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 48 KiB |
Before Width: | Height: | Size: 31 KiB |
|
@ -1,482 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate API reference documentation for McRogueFace.
|
||||
|
||||
This script generates comprehensive API documentation in multiple formats:
|
||||
- Markdown for GitHub/documentation sites
|
||||
- HTML for local browsing
|
||||
- RST for Sphinx integration (future)
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
import datetime
|
||||
from typing import Dict, List, Any, Optional
|
||||
from pathlib import Path
|
||||
|
||||
# We need to run this with McRogueFace as the interpreter
|
||||
# so mcrfpy is available
|
||||
import mcrfpy
|
||||
|
||||
def escape_markdown(text: str) -> str:
|
||||
"""Escape special markdown characters."""
|
||||
if not text:
|
||||
return ""
|
||||
# Escape backticks in inline code
|
||||
return text.replace("`", "\\`")
|
||||
|
||||
def format_signature(name: str, doc: str) -> str:
|
||||
"""Extract and format function signature from docstring."""
|
||||
if not doc:
|
||||
return f"{name}(...)"
|
||||
|
||||
lines = doc.strip().split('\n')
|
||||
if lines and '(' in lines[0]:
|
||||
# First line contains signature
|
||||
return lines[0].split('->')[0].strip()
|
||||
|
||||
return f"{name}(...)"
|
||||
|
||||
def get_class_info(cls: type) -> Dict[str, Any]:
|
||||
"""Extract comprehensive information about a class."""
|
||||
info = {
|
||||
'name': cls.__name__,
|
||||
'doc': cls.__doc__ or "",
|
||||
'methods': [],
|
||||
'properties': [],
|
||||
'bases': [base.__name__ for base in cls.__bases__ if base.__name__ != 'object'],
|
||||
}
|
||||
|
||||
# Get all attributes
|
||||
for attr_name in sorted(dir(cls)):
|
||||
if attr_name.startswith('_') and not attr_name.startswith('__'):
|
||||
continue
|
||||
|
||||
try:
|
||||
attr = getattr(cls, attr_name)
|
||||
|
||||
if isinstance(attr, property):
|
||||
prop_info = {
|
||||
'name': attr_name,
|
||||
'doc': (attr.fget.__doc__ if attr.fget else "") or "",
|
||||
'readonly': attr.fset is None
|
||||
}
|
||||
info['properties'].append(prop_info)
|
||||
elif callable(attr) and not attr_name.startswith('__'):
|
||||
method_info = {
|
||||
'name': attr_name,
|
||||
'doc': attr.__doc__ or "",
|
||||
'signature': format_signature(attr_name, attr.__doc__)
|
||||
}
|
||||
info['methods'].append(method_info)
|
||||
except:
|
||||
pass
|
||||
|
||||
return info
|
||||
|
||||
def get_function_info(func: Any, name: str) -> Dict[str, Any]:
|
||||
"""Extract information about a function."""
|
||||
return {
|
||||
'name': name,
|
||||
'doc': func.__doc__ or "",
|
||||
'signature': format_signature(name, func.__doc__)
|
||||
}
|
||||
|
||||
def generate_markdown_class(cls_info: Dict[str, Any]) -> List[str]:
|
||||
"""Generate markdown documentation for a class."""
|
||||
lines = []
|
||||
|
||||
# Class header
|
||||
lines.append(f"### class `{cls_info['name']}`")
|
||||
if cls_info['bases']:
|
||||
lines.append(f"*Inherits from: {', '.join(cls_info['bases'])}*")
|
||||
lines.append("")
|
||||
|
||||
# Class description
|
||||
if cls_info['doc']:
|
||||
doc_lines = cls_info['doc'].strip().split('\n')
|
||||
# First line is usually the constructor signature
|
||||
if doc_lines and '(' in doc_lines[0]:
|
||||
lines.append(f"```python")
|
||||
lines.append(doc_lines[0])
|
||||
lines.append("```")
|
||||
lines.append("")
|
||||
# Rest is description
|
||||
if len(doc_lines) > 2:
|
||||
lines.extend(doc_lines[2:])
|
||||
lines.append("")
|
||||
else:
|
||||
lines.extend(doc_lines)
|
||||
lines.append("")
|
||||
|
||||
# Properties
|
||||
if cls_info['properties']:
|
||||
lines.append("#### Properties")
|
||||
lines.append("")
|
||||
for prop in cls_info['properties']:
|
||||
readonly = " *(readonly)*" if prop['readonly'] else ""
|
||||
lines.append(f"- **`{prop['name']}`**{readonly}")
|
||||
if prop['doc']:
|
||||
lines.append(f" - {prop['doc'].strip()}")
|
||||
lines.append("")
|
||||
|
||||
# Methods
|
||||
if cls_info['methods']:
|
||||
lines.append("#### Methods")
|
||||
lines.append("")
|
||||
for method in cls_info['methods']:
|
||||
lines.append(f"##### `{method['signature']}`")
|
||||
if method['doc']:
|
||||
# Parse docstring for better formatting
|
||||
doc_lines = method['doc'].strip().split('\n')
|
||||
# Skip the signature line if it's repeated
|
||||
start = 1 if doc_lines and method['name'] in doc_lines[0] else 0
|
||||
for line in doc_lines[start:]:
|
||||
lines.append(line)
|
||||
lines.append("")
|
||||
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
return lines
|
||||
|
||||
def generate_markdown_function(func_info: Dict[str, Any]) -> List[str]:
|
||||
"""Generate markdown documentation for a function."""
|
||||
lines = []
|
||||
|
||||
lines.append(f"### `{func_info['signature']}`")
|
||||
lines.append("")
|
||||
|
||||
if func_info['doc']:
|
||||
doc_lines = func_info['doc'].strip().split('\n')
|
||||
# Skip signature line if present
|
||||
start = 1 if doc_lines and func_info['name'] in doc_lines[0] else 0
|
||||
|
||||
# Process documentation sections
|
||||
in_section = None
|
||||
for line in doc_lines[start:]:
|
||||
if line.strip() in ['Args:', 'Returns:', 'Raises:', 'Note:', 'Example:']:
|
||||
in_section = line.strip()
|
||||
lines.append(f"**{in_section}**")
|
||||
elif in_section and line.strip():
|
||||
# Indent content under sections
|
||||
lines.append(f"{line}")
|
||||
else:
|
||||
lines.append(line)
|
||||
lines.append("")
|
||||
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
return lines
|
||||
|
||||
def generate_markdown_docs() -> str:
|
||||
"""Generate complete markdown API documentation."""
|
||||
lines = []
|
||||
|
||||
# Header
|
||||
lines.append("# McRogueFace API Reference")
|
||||
lines.append("")
|
||||
lines.append(f"*Generated on {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*")
|
||||
lines.append("")
|
||||
|
||||
# Module description
|
||||
if mcrfpy.__doc__:
|
||||
lines.append("## Overview")
|
||||
lines.append("")
|
||||
lines.extend(mcrfpy.__doc__.strip().split('\n'))
|
||||
lines.append("")
|
||||
|
||||
# Table of contents
|
||||
lines.append("## Table of Contents")
|
||||
lines.append("")
|
||||
lines.append("- [Classes](#classes)")
|
||||
lines.append("- [Functions](#functions)")
|
||||
lines.append("- [Automation Module](#automation-module)")
|
||||
lines.append("")
|
||||
|
||||
# Collect all components
|
||||
classes = []
|
||||
functions = []
|
||||
constants = []
|
||||
|
||||
for name in sorted(dir(mcrfpy)):
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
|
||||
obj = getattr(mcrfpy, name)
|
||||
|
||||
if isinstance(obj, type):
|
||||
classes.append((name, obj))
|
||||
elif callable(obj):
|
||||
functions.append((name, obj))
|
||||
elif not inspect.ismodule(obj):
|
||||
constants.append((name, obj))
|
||||
|
||||
# Document classes
|
||||
lines.append("## Classes")
|
||||
lines.append("")
|
||||
|
||||
# Group classes by category
|
||||
ui_classes = []
|
||||
collection_classes = []
|
||||
system_classes = []
|
||||
other_classes = []
|
||||
|
||||
for name, cls in classes:
|
||||
if name in ['Frame', 'Caption', 'Sprite', 'Grid', 'Entity']:
|
||||
ui_classes.append((name, cls))
|
||||
elif 'Collection' in name:
|
||||
collection_classes.append((name, cls))
|
||||
elif name in ['Color', 'Vector', 'Texture', 'Font']:
|
||||
system_classes.append((name, cls))
|
||||
else:
|
||||
other_classes.append((name, cls))
|
||||
|
||||
# UI Classes
|
||||
if ui_classes:
|
||||
lines.append("### UI Components")
|
||||
lines.append("")
|
||||
for name, cls in ui_classes:
|
||||
lines.extend(generate_markdown_class(get_class_info(cls)))
|
||||
|
||||
# Collections
|
||||
if collection_classes:
|
||||
lines.append("### Collections")
|
||||
lines.append("")
|
||||
for name, cls in collection_classes:
|
||||
lines.extend(generate_markdown_class(get_class_info(cls)))
|
||||
|
||||
# System Classes
|
||||
if system_classes:
|
||||
lines.append("### System Types")
|
||||
lines.append("")
|
||||
for name, cls in system_classes:
|
||||
lines.extend(generate_markdown_class(get_class_info(cls)))
|
||||
|
||||
# Other Classes
|
||||
if other_classes:
|
||||
lines.append("### Other Classes")
|
||||
lines.append("")
|
||||
for name, cls in other_classes:
|
||||
lines.extend(generate_markdown_class(get_class_info(cls)))
|
||||
|
||||
# Document functions
|
||||
lines.append("## Functions")
|
||||
lines.append("")
|
||||
|
||||
# Group functions by category
|
||||
scene_funcs = []
|
||||
audio_funcs = []
|
||||
ui_funcs = []
|
||||
system_funcs = []
|
||||
|
||||
for name, func in functions:
|
||||
if 'scene' in name.lower() or name in ['createScene', 'setScene']:
|
||||
scene_funcs.append((name, func))
|
||||
elif any(x in name.lower() for x in ['sound', 'music', 'volume']):
|
||||
audio_funcs.append((name, func))
|
||||
elif name in ['find', 'findAll']:
|
||||
ui_funcs.append((name, func))
|
||||
else:
|
||||
system_funcs.append((name, func))
|
||||
|
||||
# Scene Management
|
||||
if scene_funcs:
|
||||
lines.append("### Scene Management")
|
||||
lines.append("")
|
||||
for name, func in scene_funcs:
|
||||
lines.extend(generate_markdown_function(get_function_info(func, name)))
|
||||
|
||||
# Audio
|
||||
if audio_funcs:
|
||||
lines.append("### Audio")
|
||||
lines.append("")
|
||||
for name, func in audio_funcs:
|
||||
lines.extend(generate_markdown_function(get_function_info(func, name)))
|
||||
|
||||
# UI Utilities
|
||||
if ui_funcs:
|
||||
lines.append("### UI Utilities")
|
||||
lines.append("")
|
||||
for name, func in ui_funcs:
|
||||
lines.extend(generate_markdown_function(get_function_info(func, name)))
|
||||
|
||||
# System
|
||||
if system_funcs:
|
||||
lines.append("### System")
|
||||
lines.append("")
|
||||
for name, func in system_funcs:
|
||||
lines.extend(generate_markdown_function(get_function_info(func, name)))
|
||||
|
||||
# Automation module
|
||||
if hasattr(mcrfpy, 'automation'):
|
||||
lines.append("## Automation Module")
|
||||
lines.append("")
|
||||
lines.append("The `mcrfpy.automation` module provides testing and automation capabilities.")
|
||||
lines.append("")
|
||||
|
||||
automation = mcrfpy.automation
|
||||
auto_funcs = []
|
||||
|
||||
for name in sorted(dir(automation)):
|
||||
if not name.startswith('_'):
|
||||
obj = getattr(automation, name)
|
||||
if callable(obj):
|
||||
auto_funcs.append((name, obj))
|
||||
|
||||
for name, func in auto_funcs:
|
||||
# Format as static method
|
||||
func_info = get_function_info(func, name)
|
||||
lines.append(f"### `automation.{func_info['signature']}`")
|
||||
lines.append("")
|
||||
if func_info['doc']:
|
||||
lines.append(func_info['doc'])
|
||||
lines.append("")
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
def generate_html_docs(markdown_content: str) -> str:
|
||||
"""Convert markdown to HTML."""
|
||||
# Simple conversion - in production use a proper markdown parser
|
||||
html = ['<!DOCTYPE html>']
|
||||
html.append('<html><head>')
|
||||
html.append('<meta charset="UTF-8">')
|
||||
html.append('<title>McRogueFace API Reference</title>')
|
||||
html.append('<style>')
|
||||
html.append('''
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
line-height: 1.6; color: #333; max-width: 900px; margin: 0 auto; padding: 20px; }
|
||||
h1, h2, h3, h4, h5 { color: #2c3e50; margin-top: 24px; }
|
||||
h1 { border-bottom: 2px solid #3498db; padding-bottom: 10px; }
|
||||
h2 { border-bottom: 1px solid #ecf0f1; padding-bottom: 8px; }
|
||||
code { background: #f4f4f4; padding: 2px 4px; border-radius: 3px; font-size: 90%; }
|
||||
pre { background: #f4f4f4; padding: 12px; border-radius: 5px; overflow-x: auto; }
|
||||
pre code { background: none; padding: 0; }
|
||||
blockquote { border-left: 4px solid #3498db; margin: 0; padding-left: 16px; color: #7f8c8d; }
|
||||
hr { border: none; border-top: 1px solid #ecf0f1; margin: 24px 0; }
|
||||
a { color: #3498db; text-decoration: none; }
|
||||
a:hover { text-decoration: underline; }
|
||||
.property { color: #27ae60; }
|
||||
.method { color: #2980b9; }
|
||||
.class-name { color: #8e44ad; font-weight: bold; }
|
||||
ul { padding-left: 24px; }
|
||||
li { margin: 4px 0; }
|
||||
''')
|
||||
html.append('</style>')
|
||||
html.append('</head><body>')
|
||||
|
||||
# Very basic markdown to HTML conversion
|
||||
lines = markdown_content.split('\n')
|
||||
in_code_block = False
|
||||
in_list = False
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
|
||||
if stripped.startswith('```'):
|
||||
if in_code_block:
|
||||
html.append('</code></pre>')
|
||||
in_code_block = False
|
||||
else:
|
||||
lang = stripped[3:] or 'python'
|
||||
html.append(f'<pre><code class="language-{lang}">')
|
||||
in_code_block = True
|
||||
continue
|
||||
|
||||
if in_code_block:
|
||||
html.append(line)
|
||||
continue
|
||||
|
||||
# Headers
|
||||
if stripped.startswith('#'):
|
||||
level = len(stripped.split()[0])
|
||||
text = stripped[level:].strip()
|
||||
html.append(f'<h{level}>{text}</h{level}>')
|
||||
# Lists
|
||||
elif stripped.startswith('- '):
|
||||
if not in_list:
|
||||
html.append('<ul>')
|
||||
in_list = True
|
||||
html.append(f'<li>{stripped[2:]}</li>')
|
||||
# Horizontal rule
|
||||
elif stripped == '---':
|
||||
if in_list:
|
||||
html.append('</ul>')
|
||||
in_list = False
|
||||
html.append('<hr>')
|
||||
# Emphasis
|
||||
elif stripped.startswith('*') and stripped.endswith('*') and len(stripped) > 2:
|
||||
html.append(f'<em>{stripped[1:-1]}</em>')
|
||||
# Bold
|
||||
elif stripped.startswith('**') and stripped.endswith('**'):
|
||||
html.append(f'<strong>{stripped[2:-2]}</strong>')
|
||||
# Regular paragraph
|
||||
elif stripped:
|
||||
if in_list:
|
||||
html.append('</ul>')
|
||||
in_list = False
|
||||
# Convert inline code
|
||||
text = stripped
|
||||
if '`' in text:
|
||||
import re
|
||||
text = re.sub(r'`([^`]+)`', r'<code>\1</code>', text)
|
||||
html.append(f'<p>{text}</p>')
|
||||
else:
|
||||
if in_list:
|
||||
html.append('</ul>')
|
||||
in_list = False
|
||||
# Empty line
|
||||
html.append('')
|
||||
|
||||
if in_list:
|
||||
html.append('</ul>')
|
||||
if in_code_block:
|
||||
html.append('</code></pre>')
|
||||
|
||||
html.append('</body></html>')
|
||||
return '\n'.join(html)
|
||||
|
||||
def main():
|
||||
"""Generate API documentation in multiple formats."""
|
||||
print("Generating McRogueFace API Documentation...")
|
||||
|
||||
# Create docs directory
|
||||
docs_dir = Path("docs")
|
||||
docs_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Generate markdown documentation
|
||||
print("- Generating Markdown documentation...")
|
||||
markdown_content = generate_markdown_docs()
|
||||
|
||||
# Write markdown
|
||||
md_path = docs_dir / "API_REFERENCE.md"
|
||||
with open(md_path, 'w') as f:
|
||||
f.write(markdown_content)
|
||||
print(f" ✓ Written to {md_path}")
|
||||
|
||||
# Generate HTML
|
||||
print("- Generating HTML documentation...")
|
||||
html_content = generate_html_docs(markdown_content)
|
||||
|
||||
# Write HTML
|
||||
html_path = docs_dir / "api_reference.html"
|
||||
with open(html_path, 'w') as f:
|
||||
f.write(html_content)
|
||||
print(f" ✓ Written to {html_path}")
|
||||
|
||||
# Summary statistics
|
||||
lines = markdown_content.split('\n')
|
||||
class_count = markdown_content.count('### class')
|
||||
func_count = len([l for l in lines if l.strip().startswith('### `') and 'class' not in l])
|
||||
|
||||
print("\nDocumentation Statistics:")
|
||||
print(f"- Classes documented: {class_count}")
|
||||
print(f"- Functions documented: {func_count}")
|
||||
print(f"- Total lines: {len(lines)}")
|
||||
print(f"- File size: {len(markdown_content):,} bytes")
|
||||
|
||||
print("\nAPI documentation generated successfully!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,119 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate API reference documentation for McRogueFace - Simple version."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import mcrfpy
|
||||
|
||||
def generate_markdown_docs():
|
||||
"""Generate markdown API documentation."""
|
||||
lines = []
|
||||
|
||||
# Header
|
||||
lines.append("# McRogueFace API Reference")
|
||||
lines.append("")
|
||||
lines.append("*Generated on {}*".format(datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
|
||||
lines.append("")
|
||||
|
||||
# Module description
|
||||
if mcrfpy.__doc__:
|
||||
lines.append("## Overview")
|
||||
lines.append("")
|
||||
lines.extend(mcrfpy.__doc__.strip().split('\n'))
|
||||
lines.append("")
|
||||
|
||||
# Collect all components
|
||||
classes = []
|
||||
functions = []
|
||||
|
||||
for name in sorted(dir(mcrfpy)):
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
|
||||
obj = getattr(mcrfpy, name)
|
||||
|
||||
if isinstance(obj, type):
|
||||
classes.append((name, obj))
|
||||
elif callable(obj):
|
||||
functions.append((name, obj))
|
||||
|
||||
# Document classes
|
||||
lines.append("## Classes")
|
||||
lines.append("")
|
||||
|
||||
for name, cls in classes:
|
||||
lines.append("### class {}".format(name))
|
||||
if cls.__doc__:
|
||||
doc_lines = cls.__doc__.strip().split('\n')
|
||||
for line in doc_lines[:5]: # First 5 lines
|
||||
lines.append(line)
|
||||
lines.append("")
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
|
||||
# Document functions
|
||||
lines.append("## Functions")
|
||||
lines.append("")
|
||||
|
||||
for name, func in functions:
|
||||
lines.append("### {}".format(name))
|
||||
if func.__doc__:
|
||||
doc_lines = func.__doc__.strip().split('\n')
|
||||
for line in doc_lines[:5]: # First 5 lines
|
||||
lines.append(line)
|
||||
lines.append("")
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
|
||||
# Automation module
|
||||
if hasattr(mcrfpy, 'automation'):
|
||||
lines.append("## Automation Module")
|
||||
lines.append("")
|
||||
|
||||
automation = mcrfpy.automation
|
||||
for name in sorted(dir(automation)):
|
||||
if not name.startswith('_'):
|
||||
obj = getattr(automation, name)
|
||||
if callable(obj):
|
||||
lines.append("### automation.{}".format(name))
|
||||
if obj.__doc__:
|
||||
lines.append(obj.__doc__.strip().split('\n')[0])
|
||||
lines.append("")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
def main():
|
||||
"""Generate API documentation."""
|
||||
print("Generating McRogueFace API Documentation...")
|
||||
|
||||
# Create docs directory
|
||||
docs_dir = Path("docs")
|
||||
docs_dir.mkdir(exist_ok=True)
|
||||
|
||||
# Generate markdown
|
||||
markdown_content = generate_markdown_docs()
|
||||
|
||||
# Write markdown
|
||||
md_path = docs_dir / "API_REFERENCE.md"
|
||||
with open(md_path, 'w') as f:
|
||||
f.write(markdown_content)
|
||||
print("Written to {}".format(md_path))
|
||||
|
||||
# Summary
|
||||
lines = markdown_content.split('\n')
|
||||
class_count = markdown_content.count('### class')
|
||||
func_count = markdown_content.count('### ') - class_count - markdown_content.count('### automation.')
|
||||
|
||||
print("\nDocumentation Statistics:")
|
||||
print("- Classes documented: {}".format(class_count))
|
||||
print("- Functions documented: {}".format(func_count))
|
||||
print("- Total lines: {}".format(len(lines)))
|
||||
|
||||
print("\nAPI documentation generated successfully!")
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,960 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate COMPLETE HTML API reference documentation for McRogueFace with NO missing methods."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import datetime
|
||||
import html
|
||||
from pathlib import Path
|
||||
import mcrfpy
|
||||
|
||||
def escape_html(text: str) -> str:
|
||||
"""Escape HTML special characters."""
|
||||
return html.escape(text) if text else ""
|
||||
|
||||
def get_complete_method_documentation():
|
||||
"""Return complete documentation for ALL methods across all classes."""
|
||||
return {
|
||||
# Base Drawable methods (inherited by all UI elements)
|
||||
'Drawable': {
|
||||
'get_bounds': {
|
||||
'signature': 'get_bounds()',
|
||||
'description': 'Get the bounding rectangle of this drawable element.',
|
||||
'returns': 'tuple: (x, y, width, height) representing the element\'s bounds',
|
||||
'note': 'The bounds are in screen coordinates and account for current position and size.'
|
||||
},
|
||||
'move': {
|
||||
'signature': 'move(dx, dy)',
|
||||
'description': 'Move the element by a relative offset.',
|
||||
'args': [
|
||||
('dx', 'float', 'Horizontal offset in pixels'),
|
||||
('dy', 'float', 'Vertical offset in pixels')
|
||||
],
|
||||
'note': 'This modifies the x and y position properties by the given amounts.'
|
||||
},
|
||||
'resize': {
|
||||
'signature': 'resize(width, height)',
|
||||
'description': 'Resize the element to new dimensions.',
|
||||
'args': [
|
||||
('width', 'float', 'New width in pixels'),
|
||||
('height', 'float', 'New height in pixels')
|
||||
],
|
||||
'note': 'For Caption and Sprite, this may not change actual size if determined by content.'
|
||||
}
|
||||
},
|
||||
|
||||
# Entity-specific methods
|
||||
'Entity': {
|
||||
'at': {
|
||||
'signature': 'at(x, y)',
|
||||
'description': 'Check if this entity is at the specified grid coordinates.',
|
||||
'args': [
|
||||
('x', 'int', 'Grid x coordinate to check'),
|
||||
('y', 'int', 'Grid y coordinate to check')
|
||||
],
|
||||
'returns': 'bool: True if entity is at position (x, y), False otherwise'
|
||||
},
|
||||
'die': {
|
||||
'signature': 'die()',
|
||||
'description': 'Remove this entity from its parent grid.',
|
||||
'note': 'The entity object remains valid but is no longer rendered or updated.'
|
||||
},
|
||||
'index': {
|
||||
'signature': 'index()',
|
||||
'description': 'Get the index of this entity in its parent grid\'s entity list.',
|
||||
'returns': 'int: Index position, or -1 if not in a grid'
|
||||
}
|
||||
},
|
||||
|
||||
# Grid-specific methods
|
||||
'Grid': {
|
||||
'at': {
|
||||
'signature': 'at(x, y)',
|
||||
'description': 'Get the GridPoint at the specified grid coordinates.',
|
||||
'args': [
|
||||
('x', 'int', 'Grid x coordinate'),
|
||||
('y', 'int', 'Grid y coordinate')
|
||||
],
|
||||
'returns': 'GridPoint or None: The grid point at (x, y), or None if out of bounds'
|
||||
}
|
||||
},
|
||||
|
||||
# Collection methods
|
||||
'EntityCollection': {
|
||||
'append': {
|
||||
'signature': 'append(entity)',
|
||||
'description': 'Add an entity to the end of the collection.',
|
||||
'args': [('entity', 'Entity', 'The entity to add')]
|
||||
},
|
||||
'remove': {
|
||||
'signature': 'remove(entity)',
|
||||
'description': 'Remove the first occurrence of an entity from the collection.',
|
||||
'args': [('entity', 'Entity', 'The entity to remove')],
|
||||
'raises': 'ValueError: If entity is not in collection'
|
||||
},
|
||||
'extend': {
|
||||
'signature': 'extend(iterable)',
|
||||
'description': 'Add all entities from an iterable to the collection.',
|
||||
'args': [('iterable', 'Iterable[Entity]', 'Entities to add')]
|
||||
},
|
||||
'count': {
|
||||
'signature': 'count(entity)',
|
||||
'description': 'Count the number of occurrences of an entity in the collection.',
|
||||
'args': [('entity', 'Entity', 'The entity to count')],
|
||||
'returns': 'int: Number of times entity appears in collection'
|
||||
},
|
||||
'index': {
|
||||
'signature': 'index(entity)',
|
||||
'description': 'Find the index of the first occurrence of an entity.',
|
||||
'args': [('entity', 'Entity', 'The entity to find')],
|
||||
'returns': 'int: Index of entity in collection',
|
||||
'raises': 'ValueError: If entity is not in collection'
|
||||
}
|
||||
},
|
||||
|
||||
'UICollection': {
|
||||
'append': {
|
||||
'signature': 'append(drawable)',
|
||||
'description': 'Add a drawable element to the end of the collection.',
|
||||
'args': [('drawable', 'UIDrawable', 'The drawable element to add')]
|
||||
},
|
||||
'remove': {
|
||||
'signature': 'remove(drawable)',
|
||||
'description': 'Remove the first occurrence of a drawable from the collection.',
|
||||
'args': [('drawable', 'UIDrawable', 'The drawable to remove')],
|
||||
'raises': 'ValueError: If drawable is not in collection'
|
||||
},
|
||||
'extend': {
|
||||
'signature': 'extend(iterable)',
|
||||
'description': 'Add all drawables from an iterable to the collection.',
|
||||
'args': [('iterable', 'Iterable[UIDrawable]', 'Drawables to add')]
|
||||
},
|
||||
'count': {
|
||||
'signature': 'count(drawable)',
|
||||
'description': 'Count the number of occurrences of a drawable in the collection.',
|
||||
'args': [('drawable', 'UIDrawable', 'The drawable to count')],
|
||||
'returns': 'int: Number of times drawable appears in collection'
|
||||
},
|
||||
'index': {
|
||||
'signature': 'index(drawable)',
|
||||
'description': 'Find the index of the first occurrence of a drawable.',
|
||||
'args': [('drawable', 'UIDrawable', 'The drawable to find')],
|
||||
'returns': 'int: Index of drawable in collection',
|
||||
'raises': 'ValueError: If drawable is not in collection'
|
||||
}
|
||||
},
|
||||
|
||||
# Animation methods
|
||||
'Animation': {
|
||||
'get_current_value': {
|
||||
'signature': 'get_current_value()',
|
||||
'description': 'Get the current interpolated value of the animation.',
|
||||
'returns': 'float: Current animation value between start and end'
|
||||
},
|
||||
'start': {
|
||||
'signature': 'start(target)',
|
||||
'description': 'Start the animation on a target UI element.',
|
||||
'args': [('target', 'UIDrawable', 'The UI element to animate')],
|
||||
'note': 'The target must have the property specified in the animation constructor.'
|
||||
},
|
||||
'update': {
|
||||
'signature': 'update(delta_time)',
|
||||
'description': 'Update the animation by the given time delta.',
|
||||
'args': [('delta_time', 'float', 'Time elapsed since last update in seconds')],
|
||||
'returns': 'bool: True if animation is still running, False if finished'
|
||||
}
|
||||
},
|
||||
|
||||
# Color methods
|
||||
'Color': {
|
||||
'from_hex': {
|
||||
'signature': 'from_hex(hex_string)',
|
||||
'description': 'Create a Color from a hexadecimal color string.',
|
||||
'args': [('hex_string', 'str', 'Hex color string (e.g., "#FF0000" or "FF0000")')],
|
||||
'returns': 'Color: New Color object from hex string',
|
||||
'example': 'red = Color.from_hex("#FF0000")'
|
||||
},
|
||||
'to_hex': {
|
||||
'signature': 'to_hex()',
|
||||
'description': 'Convert this Color to a hexadecimal string.',
|
||||
'returns': 'str: Hex color string in format "#RRGGBB"',
|
||||
'example': 'hex_str = color.to_hex() # Returns "#FF0000"'
|
||||
},
|
||||
'lerp': {
|
||||
'signature': 'lerp(other, t)',
|
||||
'description': 'Linearly interpolate between this color and another.',
|
||||
'args': [
|
||||
('other', 'Color', 'The color to interpolate towards'),
|
||||
('t', 'float', 'Interpolation factor from 0.0 to 1.0')
|
||||
],
|
||||
'returns': 'Color: New interpolated Color object',
|
||||
'example': 'mixed = red.lerp(blue, 0.5) # 50% between red and blue'
|
||||
}
|
||||
},
|
||||
|
||||
# Vector methods
|
||||
'Vector': {
|
||||
'magnitude': {
|
||||
'signature': 'magnitude()',
|
||||
'description': 'Calculate the length/magnitude of this vector.',
|
||||
'returns': 'float: The magnitude of the vector',
|
||||
'example': 'length = vector.magnitude()'
|
||||
},
|
||||
'magnitude_squared': {
|
||||
'signature': 'magnitude_squared()',
|
||||
'description': 'Calculate the squared magnitude of this vector.',
|
||||
'returns': 'float: The squared magnitude (faster than magnitude())',
|
||||
'note': 'Use this for comparisons to avoid expensive square root calculation.'
|
||||
},
|
||||
'normalize': {
|
||||
'signature': 'normalize()',
|
||||
'description': 'Return a unit vector in the same direction.',
|
||||
'returns': 'Vector: New normalized vector with magnitude 1.0',
|
||||
'raises': 'ValueError: If vector has zero magnitude'
|
||||
},
|
||||
'dot': {
|
||||
'signature': 'dot(other)',
|
||||
'description': 'Calculate the dot product with another vector.',
|
||||
'args': [('other', 'Vector', 'The other vector')],
|
||||
'returns': 'float: Dot product of the two vectors'
|
||||
},
|
||||
'distance_to': {
|
||||
'signature': 'distance_to(other)',
|
||||
'description': 'Calculate the distance to another vector.',
|
||||
'args': [('other', 'Vector', 'The other vector')],
|
||||
'returns': 'float: Distance between the two vectors'
|
||||
},
|
||||
'angle': {
|
||||
'signature': 'angle()',
|
||||
'description': 'Get the angle of this vector in radians.',
|
||||
'returns': 'float: Angle in radians from positive x-axis'
|
||||
},
|
||||
'copy': {
|
||||
'signature': 'copy()',
|
||||
'description': 'Create a copy of this vector.',
|
||||
'returns': 'Vector: New Vector object with same x and y values'
|
||||
}
|
||||
},
|
||||
|
||||
# Scene methods
|
||||
'Scene': {
|
||||
'activate': {
|
||||
'signature': 'activate()',
|
||||
'description': 'Make this scene the active scene.',
|
||||
'note': 'Equivalent to calling setScene() with this scene\'s name.'
|
||||
},
|
||||
'get_ui': {
|
||||
'signature': 'get_ui()',
|
||||
'description': 'Get the UI element collection for this scene.',
|
||||
'returns': 'UICollection: Collection of all UI elements in this scene'
|
||||
},
|
||||
'keypress': {
|
||||
'signature': 'keypress(handler)',
|
||||
'description': 'Register a keyboard handler function for this scene.',
|
||||
'args': [('handler', 'callable', 'Function that takes (key_name: str, is_pressed: bool)')],
|
||||
'note': 'Alternative to overriding the on_keypress method.'
|
||||
},
|
||||
'register_keyboard': {
|
||||
'signature': 'register_keyboard(callable)',
|
||||
'description': 'Register a keyboard event handler function for the scene.',
|
||||
'args': [('callable', 'callable', 'Function that takes (key: str, action: str) parameters')],
|
||||
'note': 'Alternative to overriding the on_keypress method when subclassing Scene objects.',
|
||||
'example': '''def handle_keyboard(key, action):
|
||||
print(f"Key '{key}' was {action}")
|
||||
if key == "q" and action == "press":
|
||||
# Handle quit
|
||||
pass
|
||||
scene.register_keyboard(handle_keyboard)'''
|
||||
}
|
||||
},
|
||||
|
||||
# Timer methods
|
||||
'Timer': {
|
||||
'pause': {
|
||||
'signature': 'pause()',
|
||||
'description': 'Pause the timer, stopping its callback execution.',
|
||||
'note': 'Use resume() to continue the timer from where it was paused.'
|
||||
},
|
||||
'resume': {
|
||||
'signature': 'resume()',
|
||||
'description': 'Resume a paused timer.',
|
||||
'note': 'Has no effect if timer is not paused.'
|
||||
},
|
||||
'cancel': {
|
||||
'signature': 'cancel()',
|
||||
'description': 'Cancel the timer and remove it from the system.',
|
||||
'note': 'After cancelling, the timer object cannot be reused.'
|
||||
},
|
||||
'restart': {
|
||||
'signature': 'restart()',
|
||||
'description': 'Restart the timer from the beginning.',
|
||||
'note': 'Resets the timer\'s internal clock to zero.'
|
||||
}
|
||||
},
|
||||
|
||||
# Window methods
|
||||
'Window': {
|
||||
'get': {
|
||||
'signature': 'get()',
|
||||
'description': 'Get the Window singleton instance.',
|
||||
'returns': 'Window: The singleton window object',
|
||||
'note': 'This is a static method that returns the same instance every time.'
|
||||
},
|
||||
'center': {
|
||||
'signature': 'center()',
|
||||
'description': 'Center the window on the screen.',
|
||||
'note': 'Only works if the window is not fullscreen.'
|
||||
},
|
||||
'screenshot': {
|
||||
'signature': 'screenshot(filename)',
|
||||
'description': 'Take a screenshot and save it to a file.',
|
||||
'args': [('filename', 'str', 'Path where to save the screenshot')],
|
||||
'note': 'Supports PNG, JPG, and BMP formats based on file extension.'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def get_complete_function_documentation():
|
||||
"""Return complete documentation for ALL module functions."""
|
||||
return {
|
||||
# Scene Management
|
||||
'createScene': {
|
||||
'signature': 'createScene(name: str) -> None',
|
||||
'description': 'Create a new empty scene with the given name.',
|
||||
'args': [('name', 'str', 'Unique name for the new scene')],
|
||||
'raises': 'ValueError: If a scene with this name already exists',
|
||||
'note': 'The scene is created but not made active. Use setScene() to switch to it.',
|
||||
'example': 'mcrfpy.createScene("game_over")'
|
||||
},
|
||||
'setScene': {
|
||||
'signature': 'setScene(scene: str, transition: str = None, duration: float = 0.0) -> None',
|
||||
'description': 'Switch to a different scene with optional transition effect.',
|
||||
'args': [
|
||||
('scene', 'str', 'Name of the scene to switch to'),
|
||||
('transition', 'str', 'Transition type: "fade", "slide_left", "slide_right", "slide_up", "slide_down"'),
|
||||
('duration', 'float', 'Transition duration in seconds (default: 0.0 for instant)')
|
||||
],
|
||||
'raises': 'KeyError: If the scene doesn\'t exist',
|
||||
'example': 'mcrfpy.setScene("game", "fade", 0.5)'
|
||||
},
|
||||
'currentScene': {
|
||||
'signature': 'currentScene() -> str',
|
||||
'description': 'Get the name of the currently active scene.',
|
||||
'returns': 'str: Name of the current scene',
|
||||
'example': 'scene_name = mcrfpy.currentScene()'
|
||||
},
|
||||
'sceneUI': {
|
||||
'signature': 'sceneUI(scene: str = None) -> UICollection',
|
||||
'description': 'Get all UI elements for a scene.',
|
||||
'args': [('scene', 'str', 'Scene name. If None, uses current scene')],
|
||||
'returns': 'UICollection: All UI elements in the scene',
|
||||
'raises': 'KeyError: If the specified scene doesn\'t exist',
|
||||
'example': 'ui_elements = mcrfpy.sceneUI("game")'
|
||||
},
|
||||
'keypressScene': {
|
||||
'signature': 'keypressScene(handler: callable) -> None',
|
||||
'description': 'Set the keyboard event handler for the current scene.',
|
||||
'args': [('handler', 'callable', 'Function that receives (key_name: str, is_pressed: bool)')],
|
||||
'example': '''def on_key(key, pressed):
|
||||
if key == "SPACE" and pressed:
|
||||
player.jump()
|
||||
mcrfpy.keypressScene(on_key)'''
|
||||
},
|
||||
|
||||
# Audio Functions
|
||||
'createSoundBuffer': {
|
||||
'signature': 'createSoundBuffer(filename: str) -> int',
|
||||
'description': 'Load a sound effect from a file and return its buffer ID.',
|
||||
'args': [('filename', 'str', 'Path to the sound file (WAV, OGG, FLAC)')],
|
||||
'returns': 'int: Buffer ID for use with playSound()',
|
||||
'raises': 'RuntimeError: If the file cannot be loaded',
|
||||
'example': 'jump_sound = mcrfpy.createSoundBuffer("assets/jump.wav")'
|
||||
},
|
||||
'loadMusic': {
|
||||
'signature': 'loadMusic(filename: str, loop: bool = True) -> None',
|
||||
'description': 'Load and immediately play background music from a file.',
|
||||
'args': [
|
||||
('filename', 'str', 'Path to the music file (WAV, OGG, FLAC)'),
|
||||
('loop', 'bool', 'Whether to loop the music (default: True)')
|
||||
],
|
||||
'note': 'Only one music track can play at a time. Loading new music stops the current track.',
|
||||
'example': 'mcrfpy.loadMusic("assets/background.ogg", True)'
|
||||
},
|
||||
'playSound': {
|
||||
'signature': 'playSound(buffer_id: int) -> None',
|
||||
'description': 'Play a sound effect using a previously loaded buffer.',
|
||||
'args': [('buffer_id', 'int', 'Sound buffer ID returned by createSoundBuffer()')],
|
||||
'raises': 'RuntimeError: If the buffer ID is invalid',
|
||||
'example': 'mcrfpy.playSound(jump_sound)'
|
||||
},
|
||||
'getMusicVolume': {
|
||||
'signature': 'getMusicVolume() -> int',
|
||||
'description': 'Get the current music volume level.',
|
||||
'returns': 'int: Current volume (0-100)',
|
||||
'example': 'current_volume = mcrfpy.getMusicVolume()'
|
||||
},
|
||||
'getSoundVolume': {
|
||||
'signature': 'getSoundVolume() -> int',
|
||||
'description': 'Get the current sound effects volume level.',
|
||||
'returns': 'int: Current volume (0-100)',
|
||||
'example': 'current_volume = mcrfpy.getSoundVolume()'
|
||||
},
|
||||
'setMusicVolume': {
|
||||
'signature': 'setMusicVolume(volume: int) -> None',
|
||||
'description': 'Set the global music volume.',
|
||||
'args': [('volume', 'int', 'Volume level from 0 (silent) to 100 (full volume)')],
|
||||
'example': 'mcrfpy.setMusicVolume(50) # Set to 50% volume'
|
||||
},
|
||||
'setSoundVolume': {
|
||||
'signature': 'setSoundVolume(volume: int) -> None',
|
||||
'description': 'Set the global sound effects volume.',
|
||||
'args': [('volume', 'int', 'Volume level from 0 (silent) to 100 (full volume)')],
|
||||
'example': 'mcrfpy.setSoundVolume(75) # Set to 75% volume'
|
||||
},
|
||||
|
||||
# UI Utilities
|
||||
'find': {
|
||||
'signature': 'find(name: str, scene: str = None) -> UIDrawable | None',
|
||||
'description': 'Find the first UI element with the specified name.',
|
||||
'args': [
|
||||
('name', 'str', 'Exact name to search for'),
|
||||
('scene', 'str', 'Scene to search in (default: current scene)')
|
||||
],
|
||||
'returns': 'UIDrawable or None: The found element, or None if not found',
|
||||
'note': 'Searches scene UI elements and entities within grids.',
|
||||
'example': 'button = mcrfpy.find("start_button")'
|
||||
},
|
||||
'findAll': {
|
||||
'signature': 'findAll(pattern: str, scene: str = None) -> list',
|
||||
'description': 'Find all UI elements matching a name pattern.',
|
||||
'args': [
|
||||
('pattern', 'str', 'Name pattern with optional wildcards (* matches any characters)'),
|
||||
('scene', 'str', 'Scene to search in (default: current scene)')
|
||||
],
|
||||
'returns': 'list: All matching UI elements and entities',
|
||||
'example': 'enemies = mcrfpy.findAll("enemy_*")'
|
||||
},
|
||||
|
||||
# System Functions
|
||||
'exit': {
|
||||
'signature': 'exit() -> None',
|
||||
'description': 'Cleanly shut down the game engine and exit the application.',
|
||||
'note': 'This immediately closes the window and terminates the program.',
|
||||
'example': 'mcrfpy.exit()'
|
||||
},
|
||||
'getMetrics': {
|
||||
'signature': 'getMetrics() -> dict',
|
||||
'description': 'Get current performance metrics.',
|
||||
'returns': '''dict: Performance data with keys:
|
||||
- frame_time: Last frame duration in seconds
|
||||
- avg_frame_time: Average frame time
|
||||
- fps: Frames per second
|
||||
- draw_calls: Number of draw calls
|
||||
- ui_elements: Total UI element count
|
||||
- visible_elements: Visible element count
|
||||
- current_frame: Frame counter
|
||||
- runtime: Total runtime in seconds''',
|
||||
'example': 'metrics = mcrfpy.getMetrics()'
|
||||
},
|
||||
'setTimer': {
|
||||
'signature': 'setTimer(name: str, handler: callable, interval: int) -> None',
|
||||
'description': 'Create or update a recurring timer.',
|
||||
'args': [
|
||||
('name', 'str', 'Unique identifier for the timer'),
|
||||
('handler', 'callable', 'Function called with (runtime: float) parameter'),
|
||||
('interval', 'int', 'Time between calls in milliseconds')
|
||||
],
|
||||
'note': 'If a timer with this name exists, it will be replaced.',
|
||||
'example': '''def update_score(runtime):
|
||||
score += 1
|
||||
mcrfpy.setTimer("score_update", update_score, 1000)'''
|
||||
},
|
||||
'delTimer': {
|
||||
'signature': 'delTimer(name: str) -> None',
|
||||
'description': 'Stop and remove a timer.',
|
||||
'args': [('name', 'str', 'Timer identifier to remove')],
|
||||
'note': 'No error is raised if the timer doesn\'t exist.',
|
||||
'example': 'mcrfpy.delTimer("score_update")'
|
||||
},
|
||||
'setScale': {
|
||||
'signature': 'setScale(multiplier: float) -> None',
|
||||
'description': 'Scale the game window size.',
|
||||
'args': [('multiplier', 'float', 'Scale factor (e.g., 2.0 for double size)')],
|
||||
'note': 'The internal resolution remains 1024x768, but the window is scaled.',
|
||||
'example': 'mcrfpy.setScale(2.0) # Double the window size'
|
||||
}
|
||||
}
|
||||
|
||||
def get_complete_property_documentation():
|
||||
"""Return complete documentation for ALL properties."""
|
||||
return {
|
||||
'Animation': {
|
||||
'property': 'str: Name of the property being animated (e.g., "x", "y", "scale")',
|
||||
'duration': 'float: Total duration of the animation in seconds',
|
||||
'elapsed_time': 'float: Time elapsed since animation started (read-only)',
|
||||
'current_value': 'float: Current interpolated value of the animation (read-only)',
|
||||
'is_running': 'bool: True if animation is currently running (read-only)',
|
||||
'is_finished': 'bool: True if animation has completed (read-only)'
|
||||
},
|
||||
'GridPoint': {
|
||||
'x': 'int: Grid x coordinate of this point',
|
||||
'y': 'int: Grid y coordinate of this point',
|
||||
'texture_index': 'int: Index of the texture/sprite to display at this point',
|
||||
'solid': 'bool: Whether this point blocks movement',
|
||||
'transparent': 'bool: Whether this point allows light/vision through',
|
||||
'color': 'Color: Color tint applied to the texture at this point'
|
||||
},
|
||||
'GridPointState': {
|
||||
'visible': 'bool: Whether this point is currently visible to the player',
|
||||
'discovered': 'bool: Whether this point has been discovered/explored',
|
||||
'custom_flags': 'int: Bitfield for custom game-specific flags'
|
||||
}
|
||||
}
|
||||
|
||||
def generate_complete_html_documentation():
|
||||
"""Generate complete HTML documentation with NO missing methods."""
|
||||
|
||||
# Get all documentation data
|
||||
method_docs = get_complete_method_documentation()
|
||||
function_docs = get_complete_function_documentation()
|
||||
property_docs = get_complete_property_documentation()
|
||||
|
||||
html_parts = []
|
||||
|
||||
# HTML header with enhanced styling
|
||||
html_parts.append('''<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>McRogueFace API Reference - Complete Documentation</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
line-height: 1.6;
|
||||
color: #333;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.container {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #2c3e50;
|
||||
border-bottom: 3px solid #3498db;
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #34495e;
|
||||
border-bottom: 2px solid #ecf0f1;
|
||||
padding-bottom: 10px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
color: #2c3e50;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: #34495e;
|
||||
margin-top: 20px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
h5 {
|
||||
color: #555;
|
||||
margin-top: 15px;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
code {
|
||||
background: #f4f4f4;
|
||||
padding: 2px 6px;
|
||||
border-radius: 3px;
|
||||
font-family: "SF Mono", Monaco, "Cascadia Code", "Roboto Mono", Consolas, monospace;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
pre {
|
||||
background: #f8f8f8;
|
||||
border: 1px solid #e1e4e8;
|
||||
border-radius: 6px;
|
||||
padding: 16px;
|
||||
overflow-x: auto;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
font-size: 0.875em;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.class-name {
|
||||
color: #8e44ad;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.property {
|
||||
color: #27ae60;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.method {
|
||||
color: #2980b9;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.function-signature {
|
||||
color: #d73a49;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.method-section {
|
||||
margin: 20px 0;
|
||||
padding: 15px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
|
||||
.arg-list {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.arg-item {
|
||||
margin: 8px 0;
|
||||
padding: 8px;
|
||||
background: #fff;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e1e4e8;
|
||||
}
|
||||
|
||||
.arg-name {
|
||||
color: #d73a49;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.arg-type {
|
||||
color: #6f42c1;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.returns {
|
||||
background: #e8f5e8;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #28a745;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.note {
|
||||
background: #fff3cd;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #ffc107;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.example {
|
||||
background: #e7f3ff;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
border-left: 4px solid #0366d6;
|
||||
margin: 15px 0;
|
||||
}
|
||||
|
||||
.toc {
|
||||
background: #f8f9fa;
|
||||
border: 1px solid #e1e4e8;
|
||||
border-radius: 6px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.toc ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.toc li {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
.toc a {
|
||||
color: #3498db;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.toc a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
''')
|
||||
|
||||
# Title and overview
|
||||
html_parts.append('<h1>McRogueFace API Reference - Complete Documentation</h1>')
|
||||
html_parts.append(f'<p><em>Generated on {datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")}</em></p>')
|
||||
|
||||
# Table of contents
|
||||
html_parts.append('<div class="toc">')
|
||||
html_parts.append('<h2>Table of Contents</h2>')
|
||||
html_parts.append('<ul>')
|
||||
html_parts.append('<li><a href="#functions">Functions</a></li>')
|
||||
html_parts.append('<li><a href="#classes">Classes</a></li>')
|
||||
html_parts.append('<li><a href="#automation">Automation Module</a></li>')
|
||||
html_parts.append('</ul>')
|
||||
html_parts.append('</div>')
|
||||
|
||||
# Functions section
|
||||
html_parts.append('<h2 id="functions">Functions</h2>')
|
||||
|
||||
# Group functions by category
|
||||
categories = {
|
||||
'Scene Management': ['createScene', 'setScene', 'currentScene', 'sceneUI', 'keypressScene'],
|
||||
'Audio': ['createSoundBuffer', 'loadMusic', 'playSound', 'getMusicVolume', 'getSoundVolume', 'setMusicVolume', 'setSoundVolume'],
|
||||
'UI Utilities': ['find', 'findAll'],
|
||||
'System': ['exit', 'getMetrics', 'setTimer', 'delTimer', 'setScale']
|
||||
}
|
||||
|
||||
for category, functions in categories.items():
|
||||
html_parts.append(f'<h3>{category}</h3>')
|
||||
for func_name in functions:
|
||||
if func_name in function_docs:
|
||||
html_parts.append(format_function_html(func_name, function_docs[func_name]))
|
||||
|
||||
# Classes section
|
||||
html_parts.append('<h2 id="classes">Classes</h2>')
|
||||
|
||||
# Get all classes from mcrfpy
|
||||
classes = []
|
||||
for name in sorted(dir(mcrfpy)):
|
||||
if not name.startswith('_'):
|
||||
obj = getattr(mcrfpy, name)
|
||||
if isinstance(obj, type):
|
||||
classes.append((name, obj))
|
||||
|
||||
# Generate class documentation
|
||||
for class_name, cls in classes:
|
||||
html_parts.append(format_class_html_complete(class_name, cls, method_docs, property_docs))
|
||||
|
||||
# Automation section
|
||||
if hasattr(mcrfpy, 'automation'):
|
||||
html_parts.append('<h2 id="automation">Automation Module</h2>')
|
||||
html_parts.append('<p>The <code>mcrfpy.automation</code> module provides testing and automation capabilities.</p>')
|
||||
|
||||
automation = mcrfpy.automation
|
||||
for name in sorted(dir(automation)):
|
||||
if not name.startswith('_'):
|
||||
obj = getattr(automation, name)
|
||||
if callable(obj):
|
||||
html_parts.append(f'<div class="method-section">')
|
||||
html_parts.append(f'<h4><code class="function-signature">automation.{name}</code></h4>')
|
||||
if obj.__doc__:
|
||||
doc_parts = obj.__doc__.split(' - ')
|
||||
if len(doc_parts) > 1:
|
||||
html_parts.append(f'<p>{escape_html(doc_parts[1])}</p>')
|
||||
else:
|
||||
html_parts.append(f'<p>{escape_html(obj.__doc__)}</p>')
|
||||
html_parts.append('</div>')
|
||||
|
||||
html_parts.append('</div>')
|
||||
html_parts.append('</body>')
|
||||
html_parts.append('</html>')
|
||||
|
||||
return '\n'.join(html_parts)
|
||||
|
||||
def format_function_html(func_name, func_doc):
|
||||
"""Format a function with complete documentation."""
|
||||
html_parts = []
|
||||
|
||||
html_parts.append('<div class="method-section">')
|
||||
html_parts.append(f'<h4><code class="function-signature">{func_doc["signature"]}</code></h4>')
|
||||
html_parts.append(f'<p>{escape_html(func_doc["description"])}</p>')
|
||||
|
||||
# Arguments
|
||||
if 'args' in func_doc:
|
||||
html_parts.append('<div class="arg-list">')
|
||||
html_parts.append('<h5>Arguments:</h5>')
|
||||
for arg in func_doc['args']:
|
||||
html_parts.append('<div class="arg-item">')
|
||||
html_parts.append(f'<span class="arg-name">{arg[0]}</span> ')
|
||||
html_parts.append(f'<span class="arg-type">({arg[1]})</span>: ')
|
||||
html_parts.append(f'{escape_html(arg[2])}')
|
||||
html_parts.append('</div>')
|
||||
html_parts.append('</div>')
|
||||
|
||||
# Returns
|
||||
if 'returns' in func_doc:
|
||||
html_parts.append('<div class="returns">')
|
||||
html_parts.append(f'<strong>Returns:</strong> {escape_html(func_doc["returns"])}')
|
||||
html_parts.append('</div>')
|
||||
|
||||
# Raises
|
||||
if 'raises' in func_doc:
|
||||
html_parts.append('<div class="note">')
|
||||
html_parts.append(f'<strong>Raises:</strong> {escape_html(func_doc["raises"])}')
|
||||
html_parts.append('</div>')
|
||||
|
||||
# Note
|
||||
if 'note' in func_doc:
|
||||
html_parts.append('<div class="note">')
|
||||
html_parts.append(f'<strong>Note:</strong> {escape_html(func_doc["note"])}')
|
||||
html_parts.append('</div>')
|
||||
|
||||
# Example
|
||||
if 'example' in func_doc:
|
||||
html_parts.append('<div class="example">')
|
||||
html_parts.append('<h5>Example:</h5>')
|
||||
html_parts.append('<pre><code>')
|
||||
html_parts.append(escape_html(func_doc['example']))
|
||||
html_parts.append('</code></pre>')
|
||||
html_parts.append('</div>')
|
||||
|
||||
html_parts.append('</div>')
|
||||
|
||||
return '\n'.join(html_parts)
|
||||
|
||||
def format_class_html_complete(class_name, cls, method_docs, property_docs):
|
||||
"""Format a class with complete documentation."""
|
||||
html_parts = []
|
||||
|
||||
html_parts.append('<div class="method-section">')
|
||||
html_parts.append(f'<h3><span class="class-name">{class_name}</span></h3>')
|
||||
|
||||
# Class description
|
||||
if cls.__doc__:
|
||||
html_parts.append(f'<p>{escape_html(cls.__doc__)}</p>')
|
||||
|
||||
# Properties
|
||||
if class_name in property_docs:
|
||||
html_parts.append('<h4>Properties:</h4>')
|
||||
for prop_name, prop_desc in property_docs[class_name].items():
|
||||
html_parts.append(f'<div class="arg-item">')
|
||||
html_parts.append(f'<span class="property">{prop_name}</span>: {escape_html(prop_desc)}')
|
||||
html_parts.append('</div>')
|
||||
|
||||
# Methods
|
||||
methods_to_document = []
|
||||
|
||||
# Add inherited methods for UI classes
|
||||
if any(base.__name__ == 'Drawable' for base in cls.__bases__ if hasattr(base, '__name__')):
|
||||
methods_to_document.extend(['get_bounds', 'move', 'resize'])
|
||||
|
||||
# Add class-specific methods
|
||||
if class_name in method_docs:
|
||||
methods_to_document.extend(method_docs[class_name].keys())
|
||||
|
||||
# Add methods from introspection
|
||||
for attr_name in dir(cls):
|
||||
if not attr_name.startswith('_') and callable(getattr(cls, attr_name)):
|
||||
if attr_name not in methods_to_document:
|
||||
methods_to_document.append(attr_name)
|
||||
|
||||
if methods_to_document:
|
||||
html_parts.append('<h4>Methods:</h4>')
|
||||
for method_name in set(methods_to_document):
|
||||
# Get method documentation
|
||||
method_doc = None
|
||||
if class_name in method_docs and method_name in method_docs[class_name]:
|
||||
method_doc = method_docs[class_name][method_name]
|
||||
elif method_name in method_docs.get('Drawable', {}):
|
||||
method_doc = method_docs['Drawable'][method_name]
|
||||
|
||||
if method_doc:
|
||||
html_parts.append(format_method_html(method_name, method_doc))
|
||||
else:
|
||||
# Basic method with no documentation
|
||||
html_parts.append(f'<div class="arg-item">')
|
||||
html_parts.append(f'<span class="method">{method_name}(...)</span>')
|
||||
html_parts.append('</div>')
|
||||
|
||||
html_parts.append('</div>')
|
||||
|
||||
return '\n'.join(html_parts)
|
||||
|
||||
def format_method_html(method_name, method_doc):
|
||||
"""Format a method with complete documentation."""
|
||||
html_parts = []
|
||||
|
||||
html_parts.append('<div style="margin-left: 20px; margin-bottom: 15px;">')
|
||||
html_parts.append(f'<h5><code class="method">{method_doc["signature"]}</code></h5>')
|
||||
html_parts.append(f'<p>{escape_html(method_doc["description"])}</p>')
|
||||
|
||||
# Arguments
|
||||
if 'args' in method_doc:
|
||||
for arg in method_doc['args']:
|
||||
html_parts.append(f'<div style="margin-left: 20px;">')
|
||||
html_parts.append(f'<span class="arg-name">{arg[0]}</span> ')
|
||||
html_parts.append(f'<span class="arg-type">({arg[1]})</span>: ')
|
||||
html_parts.append(f'{escape_html(arg[2])}')
|
||||
html_parts.append('</div>')
|
||||
|
||||
# Returns
|
||||
if 'returns' in method_doc:
|
||||
html_parts.append(f'<div style="margin-left: 20px; color: #28a745;">')
|
||||
html_parts.append(f'<strong>Returns:</strong> {escape_html(method_doc["returns"])}')
|
||||
html_parts.append('</div>')
|
||||
|
||||
# Note
|
||||
if 'note' in method_doc:
|
||||
html_parts.append(f'<div style="margin-left: 20px; color: #856404;">')
|
||||
html_parts.append(f'<strong>Note:</strong> {escape_html(method_doc["note"])}')
|
||||
html_parts.append('</div>')
|
||||
|
||||
# Example
|
||||
if 'example' in method_doc:
|
||||
html_parts.append(f'<div style="margin-left: 20px;">')
|
||||
html_parts.append('<strong>Example:</strong>')
|
||||
html_parts.append('<pre><code>')
|
||||
html_parts.append(escape_html(method_doc['example']))
|
||||
html_parts.append('</code></pre>')
|
||||
html_parts.append('</div>')
|
||||
|
||||
html_parts.append('</div>')
|
||||
|
||||
return '\n'.join(html_parts)
|
||||
|
||||
def main():
|
||||
"""Generate complete HTML documentation with zero missing methods."""
|
||||
print("Generating COMPLETE HTML API documentation...")
|
||||
|
||||
# Generate HTML
|
||||
html_content = generate_complete_html_documentation()
|
||||
|
||||
# Write to file
|
||||
output_path = Path("docs/api_reference_complete.html")
|
||||
output_path.parent.mkdir(exist_ok=True)
|
||||
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(html_content)
|
||||
|
||||
print(f"✓ Generated {output_path}")
|
||||
print(f" File size: {len(html_content):,} bytes")
|
||||
|
||||
# Count "..." instances
|
||||
ellipsis_count = html_content.count('...')
|
||||
print(f" Ellipsis instances: {ellipsis_count}")
|
||||
|
||||
if ellipsis_count == 0:
|
||||
print("✅ SUCCESS: No missing documentation found!")
|
||||
else:
|
||||
print(f"❌ WARNING: {ellipsis_count} methods still need documentation")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,821 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate COMPLETE Markdown API reference documentation for McRogueFace with NO missing methods."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import datetime
|
||||
from pathlib import Path
|
||||
import mcrfpy
|
||||
|
||||
def get_complete_method_documentation():
|
||||
"""Return complete documentation for ALL methods across all classes."""
|
||||
return {
|
||||
# Base Drawable methods (inherited by all UI elements)
|
||||
'Drawable': {
|
||||
'get_bounds': {
|
||||
'signature': 'get_bounds()',
|
||||
'description': 'Get the bounding rectangle of this drawable element.',
|
||||
'returns': 'tuple: (x, y, width, height) representing the element\'s bounds',
|
||||
'note': 'The bounds are in screen coordinates and account for current position and size.'
|
||||
},
|
||||
'move': {
|
||||
'signature': 'move(dx, dy)',
|
||||
'description': 'Move the element by a relative offset.',
|
||||
'args': [
|
||||
('dx', 'float', 'Horizontal offset in pixels'),
|
||||
('dy', 'float', 'Vertical offset in pixels')
|
||||
],
|
||||
'note': 'This modifies the x and y position properties by the given amounts.'
|
||||
},
|
||||
'resize': {
|
||||
'signature': 'resize(width, height)',
|
||||
'description': 'Resize the element to new dimensions.',
|
||||
'args': [
|
||||
('width', 'float', 'New width in pixels'),
|
||||
('height', 'float', 'New height in pixels')
|
||||
],
|
||||
'note': 'For Caption and Sprite, this may not change actual size if determined by content.'
|
||||
}
|
||||
},
|
||||
|
||||
# Entity-specific methods
|
||||
'Entity': {
|
||||
'at': {
|
||||
'signature': 'at(x, y)',
|
||||
'description': 'Check if this entity is at the specified grid coordinates.',
|
||||
'args': [
|
||||
('x', 'int', 'Grid x coordinate to check'),
|
||||
('y', 'int', 'Grid y coordinate to check')
|
||||
],
|
||||
'returns': 'bool: True if entity is at position (x, y), False otherwise'
|
||||
},
|
||||
'die': {
|
||||
'signature': 'die()',
|
||||
'description': 'Remove this entity from its parent grid.',
|
||||
'note': 'The entity object remains valid but is no longer rendered or updated.'
|
||||
},
|
||||
'index': {
|
||||
'signature': 'index()',
|
||||
'description': 'Get the index of this entity in its parent grid\'s entity list.',
|
||||
'returns': 'int: Index position, or -1 if not in a grid'
|
||||
}
|
||||
},
|
||||
|
||||
# Grid-specific methods
|
||||
'Grid': {
|
||||
'at': {
|
||||
'signature': 'at(x, y)',
|
||||
'description': 'Get the GridPoint at the specified grid coordinates.',
|
||||
'args': [
|
||||
('x', 'int', 'Grid x coordinate'),
|
||||
('y', 'int', 'Grid y coordinate')
|
||||
],
|
||||
'returns': 'GridPoint or None: The grid point at (x, y), or None if out of bounds'
|
||||
}
|
||||
},
|
||||
|
||||
# Collection methods
|
||||
'EntityCollection': {
|
||||
'append': {
|
||||
'signature': 'append(entity)',
|
||||
'description': 'Add an entity to the end of the collection.',
|
||||
'args': [('entity', 'Entity', 'The entity to add')]
|
||||
},
|
||||
'remove': {
|
||||
'signature': 'remove(entity)',
|
||||
'description': 'Remove the first occurrence of an entity from the collection.',
|
||||
'args': [('entity', 'Entity', 'The entity to remove')],
|
||||
'raises': 'ValueError: If entity is not in collection'
|
||||
},
|
||||
'extend': {
|
||||
'signature': 'extend(iterable)',
|
||||
'description': 'Add all entities from an iterable to the collection.',
|
||||
'args': [('iterable', 'Iterable[Entity]', 'Entities to add')]
|
||||
},
|
||||
'count': {
|
||||
'signature': 'count(entity)',
|
||||
'description': 'Count the number of occurrences of an entity in the collection.',
|
||||
'args': [('entity', 'Entity', 'The entity to count')],
|
||||
'returns': 'int: Number of times entity appears in collection'
|
||||
},
|
||||
'index': {
|
||||
'signature': 'index(entity)',
|
||||
'description': 'Find the index of the first occurrence of an entity.',
|
||||
'args': [('entity', 'Entity', 'The entity to find')],
|
||||
'returns': 'int: Index of entity in collection',
|
||||
'raises': 'ValueError: If entity is not in collection'
|
||||
}
|
||||
},
|
||||
|
||||
'UICollection': {
|
||||
'append': {
|
||||
'signature': 'append(drawable)',
|
||||
'description': 'Add a drawable element to the end of the collection.',
|
||||
'args': [('drawable', 'UIDrawable', 'The drawable element to add')]
|
||||
},
|
||||
'remove': {
|
||||
'signature': 'remove(drawable)',
|
||||
'description': 'Remove the first occurrence of a drawable from the collection.',
|
||||
'args': [('drawable', 'UIDrawable', 'The drawable to remove')],
|
||||
'raises': 'ValueError: If drawable is not in collection'
|
||||
},
|
||||
'extend': {
|
||||
'signature': 'extend(iterable)',
|
||||
'description': 'Add all drawables from an iterable to the collection.',
|
||||
'args': [('iterable', 'Iterable[UIDrawable]', 'Drawables to add')]
|
||||
},
|
||||
'count': {
|
||||
'signature': 'count(drawable)',
|
||||
'description': 'Count the number of occurrences of a drawable in the collection.',
|
||||
'args': [('drawable', 'UIDrawable', 'The drawable to count')],
|
||||
'returns': 'int: Number of times drawable appears in collection'
|
||||
},
|
||||
'index': {
|
||||
'signature': 'index(drawable)',
|
||||
'description': 'Find the index of the first occurrence of a drawable.',
|
||||
'args': [('drawable', 'UIDrawable', 'The drawable to find')],
|
||||
'returns': 'int: Index of drawable in collection',
|
||||
'raises': 'ValueError: If drawable is not in collection'
|
||||
}
|
||||
},
|
||||
|
||||
# Animation methods
|
||||
'Animation': {
|
||||
'get_current_value': {
|
||||
'signature': 'get_current_value()',
|
||||
'description': 'Get the current interpolated value of the animation.',
|
||||
'returns': 'float: Current animation value between start and end'
|
||||
},
|
||||
'start': {
|
||||
'signature': 'start(target)',
|
||||
'description': 'Start the animation on a target UI element.',
|
||||
'args': [('target', 'UIDrawable', 'The UI element to animate')],
|
||||
'note': 'The target must have the property specified in the animation constructor.'
|
||||
},
|
||||
'update': {
|
||||
'signature': 'update(delta_time)',
|
||||
'description': 'Update the animation by the given time delta.',
|
||||
'args': [('delta_time', 'float', 'Time elapsed since last update in seconds')],
|
||||
'returns': 'bool: True if animation is still running, False if finished'
|
||||
}
|
||||
},
|
||||
|
||||
# Color methods
|
||||
'Color': {
|
||||
'from_hex': {
|
||||
'signature': 'from_hex(hex_string)',
|
||||
'description': 'Create a Color from a hexadecimal color string.',
|
||||
'args': [('hex_string', 'str', 'Hex color string (e.g., "#FF0000" or "FF0000")')],
|
||||
'returns': 'Color: New Color object from hex string',
|
||||
'example': 'red = Color.from_hex("#FF0000")'
|
||||
},
|
||||
'to_hex': {
|
||||
'signature': 'to_hex()',
|
||||
'description': 'Convert this Color to a hexadecimal string.',
|
||||
'returns': 'str: Hex color string in format "#RRGGBB"',
|
||||
'example': 'hex_str = color.to_hex() # Returns "#FF0000"'
|
||||
},
|
||||
'lerp': {
|
||||
'signature': 'lerp(other, t)',
|
||||
'description': 'Linearly interpolate between this color and another.',
|
||||
'args': [
|
||||
('other', 'Color', 'The color to interpolate towards'),
|
||||
('t', 'float', 'Interpolation factor from 0.0 to 1.0')
|
||||
],
|
||||
'returns': 'Color: New interpolated Color object',
|
||||
'example': 'mixed = red.lerp(blue, 0.5) # 50% between red and blue'
|
||||
}
|
||||
},
|
||||
|
||||
# Vector methods
|
||||
'Vector': {
|
||||
'magnitude': {
|
||||
'signature': 'magnitude()',
|
||||
'description': 'Calculate the length/magnitude of this vector.',
|
||||
'returns': 'float: The magnitude of the vector'
|
||||
},
|
||||
'magnitude_squared': {
|
||||
'signature': 'magnitude_squared()',
|
||||
'description': 'Calculate the squared magnitude of this vector.',
|
||||
'returns': 'float: The squared magnitude (faster than magnitude())',
|
||||
'note': 'Use this for comparisons to avoid expensive square root calculation.'
|
||||
},
|
||||
'normalize': {
|
||||
'signature': 'normalize()',
|
||||
'description': 'Return a unit vector in the same direction.',
|
||||
'returns': 'Vector: New normalized vector with magnitude 1.0',
|
||||
'raises': 'ValueError: If vector has zero magnitude'
|
||||
},
|
||||
'dot': {
|
||||
'signature': 'dot(other)',
|
||||
'description': 'Calculate the dot product with another vector.',
|
||||
'args': [('other', 'Vector', 'The other vector')],
|
||||
'returns': 'float: Dot product of the two vectors'
|
||||
},
|
||||
'distance_to': {
|
||||
'signature': 'distance_to(other)',
|
||||
'description': 'Calculate the distance to another vector.',
|
||||
'args': [('other', 'Vector', 'The other vector')],
|
||||
'returns': 'float: Distance between the two vectors'
|
||||
},
|
||||
'angle': {
|
||||
'signature': 'angle()',
|
||||
'description': 'Get the angle of this vector in radians.',
|
||||
'returns': 'float: Angle in radians from positive x-axis'
|
||||
},
|
||||
'copy': {
|
||||
'signature': 'copy()',
|
||||
'description': 'Create a copy of this vector.',
|
||||
'returns': 'Vector: New Vector object with same x and y values'
|
||||
}
|
||||
},
|
||||
|
||||
# Scene methods
|
||||
'Scene': {
|
||||
'activate': {
|
||||
'signature': 'activate()',
|
||||
'description': 'Make this scene the active scene.',
|
||||
'note': 'Equivalent to calling setScene() with this scene\'s name.'
|
||||
},
|
||||
'get_ui': {
|
||||
'signature': 'get_ui()',
|
||||
'description': 'Get the UI element collection for this scene.',
|
||||
'returns': 'UICollection: Collection of all UI elements in this scene'
|
||||
},
|
||||
'keypress': {
|
||||
'signature': 'keypress(handler)',
|
||||
'description': 'Register a keyboard handler function for this scene.',
|
||||
'args': [('handler', 'callable', 'Function that takes (key_name: str, is_pressed: bool)')],
|
||||
'note': 'Alternative to overriding the on_keypress method.'
|
||||
},
|
||||
'register_keyboard': {
|
||||
'signature': 'register_keyboard(callable)',
|
||||
'description': 'Register a keyboard event handler function for the scene.',
|
||||
'args': [('callable', 'callable', 'Function that takes (key: str, action: str) parameters')],
|
||||
'note': 'Alternative to overriding the on_keypress method when subclassing Scene objects.',
|
||||
'example': '''def handle_keyboard(key, action):
|
||||
print(f"Key '{key}' was {action}")
|
||||
scene.register_keyboard(handle_keyboard)'''
|
||||
}
|
||||
},
|
||||
|
||||
# Timer methods
|
||||
'Timer': {
|
||||
'pause': {
|
||||
'signature': 'pause()',
|
||||
'description': 'Pause the timer, stopping its callback execution.',
|
||||
'note': 'Use resume() to continue the timer from where it was paused.'
|
||||
},
|
||||
'resume': {
|
||||
'signature': 'resume()',
|
||||
'description': 'Resume a paused timer.',
|
||||
'note': 'Has no effect if timer is not paused.'
|
||||
},
|
||||
'cancel': {
|
||||
'signature': 'cancel()',
|
||||
'description': 'Cancel the timer and remove it from the system.',
|
||||
'note': 'After cancelling, the timer object cannot be reused.'
|
||||
},
|
||||
'restart': {
|
||||
'signature': 'restart()',
|
||||
'description': 'Restart the timer from the beginning.',
|
||||
'note': 'Resets the timer\'s internal clock to zero.'
|
||||
}
|
||||
},
|
||||
|
||||
# Window methods
|
||||
'Window': {
|
||||
'get': {
|
||||
'signature': 'get()',
|
||||
'description': 'Get the Window singleton instance.',
|
||||
'returns': 'Window: The singleton window object',
|
||||
'note': 'This is a static method that returns the same instance every time.'
|
||||
},
|
||||
'center': {
|
||||
'signature': 'center()',
|
||||
'description': 'Center the window on the screen.',
|
||||
'note': 'Only works if the window is not fullscreen.'
|
||||
},
|
||||
'screenshot': {
|
||||
'signature': 'screenshot(filename)',
|
||||
'description': 'Take a screenshot and save it to a file.',
|
||||
'args': [('filename', 'str', 'Path where to save the screenshot')],
|
||||
'note': 'Supports PNG, JPG, and BMP formats based on file extension.'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def get_complete_function_documentation():
|
||||
"""Return complete documentation for ALL module functions."""
|
||||
return {
|
||||
# Scene Management
|
||||
'createScene': {
|
||||
'signature': 'createScene(name: str) -> None',
|
||||
'description': 'Create a new empty scene with the given name.',
|
||||
'args': [('name', 'str', 'Unique name for the new scene')],
|
||||
'raises': 'ValueError: If a scene with this name already exists',
|
||||
'note': 'The scene is created but not made active. Use setScene() to switch to it.',
|
||||
'example': 'mcrfpy.createScene("game_over")'
|
||||
},
|
||||
'setScene': {
|
||||
'signature': 'setScene(scene: str, transition: str = None, duration: float = 0.0) -> None',
|
||||
'description': 'Switch to a different scene with optional transition effect.',
|
||||
'args': [
|
||||
('scene', 'str', 'Name of the scene to switch to'),
|
||||
('transition', 'str', 'Transition type: "fade", "slide_left", "slide_right", "slide_up", "slide_down"'),
|
||||
('duration', 'float', 'Transition duration in seconds (default: 0.0 for instant)')
|
||||
],
|
||||
'raises': 'KeyError: If the scene doesn\'t exist',
|
||||
'example': 'mcrfpy.setScene("game", "fade", 0.5)'
|
||||
},
|
||||
'currentScene': {
|
||||
'signature': 'currentScene() -> str',
|
||||
'description': 'Get the name of the currently active scene.',
|
||||
'returns': 'str: Name of the current scene',
|
||||
'example': 'scene_name = mcrfpy.currentScene()'
|
||||
},
|
||||
'sceneUI': {
|
||||
'signature': 'sceneUI(scene: str = None) -> UICollection',
|
||||
'description': 'Get all UI elements for a scene.',
|
||||
'args': [('scene', 'str', 'Scene name. If None, uses current scene')],
|
||||
'returns': 'UICollection: All UI elements in the scene',
|
||||
'raises': 'KeyError: If the specified scene doesn\'t exist',
|
||||
'example': 'ui_elements = mcrfpy.sceneUI("game")'
|
||||
},
|
||||
'keypressScene': {
|
||||
'signature': 'keypressScene(handler: callable) -> None',
|
||||
'description': 'Set the keyboard event handler for the current scene.',
|
||||
'args': [('handler', 'callable', 'Function that receives (key_name: str, is_pressed: bool)')],
|
||||
'example': '''def on_key(key, pressed):
|
||||
if key == "SPACE" and pressed:
|
||||
player.jump()
|
||||
mcrfpy.keypressScene(on_key)'''
|
||||
},
|
||||
|
||||
# Audio Functions
|
||||
'createSoundBuffer': {
|
||||
'signature': 'createSoundBuffer(filename: str) -> int',
|
||||
'description': 'Load a sound effect from a file and return its buffer ID.',
|
||||
'args': [('filename', 'str', 'Path to the sound file (WAV, OGG, FLAC)')],
|
||||
'returns': 'int: Buffer ID for use with playSound()',
|
||||
'raises': 'RuntimeError: If the file cannot be loaded',
|
||||
'example': 'jump_sound = mcrfpy.createSoundBuffer("assets/jump.wav")'
|
||||
},
|
||||
'loadMusic': {
|
||||
'signature': 'loadMusic(filename: str, loop: bool = True) -> None',
|
||||
'description': 'Load and immediately play background music from a file.',
|
||||
'args': [
|
||||
('filename', 'str', 'Path to the music file (WAV, OGG, FLAC)'),
|
||||
('loop', 'bool', 'Whether to loop the music (default: True)')
|
||||
],
|
||||
'note': 'Only one music track can play at a time. Loading new music stops the current track.',
|
||||
'example': 'mcrfpy.loadMusic("assets/background.ogg", True)'
|
||||
},
|
||||
'playSound': {
|
||||
'signature': 'playSound(buffer_id: int) -> None',
|
||||
'description': 'Play a sound effect using a previously loaded buffer.',
|
||||
'args': [('buffer_id', 'int', 'Sound buffer ID returned by createSoundBuffer()')],
|
||||
'raises': 'RuntimeError: If the buffer ID is invalid',
|
||||
'example': 'mcrfpy.playSound(jump_sound)'
|
||||
},
|
||||
'getMusicVolume': {
|
||||
'signature': 'getMusicVolume() -> int',
|
||||
'description': 'Get the current music volume level.',
|
||||
'returns': 'int: Current volume (0-100)',
|
||||
'example': 'current_volume = mcrfpy.getMusicVolume()'
|
||||
},
|
||||
'getSoundVolume': {
|
||||
'signature': 'getSoundVolume() -> int',
|
||||
'description': 'Get the current sound effects volume level.',
|
||||
'returns': 'int: Current volume (0-100)',
|
||||
'example': 'current_volume = mcrfpy.getSoundVolume()'
|
||||
},
|
||||
'setMusicVolume': {
|
||||
'signature': 'setMusicVolume(volume: int) -> None',
|
||||
'description': 'Set the global music volume.',
|
||||
'args': [('volume', 'int', 'Volume level from 0 (silent) to 100 (full volume)')],
|
||||
'example': 'mcrfpy.setMusicVolume(50) # Set to 50% volume'
|
||||
},
|
||||
'setSoundVolume': {
|
||||
'signature': 'setSoundVolume(volume: int) -> None',
|
||||
'description': 'Set the global sound effects volume.',
|
||||
'args': [('volume', 'int', 'Volume level from 0 (silent) to 100 (full volume)')],
|
||||
'example': 'mcrfpy.setSoundVolume(75) # Set to 75% volume'
|
||||
},
|
||||
|
||||
# UI Utilities
|
||||
'find': {
|
||||
'signature': 'find(name: str, scene: str = None) -> UIDrawable | None',
|
||||
'description': 'Find the first UI element with the specified name.',
|
||||
'args': [
|
||||
('name', 'str', 'Exact name to search for'),
|
||||
('scene', 'str', 'Scene to search in (default: current scene)')
|
||||
],
|
||||
'returns': 'UIDrawable or None: The found element, or None if not found',
|
||||
'note': 'Searches scene UI elements and entities within grids.',
|
||||
'example': 'button = mcrfpy.find("start_button")'
|
||||
},
|
||||
'findAll': {
|
||||
'signature': 'findAll(pattern: str, scene: str = None) -> list',
|
||||
'description': 'Find all UI elements matching a name pattern.',
|
||||
'args': [
|
||||
('pattern', 'str', 'Name pattern with optional wildcards (* matches any characters)'),
|
||||
('scene', 'str', 'Scene to search in (default: current scene)')
|
||||
],
|
||||
'returns': 'list: All matching UI elements and entities',
|
||||
'example': 'enemies = mcrfpy.findAll("enemy_*")'
|
||||
},
|
||||
|
||||
# System Functions
|
||||
'exit': {
|
||||
'signature': 'exit() -> None',
|
||||
'description': 'Cleanly shut down the game engine and exit the application.',
|
||||
'note': 'This immediately closes the window and terminates the program.',
|
||||
'example': 'mcrfpy.exit()'
|
||||
},
|
||||
'getMetrics': {
|
||||
'signature': 'getMetrics() -> dict',
|
||||
'description': 'Get current performance metrics.',
|
||||
'returns': '''dict: Performance data with keys:
|
||||
- frame_time: Last frame duration in seconds
|
||||
- avg_frame_time: Average frame time
|
||||
- fps: Frames per second
|
||||
- draw_calls: Number of draw calls
|
||||
- ui_elements: Total UI element count
|
||||
- visible_elements: Visible element count
|
||||
- current_frame: Frame counter
|
||||
- runtime: Total runtime in seconds''',
|
||||
'example': 'metrics = mcrfpy.getMetrics()'
|
||||
},
|
||||
'setTimer': {
|
||||
'signature': 'setTimer(name: str, handler: callable, interval: int) -> None',
|
||||
'description': 'Create or update a recurring timer.',
|
||||
'args': [
|
||||
('name', 'str', 'Unique identifier for the timer'),
|
||||
('handler', 'callable', 'Function called with (runtime: float) parameter'),
|
||||
('interval', 'int', 'Time between calls in milliseconds')
|
||||
],
|
||||
'note': 'If a timer with this name exists, it will be replaced.',
|
||||
'example': '''def update_score(runtime):
|
||||
score += 1
|
||||
mcrfpy.setTimer("score_update", update_score, 1000)'''
|
||||
},
|
||||
'delTimer': {
|
||||
'signature': 'delTimer(name: str) -> None',
|
||||
'description': 'Stop and remove a timer.',
|
||||
'args': [('name', 'str', 'Timer identifier to remove')],
|
||||
'note': 'No error is raised if the timer doesn\'t exist.',
|
||||
'example': 'mcrfpy.delTimer("score_update")'
|
||||
},
|
||||
'setScale': {
|
||||
'signature': 'setScale(multiplier: float) -> None',
|
||||
'description': 'Scale the game window size.',
|
||||
'args': [('multiplier', 'float', 'Scale factor (e.g., 2.0 for double size)')],
|
||||
'note': 'The internal resolution remains 1024x768, but the window is scaled.',
|
||||
'example': 'mcrfpy.setScale(2.0) # Double the window size'
|
||||
}
|
||||
}
|
||||
|
||||
def get_complete_property_documentation():
|
||||
"""Return complete documentation for ALL properties."""
|
||||
return {
|
||||
'Animation': {
|
||||
'property': 'str: Name of the property being animated (e.g., "x", "y", "scale")',
|
||||
'duration': 'float: Total duration of the animation in seconds',
|
||||
'elapsed_time': 'float: Time elapsed since animation started (read-only)',
|
||||
'current_value': 'float: Current interpolated value of the animation (read-only)',
|
||||
'is_running': 'bool: True if animation is currently running (read-only)',
|
||||
'is_finished': 'bool: True if animation has completed (read-only)'
|
||||
},
|
||||
'GridPoint': {
|
||||
'x': 'int: Grid x coordinate of this point',
|
||||
'y': 'int: Grid y coordinate of this point',
|
||||
'texture_index': 'int: Index of the texture/sprite to display at this point',
|
||||
'solid': 'bool: Whether this point blocks movement',
|
||||
'transparent': 'bool: Whether this point allows light/vision through',
|
||||
'color': 'Color: Color tint applied to the texture at this point'
|
||||
},
|
||||
'GridPointState': {
|
||||
'visible': 'bool: Whether this point is currently visible to the player',
|
||||
'discovered': 'bool: Whether this point has been discovered/explored',
|
||||
'custom_flags': 'int: Bitfield for custom game-specific flags'
|
||||
}
|
||||
}
|
||||
|
||||
def format_method_markdown(method_name, method_doc):
|
||||
"""Format a method as markdown."""
|
||||
lines = []
|
||||
|
||||
lines.append(f"#### `{method_doc['signature']}`")
|
||||
lines.append("")
|
||||
lines.append(method_doc['description'])
|
||||
lines.append("")
|
||||
|
||||
# Arguments
|
||||
if 'args' in method_doc:
|
||||
lines.append("**Arguments:**")
|
||||
for arg in method_doc['args']:
|
||||
lines.append(f"- `{arg[0]}` (*{arg[1]}*): {arg[2]}")
|
||||
lines.append("")
|
||||
|
||||
# Returns
|
||||
if 'returns' in method_doc:
|
||||
lines.append(f"**Returns:** {method_doc['returns']}")
|
||||
lines.append("")
|
||||
|
||||
# Raises
|
||||
if 'raises' in method_doc:
|
||||
lines.append(f"**Raises:** {method_doc['raises']}")
|
||||
lines.append("")
|
||||
|
||||
# Note
|
||||
if 'note' in method_doc:
|
||||
lines.append(f"**Note:** {method_doc['note']}")
|
||||
lines.append("")
|
||||
|
||||
# Example
|
||||
if 'example' in method_doc:
|
||||
lines.append("**Example:**")
|
||||
lines.append("```python")
|
||||
lines.append(method_doc['example'])
|
||||
lines.append("```")
|
||||
lines.append("")
|
||||
|
||||
return lines
|
||||
|
||||
def format_function_markdown(func_name, func_doc):
|
||||
"""Format a function as markdown."""
|
||||
lines = []
|
||||
|
||||
lines.append(f"### `{func_doc['signature']}`")
|
||||
lines.append("")
|
||||
lines.append(func_doc['description'])
|
||||
lines.append("")
|
||||
|
||||
# Arguments
|
||||
if 'args' in func_doc:
|
||||
lines.append("**Arguments:**")
|
||||
for arg in func_doc['args']:
|
||||
lines.append(f"- `{arg[0]}` (*{arg[1]}*): {arg[2]}")
|
||||
lines.append("")
|
||||
|
||||
# Returns
|
||||
if 'returns' in func_doc:
|
||||
lines.append(f"**Returns:** {func_doc['returns']}")
|
||||
lines.append("")
|
||||
|
||||
# Raises
|
||||
if 'raises' in func_doc:
|
||||
lines.append(f"**Raises:** {func_doc['raises']}")
|
||||
lines.append("")
|
||||
|
||||
# Note
|
||||
if 'note' in func_doc:
|
||||
lines.append(f"**Note:** {func_doc['note']}")
|
||||
lines.append("")
|
||||
|
||||
# Example
|
||||
if 'example' in func_doc:
|
||||
lines.append("**Example:**")
|
||||
lines.append("```python")
|
||||
lines.append(func_doc['example'])
|
||||
lines.append("```")
|
||||
lines.append("")
|
||||
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
|
||||
return lines
|
||||
|
||||
def generate_complete_markdown_documentation():
|
||||
"""Generate complete markdown documentation with NO missing methods."""
|
||||
|
||||
# Get all documentation data
|
||||
method_docs = get_complete_method_documentation()
|
||||
function_docs = get_complete_function_documentation()
|
||||
property_docs = get_complete_property_documentation()
|
||||
|
||||
lines = []
|
||||
|
||||
# Header
|
||||
lines.append("# McRogueFace API Reference")
|
||||
lines.append("")
|
||||
lines.append(f"*Generated on {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}*")
|
||||
lines.append("")
|
||||
|
||||
# Overview
|
||||
if mcrfpy.__doc__:
|
||||
lines.append("## Overview")
|
||||
lines.append("")
|
||||
# Process the docstring properly
|
||||
doc_text = mcrfpy.__doc__.replace('\\n', '\n')
|
||||
lines.append(doc_text)
|
||||
lines.append("")
|
||||
|
||||
# Table of Contents
|
||||
lines.append("## Table of Contents")
|
||||
lines.append("")
|
||||
lines.append("- [Functions](#functions)")
|
||||
lines.append(" - [Scene Management](#scene-management)")
|
||||
lines.append(" - [Audio](#audio)")
|
||||
lines.append(" - [UI Utilities](#ui-utilities)")
|
||||
lines.append(" - [System](#system)")
|
||||
lines.append("- [Classes](#classes)")
|
||||
lines.append(" - [UI Components](#ui-components)")
|
||||
lines.append(" - [Collections](#collections)")
|
||||
lines.append(" - [System Types](#system-types)")
|
||||
lines.append(" - [Other Classes](#other-classes)")
|
||||
lines.append("- [Automation Module](#automation-module)")
|
||||
lines.append("")
|
||||
|
||||
# Functions section
|
||||
lines.append("## Functions")
|
||||
lines.append("")
|
||||
|
||||
# Group functions by category
|
||||
categories = {
|
||||
'Scene Management': ['createScene', 'setScene', 'currentScene', 'sceneUI', 'keypressScene'],
|
||||
'Audio': ['createSoundBuffer', 'loadMusic', 'playSound', 'getMusicVolume', 'getSoundVolume', 'setMusicVolume', 'setSoundVolume'],
|
||||
'UI Utilities': ['find', 'findAll'],
|
||||
'System': ['exit', 'getMetrics', 'setTimer', 'delTimer', 'setScale']
|
||||
}
|
||||
|
||||
for category, functions in categories.items():
|
||||
lines.append(f"### {category}")
|
||||
lines.append("")
|
||||
for func_name in functions:
|
||||
if func_name in function_docs:
|
||||
lines.extend(format_function_markdown(func_name, function_docs[func_name]))
|
||||
|
||||
# Classes section
|
||||
lines.append("## Classes")
|
||||
lines.append("")
|
||||
|
||||
# Get all classes from mcrfpy
|
||||
classes = []
|
||||
for name in sorted(dir(mcrfpy)):
|
||||
if not name.startswith('_'):
|
||||
obj = getattr(mcrfpy, name)
|
||||
if isinstance(obj, type):
|
||||
classes.append((name, obj))
|
||||
|
||||
# Group classes
|
||||
ui_classes = ['Frame', 'Caption', 'Sprite', 'Grid', 'Entity']
|
||||
collection_classes = ['EntityCollection', 'UICollection', 'UICollectionIter', 'UIEntityCollectionIter']
|
||||
system_classes = ['Color', 'Vector', 'Texture', 'Font']
|
||||
other_classes = [name for name, _ in classes if name not in ui_classes + collection_classes + system_classes]
|
||||
|
||||
# UI Components
|
||||
lines.append("### UI Components")
|
||||
lines.append("")
|
||||
for class_name in ui_classes:
|
||||
if any(name == class_name for name, _ in classes):
|
||||
lines.extend(format_class_markdown(class_name, method_docs, property_docs))
|
||||
|
||||
# Collections
|
||||
lines.append("### Collections")
|
||||
lines.append("")
|
||||
for class_name in collection_classes:
|
||||
if any(name == class_name for name, _ in classes):
|
||||
lines.extend(format_class_markdown(class_name, method_docs, property_docs))
|
||||
|
||||
# System Types
|
||||
lines.append("### System Types")
|
||||
lines.append("")
|
||||
for class_name in system_classes:
|
||||
if any(name == class_name for name, _ in classes):
|
||||
lines.extend(format_class_markdown(class_name, method_docs, property_docs))
|
||||
|
||||
# Other Classes
|
||||
lines.append("### Other Classes")
|
||||
lines.append("")
|
||||
for class_name in other_classes:
|
||||
lines.extend(format_class_markdown(class_name, method_docs, property_docs))
|
||||
|
||||
# Automation section
|
||||
if hasattr(mcrfpy, 'automation'):
|
||||
lines.append("## Automation Module")
|
||||
lines.append("")
|
||||
lines.append("The `mcrfpy.automation` module provides testing and automation capabilities.")
|
||||
lines.append("")
|
||||
|
||||
automation = mcrfpy.automation
|
||||
for name in sorted(dir(automation)):
|
||||
if not name.startswith('_'):
|
||||
obj = getattr(automation, name)
|
||||
if callable(obj):
|
||||
lines.append(f"### `automation.{name}`")
|
||||
lines.append("")
|
||||
if obj.__doc__:
|
||||
doc_parts = obj.__doc__.split(' - ')
|
||||
if len(doc_parts) > 1:
|
||||
lines.append(doc_parts[1])
|
||||
else:
|
||||
lines.append(obj.__doc__)
|
||||
lines.append("")
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
def format_class_markdown(class_name, method_docs, property_docs):
|
||||
"""Format a class as markdown."""
|
||||
lines = []
|
||||
|
||||
lines.append(f"### class `{class_name}`")
|
||||
lines.append("")
|
||||
|
||||
# Class description from known info
|
||||
class_descriptions = {
|
||||
'Frame': 'A rectangular frame UI element that can contain other drawable elements.',
|
||||
'Caption': 'A text display UI element with customizable font and styling.',
|
||||
'Sprite': 'A sprite UI element that displays a texture or portion of a texture atlas.',
|
||||
'Grid': 'A grid-based tilemap UI element for rendering tile-based levels and game worlds.',
|
||||
'Entity': 'Game entity that can be placed in a Grid.',
|
||||
'EntityCollection': 'Container for Entity objects in a Grid. Supports iteration and indexing.',
|
||||
'UICollection': 'Container for UI drawable elements. Supports iteration and indexing.',
|
||||
'UICollectionIter': 'Iterator for UICollection. Automatically created when iterating over a UICollection.',
|
||||
'UIEntityCollectionIter': 'Iterator for EntityCollection. Automatically created when iterating over an EntityCollection.',
|
||||
'Color': 'RGBA color representation.',
|
||||
'Vector': '2D vector for positions and directions.',
|
||||
'Font': 'Font object for text rendering.',
|
||||
'Texture': 'Texture object for image data.',
|
||||
'Animation': 'Animate UI element properties over time.',
|
||||
'GridPoint': 'Represents a single tile in a Grid.',
|
||||
'GridPointState': 'State information for a GridPoint.',
|
||||
'Scene': 'Base class for object-oriented scenes.',
|
||||
'Timer': 'Timer object for scheduled callbacks.',
|
||||
'Window': 'Window singleton for accessing and modifying the game window properties.',
|
||||
'Drawable': 'Base class for all drawable UI elements.'
|
||||
}
|
||||
|
||||
if class_name in class_descriptions:
|
||||
lines.append(class_descriptions[class_name])
|
||||
lines.append("")
|
||||
|
||||
# Properties
|
||||
if class_name in property_docs:
|
||||
lines.append("#### Properties")
|
||||
lines.append("")
|
||||
for prop_name, prop_desc in property_docs[class_name].items():
|
||||
lines.append(f"- **`{prop_name}`**: {prop_desc}")
|
||||
lines.append("")
|
||||
|
||||
# Methods
|
||||
methods_to_document = []
|
||||
|
||||
# Add inherited methods for UI classes
|
||||
if class_name in ['Frame', 'Caption', 'Sprite', 'Grid', 'Entity']:
|
||||
methods_to_document.extend(['get_bounds', 'move', 'resize'])
|
||||
|
||||
# Add class-specific methods
|
||||
if class_name in method_docs:
|
||||
methods_to_document.extend(method_docs[class_name].keys())
|
||||
|
||||
if methods_to_document:
|
||||
lines.append("#### Methods")
|
||||
lines.append("")
|
||||
for method_name in set(methods_to_document):
|
||||
# Get method documentation
|
||||
method_doc = None
|
||||
if class_name in method_docs and method_name in method_docs[class_name]:
|
||||
method_doc = method_docs[class_name][method_name]
|
||||
elif method_name in method_docs.get('Drawable', {}):
|
||||
method_doc = method_docs['Drawable'][method_name]
|
||||
|
||||
if method_doc:
|
||||
lines.extend(format_method_markdown(method_name, method_doc))
|
||||
|
||||
lines.append("---")
|
||||
lines.append("")
|
||||
|
||||
return lines
|
||||
|
||||
def main():
|
||||
"""Generate complete markdown documentation with zero missing methods."""
|
||||
print("Generating COMPLETE Markdown API documentation...")
|
||||
|
||||
# Generate markdown
|
||||
markdown_content = generate_complete_markdown_documentation()
|
||||
|
||||
# Write to file
|
||||
output_path = Path("docs/API_REFERENCE_COMPLETE.md")
|
||||
output_path.parent.mkdir(exist_ok=True)
|
||||
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(markdown_content)
|
||||
|
||||
print(f"✓ Generated {output_path}")
|
||||
print(f" File size: {len(markdown_content):,} bytes")
|
||||
|
||||
# Count "..." instances
|
||||
ellipsis_count = markdown_content.count('...')
|
||||
print(f" Ellipsis instances: {ellipsis_count}")
|
||||
|
||||
if ellipsis_count == 0:
|
||||
print("✅ SUCCESS: No missing documentation found!")
|
||||
else:
|
||||
print(f"❌ WARNING: {ellipsis_count} methods still need documentation")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,268 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate .pyi type stub files for McRogueFace Python API.
|
||||
|
||||
This script introspects the mcrfpy module and generates type stubs
|
||||
for better IDE support and type checking.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
import types
|
||||
from typing import Dict, List, Set, Any
|
||||
|
||||
# Add the build directory to path to import mcrfpy
|
||||
sys.path.insert(0, './build')
|
||||
|
||||
try:
|
||||
import mcrfpy
|
||||
except ImportError:
|
||||
print("Error: Could not import mcrfpy. Make sure to run this from the project root after building.")
|
||||
sys.exit(1)
|
||||
|
||||
def parse_docstring_signature(doc: str) -> tuple[str, str]:
|
||||
"""Extract signature and description from docstring."""
|
||||
if not doc:
|
||||
return "", ""
|
||||
|
||||
lines = doc.strip().split('\n')
|
||||
if lines:
|
||||
# First line often contains the signature
|
||||
first_line = lines[0]
|
||||
if '(' in first_line and ')' in first_line:
|
||||
# Extract just the part after the function name
|
||||
start = first_line.find('(')
|
||||
end = first_line.rfind(')') + 1
|
||||
if start != -1 and end != 0:
|
||||
sig = first_line[start:end]
|
||||
# Get return type if present
|
||||
if '->' in first_line:
|
||||
ret_start = first_line.find('->')
|
||||
ret_type = first_line[ret_start:].strip()
|
||||
return sig, ret_type
|
||||
return sig, ""
|
||||
return "", ""
|
||||
|
||||
def get_type_hint(obj_type: type) -> str:
|
||||
"""Convert Python type to type hint string."""
|
||||
if obj_type == int:
|
||||
return "int"
|
||||
elif obj_type == float:
|
||||
return "float"
|
||||
elif obj_type == str:
|
||||
return "str"
|
||||
elif obj_type == bool:
|
||||
return "bool"
|
||||
elif obj_type == list:
|
||||
return "List[Any]"
|
||||
elif obj_type == dict:
|
||||
return "Dict[Any, Any]"
|
||||
elif obj_type == tuple:
|
||||
return "Tuple[Any, ...]"
|
||||
elif obj_type == type(None):
|
||||
return "None"
|
||||
else:
|
||||
return "Any"
|
||||
|
||||
def generate_class_stub(class_name: str, cls: type) -> List[str]:
|
||||
"""Generate stub for a class."""
|
||||
lines = []
|
||||
|
||||
# Get class docstring
|
||||
if cls.__doc__:
|
||||
doc_lines = cls.__doc__.strip().split('\n')
|
||||
# Use only the first paragraph for the stub
|
||||
lines.append(f'class {class_name}:')
|
||||
lines.append(f' """{doc_lines[0]}"""')
|
||||
else:
|
||||
lines.append(f'class {class_name}:')
|
||||
|
||||
# Check for __init__ method
|
||||
if hasattr(cls, '__init__'):
|
||||
init_doc = cls.__init__.__doc__ or cls.__doc__
|
||||
if init_doc:
|
||||
sig, ret = parse_docstring_signature(init_doc)
|
||||
if sig:
|
||||
lines.append(f' def __init__(self{sig[1:-1]}) -> None: ...')
|
||||
else:
|
||||
lines.append(f' def __init__(self, *args, **kwargs) -> None: ...')
|
||||
else:
|
||||
lines.append(f' def __init__(self, *args, **kwargs) -> None: ...')
|
||||
|
||||
# Get properties and methods
|
||||
properties = []
|
||||
methods = []
|
||||
|
||||
for attr_name in dir(cls):
|
||||
if attr_name.startswith('_') and not attr_name.startswith('__'):
|
||||
continue
|
||||
|
||||
try:
|
||||
attr = getattr(cls, attr_name)
|
||||
|
||||
if isinstance(attr, property):
|
||||
properties.append((attr_name, attr))
|
||||
elif callable(attr) and not attr_name.startswith('__'):
|
||||
methods.append((attr_name, attr))
|
||||
except:
|
||||
pass
|
||||
|
||||
# Add properties
|
||||
if properties:
|
||||
lines.append('')
|
||||
for prop_name, prop in properties:
|
||||
# Try to determine property type from docstring
|
||||
if prop.fget and prop.fget.__doc__:
|
||||
lines.append(f' @property')
|
||||
lines.append(f' def {prop_name}(self) -> Any: ...')
|
||||
if prop.fset:
|
||||
lines.append(f' @{prop_name}.setter')
|
||||
lines.append(f' def {prop_name}(self, value: Any) -> None: ...')
|
||||
else:
|
||||
lines.append(f' {prop_name}: Any')
|
||||
|
||||
# Add methods
|
||||
if methods:
|
||||
lines.append('')
|
||||
for method_name, method in methods:
|
||||
if method.__doc__:
|
||||
sig, ret = parse_docstring_signature(method.__doc__)
|
||||
if sig and ret:
|
||||
lines.append(f' def {method_name}(self{sig[1:-1]}) {ret}: ...')
|
||||
elif sig:
|
||||
lines.append(f' def {method_name}(self{sig[1:-1]}) -> Any: ...')
|
||||
else:
|
||||
lines.append(f' def {method_name}(self, *args, **kwargs) -> Any: ...')
|
||||
else:
|
||||
lines.append(f' def {method_name}(self, *args, **kwargs) -> Any: ...')
|
||||
|
||||
lines.append('')
|
||||
return lines
|
||||
|
||||
def generate_function_stub(func_name: str, func: Any) -> str:
|
||||
"""Generate stub for a function."""
|
||||
if func.__doc__:
|
||||
sig, ret = parse_docstring_signature(func.__doc__)
|
||||
if sig and ret:
|
||||
return f'def {func_name}{sig} {ret}: ...'
|
||||
elif sig:
|
||||
return f'def {func_name}{sig} -> Any: ...'
|
||||
|
||||
return f'def {func_name}(*args, **kwargs) -> Any: ...'
|
||||
|
||||
def generate_stubs():
|
||||
"""Generate the main mcrfpy.pyi file."""
|
||||
lines = [
|
||||
'"""Type stubs for McRogueFace Python API.',
|
||||
'',
|
||||
'Auto-generated - do not edit directly.',
|
||||
'"""',
|
||||
'',
|
||||
'from typing import Any, List, Dict, Tuple, Optional, Callable, Union',
|
||||
'',
|
||||
'# Module documentation',
|
||||
]
|
||||
|
||||
# Add module docstring as comment
|
||||
if mcrfpy.__doc__:
|
||||
for line in mcrfpy.__doc__.strip().split('\n')[:3]:
|
||||
lines.append(f'# {line}')
|
||||
|
||||
lines.extend(['', '# Classes', ''])
|
||||
|
||||
# Collect all classes
|
||||
classes = []
|
||||
functions = []
|
||||
constants = []
|
||||
|
||||
for name in sorted(dir(mcrfpy)):
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
|
||||
obj = getattr(mcrfpy, name)
|
||||
|
||||
if isinstance(obj, type):
|
||||
classes.append((name, obj))
|
||||
elif callable(obj):
|
||||
functions.append((name, obj))
|
||||
elif not inspect.ismodule(obj):
|
||||
constants.append((name, obj))
|
||||
|
||||
# Generate class stubs
|
||||
for class_name, cls in classes:
|
||||
lines.extend(generate_class_stub(class_name, cls))
|
||||
|
||||
# Generate function stubs
|
||||
if functions:
|
||||
lines.extend(['# Functions', ''])
|
||||
for func_name, func in functions:
|
||||
lines.append(generate_function_stub(func_name, func))
|
||||
lines.append('')
|
||||
|
||||
# Generate constants
|
||||
if constants:
|
||||
lines.extend(['# Constants', ''])
|
||||
for const_name, const in constants:
|
||||
const_type = get_type_hint(type(const))
|
||||
lines.append(f'{const_name}: {const_type}')
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
def generate_automation_stubs():
|
||||
"""Generate stubs for the automation submodule."""
|
||||
if not hasattr(mcrfpy, 'automation'):
|
||||
return None
|
||||
|
||||
automation = mcrfpy.automation
|
||||
|
||||
lines = [
|
||||
'"""Type stubs for McRogueFace automation API."""',
|
||||
'',
|
||||
'from typing import Optional, Tuple',
|
||||
'',
|
||||
]
|
||||
|
||||
# Get all automation functions
|
||||
for name in sorted(dir(automation)):
|
||||
if name.startswith('_'):
|
||||
continue
|
||||
|
||||
obj = getattr(automation, name)
|
||||
if callable(obj):
|
||||
lines.append(generate_function_stub(name, obj))
|
||||
|
||||
return '\n'.join(lines)
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
print("Generating type stubs for McRogueFace...")
|
||||
|
||||
# Generate main module stubs
|
||||
stubs = generate_stubs()
|
||||
|
||||
# Create stubs directory
|
||||
os.makedirs('stubs', exist_ok=True)
|
||||
|
||||
# Write main module stubs
|
||||
with open('stubs/mcrfpy.pyi', 'w') as f:
|
||||
f.write(stubs)
|
||||
print("Generated stubs/mcrfpy.pyi")
|
||||
|
||||
# Generate automation module stubs if available
|
||||
automation_stubs = generate_automation_stubs()
|
||||
if automation_stubs:
|
||||
os.makedirs('stubs/mcrfpy', exist_ok=True)
|
||||
with open('stubs/mcrfpy/__init__.pyi', 'w') as f:
|
||||
f.write(stubs)
|
||||
with open('stubs/mcrfpy/automation.pyi', 'w') as f:
|
||||
f.write(automation_stubs)
|
||||
print("Generated stubs/mcrfpy/automation.pyi")
|
||||
|
||||
print("\nType stubs generated successfully!")
|
||||
print("\nTo use in your IDE:")
|
||||
print("1. Add the 'stubs' directory to your PYTHONPATH")
|
||||
print("2. Or configure your IDE to look for stubs in the 'stubs' directory")
|
||||
print("3. Most IDEs will automatically detect .pyi files")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,574 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Generate .pyi type stub files for McRogueFace Python API - Version 2.
|
||||
|
||||
This script creates properly formatted type stubs by manually defining
|
||||
the API based on the documentation we've created.
|
||||
"""
|
||||
|
||||
import os
|
||||
import mcrfpy
|
||||
|
||||
def generate_mcrfpy_stub():
|
||||
"""Generate the main mcrfpy.pyi stub file."""
|
||||
return '''"""Type stubs for McRogueFace Python API.
|
||||
|
||||
Core game engine interface for creating roguelike games with Python.
|
||||
"""
|
||||
|
||||
from typing import Any, List, Dict, Tuple, Optional, Callable, Union, overload
|
||||
|
||||
# Type aliases
|
||||
UIElement = Union['Frame', 'Caption', 'Sprite', 'Grid']
|
||||
Transition = Union[str, None]
|
||||
|
||||
# Classes
|
||||
|
||||
class Color:
|
||||
"""SFML Color Object for RGBA colors."""
|
||||
|
||||
r: int
|
||||
g: int
|
||||
b: int
|
||||
a: int
|
||||
|
||||
@overload
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def __init__(self, r: int, g: int, b: int, a: int = 255) -> None: ...
|
||||
|
||||
def from_hex(self, hex_string: str) -> 'Color':
|
||||
"""Create color from hex string (e.g., '#FF0000' or 'FF0000')."""
|
||||
...
|
||||
|
||||
def to_hex(self) -> str:
|
||||
"""Convert color to hex string format."""
|
||||
...
|
||||
|
||||
def lerp(self, other: 'Color', t: float) -> 'Color':
|
||||
"""Linear interpolation between two colors."""
|
||||
...
|
||||
|
||||
class Vector:
|
||||
"""SFML Vector Object for 2D coordinates."""
|
||||
|
||||
x: float
|
||||
y: float
|
||||
|
||||
@overload
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def __init__(self, x: float, y: float) -> None: ...
|
||||
|
||||
def add(self, other: 'Vector') -> 'Vector': ...
|
||||
def subtract(self, other: 'Vector') -> 'Vector': ...
|
||||
def multiply(self, scalar: float) -> 'Vector': ...
|
||||
def divide(self, scalar: float) -> 'Vector': ...
|
||||
def distance(self, other: 'Vector') -> float: ...
|
||||
def normalize(self) -> 'Vector': ...
|
||||
def dot(self, other: 'Vector') -> float: ...
|
||||
|
||||
class Texture:
|
||||
"""SFML Texture Object for images."""
|
||||
|
||||
def __init__(self, filename: str) -> None: ...
|
||||
|
||||
filename: str
|
||||
width: int
|
||||
height: int
|
||||
sprite_count: int
|
||||
|
||||
class Font:
|
||||
"""SFML Font Object for text rendering."""
|
||||
|
||||
def __init__(self, filename: str) -> None: ...
|
||||
|
||||
filename: str
|
||||
family: str
|
||||
|
||||
class Drawable:
|
||||
"""Base class for all drawable UI elements."""
|
||||
|
||||
x: float
|
||||
y: float
|
||||
visible: bool
|
||||
z_index: int
|
||||
name: str
|
||||
pos: Vector
|
||||
|
||||
def get_bounds(self) -> Tuple[float, float, float, float]:
|
||||
"""Get bounding box as (x, y, width, height)."""
|
||||
...
|
||||
|
||||
def move(self, dx: float, dy: float) -> None:
|
||||
"""Move by relative offset (dx, dy)."""
|
||||
...
|
||||
|
||||
def resize(self, width: float, height: float) -> None:
|
||||
"""Resize to new dimensions (width, height)."""
|
||||
...
|
||||
|
||||
class Frame(Drawable):
|
||||
"""Frame(x=0, y=0, w=0, h=0, fill_color=None, outline_color=None, outline=0, click=None, children=None)
|
||||
|
||||
A rectangular frame UI element that can contain other drawable elements.
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def __init__(self, x: float = 0, y: float = 0, w: float = 0, h: float = 0,
|
||||
fill_color: Optional[Color] = None, outline_color: Optional[Color] = None,
|
||||
outline: float = 0, click: Optional[Callable] = None,
|
||||
children: Optional[List[UIElement]] = None) -> None: ...
|
||||
|
||||
w: float
|
||||
h: float
|
||||
fill_color: Color
|
||||
outline_color: Color
|
||||
outline: float
|
||||
click: Optional[Callable[[float, float, int], None]]
|
||||
children: 'UICollection'
|
||||
clip_children: bool
|
||||
|
||||
class Caption(Drawable):
|
||||
"""Caption(text='', x=0, y=0, font=None, fill_color=None, outline_color=None, outline=0, click=None)
|
||||
|
||||
A text display UI element with customizable font and styling.
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def __init__(self, text: str = '', x: float = 0, y: float = 0,
|
||||
font: Optional[Font] = None, fill_color: Optional[Color] = None,
|
||||
outline_color: Optional[Color] = None, outline: float = 0,
|
||||
click: Optional[Callable] = None) -> None: ...
|
||||
|
||||
text: str
|
||||
font: Font
|
||||
fill_color: Color
|
||||
outline_color: Color
|
||||
outline: float
|
||||
click: Optional[Callable[[float, float, int], None]]
|
||||
w: float # Read-only, computed from text
|
||||
h: float # Read-only, computed from text
|
||||
|
||||
class Sprite(Drawable):
|
||||
"""Sprite(x=0, y=0, texture=None, sprite_index=0, scale=1.0, click=None)
|
||||
|
||||
A sprite UI element that displays a texture or portion of a texture atlas.
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def __init__(self, x: float = 0, y: float = 0, texture: Optional[Texture] = None,
|
||||
sprite_index: int = 0, scale: float = 1.0,
|
||||
click: Optional[Callable] = None) -> None: ...
|
||||
|
||||
texture: Texture
|
||||
sprite_index: int
|
||||
scale: float
|
||||
click: Optional[Callable[[float, float, int], None]]
|
||||
w: float # Read-only, computed from texture
|
||||
h: float # Read-only, computed from texture
|
||||
|
||||
class Grid(Drawable):
|
||||
"""Grid(x=0, y=0, grid_size=(20, 20), texture=None, tile_width=16, tile_height=16, scale=1.0, click=None)
|
||||
|
||||
A grid-based tilemap UI element for rendering tile-based levels and game worlds.
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def __init__(self, x: float = 0, y: float = 0, grid_size: Tuple[int, int] = (20, 20),
|
||||
texture: Optional[Texture] = None, tile_width: int = 16, tile_height: int = 16,
|
||||
scale: float = 1.0, click: Optional[Callable] = None) -> None: ...
|
||||
|
||||
grid_size: Tuple[int, int]
|
||||
tile_width: int
|
||||
tile_height: int
|
||||
texture: Texture
|
||||
scale: float
|
||||
points: List[List['GridPoint']]
|
||||
entities: 'EntityCollection'
|
||||
background_color: Color
|
||||
click: Optional[Callable[[int, int, int], None]]
|
||||
|
||||
def at(self, x: int, y: int) -> 'GridPoint':
|
||||
"""Get grid point at tile coordinates."""
|
||||
...
|
||||
|
||||
class GridPoint:
|
||||
"""Grid point representing a single tile."""
|
||||
|
||||
texture_index: int
|
||||
solid: bool
|
||||
color: Color
|
||||
|
||||
class GridPointState:
|
||||
"""State information for a grid point."""
|
||||
|
||||
texture_index: int
|
||||
color: Color
|
||||
|
||||
class Entity(Drawable):
|
||||
"""Entity(grid_x=0, grid_y=0, texture=None, sprite_index=0, name='')
|
||||
|
||||
Game entity that lives within a Grid.
|
||||
"""
|
||||
|
||||
@overload
|
||||
def __init__(self) -> None: ...
|
||||
@overload
|
||||
def __init__(self, grid_x: float = 0, grid_y: float = 0, texture: Optional[Texture] = None,
|
||||
sprite_index: int = 0, name: str = '') -> None: ...
|
||||
|
||||
grid_x: float
|
||||
grid_y: float
|
||||
texture: Texture
|
||||
sprite_index: int
|
||||
grid: Optional[Grid]
|
||||
|
||||
def at(self, grid_x: float, grid_y: float) -> None:
|
||||
"""Move entity to grid position."""
|
||||
...
|
||||
|
||||
def die(self) -> None:
|
||||
"""Remove entity from its grid."""
|
||||
...
|
||||
|
||||
def index(self) -> int:
|
||||
"""Get index in parent grid's entity collection."""
|
||||
...
|
||||
|
||||
class UICollection:
|
||||
"""Collection of UI drawable elements (Frame, Caption, Sprite, Grid)."""
|
||||
|
||||
def __len__(self) -> int: ...
|
||||
def __getitem__(self, index: int) -> UIElement: ...
|
||||
def __setitem__(self, index: int, value: UIElement) -> None: ...
|
||||
def __delitem__(self, index: int) -> None: ...
|
||||
def __contains__(self, item: UIElement) -> bool: ...
|
||||
def __iter__(self) -> Any: ...
|
||||
def __add__(self, other: 'UICollection') -> 'UICollection': ...
|
||||
def __iadd__(self, other: 'UICollection') -> 'UICollection': ...
|
||||
|
||||
def append(self, item: UIElement) -> None: ...
|
||||
def extend(self, items: List[UIElement]) -> None: ...
|
||||
def remove(self, item: UIElement) -> None: ...
|
||||
def index(self, item: UIElement) -> int: ...
|
||||
def count(self, item: UIElement) -> int: ...
|
||||
|
||||
class EntityCollection:
|
||||
"""Collection of Entity objects."""
|
||||
|
||||
def __len__(self) -> int: ...
|
||||
def __getitem__(self, index: int) -> Entity: ...
|
||||
def __setitem__(self, index: int, value: Entity) -> None: ...
|
||||
def __delitem__(self, index: int) -> None: ...
|
||||
def __contains__(self, item: Entity) -> bool: ...
|
||||
def __iter__(self) -> Any: ...
|
||||
def __add__(self, other: 'EntityCollection') -> 'EntityCollection': ...
|
||||
def __iadd__(self, other: 'EntityCollection') -> 'EntityCollection': ...
|
||||
|
||||
def append(self, item: Entity) -> None: ...
|
||||
def extend(self, items: List[Entity]) -> None: ...
|
||||
def remove(self, item: Entity) -> None: ...
|
||||
def index(self, item: Entity) -> int: ...
|
||||
def count(self, item: Entity) -> int: ...
|
||||
|
||||
class Scene:
|
||||
"""Base class for object-oriented scenes."""
|
||||
|
||||
name: str
|
||||
|
||||
def __init__(self, name: str) -> None: ...
|
||||
|
||||
def activate(self) -> None:
|
||||
"""Called when scene becomes active."""
|
||||
...
|
||||
|
||||
def deactivate(self) -> None:
|
||||
"""Called when scene becomes inactive."""
|
||||
...
|
||||
|
||||
def get_ui(self) -> UICollection:
|
||||
"""Get UI elements collection."""
|
||||
...
|
||||
|
||||
def on_keypress(self, key: str, pressed: bool) -> None:
|
||||
"""Handle keyboard events."""
|
||||
...
|
||||
|
||||
def on_click(self, x: float, y: float, button: int) -> None:
|
||||
"""Handle mouse clicks."""
|
||||
...
|
||||
|
||||
def on_enter(self) -> None:
|
||||
"""Called when entering the scene."""
|
||||
...
|
||||
|
||||
def on_exit(self) -> None:
|
||||
"""Called when leaving the scene."""
|
||||
...
|
||||
|
||||
def on_resize(self, width: int, height: int) -> None:
|
||||
"""Handle window resize events."""
|
||||
...
|
||||
|
||||
def update(self, dt: float) -> None:
|
||||
"""Update scene logic."""
|
||||
...
|
||||
|
||||
class Timer:
|
||||
"""Timer object for scheduled callbacks."""
|
||||
|
||||
name: str
|
||||
interval: int
|
||||
active: bool
|
||||
|
||||
def __init__(self, name: str, callback: Callable[[float], None], interval: int) -> None: ...
|
||||
|
||||
def pause(self) -> None:
|
||||
"""Pause the timer."""
|
||||
...
|
||||
|
||||
def resume(self) -> None:
|
||||
"""Resume the timer."""
|
||||
...
|
||||
|
||||
def cancel(self) -> None:
|
||||
"""Cancel and remove the timer."""
|
||||
...
|
||||
|
||||
class Window:
|
||||
"""Window singleton for managing the game window."""
|
||||
|
||||
resolution: Tuple[int, int]
|
||||
fullscreen: bool
|
||||
vsync: bool
|
||||
title: str
|
||||
fps_limit: int
|
||||
game_resolution: Tuple[int, int]
|
||||
scaling_mode: str
|
||||
|
||||
@staticmethod
|
||||
def get() -> 'Window':
|
||||
"""Get the window singleton instance."""
|
||||
...
|
||||
|
||||
class Animation:
|
||||
"""Animation object for animating UI properties."""
|
||||
|
||||
target: Any
|
||||
property: str
|
||||
duration: float
|
||||
easing: str
|
||||
loop: bool
|
||||
on_complete: Optional[Callable]
|
||||
|
||||
def __init__(self, target: Any, property: str, start_value: Any, end_value: Any,
|
||||
duration: float, easing: str = 'linear', loop: bool = False,
|
||||
on_complete: Optional[Callable] = None) -> None: ...
|
||||
|
||||
def start(self) -> None:
|
||||
"""Start the animation."""
|
||||
...
|
||||
|
||||
def update(self, dt: float) -> bool:
|
||||
"""Update animation, returns True if still running."""
|
||||
...
|
||||
|
||||
def get_current_value(self) -> Any:
|
||||
"""Get the current interpolated value."""
|
||||
...
|
||||
|
||||
# Module functions
|
||||
|
||||
def createSoundBuffer(filename: str) -> int:
|
||||
"""Load a sound effect from a file and return its buffer ID."""
|
||||
...
|
||||
|
||||
def loadMusic(filename: str) -> None:
|
||||
"""Load and immediately play background music from a file."""
|
||||
...
|
||||
|
||||
def setMusicVolume(volume: int) -> None:
|
||||
"""Set the global music volume (0-100)."""
|
||||
...
|
||||
|
||||
def setSoundVolume(volume: int) -> None:
|
||||
"""Set the global sound effects volume (0-100)."""
|
||||
...
|
||||
|
||||
def playSound(buffer_id: int) -> None:
|
||||
"""Play a sound effect using a previously loaded buffer."""
|
||||
...
|
||||
|
||||
def getMusicVolume() -> int:
|
||||
"""Get the current music volume level (0-100)."""
|
||||
...
|
||||
|
||||
def getSoundVolume() -> int:
|
||||
"""Get the current sound effects volume level (0-100)."""
|
||||
...
|
||||
|
||||
def sceneUI(scene: Optional[str] = None) -> UICollection:
|
||||
"""Get all UI elements for a scene."""
|
||||
...
|
||||
|
||||
def currentScene() -> str:
|
||||
"""Get the name of the currently active scene."""
|
||||
...
|
||||
|
||||
def setScene(scene: str, transition: Optional[str] = None, duration: float = 0.0) -> None:
|
||||
"""Switch to a different scene with optional transition effect."""
|
||||
...
|
||||
|
||||
def createScene(name: str) -> None:
|
||||
"""Create a new empty scene."""
|
||||
...
|
||||
|
||||
def keypressScene(handler: Callable[[str, bool], None]) -> None:
|
||||
"""Set the keyboard event handler for the current scene."""
|
||||
...
|
||||
|
||||
def setTimer(name: str, handler: Callable[[float], None], interval: int) -> None:
|
||||
"""Create or update a recurring timer."""
|
||||
...
|
||||
|
||||
def delTimer(name: str) -> None:
|
||||
"""Stop and remove a timer."""
|
||||
...
|
||||
|
||||
def exit() -> None:
|
||||
"""Cleanly shut down the game engine and exit the application."""
|
||||
...
|
||||
|
||||
def setScale(multiplier: float) -> None:
|
||||
"""Scale the game window size (deprecated - use Window.resolution)."""
|
||||
...
|
||||
|
||||
def find(name: str, scene: Optional[str] = None) -> Optional[UIElement]:
|
||||
"""Find the first UI element with the specified name."""
|
||||
...
|
||||
|
||||
def findAll(pattern: str, scene: Optional[str] = None) -> List[UIElement]:
|
||||
"""Find all UI elements matching a name pattern (supports * wildcards)."""
|
||||
...
|
||||
|
||||
def getMetrics() -> Dict[str, Union[int, float]]:
|
||||
"""Get current performance metrics."""
|
||||
...
|
||||
|
||||
# Submodule
|
||||
class automation:
|
||||
"""Automation API for testing and scripting."""
|
||||
|
||||
@staticmethod
|
||||
def screenshot(filename: str) -> bool:
|
||||
"""Save a screenshot to the specified file."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def position() -> Tuple[int, int]:
|
||||
"""Get current mouse position as (x, y) tuple."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def size() -> Tuple[int, int]:
|
||||
"""Get screen size as (width, height) tuple."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def onScreen(x: int, y: int) -> bool:
|
||||
"""Check if coordinates are within screen bounds."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def moveTo(x: int, y: int, duration: float = 0.0) -> None:
|
||||
"""Move mouse to absolute position."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def moveRel(xOffset: int, yOffset: int, duration: float = 0.0) -> None:
|
||||
"""Move mouse relative to current position."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def dragTo(x: int, y: int, duration: float = 0.0, button: str = 'left') -> None:
|
||||
"""Drag mouse to position."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def dragRel(xOffset: int, yOffset: int, duration: float = 0.0, button: str = 'left') -> None:
|
||||
"""Drag mouse relative to current position."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def click(x: Optional[int] = None, y: Optional[int] = None, clicks: int = 1,
|
||||
interval: float = 0.0, button: str = 'left') -> None:
|
||||
"""Click mouse at position."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def mouseDown(x: Optional[int] = None, y: Optional[int] = None, button: str = 'left') -> None:
|
||||
"""Press mouse button down."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def mouseUp(x: Optional[int] = None, y: Optional[int] = None, button: str = 'left') -> None:
|
||||
"""Release mouse button."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def keyDown(key: str) -> None:
|
||||
"""Press key down."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def keyUp(key: str) -> None:
|
||||
"""Release key."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def press(key: str) -> None:
|
||||
"""Press and release a key."""
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def typewrite(text: str, interval: float = 0.0) -> None:
|
||||
"""Type text with optional interval between characters."""
|
||||
...
|
||||
'''
|
||||
|
||||
def main():
|
||||
"""Generate type stubs."""
|
||||
print("Generating comprehensive type stubs for McRogueFace...")
|
||||
|
||||
# Create stubs directory
|
||||
os.makedirs('stubs', exist_ok=True)
|
||||
|
||||
# Write main stub file
|
||||
with open('stubs/mcrfpy.pyi', 'w') as f:
|
||||
f.write(generate_mcrfpy_stub())
|
||||
|
||||
print("Generated stubs/mcrfpy.pyi")
|
||||
|
||||
# Create py.typed marker
|
||||
with open('stubs/py.typed', 'w') as f:
|
||||
f.write('')
|
||||
|
||||
print("Created py.typed marker")
|
||||
|
||||
print("\nType stubs generated successfully!")
|
||||
print("\nTo use in your IDE:")
|
||||
print("1. Add the 'stubs' directory to your project")
|
||||
print("2. Most IDEs will automatically detect the .pyi files")
|
||||
print("3. For VS Code: add to python.analysis.extraPaths in settings.json")
|
||||
print("4. For PyCharm: mark 'stubs' directory as Sources Root")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -1,449 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" style="color-scheme: dark;"><head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<title>
|
||||
Part 0 - Setting Up · Roguelike Tutorials
|
||||
</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
|
||||
|
||||
|
||||
|
||||
<meta name="description" content="Prior knowledge Link to heading This tutorial assumes some basic familiarity with programming in general, and with Python. If you’ve never used Python before, this tutorial could be a little confusing. There are many free resources online about learning programming and Python (too many to list here), and I’d recommend learning about objects and functions in Python at the very least before attempting to read this tutorial.
|
||||
… Of course, there are those who have ignored this advice and done well with this tutorial anyway, so feel free to ignore that last paragraph if you’re feeling bold!">
|
||||
<meta name="keywords" content="">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="Part 0 - Setting Up">
|
||||
<meta name="twitter:description" content="Prior knowledge Link to heading This tutorial assumes some basic familiarity with programming in general, and with Python. If you’ve never used Python before, this tutorial could be a little confusing. There are many free resources online about learning programming and Python (too many to list here), and I’d recommend learning about objects and functions in Python at the very least before attempting to read this tutorial.
|
||||
… Of course, there are those who have ignored this advice and done well with this tutorial anyway, so feel free to ignore that last paragraph if you’re feeling bold!">
|
||||
|
||||
<meta property="og:title" content="Part 0 - Setting Up">
|
||||
<meta property="og:description" content="Prior knowledge Link to heading This tutorial assumes some basic familiarity with programming in general, and with Python. If you’ve never used Python before, this tutorial could be a little confusing. There are many free resources online about learning programming and Python (too many to list here), and I’d recommend learning about objects and functions in Python at the very least before attempting to read this tutorial.
|
||||
… Of course, there are those who have ignored this advice and done well with this tutorial anyway, so feel free to ignore that last paragraph if you’re feeling bold!">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:url" content="https://rogueliketutorials.com/tutorials/tcod/v2/part-0/"><meta property="article:section" content="tutorials">
|
||||
<meta property="article:published_time" content="2020-06-14T11:25:36-07:00">
|
||||
<meta property="article:modified_time" content="2020-06-14T11:25:36-07:00">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="canonical" href="https://rogueliketutorials.com/tutorials/tcod/v2/part-0/">
|
||||
|
||||
|
||||
<link rel="preload" href="https://rogueliketutorials.com/fonts/forkawesome-webfont.woff2?v=1.2.0" as="font" type="font/woff2" crossorigin="">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="Part%200%20-%20Setting%20Up%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.c4d7e93a158eda5a65b3df343745d2092a0a1e2170feeec909.css" integrity="sha256-xNfpOhWO2lpls980N0XSCSoKHiFw/u7JCbiolEOQPGo=" crossorigin="anonymous" media="screen">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="Part%200%20-%20Setting%20Up%20%C2%B7%20Roguelike%20Tutorials_files/coder-dark.min.78b5fe3864945faf5207fb8fe3ab2320d49c3365def0e.css" integrity="sha256-eLX+OGSUX69SB/uP46sjINScM2Xe8OiKwd8N2txUoDw=" crossorigin="anonymous" media="screen">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="Part%200%20-%20Setting%20Up%20%C2%B7%20Roguelike%20Tutorials_files/style.min.9d3eb202952dddb888856ff12c83bc88de866c596286bfb4c1.css" integrity="sha256-nT6yApUt3biIhW/xLIO8iN6GbFlihr+0wfjmvq2a42Y=" crossorigin="anonymous" media="screen">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="icon" type="image/png" href="https://rogueliketutorials.com/images/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="https://rogueliketutorials.com/images/favicon-16x16.png" sizes="16x16">
|
||||
|
||||
<link rel="apple-touch-icon" href="https://rogueliketutorials.com/images/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="https://rogueliketutorials.com/images/apple-touch-icon.png">
|
||||
|
||||
<link rel="manifest" href="https://rogueliketutorials.com/site.webmanifest">
|
||||
<link rel="mask-icon" href="https://rogueliketutorials.com/images/safari-pinned-tab.svg" color="#5bbad5">
|
||||
|
||||
|
||||
|
||||
|
||||
<meta name="generator" content="Hugo 0.110.0">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<style>:is([id*='google_ads_iframe'],[id*='taboola-'],.taboolaHeight,.taboola-placeholder,#top-ad,#credential_picker_container,#credentials-picker-container,#credential_picker_iframe,[id*='google-one-tap-iframe'],#google-one-tap-popup-container,.google-one-tap__module,.google-one-tap-modal-div,#amp_floatingAdDiv,#ez-content-blocker-container) {display:none!important;min-height:0!important;height:0!important;}</style></head>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<body class="colorscheme-dark vsc-initialized">
|
||||
|
||||
<div class="float-container">
|
||||
<a id="dark-mode-toggle" class="colorscheme-toggle">
|
||||
<i class="fa fa-adjust fa-fw" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<main class="wrapper">
|
||||
<nav class="navigation">
|
||||
<section class="container">
|
||||
<a class="navigation-title" href="https://rogueliketutorials.com/">
|
||||
Roguelike Tutorials
|
||||
</a>
|
||||
|
||||
<input type="checkbox" id="menu-toggle">
|
||||
<label class="menu-button float-right" for="menu-toggle">
|
||||
<i class="fa fa-bars fa-fw" aria-hidden="true"></i>
|
||||
</label>
|
||||
<ul class="navigation-list">
|
||||
|
||||
|
||||
<li class="navigation-item">
|
||||
<a class="navigation-link" href="https://rogueliketutorials.com/">Home</a>
|
||||
</li>
|
||||
|
||||
<li class="navigation-item">
|
||||
<a class="navigation-link" href="https://rogueliketutorials.com/tutorials/tcod/v2/">TCOD Tutorial (2020)</a>
|
||||
</li>
|
||||
|
||||
<li class="navigation-item">
|
||||
<a class="navigation-link" href="https://rogueliketutorials.com/tutorials/tcod/2019/">TCOD Tutorial (2019)</a>
|
||||
</li>
|
||||
|
||||
<li class="navigation-item">
|
||||
<a class="navigation-link" href="https://rogueliketutorials.com/about/">About</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</section>
|
||||
</nav>
|
||||
|
||||
|
||||
<div class="content">
|
||||
|
||||
<section class="container page">
|
||||
<article>
|
||||
<header>
|
||||
<h1 class="title">
|
||||
<a class="title-link" href="https://rogueliketutorials.com/tutorials/tcod/v2/part-0/">
|
||||
Part 0 - Setting Up
|
||||
</a>
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<h4 id="prior-knowledge">
|
||||
Prior knowledge
|
||||
<a class="heading-link" href="#prior-knowledge">
|
||||
<i class="fa fa-link" aria-hidden="true" title="Link to heading"></i>
|
||||
<span class="sr-only">Link to heading</span>
|
||||
</a>
|
||||
</h4>
|
||||
<p>This tutorial assumes some basic familiarity with programming in
|
||||
general, and with Python. If you’ve never used Python before, this
|
||||
tutorial could be a little confusing. There are many free resources
|
||||
online about learning programming and Python (too many to list here),
|
||||
and I’d recommend learning about objects and functions in Python at the
|
||||
very least before attempting to read this tutorial.</p>
|
||||
<p>… Of course, there are those who have ignored this advice and done
|
||||
well with this tutorial anyway, so feel free to ignore that last
|
||||
paragraph if you’re feeling bold!</p>
|
||||
<h4 id="installation">
|
||||
Installation
|
||||
<a class="heading-link" href="#installation">
|
||||
<i class="fa fa-link" aria-hidden="true" title="Link to heading"></i>
|
||||
<span class="sr-only">Link to heading</span>
|
||||
</a>
|
||||
</h4>
|
||||
<p>To do this tutorial, you’ll need Python version 3.7 or higher. The
|
||||
latest version of Python is recommended (currently 3.8 as of June
|
||||
2020). <strong>Note: Python 2 is not compatible.</strong></p>
|
||||
<p><a href="https://www.python.org/downloads/">Download Python here</a>.</p>
|
||||
<p>You’ll also want the latest version of the TCOD library, which is what
|
||||
this tutorial is based on.</p>
|
||||
<p><a href="https://python-tcod.readthedocs.io/en/latest/installation.html">Installation instructions for TCOD can be found
|
||||
here.</a></p>
|
||||
<p>While you can certainly install TCOD and complete this tutorial without
|
||||
it, I’d highly recommend using a virtual environment. <a href="https://docs.python.org/3/library/venv.html">Documentation on
|
||||
how to do that can be found
|
||||
here.</a></p>
|
||||
<p>Additionally, if you are going to use a virtual environment, you may want to take the time to set up a <code>requirements.txt</code>
|
||||
file. This will allow you to track your project dependencies if you add
|
||||
any in the future, and more easily install them if you need to (for
|
||||
example, if you pull from a remote git repository).</p>
|
||||
<p>You can set up your <code>requirements.txt</code> file in the same directory that you plan on working in for the project. Create the file <code>requirements.txt</code> and put the following in it:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>tcod>=11.13
|
||||
</span></span><span style="display:flex;"><span>numpy>=1.18</span></span></code></pre></div>
|
||||
<p>Once that’s done, with your virtual environment activated, type the following command:</p>
|
||||
<p><code>pip install -r requirements.txt</code></p>
|
||||
<p>This should install the TCOD library, along with its dependency, numpy.</p>
|
||||
<p>Depending on your computer, you might also need to install SDL2.
|
||||
Check the instructions for installing it based on your operating system.
|
||||
For example, Ubuntu can install it with the following command:</p>
|
||||
<p><code>sudo apt-get install libsdl2-dev</code></p>
|
||||
<h4 id="editors">
|
||||
Editors
|
||||
<a class="heading-link" href="#editors">
|
||||
<i class="fa fa-link" aria-hidden="true" title="Link to heading"></i>
|
||||
<span class="sr-only">Link to heading</span>
|
||||
</a>
|
||||
</h4>
|
||||
<p>Any text editor can work for writing Python. You could even use Notepad
|
||||
if you really wanted to. Personally, I’m a fan of
|
||||
<a href="https://www.jetbrains.com/pycharm/">Pycharm</a> and <a href="https://code.visualstudio.com/">Visual Studio
|
||||
Code</a>. Whatever you choose, I strongly
|
||||
recommend something that can help catch Python syntax errors at the very
|
||||
least. I’ve been working with Python for over five years, and I still
|
||||
make these types of mistakes all the time!</p>
|
||||
<h4 id="making-sure-python-works">
|
||||
Making sure Python works
|
||||
<a class="heading-link" href="#making-sure-python-works">
|
||||
<i class="fa fa-link" aria-hidden="true" title="Link to heading"></i>
|
||||
<span class="sr-only">Link to heading</span>
|
||||
</a>
|
||||
</h4>
|
||||
<p>To verify that your installation of both Python 3 and TCOD are working,
|
||||
create a new file (in whatever directory you plan on using for the
|
||||
tutorial) called <code>main.py</code>, and enter the following text into it:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env python3</span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> tcod
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
|
||||
</span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">"Hello World!"</span>)
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">"__main__"</span>:
|
||||
</span></span><span style="display:flex;"><span> main()
|
||||
</span></span></code></pre></div><p>Run the file in your terminal (or alternatively in your editor, if
|
||||
possible):</p>
|
||||
<p><code>python main.py</code></p>
|
||||
<p>If you’re not using <code>virtualenv</code>, the command will probably look like
|
||||
this:</p>
|
||||
<p><code>python3 main.py</code></p>
|
||||
<p>You should see “Hello World!” printed out to the terminal. If you
|
||||
receive an error, there is probably an issue with either your Python or
|
||||
TCOD installation.</p>
|
||||
<h3 id="downloading-the-image-file">
|
||||
Downloading the Image File
|
||||
<a class="heading-link" href="#downloading-the-image-file">
|
||||
<i class="fa fa-link" aria-hidden="true" title="Link to heading"></i>
|
||||
<span class="sr-only">Link to heading</span>
|
||||
</a>
|
||||
</h3>
|
||||
<p>For this tutorial, we’ll need an image file. The default one is provided below.</p>
|
||||
<p><img src="Part%200%20-%20Setting%20Up%20%C2%B7%20Roguelike%20Tutorials_files/dejavu10x10_gs_tc.png" alt="Font File"></p>
|
||||
<p>Right click the image and save it to the same directory that you’re planning on
|
||||
placing your code in. If the above image is not displaying for some reason,
|
||||
it is also <a href="https://raw.githubusercontent.com/TStand90/tcod_tutorial_v2/1667c8995fb0d0fd6df98bd84c0be46cb8b78dac/dejavu10x10_gs_tc.png">available for download here.</a></p>
|
||||
<h3 id="about-this-site">
|
||||
About this site
|
||||
<a class="heading-link" href="#about-this-site">
|
||||
<i class="fa fa-link" aria-hidden="true" title="Link to heading"></i>
|
||||
<span class="sr-only">Link to heading</span>
|
||||
</a>
|
||||
</h3>
|
||||
<p>Code snippets in this website are presented in a way that tries to convey
|
||||
exactly what the user should be adding to a file at what time. When a user
|
||||
is expected to create a file from scratch and enter code into it, it will
|
||||
be represented with standard Python code highlighting, like so:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Fighter</span>:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, hp, defense, power):
|
||||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>max_hp <span style="color:#f92672">=</span> hp
|
||||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>hp <span style="color:#f92672">=</span> hp
|
||||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>defense <span style="color:#f92672">=</span> defense
|
||||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>power <span style="color:#f92672">=</span> power</span></span></code></pre></div>
|
||||
<p>*<em>Taken from part 6</em>.</p>
|
||||
<p>Most of the time, you’ll be editing a file and code that already exists.
|
||||
In such cases, the code will be displayed like this:</p>
|
||||
<div>
|
||||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||||
Diff
|
||||
</button>
|
||||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||||
Original
|
||||
</button>
|
||||
|
||||
<div class="data-pane active" data-pane="diff">
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class Entity:
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- def __init__(self, x, y, char, color, name, blocks=False):
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ def __init__(self, x, y, char, color, name, blocks=False, fighter=None, ai=None):
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> self.x = x
|
||||
</span></span><span style="display:flex;"><span> self.y = y
|
||||
</span></span><span style="display:flex;"><span> self.char = char
|
||||
</span></span><span style="display:flex;"><span> self.color = color
|
||||
</span></span><span style="display:flex;"><span> self.name = name
|
||||
</span></span><span style="display:flex;"><span> self.blocks = blocks
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.fighter = fighter
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.ai = ai
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if self.fighter:
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.fighter.owner = self
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if self.ai:
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.ai.owner = self
|
||||
</span></span></span></code></pre></div>
|
||||
|
||||
</div>
|
||||
<div class="data-pane" data-pane="original">
|
||||
|
||||
<pre>class Entity:
|
||||
<span class="crossed-out-text">def __init__(self, x, y, char, color, name, blocks=False):</span>
|
||||
<span class="new-text">def __init__(self, x, y, char, color, name, blocks=False, fighter=None, ai=None):</span>
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.char = char
|
||||
self.color = color
|
||||
self.name = name
|
||||
self.blocks = blocks
|
||||
<span class="new-text">self.fighter = fighter
|
||||
self.ai = ai
|
||||
|
||||
if self.fighter:
|
||||
self.fighter.owner = self
|
||||
|
||||
if self.ai:
|
||||
self.ai.owner = self</span></pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<p>*<em>Also taken from part 6.</em></p>
|
||||
<p>Clicking a button above the code section changes the “style” for not just that code block,
|
||||
but the entire website. You can switch between these styles at any time.</p>
|
||||
<p>In the case of the example above, you would remove the old <code>__init__</code> definition, replacing
|
||||
it with the new one. Then, you’d add the necessary lines at the bottom. Both styles convey
|
||||
the same idea.</p>
|
||||
<p>But what’s the difference? The “Diff” style shows the code as you might find it when doing
|
||||
a Git diff comparison (hence the name). It shows plusses and minuses on the side to denote
|
||||
whether you should be adding or subtracting a line from a file. The “Original” style shows
|
||||
the same thing, but it crosses out the lines to remove and does not have plusses nor minuses.</p>
|
||||
<p>The benefit of the “Diff” style is that it doesn’t rely on color to denote what to add, making
|
||||
it more accessible all around. The drawback is that it’s impossible to accurately display the
|
||||
proper indentation in some instances. The plusses and minuses take up one space, so in a code
|
||||
section like this one, be sure not to leave the space for the plus in your code (there should
|
||||
be no spaces before “from”):</p>
|
||||
<div>
|
||||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||||
Diff
|
||||
</button>
|
||||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||||
Original
|
||||
</button>
|
||||
|
||||
|
||||
<div class="data-pane active" data-pane="diff">
|
||||
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>import tcod
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+from input_handlers import handle_keys
|
||||
</span></span></span></code></pre></div>
|
||||
|
||||
</div>
|
||||
<div class="data-pane" data-pane="original">
|
||||
|
||||
<pre>import tcod
|
||||
|
||||
<span class="new-text">from input_handlers import handle_keys</span></pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<p>The “Original” style omits the + and - symbols and doesn’t have the indentation issue,
|
||||
making it a bit easier to copy and paste code sections.</p>
|
||||
<p>Which style you use is a matter of personal preference. The actual code of the tutorial
|
||||
remains the same.</p>
|
||||
<h3 id="getting-help">
|
||||
Getting help
|
||||
<a class="heading-link" href="#getting-help">
|
||||
<i class="fa fa-link" aria-hidden="true" title="Link to heading"></i>
|
||||
<span class="sr-only">Link to heading</span>
|
||||
</a>
|
||||
</h3>
|
||||
<p>Be sure to check out the <a href="https://www.reddit.com/r/roguelikedev">Roguelike Development
|
||||
Subreddit</a> for help. There’s a
|
||||
link there to the Discord channel as well.</p>
|
||||
<hr>
|
||||
<h3 id="ready-to-go">
|
||||
Ready to go?
|
||||
<a class="heading-link" href="#ready-to-go">
|
||||
<i class="fa fa-link" aria-hidden="true" title="Link to heading"></i>
|
||||
<span class="sr-only">Link to heading</span>
|
||||
</a>
|
||||
</h3>
|
||||
<p>Once you’re set up and ready to go, you can proceed to <a href="https://rogueliketutorials.com/tutorials/tcod/v2/part-1">Part
|
||||
1</a>.</p>
|
||||
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<section class="container">
|
||||
©
|
||||
|
||||
2023
|
||||
|
||||
·
|
||||
|
||||
Powered by <a href="https://gohugo.io/">Hugo</a> & <a href="https://github.com/luizdepra/hugo-coder/">Coder</a>.
|
||||
|
||||
</section>
|
||||
</footer>
|
||||
|
||||
</main>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="Part%200%20-%20Setting%20Up%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.236049395dc3682fb2719640872958e12f1f24067bb09c327b2.js" integrity="sha256-I2BJOV3DaC+ycZZAhylY4S8fJAZ7sJwyeyM+YpDH7aw="></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="Part%200%20-%20Setting%20Up%20%C2%B7%20Roguelike%20Tutorials_files/codetabs.min.cc52451e7f25e50f64c1c893826f606d58410d742c214dce.js" integrity="sha256-zFJFHn8l5Q9kwciTgm9gbVhBDXQsIU3OI/tEfJlh8rA="></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</body></html>
|
|
@ -1 +0,0 @@
|
|||
const body=document.body,darkModeToggle=document.getElementById("dark-mode-toggle"),darkModeMediaQuery=window.matchMedia("(prefers-color-scheme: dark)");localStorage.getItem("colorscheme")?setTheme(localStorage.getItem("colorscheme")):setTheme(body.classList.contains("colorscheme-light")||body.classList.contains("colorscheme-dark")?body.classList.contains("colorscheme-dark")?"dark":"light":darkModeMediaQuery.matches?"dark":"light"),darkModeToggle&&darkModeToggle.addEventListener("click",()=>{let e=body.classList.contains("colorscheme-dark")?"light":"dark";setTheme(e),rememberTheme(e)}),darkModeMediaQuery.addListener(e=>{setTheme(e.matches?"dark":"light")}),document.addEventListener("DOMContentLoaded",function(){let e=document.querySelector(".preload-transitions");e.classList.remove("preload-transitions")});function setTheme(e){body.classList.remove("colorscheme-auto");let n=e==="dark"?"light":"dark";body.classList.remove("colorscheme-"+n),body.classList.add("colorscheme-"+e),document.documentElement.style["color-scheme"]=e;function t(e){return new Promise(t=>{if(document.querySelector(e))return t(document.querySelector(e));const n=new MutationObserver(s=>{document.querySelector(e)&&(t(document.querySelector(e)),n.disconnect())});n.observe(document.body,{childList:!0,subtree:!0})})}if(e==="dark"){const e={type:"set-theme",theme:"github-dark"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}else{const e={type:"set-theme",theme:"github-light"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}}function rememberTheme(e){localStorage.setItem("colorscheme",e)}
|
|
@ -1 +0,0 @@
|
|||
var allTabs=document.querySelectorAll("[data-toggle-tab]"),allPanes=document.querySelectorAll("[data-pane]");function toggleTabs(e){if(e.target){e.preventDefault();var n,s,o=e.currentTarget,t=o.getAttribute("data-toggle-tab")}else t=e;window.localStorage&&window.localStorage.setItem("configLangPref",t),n=document.querySelectorAll("[data-toggle-tab='"+t+"']"),s=document.querySelectorAll("[data-pane='"+t+"']");for(let e=0;e<allTabs.length;e++)allTabs[e].classList.remove("active"),allPanes[e].classList.remove("active");for(let e=0;e<n.length;e++)n[e].classList.add("active"),s[e].classList.add("active")}for(let e=0;e<allTabs.length;e++)allTabs[e].addEventListener("click",toggleTabs);window.localStorage.getItem("configLangPref")&&toggleTabs(window.localStorage.getItem("configLangPref"))
|
Before Width: | Height: | Size: 8.2 KiB |
|
@ -1 +0,0 @@
|
|||
pre{border:1px solid #000;padding:15px;background-color:#272822;color:#f8f8f2;background-color:#272822}.language-diff,.language-py3{background-color:#272822!important}body.colorscheme-dart code{background-color:#272822!important}.crossed-out-text{color:#f92672;text-decoration:line-through}.new-text{color:#a6e22e}.data-pane{display:none}.data-pane.active{display:inline}
|
|
@ -1,704 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" style="color-scheme: dark;"><head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<title>
|
||||
Part 1 - Drawing the '@' symbol and moving it around · Roguelike Tutorials
|
||||
</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
|
||||
|
||||
|
||||
|
||||
<meta name="description" content="Welcome to part 1 of this tutorial! This series will help you create your very first roguelike game, written in Python!
|
||||
This tutorial is largely based off the one found on Roguebasin. Many of the design decisions were mainly to keep this tutorial in lockstep with that one (at least in terms of chapter composition and general direction). This tutorial would not have been possible without the guidance of those who wrote that tutorial, along with all the wonderful contributors to tcod and python-tcod over the years.">
|
||||
<meta name="keywords" content="">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="Part 1 - Drawing the '@' symbol and moving it around">
|
||||
<meta name="twitter:description" content="Welcome to part 1 of this tutorial! This series will help you create your very first roguelike game, written in Python!
|
||||
This tutorial is largely based off the one found on Roguebasin. Many of the design decisions were mainly to keep this tutorial in lockstep with that one (at least in terms of chapter composition and general direction). This tutorial would not have been possible without the guidance of those who wrote that tutorial, along with all the wonderful contributors to tcod and python-tcod over the years.">
|
||||
|
||||
<meta property="og:title" content="Part 1 - Drawing the '@' symbol and moving it around">
|
||||
<meta property="og:description" content="Welcome to part 1 of this tutorial! This series will help you create your very first roguelike game, written in Python!
|
||||
This tutorial is largely based off the one found on Roguebasin. Many of the design decisions were mainly to keep this tutorial in lockstep with that one (at least in terms of chapter composition and general direction). This tutorial would not have been possible without the guidance of those who wrote that tutorial, along with all the wonderful contributors to tcod and python-tcod over the years.">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:url" content="https://rogueliketutorials.com/tutorials/tcod/v2/part-1/"><meta property="article:section" content="tutorials">
|
||||
<meta property="article:published_time" content="2020-06-14T11:35:26-07:00">
|
||||
<meta property="article:modified_time" content="2020-06-14T11:35:26-07:00">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="canonical" href="https://rogueliketutorials.com/tutorials/tcod/v2/part-1/">
|
||||
|
||||
|
||||
<link rel="preload" href="https://rogueliketutorials.com/fonts/forkawesome-webfont.woff2?v=1.2.0" as="font" type="font/woff2" crossorigin="">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="Part%201%20-%20Drawing%20the%20'@'%20symbol%20and%20moving%20it%20around%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.c4d7e93a158eda5a65b3df343745d2092a0a1e2170feeec909.css" integrity="sha256-xNfpOhWO2lpls980N0XSCSoKHiFw/u7JCbiolEOQPGo=" crossorigin="anonymous" media="screen">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="Part%201%20-%20Drawing%20the%20'@'%20symbol%20and%20moving%20it%20around%20%C2%B7%20Roguelike%20Tutorials_files/coder-dark.min.78b5fe3864945faf5207fb8fe3ab2320d49c3365def0e.css" integrity="sha256-eLX+OGSUX69SB/uP46sjINScM2Xe8OiKwd8N2txUoDw=" crossorigin="anonymous" media="screen">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="Part%201%20-%20Drawing%20the%20'@'%20symbol%20and%20moving%20it%20around%20%C2%B7%20Roguelike%20Tutorials_files/style.min.9d3eb202952dddb888856ff12c83bc88de866c596286bfb4c1.css" integrity="sha256-nT6yApUt3biIhW/xLIO8iN6GbFlihr+0wfjmvq2a42Y=" crossorigin="anonymous" media="screen">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="icon" type="image/png" href="https://rogueliketutorials.com/images/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="https://rogueliketutorials.com/images/favicon-16x16.png" sizes="16x16">
|
||||
|
||||
<link rel="apple-touch-icon" href="https://rogueliketutorials.com/images/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="https://rogueliketutorials.com/images/apple-touch-icon.png">
|
||||
|
||||
<link rel="manifest" href="https://rogueliketutorials.com/site.webmanifest">
|
||||
<link rel="mask-icon" href="https://rogueliketutorials.com/images/safari-pinned-tab.svg" color="#5bbad5">
|
||||
|
||||
|
||||
|
||||
|
||||
<meta name="generator" content="Hugo 0.110.0">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<style>:is([id*='google_ads_iframe'],[id*='taboola-'],.taboolaHeight,.taboola-placeholder,#top-ad,#credential_picker_container,#credentials-picker-container,#credential_picker_iframe,[id*='google-one-tap-iframe'],#google-one-tap-popup-container,.google-one-tap__module,.google-one-tap-modal-div,#amp_floatingAdDiv,#ez-content-blocker-container) {display:none!important;min-height:0!important;height:0!important;}</style></head>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<body class="colorscheme-dark vsc-initialized">
|
||||
|
||||
<div class="float-container">
|
||||
<a id="dark-mode-toggle" class="colorscheme-toggle">
|
||||
<i class="fa fa-adjust fa-fw" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<main class="wrapper">
|
||||
<nav class="navigation">
|
||||
<section class="container">
|
||||
<a class="navigation-title" href="https://rogueliketutorials.com/">
|
||||
Roguelike Tutorials
|
||||
</a>
|
||||
|
||||
<input type="checkbox" id="menu-toggle">
|
||||
<label class="menu-button float-right" for="menu-toggle">
|
||||
<i class="fa fa-bars fa-fw" aria-hidden="true"></i>
|
||||
</label>
|
||||
<ul class="navigation-list">
|
||||
|
||||
|
||||
<li class="navigation-item">
|
||||
<a class="navigation-link" href="https://rogueliketutorials.com/">Home</a>
|
||||
</li>
|
||||
|
||||
<li class="navigation-item">
|
||||
<a class="navigation-link" href="https://rogueliketutorials.com/tutorials/tcod/v2/">TCOD Tutorial (2020)</a>
|
||||
</li>
|
||||
|
||||
<li class="navigation-item">
|
||||
<a class="navigation-link" href="https://rogueliketutorials.com/tutorials/tcod/2019/">TCOD Tutorial (2019)</a>
|
||||
</li>
|
||||
|
||||
<li class="navigation-item">
|
||||
<a class="navigation-link" href="https://rogueliketutorials.com/about/">About</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</section>
|
||||
</nav>
|
||||
|
||||
|
||||
<div class="content">
|
||||
|
||||
<section class="container page">
|
||||
<article>
|
||||
<header>
|
||||
<h1 class="title">
|
||||
<a class="title-link" href="https://rogueliketutorials.com/tutorials/tcod/v2/part-1/">
|
||||
Part 1 - Drawing the '@' symbol and moving it around
|
||||
</a>
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<p>Welcome to part 1 of this tutorial! This series will help you create your very first roguelike game, written in Python!</p>
|
||||
<p>This tutorial is largely based off the <a href="http://www.roguebasin.com/index.php?title=Complete_Roguelike_Tutorial,_using_python%2Blibtcod">one found on Roguebasin</a>.
|
||||
Many of the design decisions were mainly to keep this tutorial in
|
||||
lockstep
|
||||
with that one (at least in terms of chapter composition and general
|
||||
direction). This tutorial would not have been possible without the
|
||||
guidance of those who wrote that tutorial, along with all the wonderful
|
||||
contributors to tcod and python-tcod over the years.</p>
|
||||
<p>This part assumes that you have either checked <a href="https://rogueliketutorials.com/tutorials/tcod/part-0">Part 0</a>
|
||||
and are already set up and ready to go. If not, be sure to check that
|
||||
page, and make sure that you’ve got Python and TCOD installed, and a
|
||||
file called <code>main.py</code> created in the directory that you want to work in.</p>
|
||||
<p>Assuming that you’ve done all that, let’s get started. Modify (or create, if you haven’t already) the file <code>main.py</code> to look like this:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env python3</span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> tcod
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>():
|
||||
</span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">"Hello World!"</span>)
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">"__main__"</span>:
|
||||
</span></span><span style="display:flex;"><span> main()</span></span></code></pre></div>
|
||||
<p>You can run the program like any other Python program, but for those who are brand new, you do that by typing <code>python main.py</code> in the terminal. If you have both Python 2 and 3 installed on your machine, you might have to use <code>python3 main.py</code> to run (it depends on your default python, and whether you’re using a virtualenv or not).</p>
|
||||
<p>Alternatively, because of the first line, <code>#!usr/bin/env python</code>, you can run the program by typing <code>./main.py</code>,
|
||||
assuming you’ve either activated your virtual environment, or installed
|
||||
tcod on your base Python installation. This line is called a “shebang”.</p>
|
||||
<p>Okay, not the most exciting program in the world, I admit, but we’ve
|
||||
already got our first major difference from the other tutorial. Namely,
|
||||
this funky looking thing here:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">"__main__"</span>:
|
||||
</span></span><span style="display:flex;"><span> main()</span></span></code></pre></div>
|
||||
<p>So what does that do? Basically, we’re saying that we’re only going
|
||||
to run the “main” function when we explicitly run the script, using <code>python main.py</code>. It’s not super important that you understand this now, but if you want a more detailed explanation, <a href="https://stackoverflow.com/a/419185">this answer on Stack Overflow</a> gives a pretty good overview.</p>
|
||||
<p>Confirm that the above program runs (if not, there’s probably an
|
||||
issue with your tcod setup). Once that’s done, we can move on to bigger
|
||||
and better things. The first major step to creating any roguelike is
|
||||
getting an ‘@’ character on the screen and moving, so let’s get started
|
||||
with that.</p>
|
||||
<p>Modify <code>main.py</code> to look like this:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#75715e">#!/usr/bin/env python3</span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> tcod
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">main</span>() <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||||
</span></span><span style="display:flex;"><span> screen_width <span style="color:#f92672">=</span> <span style="color:#ae81ff">80</span>
|
||||
</span></span><span style="display:flex;"><span> screen_height <span style="color:#f92672">=</span> <span style="color:#ae81ff">50</span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> tileset <span style="color:#f92672">=</span> tcod<span style="color:#f92672">.</span>tileset<span style="color:#f92672">.</span>load_tilesheet(
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"dejavu10x10_gs_tc.png"</span>, <span style="color:#ae81ff">32</span>, <span style="color:#ae81ff">8</span>, tcod<span style="color:#f92672">.</span>tileset<span style="color:#f92672">.</span>CHARMAP_TCOD
|
||||
</span></span><span style="display:flex;"><span> )
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">with</span> tcod<span style="color:#f92672">.</span>context<span style="color:#f92672">.</span>new_terminal(
|
||||
</span></span><span style="display:flex;"><span> screen_width,
|
||||
</span></span><span style="display:flex;"><span> screen_height,
|
||||
</span></span><span style="display:flex;"><span> tileset<span style="color:#f92672">=</span>tileset,
|
||||
</span></span><span style="display:flex;"><span> title<span style="color:#f92672">=</span><span style="color:#e6db74">"Yet Another Roguelike Tutorial"</span>,
|
||||
</span></span><span style="display:flex;"><span> vsync<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>,
|
||||
</span></span><span style="display:flex;"><span> ) <span style="color:#66d9ef">as</span> context:
|
||||
</span></span><span style="display:flex;"><span> root_console <span style="color:#f92672">=</span> tcod<span style="color:#f92672">.</span>Console(screen_width, screen_height, order<span style="color:#f92672">=</span><span style="color:#e6db74">"F"</span>)
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span>:
|
||||
</span></span><span style="display:flex;"><span> root_console<span style="color:#f92672">.</span>print(x<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>, y<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>, string<span style="color:#f92672">=</span><span style="color:#e6db74">"@"</span>)
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> context<span style="color:#f92672">.</span>present(root_console)
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> event <span style="color:#f92672">in</span> tcod<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>wait():
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> event<span style="color:#f92672">.</span>type <span style="color:#f92672">==</span> <span style="color:#e6db74">"QUIT"</span>:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">raise</span> <span style="color:#a6e22e">SystemExit</span>()
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> __name__ <span style="color:#f92672">==</span> <span style="color:#e6db74">"__main__"</span>:
|
||||
</span></span><span style="display:flex;"><span> main()</span></span></code></pre></div>
|
||||
<p>Run <code>main.py</code> again, and you should see an ‘@’ symbol on
|
||||
the screen. Once you’ve fully soaked in the glory on the screen in front
|
||||
of you, you can click the “X” in the top-left corner of the program to
|
||||
close it.</p>
|
||||
<p>There’s a lot going on here, so let’s break it down line by line.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> screen_width <span style="color:#f92672">=</span> <span style="color:#ae81ff">80</span>
|
||||
</span></span><span style="display:flex;"><span> screen_height <span style="color:#f92672">=</span> <span style="color:#ae81ff">50</span></span></span></code></pre></div>
|
||||
<p>This is simple enough. We’re defining some variables for the screen size.</p>
|
||||
<p>Eventually, we’ll load these values from a JSON file rather than hard
|
||||
coding them in the source, but we won’t worry about that until we have
|
||||
some more variables like this.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> tileset <span style="color:#f92672">=</span> tcod<span style="color:#f92672">.</span>tileset<span style="color:#f92672">.</span>load_tilesheet(
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"dejavu10x10_gs_tc.png"</span>, <span style="color:#ae81ff">32</span>, <span style="color:#ae81ff">8</span>, tcod<span style="color:#f92672">.</span>tileset<span style="color:#f92672">.</span>CHARMAP_TCOD
|
||||
</span></span><span style="display:flex;"><span> )</span></span></code></pre></div>
|
||||
<p>Here, we’re telling tcod which font to use. The <code>"dejavu10x10_gs_tc.png"</code> bit is the actual file we’re reading from (this should exist in your project folder).</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">with</span> tcod<span style="color:#f92672">.</span>context<span style="color:#f92672">.</span>new_terminal(
|
||||
</span></span><span style="display:flex;"><span> screen_width,
|
||||
</span></span><span style="display:flex;"><span> screen_height,
|
||||
</span></span><span style="display:flex;"><span> tileset<span style="color:#f92672">=</span>tileset
|
||||
</span></span><span style="display:flex;"><span> title<span style="color:#f92672">=</span><span style="color:#e6db74">"Yet Another Roguelike Tutorial"</span>,
|
||||
</span></span><span style="display:flex;"><span> vsync<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>,
|
||||
</span></span><span style="display:flex;"><span> ) <span style="color:#66d9ef">as</span> context:</span></span></code></pre></div>
|
||||
<p>This part is what actually creates the screen. We’re giving it the <code>screen_width</code> and <code>screen_height</code>
|
||||
values from before (80 and 50, respectively), along with a title
|
||||
(change this if you’ve already got your game’s name figured out). <code>tileset</code> uses the tileset we defined earlier. and <code>vsync</code> will either enable or disable vsync, which shouldn’t matter too much in our case.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> root_console <span style="color:#f92672">=</span> tcod<span style="color:#f92672">.</span>Console(screen_width, screen_height, order<span style="color:#f92672">=</span><span style="color:#e6db74">"F"</span>)</span></span></code></pre></div>
|
||||
<p>This creates our “console” which is what we’ll be drawing to. We also
|
||||
set this console’s width and height to the same as our new terminal.
|
||||
The “order” argument affects the order of our x and y variables in numpy
|
||||
(an underlying library that tcod uses). By default, numpy accesses 2D
|
||||
arrays in [y, x] order, which is fairly unintuitive. By setting <code>order="F"</code>, we can change this to be [x, y] instead. This will make more sense once we start drawing the map.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span>:</span></span></code></pre></div>
|
||||
<p>This is what’s called our ‘game loop’. Basically, this is a loop that
|
||||
won’t ever end, until we close the screen. Every game has some sort of
|
||||
game loop or another.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> root_console<span style="color:#f92672">.</span>print(x<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>, y<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>, string<span style="color:#f92672">=</span><span style="color:#e6db74">"@"</span>)</span></span></code></pre></div>
|
||||
<p>This line is what tells the program to actually put the “@” symbol on the screen in its proper place. We’re telling the <code>root_console</code> we created to <code>print</code> the “@” symbol at the given x and y coordinates. Try changing the x and y values and see what happens, if you feel so inclined.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> context<span style="color:#f92672">.</span>present(root_console)</span></span></code></pre></div>
|
||||
<p>Without this line, nothing would actually print out on the screen. This is because <code>context.present</code> is what actually updates the screen with what we’ve told it to display so far.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> event <span style="color:#f92672">in</span> tcod<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>wait():
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> event<span style="color:#f92672">.</span>type <span style="color:#f92672">==</span> <span style="color:#e6db74">"QUIT"</span>:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">raise</span> <span style="color:#a6e22e">SystemExit</span>()</span></span></code></pre></div>
|
||||
<p>This part gives us a way to gracefully exit (i.e. not crashing) the program by hitting the <code>X</code> button in the console’s window. The line <code>for event in tcod.event.wait()</code>
|
||||
will wait for some sort of input from the user (mouse clicks, keyboard
|
||||
strokes, etc.) and loop through each event that happened. <code>SystemExit()</code> tells Python to quit the current running program.</p>
|
||||
<p>Alright, our “@” symbol is successfully displayed on the screen, but
|
||||
we can’t rest just yet. We still need to get it moving around!</p>
|
||||
<p>We need to keep track of the player’s position at all times. Since
|
||||
this is a 2D game, we can express this in two data points: the <code>x</code> and <code>y</code> coordinates. Let’s create two variables, <code>player_x</code> and <code>player_y</code>, to keep track of this.</p>
|
||||
<div>
|
||||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||||
Diff
|
||||
</button>
|
||||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||||
Original
|
||||
</button>
|
||||
|
||||
|
||||
<div class="data-pane active" data-pane="diff">
|
||||
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> ...
|
||||
</span></span><span style="display:flex;"><span> screen_height = 50
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ player_x = int(screen_width / 2)
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ player_y = int(screen_height / 2)
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> tileset = tcod.tileset.load_tilesheet(
|
||||
</span></span><span style="display:flex;"><span> "dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD
|
||||
</span></span><span style="display:flex;"><span> )
|
||||
</span></span><span style="display:flex;"><span> ...
|
||||
</span></span></code></pre></div>
|
||||
|
||||
</div>
|
||||
<div class="data-pane" data-pane="original">
|
||||
|
||||
<pre> ...
|
||||
screen_height = 50
|
||||
<span class="new-text">
|
||||
player_x = int(screen_width / 2)
|
||||
player_y = int(screen_height / 2)
|
||||
</span>
|
||||
tileset = tcod.tileset.load_tilesheet(
|
||||
"dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD
|
||||
)
|
||||
...</pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<p><em>Note: Ellipses denote omitted parts of the code. I’ll include
|
||||
lines around the code to be inserted so that you’ll know exactly where
|
||||
to put new pieces of code, but I won’t be showing the entire file every
|
||||
time. The green lines denote code that you should be adding.</em></p>
|
||||
<p>We’re placing the player right in the middle of the screen. What’s with the <code>int()</code>
|
||||
function though? Well, Python 3 doesn’t automatically
|
||||
truncate division like Python 2 does, so we have to cast the division
|
||||
result (a float) to an integer. If we don’t, tcod will give an error.</p>
|
||||
<p><em>Note: It’s been pointed out that you could divide with <code>//</code> instead of <code>/</code>
|
||||
and achieve the same effect. This is true, except in cases where, for
|
||||
whatever reason, one of the numbers given is a decimal. For example, <code>screen_width // 2.0</code> will give an error. That shouldn’t happen in this case, but wrapping the function in <code>int()</code> gives us certainty that this won’t ever happen.</em></p>
|
||||
<p>We also have to modify the command to put the ‘@’ symbol to use these new coordinates.</p>
|
||||
<div>
|
||||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||||
Diff
|
||||
</button>
|
||||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||||
Original
|
||||
</button>
|
||||
|
||||
|
||||
<div class="data-pane active" data-pane="diff">
|
||||
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> ...
|
||||
</span></span><span style="display:flex;"><span> while True:
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- root_console.print(x=1, y=1, string="@")
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ root_console.print(x=player_x, y=player_y, string="@")
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||||
</span></span><span style="display:flex;"><span> context.present(root_console)
|
||||
</span></span><span style="display:flex;"><span> ...
|
||||
</span></span></code></pre></div>
|
||||
|
||||
</div>
|
||||
<div class="data-pane" data-pane="original">
|
||||
|
||||
<pre> ...
|
||||
while True:
|
||||
<span class="crossed-out-text">root_console.print(x=1, y=1, string="@")</span>
|
||||
<span class="new-text">root_console.print(x=player_x, y=player_y, string="@")</span>
|
||||
|
||||
context.present(root_console)
|
||||
...</pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<p><em>Note: The red lines denote code that has been removed.</em></p>
|
||||
<p>Run the code now and you should see the ‘@’ in the center of the screen. Let’s take care of moving it around now.</p>
|
||||
<p>So, how do we actually capture the user’s input? TCOD makes this
|
||||
pretty easy, and in fact, we’re already doing it. This line takes care
|
||||
of it for us:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> event <span style="color:#f92672">in</span> tcod<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>wait():</span></span></code></pre></div>
|
||||
<p>It gets the “events”, which we can then process. Events range from
|
||||
mouse movements to keyboard strokes. Let’s start by getting some basic
|
||||
keyboard commands and processing them, and based on what we get, we’ll
|
||||
move our little “@” symbol around.</p>
|
||||
<p>We <em>could</em> identify which key is being pressed right here in <code>main.py</code>,
|
||||
but this is a good opportunity to break our project up a little bit.
|
||||
Sooner or later, we’re going to have quite a few potential keyboard
|
||||
commands, so putting them all in <code>main.py</code> would make the file longer than it needs to be. Maybe we should import what we need into <code>main.py</code> rather than writing it all there.</p>
|
||||
<p>To handle the keyboard inputs and the actions associated with them, let’s actually create <em>two</em>
|
||||
new files. One will hold the different types of “actions” our rogue can
|
||||
perform, and the other will bridge the gap between the keys we press
|
||||
and those actions.</p>
|
||||
<p>Create two new Python files in your project’s directory, one called <code>input_handlers.py</code>, and the other called <code>actions.py</code>. Let’s fill out <code>actions.py</code> first:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Action</span>:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">pass</span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">EscapeAction</span>(Action):
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">pass</span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">MovementAction</span>(Action):
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, dx: int, dy: int):
|
||||
</span></span><span style="display:flex;"><span> super()<span style="color:#f92672">.</span>__init__()
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>dx <span style="color:#f92672">=</span> dx
|
||||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>dy <span style="color:#f92672">=</span> dy</span></span></code></pre></div>
|
||||
<p>We define three classes: <code>Action</code>, <code>EscapeAction</code>, and <code>MovementAction</code>. <code>EscapeAction</code> and <code>MovementAction</code> are subclasses of <code>Action</code>.</p>
|
||||
<p>So what’s the plan for these classes? Basically, whenever we have an “action”, we’ll use one of the subclasses of <code>Action</code> to describe it. We’ll be able to detect which subclass we’re using, and respond accordingly. In this case, <code>EscapeAction</code> will be when we hit the <code>Esc</code> key (to exit the game), and <code>MovementAction</code> will be used to describe our player moving around.</p>
|
||||
<p>There might be instances where we need to know more than just the “type” of action, like in the case of <code>MovementAction</code>. There, we need to know not only that we’re trying to move, but in which direction. Therefore, we can pass the <code>dx</code> and <code>dy</code> arguments to <code>MovementAction</code>, which will tell us where the player is trying to move to. Other <code>Action</code> subclasses might contain additional data as well, and others might just be subclasses with nothing else in them, like <code>EscapeAction</code>.</p>
|
||||
<p>That’s all we need to do in <code>actions.py</code> right now. Let’s fill out <code>input_handlers.py</code>, which will use the <code>Action</code> class and subclasses we just created:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#f92672">from</span> typing <span style="color:#f92672">import</span> Optional
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">import</span> tcod.event
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> actions <span style="color:#f92672">import</span> Action, EscapeAction, MovementAction
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">EventHandler</span>(tcod<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>EventDispatch[Action]):
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">ev_quit</span>(self, event: tcod<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>Quit) <span style="color:#f92672">-></span> Optional[Action]:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">raise</span> <span style="color:#a6e22e">SystemExit</span>()
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">ev_keydown</span>(self, event: tcod<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>KeyDown) <span style="color:#f92672">-></span> Optional[Action]:
|
||||
</span></span><span style="display:flex;"><span> action: Optional[Action] <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> key <span style="color:#f92672">=</span> event<span style="color:#f92672">.</span>sym
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> key <span style="color:#f92672">==</span> tcod<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>K_UP:
|
||||
</span></span><span style="display:flex;"><span> action <span style="color:#f92672">=</span> MovementAction(dx<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>, dy<span style="color:#f92672">=-</span><span style="color:#ae81ff">1</span>)
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">elif</span> key <span style="color:#f92672">==</span> tcod<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>K_DOWN:
|
||||
</span></span><span style="display:flex;"><span> action <span style="color:#f92672">=</span> MovementAction(dx<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>, dy<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>)
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">elif</span> key <span style="color:#f92672">==</span> tcod<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>K_LEFT:
|
||||
</span></span><span style="display:flex;"><span> action <span style="color:#f92672">=</span> MovementAction(dx<span style="color:#f92672">=-</span><span style="color:#ae81ff">1</span>, dy<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>)
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">elif</span> key <span style="color:#f92672">==</span> tcod<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>K_RIGHT:
|
||||
</span></span><span style="display:flex;"><span> action <span style="color:#f92672">=</span> MovementAction(dx<span style="color:#f92672">=</span><span style="color:#ae81ff">1</span>, dy<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>)
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">elif</span> key <span style="color:#f92672">==</span> tcod<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>K_ESCAPE:
|
||||
</span></span><span style="display:flex;"><span> action <span style="color:#f92672">=</span> EscapeAction()
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># No valid key was pressed</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> action</span></span></code></pre></div>
|
||||
<p>Let’s go over what we’ve added.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#f92672">from</span> typing <span style="color:#f92672">import</span> Optional</span></span></code></pre></div>
|
||||
<p>This is part of Python’s type hinting system (which you don’t have to include in your project). <code>Optional</code> denotes something that could be set to <code>None</code>.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#f92672">import</span> tcod.event
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> actions <span style="color:#f92672">import</span> Action, EscapeAction, MovementAction</span></span></code></pre></div>
|
||||
<p>We’re importing <code>tcod.event</code> so that we can use tcod’s event system. We don’t need to import <code>tcod</code>, as we only need the contents of <code>event</code>.</p>
|
||||
<p>The next line imports the <code>Action</code> class and its subclasses that we just created.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">EventHandler</span>(tcod<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>EventDispatch[Action]):</span></span></code></pre></div>
|
||||
<p>We’re creating a class called <code>EventHandler</code>, which is a subclass of tcod’s <code>EventDispatch</code> class. <code>EventDispatch</code>
|
||||
is a class that allows us to send an event to its proper method based
|
||||
on what type of event it is. Let’s take a look at the methods we’re
|
||||
creating for <code>EventHandler</code> to see a few examples of this.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">ev_quit</span>(self, event: tcod<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>Quit) <span style="color:#f92672">-></span> Optional[Action]:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">raise</span> <span style="color:#a6e22e">SystemExit</span>()</span></span></code></pre></div>
|
||||
<p>Here’s an example of us using a method of <code>EventDispatch</code>: <code>ev_quit</code> is a method defined in <code>EventDispatch</code>, which we’re overriding in <code>EventHandler</code>. <code>ev_quit</code>
|
||||
is called when we receive a “quit” event, which happens when we click
|
||||
the “X” in the window of the program. In that case, we want to quit the
|
||||
program, so we raise <code>SystemExit()</code> to do so.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">ev_keydown</span>(self, event: tcod<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>KeyDown) <span style="color:#f92672">-></span> Optional[Action]:</span></span></code></pre></div>
|
||||
<p>This method will receive key press events, and return either an <code>Action</code> subclass, or <code>None</code>, if no valid key was pressed.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> action: Optional[Action] <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> key <span style="color:#f92672">=</span> event<span style="color:#f92672">.</span>sym</span></span></code></pre></div>
|
||||
<p><code>action</code> is the variable that will hold whatever subclass of <code>Action</code> we end up assigning it to. If no valid key press is found, it will remain set to <code>None</code>. We’ll return it either way.</p>
|
||||
<p><code>key</code> holds the actual key we pressed. It doesn’t contain additional information about modifiers like <code>Shift</code> or <code>Alt</code>, just the actual key that was pressed. That’s all we need right now.</p>
|
||||
<p>From there, we go down a list of possible keys pressed. For example:</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> key <span style="color:#f92672">==</span> tcod<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>K_UP:
|
||||
</span></span><span style="display:flex;"><span> action <span style="color:#f92672">=</span> MovementAction(dx<span style="color:#f92672">=</span><span style="color:#ae81ff">0</span>, dy<span style="color:#f92672">=-</span><span style="color:#ae81ff">1</span>)</span></span></code></pre></div>
|
||||
<p>In this case, the user pressed the up-arrow key, so we’re creating a <code>MovementAction</code>. Notice that here (and in all the other cases of <code>MovementAction</code>) we provide <code>dx</code> and <code>dy</code>. These describe which direction our character will move in.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">elif</span> key <span style="color:#f92672">==</span> tcod<span style="color:#f92672">.</span>event<span style="color:#f92672">.</span>K_ESCAPE:
|
||||
</span></span><span style="display:flex;"><span> action <span style="color:#f92672">=</span> EscapeAction()</span></span></code></pre></div>
|
||||
<p>If the user pressed the “Escape” key, we return <code>EscapeAction</code>. We’ll use this to exit the game for now, though in the future, <code>EscapeAction</code> can be used to do things like exit menus.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> action</span></span></code></pre></div>
|
||||
<p>Whether <code>action</code> is assigned to an <code>Action</code> subclass or <code>None</code>, we return it.</p>
|
||||
<p>Let’s put our new actions and input handlers to use in <code>main.py</code>. Edit <code>main.py</code> like this:</p>
|
||||
<div>
|
||||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||||
Diff
|
||||
</button>
|
||||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||||
Original
|
||||
</button>
|
||||
|
||||
|
||||
<div class="data-pane active" data-pane="diff">
|
||||
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>#!/usr/bin/env python3
|
||||
</span></span><span style="display:flex;"><span>import tcod
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+from actions import EscapeAction, MovementAction
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+from input_handlers import EventHandler
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span>def main() -> None:
|
||||
</span></span><span style="display:flex;"><span> screen_width = 80
|
||||
</span></span><span style="display:flex;"><span> screen_height = 50
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> player_x = int(screen_width / 2)
|
||||
</span></span><span style="display:flex;"><span> player_y = int(screen_height / 2)
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> tileset = tcod.tileset.load_tilesheet(
|
||||
</span></span><span style="display:flex;"><span> "dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD
|
||||
</span></span><span style="display:flex;"><span> )
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ event_handler = EventHandler()
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||||
</span></span><span style="display:flex;"><span> with tcod.context.new_terminal(
|
||||
</span></span><span style="display:flex;"><span> ...
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> ...
|
||||
</span></span><span style="display:flex;"><span> for event in tcod.event.wait():
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- if event.type == "QUIT":
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- raise SystemExit()
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ action = event_handler.dispatch(event)
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if action is None:
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ continue
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if isinstance(action, MovementAction):
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ player_x += action.dx
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ player_y += action.dy
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ elif isinstance(action, EscapeAction):
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ raise SystemExit()
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span>if __name__ == "__main__":
|
||||
</span></span><span style="display:flex;"><span> main()
|
||||
</span></span></code></pre></div>
|
||||
|
||||
</div>
|
||||
<div class="data-pane" data-pane="original">
|
||||
|
||||
<pre>#!/usr/bin/env python3
|
||||
import tcod
|
||||
|
||||
<span class="new-text">from actions import EscapeAction, MovementAction
|
||||
from input_handlers import EventHandler</span>
|
||||
|
||||
|
||||
def main() -> None:
|
||||
screen_width = 80
|
||||
screen_height = 50
|
||||
|
||||
player_x = int(screen_width / 2)
|
||||
player_y = int(screen_height / 2)
|
||||
|
||||
tileset = tcod.tileset.load_tilesheet(
|
||||
"dejavu10x10_gs_tc.png", 32, 8, tcod.tileset.CHARMAP_TCOD
|
||||
)
|
||||
|
||||
<span class="new-text">event_handler = EventHandler()</span>
|
||||
|
||||
with tcod.context.new_terminal(
|
||||
...
|
||||
|
||||
...
|
||||
for event in tcod.event.wait():
|
||||
<span class="crossed-out-text">if event.type == "QUIT":</span>
|
||||
<span class="crossed-out-text">raise SystemExit()</span>
|
||||
<span class="new-text">
|
||||
action = event_handler.dispatch(event)
|
||||
|
||||
if action is None:
|
||||
continue
|
||||
|
||||
if isinstance(action, MovementAction):
|
||||
player_x += action.dx
|
||||
player_y += action.dy
|
||||
|
||||
elif isinstance(action, EscapeAction):
|
||||
raise SystemExit()</span>
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()</pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<p>Let’s break down the new additions a bit.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#f92672">from</span> actions <span style="color:#f92672">import</span> EscapeAction, MovementAction
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> input_handlers <span style="color:#f92672">import</span> EventHandler</span></span></code></pre></div>
|
||||
<p>We’re importing the <code>EscapeAction</code> and <code>MovementAction</code> from <code>actions</code>, and <code>EventHandler</code> from <code>input_handlers</code>. This allows us to use the functions we wrote in those files in our <code>main</code> file.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> event_handler <span style="color:#f92672">=</span> EventHandler()</span></span></code></pre></div>
|
||||
<p><code>event_handler</code> is an instance of our <code>EventHandler</code> class. We’ll use it to receive events and process them.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> action <span style="color:#f92672">=</span> event_handler<span style="color:#f92672">.</span>dispatch(event)</span></span></code></pre></div>
|
||||
<p>We send the <code>event</code> to our <code>event_handler</code>’s “dispatch” method, which sends the event to its proper place. In this case, a keyboard event will be sent to the <code>ev_keydown</code> method we wrote. The <code>Action</code> returned from that method is assigned to our local <code>action</code> variable.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> action <span style="color:#f92672">is</span> <span style="color:#66d9ef">None</span>:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">continue</span></span></span></code></pre></div>
|
||||
<p>This is pretty straightforward: If <code>action</code> is <code>None</code>
|
||||
(that is, no key was pressed, or the key pressed isn’t recognized),
|
||||
then we skip over the rest the loop. There’s no need to go any further,
|
||||
since the lines below are going to handle the valid key presses.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> isinstance(action, MovementAction):
|
||||
</span></span><span style="display:flex;"><span> player_x <span style="color:#f92672">+=</span> action<span style="color:#f92672">.</span>dx
|
||||
</span></span><span style="display:flex;"><span> player_y <span style="color:#f92672">+=</span> action<span style="color:#f92672">.</span>dy</span></span></code></pre></div>
|
||||
<p>Now we arrive at the interesting part. If the <code>action</code> is an instance of the class <code>MovementAction</code>, we need to move our “@” symbol. We grab the <code>dx</code> and <code>dy</code> values we gave to <code>MovementAction</code> earlier, which will move the “@” symbol in which direction we want it to move. <code>dx</code> and <code>dy</code>, as of now, will only ever be -1, 0, or 1. Regardless of what the value is, we add <code>dx</code> and <code>dy</code> to <code>player_x</code> and <code>player_y</code>, respectively. Because the console is using <code>player_x</code> and <code>player_y</code> to draw where our “@” symbol is, modifying these two variables will cause the symbol to move.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">elif</span> isinstance(action, EscapeAction):
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">raise</span> <span style="color:#a6e22e">SystemExit</span>()</span></span></code></pre></div>
|
||||
<p><code>raise SystemExit()</code> should look familiar: it’s how we’re quitting out of the program. So basically, if the user hits the <code>Esc</code> key, our program should exit.</p>
|
||||
<p>With all that done, let’s run the program and see what happens!</p>
|
||||
<p>Indeed, our “@” symbol does move, but… it’s perhaps not what was expected.</p>
|
||||
<p><img src="Part%201%20-%20Drawing%20the%20'@'%20symbol%20and%20moving%20it%20around%20%C2%B7%20Roguelike%20Tutorials_files/snake_the_roguelike.png" alt="Snake the Roguelike?" title="Snake the Roguelike?"></p>
|
||||
<p>Unless you’re making a roguelike version of “Snake” (and who knows,
|
||||
maybe you are), we need to fix the “@” symbol being left behind wherever
|
||||
we move. So why is this happening in the first place?</p>
|
||||
<p>Turns out, we need to “clear” the console after we’ve drawn it, or
|
||||
we’ll get these leftovers when we draw symbols in their new places.
|
||||
Luckily, this is as easy as adding one line:</p>
|
||||
<div>
|
||||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||||
Diff
|
||||
</button>
|
||||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||||
Original
|
||||
</button>
|
||||
|
||||
|
||||
<div class="data-pane active" data-pane="diff">
|
||||
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span> ...
|
||||
</span></span><span style="display:flex;"><span> while True:
|
||||
</span></span><span style="display:flex;"><span> root_console.print(x=player_x, y=player_y, string="@")
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> context.present(root_console)
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ root_console.clear()
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||||
</span></span><span style="display:flex;"><span> for event in tcod.event.wait():
|
||||
</span></span><span style="display:flex;"><span> ...
|
||||
</span></span></code></pre></div>
|
||||
|
||||
</div>
|
||||
<div class="data-pane" data-pane="original">
|
||||
|
||||
<pre> ...
|
||||
while True:
|
||||
root_console.print(x=player_x, y=player_y, string="@")
|
||||
|
||||
context.present(root_console)
|
||||
|
||||
<span class="new-text">root_console.clear()</span>
|
||||
|
||||
for event in tcod.event.wait():
|
||||
...</pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<p>That’s it! Run the project now, and the “@” symbol will move around, without leaving traces of itself behind.</p>
|
||||
<p>That wraps up part one of this tutorial! If you’re using git or some
|
||||
other form of version control (and I recommend you do), commit your
|
||||
changes now.</p>
|
||||
<p>If you want to see the code so far in its entirety, <a href="https://github.com/TStand90/tcod_tutorial_v2/tree/2020/part-1">click
|
||||
here</a>.</p>
|
||||
<p><a href="https://rogueliketutorials.com/tutorials/tcod/v2/part-2">Click here to move on to the next part of this
|
||||
tutorial.</a></p>
|
||||
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<section class="container">
|
||||
©
|
||||
|
||||
2023
|
||||
|
||||
·
|
||||
|
||||
Powered by <a href="https://gohugo.io/">Hugo</a> & <a href="https://github.com/luizdepra/hugo-coder/">Coder</a>.
|
||||
|
||||
</section>
|
||||
</footer>
|
||||
|
||||
</main>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="Part%201%20-%20Drawing%20the%20'@'%20symbol%20and%20moving%20it%20around%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.236049395dc3682fb2719640872958e12f1f24067bb09c327b2.js" integrity="sha256-I2BJOV3DaC+ycZZAhylY4S8fJAZ7sJwyeyM+YpDH7aw="></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="Part%201%20-%20Drawing%20the%20'@'%20symbol%20and%20moving%20it%20around%20%C2%B7%20Roguelike%20Tutorials_files/codetabs.min.cc52451e7f25e50f64c1c893826f606d58410d742c214dce.js" integrity="sha256-zFJFHn8l5Q9kwciTgm9gbVhBDXQsIU3OI/tEfJlh8rA="></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</body></html>
|
|
@ -1 +0,0 @@
|
|||
const body=document.body,darkModeToggle=document.getElementById("dark-mode-toggle"),darkModeMediaQuery=window.matchMedia("(prefers-color-scheme: dark)");localStorage.getItem("colorscheme")?setTheme(localStorage.getItem("colorscheme")):setTheme(body.classList.contains("colorscheme-light")||body.classList.contains("colorscheme-dark")?body.classList.contains("colorscheme-dark")?"dark":"light":darkModeMediaQuery.matches?"dark":"light"),darkModeToggle&&darkModeToggle.addEventListener("click",()=>{let e=body.classList.contains("colorscheme-dark")?"light":"dark";setTheme(e),rememberTheme(e)}),darkModeMediaQuery.addListener(e=>{setTheme(e.matches?"dark":"light")}),document.addEventListener("DOMContentLoaded",function(){let e=document.querySelector(".preload-transitions");e.classList.remove("preload-transitions")});function setTheme(e){body.classList.remove("colorscheme-auto");let n=e==="dark"?"light":"dark";body.classList.remove("colorscheme-"+n),body.classList.add("colorscheme-"+e),document.documentElement.style["color-scheme"]=e;function t(e){return new Promise(t=>{if(document.querySelector(e))return t(document.querySelector(e));const n=new MutationObserver(s=>{document.querySelector(e)&&(t(document.querySelector(e)),n.disconnect())});n.observe(document.body,{childList:!0,subtree:!0})})}if(e==="dark"){const e={type:"set-theme",theme:"github-dark"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}else{const e={type:"set-theme",theme:"github-light"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}}function rememberTheme(e){localStorage.setItem("colorscheme",e)}
|
|
@ -1 +0,0 @@
|
|||
var allTabs=document.querySelectorAll("[data-toggle-tab]"),allPanes=document.querySelectorAll("[data-pane]");function toggleTabs(e){if(e.target){e.preventDefault();var n,s,o=e.currentTarget,t=o.getAttribute("data-toggle-tab")}else t=e;window.localStorage&&window.localStorage.setItem("configLangPref",t),n=document.querySelectorAll("[data-toggle-tab='"+t+"']"),s=document.querySelectorAll("[data-pane='"+t+"']");for(let e=0;e<allTabs.length;e++)allTabs[e].classList.remove("active"),allPanes[e].classList.remove("active");for(let e=0;e<n.length;e++)n[e].classList.add("active"),s[e].classList.add("active")}for(let e=0;e<allTabs.length;e++)allTabs[e].addEventListener("click",toggleTabs);window.localStorage.getItem("configLangPref")&&toggleTabs(window.localStorage.getItem("configLangPref"))
|
Before Width: | Height: | Size: 8.8 KiB |
|
@ -1 +0,0 @@
|
|||
pre{border:1px solid #000;padding:15px;background-color:#272822;color:#f8f8f2;background-color:#272822}.language-diff,.language-py3{background-color:#272822!important}body.colorscheme-dart code{background-color:#272822!important}.crossed-out-text{color:#f92672;text-decoration:line-through}.new-text{color:#a6e22e}.data-pane{display:none}.data-pane.active{display:inline}
|
|
@ -1 +0,0 @@
|
|||
const body=document.body,darkModeToggle=document.getElementById("dark-mode-toggle"),darkModeMediaQuery=window.matchMedia("(prefers-color-scheme: dark)");localStorage.getItem("colorscheme")?setTheme(localStorage.getItem("colorscheme")):setTheme(body.classList.contains("colorscheme-light")||body.classList.contains("colorscheme-dark")?body.classList.contains("colorscheme-dark")?"dark":"light":darkModeMediaQuery.matches?"dark":"light"),darkModeToggle&&darkModeToggle.addEventListener("click",()=>{let e=body.classList.contains("colorscheme-dark")?"light":"dark";setTheme(e),rememberTheme(e)}),darkModeMediaQuery.addListener(e=>{setTheme(e.matches?"dark":"light")}),document.addEventListener("DOMContentLoaded",function(){let e=document.querySelector(".preload-transitions");e.classList.remove("preload-transitions")});function setTheme(e){body.classList.remove("colorscheme-auto");let n=e==="dark"?"light":"dark";body.classList.remove("colorscheme-"+n),body.classList.add("colorscheme-"+e),document.documentElement.style["color-scheme"]=e;function t(e){return new Promise(t=>{if(document.querySelector(e))return t(document.querySelector(e));const n=new MutationObserver(s=>{document.querySelector(e)&&(t(document.querySelector(e)),n.disconnect())});n.observe(document.body,{childList:!0,subtree:!0})})}if(e==="dark"){const e={type:"set-theme",theme:"github-dark"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}else{const e={type:"set-theme",theme:"github-light"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}}function rememberTheme(e){localStorage.setItem("colorscheme",e)}
|
|
@ -1 +0,0 @@
|
|||
var allTabs=document.querySelectorAll("[data-toggle-tab]"),allPanes=document.querySelectorAll("[data-pane]");function toggleTabs(e){if(e.target){e.preventDefault();var n,s,o=e.currentTarget,t=o.getAttribute("data-toggle-tab")}else t=e;window.localStorage&&window.localStorage.setItem("configLangPref",t),n=document.querySelectorAll("[data-toggle-tab='"+t+"']"),s=document.querySelectorAll("[data-pane='"+t+"']");for(let e=0;e<allTabs.length;e++)allTabs[e].classList.remove("active"),allPanes[e].classList.remove("active");for(let e=0;e<n.length;e++)n[e].classList.add("active"),s[e].classList.add("active")}for(let e=0;e<allTabs.length;e++)allTabs[e].addEventListener("click",toggleTabs);window.localStorage.getItem("configLangPref")&&toggleTabs(window.localStorage.getItem("configLangPref"))
|
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 29 KiB |
|
@ -1 +0,0 @@
|
|||
pre{border:1px solid #000;padding:15px;background-color:#272822;color:#f8f8f2;background-color:#272822}.language-diff,.language-py3{background-color:#272822!important}body.colorscheme-dart code{background-color:#272822!important}.crossed-out-text{color:#f92672;text-decoration:line-through}.new-text{color:#a6e22e}.data-pane{display:none}.data-pane.active{display:inline}
|
|
@ -1 +0,0 @@
|
|||
const body=document.body,darkModeToggle=document.getElementById("dark-mode-toggle"),darkModeMediaQuery=window.matchMedia("(prefers-color-scheme: dark)");localStorage.getItem("colorscheme")?setTheme(localStorage.getItem("colorscheme")):setTheme(body.classList.contains("colorscheme-light")||body.classList.contains("colorscheme-dark")?body.classList.contains("colorscheme-dark")?"dark":"light":darkModeMediaQuery.matches?"dark":"light"),darkModeToggle&&darkModeToggle.addEventListener("click",()=>{let e=body.classList.contains("colorscheme-dark")?"light":"dark";setTheme(e),rememberTheme(e)}),darkModeMediaQuery.addListener(e=>{setTheme(e.matches?"dark":"light")}),document.addEventListener("DOMContentLoaded",function(){let e=document.querySelector(".preload-transitions");e.classList.remove("preload-transitions")});function setTheme(e){body.classList.remove("colorscheme-auto");let n=e==="dark"?"light":"dark";body.classList.remove("colorscheme-"+n),body.classList.add("colorscheme-"+e),document.documentElement.style["color-scheme"]=e;function t(e){return new Promise(t=>{if(document.querySelector(e))return t(document.querySelector(e));const n=new MutationObserver(s=>{document.querySelector(e)&&(t(document.querySelector(e)),n.disconnect())});n.observe(document.body,{childList:!0,subtree:!0})})}if(e==="dark"){const e={type:"set-theme",theme:"github-dark"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}else{const e={type:"set-theme",theme:"github-light"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}}function rememberTheme(e){localStorage.setItem("colorscheme",e)}
|
|
@ -1 +0,0 @@
|
|||
var allTabs=document.querySelectorAll("[data-toggle-tab]"),allPanes=document.querySelectorAll("[data-pane]");function toggleTabs(e){if(e.target){e.preventDefault();var n,s,o=e.currentTarget,t=o.getAttribute("data-toggle-tab")}else t=e;window.localStorage&&window.localStorage.setItem("configLangPref",t),n=document.querySelectorAll("[data-toggle-tab='"+t+"']"),s=document.querySelectorAll("[data-pane='"+t+"']");for(let e=0;e<allTabs.length;e++)allTabs[e].classList.remove("active"),allPanes[e].classList.remove("active");for(let e=0;e<n.length;e++)n[e].classList.add("active"),s[e].classList.add("active")}for(let e=0;e<allTabs.length;e++)allTabs[e].addEventListener("click",toggleTabs);window.localStorage.getItem("configLangPref")&&toggleTabs(window.localStorage.getItem("configLangPref"))
|
Before Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 14 KiB |
|
@ -1 +0,0 @@
|
|||
pre{border:1px solid #000;padding:15px;background-color:#272822;color:#f8f8f2;background-color:#272822}.language-diff,.language-py3{background-color:#272822!important}body.colorscheme-dart code{background-color:#272822!important}.crossed-out-text{color:#f92672;text-decoration:line-through}.new-text{color:#a6e22e}.data-pane{display:none}.data-pane.active{display:inline}
|
|
@ -1 +0,0 @@
|
|||
const body=document.body,darkModeToggle=document.getElementById("dark-mode-toggle"),darkModeMediaQuery=window.matchMedia("(prefers-color-scheme: dark)");localStorage.getItem("colorscheme")?setTheme(localStorage.getItem("colorscheme")):setTheme(body.classList.contains("colorscheme-light")||body.classList.contains("colorscheme-dark")?body.classList.contains("colorscheme-dark")?"dark":"light":darkModeMediaQuery.matches?"dark":"light"),darkModeToggle&&darkModeToggle.addEventListener("click",()=>{let e=body.classList.contains("colorscheme-dark")?"light":"dark";setTheme(e),rememberTheme(e)}),darkModeMediaQuery.addListener(e=>{setTheme(e.matches?"dark":"light")}),document.addEventListener("DOMContentLoaded",function(){let e=document.querySelector(".preload-transitions");e.classList.remove("preload-transitions")});function setTheme(e){body.classList.remove("colorscheme-auto");let n=e==="dark"?"light":"dark";body.classList.remove("colorscheme-"+n),body.classList.add("colorscheme-"+e),document.documentElement.style["color-scheme"]=e;function t(e){return new Promise(t=>{if(document.querySelector(e))return t(document.querySelector(e));const n=new MutationObserver(s=>{document.querySelector(e)&&(t(document.querySelector(e)),n.disconnect())});n.observe(document.body,{childList:!0,subtree:!0})})}if(e==="dark"){const e={type:"set-theme",theme:"github-dark"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}else{const e={type:"set-theme",theme:"github-light"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}}function rememberTheme(e){localStorage.setItem("colorscheme",e)}
|
|
@ -1 +0,0 @@
|
|||
var allTabs=document.querySelectorAll("[data-toggle-tab]"),allPanes=document.querySelectorAll("[data-pane]");function toggleTabs(e){if(e.target){e.preventDefault();var n,s,o=e.currentTarget,t=o.getAttribute("data-toggle-tab")}else t=e;window.localStorage&&window.localStorage.setItem("configLangPref",t),n=document.querySelectorAll("[data-toggle-tab='"+t+"']"),s=document.querySelectorAll("[data-pane='"+t+"']");for(let e=0;e<allTabs.length;e++)allTabs[e].classList.remove("active"),allPanes[e].classList.remove("active");for(let e=0;e<n.length;e++)n[e].classList.add("active"),s[e].classList.add("active")}for(let e=0;e<allTabs.length;e++)allTabs[e].addEventListener("click",toggleTabs);window.localStorage.getItem("configLangPref")&&toggleTabs(window.localStorage.getItem("configLangPref"))
|
|
@ -1 +0,0 @@
|
|||
pre{border:1px solid #000;padding:15px;background-color:#272822;color:#f8f8f2;background-color:#272822}.language-diff,.language-py3{background-color:#272822!important}body.colorscheme-dart code{background-color:#272822!important}.crossed-out-text{color:#f92672;text-decoration:line-through}.new-text{color:#a6e22e}.data-pane{display:none}.data-pane.active{display:inline}
|
|
@ -1 +0,0 @@
|
|||
const body=document.body,darkModeToggle=document.getElementById("dark-mode-toggle"),darkModeMediaQuery=window.matchMedia("(prefers-color-scheme: dark)");localStorage.getItem("colorscheme")?setTheme(localStorage.getItem("colorscheme")):setTheme(body.classList.contains("colorscheme-light")||body.classList.contains("colorscheme-dark")?body.classList.contains("colorscheme-dark")?"dark":"light":darkModeMediaQuery.matches?"dark":"light"),darkModeToggle&&darkModeToggle.addEventListener("click",()=>{let e=body.classList.contains("colorscheme-dark")?"light":"dark";setTheme(e),rememberTheme(e)}),darkModeMediaQuery.addListener(e=>{setTheme(e.matches?"dark":"light")}),document.addEventListener("DOMContentLoaded",function(){let e=document.querySelector(".preload-transitions");e.classList.remove("preload-transitions")});function setTheme(e){body.classList.remove("colorscheme-auto");let n=e==="dark"?"light":"dark";body.classList.remove("colorscheme-"+n),body.classList.add("colorscheme-"+e),document.documentElement.style["color-scheme"]=e;function t(e){return new Promise(t=>{if(document.querySelector(e))return t(document.querySelector(e));const n=new MutationObserver(s=>{document.querySelector(e)&&(t(document.querySelector(e)),n.disconnect())});n.observe(document.body,{childList:!0,subtree:!0})})}if(e==="dark"){const e={type:"set-theme",theme:"github-dark"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}else{const e={type:"set-theme",theme:"github-light"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}}function rememberTheme(e){localStorage.setItem("colorscheme",e)}
|
|
@ -1 +0,0 @@
|
|||
var allTabs=document.querySelectorAll("[data-toggle-tab]"),allPanes=document.querySelectorAll("[data-pane]");function toggleTabs(e){if(e.target){e.preventDefault();var n,s,o=e.currentTarget,t=o.getAttribute("data-toggle-tab")}else t=e;window.localStorage&&window.localStorage.setItem("configLangPref",t),n=document.querySelectorAll("[data-toggle-tab='"+t+"']"),s=document.querySelectorAll("[data-pane='"+t+"']");for(let e=0;e<allTabs.length;e++)allTabs[e].classList.remove("active"),allPanes[e].classList.remove("active");for(let e=0;e<n.length;e++)n[e].classList.add("active"),s[e].classList.add("active")}for(let e=0;e<allTabs.length;e++)allTabs[e].addEventListener("click",toggleTabs);window.localStorage.getItem("configLangPref")&&toggleTabs(window.localStorage.getItem("configLangPref"))
|
Before Width: | Height: | Size: 13 KiB |
|
@ -1 +0,0 @@
|
|||
pre{border:1px solid #000;padding:15px;background-color:#272822;color:#f8f8f2;background-color:#272822}.language-diff,.language-py3{background-color:#272822!important}body.colorscheme-dart code{background-color:#272822!important}.crossed-out-text{color:#f92672;text-decoration:line-through}.new-text{color:#a6e22e}.data-pane{display:none}.data-pane.active{display:inline}
|
|
@ -1,292 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" style="color-scheme: dark;"><head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<title>
|
||||
Extra - A more "Traditional" Look · Roguelike Tutorials
|
||||
</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
|
||||
|
||||
|
||||
|
||||
<meta name="description" content="Prerequisites: Completion of part 4
|
||||
The tutorial itself goes in a much different visual direction than most roguelikes. If you like this look, great! If you want to make your game look a bit more like other roguelikes you might be more familiar with, this section is for you.
|
||||
Most roguelikes define the floor tiles as a period (.) and the wall tiles as a pound sign (#). This is simple enough to implement, by adjusting our tile types like this:">
|
||||
<meta name="keywords" content="">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="Extra - A more "Traditional" Look">
|
||||
<meta name="twitter:description" content="Prerequisites: Completion of part 4
|
||||
The tutorial itself goes in a much different visual direction than most roguelikes. If you like this look, great! If you want to make your game look a bit more like other roguelikes you might be more familiar with, this section is for you.
|
||||
Most roguelikes define the floor tiles as a period (.) and the wall tiles as a pound sign (#). This is simple enough to implement, by adjusting our tile types like this:">
|
||||
|
||||
<meta property="og:title" content="Extra - A more "Traditional" Look">
|
||||
<meta property="og:description" content="Prerequisites: Completion of part 4
|
||||
The tutorial itself goes in a much different visual direction than most roguelikes. If you like this look, great! If you want to make your game look a bit more like other roguelikes you might be more familiar with, this section is for you.
|
||||
Most roguelikes define the floor tiles as a period (.) and the wall tiles as a pound sign (#). This is simple enough to implement, by adjusting our tile types like this:">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:url" content="https://rogueliketutorials.com/tutorials/tcod/v2/extras/traditional-look/"><meta property="article:section" content="tutorials">
|
||||
<meta property="article:published_time" content="2020-09-18T16:39:01-07:00">
|
||||
<meta property="article:modified_time" content="2020-09-18T16:39:01-07:00">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="canonical" href="https://rogueliketutorials.com/tutorials/tcod/v2/extras/traditional-look/">
|
||||
|
||||
|
||||
<link rel="preload" href="https://rogueliketutorials.com/fonts/forkawesome-webfont.woff2?v=1.2.0" as="font" type="font/woff2" crossorigin="">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="Part%2014%20-%20Extra%20-%20A%20more%20_Traditional_%20Look%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.c4d7e93a158eda5a65b3df343745d2092a0a1e2170feeec909.css" integrity="sha256-xNfpOhWO2lpls980N0XSCSoKHiFw/u7JCbiolEOQPGo=" crossorigin="anonymous" media="screen">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="Part%2014%20-%20Extra%20-%20A%20more%20_Traditional_%20Look%20%C2%B7%20Roguelike%20Tutorials_files/coder-dark.min.78b5fe3864945faf5207fb8fe3ab2320d49c3365def0e.css" integrity="sha256-eLX+OGSUX69SB/uP46sjINScM2Xe8OiKwd8N2txUoDw=" crossorigin="anonymous" media="screen">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="Part%2014%20-%20Extra%20-%20A%20more%20_Traditional_%20Look%20%C2%B7%20Roguelike%20Tutorials_files/style.min.9d3eb202952dddb888856ff12c83bc88de866c596286bfb4c1.css" integrity="sha256-nT6yApUt3biIhW/xLIO8iN6GbFlihr+0wfjmvq2a42Y=" crossorigin="anonymous" media="screen">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="icon" type="image/png" href="https://rogueliketutorials.com/images/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="https://rogueliketutorials.com/images/favicon-16x16.png" sizes="16x16">
|
||||
|
||||
<link rel="apple-touch-icon" href="https://rogueliketutorials.com/images/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="https://rogueliketutorials.com/images/apple-touch-icon.png">
|
||||
|
||||
<link rel="manifest" href="https://rogueliketutorials.com/site.webmanifest">
|
||||
<link rel="mask-icon" href="https://rogueliketutorials.com/images/safari-pinned-tab.svg" color="#5bbad5">
|
||||
|
||||
|
||||
|
||||
|
||||
<meta name="generator" content="Hugo 0.110.0">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<style>:is([id*='google_ads_iframe'],[id*='taboola-'],.taboolaHeight,.taboola-placeholder,#top-ad,#credential_picker_container,#credentials-picker-container,#credential_picker_iframe,[id*='google-one-tap-iframe'],#google-one-tap-popup-container,.google-one-tap__module,.google-one-tap-modal-div,#amp_floatingAdDiv,#ez-content-blocker-container) {display:none!important;min-height:0!important;height:0!important;}</style></head>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<body class="colorscheme-dark vsc-initialized">
|
||||
|
||||
<div class="float-container">
|
||||
<a id="dark-mode-toggle" class="colorscheme-toggle">
|
||||
<i class="fa fa-adjust fa-fw" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<main class="wrapper">
|
||||
<nav class="navigation">
|
||||
<section class="container">
|
||||
<a class="navigation-title" href="https://rogueliketutorials.com/">
|
||||
Roguelike Tutorials
|
||||
</a>
|
||||
|
||||
<input type="checkbox" id="menu-toggle">
|
||||
<label class="menu-button float-right" for="menu-toggle">
|
||||
<i class="fa fa-bars fa-fw" aria-hidden="true"></i>
|
||||
</label>
|
||||
<ul class="navigation-list">
|
||||
|
||||
|
||||
<li class="navigation-item">
|
||||
<a class="navigation-link" href="https://rogueliketutorials.com/">Home</a>
|
||||
</li>
|
||||
|
||||
<li class="navigation-item">
|
||||
<a class="navigation-link" href="https://rogueliketutorials.com/tutorials/tcod/v2/">TCOD Tutorial (2020)</a>
|
||||
</li>
|
||||
|
||||
<li class="navigation-item">
|
||||
<a class="navigation-link" href="https://rogueliketutorials.com/tutorials/tcod/2019/">TCOD Tutorial (2019)</a>
|
||||
</li>
|
||||
|
||||
<li class="navigation-item">
|
||||
<a class="navigation-link" href="https://rogueliketutorials.com/about/">About</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</section>
|
||||
</nav>
|
||||
|
||||
|
||||
<div class="content">
|
||||
|
||||
<section class="container page">
|
||||
<article>
|
||||
<header>
|
||||
<h1 class="title">
|
||||
<a class="title-link" href="https://rogueliketutorials.com/tutorials/tcod/v2/extras/traditional-look/">
|
||||
Extra - A more "Traditional" Look
|
||||
</a>
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<p><em>Prerequisites: Completion of <a href="https://rogueliketutorials.com/tutorials/tcod/v2/part-4/">part 4</a></em></p>
|
||||
<p>The tutorial itself goes in a much different visual direction than
|
||||
most roguelikes. If you like this look, great! If you want to make your
|
||||
game look a bit more like other roguelikes you might be more familiar
|
||||
with, this section is for you.</p>
|
||||
<p>Most roguelikes define the floor tiles as a period (<code>.</code>) and the wall tiles as a pound sign (<code>#</code>). This is simple enough to implement, by adjusting our tile types like this:</p>
|
||||
<div>
|
||||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||||
Diff
|
||||
</button>
|
||||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||||
Original
|
||||
</button>
|
||||
|
||||
|
||||
<div class="data-pane active" data-pane="diff">
|
||||
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>floor = new_tile(
|
||||
</span></span><span style="display:flex;"><span> walkable=True,
|
||||
</span></span><span style="display:flex;"><span> transparent=True,
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- dark=(ord(" "), (255, 255, 255), (50, 50, 150)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- light=(ord(" "), (255, 255, 255), (200, 180, 50)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ dark=(ord("."), (100, 100, 100), (0, 0, 0)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ light=(ord("."), (200, 200, 200), (0, 0, 0)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>)
|
||||
</span></span><span style="display:flex;"><span>wall = new_tile(
|
||||
</span></span><span style="display:flex;"><span> walkable=False,
|
||||
</span></span><span style="display:flex;"><span> transparent=False,
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- dark=(ord(" "), (255, 255, 255), (0, 0, 100)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- light=(ord(" "), (255, 255, 255), (130, 110, 50)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ dark=(ord("#"), (100, 100, 100), (0, 0, 0)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ light=(ord("#"), (200, 200, 200), (0, 0, 0)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>)
|
||||
</span></span><span style="display:flex;"><span>down_stairs = new_tile(
|
||||
</span></span><span style="display:flex;"><span> walkable=True,
|
||||
</span></span><span style="display:flex;"><span> transparent=True,
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- dark=(ord(">"), (0, 0, 100), (50, 50, 150)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- light=(ord(">"), (255, 255, 255), (200, 180, 50)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ dark=(ord(">"), (100, 100, 100), (0, 0, 0)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ light=(ord(">"), (200, 200, 200), (0, 0, 0)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>)
|
||||
</span></span></code></pre></div>
|
||||
|
||||
</div>
|
||||
<div class="data-pane" data-pane="original">
|
||||
|
||||
<pre>floor = new_tile(
|
||||
walkable=True,
|
||||
transparent=True,
|
||||
<span class="crossed-out-text">dark=(ord(" "), (255, 255, 255), (50, 50, 150)),</span>
|
||||
<span class="crossed-out-text">light=(ord(" "), (255, 255, 255), (200, 180, 50)),</span>
|
||||
<span class="new-text">dark=(ord("."), (100, 100, 100), (0, 0, 0)),</span>
|
||||
<span class="new-text">light=(ord("."), (200, 200, 200), (0, 0, 0)),</span>
|
||||
)
|
||||
wall = new_tile(
|
||||
walkable=False,
|
||||
transparent=False,
|
||||
<span class="crossed-out-text">dark=(ord(" "), (255, 255, 255), (0, 0, 100)),</span>
|
||||
<span class="crossed-out-text">light=(ord(" "), (255, 255, 255), (130, 110, 50)),</span>
|
||||
<span class="new-text">dark=(ord("#"), (100, 100, 100), (0, 0, 0)),</span>
|
||||
<span class="new-text">light=(ord("#"), (200, 200, 200), (0, 0, 0)),</span>
|
||||
)
|
||||
down_stairs = new_tile(
|
||||
walkable=True,
|
||||
transparent=True,
|
||||
<span class="crossed-out-text">dark=(ord(">"), (0, 0, 100), (50, 50, 150)),</span>
|
||||
<span class="crossed-out-text">light=(ord(">"), (255, 255, 255), (200, 180, 50)),</span>
|
||||
<span class="new-text">dark=(ord(">"), (100, 100, 100), (0, 0, 0)),</span>
|
||||
<span class="new-text">light=(ord(">"), (200, 200, 200), (0, 0, 0)),</span>
|
||||
)</pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<p><em>Note: If you haven’t completed <a href="https://rogueliketutorials.com/tutorials/tcod/v2/part-11/">part 11</a> yet, just ignore the <code>down_stairs</code> tile type.</em></p>
|
||||
<p>The tile types are now represented by <code>.</code> and <code>#</code>, and the colors are a lighter gray if the tile is in the field of view, and a darker gray if it’s outside of it.</p>
|
||||
<p>After these changes, the game will look like this:</p>
|
||||
<p><img src="Part%2014%20-%20Extra%20-%20A%20more%20_Traditional_%20Look%20%C2%B7%20Roguelike%20Tutorials_files/traditional-look.png" alt="Traditional Look"></p>
|
||||
<p><em>Note: Screenshot taken from a version of the game after part 13</em></p>
|
||||
<p>You should experiment with different looks for your game, based on
|
||||
what you think is visually appealing. Adjust colors, change symbols, and
|
||||
modify the UI to your heart’s content!</p>
|
||||
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<section class="container">
|
||||
©
|
||||
|
||||
2023
|
||||
|
||||
·
|
||||
|
||||
Powered by <a href="https://gohugo.io/">Hugo</a> & <a href="https://github.com/luizdepra/hugo-coder/">Coder</a>.
|
||||
|
||||
</section>
|
||||
</footer>
|
||||
|
||||
</main>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="Part%2014%20-%20Extra%20-%20A%20more%20_Traditional_%20Look%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.236049395dc3682fb2719640872958e12f1f24067bb09c327b2.js" integrity="sha256-I2BJOV3DaC+ycZZAhylY4S8fJAZ7sJwyeyM+YpDH7aw="></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="Part%2014%20-%20Extra%20-%20A%20more%20_Traditional_%20Look%20%C2%B7%20Roguelike%20Tutorials_files/codetabs.min.cc52451e7f25e50f64c1c893826f606d58410d742c214dce.js" integrity="sha256-zFJFHn8l5Q9kwciTgm9gbVhBDXQsIU3OI/tEfJlh8rA="></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</body></html>
|
|
@ -1 +0,0 @@
|
|||
const body=document.body,darkModeToggle=document.getElementById("dark-mode-toggle"),darkModeMediaQuery=window.matchMedia("(prefers-color-scheme: dark)");localStorage.getItem("colorscheme")?setTheme(localStorage.getItem("colorscheme")):setTheme(body.classList.contains("colorscheme-light")||body.classList.contains("colorscheme-dark")?body.classList.contains("colorscheme-dark")?"dark":"light":darkModeMediaQuery.matches?"dark":"light"),darkModeToggle&&darkModeToggle.addEventListener("click",()=>{let e=body.classList.contains("colorscheme-dark")?"light":"dark";setTheme(e),rememberTheme(e)}),darkModeMediaQuery.addListener(e=>{setTheme(e.matches?"dark":"light")}),document.addEventListener("DOMContentLoaded",function(){let e=document.querySelector(".preload-transitions");e.classList.remove("preload-transitions")});function setTheme(e){body.classList.remove("colorscheme-auto");let n=e==="dark"?"light":"dark";body.classList.remove("colorscheme-"+n),body.classList.add("colorscheme-"+e),document.documentElement.style["color-scheme"]=e;function t(e){return new Promise(t=>{if(document.querySelector(e))return t(document.querySelector(e));const n=new MutationObserver(s=>{document.querySelector(e)&&(t(document.querySelector(e)),n.disconnect())});n.observe(document.body,{childList:!0,subtree:!0})})}if(e==="dark"){const e={type:"set-theme",theme:"github-dark"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}else{const e={type:"set-theme",theme:"github-light"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}}function rememberTheme(e){localStorage.setItem("colorscheme",e)}
|
|
@ -1 +0,0 @@
|
|||
var allTabs=document.querySelectorAll("[data-toggle-tab]"),allPanes=document.querySelectorAll("[data-pane]");function toggleTabs(e){if(e.target){e.preventDefault();var n,s,o=e.currentTarget,t=o.getAttribute("data-toggle-tab")}else t=e;window.localStorage&&window.localStorage.setItem("configLangPref",t),n=document.querySelectorAll("[data-toggle-tab='"+t+"']"),s=document.querySelectorAll("[data-pane='"+t+"']");for(let e=0;e<allTabs.length;e++)allTabs[e].classList.remove("active"),allPanes[e].classList.remove("active");for(let e=0;e<n.length;e++)n[e].classList.add("active"),s[e].classList.add("active")}for(let e=0;e<allTabs.length;e++)allTabs[e].addEventListener("click",toggleTabs);window.localStorage.getItem("configLangPref")&&toggleTabs(window.localStorage.getItem("configLangPref"))
|
|
@ -1 +0,0 @@
|
|||
pre{border:1px solid #000;padding:15px;background-color:#272822;color:#f8f8f2;background-color:#272822}.language-diff,.language-py3{background-color:#272822!important}body.colorscheme-dart code{background-color:#272822!important}.crossed-out-text{color:#f92672;text-decoration:line-through}.new-text{color:#a6e22e}.data-pane{display:none}.data-pane.active{display:inline}
|
Before Width: | Height: | Size: 22 KiB |
|
@ -1 +0,0 @@
|
|||
const body=document.body,darkModeToggle=document.getElementById("dark-mode-toggle"),darkModeMediaQuery=window.matchMedia("(prefers-color-scheme: dark)");localStorage.getItem("colorscheme")?setTheme(localStorage.getItem("colorscheme")):setTheme(body.classList.contains("colorscheme-light")||body.classList.contains("colorscheme-dark")?body.classList.contains("colorscheme-dark")?"dark":"light":darkModeMediaQuery.matches?"dark":"light"),darkModeToggle&&darkModeToggle.addEventListener("click",()=>{let e=body.classList.contains("colorscheme-dark")?"light":"dark";setTheme(e),rememberTheme(e)}),darkModeMediaQuery.addListener(e=>{setTheme(e.matches?"dark":"light")}),document.addEventListener("DOMContentLoaded",function(){let e=document.querySelector(".preload-transitions");e.classList.remove("preload-transitions")});function setTheme(e){body.classList.remove("colorscheme-auto");let n=e==="dark"?"light":"dark";body.classList.remove("colorscheme-"+n),body.classList.add("colorscheme-"+e),document.documentElement.style["color-scheme"]=e;function t(e){return new Promise(t=>{if(document.querySelector(e))return t(document.querySelector(e));const n=new MutationObserver(s=>{document.querySelector(e)&&(t(document.querySelector(e)),n.disconnect())});n.observe(document.body,{childList:!0,subtree:!0})})}if(e==="dark"){const e={type:"set-theme",theme:"github-dark"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}else{const e={type:"set-theme",theme:"github-light"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}}function rememberTheme(e){localStorage.setItem("colorscheme",e)}
|
|
@ -1 +0,0 @@
|
|||
var allTabs=document.querySelectorAll("[data-toggle-tab]"),allPanes=document.querySelectorAll("[data-pane]");function toggleTabs(e){if(e.target){e.preventDefault();var n,s,o=e.currentTarget,t=o.getAttribute("data-toggle-tab")}else t=e;window.localStorage&&window.localStorage.setItem("configLangPref",t),n=document.querySelectorAll("[data-toggle-tab='"+t+"']"),s=document.querySelectorAll("[data-pane='"+t+"']");for(let e=0;e<allTabs.length;e++)allTabs[e].classList.remove("active"),allPanes[e].classList.remove("active");for(let e=0;e<n.length;e++)n[e].classList.add("active"),s[e].classList.add("active")}for(let e=0;e<allTabs.length;e++)allTabs[e].addEventListener("click",toggleTabs);window.localStorage.getItem("configLangPref")&&toggleTabs(window.localStorage.getItem("configLangPref"))
|
Before Width: | Height: | Size: 7.9 KiB |
Before Width: | Height: | Size: 8.0 KiB |
|
@ -1 +0,0 @@
|
|||
pre{border:1px solid #000;padding:15px;background-color:#272822;color:#f8f8f2;background-color:#272822}.language-diff,.language-py3{background-color:#272822!important}body.colorscheme-dart code{background-color:#272822!important}.crossed-out-text{color:#f92672;text-decoration:line-through}.new-text{color:#a6e22e}.data-pane{display:none}.data-pane.active{display:inline}
|
|
@ -1 +0,0 @@
|
|||
const body=document.body,darkModeToggle=document.getElementById("dark-mode-toggle"),darkModeMediaQuery=window.matchMedia("(prefers-color-scheme: dark)");localStorage.getItem("colorscheme")?setTheme(localStorage.getItem("colorscheme")):setTheme(body.classList.contains("colorscheme-light")||body.classList.contains("colorscheme-dark")?body.classList.contains("colorscheme-dark")?"dark":"light":darkModeMediaQuery.matches?"dark":"light"),darkModeToggle&&darkModeToggle.addEventListener("click",()=>{let e=body.classList.contains("colorscheme-dark")?"light":"dark";setTheme(e),rememberTheme(e)}),darkModeMediaQuery.addListener(e=>{setTheme(e.matches?"dark":"light")}),document.addEventListener("DOMContentLoaded",function(){let e=document.querySelector(".preload-transitions");e.classList.remove("preload-transitions")});function setTheme(e){body.classList.remove("colorscheme-auto");let n=e==="dark"?"light":"dark";body.classList.remove("colorscheme-"+n),body.classList.add("colorscheme-"+e),document.documentElement.style["color-scheme"]=e;function t(e){return new Promise(t=>{if(document.querySelector(e))return t(document.querySelector(e));const n=new MutationObserver(s=>{document.querySelector(e)&&(t(document.querySelector(e)),n.disconnect())});n.observe(document.body,{childList:!0,subtree:!0})})}if(e==="dark"){const e={type:"set-theme",theme:"github-dark"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}else{const e={type:"set-theme",theme:"github-light"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}}function rememberTheme(e){localStorage.setItem("colorscheme",e)}
|
|
@ -1 +0,0 @@
|
|||
var allTabs=document.querySelectorAll("[data-toggle-tab]"),allPanes=document.querySelectorAll("[data-pane]");function toggleTabs(e){if(e.target){e.preventDefault();var n,s,o=e.currentTarget,t=o.getAttribute("data-toggle-tab")}else t=e;window.localStorage&&window.localStorage.setItem("configLangPref",t),n=document.querySelectorAll("[data-toggle-tab='"+t+"']"),s=document.querySelectorAll("[data-pane='"+t+"']");for(let e=0;e<allTabs.length;e++)allTabs[e].classList.remove("active"),allPanes[e].classList.remove("active");for(let e=0;e<n.length;e++)n[e].classList.add("active"),s[e].classList.add("active")}for(let e=0;e<allTabs.length;e++)allTabs[e].addEventListener("click",toggleTabs);window.localStorage.getItem("configLangPref")&&toggleTabs(window.localStorage.getItem("configLangPref"))
|
Before Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 8.1 KiB |
Before Width: | Height: | Size: 8.0 KiB |
|
@ -1 +0,0 @@
|
|||
pre{border:1px solid #000;padding:15px;background-color:#272822;color:#f8f8f2;background-color:#272822}.language-diff,.language-py3{background-color:#272822!important}body.colorscheme-dart code{background-color:#272822!important}.crossed-out-text{color:#f92672;text-decoration:line-through}.new-text{color:#a6e22e}.data-pane{display:none}.data-pane.active{display:inline}
|
|
@ -1,627 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" style="color-scheme: dark;"><head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<title>
|
||||
Part 4 - Field of View · Roguelike Tutorials
|
||||
</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="color-scheme" content="light dark">
|
||||
|
||||
|
||||
|
||||
|
||||
<meta name="description" content="We have a dungeon now, and we can move about it freely. But are we really exploring the dungeon if we can just see it all from the beginning?
|
||||
Most roguelikes (not all!) only let you see within a certain range of your character, and ours will be no different. We need to implement a way to calculate the “Field of View” for our adventurer, and fortunately, tcod makes that easy!">
|
||||
<meta name="keywords" content="">
|
||||
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="Part 4 - Field of View">
|
||||
<meta name="twitter:description" content="We have a dungeon now, and we can move about it freely. But are we really exploring the dungeon if we can just see it all from the beginning?
|
||||
Most roguelikes (not all!) only let you see within a certain range of your character, and ours will be no different. We need to implement a way to calculate the “Field of View” for our adventurer, and fortunately, tcod makes that easy!">
|
||||
|
||||
<meta property="og:title" content="Part 4 - Field of View">
|
||||
<meta property="og:description" content="We have a dungeon now, and we can move about it freely. But are we really exploring the dungeon if we can just see it all from the beginning?
|
||||
Most roguelikes (not all!) only let you see within a certain range of your character, and ours will be no different. We need to implement a way to calculate the “Field of View” for our adventurer, and fortunately, tcod makes that easy!">
|
||||
<meta property="og:type" content="article">
|
||||
<meta property="og:url" content="https://rogueliketutorials.com/tutorials/tcod/v2/part-4/"><meta property="article:section" content="tutorials">
|
||||
<meta property="article:published_time" content="2020-06-29T00:00:00+00:00">
|
||||
<meta property="article:modified_time" content="2020-06-29T00:00:00+00:00">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="canonical" href="https://rogueliketutorials.com/tutorials/tcod/v2/part-4/">
|
||||
|
||||
|
||||
<link rel="preload" href="https://rogueliketutorials.com/fonts/forkawesome-webfont.woff2?v=1.2.0" as="font" type="font/woff2" crossorigin="">
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="Part%204%20-%20Field%20of%20View%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.c4d7e93a158eda5a65b3df343745d2092a0a1e2170feeec909.css" integrity="sha256-xNfpOhWO2lpls980N0XSCSoKHiFw/u7JCbiolEOQPGo=" crossorigin="anonymous" media="screen">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="Part%204%20-%20Field%20of%20View%20%C2%B7%20Roguelike%20Tutorials_files/coder-dark.min.78b5fe3864945faf5207fb8fe3ab2320d49c3365def0e.css" integrity="sha256-eLX+OGSUX69SB/uP46sjINScM2Xe8OiKwd8N2txUoDw=" crossorigin="anonymous" media="screen">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="stylesheet" href="Part%204%20-%20Field%20of%20View%20%C2%B7%20Roguelike%20Tutorials_files/style.min.9d3eb202952dddb888856ff12c83bc88de866c596286bfb4c1.css" integrity="sha256-nT6yApUt3biIhW/xLIO8iN6GbFlihr+0wfjmvq2a42Y=" crossorigin="anonymous" media="screen">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link rel="icon" type="image/png" href="https://rogueliketutorials.com/images/favicon-32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="https://rogueliketutorials.com/images/favicon-16x16.png" sizes="16x16">
|
||||
|
||||
<link rel="apple-touch-icon" href="https://rogueliketutorials.com/images/apple-touch-icon.png">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="https://rogueliketutorials.com/images/apple-touch-icon.png">
|
||||
|
||||
<link rel="manifest" href="https://rogueliketutorials.com/site.webmanifest">
|
||||
<link rel="mask-icon" href="https://rogueliketutorials.com/images/safari-pinned-tab.svg" color="#5bbad5">
|
||||
|
||||
|
||||
|
||||
|
||||
<meta name="generator" content="Hugo 0.110.0">
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<style>:is([id*='google_ads_iframe'],[id*='taboola-'],.taboolaHeight,.taboola-placeholder,#top-ad,#credential_picker_container,#credentials-picker-container,#credential_picker_iframe,[id*='google-one-tap-iframe'],#google-one-tap-popup-container,.google-one-tap__module,.google-one-tap-modal-div,#amp_floatingAdDiv,#ez-content-blocker-container) {display:none!important;min-height:0!important;height:0!important;}</style></head>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<body class="colorscheme-dark vsc-initialized">
|
||||
|
||||
<div class="float-container">
|
||||
<a id="dark-mode-toggle" class="colorscheme-toggle">
|
||||
<i class="fa fa-adjust fa-fw" aria-hidden="true"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
<main class="wrapper">
|
||||
<nav class="navigation">
|
||||
<section class="container">
|
||||
<a class="navigation-title" href="https://rogueliketutorials.com/">
|
||||
Roguelike Tutorials
|
||||
</a>
|
||||
|
||||
<input type="checkbox" id="menu-toggle">
|
||||
<label class="menu-button float-right" for="menu-toggle">
|
||||
<i class="fa fa-bars fa-fw" aria-hidden="true"></i>
|
||||
</label>
|
||||
<ul class="navigation-list">
|
||||
|
||||
|
||||
<li class="navigation-item">
|
||||
<a class="navigation-link" href="https://rogueliketutorials.com/">Home</a>
|
||||
</li>
|
||||
|
||||
<li class="navigation-item">
|
||||
<a class="navigation-link" href="https://rogueliketutorials.com/tutorials/tcod/v2/">TCOD Tutorial (2020)</a>
|
||||
</li>
|
||||
|
||||
<li class="navigation-item">
|
||||
<a class="navigation-link" href="https://rogueliketutorials.com/tutorials/tcod/2019/">TCOD Tutorial (2019)</a>
|
||||
</li>
|
||||
|
||||
<li class="navigation-item">
|
||||
<a class="navigation-link" href="https://rogueliketutorials.com/about/">About</a>
|
||||
</li>
|
||||
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</section>
|
||||
</nav>
|
||||
|
||||
|
||||
<div class="content">
|
||||
|
||||
<section class="container page">
|
||||
<article>
|
||||
<header>
|
||||
<h1 class="title">
|
||||
<a class="title-link" href="https://rogueliketutorials.com/tutorials/tcod/v2/part-4/">
|
||||
Part 4 - Field of View
|
||||
</a>
|
||||
</h1>
|
||||
</header>
|
||||
|
||||
<p>We have a dungeon now, and we can move about it freely. But are we
|
||||
really <em>exploring</em> the dungeon if we can just see it all from the
|
||||
beginning?</p>
|
||||
<p>Most roguelikes (not all!) only let you see within a certain range of
|
||||
your character, and ours will be no different. We need to implement a way
|
||||
to calculate the “Field of View” for our adventurer, and fortunately,
|
||||
tcod makes that easy!</p>
|
||||
<p>When walking around the dungeon, there will essentially be three “states” a tile can be in, relating to our field of view.</p>
|
||||
<ol>
|
||||
<li>Visible</li>
|
||||
<li>Not visible</li>
|
||||
<li>Not visible, but previously seen</li>
|
||||
</ol>
|
||||
<p>What this means is that we should draw the “visible” tiles as well as
|
||||
the “not visible, but previously seen” ones to the screen, but
|
||||
differentiate them somehow. The “not visible” tiles can simply be drawn
|
||||
as an empty tile, with the color black, gray, or whatever you want to
|
||||
use.</p>
|
||||
<p>In order to differentiate between these tiles, we’ll need two new
|
||||
Numpy arrays: One to keep track of the tiles that are currently visible,
|
||||
and another to keep track of all the tiles that our character has seen
|
||||
before. Add the two arrays to <code>GameMap</code> like this:</p>
|
||||
<div>
|
||||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||||
Diff
|
||||
</button>
|
||||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||||
Original
|
||||
</button>
|
||||
|
||||
|
||||
<div class="data-pane active" data-pane="diff">
|
||||
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class GameMap:
|
||||
</span></span><span style="display:flex;"><span> def __init__(self, width: int, height: int):
|
||||
</span></span><span style="display:flex;"><span> self.width, self.height = width, height
|
||||
</span></span><span style="display:flex;"><span> self.tiles = np.full((width, height), fill_value=tile_types.wall, order="F")
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.visible = np.full((width, height), fill_value=False, order="F") # Tiles the player can currently see
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.explored = np.full((width, height), fill_value=False, order="F") # Tiles the player has seen before
|
||||
</span></span></span></code></pre></div>
|
||||
|
||||
</div>
|
||||
<div class="data-pane" data-pane="original">
|
||||
|
||||
<pre>class GameMap:
|
||||
def __init__(self, width: int, height: int):
|
||||
self.width, self.height = width, height
|
||||
self.tiles = np.full((width, height), fill_value=tile_types.wall, order="F")
|
||||
|
||||
<span class="new-text">self.visible = np.full((width, height), fill_value=False, order="F") # Tiles the player can currently see
|
||||
self.explored = np.full((width, height), fill_value=False, order="F") # Tiles the player has seen before</span></pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<p>We create two arrays, <code>visible</code> and <code>explored</code>, and fill them with the value <code>False</code>. In a moment, we’ll create a function that will update these arrays based on what’s in the field of view.</p>
|
||||
<p>Let’s turn our attention back to the tile types. Remember when we
|
||||
specified the “walkable”, “transparent”, and “dark” attributes? We
|
||||
called it “dark” because it’s what the tile will look like when its not
|
||||
in the field of view, but what about when it <em>is</em>?</p>
|
||||
<p>For that, we’ll want a new <code>graphic_dt</code> in the <code>tile_dt</code> type, called <code>light</code>. We can add that by modifying <code>tile_types.py</code> like this:</p>
|
||||
<div>
|
||||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||||
Diff
|
||||
</button>
|
||||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||||
Original
|
||||
</button>
|
||||
|
||||
|
||||
<div class="data-pane active" data-pane="diff">
|
||||
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>tile_dt = np.dtype(
|
||||
</span></span><span style="display:flex;"><span> [
|
||||
</span></span><span style="display:flex;"><span> ("walkable", np.bool), # True if this tile can be walked over.
|
||||
</span></span><span style="display:flex;"><span> ("transparent", np.bool), # True if this tile doesn't block FOV.
|
||||
</span></span><span style="display:flex;"><span> ("dark", graphic_dt), # Graphics for when this tile is not in FOV.
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ ("light", graphic_dt), # Graphics for when the tile is in FOV.
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span> ]
|
||||
</span></span><span style="display:flex;"><span>)
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span>def new_tile(
|
||||
</span></span><span style="display:flex;"><span> *, # Enforce the use of keywords, so that parameter order doesn't matter.
|
||||
</span></span><span style="display:flex;"><span> walkable: int,
|
||||
</span></span><span style="display:flex;"><span> transparent: int,
|
||||
</span></span><span style="display:flex;"><span> dark: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]],
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ light: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]],
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>) -> np.ndarray:
|
||||
</span></span><span style="display:flex;"><span> """Helper function for defining individual tile types """
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- return np.array((walkable, transparent, dark), dtype=tile_dt)
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ return np.array((walkable, transparent, dark, light), dtype=tile_dt)
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+# SHROUD represents unexplored, unseen tiles
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+SHROUD = np.array((ord(" "), (255, 255, 255), (0, 0, 0)), dtype=graphic_dt)
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||||
</span></span><span style="display:flex;"><span>floor = new_tile(
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- walkable=True, transparent=True, dark=(ord(" "), (255, 255, 255), (50, 50, 150)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ walkable=True,
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ transparent=True,
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dark=(ord(" "), (255, 255, 255), (50, 50, 150)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ light=(ord(" "), (255, 255, 255), (200, 180, 50)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>)
|
||||
</span></span><span style="display:flex;"><span>wall = new_tile(
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- walkable=False, transparent=False, dark=(ord(" "), (255, 255, 255), (0, 0, 100)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ walkable=False,
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ transparent=False,
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ dark=(ord(" "), (255, 255, 255), (0, 0, 100)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ light=(ord(" "), (255, 255, 255), (130, 110, 50)),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>)
|
||||
</span></span></code></pre></div>
|
||||
|
||||
</div>
|
||||
<div class="data-pane" data-pane="original">
|
||||
|
||||
<pre>tile_dt = np.dtype(
|
||||
[
|
||||
("walkable", np.bool), # True if this tile can be walked over.
|
||||
("transparent", np.bool), # True if this tile doesn't block FOV.
|
||||
("dark", graphic_dt), # Graphics for when this tile is not in FOV.
|
||||
<span class="new-text">("light", graphic_dt), # Graphics for when the tile is in FOV.</span>
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def new_tile(
|
||||
*, # Enforce the use of keywords, so that parameter order doesn't matter.
|
||||
walkable: int,
|
||||
transparent: int,
|
||||
dark: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]],
|
||||
<span class="new-text">light: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]],</span>
|
||||
) -> np.ndarray:
|
||||
"""Helper function for defining individual tile types """
|
||||
<span class="crossed-out-text">return np.array((walkable, transparent, dark), dtype=tile_dt)</span>
|
||||
<span class="new-text">return np.array((walkable, transparent, dark, light), dtype=tile_dt)</span>
|
||||
|
||||
|
||||
<span class="new-text"># SHROUD represents unexplored, unseen tiles
|
||||
SHROUD = np.array((ord(" "), (255, 255, 255), (0, 0, 0)), dtype=graphic_dt)</span>
|
||||
|
||||
floor = new_tile(
|
||||
<span class="crossed-out-text">walkable=True, transparent=True, dark=(ord(" "), (255, 255, 255), (50, 50, 150)),</span>
|
||||
<span class="new-text">walkable=True,
|
||||
transparent=True,
|
||||
dark=(ord(" "), (255, 255, 255), (50, 50, 150)),
|
||||
light=(ord(" "), (255, 255, 255), (200, 180, 50)),</span>
|
||||
)
|
||||
wall = new_tile(
|
||||
<span class="crossed-out-text">walkable=False, transparent=False, dark=(ord(" "), (255, 255, 255), (0, 0, 100)),</span>
|
||||
<span class="new-text">walkable=False,
|
||||
transparent=False,
|
||||
dark=(ord(" "), (255, 255, 255), (0, 0, 100)),
|
||||
light=(ord(" "), (255, 255, 255), (130, 110, 50)),</span>
|
||||
)</pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<p>Let’s go through the new additions.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span>tile_dt <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>dtype(
|
||||
</span></span><span style="display:flex;"><span> [
|
||||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"walkable"</span>, np<span style="color:#f92672">.</span>bool), <span style="color:#75715e"># True if this tile can be walked over.</span>
|
||||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"transparent"</span>, np<span style="color:#f92672">.</span>bool), <span style="color:#75715e"># True if this tile doesn't block FOV.</span>
|
||||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"dark"</span>, graphic_dt), <span style="color:#75715e"># Graphics for when this tile is not in FOV.</span>
|
||||
</span></span><span style="display:flex;"><span> (<span style="color:#e6db74">"light"</span>, graphic_dt), <span style="color:#75715e"># Graphics for when the tile is in FOV.</span>
|
||||
</span></span><span style="display:flex;"><span> ]
|
||||
</span></span><span style="display:flex;"><span>)
|
||||
</span></span></code></pre></div><p>We’re adding a new <code>graphic_dt</code> to the <code>tile_dt</code> that we use to define our tiles. <code>light</code> will hold the information about what our tile looks like when it’s in the field of view.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">new_tile</span>(
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#f92672">*</span>, <span style="color:#75715e"># Enforce the use of keywords, so that parameter order doesn't matter.</span>
|
||||
</span></span><span style="display:flex;"><span> walkable: int,
|
||||
</span></span><span style="display:flex;"><span> transparent: int,
|
||||
</span></span><span style="display:flex;"><span> dark: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]],
|
||||
</span></span><span style="display:flex;"><span> light: Tuple[int, Tuple[int, int, int], Tuple[int, int, int]],
|
||||
</span></span><span style="display:flex;"><span>) <span style="color:#f92672">-></span> np<span style="color:#f92672">.</span>ndarray:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""Helper function for defining individual tile types """</span>
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> np<span style="color:#f92672">.</span>array((walkable, transparent, dark, light), dtype<span style="color:#f92672">=</span>tile_dt)
|
||||
</span></span></code></pre></div><p>We’ve modified the <code>new_tile</code> function to account for the new <code>light</code> attribute. <code>light</code> works the same as <code>dark</code>.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span><span style="color:#75715e"># SHROUD represents unexplored, unseen tiles</span>
|
||||
</span></span><span style="display:flex;"><span>SHROUD <span style="color:#f92672">=</span> np<span style="color:#f92672">.</span>array((ord(<span style="color:#e6db74">" "</span>), (<span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>), (<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>)), dtype<span style="color:#f92672">=</span>graphic_dt)
|
||||
</span></span></code></pre></div><p><code>SHROUD</code> is what we’ll use for when a tile is neither in view nor has been “explored”. It’s set to just draw a black tile.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span>floor <span style="color:#f92672">=</span> new_tile(
|
||||
</span></span><span style="display:flex;"><span> walkable<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>,
|
||||
</span></span><span style="display:flex;"><span> transparent<span style="color:#f92672">=</span><span style="color:#66d9ef">True</span>,
|
||||
</span></span><span style="display:flex;"><span> dark<span style="color:#f92672">=</span>(ord(<span style="color:#e6db74">" "</span>), (<span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>), (<span style="color:#ae81ff">50</span>, <span style="color:#ae81ff">50</span>, <span style="color:#ae81ff">150</span>)),
|
||||
</span></span><span style="display:flex;"><span> light<span style="color:#f92672">=</span>(ord(<span style="color:#e6db74">" "</span>), (<span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>), (<span style="color:#ae81ff">200</span>, <span style="color:#ae81ff">180</span>, <span style="color:#ae81ff">50</span>)),
|
||||
</span></span><span style="display:flex;"><span>)
|
||||
</span></span><span style="display:flex;"><span>wall <span style="color:#f92672">=</span> new_tile(
|
||||
</span></span><span style="display:flex;"><span> walkable<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>,
|
||||
</span></span><span style="display:flex;"><span> transparent<span style="color:#f92672">=</span><span style="color:#66d9ef">False</span>,
|
||||
</span></span><span style="display:flex;"><span> dark<span style="color:#f92672">=</span>(ord(<span style="color:#e6db74">" "</span>), (<span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>), (<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">100</span>)),
|
||||
</span></span><span style="display:flex;"><span> light<span style="color:#f92672">=</span>(ord(<span style="color:#e6db74">" "</span>), (<span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>, <span style="color:#ae81ff">255</span>), (<span style="color:#ae81ff">130</span>, <span style="color:#ae81ff">110</span>, <span style="color:#ae81ff">50</span>)),
|
||||
</span></span><span style="display:flex;"><span>)
|
||||
</span></span></code></pre></div><p>Finally, we add <code>light</code> to both the <code>floor</code> and <code>wall</code>
|
||||
tiles. We also modify the functions to fit a bit better on the screen,
|
||||
adding new lines after each argument. This is just for the sake of
|
||||
readability.</p>
|
||||
<p><code>light</code> in both cases is set to a brighter color, so that
|
||||
when we draw the field of view to the screen, the player can easily
|
||||
differentiate between what’s in view and what’s not. As usual, feel free
|
||||
to play with the color schemes to match whatever you might have in
|
||||
mind.</p>
|
||||
<p>With all that in place, we need to modify the way <code>GameMap</code> draws itself to the screen.</p>
|
||||
<div>
|
||||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||||
Diff
|
||||
</button>
|
||||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||||
Original
|
||||
</button>
|
||||
|
||||
|
||||
<div class="data-pane active" data-pane="diff">
|
||||
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>class GameMap:
|
||||
</span></span><span style="display:flex;"><span> ...
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> def render(self, console: Console) -> None:
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- console.tiles_rgb[0:self.width, 0:self.height] = self.tiles["dark"]
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ """
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ Renders the map.
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ If a tile is in the "visible" array, then draw it with the "light" colors.
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ If it isn't, but it's in the "explored" array, then draw it with the "dark" colors.
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ Otherwise, the default is "SHROUD".
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.tiles_rgb[0:self.width, 0:self.height] = np.select(
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ condlist=[self.visible, self.explored],
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ choicelist=[self.tiles["light"], self.tiles["dark"]],
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ default=tile_types.SHROUD
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
|
||||
</span></span></span></code></pre></div>
|
||||
|
||||
</div>
|
||||
<div class="data-pane" data-pane="original">
|
||||
|
||||
<pre>class GameMap:
|
||||
...
|
||||
|
||||
def render(self, console: Console) -> None:
|
||||
<span class="crossed-out-text">console.tiles_rgb[0:self.width, 0:self.height] = self.tiles["dark"]</span>
|
||||
<span class="new-text">"""
|
||||
Renders the map.
|
||||
|
||||
If a tile is in the "visible" array, then draw it with the "light" colors.
|
||||
If it isn't, but it's in the "explored" array, then draw it with the "dark" colors.
|
||||
Otherwise, the default is "SHROUD".
|
||||
"""
|
||||
console.tiles_rgb[0:self.width, 0:self.height] = np.select(
|
||||
condlist=[self.visible, self.explored],
|
||||
choicelist=[self.tiles["light"], self.tiles["dark"]],
|
||||
default=tile_types.SHROUD
|
||||
)</span></pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<p>The first part of the statement, <code>console.tiles_rgb[0:self.width, 0:self.height]</code>, hasn’t changed. But instead of just setting it to <code>self.tiles["dark"]</code>, we’re using <code>np.select</code>.</p>
|
||||
<p><code>np.select</code> allows us to conditionally draw the tiles we want, based on what’s specified in <code>condlist</code>. Since we’re passing <code>[self.visible, self.explored]</code>, it will check if the tile being drawn is either visible, then explored. If it’s visible, it uses the first value in <code>choicelist</code>, in this case, <code>self.tiles["light"]</code>. If it’s not visible, but explored, then we draw <code>self.tiles["dark"]</code>. If neither is true, we use the <code>default</code> argument, which is just the <code>SHROUD</code> we defined earlier.</p>
|
||||
<p>If you run the project now, none of the tiles will be drawn to the screen. This is because we need a way to actually modify the <code>visible</code> and <code>explored</code> tiles. Let’s modify <code>Engine</code> to do just that:</p>
|
||||
<div>
|
||||
<button class="btn btn-primary data-toggle-tab active" data-toggle-tab="diff">
|
||||
Diff
|
||||
</button>
|
||||
<button class="btn btn-secondary data-toggle-tab" data-toggle-tab="original">
|
||||
Original
|
||||
</button>
|
||||
|
||||
|
||||
<div class="data-pane active" data-pane="diff">
|
||||
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span>...
|
||||
</span></span><span style="display:flex;"><span>from tcod.context import Context
|
||||
</span></span><span style="display:flex;"><span>from tcod.console import Console
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+from tcod.map import compute_fov
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||||
</span></span><span style="display:flex;"><span>from entity import Entity
|
||||
</span></span><span style="display:flex;"><span>...
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span>class Engine:
|
||||
</span></span><span style="display:flex;"><span> def __init__(self, entities: Set[Entity], event_handler: EventHandler, game_map: GameMap, player: Entity):
|
||||
</span></span><span style="display:flex;"><span> self.entities = entities
|
||||
</span></span><span style="display:flex;"><span> self.event_handler = event_handler
|
||||
</span></span><span style="display:flex;"><span> self.game_map = game_map
|
||||
</span></span><span style="display:flex;"><span> self.player = player
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.update_fov()
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||||
</span></span><span style="display:flex;"><span> def handle_events(self, events: Iterable[Any]) -> None:
|
||||
</span></span><span style="display:flex;"><span> for event in events:
|
||||
</span></span><span style="display:flex;"><span> action = self.event_handler.dispatch(event)
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> if action is None:
|
||||
</span></span><span style="display:flex;"><span> continue
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> action.perform(self, self.player)
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.update_fov() # Update the FOV before the players next action.
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||||
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ def update_fov(self) -> None:
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ """Recompute the visible area based on the players point of view."""
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.game_map.visible[:] = compute_fov(
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.game_map.tiles["transparent"],
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ (self.player.x, self.player.y),
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ radius=8,
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ )
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ # If a tile is "visible" it should be added to "explored".
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ self.game_map.explored |= self.game_map.visible
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||||
</span></span><span style="display:flex;"><span> def render(self, console: Console, context: Context) -> None:
|
||||
</span></span><span style="display:flex;"><span> self.game_map.render(console)
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> for entity in self.entities:
|
||||
</span></span><span style="display:flex;"><span><span style="color:#f92672">- console.print(entity.x, entity.y, entity.char, fg=entity.color)
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#f92672"></span><span style="color:#a6e22e">+ # Only print entities that are in the FOV
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ if self.game_map.visible[entity.x, entity.y]:
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ console.print(entity.x, entity.y, entity.char, fg=entity.color)
|
||||
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e"></span>
|
||||
</span></span><span style="display:flex;"><span> context.present(console)
|
||||
</span></span><span style="display:flex;"><span>
|
||||
</span></span><span style="display:flex;"><span> console.clear()
|
||||
</span></span></code></pre></div>
|
||||
|
||||
</div>
|
||||
<div class="data-pane" data-pane="original">
|
||||
|
||||
<pre>...
|
||||
from tcod.context import Context
|
||||
from tcod.console import Console
|
||||
<span class="new-text">from tcod.map import compute_fov</span>
|
||||
|
||||
from entity import Entity
|
||||
...
|
||||
|
||||
class Engine:
|
||||
def __init__(self, entities: Set[Entity], event_handler: EventHandler, game_map: GameMap, player: Entity):
|
||||
self.entities = entities
|
||||
self.event_handler = event_handler
|
||||
self.game_map = game_map
|
||||
self.player = player
|
||||
<span class="new-text">self.update_fov()</span>
|
||||
|
||||
def handle_events(self, events: Iterable[Any]) -> None:
|
||||
for event in events:
|
||||
action = self.event_handler.dispatch(event)
|
||||
|
||||
if action is None:
|
||||
continue
|
||||
|
||||
action.perform(self, self.player)
|
||||
|
||||
<span class="new-text">self.update_fov() # Update the FOV before the players next action.</span>
|
||||
|
||||
<span class="new-text">def update_fov(self) -> None:
|
||||
"""Recompute the visible area based on the players point of view."""
|
||||
self.game_map.visible[:] = compute_fov(
|
||||
self.game_map.tiles["transparent"],
|
||||
(self.player.x, self.player.y),
|
||||
radius=8,
|
||||
)
|
||||
# If a tile is "visible" it should be added to "explored".
|
||||
self.game_map.explored |= self.game_map.visible</span>
|
||||
|
||||
def render(self, console: Console, context: Context) -> None:
|
||||
self.game_map.render(console)
|
||||
|
||||
for entity in self.entities:
|
||||
<span class="crossed-out-text">console.print(entity.x, entity.y, entity.char, fg=entity.color)</span>
|
||||
<span class="new-text"># Only print entities that are in the FOV
|
||||
if self.game_map.visible[entity.x, entity.y]:
|
||||
console.print(entity.x, entity.y, entity.char, fg=entity.color)</span>
|
||||
|
||||
context.present(console)
|
||||
|
||||
console.clear()</pre>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<p>The most important part of our additions is the <code>update_fov</code> function.</p>
|
||||
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-py3" data-lang="py3"><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">update_fov</span>(self) <span style="color:#f92672">-></span> <span style="color:#66d9ef">None</span>:
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">"""Recompute the visible area based on the players point of view."""</span>
|
||||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>game_map<span style="color:#f92672">.</span>visible[:] <span style="color:#f92672">=</span> compute_fov(
|
||||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>game_map<span style="color:#f92672">.</span>tiles[<span style="color:#e6db74">"transparent"</span>],
|
||||
</span></span><span style="display:flex;"><span> (self<span style="color:#f92672">.</span>player<span style="color:#f92672">.</span>x, self<span style="color:#f92672">.</span>player<span style="color:#f92672">.</span>y),
|
||||
</span></span><span style="display:flex;"><span> radius<span style="color:#f92672">=</span><span style="color:#ae81ff">8</span>,
|
||||
</span></span><span style="display:flex;"><span> )
|
||||
</span></span><span style="display:flex;"><span> <span style="color:#75715e"># If a tile is "visible" it should be added to "explored".</span>
|
||||
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>game_map<span style="color:#f92672">.</span>explored <span style="color:#f92672">|=</span> self<span style="color:#f92672">.</span>game_map<span style="color:#f92672">.</span>visible
|
||||
</span></span></code></pre></div><p>We’re setting the <code>game_map</code>’s <code>visible</code> tiles to equal the result of the <code>compute_fov</code>. We’re giving <code>compute_fov</code> three arguments, which it uses to compute our field of view.</p>
|
||||
<ul>
|
||||
<li><code>transparency</code>: This is the first argument, which we’re passing <code>self.game_map.tiles["transparent"]</code>. <code>transparency</code>
|
||||
takes a 2D numpy array, and considers any non-zero values to be
|
||||
transparent. This is the array it uses to calculate the field of view.</li>
|
||||
<li><code>pov</code>: The origin point for the field of view, which is a 2D index. We use the player’s x and y position here.</li>
|
||||
<li><code>radius</code>: How far the FOV extends.</li>
|
||||
</ul>
|
||||
<p>There’s more that this function can do, including not lighting up
|
||||
walls, and using different algorithms to calculate the FOV. If you’re
|
||||
interested, you can find the documentation <a href="https://python-tcod.readthedocs.io/en/latest/tcod/map.html#tcod.map.compute_fov">here</a>.</p>
|
||||
<p>The line <code>self.game_map.explored |= self.game_map.visible</code> sets the <code>explored</code> array to include everything in the <code>visible</code> array, plus whatever it already had. This means that any tile the player can see, the player has also “explored.”</p>
|
||||
<p>That’s all we need to do to update our field of view. Notice that we call the function when we initialize the <code>Engine</code>
|
||||
class, so that the field of view is created before the player can move,
|
||||
and after handling an action, so that whenever the player does move,
|
||||
the field of view will be updated.</p>
|
||||
<p>Lastly, we modify the part that draws the entities, so that only entities in the field of view are drawn.</p>
|
||||
<p>Run the project now, and you’ll see something like this:</p>
|
||||
<p><img src="Part%204%20-%20Field%20of%20View%20%C2%B7%20Roguelike%20Tutorials_files/part-4-fov.png" alt="Part 4 - FOV" title="Field of View"></p>
|
||||
<p>It’s hard to believe, but that’s all we need to do for a functioning field of view!</p>
|
||||
<p>This chapter was a shorter one, but we’ve accomplished quite a lot.
|
||||
Our dungeon feels a lot more mysterious, and in coming chapters, it will
|
||||
get a lot more dangerous.</p>
|
||||
<p>If you want to see the code so far in its entirety, <a href="https://github.com/TStand90/tcod_tutorial_v2/tree/2020/part-4">click
|
||||
here</a>.</p>
|
||||
<p><a href="https://rogueliketutorials.com/tutorials/tcod/v2/part-5">Click here to move on to the next part of this
|
||||
tutorial.</a></p>
|
||||
|
||||
</article>
|
||||
</section>
|
||||
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<section class="container">
|
||||
©
|
||||
|
||||
2023
|
||||
|
||||
·
|
||||
|
||||
Powered by <a href="https://gohugo.io/">Hugo</a> & <a href="https://github.com/luizdepra/hugo-coder/">Coder</a>.
|
||||
|
||||
</section>
|
||||
</footer>
|
||||
|
||||
</main>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="Part%204%20-%20Field%20of%20View%20%C2%B7%20Roguelike%20Tutorials_files/coder.min.236049395dc3682fb2719640872958e12f1f24067bb09c327b2.js" integrity="sha256-I2BJOV3DaC+ycZZAhylY4S8fJAZ7sJwyeyM+YpDH7aw="></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<script src="Part%204%20-%20Field%20of%20View%20%C2%B7%20Roguelike%20Tutorials_files/codetabs.min.cc52451e7f25e50f64c1c893826f606d58410d742c214dce.js" integrity="sha256-zFJFHn8l5Q9kwciTgm9gbVhBDXQsIU3OI/tEfJlh8rA="></script>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</body></html>
|
|
@ -1 +0,0 @@
|
|||
const body=document.body,darkModeToggle=document.getElementById("dark-mode-toggle"),darkModeMediaQuery=window.matchMedia("(prefers-color-scheme: dark)");localStorage.getItem("colorscheme")?setTheme(localStorage.getItem("colorscheme")):setTheme(body.classList.contains("colorscheme-light")||body.classList.contains("colorscheme-dark")?body.classList.contains("colorscheme-dark")?"dark":"light":darkModeMediaQuery.matches?"dark":"light"),darkModeToggle&&darkModeToggle.addEventListener("click",()=>{let e=body.classList.contains("colorscheme-dark")?"light":"dark";setTheme(e),rememberTheme(e)}),darkModeMediaQuery.addListener(e=>{setTheme(e.matches?"dark":"light")}),document.addEventListener("DOMContentLoaded",function(){let e=document.querySelector(".preload-transitions");e.classList.remove("preload-transitions")});function setTheme(e){body.classList.remove("colorscheme-auto");let n=e==="dark"?"light":"dark";body.classList.remove("colorscheme-"+n),body.classList.add("colorscheme-"+e),document.documentElement.style["color-scheme"]=e;function t(e){return new Promise(t=>{if(document.querySelector(e))return t(document.querySelector(e));const n=new MutationObserver(s=>{document.querySelector(e)&&(t(document.querySelector(e)),n.disconnect())});n.observe(document.body,{childList:!0,subtree:!0})})}if(e==="dark"){const e={type:"set-theme",theme:"github-dark"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}else{const e={type:"set-theme",theme:"github-light"};t(".utterances-frame").then(t=>{t.contentWindow.postMessage(e,"https://utteranc.es")})}}function rememberTheme(e){localStorage.setItem("colorscheme",e)}
|
|
@ -1 +0,0 @@
|
|||
var allTabs=document.querySelectorAll("[data-toggle-tab]"),allPanes=document.querySelectorAll("[data-pane]");function toggleTabs(e){if(e.target){e.preventDefault();var n,s,o=e.currentTarget,t=o.getAttribute("data-toggle-tab")}else t=e;window.localStorage&&window.localStorage.setItem("configLangPref",t),n=document.querySelectorAll("[data-toggle-tab='"+t+"']"),s=document.querySelectorAll("[data-pane='"+t+"']");for(let e=0;e<allTabs.length;e++)allTabs[e].classList.remove("active"),allPanes[e].classList.remove("active");for(let e=0;e<n.length;e++)n[e].classList.add("active"),s[e].classList.add("active")}for(let e=0;e<allTabs.length;e++)allTabs[e].addEventListener("click",toggleTabs);window.localStorage.getItem("configLangPref")&&toggleTabs(window.localStorage.getItem("configLangPref"))
|