mazes2/game.py

239 lines
7.2 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)
def draw_map(
screen: pygame.Surface,
player_pos: pygame.Vector2,
player_dir: pygame.Vector2,
world_map: maze.Maze,
):
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, debug=True)
if side is not None:
pygame.draw.line(
screen,
pygame.Color(0,255,0) if side else pygame.Color(255,0,0),
(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,
):
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)
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
pygame.draw.line(
screen,
pygame.Color(192,192,192) if side else
pygame.Color(255,255,255),
(x, draw_start),
(x, draw_end),
)
def ray(
ray_pos: pygame.Vector2,
ray_dir: pygame.Vector2,
world_map: maze.Maze,
max_dis_squared: int = 10_000,
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))
map_pos = pygame.Vector2(int(ray_pos.x),int(ray_pos.y))
step = pygame.Vector2(0,0)
side_dis = 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
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
): return 0, None
# if debug: print(map_pos, side_dis)
if 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))
top_view = pygame.Surface((screen.get_width() // 2, screen.get_height()))
game_view = pygame.Surface((screen.get_width() // 2, 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.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.run()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
draw_map(top_view, player_pos, player_dir, world_map)
draw_game(game_view, player_pos, player_dir, world_map)
keys = pygame.key.get_pressed()
if keys[pygame.K_w]:
player_pos += player_dir * dt
if keys[pygame.K_s]:
player_pos -= player_dir * dt
if keys[pygame.K_a]:
player_pos -= player_dir.rotate(90) * dt
if keys[pygame.K_d]:
player_pos += player_dir.rotate(90) * dt
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_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_ESCAPE]:
running = False
screen.blit(top_view, (0,0))
screen.blit(game_view, (screen.get_width() // 2,0))
label = myfont.render(
f"Player: {player_pos} {player_dir} {round(player_dir.length())}",
True, (192,192,192)
)
screen.blit(label, (10, 10))
pygame.display.update()
dt = clock.tick(60) / 1000
pygame.quit()
game_loop()