```python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
Hammer Editor Source Map GENERATOR 2D
Генерирует карту с комнатами и коридорами, визуализирует её и экспортирует в формат VMF для Hammer Editor.
"""

import argparse
import math
import random
from dataclasses import dataclass
from typing import List, Tuple, Optional, Set

import matplotlib.pyplot as plt
import matplotlib.patches as patches
import networkx as nx


@dataclass
class Config:
    num_rooms: int = 20
    map_width: int = 200
    map_height: int = 200
    room_min_size: int = 5
    room_max_size: int = 20
    passageway_width: int = 3
    extra_connections_factor: float = 0.4
    max_attempts_multiplier: int = 10
    # Set an integer for reproducible results
    random_seed: Optional[int] = None


@dataclass
class Room:
    x: int
    y: int
    width: int
    height: int

    def center(self) -> Tuple[float, float]:
        """Calculate the center coordinates of the room."""
        center_x = self.x + self.width / 2
        center_y = self.y + self.height / 2
        return center_x, center_y

    def intersects(self, other: 'Room') -> bool:
        """Check if this room intersects with another room."""
        return not (
            self.x + self.width <= other.x or
            self.x >= other.x + other.width or
            self.y + self.height <= other.y or
            self.y >= other.y + other.height
        )


class SpatialGrid:
    """Spatial grid for efficient room collision detection."""

    def __init__(self, map_width: int, map_height: int, cell_size: int):
        self.cell_size = cell_size
        self.cols = math.ceil(map_width / cell_size)
        self.rows = math.ceil(map_height / cell_size)
        self.grid = [[[] for _ in range(self.rows)] for _ in range(self.cols)]

    def _get_cells_for_room(self, room: Room) -> Set[Tuple[int, int]]:
        """Determine which grid cells the room occupies."""
        start_col = room.x // self.cell_size
        end_col = (room.x + room.width) // self.cell_size
        start_row = room.y // self.cell_size
        end_row = (room.y + room.height) // self.cell_size
        cells = set()
        for col in range(start_col, end_col + 1):
            for row in range(start_row, end_row + 1):
                if 0 <= col < self.cols and 0 <= row < self.rows:
                    cells.add((col, row))
        return cells

    def add_room(self, room: Room):
        """Add a room to the grid."""
        cells = self._get_cells_for_room(room)
        for col, row in cells:
            self.grid[col][row].append(room)

    def has_intersection(self, room: Room) -> bool:
        """Check if the room intersects with any existing rooms in relevant grid cells."""
        cells = self._get_cells_for_room(room)
        for col, row in cells:
            for existing_room in self.grid[col][row]:
                if room.intersects(existing_room):
                    return True
        return False


class MapGenerator:
    def __init__(self, config: Config):
        self.config = config
        if config.random_seed is not None:
            random.seed(config.random_seed)
        # Initialize spatial grid with cell size equal to the max room size
        self.spatial_grid = SpatialGrid(
            config.map_width,
            config.map_height,
            config.room_max_size
        )

    def generate_rooms(self) -> List[Room]:
        """Generate non-overlapping rooms within the map boundaries."""
        rooms = []
        max_attempts = self.config.num_rooms * self.config.max_attempts_multiplier
        attempts = 0

        while len(rooms) < self.config.num_rooms and attempts < max_attempts:
            width = random.randint(
                self.config.room_min_size, self.config.room_max_size)
            height = random.randint(
                self.config.room_min_size, self.config.room_max_size)
            x = random.randint(0, self.config.map_width - width)
            y = random.randint(0, self.config.map_height - height)
            new_room = Room(x, y, width, height)

            if not self.spatial_grid.has_intersection(new_room):
                rooms.append(new_room)
                self.spatial_grid.add_room(new_room)
            attempts += 1

        if len(rooms) < self.config.num_rooms:
            print(
                f"Warning: Only {len(rooms)} out of {self.config.num_rooms} rooms were placed "
                f"after {attempts} attempts."
            )
        return rooms

    def connect_rooms(self, rooms: List[Room]) -> nx.Graph:
        """Connect rooms using Minimum Spanning Tree and add extra connections."""
        G = nx.Graph()

        # Add nodes with positions
        for idx, room in enumerate(rooms):
            G.add_node(idx, pos=room.center())

        # Add all possible edges with Euclidean distance as weight
        for i in range(len(rooms)):
            for j in range(i + 1, len(rooms)):
                pos_i = rooms[i].center()
                pos_j = rooms[j].center()
                distance = math.hypot(pos_i[0] - pos_j[0], pos_i[1] - pos_j[1])
                G.add_edge(i, j, weight=distance)

        # Compute Minimum Spanning Tree (MST)
        mst = nx.minimum_spanning_tree(G)

        # Determine number of additional connections
        extra_connections = int(
            self.config.extra_connections_factor * len(rooms))
        potential_extra = set(G.edges()) - set(mst.edges())

        if potential_extra:
            # Sort potential extras by weight (can also shuffle for randomness)
            sorted_extra = sorted(
                potential_extra,
                key=lambda edge: G.edges[edge]['weight']
            )
            num_extra = min(extra_connections, len(sorted_extra))
            extra_edges = sorted_extra[:num_extra]
            mst.add_edges_from(extra_edges)

        return mst


class MapVisualizer:
    def __init__(self, config: Config):
        self.config = config

    def draw_map_2d(self, rooms: List[Room], connections: nx.Graph) -> None:
        """Render the map in 2D using matplotlib."""
        fig, ax = plt.subplots(figsize=(15, 15))

        # Draw rooms
        for idx, room in enumerate(rooms):
            rect = patches.Rectangle(
                (room.x, room.y),
                room.width,
                room.height,
                linewidth=1,
                edgecolor='black',
                facecolor='cyan',
                alpha=0.8
            )
            ax.add_patch(rect)

            cx, cy = room.center()
            ax.plot(cx, cy, 'ro')  # Red dot at center
            ax.text(
                cx, cy, str(idx + 1),
                color='white', ha='center', va='center',
                fontsize=8, fontweight='bold'
            )

        # Draw corridors as orthogonal corridors
        for edge in connections.edges():
            self._draw_corridor_2d(ax, rooms[edge[0]], rooms[edge[1]])

        # Configure plot
        ax.set_xlim(0, self.config.map_width)
        ax.set_ylim(0, self.config.map_height)
        ax.set_aspect('equal')
        ax.set_title(
            'Hammer Editor - Randomly Generated Rooms',
            fontsize=16
        )
        ax.set_xlabel('X', fontsize=12)
        ax.set_ylabel('Y', fontsize=12)
        ax.grid(True, which='both', linestyle='--', linewidth=0.5, alpha=0.7)
        plt.show()

    def _draw_corridor_2d(self, ax, room_a: Room, room_b: Room) -> None:
        """Draw an orthogonal corridor between two rooms in 2D."""
        ax_center = room_a.center()
        bx_center = room_b.center()

        # Randomly decide whether to go horizontal first or vertical
        horizontal_first = random.choice([True, False])

        if horizontal_first:
            intermediate_point = (bx_center[0], ax_center[1])
            self._draw_rectangle_corridor(
                ax,
                start=ax_center,
                end=intermediate_point,
                orientation='horizontal'
            )
            self._draw_rectangle_corridor(
                ax,
                start=intermediate_point,
                end=bx_center,
                orientation='vertical'
            )
        else:
            intermediate_point = (ax_center[0], bx_center[1])
            self._draw_rectangle_corridor(
                ax,
                start=ax_center,
                end=intermediate_point,
                orientation='vertical'
            )
            self._draw_rectangle_corridor(
                ax,
                start=intermediate_point,
                end=bx_center,
                orientation='horizontal'
            )

    def _draw_rectangle_corridor(self, ax, start: Tuple[float, float], end: Tuple[float, float], orientation: str) -> None:
        """Helper method to draw a rectangular corridor."""
        if orientation == 'horizontal':
            corridor_length = abs(end[0] - start[0])
            corridor_x = min(start[0], end[0])
            corridor_y = start[1] - self.config.passageway_width / 2
            rectangle = patches.Rectangle(
                (corridor_x, corridor_y),
                corridor_length,
                self.config.passageway_width,
                linewidth=0,
                edgecolor=None,
                facecolor='blue',
                alpha=0.6
            )
        else:
            corridor_length = abs(end[1] - start[1])
            corridor_x = start[0] - self.config.passageway_width / 2
            corridor_y = min(start[1], end[1])
            rectangle = patches.Rectangle(
                (corridor_x, corridor_y),
                self.config.passageway_width,
                corridor_length,
                linewidth=0,
                edgecolor=None,
                facecolor='blue',
                alpha=0.6
            )
        ax.add_patch(rectangle)


class VMFGenerator:
    """Класс для генерации VMF-файла из комнат и коридоров."""

    def __init__(self, config: Config, rooms: List[Room], connections: nx.Graph):
        self.config = config
        self.rooms = rooms
        self.connections = connections
        self.vmf = []
        self.brush_id = 1
        self.side_id = 1  # Each side of a brush needs a unique ID

    def generate_vmf(self) -> str:
        """Сгенерировать содержимое VMF-файла."""
        self._add_versioninfo()
        self._add_visgroups()
        self._add_viewsettings()
        self._add_palette_plus()
        self._add_colorcorrection_plus()
        self._add_light_plus()
        self._add_postprocess_plus()
        self._add_bgimages_plus()
        self._add_world()
        self._add_entities()
        return '\n'.join(self.vmf)

    def _add_versioninfo(self):
        """Добавить блок versioninfo."""
        self.vmf.append('versioninfo')
        self.vmf.append('{')
        self.vmf.append('    "editorversion" "400"')
        self.vmf.append('    "editorbuild" "8868"')
        self.vmf.append('    "mapversion" "0"')
        self.vmf.append('    "formatversion" "100"')
        self.vmf.append('    "prefab" "0"')
        self.vmf.append('}')

    def _add_visgroups(self):
        """Добавить блок visgroups."""
        self.vmf.append('visgroups')
        self.vmf.append('{')
        self.vmf.append('}')

    def _add_viewsettings(self):
        """Добавить блок viewsettings."""
        self.vmf.append('viewsettings')
        self.vmf.append('{')
        self.vmf.append('    "bSnapToGrid" "1"')
        self.vmf.append('    "bShowGrid" "1"')
        self.vmf.append('    "bShowLogicalGrid" "0"')
        self.vmf.append('    "nGridSpacing" "64"')
        self.vmf.append('}')

    def _add_palette_plus(self):
        """Добавить блок palette_plus с цветами по умолчанию."""
        self.vmf.append('palette_plus')
        self.vmf.append('{')
        for i in range(16):
            self.vmf.append(f'    "color{i}" "255 255 255"')
        self.vmf.append('}')

    def _add_colorcorrection_plus(self):
        """Добавить блок colorcorrection_plus."""
        self.vmf.append('colorcorrection_plus')
        self.vmf.append('{')
        for i in range(4):
            self.vmf.append(f'    "name{i}" ""')
            self.vmf.append(f'    "weight{i}" "1"')
        self.vmf.append('}')

    def _add_light_plus(self):
        """Добавить блок light_plus."""
        self.vmf.append('light_plus')
        self.vmf.append('{')
        self.vmf.append('    "samples_sun" "6"')
        self.vmf.append('    "samples_ambient" "40"')
        self.vmf.append('    "samples_vis" "256"')
        self.vmf.append('    "texlight" ""')
        self.vmf.append('    "incremental_delay" "0"')
        self.vmf.append('    "bake_dist" "1024"')
        self.vmf.append('    "radius_scale" "1"')
        self.vmf.append('    "brightness_scale" "1"')
        self.vmf.append('    "ao_scale" "0"')
        self.vmf.append('    "bounced" "1"')
        self.vmf.append('    "incremental" "1"')
        self.vmf.append('    "supersample" "0"')
        self.vmf.append('    "bleed_hack" "1"')
        self.vmf.append('    "soften_cosine" "0"')
        self.vmf.append('    "debug" "0"')
        self.vmf.append('    "cubemap" "1"')
        self.vmf.append('}')

    def _add_postprocess_plus(self):
        """Добавить блок postprocess_plus."""
        self.vmf.append('postprocess_plus')
        self.vmf.append('{')
        self.vmf.append('    "enable" "1"')
        self.vmf.append('    "copied_from_controller" "0"')
        self.vmf.append('    "bloom_scale" "1"')
        self.vmf.append('    "bloom_exponent" "2.5"')
        self.vmf.append('    "bloom_saturation" "1"')
        self.vmf.append('    "auto_exposure_min" "0.5"')
        self.vmf.append('    "auto_exposure_max" "2"')
        self.vmf.append('    "tonemap_percent_target" "60"')
        self.vmf.append('    "tonemap_percent_bright_pixels" "2"')
        self.vmf.append('    "tonemap_min_avg_luminance" "3"')
        self.vmf.append('    "tonemap_rate" "1"')
        self.vmf.append('    "vignette_enable" "0"')
        self.vmf.append('    "vignette_start" "1"')
        self.vmf.append('    "vignette_end" "2"')
        self.vmf.append('    "vignette_blur" "0"')
        self.vmf.append('}')

    def _add_bgimages_plus(self):
        """Добавить пустой блок bgimages_plus."""
        self.vmf.append('bgimages_plus')
        self.vmf.append('{')
        self.vmf.append('}')

    def _add_world(self):
        """Добавить блок world с brushes."""
        self.vmf.append('world')
        self.vmf.append('{')
        self.vmf.append('    "id" "1"')
        self.vmf.append('    "classname" "worldspawn"')
        self.vmf.append('    "detailmaterial" "detail/detailsprites"')
        self.vmf.append('    "detailvbsp" "detail.vbsp"')
        self.vmf.append('    "maxpropscreenwidth" "-1"')
        self.vmf.append('    "skyname" "sky_day01_01"')

        # Генерация комнат
        for idx, room in enumerate(self.rooms):
            self.vmf.append(f'    solid')
            self.vmf.append(f'    {{')
            self.vmf.append(f'        "id" "{self.brush_id}"')
            min_x = room.x
            min_y = room.y
            min_z = 0
            max_x = room.x + room.width
            max_y = room.y + room.height
            max_z = 64  # Высота комнаты

            # Определение координат для шести сторон комнаты
            # Пол (нормаль вверх, не инвертируем)
            self.vmf.extend(self._create_side(
                a=(min_x, min_y, min_z),
                b=(max_x, min_y, min_z),
                c=(max_x, max_y, min_z),
                material='"tools/toolsfloor"',
                scale_u='[1 0 0 0] [0 1 0 0]',
                scale_v='[0 1 0 0] [0 0 1 0]',
                reverse=False  # Нормаль вверх
            ))

            # Потолок (нормаль вниз, инвертируем)
            self.vmf.extend(self._create_side(
                a=(min_x, min_y, max_z),
                b=(max_x, min_y, max_z),
                c=(max_x, max_y, max_z),
                material='"tools/toolsroof"',
                scale_u='[1 0 0 0] [0 1 0 0]',
                scale_v='[0 1 0 0] [0 0 1 0]',
                reverse=True  # Нормаль вниз
            ))

            # Фронтальная стена (нормаль наружу)
            self.vmf.extend(self._create_side(
                a=(min_x, min_y, min_z),
                b=(max_x, min_y, min_z),
                c=(max_x, min_y, max_z),
                material='"tools/toolsside"',
                scale_u='[1 0 0 0] [0 1 0 0]',
                scale_v='[0 1 0 0] [0 0 1 0]',
                reverse=False
            ))

            # Задняя стена (нормаль наружу)
            self.vmf.extend(self._create_side(
                a=(min_x, max_y, min_z),
                b=(max_x, max_y, min_z),
                c=(max_x, max_y, max_z),
                material='"tools/toolsside"',
                scale_u='[1 0 0 0] [0 1 0 0]',
                scale_v='[0 1 0 0] [0 0 1 0]',
                reverse=True  # Инвертируем для правильного направления нормали
            ))

            # Левая стена (нормаль наружу)
            self.vmf.extend(self._create_side(
                a=(min_x, min_y, min_z),
                b=(min_x, max_y, min_z),
                c=(min_x, max_y, max_z),
                material='"tools/toolsside"',
                scale_u='[1 0 0 0] [0 1 0 0]',
                scale_v='[0 1 0 0] [0 0 1 0]',
                reverse=False
            ))

            # Правая стена (нормаль наружу)
            self.vmf.extend(self._create_side(
                a=(max_x, min_y, min_z),
                b=(max_x, max_y, min_z),
                c=(max_x, max_y, max_z),
                material='"tools/toolsside"',
                scale_u='[1 0 0 0] [0 1 0 0]',
                scale_v='[0 1 0 0] [0 0 1 0]',
                reverse=True  # Инвертируем для правильного направления нормали
            ))

            self.vmf.append(f'    }}')  # Закрытие solid
            self.brush_id += 1

        # Генерация коридоров
        for edge in self.connections.edges():
            room_a = self.rooms[edge[0]]
            room_b = self.rooms[edge[1]]
            corridors = self._generate_corridors(room_a, room_b)
            for corridor in corridors:
                # Проверка, что коридор находится внутри карты
                if (corridor.x < 0 or corridor.y < 0 or
                        corridor.x + corridor.width > self.config.map_width or
                        corridor.y + corridor.height > self.config.map_height):
                    print(f"Warning: Corridor at ({corridor.x}, {corridor.y}) with size ({corridor.width}, {corridor.height}) is out of map bounds and will be skipped.")
                    continue  # Пропустить коридор, выходящий за границы карты

                self.vmf.append(f'    solid')
                self.vmf.append(f'    {{')
                self.vmf.append(f'        "id" "{self.brush_id}"')
                min_x = corridor.x
                min_y = corridor.y
                min_z = 0
                max_x = corridor.x + corridor.width
                max_y = corridor.y + corridor.height
                max_z = 64  # Высота коридора

                # Определение координат для шести сторон коридора
                # Пол (нормаль вверх)
                self.vmf.extend(self._create_side(
                    a=(min_x, min_y, min_z),
                    b=(max_x, min_y, min_z),
                    c=(max_x, max_y, min_z),
                    material='"tools/toolsfloor"',
                    scale_u='[1 0 0 0] [0 1 0 0]',
                    scale_v='[0 1 0 0] [0 0 1 0]',
                    reverse=False  # Нормаль вверх
                ))

                # Потолок (нормаль вниз)
                self.vmf.extend(self._create_side(
                    a=(min_x, min_y, max_z),
                    b=(max_x, min_y, max_z),
                    c=(max_x, max_y, max_z),
                    material='"tools/toolsroof"',
                    scale_u='[1 0 0 0] [0 1 0 0]',
                    scale_v='[0 1 0 0] [0 0 1 0]',
                    reverse=True  # Нормаль вниз
                ))

                # Фронтальная стена (нормаль наружу)
                self.vmf.extend(self._create_side(
                    a=(min_x, min_y, min_z),
                    b=(max_x, min_y, min_z),
                    c=(max_x, min_y, max_z),
                    material='"tools/toolsside"',
                    scale_u='[1 0 0 0] [0 1 0 0]',
                    scale_v='[0 1 0 0] [0 0 1 0]',
                    reverse=False
                ))

                # Задняя стена (нормаль наружу)
                self.vmf.extend(self._create_side(
                    a=(min_x, max_y, min_z),
                    b=(max_x, max_y, min_z),
                    c=(max_x, max_y, max_z),
                    material='"tools/toolsside"',
                    scale_u='[1 0 0 0] [0 1 0 0]',
                    scale_v='[0 1 0 0] [0 0 1 0]',
                    reverse=True
                ))

                # Левая стена (нормаль наружу)
                self.vmf.extend(self._create_side(
                    a=(min_x, min_y, min_z),
                    b=(min_x, max_y, min_z),
                    c=(min_x, max_y, max_z),
                    material='"tools/toolsside"',
                    scale_u='[1 0 0 0] [0 1 0 0]',
                    scale_v='[0 1 0 0] [0 0 1 0]',
                    reverse=False
                ))

                # Правая стена (нормаль наружу)
                self.vmf.extend(self._create_side(
                    a=(max_x, min_y, min_z),
                    b=(max_x, max_y, min_z),
                    c=(max_x, max_y, max_z),
                    material='"tools/toolsside"',
                    scale_u='[1 0 0 0] [0 1 0 0]',
                    scale_v='[0 1 0 0] [0 0 1 0]',
                    reverse=True
                ))

                self.vmf.append(f'    }}')  # Закрытие solid
                self.brush_id += 1

        self.vmf.append('}')  # Закрытие блока 'world'

    def _add_entities(self):
        """Добавить пустой блок entities."""
        self.vmf.append('entities')
        self.vmf.append('{')
        self.vmf.append('}')

    def _create_side(self, a: Tuple[float, float, float], b: Tuple[float, float, float],
                    c: Tuple[float, float, float], material: str,
                    scale_u: str, scale_v: str, reverse: bool = False) -> List[str]:
        """Создать строку для стороны (face) в brush."""
        side = []
        side.append('        side')
        side.append('        {')
        side.append(f'            "id" "{self.side_id}"')
        # Инвертировать порядок точек, если нужно
        if reverse:
            side.append(f'            "plane" "({c[0]} {c[1]} {c[2]}) ({b[0]} {b[1]} {b[2]}) ({a[0]} {a[1]} {a[2]})"')
        else:
            side.append(f'            "plane" "({a[0]} {a[1]} {a[2]}) ({b[0]} {b[1]} {b[2]}) ({c[0]} {c[1]} {c[2]})"')
        side.append(f'            "material" {material}')
        side.append(f'            "uaxis" "{scale_u} 0.0"')
        side.append(f'            "vaxis" "{scale_v} 0.0"')
        side.append('            "rotation" "0"')
        side.append('            "lightmapscale" "16"')
        side.append('            "smoothing_groups" "0"')
        side.append('        }')
        self.side_id += 1
        return side

    def _generate_corridors(self, room_a: Room, room_b: Room) -> List[Room]:
        """Создать коридоры между двумя комнатами."""
        corridors = []
        center_a = room_a.center()
        center_b = room_b.center()

        # Определяем промежуточные точки для коридора
        horizontal_first = random.choice([True, False])
        if horizontal_first:
            intermediate = (center_b[0], center_a[1])
        else:
            intermediate = (center_a[0], center_b[1])

        # Создаем коридор от room_a до intermediate
        corridor1 = self._create_corridor_room(center_a, intermediate)
        corridors.append(corridor1)

        # Создаем коридор от intermediate до room_b
        corridor2 = self._create_corridor_room(intermediate, center_b)
        corridors.append(corridor2)

        return corridors

    def _create_corridor_room(self, start: Tuple[float, float], end: Tuple[float, float]) -> Optional[Room]:
        """Создать 'комнату' для коридора между двумя точками, обеспечивая нахождение внутри карты."""
        passage_width = self.config.passageway_width
        map_width = self.config.map_width
        map_height = self.config.map_height

        if start[0] == end[0]:  # Вертикальный коридор
            x = start[0] - passage_width / 2
            # Коррекция координат, чтобы не выйти за пределы карты
            x = max(0, min(x, map_width - passage_width))
            y = min(start[1], end[1])
            height = abs(end[1] - start[1])
            y = max(0, min(y, map_height - height))
            width = passage_width
        else:  # Горизонтальный коридор
            y = start[1] - passage_width / 2
            # Коррекция координат, чтобы не выйти за пределы карты
            y = max(0, min(y, map_height - passage_width))
            x = min(start[0], end[0])
            width = abs(end[0] - start[0])
            x = max(0, min(x, map_width - width))
            height = passage_width

        # Проверка, имеет ли коридор положительные размеры
        if width <= 0 or height <= 0:
            print(f"Warning: Corridor from ({start}) to ({end}) has non-positive size and will be skipped.")
            return None

        return Room(int(x), int(y), int(width), int(height))

    def save_vmf(self, filename: str):
        """Сохранить VMF-файл."""
        vmf_content = self.generate_vmf()
        with open(filename, 'w', encoding='utf-8') as file:
            file.write(vmf_content)
        print(f"VMF-файл успешно сохранён как '{filename}'.")


def parse_arguments() -> argparse.Namespace:
    """Parse command-line arguments."""
    parser = argparse.ArgumentParser(
        description="Hammer Editor Source Map GENERATOR 2D - Generate and visualize maps with rooms and corridors."
    )
    parser.add_argument(
        '--view', type=str, default='2d',
        choices=['2d'],
        help="Type of visualization: '2d'. Default is '2d'."
    )
    parser.add_argument(
        '--seed', type=int, default=None,
        help="Random seed for reproducibility."
    )
    # Placeholder for future configuration file support
    parser.add_argument(
        '--config', type=str, default=None,
        help="Path to a configuration file (optional)."
    )
    return parser.parse_args()


def main():
    args = parse_arguments()

    # Initialize configuration
    config = Config(
        num_rooms=20,
        map_width=200,
        map_height=200,
        room_min_size=5,
        room_max_size=20,
        passageway_width=3,
        extra_connections_factor=0.4,
        max_attempts_multiplier=10,
        random_seed=args.seed
    )

    # Generate map
    generator = MapGenerator(config)
    rooms = generator.generate_rooms()
    connections = generator.connect_rooms(rooms)

    # Visualize map
    visualizer = MapVisualizer(config)
    if args.view == '2d':
        visualizer.draw_map_2d(rooms, connections)

    # Generate and save VMF
    vmf_generator = VMFGenerator(config, rooms, connections)
    vmf_filename = "generated_map.vmf"
    vmf_generator.save_vmf(vmf_filename)


if __name__ == "__main__":
    main()
```
Почему при открытии ошибка out of range invalid solids.