diff --git a/03_2_map_tunnel.py b/03_2_map_tunnel.py new file mode 100644 index 0000000..22f59bb --- /dev/null +++ b/03_2_map_tunnel.py @@ -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()