import pygame import maze class Maze(maze.Maze): def __init__(self, m: maze.Maze, s: int = 1): self.__maze = m self.__scale = s @property def width(self) -> int: return self.__maze.width * self.__scale @property def height(self) -> int: return self.__maze.height * self.__scale @property def highlighted(self) -> tuple[int,int] | None: if self.__maze.highlighted is None: return None return ( self.__maze.highlighted[0] * self.__scale, self.__maze.highlighted[1] * self.__scale ) def __getitem__(self, index: tuple[int,int]) -> bool: return self.__maze[index[0] // self.__scale, index[1] // self.__scale] def step(self) -> bool: return self.__maze.step() def run(self, i=1_000_000) -> bool: return self.__maze.run(i) @property def maze(self) -> maze.Maze: return self.__maze def draw_map( screen: pygame.Surface, player_pos: pygame.Vector2, player_dir: pygame.Vector2, world_map: maze.Maze, max_dis: float, ): screen.fill(pygame.Color(64,64,64)) pygame.draw.rect(screen, "blue", pygame.Rect( (player_pos)-(2.5,2.5) + (pygame.Vector2(screen.size) // 2),(5,5))) pygame.draw.line( screen, "blue", (player_pos) + (pygame.Vector2(screen.size) // 2), (player_pos) + (player_dir) + (pygame.Vector2(screen.size) // 2), ) plane = pygame.Vector2(0,0.66).rotate(player_dir.as_polar()[1]) # for x in range(0, screen.width, 50): # camera = 2 * x / screen.width - 1 if True: camera = 0 ray_dir = player_dir.normalize() + (plane * camera) wall_dist, side = ray( player_pos, ray_dir, world_map, max_dis*max_dis)#, debug=True) if side is None: color = pygame.Color(255,0,0) elif side == 0: color = pygame.Color(255,255,0) else: color = pygame.Color(0,255,255) pygame.draw.line( screen, color, (player_pos) + (pygame.Vector2(screen.size) // 2), (player_pos) + (ray_dir * wall_dist) + (pygame.Vector2(screen.size) // 2), ) for y in range(world_map.height): for x in range(world_map.width): if world_map[x, y]: pygame.draw.rect(screen, pygame.Color(255,255,255), pygame.Rect( (pygame.Vector2(x, y)) + (pygame.Vector2(screen.size) // 2), (1, 1))) def draw_game( screen: pygame.Surface, player_pos: pygame.Vector2, player_dir: pygame.Vector2, world_map: maze.Maze, max_dis: float, fog: bool = True, ): screen.fill(pygame.Color(0,0,0)) plane = pygame.Vector2(0,0.66).rotate(player_dir.as_polar()[1]) for x in range(screen.width): camera = 2 * x / screen.width - 1 ray_dir = player_dir.normalize() + (plane * camera) wall_dist, side = ray(player_pos, ray_dir, world_map, max_dis*max_dis) if side is not None: lineHeight = int(screen.height / max(wall_dist, 1e-3)) draw_start = -lineHeight / 2 + screen.height / 2 if draw_start < 0: draw_start = 0 draw_end = lineHeight / 2 + screen.height / 2 if draw_end >= screen.height: draw_end = screen.height - 1 if side == 0: c = pygame.Vector3(255,255,255) / 256 else: c = pygame.Vector3(192,192,192) / 256 if fog: c *= max(min(1 - (wall_dist / (3 * max_dis / 4)), 1), 0) color = pygame.Color.from_normalized(c) # type: ignore pygame.draw.line( screen, color, (x, draw_start), (x, draw_end), ) def ray( ray_pos: pygame.Vector2, ray_dir: pygame.Vector2, world_map: maze.Maze, max_dis_squared: float, debug: bool = False ) -> tuple[float, int | None]: delta = pygame.Vector2( 1/max(abs(ray_dir.x),1e-3),1/max(abs(ray_dir.y),1e-3)) # delta = pygame.Vector2( # 1e-31 if ray_dir.x == 0 else 1/abs(ray_dir.x), # 1e-31 if ray_dir.y == 0 else 1/abs(ray_dir.y) # ) map_pos = pygame.Vector2(int(ray_pos.x),int(ray_pos.y)) step = pygame.Vector2(0,0) side_dis = pygame.Vector2(0,0) test_dist = pygame.Vector2(0,0) # if debug: print(map_pos) if ray_dir.x < 0: step.x = -1 side_dis.x = (ray_pos.x - map_pos.x) * delta.x else: step.x = 1 side_dis.x = (map_pos.x + 1.0 - ray_pos.x) * delta.x if ray_dir.y < 0: step.y = -1 side_dis.y = (ray_pos.y - map_pos.y) * delta.y else: step.y = 1 side_dis.y = (map_pos.y + 1.0 - ray_pos.y) * delta.y side = 0 while True: if side_dis.x < side_dis.y: side_dis.x += delta.x map_pos.x += step.x side = 0 else: side_dis.y += delta.y map_pos.y += step.y side = 1 if not ( map_pos.x >= 0 and map_pos.y >= 0 and map_pos.x < world_map.width and map_pos.y < world_map.height and not world_map[int(map_pos.x),int(map_pos.y)] ): break test_dist.x = (side_dis.x - delta.x) if \ (side_dis.x - delta.x) > -1e-5 else min(side_dis.x, 15) test_dist.y = (side_dis.y - delta.y) if \ (side_dis.y - delta.y) > -1e-5 else min(side_dis.y, 15) if ( map_pos.x < 0 or map_pos.y < 0 or map_pos.x > world_map.width or map_pos.y > world_map.height or # side_dis.length_squared() > max_dis_squared # (side == 0 and side_dis.x > max_dis_squared) or # (side == 1 and side_dis.y > max_dis_squared) # (side_dis - delta).length_squared() > max_dis_squared test_dist.length_squared() > max_dis_squared ): side = None break if debug: print(ray_pos, map_pos, test_dist, side_dis, delta, side) if side is None: perp_wall_dist = side_dis.length() elif side == 0: perp_wall_dist = side_dis.x - delta.x else: perp_wall_dist = side_dis.y - delta.y return perp_wall_dist, side def game_loop(): pygame.init() pygame.display.set_caption("Maze Raycaster") screen = pygame.display.set_mode((1280, 720)) show_top = False# or True if show_top: top_view = pygame.Surface( (screen.get_width() // 2, screen.get_height())) game_view = pygame.Surface( (screen.get_width() // 2, screen.get_height())) else: game_view = pygame.Surface((screen.get_width(), screen.get_height())) myfont = pygame.font.SysFont("monospace", 15) clock = pygame.time.Clock() running = True dt = 0 player_pos = pygame.Vector2(6, 6) player_dir = pygame.Vector2(4, 0) # world_map = Maze(maze.RecursiveBacktracker(63), 4) # world_map = Maze(maze.VectorWrapper(maze.BinaryTree(31)), 4) # world_map = Maze(maze.Sidewinder(63), 4) # world_map = Maze(maze.Sidewinder(63, run_param=0), 4) # world_map = Maze(maze.RecursiveDivision(63), 4) # world_map = Maze(maze.RecursiveDivision(63, binary=True), 4) # world_map = Maze(maze.VectorWrapper(maze.Prim(31)), 4) world_map = Maze(maze.VectorWrapper(maze.Wilson(31)), 4) # world_map = Maze(maze.VectorWrapper(maze.Wilson(7)), 4) world_map.run() # world_map = Maze(maze.VectorWrapper.convert(world_map.maze), 4) s = 0 s_param = 0.35 max_dis = 1000 fog = False while running: for event in pygame.event.get(): if event.type == pygame.QUIT: running = False if isinstance(world_map.maze, maze.VectorWrapper) and \ isinstance(world_map.maze.maze, maze.OriginShift) and s > s_param: world_map = Maze( maze.VectorWrapper.convert(world_map.maze), 4) world_map.step() s -= s_param if show_top: draw_map(top_view, player_pos, player_dir, world_map, max_dis) draw_game(game_view, player_pos, player_dir, world_map, max_dis, fog) keys = pygame.key.get_pressed() if keys[pygame.K_w]: new_pos = player_pos + (player_dir * dt) if not world_map[int(new_pos[0]),int(new_pos[1])]: player_pos = new_pos if keys[pygame.K_s]: new_pos = player_pos - (player_dir * dt) if not world_map[int(new_pos[0]),int(new_pos[1])]: player_pos = new_pos if keys[pygame.K_a]: new_pos = player_pos - (player_dir.rotate(90) * dt) if not world_map[int(new_pos[0]),int(new_pos[1])]: player_pos = new_pos if keys[pygame.K_d]: new_pos = player_pos + (player_dir.rotate(90) * dt) if not world_map[int(new_pos[0]),int(new_pos[1])]: player_pos = new_pos if keys[pygame.K_q]: player_dir.rotate_ip(-90 * dt) if keys[pygame.K_e]: player_dir.rotate_ip(90 * dt) if keys[pygame.K_u]: player_dir.rotate_ip(-1 * dt) if keys[pygame.K_o]: player_dir.rotate_ip(1 * dt) if keys[pygame.K_n]: player_dir.scale_to_length(player_dir.length() / 1.01) if keys[pygame.K_m]: player_dir.scale_to_length(player_dir.length() * 1.01) if keys[pygame.K_COMMA]: max_dis = max_dis / 1.01 if keys[pygame.K_PERIOD]: max_dis = max_dis * 1.01 # if keys[pygame.K_t]: # show_top = not show_top if keys[pygame.K_ESCAPE]: running = False if show_top: screen.blit(top_view, (0,0)) screen.blit(game_view, (screen.get_width() // 2,0)) else: screen.blit(game_view, (0,0)) label = myfont.render( f"Player: {player_pos} {player_dir} {round(player_dir.length())}" f" | FPS: {int(clock.get_fps())} | Change: {round(s, 2)}" f" | View Distance: {round(max_dis,1)}", True, (192,192,192) ) screen.blit(label, (10, 10)) pygame.display.update() dt = clock.tick(60) / 1000 s += dt pygame.quit() game_loop()