diff --git a/src/GameEngine.cpp b/src/GameEngine.cpp index f548709..fdf0d84 100644 --- a/src/GameEngine.cpp +++ b/src/GameEngine.cpp @@ -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"); } } diff --git a/src/PyVector.cpp b/src/PyVector.cpp index 866431b..adf2aff 100644 --- a/src/PyVector.cpp +++ b/src/PyVector.cpp @@ -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; -} diff --git a/src/PyVector.h b/src/PyVector.h index f678ad6..6ea0622 100644 --- a/src/PyVector.h +++ b/src/PyVector.h @@ -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[]; }; diff --git a/src/UICaption.cpp b/src/UICaption.cpp index d960c25..5daddaf 100644 --- a/src/UICaption.cpp +++ b/src/UICaption.cpp @@ -223,30 +223,17 @@ 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(keywords), &x, &y, &text, &font, &fill_color, &outline_color, &outline)) - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|zOOOf", - const_cast(keywords), &pos, &text, &font, &fill_color, &outline_color, &outline)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|ffzOOOf", + const_cast(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; @@ -265,6 +252,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) { diff --git a/src/UIEntity.cpp b/src/UIEntity.cpp index db8073a..d88fbe2 100644 --- a/src/UIEntity.cpp +++ b/src/UIEntity.cpp @@ -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(keywords), &x, &y, &texture, &sprite_index, &grid)) - if (!PyArg_ParseTupleAndKeywords(args, kwds, "OOi|O", - const_cast(keywords), &pos, &texture, &sprite_index, &grid)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "ffOi|O", + const_cast(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; diff --git a/src/UIEntity.h b/src/UIEntity.h index 042a933..d65678c 100644 --- a/src/UIEntity.h +++ b/src/UIEntity.h @@ -66,7 +66,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, diff --git a/src/UIGrid.cpp b/src/UIGrid.cpp index 2d95120..0ea7d94 100644 --- a/src/UIGrid.cpp +++ b/src/UIGrid.cpp @@ -205,28 +205,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 +225,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(grid_x, grid_y, pyTexture->data, - // sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h)); - self->data = std::make_shared(grid_x, grid_y, pyTexture->data, pos_result->data, size_result->data); + self->data = std::make_shared(grid_x, grid_y, pyTexture->data, + sf::Vector2f(box_x, box_y), sf::Vector2f(box_w, box_h)); return 0; // Success } diff --git a/src/scripts/cos_entities.py b/src/scripts/cos_entities.py deleted file mode 100644 index ed916f7..0000000 --- a/src/scripts/cos_entities.py +++ /dev/null @@ -1,203 +0,0 @@ -import mcrfpy -#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"" - - 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 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 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) - - 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"" - - -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 - #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, 42, 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: - other._relative_move(dx, dy) - 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.create_level(self.game.depth + 1) - self.game.swap_level(self.game.level, self.game.spawn_point) diff --git a/src/scripts/cos_level.py b/src/scripts/cos_level.py deleted file mode 100644 index 32392b0..0000000 --- a/src/scripts/cos_level.py +++ /dev/null @@ -1,195 +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"" - - 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] - -class RoomGraph: - def __init__(self, xywh): - self.root = BinaryRoomNode(xywh) - - def __repr__(self): - return f"" - - 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 - w -= 1 - h -= 1 - margin += 1 - x += margin - y += margin - w -= margin - h -= margin - if w < 0: w = 0 - if h < 0: h = 0 - tx = x if w==0 else random.randint(x, x+w) - ty = y if h==0 else random.randint(y, y+h) - return (tx, ty) - -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 - - 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): - rooms = self.graph.walk() - for g in rooms: - #if random.random() > 0.66: continue - 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 generate(self, target_rooms = 5, features=None): - if features is None: - shuffled = ["boulder", "button"] - random.shuffle(shuffled) - features = ["spawn"] + shuffled + ["exit", "treasure"] - # Binary space partition to reach given # of rooms - self.reset() - 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 - for room in rooms: - if not features: break - f = features.pop(0) - feature_coords[f] = room_coord(room, margin=4 if f in ("boulder",) else 1) - - ## Hallway generation - # plow an inelegant path - if prev_room: - start = room_coord(prev_room, margin=2) - end = room_coord(room, margin=2) - # 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 - #print(x1, y1, x2, y2, dw, dh) - - # 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): - self.grid.at((x, ty)).walkable = True - #self.grid.at((x, ty)).color = (255, 0, 0) - #self.grid.at((x, ty)).tilesprite = -1 - for y in range(y1, y1+dh): - self.grid.at((tx, y)).walkable = True - #self.grid.at((tx, y)).color = (0, 255, 0) - #self.grid.at((tx, y)).tilesprite = -1 - prev_room = room - - - # Tile painting - possibilities = None - while possibilities or possibilities is None: - possibilities = ct.wfc_pass(self.grid, possibilities) - - return feature_coords diff --git a/src/scripts/cos_play.py b/src/scripts/cos_play.py new file mode 100644 index 0000000..5cc9ffd --- /dev/null +++ b/src/scripts/cos_play.py @@ -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 diff --git a/src/scripts/cos_tiles.py b/src/scripts/cos_tiles.py deleted file mode 100644 index d7cb57b..0000000 --- a/src/scripts/cos_tiles.py +++ /dev/null @@ -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"" - -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 - - diff --git a/src/scripts/game.py b/src/scripts/game.py index b6ce058..43989b3 100644 --- a/src/scripts/game.py +++ b/src/scripts/game.py @@ -1,109 +1,98 @@ 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) font = mcrfpy.Font("assets/JetbrainsMono.ttf") +texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16) -frame_color = (64, 64, 128) +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 +s.click = lambda *args, **kwargs: print("clicky", args, kwargs) + +# 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)] + +# Entity +e = mcrfpy.Entity(5, 5, mcrfpy.default_texture, 86) +e.pos = e.draw_pos # TODO - sync draw/collision positions on init +g.entities.append(e) import random -import cos_entities as ce -import cos_level as cl -#import cos_tiles as ct +def wander(*args, **kwargs): + p = e.pos + new_p = (p[0] + random.randint(-1, 1), p[1] + random.randint(-1, 1)) + if g.grid_size[0] >= new_p[0] >= 0 and g.grid_size[1] >= new_p[1] >= 0: + e.pos = new_p + #print(e.pos) -class Crypt: - def __init__(self): - mcrfpy.createScene("play") - self.ui = mcrfpy.sceneUI("play") - mcrfpy.setScene("play") - mcrfpy.keypressScene(self.cos_keys) +mcrfpy.setTimer("wander", wander, 400) - 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) +last_anim = None +def anim(t, *args, **kwargs): + global last_anim + if last_anim is None: + last_anim = t + return + duration = t - last_anim - #self.level = cl.Level(30, 23) - self.entities = [] - self.depth=1 - self.create_level(self.depth) - #self.grid = mcrfpy.Grid(20, 15, t, (10, 10), (1014, 758)) - self.player = ce.PlayerEntity(game=self) - self.swap_level(self.level, self.spawn_point) + entity_speed = 1 / 250 # 250 milliseconds to move one square + if e.pos == e.draw_pos: + return + tx, ty = e.pos #"target" position - entity is already occupying that spot, animate them moving there. + dx, dy = e.draw_pos #"draw" position + newx = tx if (abs(dx - tx) < entity_speed * duration) else entity_speed * duration + if tx < dx: newx *= -1 + newy = ty if (abs(dy - ty) < entity_speed * duration) else entity_speed * duration + if ty < dy: newx *= -1 - # Test Entities - #ce.BoulderEntity(9, 7, game=self) - #ce.BoulderEntity(9, 8, game=self) - #ce.ExitEntity(12, 6, 14, 4, game=self) - # scene setup + print(f"({dx}, {dy}) -> ({tx}, {ty}) = ({newx}, {newy}) ; @{entity_speed} * {duration} = {entity_speed * duration}") + e.draw_pos = (newx, newy) +mcrfpy.setTimer("anim", anim, 67) - [self.ui.append(e) for e in (self.grid,)] # entity_frame, inventory_frame, stats_frame)] +print("built!") - self.possibilities = None # track WFC possibilities between rounds +# tests - 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): - #if depth < 3: - # features = None - self.level = cl.Level(30, 23) - self.grid = self.level.grid - coords = self.level.generate() - self.entities = [] - for k, v in coords.items(): - if k == "spawn": - self.spawn_point = v - elif k == "boulder": - ce.BoulderEntity(v[0], v[1], game=self) - elif k == "button": - pass - elif k == "exit": - ce.ExitEntity(v[0], v[1], coords["button"][0], coords["button"][1], game=self) - - 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) - if d: self.player.try_move(*d) - - 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) - try: - self.ui.remove(0) - except: - pass - self.ui.append(self.grid) - -crypt = Crypt() diff --git a/src/scripts/game_old.py b/src/scripts/game_old.py new file mode 100644 index 0000000..2975588 --- /dev/null +++ b/src/scripts/game_old.py @@ -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