feat: stabilize test suite and add UIDrawable methods
- Add visible, opacity properties to all UI classes (#87, #88) - Add get_bounds(), move(), resize() methods to UIDrawable (#89, #98) - Create UIDrawable_methods.h with template implementations - Fix test termination issues - all tests now exit properly - Fix test_sprite_texture_swap.py click handler signature - Fix test_drawable_base.py segfault in headless mode - Convert audio objects to pointers for cleanup (OpenAL warning persists) - Remove debug print statements from UICaption - Special handling for UIEntity to delegate drawable methods to sprite All test files are now "airtight" - they complete successfully, terminate on their own, and handle edge cases properly. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
cc9b5c8f88
commit
ee6550bf63
|
@ -11,9 +11,9 @@
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
std::vector<sf::SoundBuffer> McRFPy_API::soundbuffers;
|
std::vector<sf::SoundBuffer>* McRFPy_API::soundbuffers = nullptr;
|
||||||
sf::Music McRFPy_API::music;
|
sf::Music* McRFPy_API::music = nullptr;
|
||||||
sf::Sound McRFPy_API::sfx;
|
sf::Sound* McRFPy_API::sfx = nullptr;
|
||||||
|
|
||||||
std::shared_ptr<PyFont> McRFPy_API::default_font;
|
std::shared_ptr<PyFont> McRFPy_API::default_font;
|
||||||
std::shared_ptr<PyTexture> McRFPy_API::default_texture;
|
std::shared_ptr<PyTexture> McRFPy_API::default_texture;
|
||||||
|
@ -356,6 +356,23 @@ void McRFPy_API::executeScript(std::string filename)
|
||||||
|
|
||||||
void McRFPy_API::api_shutdown()
|
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();
|
Py_Finalize();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,25 +407,29 @@ PyObject* McRFPy_API::_refreshFov(PyObject* self, PyObject* args) {
|
||||||
PyObject* McRFPy_API::_createSoundBuffer(PyObject* self, PyObject* args) {
|
PyObject* McRFPy_API::_createSoundBuffer(PyObject* self, PyObject* args) {
|
||||||
const char *fn_cstr;
|
const char *fn_cstr;
|
||||||
if (!PyArg_ParseTuple(args, "s", &fn_cstr)) return NULL;
|
if (!PyArg_ParseTuple(args, "s", &fn_cstr)) return NULL;
|
||||||
|
// Initialize soundbuffers if needed
|
||||||
|
if (!McRFPy_API::soundbuffers) {
|
||||||
|
McRFPy_API::soundbuffers = new std::vector<sf::SoundBuffer>();
|
||||||
|
}
|
||||||
auto b = sf::SoundBuffer();
|
auto b = sf::SoundBuffer();
|
||||||
b.loadFromFile(fn_cstr);
|
b.loadFromFile(fn_cstr);
|
||||||
McRFPy_API::soundbuffers.push_back(b);
|
McRFPy_API::soundbuffers->push_back(b);
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* McRFPy_API::_loadMusic(PyObject* self, PyObject* args) {
|
PyObject* McRFPy_API::_loadMusic(PyObject* self, PyObject* args) {
|
||||||
const char *fn_cstr;
|
const char *fn_cstr;
|
||||||
PyObject* loop_obj;
|
PyObject* loop_obj = Py_False;
|
||||||
if (!PyArg_ParseTuple(args, "s|O", &fn_cstr, &loop_obj)) return NULL;
|
if (!PyArg_ParseTuple(args, "s|O", &fn_cstr, &loop_obj)) return NULL;
|
||||||
McRFPy_API::music.stop();
|
// Initialize music if needed
|
||||||
// get params for sf::Music initialization
|
if (!McRFPy_API::music) {
|
||||||
//sf::InputSoundFile file;
|
McRFPy_API::music = new sf::Music();
|
||||||
//file.openFromFile(fn_cstr);
|
}
|
||||||
McRFPy_API::music.openFromFile(fn_cstr);
|
McRFPy_API::music->stop();
|
||||||
McRFPy_API::music.setLoop(PyObject_IsTrue(loop_obj));
|
McRFPy_API::music->openFromFile(fn_cstr);
|
||||||
//McRFPy_API::music.initialize(file.getChannelCount(), file.getSampleRate());
|
McRFPy_API::music->setLoop(PyObject_IsTrue(loop_obj));
|
||||||
McRFPy_API::music.play();
|
McRFPy_API::music->play();
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
return 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) {
|
PyObject* McRFPy_API::_setMusicVolume(PyObject* self, PyObject* args) {
|
||||||
int vol;
|
int vol;
|
||||||
if (!PyArg_ParseTuple(args, "i", &vol)) return NULL;
|
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);
|
Py_INCREF(Py_None);
|
||||||
return 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) {
|
PyObject* McRFPy_API::_setSoundVolume(PyObject* self, PyObject* args) {
|
||||||
float vol;
|
float vol;
|
||||||
if (!PyArg_ParseTuple(args, "f", &vol)) return NULL;
|
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);
|
Py_INCREF(Py_None);
|
||||||
return 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) {
|
PyObject* McRFPy_API::_playSound(PyObject* self, PyObject* args) {
|
||||||
float index;
|
float index;
|
||||||
if (!PyArg_ParseTuple(args, "f", &index)) return NULL;
|
if (!PyArg_ParseTuple(args, "f", &index)) return NULL;
|
||||||
if (index >= McRFPy_API::soundbuffers.size()) return NULL;
|
if (!McRFPy_API::soundbuffers || index >= McRFPy_API::soundbuffers->size()) return NULL;
|
||||||
McRFPy_API::sfx.stop();
|
if (!McRFPy_API::sfx) {
|
||||||
McRFPy_API::sfx.setBuffer(McRFPy_API::soundbuffers[index]);
|
McRFPy_API::sfx = new sf::Sound();
|
||||||
McRFPy_API::sfx.play();
|
}
|
||||||
|
McRFPy_API::sfx->stop();
|
||||||
|
McRFPy_API::sfx->setBuffer((*McRFPy_API::soundbuffers)[index]);
|
||||||
|
McRFPy_API::sfx->play();
|
||||||
Py_INCREF(Py_None);
|
Py_INCREF(Py_None);
|
||||||
return Py_None;
|
return Py_None;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* McRFPy_API::_getMusicVolume(PyObject* self, PyObject* args) {
|
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) {
|
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
|
// Removed deprecated player_input, computerTurn, playerTurn functions
|
||||||
|
|
|
@ -36,9 +36,9 @@ public:
|
||||||
static void REPL_device(FILE * fp, const char *filename);
|
static void REPL_device(FILE * fp, const char *filename);
|
||||||
static void REPL();
|
static void REPL();
|
||||||
|
|
||||||
static std::vector<sf::SoundBuffer> soundbuffers;
|
static std::vector<sf::SoundBuffer>* soundbuffers;
|
||||||
static sf::Music music;
|
static sf::Music* music;
|
||||||
static sf::Sound sfx;
|
static sf::Sound* sfx;
|
||||||
|
|
||||||
|
|
||||||
static PyObject* _createSoundBuffer(PyObject*, PyObject*);
|
static PyObject* _createSoundBuffer(PyObject*, PyObject*);
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "PyVector.h"
|
#include "PyVector.h"
|
||||||
#include "PyFont.h"
|
#include "PyFont.h"
|
||||||
#include "PyPositionHelper.h"
|
#include "PyPositionHelper.h"
|
||||||
|
#include "UIDrawable_methods.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
UICaption::UICaption()
|
UICaption::UICaption()
|
||||||
|
@ -163,7 +164,6 @@ int UICaption::set_color_member(PyUICaptionObject* self, PyObject* value, void*
|
||||||
// get value from mcrfpy.Color instance
|
// get value from mcrfpy.Color instance
|
||||||
auto c = ((PyColorObject*)value)->data;
|
auto c = ((PyColorObject*)value)->data;
|
||||||
r = c.r; g = c.g; b = c.b; a = c.a;
|
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)
|
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?
|
//TODO: evaluate use of Resources::caption_buffer... can't I do this with a std::string?
|
||||||
PyObject* UICaption::get_text(PyUICaptionObject* self, void* closure)
|
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},
|
{"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},
|
{"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},
|
{"z_index", (getter)UIDrawable::get_int, (setter)UIDrawable::set_int, "Z-order for rendering (lower values rendered first)", (void*)PyObjectsEnum::UICAPTION},
|
||||||
|
UIDRAWABLE_GETSETTERS,
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,8 @@ public:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
extern PyMethodDef UICaption_methods[];
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
static PyTypeObject PyUICaptionType = {
|
static PyTypeObject PyUICaptionType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||||
|
@ -62,7 +64,7 @@ namespace mcrfpydef {
|
||||||
//.tp_iternext
|
//.tp_iternext
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("docstring"),
|
.tp_doc = PyDoc_STR("docstring"),
|
||||||
//.tp_methods = PyUIFrame_methods,
|
.tp_methods = UICaption_methods,
|
||||||
//.tp_members = PyUIFrame_members,
|
//.tp_members = PyUIFrame_members,
|
||||||
.tp_getset = UICaption::getsetters,
|
.tp_getset = UICaption::getsetters,
|
||||||
//.tp_base = NULL,
|
//.tp_base = NULL,
|
||||||
|
|
|
@ -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<typename T>
|
||||||
|
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<typename T>
|
||||||
|
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<typename T>
|
||||||
|
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<PyObjectType>, METH_NOARGS, \
|
||||||
|
"Get bounding box as (x, y, width, height)"}, \
|
||||||
|
{"move", (PyCFunction)UIDrawable_move<PyObjectType>, METH_VARARGS, \
|
||||||
|
"Move by relative offset (dx, dy)"}, \
|
||||||
|
{"resize", (PyCFunction)UIDrawable_resize<PyObjectType>, METH_VARARGS, \
|
||||||
|
"Resize to new dimensions (width, height)"}
|
||||||
|
|
||||||
|
// Common getsetters for UIDrawable properties
|
||||||
|
#define UIDRAWABLE_GETSETTERS \
|
||||||
|
{"visible", (getter)UIDrawable_get_visible<PyObjectType>, (setter)UIDrawable_set_visible<PyObjectType>, \
|
||||||
|
"Whether the object is visible", NULL}, \
|
||||||
|
{"opacity", (getter)UIDrawable_get_opacity<PyObjectType>, (setter)UIDrawable_set_opacity<PyObjectType>, \
|
||||||
|
"Opacity level (0.0 = transparent, 1.0 = opaque)", NULL}
|
||||||
|
|
||||||
|
// Visible property getter (new for #87)
|
||||||
|
template<typename T>
|
||||||
|
static PyObject* UIDrawable_get_visible(T* self, void* closure)
|
||||||
|
{
|
||||||
|
return PyBool_FromLong(self->data->visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Visible property setter (new for #87)
|
||||||
|
template<typename T>
|
||||||
|
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<typename T>
|
||||||
|
static PyObject* UIDrawable_get_opacity(T* self, void* closure)
|
||||||
|
{
|
||||||
|
return PyFloat_FromDouble(self->data->opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Opacity property setter (new for #88)
|
||||||
|
template<typename T>
|
||||||
|
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>(PyUIEntityObject* self, void* closure)
|
||||||
|
{
|
||||||
|
return PyBool_FromLong(self->data->sprite.visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline int UIDrawable_set_visible<PyUIEntityObject>(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>(PyUIEntityObject* self, void* closure)
|
||||||
|
{
|
||||||
|
return PyFloat_FromDouble(self->data->sprite.opacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
inline int UIDrawable_set_opacity<PyUIEntityObject>(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>(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>(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>(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;
|
||||||
|
}
|
|
@ -5,6 +5,7 @@
|
||||||
#include "PyObjectUtils.h"
|
#include "PyObjectUtils.h"
|
||||||
#include "PyVector.h"
|
#include "PyVector.h"
|
||||||
#include "PyPositionHelper.h"
|
#include "PyPositionHelper.h"
|
||||||
|
#include "UIDrawable_methods.h"
|
||||||
|
|
||||||
|
|
||||||
UIEntity::UIEntity()
|
UIEntity::UIEntity()
|
||||||
|
@ -362,6 +363,18 @@ PyMethodDef UIEntity::methods[] = {
|
||||||
{NULL, NULL, 0, NULL}
|
{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[] = {
|
PyGetSetDef UIEntity::getsetters[] = {
|
||||||
{"draw_pos", (getter)UIEntity::get_position, (setter)UIEntity::set_position, "Entity position (graphically)", (void*)0},
|
{"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},
|
{"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},
|
{"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},
|
{"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},
|
{"y", (getter)UIEntity::get_float_member, (setter)UIEntity::set_float_member, "Entity y position", (void*)1},
|
||||||
|
UIDRAWABLE_GETSETTERS,
|
||||||
{NULL} /* Sentinel */
|
{NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,9 @@ public:
|
||||||
static PyObject* repr(PyUIEntityObject* self);
|
static PyObject* repr(PyUIEntityObject* self);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Forward declaration of methods array
|
||||||
|
extern PyMethodDef UIEntity_all_methods[];
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
static PyTypeObject PyUIEntityType = {
|
static PyTypeObject PyUIEntityType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
.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_repr = (reprfunc)UIEntity::repr,
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
||||||
.tp_doc = "UIEntity objects",
|
.tp_doc = "UIEntity objects",
|
||||||
.tp_methods = UIEntity::methods,
|
.tp_methods = UIEntity_all_methods,
|
||||||
.tp_getset = UIEntity::getsetters,
|
.tp_getset = UIEntity::getsetters,
|
||||||
.tp_init = (initproc)UIEntity::init,
|
.tp_init = (initproc)UIEntity::init,
|
||||||
.tp_new = PyType_GenericNew,
|
.tp_new = PyType_GenericNew,
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PyPositionHelper.h"
|
#include "PyPositionHelper.h"
|
||||||
|
#include "UIDrawable_methods.h"
|
||||||
|
|
||||||
UIDrawable* UIFrame::click_at(sf::Vector2f point)
|
UIDrawable* UIFrame::click_at(sf::Vector2f point)
|
||||||
{
|
{
|
||||||
|
@ -265,6 +266,15 @@ int UIFrame::set_pos(PyUIFrameObject* self, PyObject* value, void* closure)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Define the PyObjectType alias for the macros
|
||||||
|
typedef PyUIFrameObject PyObjectType;
|
||||||
|
|
||||||
|
// Method definitions
|
||||||
|
PyMethodDef UIFrame_methods[] = {
|
||||||
|
UIDRAWABLE_METHODS,
|
||||||
|
{NULL} // Sentinel
|
||||||
|
};
|
||||||
|
|
||||||
PyGetSetDef UIFrame::getsetters[] = {
|
PyGetSetDef UIFrame::getsetters[] = {
|
||||||
{"x", (getter)UIFrame::get_float_member, (setter)UIFrame::set_float_member, "X coordinate of top-left corner", (void*)0},
|
{"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},
|
{"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},
|
{"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},
|
{"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},
|
{"pos", (getter)UIFrame::get_pos, (setter)UIFrame::set_pos, "Position as a Vector", NULL},
|
||||||
|
UIDRAWABLE_GETSETTERS,
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,9 @@ public:
|
||||||
bool getProperty(const std::string& name, sf::Vector2f& value) const override;
|
bool getProperty(const std::string& name, sf::Vector2f& value) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Forward declaration of methods array
|
||||||
|
extern PyMethodDef UIFrame_methods[];
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
static PyTypeObject PyUIFrameType = {
|
static PyTypeObject PyUIFrameType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||||
|
@ -79,7 +82,7 @@ namespace mcrfpydef {
|
||||||
//.tp_iternext
|
//.tp_iternext
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("docstring"),
|
.tp_doc = PyDoc_STR("docstring"),
|
||||||
//.tp_methods = PyUIFrame_methods,
|
.tp_methods = UIFrame_methods,
|
||||||
//.tp_members = PyUIFrame_members,
|
//.tp_members = PyUIFrame_members,
|
||||||
.tp_getset = UIFrame::getsetters,
|
.tp_getset = UIFrame::getsetters,
|
||||||
//.tp_base = NULL,
|
//.tp_base = NULL,
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PyPositionHelper.h"
|
#include "PyPositionHelper.h"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include "UIDrawable_methods.h"
|
||||||
|
|
||||||
UIGrid::UIGrid()
|
UIGrid::UIGrid()
|
||||||
: grid_x(0), grid_y(0), zoom(1.0f), center_x(0.0f), center_y(0.0f), ptex(nullptr)
|
: 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}
|
{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[] = {
|
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
|
{"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},
|
{"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 */
|
{NULL} /* Sentinel */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -123,6 +123,9 @@ public:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Forward declaration of methods array
|
||||||
|
extern PyMethodDef UIGrid_all_methods[];
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
static PyTypeObject PyUIGridType = {
|
static PyTypeObject PyUIGridType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||||
|
@ -142,7 +145,7 @@ namespace mcrfpydef {
|
||||||
//.tp_iternext
|
//.tp_iternext
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("docstring"),
|
.tp_doc = PyDoc_STR("docstring"),
|
||||||
.tp_methods = UIGrid::methods,
|
.tp_methods = UIGrid_all_methods,
|
||||||
//.tp_members = UIGrid::members,
|
//.tp_members = UIGrid::members,
|
||||||
.tp_getset = UIGrid::getsetters,
|
.tp_getset = UIGrid::getsetters,
|
||||||
//.tp_base = NULL,
|
//.tp_base = NULL,
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
#include "PyVector.h"
|
#include "PyVector.h"
|
||||||
#include "PyPositionHelper.h"
|
#include "PyPositionHelper.h"
|
||||||
|
#include "UIDrawable_methods.h"
|
||||||
|
|
||||||
UIDrawable* UISprite::click_at(sf::Vector2f point)
|
UIDrawable* UISprite::click_at(sf::Vector2f point)
|
||||||
{
|
{
|
||||||
|
@ -267,6 +268,15 @@ int UISprite::set_pos(PyUISpriteObject* self, PyObject* value, void* closure)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Define the PyObjectType alias for the macros
|
||||||
|
typedef PyUISpriteObject PyObjectType;
|
||||||
|
|
||||||
|
// Method definitions
|
||||||
|
PyMethodDef UISprite_methods[] = {
|
||||||
|
UIDRAWABLE_METHODS,
|
||||||
|
{NULL} // Sentinel
|
||||||
|
};
|
||||||
|
|
||||||
PyGetSetDef UISprite::getsetters[] = {
|
PyGetSetDef UISprite::getsetters[] = {
|
||||||
{"x", (getter)UISprite::get_float_member, (setter)UISprite::set_float_member, "X coordinate of top-left corner", (void*)0},
|
{"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},
|
{"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},
|
{"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},
|
{"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},
|
{"pos", (getter)UISprite::get_pos, (setter)UISprite::set_pos, "Position as a Vector", NULL},
|
||||||
|
UIDRAWABLE_GETSETTERS,
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,9 @@ public:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Forward declaration of methods array
|
||||||
|
extern PyMethodDef UISprite_methods[];
|
||||||
|
|
||||||
namespace mcrfpydef {
|
namespace mcrfpydef {
|
||||||
static PyTypeObject PyUISpriteType = {
|
static PyTypeObject PyUISpriteType = {
|
||||||
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
.ob_base = {.ob_base = {.ob_refcnt = 1, .ob_type = NULL}, .ob_size = 0},
|
||||||
|
@ -88,7 +91,7 @@ namespace mcrfpydef {
|
||||||
//.tp_iternext
|
//.tp_iternext
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("docstring"),
|
.tp_doc = PyDoc_STR("docstring"),
|
||||||
//.tp_methods = PyUIFrame_methods,
|
.tp_methods = UISprite_methods,
|
||||||
//.tp_members = PyUIFrame_members,
|
//.tp_members = PyUIFrame_members,
|
||||||
.tp_getset = UISprite::getsetters,
|
.tp_getset = UISprite::getsetters,
|
||||||
//.tp_base = NULL,
|
//.tp_base = NULL,
|
||||||
|
|
13
src/main.cpp
13
src/main.cpp
|
@ -41,6 +41,9 @@ int run_game_engine(const McRogueFaceConfig& config)
|
||||||
{
|
{
|
||||||
GameEngine g(config);
|
GameEngine g(config);
|
||||||
g.run();
|
g.run();
|
||||||
|
if (Py_IsInitialized()) {
|
||||||
|
McRFPy_API::api_shutdown();
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,7 +105,7 @@ int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv
|
||||||
// Continue to interactive mode below
|
// Continue to interactive mode below
|
||||||
} else {
|
} else {
|
||||||
int result = PyRun_SimpleString(config.python_command.c_str());
|
int result = PyRun_SimpleString(config.python_command.c_str());
|
||||||
Py_Finalize();
|
McRFPy_API::api_shutdown();
|
||||||
delete engine;
|
delete engine;
|
||||||
return result;
|
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";
|
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());
|
int result = PyRun_SimpleString(run_module_code.c_str());
|
||||||
Py_Finalize();
|
McRFPy_API::api_shutdown();
|
||||||
delete engine;
|
delete engine;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -179,7 +182,7 @@ int run_python_interpreter(const McRogueFaceConfig& config, int argc, char* argv
|
||||||
// Run the game engine after script execution
|
// Run the game engine after script execution
|
||||||
engine->run();
|
engine->run();
|
||||||
|
|
||||||
Py_Finalize();
|
McRFPy_API::api_shutdown();
|
||||||
delete engine;
|
delete engine;
|
||||||
return result;
|
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)
|
// Interactive Python interpreter (only if explicitly requested with -i)
|
||||||
Py_InspectFlag = 1;
|
Py_InspectFlag = 1;
|
||||||
PyRun_InteractiveLoop(stdin, "<stdin>");
|
PyRun_InteractiveLoop(stdin, "<stdin>");
|
||||||
Py_Finalize();
|
McRFPy_API::api_shutdown();
|
||||||
delete engine;
|
delete engine;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
else if (!config.exec_scripts.empty()) {
|
else if (!config.exec_scripts.empty()) {
|
||||||
// With --exec, run the game engine after scripts execute
|
// With --exec, run the game engine after scripts execute
|
||||||
engine->run();
|
engine->run();
|
||||||
Py_Finalize();
|
McRFPy_API::api_shutdown();
|
||||||
delete engine;
|
delete engine;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
Loading…
Reference in New Issue