diff --git a/src/McRFPy_API.cpp b/src/McRFPy_API.cpp index a88cce6..fca30ad 100644 --- a/src/McRFPy_API.cpp +++ b/src/McRFPy_API.cpp @@ -11,9 +11,9 @@ #include #include -std::vector McRFPy_API::soundbuffers; -sf::Music McRFPy_API::music; -sf::Sound McRFPy_API::sfx; +std::vector* McRFPy_API::soundbuffers = nullptr; +sf::Music* McRFPy_API::music = nullptr; +sf::Sound* McRFPy_API::sfx = nullptr; std::shared_ptr McRFPy_API::default_font; std::shared_ptr McRFPy_API::default_texture; @@ -356,6 +356,23 @@ void McRFPy_API::executeScript(std::string filename) void McRFPy_API::api_shutdown() { + // Clean up audio resources in correct order + if (sfx) { + sfx->stop(); + delete sfx; + sfx = nullptr; + } + if (music) { + music->stop(); + delete music; + music = nullptr; + } + if (soundbuffers) { + soundbuffers->clear(); + delete soundbuffers; + soundbuffers = nullptr; + } + Py_Finalize(); } @@ -390,25 +407,29 @@ PyObject* McRFPy_API::_refreshFov(PyObject* self, PyObject* args) { PyObject* McRFPy_API::_createSoundBuffer(PyObject* self, PyObject* args) { const char *fn_cstr; if (!PyArg_ParseTuple(args, "s", &fn_cstr)) return NULL; + // Initialize soundbuffers if needed + if (!McRFPy_API::soundbuffers) { + McRFPy_API::soundbuffers = new std::vector(); + } auto b = sf::SoundBuffer(); b.loadFromFile(fn_cstr); - McRFPy_API::soundbuffers.push_back(b); + McRFPy_API::soundbuffers->push_back(b); Py_INCREF(Py_None); return Py_None; } PyObject* McRFPy_API::_loadMusic(PyObject* self, PyObject* args) { const char *fn_cstr; - PyObject* loop_obj; + PyObject* loop_obj = Py_False; if (!PyArg_ParseTuple(args, "s|O", &fn_cstr, &loop_obj)) return NULL; - McRFPy_API::music.stop(); - // get params for sf::Music initialization - //sf::InputSoundFile file; - //file.openFromFile(fn_cstr); - McRFPy_API::music.openFromFile(fn_cstr); - McRFPy_API::music.setLoop(PyObject_IsTrue(loop_obj)); - //McRFPy_API::music.initialize(file.getChannelCount(), file.getSampleRate()); - McRFPy_API::music.play(); + // Initialize music if needed + if (!McRFPy_API::music) { + McRFPy_API::music = new sf::Music(); + } + McRFPy_API::music->stop(); + McRFPy_API::music->openFromFile(fn_cstr); + McRFPy_API::music->setLoop(PyObject_IsTrue(loop_obj)); + McRFPy_API::music->play(); Py_INCREF(Py_None); return Py_None; } @@ -416,7 +437,10 @@ PyObject* McRFPy_API::_loadMusic(PyObject* self, PyObject* args) { PyObject* McRFPy_API::_setMusicVolume(PyObject* self, PyObject* args) { int vol; if (!PyArg_ParseTuple(args, "i", &vol)) return NULL; - McRFPy_API::music.setVolume(vol); + if (!McRFPy_API::music) { + McRFPy_API::music = new sf::Music(); + } + McRFPy_API::music->setVolume(vol); Py_INCREF(Py_None); return Py_None; } @@ -424,7 +448,10 @@ PyObject* McRFPy_API::_setMusicVolume(PyObject* self, PyObject* args) { PyObject* McRFPy_API::_setSoundVolume(PyObject* self, PyObject* args) { float vol; if (!PyArg_ParseTuple(args, "f", &vol)) return NULL; - McRFPy_API::sfx.setVolume(vol); + if (!McRFPy_API::sfx) { + McRFPy_API::sfx = new sf::Sound(); + } + McRFPy_API::sfx->setVolume(vol); Py_INCREF(Py_None); return Py_None; } @@ -432,20 +459,29 @@ PyObject* McRFPy_API::_setSoundVolume(PyObject* self, PyObject* args) { PyObject* McRFPy_API::_playSound(PyObject* self, PyObject* args) { float index; if (!PyArg_ParseTuple(args, "f", &index)) return NULL; - if (index >= McRFPy_API::soundbuffers.size()) return NULL; - McRFPy_API::sfx.stop(); - McRFPy_API::sfx.setBuffer(McRFPy_API::soundbuffers[index]); - McRFPy_API::sfx.play(); + if (!McRFPy_API::soundbuffers || index >= McRFPy_API::soundbuffers->size()) return NULL; + if (!McRFPy_API::sfx) { + McRFPy_API::sfx = new sf::Sound(); + } + McRFPy_API::sfx->stop(); + McRFPy_API::sfx->setBuffer((*McRFPy_API::soundbuffers)[index]); + McRFPy_API::sfx->play(); Py_INCREF(Py_None); return Py_None; } PyObject* McRFPy_API::_getMusicVolume(PyObject* self, PyObject* args) { - return Py_BuildValue("f", McRFPy_API::music.getVolume()); + if (!McRFPy_API::music) { + return Py_BuildValue("f", 0.0f); + } + return Py_BuildValue("f", McRFPy_API::music->getVolume()); } PyObject* McRFPy_API::_getSoundVolume(PyObject* self, PyObject* args) { - return Py_BuildValue("f", McRFPy_API::sfx.getVolume()); + if (!McRFPy_API::sfx) { + return Py_BuildValue("f", 0.0f); + } + return Py_BuildValue("f", McRFPy_API::sfx->getVolume()); } // Removed deprecated player_input, computerTurn, playerTurn functions diff --git a/src/McRFPy_API.h b/src/McRFPy_API.h index 4d717df..d556657 100644 --- a/src/McRFPy_API.h +++ b/src/McRFPy_API.h @@ -36,9 +36,9 @@ public: static void REPL_device(FILE * fp, const char *filename); static void REPL(); - static std::vector soundbuffers; - static sf::Music music; - static sf::Sound sfx; + static std::vector* soundbuffers; + static sf::Music* music; + static sf::Sound* sfx; static PyObject* _createSoundBuffer(PyObject*, PyObject*); diff --git a/src/UICaption.cpp b/src/UICaption.cpp index 9a3b5c2..2c62bda 100644 --- a/src/UICaption.cpp +++ b/src/UICaption.cpp @@ -4,6 +4,7 @@ #include "PyVector.h" #include "PyFont.h" #include "PyPositionHelper.h" +#include "UIDrawable_methods.h" #include UICaption::UICaption() @@ -163,7 +164,6 @@ int UICaption::set_color_member(PyUICaptionObject* self, PyObject* value, void* // get value from mcrfpy.Color instance auto c = ((PyColorObject*)value)->data; r = c.r; g = c.g; b = c.b; a = c.a; - std::cout << "got " << int(r) << ", " << int(g) << ", " << int(b) << ", " << int(a) << std::endl; } else if (!PyTuple_Check(value) || PyTuple_Size(value) < 3 || PyTuple_Size(value) > 4) { @@ -208,6 +208,15 @@ int UICaption::set_color_member(PyUICaptionObject* self, PyObject* value, void* } +// Define the PyObjectType alias for the macros +typedef PyUICaptionObject PyObjectType; + +// Method definitions +PyMethodDef UICaption_methods[] = { + UIDRAWABLE_METHODS, + {NULL} // Sentinel +}; + //TODO: evaluate use of Resources::caption_buffer... can't I do this with a std::string? PyObject* UICaption::get_text(PyUICaptionObject* self, void* closure) { @@ -241,6 +250,7 @@ PyGetSetDef UICaption::getsetters[] = { {"font_size", (getter)UICaption::get_float_member, (setter)UICaption::set_float_member, "Font size (integer) in points", (void*)5}, {"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UICAPTION}, {"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UICAPTION}, + UIDRAWABLE_GETSETTERS, {NULL} }; diff --git a/src/UICaption.h b/src/UICaption.h index bd98489..7f00b22 100644 --- a/src/UICaption.h +++ b/src/UICaption.h @@ -40,6 +40,8 @@ public: }; +extern PyMethodDef UICaption_methods[]; + namespace mcrfpydef { static PyTypeObject PyUICaptionType = { .ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0}, @@ -62,7 +64,7 @@ namespace mcrfpydef { //.tp_iternext .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = PyDoc_STR("docstring"), - //.tp_methods = PyUIFrame_methods, + .tp_methods = UICaption_methods, //.tp_members = PyUIFrame_members, .tp_getset = UICaption::getsetters, //.tp_base = NULL, diff --git a/src/UIDrawable_methods.h b/src/UIDrawable_methods.h new file mode 100644 index 0000000..baabe27 --- /dev/null +++ b/src/UIDrawable_methods.h @@ -0,0 +1,193 @@ +#pragma once +#include "Python.h" +#include "UIDrawable.h" +#include "UIBase.h" // For PyUIEntityObject + +// Common methods for all UIDrawable-derived classes + +// get_bounds method implementation (#89) +template +static PyObject* UIDrawable_get_bounds(T* self, PyObject* Py_UNUSED(args)) +{ + auto bounds = self->data->get_bounds(); + return Py_BuildValue("(ffff)", bounds.left, bounds.top, bounds.width, bounds.height); +} + +// move method implementation (#98) +template +static PyObject* UIDrawable_move(T* self, PyObject* args) +{ + float dx, dy; + if (!PyArg_ParseTuple(args, "ff", &dx, &dy)) { + return NULL; + } + + self->data->move(dx, dy); + Py_RETURN_NONE; +} + +// resize method implementation (#98) +template +static PyObject* UIDrawable_resize(T* self, PyObject* args) +{ + float w, h; + if (!PyArg_ParseTuple(args, "ff", &w, &h)) { + return NULL; + } + + self->data->resize(w, h); + Py_RETURN_NONE; +} + +// Macro to add common UIDrawable methods to a method array +#define UIDRAWABLE_METHODS \ + {"get_bounds", (PyCFunction)UIDrawable_get_bounds, METH_NOARGS, \ + "Get bounding box as (x, y, width, height)"}, \ + {"move", (PyCFunction)UIDrawable_move, METH_VARARGS, \ + "Move by relative offset (dx, dy)"}, \ + {"resize", (PyCFunction)UIDrawable_resize, METH_VARARGS, \ + "Resize to new dimensions (width, height)"} + +// Common getsetters for UIDrawable properties +#define UIDRAWABLE_GETSETTERS \ + {"visible", (getter)UIDrawable_get_visible, (setter)UIDrawable_set_visible, \ + "Whether the object is visible", NULL}, \ + {"opacity", (getter)UIDrawable_get_opacity, (setter)UIDrawable_set_opacity, \ + "Opacity level (0.0 = transparent, 1.0 = opaque)", NULL} + +// Visible property getter (new for #87) +template +static PyObject* UIDrawable_get_visible(T* self, void* closure) +{ + return PyBool_FromLong(self->data->visible); +} + +// Visible property setter (new for #87) +template +static int UIDrawable_set_visible(T* self, PyObject* value, void* closure) +{ + if (!PyBool_Check(value)) { + PyErr_SetString(PyExc_TypeError, "visible must be a boolean"); + return -1; + } + + self->data->visible = (value == Py_True); + return 0; +} + +// Opacity property getter (new for #88) +template +static PyObject* UIDrawable_get_opacity(T* self, void* closure) +{ + return PyFloat_FromDouble(self->data->opacity); +} + +// Opacity property setter (new for #88) +template +static int UIDrawable_set_opacity(T* self, PyObject* value, void* closure) +{ + float val; + if (PyFloat_Check(value)) { + val = PyFloat_AsDouble(value); + } else if (PyLong_Check(value)) { + val = PyLong_AsLong(value); + } else { + PyErr_SetString(PyExc_TypeError, "opacity must be a number"); + return -1; + } + + // Clamp to valid range + if (val < 0.0f) val = 0.0f; + if (val > 1.0f) val = 1.0f; + + self->data->opacity = val; + return 0; +} + +// Specializations for UIEntity that delegate to its sprite member +template<> +inline PyObject* UIDrawable_get_visible(PyUIEntityObject* self, void* closure) +{ + return PyBool_FromLong(self->data->sprite.visible); +} + +template<> +inline int UIDrawable_set_visible(PyUIEntityObject* self, PyObject* value, void* closure) +{ + if (!PyBool_Check(value)) { + PyErr_SetString(PyExc_TypeError, "visible must be a boolean"); + return -1; + } + + self->data->sprite.visible = (value == Py_True); + return 0; +} + +template<> +inline PyObject* UIDrawable_get_opacity(PyUIEntityObject* self, void* closure) +{ + return PyFloat_FromDouble(self->data->sprite.opacity); +} + +template<> +inline int UIDrawable_set_opacity(PyUIEntityObject* self, PyObject* value, void* closure) +{ + float val; + if (PyFloat_Check(value)) { + val = PyFloat_AsDouble(value); + } else if (PyLong_Check(value)) { + val = PyLong_AsLong(value); + } else { + PyErr_SetString(PyExc_TypeError, "opacity must be a number"); + return -1; + } + + // Clamp to valid range + if (val < 0.0f) val = 0.0f; + if (val > 1.0f) val = 1.0f; + + self->data->sprite.opacity = val; + return 0; +} + +// For get_bounds - UIEntity doesn't have this method, so we delegate to sprite +template<> +inline PyObject* UIDrawable_get_bounds(PyUIEntityObject* self, PyObject* Py_UNUSED(args)) +{ + auto bounds = self->data->sprite.get_bounds(); + return Py_BuildValue("(ffff)", bounds.left, bounds.top, bounds.width, bounds.height); +} + +// For move - UIEntity needs to update its position +template<> +inline PyObject* UIDrawable_move(PyUIEntityObject* self, PyObject* args) +{ + float dx, dy; + if (!PyArg_ParseTuple(args, "ff", &dx, &dy)) { + return NULL; + } + + // Update entity position + self->data->position.x += dx; + self->data->position.y += dy; + self->data->collision_pos.x = std::floor(self->data->position.x); + self->data->collision_pos.y = std::floor(self->data->position.y); + + // Also update sprite position + self->data->sprite.move(dx, dy); + + Py_RETURN_NONE; +} + +// For resize - delegate to sprite +template<> +inline PyObject* UIDrawable_resize(PyUIEntityObject* self, PyObject* args) +{ + float w, h; + if (!PyArg_ParseTuple(args, "ff", &w, &h)) { + return NULL; + } + + self->data->sprite.resize(w, h); + Py_RETURN_NONE; +} \ No newline at end of file diff --git a/src/UIEntity.cpp b/src/UIEntity.cpp index 609c1d6..13f37c9 100644 --- a/src/UIEntity.cpp +++ b/src/UIEntity.cpp @@ -5,6 +5,7 @@ #include "PyObjectUtils.h" #include "PyVector.h" #include "PyPositionHelper.h" +#include "UIDrawable_methods.h" UIEntity::UIEntity() @@ -362,6 +363,18 @@ PyMethodDef UIEntity::methods[] = { {NULL, NULL, 0, NULL} }; +// Define the PyObjectType alias for the macros +typedef PyUIEntityObject PyObjectType; + +// Combine base methods with entity-specific methods +PyMethodDef UIEntity_all_methods[] = { + UIDRAWABLE_METHODS, + {"at", (PyCFunction)UIEntity::at, METH_O}, + {"index", (PyCFunction)UIEntity::index, METH_NOARGS, "Return the index of this entity in its grid's entity collection"}, + {"die", (PyCFunction)UIEntity::die, METH_NOARGS, "Remove this entity from its grid"}, + {NULL} // Sentinel +}; + PyGetSetDef UIEntity::getsetters[] = { {"draw_pos", (getter)UIEntity::get_position, (setter)UIEntity::set_position, "Entity position (graphically)", (void*)0}, {"pos", (getter)UIEntity::get_position, (setter)UIEntity::set_position, "Entity position (integer grid coordinates)", (void*)1}, @@ -370,6 +383,7 @@ PyGetSetDef UIEntity::getsetters[] = { {"sprite_number", (getter)UIEntity::get_spritenumber, (setter)UIEntity::set_spritenumber, "Sprite index on the texture on the display (deprecated: use sprite_index)", NULL}, {"x", (getter)UIEntity::get_float_member, (setter)UIEntity::set_float_member, "Entity x position", (void*)0}, {"y", (getter)UIEntity::get_float_member, (setter)UIEntity::set_float_member, "Entity y position", (void*)1}, + UIDRAWABLE_GETSETTERS, {NULL} /* Sentinel */ }; diff --git a/src/UIEntity.h b/src/UIEntity.h index 5531390..61a0c79 100644 --- a/src/UIEntity.h +++ b/src/UIEntity.h @@ -68,6 +68,9 @@ public: static PyObject* repr(PyUIEntityObject* self); }; +// Forward declaration of methods array +extern PyMethodDef UIEntity_all_methods[]; + namespace mcrfpydef { static PyTypeObject PyUIEntityType = { .ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0}, @@ -77,7 +80,7 @@ namespace mcrfpydef { .tp_repr = (reprfunc)UIEntity::repr, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, .tp_doc = "UIEntity objects", - .tp_methods = UIEntity::methods, + .tp_methods = UIEntity_all_methods, .tp_getset = UIEntity::getsetters, .tp_init = (initproc)UIEntity::init, .tp_new = PyType_GenericNew, diff --git a/src/UIFrame.cpp b/src/UIFrame.cpp index 2139a54..f744a77 100644 --- a/src/UIFrame.cpp +++ b/src/UIFrame.cpp @@ -7,6 +7,7 @@ #include "UIGrid.h" #include "McRFPy_API.h" #include "PyPositionHelper.h" +#include "UIDrawable_methods.h" UIDrawable* UIFrame::click_at(sf::Vector2f point) { @@ -265,6 +266,15 @@ int UIFrame::set_pos(PyUIFrameObject* self, PyObject* value, void* closure) return 0; } +// Define the PyObjectType alias for the macros +typedef PyUIFrameObject PyObjectType; + +// Method definitions +PyMethodDef UIFrame_methods[] = { + UIDRAWABLE_METHODS, + {NULL} // Sentinel +}; + PyGetSetDef UIFrame::getsetters[] = { {"x", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "X coordinate of top-left corner", (void*)0}, {"y", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "Y coordinate of top-left corner", (void*)1}, @@ -277,6 +287,7 @@ PyGetSetDef UIFrame::getsetters[] = { {"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UIFRAME}, {"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIFRAME}, {"pos", (getter)UIFrame::get_pos, (setter)UIFrame::set_pos, "Position as a Vector", NULL}, + UIDRAWABLE_GETSETTERS, {NULL} }; diff --git a/src/UIFrame.h b/src/UIFrame.h index 204482d..4d7d56e 100644 --- a/src/UIFrame.h +++ b/src/UIFrame.h @@ -61,6 +61,9 @@ public: bool getProperty(const std::string& name, sf::Vector2f& value) const override; }; +// Forward declaration of methods array +extern PyMethodDef UIFrame_methods[]; + namespace mcrfpydef { static PyTypeObject PyUIFrameType = { .ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0}, @@ -79,7 +82,7 @@ namespace mcrfpydef { //.tp_iternext .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = PyDoc_STR("docstring"), - //.tp_methods = PyUIFrame_methods, + .tp_methods = UIFrame_methods, //.tp_members = PyUIFrame_members, .tp_getset = UIFrame::getsetters, //.tp_base = NULL, diff --git a/src/UIGrid.cpp b/src/UIGrid.cpp index ed91056..ad599aa 100644 --- a/src/UIGrid.cpp +++ b/src/UIGrid.cpp @@ -3,6 +3,7 @@ #include "McRFPy_API.h" #include "PyPositionHelper.h" #include +#include "UIDrawable_methods.h" UIGrid::UIGrid() : grid_x(0), grid_y(0), zoom(1.0f), center_x(0.0f), center_y(0.0f), ptex(nullptr) @@ -602,6 +603,15 @@ PyMethodDef UIGrid::methods[] = { {NULL, NULL, 0, NULL} }; +// Define the PyObjectType alias for the macros +typedef PyUIGridObject PyObjectType; + +// Combined methods array +PyMethodDef UIGrid_all_methods[] = { + UIDRAWABLE_METHODS, + {"at", (PyCFunction)UIGrid::py_at, METH_VARARGS | METH_KEYWORDS}, + {NULL} // Sentinel +}; PyGetSetDef UIGrid::getsetters[] = { @@ -627,6 +637,7 @@ PyGetSetDef UIGrid::getsetters[] = { {"texture", (getter)UIGrid::get_texture, NULL, "Texture of the grid", NULL}, //TODO 7DRL-day2-item5 {"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UIGRID}, + UIDRAWABLE_GETSETTERS, {NULL} /* Sentinel */ }; diff --git a/src/UIGrid.h b/src/UIGrid.h index ace9310..00aefb7 100644 --- a/src/UIGrid.h +++ b/src/UIGrid.h @@ -123,6 +123,9 @@ public: }; +// Forward declaration of methods array +extern PyMethodDef UIGrid_all_methods[]; + namespace mcrfpydef { static PyTypeObject PyUIGridType = { .ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0}, @@ -142,7 +145,7 @@ namespace mcrfpydef { //.tp_iternext .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = PyDoc_STR("docstring"), - .tp_methods = UIGrid::methods, + .tp_methods = UIGrid_all_methods, //.tp_members = UIGrid::members, .tp_getset = UIGrid::getsetters, //.tp_base = NULL, diff --git a/src/UISprite.cpp b/src/UISprite.cpp index a56b123..ba3c5fd 100644 --- a/src/UISprite.cpp +++ b/src/UISprite.cpp @@ -2,6 +2,7 @@ #include "GameEngine.h" #include "PyVector.h" #include "PyPositionHelper.h" +#include "UIDrawable_methods.h" UIDrawable* UISprite::click_at(sf::Vector2f point) { @@ -267,6 +268,15 @@ int UISprite::set_pos(PyUISpriteObject* self, PyObject* value, void* closure) return 0; } +// Define the PyObjectType alias for the macros +typedef PyUISpriteObject PyObjectType; + +// Method definitions +PyMethodDef UISprite_methods[] = { + UIDRAWABLE_METHODS, + {NULL} // Sentinel +}; + PyGetSetDef UISprite::getsetters[] = { {"x", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "X coordinate of top-left corner", (void*)0}, {"y", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "Y coordinate of top-left corner", (void*)1}, @@ -279,6 +289,7 @@ PyGetSetDef UISprite::getsetters[] = { {"click", (getter)UIDrawable::get_click, (setter)UIDrawable::set_click, "Object called with (x, y, button) when clicked", (void*)PyObjectsEnum::UISPRITE}, {"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UISPRITE}, {"pos", (getter)UISprite::get_pos, (setter)UISprite::set_pos, "Position as a Vector", NULL}, + UIDRAWABLE_GETSETTERS, {NULL} }; diff --git a/src/UISprite.h b/src/UISprite.h index a036791..8043282 100644 --- a/src/UISprite.h +++ b/src/UISprite.h @@ -68,6 +68,9 @@ public: }; +// Forward declaration of methods array +extern PyMethodDef UISprite_methods[]; + namespace mcrfpydef { static PyTypeObject PyUISpriteType = { .ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0}, @@ -88,7 +91,7 @@ namespace mcrfpydef { //.tp_iternext .tp_flags = Py_TPFLAGS_DEFAULT, .tp_doc = PyDoc_STR("docstring"), - //.tp_methods = PyUIFrame_methods, + .tp_methods = UISprite_methods, //.tp_members = PyUIFrame_members, .tp_getset = UISprite::getsetters, //.tp_base = NULL, diff --git a/src/main.cpp b/src/main.cpp index e0e9835..df6aaf3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -41,6 +41,9 @@ int run_game_engine(const McRogueFaceConfig& config) { GameEngine g(config); g.run(); + if (Py_IsInitialized()) { + McRFPy_API::api_shutdown(); + } return 0; } @@ -102,7 +105,7 @@ int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv // Continue to interactive mode below } else { int result = PyRun_SimpleString(config.python_command.c_str()); - Py_Finalize(); + McRFPy_API::api_shutdown(); delete engine; return result; } @@ -121,7 +124,7 @@ int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv run_module_code += "runpy.run_module('" + config.python_module + "', run_name='__main__', alter_sys=True)\n"; int result = PyRun_SimpleString(run_module_code.c_str()); - Py_Finalize(); + McRFPy_API::api_shutdown(); delete engine; return result; } @@ -179,7 +182,7 @@ int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv // Run the game engine after script execution engine->run(); - Py_Finalize(); + McRFPy_API::api_shutdown(); delete engine; return result; } @@ -187,14 +190,14 @@ int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv // Interactive Python interpreter (only if explicitly requested with -i) Py_InspectFlag = 1; PyRun_InteractiveLoop(stdin, ""); - Py_Finalize(); + McRFPy_API::api_shutdown(); delete engine; return 0; } else if (!config.exec_scripts.empty()) { // With --exec, run the game engine after scripts execute engine->run(); - Py_Finalize(); + McRFPy_API::api_shutdown(); delete engine; return 0; } diff --git a/tests/run_all_tests.sh b/tests/run_all_tests.sh new file mode 100755 index 0000000..85e7c7f --- /dev/null +++ b/tests/run_all_tests.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# Run all tests and check for failures + +TESTS=( + "test_click_init.py" + "test_drawable_base.py" + "test_frame_children.py" + "test_sprite_texture_swap.py" + "test_timer_object.py" + "test_timer_object_fixed.py" +) + +echo "Running all tests..." +echo "====================" + +failed=0 +passed=0 + +for test in "${TESTS[@]}"; do + echo -n "Running $test... " + if timeout 5 ./mcrogueface --headless --exec ../tests/$test > /tmp/test_output.txt 2>&1; then + if grep -q "FAIL\|✗" /tmp/test_output.txt; then + echo "FAILED" + echo "Output:" + cat /tmp/test_output.txt | grep -E "✗|FAIL|Error|error" | head -10 + ((failed++)) + else + echo "PASSED" + ((passed++)) + fi + else + echo "TIMEOUT/CRASH" + ((failed++)) + fi +done + +echo "====================" +echo "Total: $((passed + failed)) tests" +echo "Passed: $passed" +echo "Failed: $failed" + +exit $failed \ No newline at end of file