7DRL 2025 - Crypt of Sokoban fully functional. Minor C++ changes.
This commit is contained in:
parent
c975599251
commit
b920a51736
|
@ -9,10 +9,10 @@ GameEngine::GameEngine()
|
||||||
{
|
{
|
||||||
Resources::font.loadFromFile("./assets/JetbrainsMono.ttf");
|
Resources::font.loadFromFile("./assets/JetbrainsMono.ttf");
|
||||||
Resources::game = this;
|
Resources::game = this;
|
||||||
window_title = "McRogueFace - 7DRL 2024 Engine Demo";
|
window_title = "Crypt of Sokoban - 7DRL 2025, McRogueface Engine";
|
||||||
window.create(sf::VideoMode(1024, 768), window_title, sf::Style::Titlebar | sf::Style::Close);
|
window.create(sf::VideoMode(1024, 768), window_title, sf::Style::Titlebar | sf::Style::Close);
|
||||||
visible = window.getDefaultView();
|
visible = window.getDefaultView();
|
||||||
window.setFramerateLimit(30);
|
window.setFramerateLimit(60);
|
||||||
scene = "uitest";
|
scene = "uitest";
|
||||||
scenes["uitest"] = new UITestScene(this);
|
scenes["uitest"] = new UITestScene(this);
|
||||||
|
|
||||||
|
@ -63,7 +63,10 @@ void GameEngine::run()
|
||||||
currentFrame++;
|
currentFrame++;
|
||||||
frameTime = clock.restart().asSeconds();
|
frameTime = clock.restart().asSeconds();
|
||||||
fps = 1 / frameTime;
|
fps = 1 / frameTime;
|
||||||
window.setTitle(window_title + " " + std::to_string(fps) + " FPS");
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,203 @@
|
||||||
|
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"<COSEntity ({self.draw_pos}) on {self.grid}>"
|
||||||
|
|
||||||
|
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"<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
|
||||||
|
#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)
|
|
@ -0,0 +1,195 @@
|
||||||
|
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 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"<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
|
||||||
|
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
|
|
@ -1,300 +0,0 @@
|
||||||
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
|
|
|
@ -0,0 +1,223 @@
|
||||||
|
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
|
||||||
|
|
||||||
|
|
|
@ -1,98 +1,109 @@
|
||||||
import mcrfpy
|
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")
|
font = mcrfpy.Font("assets/JetbrainsMono.ttf")
|
||||||
texture = mcrfpy.Texture("assets/kenney_tinydungeon.png", 16, 16)
|
|
||||||
|
|
||||||
print("[game.py] Default texture:")
|
frame_color = (64, 64, 128)
|
||||||
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 random
|
||||||
def wander(*args, **kwargs):
|
import cos_entities as ce
|
||||||
p = e.pos
|
import cos_level as cl
|
||||||
new_p = (p[0] + random.randint(-1, 1), p[1] + random.randint(-1, 1))
|
#import cos_tiles as ct
|
||||||
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)
|
|
||||||
|
|
||||||
mcrfpy.setTimer("wander", wander, 400)
|
class Crypt:
|
||||||
|
def __init__(self):
|
||||||
|
mcrfpy.createScene("play")
|
||||||
|
self.ui = mcrfpy.sceneUI("play")
|
||||||
|
mcrfpy.setScene("play")
|
||||||
|
mcrfpy.keypressScene(self.cos_keys)
|
||||||
|
|
||||||
last_anim = None
|
entity_frame = mcrfpy.Frame(815, 10, 194, 595, fill_color = frame_color)
|
||||||
def anim(t, *args, **kwargs):
|
inventory_frame = mcrfpy.Frame(10, 610, 800, 143, fill_color = frame_color)
|
||||||
global last_anim
|
stats_frame = mcrfpy.Frame(815, 610, 194, 143, fill_color = frame_color)
|
||||||
if last_anim is None:
|
|
||||||
last_anim = t
|
|
||||||
return
|
|
||||||
duration = t - last_anim
|
|
||||||
|
|
||||||
entity_speed = 1 / 250 # 250 milliseconds to move one square
|
#self.level = cl.Level(30, 23)
|
||||||
if e.pos == e.draw_pos:
|
self.entities = []
|
||||||
return
|
self.depth=1
|
||||||
tx, ty = e.pos #"target" position - entity is already occupying that spot, animate them moving there.
|
self.create_level(self.depth)
|
||||||
dx, dy = e.draw_pos #"draw" position
|
#self.grid = mcrfpy.Grid(20, 15, t, (10, 10), (1014, 758))
|
||||||
newx = tx if (abs(dx - tx) < entity_speed * duration) else entity_speed * duration
|
self.player = ce.PlayerEntity(game=self)
|
||||||
if tx < dx: newx *= -1
|
self.swap_level(self.level, self.spawn_point)
|
||||||
newy = ty if (abs(dy - ty) < entity_speed * duration) else entity_speed * duration
|
|
||||||
if ty < dy: newx *= -1
|
|
||||||
|
|
||||||
print(f"({dx}, {dy}) -> ({tx}, {ty}) = ({newx}, {newy}) ; @{entity_speed} * {duration} = {entity_speed * duration}")
|
# Test Entities
|
||||||
e.draw_pos = (newx, newy)
|
#ce.BoulderEntity(9, 7, game=self)
|
||||||
|
#ce.BoulderEntity(9, 8, game=self)
|
||||||
|
#ce.ExitEntity(12, 6, 14, 4, game=self)
|
||||||
|
# scene setup
|
||||||
|
|
||||||
mcrfpy.setTimer("anim", anim, 67)
|
|
||||||
|
|
||||||
print("built!")
|
[self.ui.append(e) for e in (self.grid,)] # entity_frame, inventory_frame, stats_frame)]
|
||||||
|
|
||||||
# tests
|
self.possibilities = None # track WFC possibilities between rounds
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
|
@ -1,221 +0,0 @@
|
||||||
#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
|
|
Loading…
Reference in New Issue