Compare commits

..

4 Commits

Author SHA1 Message Date
John McCardle 43fac8f4f3 Typo in UIFrame repr 2024-04-20 18:32:52 -04:00
John McCardle 3fd5ad93e2 Add UIGridPoint and UIGridPointState repr 2024-04-20 18:32:30 -04:00
John McCardle 03376897b8 Add UIGrid repr 2024-04-20 18:32:17 -04:00
John McCardle 48af072a33 Add UIEntity repr 2024-04-20 18:32:05 -04:00
36 changed files with 637 additions and 1779 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 674 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -9,10 +9,10 @@ GameEngine::GameEngine()
{
Resources::font.loadFromFile("./assets/JetbrainsMono.ttf");
Resources::game = this;
window_title = "Crypt of Sokoban - 7DRL 2025, McRogueface Engine";
window_title = "McRogueFace - 7DRL 2024 Engine Demo";
window.create(sf::VideoMode(1024, 768), window_title, sf::Style::Titlebar | sf::Style::Close);
visible = window.getDefaultView();
window.setFramerateLimit(60);
window.setFramerateLimit(30);
scene = "uitest";
scenes["uitest"] = new UITestScene(this);
@ -63,10 +63,7 @@ void GameEngine::run()
currentFrame++;
frameTime = clock.restart().asSeconds();
fps = 1 / frameTime;
int whole_fps = (int)fps;
int tenth_fps = int(fps * 100) % 10;
//window.setTitle(window_title + " " + std::to_string(fps) + " FPS");
window.setTitle(window_title + " " + std::to_string(whole_fps) + "." + std::to_string(tenth_fps) + " FPS");
window.setTitle(window_title + " " + std::to_string(fps) + " FPS");
}
}

View File

@ -251,7 +251,7 @@ PyObject* McRFPy_API::_registerInputAction(PyObject *self, PyObject *args)
std::cout << "Unregistering\n";
success = game->currentScene()->unregisterActionInjected(action_code, std::string(actionstr) + "_py");
} else {
std::cout << "Registering " << actionstr << "_py to " << action_code << "\n";
std::cout << "Registering" << actionstr << "_py to " << action_code << "\n";
success = game->currentScene()->registerActionInjected(action_code, std::string(actionstr) + "_py");
}

View File

@ -11,8 +11,7 @@ PyScene::PyScene(GameEngine* g) : Scene(g)
registerAction(ActionCode::MOUSEWHEEL + ActionCode::WHEEL_DEL, "wheel_up");
registerAction(ActionCode::MOUSEWHEEL + ActionCode::WHEEL_NEG + ActionCode::WHEEL_DEL, "wheel_down");
// console (` / ~ key) - don't hard code.
//registerAction(ActionCode::KEY + sf::Keyboard::Grave, "debug_menu");
registerAction(ActionCode::KEY + sf::Keyboard::Grave, "debug_menu");
}
void PyScene::update()

View File

@ -28,6 +28,7 @@ sf::Sprite PyTexture::sprite(int index, sf::Vector2f pos, sf::Vector2f s)
PyObject* PyTexture::pyObject()
{
std::cout << "Find type" << std::endl;
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Texture");
PyObject* obj = PyTexture::pynew(type, Py_None, Py_None);

View File

@ -109,16 +109,3 @@ int PyVector::set_member(PyObject* obj, PyObject* value, void* closure)
// TODO
return 0;
}
PyVectorObject* PyVector::from_arg(PyObject* args)
{
auto type = (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "Vector");
if (PyObject_IsInstance(args, (PyObject*)type)) return (PyVectorObject*)args;
auto obj = (PyVectorObject*)type->tp_alloc(type, 0);
int err = init(obj, args, NULL);
if (err) {
Py_DECREF(obj);
return NULL;
}
return obj;
}

View File

@ -1,7 +1,6 @@
#pragma once
#include "Common.h"
#include "Python.h"
#include "McRFPy_API.h"
typedef struct {
PyObject_HEAD
@ -23,7 +22,6 @@ public:
static PyObject* pynew(PyTypeObject* type, PyObject* args=NULL, PyObject* kwds=NULL);
static PyObject* get_member(PyObject*, void*);
static int set_member(PyObject*, PyObject*, void*);
static PyVectorObject* from_arg(PyObject*);
static PyGetSetDef getsetters[];
};

View File

@ -13,11 +13,10 @@ UIDrawable* UICaption::click_at(sf::Vector2f point)
return NULL;
}
void UICaption::render(sf::Vector2f offset, sf::RenderTarget& target)
void UICaption::render(sf::Vector2f offset)
{
text.move(offset);
//Resources::game->getWindow().draw(text);
target.draw(text);
Resources::game->getWindow().draw(text);
text.move(-offset);
}
@ -223,33 +222,20 @@ PyObject* UICaption::repr(PyUICaptionObject* self)
int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
{
using namespace mcrfpydef;
// Constructor switch to Vector position
//static const char* keywords[] = { "x", "y", "text", "font", "fill_color", "outline_color", "outline", nullptr };
//float x = 0.0f, y = 0.0f, outline = 0.0f;
static const char* keywords[] = { "pos", "text", "font", "fill_color", "outline_color", "outline", nullptr };
PyObject* pos;
float outline = 0.0f;
static const char* keywords[] = { "x", "y", "text", "font", "fill_color", "outline_color", "outline", nullptr };
float x = 0.0f, y = 0.0f, outline = 0.0f;
char* text;
PyObject* font=NULL, *fill_color=NULL, *outline_color=NULL;
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf",
// const_cast<char**>(keywords), &x, &y, &text, &font, &fill_color, &outline_color, &outline))
if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|zOOOf",
const_cast<char**>(keywords), &pos, &text, &font, &fill_color, &outline_color, &outline))
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf",
const_cast<char**>(keywords), &x, &y, &text, &font, &fill_color, &outline_color, &outline))
{
return -1;
}
PyVectorObject* pos_result = PyVector::from_arg(pos);
if (!pos_result)
{
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
return -1;
}
self->data->text.setPosition(pos_result->data);
// check types for font, fill_color, outline_color
//std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl;
std::cout << PyUnicode_AsUTF8(PyObject_Repr(font)) << std::endl;
if (font != NULL && !PyObject_IsInstance(font, PyObject_GetAttrString(McRFPy_API::mcrf_module, "Font")/*(PyObject*)&PyFontType)*/)){
PyErr_SetString(PyExc_TypeError, "font must be a mcrfpy.Font instance");
return -1;
@ -265,6 +251,7 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
//self->data->text.setFont(Resources::game->getFont());
}
self->data->text.setPosition(sf::Vector2f(x, y));
self->data->text.setString((std::string)text);
self->data->text.setOutlineThickness(outline);
if (fill_color) {

View File

@ -7,7 +7,7 @@ class UICaption: public UIDrawable
{
public:
sf::Text text;
void render(sf::Vector2f, sf::RenderTarget&) override final;
void render(sf::Vector2f) override final;
PyObjectsEnum derived_type() override final;
virtual UIDrawable* click_at(sf::Vector2f point) override final;

View File

@ -3,7 +3,6 @@
#include "UICaption.h"
#include "UISprite.h"
#include "UIGrid.h"
#include "GameEngine.h"
UIDrawable::UIDrawable() { click_callable = NULL; }
@ -14,7 +13,7 @@ void UIDrawable::click_unregister()
void UIDrawable::render()
{
render(sf::Vector2f(), Resources::game->getWindow());
render(sf::Vector2f());
}
PyObject* UIDrawable::get_click(PyObject* self, void* closure) {

View File

@ -28,8 +28,7 @@ class UIDrawable
{
public:
void render();
//virtual void render(sf::Vector2f) = 0;
virtual void render(sf::Vector2f, sf::RenderTarget&) = 0;
virtual void render(sf::Vector2f) = 0;
virtual PyObjectsEnum derived_type() = 0;
// Mouse input handling - callable object, methods to find event's destination

View File

@ -34,30 +34,18 @@ PyObject* UIEntity::at(PyUIEntityObject* self, PyObject* o) {
}
int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
//static const char* keywords[] = { "x", "y", "texture", "sprite_index", "grid", nullptr };
//float x = 0.0f, y = 0.0f, scale = 1.0f;
static const char* keywords[] = { "pos", "texture", "sprite_index", "grid", nullptr };
PyObject* pos;
float scale = 1.0f;
static const char* keywords[] = { "x", "y", "texture", "sprite_index", "grid", nullptr };
float x = 0.0f, y = 0.0f, scale = 1.0f;
int sprite_index = -1;
PyObject* texture = NULL;
PyObject* grid = NULL;
//if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O",
// const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &grid))
if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOi|O",
const_cast<char**>(keywords), &pos, &texture, &sprite_index, &grid))
if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O",
const_cast<char**>(keywords), &x, &y, &texture, &sprite_index, &grid))
{
return -1;
}
PyVectorObject* pos_result = PyVector::from_arg(pos);
if (!pos_result)
{
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
return -1;
}
// check types for texture
//
// Set Texture
@ -87,7 +75,7 @@ int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
// TODO - PyTextureObjects and IndexTextures are a little bit of a mess with shared/unshared pointers
self->data->sprite = UISprite(pytexture->data, sprite_index, sf::Vector2f(0,0), 1.0);
self->data->position = pos_result->data;
self->data->position = sf::Vector2f(x, y);
if (grid != NULL) {
PyUIGridObject* pygrid = (PyUIGridObject*)grid;
self->data->grid = pygrid->data;
@ -107,10 +95,6 @@ PyObject* sfVector2f_to_PyObject(sf::Vector2f vector) {
return Py_BuildValue("(ff)", vector.x, vector.y);
}
PyObject* sfVector2i_to_PyObject(sf::Vector2i vector) {
return Py_BuildValue("(ii)", vector.x, vector.y);
}
sf::Vector2f PyObject_to_sfVector2f(PyObject* obj) {
float x, y;
if (!PyArg_ParseTuple(obj, "ff", &x, &y)) {
@ -119,14 +103,6 @@ sf::Vector2f PyObject_to_sfVector2f(PyObject* obj) {
return sf::Vector2f(x, y);
}
sf::Vector2i PyObject_to_sfVector2i(PyObject* obj) {
int x, y;
if (!PyArg_ParseTuple(obj, "ii", &x, &y)) {
return sf::Vector2i(); // TODO / reconsider this default: Return default vector on parse error
}
return sf::Vector2i(x, y);
}
// TODO - deprecate / remove this helper
PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state) {
return PyObject_New(PyObject, (PyTypeObject*)PyObject_GetAttrString(McRFPy_API::mcrf_module, "GridPointState"));
@ -149,19 +125,11 @@ PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>&
}
PyObject* UIEntity::get_position(PyUIEntityObject* self, void* closure) {
if (reinterpret_cast<long>(closure) == 0) {
return sfVector2f_to_PyObject(self->data->position);
} else {
return sfVector2i_to_PyObject(self->data->collision_pos);
}
return sfVector2f_to_PyObject(self->data->position);
}
int UIEntity::set_position(PyUIEntityObject* self, PyObject* value, void* closure) {
if (reinterpret_cast<long>(closure) == 0) {
self->data->position = PyObject_to_sfVector2f(value);
} else {
self->data->collision_pos = PyObject_to_sfVector2i(value);
}
self->data->position = PyObject_to_sfVector2f(value);
return 0;
}
@ -190,8 +158,7 @@ PyMethodDef UIEntity::methods[] = {
};
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},
{"position", (getter)UIEntity::get_position, (setter)UIEntity::set_position, "Entity position", NULL},
{"gridstate", (getter)UIEntity::get_gridstate, NULL, "Grid point states for the entity", NULL},
{"sprite_number", (getter)UIEntity::get_spritenumber, (setter)UIEntity::set_spritenumber, "Sprite number (index) on the texture on the display", NULL},
{NULL} /* Sentinel */

View File

@ -40,8 +40,7 @@ public:
std::vector<UIGridPointState> gridstate;
UISprite sprite;
sf::Vector2f position; //(x,y) in grid coordinates; float for animation
sf::Vector2i collision_pos; //(x, y) in grid coordinates: int for collision
//void render(sf::Vector2f); //override final;
void render(sf::Vector2f); //override final;
UIEntity();
UIEntity(UIGrid&);
@ -66,7 +65,7 @@ namespace mcrfpydef {
.tp_basicsize = sizeof(PyUIEntityObject),
.tp_itemsize = 0,
.tp_repr = (reprfunc)UIEntity::repr,
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_doc = "UIEntity objects",
.tp_methods = UIEntity::methods,
.tp_getset = UIEntity::getsetters,

View File

@ -44,15 +44,14 @@ PyObjectsEnum UIFrame::derived_type()
return PyObjectsEnum::UIFRAME;
}
void UIFrame::render(sf::Vector2f offset, sf::RenderTarget& target)
void UIFrame::render(sf::Vector2f offset)
{
box.move(offset);
//Resources::game->getWindow().draw(box);
target.draw(box);
Resources::game->getWindow().draw(box);
box.move(-offset);
for (auto drawable : *children) {
drawable->render(offset + box.getPosition(), target);
drawable->render(offset + box.getPosition());
}
}

View File

@ -28,7 +28,7 @@ public:
sf::RectangleShape box;
float outline;
std::shared_ptr<std::vector<std::shared_ptr<UIDrawable>>> children;
void render(sf::Vector2f, sf::RenderTarget&) override final;
void render(sf::Vector2f) override final;
void move(sf::Vector2f);
PyObjectsEnum derived_type() override final;
virtual UIDrawable* click_at(sf::Vector2f point) override final;

View File

@ -32,9 +32,9 @@ UIGrid::UIGrid(int gx, int gy, std::shared_ptr<PyTexture> _ptex, sf::Vector2f _x
void UIGrid::update() {}
void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target)
void UIGrid::render(sf::Vector2f)
{
output.setPosition(box.getPosition() + offset); // output sprite can move; update position when drawing
output.setPosition(box.getPosition()); // output sprite can move; update position when drawing
// output size can change; update size when drawing
output.setTextureRect(
sf::IntRect(0, 0,
@ -172,8 +172,7 @@ void UIGrid::render(sf::Vector2f offset, sf::RenderTarget& target)
// render to window
renderTexture.display();
//Resources::game->getWindow().draw(output);
target.draw(output);
Resources::game->getWindow().draw(output);
}
@ -205,28 +204,12 @@ UIDrawable* UIGrid::click_at(sf::Vector2f point)
int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
int grid_x, grid_y;
PyObject* textureObj;
//float box_x, box_y, box_w, box_h;
PyObject* pos, *size;
float box_x, box_y, box_w, box_h;
//if (!PyArg_ParseTuple(args, "iiOffff", &grid_x, &grid_y, &textureObj, &box_x, &box_y, &box_w, &box_h)) {
if (!PyArg_ParseTuple(args, "iiOOO", &grid_x, &grid_y, &textureObj, &pos, &size)) {
if (!PyArg_ParseTuple(args, "iiOffff", &grid_x, &grid_y, &textureObj, &box_x, &box_y, &box_w, &box_h)) {
return -1; // If parsing fails, return an error
}
PyVectorObject* pos_result = PyVector::from_arg(pos);
if (!pos_result)
{
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
return -1;
}
PyVectorObject* size_result = PyVector::from_arg(size);
if (!size_result)
{
PyErr_SetString(PyExc_TypeError, "pos must be a mcrfpy.Vector instance or arguments to mcrfpy.Vector.__init__");
return -1;
}
// Convert PyObject texture to IndexTexture*
// This requires the texture object to have been initialized similar to UISprite's texture handling
@ -241,9 +224,8 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
// Initialize UIGrid
//self->data = new UIGrid(grid_x, grid_y, texture, sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h));
//self->data = std::make_shared<UIGrid>(grid_x, grid_y, pyTexture->data,
// sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h));
self->data = std::make_shared<UIGrid>(grid_x, grid_y, pyTexture->data, pos_result->data, size_result->data);
self->data = std::make_shared<UIGrid>(grid_x, grid_y, pyTexture->data,
sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h));
return 0; // Success
}
@ -441,6 +423,22 @@ PyObject* UIGrid::get_children(PyUIGridObject* self, void* closure)
PyObject* UIGrid::repr(PyUIGridObject* self)
{
// if (member_ptr == 0) // x
// self->data->box.setPosition(val, self->data->box.getPosition().y);
// else if (member_ptr == 1) // y
// self->data->box.setPosition(self->data->box.getPosition().x, val);
// else if (member_ptr == 2) // w
// self->data->box.setSize(sf::Vector2f(val, self->data->box.getSize().y));
// else if (member_ptr == 3) // h
// self->data->box.setSize(sf::Vector2f(self->data->box.getSize().x, val));
// else if (member_ptr == 4) // center_x
// self->data->center_x = val;
// else if (member_ptr == 5) // center_y
// self->data->center_y = val;
// else if (member_ptr == 6) // zoom
// self->data->zoom = val;
std::ostringstream ss;
if (!self->data) ss << "<Grid (invalid internal object)>";
else {

View File

@ -26,7 +26,7 @@ public:
//UIGrid(int, int, IndexTexture*, float, float, float, float);
UIGrid(int, int, std::shared_ptr<PyTexture>, sf::Vector2f, sf::Vector2f);
void update();
void render(sf::Vector2f, sf::RenderTarget&) override final;
void render(sf::Vector2f) override final;
UIGridPoint& at(int, int);
PyObjectsEnum derived_type() override final;
//void setSprite(int);

View File

@ -18,16 +18,14 @@ UISprite::UISprite(std::shared_ptr<PyTexture> _ptex, int _sprite_index, sf::Vect
sprite = ptex->sprite(sprite_index, _pos, sf::Vector2f(_scale, _scale));
}
/*
void UISprite::render(sf::Vector2f offset)
{
sprite.move(offset);
Resources::game->getWindow().draw(sprite);
sprite.move(-offset);
}
*/
void UISprite::render(sf::Vector2f offset, sf::RenderTarget& target)
void UISprite::render(sf::Vector2f offset, sf::RenderTexture& target)
{
sprite.move(offset);
target.draw(sprite);

View File

@ -25,10 +25,10 @@ public:
UISprite();
UISprite(std::shared_ptr<PyTexture>, int, sf::Vector2f, float);
void update();
void render(sf::Vector2f, sf::RenderTarget&) override final;
void render(sf::Vector2f) override final;
virtual UIDrawable* click_at(sf::Vector2f point) override final;
//void render(sf::Vector2f, sf::RenderTexture&);
void render(sf::Vector2f, sf::RenderTexture&);
void setPosition(sf::Vector2f);
sf::Vector2f getPosition();

View File

@ -1,393 +0,0 @@
import mcrfpy
import random
#t = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
t = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16)
#def iterable_entities(grid):
# """Workaround for UIEntityCollection bug; see issue #72"""
# entities = []
# for i in range(len(grid.entities)):
# entities.append(grid.entities[i])
# return entities
class COSEntity(): #mcrfpy.Entity): # Fake mcrfpy.Entity integration; engine bugs workarounds
def __init__(self, g:mcrfpy.Grid, x=0, y=0, sprite_num=86, *, game):
#self.e = mcrfpy.Entity((x, y), t, sprite_num)
#super().__init__((x, y), t, sprite_num)
self._entity = mcrfpy.Entity((x, y), t, sprite_num)
#grid.entities.append(self.e)
self.grid = g
#g.entities.append(self._entity)
self.game = game
self.game.add_entity(self)
## Wrapping mcfrpy.Entity properties to emulate derived class... see issue #76
@property
def draw_pos(self):
return self._entity.draw_pos
@draw_pos.setter
def draw_pos(self, value):
self._entity.draw_pos = value
@property
def sprite_number(self):
return self._entity.sprite_number
@sprite_number.setter
def sprite_number(self, value):
self._entity.sprite_number = value
def __repr__(self):
return f"<{self.__class__.__name__} ({self.draw_pos})>"
def die(self):
# ugly workaround! grid.entities isn't really iterable (segfaults)
for i in range(len(self.grid.entities)):
e = self.grid.entities[i]
if e == self._entity:
#if e == self:
self.grid.entities.remove(i)
break
else:
print(f"!!! {self!r} wasn't removed from grid on call to die()")
def bump(self, other, dx, dy, test=False):
raise NotImplementedError
def do_move(self, tx, ty):
"""Base class method to move this entity
Assumes try_move succeeded, for everyone!
from: self._entity.draw_pos
to: (tx, ty)
calls ev_exit for every entity at (draw_pos)
calls ev_enter for every entity at (tx, ty)
"""
old_pos = self.draw_pos
self.draw_pos = (tx, ty)
for e in self.game.entities:
if e is self: continue
if e.draw_pos == old_pos: e.ev_exit(self)
for e in self.game.entities:
if e is self: continue
if e.draw_pos == (tx, ty): e.ev_enter(self)
def act(self):
pass
def ev_enter(self, other):
pass
def ev_exit(self, other):
pass
def try_move(self, dx, dy, test=False):
x_max, y_max = self.grid.grid_size
tx, ty = int(self.draw_pos[0] + dx), int(self.draw_pos[1] + dy)
#for e in iterable_entities(self.grid):
# sorting entities to test against the boulder instead of the button when they overlap.
for e in sorted(self.game.entities, key = lambda i: i.draw_order, reverse = True):
if e.draw_pos == (tx, ty):
#print(f"bumping {e}")
return e.bump(self, dx, dy)
if tx < 0 or tx >= x_max:
return False
if ty < 0 or ty >= y_max:
return False
if self.grid.at((tx, ty)).walkable == True:
if not test:
#self.draw_pos = (tx, ty)
self.do_move(tx, ty)
return True
else:
#print("Bonk")
return False
def _relative_move(self, dx, dy):
tx, ty = int(self.draw_pos[0] + dx), int(self.draw_pos[1] + dy)
#self.draw_pos = (tx, ty)
self.do_move(tx, ty)
class Equippable:
def __init__(self, hands = 0, hp_healing = 0, damage = 0, defense = 0, zap_damage = 1, zap_cooldown = 10, sprite = 129):
self.hands = hands
self.hp_healing = hp_healing
self.damage = damage
self.defense = defense
self.zap_damage = zap_damage
self.zap_cooldown = zap_cooldown
self.zap_cooldown_remaining = 0
self.sprite = self.sprite
self.quality = 0
def tick(self):
if self.zap_cooldown_remaining:
self.zap_cooldown_remaining -= 1
if self.zap_cooldown_remaining < 0: self.zap_cooldown_remaining = 0
def __repr__(self):
cooldown_str = f'({self.zap_cooldown_remaining} rounds until ready)'
return f"<Equippable hands={self.hands}, hp_healing={self.hp_healing}, damage={self.damage}, defense={self.defense}, zap_damage={self.zap_damage}, zap_cooldown={self.zap_cooldown}{cooldown_str if self.zap_cooldown_remaining else ''}, sprite={self.sprite}>"
def classify(self):
categories = []
if self.hands==0:
categories.append("consumable")
elif self.damage > 0:
categories.append(f"{self.hands}-handed weapon")
elif self.defense > 0:
categories.append(f"defense")
elif self.zap_damage > 0:
categories.append("{self.hands}-handed magic weapon")
if len(categories) == 0:
return "unclassifiable"
elif len(categories) == 1:
return categories[0]
else:
return "Erratic: " + ', '.join(categories)
#def compare(self, other):
# my_class = self.classify()
# o_class = other.classify()
# if my_class == "unclassifiable" or o_class == "unclassifiable":
# return None
# if my_class == "consumable":
# return other.hp_healing - self.hp_healing
class PlayerEntity(COSEntity):
def __init__(self, *, game):
#print(f"spawn at origin")
self.draw_order = 10
super().__init__(game.grid, 0, 0, sprite_num=84, game=game)
self.hp = 10
self.max_hp = 10
self.base_damage = 1
self.base_defense = 0
self.luck = 0
self.archetype = None
self.equipped = []
self.inventory = []
def tick(self):
for i in self.equipped:
i.tick()
def calc_damage(self):
dmg = self.base_damage
for i in self.equipped:
dmg += i.damage
return dmg
def calc_defense(self):
defense = self.base_defense
for i in self.equipped:
defense += i.damage
return defense
def do_zap(self):
pass
def bump(self, other, dx, dy, test=False):
if type(other) == BoulderEntity:
print("Boulder hit w/ knockback!")
return self.game.pull_boulder_move((-dx, -dy), other)
print(f"oof, ouch, {other} bumped the player - {other.base_damage} damage from {other}")
self.hp = max(self.hp - max(other.base_damage - self.calc_defense(), 0), 0)
def respawn(self, avoid=None):
# find spawn point
x_max, y_max = g.size
spawn_points = []
for x in range(x_max):
for y in range(y_max):
if g.at((x, y)).walkable:
spawn_points.append((x, y))
random.shuffle(spawn_points)
## TODO - find other entities to avoid spawning on top of
for spawn in spawn_points:
for e in avoid or []:
if e.draw_pos == spawn: break
else:
break
self.draw_pos = spawn
def __repr__(self):
return f"<PlayerEntity {self.draw_pos}, {self.grid}>"
class BoulderEntity(COSEntity):
def __init__(self, x, y, *, game):
self.draw_order = 8
super().__init__(game.grid, x, y, 66, game=game)
def bump(self, other, dx, dy, test=False):
if type(other) == BoulderEntity:
#print("Boulders can't push boulders")
return False
elif type(other) == EnemyEntity:
if not other.can_push: return False
#tx, ty = int(self.e.position[0] + dx), int(self.e.position[1] + dy)
tx, ty = int(self.draw_pos[0] + dx), int(self.draw_pos[1] + dy)
# Is the boulder blocked the same direction as the bumper? If not, let's both move
old_pos = int(self.draw_pos[0]), int(self.draw_pos[1])
if self.try_move(dx, dy, test=test):
if not test:
other.do_move(*old_pos)
#other.draw_pos = old_pos
return True
class ButtonEntity(COSEntity):
def __init__(self, x, y, exit_entity, *, game):
self.draw_order = 1
super().__init__(game.grid, x, y, 250, game=game)
self.exit = exit_entity
def ev_enter(self, other):
print("Button makes a satisfying click!")
self.exit.unlock()
def ev_exit(self, other):
print("Button makes a disappointing click.")
self.exit.lock()
def bump(self, other, dx, dy, test=False):
#if type(other) == BoulderEntity:
# self.exit.unlock()
# TODO: unlock, and then lock again, when player steps on/off
if not test:
pos = int(self.draw_pos[0]), int(self.draw_pos[1])
other.do_move(*pos)
return True
class ExitEntity(COSEntity):
def __init__(self, x, y, bx, by, *, game):
self.draw_order = 2
super().__init__(game.grid, x, y, 45, game=game)
self.my_button = ButtonEntity(bx, by, self, game=game)
self.unlocked = False
#global cos_entities
#cos_entities.append(self.my_button)
def unlock(self):
self.sprite_number = 21
self.unlocked = True
def lock(self):
self.sprite_number = 45
self.unlocked = False
def bump(self, other, dx, dy, test=False):
if type(other) == BoulderEntity:
return False
if self.unlocked:
if not test:
other._relative_move(dx, dy)
#TODO - player go down a level logic
if type(other) == PlayerEntity:
self.game.depth += 1
print(f"welcome to level {self.game.depth}")
self.game.create_level(self.game.depth)
self.game.swap_level(self.game.level, self.game.spawn_point)
class EnemyEntity(COSEntity):
def __init__(self, x, y, hp=2, base_damage=1, base_defense=0, sprite=123, can_push=False, crushable=True, sight=8, move_cooldown=1, *, game):
self.draw_order = 7
super().__init__(game.grid, x, y, sprite, game=game)
self.hp = hp
self.base_damage = base_damage
self.base_defense = base_defense
self.base_sprite = sprite
self.can_push = can_push
self.crushable = crushable
self.sight = sight
self.move_cooldown = move_cooldown
self.moved_last = 0
def bump(self, other, dx, dy, test=False):
if self.hp == 0:
if not test:
old_pos = int(self.draw_pos[0]), int(self.draw_pos[1])
other.do_move(*old_pos)
return True
if type(other) == PlayerEntity:
# TODO - get damage from player, take damage, decide to die or not
d = other.calc_damage()
self.hp -= d
self.hp = max(self.hp, 0)
if self.hp == 0:
self._entity.sprite_number = self.base_sprite + 246
self.draw_order = 1
print(f"Player hit for {d}. HP = {self.hp}")
#self.hp = 0
return False
elif type(other) == BoulderEntity:
if not self.crushable and self.hp > 0:
print("Uncrushable!")
return False
if self.hp > 0:
print("Ouch, my entire body!!")
self._entity.sprite_number = self.base_sprite + 246
self.hp = 0
old_pos = int(self.draw_pos[0]), int(self.draw_pos[1])
if not test:
other.do_move(*old_pos)
return True
def act(self):
if self.hp > 0:
# if player nearby: attack
x, y = self.draw_pos
px, py = self.game.player.draw_pos
for d in ((1, 0), (0, 1), (-1, 0), (1, 0)):
if int(x + d[0]) == int(px) and int(y + d[1]) == int(py):
self.try_move(*d)
return
# slow movement (doesn't affect ability to attack)
if self.moved_last < 0:
self.moved_last -= 1
return
else:
self.moved_last = self.move_cooldown
# if player is not nearby, wander
if abs(x - px) + abs(y - py) > self.sight:
d = random.choice(((1, 0), (0, 1), (-1, 0), (1, 0)))
self.try_move(*d)
# if can_push and player in a line: KICK
if self.can_push:
if int(x) == int(px):
pass # vertical kick
elif int(y) == int(py):
pass # horizontal kick
# else, nearby pursue
towards = []
dist = lambda dx, dy: abs(px - (x + dx)) + abs(py - (y + dy))
current_dist = dist(0, 0)
for d in ((1, 0), (0, 1), (-1, 0), (1, 0)):
if dist(*d) <= current_dist + 0.75: towards.append(d)
print(current_dist, towards)
target_dir = random.choice(towards)
self.try_move(*target_dir)
class TreasureEntity(COSEntity):
def __init__(self, x, y, treasure_table=None, *, game):
self.draw_order = 6
super().__init__(game.grid, x, y, 89, game=game)
self.popped = False
def bump(self, other, dx, dy, test=False):
if type(other) != PlayerEntity:
return False
if self.popped:
print("It's already open.")
return
print("Take me, I'm yours!")
self._entity.sprite_number = 91
self.popped = True

View File

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

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

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

View File

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

View File

@ -1,606 +1,60 @@
import mcrfpy
#t = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) # 12, 11)
t = mcrfpy.Texture("assets/kenney_TD_MR_IP.png", 16, 16) # 12, 11)
btn_tex = mcrfpy.Texture("assets/48px_ui_icons-KenneyNL.png", 48, 48)
font = mcrfpy.Font("assets/JetbrainsMono.ttf")
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
print("[game.py] Default texture:")
print(mcrfpy.default_texture)
print(type(mcrfpy.default_texture))
# build test widgets
mcrfpy.createScene("pytest")
mcrfpy.setScene("pytest")
ui = mcrfpy.sceneUI("pytest")
# Frame
f = mcrfpy.Frame(25, 19, 462, 346, fill_color=(255, 92, 92))
print("Frame alive")
# fill (LinkedColor / Color): f.fill_color
# outline (LinkedColor / Color): f.outline_color
# pos (LinkedVector / Vector): f.pos
# size (LinkedVector / Vector): f.size
# Caption
print("Caption attempt w/ fill_color:")
#c = mcrfpy.Caption(512+25, 19, "Hi.", font)
#c = mcrfpy.Caption(512+25, 19, "Hi.", font, fill_color=(255, 128, 128))
c = mcrfpy.Caption(512+25, 19, "Hi.", font, fill_color=mcrfpy.Color(255, 128, 128), outline_color=(128, 255, 128))
print("Caption alive")
# fill (LinkedColor / Color): c.fill_color
#color_val = c.fill_color
print(c.fill_color)
#print("Set a fill color")
#c.fill_color = (255, 255, 255)
print("Lol, did it segfault?")
# outline (LinkedColor / Color): c.outline_color
# font (Font): c.font
# pos (LinkedVector / Vector): c.pos
# Sprite
s = mcrfpy.Sprite(25, 384+19, texture, 86, 9.0)
# pos (LinkedVector / Vector): s.pos
# texture (Texture): s.texture
# Grid
g = mcrfpy.Grid(10, 10, texture, 512+25, 384+19, 462, 346)
# texture (Texture): g.texture
# pos (LinkedVector / Vector): g.pos
# size (LinkedVector / Vector): g.size
for _x in range(10):
for _y in range(10):
g.at((_x, _y)).color = (255 - _x*25, 255 - _y*25, 255)
g.zoom = 2.0
[ui.append(d) for d in (f, c, s, g)]
print("built!")
# tests
frame_color = (64, 64, 128)
import random
import cos_entities as ce
import cos_level as cl
#import cos_tiles as ct
class Resources:
def __init__(self):
self.music_enabled = True
self.music_volume = 40
self.sfx_enabled = True
self.sfx_volume = 100
self.master_volume = 100
# load the music/sfx files here
self.splats = []
for i in range(1, 10):
mcrfpy.createSoundBuffer(f"assets/sfx/splat{i}.ogg")
def play_sfx(self, sfx_id):
if self.sfx_enabled and self.sfx_volume and self.master_volume:
mcrfpy.setSoundVolume(self.master_volume/100 * self.sfx_volume)
mcrfpy.playSound(sfx_id)
def play_music(self, track_id):
if self.music_enabled and self.music_volume and self.master_volume:
mcrfpy.setMusicVolume(self.master_volume/100 * self.music_volume)
mcrfpy.playMusic(...)
resources = Resources()
class Crypt:
def __init__(self):
mcrfpy.createScene("play")
self.ui = mcrfpy.sceneUI("play")
entity_frame = mcrfpy.Frame(815, 10, 194, 595, fill_color = frame_color)
inventory_frame = mcrfpy.Frame(10, 610, 800, 143, fill_color = frame_color)
stats_frame = mcrfpy.Frame(815, 610, 194, 143, fill_color = frame_color)
#self.level = cl.Level(30, 23)
self.entities = []
self.depth=1
self.stuck_btn = SweetButton(self.ui, (810, 700), "Stuck", icon=19, box_width=150, box_height = 60, click=self.stuck)
self.level_plan = {
1: [("spawn", "button", "boulder"), ("exit")],
2: [("spawn", "button", "boulder"), ("rat"), ("exit")],
3: [("spawn", "button", "boulder"), ("rat"), ("exit")],
4: [("spawn", "button", "rat"), ("boulder", "rat", "treasure"), ("exit")],
5: [("spawn", "button", "rat"), ("boulder", "rat"), ("exit")],
6: {(("spawn", "button"), ("boulder", "treasure", "exit")),
(("spawn", "boulder"), ("button", "treasure", "exit"))},
7: {(("spawn", "button"), ("boulder", "treasure", "exit")),
(("spawn", "boulder"), ("button", "treasure", "exit"))},
8: {(("spawn", "treasure", "button"), ("boulder", "treasure", "exit")),
(("spawn", "treasure", "boulder"), ("button", "treasure", "exit"))}
#9: self.lv_planner
}
# empty void for the player to initialize into
self.headsup = mcrfpy.Frame(10, 684, 320, 64, fill_color = (0, 0, 0, 0))
self.sidebar = mcrfpy.Frame(860, 4, 160, 600, fill_color = (96, 96, 160))
# Heads Up (health bar, armor bar) config
self.health_bar = [mcrfpy.Sprite(32*i, 2, t, 659, 2) for i in range(10)]
[self.headsup.children.append(i) for i in self.health_bar]
self.armor_bar = [mcrfpy.Sprite(32*i, 42, t, 659, 2) for i in range(10)]
[self.headsup.children.append(i) for i in self.armor_bar]
# (40, 3), caption, font, fill_color=font_color
self.stat_captions = mcrfpy.Caption((325,0), "HP:10\nDef:0(+0)", font, fill_color=(255, 255, 255))
self.stat_captions.outline = 3
self.stat_captions.outline_color = (0, 0, 0)
self.headsup.children.append(self.stat_captions)
# Side Bar (inventory, level info) config
self.level_caption = mcrfpy.Caption((5,5), "Level: 1", font, fill_color=(255, 255, 255))
self.level_caption.size = 26
self.level_caption.outline = 3
self.level_caption.outline_color = (0, 0, 0)
self.sidebar.children.append(self.level_caption)
self.inv_sprites = [mcrfpy.Sprite(15, 70 + 95*i, t, 659, 6.0) for i in range(5)]
for i in self.inv_sprites:
self.sidebar.children.append(i)
self.key_captions = [
mcrfpy.Sprite(75, 130 + (95*2) + 95 * i, t, 384 + i, 3.0) for i in range(3)
]
for i in self.key_captions:
self.sidebar.children.append(i)
self.inv_captions = [
mcrfpy.Caption((25, 130 + 95 * i), "x", font, fill_color=(255, 255, 255)) for i in range(5)
]
for i in self.inv_captions:
self.sidebar.children.append(i)
liminal_void = mcrfpy.Grid(1, 1, t, (0, 0), (16, 16))
self.grid = liminal_void
self.player = ce.PlayerEntity(game=self)
self.spawn_point = (0, 0)
# level creation moves player to the game level at the generated spawn point
self.create_level(self.depth)
#self.grid = mcrfpy.Grid(20, 15, t, (10, 10), (1014, 758))
self.swap_level(self.level, self.spawn_point)
# Test Entities
#ce.BoulderEntity(9, 7, game=self)
#ce.BoulderEntity(9, 8, game=self)
#ce.ExitEntity(12, 6, 14, 4, game=self)
# scene setup
## might be done by self.swap_level
#[self.ui.append(e) for e in (self.grid, self.stuck_btn.base_frame)] # entity_frame, inventory_frame, stats_frame)]
self.possibilities = None # track WFC possibilities between rounds
self.enemies = []
#mcrfpy.setTimer("enemy_test", self.enemy_movement, 750)
#mcrfpy.Frame(x, y, box_width+shadow_offset, box_height, fill_color = (0, 0, 0, 255))
#Sprite(0, 3, btn_tex, icon, icon_scale)
#def enemy_movement(self, *args):
# for e in self.enemies: e.act()
#def spawn_test_rat(self):
# success = False
# while not success:
# x, y = [random.randint(0, i-1) for i in self.grid.grid_size]
# success = self.grid.at((x,y)).walkable
# self.enemies.append(ce.EnemyEntity(x, y, game=self))
def gui_update(self):
self.stat_captions.text = f"HP:{self.player.hp}\nDef:{self.player.calc_defense()}(+{self.player.calc_defense() - self.player.base_defense})"
for i, hs in enumerate(self.health_bar):
full_hearts = self.player.hp - (i*2)
empty_hearts = self.player.max_hp - (i*2)
hs.sprite_number = 659
if empty_hearts >= 2:
hs.sprite_number = 208
if full_hearts >= 2:
hs.sprite_number = 210
elif full_hearts == 1:
hs.sprite_number = 209
for i, arm_s in enumerate(self.armor_bar):
full_hearts = self.player.calc_defense() - (i*2)
arm_s.sprite_number = 659
if full_hearts >= 2:
arm_s.sprite_number = 211
elif full_hearts == 1:
arm_s.sprite_number = 212
#items = self.player.equipped[:] + self.player.inventory[:]
for i in range(5):
if i == 0:
item = self.player.equipped[0] if len(self.player.equipped) > 0 else None
elif i == 1:
item = self.player.equipped[1] if len(self.player.equipped) > 1 else None
elif i == 2:
item = self.player.inventory[0] if len(self.player.inventory) > 0 else None
elif i == 3:
item = self.player.inventory[1] if len(self.player.inventory) > 1 else None
elif i == 4:
item = self.player.inventory[2] if len(self.player.inventory) > 2 else None
if item is None:
self.inv_sprites[i].sprite_number = 659
if i > 1: self.key_captions[i - 2].sprite_number = 659
self.inv_captions[i].text = ""
continue
self.inv_sprites[i].sprite_number = item.sprite
if i > 1:
self.key_captions[i - 2].sprite_number = 384 + (i - 2)
self.inv_captions[i].text = "Blah"
def lv_planner(self, target_level):
"""Plan room sequence in levels > 9"""
monsters = (target_level - 6) // 2
target_rooms = min(int(target_level // 2), 6)
target_treasure = min(int(target_level // 3), 4)
rooms = []
for i in range(target_rooms):
rooms.append([])
for o in ("spawn", "boulder", "button", "exit"):
r = random.randint(0, target_rooms-1)
rooms[r].append(o)
monster_table = {
"rat": int(monsters * 0.8) + 2,
"big rat": max(int(monsters * 0.2) - 2, 0),
"cyclops": max(int(monsters * 0.1) - 3, 0)
}
monster_table = {k: v for k, v in monster_table.items() if v > 0}
monster_names = list(monster_table.keys())
monster_weights = [monster_table[k] for k in monster_names]
for m in range(monsters):
r = random.randint(0, target_rooms - 1)
rooms[r].append(random.choices(monster_names, weights = monster_weights)[0])
for t in range(target_treasure):
r = random.randint(0, target_rooms - 1)
rooms[r].append("treasure")
return rooms
def treasure_planner(self, treasure_level):
"""Plan treasure contents at all levels"""
item_minlv = {
"buckler": 1,
"shield": 2,
"sword": 1,
"sword2": 2,
"sword3": 5,
"axe": 1,
"axe2": 2,
"axe3": 5,
"wand": 1,
"staff": 2,
"staff2": 5,
"red_pot": 3,
"blue_pot": 6,
"green_pot": 6,
"grey_pot": 6,
"sm_grey_pot": 1
}
base_wts = {
("buckler", "shield"): 0.25, # defensive items
("sword", "sword2", "axe", "axe2"): 0.4, #1h weapons
("sword3", "axe3"): 0.25, #2h weapons
("wand", "staff", "staff2"): 0.15, #magic weapons
("red_pot",): 0.25, #health
("blue_pot", "green_pot", "grey_pot", "sm_grey_pot"): 0.2 #stat upgrade potions
}
# find item name in base_wts key (base weight of the category)
base_weight = lambda s: base_wts[list([t for t in base_wts.keys() if s in t])[0]]
weights = {d[0]: base_weight(d[0]) for d in item_minlv.items() if treasure_level > d[1]}
if self.player.archetype is None:
prefs = []
elif self.player.archetype == "viking":
prefs = ["axe2", "axe3", "green_pot"]
elif self.player.archetype == "knight":
prefs = ["sword2", "shield", "grey_pot"]
elif self.player.archetype == "wizard":
prefs = ["staff", "staff2", "blue_pot"]
for i in prefs:
if i in weights: weights[i] *= 3
return weights
def start(self):
resources.play_sfx(1)
mcrfpy.setScene("play")
mcrfpy.keypressScene(self.cos_keys)
def add_entity(self, e:ce.COSEntity):
self.entities.append(e)
self.entities.sort(key = lambda e: e.draw_order, reverse=False)
# hack / workaround for grid.entities not interable
while len(self.grid.entities): # while there are entities on the grid,
self.grid.entities.remove(0) # remove the 1st ("0th")
for e in self.entities:
self.grid.entities.append(e._entity)
def create_level(self, depth, _luck = 0):
#if depth < 3:
# features = None
self.level = cl.Level(20, 20)
self.grid = self.level.grid
if depth in self.level_plan:
plan = self.level_plan[depth]
else:
plan = self.lv_planner(depth)
coords = self.level.generate(plan)
self.entities = []
if self.player:
luck = self.player.luck
else:
luck = 0
buttons = []
for k, v in sorted(coords, key=lambda i: i[0]): # "button" before "exit"; "button", "button", "door", "exit" -> alphabetical is correct sequence
if k == "spawn":
if self.player:
self.add_entity(self.player)
#self.player.draw_pos = v
self.spawn_point = v
elif k == "boulder":
ce.BoulderEntity(v[0], v[1], game=self)
elif k == "treasure":
ce.TreasureEntity(v[0], v[1], treasure_table = self.treasure_planner(depth + luck), game=self)
elif k == "button":
buttons.append(v)
elif k == "exit":
btn = buttons.pop(0)
ce.ExitEntity(v[0], v[1], btn[0], btn[1], game=self)
elif k == "rat":
ce.EnemyEntity(*v, game=self)
elif k == "big rat":
ce.EnemyEntity(*v, game=self, base_damage=2, hp=4, sprite=130)
elif k == "cyclops":
ce.EnemyEntity(*v, game=self, base_damage=3, hp=8, sprite=109, base_defense=2)
#if self.depth > 2:
#for i in range(10):
# self.spawn_test_rat()
def stuck(self, sweet_btn, args):
if args[3] == "end": return
self.create_level(self.depth)
self.swap_level(self.level, self.spawn_point)
def cos_keys(self, key, state):
d = None
if state == "end": return
elif key == "W": d = (0, -1)
elif key == "A": d = (-1, 0)
elif key == "S": d = (0, 1)
elif key == "D": d = (1, 0)
#elif key == "M": self.level.generate()
#elif key == "R":
# self.level.reset()
# self.possibilities = None
#elif key == "T":
# self.level.split()
# self.possibilities = None
#elif key == "Y": self.level.split(single=True)
#elif key == "U": self.level.highlight(+1)
#elif key == "I": self.level.highlight(-1)
#elif key == "O":
# self.level.wall_rooms()
# self.possibilities = None
#elif key == "P": ct.format_tiles(self.grid)
#elif key == "P":
#self.possibilities = ct.wfc_pass(self.grid, self.possibilities)
elif key == "P":
self.depth += 1
print(f"Descending: lv {self.depth}")
self.stuck(None, [1,2,3,4])
elif key == "Period":
self.enemy_turn()
elif key == "X":
self.pull_boulder_search()
#else:
# print(key)
if d:
self.entities.sort(key = lambda e: e.draw_order, reverse=False)
self.player.try_move(*d)
self.enemy_turn()
def enemy_turn(self):
self.entities.sort(key = lambda e: e.draw_order, reverse=False)
for e in self.entities:
e.act()
self.gui_update()
def pull_boulder_search(self):
for dx, dy in ( (0, -1), (-1, 0), (1, 0), (0, 1) ):
for e in self.entities:
if e.draw_pos != (self.player.draw_pos[0] + dx, self.player.draw_pos[1] + dy): continue
if type(e) == ce.BoulderEntity:
self.pull_boulder_move((dx, dy), e)
return self.enemy_turn()
else:
print("No boulder found to pull.")
def pull_boulder_move(self, p, target_boulder):
print(p, target_boulder)
self.entities.sort(key = lambda e: e.draw_order, reverse=False)
if self.player.try_move(-p[0], -p[1], test=True):
old_pos = self.player.draw_pos
self.player.try_move(-p[0], -p[1])
target_boulder.do_move(*old_pos)
def swap_level(self, new_level, spawn_point):
self.level = new_level
self.grid = self.level.grid
self.grid.zoom = 2.0
# TODO, make an entity mover function
#self.add_entity(self.player)
self.player.grid = self.grid
self.player.draw_pos = spawn_point
#self.grid.entities.append(self.player._entity)
# reform UI (workaround to ui collection iterators crashing)
while len(self.ui) > 0:
try:
self.ui.remove(0)
except:
pass
self.ui.append(self.grid)
self.ui.append(self.stuck_btn.base_frame)
self.ui.append(self.headsup)
self.level_caption.text = f"Level: {self.depth}"
self.ui.append(self.sidebar)
self.gui_update()
class SweetButton:
def __init__(self, ui:mcrfpy.UICollection,
pos:"Tuple[int, int]",
caption:str, font=font, font_size=24, font_color=(255,255,255), font_outline_color=(0, 0, 0), font_outline_width=2,
shadow_offset = 8, box_width=200, box_height = 80, shadow_color=(64, 64, 86), box_color=(96, 96, 160),
icon=4, icon_scale=1.75, click=lambda *args: None):
self.ui = ui
self.shadow_box = mcrfpy.Frame
x, y = pos
# box w/ drop shadow
self.shadow_offset = shadow_offset
self.base_frame = mcrfpy.Frame(x, y, box_width+shadow_offset, box_height, fill_color = (0, 0, 0, 255))
self.base_frame.click = self.do_click
# drop shadow won't need configured, append directly
self.base_frame.children.append(mcrfpy.Frame(0, 0, box_width, box_height, fill_color = shadow_color))
# main button is where the content lives
self.main_button = mcrfpy.Frame(shadow_offset, shadow_offset, box_width, box_height, fill_color = box_color)
self.click = click
self.base_frame.children.append(self.main_button)
# main button icon
self.icon = mcrfpy.Sprite(0, 3, btn_tex, icon, icon_scale)
self.main_button.children.append(self.icon)
# main button caption
self.caption = mcrfpy.Caption((40, 3), caption, font, fill_color=font_color)
self.caption.size = font_size
self.caption.outline_color=font_outline_color
self.caption.outline=font_outline_width
self.main_button.children.append(self.caption)
def unpress(self):
"""Helper func for when graphics changes or glitches make the button stuck down"""
self.main_button.x, self.main_button.y = (self.shadow_offset, self.shadow_offset)
def do_click(self, x, y, mousebtn, event):
if event == "start":
self.main_button.x, self.main_button.y = (0, 0)
elif event == "end":
self.main_button.x, self.main_button.y = (self.shadow_offset, self.shadow_offset)
result = self.click(self, (x, y, mousebtn, event))
if result: # return True from event function to instantly un-pop
self.main_button.x, self.main_button.y = (self.shadow_offset, self.shadow_offset)
@property
def text(self):
return self.caption.text
@text.setter
def text(self, value):
self.caption.text = value
@property
def sprite_number(self):
return self.icon.sprite_number
@sprite_number.setter
def sprite_number(self, value):
self.icon.sprite_number = value
class MainMenu:
def __init__(self):
mcrfpy.createScene("menu")
self.ui = mcrfpy.sceneUI("menu")
mcrfpy.setScene("menu")
self.crypt = None
components = []
# demo grid
#components.append(
# )
# title text
drop_shadow = mcrfpy.Caption((150, 10), "Crypt Of Sokoban", font, fill_color=(96, 96, 96), outline_color=(192, 0, 0))
drop_shadow.outline = 3
drop_shadow.size = 64
components.append(
drop_shadow
)
title_txt = mcrfpy.Caption((158, 18), "Crypt Of Sokoban", font, fill_color=(255, 255, 255))
title_txt.size = 64
components.append(
title_txt
)
# toast: text over the demo grid that fades out on a timer
self.toast = mcrfpy.Caption((150, 400), "", font, fill_color=(0, 0, 0))
self.toast.size = 28
self.toast.outline = 2
self.toast.outline_color = (255, 255, 255)
self.toast_event = None
components.append(self.toast)
# button - PLAY
#playbtn = mcrfpy.Frame(284, 548, 456, 120, fill_color =
play_btn = SweetButton(self.ui, (284, 548), "PLAY", box_width=456, box_height=110, icon=1, icon_scale=2.0, click=self.play)
components.append(play_btn.base_frame)
# button - config menu pane
#self.config = lambda self, sweet_btn, *args: print(f"boop, sweet button {sweet_btn} config {args}")
config_btn = SweetButton(self.ui, (10, 678), "Settings", icon=2, click=self.show_config)
components.append(config_btn.base_frame)
# button - insta-1080p scaling
scale_btn = SweetButton(self.ui, (10+256, 678), "Scale up\nto 1080p", icon=15, click=self.scale)
self.scaled = False
components.append(scale_btn.base_frame)
# button - music toggle
music_btn = SweetButton(self.ui, (10+256*2, 678), "Music\nON", icon=12, click=self.music_toggle)
self.music_enabled = True
self.music_volume = 40
components.append(music_btn.base_frame)
# button - sfx toggle
sfx_btn = SweetButton(self.ui, (10+256*3, 678), "SFX\nON", icon=0, click=self.sfx_toggle)
self.sfx_enabled = True
self.sfx_volume = 40
components.append(sfx_btn.base_frame)
[self.ui.append(e) for e in components]
def toast_say(self, txt, delay=10):
"kick off a toast event"
if self.toast_event is not None:
mcrfpy.delTimer("toast_timer")
self.toast.text = txt
self.toast_event = 350
self.toast.fill_color = (255, 255, 255, 255)
self.toast.outline = 2
self.toast.outline_color = (0, 0, 0, 255)
mcrfpy.setTimer("toast_timer", self.toast_callback, 100)
def toast_callback(self, *args):
"fade out the toast text"
self.toast_event -= 5
if self.toast_event < 0:
self.toast_event = None
mcrfpy.delTimer("toast_timer")
mcrfpy.text = ""
return
a = min(self.toast_event, 255)
self.toast.fill_color = (255, 255, 255, a)
self.toast.outline_color = (0, 0, 0, a)
def show_config(self, sweet_btn, args):
self.toast_say("Beep, Boop! Configurations will go here.")
def play(self, sweet_btn, args):
#if args[3] == "start": return # DRAMATIC on release action!
if args[3] == "end": return
self.crypt = Crypt()
#mcrfpy.setScene("play")
self.crypt.start()
def scale(self, sweet_btn, args, window_scale=None):
if args[3] == "end": return
if not window_scale:
self.scaled = not self.scaled
window_scale = 1.3
else:
self.scaled = True
sweet_btn.unpress()
if self.scaled:
self.toast_say("Windowed mode only, sorry!\nCheck Settings for for fine-tuned controls.")
mcrfpy.setScale(window_scale)
sweet_btn.text = "Scale down\n to 1.0x"
else:
mcrfpy.setScale(1.0)
sweet_btn.text = "Scale up\nto 1080p"
def music_toggle(self, sweet_btn, args):
if args[3] == "end": return
self.music_enabled = not self.music_enabled
print(f"music: {self.music_enabled}")
if self.music_enabled:
mcrfpy.setMusicVolume(self.music_volume)
sweet_btn.text = "Music is ON"
sweet_btn.sprite_number = 12
else:
self.toast_say("Use your volume keys or\nlook in Settings for a volume meter.")
mcrfpy.setMusicVolume(0)
sweet_btn.text = "Music is OFF"
sweet_btn.sprite_number = 17
def sfx_toggle(self, sweet_btn, args):
if args[3] == "end": return
self.sfx_enabled = not self.sfx_enabled
print(f"sfx: {self.sfx_enabled}")
if self.sfx_enabled:
mcrfpy.setSoundVolume(self.sfx_volume)
sweet_btn.text = "SFX are ON"
sweet_btn.sprite_number = 0
else:
self.toast_say("Use your volume keys or\nlook in Settings for a volume meter.")
mcrfpy.setSoundVolume(0)
sweet_btn.text = "SFX are OFF"
sweet_btn.sprite_number = 17
mainmenu = MainMenu()

221
src/scripts/game_old.py Normal file
View File

@ -0,0 +1,221 @@
#print("Hello mcrogueface")
import mcrfpy
import cos_play
# Universal stuff
font = mcrfpy.Font("assets/JetbrainsMono.ttf")
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) #12, 11)
texture_cold = mcrfpy.Texture("assets/kenney_ice.png", 16, 16) #12, 11)
texture_hot = mcrfpy.Texture("assets/kenney_lava.png", 16, 16) #12, 11)
# Test stuff
mcrfpy.createScene("boom")
mcrfpy.setScene("boom")
ui = mcrfpy.sceneUI("boom")
box = mcrfpy.Frame(40, 60, 200, 300, fill_color=(255,128,0), outline=4.0, outline_color=(64,64,255,96))
ui.append(box)
#caption = mcrfpy.Caption(10, 10, "Clicky", font, (255, 255, 255, 255), (0, 0, 0, 255))
#box.click = lambda x, y, btn, type: print("Hello callback: ", x, y, btn, type)
#box.children.append(caption)
test_sprite_number = 86
sprite = mcrfpy.Sprite(20, 60, texture, test_sprite_number, 4.0)
spritecap = mcrfpy.Caption(5, 5, "60", font)
def click_sprite(x, y, btn, action):
global test_sprite_number
if action != "start": return
if btn in ("left", "wheel_up"):
test_sprite_number -= 1
elif btn in ("right", "wheel_down"):
test_sprite_number += 1
sprite.sprite_number = test_sprite_number # TODO - inconsistent naming for __init__, __repr__ and getsetter: sprite_number vs sprite_index
spritecap.text = test_sprite_number
sprite.click = click_sprite # TODO - sprites don't seem to correct for screen position or scale when clicking
box.children.append(sprite)
box.children.append(spritecap)
box.click = click_sprite
f_a = mcrfpy.Frame(250, 60, 80, 80, fill_color=(255, 92, 92))
f_a_txt = mcrfpy.Caption(5, 5, "0", font)
f_b = mcrfpy.Frame(340, 60, 80, 80, fill_color=(92, 255, 92))
f_b_txt = mcrfpy.Caption(5, 5, "0", font)
f_c = mcrfpy.Frame(430, 60, 80, 80, fill_color=(92, 92, 255))
f_c_txt = mcrfpy.Caption(5, 5, "0", font)
ui.append(f_a)
f_a.children.append(f_a_txt)
ui.append(f_b)
f_b.children.append(f_b_txt)
ui.append(f_c)
f_c.children.append(f_c_txt)
import sys
def ding(*args):
f_a_txt.text = str(sys.getrefcount(ding)) + " refs"
f_b_txt.text = sys.getrefcount(dong)
f_c_txt.text = sys.getrefcount(stress_test)
def dong(*args):
f_a_txt.text = str(sys.getrefcount(ding)) + " refs"
f_b_txt.text = sys.getrefcount(dong)
f_c_txt.text = sys.getrefcount(stress_test)
running = False
timers = []
def add_ding():
global timers
n = len(timers)
mcrfpy.setTimer(f"timer{n}", ding, 100)
print("+1 ding:", timers)
def add_dong():
global timers
n = len(timers)
mcrfpy.setTimer(f"timer{n}", dong, 100)
print("+1 dong:", timers)
def remove_random():
global timers
target = random.choice(timers)
print("-1 timer:", target)
print("remove from list")
timers.remove(target)
print("delTimer")
mcrfpy.delTimer(target)
print("done")
import random
import time
def stress_test(*args):
global running
global timers
if not running:
print("stress test initial")
running = True
timers.append("recurse")
add_ding()
add_dong()
mcrfpy.setTimer("recurse", stress_test, 1000)
mcrfpy.setTimer("terminate", lambda *args: mcrfpy.delTimer("recurse"), 30000)
ding(); dong()
else:
#print("stress test random activity")
#random.choice([
# add_ding,
# add_dong,
# remove_random
# ])()
#print(timers)
print("Segfaultin' time")
mcrfpy.delTimer("recurse")
print("Does this still work?")
time.sleep(0.5)
print("How about now?")
stress_test()
# Loading Screen
mcrfpy.createScene("loading")
ui = mcrfpy.sceneUI("loading")
#mcrfpy.setScene("loading")
logo_texture = mcrfpy.Texture("assets/temp_logo.png", 1024, 1024)#1, 1)
logo_sprite = mcrfpy.Sprite(50, 50, logo_texture, 0, 0.5)
ui.append(logo_sprite)
logo_sprite.click = lambda *args: mcrfpy.setScene("menu")
logo_caption = mcrfpy.Caption(70, 600, "Click to Proceed", font, (255, 0, 0, 255), (0, 0, 0, 255))
#logo_caption.fill_color =(255, 0, 0, 255)
ui.append(logo_caption)
# menu screen
mcrfpy.createScene("menu")
for e in [
mcrfpy.Caption(10, 10, "Crypt of Sokoban", font, (255, 255, 255), (0, 0, 0)),
mcrfpy.Caption(20, 55, "a McRogueFace demo project", font, (192, 192, 192), (0, 0, 0)),
mcrfpy.Frame(15, 70, 150, 60, fill_color=(64, 64, 128)),
mcrfpy.Frame(15, 145, 150, 60, fill_color=(64, 64, 128)),
mcrfpy.Frame(15, 220, 150, 60, fill_color=(64, 64, 128)),
mcrfpy.Frame(15, 295, 150, 60, fill_color=(64, 64, 128)),
#mcrfpy.Frame(900, 10, 100, 100, fill_color=(255, 0, 0)),
]:
mcrfpy.sceneUI("menu").append(e)
def click_once(fn):
def wraps(*args, **kwargs):
#print(args)
action = args[3]
if action != "start": return
return fn(*args, **kwargs)
return wraps
@click_once
def asdf(x, y, btn, action):
print(f"clicky @({x},{y}) {action}->{btn}")
@click_once
def clicked_exit(*args):
mcrfpy.exit()
menu_btns = [
("Boom", lambda *args: 1 / 0),
("Exit", clicked_exit),
("About", lambda *args: mcrfpy.setScene("about")),
("Settings", lambda *args: mcrfpy.setScene("settings")),
("Start", lambda *args: mcrfpy.setScene("play"))
]
for i in range(len(mcrfpy.sceneUI("menu"))):
e = mcrfpy.sceneUI("menu")[i] # TODO - fix iterator
#print(e, type(e))
if type(e) is not mcrfpy.Frame: continue
label, fn = menu_btns.pop()
#print(label)
e.children.append(mcrfpy.Caption(5, 5, label, font, (192, 192, 255), (0,0,0)))
e.click = fn
# settings screen
mcrfpy.createScene("settings")
window_scaling = 1.0
scale_caption = mcrfpy.Caption(180, 70, "1.0x", font, (255, 255, 255), (0, 0, 0))
#scale_caption.fill_color = (255, 255, 255) # TODO - mcrfpy.Caption.__init__ is not setting colors
for e in [
mcrfpy.Caption(10, 10, "Settings", font, (255, 255, 255), (0, 0, 0)),
mcrfpy.Frame(15, 70, 150, 60, fill_color=(64, 64, 128)), # +
mcrfpy.Frame(300, 70, 150, 60, fill_color=(64, 64, 128)), # -
mcrfpy.Frame(15, 295, 150, 60, fill_color=(64, 64, 128)),
scale_caption,
]:
mcrfpy.sceneUI("settings").append(e)
@click_once
def game_scale(x, y, btn, action, delta):
global window_scaling
print(f"WIP - scale the window from {window_scaling:.1f} to {window_scaling+delta:.1f}")
window_scaling += delta
scale_caption.text = f"{window_scaling:.1f}x"
mcrfpy.setScale(window_scaling)
#mcrfpy.setScale(2)
settings_btns = [
("back", lambda *args: mcrfpy.setScene("menu")),
("-", lambda x, y, btn, action: game_scale(x, y, btn, action, -0.1)),
("+", lambda x, y, btn, action: game_scale(x, y, btn, action, +0.1))
]
for i in range(len(mcrfpy.sceneUI("settings"))):
e = mcrfpy.sceneUI("settings")[i] # TODO - fix iterator
#print(e, type(e))
if type(e) is not mcrfpy.Frame: continue
label, fn = settings_btns.pop()
#print(label, fn)
e.children.append(mcrfpy.Caption(5, 5, label, font, (192, 192, 255), (0,0,0)))
e.click = fn

View File

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