commit fa2fdaf4c0ffb940bee02132204c7979dcc4746a Author: serenitatis <2+serenitatis@noreply.localhost> Date: Fri Jun 19 21:49:48 2026 +0300 Загрузить файлы в «/» diff --git a/3d.py b/3d.py new file mode 100644 index 0000000..c54ae81 --- /dev/null +++ b/3d.py @@ -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() \ No newline at end of file diff --git a/floor.png b/floor.png new file mode 100644 index 0000000..71827a0 Binary files /dev/null and b/floor.png differ diff --git a/top.png b/top.png new file mode 100644 index 0000000..98bd0ae Binary files /dev/null and b/top.png differ diff --git a/wall.png b/wall.png new file mode 100644 index 0000000..24c505e Binary files /dev/null and b/wall.png differ