labytest/03_2_map_tunnel.py

370 lines
11 KiB
Python

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
# UP, DOWN, LEFT, RIGHT
DX = [0, 0, -1, 1]
DY = [-1, 1, 0, 0]
BACKWARD = [1, 0, 3, 2]
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)
next_id = self.id + 10 + 2 * (self.id % 10)
self.left_leaf = Leaf(self.x, self.y, split_point, self.height, next_id)
self.right_leaf = Leaf(self.x + split_point, self.y, self.width - split_point, self.height, next_id + 1)
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)
next_id = self.id + 10 + 2 * (self.id % 10)
self.left_leaf = Leaf(self.x, self.y, self.width, split_point, next_id)
self.right_leaf = Leaf(self.x, self.y + split_point, self.width, self.height - split_point, next_id + 1)
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 self.x < x < self.x + self.width \
and self.y < 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_graphviz(self.tree[0])
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)
# start some random carvers
for i in range(0, 1):
randx = random.randint(0, self.sizex-1)
randy = random.randint(0, self.sizey-1)
while not self.carvable(randx, randy, -1):
randx = random.randint(0, self.sizex-1)
randy = random.randint(0, self.sizey-1)
self.carve_tunnel(randx, randy)
def carve_tunnel(self, startx, starty):
""" carve a tunnel from (startx, starty) """
print("start tunnel at (" + str(startx) + ", " + str(starty) + ")")
# carve
self.tilemap[startx][starty] = self.TILE_HALLWAY
# 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 + DX[direction]
newy = starty + DY[direction]
if self.carvable(newx, newy, direction):
self.carve_tunnel(newx, newy)
# self.draw_map()
# remove this direction from the list
dir_list.remove(direction)
def carvable(self, x, y, d):
if x < 0 or x > self.sizex - 1:
return False
if y < 0 or y > self.sizey - 1:
return False
print("trying (" + str(x) + ", " + str(y) + "): " + str(self.tilemap[x][y]))
if self.tilemap[x][y] != self.TILE_GROUND:
return False
# check if in a room
for r in self.rooms:
if r.is_inside(x, y):
return False
# prevent hallway from touching each other
check_dir = [0, 1, 2, 3]
# remove_opposite if we have a direction
if d != -1:
check_dir.remove(BACKWARD[d])
for dir in check_dir:
testx = x + DX[dir]
testy = y + DY[dir]
print("checking: (" + str(x) + ", " + str(y) + ") dir: " + str(dir))
if 0 < testx < self.sizex-1:
if 0 < testy < self.sizey-1:
print("tile: " + str(self.tilemap[testx][testy]))
if self.tilemap[testx][testy] == self.TILE_HALLWAY:
return False
return True
def generate_graphviz(self, leaf):
""" generate a dot file for graphviz representation of rooms tree"""
filename='bsp.dot'
f = open(filename, 'w+t')
f.write("digraph BST {\n")
self.generate_graphviz_sub(leaf, f)
f.write("}")
f.close()
def generate_graphviz_sub(self, leaf, f):
f.write("\n\tl" + str(leaf.id) + " [label = " + str(leaf.id) + "];\n")
if leaf.left_leaf:
f.write("\tl" + str(leaf.id )+ " -> l" + str(leaf.left_leaf.id) + ";\n")
self.generate_graphviz_sub(leaf.left_leaf, f)
if leaf.right_leaf:
f.write("\tl" + str(leaf.id) + " -> l" + str(leaf.right_leaf.id) + ";\n")
self.generate_graphviz_sub(leaf.right_leaf, f)
##
# 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()