782 lines
32 KiB
JavaScript
782 lines
32 KiB
JavaScript
class Game {
|
||
constructor() {
|
||
this.players = [];
|
||
this.currentPlayerIndex = 0;
|
||
this.diceRolled = false;
|
||
this.diceValue = 0;
|
||
this.availableMissions = [];
|
||
|
||
this.setupStartModal();
|
||
}
|
||
|
||
setupStartModal() {
|
||
const playerCountBtns = document.querySelectorAll('.player-count-btn');
|
||
let selectedCount = 1;
|
||
|
||
playerCountBtns.forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
playerCountBtns.forEach(b => b.classList.remove('active'));
|
||
btn.classList.add('active');
|
||
selectedCount = parseInt(btn.dataset.count);
|
||
this.updatePlayerInputs(selectedCount);
|
||
});
|
||
});
|
||
|
||
document.getElementById('startGameBtn').addEventListener('click', () => {
|
||
this.startGame(selectedCount);
|
||
});
|
||
}
|
||
|
||
updatePlayerInputs(count) {
|
||
const container = document.getElementById('playerNamesContainer');
|
||
container.innerHTML = '';
|
||
|
||
for (let i = 0; i < count; i++) {
|
||
const div = document.createElement('div');
|
||
div.className = 'form-group';
|
||
div.innerHTML = `
|
||
<label>Имя игрока ${i + 1}:</label>
|
||
<input type="text" id="player${i + 1}Name" value="Игрок ${i + 1}" class="player-name-input">
|
||
`;
|
||
container.appendChild(div);
|
||
}
|
||
}
|
||
|
||
startGame(playerCount) {
|
||
// Создаем игроков
|
||
for (let i = 0; i < playerCount; i++) {
|
||
const nameInput = document.getElementById(`player${i + 1}Name`);
|
||
const name = nameInput ? nameInput.value : `Игрок ${i + 1}`;
|
||
|
||
this.players.push({
|
||
name: name,
|
||
location: 'newyork', // Все начинают в Нью-Йорке
|
||
score: 0,
|
||
missions: [], // Массив активных заданий
|
||
completedMissionIds: [], // ID всех когда-либо взятых заданий (включая завершенные)
|
||
currentWeight: 0,
|
||
currentVolume: 0
|
||
});
|
||
}
|
||
|
||
// Генерируем задания
|
||
this.generateMissions();
|
||
|
||
// Закрываем стартовое окно
|
||
document.getElementById('startModal').style.display = 'none';
|
||
|
||
// Инициализируем интерфейс
|
||
this.initGame();
|
||
this.updateUI();
|
||
}
|
||
|
||
initGame() {
|
||
// Настройка кнопок
|
||
document.getElementById('rollDiceBtn').addEventListener('click', () => this.rollDice());
|
||
document.getElementById('endTurnBtn').addEventListener('click', () => this.endTurn());
|
||
document.getElementById('newMissionBtn').addEventListener('click', () => this.showMissionsModal());
|
||
|
||
// Модальные окна
|
||
document.querySelector('.close').addEventListener('click', () => {
|
||
document.getElementById('missionModal').style.display = 'none';
|
||
});
|
||
|
||
document.getElementById('closeCompleteModal').addEventListener('click', () => {
|
||
document.getElementById('completeModal').style.display = 'none';
|
||
});
|
||
|
||
window.addEventListener('click', (e) => {
|
||
if (e.target.classList.contains('modal')) {
|
||
e.target.style.display = 'none';
|
||
}
|
||
});
|
||
}
|
||
|
||
generateMissions() {
|
||
this.availableMissions = [];
|
||
|
||
// Генерируем 15-20 случайных заданий
|
||
const count = 15 + Math.floor(Math.random() * 6);
|
||
|
||
for (let i = 0; i < count; i++) {
|
||
this.availableMissions.push(this.generateSingleMission());
|
||
}
|
||
}
|
||
|
||
calculateRouteDistance(fromId, toId) {
|
||
// BFS для нахождения кратчайшего пути
|
||
const visited = new Set();
|
||
const queue = [{id: fromId, distance: 0}];
|
||
|
||
while (queue.length > 0) {
|
||
const {id, distance} = queue.shift();
|
||
|
||
if (id === toId) {
|
||
return distance;
|
||
}
|
||
|
||
if (visited.has(id)) continue;
|
||
visited.add(id);
|
||
|
||
const neighbors = routes
|
||
.filter(r => r.from === id)
|
||
.map(r => r.to);
|
||
|
||
neighbors.forEach(neighborId => {
|
||
if (!visited.has(neighborId)) {
|
||
queue.push({id: neighborId, distance: distance + 1});
|
||
}
|
||
});
|
||
}
|
||
|
||
return 20; // По умолчанию, если путь не найден
|
||
}
|
||
|
||
generateSingleMission() {
|
||
// Выбираем случайные локации (только города/порты, не путевые точки)
|
||
const startLocations = locations.filter(l => l.type);
|
||
const fromLocation = startLocations[Math.floor(Math.random() * startLocations.length)];
|
||
let toLocation = startLocations[Math.floor(Math.random() * startLocations.length)];
|
||
|
||
// Убедимся, что это разные локации
|
||
while (toLocation.id === fromLocation.id) {
|
||
toLocation = startLocations[Math.floor(Math.random() * startLocations.length)];
|
||
}
|
||
|
||
// Выбираем случайный груз
|
||
const cargo = cargoTypes[Math.floor(Math.random() * cargoTypes.length)];
|
||
|
||
// Рассчитываем расстояние между городами
|
||
const distance = this.calculateRouteDistance(fromLocation.id, toLocation.id);
|
||
|
||
// Награда зависит от расстояния и типа груза
|
||
// Базовая награда груза + бонус за расстояние (50 очков за каждую точку маршрута)
|
||
const distanceBonus = distance * 50;
|
||
const totalReward = Math.floor(cargo.reward * 0.5 + distanceBonus);
|
||
|
||
return {
|
||
id: 'mission_' + Date.now() + '_' + Math.random(),
|
||
from: fromLocation.id,
|
||
to: toLocation.id,
|
||
fromName: fromLocation.name,
|
||
toName: toLocation.name,
|
||
cargo: cargo.name,
|
||
cargoId: cargo.id,
|
||
reward: totalReward,
|
||
weight: cargo.weight,
|
||
volume: cargo.volume,
|
||
distance: distance
|
||
};
|
||
}
|
||
|
||
showMissionsModal() {
|
||
const modal = document.getElementById('missionModal');
|
||
const container = document.getElementById('missionsListModal');
|
||
const player = this.getCurrentPlayer();
|
||
|
||
container.innerHTML = `
|
||
<div style="background: #e3f2fd; padding: 15px; border-radius: 8px; margin-bottom: 15px;">
|
||
<h4>Вместимость корабля:</h4>
|
||
<p>Вес: ${player.currentWeight}/${SHIP_MAX_WEIGHT} кг</p>
|
||
<p>Объем: ${player.currentVolume}/${SHIP_MAX_VOLUME} м³</p>
|
||
</div>
|
||
`;
|
||
|
||
this.availableMissions.forEach(mission => {
|
||
const alreadyTaken = player.missions.find(m => m.id === mission.id);
|
||
const wasTakenBefore = player.completedMissionIds.includes(mission.id);
|
||
const canTake = !alreadyTaken &&
|
||
!wasTakenBefore &&
|
||
(player.currentWeight + mission.weight <= SHIP_MAX_WEIGHT) &&
|
||
(player.currentVolume + mission.volume <= SHIP_MAX_VOLUME);
|
||
|
||
const card = document.createElement('div');
|
||
card.className = 'mission-card' + (canTake ? '' : ' mission-card-disabled');
|
||
card.innerHTML = `
|
||
<div class="mission-card-header">
|
||
${mission.fromName} ➜ ${mission.toName}
|
||
</div>
|
||
<div class="mission-card-details">
|
||
<p>📦 Груз: ${mission.cargo}</p>
|
||
<p>⚖️ Вес: ${mission.weight} кг | 📐 Объем: ${mission.volume} м³</p>
|
||
<p>🗺️ Расстояние: ${mission.distance} шагов</p>
|
||
${alreadyTaken ? '<p style="color: #f39c12;">✓ Уже взято!</p>' : ''}
|
||
${wasTakenBefore ? '<p style="color: #e74c3c;">⚠️ Уже выполнялось ранее!</p>' : ''}
|
||
${!canTake && !alreadyTaken && !wasTakenBefore ? '<p style="color: #e74c3c;">⚠️ Не помещается!</p>' : ''}
|
||
</div>
|
||
<div class="mission-card-reward">
|
||
💰 Награда: ${mission.reward} очков
|
||
</div>
|
||
`;
|
||
|
||
if (canTake) {
|
||
card.addEventListener('click', () => {
|
||
this.selectMission(mission);
|
||
this.showMissionsModal(); // Обновляем модальное окно
|
||
});
|
||
}
|
||
|
||
container.appendChild(card);
|
||
});
|
||
|
||
modal.style.display = 'block';
|
||
}
|
||
|
||
selectMission(mission) {
|
||
const player = this.getCurrentPlayer();
|
||
|
||
// Проверяем, не взято ли уже это задание
|
||
if (player.missions.find(m => m.id === mission.id)) {
|
||
alert('Вы уже взяли это задание!');
|
||
return;
|
||
}
|
||
|
||
// Проверяем, не брал ли игрок это задание ранее
|
||
if (player.completedMissionIds.includes(mission.id)) {
|
||
alert('Вы уже выполняли это задание ранее! Нельзя взять одно и то же задание дважды.');
|
||
return;
|
||
}
|
||
|
||
// Проверяем вместимость корабля
|
||
const totalWeight = player.currentWeight + mission.weight;
|
||
const totalVolume = player.currentVolume + mission.volume;
|
||
|
||
if (totalWeight > SHIP_MAX_WEIGHT) {
|
||
alert(`Превышен максимальный вес! (${totalWeight}/${SHIP_MAX_WEIGHT} кг)`);
|
||
return;
|
||
}
|
||
|
||
if (totalVolume > SHIP_MAX_VOLUME) {
|
||
alert(`Превышен максимальный объем! (${totalVolume}/${SHIP_MAX_VOLUME} м³)`);
|
||
return;
|
||
}
|
||
|
||
// Добавляем задание
|
||
player.missions.push(mission);
|
||
// Добавляем ID задания в список всех когда-либо взятых
|
||
player.completedMissionIds.push(mission.id);
|
||
player.currentWeight = totalWeight;
|
||
player.currentVolume = totalVolume;
|
||
|
||
// НЕ удаляем задание из списка, чтобы другие игроки могли его взять
|
||
// this.availableMissions = this.availableMissions.filter(m => m.id !== mission.id);
|
||
|
||
this.updateUI();
|
||
this.highlightMissionDestinations();
|
||
this.showNotification(`${player.name} взял груз: ${mission.cargo} (${mission.fromName} → ${mission.toName})`);
|
||
}
|
||
|
||
rollDice() {
|
||
if (this.gameEnded) {
|
||
alert('Игра окончена! Обновите страницу для новой игры.');
|
||
return;
|
||
}
|
||
|
||
if (this.diceRolled) {
|
||
alert('Вы уже бросили кости! Выберите куда пойти или завершите ход.');
|
||
return;
|
||
}
|
||
|
||
const dice1El = document.getElementById('dice1');
|
||
const dice2El = document.getElementById('dice2');
|
||
|
||
// Анимация
|
||
dice1El.classList.add('rolling');
|
||
dice2El.classList.add('rolling');
|
||
|
||
let counter = 0;
|
||
const interval = setInterval(() => {
|
||
dice1El.textContent = Math.floor(Math.random() * 6) + 1;
|
||
dice2El.textContent = Math.floor(Math.random() * 6) + 1;
|
||
counter++;
|
||
|
||
if (counter > 10) {
|
||
clearInterval(interval);
|
||
|
||
// Финальные значения
|
||
const dice1 = Math.floor(Math.random() * 6) + 1;
|
||
const dice2 = Math.floor(Math.random() * 6) + 1;
|
||
|
||
dice1El.textContent = dice1;
|
||
dice2El.textContent = dice2;
|
||
dice1El.classList.remove('rolling');
|
||
dice2El.classList.remove('rolling');
|
||
|
||
this.diceValue = dice1 + dice2;
|
||
this.diceRolled = true;
|
||
|
||
document.getElementById('diceTotal').textContent = this.diceValue;
|
||
document.getElementById('diceResult').style.display = 'block';
|
||
document.getElementById('rollDiceBtn').disabled = true;
|
||
|
||
this.showAvailableMoves();
|
||
}
|
||
}, 100);
|
||
}
|
||
|
||
showAvailableMoves() {
|
||
const player = this.getCurrentPlayer();
|
||
const currentLoc = player.location;
|
||
|
||
// Находим все доступные локации на расстоянии <= diceValue шагов
|
||
const available = this.getReachableLocations(currentLoc, this.diceValue);
|
||
|
||
worldMap.setAvailableLocations(available);
|
||
|
||
this.showNotification(`${player.name} может пойти на ${this.diceValue} шагов. Выберите локацию на карте.`);
|
||
}
|
||
|
||
getReachableLocations(startId, steps) {
|
||
// BFS для поиска всех доступных локаций
|
||
const visited = new Set();
|
||
const queue = [{id: startId, steps: 0}];
|
||
const reachable = [];
|
||
|
||
while (queue.length > 0) {
|
||
const {id, steps: currentSteps} = queue.shift();
|
||
|
||
if (visited.has(id)) continue;
|
||
visited.add(id);
|
||
|
||
if (currentSteps > 0 && currentSteps <= steps) {
|
||
reachable.push(id);
|
||
}
|
||
|
||
if (currentSteps < steps) {
|
||
// Находим соседей
|
||
const neighbors = routes
|
||
.filter(r => r.from === id || r.to === id)
|
||
.map(r => r.from === id ? r.to : r.from);
|
||
|
||
neighbors.forEach(neighborId => {
|
||
if (!visited.has(neighborId)) {
|
||
queue.push({id: neighborId, steps: currentSteps + 1});
|
||
}
|
||
});
|
||
}
|
||
}
|
||
|
||
return reachable;
|
||
}
|
||
|
||
onLocationClick(location) {
|
||
if (!this.diceRolled) {
|
||
// Показываем информацию о локации
|
||
this.showLocationInfo(location);
|
||
return;
|
||
}
|
||
|
||
// Проверяем, доступна ли эта локация
|
||
if (!worldMap.availableLocations.includes(location.id)) {
|
||
alert('Эта локация недоступна. Выберите подсвеченную локацию.');
|
||
return;
|
||
}
|
||
|
||
this.movePlayer(location);
|
||
}
|
||
|
||
async movePlayer(location) {
|
||
const player = this.getCurrentPlayer();
|
||
const oldLocation = player.location;
|
||
|
||
// Вычисляем путь
|
||
const path = this.calculatePath(oldLocation, location.id);
|
||
const stepsUsed = path.length - 1;
|
||
|
||
// Анимируем перемещение по пути
|
||
await this.animatePlayerMovement(path);
|
||
|
||
// Устанавливаем финальную позицию
|
||
player.location = location.id;
|
||
this.diceValue -= stepsUsed;
|
||
|
||
if (this.diceValue <= 0) {
|
||
// Все ходы использованы
|
||
this.diceRolled = false;
|
||
worldMap.clearAvailableLocations();
|
||
document.getElementById('endTurnBtn').disabled = false;
|
||
document.getElementById('diceResult').style.display = 'none';
|
||
} else {
|
||
// Еще остались ходы
|
||
this.showAvailableMoves();
|
||
this.showNotification(`${player.name} переместился в ${location.name}. Осталось ${this.diceValue} шагов.`);
|
||
}
|
||
|
||
worldMap.draw();
|
||
this.showLocationInfo(location);
|
||
this.checkMissionCompletion();
|
||
|
||
if (this.diceValue <= 0) {
|
||
this.showNotification(`${player.name} переместился в ${location.name}. Нажмите "Закончить ход".`);
|
||
}
|
||
}
|
||
|
||
async animatePlayerMovement(path) {
|
||
const player = this.getCurrentPlayer();
|
||
const playerIndex = this.currentPlayerIndex;
|
||
|
||
// Сохраняем оригинальную позицию
|
||
const originalLocation = player.location;
|
||
|
||
// Для каждого шага пути
|
||
for (let i = 1; i < path.length; i++) {
|
||
const currentLocationId = path[i - 1];
|
||
const nextLocationId = path[i];
|
||
|
||
const currentLoc = allPoints.find(l => l.id === currentLocationId);
|
||
const nextLoc = allPoints.find(l => l.id === nextLocationId);
|
||
|
||
if (!currentLoc || !nextLoc) {
|
||
player.location = nextLocationId;
|
||
worldMap.draw();
|
||
await new Promise(resolve => setTimeout(resolve, 200));
|
||
continue;
|
||
}
|
||
|
||
// Плавная анимация между точками
|
||
const steps = 10; // Количество промежуточных кадров
|
||
for (let step = 0; step <= steps; step++) {
|
||
const progress = step / steps;
|
||
|
||
// Временно устанавливаем промежуточную позицию для визуализации
|
||
const currentCoords = worldMap.getLocationCoords(currentLoc);
|
||
const nextCoords = worldMap.getLocationCoords(nextLoc);
|
||
|
||
// Вычисляем промежуточную позицию
|
||
const x = currentCoords.x + (nextCoords.x - currentCoords.x) * progress;
|
||
const y = currentCoords.y + (nextCoords.y - currentCoords.y) * progress;
|
||
|
||
// Сохраняем временную позицию для отрисовки
|
||
player._tempAnimationPos = { x, y };
|
||
player.location = step === steps ? nextLocationId : currentLocationId;
|
||
|
||
// Перерисовываем карту
|
||
worldMap.draw();
|
||
|
||
// Небольшая задержка для плавности
|
||
await new Promise(resolve => setTimeout(resolve, 30));
|
||
}
|
||
|
||
// Убираем временную позицию
|
||
player.location = nextLocationId;
|
||
delete player._tempAnimationPos;
|
||
}
|
||
|
||
// Восстанавливаем финальную позицию
|
||
player.location = path[path.length - 1];
|
||
}
|
||
|
||
calculateDistance(fromId, toId) {
|
||
const path = this.calculatePath(fromId, toId);
|
||
return path ? path.length - 1 : 1;
|
||
}
|
||
|
||
calculatePath(fromId, toId) {
|
||
// BFS для нахождения кратчайшего пути с сохранением пути
|
||
const visited = new Set();
|
||
const queue = [{id: fromId, distance: 0, path: [fromId]}];
|
||
|
||
while (queue.length > 0) {
|
||
const {id, distance, path} = queue.shift();
|
||
|
||
if (id === toId) {
|
||
return path;
|
||
}
|
||
|
||
if (visited.has(id)) continue;
|
||
visited.add(id);
|
||
|
||
const neighbors = routes
|
||
.filter(r => r.from === id || r.to === id)
|
||
.map(r => r.from === id ? r.to : r.from);
|
||
|
||
neighbors.forEach(neighborId => {
|
||
if (!visited.has(neighborId)) {
|
||
queue.push({id: neighborId, distance: distance + 1, path: [...path, neighborId]});
|
||
}
|
||
});
|
||
}
|
||
|
||
return [fromId, toId]; // По умолчанию прямой путь
|
||
}
|
||
|
||
checkMissionCompletion() {
|
||
const player = this.getCurrentPlayer();
|
||
|
||
// Проверяем все активные задания
|
||
const completedMissions = player.missions.filter(m => m.to === player.location);
|
||
|
||
if (completedMissions.length > 0) {
|
||
let totalReward = 0;
|
||
let completedList = '';
|
||
|
||
completedMissions.forEach(mission => {
|
||
player.score += mission.reward;
|
||
totalReward += mission.reward;
|
||
player.currentWeight -= mission.weight;
|
||
player.currentVolume -= mission.volume;
|
||
|
||
completedList += `<p>📦 ${mission.cargo}: ${mission.fromName} → ${mission.toName} (+${mission.reward} очков)</p>`;
|
||
|
||
// Удаляем выполненное задание из активных
|
||
player.missions = player.missions.filter(m => m.id !== mission.id);
|
||
// ID уже сохранен в completedMissionIds, поэтому игрок не сможет взять его снова
|
||
|
||
// Генерируем новое задание
|
||
this.availableMissions.push(this.generateSingleMission());
|
||
});
|
||
|
||
const modal = document.getElementById('completeModal');
|
||
const body = document.getElementById('completeModalBody');
|
||
|
||
body.innerHTML = `
|
||
<p><strong>${player.name}</strong> доставил ${completedMissions.length} груз(ов)!</p>
|
||
${completedList}
|
||
<p style="font-size: 24px; color: #27ae60; font-weight: bold;">
|
||
Всего: +${totalReward} очков
|
||
</p>
|
||
<p>Общий счет: <strong>${player.score}</strong></p>
|
||
`;
|
||
|
||
modal.style.display = 'block';
|
||
this.updateUI();
|
||
|
||
// Проверяем победу
|
||
this.checkVictory();
|
||
}
|
||
}
|
||
|
||
checkVictory() {
|
||
const WINNING_SCORE = 5000; // Победное количество очков
|
||
|
||
const winners = this.players.filter(p => p.score >= WINNING_SCORE);
|
||
|
||
if (winners.length > 0) {
|
||
// Сортируем по очкам
|
||
winners.sort((a, b) => b.score - a.score);
|
||
const winner = winners[0];
|
||
|
||
setTimeout(() => {
|
||
const modal = document.getElementById('completeModal');
|
||
const body = document.getElementById('completeModalBody');
|
||
|
||
let winnersHTML = '';
|
||
this.players.sort((a, b) => b.score - a.score).forEach((player, index) => {
|
||
const medal = index === 0 ? '🥇' : index === 1 ? '🥈' : index === 2 ? '🥉' : '';
|
||
winnersHTML += `<p>${medal} <strong>${player.name}</strong>: ${player.score} очков</p>`;
|
||
});
|
||
|
||
body.innerHTML = `
|
||
<h2 style="color: #f39c12; margin-bottom: 20px;">🎉 Игра окончена!</h2>
|
||
<h3 style="color: #27ae60;">🏆 Победитель: ${winner.name}!</h3>
|
||
<p style="font-size: 32px; font-weight: bold; color: #27ae60; margin: 20px 0;">
|
||
${winner.score} очков
|
||
</p>
|
||
<hr style="margin: 20px 0;">
|
||
<h4>Итоговые результаты:</h4>
|
||
${winnersHTML}
|
||
<hr style="margin: 20px 0;">
|
||
<p style="margin-top: 20px;">Обновите страницу для новой игры</p>
|
||
`;
|
||
|
||
modal.style.display = 'block';
|
||
|
||
// Блокируем дальнейшую игру
|
||
this.gameEnded = true;
|
||
}, 2000);
|
||
}
|
||
}
|
||
|
||
showLocationInfo(location) {
|
||
const infoBox = document.getElementById('locationInfo');
|
||
|
||
if (location.type) {
|
||
// Это город/порт/хаб
|
||
infoBox.innerHTML = `
|
||
<h4>${location.icon} ${location.name}</h4>
|
||
<p><strong>Страна:</strong> ${location.country}</p>
|
||
<p><strong>Континент:</strong> ${location.continent}</p>
|
||
<p><strong>Тип:</strong> ${
|
||
location.type === 'port' ? '🚢 Порт' :
|
||
location.type === 'city' ? '🏙️ Город' :
|
||
'🏭 Логистический хаб'
|
||
}</p>
|
||
`;
|
||
} else {
|
||
// Это путевая точка
|
||
infoBox.innerHTML = `
|
||
<h4>🌊 ${location.name}</h4>
|
||
<p><strong>Тип:</strong> Морской путь</p>
|
||
<p class="mt-2">Промежуточная точка на торговом маршруте</p>
|
||
`;
|
||
}
|
||
}
|
||
|
||
endTurn() {
|
||
this.currentPlayerIndex = (this.currentPlayerIndex + 1) % this.players.length;
|
||
this.diceRolled = false;
|
||
this.diceValue = 0;
|
||
|
||
document.getElementById('diceResult').style.display = 'none';
|
||
document.getElementById('rollDiceBtn').disabled = false;
|
||
document.getElementById('endTurnBtn').disabled = true;
|
||
document.getElementById('dice1').textContent = '?';
|
||
document.getElementById('dice2').textContent = '?';
|
||
|
||
worldMap.clearAvailableLocations();
|
||
|
||
this.updateUI();
|
||
this.showNotification(`Ход переходит к ${this.getCurrentPlayer().name}`);
|
||
}
|
||
|
||
highlightMissionDestinations() {
|
||
// Обновляем карту с подсветкой точек назначения
|
||
worldMap.draw();
|
||
}
|
||
|
||
updateUI() {
|
||
// Обновляем список игроков
|
||
const playersList = document.getElementById('playersList');
|
||
playersList.innerHTML = '';
|
||
|
||
this.players.forEach((player, index) => {
|
||
const card = document.createElement('div');
|
||
card.className = `player-card player-${index + 1}`;
|
||
if (index === this.currentPlayerIndex) {
|
||
card.classList.add('active');
|
||
}
|
||
|
||
const loc = allPoints.find(l => l.id === player.location);
|
||
const WINNING_SCORE = 5000;
|
||
const progress = Math.min(100, (player.score / WINNING_SCORE) * 100);
|
||
|
||
card.innerHTML = `
|
||
<div class="player-info">
|
||
<span class="player-token">${playerShips[index]}</span>
|
||
<div class="player-details">
|
||
<div class="player-name-display">${player.name}</div>
|
||
<div class="player-location">📍 ${loc ? loc.name : 'Неизвестно'}</div>
|
||
<div style="margin-top: 5px;">
|
||
<div style="background: #e0e0e0; height: 6px; border-radius: 3px; overflow: hidden;">
|
||
<div style="background: ${playerColors[index]}; height: 100%; width: ${progress}%; transition: width 0.3s;"></div>
|
||
</div>
|
||
<div style="font-size: 10px; color: #999; margin-top: 2px;">
|
||
${player.score} / ${WINNING_SCORE}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="player-score">${player.score}</div>
|
||
</div>
|
||
`;
|
||
|
||
playersList.appendChild(card);
|
||
});
|
||
|
||
// Обновляем текущего игрока в шапке
|
||
document.getElementById('currentPlayerName').textContent = this.getCurrentPlayer().name;
|
||
|
||
// Обновляем активные задания
|
||
const player = this.getCurrentPlayer();
|
||
const missionBox = document.getElementById('currentMissionBox');
|
||
|
||
if (player.missions.length > 0) {
|
||
let missionsHTML = '';
|
||
player.missions.forEach((mission, index) => {
|
||
const isAtDestination = player.location === mission.to;
|
||
const isAtOrigin = player.location === mission.from;
|
||
|
||
missionsHTML += `
|
||
<div class="active-mission ${isAtDestination ? 'mission-at-destination' : ''}">
|
||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||
<span>${index + 1}.</span>
|
||
<span style="flex: 1; margin-left: 5px;">
|
||
${isAtOrigin ? '📍' : isAtDestination ? '✅' : '🚢'}
|
||
${mission.fromName} ➜ ${mission.toName}
|
||
</span>
|
||
</div>
|
||
<div style="font-size: 12px; color: #666; margin-left: 15px;">
|
||
📦 ${mission.cargo} | 💰 ${mission.reward}
|
||
</div>
|
||
${isAtDestination ? '<div style="color: #27ae60; font-size: 11px; margin-left: 15px;">⚡ Готов к разгрузке!</div>' : ''}
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
missionBox.innerHTML = `
|
||
<div style="margin-bottom: 10px; font-size: 12px; color: #666;">
|
||
Загрузка: ${player.currentWeight}/${SHIP_MAX_WEIGHT} кг | ${player.currentVolume}/${SHIP_MAX_VOLUME} м³
|
||
</div>
|
||
${missionsHTML}
|
||
`;
|
||
} else {
|
||
missionBox.innerHTML = '<p class="no-mission">Корабль пуст</p>';
|
||
}
|
||
|
||
worldMap.draw();
|
||
}
|
||
|
||
getCurrentPlayer() {
|
||
return this.players[this.currentPlayerIndex];
|
||
}
|
||
|
||
showNotification(message) {
|
||
const notification = document.createElement('div');
|
||
notification.style.cssText = `
|
||
position: fixed;
|
||
top: 100px;
|
||
right: 30px;
|
||
background: white;
|
||
padding: 20px 30px;
|
||
border-radius: 12px;
|
||
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
||
z-index: 10000;
|
||
animation: slideInRight 0.5s, slideOutRight 0.5s 3s;
|
||
max-width: 350px;
|
||
`;
|
||
notification.textContent = message;
|
||
document.body.appendChild(notification);
|
||
|
||
// Добавляем CSS анимацию если еще нет
|
||
if (!document.getElementById('notificationStyles')) {
|
||
const style = document.createElement('style');
|
||
style.id = 'notificationStyles';
|
||
style.textContent = `
|
||
@keyframes slideInRight {
|
||
from {
|
||
transform: translateX(400px);
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
transform: translateX(0);
|
||
opacity: 1;
|
||
}
|
||
}
|
||
@keyframes slideOutRight {
|
||
from {
|
||
transform: translateX(0);
|
||
opacity: 1;
|
||
}
|
||
to {
|
||
transform: translateX(400px);
|
||
opacity: 0;
|
||
}
|
||
}
|
||
`;
|
||
document.head.appendChild(style);
|
||
}
|
||
|
||
setTimeout(() => {
|
||
notification.remove();
|
||
}, 3500);
|
||
}
|
||
}
|
||
|
||
// Инициализация игры
|
||
let worldMap, game;
|
||
|
||
window.addEventListener('DOMContentLoaded', () => {
|
||
worldMap = new WorldMap('worldMap');
|
||
game = new Game();
|
||
|
||
console.log('🌍 Geo-Step Game загружена!');
|
||
});
|