cleaning
This commit is contained in:
@@ -1,403 +0,0 @@
|
|||||||
# 🏁 Реализация новых правил - Итоговый отчет
|
|
||||||
|
|
||||||
**Дата**: 19 октября 2025
|
|
||||||
**Проект**: Paper Racing - A* Algorithm
|
|
||||||
**Статус**: ✅ **Полностью реализовано и протестировано**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Задача
|
|
||||||
|
|
||||||
Адаптировать алгоритм A* для поддержки новых игровых правил:
|
|
||||||
|
|
||||||
1. **Препятствия**: Можно проезжать через камни, но нельзя на них останавливаться
|
|
||||||
2. **Снег**: Ускорение ограничено диапазоном от -1 до +1 по каждой оси
|
|
||||||
3. **Лёд**: Ускорение нельзя менять (инерция - только сохранение текущей скорости)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Выполненные изменения
|
|
||||||
|
|
||||||
### 1. Обновление структуры данных
|
|
||||||
|
|
||||||
#### RaceTrack класс
|
|
||||||
```csharp
|
|
||||||
// БЫЛО:
|
|
||||||
public RaceTrack(int width, int height, Point start,
|
|
||||||
Dictionary<int, Point> checkpoints,
|
|
||||||
HashSet<Point> obstacles)
|
|
||||||
|
|
||||||
// СТАЛО:
|
|
||||||
public RaceTrack(int width, int height, Point start,
|
|
||||||
Dictionary<int, Point> checkpoints,
|
|
||||||
HashSet<Point> obstacles,
|
|
||||||
Dictionary<Point, int> cellTypes) // +новое поле
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Новые поля
|
|
||||||
```csharp
|
|
||||||
private readonly Dictionary<Point, int> _cellTypes; // Тип каждой клетки
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2. Новая логика ускорений
|
|
||||||
|
|
||||||
#### Метод GetAccelerationRange()
|
|
||||||
```csharp
|
|
||||||
private (int minAccel, int maxAccel) GetAccelerationRange(Point position)
|
|
||||||
{
|
|
||||||
if (_cellTypes.TryGetValue(position, out int cellType))
|
|
||||||
{
|
|
||||||
return cellType switch
|
|
||||||
{
|
|
||||||
2 => (-1, 1), // Снег: ограниченное маневрирование
|
|
||||||
3 => (0, 0), // Лёд: только инерция
|
|
||||||
_ => (-2, 2) // Обычная дорога
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return (-2, 2); // По умолчанию
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Применение в алгоритме A*
|
|
||||||
```csharp
|
|
||||||
// БЫЛО:
|
|
||||||
for (int dx = -2; dx <= 2; dx++)
|
|
||||||
for (int dy = -2; dy <= 2; dy++)
|
|
||||||
|
|
||||||
// СТАЛО:
|
|
||||||
var (minAccel, maxAccel) = GetAccelerationRange(currentState.Position);
|
|
||||||
for (int dx = minAccel; dx <= maxAccel; dx++)
|
|
||||||
for (int dy = minAccel; dy <= maxAccel; dy++)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 3. Новая логика препятствий
|
|
||||||
|
|
||||||
#### Проверка препятствий
|
|
||||||
```csharp
|
|
||||||
// БЫЛО:
|
|
||||||
if (IntersectsObstacle(currentState.Position, newPosition))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// СТАЛО:
|
|
||||||
// Можно проезжать через препятствия, но нельзя на них останавливаться
|
|
||||||
if (_obstacles.Contains(newPosition))
|
|
||||||
continue;
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Удалено
|
|
||||||
- Метод `IntersectsObstacle()` - больше не нужен
|
|
||||||
- Алгоритм Брезенхема для проверки пути - больше не используется
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 4. Обновление MapLoader
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// БЫЛО:
|
|
||||||
public static (int width, int height, Point start,
|
|
||||||
Dictionary<int, Point> checkpoints,
|
|
||||||
HashSet<Point> obstacles) LoadFromJson(string filePath)
|
|
||||||
|
|
||||||
// СТАЛО:
|
|
||||||
public static (int width, int height, Point start,
|
|
||||||
Dictionary<int, Point> checkpoints,
|
|
||||||
HashSet<Point> obstacles,
|
|
||||||
Dictionary<Point, int> cellTypes) LoadFromJson(string filePath)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Добавлен подсчет
|
|
||||||
```csharp
|
|
||||||
int snowCount = 0;
|
|
||||||
int iceCount = 0;
|
|
||||||
|
|
||||||
// ...обработка карты...
|
|
||||||
|
|
||||||
Console.WriteLine($"Снег: {snowCount} клеток");
|
|
||||||
Console.WriteLine($"Лёд: {iceCount} клеток");
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 5. Улучшенная визуализация
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// Добавлено отображение типов поверхностей
|
|
||||||
else if (_cellTypes.TryGetValue(point, out int cellType))
|
|
||||||
{
|
|
||||||
switch (cellType)
|
|
||||||
{
|
|
||||||
case 2: // Снег
|
|
||||||
Console.Write("~ ");
|
|
||||||
break;
|
|
||||||
case 3: // Лёд
|
|
||||||
Console.Write("= ");
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
Console.Write(" ");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Обновлена легенда
|
|
||||||
```
|
|
||||||
# - препятствия (можно проезжать, нельзя останавливаться)
|
|
||||||
~ - снег (ускорение ±1)
|
|
||||||
= - лёд (ускорение нельзя менять)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### 6. Обработка встроенной карты
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
// Для встроенной карты по умолчанию:
|
|
||||||
cellTypes = new Dictionary<Point, int>();
|
|
||||||
for (int y = 0; y < height; y++)
|
|
||||||
{
|
|
||||||
for (int x = 0; x < width; x++)
|
|
||||||
{
|
|
||||||
cellTypes[new Point(x, y)] = 0; // Обычная дорога
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🧪 Созданные тестовые карты
|
|
||||||
|
|
||||||
| Файл | Назначение | Размер | Особенности |
|
|
||||||
|------|------------|--------|-------------|
|
|
||||||
| `test-obstacles.json` | Проверка проезда через препятствия | 15×11 | 56 препятствий |
|
|
||||||
| `test-snow.json` | Проверка ограниченного маневрирования | 15×9 | 49 клеток снега |
|
|
||||||
| `test-ice.json` | Проверка инерции | 18×9 | 54 клетки льда |
|
|
||||||
| `test-combined.json` | Комплексная проверка | 20×15 | Все типы + 4 чекпоинта |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📊 Результаты тестирования
|
|
||||||
|
|
||||||
### Автоматические тесты
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./run-all-tests.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
**Результат**: ✅ **7/7 тестов пройдено успешно (100%)**
|
|
||||||
|
|
||||||
| # | Карта | Ходов | Итераций | Время | Статус |
|
|
||||||
|---|-------|-------|----------|-------|--------|
|
|
||||||
| 1 | test-obstacles.json | 4 | 24 | 0.04с | ✅ |
|
|
||||||
| 2 | test-snow.json | 3 | 42 | 0.04с | ✅ |
|
|
||||||
| 3 | test-ice.json | 3 | 34 | 0.04с | ✅ |
|
|
||||||
| 4 | test-combined.json | 9 | 21 | 0.04с | ✅ |
|
|
||||||
| 5 | simple-test.json | 5 | 23 | 0.04с | ✅ |
|
|
||||||
| 6 | easy-test.json | 3 | 4 | 0.04с | ✅ |
|
|
||||||
| 7 | open-field.json | 6 | 15 | 0.04с | ✅ |
|
|
||||||
|
|
||||||
### Ключевые метрики
|
|
||||||
- **Среднее время**: 0.04 секунды
|
|
||||||
- **Средние итерации**: 23 итерации
|
|
||||||
- **Минимальное решение**: 3 хода
|
|
||||||
- **Максимальное решение**: 9 ходов
|
|
||||||
- **Успешность**: 100%
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Проверенные сценарии
|
|
||||||
|
|
||||||
### ✅ Сценарий 1: Проезд через препятствия
|
|
||||||
**Карта**: test-obstacles.json
|
|
||||||
**Результат**: Машина успешно пролетела через зону из 56 препятствий
|
|
||||||
**Траектория**: (0,10) → (2,10) → (6,9) → (11,6) → (14,1)
|
|
||||||
**Вывод**: Препятствия больше не блокируют траектории
|
|
||||||
|
|
||||||
### ✅ Сценарий 2: Маневрирование на снегу
|
|
||||||
**Карта**: test-snow.json
|
|
||||||
**Результат**: Все ускорения в пределах ±1
|
|
||||||
**Ускорения**: (1,1), (-1,0)
|
|
||||||
**Вывод**: Ограничение работает корректно
|
|
||||||
|
|
||||||
### ✅ Сценарий 3: Инерция на льду
|
|
||||||
**Карта**: test-ice.json
|
|
||||||
**Результат**: Алгоритм не планирует остановок на льду
|
|
||||||
**Стратегия**: Машина обошла ледяную зону
|
|
||||||
**Вывод**: Ограничение ускорения (0,0) применяется
|
|
||||||
|
|
||||||
### ✅ Сценарий 4: Комбинация всех типов
|
|
||||||
**Карта**: test-combined.json
|
|
||||||
**Результат**: Все 4 чекпоинта собраны за 9 ходов
|
|
||||||
**Проверки**:
|
|
||||||
- Проезд через препятствия: шаги 3-4 ✅
|
|
||||||
- Маневр на снегу: шаг 7 с ускорением (-1,1) ✅
|
|
||||||
- Обход льда: шаги 8-9 ✅
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📁 Созданные файлы
|
|
||||||
|
|
||||||
### Тестовые карты
|
|
||||||
- `/maps/test-obstacles.json` - тест препятствий
|
|
||||||
- `/maps/test-snow.json` - тест снега
|
|
||||||
- `/maps/test-ice.json` - тест льда
|
|
||||||
- `/maps/test-combined.json` - комплексный тест
|
|
||||||
|
|
||||||
### Документация
|
|
||||||
- `/TEST-RESULTS.md` - детальные результаты каждого теста
|
|
||||||
- `/TESTING-SUMMARY.md` - полная сводка тестирования
|
|
||||||
- `/maps/TEST-MAPS-README.md` - руководство по тестовым картам
|
|
||||||
- `/IMPLEMENTATION-SUMMARY.md` - этот файл
|
|
||||||
|
|
||||||
### Скрипты
|
|
||||||
- `/run-all-tests.sh` - автоматический запуск всех тестов
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Изменения в коде
|
|
||||||
|
|
||||||
### Файлы с изменениями
|
|
||||||
- `ProgramAStar.cs` - основная реализация
|
|
||||||
|
|
||||||
### Статистика изменений
|
|
||||||
- **Добавлено**:
|
|
||||||
- Метод `GetAccelerationRange()` (15 строк)
|
|
||||||
- Поле `_cellTypes` (1 строка)
|
|
||||||
- Обработка типов клеток в `MapLoader` (20 строк)
|
|
||||||
- Визуализация снега и льда (10 строк)
|
|
||||||
- **Удалено**:
|
|
||||||
- Метод `IntersectsObstacle()` (34 строки)
|
|
||||||
- Вызов `IntersectsObstacle()` (2 строки)
|
|
||||||
- **Изменено**:
|
|
||||||
- Цикл генерации ускорений (4 строки)
|
|
||||||
- Конструктор `RaceTrack` (1 строка)
|
|
||||||
- Сигнатура `MapLoader.LoadFromJson()` (1 строка)
|
|
||||||
|
|
||||||
### Чистый результат
|
|
||||||
- **+46 строк** (новый функционал)
|
|
||||||
- **-36 строк** (удаленный код)
|
|
||||||
- **Итого**: +10 строк чистого кода
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Использование
|
|
||||||
|
|
||||||
### Компиляция
|
|
||||||
```bash
|
|
||||||
dotnet build racing-astar.csproj
|
|
||||||
```
|
|
||||||
|
|
||||||
### Запуск на карте
|
|
||||||
```bash
|
|
||||||
./bin/Debug/net8.0/racing-astar maps/test-combined.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### Автоматическое тестирование
|
|
||||||
```bash
|
|
||||||
./run-all-tests.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💡 Преимущества реализации
|
|
||||||
|
|
||||||
### 1. Чистый код
|
|
||||||
- Удален сложный метод `IntersectsObstacle()`
|
|
||||||
- Добавлен простой и понятный `GetAccelerationRange()`
|
|
||||||
- Код стал короче и проще
|
|
||||||
|
|
||||||
### 2. Производительность
|
|
||||||
- Убрана проверка всего пути (алгоритм Брезенхема)
|
|
||||||
- Только одна проверка конечной позиции
|
|
||||||
- Меньше вычислений = быстрее работа
|
|
||||||
|
|
||||||
### 3. Гибкость
|
|
||||||
- Легко добавить новые типы поверхностей
|
|
||||||
- Все правила в одном месте (`GetAccelerationRange()`)
|
|
||||||
- Просто менять ограничения ускорений
|
|
||||||
|
|
||||||
### 4. Расширяемость
|
|
||||||
```csharp
|
|
||||||
// Легко добавить новые типы:
|
|
||||||
return cellType switch
|
|
||||||
{
|
|
||||||
2 => (-1, 1), // Снег
|
|
||||||
3 => (0, 0), // Лёд
|
|
||||||
5 => (-3, 3), // Новый тип: супер-дорога
|
|
||||||
6 => (-1, 2), // Новый тип: асимметричная поверхность
|
|
||||||
_ => (-2, 2)
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Достигнутые цели
|
|
||||||
|
|
||||||
### Функциональные требования
|
|
||||||
- ✅ Препятствия можно проезжать
|
|
||||||
- ✅ На препятствиях нельзя останавливаться
|
|
||||||
- ✅ Снег ограничивает ускорение до ±1
|
|
||||||
- ✅ Лёд не позволяет менять ускорение
|
|
||||||
- ✅ Обычная дорога работает как раньше (±2)
|
|
||||||
|
|
||||||
### Нефункциональные требования
|
|
||||||
- ✅ Высокая производительность (< 0.05 сек)
|
|
||||||
- ✅ Обратная совместимость с существующими картами
|
|
||||||
- ✅ Чистый и понятный код
|
|
||||||
- ✅ Полное тестовое покрытие
|
|
||||||
- ✅ Подробная документация
|
|
||||||
|
|
||||||
### Качество
|
|
||||||
- ✅ 0 ошибок компиляции
|
|
||||||
- ✅ 0 предупреждений
|
|
||||||
- ✅ 100% тестов пройдено
|
|
||||||
- ✅ Все сценарии проверены
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Дальнейшие возможности
|
|
||||||
|
|
||||||
### Потенциальные улучшения
|
|
||||||
1. **Учет типов в эвристике**
|
|
||||||
- Снег = увеличение стоимости пути
|
|
||||||
- Лёд = планирование длинных инерционных участков
|
|
||||||
|
|
||||||
2. **Новые типы поверхностей**
|
|
||||||
- Грязь: случайное ускорение
|
|
||||||
- Турбо-полоса: увеличенное ускорение ±3
|
|
||||||
- Телепорты: мгновенное перемещение
|
|
||||||
|
|
||||||
3. **Визуальные улучшения**
|
|
||||||
- Цветной вывод для разных поверхностей
|
|
||||||
- Анимация движения
|
|
||||||
- Экспорт в графический формат
|
|
||||||
|
|
||||||
4. **Оптимизации**
|
|
||||||
- Кэширование эвристики для клеток
|
|
||||||
- Предрасчет зон типов поверхностей
|
|
||||||
- Параллельная обработка ветвей поиска
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✅ Заключение
|
|
||||||
|
|
||||||
**Все новые правила успешно реализованы и протестированы.**
|
|
||||||
|
|
||||||
Система полностью готова к использованию и показывает отличную производительность на картах любой сложности. Код стал чище, проще и быстрее. Все тесты проходят со 100% успешностью.
|
|
||||||
|
|
||||||
### Итоговая оценка проекта
|
|
||||||
- **Функциональность**: ✅ 10/10
|
|
||||||
- **Производительность**: ✅ 10/10
|
|
||||||
- **Качество кода**: ✅ 10/10
|
|
||||||
- **Тестирование**: ✅ 10/10
|
|
||||||
- **Документация**: ✅ 10/10
|
|
||||||
|
|
||||||
**Общая оценка**: ⭐⭐⭐⭐⭐ **10/10**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Проект завершен успешно!** 🎉
|
|
||||||
|
|
||||||
126
MAP-FORMAT.md
126
MAP-FORMAT.md
@@ -1,126 +0,0 @@
|
|||||||
# Формат карт для Paper Racing A*
|
|
||||||
|
|
||||||
## Обзор
|
|
||||||
|
|
||||||
Программа поддерживает загрузку карт из JSON-файлов. Это позволяет создавать собственные треки любой сложности.
|
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
### Запуск со встроенной картой
|
|
||||||
```bash
|
|
||||||
dotnet run --project racing-astar.csproj
|
|
||||||
```
|
|
||||||
|
|
||||||
### Запуск с картой из файла
|
|
||||||
```bash
|
|
||||||
dotnet run --project racing-astar.csproj maps/your-map.json
|
|
||||||
```
|
|
||||||
|
|
||||||
или через скомпилированный бинарник:
|
|
||||||
```bash
|
|
||||||
./bin/Debug/net8.0/racing-astar maps/your-map.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## Формат JSON
|
|
||||||
|
|
||||||
Карта представляет собой JSON-файл со следующей структурой:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"map": [
|
|
||||||
[0, 0, 0, 0, 0],
|
|
||||||
[0, 1, 4, 1, 0],
|
|
||||||
[0, 0, 0, 0, 0]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Типы ячеек
|
|
||||||
|
|
||||||
| Код | Тип | Описание | Поведение |
|
|
||||||
|-----|-----|----------|-----------|
|
|
||||||
| `0` | Дорога | Обычная проходимая ячейка | Первая найденная становится стартом (если нет типа 5). Ускорение ±2 |
|
|
||||||
| `1` | Камень | Препятствие | Можно проезжать, нельзя останавливаться |
|
|
||||||
| `2` | Снег | Скользкая поверхность | Ограниченное маневрирование, ускорение ±1 |
|
|
||||||
| `3` | Лед | Очень скользкая поверхность | Инерция, ускорение нельзя менять |
|
|
||||||
| `4` | Чекпоинт | Контрольная точка | Должна быть посещена |
|
|
||||||
| `5` | Старт | Явная стартовая позиция | Приоритет над типом 0, ускорение ±2 |
|
|
||||||
|
|
||||||
### Особенности
|
|
||||||
|
|
||||||
1. **Стартовая позиция**:
|
|
||||||
- Если на карте есть ячейка типа `5` (Старт), она становится стартовой позицией
|
|
||||||
- Если нет типа `5`, то автоматически определяется как первая ячейка типа `0` (дорога), найденная при сканировании карты слева направо, сверху вниз
|
|
||||||
|
|
||||||
2. **Чекпоинты**: Все ячейки с типом `4` автоматически становятся чекпоинтами. Нумерация чекпоинтов идет по порядку их обнаружения (слева направо, сверху вниз).
|
|
||||||
|
|
||||||
3. **Система координат**:
|
|
||||||
- JSON-карта описывается сверху вниз (первая строка массива = верх карты)
|
|
||||||
- Визуализация отображает карту в правильной ориентации (Y растет вверх)
|
|
||||||
|
|
||||||
4. **Физика движения**: Алгоритм учитывает инерцию - машинка не может мгновенно остановиться или изменить направление. Ускорение на каждом шаге ограничено диапазоном `[-2, +2]` по каждой оси.
|
|
||||||
|
|
||||||
## Примеры карт
|
|
||||||
|
|
||||||
### Простая открытая карта (3 чекпоинта)
|
|
||||||
Файл: `maps/open-field.json`
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"map": [
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Сложная карта с препятствиями (59 чекпоинтов)
|
|
||||||
Файл: `maps/racing-map-42x42.json`
|
|
||||||
|
|
||||||
Карта размером 42x42 с многочисленными препятствиями и 59 чекпоинтами. Требует значительно больше времени для поиска решения.
|
|
||||||
|
|
||||||
## Советы по созданию карт
|
|
||||||
|
|
||||||
1. **Начните с малого**: Создайте небольшую карту (10x10 - 20x20) для тестирования.
|
|
||||||
|
|
||||||
2. **Учитывайте инерцию**: Оставляйте достаточно пространства для маневрирования. Машинка не может мгновенно остановиться.
|
|
||||||
|
|
||||||
3. **Не переусердствуйте с чекпоинтами**: Сложность растет экспоненциально с количеством чекпоинтов. Начните с 2-5 чекпоинтов.
|
|
||||||
|
|
||||||
4. **Открытые пространства**: Карты с большим количеством открытого пространства решаются быстрее.
|
|
||||||
|
|
||||||
5. **Тестирование**: Используйте встроенные примеры для понимания того, какие карты решаются быстро:
|
|
||||||
```bash
|
|
||||||
dotnet run --project racing-astar.csproj maps/open-field.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## Ограничения
|
|
||||||
|
|
||||||
- **Максимум итераций**: По умолчанию алгоритм ограничен 5,000,000 итераций
|
|
||||||
- **Память**: Сложные карты с большим количеством чекпоинтов требуют значительной памяти
|
|
||||||
- **Время**: Карты с более чем 10 чекпоинтами могут требовать длительного времени решения
|
|
||||||
|
|
||||||
## Технические детали
|
|
||||||
|
|
||||||
### Алгоритм A*
|
|
||||||
Программа использует алгоритм A* с эвристикой на основе:
|
|
||||||
- Евклидова расстояния до чекпоинтов
|
|
||||||
- Текущей скорости движения
|
|
||||||
- Упрощенной модели физики ускорения
|
|
||||||
|
|
||||||
### Ключ состояния
|
|
||||||
Каждое уникальное состояние определяется комбинацией:
|
|
||||||
- Позиция (X, Y)
|
|
||||||
- Скорость (VX, VY)
|
|
||||||
- Набор посещенных чекпоинтов
|
|
||||||
|
|
||||||
Это позволяет алгоритму эффективно отслеживать уже исследованные состояния и избегать повторной обработки.
|
|
||||||
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
# 🚀 Быстрый старт - Веб-инструменты
|
|
||||||
|
|
||||||
## Два независимых приложения
|
|
||||||
|
|
||||||
### 🗺️ Редактор карт
|
|
||||||
```bash
|
|
||||||
cd map-editor
|
|
||||||
./open-editor.sh
|
|
||||||
```
|
|
||||||
**Для чего:** Создание и редактирование карт
|
|
||||||
|
|
||||||
### 🎬 Визуализатор решений
|
|
||||||
```bash
|
|
||||||
cd solution-player
|
|
||||||
./open-player.sh
|
|
||||||
```
|
|
||||||
**Для чего:** Просмотр анимации решений
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📖 Полный цикл работы
|
|
||||||
|
|
||||||
### Шаг 1: Создайте карту
|
|
||||||
```bash
|
|
||||||
cd map-editor
|
|
||||||
./open-editor.sh
|
|
||||||
```
|
|
||||||
1. Нарисуйте трассу
|
|
||||||
2. Добавьте точку старта (S, тип 5)
|
|
||||||
3. Добавьте чекпоинты (C, тип 4)
|
|
||||||
4. Нажмите "📥 Экспорт JSON"
|
|
||||||
5. Сохраните как `my-map.json`
|
|
||||||
|
|
||||||
### Шаг 2: Решите карту
|
|
||||||
```bash
|
|
||||||
cd ..
|
|
||||||
dotnet run --project racing-astar.csproj maps/my-map.json
|
|
||||||
```
|
|
||||||
Решение сохранится в `solution.json`
|
|
||||||
|
|
||||||
### Шаг 3: Визуализируйте
|
|
||||||
```bash
|
|
||||||
cd solution-player
|
|
||||||
./open-player.sh
|
|
||||||
```
|
|
||||||
1. Нажмите "📂 Загрузить карту" → выберите `my-map.json`
|
|
||||||
2. Нажмите "🎬 Загрузить решение" → выберите `solution.json`
|
|
||||||
3. Нажмите "▶ Play"
|
|
||||||
4. Наслаждайтесь анимацией!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Быстрый тест с примерами
|
|
||||||
|
|
||||||
### Вариант 1: Простая карта
|
|
||||||
```bash
|
|
||||||
cd solution-player
|
|
||||||
./open-player.sh
|
|
||||||
```
|
|
||||||
1. Загрузите карту: `simple-track.json`
|
|
||||||
2. Загрузите решение: `example-solution.json`
|
|
||||||
3. Play!
|
|
||||||
|
|
||||||
### Вариант 2: Демо-карта
|
|
||||||
```bash
|
|
||||||
cd solution-player
|
|
||||||
./open-player.sh
|
|
||||||
```
|
|
||||||
1. Загрузите карту: `demo-with-start.json`
|
|
||||||
2. Загрузите решение: `demo-solution.json`
|
|
||||||
3. Play!
|
|
||||||
|
|
||||||
### Вариант 3: Сложная траектория
|
|
||||||
```bash
|
|
||||||
cd solution-player
|
|
||||||
./open-player.sh
|
|
||||||
```
|
|
||||||
1. Загрузите карту: `demo-with-start.json`
|
|
||||||
2. Загрузите решение: `complex-solution.json`
|
|
||||||
3. Play!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎮 Управление визуализатором
|
|
||||||
|
|
||||||
- **▶ Play** - автоматическое воспроизведение
|
|
||||||
- **⏸ Pause** - пауза
|
|
||||||
- **⏮ Reset** - вернуться к началу
|
|
||||||
- **⏪ Back** - шаг назад
|
|
||||||
- **⏩ Forward** - шаг вперед
|
|
||||||
- **Слайдер** - скорость 1x-10x
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Дополнительная информация
|
|
||||||
|
|
||||||
- **WEB-TOOLS.md** - подробное описание инструментов
|
|
||||||
- **map-editor/README.md** - документация редактора
|
|
||||||
- **solution-player/README.md** - документация визуализатора
|
|
||||||
- **SEPARATION-SUMMARY.md** - детали разделения проектов
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 💡 Полезные советы
|
|
||||||
|
|
||||||
### Для редактора:
|
|
||||||
- Используйте ЛКМ для рисования
|
|
||||||
- Удерживайте кнопку мыши для быстрого рисования
|
|
||||||
- Проверяйте JSON в консоли (F12)
|
|
||||||
|
|
||||||
### Для визуализатора:
|
|
||||||
- Начните с малых скоростей (1x-3x)
|
|
||||||
- Используйте пошаговый режим для анализа
|
|
||||||
- Следите за красной стрелкой (вектор скорости)
|
|
||||||
- Смотрите информацию о каждом шаге внизу
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔧 Troubleshooting
|
|
||||||
|
|
||||||
**Проблема:** Визуализатор не показывает траекторию
|
|
||||||
**Решение:** Убедитесь, что на карте есть точка старта (тип 5)
|
|
||||||
|
|
||||||
**Проблема:** Траектория выходит за пределы карты
|
|
||||||
**Решение:** Проверьте, что решение создано для правильной карты
|
|
||||||
|
|
||||||
**Проблема:** Браузер не открывается
|
|
||||||
**Решение:** Откройте `index.html` вручную
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Приятного использования! 🎉
|
|
||||||
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
# 🏁 Racing Tools - Новый объединенный проект
|
|
||||||
|
|
||||||
## 🎉 Что нового?
|
|
||||||
|
|
||||||
Проекты `map-editor/` и `solution-player/` объединены в один: **`racing-tools/`**
|
|
||||||
|
|
||||||
## 🚀 Быстрый старт
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd racing-tools
|
|
||||||
|
|
||||||
# Редактор карт
|
|
||||||
./open-editor.sh
|
|
||||||
# или
|
|
||||||
firefox editor.html
|
|
||||||
|
|
||||||
# Визуализатор решений
|
|
||||||
./open-player.sh
|
|
||||||
# или
|
|
||||||
firefox player.html
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📦 Что внутри?
|
|
||||||
|
|
||||||
### Два веб-приложения
|
|
||||||
- **🏁 Редактор карт** (`editor.html`) - создание и редактирование карт
|
|
||||||
- **🎬 Визуализатор решений** (`player.html`) - анимация траекторий
|
|
||||||
|
|
||||||
### Общие файлы
|
|
||||||
- `styles.css` - единый стиль для обоих приложений
|
|
||||||
- `common.js` - общие функции и константы
|
|
||||||
- `README.md` - полная документация
|
|
||||||
|
|
||||||
## 🔑 Ключевые улучшения
|
|
||||||
|
|
||||||
✅ **Единый стиль** - общий CSS файл
|
|
||||||
✅ **Навигация** - кнопки переключения между страницами
|
|
||||||
✅ **Меньше дублирования** - общие функции в отдельном файле
|
|
||||||
✅ **Удобство** - все в одной папке
|
|
||||||
✅ **Единая документация** - один README
|
|
||||||
|
|
||||||
## 📄 Документация
|
|
||||||
|
|
||||||
- **[racing-tools/README.md](racing-tools/README.md)** - полная документация
|
|
||||||
- **[racing-tools/MIGRATION.md](racing-tools/MIGRATION.md)** - руководство по миграции
|
|
||||||
|
|
||||||
## 🗂️ Структура проекта
|
|
||||||
|
|
||||||
```
|
|
||||||
racing-tools/
|
|
||||||
├── editor.html # Редактор карт
|
|
||||||
├── player.html # Визуализатор решений
|
|
||||||
├── styles.css # Общие стили
|
|
||||||
├── common.js # Общий код
|
|
||||||
├── editor.js # Логика редактора
|
|
||||||
├── player.js # Логика визуализатора
|
|
||||||
├── open-editor.sh # Запуск редактора
|
|
||||||
├── open-player.sh # Запуск визуализатора
|
|
||||||
├── demo-with-start.json # Демо-карта
|
|
||||||
├── demo-with-start-solution.json # Демо-решение
|
|
||||||
├── README.md # Документация
|
|
||||||
└── MIGRATION.md # Руководство по миграции
|
|
||||||
```
|
|
||||||
|
|
||||||
## ⚡ Совместимость
|
|
||||||
|
|
||||||
✅ Все форматы JSON остались прежними
|
|
||||||
✅ Старые карты и решения работают без изменений
|
|
||||||
✅ Можно использовать параллельно со старыми проектами
|
|
||||||
|
|
||||||
## 🔗 Связанные файлы
|
|
||||||
|
|
||||||
- [WEB-TOOLS.md](WEB-TOOLS.md) - общий обзор веб-инструментов
|
|
||||||
- [MAP-FORMAT.md](MAP-FORMAT.md) - формат карт
|
|
||||||
- [maps/](maps/) - коллекция карт
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Создано:** 20 октября 2025
|
|
||||||
**Версия:** 2.0 (Объединенная)
|
|
||||||
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
# 📊 Итоги разделения проектов
|
|
||||||
|
|
||||||
## ✅ Выполненные изменения
|
|
||||||
|
|
||||||
### Создано два независимых проекта:
|
|
||||||
|
|
||||||
#### 1. 🗺️ Редактор карт (`map-editor/`)
|
|
||||||
- **Назначение:** Создание и редактирование карт
|
|
||||||
- **Функции:**
|
|
||||||
- Рисование карт
|
|
||||||
- Экспорт/импорт JSON
|
|
||||||
- 6 типов ячеек
|
|
||||||
- Валидация данных
|
|
||||||
|
|
||||||
#### 2. 🎬 Визуализатор решений (`solution-player/`)
|
|
||||||
- **Назначение:** Визуализация и анимация решений
|
|
||||||
- **Функции:**
|
|
||||||
- Загрузка карт и решений
|
|
||||||
- Пошаговое воспроизведение
|
|
||||||
- Регулировка скорости
|
|
||||||
- Отображение векторов
|
|
||||||
- Детальная информация
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📂 Структура файлов
|
|
||||||
|
|
||||||
### map-editor/
|
|
||||||
```
|
|
||||||
map-editor/
|
|
||||||
├── index.html ✅ Обновлен (удалена визуализация)
|
|
||||||
├── editor.js ✅ Обновлен (только редактирование)
|
|
||||||
├── README.md ✅ Обновлен
|
|
||||||
├── open-editor.sh ✅ Существующий скрипт
|
|
||||||
├── *.json ✅ Карты и примеры
|
|
||||||
└── документация...
|
|
||||||
```
|
|
||||||
|
|
||||||
### solution-player/
|
|
||||||
```
|
|
||||||
solution-player/
|
|
||||||
├── index.html ✅ Создан (только визуализация)
|
|
||||||
├── player.js ✅ Создан (только плеер)
|
|
||||||
├── README.md ✅ Создан
|
|
||||||
├── open-player.sh ✅ Создан
|
|
||||||
├── demo-with-start.json ✅ Скопирован
|
|
||||||
├── simple-track.json ✅ Скопирован
|
|
||||||
├── easy-test.json ✅ Скопирован
|
|
||||||
├── example-solution.json ✅ Скопирован
|
|
||||||
├── complex-solution.json ✅ Скопирован
|
|
||||||
└── demo-solution.json ✅ Скопирован
|
|
||||||
```
|
|
||||||
|
|
||||||
### Корневая документация
|
|
||||||
```
|
|
||||||
racing/
|
|
||||||
├── WEB-TOOLS.md ✅ Создан (обзор обоих проектов)
|
|
||||||
└── SEPARATION-SUMMARY.md ✅ Этот файл
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔄 Основные изменения
|
|
||||||
|
|
||||||
### map-editor/index.html
|
|
||||||
- ❌ Удалена секция "Визуализация решения"
|
|
||||||
- ❌ Удалена панель воспроизведения
|
|
||||||
- ❌ Удалены элементы управления плеером
|
|
||||||
- ❌ Удалена информация о шагах
|
|
||||||
- ❌ Удалены неиспользуемые CSS стили
|
|
||||||
- ✅ Добавлена ссылка на визуализатор решений
|
|
||||||
|
|
||||||
### map-editor/editor.js
|
|
||||||
- ❌ Удалены функции визуализации траектории
|
|
||||||
- ❌ Удалены функции воспроизведения
|
|
||||||
- ❌ Удалены переменные состояния плеера
|
|
||||||
- ❌ Удалены функции симуляции
|
|
||||||
- ✅ Оставлен только редактор карт
|
|
||||||
|
|
||||||
### solution-player/ (новый проект)
|
|
||||||
- ✅ Создан полностью новый HTML
|
|
||||||
- ✅ Создан player.js с функциями визуализации
|
|
||||||
- ✅ Добавлена возможность загрузки карты
|
|
||||||
- ✅ Добавлена возможность загрузки решения
|
|
||||||
- ✅ Добавлена навигация вперед/назад
|
|
||||||
- ✅ Улучшен UI/UX для просмотра
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Как использовать
|
|
||||||
|
|
||||||
### Редактор карт
|
|
||||||
```bash
|
|
||||||
cd map-editor
|
|
||||||
./open-editor.sh
|
|
||||||
```
|
|
||||||
- Создавайте карты
|
|
||||||
- Экспортируйте JSON
|
|
||||||
- Переходите к визуализатору через ссылку
|
|
||||||
|
|
||||||
### Визуализатор решений
|
|
||||||
```bash
|
|
||||||
cd solution-player
|
|
||||||
./open-player.sh
|
|
||||||
```
|
|
||||||
- Загрузите карту
|
|
||||||
- Загрузите решение
|
|
||||||
- Наслаждайтесь анимацией
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📋 Workflow
|
|
||||||
|
|
||||||
```
|
|
||||||
1. Создание карты
|
|
||||||
↓
|
|
||||||
map-editor → export map.json
|
|
||||||
|
|
||||||
2. Генерация решения
|
|
||||||
↓
|
|
||||||
dotnet run → solution.json
|
|
||||||
|
|
||||||
3. Визуализация
|
|
||||||
↓
|
|
||||||
solution-player → load map + solution
|
|
||||||
↓
|
|
||||||
Просмотр анимации
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Преимущества разделения
|
|
||||||
|
|
||||||
### ✅ Разделение ответственности
|
|
||||||
- Редактор занимается только картами
|
|
||||||
- Плеер занимается только визуализацией
|
|
||||||
|
|
||||||
### ✅ Независимость
|
|
||||||
- Каждый проект можно использовать отдельно
|
|
||||||
- Упрощенный код
|
|
||||||
- Легче поддерживать
|
|
||||||
|
|
||||||
### ✅ Улучшенный UX
|
|
||||||
- Четкое назначение каждого инструмента
|
|
||||||
- Упрощенный интерфейс
|
|
||||||
- Меньше путаницы
|
|
||||||
|
|
||||||
### ✅ Масштабируемость
|
|
||||||
- Легко добавлять новые функции
|
|
||||||
- Проще тестировать
|
|
||||||
- Модульная архитектура
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📚 Документация
|
|
||||||
|
|
||||||
- **WEB-TOOLS.md** - Обзор обоих инструментов
|
|
||||||
- **map-editor/README.md** - Документация редактора
|
|
||||||
- **solution-player/README.md** - Документация визуализатора
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔗 Связи между проектами
|
|
||||||
|
|
||||||
1. **map-editor** создает JSON карты
|
|
||||||
2. **C# solver** генерирует решения
|
|
||||||
3. **solution-player** визуализирует результаты
|
|
||||||
|
|
||||||
Все проекты полностью независимы и связаны только через JSON файлы.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ✨ Итого
|
|
||||||
|
|
||||||
Проект успешно разделен на два независимых веб-приложения:
|
|
||||||
- ✅ Редактор карт (map-editor)
|
|
||||||
- ✅ Визуализатор решений (solution-player)
|
|
||||||
|
|
||||||
Оба приложения:
|
|
||||||
- ✅ Полностью функциональны
|
|
||||||
- ✅ Имеют свою документацию
|
|
||||||
- ✅ Имеют скрипты запуска
|
|
||||||
- ✅ Включают примеры файлов
|
|
||||||
- ✅ Работают без сервера
|
|
||||||
- ✅ Современный UI/UX
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Дата разделения: 2025-10-20
|
|
||||||
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
# 🗺️ Редактор карт для "Гонки на бумаге"
|
|
||||||
|
|
||||||
Веб-приложение для создания и редактирования карт. Работает без сервера, просто откройте в браузере.
|
|
||||||
|
|
||||||
> **Примечание:** Для визуализации решений используйте [Визуализатор решений](../solution-player/).
|
|
||||||
|
|
||||||
## 🚀 Быстрый старт
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Запуск редактора
|
|
||||||
./open-editor.sh
|
|
||||||
# или
|
|
||||||
firefox index.html
|
|
||||||
```
|
|
||||||
|
|
||||||
**Три простых шага:**
|
|
||||||
1. Установите размеры (5-100), выберите тип ячейки из палитры
|
|
||||||
2. Рисуйте мышью на карте (обязательно добавьте точку старта)
|
|
||||||
3. Нажмите "Экспорт JSON" - файл скачается автоматически
|
|
||||||
|
|
||||||
## 🎨 Типы ячеек
|
|
||||||
|
|
||||||
| Код | Тип | Описание | Цвет | Маркер |
|
|
||||||
|-----|-----|----------|------|--------|
|
|
||||||
| 0 | Дорога | Обычная дорога | Светло-серый | - |
|
|
||||||
| 1 | Камень | Препятствие (непроходимо) | Тёмно-серый | - |
|
|
||||||
| 2 | Снег | Замедление движения | Голубой | - |
|
|
||||||
| 3 | Лёд | Скользкая поверхность | Светло-голубой | - |
|
|
||||||
| 4 | Чекпоинт | Контрольная точка | Жёлтый | C |
|
|
||||||
| 5 | Старт | Точка старта | Зелёный | S |
|
|
||||||
|
|
||||||
## 📖 Использование
|
|
||||||
|
|
||||||
**Рисование:**
|
|
||||||
- Выберите тип ячейки из палитры
|
|
||||||
- Кликните на ячейку или удерживайте мышь для быстрого рисования
|
|
||||||
|
|
||||||
**Экспорт/Импорт:**
|
|
||||||
- Экспорт: нажмите "Экспорт JSON" (также выводится в консоль F12)
|
|
||||||
- Импорт: нажмите "Импорт JSON" и выберите файл
|
|
||||||
|
|
||||||
**Изменение размеров:**
|
|
||||||
- Укажите ширину и высоту (5-100), нажмите "Применить"
|
|
||||||
- Существующие данные сохранятся при изменении размера
|
|
||||||
|
|
||||||
## 📄 Формат JSON
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"map": [
|
|
||||||
[0, 0, 0, 1, 0],
|
|
||||||
[5, 1, 0, 1, 0],
|
|
||||||
[0, 0, 2, 2, 4],
|
|
||||||
[1, 0, 3, 3, 0],
|
|
||||||
[0, 0, 0, 0, 0]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Структура:**
|
|
||||||
- `map` - двумерный массив целых чисел (int[][])
|
|
||||||
- Первая строка = верхняя строка карты
|
|
||||||
- Первый элемент в строке = левая ячейка
|
|
||||||
- Значения: 0-5 (типы ячеек)
|
|
||||||
|
|
||||||
## 🔗 Интеграция с C#
|
|
||||||
|
|
||||||
```csharp
|
|
||||||
using System.Text.Json;
|
|
||||||
|
|
||||||
var json = File.ReadAllText("racing-map-15x15.json");
|
|
||||||
var mapData = JsonSerializer.Deserialize<MapData>(json);
|
|
||||||
int[][] map = mapData.map;
|
|
||||||
|
|
||||||
public class MapData
|
|
||||||
{
|
|
||||||
public int[][] map { get; set; }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎯 Файлы проекта
|
|
||||||
|
|
||||||
| Файл | Описание |
|
|
||||||
|------|----------|
|
|
||||||
| `index.html` | Главная страница редактора |
|
|
||||||
| `editor.js` | Логика редактора и визуализация |
|
|
||||||
| `open-editor.sh` | Скрипт для быстрого запуска |
|
|
||||||
| `demo-with-start.json` | Демонстрационная карта 15×15 |
|
|
||||||
|
|
||||||
## 🎨 Особенности
|
|
||||||
|
|
||||||
- ✅ Современный интерфейс, работает без сервера
|
|
||||||
- ✅ Интуитивное рисование мышью
|
|
||||||
- ✅ Валидация данных при импорте
|
|
||||||
- ✅ Автоматическое сохранение с именем по размеру
|
|
||||||
- ✅ HTML5 Canvas, Vanilla JavaScript (ES6+)
|
|
||||||
|
|
||||||
## 🔗 Связанные проекты
|
|
||||||
|
|
||||||
- [Визуализатор решений](../solution-player/) - анимация решений
|
|
||||||
- [Основной проект](../README.md) - C# решатель на основе A*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Технологии:** HTML5, CSS3, JavaScript ES6+, Canvas API
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
{
|
|
||||||
"map": [
|
|
||||||
[5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 1, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 1, 0, 0, 0, 2, 4, 2, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 1, 1, 0],
|
|
||||||
[0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 2, 2, 2, 0],
|
|
||||||
[0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 2, 0],
|
|
||||||
[0, 0, 0, 0, 0, 3, 3, 3, 0, 0, 0, 2, 2, 2, 0],
|
|
||||||
[0, 0, 0, 0, 0, 3, 1, 3, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 4],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
// Конфигурация редактора
|
|
||||||
const CELL_SIZE = 30;
|
|
||||||
const GRID_COLOR = '#dee2e6';
|
|
||||||
const COLORS = {
|
|
||||||
0: '#f8f9fa', // Дорога
|
|
||||||
1: '#6c757d', // Камень
|
|
||||||
2: '#e3f2fd', // Снег
|
|
||||||
3: '#b3e5fc', // Лёд
|
|
||||||
4: '#fff3cd', // Чекпоинт
|
|
||||||
5: '#d4edda' // Старт
|
|
||||||
};
|
|
||||||
|
|
||||||
// Состояние редактора
|
|
||||||
let width = 15;
|
|
||||||
let height = 15;
|
|
||||||
let map = [];
|
|
||||||
let selectedType = 0;
|
|
||||||
let isDrawing = false;
|
|
||||||
|
|
||||||
// Canvas элементы
|
|
||||||
const canvas = document.getElementById('mapCanvas');
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
// Инициализация
|
|
||||||
function init() {
|
|
||||||
width = parseInt(document.getElementById('width').value);
|
|
||||||
height = parseInt(document.getElementById('height').value);
|
|
||||||
initMap();
|
|
||||||
resizeCanvas();
|
|
||||||
drawMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Инициализация карты
|
|
||||||
function initMap() {
|
|
||||||
map = Array(height).fill(null).map(() => Array(width).fill(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Изменение размера canvas
|
|
||||||
function resizeCanvas() {
|
|
||||||
canvas.width = width * CELL_SIZE;
|
|
||||||
canvas.height = height * CELL_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отрисовка карты
|
|
||||||
function drawMap() {
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
|
|
||||||
// Рисуем ячейки
|
|
||||||
for (let y = 0; y < height; y++) {
|
|
||||||
for (let x = 0; x < width; x++) {
|
|
||||||
const cellType = map[y][x];
|
|
||||||
ctx.fillStyle = COLORS[cellType];
|
|
||||||
ctx.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Рисуем сетку
|
|
||||||
ctx.strokeStyle = GRID_COLOR;
|
|
||||||
ctx.lineWidth = 1;
|
|
||||||
|
|
||||||
// Вертикальные линии
|
|
||||||
for (let x = 0; x <= width; x++) {
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(x * CELL_SIZE, 0);
|
|
||||||
ctx.lineTo(x * CELL_SIZE, height * CELL_SIZE);
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Горизонтальные линии
|
|
||||||
for (let y = 0; y <= height; y++) {
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(0, y * CELL_SIZE);
|
|
||||||
ctx.lineTo(width * CELL_SIZE, y * CELL_SIZE);
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Рисуем маркеры для чекпоинтов и старта
|
|
||||||
ctx.font = 'bold 16px Arial';
|
|
||||||
ctx.textAlign = 'center';
|
|
||||||
ctx.textBaseline = 'middle';
|
|
||||||
|
|
||||||
for (let y = 0; y < height; y++) {
|
|
||||||
for (let x = 0; x < width; x++) {
|
|
||||||
if (map[y][x] === 4) {
|
|
||||||
ctx.fillStyle = '#856404';
|
|
||||||
ctx.fillText('C', x * CELL_SIZE + CELL_SIZE / 2, y * CELL_SIZE + CELL_SIZE / 2);
|
|
||||||
} else if (map[y][x] === 5) {
|
|
||||||
ctx.fillStyle = '#155724';
|
|
||||||
ctx.fillText('S', x * CELL_SIZE + CELL_SIZE / 2, y * CELL_SIZE + CELL_SIZE / 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Выбор типа ячейки
|
|
||||||
function selectCellType(type) {
|
|
||||||
selectedType = type;
|
|
||||||
|
|
||||||
// Обновляем UI
|
|
||||||
document.querySelectorAll('.cell-type').forEach(el => {
|
|
||||||
el.classList.remove('active');
|
|
||||||
});
|
|
||||||
document.querySelector(`[data-type="${type}"]`).classList.add('active');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Получение координат ячейки по клику
|
|
||||||
function getCellCoords(event) {
|
|
||||||
const rect = canvas.getBoundingClientRect();
|
|
||||||
const x = Math.floor((event.clientX - rect.left) / CELL_SIZE);
|
|
||||||
const y = Math.floor((event.clientY - rect.top) / CELL_SIZE);
|
|
||||||
|
|
||||||
if (x >= 0 && x < width && y >= 0 && y < height) {
|
|
||||||
return { x, y };
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Установка типа ячейки
|
|
||||||
function setCellType(x, y) {
|
|
||||||
if (x >= 0 && x < width && y >= 0 && y < height) {
|
|
||||||
map[y][x] = selectedType;
|
|
||||||
drawMap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Обработчики событий мыши
|
|
||||||
canvas.addEventListener('mousedown', (e) => {
|
|
||||||
isDrawing = true;
|
|
||||||
const coords = getCellCoords(e);
|
|
||||||
if (coords) {
|
|
||||||
setCellType(coords.x, coords.y);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
canvas.addEventListener('mousemove', (e) => {
|
|
||||||
if (isDrawing) {
|
|
||||||
const coords = getCellCoords(e);
|
|
||||||
if (coords) {
|
|
||||||
setCellType(coords.x, coords.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
canvas.addEventListener('mouseup', () => {
|
|
||||||
isDrawing = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
canvas.addEventListener('mouseleave', () => {
|
|
||||||
isDrawing = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Изменение размера карты
|
|
||||||
function resizeMap() {
|
|
||||||
const newWidth = parseInt(document.getElementById('width').value);
|
|
||||||
const newHeight = parseInt(document.getElementById('height').value);
|
|
||||||
|
|
||||||
if (newWidth < 5 || newWidth > 100 || newHeight < 5 || newHeight > 100) {
|
|
||||||
alert('Размеры должны быть от 5 до 100');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const newMap = Array(newHeight).fill(null).map(() => Array(newWidth).fill(0));
|
|
||||||
|
|
||||||
// Копируем существующие данные
|
|
||||||
const minHeight = Math.min(height, newHeight);
|
|
||||||
const minWidth = Math.min(width, newWidth);
|
|
||||||
|
|
||||||
for (let y = 0; y < minHeight; y++) {
|
|
||||||
for (let x = 0; x < minWidth; x++) {
|
|
||||||
newMap[y][x] = map[y][x];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
width = newWidth;
|
|
||||||
height = newHeight;
|
|
||||||
map = newMap;
|
|
||||||
|
|
||||||
resizeCanvas();
|
|
||||||
drawMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Очистка карты
|
|
||||||
function clearMap() {
|
|
||||||
if (confirm('Вы уверены, что хотите очистить всю карту?')) {
|
|
||||||
initMap();
|
|
||||||
drawMap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Экспорт карты в JSON
|
|
||||||
function exportMap() {
|
|
||||||
const data = {
|
|
||||||
map: map
|
|
||||||
};
|
|
||||||
|
|
||||||
const json = JSON.stringify(data, null, 2);
|
|
||||||
const blob = new Blob([json], { type: 'application/json' });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
|
|
||||||
const a = document.createElement('a');
|
|
||||||
a.href = url;
|
|
||||||
a.download = `racing-map-${width}x${height}.json`;
|
|
||||||
document.body.appendChild(a);
|
|
||||||
a.click();
|
|
||||||
document.body.removeChild(a);
|
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
|
|
||||||
// Также выводим в консоль для быстрого копирования
|
|
||||||
console.log('Экспортированная карта:', json);
|
|
||||||
alert('Карта экспортирована! JSON также выведен в консоль браузера (F12)');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Импорт карты из JSON
|
|
||||||
function importMap(event) {
|
|
||||||
const file = event.target.files[0];
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(e.target.result);
|
|
||||||
|
|
||||||
if (!data.map || !Array.isArray(data.map)) {
|
|
||||||
throw new Error('Неверный формат: отсутствует массив map');
|
|
||||||
}
|
|
||||||
|
|
||||||
const newMap = data.map;
|
|
||||||
|
|
||||||
// Валидация
|
|
||||||
if (!newMap.every(row => Array.isArray(row))) {
|
|
||||||
throw new Error('Неверный формат: map должен быть двумерным массивом');
|
|
||||||
}
|
|
||||||
|
|
||||||
const newHeight = newMap.length;
|
|
||||||
const newWidth = newMap[0].length;
|
|
||||||
|
|
||||||
if (newHeight < 5 || newHeight > 100 || newWidth < 5 || newWidth > 100) {
|
|
||||||
throw new Error('Размеры карты должны быть от 5 до 100');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newMap.every(row => row.length === newWidth)) {
|
|
||||||
throw new Error('Все строки должны иметь одинаковую длину');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверка значений ячеек
|
|
||||||
const validValues = [0, 1, 2, 3, 4, 5];
|
|
||||||
for (let y = 0; y < newHeight; y++) {
|
|
||||||
for (let x = 0; x < newWidth; x++) {
|
|
||||||
if (!validValues.includes(newMap[y][x])) {
|
|
||||||
throw new Error(`Недопустимое значение ячейки: ${newMap[y][x]} на позиции [${y}][${x}]`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Импортируем карту
|
|
||||||
height = newHeight;
|
|
||||||
width = newWidth;
|
|
||||||
map = newMap;
|
|
||||||
|
|
||||||
// Обновляем UI
|
|
||||||
document.getElementById('width').value = width;
|
|
||||||
document.getElementById('height').value = height;
|
|
||||||
|
|
||||||
resizeCanvas();
|
|
||||||
drawMap();
|
|
||||||
|
|
||||||
alert('Карта успешно импортирована!');
|
|
||||||
} catch (error) {
|
|
||||||
alert('Ошибка при импорте: ' + error.message);
|
|
||||||
console.error('Ошибка импорта:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.readAsText(file);
|
|
||||||
|
|
||||||
// Сброс input для возможности повторного импорта того же файла
|
|
||||||
event.target.value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Обработчики изменения размеров
|
|
||||||
document.getElementById('width').addEventListener('keypress', (e) => {
|
|
||||||
if (e.key === 'Enter') resizeMap();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('height').addEventListener('keypress', (e) => {
|
|
||||||
if (e.key === 'Enter') resizeMap();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Инициализация при загрузке страницы
|
|
||||||
init();
|
|
||||||
@@ -1,358 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ru">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Редактор карт - Гонки на бумаге</title>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
min-height: 100vh;
|
|
||||||
padding: 20px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
background: white;
|
|
||||||
border-radius: 20px;
|
|
||||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
||||||
padding: 30px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
text-align: center;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
text-align: center;
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-group {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 2px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-group h3 {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
color: #495057;
|
|
||||||
font-size: 1.1em;
|
|
||||||
border-bottom: 2px solid #dee2e6;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.size-inputs {
|
|
||||||
display: flex;
|
|
||||||
gap: 15px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-wrapper {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-wrapper label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
color: #495057;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-wrapper input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
border: 2px solid #dee2e6;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 16px;
|
|
||||||
transition: border-color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-wrapper input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #667eea;
|
|
||||||
}
|
|
||||||
|
|
||||||
.palette {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cell-type {
|
|
||||||
padding: 15px;
|
|
||||||
border: 3px solid #dee2e6;
|
|
||||||
border-radius: 10px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s;
|
|
||||||
text-align: center;
|
|
||||||
font-weight: 600;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cell-type:hover {
|
|
||||||
transform: translateY(-3px);
|
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cell-type.active {
|
|
||||||
border-color: #667eea;
|
|
||||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.cell-type .code {
|
|
||||||
font-size: 0.8em;
|
|
||||||
color: #6c757d;
|
|
||||||
display: block;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.road { background: #f8f9fa; color: #495057; }
|
|
||||||
.stone { background: #6c757d; color: white; }
|
|
||||||
.snow { background: #e3f2fd; color: #1976d2; }
|
|
||||||
.ice { background: #b3e5fc; color: #0277bd; }
|
|
||||||
.checkpoint { background: #fff3cd; color: #856404; border-color: #ffc107 !important; }
|
|
||||||
.start { background: #d4edda; color: #155724; border-color: #28a745 !important; }
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
flex: 1;
|
|
||||||
padding: 12px 24px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s;
|
|
||||||
min-width: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
button:active {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-success {
|
|
||||||
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-warning {
|
|
||||||
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-danger {
|
|
||||||
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas-wrapper {
|
|
||||||
margin-top: 30px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 12px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas {
|
|
||||||
border: 3px solid #dee2e6;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: crosshair;
|
|
||||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
background: #e7f3ff;
|
|
||||||
border-left: 4px solid #2196f3;
|
|
||||||
border-radius: 8px;
|
|
||||||
color: #0d47a1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info strong {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fileInput {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 8px;
|
|
||||||
background: white;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend-color {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 2px solid #dee2e6;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>🏁 Редактор карт</h1>
|
|
||||||
<p class="subtitle">Гонки на бумаге / Paper Racing</p>
|
|
||||||
|
|
||||||
<div class="controls">
|
|
||||||
<div class="control-group">
|
|
||||||
<h3>📐 Размеры карты</h3>
|
|
||||||
<div class="size-inputs">
|
|
||||||
<div class="input-wrapper">
|
|
||||||
<label for="width">Ширина:</label>
|
|
||||||
<input type="number" id="width" min="5" max="100" value="15">
|
|
||||||
</div>
|
|
||||||
<div class="input-wrapper">
|
|
||||||
<label for="height">Высота:</label>
|
|
||||||
<input type="number" id="height" min="5" max="100" value="15">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="buttons">
|
|
||||||
<button class="btn-primary" onclick="resizeMap()">Применить</button>
|
|
||||||
<button class="btn-danger" onclick="clearMap()">Очистить</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<h3>🎨 Тип ячейки</h3>
|
|
||||||
<div class="palette">
|
|
||||||
<div class="cell-type road active" data-type="0" onclick="selectCellType(0)">
|
|
||||||
<span>Дорога</span>
|
|
||||||
<span class="code">(0)</span>
|
|
||||||
</div>
|
|
||||||
<div class="cell-type stone" data-type="1" onclick="selectCellType(1)">
|
|
||||||
<span>Камень</span>
|
|
||||||
<span class="code">(1)</span>
|
|
||||||
</div>
|
|
||||||
<div class="cell-type snow" data-type="2" onclick="selectCellType(2)">
|
|
||||||
<span>Снег</span>
|
|
||||||
<span class="code">(2)</span>
|
|
||||||
</div>
|
|
||||||
<div class="cell-type ice" data-type="3" onclick="selectCellType(3)">
|
|
||||||
<span>Лёд</span>
|
|
||||||
<span class="code">(3)</span>
|
|
||||||
</div>
|
|
||||||
<div class="cell-type checkpoint" data-type="4" onclick="selectCellType(4)">
|
|
||||||
<span>Чекпоинт</span>
|
|
||||||
<span class="code">(4)</span>
|
|
||||||
</div>
|
|
||||||
<div class="cell-type start" data-type="5" onclick="selectCellType(5)">
|
|
||||||
<span>Старт</span>
|
|
||||||
<span class="code">(5)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<h3>💾 Импорт / Экспорт</h3>
|
|
||||||
<div class="buttons">
|
|
||||||
<button class="btn-success" onclick="exportMap()">📥 Экспорт JSON</button>
|
|
||||||
<button class="btn-warning" onclick="document.getElementById('fileInput').click()">📤 Импорт JSON</button>
|
|
||||||
</div>
|
|
||||||
<input type="file" id="fileInput" accept=".json" onchange="importMap(event)">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="canvas-wrapper">
|
|
||||||
<canvas id="mapCanvas"></canvas>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info">
|
|
||||||
<strong>💡 Подсказки:</strong>
|
|
||||||
• Кликайте по ячейкам для изменения типа<br>
|
|
||||||
• Удерживайте кнопку мыши для рисования<br>
|
|
||||||
• Экспортируйте карту в JSON для использования в игре<br>
|
|
||||||
• Для визуализации решений используйте отдельный <a href="../solution-player/index.html" style="color: #0d47a1; font-weight: bold;">Визуализатор решений</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<h3>📖 Легенда цветов</h3>
|
|
||||||
<div class="legend">
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color road"></div>
|
|
||||||
<span>Дорога (0)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color stone"></div>
|
|
||||||
<span>Камень (1)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color snow"></div>
|
|
||||||
<span>Снег (2)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color ice"></div>
|
|
||||||
<span>Лёд (3)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color checkpoint"></div>
|
|
||||||
<span>Чекпоинт (4)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color start"></div>
|
|
||||||
<span>Старт (5)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="editor.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Скрипт для открытия редактора карт в браузере
|
|
||||||
|
|
||||||
echo "🗺️ Редактор карт - Гонки на бумаге"
|
|
||||||
echo "=================================="
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Определяем путь к редактору
|
|
||||||
EDITOR_PATH="$(cd "$(dirname "$0")" && pwd)/index.html"
|
|
||||||
|
|
||||||
# Пытаемся открыть в браузере
|
|
||||||
if command -v xdg-open > /dev/null; then
|
|
||||||
echo "Открываю редактор в браузере по умолчанию..."
|
|
||||||
xdg-open "$EDITOR_PATH"
|
|
||||||
elif command -v firefox > /dev/null; then
|
|
||||||
echo "Открываю редактор в Firefox..."
|
|
||||||
firefox "$EDITOR_PATH"
|
|
||||||
elif command -v google-chrome > /dev/null; then
|
|
||||||
echo "Открываю редактор в Chrome..."
|
|
||||||
google-chrome "$EDITOR_PATH"
|
|
||||||
elif command -v chromium > /dev/null; then
|
|
||||||
echo "Открываю редактор в Chromium..."
|
|
||||||
chromium "$EDITOR_PATH"
|
|
||||||
else
|
|
||||||
echo "❌ Не найден браузер для автоматического открытия"
|
|
||||||
echo ""
|
|
||||||
echo "Откройте файл вручную:"
|
|
||||||
echo " $EDITOR_PATH"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "✅ Редактор готов к использованию!"
|
|
||||||
echo ""
|
|
||||||
echo "💡 Подсказки:"
|
|
||||||
echo " • Кликайте по ячейкам для изменения типа"
|
|
||||||
echo " • Удерживайте кнопку мыши для рисования"
|
|
||||||
echo " • Экспортируйте карту в JSON для использования в игре"
|
|
||||||
echo ""
|
|
||||||
echo "📖 Документация: README.md"
|
|
||||||
|
|
||||||
|
|
||||||
@@ -24,6 +24,10 @@
|
|||||||
0,
|
0,
|
||||||
2
|
2
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
0,
|
||||||
|
-1
|
||||||
|
],
|
||||||
[
|
[
|
||||||
0,
|
0,
|
||||||
-1
|
-1
|
||||||
|
|||||||
@@ -8,31 +8,44 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>🏁 Редактор карт</h1>
|
<div class="header">
|
||||||
<p class="subtitle">Гонки на бумаге / Paper Racing</p>
|
<div class="header-title">
|
||||||
|
<h1>🏁 Гонки на бумаге</h1>
|
||||||
|
<p class="subtitle">Редактор карт</p>
|
||||||
|
</div>
|
||||||
<div class="nav-links">
|
<div class="nav-links">
|
||||||
<a href="editor.html">🏁 Редактор карт</a>
|
<a href="editor.html" class="active">🏁 Редактор карт</a>
|
||||||
<a href="player.html">🎬 Визуализатор решений</a>
|
<a href="player.html">🎬 Визуализатор решений</a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="controls">
|
<div class="main-content">
|
||||||
|
<div class="sidebar">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<h3>📐 Размеры карты</h3>
|
<h3>📐 Размеры карты</h3>
|
||||||
<div class="size-inputs">
|
<div class="size-inputs">
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<label for="width">Ширина:</label>
|
<label for="width">Ширина:</label>
|
||||||
<input type="number" id="width" min="5" max="100" value="15">
|
<input type="number" id="width" min="5" max="1000" value="15">
|
||||||
</div>
|
</div>
|
||||||
<div class="input-wrapper">
|
<div class="input-wrapper">
|
||||||
<label for="height">Высота:</label>
|
<label for="height">Высота:</label>
|
||||||
<input type="number" id="height" min="5" max="100" value="15">
|
<input type="number" id="height" min="5" max="1000" value="15">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button class="btn-primary" onclick="resizeMap()">Применить</button>
|
<button class="btn-primary" onclick="resizeMap()">Применить</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="control-group">
|
||||||
|
<h3>💾 Импорт / Экспорт</h3>
|
||||||
|
<div class="buttons">
|
||||||
|
<button class="btn-success" onclick="exportMap()">📥 Экспорт JSON</button>
|
||||||
|
<button class="btn-success" onclick="document.getElementById('fileInput').click()">📤 Импорт JSON</button>
|
||||||
<button class="btn-danger" onclick="clearMap()">Очистить</button>
|
<button class="btn-danger" onclick="clearMap()">Очистить</button>
|
||||||
</div>
|
</div>
|
||||||
|
<input type="file" id="fileInput" accept=".json" onchange="importMap(event)">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
@@ -64,20 +77,12 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<h3>💾 Импорт / Экспорт</h3>
|
|
||||||
<div class="buttons">
|
|
||||||
<button class="btn-success" onclick="exportMap()">📥 Экспорт JSON</button>
|
|
||||||
<button class="btn-warning" onclick="document.getElementById('fileInput').click()">📤 Импорт JSON</button>
|
|
||||||
</div>
|
|
||||||
<input type="file" id="fileInput" accept=".json" onchange="importMap(event)">
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="canvas-wrapper">
|
<div class="canvas-wrapper">
|
||||||
<canvas id="mapCanvas"></canvas>
|
<canvas id="mapCanvas"></canvas>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="info">
|
<div class="info">
|
||||||
<strong>💡 Подсказки:</strong>
|
<strong>💡 Подсказки:</strong>
|
||||||
@@ -86,36 +91,6 @@
|
|||||||
• Экспортируйте карту в JSON для использования в игре<br>
|
• Экспортируйте карту в JSON для использования в игре<br>
|
||||||
• Для визуализации решений используйте <a href="player.html" style="color: #0d47a1; font-weight: bold;">Визуализатор решений</a>
|
• Для визуализации решений используйте <a href="player.html" style="color: #0d47a1; font-weight: bold;">Визуализатор решений</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<h3>📖 Легенда цветов</h3>
|
|
||||||
<div class="legend">
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color road"></div>
|
|
||||||
<span>Дорога (0)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color stone"></div>
|
|
||||||
<span>Камень (1)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color snow"></div>
|
|
||||||
<span>Снег (2)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color ice"></div>
|
|
||||||
<span>Лёд (3)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color checkpoint"></div>
|
|
||||||
<span>Чекпоинт (4)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color start"></div>
|
|
||||||
<span>Старт (5)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="common.js"></script>
|
<script src="common.js"></script>
|
||||||
|
|||||||
@@ -8,15 +8,19 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>🎬 Визуализатор решений</h1>
|
<div class="header">
|
||||||
<p class="subtitle">Гонки на бумаге / Paper Racing</p>
|
<div class="header-title">
|
||||||
|
<h1>🎬 Гонки на бумаге</h1>
|
||||||
|
<p class="subtitle">Визуализатор решений</p>
|
||||||
|
</div>
|
||||||
<div class="nav-links">
|
<div class="nav-links">
|
||||||
<a href="editor.html">🏁 Редактор карт</a>
|
<a href="editor.html">🏁 Редактор карт</a>
|
||||||
<a href="player.html">🎬 Визуализатор решений</a>
|
<a href="player.html" class="active">🎬 Визуализатор решений</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="controls">
|
<div class="main-content">
|
||||||
|
<div class="sidebar">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<h3>📂 Загрузка файлов</h3>
|
<h3>📂 Загрузка файлов</h3>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
@@ -26,31 +30,28 @@
|
|||||||
<input type="file" id="mapInput" accept=".json" onchange="loadMap(event)">
|
<input type="file" id="mapInput" accept=".json" onchange="loadMap(event)">
|
||||||
<input type="file" id="solutionInput" accept=".json" onchange="loadSolution(event)">
|
<input type="file" id="solutionInput" accept=".json" onchange="loadSolution(event)">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="canvas-wrapper">
|
<div id="playbackControls" class="visualization-panel">
|
||||||
<canvas id="mapCanvas"></canvas>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="playbackControls" class="visualization-panel hidden">
|
|
||||||
<h3>🎮 Управление воспроизведением</h3>
|
<h3>🎮 Управление воспроизведением</h3>
|
||||||
|
|
||||||
<div class="playback-controls">
|
<div class="playback-controls">
|
||||||
<button class="playback-btn" onclick="playVisualization()" id="playBtn">▶ Play</button>
|
<button class="playback-btn" onclick="playVisualization()" id="playBtn" disabled>▶ Воспроизвести</button>
|
||||||
<button class="playback-btn" onclick="pauseVisualization()" id="pauseBtn" disabled>⏸ Pause</button>
|
<button class="playback-btn" onclick="pauseVisualization()" id="pauseBtn" disabled>⏸ Пауза</button>
|
||||||
<button class="playback-btn" onclick="resetVisualization()">⏮ Reset</button>
|
<button class="playback-btn" onclick="resetVisualization()" id="resetBtn" disabled>⏮ Сброс</button>
|
||||||
<button class="playback-btn" onclick="stepBackward()">⏪ Back</button>
|
<button class="playback-btn" onclick="stepBackward()" id="backBtn" disabled>⏪ Назад</button>
|
||||||
<button class="playback-btn" onclick="stepForward()">⏩ Forward</button>
|
<button class="playback-btn" onclick="stepForward()" id="forwardBtn" disabled>⏩ Вперёд</button>
|
||||||
|
|
||||||
<div class="speed-control">
|
<div class="speed-control">
|
||||||
|
<div class="speed-control-header">
|
||||||
<label for="speedSlider">Скорость:</label>
|
<label for="speedSlider">Скорость:</label>
|
||||||
<input type="range" id="speedSlider" min="1" max="10" value="5" onchange="updateSpeed()">
|
|
||||||
<span id="speedValue">5x</span>
|
<span id="speedValue">5x</span>
|
||||||
</div>
|
</div>
|
||||||
|
<input type="range" id="speedSlider" min="1" max="10" value="5" onchange="updateSpeed()" disabled>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="stepInfo" class="step-info hidden">
|
<div id="stepInfo" class="step-info">
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<span class="info-label">Шаг</span>
|
<span class="info-label">Шаг</span>
|
||||||
<span class="info-value" id="stepNumber">0 / 0</span>
|
<span class="info-value" id="stepNumber">0 / 0</span>
|
||||||
@@ -67,16 +68,10 @@
|
|||||||
<span class="info-label">Ускорение (ax, ay)</span>
|
<span class="info-label">Ускорение (ax, ay)</span>
|
||||||
<span class="info-value" id="accelerationValue">(0, 0)</span>
|
<span class="info-value" id="accelerationValue">(0, 0)</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<span class="info-label">Очки</span>
|
||||||
|
<span class="info-value" id="scoreValue">0</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="info">
|
|
||||||
<strong>💡 Инструкция:</strong>
|
|
||||||
• Сначала загрузите карту с точкой старта (тип 5)<br>
|
|
||||||
• Затем загрузите файл решения с векторами ускорений<br>
|
|
||||||
• Используйте кнопки управления для просмотра анимации<br>
|
|
||||||
• 🔵 Синяя линия - пройденная траектория<br>
|
|
||||||
• 🔴 Красный круг - текущая позиция<br>
|
|
||||||
• ➡️ Красная стрелка - направление и скорость движения
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
@@ -110,6 +105,22 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="canvas-wrapper">
|
||||||
|
<canvas id="mapCanvas"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<strong>💡 Инструкция:</strong>
|
||||||
|
• Сначала загрузите карту с точкой старта (тип 5)<br>
|
||||||
|
• Затем загрузите файл решения с векторами ускорений<br>
|
||||||
|
• Используйте кнопки управления для просмотра анимации<br>
|
||||||
|
• 🔵 Синяя линия - пройденная траектория<br>
|
||||||
|
• 🔴 Красный круг - текущая позиция<br>
|
||||||
|
• ➡️ Красная стрелка - направление и скорость движения
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script src="common.js"></script>
|
<script src="common.js"></script>
|
||||||
<script src="player.js"></script>
|
<script src="player.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ function init() {
|
|||||||
initMap();
|
initMap();
|
||||||
resizeCanvas();
|
resizeCanvas();
|
||||||
drawMap();
|
drawMap();
|
||||||
|
updateControlsState();
|
||||||
|
updateStepInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Инициализация пустой карты
|
// Инициализация пустой карты
|
||||||
@@ -148,9 +150,11 @@ function simulateTrajectory(accelerations, start) {
|
|||||||
let y = start.y;
|
let y = start.y;
|
||||||
let vx = 0;
|
let vx = 0;
|
||||||
let vy = 0;
|
let vy = 0;
|
||||||
|
let passedCheckpoints = new Set();
|
||||||
|
let totalSteps = accelerations.length;
|
||||||
|
|
||||||
// Начальная позиция
|
// Начальная позиция
|
||||||
traj.push({ x, y, vx, vy, ax: 0, ay: 0 });
|
traj.push({ x, y, vx, vy, ax: 0, ay: 0, passedCheckpoints: 0, score: 0 });
|
||||||
|
|
||||||
// Применяем каждое ускорение
|
// Применяем каждое ускорение
|
||||||
for (let i = 0; i < accelerations.length; i++) {
|
for (let i = 0; i < accelerations.length; i++) {
|
||||||
@@ -164,7 +168,21 @@ function simulateTrajectory(accelerations, start) {
|
|||||||
x += vx;
|
x += vx;
|
||||||
y += vy;
|
y += vy;
|
||||||
|
|
||||||
traj.push({ x, y, vx, vy, ax, ay });
|
// Проверяем, попали ли на чекпоинт
|
||||||
|
const checkpointKey = `${Math.floor(x)},${Math.floor(y)}`;
|
||||||
|
if (map[Math.floor(y)] && map[Math.floor(y)][Math.floor(x)] === 4) {
|
||||||
|
passedCheckpoints.add(checkpointKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вычисляем очки по формуле: 100 * количество пройденных чекпоинтов ^ 1.3 / количество шагов
|
||||||
|
const checkpointCount = passedCheckpoints.size;
|
||||||
|
const score = totalSteps > 0 ? Math.round(100 * Math.pow(checkpointCount, 1.3) / totalSteps) : 0;
|
||||||
|
|
||||||
|
traj.push({
|
||||||
|
x, y, vx, vy, ax, ay,
|
||||||
|
passedCheckpoints: checkpointCount,
|
||||||
|
score: score
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return traj;
|
return traj;
|
||||||
@@ -237,11 +255,8 @@ function loadSolution(event) {
|
|||||||
trajectory = simulateTrajectory(solution, startPosition);
|
trajectory = simulateTrajectory(solution, startPosition);
|
||||||
currentStep = 0;
|
currentStep = 0;
|
||||||
|
|
||||||
// Показываем панель визуализации
|
// Обновляем состояние кнопок и информацию
|
||||||
document.getElementById('playbackControls').classList.remove('hidden');
|
updateControlsState();
|
||||||
document.getElementById('stepInfo').classList.remove('hidden');
|
|
||||||
|
|
||||||
// Обновляем информацию
|
|
||||||
updateStepInfo();
|
updateStepInfo();
|
||||||
drawMap();
|
drawMap();
|
||||||
|
|
||||||
@@ -261,9 +276,28 @@ function loadSolution(event) {
|
|||||||
event.target.value = '';
|
event.target.value = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обновление состояния элементов управления
|
||||||
|
function updateControlsState() {
|
||||||
|
const hasTrajectory = trajectory && trajectory.length > 0;
|
||||||
|
|
||||||
|
document.getElementById('playBtn').disabled = !hasTrajectory;
|
||||||
|
document.getElementById('pauseBtn').disabled = !hasTrajectory || !isPlaying;
|
||||||
|
document.getElementById('resetBtn').disabled = !hasTrajectory;
|
||||||
|
document.getElementById('backBtn').disabled = !hasTrajectory;
|
||||||
|
document.getElementById('forwardBtn').disabled = !hasTrajectory;
|
||||||
|
document.getElementById('speedSlider').disabled = !hasTrajectory;
|
||||||
|
}
|
||||||
|
|
||||||
// Обновление информации о текущем шаге
|
// Обновление информации о текущем шаге
|
||||||
function updateStepInfo() {
|
function updateStepInfo() {
|
||||||
if (!trajectory || trajectory.length === 0) return;
|
if (!trajectory || trajectory.length === 0) {
|
||||||
|
document.getElementById('stepNumber').textContent = '0 / 0';
|
||||||
|
document.getElementById('positionValue').textContent = '(0, 0)';
|
||||||
|
document.getElementById('velocityValue').textContent = '(0, 0)';
|
||||||
|
document.getElementById('accelerationValue').textContent = '(0, 0)';
|
||||||
|
document.getElementById('scoreValue').textContent = '0';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const current = trajectory[currentStep];
|
const current = trajectory[currentStep];
|
||||||
|
|
||||||
@@ -271,6 +305,7 @@ function updateStepInfo() {
|
|||||||
document.getElementById('positionValue').textContent = `(${current.x}, ${current.y})`;
|
document.getElementById('positionValue').textContent = `(${current.x}, ${current.y})`;
|
||||||
document.getElementById('velocityValue').textContent = `(${current.vx}, ${current.vy})`;
|
document.getElementById('velocityValue').textContent = `(${current.vx}, ${current.vy})`;
|
||||||
document.getElementById('accelerationValue').textContent = `(${current.ax}, ${current.ay})`;
|
document.getElementById('accelerationValue').textContent = `(${current.ax}, ${current.ay})`;
|
||||||
|
document.getElementById('scoreValue').textContent = current.score || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Воспроизведение визуализации
|
// Воспроизведение визуализации
|
||||||
@@ -278,8 +313,7 @@ function playVisualization() {
|
|||||||
if (!trajectory || trajectory.length === 0) return;
|
if (!trajectory || trajectory.length === 0) return;
|
||||||
|
|
||||||
isPlaying = true;
|
isPlaying = true;
|
||||||
document.getElementById('playBtn').disabled = true;
|
updateControlsState();
|
||||||
document.getElementById('pauseBtn').disabled = false;
|
|
||||||
|
|
||||||
playbackInterval = setInterval(() => {
|
playbackInterval = setInterval(() => {
|
||||||
if (currentStep < trajectory.length - 1) {
|
if (currentStep < trajectory.length - 1) {
|
||||||
@@ -295,8 +329,7 @@ function playVisualization() {
|
|||||||
// Пауза воспроизведения
|
// Пауза воспроизведения
|
||||||
function pauseVisualization() {
|
function pauseVisualization() {
|
||||||
isPlaying = false;
|
isPlaying = false;
|
||||||
document.getElementById('playBtn').disabled = false;
|
updateControlsState();
|
||||||
document.getElementById('pauseBtn').disabled = true;
|
|
||||||
|
|
||||||
if (playbackInterval) {
|
if (playbackInterval) {
|
||||||
clearInterval(playbackInterval);
|
clearInterval(playbackInterval);
|
||||||
@@ -354,9 +387,8 @@ function clearVisualization() {
|
|||||||
currentStep = 0;
|
currentStep = 0;
|
||||||
startPosition = null;
|
startPosition = null;
|
||||||
|
|
||||||
document.getElementById('playbackControls').classList.add('hidden');
|
updateControlsState();
|
||||||
document.getElementById('stepInfo').classList.add('hidden');
|
updateStepInfo();
|
||||||
|
|
||||||
drawMap();
|
drawMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,77 +18,100 @@ body {
|
|||||||
background: white;
|
background: white;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
||||||
padding: 30px;
|
padding: 20px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-height: calc(100vh - 40px);
|
min-height: calc(100vh - 40px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
padding: 10px 15px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 10px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
text-align: center;
|
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 2px;
|
||||||
font-size: 2em;
|
font-size: 1.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
text-align: center;
|
|
||||||
color: #666;
|
color: #666;
|
||||||
margin-bottom: 20px;
|
font-size: 0.8em;
|
||||||
font-size: 0.9em;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-links {
|
.nav-links {
|
||||||
text-align: center;
|
display: flex;
|
||||||
margin-bottom: 20px;
|
gap: 10px;
|
||||||
padding: 15px;
|
align-items: center;
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-links a {
|
.nav-links a {
|
||||||
margin: 0 15px;
|
padding: 8px 16px;
|
||||||
padding: 10px 20px;
|
background: #e9ecef;
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
color: #495057;
|
||||||
color: white;
|
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
font-size: 0.9em;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-links a:hover {
|
.nav-links a:hover {
|
||||||
|
background: #dee2e6;
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.nav-links a.active {
|
||||||
display: grid;
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
color: white;
|
||||||
gap: 20px;
|
box-shadow: 0 3px 10px rgba(102, 126, 234, 0.4);
|
||||||
margin-bottom: 30px;
|
}
|
||||||
|
|
||||||
|
.nav-links a.active:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.control-group {
|
.control-group {
|
||||||
background: #f8f9fa;
|
background: #f8f9fa;
|
||||||
padding: 20px;
|
padding: 15px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
border: 2px solid #e9ecef;
|
border: 2px solid #e9ecef;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar .control-group {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.control-group h3 {
|
.control-group h3 {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 12px;
|
||||||
color: #495057;
|
color: #495057;
|
||||||
font-size: 1.1em;
|
font-size: 1em;
|
||||||
border-bottom: 2px solid #dee2e6;
|
border-bottom: 2px solid #dee2e6;
|
||||||
padding-bottom: 8px;
|
padding-bottom: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.size-inputs {
|
.size-inputs {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 15px;
|
gap: 10px;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,38 +142,39 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.palette {
|
.palette {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
flex-direction: column;
|
||||||
gap: 10px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell-type {
|
.cell-type {
|
||||||
padding: 15px;
|
padding: 10px;
|
||||||
border: 3px solid #dee2e6;
|
border: 2px solid #dee2e6;
|
||||||
border-radius: 10px;
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
text-align: center;
|
text-align: left;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell-type:hover {
|
.cell-type:hover {
|
||||||
transform: translateY(-3px);
|
transform: translateX(-3px);
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell-type.active {
|
.cell-type.active {
|
||||||
border-color: #667eea;
|
border-color: #667eea;
|
||||||
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
|
border-width: 3px;
|
||||||
transform: scale(1.05);
|
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.cell-type .code {
|
.cell-type .code {
|
||||||
font-size: 0.8em;
|
font-size: 0.75em;
|
||||||
color: #6c757d;
|
color: #6c757d;
|
||||||
display: block;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.road { background: #f8f9fa; color: #495057; }
|
.road { background: #f8f9fa; color: #495057; }
|
||||||
@@ -166,6 +190,14 @@ h1 {
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar .buttons {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar .buttons button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
button {
|
button {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 12px 24px;
|
padding: 12px 24px;
|
||||||
@@ -213,22 +245,55 @@ button:disabled {
|
|||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.main-content {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 15px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
.canvas-wrapper {
|
.canvas-wrapper {
|
||||||
margin-top: 30px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
background: #f8f9fa;
|
background: #f8f9fa;
|
||||||
padding: 20px;
|
|
||||||
border-radius: 12px;
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 1000px;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 280px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 15px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 100%;
|
||||||
|
padding-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar::-webkit-scrollbar {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar::-webkit-scrollbar-track {
|
||||||
|
background: #f1f1f1;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar::-webkit-scrollbar-thumb {
|
||||||
|
background: #667eea;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
|
||||||
canvas {
|
canvas {
|
||||||
border: 3px solid #dee2e6;
|
border: 2px solid #dee2e6;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
cursor: crosshair;
|
cursor: crosshair;
|
||||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
|
||||||
@@ -237,12 +302,13 @@ canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
margin-top: 20px;
|
margin-top: 15px;
|
||||||
padding: 15px;
|
padding: 12px;
|
||||||
background: #e7f3ff;
|
background: #e7f3ff;
|
||||||
border-left: 4px solid #2196f3;
|
border-left: 4px solid #2196f3;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
color: #0d47a1;
|
color: #0d47a1;
|
||||||
|
font-size: 0.9em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info strong {
|
.info strong {
|
||||||
@@ -261,42 +327,49 @@ canvas {
|
|||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar .legend {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.legend-item {
|
.legend-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
gap: 8px;
|
||||||
padding: 8px;
|
padding: 6px;
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
font-size: 0.85em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.legend-color {
|
.legend-color {
|
||||||
width: 30px;
|
width: 24px;
|
||||||
height: 30px;
|
height: 24px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 2px solid #dee2e6;
|
border: 2px solid #dee2e6;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Стили для визуализатора */
|
/* Стили для визуализатора */
|
||||||
.playback-controls {
|
.playback-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
flex-direction: column;
|
||||||
align-items: center;
|
gap: 8px;
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playback-btn {
|
.playback-btn {
|
||||||
padding: 10px 20px;
|
padding: 10px 15px;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 16px;
|
font-size: 14px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
background: #667eea;
|
background: #667eea;
|
||||||
color: white;
|
color: white;
|
||||||
min-width: auto;
|
min-width: auto;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.playback-btn:hover {
|
.playback-btn:hover {
|
||||||
@@ -312,20 +385,44 @@ canvas {
|
|||||||
|
|
||||||
.speed-control {
|
.speed-control {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.speed-control-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.speed-control input[type="range"] {
|
.speed-control input[type="range"] {
|
||||||
width: 150px;
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.speed-control input[type="range"]:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.speed-control label {
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.speed-control #speedValue {
|
||||||
|
font-size: 0.9em;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #667eea;
|
||||||
}
|
}
|
||||||
|
|
||||||
.step-info {
|
.step-info {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 15px;
|
gap: 10px;
|
||||||
margin-top: 15px;
|
padding: 12px;
|
||||||
padding: 15px;
|
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
@@ -342,7 +439,7 @@ canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-value {
|
.info-value {
|
||||||
font-size: 1.2em;
|
font-size: 1.1em;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
@@ -352,15 +449,15 @@ canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.visualization-panel {
|
.visualization-panel {
|
||||||
margin-top: 20px;
|
padding: 15px;
|
||||||
padding: 20px;
|
|
||||||
background: #f8f9fa;
|
background: #f8f9fa;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
border: 2px solid #dee2e6;
|
border: 2px solid #dee2e6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.visualization-panel h3 {
|
.visualization-panel h3 {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 10px;
|
||||||
color: #495057;
|
color: #495057;
|
||||||
|
font-size: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
116
sol-15x15.json
116
sol-15x15.json
@@ -1,116 +0,0 @@
|
|||||||
{
|
|
||||||
"solution": [
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
2,
|
|
||||||
-1
|
|
||||||
],
|
|
||||||
[
|
|
||||||
2,
|
|
||||||
1
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-2,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-1,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-1,
|
|
||||||
1
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-2,
|
|
||||||
-2
|
|
||||||
],
|
|
||||||
[
|
|
||||||
1,
|
|
||||||
-2
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
2
|
|
||||||
],
|
|
||||||
[
|
|
||||||
2,
|
|
||||||
2
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-2,
|
|
||||||
2
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-2,
|
|
||||||
-1
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-1,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
2,
|
|
||||||
-2
|
|
||||||
],
|
|
||||||
[
|
|
||||||
1,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
2,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
# 🎬 Визуализатор решений - Гонки на бумаге
|
|
||||||
|
|
||||||
Веб-приложение для визуализации и анимации решений. Работает без сервера, просто откройте в браузере.
|
|
||||||
|
|
||||||
## 🚀 Быстрый старт
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Запуск визуализатора
|
|
||||||
./open-player.sh
|
|
||||||
# или
|
|
||||||
firefox index.html
|
|
||||||
```
|
|
||||||
|
|
||||||
**Три простых шага:**
|
|
||||||
1. Загрузите карту (📂 Загрузить карту) - файл с полем
|
|
||||||
2. Загрузите решение (🎬 Загрузить решение) - файл с траекторией
|
|
||||||
3. Нажмите ▶ Play и наблюдайте за движением
|
|
||||||
|
|
||||||
## 🎮 Управление
|
|
||||||
|
|
||||||
| Кнопка | Действие |
|
|
||||||
|--------|----------|
|
|
||||||
| ▶ Play | Автоматическое воспроизведение |
|
|
||||||
| ⏸ Pause | Пауза |
|
|
||||||
| ⏮ Reset | Сброс к началу |
|
|
||||||
| ⏪ Back | Шаг назад |
|
|
||||||
| ⏩ Forward | Шаг вперед |
|
|
||||||
| Слайдер | Скорость 1x - 10x |
|
|
||||||
|
|
||||||
## 📊 Визуализация
|
|
||||||
|
|
||||||
- 🔵 **Синяя линия и точки** - пройденная траектория
|
|
||||||
- 🔴 **Красный круг** - текущая позиция
|
|
||||||
- ➡️ **Красная стрелка** - вектор скорости (направление и величина)
|
|
||||||
- **Панель информации** - шаг, позиция (x, y), скорость (vx, vy), ускорение (ax, ay)
|
|
||||||
|
|
||||||
## 📄 Форматы файлов
|
|
||||||
|
|
||||||
### Формат карты
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"map": [
|
|
||||||
[5, 0, 0, 1, 0],
|
|
||||||
[0, 1, 0, 1, 0],
|
|
||||||
[0, 0, 2, 2, 4]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Типы ячеек:** 0-Дорога, 1-Камень, 2-Снег, 3-Лёд, 4-Чекпоинт, 5-Старт (обязательно!)
|
|
||||||
|
|
||||||
### Формат решения
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"solution": [
|
|
||||||
[1, 1],
|
|
||||||
[1, 0],
|
|
||||||
[0, 1]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Структура:** массив векторов ускорения `[[ax, ay], ...]` где `ax`, `ay` - целые числа (обычно от -1 до 1)
|
|
||||||
|
|
||||||
### Физика движения
|
|
||||||
|
|
||||||
На каждом шаге:
|
|
||||||
```
|
|
||||||
velocity += acceleration
|
|
||||||
position += velocity
|
|
||||||
```
|
|
||||||
|
|
||||||
**Пример:**
|
|
||||||
```
|
|
||||||
Шаг 0: pos=(0,0), vel=(0,0)
|
|
||||||
Шаг 1: acc=(1,1) → vel=(1,1) → pos=(1,1)
|
|
||||||
Шаг 2: acc=(1,0) → vel=(2,1) → pos=(3,2)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🎯 Быстрый тест
|
|
||||||
|
|
||||||
1. Откройте `./open-player.sh`
|
|
||||||
2. Загрузите карту `demo-with-start.json`
|
|
||||||
3. Загрузите решение `demo-with-start-solution.json`
|
|
||||||
4. Нажмите ▶ Play и экспериментируйте со скоростью
|
|
||||||
|
|
||||||
## 🔗 Связанные проекты
|
|
||||||
|
|
||||||
- [Редактор карт](../map-editor/) - для создания карт
|
|
||||||
- [Основной проект](../README.md) - C# решатель на основе A*
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Технологии:** HTML5 Canvas, Vanilla JavaScript (ES6+), CSS3
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"solution": [
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
0
|
|
||||||
],
|
|
||||||
[
|
|
||||||
2,
|
|
||||||
2
|
|
||||||
],
|
|
||||||
[
|
|
||||||
2,
|
|
||||||
-1
|
|
||||||
],
|
|
||||||
[
|
|
||||||
-2,
|
|
||||||
-1
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
2
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
2
|
|
||||||
],
|
|
||||||
[
|
|
||||||
0,
|
|
||||||
-1
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
{
|
|
||||||
"map": [
|
|
||||||
[5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 1, 0, 0, 0, 2, 2, 2, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 1, 0, 0, 0, 2, 4, 2, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 1, 1, 0],
|
|
||||||
[0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 2, 2, 2, 0],
|
|
||||||
[0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 4, 2, 0],
|
|
||||||
[0, 0, 0, 0, 0, 3, 3, 3, 0, 0, 0, 2, 2, 2, 0],
|
|
||||||
[0, 0, 0, 0, 0, 3, 1, 3, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 3, 3, 3, 0, 0, 0, 0, 0, 0, 4],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
||||||
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,358 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ru">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Визуализатор решений - Гонки на бумаге</title>
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
min-height: 100vh;
|
|
||||||
padding: 20px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
background: white;
|
|
||||||
border-radius: 20px;
|
|
||||||
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
|
|
||||||
padding: 30px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
text-align: center;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subtitle {
|
|
||||||
text-align: center;
|
|
||||||
color: #666;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-group {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 2px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.control-group h3 {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
color: #495057;
|
|
||||||
font-size: 1.1em;
|
|
||||||
border-bottom: 2px solid #dee2e6;
|
|
||||||
padding-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
flex: 1;
|
|
||||||
padding: 12px 24px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s;
|
|
||||||
min-width: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
button:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
button:active {
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-success {
|
|
||||||
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas-wrapper {
|
|
||||||
margin-top: 30px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: 12px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas {
|
|
||||||
border: 3px solid #dee2e6;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
background: #e7f3ff;
|
|
||||||
border-left: 4px solid #2196f3;
|
|
||||||
border-radius: 8px;
|
|
||||||
color: #0d47a1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info strong {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#mapInput, #solutionInput {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
padding: 8px;
|
|
||||||
background: white;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend-color {
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 2px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.road { background: #f8f9fa; }
|
|
||||||
.stone { background: #6c757d; }
|
|
||||||
.snow { background: #e3f2fd; }
|
|
||||||
.ice { background: #b3e5fc; }
|
|
||||||
.checkpoint { background: #fff3cd; }
|
|
||||||
.start { background: #d4edda; }
|
|
||||||
|
|
||||||
.playback-controls {
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
align-items: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playback-btn {
|
|
||||||
padding: 10px 20px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s;
|
|
||||||
background: #667eea;
|
|
||||||
color: white;
|
|
||||||
min-width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.playback-btn:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.playback-btn:disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
cursor: not-allowed;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.speed-control {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.speed-control input[type="range"] {
|
|
||||||
width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-info {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 15px;
|
|
||||||
margin-top: 15px;
|
|
||||||
padding: 15px;
|
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-label {
|
|
||||||
font-size: 0.85em;
|
|
||||||
color: #6c757d;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-value {
|
|
||||||
font-size: 1.2em;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.visualization-panel {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: 12px;
|
|
||||||
border: 2px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.visualization-panel h3 {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
color: #495057;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>🎬 Визуализатор решений</h1>
|
|
||||||
<p class="subtitle">Гонки на бумаге / Paper Racing</p>
|
|
||||||
|
|
||||||
<div class="controls">
|
|
||||||
<div class="control-group">
|
|
||||||
<h3>📂 Загрузка файлов</h3>
|
|
||||||
<div class="buttons">
|
|
||||||
<button class="btn-primary" id="loadMapBtn" onclick="document.getElementById('mapInput').click()">📂 Загрузить карту</button>
|
|
||||||
<button class="btn-success" id="loadSolutionBtn" onclick="document.getElementById('solutionInput').click()">🎬 Загрузить решение</button>
|
|
||||||
</div>
|
|
||||||
<input type="file" id="mapInput" accept=".json" onchange="loadMap(event)">
|
|
||||||
<input type="file" id="solutionInput" accept=".json" onchange="loadSolution(event)">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="canvas-wrapper">
|
|
||||||
<canvas id="mapCanvas"></canvas>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="playbackControls" class="visualization-panel hidden">
|
|
||||||
<h3>🎮 Управление воспроизведением</h3>
|
|
||||||
|
|
||||||
<div class="playback-controls">
|
|
||||||
<button class="playback-btn" onclick="playVisualization()" id="playBtn">▶ Play</button>
|
|
||||||
<button class="playback-btn" onclick="pauseVisualization()" id="pauseBtn" disabled>⏸ Pause</button>
|
|
||||||
<button class="playback-btn" onclick="resetVisualization()">⏮ Reset</button>
|
|
||||||
<button class="playback-btn" onclick="stepBackward()">⏪ Back</button>
|
|
||||||
<button class="playback-btn" onclick="stepForward()">⏩ Forward</button>
|
|
||||||
|
|
||||||
<div class="speed-control">
|
|
||||||
<label for="speedSlider">Скорость:</label>
|
|
||||||
<input type="range" id="speedSlider" min="1" max="10" value="5" onchange="updateSpeed()">
|
|
||||||
<span id="speedValue">5x</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="stepInfo" class="step-info hidden">
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">Шаг</span>
|
|
||||||
<span class="info-value" id="stepNumber">0 / 0</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">Позиция (x, y)</span>
|
|
||||||
<span class="info-value" id="positionValue">(0, 0)</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">Скорость (vx, vy)</span>
|
|
||||||
<span class="info-value" id="velocityValue">(0, 0)</span>
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<span class="info-label">Ускорение (ax, ay)</span>
|
|
||||||
<span class="info-value" id="accelerationValue">(0, 0)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info">
|
|
||||||
<strong>💡 Инструкция:</strong>
|
|
||||||
• Сначала загрузите карту с точкой старта (тип 5)<br>
|
|
||||||
• Затем загрузите файл решения с векторами ускорений<br>
|
|
||||||
• Используйте кнопки управления для просмотра анимации<br>
|
|
||||||
• 🔵 Синяя линия - пройденная траектория<br>
|
|
||||||
• 🔴 Красный круг - текущая позиция<br>
|
|
||||||
• ➡️ Красная стрелка - направление и скорость движения
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="control-group">
|
|
||||||
<h3>📖 Легенда цветов</h3>
|
|
||||||
<div class="legend">
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color road"></div>
|
|
||||||
<span>Дорога (0)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color stone"></div>
|
|
||||||
<span>Камень (1)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color snow"></div>
|
|
||||||
<span>Снег (2)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color ice"></div>
|
|
||||||
<span>Лёд (3)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color checkpoint"></div>
|
|
||||||
<span>Чекпоинт (4)</span>
|
|
||||||
</div>
|
|
||||||
<div class="legend-item">
|
|
||||||
<div class="legend-color start"></div>
|
|
||||||
<span>Старт (5)</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script src="player.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Скрипт для открытия визуализатора решений в браузере
|
|
||||||
# Script to open solution player in browser
|
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
||||||
HTML_FILE="${SCRIPT_DIR}/index.html"
|
|
||||||
|
|
||||||
echo "🎬 Открытие визуализатора решений..."
|
|
||||||
echo "📂 Путь: ${HTML_FILE}"
|
|
||||||
|
|
||||||
# Пробуем открыть в различных браузерах
|
|
||||||
if command -v xdg-open &> /dev/null; then
|
|
||||||
xdg-open "${HTML_FILE}"
|
|
||||||
echo "✓ Открыто в браузере по умолчанию"
|
|
||||||
elif command -v firefox &> /dev/null; then
|
|
||||||
firefox "${HTML_FILE}" &
|
|
||||||
echo "✓ Открыто в Firefox"
|
|
||||||
elif command -v chromium &> /dev/null; then
|
|
||||||
chromium "${HTML_FILE}" &
|
|
||||||
echo "✓ Открыто в Chromium"
|
|
||||||
elif command -v google-chrome &> /dev/null; then
|
|
||||||
google-chrome "${HTML_FILE}" &
|
|
||||||
echo "✓ Открыто в Google Chrome"
|
|
||||||
else
|
|
||||||
echo "❌ Не найден браузер"
|
|
||||||
echo "Откройте файл вручную: ${HTML_FILE}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "💡 Инструкция:"
|
|
||||||
echo "1. Загрузите карту с точкой старта (файл map)"
|
|
||||||
echo "2. Загрузите решение (файл solution)"
|
|
||||||
echo "3. Используйте кнопки управления для просмотра"
|
|
||||||
echo ""
|
|
||||||
echo "📁 Примеры файлов:"
|
|
||||||
echo " - demo-with-start.json (карта)"
|
|
||||||
echo " - example-solution.json (решение)"
|
|
||||||
|
|
||||||
@@ -1,454 +0,0 @@
|
|||||||
// Конфигурация плеера
|
|
||||||
const CELL_SIZE = 30;
|
|
||||||
const GRID_COLOR = '#dee2e6';
|
|
||||||
const COLORS = {
|
|
||||||
0: '#f8f9fa', // Дорога
|
|
||||||
1: '#6c757d', // Камень
|
|
||||||
2: '#e3f2fd', // Снег
|
|
||||||
3: '#b3e5fc', // Лёд
|
|
||||||
4: '#fff3cd', // Чекпоинт
|
|
||||||
5: '#d4edda' // Старт
|
|
||||||
};
|
|
||||||
|
|
||||||
// Состояние плеера
|
|
||||||
let width = 15;
|
|
||||||
let height = 15;
|
|
||||||
let map = [];
|
|
||||||
let solution = null;
|
|
||||||
let trajectory = [];
|
|
||||||
let currentStep = 0;
|
|
||||||
let isPlaying = false;
|
|
||||||
let playbackSpeed = 5;
|
|
||||||
let playbackInterval = null;
|
|
||||||
let startPosition = null;
|
|
||||||
|
|
||||||
// Canvas элементы
|
|
||||||
const canvas = document.getElementById('mapCanvas');
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
// Инициализация
|
|
||||||
function init() {
|
|
||||||
initMap();
|
|
||||||
resizeCanvas();
|
|
||||||
drawMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Инициализация пустой карты
|
|
||||||
function initMap() {
|
|
||||||
map = Array(height).fill(null).map(() => Array(width).fill(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Изменение размера canvas
|
|
||||||
function resizeCanvas() {
|
|
||||||
canvas.width = width * CELL_SIZE;
|
|
||||||
canvas.height = height * CELL_SIZE;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отрисовка карты
|
|
||||||
function drawMap() {
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
|
|
||||||
// Рисуем ячейки
|
|
||||||
for (let y = 0; y < height; y++) {
|
|
||||||
for (let x = 0; x < width; x++) {
|
|
||||||
const cellType = map[y][x];
|
|
||||||
ctx.fillStyle = COLORS[cellType];
|
|
||||||
ctx.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Рисуем сетку
|
|
||||||
ctx.strokeStyle = GRID_COLOR;
|
|
||||||
ctx.lineWidth = 1;
|
|
||||||
|
|
||||||
// Вертикальные линии
|
|
||||||
for (let x = 0; x <= width; x++) {
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(x * CELL_SIZE, 0);
|
|
||||||
ctx.lineTo(x * CELL_SIZE, height * CELL_SIZE);
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Горизонтальные линии
|
|
||||||
for (let y = 0; y <= height; y++) {
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(0, y * CELL_SIZE);
|
|
||||||
ctx.lineTo(width * CELL_SIZE, y * CELL_SIZE);
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Рисуем маркеры для чекпоинтов и старта
|
|
||||||
ctx.font = 'bold 16px Arial';
|
|
||||||
ctx.textAlign = 'center';
|
|
||||||
ctx.textBaseline = 'middle';
|
|
||||||
|
|
||||||
for (let y = 0; y < height; y++) {
|
|
||||||
for (let x = 0; x < width; x++) {
|
|
||||||
if (map[y][x] === 4) {
|
|
||||||
ctx.fillStyle = '#856404';
|
|
||||||
ctx.fillText('C', x * CELL_SIZE + CELL_SIZE / 2, y * CELL_SIZE + CELL_SIZE / 2);
|
|
||||||
} else if (map[y][x] === 5) {
|
|
||||||
ctx.fillStyle = '#155724';
|
|
||||||
ctx.fillText('S', x * CELL_SIZE + CELL_SIZE / 2, y * CELL_SIZE + CELL_SIZE / 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Рисуем визуализацию траектории, если она есть
|
|
||||||
if (trajectory.length > 0) {
|
|
||||||
drawTrajectory();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Рисование траектории решения
|
|
||||||
function drawTrajectory() {
|
|
||||||
if (!trajectory || trajectory.length === 0) return;
|
|
||||||
|
|
||||||
// Рисуем все предыдущие позиции как след
|
|
||||||
ctx.strokeStyle = '#667eea';
|
|
||||||
ctx.lineWidth = 3;
|
|
||||||
ctx.lineCap = 'round';
|
|
||||||
ctx.lineJoin = 'round';
|
|
||||||
|
|
||||||
ctx.beginPath();
|
|
||||||
for (let i = 0; i <= currentStep && i < trajectory.length; i++) {
|
|
||||||
const pos = trajectory[i];
|
|
||||||
const screenX = pos.x * CELL_SIZE + CELL_SIZE / 2;
|
|
||||||
const screenY = pos.y * CELL_SIZE + CELL_SIZE / 2;
|
|
||||||
|
|
||||||
if (i === 0) {
|
|
||||||
ctx.moveTo(screenX, screenY);
|
|
||||||
} else {
|
|
||||||
ctx.lineTo(screenX, screenY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
// Рисуем точки на каждом шаге
|
|
||||||
for (let i = 0; i <= currentStep && i < trajectory.length; i++) {
|
|
||||||
const pos = trajectory[i];
|
|
||||||
const screenX = pos.x * CELL_SIZE + CELL_SIZE / 2;
|
|
||||||
const screenY = pos.y * CELL_SIZE + CELL_SIZE / 2;
|
|
||||||
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(screenX, screenY, 4, 0, Math.PI * 2);
|
|
||||||
ctx.fillStyle = '#667eea';
|
|
||||||
ctx.fill();
|
|
||||||
ctx.strokeStyle = 'white';
|
|
||||||
ctx.lineWidth = 2;
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Рисуем текущую позицию большим кругом
|
|
||||||
if (currentStep < trajectory.length) {
|
|
||||||
const current = trajectory[currentStep];
|
|
||||||
const screenX = current.x * CELL_SIZE + CELL_SIZE / 2;
|
|
||||||
const screenY = current.y * CELL_SIZE + CELL_SIZE / 2;
|
|
||||||
|
|
||||||
// Пульсирующий эффект
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(screenX, screenY, 10, 0, Math.PI * 2);
|
|
||||||
ctx.fillStyle = '#f5576c';
|
|
||||||
ctx.fill();
|
|
||||||
ctx.strokeStyle = 'white';
|
|
||||||
ctx.lineWidth = 3;
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
// Стрелка направления скорости
|
|
||||||
if (current.vx !== 0 || current.vy !== 0) {
|
|
||||||
const arrowLen = 20;
|
|
||||||
const angle = Math.atan2(current.vy, current.vx);
|
|
||||||
const endX = screenX + Math.cos(angle) * arrowLen;
|
|
||||||
const endY = screenY + Math.sin(angle) * arrowLen;
|
|
||||||
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(screenX, screenY);
|
|
||||||
ctx.lineTo(endX, endY);
|
|
||||||
ctx.strokeStyle = '#f5576c';
|
|
||||||
ctx.lineWidth = 3;
|
|
||||||
ctx.stroke();
|
|
||||||
|
|
||||||
// Наконечник стрелки
|
|
||||||
const headLen = 8;
|
|
||||||
const headAngle = Math.PI / 6;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(endX, endY);
|
|
||||||
ctx.lineTo(
|
|
||||||
endX - headLen * Math.cos(angle - headAngle),
|
|
||||||
endY - headLen * Math.sin(angle - headAngle)
|
|
||||||
);
|
|
||||||
ctx.moveTo(endX, endY);
|
|
||||||
ctx.lineTo(
|
|
||||||
endX - headLen * Math.cos(angle + headAngle),
|
|
||||||
endY - headLen * Math.sin(angle + headAngle)
|
|
||||||
);
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Поиск стартовой позиции на карте
|
|
||||||
function findStartPosition() {
|
|
||||||
for (let y = 0; y < height; y++) {
|
|
||||||
for (let x = 0; x < width; x++) {
|
|
||||||
if (map[y][x] === 5) {
|
|
||||||
return { x, y };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Симуляция траектории на основе векторов ускорений
|
|
||||||
function simulateTrajectory(accelerations, start) {
|
|
||||||
const traj = [];
|
|
||||||
let x = start.x;
|
|
||||||
let y = start.y;
|
|
||||||
let vx = 0;
|
|
||||||
let vy = 0;
|
|
||||||
|
|
||||||
// Начальная позиция
|
|
||||||
traj.push({ x, y, vx, vy, ax: 0, ay: 0 });
|
|
||||||
|
|
||||||
// Применяем каждое ускорение
|
|
||||||
for (let i = 0; i < accelerations.length; i++) {
|
|
||||||
const [ax, ay] = accelerations[i];
|
|
||||||
|
|
||||||
// Обновляем скорость
|
|
||||||
vx += ax;
|
|
||||||
vy += ay;
|
|
||||||
|
|
||||||
// Обновляем позицию
|
|
||||||
x += vx;
|
|
||||||
y += vy;
|
|
||||||
|
|
||||||
traj.push({ x, y, vx, vy, ax, ay });
|
|
||||||
}
|
|
||||||
|
|
||||||
return traj;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Загрузка карты из JSON
|
|
||||||
function loadMap(event) {
|
|
||||||
const file = event.target.files[0];
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(e.target.result);
|
|
||||||
|
|
||||||
if (!data.map || !Array.isArray(data.map)) {
|
|
||||||
throw new Error('Неверный формат: отсутствует массив map');
|
|
||||||
}
|
|
||||||
|
|
||||||
const newMap = data.map;
|
|
||||||
|
|
||||||
// Валидация
|
|
||||||
if (!newMap.every(row => Array.isArray(row))) {
|
|
||||||
throw new Error('Неверный формат: map должен быть двумерным массивом');
|
|
||||||
}
|
|
||||||
|
|
||||||
const newHeight = newMap.length;
|
|
||||||
const newWidth = newMap[0].length;
|
|
||||||
|
|
||||||
if (newHeight < 5 || newHeight > 100 || newWidth < 5 || newWidth > 100) {
|
|
||||||
throw new Error('Размеры карты должны быть от 5 до 100');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!newMap.every(row => row.length === newWidth)) {
|
|
||||||
throw new Error('Все строки должны иметь одинаковую длину');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверка значений ячеек
|
|
||||||
const validValues = [0, 1, 2, 3, 4, 5];
|
|
||||||
for (let y = 0; y < newHeight; y++) {
|
|
||||||
for (let x = 0; x < newWidth; x++) {
|
|
||||||
if (!validValues.includes(newMap[y][x])) {
|
|
||||||
throw new Error(`Недопустимое значение ячейки: ${newMap[y][x]} на позиции [${y}][${x}]`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Импортируем карту
|
|
||||||
height = newHeight;
|
|
||||||
width = newWidth;
|
|
||||||
map = newMap;
|
|
||||||
|
|
||||||
// Очищаем траекторию при загрузке новой карты
|
|
||||||
clearVisualization();
|
|
||||||
|
|
||||||
resizeCanvas();
|
|
||||||
drawMap();
|
|
||||||
|
|
||||||
document.getElementById('loadMapBtn').textContent = '✓ Карта загружена';
|
|
||||||
setTimeout(() => {
|
|
||||||
document.getElementById('loadMapBtn').textContent = '📂 Загрузить карту';
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
alert('Карта успешно загружена!');
|
|
||||||
} catch (error) {
|
|
||||||
alert('Ошибка при загрузке карты: ' + error.message);
|
|
||||||
console.error('Ошибка загрузки:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.readAsText(file);
|
|
||||||
event.target.value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Загрузка решения из JSON
|
|
||||||
function loadSolution(event) {
|
|
||||||
const file = event.target.files[0];
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = (e) => {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(e.target.result);
|
|
||||||
|
|
||||||
if (!data.solution || !Array.isArray(data.solution)) {
|
|
||||||
throw new Error('Неверный формат: отсутствует массив solution');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверяем, что это массив массивов из двух чисел
|
|
||||||
if (!data.solution.every(acc => Array.isArray(acc) && acc.length === 2)) {
|
|
||||||
throw new Error('Неверный формат: solution должен быть массивом [[ax, ay], ...]');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Находим стартовую позицию
|
|
||||||
startPosition = findStartPosition();
|
|
||||||
if (!startPosition) {
|
|
||||||
throw new Error('На карте не найдена точка старта (тип 5). Сначала загрузите карту с точкой старта.');
|
|
||||||
}
|
|
||||||
|
|
||||||
solution = data.solution;
|
|
||||||
trajectory = simulateTrajectory(solution, startPosition);
|
|
||||||
currentStep = 0;
|
|
||||||
|
|
||||||
// Показываем панель визуализации
|
|
||||||
document.getElementById('playbackControls').classList.remove('hidden');
|
|
||||||
document.getElementById('stepInfo').classList.remove('hidden');
|
|
||||||
|
|
||||||
// Обновляем информацию
|
|
||||||
updateStepInfo();
|
|
||||||
drawMap();
|
|
||||||
|
|
||||||
document.getElementById('loadSolutionBtn').textContent = '✓ Решение загружено';
|
|
||||||
setTimeout(() => {
|
|
||||||
document.getElementById('loadSolutionBtn').textContent = '🎬 Загрузить решение';
|
|
||||||
}, 2000);
|
|
||||||
|
|
||||||
alert(`Решение загружено! ${solution.length} шагов.`);
|
|
||||||
} catch (error) {
|
|
||||||
alert('Ошибка при загрузке решения: ' + error.message);
|
|
||||||
console.error('Ошибка загрузки:', error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.readAsText(file);
|
|
||||||
event.target.value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Обновление информации о текущем шаге
|
|
||||||
function updateStepInfo() {
|
|
||||||
if (!trajectory || trajectory.length === 0) return;
|
|
||||||
|
|
||||||
const current = trajectory[currentStep];
|
|
||||||
|
|
||||||
document.getElementById('stepNumber').textContent = `${currentStep} / ${trajectory.length - 1}`;
|
|
||||||
document.getElementById('positionValue').textContent = `(${current.x}, ${current.y})`;
|
|
||||||
document.getElementById('velocityValue').textContent = `(${current.vx}, ${current.vy})`;
|
|
||||||
document.getElementById('accelerationValue').textContent = `(${current.ax}, ${current.ay})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Воспроизведение визуализации
|
|
||||||
function playVisualization() {
|
|
||||||
if (!trajectory || trajectory.length === 0) return;
|
|
||||||
|
|
||||||
isPlaying = true;
|
|
||||||
document.getElementById('playBtn').disabled = true;
|
|
||||||
document.getElementById('pauseBtn').disabled = false;
|
|
||||||
|
|
||||||
playbackInterval = setInterval(() => {
|
|
||||||
if (currentStep < trajectory.length - 1) {
|
|
||||||
currentStep++;
|
|
||||||
updateStepInfo();
|
|
||||||
drawMap();
|
|
||||||
} else {
|
|
||||||
pauseVisualization();
|
|
||||||
}
|
|
||||||
}, 1000 / playbackSpeed);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Пауза воспроизведения
|
|
||||||
function pauseVisualization() {
|
|
||||||
isPlaying = false;
|
|
||||||
document.getElementById('playBtn').disabled = false;
|
|
||||||
document.getElementById('pauseBtn').disabled = true;
|
|
||||||
|
|
||||||
if (playbackInterval) {
|
|
||||||
clearInterval(playbackInterval);
|
|
||||||
playbackInterval = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Сброс визуализации
|
|
||||||
function resetVisualization() {
|
|
||||||
pauseVisualization();
|
|
||||||
currentStep = 0;
|
|
||||||
updateStepInfo();
|
|
||||||
drawMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Шаг вперед
|
|
||||||
function stepForward() {
|
|
||||||
if (!trajectory || trajectory.length === 0) return;
|
|
||||||
|
|
||||||
if (currentStep < trajectory.length - 1) {
|
|
||||||
currentStep++;
|
|
||||||
updateStepInfo();
|
|
||||||
drawMap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Шаг назад
|
|
||||||
function stepBackward() {
|
|
||||||
if (!trajectory || trajectory.length === 0) return;
|
|
||||||
|
|
||||||
if (currentStep > 0) {
|
|
||||||
currentStep--;
|
|
||||||
updateStepInfo();
|
|
||||||
drawMap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Обновление скорости воспроизведения
|
|
||||||
function updateSpeed() {
|
|
||||||
playbackSpeed = parseInt(document.getElementById('speedSlider').value);
|
|
||||||
document.getElementById('speedValue').textContent = `${playbackSpeed}x`;
|
|
||||||
|
|
||||||
// Если воспроизведение идет, перезапускаем с новой скоростью
|
|
||||||
if (isPlaying) {
|
|
||||||
pauseVisualization();
|
|
||||||
playVisualization();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Очистка визуализации
|
|
||||||
function clearVisualization() {
|
|
||||||
pauseVisualization();
|
|
||||||
solution = null;
|
|
||||||
trajectory = [];
|
|
||||||
currentStep = 0;
|
|
||||||
startPosition = null;
|
|
||||||
|
|
||||||
document.getElementById('playbackControls').classList.add('hidden');
|
|
||||||
document.getElementById('stepInfo').classList.add('hidden');
|
|
||||||
|
|
||||||
drawMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Инициализация при загрузке страницы
|
|
||||||
init();
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user