318 lines
10 KiB
Python
318 lines
10 KiB
Python
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()
|