Загрузить файлы в «/»

This commit is contained in:
2026-06-19 21:49:48 +03:00
commit fa2fdaf4c0
4 changed files with 300 additions and 0 deletions
+300
View File
@@ -0,0 +1,300 @@
import pygame
import math
import random
import os
WIDTH, HEIGHT = 800, 600
HALF_HEIGHT = HEIGHT // 2
FPS = 60
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("3DMAZE")
clock = pygame.time.Clock()
TILE_SIZE = 64
fov = math.pi / 3
NUM_RAYS = 160
SCALE = WIDTH // NUM_RAYS
# --- НАСТРОЙКА РАЗМЕРА ЛАБИРИНТА ---
MAP_SIZE = 32 # Задайте любой размер (например, 32, 64, 128)
world_map = {}
def generate_maze(size):
"""Генерирует карту заданного размера с границами и случайными стенами"""
grid = {}
for row in range(size):
for col in range(size):
# Внешние границы лабиринта — всегда стены
if col == 0 or col == size - 1 or row == 0 or row == size - 1:
grid[(col, row)] = 1
# Стартовая зона 3х3 в левом верхнем углу — всегда пустая
elif 1 <= col <= 3 and 1 <= row <= 3:
grid[(col, row)] = 0
else:
# Внутри — случайные стены (плотность 30%)
grid[(col, row)] = 1 if random.random() < 0.30 else 0
return grid
world_map = generate_maze(MAP_SIZE)
# Игрок начинает в пустой зоне (клетка 2, 2)
cell_x = 2
cell_y = 2
player_x = cell_x * TILE_SIZE + 32
player_y = cell_y * TILE_SIZE + 32
player_angle = 0.0
player_speed = 100
rotation_speed = 2.0
demo_mode = True
day_mode = False
current_dir_index = 0
DIRECTIONS = [0.0, math.pi / 2, math.pi, -math.pi / 2]
ai_state = "MOVE"
target_angle = 0.0
def get_wall_type(col, row):
# Если вышли за пределы сгенерированной карты, возвращаем стену
if (col, row) in world_map:
return world_map[(col, row)]
return 1
def load_or_generate_textures():
if os.path.exists("wall.png"):
try:
wall_tex = pygame.image.load("wall.png").convert()
wall_tex = pygame.transform.scale(wall_tex, (TILE_SIZE, TILE_SIZE))
except Exception: wall_tex = None
else: wall_tex = None
if wall_tex is None:
wall_tex = pygame.Surface((TILE_SIZE, TILE_SIZE))
wall_tex.fill((125, 45, 30))
for y in [0, 16, 32, 48, 63]: pygame.draw.line(wall_tex, (110, 105, 100), (0, y), (64, y), 2)
for x in [0, 32, 64]:
pygame.draw.line(wall_tex, (110, 105, 100), (x, 0), (x, 16), 2)
pygame.draw.line(wall_tex, (110, 105, 100), (x, 32), (x, 48), 2)
for x in [16, 48]:
pygame.draw.line(wall_tex, (110, 105, 100), (x, 16), (x, 32), 2)
pygame.draw.line(wall_tex, (110, 105, 100), (x, 48), (x, 64), 2)
if os.path.exists("top.png"):
try:
ceil_tex = pygame.image.load("top.png").convert()
ceil_tex = pygame.transform.scale(ceil_tex, (TILE_SIZE, TILE_SIZE))
except Exception: ceil_tex = None
else: ceil_tex = None
if ceil_tex is None:
ceil_tex = pygame.Surface((TILE_SIZE, TILE_SIZE))
ceil_tex.fill((75, 75, 75))
if os.path.exists("floor.png"):
try:
floor_tex = pygame.image.load("floor.png").convert()
floor_tex = pygame.transform.scale(floor_tex, (TILE_SIZE, TILE_SIZE))
except Exception: floor_tex = None
else: floor_tex = None
if floor_tex is None:
floor_tex = pygame.Surface((TILE_SIZE, TILE_SIZE))
floor_tex.fill((45, 45, 50))
for i in range(0, TILE_SIZE, 16):
pygame.draw.line(floor_tex, (25, 25, 28), (i, 0), (i, TILE_SIZE), 1)
pygame.draw.line(floor_tex, (25, 25, 28), (0, i), (TILE_SIZE, i), 1)
return wall_tex, ceil_tex, floor_tex
tex_wall, tex_ceil, tex_floor = load_or_generate_textures()
running = True
while running:
dt = clock.tick(FPS) / 1000.0
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_y:
demo_mode = not demo_mode
ai_state = "MOVE"
elif event.key == pygame.K_l:
day_mode = not day_mode
if demo_mode:
if ai_state == "MOVE":
target_x = cell_x * TILE_SIZE + 32
target_y = cell_y * TILE_SIZE + 32
move_cos = round(math.cos(DIRECTIONS[current_dir_index]))
move_sin = round(math.sin(DIRECTIONS[current_dir_index]))
dist = math.hypot(target_x - player_x, target_y - player_y)
if dist > 1.0:
step = player_speed * dt
if step > dist: step = dist
player_x += move_cos * step
player_y += move_sin * step
else:
# Мы дошли до центра клетки. Определяем, куда можно идти дальше.
player_x, player_y = target_x, target_y
# Проверяем все 4 направления: какое из них свободно?
available_dirs = []
for idx, angle in enumerate(DIRECTIONS):
t_cos = round(math.cos(angle))
t_sin = round(math.sin(angle))
if get_wall_type(cell_x + t_cos, cell_y + t_sin) == 0:
available_dirs.append(idx)
# Направление, противоположное текущему (откуда пришли)
backward_dir_index = (current_dir_index + 2) % 4
# Фильтруем варианты, убирая путь назад (чтобы не топтаться на месте)
forward_and_sides = [d for d in available_dirs if d != backward_dir_index]
if forward_and_sides:
# Если можно идти вперед, даем этому приоритет (например, 75%)
if current_dir_index in forward_and_sides and random.random() < 0.75:
next_dir = current_dir_index
else:
# В остальных случаях случайно выбираем поворот (налево/направо/вперед)
next_dir = random.choice(forward_and_sides)
else:
# Если идти вообще некуда (тупик) — только тогда разворачиваемся назад
next_dir = backward_dir_index if backward_dir_index in available_dirs else current_dir_index
# Если направление изменилось, запускаем режим поворота камеры
if next_dir != current_dir_index:
current_dir_index = next_dir
target_angle = DIRECTIONS[current_dir_index]
ai_state = "TURN"
else:
# Если идем прямо, просто обновляем целевую клетку
cell_x += move_cos
cell_y += move_sin
elif ai_state == "TURN":
angle_diff = (target_angle - player_angle + math.pi) % (2 * math.pi) - math.pi
if abs(angle_diff) > 0.01:
player_angle += math.copysign(min(rotation_speed * dt, abs(angle_diff)), angle_diff)
else:
player_angle = target_angle
move_cos = round(math.cos(DIRECTIONS[current_dir_index]))
move_sin = round(math.sin(DIRECTIONS[current_dir_index]))
cell_x += move_cos
cell_y += move_sin
ai_state = "MOVE"
else:
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT] or keys[pygame.K_a]: player_angle -= 3.0 * dt
if keys[pygame.K_RIGHT] or keys[pygame.K_d]: player_angle += 3.0 * dt
cos_a, sin_a = math.cos(player_angle), math.sin(player_angle)
mx = player_speed * cos_a * dt if (keys[pygame.K_w] or keys[pygame.K_UP]) else (-player_speed * cos_a * dt if (keys[pygame.K_s] or keys[pygame.K_DOWN]) else 0)
my = player_speed * sin_a * dt if (keys[pygame.K_w] or keys[pygame.K_UP]) else (-player_speed * sin_a * dt if (keys[pygame.K_s] or keys[pygame.K_DOWN]) else 0)
r = 12
if get_wall_type(int((player_x + mx + math.copysign(r, mx)) // TILE_SIZE), int(player_y // TILE_SIZE)) == 0: player_x += mx
if get_wall_type(int(player_x // TILE_SIZE), int((player_y + my + math.copysign(r, my)) // TILE_SIZE)) == 0: player_y += my
cell_x = int(player_x // TILE_SIZE)
cell_y = int(player_y // TILE_SIZE)
screen.fill((140, 140, 145) if day_mode else (15, 15, 15))
# Рэйкастинг
start_angle = player_angle - fov / 2
for ray in range(NUM_RAYS):
angle = start_angle + ray * (fov / NUM_RAYS)
r_cos = math.cos(angle) if math.cos(angle) != 0 else 0.00001
r_sin = math.sin(angle) if math.sin(angle) != 0 else 0.00001
# Вертикали
x_depth, x_texture = 9999, 0
x_step = TILE_SIZE / r_cos
first_x = (int(player_x // TILE_SIZE) + 1) * TILE_SIZE if r_cos > 0 else int(player_x // TILE_SIZE) * TILE_SIZE
current_depth_x = (first_x - player_x) / r_cos
for _ in range(30):
tx = player_x + current_depth_x * r_cos
ty = player_y + current_depth_x * r_sin
col = int((tx + (1 if r_cos > 0 else -1)) // TILE_SIZE)
row = int(ty // TILE_SIZE)
if get_wall_type(col, row) > 0:
x_depth, x_texture = current_depth_x, int(ty % TILE_SIZE)
break
current_depth_x += abs(x_step)
# Горизонтали
y_depth, y_texture = 9999, 0
y_step = TILE_SIZE / r_sin
first_y = (int(player_y // TILE_SIZE) + 1) * TILE_SIZE if r_sin > 0 else int(player_y // TILE_SIZE) * TILE_SIZE
current_depth_y = (first_y - player_y) / r_sin
for _ in range(30):
tx = player_x + current_depth_y * r_cos
ty = player_y + current_depth_y * r_sin
col = int(tx // TILE_SIZE)
row = int((ty + (1 if r_sin > 0 else -1)) // TILE_SIZE)
if get_wall_type(col, row) > 0:
y_depth, y_texture = current_depth_y, int(tx % TILE_SIZE)
break
current_depth_y += abs(y_step)
if x_depth < y_depth:
depth, x_tex_coord, is_vertical_wall = x_depth, x_texture, True
else:
depth, x_tex_coord, is_vertical_wall = y_depth, y_texture, False
if depth < 9999:
fixed_depth = depth * math.cos(player_angle - angle)
fixed_depth = max(fixed_depth, 1)
proj_height = min(int((TILE_SIZE / fixed_depth) * 600), HEIGHT * 2)
wall_column = tex_wall.subsurface(x_tex_coord, 0, 1, TILE_SIZE)
wall_column = pygame.transform.scale(wall_column, (SCALE, proj_height))
if day_mode: shadow = 15 if is_vertical_wall else 0
else:
base_shadow = int(fixed_depth * 0.75)
if is_vertical_wall: base_shadow += 20
shadow = max(0, min(255, base_shadow))
if shadow > 0:
shadow_surf = pygame.Surface((SCALE, proj_height))
shadow_surf.fill((shadow, shadow, shadow))
wall_column.blit(shadow_surf, (0, 0), special_flags=pygame.BLEND_SUB)
wall_y_pos = HALF_HEIGHT - proj_height // 2
screen.blit(wall_column, (ray * SCALE, wall_y_pos))
# Пол / Потолок
cos_tilt = math.cos(player_angle - angle)
for screen_y in range(0, wall_y_pos, 4):
if screen_y >= HALF_HEIGHT: break
current_dist = (TILE_SIZE * HEIGHT) / (2.0 * (HALF_HEIGHT - screen_y) * cos_tilt)
tex_x = int(player_x + r_cos * current_dist) % TILE_SIZE
tex_y = int(player_y + r_sin * current_dist) % TILE_SIZE
f_shadow = 0 if day_mode else max(0, min(255, int(current_dist * 0.75)))
c_color = tex_ceil.get_at((tex_x, tex_y))
if f_shadow > 0:
c_color = (max(0, c_color[0] - f_shadow), max(0, c_color[1] - f_shadow), max(0, c_color[2] - f_shadow))
pygame.draw.rect(screen, c_color, (ray * SCALE, screen_y, SCALE, 4))
floor_y = HEIGHT - screen_y - 4
fl_color = tex_floor.get_at((tex_x, tex_y))
if f_shadow > 0:
fl_color = (max(0, fl_color[0] - f_shadow), max(0, fl_color[1] - f_shadow), max(0, fl_color[2] - f_shadow))
pygame.draw.rect(screen, fl_color, (ray * SCALE, floor_y, SCALE, 4))
pygame.display.flip()
pygame.quit()
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB