Files
paper-racing-gpi/racing-tools/editor.js
2025-10-20 21:07:28 +05:00

254 lines
7.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Состояние редактора
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();