Squashed: grid-entity-integration partial features for 7DRL 2025 deployment
This squash commit includes changes from April 21st through 28th, 2024, and the past 3 days of work at 7DRL. Rather than resume my feature branch work, I made minor changes to safe the C++ functionality and wrote workarounds in Python. I'm very likely to delete this commit from history by rolling master back to the previous commit, and squash merging a finished feature branch.
This commit is contained in:
parent
232105a893
commit
e928dda4b3
22 changed files with 843 additions and 622 deletions
195
src/scripts/cos_level.py
Normal file
195
src/scripts/cos_level.py
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue