geo-step-game/map.js
2025-11-02 16:42:25 +03:00

320 lines
12 KiB
JavaScript
Raw Permalink 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.

class WorldMap {
constructor(canvasId) {
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d');
this.hoveredLocation = null;
this.availableLocations = [];
this.mapImage = null;
this.mapLoaded = false;
this.initCanvas();
this.setupEventListeners();
this.loadWorldMapImage();
}
initCanvas() {
this.canvas.width = this.canvas.offsetWidth;
this.canvas.height = this.canvas.offsetHeight;
}
loadWorldMapImage() {
// Загружаем реальную карту мира
this.mapImage = new Image();
this.mapImage.crossOrigin = "anonymous";
// Используем бесплатную карту мира с Wikimedia Commons
this.mapImage.src = 'https://upload.wikimedia.org/wikipedia/commons/thumb/8/83/Equirectangular_projection_SW.jpg/1280px-Equirectangular_projection_SW.jpg';
this.mapImage.onload = () => {
this.mapLoaded = true;
this.draw();
};
this.mapImage.onerror = () => {
console.warn('Не удалось загрузить карту, рисуем базовую версию');
this.mapLoaded = false;
this.draw();
};
}
setupEventListeners() {
this.canvas.addEventListener('mousemove', (e) => this.onMouseMove(e));
this.canvas.addEventListener('click', (e) => this.onClick(e));
window.addEventListener('resize', () => {
this.initCanvas();
this.draw();
});
}
onMouseMove(e) {
const rect = this.canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
this.hoveredLocation = this.getLocationAt(x, y);
this.canvas.style.cursor = this.hoveredLocation ? 'pointer' : 'default';
this.draw();
}
onClick(e) {
const rect = this.canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const location = this.getLocationAt(x, y);
if (location) {
game.onLocationClick(location);
}
}
getLocationAt(x, y) {
for (const loc of allPoints) {
const coords = this.getLocationCoords(loc);
const dx = x - coords.x;
const dy = y - coords.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < 20) {
return loc;
}
}
return null;
}
drawWorldMap() {
const ctx = this.ctx;
if (this.mapLoaded && this.mapImage) {
// Рисуем загруженную карту мира
ctx.drawImage(this.mapImage, 0, 0, this.canvas.width, this.canvas.height);
// Добавляем легкое затемнение для лучшей видимости маркеров
ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
} else {
// Запасной вариант - простая карта
ctx.fillStyle = '#a8dadc';
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
// Рисуем простые континенты
this.drawSimpleContinents();
}
}
drawSimpleContinents() {
const ctx = this.ctx;
ctx.fillStyle = '#90be6d';
// Упрощенные континенты
// Европа
ctx.fillRect(480, 170, 100, 120);
// Азия
ctx.fillRect(650, 150, 350, 280);
// Африка
ctx.fillRect(480, 280, 120, 260);
// Северная Америка
ctx.fillRect(100, 180, 180, 180);
// Южная Америка
ctx.fillRect(240, 360, 90, 200);
// Австралия
ctx.fillRect(900, 480, 120, 100);
}
draw() {
this.drawWorldMap();
this.drawRoutes();
this.drawLocations();
this.drawPlayers();
}
drawRoutes() {
const ctx = this.ctx;
ctx.strokeStyle = 'rgba(0, 0, 0, 0.15)';
ctx.lineWidth = 1.5;
ctx.setLineDash([3, 3]);
routes.forEach(route => {
const from = allPoints.find(l => l.id === route.from);
const to = allPoints.find(l => l.id === route.to);
if (from && to) {
const fromCoords = this.getLocationCoords(from);
const toCoords = this.getLocationCoords(to);
ctx.beginPath();
ctx.moveTo(fromCoords.x, fromCoords.y);
ctx.lineTo(toCoords.x, toCoords.y);
ctx.stroke();
}
});
ctx.setLineDash([]);
}
getLocationCoords(loc) {
// Пересчитываем координаты на основе текущего размера canvas
const x = (loc.lon + 180) * (this.canvas.width / 360);
const y = (90 - loc.lat) * (this.canvas.height / 180);
return { x, y };
}
drawLocations() {
const ctx = this.ctx;
// Получаем активные задания текущего игрока
const activeMissionDestinations = [];
if (game && game.getCurrentPlayer() && game.getCurrentPlayer().missions) {
game.getCurrentPlayer().missions.forEach(mission => {
activeMissionDestinations.push(mission.to);
});
}
allPoints.forEach(loc => {
const coords = this.getLocationCoords(loc);
const isHovered = this.hoveredLocation && this.hoveredLocation.id === loc.id;
const isAvailable = this.availableLocations.includes(loc.id);
const isWaypoint = !loc.type; // waypoints не имеют type
const isMissionDestination = activeMissionDestinations.includes(loc.id);
// Подсветка точек назначения активных заданий
if (isMissionDestination) {
ctx.shadowColor = 'rgba(46, 204, 113, 0.8)';
ctx.shadowBlur = 25;
ctx.beginPath();
ctx.arc(coords.x, coords.y, 30, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(46, 204, 113, 0.2)';
ctx.fill();
ctx.shadowBlur = 0;
// Анимированный круг вокруг точки назначения
ctx.strokeStyle = 'rgba(46, 204, 113, 0.6)';
ctx.lineWidth = 3;
ctx.stroke();
}
// Подсветка доступных локаций (желтая)
if (isAvailable) {
ctx.shadowColor = 'rgba(255, 215, 0, 0.8)';
ctx.shadowBlur = 20;
ctx.beginPath();
ctx.arc(coords.x, coords.y, 25, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(255, 215, 0, 0.3)';
ctx.fill();
ctx.shadowBlur = 0;
}
// Размер точки
const radius = isWaypoint ? (isHovered ? 8 : 5) : (isHovered ? 12 : 10);
// Круг локации
ctx.beginPath();
ctx.arc(coords.x, coords.y, radius, 0, Math.PI * 2);
if (isWaypoint) {
// Путевые точки - маленькие белые точки
ctx.fillStyle = isAvailable ? '#ffd700' : '#ffffff';
ctx.fill();
ctx.strokeStyle = isAvailable ? '#ff8c00' : 'rgba(0, 0, 0, 0.3)';
ctx.lineWidth = 2;
ctx.stroke();
} else {
// Города и порты - большие цветные
if (isMissionDestination) {
// Точки назначения - зеленые
ctx.fillStyle = '#27ae60';
} else if (loc.type === 'port') {
ctx.fillStyle = '#457b9d';
} else if (loc.type === 'city') {
ctx.fillStyle = '#e63946';
} else {
ctx.fillStyle = '#f77f00';
}
ctx.fill();
ctx.strokeStyle = 'white';
ctx.lineWidth = 3;
ctx.stroke();
}
// Название при наведении
if (isHovered) {
ctx.font = isWaypoint ? '12px Arial' : 'bold 14px Arial';
ctx.fillStyle = 'white';
ctx.strokeStyle = 'black';
ctx.lineWidth = 3;
const textWidth = ctx.measureText(loc.name).width;
const yOffset = isWaypoint ? -15 : -20;
ctx.strokeText(loc.name, coords.x - textWidth / 2, coords.y + yOffset);
ctx.fillText(loc.name, coords.x - textWidth / 2, coords.y + yOffset);
}
});
}
drawPlayers() {
if (!game || !game.players) return;
const ctx = this.ctx;
game.players.forEach((player, index) => {
let coords;
let offset = index * 25 - (game.players.length - 1) * 12;
// Если есть временная позиция анимации, используем её
if (player._tempAnimationPos) {
coords = player._tempAnimationPos;
} else {
const loc = allPoints.find(l => l.id === player.location);
if (!loc) return;
coords = this.getLocationCoords(loc);
}
// Рисуем кораблик игрока
ctx.font = 'bold 28px Arial';
// Тень для корабля
ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
ctx.shadowBlur = 5;
ctx.fillText(playerShips[index], coords.x + offset - 14, coords.y + 38);
ctx.shadowBlur = 0;
// Рамка вокруг текущего игрока
if (game.currentPlayerIndex === index) {
ctx.strokeStyle = playerColors[index];
ctx.lineWidth = 3;
ctx.strokeRect(coords.x + offset - 18, coords.y + 15, 36, 36);
}
// Индикатор активных заданий (не показываем во время анимации)
if (!player._tempAnimationPos && player.missions && player.missions.length > 0) {
ctx.fillStyle = '#f39c12';
ctx.beginPath();
ctx.arc(coords.x + offset + 12, coords.y + 20, 6, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = 'white';
ctx.lineWidth = 2;
ctx.stroke();
// Количество грузов
ctx.fillStyle = 'white';
ctx.font = 'bold 10px Arial';
ctx.fillText(player.missions.length, coords.x + offset + 9, coords.y + 24);
}
});
}
setAvailableLocations(locationIds) {
this.availableLocations = locationIds;
this.draw();
}
clearAvailableLocations() {
this.availableLocations = [];
this.draw();
}
}