Compare commits
No commits in common. "1438044c6a9d30572357985057d284cabff64e00" and "a57f0875f8394cca6a64ef5eb5bae7be150ee769" have entirely different histories.
1438044c6a
...
a57f0875f8
40 changed files with 124 additions and 1879 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -16,7 +16,6 @@ CMakeFiles/
|
||||||
Makefile
|
Makefile
|
||||||
*.zip
|
*.zip
|
||||||
__lib/
|
__lib/
|
||||||
__lib_windows/
|
|
||||||
_oldscripts/
|
_oldscripts/
|
||||||
assets/
|
assets/
|
||||||
cellular_automata_fire/
|
cellular_automata_fire/
|
||||||
|
|
|
||||||
177
CMakeLists.txt
177
CMakeLists.txt
|
|
@ -8,33 +8,14 @@ project(McRogueFace)
|
||||||
set(CMAKE_CXX_STANDARD 20)
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
set(CMAKE_CXX_STANDARD_REQUIRED True)
|
||||||
|
|
||||||
# Detect cross-compilation for Windows (MinGW)
|
|
||||||
if(CMAKE_CROSSCOMPILING AND WIN32)
|
|
||||||
set(MCRF_CROSS_WINDOWS TRUE)
|
|
||||||
message(STATUS "Cross-compiling for Windows using MinGW")
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Add include directories
|
# Add include directories
|
||||||
|
#include_directories(${CMAKE_SOURCE_DIR}/deps_linux)
|
||||||
include_directories(${CMAKE_SOURCE_DIR}/deps)
|
include_directories(${CMAKE_SOURCE_DIR}/deps)
|
||||||
|
#include_directories(${CMAKE_SOURCE_DIR}/deps_linux/Python-3.11.1)
|
||||||
include_directories(${CMAKE_SOURCE_DIR}/deps/libtcod)
|
include_directories(${CMAKE_SOURCE_DIR}/deps/libtcod)
|
||||||
|
|
||||||
# Python includes: use different paths for Windows vs Linux
|
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython)
|
||||||
if(MCRF_CROSS_WINDOWS)
|
include_directories(${CMAKE_SOURCE_DIR}/deps/Python)
|
||||||
# Windows cross-compilation: use cpython headers with PC/pyconfig.h
|
|
||||||
# Problem: Python.h uses #include "pyconfig.h" which finds Include/pyconfig.h (Linux) first
|
|
||||||
# Solution: Use -include to force Windows pyconfig.h to be included first
|
|
||||||
# This defines MS_WINDOWS before Python.h is processed, ensuring correct struct layouts
|
|
||||||
add_compile_options(-include ${CMAKE_SOURCE_DIR}/deps/cpython/PC/pyconfig.h)
|
|
||||||
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython/Include)
|
|
||||||
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython/PC) # For other Windows-specific headers
|
|
||||||
# Also include SFML and libtcod Windows headers
|
|
||||||
include_directories(${CMAKE_SOURCE_DIR}/__lib_windows/sfml/include)
|
|
||||||
include_directories(${CMAKE_SOURCE_DIR}/__lib_windows/libtcod/include)
|
|
||||||
else()
|
|
||||||
# Native builds (Linux/Windows): use existing Python setup
|
|
||||||
include_directories(${CMAKE_SOURCE_DIR}/deps/cpython)
|
|
||||||
include_directories(${CMAKE_SOURCE_DIR}/deps/Python)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# ImGui and ImGui-SFML include directories
|
# ImGui and ImGui-SFML include directories
|
||||||
include_directories(${CMAKE_SOURCE_DIR}/modules/imgui)
|
include_directories(${CMAKE_SOURCE_DIR}/modules/imgui)
|
||||||
|
|
@ -56,107 +37,47 @@ file(GLOB_RECURSE SOURCES "src/*.cpp")
|
||||||
list(APPEND SOURCES ${IMGUI_SOURCES})
|
list(APPEND SOURCES ${IMGUI_SOURCES})
|
||||||
|
|
||||||
# Find OpenGL (required by ImGui-SFML)
|
# Find OpenGL (required by ImGui-SFML)
|
||||||
if(MCRF_CROSS_WINDOWS)
|
find_package(OpenGL REQUIRED)
|
||||||
# For cross-compilation, OpenGL is provided by MinGW
|
|
||||||
set(OPENGL_LIBRARIES opengl32)
|
|
||||||
else()
|
|
||||||
find_package(OpenGL REQUIRED)
|
|
||||||
set(OPENGL_LIBRARIES OpenGL::GL)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Create a list of libraries to link against
|
# Create a list of libraries to link against
|
||||||
if(MCRF_CROSS_WINDOWS)
|
set(LINK_LIBS
|
||||||
# MinGW cross-compilation: use full library names
|
sfml-graphics
|
||||||
set(LINK_LIBS
|
sfml-window
|
||||||
sfml-graphics
|
sfml-system
|
||||||
sfml-window
|
sfml-audio
|
||||||
sfml-system
|
tcod
|
||||||
sfml-audio
|
OpenGL::GL)
|
||||||
libtcod
|
|
||||||
python314
|
|
||||||
${OPENGL_LIBRARIES})
|
|
||||||
|
|
||||||
# Add Windows system libraries needed by SFML and MinGW
|
|
||||||
list(APPEND LINK_LIBS
|
|
||||||
winmm # Windows multimedia (for audio)
|
|
||||||
gdi32 # Graphics Device Interface
|
|
||||||
ws2_32 # Winsock (networking, used by some deps)
|
|
||||||
ole32 # OLE support
|
|
||||||
oleaut32 # OLE automation
|
|
||||||
uuid # UUID library
|
|
||||||
comdlg32 # Common dialogs
|
|
||||||
imm32 # Input Method Manager
|
|
||||||
version # Version info
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# On Windows, add any additional libs and include directories
|
||||||
|
if(WIN32)
|
||||||
|
# Windows-specific Python library name (no dots)
|
||||||
|
list(APPEND LINK_LIBS python314)
|
||||||
|
# Add the necessary Windows-specific libraries and include directories
|
||||||
|
# include_directories(path_to_additional_includes)
|
||||||
|
# link_directories(path_to_additional_libs)
|
||||||
|
# list(APPEND LINK_LIBS additional_windows_libs)
|
||||||
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/windows)
|
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/windows)
|
||||||
|
|
||||||
# Link directories for cross-compiled Windows libs
|
|
||||||
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows/sfml/lib)
|
|
||||||
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows/libtcod/lib)
|
|
||||||
link_directories(${CMAKE_SOURCE_DIR}/__lib_windows)
|
|
||||||
elseif(WIN32)
|
|
||||||
# Native Windows build (MSVC)
|
|
||||||
set(LINK_LIBS
|
|
||||||
sfml-graphics
|
|
||||||
sfml-window
|
|
||||||
sfml-system
|
|
||||||
sfml-audio
|
|
||||||
tcod
|
|
||||||
python314
|
|
||||||
${OPENGL_LIBRARIES})
|
|
||||||
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/windows)
|
|
||||||
link_directories(${CMAKE_SOURCE_DIR}/__lib)
|
|
||||||
else()
|
else()
|
||||||
# Unix/Linux build
|
# Unix/Linux specific libraries
|
||||||
set(LINK_LIBS
|
list(APPEND LINK_LIBS python3.14 m dl util pthread)
|
||||||
sfml-graphics
|
|
||||||
sfml-window
|
|
||||||
sfml-system
|
|
||||||
sfml-audio
|
|
||||||
tcod
|
|
||||||
python3.14
|
|
||||||
m dl util pthread
|
|
||||||
${OPENGL_LIBRARIES})
|
|
||||||
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux)
|
include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux)
|
||||||
link_directories(${CMAKE_SOURCE_DIR}/__lib)
|
|
||||||
endif()
|
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)
|
||||||
|
|
||||||
# Define the executable target before linking libraries
|
# Define the executable target before linking libraries
|
||||||
add_executable(mcrogueface ${SOURCES})
|
add_executable(mcrogueface ${SOURCES})
|
||||||
|
|
||||||
# Define NO_SDL for libtcod-headless headers (excludes SDL-dependent code)
|
# Define NO_SDL for libtcod-headless headers (excludes SDL-dependent code)
|
||||||
target_compile_definitions(mcrogueface PRIVATE NO_SDL)
|
target_compile_definitions(mcrogueface PRIVATE NO_SDL)
|
||||||
|
|
||||||
# On Windows, define Py_ENABLE_SHARED for proper Python DLL imports
|
# On Windows, set subsystem to WINDOWS to hide console
|
||||||
# Py_PYCONFIG_H prevents Include/pyconfig.h (Linux config) from being included
|
if(WIN32)
|
||||||
# (PC/pyconfig.h already defines HAVE_DECLSPEC_DLL and MS_WINDOWS)
|
set_target_properties(mcrogueface PROPERTIES
|
||||||
if(WIN32 OR MCRF_CROSS_WINDOWS)
|
WIN32_EXECUTABLE TRUE
|
||||||
target_compile_definitions(mcrogueface PRIVATE Py_ENABLE_SHARED Py_PYCONFIG_H)
|
LINK_FLAGS "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")
|
||||||
endif()
|
|
||||||
|
|
||||||
# On Windows, set subsystem to WINDOWS to hide console (release builds only)
|
|
||||||
# Use -DMCRF_WINDOWS_CONSOLE=ON for debug builds with console output
|
|
||||||
option(MCRF_WINDOWS_CONSOLE "Keep console window visible for debugging" OFF)
|
|
||||||
|
|
||||||
if(WIN32 AND NOT MCRF_CROSS_WINDOWS)
|
|
||||||
# MSVC-specific flags
|
|
||||||
if(NOT MCRF_WINDOWS_CONSOLE)
|
|
||||||
set_target_properties(mcrogueface PROPERTIES
|
|
||||||
WIN32_EXECUTABLE TRUE
|
|
||||||
LINK_FLAGS "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")
|
|
||||||
endif()
|
|
||||||
elseif(MCRF_CROSS_WINDOWS)
|
|
||||||
# MinGW cross-compilation
|
|
||||||
if(NOT MCRF_WINDOWS_CONSOLE)
|
|
||||||
# Release: use -mwindows to hide console
|
|
||||||
set_target_properties(mcrogueface PROPERTIES
|
|
||||||
WIN32_EXECUTABLE TRUE
|
|
||||||
LINK_FLAGS "-mwindows")
|
|
||||||
else()
|
|
||||||
# Debug: keep console for stdout/stderr output
|
|
||||||
message(STATUS "Windows console enabled for debugging")
|
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Now the linker will find the libraries in the specified directory
|
# Now the linker will find the libraries in the specified directory
|
||||||
|
|
@ -178,36 +99,20 @@ add_custom_command(TARGET mcrogueface POST_BUILD
|
||||||
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrogueface>/lib)
|
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrogueface>/lib)
|
||||||
|
|
||||||
# On Windows, copy DLLs to executable directory
|
# On Windows, copy DLLs to executable directory
|
||||||
if(MCRF_CROSS_WINDOWS)
|
if(WIN32)
|
||||||
# Cross-compilation: copy DLLs from __lib_windows
|
# Copy all DLL files from lib to the executable directory
|
||||||
add_custom_command(TARGET mcrogueface POST_BUILD
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
|
||||||
${CMAKE_SOURCE_DIR}/__lib_windows/sfml/bin $<TARGET_FILE_DIR:mcrogueface>
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
|
||||||
${CMAKE_SOURCE_DIR}/__lib_windows/libtcod/bin $<TARGET_FILE_DIR:mcrogueface>
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy
|
|
||||||
${CMAKE_SOURCE_DIR}/__lib_windows/python314.dll $<TARGET_FILE_DIR:mcrogueface>
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy
|
|
||||||
${CMAKE_SOURCE_DIR}/__lib_windows/python3.dll $<TARGET_FILE_DIR:mcrogueface>
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy
|
|
||||||
${CMAKE_SOURCE_DIR}/__lib_windows/vcruntime140.dll $<TARGET_FILE_DIR:mcrogueface>
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy
|
|
||||||
${CMAKE_SOURCE_DIR}/__lib_windows/vcruntime140_1.dll $<TARGET_FILE_DIR:mcrogueface>
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy
|
|
||||||
/usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll $<TARGET_FILE_DIR:mcrogueface>
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E echo "Copied Windows DLLs to executable directory")
|
|
||||||
|
|
||||||
# Copy Python standard library zip
|
|
||||||
add_custom_command(TARGET mcrogueface POST_BUILD
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy
|
|
||||||
${CMAKE_SOURCE_DIR}/__lib_windows/python314.zip $<TARGET_FILE_DIR:mcrogueface>
|
|
||||||
COMMAND ${CMAKE_COMMAND} -E echo "Copied Python stdlib")
|
|
||||||
elseif(WIN32)
|
|
||||||
# Native Windows build: copy DLLs from __lib
|
|
||||||
add_custom_command(TARGET mcrogueface POST_BUILD
|
add_custom_command(TARGET mcrogueface POST_BUILD
|
||||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrogueface>
|
${CMAKE_SOURCE_DIR}/__lib $<TARGET_FILE_DIR:mcrogueface>
|
||||||
COMMAND ${CMAKE_COMMAND} -E echo "Copied DLLs to executable directory")
|
COMMAND ${CMAKE_COMMAND} -E echo "Copied DLLs to executable directory")
|
||||||
|
|
||||||
|
# Alternative: Copy specific DLLs if you want more control
|
||||||
|
# file(GLOB DLLS "${CMAKE_SOURCE_DIR}/__lib/*.dll")
|
||||||
|
# foreach(DLL ${DLLS})
|
||||||
|
# add_custom_command(TARGET mcrogueface POST_BUILD
|
||||||
|
# COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||||
|
# ${DLL} $<TARGET_FILE_DIR:mcrogueface>)
|
||||||
|
# endforeach()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# rpath for including shared libraries (Linux/Unix only)
|
# rpath for including shared libraries (Linux/Unix only)
|
||||||
|
|
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
# CMake toolchain file for cross-compiling to Windows using MinGW-w64
|
|
||||||
# Usage: cmake -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64-x86_64.cmake ..
|
|
||||||
|
|
||||||
set(CMAKE_SYSTEM_NAME Windows)
|
|
||||||
set(CMAKE_SYSTEM_PROCESSOR x86_64)
|
|
||||||
|
|
||||||
# Specify the cross-compiler (use posix variant for std::mutex support)
|
|
||||||
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc-posix)
|
|
||||||
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++-posix)
|
|
||||||
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
|
|
||||||
|
|
||||||
# Target environment location
|
|
||||||
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
|
|
||||||
|
|
||||||
# Add MinGW system include directories for Windows headers
|
|
||||||
include_directories(SYSTEM /usr/x86_64-w64-mingw32/include)
|
|
||||||
|
|
||||||
# Adjust search behavior
|
|
||||||
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
|
||||||
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
|
||||||
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
|
||||||
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
|
|
||||||
|
|
||||||
# Static linking of libgcc and libstdc++ to avoid runtime dependency issues
|
|
||||||
# Enable auto-import for Python DLL data symbols
|
|
||||||
set(CMAKE_EXE_LINKER_FLAGS_INIT "-static-libgcc -static-libstdc++ -Wl,--enable-auto-import")
|
|
||||||
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-static-libgcc -static-libstdc++ -Wl,--enable-auto-import")
|
|
||||||
|
|
||||||
# Windows-specific defines
|
|
||||||
add_definitions(-DWIN32 -D_WIN32 -D_WINDOWS)
|
|
||||||
add_definitions(-DMINGW_HAS_SECURE_API)
|
|
||||||
|
|
||||||
# Disable console window for GUI applications (optional, can be overridden)
|
|
||||||
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -mwindows")
|
|
||||||
8
deps/platform/windows/platform.h
vendored
8
deps/platform/windows/platform.h
vendored
|
|
@ -1,12 +1,12 @@
|
||||||
#ifndef __PLATFORM
|
#ifndef __PLATFORM
|
||||||
#define __PLATFORM
|
#define __PLATFORM
|
||||||
#define __PLATFORM_SET_PYTHON_SEARCH_PATHS 1
|
#define __PLATFORM_SET_PYTHON_SEARCH_PATHS 0
|
||||||
#include <windows.h>
|
#include <Windows.h>
|
||||||
|
|
||||||
std::wstring executable_path()
|
std::wstring executable_path()
|
||||||
{
|
{
|
||||||
wchar_t buffer[MAX_PATH];
|
wchar_t buffer[MAX_PATH];
|
||||||
GetModuleFileNameW(NULL, buffer, MAX_PATH); // Use explicit Unicode version
|
GetModuleFileName(NULL, buffer, MAX_PATH);
|
||||||
std::wstring exec_path = buffer;
|
std::wstring exec_path = buffer;
|
||||||
size_t path_index = exec_path.find_last_of(L"\\/");
|
size_t path_index = exec_path.find_last_of(L"\\/");
|
||||||
return exec_path.substr(0, path_index);
|
return exec_path.substr(0, path_index);
|
||||||
|
|
@ -15,7 +15,7 @@ std::wstring executable_path()
|
||||||
std::wstring executable_filename()
|
std::wstring executable_filename()
|
||||||
{
|
{
|
||||||
wchar_t buffer[MAX_PATH];
|
wchar_t buffer[MAX_PATH];
|
||||||
GetModuleFileNameW(NULL, buffer, MAX_PATH); // Use explicit Unicode version
|
GetModuleFileName(NULL, buffer, MAX_PATH);
|
||||||
std::wstring exec_path = buffer;
|
std::wstring exec_path = buffer;
|
||||||
return exec_path;
|
return exec_path;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -767,7 +767,7 @@ void AnimationManager::addAnimation(std::shared_ptr<Animation> animation,
|
||||||
}
|
}
|
||||||
return; // Don't add to active animations yet
|
return; // Don't add to active animations yet
|
||||||
|
|
||||||
case AnimationConflictMode::RAISE_ERROR:
|
case AnimationConflictMode::ERROR:
|
||||||
// Raise Python exception
|
// Raise Python exception
|
||||||
PyGILState_STATE gstate = PyGILState_Ensure();
|
PyGILState_STATE gstate = PyGILState_Ensure();
|
||||||
PyErr_Format(PyExc_RuntimeError,
|
PyErr_Format(PyExc_RuntimeError,
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,9 @@ class UIEntity;
|
||||||
* ConflictMode - How to handle multiple animations on the same property (#120)
|
* ConflictMode - How to handle multiple animations on the same property (#120)
|
||||||
*/
|
*/
|
||||||
enum class AnimationConflictMode {
|
enum class AnimationConflictMode {
|
||||||
REPLACE, // Stop/complete existing animation, start new one (default)
|
REPLACE, // Stop/complete existing animation, start new one (default)
|
||||||
QUEUE, // Queue new animation to run after existing one completes
|
QUEUE, // Queue new animation to run after existing one completes
|
||||||
RAISE_ERROR // Raise an error if property is already being animated
|
ERROR // Raise an error if property is already being animated
|
||||||
// Note: Can't use ERROR as it conflicts with Windows macro
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Forward declare namespace
|
// Forward declare namespace
|
||||||
|
|
|
||||||
|
|
@ -65,21 +65,11 @@ GameEngine::GameEngine(const McRogueFaceConfig& cfg)
|
||||||
!config.python_mode;
|
!config.python_mode;
|
||||||
|
|
||||||
if (should_load_game) {
|
if (should_load_game) {
|
||||||
std::cerr << "[DEBUG] GameEngine: loading default game.py" << std::endl;
|
|
||||||
std::cerr.flush();
|
|
||||||
if (!Py_IsInitialized()) {
|
if (!Py_IsInitialized()) {
|
||||||
std::cerr << "[DEBUG] GameEngine: initializing Python API" << std::endl;
|
|
||||||
std::cerr.flush();
|
|
||||||
McRFPy_API::api_init();
|
McRFPy_API::api_init();
|
||||||
}
|
}
|
||||||
std::cerr << "[DEBUG] GameEngine: importing mcrfpy" << std::endl;
|
|
||||||
std::cerr.flush();
|
|
||||||
McRFPy_API::executePyString("import mcrfpy");
|
McRFPy_API::executePyString("import mcrfpy");
|
||||||
std::cerr << "[DEBUG] GameEngine: executing scripts/game.py" << std::endl;
|
|
||||||
std::cerr.flush();
|
|
||||||
McRFPy_API::executeScript("scripts/game.py");
|
McRFPy_API::executeScript("scripts/game.py");
|
||||||
std::cerr << "[DEBUG] GameEngine: game.py execution complete" << std::endl;
|
|
||||||
std::cerr.flush();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note: --exec scripts are NOT executed here.
|
// Note: --exec scripts are NOT executed here.
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@
|
||||||
#include "PyScene.h"
|
#include "PyScene.h"
|
||||||
#include "PythonObjectCache.h"
|
#include "PythonObjectCache.h"
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <libtcod.h>
|
#include <libtcod.h>
|
||||||
|
|
||||||
|
|
@ -807,26 +806,13 @@ void McRFPy_API::executeScript(std::string filename)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use std::ifstream + PyRun_SimpleString instead of PyRun_SimpleFile
|
FILE* PScriptFile = fopen(script_path.string().c_str(), "r");
|
||||||
// PyRun_SimpleFile has compatibility issues with MinGW-compiled code
|
if(PScriptFile) {
|
||||||
std::ifstream file(script_path);
|
PyRun_SimpleFile(PScriptFile, script_path.string().c_str());
|
||||||
if (!file.is_open()) {
|
fclose(PScriptFile);
|
||||||
|
} else {
|
||||||
std::cout << "Failed to open script: " << script_path.string() << std::endl;
|
std::cout << "Failed to open script: " << script_path.string() << std::endl;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string script_content((std::istreambuf_iterator<char>(file)),
|
|
||||||
std::istreambuf_iterator<char>());
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
// Set __file__ before execution
|
|
||||||
PyObject* main_module = PyImport_AddModule("__main__");
|
|
||||||
PyObject* main_dict = PyModule_GetDict(main_module);
|
|
||||||
PyObject* py_filename = PyUnicode_FromString(script_path.string().c_str());
|
|
||||||
PyDict_SetItemString(main_dict, "__file__", py_filename);
|
|
||||||
Py_DECREF(py_filename);
|
|
||||||
|
|
||||||
PyRun_SimpleString(script_content.c_str());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void McRFPy_API::api_shutdown()
|
void McRFPy_API::api_shutdown()
|
||||||
|
|
|
||||||
|
|
@ -186,7 +186,7 @@ static bool parseConflictMode(const char* mode_str, AnimationConflictMode& mode)
|
||||||
} else if (strcmp(mode_str, "queue") == 0) {
|
} else if (strcmp(mode_str, "queue") == 0) {
|
||||||
mode = AnimationConflictMode::QUEUE;
|
mode = AnimationConflictMode::QUEUE;
|
||||||
} else if (strcmp(mode_str, "error") == 0) {
|
} else if (strcmp(mode_str, "error") == 0) {
|
||||||
mode = AnimationConflictMode::RAISE_ERROR;
|
mode = AnimationConflictMode::ERROR;
|
||||||
} else {
|
} else {
|
||||||
PyErr_Format(PyExc_ValueError,
|
PyErr_Format(PyExc_ValueError,
|
||||||
"Invalid conflict_mode '%s'. Must be 'replace', 'queue', or 'error'.", mode_str);
|
"Invalid conflict_mode '%s'. Must be 'replace', 'queue', or 'error'.", mode_str);
|
||||||
|
|
|
||||||
|
|
@ -172,7 +172,7 @@ PyObject* PyColor::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds)
|
||||||
PyObject* PyColor::get_member(PyObject* obj, void* closure)
|
PyObject* PyColor::get_member(PyObject* obj, void* closure)
|
||||||
{
|
{
|
||||||
PyColorObject* self = (PyColorObject*)obj;
|
PyColorObject* self = (PyColorObject*)obj;
|
||||||
intptr_t member = (intptr_t)closure;
|
long member = (long)closure;
|
||||||
|
|
||||||
switch (member) {
|
switch (member) {
|
||||||
case 0: // r
|
case 0: // r
|
||||||
|
|
@ -192,7 +192,7 @@ PyObject* PyColor::get_member(PyObject* obj, void* closure)
|
||||||
int PyColor::set_member(PyObject* obj, PyObject* value, void* closure)
|
int PyColor::set_member(PyObject* obj, PyObject* value, void* closure)
|
||||||
{
|
{
|
||||||
PyColorObject* self = (PyColorObject*)obj;
|
PyColorObject* self = (PyColorObject*)obj;
|
||||||
intptr_t member = (intptr_t)closure;
|
long member = (long)closure;
|
||||||
|
|
||||||
if (!PyLong_Check(value)) {
|
if (!PyLong_Check(value)) {
|
||||||
PyErr_SetString(PyExc_TypeError, "Color values must be integers");
|
PyErr_SetString(PyExc_TypeError, "Color values must be integers");
|
||||||
|
|
|
||||||
|
|
@ -227,7 +227,7 @@ PyObject* PyVector::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds)
|
||||||
PyObject* PyVector::get_member(PyObject* obj, void* closure)
|
PyObject* PyVector::get_member(PyObject* obj, void* closure)
|
||||||
{
|
{
|
||||||
PyVectorObject* self = (PyVectorObject*)obj;
|
PyVectorObject* self = (PyVectorObject*)obj;
|
||||||
if (reinterpret_cast<intptr_t>(closure) == 0) {
|
if (reinterpret_cast<long>(closure) == 0) {
|
||||||
// x
|
// x
|
||||||
return PyFloat_FromDouble(self->data.x);
|
return PyFloat_FromDouble(self->data.x);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -250,7 +250,7 @@ int PyVector::set_member(PyObject* obj, PyObject* value, void* closure)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reinterpret_cast<intptr_t>(closure) == 0) {
|
if (reinterpret_cast<long>(closure) == 0) {
|
||||||
// x
|
// x
|
||||||
self->data.x = val;
|
self->data.x = val;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ void UICaption::onPositionChanged()
|
||||||
|
|
||||||
PyObject* UICaption::get_float_member(PyUICaptionObject* self, void* closure)
|
PyObject* UICaption::get_float_member(PyUICaptionObject* self, void* closure)
|
||||||
{
|
{
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (member_ptr == 0)
|
if (member_ptr == 0)
|
||||||
return PyFloat_FromDouble(self->data->text.getPosition().x);
|
return PyFloat_FromDouble(self->data->text.getPosition().x);
|
||||||
else if (member_ptr == 1)
|
else if (member_ptr == 1)
|
||||||
|
|
@ -115,7 +115,7 @@ PyObject* UICaption::get_float_member(PyUICaptionObject* self, void* closure)
|
||||||
int UICaption::set_float_member(PyUICaptionObject* self, PyObject* value, void* closure)
|
int UICaption::set_float_member(PyUICaptionObject* self, PyObject* value, void* closure)
|
||||||
{
|
{
|
||||||
float val;
|
float val;
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (PyFloat_Check(value))
|
if (PyFloat_Check(value))
|
||||||
{
|
{
|
||||||
val = PyFloat_AsDouble(value);
|
val = PyFloat_AsDouble(value);
|
||||||
|
|
@ -156,7 +156,7 @@ 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
|
// 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)
|
// validate closure (should be impossible to be wrong, but it's thorough)
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (member_ptr != 0 && member_ptr != 1)
|
if (member_ptr != 0 && member_ptr != 1)
|
||||||
{
|
{
|
||||||
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
|
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
|
||||||
|
|
@ -181,7 +181,7 @@ PyObject* UICaption::get_color_member(PyUICaptionObject* self, void* closure)
|
||||||
|
|
||||||
int UICaption::set_color_member(PyUICaptionObject* self, PyObject* value, void* closure)
|
int UICaption::set_color_member(PyUICaptionObject* self, PyObject* value, void* closure)
|
||||||
{
|
{
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
//TODO: this logic of (PyColor instance OR tuple -> sf::color) should be encapsulated for reuse
|
//TODO: this logic of (PyColor instance OR tuple -> sf::color) should be encapsulated for reuse
|
||||||
int r, g, b, a;
|
int r, g, b, a;
|
||||||
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color") /*(PyObject*)&mcrfpydef::PyColorType)*/))
|
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color") /*(PyObject*)&mcrfpydef::PyColorType)*/))
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,7 @@ void UIDrawable::render()
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* UIDrawable::get_click(PyObject* self, void* closure) {
|
PyObject* UIDrawable::get_click(PyObject* self, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure)); // trust me bro, it's an Enum
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure)); // trust me bro, it's an Enum
|
||||||
PyObject* ptr;
|
PyObject* ptr;
|
||||||
|
|
||||||
switch (objtype)
|
switch (objtype)
|
||||||
|
|
@ -228,7 +228,7 @@ PyObject* UIDrawable::get_click(PyObject* self, void* closure) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int UIDrawable::set_click(PyObject* self, PyObject* value, void* closure) {
|
int UIDrawable::set_click(PyObject* self, PyObject* value, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure)); // trust me bro, it's an Enum
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure)); // trust me bro, it's an Enum
|
||||||
UIDrawable* target;
|
UIDrawable* target;
|
||||||
switch (objtype)
|
switch (objtype)
|
||||||
{
|
{
|
||||||
|
|
@ -305,7 +305,7 @@ void UIDrawable::on_move_unregister()
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* UIDrawable::get_int(PyObject* self, void* closure) {
|
PyObject* UIDrawable::get_int(PyObject* self, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
UIDrawable* drawable = nullptr;
|
UIDrawable* drawable = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -339,7 +339,7 @@ PyObject* UIDrawable::get_int(PyObject* self, void* closure) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int UIDrawable::set_int(PyObject* self, PyObject* value, void* closure) {
|
int UIDrawable::set_int(PyObject* self, PyObject* value, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
UIDrawable* drawable = nullptr;
|
UIDrawable* drawable = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -405,7 +405,7 @@ void UIDrawable::notifyZIndexChanged() {
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* UIDrawable::get_name(PyObject* self, void* closure) {
|
PyObject* UIDrawable::get_name(PyObject* self, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
UIDrawable* drawable = nullptr;
|
UIDrawable* drawable = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -439,7 +439,7 @@ PyObject* UIDrawable::get_name(PyObject* self, void* closure) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int UIDrawable::set_name(PyObject* self, PyObject* value, void* closure) {
|
int UIDrawable::set_name(PyObject* self, PyObject* value, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
UIDrawable* drawable = nullptr;
|
UIDrawable* drawable = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -639,7 +639,7 @@ int UIDrawable::set_float_member(PyObject* self, PyObject* value, void* closure)
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* UIDrawable::get_pos(PyObject* self, void* closure) {
|
PyObject* UIDrawable::get_pos(PyObject* self, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
UIDrawable* drawable = nullptr;
|
UIDrawable* drawable = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -686,7 +686,7 @@ PyObject* UIDrawable::get_pos(PyObject* self, void* closure) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int UIDrawable::set_pos(PyObject* self, PyObject* value, void* closure) {
|
int UIDrawable::set_pos(PyObject* self, PyObject* value, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
UIDrawable* drawable = nullptr;
|
UIDrawable* drawable = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -892,7 +892,7 @@ void UIDrawable::markDirty() {
|
||||||
|
|
||||||
// Python API - get parent drawable
|
// Python API - get parent drawable
|
||||||
PyObject* UIDrawable::get_parent(PyObject* self, void* closure) {
|
PyObject* UIDrawable::get_parent(PyObject* self, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
UIDrawable* drawable = nullptr;
|
UIDrawable* drawable = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -1003,7 +1003,7 @@ PyObject* UIDrawable::get_parent(PyObject* self, void* closure) {
|
||||||
|
|
||||||
// Python API - set parent drawable (or None to remove from parent)
|
// Python API - set parent drawable (or None to remove from parent)
|
||||||
int UIDrawable::set_parent(PyObject* self, PyObject* value, void* closure) {
|
int UIDrawable::set_parent(PyObject* self, PyObject* value, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
std::shared_ptr<UIDrawable> drawable = nullptr;
|
std::shared_ptr<UIDrawable> drawable = nullptr;
|
||||||
|
|
||||||
// Get the shared_ptr for self
|
// Get the shared_ptr for self
|
||||||
|
|
@ -1125,7 +1125,7 @@ int UIDrawable::set_parent(PyObject* self, PyObject* value, void* closure) {
|
||||||
|
|
||||||
// Python API - get global position (read-only)
|
// Python API - get global position (read-only)
|
||||||
PyObject* UIDrawable::get_global_pos(PyObject* self, void* closure) {
|
PyObject* UIDrawable::get_global_pos(PyObject* self, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
UIDrawable* drawable = nullptr;
|
UIDrawable* drawable = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -1175,7 +1175,7 @@ PyObject* UIDrawable::get_global_pos(PyObject* self, void* closure) {
|
||||||
|
|
||||||
// #138, #188 - Python API for bounds property - returns (pos, size) as pair of Vectors
|
// #138, #188 - Python API for bounds property - returns (pos, size) as pair of Vectors
|
||||||
PyObject* UIDrawable::get_bounds_py(PyObject* self, void* closure) {
|
PyObject* UIDrawable::get_bounds_py(PyObject* self, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
UIDrawable* drawable = nullptr;
|
UIDrawable* drawable = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -1236,7 +1236,7 @@ PyObject* UIDrawable::get_bounds_py(PyObject* self, void* closure) {
|
||||||
|
|
||||||
// #138, #188 - Python API for global_bounds property - returns (pos, size) as pair of Vectors
|
// #138, #188 - Python API for global_bounds property - returns (pos, size) as pair of Vectors
|
||||||
PyObject* UIDrawable::get_global_bounds_py(PyObject* self, void* closure) {
|
PyObject* UIDrawable::get_global_bounds_py(PyObject* self, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
UIDrawable* drawable = nullptr;
|
UIDrawable* drawable = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -1297,7 +1297,7 @@ PyObject* UIDrawable::get_global_bounds_py(PyObject* self, void* closure) {
|
||||||
|
|
||||||
// #140 - Python API for on_enter property
|
// #140 - Python API for on_enter property
|
||||||
PyObject* UIDrawable::get_on_enter(PyObject* self, void* closure) {
|
PyObject* UIDrawable::get_on_enter(PyObject* self, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
PyObject* ptr = nullptr;
|
PyObject* ptr = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -1340,7 +1340,7 @@ PyObject* UIDrawable::get_on_enter(PyObject* self, void* closure) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int UIDrawable::set_on_enter(PyObject* self, PyObject* value, void* closure) {
|
int UIDrawable::set_on_enter(PyObject* self, PyObject* value, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
UIDrawable* target = nullptr;
|
UIDrawable* target = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -1380,7 +1380,7 @@ int UIDrawable::set_on_enter(PyObject* self, PyObject* value, void* closure) {
|
||||||
|
|
||||||
// #140 - Python API for on_exit property
|
// #140 - Python API for on_exit property
|
||||||
PyObject* UIDrawable::get_on_exit(PyObject* self, void* closure) {
|
PyObject* UIDrawable::get_on_exit(PyObject* self, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
PyObject* ptr = nullptr;
|
PyObject* ptr = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -1423,7 +1423,7 @@ PyObject* UIDrawable::get_on_exit(PyObject* self, void* closure) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int UIDrawable::set_on_exit(PyObject* self, PyObject* value, void* closure) {
|
int UIDrawable::set_on_exit(PyObject* self, PyObject* value, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
UIDrawable* target = nullptr;
|
UIDrawable* target = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -1463,7 +1463,7 @@ int UIDrawable::set_on_exit(PyObject* self, PyObject* value, void* closure) {
|
||||||
|
|
||||||
// #140 - Python API for hovered property (read-only)
|
// #140 - Python API for hovered property (read-only)
|
||||||
PyObject* UIDrawable::get_hovered(PyObject* self, void* closure) {
|
PyObject* UIDrawable::get_hovered(PyObject* self, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
UIDrawable* drawable = nullptr;
|
UIDrawable* drawable = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -1498,7 +1498,7 @@ PyObject* UIDrawable::get_hovered(PyObject* self, void* closure) {
|
||||||
|
|
||||||
// #141 - Python API for on_move property
|
// #141 - Python API for on_move property
|
||||||
PyObject* UIDrawable::get_on_move(PyObject* self, void* closure) {
|
PyObject* UIDrawable::get_on_move(PyObject* self, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
PyObject* ptr = nullptr;
|
PyObject* ptr = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -1541,7 +1541,7 @@ PyObject* UIDrawable::get_on_move(PyObject* self, void* closure) {
|
||||||
}
|
}
|
||||||
|
|
||||||
int UIDrawable::set_on_move(PyObject* self, PyObject* value, void* closure) {
|
int UIDrawable::set_on_move(PyObject* self, PyObject* value, void* closure) {
|
||||||
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<intptr_t>(closure));
|
PyObjectsEnum objtype = static_cast<PyObjectsEnum>(reinterpret_cast<long>(closure));
|
||||||
UIDrawable* target = nullptr;
|
UIDrawable* target = nullptr;
|
||||||
|
|
||||||
switch (objtype) {
|
switch (objtype) {
|
||||||
|
|
@ -1689,7 +1689,7 @@ PyObject* UIDrawable_animate_impl(std::shared_ptr<UIDrawable> self, PyObject* ar
|
||||||
} else if (strcmp(conflict_mode_str, "queue") == 0) {
|
} else if (strcmp(conflict_mode_str, "queue") == 0) {
|
||||||
conflict_mode = AnimationConflictMode::QUEUE;
|
conflict_mode = AnimationConflictMode::QUEUE;
|
||||||
} else if (strcmp(conflict_mode_str, "error") == 0) {
|
} else if (strcmp(conflict_mode_str, "error") == 0) {
|
||||||
conflict_mode = AnimationConflictMode::RAISE_ERROR;
|
conflict_mode = AnimationConflictMode::ERROR;
|
||||||
} else {
|
} else {
|
||||||
PyErr_Format(PyExc_ValueError,
|
PyErr_Format(PyExc_ValueError,
|
||||||
"Invalid conflict_mode '%s'. Must be 'replace', 'queue', or 'error'.", conflict_mode_str);
|
"Invalid conflict_mode '%s'. Must be 'replace', 'queue', or 'error'.", conflict_mode_str);
|
||||||
|
|
|
||||||
|
|
@ -358,7 +358,7 @@ PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>&
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* UIEntity::get_position(PyUIEntityObject* self, void* closure) {
|
PyObject* UIEntity::get_position(PyUIEntityObject* self, void* closure) {
|
||||||
if (reinterpret_cast<intptr_t>(closure) == 0) {
|
if (reinterpret_cast<long>(closure) == 0) {
|
||||||
return sfVector2f_to_PyObject(self->data->position);
|
return sfVector2f_to_PyObject(self->data->position);
|
||||||
} else {
|
} else {
|
||||||
// Return integer-cast position for grid coordinates
|
// Return integer-cast position for grid coordinates
|
||||||
|
|
@ -373,7 +373,7 @@ int UIEntity::set_position(PyUIEntityObject* self, PyObject* value, void* closur
|
||||||
float old_x = self->data->position.x;
|
float old_x = self->data->position.x;
|
||||||
float old_y = self->data->position.y;
|
float old_y = self->data->position.y;
|
||||||
|
|
||||||
if (reinterpret_cast<intptr_t>(closure) == 0) {
|
if (reinterpret_cast<long>(closure) == 0) {
|
||||||
sf::Vector2f vec = PyObject_to_sfVector2f(value);
|
sf::Vector2f vec = PyObject_to_sfVector2f(value);
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
return -1; // Error already set by PyObject_to_sfVector2f
|
return -1; // Error already set by PyObject_to_sfVector2f
|
||||||
|
|
@ -418,7 +418,7 @@ int UIEntity::set_spritenumber(PyUIEntityObject* self, PyObject* value, void* cl
|
||||||
|
|
||||||
PyObject* UIEntity::get_float_member(PyUIEntityObject* self, void* closure)
|
PyObject* UIEntity::get_float_member(PyUIEntityObject* self, void* closure)
|
||||||
{
|
{
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (member_ptr == 0) // x
|
if (member_ptr == 0) // x
|
||||||
return PyFloat_FromDouble(self->data->position.x);
|
return PyFloat_FromDouble(self->data->position.x);
|
||||||
else if (member_ptr == 1) // y
|
else if (member_ptr == 1) // y
|
||||||
|
|
@ -433,7 +433,7 @@ PyObject* UIEntity::get_float_member(PyUIEntityObject* self, void* closure)
|
||||||
int UIEntity::set_float_member(PyUIEntityObject* self, PyObject* value, void* closure)
|
int UIEntity::set_float_member(PyUIEntityObject* self, PyObject* value, void* closure)
|
||||||
{
|
{
|
||||||
float val;
|
float val;
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (PyFloat_Check(value))
|
if (PyFloat_Check(value))
|
||||||
{
|
{
|
||||||
val = PyFloat_AsDouble(value);
|
val = PyFloat_AsDouble(value);
|
||||||
|
|
@ -540,7 +540,7 @@ PyObject* UIEntity::get_pixel_member(PyUIEntityObject* self, void* closure) {
|
||||||
float cell_width, cell_height;
|
float cell_width, cell_height;
|
||||||
get_cell_dimensions(self->data.get(), cell_width, cell_height);
|
get_cell_dimensions(self->data.get(), cell_width, cell_height);
|
||||||
|
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (member_ptr == 0) // x
|
if (member_ptr == 0) // x
|
||||||
return PyFloat_FromDouble(self->data->position.x * cell_width);
|
return PyFloat_FromDouble(self->data->position.x * cell_width);
|
||||||
else // y
|
else // y
|
||||||
|
|
@ -570,7 +570,7 @@ int UIEntity::set_pixel_member(PyUIEntityObject* self, PyObject* value, void* cl
|
||||||
float old_x = self->data->position.x;
|
float old_x = self->data->position.x;
|
||||||
float old_y = self->data->position.y;
|
float old_y = self->data->position.y;
|
||||||
|
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (member_ptr == 0) // x
|
if (member_ptr == 0) // x
|
||||||
self->data->position.x = val / cell_width;
|
self->data->position.x = val / cell_width;
|
||||||
else // y
|
else // y
|
||||||
|
|
@ -584,7 +584,7 @@ int UIEntity::set_pixel_member(PyUIEntityObject* self, PyObject* value, void* cl
|
||||||
|
|
||||||
// #176 - Integer grid position (grid_x, grid_y)
|
// #176 - Integer grid position (grid_x, grid_y)
|
||||||
PyObject* UIEntity::get_grid_int_member(PyUIEntityObject* self, void* closure) {
|
PyObject* UIEntity::get_grid_int_member(PyUIEntityObject* self, void* closure) {
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (member_ptr == 0) // grid_x
|
if (member_ptr == 0) // grid_x
|
||||||
return PyLong_FromLong(static_cast<int>(self->data->position.x));
|
return PyLong_FromLong(static_cast<int>(self->data->position.x));
|
||||||
else // grid_y
|
else // grid_y
|
||||||
|
|
@ -606,7 +606,7 @@ int UIEntity::set_grid_int_member(PyUIEntityObject* self, PyObject* value, void*
|
||||||
float old_x = self->data->position.x;
|
float old_x = self->data->position.x;
|
||||||
float old_y = self->data->position.y;
|
float old_y = self->data->position.y;
|
||||||
|
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (member_ptr == 0) // grid_x
|
if (member_ptr == 0) // grid_x
|
||||||
self->data->position.x = static_cast<float>(val);
|
self->data->position.x = static_cast<float>(val);
|
||||||
else // grid_y
|
else // grid_y
|
||||||
|
|
@ -1172,7 +1172,7 @@ PyObject* UIEntity::animate(PyUIEntityObject* self, PyObject* args, PyObject* kw
|
||||||
} else if (strcmp(conflict_mode_str, "queue") == 0) {
|
} else if (strcmp(conflict_mode_str, "queue") == 0) {
|
||||||
conflict_mode = AnimationConflictMode::QUEUE;
|
conflict_mode = AnimationConflictMode::QUEUE;
|
||||||
} else if (strcmp(conflict_mode_str, "error") == 0) {
|
} else if (strcmp(conflict_mode_str, "error") == 0) {
|
||||||
conflict_mode = AnimationConflictMode::RAISE_ERROR;
|
conflict_mode = AnimationConflictMode::ERROR;
|
||||||
} else {
|
} else {
|
||||||
PyErr_Format(PyExc_ValueError,
|
PyErr_Format(PyExc_ValueError,
|
||||||
"Invalid conflict_mode '%s'. Must be 'replace', 'queue', or 'error'.", conflict_mode_str);
|
"Invalid conflict_mode '%s'. Must be 'replace', 'queue', or 'error'.", conflict_mode_str);
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,7 @@ PyObject* UIFrame::get_children(PyUIFrameObject* self, void* closure)
|
||||||
|
|
||||||
PyObject* UIFrame::get_float_member(PyUIFrameObject* self, void* closure)
|
PyObject* UIFrame::get_float_member(PyUIFrameObject* self, void* closure)
|
||||||
{
|
{
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (member_ptr == 0)
|
if (member_ptr == 0)
|
||||||
return PyFloat_FromDouble(self->data->box.getPosition().x);
|
return PyFloat_FromDouble(self->data->box.getPosition().x);
|
||||||
else if (member_ptr == 1)
|
else if (member_ptr == 1)
|
||||||
|
|
@ -208,7 +208,7 @@ PyObject* UIFrame::get_float_member(PyUIFrameObject* self, void* closure)
|
||||||
int UIFrame::set_float_member(PyUIFrameObject* self, PyObject* value, void* closure)
|
int UIFrame::set_float_member(PyUIFrameObject* self, PyObject* value, void* closure)
|
||||||
{
|
{
|
||||||
float val;
|
float val;
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (PyFloat_Check(value))
|
if (PyFloat_Check(value))
|
||||||
{
|
{
|
||||||
val = PyFloat_AsDouble(value);
|
val = PyFloat_AsDouble(value);
|
||||||
|
|
@ -258,7 +258,7 @@ int UIFrame::set_float_member(PyUIFrameObject* self, PyObject* value, void* clos
|
||||||
PyObject* UIFrame::get_color_member(PyUIFrameObject* self, void* closure)
|
PyObject* UIFrame::get_color_member(PyUIFrameObject* self, void* closure)
|
||||||
{
|
{
|
||||||
// validate closure (should be impossible to be wrong, but it's thorough)
|
// validate closure (should be impossible to be wrong, but it's thorough)
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (member_ptr != 0 && member_ptr != 1)
|
if (member_ptr != 0 && member_ptr != 1)
|
||||||
{
|
{
|
||||||
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
|
PyErr_SetString(PyExc_AttributeError, "Invalid attribute");
|
||||||
|
|
@ -293,7 +293,7 @@ PyObject* UIFrame::get_color_member(PyUIFrameObject* self, void* closure)
|
||||||
int UIFrame::set_color_member(PyUIFrameObject* self, PyObject* value, void* closure)
|
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
|
//TODO: this logic of (PyColor instance OR tuple -> sf::color) should be encapsulated for reuse
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
int r, g, b, a;
|
int r, g, b, a;
|
||||||
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color")))
|
if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color")))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1076,7 +1076,7 @@ int UIGrid::set_center(PyUIGridObject* self, PyObject* value, void* closure) {
|
||||||
|
|
||||||
PyObject* UIGrid::get_float_member(PyUIGridObject* self, void* closure)
|
PyObject* UIGrid::get_float_member(PyUIGridObject* self, void* closure)
|
||||||
{
|
{
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (member_ptr == 0) // x
|
if (member_ptr == 0) // x
|
||||||
return PyFloat_FromDouble(self->data->box.getPosition().x);
|
return PyFloat_FromDouble(self->data->box.getPosition().x);
|
||||||
else if (member_ptr == 1) // y
|
else if (member_ptr == 1) // y
|
||||||
|
|
@ -1101,7 +1101,7 @@ PyObject* UIGrid::get_float_member(PyUIGridObject* self, void* closure)
|
||||||
int UIGrid::set_float_member(PyUIGridObject* self, PyObject* value, void* closure)
|
int UIGrid::set_float_member(PyUIGridObject* self, PyObject* value, void* closure)
|
||||||
{
|
{
|
||||||
float val;
|
float val;
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (PyFloat_Check(value))
|
if (PyFloat_Check(value))
|
||||||
{
|
{
|
||||||
val = PyFloat_AsDouble(value);
|
val = PyFloat_AsDouble(value);
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ sf::Color PyObject_to_sfColor(PyObject* obj) {
|
||||||
// #150 - Removed get_color/set_color - now handled by layers
|
// #150 - Removed get_color/set_color - now handled by layers
|
||||||
|
|
||||||
PyObject* UIGridPoint::get_bool_member(PyUIGridPointObject* self, void* closure) {
|
PyObject* UIGridPoint::get_bool_member(PyUIGridPointObject* self, void* closure) {
|
||||||
if (reinterpret_cast<intptr_t>(closure) == 0) { // walkable
|
if (reinterpret_cast<long>(closure) == 0) { // walkable
|
||||||
return PyBool_FromLong(self->data->walkable);
|
return PyBool_FromLong(self->data->walkable);
|
||||||
} else { // transparent
|
} else { // transparent
|
||||||
return PyBool_FromLong(self->data->transparent);
|
return PyBool_FromLong(self->data->transparent);
|
||||||
|
|
@ -66,13 +66,13 @@ PyObject* UIGridPoint::get_bool_member(PyUIGridPointObject* self, void* closure)
|
||||||
|
|
||||||
int UIGridPoint::set_bool_member(PyUIGridPointObject* self, PyObject* value, void* closure) {
|
int UIGridPoint::set_bool_member(PyUIGridPointObject* self, PyObject* value, void* closure) {
|
||||||
if (value == Py_True) {
|
if (value == Py_True) {
|
||||||
if (reinterpret_cast<intptr_t>(closure) == 0) { // walkable
|
if (reinterpret_cast<long>(closure) == 0) { // walkable
|
||||||
self->data->walkable = true;
|
self->data->walkable = true;
|
||||||
} else { // transparent
|
} else { // transparent
|
||||||
self->data->transparent = true;
|
self->data->transparent = true;
|
||||||
}
|
}
|
||||||
} else if (value == Py_False) {
|
} else if (value == Py_False) {
|
||||||
if (reinterpret_cast<intptr_t>(closure) == 0) { // walkable
|
if (reinterpret_cast<long>(closure) == 0) { // walkable
|
||||||
self->data->walkable = false;
|
self->data->walkable = false;
|
||||||
} else { // transparent
|
} else { // transparent
|
||||||
self->data->transparent = false;
|
self->data->transparent = false;
|
||||||
|
|
@ -162,7 +162,7 @@ PyObject* UIGridPoint::repr(PyUIGridPointObject* self) {
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* UIGridPointState::get_bool_member(PyUIGridPointStateObject* self, void* closure) {
|
PyObject* UIGridPointState::get_bool_member(PyUIGridPointStateObject* self, void* closure) {
|
||||||
if (reinterpret_cast<intptr_t>(closure) == 0) { // visible
|
if (reinterpret_cast<long>(closure) == 0) { // visible
|
||||||
return PyBool_FromLong(self->data->visible);
|
return PyBool_FromLong(self->data->visible);
|
||||||
} else { // discovered
|
} else { // discovered
|
||||||
return PyBool_FromLong(self->data->discovered);
|
return PyBool_FromLong(self->data->discovered);
|
||||||
|
|
@ -180,7 +180,7 @@ int UIGridPointState::set_bool_member(PyUIGridPointStateObject* self, PyObject*
|
||||||
return -1; // PyObject_IsTrue returns -1 on error
|
return -1; // PyObject_IsTrue returns -1 on error
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reinterpret_cast<intptr_t>(closure) == 0) { // visible
|
if (reinterpret_cast<long>(closure) == 0) { // visible
|
||||||
self->data->visible = truthValue;
|
self->data->visible = truthValue;
|
||||||
} else { // discovered
|
} else { // discovered
|
||||||
self->data->discovered = truthValue;
|
self->data->discovered = truthValue;
|
||||||
|
|
|
||||||
|
|
@ -182,7 +182,7 @@ void UISprite::onPositionChanged()
|
||||||
|
|
||||||
PyObject* UISprite::get_float_member(PyUISpriteObject* self, void* closure)
|
PyObject* UISprite::get_float_member(PyUISpriteObject* self, void* closure)
|
||||||
{
|
{
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (member_ptr == 0)
|
if (member_ptr == 0)
|
||||||
return PyFloat_FromDouble(self->data->getPosition().x);
|
return PyFloat_FromDouble(self->data->getPosition().x);
|
||||||
else if (member_ptr == 1)
|
else if (member_ptr == 1)
|
||||||
|
|
@ -203,7 +203,7 @@ PyObject* UISprite::get_float_member(PyUISpriteObject* self, void* closure)
|
||||||
int UISprite::set_float_member(PyUISpriteObject* self, PyObject* value, void* closure)
|
int UISprite::set_float_member(PyUISpriteObject* self, PyObject* value, void* closure)
|
||||||
{
|
{
|
||||||
float val;
|
float val;
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (PyFloat_Check(value))
|
if (PyFloat_Check(value))
|
||||||
{
|
{
|
||||||
val = PyFloat_AsDouble(value);
|
val = PyFloat_AsDouble(value);
|
||||||
|
|
@ -232,7 +232,7 @@ int UISprite::set_float_member(PyUISpriteObject* self, PyObject* value, void* cl
|
||||||
|
|
||||||
PyObject* UISprite::get_int_member(PyUISpriteObject* self, void* closure)
|
PyObject* UISprite::get_int_member(PyUISpriteObject* self, void* closure)
|
||||||
{
|
{
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (true) {}
|
if (true) {}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -246,7 +246,7 @@ PyObject* UISprite::get_int_member(PyUISpriteObject* self, void* closure)
|
||||||
int UISprite::set_int_member(PyUISpriteObject* self, PyObject* value, void* closure)
|
int UISprite::set_int_member(PyUISpriteObject* self, PyObject* value, void* closure)
|
||||||
{
|
{
|
||||||
int val;
|
int val;
|
||||||
auto member_ptr = reinterpret_cast<intptr_t>(closure);
|
auto member_ptr = reinterpret_cast<long>(closure);
|
||||||
if (PyLong_Check(value))
|
if (PyLong_Check(value))
|
||||||
{
|
{
|
||||||
val = PyLong_AsLong(value);
|
val = PyLong_AsLong(value);
|
||||||
|
|
|
||||||
62
src/main.cpp
62
src/main.cpp
|
|
@ -7,7 +7,6 @@
|
||||||
#include "PyTexture.h"
|
#include "PyTexture.h"
|
||||||
#include <Python.h>
|
#include <Python.h>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
// Forward declarations
|
// Forward declarations
|
||||||
|
|
@ -19,6 +18,7 @@ int main(int argc, char* argv[])
|
||||||
McRogueFaceConfig config;
|
McRogueFaceConfig config;
|
||||||
CommandLineParser parser(argc, argv);
|
CommandLineParser parser(argc, argv);
|
||||||
|
|
||||||
|
// Parse arguments
|
||||||
auto parse_result = parser.parse(config);
|
auto parse_result = parser.parse(config);
|
||||||
if (parse_result.should_exit) {
|
if (parse_result.should_exit) {
|
||||||
return parse_result.exit_code;
|
return parse_result.exit_code;
|
||||||
|
|
@ -59,7 +59,7 @@ int run_python_interpreter(const McRogueFaceConfig& config)
|
||||||
|
|
||||||
// Initialize Python with configuration (argv is constructed from config)
|
// Initialize Python with configuration (argv is constructed from config)
|
||||||
McRFPy_API::init_python_with_config(config);
|
McRFPy_API::init_python_with_config(config);
|
||||||
|
|
||||||
// Import mcrfpy module and store reference
|
// Import mcrfpy module and store reference
|
||||||
McRFPy_API::mcrf_module = PyImport_ImportModule("mcrfpy");
|
McRFPy_API::mcrf_module = PyImport_ImportModule("mcrfpy");
|
||||||
if (!McRFPy_API::mcrf_module) {
|
if (!McRFPy_API::mcrf_module) {
|
||||||
|
|
@ -74,7 +74,7 @@ int run_python_interpreter(const McRogueFaceConfig& config)
|
||||||
PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_font", McRFPy_API::default_font->pyObject());
|
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());
|
PyObject_SetAttrString(McRFPy_API::mcrf_module, "default_texture", McRFPy_API::default_texture->pyObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle different Python modes
|
// Handle different Python modes
|
||||||
if (!config.python_command.empty()) {
|
if (!config.python_command.empty()) {
|
||||||
// Execute command from -c
|
// Execute command from -c
|
||||||
|
|
@ -82,15 +82,15 @@ int run_python_interpreter(const McRogueFaceConfig& config)
|
||||||
// Use PyRun_String to catch SystemExit
|
// Use PyRun_String to catch SystemExit
|
||||||
PyObject* main_module = PyImport_AddModule("__main__");
|
PyObject* main_module = PyImport_AddModule("__main__");
|
||||||
PyObject* main_dict = PyModule_GetDict(main_module);
|
PyObject* main_dict = PyModule_GetDict(main_module);
|
||||||
PyObject* result_obj = PyRun_String(config.python_command.c_str(),
|
PyObject* result_obj = PyRun_String(config.python_command.c_str(),
|
||||||
Py_file_input, main_dict, main_dict);
|
Py_file_input, main_dict, main_dict);
|
||||||
|
|
||||||
if (result_obj == NULL) {
|
if (result_obj == NULL) {
|
||||||
// Check if it's SystemExit
|
// Check if it's SystemExit
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
PyObject *type, *value, *traceback;
|
PyObject *type, *value, *traceback;
|
||||||
PyErr_Fetch(&type, &value, &traceback);
|
PyErr_Fetch(&type, &value, &traceback);
|
||||||
|
|
||||||
// If it's SystemExit and we're in interactive mode, clear it
|
// If it's SystemExit and we're in interactive mode, clear it
|
||||||
if (PyErr_GivenExceptionMatches(type, PyExc_SystemExit)) {
|
if (PyErr_GivenExceptionMatches(type, PyExc_SystemExit)) {
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
|
|
@ -99,7 +99,7 @@ int run_python_interpreter(const McRogueFaceConfig& config)
|
||||||
PyErr_Restore(type, value, traceback);
|
PyErr_Restore(type, value, traceback);
|
||||||
PyErr_Print();
|
PyErr_Print();
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_XDECREF(type);
|
Py_XDECREF(type);
|
||||||
Py_XDECREF(value);
|
Py_XDECREF(value);
|
||||||
Py_XDECREF(traceback);
|
Py_XDECREF(traceback);
|
||||||
|
|
@ -128,46 +128,16 @@ int run_python_interpreter(const McRogueFaceConfig& config)
|
||||||
}
|
}
|
||||||
else if (!config.script_path.empty()) {
|
else if (!config.script_path.empty()) {
|
||||||
// Execute script file (sys.argv already set at init time)
|
// Execute script file (sys.argv already set at init time)
|
||||||
// Note: Using PyRun_SimpleString instead of PyRun_SimpleFile for better
|
FILE* fp = fopen(config.script_path.string().c_str(), "r");
|
||||||
// cross-platform compatibility (PyRun_SimpleFile has issues with MinGW/Wine)
|
if (!fp) {
|
||||||
|
|
||||||
// Read file contents
|
|
||||||
std::ifstream file(config.script_path);
|
|
||||||
if (!file.is_open()) {
|
|
||||||
std::cerr << "mcrogueface: can't open file '" << config.script_path << "': ";
|
std::cerr << "mcrogueface: can't open file '" << config.script_path << "': ";
|
||||||
std::cerr << "[Errno " << errno << "] " << strerror(errno) << std::endl;
|
std::cerr << "[Errno " << errno << "] " << strerror(errno) << std::endl;
|
||||||
delete engine;
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
std::string script_content((std::istreambuf_iterator<char>(file)),
|
|
||||||
std::istreambuf_iterator<char>());
|
|
||||||
file.close();
|
|
||||||
|
|
||||||
// Set __file__ before execution
|
|
||||||
PyObject* main_module = PyImport_AddModule("__main__");
|
|
||||||
PyObject* main_dict = PyModule_GetDict(main_module);
|
|
||||||
PyObject* py_filename = PyUnicode_FromString(config.script_path.string().c_str());
|
|
||||||
PyDict_SetItemString(main_dict, "__file__", py_filename);
|
|
||||||
Py_DECREF(py_filename);
|
|
||||||
|
|
||||||
int result = PyRun_SimpleString(script_content.c_str());
|
|
||||||
|
|
||||||
// Flush Python stdout/stderr to ensure output appears before engine->run() blocks
|
|
||||||
PyObject* sys_module = PyImport_ImportModule("sys");
|
|
||||||
if (sys_module) {
|
|
||||||
PyObject* stdout_obj = PyObject_GetAttrString(sys_module, "stdout");
|
|
||||||
if (stdout_obj) {
|
|
||||||
PyObject_CallMethod(stdout_obj, "flush", NULL);
|
|
||||||
Py_DECREF(stdout_obj);
|
|
||||||
}
|
|
||||||
PyObject* stderr_obj = PyObject_GetAttrString(sys_module, "stderr");
|
|
||||||
if (stderr_obj) {
|
|
||||||
PyObject_CallMethod(stderr_obj, "flush", NULL);
|
|
||||||
Py_DECREF(stderr_obj);
|
|
||||||
}
|
|
||||||
Py_DECREF(sys_module);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
int result = PyRun_SimpleFile(fp, config.script_path.string().c_str());
|
||||||
|
fclose(fp);
|
||||||
|
|
||||||
if (config.interactive_mode) {
|
if (config.interactive_mode) {
|
||||||
// Even if script had SystemExit, continue to interactive mode
|
// Even if script had SystemExit, continue to interactive mode
|
||||||
if (result != 0) {
|
if (result != 0) {
|
||||||
|
|
@ -175,7 +145,7 @@ int run_python_interpreter(const McRogueFaceConfig& config)
|
||||||
if (PyErr_Occurred()) {
|
if (PyErr_Occurred()) {
|
||||||
PyObject *type, *value, *traceback;
|
PyObject *type, *value, *traceback;
|
||||||
PyErr_Fetch(&type, &value, &traceback);
|
PyErr_Fetch(&type, &value, &traceback);
|
||||||
|
|
||||||
if (PyErr_GivenExceptionMatches(type, PyExc_SystemExit)) {
|
if (PyErr_GivenExceptionMatches(type, PyExc_SystemExit)) {
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
result = 0; // Don't exit with error
|
result = 0; // Don't exit with error
|
||||||
|
|
@ -183,7 +153,7 @@ int run_python_interpreter(const McRogueFaceConfig& config)
|
||||||
PyErr_Restore(type, value, traceback);
|
PyErr_Restore(type, value, traceback);
|
||||||
PyErr_Print();
|
PyErr_Print();
|
||||||
}
|
}
|
||||||
|
|
||||||
Py_XDECREF(type);
|
Py_XDECREF(type);
|
||||||
Py_XDECREF(value);
|
Py_XDECREF(value);
|
||||||
Py_XDECREF(traceback);
|
Py_XDECREF(traceback);
|
||||||
|
|
@ -192,7 +162,7 @@ int run_python_interpreter(const McRogueFaceConfig& config)
|
||||||
// Run interactive mode after script
|
// Run interactive mode after script
|
||||||
PyRun_InteractiveLoop(stdin, "<stdin>");
|
PyRun_InteractiveLoop(stdin, "<stdin>");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the game engine after script execution
|
// Run the game engine after script execution
|
||||||
engine->run();
|
engine->run();
|
||||||
|
|
||||||
|
|
@ -227,7 +197,7 @@ int run_python_interpreter(const McRogueFaceConfig& config)
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
delete engine;
|
delete engine;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,226 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Test batch of API changes for issues #177, #179, #181, #182, #184, #185, #188, #189, #190"""
|
|
||||||
import sys
|
|
||||||
import mcrfpy
|
|
||||||
|
|
||||||
def test_issue_177_gridpoint_grid_pos():
|
|
||||||
"""Test GridPoint.grid_pos property returns tuple"""
|
|
||||||
print("Testing #177: GridPoint.grid_pos property...")
|
|
||||||
|
|
||||||
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
|
|
||||||
grid = mcrfpy.Grid(grid_size=(10, 10), texture=texture, pos=(0, 0), size=(160, 160))
|
|
||||||
|
|
||||||
# Get a grid point
|
|
||||||
point = grid.at(3, 5)
|
|
||||||
|
|
||||||
# Test grid_pos property exists and returns tuple
|
|
||||||
grid_pos = point.grid_pos
|
|
||||||
assert isinstance(grid_pos, tuple), f"grid_pos should be tuple, got {type(grid_pos)}"
|
|
||||||
assert len(grid_pos) == 2, f"grid_pos should have 2 elements, got {len(grid_pos)}"
|
|
||||||
assert grid_pos == (3, 5), f"grid_pos should be (3, 5), got {grid_pos}"
|
|
||||||
|
|
||||||
# Test another position
|
|
||||||
point2 = grid.at(7, 2)
|
|
||||||
assert point2.grid_pos == (7, 2), f"grid_pos should be (7, 2), got {point2.grid_pos}"
|
|
||||||
|
|
||||||
print(" PASS: GridPoint.grid_pos works correctly")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def test_issue_179_181_grid_vectors():
|
|
||||||
"""Test Grid properties return Vectors instead of tuples"""
|
|
||||||
print("Testing #179, #181: Grid Vector returns and grid_w/grid_h rename...")
|
|
||||||
|
|
||||||
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
|
|
||||||
grid = mcrfpy.Grid(grid_size=(15, 20), texture=texture, pos=(50, 100), size=(240, 320))
|
|
||||||
|
|
||||||
# Test center returns Vector
|
|
||||||
center = grid.center
|
|
||||||
assert hasattr(center, 'x') and hasattr(center, 'y'), f"center should be Vector, got {type(center)}"
|
|
||||||
|
|
||||||
# Test grid_size returns Vector
|
|
||||||
grid_size = grid.grid_size
|
|
||||||
assert hasattr(grid_size, 'x') and hasattr(grid_size, 'y'), f"grid_size should be Vector, got {type(grid_size)}"
|
|
||||||
assert grid_size.x == 15 and grid_size.y == 20, f"grid_size should be (15, 20), got ({grid_size.x}, {grid_size.y})"
|
|
||||||
|
|
||||||
# Test pos returns Vector
|
|
||||||
pos = grid.pos
|
|
||||||
assert hasattr(pos, 'x') and hasattr(pos, 'y'), f"pos should be Vector, got {type(pos)}"
|
|
||||||
|
|
||||||
print(" PASS: Grid properties return Vectors correctly")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def test_issue_182_caption_size():
|
|
||||||
"""Test Caption read-only size, w, h properties"""
|
|
||||||
print("Testing #182: Caption read-only size/w/h properties...")
|
|
||||||
|
|
||||||
font = mcrfpy.Font("assets/JetbrainsMono.ttf")
|
|
||||||
caption = mcrfpy.Caption(text="Test Caption", pos=(100, 100), font=font)
|
|
||||||
|
|
||||||
# Test size property
|
|
||||||
size = caption.size
|
|
||||||
assert hasattr(size, 'x') and hasattr(size, 'y'), f"size should be Vector, got {type(size)}"
|
|
||||||
assert size.x > 0, f"width should be positive, got {size.x}"
|
|
||||||
assert size.y > 0, f"height should be positive, got {size.y}"
|
|
||||||
|
|
||||||
# Test w property
|
|
||||||
w = caption.w
|
|
||||||
assert isinstance(w, float), f"w should be float, got {type(w)}"
|
|
||||||
assert w > 0, f"w should be positive, got {w}"
|
|
||||||
|
|
||||||
# Test h property
|
|
||||||
h = caption.h
|
|
||||||
assert isinstance(h, float), f"h should be float, got {type(h)}"
|
|
||||||
assert h > 0, f"h should be positive, got {h}"
|
|
||||||
|
|
||||||
# Verify w and h match size
|
|
||||||
assert abs(w - size.x) < 0.001, f"w ({w}) should match size.x ({size.x})"
|
|
||||||
assert abs(h - size.y) < 0.001, f"h ({h}) should match size.y ({size.y})"
|
|
||||||
|
|
||||||
# Verify read-only
|
|
||||||
try:
|
|
||||||
caption.size = mcrfpy.Vector(100, 100)
|
|
||||||
print(" FAIL: size should be read-only")
|
|
||||||
return False
|
|
||||||
except AttributeError:
|
|
||||||
pass # Expected
|
|
||||||
|
|
||||||
try:
|
|
||||||
caption.w = 100
|
|
||||||
print(" FAIL: w should be read-only")
|
|
||||||
return False
|
|
||||||
except AttributeError:
|
|
||||||
pass # Expected
|
|
||||||
|
|
||||||
try:
|
|
||||||
caption.h = 100
|
|
||||||
print(" FAIL: h should be read-only")
|
|
||||||
return False
|
|
||||||
except AttributeError:
|
|
||||||
pass # Expected
|
|
||||||
|
|
||||||
print(" PASS: Caption size/w/h properties work correctly")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def test_issue_184_189_module_namespace():
|
|
||||||
"""Test window singleton and hidden internal types"""
|
|
||||||
print("Testing #184, #189: window singleton + hide classes...")
|
|
||||||
|
|
||||||
# Test window singleton exists
|
|
||||||
assert hasattr(mcrfpy, 'window'), "mcrfpy.window should exist"
|
|
||||||
window = mcrfpy.window
|
|
||||||
assert window is not None, "window should not be None"
|
|
||||||
|
|
||||||
# Verify window properties
|
|
||||||
assert hasattr(window, 'resolution'), "window should have resolution property"
|
|
||||||
|
|
||||||
# Test that internal types are hidden from module namespace
|
|
||||||
assert not hasattr(mcrfpy, 'UICollectionIter'), "UICollectionIter should be hidden from module namespace"
|
|
||||||
assert not hasattr(mcrfpy, 'UIEntityCollectionIter'), "UIEntityCollectionIter should be hidden from module namespace"
|
|
||||||
|
|
||||||
# But iteration should still work - test UICollection iteration
|
|
||||||
scene = mcrfpy.Scene("test_scene")
|
|
||||||
ui = scene.children
|
|
||||||
ui.append(mcrfpy.Frame(pos=(0,0), size=(50,50)))
|
|
||||||
ui.append(mcrfpy.Caption(text="hi", pos=(0,0)))
|
|
||||||
|
|
||||||
count = 0
|
|
||||||
for item in ui:
|
|
||||||
count += 1
|
|
||||||
assert count == 2, f"Should iterate over 2 items, got {count}"
|
|
||||||
|
|
||||||
print(" PASS: window singleton and hidden types work correctly")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def test_issue_185_188_bounds_vectors():
|
|
||||||
"""Test bounds returns Vector pair, get_bounds() removed"""
|
|
||||||
print("Testing #185, #188: Remove get_bounds(), bounds as Vector pair...")
|
|
||||||
|
|
||||||
frame = mcrfpy.Frame(pos=(50, 100), size=(200, 150))
|
|
||||||
|
|
||||||
# Test bounds returns tuple of Vectors
|
|
||||||
bounds = frame.bounds
|
|
||||||
assert isinstance(bounds, tuple), f"bounds should be tuple, got {type(bounds)}"
|
|
||||||
assert len(bounds) == 2, f"bounds should have 2 elements, got {len(bounds)}"
|
|
||||||
|
|
||||||
pos, size = bounds
|
|
||||||
assert hasattr(pos, 'x') and hasattr(pos, 'y'), f"pos should be Vector, got {type(pos)}"
|
|
||||||
assert hasattr(size, 'x') and hasattr(size, 'y'), f"size should be Vector, got {type(size)}"
|
|
||||||
|
|
||||||
assert pos.x == 50 and pos.y == 100, f"pos should be (50, 100), got ({pos.x}, {pos.y})"
|
|
||||||
assert size.x == 200 and size.y == 150, f"size should be (200, 150), got ({size.x}, {size.y})"
|
|
||||||
|
|
||||||
# Test global_bounds also returns Vector pair
|
|
||||||
global_bounds = frame.global_bounds
|
|
||||||
assert isinstance(global_bounds, tuple), f"global_bounds should be tuple, got {type(global_bounds)}"
|
|
||||||
assert len(global_bounds) == 2, f"global_bounds should have 2 elements"
|
|
||||||
|
|
||||||
# Test get_bounds() method is removed (#185)
|
|
||||||
assert not hasattr(frame, 'get_bounds'), "get_bounds() method should be removed"
|
|
||||||
|
|
||||||
print(" PASS: bounds returns Vector pairs, get_bounds() removed")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def test_issue_190_layer_documentation():
|
|
||||||
"""Test that layer types have documentation"""
|
|
||||||
print("Testing #190: TileLayer/ColorLayer documentation...")
|
|
||||||
|
|
||||||
# Verify layer types exist and have docstrings
|
|
||||||
assert hasattr(mcrfpy, 'TileLayer'), "TileLayer should exist"
|
|
||||||
assert hasattr(mcrfpy, 'ColorLayer'), "ColorLayer should exist"
|
|
||||||
|
|
||||||
# Check that docstrings exist and contain useful info
|
|
||||||
tile_doc = mcrfpy.TileLayer.__doc__
|
|
||||||
color_doc = mcrfpy.ColorLayer.__doc__
|
|
||||||
|
|
||||||
assert tile_doc is not None and len(tile_doc) > 50, f"TileLayer should have substantial docstring, got: {tile_doc}"
|
|
||||||
assert color_doc is not None and len(color_doc) > 50, f"ColorLayer should have substantial docstring, got: {color_doc}"
|
|
||||||
|
|
||||||
# Check for key documentation elements
|
|
||||||
assert "layer" in tile_doc.lower() or "tile" in tile_doc.lower(), "TileLayer doc should mention layer or tile"
|
|
||||||
assert "layer" in color_doc.lower() or "color" in color_doc.lower(), "ColorLayer doc should mention layer or color"
|
|
||||||
|
|
||||||
print(" PASS: Layer documentation exists")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def run_all_tests():
|
|
||||||
"""Run all tests and report results"""
|
|
||||||
print("=" * 60)
|
|
||||||
print("API Changes Batch Test - Issues #177, #179, #181, #182, #184, #185, #188, #189, #190")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
tests = [
|
|
||||||
("Issue #177 GridPoint.grid_pos", test_issue_177_gridpoint_grid_pos),
|
|
||||||
("Issue #179, #181 Grid Vectors", test_issue_179_181_grid_vectors),
|
|
||||||
("Issue #182 Caption size/w/h", test_issue_182_caption_size),
|
|
||||||
("Issue #184, #189 Module namespace", test_issue_184_189_module_namespace),
|
|
||||||
("Issue #185, #188 Bounds Vectors", test_issue_185_188_bounds_vectors),
|
|
||||||
("Issue #190 Layer documentation", test_issue_190_layer_documentation),
|
|
||||||
]
|
|
||||||
|
|
||||||
passed = 0
|
|
||||||
failed = 0
|
|
||||||
|
|
||||||
for name, test_func in tests:
|
|
||||||
try:
|
|
||||||
if test_func():
|
|
||||||
passed += 1
|
|
||||||
else:
|
|
||||||
failed += 1
|
|
||||||
print(f" FAILED: {name}")
|
|
||||||
except Exception as e:
|
|
||||||
failed += 1
|
|
||||||
print(f" ERROR in {name}: {e}")
|
|
||||||
|
|
||||||
print("=" * 60)
|
|
||||||
print(f"Results: {passed} passed, {failed} failed")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
if failed == 0:
|
|
||||||
print("ALL TESTS PASSED")
|
|
||||||
sys.exit(0)
|
|
||||||
else:
|
|
||||||
print("SOME TESTS FAILED")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Run tests
|
|
||||||
run_all_tests()
|
|
||||||
|
|
@ -1,181 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Test for issue #176: Entity position naming consistency.
|
|
||||||
|
|
||||||
Tests the new Entity position properties:
|
|
||||||
- pos, x, y: pixel coordinates (requires grid attachment)
|
|
||||||
- grid_pos, grid_x, grid_y: integer tile coordinates
|
|
||||||
- draw_pos: fractional tile coordinates for animation
|
|
||||||
"""
|
|
||||||
import mcrfpy
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def test_entity_positions():
|
|
||||||
"""Test Entity position properties with grid attachment."""
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
# Create a texture with 16x16 sprites (standard tile size)
|
|
||||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
|
||||||
|
|
||||||
# Create a grid (10x10 tiles, 16x16 pixels each)
|
|
||||||
grid = mcrfpy.Grid(grid_size=(10, 10), texture=texture, pos=(0, 0), size=(160, 160))
|
|
||||||
|
|
||||||
# Create entity at tile position (3, 5)
|
|
||||||
entity = mcrfpy.Entity(grid_pos=(3, 5), texture=texture, grid=grid)
|
|
||||||
|
|
||||||
# Test 1: grid_pos should return integer tile coordinates
|
|
||||||
gpos = entity.grid_pos
|
|
||||||
if gpos.x != 3 or gpos.y != 5:
|
|
||||||
errors.append(f"grid_pos: expected (3, 5), got ({gpos.x}, {gpos.y})")
|
|
||||||
|
|
||||||
# Test 2: grid_x and grid_y should return integers
|
|
||||||
if entity.grid_x != 3:
|
|
||||||
errors.append(f"grid_x: expected 3, got {entity.grid_x}")
|
|
||||||
if entity.grid_y != 5:
|
|
||||||
errors.append(f"grid_y: expected 5, got {entity.grid_y}")
|
|
||||||
|
|
||||||
# Test 3: draw_pos should return float tile coordinates
|
|
||||||
dpos = entity.draw_pos
|
|
||||||
if abs(dpos.x - 3.0) > 0.001 or abs(dpos.y - 5.0) > 0.001:
|
|
||||||
errors.append(f"draw_pos: expected (3.0, 5.0), got ({dpos.x}, {dpos.y})")
|
|
||||||
|
|
||||||
# Test 4: pos should return pixel coordinates (tile * tile_size)
|
|
||||||
# With 16x16 tiles: (3, 5) tiles = (48, 80) pixels
|
|
||||||
ppos = entity.pos
|
|
||||||
if abs(ppos.x - 48.0) > 0.001 or abs(ppos.y - 80.0) > 0.001:
|
|
||||||
errors.append(f"pos: expected (48.0, 80.0), got ({ppos.x}, {ppos.y})")
|
|
||||||
|
|
||||||
# Test 5: x and y should return pixel coordinates
|
|
||||||
if abs(entity.x - 48.0) > 0.001:
|
|
||||||
errors.append(f"x: expected 48.0, got {entity.x}")
|
|
||||||
if abs(entity.y - 80.0) > 0.001:
|
|
||||||
errors.append(f"y: expected 80.0, got {entity.y}")
|
|
||||||
|
|
||||||
# Test 6: Setting grid_x/grid_y should update position
|
|
||||||
entity.grid_x = 7
|
|
||||||
entity.grid_y = 2
|
|
||||||
if entity.grid_x != 7 or entity.grid_y != 2:
|
|
||||||
errors.append(f"After setting grid_x/y: expected (7, 2), got ({entity.grid_x}, {entity.grid_y})")
|
|
||||||
# Pixel should update too: (7, 2) * 16 = (112, 32)
|
|
||||||
if abs(entity.x - 112.0) > 0.001 or abs(entity.y - 32.0) > 0.001:
|
|
||||||
errors.append(f"After grid_x/y set, pixel pos: expected (112, 32), got ({entity.x}, {entity.y})")
|
|
||||||
|
|
||||||
# Test 7: Setting pos (pixels) should update grid position
|
|
||||||
entity.pos = mcrfpy.Vector(64, 96) # (64, 96) / 16 = (4, 6) tiles
|
|
||||||
if abs(entity.draw_pos.x - 4.0) > 0.001 or abs(entity.draw_pos.y - 6.0) > 0.001:
|
|
||||||
errors.append(f"After setting pos, draw_pos: expected (4, 6), got ({entity.draw_pos.x}, {entity.draw_pos.y})")
|
|
||||||
if entity.grid_x != 4 or entity.grid_y != 6:
|
|
||||||
errors.append(f"After setting pos, grid_x/y: expected (4, 6), got ({entity.grid_x}, {entity.grid_y})")
|
|
||||||
|
|
||||||
# Test 8: repr should show grid_x/grid_y
|
|
||||||
repr_str = repr(entity)
|
|
||||||
if "grid_x=" not in repr_str or "grid_y=" not in repr_str:
|
|
||||||
errors.append(f"repr should contain grid_x/grid_y: {repr_str}")
|
|
||||||
|
|
||||||
return errors
|
|
||||||
|
|
||||||
|
|
||||||
def test_entity_without_grid():
|
|
||||||
"""Test that pixel positions require grid attachment."""
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
|
||||||
entity = mcrfpy.Entity(grid_pos=(3, 5), texture=texture) # No grid
|
|
||||||
|
|
||||||
# grid_pos should work without grid
|
|
||||||
if entity.grid_x != 3 or entity.grid_y != 5:
|
|
||||||
errors.append(f"grid_x/y without grid: expected (3, 5), got ({entity.grid_x}, {entity.grid_y})")
|
|
||||||
|
|
||||||
# pos should raise RuntimeError without grid
|
|
||||||
try:
|
|
||||||
_ = entity.pos
|
|
||||||
errors.append("entity.pos should raise RuntimeError without grid")
|
|
||||||
except RuntimeError as e:
|
|
||||||
if "not attached to a Grid" not in str(e):
|
|
||||||
errors.append(f"Wrong error message for pos: {e}")
|
|
||||||
|
|
||||||
# x should raise RuntimeError without grid
|
|
||||||
try:
|
|
||||||
_ = entity.x
|
|
||||||
errors.append("entity.x should raise RuntimeError without grid")
|
|
||||||
except RuntimeError as e:
|
|
||||||
if "not attached to a Grid" not in str(e):
|
|
||||||
errors.append(f"Wrong error message for x: {e}")
|
|
||||||
|
|
||||||
# Setting pos should raise RuntimeError without grid
|
|
||||||
try:
|
|
||||||
entity.pos = mcrfpy.Vector(100, 100)
|
|
||||||
errors.append("setting entity.pos should raise RuntimeError without grid")
|
|
||||||
except RuntimeError as e:
|
|
||||||
if "not attached to a Grid" not in str(e):
|
|
||||||
errors.append(f"Wrong error message for setting pos: {e}")
|
|
||||||
|
|
||||||
return errors
|
|
||||||
|
|
||||||
|
|
||||||
def test_animation_properties():
|
|
||||||
"""Test that animation properties work correctly."""
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
|
||||||
grid = mcrfpy.Grid(grid_size=(10, 10), texture=texture, pos=(0, 0), size=(160, 160))
|
|
||||||
entity = mcrfpy.Entity(grid_pos=(0, 0), texture=texture, grid=grid)
|
|
||||||
|
|
||||||
# Test draw_x/draw_y animation properties exist
|
|
||||||
try:
|
|
||||||
# hasProperty should accept draw_x and draw_y
|
|
||||||
# We can't call hasProperty directly, but we can try to animate
|
|
||||||
# and check if it raises ValueError for invalid property
|
|
||||||
pass # Animation tested implicitly through animate() error handling
|
|
||||||
except Exception as e:
|
|
||||||
errors.append(f"Animation property test failed: {e}")
|
|
||||||
|
|
||||||
return errors
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("Testing issue #176: Entity position naming consistency")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
all_errors = []
|
|
||||||
|
|
||||||
# Test 1: Entity with grid
|
|
||||||
print("\n1. Testing entity positions with grid attachment...")
|
|
||||||
errors = test_entity_positions()
|
|
||||||
if errors:
|
|
||||||
for e in errors:
|
|
||||||
print(f" FAIL: {e}")
|
|
||||||
all_errors.extend(errors)
|
|
||||||
else:
|
|
||||||
print(" PASS")
|
|
||||||
|
|
||||||
# Test 2: Entity without grid
|
|
||||||
print("\n2. Testing entity positions without grid...")
|
|
||||||
errors = test_entity_without_grid()
|
|
||||||
if errors:
|
|
||||||
for e in errors:
|
|
||||||
print(f" FAIL: {e}")
|
|
||||||
all_errors.extend(errors)
|
|
||||||
else:
|
|
||||||
print(" PASS")
|
|
||||||
|
|
||||||
# Test 3: Animation properties
|
|
||||||
print("\n3. Testing animation properties...")
|
|
||||||
errors = test_animation_properties()
|
|
||||||
if errors:
|
|
||||||
for e in errors:
|
|
||||||
print(f" FAIL: {e}")
|
|
||||||
all_errors.extend(errors)
|
|
||||||
else:
|
|
||||||
print(" PASS")
|
|
||||||
|
|
||||||
print("\n" + "=" * 60)
|
|
||||||
if all_errors:
|
|
||||||
print(f"FAILED: {len(all_errors)} error(s)")
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
print("All tests passed!")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Test for issue #177: GridPoint.grid_pos property
|
|
||||||
|
|
||||||
Verifies that GridPoint objects have a grid_pos property that returns
|
|
||||||
the (grid_x, grid_y) coordinates as a tuple.
|
|
||||||
"""
|
|
||||||
import mcrfpy
|
|
||||||
import sys
|
|
||||||
|
|
||||||
print("Starting test...")
|
|
||||||
|
|
||||||
# Create a simple grid without texture (should work in headless mode)
|
|
||||||
grid = mcrfpy.Grid(grid_x=10, grid_y=8)
|
|
||||||
print(f"Created grid: {grid}")
|
|
||||||
|
|
||||||
# Test various grid positions
|
|
||||||
test_cases = [
|
|
||||||
(0, 0),
|
|
||||||
(5, 3),
|
|
||||||
(9, 7),
|
|
||||||
(0, 7),
|
|
||||||
(9, 0),
|
|
||||||
]
|
|
||||||
|
|
||||||
all_passed = True
|
|
||||||
for x, y in test_cases:
|
|
||||||
point = grid.at(x, y)
|
|
||||||
print(f"Got point at ({x}, {y}): {point}")
|
|
||||||
|
|
||||||
# Check that grid_pos property exists and returns correct value
|
|
||||||
if not hasattr(point, 'grid_pos'):
|
|
||||||
print(f"FAIL: GridPoint at ({x}, {y}) has no 'grid_pos' attribute")
|
|
||||||
all_passed = False
|
|
||||||
continue
|
|
||||||
|
|
||||||
grid_pos = point.grid_pos
|
|
||||||
|
|
||||||
# Verify it's a tuple
|
|
||||||
if not isinstance(grid_pos, tuple):
|
|
||||||
print(f"FAIL: grid_pos is {type(grid_pos).__name__}, expected tuple")
|
|
||||||
all_passed = False
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Verify it has correct length
|
|
||||||
if len(grid_pos) != 2:
|
|
||||||
print(f"FAIL: grid_pos has length {len(grid_pos)}, expected 2")
|
|
||||||
all_passed = False
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Verify correct values
|
|
||||||
if grid_pos != (x, y):
|
|
||||||
print(f"FAIL: grid_pos = {grid_pos}, expected ({x}, {y})")
|
|
||||||
all_passed = False
|
|
||||||
continue
|
|
||||||
|
|
||||||
print(f"OK: GridPoint at ({x}, {y}) has grid_pos = {grid_pos}")
|
|
||||||
|
|
||||||
# Test that grid_pos is read-only (should raise AttributeError)
|
|
||||||
point = grid.at(2, 3)
|
|
||||||
try:
|
|
||||||
point.grid_pos = (5, 5)
|
|
||||||
print("FAIL: grid_pos should be read-only but allowed assignment")
|
|
||||||
all_passed = False
|
|
||||||
except AttributeError:
|
|
||||||
print("OK: grid_pos is read-only (raises AttributeError on assignment)")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"FAIL: Unexpected exception on assignment: {type(e).__name__}: {e}")
|
|
||||||
all_passed = False
|
|
||||||
|
|
||||||
# Verify the repr includes the coordinates
|
|
||||||
point = grid.at(4, 6)
|
|
||||||
repr_str = repr(point)
|
|
||||||
if "(4, 6)" in repr_str:
|
|
||||||
print(f"OK: repr includes coordinates: {repr_str}")
|
|
||||||
else:
|
|
||||||
print(f"Note: repr format: {repr_str}")
|
|
||||||
|
|
||||||
if all_passed:
|
|
||||||
print("PASS")
|
|
||||||
sys.exit(0)
|
|
||||||
else:
|
|
||||||
print("FAIL")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
@ -1,113 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Test for issues #179 and #181: Grid attributes return Vectors and grid_x/grid_y renamed to grid_w/grid_h"""
|
|
||||||
|
|
||||||
import mcrfpy
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def test_grid_vectors():
|
|
||||||
"""Test that Grid attributes return Vector objects instead of tuples."""
|
|
||||||
print("Testing issue #179: Grid attributes should return Vectors...")
|
|
||||||
|
|
||||||
# Create a Grid for testing
|
|
||||||
grid = mcrfpy.Grid(pos=(100, 150), size=(400, 300), grid_size=(20, 15))
|
|
||||||
|
|
||||||
# Test grid.size returns a Vector
|
|
||||||
size = grid.size
|
|
||||||
print(f" grid.size = {size}")
|
|
||||||
assert hasattr(size, 'x'), f"grid.size should have .x attribute, got {type(size)}"
|
|
||||||
assert hasattr(size, 'y'), f"grid.size should have .y attribute, got {type(size)}"
|
|
||||||
assert size.x == 400.0, f"grid.size.x should be 400.0, got {size.x}"
|
|
||||||
assert size.y == 300.0, f"grid.size.y should be 300.0, got {size.y}"
|
|
||||||
print(" PASS: grid.size returns Vector")
|
|
||||||
|
|
||||||
# Test grid.grid_size returns a Vector
|
|
||||||
grid_size = grid.grid_size
|
|
||||||
print(f" grid.grid_size = {grid_size}")
|
|
||||||
assert hasattr(grid_size, 'x'), f"grid.grid_size should have .x attribute, got {type(grid_size)}"
|
|
||||||
assert hasattr(grid_size, 'y'), f"grid.grid_size should have .y attribute, got {type(grid_size)}"
|
|
||||||
assert grid_size.x == 20.0, f"grid.grid_size.x should be 20.0, got {grid_size.x}"
|
|
||||||
assert grid_size.y == 15.0, f"grid.grid_size.y should be 15.0, got {grid_size.y}"
|
|
||||||
print(" PASS: grid.grid_size returns Vector")
|
|
||||||
|
|
||||||
# Test grid.center returns a Vector
|
|
||||||
grid.center = (50.0, 75.0) # Set center first
|
|
||||||
center = grid.center
|
|
||||||
print(f" grid.center = {center}")
|
|
||||||
assert hasattr(center, 'x'), f"grid.center should have .x attribute, got {type(center)}"
|
|
||||||
assert hasattr(center, 'y'), f"grid.center should have .y attribute, got {type(center)}"
|
|
||||||
assert center.x == 50.0, f"grid.center.x should be 50.0, got {center.x}"
|
|
||||||
assert center.y == 75.0, f"grid.center.y should be 75.0, got {center.y}"
|
|
||||||
print(" PASS: grid.center returns Vector")
|
|
||||||
|
|
||||||
# Test grid.position returns a Vector
|
|
||||||
position = grid.position
|
|
||||||
print(f" grid.position = {position}")
|
|
||||||
assert hasattr(position, 'x'), f"grid.position should have .x attribute, got {type(position)}"
|
|
||||||
assert hasattr(position, 'y'), f"grid.position should have .y attribute, got {type(position)}"
|
|
||||||
assert position.x == 100.0, f"grid.position.x should be 100.0, got {position.x}"
|
|
||||||
assert position.y == 150.0, f"grid.position.y should be 150.0, got {position.y}"
|
|
||||||
print(" PASS: grid.position returns Vector")
|
|
||||||
|
|
||||||
print("Issue #179 tests PASSED!")
|
|
||||||
|
|
||||||
|
|
||||||
def test_grid_w_h():
|
|
||||||
"""Test that grid_w and grid_h exist and grid_x/grid_y do not."""
|
|
||||||
print("\nTesting issue #181: grid_x/grid_y renamed to grid_w/grid_h...")
|
|
||||||
|
|
||||||
grid = mcrfpy.Grid(grid_size=(25, 18))
|
|
||||||
|
|
||||||
# Test grid_w and grid_h exist and return correct values
|
|
||||||
grid_w = grid.grid_w
|
|
||||||
grid_h = grid.grid_h
|
|
||||||
print(f" grid.grid_w = {grid_w}")
|
|
||||||
print(f" grid.grid_h = {grid_h}")
|
|
||||||
assert grid_w == 25, f"grid.grid_w should be 25, got {grid_w}"
|
|
||||||
assert grid_h == 18, f"grid.grid_h should be 18, got {grid_h}"
|
|
||||||
print(" PASS: grid.grid_w and grid.grid_h exist and return correct values")
|
|
||||||
|
|
||||||
# Test grid_x and grid_y do NOT exist (AttributeError expected)
|
|
||||||
try:
|
|
||||||
_ = grid.grid_x
|
|
||||||
print(" FAIL: grid.grid_x should not exist but it does!")
|
|
||||||
sys.exit(1)
|
|
||||||
except AttributeError:
|
|
||||||
print(" PASS: grid.grid_x correctly raises AttributeError")
|
|
||||||
|
|
||||||
try:
|
|
||||||
_ = grid.grid_y
|
|
||||||
print(" FAIL: grid.grid_y should not exist but it does!")
|
|
||||||
sys.exit(1)
|
|
||||||
except AttributeError:
|
|
||||||
print(" PASS: grid.grid_y correctly raises AttributeError")
|
|
||||||
|
|
||||||
print("Issue #181 tests PASSED!")
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Run all tests."""
|
|
||||||
print("=" * 60)
|
|
||||||
print("Testing Grid Vector attributes and grid_w/grid_h rename")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
try:
|
|
||||||
test_grid_vectors()
|
|
||||||
test_grid_w_h()
|
|
||||||
|
|
||||||
print("\n" + "=" * 60)
|
|
||||||
print("ALL TESTS PASSED!")
|
|
||||||
print("=" * 60)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
||||||
except AssertionError as e:
|
|
||||||
print(f"\nFAIL: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"\nERROR: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
"""Test for issue #180: Timers without a user-stored reference should still fire.
|
|
||||||
|
|
||||||
This test verifies that timers continue running even when the Python object
|
|
||||||
goes out of scope, and that they can be accessed via mcrfpy.timers.
|
|
||||||
"""
|
|
||||||
import mcrfpy
|
|
||||||
import gc
|
|
||||||
import sys
|
|
||||||
|
|
||||||
# Track timer fires
|
|
||||||
fire_count = 0
|
|
||||||
|
|
||||||
def on_timer(timer, runtime):
|
|
||||||
"""Timer callback that increments fire count."""
|
|
||||||
global fire_count
|
|
||||||
fire_count += 1
|
|
||||||
print(f"Timer fired! count={fire_count}, runtime={runtime}")
|
|
||||||
|
|
||||||
def create_orphan_timer():
|
|
||||||
"""Create a timer without storing a reference."""
|
|
||||||
# This timer should keep running even though we don't store the reference
|
|
||||||
mcrfpy.Timer("orphan_timer", on_timer, 50) # 50ms interval
|
|
||||||
print("Created orphan timer (no reference stored)")
|
|
||||||
|
|
||||||
# Set up test scene
|
|
||||||
scene = mcrfpy.Scene("test")
|
|
||||||
mcrfpy.current_scene = scene
|
|
||||||
|
|
||||||
# Create the orphan timer (no reference stored)
|
|
||||||
create_orphan_timer()
|
|
||||||
|
|
||||||
# Force garbage collection to ensure the Python wrapper is collected
|
|
||||||
gc.collect()
|
|
||||||
print("Forced garbage collection")
|
|
||||||
|
|
||||||
# Check timers immediately after GC
|
|
||||||
timers = mcrfpy.timers
|
|
||||||
print(f"Timers after GC: {len(timers)}")
|
|
||||||
for t in timers:
|
|
||||||
print(f" - {t.name}")
|
|
||||||
|
|
||||||
# In headless mode, use step() to advance time and process timers
|
|
||||||
print("\nAdvancing time with step()...")
|
|
||||||
for i in range(6):
|
|
||||||
mcrfpy.step(50) # Advance 50ms per step = 300ms total
|
|
||||||
print(f" Step {i+1}: fire_count={fire_count}")
|
|
||||||
|
|
||||||
# Now check results
|
|
||||||
print(f"\n=== Final Results ===")
|
|
||||||
print(f"Fire count: {fire_count}")
|
|
||||||
|
|
||||||
# Check that we can still find the timer in mcrfpy.timers
|
|
||||||
timers = mcrfpy.timers
|
|
||||||
print(f"Number of timers: {len(timers)}")
|
|
||||||
|
|
||||||
orphan_found = False
|
|
||||||
for t in timers:
|
|
||||||
print(f" - Timer: name={t.name}, interval={t.interval}")
|
|
||||||
if t.name == "orphan_timer":
|
|
||||||
orphan_found = True
|
|
||||||
# Stop it now that we've verified it exists
|
|
||||||
t.stop()
|
|
||||||
print(f" -> Stopped orphan timer")
|
|
||||||
|
|
||||||
# Verify the orphan timer was found and fired
|
|
||||||
if not orphan_found:
|
|
||||||
print("FAIL: Orphan timer not found in mcrfpy.timers")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if fire_count == 0:
|
|
||||||
print("FAIL: Orphan timer never fired")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print(f"\nPASS: Orphan timer fired {fire_count} times and was accessible via mcrfpy.timers")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
@ -1,83 +0,0 @@
|
||||||
"""Test for issue #180: Stopped timers with user reference should stay alive.
|
|
||||||
|
|
||||||
This test verifies that:
|
|
||||||
1. A stopped timer with a user reference remains accessible
|
|
||||||
2. A stopped timer can be restarted
|
|
||||||
3. A stopped timer without references is properly cleaned up
|
|
||||||
"""
|
|
||||||
import mcrfpy
|
|
||||||
import gc
|
|
||||||
import sys
|
|
||||||
|
|
||||||
fire_count = 0
|
|
||||||
|
|
||||||
def on_timer(timer, runtime):
|
|
||||||
"""Timer callback."""
|
|
||||||
global fire_count
|
|
||||||
fire_count += 1
|
|
||||||
print(f"Timer fired! count={fire_count}")
|
|
||||||
|
|
||||||
# Set up test scene
|
|
||||||
scene = mcrfpy.Scene("test")
|
|
||||||
mcrfpy.current_scene = scene
|
|
||||||
|
|
||||||
print("=== Test 1: Stopped timer with reference stays alive ===")
|
|
||||||
|
|
||||||
# Create timer and keep reference
|
|
||||||
my_timer = mcrfpy.Timer("kept_timer", on_timer, 50)
|
|
||||||
print(f"Created timer: {my_timer.name}, active={my_timer.active}")
|
|
||||||
|
|
||||||
# Advance time to fire once
|
|
||||||
mcrfpy.step(60)
|
|
||||||
print(f"After step: fire_count={fire_count}")
|
|
||||||
|
|
||||||
# Stop the timer
|
|
||||||
my_timer.stop()
|
|
||||||
print(f"Stopped timer: active={my_timer.active}, stopped={my_timer.stopped}")
|
|
||||||
|
|
||||||
# Timer should NOT be in mcrfpy.timers (it's stopped)
|
|
||||||
timers = mcrfpy.timers
|
|
||||||
timer_names = [t.name for t in timers]
|
|
||||||
print(f"Timers after stop: {timer_names}")
|
|
||||||
|
|
||||||
if "kept_timer" in timer_names:
|
|
||||||
print("Note: Stopped timer still in mcrfpy.timers (expected - it was accessed)")
|
|
||||||
|
|
||||||
# But we should still have our reference and can restart
|
|
||||||
print(f"Our reference still valid: {my_timer.name}")
|
|
||||||
my_timer.restart()
|
|
||||||
print(f"Restarted timer: active={my_timer.active}")
|
|
||||||
|
|
||||||
# Advance time and verify it fires again
|
|
||||||
old_count = fire_count
|
|
||||||
mcrfpy.step(60)
|
|
||||||
print(f"After restart step: fire_count={fire_count}")
|
|
||||||
|
|
||||||
if fire_count <= old_count:
|
|
||||||
print("FAIL: Restarted timer didn't fire")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print("\n=== Test 2: Stopped timer without reference is cleaned up ===")
|
|
||||||
|
|
||||||
# Create another timer, stop it, and lose the reference
|
|
||||||
temp_timer = mcrfpy.Timer("temp_timer", on_timer, 50)
|
|
||||||
temp_timer.stop()
|
|
||||||
print(f"Created and stopped temp_timer")
|
|
||||||
|
|
||||||
# Clear reference and GC
|
|
||||||
del temp_timer
|
|
||||||
gc.collect()
|
|
||||||
|
|
||||||
# The timer should be gone (stopped + no reference = GC'd)
|
|
||||||
timers = mcrfpy.timers
|
|
||||||
timer_names = [t.name for t in timers]
|
|
||||||
print(f"Timers after del+GC: {timer_names}")
|
|
||||||
|
|
||||||
# Note: temp_timer might still be there if it was retrieved before - that's OK
|
|
||||||
# The key test is that it WON'T fire since it's stopped
|
|
||||||
|
|
||||||
# Clean up
|
|
||||||
my_timer.stop()
|
|
||||||
|
|
||||||
print("\nPASS: Timer lifecycle works correctly")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
@ -1,97 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Test for issue #182: Caption size, w, and h read-only properties.
|
|
||||||
|
|
||||||
This test verifies that:
|
|
||||||
1. Caption.size returns a Vector with the text dimensions
|
|
||||||
2. Caption.w and Caption.h return float values matching size.x and size.y
|
|
||||||
3. All three properties are read-only (setting raises AttributeError)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import mcrfpy
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def test_caption_size_properties():
|
|
||||||
"""Test Caption size, w, and h properties."""
|
|
||||||
|
|
||||||
# Create a caption with some text
|
|
||||||
caption = mcrfpy.Caption(text="Hello World", pos=(100, 100), font_size=24)
|
|
||||||
|
|
||||||
# Test 1: size property exists and returns a Vector
|
|
||||||
size = caption.size
|
|
||||||
print(f"caption.size = {size}")
|
|
||||||
|
|
||||||
# Verify it's a Vector
|
|
||||||
assert hasattr(size, 'x') and hasattr(size, 'y'), "size should be a Vector with x and y attributes"
|
|
||||||
print(f" size.x = {size.x}, size.y = {size.y}")
|
|
||||||
|
|
||||||
# Test 2: size values are positive (text has non-zero dimensions)
|
|
||||||
assert size.x > 0, f"size.x should be positive, got {size.x}"
|
|
||||||
assert size.y > 0, f"size.y should be positive, got {size.y}"
|
|
||||||
print(" size values are positive: PASS")
|
|
||||||
|
|
||||||
# Test 3: w and h properties exist and return floats
|
|
||||||
w = caption.w
|
|
||||||
h = caption.h
|
|
||||||
print(f"caption.w = {w}, caption.h = {h}")
|
|
||||||
|
|
||||||
assert isinstance(w, float), f"w should be a float, got {type(w)}"
|
|
||||||
assert isinstance(h, float), f"h should be a float, got {type(h)}"
|
|
||||||
print(" w and h are floats: PASS")
|
|
||||||
|
|
||||||
# Test 4: w and h match size.x and size.y
|
|
||||||
assert abs(w - size.x) < 0.001, f"w ({w}) should match size.x ({size.x})"
|
|
||||||
assert abs(h - size.y) < 0.001, f"h ({h}) should match size.y ({size.y})"
|
|
||||||
print(" w/h match size.x/size.y: PASS")
|
|
||||||
|
|
||||||
# Test 5: size is read-only
|
|
||||||
try:
|
|
||||||
caption.size = mcrfpy.Vector(50, 50)
|
|
||||||
print(" ERROR: setting size should raise AttributeError")
|
|
||||||
sys.exit(1)
|
|
||||||
except AttributeError:
|
|
||||||
print(" size is read-only: PASS")
|
|
||||||
|
|
||||||
# Test 6: w is read-only
|
|
||||||
try:
|
|
||||||
caption.w = 100.0
|
|
||||||
print(" ERROR: setting w should raise AttributeError")
|
|
||||||
sys.exit(1)
|
|
||||||
except AttributeError:
|
|
||||||
print(" w is read-only: PASS")
|
|
||||||
|
|
||||||
# Test 7: h is read-only
|
|
||||||
try:
|
|
||||||
caption.h = 50.0
|
|
||||||
print(" ERROR: setting h should raise AttributeError")
|
|
||||||
sys.exit(1)
|
|
||||||
except AttributeError:
|
|
||||||
print(" h is read-only: PASS")
|
|
||||||
|
|
||||||
# Test 8: Changing text changes the size
|
|
||||||
old_w = caption.w
|
|
||||||
caption.text = "Hello World! This is a much longer text."
|
|
||||||
new_w = caption.w
|
|
||||||
print(f"After changing text: old_w = {old_w}, new_w = {new_w}")
|
|
||||||
assert new_w > old_w, f"Longer text should have larger width: {new_w} > {old_w}"
|
|
||||||
print(" Changing text updates size: PASS")
|
|
||||||
|
|
||||||
# Test 9: Empty caption has zero or near-zero size
|
|
||||||
empty_caption = mcrfpy.Caption(text="", pos=(0, 0))
|
|
||||||
print(f"Empty caption: w={empty_caption.w}, h={empty_caption.h}")
|
|
||||||
# Note: Even empty text might have some height due to font metrics
|
|
||||||
assert empty_caption.w == 0 or empty_caption.w < 1, f"Empty text should have ~zero width, got {empty_caption.w}"
|
|
||||||
print(" Empty caption has minimal size: PASS")
|
|
||||||
|
|
||||||
print("\nAll tests passed!")
|
|
||||||
return True
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
test_caption_size_properties()
|
|
||||||
print("PASS")
|
|
||||||
sys.exit(0)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"FAIL: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
@ -1,95 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Test for issues #184 (mcrfpy.window singleton) and #189 (hide non-instantiable classes)"""
|
|
||||||
|
|
||||||
import mcrfpy
|
|
||||||
import sys
|
|
||||||
|
|
||||||
errors = []
|
|
||||||
|
|
||||||
# Test #184: mcrfpy.window singleton exists
|
|
||||||
print("Testing #184: mcrfpy.window singleton...")
|
|
||||||
|
|
||||||
try:
|
|
||||||
window = mcrfpy.window
|
|
||||||
print(f" mcrfpy.window exists: {window}")
|
|
||||||
except AttributeError as e:
|
|
||||||
errors.append(f"#184 FAIL: mcrfpy.window not found: {e}")
|
|
||||||
|
|
||||||
# Check window has expected attributes
|
|
||||||
if hasattr(mcrfpy, 'window'):
|
|
||||||
window = mcrfpy.window
|
|
||||||
|
|
||||||
# Check for expected properties
|
|
||||||
expected_attrs = ['resolution', 'fullscreen', 'vsync', 'title', 'visible']
|
|
||||||
for attr in expected_attrs:
|
|
||||||
if hasattr(window, attr):
|
|
||||||
print(f" window.{attr} = {getattr(window, attr)}")
|
|
||||||
else:
|
|
||||||
errors.append(f"#184 FAIL: mcrfpy.window missing attribute '{attr}'")
|
|
||||||
|
|
||||||
# Check that Window TYPE still exists (for isinstance checks)
|
|
||||||
if hasattr(mcrfpy, 'Window'):
|
|
||||||
print(f" mcrfpy.Window type exists: {mcrfpy.Window}")
|
|
||||||
# Verify window is an instance of Window
|
|
||||||
if isinstance(mcrfpy.window, mcrfpy.Window):
|
|
||||||
print(" isinstance(mcrfpy.window, mcrfpy.Window) = True")
|
|
||||||
else:
|
|
||||||
errors.append("#184 FAIL: mcrfpy.window is not an instance of mcrfpy.Window")
|
|
||||||
else:
|
|
||||||
errors.append("#184 FAIL: mcrfpy.Window type not found")
|
|
||||||
|
|
||||||
# Test #189: Hidden classes should NOT be accessible
|
|
||||||
print("\nTesting #189: Hidden classes should raise AttributeError...")
|
|
||||||
|
|
||||||
hidden_classes = [
|
|
||||||
'EntityCollection',
|
|
||||||
'UICollection',
|
|
||||||
'UICollectionIter',
|
|
||||||
'UIEntityCollectionIter',
|
|
||||||
'GridPoint',
|
|
||||||
'GridPointState'
|
|
||||||
]
|
|
||||||
|
|
||||||
for class_name in hidden_classes:
|
|
||||||
try:
|
|
||||||
cls = getattr(mcrfpy, class_name)
|
|
||||||
errors.append(f"#189 FAIL: mcrfpy.{class_name} should be hidden but is accessible: {cls}")
|
|
||||||
except AttributeError:
|
|
||||||
print(f" mcrfpy.{class_name} correctly raises AttributeError")
|
|
||||||
|
|
||||||
# Test that hidden classes still WORK (just not exported)
|
|
||||||
print("\nTesting that internal types still function correctly...")
|
|
||||||
|
|
||||||
# Create a scene to test UICollection
|
|
||||||
scene = mcrfpy.Scene("test_scene")
|
|
||||||
scene.activate()
|
|
||||||
|
|
||||||
# Test UICollection via .children
|
|
||||||
print(" Getting scene.children...")
|
|
||||||
children = scene.children
|
|
||||||
print(f" scene.children works: {children}")
|
|
||||||
children_type = type(children)
|
|
||||||
print(f" type(scene.children) = {children_type}")
|
|
||||||
if 'UICollection' in str(children_type):
|
|
||||||
print(" UICollection type works correctly (internal use)")
|
|
||||||
else:
|
|
||||||
errors.append(f"#189 FAIL: scene.children returned unexpected type: {children_type}")
|
|
||||||
|
|
||||||
# Test that Drawable IS still exported (should NOT be hidden)
|
|
||||||
print("\nTesting that Drawable is still exported...")
|
|
||||||
if hasattr(mcrfpy, 'Drawable'):
|
|
||||||
print(f" mcrfpy.Drawable exists: {mcrfpy.Drawable}")
|
|
||||||
else:
|
|
||||||
errors.append("FAIL: mcrfpy.Drawable should still be exported but is missing")
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
print("\n" + "="*60)
|
|
||||||
if errors:
|
|
||||||
print("FAILURES:")
|
|
||||||
for e in errors:
|
|
||||||
print(f" {e}")
|
|
||||||
print(f"\nFAIL: {len(errors)} error(s)")
|
|
||||||
sys.exit(1)
|
|
||||||
else:
|
|
||||||
print("PASS: All tests passed for issues #184 and #189")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
@ -1,122 +0,0 @@
|
||||||
"""Test for issues #185 and #188: bounds handling changes.
|
|
||||||
|
|
||||||
Issue #185: Remove .get_bounds() method - redundant with .bounds property
|
|
||||||
Issue #188: Change .bounds and .global_bounds to return (pos, size) as pair of Vectors
|
|
||||||
"""
|
|
||||||
import mcrfpy
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def test_bounds():
|
|
||||||
"""Test that bounds returns (Vector, Vector) tuple."""
|
|
||||||
print("Testing bounds format...")
|
|
||||||
|
|
||||||
# Create a Frame with known position and size
|
|
||||||
frame = mcrfpy.Frame(pos=(100, 200), size=(300, 400))
|
|
||||||
bounds = frame.bounds
|
|
||||||
|
|
||||||
# Should be a tuple of 2 elements
|
|
||||||
assert isinstance(bounds, tuple), f"Expected tuple, got {type(bounds)}"
|
|
||||||
assert len(bounds) == 2, f"Expected 2 elements, got {len(bounds)}"
|
|
||||||
|
|
||||||
pos, size = bounds
|
|
||||||
|
|
||||||
# Check that pos is a Vector with correct values
|
|
||||||
assert isinstance(pos, mcrfpy.Vector), f"Expected Vector for pos, got {type(pos)}"
|
|
||||||
assert pos.x == 100, f"Expected pos.x=100, got {pos.x}"
|
|
||||||
assert pos.y == 200, f"Expected pos.y=200, got {pos.y}"
|
|
||||||
|
|
||||||
# Check that size is a Vector with correct values
|
|
||||||
assert isinstance(size, mcrfpy.Vector), f"Expected Vector for size, got {type(size)}"
|
|
||||||
assert size.x == 300, f"Expected size.x=300, got {size.x}"
|
|
||||||
assert size.y == 400, f"Expected size.y=400, got {size.y}"
|
|
||||||
|
|
||||||
print(" Frame bounds: PASS")
|
|
||||||
|
|
||||||
def test_global_bounds():
|
|
||||||
"""Test that global_bounds returns (Vector, Vector) tuple."""
|
|
||||||
print("Testing global_bounds format...")
|
|
||||||
|
|
||||||
frame = mcrfpy.Frame(pos=(50, 75), size=(150, 250))
|
|
||||||
global_bounds = frame.global_bounds
|
|
||||||
|
|
||||||
# Should be a tuple of 2 elements
|
|
||||||
assert isinstance(global_bounds, tuple), f"Expected tuple, got {type(global_bounds)}"
|
|
||||||
assert len(global_bounds) == 2, f"Expected 2 elements, got {len(global_bounds)}"
|
|
||||||
|
|
||||||
pos, size = global_bounds
|
|
||||||
assert isinstance(pos, mcrfpy.Vector), f"Expected Vector for pos, got {type(pos)}"
|
|
||||||
assert isinstance(size, mcrfpy.Vector), f"Expected Vector for size, got {type(size)}"
|
|
||||||
|
|
||||||
print(" Frame global_bounds: PASS")
|
|
||||||
|
|
||||||
def test_get_bounds_removed():
|
|
||||||
"""Test that get_bounds() method has been removed."""
|
|
||||||
print("Testing get_bounds removal...")
|
|
||||||
|
|
||||||
frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
|
|
||||||
assert not hasattr(frame, 'get_bounds'), "get_bounds method should be removed"
|
|
||||||
|
|
||||||
print(" get_bounds removed: PASS")
|
|
||||||
|
|
||||||
def test_caption_bounds():
|
|
||||||
"""Test bounds on Caption."""
|
|
||||||
print("Testing Caption bounds...")
|
|
||||||
|
|
||||||
caption = mcrfpy.Caption(text="Test", pos=(25, 50))
|
|
||||||
bounds = caption.bounds
|
|
||||||
|
|
||||||
assert isinstance(bounds, tuple), f"Expected tuple, got {type(bounds)}"
|
|
||||||
assert len(bounds) == 2, f"Expected 2 elements, got {len(bounds)}"
|
|
||||||
|
|
||||||
pos, size = bounds
|
|
||||||
assert isinstance(pos, mcrfpy.Vector), f"Expected Vector for pos, got {type(pos)}"
|
|
||||||
assert isinstance(size, mcrfpy.Vector), f"Expected Vector for size, got {type(size)}"
|
|
||||||
|
|
||||||
# Caption should not have get_bounds
|
|
||||||
assert not hasattr(caption, 'get_bounds'), "get_bounds method should be removed from Caption"
|
|
||||||
|
|
||||||
print(" Caption bounds: PASS")
|
|
||||||
|
|
||||||
def test_sprite_bounds():
|
|
||||||
"""Test bounds on Sprite."""
|
|
||||||
print("Testing Sprite bounds...")
|
|
||||||
|
|
||||||
sprite = mcrfpy.Sprite(pos=(10, 20))
|
|
||||||
bounds = sprite.bounds
|
|
||||||
|
|
||||||
assert isinstance(bounds, tuple), f"Expected tuple, got {type(bounds)}"
|
|
||||||
assert len(bounds) == 2, f"Expected 2 elements, got {len(bounds)}"
|
|
||||||
|
|
||||||
pos, size = bounds
|
|
||||||
assert isinstance(pos, mcrfpy.Vector), f"Expected Vector for pos, got {type(pos)}"
|
|
||||||
assert isinstance(size, mcrfpy.Vector), f"Expected Vector for size, got {type(size)}"
|
|
||||||
|
|
||||||
# Sprite should not have get_bounds
|
|
||||||
assert not hasattr(sprite, 'get_bounds'), "get_bounds method should be removed from Sprite"
|
|
||||||
|
|
||||||
print(" Sprite bounds: PASS")
|
|
||||||
|
|
||||||
# Run tests
|
|
||||||
print("=" * 60)
|
|
||||||
print("Testing Issues #185 and #188: Bounds Handling")
|
|
||||||
print("=" * 60)
|
|
||||||
|
|
||||||
try:
|
|
||||||
test_bounds()
|
|
||||||
test_global_bounds()
|
|
||||||
test_get_bounds_removed()
|
|
||||||
test_caption_bounds()
|
|
||||||
test_sprite_bounds()
|
|
||||||
|
|
||||||
print("=" * 60)
|
|
||||||
print("All tests PASSED!")
|
|
||||||
print("=" * 60)
|
|
||||||
sys.exit(0)
|
|
||||||
except AssertionError as e:
|
|
||||||
print(f"FAILED: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"ERROR: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
import mcrfpy
|
|
||||||
scene = mcrfpy.Scene("test")
|
|
||||||
scene.activate()
|
|
||||||
f1 = mcrfpy.Frame((10,10), (100,100), fill_color = (255, 0, 0, 64))
|
|
||||||
f2 = mcrfpy.Frame((200,10), (100,100), fill_color = (0, 255, 0, 64))
|
|
||||||
f_child = mcrfpy.Frame((25,25), (50,50), fill_color = (0, 0, 255, 64))
|
|
||||||
|
|
||||||
scene.children.append(f1)
|
|
||||||
scene.children.append(f2)
|
|
||||||
f1.children.append(f_child)
|
|
||||||
f_child.parent = f2
|
|
||||||
|
|
||||||
print(f1.children)
|
|
||||||
print(f2.children)
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Minimal test to check if module works"""
|
|
||||||
import sys
|
|
||||||
import mcrfpy
|
|
||||||
|
|
||||||
print("Module loaded successfully")
|
|
||||||
print(f"mcrfpy has window: {hasattr(mcrfpy, 'window')}")
|
|
||||||
print(f"mcrfpy.Frame exists: {hasattr(mcrfpy, 'Frame')}")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
@ -1,285 +0,0 @@
|
||||||
"""Regression test for issue #183: .parent quirks
|
|
||||||
|
|
||||||
Tests:
|
|
||||||
1. Newly-created drawable has parent None
|
|
||||||
2. Setting same parent twice doesn't duplicate children
|
|
||||||
3. Setting parent removes from old collection
|
|
||||||
4. Setting parent to None removes from parent collection
|
|
||||||
5. Grid parent handling works correctly
|
|
||||||
6. Moving from Frame to Grid works
|
|
||||||
7. Scene-level elements return Scene object from .parent
|
|
||||||
8. Setting parent to None removes from scene's children
|
|
||||||
9. Moving from scene to Frame works
|
|
||||||
10. Moving from Frame to scene works (via scene.children.append)
|
|
||||||
11. Direct .parent = scene assignment works
|
|
||||||
12. .parent = scene removes from Frame and adds to Scene
|
|
||||||
|
|
||||||
Note: Parent comparison uses repr() or child containment checking
|
|
||||||
since child.parent returns a new Python wrapper object each time.
|
|
||||||
"""
|
|
||||||
import mcrfpy
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def same_drawable(a, b):
|
|
||||||
"""Check if two drawable wrappers point to the same C++ object.
|
|
||||||
|
|
||||||
Since get_parent creates new Python wrappers, we can't use == or is.
|
|
||||||
Instead, verify by checking that modifications to one affect the other.
|
|
||||||
"""
|
|
||||||
if a is None or b is None:
|
|
||||||
return a is None and b is None
|
|
||||||
# Modify a property on 'a' and verify it changes on 'b'
|
|
||||||
original_x = b.x
|
|
||||||
test_value = original_x + 12345.0
|
|
||||||
a.x = test_value
|
|
||||||
result = b.x == test_value
|
|
||||||
a.x = original_x # Restore
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def test_new_drawable_parent_none():
|
|
||||||
"""Newly-created drawable has parent None"""
|
|
||||||
frame = mcrfpy.Frame(pos=(0,0), size=(100,100))
|
|
||||||
assert frame.parent is None, f"Expected None, got {frame.parent}"
|
|
||||||
print("PASS: New drawable has parent None")
|
|
||||||
|
|
||||||
|
|
||||||
def test_no_duplicate_on_same_parent():
|
|
||||||
"""Setting same parent twice doesn't duplicate children"""
|
|
||||||
parent = mcrfpy.Frame(pos=(0,0), size=(200,200))
|
|
||||||
child = mcrfpy.Frame(pos=(10,10), size=(50,50))
|
|
||||||
|
|
||||||
# Add child to parent
|
|
||||||
parent.children.append(child)
|
|
||||||
initial_count = len(parent.children)
|
|
||||||
|
|
||||||
# Set same parent again via property
|
|
||||||
child.parent = parent
|
|
||||||
|
|
||||||
# Should not duplicate
|
|
||||||
assert len(parent.children) == initial_count, \
|
|
||||||
f"Expected {initial_count} children, got {len(parent.children)} - duplicate was added!"
|
|
||||||
print("PASS: Setting same parent twice doesn't duplicate")
|
|
||||||
|
|
||||||
|
|
||||||
def test_parent_removes_from_old_collection():
|
|
||||||
"""Setting parent removes from old collection"""
|
|
||||||
parent1 = mcrfpy.Frame(pos=(0,0), size=(200,200))
|
|
||||||
parent2 = mcrfpy.Frame(pos=(100,0), size=(200,200)) # Different x to distinguish
|
|
||||||
child = mcrfpy.Frame(pos=(10,10), size=(50,50))
|
|
||||||
|
|
||||||
# Add to parent1
|
|
||||||
parent1.children.append(child)
|
|
||||||
assert len(parent1.children) == 1, "Child should be in parent1"
|
|
||||||
assert child.parent is not None, "parent should not be None"
|
|
||||||
assert same_drawable(child.parent, parent1), "parent should be parent1"
|
|
||||||
|
|
||||||
# Move to parent2
|
|
||||||
child.parent = parent2
|
|
||||||
|
|
||||||
# Should be removed from parent1 and added to parent2
|
|
||||||
assert len(parent1.children) == 0, f"Expected 0 children in parent1, got {len(parent1.children)}"
|
|
||||||
assert len(parent2.children) == 1, f"Expected 1 child in parent2, got {len(parent2.children)}"
|
|
||||||
assert same_drawable(child.parent, parent2), "parent should be parent2"
|
|
||||||
print("PASS: Setting parent removes from old collection")
|
|
||||||
|
|
||||||
|
|
||||||
def test_parent_none_removes_from_collection():
|
|
||||||
"""Setting parent to None removes from parent's collection"""
|
|
||||||
parent = mcrfpy.Frame(pos=(0,0), size=(200,200))
|
|
||||||
child = mcrfpy.Frame(pos=(10,10), size=(50,50))
|
|
||||||
|
|
||||||
# Add child to parent
|
|
||||||
parent.children.append(child)
|
|
||||||
assert len(parent.children) == 1, "Child should be in parent"
|
|
||||||
|
|
||||||
# Set parent to None
|
|
||||||
child.parent = None
|
|
||||||
|
|
||||||
# Should be removed from parent
|
|
||||||
assert len(parent.children) == 0, f"Expected 0 children, got {len(parent.children)}"
|
|
||||||
assert child.parent is None, "parent should be None"
|
|
||||||
print("PASS: Setting parent to None removes from collection")
|
|
||||||
|
|
||||||
|
|
||||||
def test_grid_parent_handling():
|
|
||||||
"""Grid parent handling works correctly"""
|
|
||||||
grid = mcrfpy.Grid(grid_size=(10,10), pos=(0,0), size=(200,200))
|
|
||||||
child = mcrfpy.Frame(pos=(10,10), size=(50,50))
|
|
||||||
|
|
||||||
# Add child to grid
|
|
||||||
grid.children.append(child)
|
|
||||||
assert len(grid.children) == 1, "Child should be in grid"
|
|
||||||
assert child.parent is not None, "parent should not be None"
|
|
||||||
|
|
||||||
# Set same parent again (should not duplicate)
|
|
||||||
child.parent = grid
|
|
||||||
assert len(grid.children) == 1, f"Expected 1 child, got {len(grid.children)} - duplicate was added!"
|
|
||||||
|
|
||||||
# Move to a frame
|
|
||||||
frame = mcrfpy.Frame(pos=(0,0), size=(200,200))
|
|
||||||
child.parent = frame
|
|
||||||
|
|
||||||
# Should be removed from grid and added to frame
|
|
||||||
assert len(grid.children) == 0, f"Expected 0 children in grid, got {len(grid.children)}"
|
|
||||||
assert len(frame.children) == 1, f"Expected 1 child in frame, got {len(frame.children)}"
|
|
||||||
assert same_drawable(child.parent, frame), "parent should be frame"
|
|
||||||
print("PASS: Grid parent handling works correctly")
|
|
||||||
|
|
||||||
|
|
||||||
def test_move_from_frame_to_grid():
|
|
||||||
"""Moving from Frame parent to Grid parent works"""
|
|
||||||
frame = mcrfpy.Frame(pos=(0,0), size=(200,200))
|
|
||||||
grid = mcrfpy.Grid(grid_size=(10,10), pos=(100,0), size=(200,200)) # Different x
|
|
||||||
child = mcrfpy.Caption(text="Test", pos=(10,10))
|
|
||||||
|
|
||||||
# Add to frame
|
|
||||||
frame.children.append(child)
|
|
||||||
assert len(frame.children) == 1
|
|
||||||
|
|
||||||
# Move to grid
|
|
||||||
child.parent = grid
|
|
||||||
|
|
||||||
assert len(frame.children) == 0, f"Expected 0 children in frame, got {len(frame.children)}"
|
|
||||||
assert len(grid.children) == 1, f"Expected 1 child in grid, got {len(grid.children)}"
|
|
||||||
# Note: Caption doesn't have x property, so just check parent is not None
|
|
||||||
assert child.parent is not None, "parent should not be None"
|
|
||||||
print("PASS: Moving from Frame to Grid works")
|
|
||||||
|
|
||||||
|
|
||||||
def test_scene_parent_returns_scene_object():
|
|
||||||
"""Scene-level elements return Scene object from .parent"""
|
|
||||||
scene = mcrfpy.Scene('test_scene_parent_return')
|
|
||||||
|
|
||||||
child = mcrfpy.Frame(pos=(10,10), size=(50,50))
|
|
||||||
scene.children.append(child)
|
|
||||||
|
|
||||||
# .parent should return a Scene object, not None
|
|
||||||
parent = child.parent
|
|
||||||
assert parent is not None, "parent should not be None for scene-level element"
|
|
||||||
assert type(parent).__name__ == 'Scene', f"Expected Scene, got {type(parent).__name__}"
|
|
||||||
assert parent.name == 'test_scene_parent_return', f"Expected scene name 'test_scene_parent_return', got '{parent.name}'"
|
|
||||||
print("PASS: Scene-level elements return Scene object from .parent")
|
|
||||||
|
|
||||||
|
|
||||||
def test_scene_parent_none_removes():
|
|
||||||
"""Setting parent to None removes from scene's children"""
|
|
||||||
scene = mcrfpy.Scene('test_scene_remove')
|
|
||||||
mcrfpy.current_scene = scene
|
|
||||||
|
|
||||||
child = mcrfpy.Frame(pos=(10,10), size=(50,50))
|
|
||||||
scene.children.append(child)
|
|
||||||
assert len(scene.children) == 1, "Child should be in scene"
|
|
||||||
|
|
||||||
# Set parent to None - should remove from scene
|
|
||||||
child.parent = None
|
|
||||||
|
|
||||||
assert len(scene.children) == 0, f"Expected 0 children in scene, got {len(scene.children)}"
|
|
||||||
assert child.parent is None, "parent should be None"
|
|
||||||
print("PASS: Scene parent=None removes from scene")
|
|
||||||
|
|
||||||
|
|
||||||
def test_scene_to_frame():
|
|
||||||
"""Moving from scene to Frame removes from scene, adds to Frame"""
|
|
||||||
scene = mcrfpy.Scene('test_scene_to_frame')
|
|
||||||
mcrfpy.current_scene = scene
|
|
||||||
|
|
||||||
child = mcrfpy.Frame(pos=(10,10), size=(50,50))
|
|
||||||
scene.children.append(child)
|
|
||||||
assert len(scene.children) == 1
|
|
||||||
|
|
||||||
# Move to a Frame
|
|
||||||
parent_frame = mcrfpy.Frame(pos=(0,0), size=(200,200))
|
|
||||||
child.parent = parent_frame
|
|
||||||
|
|
||||||
assert len(scene.children) == 0, f"Expected 0 children in scene, got {len(scene.children)}"
|
|
||||||
assert len(parent_frame.children) == 1, f"Expected 1 child in frame, got {len(parent_frame.children)}"
|
|
||||||
print("PASS: Scene -> Frame movement works")
|
|
||||||
|
|
||||||
|
|
||||||
def test_frame_to_scene():
|
|
||||||
"""Moving from Frame to scene removes from Frame, adds to scene"""
|
|
||||||
scene = mcrfpy.Scene('test_frame_to_scene')
|
|
||||||
mcrfpy.current_scene = scene
|
|
||||||
|
|
||||||
parent_frame = mcrfpy.Frame(pos=(0,0), size=(200,200))
|
|
||||||
child = mcrfpy.Frame(pos=(10,10), size=(50,50))
|
|
||||||
parent_frame.children.append(child)
|
|
||||||
assert len(parent_frame.children) == 1
|
|
||||||
|
|
||||||
# Move to scene via scene.children.append()
|
|
||||||
scene.children.append(child)
|
|
||||||
|
|
||||||
assert len(parent_frame.children) == 0, f"Expected 0 children in frame, got {len(parent_frame.children)}"
|
|
||||||
assert len(scene.children) == 1, f"Expected 1 child in scene, got {len(scene.children)}"
|
|
||||||
print("PASS: Frame -> Scene movement works")
|
|
||||||
|
|
||||||
|
|
||||||
def test_parent_assign_scene():
|
|
||||||
"""Setting .parent = scene directly adds to scene's children"""
|
|
||||||
scene = mcrfpy.Scene('test_parent_assign_scene')
|
|
||||||
frame = mcrfpy.Frame(pos=(10,10), size=(50,50))
|
|
||||||
|
|
||||||
# Direct assignment: frame.parent = scene
|
|
||||||
frame.parent = scene
|
|
||||||
|
|
||||||
assert len(scene.children) == 1, f"Expected 1 child in scene, got {len(scene.children)}"
|
|
||||||
assert frame.parent is not None, "parent should not be None"
|
|
||||||
assert frame.parent.name == 'test_parent_assign_scene', f"Expected scene name, got '{frame.parent.name}'"
|
|
||||||
print("PASS: Direct .parent = scene assignment works")
|
|
||||||
|
|
||||||
|
|
||||||
def test_parent_assign_scene_from_frame():
|
|
||||||
"""Setting .parent = scene removes from Frame and adds to Scene"""
|
|
||||||
scene = mcrfpy.Scene('test_assign_scene_from_frame')
|
|
||||||
parent_frame = mcrfpy.Frame(pos=(0,0), size=(200,200))
|
|
||||||
child = mcrfpy.Frame(pos=(10,10), size=(50,50))
|
|
||||||
|
|
||||||
parent_frame.children.append(child)
|
|
||||||
assert len(parent_frame.children) == 1
|
|
||||||
|
|
||||||
# Move via direct assignment
|
|
||||||
child.parent = scene
|
|
||||||
|
|
||||||
assert len(parent_frame.children) == 0, f"Expected 0 children in frame, got {len(parent_frame.children)}"
|
|
||||||
assert len(scene.children) == 1, f"Expected 1 child in scene, got {len(scene.children)}"
|
|
||||||
assert child.parent.name == 'test_assign_scene_from_frame'
|
|
||||||
print("PASS: .parent = scene from Frame works")
|
|
||||||
|
|
||||||
|
|
||||||
def run_tests():
|
|
||||||
"""Run all tests"""
|
|
||||||
print("Issue #183: .parent quirks regression test")
|
|
||||||
print("=" * 50)
|
|
||||||
|
|
||||||
try:
|
|
||||||
test_new_drawable_parent_none()
|
|
||||||
test_no_duplicate_on_same_parent()
|
|
||||||
test_parent_removes_from_old_collection()
|
|
||||||
test_parent_none_removes_from_collection()
|
|
||||||
test_grid_parent_handling()
|
|
||||||
test_move_from_frame_to_grid()
|
|
||||||
|
|
||||||
# Scene parent tracking tests
|
|
||||||
test_scene_parent_returns_scene_object()
|
|
||||||
test_scene_parent_none_removes()
|
|
||||||
test_scene_to_frame()
|
|
||||||
test_frame_to_scene()
|
|
||||||
test_parent_assign_scene()
|
|
||||||
test_parent_assign_scene_from_frame()
|
|
||||||
|
|
||||||
print("=" * 50)
|
|
||||||
print("All tests PASSED!")
|
|
||||||
sys.exit(0)
|
|
||||||
except AssertionError as e:
|
|
||||||
print(f"FAIL: {e}")
|
|
||||||
sys.exit(1)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"ERROR: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Run tests immediately (no game loop needed)
|
|
||||||
run_tests()
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import mcrfpy
|
|
||||||
print("1 - Creating scene")
|
|
||||||
scene = mcrfpy.Scene("test")
|
|
||||||
print("2 - Getting children")
|
|
||||||
ui = scene.children
|
|
||||||
print("3 - Creating frame")
|
|
||||||
frame = mcrfpy.Frame(pos=(0,0), size=(50,50))
|
|
||||||
print("4 - Appending frame")
|
|
||||||
ui.append(frame)
|
|
||||||
print("5 - Length check")
|
|
||||||
print(f"len: {len(ui)}")
|
|
||||||
print("DONE")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import mcrfpy
|
|
||||||
print("1")
|
|
||||||
scene = mcrfpy.Scene("test")
|
|
||||||
print("2")
|
|
||||||
ui = scene.children
|
|
||||||
print("3")
|
|
||||||
print(f"children: {ui}")
|
|
||||||
print("4")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import mcrfpy
|
|
||||||
print("1 - Loading texture", flush=True)
|
|
||||||
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
|
|
||||||
print("2 - Creating grid", flush=True)
|
|
||||||
grid = mcrfpy.Grid(grid_size=(10, 10), texture=texture, pos=(0, 0), size=(160, 160))
|
|
||||||
print("3 - Getting grid point at (3, 5)", flush=True)
|
|
||||||
point = grid.at(3, 5)
|
|
||||||
print(f"4 - Point: {point}", flush=True)
|
|
||||||
print("5 - Getting grid_pos", flush=True)
|
|
||||||
grid_pos = point.grid_pos
|
|
||||||
print(f"6 - grid_pos: {grid_pos}", flush=True)
|
|
||||||
print("PASS", flush=True)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Test GridPoint.grid_pos property"""
|
|
||||||
import sys
|
|
||||||
import mcrfpy
|
|
||||||
|
|
||||||
print("Testing GridPoint.grid_pos...")
|
|
||||||
|
|
||||||
texture = mcrfpy.Texture("assets/kenney_ice.png", 16, 16)
|
|
||||||
grid = mcrfpy.Grid(grid_size=(10, 10), texture=texture, pos=(0, 0), size=(160, 160))
|
|
||||||
|
|
||||||
# Get a grid point
|
|
||||||
print("Getting grid point at (3, 5)...")
|
|
||||||
point = grid.at(3, 5)
|
|
||||||
print(f"Point: {point}")
|
|
||||||
|
|
||||||
# Test grid_pos property exists and returns tuple
|
|
||||||
print("Checking grid_pos property...")
|
|
||||||
grid_pos = point.grid_pos
|
|
||||||
print(f"grid_pos type: {type(grid_pos)}")
|
|
||||||
print(f"grid_pos value: {grid_pos}")
|
|
||||||
|
|
||||||
if not isinstance(grid_pos, tuple):
|
|
||||||
print(f"FAIL: grid_pos should be tuple, got {type(grid_pos)}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if len(grid_pos) != 2:
|
|
||||||
print(f"FAIL: grid_pos should have 2 elements, got {len(grid_pos)}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if grid_pos != (3, 5):
|
|
||||||
print(f"FAIL: grid_pos should be (3, 5), got {grid_pos}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
# Test another position
|
|
||||||
print("Getting grid point at (7, 2)...")
|
|
||||||
point2 = grid.at(7, 2)
|
|
||||||
if point2.grid_pos != (7, 2):
|
|
||||||
print(f"FAIL: grid_pos should be (7, 2), got {point2.grid_pos}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print("PASS: GridPoint.grid_pos works correctly!")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import mcrfpy
|
|
||||||
print("1 - Creating scene", flush=True)
|
|
||||||
scene = mcrfpy.Scene("test")
|
|
||||||
print("2 - Getting children", flush=True)
|
|
||||||
ui = scene.children
|
|
||||||
print("3 - Creating frame", flush=True)
|
|
||||||
frame = mcrfpy.Frame(pos=(0,0), size=(50,50))
|
|
||||||
print("4 - Appending frame", flush=True)
|
|
||||||
ui.append(frame)
|
|
||||||
print("5 - Starting iteration", flush=True)
|
|
||||||
for item in ui:
|
|
||||||
print(f"Item: {item}", flush=True)
|
|
||||||
print("6 - Iteration done", flush=True)
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import mcrfpy
|
|
||||||
print("1 - Creating scene")
|
|
||||||
scene = mcrfpy.Scene("test")
|
|
||||||
print("2 - Getting children")
|
|
||||||
ui = scene.children
|
|
||||||
print("3 - Creating frame")
|
|
||||||
frame = mcrfpy.Frame(pos=(0,0), size=(50,50))
|
|
||||||
print("4 - Appending frame")
|
|
||||||
ui.append(frame)
|
|
||||||
print("5 - Starting iteration")
|
|
||||||
for item in ui:
|
|
||||||
print(f"Item: {item}")
|
|
||||||
print("6 - Iteration done")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Test iteration works with hidden types"""
|
|
||||||
import sys
|
|
||||||
import mcrfpy
|
|
||||||
|
|
||||||
print("Step 1: Creating scene...")
|
|
||||||
scene = mcrfpy.Scene("test_scene")
|
|
||||||
print(f" scene: {scene}")
|
|
||||||
|
|
||||||
print("Step 2: Getting children...")
|
|
||||||
ui = scene.children
|
|
||||||
print(f" children: {ui}")
|
|
||||||
|
|
||||||
print("Step 3: Creating Frame...")
|
|
||||||
frame = mcrfpy.Frame(pos=(0,0), size=(50,50))
|
|
||||||
print(f" frame: {frame}")
|
|
||||||
|
|
||||||
print("Step 4: Appending Frame...")
|
|
||||||
ui.append(frame)
|
|
||||||
print(f" append succeeded, len={len(ui)}")
|
|
||||||
|
|
||||||
print("Step 5: Creating Caption...")
|
|
||||||
caption = mcrfpy.Caption(text="hi", pos=(0,0))
|
|
||||||
print(f" caption: {caption}")
|
|
||||||
|
|
||||||
print("Step 6: Appending Caption...")
|
|
||||||
ui.append(caption)
|
|
||||||
print(f" append succeeded, len={len(ui)}")
|
|
||||||
|
|
||||||
print("Step 7: Starting iteration...")
|
|
||||||
count = 0
|
|
||||||
for item in ui:
|
|
||||||
count += 1
|
|
||||||
print(f" Item {count}: {item}")
|
|
||||||
|
|
||||||
print(f"Step 8: Iteration complete, {count} items")
|
|
||||||
|
|
||||||
if count == 2:
|
|
||||||
print("PASS")
|
|
||||||
sys.exit(0)
|
|
||||||
else:
|
|
||||||
print(f"FAIL: expected 2 items, got {count}")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Simple module test"""
|
|
||||||
import sys
|
|
||||||
import mcrfpy
|
|
||||||
|
|
||||||
print("Step 1: Module loaded")
|
|
||||||
|
|
||||||
# Test window singleton exists (#184)
|
|
||||||
print("Step 2: Checking window...")
|
|
||||||
has_window = hasattr(mcrfpy, 'window')
|
|
||||||
print(f" has window: {has_window}")
|
|
||||||
|
|
||||||
if has_window:
|
|
||||||
print("Step 3: Getting window...")
|
|
||||||
window = mcrfpy.window
|
|
||||||
print(f" window: {window}")
|
|
||||||
print("Step 4: Checking resolution...")
|
|
||||||
res = window.resolution
|
|
||||||
print(f" resolution: {res}")
|
|
||||||
|
|
||||||
print("PASS")
|
|
||||||
sys.exit(0)
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import sys
|
|
||||||
import mcrfpy
|
|
||||||
print("Creating scene...")
|
|
||||||
scene = mcrfpy.Scene("test")
|
|
||||||
print("Scene created")
|
|
||||||
sys.exit(0)
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue