diff --git a/.gitignore b/.gitignore index 206da9b..2a59fe4 100644 --- a/.gitignore +++ b/.gitignore @@ -16,7 +16,6 @@ CMakeFiles/ Makefile *.zip __lib/ -__lib_windows/ _oldscripts/ assets/ cellular_automata_fire/ diff --git a/CMakeLists.txt b/CMakeLists.txt index 45f420f..ac073a8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,33 +8,14 @@ project(McRogueFace) set(CMAKE_CXX_STANDARD 20) 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 +#include_directories(${CMAKE_SOURCE_DIR}/deps_linux) include_directories(${CMAKE_SOURCE_DIR}/deps) +#include_directories(${CMAKE_SOURCE_DIR}/deps_linux/Python-3.11.1) include_directories(${CMAKE_SOURCE_DIR}/deps/libtcod) -# Python includes: use different paths for Windows vs Linux -if(MCRF_CROSS_WINDOWS) - # 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() +include_directories(${CMAKE_SOURCE_DIR}/deps/cpython) +include_directories(${CMAKE_SOURCE_DIR}/deps/Python) # ImGui and ImGui-SFML include directories include_directories(${CMAKE_SOURCE_DIR}/modules/imgui) @@ -56,107 +37,47 @@ file(GLOB_RECURSE SOURCES "src/*.cpp") list(APPEND SOURCES ${IMGUI_SOURCES}) # Find OpenGL (required by ImGui-SFML) -if(MCRF_CROSS_WINDOWS) - # For cross-compilation, OpenGL is provided by MinGW - set(OPENGL_LIBRARIES opengl32) -else() - find_package(OpenGL REQUIRED) - set(OPENGL_LIBRARIES OpenGL::GL) -endif() +find_package(OpenGL REQUIRED) # Create a list of libraries to link against -if(MCRF_CROSS_WINDOWS) - # MinGW cross-compilation: use full library names - set(LINK_LIBS - sfml-graphics - sfml-window - sfml-system - sfml-audio - 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 - ) +set(LINK_LIBS + sfml-graphics + sfml-window + sfml-system + sfml-audio + tcod + OpenGL::GL) +# 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) - - # 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() - # Unix/Linux build - set(LINK_LIBS - sfml-graphics - sfml-window - sfml-system - sfml-audio - tcod - python3.14 - m dl util pthread - ${OPENGL_LIBRARIES}) + # Unix/Linux specific libraries + list(APPEND LINK_LIBS python3.14 m dl util pthread) include_directories(${CMAKE_SOURCE_DIR}/deps/platform/linux) - link_directories(${CMAKE_SOURCE_DIR}/__lib) 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 add_executable(mcrogueface ${SOURCES}) # Define NO_SDL for libtcod-headless headers (excludes SDL-dependent code) target_compile_definitions(mcrogueface PRIVATE NO_SDL) -# On Windows, define Py_ENABLE_SHARED for proper Python DLL imports -# Py_PYCONFIG_H prevents Include/pyconfig.h (Linux config) from being included -# (PC/pyconfig.h already defines HAVE_DECLSPEC_DLL and MS_WINDOWS) -if(WIN32 OR MCRF_CROSS_WINDOWS) - target_compile_definitions(mcrogueface PRIVATE Py_ENABLE_SHARED Py_PYCONFIG_H) -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() +# On Windows, set subsystem to WINDOWS to hide console +if(WIN32) + set_target_properties(mcrogueface PROPERTIES + WIN32_EXECUTABLE TRUE + LINK_FLAGS "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup") endif() # 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 $/lib) # On Windows, copy DLLs to executable directory -if(MCRF_CROSS_WINDOWS) - # Cross-compilation: copy DLLs from __lib_windows - add_custom_command(TARGET mcrogueface POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/__lib_windows/sfml/bin $ - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/__lib_windows/libtcod/bin $ - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_SOURCE_DIR}/__lib_windows/python314.dll $ - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_SOURCE_DIR}/__lib_windows/python3.dll $ - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_SOURCE_DIR}/__lib_windows/vcruntime140.dll $ - COMMAND ${CMAKE_COMMAND} -E copy - ${CMAKE_SOURCE_DIR}/__lib_windows/vcruntime140_1.dll $ - COMMAND ${CMAKE_COMMAND} -E copy - /usr/x86_64-w64-mingw32/lib/libwinpthread-1.dll $ - 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 $ - COMMAND ${CMAKE_COMMAND} -E echo "Copied Python stdlib") -elseif(WIN32) - # Native Windows build: copy DLLs from __lib +if(WIN32) + # 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 $ 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} $) + # endforeach() endif() # rpath for including shared libraries (Linux/Unix only) diff --git a/cmake/toolchains/mingw-w64-x86_64.cmake b/cmake/toolchains/mingw-w64-x86_64.cmake deleted file mode 100644 index fb8ebf5..0000000 --- a/cmake/toolchains/mingw-w64-x86_64.cmake +++ /dev/null @@ -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") diff --git a/deps/platform/windows/platform.h b/deps/platform/windows/platform.h index ac9b60e..ee4176e 100644 --- a/deps/platform/windows/platform.h +++ b/deps/platform/windows/platform.h @@ -1,12 +1,12 @@ #ifndef __PLATFORM #define __PLATFORM -#define __PLATFORM_SET_PYTHON_SEARCH_PATHS 1 -#include +#define __PLATFORM_SET_PYTHON_SEARCH_PATHS 0 +#include std::wstring executable_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; size_t path_index = exec_path.find_last_of(L"\\/"); return exec_path.substr(0, path_index); @@ -15,7 +15,7 @@ std::wstring executable_path() std::wstring executable_filename() { wchar_t buffer[MAX_PATH]; - GetModuleFileNameW(NULL, buffer, MAX_PATH); // Use explicit Unicode version + GetModuleFileName(NULL, buffer, MAX_PATH); std::wstring exec_path = buffer; return exec_path; } diff --git a/src/Animation.cpp b/src/Animation.cpp index 9b4c7ba..de298ec 100644 --- a/src/Animation.cpp +++ b/src/Animation.cpp @@ -767,7 +767,7 @@ void AnimationManager::addAnimation(std::shared_ptr animation, } return; // Don't add to active animations yet - case AnimationConflictMode::RAISE_ERROR: + case AnimationConflictMode::ERROR: // Raise Python exception PyGILState_STATE gstate = PyGILState_Ensure(); PyErr_Format(PyExc_RuntimeError, diff --git a/src/Animation.h b/src/Animation.h index d364e91..4d546ab 100644 --- a/src/Animation.h +++ b/src/Animation.h @@ -16,10 +16,9 @@ class UIEntity; * ConflictMode - How to handle multiple animations on the same property (#120) */ enum class AnimationConflictMode { - REPLACE, // Stop/complete existing animation, start new one (default) - QUEUE, // Queue new animation to run after existing one completes - RAISE_ERROR // Raise an error if property is already being animated - // Note: Can't use ERROR as it conflicts with Windows macro + REPLACE, // Stop/complete existing animation, start new one (default) + QUEUE, // Queue new animation to run after existing one completes + ERROR // Raise an error if property is already being animated }; // Forward declare namespace diff --git a/src/GameEngine.cpp b/src/GameEngine.cpp index de149be..ab86bab 100644 --- a/src/GameEngine.cpp +++ b/src/GameEngine.cpp @@ -65,21 +65,11 @@ GameEngine::GameEngine(const McRogueFaceConfig& cfg) !config.python_mode; if (should_load_game) { - std::cerr << "[DEBUG] GameEngine: loading default game.py" << std::endl; - std::cerr.flush(); if (!Py_IsInitialized()) { - std::cerr << "[DEBUG] GameEngine: initializing Python API" << std::endl; - std::cerr.flush(); McRFPy_API::api_init(); } - std::cerr << "[DEBUG] GameEngine: importing mcrfpy" << std::endl; - std::cerr.flush(); McRFPy_API::executePyString("import mcrfpy"); - std::cerr << "[DEBUG] GameEngine: executing scripts/game.py" << std::endl; - std::cerr.flush(); 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. diff --git a/src/McRFPy_API.cpp b/src/McRFPy_API.cpp index 37a2654..0f46054 100644 --- a/src/McRFPy_API.cpp +++ b/src/McRFPy_API.cpp @@ -28,7 +28,6 @@ #include "PyScene.h" #include "PythonObjectCache.h" #include -#include #include #include @@ -807,26 +806,13 @@ void McRFPy_API::executeScript(std::string filename) } } - // Use std::ifstream + PyRun_SimpleString instead of PyRun_SimpleFile - // PyRun_SimpleFile has compatibility issues with MinGW-compiled code - std::ifstream file(script_path); - if (!file.is_open()) { + FILE* PScriptFile = fopen(script_path.string().c_str(), "r"); + if(PScriptFile) { + PyRun_SimpleFile(PScriptFile, script_path.string().c_str()); + fclose(PScriptFile); + } else { std::cout << "Failed to open script: " << script_path.string() << std::endl; - return; } - - std::string script_content((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); - 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() diff --git a/src/PyAnimation.cpp b/src/PyAnimation.cpp index 0081365..7208fe8 100644 --- a/src/PyAnimation.cpp +++ b/src/PyAnimation.cpp @@ -186,7 +186,7 @@ static bool parseConflictMode(const char* mode_str, AnimationConflictMode& mode) } else if (strcmp(mode_str, "queue") == 0) { mode = AnimationConflictMode::QUEUE; } else if (strcmp(mode_str, "error") == 0) { - mode = AnimationConflictMode::RAISE_ERROR; + mode = AnimationConflictMode::ERROR; } else { PyErr_Format(PyExc_ValueError, "Invalid conflict_mode '%s'. Must be 'replace', 'queue', or 'error'.", mode_str); diff --git a/src/PyColor.cpp b/src/PyColor.cpp index 75d5242..ef6ca2b 100644 --- a/src/PyColor.cpp +++ b/src/PyColor.cpp @@ -172,7 +172,7 @@ PyObject* PyColor::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds) PyObject* PyColor::get_member(PyObject* obj, void* closure) { PyColorObject* self = (PyColorObject*)obj; - intptr_t member = (intptr_t)closure; + long member = (long)closure; switch (member) { 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) { PyColorObject* self = (PyColorObject*)obj; - intptr_t member = (intptr_t)closure; + long member = (long)closure; if (!PyLong_Check(value)) { PyErr_SetString(PyExc_TypeError, "Color values must be integers"); diff --git a/src/PyVector.cpp b/src/PyVector.cpp index 4315b30..1625106 100644 --- a/src/PyVector.cpp +++ b/src/PyVector.cpp @@ -227,7 +227,7 @@ PyObject* PyVector::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds) PyObject* PyVector::get_member(PyObject* obj, void* closure) { PyVectorObject* self = (PyVectorObject*)obj; - if (reinterpret_cast(closure) == 0) { + if (reinterpret_cast(closure) == 0) { // x return PyFloat_FromDouble(self->data.x); } else { @@ -250,7 +250,7 @@ int PyVector::set_member(PyObject* obj, PyObject* value, void* closure) return -1; } - if (reinterpret_cast(closure) == 0) { + if (reinterpret_cast(closure) == 0) { // x self->data.x = val; } else { diff --git a/src/UICaption.cpp b/src/UICaption.cpp index 805bc2b..bc10bc0 100644 --- a/src/UICaption.cpp +++ b/src/UICaption.cpp @@ -96,7 +96,7 @@ void UICaption::onPositionChanged() PyObject* UICaption::get_float_member(PyUICaptionObject* self, void* closure) { - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (member_ptr == 0) return PyFloat_FromDouble(self->data->text.getPosition().x); 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) { float val; - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (PyFloat_Check(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 // validate closure (should be impossible to be wrong, but it's thorough) - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (member_ptr != 0 && member_ptr != 1) { 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) { - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); //TODO: this logic of (PyColor instance OR tuple -> sf::color) should be encapsulated for reuse int r, g, b, a; if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color") /*(PyObject*)&mcrfpydef::PyColorType)*/)) diff --git a/src/UIDrawable.cpp b/src/UIDrawable.cpp index 4812f57..bde2bb6 100644 --- a/src/UIDrawable.cpp +++ b/src/UIDrawable.cpp @@ -170,7 +170,7 @@ void UIDrawable::render() } PyObject* UIDrawable::get_click(PyObject* self, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); // trust me bro, it's an Enum + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); // trust me bro, it's an Enum PyObject* ptr; switch (objtype) @@ -228,7 +228,7 @@ PyObject* UIDrawable::get_click(PyObject* self, void* closure) { } int UIDrawable::set_click(PyObject* self, PyObject* value, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); // trust me bro, it's an Enum + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); // trust me bro, it's an Enum UIDrawable* target; switch (objtype) { @@ -305,7 +305,7 @@ void UIDrawable::on_move_unregister() } PyObject* UIDrawable::get_int(PyObject* self, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); UIDrawable* drawable = nullptr; switch (objtype) { @@ -339,7 +339,7 @@ PyObject* UIDrawable::get_int(PyObject* self, void* closure) { } int UIDrawable::set_int(PyObject* self, PyObject* value, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); UIDrawable* drawable = nullptr; switch (objtype) { @@ -405,7 +405,7 @@ void UIDrawable::notifyZIndexChanged() { } PyObject* UIDrawable::get_name(PyObject* self, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); UIDrawable* drawable = nullptr; switch (objtype) { @@ -439,7 +439,7 @@ PyObject* UIDrawable::get_name(PyObject* self, void* closure) { } int UIDrawable::set_name(PyObject* self, PyObject* value, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); UIDrawable* drawable = nullptr; 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) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); UIDrawable* drawable = nullptr; switch (objtype) { @@ -686,7 +686,7 @@ PyObject* UIDrawable::get_pos(PyObject* self, void* closure) { } int UIDrawable::set_pos(PyObject* self, PyObject* value, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); UIDrawable* drawable = nullptr; switch (objtype) { @@ -892,7 +892,7 @@ void UIDrawable::markDirty() { // Python API - get parent drawable PyObject* UIDrawable::get_parent(PyObject* self, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); UIDrawable* drawable = nullptr; 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) int UIDrawable::set_parent(PyObject* self, PyObject* value, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); std::shared_ptr drawable = nullptr; // 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) PyObject* UIDrawable::get_global_pos(PyObject* self, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); UIDrawable* drawable = nullptr; 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 PyObject* UIDrawable::get_bounds_py(PyObject* self, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); UIDrawable* drawable = nullptr; 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 PyObject* UIDrawable::get_global_bounds_py(PyObject* self, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); UIDrawable* drawable = nullptr; switch (objtype) { @@ -1297,7 +1297,7 @@ PyObject* UIDrawable::get_global_bounds_py(PyObject* self, void* closure) { // #140 - Python API for on_enter property PyObject* UIDrawable::get_on_enter(PyObject* self, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); PyObject* ptr = nullptr; 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) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); UIDrawable* target = nullptr; switch (objtype) { @@ -1380,7 +1380,7 @@ int UIDrawable::set_on_enter(PyObject* self, PyObject* value, void* closure) { // #140 - Python API for on_exit property PyObject* UIDrawable::get_on_exit(PyObject* self, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); PyObject* ptr = nullptr; 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) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); UIDrawable* target = nullptr; 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) PyObject* UIDrawable::get_hovered(PyObject* self, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); UIDrawable* drawable = nullptr; switch (objtype) { @@ -1498,7 +1498,7 @@ PyObject* UIDrawable::get_hovered(PyObject* self, void* closure) { // #141 - Python API for on_move property PyObject* UIDrawable::get_on_move(PyObject* self, void* closure) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); PyObject* ptr = nullptr; 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) { - PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); + PyObjectsEnum objtype = static_cast(reinterpret_cast(closure)); UIDrawable* target = nullptr; switch (objtype) { @@ -1689,7 +1689,7 @@ PyObject* UIDrawable_animate_impl(std::shared_ptr self, PyObject* ar } else if (strcmp(conflict_mode_str, "queue") == 0) { conflict_mode = AnimationConflictMode::QUEUE; } else if (strcmp(conflict_mode_str, "error") == 0) { - conflict_mode = AnimationConflictMode::RAISE_ERROR; + conflict_mode = AnimationConflictMode::ERROR; } else { PyErr_Format(PyExc_ValueError, "Invalid conflict_mode '%s'. Must be 'replace', 'queue', or 'error'.", conflict_mode_str); diff --git a/src/UIEntity.cpp b/src/UIEntity.cpp index 7950c90..25c15a8 100644 --- a/src/UIEntity.cpp +++ b/src/UIEntity.cpp @@ -358,7 +358,7 @@ PyObject* UIGridPointStateVector_to_PyList(const std::vector& } PyObject* UIEntity::get_position(PyUIEntityObject* self, void* closure) { - if (reinterpret_cast(closure) == 0) { + if (reinterpret_cast(closure) == 0) { return sfVector2f_to_PyObject(self->data->position); } else { // 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_y = self->data->position.y; - if (reinterpret_cast(closure) == 0) { + if (reinterpret_cast(closure) == 0) { sf::Vector2f vec = PyObject_to_sfVector2f(value); if (PyErr_Occurred()) { 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) { - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (member_ptr == 0) // x return PyFloat_FromDouble(self->data->position.x); 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) { float val; - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (PyFloat_Check(value)) { val = PyFloat_AsDouble(value); @@ -540,7 +540,7 @@ PyObject* UIEntity::get_pixel_member(PyUIEntityObject* self, void* closure) { float cell_width, cell_height; get_cell_dimensions(self->data.get(), cell_width, cell_height); - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (member_ptr == 0) // x return PyFloat_FromDouble(self->data->position.x * cell_width); 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_y = self->data->position.y; - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (member_ptr == 0) // x self->data->position.x = val / cell_width; 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) PyObject* UIEntity::get_grid_int_member(PyUIEntityObject* self, void* closure) { - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (member_ptr == 0) // grid_x return PyLong_FromLong(static_cast(self->data->position.x)); 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_y = self->data->position.y; - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (member_ptr == 0) // grid_x self->data->position.x = static_cast(val); else // grid_y @@ -1172,7 +1172,7 @@ PyObject* UIEntity::animate(PyUIEntityObject* self, PyObject* args, PyObject* kw } else if (strcmp(conflict_mode_str, "queue") == 0) { conflict_mode = AnimationConflictMode::QUEUE; } else if (strcmp(conflict_mode_str, "error") == 0) { - conflict_mode = AnimationConflictMode::RAISE_ERROR; + conflict_mode = AnimationConflictMode::ERROR; } else { PyErr_Format(PyExc_ValueError, "Invalid conflict_mode '%s'. Must be 'replace', 'queue', or 'error'.", conflict_mode_str); diff --git a/src/UIFrame.cpp b/src/UIFrame.cpp index 195acc6..251e9af 100644 --- a/src/UIFrame.cpp +++ b/src/UIFrame.cpp @@ -187,7 +187,7 @@ PyObject* UIFrame::get_children(PyUIFrameObject* self, void* closure) PyObject* UIFrame::get_float_member(PyUIFrameObject* self, void* closure) { - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (member_ptr == 0) return PyFloat_FromDouble(self->data->box.getPosition().x); 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) { float val; - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (PyFloat_Check(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) { // validate closure (should be impossible to be wrong, but it's thorough) - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (member_ptr != 0 && member_ptr != 1) { 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) { //TODO: this logic of (PyColor instance OR tuple -> sf::color) should be encapsulated for reuse - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); int r, g, b, a; if (PyObject_IsInstance(value, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Color"))) { diff --git a/src/UIGrid.cpp b/src/UIGrid.cpp index e4188a5..f6f21a5 100644 --- a/src/UIGrid.cpp +++ b/src/UIGrid.cpp @@ -1076,7 +1076,7 @@ int UIGrid::set_center(PyUIGridObject* self, PyObject* value, void* closure) { PyObject* UIGrid::get_float_member(PyUIGridObject* self, void* closure) { - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (member_ptr == 0) // x return PyFloat_FromDouble(self->data->box.getPosition().x); 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) { float val; - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (PyFloat_Check(value)) { val = PyFloat_AsDouble(value); diff --git a/src/UIGridPoint.cpp b/src/UIGridPoint.cpp index 02131f4..a636c8c 100644 --- a/src/UIGridPoint.cpp +++ b/src/UIGridPoint.cpp @@ -57,7 +57,7 @@ sf::Color PyObject_to_sfColor(PyObject* obj) { // #150 - Removed get_color/set_color - now handled by layers PyObject* UIGridPoint::get_bool_member(PyUIGridPointObject* self, void* closure) { - if (reinterpret_cast(closure) == 0) { // walkable + if (reinterpret_cast(closure) == 0) { // walkable return PyBool_FromLong(self->data->walkable); } else { // 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) { if (value == Py_True) { - if (reinterpret_cast(closure) == 0) { // walkable + if (reinterpret_cast(closure) == 0) { // walkable self->data->walkable = true; } else { // transparent self->data->transparent = true; } } else if (value == Py_False) { - if (reinterpret_cast(closure) == 0) { // walkable + if (reinterpret_cast(closure) == 0) { // walkable self->data->walkable = false; } else { // transparent self->data->transparent = false; @@ -162,7 +162,7 @@ PyObject* UIGridPoint::repr(PyUIGridPointObject* self) { } PyObject* UIGridPointState::get_bool_member(PyUIGridPointStateObject* self, void* closure) { - if (reinterpret_cast(closure) == 0) { // visible + if (reinterpret_cast(closure) == 0) { // visible return PyBool_FromLong(self->data->visible); } else { // 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 } - if (reinterpret_cast(closure) == 0) { // visible + if (reinterpret_cast(closure) == 0) { // visible self->data->visible = truthValue; } else { // discovered self->data->discovered = truthValue; diff --git a/src/UISprite.cpp b/src/UISprite.cpp index 7803cc8..a77fea3 100644 --- a/src/UISprite.cpp +++ b/src/UISprite.cpp @@ -182,7 +182,7 @@ void UISprite::onPositionChanged() PyObject* UISprite::get_float_member(PyUISpriteObject* self, void* closure) { - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (member_ptr == 0) return PyFloat_FromDouble(self->data->getPosition().x); 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) { float val; - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (PyFloat_Check(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) { - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (true) {} 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 val; - auto member_ptr = reinterpret_cast(closure); + auto member_ptr = reinterpret_cast(closure); if (PyLong_Check(value)) { val = PyLong_AsLong(value); diff --git a/src/main.cpp b/src/main.cpp index b9f95f2..b0e9599 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,7 +7,6 @@ #include "PyTexture.h" #include #include -#include #include // Forward declarations @@ -19,6 +18,7 @@ int main(int argc, char* argv[]) McRogueFaceConfig config; CommandLineParser parser(argc, argv); + // Parse arguments auto parse_result = parser.parse(config); if (parse_result.should_exit) { 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) McRFPy_API::init_python_with_config(config); - + // Import mcrfpy module and store reference McRFPy_API::mcrf_module = PyImport_ImportModule("mcrfpy"); 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_texture", McRFPy_API::default_texture->pyObject()); } - + // Handle different Python modes if (!config.python_command.empty()) { // Execute command from -c @@ -82,15 +82,15 @@ int run_python_interpreter(const McRogueFaceConfig& config) // Use PyRun_String to catch SystemExit PyObject* main_module = PyImport_AddModule("__main__"); PyObject* main_dict = PyModule_GetDict(main_module); - PyObject* result_obj = PyRun_String(config.python_command.c_str(), + PyObject* result_obj = PyRun_String(config.python_command.c_str(), Py_file_input, main_dict, main_dict); - + if (result_obj == NULL) { // Check if it's SystemExit if (PyErr_Occurred()) { PyObject *type, *value, *traceback; PyErr_Fetch(&type, &value, &traceback); - + // If it's SystemExit and we're in interactive mode, clear it if (PyErr_GivenExceptionMatches(type, PyExc_SystemExit)) { PyErr_Clear(); @@ -99,7 +99,7 @@ int run_python_interpreter(const McRogueFaceConfig& config) PyErr_Restore(type, value, traceback); PyErr_Print(); } - + Py_XDECREF(type); Py_XDECREF(value); Py_XDECREF(traceback); @@ -128,46 +128,16 @@ int run_python_interpreter(const McRogueFaceConfig& config) } else if (!config.script_path.empty()) { // Execute script file (sys.argv already set at init time) - // Note: Using PyRun_SimpleString instead of PyRun_SimpleFile for better - // cross-platform compatibility (PyRun_SimpleFile has issues with MinGW/Wine) - - // Read file contents - std::ifstream file(config.script_path); - if (!file.is_open()) { + FILE* fp = fopen(config.script_path.string().c_str(), "r"); + if (!fp) { std::cerr << "mcrogueface: can't open file '" << config.script_path << "': "; std::cerr << "[Errno " << errno << "] " << strerror(errno) << std::endl; - delete engine; return 1; } - std::string script_content((std::istreambuf_iterator(file)), - std::istreambuf_iterator()); - 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) { // Even if script had SystemExit, continue to interactive mode if (result != 0) { @@ -175,7 +145,7 @@ int run_python_interpreter(const McRogueFaceConfig& config) if (PyErr_Occurred()) { PyObject *type, *value, *traceback; PyErr_Fetch(&type, &value, &traceback); - + if (PyErr_GivenExceptionMatches(type, PyExc_SystemExit)) { PyErr_Clear(); result = 0; // Don't exit with error @@ -183,7 +153,7 @@ int run_python_interpreter(const McRogueFaceConfig& config) PyErr_Restore(type, value, traceback); PyErr_Print(); } - + Py_XDECREF(type); Py_XDECREF(value); Py_XDECREF(traceback); @@ -192,7 +162,7 @@ int run_python_interpreter(const McRogueFaceConfig& config) // Run interactive mode after script PyRun_InteractiveLoop(stdin, ""); } - + // Run the game engine after script execution engine->run(); @@ -227,7 +197,7 @@ int run_python_interpreter(const McRogueFaceConfig& config) } return 0; } - + delete engine; return 0; } diff --git a/tests/api_changes_batch_test.py b/tests/api_changes_batch_test.py deleted file mode 100644 index 48239e0..0000000 --- a/tests/api_changes_batch_test.py +++ /dev/null @@ -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() diff --git a/tests/issue_176_entity_position_test.py b/tests/issue_176_entity_position_test.py deleted file mode 100644 index 6372e6c..0000000 --- a/tests/issue_176_entity_position_test.py +++ /dev/null @@ -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() diff --git a/tests/issue_177_gridpoint_grid_pos_test.py b/tests/issue_177_gridpoint_grid_pos_test.py deleted file mode 100644 index 7967bf9..0000000 --- a/tests/issue_177_gridpoint_grid_pos_test.py +++ /dev/null @@ -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) diff --git a/tests/issue_179_181_grid_vectors_test.py b/tests/issue_179_181_grid_vectors_test.py deleted file mode 100644 index e177935..0000000 --- a/tests/issue_179_181_grid_vectors_test.py +++ /dev/null @@ -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() diff --git a/tests/issue_180_timer_orphan_test.py b/tests/issue_180_timer_orphan_test.py deleted file mode 100644 index 9871d94..0000000 --- a/tests/issue_180_timer_orphan_test.py +++ /dev/null @@ -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) diff --git a/tests/issue_180_timer_stopped_test.py b/tests/issue_180_timer_stopped_test.py deleted file mode 100644 index 0835630..0000000 --- a/tests/issue_180_timer_stopped_test.py +++ /dev/null @@ -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) diff --git a/tests/issue_182_caption_size_test.py b/tests/issue_182_caption_size_test.py deleted file mode 100644 index 0f6b3b2..0000000 --- a/tests/issue_182_caption_size_test.py +++ /dev/null @@ -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) diff --git a/tests/issue_184_189_module_test.py b/tests/issue_184_189_module_test.py deleted file mode 100644 index 619649c..0000000 --- a/tests/issue_184_189_module_test.py +++ /dev/null @@ -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) diff --git a/tests/issue_185_188_bounds_test.py b/tests/issue_185_188_bounds_test.py deleted file mode 100644 index 727cfb2..0000000 --- a/tests/issue_185_188_bounds_test.py +++ /dev/null @@ -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) diff --git a/tests/minimal_reparent.py b/tests/minimal_reparent.py deleted file mode 100644 index 1c3b385..0000000 --- a/tests/minimal_reparent.py +++ /dev/null @@ -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) diff --git a/tests/minimal_test.py b/tests/minimal_test.py deleted file mode 100644 index f887251..0000000 --- a/tests/minimal_test.py +++ /dev/null @@ -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) diff --git a/tests/regression/issue_183_parent_quirks_test.py b/tests/regression/issue_183_parent_quirks_test.py deleted file mode 100644 index 8e3136e..0000000 --- a/tests/regression/issue_183_parent_quirks_test.py +++ /dev/null @@ -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() diff --git a/tests/test_append.py b/tests/test_append.py deleted file mode 100644 index ac496af..0000000 --- a/tests/test_append.py +++ /dev/null @@ -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) diff --git a/tests/test_children.py b/tests/test_children.py deleted file mode 100644 index 4196739..0000000 --- a/tests/test_children.py +++ /dev/null @@ -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) diff --git a/tests/test_gridpoint_debug.py b/tests/test_gridpoint_debug.py deleted file mode 100644 index d80c7b3..0000000 --- a/tests/test_gridpoint_debug.py +++ /dev/null @@ -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) diff --git a/tests/test_gridpoint_grid_pos.py b/tests/test_gridpoint_grid_pos.py deleted file mode 100644 index 21aa451..0000000 --- a/tests/test_gridpoint_grid_pos.py +++ /dev/null @@ -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) diff --git a/tests/test_iter_flush.py b/tests/test_iter_flush.py deleted file mode 100644 index d5f9776..0000000 --- a/tests/test_iter_flush.py +++ /dev/null @@ -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) diff --git a/tests/test_iter_only.py b/tests/test_iter_only.py deleted file mode 100644 index 27866e3..0000000 --- a/tests/test_iter_only.py +++ /dev/null @@ -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) diff --git a/tests/test_iteration.py b/tests/test_iteration.py deleted file mode 100644 index 9406056..0000000 --- a/tests/test_iteration.py +++ /dev/null @@ -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) diff --git a/tests/test_module_simple.py b/tests/test_module_simple.py deleted file mode 100644 index ca97d9b..0000000 --- a/tests/test_module_simple.py +++ /dev/null @@ -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) diff --git a/tests/test_scene_create.py b/tests/test_scene_create.py deleted file mode 100644 index a7c93ce..0000000 --- a/tests/test_scene_create.py +++ /dev/null @@ -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)