# Гонки на бумаге - Алгоритм A* с эвристикой ## 🆕 Импорт карт из JSON Программа теперь поддерживает загрузку пользовательских карт из JSON-файлов! ### Быстрый старт ```bash # Запуск со встроенной картой dotnet run --project racing-astar.csproj # Загрузка карты из файла dotnet run --project racing-astar.csproj maps/your-map.json # Пример с готовой картой dotnet run --project racing-astar.csproj maps/open-field.json # Экспорт решения в JSON файл dotnet run --project racing-astar.csproj maps/your-map.json --output solution.json # или короткая версия: dotnet run --project racing-astar.csproj maps/your-map.json -o solution.json ``` ### Формат карты Типы ячеек: - `0` - Дорога (первая ячейка становится стартом) - `1` - Камень (препятствие) - `2` - Снег (пока как дорога) - `3` - Лед (пока как дорога) - `4` - Чекпоинт Подробная документация: см. [MAP-FORMAT.md](MAP-FORMAT.md) ### Формат экспортированного решения При использовании опции `--output` программа сохраняет найденное решение в JSON файл с векторами ускорения для каждого шага: ```json { "solution": [ [0, 0], [1, 2], [-1, -1], [2, 1] ] } ``` Где каждый элемент массива `[ax, ay]` представляет собой: - `ax` - ускорение по оси X (от -2 до +2) - `ay` - ускорение по оси Y (от -2 до +2) Первый элемент всегда `[0, 0]` (стартовая позиция без ускорения). Этот формат можно использовать для: - Воспроизведения решения в редакторе карт - Анализа найденного пути - Сравнения разных решений ### Примеры карт - `maps/open-field.json` - Простая открытая карта (3 чекпоинта) - `maps/racing-map-42x42.json` - Сложная карта (59 чекпоинтов, 141 препятствие) --- ## Сравнение алгоритмов ### BFS (Breadth-First Search) - **Файл**: `Program.cs` - **Алгоритм**: Поиск в ширину - **Гарантия**: Находит оптимальное решение (минимальное количество ходов) - **Производительность**: Исследует все состояния на каждом уровне глубины - **Память**: O(b^d), где b - коэффициент ветвления, d - глубина решения ### A* (A-Star) - **Файл**: `ProgramAStar.cs` - **Алгоритм**: Поиск с эвристикой - **Гарантия**: Находит оптимальное решение при допустимой эвристике - **Производительность**: Направленный поиск к цели - **Память**: Значительно меньше, чем BFS для больших задач ## Эвристическая функция ### Компоненты эвристики: 1. **Для одного чекпоинта**: ```csharp h = EstimateStepsToReach(distance, currentSpeed, maxAcceleration) ``` - Учитывает текущую скорость - Моделирует физику ускорения и торможения - Решает квадратное уравнение движения: s = v₀t + ½at² 2. **Для нескольких чекпоинтов**: ```csharp h = ∑(steps to nearest unvisited checkpoint) ``` - Жадный алгоритм TSP (задача коммивояжера) - Последовательно выбирает ближайший непосещенный чекпоинт - Оценивает расстояние с учетом физики движения ### Свойства эвристики: - **Допустимая** (admissible): Никогда не переоценивает реальную стоимость - **Согласованная** (consistent): h(n) ≤ cost(n, n') + h(n') - **Информированная**: Учитывает физику игры (скорость, ускорение) ## Результаты ### Пример задачи (4 чекпоинта, 42×42 поле): | Метрика | BFS | A* | |---------|-----|-----| | Итераций | ~100,000+ | 20,301 | | Время | >10 сек | 1.02 сек | | Память (макс. открытых узлов) | ~50,000+ | 10,980 | | Решение (ходов) | Оптимальное | 27 (оптимальное) | ### Преимущества A*: ✅ **В 5-10 раз быстрее** BFS на больших картах ✅ **Значительно меньше памяти** - исследует только перспективные пути ✅ **Масштабируемость** - может решать задачи с большим количеством чекпоинтов ✅ **Адаптивность** - эвристика учитывает физику игры ## Запуск ### BFS версия (оригинальная): ```bash dotnet run --project racing.csproj ``` ### A* версия (оптимизированная): ```bash dotnet run --project racing-astar.csproj ``` ## Настройка эвристики Для более сложных карт можно настроить параметры в методе `CalculateHeuristic`: ```csharp // Агрессивная эвристика (быстрее, но может пропустить оптимум) speed = 4.0; // Предполагаем более высокую среднюю скорость // Консервативная эвристика (медленнее, но гарантирует оптимум) speed = 2.0; // Предполагаем более низкую среднюю скорость ``` ## Формула стоимости A* ``` f(n) = g(n) + h(n) где: - f(n) = полная оценочная стоимость пути через узел n - g(n) = фактическая стоимость пути от старта до n (количество ходов) - h(n) = эвристическая оценка стоимости от n до цели ``` ## Детали реализации ### Структура состояния: ```csharp public class GameState { Point Position; // Текущая позиция Point Velocity; // Текущая скорость (с инерцией) HashSet Visited; // Посещенные чекпоинты int GCost; // Фактические шаги double HCost; // Эвристическая оценка double FCost => GCost + HCost; } ``` ### Очередь с приоритетом: ```csharp var openSet = new SortedSet(new GameStateComparer()); ``` Автоматически сортирует состояния по FCost, всегда извлекая самое перспективное. ### Проверка дубликатов: ```csharp string key = $"{pos.X},{pos.Y}|{vel.X},{vel.Y}|{checkpointsMask}"; ``` Уникальный ключ учитывает позицию, скорость и собранные чекпоинты. ## Визуализация работы При запуске A* показывает прогресс каждые 10,000 итераций: ``` Итерация 10000: OpenSet=7131, FCost=26.78, GCost=12, Посещено=1/4 ``` Где: - **OpenSet** - количество состояний в рассмотрении - **FCost** - текущая минимальная оценочная стоимость - **GCost** - количество фактических шагов - **Посещено** - собрано чекпоинтов из общего количества ## Оптимизация для больших карт Для карт >50×50 или >5 чекпоинтов рекомендуется: 1. **Увеличить maxIterations**: ```csharp const int maxIterations = 5000000; ``` 2. **Использовать более агрессивную эвристику**: ```csharp return totalCost * 0.9; // Множитель < 1 для ускорения ``` 3. **Ограничить максимальную скорость**: ```csharp if (Math.Abs(newVelocity.X) > 5 || Math.Abs(newVelocity.Y) > 5) continue; ``` ## Теоретические основы ### Временная сложность: - **Лучший случай**: O(d), где d - глубина решения - **Средний случай**: O(b^d), где b - эффективный коэффициент ветвления - **Худший случай**: O(b^d) - вырождается в BFS ### Пространственная сложность: - **O(b^d)** для хранения открытого и закрытого множеств - В практике значительно меньше благодаря эвристике ### Условия оптимальности: A* гарантирует оптимальное решение, если: 1. h(n) ≤ h*(n) (допустимость) - эвристика не переоценивает 2. h(n) ≤ c(n,n') + h(n') (согласованность) - монотонность Наша эвристика удовлетворяет обоим условиям: - Использует оптимистичную оценку времени (без учета препятствий) - Монотонна относительно расстояния