```python
# raycaster.py

import math
import pygame
from map import game_map

class Raycaster:
    def __init__(self, screen, player, textures, fov=60, max_depth=20):
        self.screen = screen
        self.player = player
        self.textures = textures
        self.map = game_map
        self.screen_width, self.screen_height = self.screen.get_size()
        self.fov = math.radians(fov)
        self.max_depth = max_depth

        # Количество лучей равное ширине экрана (1 луч на пиксель)
        self.num_rays = self.screen_width
        self.delta_angle = self.fov / self.num_rays
        self.dist_proj_plane = (self.screen_width / 2) / math.tan(self.fov / 2)

    def cast_single_ray(self, angle):
        sin_a = math.sin(angle)
        cos_a = math.cos(angle)

        # Текущие координаты в сетке карты
        map_x = int(self.player.x)
        map_y = int(self.player.y)

        # Расстояния до следующего пересечения по x и y
        delta_dist_x = abs(1 / cos_a) if cos_a != 0 else 1e30
        delta_dist_y = abs(1 / sin_a) if sin_a != 0 else 1e30

        # Шаги по x и y
        if cos_a < 0:
            step_x = -1
            side_dist_x = (self.player.x - map_x) * delta_dist_x
        else:
            step_x = 1
            side_dist_x = (map_x + 1.0 - self.player.x) * delta_dist_x

        if sin_a < 0:
            step_y = -1
            side_dist_y = (self.player.y - map_y) * delta_dist_y
        else:
            step_y = 1
            side_dist_y = (map_y + 1.0 - self.player.y) * delta_dist_y

        # DDA алгоритм
        hit = False
        side = None
        while not hit:
            if side_dist_x < side_dist_y:
                side_dist_x += delta_dist_x
                map_x += step_x
                side = 'vert'
            else:
                side_dist_y += delta_dist_y
                map_y += step_y
                side = 'hor'

            # Проверка на пересечение стены
            if 0 <= map_x < len(self.map[0]) and 0 <= map_y < len(self.map):
                if self.map[map_y][map_x] != 0:
                    hit = True
                    break
            else:
                # Выход за пределы карты
                return self.max_depth, None, 0

        # Вычисление расстояния до точки пересечения
        if side == 'vert':
            perp_wall_dist = (map_x - self.player.x + (1 - step_x) / 2) / cos_a
            offset = self.player.y + perp_wall_dist * sin_a
        else:
            perp_wall_dist = (map_y - self.player.y + (1 - step_y) / 2) / sin_a
            offset = self.player.x + perp_wall_dist * cos_a

        offset -= math.floor(offset)

        # Получение типа стены
        wall_type = self.map[map_y][map_x]

        # Загрузка текстуры
        texture = self.textures.get(str(wall_type), self.textures['1'])

        return perp_wall_dist, texture, offset

    def render(self):
        # 1. Отрисовка потолка и пола сначала
        floor_texture = self.textures.get('floor')
        ceiling_texture = self.textures.get('ceiling')
        if floor_texture and ceiling_texture:
            # Создание поверхностей для пола и потолка
            floor_scaled = pygame.transform.scale(
                floor_texture,
                (self.screen_width, self.screen_height // 2)
            )
            ceiling_scaled = pygame.transform.scale(
                ceiling_texture,
                (self.screen_width, self.screen_height // 2)
            )
            # Отрисовка потолка и пола
            self.screen.blit(ceiling_scaled, (0, 0))
            self.screen.blit(floor_scaled, (0, self.screen_height // 2))
        else:
            # Если текстуры отсутствуют, рисуем однотонные цвета
            pygame.draw.rect(self.screen, (100, 100, 100), (0, 0, self.screen_width, self.screen_height // 2))  # Потолок
            pygame.draw.rect(self.screen, (70, 70, 70), (0, self.screen_height // 2, self.screen_width, self.screen_height // 2))  # Пол

        # 2. Отрисовка стен
        current_angle = self.player.angle - self.fov / 2

        for ray in range(self.num_rays):
            depth, texture, offset = self.cast_single_ray(current_angle)

            # Коррекция эффекта "кошачьего глаза"
            depth_corrected = depth * math.cos(self.player.angle - current_angle)

            # Вычисление высоты проекции стены
            if depth_corrected > 0:
                proj_height = min(int(self.dist_proj_plane / (depth_corrected + 1e-5)), self.screen_height)
            else:
                proj_height = self.screen_height

            # Затенение на основе расстояния
            shade = max(255 - int(depth_corrected * 12), 30)

            if texture:
                # Вычисление координаты текстуры по x
                tex_width, tex_height = texture.get_size()
                tex_x = int(offset * tex_width)
                tex_x = max(0, min(tex_x, tex_width - 1))  # Ограничение координаты

                # Получение вертикального среза текстуры
                wall_column = texture.subsurface(tex_x, 0, 1, tex_height).copy()

                # Масштабирование вертикального среза текстуры по высоте стены
                wall_column = pygame.transform.scale(wall_column, (1, proj_height))

                # Применение затенения
                wall_column.fill((shade, shade, shade), special_flags=pygame.BLEND_MULT)

                # Отрисовка стены
                self.screen.blit(wall_column, (ray, self.screen_height // 2 - proj_height // 2))
            else:
                # Если текстура не найдена, отрисовка однотонного цвета
                pygame.draw.rect(self.screen, (shade, shade, shade),
                                 (ray, self.screen_height // 2 - proj_height // 2, 1, proj_height))

            # Увеличение угла для следующего луча
            current_angle += self.delta_angle
```
```python
# main.py

import pygame
import sys
import math
from player import Player
from raycaster import Raycaster
from map import game_map

def load_textures():
    textures = {}
    try:
        # Загрузка текстуры для стен типа 1
        textures['1'] = pygame.image.load('textures/wall1.png').convert_alpha()
        textures['1'] = pygame.transform.scale(textures['1'], (64, 64))
    except pygame.error:
        print("Не удалось загрузить 'wall1.png'. Используется цвет по умолчанию.")
        wall1 = pygame.Surface((64, 64))
        wall1.fill((200, 0, 0))
        textures['1'] = wall1

    try:
        # Загрузка текстуры для стен типа 2
        textures['2'] = pygame.image.load('textures/wall2.png').convert_alpha()
        textures['2'] = pygame.transform.scale(textures['2'], (64, 64))
    except pygame.error:
        print("Не удалось загрузить 'wall2.png'. Используется цвет по умолчанию.")
        wall2 = pygame.Surface((64, 64))
        wall2.fill((0, 200, 0))
        textures['2'] = wall2

    try:
        # Загрузка текстуры пола
        textures['floor'] = pygame.image.load('textures/floor.png').convert_alpha()
        textures['floor'] = pygame.transform.scale(textures['floor'], (64, 64))
    except pygame.error:
        print("Не удалось загрузить 'floor.png'. Используется цвет по умолчанию.")
        floor = pygame.Surface((64, 64))
        floor.fill((50, 50, 50))
        textures['floor'] = floor

    try:
        # Загрузка текстуры потолка
        textures['ceiling'] = pygame.image.load('textures/ceiling.png').convert_alpha()
        textures['ceiling'] = pygame.transform.scale(textures['ceiling'], (64, 64))
    except pygame.error:
        print("Не удалось загрузить 'ceiling.png'. Используется цвет по умолчанию.")
        ceiling = pygame.Surface((64, 64))
        ceiling.fill((80, 80, 80))
        textures['ceiling'] = ceiling

    return textures

def main():
    pygame.init()
    clock = pygame.time.Clock()

    # Параметры окна
    SCREEN_WIDTH = 800
    SCREEN_HEIGHT = 600
    screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
    pygame.display.set_caption("Raycasting Game - Профессиональная Версия")

    # Скрываем курсор и захватываем мышь
    pygame.event.set_grab(True)
    pygame.mouse.set_visible(False)

    # Загрузка текстур
    textures = load_textures()

    # Инициализация игрока
    # Инициализируем в свободной области карты, например, (1.5, 1.5)
    player = Player(x=1.5, y=1.5, angle=0)

    # Проверяем, что начальная позиция свободна
    if game_map[int(player.y)][int(player.x)] != 0:
        print("Ошибка: начальная позиция игрока занята стеной.")
        pygame.quit()
        sys.exit()

    # Инициализация рэйкастера
    raycaster = Raycaster(screen, player, textures)

    # Основной игровой цикл
    running = True
    while running:
        delta_time = clock.tick(60) / 1000.0  # Время с прошлого кадра (секунды)

        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                running = False
                break
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_ESCAPE:
                    running = False
                    break

        # Обработка ввода с клавиатуры
        keys = pygame.key.get_pressed()
        if keys[pygame.K_w]:
            player.move(game_map, 'forward', delta_time)
        if keys[pygame.K_s]:
            player.move(game_map, 'backward', delta_time)
        if keys[pygame.K_a]:
            player.rotate('left', delta_time)
        if keys[pygame.K_d]:
            player.rotate('right', delta_time)

        # Обработка ввода с мыши для вращения обзора
        mouse_dx, _ = pygame.mouse.get_rel()
        sensitivity = 0.002  # Настройка чувствительности мыши
        player.angle += mouse_dx * sensitivity
        player.angle %= 2 * math.pi  # Ограничение угла в диапазоне [0, 2π]

        # Рендеринг сцены
        raycaster.render()

        # Обновление экрана
        pygame.display.flip()

    pygame.quit()
    sys.exit()

if __name__ == "__main__":
    main()
```
```python
# player.py

import math

class Player:
    def __init__(self, x, y, angle=0):
        self.x = x
        self.y = y
        self.angle = angle  # В радианах
        self.move_speed = 3.0  # Ячейки в секунду
        self.rot_speed = 120.0  # Угловой поворот в градусах в секунду

    def move(self, game_map, direction, delta_time):
        # Направление движения: 'forward' или 'backward'
        rad_angle = self.angle
        if direction == 'backward':
            rad_angle = (self.angle + math.pi) % (2 * math.pi)

        dx = math.cos(rad_angle) * self.move_speed * delta_time
        dy = math.sin(rad_angle) * self.move_speed * delta_time

        new_x = self.x + dx
        new_y = self.y + dy

        # Проверка коллизий
        if 0 <= int(new_y) < len(game_map) and 0 <= int(self.x) < len(game_map[0]):
            if game_map[int(new_y)][int(self.x)] == 0:
                self.y = new_y
        if 0 <= int(self.y) < len(game_map) and 0 <= int(new_x) < len(game_map[0]):
            if game_map[int(self.y)][int(new_x)] == 0:
                self.x = new_x

    def rotate(self, direction, delta_time):
        # Направление поворота: 'left' или 'right'
        rot_amount = math.radians(self.rot_speed) * delta_time
        if direction == 'right':
            self.angle += rot_amount
        elif direction == 'left':
            self.angle -= rot_amount
        self.angle %= 2 * math.pi
```
Improve rendering, add shadows, optimize code, improve code and structure