diff --git a/QUICKSTART-WEB.md b/QUICKSTART-WEB.md new file mode 100644 index 0000000..a0ff85f --- /dev/null +++ b/QUICKSTART-WEB.md @@ -0,0 +1,133 @@ +# 🚀 Быстрый старт - Веб-инструменты + +## Два независимых приложения + +### 🗺️ Редактор карт +```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` вручную + +--- + +Приятного использования! 🎉 + diff --git a/RACING-TOOLS-INFO.md b/RACING-TOOLS-INFO.md new file mode 100644 index 0000000..5696067 --- /dev/null +++ b/RACING-TOOLS-INFO.md @@ -0,0 +1,81 @@ +# 🏁 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 (Объединенная) + diff --git a/SEPARATION-SUMMARY.md b/SEPARATION-SUMMARY.md new file mode 100644 index 0000000..8bff5b3 --- /dev/null +++ b/SEPARATION-SUMMARY.md @@ -0,0 +1,190 @@ +# 📊 Итоги разделения проектов + +## ✅ Выполненные изменения + +### Создано два независимых проекта: + +#### 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 + diff --git a/WEB-TOOLS.md b/WEB-TOOLS.md new file mode 100644 index 0000000..3c3d000 --- /dev/null +++ b/WEB-TOOLS.md @@ -0,0 +1,166 @@ +# 🌐 Веб-инструменты для "Гонки на бумаге" + +В проекте доступны два независимых веб-приложения: + +## 🗺️ Редактор карт + +**Назначение:** Создание и редактирование карт для игры + +**Расположение:** `map-editor/` + +**Функции:** +- Рисование карт мышью +- 6 типов ячеек (дорога, камень, снег, лёд, чекпоинт, старт) +- Экспорт/импорт JSON +- Изменение размеров карты +- Валидация данных + +**Запуск:** +```bash +cd map-editor +./open-editor.sh +# или просто откройте index.html в браузере +``` + +**Документация:** [map-editor/README.md](map-editor/README.md) + +--- + +## 🎬 Визуализатор решений + +**Назначение:** Анимация и визуализация решений + +**Расположение:** `solution-player/` + +**Функции:** +- Загрузка карт и решений +- Пошаговое воспроизведение +- Регулировка скорости (1x-10x) +- Навигация вперед/назад +- Отображение векторов скорости +- Детальная информация о каждом шаге + +**Запуск:** +```bash +cd solution-player +./open-player.sh +# или просто откройте index.html в браузере +``` + +**Документация:** [solution-player/README.md](solution-player/README.md) + +--- + +## 🚀 Быстрый старт + +### Создание и тестирование карты + +1. **Создайте карту:** + ```bash + cd map-editor + ./open-editor.sh + ``` + - Нарисуйте карту + - Обязательно добавьте точку старта (тип 5) + - Экспортируйте в JSON + +2. **Решите карту:** + ```bash + cd .. + dotnet run --project racing-astar.csproj maps/your-map.json + ``` + +3. **Визуализируйте решение:** + ```bash + cd solution-player + ./open-player.sh + ``` + - Загрузите карту + - Загрузите решение + - Нажмите Play + +--- + +## 📋 Форматы файлов + +### Карта (map) + +```json +{ + "map": [ + [0, 0, 5], + [0, 1, 0], + [4, 0, 0] + ] +} +``` + +**Типы ячеек:** +- `0` - Дорога +- `1` - Камень (препятствие) +- `2` - Снег (замедление) +- `3` - Лёд (скольжение) +- `4` - Чекпоинт +- `5` - Старт + +### Решение (solution) + +```json +{ + "solution": [ + [1, 0], + [0, 1], + [-1, 0] + ] +} +``` + +**Формат:** массив векторов ускорения `[[ax, ay], ...]` + +--- + +## 🛠️ Технологии + +Оба приложения: +- ✅ Работают без сервера +- ✅ Pure HTML/CSS/JavaScript +- ✅ HTML5 Canvas +- ✅ Современный UI/UX +- ✅ Не требуют установки + +--- + +## 📁 Структура проекта + +``` +racing/ +├── map-editor/ # Редактор карт +│ ├── index.html +│ ├── editor.js +│ ├── README.md +│ └── *.json # Примеры карт +│ +├── solution-player/ # Визуализатор решений +│ ├── index.html +│ ├── player.js +│ ├── README.md +│ └── *.json # Примеры карт и решений +│ +└── maps/ # Основная коллекция карт + └── *.json +``` + +--- + +## 🔗 Связь между приложениями + +1. **Редактор карт** → создает JSON файлы карт +2. **C# Solver** → принимает карты, генерирует решения +3. **Визуализатор** → отображает карты и решения + +Приложения полностью независимы и могут использоваться отдельно. + +--- + +Создано для проекта [Racing](README.md) + diff --git a/map-editor/FEATURES.md b/map-editor/FEATURES.md deleted file mode 100644 index d5a5333..0000000 --- a/map-editor/FEATURES.md +++ /dev/null @@ -1,166 +0,0 @@ -# 🎯 Возможности редактора карт - -## 🗺️ Редактирование карт - -### Интерфейс редактора -- **Современный дизайн** с градиентами и анимациями -- **Адаптивная сетка** для различных размеров экрана -- **Интуитивные элементы управления** с подсказками -- **Цветовая палитра** для быстрого выбора типов ячеек - -### Типы поверхностей -1. **Дорога (0)** - Обычная поверхность -2. **Камень (1)** - Непроходимое препятствие -3. **Снег (2)** - Поверхность с замедлением -4. **Лёд (3)** - Скользкая поверхность -5. **Чекпоинт (4)** - Контрольная точка маршрута -6. **Старт (5)** - Начальная точка движения - -### Инструменты рисования -- **Одиночный клик** - установка одной ячейки -- **Перетаскивание мыши** - непрерывное рисование -- **Переключение типов** - мгновенный выбор из палитры -- **Изменение размеров** - от 5×5 до 100×100 -- **Очистка карты** - быстрый сброс - -### Импорт/Экспорт -- **JSON формат** - стандартный формат данных -- **Валидация** - проверка корректности при импорте -- **Автоматическое именование** - файлы именуются по размеру -- **Консольный вывод** - JSON доступен в консоли браузера (F12) - -## 🎬 Визуализация решений - -### Загрузка и симуляция -- **Формат решения** - массив векторов ускорения `[[ax, ay], ...]` -- **Физика движения** - точная симуляция инерции и ускорения -- **Поиск старта** - автоматическое определение начальной позиции -- **Валидация** - проверка корректности формата решения - -### Элементы визуализации - -#### Траектория движения -- 🔵 **Синяя линия** - пройденный путь -- 🔵 **Синие точки** - позиции на каждом шаге -- 🔴 **Красный круг** - текущая позиция (10px) -- ➡️ **Красная стрелка** - вектор скорости - -#### Информационная панель -Показывает в реальном времени: -- **Номер шага** - текущий/всего -- **Позиция (x, y)** - координаты на карте -- **Скорость (vx, vy)** - вектор скорости -- **Ускорение (ax, ay)** - текущий вектор ускорения - -### Управление воспроизведением - -#### Кнопки управления -- **▶ Play** - автоматическое воспроизведение -- **⏸ Pause** - остановка воспроизведения -- **⏮ Reset** - возврат к началу -- **⏭ Step** - пошаговое движение вперёд - -#### Настройки скорости -- **Slider** - регулятор от 1x до 10x -- **Динамическое изменение** - применяется на лету -- **Индикатор** - отображение текущей скорости - -### Физическая модель - -``` -Начальное состояние: - position = start_cell - velocity = (0, 0) - -На каждом шаге: - 1. velocity += acceleration - 2. position += velocity - -Пример: - Шаг 0: pos=(0,0), vel=(0,0), acc=(0,0) - Шаг 1: acc=(1,1) → vel=(1,1) → pos=(1,1) - Шаг 2: acc=(1,0) → vel=(2,1) → pos=(3,2) - Шаг 3: acc=(0,1) → vel=(2,2) → pos=(5,4) -``` - -## 🎨 Визуальные эффекты - -### Анимации -- **Плавные переходы** - smooth transitions на всех элементах -- **Hover эффекты** - поднятие кнопок при наведении -- **Пульсация** - текущая позиция выделяется -- **Градиенты** - современные цветовые переходы - -### Цветовая схема -- **Основной фон** - градиент фиолетовый → пурпурный -- **Карточки** - белый с тенями и скруглением -- **Акценты** - фиолетовый (#667eea) -- **Траектория** - синий с прозрачностью -- **Текущая позиция** - ярко-красный (#f5576c) - -## 🔧 Технические детали - -### Canvas отрисовка -- **Размер ячейки** - 30px -- **Сглаживание** - anti-aliasing включён -- **Слои** - карта → траектория → маркеры -- **Обновление** - перерисовка только при изменении - -### Производительность -- **Vanilla JS** - без тяжёлых фреймворков -- **Оптимизация** - минимум перерисовок -- **Память** - эффективное использование -- **Масштабируемость** - работает до 100×100 - -### Совместимость -- ✅ Chrome/Chromium -- ✅ Firefox -- ✅ Safari -- ✅ Edge -- ✅ Любой современный браузер с Canvas API - -## 📊 Варианты использования - -### Для разработчиков -1. Создание тестовых карт -2. Отладка алгоритмов поиска пути -3. Визуализация результатов решения -4. Демонстрация работы алгоритмов - -### Для преподавателей -1. Демонстрация алгоритмов на лекциях -2. Создание заданий для студентов -3. Визуализация различных стратегий -4. Анализ оптимальности решений - -### Для студентов -1. Понимание физики движения -2. Анализ работы алгоритмов -3. Сравнение различных решений -4. Отладка собственных реализаций - -## 🚀 Преимущества - -### Простота использования -- **Без установки** - работает в браузере -- **Без зависимостей** - pure HTML/CSS/JS -- **Интуитивный UI** - понятен с первого взгляда -- **Примеры** - готовые карты и решения - -### Функциональность -- **Полный цикл** - от создания до визуализации -- **Гибкость** - настройка всех параметров -- **Точность** - корректная физическая модель -- **Наглядность** - понятное отображение данных - -### Расширяемость -- **Открытый код** - легко модифицировать -- **Модульность** - чёткая структура функций -- **Документация** - подробные комментарии -- **Примеры** - образцы для кастомизации - ---- - -🎯 **Итог**: Мощный и удобный инструмент для работы с картами гонок и визуализации решений! - - diff --git a/map-editor/INDEX.md b/map-editor/INDEX.md deleted file mode 100644 index 95ca4a1..0000000 --- a/map-editor/INDEX.md +++ /dev/null @@ -1,119 +0,0 @@ -# 📑 Индекс файлов редактора карт - -## 📚 Документация - -| Файл | Описание | -|------|----------| -| **README.md** | Полная документация с инструкциями | -| **QUICKSTART.md** | Быстрый старт за 2 минуты | -| **FEATURES.md** | Подробное описание всех возможностей | -| **INDEX.md** | Этот файл - навигация по проекту | - -## 🎯 Основные файлы - -| Файл | Тип | Описание | -|------|-----|----------| -| **index.html** | HTML | Главная страница редактора | -| **editor.js** | JS | Логика редактора и визуализации | -| **open-editor.sh** | Shell | Скрипт для быстрого запуска | - -## 🗺️ Примеры карт - -| Файл | Размер | Описание | -|------|--------|----------| -| **simple-track.json** | 10×10 | Простая трасса для начинающих | -| **demo-with-start.json** | 15×15 | Демонстрационная карта с препятствиями | -| **example-maps.json** | Разные | Коллекция из 5 различных карт | - -### Содержимое example-maps.json: -1. Simple Track (10×10) - простая трасса -2. Ice Circuit (10×10) - карта с ледяными участками -3. Obstacle Course (15×15) - сложная карта с препятствиями -4. Minimal (5×5) - минимальная карта для тестирования -5. Empty Large (20×20) - пустая карта для создания - -## 🎬 Примеры решений - -| Файл | Шагов | Сложность | Рекомендуется для | -|------|-------|-----------|-------------------| -| **example-solution.json** | 10 | Простая | simple-track.json | -| **complex-solution.json** | 20 | Сложная | Любой карты | -| **demo-solution.json** | 15 | Средняя | demo-with-start.json | - -## 🚀 Быстрый старт - -### Вариант 1: Просмотр примера -1. Откройте `index.html` -2. Импортируйте `demo-with-start.json` -3. Загрузите решение `demo-solution.json` -4. Нажмите ▶ Play - -### Вариант 2: Создание своей карты -1. Откройте `index.html` -2. Создайте карту с помощью инструментов -3. Экспортируйте в JSON -4. Используйте в игре - -## 📖 Рекомендуемый порядок чтения - -Для новичков: -1. 📄 **QUICKSTART.md** - начните здесь -2. 🎮 Поэкспериментируйте с редактором -3. 📚 **README.md** - полная документация -4. 🎯 **FEATURES.md** - подробности - -Для опытных: -1. 📚 **README.md** - вся информация сразу -2. 🎯 **FEATURES.md** - технические детали - -## 🎨 Типы ячеек - -| Код | Тип | Цвет | Маркер | -|-----|-----|------|--------| -| 0 | Дорога | Светло-серый | - | -| 1 | Камень | Тёмно-серый | - | -| 2 | Снег | Голубой | - | -| 3 | Лёд | Светло-голубой | - | -| 4 | Чекпоинт | Жёлтый | C | -| 5 | Старт | Зелёный | S | - -## 📦 Форматы файлов - -### Карта (map) -```json -{ - "map": [[0, 1, 2], [3, 4, 5]] -} -``` - -### Решение (solution) -```json -{ - "solution": [[1, 0], [0, 1], [-1, 0]] -} -``` - -## 🔗 Полезные ссылки - -- [Основной README проекта](../README.md) -- [Документация BFS алгоритма](../README.md) -- [Документация A* алгоритма](../ASTAR-README.md) - -## 💡 Быстрые команды - -```bash -# Открыть редактор -./open-editor.sh - -# Или напрямую в браузере -firefox index.html -chrome index.html -``` - ---- - -**Создано**: 2025-10-19 -**Версия**: 2.0 (с визуализацией решений) -**Технологии**: HTML5, CSS3, JavaScript ES6+, Canvas API - - diff --git a/map-editor/QUICKSTART.md b/map-editor/QUICKSTART.md deleted file mode 100644 index f76195a..0000000 --- a/map-editor/QUICKSTART.md +++ /dev/null @@ -1,71 +0,0 @@ -# 🚀 Быстрый старт - Редактор карт - -## За 2 минуты - -### 1️⃣ Открыть редактор -```bash -./open-editor.sh -``` -или просто откройте `index.html` в браузере - -### 2️⃣ Создать карту -1. Установите размеры (например, 15×15) -2. Выберите тип ячейки из палитры -3. Рисуйте мышью на карте -4. Обязательно добавьте точку **Старта** (зелёная, код 5) - -### 3️⃣ Сохранить карту -- Нажмите "📥 Экспорт JSON" -- Файл скачается автоматически - -### 4️⃣ Визуализировать решение (опционально) -1. Нажмите "📂 Загрузить решение" -2. Выберите файл с решением (например, `example-solution.json`) -3. Нажмите ▶ Play -4. Наслаждайтесь анимацией! - -## 🎨 Типы ячеек - -| Кнопка | Код | Описание | -|--------|-----|----------| -| Дорога | 0 | Обычная дорога (серая) | -| Камень | 1 | Препятствие (тёмно-серая) | -| Снег | 2 | Замедление (голубая) | -| Лёд | 3 | Скользко (светло-голубая) | -| Чекпоинт | 4 | Контрольная точка (жёлтая, "C") | -| **Старт** | **5** | **Точка старта (зелёная, "S")** | - -## 🎮 Управление визуализацией - -| Кнопка | Действие | -|--------|----------| -| ▶ Play | Автоматическое воспроизведение | -| ⏸ Pause | Пауза | -| ⏮ Reset | Сброс к началу | -| ⏭ Step | Один шаг вперёд | -| Slider | Скорость (1x - 10x) | - -## 📄 Формат решения - -```json -{ - "solution": [ - [1, 0], // вправо - [1, 1], // вправо-вниз - [0, 1], // вниз - [-1, 0], // торможение по X - [0, -1] // вверх - ] -} -``` - -## 💡 Советы -- Удерживайте мышь для быстрого рисования -- Используйте Step для детального анализа -- Начните с примеров: `demo-with-start.json` + `example-solution.json` - ---- - -📖 Полная документация: [README.md](README.md) - - diff --git a/map-editor/README.md b/map-editor/README.md index 467e6b6..bc98e89 100644 --- a/map-editor/README.md +++ b/map-editor/README.md @@ -1,64 +1,47 @@ # 🗺️ Редактор карт для "Гонки на бумаге" -Веб-приложение для создания и редактирования карт для игры "Гонки на бумаге". +Веб-приложение для создания и редактирования карт. Работает без сервера, просто откройте в браузере. + +> **Примечание:** Для визуализации решений используйте [Визуализатор решений](../solution-player/). ## 🚀 Быстрый старт -Просто откройте `index.html` в браузере. Никаких зависимостей или установки не требуется! - ```bash -# Из папки map-editor -firefox index.html +# Запуск редактора +./open-editor.sh # или -chrome index.html -# или просто откройте файл двойным кликом +firefox index.html ``` -## 📖 Инструкция +**Три простых шага:** +1. Установите размеры (5-100), выберите тип ячейки из палитры +2. Рисуйте мышью на карте (обязательно добавьте точку старта) +3. Нажмите "Экспорт JSON" - файл скачается автоматически -### Типы ячеек +## 🎨 Типы ячеек -| Тип | Код | Описание | Цвет | -|-----|-----|----------|------| -| Дорога | 0 | Обычная дорога | Светло-серый | -| Камень | 1 | Препятствие (непроходимо) | Тёмно-серый | -| Снег | 2 | Замедление движения | Голубой | -| Лёд | 3 | Скользкая поверхность | Светло-голубой | -| Чекпоинт | 4 | Контрольная точка | Жёлтый с "C" | -| Старт | 5 | Точка старта | Зелёный с "S" | +| Код | Тип | Описание | Цвет | Маркер | +|-----|-----|----------|------|--------| +| 0 | Дорога | Обычная дорога | Светло-серый | - | +| 1 | Камень | Препятствие (непроходимо) | Тёмно-серый | - | +| 2 | Снег | Замедление движения | Голубой | - | +| 3 | Лёд | Скользкая поверхность | Светло-голубой | - | +| 4 | Чекпоинт | Контрольная точка | Жёлтый | C | +| 5 | Старт | Точка старта | Зелёный | S | -### Использование +## 📖 Использование -1. **Изменение размеров:** - - Укажите ширину и высоту карты (5-100) - - Нажмите "Применить" - - Существующие данные сохранятся при изменении размера +**Рисование:** +- Выберите тип ячейки из палитры +- Кликните на ячейку или удерживайте мышь для быстрого рисования -2. **Рисование:** - - Выберите тип ячейки из палитры - - Кликните на ячейку для изменения типа - - Удерживайте кнопку мыши для рисования +**Экспорт/Импорт:** +- Экспорт: нажмите "Экспорт JSON" (также выводится в консоль F12) +- Импорт: нажмите "Импорт JSON" и выберите файл -3. **Экспорт:** - - Нажмите "Экспорт JSON" - - Файл автоматически скачается - - JSON также выводится в консоль браузера (F12) - -4. **Импорт:** - - Нажмите "Импорт JSON" - - Выберите JSON файл - - Карта загрузится автоматически - -5. **Визуализация решения:** - - Убедитесь, что на карте есть точка старта (тип 5) - - Нажмите "Загрузить решение" - - Выберите JSON файл с решением - - Используйте кнопки управления: - - ▶ Play - автоматическое воспроизведение - - ⏸ Pause - пауза - - ⏮ Reset - сброс к началу - - ⏭ Step - шаг вперед - - Регулируйте скорость воспроизведения (1x - 10x) +**Изменение размеров:** +- Укажите ширину и высоту (5-100), нажмите "Применить" +- Существующие данные сохранятся при изменении размера ## 📄 Формат JSON @@ -74,106 +57,13 @@ chrome index.html } ``` -### Структура карты: +**Структура:** - `map` - двумерный массив целых чисел (int[][]) -- Каждая строка массива = строка карты -- Каждый элемент = тип ячейки (0-5) - Первая строка = верхняя строка карты - Первый элемент в строке = левая ячейка +- Значения: 0-5 (типы ячеек) -## 🎬 Формат решения - -Решение представляет собой JSON файл с массивом векторов ускорений: - -```json -{ - "solution": [ - [1, 1], - [1, 0], - [0, 1], - [-1, 0], - [0, -1] - ] -} -``` - -### Структура решения: -- `solution` - массив векторов ускорения [[ax, ay], ...] -- Каждый элемент - вектор ускорения [ax, ay] -- ax, ay - целые числа (обычно от -1 до 1) -- Применяются последовательно от точки старта - -### Физика движения: -1. Начальная позиция - ячейка с типом 5 (старт) -2. Начальная скорость = (0, 0) -3. На каждом шаге: - - velocity += acceleration - - position += velocity - -### Пример: -``` -Шаг 0: pos=(0,0), vel=(0,0), acc=(0,0) -Шаг 1: acc=(1,1) → vel=(1,1) → pos=(1,1) -Шаг 2: acc=(1,0) → vel=(2,1) → pos=(3,2) -Шаг 3: acc=(0,1) → vel=(2,2) → pos=(5,4) -``` - -### Визуализация показывает: -- 🔵 Синяя линия - пройденная траектория -- 🔴 Красный круг - текущая позиция -- ➡️ Красная стрелка - направление и скорость движения -- Информацию о шаге, позиции, скорости и ускорении - -## 🎨 Особенности - -### Редактор карт: -- ✅ Современный красивый интерфейс -- ✅ Интуитивное рисование мышью -- ✅ Валидация данных при импорте -- ✅ Автоматическое сохранение с именем по размеру -- ✅ Вывод JSON в консоль для быстрого копирования -- ✅ Адаптивный дизайн -- ✅ Сохранение данных при изменении размера -- ✅ 6 типов ячеек (дорога, камень, снег, лёд, чекпоинт, старт) - -### Визуализация решений: -- ✅ Пошаговое воспроизведение траектории -- ✅ Регулируемая скорость воспроизведения (1x - 10x) -- ✅ Отображение вектора скорости в реальном времени -- ✅ Информация о позиции, скорости и ускорении на каждом шаге -- ✅ Плавная анимация движения -- ✅ Визуализация пройденного пути -- ✅ Управление: Play, Pause, Reset, Step - -### Технологии: -- ✅ Работает без сервера (pure HTML/JS/CSS) -- ✅ HTML5 Canvas для отрисовки -- ✅ Vanilla JavaScript (ES6+) -- ✅ FileReader API для импорта -- ✅ Blob API для экспорта - -## 💡 Советы - -### Работа с картами: -- Используйте консоль браузера (F12) для просмотра экспортированного JSON -- При импорте проверяются все значения ячеек (только 0-5) -- Все строки карты должны иметь одинаковую длину -- Максимальный размер карты: 100×100 -- Минимальный размер карты: 5×5 -- Рекомендуется устанавливать одну точку старта (5) на карте - -### Работа с визуализацией: -- Сначала создайте или загрузите карту с точкой старта -- Решение загружайте из отдельного JSON файла -- Используйте Step для детального анализа каждого шага -- Регулируйте скорость для комфортного просмотра -- Красная стрелка показывает направление и скорость движения -- Точка старта должна совпадать с началом траектории решения -- Примеры решений: `example-solution.json`, `complex-solution.json` - -## 🔗 Интеграция с игрой - -Экспортированный JSON можно использовать в C# коде: +## 🔗 Интеграция с C# ```csharp using System.Text.Json; @@ -182,79 +72,34 @@ var json = File.ReadAllText("racing-map-15x15.json"); var mapData = JsonSerializer.Deserialize(json); int[][] map = mapData.map; -// Использование карты -for (int y = 0; y < map.Length; y++) -{ - for (int x = 0; x < map[y].Length; x++) - { - int cellType = map[y][x]; - // обработка... - } -} - public class MapData { public int[][] map { get; set; } } ``` -## 🛠️ Технологии +## 🎯 Файлы проекта -- HTML5 Canvas -- Vanilla JavaScript (ES6+) -- CSS3 с градиентами и анимациями -- FileReader API для импорта -- Blob API для экспорта +| Файл | Описание | +|------|----------| +| `index.html` | Главная страница редактора | +| `editor.js` | Логика редактора и визуализация | +| `open-editor.sh` | Скрипт для быстрого запуска | +| `demo-with-start.json` | Демонстрационная карта 15×15 | -## 📝 Файлы примеров +## 🎨 Особенности -### Карты: -- `simple-track.json` - Простая трасса 10×10 -- `demo-with-start.json` - Демонстрационная карта 15×15 -- `example-maps.json` - Коллекция различных карт +- ✅ Современный интерфейс, работает без сервера +- ✅ Интуитивное рисование мышью +- ✅ Валидация данных при импорте +- ✅ Автоматическое сохранение с именем по размеру +- ✅ HTML5 Canvas, Vanilla JavaScript (ES6+) -### Решения: -- `example-solution.json` - Простое решение на 10 шагов -- `complex-solution.json` - Сложное решение на 20 шагов +## 🔗 Связанные проекты -## 🎯 Быстрый старт с визуализацией - -1. Откройте редактор: - ```bash - ./open-editor.sh - # или просто откройте index.html в браузере - ``` - -2. Загрузите карту с примером: - - Нажмите "Импорт JSON" - - Выберите `demo-with-start.json` - -3. Загрузите решение: - - Нажмите "Загрузить решение" - - Выберите `example-solution.json` - -4. Запустите визуализацию: - - Нажмите ▶ Play - - Наблюдайте за движением - - Экспериментируйте со скоростью и пошаговым режимом - -## 🔧 Создание собственного решения - -Пример структуры файла решения: - -```json -{ - "solution": [ - [1, 0], // ускорение вправо - [1, 0], // еще ускорение вправо (скорость нарастает) - [0, 1], // ускорение вниз - [-1, 0], // торможение по X - [0, 0] // без ускорения (движение по инерции) - ] -} -``` +- [Визуализатор решений](../solution-player/) - анимация решений +- [Основной проект](../README.md) - C# решатель на основе A* --- -Создано для проекта [Racing](../README.md) - +**Технологии:** HTML5, CSS3, JavaScript ES6+, Canvas API diff --git a/map-editor/complex-solution.json b/map-editor/complex-solution.json deleted file mode 100644 index 34f7dd2..0000000 --- a/map-editor/complex-solution.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "solution": [ - [1, 0], - [1, 0], - [1, 1], - [0, 1], - [0, 1], - [-1, 0], - [-1, 0], - [0, -1], - [0, -1], - [1, 0], - [1, 0], - [0, 1], - [0, 0], - [-1, 0], - [0, 1], - [1, 0], - [0, -1], - [0, 0], - [0, 0], - [-1, -1] - ] -} - - diff --git a/map-editor/demo-solution.json b/map-editor/demo-solution.json deleted file mode 100644 index 5a8db42..0000000 --- a/map-editor/demo-solution.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "solution": [ - [1, 0], - [1, 1], - [1, 1], - [0, 1], - [0, 0], - [0, 0], - [-1, 0], - [-1, 0], - [0, 1], - [1, 0], - [1, 0], - [0, -1], - [0, 0], - [0, 1], - [0, 1] - ] -} - - diff --git a/map-editor/editor.js b/map-editor/editor.js index d69f875..741331b 100644 --- a/map-editor/editor.js +++ b/map-editor/editor.js @@ -17,15 +17,6 @@ let map = []; let selectedType = 0; let isDrawing = false; -// Состояние визуализации -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'); @@ -99,98 +90,6 @@ function drawMap() { } } } - - // Рисуем визуализацию траектории, если она есть - 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(); - } - } } // Выбор типа ячейки @@ -387,187 +286,5 @@ document.getElementById('height').addEventListener('keypress', (e) => { if (e.key === 'Enter') resizeMap(); }); -// ============================================ -// Функции визуализации решения -// ============================================ - -// Поиск стартовой позиции на карте -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 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('visualizationPanel').classList.remove('hidden'); - document.getElementById('clearVisBtn').disabled = false; - - // Обновляем информацию - updateStepInfo(); - drawMap(); - - 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 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('visualizationPanel').classList.add('hidden'); - document.getElementById('clearVisBtn').disabled = true; - - drawMap(); -} - // Инициализация при загрузке страницы init(); - diff --git a/map-editor/example-maps.json b/map-editor/example-maps.json deleted file mode 100644 index b661c99..0000000 --- a/map-editor/example-maps.json +++ /dev/null @@ -1,95 +0,0 @@ -{ - "examples": [ - { - "name": "Simple Track", - "description": "Простая карта 10x10 с точкой старта и чекпоинтом", - "map": [ - [5, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 0, 0, 1, 1, 1, 0], - [0, 1, 0, 0, 0, 0, 0, 0, 1, 0], - [0, 1, 0, 2, 2, 2, 2, 0, 1, 0], - [0, 1, 0, 2, 4, 4, 2, 0, 1, 0], - [0, 1, 0, 2, 2, 2, 2, 0, 1, 0], - [0, 1, 0, 0, 0, 0, 0, 0, 1, 0], - [0, 1, 1, 1, 0, 0, 1, 1, 1, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - ] - }, - { - "name": "Ice Circuit", - "description": "Карта с ледяными участками и точкой старта", - "map": [ - [5, 0, 0, 1, 1, 1, 1, 0, 0, 0], - [0, 3, 3, 3, 0, 0, 1, 0, 4, 0], - [0, 3, 1, 3, 0, 0, 1, 0, 0, 0], - [0, 3, 3, 3, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 2, 2, 2, 0, 0], - [0, 0, 2, 2, 0, 2, 1, 2, 0, 0], - [0, 0, 2, 1, 0, 2, 2, 2, 0, 0], - [0, 0, 2, 2, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 3, 3, 0], - [0, 4, 0, 0, 1, 1, 0, 3, 3, 0] - ] - }, - { - "name": "Obstacle Course", - "description": "Сложная карта с множеством препятствий и точкой старта", - "map": [ - [5, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 4, 0, 0, 1, 0, 2, 2, 2, 0, 1, 1, 1, 0, 0], - [0, 0, 0, 0, 1, 0, 2, 1, 2, 0, 0, 0, 0, 0, 0], - [1, 1, 1, 0, 0, 0, 2, 2, 2, 0, 0, 3, 3, 3, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 1, 3, 0], - [0, 3, 3, 3, 0, 1, 1, 0, 1, 1, 0, 3, 3, 3, 0], - [0, 3, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 3, 3, 3, 0, 0, 4, 0, 0, 0, 2, 2, 2, 0, 0], - [0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 2, 1, 2, 0, 0], - [0, 2, 2, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0], - [0, 2, 2, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 4], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 0, 0, 3, 3, 3, 3, 0, 0, 1, 1, 1, 0, 0], - [0, 1, 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] - ] - }, - { - "name": "Minimal", - "description": "Минимальная карта 5x5 для тестирования с точкой старта", - "map": [ - [5, 0, 0, 0, 4], - [0, 1, 1, 0, 0], - [0, 1, 1, 0, 0], - [0, 0, 0, 0, 0], - [4, 0, 0, 0, 0] - ] - }, - { - "name": "Empty Large", - "description": "Пустая карта 20x20 для создания своей карты", - "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, 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, 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, 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] - ] - } - ] -} - diff --git a/map-editor/example-solution.json b/map-editor/example-solution.json deleted file mode 100644 index b3b55ea..0000000 --- a/map-editor/example-solution.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "solution": [ - [1, 1], - [1, 0], - [1, 0], - [0, 1], - [0, 1], - [-1, 0], - [0, -1], - [0, 0], - [1, 0], - [0, 1] - ] -} - - diff --git a/map-editor/index.html b/map-editor/index.html index f8043a4..00903ff 100644 --- a/map-editor/index.html +++ b/map-editor/index.html @@ -26,7 +26,6 @@ border-radius: 20px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); padding: 30px; - max-width: 1200px; width: 100%; } @@ -244,111 +243,6 @@ border-radius: 4px; border: 2px solid #dee2e6; } - - .visualization-panel { - margin-top: 20px; - padding: 20px; - background: #f8f9fa; - border-radius: 12px; - border: 2px solid #dee2e6; - } - - .visualization-panel h3 { - margin-bottom: 15px; - color: #495057; - } - - .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; - } - - .trajectory-path { - stroke: #667eea; - stroke-width: 3; - fill: none; - opacity: 0.6; - } - - .current-position { - fill: #f5576c; - stroke: white; - stroke-width: 2; - } - - .start-marker { - fill: #28a745; - stroke: white; - stroke-width: 2; - } - - .hidden { - display: none; - } @@ -413,63 +307,18 @@ - -
-

🎬 Визуализация решения

-
- - -
- -
- -
💡 Подсказки: • Кликайте по ячейкам для изменения типа
• Удерживайте кнопку мыши для рисования
• Экспортируйте карту в JSON для использования в игре
- • Загрузите решение для визуализации прохождения карты + • Для визуализации решений используйте отдельный Визуализатор решений
diff --git a/map-editor/simple-track.json b/map-editor/simple-track.json deleted file mode 100644 index 91647f5..0000000 --- a/map-editor/simple-track.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "map": [ - [5, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 1, 1, 1, 0, 0, 1, 1, 1, 0], - [0, 1, 0, 0, 0, 0, 0, 0, 1, 0], - [0, 1, 0, 2, 2, 2, 2, 0, 1, 0], - [0, 1, 0, 2, 4, 4, 2, 0, 1, 0], - [0, 1, 0, 2, 2, 2, 2, 0, 1, 0], - [0, 1, 0, 0, 0, 0, 0, 0, 1, 0], - [0, 1, 1, 1, 0, 0, 1, 1, 1, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0] - ] -} - diff --git a/maps/racing-map-15x15.json b/maps/racing-map-15x15.json new file mode 100644 index 0000000..7f6be53 --- /dev/null +++ b/maps/racing-map-15x15.json @@ -0,0 +1,259 @@ +{ + "map": [ + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 4 + ], + [ + 0, + 5, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 4 + ], + [ + 0, + 0, + 0, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 3, + 3, + 0, + 0, + 4 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 3, + 0, + 4 + ], + [ + 0, + 0, + 0, + 2, + 2, + 0, + 1, + 0, + 0, + 0, + 0, + 3, + 0, + 0, + 4 + ], + [ + 0, + 0, + 0, + 0, + 2, + 0, + 0, + 0, + 1, + 1, + 3, + 3, + 0, + 0, + 4 + ], + [ + 0, + 0, + 0, + 0, + 2, + 0, + 0, + 0, + 0, + 1, + 0, + 3, + 3, + 0, + 4 + ], + [ + 0, + 0, + 0, + 0, + 2, + 2, + 0, + 0, + 0, + 1, + 1, + 4, + 3, + 0, + 4 + ], + [ + 0, + 0, + 0, + 0, + 0, + 2, + 3, + 0, + 0, + 0, + 1, + 3, + 3, + 0, + 4 + ], + [ + 0, + 0, + 0, + 0, + 0, + 2, + 0, + 3, + 0, + 1, + 1, + 3, + 1, + 0, + 4 + ], + [ + 0, + 0, + 0, + 0, + 0, + 2, + 0, + 3, + 3, + 0, + 4, + 3, + 0, + 0, + 4 + ], + [ + 0, + 0, + 0, + 0, + 0, + 2, + 2, + 0, + 3, + 3, + 3, + 3, + 0, + 0, + 4 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 2, + 2, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + [ + 4, + 4, + 4, + 4, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ] + ] +} \ No newline at end of file diff --git a/racing-tools/README.md b/racing-tools/README.md new file mode 100644 index 0000000..9d30d01 --- /dev/null +++ b/racing-tools/README.md @@ -0,0 +1,294 @@ +# 🏁 Racing Tools - Инструменты для "Гонки на бумаге" + +Веб-приложения для создания карт и визуализации решений. Работают без сервера, просто откройте в браузере. + +## 📦 Что внутри + +Два веб-приложения в одном проекте: + +- **🏁 Редактор карт** (`editor.html`) - создание и редактирование игровых карт +- **🎬 Визуализатор решений** (`player.html`) - анимация траекторий движения + +## 🚀 Быстрый старт + +### Вариант 1: Через скрипты +```bash +# Редактор карт +./open-editor.sh + +# Визуализатор решений +./open-player.sh +``` + +### Вариант 2: Напрямую в браузере +```bash +firefox editor.html # Редактор карт +firefox player.html # Визуализатор решений +``` + +### Вариант 3: Через Python HTTP сервер +```bash +python3 -m http.server 8000 +# Откройте http://localhost:8000/editor.html или http://localhost:8000/player.html +``` + +## 🏁 Редактор карт + +### Возможности +- Создание карт размером от 5×5 до 100×100 +- Интуитивное рисование мышью (клик или удержание) +- 6 типов ячеек: дорога, камень, снег, лёд, чекпоинт, старт +- Экспорт/импорт в JSON формате +- Изменение размера с сохранением данных + +### Типы ячеек + +| Код | Тип | Описание | Цвет | Маркер | +|-----|-----|----------|------|--------| +| 0 | Дорога | Обычная дорога | Светло-серый | - | +| 1 | Камень | Препятствие (непроходимо) | Тёмно-серый | - | +| 2 | Снег | Замедление движения | Голубой | - | +| 3 | Лёд | Скользкая поверхность | Светло-голубой | - | +| 4 | Чекпоинт | Контрольная точка | Жёлтый | C | +| 5 | Старт | Точка старта (обязательно!) | Зелёный | S | + +### Быстрый старт редактора + +1. Откройте `editor.html` +2. Установите размеры (по умолчанию 15×15) +3. Выберите тип ячейки из палитры +4. Рисуйте мышью на карте (обязательно добавьте точку старта - тип 5) +5. Нажмите "Экспорт JSON" - файл скачается автоматически + +## 🎬 Визуализатор решений + +### Возможности +- Загрузка карты и решения из JSON +- Пошаговая анимация траектории +- Контроль скорости воспроизведения (1x - 10x) +- Ручное управление (шаг вперед/назад) +- Отображение позиции, скорости и ускорения + +### Элементы визуализации + +- 🔵 **Синяя линия и точки** - пройденная траектория +- 🔴 **Красный круг** - текущая позиция +- ➡️ **Красная стрелка** - вектор скорости (направление и величина) +- **Панель информации** - шаг, позиция (x, y), скорость (vx, vy), ускорение (ax, ay) + +### Управление + +| Кнопка | Действие | +|--------|----------| +| ▶ Play | Автоматическое воспроизведение | +| ⏸ Pause | Пауза | +| ⏮ Reset | Сброс к началу | +| ⏪ Back | Шаг назад | +| ⏩ Forward | Шаг вперед | +| Слайдер | Скорость 1x - 10x | + +### Быстрый старт визуализатора + +1. Откройте `player.html` +2. Загрузите карту (📂 Загрузить карту) +3. Загрузите решение (🎬 Загрузить решение) +4. Нажмите ▶ Play и наблюдайте за движением + +## 📄 Форматы файлов + +### Формат карты (map) + +```json +{ + "map": [ + [5, 0, 0, 1, 0], + [0, 1, 0, 1, 0], + [0, 0, 2, 2, 4] + ] +} +``` + +**Структура:** +- `map` - двумерный массив целых чисел (int[][]) +- Первая строка = верхняя строка карты +- Первый элемент в строке = левая ячейка +- Значения: 0-5 (типы ячеек) +- **Обязательно наличие точки старта (5)** + +### Формат решения (solution) + +```json +{ + "solution": [ + [1, 1], + [1, 0], + [0, 1], + [-1, 0] + ] +} +``` + +**Структура:** +- `solution` - массив векторов ускорения `[[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) +Шаг 3: acc=(0,0) → vel=(2,1) → pos=(5,3) +``` + +## 🔗 Интеграция с C# + +### Чтение карты +```csharp +using System.Text.Json; + +var json = File.ReadAllText("racing-map-15x15.json"); +var mapData = JsonSerializer.Deserialize(json); +int[][] map = mapData.map; + +public class MapData +{ + public int[][] map { get; set; } +} +``` + +### Запись решения +```csharp +var solution = new { solution = new int[][] { + new[] { 1, 1 }, + new[] { 1, 0 }, + new[] { 0, 1 } +}}; + +var json = JsonSerializer.Serialize(solution, new JsonSerializerOptions +{ + WriteIndented = true +}); + +File.WriteAllText("solution.json", json); +``` + +## 🎯 Структура проекта + +``` +racing-tools/ +├── editor.html # Страница редактора карт +├── player.html # Страница визуализатора решений +├── styles.css # Общие стили для обоих приложений +├── common.js # Общие функции и константы +├── editor.js # Логика редактора +├── player.js # Логика визуализатора +├── open-editor.sh # Скрипт запуска редактора +├── open-player.sh # Скрипт запуска визуализатора +├── demo-with-start.json # Демо-карта 15×15 +├── demo-with-start-solution.json # Демо-решение +└── README.md # Этот файл +``` + +## 🎨 Технические детали + +### Технологии +- **HTML5** - структура страниц +- **CSS3** - современный градиентный дизайн +- **JavaScript (ES6+)** - вся логика на клиенте +- **Canvas API** - отрисовка карт и визуализация + +### Особенности +- ✅ Работает без сервера (статические файлы) +- ✅ Валидация данных при импорте +- ✅ Адаптивная сетка интерфейса +- ✅ Общий CSS и JS для уменьшения дублирования +- ✅ Модульная архитектура (common.js для общего кода) +- ✅ Навигация между страницами +- ✅ Визуальная обратная связь (анимации, подсветка) + +### Зависимости +- Нет внешних зависимостей +- Чистый Vanilla JavaScript +- Работает в любом современном браузере + +## 🧪 Быстрый тест + +### Тест редактора +1. Откройте `editor.html` +2. Нарисуйте простую карту с точкой старта +3. Экспортируйте в JSON +4. Импортируйте обратно - данные должны сохраниться + +### Тест визуализатора +1. Откройте `player.html` +2. Загрузите `demo-with-start.json` +3. Загрузите `demo-with-start-solution.json` +4. Нажмите ▶ Play и экспериментируйте с кнопками и скоростью + +## 📚 Примеры использования + +### Создание карты с чекпоинтами +1. Создайте дорогу (тип 0) в виде трассы +2. Добавьте препятствия (тип 1) по краям +3. Разместите чекпоинты (тип 4) вдоль трассы +4. Обозначьте старт (тип 5) в начале +5. Экспортируйте и используйте в решателе + +### Визуализация решения A* +1. Запустите C# решатель, получите JSON с решением +2. Откройте визуализатор +3. Загрузите карту и решение +4. Наблюдайте за оптимальным путем + +## 🔗 Связанные проекты + +- [Основной проект C#](../README.md) - решатель на основе A* +- [Примеры карт](../maps/) - коллекция готовых карт +- [Документация формата](../MAP-FORMAT.md) - подробное описание формата + +## 🤝 Советы и трюки + +### Редактор +- Используйте Enter в полях размера для быстрого применения +- Удерживайте мышь для быстрого рисования линий +- JSON также выводится в консоль (F12) для быстрого копирования +- При изменении размера существующие данные сохраняются + +### Визуализатор +- Используйте пошаговый режим для детального анализа +- Скорость 1x подходит для медленного разбора +- Скорость 10x для быстрого просмотра длинных решений +- Можно загрузить новое решение без перезагрузки карты + +## 🐛 Решение проблем + +**Карта не загружается?** +- Проверьте формат JSON +- Убедитесь, что все значения от 0 до 5 +- Размеры должны быть от 5×5 до 100×100 + +**Решение не работает?** +- Убедитесь, что на карте есть точка старта (5) +- Проверьте формат: массив массивов [[ax, ay], ...] +- Сначала загрузите карту, потом решение + +**Визуализация странная?** +- Возможно, решение выходит за границы карты +- Проверьте корректность векторов ускорений + +--- + +**Версия:** 2.0 (Объединенная) +**Дата:** 2025 +**Лицензия:** MIT +**Автор:** Racing Team + diff --git a/racing-tools/common.js b/racing-tools/common.js new file mode 100644 index 0000000..3c6fe99 --- /dev/null +++ b/racing-tools/common.js @@ -0,0 +1,111 @@ +// Общие константы для редактора и плеера +const CELL_SIZE = 30; +const GRID_COLOR = '#dee2e6'; +const COLORS = { + 0: '#f8f9fa', // Дорога + 1: '#6c757d', // Камень + 2: '#e3f2fd', // Снег + 3: '#b3e5fc', // Лёд + 4: '#fff3cd', // Чекпоинт + 5: '#d4edda' // Старт +}; + +// Общие функции для работы с canvas +function drawGrid(ctx, width, height) { + 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(); + } +} + +function drawCells(ctx, map, width, 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); + } + } +} + +function drawMarkers(ctx, map, width, height) { + // Рисуем маркеры для чекпоинтов и старта + 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 validateMap(data) { + 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}]`); + } + } + } + + return { map: newMap, width: newWidth, height: newHeight }; +} + +function findStartPosition(map, width, height) { + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + if (map[y][x] === 5) { + return { x, y }; + } + } + } + return null; +} + diff --git a/map-editor/my-solution.json b/racing-tools/demo-with-start-solution.json similarity index 66% rename from map-editor/my-solution.json rename to racing-tools/demo-with-start-solution.json index 7b2ed46..404ef99 100644 --- a/map-editor/my-solution.json +++ b/racing-tools/demo-with-start-solution.json @@ -5,28 +5,28 @@ 0 ], [ - -1, + 2, + 2 + ], + [ + 2, + -1 + ], + [ + -2, + -1 + ], + [ + 0, 2 ], [ 0, - 0 + 2 ], [ - -2, - -2 - ], - [ - 1, - 0 - ], - [ - -1, - 0 - ], - [ - 1, - 0 + 0, + -1 ] ] } \ No newline at end of file diff --git a/racing-tools/demo-with-start.json b/racing-tools/demo-with-start.json new file mode 100644 index 0000000..fd7f4ad --- /dev/null +++ b/racing-tools/demo-with-start.json @@ -0,0 +1,21 @@ +{ + "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] + ] +} + + diff --git a/racing-tools/editor.html b/racing-tools/editor.html new file mode 100644 index 0000000..bc7ec66 --- /dev/null +++ b/racing-tools/editor.html @@ -0,0 +1,125 @@ + + + + + + Редактор карт - Гонки на бумаге + + + +
+

🏁 Редактор карт

+

Гонки на бумаге / Paper Racing

+ + + +
+
+

📐 Размеры карты

+
+
+ + +
+
+ + +
+
+
+ + +
+
+ +
+

🎨 Тип ячейки

+
+
+ Дорога + (0) +
+
+ Камень + (1) +
+
+ Снег + (2) +
+
+ Лёд + (3) +
+
+ Чекпоинт + (4) +
+
+ Старт + (5) +
+
+
+ +
+

💾 Импорт / Экспорт

+
+ + +
+ +
+
+ +
+ +
+ +
+ 💡 Подсказки: + • Кликайте по ячейкам для изменения типа
+ • Удерживайте кнопку мыши для рисования
+ • Экспортируйте карту в JSON для использования в игре
+ • Для визуализации решений используйте Визуализатор решений +
+ +
+

📖 Легенда цветов

+
+
+
+ Дорога (0) +
+
+
+ Камень (1) +
+
+
+ Снег (2) +
+
+
+ Лёд (3) +
+
+
+ Чекпоинт (4) +
+
+
+ Старт (5) +
+
+
+
+ + + + + + diff --git a/racing-tools/editor.js b/racing-tools/editor.js new file mode 100644 index 0000000..bd6d873 --- /dev/null +++ b/racing-tools/editor.js @@ -0,0 +1,253 @@ +// Состояние редактора +let width = 15; +let height = 15; +let map = []; +let selectedType = 0; +let isDrawing = false; + +let scale = 1; +let offsetX = 0; +let offsetY = 0; +let isPanning = false; +let startPanX = 0; +let startPanY = 0; + +// 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)); +} + +function resizeCanvas() { + const wrapper = canvas.parentElement; + canvas.width = Math.max(1000, wrapper.clientWidth || 1000); + canvas.height = Math.max(1000, wrapper.clientHeight || 1000); +} + +// Отрисовка карты +function drawMap() { + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.translate(offsetX, offsetY); + ctx.scale(scale, scale); + drawCells(ctx, map, width, height); + drawGrid(ctx, width, height); + drawMarkers(ctx, map, width, height); +} + +// Выбор типа ячейки +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 canvasX = (event.clientX - rect.left - offsetX) / scale; + const canvasY = (event.clientY - rect.top - offsetY) / scale; + const x = Math.floor(canvasX / CELL_SIZE); + const y = Math.floor(canvasY / 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) => { + if (e.button === 2 || e.shiftKey) { + isPanning = true; + startPanX = e.clientX - offsetX; + startPanY = e.clientY - offsetY; + canvas.style.cursor = 'grabbing'; + } else { + isDrawing = true; + const coords = getCellCoords(e); + if (coords) { + setCellType(coords.x, coords.y); + } + } +}); + +canvas.addEventListener('mousemove', (e) => { + if (isPanning) { + offsetX = e.clientX - startPanX; + offsetY = e.clientY - startPanY; + drawMap(); + } else if (isDrawing) { + const coords = getCellCoords(e); + if (coords) { + setCellType(coords.x, coords.y); + } + } +}); + +canvas.addEventListener('mouseup', () => { + isDrawing = false; + isPanning = false; + canvas.style.cursor = 'default'; +}); + +canvas.addEventListener('mouseleave', () => { + isDrawing = false; + isPanning = false; + canvas.style.cursor = 'default'; +}); + +canvas.addEventListener('wheel', (e) => { + e.preventDefault(); + const rect = canvas.getBoundingClientRect(); + const mouseX = e.clientX - rect.left; + const mouseY = e.clientY - rect.top; + const zoom = e.deltaY < 0 ? 1.1 : 0.9; + const newScale = Math.min(Math.max(0.1, scale * zoom), 5); + + offsetX = mouseX - (mouseX - offsetX) * (newScale / scale); + offsetY = mouseY - (mouseY - offsetY) * (newScale / scale); + scale = newScale; + + drawMap(); +}); + +canvas.addEventListener('contextmenu', (e) => e.preventDefault()); + +// Изменение размера карты +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); + const validated = validateMap(data); + + // Импортируем карту + height = validated.height; + width = validated.width; + map = validated.map; + + // Обновляем 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(); +}); + +window.addEventListener('resize', () => { + resizeCanvas(); + drawMap(); +}); + +init(); + diff --git a/racing-tools/open-editor.sh b/racing-tools/open-editor.sh new file mode 100755 index 0000000..1be44a9 --- /dev/null +++ b/racing-tools/open-editor.sh @@ -0,0 +1,43 @@ +#!/bin/bash + +# Скрипт для запуска редактора карт в браузере +# Автоматически определяет доступный браузер и открывает страницу + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +HTML_FILE="$SCRIPT_DIR/editor.html" + +echo "🏁 Запуск редактора карт..." +echo "📁 Путь: $HTML_FILE" + +# Проверяем наличие файла +if [ ! -f "$HTML_FILE" ]; then + echo "❌ Ошибка: Файл $HTML_FILE не найден" + exit 1 +fi + +# Функция для открытия в браузере +open_browser() { + if command -v xdg-open &> /dev/null; then + xdg-open "$HTML_FILE" + elif command -v firefox &> /dev/null; then + firefox "$HTML_FILE" + elif command -v google-chrome &> /dev/null; then + google-chrome "$HTML_FILE" + elif command -v chromium &> /dev/null; then + chromium "$HTML_FILE" + else + echo "❌ Браузер не найден. Откройте вручную: $HTML_FILE" + exit 1 + fi +} + +# Открываем браузер +open_browser + +echo "✅ Редактор открыт в браузере" +echo "" +echo "💡 Подсказки:" +echo " • Нарисуйте карту, обязательно добавьте точку старта (тип 5)" +echo " • Экспортируйте в JSON для использования в решателе" +echo " • Для визуализации решений откройте ./open-player.sh" + diff --git a/racing-tools/open-player.sh b/racing-tools/open-player.sh new file mode 100755 index 0000000..c2429c1 --- /dev/null +++ b/racing-tools/open-player.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# Скрипт для запуска визуализатора решений в браузере +# Автоматически определяет доступный браузер и открывает страницу + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +HTML_FILE="$SCRIPT_DIR/player.html" + +echo "🎬 Запуск визуализатора решений..." +echo "📁 Путь: $HTML_FILE" + +# Проверяем наличие файла +if [ ! -f "$HTML_FILE" ]; then + echo "❌ Ошибка: Файл $HTML_FILE не найден" + exit 1 +fi + +# Функция для открытия в браузере +open_browser() { + if command -v xdg-open &> /dev/null; then + xdg-open "$HTML_FILE" + elif command -v firefox &> /dev/null; then + firefox "$HTML_FILE" + elif command -v google-chrome &> /dev/null; then + google-chrome "$HTML_FILE" + elif command -v chromium &> /dev/null; then + chromium "$HTML_FILE" + else + echo "❌ Браузер не найден. Откройте вручную: $HTML_FILE" + exit 1 + fi +} + +# Открываем браузер +open_browser + +echo "✅ Визуализатор открыт в браузере" +echo "" +echo "💡 Подсказки:" +echo " • Сначала загрузите карту (📂 Загрузить карту)" +echo " • Затем загрузите решение (🎬 Загрузить решение)" +echo " • Используйте кнопки управления для анимации" +echo " • Демо-файлы: demo-with-start.json и demo-with-start-solution.json" + diff --git a/racing-tools/player.html b/racing-tools/player.html new file mode 100644 index 0000000..45b991b --- /dev/null +++ b/racing-tools/player.html @@ -0,0 +1,117 @@ + + + + + + Визуализатор решений - Гонки на бумаге + + + +
+

🎬 Визуализатор решений

+

Гонки на бумаге / Paper Racing

+ + + +
+
+

📂 Загрузка файлов

+
+ + +
+ + +
+
+ +
+ +
+ + + + + +
+ 💡 Инструкция: + • Сначала загрузите карту с точкой старта (тип 5)
+ • Затем загрузите файл решения с векторами ускорений
+ • Используйте кнопки управления для просмотра анимации
+ • 🔵 Синяя линия - пройденная траектория
+ • 🔴 Красный круг - текущая позиция
+ • ➡️ Красная стрелка - направление и скорость движения +
+ +
+

📖 Легенда цветов

+
+
+
+ Дорога (0) +
+
+
+ Камень (1) +
+
+
+ Снег (2) +
+
+
+ Лёд (3) +
+
+
+ Чекпоинт (4) +
+
+
+ Старт (5) +
+
+
+
+ + + + + + diff --git a/racing-tools/player.js b/racing-tools/player.js new file mode 100644 index 0000000..99cb9ac --- /dev/null +++ b/racing-tools/player.js @@ -0,0 +1,413 @@ +// Состояние плеера +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; + +let scale = 1; +let offsetX = 0; +let offsetY = 0; +let isPanning = false; +let startPanX = 0; +let startPanY = 0; + +// 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)); +} + +function resizeCanvas() { + const wrapper = canvas.parentElement; + canvas.width = Math.max(1000, wrapper.clientWidth || 1000); + canvas.height = Math.max(1000, wrapper.clientHeight || 1000); +} + +// Отрисовка карты +function drawMap() { + ctx.setTransform(1, 0, 0, 1, 0, 0); + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.translate(offsetX, offsetY); + ctx.scale(scale, scale); + drawCells(ctx, map, width, height); + drawGrid(ctx, width, height); + drawMarkers(ctx, map, width, height); + + 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 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); + const validated = validateMap(data); + + // Импортируем карту + height = validated.height; + width = validated.width; + map = validated.map; + + // Очищаем траекторию при загрузке новой карты + 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(map, width, height); + 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(); +} + +canvas.addEventListener('mousedown', (e) => { + if (e.button === 2 || e.shiftKey) { + isPanning = true; + startPanX = e.clientX - offsetX; + startPanY = e.clientY - offsetY; + canvas.style.cursor = 'grabbing'; + } +}); + +canvas.addEventListener('mousemove', (e) => { + if (isPanning) { + offsetX = e.clientX - startPanX; + offsetY = e.clientY - startPanY; + drawMap(); + } +}); + +canvas.addEventListener('mouseup', () => { + isPanning = false; + canvas.style.cursor = 'default'; +}); + +canvas.addEventListener('mouseleave', () => { + isPanning = false; + canvas.style.cursor = 'default'; +}); + +canvas.addEventListener('wheel', (e) => { + e.preventDefault(); + const rect = canvas.getBoundingClientRect(); + const mouseX = e.clientX - rect.left; + const mouseY = e.clientY - rect.top; + const zoom = e.deltaY < 0 ? 1.1 : 0.9; + const newScale = Math.min(Math.max(0.1, scale * zoom), 5); + + offsetX = mouseX - (mouseX - offsetX) * (newScale / scale); + offsetY = mouseY - (mouseY - offsetY) * (newScale / scale); + scale = newScale; + + drawMap(); +}); + +canvas.addEventListener('contextmenu', (e) => e.preventDefault()); + +window.addEventListener('resize', () => { + resizeCanvas(); + drawMap(); +}); + +init(); + diff --git a/racing-tools/styles.css b/racing-tools/styles.css new file mode 100644 index 0000000..43bb46c --- /dev/null +++ b/racing-tools/styles.css @@ -0,0 +1,366 @@ +* { + 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: flex-start; +} + +.container { + background: white; + border-radius: 20px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + padding: 30px; + width: 100%; + display: flex; + flex-direction: column; + min-height: calc(100vh - 40px); +} + +h1 { + text-align: center; + color: #333; + margin-bottom: 10px; + font-size: 2em; +} + +.subtitle { + text-align: center; + color: #666; + margin-bottom: 20px; + font-size: 0.9em; +} + +.nav-links { + text-align: center; + margin-bottom: 20px; + padding: 15px; + background: #f8f9fa; + border-radius: 10px; +} + +.nav-links a { + margin: 0 15px; + padding: 10px 20px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + text-decoration: none; + border-radius: 8px; + font-weight: 600; + transition: all 0.3s; + display: inline-block; +} + +.nav-links a:hover { + transform: translateY(-2px); + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); +} + +.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); +} + +button:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; +} + +.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; + align-items: center; + background: #f8f9fa; + padding: 20px; + border-radius: 12px; + overflow: hidden; + flex: 1; + min-height: 1000px; + position: relative; +} + +canvas { + border: 3px solid #dee2e6; + border-radius: 8px; + cursor: crosshair; + box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1); + max-width: 100%; + max-height: 100%; +} + +.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, #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; +} + +/* Стили для визуализатора */ +.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; +} + diff --git a/sol-15x15.json b/sol-15x15.json new file mode 100644 index 0000000..5ce6279 --- /dev/null +++ b/sol-15x15.json @@ -0,0 +1,116 @@ +{ + "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 + ] + ] +} \ No newline at end of file diff --git a/solution-player/README.md b/solution-player/README.md new file mode 100644 index 0000000..7e134ba --- /dev/null +++ b/solution-player/README.md @@ -0,0 +1,94 @@ +# 🎬 Визуализатор решений - Гонки на бумаге + +Веб-приложение для визуализации и анимации решений. Работает без сервера, просто откройте в браузере. + +## 🚀 Быстрый старт + +```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 diff --git a/solution-player/demo-with-start-solution.json b/solution-player/demo-with-start-solution.json new file mode 100644 index 0000000..404ef99 --- /dev/null +++ b/solution-player/demo-with-start-solution.json @@ -0,0 +1,32 @@ +{ + "solution": [ + [ + 0, + 0 + ], + [ + 2, + 2 + ], + [ + 2, + -1 + ], + [ + -2, + -1 + ], + [ + 0, + 2 + ], + [ + 0, + 2 + ], + [ + 0, + -1 + ] + ] +} \ No newline at end of file diff --git a/solution-player/demo-with-start.json b/solution-player/demo-with-start.json new file mode 100644 index 0000000..faa82ec --- /dev/null +++ b/solution-player/demo-with-start.json @@ -0,0 +1,20 @@ +{ + "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] + ] +} + diff --git a/solution-player/index.html b/solution-player/index.html new file mode 100644 index 0000000..640265a --- /dev/null +++ b/solution-player/index.html @@ -0,0 +1,358 @@ + + + + + + Визуализатор решений - Гонки на бумаге + + + +
+

🎬 Визуализатор решений

+

Гонки на бумаге / Paper Racing

+ +
+
+

📂 Загрузка файлов

+
+ + +
+ + +
+
+ +
+ +
+ + + + + +
+ 💡 Инструкция: + • Сначала загрузите карту с точкой старта (тип 5)
+ • Затем загрузите файл решения с векторами ускорений
+ • Используйте кнопки управления для просмотра анимации
+ • 🔵 Синяя линия - пройденная траектория
+ • 🔴 Красный круг - текущая позиция
+ • ➡️ Красная стрелка - направление и скорость движения +
+ +
+

📖 Легенда цветов

+
+
+
+ Дорога (0) +
+
+
+ Камень (1) +
+
+
+ Снег (2) +
+
+
+ Лёд (3) +
+
+
+ Чекпоинт (4) +
+
+
+ Старт (5) +
+
+
+
+ + + + + diff --git a/solution-player/open-player.sh b/solution-player/open-player.sh new file mode 100755 index 0000000..d72cb9b --- /dev/null +++ b/solution-player/open-player.sh @@ -0,0 +1,40 @@ +#!/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 (решение)" + diff --git a/solution-player/player.js b/solution-player/player.js new file mode 100644 index 0000000..fbee17d --- /dev/null +++ b/solution-player/player.js @@ -0,0 +1,454 @@ +// Конфигурация плеера +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(); +