[try] Carve tunnels instead of hallways
This commit is contained in:
parent
9a9a2a56b2
commit
72a71e9580
1 changed files with 381 additions and 0 deletions
381
03_2_map_tunnel.py
Normal file
381
03_2_map_tunnel.py
Normal file
|
@ -0,0 +1,381 @@
|
|||
import pyglet
|
||||
from pyglet.window import key
|
||||
|
||||
import re
|
||||
|
||||
import sys
|
||||
import random
|
||||
|
||||
# constants
|
||||
TILE_SIZE_X = 16
|
||||
TILE_SIZE_Y = 16
|
||||
|
||||
MIN_LEAF_SIZE = 8
|
||||
|
||||
|
||||
class Leaf:
|
||||
""" Leaf of the BSP """
|
||||
|
||||
def __init__(self, x, y, w, h, id):
|
||||
self.id = id
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.width = w
|
||||
self.height = h
|
||||
self.left_leaf = None
|
||||
self.right_leaf = None
|
||||
|
||||
def print_leaf(self):
|
||||
print('[Leaf]' + str(self.id) + ': (' + str(self.x) + '), (' + str(self.y) + ')' +
|
||||
' - ' + str(self.width) + 'x' + str(self.height))
|
||||
|
||||
def is_terminal(self):
|
||||
""" Returns True if the leave has no children """
|
||||
if self.left_leaf or self.right_leaf:
|
||||
return False
|
||||
return True
|
||||
|
||||
def split(self):
|
||||
"""
|
||||
:return: True if the leaf has been split
|
||||
"""
|
||||
|
||||
# self.print_leaf()
|
||||
# choose direction
|
||||
# TODO: if leaf is not so square, split along the long side
|
||||
# if width > XX% height (25, 33 ?) -> split vertical
|
||||
if random.random() < 0.5:
|
||||
# x
|
||||
if self.width < MIN_LEAF_SIZE * 2: # leaf is too small
|
||||
return False
|
||||
split_point = random.randint(MIN_LEAF_SIZE, self.width - MIN_LEAF_SIZE)
|
||||
self.left_leaf = Leaf(self.x, self.y, split_point, self.height, self.id+1)
|
||||
self.right_leaf = Leaf(self.x + split_point, self.y, self.width - split_point, self.height, self.id+2)
|
||||
|
||||
else:
|
||||
# y
|
||||
if self.height < MIN_LEAF_SIZE * 2: # leaf is too small
|
||||
return False
|
||||
split_point = random.randint(MIN_LEAF_SIZE, self.height - MIN_LEAF_SIZE)
|
||||
self.left_leaf = Leaf(self.x, self.y, self.width, split_point, self.id+1)
|
||||
self.right_leaf = Leaf(self.x, self.y + split_point, self.width, self.height - split_point, self.id+2)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class Room:
|
||||
""" room """
|
||||
|
||||
def __init__(self, x, y, w, h, id):
|
||||
self.x = x
|
||||
self.y = y
|
||||
self.width = w
|
||||
self.height = h
|
||||
self.id = id
|
||||
self.print_room()
|
||||
|
||||
def print_room(self):
|
||||
print('[room]' + str(self.id) + ': (' + str(self.x) + '), (' + str(self.y) + ')' +
|
||||
' - ' + str(self.width) + 'x' + str(self.height))
|
||||
|
||||
def draw_map(self, tilemap):
|
||||
# first wall
|
||||
for x in range(0, self.width):
|
||||
tilemap[self.x + x][self.y] = Level.TILE_WALL
|
||||
# middle
|
||||
for y in range(1, self.height - 1):
|
||||
tilemap[self.x][self.y + y] = Level.TILE_WALL
|
||||
for x in range(1, self.width - 1):
|
||||
tilemap[self.x + x][self.y + y] = Level.TILE_GROUND
|
||||
tilemap[self.x ++ self.width - 1][self.y + y] = Level.TILE_WALL
|
||||
# end
|
||||
for x in range(0, self.width):
|
||||
tilemap[self.x + x][self.y + self.height - 1] = Level.TILE_WALL
|
||||
|
||||
def center(self):
|
||||
cx = round(self.x + self.width / 2)
|
||||
cy = round(self.y + self.height / 2)
|
||||
|
||||
return cx, cy
|
||||
|
||||
def is_inside(self, x, y):
|
||||
if x > self.x and x < self.x + self.width \
|
||||
and y > self.y and y < self.y + self.height:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Level:
|
||||
TILE_WALL = 0
|
||||
TILE_ROAD = 1
|
||||
TILE_GROUND = 2
|
||||
TILE_HALLWAY = 3
|
||||
|
||||
def __init__(self, sizex, sizey, seed, tile_file=None):
|
||||
self.sizex, self.sizey = sizex, sizey
|
||||
self.tileset = []
|
||||
self.tilemap = []
|
||||
if tile_file:
|
||||
self.load_tileset(tile_file)
|
||||
|
||||
self.tree = None
|
||||
self.rooms = []
|
||||
self.regenerate()
|
||||
self.seed = seed
|
||||
self.grid = 0
|
||||
|
||||
def load_tileset(self, tile_file):
|
||||
resource_file = open(tile_file)
|
||||
line = resource_file.readline().strip()
|
||||
while line:
|
||||
image_file = re.compile('\d: ').split(line)[1]
|
||||
print('loading tile: ' + str(image_file))
|
||||
self.tileset.append(pyglet.resource.image(image_file))
|
||||
line = resource_file.readline().strip()
|
||||
resource_file.close()
|
||||
|
||||
def generate_tree(self):
|
||||
# init tree
|
||||
tree = [Leaf(0, 0, self.sizex, self.sizey, 0)]
|
||||
|
||||
# split leaves until none succeed
|
||||
# Lists are ordered. Tree will be created and travel from left to right
|
||||
for l in tree:
|
||||
if l.split():
|
||||
tree.append(l.left_leaf)
|
||||
tree.append(l.right_leaf)
|
||||
|
||||
return tree
|
||||
|
||||
def generate_rooms(self, leaf, tilemap, room_list):
|
||||
"""
|
||||
generate rooms inside partitioned space
|
||||
:return: a list of generated rooms
|
||||
"""
|
||||
if leaf.left_leaf:
|
||||
self.generate_rooms(leaf.left_leaf, tilemap, room_list)
|
||||
if leaf.right_leaf:
|
||||
self.generate_rooms(leaf.right_leaf, tilemap, room_list)
|
||||
if not leaf.left_leaf and not leaf.right_leaf:
|
||||
room = self.generate_room(leaf, tilemap)
|
||||
room_list.append(room)
|
||||
|
||||
return room_list
|
||||
|
||||
def generate_room(self, leaf, tilemap):
|
||||
# Leave space for walls
|
||||
x = random.randint(leaf.x + 1, leaf.x + 2)
|
||||
y = random.randint(leaf.y + 1, leaf.y + 2)
|
||||
w = random.randint(MIN_LEAF_SIZE / 2, leaf.width - 2)
|
||||
h = random.randint(MIN_LEAF_SIZE / 2, leaf.height - 2)
|
||||
|
||||
r = Room(x, y, w, h, leaf.id)
|
||||
r.draw_map(tilemap)
|
||||
|
||||
return r
|
||||
|
||||
def regenerate(self):
|
||||
"""" generate a new level """
|
||||
self.tree = self.generate_tree()
|
||||
|
||||
# initialize tilemap with road/ground
|
||||
self.tilemap = [[self.TILE_GROUND for y in range(0, self.sizey)] for x in range(0, self.sizex)]
|
||||
|
||||
# create rooms from partitions
|
||||
self.rooms = self.generate_rooms(self.tree[0], self.tilemap, [])
|
||||
|
||||
self.generate_tunnels()
|
||||
|
||||
def draw_map(self):
|
||||
for y in range(0, self.sizey):
|
||||
for x in range(0, self.sizex):
|
||||
self.tileset[self.tilemap[x][y]].blit(x*TILE_SIZE_X, y*TILE_SIZE_Y)
|
||||
if self.grid:
|
||||
for x in range(0, self.sizex):
|
||||
pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
|
||||
('v2i', (x*TILE_SIZE_X, 0, x*TILE_SIZE_X, self.sizey*TILE_SIZE_Y)),
|
||||
('c3f', (0, 0, 1)*2 ) )
|
||||
|
||||
for y in range(0, self.sizey):
|
||||
pyglet.graphics.draw(2, pyglet.gl.GL_LINES,
|
||||
('v2i', (0, y * TILE_SIZE_Y, self.sizex * TILE_SIZE_X, y * TILE_SIZE_Y)),
|
||||
('c3f', (0, 0, 1)*2 ) )
|
||||
|
||||
def dump_tilemap(self):
|
||||
for y in range(0, self.sizey):
|
||||
for x in range(0, self.sizex):
|
||||
print(self.tilemap[x][y], end="")
|
||||
print("")
|
||||
|
||||
def generate_tunnels(self):
|
||||
""" carve tunnels in the space left by the rooms """
|
||||
|
||||
#self.carve_tunnel(2, 2)
|
||||
#self.tilemap[0][0] = self.TILE_ROAD
|
||||
|
||||
# start some random carvers
|
||||
# for i in range(0, 5):
|
||||
#
|
||||
# randx = random.randint(0, self.sizex)
|
||||
# randy = random.randint(0, self.sizey)
|
||||
# while not self.carvable(randx, randy, -1):
|
||||
# randx = random.randint(0, self.sizex)
|
||||
# randy = random.randint(0, self.sizey)
|
||||
#
|
||||
# self.carve_tunnel(randx, randy)
|
||||
|
||||
for y in range(2, self.sizey, 1):
|
||||
for x in range(2, self.sizex, 1):
|
||||
#if self.tilemap[x][y] == self.TILE_GROUND:
|
||||
if self.carvable(x, y, -1):
|
||||
self.carve_tunnel(x, y)
|
||||
|
||||
def carve_tunnel(self, startx, starty):
|
||||
""" carve a tunnel from (startx, starty) """
|
||||
|
||||
self.tilemap[startx][starty] = self.TILE_HALLWAY
|
||||
|
||||
print("start tunnel at (" + str(startx) + ", " + str(starty) + ")")
|
||||
# construct direction list
|
||||
dir_list = [0, 1, 2, 3]
|
||||
|
||||
while len(dir_list) > 0:
|
||||
# choose random direction
|
||||
direction = int(random.choice(dir_list))
|
||||
# print("direction chosen " + str(direction))
|
||||
newx = startx
|
||||
newy = starty
|
||||
if direction == 0 and newy > 0: # up
|
||||
newy -= 1
|
||||
elif direction == 1 and newy < self.sizey-1: # down
|
||||
newy += 1
|
||||
elif direction == 2 and newx > 0: # left
|
||||
newx -= 1
|
||||
elif direction == 3 and newx < self.sizex-1: # right
|
||||
newx += 1
|
||||
|
||||
print("will dig (" + str(newx) + ", " + str(newy) + ")")
|
||||
if self.carvable(newx, newy, direction):
|
||||
# if self.carvable(newx, newy, -1):
|
||||
# carve
|
||||
self.tilemap[newx][newy] = self.TILE_HALLWAY
|
||||
# TODO: remove backward while reseting directions list?
|
||||
print("dig (" + str(newx) + ", " + str(newy) + ")")
|
||||
dir_list = [0, 1, 2, 3]
|
||||
startx = newx
|
||||
starty = newy
|
||||
# self.draw_map()
|
||||
else:
|
||||
# remove this direction from the list
|
||||
# print("remove direction " + str(direction))
|
||||
dir_list.remove(direction)
|
||||
# print(dir_list)
|
||||
|
||||
def carvable(self, x, y, d):
|
||||
testx = x
|
||||
testy = y
|
||||
|
||||
# check if in a room
|
||||
for r in self.rooms:
|
||||
if r.is_inside(x, y):
|
||||
return False
|
||||
|
||||
if self.tilemap[x][y] == self.TILE_GROUND:
|
||||
# check all directions
|
||||
if d == -1:
|
||||
if x - 1 < 0:
|
||||
return False
|
||||
if x + 1 >= self.sizex:
|
||||
return False
|
||||
if y - 1 < 0:
|
||||
return False
|
||||
if y + 1 >= self.sizey:
|
||||
return False
|
||||
|
||||
if self.tilemap[x-1][y] != self.TILE_GROUND:
|
||||
return False
|
||||
if self.tilemap[x+1][y] != self.TILE_GROUND:
|
||||
return False
|
||||
if self.tilemap[x][y-1] != self.TILE_GROUND:
|
||||
return False
|
||||
if self.tilemap[x][y+1] != self.TILE_GROUND:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
# check specific direction
|
||||
if d == 0 and y - 1 > 0: # up
|
||||
testy = y - 1
|
||||
elif d == 1 and y + 1 < self.sizey-1: # down
|
||||
testy = y + 1
|
||||
elif d == 2 and x - 1 > 0: # left
|
||||
testx = x - 1
|
||||
elif d == 3 and x + 1 < self.sizex-1: # right
|
||||
testx = x + 1
|
||||
|
||||
# if self.tilemap[testx][testy] == self.TILE_GROUND:
|
||||
|
||||
# prevent to carve alongside another hallway
|
||||
# BUG: backward os already carved, so it never returns true
|
||||
if self.carvable(testx, testy, -1):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
##
|
||||
# main
|
||||
##
|
||||
|
||||
# main window
|
||||
window = pyglet.window.Window()
|
||||
|
||||
|
||||
@window.event
|
||||
def on_key_press(symbol, modifiers):
|
||||
# TODO: ugly hack
|
||||
global level
|
||||
|
||||
if symbol == key.Q:
|
||||
print('Will quit')
|
||||
sys.exit(0)
|
||||
if symbol == key.R:
|
||||
print('Regeneration')
|
||||
level.seed = init_random()
|
||||
level.regenerate()
|
||||
window.invalid = True
|
||||
if symbol == key.D:
|
||||
level.dump_tilemap()
|
||||
if symbol == key.G:
|
||||
level.grid ^= 1
|
||||
elif symbol == key.LEFT:
|
||||
print('Left arrow')
|
||||
elif symbol == key.ENTER:
|
||||
print('Enter !')
|
||||
|
||||
|
||||
@window.event
|
||||
def on_draw():
|
||||
window.clear()
|
||||
level.draw_map()
|
||||
label.draw()
|
||||
|
||||
|
||||
def init_random():
|
||||
if len(sys.argv) > 1:
|
||||
seed = int(sys.argv[1])
|
||||
else:
|
||||
seed = random.randint(0, sys.maxsize)
|
||||
print("Using seed: " + str(seed))
|
||||
random.seed(seed)
|
||||
return seed
|
||||
|
||||
|
||||
genseed = init_random()
|
||||
label_txt = 'Plop World seed: ' + str(genseed)
|
||||
label = pyglet.text.Label(label_txt, x=window.width*5/6, y=window.height*5/6, anchor_x='center', anchor_y='center',
|
||||
multiline=True, width=window.width - window.width*5/6)
|
||||
|
||||
level = Level(30, 30, genseed, 'tiles.txt')
|
||||
|
||||
pyglet.app.run()
|
Loading…
Reference in a new issue