#!/usr/bin/env python # -*- coding: utf-8 -*- from sys import exit import pygame import random # Parameters WIDTH = 960 WAIT = 2000 SIZE = 15 WALL_COLOR = pygame.Color('#8F8F8FFF') EXIT_COLOR = pygame.Color('#FF7F00FF') SOFT_EDGE_COLOR = pygame.Color('#AFAFAFFF') SHARP_EDGE_COLOR = pygame.Color('#5F5F9FFF') FLOOR_COLOR = pygame.Color('#0F0F0FFF') SEEN_COLOR = pygame.Color('#0F0F0FFF') WIN_COLOR = pygame.Color('#FF0000FF') START_COLOR = pygame.Color('#00FF00FF') PLAYER_COLOR = pygame.Color('#0000FFFF') UP = (-1, 0) RIGHT = (0, 1) DOWN = (1, 0) LEFT = (0, -1) FONT_FACE = 'Serif' KP_NUMS = [pygame.K_KP0, pygame.K_KP1, pygame.K_KP2, pygame.K_KP3, pygame.K_KP4, pygame.K_KP5, pygame.K_KP6, pygame.K_KP7, pygame.K_KP8, pygame.K_KP9] # Secondary parameters HEIGHT = 10*WIDTH//16 CENTERX = WIDTH // 2 CENTERY = HEIGHT // 3 WIDTHS = [(5 * WIDTH / (7 * (2 * depth + 1))) // 2 for depth in range(0, SIZE)] WIDTHS.insert(0, 3 * WIDTHS[0] // 2) HEIGHTS = [(6 * HEIGHT / (7 * (2 * depth + 1))) // 3 for depth in range(0, SIZE)] HEIGHTS.insert(0, 3 * HEIGHTS[0] // 2) FONT_SIZE = WIDTHS[1] // 15 DIRECTIONS = (UP, RIGHT, DOWN, LEFT) ANGLESYX = ((UP, RIGHT), (DOWN, RIGHT), (DOWN, LEFT), (UP, LEFT)) SEEN_SIZE = max((SIZE * SIZE) // 50, 1) def debug(message, maze = None, path = None): return print('-----------------') print(message) if maze is not None: for row in range(0, len(maze)): r = maze[row] s = '' sr = '' for col in range(0, 2 * SIZE + 1): sr += '1' if r & 1< 0: d = exits[random.randrange(0, len(exits))] exits.remove(d) debug(str(recurs) + '...Try' + str(d)) nmaze = maze[:] nyx = yx npath = path[:] if d is UP: if nyx[0] == 1: for row in (1, 2): nmaze.insert(0, 0) shift_path(npath, (2, 0)) nyx = (nyx[0] + 2, nyx[1]) nmaze[nyx[0] - 2] |= 1<<(nyx[1]) npath.append((nyx[0] - 2, nyx[1])) if nyx[1] > 1 and nmaze[nyx[0] - 2] & 1<<(nyx[1] - 2): nmaze[nyx[0] - 2] |= 1<<(nyx[1] - 1) if nmaze[nyx[0] - 2] & 1<<(nyx[1] + 2): nmaze[nyx[0] - 2] |= 1<<(nyx[1] + 1) if nyx[0] > 3 and nmaze[nyx[0] - 4] & 1<<(nyx[1]): nmaze[nyx[0] - 3] |= 1<<(nyx[1]) if bdir is LEFT: nmaze[nyx[0] - 1] |= 1<<(nyx[1] - 2) nmaze[nyx[0] - 2] |= 1<<(nyx[1] - 1) if bdir is RIGHT: nmaze[nyx[0] - 1] |= 1<<(nyx[1] + 2) nmaze[nyx[0] - 2] |= 1<<(nyx[1] + 1) if len(nmaze) == 2 * SIZE + 1: return nmaze, npath nmaze, npath = create_maze(recurs + 1, nmaze, (nyx[0] - 2, nyx[1]), DOWN, npath) if nmaze is not None: return nmaze, npath if d is RIGHT: nmaze[nyx[0]] |= 1<<(nyx[1] + 2) npath.append((nyx[0], nyx[1] + 2)) if nyx[0] > 1 and nmaze[nyx[0] - 2] & 1<<(nyx[1] + 2): nmaze[nyx[0] - 1] |= 1<<(nyx[1] + 2) if nyx[0] < len(nmaze) - 2 and nmaze[nyx[0] + 2] & 1<<(nyx[1] + 2): nmaze[nyx[0] + 1] |= 1<<(nyx[1] + 2) if nmaze[nyx[0]] & 1<<(nyx[1] + 4): nmaze[nyx[0]] |= 1<<(nyx[1] + 3) if bdir is UP: nmaze[nyx[0] - 1] |= 1<<(nyx[1] + 2) nmaze[nyx[0] - 2] |= 1<<(nyx[1] + 1) if bdir is DOWN: nmaze[nyx[0] + 1] |= 1<<(nyx[1] + 2) nmaze[nyx[0] + 2] |= 1<<(nyx[1] + 1) if any(row & 1<<(2 * SIZE - 1) for row in nmaze): return nmaze, npath nmaze, npath = create_maze(recurs + 1, nmaze, (nyx[0], nyx[1] + 2), LEFT, npath) if nmaze is not None: return nmaze, npath if d is DOWN: if nyx[0] == len(nmaze) - 2: nmaze.extend([0, 0]) nmaze[nyx[0] + 2] |= 1<<(nyx[1]) npath.append((nyx[0] + 2, nyx[1])) if nyx[1] > 1 and nmaze[nyx[0] + 2] & 1<<(nyx[1] - 2): nmaze[nyx[0] + 2] |= 1<<(nyx[1] - 1) if nmaze[nyx[0] + 2] & 1<<(nyx[1] + 2): nmaze[nyx[0] + 2] |= 1<<(nyx[1] + 1) if nyx[0] < len(nmaze) - 4 and nmaze[nyx[0] + 4] & 1<<(nyx[1]): nmaze[nyx[0] + 3] |= 1<<(nyx[1]) if bdir is LEFT: nmaze[nyx[0] + 1] |= 1<<(nyx[1] - 2) nmaze[nyx[0] + 2] |= 1<<(nyx[1] - 1) if bdir is RIGHT: nmaze[nyx[0] + 1] |= 1<<(nyx[1] + 2) nmaze[nyx[0] + 2] |= 1<<(nyx[1] + 1) if len(nmaze) == 2 * SIZE + 1: return nmaze, npath nmaze, npath = create_maze(recurs + 1, nmaze, (nyx[0] + 2, nyx[1]), UP, npath) if nmaze is not None: return nmaze, npath if d is LEFT: if nyx[1] == 1: for r in range(0, len(nmaze)): nmaze[r] = nmaze[r] << 2 shift_path(npath, (0, 2)) nyx = (nyx[0], nyx[1] + 2) nmaze[nyx[0]] |= 1<<(nyx[1] - 2) npath.append((nyx[0], nyx[1] - 2)) if nyx[0] > 1 and nmaze[nyx[0] - 2] & 1<<(nyx[1] - 2): nmaze[nyx[0] - 1] |= 1<<(nyx[1] - 2) if nyx[0] < len(nmaze) - 2 and nmaze[nyx[0] + 2] & 1<<(nyx[1] - 2): nmaze[nyx[0] + 1] |= 1<<(nyx[1] - 2) if nyx[1] > 3 and nmaze[nyx[0]] & 1<<(nyx[1] - 4): nmaze[nyx[0]] |= 1<<(nyx[1] - 3) if bdir is UP: nmaze[nyx[0] - 1] |= 1<<(nyx[1] - 2) nmaze[nyx[0] - 2] |= 1<<(nyx[1] - 1) if bdir is DOWN: nmaze[nyx[0] + 1] |= 1<<(nyx[1] - 2) nmaze[nyx[0] + 2] |= 1<<(nyx[1] - 1) if any(row & 1<<(2 * SIZE - 1) for row in nmaze): return nmaze, npath nmaze, npath = create_maze(recurs + 1, nmaze, (nyx[0], nyx[1] - 2), RIGHT, npath) if nmaze is not None: return nmaze, npath return None, None def relative(dir, rel): if dir is UP: return rel elif rel is UP: return dir elif rel is DOWN: if dir is RIGHT: return LEFT elif dir is LEFT: return RIGHT else: return UP elif rel == dir: return DOWN elif dir is DOWN: if rel is RIGHT: return LEFT else: return RIGHT else: return UP def is_exit(path, pos, dir): return (pos[0] + dir[0] == 0 or pos[0] + dir[0] == 2 * SIZE \ or pos[1] + dir[1] == 0 or pos[1] + dir[1] == 2 * SIZE) \ and (pos[0], pos[1]) == path[-1] def is_wall(maze, pos, dir): return maze[pos[0] + dir[0]] & 1<<(pos[1] + dir[1]) def new_pos(pos, dir): return (pos[0] + 2 * dir[0], pos[1] + 2 * dir[1]) def rotate_dir(dir, left_or_right): return DIRECTIONS[(DIRECTIONS.index(dir) + left_or_right[1]) % 4] def draw_maze(maze, path, game, seen, pos, dir, shift = None, depth = None, clip = None): s = pygame.display.get_surface() if clip is None: fake_path = [pos, new_pos(pos, dir)] debug('played:', maze, fake_path) s.set_clip(None) s.fill(FLOOR_COLOR) draw_maze(maze, path, game, seen, pos, dir, 0, 1, s.get_rect()) pygame.display.update() s.set_clip(None) return # Inner/Outer - Width/Height/X/Y iw = WIDTHS[depth] ow = WIDTHS[depth - 1] ih = HEIGHTS[depth] oh = HEIGHTS[depth - 1] ix = CENTERX + 2 * shift * iw ox = CENTERX + 2 * shift * ow iy = oy = CENTERY if not pygame.Rect(ox - ow, oy - oh, 2 * ow + 1, 3 * oh + 1).colliderect(clip) \ and not pygame.Rect(ix - iw, iy - ih, 2 * iw + 1, 3 * ih + 1).colliderect(clip): return it = [None, (ix + iw, iy - ih), (ix - iw, iy - ih)] ib = [None, (ix + iw, iy + ih * 2), (ix - iw, iy + ih * 2)] ot = [None, (ox + ow, oy - oh), (ox - ow, oy - oh)] ob = [None, (ox + ow, oy + oh * 2), (ox - ow, oy + oh * 2)] # floor if seen != 0 and pos in game: alpha = game[::-1].index(pos) if seen < 0: alpha = 255 - 171 * alpha / len(game) elif alpha > seen - 1: alpha = -1 else: alpha = 255 - 190 * alpha / seen else: alpha = -1 if alpha >= 0: s.set_clip(clip) p = pygame.Surface(((iw + ow) // 2, (oy + 2 * oh - iy - 2 * ih) // 2)) p.fill(FLOOR_COLOR) p.set_colorkey(FLOOR_COLOR) pygame.draw.ellipse(p, PLAYER_COLOR, p.get_rect()) p.set_alpha(alpha) s.blit(p, ((2 * ix + 2 * ox - iw -ow) // 4, (3 * iy / 2 + 3 * ih + oy / 2 + oh) // 2)) # ahead if is_wall(maze, pos, dir): s.set_clip(clip) pygame.draw.polygon(s, EXIT_COLOR if is_exit(path, pos, dir) else WALL_COLOR, [ib[1], it[1], it[-1], ib[-1]]) pygame.draw.line(s, SHARP_EDGE_COLOR, it[-1], it[1]) pygame.draw.line(s, SHARP_EDGE_COLOR, ib[-1], ib[1]) pygame.draw.line(s, SOFT_EDGE_COLOR, it[-1], ib[-1]) pygame.draw.line(s, SOFT_EDGE_COLOR, it[1], ib[1]) else: r = pygame.Rect(ix - iw, iy - ih, 2 * iw + 1, 3 * ih + 1).clip(clip) if r.width > 0 and r.height > 0: draw_maze(maze, path, game, seen, new_pos(pos, dir), dir, shift, depth + 1, r) # sides for d in (LEFT, RIGHT): tmpdir = relative(d, dir) s.set_clip(clip) if (d is LEFT and shift > 0) or (d is RIGHT and shift < 0): if not is_wall(maze, pos, tmpdir): pygame.draw.line(s, SHARP_EDGE_COLOR, ot[-1 * d[1]], ob[-1 * d[1]]) elif is_wall(maze, pos, tmpdir): pygame.draw.polygon(s, EXIT_COLOR if is_exit(path, pos, tmpdir) else WALL_COLOR, [ib[d[1]], it[d[1]], ot[d[1]], ob[d[1]]]) pygame.draw.line(s, SHARP_EDGE_COLOR, it[d[1]], ot[d[1]]) pygame.draw.line(s, SHARP_EDGE_COLOR, ib[d[1]], ob[d[1]]) pygame.draw.line(s, SOFT_EDGE_COLOR, it[d[1]], ib[d[1]]) pygame.draw.line(s, SOFT_EDGE_COLOR, ot[d[1]], ob[d[1]]) else: tmppos = new_pos(pos, tmpdir) r = pygame.Rect(ix + iw if d is RIGHT else ox - ow, 0, d[1] * (ox + d[1] * ow) - d[1] * (ix + d[1] * iw) + 1, HEIGHT).clip(clip) if r.width > 0 and r.height > 0: draw_maze(maze, path, game, seen, tmppos, dir, shift + d[1], depth, r) if not is_wall(maze, tmppos, dir): s.set_clip(clip) pygame.draw.line(s, SHARP_EDGE_COLOR, it[d[1]], ib[d[1]]) def get_help_surface(maze, path = None): size = HEIGHT // SIZE s = pygame.Surface((SIZE * size, SIZE * size)).convert() for row in range(0, SIZE): for col in range(0, SIZE): yx = (2 * row + 1, 2 * col + 1) if path and yx == path[0]: color = START_COLOR elif path and yx == path[-1]: color = EXIT_COLOR else: color = FLOOR_COLOR pygame.draw.rect(s, color, pygame.Rect(col * size + 1, row * size + 1, size - 2, size - 2)) if is_wall(maze, yx, UP): color = SHARP_EDGE_COLOR else: color = FLOOR_COLOR pygame.draw.line(s, color, (col * size, row * size), ((col + 1) * size - 1, row * size)) if is_wall(maze, yx, RIGHT): color = SHARP_EDGE_COLOR else: color = FLOOR_COLOR pygame.draw.line(s, color, ((col + 1) * size - 1, row * size), ((col + 1) * size - 1, (row + 1) * size - 1)) if is_wall(maze, yx, DOWN): color = SHARP_EDGE_COLOR else: color = FLOOR_COLOR pygame.draw.line(s, color, (col * size, (row + 1) * size - 1), ((col + 1) * size - 1, (row + 1) * size - 1)) if is_wall(maze, yx, LEFT): color = SHARP_EDGE_COLOR else: color = FLOOR_COLOR pygame.draw.line(s, color, (col * size, row * size), (col * size, (row + 1) * size - 1)) return s def get_player_surface(): size = HEIGHT // SIZE p = pygame.Surface((size, size)).convert() p.fill(WALL_COLOR) pygame.draw.line(p, PLAYER_COLOR, (size // 3, 2 * size // 3), (size // 2, size // 3)) pygame.draw.line(p, PLAYER_COLOR, (2 * size // 3, 2 * size // 3), (size // 2, size // 3)) p.set_colorkey(WALL_COLOR) return p def draw_help(helpmap, pos = None, dir = None, player = None): s = pygame.display.get_surface() s.fill(WALL_COLOR) s.blit(helpmap, (CENTERX - helpmap.get_width() // 2, 0)) if pos: size = HEIGHT // SIZE x = CENTERX - helpmap.get_width() // 2 + ((pos[1] - 1) / 2) * size y = ((pos[0] - 1) / 2) * size if dir is RIGHT: p = pygame.transform.rotate(player, -90) elif dir is LEFT: p = pygame.transform.rotate(player, 90) elif dir is DOWN: p = pygame.transform.rotate(player, 180) else: p = player s.blit(p, (x + 1, y + 1)) pygame.display.update() def draw_end(helpmap, path, game): txt = pygame.font.SysFont(FONT_FACE, FONT_SIZE)\ .render(str(len(game) - 1) + ' STEPS (+ ' + str(len(game) - len(path)) + ')', True, WIN_COLOR) pygame.display.get_surface().blit(txt, (CENTERX - txt.get_width() // 2, CENTERY - txt.get_height() // 2)) pygame.display.update() pygame.time.wait(WAIT) size = HEIGHT // SIZE player = pygame.Surface((size, size)).convert() player.fill(WALL_COLOR) pygame.draw.circle(player, PLAYER_COLOR, (size // 2, size // 2), size // 3) shadow = pygame.Surface((size, size)).convert() shadow.blit(player, (0, 0)) player.set_colorkey(WALL_COLOR) shadow.set_colorkey(WALL_COLOR) shadow.set_alpha(84) endwait = True while endwait and len(game): yx = game.pop(0) y = ((yx[0] - 1) / 2) * size x = ((yx[1] - 1) / 2) * size helpmap.blit(shadow, (x, y)) draw_help(helpmap, yx, UP, player) pygame.time.wait(200) for evt in pygame.event.get(): if evt.type == pygame.QUIT: endwait = False elif evt.type == pygame.KEYUP and evt.key == pygame.K_ESCAPE: endwait = False if endwait: pygame.time.wait(WAIT) def draw_fake_maze(): high = 1 low = 1 for i in range(0, SIZE): high = (high<<2) | 3 low = (low<<2) | 1 fake = [high] for i in range(0, SIZE): fake.extend([low, high]) draw_help(get_help_surface(fake)) # Pygame initialisation pygame.init() pygame.event.set_allowed([pygame.QUIT, pygame.KEYUP]) pygame.display.set_mode((WIDTH, HEIGHT)) pygame.display.set_caption('Laby') pygame.mouse.set_visible(0) # Choose maze size draw_fake_maze() redraw = False while True: evt = pygame.event.wait() if evt.type == pygame.QUIT: exit() elif evt.type == pygame.KEYUP: if evt.key == pygame.K_ESCAPE: exit() elif evt.key == pygame.K_KP_PLUS: SIZE += 5 redraw = True elif evt.key == pygame.K_KP_MINUS: SIZE -= 5 redraw = True else: break if redraw: draw_fake_maze() redraw = False # Maze creation maze, path = create_maze() debug('PATH ', maze, path) grow_maze(maze, path) debug('GROWN ', maze, path) # Player variables play_pos = path[0] play_dir = DIRECTIONS[random.randrange(0, 4)] game = [play_pos] helpmap = get_help_surface(maze, path) heading = get_player_surface() # Main loop seen = 0 endwait = True help = False run = True draw_maze(maze, path, game, seen, play_pos, play_dir) while run: evt = pygame.event.wait() if evt.type == pygame.QUIT: run = False endwait = False elif evt.type == pygame.KEYUP: if evt.key == pygame.K_ESCAPE: run = False endwait = False elif evt.key == pygame.K_h: help = not help elif evt.key == pygame.K_UP: rel_dir = relative(UP, play_dir) if is_exit(path, play_pos, rel_dir): run = False elif not is_wall(maze, play_pos, rel_dir): play_pos = new_pos(play_pos, rel_dir) game.append(play_pos) elif evt.key == pygame.K_RIGHT: play_dir = rotate_dir(play_dir, RIGHT) elif evt.key == pygame.K_DOWN: rel_dir = relative(DOWN, play_dir) if is_exit(path, play_pos, rel_dir): run = False elif not is_wall(maze, play_pos, rel_dir): play_pos = new_pos(play_pos, rel_dir) game.append(play_pos) elif evt.key == pygame.K_LEFT: play_dir = rotate_dir(play_dir, LEFT) elif evt.key in KP_NUMS: seen = SEEN_SIZE * KP_NUMS.index(evt.key) elif evt.key == pygame.K_KP_MULTIPLY: seen = -SEEN_SIZE elif evt.key == pygame.K_KP_PLUS: seen += 5 * SEEN_SIZE elif evt.key == pygame.K_KP_MINUS: seen -= 5 * SEEN_SIZE if help: draw_help(helpmap, play_pos, play_dir, heading) else: draw_maze(maze, path, game, seen, play_pos, play_dir) if endwait: draw_end(helpmap, path, game)