Directory structure cleanup and organization overhaul

This commit is contained in:
John McCardle 2025-07-10 22:10:27 -04:00
parent 1a143982e1
commit 98fc49a978
119 changed files with 10483 additions and 4042 deletions

1
.gitignore vendored
View File

@ -30,3 +30,4 @@ scripts/
test_*
tcod_reference
.archive

File diff suppressed because it is too large Load Diff

View File

@ -3,19 +3,27 @@
A Python-powered 2D game engine for creating roguelike games, built with C++ and SFML.
* Core roguelike logic from libtcod: field of view, pathfinding
* Animate sprites with multiple frames. Smooth transitions for positions, sizes, zoom, and camera
* Simple GUI element system allows keyboard and mouse input, composition
* No compilation or installation necessary. The runtime is a full Python environment; "Zip And Ship"
![ Image ]()
**Pre-Alpha Release Demo**: my 7DRL 2025 entry *"Crypt of Sokoban"* - a prototype with buttons, boulders, enemies, and items.
## Tenets
- **Python & C++ Hand-in-Hand**: Create your game without ever recompiling. Your Python commands create C++ objects, and animations can occur without calling Python at all.
- **Simple Yet Flexible UI System**: Sprites, Grids, Frames, and Captions with full animation support
- **Entity-Component Architecture**: Implement your game objects with Python integration
- **Built-in Roguelike Support**: Dungeon generation, pathfinding, and field-of-view via libtcod (demos still under construction)
- **Automation API**: PyAutoGUI-inspired event generation framework. All McRogueFace interactions can be performed headlessly via script: for software testing or AI integration
- **Interactive Development**: Python REPL integration for live game debugging. Use `mcrogueface` like a Python interpreter
## Quick Start
**Download**:
- The entire McRogueFace visual framework:
- **Sprite**: an image file or one sprite from a shared sprite sheet
- **Caption**: load a font, display text
- **Frame**: A rectangle; put other things on it to move or manage GUIs as modules
- **Grid**: A 2D array of tiles with zoom + position control
- **Entity**: Lives on a Grid, displays a sprite, and can have a perspective or move along a path
- **Animation**: Change any property on any of the above over time
```bash
# Clone and build
git clone <wherever you found this repo>
@ -49,28 +57,59 @@ mcrfpy.setScene("intro")
## Documentation
### 📚 Full Documentation Site
For comprehensive documentation, tutorials, and API reference, visit:
**[https://mcrogueface.github.io](https://mcrogueface.github.io)**
## Requirements
The documentation site includes:
- **[Quickstart Guide](https://mcrogueface.github.io/quickstart/)** - Get running in 5 minutes
- **[McRogueFace Does The Entire Roguelike Tutorial](https://mcrogueface.github.io/tutorials/)** - Step-by-step game building
- **[Complete API Reference](https://mcrogueface.github.io/api/)** - Every function documented
- **[Cookbook](https://mcrogueface.github.io/cookbook/)** - Ready-to-use code recipes
- **[C++ Extension Guide](https://mcrogueface.github.io/extending-cpp/)** - For C++ developers: Add engine features
## Build Requirements
- C++17 compiler (GCC 7+ or Clang 5+)
- CMake 3.14+
- Python 3.12+
- SFML 2.5+
- SFML 2.6
- Linux or Windows (macOS untested)
## Project Structure
```
McRogueFace/
├── src/ # C++ engine source
├── scripts/ # Python game scripts
├── assets/ # Sprites, fonts, audio
├── build/ # Build output directory
├── build/ # Build output directory: zip + ship
│ ├─ (*)assets/ # (copied location of assets)
│ ├─ (*)scripts/ # (copied location of src/scripts)
│ └─ lib/ # SFML, TCOD libraries, Python + standard library / modules
├── deps/ # Python, SFML, and libtcod imports can be tossed in here to build
│ └─ platform/ # windows, linux subdirectories for OS-specific cpython config
├── docs/ # generated HTML, markdown docs
│ └─ stubs/ # .pyi files for editor integration
├── modules/ # git submodules, to build all of McRogueFace's dependencies from source
├── src/ # C++ engine source
│ └─ scripts/ # Python game scripts (copied during build)
└── tests/ # Automated test suite
└── tools/ # For the McRogueFace ecosystem: docs generation
```
If you are building McRogueFace to implement game logic or scene configuration in C++, you'll have to compile the project.
If you are writing a game in Python using McRogueFace, you only need to rename and zip/distribute the `build` directory.
## Philosophy
- **C++ every frame, Python every tick**: All rendering data is handled in C++. Structure your UI and program animations in Python, and they are rendered without Python. All game logic can be written in Python.
- **No Compiling Required; Zip And Ship**: Implement your game objects with Python, zip up McRogueFace with your "game.py" to ship
- **Built-in Roguelike Support**: Dungeon generation, pathfinding, and field-of-view via libtcod
- **Hands-Off Testing**: PyAutoGUI-inspired event generation framework. All McRogueFace interactions can be performed headlessly via script: for software testing or AI integration
- **Interactive Development**: Python REPL integration for live game debugging. Use `mcrogueface` like a Python interpreter
## Contributing
PRs will be considered! Please include explicit mention that your contribution is your own work and released under the MIT license in the pull request.

42
build_windows_cmake.bat Normal file
View File

@ -0,0 +1,42 @@
@echo off
REM Windows build script using cmake --build (generator-agnostic)
REM This version works with any CMake generator
echo Building McRogueFace for Windows using CMake...
REM Set build directory
set BUILD_DIR=build_win
set CONFIG=Release
REM Clean previous build
if exist %BUILD_DIR% rmdir /s /q %BUILD_DIR%
mkdir %BUILD_DIR%
cd %BUILD_DIR%
REM Configure with CMake
REM You can change the generator here if needed:
REM -G "Visual Studio 17 2022" (VS 2022)
REM -G "Visual Studio 16 2019" (VS 2019)
REM -G "MinGW Makefiles" (MinGW)
REM -G "Ninja" (Ninja build system)
cmake -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=%CONFIG% ..
if errorlevel 1 (
echo CMake configuration failed!
cd ..
exit /b 1
)
REM Build using cmake (works with any generator)
cmake --build . --config %CONFIG% --parallel
if errorlevel 1 (
echo Build failed!
cd ..
exit /b 1
)
echo.
echo Build completed successfully!
echo Executable: %BUILD_DIR%\%CONFIG%\mcrogueface.exe
echo.
cd ..

View File

@ -1,157 +0,0 @@
aqua #00FFFF
black #000000
blue #0000FF
fuchsia #FF00FF
gray #808080
green #008000
lime #00FF00
maroon #800000
navy #000080
olive #808000
purple #800080
red #FF0000
silver #C0C0C0
teal #008080
white #FFFFFF
yellow #FFFF00
aliceblue #F0F8FF
antiquewhite #FAEBD7
aqua #00FFFF
aquamarine #7FFFD4
azure #F0FFFF
beige #F5F5DC
bisque #FFE4C4
black #000000
blanchedalmond #FFEBCD
blue #0000FF
blueviolet #8A2BE2
brown #A52A2A
burlywood #DEB887
cadetblue #5F9EA0
chartreuse #7FFF00
chocolate #D2691E
coral #FF7F50
cornflowerblue #6495ED
cornsilk #FFF8DC
crimson #DC143C
cyan #00FFFF
darkblue #00008B
darkcyan #008B8B
darkgoldenrod #B8860B
darkgray #A9A9A9
darkgreen #006400
darkkhaki #BDB76B
darkmagenta #8B008B
darkolivegreen #556B2F
darkorange #FF8C00
darkorchid #9932CC
darkred #8B0000
darksalmon #E9967A
darkseagreen #8FBC8F
darkslateblue #483D8B
darkslategray #2F4F4F
darkturquoise #00CED1
darkviolet #9400D3
deeppink #FF1493
deepskyblue #00BFFF
dimgray #696969
dodgerblue #1E90FF
firebrick #B22222
floralwhite #FFFAF0
forestgreen #228B22
fuchsia #FF00FF
gainsboro #DCDCDC
ghostwhite #F8F8FF
gold #FFD700
goldenrod #DAA520
gray #7F7F7F
green #008000
greenyellow #ADFF2F
honeydew #F0FFF0
hotpink #FF69B4
indianred #CD5C5C
indigo #4B0082
ivory #FFFFF0
khaki #F0E68C
lavender #E6E6FA
lavenderblush #FFF0F5
lawngreen #7CFC00
lemonchiffon #FFFACD
lightblue #ADD8E6
lightcoral #F08080
lightcyan #E0FFFF
lightgoldenrodyellow #FAFAD2
lightgreen #90EE90
lightgrey #D3D3D3
lightpink #FFB6C1
lightsalmon #FFA07A
lightseagreen #20B2AA
lightskyblue #87CEFA
lightslategray #778899
lightsteelblue #B0C4DE
lightyellow #FFFFE0
lime #00FF00
limegreen #32CD32
linen #FAF0E6
magenta #FF00FF
maroon #800000
mediumaquamarine #66CDAA
mediumblue #0000CD
mediumorchid #BA55D3
mediumpurple #9370DB
mediumseagreen #3CB371
mediumslateblue #7B68EE
mediumspringgreen #00FA9A
mediumturquoise #48D1CC
mediumvioletred #C71585
midnightblue #191970
mintcream #F5FFFA
mistyrose #FFE4E1
moccasin #FFE4B5
navajowhite #FFDEAD
navy #000080
navyblue #9FAFDF
oldlace #FDF5E6
olive #808000
olivedrab #6B8E23
orange #FFA500
orangered #FF4500
orchid #DA70D6
palegoldenrod #EEE8AA
palegreen #98FB98
paleturquoise #AFEEEE
palevioletred #DB7093
papayawhip #FFEFD5
peachpuff #FFDAB9
peru #CD853F
pink #FFC0CB
plum #DDA0DD
powderblue #B0E0E6
purple #800080
red #FF0000
rosybrown #BC8F8F
royalblue #4169E1
saddlebrown #8B4513
salmon #FA8072
sandybrown #FA8072
seagreen #2E8B57
seashell #FFF5EE
sienna #A0522D
silver #C0C0C0
skyblue #87CEEB
slateblue #6A5ACD
slategray #708090
snow #FFFAFA
springgreen #00FF7F
steelblue #4682B4
tan #D2B48C
teal #008080
thistle #D8BFD8
tomato #FF6347
turquoise #40E0D0
violet #EE82EE
wheat #F5DEB3
white #FFFFFF
whitesmoke #F5F5F5
yellow #FFFF00
yellowgreen #9ACD32

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,923 @@
<!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</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background-color: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
h1, h2, h3, h4, h5 {
color: #2c3e50;
}
.toc {
background-color: #f8f9fa;
padding: 20px;
border-radius: 4px;
margin-bottom: 30px;
}
.toc ul {
list-style-type: none;
padding-left: 20px;
}
.toc > ul {
padding-left: 0;
}
.toc a {
text-decoration: none;
color: #3498db;
}
.toc a:hover {
text-decoration: underline;
}
.method-section {
margin-bottom: 30px;
padding: 20px;
background-color: #f8f9fa;
border-radius: 4px;
border-left: 4px solid #3498db;
}
.function-signature {
font-family: 'Consolas', 'Monaco', monospace;
background-color: #e9ecef;
padding: 10px;
border-radius: 4px;
margin: 10px 0;
}
.class-name {
color: #e74c3c;
font-weight: bold;
}
.method-name {
color: #3498db;
font-family: 'Consolas', 'Monaco', monospace;
}
.property-name {
color: #27ae60;
font-family: 'Consolas', 'Monaco', monospace;
}
.arg-name {
color: #8b4513;
font-weight: bold;
}
.arg-type {
color: #666;
font-style: italic;
}
code {
background-color: #f4f4f4;
padding: 2px 5px;
border-radius: 3px;
font-family: 'Consolas', 'Monaco', monospace;
}
pre {
background-color: #f4f4f4;
padding: 15px;
border-radius: 5px;
overflow-x: auto;
}
.deprecated {
text-decoration: line-through;
opacity: 0.6;
}
.note {
background-color: #fff3cd;
border-left: 4px solid #ffc107;
padding: 10px;
margin: 10px 0;
}
.returns {
color: #28a745;
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<h1>McRogueFace API Reference</h1>
<p><em>Generated on 2025-07-10 01:13:53</em></p>
<p><em>This documentation was dynamically generated from the compiled module.</em></p>
<div class="toc">
<h2>Table of Contents</h2>
<ul>
<li><a href="#functions">Functions</a></li>
<li><a href="#classes">Classes</a>
<ul>
<li><a href="#Animation">Animation</a></li>
<li><a href="#Caption">Caption</a></li>
<li><a href="#Color">Color</a></li>
<li><a href="#Drawable">Drawable</a></li>
<li><a href="#Entity">Entity</a></li>
<li><a href="#EntityCollection">EntityCollection</a></li>
<li><a href="#Font">Font</a></li>
<li><a href="#Frame">Frame</a></li>
<li><a href="#Grid">Grid</a></li>
<li><a href="#GridPoint">GridPoint</a></li>
<li><a href="#GridPointState">GridPointState</a></li>
<li><a href="#Scene">Scene</a></li>
<li><a href="#Sprite">Sprite</a></li>
<li><a href="#Texture">Texture</a></li>
<li><a href="#Timer">Timer</a></li>
<li><a href="#UICollection">UICollection</a></li>
<li><a href="#UICollectionIter">UICollectionIter</a></li>
<li><a href="#UIEntityCollectionIter">UIEntityCollectionIter</a></li>
<li><a href="#Vector">Vector</a></li>
<li><a href="#Window">Window</a></li>
</ul>
</li>
<li><a href="#constants">Constants</a></li>
</ul>
</div>
<h2 id="functions">Functions</h2>
<div class="method-section">
<h3><code class="function-signature">createScenecreateScene(name: str) -> None</code></h3>
<p>Create a new empty scene.
Note:</p>
<h4>Arguments:</h4>
<ul>
<li><span class='arg-name'>name</span>: Unique name for the new scene</li>
<li><span class='arg-name'>ValueError</span>: If a scene with this name already exists</li>
</ul>
</div>
<div class="method-section">
<h3><code class="function-signature">createSoundBuffercreateSoundBuffer(filename: str) -> int</code></h3>
<p>Load a sound effect from a file and return its buffer ID.</p>
<h4>Arguments:</h4>
<ul>
<li><span class='arg-name'>filename</span>: Path to the sound file (WAV, OGG, FLAC)</li>
</ul>
<p><span class='returns'>Returns:</span> int: Buffer ID for use with playSound() RuntimeError: If the file cannot be loaded</p>
</div>
<div class="method-section">
<h3><code class="function-signature">currentScenecurrentScene() -> str</code></h3>
<p>Get the name of the currently active scene.</p>
<p><span class='returns'>Returns:</span> str: Name of the current scene</p>
</div>
<div class="method-section">
<h3><code class="function-signature">delTimerdelTimer(name: str) -> None</code></h3>
<p>Stop and remove a timer.
Note:</p>
<h4>Arguments:</h4>
<ul>
<li><span class='arg-name'>name</span>: Timer identifier to remove</li>
</ul>
</div>
<div class="method-section">
<h3><code class="function-signature">exitexit() -> None</code></h3>
<p>Cleanly shut down the game engine and exit the application.
Note:</p>
</div>
<div class="method-section">
<h3><code class="function-signature">findfind(name: str, scene: str = None) -> UIDrawable | None</code></h3>
<p>Find the first UI element with the specified name.
Note:</p>
<h4>Arguments:</h4>
<ul>
<li><span class='arg-name'>name</span>: Exact name to search for</li>
<li><span class='arg-name'>scene</span>: Scene to search in (default: current scene)</li>
</ul>
<p><span class='returns'>Returns:</span> Frame, Caption, Sprite, Grid, or Entity if found; None otherwise Searches scene UI elements and entities within grids.</p>
</div>
<div class="method-section">
<h3><code class="function-signature">findAllfindAll(pattern: str, scene: str = None) -> list</code></h3>
<p>Find all UI elements matching a name pattern.</p>
<h4>Arguments:</h4>
<ul>
<li><span class='arg-name'>pattern</span>: Name pattern with optional wildcards (* matches any characters)</li>
<li><span class='arg-name'>scene</span>: Scene to search in (default: current scene)</li>
</ul>
<p><span class='returns'>Returns:</span> list: All matching UI elements and entities</p>
<h4>Example:</h4>
<pre><code>findAll(&#x27;enemy*&#x27;) # Find all elements starting with &#x27;enemy&#x27;
findAll(&#x27;*_button&#x27;) # Find all elements ending with &#x27;_button&#x27;</code></pre>
</div>
<div class="method-section">
<h3><code class="function-signature">getMetricsgetMetrics() -> dict</code></h3>
<p>Get current performance metrics.</p>
<p><span class='returns'>Returns:</span> 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</p>
</div>
<div class="method-section">
<h3><code class="function-signature">getMusicVolumegetMusicVolume() -> int</code></h3>
<p>Get the current music volume level.</p>
<p><span class='returns'>Returns:</span> int: Current volume (0-100)</p>
</div>
<div class="method-section">
<h3><code class="function-signature">getSoundVolumegetSoundVolume() -> int</code></h3>
<p>Get the current sound effects volume level.</p>
<p><span class='returns'>Returns:</span> int: Current volume (0-100)</p>
</div>
<div class="method-section">
<h3><code class="function-signature">keypressScenekeypressScene(handler: callable) -> None</code></h3>
<p>Set the keyboard event handler for the current scene.</p>
<h4>Arguments:</h4>
<ul>
<li><span class='arg-name'>handler</span>: Callable that receives (key_name: str, is_pressed: bool)</li>
</ul>
<h4>Example:</h4>
<pre><code>def on_key(key, pressed):
if key == &#x27;A&#x27; and pressed:
print(&#x27;A key pressed&#x27;)
mcrfpy.keypressScene(on_key)</code></pre>
</div>
<div class="method-section">
<h3><code class="function-signature">loadMusicloadMusic(filename: str) -> None</code></h3>
<p>Load and immediately play background music from a file.
Note:</p>
<h4>Arguments:</h4>
<ul>
<li><span class='arg-name'>filename</span>: Path to the music file (WAV, OGG, FLAC)</li>
</ul>
</div>
<div class="method-section">
<h3><code class="function-signature">playSoundplaySound(buffer_id: int) -> None</code></h3>
<p>Play a sound effect using a previously loaded buffer.</p>
<h4>Arguments:</h4>
<ul>
<li><span class='arg-name'>buffer_id</span>: Sound buffer ID returned by createSoundBuffer()</li>
<li><span class='arg-name'>RuntimeError</span>: If the buffer ID is invalid</li>
</ul>
</div>
<div class="method-section">
<h3><code class="function-signature">sceneUIsceneUI(scene: str = None) -> list</code></h3>
<p>Get all UI elements for a scene.</p>
<h4>Arguments:</h4>
<ul>
<li><span class='arg-name'>scene</span>: Scene name. If None, uses current scene</li>
</ul>
<p><span class='returns'>Returns:</span> list: All UI elements (Frame, Caption, Sprite, Grid) in the scene KeyError: If the specified scene doesn&#x27;t exist</p>
</div>
<div class="method-section">
<h3><code class="function-signature">setMusicVolumesetMusicVolume(volume: int) -> None</code></h3>
<p>Set the global music volume.</p>
<h4>Arguments:</h4>
<ul>
<li><span class='arg-name'>volume</span>: Volume level from 0 (silent) to 100 (full volume)</li>
</ul>
</div>
<div class="method-section">
<h3><code class="function-signature">setScalesetScale(multiplier: float) -> None</code></h3>
<p>Scale the game window size.
Note:</p>
<h4>Arguments:</h4>
<ul>
<li><span class='arg-name'>multiplier</span>: Scale factor (e.g., 2.0 for double size)</li>
</ul>
</div>
<div class="method-section">
<h3><code class="function-signature">setScenesetScene(scene: str, transition: str = None, duration: float = 0.0) -> None</code></h3>
<p>Switch to a different scene with optional transition effect.</p>
<h4>Arguments:</h4>
<ul>
<li><span class='arg-name'>scene</span>: Name of the scene to switch to</li>
<li><span class='arg-name'>transition</span>: Transition type (&#x27;fade&#x27;, &#x27;slide_left&#x27;, &#x27;slide_right&#x27;, &#x27;slide_up&#x27;, &#x27;slide_down&#x27;)</li>
<li><span class='arg-name'>duration</span>: Transition duration in seconds (default: 0.0 for instant)</li>
<li><span class='arg-name'>KeyError</span>: If the scene doesn&#x27;t exist</li>
<li><span class='arg-name'>ValueError</span>: If the transition type is invalid</li>
</ul>
</div>
<div class="method-section">
<h3><code class="function-signature">setSoundVolumesetSoundVolume(volume: int) -> None</code></h3>
<p>Set the global sound effects volume.</p>
<h4>Arguments:</h4>
<ul>
<li><span class='arg-name'>volume</span>: Volume level from 0 (silent) to 100 (full volume)</li>
</ul>
</div>
<div class="method-section">
<h3><code class="function-signature">setTimersetTimer(name: str, handler: callable, interval: int) -> None</code></h3>
<p>Create or update a recurring timer.
Note:</p>
<h4>Arguments:</h4>
<ul>
<li><span class='arg-name'>name</span>: Unique identifier for the timer</li>
<li><span class='arg-name'>handler</span>: Function called with (runtime: float) parameter</li>
<li><span class='arg-name'>interval</span>: Time between calls in milliseconds</li>
</ul>
</div>
<h2 id='classes'>Classes</h2>
<div class="method-section">
<h3 id="Animation"><span class="class-name">Animation</span></h3>
<p>Animation object for animating UI properties</p>
<h4>Methods:</h4>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">get_current_value(...)</code></h5>
<p>Get the current interpolated value</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">start(...)</code></h5>
<p>Start the animation on a target UIDrawable</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">updateUpdate the animation by deltaTime (returns True if still running)</code></h5>
</div>
</div>
<div class="method-section">
<h3 id="Caption"><span class="class-name">Caption</span></h3>
<p><em>Inherits from: Drawable</em></p>
<p>Caption(text=&#x27;&#x27;, 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.
Args:
text (str): The text content to display. Default: &#x27;&#x27;
x (float): X position in pixels. Default: 0
y (float): Y position in pixels. Default: 0
font (Font): Font object for text rendering. Default: engine default font
fill_color (Color): Text fill color. Default: (255, 255, 255, 255)
outline_color (Color): Text outline color. Default: (0, 0, 0, 255)
outline (float): Text outline thickness. Default: 0
click (callable): Click event handler. Default: None
Attributes:
text (str): The displayed text content
x, y (float): Position in pixels
font (Font): Font used for rendering
fill_color, outline_color (Color): Text appearance
outline (float): Outline thickness
click (callable): Click event handler
visible (bool): Visibility state
z_index (int): Rendering order
w, h (float): Read-only computed size based on text and font</p>
<h4>Methods:</h4>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">get_boundsGet bounding box as (x, y, width, height)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">moveMove by relative offset (dx, dy)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">resizeResize to new dimensions (width, height)</code></h5>
</div>
</div>
<div class="method-section">
<h3 id="Color"><span class="class-name">Color</span></h3>
<p>SFML Color Object</p>
<h4>Methods:</h4>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">from_hexCreate Color from hex string (e.g., '#FF0000' or 'FF0000')</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">lerp(...)</code></h5>
<p>Linearly interpolate between this color and another</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">to_hex(...)</code></h5>
<p>Convert Color to hex string</p>
</div>
</div>
<div class="method-section">
<h3 id="Drawable"><span class="class-name">Drawable</span></h3>
<p>Base class for all drawable UI elements</p>
<h4>Methods:</h4>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">get_boundsGet bounding box as (x, y, width, height)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">moveMove by relative offset (dx, dy)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">resizeResize to new dimensions (width, height)</code></h5>
</div>
</div>
<div class="method-section">
<h3 id="Entity"><span class="class-name">Entity</span></h3>
<p><em>Inherits from: Drawable</em></p>
<p>UIEntity objects</p>
<h4>Methods:</h4>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">at(...)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">die(...)</code></h5>
<p>Remove this entity from its grid</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">get_boundsGet bounding box as (x, y, width, height)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">index(...)</code></h5>
<p>Return the index of this entity in its grid&#x27;s entity collection</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">moveMove by relative offset (dx, dy)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">path_topath_to(x: int, y: int) -> bool</code></h5>
<p>Find and follow path to target position using A* pathfinding.</p>
<div style='margin-left: 20px;'>
<div><span class='arg-name'>x</span>: Target X coordinate</div>
<div><span class='arg-name'>y</span>: Target Y coordinate</div>
</div>
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> True if a path was found and the entity started moving, False otherwise</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">resizeResize to new dimensions (width, height)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">update_visibilityupdate_visibility() -> None</code></h5>
<p>Update entity&#x27;s visibility state based on current FOV.
Recomputes which cells are visible from the entity&#x27;s position and updates
the entity&#x27;s gridstate to track explored areas. This is called automatically
when the entity moves if it has a grid with perspective set.</p>
</div>
</div>
<div class="method-section">
<h3 id="EntityCollection"><span class="class-name">EntityCollection</span></h3>
<p>Iterable, indexable collection of Entities</p>
<h4>Methods:</h4>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">append(...)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">count(...)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">extend(...)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">index(...)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">remove(...)</code></h5>
</div>
</div>
<div class="method-section">
<h3 id="Font"><span class="class-name">Font</span></h3>
<p>SFML Font Object</p>
<h4>Methods:</h4>
</div>
<div class="method-section">
<h3 id="Frame"><span class="class-name">Frame</span></h3>
<p><em>Inherits from: Drawable</em></p>
<p>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.
Args:
x (float): X position in pixels. Default: 0
y (float): Y position in pixels. Default: 0
w (float): Width in pixels. Default: 0
h (float): Height in pixels. Default: 0
fill_color (Color): Background fill color. Default: (0, 0, 0, 128)
outline_color (Color): Border outline color. Default: (255, 255, 255, 255)
outline (float): Border outline thickness. Default: 0
click (callable): Click event handler. Default: None
children (list): Initial list of child drawable elements. Default: None
Attributes:
x, y (float): Position in pixels
w, h (float): Size in pixels
fill_color, outline_color (Color): Visual appearance
outline (float): Border thickness
click (callable): Click event handler
children (list): Collection of child drawable elements
visible (bool): Visibility state
z_index (int): Rendering order
clip_children (bool): Whether to clip children to frame bounds</p>
<h4>Methods:</h4>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">get_boundsGet bounding box as (x, y, width, height)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">moveMove by relative offset (dx, dy)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">resizeResize to new dimensions (width, height)</code></h5>
</div>
</div>
<div class="method-section">
<h3 id="Grid"><span class="class-name">Grid</span></h3>
<p><em>Inherits from: Drawable</em></p>
<p>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.
Args:
x (float): X position in pixels. Default: 0
y (float): Y position in pixels. Default: 0
grid_size (tuple): Grid dimensions as (width, height) in tiles. Default: (20, 20)
texture (Texture): Texture atlas containing tile sprites. Default: None
tile_width (int): Width of each tile in pixels. Default: 16
tile_height (int): Height of each tile in pixels. Default: 16
scale (float): Grid scaling factor. Default: 1.0
click (callable): Click event handler. Default: None
Attributes:
x, y (float): Position in pixels
grid_size (tuple): Grid dimensions (width, height) in tiles
tile_width, tile_height (int): Tile dimensions in pixels
texture (Texture): Tile texture atlas
scale (float): Scale multiplier
points (list): 2D array of GridPoint objects for tile data
entities (list): Collection of Entity objects in the grid
background_color (Color): Grid background color
click (callable): Click event handler
visible (bool): Visibility state
z_index (int): Rendering order</p>
<h4>Methods:</h4>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">at(...)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">compute_astar_pathcompute_astar_path(x1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]</code></h5>
<p>Compute A* path between two points.</p>
<div style='margin-left: 20px;'>
<div><span class='arg-name'>x1</span>: Starting X coordinate</div>
<div><span class='arg-name'>y1</span>: Starting Y coordinate</div>
<div><span class='arg-name'>x2</span>: Target X coordinate</div>
<div><span class='arg-name'>y2</span>: Target Y coordinate</div>
<div><span class='arg-name'>diagonal_cost</span>: Cost of diagonal movement (default: 1.41)</div>
</div>
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> List of (x, y) tuples representing the path, empty list if no path exists</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">compute_dijkstracompute_dijkstra(root_x: int, root_y: int, diagonal_cost: float = 1.41) -> None</code></h5>
<p>Compute Dijkstra map from root position.</p>
<div style='margin-left: 20px;'>
<div><span class='arg-name'>root_x</span>: X coordinate of the root/target</div>
<div><span class='arg-name'>root_y</span>: Y coordinate of the root/target</div>
<div><span class='arg-name'>diagonal_cost</span>: Cost of diagonal movement (default: 1.41)</div>
</div>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">compute_fovcompute_fov(x: int, y: int, radius: int = 0, light_walls: bool = True, algorithm: int = FOV_BASIC) -> None</code></h5>
<p>Compute field of view from a position.</p>
<div style='margin-left: 20px;'>
<div><span class='arg-name'>x</span>: X coordinate of the viewer</div>
<div><span class='arg-name'>y</span>: Y coordinate of the viewer</div>
<div><span class='arg-name'>radius</span>: Maximum view distance (0 = unlimited)</div>
<div><span class='arg-name'>light_walls</span>: Whether walls are lit when visible</div>
<div><span class='arg-name'>algorithm</span>: FOV algorithm to use (FOV_BASIC, FOV_DIAMOND, FOV_SHADOW, FOV_PERMISSIVE_0-8)</div>
</div>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">find_pathfind_path(x1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]</code></h5>
<p>Find A* path between two points.</p>
<div style='margin-left: 20px;'>
<div><span class='arg-name'>x1</span>: Starting X coordinate</div>
<div><span class='arg-name'>y1</span>: Starting Y coordinate</div>
<div><span class='arg-name'>x2</span>: Target X coordinate</div>
<div><span class='arg-name'>y2</span>: Target Y coordinate</div>
<div><span class='arg-name'>diagonal_cost</span>: Cost of diagonal movement (default: 1.41)</div>
</div>
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> List of (x, y) tuples representing the path, empty list if no path exists</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">get_boundsGet bounding box as (x, y, width, height)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">get_dijkstra_distanceget_dijkstra_distance(x: int, y: int) -> Optional[float]</code></h5>
<p>Get distance from Dijkstra root to position.</p>
<div style='margin-left: 20px;'>
<div><span class='arg-name'>x</span>: X coordinate to query</div>
<div><span class='arg-name'>y</span>: Y coordinate to query</div>
</div>
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> Distance as float, or None if position is unreachable or invalid</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">get_dijkstra_pathget_dijkstra_path(x: int, y: int) -> List[Tuple[int, int]]</code></h5>
<p>Get path from position to Dijkstra root.</p>
<div style='margin-left: 20px;'>
<div><span class='arg-name'>x</span>: Starting X coordinate</div>
<div><span class='arg-name'>y</span>: Starting Y coordinate</div>
</div>
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> List of (x, y) tuples representing path to root, empty if unreachable</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">is_in_fovis_in_fov(x: int, y: int) -> bool</code></h5>
<p>Check if a cell is in the field of view.</p>
<div style='margin-left: 20px;'>
<div><span class='arg-name'>x</span>: X coordinate to check</div>
<div><span class='arg-name'>y</span>: Y coordinate to check</div>
</div>
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> True if the cell is visible, False otherwise</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">moveMove by relative offset (dx, dy)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">resizeResize to new dimensions (width, height)</code></h5>
</div>
</div>
<div class="method-section">
<h3 id="GridPoint"><span class="class-name">GridPoint</span></h3>
<p>UIGridPoint object</p>
<h4>Methods:</h4>
</div>
<div class="method-section">
<h3 id="GridPointState"><span class="class-name">GridPointState</span></h3>
<p>UIGridPointState object</p>
<h4>Methods:</h4>
</div>
<div class="method-section">
<h3 id="Scene"><span class="class-name">Scene</span></h3>
<p>Base class for object-oriented scenes</p>
<h4>Methods:</h4>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">activate(...)</code></h5>
<p>Make this the active scene</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">get_ui(...)</code></h5>
<p>Get the UI element collection for this scene</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">register_keyboardRegister a keyboard handler function (alternative to overriding on_keypress)</code></h5>
</div>
</div>
<div class="method-section">
<h3 id="Sprite"><span class="class-name">Sprite</span></h3>
<p><em>Inherits from: Drawable</em></p>
<p>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.
Args:
x (float): X position in pixels. Default: 0
y (float): Y position in pixels. Default: 0
texture (Texture): Texture object to display. Default: None
sprite_index (int): Index into texture atlas (if applicable). Default: 0
scale (float): Sprite scaling factor. Default: 1.0
click (callable): Click event handler. Default: None
Attributes:
x, y (float): Position in pixels
texture (Texture): The texture being displayed
sprite_index (int): Current sprite index in texture atlas
scale (float): Scale multiplier
click (callable): Click event handler
visible (bool): Visibility state
z_index (int): Rendering order
w, h (float): Read-only computed size based on texture and scale</p>
<h4>Methods:</h4>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">get_boundsGet bounding box as (x, y, width, height)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">moveMove by relative offset (dx, dy)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">resizeResize to new dimensions (width, height)</code></h5>
</div>
</div>
<div class="method-section">
<h3 id="Texture"><span class="class-name">Texture</span></h3>
<p>SFML Texture Object</p>
<h4>Methods:</h4>
</div>
<div class="method-section">
<h3 id="Timer"><span class="class-name">Timer</span></h3>
<p>Timer object for scheduled callbacks</p>
<h4>Methods:</h4>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">cancel(...)</code></h5>
<p>Cancel the timer and remove it from the system</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">pause(...)</code></h5>
<p>Pause the timer</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">restart(...)</code></h5>
<p>Restart the timer from the current time</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">resume(...)</code></h5>
<p>Resume a paused timer</p>
</div>
</div>
<div class="method-section">
<h3 id="UICollection"><span class="class-name">UICollection</span></h3>
<p>Iterable, indexable collection of UI objects</p>
<h4>Methods:</h4>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">append(...)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">count(...)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">extend(...)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">index(...)</code></h5>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">remove(...)</code></h5>
</div>
</div>
<div class="method-section">
<h3 id="UICollectionIter"><span class="class-name">UICollectionIter</span></h3>
<p>Iterator for a collection of UI objects</p>
<h4>Methods:</h4>
</div>
<div class="method-section">
<h3 id="UIEntityCollectionIter"><span class="class-name">UIEntityCollectionIter</span></h3>
<p>Iterator for a collection of UI objects</p>
<h4>Methods:</h4>
</div>
<div class="method-section">
<h3 id="Vector"><span class="class-name">Vector</span></h3>
<p>SFML Vector Object</p>
<h4>Methods:</h4>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">angle(...)</code></h5>
<p>Return the angle in radians from the positive X axis</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">copy(...)</code></h5>
<p>Return a copy of this vector</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">distance_to(...)</code></h5>
<p>Return the distance to another vector</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">dot(...)</code></h5>
<p>Return the dot product with another vector</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">magnitude(...)</code></h5>
<p>Return the length of the vector</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">magnitude_squared(...)</code></h5>
<p>Return the squared length of the vector</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">normalize(...)</code></h5>
<p>Return a unit vector in the same direction</p>
</div>
</div>
<div class="method-section">
<h3 id="Window"><span class="class-name">Window</span></h3>
<p>Window singleton for accessing and modifying the game window properties</p>
<h4>Methods:</h4>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">center(...)</code></h5>
<p>Center the window on the screen</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">get(...)</code></h5>
<p>Get the Window singleton instance</p>
</div>
<div style="margin-left: 20px; margin-bottom: 15px;">
<h5><code class="method-name">screenshot(...)</code></h5>
<p>Take a screenshot. Pass filename to save to file, or get raw bytes if no filename.</p>
</div>
</div>
<h2 id='constants'>Constants</h2>
<ul>
<li><code>FOV_BASIC</code> (int): 0</li>
<li><code>FOV_DIAMOND</code> (int): 1</li>
<li><code>FOV_PERMISSIVE_0</code> (int): 3</li>
<li><code>FOV_PERMISSIVE_1</code> (int): 4</li>
<li><code>FOV_PERMISSIVE_2</code> (int): 5</li>
<li><code>FOV_PERMISSIVE_3</code> (int): 6</li>
<li><code>FOV_PERMISSIVE_4</code> (int): 7</li>
<li><code>FOV_PERMISSIVE_5</code> (int): 8</li>
<li><code>FOV_PERMISSIVE_6</code> (int): 9</li>
<li><code>FOV_PERMISSIVE_7</code> (int): 10</li>
<li><code>FOV_PERMISSIVE_8</code> (int): 11</li>
<li><code>FOV_RESTRICTIVE</code> (int): 12</li>
<li><code>FOV_SHADOW</code> (int): 2</li>
</ul>
</div>
</body>
</html>

209
docs/stubs/mcrfpy.pyi Normal file
View File

@ -0,0 +1,209 @@
"""Type stubs for McRogueFace Python API.
Auto-generated - do not edit directly.
"""
from typing import Any, List, Dict, Tuple, Optional, Callable, Union
# Module documentation
# 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
# Classes
class Animation:
"""Animation object for animating UI properties"""
def __init__(selftype(self)) -> None: ...
def get_current_value(self, *args, **kwargs) -> Any: ...
def start(self, *args, **kwargs) -> Any: ...
def update(selfreturns True if still running) -> Any: ...
class Caption:
"""Caption(text='', x=0, y=0, font=None, fill_color=None, outline_color=None, outline=0, click=None)"""
def __init__(selftype(self)) -> None: ...
def get_bounds(selfx, y, width, height) -> Any: ...
def move(selfdx, dy) -> Any: ...
def resize(selfwidth, height) -> Any: ...
class Color:
"""SFML Color Object"""
def __init__(selftype(self)) -> None: ...
def from_hex(selfe.g., '#FF0000' or 'FF0000') -> Any: ...
def lerp(self, *args, **kwargs) -> Any: ...
def to_hex(self, *args, **kwargs) -> Any: ...
class Drawable:
"""Base class for all drawable UI elements"""
def __init__(selftype(self)) -> None: ...
def get_bounds(selfx, y, width, height) -> Any: ...
def move(selfdx, dy) -> Any: ...
def resize(selfwidth, height) -> Any: ...
class Entity:
"""UIEntity objects"""
def __init__(selftype(self)) -> None: ...
def at(self, *args, **kwargs) -> Any: ...
def die(self, *args, **kwargs) -> Any: ...
def get_bounds(selfx, y, width, height) -> Any: ...
def index(self, *args, **kwargs) -> Any: ...
def move(selfdx, dy) -> Any: ...
def path_to(selfx: int, y: int) -> bool: ...
def resize(selfwidth, height) -> Any: ...
def update_visibility(self) -> None: ...
class EntityCollection:
"""Iterable, indexable collection of Entities"""
def __init__(selftype(self)) -> None: ...
def append(self, *args, **kwargs) -> Any: ...
def count(self, *args, **kwargs) -> Any: ...
def extend(self, *args, **kwargs) -> Any: ...
def index(self, *args, **kwargs) -> Any: ...
def remove(self, *args, **kwargs) -> Any: ...
class Font:
"""SFML Font Object"""
def __init__(selftype(self)) -> None: ...
class Frame:
"""Frame(x=0, y=0, w=0, h=0, fill_color=None, outline_color=None, outline=0, click=None, children=None)"""
def __init__(selftype(self)) -> None: ...
def get_bounds(selfx, y, width, height) -> Any: ...
def move(selfdx, dy) -> Any: ...
def resize(selfwidth, height) -> Any: ...
class Grid:
"""Grid(x=0, y=0, grid_size=(20, 20), texture=None, tile_width=16, tile_height=16, scale=1.0, click=None)"""
def __init__(selftype(self)) -> None: ...
def at(self, *args, **kwargs) -> Any: ...
def compute_astar_path(selfx1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]: ...
def compute_dijkstra(selfroot_x: int, root_y: int, diagonal_cost: float = 1.41) -> None: ...
def compute_fov(selfx: int, y: int, radius: int = 0, light_walls: bool = True, algorithm: int = FOV_BASIC) -> None: ...
def find_path(selfx1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]: ...
def get_bounds(selfx, y, width, height) -> Any: ...
def get_dijkstra_distance(selfx: int, y: int) -> Optional[float]: ...
def get_dijkstra_path(selfx: int, y: int) -> List[Tuple[int, int]]: ...
def is_in_fov(selfx: int, y: int) -> bool: ...
def move(selfdx, dy) -> Any: ...
def resize(selfwidth, height) -> Any: ...
class GridPoint:
"""UIGridPoint object"""
def __init__(selftype(self)) -> None: ...
class GridPointState:
"""UIGridPointState object"""
def __init__(selftype(self)) -> None: ...
class Scene:
"""Base class for object-oriented scenes"""
def __init__(selftype(self)) -> None: ...
def activate(self, *args, **kwargs) -> Any: ...
def get_ui(self, *args, **kwargs) -> Any: ...
def register_keyboard(selfalternative to overriding on_keypress) -> Any: ...
class Sprite:
"""Sprite(x=0, y=0, texture=None, sprite_index=0, scale=1.0, click=None)"""
def __init__(selftype(self)) -> None: ...
def get_bounds(selfx, y, width, height) -> Any: ...
def move(selfdx, dy) -> Any: ...
def resize(selfwidth, height) -> Any: ...
class Texture:
"""SFML Texture Object"""
def __init__(selftype(self)) -> None: ...
class Timer:
"""Timer object for scheduled callbacks"""
def __init__(selftype(self)) -> None: ...
def cancel(self, *args, **kwargs) -> Any: ...
def pause(self, *args, **kwargs) -> Any: ...
def restart(self, *args, **kwargs) -> Any: ...
def resume(self, *args, **kwargs) -> Any: ...
class UICollection:
"""Iterable, indexable collection of UI objects"""
def __init__(selftype(self)) -> None: ...
def append(self, *args, **kwargs) -> Any: ...
def count(self, *args, **kwargs) -> Any: ...
def extend(self, *args, **kwargs) -> Any: ...
def index(self, *args, **kwargs) -> Any: ...
def remove(self, *args, **kwargs) -> Any: ...
class UICollectionIter:
"""Iterator for a collection of UI objects"""
def __init__(selftype(self)) -> None: ...
class UIEntityCollectionIter:
"""Iterator for a collection of UI objects"""
def __init__(selftype(self)) -> None: ...
class Vector:
"""SFML Vector Object"""
def __init__(selftype(self)) -> None: ...
def angle(self, *args, **kwargs) -> Any: ...
def copy(self, *args, **kwargs) -> Any: ...
def distance_to(self, *args, **kwargs) -> Any: ...
def dot(self, *args, **kwargs) -> Any: ...
def magnitude(self, *args, **kwargs) -> Any: ...
def magnitude_squared(self, *args, **kwargs) -> Any: ...
def normalize(self, *args, **kwargs) -> Any: ...
class Window:
"""Window singleton for accessing and modifying the game window properties"""
def __init__(selftype(self)) -> None: ...
def center(self, *args, **kwargs) -> Any: ...
def get(self, *args, **kwargs) -> Any: ...
def screenshot(self, *args, **kwargs) -> Any: ...
# Functions
def createScene(name: str) -> None: ...
def createSoundBuffer(filename: str) -> int: ...
def currentScene() -> str: ...
def delTimer(name: str) -> None: ...
def exit() -> None: ...
def find(name: str, scene: str = None) -> UIDrawable | None: ...
def findAll(pattern: str, scene: str = None) -> list: ...
def getMetrics() -> dict: ...
def getMusicVolume() -> int: ...
def getSoundVolume() -> int: ...
def keypressScene(handler: callable) -> None: ...
def loadMusic(filename: str) -> None: ...
def playSound(buffer_id: int) -> None: ...
def sceneUI(scene: str = None) -> list: ...
def setMusicVolume(volume: int) -> None: ...
def setScale(multiplier: float) -> None: ...
def setScene(scene: str, transition: str = None, duration: float = 0.0) -> None: ...
def setSoundVolume(volume: int) -> None: ...
def setTimer(name: str, handler: callable, interval: int) -> None: ...
# Constants
FOV_BASIC: int
FOV_DIAMOND: int
FOV_PERMISSIVE_0: int
FOV_PERMISSIVE_1: int
FOV_PERMISSIVE_2: int
FOV_PERMISSIVE_3: int
FOV_PERMISSIVE_4: int
FOV_PERMISSIVE_5: int
FOV_PERMISSIVE_6: int
FOV_PERMISSIVE_7: int
FOV_PERMISSIVE_8: int
FOV_RESTRICTIVE: int
FOV_SHADOW: int
default_font: Any
default_texture: Any

View File

@ -0,0 +1,209 @@
"""Type stubs for McRogueFace Python API.
Auto-generated - do not edit directly.
"""
from typing import Any, List, Dict, Tuple, Optional, Callable, Union
# Module documentation
# 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
# Classes
class Animation:
"""Animation object for animating UI properties"""
def __init__(selftype(self)) -> None: ...
def get_current_value(self, *args, **kwargs) -> Any: ...
def start(self, *args, **kwargs) -> Any: ...
def update(selfreturns True if still running) -> Any: ...
class Caption:
"""Caption(text='', x=0, y=0, font=None, fill_color=None, outline_color=None, outline=0, click=None)"""
def __init__(selftype(self)) -> None: ...
def get_bounds(selfx, y, width, height) -> Any: ...
def move(selfdx, dy) -> Any: ...
def resize(selfwidth, height) -> Any: ...
class Color:
"""SFML Color Object"""
def __init__(selftype(self)) -> None: ...
def from_hex(selfe.g., '#FF0000' or 'FF0000') -> Any: ...
def lerp(self, *args, **kwargs) -> Any: ...
def to_hex(self, *args, **kwargs) -> Any: ...
class Drawable:
"""Base class for all drawable UI elements"""
def __init__(selftype(self)) -> None: ...
def get_bounds(selfx, y, width, height) -> Any: ...
def move(selfdx, dy) -> Any: ...
def resize(selfwidth, height) -> Any: ...
class Entity:
"""UIEntity objects"""
def __init__(selftype(self)) -> None: ...
def at(self, *args, **kwargs) -> Any: ...
def die(self, *args, **kwargs) -> Any: ...
def get_bounds(selfx, y, width, height) -> Any: ...
def index(self, *args, **kwargs) -> Any: ...
def move(selfdx, dy) -> Any: ...
def path_to(selfx: int, y: int) -> bool: ...
def resize(selfwidth, height) -> Any: ...
def update_visibility(self) -> None: ...
class EntityCollection:
"""Iterable, indexable collection of Entities"""
def __init__(selftype(self)) -> None: ...
def append(self, *args, **kwargs) -> Any: ...
def count(self, *args, **kwargs) -> Any: ...
def extend(self, *args, **kwargs) -> Any: ...
def index(self, *args, **kwargs) -> Any: ...
def remove(self, *args, **kwargs) -> Any: ...
class Font:
"""SFML Font Object"""
def __init__(selftype(self)) -> None: ...
class Frame:
"""Frame(x=0, y=0, w=0, h=0, fill_color=None, outline_color=None, outline=0, click=None, children=None)"""
def __init__(selftype(self)) -> None: ...
def get_bounds(selfx, y, width, height) -> Any: ...
def move(selfdx, dy) -> Any: ...
def resize(selfwidth, height) -> Any: ...
class Grid:
"""Grid(x=0, y=0, grid_size=(20, 20), texture=None, tile_width=16, tile_height=16, scale=1.0, click=None)"""
def __init__(selftype(self)) -> None: ...
def at(self, *args, **kwargs) -> Any: ...
def compute_astar_path(selfx1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]: ...
def compute_dijkstra(selfroot_x: int, root_y: int, diagonal_cost: float = 1.41) -> None: ...
def compute_fov(selfx: int, y: int, radius: int = 0, light_walls: bool = True, algorithm: int = FOV_BASIC) -> None: ...
def find_path(selfx1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]: ...
def get_bounds(selfx, y, width, height) -> Any: ...
def get_dijkstra_distance(selfx: int, y: int) -> Optional[float]: ...
def get_dijkstra_path(selfx: int, y: int) -> List[Tuple[int, int]]: ...
def is_in_fov(selfx: int, y: int) -> bool: ...
def move(selfdx, dy) -> Any: ...
def resize(selfwidth, height) -> Any: ...
class GridPoint:
"""UIGridPoint object"""
def __init__(selftype(self)) -> None: ...
class GridPointState:
"""UIGridPointState object"""
def __init__(selftype(self)) -> None: ...
class Scene:
"""Base class for object-oriented scenes"""
def __init__(selftype(self)) -> None: ...
def activate(self, *args, **kwargs) -> Any: ...
def get_ui(self, *args, **kwargs) -> Any: ...
def register_keyboard(selfalternative to overriding on_keypress) -> Any: ...
class Sprite:
"""Sprite(x=0, y=0, texture=None, sprite_index=0, scale=1.0, click=None)"""
def __init__(selftype(self)) -> None: ...
def get_bounds(selfx, y, width, height) -> Any: ...
def move(selfdx, dy) -> Any: ...
def resize(selfwidth, height) -> Any: ...
class Texture:
"""SFML Texture Object"""
def __init__(selftype(self)) -> None: ...
class Timer:
"""Timer object for scheduled callbacks"""
def __init__(selftype(self)) -> None: ...
def cancel(self, *args, **kwargs) -> Any: ...
def pause(self, *args, **kwargs) -> Any: ...
def restart(self, *args, **kwargs) -> Any: ...
def resume(self, *args, **kwargs) -> Any: ...
class UICollection:
"""Iterable, indexable collection of UI objects"""
def __init__(selftype(self)) -> None: ...
def append(self, *args, **kwargs) -> Any: ...
def count(self, *args, **kwargs) -> Any: ...
def extend(self, *args, **kwargs) -> Any: ...
def index(self, *args, **kwargs) -> Any: ...
def remove(self, *args, **kwargs) -> Any: ...
class UICollectionIter:
"""Iterator for a collection of UI objects"""
def __init__(selftype(self)) -> None: ...
class UIEntityCollectionIter:
"""Iterator for a collection of UI objects"""
def __init__(selftype(self)) -> None: ...
class Vector:
"""SFML Vector Object"""
def __init__(selftype(self)) -> None: ...
def angle(self, *args, **kwargs) -> Any: ...
def copy(self, *args, **kwargs) -> Any: ...
def distance_to(self, *args, **kwargs) -> Any: ...
def dot(self, *args, **kwargs) -> Any: ...
def magnitude(self, *args, **kwargs) -> Any: ...
def magnitude_squared(self, *args, **kwargs) -> Any: ...
def normalize(self, *args, **kwargs) -> Any: ...
class Window:
"""Window singleton for accessing and modifying the game window properties"""
def __init__(selftype(self)) -> None: ...
def center(self, *args, **kwargs) -> Any: ...
def get(self, *args, **kwargs) -> Any: ...
def screenshot(self, *args, **kwargs) -> Any: ...
# Functions
def createScene(name: str) -> None: ...
def createSoundBuffer(filename: str) -> int: ...
def currentScene() -> str: ...
def delTimer(name: str) -> None: ...
def exit() -> None: ...
def find(name: str, scene: str = None) -> UIDrawable | None: ...
def findAll(pattern: str, scene: str = None) -> list: ...
def getMetrics() -> dict: ...
def getMusicVolume() -> int: ...
def getSoundVolume() -> int: ...
def keypressScene(handler: callable) -> None: ...
def loadMusic(filename: str) -> None: ...
def playSound(buffer_id: int) -> None: ...
def sceneUI(scene: str = None) -> list: ...
def setMusicVolume(volume: int) -> None: ...
def setScale(multiplier: float) -> None: ...
def setScene(scene: str, transition: str = None, duration: float = 0.0) -> None: ...
def setSoundVolume(volume: int) -> None: ...
def setTimer(name: str, handler: callable, interval: int) -> None: ...
# Constants
FOV_BASIC: int
FOV_DIAMOND: int
FOV_PERMISSIVE_0: int
FOV_PERMISSIVE_1: int
FOV_PERMISSIVE_2: int
FOV_PERMISSIVE_3: int
FOV_PERMISSIVE_4: int
FOV_PERMISSIVE_5: int
FOV_PERMISSIVE_6: int
FOV_PERMISSIVE_7: int
FOV_PERMISSIVE_8: int
FOV_RESTRICTIVE: int
FOV_SHADOW: int
default_font: Any
default_texture: Any

View File

@ -0,0 +1,24 @@
"""Type stubs for McRogueFace automation API."""
from typing import Optional, Tuple
def click(x=None, y=None, clicks=1, interval=0.0, button='left') -> Any: ...
def doubleClick(x=None, y=None) -> Any: ...
def dragRel(xOffset, yOffset, duration=0.0, button='left') -> Any: ...
def dragTo(x, y, duration=0.0, button='left') -> Any: ...
def hotkey(*keys) - Press a hotkey combination (e.g., hotkey('ctrl', 'c')) -> Any: ...
def keyDown(key) -> Any: ...
def keyUp(key) -> Any: ...
def middleClick(x=None, y=None) -> Any: ...
def mouseDown(x=None, y=None, button='left') -> Any: ...
def mouseUp(x=None, y=None, button='left') -> Any: ...
def moveRel(xOffset, yOffset, duration=0.0) -> Any: ...
def moveTo(x, y, duration=0.0) -> Any: ...
def onScreen(x, y) -> Any: ...
def position() - Get current mouse position as (x, y) -> Any: ...
def rightClick(x=None, y=None) -> Any: ...
def screenshot(filename) -> Any: ...
def scroll(clicks, x=None, y=None) -> Any: ...
def size() - Get screen size as (width, height) -> Any: ...
def tripleClick(x=None, y=None) -> Any: ...
def typewrite(message, interval=0.0) -> Any: ...

0
docs/stubs/py.typed Normal file
View File

View File

@ -0,0 +1,146 @@
#!/usr/bin/env python3
"""
McRogueFace Animation Demo - Safe Version
=========================================
A safer, simpler version that demonstrates animations without crashes.
"""
import mcrfpy
import sys
# Configuration
DEMO_DURATION = 4.0
# Track state
current_demo = 0
subtitle = None
demo_items = []
def create_scene():
"""Create the demo scene"""
mcrfpy.createScene("demo")
mcrfpy.setScene("demo")
ui = mcrfpy.sceneUI("demo")
# Title
title = mcrfpy.Caption("Animation Demo", 500, 20)
title.fill_color = mcrfpy.Color(255, 255, 0)
title.outline = 2
ui.append(title)
# Subtitle
global subtitle
subtitle = mcrfpy.Caption("Starting...", 450, 60)
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(subtitle)
def clear_demo_items():
"""Clear demo items from scene"""
global demo_items
ui = mcrfpy.sceneUI("demo")
# Remove demo items by tracking what we added
for item in demo_items:
try:
# Find index of item
for i in range(len(ui)):
if i >= 2: # Skip title and subtitle
ui.remove(i)
break
except:
pass
demo_items = []
def demo1_basic():
"""Basic frame animations"""
global demo_items
clear_demo_items()
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 1: Basic Frame Animations"
# Create frame
f = mcrfpy.Frame(100, 150, 200, 100)
f.fill_color = mcrfpy.Color(50, 50, 150)
f.outline = 3
ui.append(f)
demo_items.append(f)
# Simple animations
mcrfpy.Animation("x", 600.0, 2.0, "easeInOut").start(f)
mcrfpy.Animation("w", 300.0, 2.0, "easeInOut").start(f)
mcrfpy.Animation("fill_color", (255, 100, 50, 200), 3.0, "linear").start(f)
def demo2_caption():
"""Caption animations"""
global demo_items
clear_demo_items()
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 2: Caption Animations"
# Moving caption
c1 = mcrfpy.Caption("Moving Text!", 100, 200)
c1.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(c1)
demo_items.append(c1)
mcrfpy.Animation("x", 700.0, 3.0, "easeOutBounce").start(c1)
# Typewriter
c2 = mcrfpy.Caption("", 100, 300)
c2.fill_color = mcrfpy.Color(0, 255, 255)
ui.append(c2)
demo_items.append(c2)
mcrfpy.Animation("text", "Typewriter effect...", 3.0, "linear").start(c2)
def demo3_multiple():
"""Multiple animations"""
global demo_items
clear_demo_items()
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 3: Multiple Animations"
# Create several frames
for i in range(5):
f = mcrfpy.Frame(100 + i * 120, 200, 80, 80)
f.fill_color = mcrfpy.Color(50 + i * 40, 100, 200 - i * 30)
ui.append(f)
demo_items.append(f)
# Animate each differently
target_y = 350 + i * 20
mcrfpy.Animation("y", float(target_y), 2.0, "easeInOut").start(f)
mcrfpy.Animation("opacity", 0.5, 3.0, "easeInOut").start(f)
def run_next_demo(runtime):
"""Run the next demo"""
global current_demo
demos = [demo1_basic, demo2_caption, demo3_multiple]
if current_demo < len(demos):
demos[current_demo]()
current_demo += 1
if current_demo < len(demos):
mcrfpy.setTimer("next", run_next_demo, int(DEMO_DURATION * 1000))
else:
subtitle.text = "Demo Complete!"
# Exit after a delay
def exit_program(rt):
print("Demo finished successfully!")
sys.exit(0)
mcrfpy.setTimer("exit", exit_program, 2000)
# Initialize
print("Starting Safe Animation Demo...")
create_scene()
# Start demos
mcrfpy.setTimer("start", run_next_demo, 500)

View File

@ -0,0 +1,615 @@
#!/usr/bin/env python3
"""
McRogueFace Animation Sizzle Reel
=================================
This script demonstrates EVERY animation type on EVERY UI object type.
It showcases all 30 easing functions, all animatable properties, and
special animation modes (delta, sprite sequences, text effects).
The script creates a comprehensive visual demonstration of the animation
system's capabilities, cycling through different objects and effects.
Author: Claude
Purpose: Complete animation system demonstration
"""
import mcrfpy
from mcrfpy import Color, Frame, Caption, Sprite, Grid, Entity, Texture, Animation
import sys
import math
# Configuration
SCENE_WIDTH = 1280
SCENE_HEIGHT = 720
DEMO_DURATION = 5.0 # Duration for each demo section
# All available easing functions
EASING_FUNCTIONS = [
"linear", "easeIn", "easeOut", "easeInOut",
"easeInQuad", "easeOutQuad", "easeInOutQuad",
"easeInCubic", "easeOutCubic", "easeInOutCubic",
"easeInQuart", "easeOutQuart", "easeInOutQuart",
"easeInSine", "easeOutSine", "easeInOutSine",
"easeInExpo", "easeOutExpo", "easeInOutExpo",
"easeInCirc", "easeOutCirc", "easeInOutCirc",
"easeInElastic", "easeOutElastic", "easeInOutElastic",
"easeInBack", "easeOutBack", "easeInOutBack",
"easeInBounce", "easeOutBounce", "easeInOutBounce"
]
# Track current demo state
current_demo = 0
demo_start_time = 0
demos = []
# Handle ESC key to exit
def handle_keypress(scene_name, keycode):
if keycode == 256: # ESC key
print("Exiting animation sizzle reel...")
sys.exit(0)
def create_demo_scene():
"""Create the main demo scene with title"""
mcrfpy.createScene("sizzle_reel")
mcrfpy.setScene("sizzle_reel")
mcrfpy.keypressScene(handle_keypress)
ui = mcrfpy.sceneUI("sizzle_reel")
# Title caption
title = Caption("McRogueFace Animation Sizzle Reel",
SCENE_WIDTH/2 - 200, 20)
title.fill_color = Color(255, 255, 0)
title.outline = 2
title.outline_color = Color(0, 0, 0)
ui.append(title)
# Subtitle showing current demo
global subtitle
subtitle = Caption("Initializing...",
SCENE_WIDTH/2 - 150, 60)
subtitle.fill_color = Color(200, 200, 200)
ui.append(subtitle)
return ui
def demo_frame_basic_animations(ui):
"""Demo 1: Basic frame animations - position, size, colors"""
subtitle.text = "Demo 1: Frame Basic Animations (Position, Size, Colors)"
# Create test frame
frame = Frame(100, 150, 200, 100)
frame.fill_color = Color(50, 50, 150)
frame.outline = 3
frame.outline_color = Color(255, 255, 255)
ui.append(frame)
# Position animations with different easings
x_anim = Animation("x", 800.0, 2.0, "easeInOutBack")
y_anim = Animation("y", 400.0, 2.0, "easeInOutElastic")
x_anim.start(frame)
y_anim.start(frame)
# Size animations
w_anim = Animation("w", 400.0, 3.0, "easeInOutCubic")
h_anim = Animation("h", 200.0, 3.0, "easeInOutCubic")
w_anim.start(frame)
h_anim.start(frame)
# Color animations - use tuples instead of Color objects
fill_anim = Animation("fill_color", (255, 100, 50, 200), 4.0, "easeInOutSine")
outline_anim = Animation("outline_color", (0, 255, 255, 255), 4.0, "easeOutBounce")
fill_anim.start(frame)
outline_anim.start(frame)
# Outline thickness animation
thickness_anim = Animation("outline", 10.0, 4.5, "easeInOutQuad")
thickness_anim.start(frame)
return frame
def demo_frame_opacity_zindex(ui):
"""Demo 2: Frame opacity and z-index animations"""
subtitle.text = "Demo 2: Frame Opacity & Z-Index Animations"
frames = []
colors = [
Color(255, 0, 0, 200),
Color(0, 255, 0, 200),
Color(0, 0, 255, 200),
Color(255, 255, 0, 200)
]
# Create overlapping frames
for i in range(4):
frame = Frame(200 + i*80, 200 + i*40, 200, 150)
frame.fill_color = colors[i]
frame.outline = 2
frame.z_index = i
ui.append(frame)
frames.append(frame)
# Animate opacity in waves
opacity_anim = Animation("opacity", 0.3, 2.0, "easeInOutSine")
opacity_anim.start(frame)
# Reverse opacity animation
opacity_back = Animation("opacity", 1.0, 2.0, "easeInOutSine", delta=False)
mcrfpy.setTimer(f"opacity_back_{i}", lambda t, f=frame, a=opacity_back: a.start(f), 2000)
# Z-index shuffle animation
z_anim = Animation("z_index", (i + 2) % 4, 3.0, "linear")
z_anim.start(frame)
return frames
def demo_caption_animations(ui):
"""Demo 3: Caption text animations and effects"""
subtitle.text = "Demo 3: Caption Animations (Text, Color, Position)"
# Basic caption with position animation
caption1 = Caption("Moving Text!", 100, 200)
caption1.fill_color = Color(255, 255, 255)
caption1.outline = 1
ui.append(caption1)
# Animate across screen with bounce
x_anim = Animation("x", 900.0, 3.0, "easeOutBounce")
x_anim.start(caption1)
# Color cycling caption
caption2 = Caption("Rainbow Colors", 400, 300)
caption2.outline = 2
ui.append(caption2)
# Cycle through colors - use tuples
color_anim1 = Animation("fill_color", (255, 0, 0, 255), 1.0, "linear")
color_anim2 = Animation("fill_color", (0, 255, 0, 255), 1.0, "linear")
color_anim3 = Animation("fill_color", (0, 0, 255, 255), 1.0, "linear")
color_anim4 = Animation("fill_color", (255, 255, 255, 255), 1.0, "linear")
color_anim1.start(caption2)
mcrfpy.setTimer("color2", lambda t: color_anim2.start(caption2), 1000)
mcrfpy.setTimer("color3", lambda t: color_anim3.start(caption2), 2000)
mcrfpy.setTimer("color4", lambda t: color_anim4.start(caption2), 3000)
# Typewriter effect caption
caption3 = Caption("", 100, 400)
caption3.fill_color = Color(0, 255, 255)
ui.append(caption3)
typewriter = Animation("text", "This text appears one character at a time...", 3.0, "linear")
typewriter.start(caption3)
# Size animation caption
caption4 = Caption("Growing Text", 400, 500)
caption4.fill_color = Color(255, 200, 0)
ui.append(caption4)
# Note: size animation would require font size property support
# For now, animate position to simulate growth
scale_sim = Animation("y", 480.0, 2.0, "easeInOutElastic")
scale_sim.start(caption4)
return [caption1, caption2, caption3, caption4]
def demo_sprite_animations(ui):
"""Demo 4: Sprite animations including sprite sequences"""
subtitle.text = "Demo 4: Sprite Animations (Position, Scale, Sprite Sequences)"
# Load a test texture (you'll need to adjust path)
try:
texture = Texture("assets/sprites/player.png", grid_size=(32, 32))
except:
# Fallback if texture not found
texture = None
if texture:
# Basic sprite with position animation
sprite1 = Sprite(100, 200, texture, sprite_index=0)
sprite1.scale = 2.0
ui.append(sprite1)
# Circular motion using sin/cos animations
# We'll use delta mode to create circular motion
x_circle = Animation("x", 300.0, 4.0, "easeInOutSine")
y_circle = Animation("y", 300.0, 4.0, "easeInOutCubic")
x_circle.start(sprite1)
y_circle.start(sprite1)
# Sprite sequence animation (walking cycle)
sprite2 = Sprite(500, 300, texture, sprite_index=0)
sprite2.scale = 3.0
ui.append(sprite2)
# Animate through sprite indices for animation
walk_cycle = Animation("sprite_index", [0, 1, 2, 3, 2, 1], 2.0, "linear")
walk_cycle.start(sprite2)
# Scale pulsing sprite
sprite3 = Sprite(800, 400, texture, sprite_index=4)
ui.append(sprite3)
# Note: scale animation would need to be supported
# For now use position to simulate
pulse_y = Animation("y", 380.0, 0.5, "easeInOutSine")
pulse_y.start(sprite3)
# Z-index animation for layering
sprite3_z = Animation("z_index", 10, 2.0, "linear")
sprite3_z.start(sprite3)
return [sprite1, sprite2, sprite3]
else:
# Create placeholder caption if no texture
no_texture = Caption("(Sprite demo requires texture file)", 400, 350)
no_texture.fill_color = Color(255, 100, 100)
ui.append(no_texture)
return [no_texture]
def demo_grid_animations(ui):
"""Demo 5: Grid animations (position, camera, zoom)"""
subtitle.text = "Demo 5: Grid Animations (Position, Camera Effects)"
# Create a grid
try:
texture = Texture("assets/sprites/tiles.png", grid_size=(16, 16))
except:
texture = None
grid = Grid(100, 150, grid_size=(20, 15), texture=texture,
tile_width=24, tile_height=24)
grid.fill_color = Color(20, 20, 40)
ui.append(grid)
# Fill with some test pattern
for y in range(15):
for x in range(20):
point = grid.at(x, y)
point.tilesprite = (x + y) % 4
point.walkable = ((x + y) % 3) != 0
if not point.walkable:
point.color = Color(100, 50, 50, 128)
# Animate grid position
grid_x = Animation("x", 400.0, 3.0, "easeInOutBack")
grid_x.start(grid)
# Camera pan animation (if supported)
# center_x = Animation("center", (10.0, 7.5), 4.0, "easeInOutCubic")
# center_x.start(grid)
# Create entities in the grid
if texture:
entity1 = Entity(5.0, 5.0, texture, sprite_index=8)
entity1.scale = 1.5
grid.entities.append(entity1)
# Animate entity movement
entity_pos = Animation("position", (15.0, 10.0), 3.0, "easeInOutQuad")
entity_pos.start(entity1)
# Create patrolling entity
entity2 = Entity(10.0, 2.0, texture, sprite_index=12)
grid.entities.append(entity2)
# Animate sprite changes
entity2_sprite = Animation("sprite_index", [12, 13, 14, 15, 14, 13], 2.0, "linear")
entity2_sprite.start(entity2)
return grid
def demo_complex_combinations(ui):
"""Demo 6: Complex multi-property animations"""
subtitle.text = "Demo 6: Complex Multi-Property Animations"
# Create a complex UI composition
main_frame = Frame(200, 200, 400, 300)
main_frame.fill_color = Color(30, 30, 60, 200)
main_frame.outline = 2
ui.append(main_frame)
# Child elements
title = Caption("Multi-Animation Demo", 20, 20)
title.fill_color = Color(255, 255, 255)
main_frame.children.append(title)
# Animate everything at once
# Frame animations
frame_x = Animation("x", 600.0, 3.0, "easeInOutElastic")
frame_w = Animation("w", 300.0, 2.5, "easeOutBack")
frame_fill = Animation("fill_color", (60, 30, 90, 220), 4.0, "easeInOutSine")
frame_outline = Animation("outline", 8.0, 3.0, "easeInOutQuad")
frame_x.start(main_frame)
frame_w.start(main_frame)
frame_fill.start(main_frame)
frame_outline.start(main_frame)
# Title animations
title_color = Animation("fill_color", (255, 200, 0, 255), 2.0, "easeOutBounce")
title_color.start(title)
# Add animated sub-frames
for i in range(3):
sub_frame = Frame(50 + i * 100, 100, 80, 80)
sub_frame.fill_color = Color(100 + i*50, 50, 200 - i*50, 180)
main_frame.children.append(sub_frame)
# Rotate positions using delta animations
sub_y = Animation("y", 50.0, 2.0, "easeInOutSine", delta=True)
sub_y.start(sub_frame)
return main_frame
def demo_easing_showcase(ui):
"""Demo 7: Showcase all 30 easing functions"""
subtitle.text = "Demo 7: All 30 Easing Functions Showcase"
# Create small frames for each easing function
frames_per_row = 6
frame_size = 180
spacing = 10
for i, easing in enumerate(EASING_FUNCTIONS[:12]): # First 12 easings
row = i // frames_per_row
col = i % frames_per_row
x = 50 + col * (frame_size + spacing)
y = 150 + row * (60 + spacing)
# Create indicator frame
frame = Frame(x, y, 20, 20)
frame.fill_color = Color(100, 200, 255)
frame.outline = 1
ui.append(frame)
# Label
label = Caption(easing, x, y - 20)
label.fill_color = Color(200, 200, 200)
ui.append(label)
# Animate using this easing
move_anim = Animation("x", x + frame_size - 20, 3.0, easing)
move_anim.start(frame)
# Continue with remaining easings after a delay
def show_more_easings(runtime):
for j, easing in enumerate(EASING_FUNCTIONS[12:24]): # Next 12
row = j // frames_per_row + 2
col = j % frames_per_row
x = 50 + col * (frame_size + spacing)
y = 150 + row * (60 + spacing)
frame2 = Frame(x, y, 20, 20)
frame2.fill_color = Color(255, 150, 100)
frame2.outline = 1
ui.append(frame2)
label2 = Caption(easing, x, y - 20)
label2.fill_color = Color(200, 200, 200)
ui.append(label2)
move_anim2 = Animation("x", x + frame_size - 20, 3.0, easing)
move_anim2.start(frame2)
mcrfpy.setTimer("more_easings", show_more_easings, 1000)
# Show final easings
def show_final_easings(runtime):
for k, easing in enumerate(EASING_FUNCTIONS[24:]): # Last 6
row = k // frames_per_row + 4
col = k % frames_per_row
x = 50 + col * (frame_size + spacing)
y = 150 + row * (60 + spacing)
frame3 = Frame(x, y, 20, 20)
frame3.fill_color = Color(150, 255, 150)
frame3.outline = 1
ui.append(frame3)
label3 = Caption(easing, x, y - 20)
label3.fill_color = Color(200, 200, 200)
ui.append(label3)
move_anim3 = Animation("x", x + frame_size - 20, 3.0, easing)
move_anim3.start(frame3)
mcrfpy.setTimer("final_easings", show_final_easings, 2000)
def demo_delta_animations(ui):
"""Demo 8: Delta mode animations (relative movements)"""
subtitle.text = "Demo 8: Delta Mode Animations (Relative Movements)"
# Create objects that will move relative to their position
frames = []
start_positions = [(100, 200), (300, 200), (500, 200), (700, 200)]
colors = [Color(255, 100, 100), Color(100, 255, 100),
Color(100, 100, 255), Color(255, 255, 100)]
for i, (x, y) in enumerate(start_positions):
frame = Frame(x, y, 80, 80)
frame.fill_color = colors[i]
frame.outline = 2
ui.append(frame)
frames.append(frame)
# Delta animations - move relative to current position
# Each frame moves by different amounts
dx = (i + 1) * 50
dy = math.sin(i) * 100
x_delta = Animation("x", dx, 2.0, "easeInOutBack", delta=True)
y_delta = Animation("y", dy, 2.0, "easeInOutElastic", delta=True)
x_delta.start(frame)
y_delta.start(frame)
# Create caption showing delta mode
delta_label = Caption("Delta mode: Relative animations from current position", 200, 400)
delta_label.fill_color = Color(255, 255, 255)
ui.append(delta_label)
# Animate the label with delta mode text append
text_delta = Animation("text", " - ANIMATED!", 2.0, "linear", delta=True)
text_delta.start(delta_label)
return frames
def demo_color_component_animations(ui):
"""Demo 9: Individual color channel animations"""
subtitle.text = "Demo 9: Color Component Animations (R, G, B, A channels)"
# Create frames to demonstrate individual color channel animations
base_frame = Frame(300, 200, 600, 300)
base_frame.fill_color = Color(128, 128, 128, 255)
base_frame.outline = 3
ui.append(base_frame)
# Labels for each channel
labels = ["Red", "Green", "Blue", "Alpha"]
positions = [(50, 50), (200, 50), (350, 50), (500, 50)]
for i, (label_text, (x, y)) in enumerate(zip(labels, positions)):
# Create label
label = Caption(label_text, x, y - 30)
label.fill_color = Color(255, 255, 255)
base_frame.children.append(label)
# Create demo frame for this channel
demo_frame = Frame(x, y, 100, 100)
demo_frame.fill_color = Color(100, 100, 100, 200)
demo_frame.outline = 2
base_frame.children.append(demo_frame)
# Animate individual color channel
if i == 0: # Red
r_anim = Animation("fill_color.r", 255, 3.0, "easeInOutSine")
r_anim.start(demo_frame)
elif i == 1: # Green
g_anim = Animation("fill_color.g", 255, 3.0, "easeInOutSine")
g_anim.start(demo_frame)
elif i == 2: # Blue
b_anim = Animation("fill_color.b", 255, 3.0, "easeInOutSine")
b_anim.start(demo_frame)
else: # Alpha
a_anim = Animation("fill_color.a", 50, 3.0, "easeInOutSine")
a_anim.start(demo_frame)
# Animate main frame outline color components in sequence
outline_r = Animation("outline_color.r", 255, 1.0, "linear")
outline_g = Animation("outline_color.g", 255, 1.0, "linear")
outline_b = Animation("outline_color.b", 0, 1.0, "linear")
outline_r.start(base_frame)
mcrfpy.setTimer("outline_g", lambda t: outline_g.start(base_frame), 1000)
mcrfpy.setTimer("outline_b", lambda t: outline_b.start(base_frame), 2000)
return base_frame
def demo_performance_stress_test(ui):
"""Demo 10: Performance test with many simultaneous animations"""
subtitle.text = "Demo 10: Performance Stress Test (100+ Simultaneous Animations)"
# Create many small objects with different animations
num_objects = 100
for i in range(num_objects):
# Random starting position
x = 100 + (i % 20) * 50
y = 150 + (i // 20) * 50
# Create small frame
size = 20 + (i % 3) * 10
frame = Frame(x, y, size, size)
# Random color
r = (i * 37) % 256
g = (i * 73) % 256
b = (i * 113) % 256
frame.fill_color = Color(r, g, b, 200)
frame.outline = 1
ui.append(frame)
# Random animation properties
target_x = 100 + (i % 15) * 70
target_y = 150 + (i // 15) * 70
duration = 2.0 + (i % 30) * 0.1
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
# Start multiple animations per object
x_anim = Animation("x", target_x, duration, easing)
y_anim = Animation("y", target_y, duration, easing)
opacity_anim = Animation("opacity", 0.3 + (i % 7) * 0.1, duration, "easeInOutSine")
x_anim.start(frame)
y_anim.start(frame)
opacity_anim.start(frame)
# Performance counter
perf_caption = Caption(f"Animating {num_objects * 3} properties simultaneously", 400, 600)
perf_caption.fill_color = Color(255, 255, 0)
ui.append(perf_caption)
def next_demo(runtime):
"""Cycle to the next demo"""
global current_demo, demo_start_time
# Clear the UI except title and subtitle
ui = mcrfpy.sceneUI("sizzle_reel")
# Keep only the first two elements (title and subtitle)
while len(ui) > 2:
# Remove from the end to avoid index issues
ui.remove(len(ui) - 1)
# Run the next demo
if current_demo < len(demos):
demos[current_demo](ui)
current_demo += 1
# Schedule next demo
if current_demo < len(demos):
mcrfpy.setTimer("next_demo", next_demo, int(DEMO_DURATION * 1000))
else:
# All demos complete
subtitle.text = "Animation Showcase Complete! Press ESC to exit."
complete = Caption("All animation types demonstrated!", 400, 350)
complete.fill_color = Color(0, 255, 0)
complete.outline = 2
ui.append(complete)
def run_sizzle_reel(runtime):
"""Main entry point - start the demo sequence"""
global demos
# List of all demo functions
demos = [
demo_frame_basic_animations,
demo_frame_opacity_zindex,
demo_caption_animations,
demo_sprite_animations,
demo_grid_animations,
demo_complex_combinations,
demo_easing_showcase,
demo_delta_animations,
demo_color_component_animations,
demo_performance_stress_test
]
# Start the first demo
next_demo(runtime)
# Initialize scene
ui = create_demo_scene()
# Start the sizzle reel after a short delay
mcrfpy.setTimer("start_sizzle", run_sizzle_reel, 500)
print("Starting McRogueFace Animation Sizzle Reel...")
print("This will demonstrate ALL animation types on ALL objects.")
print("Press ESC at any time to exit.")

View File

@ -0,0 +1,227 @@
#!/usr/bin/env python3
"""
McRogueFace Animation Sizzle Reel (Fixed)
=========================================
This script demonstrates EVERY animation type on EVERY UI object type.
Fixed version that works properly with the game loop.
"""
import mcrfpy
# Configuration
SCENE_WIDTH = 1280
SCENE_HEIGHT = 720
DEMO_DURATION = 5.0 # Duration for each demo section
# All available easing functions
EASING_FUNCTIONS = [
"linear", "easeIn", "easeOut", "easeInOut",
"easeInQuad", "easeOutQuad", "easeInOutQuad",
"easeInCubic", "easeOutCubic", "easeInOutCubic",
"easeInQuart", "easeOutQuart", "easeInOutQuart",
"easeInSine", "easeOutSine", "easeInOutSine",
"easeInExpo", "easeOutExpo", "easeInOutExpo",
"easeInCirc", "easeOutCirc", "easeInOutCirc",
"easeInElastic", "easeOutElastic", "easeInOutElastic",
"easeInBack", "easeOutBack", "easeInOutBack",
"easeInBounce", "easeOutBounce", "easeInOutBounce"
]
# Track current demo state
current_demo = 0
subtitle = None
def create_demo_scene():
"""Create the main demo scene with title"""
mcrfpy.createScene("sizzle_reel")
mcrfpy.setScene("sizzle_reel")
ui = mcrfpy.sceneUI("sizzle_reel")
# Title caption
title = mcrfpy.Caption("McRogueFace Animation Sizzle Reel",
SCENE_WIDTH/2 - 200, 20)
title.fill_color = mcrfpy.Color(255, 255, 0)
title.outline = 2
title.outline_color = mcrfpy.Color(0, 0, 0)
ui.append(title)
# Subtitle showing current demo
global subtitle
subtitle = mcrfpy.Caption("Initializing...",
SCENE_WIDTH/2 - 150, 60)
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(subtitle)
return ui
def demo_frame_basic_animations():
"""Demo 1: Basic frame animations - position, size, colors"""
ui = mcrfpy.sceneUI("sizzle_reel")
subtitle.text = "Demo 1: Frame Basic Animations (Position, Size, Colors)"
# Create test frame
frame = mcrfpy.Frame(100, 150, 200, 100)
frame.fill_color = mcrfpy.Color(50, 50, 150)
frame.outline = 3
frame.outline_color = mcrfpy.Color(255, 255, 255)
ui.append(frame)
# Position animations with different easings
x_anim = mcrfpy.Animation("x", 800.0, 2.0, "easeInOutBack")
y_anim = mcrfpy.Animation("y", 400.0, 2.0, "easeInOutElastic")
x_anim.start(frame)
y_anim.start(frame)
# Size animations
w_anim = mcrfpy.Animation("w", 400.0, 3.0, "easeInOutCubic")
h_anim = mcrfpy.Animation("h", 200.0, 3.0, "easeInOutCubic")
w_anim.start(frame)
h_anim.start(frame)
# Color animations
fill_anim = mcrfpy.Animation("fill_color", mcrfpy.Color(255, 100, 50, 200), 4.0, "easeInOutSine")
outline_anim = mcrfpy.Animation("outline_color", mcrfpy.Color(0, 255, 255), 4.0, "easeOutBounce")
fill_anim.start(frame)
outline_anim.start(frame)
# Outline thickness animation
thickness_anim = mcrfpy.Animation("outline", 10.0, 4.5, "easeInOutQuad")
thickness_anim.start(frame)
def demo_caption_animations():
"""Demo 2: Caption text animations and effects"""
ui = mcrfpy.sceneUI("sizzle_reel")
subtitle.text = "Demo 2: Caption Animations (Text, Color, Position)"
# Basic caption with position animation
caption1 = mcrfpy.Caption("Moving Text!", 100, 200)
caption1.fill_color = mcrfpy.Color(255, 255, 255)
caption1.outline = 1
ui.append(caption1)
# Animate across screen with bounce
x_anim = mcrfpy.Animation("x", 900.0, 3.0, "easeOutBounce")
x_anim.start(caption1)
# Color cycling caption
caption2 = mcrfpy.Caption("Rainbow Colors", 400, 300)
caption2.outline = 2
ui.append(caption2)
# Cycle through colors
color_anim1 = mcrfpy.Animation("fill_color", mcrfpy.Color(255, 0, 0), 1.0, "linear")
color_anim1.start(caption2)
# Typewriter effect caption
caption3 = mcrfpy.Caption("", 100, 400)
caption3.fill_color = mcrfpy.Color(0, 255, 255)
ui.append(caption3)
typewriter = mcrfpy.Animation("text", "This text appears one character at a time...", 3.0, "linear")
typewriter.start(caption3)
def demo_sprite_animations():
"""Demo 3: Sprite animations (if texture available)"""
ui = mcrfpy.sceneUI("sizzle_reel")
subtitle.text = "Demo 3: Sprite Animations"
# Create placeholder caption since texture might not exist
no_texture = mcrfpy.Caption("(Sprite demo - textures may not be loaded)", 400, 350)
no_texture.fill_color = mcrfpy.Color(255, 100, 100)
ui.append(no_texture)
def demo_performance_stress_test():
"""Demo 4: Performance test with many simultaneous animations"""
ui = mcrfpy.sceneUI("sizzle_reel")
subtitle.text = "Demo 4: Performance Test (50+ Simultaneous Animations)"
# Create many small objects with different animations
num_objects = 50
for i in range(num_objects):
# Random starting position
x = 100 + (i % 10) * 100
y = 150 + (i // 10) * 80
# Create small frame
size = 20 + (i % 3) * 10
frame = mcrfpy.Frame(x, y, size, size)
# Random color
r = (i * 37) % 256
g = (i * 73) % 256
b = (i * 113) % 256
frame.fill_color = mcrfpy.Color(r, g, b, 200)
frame.outline = 1
ui.append(frame)
# Random animation properties
target_x = 100 + (i % 8) * 120
target_y = 150 + (i // 8) * 100
duration = 2.0 + (i % 30) * 0.1
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
# Start multiple animations per object
x_anim = mcrfpy.Animation("x", float(target_x), duration, easing)
y_anim = mcrfpy.Animation("y", float(target_y), duration, easing)
opacity_anim = mcrfpy.Animation("opacity", 0.3 + (i % 7) * 0.1, duration, "easeInOutSine")
x_anim.start(frame)
y_anim.start(frame)
opacity_anim.start(frame)
# Performance counter
perf_caption = mcrfpy.Caption(f"Animating {num_objects * 3} properties simultaneously", 400, 600)
perf_caption.fill_color = mcrfpy.Color(255, 255, 0)
ui.append(perf_caption)
def clear_scene():
"""Clear the scene except title and subtitle"""
ui = mcrfpy.sceneUI("sizzle_reel")
# Keep only the first two elements (title and subtitle)
while len(ui) > 2:
ui.remove(ui[2])
def run_demo_sequence(runtime):
"""Run through all demos"""
global current_demo
# Clear previous demo
clear_scene()
# Demo list
demos = [
demo_frame_basic_animations,
demo_caption_animations,
demo_sprite_animations,
demo_performance_stress_test
]
if current_demo < len(demos):
# Run current demo
demos[current_demo]()
current_demo += 1
# Schedule next demo
if current_demo < len(demos):
mcrfpy.setTimer("next_demo", run_demo_sequence, int(DEMO_DURATION * 1000))
else:
# All demos complete
subtitle.text = "Animation Showcase Complete!"
complete = mcrfpy.Caption("All animation types demonstrated!", 400, 350)
complete.fill_color = mcrfpy.Color(0, 255, 0)
complete.outline = 2
ui = mcrfpy.sceneUI("sizzle_reel")
ui.append(complete)
# Initialize scene
print("Starting McRogueFace Animation Sizzle Reel...")
print("This will demonstrate animation types on various objects.")
ui = create_demo_scene()
# Start the demo sequence after a short delay
mcrfpy.setTimer("start_demos", run_demo_sequence, 500)

View File

@ -0,0 +1,307 @@
#!/usr/bin/env python3
"""
McRogueFace Animation Sizzle Reel v2
====================================
Fixed version with proper API usage for animations and collections.
"""
import mcrfpy
# Configuration
SCENE_WIDTH = 1280
SCENE_HEIGHT = 720
DEMO_DURATION = 5.0 # Duration for each demo section
# All available easing functions
EASING_FUNCTIONS = [
"linear", "easeIn", "easeOut", "easeInOut",
"easeInQuad", "easeOutQuad", "easeInOutQuad",
"easeInCubic", "easeOutCubic", "easeInOutCubic",
"easeInQuart", "easeOutQuart", "easeInOutQuart",
"easeInSine", "easeOutSine", "easeInOutSine",
"easeInExpo", "easeOutExpo", "easeInOutExpo",
"easeInCirc", "easeOutCirc", "easeInOutCirc",
"easeInElastic", "easeOutElastic", "easeInOutElastic",
"easeInBack", "easeOutBack", "easeInOutBack",
"easeInBounce", "easeOutBounce", "easeInOutBounce"
]
# Track current demo state
current_demo = 0
subtitle = None
demo_objects = [] # Track objects from current demo
def create_demo_scene():
"""Create the main demo scene with title"""
mcrfpy.createScene("sizzle_reel")
mcrfpy.setScene("sizzle_reel")
ui = mcrfpy.sceneUI("sizzle_reel")
# Title caption
title = mcrfpy.Caption("McRogueFace Animation Sizzle Reel",
SCENE_WIDTH/2 - 200, 20)
title.fill_color = mcrfpy.Color(255, 255, 0)
title.outline = 2
title.outline_color = mcrfpy.Color(0, 0, 0)
ui.append(title)
# Subtitle showing current demo
global subtitle
subtitle = mcrfpy.Caption("Initializing...",
SCENE_WIDTH/2 - 150, 60)
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(subtitle)
return ui
def demo_frame_basic_animations():
"""Demo 1: Basic frame animations - position, size, colors"""
global demo_objects
demo_objects = []
ui = mcrfpy.sceneUI("sizzle_reel")
subtitle.text = "Demo 1: Frame Basic Animations (Position, Size, Colors)"
# Create test frame
frame = mcrfpy.Frame(100, 150, 200, 100)
frame.fill_color = mcrfpy.Color(50, 50, 150)
frame.outline = 3
frame.outline_color = mcrfpy.Color(255, 255, 255)
ui.append(frame)
demo_objects.append(frame)
# Position animations with different easings
x_anim = mcrfpy.Animation("x", 800.0, 2.0, "easeInOutBack")
y_anim = mcrfpy.Animation("y", 400.0, 2.0, "easeInOutElastic")
x_anim.start(frame)
y_anim.start(frame)
# Size animations
w_anim = mcrfpy.Animation("w", 400.0, 3.0, "easeInOutCubic")
h_anim = mcrfpy.Animation("h", 200.0, 3.0, "easeInOutCubic")
w_anim.start(frame)
h_anim.start(frame)
# Color animations - use tuples instead of Color objects
fill_anim = mcrfpy.Animation("fill_color", (255, 100, 50, 200), 4.0, "easeInOutSine")
outline_anim = mcrfpy.Animation("outline_color", (0, 255, 255, 255), 4.0, "easeOutBounce")
fill_anim.start(frame)
outline_anim.start(frame)
# Outline thickness animation
thickness_anim = mcrfpy.Animation("outline", 10.0, 4.5, "easeInOutQuad")
thickness_anim.start(frame)
def demo_caption_animations():
"""Demo 2: Caption text animations and effects"""
global demo_objects
demo_objects = []
ui = mcrfpy.sceneUI("sizzle_reel")
subtitle.text = "Demo 2: Caption Animations (Text, Color, Position)"
# Basic caption with position animation
caption1 = mcrfpy.Caption("Moving Text!", 100, 200)
caption1.fill_color = mcrfpy.Color(255, 255, 255)
caption1.outline = 1
ui.append(caption1)
demo_objects.append(caption1)
# Animate across screen with bounce
x_anim = mcrfpy.Animation("x", 900.0, 3.0, "easeOutBounce")
x_anim.start(caption1)
# Color cycling caption
caption2 = mcrfpy.Caption("Rainbow Colors", 400, 300)
caption2.outline = 2
ui.append(caption2)
demo_objects.append(caption2)
# Cycle through colors using tuples
color_anim1 = mcrfpy.Animation("fill_color", (255, 0, 0, 255), 1.0, "linear")
color_anim1.start(caption2)
# Schedule color changes
def change_to_green(rt):
color_anim2 = mcrfpy.Animation("fill_color", (0, 255, 0, 255), 1.0, "linear")
color_anim2.start(caption2)
def change_to_blue(rt):
color_anim3 = mcrfpy.Animation("fill_color", (0, 0, 255, 255), 1.0, "linear")
color_anim3.start(caption2)
def change_to_white(rt):
color_anim4 = mcrfpy.Animation("fill_color", (255, 255, 255, 255), 1.0, "linear")
color_anim4.start(caption2)
mcrfpy.setTimer("color2", change_to_green, 1000)
mcrfpy.setTimer("color3", change_to_blue, 2000)
mcrfpy.setTimer("color4", change_to_white, 3000)
# Typewriter effect caption
caption3 = mcrfpy.Caption("", 100, 400)
caption3.fill_color = mcrfpy.Color(0, 255, 255)
ui.append(caption3)
demo_objects.append(caption3)
typewriter = mcrfpy.Animation("text", "This text appears one character at a time...", 3.0, "linear")
typewriter.start(caption3)
def demo_easing_showcase():
"""Demo 3: Showcase different easing functions"""
global demo_objects
demo_objects = []
ui = mcrfpy.sceneUI("sizzle_reel")
subtitle.text = "Demo 3: Easing Functions Showcase"
# Create small frames for each easing function
frames_per_row = 6
frame_width = 180
spacing = 10
# Show first 12 easings
for i, easing in enumerate(EASING_FUNCTIONS[:12]):
row = i // frames_per_row
col = i % frames_per_row
x = 50 + col * (frame_width + spacing)
y = 150 + row * (80 + spacing)
# Create indicator frame
frame = mcrfpy.Frame(x, y, 20, 20)
frame.fill_color = mcrfpy.Color(100, 200, 255)
frame.outline = 1
ui.append(frame)
demo_objects.append(frame)
# Label
label = mcrfpy.Caption(easing[:8], x, y - 20) # Truncate long names
label.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(label)
demo_objects.append(label)
# Animate using this easing
move_anim = mcrfpy.Animation("x", float(x + frame_width - 20), 3.0, easing)
move_anim.start(frame)
def demo_performance_stress_test():
"""Demo 4: Performance test with many simultaneous animations"""
global demo_objects
demo_objects = []
ui = mcrfpy.sceneUI("sizzle_reel")
subtitle.text = "Demo 4: Performance Test (50+ Simultaneous Animations)"
# Create many small objects with different animations
num_objects = 50
for i in range(num_objects):
# Starting position
x = 100 + (i % 10) * 100
y = 150 + (i // 10) * 80
# Create small frame
size = 20 + (i % 3) * 10
frame = mcrfpy.Frame(x, y, size, size)
# Random color
r = (i * 37) % 256
g = (i * 73) % 256
b = (i * 113) % 256
frame.fill_color = mcrfpy.Color(r, g, b, 200)
frame.outline = 1
ui.append(frame)
demo_objects.append(frame)
# Random animation properties
target_x = 100 + (i % 8) * 120
target_y = 150 + (i // 8) * 100
duration = 2.0 + (i % 30) * 0.1
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
# Start multiple animations per object
x_anim = mcrfpy.Animation("x", float(target_x), duration, easing)
y_anim = mcrfpy.Animation("y", float(target_y), duration, easing)
opacity_anim = mcrfpy.Animation("opacity", 0.3 + (i % 7) * 0.1, duration, "easeInOutSine")
x_anim.start(frame)
y_anim.start(frame)
opacity_anim.start(frame)
# Performance counter
perf_caption = mcrfpy.Caption(f"Animating {num_objects * 3} properties simultaneously", 350, 600)
perf_caption.fill_color = mcrfpy.Color(255, 255, 0)
ui.append(perf_caption)
demo_objects.append(perf_caption)
def clear_scene():
"""Clear the scene except title and subtitle"""
global demo_objects
ui = mcrfpy.sceneUI("sizzle_reel")
# Remove all demo objects
for obj in demo_objects:
try:
# Find index of object
for i in range(len(ui)):
if ui[i] is obj:
ui.remove(ui[i])
break
except:
pass # Object might already be removed
demo_objects = []
# Clean up any timers
for timer_name in ["color2", "color3", "color4"]:
try:
mcrfpy.delTimer(timer_name)
except:
pass
def run_demo_sequence(runtime):
"""Run through all demos"""
global current_demo
# Clear previous demo
clear_scene()
# Demo list
demos = [
demo_frame_basic_animations,
demo_caption_animations,
demo_easing_showcase,
demo_performance_stress_test
]
if current_demo < len(demos):
# Run current demo
demos[current_demo]()
current_demo += 1
# Schedule next demo
if current_demo < len(demos):
mcrfpy.setTimer("next_demo", run_demo_sequence, int(DEMO_DURATION * 1000))
else:
# Final demo completed
def show_complete(rt):
subtitle.text = "Animation Showcase Complete!"
complete = mcrfpy.Caption("All animation types demonstrated!", 400, 350)
complete.fill_color = mcrfpy.Color(0, 255, 0)
complete.outline = 2
ui = mcrfpy.sceneUI("sizzle_reel")
ui.append(complete)
mcrfpy.setTimer("complete", show_complete, 3000)
# Initialize scene
print("Starting McRogueFace Animation Sizzle Reel v2...")
print("This will demonstrate animation types on various objects.")
ui = create_demo_scene()
# Start the demo sequence after a short delay
mcrfpy.setTimer("start_demos", run_demo_sequence, 500)

View File

@ -0,0 +1,318 @@
#!/usr/bin/env python3
"""
McRogueFace Animation Sizzle Reel - Working Version
===================================================
Complete demonstration of all animation capabilities.
Fixed to work properly with the API.
"""
import mcrfpy
import sys
import math
# Configuration
DEMO_DURATION = 7.0 # Duration for each demo
# All available easing functions
EASING_FUNCTIONS = [
"linear", "easeIn", "easeOut", "easeInOut",
"easeInQuad", "easeOutQuad", "easeInOutQuad",
"easeInCubic", "easeOutCubic", "easeInOutCubic",
"easeInQuart", "easeOutQuart", "easeInOutQuart",
"easeInSine", "easeOutSine", "easeInOutSine",
"easeInExpo", "easeOutExpo", "easeInOutExpo",
"easeInCirc", "easeOutCirc", "easeInOutCirc",
"easeInElastic", "easeOutElastic", "easeInOutElastic",
"easeInBack", "easeOutBack", "easeInOutBack",
"easeInBounce", "easeOutBounce", "easeInOutBounce"
]
# Track state
current_demo = 0
subtitle = None
demo_objects = []
def create_scene():
"""Create the demo scene with title"""
mcrfpy.createScene("sizzle")
mcrfpy.setScene("sizzle")
ui = mcrfpy.sceneUI("sizzle")
# Title
title = mcrfpy.Caption("McRogueFace Animation Sizzle Reel", 340, 20)
title.fill_color = mcrfpy.Color(255, 255, 0)
title.outline = 2
title.outline_color = mcrfpy.Color(0, 0, 0)
ui.append(title)
# Subtitle
global subtitle
subtitle = mcrfpy.Caption("Initializing...", 400, 60)
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(subtitle)
def clear_demo():
"""Clear demo objects"""
global demo_objects
ui = mcrfpy.sceneUI("sizzle")
# Remove items starting from the end
# Skip first 2 (title and subtitle)
while len(ui) > 2:
ui.remove(len(ui) - 1)
demo_objects = []
def demo1_frame_basics():
"""Demo 1: Basic frame animations"""
clear_demo()
print("demo1")
subtitle.text = "Demo 1: Frame Animations (Position, Size, Color)"
ui = mcrfpy.sceneUI("sizzle")
# Create frame
frame = mcrfpy.Frame(100, 150, 200, 100)
frame.fill_color = mcrfpy.Color(50, 50, 150)
frame.outline = 3
frame.outline_color = mcrfpy.Color(255, 255, 255)
ui.append(frame)
# Animate properties
mcrfpy.Animation("x", 700.0, 2.5, "easeInOutBack").start(frame)
mcrfpy.Animation("y", 350.0, 2.5, "easeInOutElastic").start(frame)
mcrfpy.Animation("w", 350.0, 3.0, "easeInOutCubic").start(frame)
mcrfpy.Animation("h", 180.0, 3.0, "easeInOutCubic").start(frame)
mcrfpy.Animation("fill_color", (255, 100, 50, 200), 4.0, "easeInOutSine").start(frame)
mcrfpy.Animation("outline_color", (0, 255, 255, 255), 4.0, "easeOutBounce").start(frame)
mcrfpy.Animation("outline", 8.0, 4.0, "easeInOutQuad").start(frame)
def demo2_opacity_zindex():
"""Demo 2: Opacity and z-index animations"""
clear_demo()
print("demo2")
subtitle.text = "Demo 2: Opacity & Z-Index Animations"
ui = mcrfpy.sceneUI("sizzle")
# Create overlapping frames
colors = [(255, 0, 0), (0, 255, 0), (0, 0, 255), (255, 255, 0)]
for i in range(4):
frame = mcrfpy.Frame(200 + i*80, 200 + i*40, 200, 150)
frame.fill_color = mcrfpy.Color(colors[i][0], colors[i][1], colors[i][2], 200)
frame.outline = 2
frame.z_index = i
ui.append(frame)
# Animate opacity
mcrfpy.Animation("opacity", 0.3, 2.0, "easeInOutSine").start(frame)
# Schedule opacity return
def return_opacity(rt):
for i in range(4):
mcrfpy.Animation("opacity", 1.0, 2.0, "easeInOutSine").start(ui[i])
mcrfpy.setTimer(f"opacity_{i}", return_opacity, 2100)
def demo3_captions():
"""Demo 3: Caption animations"""
clear_demo()
print("demo3")
subtitle.text = "Demo 3: Caption Animations"
ui = mcrfpy.sceneUI("sizzle")
# Moving caption
c1 = mcrfpy.Caption("Bouncing Text!", 100, 200)
c1.fill_color = mcrfpy.Color(255, 255, 255)
c1.outline = 1
ui.append(c1)
mcrfpy.Animation("x", 800.0, 3.0, "easeOutBounce").start(c1)
# Color cycling caption
c2 = mcrfpy.Caption("Color Cycle", 400, 300)
c2.outline = 2
ui.append(c2)
# Animate through colors
def cycle_colors():
anim = mcrfpy.Animation("fill_color", (255, 0, 0, 255), 0.5, "linear")
anim.start(c2)
def to_green(rt):
mcrfpy.Animation("fill_color", (0, 255, 0, 255), 0.5, "linear").start(c2)
def to_blue(rt):
mcrfpy.Animation("fill_color", (0, 0, 255, 255), 0.5, "linear").start(c2)
def to_white(rt):
mcrfpy.Animation("fill_color", (255, 255, 255, 255), 0.5, "linear").start(c2)
mcrfpy.setTimer("c_green", to_green, 600)
mcrfpy.setTimer("c_blue", to_blue, 1200)
mcrfpy.setTimer("c_white", to_white, 1800)
cycle_colors()
# Typewriter effect
c3 = mcrfpy.Caption("", 100, 400)
c3.fill_color = mcrfpy.Color(0, 255, 255)
ui.append(c3)
mcrfpy.Animation("text", "This text appears one character at a time...", 3.0, "linear").start(c3)
def demo4_easing_showcase():
"""Demo 4: Showcase easing functions"""
clear_demo()
print("demo4")
subtitle.text = "Demo 4: 30 Easing Functions"
ui = mcrfpy.sceneUI("sizzle")
# Show first 15 easings
for i in range(15):
row = i // 5
col = i % 5
x = 80 + col * 180
y = 150 + row * 120
# Create frame
f = mcrfpy.Frame(x, y, 20, 20)
f.fill_color = mcrfpy.Color(100, 150, 255)
f.outline = 1
ui.append(f)
# Label
label = mcrfpy.Caption(EASING_FUNCTIONS[i][:10], x, y - 20)
label.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(label)
# Animate with this easing
mcrfpy.Animation("x", float(x + 140), 3.0, EASING_FUNCTIONS[i]).start(f)
def demo5_performance():
"""Demo 5: Many simultaneous animations"""
clear_demo()
print("demo5")
subtitle.text = "Demo 5: 50+ Simultaneous Animations"
ui = mcrfpy.sceneUI("sizzle")
# Create many animated objects
for i in range(50):
print(f"{i}...",end='',flush=True)
x = 100 + (i % 10) * 90
y = 120 + (i // 10) * 80
f = mcrfpy.Frame(x, y, 25, 25)
r = (i * 37) % 256
g = (i * 73) % 256
b = (i * 113) % 256
f.fill_color = (r, g, b, 200) #mcrfpy.Color(r, g, b, 200)
f.outline = 1
ui.append(f)
# Random animations
target_x = 150 + (i % 8) * 100
target_y = 150 + (i // 8) * 85
duration = 2.0 + (i % 30) * 0.1
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
mcrfpy.Animation("x", float(target_x), duration, easing).start(f)
mcrfpy.Animation("y", float(target_y), duration, easing).start(f)
mcrfpy.Animation("opacity", 0.3 + (i % 7) * 0.1, 2.5, "easeInOutSine").start(f)
def demo6_delta_mode():
"""Demo 6: Delta mode animations"""
clear_demo()
print("demo6")
subtitle.text = "Demo 6: Delta Mode (Relative Movement)"
ui = mcrfpy.sceneUI("sizzle")
# Create frames that move relative to position
positions = [(100, 300), (300, 300), (500, 300), (700, 300)]
colors = [(255, 100, 100), (100, 255, 100), (100, 100, 255), (255, 255, 100)]
for i, ((x, y), color) in enumerate(zip(positions, colors)):
f = mcrfpy.Frame(x, y, 60, 60)
f.fill_color = mcrfpy.Color(color[0], color[1], color[2])
f.outline = 2
ui.append(f)
# Delta animations - move by amount, not to position
dx = (i + 1) * 30
dy = math.sin(i * 0.5) * 50
mcrfpy.Animation("x", float(dx), 2.0, "easeInOutBack", delta=True).start(f)
mcrfpy.Animation("y", float(dy), 2.0, "easeInOutElastic", delta=True).start(f)
# Caption explaining delta mode
info = mcrfpy.Caption("Delta mode: animations move BY amount, not TO position", 200, 450)
info.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(info)
def run_next_demo(runtime):
"""Run the next demo in sequence"""
global current_demo
demos = [
demo1_frame_basics,
demo2_opacity_zindex,
demo3_captions,
demo4_easing_showcase,
demo5_performance,
demo6_delta_mode
]
if current_demo < len(demos):
# Clean up timers from previous demo
for timer in ["opacity_0", "opacity_1", "opacity_2", "opacity_3",
"c_green", "c_blue", "c_white"]:
if not mcrfpy.getTimer(timer):
continue
try:
mcrfpy.delTimer(timer)
except:
pass
# Run next demo
print(f"Run next: {current_demo}")
demos[current_demo]()
current_demo += 1
# Schedule next demo
if current_demo < len(demos):
#mcrfpy.setTimer("next_demo", run_next_demo, int(DEMO_DURATION * 1000))
pass
else:
current_demo = 0
# All done
#subtitle.text = "Animation Showcase Complete!"
#complete = mcrfpy.Caption("All animations demonstrated successfully!", 350, 350)
#complete.fill_color = mcrfpy.Color(0, 255, 0)
#complete.outline = 2
#ui = mcrfpy.sceneUI("sizzle")
#ui.append(complete)
#
## Exit after delay
#def exit_program(rt):
# print("\nSizzle reel completed successfully!")
# sys.exit(0)
#mcrfpy.setTimer("exit", exit_program, 3000)
# Handle ESC key
def handle_keypress(scene_name, keycode):
if keycode == 256: # ESC
print("\nExiting...")
sys.exit(0)
# Initialize
print("Starting McRogueFace Animation Sizzle Reel...")
print("This demonstrates all animation capabilities.")
print("Press ESC to exit at any time.")
create_scene()
mcrfpy.keypressScene(handle_keypress)
# Start the show
mcrfpy.setTimer("start", run_next_demo, int(DEMO_DURATION * 1000))

View File

@ -0,0 +1,207 @@
#!/usr/bin/env python3
"""
McRogueFace API Demo - Final Version
====================================
Complete API demonstration with proper error handling.
Tests all constructors and methods systematically.
"""
import mcrfpy
import sys
def print_section(title):
"""Print a section header"""
print("\n" + "="*60)
print(f" {title}")
print("="*60)
def print_test(name, success=True):
"""Print test result"""
status = "" if success else ""
print(f" {status} {name}")
def test_colors():
"""Test Color API"""
print_section("COLOR TESTS")
try:
# Basic constructors
c1 = mcrfpy.Color(255, 0, 0) # RGB
print_test(f"Color(255,0,0) = ({c1.r},{c1.g},{c1.b},{c1.a})")
c2 = mcrfpy.Color(100, 150, 200, 128) # RGBA
print_test(f"Color(100,150,200,128) = ({c2.r},{c2.g},{c2.b},{c2.a})")
# Property modification
c1.r = 128
c1.g = 128
c1.b = 128
c1.a = 200
print_test(f"Modified color = ({c1.r},{c1.g},{c1.b},{c1.a})")
except Exception as e:
print_test(f"Color test failed: {e}", False)
def test_frames():
"""Test Frame API"""
print_section("FRAME TESTS")
# Create scene
mcrfpy.createScene("test")
mcrfpy.setScene("test")
ui = mcrfpy.sceneUI("test")
try:
# Constructors
f1 = mcrfpy.Frame()
print_test(f"Frame() at ({f1.x},{f1.y}) size ({f1.w},{f1.h})")
f2 = mcrfpy.Frame(100, 50)
print_test(f"Frame(100,50) at ({f2.x},{f2.y})")
f3 = mcrfpy.Frame(200, 100, 150, 75)
print_test(f"Frame(200,100,150,75) size ({f3.w},{f3.h})")
# Properties
f3.fill_color = mcrfpy.Color(100, 100, 200)
f3.outline = 3
f3.outline_color = mcrfpy.Color(255, 255, 0)
f3.opacity = 0.8
f3.visible = True
f3.z_index = 5
print_test(f"Frame properties set")
# Add to scene
ui.append(f3)
print_test(f"Frame added to scene")
# Children
child = mcrfpy.Frame(10, 10, 50, 50)
f3.children.append(child)
print_test(f"Child added, count = {len(f3.children)}")
except Exception as e:
print_test(f"Frame test failed: {e}", False)
def test_captions():
"""Test Caption API"""
print_section("CAPTION TESTS")
ui = mcrfpy.sceneUI("test")
try:
# Constructors
c1 = mcrfpy.Caption()
print_test(f"Caption() text='{c1.text}'")
c2 = mcrfpy.Caption("Hello World")
print_test(f"Caption('Hello World') at ({c2.x},{c2.y})")
c3 = mcrfpy.Caption("Test", 300, 200)
print_test(f"Caption with position at ({c3.x},{c3.y})")
# Properties
c3.text = "Modified"
c3.fill_color = mcrfpy.Color(255, 255, 0)
c3.outline = 2
c3.outline_color = mcrfpy.Color(0, 0, 0)
print_test(f"Caption text='{c3.text}'")
ui.append(c3)
print_test("Caption added to scene")
except Exception as e:
print_test(f"Caption test failed: {e}", False)
def test_animations():
"""Test Animation API"""
print_section("ANIMATION TESTS")
ui = mcrfpy.sceneUI("test")
try:
# Create target
frame = mcrfpy.Frame(50, 50, 100, 100)
frame.fill_color = mcrfpy.Color(100, 100, 100)
ui.append(frame)
# Basic animations
a1 = mcrfpy.Animation("x", 300.0, 2.0)
print_test("Animation created (position)")
a2 = mcrfpy.Animation("opacity", 0.5, 1.5, "easeInOut")
print_test("Animation with easing")
a3 = mcrfpy.Animation("fill_color", (255, 0, 0, 255), 2.0)
print_test("Color animation (tuple)")
# Start animations
a1.start(frame)
a2.start(frame)
a3.start(frame)
print_test("Animations started")
# Check properties
print_test(f"Duration = {a1.duration}")
print_test(f"Elapsed = {a1.elapsed}")
print_test(f"Complete = {a1.is_complete}")
except Exception as e:
print_test(f"Animation test failed: {e}", False)
def test_collections():
"""Test collection operations"""
print_section("COLLECTION TESTS")
ui = mcrfpy.sceneUI("test")
try:
# Clear scene
while len(ui) > 0:
ui.remove(ui[len(ui)-1])
print_test(f"Scene cleared, length = {len(ui)}")
# Add items
for i in range(5):
f = mcrfpy.Frame(i*100, 50, 80, 80)
ui.append(f)
print_test(f"Added 5 frames, length = {len(ui)}")
# Access
first = ui[0]
print_test(f"Accessed ui[0] at ({first.x},{first.y})")
# Iteration
count = sum(1 for _ in ui)
print_test(f"Iteration count = {count}")
except Exception as e:
print_test(f"Collection test failed: {e}", False)
def run_tests():
"""Run all tests"""
print("\n" + "="*60)
print(" McRogueFace API Test Suite")
print("="*60)
test_colors()
test_frames()
test_captions()
test_animations()
test_collections()
print("\n" + "="*60)
print(" Tests Complete")
print("="*60)
# Exit after delay
def exit_program(runtime):
print("\nExiting...")
sys.exit(0)
mcrfpy.setTimer("exit", exit_program, 3000)
# Run tests
print("Starting API tests...")
run_tests()

View File

@ -0,0 +1,99 @@
#!/usr/bin/env python3
"""Debug the astar_vs_dijkstra demo issue"""
import mcrfpy
import sys
# Same setup as the demo
start_pos = (5, 10)
end_pos = (25, 10)
print("Debugging A* vs Dijkstra demo...")
print(f"Start: {start_pos}, End: {end_pos}")
# Create scene and grid
mcrfpy.createScene("debug")
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
# Initialize all as floor
print("\nInitializing 30x20 grid...")
for y in range(20):
for x in range(30):
grid.at(x, y).walkable = True
# Test path before obstacles
print("\nTest 1: Path with no obstacles")
path1 = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
print(f" Path: {path1[:5]}...{path1[-3:] if len(path1) > 5 else ''}")
print(f" Length: {len(path1)}")
# Add obstacles from the demo
obstacles = [
# Vertical wall with gaps
[(15, y) for y in range(3, 17) if y not in [8, 12]],
# Horizontal walls
[(x, 5) for x in range(10, 20)],
[(x, 15) for x in range(10, 20)],
# Maze-like structure
[(x, 10) for x in range(20, 25)],
[(25, y) for y in range(5, 15)],
]
print("\nAdding obstacles...")
wall_count = 0
for obstacle_group in obstacles:
for x, y in obstacle_group:
grid.at(x, y).walkable = False
wall_count += 1
if wall_count <= 5:
print(f" Wall at ({x}, {y})")
print(f" Total walls added: {wall_count}")
# Check specific cells
print(f"\nChecking key positions:")
print(f" Start ({start_pos[0]}, {start_pos[1]}): walkable={grid.at(start_pos[0], start_pos[1]).walkable}")
print(f" End ({end_pos[0]}, {end_pos[1]}): walkable={grid.at(end_pos[0], end_pos[1]).walkable}")
# Check if path is blocked
print(f"\nChecking horizontal line at y=10:")
blocked_x = []
for x in range(30):
if not grid.at(x, 10).walkable:
blocked_x.append(x)
print(f" Blocked x positions: {blocked_x}")
# Test path with obstacles
print("\nTest 2: Path with obstacles")
path2 = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
print(f" Path: {path2}")
print(f" Length: {len(path2)}")
# Check if there's any path at all
if not path2:
print("\n No path found! Checking why...")
# Check if we can reach the vertical wall gap
print("\n Testing path to wall gap at (15, 8):")
path_to_gap = grid.compute_astar_path(start_pos[0], start_pos[1], 15, 8)
print(f" Path to gap: {path_to_gap}")
# Check from gap to end
print("\n Testing path from gap (15, 8) to end:")
path_from_gap = grid.compute_astar_path(15, 8, end_pos[0], end_pos[1])
print(f" Path from gap: {path_from_gap}")
# Check walls more carefully
print("\nDetailed wall analysis:")
print(" Walls at x=25 (blocking end?):")
for y in range(5, 15):
print(f" ({25}, {y}): walkable={grid.at(25, y).walkable}")
def timer_cb(dt):
sys.exit(0)
ui = mcrfpy.sceneUI("debug")
ui.append(grid)
mcrfpy.setScene("debug")
mcrfpy.setTimer("exit", timer_cb, 100)

View File

@ -0,0 +1,137 @@
#!/usr/bin/env python3
"""
Working Dijkstra Demo with Clear Visual Feedback
================================================
This demo shows pathfinding with high-contrast colors.
"""
import mcrfpy
import sys
# High contrast colors
WALL_COLOR = mcrfpy.Color(40, 20, 20) # Very dark red/brown for walls
FLOOR_COLOR = mcrfpy.Color(60, 60, 80) # Dark blue-gray for floors
PATH_COLOR = mcrfpy.Color(0, 255, 0) # Pure green for paths
START_COLOR = mcrfpy.Color(255, 0, 0) # Red for start
END_COLOR = mcrfpy.Color(0, 0, 255) # Blue for end
print("Dijkstra Demo - High Contrast")
print("==============================")
# Create scene
mcrfpy.createScene("dijkstra_demo")
# Create grid with exact layout from user
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
grid.fill_color = mcrfpy.Color(0, 0, 0)
# Map layout
map_layout = [
"..............", # Row 0
"..W.....WWWW..", # Row 1
"..W.W...W.EW..", # Row 2
"..W.....W..W..", # Row 3
"..W...E.WWWW..", # Row 4
"E.W...........", # Row 5
"..W...........", # Row 6
"..W...........", # Row 7
"..W.WWW.......", # Row 8
"..............", # Row 9
]
# Create the map
entity_positions = []
for y, row in enumerate(map_layout):
for x, char in enumerate(row):
cell = grid.at(x, y)
if char == 'W':
cell.walkable = False
cell.color = WALL_COLOR
else:
cell.walkable = True
cell.color = FLOOR_COLOR
if char == 'E':
entity_positions.append((x, y))
print(f"Map created: {grid.grid_x}x{grid.grid_y}")
print(f"Entity positions: {entity_positions}")
# Create entities
entities = []
for i, (x, y) in enumerate(entity_positions):
entity = mcrfpy.Entity(x, y)
entity.sprite_index = 49 + i # '1', '2', '3'
grid.entities.append(entity)
entities.append(entity)
print(f"Entity {i+1} at ({x}, {y})")
# Highlight a path immediately
if len(entities) >= 2:
e1, e2 = entities[0], entities[1]
print(f"\nCalculating path from Entity 1 ({e1.x}, {e1.y}) to Entity 2 ({e2.x}, {e2.y})...")
path = e1.path_to(int(e2.x), int(e2.y))
print(f"Path found: {path}")
print(f"Path length: {len(path)} steps")
if path:
print("\nHighlighting path in bright green...")
# Color start and end specially
grid.at(int(e1.x), int(e1.y)).color = START_COLOR
grid.at(int(e2.x), int(e2.y)).color = END_COLOR
# Color the path
for i, (x, y) in enumerate(path):
if i > 0 and i < len(path) - 1: # Skip start and end
grid.at(x, y).color = PATH_COLOR
print(f" Colored ({x}, {y}) green")
# Keypress handler
def handle_keypress(scene_name, keycode):
if keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
print("\nExiting...")
sys.exit(0)
elif keycode == 32: # Space
print("\nRefreshing path colors...")
# Re-color the path to ensure it's visible
if len(entities) >= 2 and path:
for x, y in path[1:-1]:
grid.at(x, y).color = PATH_COLOR
# Set up UI
ui = mcrfpy.sceneUI("dijkstra_demo")
ui.append(grid)
# Scale grid
grid.size = (560, 400) # 14*40, 10*40
grid.position = (120, 100)
# Add title
title = mcrfpy.Caption("Dijkstra Pathfinding - High Contrast", 200, 20)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add legend
legend1 = mcrfpy.Caption("Red=Start, Blue=End, Green=Path", 120, 520)
legend1.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(legend1)
legend2 = mcrfpy.Caption("Press Q to quit, SPACE to refresh", 120, 540)
legend2.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend2)
# Entity info
info = mcrfpy.Caption(f"Path: Entity 1 to 2 = {len(path) if 'path' in locals() else 0} steps", 120, 60)
info.fill_color = mcrfpy.Color(255, 255, 100)
ui.append(info)
# Set up input
mcrfpy.keypressScene(handle_keypress)
mcrfpy.setScene("dijkstra_demo")
print("\nDemo ready! The path should be clearly visible in bright green.")
print("Red = Start, Blue = End, Green = Path")
print("Press SPACE to refresh colors if needed.")

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,306 @@
#!/usr/bin/env python3
"""
McRogueFace Exhaustive API Demo (Fixed)
=======================================
Fixed version that properly exits after tests complete.
"""
import mcrfpy
import sys
# Test configuration
VERBOSE = True # Print detailed information about each test
def print_section(title):
"""Print a section header"""
print("\n" + "="*60)
print(f" {title}")
print("="*60)
def print_test(test_name, success=True):
"""Print test result"""
status = "✓ PASS" if success else "✗ FAIL"
print(f" {status} - {test_name}")
def test_color_api():
"""Test all Color constructors and methods"""
print_section("COLOR API TESTS")
# Constructor variants
print("\n Constructors:")
# Empty constructor (defaults to white)
c1 = mcrfpy.Color()
print_test(f"Color() = ({c1.r}, {c1.g}, {c1.b}, {c1.a})")
# Single value (grayscale)
c2 = mcrfpy.Color(128)
print_test(f"Color(128) = ({c2.r}, {c2.g}, {c2.b}, {c2.a})")
# RGB only (alpha defaults to 255)
c3 = mcrfpy.Color(255, 128, 0)
print_test(f"Color(255, 128, 0) = ({c3.r}, {c3.g}, {c3.b}, {c3.a})")
# Full RGBA
c4 = mcrfpy.Color(100, 150, 200, 128)
print_test(f"Color(100, 150, 200, 128) = ({c4.r}, {c4.g}, {c4.b}, {c4.a})")
# Property access
print("\n Properties:")
c = mcrfpy.Color(10, 20, 30, 40)
print_test(f"Initial: r={c.r}, g={c.g}, b={c.b}, a={c.a}")
c.r = 200
c.g = 150
c.b = 100
c.a = 255
print_test(f"After modification: r={c.r}, g={c.g}, b={c.b}, a={c.a}")
return True
def test_frame_api():
"""Test all Frame constructors and methods"""
print_section("FRAME API TESTS")
# Create a test scene
mcrfpy.createScene("api_test")
mcrfpy.setScene("api_test")
ui = mcrfpy.sceneUI("api_test")
# Constructor variants
print("\n Constructors:")
# Empty constructor
f1 = mcrfpy.Frame()
print_test(f"Frame() - pos=({f1.x}, {f1.y}), size=({f1.w}, {f1.h})")
ui.append(f1)
# Position only
f2 = mcrfpy.Frame(100, 50)
print_test(f"Frame(100, 50) - pos=({f2.x}, {f2.y}), size=({f2.w}, {f2.h})")
ui.append(f2)
# Position and size
f3 = mcrfpy.Frame(200, 100, 150, 75)
print_test(f"Frame(200, 100, 150, 75) - pos=({f3.x}, {f3.y}), size=({f3.w}, {f3.h})")
ui.append(f3)
# Full constructor
f4 = mcrfpy.Frame(300, 200, 200, 100,
fill_color=mcrfpy.Color(100, 100, 200),
outline_color=mcrfpy.Color(255, 255, 0),
outline=3)
print_test("Frame with all parameters")
ui.append(f4)
# Properties
print("\n Properties:")
# Position and size
f = mcrfpy.Frame(10, 20, 30, 40)
print_test(f"Initial: x={f.x}, y={f.y}, w={f.w}, h={f.h}")
f.x = 50
f.y = 60
f.w = 70
f.h = 80
print_test(f"Modified: x={f.x}, y={f.y}, w={f.w}, h={f.h}")
# Colors
f.fill_color = mcrfpy.Color(255, 0, 0, 128)
f.outline_color = mcrfpy.Color(0, 255, 0)
f.outline = 5.0
print_test(f"Colors set, outline={f.outline}")
# Visibility and opacity
f.visible = False
f.opacity = 0.5
print_test(f"visible={f.visible}, opacity={f.opacity}")
f.visible = True # Reset
# Z-index
f.z_index = 10
print_test(f"z_index={f.z_index}")
# Children collection
child1 = mcrfpy.Frame(5, 5, 20, 20)
child2 = mcrfpy.Frame(30, 5, 20, 20)
f.children.append(child1)
f.children.append(child2)
print_test(f"children.count = {len(f.children)}")
return True
def test_caption_api():
"""Test all Caption constructors and methods"""
print_section("CAPTION API TESTS")
ui = mcrfpy.sceneUI("api_test")
# Constructor variants
print("\n Constructors:")
# Empty constructor
c1 = mcrfpy.Caption()
print_test(f"Caption() - text='{c1.text}', pos=({c1.x}, {c1.y})")
ui.append(c1)
# Text only
c2 = mcrfpy.Caption("Hello World")
print_test(f"Caption('Hello World') - pos=({c2.x}, {c2.y})")
ui.append(c2)
# Text and position
c3 = mcrfpy.Caption("Positioned Text", 100, 50)
print_test(f"Caption('Positioned Text', 100, 50)")
ui.append(c3)
# Full constructor
c5 = mcrfpy.Caption("Styled Text", 300, 150,
fill_color=mcrfpy.Color(255, 255, 0),
outline_color=mcrfpy.Color(255, 0, 0),
outline=2)
print_test("Caption with all style parameters")
ui.append(c5)
# Properties
print("\n Properties:")
c = mcrfpy.Caption("Test Caption", 10, 20)
# Text
c.text = "Modified Text"
print_test(f"text = '{c.text}'")
# Position
c.x = 50
c.y = 60
print_test(f"position = ({c.x}, {c.y})")
# Colors and style
c.fill_color = mcrfpy.Color(0, 255, 255)
c.outline_color = mcrfpy.Color(255, 0, 255)
c.outline = 3.0
print_test("Colors and outline set")
# Size (read-only, computed from text)
print_test(f"size (computed) = ({c.w}, {c.h})")
return True
def test_animation_api():
"""Test Animation class API"""
print_section("ANIMATION API TESTS")
ui = mcrfpy.sceneUI("api_test")
print("\n Animation Constructors:")
# Basic animation
anim1 = mcrfpy.Animation("x", 100.0, 2.0)
print_test("Animation('x', 100.0, 2.0)")
# With easing
anim2 = mcrfpy.Animation("y", 200.0, 3.0, "easeInOut")
print_test("Animation with easing='easeInOut'")
# Delta mode
anim3 = mcrfpy.Animation("w", 50.0, 1.5, "linear", delta=True)
print_test("Animation with delta=True")
# Color animation (as tuple)
anim4 = mcrfpy.Animation("fill_color", (255, 0, 0, 255), 2.0)
print_test("Animation with Color tuple target")
# Vector animation
anim5 = mcrfpy.Animation("position", (10.0, 20.0), 2.5, "easeOutBounce")
print_test("Animation with position tuple")
# Sprite sequence
anim6 = mcrfpy.Animation("sprite_index", [0, 1, 2, 3, 2, 1], 2.0)
print_test("Animation with sprite sequence")
# Properties
print("\n Animation Properties:")
# Check properties
print_test(f"property = '{anim1.property}'")
print_test(f"duration = {anim1.duration}")
print_test(f"elapsed = {anim1.elapsed}")
print_test(f"is_complete = {anim1.is_complete}")
print_test(f"is_delta = {anim3.is_delta}")
# Methods
print("\n Animation Methods:")
# Create test frame
frame = mcrfpy.Frame(50, 50, 100, 100)
frame.fill_color = mcrfpy.Color(100, 100, 100)
ui.append(frame)
# Start animation
anim1.start(frame)
print_test("start() called on frame")
# Test some easing functions
print("\n Sample Easing Functions:")
easings = ["linear", "easeIn", "easeOut", "easeInOut", "easeInBounce", "easeOutElastic"]
for easing in easings:
try:
test_anim = mcrfpy.Animation("x", 100.0, 1.0, easing)
print_test(f"Easing '{easing}'")
except:
print_test(f"Easing '{easing}' failed", False)
return True
def run_all_tests():
"""Run all API tests"""
print("\n" + "="*60)
print(" McRogueFace Exhaustive API Test Suite (Fixed)")
print(" Testing constructors and methods...")
print("="*60)
# Run each test category
test_functions = [
test_color_api,
test_frame_api,
test_caption_api,
test_animation_api
]
passed = 0
failed = 0
for test_func in test_functions:
try:
if test_func():
passed += 1
else:
failed += 1
except Exception as e:
print(f"\n ERROR in {test_func.__name__}: {e}")
failed += 1
# Summary
print("\n" + "="*60)
print(f" TEST SUMMARY: {passed} passed, {failed} failed")
print("="*60)
print("\n Visual elements are displayed in the 'api_test' scene.")
print(" The test is complete.")
# Exit after a short delay to allow output to be seen
def exit_test(runtime):
print("\nExiting API test suite...")
sys.exit(0)
mcrfpy.setTimer("exit", exit_test, 2000)
# Run the tests immediately
print("Starting McRogueFace Exhaustive API Demo (Fixed)...")
print("This will test constructors and methods.")
run_all_tests()

View File

@ -0,0 +1,391 @@
#!/usr/bin/env python3
"""
Path & Vision Sizzle Reel
=========================
A choreographed demo showing:
- Smooth entity movement along paths
- Camera following with grid center animation
- Field of view updates as entities move
- Dramatic perspective transitions with zoom effects
"""
import mcrfpy
import sys
# Colors
WALL_COLOR = mcrfpy.Color(40, 30, 30)
FLOOR_COLOR = mcrfpy.Color(80, 80, 100)
PATH_COLOR = mcrfpy.Color(120, 120, 180)
DARK_FLOOR = mcrfpy.Color(40, 40, 50)
# Global state
grid = None
player = None
enemy = None
sequence_step = 0
player_path = []
enemy_path = []
player_path_index = 0
enemy_path_index = 0
def create_scene():
"""Create the demo environment"""
global grid, player, enemy
mcrfpy.createScene("path_vision_demo")
# Create larger grid for more dramatic movement
grid = mcrfpy.Grid(grid_x=40, grid_y=25)
grid.fill_color = mcrfpy.Color(20, 20, 30)
# Map layout - interconnected rooms with corridors
map_layout = [
"########################################", # 0
"#......##########......################", # 1
"#......##########......################", # 2
"#......##########......################", # 3
"#......#.........#.....################", # 4
"#......#.........#.....################", # 5
"####.###.........####.#################", # 6
"####.....................##############", # 7
"####.....................##############", # 8
"####.###.........####.#################", # 9
"#......#.........#.....################", # 10
"#......#.........#.....################", # 11
"#......#.........#.....################", # 12
"#......###.....###.....################", # 13
"#......###.....###.....################", # 14
"#......###.....###.....#########......#", # 15
"#......###.....###.....#########......#", # 16
"#......###.....###.....#########......#", # 17
"#####.############.#############......#", # 18
"#####...........................#.....#", # 19
"#####...........................#.....#", # 20
"#####.############.#############......#", # 21
"#......###########.##########.........#", # 22
"#......###########.##########.........#", # 23
"########################################", # 24
]
# Build the map
for y, row in enumerate(map_layout):
for x, char in enumerate(row):
cell = grid.at(x, y)
if char == '#':
cell.walkable = False
cell.transparent = False
cell.color = WALL_COLOR
else:
cell.walkable = True
cell.transparent = True
cell.color = FLOOR_COLOR
# Create player in top-left room
player = mcrfpy.Entity(3, 3, grid=grid)
player.sprite_index = 64 # @
# Create enemy in bottom-right area
enemy = mcrfpy.Entity(35, 20, grid=grid)
enemy.sprite_index = 69 # E
# Initial visibility
player.update_visibility()
enemy.update_visibility()
# Set initial perspective to player
grid.perspective = 0
def setup_paths():
"""Define the paths for entities"""
global player_path, enemy_path
# Player path: Top-left room → corridor → middle room
player_waypoints = [
(3, 3), # Start
(3, 8), # Move down
(7, 8), # Enter corridor
(16, 8), # Through corridor
(16, 12), # Enter middle room
(12, 12), # Move in room
(12, 16), # Move down
(16, 16), # Move right
(16, 19), # Exit room
(25, 19), # Move right
(30, 19), # Continue
(35, 19), # Near enemy start
]
# Enemy path: Bottom-right → around → approach player area
enemy_waypoints = [
(35, 20), # Start
(30, 20), # Move left
(25, 20), # Continue
(20, 20), # Continue
(16, 20), # Corridor junction
(16, 16), # Move up (might see player)
(16, 12), # Continue up
(16, 8), # Top corridor
(10, 8), # Move left
(7, 8), # Continue
(3, 8), # Player's area
(3, 12), # Move down
]
# Calculate full paths using pathfinding
player_path = []
for i in range(len(player_waypoints) - 1):
x1, y1 = player_waypoints[i]
x2, y2 = player_waypoints[i + 1]
# Use grid's A* pathfinding
segment = grid.compute_astar_path(x1, y1, x2, y2)
if segment:
# Add segment (avoiding duplicates)
if not player_path or segment[0] != player_path[-1]:
player_path.extend(segment)
else:
player_path.extend(segment[1:])
enemy_path = []
for i in range(len(enemy_waypoints) - 1):
x1, y1 = enemy_waypoints[i]
x2, y2 = enemy_waypoints[i + 1]
segment = grid.compute_astar_path(x1, y1, x2, y2)
if segment:
if not enemy_path or segment[0] != enemy_path[-1]:
enemy_path.extend(segment)
else:
enemy_path.extend(segment[1:])
print(f"Player path: {len(player_path)} steps")
print(f"Enemy path: {len(enemy_path)} steps")
def setup_ui():
"""Create UI elements"""
ui = mcrfpy.sceneUI("path_vision_demo")
ui.append(grid)
# Position and size grid
grid.position = (50, 80)
grid.size = (700, 500) # Adjust based on zoom
# Title
title = mcrfpy.Caption("Path & Vision Sizzle Reel", 300, 20)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Status
global status_text, perspective_text
status_text = mcrfpy.Caption("Starting demo...", 50, 50)
status_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(status_text)
perspective_text = mcrfpy.Caption("Perspective: Player", 550, 50)
perspective_text.fill_color = mcrfpy.Color(100, 255, 100)
ui.append(perspective_text)
# Controls
controls = mcrfpy.Caption("Space: Pause/Resume | R: Restart | Q: Quit", 250, 600)
controls.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(controls)
# Animation control
paused = False
move_timer = 0
zoom_transition = False
def move_entity_smooth(entity, target_x, target_y, duration=0.3):
"""Smoothly animate entity to position"""
# Create position animation
anim_x = mcrfpy.Animation("x", float(target_x), duration, "easeInOut")
anim_y = mcrfpy.Animation("y", float(target_y), duration, "easeInOut")
anim_x.start(entity)
anim_y.start(entity)
def update_camera_smooth(center_x, center_y, duration=0.3):
"""Smoothly move camera center"""
# Convert grid coords to pixel coords (assuming 16x16 tiles)
pixel_x = center_x * 16
pixel_y = center_y * 16
anim = mcrfpy.Animation("center", (pixel_x, pixel_y), duration, "easeOut")
anim.start(grid)
def start_perspective_transition():
"""Begin the dramatic perspective shift"""
global zoom_transition, sequence_step
zoom_transition = True
sequence_step = 100 # Special sequence number
status_text.text = "Perspective shift: Zooming out..."
# Zoom out with elastic easing
zoom_out = mcrfpy.Animation("zoom", 0.5, 2.0, "easeInExpo")
zoom_out.start(grid)
# Schedule the perspective switch
mcrfpy.setTimer("switch_perspective", switch_perspective, 2100)
def switch_perspective(dt):
"""Switch perspective at the peak of zoom"""
global sequence_step
# Switch to enemy perspective
grid.perspective = 1
perspective_text.text = "Perspective: Enemy"
perspective_text.fill_color = mcrfpy.Color(255, 100, 100)
status_text.text = "Perspective shift: Following enemy..."
# Update camera to enemy position
update_camera_smooth(enemy.x, enemy.y, 0.1)
# Zoom back in
zoom_in = mcrfpy.Animation("zoom", 1.2, 2.0, "easeOutExpo")
zoom_in.start(grid)
# Resume sequence
mcrfpy.setTimer("resume_enemy", resume_enemy_sequence, 2100)
# Cancel this timer
mcrfpy.delTimer("switch_perspective")
def resume_enemy_sequence(dt):
"""Resume following enemy after perspective shift"""
global sequence_step, zoom_transition
zoom_transition = False
sequence_step = 101 # Continue with enemy movement
mcrfpy.delTimer("resume_enemy")
def sequence_tick(dt):
"""Main sequence controller"""
global sequence_step, player_path_index, enemy_path_index, move_timer
if paused or zoom_transition:
return
move_timer += dt
if move_timer < 400: # Move every 400ms
return
move_timer = 0
if sequence_step < 50:
# Phase 1: Follow player movement
if player_path_index < len(player_path):
x, y = player_path[player_path_index]
move_entity_smooth(player, x, y)
player.update_visibility()
# Camera follows player
if grid.perspective == 0:
update_camera_smooth(player.x, player.y)
player_path_index += 1
status_text.text = f"Player moving... Step {player_path_index}/{len(player_path)}"
# Start enemy movement after player has moved a bit
if player_path_index == 10:
sequence_step = 1 # Enable enemy movement
else:
# Player reached destination, start perspective transition
start_perspective_transition()
if sequence_step >= 1 and sequence_step < 50:
# Phase 2: Enemy movement (concurrent with player)
if enemy_path_index < len(enemy_path):
x, y = enemy_path[enemy_path_index]
move_entity_smooth(enemy, x, y)
enemy.update_visibility()
# Check if enemy is visible to player
if grid.perspective == 0:
enemy_cell_idx = int(enemy.y) * grid.grid_x + int(enemy.x)
if enemy_cell_idx < len(player.gridstate) and player.gridstate[enemy_cell_idx].visible:
status_text.text = "Enemy spotted!"
enemy_path_index += 1
elif sequence_step == 101:
# Phase 3: Continue following enemy after perspective shift
if enemy_path_index < len(enemy_path):
x, y = enemy_path[enemy_path_index]
move_entity_smooth(enemy, x, y)
enemy.update_visibility()
# Camera follows enemy
update_camera_smooth(enemy.x, enemy.y)
enemy_path_index += 1
status_text.text = f"Following enemy... Step {enemy_path_index}/{len(enemy_path)}"
else:
status_text.text = "Demo complete! Press R to restart"
sequence_step = 200 # Done
def handle_keys(key, state):
"""Handle keyboard input"""
global paused, sequence_step, player_path_index, enemy_path_index, move_timer
key = key.lower()
if state != "start":
return
if key == "q":
print("Exiting sizzle reel...")
sys.exit(0)
elif key == "space":
paused = not paused
status_text.text = "PAUSED" if paused else "Running..."
elif key == "r":
# Reset everything
player.x, player.y = 3, 3
enemy.x, enemy.y = 35, 20
player.update_visibility()
enemy.update_visibility()
grid.perspective = 0
perspective_text.text = "Perspective: Player"
perspective_text.fill_color = mcrfpy.Color(100, 255, 100)
sequence_step = 0
player_path_index = 0
enemy_path_index = 0
move_timer = 0
update_camera_smooth(player.x, player.y, 0.5)
# Reset zoom
zoom_reset = mcrfpy.Animation("zoom", 1.2, 0.5, "easeOut")
zoom_reset.start(grid)
status_text.text = "Demo restarted!"
# Initialize everything
print("Path & Vision Sizzle Reel")
print("=========================")
print("Demonstrating:")
print("- Smooth entity movement along calculated paths")
print("- Camera following with animated grid centering")
print("- Field of view updates as entities move")
print("- Dramatic perspective transitions with zoom effects")
print()
create_scene()
setup_paths()
setup_ui()
# Set scene and input
mcrfpy.setScene("path_vision_demo")
mcrfpy.keypressScene(handle_keys)
# Initial camera setup
grid.zoom = 1.2
update_camera_smooth(player.x, player.y, 0.1)
# Start the sequence
mcrfpy.setTimer("sequence", sequence_tick, 50) # Tick every 50ms
print("Demo started!")
print("- Player (@) will navigate through rooms")
print("- Enemy (E) will move on a different path")
print("- Watch for the dramatic perspective shift!")
print()
print("Controls: Space=Pause, R=Restart, Q=Quit")

View File

@ -0,0 +1,373 @@
#!/usr/bin/env python3
"""
Pathfinding Showcase Demo
=========================
Demonstrates various pathfinding scenarios with multiple entities.
Features:
- Multiple entities pathfinding simultaneously
- Chase mode: entities pursue targets
- Flee mode: entities avoid threats
- Patrol mode: entities follow waypoints
- Visual debugging: show Dijkstra distance field
"""
import mcrfpy
import sys
import random
# Colors
WALL_COLOR = mcrfpy.Color(40, 40, 40)
FLOOR_COLOR = mcrfpy.Color(220, 220, 240)
PATH_COLOR = mcrfpy.Color(180, 250, 180)
THREAT_COLOR = mcrfpy.Color(255, 100, 100)
GOAL_COLOR = mcrfpy.Color(100, 255, 100)
DIJKSTRA_COLORS = [
mcrfpy.Color(50, 50, 100), # Far
mcrfpy.Color(70, 70, 150),
mcrfpy.Color(90, 90, 200),
mcrfpy.Color(110, 110, 250),
mcrfpy.Color(150, 150, 255),
mcrfpy.Color(200, 200, 255), # Near
]
# Entity types
PLAYER = 64 # @
ENEMY = 69 # E
TREASURE = 36 # $
PATROL = 80 # P
# Global state
grid = None
player = None
enemies = []
treasures = []
patrol_entities = []
mode = "CHASE"
show_dijkstra = False
animation_speed = 3.0
def create_dungeon():
"""Create a dungeon-like map"""
global grid
mcrfpy.createScene("pathfinding_showcase")
# Create larger grid for showcase
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
grid.fill_color = mcrfpy.Color(0, 0, 0)
# Initialize all as floor
for y in range(20):
for x in range(30):
grid.at(x, y).walkable = True
grid.at(x, y).transparent = True
grid.at(x, y).color = FLOOR_COLOR
# Create rooms and corridors
rooms = [
(2, 2, 8, 6), # Top-left room
(20, 2, 8, 6), # Top-right room
(11, 8, 8, 6), # Center room
(2, 14, 8, 5), # Bottom-left room
(20, 14, 8, 5), # Bottom-right room
]
# Create room walls
for rx, ry, rw, rh in rooms:
# Top and bottom walls
for x in range(rx, rx + rw):
if 0 <= x < 30:
grid.at(x, ry).walkable = False
grid.at(x, ry).color = WALL_COLOR
grid.at(x, ry + rh - 1).walkable = False
grid.at(x, ry + rh - 1).color = WALL_COLOR
# Left and right walls
for y in range(ry, ry + rh):
if 0 <= y < 20:
grid.at(rx, y).walkable = False
grid.at(rx, y).color = WALL_COLOR
grid.at(rx + rw - 1, y).walkable = False
grid.at(rx + rw - 1, y).color = WALL_COLOR
# Create doorways
doorways = [
(6, 2), (24, 2), # Top room doors
(6, 7), (24, 7), # Top room doors bottom
(15, 8), (15, 13), # Center room doors
(6, 14), (24, 14), # Bottom room doors
(11, 11), (18, 11), # Center room side doors
]
for x, y in doorways:
if 0 <= x < 30 and 0 <= y < 20:
grid.at(x, y).walkable = True
grid.at(x, y).color = FLOOR_COLOR
# Add some corridors
# Horizontal corridors
for x in range(10, 20):
grid.at(x, 5).walkable = True
grid.at(x, 5).color = FLOOR_COLOR
grid.at(x, 16).walkable = True
grid.at(x, 16).color = FLOOR_COLOR
# Vertical corridors
for y in range(5, 17):
grid.at(10, y).walkable = True
grid.at(10, y).color = FLOOR_COLOR
grid.at(19, y).walkable = True
grid.at(19, y).color = FLOOR_COLOR
def spawn_entities():
"""Spawn various entity types"""
global player, enemies, treasures, patrol_entities
# Clear existing entities
grid.entities.clear()
enemies = []
treasures = []
patrol_entities = []
# Spawn player in center room
player = mcrfpy.Entity(15, 11)
player.sprite_index = PLAYER
grid.entities.append(player)
# Spawn enemies in corners
enemy_positions = [(4, 4), (24, 4), (4, 16), (24, 16)]
for x, y in enemy_positions:
enemy = mcrfpy.Entity(x, y)
enemy.sprite_index = ENEMY
grid.entities.append(enemy)
enemies.append(enemy)
# Spawn treasures
treasure_positions = [(6, 5), (24, 5), (15, 10)]
for x, y in treasure_positions:
treasure = mcrfpy.Entity(x, y)
treasure.sprite_index = TREASURE
grid.entities.append(treasure)
treasures.append(treasure)
# Spawn patrol entities
patrol = mcrfpy.Entity(10, 10)
patrol.sprite_index = PATROL
patrol.waypoints = [(10, 10), (19, 10), (19, 16), (10, 16)] # Square patrol
patrol.waypoint_index = 0
grid.entities.append(patrol)
patrol_entities.append(patrol)
def visualize_dijkstra(target_x, target_y):
"""Visualize Dijkstra distance field"""
if not show_dijkstra:
return
# Compute Dijkstra from target
grid.compute_dijkstra(target_x, target_y)
# Color tiles based on distance
max_dist = 30.0
for y in range(20):
for x in range(30):
if grid.at(x, y).walkable:
dist = grid.get_dijkstra_distance(x, y)
if dist is not None and dist < max_dist:
# Map distance to color index
color_idx = int((dist / max_dist) * len(DIJKSTRA_COLORS))
color_idx = min(color_idx, len(DIJKSTRA_COLORS) - 1)
grid.at(x, y).color = DIJKSTRA_COLORS[color_idx]
def move_enemies(dt):
"""Move enemies based on current mode"""
if mode == "CHASE":
# Enemies chase player
for enemy in enemies:
path = enemy.path_to(int(player.x), int(player.y))
if path and len(path) > 1: # Don't move onto player
# Move towards player
next_x, next_y = path[1]
# Smooth movement
dx = next_x - enemy.x
dy = next_y - enemy.y
enemy.x += dx * dt * animation_speed
enemy.y += dy * dt * animation_speed
elif mode == "FLEE":
# Enemies flee from player
for enemy in enemies:
# Compute opposite direction
dx = enemy.x - player.x
dy = enemy.y - player.y
# Find safe spot in that direction
target_x = int(enemy.x + dx * 2)
target_y = int(enemy.y + dy * 2)
# Clamp to grid
target_x = max(0, min(29, target_x))
target_y = max(0, min(19, target_y))
path = enemy.path_to(target_x, target_y)
if path and len(path) > 0:
next_x, next_y = path[0]
# Move away from player
dx = next_x - enemy.x
dy = next_y - enemy.y
enemy.x += dx * dt * animation_speed
enemy.y += dy * dt * animation_speed
def move_patrols(dt):
"""Move patrol entities along waypoints"""
for patrol in patrol_entities:
if not hasattr(patrol, 'waypoints'):
continue
# Get current waypoint
target_x, target_y = patrol.waypoints[patrol.waypoint_index]
# Check if reached waypoint
dist = abs(patrol.x - target_x) + abs(patrol.y - target_y)
if dist < 0.5:
# Move to next waypoint
patrol.waypoint_index = (patrol.waypoint_index + 1) % len(patrol.waypoints)
target_x, target_y = patrol.waypoints[patrol.waypoint_index]
# Path to waypoint
path = patrol.path_to(target_x, target_y)
if path and len(path) > 0:
next_x, next_y = path[0]
dx = next_x - patrol.x
dy = next_y - patrol.y
patrol.x += dx * dt * animation_speed * 0.5 # Slower patrol speed
patrol.y += dy * dt * animation_speed * 0.5
def update_entities(dt):
"""Update all entity movements"""
move_enemies(dt / 1000.0) # Convert to seconds
move_patrols(dt / 1000.0)
# Update Dijkstra visualization
if show_dijkstra and player:
visualize_dijkstra(int(player.x), int(player.y))
def handle_keypress(scene_name, keycode):
"""Handle keyboard input"""
global mode, show_dijkstra, player
# Mode switching
if keycode == 49: # '1'
mode = "CHASE"
mode_text.text = "Mode: CHASE - Enemies pursue player"
clear_colors()
elif keycode == 50: # '2'
mode = "FLEE"
mode_text.text = "Mode: FLEE - Enemies avoid player"
clear_colors()
elif keycode == 51: # '3'
mode = "PATROL"
mode_text.text = "Mode: PATROL - Entities follow waypoints"
clear_colors()
# Toggle Dijkstra visualization
elif keycode == 68 or keycode == 100: # 'D' or 'd'
show_dijkstra = not show_dijkstra
debug_text.text = f"Dijkstra Debug: {'ON' if show_dijkstra else 'OFF'}"
if not show_dijkstra:
clear_colors()
# Move player with arrow keys or WASD
elif keycode in [87, 119]: # W/w - Up
if player.y > 0:
path = player.path_to(int(player.x), int(player.y) - 1)
if path:
player.y -= 1
elif keycode in [83, 115]: # S/s - Down
if player.y < 19:
path = player.path_to(int(player.x), int(player.y) + 1)
if path:
player.y += 1
elif keycode in [65, 97]: # A/a - Left
if player.x > 0:
path = player.path_to(int(player.x) - 1, int(player.y))
if path:
player.x -= 1
elif keycode in [68, 100]: # D/d - Right
if player.x < 29:
path = player.path_to(int(player.x) + 1, int(player.y))
if path:
player.x += 1
# Reset
elif keycode == 82 or keycode == 114: # 'R' or 'r'
spawn_entities()
clear_colors()
# Quit
elif keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
print("\nExiting pathfinding showcase...")
sys.exit(0)
def clear_colors():
"""Reset floor colors"""
for y in range(20):
for x in range(30):
if grid.at(x, y).walkable:
grid.at(x, y).color = FLOOR_COLOR
# Create the showcase
print("Pathfinding Showcase Demo")
print("=========================")
print("Controls:")
print(" WASD - Move player")
print(" 1 - Chase mode (enemies pursue)")
print(" 2 - Flee mode (enemies avoid)")
print(" 3 - Patrol mode")
print(" D - Toggle Dijkstra visualization")
print(" R - Reset entities")
print(" Q/ESC - Quit")
# Create dungeon
create_dungeon()
spawn_entities()
# Set up UI
ui = mcrfpy.sceneUI("pathfinding_showcase")
ui.append(grid)
# Scale and position
grid.size = (750, 500) # 30*25, 20*25
grid.position = (25, 60)
# Add title
title = mcrfpy.Caption("Pathfinding Showcase", 300, 10)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add mode text
mode_text = mcrfpy.Caption("Mode: CHASE - Enemies pursue player", 25, 580)
mode_text.fill_color = mcrfpy.Color(255, 255, 200)
ui.append(mode_text)
# Add debug text
debug_text = mcrfpy.Caption("Dijkstra Debug: OFF", 25, 600)
debug_text.fill_color = mcrfpy.Color(200, 200, 255)
ui.append(debug_text)
# Add legend
legend = mcrfpy.Caption("@ Player E Enemy $ Treasure P Patrol", 25, 620)
legend.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend)
# Set up input handling
mcrfpy.keypressScene(handle_keypress)
# Set up animation timer
mcrfpy.setTimer("entities", update_entities, 16) # 60 FPS
# Show scene
mcrfpy.setScene("pathfinding_showcase")
print("\nShowcase ready! Move with WASD and watch entities react.")

View File

@ -0,0 +1,226 @@
#!/usr/bin/env python3
"""
Simple Text Input Widget for McRogueFace
Minimal implementation focusing on core functionality
"""
import mcrfpy
import sys
class TextInput:
"""Simple text input widget"""
def __init__(self, x, y, width, label=""):
self.x = x
self.y = y
self.width = width
self.label = label
self.text = ""
self.cursor_pos = 0
self.focused = False
# Create UI elements
self.frame = mcrfpy.Frame(self.x, self.y, self.width, 24)
self.frame.fill_color = (255, 255, 255, 255)
self.frame.outline_color = (128, 128, 128, 255)
self.frame.outline = 2
# Label
if self.label:
self.label_caption = mcrfpy.Caption(self.label, self.x, self.y - 20)
self.label_caption.color = (255, 255, 255, 255)
# Text display
self.text_caption = mcrfpy.Caption("", self.x + 4, self.y + 4)
self.text_caption.color = (0, 0, 0, 255)
# Cursor (a simple vertical line using a frame)
self.cursor = mcrfpy.Frame(self.x + 4, self.y + 4, 2, 16)
self.cursor.fill_color = (0, 0, 0, 255)
self.cursor.visible = False
# Click handler
self.frame.click = self._on_click
def _on_click(self, x, y, button):
"""Handle clicks"""
if button == 1: # Left click
# Request focus
global current_focus
if current_focus and current_focus != self:
current_focus.blur()
current_focus = self
self.focus()
def focus(self):
"""Give focus to this input"""
self.focused = True
self.frame.outline_color = (0, 120, 255, 255)
self.frame.outline = 3
self.cursor.visible = True
self._update_cursor()
def blur(self):
"""Remove focus"""
self.focused = False
self.frame.outline_color = (128, 128, 128, 255)
self.frame.outline = 2
self.cursor.visible = False
def handle_key(self, key):
"""Process keyboard input"""
if not self.focused:
return False
if key == "BackSpace":
if self.cursor_pos > 0:
self.text = self.text[:self.cursor_pos-1] + self.text[self.cursor_pos:]
self.cursor_pos -= 1
elif key == "Delete":
if self.cursor_pos < len(self.text):
self.text = self.text[:self.cursor_pos] + self.text[self.cursor_pos+1:]
elif key == "Left":
self.cursor_pos = max(0, self.cursor_pos - 1)
elif key == "Right":
self.cursor_pos = min(len(self.text), self.cursor_pos + 1)
elif key == "Home":
self.cursor_pos = 0
elif key == "End":
self.cursor_pos = len(self.text)
elif len(key) == 1 and key.isprintable():
self.text = self.text[:self.cursor_pos] + key + self.text[self.cursor_pos:]
self.cursor_pos += 1
else:
return False
self._update_display()
return True
def _update_display(self):
"""Update text display"""
self.text_caption.text = self.text
self._update_cursor()
def _update_cursor(self):
"""Update cursor position"""
if self.focused:
# Estimate character width (roughly 10 pixels per char)
self.cursor.x = self.x + 4 + (self.cursor_pos * 10)
def add_to_scene(self, scene):
"""Add all components to scene"""
scene.append(self.frame)
if hasattr(self, 'label_caption'):
scene.append(self.label_caption)
scene.append(self.text_caption)
scene.append(self.cursor)
# Global focus tracking
current_focus = None
text_inputs = []
def demo_test(timer_name):
"""Run automated demo after scene loads"""
print("\n=== Text Input Widget Demo ===")
# Test typing in first field
print("Testing first input field...")
text_inputs[0].focus()
for char in "Hello":
text_inputs[0].handle_key(char)
print(f"First field contains: '{text_inputs[0].text}'")
# Test second field
print("\nTesting second input field...")
text_inputs[1].focus()
for char in "World":
text_inputs[1].handle_key(char)
print(f"Second field contains: '{text_inputs[1].text}'")
# Test text operations
print("\nTesting cursor movement and deletion...")
text_inputs[1].handle_key("Home")
text_inputs[1].handle_key("Delete")
print(f"After delete at start: '{text_inputs[1].text}'")
text_inputs[1].handle_key("End")
text_inputs[1].handle_key("BackSpace")
print(f"After backspace at end: '{text_inputs[1].text}'")
print("\n=== Demo Complete! ===")
print("Text input widget is working successfully!")
print("Features demonstrated:")
print(" - Text entry")
print(" - Focus management (blue outline)")
print(" - Cursor positioning")
print(" - Delete/Backspace operations")
sys.exit(0)
def create_scene():
"""Create the demo scene"""
global text_inputs
mcrfpy.createScene("demo")
scene = mcrfpy.sceneUI("demo")
# Background
bg = mcrfpy.Frame(0, 0, 800, 600)
bg.fill_color = (40, 40, 40, 255)
scene.append(bg)
# Title
title = mcrfpy.Caption("Text Input Widget Demo", 10, 10)
title.color = (255, 255, 255, 255)
scene.append(title)
# Create input fields
input1 = TextInput(50, 100, 300, "Name:")
input1.add_to_scene(scene)
text_inputs.append(input1)
input2 = TextInput(50, 160, 300, "Email:")
input2.add_to_scene(scene)
text_inputs.append(input2)
input3 = TextInput(50, 220, 400, "Comment:")
input3.add_to_scene(scene)
text_inputs.append(input3)
# Status text
status = mcrfpy.Caption("Click to focus, type to enter text", 50, 280)
status.color = (200, 200, 200, 255)
scene.append(status)
# Keyboard handler
def handle_keys(scene_name, key):
global current_focus, text_inputs
# Tab to switch fields
if key == "Tab" and current_focus:
idx = text_inputs.index(current_focus)
next_idx = (idx + 1) % len(text_inputs)
text_inputs[next_idx]._on_click(0, 0, 1)
else:
# Pass to focused input
if current_focus:
current_focus.handle_key(key)
# Update status
texts = [inp.text for inp in text_inputs]
status.text = f"Values: {texts[0]} | {texts[1]} | {texts[2]}"
mcrfpy.keypressScene("demo", handle_keys)
mcrfpy.setScene("demo")
# Schedule test
mcrfpy.setTimer("test", demo_test, 500)
if __name__ == "__main__":
print("Starting simple text input demo...")
create_scene()

View File

@ -0,0 +1,177 @@
#!/usr/bin/env python3
"""
McRogueFace Animation Sizzle Reel - Final Version
=================================================
Complete demonstration of all animation capabilities.
This version works properly with the game loop and avoids API issues.
"""
import mcrfpy
# Configuration
DEMO_DURATION = 4.0 # Duration for each demo
# All available easing functions
EASING_FUNCTIONS = [
"linear", "easeIn", "easeOut", "easeInOut",
"easeInQuad", "easeOutQuad", "easeInOutQuad",
"easeInCubic", "easeOutCubic", "easeInOutCubic",
"easeInQuart", "easeOutQuart", "easeInOutQuart",
"easeInSine", "easeOutSine", "easeInOutSine",
"easeInExpo", "easeOutExpo", "easeInOutExpo",
"easeInCirc", "easeOutCirc", "easeInOutCirc",
"easeInElastic", "easeOutElastic", "easeInOutElastic",
"easeInBack", "easeOutBack", "easeInOutBack",
"easeInBounce", "easeOutBounce", "easeInOutBounce"
]
# Track demo state
current_demo = 0
subtitle = None
def create_scene():
"""Create the demo scene"""
mcrfpy.createScene("demo")
mcrfpy.setScene("demo")
ui = mcrfpy.sceneUI("demo")
# Title
title = mcrfpy.Caption("Animation Sizzle Reel", 500, 20)
title.fill_color = mcrfpy.Color(255, 255, 0)
title.outline = 2
ui.append(title)
# Subtitle
global subtitle
subtitle = mcrfpy.Caption("Starting...", 450, 60)
subtitle.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(subtitle)
return ui
def demo1_frame_animations():
"""Frame position, size, and color animations"""
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 1: Frame Animations"
# Create frame
f = mcrfpy.Frame(100, 150, 200, 100)
f.fill_color = mcrfpy.Color(50, 50, 150)
f.outline = 3
f.outline_color = mcrfpy.Color(255, 255, 255)
ui.append(f)
# Animate properties
mcrfpy.Animation("x", 600.0, 2.0, "easeInOutBack").start(f)
mcrfpy.Animation("y", 300.0, 2.0, "easeInOutElastic").start(f)
mcrfpy.Animation("w", 300.0, 2.5, "easeInOutCubic").start(f)
mcrfpy.Animation("h", 150.0, 2.5, "easeInOutCubic").start(f)
mcrfpy.Animation("fill_color", (255, 100, 50, 200), 3.0, "easeInOutSine").start(f)
mcrfpy.Animation("outline", 8.0, 3.0, "easeInOutQuad").start(f)
def demo2_caption_animations():
"""Caption movement and text effects"""
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 2: Caption Animations"
# Moving caption
c1 = mcrfpy.Caption("Bouncing Text!", 100, 200)
c1.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(c1)
mcrfpy.Animation("x", 800.0, 3.0, "easeOutBounce").start(c1)
# Color cycling
c2 = mcrfpy.Caption("Color Cycle", 400, 300)
c2.outline = 2
ui.append(c2)
mcrfpy.Animation("fill_color", (255, 0, 0, 255), 1.0, "linear").start(c2)
# Typewriter effect
c3 = mcrfpy.Caption("", 100, 400)
c3.fill_color = mcrfpy.Color(0, 255, 255)
ui.append(c3)
mcrfpy.Animation("text", "Typewriter effect animation...", 3.0, "linear").start(c3)
def demo3_easing_showcase():
"""Show all 30 easing functions"""
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 3: All 30 Easing Functions"
# Create a small frame for each easing
for i, easing in enumerate(EASING_FUNCTIONS[:15]): # First 15
row = i // 5
col = i % 5
x = 100 + col * 200
y = 150 + row * 100
# Frame
f = mcrfpy.Frame(x, y, 20, 20)
f.fill_color = mcrfpy.Color(100, 150, 255)
ui.append(f)
# Label
label = mcrfpy.Caption(easing[:10], x, y - 20)
label.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(label)
# Animate with this easing
mcrfpy.Animation("x", float(x + 150), 3.0, easing).start(f)
def demo4_performance():
"""Many simultaneous animations"""
ui = mcrfpy.sceneUI("demo")
subtitle.text = "Demo 4: 50+ Simultaneous Animations"
for i in range(50):
x = 100 + (i % 10) * 100
y = 150 + (i // 10) * 100
f = mcrfpy.Frame(x, y, 30, 30)
f.fill_color = mcrfpy.Color((i*37)%256, (i*73)%256, (i*113)%256)
ui.append(f)
# Animate to random position
target_x = 150 + (i % 8) * 110
target_y = 200 + (i // 8) * 90
easing = EASING_FUNCTIONS[i % len(EASING_FUNCTIONS)]
mcrfpy.Animation("x", float(target_x), 2.5, easing).start(f)
mcrfpy.Animation("y", float(target_y), 2.5, easing).start(f)
mcrfpy.Animation("opacity", 0.3 + (i%7)*0.1, 2.0, "easeInOutSine").start(f)
def clear_demo_objects():
"""Clear scene except title and subtitle"""
ui = mcrfpy.sceneUI("demo")
# Keep removing items after the first 2 (title and subtitle)
while len(ui) > 2:
# Remove the last item
ui.remove(ui[len(ui)-1])
def next_demo(runtime):
"""Run the next demo"""
global current_demo
clear_demo_objects()
demos = [
demo1_frame_animations,
demo2_caption_animations,
demo3_easing_showcase,
demo4_performance
]
if current_demo < len(demos):
demos[current_demo]()
current_demo += 1
if current_demo < len(demos):
mcrfpy.setTimer("next", next_demo, int(DEMO_DURATION * 1000))
else:
subtitle.text = "Demo Complete!"
# Initialize
print("Starting Animation Sizzle Reel...")
create_scene()
mcrfpy.setTimer("start", next_demo, 500)

View File

@ -0,0 +1,149 @@
#!/usr/bin/env python3
"""
Text Input Demo with Auto-Test
Demonstrates the text input widget system with automated testing
"""
import mcrfpy
from mcrfpy import automation
import sys
from text_input_widget import FocusManager, TextInput
def test_text_input(timer_name):
"""Automated test that runs after scene is loaded"""
print("Testing text input widget system...")
# Take a screenshot of the initial state
automation.screenshot("text_input_initial.png")
# Simulate typing in the first field
print("Clicking on first field...")
automation.click(200, 130) # Click on name field
# Type some text
for char in "John Doe":
mcrfpy.keypressScene("text_input_demo", char)
# Tab to next field
mcrfpy.keypressScene("text_input_demo", "Tab")
# Type email
for char in "john@example.com":
mcrfpy.keypressScene("text_input_demo", char)
# Tab to comment field
mcrfpy.keypressScene("text_input_demo", "Tab")
# Type comment
for char in "Testing the widget!":
mcrfpy.keypressScene("text_input_demo", char)
# Take final screenshot
automation.screenshot("text_input_filled.png")
print("Text input test complete!")
print("Screenshots saved: text_input_initial.png, text_input_filled.png")
# Exit after test
sys.exit(0)
def create_demo():
"""Create a demo scene with multiple text input fields"""
mcrfpy.createScene("text_input_demo")
scene = mcrfpy.sceneUI("text_input_demo")
# Create background
bg = mcrfpy.Frame(0, 0, 800, 600)
bg.fill_color = (40, 40, 40, 255)
scene.append(bg)
# Title
title = mcrfpy.Caption(10, 10, "Text Input Widget Demo - Auto Test", font_size=24)
title.color = (255, 255, 255, 255)
scene.append(title)
# Instructions
instructions = mcrfpy.Caption(10, 50, "This will automatically test the text input system", font_size=14)
instructions.color = (200, 200, 200, 255)
scene.append(instructions)
# Create focus manager
focus_manager = FocusManager()
# Create text input fields
fields = []
# Name field
name_input = TextInput(50, 120, 300, "Name:", 16)
name_input._focus_manager = focus_manager
focus_manager.register(name_input)
scene.append(name_input.frame)
if hasattr(name_input, 'label_text'):
scene.append(name_input.label_text)
scene.append(name_input.text_display)
scene.append(name_input.cursor)
fields.append(name_input)
# Email field
email_input = TextInput(50, 180, 300, "Email:", 16)
email_input._focus_manager = focus_manager
focus_manager.register(email_input)
scene.append(email_input.frame)
if hasattr(email_input, 'label_text'):
scene.append(email_input.label_text)
scene.append(email_input.text_display)
scene.append(email_input.cursor)
fields.append(email_input)
# Comment field
comment_input = TextInput(50, 240, 400, "Comment:", 16)
comment_input._focus_manager = focus_manager
focus_manager.register(comment_input)
scene.append(comment_input.frame)
if hasattr(comment_input, 'label_text'):
scene.append(comment_input.label_text)
scene.append(comment_input.text_display)
scene.append(comment_input.cursor)
fields.append(comment_input)
# Result display
result_text = mcrfpy.Caption(50, 320, "Values will appear here as you type...", font_size=14)
result_text.color = (150, 255, 150, 255)
scene.append(result_text)
def update_result(*args):
"""Update the result display with current field values"""
name = fields[0].get_text()
email = fields[1].get_text()
comment = fields[2].get_text()
result_text.text = f"Name: {name} | Email: {email} | Comment: {comment}"
# Set change handlers
for field in fields:
field.on_change = update_result
# Keyboard handler
def handle_keys(scene_name, key):
"""Global keyboard handler"""
# Let focus manager handle the key first
if not focus_manager.handle_key(key):
# Handle focus switching
if key == "Tab":
focus_manager.focus_next()
elif key == "Escape":
print("Demo terminated by user")
sys.exit(0)
mcrfpy.keypressScene("text_input_demo", handle_keys)
# Set the scene
mcrfpy.setScene("text_input_demo")
# Schedule the automated test
mcrfpy.setTimer("test", test_text_input, 500) # Run test after 500ms
if __name__ == "__main__":
create_demo()

View File

@ -0,0 +1,322 @@
#!/usr/bin/env python3
"""
Standalone Text Input Widget System for McRogueFace
Complete implementation with demo and automated test
"""
import mcrfpy
import sys
class FocusManager:
"""Manages focus state across multiple widgets"""
def __init__(self):
self.widgets = []
self.focused_widget = None
self.focus_index = -1
def register(self, widget):
"""Register a widget with the focus manager"""
self.widgets.append(widget)
if self.focused_widget is None:
self.focus(widget)
def focus(self, widget):
"""Set focus to a specific widget"""
if self.focused_widget:
self.focused_widget.on_blur()
self.focused_widget = widget
self.focus_index = self.widgets.index(widget) if widget in self.widgets else -1
if widget:
widget.on_focus()
def focus_next(self):
"""Focus the next widget in the list"""
if not self.widgets:
return
self.focus_index = (self.focus_index + 1) % len(self.widgets)
self.focus(self.widgets[self.focus_index])
def handle_key(self, key):
"""Route key events to focused widget. Returns True if handled."""
if self.focused_widget:
return self.focused_widget.handle_key(key)
return False
class TextInput:
"""A text input widget with cursor support"""
def __init__(self, x, y, width, label="", font_size=16):
self.x = x
self.y = y
self.width = width
self.label = label
self.font_size = font_size
# Text state
self.text = ""
self.cursor_pos = 0
# Visual state
self.focused = False
# Create UI elements
self._create_ui()
def _create_ui(self):
"""Create the visual components"""
# Background frame
self.frame = mcrfpy.Frame(self.x, self.y, self.width, self.font_size + 8)
self.frame.outline = 2
self.frame.fill_color = (255, 255, 255, 255)
self.frame.outline_color = (128, 128, 128, 255)
# Label (if provided)
if self.label:
self.label_text = mcrfpy.Caption(
self.x - 5,
self.y - self.font_size - 5,
self.label,
font_size=self.font_size
)
self.label_text.color = (255, 255, 255, 255)
# Text display
self.text_display = mcrfpy.Caption(
self.x + 4,
self.y + 4,
"",
font_size=self.font_size
)
self.text_display.color = (0, 0, 0, 255)
# Cursor (using a thin frame)
self.cursor = mcrfpy.Frame(
self.x + 4,
self.y + 4,
2,
self.font_size
)
self.cursor.fill_color = (0, 0, 0, 255)
self.cursor.visible = False
# Click handler
self.frame.click = self._on_click
def _on_click(self, x, y, button):
"""Handle mouse clicks on the input field"""
if button == 1: # Left click
if hasattr(self, '_focus_manager'):
self._focus_manager.focus(self)
def on_focus(self):
"""Called when this widget receives focus"""
self.focused = True
self.frame.outline_color = (0, 120, 255, 255)
self.frame.outline = 3
self.cursor.visible = True
self._update_cursor_position()
def on_blur(self):
"""Called when this widget loses focus"""
self.focused = False
self.frame.outline_color = (128, 128, 128, 255)
self.frame.outline = 2
self.cursor.visible = False
def handle_key(self, key):
"""Handle keyboard input. Returns True if key was handled."""
if not self.focused:
return False
handled = True
# Special keys
if key == "BackSpace":
if self.cursor_pos > 0:
self.text = self.text[:self.cursor_pos-1] + self.text[self.cursor_pos:]
self.cursor_pos -= 1
elif key == "Delete":
if self.cursor_pos < len(self.text):
self.text = self.text[:self.cursor_pos] + self.text[self.cursor_pos+1:]
elif key == "Left":
self.cursor_pos = max(0, self.cursor_pos - 1)
elif key == "Right":
self.cursor_pos = min(len(self.text), self.cursor_pos + 1)
elif key == "Home":
self.cursor_pos = 0
elif key == "End":
self.cursor_pos = len(self.text)
elif key == "Tab":
handled = False # Let focus manager handle
elif len(key) == 1 and key.isprintable():
# Regular character input
self.text = self.text[:self.cursor_pos] + key + self.text[self.cursor_pos:]
self.cursor_pos += 1
else:
handled = False
# Update display
self._update_display()
return handled
def _update_display(self):
"""Update the text display and cursor position"""
self.text_display.text = self.text
self._update_cursor_position()
def _update_cursor_position(self):
"""Update cursor visual position based on text position"""
if not self.focused:
return
# Simple character width estimation (monospace assumption)
char_width = self.font_size * 0.6
cursor_x = self.x + 4 + int(self.cursor_pos * char_width)
self.cursor.x = cursor_x
def get_text(self):
"""Get the current text content"""
return self.text
def add_to_scene(self, scene):
"""Add all components to a scene"""
scene.append(self.frame)
if hasattr(self, 'label_text'):
scene.append(self.label_text)
scene.append(self.text_display)
scene.append(self.cursor)
def run_automated_test(timer_name):
"""Automated test that demonstrates the text input functionality"""
print("\n=== Running Text Input Widget Test ===")
# Take initial screenshot
if hasattr(mcrfpy, 'automation'):
mcrfpy.automation.screenshot("text_input_test_1_initial.png")
print("Screenshot 1: Initial state saved")
# Simulate some typing
print("Simulating keyboard input...")
# The scene's keyboard handler will process these
test_sequence = [
("H", "Typing 'H'"),
("e", "Typing 'e'"),
("l", "Typing 'l'"),
("l", "Typing 'l'"),
("o", "Typing 'o'"),
("Tab", "Switching to next field"),
("T", "Typing 'T'"),
("e", "Typing 'e'"),
("s", "Typing 's'"),
("t", "Typing 't'"),
("Tab", "Switching to comment field"),
("W", "Typing 'W'"),
("o", "Typing 'o'"),
("r", "Typing 'r'"),
("k", "Typing 'k'"),
("s", "Typing 's'"),
("!", "Typing '!'"),
]
# Process each key
for key, desc in test_sequence:
print(f" - {desc}")
# Trigger the scene's keyboard handler
if hasattr(mcrfpy, '_scene_key_handler'):
mcrfpy._scene_key_handler("text_input_demo", key)
# Take final screenshot
if hasattr(mcrfpy, 'automation'):
mcrfpy.automation.screenshot("text_input_test_2_filled.png")
print("Screenshot 2: Filled state saved")
print("\n=== Text Input Test Complete! ===")
print("The text input widget system is working correctly.")
print("Features demonstrated:")
print(" - Focus management (blue outline on focused field)")
print(" - Text entry with cursor")
print(" - Tab navigation between fields")
print(" - Visual feedback")
# Exit successfully
sys.exit(0)
def create_demo():
"""Create the demo scene"""
mcrfpy.createScene("text_input_demo")
scene = mcrfpy.sceneUI("text_input_demo")
# Create background
bg = mcrfpy.Frame(0, 0, 800, 600)
bg.fill_color = (40, 40, 40, 255)
scene.append(bg)
# Title
title = mcrfpy.Caption(10, 10, "Text Input Widget System", font_size=24)
title.color = (255, 255, 255, 255)
scene.append(title)
# Instructions
info = mcrfpy.Caption(10, 50, "Click to focus | Tab to switch fields | Type to enter text", font_size=14)
info.color = (200, 200, 200, 255)
scene.append(info)
# Create focus manager
focus_manager = FocusManager()
# Create text inputs
name_input = TextInput(50, 120, 300, "Name:", 16)
name_input._focus_manager = focus_manager
focus_manager.register(name_input)
name_input.add_to_scene(scene)
email_input = TextInput(50, 180, 300, "Email:", 16)
email_input._focus_manager = focus_manager
focus_manager.register(email_input)
email_input.add_to_scene(scene)
comment_input = TextInput(50, 240, 400, "Comment:", 16)
comment_input._focus_manager = focus_manager
focus_manager.register(comment_input)
comment_input.add_to_scene(scene)
# Status display
status = mcrfpy.Caption(50, 320, "Ready for input...", font_size=14)
status.color = (150, 255, 150, 255)
scene.append(status)
# Store references for the keyboard handler
widgets = [name_input, email_input, comment_input]
# Keyboard handler
def handle_keys(scene_name, key):
"""Global keyboard handler"""
if not focus_manager.handle_key(key):
if key == "Tab":
focus_manager.focus_next()
# Update status
texts = [w.get_text() for w in widgets]
status.text = f"Name: '{texts[0]}' | Email: '{texts[1]}' | Comment: '{texts[2]}'"
# Store handler reference for test
mcrfpy._scene_key_handler = handle_keys
mcrfpy.keypressScene("text_input_demo", handle_keys)
mcrfpy.setScene("text_input_demo")
# Schedule automated test
mcrfpy.setTimer("test", run_automated_test, 1000) # Run after 1 second
if __name__ == "__main__":
print("Starting Text Input Widget Demo...")
create_demo()

View File

@ -0,0 +1,322 @@
#!/usr/bin/env python3
"""
Text Input Widget System for McRogueFace
A pure Python implementation of focusable text input fields
"""
import mcrfpy
import sys
from dataclasses import dataclass
from typing import Optional, List, Callable
class FocusManager:
"""Manages focus state across multiple widgets"""
def __init__(self):
self.widgets: List['TextInput'] = []
self.focused_widget: Optional['TextInput'] = None
self.focus_index: int = -1
def register(self, widget: 'TextInput'):
"""Register a widget with the focus manager"""
self.widgets.append(widget)
if self.focused_widget is None:
self.focus(widget)
def focus(self, widget: 'TextInput'):
"""Set focus to a specific widget"""
if self.focused_widget:
self.focused_widget.on_blur()
self.focused_widget = widget
self.focus_index = self.widgets.index(widget) if widget in self.widgets else -1
if widget:
widget.on_focus()
def focus_next(self):
"""Focus the next widget in the list"""
if not self.widgets:
return
self.focus_index = (self.focus_index + 1) % len(self.widgets)
self.focus(self.widgets[self.focus_index])
def focus_prev(self):
"""Focus the previous widget in the list"""
if not self.widgets:
return
self.focus_index = (self.focus_index - 1) % len(self.widgets)
self.focus(self.widgets[self.focus_index])
def handle_key(self, key: str) -> bool:
"""Route key events to focused widget. Returns True if handled."""
if self.focused_widget:
return self.focused_widget.handle_key(key)
return False
class TextInput:
"""A text input widget with cursor and selection support"""
def __init__(self, x: int, y: int, width: int = 200, label: str = "",
font_size: int = 16, on_change: Optional[Callable] = None):
self.x = x
self.y = y
self.width = width
self.label = label
self.font_size = font_size
self.on_change = on_change
# Text state
self.text = ""
self.cursor_pos = 0
self.selection_start = -1
self.selection_end = -1
# Visual state
self.focused = False
self.cursor_visible = True
self.cursor_blink_timer = 0
# Create UI elements
self._create_ui()
def _create_ui(self):
"""Create the visual components"""
# Background frame
self.frame = mcrfpy.Frame(self.x, self.y, self.width, self.font_size + 8)
self.frame.outline = 2
self.frame.fill_color = (255, 255, 255, 255)
self.frame.outline_color = (128, 128, 128, 255)
# Label (if provided)
if self.label:
self.label_text = mcrfpy.Caption(
self.x - 5,
self.y - self.font_size - 5,
self.label,
font_size=self.font_size
)
self.label_text.color = (255, 255, 255, 255)
# Text display
self.text_display = mcrfpy.Caption(
self.x + 4,
self.y + 4,
"",
font_size=self.font_size
)
self.text_display.color = (0, 0, 0, 255)
# Cursor (using a thin frame)
self.cursor = mcrfpy.Frame(
self.x + 4,
self.y + 4,
2,
self.font_size
)
self.cursor.fill_color = (0, 0, 0, 255)
self.cursor.visible = False
# Click handler
self.frame.click = self._on_click
def _on_click(self, x: int, y: int, button: int):
"""Handle mouse clicks on the input field"""
if button == 1: # Left click
# Request focus through the focus manager
if hasattr(self, '_focus_manager'):
self._focus_manager.focus(self)
def on_focus(self):
"""Called when this widget receives focus"""
self.focused = True
self.frame.outline_color = (0, 120, 255, 255)
self.frame.outline = 3
self.cursor.visible = True
self._update_cursor_position()
def on_blur(self):
"""Called when this widget loses focus"""
self.focused = False
self.frame.outline_color = (128, 128, 128, 255)
self.frame.outline = 2
self.cursor.visible = False
def handle_key(self, key: str) -> bool:
"""Handle keyboard input. Returns True if key was handled."""
if not self.focused:
return False
handled = True
old_text = self.text
# Special keys
if key == "BackSpace":
if self.cursor_pos > 0:
self.text = self.text[:self.cursor_pos-1] + self.text[self.cursor_pos:]
self.cursor_pos -= 1
elif key == "Delete":
if self.cursor_pos < len(self.text):
self.text = self.text[:self.cursor_pos] + self.text[self.cursor_pos+1:]
elif key == "Left":
self.cursor_pos = max(0, self.cursor_pos - 1)
elif key == "Right":
self.cursor_pos = min(len(self.text), self.cursor_pos + 1)
elif key == "Home":
self.cursor_pos = 0
elif key == "End":
self.cursor_pos = len(self.text)
elif key == "Return":
handled = False # Let parent handle submit
elif key == "Tab":
handled = False # Let focus manager handle
elif len(key) == 1 and key.isprintable():
# Regular character input
self.text = self.text[:self.cursor_pos] + key + self.text[self.cursor_pos:]
self.cursor_pos += 1
else:
handled = False
# Update display
if old_text != self.text:
self._update_display()
if self.on_change:
self.on_change(self.text)
else:
self._update_cursor_position()
return handled
def _update_display(self):
"""Update the text display and cursor position"""
self.text_display.text = self.text
self._update_cursor_position()
def _update_cursor_position(self):
"""Update cursor visual position based on text position"""
if not self.focused:
return
# Simple character width estimation (monospace assumption)
char_width = self.font_size * 0.6
cursor_x = self.x + 4 + int(self.cursor_pos * char_width)
self.cursor.x = cursor_x
def set_text(self, text: str):
"""Set the text content"""
self.text = text
self.cursor_pos = len(text)
self._update_display()
def get_text(self) -> str:
"""Get the current text content"""
return self.text
# Demo application
def create_demo():
"""Create a demo scene with multiple text input fields"""
mcrfpy.createScene("text_input_demo")
scene = mcrfpy.sceneUI("text_input_demo")
# Create background
bg = mcrfpy.Frame(0, 0, 800, 600)
bg.fill_color = (40, 40, 40, 255)
scene.append(bg)
# Title
title = mcrfpy.Caption(10, 10, "Text Input Widget Demo", font_size=24)
title.color = (255, 255, 255, 255)
scene.append(title)
# Instructions
instructions = mcrfpy.Caption(10, 50, "Click to focus, Tab to switch fields, Type to enter text", font_size=14)
instructions.color = (200, 200, 200, 255)
scene.append(instructions)
# Create focus manager
focus_manager = FocusManager()
# Create text input fields
fields = []
# Name field
name_input = TextInput(50, 120, 300, "Name:", 16)
name_input._focus_manager = focus_manager
focus_manager.register(name_input)
scene.append(name_input.frame)
if hasattr(name_input, 'label_text'):
scene.append(name_input.label_text)
scene.append(name_input.text_display)
scene.append(name_input.cursor)
fields.append(name_input)
# Email field
email_input = TextInput(50, 180, 300, "Email:", 16)
email_input._focus_manager = focus_manager
focus_manager.register(email_input)
scene.append(email_input.frame)
if hasattr(email_input, 'label_text'):
scene.append(email_input.label_text)
scene.append(email_input.text_display)
scene.append(email_input.cursor)
fields.append(email_input)
# Comment field
comment_input = TextInput(50, 240, 400, "Comment:", 16)
comment_input._focus_manager = focus_manager
focus_manager.register(comment_input)
scene.append(comment_input.frame)
if hasattr(comment_input, 'label_text'):
scene.append(comment_input.label_text)
scene.append(comment_input.text_display)
scene.append(comment_input.cursor)
fields.append(comment_input)
# Result display
result_text = mcrfpy.Caption(50, 320, "Type in the fields above...", font_size=14)
result_text.color = (150, 255, 150, 255)
scene.append(result_text)
def update_result(*args):
"""Update the result display with current field values"""
name = fields[0].get_text()
email = fields[1].get_text()
comment = fields[2].get_text()
result_text.text = f"Name: {name} | Email: {email} | Comment: {comment}"
# Set change handlers
for field in fields:
field.on_change = update_result
# Keyboard handler
def handle_keys(scene_name, key):
"""Global keyboard handler"""
# Let focus manager handle the key first
if not focus_manager.handle_key(key):
# Handle focus switching
if key == "Tab":
focus_manager.focus_next()
elif key == "Escape":
print("Demo complete!")
sys.exit(0)
mcrfpy.keypressScene("text_input_demo", handle_keys)
# Set the scene
mcrfpy.setScene("text_input_demo")
# Add a timer for cursor blinking (optional enhancement)
def blink_cursor(timer_name):
"""Blink the cursor for the focused widget"""
if focus_manager.focused_widget and focus_manager.focused_widget.focused:
cursor = focus_manager.focused_widget.cursor
cursor.visible = not cursor.visible
mcrfpy.setTimer("cursor_blink", blink_cursor, 500) # Blink every 500ms
if __name__ == "__main__":
create_demo()

View File

@ -0,0 +1,235 @@
#!/usr/bin/env python3
"""
A* vs Dijkstra Visual Comparison
=================================
Shows the difference between A* (single target) and Dijkstra (multi-target).
"""
import mcrfpy
import sys
# Colors
WALL_COLOR = mcrfpy.Color(40, 20, 20)
FLOOR_COLOR = mcrfpy.Color(60, 60, 80)
ASTAR_COLOR = mcrfpy.Color(0, 255, 0) # Green for A*
DIJKSTRA_COLOR = mcrfpy.Color(0, 150, 255) # Blue for Dijkstra
START_COLOR = mcrfpy.Color(255, 100, 100) # Red for start
END_COLOR = mcrfpy.Color(255, 255, 100) # Yellow for end
# Global state
grid = None
mode = "ASTAR"
start_pos = (5, 10)
end_pos = (27, 10) # Changed from 25 to 27 to avoid the wall
def create_map():
"""Create a map with obstacles to show pathfinding differences"""
global grid
mcrfpy.createScene("pathfinding_comparison")
# Create grid
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
grid.fill_color = mcrfpy.Color(0, 0, 0)
# Initialize all as floor
for y in range(20):
for x in range(30):
grid.at(x, y).walkable = True
grid.at(x, y).color = FLOOR_COLOR
# Create obstacles that make A* and Dijkstra differ
obstacles = [
# Vertical wall with gaps
[(15, y) for y in range(3, 17) if y not in [8, 12]],
# Horizontal walls
[(x, 5) for x in range(10, 20)],
[(x, 15) for x in range(10, 20)],
# Maze-like structure
[(x, 10) for x in range(20, 25)],
[(25, y) for y in range(5, 15)],
]
for obstacle_group in obstacles:
for x, y in obstacle_group:
grid.at(x, y).walkable = False
grid.at(x, y).color = WALL_COLOR
# Mark start and end
grid.at(start_pos[0], start_pos[1]).color = START_COLOR
grid.at(end_pos[0], end_pos[1]).color = END_COLOR
def clear_paths():
"""Clear path highlighting"""
for y in range(20):
for x in range(30):
cell = grid.at(x, y)
if cell.walkable:
cell.color = FLOOR_COLOR
# Restore start and end colors
grid.at(start_pos[0], start_pos[1]).color = START_COLOR
grid.at(end_pos[0], end_pos[1]).color = END_COLOR
def show_astar():
"""Show A* path"""
clear_paths()
# Compute A* path
path = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
# Color the path
for i, (x, y) in enumerate(path):
if (x, y) != start_pos and (x, y) != end_pos:
grid.at(x, y).color = ASTAR_COLOR
status_text.text = f"A* Path: {len(path)} steps (optimized for single target)"
status_text.fill_color = ASTAR_COLOR
def show_dijkstra():
"""Show Dijkstra exploration"""
clear_paths()
# Compute Dijkstra from start
grid.compute_dijkstra(start_pos[0], start_pos[1])
# Color cells by distance (showing exploration)
max_dist = 40.0
for y in range(20):
for x in range(30):
if grid.at(x, y).walkable:
dist = grid.get_dijkstra_distance(x, y)
if dist is not None and dist < max_dist:
# Color based on distance
intensity = int(255 * (1 - dist / max_dist))
grid.at(x, y).color = mcrfpy.Color(0, intensity // 2, intensity)
# Get the actual path
path = grid.get_dijkstra_path(end_pos[0], end_pos[1])
# Highlight the actual path more brightly
for x, y in path:
if (x, y) != start_pos and (x, y) != end_pos:
grid.at(x, y).color = DIJKSTRA_COLOR
# Restore start and end
grid.at(start_pos[0], start_pos[1]).color = START_COLOR
grid.at(end_pos[0], end_pos[1]).color = END_COLOR
status_text.text = f"Dijkstra: {len(path)} steps (explores all directions)"
status_text.fill_color = DIJKSTRA_COLOR
def show_both():
"""Show both paths overlaid"""
clear_paths()
# Get both paths
astar_path = grid.compute_astar_path(start_pos[0], start_pos[1], end_pos[0], end_pos[1])
grid.compute_dijkstra(start_pos[0], start_pos[1])
dijkstra_path = grid.get_dijkstra_path(end_pos[0], end_pos[1])
print(astar_path, dijkstra_path)
# Color Dijkstra path first (blue)
for x, y in dijkstra_path:
if (x, y) != start_pos and (x, y) != end_pos:
grid.at(x, y).color = DIJKSTRA_COLOR
# Then A* path (green) - will overwrite shared cells
for x, y in astar_path:
if (x, y) != start_pos and (x, y) != end_pos:
grid.at(x, y).color = ASTAR_COLOR
# Mark differences
different_cells = []
for cell in dijkstra_path:
if cell not in astar_path:
different_cells.append(cell)
status_text.text = f"Both paths: A*={len(astar_path)} steps, Dijkstra={len(dijkstra_path)} steps"
if different_cells:
info_text.text = f"Paths differ at {len(different_cells)} cells"
else:
info_text.text = "Paths are identical"
def handle_keypress(key_str, state):
"""Handle keyboard input"""
global mode
if state == "end": return
print(key_str)
if key_str == "Esc" or key_str == "Q":
print("\nExiting...")
sys.exit(0)
elif key_str == "A" or key_str == "1":
mode = "ASTAR"
show_astar()
elif key_str == "D" or key_str == "2":
mode = "DIJKSTRA"
show_dijkstra()
elif key_str == "B" or key_str == "3":
mode = "BOTH"
show_both()
elif key_str == "Space":
# Refresh current mode
if mode == "ASTAR":
show_astar()
elif mode == "DIJKSTRA":
show_dijkstra()
else:
show_both()
# Create the demo
print("A* vs Dijkstra Pathfinding Comparison")
print("=====================================")
print("Controls:")
print(" A or 1 - Show A* path (green)")
print(" D or 2 - Show Dijkstra (blue gradient)")
print(" B or 3 - Show both paths")
print(" Q/ESC - Quit")
print()
print("A* is optimized for single-target pathfinding")
print("Dijkstra explores in all directions (good for multiple targets)")
create_map()
# Set up UI
ui = mcrfpy.sceneUI("pathfinding_comparison")
ui.append(grid)
# Scale and position
grid.size = (600, 400) # 30*20, 20*20
grid.position = (100, 100)
# Add title
title = mcrfpy.Caption("A* vs Dijkstra Pathfinding", 250, 20)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add status
status_text = mcrfpy.Caption("Press A for A*, D for Dijkstra, B for Both", 100, 60)
status_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(status_text)
# Add info
info_text = mcrfpy.Caption("", 100, 520)
info_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(info_text)
# Add legend
legend1 = mcrfpy.Caption("Red=Start, Yellow=End, Green=A*, Blue=Dijkstra", 100, 540)
legend1.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend1)
legend2 = mcrfpy.Caption("Dark=Walls, Light=Floor", 100, 560)
legend2.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend2)
# Set scene and input
mcrfpy.setScene("pathfinding_comparison")
mcrfpy.keypressScene(handle_keypress)
# Show initial A* path
show_astar()
print("\nDemo ready!")

View File

@ -0,0 +1,59 @@
#!/usr/bin/env python3
"""Debug visibility crash"""
import mcrfpy
import sys
print("Debug visibility...")
# Create scene and grid
mcrfpy.createScene("debug")
grid = mcrfpy.Grid(grid_x=5, grid_y=5)
# Initialize grid
print("Initializing grid...")
for y in range(5):
for x in range(5):
cell = grid.at(x, y)
cell.walkable = True
cell.transparent = True
# Create entity
print("Creating entity...")
entity = mcrfpy.Entity(2, 2)
entity.sprite_index = 64
grid.entities.append(entity)
print(f"Entity at ({entity.x}, {entity.y})")
# Check gridstate
print(f"\nGridstate length: {len(entity.gridstate)}")
print(f"Expected: {5 * 5}")
# Try to access gridstate
print("\nChecking gridstate access...")
try:
if len(entity.gridstate) > 0:
state = entity.gridstate[0]
print(f"First state: visible={state.visible}, discovered={state.discovered}")
except Exception as e:
print(f"Error accessing gridstate: {e}")
# Try update_visibility
print("\nTrying update_visibility...")
try:
entity.update_visibility()
print("update_visibility succeeded")
except Exception as e:
print(f"Error in update_visibility: {e}")
# Try perspective
print("\nTesting perspective...")
print(f"Initial perspective: {grid.perspective}")
try:
grid.perspective = 0
print(f"Set perspective to 0: {grid.perspective}")
except Exception as e:
print(f"Error setting perspective: {e}")
print("\nTest complete")
sys.exit(0)

View File

@ -0,0 +1,234 @@
#!/usr/bin/env python3
"""
Dijkstra Demo - Shows ALL Path Combinations (Including Invalid)
===============================================================
Cycles through every possible entity pair to demonstrate both
valid paths and properly handled invalid paths (empty lists).
"""
import mcrfpy
import sys
# High contrast colors
WALL_COLOR = mcrfpy.Color(40, 20, 20) # Very dark red/brown
FLOOR_COLOR = mcrfpy.Color(60, 60, 80) # Dark blue-gray
PATH_COLOR = mcrfpy.Color(0, 255, 0) # Bright green
START_COLOR = mcrfpy.Color(255, 100, 100) # Light red
END_COLOR = mcrfpy.Color(100, 100, 255) # Light blue
NO_PATH_COLOR = mcrfpy.Color(255, 0, 0) # Pure red for unreachable
# Global state
grid = None
entities = []
current_combo_index = 0
all_combinations = [] # All possible pairs
current_path = []
def create_map():
"""Create the map with entities"""
global grid, entities, all_combinations
mcrfpy.createScene("dijkstra_all")
# Create grid
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
grid.fill_color = mcrfpy.Color(0, 0, 0)
# Map layout - Entity 1 is intentionally trapped!
map_layout = [
"..............", # Row 0
"..W.....WWWW..", # Row 1
"..W.W...W.EW..", # Row 2 - Entity 1 TRAPPED at (10,2)
"..W.....W..W..", # Row 3
"..W...E.WWWW..", # Row 4 - Entity 2 at (6,4)
"E.W...........", # Row 5 - Entity 3 at (0,5)
"..W...........", # Row 6
"..W...........", # Row 7
"..W.WWW.......", # Row 8
"..............", # Row 9
]
# Create the map
entity_positions = []
for y, row in enumerate(map_layout):
for x, char in enumerate(row):
cell = grid.at(x, y)
if char == 'W':
cell.walkable = False
cell.color = WALL_COLOR
else:
cell.walkable = True
cell.color = FLOOR_COLOR
if char == 'E':
entity_positions.append((x, y))
# Create entities
entities = []
for i, (x, y) in enumerate(entity_positions):
entity = mcrfpy.Entity(x, y)
entity.sprite_index = 49 + i # '1', '2', '3'
grid.entities.append(entity)
entities.append(entity)
print("Map Analysis:")
print("=============")
for i, (x, y) in enumerate(entity_positions):
print(f"Entity {i+1} at ({x}, {y})")
# Generate ALL combinations (including invalid ones)
all_combinations = []
for i in range(len(entities)):
for j in range(len(entities)):
if i != j: # Skip self-paths
all_combinations.append((i, j))
print(f"\nTotal path combinations to test: {len(all_combinations)}")
def clear_path_colors():
"""Reset all floor tiles to original color"""
global current_path
for y in range(grid.grid_y):
for x in range(grid.grid_x):
cell = grid.at(x, y)
if cell.walkable:
cell.color = FLOOR_COLOR
current_path = []
def show_combination(index):
"""Show a specific path combination (valid or invalid)"""
global current_combo_index, current_path
current_combo_index = index % len(all_combinations)
from_idx, to_idx = all_combinations[current_combo_index]
# Clear previous path
clear_path_colors()
# Get entities
e_from = entities[from_idx]
e_to = entities[to_idx]
# Calculate path
path = e_from.path_to(int(e_to.x), int(e_to.y))
current_path = path if path else []
# Always color start and end positions
grid.at(int(e_from.x), int(e_from.y)).color = START_COLOR
grid.at(int(e_to.x), int(e_to.y)).color = NO_PATH_COLOR if not path else END_COLOR
# Color the path if it exists
if path:
# Color intermediate steps
for i, (x, y) in enumerate(path):
if i > 0 and i < len(path) - 1:
grid.at(x, y).color = PATH_COLOR
status_text.text = f"Path {current_combo_index + 1}/{len(all_combinations)}: Entity {from_idx+1} → Entity {to_idx+1} = {len(path)} steps"
status_text.fill_color = mcrfpy.Color(100, 255, 100) # Green for valid
# Show path steps
path_display = []
for i, (x, y) in enumerate(path[:5]):
path_display.append(f"({x},{y})")
if len(path) > 5:
path_display.append("...")
path_text.text = "Path: " + "".join(path_display)
else:
status_text.text = f"Path {current_combo_index + 1}/{len(all_combinations)}: Entity {from_idx+1} → Entity {to_idx+1} = NO PATH!"
status_text.fill_color = mcrfpy.Color(255, 100, 100) # Red for invalid
path_text.text = "Path: [] (No valid path exists)"
# Update info
info_text.text = f"From: Entity {from_idx+1} at ({int(e_from.x)}, {int(e_from.y)}) | To: Entity {to_idx+1} at ({int(e_to.x)}, {int(e_to.y)})"
def handle_keypress(key_str, state):
"""Handle keyboard input"""
global current_combo_index
if state == "end": return
if key_str == "Esc" or key_str == "Q":
print("\nExiting...")
sys.exit(0)
elif key_str == "Space" or key_str == "N":
show_combination(current_combo_index + 1)
elif key_str == "P":
show_combination(current_combo_index - 1)
elif key_str == "R":
show_combination(current_combo_index)
elif key_str in "123456":
combo_num = int(key_str) - 1 # 0-based index
if combo_num < len(all_combinations):
show_combination(combo_num)
# Create the demo
print("Dijkstra All Paths Demo")
print("=======================")
print("Shows ALL path combinations including invalid ones")
print("Entity 1 is trapped - paths to/from it will be empty!")
print()
create_map()
# Set up UI
ui = mcrfpy.sceneUI("dijkstra_all")
ui.append(grid)
# Scale and position
grid.size = (560, 400)
grid.position = (120, 100)
# Add title
title = mcrfpy.Caption("Dijkstra - All Paths (Valid & Invalid)", 200, 20)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add status (will change color based on validity)
status_text = mcrfpy.Caption("Ready", 120, 60)
status_text.fill_color = mcrfpy.Color(255, 255, 100)
ui.append(status_text)
# Add info
info_text = mcrfpy.Caption("", 120, 80)
info_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(info_text)
# Add path display
path_text = mcrfpy.Caption("Path: None", 120, 520)
path_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(path_text)
# Add controls
controls = mcrfpy.Caption("SPACE/N=Next, P=Previous, 1-6=Jump to path, Q=Quit", 120, 540)
controls.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(controls)
# Add legend
legend = mcrfpy.Caption("Red Start→Blue End (valid) | Red Start→Red End (invalid)", 120, 560)
legend.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend)
# Expected results info
expected = mcrfpy.Caption("Entity 1 is trapped: paths 1→2, 1→3, 2→1, 3→1 will fail", 120, 580)
expected.fill_color = mcrfpy.Color(255, 150, 150)
ui.append(expected)
# Set scene first, then set up input handler
mcrfpy.setScene("dijkstra_all")
mcrfpy.keypressScene(handle_keypress)
# Show first combination
show_combination(0)
print("\nDemo ready!")
print("Expected results:")
print(" Path 1: Entity 1→2 = NO PATH (Entity 1 is trapped)")
print(" Path 2: Entity 1→3 = NO PATH (Entity 1 is trapped)")
print(" Path 3: Entity 2→1 = NO PATH (Entity 1 is trapped)")
print(" Path 4: Entity 2→3 = Valid path")
print(" Path 5: Entity 3→1 = NO PATH (Entity 1 is trapped)")
print(" Path 6: Entity 3→2 = Valid path")

View File

@ -0,0 +1,236 @@
#!/usr/bin/env python3
"""
Dijkstra Demo - Cycles Through Different Path Combinations
==========================================================
Shows paths between different entity pairs, skipping impossible paths.
"""
import mcrfpy
import sys
# High contrast colors
WALL_COLOR = mcrfpy.Color(40, 20, 20) # Very dark red/brown
FLOOR_COLOR = mcrfpy.Color(60, 60, 80) # Dark blue-gray
PATH_COLOR = mcrfpy.Color(0, 255, 0) # Bright green
START_COLOR = mcrfpy.Color(255, 100, 100) # Light red
END_COLOR = mcrfpy.Color(100, 100, 255) # Light blue
# Global state
grid = None
entities = []
current_path_index = 0
path_combinations = []
current_path = []
def create_map():
"""Create the map with entities"""
global grid, entities
mcrfpy.createScene("dijkstra_cycle")
# Create grid
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
grid.fill_color = mcrfpy.Color(0, 0, 0)
# Map layout
map_layout = [
"..............", # Row 0
"..W.....WWWW..", # Row 1
"..W.W...W.EW..", # Row 2 - Entity 1 at (10,2) is TRAPPED!
"..W.....W..W..", # Row 3
"..W...E.WWWW..", # Row 4 - Entity 2 at (6,4)
"E.W...........", # Row 5 - Entity 3 at (0,5)
"..W...........", # Row 6
"..W...........", # Row 7
"..W.WWW.......", # Row 8
"..............", # Row 9
]
# Create the map
entity_positions = []
for y, row in enumerate(map_layout):
for x, char in enumerate(row):
cell = grid.at(x, y)
if char == 'W':
cell.walkable = False
cell.color = WALL_COLOR
else:
cell.walkable = True
cell.color = FLOOR_COLOR
if char == 'E':
entity_positions.append((x, y))
# Create entities
entities = []
for i, (x, y) in enumerate(entity_positions):
entity = mcrfpy.Entity(x, y)
entity.sprite_index = 49 + i # '1', '2', '3'
grid.entities.append(entity)
entities.append(entity)
print("Entities created:")
for i, (x, y) in enumerate(entity_positions):
print(f" Entity {i+1} at ({x}, {y})")
# Check which entity is trapped
print("\nChecking accessibility:")
for i, e in enumerate(entities):
# Try to path to each other entity
can_reach = []
for j, other in enumerate(entities):
if i != j:
path = e.path_to(int(other.x), int(other.y))
if path:
can_reach.append(j+1)
if not can_reach:
print(f" Entity {i+1} at ({int(e.x)}, {int(e.y)}) is TRAPPED!")
else:
print(f" Entity {i+1} can reach entities: {can_reach}")
# Generate valid path combinations (excluding trapped entity)
global path_combinations
path_combinations = []
# Only paths between entities 2 and 3 (indices 1 and 2) will work
# since entity 1 (index 0) is trapped
if len(entities) >= 3:
# Entity 2 to Entity 3
path = entities[1].path_to(int(entities[2].x), int(entities[2].y))
if path:
path_combinations.append((1, 2, path))
# Entity 3 to Entity 2
path = entities[2].path_to(int(entities[1].x), int(entities[1].y))
if path:
path_combinations.append((2, 1, path))
print(f"\nFound {len(path_combinations)} valid paths")
def clear_path_colors():
"""Reset all floor tiles to original color"""
global current_path
for y in range(grid.grid_y):
for x in range(grid.grid_x):
cell = grid.at(x, y)
if cell.walkable:
cell.color = FLOOR_COLOR
current_path = []
def show_path(index):
"""Show a specific path combination"""
global current_path_index, current_path
if not path_combinations:
status_text.text = "No valid paths available (Entity 1 is trapped!)"
return
current_path_index = index % len(path_combinations)
from_idx, to_idx, path = path_combinations[current_path_index]
# Clear previous path
clear_path_colors()
# Get entities
e_from = entities[from_idx]
e_to = entities[to_idx]
# Color the path
current_path = path
if path:
# Color start and end
grid.at(int(e_from.x), int(e_from.y)).color = START_COLOR
grid.at(int(e_to.x), int(e_to.y)).color = END_COLOR
# Color intermediate steps
for i, (x, y) in enumerate(path):
if i > 0 and i < len(path) - 1:
grid.at(x, y).color = PATH_COLOR
# Update status
status_text.text = f"Path {current_path_index + 1}/{len(path_combinations)}: Entity {from_idx+1} → Entity {to_idx+1} ({len(path)} steps)"
# Update path display
path_display = []
for i, (x, y) in enumerate(path[:5]): # Show first 5 steps
path_display.append(f"({x},{y})")
if len(path) > 5:
path_display.append("...")
path_text.text = "Path: " + "".join(path_display) if path_display else "Path: None"
def handle_keypress(key_str, state):
"""Handle keyboard input"""
global current_path_index
if state == "end": return
if key_str == "Esc":
print("\nExiting...")
sys.exit(0)
elif key_str == "N" or key_str == "Space":
show_path(current_path_index + 1)
elif key_str == "P":
show_path(current_path_index - 1)
elif key_str == "R":
show_path(current_path_index)
# Create the demo
print("Dijkstra Path Cycling Demo")
print("==========================")
print("Note: Entity 1 is trapped by walls!")
print()
create_map()
# Set up UI
ui = mcrfpy.sceneUI("dijkstra_cycle")
ui.append(grid)
# Scale and position
grid.size = (560, 400)
grid.position = (120, 100)
# Add title
title = mcrfpy.Caption("Dijkstra Pathfinding - Cycle Paths", 200, 20)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add status
status_text = mcrfpy.Caption("Press SPACE to cycle paths", 120, 60)
status_text.fill_color = mcrfpy.Color(255, 255, 100)
ui.append(status_text)
# Add path display
path_text = mcrfpy.Caption("Path: None", 120, 520)
path_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(path_text)
# Add controls
controls = mcrfpy.Caption("SPACE/N=Next, P=Previous, R=Refresh, Q=Quit", 120, 540)
controls.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(controls)
# Add legend
legend = mcrfpy.Caption("Red=Start, Blue=End, Green=Path, Dark=Wall", 120, 560)
legend.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend)
# Show first valid path
mcrfpy.setScene("dijkstra_cycle")
mcrfpy.keypressScene(handle_keypress)
# Display initial path
if path_combinations:
show_path(0)
else:
status_text.text = "No valid paths! Entity 1 is trapped!"
print("\nDemo ready!")
print("Controls:")
print(" SPACE or N - Next path")
print(" P - Previous path")
print(" R - Refresh current path")
print(" Q - Quit")

View File

@ -0,0 +1,161 @@
#!/usr/bin/env python3
"""
Debug version of Dijkstra pathfinding to diagnose visualization issues
"""
import mcrfpy
import sys
# Colors
WALL_COLOR = mcrfpy.Color(60, 30, 30)
FLOOR_COLOR = mcrfpy.Color(200, 200, 220)
PATH_COLOR = mcrfpy.Color(200, 250, 220)
ENTITY_COLORS = [
mcrfpy.Color(255, 100, 100), # Entity 1 - Red
mcrfpy.Color(100, 255, 100), # Entity 2 - Green
mcrfpy.Color(100, 100, 255), # Entity 3 - Blue
]
# Global state
grid = None
entities = []
first_point = None
second_point = None
def create_simple_map():
"""Create a simple test map"""
global grid, entities
mcrfpy.createScene("dijkstra_debug")
# Small grid for easy debugging
grid = mcrfpy.Grid(grid_x=10, grid_y=10)
grid.fill_color = mcrfpy.Color(0, 0, 0)
print("Initializing 10x10 grid...")
# Initialize all as floor
for y in range(10):
for x in range(10):
grid.at(x, y).walkable = True
grid.at(x, y).transparent = True
grid.at(x, y).color = FLOOR_COLOR
# Add a simple wall
print("Adding walls at:")
walls = [(5, 2), (5, 3), (5, 4), (5, 5), (5, 6)]
for x, y in walls:
print(f" Wall at ({x}, {y})")
grid.at(x, y).walkable = False
grid.at(x, y).color = WALL_COLOR
# Create 3 entities
entity_positions = [(2, 5), (8, 5), (5, 8)]
entities = []
print("\nCreating entities at:")
for i, (x, y) in enumerate(entity_positions):
print(f" Entity {i+1} at ({x}, {y})")
entity = mcrfpy.Entity(x, y)
entity.sprite_index = 49 + i # '1', '2', '3'
grid.entities.append(entity)
entities.append(entity)
return grid
def test_path_highlighting():
"""Test path highlighting with debug output"""
print("\n" + "="*50)
print("Testing path highlighting...")
# Select first two entities
e1 = entities[0]
e2 = entities[1]
print(f"\nEntity 1 position: ({e1.x}, {e1.y})")
print(f"Entity 2 position: ({e2.x}, {e2.y})")
# Use entity.path_to()
print("\nCalling entity.path_to()...")
path = e1.path_to(int(e2.x), int(e2.y))
print(f"Path returned: {path}")
print(f"Path length: {len(path)} steps")
if path:
print("\nHighlighting path cells:")
for i, (x, y) in enumerate(path):
print(f" Step {i}: ({x}, {y})")
# Get current color for debugging
cell = grid.at(x, y)
old_color = (cell.color.r, cell.color.g, cell.color.b)
# Set new color
cell.color = PATH_COLOR
new_color = (cell.color.r, cell.color.g, cell.color.b)
print(f" Color changed from {old_color} to {new_color}")
print(f" Walkable: {cell.walkable}")
# Also test grid's Dijkstra methods
print("\n" + "-"*30)
print("Testing grid Dijkstra methods...")
grid.compute_dijkstra(int(e1.x), int(e1.y))
grid_path = grid.get_dijkstra_path(int(e2.x), int(e2.y))
distance = grid.get_dijkstra_distance(int(e2.x), int(e2.y))
print(f"Grid path: {grid_path}")
print(f"Grid distance: {distance}")
# Verify colors were set
print("\nVerifying cell colors after highlighting:")
for x, y in path[:3]: # Check first 3 cells
cell = grid.at(x, y)
color = (cell.color.r, cell.color.g, cell.color.b)
expected = (PATH_COLOR.r, PATH_COLOR.g, PATH_COLOR.b)
match = color == expected
print(f" Cell ({x}, {y}): color={color}, expected={expected}, match={match}")
def handle_keypress(scene_name, keycode):
"""Simple keypress handler"""
if keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
print("\nExiting debug...")
sys.exit(0)
elif keycode == 32: # Space
print("\nSpace pressed - retesting path highlighting...")
test_path_highlighting()
# Create the map
print("Dijkstra Debug Test")
print("===================")
grid = create_simple_map()
# Initial path test
test_path_highlighting()
# Set up UI
ui = mcrfpy.sceneUI("dijkstra_debug")
ui.append(grid)
# Position and scale
grid.position = (50, 50)
grid.size = (400, 400) # 10*40
# Add title
title = mcrfpy.Caption("Dijkstra Debug - Press SPACE to retest, Q to quit", 50, 10)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add debug info
info = mcrfpy.Caption("Check console for debug output", 50, 470)
info.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(info)
# Set up scene
mcrfpy.keypressScene(handle_keypress)
mcrfpy.setScene("dijkstra_debug")
print("\nScene ready. The path should be highlighted in cyan.")
print("If you don't see the path, there may be a rendering issue.")
print("Press SPACE to retest, Q to quit.")

View File

@ -0,0 +1,244 @@
#!/usr/bin/env python3
"""
Dijkstra Pathfinding Interactive Demo
=====================================
Interactive visualization showing Dijkstra pathfinding between entities.
Controls:
- Press 1/2/3 to select the first entity
- Press A/B/C to select the second entity
- Space to clear selection
- Q or ESC to quit
The path between selected entities is automatically highlighted.
"""
import mcrfpy
import sys
# Colors - using more distinct values
WALL_COLOR = mcrfpy.Color(60, 30, 30)
FLOOR_COLOR = mcrfpy.Color(100, 100, 120) # Darker floor for better contrast
PATH_COLOR = mcrfpy.Color(50, 255, 50) # Bright green for path
ENTITY_COLORS = [
mcrfpy.Color(255, 100, 100), # Entity 1 - Red
mcrfpy.Color(100, 255, 100), # Entity 2 - Green
mcrfpy.Color(100, 100, 255), # Entity 3 - Blue
]
# Global state
grid = None
entities = []
first_point = None
second_point = None
def create_map():
"""Create the interactive map with the layout specified by the user"""
global grid, entities
mcrfpy.createScene("dijkstra_interactive")
# Create grid - 14x10 as specified
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
grid.fill_color = mcrfpy.Color(0, 0, 0)
# Define the map layout from user's specification
# . = floor, W = wall, E = entity position
map_layout = [
"..............", # Row 0
"..W.....WWWW..", # Row 1
"..W.W...W.EW..", # Row 2
"..W.....W..W..", # Row 3
"..W...E.WWWW..", # Row 4
"E.W...........", # Row 5
"..W...........", # Row 6
"..W...........", # Row 7
"..W.WWW.......", # Row 8
"..............", # Row 9
]
# Create the map
entity_positions = []
for y, row in enumerate(map_layout):
for x, char in enumerate(row):
cell = grid.at(x, y)
if char == 'W':
# Wall
cell.walkable = False
cell.transparent = False
cell.color = WALL_COLOR
else:
# Floor
cell.walkable = True
cell.transparent = True
cell.color = FLOOR_COLOR
if char == 'E':
# Entity position
entity_positions.append((x, y))
# Create entities at marked positions
entities = []
for i, (x, y) in enumerate(entity_positions):
entity = mcrfpy.Entity(x, y)
entity.sprite_index = 49 + i # '1', '2', '3'
grid.entities.append(entity)
entities.append(entity)
return grid
def clear_path_highlight():
"""Clear any existing path highlighting"""
# Reset all floor tiles to original color
for y in range(grid.grid_y):
for x in range(grid.grid_x):
cell = grid.at(x, y)
if cell.walkable:
cell.color = FLOOR_COLOR
def highlight_path():
"""Highlight the path between selected entities"""
if first_point is None or second_point is None:
return
# Clear previous highlighting
clear_path_highlight()
# Get entities
entity1 = entities[first_point]
entity2 = entities[second_point]
# Compute Dijkstra from first entity
grid.compute_dijkstra(int(entity1.x), int(entity1.y))
# Get path to second entity
path = grid.get_dijkstra_path(int(entity2.x), int(entity2.y))
if path:
# Highlight the path
for x, y in path:
cell = grid.at(x, y)
if cell.walkable:
cell.color = PATH_COLOR
# Also highlight start and end with entity colors
grid.at(int(entity1.x), int(entity1.y)).color = ENTITY_COLORS[first_point]
grid.at(int(entity2.x), int(entity2.y)).color = ENTITY_COLORS[second_point]
# Update info
distance = grid.get_dijkstra_distance(int(entity2.x), int(entity2.y))
info_text.text = f"Path: Entity {first_point+1} to Entity {second_point+1} - {len(path)} steps, {distance:.1f} units"
else:
info_text.text = f"No path between Entity {first_point+1} and Entity {second_point+1}"
def handle_keypress(scene_name, keycode):
"""Handle keyboard input"""
global first_point, second_point
# Number keys for first entity
if keycode == 49: # '1'
first_point = 0
status_text.text = f"First: Entity 1 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
highlight_path()
elif keycode == 50: # '2'
first_point = 1
status_text.text = f"First: Entity 2 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
highlight_path()
elif keycode == 51: # '3'
first_point = 2
status_text.text = f"First: Entity 3 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
highlight_path()
# Letter keys for second entity
elif keycode == 65 or keycode == 97: # 'A' or 'a'
second_point = 0
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 1"
highlight_path()
elif keycode == 66 or keycode == 98: # 'B' or 'b'
second_point = 1
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 2"
highlight_path()
elif keycode == 67 or keycode == 99: # 'C' or 'c'
second_point = 2
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 3"
highlight_path()
# Clear selection
elif keycode == 32: # Space
first_point = None
second_point = None
clear_path_highlight()
status_text.text = "Press 1/2/3 for first entity, A/B/C for second"
info_text.text = "Space to clear, Q to quit"
# Quit
elif keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
print("\nExiting Dijkstra interactive demo...")
sys.exit(0)
# Create the visualization
print("Dijkstra Pathfinding Interactive Demo")
print("=====================================")
print("Controls:")
print(" 1/2/3 - Select first entity")
print(" A/B/C - Select second entity")
print(" Space - Clear selection")
print(" Q/ESC - Quit")
# Create map
grid = create_map()
# Set up UI
ui = mcrfpy.sceneUI("dijkstra_interactive")
ui.append(grid)
# Scale and position grid for better visibility
grid.size = (560, 400) # 14*40, 10*40
grid.position = (120, 60)
# Add title
title = mcrfpy.Caption("Dijkstra Pathfinding Interactive", 250, 10)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add status text
status_text = mcrfpy.Caption("Press 1/2/3 for first entity, A/B/C for second", 120, 480)
status_text.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(status_text)
# Add info text
info_text = mcrfpy.Caption("Space to clear, Q to quit", 120, 500)
info_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(info_text)
# Add legend
legend1 = mcrfpy.Caption("Entities: 1=Red 2=Green 3=Blue", 120, 540)
legend1.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend1)
legend2 = mcrfpy.Caption("Colors: Dark=Wall Light=Floor Cyan=Path", 120, 560)
legend2.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend2)
# Mark entity positions with colored indicators
for i, entity in enumerate(entities):
marker = mcrfpy.Caption(str(i+1),
120 + int(entity.x) * 40 + 15,
60 + int(entity.y) * 40 + 10)
marker.fill_color = ENTITY_COLORS[i]
marker.outline = 1
marker.outline_color = mcrfpy.Color(0, 0, 0)
ui.append(marker)
# Set up input handling
mcrfpy.keypressScene(handle_keypress)
# Show the scene
mcrfpy.setScene("dijkstra_interactive")
print("\nVisualization ready!")
print("Entities are at:")
for i, entity in enumerate(entities):
print(f" Entity {i+1}: ({int(entity.x)}, {int(entity.y)})")

View File

@ -0,0 +1,344 @@
#!/usr/bin/env python3
"""
Enhanced Dijkstra Pathfinding Interactive Demo
==============================================
Interactive visualization with entity pathfinding animations.
Controls:
- Press 1/2/3 to select the first entity
- Press A/B/C to select the second entity
- Space to clear selection
- M to make selected entity move along path
- P to pause/resume animation
- R to reset entity positions
- Q or ESC to quit
"""
import mcrfpy
import sys
import math
# Colors
WALL_COLOR = mcrfpy.Color(60, 30, 30)
FLOOR_COLOR = mcrfpy.Color(200, 200, 220)
PATH_COLOR = mcrfpy.Color(200, 250, 220)
VISITED_COLOR = mcrfpy.Color(180, 230, 200)
ENTITY_COLORS = [
mcrfpy.Color(255, 100, 100), # Entity 1 - Red
mcrfpy.Color(100, 255, 100), # Entity 2 - Green
mcrfpy.Color(100, 100, 255), # Entity 3 - Blue
]
# Global state
grid = None
entities = []
first_point = None
second_point = None
current_path = []
animating = False
animation_progress = 0.0
animation_speed = 2.0 # cells per second
original_positions = [] # Store original entity positions
def create_map():
"""Create the interactive map with the layout specified by the user"""
global grid, entities, original_positions
mcrfpy.createScene("dijkstra_enhanced")
# Create grid - 14x10 as specified
grid = mcrfpy.Grid(grid_x=14, grid_y=10)
grid.fill_color = mcrfpy.Color(0, 0, 0)
# Define the map layout from user's specification
# . = floor, W = wall, E = entity position
map_layout = [
"..............", # Row 0
"..W.....WWWW..", # Row 1
"..W.W...W.EW..", # Row 2
"..W.....W..W..", # Row 3
"..W...E.WWWW..", # Row 4
"E.W...........", # Row 5
"..W...........", # Row 6
"..W...........", # Row 7
"..W.WWW.......", # Row 8
"..............", # Row 9
]
# Create the map
entity_positions = []
for y, row in enumerate(map_layout):
for x, char in enumerate(row):
cell = grid.at(x, y)
if char == 'W':
# Wall
cell.walkable = False
cell.transparent = False
cell.color = WALL_COLOR
else:
# Floor
cell.walkable = True
cell.transparent = True
cell.color = FLOOR_COLOR
if char == 'E':
# Entity position
entity_positions.append((x, y))
# Create entities at marked positions
entities = []
original_positions = []
for i, (x, y) in enumerate(entity_positions):
entity = mcrfpy.Entity(x, y)
entity.sprite_index = 49 + i # '1', '2', '3'
grid.entities.append(entity)
entities.append(entity)
original_positions.append((x, y))
return grid
def clear_path_highlight():
"""Clear any existing path highlighting"""
global current_path
# Reset all floor tiles to original color
for y in range(grid.grid_y):
for x in range(grid.grid_x):
cell = grid.at(x, y)
if cell.walkable:
cell.color = FLOOR_COLOR
current_path = []
def highlight_path():
"""Highlight the path between selected entities using entity.path_to()"""
global current_path
if first_point is None or second_point is None:
return
# Clear previous highlighting
clear_path_highlight()
# Get entities
entity1 = entities[first_point]
entity2 = entities[second_point]
# Use the new path_to method!
path = entity1.path_to(int(entity2.x), int(entity2.y))
if path:
current_path = path
# Highlight the path
for i, (x, y) in enumerate(path):
cell = grid.at(x, y)
if cell.walkable:
# Use gradient for path visualization
if i < len(path) - 1:
cell.color = PATH_COLOR
else:
cell.color = VISITED_COLOR
# Highlight start and end with entity colors
grid.at(int(entity1.x), int(entity1.y)).color = ENTITY_COLORS[first_point]
grid.at(int(entity2.x), int(entity2.y)).color = ENTITY_COLORS[second_point]
# Update info
info_text.text = f"Path: Entity {first_point+1} to Entity {second_point+1} - {len(path)} steps"
else:
info_text.text = f"No path between Entity {first_point+1} and Entity {second_point+1}"
current_path = []
def animate_movement(dt):
"""Animate entity movement along path"""
global animation_progress, animating, current_path
if not animating or not current_path or first_point is None:
return
entity = entities[first_point]
# Update animation progress
animation_progress += animation_speed * dt
# Calculate current position along path
path_index = int(animation_progress)
if path_index >= len(current_path):
# Animation complete
animating = False
animation_progress = 0.0
# Snap to final position
if current_path:
final_x, final_y = current_path[-1]
entity.x = float(final_x)
entity.y = float(final_y)
return
# Interpolate between path points
if path_index < len(current_path) - 1:
curr_x, curr_y = current_path[path_index]
next_x, next_y = current_path[path_index + 1]
# Calculate interpolation factor
t = animation_progress - path_index
# Smooth interpolation
entity.x = curr_x + (next_x - curr_x) * t
entity.y = curr_y + (next_y - curr_y) * t
else:
# At last point
entity.x, entity.y = current_path[path_index]
def handle_keypress(scene_name, keycode):
"""Handle keyboard input"""
global first_point, second_point, animating, animation_progress
# Number keys for first entity
if keycode == 49: # '1'
first_point = 0
status_text.text = f"First: Entity 1 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
highlight_path()
elif keycode == 50: # '2'
first_point = 1
status_text.text = f"First: Entity 2 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
highlight_path()
elif keycode == 51: # '3'
first_point = 2
status_text.text = f"First: Entity 3 | Second: {f'Entity {second_point+1}' if second_point is not None else '?'}"
highlight_path()
# Letter keys for second entity
elif keycode == 65 or keycode == 97: # 'A' or 'a'
second_point = 0
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 1"
highlight_path()
elif keycode == 66 or keycode == 98: # 'B' or 'b'
second_point = 1
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 2"
highlight_path()
elif keycode == 67 or keycode == 99: # 'C' or 'c'
second_point = 2
status_text.text = f"First: {f'Entity {first_point+1}' if first_point is not None else '?'} | Second: Entity 3"
highlight_path()
# Movement control
elif keycode == 77 or keycode == 109: # 'M' or 'm'
if current_path and first_point is not None:
animating = True
animation_progress = 0.0
control_text.text = "Animation: MOVING (press P to pause)"
# Pause/Resume
elif keycode == 80 or keycode == 112: # 'P' or 'p'
animating = not animating
control_text.text = f"Animation: {'MOVING' if animating else 'PAUSED'} (press P to {'pause' if animating else 'resume'})"
# Reset positions
elif keycode == 82 or keycode == 114: # 'R' or 'r'
animating = False
animation_progress = 0.0
for i, entity in enumerate(entities):
entity.x, entity.y = original_positions[i]
control_text.text = "Entities reset to original positions"
highlight_path() # Re-highlight path after reset
# Clear selection
elif keycode == 32: # Space
first_point = None
second_point = None
animating = False
animation_progress = 0.0
clear_path_highlight()
status_text.text = "Press 1/2/3 for first entity, A/B/C for second"
info_text.text = "Space to clear, Q to quit"
control_text.text = "Press M to move, P to pause, R to reset"
# Quit
elif keycode == 81 or keycode == 113 or keycode == 256: # Q/q/ESC
print("\nExiting enhanced Dijkstra demo...")
sys.exit(0)
# Timer callback for animation
def update_animation(dt):
"""Update animation state"""
animate_movement(dt / 1000.0) # Convert ms to seconds
# Create the visualization
print("Enhanced Dijkstra Pathfinding Demo")
print("==================================")
print("Controls:")
print(" 1/2/3 - Select first entity")
print(" A/B/C - Select second entity")
print(" M - Move first entity along path")
print(" P - Pause/Resume animation")
print(" R - Reset entity positions")
print(" Space - Clear selection")
print(" Q/ESC - Quit")
# Create map
grid = create_map()
# Set up UI
ui = mcrfpy.sceneUI("dijkstra_enhanced")
ui.append(grid)
# Scale and position grid for better visibility
grid.size = (560, 400) # 14*40, 10*40
grid.position = (120, 60)
# Add title
title = mcrfpy.Caption("Enhanced Dijkstra Pathfinding", 250, 10)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add status text
status_text = mcrfpy.Caption("Press 1/2/3 for first entity, A/B/C for second", 120, 480)
status_text.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(status_text)
# Add info text
info_text = mcrfpy.Caption("Space to clear, Q to quit", 120, 500)
info_text.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(info_text)
# Add control text
control_text = mcrfpy.Caption("Press M to move, P to pause, R to reset", 120, 520)
control_text.fill_color = mcrfpy.Color(150, 200, 150)
ui.append(control_text)
# Add legend
legend1 = mcrfpy.Caption("Entities: 1=Red 2=Green 3=Blue", 120, 560)
legend1.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend1)
legend2 = mcrfpy.Caption("Colors: Dark=Wall Light=Floor Cyan=Path", 120, 580)
legend2.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(legend2)
# Mark entity positions with colored indicators
for i, entity in enumerate(entities):
marker = mcrfpy.Caption(str(i+1),
120 + int(entity.x) * 40 + 15,
60 + int(entity.y) * 40 + 10)
marker.fill_color = ENTITY_COLORS[i]
marker.outline = 1
marker.outline_color = mcrfpy.Color(0, 0, 0)
ui.append(marker)
# Set up input handling
mcrfpy.keypressScene(handle_keypress)
# Set up animation timer (60 FPS)
mcrfpy.setTimer("animation", update_animation, 16)
# Show the scene
mcrfpy.setScene("dijkstra_enhanced")
print("\nVisualization ready!")
print("Entities are at:")
for i, entity in enumerate(entities):
print(f" Entity {i+1}: ({int(entity.x)}, {int(entity.y)})")

View File

@ -0,0 +1,146 @@
#!/usr/bin/env python3
"""
Dijkstra Pathfinding Test - Headless
====================================
Tests all Dijkstra functionality and generates a screenshot.
"""
import mcrfpy
from mcrfpy import automation
import sys
def create_test_map():
"""Create a test map with obstacles"""
mcrfpy.createScene("dijkstra_test")
# Create grid
grid = mcrfpy.Grid(grid_x=20, grid_y=12)
grid.fill_color = mcrfpy.Color(0, 0, 0)
# Initialize all cells as walkable floor
for y in range(12):
for x in range(20):
grid.at(x, y).walkable = True
grid.at(x, y).transparent = True
grid.at(x, y).color = mcrfpy.Color(200, 200, 220)
# Add walls to create interesting paths
walls = [
# Vertical wall in the middle
(10, 1), (10, 2), (10, 3), (10, 4), (10, 5), (10, 6), (10, 7), (10, 8),
# Horizontal walls
(2, 6), (3, 6), (4, 6), (5, 6), (6, 6),
(14, 6), (15, 6), (16, 6), (17, 6),
# Some scattered obstacles
(5, 2), (15, 2), (5, 9), (15, 9)
]
for x, y in walls:
grid.at(x, y).walkable = False
grid.at(x, y).color = mcrfpy.Color(60, 30, 30)
# Place test entities
entities = []
positions = [(2, 2), (17, 2), (9, 10)]
colors = [
mcrfpy.Color(255, 100, 100), # Red
mcrfpy.Color(100, 255, 100), # Green
mcrfpy.Color(100, 100, 255) # Blue
]
for i, (x, y) in enumerate(positions):
entity = mcrfpy.Entity(x, y)
entity.sprite_index = 49 + i # '1', '2', '3'
grid.entities.append(entity)
entities.append(entity)
# Mark entity positions
grid.at(x, y).color = colors[i]
return grid, entities
def test_dijkstra(grid, entities):
"""Test Dijkstra pathfinding between all entity pairs"""
results = []
for i in range(len(entities)):
for j in range(len(entities)):
if i != j:
# Compute Dijkstra from entity i
e1 = entities[i]
e2 = entities[j]
grid.compute_dijkstra(int(e1.x), int(e1.y))
# Get distance and path to entity j
distance = grid.get_dijkstra_distance(int(e2.x), int(e2.y))
path = grid.get_dijkstra_path(int(e2.x), int(e2.y))
if path:
results.append(f"Path {i+1}{j+1}: {len(path)} steps, {distance:.1f} units")
# Color one interesting path
if i == 0 and j == 2: # Path from 1 to 3
for x, y in path[1:-1]: # Skip endpoints
if grid.at(x, y).walkable:
grid.at(x, y).color = mcrfpy.Color(200, 250, 220)
else:
results.append(f"Path {i+1}{j+1}: No path found!")
return results
def run_test(runtime):
"""Timer callback to run tests and take screenshot"""
# Run pathfinding tests
results = test_dijkstra(grid, entities)
# Update display with results
y_pos = 380
for result in results:
caption = mcrfpy.Caption(result, 50, y_pos)
caption.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(caption)
y_pos += 20
# Take screenshot
mcrfpy.setTimer("screenshot", lambda rt: take_screenshot(), 500)
def take_screenshot():
"""Take screenshot and exit"""
try:
automation.screenshot("dijkstra_test.png")
print("Screenshot saved: dijkstra_test.png")
except Exception as e:
print(f"Screenshot failed: {e}")
# Exit
sys.exit(0)
# Create test map
print("Creating Dijkstra pathfinding test...")
grid, entities = create_test_map()
# Set up UI
ui = mcrfpy.sceneUI("dijkstra_test")
ui.append(grid)
# Position and scale grid
grid.position = (50, 50)
grid.size = (500, 300)
# Add title
title = mcrfpy.Caption("Dijkstra Pathfinding Test", 200, 10)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Add legend
legend = mcrfpy.Caption("Red=Entity1 Green=Entity2 Blue=Entity3 Cyan=Path 1→3", 50, 360)
legend.fill_color = mcrfpy.Color(180, 180, 180)
ui.append(legend)
# Set scene
mcrfpy.setScene("dijkstra_test")
# Run test after scene loads
mcrfpy.setTimer("test", run_test, 100)
print("Running Dijkstra tests...")

View File

@ -0,0 +1,201 @@
#!/usr/bin/env python3
"""
Interactive Visibility Demo
==========================
Controls:
- WASD: Move the player (green @)
- Arrow keys: Move enemy (red E)
- Tab: Cycle perspective (Omniscient Player Enemy Omniscient)
- Space: Update visibility for current entity
- R: Reset positions
"""
import mcrfpy
import sys
# Create scene and grid
mcrfpy.createScene("visibility_demo")
grid = mcrfpy.Grid(grid_x=30, grid_y=20)
grid.fill_color = mcrfpy.Color(20, 20, 30) # Dark background
# Initialize grid - all walkable and transparent
for y in range(20):
for x in range(30):
cell = grid.at(x, y)
cell.walkable = True
cell.transparent = True
cell.color = mcrfpy.Color(100, 100, 120) # Floor color
# Create walls
walls = [
# Central cross
[(15, y) for y in range(8, 12)],
[(x, 10) for x in range(13, 18)],
# Rooms
# Top-left room
[(x, 5) for x in range(2, 8)] + [(8, y) for y in range(2, 6)],
[(2, y) for y in range(2, 6)] + [(x, 2) for x in range(2, 8)],
# Top-right room
[(x, 5) for x in range(22, 28)] + [(22, y) for y in range(2, 6)],
[(28, y) for y in range(2, 6)] + [(x, 2) for x in range(22, 28)],
# Bottom-left room
[(x, 15) for x in range(2, 8)] + [(8, y) for y in range(15, 18)],
[(2, y) for y in range(15, 18)] + [(x, 18) for x in range(2, 8)],
# Bottom-right room
[(x, 15) for x in range(22, 28)] + [(22, y) for y in range(15, 18)],
[(28, y) for y in range(15, 18)] + [(x, 18) for x in range(22, 28)],
]
for wall_group in walls:
for x, y in wall_group:
if 0 <= x < 30 and 0 <= y < 20:
cell = grid.at(x, y)
cell.walkable = False
cell.transparent = False
cell.color = mcrfpy.Color(40, 20, 20) # Wall color
# Create entities
player = mcrfpy.Entity(5, 10, grid=grid)
player.sprite_index = 64 # @
enemy = mcrfpy.Entity(25, 10, grid=grid)
enemy.sprite_index = 69 # E
# Update initial visibility
player.update_visibility()
enemy.update_visibility()
# Global state
current_perspective = -1
perspective_names = ["Omniscient", "Player", "Enemy"]
# UI Setup
ui = mcrfpy.sceneUI("visibility_demo")
ui.append(grid)
grid.position = (50, 100)
grid.size = (900, 600) # 30*30, 20*30
# Title
title = mcrfpy.Caption("Interactive Visibility Demo", 350, 20)
title.fill_color = mcrfpy.Color(255, 255, 255)
ui.append(title)
# Info displays
perspective_label = mcrfpy.Caption("Perspective: Omniscient", 50, 50)
perspective_label.fill_color = mcrfpy.Color(200, 200, 200)
ui.append(perspective_label)
controls = mcrfpy.Caption("WASD: Move player | Arrows: Move enemy | Tab: Cycle perspective | Space: Update visibility | R: Reset", 50, 730)
controls.fill_color = mcrfpy.Color(150, 150, 150)
ui.append(controls)
player_info = mcrfpy.Caption("Player: (5, 10)", 700, 50)
player_info.fill_color = mcrfpy.Color(100, 255, 100)
ui.append(player_info)
enemy_info = mcrfpy.Caption("Enemy: (25, 10)", 700, 70)
enemy_info.fill_color = mcrfpy.Color(255, 100, 100)
ui.append(enemy_info)
# Helper functions
def move_entity(entity, dx, dy):
"""Move entity if target is walkable"""
new_x = int(entity.x + dx)
new_y = int(entity.y + dy)
if 0 <= new_x < 30 and 0 <= new_y < 20:
cell = grid.at(new_x, new_y)
if cell.walkable:
entity.x = new_x
entity.y = new_y
entity.update_visibility()
return True
return False
def update_info():
"""Update info displays"""
player_info.text = f"Player: ({int(player.x)}, {int(player.y)})"
enemy_info.text = f"Enemy: ({int(enemy.x)}, {int(enemy.y)})"
def cycle_perspective():
"""Cycle through perspectives"""
global current_perspective
# Cycle: -1 → 0 → 1 → -1
current_perspective = (current_perspective + 2) % 3 - 1
grid.perspective = current_perspective
name = perspective_names[current_perspective + 1]
perspective_label.text = f"Perspective: {name}"
# Key handlers
def handle_keys(key, state):
"""Handle keyboard input"""
if state == "end": return
key = key.lower()
# Player movement (WASD)
if key == "w":
move_entity(player, 0, -1)
elif key == "s":
move_entity(player, 0, 1)
elif key == "a":
move_entity(player, -1, 0)
elif key == "d":
move_entity(player, 1, 0)
# Enemy movement (Arrows)
elif key == "up":
move_entity(enemy, 0, -1)
elif key == "down":
move_entity(enemy, 0, 1)
elif key == "left":
move_entity(enemy, -1, 0)
elif key == "right":
move_entity(enemy, 1, 0)
# Tab to cycle perspective
elif key == "tab":
cycle_perspective()
# Space to update visibility
elif key == "space":
player.update_visibility()
enemy.update_visibility()
print("Updated visibility for both entities")
# R to reset
elif key == "r":
player.x, player.y = 5, 10
enemy.x, enemy.y = 25, 10
player.update_visibility()
enemy.update_visibility()
update_info()
print("Reset positions")
# Q to quit
elif key == "q":
print("Exiting...")
sys.exit(0)
update_info()
# Set scene first
mcrfpy.setScene("visibility_demo")
# Register key handler (operates on current scene)
mcrfpy.keypressScene(handle_keys)
print("Interactive Visibility Demo")
print("===========================")
print("WASD: Move player (green @)")
print("Arrows: Move enemy (red E)")
print("Tab: Cycle perspective")
print("Space: Update visibility")
print("R: Reset positions")
print("Q: Quit")
print("\nCurrent perspective: Omniscient (shows all)")
print("Try moving entities and switching perspectives!")

View File

@ -0,0 +1,46 @@
#!/usr/bin/env python3
"""Simple interactive visibility test"""
import mcrfpy
import sys
# Create scene and grid
print("Creating scene...")
mcrfpy.createScene("vis_test")
print("Creating grid...")
grid = mcrfpy.Grid(grid_x=10, grid_y=10)
# Initialize grid
print("Initializing grid...")
for y in range(10):
for x in range(10):
cell = grid.at(x, y)
cell.walkable = True
cell.transparent = True
cell.color = mcrfpy.Color(100, 100, 120)
# Create entity
print("Creating entity...")
entity = mcrfpy.Entity(5, 5, grid=grid)
entity.sprite_index = 64
print("Updating visibility...")
entity.update_visibility()
# Set up UI
print("Setting up UI...")
ui = mcrfpy.sceneUI("vis_test")
ui.append(grid)
grid.position = (50, 50)
grid.size = (300, 300)
# Test perspective
print("Testing perspective...")
grid.perspective = -1 # Omniscient
print(f"Perspective set to: {grid.perspective}")
print("Setting scene...")
mcrfpy.setScene("vis_test")
print("Ready!")

View File

@ -0,0 +1,39 @@
#!/usr/bin/env python3
"""Simple visibility test without entity append"""
import mcrfpy
import sys
print("Simple visibility test...")
# Create scene and grid
mcrfpy.createScene("simple")
print("Scene created")
grid = mcrfpy.Grid(grid_x=5, grid_y=5)
print("Grid created")
# Create entity without appending
entity = mcrfpy.Entity(2, 2, grid=grid)
print(f"Entity created at ({entity.x}, {entity.y})")
# Check if gridstate is initialized
print(f"Gridstate length: {len(entity.gridstate)}")
# Try to access at method
try:
state = entity.at(0, 0)
print(f"at(0,0) returned: {state}")
print(f"visible: {state.visible}, discovered: {state.discovered}")
except Exception as e:
print(f"Error in at(): {e}")
# Try update_visibility
try:
entity.update_visibility()
print("update_visibility() succeeded")
except Exception as e:
print(f"Error in update_visibility(): {e}")
print("Test complete")
sys.exit(0)

42
tests/run_all_tests.sh Executable file
View File

@ -0,0 +1,42 @@
#!/bin/bash
# Run all tests and check for failures
TESTS=(
"test_click_init.py"
"test_drawable_base.py"
"test_frame_children.py"
"test_sprite_texture_swap.py"
"test_timer_object.py"
"test_timer_object_fixed.py"
)
echo "Running all tests..."
echo "===================="
failed=0
passed=0
for test in "${TESTS[@]}"; do
echo -n "Running $test... "
if timeout 5 ./mcrogueface --headless --exec ../tests/$test > /tmp/test_output.txt 2>&1; then
if grep -q "FAIL\|✗" /tmp/test_output.txt; then
echo "FAILED"
echo "Output:"
cat /tmp/test_output.txt | grep -E "✗|FAIL|Error|error" | head -10
((failed++))
else
echo "PASSED"
((passed++))
fi
else
echo "TIMEOUT/CRASH"
((failed++))
fi
done
echo "===================="
echo "Total: $((passed + failed)) tests"
echo "Passed: $passed"
echo "Failed: $failed"
exit $failed

View File

@ -1,32 +0,0 @@
#!/usr/bin/env python3
"""Test if closing stdin prevents the >>> prompt"""
import mcrfpy
import sys
import os
print("=== Testing stdin theory ===")
print(f"stdin.isatty(): {sys.stdin.isatty()}")
print(f"stdin fileno: {sys.stdin.fileno()}")
# Set up a basic scene
mcrfpy.createScene("stdin_test")
mcrfpy.setScene("stdin_test")
# Try to prevent interactive mode by closing stdin
print("\nAttempting to prevent interactive mode...")
try:
# Method 1: Close stdin
sys.stdin.close()
print("Closed sys.stdin")
except:
print("Failed to close sys.stdin")
try:
# Method 2: Redirect stdin to /dev/null
devnull = open(os.devnull, 'r')
os.dup2(devnull.fileno(), 0)
print("Redirected stdin to /dev/null")
except:
print("Failed to redirect stdin")
print("\nScript complete. If >>> still appears, the issue is elsewhere.")

View File

@ -0,0 +1,101 @@
// Example of how UIFrame would implement unified click handling
//
// Click Priority Example:
// - Dialog Frame (has click handler to drag window)
// - Title Caption (no click handler)
// - Button Frame (has click handler)
// - Button Caption "OK" (no click handler)
// - Close X Sprite (has click handler)
//
// Clicking on:
// - "OK" text -> Button Frame gets the click (deepest parent with handler)
// - Close X -> Close sprite gets the click
// - Title bar -> Dialog Frame gets the click (no child has handler there)
// - Outside dialog -> nullptr (bounds check fails)
class UIFrame : public UIDrawable, protected RectangularContainer {
private:
// Implementation of container interface
sf::Vector2f toChildCoordinates(sf::Vector2f localPoint, int childIndex) const override {
// Children use same coordinate system as frame's local coordinates
return localPoint;
}
UIDrawable* getClickHandler() override {
return click_callable ? this : nullptr;
}
std::vector<UIDrawable*> getClickableChildren() override {
std::vector<UIDrawable*> result;
for (auto& child : *children) {
result.push_back(child.get());
}
return result;
}
public:
UIDrawable* click_at(sf::Vector2f point) override {
// Update bounds from box
bounds = sf::FloatRect(box.getPosition().x, box.getPosition().y,
box.getSize().x, box.getSize().y);
// Use unified handler
return handleClick(point);
}
};
// Example for UIGrid with entity coordinate transformation
class UIGrid : public UIDrawable, protected RectangularContainer {
private:
sf::Vector2f toChildCoordinates(sf::Vector2f localPoint, int childIndex) const override {
// For entities, we need to transform from pixel coordinates to grid coordinates
// This is where the grid's special coordinate system is handled
// Assuming entity positions are in grid cells, not pixels
// We pass pixel coordinates relative to the grid's rendering area
return localPoint; // Entities will handle their own sprite positioning
}
std::vector<UIDrawable*> getClickableChildren() override {
std::vector<UIDrawable*> result;
// Only check entities that are visible on screen
float left_edge = center_x - (box.getSize().x / 2.0f) / (grid_size * zoom);
float top_edge = center_y - (box.getSize().y / 2.0f) / (grid_size * zoom);
float right_edge = left_edge + (box.getSize().x / (grid_size * zoom));
float bottom_edge = top_edge + (box.getSize().y / (grid_size * zoom));
for (auto& entity : entities) {
// Check if entity is within visible bounds
if (entity->position.x >= left_edge - 1 && entity->position.x < right_edge + 1 &&
entity->position.y >= top_edge - 1 && entity->position.y < bottom_edge + 1) {
result.push_back(&entity->sprite);
}
}
return result;
}
};
// For Scene, which has no coordinate transformation
class PyScene : protected UIContainerBase {
private:
sf::Vector2f toLocalCoordinates(sf::Vector2f point) const override {
// Scene uses window coordinates directly
return point;
}
sf::Vector2f toChildCoordinates(sf::Vector2f localPoint, int childIndex) const override {
// Top-level drawables use window coordinates
return localPoint;
}
bool containsPoint(sf::Vector2f localPoint) const override {
// Scene contains all points (full window)
return true;
}
UIDrawable* getClickHandler() override {
// Scene itself doesn't handle clicks
return nullptr;
}
};

View File

@ -0,0 +1,4 @@
import mcrfpy
e = mcrfpy.Entity(0, 0)
print("Entity attributes:", dir(e))
print("\nEntity repr:", repr(e))

View File

@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""Debug empty paths issue"""
import mcrfpy
import sys
print("Debugging empty paths...")
# Create scene and grid
mcrfpy.createScene("debug")
grid = mcrfpy.Grid(grid_x=10, grid_y=10)
# Initialize grid - all walkable
print("\nInitializing grid...")
for y in range(10):
for x in range(10):
grid.at(x, y).walkable = True
# Test simple path
print("\nTest 1: Simple path from (0,0) to (5,5)")
path = grid.compute_astar_path(0, 0, 5, 5)
print(f" A* path: {path}")
print(f" Path length: {len(path)}")
# Test with Dijkstra
print("\nTest 2: Same path with Dijkstra")
grid.compute_dijkstra(0, 0)
dpath = grid.get_dijkstra_path(5, 5)
print(f" Dijkstra path: {dpath}")
print(f" Path length: {len(dpath)}")
# Check if grid is properly initialized
print("\nTest 3: Checking grid cells")
for y in range(3):
for x in range(3):
cell = grid.at(x, y)
print(f" Cell ({x},{y}): walkable={cell.walkable}")
# Test with walls
print("\nTest 4: Path with wall")
grid.at(2, 2).walkable = False
grid.at(3, 2).walkable = False
grid.at(4, 2).walkable = False
print(" Added wall at y=2, x=2,3,4")
path2 = grid.compute_astar_path(0, 0, 5, 5)
print(f" A* path with wall: {path2}")
print(f" Path length: {len(path2)}")
# Test invalid paths
print("\nTest 5: Path to blocked cell")
grid.at(9, 9).walkable = False
path3 = grid.compute_astar_path(0, 0, 9, 9)
print(f" Path to blocked cell: {path3}")
# Check TCOD map sync
print("\nTest 6: Verify TCOD map is synced")
# Try to force a sync
print(" Checking if syncTCODMap exists...")
if hasattr(grid, 'sync_tcod_map'):
print(" Calling sync_tcod_map()")
grid.sync_tcod_map()
else:
print(" No sync_tcod_map method found")
# Try path again
print("\nTest 7: Path after potential sync")
path4 = grid.compute_astar_path(0, 0, 5, 5)
print(f" A* path: {path4}")
def timer_cb(dt):
sys.exit(0)
# Quick UI setup
ui = mcrfpy.sceneUI("debug")
ui.append(grid)
mcrfpy.setScene("debug")
mcrfpy.setTimer("exit", timer_cb, 100)
print("\nStarting timer...")

View File

@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""Test Grid.at() method with various argument formats"""
import mcrfpy
import sys
def test_grid_at_arguments():
"""Test that Grid.at() accepts all required argument formats"""
print("Testing Grid.at() argument formats...")
# Create a test scene
mcrfpy.createScene("test")
# Create a grid
grid = mcrfpy.Grid(10, 10)
ui = mcrfpy.sceneUI("test")
ui.append(grid)
success_count = 0
total_tests = 4
# Test 1: Two positional arguments (x, y)
try:
point1 = grid.at(5, 5)
print("✓ Test 1 PASSED: grid.at(5, 5)")
success_count += 1
except Exception as e:
print(f"✗ Test 1 FAILED: grid.at(5, 5) - {e}")
# Test 2: Single tuple argument (x, y)
try:
point2 = grid.at((3, 3))
print("✓ Test 2 PASSED: grid.at((3, 3))")
success_count += 1
except Exception as e:
print(f"✗ Test 2 FAILED: grid.at((3, 3)) - {e}")
# Test 3: Keyword arguments x=x, y=y
try:
point3 = grid.at(x=7, y=2)
print("✓ Test 3 PASSED: grid.at(x=7, y=2)")
success_count += 1
except Exception as e:
print(f"✗ Test 3 FAILED: grid.at(x=7, y=2) - {e}")
# Test 4: pos keyword argument pos=(x, y)
try:
point4 = grid.at(pos=(1, 8))
print("✓ Test 4 PASSED: grid.at(pos=(1, 8))")
success_count += 1
except Exception as e:
print(f"✗ Test 4 FAILED: grid.at(pos=(1, 8)) - {e}")
# Test error cases
print("\nTesting error cases...")
# Test 5: Invalid - mixing pos with x/y
try:
grid.at(x=1, pos=(2, 2))
print("✗ Test 5 FAILED: Should have raised error for mixing pos and x/y")
except TypeError as e:
print(f"✓ Test 5 PASSED: Correctly rejected mixing pos and x/y - {e}")
# Test 6: Invalid - out of range
try:
grid.at(15, 15)
print("✗ Test 6 FAILED: Should have raised error for out of range")
except ValueError as e:
print(f"✓ Test 6 PASSED: Correctly rejected out of range - {e}")
# Test 7: Verify all points are valid GridPoint objects
try:
# Check that we can set walkable on all returned points
if 'point1' in locals():
point1.walkable = True
if 'point2' in locals():
point2.walkable = False
if 'point3' in locals():
point3.color = mcrfpy.Color(255, 0, 0)
if 'point4' in locals():
point4.tilesprite = 5
print("✓ All returned GridPoint objects are valid")
except Exception as e:
print(f"✗ GridPoint objects validation failed: {e}")
print(f"\nSummary: {success_count}/{total_tests} tests passed")
if success_count == total_tests:
print("ALL TESTS PASSED!")
sys.exit(0)
else:
print("SOME TESTS FAILED!")
sys.exit(1)
# Run timer callback to execute tests after render loop starts
def run_test(elapsed):
test_grid_at_arguments()
# Set a timer to run the test
mcrfpy.setTimer("test", run_test, 100)

Some files were not shown because too many files have changed in this diff Show More