This commit is contained in:
2025-10-20 19:35:38 +05:00
commit 023ccd03d8
42 changed files with 10007 additions and 0 deletions

166
map-editor/FEATURES.md Normal file
View File

@@ -0,0 +1,166 @@
# 🎯 Возможности редактора карт
## 🗺️ Редактирование карт
### Интерфейс редактора
- **Современный дизайн** с градиентами и анимациями
- **Адаптивная сетка** для различных размеров экрана
- **Интуитивные элементы управления** с подсказками
- **Цветовая палитра** для быстрого выбора типов ячеек
### Типы поверхностей
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** - понятен с первого взгляда
- **Примеры** - готовые карты и решения
### Функциональность
- **Полный цикл** - от создания до визуализации
- **Гибкость** - настройка всех параметров
- **Точность** - корректная физическая модель
- **Наглядность** - понятное отображение данных
### Расширяемость
- **Открытый код** - легко модифицировать
- **Модульность** - чёткая структура функций
- **Документация** - подробные комментарии
- **Примеры** - образцы для кастомизации
---
🎯 **Итог**: Мощный и удобный инструмент для работы с картами гонок и визуализации решений!

119
map-editor/INDEX.md Normal file
View File

@@ -0,0 +1,119 @@
# 📑 Индекс файлов редактора карт
## 📚 Документация
| Файл | Описание |
|------|----------|
| **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

71
map-editor/QUICKSTART.md Normal file
View File

@@ -0,0 +1,71 @@
# 🚀 Быстрый старт - Редактор карт
## За 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)

260
map-editor/README.md Normal file
View File

@@ -0,0 +1,260 @@
# 🗺️ Редактор карт для "Гонки на бумаге"
Веб-приложение для создания и редактирования карт для игры "Гонки на бумаге".
## 🚀 Быстрый старт
Просто откройте `index.html` в браузере. Никаких зависимостей или установки не требуется!
```bash
# Из папки map-editor
firefox index.html
# или
chrome index.html
# или просто откройте файл двойным кликом
```
## 📖 Инструкция
### Типы ячеек
| Тип | Код | Описание | Цвет |
|-----|-----|----------|------|
| Дорога | 0 | Обычная дорога | Светло-серый |
| Камень | 1 | Препятствие (непроходимо) | Тёмно-серый |
| Снег | 2 | Замедление движения | Голубой |
| Лёд | 3 | Скользкая поверхность | Светло-голубой |
| Чекпоинт | 4 | Контрольная точка | Жёлтый с "C" |
| Старт | 5 | Точка старта | Зелёный с "S" |
### Использование
1. **Изменение размеров:**
- Укажите ширину и высоту карты (5-100)
- Нажмите "Применить"
- Существующие данные сохранятся при изменении размера
2. **Рисование:**
- Выберите тип ячейки из палитры
- Кликните на ячейку для изменения типа
- Удерживайте кнопку мыши для рисования
3. **Экспорт:**
- Нажмите "Экспорт JSON"
- Файл автоматически скачается
- JSON также выводится в консоль браузера (F12)
4. **Импорт:**
- Нажмите "Импорт JSON"
- Выберите JSON файл
- Карта загрузится автоматически
5. **Визуализация решения:**
- Убедитесь, что на карте есть точка старта (тип 5)
- Нажмите "Загрузить решение"
- Выберите JSON файл с решением
- Используйте кнопки управления:
- ▶ Play - автоматическое воспроизведение
- ⏸ Pause - пауза
- ⏮ Reset - сброс к началу
- ⏭ Step - шаг вперед
- Регулируйте скорость воспроизведения (1x - 10x)
## 📄 Формат JSON
```json
{
"map": [
[0, 0, 0, 1, 0],
[5, 1, 0, 1, 0],
[0, 0, 2, 2, 4],
[1, 0, 3, 3, 0],
[0, 0, 0, 0, 0]
]
}
```
### Структура карты:
- `map` - двумерный массив целых чисел (int[][])
- Каждая строка массива = строка карты
- Каждый элемент = тип ячейки (0-5)
- Первая строка = верхняя строка карты
- Первый элемент в строке = левая ячейка
## 🎬 Формат решения
Решение представляет собой 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# коде:
```csharp
using System.Text.Json;
var json = File.ReadAllText("racing-map-15x15.json");
var mapData = JsonSerializer.Deserialize<MapData>(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 для экспорта
## 📝 Файлы примеров
### Карты:
- `simple-track.json` - Простая трасса 10×10
- `demo-with-start.json` - Демонстрационная карта 15×15
- `example-maps.json` - Коллекция различных карт
### Решения:
- `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] // без ускорения (движение по инерции)
]
}
```
---
Создано для проекта [Racing](../README.md)

View File

@@ -0,0 +1,26 @@
{
"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]
]
}

View File

@@ -0,0 +1,21 @@
{
"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]
]
}

View File

@@ -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]
]
}

573
map-editor/editor.js Normal file
View File

@@ -0,0 +1,573 @@
// Конфигурация редактора
const CELL_SIZE = 30;
const GRID_COLOR = '#dee2e6';
const COLORS = {
0: '#f8f9fa', // Дорога
1: '#6c757d', // Камень
2: '#e3f2fd', // Снег
3: '#b3e5fc', // Лёд
4: '#fff3cd', // Чекпоинт
5: '#d4edda' // Старт
};
// Состояние редактора
let width = 15;
let height = 15;
let map = [];
let selectedType = 0;
let isDrawing = false;
// Состояние визуализации
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() {
width = parseInt(document.getElementById('width').value);
height = parseInt(document.getElementById('height').value);
initMap();
resizeCanvas();
drawMap();
}
// Инициализация карты
function initMap() {
map = Array(height).fill(null).map(() => Array(width).fill(0));
}
// Изменение размера canvas
function resizeCanvas() {
canvas.width = width * CELL_SIZE;
canvas.height = height * CELL_SIZE;
}
// Отрисовка карты
function drawMap() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Рисуем ячейки
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const cellType = map[y][x];
ctx.fillStyle = COLORS[cellType];
ctx.fillRect(x * CELL_SIZE, y * CELL_SIZE, CELL_SIZE, CELL_SIZE);
}
}
// Рисуем сетку
ctx.strokeStyle = GRID_COLOR;
ctx.lineWidth = 1;
// Вертикальные линии
for (let x = 0; x <= width; x++) {
ctx.beginPath();
ctx.moveTo(x * CELL_SIZE, 0);
ctx.lineTo(x * CELL_SIZE, height * CELL_SIZE);
ctx.stroke();
}
// Горизонтальные линии
for (let y = 0; y <= height; y++) {
ctx.beginPath();
ctx.moveTo(0, y * CELL_SIZE);
ctx.lineTo(width * CELL_SIZE, y * CELL_SIZE);
ctx.stroke();
}
// Рисуем маркеры для чекпоинтов и старта
ctx.font = 'bold 16px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
if (map[y][x] === 4) {
ctx.fillStyle = '#856404';
ctx.fillText('C', x * CELL_SIZE + CELL_SIZE / 2, y * CELL_SIZE + CELL_SIZE / 2);
} else if (map[y][x] === 5) {
ctx.fillStyle = '#155724';
ctx.fillText('S', x * CELL_SIZE + CELL_SIZE / 2, y * CELL_SIZE + CELL_SIZE / 2);
}
}
}
// Рисуем визуализацию траектории, если она есть
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 selectCellType(type) {
selectedType = type;
// Обновляем UI
document.querySelectorAll('.cell-type').forEach(el => {
el.classList.remove('active');
});
document.querySelector(`[data-type="${type}"]`).classList.add('active');
}
// Получение координат ячейки по клику
function getCellCoords(event) {
const rect = canvas.getBoundingClientRect();
const x = Math.floor((event.clientX - rect.left) / CELL_SIZE);
const y = Math.floor((event.clientY - rect.top) / CELL_SIZE);
if (x >= 0 && x < width && y >= 0 && y < height) {
return { x, y };
}
return null;
}
// Установка типа ячейки
function setCellType(x, y) {
if (x >= 0 && x < width && y >= 0 && y < height) {
map[y][x] = selectedType;
drawMap();
}
}
// Обработчики событий мыши
canvas.addEventListener('mousedown', (e) => {
isDrawing = true;
const coords = getCellCoords(e);
if (coords) {
setCellType(coords.x, coords.y);
}
});
canvas.addEventListener('mousemove', (e) => {
if (isDrawing) {
const coords = getCellCoords(e);
if (coords) {
setCellType(coords.x, coords.y);
}
}
});
canvas.addEventListener('mouseup', () => {
isDrawing = false;
});
canvas.addEventListener('mouseleave', () => {
isDrawing = false;
});
// Изменение размера карты
function resizeMap() {
const newWidth = parseInt(document.getElementById('width').value);
const newHeight = parseInt(document.getElementById('height').value);
if (newWidth < 5 || newWidth > 100 || newHeight < 5 || newHeight > 100) {
alert('Размеры должны быть от 5 до 100');
return;
}
const newMap = Array(newHeight).fill(null).map(() => Array(newWidth).fill(0));
// Копируем существующие данные
const minHeight = Math.min(height, newHeight);
const minWidth = Math.min(width, newWidth);
for (let y = 0; y < minHeight; y++) {
for (let x = 0; x < minWidth; x++) {
newMap[y][x] = map[y][x];
}
}
width = newWidth;
height = newHeight;
map = newMap;
resizeCanvas();
drawMap();
}
// Очистка карты
function clearMap() {
if (confirm('Вы уверены, что хотите очистить всю карту?')) {
initMap();
drawMap();
}
}
// Экспорт карты в JSON
function exportMap() {
const data = {
map: map
};
const json = JSON.stringify(data, null, 2);
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `racing-map-${width}x${height}.json`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
// Также выводим в консоль для быстрого копирования
console.log('Экспортированная карта:', json);
alert('Карта экспортирована! JSON также выведен в консоль браузера (F12)');
}
// Импорт карты из JSON
function importMap(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = JSON.parse(e.target.result);
if (!data.map || !Array.isArray(data.map)) {
throw new Error('Неверный формат: отсутствует массив map');
}
const newMap = data.map;
// Валидация
if (!newMap.every(row => Array.isArray(row))) {
throw new Error('Неверный формат: map должен быть двумерным массивом');
}
const newHeight = newMap.length;
const newWidth = newMap[0].length;
if (newHeight < 5 || newHeight > 100 || newWidth < 5 || newWidth > 100) {
throw new Error('Размеры карты должны быть от 5 до 100');
}
if (!newMap.every(row => row.length === newWidth)) {
throw new Error('Все строки должны иметь одинаковую длину');
}
// Проверка значений ячеек
const validValues = [0, 1, 2, 3, 4, 5];
for (let y = 0; y < newHeight; y++) {
for (let x = 0; x < newWidth; x++) {
if (!validValues.includes(newMap[y][x])) {
throw new Error(`Недопустимое значение ячейки: ${newMap[y][x]} на позиции [${y}][${x}]`);
}
}
}
// Импортируем карту
height = newHeight;
width = newWidth;
map = newMap;
// Обновляем UI
document.getElementById('width').value = width;
document.getElementById('height').value = height;
resizeCanvas();
drawMap();
alert('Карта успешно импортирована!');
} catch (error) {
alert('Ошибка при импорте: ' + error.message);
console.error('Ошибка импорта:', error);
}
};
reader.readAsText(file);
// Сброс input для возможности повторного импорта того же файла
event.target.value = '';
}
// Обработчики изменения размеров
document.getElementById('width').addEventListener('keypress', (e) => {
if (e.key === 'Enter') resizeMap();
});
document.getElementById('height').addEventListener('keypress', (e) => {
if (e.key === 'Enter') resizeMap();
});
// ============================================
// Функции визуализации решения
// ============================================
// Поиск стартовой позиции на карте
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();

View File

@@ -0,0 +1,95 @@
{
"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]
]
}
]
}

View File

@@ -0,0 +1,16 @@
{
"solution": [
[1, 1],
[1, 0],
[1, 0],
[0, 1],
[0, 1],
[-1, 0],
[0, -1],
[0, 0],
[1, 0],
[0, 1]
]
}

509
map-editor/index.html Normal file
View File

@@ -0,0 +1,509 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Редактор карт - Гонки на бумаге</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.container {
background: white;
border-radius: 20px;
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
padding: 30px;
max-width: 1200px;
width: 100%;
}
h1 {
text-align: center;
color: #333;
margin-bottom: 10px;
font-size: 2em;
}
.subtitle {
text-align: center;
color: #666;
margin-bottom: 30px;
font-size: 0.9em;
}
.controls {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.control-group {
background: #f8f9fa;
padding: 20px;
border-radius: 12px;
border: 2px solid #e9ecef;
}
.control-group h3 {
margin-bottom: 15px;
color: #495057;
font-size: 1.1em;
border-bottom: 2px solid #dee2e6;
padding-bottom: 8px;
}
.size-inputs {
display: flex;
gap: 15px;
margin-bottom: 15px;
}
.input-wrapper {
flex: 1;
}
.input-wrapper label {
display: block;
margin-bottom: 5px;
color: #495057;
font-weight: 500;
font-size: 0.9em;
}
.input-wrapper input {
width: 100%;
padding: 10px;
border: 2px solid #dee2e6;
border-radius: 8px;
font-size: 16px;
transition: border-color 0.3s;
}
.input-wrapper input:focus {
outline: none;
border-color: #667eea;
}
.palette {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 10px;
}
.cell-type {
padding: 15px;
border: 3px solid #dee2e6;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
text-align: center;
font-weight: 600;
position: relative;
}
.cell-type:hover {
transform: translateY(-3px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
.cell-type.active {
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2);
transform: scale(1.05);
}
.cell-type .code {
font-size: 0.8em;
color: #6c757d;
display: block;
margin-top: 5px;
}
.road { background: #f8f9fa; color: #495057; }
.stone { background: #6c757d; color: white; }
.snow { background: #e3f2fd; color: #1976d2; }
.ice { background: #b3e5fc; color: #0277bd; }
.checkpoint { background: #fff3cd; color: #856404; border-color: #ffc107 !important; }
.start { background: #d4edda; color: #155724; border-color: #28a745 !important; }
.buttons {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
button {
flex: 1;
padding: 12px 24px;
border: none;
border-radius: 8px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
min-width: 120px;
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
}
button:active {
transform: translateY(0);
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
.btn-success {
background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
color: #333;
}
.btn-warning {
background: linear-gradient(135deg, #fa709a 0%, #fee140 100%);
color: #333;
}
.btn-danger {
background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
color: white;
}
.canvas-wrapper {
margin-top: 30px;
display: flex;
justify-content: center;
background: #f8f9fa;
padding: 20px;
border-radius: 12px;
overflow: auto;
}
canvas {
border: 3px solid #dee2e6;
border-radius: 8px;
cursor: crosshair;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.1);
}
.info {
margin-top: 20px;
padding: 15px;
background: #e7f3ff;
border-left: 4px solid #2196f3;
border-radius: 8px;
color: #0d47a1;
}
.info strong {
display: block;
margin-bottom: 5px;
}
#fileInput {
display: none;
}
.legend {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
margin-top: 15px;
}
.legend-item {
display: flex;
align-items: center;
gap: 10px;
padding: 8px;
background: white;
border-radius: 6px;
}
.legend-color {
width: 30px;
height: 30px;
border-radius: 4px;
border: 2px solid #dee2e6;
}
.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;
}
</style>
</head>
<body>
<div class="container">
<h1>🏁 Редактор карт</h1>
<p class="subtitle">Гонки на бумаге / Paper Racing</p>
<div class="controls">
<div class="control-group">
<h3>📐 Размеры карты</h3>
<div class="size-inputs">
<div class="input-wrapper">
<label for="width">Ширина:</label>
<input type="number" id="width" min="5" max="100" value="15">
</div>
<div class="input-wrapper">
<label for="height">Высота:</label>
<input type="number" id="height" min="5" max="100" value="15">
</div>
</div>
<div class="buttons">
<button class="btn-primary" onclick="resizeMap()">Применить</button>
<button class="btn-danger" onclick="clearMap()">Очистить</button>
</div>
</div>
<div class="control-group">
<h3>🎨 Тип ячейки</h3>
<div class="palette">
<div class="cell-type road active" data-type="0" onclick="selectCellType(0)">
<span>Дорога</span>
<span class="code">(0)</span>
</div>
<div class="cell-type stone" data-type="1" onclick="selectCellType(1)">
<span>Камень</span>
<span class="code">(1)</span>
</div>
<div class="cell-type snow" data-type="2" onclick="selectCellType(2)">
<span>Снег</span>
<span class="code">(2)</span>
</div>
<div class="cell-type ice" data-type="3" onclick="selectCellType(3)">
<span>Лёд</span>
<span class="code">(3)</span>
</div>
<div class="cell-type checkpoint" data-type="4" onclick="selectCellType(4)">
<span>Чекпоинт</span>
<span class="code">(4)</span>
</div>
<div class="cell-type start" data-type="5" onclick="selectCellType(5)">
<span>Старт</span>
<span class="code">(5)</span>
</div>
</div>
</div>
<div class="control-group">
<h3>💾 Импорт / Экспорт</h3>
<div class="buttons">
<button class="btn-success" onclick="exportMap()">📥 Экспорт JSON</button>
<button class="btn-warning" onclick="document.getElementById('fileInput').click()">📤 Импорт JSON</button>
</div>
<input type="file" id="fileInput" accept=".json" onchange="importMap(event)">
</div>
<div class="control-group">
<h3>🎬 Визуализация решения</h3>
<div class="buttons">
<button class="btn-primary" onclick="document.getElementById('solutionInput').click()">📂 Загрузить решение</button>
<button class="btn-danger" onclick="clearVisualization()" id="clearVisBtn" disabled>🗑️ Очистить</button>
</div>
<input type="file" id="solutionInput" accept=".json" onchange="loadSolution(event)" style="display: none;">
</div>
</div>
<div class="canvas-wrapper">
<canvas id="mapCanvas"></canvas>
</div>
<div id="visualizationPanel" class="visualization-panel hidden">
<h3>🎮 Управление воспроизведением</h3>
<div class="playback-controls">
<button class="playback-btn" onclick="playVisualization()" id="playBtn">▶ Play</button>
<button class="playback-btn" onclick="pauseVisualization()" id="pauseBtn" disabled>⏸ Pause</button>
<button class="playback-btn" onclick="resetVisualization()">⏮ Reset</button>
<button class="playback-btn" onclick="stepForward()">⏭ Step</button>
<div class="speed-control">
<label for="speedSlider">Скорость:</label>
<input type="range" id="speedSlider" min="1" max="10" value="5" onchange="updateSpeed()">
<span id="speedValue">5x</span>
</div>
</div>
<div class="step-info">
<div class="info-item">
<span class="info-label">Шаг</span>
<span class="info-value" id="stepNumber">0 / 0</span>
</div>
<div class="info-item">
<span class="info-label">Позиция (x, y)</span>
<span class="info-value" id="positionValue">(0, 0)</span>
</div>
<div class="info-item">
<span class="info-label">Скорость (vx, vy)</span>
<span class="info-value" id="velocityValue">(0, 0)</span>
</div>
<div class="info-item">
<span class="info-label">Ускорение (ax, ay)</span>
<span class="info-value" id="accelerationValue">(0, 0)</span>
</div>
</div>
</div>
<div class="info">
<strong>💡 Подсказки:</strong>
• Кликайте по ячейкам для изменения типа<br>
• Удерживайте кнопку мыши для рисования<br>
• Экспортируйте карту в JSON для использования в игре<br>
• Загрузите решение для визуализации прохождения карты
</div>
<div class="control-group">
<h3>📖 Легенда цветов</h3>
<div class="legend">
<div class="legend-item">
<div class="legend-color road"></div>
<span>Дорога (0)</span>
</div>
<div class="legend-item">
<div class="legend-color stone"></div>
<span>Камень (1)</span>
</div>
<div class="legend-item">
<div class="legend-color snow"></div>
<span>Снег (2)</span>
</div>
<div class="legend-item">
<div class="legend-color ice"></div>
<span>Лёд (3)</span>
</div>
<div class="legend-item">
<div class="legend-color checkpoint"></div>
<span>Чекпоинт (4)</span>
</div>
<div class="legend-item">
<div class="legend-color start"></div>
<span>Старт (5)</span>
</div>
</div>
</div>
</div>
<script src="editor.js"></script>
</body>
</html>

View File

@@ -0,0 +1,32 @@
{
"solution": [
[
0,
0
],
[
-1,
2
],
[
0,
0
],
[
-2,
-2
],
[
1,
0
],
[
-1,
0
],
[
1,
0
]
]
}

42
map-editor/open-editor.sh Executable file
View File

@@ -0,0 +1,42 @@
#!/bin/bash
# Скрипт для открытия редактора карт в браузере
echo "🗺️ Редактор карт - Гонки на бумаге"
echo "=================================="
echo ""
# Определяем путь к редактору
EDITOR_PATH="$(cd "$(dirname "$0")" && pwd)/index.html"
# Пытаемся открыть в браузере
if command -v xdg-open > /dev/null; then
echo "Открываю редактор в браузере по умолчанию..."
xdg-open "$EDITOR_PATH"
elif command -v firefox > /dev/null; then
echo "Открываю редактор в Firefox..."
firefox "$EDITOR_PATH"
elif command -v google-chrome > /dev/null; then
echo "Открываю редактор в Chrome..."
google-chrome "$EDITOR_PATH"
elif command -v chromium > /dev/null; then
echo "Открываю редактор в Chromium..."
chromium "$EDITOR_PATH"
else
echo "❌ Не найден браузер для автоматического открытия"
echo ""
echo "Откройте файл вручную:"
echo " $EDITOR_PATH"
fi
echo ""
echo "✅ Редактор готов к использованию!"
echo ""
echo "💡 Подсказки:"
echo " • Кликайте по ячейкам для изменения типа"
echo " • Удерживайте кнопку мыши для рисования"
echo " • Экспортируйте карту в JSON для использования в игре"
echo ""
echo "📖 Документация: README.md"

View File

@@ -0,0 +1,15 @@
{
"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]
]
}