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