Compare commits

..

8 Commits

Author SHA1 Message Date
John McCardle b114ec3085 cleaning up for merge 2024-03-21 22:22:35 -04:00
John McCardle d7228172c4 Messy, but monumental: PyTexture::pyObject works
this also coincidentally fixes a weird bug I encountered while
(mis?)using tp_alloc: by using PyType_GenericAlloc, I avoid the segfault
that tp_alloc sometimes causes. See the horrible UIDrawable retrieval
macro that I use in UI.h for a workaround that can probably be replaced
with this technique
2024-03-21 21:39:15 -04:00
John McCardle 2cf8f94310 Radical new example pattern for exposing a C++ class to Python 2024-03-20 21:16:52 -04:00
John McCardle 84a8886da2 Fixed render issue with UIGrid / PyTexture: wasn't positioning or scaling properly after fetching sprite 2024-03-17 16:29:33 -04:00
John McCardle 20f80c4114 Fixed sprite indexing error in PyTexture; needs non-square sprite tests, but feeling confident! 2024-03-17 16:23:52 -04:00
John McCardle afd4ff1925 good progress, we're building again. Issue with Grid (tile sprite) textures and I think the sprite indexes are being calculated wrong (x and y transposed?) 2024-03-16 21:53:24 -04:00
John McCardle bfd33102d1 Squashed basically all the compile bugs in UISprite, but UIEntity and UIGrid use textures as well, so they need to be fixed too before the project will build again 2024-03-16 14:52:35 -04:00
John McCardle 47d0e34a17 Initial PyTexture class
no testing done.
should enable rectangular (non-square) textures

"sprite" method; let's just overwrite sprites with texture coords
Hoping to replace awful code like:
`self->data->sprite.sprite.setTextureRect(self->data->sprite.itex->spriteCoordinates(val));`

with something like:
`self->data->sprite = self->data->texture->sprite(val);`
2024-03-16 11:31:39 -04:00
140 changed files with 3793 additions and 19753 deletions

17
.gitignore vendored
View File

@ -10,20 +10,3 @@ build
lib
obj
.cache/
7DRL2025 Release/
CMakeFiles/
Makefile
*.md
*.zip
__lib/
_oldscripts/
assets/
cellular_automata_fire/
*.txt
deps/
fetch_issues_txt.py
forest_fire_CA.py
mcrogueface.github.io
scripts/
test_*

View File

@ -46,7 +46,7 @@ endif()
# Add the directory where the linker should look for the libraries
#link_directories(${CMAKE_SOURCE_DIR}/deps_linux)
link_directories(${CMAKE_SOURCE_DIR}/__lib)
link_directories(${CMAKE_SOURCE_DIR}/lib)
# Define the executable target before linking libraries
add_executable(mcrogueface ${SOURCES})
@ -67,9 +67,9 @@ add_custom_command(TARGET mcrogueface POST_BUILD
# Copy Python standard library to build directory
add_custom_command(TARGET mcrogueface POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrogueface>/lib)
${CMAKE_SOURCE_DIR}/lib $<TARGET_FILE_DIR:mcrogueface>/lib)
# rpath for including shared libraries
set_target_properties(mcrogueface PROPERTIES
INSTALL_RPATH "$ORIGIN/./lib")
INSTALL_RPATH "./lib")

View File

@ -1,54 +0,0 @@
# Convenience Makefile wrapper for McRogueFace
# This delegates to CMake build in the build directory
.PHONY: all build clean run test dist help
# Default target
all: build
# Build the project
build:
@./build.sh
# Clean build artifacts
clean:
@./clean.sh
# Run the game
run: build
@cd build && ./mcrogueface
# Run in Python mode
python: build
@cd build && ./mcrogueface -i
# Test basic functionality
test: build
@echo "Testing McRogueFace..."
@cd build && ./mcrogueface -V
@cd build && ./mcrogueface -c "print('Test passed')"
@cd build && ./mcrogueface --headless -c "import mcrfpy; print('mcrfpy imported successfully')"
# Create distribution archive
dist: build
@echo "Creating distribution archive..."
@cd build && zip -r ../McRogueFace-$$(date +%Y%m%d).zip . -x "*.o" "CMakeFiles/*" "Makefile" "*.cmake"
@echo "Distribution archive created: McRogueFace-$$(date +%Y%m%d).zip"
# Show help
help:
@echo "McRogueFace Build System"
@echo "======================="
@echo ""
@echo "Available targets:"
@echo " make - Build the project (default)"
@echo " make build - Build the project"
@echo " make clean - Remove all build artifacts"
@echo " make run - Build and run the game"
@echo " make python - Build and run in Python interactive mode"
@echo " make test - Run basic tests"
@echo " make dist - Create distribution archive"
@echo " make help - Show this help message"
@echo ""
@echo "Build output goes to: ./build/"
@echo "Distribution archives are created in project root"

104
README.md
View File

@ -1,88 +1,30 @@
# McRogueFace
# McRogueFace - 2D Game Engine
An experimental prototype game engine built for my own use in 7DRL 2023.
*Blame my wife for the name*
A Python-powered 2D game engine for creating roguelike games, built with C++ and SFML.
## Tenets:
**Pre-Alpha Release Demo**: my 7DRL 2025 entry *"Crypt of Sokoban"* - a prototype with buttons, boulders, enemies, and items.
* C++ first, Python close behind.
* Entity-Component system based on David Churchill's Memorial University COMP4300 course lectures available on Youtube.
* Graphics, particles and shaders provided by SFML.
* Pathfinding, noise generation, and other Roguelike goodness provided by TCOD.
## Tenets
## Why?
- **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
I did the r/RoguelikeDev TCOD tutorial in Python. I loved it, but I did not want to be limited to ASCII. I want to be able to draw pixels on top of my tiles (like lines or circles) and eventually incorporate even more polish.
## Quick Start
## To-do
```bash
# Clone and build
git clone <wherever you found this repo>
cd McRogueFace
make
# Run the example game
cd build
./mcrogueface
```
## Example: Creating a Simple Scene
```python
import mcrfpy
# Create a new scene
mcrfpy.createScene("intro")
# Add a text caption
caption = mcrfpy.Caption((50, 50), "Welcome to McRogueFace!")
caption.size = 48
caption.fill_color = (255, 255, 255)
# Add to scene
mcrfpy.sceneUI("intro").append(caption)
# Switch to the scene
mcrfpy.setScene("intro")
```
## Documentation
For comprehensive documentation, tutorials, and API reference, visit:
**[https://mcrogueface.github.io](https://mcrogueface.github.io)**
## Requirements
- C++17 compiler (GCC 7+ or Clang 5+)
- CMake 3.14+
- Python 3.12+
- SFML 2.5+
- Linux or Windows (macOS untested)
## Project Structure
```
McRogueFace/
├── src/ # C++ engine source
├── scripts/ # Python game scripts
├── assets/ # Sprites, fonts, audio
├── build/ # Build output directory
└── tests/ # Automated test suite
```
## 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.
The project has a private roadmap and issue list. Reach out via email or social media if you have bugs or feature requests.
## License
This project is licensed under the MIT License - see LICENSE file for details.
## Acknowledgments
- Developed for 7-Day Roguelike 2023, 2024, 2025 - here's to many more
- Built with [SFML](https://www.sfml-dev.org/), [libtcod](https://github.com/libtcod/libtcod), and Python
- Inspired by David Churchill's COMP4300 game engine lectures
* ✅ Initial Commit
* ✅ Integrate scene, action, entity, component system from COMP4300 engine
* ✅ Windows / Visual Studio project
* ✅ Draw Sprites
* ✅ Play Sounds
* ✅ Draw UI, spawn entity from Python code
* ❌ Python AI for entities (NPCs on set paths, enemies towards player)
* ✅ Walking / Collision
* ❌ "Boards" (stairs / doors / walk off edge of screen)
* ❌ Cutscenes - interrupt normal controls, text scroll, character portraits
* ❌ Mouse integration - tooltips, zoom, click to select targets, cursors

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,112 +0,0 @@
[
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/GameEngine.cpp.o -c /home/john/Development/McRogueFace/src/GameEngine.cpp",
"file": "/home/john/Development/McRogueFace/src/GameEngine.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/IndexTexture.cpp.o -c /home/john/Development/McRogueFace/src/IndexTexture.cpp",
"file": "/home/john/Development/McRogueFace/src/IndexTexture.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/McRFPy_API.cpp.o -c /home/john/Development/McRogueFace/src/McRFPy_API.cpp",
"file": "/home/john/Development/McRogueFace/src/McRFPy_API.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/PyCallable.cpp.o -c /home/john/Development/McRogueFace/src/PyCallable.cpp",
"file": "/home/john/Development/McRogueFace/src/PyCallable.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/PyColor.cpp.o -c /home/john/Development/McRogueFace/src/PyColor.cpp",
"file": "/home/john/Development/McRogueFace/src/PyColor.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/PyFont.cpp.o -c /home/john/Development/McRogueFace/src/PyFont.cpp",
"file": "/home/john/Development/McRogueFace/src/PyFont.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/PyScene.cpp.o -c /home/john/Development/McRogueFace/src/PyScene.cpp",
"file": "/home/john/Development/McRogueFace/src/PyScene.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/PyTexture.cpp.o -c /home/john/Development/McRogueFace/src/PyTexture.cpp",
"file": "/home/john/Development/McRogueFace/src/PyTexture.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/PyVector.cpp.o -c /home/john/Development/McRogueFace/src/PyVector.cpp",
"file": "/home/john/Development/McRogueFace/src/PyVector.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/Resources.cpp.o -c /home/john/Development/McRogueFace/src/Resources.cpp",
"file": "/home/john/Development/McRogueFace/src/Resources.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/Scene.cpp.o -c /home/john/Development/McRogueFace/src/Scene.cpp",
"file": "/home/john/Development/McRogueFace/src/Scene.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/Timer.cpp.o -c /home/john/Development/McRogueFace/src/Timer.cpp",
"file": "/home/john/Development/McRogueFace/src/Timer.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UICaption.cpp.o -c /home/john/Development/McRogueFace/src/UICaption.cpp",
"file": "/home/john/Development/McRogueFace/src/UICaption.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UICollection.cpp.o -c /home/john/Development/McRogueFace/src/UICollection.cpp",
"file": "/home/john/Development/McRogueFace/src/UICollection.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UIDrawable.cpp.o -c /home/john/Development/McRogueFace/src/UIDrawable.cpp",
"file": "/home/john/Development/McRogueFace/src/UIDrawable.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UIEntity.cpp.o -c /home/john/Development/McRogueFace/src/UIEntity.cpp",
"file": "/home/john/Development/McRogueFace/src/UIEntity.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UIFrame.cpp.o -c /home/john/Development/McRogueFace/src/UIFrame.cpp",
"file": "/home/john/Development/McRogueFace/src/UIFrame.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UIGrid.cpp.o -c /home/john/Development/McRogueFace/src/UIGrid.cpp",
"file": "/home/john/Development/McRogueFace/src/UIGrid.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UIGridPoint.cpp.o -c /home/john/Development/McRogueFace/src/UIGridPoint.cpp",
"file": "/home/john/Development/McRogueFace/src/UIGridPoint.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UISprite.cpp.o -c /home/john/Development/McRogueFace/src/UISprite.cpp",
"file": "/home/john/Development/McRogueFace/src/UISprite.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/UITestScene.cpp.o -c /home/john/Development/McRogueFace/src/UITestScene.cpp",
"file": "/home/john/Development/McRogueFace/src/UITestScene.cpp"
},
{
"directory": "/home/john/Development/McRogueFace/build",
"command": "/usr/bin/c++ -I/home/john/Development/McRogueFace/deps -I/home/john/Development/McRogueFace/deps/libtcod -I/home/john/Development/McRogueFace/deps/cpython -I/home/john/Development/McRogueFace/deps/Python -I/home/john/Development/McRogueFace/deps/platform/linux -g -std=gnu++2a -o CMakeFiles/mcrogueface.dir/src/main.cpp.o -c /home/john/Development/McRogueFace/src/main.cpp",
"file": "/home/john/Development/McRogueFace/src/main.cpp"
}
]

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

View File

@ -1,111 +0,0 @@
# data sources: CSS docs, jennyscrayoncollection 2017 article on Crayola colors, XKCD color survey
# target: Single C++ header file to provide a struct of color RGB codes and names.
# This file pre-computes the nearest neighbor of every color.
# if an RGB code being searched for is closer than the nearest neighbor, it's the closest color name.
def hex_to_rgb(txt):
if '#' in txt: txt = txt.replace('#', '')
r = txt[0:2]
g = txt[2:4]
b = txt[4:6]
return tuple([int(s, 16) for s in (r,g,b)])
class palette:
def __init__(self, name, filename, priority):
self.name = name
self.priority = priority
with open(filename, "r") as f:
print(f"scanning {filename}")
self.colors = {}
for line in f.read().split('\n'):
if len(line.split('\t')) < 2: continue
name, code = line.split('\t')
#print(name, code)
self.colors[name] = hex_to_rgb(code)
def __repr__(self):
return f"<Palette '{self.name}' - {len(self.colors)} colors, priority = {self.priority}>"
palettes = [
#palette("jenny", "jenny_colors.txt", 3), # I should probably use wikipedia as a source for copyright reasons
palette("crayon", "wikicrayons_colors.txt", 2),
palette("xkcd", "xkcd_colors.txt", 1),
palette("css", "css_colors.txt", 0),
#palette("matplotlib", "matplotlib_colors.txt", 2) # there's like 10 colors total, I think we'll survive without them
]
all_colors = []
from math import sqrt
def rgbdist(c1, c2):
return sqrt((c1.r - c2.r)**2 + (c1.g - c2.g)**2 + (c1.b - c2.b)**2)
class Color:
def __init__(self, r, g, b, name, prefix, priority):
self.r = r
self.g = g
self.b = b
self.name = name
self.prefix = prefix
self.priority = priority
self.nearest_neighbor = None
def __repr__(self):
return f"<Color ({self.r}, {self.g}, {self.b}) - '{self.prefix}:{self.name}', priority = {self.priority}, nearest_neighbor={self.nearest_neighbor.name if self.nearest_neighbor is not None else None}>"
def nn(self, colors):
nearest = None
nearest_dist = 999999
for c in colors:
dist = rgbdist(self, c)
if dist == 0: continue
if dist < nearest_dist:
nearest = c
nearest_dist = dist
self.nearest_neighbor = nearest
for p in palettes:
prefix = p.name
priority = p.priority
for name, rgb in p.colors.items():
all_colors.append(Color(*rgb, name, prefix, priority))
print(f"{prefix}->{len(all_colors)}")
for c in all_colors:
c.nn(all_colors)
smallest_dist = 9999999999999
largest_dist = 0
for c in all_colors:
dist = rgbdist(c, c.nearest_neighbor)
if dist > largest_dist: largest_dist = dist
if dist < smallest_dist: smallest_dist = dist
#print(f"{c.prefix}:{c.name} -> {c.nearest_neighbor.prefix}:{c.nearest_neighbor.name}\t{rgbdist(c, c.nearest_neighbor):.2f}")
# questions -
# are there any colors where their nearest neighbor's nearest neighbor isn't them? (There should be)
nonnear_pairs = 0
for c in all_colors:
neighbor = c.nearest_neighbor
their_neighbor = neighbor.nearest_neighbor
if c is not their_neighbor:
#print(f"{c.prefix}:{c.name} -> {neighbor.prefix}:{neighbor.name} -> {their_neighbor.prefix}:{their_neighbor.name}")
nonnear_pairs += 1
print("Non-near pairs:", nonnear_pairs)
#print(f"{c.prefix}:{c.name} -> {c.nearest_neighbor.prefix}:{c.nearest_neighbor.name}\t{rgbdist(c, c.nearest_neighbor):.2f}")
# Are there duplicates? They should be removed from the palette that won't be used
dupes = 0
for c in all_colors:
for c2 in all_colors:
if c is c2: continue
if c.r == c2.r and c.g == c2.g and c.b == c2.b:
dupes += 1
print("dupes:", dupes, "this many will need to be removed:", dupes / 2)
# What order to put them in? Do we want large radiuses first, or some sort of "common color" table?
# does manhattan distance change any answers over the 16.7M RGB values?
# What's the worst case lookup? (Checking all 1200 colors to find the name?)

View File

@ -11,10 +11,10 @@ public:
const static int WHEEL_NUM = 4;
const static int WHEEL_NEG = 2;
const static int WHEEL_DEL = 1;
static int keycode(const sf::Keyboard::Key& k) { return KEY + (int)k; }
static int keycode(const sf::Mouse::Button& b) { return MOUSEBUTTON + (int)b; }
static int keycode(sf::Keyboard::Key& k) { return KEY + (int)k; }
static int keycode(sf::Mouse::Button& b) { return MOUSEBUTTON + (int)b; }
//static int keycode(sf::Mouse::Wheel& w, float d) { return MOUSEWHEEL + (((int)w)<<12) + int(d*16) + 512; }
static int keycode(const sf::Mouse::Wheel& w, float d) {
static int keycode(sf::Mouse::Wheel& w, float d) {
int neg = 0;
if (d < 0) { neg = 1; }
return MOUSEWHEEL + (w * WHEEL_NUM) + (neg * WHEEL_NEG) + 1;
@ -32,7 +32,7 @@ public:
return (a & WHEEL_DEL) * factor;
}
static std::string key_str(const sf::Keyboard::Key& keycode)
static std::string key_str(sf::Keyboard::Key& keycode)
{
switch(keycode)
{

View File

@ -1,527 +0,0 @@
#include "Animation.h"
#include "UIDrawable.h"
#include "UIEntity.h"
#include <cmath>
#include <algorithm>
#include <unordered_map>
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
// Animation implementation
Animation::Animation(const std::string& targetProperty,
const AnimationValue& targetValue,
float duration,
EasingFunction easingFunc,
bool delta)
: targetProperty(targetProperty)
, targetValue(targetValue)
, duration(duration)
, easingFunc(easingFunc)
, delta(delta)
{
}
void Animation::start(UIDrawable* target) {
currentTarget = target;
elapsed = 0.0f;
// Capture startValue from target based on targetProperty
if (!currentTarget) return;
// Try to get the current value based on the expected type
std::visit([this](const auto& targetVal) {
using T = std::decay_t<decltype(targetVal)>;
if constexpr (std::is_same_v<T, float>) {
float value;
if (currentTarget->getProperty(targetProperty, value)) {
startValue = value;
}
}
else if constexpr (std::is_same_v<T, int>) {
int value;
if (currentTarget->getProperty(targetProperty, value)) {
startValue = value;
}
}
else if constexpr (std::is_same_v<T, std::vector<int>>) {
// For sprite animation, get current sprite index
int value;
if (currentTarget->getProperty(targetProperty, value)) {
startValue = value;
}
}
else if constexpr (std::is_same_v<T, sf::Color>) {
sf::Color value;
if (currentTarget->getProperty(targetProperty, value)) {
startValue = value;
}
}
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
sf::Vector2f value;
if (currentTarget->getProperty(targetProperty, value)) {
startValue = value;
}
}
else if constexpr (std::is_same_v<T, std::string>) {
std::string value;
if (currentTarget->getProperty(targetProperty, value)) {
startValue = value;
}
}
}, targetValue);
}
void Animation::startEntity(UIEntity* target) {
currentEntityTarget = target;
currentTarget = nullptr; // Clear drawable target
elapsed = 0.0f;
// Capture the starting value from the entity
std::visit([this, target](const auto& val) {
using T = std::decay_t<decltype(val)>;
if constexpr (std::is_same_v<T, float>) {
float value = 0.0f;
if (target->getProperty(targetProperty, value)) {
startValue = value;
}
}
else if constexpr (std::is_same_v<T, int>) {
// For entities, we might need to handle sprite_index differently
if (targetProperty == "sprite_index" || targetProperty == "sprite_number") {
startValue = target->sprite.getSpriteIndex();
}
}
// Entities don't support other types yet
}, targetValue);
}
bool Animation::update(float deltaTime) {
if ((!currentTarget && !currentEntityTarget) || isComplete()) {
return false;
}
elapsed += deltaTime;
elapsed = std::min(elapsed, duration);
// Calculate easing value (0.0 to 1.0)
float t = duration > 0 ? elapsed / duration : 1.0f;
float easedT = easingFunc(t);
// Get interpolated value
AnimationValue currentValue = interpolate(easedT);
// Apply currentValue to target (either drawable or entity)
std::visit([this](const auto& value) {
using T = std::decay_t<decltype(value)>;
if (currentTarget) {
// Handle UIDrawable targets
if constexpr (std::is_same_v<T, float>) {
currentTarget->setProperty(targetProperty, value);
}
else if constexpr (std::is_same_v<T, int>) {
currentTarget->setProperty(targetProperty, value);
}
else if constexpr (std::is_same_v<T, sf::Color>) {
currentTarget->setProperty(targetProperty, value);
}
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
currentTarget->setProperty(targetProperty, value);
}
else if constexpr (std::is_same_v<T, std::string>) {
currentTarget->setProperty(targetProperty, value);
}
}
else if (currentEntityTarget) {
// Handle UIEntity targets
if constexpr (std::is_same_v<T, float>) {
currentEntityTarget->setProperty(targetProperty, value);
}
else if constexpr (std::is_same_v<T, int>) {
currentEntityTarget->setProperty(targetProperty, value);
}
// Entities don't support other types yet
}
}, currentValue);
return !isComplete();
}
AnimationValue Animation::getCurrentValue() const {
float t = duration > 0 ? elapsed / duration : 1.0f;
float easedT = easingFunc(t);
return interpolate(easedT);
}
AnimationValue Animation::interpolate(float t) const {
// Visit the variant to perform type-specific interpolation
return std::visit([this, t](const auto& target) -> AnimationValue {
using T = std::decay_t<decltype(target)>;
if constexpr (std::is_same_v<T, float>) {
// Interpolate float
const float* start = std::get_if<float>(&startValue);
if (!start) return target; // Type mismatch
if (delta) {
return *start + target * t;
} else {
return *start + (target - *start) * t;
}
}
else if constexpr (std::is_same_v<T, int>) {
// Interpolate integer
const int* start = std::get_if<int>(&startValue);
if (!start) return target;
float result;
if (delta) {
result = *start + target * t;
} else {
result = *start + (target - *start) * t;
}
return static_cast<int>(std::round(result));
}
else if constexpr (std::is_same_v<T, std::vector<int>>) {
// For sprite animation, interpolate through the list
if (target.empty()) return target;
// Map t to an index in the vector
size_t index = static_cast<size_t>(t * (target.size() - 1));
index = std::min(index, target.size() - 1);
return static_cast<int>(target[index]);
}
else if constexpr (std::is_same_v<T, sf::Color>) {
// Interpolate color
const sf::Color* start = std::get_if<sf::Color>(&startValue);
if (!start) return target;
sf::Color result;
if (delta) {
result.r = std::clamp(start->r + target.r * t, 0.0f, 255.0f);
result.g = std::clamp(start->g + target.g * t, 0.0f, 255.0f);
result.b = std::clamp(start->b + target.b * t, 0.0f, 255.0f);
result.a = std::clamp(start->a + target.a * t, 0.0f, 255.0f);
} else {
result.r = start->r + (target.r - start->r) * t;
result.g = start->g + (target.g - start->g) * t;
result.b = start->b + (target.b - start->b) * t;
result.a = start->a + (target.a - start->a) * t;
}
return result;
}
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
// Interpolate vector
const sf::Vector2f* start = std::get_if<sf::Vector2f>(&startValue);
if (!start) return target;
if (delta) {
return sf::Vector2f(start->x + target.x * t,
start->y + target.y * t);
} else {
return sf::Vector2f(start->x + (target.x - start->x) * t,
start->y + (target.y - start->y) * t);
}
}
else if constexpr (std::is_same_v<T, std::string>) {
// For text, show characters based on t
const std::string* start = std::get_if<std::string>(&startValue);
if (!start) return target;
// If delta mode, append characters from target
if (delta) {
size_t chars = static_cast<size_t>(target.length() * t);
return *start + target.substr(0, chars);
} else {
// Transition from start text to target text
if (t < 0.5f) {
// First half: remove characters from start
size_t chars = static_cast<size_t>(start->length() * (1.0f - t * 2.0f));
return start->substr(0, chars);
} else {
// Second half: add characters to target
size_t chars = static_cast<size_t>(target.length() * ((t - 0.5f) * 2.0f));
return target.substr(0, chars);
}
}
}
return target; // Fallback
}, targetValue);
}
// Easing functions implementation
namespace EasingFunctions {
float linear(float t) {
return t;
}
float easeIn(float t) {
return t * t;
}
float easeOut(float t) {
return t * (2.0f - t);
}
float easeInOut(float t) {
return t < 0.5f ? 2.0f * t * t : -1.0f + (4.0f - 2.0f * t) * t;
}
// Quadratic
float easeInQuad(float t) {
return t * t;
}
float easeOutQuad(float t) {
return t * (2.0f - t);
}
float easeInOutQuad(float t) {
return t < 0.5f ? 2.0f * t * t : -1.0f + (4.0f - 2.0f * t) * t;
}
// Cubic
float easeInCubic(float t) {
return t * t * t;
}
float easeOutCubic(float t) {
float t1 = t - 1.0f;
return t1 * t1 * t1 + 1.0f;
}
float easeInOutCubic(float t) {
return t < 0.5f ? 4.0f * t * t * t : (t - 1.0f) * (2.0f * t - 2.0f) * (2.0f * t - 2.0f) + 1.0f;
}
// Quartic
float easeInQuart(float t) {
return t * t * t * t;
}
float easeOutQuart(float t) {
float t1 = t - 1.0f;
return 1.0f - t1 * t1 * t1 * t1;
}
float easeInOutQuart(float t) {
return t < 0.5f ? 8.0f * t * t * t * t : 1.0f - 8.0f * (t - 1.0f) * (t - 1.0f) * (t - 1.0f) * (t - 1.0f);
}
// Sine
float easeInSine(float t) {
return 1.0f - std::cos(t * M_PI / 2.0f);
}
float easeOutSine(float t) {
return std::sin(t * M_PI / 2.0f);
}
float easeInOutSine(float t) {
return 0.5f * (1.0f - std::cos(M_PI * t));
}
// Exponential
float easeInExpo(float t) {
return t == 0.0f ? 0.0f : std::pow(2.0f, 10.0f * (t - 1.0f));
}
float easeOutExpo(float t) {
return t == 1.0f ? 1.0f : 1.0f - std::pow(2.0f, -10.0f * t);
}
float easeInOutExpo(float t) {
if (t == 0.0f) return 0.0f;
if (t == 1.0f) return 1.0f;
if (t < 0.5f) {
return 0.5f * std::pow(2.0f, 20.0f * t - 10.0f);
} else {
return 1.0f - 0.5f * std::pow(2.0f, -20.0f * t + 10.0f);
}
}
// Circular
float easeInCirc(float t) {
return 1.0f - std::sqrt(1.0f - t * t);
}
float easeOutCirc(float t) {
float t1 = t - 1.0f;
return std::sqrt(1.0f - t1 * t1);
}
float easeInOutCirc(float t) {
if (t < 0.5f) {
return 0.5f * (1.0f - std::sqrt(1.0f - 4.0f * t * t));
} else {
return 0.5f * (std::sqrt(1.0f - (2.0f * t - 2.0f) * (2.0f * t - 2.0f)) + 1.0f);
}
}
// Elastic
float easeInElastic(float t) {
if (t == 0.0f) return 0.0f;
if (t == 1.0f) return 1.0f;
float p = 0.3f;
float a = 1.0f;
float s = p / 4.0f;
float t1 = t - 1.0f;
return -(a * std::pow(2.0f, 10.0f * t1) * std::sin((t1 - s) * (2.0f * M_PI) / p));
}
float easeOutElastic(float t) {
if (t == 0.0f) return 0.0f;
if (t == 1.0f) return 1.0f;
float p = 0.3f;
float a = 1.0f;
float s = p / 4.0f;
return a * std::pow(2.0f, -10.0f * t) * std::sin((t - s) * (2.0f * M_PI) / p) + 1.0f;
}
float easeInOutElastic(float t) {
if (t == 0.0f) return 0.0f;
if (t == 1.0f) return 1.0f;
float p = 0.45f;
float a = 1.0f;
float s = p / 4.0f;
if (t < 0.5f) {
float t1 = 2.0f * t - 1.0f;
return -0.5f * (a * std::pow(2.0f, 10.0f * t1) * std::sin((t1 - s) * (2.0f * M_PI) / p));
} else {
float t1 = 2.0f * t - 1.0f;
return a * std::pow(2.0f, -10.0f * t1) * std::sin((t1 - s) * (2.0f * M_PI) / p) * 0.5f + 1.0f;
}
}
// Back (overshooting)
float easeInBack(float t) {
const float s = 1.70158f;
return t * t * ((s + 1.0f) * t - s);
}
float easeOutBack(float t) {
const float s = 1.70158f;
float t1 = t - 1.0f;
return t1 * t1 * ((s + 1.0f) * t1 + s) + 1.0f;
}
float easeInOutBack(float t) {
const float s = 1.70158f * 1.525f;
if (t < 0.5f) {
return 0.5f * (4.0f * t * t * ((s + 1.0f) * 2.0f * t - s));
} else {
float t1 = 2.0f * t - 2.0f;
return 0.5f * (t1 * t1 * ((s + 1.0f) * t1 + s) + 2.0f);
}
}
// Bounce
float easeOutBounce(float t) {
if (t < 1.0f / 2.75f) {
return 7.5625f * t * t;
} else if (t < 2.0f / 2.75f) {
float t1 = t - 1.5f / 2.75f;
return 7.5625f * t1 * t1 + 0.75f;
} else if (t < 2.5f / 2.75f) {
float t1 = t - 2.25f / 2.75f;
return 7.5625f * t1 * t1 + 0.9375f;
} else {
float t1 = t - 2.625f / 2.75f;
return 7.5625f * t1 * t1 + 0.984375f;
}
}
float easeInBounce(float t) {
return 1.0f - easeOutBounce(1.0f - t);
}
float easeInOutBounce(float t) {
if (t < 0.5f) {
return 0.5f * easeInBounce(2.0f * t);
} else {
return 0.5f * easeOutBounce(2.0f * t - 1.0f) + 0.5f;
}
}
// Get easing function by name
EasingFunction getByName(const std::string& name) {
static std::unordered_map<std::string, EasingFunction> easingMap = {
{"linear", linear},
{"easeIn", easeIn},
{"easeOut", easeOut},
{"easeInOut", easeInOut},
{"easeInQuad", easeInQuad},
{"easeOutQuad", easeOutQuad},
{"easeInOutQuad", easeInOutQuad},
{"easeInCubic", easeInCubic},
{"easeOutCubic", easeOutCubic},
{"easeInOutCubic", easeInOutCubic},
{"easeInQuart", easeInQuart},
{"easeOutQuart", easeOutQuart},
{"easeInOutQuart", easeInOutQuart},
{"easeInSine", easeInSine},
{"easeOutSine", easeOutSine},
{"easeInOutSine", easeInOutSine},
{"easeInExpo", easeInExpo},
{"easeOutExpo", easeOutExpo},
{"easeInOutExpo", easeInOutExpo},
{"easeInCirc", easeInCirc},
{"easeOutCirc", easeOutCirc},
{"easeInOutCirc", easeInOutCirc},
{"easeInElastic", easeInElastic},
{"easeOutElastic", easeOutElastic},
{"easeInOutElastic", easeInOutElastic},
{"easeInBack", easeInBack},
{"easeOutBack", easeOutBack},
{"easeInOutBack", easeInOutBack},
{"easeInBounce", easeInBounce},
{"easeOutBounce", easeOutBounce},
{"easeInOutBounce", easeInOutBounce}
};
auto it = easingMap.find(name);
if (it != easingMap.end()) {
return it->second;
}
return linear; // Default to linear
}
} // namespace EasingFunctions
// AnimationManager implementation
AnimationManager& AnimationManager::getInstance() {
static AnimationManager instance;
return instance;
}
void AnimationManager::addAnimation(std::shared_ptr<Animation> animation) {
activeAnimations.push_back(animation);
}
void AnimationManager::update(float deltaTime) {
for (auto& anim : activeAnimations) {
anim->update(deltaTime);
}
cleanup();
}
void AnimationManager::cleanup() {
activeAnimations.erase(
std::remove_if(activeAnimations.begin(), activeAnimations.end(),
[](const std::shared_ptr<Animation>& anim) {
return anim->isComplete();
}),
activeAnimations.end()
);
}
void AnimationManager::clear() {
activeAnimations.clear();
}

View File

@ -1,146 +0,0 @@
#pragma once
#include <string>
#include <functional>
#include <memory>
#include <variant>
#include <vector>
#include <SFML/Graphics.hpp>
// Forward declarations
class UIDrawable;
class UIEntity;
// Forward declare namespace
namespace EasingFunctions {
float linear(float t);
}
// Easing function type
typedef std::function<float(float)> EasingFunction;
// Animation target value can be various types
typedef std::variant<
float, // Single float value
int, // Single integer value
std::vector<int>, // List of integers (for sprite animation)
sf::Color, // Color animation
sf::Vector2f, // Vector animation
std::string // String animation (for text)
> AnimationValue;
class Animation {
public:
// Constructor
Animation(const std::string& targetProperty,
const AnimationValue& targetValue,
float duration,
EasingFunction easingFunc = EasingFunctions::linear,
bool delta = false);
// Apply this animation to a drawable
void start(UIDrawable* target);
// Apply this animation to an entity (special case since Entity doesn't inherit from UIDrawable)
void startEntity(UIEntity* target);
// Update animation (called each frame)
// Returns true if animation is still running, false if complete
bool update(float deltaTime);
// Get current interpolated value
AnimationValue getCurrentValue() const;
// Animation properties
std::string getTargetProperty() const { return targetProperty; }
float getDuration() const { return duration; }
float getElapsed() const { return elapsed; }
bool isComplete() const { return elapsed >= duration; }
bool isDelta() const { return delta; }
private:
std::string targetProperty; // Property name to animate (e.g., "x", "color.r", "sprite_number")
AnimationValue startValue; // Starting value (captured when animation starts)
AnimationValue targetValue; // Target value to animate to
float duration; // Animation duration in seconds
float elapsed = 0.0f; // Elapsed time
EasingFunction easingFunc; // Easing function to use
bool delta; // If true, targetValue is relative to start
UIDrawable* currentTarget = nullptr; // Current target being animated
UIEntity* currentEntityTarget = nullptr; // Current entity target (alternative to drawable)
// Helper to interpolate between values
AnimationValue interpolate(float t) const;
};
// Easing functions library
namespace EasingFunctions {
// Basic easing functions
float linear(float t);
float easeIn(float t);
float easeOut(float t);
float easeInOut(float t);
// Advanced easing functions
float easeInQuad(float t);
float easeOutQuad(float t);
float easeInOutQuad(float t);
float easeInCubic(float t);
float easeOutCubic(float t);
float easeInOutCubic(float t);
float easeInQuart(float t);
float easeOutQuart(float t);
float easeInOutQuart(float t);
float easeInSine(float t);
float easeOutSine(float t);
float easeInOutSine(float t);
float easeInExpo(float t);
float easeOutExpo(float t);
float easeInOutExpo(float t);
float easeInCirc(float t);
float easeOutCirc(float t);
float easeInOutCirc(float t);
float easeInElastic(float t);
float easeOutElastic(float t);
float easeInOutElastic(float t);
float easeInBack(float t);
float easeOutBack(float t);
float easeInOutBack(float t);
float easeInBounce(float t);
float easeOutBounce(float t);
float easeInOutBounce(float t);
// Get easing function by name
EasingFunction getByName(const std::string& name);
}
// Animation manager to handle active animations
class AnimationManager {
public:
static AnimationManager& getInstance();
// Add an animation to be managed
void addAnimation(std::shared_ptr<Animation> animation);
// Update all animations
void update(float deltaTime);
// Remove completed animations
void cleanup();
// Clear all animations
void clear();
private:
AnimationManager() = default;
std::vector<std::shared_ptr<Animation>> activeAnimations;
};

View File

@ -1,172 +0,0 @@
#include "CommandLineParser.h"
#include <iostream>
#include <filesystem>
#include <algorithm>
CommandLineParser::CommandLineParser(int argc, char* argv[])
: argc(argc), argv(argv) {}
CommandLineParser::ParseResult CommandLineParser::parse(McRogueFaceConfig& config) {
ParseResult result;
current_arg = 1; // Reset for each parse
// Detect if running as Python interpreter
std::filesystem::path exec_name = std::filesystem::path(argv[0]).filename();
if (exec_name.string().find("python") == 0) {
config.headless = true;
config.python_mode = true;
}
while (current_arg < argc) {
std::string arg = argv[current_arg];
// Handle Python-style single-letter flags
if (arg == "-h" || arg == "--help") {
print_help();
result.should_exit = true;
result.exit_code = 0;
return result;
}
if (arg == "-V" || arg == "--version") {
print_version();
result.should_exit = true;
result.exit_code = 0;
return result;
}
// Python execution modes
if (arg == "-c") {
config.python_mode = true;
current_arg++;
if (current_arg >= argc) {
std::cerr << "Argument expected for the -c option" << std::endl;
result.should_exit = true;
result.exit_code = 1;
return result;
}
config.python_command = argv[current_arg];
current_arg++;
continue;
}
if (arg == "-m") {
config.python_mode = true;
current_arg++;
if (current_arg >= argc) {
std::cerr << "Argument expected for the -m option" << std::endl;
result.should_exit = true;
result.exit_code = 1;
return result;
}
config.python_module = argv[current_arg];
current_arg++;
// Collect remaining args as module args
while (current_arg < argc) {
config.script_args.push_back(argv[current_arg]);
current_arg++;
}
continue;
}
if (arg == "-i") {
config.interactive_mode = true;
config.python_mode = true;
current_arg++;
continue;
}
// McRogueFace specific flags
if (arg == "--headless") {
config.headless = true;
config.audio_enabled = false;
current_arg++;
continue;
}
if (arg == "--audio-off") {
config.audio_enabled = false;
current_arg++;
continue;
}
if (arg == "--audio-on") {
config.audio_enabled = true;
current_arg++;
continue;
}
if (arg == "--screenshot") {
config.take_screenshot = true;
current_arg++;
if (current_arg < argc && argv[current_arg][0] != '-') {
config.screenshot_path = argv[current_arg];
current_arg++;
} else {
config.screenshot_path = "screenshot.png";
}
continue;
}
if (arg == "--exec") {
current_arg++;
if (current_arg >= argc) {
std::cerr << "Argument expected for the --exec option" << std::endl;
result.should_exit = true;
result.exit_code = 1;
return result;
}
config.exec_scripts.push_back(argv[current_arg]);
config.python_mode = true;
current_arg++;
continue;
}
// If no flags matched, treat as positional argument (script name)
if (arg[0] != '-') {
config.script_path = arg;
config.python_mode = true;
current_arg++;
// Remaining args are script args
while (current_arg < argc) {
config.script_args.push_back(argv[current_arg]);
current_arg++;
}
break;
}
// Unknown flag
std::cerr << "Unknown option: " << arg << std::endl;
result.should_exit = true;
result.exit_code = 1;
return result;
}
return result;
}
void CommandLineParser::print_help() {
std::cout << "usage: mcrogueface [option] ... [-c cmd | -m mod | file | -] [arg] ...\n"
<< "Options:\n"
<< " -c cmd : program passed in as string (terminates option list)\n"
<< " -h : print this help message and exit (also --help)\n"
<< " -i : inspect interactively after running script\n"
<< " -m mod : run library module as a script (terminates option list)\n"
<< " -V : print the Python version number and exit (also --version)\n"
<< "\n"
<< "McRogueFace specific options:\n"
<< " --exec file : execute script before main program (can be used multiple times)\n"
<< " --headless : run without creating a window (implies --audio-off)\n"
<< " --audio-off : disable audio\n"
<< " --audio-on : enable audio (even in headless mode)\n"
<< " --screenshot [path] : take a screenshot in headless mode\n"
<< "\n"
<< "Arguments:\n"
<< " file : program read from script file\n"
<< " - : program read from stdin\n"
<< " arg ...: arguments passed to program in sys.argv[1:]\n";
}
void CommandLineParser::print_version() {
std::cout << "Python 3.12.0 (McRogueFace embedded)\n";
}

View File

@ -1,30 +0,0 @@
#ifndef COMMAND_LINE_PARSER_H
#define COMMAND_LINE_PARSER_H
#include <string>
#include <vector>
#include "McRogueFaceConfig.h"
class CommandLineParser {
public:
struct ParseResult {
bool should_exit = false;
int exit_code = 0;
};
CommandLineParser(int argc, char* argv[]);
ParseResult parse(McRogueFaceConfig& config);
private:
int argc;
char** argv;
int current_arg = 1; // Skip program name
bool has_flag(const std::string& short_flag, const std::string& long_flag = "");
std::string get_next_arg(const std::string& flag_name);
void parse_positional_args(McRogueFaceConfig& config);
void print_help();
void print_version();
};
#endif // COMMAND_LINE_PARSER_H

View File

@ -4,80 +4,27 @@
#include "PyScene.h"
#include "UITestScene.h"
#include "Resources.h"
#include "Animation.h"
GameEngine::GameEngine() : GameEngine(McRogueFaceConfig{})
{
}
GameEngine::GameEngine(const McRogueFaceConfig& cfg)
: config(cfg), headless(cfg.headless)
GameEngine::GameEngine()
{
Resources::font.loadFromFile("./assets/JetbrainsMono.ttf");
Resources::game = this;
window_title = "Crypt of Sokoban - 7DRL 2025, McRogueface Engine";
// Initialize rendering based on headless mode
if (headless) {
headless_renderer = std::make_unique<HeadlessRenderer>();
if (!headless_renderer->init(1024, 768)) {
throw std::runtime_error("Failed to initialize headless renderer");
}
render_target = &headless_renderer->getRenderTarget();
} else {
window = std::make_unique<sf::RenderWindow>();
window->create(sf::VideoMode(1024, 768), window_title, sf::Style::Titlebar | sf::Style::Close);
window->setFramerateLimit(60);
render_target = window.get();
}
visible = render_target->getDefaultView();
window_title = "McRogueFace - 7DRL 2024 Engine Demo";
window.create(sf::VideoMode(1024, 768), window_title, sf::Style::Titlebar | sf::Style::Close);
visible = window.getDefaultView();
window.setFramerateLimit(30);
scene = "uitest";
scenes["uitest"] = new UITestScene(this);
McRFPy_API::game = this;
// Only load game.py if no custom script/command/module/exec is specified
bool should_load_game = config.script_path.empty() &&
config.python_command.empty() &&
config.python_module.empty() &&
config.exec_scripts.empty() &&
!config.interactive_mode &&
!config.python_mode;
if (should_load_game) {
if (!Py_IsInitialized()) {
McRFPy_API::api_init();
}
McRFPy_API::executePyString("import mcrfpy");
McRFPy_API::executeScript("scripts/game.py");
}
// Execute any --exec scripts in order
if (!config.exec_scripts.empty()) {
if (!Py_IsInitialized()) {
McRFPy_API::api_init();
}
McRFPy_API::executePyString("import mcrfpy");
for (const auto& exec_script : config.exec_scripts) {
std::cout << "Executing script: " << exec_script << std::endl;
McRFPy_API::executeScript(exec_script.string());
}
std::cout << "All --exec scripts completed" << std::endl;
}
McRFPy_API::api_init();
McRFPy_API::executePyString("import mcrfpy");
McRFPy_API::executeScript("scripts/game.py");
clock.restart();
runtime.restart();
}
GameEngine::~GameEngine()
{
for (auto& [name, scene] : scenes) {
delete scene;
}
}
Scene* GameEngine::currentScene() { return scenes[scene]; }
void GameEngine::changeScene(std::string s)
{
@ -90,77 +37,33 @@ void GameEngine::changeScene(std::string s)
void GameEngine::quit() { running = false; }
void GameEngine::setPause(bool p) { paused = p; }
sf::Font & GameEngine::getFont() { /*return font; */ return Resources::font; }
sf::RenderWindow & GameEngine::getWindow() {
if (!window) {
throw std::runtime_error("Window not available in headless mode");
}
return *window;
}
sf::RenderTarget & GameEngine::getRenderTarget() {
return *render_target;
}
sf::RenderWindow & GameEngine::getWindow() { return window; }
void GameEngine::createScene(std::string s) { scenes[s] = new PyScene(this); }
void GameEngine::setWindowScale(float multiplier)
{
if (!headless && window) {
window->setSize(sf::Vector2u(1024 * multiplier, 768 * multiplier)); // 7DRL 2024: window scaling
}
window.setSize(sf::Vector2u(1024 * multiplier, 768 * multiplier)); // 7DRL 2024: window scaling
//window.create(sf::VideoMode(1024 * multiplier, 768 * multiplier), window_title, sf::Style::Titlebar | sf::Style::Close);
}
void GameEngine::run()
{
std::cout << "GameEngine::run() starting main loop..." << std::endl;
float fps = 0.0;
frameTime = 0.016f; // Initialize to ~60 FPS
clock.restart();
while (running)
{
currentScene()->update();
testTimers();
// Update animations (only if frameTime is valid)
if (frameTime > 0.0f && frameTime < 1.0f) {
AnimationManager::getInstance().update(frameTime);
}
if (!headless) {
sUserInput();
}
sUserInput();
if (!paused)
{
}
currentScene()->render();
// Display the frame
if (headless) {
headless_renderer->display();
// Take screenshot if requested
if (config.take_screenshot) {
headless_renderer->saveScreenshot(config.screenshot_path.empty() ? "screenshot.png" : config.screenshot_path);
config.take_screenshot = false; // Only take one screenshot
}
} else {
window->display();
}
currentScene()->sRender();
currentFrame++;
frameTime = clock.restart().asSeconds();
fps = 1 / frameTime;
int whole_fps = (int)fps;
int tenth_fps = int(fps * 100) % 10;
if (!headless && window) {
window->setTitle(window_title + " " + std::to_string(whole_fps) + "." + std::to_string(tenth_fps) + " FPS");
}
// In windowed mode, check if window was closed
if (!headless && window && !window->isOpen()) {
running = false;
}
window.setTitle(window_title + " " + std::to_string(fps) + " FPS");
}
}
@ -202,54 +105,86 @@ void GameEngine::testTimers()
}
}
void GameEngine::processEvent(const sf::Event& event)
{
std::string actionType;
int actionCode = 0;
if (event.type == sf::Event::Closed) { running = false; return; }
// TODO: add resize event to Scene to react; call it after constructor too, maybe
else if (event.type == sf::Event::Resized) {
return; // 7DRL short circuit. Resizing manually disabled
}
else if (event.type == sf::Event::KeyPressed || event.type == sf::Event::MouseButtonPressed || event.type == sf::Event::MouseWheelScrolled) actionType = "start";
else if (event.type == sf::Event::KeyReleased || event.type == sf::Event::MouseButtonReleased) actionType = "end";
if (event.type == sf::Event::MouseButtonPressed || event.type == sf::Event::MouseButtonReleased)
actionCode = ActionCode::keycode(event.mouseButton.button);
else if (event.type == sf::Event::KeyPressed || event.type == sf::Event::KeyReleased)
actionCode = ActionCode::keycode(event.key.code);
else if (event.type == sf::Event::MouseWheelScrolled)
{
if (event.mouseWheelScroll.wheel == sf::Mouse::VerticalWheel)
{
int delta = 1;
if (event.mouseWheelScroll.delta < 0) delta = -1;
actionCode = ActionCode::keycode(event.mouseWheelScroll.wheel, delta );
}
}
else
return;
if (currentScene()->hasAction(actionCode))
{
std::string name = currentScene()->action(actionCode);
currentScene()->doAction(name, actionType);
}
else if (currentScene()->key_callable &&
(event.type == sf::Event::KeyPressed || event.type == sf::Event::KeyReleased))
{
currentScene()->key_callable->call(ActionCode::key_str(event.key.code), actionType);
}
}
void GameEngine::sUserInput()
{
sf::Event event;
while (window && window->pollEvent(event))
while (window.pollEvent(event))
{
processEvent(event);
std::string actionType;
int actionCode = 0;
if (event.type == sf::Event::Closed) { running = false; continue; }
// TODO: add resize event to Scene to react; call it after constructor too, maybe
else if (event.type == sf::Event::Resized) {
continue; // 7DRL short circuit. Resizing manually disabled
/*
sf::FloatRect area(0.f, 0.f, event.size.width, event.size.height);
//sf::FloatRect area(0.f, 0.f, 1024.f, 768.f); // 7DRL 2024: attempt to set scale appropriately
//sf::FloatRect area(0.f, 0.f, event.size.width, event.size.width * 0.75);
visible = sf::View(area);
window.setView(visible);
//window.setSize(sf::Vector2u(event.size.width, event.size.width * 0.75)); // 7DRL 2024: window scaling
std::cout << "Visible area set to (0, 0, " << event.size.width << ", " << event.size.height <<")"<<std::endl;
actionType = "resize";
//window.setSize(sf::Vector2u(event.size.width, event.size.width * 0.75)); // 7DRL 2024: window scaling
*/
}
else if (event.type == sf::Event::KeyPressed || event.type == sf::Event::MouseButtonPressed || event.type == sf::Event::MouseWheelScrolled) actionType = "start";
else if (event.type == sf::Event::KeyReleased || event.type == sf::Event::MouseButtonReleased) actionType = "end";
if (event.type == sf::Event::MouseButtonPressed || event.type == sf::Event::MouseButtonReleased)
actionCode = ActionCode::keycode(event.mouseButton.button);
else if (event.type == sf::Event::KeyPressed || event.type == sf::Event::KeyReleased)
actionCode = ActionCode::keycode(event.key.code);
else if (event.type == sf::Event::MouseWheelScrolled)
{
// //sf::Mouse::Wheel w = event.MouseWheelScrollEvent.wheel;
if (event.mouseWheelScroll.wheel == sf::Mouse::VerticalWheel)
{
int delta = 1;
if (event.mouseWheelScroll.delta < 0) delta = -1;
actionCode = ActionCode::keycode(event.mouseWheelScroll.wheel, delta );
/*
std::cout << "[GameEngine] Generated MouseWheel code w(" << (int)event.mouseWheelScroll.wheel << ") d(" << event.mouseWheelScroll.delta << ") D(" << delta << ") = " << actionCode << std::endl;
std::cout << " test decode: isMouseWheel=" << ActionCode::isMouseWheel(actionCode) << ", wheel=" << ActionCode::wheel(actionCode) << ", delta=" << ActionCode::delta(actionCode) << std::endl;
std::cout << " math test: actionCode && WHEEL_NEG -> " << (actionCode && ActionCode::WHEEL_NEG) << "; actionCode && WHEEL_DEL -> " << (actionCode && ActionCode::WHEEL_DEL) << ";" << std::endl;
*/
}
// float d = event.MouseWheelScrollEvent.delta;
// actionCode = ActionCode::keycode(0, d);
}
else
continue;
//std::cout << "Event produced action code " << actionCode << ": " << actionType << std::endl;
if (currentScene()->hasAction(actionCode))
{
std::string name = currentScene()->action(actionCode);
currentScene()->doAction(name, actionType);
}
else if (currentScene()->key_callable)
{
currentScene()->key_callable->call(ActionCode::key_str(event.key.code), actionType);
/*
PyObject* args = Py_BuildValue("(ss)", ActionCode::key_str(event.key.code).c_str(), actionType.c_str());
PyObject* retval = PyObject_Call(currentScene()->key_callable, args, NULL);
if (!retval)
{
std::cout << "key_callable has raised an exception. It's going to STDERR and being dropped:" << std::endl;
PyErr_Print();
PyErr_Clear();
} else if (retval != Py_None)
{
std::cout << "key_callable returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
}
*/
}
else
{
//std::cout << "[GameEngine] Action not registered for input: " << actionCode << ": " << actionType << std::endl;
}
}
}

View File

@ -6,16 +6,10 @@
#include "IndexTexture.h"
#include "Timer.h"
#include "PyCallable.h"
#include "McRogueFaceConfig.h"
#include "HeadlessRenderer.h"
#include <memory>
class GameEngine
{
std::unique_ptr<sf::RenderWindow> window;
std::unique_ptr<HeadlessRenderer> headless_renderer;
sf::RenderTarget* render_target;
sf::RenderWindow window;
sf::Font font;
std::map<std::string, Scene*> scenes;
bool running = true;
@ -25,9 +19,6 @@ class GameEngine
sf::Clock clock;
float frameTime;
std::string window_title;
bool headless = false;
McRogueFaceConfig config;
sf::Clock runtime;
//std::map<std::string, Timer> timers;
@ -37,8 +28,6 @@ class GameEngine
public:
std::string scene;
GameEngine();
GameEngine(const McRogueFaceConfig& cfg);
~GameEngine();
Scene* currentScene();
void changeScene(std::string);
void createScene(std::string);
@ -46,8 +35,6 @@ public:
void setPause(bool);
sf::Font & getFont();
sf::RenderWindow & getWindow();
sf::RenderTarget & getRenderTarget();
sf::RenderTarget* getRenderTargetPtr() { return render_target; }
void run();
void sUserInput();
int getFrame() { return currentFrame; }
@ -55,8 +42,6 @@ public:
sf::View getView() { return visible; }
void manageTimer(std::string, PyObject*, int);
void setWindowScale(float);
bool isHeadless() const { return headless; }
void processEvent(const sf::Event& event);
// global textures for scripts to access
std::vector<IndexTexture> textures;

View File

@ -1,27 +0,0 @@
#include "HeadlessRenderer.h"
#include <iostream>
bool HeadlessRenderer::init(int width, int height) {
if (!render_texture.create(width, height)) {
std::cerr << "Failed to create headless render texture" << std::endl;
return false;
}
return true;
}
sf::RenderTarget& HeadlessRenderer::getRenderTarget() {
return render_texture;
}
void HeadlessRenderer::saveScreenshot(const std::string& path) {
sf::Image screenshot = render_texture.getTexture().copyToImage();
if (!screenshot.saveToFile(path)) {
std::cerr << "Failed to save screenshot to: " << path << std::endl;
} else {
std::cout << "Screenshot saved to: " << path << std::endl;
}
}
void HeadlessRenderer::display() {
render_texture.display();
}

View File

@ -1,20 +0,0 @@
#ifndef HEADLESS_RENDERER_H
#define HEADLESS_RENDERER_H
#include <SFML/Graphics.hpp>
#include <memory>
#include <string>
class HeadlessRenderer {
private:
sf::RenderTexture render_texture;
public:
bool init(int width = 1024, int height = 768);
sf::RenderTarget& getRenderTarget();
void saveScreenshot(const std::string& path);
void display(); // Finalize the current frame
bool isOpen() const { return true; } // Always "open" in headless mode
};
#endif // HEADLESS_RENDERER_H

View File

@ -1,24 +1,20 @@
#include "McRFPy_API.h"
#include "McRFPy_Automation.h"
#include "platform.h"
#include "PyAnimation.h"
#include "GameEngine.h"
#include "UI.h"
#include "Resources.h"
#include "PyScene.h"
#include <filesystem>
#include <cstring>
std::map<std::string, PyObject*> McRFPy_API::callbacks;
std::vector<sf::SoundBuffer> McRFPy_API::soundbuffers;
sf::Music McRFPy_API::music;
sf::Sound McRFPy_API::sfx;
std::shared_ptr<PyFont> McRFPy_API::default_font;
std::shared_ptr<PyTexture> McRFPy_API::default_texture;
PyObject* McRFPy_API::mcrf_module;
static PyMethodDef mcrfpyMethods[] = {
{"registerPyAction", McRFPy_API::_registerPyAction, METH_VARARGS,
"Register a callable Python object to correspond to an action string. (actionstr, callable)"},
{"registerInputAction", McRFPy_API::_registerInputAction, METH_VARARGS,
"Register a SFML input code to correspond to an action string. (input_code, actionstr)"},
{"createSoundBuffer", McRFPy_API::_createSoundBuffer, METH_VARARGS, "(filename)"},
{"loadMusic", McRFPy_API::_loadMusic, METH_VARARGS, "(filename)"},
@ -43,15 +39,8 @@ static PyMethodDef mcrfpyMethods[] = {
};
static PyModuleDef mcrfpyModule = {
PyModuleDef_HEAD_INIT, /* m_base - Always initialize this member to PyModuleDef_HEAD_INIT. */
"mcrfpy", /* m_name */
NULL, /* m_doc - Docstring for the module; usually a docstring variable created with PyDoc_STRVAR is used. */
-1, /* m_size - Setting m_size to -1 means that the module does not support sub-interpreters, because it has global state. */
mcrfpyMethods, /* m_methods */
NULL, /* m_slots - An array of slot definitions ... When using single-phase initialization, m_slots must be NULL. */
NULL, /* traverseproc m_traverse - A traversal function to call during GC traversal of the module object */
NULL, /* inquiry m_clear - A clear function to call during GC clearing of the module object */
NULL /* freefunc m_free - A function to call during deallocation of the module object */
PyModuleDef_HEAD_INIT, "mcrfpy", NULL, -1, mcrfpyMethods,
NULL, NULL, NULL, NULL
};
// Module initializer fn, passed to PyImport_AppendInittab
@ -67,7 +56,7 @@ PyObject* PyInit_mcrfpy()
using namespace mcrfpydef;
PyTypeObject* pytypes[] = {
/*SFML exposed types*/
&PyColorType, /*&PyLinkedColorType,*/ &PyFontType, &PyTextureType, &PyVectorType,
&PyColorType, &PyFontType, &PyTextureType,
/*UI widgets*/
&PyUICaptionType, &PyUISpriteType, &PyUIFrameType, &PyUIEntityType, &PyUIGridType,
@ -78,44 +67,16 @@ PyObject* PyInit_mcrfpy()
/*collections & iterators*/
&PyUICollectionType, &PyUICollectionIterType,
&PyUIEntityCollectionType, &PyUIEntityCollectionIterType,
/*animation*/
&PyAnimationType,
nullptr};
int i = 0;
auto t = pytypes[i];
while (t != nullptr)
{
//std::cout << "Registering type: " << t->tp_name << std::endl;
if (PyType_Ready(t) < 0) {
std::cout << "ERROR: PyType_Ready failed for " << t->tp_name << std::endl;
return NULL;
}
//std::cout << " tp_alloc after PyType_Ready: " << (void*)t->tp_alloc << std::endl;
PyType_Ready(t);
PyModule_AddType(m, t);
i++;
t = pytypes[i];
t = pytypes[i++];
}
// Add default_font and default_texture to module
McRFPy_API::default_font = std::make_shared<PyFont>("assets/JetbrainsMono.ttf");
McRFPy_API::default_texture = std::make_shared<PyTexture>("assets/kenney_tinydungeon.png", 16, 16);
//PyModule_AddObject(m, "default_font", McRFPy_API::default_font->pyObject());
//PyModule_AddObject(m, "default_texture", McRFPy_API::default_texture->pyObject());
PyModule_AddObject(m, "default_font", Py_None);
PyModule_AddObject(m, "default_texture", Py_None);
// Add automation submodule
PyObject* automation_module = McRFPy_Automation::init_automation_module();
if (automation_module != NULL) {
PyModule_AddObject(m, "automation", automation_module);
// Also add to sys.modules for proper import behavior
PyObject* sys_modules = PyImport_GetModuleDict();
PyDict_SetItemString(sys_modules, "mcrfpy.automation", automation_module);
}
//McRFPy_API::mcrf_module = m;
return m;
}
@ -167,82 +128,9 @@ PyStatus init_python(const char *program_name)
#endif
status = Py_InitializeFromConfig(&config);
PyConfig_Clear(&config);
return status;
}
PyStatus McRFPy_API::init_python_with_config(const McRogueFaceConfig& config, int argc, char** argv)
{
// If Python is already initialized, just return success
if (Py_IsInitialized()) {
return PyStatus_Ok();
}
PyStatus status;
PyConfig pyconfig;
PyConfig_InitIsolatedConfig(&pyconfig);
// CRITICAL: Pass actual command line arguments to Python
status = PyConfig_SetBytesArgv(&pyconfig, argc, argv);
if (PyStatus_Exception(status)) {
return status;
}
// Check if we're in a virtual environment
auto exe_path = std::filesystem::path(argv[0]);
auto exe_dir = exe_path.parent_path();
auto venv_root = exe_dir.parent_path();
if (std::filesystem::exists(venv_root / "pyvenv.cfg")) {
// We're running from within a venv!
// Add venv's site-packages to module search paths
auto site_packages = venv_root / "lib" / "python3.12" / "site-packages";
PyWideStringList_Append(&pyconfig.module_search_paths,
site_packages.wstring().c_str());
pyconfig.module_search_paths_set = 1;
}
// Set Python home to our bundled Python
auto python_home = executable_path() + L"/lib/Python";
PyConfig_SetString(&pyconfig, &pyconfig.home, python_home.c_str());
// Set up module search paths
#if __PLATFORM_SET_PYTHON_SEARCH_PATHS == 1
if (!pyconfig.module_search_paths_set) {
pyconfig.module_search_paths_set = 1;
}
// search paths for python libs/modules/scripts
const wchar_t* str_arr[] = {
L"/scripts",
L"/lib/Python/lib.linux-x86_64-3.12",
L"/lib/Python",
L"/lib/Python/Lib",
L"/venv/lib/python3.12/site-packages"
};
for(auto s : str_arr) {
status = PyWideStringList_Append(&pyconfig.module_search_paths, (executable_path() + s).c_str());
if (PyStatus_Exception(status)) {
continue;
}
}
#endif
// Register mcrfpy module before initialization
if (!Py_IsInitialized()) {
PyImport_AppendInittab("mcrfpy", &PyInit_mcrfpy);
}
status = Py_InitializeFromConfig(&pyconfig);
PyConfig_Clear(&pyconfig);
return status;
}
/*
void McRFPy_API::setSpriteTexture(int ti)
{
int tx = ti % texture_width, ty = ti / texture_width;
@ -251,7 +139,6 @@ void McRFPy_API::setSpriteTexture(int ti)
ty * texture_size,
texture_size, texture_size));
}
*/
// functionality
//void McRFPy_API::
@ -259,81 +146,26 @@ void McRFPy_API::setSpriteTexture(int ti)
void McRFPy_API::api_init() {
// build API exposure before python initialization
if (!Py_IsInitialized()) {
PyImport_AppendInittab("mcrfpy", &PyInit_mcrfpy);
// use full path version of argv[0] from OS to init python
init_python(narrow_string(executable_filename()).c_str());
}
PyImport_AppendInittab("mcrfpy", &PyInit_mcrfpy);
// use full path version of argv[0] from OS to init python
init_python(narrow_string(executable_filename()).c_str());
//texture.loadFromFile("./assets/kenney_tinydungeon.png");
//texture_size = 16, texture_width = 12, texture_height= 11;
//texture_sprite_count = texture_width * texture_height;
//texture.setSmooth(false);
// Add default_font and default_texture to module
McRFPy_API::mcrf_module = PyImport_ImportModule("mcrfpy");
std::cout << PyUnicode_AsUTF8(PyObject_Repr(McRFPy_API::mcrf_module)) << std::endl;
//PyModule_AddObject(McRFPy_API::mcrf_module, "default_font", McRFPy_API::default_font->pyObject());
PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_font", McRFPy_API::default_font->pyObject());
//PyModule_AddObject(McRFPy_API::mcrf_module, "default_texture", McRFPy_API::default_texture->pyObject());
PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_texture", McRFPy_API::default_texture->pyObject());
//sprite.setTexture(texture);
//sprite.setScale(sf::Vector2f(4.0f, 4.0f));
//setSpriteTexture(0);
}
void McRFPy_API::api_init(const McRogueFaceConfig& config, int argc, char** argv) {
// Initialize Python with proper argv - this is CRITICAL
PyStatus status = init_python_with_config(config, argc, argv);
if (PyStatus_Exception(status)) {
Py_ExitStatusException(status);
}
McRFPy_API::mcrf_module = PyImport_ImportModule("mcrfpy");
// For -m module execution, let Python handle it
if (!config.python_module.empty() && config.python_module != "venv") {
// Py_RunMain() will handle -m execution
return;
}
// Execute based on mode - this is handled in main.cpp now
// The actual execution logic is in run_python_interpreter()
// Set up default resources only if in game mode
if (!config.python_mode) {
//PyModule_AddObject(McRFPy_API::mcrf_module, "default_font", McRFPy_API::default_font->pyObject());
PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_font", McRFPy_API::default_font->pyObject());
//PyModule_AddObject(McRFPy_API::mcrf_module, "default_texture", McRFPy_API::default_texture->pyObject());
PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_texture", McRFPy_API::default_texture->pyObject());
}
}
void McRFPy_API::executeScript(std::string filename)
{
std::filesystem::path script_path(filename);
// If the path is relative and the file doesn't exist, try resolving it relative to the executable
if (script_path.is_relative() && !std::filesystem::exists(script_path)) {
// Get the directory where the executable is located using platform-specific function
std::wstring exe_dir_w = executable_path();
std::filesystem::path exe_dir(exe_dir_w);
// Try the script path relative to the executable directory
std::filesystem::path resolved_path = exe_dir / script_path;
if (std::filesystem::exists(resolved_path)) {
script_path = resolved_path;
}
}
FILE* PScriptFile = fopen(script_path.string().c_str(), "r");
FILE* PScriptFile = fopen(filename.c_str(), "r");
if(PScriptFile) {
PyRun_SimpleFile(PScriptFile, script_path.string().c_str());
PyRun_SimpleFile(PScriptFile, filename.c_str());
fclose(PScriptFile);
} else {
std::cout << "Failed to open script: " << script_path.string() << std::endl;
}
}
@ -358,7 +190,63 @@ void McRFPy_API::REPL_device(FILE * fp, const char *filename)
}
// python connection
PyObject* McRFPy_API::_registerPyAction(PyObject *self, PyObject *args)
{
PyObject* callable;
const char * actionstr;
if (!PyArg_ParseTuple(args, "sO", &actionstr, &callable)) return NULL;
//TODO: if the string already exists in the callbacks map,
// decrease our reference count so it can potentially be garbage collected
callbacks[std::string(actionstr)] = callable;
Py_INCREF(callable);
// return None correctly
Py_INCREF(Py_None);
return Py_None;
}
PyObject* McRFPy_API::_registerInputAction(PyObject *self, PyObject *args)
{
int action_code;
const char * actionstr;
if (!PyArg_ParseTuple(args, "iz", &action_code, &actionstr)) return NULL;
bool success;
if (actionstr == NULL) { // Action provided is None, i.e. unregister
std::cout << "Unregistering\n";
success = game->currentScene()->unregisterActionInjected(action_code, std::string(actionstr) + "_py");
} else {
std::cout << "Registering" << actionstr << "_py to " << action_code << "\n";
success = game->currentScene()->registerActionInjected(action_code, std::string(actionstr) + "_py");
}
success ? Py_INCREF(Py_True) : Py_INCREF(Py_False);
return success ? Py_True : Py_False;
}
void McRFPy_API::doAction(std::string actionstr) {
// hard coded actions that require no registration
//std::cout << "Calling Python Action: " << actionstr;
if (!actionstr.compare("startrepl")) return McRFPy_API::REPL();
if (callbacks.find(actionstr) == callbacks.end())
{
//std::cout << " (not found)" << std::endl;
return;
}
//std::cout << " (" << PyUnicode_AsUTF8(PyObject_Repr(callbacks[actionstr])) << ")" << std::endl;
PyObject* retval = PyObject_Call(callbacks[actionstr], PyTuple_New(0), NULL);
if (!retval)
{
std::cout << "doAction has raised an exception. It's going to STDERR and being dropped:" << std::endl;
PyErr_Print();
PyErr_Clear();
} else if (retval != Py_None)
{
std::cout << "doAction returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
}
}
/*
PyObject* McRFPy_API::_refreshFov(PyObject* self, PyObject* args) {
@ -431,10 +319,73 @@ PyObject* McRFPy_API::_getSoundVolume(PyObject* self, PyObject* args) {
return Py_BuildValue("f", McRFPy_API::sfx.getVolume());
}
// Removed deprecated player_input, computerTurn, playerTurn functions
// These were part of the old turn-based system that is no longer used
/*
void McRFPy_API::player_input(int dx, int dy) {
//std::cout << "# entities tagged 'player': " << McRFPy_API::entities.getEntities("player").size() << std::endl;
auto player_entity = McRFPy_API::entities.getEntities("player")[0];
auto grid = player_entity->cGrid->grid;
//std::cout << "Grid pointed to: " << (long)player_entity->cGrid->grid << std::endl;
if (McRFPy_API::input_mode.compare("playerturn") != 0) {
// no input accepted while computer moving
//std::cout << "Can't move while it's not player's turn." << std::endl;
return;
}
// TODO: selection cursor via keyboard
// else if (!input_mode.compare("selectpoint") {}
// else if (!input_mode.compare("selectentity") {}
// grid bounds check
if (player_entity->cGrid->x + dx < 0 ||
player_entity->cGrid->y + dy < 0 ||
player_entity->cGrid->x + dx > grid->grid_x - 1 ||
player_entity->cGrid->y + dy > grid->grid_y - 1) {
//std::cout << "(" << player_entity->cGrid->x << ", " << player_entity->cGrid->y <<
// ") + (" << dx << ", " << dy << ") is OOB." << std::endl;
return;
}
//std::cout << PyUnicode_AsUTF8(PyObject_Repr(player_entity->cBehavior->object)) << std::endl;
PyObject* move_fn = PyObject_GetAttrString(player_entity->cBehavior->object, "move");
//std::cout << PyUnicode_AsUTF8(PyObject_Repr(move_fn)) << std::endl;
if (move_fn) {
//std::cout << "Calling `move`" << std::endl;
PyObject* move_args = Py_BuildValue("(ii)", dx, dy);
PyObject_CallObject((PyObject*) move_fn, move_args);
} else {
//std::cout << "player_input called on entity with no `move` method" << std::endl;
}
}
void McRFPy_API::computerTurn() {
McRFPy_API::input_mode = "computerturnrunning";
for (auto e : McRFPy_API::grids[McRFPy_API::active_grid]->entities) {
if (e->cBehavior) {
PyObject_Call(PyObject_GetAttrString(e->cBehavior->object, "ai_act"), PyTuple_New(0), NULL);
}
}
}
void McRFPy_API::playerTurn() {
McRFPy_API::input_mode = "playerturn";
for (auto e : McRFPy_API::entities.getEntities("player")) {
if (e->cBehavior) {
PyObject_Call(PyObject_GetAttrString(e->cBehavior->object, "player_act"), PyTuple_New(0), NULL);
}
}
}
void McRFPy_API::camFollow() {
if (!McRFPy_API::do_camfollow) return;
auto& ag = McRFPy_API::grids[McRFPy_API::active_grid];
for (auto e : McRFPy_API::entities.getEntities("player")) {
//std::cout << "grid center: " << ag->center_x << ", " << ag->center_y << std::endl <<
// "player grid pos: " << e->cGrid->x << ", " << e->cGrid->y << std::endl <<
// "player sprite pos: " << e->cGrid->indexsprite.x << ", " << e->cGrid->indexsprite.y << std::endl;
ag->center_x = e->cGrid->indexsprite.x * ag->grid_size + ag->grid_size * 0.5;
ag->center_y = e->cGrid->indexsprite.y * ag->grid_size + ag->grid_size * 0.5;
}
}
PyObject* McRFPy_API::_camFollow(PyObject* self, PyObject* args) {
PyObject* set_camfollow = NULL;
//std::cout << "camFollow Parse Args" << std::endl;
@ -498,13 +449,6 @@ PyObject* McRFPy_API::_createScene(PyObject* self, PyObject* args) {
PyObject* McRFPy_API::_keypressScene(PyObject* self, PyObject* args) {
PyObject* callable;
if (!PyArg_ParseTuple(args, "O", &callable)) return NULL;
// Validate that the argument is callable
if (!PyCallable_Check(callable)) {
PyErr_SetString(PyExc_TypeError, "keypressScene() argument must be callable");
return NULL;
}
/*
if (game->currentScene()->key_callable != NULL and game->currentScene()->key_callable != Py_None)
{
@ -515,7 +459,6 @@ PyObject* McRFPy_API::_keypressScene(PyObject* self, PyObject* args) {
Py_INCREF(Py_None);
*/
game->currentScene()->key_callable = std::make_unique<PyKeyCallable>(callable);
Py_INCREF(Py_None);
return Py_None;
}
@ -555,15 +498,3 @@ PyObject* McRFPy_API::_setScale(PyObject* self, PyObject* args) {
Py_INCREF(Py_None);
return Py_None;
}
void McRFPy_API::markSceneNeedsSort() {
// Mark the current scene as needing a z_index sort
auto scene = game->currentScene();
if (scene && scene->ui_elements) {
// Cast to PyScene to access ui_elements_need_sort
PyScene* pyscene = dynamic_cast<PyScene*>(scene);
if (pyscene) {
pyscene->ui_elements_need_sort = true;
}
}
}

View File

@ -3,10 +3,6 @@
#include "Python.h"
#include <list>
#include "PyFont.h"
#include "PyTexture.h"
#include "McRogueFaceConfig.h"
class GameEngine; // forward declared (circular members)
class McRFPy_API
@ -18,18 +14,12 @@ private:
McRFPy_API();
public:
static PyObject* mcrf_module;
static std::shared_ptr<PyFont> default_font;
static std::shared_ptr<PyTexture> default_texture;
//inline static sf::Sprite sprite;
//inline static sf::Texture texture;
//static void setSpriteTexture(int);
inline static sf::Sprite sprite;
inline static sf::Texture texture;
static void setSpriteTexture(int);
inline static GameEngine* game;
static void api_init();
static void api_init(const McRogueFaceConfig& config, int argc, char** argv);
static PyStatus init_python_with_config(const McRogueFaceConfig& config, int argc, char** argv);
static void api_shutdown();
// Python API functionality - use mcrfpy.* in scripts
//static PyObject* _drawSprite(PyObject*, PyObject*);
@ -40,6 +30,9 @@ public:
static sf::Music music;
static sf::Sound sfx;
static std::map<std::string, PyObject*> callbacks;
static PyObject* _registerPyAction(PyObject*, PyObject*);
static PyObject* _registerInputAction(PyObject*, PyObject*);
static PyObject* _createSoundBuffer(PyObject*, PyObject*);
static PyObject* _loadMusic(PyObject*, PyObject*);
@ -66,11 +59,12 @@ public:
// accept keyboard input from scene
static sf::Vector2i cursor_position;
static void player_input(int, int);
static void computerTurn();
static void playerTurn();
static void doAction(std::string);
static void executeScript(std::string);
static void executePyString(std::string);
// Helper to mark scenes as needing z_index resort
static void markSceneNeedsSort();
};

View File

@ -1,817 +0,0 @@
#include "McRFPy_Automation.h"
#include "McRFPy_API.h"
#include "GameEngine.h"
#include <fstream>
#include <iostream>
#include <sstream>
#include <unordered_map>
// Helper function to get game engine
GameEngine* McRFPy_Automation::getGameEngine() {
return McRFPy_API::game;
}
// Sleep helper
void McRFPy_Automation::sleep_ms(int milliseconds) {
std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds));
}
// Convert string to SFML key code
sf::Keyboard::Key McRFPy_Automation::stringToKey(const std::string& keyName) {
static const std::unordered_map<std::string, sf::Keyboard::Key> keyMap = {
// Letters
{"a", sf::Keyboard::A}, {"b", sf::Keyboard::B}, {"c", sf::Keyboard::C},
{"d", sf::Keyboard::D}, {"e", sf::Keyboard::E}, {"f", sf::Keyboard::F},
{"g", sf::Keyboard::G}, {"h", sf::Keyboard::H}, {"i", sf::Keyboard::I},
{"j", sf::Keyboard::J}, {"k", sf::Keyboard::K}, {"l", sf::Keyboard::L},
{"m", sf::Keyboard::M}, {"n", sf::Keyboard::N}, {"o", sf::Keyboard::O},
{"p", sf::Keyboard::P}, {"q", sf::Keyboard::Q}, {"r", sf::Keyboard::R},
{"s", sf::Keyboard::S}, {"t", sf::Keyboard::T}, {"u", sf::Keyboard::U},
{"v", sf::Keyboard::V}, {"w", sf::Keyboard::W}, {"x", sf::Keyboard::X},
{"y", sf::Keyboard::Y}, {"z", sf::Keyboard::Z},
// Numbers
{"0", sf::Keyboard::Num0}, {"1", sf::Keyboard::Num1}, {"2", sf::Keyboard::Num2},
{"3", sf::Keyboard::Num3}, {"4", sf::Keyboard::Num4}, {"5", sf::Keyboard::Num5},
{"6", sf::Keyboard::Num6}, {"7", sf::Keyboard::Num7}, {"8", sf::Keyboard::Num8},
{"9", sf::Keyboard::Num9},
// Function keys
{"f1", sf::Keyboard::F1}, {"f2", sf::Keyboard::F2}, {"f3", sf::Keyboard::F3},
{"f4", sf::Keyboard::F4}, {"f5", sf::Keyboard::F5}, {"f6", sf::Keyboard::F6},
{"f7", sf::Keyboard::F7}, {"f8", sf::Keyboard::F8}, {"f9", sf::Keyboard::F9},
{"f10", sf::Keyboard::F10}, {"f11", sf::Keyboard::F11}, {"f12", sf::Keyboard::F12},
{"f13", sf::Keyboard::F13}, {"f14", sf::Keyboard::F14}, {"f15", sf::Keyboard::F15},
// Special keys
{"escape", sf::Keyboard::Escape}, {"esc", sf::Keyboard::Escape},
{"enter", sf::Keyboard::Enter}, {"return", sf::Keyboard::Enter},
{"space", sf::Keyboard::Space}, {" ", sf::Keyboard::Space},
{"tab", sf::Keyboard::Tab}, {"\t", sf::Keyboard::Tab},
{"backspace", sf::Keyboard::BackSpace},
{"delete", sf::Keyboard::Delete}, {"del", sf::Keyboard::Delete},
{"insert", sf::Keyboard::Insert},
{"home", sf::Keyboard::Home},
{"end", sf::Keyboard::End},
{"pageup", sf::Keyboard::PageUp}, {"pgup", sf::Keyboard::PageUp},
{"pagedown", sf::Keyboard::PageDown}, {"pgdn", sf::Keyboard::PageDown},
// Arrow keys
{"left", sf::Keyboard::Left},
{"right", sf::Keyboard::Right},
{"up", sf::Keyboard::Up},
{"down", sf::Keyboard::Down},
// Modifiers
{"ctrl", sf::Keyboard::LControl}, {"ctrlleft", sf::Keyboard::LControl},
{"ctrlright", sf::Keyboard::RControl},
{"alt", sf::Keyboard::LAlt}, {"altleft", sf::Keyboard::LAlt},
{"altright", sf::Keyboard::RAlt},
{"shift", sf::Keyboard::LShift}, {"shiftleft", sf::Keyboard::LShift},
{"shiftright", sf::Keyboard::RShift},
{"win", sf::Keyboard::LSystem}, {"winleft", sf::Keyboard::LSystem},
{"winright", sf::Keyboard::RSystem}, {"command", sf::Keyboard::LSystem},
// Punctuation
{",", sf::Keyboard::Comma}, {".", sf::Keyboard::Period},
{"/", sf::Keyboard::Slash}, {"\\", sf::Keyboard::BackSlash},
{";", sf::Keyboard::SemiColon}, {"'", sf::Keyboard::Quote},
{"[", sf::Keyboard::LBracket}, {"]", sf::Keyboard::RBracket},
{"-", sf::Keyboard::Dash}, {"=", sf::Keyboard::Equal},
// Numpad
{"num0", sf::Keyboard::Numpad0}, {"num1", sf::Keyboard::Numpad1},
{"num2", sf::Keyboard::Numpad2}, {"num3", sf::Keyboard::Numpad3},
{"num4", sf::Keyboard::Numpad4}, {"num5", sf::Keyboard::Numpad5},
{"num6", sf::Keyboard::Numpad6}, {"num7", sf::Keyboard::Numpad7},
{"num8", sf::Keyboard::Numpad8}, {"num9", sf::Keyboard::Numpad9},
{"add", sf::Keyboard::Add}, {"subtract", sf::Keyboard::Subtract},
{"multiply", sf::Keyboard::Multiply}, {"divide", sf::Keyboard::Divide},
// Other
{"pause", sf::Keyboard::Pause},
{"capslock", sf::Keyboard::LControl}, // Note: SFML doesn't have CapsLock
{"numlock", sf::Keyboard::LControl}, // Note: SFML doesn't have NumLock
{"scrolllock", sf::Keyboard::LControl}, // Note: SFML doesn't have ScrollLock
};
auto it = keyMap.find(keyName);
if (it != keyMap.end()) {
return it->second;
}
return sf::Keyboard::Unknown;
}
// Inject mouse event into the game engine
void McRFPy_Automation::injectMouseEvent(sf::Event::EventType type, int x, int y, sf::Mouse::Button button) {
auto engine = getGameEngine();
if (!engine) return;
sf::Event event;
event.type = type;
switch (type) {
case sf::Event::MouseMoved:
event.mouseMove.x = x;
event.mouseMove.y = y;
break;
case sf::Event::MouseButtonPressed:
case sf::Event::MouseButtonReleased:
event.mouseButton.button = button;
event.mouseButton.x = x;
event.mouseButton.y = y;
break;
case sf::Event::MouseWheelScrolled:
event.mouseWheelScroll.wheel = sf::Mouse::VerticalWheel;
event.mouseWheelScroll.delta = static_cast<float>(x); // x is used for scroll amount
event.mouseWheelScroll.x = x;
event.mouseWheelScroll.y = y;
break;
default:
break;
}
engine->processEvent(event);
}
// Inject keyboard event into the game engine
void McRFPy_Automation::injectKeyEvent(sf::Event::EventType type, sf::Keyboard::Key key) {
auto engine = getGameEngine();
if (!engine) return;
sf::Event event;
event.type = type;
if (type == sf::Event::KeyPressed || type == sf::Event::KeyReleased) {
event.key.code = key;
event.key.alt = sf::Keyboard::isKeyPressed(sf::Keyboard::LAlt) ||
sf::Keyboard::isKeyPressed(sf::Keyboard::RAlt);
event.key.control = sf::Keyboard::isKeyPressed(sf::Keyboard::LControl) ||
sf::Keyboard::isKeyPressed(sf::Keyboard::RControl);
event.key.shift = sf::Keyboard::isKeyPressed(sf::Keyboard::LShift) ||
sf::Keyboard::isKeyPressed(sf::Keyboard::RShift);
event.key.system = sf::Keyboard::isKeyPressed(sf::Keyboard::LSystem) ||
sf::Keyboard::isKeyPressed(sf::Keyboard::RSystem);
}
engine->processEvent(event);
}
// Inject text event for typing
void McRFPy_Automation::injectTextEvent(sf::Uint32 unicode) {
auto engine = getGameEngine();
if (!engine) return;
sf::Event event;
event.type = sf::Event::TextEntered;
event.text.unicode = unicode;
engine->processEvent(event);
}
// Screenshot implementation
PyObject* McRFPy_Automation::_screenshot(PyObject* self, PyObject* args) {
const char* filename;
if (!PyArg_ParseTuple(args, "s", &filename)) {
return NULL;
}
auto engine = getGameEngine();
if (!engine) {
PyErr_SetString(PyExc_RuntimeError, "Game engine not initialized");
return NULL;
}
// Get the render target
sf::RenderTarget* target = engine->getRenderTargetPtr();
if (!target) {
PyErr_SetString(PyExc_RuntimeError, "No render target available");
return NULL;
}
// For RenderWindow, we can get a screenshot directly
if (auto* window = dynamic_cast<sf::RenderWindow*>(target)) {
sf::Vector2u windowSize = window->getSize();
sf::Texture texture;
texture.create(windowSize.x, windowSize.y);
texture.update(*window);
if (texture.copyToImage().saveToFile(filename)) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
}
}
// For RenderTexture (headless mode)
else if (auto* renderTexture = dynamic_cast<sf::RenderTexture*>(target)) {
if (renderTexture->getTexture().copyToImage().saveToFile(filename)) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
}
}
PyErr_SetString(PyExc_RuntimeError, "Unknown render target type");
return NULL;
}
// Get current mouse position
PyObject* McRFPy_Automation::_position(PyObject* self, PyObject* args) {
auto engine = getGameEngine();
if (!engine || !engine->getRenderTargetPtr()) {
return Py_BuildValue("(ii)", 0, 0);
}
// In headless mode, we'd need to track the simulated mouse position
// For now, return the actual mouse position relative to window if available
if (auto* window = dynamic_cast<sf::RenderWindow*>(engine->getRenderTargetPtr())) {
sf::Vector2i pos = sf::Mouse::getPosition(*window);
return Py_BuildValue("(ii)", pos.x, pos.y);
}
// In headless mode, return simulated position (TODO: track this)
return Py_BuildValue("(ii)", 0, 0);
}
// Get screen size
PyObject* McRFPy_Automation::_size(PyObject* self, PyObject* args) {
auto engine = getGameEngine();
if (!engine || !engine->getRenderTargetPtr()) {
return Py_BuildValue("(ii)", 1024, 768); // Default size
}
sf::Vector2u size = engine->getRenderTarget().getSize();
return Py_BuildValue("(ii)", size.x, size.y);
}
// Check if coordinates are on screen
PyObject* McRFPy_Automation::_onScreen(PyObject* self, PyObject* args) {
int x, y;
if (!PyArg_ParseTuple(args, "ii", &x, &y)) {
return NULL;
}
auto engine = getGameEngine();
if (!engine || !engine->getRenderTargetPtr()) {
Py_RETURN_FALSE;
}
sf::Vector2u size = engine->getRenderTarget().getSize();
if (x >= 0 && x < (int)size.x && y >= 0 && y < (int)size.y) {
Py_RETURN_TRUE;
} else {
Py_RETURN_FALSE;
}
}
// Move mouse to position
PyObject* McRFPy_Automation::_moveTo(PyObject* self, PyObject* args, PyObject* kwargs) {
static const char* kwlist[] = {"x", "y", "duration", NULL};
int x, y;
float duration = 0.0f;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|f", const_cast<char**>(kwlist),
&x, &y, &duration)) {
return NULL;
}
// TODO: Implement smooth movement with duration
injectMouseEvent(sf::Event::MouseMoved, x, y);
if (duration > 0) {
sleep_ms(static_cast<int>(duration * 1000));
}
Py_RETURN_NONE;
}
// Move mouse relative
PyObject* McRFPy_Automation::_moveRel(PyObject* self, PyObject* args, PyObject* kwargs) {
static const char* kwlist[] = {"xOffset", "yOffset", "duration", NULL};
int xOffset, yOffset;
float duration = 0.0f;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|f", const_cast<char**>(kwlist),
&xOffset, &yOffset, &duration)) {
return NULL;
}
// Get current position
PyObject* pos = _position(self, NULL);
if (!pos) return NULL;
int currentX, currentY;
if (!PyArg_ParseTuple(pos, "ii", &currentX, &currentY)) {
Py_DECREF(pos);
return NULL;
}
Py_DECREF(pos);
// Move to new position
injectMouseEvent(sf::Event::MouseMoved, currentX + xOffset, currentY + yOffset);
if (duration > 0) {
sleep_ms(static_cast<int>(duration * 1000));
}
Py_RETURN_NONE;
}
// Click implementation
PyObject* McRFPy_Automation::_click(PyObject* self, PyObject* args, PyObject* kwargs) {
static const char* kwlist[] = {"x", "y", "clicks", "interval", "button", NULL};
int x = -1, y = -1;
int clicks = 1;
float interval = 0.0f;
const char* button = "left";
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|iiifs", const_cast<char**>(kwlist),
&x, &y, &clicks, &interval, &button)) {
return NULL;
}
// If no position specified, use current position
if (x == -1 || y == -1) {
PyObject* pos = _position(self, NULL);
if (!pos) return NULL;
if (!PyArg_ParseTuple(pos, "ii", &x, &y)) {
Py_DECREF(pos);
return NULL;
}
Py_DECREF(pos);
}
// Determine button
sf::Mouse::Button sfButton = sf::Mouse::Left;
if (strcmp(button, "right") == 0) {
sfButton = sf::Mouse::Right;
} else if (strcmp(button, "middle") == 0) {
sfButton = sf::Mouse::Middle;
}
// Move to position first
injectMouseEvent(sf::Event::MouseMoved, x, y);
// Perform clicks
for (int i = 0; i < clicks; i++) {
if (i > 0 && interval > 0) {
sleep_ms(static_cast<int>(interval * 1000));
}
injectMouseEvent(sf::Event::MouseButtonPressed, x, y, sfButton);
sleep_ms(10); // Small delay between press and release
injectMouseEvent(sf::Event::MouseButtonReleased, x, y, sfButton);
}
Py_RETURN_NONE;
}
// Right click
PyObject* McRFPy_Automation::_rightClick(PyObject* self, PyObject* args, PyObject* kwargs) {
static const char* kwlist[] = {"x", "y", NULL};
int x = -1, y = -1;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", const_cast<char**>(kwlist), &x, &y)) {
return NULL;
}
// Build new args with button="right"
PyObject* newKwargs = PyDict_New();
PyDict_SetItemString(newKwargs, "button", PyUnicode_FromString("right"));
if (x != -1) PyDict_SetItemString(newKwargs, "x", PyLong_FromLong(x));
if (y != -1) PyDict_SetItemString(newKwargs, "y", PyLong_FromLong(y));
PyObject* result = _click(self, PyTuple_New(0), newKwargs);
Py_DECREF(newKwargs);
return result;
}
// Double click
PyObject* McRFPy_Automation::_doubleClick(PyObject* self, PyObject* args, PyObject* kwargs) {
static const char* kwlist[] = {"x", "y", NULL};
int x = -1, y = -1;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", const_cast<char**>(kwlist), &x, &y)) {
return NULL;
}
PyObject* newKwargs = PyDict_New();
PyDict_SetItemString(newKwargs, "clicks", PyLong_FromLong(2));
PyDict_SetItemString(newKwargs, "interval", PyFloat_FromDouble(0.1));
if (x != -1) PyDict_SetItemString(newKwargs, "x", PyLong_FromLong(x));
if (y != -1) PyDict_SetItemString(newKwargs, "y", PyLong_FromLong(y));
PyObject* result = _click(self, PyTuple_New(0), newKwargs);
Py_DECREF(newKwargs);
return result;
}
// Type text
PyObject* McRFPy_Automation::_typewrite(PyObject* self, PyObject* args, PyObject* kwargs) {
static const char* kwlist[] = {"message", "interval", NULL};
const char* message;
float interval = 0.0f;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "s|f", const_cast<char**>(kwlist),
&message, &interval)) {
return NULL;
}
// Type each character
for (size_t i = 0; message[i] != '\0'; i++) {
if (i > 0 && interval > 0) {
sleep_ms(static_cast<int>(interval * 1000));
}
char c = message[i];
// Handle special characters
if (c == '\n') {
injectKeyEvent(sf::Event::KeyPressed, sf::Keyboard::Enter);
injectKeyEvent(sf::Event::KeyReleased, sf::Keyboard::Enter);
} else if (c == '\t') {
injectKeyEvent(sf::Event::KeyPressed, sf::Keyboard::Tab);
injectKeyEvent(sf::Event::KeyReleased, sf::Keyboard::Tab);
} else {
// For regular characters, send text event
injectTextEvent(static_cast<sf::Uint32>(c));
}
}
Py_RETURN_NONE;
}
// Press and hold key
PyObject* McRFPy_Automation::_keyDown(PyObject* self, PyObject* args) {
const char* keyName;
if (!PyArg_ParseTuple(args, "s", &keyName)) {
return NULL;
}
sf::Keyboard::Key key = stringToKey(keyName);
if (key == sf::Keyboard::Unknown) {
PyErr_Format(PyExc_ValueError, "Unknown key: %s", keyName);
return NULL;
}
injectKeyEvent(sf::Event::KeyPressed, key);
Py_RETURN_NONE;
}
// Release key
PyObject* McRFPy_Automation::_keyUp(PyObject* self, PyObject* args) {
const char* keyName;
if (!PyArg_ParseTuple(args, "s", &keyName)) {
return NULL;
}
sf::Keyboard::Key key = stringToKey(keyName);
if (key == sf::Keyboard::Unknown) {
PyErr_Format(PyExc_ValueError, "Unknown key: %s", keyName);
return NULL;
}
injectKeyEvent(sf::Event::KeyReleased, key);
Py_RETURN_NONE;
}
// Hotkey combination
PyObject* McRFPy_Automation::_hotkey(PyObject* self, PyObject* args) {
// Get all keys as separate arguments
Py_ssize_t numKeys = PyTuple_Size(args);
if (numKeys == 0) {
PyErr_SetString(PyExc_ValueError, "hotkey() requires at least one key");
return NULL;
}
// Press all keys
for (Py_ssize_t i = 0; i < numKeys; i++) {
PyObject* keyObj = PyTuple_GetItem(args, i);
const char* keyName = PyUnicode_AsUTF8(keyObj);
if (!keyName) {
return NULL;
}
sf::Keyboard::Key key = stringToKey(keyName);
if (key == sf::Keyboard::Unknown) {
PyErr_Format(PyExc_ValueError, "Unknown key: %s", keyName);
return NULL;
}
injectKeyEvent(sf::Event::KeyPressed, key);
sleep_ms(10); // Small delay between key presses
}
// Release all keys in reverse order
for (Py_ssize_t i = numKeys - 1; i >= 0; i--) {
PyObject* keyObj = PyTuple_GetItem(args, i);
const char* keyName = PyUnicode_AsUTF8(keyObj);
sf::Keyboard::Key key = stringToKey(keyName);
injectKeyEvent(sf::Event::KeyReleased, key);
sleep_ms(10);
}
Py_RETURN_NONE;
}
// Scroll wheel
PyObject* McRFPy_Automation::_scroll(PyObject* self, PyObject* args, PyObject* kwargs) {
static const char* kwlist[] = {"clicks", "x", "y", NULL};
int clicks;
int x = -1, y = -1;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|ii", const_cast<char**>(kwlist),
&clicks, &x, &y)) {
return NULL;
}
// If no position specified, use current position
if (x == -1 || y == -1) {
PyObject* pos = _position(self, NULL);
if (!pos) return NULL;
if (!PyArg_ParseTuple(pos, "ii", &x, &y)) {
Py_DECREF(pos);
return NULL;
}
Py_DECREF(pos);
}
// Inject scroll event
injectMouseEvent(sf::Event::MouseWheelScrolled, clicks, y);
Py_RETURN_NONE;
}
// Other click types using the main click function
PyObject* McRFPy_Automation::_middleClick(PyObject* self, PyObject* args, PyObject* kwargs) {
static const char* kwlist[] = {"x", "y", NULL};
int x = -1, y = -1;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", const_cast<char**>(kwlist), &x, &y)) {
return NULL;
}
PyObject* newKwargs = PyDict_New();
PyDict_SetItemString(newKwargs, "button", PyUnicode_FromString("middle"));
if (x != -1) PyDict_SetItemString(newKwargs, "x", PyLong_FromLong(x));
if (y != -1) PyDict_SetItemString(newKwargs, "y", PyLong_FromLong(y));
PyObject* result = _click(self, PyTuple_New(0), newKwargs);
Py_DECREF(newKwargs);
return result;
}
PyObject* McRFPy_Automation::_tripleClick(PyObject* self, PyObject* args, PyObject* kwargs) {
static const char* kwlist[] = {"x", "y", NULL};
int x = -1, y = -1;
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", const_cast<char**>(kwlist), &x, &y)) {
return NULL;
}
PyObject* newKwargs = PyDict_New();
PyDict_SetItemString(newKwargs, "clicks", PyLong_FromLong(3));
PyDict_SetItemString(newKwargs, "interval", PyFloat_FromDouble(0.1));
if (x != -1) PyDict_SetItemString(newKwargs, "x", PyLong_FromLong(x));
if (y != -1) PyDict_SetItemString(newKwargs, "y", PyLong_FromLong(y));
PyObject* result = _click(self, PyTuple_New(0), newKwargs);
Py_DECREF(newKwargs);
return result;
}
// Mouse button press/release
PyObject* McRFPy_Automation::_mouseDown(PyObject* self, PyObject* args, PyObject* kwargs) {
static const char* kwlist[] = {"x", "y", "button", NULL};
int x = -1, y = -1;
const char* button = "left";
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|iis", const_cast<char**>(kwlist),
&x, &y, &button)) {
return NULL;
}
// If no position specified, use current position
if (x == -1 || y == -1) {
PyObject* pos = _position(self, NULL);
if (!pos) return NULL;
if (!PyArg_ParseTuple(pos, "ii", &x, &y)) {
Py_DECREF(pos);
return NULL;
}
Py_DECREF(pos);
}
sf::Mouse::Button sfButton = sf::Mouse::Left;
if (strcmp(button, "right") == 0) {
sfButton = sf::Mouse::Right;
} else if (strcmp(button, "middle") == 0) {
sfButton = sf::Mouse::Middle;
}
injectMouseEvent(sf::Event::MouseButtonPressed, x, y, sfButton);
Py_RETURN_NONE;
}
PyObject* McRFPy_Automation::_mouseUp(PyObject* self, PyObject* args, PyObject* kwargs) {
static const char* kwlist[] = {"x", "y", "button", NULL};
int x = -1, y = -1;
const char* button = "left";
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|iis", const_cast<char**>(kwlist),
&x, &y, &button)) {
return NULL;
}
// If no position specified, use current position
if (x == -1 || y == -1) {
PyObject* pos = _position(self, NULL);
if (!pos) return NULL;
if (!PyArg_ParseTuple(pos, "ii", &x, &y)) {
Py_DECREF(pos);
return NULL;
}
Py_DECREF(pos);
}
sf::Mouse::Button sfButton = sf::Mouse::Left;
if (strcmp(button, "right") == 0) {
sfButton = sf::Mouse::Right;
} else if (strcmp(button, "middle") == 0) {
sfButton = sf::Mouse::Middle;
}
injectMouseEvent(sf::Event::MouseButtonReleased, x, y, sfButton);
Py_RETURN_NONE;
}
// Drag operations
PyObject* McRFPy_Automation::_dragTo(PyObject* self, PyObject* args, PyObject* kwargs) {
static const char* kwlist[] = {"x", "y", "duration", "button", NULL};
int x, y;
float duration = 0.0f;
const char* button = "left";
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|fs", const_cast<char**>(kwlist),
&x, &y, &duration, &button)) {
return NULL;
}
// Get current position
PyObject* pos = _position(self, NULL);
if (!pos) return NULL;
int startX, startY;
if (!PyArg_ParseTuple(pos, "ii", &startX, &startY)) {
Py_DECREF(pos);
return NULL;
}
Py_DECREF(pos);
// Mouse down at current position
PyObject* downArgs = Py_BuildValue("(ii)", startX, startY);
PyObject* downKwargs = PyDict_New();
PyDict_SetItemString(downKwargs, "button", PyUnicode_FromString(button));
PyObject* downResult = _mouseDown(self, downArgs, downKwargs);
Py_DECREF(downArgs);
Py_DECREF(downKwargs);
if (!downResult) return NULL;
Py_DECREF(downResult);
// Move to target position
if (duration > 0) {
// Smooth movement
int steps = static_cast<int>(duration * 60); // 60 FPS
for (int i = 1; i <= steps; i++) {
int currentX = startX + (x - startX) * i / steps;
int currentY = startY + (y - startY) * i / steps;
injectMouseEvent(sf::Event::MouseMoved, currentX, currentY);
sleep_ms(1000 / 60); // 60 FPS
}
} else {
injectMouseEvent(sf::Event::MouseMoved, x, y);
}
// Mouse up at target position
PyObject* upArgs = Py_BuildValue("(ii)", x, y);
PyObject* upKwargs = PyDict_New();
PyDict_SetItemString(upKwargs, "button", PyUnicode_FromString(button));
PyObject* upResult = _mouseUp(self, upArgs, upKwargs);
Py_DECREF(upArgs);
Py_DECREF(upKwargs);
if (!upResult) return NULL;
Py_DECREF(upResult);
Py_RETURN_NONE;
}
PyObject* McRFPy_Automation::_dragRel(PyObject* self, PyObject* args, PyObject* kwargs) {
static const char* kwlist[] = {"xOffset", "yOffset", "duration", "button", NULL};
int xOffset, yOffset;
float duration = 0.0f;
const char* button = "left";
if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|fs", const_cast<char**>(kwlist),
&xOffset, &yOffset, &duration, &button)) {
return NULL;
}
// Get current position
PyObject* pos = _position(self, NULL);
if (!pos) return NULL;
int currentX, currentY;
if (!PyArg_ParseTuple(pos, "ii", &currentX, &currentY)) {
Py_DECREF(pos);
return NULL;
}
Py_DECREF(pos);
// Call dragTo with absolute position
PyObject* dragArgs = Py_BuildValue("(ii)", currentX + xOffset, currentY + yOffset);
PyObject* dragKwargs = PyDict_New();
PyDict_SetItemString(dragKwargs, "duration", PyFloat_FromDouble(duration));
PyDict_SetItemString(dragKwargs, "button", PyUnicode_FromString(button));
PyObject* result = _dragTo(self, dragArgs, dragKwargs);
Py_DECREF(dragArgs);
Py_DECREF(dragKwargs);
return result;
}
// Method definitions for the automation module
static PyMethodDef automationMethods[] = {
{"screenshot", McRFPy_Automation::_screenshot, METH_VARARGS,
"screenshot(filename) - Save a screenshot to the specified file"},
{"position", McRFPy_Automation::_position, METH_NOARGS,
"position() - Get current mouse position as (x, y) tuple"},
{"size", McRFPy_Automation::_size, METH_NOARGS,
"size() - Get screen size as (width, height) tuple"},
{"onScreen", McRFPy_Automation::_onScreen, METH_VARARGS,
"onScreen(x, y) - Check if coordinates are within screen bounds"},
{"moveTo", (PyCFunction)McRFPy_Automation::_moveTo, METH_VARARGS | METH_KEYWORDS,
"moveTo(x, y, duration=0.0) - Move mouse to absolute position"},
{"moveRel", (PyCFunction)McRFPy_Automation::_moveRel, METH_VARARGS | METH_KEYWORDS,
"moveRel(xOffset, yOffset, duration=0.0) - Move mouse relative to current position"},
{"dragTo", (PyCFunction)McRFPy_Automation::_dragTo, METH_VARARGS | METH_KEYWORDS,
"dragTo(x, y, duration=0.0, button='left') - Drag mouse to position"},
{"dragRel", (PyCFunction)McRFPy_Automation::_dragRel, METH_VARARGS | METH_KEYWORDS,
"dragRel(xOffset, yOffset, duration=0.0, button='left') - Drag mouse relative to current position"},
{"click", (PyCFunction)McRFPy_Automation::_click, METH_VARARGS | METH_KEYWORDS,
"click(x=None, y=None, clicks=1, interval=0.0, button='left') - Click at position"},
{"rightClick", (PyCFunction)McRFPy_Automation::_rightClick, METH_VARARGS | METH_KEYWORDS,
"rightClick(x=None, y=None) - Right click at position"},
{"middleClick", (PyCFunction)McRFPy_Automation::_middleClick, METH_VARARGS | METH_KEYWORDS,
"middleClick(x=None, y=None) - Middle click at position"},
{"doubleClick", (PyCFunction)McRFPy_Automation::_doubleClick, METH_VARARGS | METH_KEYWORDS,
"doubleClick(x=None, y=None) - Double click at position"},
{"tripleClick", (PyCFunction)McRFPy_Automation::_tripleClick, METH_VARARGS | METH_KEYWORDS,
"tripleClick(x=None, y=None) - Triple click at position"},
{"scroll", (PyCFunction)McRFPy_Automation::_scroll, METH_VARARGS | METH_KEYWORDS,
"scroll(clicks, x=None, y=None) - Scroll wheel at position"},
{"mouseDown", (PyCFunction)McRFPy_Automation::_mouseDown, METH_VARARGS | METH_KEYWORDS,
"mouseDown(x=None, y=None, button='left') - Press mouse button"},
{"mouseUp", (PyCFunction)McRFPy_Automation::_mouseUp, METH_VARARGS | METH_KEYWORDS,
"mouseUp(x=None, y=None, button='left') - Release mouse button"},
{"typewrite", (PyCFunction)McRFPy_Automation::_typewrite, METH_VARARGS | METH_KEYWORDS,
"typewrite(message, interval=0.0) - Type text with optional interval between keystrokes"},
{"hotkey", McRFPy_Automation::_hotkey, METH_VARARGS,
"hotkey(*keys) - Press a hotkey combination (e.g., hotkey('ctrl', 'c'))"},
{"keyDown", McRFPy_Automation::_keyDown, METH_VARARGS,
"keyDown(key) - Press and hold a key"},
{"keyUp", McRFPy_Automation::_keyUp, METH_VARARGS,
"keyUp(key) - Release a key"},
{NULL, NULL, 0, NULL}
};
// Module definition for mcrfpy.automation
static PyModuleDef automationModule = {
PyModuleDef_HEAD_INIT,
"mcrfpy.automation",
"Automation API for McRogueFace - PyAutoGUI-compatible interface",
-1,
automationMethods
};
// Initialize automation submodule
PyObject* McRFPy_Automation::init_automation_module() {
PyObject* module = PyModule_Create(&automationModule);
if (module == NULL) {
return NULL;
}
return module;
}

View File

@ -1,56 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <string>
#include <chrono>
#include <thread>
class GameEngine;
class McRFPy_Automation {
public:
// Initialize the automation submodule
static PyObject* init_automation_module();
// Screenshot functionality
static PyObject* _screenshot(PyObject* self, PyObject* args);
// Mouse position and screen info
static PyObject* _position(PyObject* self, PyObject* args);
static PyObject* _size(PyObject* self, PyObject* args);
static PyObject* _onScreen(PyObject* self, PyObject* args);
// Mouse movement
static PyObject* _moveTo(PyObject* self, PyObject* args, PyObject* kwargs);
static PyObject* _moveRel(PyObject* self, PyObject* args, PyObject* kwargs);
static PyObject* _dragTo(PyObject* self, PyObject* args, PyObject* kwargs);
static PyObject* _dragRel(PyObject* self, PyObject* args, PyObject* kwargs);
// Mouse clicks
static PyObject* _click(PyObject* self, PyObject* args, PyObject* kwargs);
static PyObject* _rightClick(PyObject* self, PyObject* args, PyObject* kwargs);
static PyObject* _middleClick(PyObject* self, PyObject* args, PyObject* kwargs);
static PyObject* _doubleClick(PyObject* self, PyObject* args, PyObject* kwargs);
static PyObject* _tripleClick(PyObject* self, PyObject* args, PyObject* kwargs);
static PyObject* _scroll(PyObject* self, PyObject* args, PyObject* kwargs);
static PyObject* _mouseDown(PyObject* self, PyObject* args, PyObject* kwargs);
static PyObject* _mouseUp(PyObject* self, PyObject* args, PyObject* kwargs);
// Keyboard
static PyObject* _typewrite(PyObject* self, PyObject* args, PyObject* kwargs);
static PyObject* _hotkey(PyObject* self, PyObject* args);
static PyObject* _keyDown(PyObject* self, PyObject* args);
static PyObject* _keyUp(PyObject* self, PyObject* args);
// Helper functions
static void injectMouseEvent(sf::Event::EventType type, int x, int y, sf::Mouse::Button button = sf::Mouse::Left);
static void injectKeyEvent(sf::Event::EventType type, sf::Keyboard::Key key);
static void injectTextEvent(sf::Uint32 unicode);
static sf::Keyboard::Key stringToKey(const std::string& keyName);
static void sleep_ms(int milliseconds);
private:
static GameEngine* getGameEngine();
};

View File

@ -1,33 +0,0 @@
#ifndef MCROGUEFACE_CONFIG_H
#define MCROGUEFACE_CONFIG_H
#include <string>
#include <vector>
#include <filesystem>
struct McRogueFaceConfig {
// McRogueFace specific
bool headless = false;
bool audio_enabled = true;
// Python interpreter emulation
bool python_mode = false;
std::string python_command; // -c command
std::string python_module; // -m module
bool interactive_mode = false; // -i flag
bool show_version = false; // -V flag
bool show_help = false; // -h flag
// Script execution
std::filesystem::path script_path;
std::vector<std::string> script_args;
// Scripts to execute before main script (--exec flag)
std::vector<std::filesystem::path> exec_scripts;
// Screenshot functionality for headless mode
std::string screenshot_path;
bool take_screenshot = false;
};
#endif // MCROGUEFACE_CONFIG_H

View File

@ -1,234 +0,0 @@
#include "PyAnimation.h"
#include "McRFPy_API.h"
#include "UIDrawable.h"
#include "UIFrame.h"
#include "UICaption.h"
#include "UISprite.h"
#include "UIGrid.h"
#include "UIEntity.h"
#include "UI.h" // For the PyTypeObject definitions
#include <cstring>
PyObject* PyAnimation::create(PyTypeObject* type, PyObject* args, PyObject* kwds) {
PyAnimationObject* self = (PyAnimationObject*)type->tp_alloc(type, 0);
if (self != NULL) {
// Will be initialized in init
}
return (PyObject*)self;
}
int PyAnimation::init(PyAnimationObject* self, PyObject* args, PyObject* kwds) {
static const char* keywords[] = {"property", "target", "duration", "easing", "delta", nullptr};
const char* property_name;
PyObject* target_value;
float duration;
const char* easing_name = "linear";
int delta = 0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOf|sp", const_cast<char**>(keywords),
&property_name, &target_value, &duration, &easing_name, &delta)) {
return -1;
}
// Convert Python target value to AnimationValue
AnimationValue animValue;
if (PyFloat_Check(target_value)) {
animValue = static_cast<float>(PyFloat_AsDouble(target_value));
}
else if (PyLong_Check(target_value)) {
animValue = static_cast<int>(PyLong_AsLong(target_value));
}
else if (PyList_Check(target_value)) {
// List of integers for sprite animation
std::vector<int> indices;
Py_ssize_t size = PyList_Size(target_value);
for (Py_ssize_t i = 0; i < size; i++) {
PyObject* item = PyList_GetItem(target_value, i);
if (PyLong_Check(item)) {
indices.push_back(PyLong_AsLong(item));
} else {
PyErr_SetString(PyExc_TypeError, "Sprite animation list must contain only integers");
return -1;
}
}
animValue = indices;
}
else if (PyTuple_Check(target_value)) {
Py_ssize_t size = PyTuple_Size(target_value);
if (size == 2) {
// Vector2f
float x = PyFloat_AsDouble(PyTuple_GetItem(target_value, 0));
float y = PyFloat_AsDouble(PyTuple_GetItem(target_value, 1));
animValue = sf::Vector2f(x, y);
}
else if (size == 3 || size == 4) {
// Color (RGB or RGBA)
int r = PyLong_AsLong(PyTuple_GetItem(target_value, 0));
int g = PyLong_AsLong(PyTuple_GetItem(target_value, 1));
int b = PyLong_AsLong(PyTuple_GetItem(target_value, 2));
int a = size == 4 ? PyLong_AsLong(PyTuple_GetItem(target_value, 3)) : 255;
animValue = sf::Color(r, g, b, a);
}
else {
PyErr_SetString(PyExc_ValueError, "Tuple must have 2 elements (vector) or 3-4 elements (color)");
return -1;
}
}
else if (PyUnicode_Check(target_value)) {
// String for text animation
const char* str = PyUnicode_AsUTF8(target_value);
animValue = std::string(str);
}
else {
PyErr_SetString(PyExc_TypeError, "Target value must be float, int, list, tuple, or string");
return -1;
}
// Get easing function
EasingFunction easingFunc = EasingFunctions::getByName(easing_name);
// Create the Animation
self->data = std::make_shared<Animation>(property_name, animValue, duration, easingFunc, delta != 0);
return 0;
}
void PyAnimation::dealloc(PyAnimationObject* self) {
self->data.reset();
Py_TYPE(self)->tp_free((PyObject*)self);
}
PyObject* PyAnimation::get_property(PyAnimationObject* self, void* closure) {
return PyUnicode_FromString(self->data->getTargetProperty().c_str());
}
PyObject* PyAnimation::get_duration(PyAnimationObject* self, void* closure) {
return PyFloat_FromDouble(self->data->getDuration());
}
PyObject* PyAnimation::get_elapsed(PyAnimationObject* self, void* closure) {
return PyFloat_FromDouble(self->data->getElapsed());
}
PyObject* PyAnimation::get_is_complete(PyAnimationObject* self, void* closure) {
return PyBool_FromLong(self->data->isComplete());
}
PyObject* PyAnimation::get_is_delta(PyAnimationObject* self, void* closure) {
return PyBool_FromLong(self->data->isDelta());
}
PyObject* PyAnimation::start(PyAnimationObject* self, PyObject* args) {
PyObject* target_obj;
if (!PyArg_ParseTuple(args, "O", &target_obj)) {
return NULL;
}
// Get the UIDrawable from the Python object
UIDrawable* drawable = nullptr;
// Check type by comparing type names
const char* type_name = Py_TYPE(target_obj)->tp_name;
if (strcmp(type_name, "mcrfpy.Frame") == 0) {
PyUIFrameObject* frame = (PyUIFrameObject*)target_obj;
drawable = frame->data.get();
}
else if (strcmp(type_name, "mcrfpy.Caption") == 0) {
PyUICaptionObject* caption = (PyUICaptionObject*)target_obj;
drawable = caption->data.get();
}
else if (strcmp(type_name, "mcrfpy.Sprite") == 0) {
PyUISpriteObject* sprite = (PyUISpriteObject*)target_obj;
drawable = sprite->data.get();
}
else if (strcmp(type_name, "mcrfpy.Grid") == 0) {
PyUIGridObject* grid = (PyUIGridObject*)target_obj;
drawable = grid->data.get();
}
else if (strcmp(type_name, "mcrfpy.Entity") == 0) {
// Special handling for Entity since it doesn't inherit from UIDrawable
PyUIEntityObject* entity = (PyUIEntityObject*)target_obj;
// Start the animation directly on the entity
self->data->startEntity(entity->data.get());
// Add to AnimationManager
AnimationManager::getInstance().addAnimation(self->data);
Py_RETURN_NONE;
}
else {
PyErr_SetString(PyExc_TypeError, "Target must be a Frame, Caption, Sprite, Grid, or Entity");
return NULL;
}
// Start the animation
self->data->start(drawable);
// Add to AnimationManager
AnimationManager::getInstance().addAnimation(self->data);
Py_RETURN_NONE;
}
PyObject* PyAnimation::update(PyAnimationObject* self, PyObject* args) {
float deltaTime;
if (!PyArg_ParseTuple(args, "f", &deltaTime)) {
return NULL;
}
bool still_running = self->data->update(deltaTime);
return PyBool_FromLong(still_running);
}
PyObject* PyAnimation::get_current_value(PyAnimationObject* self, PyObject* args) {
AnimationValue value = self->data->getCurrentValue();
// Convert AnimationValue back to Python
return std::visit([](const auto& val) -> PyObject* {
using T = std::decay_t<decltype(val)>;
if constexpr (std::is_same_v<T, float>) {
return PyFloat_FromDouble(val);
}
else if constexpr (std::is_same_v<T, int>) {
return PyLong_FromLong(val);
}
else if constexpr (std::is_same_v<T, std::vector<int>>) {
// This shouldn't happen as we interpolate to int
return PyLong_FromLong(0);
}
else if constexpr (std::is_same_v<T, sf::Color>) {
return Py_BuildValue("(iiii)", val.r, val.g, val.b, val.a);
}
else if constexpr (std::is_same_v<T, sf::Vector2f>) {
return Py_BuildValue("(ff)", val.x, val.y);
}
else if constexpr (std::is_same_v<T, std::string>) {
return PyUnicode_FromString(val.c_str());
}
Py_RETURN_NONE;
}, value);
}
PyGetSetDef PyAnimation::getsetters[] = {
{"property", (getter)get_property, NULL, "Target property name", NULL},
{"duration", (getter)get_duration, NULL, "Animation duration in seconds", NULL},
{"elapsed", (getter)get_elapsed, NULL, "Elapsed time in seconds", NULL},
{"is_complete", (getter)get_is_complete, NULL, "Whether animation is complete", NULL},
{"is_delta", (getter)get_is_delta, NULL, "Whether animation uses delta mode", NULL},
{NULL}
};
PyMethodDef PyAnimation::methods[] = {
{"start", (PyCFunction)start, METH_VARARGS,
"Start the animation on a target UIDrawable"},
{"update", (PyCFunction)update, METH_VARARGS,
"Update the animation by deltaTime (returns True if still running)"},
{"get_current_value", (PyCFunction)get_current_value, METH_NOARGS,
"Get the current interpolated value"},
{NULL}
};

View File

@ -1,50 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "structmember.h"
#include "Animation.h"
#include <memory>
typedef struct {
PyObject_HEAD
std::shared_ptr<Animation> data;
} PyAnimationObject;
class PyAnimation {
public:
static PyObject* create(PyTypeObject* type, PyObject* args, PyObject* kwds);
static int init(PyAnimationObject* self, PyObject* args, PyObject* kwds);
static void dealloc(PyAnimationObject* self);
// Properties
static PyObject* get_property(PyAnimationObject* self, void* closure);
static PyObject* get_duration(PyAnimationObject* self, void* closure);
static PyObject* get_elapsed(PyAnimationObject* self, void* closure);
static PyObject* get_is_complete(PyAnimationObject* self, void* closure);
static PyObject* get_is_delta(PyAnimationObject* self, void* closure);
// Methods
static PyObject* start(PyAnimationObject* self, PyObject* args);
static PyObject* update(PyAnimationObject* self, PyObject* args);
static PyObject* get_current_value(PyAnimationObject* self, PyObject* args);
static PyGetSetDef getsetters[];
static PyMethodDef methods[];
};
namespace mcrfpydef {
static PyTypeObject PyAnimationType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Animation",
.tp_basicsize = sizeof(PyAnimationObject),
.tp_itemsize = 0,
.tp_dealloc = (destructor)PyAnimation::dealloc,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("Animation object for animating UI properties"),
.tp_methods = PyAnimation::methods,
.tp_getset = PyAnimation::getsetters,
.tp_init = (initproc)PyAnimation::init,
.tp_new = PyAnimation::create,
};
}

View File

@ -1,219 +0,0 @@
#include "PyColor.h"
#include "McRFPy_API.h"
#include "PyObjectUtils.h"
#include "PyRAII.h"
PyGetSetDef PyColor::getsetters[] = {
{"r", (getter)PyColor::get_member, (setter)PyColor::set_member, "Red component", (void*)0},
{"g", (getter)PyColor::get_member, (setter)PyColor::set_member, "Green component", (void*)1},
{"b", (getter)PyColor::get_member, (setter)PyColor::set_member, "Blue component", (void*)2},
{"a", (getter)PyColor::get_member, (setter)PyColor::set_member, "Alpha component", (void*)3},
{NULL}
};
PyColor::PyColor(sf::Color target)
:data(target) {}
PyObject* PyColor::pyObject()
{
PyTypeObject* type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color");
if (!type) return nullptr;
PyColorObject* obj = (PyColorObject*)type->tp_alloc(type, 0);
Py_DECREF(type);
if (obj) {
obj->data = data;
}
return (PyObject*)obj;
}
sf::Color PyColor::fromPy(PyObject* obj)
{
PyColorObject* self = (PyColorObject*)obj;
return self->data;
}
sf::Color PyColor::fromPy(PyColorObject* self)
{
return self->data;
}
void PyColor::set(sf::Color color)
{
data = color;
}
sf::Color PyColor::get()
{
return data;
}
Py_hash_t PyColor::hash(PyObject* obj)
{
auto self = (PyColorObject*)obj;
Py_hash_t value = 0;
value += self->data.r;
value << 8; value += self->data.g;
value << 8; value += self->data.b;
value << 8; value += self->data.a;
return value;
}
PyObject* PyColor::repr(PyObject* obj)
{
PyColorObject* self = (PyColorObject*)obj;
std::ostringstream ss;
sf::Color c = self->data;
ss << "<Color (" << int(c.r) << ", " << int(c.g) << ", " << int(c.b) << ", " << int(c.a) << ")>";
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
int PyColor::init(PyColorObject* self, PyObject* args, PyObject* kwds) {
//using namespace mcrfpydef;
static const char* keywords[] = { "r", "g", "b", "a", nullptr };
PyObject* leader;
int r = -1, g = -1, b = -1, a = 255;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|iii", const_cast<char**>(keywords), &leader, &g, &b, &a)) {
PyErr_SetString(PyExc_TypeError, "mcrfpy.Color requires a 3-tuple, 4-tuple, color name, or integer values within 0-255 (r, g, b, optionally a)");
return -1;
}
//std::cout << "Arg parsing succeeded. Values: " << r << " " << g << " " << b << " " << a <<std::endl;
//std::cout << PyUnicode_AsUTF8(PyObject_Repr(leader)) << std::endl;
// Tuple cases
if (PyTuple_Check(leader)) {
Py_ssize_t tupleSize = PyTuple_Size(leader);
if (tupleSize < 3 || tupleSize > 4) {
PyErr_SetString(PyExc_TypeError, "Invalid tuple length: mcrfpy.Color requires a 3-tuple, 4-tuple, color name, or integer values within 0-255 (r, g, b, optionally a)");
return -1;
}
r = PyLong_AsLong(PyTuple_GetItem(leader, 0));
g = PyLong_AsLong(PyTuple_GetItem(leader, 1));
b = PyLong_AsLong(PyTuple_GetItem(leader, 2));
if (tupleSize == 4) {
a = PyLong_AsLong(PyTuple_GetItem(leader, 3));
}
}
// Color name (not implemented yet)
else if (PyUnicode_Check(leader)) {
PyErr_SetString(PyExc_NotImplementedError, "Color names aren't ready yet");
return -1;
}
// Check if the leader is actually an integer for the r value
else if (PyLong_Check(leader)) {
r = PyLong_AsLong(leader);
// Additional validation not shown; g, b are required to be parsed
} else {
PyErr_SetString(PyExc_TypeError, "mcrfpy.Color requires a 3-tuple, 4-tuple, color name, or integer values within 0-255 (r, g, b, optionally a)");
return -1;
}
// Validate color values
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || a < 0 || a > 255) {
PyErr_SetString(PyExc_ValueError, "Color values must be between 0 and 255.");
return -1;
}
self->data = sf::Color(r, g, b, a);
return 0;
}
PyObject* PyColor::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds)
{
auto obj = (PyObject*)type->tp_alloc(type, 0);
//Py_INCREF(obj);
return obj;
}
PyObject* PyColor::get_member(PyObject* obj, void* closure)
{
PyColorObject* self = (PyColorObject*)obj;
long member = (long)closure;
switch (member) {
case 0: // r
return PyLong_FromLong(self->data.r);
case 1: // g
return PyLong_FromLong(self->data.g);
case 2: // b
return PyLong_FromLong(self->data.b);
case 3: // a
return PyLong_FromLong(self->data.a);
default:
PyErr_SetString(PyExc_AttributeError, "Invalid color member");
return NULL;
}
}
int PyColor::set_member(PyObject* obj, PyObject* value, void* closure)
{
PyColorObject* self = (PyColorObject*)obj;
long member = (long)closure;
if (!PyLong_Check(value)) {
PyErr_SetString(PyExc_TypeError, "Color values must be integers");
return -1;
}
long val = PyLong_AsLong(value);
if (val < 0 || val > 255) {
PyErr_SetString(PyExc_ValueError, "Color values must be between 0 and 255");
return -1;
}
switch (member) {
case 0: // r
self->data.r = static_cast<sf::Uint8>(val);
break;
case 1: // g
self->data.g = static_cast<sf::Uint8>(val);
break;
case 2: // b
self->data.b = static_cast<sf::Uint8>(val);
break;
case 3: // a
self->data.a = static_cast<sf::Uint8>(val);
break;
default:
PyErr_SetString(PyExc_AttributeError, "Invalid color member");
return -1;
}
return 0;
}
PyColorObject* PyColor::from_arg(PyObject* args)
{
// Use RAII for type reference management
PyRAII::PyTypeRef type("Color", McRFPy_API::mcrf_module);
if (!type) {
return NULL;
}
// Check if args is already a Color instance
if (PyObject_IsInstance(args, (PyObject*)type.get())) {
return (PyColorObject*)args;
}
// Create new Color object using RAII
PyRAII::PyObjectRef obj(type->tp_alloc(type.get(), 0), true);
if (!obj) {
return NULL;
}
// Initialize the object
int err = init((PyColorObject*)obj.get(), args, NULL);
if (err) {
// obj will be automatically cleaned up when it goes out of scope
return NULL;
}
// Release ownership and return
return (PyColorObject*)obj.release();
}

View File

@ -1,49 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
class PyColor;
class UIDrawable; // forward declare for pointer
typedef struct {
PyObject_HEAD
sf::Color data;
} PyColorObject;
class PyColor
{
private:
public:
sf::Color data;
PyColor(sf::Color);
void set(sf::Color);
sf::Color get();
PyObject* pyObject();
static sf::Color fromPy(PyObject*);
static sf::Color fromPy(PyColorObject*);
static PyObject* repr(PyObject*);
static Py_hash_t hash(PyObject*);
static int init(PyColorObject*, PyObject*, PyObject*);
static PyObject* pynew(PyTypeObject* type, PyObject* args=NULL, PyObject* kwds=NULL);
static PyObject* get_member(PyObject*, void*);
static int set_member(PyObject*, PyObject*, void*);
static PyGetSetDef getsetters[];
static PyColorObject* from_arg(PyObject*);
};
namespace mcrfpydef {
static PyTypeObject PyColorType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Color",
.tp_basicsize = sizeof(PyColorObject),
.tp_itemsize = 0,
.tp_repr = PyColor::repr,
.tp_hash = PyColor::hash,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("SFML Color Object"),
.tp_getset = PyColor::getsetters,
.tp_init = (initproc)PyColor::init,
.tp_new = PyColor::pynew,
};
}

View File

@ -1,79 +0,0 @@
#include "PyFont.h"
#include "McRFPy_API.h"
PyFont::PyFont(std::string filename)
: source(filename)
{
font = sf::Font();
font.loadFromFile(source);
}
PyObject* PyFont::pyObject()
{
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Font");
//PyObject* obj = PyType_GenericAlloc(&mcrfpydef::PyFontType, 0);
PyObject* obj = PyFont::pynew(type, Py_None, Py_None);
try {
((PyFontObject*)obj)->data = shared_from_this();
}
catch (std::bad_weak_ptr& e)
{
std::cout << "Bad weak ptr: shared_from_this() failed in PyFont::pyObject(); did you create a PyFont outside of std::make_shared? enjoy your segfault, soon!" << std::endl;
}
// TODO - shared_from_this will raise an exception if the object does not have a shared pointer. Constructor should be made private; write a factory function
return obj;
}
PyObject* PyFont::repr(PyObject* obj)
{
PyFontObject* self = (PyFontObject*)obj;
std::ostringstream ss;
if (!self->data)
{
ss << "<Font [invalid internal object]>";
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
auto& pfont = *(self->data);
ss << "<Font (family=" << pfont.font.getInfo().family << ") source=`" << pfont.source << "`>";
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
Py_hash_t PyFont::hash(PyObject* obj)
{
auto self = (PyFontObject*)obj;
return reinterpret_cast<Py_hash_t>(self->data.get());
}
int PyFont::init(PyFontObject* self, PyObject* args, PyObject* kwds)
{
static const char* keywords[] = { "filename", nullptr };
char* filename;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", const_cast<char**>(keywords), &filename))
return -1;
self->data = std::make_shared<PyFont>(filename);
return 0;
}
PyObject* PyFont::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds)
{
return (PyObject*)type->tp_alloc(type, 0);
}
PyObject* PyFont::get_family(PyFontObject* self, void* closure)
{
return PyUnicode_FromString(self->data->font.getInfo().family.c_str());
}
PyObject* PyFont::get_source(PyFontObject* self, void* closure)
{
return PyUnicode_FromString(self->data->source.c_str());
}
PyGetSetDef PyFont::getsetters[] = {
{"family", (getter)PyFont::get_family, NULL, "Font family name", NULL},
{"source", (getter)PyFont::get_source, NULL, "Source filename of the font", NULL},
{NULL} // Sentinel
};

View File

@ -1,47 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
class PyFont;
typedef struct {
PyObject_HEAD
std::shared_ptr<PyFont> data;
} PyFontObject;
class PyFont : public std::enable_shared_from_this<PyFont>
{
private:
std::string source;
public:
PyFont(std::string filename);
sf::Font font;
PyObject* pyObject();
static PyObject* repr(PyObject*);
static Py_hash_t hash(PyObject*);
static int init(PyFontObject*, PyObject*, PyObject*);
static PyObject* pynew(PyTypeObject* type, PyObject* args=NULL, PyObject* kwds=NULL);
// Getters for properties
static PyObject* get_family(PyFontObject* self, void* closure);
static PyObject* get_source(PyFontObject* self, void* closure);
static PyGetSetDef getsetters[];
};
namespace mcrfpydef {
static PyTypeObject PyFontType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Font",
.tp_basicsize = sizeof(PyFontObject),
.tp_itemsize = 0,
.tp_repr = PyFont::repr,
//.tp_hash = PyFont::hash,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("SFML Font Object"),
.tp_getset = PyFont::getsetters,
//.tp_base = &PyBaseObject_Type,
.tp_init = (initproc)PyFont::init,
.tp_new = PyType_GenericNew, //PyFont::pynew,
};
}

View File

@ -1,76 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "McRFPy_API.h"
#include "PyRAII.h"
namespace PyObjectUtils {
// Template for getting Python type object from module
template<typename T>
PyTypeObject* getPythonType(const char* typeName) {
PyTypeObject* type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, typeName);
if (!type) {
PyErr_Format(PyExc_RuntimeError, "Could not find %s type in module", typeName);
}
return type;
}
// Generic function to create a Python object of given type
inline PyObject* createPyObjectGeneric(const char* typeName) {
PyTypeObject* type = getPythonType<void>(typeName);
if (!type) return nullptr;
PyObject* obj = type->tp_alloc(type, 0);
Py_DECREF(type);
return obj;
}
// Helper function to allocate and initialize a Python object with data
template<typename PyObjType, typename DataType>
PyObject* createPyObjectWithData(const char* typeName, DataType data) {
PyTypeObject* type = getPythonType<void>(typeName);
if (!type) return nullptr;
PyObjType* obj = (PyObjType*)type->tp_alloc(type, 0);
Py_DECREF(type);
if (obj) {
obj->data = data;
}
return (PyObject*)obj;
}
// Function to convert UIDrawable to appropriate Python object
// This is moved to UICollection.cpp to avoid circular dependencies
// RAII-based object creation example
inline PyObject* createPyObjectGenericRAII(const char* typeName) {
PyRAII::PyTypeRef type(typeName, McRFPy_API::mcrf_module);
if (!type) {
PyErr_Format(PyExc_RuntimeError, "Could not find %s type in module", typeName);
return nullptr;
}
PyObject* obj = type->tp_alloc(type.get(), 0);
// Return the new reference (caller owns it)
return obj;
}
// Example of using PyObjectRef for safer reference management
template<typename PyObjType, typename DataType>
PyObject* createPyObjectWithDataRAII(const char* typeName, DataType data) {
PyRAII::PyObjectRef obj = PyRAII::createObject<PyObjType>(typeName, McRFPy_API::mcrf_module);
if (!obj) {
PyErr_Format(PyExc_RuntimeError, "Could not create %s object", typeName);
return nullptr;
}
// Access the object through the RAII wrapper
((PyObjType*)obj.get())->data = data;
// Release ownership to return to Python
return obj.release();
}
}

View File

@ -1,138 +0,0 @@
#pragma once
#include "Python.h"
#include <utility>
namespace PyRAII {
// RAII wrapper for PyObject* that automatically manages reference counting
class PyObjectRef {
private:
PyObject* ptr;
public:
// Constructors
PyObjectRef() : ptr(nullptr) {}
explicit PyObjectRef(PyObject* p, bool steal_ref = false) : ptr(p) {
if (ptr && !steal_ref) {
Py_INCREF(ptr);
}
}
// Copy constructor
PyObjectRef(const PyObjectRef& other) : ptr(other.ptr) {
if (ptr) {
Py_INCREF(ptr);
}
}
// Move constructor
PyObjectRef(PyObjectRef&& other) noexcept : ptr(other.ptr) {
other.ptr = nullptr;
}
// Destructor
~PyObjectRef() {
Py_XDECREF(ptr);
}
// Copy assignment
PyObjectRef& operator=(const PyObjectRef& other) {
if (this != &other) {
Py_XDECREF(ptr);
ptr = other.ptr;
if (ptr) {
Py_INCREF(ptr);
}
}
return *this;
}
// Move assignment
PyObjectRef& operator=(PyObjectRef&& other) noexcept {
if (this != &other) {
Py_XDECREF(ptr);
ptr = other.ptr;
other.ptr = nullptr;
}
return *this;
}
// Access operators
PyObject* get() const { return ptr; }
PyObject* operator->() const { return ptr; }
PyObject& operator*() const { return *ptr; }
operator bool() const { return ptr != nullptr; }
// Release ownership (for returning to Python)
PyObject* release() {
PyObject* temp = ptr;
ptr = nullptr;
return temp;
}
// Reset with new pointer
void reset(PyObject* p = nullptr, bool steal_ref = false) {
if (p != ptr) {
Py_XDECREF(ptr);
ptr = p;
if (ptr && !steal_ref) {
Py_INCREF(ptr);
}
}
}
};
// Helper class for managing PyTypeObject* references from module lookups
class PyTypeRef {
private:
PyTypeObject* type;
public:
PyTypeRef() : type(nullptr) {}
explicit PyTypeRef(const char* typeName, PyObject* module) {
type = (PyTypeObject*)PyObject_GetAttrString(module, typeName);
// GetAttrString returns a new reference, so we own it
}
~PyTypeRef() {
Py_XDECREF((PyObject*)type);
}
// Delete copy operations to prevent accidental reference issues
PyTypeRef(const PyTypeRef&) = delete;
PyTypeRef& operator=(const PyTypeRef&) = delete;
// Allow move operations
PyTypeRef(PyTypeRef&& other) noexcept : type(other.type) {
other.type = nullptr;
}
PyTypeRef& operator=(PyTypeRef&& other) noexcept {
if (this != &other) {
Py_XDECREF((PyObject*)type);
type = other.type;
other.type = nullptr;
}
return *this;
}
PyTypeObject* get() const { return type; }
PyTypeObject* operator->() const { return type; }
operator bool() const { return type != nullptr; }
};
// Convenience function to create a new object with RAII
template<typename PyObjType>
PyObjectRef createObject(const char* typeName, PyObject* module) {
PyTypeRef type(typeName, module);
if (!type) {
return PyObjectRef();
}
PyObject* obj = type->tp_alloc(type.get(), 0);
// tp_alloc returns a new reference, so we steal it
return PyObjectRef(obj, true);
}
}

View File

@ -2,7 +2,6 @@
#include "ActionCode.h"
#include "Resources.h"
#include "PyCallable.h"
#include <algorithm>
PyScene::PyScene(GameEngine* g) : Scene(g)
{
@ -12,8 +11,7 @@ PyScene::PyScene(GameEngine* g) : Scene(g)
registerAction(ActionCode::MOUSEWHEEL + ActionCode::WHEEL_DEL, "wheel_up");
registerAction(ActionCode::MOUSEWHEEL + ActionCode::WHEEL_NEG + ActionCode::WHEEL_DEL, "wheel_down");
// console (` / ~ key) - don't hard code.
//registerAction(ActionCode::KEY + sf::Keyboard::Grave, "debug_menu");
registerAction(ActionCode::KEY + sf::Keyboard::Grave, "debug_menu");
}
void PyScene::update()
@ -22,11 +20,6 @@ void PyScene::update()
void PyScene::do_mouse_input(std::string button, std::string type)
{
// In headless mode, mouse input is not available
if (game->isHeadless()) {
return;
}
auto unscaledmousepos = sf::Mouse::getPosition(game->getWindow());
auto mousepos = game->getWindow().mapPixelToCoords(unscaledmousepos);
UIDrawable* target;
@ -55,7 +48,10 @@ void PyScene::do_mouse_input(std::string button, std::string type)
void PyScene::doAction(std::string name, std::string type)
{
if (name.compare("left") == 0 || name.compare("rclick") == 0 || name.compare("wheel_up") == 0 || name.compare("wheel_down") == 0) {
if (ACTIONPY) {
McRFPy_API::doAction(name.substr(0, name.size() - 3));
}
else if (name.compare("left") == 0 || name.compare("rclick") == 0 || name.compare("wheel_up") == 0 || name.compare("wheel_down") == 0) {
do_mouse_input(name, type);
}
else if ACTIONONCE("debug_menu") {
@ -63,25 +59,16 @@ void PyScene::doAction(std::string name, std::string type)
}
}
void PyScene::render()
void PyScene::sRender()
{
game->getRenderTarget().clear();
game->getWindow().clear();
// Only sort if z_index values have changed
if (ui_elements_need_sort) {
std::sort(ui_elements->begin(), ui_elements->end(),
[](const std::shared_ptr<UIDrawable>& a, const std::shared_ptr<UIDrawable>& b) {
return a->z_index < b->z_index;
});
ui_elements_need_sort = false;
}
// Render in sorted order (no need to copy anymore)
for (auto e: *ui_elements)
auto vec = *ui_elements;
for (auto e: vec)
{
if (e)
e->render();
}
// Display is handled by GameEngine
game->getWindow().display();
}

View File

@ -11,10 +11,7 @@ public:
PyScene(GameEngine*);
void update() override final;
void doAction(std::string, std::string) override final;
void render() override final;
void sRender() override final;
void do_mouse_input(std::string, std::string);
// Dirty flag for z_index sorting optimization
bool ui_elements_need_sort = true;
};

View File

@ -1,5 +1,5 @@
#include "PyTexture.h"
#include "McRFPy_API.h"
PyTexture::PyTexture(std::string filename, int sprite_w, int sprite_h)
: source(filename), sprite_width(sprite_w), sprite_height(sprite_h)
@ -28,9 +28,7 @@ sf::Sprite PyTexture::sprite(int index, sf::Vector2f pos, sf::Vector2f s)
PyObject* PyTexture::pyObject()
{
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture");
PyObject* obj = PyTexture::pynew(type, Py_None, Py_None);
PyObject* obj = PyType_GenericAlloc(&mcrfpydef::PyTextureType, 0);
try {
((PyTextureObject*)obj)->data = shared_from_this();
}
@ -42,22 +40,6 @@ PyObject* PyTexture::pyObject()
return obj;
}
PyObject* PyTexture::repr(PyObject* obj)
{
PyTextureObject* self = (PyTextureObject*)obj;
std::ostringstream ss;
if (!self->data)
{
ss << "<Texture [invalid internal object]>";
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
auto& ptex = *(self->data);
ss << "<Texture " << ptex.sheet_height << " rows, " << ptex.sheet_width << " columns; " << ptex.sprite_width << "x" << ptex.sprite_height << "px sprites. source='" << ptex.source << "'>";
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
Py_hash_t PyTexture::hash(PyObject* obj)
{
auto self = (PyTextureObject*)obj;
@ -79,43 +61,3 @@ PyObject* PyTexture::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds)
{
return (PyObject*)type->tp_alloc(type, 0);
}
PyObject* PyTexture::get_sprite_width(PyTextureObject* self, void* closure)
{
return PyLong_FromLong(self->data->sprite_width);
}
PyObject* PyTexture::get_sprite_height(PyTextureObject* self, void* closure)
{
return PyLong_FromLong(self->data->sprite_height);
}
PyObject* PyTexture::get_sheet_width(PyTextureObject* self, void* closure)
{
return PyLong_FromLong(self->data->sheet_width);
}
PyObject* PyTexture::get_sheet_height(PyTextureObject* self, void* closure)
{
return PyLong_FromLong(self->data->sheet_height);
}
PyObject* PyTexture::get_sprite_count(PyTextureObject* self, void* closure)
{
return PyLong_FromLong(self->data->getSpriteCount());
}
PyObject* PyTexture::get_source(PyTextureObject* self, void* closure)
{
return PyUnicode_FromString(self->data->source.c_str());
}
PyGetSetDef PyTexture::getsetters[] = {
{"sprite_width", (getter)PyTexture::get_sprite_width, NULL, "Width of each sprite in pixels", NULL},
{"sprite_height", (getter)PyTexture::get_sprite_height, NULL, "Height of each sprite in pixels", NULL},
{"sheet_width", (getter)PyTexture::get_sheet_width, NULL, "Number of sprite columns in the texture", NULL},
{"sheet_height", (getter)PyTexture::get_sheet_height, NULL, "Number of sprite rows in the texture", NULL},
{"sprite_count", (getter)PyTexture::get_sprite_count, NULL, "Total number of sprites in the texture", NULL},
{"source", (getter)PyTexture::get_source, NULL, "Source filename of the texture", NULL},
{NULL} // Sentinel
};

View File

@ -19,38 +19,22 @@ public:
int sprite_width, sprite_height; // just use them read only, OK?
PyTexture(std::string filename, int sprite_w, int sprite_h);
sf::Sprite sprite(int index, sf::Vector2f pos = sf::Vector2f(0, 0), sf::Vector2f s = sf::Vector2f(1.0, 1.0));
int getSpriteCount() const { return sheet_width * sheet_height; }
PyObject* pyObject();
static PyObject* repr(PyObject*);
static Py_hash_t hash(PyObject*);
static int init(PyTextureObject*, PyObject*, PyObject*);
static PyObject* pynew(PyTypeObject* type, PyObject* args=NULL, PyObject* kwds=NULL);
// Getters for properties
static PyObject* get_sprite_width(PyTextureObject* self, void* closure);
static PyObject* get_sprite_height(PyTextureObject* self, void* closure);
static PyObject* get_sheet_width(PyTextureObject* self, void* closure);
static PyObject* get_sheet_height(PyTextureObject* self, void* closure);
static PyObject* get_sprite_count(PyTextureObject* self, void* closure);
static PyObject* get_source(PyTextureObject* self, void* closure);
static PyGetSetDef getsetters[];
};
namespace mcrfpydef {
static PyTypeObject PyTextureType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Texture",
.tp_basicsize = sizeof(PyTextureObject),
.tp_itemsize = 0,
.tp_repr = PyTexture::repr,
.tp_hash = PyTexture::hash,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("SFML Texture Object"),
.tp_getset = PyTexture::getsetters,
//.tp_base = &PyBaseObject_Type,
.tp_init = (initproc)PyTexture::init,
.tp_new = PyType_GenericNew, //PyTexture::pynew,
.tp_new = PyTexture::pynew,
};
}

View File

@ -1,174 +0,0 @@
#include "PyVector.h"
#include "PyObjectUtils.h"
PyGetSetDef PyVector::getsetters[] = {
{"x", (getter)PyVector::get_member, (setter)PyVector::set_member, "X/horizontal component", (void*)0},
{"y", (getter)PyVector::get_member, (setter)PyVector::set_member, "Y/vertical component", (void*)1},
{NULL}
};
PyVector::PyVector(sf::Vector2f target)
:data(target) {}
PyObject* PyVector::pyObject()
{
PyTypeObject* type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
if (!type) return nullptr;
PyVectorObject* obj = (PyVectorObject*)type->tp_alloc(type, 0);
Py_DECREF(type);
if (obj) {
obj->data = data;
}
return (PyObject*)obj;
}
sf::Vector2f PyVector::fromPy(PyObject* obj)
{
PyVectorObject* self = (PyVectorObject*)obj;
return self->data;
}
sf::Vector2f PyVector::fromPy(PyVectorObject* self)
{
return self->data;
}
Py_hash_t PyVector::hash(PyObject* obj)
{
auto self = (PyVectorObject*)obj;
Py_hash_t value = 0;
value += self->data.x;
value << 8; value += self->data.y;
return value;
}
PyObject* PyVector::repr(PyObject* obj)
{
PyVectorObject* self = (PyVectorObject*)obj;
std::ostringstream ss;
sf::Vector2f v = self->data;
ss << "<Vector (" << v.x << ", " << v.y << ")>";
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
int PyVector::init(PyVectorObject* self, PyObject* args, PyObject* kwds)
{
using namespace mcrfpydef;
static const char* keywords[] = { "x", "y", nullptr };
PyObject* leader = NULL;
float x=0, y=0;
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Of", const_cast<char**>(keywords), &leader, &y))
{
//PyErr_SetString(PyExc_TypeError, "mcrfpy.Vector requires a 2-tuple or two numeric values");
return -1;
}
if (leader == NULL || leader == Py_None)
{
self->data = sf::Vector2f();
return 0;
}
if (PyTuple_Check(leader))
{
if (PyTuple_Size(leader) != 2)
{
PyErr_SetString(PyExc_TypeError, "Invalid tuple length: mcrfpy.Vector requires a 2-tuple");
return -1;
}
x = PyFloat_AsDouble(PyTuple_GetItem(leader, 0));
y = PyFloat_AsDouble(PyTuple_GetItem(leader, 1));
self->data = sf::Vector2f(x, y);
return 0;
}
// else -
else if (!PyFloat_Check(leader) && !(PyLong_Check(leader)))
{
PyErr_SetString(PyExc_TypeError, "mcrfpy.Vector requires a 2-tuple or two numeric values");
return -1;
}
if (PyFloat_Check(leader)) x = PyFloat_AsDouble(leader);
else x = PyLong_AsDouble(leader);
self->data = sf::Vector2f(x, y);
return 0;
}
PyObject* PyVector::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds)
{
return (PyObject*)type->tp_alloc(type, 0);
}
PyObject* PyVector::get_member(PyObject* obj, void* closure)
{
PyVectorObject* self = (PyVectorObject*)obj;
if (reinterpret_cast<long>(closure) == 0) {
// x
return PyFloat_FromDouble(self->data.x);
} else {
// y
return PyFloat_FromDouble(self->data.y);
}
}
int PyVector::set_member(PyObject* obj, PyObject* value, void* closure)
{
PyVectorObject* self = (PyVectorObject*)obj;
float val;
if (PyFloat_Check(value)) {
val = PyFloat_AsDouble(value);
} else if (PyLong_Check(value)) {
val = PyLong_AsDouble(value);
} else {
PyErr_SetString(PyExc_TypeError, "Vector members must be numeric");
return -1;
}
if (reinterpret_cast<long>(closure) == 0) {
// x
self->data.x = val;
} else {
// y
self->data.y = val;
}
return 0;
}
PyVectorObject* PyVector::from_arg(PyObject* args)
{
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
if (PyObject_IsInstance(args, (PyObject*)type)) return (PyVectorObject*)args;
auto obj = (PyVectorObject*)type->tp_alloc(type, 0);
// Handle different input types
if (PyTuple_Check(args)) {
// It's already a tuple, pass it directly to init
int err = init(obj, args, NULL);
if (err) {
Py_DECREF(obj);
return NULL;
}
} else {
// Wrap single argument in a tuple for init
PyObject* tuple = PyTuple_Pack(1, args);
if (!tuple) {
Py_DECREF(obj);
return NULL;
}
int err = init(obj, tuple, NULL);
Py_DECREF(tuple);
if (err) {
Py_DECREF(obj);
return NULL;
}
}
return obj;
}

View File

@ -1,45 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "McRFPy_API.h"
typedef struct {
PyObject_HEAD
sf::Vector2f data;
} PyVectorObject;
class PyVector
{
public:
sf::Vector2f data;
PyVector(sf::Vector2f);
PyVector();
PyObject* pyObject();
static sf::Vector2f fromPy(PyObject*);
static sf::Vector2f fromPy(PyVectorObject*);
static PyObject* repr(PyObject*);
static Py_hash_t hash(PyObject*);
static int init(PyVectorObject*, PyObject*, PyObject*);
static PyObject* pynew(PyTypeObject* type, PyObject* args=NULL, PyObject* kwds=NULL);
static PyObject* get_member(PyObject*, void*);
static int set_member(PyObject*, PyObject*, void*);
static PyVectorObject* from_arg(PyObject*);
static PyGetSetDef getsetters[];
};
namespace mcrfpydef {
static PyTypeObject PyVectorType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Vector",
.tp_basicsize = sizeof(PyVectorObject),
.tp_itemsize = 0,
.tp_repr = PyVector::repr,
.tp_hash = PyVector::hash,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("SFML Vector Object"),
.tp_getset = PyVector::getsetters,
.tp_init = (initproc)PyVector::init,
.tp_new = PyVector::pynew,
};
}

View File

@ -30,6 +30,16 @@ std::string Scene::action(int code)
return actions[code];
}
bool Scene::registerActionInjected(int code, std::string name)
{
std::cout << "Inject registered action - default implementation\n";
return false;
}
bool Scene::unregisterActionInjected(int code, std::string name)
{
return false;
}
void Scene::key_register(PyObject* callable)
{

View File

@ -4,6 +4,7 @@
#define ACTION(X, Y) (name.compare(X) == 0 && type.compare(Y) == 0)
#define ACTIONONCE(X) ((name.compare(X) == 0 && type.compare("start") == 0 && !actionState[name]))
#define ACTIONAFTER(X) ((name.compare(X) == 0 && type.compare("end") == 0))
#define ACTIONPY ((name.size() > 3 && name.compare(name.size() - 3, 3, "_py") == 0))
#include "Common.h"
#include <list>
@ -30,12 +31,14 @@ public:
//Scene();
Scene(GameEngine*);
virtual void update() = 0;
virtual void render() = 0;
virtual void sRender() = 0;
virtual void doAction(std::string, std::string) = 0;
bool hasAction(std::string);
bool hasAction(int);
std::string action(int);
virtual bool registerActionInjected(int, std::string);
virtual bool unregisterActionInjected(int, std::string);
std::shared_ptr<std::vector<std::shared_ptr<UIDrawable>>> ui_elements;

527
src/UI.cpp Normal file
View File

@ -0,0 +1,527 @@
#include "UI.h"
#include "Resources.h"
#include "GameEngine.h"
/* //callability fields & methods
PyObject* click_callable;
virtual UIDrawable* click_at(sf::Vector2f point);
void click_register(PyObject*);
void click_unregister();
*/
UIDrawable::UIDrawable() { click_callable = NULL; }
UIDrawable* UIFrame::click_at(sf::Vector2f point)
{
for (auto e: *children)
{
auto p = e->click_at(point + box.getPosition());
if (p)
return p;
}
if (click_callable)
{
float x = box.getPosition().x, y = box.getPosition().y, w = box.getSize().x, h = box.getSize().y;
if (point.x > x && point.y > y && point.x < x+w && point.y < y+h) return this;
}
return NULL;
}
UIDrawable* UICaption::click_at(sf::Vector2f point)
{
if (click_callable)
{
if (text.getGlobalBounds().contains(point)) return this;
}
return NULL;
}
UIDrawable* UISprite::click_at(sf::Vector2f point)
{
if (click_callable)
{
if(sprite.getGlobalBounds().contains(point)) return this;
}
return NULL;
}
UIDrawable* UIGrid::click_at(sf::Vector2f point)
{
if (click_callable)
{
if(box.getGlobalBounds().contains(point)) return this;
}
return NULL;
}
void UIDrawable::click_register(PyObject* callable)
{
/*
if (click_callable)
{
// decrement reference before overwriting
Py_DECREF(click_callable);
}
click_callable = callable;
Py_INCREF(click_callable);
*/
click_callable = std::make_unique<PyClickCallable>(callable);
}
void UIDrawable::click_unregister()
{
/*
if (click_callable == NULL) return;
Py_DECREF(click_callable);
click_callable = NULL;
*/
click_callable.reset();
}
void UIDrawable::render()
{
//std::cout << "Rendering base UIDrawable\n";
render(sf::Vector2f());
}
UIFrame::UIFrame():
//x(0), y(0), w(0), h(0),
outline(0)
{
children = std::make_shared<std::vector<std::shared_ptr<UIDrawable>>>();
box.setPosition(0, 0);
box.setSize(sf::Vector2f(0, 0));
/*
pyOutlineColor = NULL;
pyFillColor = NULL;
_outlineColor = NULL;
_fillColor = NULL;
*/
}
UIFrame::UIFrame(float _x, float _y, float _w, float _h):
//x(_x), y(_y), w(_w), h(_h),
outline(0)
{
box.setPosition(_x, _y);
box.setSize(sf::Vector2f(_w, _h));
children = std::make_shared<std::vector<std::shared_ptr<UIDrawable>>>();
/*
pyOutlineColor = NULL;
pyFillColor = NULL;
_outlineColor = NULL;
_fillColor = NULL;
*/
}
UIFrame::~UIFrame()
{
children.reset();
/*
if (pyOutlineColor) Py_DECREF(pyOutlineColor);
else if (_outlineColor) delete _outlineColor;
if (pyFillColor) Py_DECREF(pyFillColor);
else if (_fillColor) delete _fillColor;
*/
}
/*
sf::Color& fillColor(); // getter
void fillColor(sf::Color c); // C++ setter
void fillColor(PyObject* pyColor); // Python setter
sf::Color& outlineColor(); // getter
void outlineColor(sf::Color c); // C++ setter
void outlineColor(PyObject* pyColor); // Python setter
*/
PyObjectsEnum UIFrame::derived_type()
{
return PyObjectsEnum::UIFRAME;
}
void UIFrame::render(sf::Vector2f offset)
{
//std::cout << "Rendering UIFrame w/ offset " << offset.x << ", " << offset.y << "\n";
//std::cout << "position = " << x << ", " << y << "\n";
box.move(offset);
Resources::game->getWindow().draw(box);
box.move(-offset);
//sf::RectangleShape box = sf::RectangleShape(sf::Vector2f(w,h));
//sf::Vector2f pos = sf::Vector2f(x, y);
//box.setPosition(offset + pos);
//if (_fillColor) { box.setFillColor(fillColor()); }
//if (_outlineColor) { box.setOutlineColor(outlineColor()); }
//box.setOutlineThickness(outline);
//Resources::game->getWindow().draw(box);
for (auto drawable : *children) {
drawable->render(offset + box.getPosition());
}
}
void UICaption::render(sf::Vector2f offset)
{
//std::cout << "Rendering Caption with offset\n";
text.move(offset);
Resources::game->getWindow().draw(text);
text.move(-offset);
}
UISprite::UISprite() {}
/*
// * tearing down the old IndexTexture way of life
UISprite::UISprite(IndexTexture* _itex, int _sprite_index, float x = 0.0, float y = 0.0, float s = 1.0)
: itex(_itex), sprite_index(_sprite_index)
{
sprite.setTexture(_itex->texture);
sprite.setTextureRect(_itex->spriteCoordinates(_sprite_index));
sprite.setPosition(sf::Vector2f(x, y));
sprite.setScale(sf::Vector2f(s, s));
}
UISprite::UISprite(IndexTexture* _itex, int _sprite_index, sf::Vector2f pos, float s = 1.0)
: itex(_itex), sprite_index(_sprite_index)
{
sprite.setTexture(_itex->texture);
sprite.setTextureRect(_itex->spriteCoordinates(_sprite_index));
sprite.setPosition(pos);
sprite.setScale(sf::Vector2f(s, s));
}
*/
UISprite::UISprite(std::shared_ptr<PyTexture> _ptex, int _sprite_index, sf::Vector2f _pos, float _scale)
: ptex(_ptex), sprite_index(_sprite_index)
{
sprite = ptex->sprite(sprite_index, _pos, sf::Vector2f(_scale, _scale));
}
//void UISprite::update()
//{
//auto& tex = Resources::game->textures[texture_index];
//sprite.setTexture(tex.texture);
//sprite.setScale(sf::Vector2f(scale, scale));
//sprite.setPosition(sf::Vector2f(x, y));
//std::cout << "Drawable position: " << x << ", " << y << " -> " << s.getPosition().x << ", " << s.getPosition().y << std::endl;
//sprite.setTextureRect(tex.spriteCoordinates(sprite_index));
//}
void UISprite::render(sf::Vector2f offset)
{
sprite.move(offset);
Resources::game->getWindow().draw(sprite);
sprite.move(-offset);
}
// 7DRL hack; needed to draw entities to UIGrid. TODO, apply this technique to all UIDrawables
void UISprite::render(sf::Vector2f offset, sf::RenderTexture& target)
{
sprite.move(offset);
target.draw(sprite);
sprite.move(-offset);
}
/*
void UISprite::setPosition(float x, float y)
{
setPosition(sf::Vector2f(x, y));
}
*/
void UISprite::setPosition(sf::Vector2f pos)
{
sprite.setPosition(pos);
}
void UISprite::setScale(sf::Vector2f s)
{
sprite.setScale(s);
}
void UISprite::setTexture(std::shared_ptr<PyTexture> _ptex, int _sprite_index)
{
ptex = _ptex;
if (_sprite_index != -1) // if you are changing textures, there's a good chance you need a new index too
sprite_index = _sprite_index;
sprite = ptex->sprite(sprite_index, sprite.getPosition(), sprite.getScale());
}
void UISprite::setSpriteIndex(int _sprite_index)
{
sprite_index = _sprite_index;
sprite = ptex->sprite(sprite_index, sprite.getPosition(), sprite.getScale());
}
sf::Vector2f UISprite::getScale()
{
return sprite.getScale();
}
sf::Vector2f UISprite::getPosition()
{
return sprite.getPosition();
}
std::shared_ptr<PyTexture> UISprite::getTexture()
{
return ptex;
}
int UISprite::getSpriteIndex()
{
return sprite_index;
}
PyObjectsEnum UICaption::derived_type()
{
return PyObjectsEnum::UICAPTION;
}
PyObjectsEnum UISprite::derived_type()
{
return PyObjectsEnum::UISPRITE;
}
// UIGrid support classes' methods
UIGridPoint::UIGridPoint()
:color(1.0f, 1.0f, 1.0f), color_overlay(0.0f, 0.0f, 0.0f), walkable(false), transparent(false),
tilesprite(-1), tile_overlay(-1), uisprite(-1)
{
}
UIEntity::UIEntity() {} // this will not work lol. TODO remove default constructor by finding the shared pointer inits that use it
UIEntity::UIEntity(UIGrid& grid)
: gridstate(grid.grid_x * grid.grid_y)
{
}
// UIGrid methods
UIGrid::UIGrid()
{
}
/*
UIGrid::UIGrid(int gx, int gy, std::shared_ptr<PyTexture> _ptex, float _x, float _y, float _w, float _h)
: grid_x(gx), grid_y(gy),
zoom(1.0f), center_x((gx/2) * _ptex->sheet_width), center_y((gy/2) * _ptex->sheet_height),
itex(_itex), points(gx * gy)
{
// set up blank list of entities
entities = std::make_shared<std::list<std::shared_ptr<UIEntity>>>();
box.setSize(sf::Vector2f(_w, _h));
box.setPosition(sf::Vector2f(_x, _y));
box.setFillColor(sf::Color(0,0,0,0));
renderTexture.create(_w, _h);
sprite.setTexture(_itex->texture);
output.setTextureRect(
sf::IntRect(0, 0,
box.getSize().x, box.getSize().y));
output.setPosition(box.getPosition());
// textures are upside-down inside renderTexture
output.setTexture(renderTexture.getTexture());
}
*/
UIGrid::UIGrid(int gx, int gy, std::shared_ptr<PyTexture> _ptex, sf::Vector2f _xy, sf::Vector2f _wh)
: grid_x(gx), grid_y(gy),
zoom(1.0f), center_x((gx/2) * _ptex->sprite_width), center_y((gy/2) * _ptex->sprite_height),
ptex(_ptex), points(gx * gy)
{
// set up blank list of entities
entities = std::make_shared<std::list<std::shared_ptr<UIEntity>>>();
box.setSize(_wh);
box.setPosition(_xy);
box.setFillColor(sf::Color(0,0,0,0));
//renderTexture.create(_wh.x, _wh.y);
// create renderTexture with maximum theoretical size; sprite can resize to show whatever amount needs to be rendered
renderTexture.create(1920, 1080); // TODO - renderTexture should be window size; above 1080p this will cause rendering errors
//sprite.setTexture(_itex->texture);
sprite = ptex->sprite(0);
output.setTextureRect(
sf::IntRect(0, 0,
box.getSize().x, box.getSize().y));
output.setPosition(box.getPosition());
// textures are upside-down inside renderTexture
output.setTexture(renderTexture.getTexture());
}
void UIGrid::update()
{
}
/*
void UIGrid::setSprite(int ti)
{
//int tx = ti % itex->grid_width, ty = ti / itex->grid_width;
// sprite.setTextureRect(sf::IntRect(tx * itex->grid_size, ty * itex->grid_size, itex->grid_size, itex->grid_size));
sprite = ptex->sprite(ti);
}
*/
void UIGrid::render(sf::Vector2f)
{
output.setPosition(box.getPosition()); // output sprite can move; update position when drawing
// output size can change; update size when drawing
output.setTextureRect(
sf::IntRect(0, 0,
box.getSize().x, box.getSize().y));
renderTexture.clear(sf::Color(8, 8, 8, 255)); // TODO - UIGrid needs a "background color" field
// sprites that are visible according to zoom, center_x, center_y, and box width
float center_x_sq = center_x / ptex->sprite_width;
float center_y_sq = center_y / ptex->sprite_height;
float width_sq = box.getSize().x / (ptex->sprite_width * zoom);
float height_sq = box.getSize().y / (ptex->sprite_height * zoom);
float left_edge = center_x_sq - (width_sq / 2.0);
float top_edge = center_y_sq - (height_sq / 2.0);
int left_spritepixels = center_x - (box.getSize().x / 2.0 / zoom);
int top_spritepixels = center_y - (box.getSize().y / 2.0 / zoom);
//sprite.setScale(sf::Vector2f(zoom, zoom));
sf::RectangleShape r; // for colors and overlays
r.setSize(sf::Vector2f(ptex->sprite_width * zoom, ptex->sprite_height * zoom));
r.setOutlineThickness(0);
int x_limit = left_edge + width_sq + 2;
if (x_limit > grid_x) x_limit = grid_x;
int y_limit = top_edge + height_sq + 2;
if (y_limit > grid_y) y_limit = grid_y;
// base layer - bottom color, tile sprite ("ground")
for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0);
x < x_limit; //x < view_width;
x+=1)
{
//for (float y = (top_edge >= 0 ? top_edge : 0);
for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0);
y < y_limit; //y < view_height;
y+=1)
{
auto pixel_pos = sf::Vector2f(
(x*ptex->sprite_width - left_spritepixels) * zoom,
(y*ptex->sprite_height - top_spritepixels) * zoom );
auto gridpoint = at(std::floor(x), std::floor(y));
//sprite.setPosition(pixel_pos);
r.setPosition(pixel_pos);
r.setFillColor(gridpoint.color);
renderTexture.draw(r);
// tilesprite
// if discovered but not visible, set opacity to 90%
// if not discovered... just don't draw it?
if (gridpoint.tilesprite != -1) {
sprite = ptex->sprite(gridpoint.tilesprite, pixel_pos, sf::Vector2f(zoom, zoom)); //setSprite(gridpoint.tilesprite);;
renderTexture.draw(sprite);
}
}
}
// middle layer - entities
// disabling entity rendering until I can render their UISprite inside the rendertexture (not directly to window)
for (auto e : *entities) {
// TODO skip out-of-bounds entities (grid square not visible at all, check for partially on visible grid squares / floating point grid position)
//auto drawent = e->cGrid->indexsprite.drawable();
auto& drawent = e->sprite;
//drawent.setScale(zoom, zoom);
drawent.setScale(sf::Vector2f(zoom, zoom));
auto pixel_pos = sf::Vector2f(
(e->position.x*ptex->sprite_width - left_spritepixels) * zoom,
(e->position.y*ptex->sprite_height - top_spritepixels) * zoom );
//drawent.setPosition(pixel_pos);
//renderTexture.draw(drawent);
drawent.render(pixel_pos, renderTexture);
}
// top layer - opacity for discovered / visible status (debug, basically)
/* // Disabled until I attach a "perspective"
for (int x = (left_edge - 1 >= 0 ? left_edge - 1 : 0);
x < x_limit; //x < view_width;
x+=1)
{
//for (float y = (top_edge >= 0 ? top_edge : 0);
for (int y = (top_edge - 1 >= 0 ? top_edge - 1 : 0);
y < y_limit; //y < view_height;
y+=1)
{
auto pixel_pos = sf::Vector2f(
(x*itex->grid_size - left_spritepixels) * zoom,
(y*itex->grid_size - top_spritepixels) * zoom );
auto gridpoint = at(std::floor(x), std::floor(y));
sprite.setPosition(pixel_pos);
r.setPosition(pixel_pos);
// visible & discovered layers for testing purposes
if (!gridpoint.discovered) {
r.setFillColor(sf::Color(16, 16, 20, 192)); // 255 opacity for actual blackout
renderTexture.draw(r);
} else if (!gridpoint.visible) {
r.setFillColor(sf::Color(32, 32, 40, 128));
renderTexture.draw(r);
}
// overlay
// uisprite
}
}
*/
// grid lines for testing & validation
/*
sf::Vertex line[] =
{
sf::Vertex(sf::Vector2f(0, 0), sf::Color::Red),
sf::Vertex(box.getSize(), sf::Color::Red),
};
renderTexture.draw(line, 2, sf::Lines);
sf::Vertex lineb[] =
{
sf::Vertex(sf::Vector2f(0, box.getSize().y), sf::Color::Blue),
sf::Vertex(sf::Vector2f(box.getSize().x, 0), sf::Color::Blue),
};
renderTexture.draw(lineb, 2, sf::Lines);
*/
// render to window
renderTexture.display();
Resources::game->getWindow().draw(output);
}
UIGridPoint& UIGrid::at(int x, int y)
{
return points[y * grid_x + x];
}
PyObjectsEnum UIGrid::derived_type()
{
return PyObjectsEnum::UIGRID;
}
std::shared_ptr<PyTexture> UIGrid::getTexture()
{
return ptex;
}

2459
src/UI.h

File diff suppressed because it is too large Load Diff

View File

@ -1,32 +0,0 @@
#pragma once
class UIEntity;
typedef struct {
PyObject_HEAD
std::shared_ptr<UIEntity> data;
} PyUIEntityObject;
class UIFrame;
typedef struct {
PyObject_HEAD
std::shared_ptr<UIFrame> data;
} PyUIFrameObject;
class UICaption;
typedef struct {
PyObject_HEAD
std::shared_ptr<UICaption> data;
PyObject* font;
} PyUICaptionObject;
class UIGrid;
typedef struct {
PyObject_HEAD
std::shared_ptr<UIGrid> data;
} PyUIGridObject;
class UISprite;
typedef struct {
PyObject_HEAD
std::shared_ptr<UISprite> data;
} PyUISpriteObject;

View File

@ -1,475 +0,0 @@
#include "UICaption.h"
#include "GameEngine.h"
#include "PyColor.h"
#include "PyVector.h"
#include "PyFont.h"
#include <algorithm>
UIDrawable* UICaption::click_at(sf::Vector2f point)
{
if (click_callable)
{
if (text.getGlobalBounds().contains(point)) return this;
}
return NULL;
}
void UICaption::render(sf::Vector2f offset, sf::RenderTarget& target)
{
text.move(offset);
//Resources::game->getWindow().draw(text);
target.draw(text);
text.move(-offset);
}
PyObjectsEnum UICaption::derived_type()
{
return PyObjectsEnum::UICAPTION;
}
PyObject* UICaption::get_float_member(PyUICaptionObject* self, void* closure)
{
auto member_ptr = reinterpret_cast<long>(closure);
if (member_ptr == 0)
return PyFloat_FromDouble(self->data->text.getPosition().x);
else if (member_ptr == 1)
return PyFloat_FromDouble(self->data->text.getPosition().y);
else if (member_ptr == 4)
return PyFloat_FromDouble(self->data->text.getOutlineThickness());
else if (member_ptr == 5)
return PyLong_FromLong(self->data->text.getCharacterSize());
else
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return nullptr;
}
}
int UICaption::set_float_member(PyUICaptionObject* self, PyObject* value, void* closure)
{
float val;
auto member_ptr = reinterpret_cast<long>(closure);
if (PyFloat_Check(value))
{
val = PyFloat_AsDouble(value);
}
else if (PyLong_Check(value))
{
val = PyLong_AsLong(value);
}
else
{
PyErr_SetString(PyExc_TypeError, "Value must be an integer.");
return -1;
}
if (member_ptr == 0) //x
self->data->text.setPosition(val, self->data->text.getPosition().y);
else if (member_ptr == 1) //y
self->data->text.setPosition(self->data->text.getPosition().x, val);
else if (member_ptr == 4) //outline
self->data->text.setOutlineThickness(val);
else if (member_ptr == 5) // character size
self->data->text.setCharacterSize(val);
return 0;
}
PyObject* UICaption::get_vec_member(PyUICaptionObject* self, void* closure)
{
return PyVector(self->data->text.getPosition()).pyObject();
}
int UICaption::set_vec_member(PyUICaptionObject* self, PyObject* value, void* closure)
{
self->data->text.setPosition(PyVector::fromPy(value));
return 0;
}
PyObject* UICaption::get_color_member(PyUICaptionObject* self, void* closure)
{
// TODO: migrate this code to a switch statement - validate closure & return values in one tighter, more extensible structure
// validate closure (should be impossible to be wrong, but it's thorough)
auto member_ptr = reinterpret_cast<long>(closure);
if (member_ptr != 0 && member_ptr != 1)
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return nullptr;
}
// TODO: manually calling tp_alloc to create a PyColorObject seems like an antipattern
// fetch correct member data
sf::Color color;
if (member_ptr == 0)
{
color = self->data->text.getFillColor();
}
else if (member_ptr == 1)
{
color = self->data->text.getOutlineColor();
}
return PyColor(color).pyObject();
}
int UICaption::set_color_member(PyUICaptionObject* self, PyObject* value, void* closure)
{
auto member_ptr = reinterpret_cast<long>(closure);
//TODO: this logic of (PyColor instance OR tuple -> sf::color) should be encapsulated for reuse
int r, g, b, a;
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color") /*(PyObject*)&mcrfpydef::PyColorType)*/))
{
// get value from mcrfpy.Color instance
auto c = ((PyColorObject*)value)->data;
r = c.r; g = c.g; b = c.b; a = c.a;
std::cout << "got " << int(r) << ", " << int(g) << ", " << int(b) << ", " << int(a) << std::endl;
}
else if (!PyTuple_Check(value) || PyTuple_Size(value) < 3 || PyTuple_Size(value) > 4)
{
// reject non-Color, non-tuple value
PyErr_SetString(PyExc_TypeError, "Value must be a tuple of 3 or 4 integers or an mcrfpy.Color object.");
return -1;
}
else // get value from tuples
{
r = PyLong_AsLong(PyTuple_GetItem(value, 0));
g = PyLong_AsLong(PyTuple_GetItem(value, 1));
b = PyLong_AsLong(PyTuple_GetItem(value, 2));
a = 255;
if (PyTuple_Size(value) == 4)
{
a = PyLong_AsLong(PyTuple_GetItem(value, 3));
}
}
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || a < 0 || a > 255)
{
PyErr_SetString(PyExc_ValueError, "Color values must be between 0 and 255.");
return -1;
}
if (member_ptr == 0)
{
self->data->text.setFillColor(sf::Color(r, g, b, a));
}
else if (member_ptr == 1)
{
self->data->text.setOutlineColor(sf::Color(r, g, b, a));
}
else
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return -1;
}
return 0;
}
//TODO: evaluate use of Resources::caption_buffer... can't I do this with a std::string?
PyObject* UICaption::get_text(PyUICaptionObject* self, void* closure)
{
Resources::caption_buffer = self->data->text.getString();
return PyUnicode_FromString(Resources::caption_buffer.c_str());
}
int UICaption::set_text(PyUICaptionObject* self, PyObject* value, void* closure)
{
PyObject* s = PyObject_Str(value);
PyObject * temp_bytes = PyUnicode_AsEncodedString(s, "UTF-8", "strict"); // Owned reference
if (temp_bytes != NULL) {
Resources::caption_buffer = PyBytes_AS_STRING(temp_bytes); // Borrowed pointer
Py_DECREF(temp_bytes);
}
self->data->text.setString(Resources::caption_buffer);
return 0;
}
PyGetSetDef UICaption::getsetters[] = {
{"x", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "X coordinate of top-left corner", (void*)0},
{"y", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Y coordinate of top-left corner", (void*)1},
{"pos", (getter)UICaption::get_vec_member, (setter)UICaption::set_vec_member, "(x, y) vector", (void*)0},
//{"w", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "width of the rectangle", (void*)2},
//{"h", (getter)PyUIFrame_get_float_member, (setter)PyUIFrame_set_float_member, "height of the rectangle", (void*)3},
{"outline", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Thickness of the border", (void*)4},
{"fill_color", (getter)UICaption::get_color_member, (setter)UICaption::set_color_member, "Fill color of the text", (void*)0},
{"outline_color", (getter)UICaption::get_color_member, (setter)UICaption::set_color_member, "Outline color of the text", (void*)1},
//{"children", (getter)PyUIFrame_get_children, NULL, "UICollection of objects on top of this one", NULL},
{"text", (getter)UICaption::get_text, (setter)UICaption::set_text, "The text displayed", NULL},
{"font_size", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Font size (integer) in points", (void*)5},
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UICAPTION},
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UICAPTION},
{NULL}
};
PyObject* UICaption::repr(PyUICaptionObject* self)
{
std::ostringstream ss;
if (!self->data) ss << "<Caption (invalid internal object)>";
else {
auto text = self->data->text;
auto fc = text.getFillColor();
auto oc = text.getOutlineColor();
ss << "<Caption (x=" << text.getPosition().x << ", y=" << text.getPosition().y << ", " <<
"text='" << (std::string)text.getString() << "', " <<
"outline=" << text.getOutlineThickness() << ", " <<
"fill_color=(" << (int)fc.r << ", " << (int)fc.g << ", " << (int)fc.b << ", " << (int)fc.a <<"), " <<
"outline_color=(" << (int)oc.r << ", " << (int)oc.g << ", " << (int)oc.b << ", " << (int)oc.a <<"), " <<
")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
{
using namespace mcrfpydef;
// Constructor switch to Vector position
//static const char* keywords[] = { "x", "y", "text", "font", "fill_color", "outline_color", "outline", nullptr };
//float x = 0.0f, y = 0.0f, outline = 0.0f;
static const char* keywords[] = { "pos", "text", "font", "fill_color", "outline_color", "outline", nullptr };
PyObject* pos;
float outline = 0.0f;
char* text;
PyObject* font=NULL, *fill_color=NULL, *outline_color=NULL;
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf",
// const_cast<char**>(keywords), &x, &y, &text, &font, &fill_color, &outline_color, &outline))
if (!PyArg_ParseTupleAndKeywords(args, kwds, "Oz|OOOf",
const_cast<char**>(keywords), &pos, &text, &font, &fill_color, &outline_color, &outline))
{
return -1;
}
PyVectorObject* pos_result = PyVector::from_arg(pos);
if (!pos_result)
{
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
return -1;
}
self->data->text.setPosition(pos_result->data);
// check types for font, fill_color, outline_color
//std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl;
if (font != NULL && font != Py_None && !PyObject_IsInstance(font, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Font")/*(PyObject*)&PyFontType)*/)){
PyErr_SetString(PyExc_TypeError, "font must be a mcrfpy.Font instance or None");
return -1;
} else if (font != NULL && font != Py_None)
{
auto font_obj = (PyFontObject*)font;
self->data->text.setFont(font_obj->data->font);
self->font = font;
Py_INCREF(font);
} else
{
// Use default font when None or not provided
if (McRFPy_API::default_font) {
self->data->text.setFont(McRFPy_API::default_font->font);
// Store reference to default font
PyObject* default_font_obj = PyObject_GetAttrString(McRFPy_API::mcrf_module, "default_font");
if (default_font_obj) {
self->font = default_font_obj;
// Don't need to DECREF since we're storing it
}
}
}
self->data->text.setString((std::string)text);
self->data->text.setOutlineThickness(outline);
if (fill_color) {
auto fc = PyColor::from_arg(fill_color);
if (!fc) {
PyErr_SetString(PyExc_TypeError, "fill_color must be mcrfpy.Color or arguments to mcrfpy.Color.__init__");
return -1;
}
self->data->text.setFillColor(PyColor::fromPy(fc));
//Py_DECREF(fc);
} else {
self->data->text.setFillColor(sf::Color(0,0,0,255));
}
if (outline_color) {
auto oc = PyColor::from_arg(outline_color);
if (!oc) {
PyErr_SetString(PyExc_TypeError, "outline_color must be mcrfpy.Color or arguments to mcrfpy.Color.__init__");
return -1;
}
self->data->text.setOutlineColor(PyColor::fromPy(oc));
//Py_DECREF(oc);
} else {
self->data->text.setOutlineColor(sf::Color(128,128,128,255));
}
return 0;
}
// Property system implementation for animations
bool UICaption::setProperty(const std::string& name, float value) {
if (name == "x") {
text.setPosition(sf::Vector2f(value, text.getPosition().y));
return true;
}
else if (name == "y") {
text.setPosition(sf::Vector2f(text.getPosition().x, value));
return true;
}
else if (name == "font_size" || name == "size") { // Support both for backward compatibility
text.setCharacterSize(static_cast<unsigned int>(value));
return true;
}
else if (name == "outline") {
text.setOutlineThickness(value);
return true;
}
else if (name == "fill_color.r") {
auto color = text.getFillColor();
color.r = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
text.setFillColor(color);
return true;
}
else if (name == "fill_color.g") {
auto color = text.getFillColor();
color.g = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
text.setFillColor(color);
return true;
}
else if (name == "fill_color.b") {
auto color = text.getFillColor();
color.b = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
text.setFillColor(color);
return true;
}
else if (name == "fill_color.a") {
auto color = text.getFillColor();
color.a = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
text.setFillColor(color);
return true;
}
else if (name == "outline_color.r") {
auto color = text.getOutlineColor();
color.r = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
text.setOutlineColor(color);
return true;
}
else if (name == "outline_color.g") {
auto color = text.getOutlineColor();
color.g = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
text.setOutlineColor(color);
return true;
}
else if (name == "outline_color.b") {
auto color = text.getOutlineColor();
color.b = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
text.setOutlineColor(color);
return true;
}
else if (name == "outline_color.a") {
auto color = text.getOutlineColor();
color.a = static_cast<sf::Uint8>(std::clamp(value, 0.0f, 255.0f));
text.setOutlineColor(color);
return true;
}
else if (name == "z_index") {
z_index = static_cast<int>(value);
return true;
}
return false;
}
bool UICaption::setProperty(const std::string& name, const sf::Color& value) {
if (name == "fill_color") {
text.setFillColor(value);
return true;
}
else if (name == "outline_color") {
text.setOutlineColor(value);
return true;
}
return false;
}
bool UICaption::setProperty(const std::string& name, const std::string& value) {
if (name == "text") {
text.setString(value);
return true;
}
return false;
}
bool UICaption::getProperty(const std::string& name, float& value) const {
if (name == "x") {
value = text.getPosition().x;
return true;
}
else if (name == "y") {
value = text.getPosition().y;
return true;
}
else if (name == "font_size" || name == "size") { // Support both for backward compatibility
value = static_cast<float>(text.getCharacterSize());
return true;
}
else if (name == "outline") {
value = text.getOutlineThickness();
return true;
}
else if (name == "fill_color.r") {
value = text.getFillColor().r;
return true;
}
else if (name == "fill_color.g") {
value = text.getFillColor().g;
return true;
}
else if (name == "fill_color.b") {
value = text.getFillColor().b;
return true;
}
else if (name == "fill_color.a") {
value = text.getFillColor().a;
return true;
}
else if (name == "outline_color.r") {
value = text.getOutlineColor().r;
return true;
}
else if (name == "outline_color.g") {
value = text.getOutlineColor().g;
return true;
}
else if (name == "outline_color.b") {
value = text.getOutlineColor().b;
return true;
}
else if (name == "outline_color.a") {
value = text.getOutlineColor().a;
return true;
}
else if (name == "z_index") {
value = static_cast<float>(z_index);
return true;
}
return false;
}
bool UICaption::getProperty(const std::string& name, sf::Color& value) const {
if (name == "fill_color") {
value = text.getFillColor();
return true;
}
else if (name == "outline_color") {
value = text.getOutlineColor();
return true;
}
return false;
}
bool UICaption::getProperty(const std::string& name, std::string& value) const {
if (name == "text") {
value = text.getString();
return true;
}
return false;
}

View File

@ -1,72 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "UIDrawable.h"
class UICaption: public UIDrawable
{
public:
sf::Text text;
void render(sf::Vector2f, sf::RenderTarget&) override final;
PyObjectsEnum derived_type() override final;
virtual UIDrawable* click_at(sf::Vector2f point) override final;
// Property system for animations
bool setProperty(const std::string& name, float value) override;
bool setProperty(const std::string& name, const sf::Color& value) override;
bool setProperty(const std::string& name, const std::string& value) override;
bool getProperty(const std::string& name, float& value) const override;
bool getProperty(const std::string& name, sf::Color& value) const override;
bool getProperty(const std::string& name, std::string& value) const override;
static PyObject* get_float_member(PyUICaptionObject* self, void* closure);
static int set_float_member(PyUICaptionObject* self, PyObject* value, void* closure);
static PyObject* get_vec_member(PyUICaptionObject* self, void* closure);
static int set_vec_member(PyUICaptionObject* self, PyObject* value, void* closure);
static PyObject* get_color_member(PyUICaptionObject* self, void* closure);
static int set_color_member(PyUICaptionObject* self, PyObject* value, void* closure);
static PyObject* get_text(PyUICaptionObject* self, void* closure);
static int set_text(PyUICaptionObject* self, PyObject* value, void* closure);
static PyGetSetDef getsetters[];
static PyObject* repr(PyUICaptionObject* self);
static int init(PyUICaptionObject* self, PyObject* args, PyObject* kwds);
};
namespace mcrfpydef {
static PyTypeObject PyUICaptionType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Caption",
.tp_basicsize = sizeof(PyUICaptionObject),
.tp_itemsize = 0,
// TODO - move tp_dealloc to .cpp file as static function (UICaption::dealloc)
.tp_dealloc = (destructor)[](PyObject* self)
{
PyUICaptionObject* obj = (PyUICaptionObject*)self;
// TODO - reevaluate with PyFont usage; UICaption does not own the font
// release reference to font object
if (obj->font) Py_DECREF(obj->font);
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)UICaption::repr,
//.tp_hash = NULL,
//.tp_iter
//.tp_iternext
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("docstring"),
//.tp_methods = PyUIFrame_methods,
//.tp_members = PyUIFrame_members,
.tp_getset = UICaption::getsetters,
//.tp_base = NULL,
.tp_init = (initproc)UICaption::init,
// TODO - move tp_new to .cpp file as a static function (UICaption::new)
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
PyUICaptionObject* self = (PyUICaptionObject*)type->tp_alloc(type, 0);
if (self) self->data = std::make_shared<UICaption>();
return (PyObject*)self;
}
};
}

View File

@ -1,906 +0,0 @@
#include "UICollection.h"
#include "UIFrame.h"
#include "UICaption.h"
#include "UISprite.h"
#include "UIGrid.h"
#include "McRFPy_API.h"
#include "PyObjectUtils.h"
#include <climits>
#include <algorithm>
using namespace mcrfpydef;
// Local helper function to convert UIDrawable to appropriate Python object
static PyObject* convertDrawableToPython(std::shared_ptr<UIDrawable> drawable) {
if (!drawable) {
Py_RETURN_NONE;
}
PyTypeObject* type = nullptr;
PyObject* obj = nullptr;
switch (drawable->derived_type()) {
case PyObjectsEnum::UIFRAME:
{
type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame");
if (!type) return nullptr;
auto pyObj = (PyUIFrameObject*)type->tp_alloc(type, 0);
if (pyObj) {
pyObj->data = std::static_pointer_cast<UIFrame>(drawable);
}
obj = (PyObject*)pyObj;
break;
}
case PyObjectsEnum::UICAPTION:
{
type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption");
if (!type) return nullptr;
auto pyObj = (PyUICaptionObject*)type->tp_alloc(type, 0);
if (pyObj) {
pyObj->data = std::static_pointer_cast<UICaption>(drawable);
pyObj->font = nullptr;
}
obj = (PyObject*)pyObj;
break;
}
case PyObjectsEnum::UISPRITE:
{
type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite");
if (!type) return nullptr;
auto pyObj = (PyUISpriteObject*)type->tp_alloc(type, 0);
if (pyObj) {
pyObj->data = std::static_pointer_cast<UISprite>(drawable);
}
obj = (PyObject*)pyObj;
break;
}
case PyObjectsEnum::UIGRID:
{
type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid");
if (!type) return nullptr;
auto pyObj = (PyUIGridObject*)type->tp_alloc(type, 0);
if (pyObj) {
pyObj->data = std::static_pointer_cast<UIGrid>(drawable);
}
obj = (PyObject*)pyObj;
break;
}
default:
PyErr_SetString(PyExc_TypeError, "Unknown UIDrawable derived type");
return nullptr;
}
if (type) {
Py_DECREF(type);
}
return obj;
}
int UICollectionIter::init(PyUICollectionIterObject* self, PyObject* args, PyObject* kwds)
{
PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required.");
return -1;
}
PyObject* UICollectionIter::next(PyUICollectionIterObject* self)
{
// Check if self and self->data are valid
if (!self || !self->data) {
PyErr_SetString(PyExc_RuntimeError, "Iterator object or data is null");
return NULL;
}
if (self->data->size() != self->start_size)
{
PyErr_SetString(PyExc_RuntimeError, "collection changed size during iteration");
return NULL;
}
if (self->index > self->start_size - 1)
{
PyErr_SetNone(PyExc_StopIteration);
return NULL;
}
self->index++;
auto vec = self->data.get();
if (!vec)
{
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
return NULL;
}
auto target = (*vec)[self->index-1];
// Return the proper Python object for this UIDrawable
return convertDrawableToPython(target);
}
PyObject* UICollectionIter::repr(PyUICollectionIterObject* self)
{
std::ostringstream ss;
if (!self->data) ss << "<UICollectionIter (invalid internal object)>";
else {
ss << "<UICollectionIter (" << self->data->size() << " child objects, @ index " << self->index << ")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
Py_ssize_t UICollection::len(PyUICollectionObject* self) {
return self->data->size();
}
PyObject* UICollection::getitem(PyUICollectionObject* self, Py_ssize_t index) {
// build a Python version of item at self->data[index]
// Copy pasted::
auto vec = self->data.get();
if (!vec)
{
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
return NULL;
}
while (index < 0) index += self->data->size();
if (index > self->data->size() - 1)
{
PyErr_SetString(PyExc_IndexError, "UICollection index out of range");
return NULL;
}
auto target = (*vec)[index];
return convertDrawableToPython(target);
}
int UICollection::setitem(PyUICollectionObject* self, Py_ssize_t index, PyObject* value) {
auto vec = self->data.get();
if (!vec) {
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
return -1;
}
// Handle negative indexing
while (index < 0) index += self->data->size();
// Bounds check
if (index >= self->data->size()) {
PyErr_SetString(PyExc_IndexError, "UICollection assignment index out of range");
return -1;
}
// Handle deletion
if (value == NULL) {
self->data->erase(self->data->begin() + index);
return 0;
}
// Type checking - must be a UIDrawable subclass
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
PyErr_SetString(PyExc_TypeError, "UICollection can only contain Frame, Caption, Sprite, and Grid objects");
return -1;
}
// Get the C++ object from the Python object
std::shared_ptr<UIDrawable> new_drawable = nullptr;
int old_z_index = (*vec)[index]->z_index; // Preserve the z_index
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
PyUIFrameObject* frame = (PyUIFrameObject*)value;
new_drawable = frame->data;
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
PyUICaptionObject* caption = (PyUICaptionObject*)value;
new_drawable = caption->data;
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
PyUISpriteObject* sprite = (PyUISpriteObject*)value;
new_drawable = sprite->data;
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
PyUIGridObject* grid = (PyUIGridObject*)value;
new_drawable = grid->data;
}
if (!new_drawable) {
PyErr_SetString(PyExc_RuntimeError, "Failed to extract C++ object from Python object");
return -1;
}
// Preserve the z_index of the replaced element
new_drawable->z_index = old_z_index;
// Replace the element
(*vec)[index] = new_drawable;
// Mark scene as needing resort after replacing element
McRFPy_API::markSceneNeedsSort();
return 0;
}
int UICollection::contains(PyUICollectionObject* self, PyObject* value) {
auto vec = self->data.get();
if (!vec) {
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
return -1;
}
// Type checking - must be a UIDrawable subclass
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
// Not a valid type, so it can't be in the collection
return 0;
}
// Get the C++ object from the Python object
std::shared_ptr<UIDrawable> search_drawable = nullptr;
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
PyUIFrameObject* frame = (PyUIFrameObject*)value;
search_drawable = frame->data;
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
PyUICaptionObject* caption = (PyUICaptionObject*)value;
search_drawable = caption->data;
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
PyUISpriteObject* sprite = (PyUISpriteObject*)value;
search_drawable = sprite->data;
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
PyUIGridObject* grid = (PyUIGridObject*)value;
search_drawable = grid->data;
}
if (!search_drawable) {
return 0;
}
// Search for the object by comparing C++ pointers
for (const auto& drawable : *vec) {
if (drawable.get() == search_drawable.get()) {
return 1; // Found
}
}
return 0; // Not found
}
PyObject* UICollection::concat(PyUICollectionObject* self, PyObject* other) {
// Create a new Python list containing elements from both collections
if (!PySequence_Check(other)) {
PyErr_SetString(PyExc_TypeError, "can only concatenate sequence to UICollection");
return NULL;
}
Py_ssize_t self_len = self->data->size();
Py_ssize_t other_len = PySequence_Length(other);
if (other_len == -1) {
return NULL; // Error already set
}
PyObject* result_list = PyList_New(self_len + other_len);
if (!result_list) {
return NULL;
}
// Add all elements from self
for (Py_ssize_t i = 0; i < self_len; i++) {
PyObject* item = convertDrawableToPython((*self->data)[i]);
if (!item) {
Py_DECREF(result_list);
return NULL;
}
PyList_SET_ITEM(result_list, i, item); // Steals reference
}
// Add all elements from other
for (Py_ssize_t i = 0; i < other_len; i++) {
PyObject* item = PySequence_GetItem(other, i);
if (!item) {
Py_DECREF(result_list);
return NULL;
}
PyList_SET_ITEM(result_list, self_len + i, item); // Steals reference
}
return result_list;
}
PyObject* UICollection::inplace_concat(PyUICollectionObject* self, PyObject* other) {
if (!PySequence_Check(other)) {
PyErr_SetString(PyExc_TypeError, "can only concatenate sequence to UICollection");
return NULL;
}
// First, validate ALL items in the sequence before modifying anything
Py_ssize_t other_len = PySequence_Length(other);
if (other_len == -1) {
return NULL; // Error already set
}
// Validate all items first
for (Py_ssize_t i = 0; i < other_len; i++) {
PyObject* item = PySequence_GetItem(other, i);
if (!item) {
return NULL;
}
// Type check
if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
Py_DECREF(item);
PyErr_Format(PyExc_TypeError,
"UICollection can only contain Frame, Caption, Sprite, and Grid objects; "
"got %s at index %zd", Py_TYPE(item)->tp_name, i);
return NULL;
}
Py_DECREF(item);
}
// All items validated, now we can safely add them
for (Py_ssize_t i = 0; i < other_len; i++) {
PyObject* item = PySequence_GetItem(other, i);
if (!item) {
return NULL; // Shouldn't happen, but be safe
}
// Use the existing append method which handles z_index assignment
PyObject* result = append(self, item);
Py_DECREF(item);
if (!result) {
return NULL; // append() failed
}
Py_DECREF(result); // append returns Py_None
}
Py_INCREF(self);
return (PyObject*)self;
}
PyObject* UICollection::subscript(PyUICollectionObject* self, PyObject* key) {
if (PyLong_Check(key)) {
// Single index - delegate to sq_item
Py_ssize_t index = PyLong_AsSsize_t(key);
if (index == -1 && PyErr_Occurred()) {
return NULL;
}
return getitem(self, index);
} else if (PySlice_Check(key)) {
// Handle slice
Py_ssize_t start, stop, step, slicelength;
if (PySlice_GetIndicesEx(key, self->data->size(), &start, &stop, &step, &slicelength) < 0) {
return NULL;
}
PyObject* result_list = PyList_New(slicelength);
if (!result_list) {
return NULL;
}
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
PyObject* item = convertDrawableToPython((*self->data)[cur]);
if (!item) {
Py_DECREF(result_list);
return NULL;
}
PyList_SET_ITEM(result_list, i, item); // Steals reference
}
return result_list;
} else {
PyErr_Format(PyExc_TypeError, "UICollection indices must be integers or slices, not %.200s",
Py_TYPE(key)->tp_name);
return NULL;
}
}
int UICollection::ass_subscript(PyUICollectionObject* self, PyObject* key, PyObject* value) {
if (PyLong_Check(key)) {
// Single index - delegate to sq_ass_item
Py_ssize_t index = PyLong_AsSsize_t(key);
if (index == -1 && PyErr_Occurred()) {
return -1;
}
return setitem(self, index, value);
} else if (PySlice_Check(key)) {
// Handle slice assignment/deletion
Py_ssize_t start, stop, step, slicelength;
if (PySlice_GetIndicesEx(key, self->data->size(), &start, &stop, &step, &slicelength) < 0) {
return -1;
}
if (value == NULL) {
// Deletion
if (step != 1) {
// For non-contiguous slices, delete from highest to lowest to maintain indices
std::vector<Py_ssize_t> indices;
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
indices.push_back(cur);
}
// Sort in descending order and delete
std::sort(indices.begin(), indices.end(), std::greater<Py_ssize_t>());
for (Py_ssize_t idx : indices) {
self->data->erase(self->data->begin() + idx);
}
} else {
// Contiguous slice - can delete in one go
self->data->erase(self->data->begin() + start, self->data->begin() + stop);
}
// Mark scene as needing resort after slice deletion
McRFPy_API::markSceneNeedsSort();
return 0;
} else {
// Assignment
if (!PySequence_Check(value)) {
PyErr_SetString(PyExc_TypeError, "can only assign sequence to slice");
return -1;
}
Py_ssize_t value_len = PySequence_Length(value);
if (value_len == -1) {
return -1;
}
// Validate all items first
std::vector<std::shared_ptr<UIDrawable>> new_items;
for (Py_ssize_t i = 0; i < value_len; i++) {
PyObject* item = PySequence_GetItem(value, i);
if (!item) {
return -1;
}
// Type check and extract C++ object
std::shared_ptr<UIDrawable> drawable = nullptr;
if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
drawable = ((PyUIFrameObject*)item)->data;
} else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
drawable = ((PyUICaptionObject*)item)->data;
} else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
drawable = ((PyUISpriteObject*)item)->data;
} else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
drawable = ((PyUIGridObject*)item)->data;
} else {
Py_DECREF(item);
PyErr_Format(PyExc_TypeError,
"UICollection can only contain Frame, Caption, Sprite, and Grid objects; "
"got %s at index %zd", Py_TYPE(item)->tp_name, i);
return -1;
}
Py_DECREF(item);
new_items.push_back(drawable);
}
// Now perform the assignment
if (step == 1) {
// Contiguous slice
if (slicelength != value_len) {
// Need to resize
auto it_start = self->data->begin() + start;
auto it_stop = self->data->begin() + stop;
self->data->erase(it_start, it_stop);
self->data->insert(self->data->begin() + start, new_items.begin(), new_items.end());
} else {
// Same size, just replace
for (Py_ssize_t i = 0; i < slicelength; i++) {
// Preserve z_index
new_items[i]->z_index = (*self->data)[start + i]->z_index;
(*self->data)[start + i] = new_items[i];
}
}
} else {
// Extended slice
if (slicelength != value_len) {
PyErr_Format(PyExc_ValueError,
"attempt to assign sequence of size %zd to extended slice of size %zd",
value_len, slicelength);
return -1;
}
for (Py_ssize_t i = 0, cur = start; i < slicelength; i++, cur += step) {
// Preserve z_index
new_items[i]->z_index = (*self->data)[cur]->z_index;
(*self->data)[cur] = new_items[i];
}
}
// Mark scene as needing resort after slice assignment
McRFPy_API::markSceneNeedsSort();
return 0;
}
} else {
PyErr_Format(PyExc_TypeError, "UICollection indices must be integers or slices, not %.200s",
Py_TYPE(key)->tp_name);
return -1;
}
}
PyMappingMethods UICollection::mpmethods = {
.mp_length = (lenfunc)UICollection::len,
.mp_subscript = (binaryfunc)UICollection::subscript,
.mp_ass_subscript = (objobjargproc)UICollection::ass_subscript
};
PySequenceMethods UICollection::sqmethods = {
.sq_length = (lenfunc)UICollection::len,
.sq_concat = (binaryfunc)UICollection::concat,
.sq_repeat = NULL,
.sq_item = (ssizeargfunc)UICollection::getitem,
.was_sq_slice = NULL,
.sq_ass_item = (ssizeobjargproc)UICollection::setitem,
.was_sq_ass_slice = NULL,
.sq_contains = (objobjproc)UICollection::contains,
.sq_inplace_concat = (binaryfunc)UICollection::inplace_concat,
.sq_inplace_repeat = NULL
};
/* Idiomatic way to fetch complete types from the API rather than referencing their PyTypeObject struct
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture");
I never identified why `using namespace mcrfpydef;` doesn't solve the segfault issue.
The horrible macro in UIDrawable was originally a workaround for this, but as I interact with the types outside of the monster UI.h, a more general (and less icky) solution is required.
*/
PyObject* UICollection::append(PyUICollectionObject* self, PyObject* o)
{
// if not UIDrawable subclass, reject it
// self->data->push_back( c++ object inside o );
// Ensure module is initialized
if (!McRFPy_API::mcrf_module) {
PyErr_SetString(PyExc_RuntimeError, "mcrfpy module not initialized");
return NULL;
}
// this would be a great use case for .tp_base
if (!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
!PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))
)
{
PyErr_SetString(PyExc_TypeError, "Only Frame, Caption, Sprite, and Grid objects can be added to UICollection");
return NULL;
}
// Calculate z_index for the new element
int new_z_index = 0;
if (!self->data->empty()) {
// Get the z_index of the last element and add 10
int last_z = self->data->back()->z_index;
if (last_z <= INT_MAX - 10) {
new_z_index = last_z + 10;
} else {
new_z_index = INT_MAX;
}
}
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")))
{
PyUIFrameObject* frame = (PyUIFrameObject*)o;
frame->data->z_index = new_z_index;
self->data->push_back(frame->data);
}
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")))
{
PyUICaptionObject* caption = (PyUICaptionObject*)o;
caption->data->z_index = new_z_index;
self->data->push_back(caption->data);
}
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")))
{
PyUISpriteObject* sprite = (PyUISpriteObject*)o;
sprite->data->z_index = new_z_index;
self->data->push_back(sprite->data);
}
if (PyObject_IsInstance(o, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid")))
{
PyUIGridObject* grid = (PyUIGridObject*)o;
grid->data->z_index = new_z_index;
self->data->push_back(grid->data);
}
// Mark scene as needing resort after adding element
McRFPy_API::markSceneNeedsSort();
Py_INCREF(Py_None);
return Py_None;
}
PyObject* UICollection::extend(PyUICollectionObject* self, PyObject* iterable)
{
// Accept any iterable of UIDrawable objects
PyObject* iterator = PyObject_GetIter(iterable);
if (iterator == NULL) {
PyErr_SetString(PyExc_TypeError, "UICollection.extend requires an iterable");
return NULL;
}
// Ensure module is initialized
if (!McRFPy_API::mcrf_module) {
Py_DECREF(iterator);
PyErr_SetString(PyExc_RuntimeError, "mcrfpy module not initialized");
return NULL;
}
// Get current highest z_index
int current_z_index = 0;
if (!self->data->empty()) {
current_z_index = self->data->back()->z_index;
}
PyObject* item;
while ((item = PyIter_Next(iterator)) != NULL) {
// Check if item is a UIDrawable subclass
if (!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
!PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid")))
{
Py_DECREF(item);
Py_DECREF(iterator);
PyErr_SetString(PyExc_TypeError, "All items must be Frame, Caption, Sprite, or Grid objects");
return NULL;
}
// Increment z_index for each new element
if (current_z_index <= INT_MAX - 10) {
current_z_index += 10;
} else {
current_z_index = INT_MAX;
}
// Add the item based on its type
if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
PyUIFrameObject* frame = (PyUIFrameObject*)item;
frame->data->z_index = current_z_index;
self->data->push_back(frame->data);
}
else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
PyUICaptionObject* caption = (PyUICaptionObject*)item;
caption->data->z_index = current_z_index;
self->data->push_back(caption->data);
}
else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
PyUISpriteObject* sprite = (PyUISpriteObject*)item;
sprite->data->z_index = current_z_index;
self->data->push_back(sprite->data);
}
else if (PyObject_IsInstance(item, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
PyUIGridObject* grid = (PyUIGridObject*)item;
grid->data->z_index = current_z_index;
self->data->push_back(grid->data);
}
Py_DECREF(item);
}
Py_DECREF(iterator);
// Check if iteration ended due to an error
if (PyErr_Occurred()) {
return NULL;
}
// Mark scene as needing resort after adding elements
McRFPy_API::markSceneNeedsSort();
Py_INCREF(Py_None);
return Py_None;
}
PyObject* UICollection::remove(PyUICollectionObject* self, PyObject* o)
{
if (!PyLong_Check(o))
{
PyErr_SetString(PyExc_TypeError, "UICollection.remove requires an integer index to remove");
return NULL;
}
long index = PyLong_AsLong(o);
// Handle negative indexing
while (index < 0) index += self->data->size();
if (index >= self->data->size())
{
PyErr_SetString(PyExc_ValueError, "Index out of range");
return NULL;
}
// release the shared pointer at self->data[index];
self->data->erase(self->data->begin() + index);
// Mark scene as needing resort after removing element
McRFPy_API::markSceneNeedsSort();
Py_INCREF(Py_None);
return Py_None;
}
PyObject* UICollection::index_method(PyUICollectionObject* self, PyObject* value) {
auto vec = self->data.get();
if (!vec) {
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
return NULL;
}
// Type checking - must be a UIDrawable subclass
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
PyErr_SetString(PyExc_TypeError, "UICollection.index requires a Frame, Caption, Sprite, or Grid object");
return NULL;
}
// Get the C++ object from the Python object
std::shared_ptr<UIDrawable> search_drawable = nullptr;
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
search_drawable = ((PyUIFrameObject*)value)->data;
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
search_drawable = ((PyUICaptionObject*)value)->data;
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
search_drawable = ((PyUISpriteObject*)value)->data;
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
search_drawable = ((PyUIGridObject*)value)->data;
}
if (!search_drawable) {
PyErr_SetString(PyExc_RuntimeError, "Failed to extract C++ object from Python object");
return NULL;
}
// Search for the object
for (size_t i = 0; i < vec->size(); i++) {
if ((*vec)[i].get() == search_drawable.get()) {
return PyLong_FromSsize_t(i);
}
}
PyErr_SetString(PyExc_ValueError, "value not in UICollection");
return NULL;
}
PyObject* UICollection::count(PyUICollectionObject* self, PyObject* value) {
auto vec = self->data.get();
if (!vec) {
PyErr_SetString(PyExc_RuntimeError, "the collection store returned a null pointer");
return NULL;
}
// Type checking - must be a UIDrawable subclass
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame")) &&
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite")) &&
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption")) &&
!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
// Not a valid type, so count is 0
return PyLong_FromLong(0);
}
// Get the C++ object from the Python object
std::shared_ptr<UIDrawable> search_drawable = nullptr;
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Frame"))) {
search_drawable = ((PyUIFrameObject*)value)->data;
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Caption"))) {
search_drawable = ((PyUICaptionObject*)value)->data;
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Sprite"))) {
search_drawable = ((PyUISpriteObject*)value)->data;
} else if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
search_drawable = ((PyUIGridObject*)value)->data;
}
if (!search_drawable) {
return PyLong_FromLong(0);
}
// Count occurrences
Py_ssize_t count = 0;
for (const auto& drawable : *vec) {
if (drawable.get() == search_drawable.get()) {
count++;
}
}
return PyLong_FromSsize_t(count);
}
PyMethodDef UICollection::methods[] = {
{"append", (PyCFunction)UICollection::append, METH_O},
{"extend", (PyCFunction)UICollection::extend, METH_O},
{"remove", (PyCFunction)UICollection::remove, METH_O},
{"index", (PyCFunction)UICollection::index_method, METH_O},
{"count", (PyCFunction)UICollection::count, METH_O},
{NULL, NULL, 0, NULL}
};
PyObject* UICollection::repr(PyUICollectionObject* self)
{
std::ostringstream ss;
if (!self->data) ss << "<UICollection (invalid internal object)>";
else {
ss << "<UICollection (" << self->data->size() << " objects: ";
// Count each type
int frame_count = 0, caption_count = 0, sprite_count = 0, grid_count = 0, other_count = 0;
for (auto& item : *self->data) {
switch(item->derived_type()) {
case PyObjectsEnum::UIFRAME: frame_count++; break;
case PyObjectsEnum::UICAPTION: caption_count++; break;
case PyObjectsEnum::UISPRITE: sprite_count++; break;
case PyObjectsEnum::UIGRID: grid_count++; break;
default: other_count++; break;
}
}
// Build type summary
bool first = true;
if (frame_count > 0) {
ss << frame_count << " Frame" << (frame_count > 1 ? "s" : "");
first = false;
}
if (caption_count > 0) {
if (!first) ss << ", ";
ss << caption_count << " Caption" << (caption_count > 1 ? "s" : "");
first = false;
}
if (sprite_count > 0) {
if (!first) ss << ", ";
ss << sprite_count << " Sprite" << (sprite_count > 1 ? "s" : "");
first = false;
}
if (grid_count > 0) {
if (!first) ss << ", ";
ss << grid_count << " Grid" << (grid_count > 1 ? "s" : "");
first = false;
}
if (other_count > 0) {
if (!first) ss << ", ";
ss << other_count << " UIDrawable" << (other_count > 1 ? "s" : "");
}
ss << ")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
int UICollection::init(PyUICollectionObject* self, PyObject* args, PyObject* kwds)
{
PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required.");
return -1;
}
PyObject* UICollection::iter(PyUICollectionObject* self)
{
// Get the iterator type from the module to ensure we have the registered version
PyTypeObject* iterType = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UICollectionIter");
if (!iterType) {
PyErr_SetString(PyExc_RuntimeError, "Could not find UICollectionIter type in module");
return NULL;
}
// Allocate new iterator instance
PyUICollectionIterObject* iterObj = (PyUICollectionIterObject*)iterType->tp_alloc(iterType, 0);
if (iterObj == NULL) {
Py_DECREF(iterType);
return NULL; // Failed to allocate memory for the iterator object
}
iterObj->data = self->data;
iterObj->index = 0;
iterObj->start_size = self->data->size();
Py_DECREF(iterType);
return (PyObject*)iterObj;
}

View File

@ -1,101 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "UIDrawable.h"
class UICollectionIter
{
// really more of a namespace: all the members are public and static. But being consistent with other UI objects
public:
static int init(PyUICollectionIterObject* self, PyObject* args, PyObject* kwds);
static PyObject* next(PyUICollectionIterObject* self);
static PyObject* repr(PyUICollectionIterObject* self);
};
class UICollection
{
// really more of a namespace: all the members are public and static. But being consistent with other UI objects
public:
static Py_ssize_t len(PyUICollectionObject* self);
static PyObject* getitem(PyUICollectionObject* self, Py_ssize_t index);
static int setitem(PyUICollectionObject* self, Py_ssize_t index, PyObject* value);
static int contains(PyUICollectionObject* self, PyObject* value);
static PyObject* concat(PyUICollectionObject* self, PyObject* other);
static PyObject* inplace_concat(PyUICollectionObject* self, PyObject* other);
static PySequenceMethods sqmethods;
static PyMappingMethods mpmethods;
static PyObject* subscript(PyUICollectionObject* self, PyObject* key);
static int ass_subscript(PyUICollectionObject* self, PyObject* key, PyObject* value);
static PyObject* append(PyUICollectionObject* self, PyObject* o);
static PyObject* extend(PyUICollectionObject* self, PyObject* iterable);
static PyObject* remove(PyUICollectionObject* self, PyObject* o);
static PyObject* index_method(PyUICollectionObject* self, PyObject* value);
static PyObject* count(PyUICollectionObject* self, PyObject* value);
static PyMethodDef methods[];
static PyObject* repr(PyUICollectionObject* self);
static int init(PyUICollectionObject* self, PyObject* args, PyObject* kwds);
static PyObject* iter(PyUICollectionObject* self);
};
namespace mcrfpydef {
static PyTypeObject PyUICollectionIterType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.UICollectionIter",
.tp_basicsize = sizeof(PyUICollectionIterObject),
.tp_itemsize = 0,
//TODO - as static method, not inline lambda def, please
.tp_dealloc = (destructor)[](PyObject* self)
{
PyUICollectionIterObject* obj = (PyUICollectionIterObject*)self;
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)UICollectionIter::repr,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("Iterator for a collection of UI objects"),
.tp_iter = PyObject_SelfIter,
.tp_iternext = (iternextfunc)UICollectionIter::next,
//.tp_getset = PyUICollection_getset,
.tp_init = (initproc)UICollectionIter::init, // just raise an exception
.tp_alloc = PyType_GenericAlloc,
//TODO - as static method, not inline lambda def, please
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required.");
return NULL;
}
};
static PyTypeObject PyUICollectionType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.UICollection",
.tp_basicsize = sizeof(PyUICollectionObject),
.tp_itemsize = 0,
//TODO - as static method, not inline lambda def, please
.tp_dealloc = (destructor)[](PyObject* self)
{
PyUICollectionObject* obj = (PyUICollectionObject*)self;
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)UICollection::repr,
.tp_as_sequence = &UICollection::sqmethods,
.tp_as_mapping = &UICollection::mpmethods,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("Iterable, indexable collection of UI objects"),
.tp_iter = (getiterfunc)UICollection::iter,
.tp_methods = UICollection::methods, // append, remove
//.tp_getset = PyUICollection_getset,
.tp_init = (initproc)UICollection::init, // just raise an exception
//TODO - as static method, not inline lambda def, please
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
// Does PyUICollectionType need __new__ if it's not supposed to be instantiable by the user?
// Should I just raise an exception? Or is the uninitialized shared_ptr enough of a blocker?
PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required.");
return NULL;
}
};
}

View File

@ -1,165 +0,0 @@
#include "UIDrawable.h"
#include "UIFrame.h"
#include "UICaption.h"
#include "UISprite.h"
#include "UIGrid.h"
#include "GameEngine.h"
#include "McRFPy_API.h"
UIDrawable::UIDrawable() { click_callable = NULL; }
void UIDrawable::click_unregister()
{
click_callable.reset();
}
void UIDrawable::render()
{
render(sf::Vector2f(), Resources::game->getRenderTarget());
}
PyObject* UIDrawable::get_click(PyObject* self, void* closure) {
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure)); // trust me bro, it's an Enum
PyObject* ptr;
switch (objtype)
{
case PyObjectsEnum::UIFRAME:
ptr = ((PyUIFrameObject*)self)->data->click_callable->borrow();
break;
case PyObjectsEnum::UICAPTION:
ptr = ((PyUICaptionObject*)self)->data->click_callable->borrow();
break;
case PyObjectsEnum::UISPRITE:
ptr = ((PyUISpriteObject*)self)->data->click_callable->borrow();
break;
case PyObjectsEnum::UIGRID:
ptr = ((PyUIGridObject*)self)->data->click_callable->borrow();
break;
default:
PyErr_SetString(PyExc_TypeError, "no idea how you did that; invalid UIDrawable derived instance for _get_click");
return NULL;
}
if (ptr && ptr != Py_None)
return ptr;
else
return Py_None;
}
int UIDrawable::set_click(PyObject* self, PyObject* value, void* closure) {
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure)); // trust me bro, it's an Enum
UIDrawable* target;
switch (objtype)
{
case PyObjectsEnum::UIFRAME:
target = (((PyUIFrameObject*)self)->data.get());
break;
case PyObjectsEnum::UICAPTION:
target = (((PyUICaptionObject*)self)->data.get());
break;
case PyObjectsEnum::UISPRITE:
target = (((PyUISpriteObject*)self)->data.get());
break;
case PyObjectsEnum::UIGRID:
target = (((PyUIGridObject*)self)->data.get());
break;
default:
PyErr_SetString(PyExc_TypeError, "no idea how you did that; invalid UIDrawable derived instance for _set_click");
return -1;
}
if (value == Py_None)
{
target->click_unregister();
} else {
target->click_register(value);
}
return 0;
}
void UIDrawable::click_register(PyObject* callable)
{
click_callable = std::make_unique<PyClickCallable>(callable);
}
PyObject* UIDrawable::get_int(PyObject* self, void* closure) {
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
UIDrawable* drawable = nullptr;
switch (objtype) {
case PyObjectsEnum::UIFRAME:
drawable = ((PyUIFrameObject*)self)->data.get();
break;
case PyObjectsEnum::UICAPTION:
drawable = ((PyUICaptionObject*)self)->data.get();
break;
case PyObjectsEnum::UISPRITE:
drawable = ((PyUISpriteObject*)self)->data.get();
break;
case PyObjectsEnum::UIGRID:
drawable = ((PyUIGridObject*)self)->data.get();
break;
default:
PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance");
return NULL;
}
return PyLong_FromLong(drawable->z_index);
}
int UIDrawable::set_int(PyObject* self, PyObject* value, void* closure) {
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
UIDrawable* drawable = nullptr;
switch (objtype) {
case PyObjectsEnum::UIFRAME:
drawable = ((PyUIFrameObject*)self)->data.get();
break;
case PyObjectsEnum::UICAPTION:
drawable = ((PyUICaptionObject*)self)->data.get();
break;
case PyObjectsEnum::UISPRITE:
drawable = ((PyUISpriteObject*)self)->data.get();
break;
case PyObjectsEnum::UIGRID:
drawable = ((PyUIGridObject*)self)->data.get();
break;
default:
PyErr_SetString(PyExc_TypeError, "Invalid UIDrawable derived instance");
return -1;
}
if (!PyLong_Check(value)) {
PyErr_SetString(PyExc_TypeError, "z_index must be an integer");
return -1;
}
long z = PyLong_AsLong(value);
if (z == -1 && PyErr_Occurred()) {
return -1;
}
// Clamp to int range
if (z < INT_MIN) z = INT_MIN;
if (z > INT_MAX) z = INT_MAX;
int old_z_index = drawable->z_index;
drawable->z_index = static_cast<int>(z);
// Notify of z_index change
if (old_z_index != drawable->z_index) {
drawable->notifyZIndexChanged();
}
return 0;
}
void UIDrawable::notifyZIndexChanged() {
// Mark the current scene as needing sort
// This works for elements in the scene's ui_elements collection
McRFPy_API::markSceneNeedsSort();
// TODO: In the future, we could add parent tracking to handle Frame children
// For now, Frame children will need manual sorting or collection modification
// to trigger a resort
}

View File

@ -1,148 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "structmember.h"
#include "IndexTexture.h"
#include "Resources.h"
#include <list>
#include "PyCallable.h"
#include "PyTexture.h"
#include "PyColor.h"
#include "PyVector.h"
#include "PyFont.h"
#include "Resources.h"
#include "UIBase.h"
class UIFrame; class UICaption; class UISprite; class UIEntity; class UIGrid;
enum PyObjectsEnum : int
{
UIFRAME = 1,
UICAPTION,
UISPRITE,
UIGRID
};
class UIDrawable
{
public:
void render();
//virtual void render(sf::Vector2f) = 0;
virtual void render(sf::Vector2f, sf::RenderTarget&) = 0;
virtual PyObjectsEnum derived_type() = 0;
// Mouse input handling - callable object, methods to find event's destination
std::unique_ptr<PyClickCallable> click_callable;
virtual UIDrawable* click_at(sf::Vector2f point) = 0;
void click_register(PyObject*);
void click_unregister();
UIDrawable();
static PyObject* get_click(PyObject* self, void* closure);
static int set_click(PyObject* self, PyObject* value, void* closure);
static PyObject* get_int(PyObject* self, void* closure);
static int set_int(PyObject* self, PyObject* value, void* closure);
// Z-order for rendering (lower values rendered first, higher values on top)
int z_index = 0;
// Notification for z_index changes
void notifyZIndexChanged();
// Animation support
virtual bool setProperty(const std::string& name, float value) { return false; }
virtual bool setProperty(const std::string& name, int value) { return false; }
virtual bool setProperty(const std::string& name, const sf::Color& value) { return false; }
virtual bool setProperty(const std::string& name, const sf::Vector2f& value) { return false; }
virtual bool setProperty(const std::string& name, const std::string& value) { return false; }
virtual bool getProperty(const std::string& name, float& value) const { return false; }
virtual bool getProperty(const std::string& name, int& value) const { return false; }
virtual bool getProperty(const std::string& name, sf::Color& value) const { return false; }
virtual bool getProperty(const std::string& name, sf::Vector2f& value) const { return false; }
virtual bool getProperty(const std::string& name, std::string& value) const { return false; }
};
typedef struct {
PyObject_HEAD
std::shared_ptr<std::vector<std::shared_ptr<UIDrawable>>> data;
} PyUICollectionObject;
typedef struct {
PyObject_HEAD
std::shared_ptr<std::vector<std::shared_ptr<UIDrawable>>> data;
int index;
int start_size;
} PyUICollectionIterObject;
namespace mcrfpydef {
// DEPRECATED: RET_PY_INSTANCE macro has been replaced with template functions in PyObjectUtils.h
// The macro was difficult to debug and used static type references that could cause initialization order issues.
// Use PyObjectUtils::convertDrawableToPython() or PyObjectUtils::createPyObject<T>() instead.
//TODO: add this method to class scope; move implementation to .cpp file
/*
static PyObject* PyUIDrawable_get_click(PyObject* self, void* closure) {
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure)); // trust me bro, it's an Enum
PyObject* ptr;
switch (objtype)
{
case PyObjectsEnum::UIFRAME:
ptr = ((PyUIFrameObject*)self)->data->click_callable->borrow();
break;
case PyObjectsEnum::UICAPTION:
ptr = ((PyUICaptionObject*)self)->data->click_callable->borrow();
break;
case PyObjectsEnum::UISPRITE:
ptr = ((PyUISpriteObject*)self)->data->click_callable->borrow();
break;
case PyObjectsEnum::UIGRID:
ptr = ((PyUIGridObject*)self)->data->click_callable->borrow();
break;
default:
PyErr_SetString(PyExc_TypeError, "no idea how you did that; invalid UIDrawable derived instance for _get_click");
return NULL;
}
if (ptr && ptr != Py_None)
return ptr;
else
return Py_None;
}*/
//TODO: add this method to class scope; move implementation to .cpp file
/*
static int PyUIDrawable_set_click(PyObject* self, PyObject* value, void* closure) {
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure)); // trust me bro, it's an Enum
UIDrawable* target;
switch (objtype)
{
case PyObjectsEnum::UIFRAME:
target = (((PyUIFrameObject*)self)->data.get());
break;
case PyObjectsEnum::UICAPTION:
target = (((PyUICaptionObject*)self)->data.get());
break;
case PyObjectsEnum::UISPRITE:
target = (((PyUISpriteObject*)self)->data.get());
break;
case PyObjectsEnum::UIGRID:
target = (((PyUIGridObject*)self)->data.get());
break;
default:
PyErr_SetString(PyExc_TypeError, "no idea how you did that; invalid UIDrawable derived instance for _set_click");
return -1;
}
if (value == Py_None)
{
target->click_unregister();
} else {
target->click_register(value);
}
return 0;
}
*/
}

View File

@ -1,320 +0,0 @@
#include "UIEntity.h"
#include "UIGrid.h"
#include "McRFPy_API.h"
#include "PyObjectUtils.h"
#include "PyVector.h"
UIEntity::UIEntity() {} // this will not work lol. TODO remove default constructor by finding the shared pointer inits that use it
UIEntity::UIEntity(UIGrid& grid)
: gridstate(grid.grid_x * grid.grid_y)
{
}
PyObject* UIEntity::at(PyUIEntityObject* self, PyObject* o) {
int x, y;
if (!PyArg_ParseTuple(o, "ii", &x, &y)) {
PyErr_SetString(PyExc_TypeError, "UIEntity.at requires two integer arguments: (x, y)");
return NULL;
}
if (self->data->grid == NULL) {
PyErr_SetString(PyExc_ValueError, "Entity cannot access surroundings because it is not associated with a grid");
return NULL;
}
/*
PyUIGridPointStateObject* obj = (PyUIGridPointStateObject*)((&mcrfpydef::PyUIGridPointStateType)->tp_alloc(&mcrfpydef::PyUIGridPointStateType, 0));
*/
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState");
auto obj = (PyUIGridPointStateObject*)type->tp_alloc(type, 0);
//auto target = std::static_pointer_cast<UIEntity>(target);
obj->data = &(self->data->gridstate[y + self->data->grid->grid_x * x]);
obj->grid = self->data->grid;
obj->entity = self->data;
return (PyObject*)obj;
}
PyObject* UIEntity::index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored)) {
// Check if entity has an associated grid
if (!self->data || !self->data->grid) {
PyErr_SetString(PyExc_RuntimeError, "Entity is not associated with a grid");
return NULL;
}
// Get the grid's entity collection
auto entities = self->data->grid->entities;
if (!entities) {
PyErr_SetString(PyExc_RuntimeError, "Grid has no entity collection");
return NULL;
}
// Find this entity in the collection
int index = 0;
for (auto it = entities->begin(); it != entities->end(); ++it, ++index) {
if (it->get() == self->data.get()) {
return PyLong_FromLong(index);
}
}
// Entity not found in its grid's collection
PyErr_SetString(PyExc_ValueError, "Entity not found in its grid's entity collection");
return NULL;
}
int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
//static const char* keywords[] = { "x", "y", "texture", "sprite_index", "grid", nullptr };
//float x = 0.0f, y = 0.0f, scale = 1.0f;
static const char* keywords[] = { "pos", "texture", "sprite_index", "grid", nullptr };
PyObject* pos;
float scale = 1.0f;
int sprite_index = -1;
PyObject* texture = NULL;
PyObject* grid = NULL;
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O",
// const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &grid))
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|OiO",
const_cast<char**>(keywords), &pos, &texture, &sprite_index, &grid))
{
return -1;
}
PyVectorObject* pos_result = PyVector::from_arg(pos);
if (!pos_result)
{
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
return -1;
}
// check types for texture
//
// Set Texture - allow None or use default
//
std::shared_ptr<PyTexture> texture_ptr = nullptr;
if (texture != NULL && texture != Py_None && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance or None");
return -1;
} else if (texture != NULL && texture != Py_None) {
auto pytexture = (PyTextureObject*)texture;
texture_ptr = pytexture->data;
} else {
// Use default texture when None or not provided
texture_ptr = McRFPy_API::default_texture;
}
if (!texture_ptr) {
PyErr_SetString(PyExc_RuntimeError, "No texture provided and no default texture available");
return -1;
}
if (grid != NULL && !PyObject_IsInstance(grid, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Grid"))) {
PyErr_SetString(PyExc_TypeError, "grid must be a mcrfpy.Grid instance");
return -1;
}
if (grid == NULL)
self->data = std::make_shared<UIEntity>();
else
self->data = std::make_shared<UIEntity>(*((PyUIGridObject*)grid)->data);
// Store reference to Python object
self->data->self = (PyObject*)self;
Py_INCREF(self);
// TODO - PyTextureObjects and IndexTextures are a little bit of a mess with shared/unshared pointers
self->data->sprite = UISprite(texture_ptr, sprite_index, sf::Vector2f(0,0), 1.0);
self->data->position = pos_result->data;
if (grid != NULL) {
PyUIGridObject* pygrid = (PyUIGridObject*)grid;
self->data->grid = pygrid->data;
// todone - on creation of Entity with Grid assignment, also append it to the entity list
pygrid->data->entities->push_back(self->data);
}
return 0;
}
PyObject* UIEntity::get_spritenumber(PyUIEntityObject* self, void* closure) {
return PyLong_FromDouble(self->data->sprite.getSpriteIndex());
}
PyObject* sfVector2f_to_PyObject(sf::Vector2f vec) {
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
auto obj = (PyVectorObject*)type->tp_alloc(type, 0);
if (obj) {
obj->data = vec;
}
return (PyObject*)obj;
}
PyObject* sfVector2i_to_PyObject(sf::Vector2i vec) {
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
auto obj = (PyVectorObject*)type->tp_alloc(type, 0);
if (obj) {
obj->data = sf::Vector2f(static_cast<float>(vec.x), static_cast<float>(vec.y));
}
return (PyObject*)obj;
}
sf::Vector2f PyObject_to_sfVector2f(PyObject* obj) {
PyVectorObject* vec = PyVector::from_arg(obj);
if (!vec) {
// PyVector::from_arg already set the error
return sf::Vector2f(0, 0);
}
return vec->data;
}
sf::Vector2i PyObject_to_sfVector2i(PyObject* obj) {
PyVectorObject* vec = PyVector::from_arg(obj);
if (!vec) {
// PyVector::from_arg already set the error
return sf::Vector2i(0, 0);
}
return sf::Vector2i(static_cast<int>(vec->data.x), static_cast<int>(vec->data.y));
}
// TODO - deprecate / remove this helper
PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state) {
// This function is incomplete - it creates an empty object without setting state data
// Should use PyObjectUtils::createGridPointState() instead
return PyObjectUtils::createPyObjectGeneric("GridPointState");
}
PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>& vec) {
PyObject* list = PyList_New(vec.size());
if (!list) return PyErr_NoMemory();
for (size_t i = 0; i < vec.size(); ++i) {
PyObject* obj = UIGridPointState_to_PyObject(vec[i]);
if (!obj) { // Cleanup on failure
Py_DECREF(list);
return NULL;
}
PyList_SET_ITEM(list, i, obj); // This steals a reference to obj
}
return list;
}
PyObject* UIEntity::get_position(PyUIEntityObject* self, void* closure) {
if (reinterpret_cast<long>(closure) == 0) {
return sfVector2f_to_PyObject(self->data->position);
} else {
return sfVector2i_to_PyObject(self->data->collision_pos);
}
}
int UIEntity::set_position(PyUIEntityObject* self, PyObject* value, void* closure) {
if (reinterpret_cast<long>(closure) == 0) {
sf::Vector2f vec = PyObject_to_sfVector2f(value);
if (PyErr_Occurred()) {
return -1; // Error already set by PyObject_to_sfVector2f
}
self->data->position = vec;
} else {
sf::Vector2i vec = PyObject_to_sfVector2i(value);
if (PyErr_Occurred()) {
return -1; // Error already set by PyObject_to_sfVector2i
}
self->data->collision_pos = vec;
}
return 0;
}
PyObject* UIEntity::get_gridstate(PyUIEntityObject* self, void* closure) {
// Assuming a function to convert std::vector<UIGridPointState> to PyObject* list
return UIGridPointStateVector_to_PyList(self->data->gridstate);
}
int UIEntity::set_spritenumber(PyUIEntityObject* self, PyObject* value, void* closure) {
int val;
if (PyLong_Check(value))
val = PyLong_AsLong(value);
else
{
PyErr_SetString(PyExc_TypeError, "Value must be an integer.");
return -1;
}
//self->data->sprite.sprite_index = val;
self->data->sprite.setSpriteIndex(val); // todone - I don't like ".sprite.sprite" in this stack of UIEntity.UISprite.sf::Sprite
return 0;
}
PyMethodDef UIEntity::methods[] = {
{"at", (PyCFunction)UIEntity::at, METH_O},
{"index", (PyCFunction)UIEntity::index, METH_NOARGS, "Return the index of this entity in its grid's entity collection"},
{NULL, NULL, 0, NULL}
};
PyGetSetDef UIEntity::getsetters[] = {
{"draw_pos", (getter)UIEntity::get_position, (setter)UIEntity::set_position, "Entity position (graphically)", (void*)0},
{"pos", (getter)UIEntity::get_position, (setter)UIEntity::set_position, "Entity position (integer grid coordinates)", (void*)1},
{"gridstate", (getter)UIEntity::get_gridstate, NULL, "Grid point states for the entity", NULL},
{"sprite_index", (getter)UIEntity::get_spritenumber, (setter)UIEntity::set_spritenumber, "Sprite index on the texture on the display", NULL},
{"sprite_number", (getter)UIEntity::get_spritenumber, (setter)UIEntity::set_spritenumber, "Sprite index on the texture on the display (deprecated: use sprite_index)", NULL},
{NULL} /* Sentinel */
};
PyObject* UIEntity::repr(PyUIEntityObject* self) {
std::ostringstream ss;
if (!self->data) ss << "<Entity (invalid internal object)>";
else {
auto ent = self->data;
ss << "<Entity (x=" << self->data->position.x << ", y=" << self->data->position.y << ", sprite_index=" << self->data->sprite.getSpriteIndex() <<
")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
// Property system implementation for animations
bool UIEntity::setProperty(const std::string& name, float value) {
if (name == "x") {
position.x = value;
collision_pos.x = static_cast<int>(value);
// Update sprite position based on grid position
// Note: This is a simplified version - actual grid-to-pixel conversion depends on grid properties
sprite.setPosition(sf::Vector2f(position.x, position.y));
return true;
}
else if (name == "y") {
position.y = value;
collision_pos.y = static_cast<int>(value);
// Update sprite position based on grid position
sprite.setPosition(sf::Vector2f(position.x, position.y));
return true;
}
else if (name == "sprite_scale") {
sprite.setScale(sf::Vector2f(value, value));
return true;
}
return false;
}
bool UIEntity::setProperty(const std::string& name, int value) {
if (name == "sprite_index" || name == "sprite_number") {
sprite.setSpriteIndex(value);
return true;
}
return false;
}
bool UIEntity::getProperty(const std::string& name, float& value) const {
if (name == "x") {
value = position.x;
return true;
}
else if (name == "y") {
value = position.y;
return true;
}
else if (name == "sprite_scale") {
value = sprite.getScale().x; // Assuming uniform scale
return true;
}
return false;
}

View File

@ -1,82 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "structmember.h"
#include "IndexTexture.h"
#include "Resources.h"
#include <list>
#include "PyCallable.h"
#include "PyTexture.h"
#include "PyColor.h"
#include "PyVector.h"
#include "PyFont.h"
#include "UIGridPoint.h"
#include "UIDrawable.h"
#include "UIBase.h"
#include "UISprite.h"
class UIGrid;
//class UIEntity;
//typedef struct {
// PyObject_HEAD
// std::shared_ptr<UIEntity> data;
//} PyUIEntityObject;
// helper methods with no namespace requirement
static PyObject* sfVector2f_to_PyObject(sf::Vector2f vector);
static sf::Vector2f PyObject_to_sfVector2f(PyObject* obj);
static PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state);
static PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>& vec);
// TODO: make UIEntity a drawable
class UIEntity//: public UIDrawable
{
public:
PyObject* self = nullptr; // Reference to the Python object (if created from Python)
std::shared_ptr<UIGrid> grid;
std::vector<UIGridPointState> gridstate;
UISprite sprite;
sf::Vector2f position; //(x,y) in grid coordinates; float for animation
sf::Vector2i collision_pos; //(x, y) in grid coordinates: int for collision
//void render(sf::Vector2f); //override final;
UIEntity();
UIEntity(UIGrid&);
// Property system for animations
bool setProperty(const std::string& name, float value);
bool setProperty(const std::string& name, int value);
bool getProperty(const std::string& name, float& value) const;
static PyObject* at(PyUIEntityObject* self, PyObject* o);
static PyObject* index(PyUIEntityObject* self, PyObject* Py_UNUSED(ignored));
static int init(PyUIEntityObject* self, PyObject* args, PyObject* kwds);
static PyObject* get_position(PyUIEntityObject* self, void* closure);
static int set_position(PyUIEntityObject* self, PyObject* value, void* closure);
static PyObject* get_gridstate(PyUIEntityObject* self, void* closure);
static PyObject* get_spritenumber(PyUIEntityObject* self, void* closure);
static int set_spritenumber(PyUIEntityObject* self, PyObject* value, void* closure);
static PyMethodDef methods[];
static PyGetSetDef getsetters[];
static PyObject* repr(PyUIEntityObject* self);
};
namespace mcrfpydef {
static PyTypeObject PyUIEntityType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Entity",
.tp_basicsize = sizeof(PyUIEntityObject),
.tp_itemsize = 0,
.tp_repr = (reprfunc)UIEntity::repr,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_doc = "UIEntity objects",
.tp_methods = UIEntity::methods,
.tp_getset = UIEntity::getsetters,
.tp_init = (initproc)UIEntity::init,
.tp_new = PyType_GenericNew,
};
}

View File

@ -1,469 +0,0 @@
#include "UIFrame.h"
#include "UICollection.h"
#include "GameEngine.h"
#include "PyVector.h"
UIDrawable* UIFrame::click_at(sf::Vector2f point)
{
for (auto e: *children)
{
auto p = e->click_at(point + box.getPosition());
if (p)
return p;
}
if (click_callable)
{
float x = box.getPosition().x, y = box.getPosition().y, w = box.getSize().x, h = box.getSize().y;
if (point.x > x && point.y > y && point.x < x+w && point.y < y+h) return this;
}
return NULL;
}
UIFrame::UIFrame()
: outline(0)
{
children = std::make_shared<std::vector<std::shared_ptr<UIDrawable>>>();
box.setPosition(0, 0);
box.setSize(sf::Vector2f(0, 0));
}
UIFrame::UIFrame(float _x, float _y, float _w, float _h)
: outline(0)
{
box.setPosition(_x, _y);
box.setSize(sf::Vector2f(_w, _h));
children = std::make_shared<std::vector<std::shared_ptr<UIDrawable>>>();
}
UIFrame::~UIFrame()
{
children.reset();
}
PyObjectsEnum UIFrame::derived_type()
{
return PyObjectsEnum::UIFRAME;
}
void UIFrame::render(sf::Vector2f offset, sf::RenderTarget& target)
{
box.move(offset);
//Resources::game->getWindow().draw(box);
target.draw(box);
box.move(-offset);
// Sort children by z_index if needed
if (children_need_sort && !children->empty()) {
std::sort(children->begin(), children->end(),
[](const std::shared_ptr<UIDrawable>& a, const std::shared_ptr<UIDrawable>& b) {
return a->z_index < b->z_index;
});
children_need_sort = false;
}
for (auto drawable : *children) {
drawable->render(offset + box.getPosition(), target);
}
}
PyObject* UIFrame::get_children(PyUIFrameObject* self, void* closure)
{
// create PyUICollection instance pointing to self->data->children
//PyUICollectionObject* o = (PyUICollectionObject*)mcrfpydef::PyUICollectionType.tp_alloc(&mcrfpydef::PyUICollectionType, 0);
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "UICollection");
auto o = (PyUICollectionObject*)type->tp_alloc(type, 0);
if (o)
o->data = self->data->children;
return (PyObject*)o;
}
PyObject* UIFrame::get_float_member(PyUIFrameObject* self, void* closure)
{
auto member_ptr = reinterpret_cast<long>(closure);
if (member_ptr == 0)
return PyFloat_FromDouble(self->data->box.getPosition().x);
else if (member_ptr == 1)
return PyFloat_FromDouble(self->data->box.getPosition().y);
else if (member_ptr == 2)
return PyFloat_FromDouble(self->data->box.getSize().x);
else if (member_ptr == 3)
return PyFloat_FromDouble(self->data->box.getSize().y);
else if (member_ptr == 4)
return PyFloat_FromDouble(self->data->box.getOutlineThickness());
else
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return nullptr;
}
}
int UIFrame::set_float_member(PyUIFrameObject* self, PyObject* value, void* closure)
{
float val;
auto member_ptr = reinterpret_cast<long>(closure);
if (PyFloat_Check(value))
{
val = PyFloat_AsDouble(value);
}
else if (PyLong_Check(value))
{
val = PyLong_AsLong(value);
}
else
{
PyErr_SetString(PyExc_TypeError, "Value must be an integer.");
return -1;
}
if (member_ptr == 0) //x
self->data->box.setPosition(val, self->data->box.getPosition().y);
else if (member_ptr == 1) //y
self->data->box.setPosition(self->data->box.getPosition().x, val);
else if (member_ptr == 2) //w
self->data->box.setSize(sf::Vector2f(val, self->data->box.getSize().y));
else if (member_ptr == 3) //h
self->data->box.setSize(sf::Vector2f(self->data->box.getSize().x, val));
else if (member_ptr == 4) //outline
self->data->box.setOutlineThickness(val);
return 0;
}
PyObject* UIFrame::get_color_member(PyUIFrameObject* self, void* closure)
{
// validate closure (should be impossible to be wrong, but it's thorough)
auto member_ptr = reinterpret_cast<long>(closure);
if (member_ptr != 0 && member_ptr != 1)
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return nullptr;
}
//PyTypeObject* colorType = &PyColorType;
auto colorType = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color");
PyObject* pyColor = colorType->tp_alloc(colorType, 0);
if (pyColor == NULL)
{
std::cout << "failure to allocate mcrfpy.Color / PyColorType" << std::endl;
return NULL;
}
PyColorObject* pyColorObj = reinterpret_cast<PyColorObject*>(pyColor);
// fetch correct member data
sf::Color color;
if (member_ptr == 0)
{
color = self->data->box.getFillColor();
//return Py_BuildValue("(iii)", color.r, color.g, color.b);
}
else if (member_ptr == 1)
{
color = self->data->box.getOutlineColor();
//return Py_BuildValue("(iii)", color.r, color.g, color.b);
}
return PyColor(color).pyObject();
}
int UIFrame::set_color_member(PyUIFrameObject* self, PyObject* value, void* closure)
{
//TODO: this logic of (PyColor instance OR tuple -> sf::color) should be encapsulated for reuse
auto member_ptr = reinterpret_cast<long>(closure);
int r, g, b, a;
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color")))
{
sf::Color c = ((PyColorObject*)value)->data;
r = c.r; g = c.g; b = c.b; a = c.a;
}
else if (!PyTuple_Check(value) || PyTuple_Size(value) < 3 || PyTuple_Size(value) > 4)
{
// reject non-Color, non-tuple value
PyErr_SetString(PyExc_TypeError, "Value must be a tuple of 3 or 4 integers or an mcrfpy.Color object.");
return -1;
}
else // get value from tuples
{
r = PyLong_AsLong(PyTuple_GetItem(value, 0));
g = PyLong_AsLong(PyTuple_GetItem(value, 1));
b = PyLong_AsLong(PyTuple_GetItem(value, 2));
a = 255;
if (PyTuple_Size(value) == 4)
{
a = PyLong_AsLong(PyTuple_GetItem(value, 3));
}
}
if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255 || a < 0 || a > 255)
{
PyErr_SetString(PyExc_ValueError, "Color values must be between 0 and 255.");
return -1;
}
if (member_ptr == 0)
{
self->data->box.setFillColor(sf::Color(r, g, b, a));
}
else if (member_ptr == 1)
{
self->data->box.setOutlineColor(sf::Color(r, g, b, a));
}
else
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return -1;
}
return 0;
}
PyObject* UIFrame::get_pos(PyUIFrameObject* self, void* closure)
{
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
auto obj = (PyVectorObject*)type->tp_alloc(type, 0);
if (obj) {
auto pos = self->data->box.getPosition();
obj->data = sf::Vector2f(pos.x, pos.y);
}
return (PyObject*)obj;
}
int UIFrame::set_pos(PyUIFrameObject* self, PyObject* value, void* closure)
{
PyVectorObject* vec = PyVector::from_arg(value);
if (!vec) {
PyErr_SetString(PyExc_TypeError, "pos must be a Vector or convertible to Vector");
return -1;
}
self->data->box.setPosition(vec->data);
return 0;
}
PyGetSetDef UIFrame::getsetters[] = {
{"x", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "X coordinate of top-left corner", (void*)0},
{"y", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "Y coordinate of top-left corner", (void*)1},
{"w", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "width of the rectangle", (void*)2},
{"h", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "height of the rectangle", (void*)3},
{"outline", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "Thickness of the border", (void*)4},
{"fill_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Fill color of the rectangle", (void*)0},
{"outline_color", (getter)UIFrame::get_color_member, (setter)UIFrame::set_color_member, "Outline color of the rectangle", (void*)1},
{"children", (getter)UIFrame::get_children, NULL, "UICollection of objects on top of this one", NULL},
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UIFRAME},
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIFRAME},
{"pos", (getter)UIFrame::get_pos, (setter)UIFrame::set_pos, "Position as a Vector", NULL},
{NULL}
};
PyObject* UIFrame::repr(PyUIFrameObject* self)
{
std::ostringstream ss;
if (!self->data) ss << "<Frame (invalid internal object)>";
else {
auto box = self->data->box;
auto fc = box.getFillColor();
auto oc = box.getOutlineColor();
ss << "<Frame (x=" << box.getPosition().x << ", y=" << box.getPosition().y << ", w=" <<
box.getSize().x << ", w=" << box.getSize().y << ", " <<
"outline=" << box.getOutlineThickness() << ", " <<
"fill_color=(" << (int)fc.r << ", " << (int)fc.g << ", " << (int)fc.b << ", " << (int)fc.a <<"), " <<
"outline_color=(" << (int)oc.r << ", " << (int)oc.g << ", " << (int)oc.b << ", " << (int)oc.a <<"), " <<
self->data->children->size() << " child objects" <<
")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds)
{
//std::cout << "Init called\n";
const char* keywords[] = { "x", "y", "w", "h", "fill_color", "outline_color", "outline", nullptr };
float x = 0.0f, y = 0.0f, w = 0.0f, h=0.0f, outline=0.0f;
PyObject* fill_color = 0;
PyObject* outline_color = 0;
// First try to parse as (x, y, w, h, ...)
if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffff|OOf", const_cast<char**>(keywords), &x, &y, &w, &h, &fill_color, &outline_color, &outline))
{
PyErr_Clear(); // Clear the error
// Try to parse as ((x,y), w, h, ...) or (Vector, w, h, ...)
PyObject* pos_obj = nullptr;
const char* alt_keywords[] = { "pos", "w", "h", "fill_color", "outline_color", "outline", nullptr };
if (!PyArg_ParseTupleAndKeywords(args, kwds, "Off|OOf", const_cast<char**>(alt_keywords),
&pos_obj, &w, &h, &fill_color, &outline_color, &outline))
{
return -1;
}
// Convert position argument to x, y
PyVectorObject* vec = PyVector::from_arg(pos_obj);
if (!vec) {
PyErr_SetString(PyExc_TypeError, "First argument must be a tuple (x, y) or Vector when not providing x, y separately");
return -1;
}
x = vec->data.x;
y = vec->data.y;
}
self->data->box.setPosition(sf::Vector2f(x, y));
self->data->box.setSize(sf::Vector2f(w, h));
self->data->box.setOutlineThickness(outline);
// getsetter abuse because I haven't standardized Color object parsing (TODO)
int err_val = 0;
if (fill_color && fill_color != Py_None) err_val = UIFrame::set_color_member(self, fill_color, (void*)0);
else self->data->box.setFillColor(sf::Color(0,0,0,255));
if (err_val) return err_val;
if (outline_color && outline_color != Py_None) err_val = UIFrame::set_color_member(self, outline_color, (void*)1);
else self->data->box.setOutlineColor(sf::Color(128,128,128,255));
if (err_val) return err_val;
return 0;
}
// Animation property system implementation
bool UIFrame::setProperty(const std::string& name, float value) {
if (name == "x") {
box.setPosition(sf::Vector2f(value, box.getPosition().y));
return true;
} else if (name == "y") {
box.setPosition(sf::Vector2f(box.getPosition().x, value));
return true;
} else if (name == "w") {
box.setSize(sf::Vector2f(value, box.getSize().y));
return true;
} else if (name == "h") {
box.setSize(sf::Vector2f(box.getSize().x, value));
return true;
} else if (name == "outline") {
box.setOutlineThickness(value);
return true;
} else if (name == "fill_color.r") {
auto color = box.getFillColor();
color.r = std::clamp(static_cast<int>(value), 0, 255);
box.setFillColor(color);
return true;
} else if (name == "fill_color.g") {
auto color = box.getFillColor();
color.g = std::clamp(static_cast<int>(value), 0, 255);
box.setFillColor(color);
return true;
} else if (name == "fill_color.b") {
auto color = box.getFillColor();
color.b = std::clamp(static_cast<int>(value), 0, 255);
box.setFillColor(color);
return true;
} else if (name == "fill_color.a") {
auto color = box.getFillColor();
color.a = std::clamp(static_cast<int>(value), 0, 255);
box.setFillColor(color);
return true;
} else if (name == "outline_color.r") {
auto color = box.getOutlineColor();
color.r = std::clamp(static_cast<int>(value), 0, 255);
box.setOutlineColor(color);
return true;
} else if (name == "outline_color.g") {
auto color = box.getOutlineColor();
color.g = std::clamp(static_cast<int>(value), 0, 255);
box.setOutlineColor(color);
return true;
} else if (name == "outline_color.b") {
auto color = box.getOutlineColor();
color.b = std::clamp(static_cast<int>(value), 0, 255);
box.setOutlineColor(color);
return true;
} else if (name == "outline_color.a") {
auto color = box.getOutlineColor();
color.a = std::clamp(static_cast<int>(value), 0, 255);
box.setOutlineColor(color);
return true;
}
return false;
}
bool UIFrame::setProperty(const std::string& name, const sf::Color& value) {
if (name == "fill_color") {
box.setFillColor(value);
return true;
} else if (name == "outline_color") {
box.setOutlineColor(value);
return true;
}
return false;
}
bool UIFrame::setProperty(const std::string& name, const sf::Vector2f& value) {
if (name == "position") {
box.setPosition(value);
return true;
} else if (name == "size") {
box.setSize(value);
return true;
}
return false;
}
bool UIFrame::getProperty(const std::string& name, float& value) const {
if (name == "x") {
value = box.getPosition().x;
return true;
} else if (name == "y") {
value = box.getPosition().y;
return true;
} else if (name == "w") {
value = box.getSize().x;
return true;
} else if (name == "h") {
value = box.getSize().y;
return true;
} else if (name == "outline") {
value = box.getOutlineThickness();
return true;
} else if (name == "fill_color.r") {
value = box.getFillColor().r;
return true;
} else if (name == "fill_color.g") {
value = box.getFillColor().g;
return true;
} else if (name == "fill_color.b") {
value = box.getFillColor().b;
return true;
} else if (name == "fill_color.a") {
value = box.getFillColor().a;
return true;
} else if (name == "outline_color.r") {
value = box.getOutlineColor().r;
return true;
} else if (name == "outline_color.g") {
value = box.getOutlineColor().g;
return true;
} else if (name == "outline_color.b") {
value = box.getOutlineColor().b;
return true;
} else if (name == "outline_color.a") {
value = box.getOutlineColor().a;
return true;
}
return false;
}
bool UIFrame::getProperty(const std::string& name, sf::Color& value) const {
if (name == "fill_color") {
value = box.getFillColor();
return true;
} else if (name == "outline_color") {
value = box.getOutlineColor();
return true;
}
return false;
}
bool UIFrame::getProperty(const std::string& name, sf::Vector2f& value) const {
if (name == "position") {
value = box.getPosition();
return true;
} else if (name == "size") {
value = box.getSize();
return true;
}
return false;
}

View File

@ -1,89 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "structmember.h"
#include "IndexTexture.h"
#include "Resources.h"
#include <list>
#include "PyCallable.h"
#include "PyColor.h"
#include "PyVector.h"
#include "UIDrawable.h"
#include "UIBase.h"
//class UIFrame;
//
//typedef struct {
// PyObject_HEAD
// std::shared_ptr<UIFrame> data;
//} PyUIFrameObject;
class UIFrame: public UIDrawable
{
public:
UIFrame(float, float, float, float);
UIFrame();
~UIFrame();
sf::RectangleShape box;
float outline;
std::shared_ptr<std::vector<std::shared_ptr<UIDrawable>>> children;
bool children_need_sort = true; // Dirty flag for z_index sorting optimization
void render(sf::Vector2f, sf::RenderTarget&) override final;
void move(sf::Vector2f);
PyObjectsEnum derived_type() override final;
virtual UIDrawable* click_at(sf::Vector2f point) override final;
static PyObject* get_children(PyUIFrameObject* self, void* closure);
static PyObject* get_float_member(PyUIFrameObject* self, void* closure);
static int set_float_member(PyUIFrameObject* self, PyObject* value, void* closure);
static PyObject* get_color_member(PyUIFrameObject* self, void* closure);
static int set_color_member(PyUIFrameObject* self, PyObject* value, void* closure);
static PyObject* get_pos(PyUIFrameObject* self, void* closure);
static int set_pos(PyUIFrameObject* self, PyObject* value, void* closure);
static PyGetSetDef getsetters[];
static PyObject* repr(PyUIFrameObject* self);
static int init(PyUIFrameObject* self, PyObject* args, PyObject* kwds);
// Animation property system
bool setProperty(const std::string& name, float value) override;
bool setProperty(const std::string& name, const sf::Color& value) override;
bool setProperty(const std::string& name, const sf::Vector2f& value) override;
bool getProperty(const std::string& name, float& value) const override;
bool getProperty(const std::string& name, sf::Color& value) const override;
bool getProperty(const std::string& name, sf::Vector2f& value) const override;
};
namespace mcrfpydef {
static PyTypeObject PyUIFrameType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Frame",
.tp_basicsize = sizeof(PyUIFrameObject),
.tp_itemsize = 0,
.tp_dealloc = (destructor)[](PyObject* self)
{
PyUIFrameObject* obj = (PyUIFrameObject*)self;
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)UIFrame::repr,
//.tp_hash = NULL,
//.tp_iter
//.tp_iternext
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("docstring"),
//.tp_methods = PyUIFrame_methods,
//.tp_members = PyUIFrame_members,
.tp_getset = UIFrame::getsetters,
//.tp_base = NULL,
.tp_init = (initproc)UIFrame::init,
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
PyUIFrameObject* self = (PyUIFrameObject*)type->tp_alloc(type, 0);
if (self) self->data = std::make_shared<UIFrame>();
return (PyObject*)self;
}
};
}

File diff suppressed because it is too large Load Diff

View File

@ -1,208 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "structmember.h"
#include "IndexTexture.h"
#include "Resources.h"
#include <list>
#include "PyCallable.h"
#include "PyTexture.h"
#include "PyColor.h"
#include "PyVector.h"
#include "PyFont.h"
#include "UIGridPoint.h"
#include "UIEntity.h"
#include "UIDrawable.h"
#include "UIBase.h"
class UIGrid: public UIDrawable
{
private:
std::shared_ptr<PyTexture> ptex;
// Default cell dimensions when no texture is provided
static constexpr int DEFAULT_CELL_WIDTH = 16;
static constexpr int DEFAULT_CELL_HEIGHT = 16;
public:
UIGrid();
//UIGrid(int, int, IndexTexture*, float, float, float, float);
UIGrid(int, int, std::shared_ptr<PyTexture>, sf::Vector2f, sf::Vector2f);
void update();
void render(sf::Vector2f, sf::RenderTarget&) override final;
UIGridPoint& at(int, int);
PyObjectsEnum derived_type() override final;
//void setSprite(int);
virtual UIDrawable* click_at(sf::Vector2f point) override final;
int grid_x, grid_y;
//int grid_size; // grid sizes are implied by IndexTexture now
sf::RectangleShape box;
float center_x, center_y, zoom;
//IndexTexture* itex;
std::shared_ptr<PyTexture> getTexture();
sf::Sprite sprite, output;
sf::RenderTexture renderTexture;
std::vector<UIGridPoint> points;
std::shared_ptr<std::list<std::shared_ptr<UIEntity>>> entities;
// Property system for animations
bool setProperty(const std::string& name, float value) override;
bool setProperty(const std::string& name, const sf::Vector2f& value) override;
bool getProperty(const std::string& name, float& value) const override;
bool getProperty(const std::string& name, sf::Vector2f& value) const override;
static int init(PyUIGridObject* self, PyObject* args, PyObject* kwds);
static PyObject* get_grid_size(PyUIGridObject* self, void* closure);
static PyObject* get_grid_x(PyUIGridObject* self, void* closure);
static PyObject* get_grid_y(PyUIGridObject* self, void* closure);
static PyObject* get_position(PyUIGridObject* self, void* closure);
static int set_position(PyUIGridObject* self, PyObject* value, void* closure);
static PyObject* get_size(PyUIGridObject* self, void* closure);
static int set_size(PyUIGridObject* self, PyObject* value, void* closure);
static PyObject* get_center(PyUIGridObject* self, void* closure);
static int set_center(PyUIGridObject* self, PyObject* value, void* closure);
static PyObject* get_float_member(PyUIGridObject* self, void* closure);
static int set_float_member(PyUIGridObject* self, PyObject* value, void* closure);
static PyObject* get_texture(PyUIGridObject* self, void* closure);
static PyObject* py_at(PyUIGridObject* self, PyObject* o);
static PyMethodDef methods[];
static PyGetSetDef getsetters[];
static PyObject* get_children(PyUIGridObject* self, void* closure);
static PyObject* repr(PyUIGridObject* self);
};
typedef struct {
PyObject_HEAD
std::shared_ptr<std::list<std::shared_ptr<UIEntity>>> data;
std::shared_ptr<UIGrid> grid;
} PyUIEntityCollectionObject;
class UIEntityCollection {
public:
static PySequenceMethods sqmethods;
static PyMappingMethods mpmethods;
static PyObject* append(PyUIEntityCollectionObject* self, PyObject* o);
static PyObject* extend(PyUIEntityCollectionObject* self, PyObject* o);
static PyObject* remove(PyUIEntityCollectionObject* self, PyObject* o);
static PyObject* index_method(PyUIEntityCollectionObject* self, PyObject* value);
static PyObject* count(PyUIEntityCollectionObject* self, PyObject* value);
static PyMethodDef methods[];
static PyObject* repr(PyUIEntityCollectionObject* self);
static int init(PyUIEntityCollectionObject* self, PyObject* args, PyObject* kwds);
static PyObject* iter(PyUIEntityCollectionObject* self);
static Py_ssize_t len(PyUIEntityCollectionObject* self);
static PyObject* getitem(PyUIEntityCollectionObject* self, Py_ssize_t index);
static int setitem(PyUIEntityCollectionObject* self, Py_ssize_t index, PyObject* value);
static int contains(PyUIEntityCollectionObject* self, PyObject* value);
static PyObject* concat(PyUIEntityCollectionObject* self, PyObject* other);
static PyObject* inplace_concat(PyUIEntityCollectionObject* self, PyObject* other);
static PyObject* subscript(PyUIEntityCollectionObject* self, PyObject* key);
static int ass_subscript(PyUIEntityCollectionObject* self, PyObject* key, PyObject* value);
};
typedef struct {
PyObject_HEAD
std::shared_ptr<std::list<std::shared_ptr<UIEntity>>> data;
int index;
int start_size;
} PyUIEntityCollectionIterObject;
class UIEntityCollectionIter {
public:
static int init(PyUIEntityCollectionIterObject* self, PyObject* args, PyObject* kwds);
static PyObject* next(PyUIEntityCollectionIterObject* self);
static PyObject* repr(PyUIEntityCollectionIterObject* self);
static PyObject* getitem(PyUIEntityCollectionObject* self, Py_ssize_t index);
};
namespace mcrfpydef {
static PyTypeObject PyUIGridType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Grid",
.tp_basicsize = sizeof(PyUIGridObject),
.tp_itemsize = 0,
//.tp_dealloc = (destructor)[](PyObject* self)
//{
// PyUIGridObject* obj = (PyUIGridObject*)self;
// obj->data.reset();
// Py_TYPE(self)->tp_free(self);
//},
//TODO - PyUIGrid REPR def:
.tp_repr = (reprfunc)UIGrid::repr,
//.tp_hash = NULL,
//.tp_iter
//.tp_iternext
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("docstring"),
.tp_methods = UIGrid::methods,
//.tp_members = UIGrid::members,
.tp_getset = UIGrid::getsetters,
//.tp_base = NULL,
.tp_init = (initproc)UIGrid::init,
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
PyUIGridObject* self = (PyUIGridObject*)type->tp_alloc(type, 0);
if (self) self->data = std::make_shared<UIGrid>();
return (PyObject*)self;
}
};
static PyTypeObject PyUIEntityCollectionIterType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.UIEntityCollectionIter",
.tp_basicsize = sizeof(PyUIEntityCollectionIterObject),
.tp_itemsize = 0,
.tp_dealloc = (destructor)[](PyObject* self)
{
PyUIEntityCollectionIterObject* obj = (PyUIEntityCollectionIterObject*)self;
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)UIEntityCollectionIter::repr,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("Iterator for a collection of UI objects"),
.tp_iter = PyObject_SelfIter,
.tp_iternext = (iternextfunc)UIEntityCollectionIter::next,
//.tp_getset = UIEntityCollection::getset,
.tp_init = (initproc)UIEntityCollectionIter::init, // just raise an exception
.tp_alloc = PyType_GenericAlloc,
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
PyErr_SetString(PyExc_TypeError, "UICollection cannot be instantiated: a C++ data source is required.");
return NULL;
}
};
static PyTypeObject PyUIEntityCollectionType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.EntityCollection",
.tp_basicsize = sizeof(PyUIEntityCollectionObject),
.tp_itemsize = 0,
.tp_dealloc = (destructor)[](PyObject* self)
{
PyUIEntityCollectionObject* obj = (PyUIEntityCollectionObject*)self;
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)UIEntityCollection::repr,
.tp_as_sequence = &UIEntityCollection::sqmethods,
.tp_as_mapping = &UIEntityCollection::mpmethods,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("Iterable, indexable collection of Entities"),
.tp_iter = (getiterfunc)UIEntityCollection::iter,
.tp_methods = UIEntityCollection::methods, // append, remove
//.tp_getset = UIEntityCollection::getset,
.tp_init = (initproc)UIEntityCollection::init, // just raise an exception
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
// Does PyUIEntityCollectionType need __new__ if it's not supposed to be instantiable by the user?
// Should I just raise an exception? Or is the uninitialized shared_ptr enough of a blocker?
PyErr_SetString(PyExc_TypeError, "EntityCollection cannot be instantiated: a C++ data source is required.");
return NULL;
}
};
}

View File

@ -1,158 +0,0 @@
#include "UIGridPoint.h"
UIGridPoint::UIGridPoint()
: color(1.0f, 1.0f, 1.0f), color_overlay(0.0f, 0.0f, 0.0f), walkable(false), transparent(false),
tilesprite(-1), tile_overlay(-1), uisprite(-1)
{}
// Utility function to convert sf::Color to PyObject*
PyObject* sfColor_to_PyObject(sf::Color color) {
return Py_BuildValue("(iiii)", color.r, color.g, color.b, color.a);
}
// Utility function to convert PyObject* to sf::Color
sf::Color PyObject_to_sfColor(PyObject* obj) {
int r, g, b, a = 255; // Default alpha to fully opaque if not specified
if (!PyArg_ParseTuple(obj, "iii|i", &r, &g, &b, &a)) {
return sf::Color(); // Return default color on parse error
}
return sf::Color(r, g, b, a);
}
PyObject* UIGridPoint::get_color(PyUIGridPointObject* self, void* closure) {
if (reinterpret_cast<long>(closure) == 0) { // color
return sfColor_to_PyObject(self->data->color);
} else { // color_overlay
return sfColor_to_PyObject(self->data->color_overlay);
}
}
int UIGridPoint::set_color(PyUIGridPointObject* self, PyObject* value, void* closure) {
sf::Color color = PyObject_to_sfColor(value);
if (reinterpret_cast<long>(closure) == 0) { // color
self->data->color = color;
} else { // color_overlay
self->data->color_overlay = color;
}
return 0;
}
PyObject* UIGridPoint::get_bool_member(PyUIGridPointObject* self, void* closure) {
if (reinterpret_cast<long>(closure) == 0) { // walkable
return PyBool_FromLong(self->data->walkable);
} else { // transparent
return PyBool_FromLong(self->data->transparent);
}
}
int UIGridPoint::set_bool_member(PyUIGridPointObject* self, PyObject* value, void* closure) {
if (value == Py_True) {
if (reinterpret_cast<long>(closure) == 0) { // walkable
self->data->walkable = true;
} else { // transparent
self->data->transparent = true;
}
} else if (value == Py_False) {
if (reinterpret_cast<long>(closure) == 0) { // walkable
self->data->walkable = false;
} else { // transparent
self->data->transparent = false;
}
} else {
PyErr_SetString(PyExc_ValueError, "Expected a boolean value");
return -1;
}
return 0;
}
PyObject* UIGridPoint::get_int_member(PyUIGridPointObject* self, void* closure) {
switch(reinterpret_cast<long>(closure)) {
case 0: return PyLong_FromLong(self->data->tilesprite);
case 1: return PyLong_FromLong(self->data->tile_overlay);
case 2: return PyLong_FromLong(self->data->uisprite);
default: PyErr_SetString(PyExc_RuntimeError, "Invalid closure"); return nullptr;
}
}
int UIGridPoint::set_int_member(PyUIGridPointObject* self, PyObject* value, void* closure) {
long val = PyLong_AsLong(value);
if (PyErr_Occurred()) return -1;
switch(reinterpret_cast<long>(closure)) {
case 0: self->data->tilesprite = val; break;
case 1: self->data->tile_overlay = val; break;
case 2: self->data->uisprite = val; break;
default: PyErr_SetString(PyExc_RuntimeError, "Invalid closure"); return -1;
}
return 0;
}
PyGetSetDef UIGridPoint::getsetters[] = {
{"color", (getter)UIGridPoint::get_color, (setter)UIGridPoint::set_color, "GridPoint color", (void*)0},
{"color_overlay", (getter)UIGridPoint::get_color, (setter)UIGridPoint::set_color, "GridPoint color overlay", (void*)1},
{"walkable", (getter)UIGridPoint::get_bool_member, (setter)UIGridPoint::set_bool_member, "Is the GridPoint walkable", (void*)0},
{"transparent", (getter)UIGridPoint::get_bool_member, (setter)UIGridPoint::set_bool_member, "Is the GridPoint transparent", (void*)1},
{"tilesprite", (getter)UIGridPoint::get_int_member, (setter)UIGridPoint::set_int_member, "Tile sprite index", (void*)0},
{"tile_overlay", (getter)UIGridPoint::get_int_member, (setter)UIGridPoint::set_int_member, "Tile overlay sprite index", (void*)1},
{"uisprite", (getter)UIGridPoint::get_int_member, (setter)UIGridPoint::set_int_member, "UI sprite index", (void*)2},
{NULL} /* Sentinel */
};
PyObject* UIGridPoint::repr(PyUIGridPointObject* self) {
std::ostringstream ss;
if (!self->data) ss << "<GridPoint (invalid internal object)>";
else {
auto gp = self->data;
ss << "<GridPoint (walkable=" << (gp->walkable ? "True" : "False") << ", transparent=" << (gp->transparent ? "True" : "False") <<
", tilesprite=" << gp->tilesprite << ", tile_overlay=" << gp->tile_overlay << ", uisprite=" << gp->uisprite <<
")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
PyObject* UIGridPointState::get_bool_member(PyUIGridPointStateObject* self, void* closure) {
if (reinterpret_cast<long>(closure) == 0) { // visible
return PyBool_FromLong(self->data->visible);
} else { // discovered
return PyBool_FromLong(self->data->discovered);
}
}
int UIGridPointState::set_bool_member(PyUIGridPointStateObject* self, PyObject* value, void* closure) {
if (!PyBool_Check(value)) {
PyErr_SetString(PyExc_TypeError, "Value must be a boolean");
return -1;
}
int truthValue = PyObject_IsTrue(value);
if (truthValue < 0) {
return -1; // PyObject_IsTrue returns -1 on error
}
if (reinterpret_cast<long>(closure) == 0) { // visible
self->data->visible = truthValue;
} else { // discovered
self->data->discovered = truthValue;
}
return 0;
}
PyGetSetDef UIGridPointState::getsetters[] = {
{"visible", (getter)UIGridPointState::get_bool_member, (setter)UIGridPointState::set_bool_member, "Is the GridPointState visible", (void*)0},
{"discovered", (getter)UIGridPointState::get_bool_member, (setter)UIGridPointState::set_bool_member, "Has the GridPointState been discovered", (void*)1},
{NULL} /* Sentinel */
};
PyObject* UIGridPointState::repr(PyUIGridPointStateObject* self) {
std::ostringstream ss;
if (!self->data) ss << "<GridPointState (invalid internal object)>";
else {
auto gps = self->data;
ss << "<GridPointState (visible=" << (gps->visible ? "True" : "False") << ", discovered=" << (gps->discovered ? "True" : "False") <<
")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}

View File

@ -1,92 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "structmember.h"
#include "IndexTexture.h"
#include "Resources.h"
#include <list>
#include "PyCallable.h"
#include "PyTexture.h"
#include "PyColor.h"
#include "PyVector.h"
#include "PyFont.h"
static PyObject* sfColor_to_PyObject(sf::Color color);
static sf::Color PyObject_to_sfColor(PyObject* obj);
class UIGrid;
class UIEntity;
class UIGridPoint;
class UIGridPointState;
typedef struct {
PyObject_HEAD
UIGridPoint* data;
std::shared_ptr<UIGrid> grid;
} PyUIGridPointObject;
typedef struct {
PyObject_HEAD
UIGridPointState* data;
std::shared_ptr<UIGrid> grid;
std::shared_ptr<UIEntity> entity;
} PyUIGridPointStateObject;
// UIGridPoint - revised grid data for each point
class UIGridPoint
{
public:
sf::Color color, color_overlay;
bool walkable, transparent;
int tilesprite, tile_overlay, uisprite;
UIGridPoint();
static int set_int_member(PyUIGridPointObject* self, PyObject* value, void* closure);
static PyGetSetDef getsetters[];
static PyObject* get_color(PyUIGridPointObject* self, void* closure);
static PyObject* get_int_member(PyUIGridPointObject* self, void* closure);
static int set_bool_member(PyUIGridPointObject* self, PyObject* value, void* closure);
static PyObject* get_bool_member(PyUIGridPointObject* self, void* closure);
static int set_color(PyUIGridPointObject* self, PyObject* value, void* closure);
static PyObject* repr(PyUIGridPointObject* self);
};
// UIGridPointState - entity-specific info for each cell
class UIGridPointState
{
public:
bool visible, discovered;
static PyObject* get_bool_member(PyUIGridPointStateObject* self, void* closure);
static int set_bool_member(PyUIGridPointStateObject* self, PyObject* value, void* closure);
static PyGetSetDef getsetters[];
static PyObject* repr(PyUIGridPointStateObject* self);
};
namespace mcrfpydef {
static PyTypeObject PyUIGridPointType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.GridPoint",
.tp_basicsize = sizeof(PyUIGridPointObject),
.tp_itemsize = 0,
.tp_repr = (reprfunc)UIGridPoint::repr,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "UIGridPoint object",
.tp_getset = UIGridPoint::getsetters,
//.tp_init = (initproc)PyUIGridPoint_init, // TODO Define the init function
.tp_new = NULL, // Prevent instantiation from Python - Issue #12
};
static PyTypeObject PyUIGridPointStateType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.GridPointState",
.tp_basicsize = sizeof(PyUIGridPointStateObject),
.tp_itemsize = 0,
.tp_repr = (reprfunc)UIGridPointState::repr,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "UIGridPointState object", // TODO: Add PyUIGridPointState tp_init
.tp_getset = UIGridPointState::getsetters,
.tp_new = NULL, // Prevent instantiation from Python - Issue #12
};
}

View File

@ -1,397 +0,0 @@
#include "UISprite.h"
#include "GameEngine.h"
#include "PyVector.h"
UIDrawable* UISprite::click_at(sf::Vector2f point)
{
if (click_callable)
{
if(sprite.getGlobalBounds().contains(point)) return this;
}
return NULL;
}
UISprite::UISprite() {}
UISprite::UISprite(std::shared_ptr<PyTexture> _ptex, int _sprite_index, sf::Vector2f _pos, float _scale)
: ptex(_ptex), sprite_index(_sprite_index)
{
sprite = ptex->sprite(sprite_index, _pos, sf::Vector2f(_scale, _scale));
}
/*
void UISprite::render(sf::Vector2f offset)
{
sprite.move(offset);
Resources::game->getWindow().draw(sprite);
sprite.move(-offset);
}
*/
void UISprite::render(sf::Vector2f offset, sf::RenderTarget& target)
{
sprite.move(offset);
target.draw(sprite);
sprite.move(-offset);
}
void UISprite::setPosition(sf::Vector2f pos)
{
sprite.setPosition(pos);
}
void UISprite::setScale(sf::Vector2f s)
{
sprite.setScale(s);
}
void UISprite::setTexture(std::shared_ptr<PyTexture> _ptex, int _sprite_index)
{
ptex = _ptex;
if (_sprite_index != -1) // if you are changing textures, there's a good chance you need a new index too
sprite_index = _sprite_index;
sprite = ptex->sprite(sprite_index, sprite.getPosition(), sprite.getScale());
}
void UISprite::setSpriteIndex(int _sprite_index)
{
sprite_index = _sprite_index;
sprite = ptex->sprite(sprite_index, sprite.getPosition(), sprite.getScale());
}
sf::Vector2f UISprite::getScale() const
{
return sprite.getScale();
}
sf::Vector2f UISprite::getPosition()
{
return sprite.getPosition();
}
std::shared_ptr<PyTexture> UISprite::getTexture()
{
return ptex;
}
int UISprite::getSpriteIndex()
{
return sprite_index;
}
PyObjectsEnum UISprite::derived_type()
{
return PyObjectsEnum::UISPRITE;
}
PyObject* UISprite::get_float_member(PyUISpriteObject* self, void* closure)
{
auto member_ptr = reinterpret_cast<long>(closure);
if (member_ptr == 0)
return PyFloat_FromDouble(self->data->getPosition().x);
else if (member_ptr == 1)
return PyFloat_FromDouble(self->data->getPosition().y);
else if (member_ptr == 2)
return PyFloat_FromDouble(self->data->getScale().x); // scale X and Y are identical, presently
else if (member_ptr == 3)
return PyFloat_FromDouble(self->data->getScale().x); // scale_x
else if (member_ptr == 4)
return PyFloat_FromDouble(self->data->getScale().y); // scale_y
else
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return nullptr;
}
}
int UISprite::set_float_member(PyUISpriteObject* self, PyObject* value, void* closure)
{
float val;
auto member_ptr = reinterpret_cast<long>(closure);
if (PyFloat_Check(value))
{
val = PyFloat_AsDouble(value);
}
else if (PyLong_Check(value))
{
val = PyLong_AsLong(value);
}
else
{
PyErr_SetString(PyExc_TypeError, "Value must be a floating point number.");
return -1;
}
if (member_ptr == 0) //x
self->data->setPosition(sf::Vector2f(val, self->data->getPosition().y));
else if (member_ptr == 1) //y
self->data->setPosition(sf::Vector2f(self->data->getPosition().x, val));
else if (member_ptr == 2) // scale (uniform)
self->data->setScale(sf::Vector2f(val, val));
else if (member_ptr == 3) // scale_x
self->data->setScale(sf::Vector2f(val, self->data->getScale().y));
else if (member_ptr == 4) // scale_y
self->data->setScale(sf::Vector2f(self->data->getScale().x, val));
return 0;
}
PyObject* UISprite::get_int_member(PyUISpriteObject* self, void* closure)
{
auto member_ptr = reinterpret_cast<long>(closure);
if (true) {}
else
{
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
return nullptr;
}
return PyLong_FromDouble(self->data->getSpriteIndex());
}
int UISprite::set_int_member(PyUISpriteObject* self, PyObject* value, void* closure)
{
int val;
auto member_ptr = reinterpret_cast<long>(closure);
if (PyLong_Check(value))
{
val = PyLong_AsLong(value);
}
else
{
PyErr_SetString(PyExc_TypeError, "Value must be an integer.");
return -1;
}
// Validate sprite index is within texture bounds
auto texture = self->data->getTexture();
if (texture) {
int sprite_count = texture->getSpriteCount();
if (val < 0 || val >= sprite_count) {
PyErr_Format(PyExc_ValueError,
"Sprite index %d out of range. Texture has %d sprites (0-%d)",
val, sprite_count, sprite_count - 1);
return -1;
}
}
self->data->setSpriteIndex(val);
return 0;
}
PyObject* UISprite::get_texture(PyUISpriteObject* self, void* closure)
{
return self->data->getTexture()->pyObject();
}
int UISprite::set_texture(PyUISpriteObject* self, PyObject* value, void* closure)
{
// Check if value is a Texture instance
if (!PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))) {
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance");
return -1;
}
// Get the texture from the Python object
auto pytexture = (PyTextureObject*)value;
if (!pytexture->data) {
PyErr_SetString(PyExc_ValueError, "Invalid texture object");
return -1;
}
// Update the sprite's texture
self->data->setTexture(pytexture->data);
return 0;
}
PyObject* UISprite::get_pos(PyUISpriteObject* self, void* closure)
{
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
auto obj = (PyVectorObject*)type->tp_alloc(type, 0);
if (obj) {
auto pos = self->data->getPosition();
obj->data = sf::Vector2f(pos.x, pos.y);
}
return (PyObject*)obj;
}
int UISprite::set_pos(PyUISpriteObject* self, PyObject* value, void* closure)
{
PyVectorObject* vec = PyVector::from_arg(value);
if (!vec) {
PyErr_SetString(PyExc_TypeError, "pos must be a Vector or convertible to Vector");
return -1;
}
self->data->setPosition(vec->data);
return 0;
}
PyGetSetDef UISprite::getsetters[] = {
{"x", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "X coordinate of top-left corner", (void*)0},
{"y", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Y coordinate of top-left corner", (void*)1},
{"scale", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Uniform size factor", (void*)2},
{"scale_x", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Horizontal scale factor", (void*)3},
{"scale_y", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Vertical scale factor", (void*)4},
{"sprite_index", (getter)UISprite::get_int_member, (setter)UISprite::set_int_member, "Which sprite on the texture is shown", NULL},
{"sprite_number", (getter)UISprite::get_int_member, (setter)UISprite::set_int_member, "Which sprite on the texture is shown (deprecated: use sprite_index)", NULL},
{"texture", (getter)UISprite::get_texture, (setter)UISprite::set_texture, "Texture object", NULL},
{"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UISPRITE},
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UISPRITE},
{"pos", (getter)UISprite::get_pos, (setter)UISprite::set_pos, "Position as a Vector", NULL},
{NULL}
};
PyObject* UISprite::repr(PyUISpriteObject* self)
{
std::ostringstream ss;
if (!self->data) ss << "<Sprite (invalid internal object)>";
else {
//auto sprite = self->data->sprite;
ss << "<Sprite (x=" << self->data->getPosition().x << ", y=" << self->data->getPosition().y << ", " <<
"scale=" << self->data->getScale().x << ", " <<
"sprite_index=" << self->data->getSpriteIndex() << ")>";
}
std::string repr_str = ss.str();
return PyUnicode_DecodeUTF8(repr_str.c_str(), repr_str.size(), "replace");
}
int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds)
{
//std::cout << "Init called\n";
static const char* keywords[] = { "x", "y", "texture", "sprite_index", "scale", nullptr };
float x = 0.0f, y = 0.0f, scale = 1.0f;
int sprite_index = 0;
PyObject* texture = NULL;
// First try to parse as (x, y, texture, ...)
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffOif",
const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &scale))
{
PyErr_Clear(); // Clear the error
// Try to parse as ((x,y), texture, ...) or (Vector, texture, ...)
PyObject* pos_obj = nullptr;
const char* alt_keywords[] = { "pos", "texture", "sprite_index", "scale", nullptr };
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOif", const_cast<char**>(alt_keywords),
&pos_obj, &texture, &sprite_index, &scale))
{
return -1;
}
// Convert position argument to x, y
if (pos_obj) {
PyVectorObject* vec = PyVector::from_arg(pos_obj);
if (!vec) {
PyErr_SetString(PyExc_TypeError, "First argument must be a tuple (x, y) or Vector when not providing x, y separately");
return -1;
}
x = vec->data.x;
y = vec->data.y;
}
}
// Handle texture - allow None or use default
std::shared_ptr<PyTexture> texture_ptr = nullptr;
if (texture != NULL && texture != Py_None && !PyObject_IsInstance(texture, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture"))){
PyErr_SetString(PyExc_TypeError, "texture must be a mcrfpy.Texture instance or None");
return -1;
} else if (texture != NULL && texture != Py_None) {
auto pytexture = (PyTextureObject*)texture;
texture_ptr = pytexture->data;
} else {
// Use default texture when None or not provided
texture_ptr = McRFPy_API::default_texture;
}
if (!texture_ptr) {
PyErr_SetString(PyExc_RuntimeError, "No texture provided and no default texture available");
return -1;
}
self->data = std::make_shared<UISprite>(texture_ptr, sprite_index, sf::Vector2f(x, y), scale);
self->data->setPosition(sf::Vector2f(x, y));
return 0;
}
// Property system implementation for animations
bool UISprite::setProperty(const std::string& name, float value) {
if (name == "x") {
sprite.setPosition(sf::Vector2f(value, sprite.getPosition().y));
return true;
}
else if (name == "y") {
sprite.setPosition(sf::Vector2f(sprite.getPosition().x, value));
return true;
}
else if (name == "scale") {
sprite.setScale(sf::Vector2f(value, value));
return true;
}
else if (name == "scale_x") {
sprite.setScale(sf::Vector2f(value, sprite.getScale().y));
return true;
}
else if (name == "scale_y") {
sprite.setScale(sf::Vector2f(sprite.getScale().x, value));
return true;
}
else if (name == "z_index") {
z_index = static_cast<int>(value);
return true;
}
return false;
}
bool UISprite::setProperty(const std::string& name, int value) {
if (name == "sprite_index" || name == "sprite_number") {
setSpriteIndex(value);
return true;
}
else if (name == "z_index") {
z_index = value;
return true;
}
return false;
}
bool UISprite::getProperty(const std::string& name, float& value) const {
if (name == "x") {
value = sprite.getPosition().x;
return true;
}
else if (name == "y") {
value = sprite.getPosition().y;
return true;
}
else if (name == "scale") {
value = sprite.getScale().x; // Assuming uniform scale
return true;
}
else if (name == "scale_x") {
value = sprite.getScale().x;
return true;
}
else if (name == "scale_y") {
value = sprite.getScale().y;
return true;
}
else if (name == "z_index") {
value = static_cast<float>(z_index);
return true;
}
return false;
}
bool UISprite::getProperty(const std::string& name, int& value) const {
if (name == "sprite_index" || name == "sprite_number") {
value = sprite_index;
return true;
}
else if (name == "z_index") {
value = z_index;
return true;
}
return false;
}

View File

@ -1,98 +0,0 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "structmember.h"
#include "IndexTexture.h"
#include "Resources.h"
#include <list>
#include "PyCallable.h"
#include "PyTexture.h"
#include "PyColor.h"
#include "PyVector.h"
#include "PyFont.h"
#include "UIDrawable.h"
#include "UIBase.h"
class UISprite: public UIDrawable
{
private:
int sprite_index;
sf::Sprite sprite;
protected:
std::shared_ptr<PyTexture> ptex;
public:
UISprite();
UISprite(std::shared_ptr<PyTexture>, int, sf::Vector2f, float);
void update();
void render(sf::Vector2f, sf::RenderTarget&) override final;
virtual UIDrawable* click_at(sf::Vector2f point) override final;
//void render(sf::Vector2f, sf::RenderTexture&);
void setPosition(sf::Vector2f);
sf::Vector2f getPosition();
void setScale(sf::Vector2f);
sf::Vector2f getScale() const;
void setSpriteIndex(int);
int getSpriteIndex();
void setTexture(std::shared_ptr<PyTexture> _ptex, int _sprite_index=-1);
std::shared_ptr<PyTexture> getTexture();
PyObjectsEnum derived_type() override final;
// Property system for animations
bool setProperty(const std::string& name, float value) override;
bool setProperty(const std::string& name, int value) override;
bool getProperty(const std::string& name, float& value) const override;
bool getProperty(const std::string& name, int& value) const override;
static PyObject* get_float_member(PyUISpriteObject* self, void* closure);
static int set_float_member(PyUISpriteObject* self, PyObject* value, void* closure);
static PyObject* get_int_member(PyUISpriteObject* self, void* closure);
static int set_int_member(PyUISpriteObject* self, PyObject* value, void* closure);
static PyObject* get_texture(PyUISpriteObject* self, void* closure);
static int set_texture(PyUISpriteObject* self, PyObject* value, void* closure);
static PyObject* get_pos(PyUISpriteObject* self, void* closure);
static int set_pos(PyUISpriteObject* self, PyObject* value, void* closure);
static PyGetSetDef getsetters[];
static PyObject* repr(PyUISpriteObject* self);
static int init(PyUISpriteObject* self, PyObject* args, PyObject* kwds);
};
namespace mcrfpydef {
static PyTypeObject PyUISpriteType = {
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
.tp_name = "mcrfpy.Sprite",
.tp_basicsize = sizeof(PyUISpriteObject),
.tp_itemsize = 0,
.tp_dealloc = (destructor)[](PyObject* self)
{
PyUISpriteObject* obj = (PyUISpriteObject*)self;
// release reference to font object
//if (obj->texture) Py_DECREF(obj->texture);
obj->data.reset();
Py_TYPE(self)->tp_free(self);
},
.tp_repr = (reprfunc)UISprite::repr,
//.tp_hash = NULL,
//.tp_iter
//.tp_iternext
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = PyDoc_STR("docstring"),
//.tp_methods = PyUIFrame_methods,
//.tp_members = PyUIFrame_members,
.tp_getset = UISprite::getsetters,
//.tp_base = NULL,
.tp_init = (initproc)UISprite::init,
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
{
PyUISpriteObject* self = (PyUISpriteObject*)type->tp_alloc(type, 0);
//if (self) self->data = std::make_shared<UICaption>();
return (PyObject*)self;
}
};
}

View File

@ -154,10 +154,10 @@ void UITestScene::doAction(std::string name, std::string type)
*/
}
void UITestScene::render()
void UITestScene::sRender()
{
game->getRenderTarget().clear();
game->getRenderTarget().draw(text);
game->getWindow().clear();
game->getWindow().draw(text);
// draw all UI elements
//for (auto e: ui_elements)
@ -175,7 +175,7 @@ void UITestScene::render()
//e1.render(sf::Vector2f(-100, -100));
// Display is handled by GameEngine
game->getWindow().display();
//McRFPy_API::REPL();
}

View File

@ -18,5 +18,5 @@ public:
UITestScene(GameEngine*);
void update() override final;
void doAction(std::string, std::string) override final;
void render() override final;
void sRender() override final;
};

View File

@ -1,204 +1,8 @@
#include <SFML/Graphics.hpp>
#include "GameEngine.h"
#include "CommandLineParser.h"
#include "McRogueFaceConfig.h"
#include "McRFPy_API.h"
#include "PyFont.h"
#include "PyTexture.h"
#include <Python.h>
#include <iostream>
#include <filesystem>
// Forward declarations
int run_game_engine(const McRogueFaceConfig& config);
int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv[]);
int main(int argc, char* argv[])
int main()
{
McRogueFaceConfig config;
CommandLineParser parser(argc, argv);
// Parse arguments
auto parse_result = parser.parse(config);
if (parse_result.should_exit) {
return parse_result.exit_code;
}
// Special handling for -m module: let Python handle modules properly
if (!config.python_module.empty()) {
config.python_mode = true;
}
// Initialize based on configuration
if (config.python_mode) {
return run_python_interpreter(config, argc, argv);
} else {
return run_game_engine(config);
}
}
int run_game_engine(const McRogueFaceConfig& config)
{
GameEngine g(config);
GameEngine g;
g.run();
return 0;
}
int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv[])
{
// Create a game engine with the requested configuration
GameEngine* engine = new GameEngine(config);
// Initialize Python with configuration
McRFPy_API::init_python_with_config(config, argc, argv);
// Import mcrfpy module and store reference
McRFPy_API::mcrf_module = PyImport_ImportModule("mcrfpy");
if (!McRFPy_API::mcrf_module) {
PyErr_Print();
std::cerr << "Failed to import mcrfpy module" << std::endl;
} else {
// Set up default_font and default_texture if not already done
if (!McRFPy_API::default_font) {
McRFPy_API::default_font = std::make_shared<PyFont>("assets/JetbrainsMono.ttf");
McRFPy_API::default_texture = std::make_shared<PyTexture>("assets/kenney_tinydungeon.png", 16, 16);
}
PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_font", McRFPy_API::default_font->pyObject());
PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_texture", McRFPy_API::default_texture->pyObject());
}
// Handle different Python modes
if (!config.python_command.empty()) {
// Execute command from -c
if (config.interactive_mode) {
// Use PyRun_String to catch SystemExit
PyObject* main_module = PyImport_AddModule("__main__");
PyObject* main_dict = PyModule_GetDict(main_module);
PyObject* result_obj = PyRun_String(config.python_command.c_str(),
Py_file_input, main_dict, main_dict);
if (result_obj == NULL) {
// Check if it's SystemExit
if (PyErr_Occurred()) {
PyObject *type, *value, *traceback;
PyErr_Fetch(&type, &value, &traceback);
// If it's SystemExit and we're in interactive mode, clear it
if (PyErr_GivenExceptionMatches(type, PyExc_SystemExit)) {
PyErr_Clear();
} else {
// Re-raise other exceptions
PyErr_Restore(type, value, traceback);
PyErr_Print();
}
Py_XDECREF(type);
Py_XDECREF(value);
Py_XDECREF(traceback);
}
} else {
Py_DECREF(result_obj);
}
// Continue to interactive mode below
} else {
int result = PyRun_SimpleString(config.python_command.c_str());
Py_Finalize();
delete engine;
return result;
}
}
else if (!config.python_module.empty()) {
// Execute module using runpy
std::string run_module_code =
"import sys\n"
"import runpy\n"
"sys.argv = ['" + config.python_module + "'";
for (const auto& arg : config.script_args) {
run_module_code += ", '" + arg + "'";
}
run_module_code += "]\n";
run_module_code += "runpy.run_module('" + config.python_module + "', run_name='__main__', alter_sys=True)\n";
int result = PyRun_SimpleString(run_module_code.c_str());
Py_Finalize();
delete engine;
return result;
}
else if (!config.script_path.empty()) {
// Execute script file
FILE* fp = fopen(config.script_path.string().c_str(), "r");
if (!fp) {
std::cerr << "mcrogueface: can't open file '" << config.script_path << "': ";
std::cerr << "[Errno " << errno << "] " << strerror(errno) << std::endl;
return 1;
}
// Set up sys.argv
wchar_t** python_argv = new wchar_t*[config.script_args.size() + 1];
python_argv[0] = Py_DecodeLocale(config.script_path.string().c_str(), nullptr);
for (size_t i = 0; i < config.script_args.size(); i++) {
python_argv[i + 1] = Py_DecodeLocale(config.script_args[i].c_str(), nullptr);
}
PySys_SetArgvEx(config.script_args.size() + 1, python_argv, 0);
int result = PyRun_SimpleFile(fp, config.script_path.string().c_str());
fclose(fp);
// Clean up
for (size_t i = 0; i <= config.script_args.size(); i++) {
PyMem_RawFree(python_argv[i]);
}
delete[] python_argv;
if (config.interactive_mode) {
// Even if script had SystemExit, continue to interactive mode
if (result != 0) {
// Check if it was SystemExit
if (PyErr_Occurred()) {
PyObject *type, *value, *traceback;
PyErr_Fetch(&type, &value, &traceback);
if (PyErr_GivenExceptionMatches(type, PyExc_SystemExit)) {
PyErr_Clear();
result = 0; // Don't exit with error
} else {
PyErr_Restore(type, value, traceback);
PyErr_Print();
}
Py_XDECREF(type);
Py_XDECREF(value);
Py_XDECREF(traceback);
}
}
// Run interactive mode after script
PyRun_InteractiveLoop(stdin, "<stdin>");
}
// Run the game engine after script execution
engine->run();
Py_Finalize();
delete engine;
return result;
}
else if (config.interactive_mode) {
// Interactive Python interpreter (only if explicitly requested with -i)
Py_InspectFlag = 1;
PyRun_InteractiveLoop(stdin, "<stdin>");
Py_Finalize();
delete engine;
return 0;
}
else if (!config.exec_scripts.empty()) {
// With --exec, run the game engine after scripts execute
engine->run();
Py_Finalize();
delete engine;
return 0;
}
delete engine;
return 0;
}

View File

@ -1,539 +0,0 @@
import mcrfpy
import random
from cos_itemdata import itemdata
#t = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
t = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
#def iterable_entities(grid):
# """Workaround for UIEntityCollection bug; see issue #72"""
# entities = []
# for i in range(len(grid.entities)):
# entities.append(grid.entities[i])
# return entities
class COSEntity(): #mcrfpy.Entity): # Fake mcrfpy.Entity integration; engine bugs workarounds
def __init__(self, g:mcrfpy.Grid, x=0, y=0, sprite_num=86, *, game):
#self.e = mcrfpy.Entity((x, y), t, sprite_num)
#super().__init__((x, y), t, sprite_num)
self._entity = mcrfpy.Entity((x, y), t, sprite_num)
#grid.entities.append(self.e)
self.grid = g
#g.entities.append(self._entity)
self.game = game
self.game.add_entity(self)
## Wrapping mcfrpy.Entity properties to emulate derived class... see issue #76
@property
def draw_pos(self):
return self._entity.draw_pos
@draw_pos.setter
def draw_pos(self, value):
self._entity.draw_pos = value
@property
def sprite_number(self):
return self._entity.sprite_number
@sprite_number.setter
def sprite_number(self, value):
self._entity.sprite_number = value
def __repr__(self):
return f"<{self.__class__.__name__} ({self.draw_pos})>"
def die(self):
# ugly workaround! grid.entities isn't really iterable (segfaults)
for i in range(len(self.grid.entities)):
e = self.grid.entities[i]
if e == self._entity:
#if e == self:
self.grid.entities.remove(i)
break
else:
print(f"!!! {self!r} wasn't removed from grid on call to die()")
def bump(self, other, dx, dy, test=False):
raise NotImplementedError
def do_move(self, tx, ty):
"""Base class method to move this entity
Assumes try_move succeeded, for everyone!
from: self._entity.draw_pos
to: (tx, ty)
calls ev_exit for every entity at (draw_pos)
calls ev_enter for every entity at (tx, ty)
"""
old_pos = self.draw_pos
self.draw_pos = (tx, ty)
for e in self.game.entities:
if e is self: continue
if e.draw_pos == old_pos: e.ev_exit(self)
for e in self.game.entities:
if e is self: continue
if e.draw_pos == (tx, ty): e.ev_enter(self)
def act(self):
pass
def ev_enter(self, other):
pass
def ev_exit(self, other):
pass
def try_move(self, dx, dy, test=False):
x_max, y_max = self.grid.grid_size
tx, ty = int(self.draw_pos[0] + dx), int(self.draw_pos[1] + dy)
#for e in iterable_entities(self.grid):
# sorting entities to test against the boulder instead of the button when they overlap.
for e in sorted(self.game.entities, key = lambda i: i.draw_order, reverse = True):
if e.draw_pos == (tx, ty):
#print(f"bumping {e}")
return e.bump(self, dx, dy)
if tx < 0 or tx >= x_max:
return False
if ty < 0 or ty >= y_max:
return False
if self.grid.at((tx, ty)).walkable == True:
if not test:
#self.draw_pos = (tx, ty)
self.do_move(tx, ty)
return True
else:
#print("Bonk")
return False
def _relative_move(self, dx, dy):
tx, ty = int(self.draw_pos[0] + dx), int(self.draw_pos[1] + dy)
#self.draw_pos = (tx, ty)
self.do_move(tx, ty)
class Equippable:
def __init__(self, hands = 0, hp_healing = 0, damage = 0, defense = 0, zap_damage = 1, zap_cooldown = 10, sprite = 129, boost=None, text="", text_color=(255, 255, 255), value=0):
self.hands = hands
self.hp_healing = hp_healing
self.damage = damage
self.defense = defense
self.zap_damage = zap_damage
self.zap_cooldown = zap_cooldown
self.zap_cooldown_remaining = 0
self.sprite = sprite
self.quality = 0
self.text = text
self.text_color = text_color
self.boost = boost
self.value = value
def tick(self):
if self.zap_cooldown_remaining:
self.zap_cooldown_remaining -= 1
if self.zap_cooldown_remaining < 0: self.zap_cooldown_remaining = 0
def __repr__(self):
cooldown_str = f'({self.zap_cooldown_remaining} rounds until ready)'
return f"<Equippable text={self.text}, hands={self.hands}, hp_healing={self.hp_healing}, damage={self.damage}, defense={self.defense}, zap_damage={self.zap_damage}, zap_cooldown={self.zap_cooldown}{cooldown_str if self.zap_cooldown_remaining else ''}, sprite={self.sprite}>"
def classify(self):
categories = []
if self.hands==0:
categories.append("consumable")
elif self.damage > 0:
categories.append(f"{self.hands}-handed weapon")
elif self.defense > 0:
categories.append(f"defense")
elif self.zap_damage > 0:
categories.append("{self.hands}-handed magic weapon")
if len(categories) == 0:
return "unclassifiable"
elif len(categories) == 1:
return categories[0]
else:
return "Erratic: " + ', '.join(categories)
def consume(self, consumer):
if self.boost == "green_pot":
consumer.base_damage += self.value
elif self.boost == "blue_pot":
b = self.value
while b: #split bonus between damage and faster cooldown
bonus = random.choice(["damage", "cooldown", "range"])
if bonus == "damage":
consumer.base_zap_damage += 1
elif bonus == "cooldown":
consumer.base_zap_cooldown += 1
else:
consumer.base_zap_range += 1
b -= 1
elif self.boost == "grey_pot":
consumer.base_defense += self.value
elif self.boost == "sm_grey_pot":
consumer.luck += self.value
elif self.hp_healing:
consumer.hp += self.hp_healing
if consumer.hp > consumer.max_hp: consumer.hp = consumer.max_hp
def do_zap(self, caster, entities):
if self.zap_damage == 0:
print("This item can't zap.")
return False
if self.zap_cooldown_remaining != 0:
print("zap is cooling down.")
return False
fx, fy = caster.draw_pos
x, y = int(fx), int (fy)
dist = lambda tx, ty: abs(int(tx) - x) + abs(int(ty) - y)
targets = []
for e in entities:
if type(e) != EnemyEntity: continue
if dist(*e.draw_pos) > caster.base_zap_range:
continue
if e.hp <= 0: continue
targets.append(e)
if not targets:
print("No targets found in range.")
return False
target = random.choice(targets)
print(f"Zap! {target}")
target.get_zapped(self.zap_damage)
self.zap_cooldown_remaining = self.zap_cooldown
return True
#def compare(self, other):
# my_class = self.classify()
# o_class = other.classify()
# if my_class == "unclassifiable" or o_class == "unclassifiable":
# return None
# if my_class == "consumable":
# return other.hp_healing - self.hp_healing
class PlayerEntity(COSEntity):
def __init__(self, *, game):
#print(f"spawn at origin")
self.draw_order = 10
super().__init__(game.grid, 0, 0, sprite_num=84, game=game)
self.hp = 10
self.max_hp = 10
self.base_damage = 1
self.base_defense = 0
self.luck = 0
self.archetype = None
self.equipped = []
self.inventory = []
self.base_zap_damage = 0
self.base_zap_cooldown = 0
self.base_zap_range = 4
def tick(self):
for i in self.equipped:
i.tick()
def calc_damage(self):
dmg = self.base_damage
for i in self.equipped:
dmg += i.damage
return dmg
def calc_defense(self):
defense = self.base_defense
for i in self.equipped:
defense += i.defense
return defense
def do_zap(self):
for i in self.equipped:
if i.zap_damage and i.zap_cooldown_remaining == 0:
if i.do_zap(self, self.game.entities):
break
else:
print("Couldn't zap")
def bump(self, other, dx, dy, test=False):
if type(other) == BoulderEntity:
print("Boulder hit w/ knockback!")
return self.game.pull_boulder_move((-dx, -dy), other)
#print(f"oof, ouch, {other} bumped the player - {other.base_damage} damage from {other}")
self.hp = max(self.hp - max(other.base_damage - self.calc_defense(), 0), 0)
def receive(self, equip):
print(equip)
if (equip.hands == 0):
if len([i for i in self.inventory if i is not None]) < 3:
if None in self.inventory:
self.inventory[self.inventory.index(None)] = equip
else:
self.inventory.append(equip)
return
else:
print("something something, consumable GUI")
elif (equip.hands == 1):
if len(self.equipped) < 2:
self.equipped.append(equip)
return
else:
print("Something something, 1h GUI")
else: # equip.hands == 2:
if len(self.equipped) == 0:
self.equipped.append(equip)
return
else:
print("Something something, 2h GUI")
def respawn(self, avoid=None):
# find spawn point
x_max, y_max = g.size
spawn_points = []
for x in range(x_max):
for y in range(y_max):
if g.at((x, y)).walkable:
spawn_points.append((x, y))
random.shuffle(spawn_points)
## TODO - find other entities to avoid spawning on top of
for spawn in spawn_points:
for e in avoid or []:
if e.draw_pos == spawn: break
else:
break
self.draw_pos = spawn
def __repr__(self):
return f"<PlayerEntity {self.draw_pos}, {self.grid}>"
class BoulderEntity(COSEntity):
def __init__(self, x, y, *, game):
self.draw_order = 8
super().__init__(game.grid, x, y, 66, game=game)
def bump(self, other, dx, dy, test=False):
if type(other) == BoulderEntity:
#print("Boulders can't push boulders")
return False
elif type(other) == EnemyEntity:
if not other.can_push: return False
#tx, ty = int(self.e.position[0] + dx), int(self.e.position[1] + dy)
tx, ty = int(self.draw_pos[0] + dx), int(self.draw_pos[1] + dy)
# Is the boulder blocked the same direction as the bumper? If not, let's both move
old_pos = int(self.draw_pos[0]), int(self.draw_pos[1])
if self.try_move(dx, dy, test=test):
if not test:
other.do_move(*old_pos)
#other.draw_pos = old_pos
return True
class ButtonEntity(COSEntity):
def __init__(self, x, y, exit_entity, *, game):
self.draw_order = 1
super().__init__(game.grid, x, y, 250, game=game)
self.exit = exit_entity
def ev_enter(self, other):
print("Button makes a satisfying click!")
self.exit.unlock()
def ev_exit(self, other):
print("Button makes a disappointing click.")
self.exit.lock()
def bump(self, other, dx, dy, test=False):
#if type(other) == BoulderEntity:
# self.exit.unlock()
# TODO: unlock, and then lock again, when player steps on/off
if not test:
pos = int(self.draw_pos[0]), int(self.draw_pos[1])
other.do_move(*pos)
return True
class ExitEntity(COSEntity):
def __init__(self, x, y, bx, by, *, game):
self.draw_order = 2
super().__init__(game.grid, x, y, 45, game=game)
self.my_button = ButtonEntity(bx, by, self, game=game)
self.unlocked = False
#global cos_entities
#cos_entities.append(self.my_button)
def unlock(self):
self.sprite_number = 21
self.unlocked = True
def lock(self):
self.sprite_number = 45
self.unlocked = False
def bump(self, other, dx, dy, test=False):
if type(other) == BoulderEntity:
return False
if self.unlocked:
if not test:
other._relative_move(dx, dy)
#TODO - player go down a level logic
if type(other) == PlayerEntity:
self.game.depth += 1
#print(f"welcome to level {self.game.depth}")
self.game.create_level(self.game.depth)
self.game.swap_level(self.game.level, self.game.spawn_point)
class EnemyEntity(COSEntity):
def __init__(self, x, y, hp=2, base_damage=1, base_defense=0, sprite=123, can_push=False, crushable=True, sight=8, move_cooldown=1, *, game):
self.draw_order = 7
super().__init__(game.grid, x, y, sprite, game=game)
self.hp = hp
self.base_damage = base_damage
self.base_defense = base_defense
self.base_sprite = sprite
self.can_push = can_push
self.crushable = crushable
self.sight = sight
self.move_cooldown = move_cooldown
self.moved_last = 0
def bump(self, other, dx, dy, test=False):
if self.hp == 0:
if not test:
old_pos = int(self.draw_pos[0]), int(self.draw_pos[1])
other.do_move(*old_pos)
return True
if type(other) == PlayerEntity:
# TODO - get damage from player, take damage, decide to die or not
d = other.calc_damage()
self.hp -= d
self.hp = max(self.hp, 0)
if self.hp == 0:
self._entity.sprite_number = self.base_sprite + 246
self.draw_order = 1
print(f"Player hit for {d}. HP = {self.hp}")
#self.hp = 0
return False
elif type(other) == BoulderEntity:
if not self.crushable and self.hp > 0:
print("Uncrushable!")
return False
if self.hp > 0:
print("Ouch, my entire body!!")
self._entity.sprite_number = self.base_sprite + 246
self.hp = 0
old_pos = int(self.draw_pos[0]), int(self.draw_pos[1])
if not test:
other.do_move(*old_pos)
return True
def act(self):
if self.hp > 0:
# if player nearby: attack
x, y = self.draw_pos
px, py = self.game.player.draw_pos
for d in ((1, 0), (0, 1), (-1, 0), (1, 0)):
if int(x + d[0]) == int(px) and int(y + d[1]) == int(py):
self.try_move(*d)
return
# slow movement (doesn't affect ability to attack)
if self.moved_last > 0:
self.moved_last -= 1
#print(f"Deducting move cooldown, now {self.moved_last} / {self.move_cooldown}")
return
else:
#print(f"Restaring move cooldown - {self.move_cooldown}")
self.moved_last = self.move_cooldown
# if player is not nearby, wander
if abs(x - px) + abs(y - py) > self.sight:
d = random.choice(((1, 0), (0, 1), (-1, 0), (1, 0)))
self.try_move(*d)
# if can_push and player in a line: KICK
if self.can_push:
if int(x) == int(px):# vertical kick
self.try_move(0, 1 if y < py else -1)
elif int(y) == int(py):# horizontal kick
self.try_move(1 if x < px else -1, 0)
# else, nearby pursue
towards = []
dist = lambda dx, dy: abs(px - (x + dx)) + abs(py - (y + dy))
#current_dist = dist(0, 0)
#for d in ((1, 0), (0, 1), (-1, 0), (1, 0)):
# if dist(*d) <= current_dist + 0.75: towards.append(d)
#print(current_dist, towards)
if px >= x:
towards.append((1, 0))
if px <= x:
towards.append((-1, 0))
if py >= y:
towards.append((0, 1))
if py <= y:
towards.append((0, -1))
towards = [p for p in towards if self.game.grid.at((int(x + p[0]), int(y + p[1]))).walkable]
towards.sort(key = lambda p: dist(*p))
target_dir = towards[0]
self.try_move(*target_dir)
def get_zapped(self, d):
self.hp -= d
self.hp = max(self.hp, 0)
if self.hp == 0:
self._entity.sprite_number = self.base_sprite + 246
self.draw_order = 1
print(f"Player zapped for {d}. HP = {self.hp}")
class TreasureEntity(COSEntity):
def __init__(self, x, y, treasure_table=None, *, game):
self.draw_order = 6
super().__init__(game.grid, x, y, 89, game=game)
self.popped = False
self.treasure_table = treasure_table
def generate(self):
items = list(self.treasure_table.keys())
weights = [self.treasure_table[k] for k in items]
item = random.choices(items, weights=weights)[0]
bonus_stats_max = (self.game.depth + (self.game.player.luck*2)) * 0.66
bonus_stats = random.randint(0, int(bonus_stats_max))
bonus_colors = {1: (192, 255, 192), 2: (128, 128, 192), 3: (255, 192, 255),
4: (255, 192, 192), 5: (255, 0, 0)}
data = itemdata[item]
if item in ("sword", "sword2", "sword3", "axe", "axe2", "axe3"):
equip = Equippable(hands=data.handedness, sprite=data.sprite, damage=data.base_value+bonus_stats, text=data.base_name)
elif item in ("buckler", "shield"):
equip = Equippable(hands=data.handedness, sprite=data.sprite, defense=data.base_value+bonus_stats, text=data.base_name)
elif item in ("wand", "staff", "staff2"):
equip = Equippable(hands=data.handedness, sprite=data.sprite, zap_damage=data.base_value[0], zap_cooldown=data.base_value[1], text=data.base_name)
if bonus_stats:
b = bonus_stats
while b: # split bonus between damage and faster cooldown
if equip.zap_cooldown == 2 or random.random() > 0.66:
equip.zap_damage += 1
else:
equip.zap_cooldown -= 1
b -= 1
elif item == "red_pot":
equip = Equippable(hands=data.handedness, sprite=data.sprite, hp_healing=data.base_value+bonus_stats, text=data.base_name)
elif item in ("blue_pot", "green_pot", "grey_pot", "sm_grey_pot"):
print(f"Permanent stat boost ({item})")
equip = Equippable(hands=data.handedness, sprite=data.sprite, text=data.base_name, boost=item, value=data.base_value + bonus_stats)
else:
print(f"Unfound item: {item}")
equip = Equippable()
if bonus_stats:
equip.text = equip.text + f" (+{bonus_stats})"
equip.text_color = bonus_colors[bonus_stats if bonus_stats <=5 else 5]
return equip
def bump(self, other, dx, dy, test=False):
if type(other) != PlayerEntity:
return False
if self.popped:
print("It's already open.")
return True
print("Take me, I'm yours!")
self._entity.sprite_number = 91
self.popped = True
#print(self.treasure_table)
other.receive(self.generate())
return False

View File

@ -1,62 +0,0 @@
from dataclasses import dataclass
@dataclass
class ItemData:
min_lv: int
max_lv: int
base_wt: float
sprite: int
base_value: int
base_name: str
affinity: str # player archetype that makes it more common
handedness: int
itemdata = {
"buckler": ItemData(min_lv = 1, max_lv = 10, base_wt = 0.25, sprite=101, base_value=1,
base_name="Buckler", affinity="knight", handedness=1),
"shield": ItemData(min_lv = 2, max_lv = 99, base_wt = 0.15, sprite=102, base_value=2,
base_name="Shield", affinity="knight", handedness=1),
"sword": ItemData(min_lv = 1, max_lv = 10, base_wt = 0.25, sprite=103, base_value=1,
base_name="Shortsword", affinity="knight", handedness=1),
"sword2": ItemData(min_lv = 2, max_lv = 16, base_wt = 0.15, sprite=104, base_value=2,
base_name="Longsword", affinity="knight", handedness=1),
"sword3": ItemData(min_lv = 5, max_lv = 99, base_wt = 0.08, sprite=105, base_value=5,
base_name="Claymore", affinity="knight", handedness=2),
"axe": ItemData(min_lv = 1, max_lv = 10, base_wt = 0.25, sprite=119, base_value=1,
base_name="Hatchet", affinity="viking", handedness=1),
"axe2": ItemData(min_lv = 2, max_lv = 16, base_wt = 0.15, sprite=120, base_value=4,
base_name="Broad Axe", affinity="viking", handedness=2),
"axe3": ItemData(min_lv = 5, max_lv = 99, base_wt = 0.08, sprite=121, base_value=6,
base_name="Bearded Axe", affinity="viking", handedness=2),
"wand": ItemData(min_lv = 1, max_lv = 10, base_wt = 0.25, sprite=132, base_value=(1, 10),
base_name="Wand", affinity="wizard", handedness=1),
"staff": ItemData(min_lv = 2, max_lv = 16, base_wt = 0.15, sprite=130, base_value=(2, 8),
base_name="Sceptre", affinity="wizard", handedness=2),
"staff2": ItemData(min_lv = 5, max_lv = 99, base_wt = 0.08, sprite=131, base_value=(3, 7),
base_name="Wizard's Staff", affinity="wizard", handedness=2),
"red_pot": ItemData(min_lv = 1, max_lv = 99, base_wt = 0.25, sprite=115, base_value=1,
base_name="Health Potion", affinity=None, handedness=0),
"blue_pot": ItemData(min_lv = 1, max_lv = 99, base_wt = 0.10, sprite=116, base_value=1,
base_name="Sorcery Potion", affinity="wizard", handedness=0),
"green_pot": ItemData(min_lv = 1, max_lv = 99, base_wt = 0.10, sprite=114, base_value=1,
base_name="Strength Potion", affinity="viking", handedness=0),
"grey_pot": ItemData(min_lv = 1, max_lv = 99, base_wt = 0.10, sprite=113, base_value=1,
base_name="Defense Potion", affinity="knight", handedness=0),
"sm_grey_pot": ItemData(min_lv = 1, max_lv = 99, base_wt = 0.05, sprite=125, base_value=1,
base_name="Luck Potion", affinity=None, handedness=0),
}

View File

@ -1,292 +0,0 @@
import random
import mcrfpy
import cos_tiles as ct
t = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
def binary_space_partition(x, y, w, h):
d = random.choices(["vert", "horiz"], weights=[w/(w+h), h/(w+h)])[0]
split = random.randint(30, 70) / 100.0
if d == "vert":
coord = int(w * split)
return (x, y, coord, h), (x+coord, y, w-coord, h)
else: # horizontal
coord = int(h * split)
return (x, y, w, coord), (x, y+coord, w, h-coord)
room_area = lambda x, y, w, h: w * h
class BinaryRoomNode:
def __init__(self, xywh):
self.data = xywh
self.left = None
self.right = None
def __repr__(self):
return f"<RoomNode {self.data}>"
def center(self):
x, y, w, h = self.data
return (x + w // 2, y + h // 2)
def split(self):
new_data = binary_space_partition(*self.data)
self.left = BinaryRoomNode(new_data[0])
self.right = BinaryRoomNode(new_data[1])
def walk(self):
if self.left and self.right:
return self.left.walk() + self.right.walk()
return [self]
def contains(self, pt):
x, y, w, h = self.data
tx, ty = pt
return x <= tx <= x + w and y <= ty <= y + h
class RoomGraph:
def __init__(self, xywh):
self.root = BinaryRoomNode(xywh)
def __repr__(self):
return f"<RoomGraph, root={self.root}, {len(self.walk())} rooms>"
def walk(self):
w = self.root.walk() if self.root else []
#print(w)
return w
def room_coord(room, margin=0):
x, y, w, h = room.data
#print(x,y,w,h, f'{margin=}', end=';')
w -= 1
h -= 1
margin += 1
x += margin
y += margin
w -= margin
h -= margin
if w < 0: w = 0
if h < 0: h = 0
#print(x,y,w,h, end=' -> ')
tx = x if w==0 else random.randint(x, x+w)
ty = y if h==0 else random.randint(y, y+h)
#print((tx, ty))
return (tx, ty)
def adjacent_rooms(r, rooms):
x, y, w, h = r.data
adjacents = {}
for i, other_r in enumerate(rooms):
rx, ry, rw, rh = other_r.data
if (rx, ry, rw, rh) == r:
continue # Skip self
# Check vertical adjacency (above or below)
if rx < x + w and x < rx + rw: # Overlapping width
if ry + rh == y: # Above
adjacents[i] = (x + w // 2, y - 1)
elif y + h == ry: # Below
adjacents[i] = (x + w // 2, y + h + 1)
# Check horizontal adjacency (left or right)
if ry < y + h and y < ry + rh: # Overlapping height
if rx + rw == x: # Left
adjacents[i] = (x - 1, y + h // 2)
elif x + w == rx: # Right
adjacents[i] = (x + w + 1, y + h // 2)
return adjacents
class Level:
def __init__(self, width, height):
self.width = width
self.height = height
#self.graph = [(0, 0, width, height)]
self.graph = RoomGraph( (0, 0, width, height) )
self.grid = mcrfpy.Grid(width, height, t, (10, 5), (1014, 700))
self.highlighted = -1 #debug view feature
self.walled_rooms = [] # for tracking "hallway rooms" vs "walled rooms"
def fill(self, xywh, highlight = False):
if highlight:
ts = 0
else:
ts = room_area(*xywh) % 131
X, Y, W, H = xywh
for x in range(X, X+W):
for y in range(Y, Y+H):
self.grid.at((x, y)).tilesprite = ts
def highlight(self, delta):
rooms = self.graph.walk()
if self.highlighted < len(rooms):
#print(f"reset {self.highlighted}")
self.fill(rooms[self.highlighted].data) # reset
self.highlighted += delta
print(f"highlight {self.highlighted}")
self.highlighted = self.highlighted % len(rooms)
self.fill(rooms[self.highlighted].data, highlight = True)
def reset(self):
self.graph = RoomGraph( (0, 0, self.width, self.height) )
for x in range(self.width):
for y in range(self.height):
self.grid.at((x, y)).walkable = True
self.grid.at((x, y)).transparent = True
self.grid.at((x, y)).tilesprite = 0 #random.choice([40, 28])
def split(self, single=False):
if single:
areas = {g.data: room_area(*g.data) for g in self.graph.walk()}
largest = sorted(self.graph.walk(), key=lambda g: areas[g.data])[-1]
largest.split()
else:
for room in self.graph.walk(): room.split()
self.fill_rooms()
def fill_rooms(self, features=None):
rooms = self.graph.walk()
#print(f"rooms: {len(rooms)}")
for i, g in enumerate(rooms):
X, Y, W, H = g.data
#c = [random.randint(0, 255) for _ in range(3)]
ts = room_area(*g.data) % 131 + i # modulo - consistent tile pick
for x in range(X, X+W):
for y in range(Y, Y+H):
self.grid.at((x, y)).tilesprite = ts
def wall_rooms(self):
self.walled_rooms = []
rooms = self.graph.walk()
for i, g in enumerate(rooms):
# unwalled / hallways: not selected for small dungeons, first, last, and 65% of all other rooms
if len(rooms) > 3 and i > 1 and i < len(rooms) - 2 and random.random() < 0.35:
self.walled_rooms.append(False)
continue
self.walled_rooms.append(True)
X, Y, W, H = g.data
for x in range(X, X+W):
self.grid.at((x, Y)).walkable = False
#self.grid.at((x, Y+H-1)).walkable = False
for y in range(Y, Y+H):
self.grid.at((X, y)).walkable = False
#self.grid.at((X+W-1, y)).walkable = False
# boundary of entire level
for x in range(0, self.width):
# self.grid.at((x, 0)).walkable = False
self.grid.at((x, self.height-1)).walkable = False
for y in range(0, self.height):
# self.grid.at((0, y)).walkable = False
self.grid.at((self.width-1, y)).walkable = False
def dig_path(self, start:"Tuple[int, int]", end:"Tuple[int, int]", walkable=True, color=None, sprite=None):
print(f"Digging: {start} -> {end}")
# get x1,y1 and x2,y2 coordinates: top left and bottom right points on the rect formed by two random points, one from each of the 2 rooms
x1 = min([start[0], end[0]])
x2 = max([start[0], end[0]])
dw = x2 - x1
y1 = min([start[1], end[1]])
y2 = max([start[1], end[1]])
dh = y2 - y1
# random: top left or bottom right as the corner between the paths
tx, ty = (x1, y1) if random.random() >= 0.5 else (x2, y2)
for x in range(x1, x1+dw):
try:
if walkable:
self.grid.at((x, ty)).walkable = walkable
if color:
self.grid.at((x, ty)).color = color
if sprite is not None:
self.grid.at((x, ty)).tilesprite = sprite
except:
pass
for y in range(y1, y1+dh):
try:
if walkable:
self.grid.at((tx, y)).walkable = True
if color:
self.grid.at((tx, y)).color = color
if sprite is not None:
self.grid.at((tx, y)).tilesprite = sprite
except:
pass
def generate(self, level_plan): #target_rooms = 5, features=None):
self.reset()
target_rooms = len(level_plan)
if type(level_plan) is set:
level_plan = random.choice(list(level_plan))
while len(self.graph.walk()) < target_rooms:
self.split(single=len(self.graph.walk()) > target_rooms * .5)
# Player path planning
#self.fill_rooms()
self.wall_rooms()
rooms = self.graph.walk()
feature_coords = []
prev_room = None
print(level_plan)
for room_num, room in enumerate(rooms):
room_plan = level_plan[room_num]
if type(room_plan) == str: room_plan = [room_plan] # single item plans became single-character plans...
for f in room_plan:
#feature_coords.append((f, room_coord(room, margin=4 if f in ("boulder",) else 1)))
# boulders are breaking my brain. If I can't get boulders away from walls with margin, I'm just going to dig them out.
#if f == "boulder":
# x, y = room_coord(room, margin=0)
# if x < 2: x += 1
# if y < 2: y += 1
# if x > self.grid.grid_size[0] - 2: x -= 1
# if y > self.grid.grid_size[1] - 2: y -= 1
# for _x in (1, 0, -1):
# for _y in (1, 0, -1):
# self.grid.at((x + _x, y + _y)).walkable = True
# feature_coords.append((f, (x, y)))
#else:
# feature_coords.append((f, room_coord(room, margin=0)))
fcoord = None
while not fcoord:
fc = room_coord(room, margin=0)
if not self.grid.at(fc).walkable: continue
if fc in [_i[1] for _i in feature_coords]: continue
fcoord = fc
feature_coords.append((f, fcoord))
print(feature_coords[-1])
## Hallway generation
# plow an inelegant path
if prev_room:
start = room_coord(prev_room, margin=2)
end = room_coord(room, margin=2)
self.dig_path(start, end, color=(0, 64, 0))
prev_room = room
# Tile painting
possibilities = None
while possibilities or possibilities is None:
possibilities = ct.wfc_pass(self.grid, possibilities)
## "hallway room" repainting
#for i, hall_room in enumerate(rooms):
# if self.walled_rooms[i]:
# print(f"walled room: {hall_room}")
# continue
# print(f"hall room: {hall_room}")
# x, y, w, h = hall_room.data
# for _x in range(x+1, x+w-1):
# for _y in range(y+1, y+h-1):
# self.grid.at((_x, _y)).walkable = False
# self.grid.at((_x, _y)).tilesprite = -1
# self.grid.at((_x, _y)).color = (0, 0, 0) # pit!
# targets = adjacent_rooms(hall_room, rooms)
# print(targets)
# for k, v in targets.items():
# self.dig_path(hall_room.center(), v, color=(64, 32, 32))
# for _, p in feature_coords:
# if hall_room.contains(p): self.dig_path(hall_room.center(), p, color=(92, 48, 48))
return feature_coords

300
src/scripts/cos_play.py Normal file
View File

@ -0,0 +1,300 @@
import mcrfpy
mcrfpy.createScene("play")
ui = mcrfpy.sceneUI("play")
t = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) # 12, 11)
font = mcrfpy.Font("assets/JetbrainsMono.ttf")
frame_color = (64, 64, 128)
grid = mcrfpy.Grid(20, 15, t, 10, 10, 800, 595)
grid.zoom = 2.0
entity_frame = mcrfpy.Frame(815, 10, 194, 595, fill_color = frame_color)
inventory_frame = mcrfpy.Frame(10, 610, 800, 143, fill_color = frame_color)
stats_frame = mcrfpy.Frame(815, 610, 194, 143, fill_color = frame_color)
begin_btn = mcrfpy.Frame(350,250,100,100, fill_color = (255,0,0))
begin_btn.children.append(mcrfpy.Caption(5, 5, "Begin", font))
def cos_keys(key, state):
if key == 'M' and state == 'start':
mapgen()
elif state == "end": return
elif key == "W":
player.move("N")
elif key == "A":
player.move("W")
elif key == "S":
player.move("S")
elif key == "D":
player.move("E")
def cos_init(*args):
if args[3] != "start": return
mcrfpy.keypressScene(cos_keys)
ui.remove(4)
begin_btn.click = cos_init
[ui.append(e) for e in (grid, entity_frame, inventory_frame, stats_frame, begin_btn)]
import random
def rcolor():
return tuple([random.randint(0, 255) for i in range(3)]) # TODO list won't work with GridPoint.color, so had to cast to tuple
x_max, y_max = grid.grid_size
for x in range(x_max):
for y in range(y_max):
grid.at((x,y)).color = rcolor()
from math import pi, cos, sin
def mapgen(room_size_max = 7, room_size_min = 3, room_count = 4):
# reset map
for x in range(x_max):
for y in range(y_max):
grid.at((x, y)).walkable = False
grid.at((x, y)).transparent= False
grid.at((x,y)).tilesprite = random.choices([40, 28], weights=[.8, .2])[0]
global cos_entities
for e in cos_entities:
e.e.position = (999,999) # TODO
e.die()
cos_entities = []
#Dungeon generation
centers = []
attempts = 0
while len(centers) < room_count:
# Leaving this attempt here for later comparison. These rooms sucked.
# overlapping, uninteresting hallways, crowded into the corners sometimes, etc.
attempts += 1
if attempts > room_count * 15: break
# room_left = random.randint(1, x_max)
# room_top = random.randint(1, y_max)
# Take 2 - circular distribution of rooms
angle_mid = (len(centers) / room_count) * 2 * pi + 0.785
angle = random.uniform(angle_mid - 0.25, angle_mid + 0.25)
radius = random.uniform(3, 14)
room_left = int(radius * cos(angle)) + int(x_max/2)
if room_left <= 1: room_left = 1
if room_left > x_max - 1: room_left = x_max - 2
room_top = int(radius * sin(angle)) + int(y_max/2)
if room_top <= 1: room_top = 1
if room_top > y_max - 1: room_top = y_max - 2
room_w = random.randint(room_size_min, room_size_max)
if room_w + room_left >= x_max: room_w = x_max - room_left - 2
room_h = random.randint(room_size_min, room_size_max)
if room_h + room_top >= y_max: room_h = y_max - room_top - 2
#print(room_left, room_top, room_left + room_w, room_top + room_h)
if any( # centers contained in this randomly generated room
[c[0] >= room_left and c[0] <= room_left + room_w and c[1] >= room_top and c[1] <= room_top + room_h for c in centers]
):
continue # re-randomize the room position
centers.append(
(int(room_left + (room_w/2)), int(room_top + (room_h/2)))
)
for x in range(room_w):
for y in range(room_h):
grid.at((room_left+x, room_top+y)).walkable=True
grid.at((room_left+x, room_top+y)).transparent=True
grid.at((room_left+x, room_top+y)).tilesprite = random.choice([48, 49, 50, 51, 52, 53])
# generate a boulder
if (room_w > 2 and room_h > 2):
room_boulder_x, room_boulder_y = random.randint(room_left+1, room_left+room_w-1), random.randint(room_top+1, room_top+room_h-1)
cos_entities.append(BoulderEntity(room_boulder_x, room_boulder_y))
print(f"{room_count} rooms generated after {attempts} attempts.")
#print(centers)
# hallways
pairs = []
for c1 in centers:
for c2 in centers:
if c1 == c2: continue
if (c2, c1) in pairs or (c1, c2) in pairs: continue
left = min(c1[0], c2[0])
right = max(c1[0], c2[0])
top = min(c1[1], c2[1])
bottom = max(c1[1], c2[1])
corners = [(left, top), (left, bottom), (right, top), (right, bottom)]
corners.remove(c1)
corners.remove(c2)
random.shuffle(corners)
target, other = corners
for x in range(target[0], other[0], -1 if target[0] > other[0] else 1):
was_walkable = grid.at((x, target[1])).walkable
grid.at((x, target[1])).walkable=True
grid.at((x, target[1])).transparent=True
if not was_walkable:
grid.at((x, target[1])).tilesprite = random.choices([0, 12, 24], weights=[.6, .3, .1])[0]
for y in range(target[1], other[1], -1 if target[1] > other[1] else 1):
was_walkable = grid.at((target[0], y)).walkable
grid.at((target[0], y)).walkable=True
grid.at((target[0], y)).transparent=True
if not was_walkable:
grid.at((target[0], y)).tilesprite = random.choices([0, 12, 24], weights=[0.4, 0.3, 0.3])[0]
pairs.append((c1, c2))
# spawn exit and button
spawn_points = []
for x in range(x_max):
for y in range(y_max):
if grid.at((x, y)).walkable:
spawn_points.append((x, y))
random.shuffle(spawn_points)
door_spawn, button_spawn = spawn_points[:2]
cos_entities.append(ExitEntity(*door_spawn, *button_spawn))
# respawn player
global player
if player:
player.position = (999,999) # TODO - die() is broken and I don't know why
player = PlayerEntity()
#for x in range(x_max):
# for y in range(y_max):
# if grid.at((x, y)).walkable:
# #grid.at((x,y)).tilesprite = random.choice([48, 49, 50, 51, 52, 53])
# pass
# else:
# #grid.at((x,y)).tilesprite = random.choices([40, 28], weights=[.8, .2])[0]
#131 - last sprite
#123, 124 - brown, grey rats
#121 - ghost
#114, 115, 116 - green, red, blue potion
#102 - shield
#98 - low armor guy, #97 - high armor guy
#89 - chest, #91 - empty chest
#84 - wizard
#82 - barrel
#66 - boulder
#64, 65 - graves
#48 - 53: ground (not going to figure out how they fit together tonight)
#42 - button-looking ground
#40 - basic solid wall
#36, 37, 38 - wall (left, middle, right)
#28 solid wall but with a grate
#21 - wide open door, 33 medium open, 45 closed door
#0 - basic dirt
class MyEntity:
def __init__(self, x=0, y=0, sprite_num=86):
self.e = mcrfpy.Entity(x, y, t, sprite_num)
grid.entities.append(self.e)
def die(self):
for i in range(len(grid.entities)):
e = grid.entities[i]
if e == self.e:
grid.entities.remove(i)
break
def bump(self, other, dx, dy):
raise NotImplementedError
def try_move(self, dx, dy):
tx, ty = int(self.e.position[0] + dx), int(self.e.position[1] + dy)
for e in cos_entities:
if e.e.position == (tx, ty):
#print(f"bumping {e}")
return e.bump(self, dx, dy)
if tx < 0 or tx >= x_max:
#print("out of bounds horizontally")
return False
if ty < 0 or ty >= y_max:
#print("out of bounds vertically")
return False
if grid.at((tx, ty)).walkable == True:
#print("Motion!")
self.e.position = (tx, ty)
return True
else:
#print("Bonk")
return False
def _relative_move(self, dx, dy):
tx, ty = int(self.e.position[0] + dx), int(self.e.position[1] + dy)
self.e.position = (tx, ty)
def move(self, direction):
if direction == "N":
self.try_move(0, -1)
elif direction == "E":
self.try_move(1, 0)
elif direction == "S":
self.try_move(0, 1)
elif direction == "W":
self.try_move(-1, 0)
cos_entities = []
class PlayerEntity(MyEntity):
def __init__(self):
# find spawn point
spawn_points = []
for x in range(x_max):
for y in range(y_max):
if grid.at((x, y)).walkable:
spawn_points.append((x, y))
random.shuffle(spawn_points)
for spawn in spawn_points:
for e in cos_entities:
if e.e.position == spawn: break
else:
break
#print(f"spawn at {spawn}")
super().__init__(spawn[0], spawn[1], sprite_num=84)
class BoulderEntity(MyEntity):
def __init__(self, x, y):
super().__init__(x, y, 66)
def bump(self, other, dx, dy):
if type(other) == BoulderEntity:
#print("Boulders can't push boulders")
return False
tx, ty = int(self.e.position[0] + dx), int(self.e.position[1] + dy)
# Is the boulder blocked the same direction as the bumper? If not, let's both move
old_pos = int(self.e.position[0]), int(self.e.position[1])
if self.try_move(dx, dy):
other.e.position = old_pos
return True
class ButtonEntity(MyEntity):
def __init__(self, x, y, exit):
super().__init__(x, y, 42)
self.exit = exit
def bump(self, other, dx, dy):
if type(other) == BoulderEntity:
self.exit.unlock()
other._relative_move(dx, dy)
return True
class ExitEntity(MyEntity):
def __init__(self, x, y, bx, by):
super().__init__(x, y, 45)
self.my_button = ButtonEntity(bx, by, self)
self.unlocked = False
global cos_entities
cos_entities.append(self.my_button)
def unlock(self):
self.e.sprite_number = 21
self.unlocked = True
def lock(self):
self.e.sprite_number = 45
self.unlocked = True
def bump(self, other, dx, dy):
if type(other) == BoulderEntity:
return False
if self.unlocked:
other._relative_move(dx, dy)
player = None

View File

@ -1,223 +0,0 @@
tiles = {}
deltas = [
(-1, -1), ( 0, -1), (+1, -1),
(-1, 0), ( 0, 0), (+1, 0),
(-1, +1), ( 0, +1), (+1, +1)
]
class TileInfo:
GROUND, WALL, DONTCARE = True, False, None
chars = {
"X": WALL,
"_": GROUND,
"?": DONTCARE
}
symbols = {v: k for k, v in chars.items()}
def __init__(self, values:dict):
self._values = values
self.rules = []
self.chance = 1.0
@staticmethod
def from_grid(grid, xy:tuple):
values = {}
for d in deltas:
tx, ty = d[0] + xy[0], d[1] + xy[1]
try:
values[d] = grid.at((tx, ty)).walkable
except ValueError:
values[d] = True
return TileInfo(values)
@staticmethod
def from_string(s):
values = {}
for d, c in zip(deltas, s):
values[d] = TileInfo.chars[c]
return TileInfo(values)
def __hash__(self):
"""for use as a dictionary key"""
return hash(tuple(self._values.items()))
def match(self, other:"TileInfo"):
for d, rule in self._values.items():
if rule is TileInfo.DONTCARE: continue
if other._values[d] is TileInfo.DONTCARE: continue
if rule != other._values[d]: return False
return True
def show(self):
nine = ['', '', '\n'] * 3
for k, end in zip(deltas, nine):
c = TileInfo.symbols[self._values[k]]
print(c, end=end)
def __repr__(self):
return f"<TileInfo {self._values}>"
cardinal_directions = {
"N": ( 0, -1),
"S": ( 0, +1),
"E": (-1, 0),
"W": (+1, 0)
}
def special_rule_verify(rule, grid, xy, unverified_tiles, pass_unverified=False):
cardinal, allowed_tile = rule
dxy = cardinal_directions[cardinal.upper()]
tx, ty = xy[0] + dxy[0], xy[1] + dxy[1]
#print(f"Special rule: {cardinal} {allowed_tile} {type(allowed_tile)} -> ({tx}, {ty}) [{grid.at((tx, ty)).tilesprite}]{'*' if (tx, ty) in unverified_tiles else ''}")
if (tx, ty) in unverified_tiles and cardinal in "nsew": return pass_unverified
try:
return grid.at((tx, ty)).tilesprite == allowed_tile
except ValueError:
return False
import random
tile_of_last_resort = 431
def find_possible_tiles(grid, x, y, unverified_tiles=None, pass_unverified=False):
ti = TileInfo.from_grid(grid, (x, y))
if unverified_tiles is None: unverified_tiles = []
matches = [(k, v) for k, v in tiles.items() if k.match(ti)]
if not matches:
return []
possible = []
if not any([tileinfo.rules for tileinfo, _ in matches]):
# make weighted choice, as the tile does not depend on verification
wts = [k.chance for k, v in matches]
tileinfo, tile = random.choices(matches, weights=wts)[0]
return [tile]
for tileinfo, tile in matches:
if not tileinfo.rules:
possible.append(tile)
continue
for r in tileinfo.rules: #for r in ...: if ... continue == more readable than an "any" 1-liner
p = special_rule_verify(r, grid, (x,y),
unverified_tiles=unverified_tiles,
pass_unverified = pass_unverified
)
if p:
possible.append(tile)
continue
return list(set(list(possible)))
def wfc_first_pass(grid):
w, h = grid.grid_size
possibilities = {}
for x in range(0, w):
for y in range(0, h):
matches = find_possible_tiles(grid, x, y, pass_unverified=True)
if len(matches) == 0:
grid.at((x, y)).tilesprite = tile_of_last_resort
possibilities[(x,y)] = matches
elif len(matches) == 1:
grid.at((x, y)).tilesprite = matches[0]
else:
possibilities[(x,y)] = matches
return possibilities
def wfc_pass(grid, possibilities=None):
w, h = grid.grid_size
if possibilities is None:
#print("first pass results:")
possibilities = wfc_first_pass(grid)
counts = {}
for v in possibilities.values():
if len(v) in counts: counts[len(v)] += 1
else: counts[len(v)] = 1
#print(counts)
return possibilities
elif len(possibilities) == 0:
print("We're done!")
return
old_possibilities = possibilities
possibilities = {}
for (x, y) in old_possibilities.keys():
matches = find_possible_tiles(grid, x, y, unverified_tiles=old_possibilities.keys(), pass_unverified = True)
if len(matches) == 0:
print((x,y), matches)
grid.at((x, y)).tilesprite = tile_of_last_resort
possibilities[(x,y)] = matches
elif len(matches) == 1:
grid.at((x, y)).tilesprite = matches[0]
else:
grid.at((x, y)).tilesprite = -1
grid.at((x, y)).color = (32 * len(matches), 32 * len(matches), 32 * len(matches))
possibilities[(x,y)] = matches
if len(possibilities) == len(old_possibilities):
#print("No more tiles could be solved without collapse")
counts = {}
for v in possibilities.values():
if len(v) in counts: counts[len(v)] += 1
else: counts[len(v)] = 1
#print(counts)
if 0 in counts: del counts[0]
if len(counts) == 0:
print("Contrats! You broke it! (insufficient tile defs to solve remaining tiles)")
return []
target = min(list(counts.keys()))
while possibilities:
for (x, y) in possibilities.keys():
if len(possibilities[(x, y)]) != target:
continue
ti = TileInfo.from_grid(grid, (x, y))
matches = [(k, v) for k, v in tiles.items() if k.match(ti)]
verifiable_matches = find_possible_tiles(grid, x, y, unverified_tiles=possibilities.keys())
if not verifiable_matches: continue
#print(f"collapsing {(x, y)} ({target} choices)")
matches = [(k, v) for k, v in matches if v in verifiable_matches]
wts = [k.chance for k, v in matches]
tileinfo, tile = random.choices(matches, weights=wts)[0]
grid.at((x, y)).tilesprite = tile
del possibilities[(x, y)]
break
else:
selected = random.choice(list(possibilities.keys()))
#print(f"No tiles have verifable solutions: QUANTUM -> {selected}")
# sprinkle some quantumness on it
ti = TileInfo.from_grid(grid, (x, y))
matches = [(k, v) for k, v in tiles.items() if k.match(ti)]
wts = [k.chance for k, v in matches]
if not wts:
print(f"This one: {(x,y)} {matches}\n{wts}")
del possibilities[(x, y)]
return possibilities
tileinfo, tile = random.choices(matches, weights=wts)[0]
grid.at((x, y)).tilesprite = tile
del possibilities[(x, y)]
return possibilities
#with open("scripts/tile_def.txt", "r") as f:
with open("scripts/simple_tiles.txt", "r") as f:
for block in f.read().split('\n\n'):
info, constraints = block.split('\n', 1)
if '#' in info:
info, comment = info.split('#', 1)
rules = []
if '@' in info:
info, *block_rules = info.split('@')
#print(block_rules)
for r in block_rules:
rules.append((r[0], int(r[1:])))
#cardinal_dir = block_rules[0]
#partner
if ':' not in info:
tile_id = int(info)
chance = 1.0
else:
tile_id, chance = info.split(':')
tile_id = int(tile_id)
chance = float(chance.strip())
constraints = constraints.replace('\n', '')
k = TileInfo.from_string(constraints)
k.rules = rules
k.chance = chance
tiles[k] = tile_id

View File

@ -1,677 +1,221 @@
#print("Hello mcrogueface")
import mcrfpy
import code
#t = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) # 12, 11)
t = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16) # 12, 11)
btn_tex = mcrfpy.Texture("assets/48px_ui_icons-KenneyNL.png", 48, 48)
import cos_play
# Universal stuff
font = mcrfpy.Font("assets/JetbrainsMono.ttf")
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) #12, 11)
texture_cold = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) #12, 11)
texture_hot = mcrfpy.Texture("assets/kenney_lava.png", 16, 16) #12, 11)
frame_color = (64, 64, 128)
# Test stuff
mcrfpy.createScene("boom")
mcrfpy.setScene("boom")
ui = mcrfpy.sceneUI("boom")
box = mcrfpy.Frame(40, 60, 200, 300, fill_color=(255,128,0), outline=4.0, outline_color=(64,64,255,96))
ui.append(box)
#caption = mcrfpy.Caption(10, 10, "Clicky", font, (255, 255, 255, 255), (0, 0, 0, 255))
#box.click = lambda x, y, btn, type: print("Hello callback: ", x, y, btn, type)
#box.children.append(caption)
test_sprite_number = 86
sprite = mcrfpy.Sprite(20, 60, texture, test_sprite_number, 4.0)
spritecap = mcrfpy.Caption(5, 5, "60", font)
def click_sprite(x, y, btn, action):
global test_sprite_number
if action != "start": return
if btn in ("left", "wheel_up"):
test_sprite_number -= 1
elif btn in ("right", "wheel_down"):
test_sprite_number += 1
sprite.sprite_number = test_sprite_number # TODO - inconsistent naming for __init__, __repr__ and getsetter: sprite_number vs sprite_index
spritecap.text = test_sprite_number
sprite.click = click_sprite # TODO - sprites don't seem to correct for screen position or scale when clicking
box.children.append(sprite)
box.children.append(spritecap)
box.click = click_sprite
f_a = mcrfpy.Frame(250, 60, 80, 80, fill_color=(255, 92, 92))
f_a_txt = mcrfpy.Caption(5, 5, "0", font)
f_b = mcrfpy.Frame(340, 60, 80, 80, fill_color=(92, 255, 92))
f_b_txt = mcrfpy.Caption(5, 5, "0", font)
f_c = mcrfpy.Frame(430, 60, 80, 80, fill_color=(92, 92, 255))
f_c_txt = mcrfpy.Caption(5, 5, "0", font)
ui.append(f_a)
f_a.children.append(f_a_txt)
ui.append(f_b)
f_b.children.append(f_b_txt)
ui.append(f_c)
f_c.children.append(f_c_txt)
import sys
def ding(*args):
f_a_txt.text = str(sys.getrefcount(ding)) + " refs"
f_b_txt.text = sys.getrefcount(dong)
f_c_txt.text = sys.getrefcount(stress_test)
def dong(*args):
f_a_txt.text = str(sys.getrefcount(ding)) + " refs"
f_b_txt.text = sys.getrefcount(dong)
f_c_txt.text = sys.getrefcount(stress_test)
running = False
timers = []
def add_ding():
global timers
n = len(timers)
mcrfpy.setTimer(f"timer{n}", ding, 100)
print("+1 ding:", timers)
def add_dong():
global timers
n = len(timers)
mcrfpy.setTimer(f"timer{n}", dong, 100)
print("+1 dong:", timers)
def remove_random():
global timers
target = random.choice(timers)
print("-1 timer:", target)
print("remove from list")
timers.remove(target)
print("delTimer")
mcrfpy.delTimer(target)
print("done")
import random
import cos_entities as ce
import cos_level as cl
from cos_itemdata import itemdata
#import cos_tiles as ct
class Resources:
def __init__(self):
self.music_enabled = True
self.music_volume = 40
self.sfx_enabled = True
self.sfx_volume = 100
self.master_volume = 100
# load the music/sfx files here
self.splats = []
for i in range(1, 10):
mcrfpy.createSoundBuffer(f"assets/sfx/splat{i}.ogg")
def play_sfx(self, sfx_id):
if self.sfx_enabled and self.sfx_volume and self.master_volume:
mcrfpy.setSoundVolume(self.master_volume/100 * self.sfx_volume)
mcrfpy.playSound(sfx_id)
def play_music(self, track_id):
if self.music_enabled and self.music_volume and self.master_volume:
mcrfpy.setMusicVolume(self.master_volume/100 * self.music_volume)
mcrfpy.playMusic(...)
resources = Resources()
class Crypt:
def __init__(self):
mcrfpy.createScene("play")
self.ui = mcrfpy.sceneUI("play")
entity_frame = mcrfpy.Frame(815, 10, 194, 595, fill_color = frame_color)
inventory_frame = mcrfpy.Frame(10, 610, 800, 143, fill_color = frame_color)
stats_frame = mcrfpy.Frame(815, 610, 194, 143, fill_color = frame_color)
#self.level = cl.Level(30, 23)
self.entities = []
self.depth=1
self.stuck_btn = SweetButton(self.ui, (810, 700), "Stuck", icon=19, box_width=150, box_height = 60, click=self.stuck)
self.level_plan = {
1: [("spawn", "button", "boulder"), ("exit")],
2: [("spawn", "button", "treasure", "treasure", "treasure", "rat", "rat", "boulder"), ("exit")],
#2: [("spawn", "button", "boulder"), ("rat"), ("exit")],
3: [("spawn", "button", "boulder"), ("rat"), ("exit")],
4: [("spawn", "button", "rat"), ("boulder", "rat", "treasure"), ("exit")],
5: [("spawn", "button", "rat"), ("boulder", "rat"), ("exit")],
6: {(("spawn", "button"), ("boulder", "treasure", "exit")),
(("spawn", "boulder"), ("button", "treasure", "exit"))},
7: {(("spawn", "button"), ("boulder", "treasure", "exit")),
(("spawn", "boulder"), ("button", "treasure", "exit"))},
8: {(("spawn", "treasure", "button"), ("boulder", "treasure", "exit")),
(("spawn", "treasure", "boulder"), ("button", "treasure", "exit"))}
#9: self.lv_planner
}
# empty void for the player to initialize into
self.headsup = mcrfpy.Frame(10, 684, 320, 64, fill_color = (0, 0, 0, 0))
self.sidebar = mcrfpy.Frame(860, 4, 160, 600, fill_color = (96, 96, 160))
# Heads Up (health bar, armor bar) config
self.health_bar = [mcrfpy.Sprite(32*i, 2, t, 659, 2) for i in range(10)]
[self.headsup.children.append(i) for i in self.health_bar]
self.armor_bar = [mcrfpy.Sprite(32*i, 42, t, 659, 2) for i in range(10)]
[self.headsup.children.append(i) for i in self.armor_bar]
# (40, 3), caption, font, fill_color=font_color
self.stat_captions = mcrfpy.Caption((325,0), "HP:10\nDef:0(+0)", font, fill_color=(255, 255, 255))
self.stat_captions.outline = 3
self.stat_captions.outline_color = (0, 0, 0)
self.headsup.children.append(self.stat_captions)
# Side Bar (inventory, level info) config
self.level_caption = mcrfpy.Caption((5,5), "Level: 1", font, fill_color=(255, 255, 255))
self.level_caption.size = 26
self.level_caption.outline = 3
self.level_caption.outline_color = (0, 0, 0)
self.sidebar.children.append(self.level_caption)
self.inv_sprites = [mcrfpy.Sprite(15, 70 + 95*i, t, 659, 6.0) for i in range(5)]
for i in self.inv_sprites:
self.sidebar.children.append(i)
self.key_captions = [
mcrfpy.Sprite(75, 130 + (95*2) + 95 * i, t, 384 + i, 3.0) for i in range(3)
]
for i in self.key_captions:
self.sidebar.children.append(i)
self.inv_captions = [
mcrfpy.Caption((25, 130 + 95 * i), "x", font, fill_color=(255, 255, 255)) for i in range(5)
]
for i in self.inv_captions:
i.size = 16
self.sidebar.children.append(i)
liminal_void = mcrfpy.Grid(1, 1, t, (0, 0), (16, 16))
self.grid = liminal_void
self.player = ce.PlayerEntity(game=self)
self.spawn_point = (0, 0)
# level creation moves player to the game level at the generated spawn point
self.create_level(self.depth)
#self.grid = mcrfpy.Grid(20, 15, t, (10, 10), (1014, 758))
self.swap_level(self.level, self.spawn_point)
# Test Entities
#ce.BoulderEntity(9, 7, game=self)
#ce.BoulderEntity(9, 8, game=self)
#ce.ExitEntity(12, 6, 14, 4, game=self)
# scene setup
## might be done by self.swap_level
#[self.ui.append(e) for e in (self.grid, self.stuck_btn.base_frame)] # entity_frame, inventory_frame, stats_frame)]
self.possibilities = None # track WFC possibilities between rounds
self.enemies = []
#mcrfpy.setTimer("enemy_test", self.enemy_movement, 750)
#mcrfpy.Frame(x, y, box_width+shadow_offset, box_height, fill_color = (0, 0, 0, 255))
#Sprite(0, 3, btn_tex, icon, icon_scale)
#def enemy_movement(self, *args):
# for e in self.enemies: e.act()
#def spawn_test_rat(self):
# success = False
# while not success:
# x, y = [random.randint(0, i-1) for i in self.grid.grid_size]
# success = self.grid.at((x,y)).walkable
# self.enemies.append(ce.EnemyEntity(x, y, game=self))
def gui_update(self):
self.stat_captions.text = f"HP:{self.player.hp}\nDef:{self.player.calc_defense()}(+{self.player.calc_defense() - self.player.base_defense})"
for i, hs in enumerate(self.health_bar):
full_hearts = self.player.hp - (i*2)
empty_hearts = self.player.max_hp - (i*2)
hs.sprite_number = 659
if empty_hearts >= 2:
hs.sprite_number = 208
if full_hearts >= 2:
hs.sprite_number = 210
elif full_hearts == 1:
hs.sprite_number = 209
for i, arm_s in enumerate(self.armor_bar):
full_hearts = self.player.calc_defense() - (i*2)
arm_s.sprite_number = 659
if full_hearts >= 2:
arm_s.sprite_number = 211
elif full_hearts == 1:
arm_s.sprite_number = 212
#items = self.player.equipped[:] + self.player.inventory[:]
for i in range(5):
if i == 0:
item = self.player.equipped[0] if len(self.player.equipped) > 0 else None
elif i == 1:
item = self.player.equipped[1] if len(self.player.equipped) > 1 else None
elif i == 2:
item = self.player.inventory[0] if len(self.player.inventory) > 0 else None
elif i == 3:
item = self.player.inventory[1] if len(self.player.inventory) > 1 else None
elif i == 4:
item = self.player.inventory[2] if len(self.player.inventory) > 2 else None
if item is None:
self.inv_sprites[i].sprite_number = 659
if i > 1: self.key_captions[i - 2].sprite_number = 659
self.inv_captions[i].text = ""
continue
self.inv_sprites[i].sprite_number = item.sprite
if i > 1:
self.key_captions[i - 2].sprite_number = 384 + (i - 2)
if item.zap_cooldown_remaining:
self.inv_captions[i].text = f"[{item.zap_cooldown_remaining}] {item.text})"
else:
self.inv_captions[i].text = item.text
self.inv_captions[i].fill_color = item.text_color
def lv_planner(self, target_level):
"""Plan room sequence in levels > 9"""
monsters = (target_level - 6) // 2
target_rooms = min(int(target_level // 2), 6)
target_treasure = min(int(target_level // 3), 4)
rooms = []
for i in range(target_rooms):
rooms.append([])
for o in ("spawn", "boulder", "button", "exit"):
r = random.randint(0, target_rooms-1)
rooms[r].append(o)
monster_table = {
"rat": int(monsters * 0.8) + 2,
"big rat": max(int(monsters * 0.2) - 2, 0),
"cyclops": max(int(monsters * 0.1) - 3, 0)
}
monster_table = {k: v for k, v in monster_table.items() if v > 0}
monster_names = list(monster_table.keys())
monster_weights = [monster_table[k] for k in monster_names]
for m in range(monsters):
r = random.randint(0, target_rooms - 1)
rooms[r].append(random.choices(monster_names, weights = monster_weights)[0])
for t in range(target_treasure):
r = random.randint(0, target_rooms - 1)
rooms[r].append("treasure")
return rooms
def treasure_planner(self, treasure_level):
"""Plan treasure contents at all levels"""
# find item name in base_wts key (base weight of the category)
#base_weight = lambda s: base_wts[list([t for t in base_wts.keys() if s in t])[0]]
#weights = {d[0]: base_weight(d[0]) for d in item_minlv.items() if treasure_level > d[1]}
#if self.player.archetype is None:
# prefs = []
#elif self.player.archetype == "viking":
# prefs = ["axe2", "axe3", "green_pot"]
#elif self.player.archetype == "knight":
# prefs = ["sword2", "shield", "grey_pot"]
#elif self.player.archetype == "wizard":
# prefs = ["staff", "staff2", "blue_pot"]
#for i in prefs:
# if i in weights: weights[i] *= 3
weights = {}
for item in itemdata:
data = itemdata[item]
if data.min_lv > treasure_level or treasure_level > data.max_lv: continue
weights[item] = data.base_wt
if self.player.archetype is not None and data.affinity == self.player.archetype:
weights[item] *= 3
return weights
def start(self):
resources.play_sfx(1)
mcrfpy.setScene("play")
mcrfpy.keypressScene(self.cos_keys)
def add_entity(self, e:ce.COSEntity):
self.entities.append(e)
self.entities.sort(key = lambda e: e.draw_order, reverse=False)
# hack / workaround for grid.entities not interable
while len(self.grid.entities): # while there are entities on the grid,
self.grid.entities.remove(0) # remove the 1st ("0th")
for e in self.entities:
self.grid.entities.append(e._entity)
def create_level(self, depth, _luck = 0):
#if depth < 3:
# features = None
self.level = cl.Level(20, 20)
self.grid = self.level.grid
if depth in self.level_plan:
plan = self.level_plan[depth]
else:
plan = self.lv_planner(depth)
coords = self.level.generate(plan)
self.entities = []
if self.player:
luck = self.player.luck
else:
luck = 0
buttons = []
for k, v in sorted(coords, key=lambda i: i[0]): # "button" before "exit"; "button", "button", "door", "exit" -> alphabetical is correct sequence
if k == "spawn":
if self.player:
self.add_entity(self.player)
#self.player.draw_pos = v
self.spawn_point = v
elif k == "boulder":
ce.BoulderEntity(v[0], v[1], game=self)
elif k == "treasure":
ce.TreasureEntity(v[0], v[1], treasure_table = self.treasure_planner(depth + luck), game=self)
elif k == "button":
buttons.append(v)
elif k == "exit":
btn = buttons.pop(0)
ce.ExitEntity(v[0], v[1], btn[0], btn[1], game=self)
elif k == "rat":
ce.EnemyEntity(*v, game=self)
elif k == "big rat":
ce.EnemyEntity(*v, game=self, base_damage=2, hp=4, sprite=130)
elif k == "cyclops":
ce.EnemyEntity(*v, game=self, base_damage=3, hp=8, sprite=109, base_defense=2)
#if self.depth > 2:
#for i in range(10):
# self.spawn_test_rat()
def stuck(self, sweet_btn, args):
if args[3] == "end": return
self.create_level(self.depth)
self.swap_level(self.level, self.spawn_point)
def cos_keys(self, key, state):
d = None
if state == "end": return
elif key == "Grave":
code.InteractiveConsole(locals=globals()).interact()
return
elif key == "Z":
self.player.do_zap()
self.enemy_turn()
return
elif key == "W": d = (0, -1)
elif key == "A": d = (-1, 0)
elif key == "S": d = (0, 1)
elif key == "D": d = (1, 0)
elif key == "Num1":
if len(self.player.inventory) > 0:
self.player.inventory[0].consume(self.player)
self.player.inventory[0] = None
self.enemy_turn()
else:
print("No item")
elif key == "Num2":
if len(self.player.inventory) > 1:
self.player.inventory[1].consume(self.player)
self.player.inventory[1] = None
else:
print("No item")
elif key == "Num3":
if len(self.player.inventory) > 2:
self.player.inventory[2].consume(self.player)
self.player.inventory[2] = None
else:
print("No item")
#elif key == "M": self.level.generate()
#elif key == "R":
# self.level.reset()
# self.possibilities = None
#elif key == "T":
# self.level.split()
# self.possibilities = None
#elif key == "Y": self.level.split(single=True)
#elif key == "U": self.level.highlight(+1)
#elif key == "I": self.level.highlight(-1)
#elif key == "O":
# self.level.wall_rooms()
# self.possibilities = None
#elif key == "P": ct.format_tiles(self.grid)
#elif key == "P":
#self.possibilities = ct.wfc_pass(self.grid, self.possibilities)
elif key == "P":
self.depth += 1
print(f"Descending: lv {self.depth}")
self.stuck(None, [1,2,3,4])
elif key == "Period":
self.enemy_turn()
elif key == "X":
self.pull_boulder_search()
else:
print(key)
if d:
self.entities.sort(key = lambda e: e.draw_order, reverse=False)
self.player.try_move(*d)
self.enemy_turn()
def enemy_turn(self):
self.entities.sort(key = lambda e: e.draw_order, reverse=False)
for e in self.entities:
e.act()
# end of enemy turn = player turn
for i in self.player.equipped:
i.tick()
self.gui_update()
def pull_boulder_search(self):
for dx, dy in ( (0, -1), (-1, 0), (1, 0), (0, 1) ):
for e in self.entities:
if e.draw_pos != (self.player.draw_pos[0] + dx, self.player.draw_pos[1] + dy): continue
if type(e) == ce.BoulderEntity:
self.pull_boulder_move((dx, dy), e)
return self.enemy_turn()
else:
print("No boulder found to pull.")
def pull_boulder_move(self, p, target_boulder):
print(p, target_boulder)
self.entities.sort(key = lambda e: e.draw_order, reverse=False)
if self.player.try_move(-p[0], -p[1], test=True):
old_pos = self.player.draw_pos
self.player.try_move(-p[0], -p[1])
target_boulder.do_move(*old_pos)
def swap_level(self, new_level, spawn_point):
self.level = new_level
self.grid = self.level.grid
self.grid.zoom = 2.0
# TODO, make an entity mover function
#self.add_entity(self.player)
self.player.grid = self.grid
self.player.draw_pos = spawn_point
#self.grid.entities.append(self.player._entity)
# reform UI (workaround to ui collection iterators crashing)
while len(self.ui) > 0:
try:
self.ui.remove(0)
except:
pass
self.ui.append(self.grid)
self.ui.append(self.stuck_btn.base_frame)
self.ui.append(self.headsup)
self.level_caption.text = f"Level: {self.depth}"
self.ui.append(self.sidebar)
self.gui_update()
class SweetButton:
def __init__(self, ui:mcrfpy.UICollection,
pos:"Tuple[int, int]",
caption:str, font=font, font_size=24, font_color=(255,255,255), font_outline_color=(0, 0, 0), font_outline_width=2,
shadow_offset = 8, box_width=200, box_height = 80, shadow_color=(64, 64, 86), box_color=(96, 96, 160),
icon=4, icon_scale=1.75, shadow=True, click=lambda *args: None):
self.ui = ui
#self.shadow_box = mcrfpy.Frame
x, y = pos
# box w/ drop shadow
self.shadow_offset = shadow_offset
self.base_frame = mcrfpy.Frame(x, y, box_width+shadow_offset, box_height, fill_color = (0, 0, 0, 255))
self.base_frame.click = self.do_click
# drop shadow won't need configured, append directly
if shadow:
self.base_frame.children.append(mcrfpy.Frame(0, 0, box_width, box_height, fill_color = shadow_color))
# main button is where the content lives
self.main_button = mcrfpy.Frame(shadow_offset, shadow_offset, box_width, box_height, fill_color = box_color)
self.click = click
self.base_frame.children.append(self.main_button)
# main button icon
self.icon = mcrfpy.Sprite(0, 3, btn_tex, icon, icon_scale)
self.main_button.children.append(self.icon)
# main button caption
self.caption = mcrfpy.Caption((40, 3), caption, font, fill_color=font_color)
self.caption.size = font_size
self.caption.outline_color=font_outline_color
self.caption.outline=font_outline_width
self.main_button.children.append(self.caption)
def unpress(self):
"""Helper func for when graphics changes or glitches make the button stuck down"""
self.main_button.x, self.main_button.y = (self.shadow_offset, self.shadow_offset)
def do_click(self, x, y, mousebtn, event):
if event == "start":
self.main_button.x, self.main_button.y = (0, 0)
elif event == "end":
self.main_button.x, self.main_button.y = (self.shadow_offset, self.shadow_offset)
result = self.click(self, (x, y, mousebtn, event))
if result: # return True from event function to instantly un-pop
self.main_button.x, self.main_button.y = (self.shadow_offset, self.shadow_offset)
@property
def text(self):
return self.caption.text
@text.setter
def text(self, value):
self.caption.text = value
@property
def sprite_number(self):
return self.icon.sprite_number
@sprite_number.setter
def sprite_number(self, value):
self.icon.sprite_number = value
class MainMenu:
def __init__(self):
mcrfpy.createScene("menu")
self.ui = mcrfpy.sceneUI("menu")
mcrfpy.setScene("menu")
self.crypt = None
components = []
# demo grid
self.demo = cl.Level(20, 20)
self.grid = self.demo.grid
self.grid.zoom = 1.75
coords = self.demo.generate(
[("boulder", "boulder", "rat", "cyclops", "boulder"), ("spawn"), ("rat", "big rat"), ("button", "boulder", "exit")]
)
self.entities = []
self.add_entity = lambda e: self.entities.append(e)
#self.create_level = lambda *args: None
buttons = []
#self.depth = 20
for k, v in sorted(coords, key=lambda i: i[0]): # "button" before "exit"; "button", "button", "door", "exit" -> alphabetical is correct sequence
if k == "spawn":
self.player = ce.PlayerEntity(game=self)
self.player.draw_pos = v
#if self.player:
# self.add_entity(self.player)
# #self.player.draw_pos = v
# self.spawn_point = v
elif k == "boulder":
ce.BoulderEntity(v[0], v[1], game=self)
elif k == "treasure":
ce.TreasureEntity(v[0], v[1], treasure_table = {}, game=self)
elif k == "button":
buttons.append(v)
elif k == "exit":
btn = buttons.pop(0)
ce.ExitEntity(v[0], v[1], btn[0], btn[1], game=self)
elif k == "rat":
ce.EnemyEntity(*v, game=self)
elif k == "big rat":
ce.EnemyEntity(*v, game=self, base_damage=2, hp=4, sprite=124)
elif k == "cyclops":
ce.EnemyEntity(*v, game=self, base_damage=3, hp=8, sprite=109, base_defense=2, can_push=True, move_cooldown=0)
#self.demo = cl.Level(20, 20)
#self.create_level(self.depth)
for e in self.entities:
self.grid.entities.append(e._entity)
def just_wiggle(*args):
try:
self.player.try_move(*random.choice(((1, 0),(-1, 0),(0, 1),(0, -1))))
for e in self.entities:
e.act()
except:
pass
mcrfpy.setTimer("demo_motion", just_wiggle, 100)
components.append(
self.demo.grid
)
# title text
drop_shadow = mcrfpy.Caption((150, 10), "Crypt Of Sokoban", font, fill_color=(96, 96, 96), outline_color=(192, 0, 0))
drop_shadow.outline = 3
drop_shadow.size = 64
components.append(
drop_shadow
)
title_txt = mcrfpy.Caption((158, 18), "Crypt Of Sokoban", font, fill_color=(255, 255, 255))
title_txt.size = 64
components.append(
title_txt
)
# toast: text over the demo grid that fades out on a timer
self.toast = mcrfpy.Caption((150, 400), "", font, fill_color=(0, 0, 0))
self.toast.size = 28
self.toast.outline = 2
self.toast.outline_color = (255, 255, 255)
self.toast_event = None
components.append(self.toast)
# button - PLAY
#playbtn = mcrfpy.Frame(284, 548, 456, 120, fill_color =
play_btn = SweetButton(self.ui, (20, 248), "PLAY", box_width=200, box_height=110, icon=1, icon_scale=2.0, click=self.play)
components.append(play_btn.base_frame)
# button - config menu pane
#self.config = lambda self, sweet_btn, *args: print(f"boop, sweet button {sweet_btn} config {args}")
config_btn = SweetButton(self.ui, (10, 678), "Settings", icon=2, click=self.show_config)
components.append(config_btn.base_frame)
# button - insta-1080p scaling
scale_btn = SweetButton(self.ui, (10+256, 678), "Scale up\nto 1080p", icon=15, click=self.scale)
self.scaled = False
components.append(scale_btn.base_frame)
# button - music toggle
music_btn = SweetButton(self.ui, (10+256*2, 678), "Music\nON", icon=12, click=self.music_toggle)
resources.music_enabled = True
resources.music_volume = 40
components.append(music_btn.base_frame)
# button - sfx toggle
sfx_btn = SweetButton(self.ui, (10+256*3, 678), "SFX\nON", icon=0, click=self.sfx_toggle)
resources.sfx_enabled = True
resources.sfx_volume = 40
components.append(sfx_btn.base_frame)
[self.ui.append(e) for e in components]
def toast_say(self, txt, delay=10):
"kick off a toast event"
if self.toast_event is not None:
mcrfpy.delTimer("toast_timer")
self.toast.text = txt
self.toast_event = 350
self.toast.fill_color = (255, 255, 255, 255)
self.toast.outline = 2
self.toast.outline_color = (0, 0, 0, 255)
mcrfpy.setTimer("toast_timer", self.toast_callback, 100)
def toast_callback(self, *args):
"fade out the toast text"
self.toast_event -= 5
if self.toast_event < 0:
self.toast_event = None
mcrfpy.delTimer("toast_timer")
mcrfpy.text = ""
return
a = min(self.toast_event, 255)
self.toast.fill_color = (255, 255, 255, a)
self.toast.outline_color = (0, 0, 0, a)
def show_config(self, sweet_btn, args):
self.toast_say("Beep, Boop! Configurations will go here.")
def play(self, sweet_btn, args):
#if args[3] == "start": return # DRAMATIC on release action!
if args[3] == "end": return
self.crypt = Crypt()
#mcrfpy.setScene("play")
self.crypt.start()
def scale(self, sweet_btn, args, window_scale=None):
if args[3] == "end": return
if not window_scale:
self.scaled = not self.scaled
window_scale = 1.3
else:
self.scaled = True
sweet_btn.unpress()
if self.scaled:
self.toast_say("Windowed mode only, sorry!\nCheck Settings for for fine-tuned controls.")
mcrfpy.setScale(window_scale)
sweet_btn.text = "Scale down\n to 1.0x"
else:
mcrfpy.setScale(1.0)
sweet_btn.text = "Scale up\nto 1080p"
def music_toggle(self, sweet_btn, args):
if args[3] == "end": return
resources.music_enabled = not resources.music_enabled
print(f"music: {resources.music_enabled}")
if resources.music_enabled:
mcrfpy.setMusicVolume(self.music_volume)
sweet_btn.text = "Music is ON"
sweet_btn.sprite_number = 12
else:
self.toast_say("Use your volume keys or\nlook in Settings for a volume meter.")
mcrfpy.setMusicVolume(0)
sweet_btn.text = "Music is OFF"
sweet_btn.sprite_number = 17
def sfx_toggle(self, sweet_btn, args):
if args[3] == "end": return
resources.sfx_enabled = not resources.sfx_enabled
#print(f"sfx: {resources.sfx_enabled}")
if resources.sfx_enabled:
mcrfpy.setSoundVolume(self.sfx_volume)
sweet_btn.text = "SFX are ON"
sweet_btn.sprite_number = 0
else:
self.toast_say("Use your volume keys or\nlook in Settings for a volume meter.")
mcrfpy.setSoundVolume(0)
sweet_btn.text = "SFX are OFF"
sweet_btn.sprite_number = 17
mainmenu = MainMenu()
import time
def stress_test(*args):
global running
global timers
if not running:
print("stress test initial")
running = True
timers.append("recurse")
add_ding()
add_dong()
mcrfpy.setTimer("recurse", stress_test, 1000)
mcrfpy.setTimer("terminate", lambda *args: mcrfpy.delTimer("recurse"), 30000)
ding(); dong()
else:
#print("stress test random activity")
#random.choice([
# add_ding,
# add_dong,
# remove_random
# ])()
#print(timers)
print("Segfaultin' time")
mcrfpy.delTimer("recurse")
print("Does this still work?")
time.sleep(0.5)
print("How about now?")
stress_test()
# Loading Screen
mcrfpy.createScene("loading")
ui = mcrfpy.sceneUI("loading")
#mcrfpy.setScene("loading")
logo_texture = mcrfpy.Texture("assets/temp_logo.png", 1024, 1024)#1, 1)
logo_sprite = mcrfpy.Sprite(50, 50, logo_texture, 0, 0.5)
ui.append(logo_sprite)
logo_sprite.click = lambda *args: mcrfpy.setScene("menu")
logo_caption = mcrfpy.Caption(70, 600, "Click to Proceed", font, (255, 0, 0, 255), (0, 0, 0, 255))
logo_caption.fill_color =(255, 0, 0, 255)
ui.append(logo_caption)
# menu screen
mcrfpy.createScene("menu")
for e in [
mcrfpy.Caption(10, 10, "Crypt of Sokoban", font, (255, 255, 255), (0, 0, 0)),
mcrfpy.Caption(20, 55, "a McRogueFace demo project", font, (192, 192, 192), (0, 0, 0)),
mcrfpy.Frame(15, 70, 150, 60, fill_color=(64, 64, 128)),
mcrfpy.Frame(15, 145, 150, 60, fill_color=(64, 64, 128)),
mcrfpy.Frame(15, 220, 150, 60, fill_color=(64, 64, 128)),
mcrfpy.Frame(15, 295, 150, 60, fill_color=(64, 64, 128)),
#mcrfpy.Frame(900, 10, 100, 100, fill_color=(255, 0, 0)),
]:
mcrfpy.sceneUI("menu").append(e)
def click_once(fn):
def wraps(*args, **kwargs):
#print(args)
action = args[3]
if action != "start": return
return fn(*args, **kwargs)
return wraps
@click_once
def asdf(x, y, btn, action):
print(f"clicky @({x},{y}) {action}->{btn}")
@click_once
def clicked_exit(*args):
mcrfpy.exit()
menu_btns = [
("Boom", lambda *args: 1 / 0),
("Exit", clicked_exit),
("About", lambda *args: mcrfpy.setScene("about")),
("Settings", lambda *args: mcrfpy.setScene("settings")),
("Start", lambda *args: mcrfpy.setScene("play"))
]
for i in range(len(mcrfpy.sceneUI("menu"))):
e = mcrfpy.sceneUI("menu")[i] # TODO - fix iterator
#print(e, type(e))
if type(e) is not mcrfpy.Frame: continue
label, fn = menu_btns.pop()
#print(label)
e.children.append(mcrfpy.Caption(5, 5, label, font, (192, 192, 255), (0,0,0)))
e.click = fn
# settings screen
mcrfpy.createScene("settings")
window_scaling = 1.0
scale_caption = mcrfpy.Caption(180, 70, "1.0x", font, (255, 255, 255), (0, 0, 0))
scale_caption.fill_color = (255, 255, 255) # TODO - mcrfpy.Caption.__init__ is not setting colors
for e in [
mcrfpy.Caption(10, 10, "Settings", font, (255, 255, 255), (0, 0, 0)),
mcrfpy.Frame(15, 70, 150, 60, fill_color=(64, 64, 128)), # +
mcrfpy.Frame(300, 70, 150, 60, fill_color=(64, 64, 128)), # -
mcrfpy.Frame(15, 295, 150, 60, fill_color=(64, 64, 128)),
scale_caption,
]:
mcrfpy.sceneUI("settings").append(e)
@click_once
def game_scale(x, y, btn, action, delta):
global window_scaling
print(f"WIP - scale the window from {window_scaling:.1f} to {window_scaling+delta:.1f}")
window_scaling += delta
scale_caption.text = f"{window_scaling:.1f}x"
mcrfpy.setScale(window_scaling)
#mcrfpy.setScale(2)
settings_btns = [
("back", lambda *args: mcrfpy.setScene("menu")),
("-", lambda x, y, btn, action: game_scale(x, y, btn, action, -0.1)),
("+", lambda x, y, btn, action: game_scale(x, y, btn, action, +0.1))
]
for i in range(len(mcrfpy.sceneUI("settings"))):
e = mcrfpy.sceneUI("settings")[i] # TODO - fix iterator
#print(e, type(e))
if type(e) is not mcrfpy.Frame: continue
label, fn = settings_btns.pop()
#print(label, fn)
e.children.append(mcrfpy.Caption(5, 5, label, font, (192, 192, 255), (0,0,0)))
e.click = fn

View File

@ -1,144 +0,0 @@
145# open space
???
?_?
???
184:0.03# open space variant
???
?_?
???
146# lone wall / pillar
___
_X_
___
132# top left corner
?_?
_XX
?X?
133# plain horizontal wall
???
XXX
?_?
182:0.04# plain horizontal wall variant
???
XXX
?_?
183:0.04# plain horizontal wall variant
???
XXX
?_?
157:0.01# plain horizontal wall variant
???
XXX
?_?
135# top right corner
?_?
XX_
?X?
144@N132@s144@n144@n192@s192@S156@n171@s169@n180# Left side wall. Space on both sides rule may make the dungeon less robust (no double-walls allowed)
?X?
?X_
?X?
147@N135@s147@n147@n193@s193@S159@n170@s168@n181# Right side wall
?X?
_X?
?X?
156# bottom left corner
?X?
_XX
?_?
159# bottom right corner
?X?
XX_
?_?
192@n144@s144@s169# vertical T, left wall
?X?
?XX
?X?
193@n147@s147@s168# vertical T, right wall
?X?
XX?
?X?
180@s144@s144@s169# horizontal T, left wall
???
XXX
?X?
181@s147@s147@s168# horizontal T, right wall
???
XXX
?X?
195@W133@W182@W183@W157# wall for edge of a gap
??_
XX_
?__
195
?__
XX_
?__
194@E133@E182@E183@E157# wall for edge of a gap (R)
_??
_XX
__?
194
__?
_XX
__?
195@W133@W182@W183@W157# wall for edge of a gap
?__
XX_
??_
194@E133@E182@E183@E157# wall for edge of a gap (R)
__?
_XX
_??
195@W133@W182@W183@W157# wall for edge of a gap
??_
XX_
?__
194@E133@E182@E183@E157# wall for edge of a gap (R)
_??
_XX
__?
168@n147@n170@n135@n181@n193# right vertical wall, gap below
?X?
_X_
?_?
169@n144@n171@n132@n192@n180# left vertical wall, gap below
?X?
_X_
?_?
170@s147@s168@s133@s182@s183@s157@s193@s181# right vertical wall, gap above
?_?
_X_
?X?
171@s144@s169@s133@s182@s183@s157@s171@s180# left vertical wall, gap above
?_?
_X_
?X?

View File

@ -1,81 +0,0 @@
#!/usr/bin/env python3
"""Example of CORRECT test pattern using timer callbacks for automation"""
import mcrfpy
from mcrfpy import automation
from datetime import datetime
def run_automation_tests():
"""This runs AFTER the game loop has started and rendered frames"""
print("\n=== Automation Test Running (1 second after start) ===")
# NOW we can take screenshots that will show content!
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"WORKING_screenshot_{timestamp}.png"
# Take screenshot - this should now show our red frame
result = automation.screenshot(filename)
print(f"Screenshot taken: {filename} - Result: {result}")
# Test clicking on the frame
automation.click(200, 200) # Click in center of red frame
# Test keyboard input
automation.typewrite("Hello from timer callback!")
# Take another screenshot to show any changes
filename2 = f"WORKING_screenshot_after_click_{timestamp}.png"
automation.screenshot(filename2)
print(f"Second screenshot: {filename2}")
print("Test completed successfully!")
print("\nThis works because:")
print("1. The game loop has been running for 1 second")
print("2. The scene has been rendered multiple times")
print("3. The RenderTexture now contains actual rendered content")
# Cancel this timer so it doesn't repeat
mcrfpy.delTimer("automation_test")
# Optional: exit after a moment
def exit_game():
print("Exiting...")
mcrfpy.exit()
mcrfpy.setTimer("exit", exit_game, 500) # Exit 500ms later
# This code runs during --exec script execution
print("=== Setting Up Test Scene ===")
# Create scene with visible content
mcrfpy.createScene("timer_test_scene")
mcrfpy.setScene("timer_test_scene")
ui = mcrfpy.sceneUI("timer_test_scene")
# Add a bright red frame that should be visible
frame = mcrfpy.Frame(100, 100, 400, 300,
fill_color=mcrfpy.Color(255, 0, 0), # Bright red
outline_color=mcrfpy.Color(255, 255, 255), # White outline
outline=5.0)
ui.append(frame)
# Add text
caption = mcrfpy.Caption(mcrfpy.Vector(150, 150),
text="TIMER TEST - SHOULD BE VISIBLE",
fill_color=mcrfpy.Color(255, 255, 255))
caption.size = 24
frame.children.append(caption)
# Add click handler to demonstrate interaction
def frame_clicked(x, y, button):
print(f"Frame clicked at ({x}, {y}) with button {button}")
frame.click = frame_clicked
print("Scene setup complete. Setting timer for automation tests...")
# THIS IS THE KEY: Set timer to run AFTER the game loop starts
mcrfpy.setTimer("automation_test", run_automation_tests, 1000)
print("Timer set. Game loop will start after this script completes.")
print("Automation tests will run 1 second later when content is visible.")
# Script ends here - game loop starts next

View File

@ -1,165 +0,0 @@
#!/usr/bin/env python3
"""Animation System Demo - Shows all animation capabilities"""
import mcrfpy
import math
# Create main scene
mcrfpy.createScene("animation_demo")
ui = mcrfpy.sceneUI("animation_demo")
mcrfpy.setScene("animation_demo")
# Title
title = mcrfpy.Caption((400, 30), "McRogueFace Animation System Demo", mcrfpy.default_font)
title.size = 24
title.fill_color = (255, 255, 255)
# Note: centered property doesn't exist for Caption
ui.append(title)
# 1. Position Animation Demo
pos_frame = mcrfpy.Frame(50, 100, 80, 80)
pos_frame.fill_color = (255, 100, 100)
pos_frame.outline = 2
ui.append(pos_frame)
pos_label = mcrfpy.Caption((50, 80), "Position Animation", mcrfpy.default_font)
pos_label.fill_color = (200, 200, 200)
ui.append(pos_label)
# 2. Size Animation Demo
size_frame = mcrfpy.Frame(200, 100, 50, 50)
size_frame.fill_color = (100, 255, 100)
size_frame.outline = 2
ui.append(size_frame)
size_label = mcrfpy.Caption((200, 80), "Size Animation", mcrfpy.default_font)
size_label.fill_color = (200, 200, 200)
ui.append(size_label)
# 3. Color Animation Demo
color_frame = mcrfpy.Frame(350, 100, 80, 80)
color_frame.fill_color = (255, 0, 0)
ui.append(color_frame)
color_label = mcrfpy.Caption((350, 80), "Color Animation", mcrfpy.default_font)
color_label.fill_color = (200, 200, 200)
ui.append(color_label)
# 4. Easing Functions Demo
easing_y = 250
easing_frames = []
easings = ["linear", "easeIn", "easeOut", "easeInOut", "easeInElastic", "easeOutBounce"]
for i, easing in enumerate(easings):
x = 50 + i * 120
frame = mcrfpy.Frame(x, easing_y, 20, 20)
frame.fill_color = (100, 150, 255)
ui.append(frame)
easing_frames.append((frame, easing))
label = mcrfpy.Caption((x, easing_y - 20), easing, mcrfpy.default_font)
label.size = 12
label.fill_color = (200, 200, 200)
ui.append(label)
# 5. Complex Animation Demo
complex_frame = mcrfpy.Frame(300, 350, 100, 100)
complex_frame.fill_color = (128, 128, 255)
complex_frame.outline = 3
ui.append(complex_frame)
complex_label = mcrfpy.Caption((300, 330), "Complex Multi-Property", mcrfpy.default_font)
complex_label.fill_color = (200, 200, 200)
ui.append(complex_label)
# Start animations
def start_animations(runtime):
# 1. Position animation - back and forth
x_anim = mcrfpy.Animation("x", 500.0, 3.0, "easeInOut")
x_anim.start(pos_frame)
# 2. Size animation - pulsing
w_anim = mcrfpy.Animation("w", 150.0, 2.0, "easeInOut")
h_anim = mcrfpy.Animation("h", 150.0, 2.0, "easeInOut")
w_anim.start(size_frame)
h_anim.start(size_frame)
# 3. Color animation - rainbow cycle
color_anim = mcrfpy.Animation("fill_color", (0, 255, 255, 255), 2.0, "linear")
color_anim.start(color_frame)
# 4. Easing demos - all move up with different easings
for frame, easing in easing_frames:
y_anim = mcrfpy.Animation("y", 150.0, 2.0, easing)
y_anim.start(frame)
# 5. Complex animation - multiple properties
cx_anim = mcrfpy.Animation("x", 500.0, 4.0, "easeInOut")
cy_anim = mcrfpy.Animation("y", 400.0, 4.0, "easeOut")
cw_anim = mcrfpy.Animation("w", 150.0, 4.0, "easeInElastic")
ch_anim = mcrfpy.Animation("h", 150.0, 4.0, "easeInElastic")
outline_anim = mcrfpy.Animation("outline", 10.0, 4.0, "linear")
cx_anim.start(complex_frame)
cy_anim.start(complex_frame)
cw_anim.start(complex_frame)
ch_anim.start(complex_frame)
outline_anim.start(complex_frame)
# Individual color component animations
r_anim = mcrfpy.Animation("fill_color.r", 255.0, 4.0, "easeInOut")
g_anim = mcrfpy.Animation("fill_color.g", 100.0, 4.0, "easeInOut")
b_anim = mcrfpy.Animation("fill_color.b", 50.0, 4.0, "easeInOut")
r_anim.start(complex_frame)
g_anim.start(complex_frame)
b_anim.start(complex_frame)
print("All animations started!")
# Reverse some animations
def reverse_animations(runtime):
# Position back
x_anim = mcrfpy.Animation("x", 50.0, 3.0, "easeInOut")
x_anim.start(pos_frame)
# Size back
w_anim = mcrfpy.Animation("w", 50.0, 2.0, "easeInOut")
h_anim = mcrfpy.Animation("h", 50.0, 2.0, "easeInOut")
w_anim.start(size_frame)
h_anim.start(size_frame)
# Color cycle continues
color_anim = mcrfpy.Animation("fill_color", (255, 0, 255, 255), 2.0, "linear")
color_anim.start(color_frame)
# Easing frames back down
for frame, easing in easing_frames:
y_anim = mcrfpy.Animation("y", 250.0, 2.0, easing)
y_anim.start(frame)
# Continue color cycle
def cycle_colors(runtime):
color_anim = mcrfpy.Animation("fill_color", (255, 255, 0, 255), 2.0, "linear")
color_anim.start(color_frame)
# Info text
info = mcrfpy.Caption((400, 550), "Watch as different properties animate with various easing functions!", mcrfpy.default_font)
info.fill_color = (255, 255, 200)
# Note: centered property doesn't exist for Caption
ui.append(info)
# Schedule animations
mcrfpy.setTimer("start", start_animations, 500)
mcrfpy.setTimer("reverse", reverse_animations, 4000)
mcrfpy.setTimer("cycle", cycle_colors, 2500)
# Exit handler
def on_key(key):
if key == "Escape":
mcrfpy.exit()
mcrfpy.keypressScene(on_key)
print("Animation demo started! Press Escape to exit.")

View File

@ -1,34 +0,0 @@
#!/usr/bin/env python3
"""Test for mcrfpy.createScene() method"""
import mcrfpy
def test_createScene():
"""Test creating a new scene"""
# Test creating scenes
test_scenes = ["test_scene1", "test_scene2", "special_chars_!@#"]
for scene_name in test_scenes:
try:
mcrfpy.createScene(scene_name)
print(f"✓ Created scene: {scene_name}")
except Exception as e:
print(f"✗ Failed to create scene {scene_name}: {e}")
return
# Try to set scene to verify it was created
try:
mcrfpy.setScene("test_scene1")
current = mcrfpy.currentScene()
if current == "test_scene1":
print("✓ Scene switching works correctly")
else:
print(f"✗ Scene switch failed: expected 'test_scene1', got '{current}'")
except Exception as e:
print(f"✗ Scene switching error: {e}")
print("PASS")
# Run test immediately
print("Running createScene test...")
test_createScene()
print("Test completed.")

View File

@ -1,92 +0,0 @@
#!/usr/bin/env python3
"""Test for mcrfpy.keypressScene() - Related to issue #61"""
import mcrfpy
# Track keypresses for different scenes
scene1_presses = []
scene2_presses = []
def scene1_handler(key_code):
"""Handle keyboard events for scene 1"""
scene1_presses.append(key_code)
print(f"Scene 1 key pressed: {key_code}")
def scene2_handler(key_code):
"""Handle keyboard events for scene 2"""
scene2_presses.append(key_code)
print(f"Scene 2 key pressed: {key_code}")
def test_keypressScene():
"""Test keyboard event handling for scenes"""
print("=== Testing mcrfpy.keypressScene() ===")
# Test 1: Basic handler registration
print("\n1. Basic handler registration:")
mcrfpy.createScene("scene1")
mcrfpy.setScene("scene1")
try:
mcrfpy.keypressScene(scene1_handler)
print("✓ Keypress handler registered for scene1")
except Exception as e:
print(f"✗ Failed to register handler: {e}")
print("FAIL")
return
# Test 2: Handler persists across scene changes
print("\n2. Testing handler persistence:")
mcrfpy.createScene("scene2")
mcrfpy.setScene("scene2")
try:
mcrfpy.keypressScene(scene2_handler)
print("✓ Keypress handler registered for scene2")
except Exception as e:
print(f"✗ Failed to register handler for scene2: {e}")
# Switch back to scene1
mcrfpy.setScene("scene1")
current = mcrfpy.currentScene()
print(f"✓ Switched back to: {current}")
# Test 3: Clear handler
print("\n3. Testing handler clearing:")
try:
mcrfpy.keypressScene(None)
print("✓ Handler cleared with None")
except Exception as e:
print(f"✗ Failed to clear handler: {e}")
# Test 4: Re-register handler
print("\n4. Testing re-registration:")
try:
mcrfpy.keypressScene(scene1_handler)
print("✓ Handler re-registered successfully")
except Exception as e:
print(f"✗ Failed to re-register: {e}")
# Test 5: Lambda functions
print("\n5. Testing lambda functions:")
try:
mcrfpy.keypressScene(lambda k: print(f"Lambda key: {k}"))
print("✓ Lambda function accepted as handler")
except Exception as e:
print(f"✗ Failed with lambda: {e}")
# Known issues
print("\n⚠ Known Issues:")
print("- Invalid argument (non-callable) causes segfault")
print("- No way to query current handler")
print("- Handler is global, not per-scene (issue #61)")
# Summary related to issue #61
print("\n📋 Issue #61 Analysis:")
print("Current: mcrfpy.keypressScene() sets a global handler")
print("Proposed: Scene objects should encapsulate their own callbacks")
print("Impact: Currently only one keypress handler active at a time")
print("\n=== Test Complete ===")
print("PASS - API functions correctly within current limitations")
# Run test immediately
test_keypressScene()

View File

@ -1,80 +0,0 @@
#!/usr/bin/env python3
"""Test for mcrfpy.sceneUI() method - Related to issue #28"""
import mcrfpy
from mcrfpy import automation
from datetime import datetime
def test_sceneUI():
"""Test getting UI collection from scene"""
# Create a test scene
mcrfpy.createScene("ui_test_scene")
mcrfpy.setScene("ui_test_scene")
# Get initial UI collection (should be empty)
try:
ui_collection = mcrfpy.sceneUI("ui_test_scene")
print(f"✓ sceneUI returned collection with {len(ui_collection)} items")
except Exception as e:
print(f"✗ sceneUI failed: {e}")
print("FAIL")
return
# Add some UI elements to the scene
frame = mcrfpy.Frame(10, 10, 200, 150,
fill_color=mcrfpy.Color(100, 100, 200),
outline_color=mcrfpy.Color(255, 255, 255),
outline=2.0)
ui_collection.append(frame)
caption = mcrfpy.Caption(mcrfpy.Vector(220, 10),
text="Test Caption",
fill_color=mcrfpy.Color(255, 255, 0))
ui_collection.append(caption)
# Skip sprite for now since it requires a texture
# sprite = mcrfpy.Sprite(10, 170, scale=2.0)
# ui_collection.append(sprite)
# Get UI collection again
ui_collection2 = mcrfpy.sceneUI("ui_test_scene")
print(f"✓ After adding elements: {len(ui_collection2)} items")
# Test iteration (Issue #28 - UICollectionIter)
try:
item_types = []
for item in ui_collection2:
item_types.append(type(item).__name__)
print(f"✓ Iteration works, found types: {item_types}")
except Exception as e:
print(f"✗ Iteration failed (Issue #28): {e}")
# Test indexing
try:
first_item = ui_collection2[0]
print(f"✓ Indexing works, first item type: {type(first_item).__name__}")
except Exception as e:
print(f"✗ Indexing failed: {e}")
# Test invalid scene name
try:
invalid_ui = mcrfpy.sceneUI("nonexistent_scene")
print(f"✗ sceneUI should fail for nonexistent scene, got {len(invalid_ui)} items")
except Exception as e:
print(f"✓ sceneUI correctly fails for nonexistent scene: {e}")
# Take screenshot
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"test_sceneUI_{timestamp}.png"
automation.screenshot(filename)
print(f"Screenshot saved: {filename}")
print("PASS")
# Set up timer to run test
mcrfpy.setTimer("test", test_sceneUI, 1000)
# Cancel timer after running once
def cleanup():
mcrfpy.delTimer("test")
mcrfpy.delTimer("cleanup")
mcrfpy.setTimer("cleanup", cleanup, 1100)

View File

@ -1,44 +0,0 @@
#!/usr/bin/env python3
"""Test for mcrfpy.setScene() and currentScene() methods"""
import mcrfpy
print("Starting setScene/currentScene test...")
# Create test scenes first
scenes = ["scene_A", "scene_B", "scene_C"]
for scene in scenes:
mcrfpy.createScene(scene)
print(f"Created scene: {scene}")
results = []
# Test switching between scenes
for scene in scenes:
try:
mcrfpy.setScene(scene)
current = mcrfpy.currentScene()
if current == scene:
results.append(f"✓ setScene/currentScene works for '{scene}'")
else:
results.append(f"✗ Scene mismatch: set '{scene}', got '{current}'")
except Exception as e:
results.append(f"✗ Error with scene '{scene}': {e}")
# Test invalid scene - it should not change the current scene
current_before = mcrfpy.currentScene()
mcrfpy.setScene("nonexistent_scene")
current_after = mcrfpy.currentScene()
if current_before == current_after:
results.append(f"✓ setScene correctly ignores nonexistent scene (stayed on '{current_after}')")
else:
results.append(f"✗ Scene changed unexpectedly from '{current_before}' to '{current_after}'")
# Print results
for result in results:
print(result)
# Determine pass/fail
if all("" in r for r in results):
print("PASS")
else:
print("FAIL")

View File

@ -1,70 +0,0 @@
#!/usr/bin/env python3
"""Test for mcrfpy.setTimer() and delTimer() methods"""
import mcrfpy
import sys
def test_timers():
"""Test timer API methods"""
print("Testing mcrfpy timer methods...")
# Test 1: Create a simple timer
try:
call_count = [0]
def simple_callback(runtime):
call_count[0] += 1
print(f"Timer callback called, count={call_count[0]}, runtime={runtime}")
mcrfpy.setTimer("test_timer", simple_callback, 100)
print("✓ setTimer() called successfully")
except Exception as e:
print(f"✗ setTimer() failed: {e}")
print("FAIL")
return
# Test 2: Delete the timer
try:
mcrfpy.delTimer("test_timer")
print("✓ delTimer() called successfully")
except Exception as e:
print(f"✗ delTimer() failed: {e}")
print("FAIL")
return
# Test 3: Delete non-existent timer (should not crash)
try:
mcrfpy.delTimer("nonexistent_timer")
print("✓ delTimer() accepts non-existent timer names")
except Exception as e:
print(f"✗ delTimer() failed on non-existent timer: {e}")
print("FAIL")
return
# Test 4: Create multiple timers
try:
def callback1(rt): pass
def callback2(rt): pass
def callback3(rt): pass
mcrfpy.setTimer("timer1", callback1, 500)
mcrfpy.setTimer("timer2", callback2, 750)
mcrfpy.setTimer("timer3", callback3, 250)
print("✓ Multiple timers created successfully")
# Clean up
mcrfpy.delTimer("timer1")
mcrfpy.delTimer("timer2")
mcrfpy.delTimer("timer3")
print("✓ Multiple timers deleted successfully")
except Exception as e:
print(f"✗ Multiple timer test failed: {e}")
print("FAIL")
return
print("\nAll timer API tests passed")
print("PASS")
# Run the test
test_timers()
# Exit cleanly
sys.exit(0)

View File

@ -1,63 +0,0 @@
#!/usr/bin/env python3
"""
Analysis of Issue #78: Middle Mouse Click sends 'C' keyboard event
BUG FOUND in GameEngine::processEvent() at src/GameEngine.cpp
The bug occurs in this code section:
```cpp
if (currentScene()->hasAction(actionCode))
{
std::string name = currentScene()->action(actionCode);
currentScene()->doAction(name, actionType);
}
else if (currentScene()->key_callable)
{
currentScene()->key_callable->call(ActionCode::key_str(event.key.code), actionType);
}
```
ISSUE: When a middle mouse button event occurs and there's no registered action for it,
the code falls through to the key_callable branch. However, it then tries to access
`event.key.code` from what is actually a mouse button event!
Since it's a union, `event.key.code` reads garbage data from the mouse event structure.
The middle mouse button has value 2, which coincidentally matches sf::Keyboard::C (also value 2),
causing the spurious 'C' keyboard event.
SOLUTION: The code should check the event type before accessing event-specific fields:
```cpp
else if (currentScene()->key_callable &&
(event.type == sf::Event::KeyPressed || event.type == sf::Event::KeyReleased))
{
currentScene()->key_callable->call(ActionCode::key_str(event.key.code), actionType);
}
```
TEST STATUS:
- Test Name: automation_click_issue78_test.py
- Method Tested: Middle mouse click behavior
- Pass/Fail: FAIL - Issue #78 confirmed to exist
- Error: Middle mouse clicks incorrectly trigger 'C' keyboard events
- Modifications: None needed - bug is in C++ code, not the test
The test correctly identifies the issue but cannot run in headless mode due to
requiring actual event processing through the game loop.
"""
import mcrfpy
import sys
print(__doc__)
# Demonstrate the issue conceptually
print("\nDemonstration of the bug:")
print("1. Middle mouse button value in SFML: 2")
print("2. Keyboard 'C' value in SFML: 2")
print("3. When processEvent reads event.key.code from a mouse event,")
print(" it gets the value 2, which ActionCode::key_str interprets as 'C'")
print("\nThe fix is simple: add an event type check before accessing key.code")
sys.exit(0)

View File

@ -1,152 +0,0 @@
#!/usr/bin/env python3
"""Test for automation click methods - Related to issue #78 (Middle click sends 'C')"""
import mcrfpy
from datetime import datetime
# Try to import automation, but handle if it doesn't exist
try:
from mcrfpy import automation
HAS_AUTOMATION = True
print("SUCCESS: mcrfpy.automation module imported successfully")
except (ImportError, AttributeError) as e:
HAS_AUTOMATION = False
print(f"WARNING: mcrfpy.automation module not available - {e}")
print("The automation module may not be implemented yet")
# Track events
click_events = []
key_events = []
def click_handler(x, y, button):
"""Track click events"""
click_events.append((x, y, button))
print(f"Click received: ({x}, {y}, button={button})")
def key_handler(key, scancode=None):
"""Track keyboard events"""
key_events.append(key)
print(f"Key received: {key} (scancode: {scancode})")
def test_clicks():
"""Test various click types, especially middle click (Issue #78)"""
if not HAS_AUTOMATION:
print("SKIP - automation module not available")
print("The automation module may not be implemented yet")
return
# Create test scene
mcrfpy.createScene("click_test")
mcrfpy.setScene("click_test")
ui = mcrfpy.sceneUI("click_test")
# Set up keyboard handler to detect Issue #78
mcrfpy.keypressScene(key_handler)
# Create clickable frame
frame = mcrfpy.Frame(50, 50, 300, 200,
fill_color=mcrfpy.Color(100, 100, 200),
outline_color=mcrfpy.Color(255, 255, 255),
outline=2.0)
frame.click = click_handler
ui.append(frame)
caption = mcrfpy.Caption(mcrfpy.Vector(60, 60),
text="Click Test Area",
fill_color=mcrfpy.Color(255, 255, 255))
frame.children.append(caption)
# Test different click types
print("Testing click types...")
# Left click
try:
automation.click(200, 150)
print("✓ Left click sent")
except Exception as e:
print(f"✗ Left click failed: {e}")
# Right click
try:
automation.rightClick(200, 150)
print("✓ Right click sent")
except Exception as e:
print(f"✗ Right click failed: {e}")
# Middle click - This is Issue #78
try:
automation.middleClick(200, 150)
print("✓ Middle click sent")
except Exception as e:
print(f"✗ Middle click failed: {e}")
# Double click
try:
automation.doubleClick(200, 150)
print("✓ Double click sent")
except Exception as e:
print(f"✗ Double click failed: {e}")
# Triple click
try:
automation.tripleClick(200, 150)
print("✓ Triple click sent")
except Exception as e:
print(f"✗ Triple click failed: {e}")
# Click with specific button parameter
try:
automation.click(200, 150, button='middle')
print("✓ Click with button='middle' sent")
except Exception as e:
print(f"✗ Click with button parameter failed: {e}")
# Check results after a delay
def check_results(runtime):
print(f"\nClick events received: {len(click_events)}")
print(f"Keyboard events received: {len(key_events)}")
# Check for Issue #78
if any('C' in str(event) or ord('C') == event for event in key_events):
print("✗ ISSUE #78 CONFIRMED: Middle click sent 'C' keyboard event!")
else:
print("✓ No spurious 'C' keyboard events detected")
# Analyze click events
for event in click_events:
print(f" Click: {event}")
# Take screenshot
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = f"test_clicks_issue78_{timestamp}.png"
automation.screenshot(filename)
print(f"Screenshot saved: {filename}")
if len(click_events) > 0:
print("PASS - Clicks detected")
else:
print("FAIL - No clicks detected (may be headless limitation)")
mcrfpy.delTimer("check_results")
mcrfpy.setTimer("check_results", check_results, 2000)
# Set up timer to run test
print("Setting up test timer...")
mcrfpy.setTimer("test", test_clicks, 1000)
# Cancel timer after running once
def cleanup():
mcrfpy.delTimer("test")
mcrfpy.delTimer("cleanup")
mcrfpy.setTimer("cleanup", cleanup, 1100)
# Exit after test completes
def exit_test():
print("\nTest completed - exiting")
import sys
sys.exit(0)
mcrfpy.setTimer("exit", exit_test, 5000)
print("Test script initialized, waiting for timers...")

View File

@ -1,96 +0,0 @@
#!/usr/bin/env python3
"""Test for mcrfpy.automation.screenshot()"""
import mcrfpy
from mcrfpy import automation
from datetime import datetime
import os
import sys
import time
runs = 0
def test_screenshot(*args):
"""Test screenshot functionality"""
#global runs
#runs += 1
#if runs < 2:
# print("tick")
# return
#print("tock")
#mcrfpy.delTimer("timer1")
# Create a scene with some visual elements
mcrfpy.createScene("screenshot_test")
mcrfpy.setScene("screenshot_test")
ui = mcrfpy.sceneUI("screenshot_test")
# Add some colorful elements
frame1 = mcrfpy.Frame(10, 10, 200, 150,
fill_color=mcrfpy.Color(255, 0, 0),
outline_color=mcrfpy.Color(255, 255, 255),
outline=3.0)
ui.append(frame1)
frame2 = mcrfpy.Frame(220, 10, 200, 150,
fill_color=mcrfpy.Color(0, 255, 0),
outline_color=mcrfpy.Color(0, 0, 0),
outline=2.0)
ui.append(frame2)
caption = mcrfpy.Caption(mcrfpy.Vector(10, 170),
text="Screenshot Test Scene",
fill_color=mcrfpy.Color(255, 255, 0))
caption.size = 24
ui.append(caption)
# Test multiple screenshots
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filenames = []
# Test 1: Basic screenshot
try:
filename1 = f"test_screenshot_basic_{timestamp}.png"
result = automation.screenshot(filename1)
filenames.append(filename1)
print(f"✓ Basic screenshot saved: {filename1} (result: {result})")
except Exception as e:
print(f"✗ Basic screenshot failed: {e}")
print("FAIL")
sys.exit(1)
# Test 2: Screenshot with special characters in filename
try:
filename2 = f"test_screenshot_special_chars_{timestamp}_test.png"
result = automation.screenshot(filename2)
filenames.append(filename2)
print(f"✓ Screenshot with special filename saved: {filename2} (result: {result})")
except Exception as e:
print(f"✗ Special filename screenshot failed: {e}")
# Test 3: Invalid filename (if applicable)
try:
result = automation.screenshot("")
print(f"✗ Empty filename should fail but returned: {result}")
except Exception as e:
print(f"✓ Empty filename correctly rejected: {e}")
# Check files exist immediately
files_found = 0
for filename in filenames:
if os.path.exists(filename):
size = os.path.getsize(filename)
print(f"✓ File exists: {filename} ({size} bytes)")
files_found += 1
else:
print(f"✗ File not found: {filename}")
if files_found == len(filenames):
print("PASS")
sys.exit(0)
else:
print("FAIL")
sys.exit(1)
print("Set callback")
mcrfpy.setTimer("timer1", test_screenshot, 1000)
# Run the test immediately
#test_screenshot()

View File

@ -1,30 +0,0 @@
#!/usr/bin/env python3
"""Simple test for mcrfpy.automation.screenshot()"""
import mcrfpy
from mcrfpy import automation
import os
import sys
# Create a simple scene
mcrfpy.createScene("test")
mcrfpy.setScene("test")
# Take a screenshot immediately
try:
filename = "test_screenshot.png"
result = automation.screenshot(filename)
print(f"Screenshot result: {result}")
# Check if file exists
if os.path.exists(filename):
size = os.path.getsize(filename)
print(f"PASS - Screenshot saved: {filename} ({size} bytes)")
else:
print(f"FAIL - Screenshot file not created: {filename}")
except Exception as e:
print(f"FAIL - Screenshot error: {e}")
import traceback
traceback.print_exc()
# Exit immediately
sys.exit(0)

View File

@ -1,49 +0,0 @@
#!/usr/bin/env python3
"""Debug rendering to find why screenshots are transparent"""
import mcrfpy
from mcrfpy import automation
import sys
# Check if we're in headless mode
print("=== Debug Render Test ===")
print(f"Module loaded: {mcrfpy}")
print(f"Automation available: {'automation' in dir(mcrfpy)}")
# Try to understand the scene state
print("\nCreating and checking scene...")
mcrfpy.createScene("debug_scene")
mcrfpy.setScene("debug_scene")
current = mcrfpy.currentScene()
print(f"Current scene: {current}")
# Get UI collection
ui = mcrfpy.sceneUI("debug_scene")
print(f"UI collection type: {type(ui)}")
print(f"Initial UI elements: {len(ui)}")
# Add a simple frame
frame = mcrfpy.Frame(0, 0, 100, 100,
fill_color=mcrfpy.Color(255, 255, 255))
ui.append(frame)
print(f"After adding frame: {len(ui)} elements")
# Check if the issue is with timing
print("\nTaking immediate screenshot...")
result1 = automation.screenshot("debug_immediate.png")
print(f"Immediate screenshot result: {result1}")
# Maybe we need to let the engine process the frame?
# In headless mode with --exec, the game loop might not be running
print("\nNote: In --exec mode, the game loop doesn't run continuously.")
print("This might prevent rendering from occurring.")
# Let's also check what happens with multiple screenshots
for i in range(3):
result = automation.screenshot(f"debug_multi_{i}.png")
print(f"Screenshot {i}: {result}")
print("\nConclusion: The issue appears to be that in --exec mode,")
print("the render loop never runs, so nothing is drawn to the RenderTexture.")
print("The screenshot captures an uninitialized/unrendered texture.")
sys.exit(0)

View File

@ -1,2 +0,0 @@
# This script is intentionally empty
pass

View File

@ -1,7 +0,0 @@
#!/usr/bin/env python3
"""Test if calling mcrfpy.exit() prevents the >>> prompt"""
import mcrfpy
print("Calling mcrfpy.exit() immediately...")
mcrfpy.exit()
print("This should not print if exit worked")

View File

@ -1,29 +0,0 @@
#!/usr/bin/env python3
"""Force Python to be non-interactive"""
import sys
import os
print("Attempting to force non-interactive mode...")
# Remove ps1/ps2 if they exist
if hasattr(sys, 'ps1'):
delattr(sys, 'ps1')
if hasattr(sys, 'ps2'):
delattr(sys, 'ps2')
# Set environment variable
os.environ['PYTHONSTARTUP'] = ''
# Try to set stdin to non-interactive
try:
import fcntl
import termios
# Make stdin non-interactive by removing ICANON flag
attrs = termios.tcgetattr(0)
attrs[3] = attrs[3] & ~termios.ICANON
termios.tcsetattr(0, termios.TCSANOW, attrs)
print("Modified terminal attributes")
except:
print("Could not modify terminal attributes")
print("Script complete")

View File

@ -1,129 +0,0 @@
#!/usr/bin/env python3
"""Generate caption documentation screenshot with proper font"""
import mcrfpy
from mcrfpy import automation
import sys
def capture_caption(runtime):
"""Capture caption example after render loop starts"""
# Take screenshot
automation.screenshot("mcrogueface.github.io/images/ui_caption_example.png")
print("Caption screenshot saved!")
# Exit after capturing
sys.exit(0)
# Create scene
mcrfpy.createScene("captions")
# Title
title = mcrfpy.Caption(400, 30, "Caption Examples")
title.font = mcrfpy.default_font
title.font_size = 28
title.font_color = (255, 255, 255)
# Different sizes
size_label = mcrfpy.Caption(100, 100, "Different Sizes:")
size_label.font = mcrfpy.default_font
size_label.font_color = (200, 200, 200)
large = mcrfpy.Caption(300, 100, "Large Text (24pt)")
large.font = mcrfpy.default_font
large.font_size = 24
large.font_color = (255, 255, 255)
medium = mcrfpy.Caption(300, 140, "Medium Text (18pt)")
medium.font = mcrfpy.default_font
medium.font_size = 18
medium.font_color = (255, 255, 255)
small = mcrfpy.Caption(300, 170, "Small Text (14pt)")
small.font = mcrfpy.default_font
small.font_size = 14
small.font_color = (255, 255, 255)
# Different colors
color_label = mcrfpy.Caption(100, 230, "Different Colors:")
color_label.font = mcrfpy.default_font
color_label.font_color = (200, 200, 200)
white_text = mcrfpy.Caption(300, 230, "White Text")
white_text.font = mcrfpy.default_font
white_text.font_color = (255, 255, 255)
green_text = mcrfpy.Caption(300, 260, "Green Text")
green_text.font = mcrfpy.default_font
green_text.font_color = (100, 255, 100)
red_text = mcrfpy.Caption(300, 290, "Red Text")
red_text.font = mcrfpy.default_font
red_text.font_color = (255, 100, 100)
blue_text = mcrfpy.Caption(300, 320, "Blue Text")
blue_text.font = mcrfpy.default_font
blue_text.font_color = (100, 150, 255)
# Caption with background
bg_label = mcrfpy.Caption(100, 380, "With Background:")
bg_label.font = mcrfpy.default_font
bg_label.font_color = (200, 200, 200)
# Frame background
frame = mcrfpy.Frame(280, 370, 250, 50)
frame.bgcolor = (64, 64, 128)
frame.outline = 2
framed_text = mcrfpy.Caption(405, 395, "Caption on Frame")
framed_text.font = mcrfpy.default_font
framed_text.font_size = 18
framed_text.font_color = (255, 255, 255)
framed_text.centered = True
# Centered text example
center_label = mcrfpy.Caption(100, 460, "Centered Text:")
center_label.font = mcrfpy.default_font
center_label.font_color = (200, 200, 200)
centered = mcrfpy.Caption(400, 460, "This text is centered")
centered.font = mcrfpy.default_font
centered.font_size = 20
centered.font_color = (255, 255, 100)
centered.centered = True
# Multi-line example
multi_label = mcrfpy.Caption(100, 520, "Multi-line:")
multi_label.font = mcrfpy.default_font
multi_label.font_color = (200, 200, 200)
multiline = mcrfpy.Caption(300, 520, "Line 1: McRogueFace\nLine 2: Game Engine\nLine 3: Python API")
multiline.font = mcrfpy.default_font
multiline.font_size = 14
multiline.font_color = (255, 255, 255)
# Add all to scene
ui = mcrfpy.sceneUI("captions")
ui.append(title)
ui.append(size_label)
ui.append(large)
ui.append(medium)
ui.append(small)
ui.append(color_label)
ui.append(white_text)
ui.append(green_text)
ui.append(red_text)
ui.append(blue_text)
ui.append(bg_label)
ui.append(frame)
ui.append(framed_text)
ui.append(center_label)
ui.append(centered)
ui.append(multi_label)
ui.append(multiline)
# Switch to scene
mcrfpy.setScene("captions")
# Set timer to capture after rendering starts
mcrfpy.setTimer("capture", capture_caption, 100)

View File

@ -1,451 +0,0 @@
#!/usr/bin/env python3
"""Generate documentation screenshots for McRogueFace UI elements"""
import mcrfpy
from mcrfpy import automation
import sys
import os
# Crypt of Sokoban color scheme
FRAME_COLOR = mcrfpy.Color(64, 64, 128)
SHADOW_COLOR = mcrfpy.Color(64, 64, 86)
BOX_COLOR = mcrfpy.Color(96, 96, 160)
WHITE = mcrfpy.Color(255, 255, 255)
BLACK = mcrfpy.Color(0, 0, 0)
GREEN = mcrfpy.Color(0, 255, 0)
RED = mcrfpy.Color(255, 0, 0)
# Create texture for sprites
sprite_texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
# Output directory - create it during setup
output_dir = "mcrogueface.github.io/images"
if not os.path.exists(output_dir):
os.makedirs(output_dir)
def create_caption(x, y, text, font_size=16, text_color=WHITE, outline_color=BLACK):
"""Helper function to create captions with common settings"""
caption = mcrfpy.Caption(mcrfpy.Vector(x, y), text=text)
caption.size = font_size
caption.fill_color = text_color
caption.outline_color = outline_color
return caption
def create_caption_example():
"""Create a scene showing Caption UI element examples"""
mcrfpy.createScene("caption_example")
ui = mcrfpy.sceneUI("caption_example")
# Background frame
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
ui.append(bg)
# Title caption
title = create_caption(200, 50, "Caption Examples", 32)
ui.append(title)
# Different sized captions
caption1 = create_caption(100, 150, "Large Caption (24pt)", 24)
ui.append(caption1)
caption2 = create_caption(100, 200, "Medium Caption (18pt)", 18, GREEN)
ui.append(caption2)
caption3 = create_caption(100, 240, "Small Caption (14pt)", 14, RED)
ui.append(caption3)
# Caption with background
caption_bg = mcrfpy.Frame(100, 300, 300, 50, fill_color=BOX_COLOR)
ui.append(caption_bg)
caption4 = create_caption(110, 315, "Caption with Background", 16)
ui.append(caption4)
def create_sprite_example():
"""Create a scene showing Sprite UI element examples"""
mcrfpy.createScene("sprite_example")
ui = mcrfpy.sceneUI("sprite_example")
# Background frame
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
ui.append(bg)
# Title
title = create_caption(250, 50, "Sprite Examples", 32)
ui.append(title)
# Create a grid background for sprites
sprite_bg = mcrfpy.Frame(100, 150, 600, 300, fill_color=BOX_COLOR)
ui.append(sprite_bg)
# Player sprite (84)
player_label = create_caption(150, 180, "Player", 14)
ui.append(player_label)
player_sprite = mcrfpy.Sprite(150, 200, sprite_texture, 84, 3.0)
ui.append(player_sprite)
# Enemy sprites
enemy_label = create_caption(250, 180, "Enemies", 14)
ui.append(enemy_label)
enemy1 = mcrfpy.Sprite(250, 200, sprite_texture, 123, 3.0) # Basic enemy
ui.append(enemy1)
enemy2 = mcrfpy.Sprite(300, 200, sprite_texture, 107, 3.0) # Different enemy
ui.append(enemy2)
# Boulder sprite (66)
boulder_label = create_caption(400, 180, "Boulder", 14)
ui.append(boulder_label)
boulder_sprite = mcrfpy.Sprite(400, 200, sprite_texture, 66, 3.0)
ui.append(boulder_sprite)
# Exit sprites
exit_label = create_caption(500, 180, "Exit States", 14)
ui.append(exit_label)
exit_locked = mcrfpy.Sprite(500, 200, sprite_texture, 45, 3.0) # Locked
ui.append(exit_locked)
exit_open = mcrfpy.Sprite(550, 200, sprite_texture, 21, 3.0) # Open
ui.append(exit_open)
# Item sprites
item_label = create_caption(150, 300, "Items", 14)
ui.append(item_label)
treasure = mcrfpy.Sprite(150, 320, sprite_texture, 89, 3.0) # Treasure
ui.append(treasure)
sword = mcrfpy.Sprite(200, 320, sprite_texture, 222, 3.0) # Sword
ui.append(sword)
potion = mcrfpy.Sprite(250, 320, sprite_texture, 175, 3.0) # Potion
ui.append(potion)
# Button sprite
button_label = create_caption(350, 300, "Button", 14)
ui.append(button_label)
button = mcrfpy.Sprite(350, 320, sprite_texture, 250, 3.0)
ui.append(button)
def create_frame_example():
"""Create a scene showing Frame UI element examples"""
mcrfpy.createScene("frame_example")
ui = mcrfpy.sceneUI("frame_example")
# Background
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=SHADOW_COLOR)
ui.append(bg)
# Title
title = create_caption(250, 30, "Frame Examples", 32)
ui.append(title)
# Basic frame
frame1 = mcrfpy.Frame(50, 100, 200, 150, fill_color=FRAME_COLOR)
ui.append(frame1)
label1 = create_caption(60, 110, "Basic Frame", 16)
ui.append(label1)
# Frame with outline
frame2 = mcrfpy.Frame(300, 100, 200, 150, fill_color=BOX_COLOR,
outline_color=WHITE, outline=2.0)
ui.append(frame2)
label2 = create_caption(310, 110, "Frame with Outline", 16)
ui.append(label2)
# Nested frames
frame3 = mcrfpy.Frame(550, 100, 200, 150, fill_color=FRAME_COLOR,
outline_color=WHITE, outline=1)
ui.append(frame3)
inner_frame = mcrfpy.Frame(570, 130, 160, 90, fill_color=BOX_COLOR)
ui.append(inner_frame)
label3 = create_caption(560, 110, "Nested Frames", 16)
ui.append(label3)
# Complex layout with frames
main_frame = mcrfpy.Frame(50, 300, 700, 250, fill_color=FRAME_COLOR,
outline_color=WHITE, outline=2)
ui.append(main_frame)
# Add some UI elements inside
ui_label = create_caption(60, 310, "Complex UI Layout", 18)
ui.append(ui_label)
# Status panel
status_frame = mcrfpy.Frame(70, 350, 150, 180, fill_color=BOX_COLOR)
ui.append(status_frame)
status_label = create_caption(80, 360, "Status", 14)
ui.append(status_label)
# Inventory panel
inv_frame = mcrfpy.Frame(240, 350, 300, 180, fill_color=BOX_COLOR)
ui.append(inv_frame)
inv_label = create_caption(250, 360, "Inventory", 14)
ui.append(inv_label)
# Actions panel
action_frame = mcrfpy.Frame(560, 350, 170, 180, fill_color=BOX_COLOR)
ui.append(action_frame)
action_label = create_caption(570, 360, "Actions", 14)
ui.append(action_label)
def create_grid_example():
"""Create a scene showing Grid UI element examples"""
mcrfpy.createScene("grid_example")
ui = mcrfpy.sceneUI("grid_example")
# Background
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
ui.append(bg)
# Title
title = create_caption(250, 30, "Grid Example", 32)
ui.append(title)
# Create a grid showing a small dungeon
grid = mcrfpy.Grid(20, 15, sprite_texture,
mcrfpy.Vector(100, 100), mcrfpy.Vector(320, 240))
# Set up dungeon tiles
# Floor tiles (index 48)
# Wall tiles (index 3)
for x in range(20):
for y in range(15):
if x == 0 or x == 19 or y == 0 or y == 14:
# Walls around edge
grid.at((x, y)).tilesprite = 3
grid.at((x, y)).walkable = False
else:
# Floor
grid.at((x, y)).tilesprite = 48
grid.at((x, y)).walkable = True
# Add some internal walls
for x in range(5, 15):
grid.at((x, 7)).tilesprite = 3
grid.at((x, 7)).walkable = False
for y in range(3, 8):
grid.at((10, y)).tilesprite = 3
grid.at((10, y)).walkable = False
# Add a door
grid.at((10, 7)).tilesprite = 131 # Door tile
grid.at((10, 7)).walkable = True
# Add to UI
ui.append(grid)
# Label
grid_label = create_caption(100, 480, "20x15 Grid with 2x scale - Simple Dungeon Layout", 16)
ui.append(grid_label)
def create_entity_example():
"""Create a scene showing Entity examples in a Grid"""
mcrfpy.createScene("entity_example")
ui = mcrfpy.sceneUI("entity_example")
# Background
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
ui.append(bg)
# Title
title = create_caption(200, 30, "Entity Collection Example", 32)
ui.append(title)
# Create a grid for the entities
grid = mcrfpy.Grid(15, 10, sprite_texture,
mcrfpy.Vector(150, 100), mcrfpy.Vector(360, 240))
# Set all tiles to floor
for x in range(15):
for y in range(10):
grid.at((x, y)).tilesprite = 48
grid.at((x, y)).walkable = True
# Add walls
for x in range(15):
grid.at((x, 0)).tilesprite = 3
grid.at((x, 0)).walkable = False
grid.at((x, 9)).tilesprite = 3
grid.at((x, 9)).walkable = False
for y in range(10):
grid.at((0, y)).tilesprite = 3
grid.at((0, y)).walkable = False
grid.at((14, y)).tilesprite = 3
grid.at((14, y)).walkable = False
ui.append(grid)
# Add entities to the grid
# Player entity
player = mcrfpy.Entity(mcrfpy.Vector(3, 3), sprite_texture, 84, grid)
grid.entities.append(player)
# Enemy entities
enemy1 = mcrfpy.Entity(mcrfpy.Vector(7, 4), sprite_texture, 123, grid)
grid.entities.append(enemy1)
enemy2 = mcrfpy.Entity(mcrfpy.Vector(10, 6), sprite_texture, 107, grid)
grid.entities.append(enemy2)
# Boulder
boulder = mcrfpy.Entity(mcrfpy.Vector(5, 5), sprite_texture, 66, grid)
grid.entities.append(boulder)
# Treasure
treasure = mcrfpy.Entity(mcrfpy.Vector(12, 2), sprite_texture, 89, grid)
grid.entities.append(treasure)
# Exit (locked)
exit_door = mcrfpy.Entity(mcrfpy.Vector(12, 8), sprite_texture, 45, grid)
grid.entities.append(exit_door)
# Button
button = mcrfpy.Entity(mcrfpy.Vector(3, 7), sprite_texture, 250, grid)
grid.entities.append(button)
# Items
sword = mcrfpy.Entity(mcrfpy.Vector(8, 2), sprite_texture, 222, grid)
grid.entities.append(sword)
potion = mcrfpy.Entity(mcrfpy.Vector(6, 8), sprite_texture, 175, grid)
grid.entities.append(potion)
# Label
entity_label = create_caption(150, 500, "Grid with Entity Collection - Game Objects", 16)
ui.append(entity_label)
def create_combined_example():
"""Create a scene showing all UI elements combined"""
mcrfpy.createScene("combined_example")
ui = mcrfpy.sceneUI("combined_example")
# Background
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=SHADOW_COLOR)
ui.append(bg)
# Title
title = create_caption(200, 20, "McRogueFace UI Elements", 28)
ui.append(title)
# Main game area frame
game_frame = mcrfpy.Frame(20, 70, 500, 400, fill_color=FRAME_COLOR,
outline_color=WHITE, outline=2)
ui.append(game_frame)
# Grid inside game frame
grid = mcrfpy.Grid(12, 10, sprite_texture,
mcrfpy.Vector(30, 80), mcrfpy.Vector(480, 400))
for x in range(12):
for y in range(10):
if x == 0 or x == 11 or y == 0 or y == 9:
grid.at((x, y)).tilesprite = 3
grid.at((x, y)).walkable = False
else:
grid.at((x, y)).tilesprite = 48
grid.at((x, y)).walkable = True
# Add some entities
player = mcrfpy.Entity(mcrfpy.Vector(2, 2), sprite_texture, 84, grid)
grid.entities.append(player)
enemy = mcrfpy.Entity(mcrfpy.Vector(8, 6), sprite_texture, 123, grid)
grid.entities.append(enemy)
boulder = mcrfpy.Entity(mcrfpy.Vector(5, 4), sprite_texture, 66, grid)
grid.entities.append(boulder)
ui.append(grid)
# Status panel
status_frame = mcrfpy.Frame(540, 70, 240, 200, fill_color=BOX_COLOR,
outline_color=WHITE, outline=1)
ui.append(status_frame)
status_title = create_caption(550, 80, "Status", 20)
ui.append(status_title)
hp_label = create_caption(550, 120, "HP: 10/10", 16, GREEN)
ui.append(hp_label)
level_label = create_caption(550, 150, "Level: 1", 16)
ui.append(level_label)
# Inventory panel
inv_frame = mcrfpy.Frame(540, 290, 240, 180, fill_color=BOX_COLOR,
outline_color=WHITE, outline=1)
ui.append(inv_frame)
inv_title = create_caption(550, 300, "Inventory", 20)
ui.append(inv_title)
# Add some item sprites
item1 = mcrfpy.Sprite(560, 340, sprite_texture, 222, 2.0)
ui.append(item1)
item2 = mcrfpy.Sprite(610, 340, sprite_texture, 175, 2.0)
ui.append(item2)
# Message log
log_frame = mcrfpy.Frame(20, 490, 760, 90, fill_color=BOX_COLOR,
outline_color=WHITE, outline=1)
ui.append(log_frame)
log_msg = create_caption(30, 500, "Welcome to McRogueFace!", 14)
ui.append(log_msg)
# Set up all the scenes
print("Creating UI example scenes...")
create_caption_example()
create_sprite_example()
create_frame_example()
create_grid_example()
create_entity_example()
create_combined_example()
# Screenshot state
current_screenshot = 0
screenshots = [
("caption_example", "ui_caption_example.png"),
("sprite_example", "ui_sprite_example.png"),
("frame_example", "ui_frame_example.png"),
("grid_example", "ui_grid_example.png"),
("entity_example", "ui_entity_example.png"),
("combined_example", "ui_combined_example.png")
]
def take_screenshots(runtime):
"""Timer callback to take screenshots sequentially"""
global current_screenshot
if current_screenshot >= len(screenshots):
print("\nAll screenshots captured successfully!")
print(f"Screenshots saved to: {output_dir}/")
mcrfpy.exit()
return
scene_name, filename = screenshots[current_screenshot]
# Switch to the scene
mcrfpy.setScene(scene_name)
# Take screenshot after a short delay to ensure rendering
def capture():
global current_screenshot
full_path = f"{output_dir}/{filename}"
result = automation.screenshot(full_path)
print(f"Screenshot {current_screenshot + 1}/{len(screenshots)}: {filename} - {'Success' if result else 'Failed'}")
current_screenshot += 1
# Schedule next screenshot
mcrfpy.setTimer("next_screenshot", take_screenshots, 200)
# Give scene time to render
mcrfpy.setTimer("capture", lambda r: capture(), 100)
# Start with the first scene
mcrfpy.setScene("caption_example")
# Start the screenshot process
print(f"\nStarting screenshot capture of {len(screenshots)} scenes...")
mcrfpy.setTimer("start", take_screenshots, 500)
# Safety timeout
def safety_exit(runtime):
print("\nERROR: Safety timeout reached! Exiting...")
mcrfpy.exit()
mcrfpy.setTimer("safety", safety_exit, 30000)
print("Setup complete. Game loop starting...")

View File

@ -1,217 +0,0 @@
#!/usr/bin/env python3
"""Generate documentation screenshots for McRogueFace UI elements - Simple version"""
import mcrfpy
from mcrfpy import automation
import sys
import os
# Crypt of Sokoban color scheme
FRAME_COLOR = mcrfpy.Color(64, 64, 128)
SHADOW_COLOR = mcrfpy.Color(64, 64, 86)
BOX_COLOR = mcrfpy.Color(96, 96, 160)
WHITE = mcrfpy.Color(255, 255, 255)
BLACK = mcrfpy.Color(0, 0, 0)
GREEN = mcrfpy.Color(0, 255, 0)
RED = mcrfpy.Color(255, 0, 0)
# Create texture for sprites
sprite_texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
# Output directory
output_dir = "mcrogueface.github.io/images"
if not os.path.exists(output_dir):
os.makedirs(output_dir)
def create_caption(x, y, text, font_size=16, text_color=WHITE, outline_color=BLACK):
"""Helper function to create captions with common settings"""
caption = mcrfpy.Caption(mcrfpy.Vector(x, y), text=text)
caption.size = font_size
caption.fill_color = text_color
caption.outline_color = outline_color
return caption
# Screenshot counter
screenshot_count = 0
total_screenshots = 4
def screenshot_and_continue(runtime):
"""Take a screenshot and move to the next scene"""
global screenshot_count
if screenshot_count == 0:
# Caption example
print("Creating Caption example...")
mcrfpy.createScene("caption_example")
ui = mcrfpy.sceneUI("caption_example")
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
ui.append(bg)
title = create_caption(200, 50, "Caption Examples", 32)
ui.append(title)
caption1 = create_caption(100, 150, "Large Caption (24pt)", 24)
ui.append(caption1)
caption2 = create_caption(100, 200, "Medium Caption (18pt)", 18, GREEN)
ui.append(caption2)
caption3 = create_caption(100, 240, "Small Caption (14pt)", 14, RED)
ui.append(caption3)
caption_bg = mcrfpy.Frame(100, 300, 300, 50, fill_color=BOX_COLOR)
ui.append(caption_bg)
caption4 = create_caption(110, 315, "Caption with Background", 16)
ui.append(caption4)
mcrfpy.setScene("caption_example")
mcrfpy.setTimer("next1", lambda r: capture_screenshot("ui_caption_example.png"), 200)
elif screenshot_count == 1:
# Sprite example
print("Creating Sprite example...")
mcrfpy.createScene("sprite_example")
ui = mcrfpy.sceneUI("sprite_example")
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
ui.append(bg)
title = create_caption(250, 50, "Sprite Examples", 32)
ui.append(title)
sprite_bg = mcrfpy.Frame(100, 150, 600, 300, fill_color=BOX_COLOR)
ui.append(sprite_bg)
player_label = create_caption(150, 180, "Player", 14)
ui.append(player_label)
player_sprite = mcrfpy.Sprite(150, 200, sprite_texture, 84, 3.0)
ui.append(player_sprite)
enemy_label = create_caption(250, 180, "Enemies", 14)
ui.append(enemy_label)
enemy1 = mcrfpy.Sprite(250, 200, sprite_texture, 123, 3.0)
ui.append(enemy1)
enemy2 = mcrfpy.Sprite(300, 200, sprite_texture, 107, 3.0)
ui.append(enemy2)
boulder_label = create_caption(400, 180, "Boulder", 14)
ui.append(boulder_label)
boulder_sprite = mcrfpy.Sprite(400, 200, sprite_texture, 66, 3.0)
ui.append(boulder_sprite)
exit_label = create_caption(500, 180, "Exit States", 14)
ui.append(exit_label)
exit_locked = mcrfpy.Sprite(500, 200, sprite_texture, 45, 3.0)
ui.append(exit_locked)
exit_open = mcrfpy.Sprite(550, 200, sprite_texture, 21, 3.0)
ui.append(exit_open)
mcrfpy.setScene("sprite_example")
mcrfpy.setTimer("next2", lambda r: capture_screenshot("ui_sprite_example.png"), 200)
elif screenshot_count == 2:
# Frame example
print("Creating Frame example...")
mcrfpy.createScene("frame_example")
ui = mcrfpy.sceneUI("frame_example")
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=SHADOW_COLOR)
ui.append(bg)
title = create_caption(250, 30, "Frame Examples", 32)
ui.append(title)
frame1 = mcrfpy.Frame(50, 100, 200, 150, fill_color=FRAME_COLOR)
ui.append(frame1)
label1 = create_caption(60, 110, "Basic Frame", 16)
ui.append(label1)
frame2 = mcrfpy.Frame(300, 100, 200, 150, fill_color=BOX_COLOR,
outline_color=WHITE, outline=2.0)
ui.append(frame2)
label2 = create_caption(310, 110, "Frame with Outline", 16)
ui.append(label2)
frame3 = mcrfpy.Frame(550, 100, 200, 150, fill_color=FRAME_COLOR,
outline_color=WHITE, outline=1)
ui.append(frame3)
inner_frame = mcrfpy.Frame(570, 130, 160, 90, fill_color=BOX_COLOR)
ui.append(inner_frame)
label3 = create_caption(560, 110, "Nested Frames", 16)
ui.append(label3)
mcrfpy.setScene("frame_example")
mcrfpy.setTimer("next3", lambda r: capture_screenshot("ui_frame_example.png"), 200)
elif screenshot_count == 3:
# Grid example
print("Creating Grid example...")
mcrfpy.createScene("grid_example")
ui = mcrfpy.sceneUI("grid_example")
bg = mcrfpy.Frame(0, 0, 800, 600, fill_color=FRAME_COLOR)
ui.append(bg)
title = create_caption(250, 30, "Grid Example", 32)
ui.append(title)
grid = mcrfpy.Grid(20, 15, sprite_texture,
mcrfpy.Vector(100, 100), mcrfpy.Vector(320, 240))
# Set up dungeon tiles
for x in range(20):
for y in range(15):
if x == 0 or x == 19 or y == 0 or y == 14:
# Walls
grid.at((x, y)).tilesprite = 3
grid.at((x, y)).walkable = False
else:
# Floor
grid.at((x, y)).tilesprite = 48
grid.at((x, y)).walkable = True
# Add some internal walls
for x in range(5, 15):
grid.at((x, 7)).tilesprite = 3
grid.at((x, 7)).walkable = False
for y in range(3, 8):
grid.at((10, y)).tilesprite = 3
grid.at((10, y)).walkable = False
# Add a door
grid.at((10, 7)).tilesprite = 131
grid.at((10, 7)).walkable = True
ui.append(grid)
grid_label = create_caption(100, 480, "20x15 Grid - Simple Dungeon Layout", 16)
ui.append(grid_label)
mcrfpy.setScene("grid_example")
mcrfpy.setTimer("next4", lambda r: capture_screenshot("ui_grid_example.png"), 200)
else:
print("\nAll screenshots captured successfully!")
print(f"Screenshots saved to: {output_dir}/")
mcrfpy.exit()
return
def capture_screenshot(filename):
"""Capture a screenshot"""
global screenshot_count
full_path = f"{output_dir}/{filename}"
result = automation.screenshot(full_path)
print(f"Screenshot {screenshot_count + 1}/{total_screenshots}: {filename} - {'Success' if result else 'Failed'}")
screenshot_count += 1
# Schedule next scene
mcrfpy.setTimer("continue", screenshot_and_continue, 300)
# Start the process
print("Starting screenshot generation...")
mcrfpy.setTimer("start", screenshot_and_continue, 500)
# Safety timeout
mcrfpy.setTimer("safety", lambda r: mcrfpy.exit(), 30000)
print("Setup complete. Game loop starting...")

View File

@ -1,144 +0,0 @@
#!/usr/bin/env python3
"""Generate entity documentation screenshot with proper font loading"""
import mcrfpy
from mcrfpy import automation
import sys
def capture_entity(runtime):
"""Capture entity example after render loop starts"""
# Take screenshot
automation.screenshot("mcrogueface.github.io/images/ui_entity_example.png")
print("Entity screenshot saved!")
# Exit after capturing
sys.exit(0)
# Create scene
mcrfpy.createScene("entities")
# Use the default font which is already loaded
# Instead of: font = mcrfpy.Font("assets/JetbrainsMono.ttf")
# We use: mcrfpy.default_font (which is already loaded by the engine)
# Title
title = mcrfpy.Caption((400, 30), "Entity Example - Roguelike Characters", font=mcrfpy.default_font)
#title.font = mcrfpy.default_font
#title.font_size = 24
title.size=24
#title.font_color = (255, 255, 255)
#title.text_color = (255,255,255)
# Create a grid background
texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
# Create grid with entities - using 2x scale (32x32 pixel tiles)
#grid = mcrfpy.Grid((100, 100), (20, 15), texture, 16, 16) # I can never get the args right for this thing
t = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
grid = mcrfpy.Grid(20, 15, t, (10, 10), (1014, 758))
grid.zoom = 2.0
#grid.texture = texture
# Define tile types
FLOOR = 58 # Stone floor
WALL = 11 # Stone wall
# Fill with floor
for x in range(20):
for y in range(15):
grid.at((x, y)).tilesprite = WALL
# Add walls around edges
for x in range(20):
grid.at((x, 0)).tilesprite = WALL
grid.at((x, 14)).tilesprite = WALL
for y in range(15):
grid.at((0, y)).tilesprite = WALL
grid.at((19, y)).tilesprite = WALL
# Create entities
# Player at center
player = mcrfpy.Entity((10, 7), t, 84)
#player.texture = texture
#player.sprite_index = 84 # Player sprite
# Enemies
rat1 = mcrfpy.Entity((5, 5), t, 123)
#rat1.texture = texture
#rat1.sprite_index = 123 # Rat
rat2 = mcrfpy.Entity((15, 5), t, 123)
#rat2.texture = texture
#rat2.sprite_index = 123 # Rat
big_rat = mcrfpy.Entity((7, 10), t, 130)
#big_rat.texture = texture
#big_rat.sprite_index = 130 # Big rat
cyclops = mcrfpy.Entity((13, 10), t, 109)
#cyclops.texture = texture
#cyclops.sprite_index = 109 # Cyclops
# Items
chest = mcrfpy.Entity((3, 3), t, 89)
#chest.texture = texture
#chest.sprite_index = 89 # Chest
boulder = mcrfpy.Entity((10, 5), t, 66)
#boulder.texture = texture
#boulder.sprite_index = 66 # Boulder
key = mcrfpy.Entity((17, 12), t, 384)
#key.texture = texture
#key.sprite_index = 384 # Key
# Add all entities to grid
grid.entities.append(player)
grid.entities.append(rat1)
grid.entities.append(rat2)
grid.entities.append(big_rat)
grid.entities.append(cyclops)
grid.entities.append(chest)
grid.entities.append(boulder)
grid.entities.append(key)
# Labels
entity_label = mcrfpy.Caption((100, 580), "Entities move independently on the grid. Grid scale: 2x (32x32 pixels)")
#entity_label.font = mcrfpy.default_font
#entity_label.font_color = (255, 255, 255)
info = mcrfpy.Caption((100, 600), "Player (center), Enemies (rats, cyclops), Items (chest, boulder, key)")
#info.font = mcrfpy.default_font
#info.font_size = 14
#info.font_color = (200, 200, 200)
# Legend frame
legend_frame = mcrfpy.Frame(50, 50, 200, 150)
#legend_frame.bgcolor = (64, 64, 128)
#legend_frame.outline = 2
legend_title = mcrfpy.Caption((150, 60), "Entity Types")
#legend_title.font = mcrfpy.default_font
#legend_title.font_color = (255, 255, 255)
#legend_title.centered = True
#legend_text = mcrfpy.Caption((60, 90), "Player: @\nRat: r\nBig Rat: R\nCyclops: C\nChest: $\nBoulder: O\nKey: k")
#legend_text.font = mcrfpy.default_font
#legend_text.font_size = 12
#legend_text.font_color = (255, 255, 255)
# Add all to scene
ui = mcrfpy.sceneUI("entities")
ui.append(grid)
ui.append(title)
ui.append(entity_label)
ui.append(info)
ui.append(legend_frame)
ui.append(legend_title)
#ui.append(legend_text)
# Switch to scene
mcrfpy.setScene("entities")
# Set timer to capture after rendering starts
mcrfpy.setTimer("capture", capture_entity, 100)

View File

@ -1,131 +0,0 @@
#!/usr/bin/env python3
"""Generate grid documentation screenshot for McRogueFace"""
import mcrfpy
from mcrfpy import automation
import sys
def capture_grid(runtime):
"""Capture grid example after render loop starts"""
# Take screenshot
automation.screenshot("mcrogueface.github.io/images/ui_grid_example.png")
print("Grid screenshot saved!")
# Exit after capturing
sys.exit(0)
# Create scene
mcrfpy.createScene("grid")
# Load texture
texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
# Title
title = mcrfpy.Caption(400, 30, "Grid Example - Dungeon View")
title.font = mcrfpy.default_font
title.font_size = 24
title.font_color = (255, 255, 255)
# Create main grid (20x15 tiles, each 32x32 pixels)
grid = mcrfpy.Grid(100, 100, 20, 15, texture, 32, 32)
grid.texture = texture
# Define tile types from Crypt of Sokoban
FLOOR = 58 # Stone floor
WALL = 11 # Stone wall
DOOR = 28 # Closed door
CHEST = 89 # Treasure chest
BUTTON = 250 # Floor button
EXIT = 45 # Locked exit
BOULDER = 66 # Boulder
# Create a simple dungeon room layout
# Fill with walls first
for x in range(20):
for y in range(15):
grid.set_tile(x, y, WALL)
# Carve out room
for x in range(2, 18):
for y in range(2, 13):
grid.set_tile(x, y, FLOOR)
# Add door
grid.set_tile(10, 2, DOOR)
# Add some features
grid.set_tile(5, 5, CHEST)
grid.set_tile(15, 10, BUTTON)
grid.set_tile(10, 12, EXIT)
grid.set_tile(8, 8, BOULDER)
grid.set_tile(12, 8, BOULDER)
# Create some entities on the grid
# Player entity
player = mcrfpy.Entity(5, 7)
player.texture = texture
player.sprite_index = 84 # Player sprite
# Enemy entities
rat1 = mcrfpy.Entity(12, 5)
rat1.texture = texture
rat1.sprite_index = 123 # Rat
rat2 = mcrfpy.Entity(14, 9)
rat2.texture = texture
rat2.sprite_index = 123 # Rat
cyclops = mcrfpy.Entity(10, 10)
cyclops.texture = texture
cyclops.sprite_index = 109 # Cyclops
# Add entities to grid
grid.entities.append(player)
grid.entities.append(rat1)
grid.entities.append(rat2)
grid.entities.append(cyclops)
# Create a smaller grid showing tile palette
palette_label = mcrfpy.Caption(100, 600, "Tile Types:")
palette_label.font = mcrfpy.default_font
palette_label.font_color = (255, 255, 255)
palette = mcrfpy.Grid(250, 580, 7, 1, texture, 32, 32)
palette.texture = texture
palette.set_tile(0, 0, FLOOR)
palette.set_tile(1, 0, WALL)
palette.set_tile(2, 0, DOOR)
palette.set_tile(3, 0, CHEST)
palette.set_tile(4, 0, BUTTON)
palette.set_tile(5, 0, EXIT)
palette.set_tile(6, 0, BOULDER)
# Labels for palette
labels = ["Floor", "Wall", "Door", "Chest", "Button", "Exit", "Boulder"]
for i, label in enumerate(labels):
l = mcrfpy.Caption(250 + i * 32, 615, label)
l.font = mcrfpy.default_font
l.font_size = 10
l.font_color = (255, 255, 255)
mcrfpy.sceneUI("grid").append(l)
# Add info caption
info = mcrfpy.Caption(100, 680, "Grid supports tiles and entities. Entities can move independently of the tile grid.")
info.font = mcrfpy.default_font
info.font_size = 14
info.font_color = (200, 200, 200)
# Add all elements to scene
ui = mcrfpy.sceneUI("grid")
ui.append(title)
ui.append(grid)
ui.append(palette_label)
ui.append(palette)
ui.append(info)
# Switch to scene
mcrfpy.setScene("grid")
# Set timer to capture after rendering starts
mcrfpy.setTimer("capture", capture_grid, 100)

View File

@ -1,160 +0,0 @@
#!/usr/bin/env python3
"""Generate sprite documentation screenshots for McRogueFace"""
import mcrfpy
from mcrfpy import automation
import sys
def capture_sprites(runtime):
"""Capture sprite examples after render loop starts"""
# Take screenshot
automation.screenshot("mcrogueface.github.io/images/ui_sprite_example.png")
print("Sprite screenshot saved!")
# Exit after capturing
sys.exit(0)
# Create scene
mcrfpy.createScene("sprites")
# Load texture
texture = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
# Title
title = mcrfpy.Caption(400, 30, "Sprite Examples")
title.font = mcrfpy.default_font
title.font_size = 24
title.font_color = (255, 255, 255)
# Create a frame background
frame = mcrfpy.Frame(50, 80, 700, 500)
frame.bgcolor = (64, 64, 128)
frame.outline = 2
# Player sprite
player_label = mcrfpy.Caption(100, 120, "Player")
player_label.font = mcrfpy.default_font
player_label.font_color = (255, 255, 255)
player = mcrfpy.Sprite(120, 150)
player.texture = texture
player.sprite_index = 84 # Player sprite
player.scale = (3.0, 3.0)
# Enemy sprites
enemy_label = mcrfpy.Caption(250, 120, "Enemies")
enemy_label.font = mcrfpy.default_font
enemy_label.font_color = (255, 255, 255)
rat = mcrfpy.Sprite(250, 150)
rat.texture = texture
rat.sprite_index = 123 # Rat
rat.scale = (3.0, 3.0)
big_rat = mcrfpy.Sprite(320, 150)
big_rat.texture = texture
big_rat.sprite_index = 130 # Big rat
big_rat.scale = (3.0, 3.0)
cyclops = mcrfpy.Sprite(390, 150)
cyclops.texture = texture
cyclops.sprite_index = 109 # Cyclops
cyclops.scale = (3.0, 3.0)
# Items row
items_label = mcrfpy.Caption(100, 250, "Items")
items_label.font = mcrfpy.default_font
items_label.font_color = (255, 255, 255)
# Boulder
boulder = mcrfpy.Sprite(100, 280)
boulder.texture = texture
boulder.sprite_index = 66 # Boulder
boulder.scale = (3.0, 3.0)
# Chest
chest = mcrfpy.Sprite(170, 280)
chest.texture = texture
chest.sprite_index = 89 # Closed chest
chest.scale = (3.0, 3.0)
# Key
key = mcrfpy.Sprite(240, 280)
key.texture = texture
key.sprite_index = 384 # Key
key.scale = (3.0, 3.0)
# Button
button = mcrfpy.Sprite(310, 280)
button.texture = texture
button.sprite_index = 250 # Button
button.scale = (3.0, 3.0)
# UI elements row
ui_label = mcrfpy.Caption(100, 380, "UI Elements")
ui_label.font = mcrfpy.default_font
ui_label.font_color = (255, 255, 255)
# Hearts
heart_full = mcrfpy.Sprite(100, 410)
heart_full.texture = texture
heart_full.sprite_index = 210 # Full heart
heart_full.scale = (3.0, 3.0)
heart_half = mcrfpy.Sprite(170, 410)
heart_half.texture = texture
heart_half.sprite_index = 209 # Half heart
heart_half.scale = (3.0, 3.0)
heart_empty = mcrfpy.Sprite(240, 410)
heart_empty.texture = texture
heart_empty.sprite_index = 208 # Empty heart
heart_empty.scale = (3.0, 3.0)
# Armor
armor = mcrfpy.Sprite(340, 410)
armor.texture = texture
armor.sprite_index = 211 # Armor
armor.scale = (3.0, 3.0)
# Scale demonstration
scale_label = mcrfpy.Caption(500, 120, "Scale Demo")
scale_label.font = mcrfpy.default_font
scale_label.font_color = (255, 255, 255)
# Same sprite at different scales
for i, scale in enumerate([1.0, 2.0, 3.0, 4.0]):
s = mcrfpy.Sprite(500 + i * 60, 150)
s.texture = texture
s.sprite_index = 84 # Player
s.scale = (scale, scale)
mcrfpy.sceneUI("sprites").append(s)
# Add all elements to scene
ui = mcrfpy.sceneUI("sprites")
ui.append(frame)
ui.append(title)
ui.append(player_label)
ui.append(player)
ui.append(enemy_label)
ui.append(rat)
ui.append(big_rat)
ui.append(cyclops)
ui.append(items_label)
ui.append(boulder)
ui.append(chest)
ui.append(key)
ui.append(button)
ui.append(ui_label)
ui.append(heart_full)
ui.append(heart_half)
ui.append(heart_empty)
ui.append(armor)
ui.append(scale_label)
# Switch to scene
mcrfpy.setScene("sprites")
# Set timer to capture after rendering starts
mcrfpy.setTimer("capture", capture_sprites, 100)

View File

@ -1,136 +0,0 @@
#!/usr/bin/env python3
"""
Test for Issue #12: Forbid GridPoint/GridPointState instantiation
This test verifies that GridPoint and GridPointState cannot be instantiated
directly from Python, as they should only be created internally by the C++ code.
"""
import mcrfpy
import sys
def test_gridpoint_instantiation():
"""Test that GridPoint and GridPointState cannot be instantiated"""
print("=== Testing GridPoint/GridPointState Instantiation Prevention (Issue #12) ===\n")
tests_passed = 0
tests_total = 0
# Test 1: Try to instantiate GridPoint
print("--- Test 1: GridPoint instantiation ---")
tests_total += 1
try:
point = mcrfpy.GridPoint()
print("✗ FAIL: GridPoint() should not be allowed")
except TypeError as e:
print(f"✓ PASS: GridPoint instantiation correctly prevented: {e}")
tests_passed += 1
except Exception as e:
print(f"✗ FAIL: Unexpected error: {e}")
# Test 2: Try to instantiate GridPointState
print("\n--- Test 2: GridPointState instantiation ---")
tests_total += 1
try:
state = mcrfpy.GridPointState()
print("✗ FAIL: GridPointState() should not be allowed")
except TypeError as e:
print(f"✓ PASS: GridPointState instantiation correctly prevented: {e}")
tests_passed += 1
except Exception as e:
print(f"✗ FAIL: Unexpected error: {e}")
# Test 3: Verify GridPoint can still be obtained from Grid
print("\n--- Test 3: GridPoint obtained from Grid.at() ---")
tests_total += 1
try:
grid = mcrfpy.Grid(10, 10)
point = grid.at(5, 5)
print(f"✓ PASS: GridPoint obtained from Grid.at(): {point}")
print(f" Type: {type(point).__name__}")
tests_passed += 1
except Exception as e:
print(f"✗ FAIL: Could not get GridPoint from Grid: {e}")
# Test 4: Verify GridPointState can still be obtained from GridPoint
print("\n--- Test 4: GridPointState obtained from GridPoint ---")
tests_total += 1
try:
# GridPointState is accessed through GridPoint's click handler
# Let's check if we can access point properties that would use GridPointState
if hasattr(point, 'walkable'):
print(f"✓ PASS: GridPoint has expected properties")
print(f" walkable: {point.walkable}")
print(f" transparent: {point.transparent}")
tests_passed += 1
else:
print("✗ FAIL: GridPoint missing expected properties")
except Exception as e:
print(f"✗ FAIL: Error accessing GridPoint properties: {e}")
# Test 5: Try to call the types directly (alternative syntax)
print("\n--- Test 5: Alternative instantiation attempts ---")
tests_total += 1
all_prevented = True
# Try various ways to instantiate
attempts = [
("mcrfpy.GridPoint.__new__(mcrfpy.GridPoint)",
lambda: mcrfpy.GridPoint.__new__(mcrfpy.GridPoint)),
("type(point)()",
lambda: type(point)() if 'point' in locals() else None),
]
for desc, func in attempts:
try:
if func:
result = func()
print(f"✗ FAIL: {desc} should not be allowed")
all_prevented = False
except (TypeError, AttributeError) as e:
print(f" ✓ Correctly prevented: {desc}")
except Exception as e:
print(f" ? Unexpected error for {desc}: {e}")
if all_prevented:
print("✓ PASS: All alternative instantiation attempts prevented")
tests_passed += 1
else:
print("✗ FAIL: Some instantiation attempts succeeded")
# Summary
print(f"\n=== SUMMARY ===")
print(f"Tests passed: {tests_passed}/{tests_total}")
if tests_passed == tests_total:
print("\nIssue #12 FIXED: GridPoint/GridPointState instantiation properly forbidden!")
else:
print("\nIssue #12: Some tests failed")
return tests_passed == tests_total
def run_test(runtime):
"""Timer callback to run the test"""
try:
# First verify the types exist
print("Checking that GridPoint and GridPointState types exist...")
print(f"GridPoint type: {mcrfpy.GridPoint}")
print(f"GridPointState type: {mcrfpy.GridPointState}")
print()
success = test_gridpoint_instantiation()
print("\nOverall result: " + ("PASS" if success else "FAIL"))
except Exception as e:
print(f"\nTest error: {e}")
import traceback
traceback.print_exc()
print("\nOverall result: FAIL")
sys.exit(0)
# Set up the test scene
mcrfpy.createScene("test")
mcrfpy.setScene("test")
# Schedule test to run after game loop starts
mcrfpy.setTimer("test", run_test, 100)

View File

@ -1,337 +0,0 @@
#!/usr/bin/env python3
"""
Comprehensive test for Issues #26 & #28: Iterator implementation for collections
This test covers both UICollection and UIEntityCollection iterator implementations,
testing all aspects of the Python sequence protocol.
Issues:
- #26: Iterator support for UIEntityCollection
- #28: Iterator support for UICollection
"""
import mcrfpy
from mcrfpy import automation
import sys
import gc
def test_sequence_protocol(collection, name, expected_types=None):
"""Test all sequence protocol operations on a collection"""
print(f"\n=== Testing {name} ===")
tests_passed = 0
tests_total = 0
# Test 1: len()
tests_total += 1
try:
length = len(collection)
print(f"✓ len() works: {length} items")
tests_passed += 1
except Exception as e:
print(f"✗ len() failed: {e}")
return tests_passed, tests_total
# Test 2: Basic iteration
tests_total += 1
try:
items = []
types = []
for item in collection:
items.append(item)
types.append(type(item).__name__)
print(f"✓ Iteration works: found {len(items)} items")
print(f" Types: {types}")
if expected_types and types != expected_types:
print(f" WARNING: Expected types {expected_types}")
tests_passed += 1
except Exception as e:
print(f"✗ Iteration failed (Issue #26/#28): {e}")
# Test 3: Indexing (positive)
tests_total += 1
try:
if length > 0:
first = collection[0]
last = collection[length-1]
print(f"✓ Positive indexing works: [0]={type(first).__name__}, [{length-1}]={type(last).__name__}")
tests_passed += 1
else:
print(" Skipping indexing test - empty collection")
except Exception as e:
print(f"✗ Positive indexing failed: {e}")
# Test 4: Negative indexing
tests_total += 1
try:
if length > 0:
last = collection[-1]
first = collection[-length]
print(f"✓ Negative indexing works: [-1]={type(last).__name__}, [-{length}]={type(first).__name__}")
tests_passed += 1
else:
print(" Skipping negative indexing test - empty collection")
except Exception as e:
print(f"✗ Negative indexing failed: {e}")
# Test 5: Out of bounds indexing
tests_total += 1
try:
_ = collection[length + 10]
print(f"✗ Out of bounds indexing should raise IndexError but didn't")
except IndexError:
print(f"✓ Out of bounds indexing correctly raises IndexError")
tests_passed += 1
except Exception as e:
print(f"✗ Out of bounds indexing raised wrong exception: {type(e).__name__}: {e}")
# Test 6: Slicing
tests_total += 1
try:
if length >= 2:
slice_result = collection[0:2]
print(f"✓ Slicing works: [0:2] returned {len(slice_result)} items")
tests_passed += 1
else:
print(" Skipping slicing test - not enough items")
except NotImplementedError:
print(f"✗ Slicing not implemented")
except Exception as e:
print(f"✗ Slicing failed: {e}")
# Test 7: Contains operator
tests_total += 1
try:
if length > 0:
first_item = collection[0]
if first_item in collection:
print(f"'in' operator works")
tests_passed += 1
else:
print(f"'in' operator returned False for existing item")
else:
print(" Skipping 'in' operator test - empty collection")
except NotImplementedError:
print(f"'in' operator not implemented")
except Exception as e:
print(f"'in' operator failed: {e}")
# Test 8: Multiple iterations
tests_total += 1
try:
count1 = sum(1 for _ in collection)
count2 = sum(1 for _ in collection)
if count1 == count2 == length:
print(f"✓ Multiple iterations work correctly")
tests_passed += 1
else:
print(f"✗ Multiple iterations inconsistent: {count1} vs {count2} vs {length}")
except Exception as e:
print(f"✗ Multiple iterations failed: {e}")
# Test 9: Iterator state independence
tests_total += 1
try:
iter1 = iter(collection)
iter2 = iter(collection)
# Advance iter1
next(iter1)
# iter2 should still be at the beginning
item1_from_iter2 = next(iter2)
item1_from_collection = collection[0]
if type(item1_from_iter2).__name__ == type(item1_from_collection).__name__:
print(f"✓ Iterator state independence maintained")
tests_passed += 1
else:
print(f"✗ Iterator states are not independent")
except Exception as e:
print(f"✗ Iterator state test failed: {e}")
# Test 10: List conversion
tests_total += 1
try:
as_list = list(collection)
if len(as_list) == length:
print(f"✓ list() conversion works: {len(as_list)} items")
tests_passed += 1
else:
print(f"✗ list() conversion wrong length: {len(as_list)} vs {length}")
except Exception as e:
print(f"✗ list() conversion failed: {e}")
return tests_passed, tests_total
def test_modification_during_iteration(collection, name):
"""Test collection modification during iteration"""
print(f"\n=== Testing {name} Modification During Iteration ===")
# This is a tricky case - some implementations might crash
# or behave unexpectedly when the collection is modified during iteration
if len(collection) < 2:
print(" Skipping - need at least 2 items")
return
try:
count = 0
for i, item in enumerate(collection):
count += 1
if i == 0 and hasattr(collection, 'remove'):
# Try to remove an item during iteration
# This might raise an exception or cause undefined behavior
pass # Don't actually modify to avoid breaking the test
print(f"✓ Iteration completed without modification: {count} items")
except Exception as e:
print(f" Note: Iteration with modification would fail: {e}")
def run_comprehensive_test():
"""Run comprehensive iterator tests for both collection types"""
print("=== Testing Collection Iterator Implementation (Issues #26 & #28) ===")
total_passed = 0
total_tests = 0
# Test UICollection
print("\n--- Testing UICollection ---")
# Create UI elements
scene_ui = mcrfpy.sceneUI("test")
# Add various UI elements
frame = mcrfpy.Frame(10, 10, 200, 150,
fill_color=mcrfpy.Color(100, 100, 200),
outline_color=mcrfpy.Color(255, 255, 255))
caption = mcrfpy.Caption(mcrfpy.Vector(220, 10),
text="Test Caption",
fill_color=mcrfpy.Color(255, 255, 0))
scene_ui.append(frame)
scene_ui.append(caption)
# Test UICollection
passed, total = test_sequence_protocol(scene_ui, "UICollection",
expected_types=["Frame", "Caption"])
total_passed += passed
total_tests += total
test_modification_during_iteration(scene_ui, "UICollection")
# Test UICollection with children
print("\n--- Testing UICollection Children (Nested) ---")
child_caption = mcrfpy.Caption(mcrfpy.Vector(10, 10),
text="Child",
fill_color=mcrfpy.Color(200, 200, 200))
frame.children.append(child_caption)
passed, total = test_sequence_protocol(frame.children, "Frame.children",
expected_types=["Caption"])
total_passed += passed
total_tests += total
# Test UIEntityCollection
print("\n--- Testing UIEntityCollection ---")
# Create a grid with entities
grid = mcrfpy.Grid(30, 30)
grid.x = 10
grid.y = 200
grid.w = 600
grid.h = 400
scene_ui.append(grid)
# Add various entities
entity1 = mcrfpy.Entity(5, 5)
entity2 = mcrfpy.Entity(10, 10)
entity3 = mcrfpy.Entity(15, 15)
grid.entities.append(entity1)
grid.entities.append(entity2)
grid.entities.append(entity3)
passed, total = test_sequence_protocol(grid.entities, "UIEntityCollection",
expected_types=["Entity", "Entity", "Entity"])
total_passed += passed
total_tests += total
test_modification_during_iteration(grid.entities, "UIEntityCollection")
# Test empty collections
print("\n--- Testing Empty Collections ---")
empty_grid = mcrfpy.Grid(10, 10)
passed, total = test_sequence_protocol(empty_grid.entities, "Empty UIEntityCollection")
total_passed += passed
total_tests += total
empty_frame = mcrfpy.Frame(0, 0, 50, 50)
passed, total = test_sequence_protocol(empty_frame.children, "Empty UICollection")
total_passed += passed
total_tests += total
# Test large collection
print("\n--- Testing Large Collection ---")
large_grid = mcrfpy.Grid(50, 50)
for i in range(100):
large_grid.entities.append(mcrfpy.Entity(i % 50, i // 50))
print(f"Created large collection with {len(large_grid.entities)} entities")
# Just test basic iteration performance
import time
start = time.time()
count = sum(1 for _ in large_grid.entities)
elapsed = time.time() - start
print(f"✓ Large collection iteration: {count} items in {elapsed:.3f}s")
# Edge case: Single item collection
print("\n--- Testing Single Item Collection ---")
single_grid = mcrfpy.Grid(5, 5)
single_grid.entities.append(mcrfpy.Entity(1, 1))
passed, total = test_sequence_protocol(single_grid.entities, "Single Item UIEntityCollection")
total_passed += passed
total_tests += total
# Take screenshot
automation.screenshot("/tmp/issue_26_28_iterator_test.png")
# Summary
print(f"\n=== SUMMARY ===")
print(f"Total tests passed: {total_passed}/{total_tests}")
if total_passed < total_tests:
print("\nIssues found:")
print("- Issue #26: UIEntityCollection may not fully implement iterator protocol")
print("- Issue #28: UICollection may not fully implement iterator protocol")
print("\nThe iterator implementation should support:")
print("1. Forward iteration with 'for item in collection'")
print("2. Multiple independent iterators")
print("3. Proper cleanup when iteration completes")
print("4. Integration with Python's sequence protocol")
else:
print("\nAll iterator tests passed!")
return total_passed == total_tests
def run_test(runtime):
"""Timer callback to run the test"""
try:
success = run_comprehensive_test()
print("\nOverall result: " + ("PASS" if success else "FAIL"))
except Exception as e:
print(f"\nTest error: {e}")
import traceback
traceback.print_exc()
print("\nOverall result: FAIL")
sys.exit(0)
# Set up the test scene
mcrfpy.createScene("test")
mcrfpy.setScene("test")
# Schedule test to run after game loop starts
mcrfpy.setTimer("test", run_test, 100)

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