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()