space_sim/n.ts
2026-06-17 00:31:14 +07:00

836 lines
36 KiB
TypeScript
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.

<script>
(function () {
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let HEX_SIZE = 58;
function hexToPixel(q, r) {
const x = HEX_SIZE * (Math.sqrt(3) * q + Math.sqrt(3) / 2 * r);
const y = HEX_SIZE * (3 / 2 * r);
return { x, y };
}
// Глобальные переменные для генерации мира
let systems = [];
let axialCoords = []; // будет заполняться динамически
let selectedSystem = null;
let camera = { offsetX: 0, offsetY: 0, zoom: 1.0 };
// Параметры игры
let maxShipSpeed = 520;
let cargoCapacity = 800;
let gameState = {
view: "galaxy",
currentSystemId: null,
targetSystemId: null,
targetPlanet: null,
pendingAutoEnter: false,
ship: {
posX: 0, posY: 0,
velX: 0, velY: 0,
maxSpeed: maxShipSpeed,
accel: 680,
drag: 1.15,
radius: 9
},
inventory: { metal: 0, gas: 0, crystal: 0 },
messages: []
};
// Функция генерации планет
function generatePlanetsForSystem(sysName, targetCount = null) {
const templates = [
{ name: "Вулкания", type: "металлическая", resource: "metal", baseYield: 12, icon: "💎" },
{ name: "Акварис", type: "океаническая", resource: "gas", baseYield: 8, icon: "🌫" },
{ name: "Кристаллис", type: "ледяная", resource: "crystal", baseYield: 10, icon: "🔮" },
{ name: "Терра Нова", type: "землеподобная", resource: "metal", baseYield: 15, icon: "💎" },
{ name: "Газовая бухта", type: "газовый гигант", resource: "gas", baseYield: 20, icon: "🌫" },
{ name: "Кремниевый пояс", type: "астероидное поле", resource: "crystal", baseYield: 9, icon: "🔮" },
{ name: "Инферно", type: "раскалённая", resource: "metal", baseYield: 11, icon: "💎" },
{ name: "Эфир", type: "туманность", resource: "crystal", baseYield: 14, icon: "🔮" }
];
let planetsCount = targetCount !== null ? targetCount : (3 + Math.floor(Math.random() * 4));
let shuffled = [...templates];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
let planets = [];
for (let i = 0; i < planetsCount; i++) {
let tpl = shuffled[i % shuffled.length];
let orbitRadius = 70 + i * 40 + Math.random() * 20;
let angle = (i * 97) % 360;
planets.push({
id: `${sysName}_p${i}`,
name: tpl.name + " " + (i + 1),
type: tpl.type,
resource: tpl.resource,
yield: tpl.baseYield + Math.floor(Math.random() * 7),
orbitRadius: orbitRadius,
angle: angle,
icon: tpl.icon,
posX: 0, posY: 0
});
}
return planets;
}
// Генерация гексагональной сетки с заданным числом систем
function generateHexGrid(systemsCount) {
// Генерируем координаты по спирали или просто кольцами. Используем простой метод: создаём список осевых координат вокруг центра.
let coords = [{ q: 0, r: 0, name: "SOL PRIME" }];
let names = ["ORIONIS", "SIRIUS", "ANTARES", "RIGEL", "VEGA", "ALTAIR", "THUBAN", "POLARIS", "BETELGEUSE", "CENTAURI", "DRACO", "LYRA", "AQUILA", "CYGNUS", "ANDROMEDA"];
let ring = 1;
while (coords.length < systemsCount) {
for (let i = -ring; i <= ring; i++) {
for (let j = -ring; j <= ring; j++) {
let k = -i - j;
if (Math.max(Math.abs(i), Math.abs(j), Math.abs(k)) === ring) {
if (coords.length < systemsCount && !coords.some(c => c.q === i && c.r === j)) {
let idx = coords.length % names.length;
coords.push({ q: i, r: j, name: names[idx] + (ring) });
}
}
}
}
ring++;
}
return coords;
}
// Построение систем (полная перегенерация)
function rebuildWorld(systemsCount, planetsPerSystem = null) {
axialCoords = generateHexGrid(systemsCount);
let newSystems = [];
for (let idx = 0; idx < axialCoords.length; idx++) {
let a = axialCoords[idx];
let pixel = hexToPixel(a.q, a.r);
let systemId = `sys_${idx}`;
let planets = generatePlanetsForSystem(a.name, planetsPerSystem);
newSystems.push({
id: systemId,
name: a.name,
q: a.q,
r: a.r,
x: pixel.x + canvas.width / 2,
y: pixel.y + canvas.height / 2,
planets: planets,
visited: false
});
}
// Вычисляем соседей
for (let sys of newSystems) {
sys.neighbors = [];
for (let other of newSystems) {
if (sys.id === other.id) continue;
let dq = Math.abs(sys.q - other.q);
let dr = Math.abs(sys.r - other.r);
let ds = Math.abs((sys.q + sys.r) - (other.q + other.r));
let dist = Math.max(dq, dr, ds);
if (dist === 1) sys.neighbors.push(other.id);
}
}
systems = newSystems;
// Если текущая система не существует, выбираем первую
if (!systems.find(s => s.id === gameState.currentSystemId)) {
gameState.currentSystemId = systems[0].id;
gameState.ship.posX = systems[0].x;
gameState.ship.posY = systems[0].y;
} else {
let curSys = systems.find(s => s.id === gameState.currentSystemId);
if (curSys) {
gameState.ship.posX = curSys.x;
gameState.ship.posY = curSys.y;
}
}
gameState.targetSystemId = null;
gameState.targetPlanet = null;
if (gameState.view === "system") gameState.view = "galaxy";
selectedSystem = null;
updateSystemInfoPanel();
addMessage(`🌌 Вселенная обновлена: ${systems.length} систем, настройки планет применены.`);
updateUIStats();
}
// Обновить планеты в выбранной системе (или текущей)
function updatePlanetsInSelectedSystem(planetCount) {
let targetSys = selectedSystem;
if (!targetSys && gameState.view === "system") targetSys = getCurrentSystem();
if (!targetSys) {
addMessage("Сначала выберите систему (кликните на карте)", true);
return;
}
let newPlanets = generatePlanetsForSystem(targetSys.name, planetCount);
targetSys.planets = newPlanets;
if (gameState.view === "system" && gameState.currentSystemId === targetSys.id) {
// Если мы внутри этой системы — телепортируем корабль в центр, чтобы избежать конфликтов
gameState.ship.posX = 0;
gameState.ship.posY = 0;
gameState.ship.velX = 0;
gameState.ship.velY = 0;
gameState.targetPlanet = null;
}
updateSystemInfoPanel(); // обновить отображение в панели
addMessage(`🪐 Планеты системы ${targetSys.name} обновлены (${newPlanets.length} шт.)`);
}
// Ограничение по вместимости
function applyCapacityLimit() {
if (gameState.inventory.metal > cargoCapacity) gameState.inventory.metal = cargoCapacity;
if (gameState.inventory.gas > cargoCapacity) gameState.inventory.gas = cargoCapacity;
if (gameState.inventory.crystal > cargoCapacity) gameState.inventory.crystal = cargoCapacity;
document.getElementById('metalCap').innerText = cargoCapacity;
document.getElementById('gasCap').innerText = cargoCapacity;
document.getElementById('crystalCap').innerText = cargoCapacity;
}
function addMessage(msg, isError = false) {
gameState.messages.unshift(msg);
if (gameState.messages.length > 3) gameState.messages.pop();
let msgDiv = document.getElementById('gameMsg');
msgDiv.innerHTML = `🛸 ${msg}`;
if (isError) msgDiv.style.borderRightColor = "#ff8866";
else msgDiv.style.borderRightColor = "#2e8bc0";
setTimeout(() => {
if (document.getElementById('gameMsg').innerHTML === `🛸 ${msg}`)
msgDiv.style.borderRightColor = "#2e8bc0";
}, 2000);
}
function updateUIStats() {
document.getElementById('metalCount').innerText = gameState.inventory.metal;
document.getElementById('gasCount').innerText = gameState.inventory.gas;
document.getElementById('crystalCount').innerText = gameState.inventory.crystal;
let spd = Math.hypot(gameState.ship.velX, gameState.ship.velY);
document.getElementById('speedVal').innerText = spd.toFixed(1);
let statusText = "В ГАЛАКТИКЕ";
if (gameState.view === "system") statusText = `В СИСТЕМЕ ${getCurrentSystem().name}`;
if (gameState.targetSystemId) statusText = "ПЕРЕЛЁТ ...";
if (gameState.targetPlanet) statusText = "К ПЛАНЕТЕ";
document.getElementById('shipStatus').innerHTML = statusText;
applyCapacityLimit();
}
function getCurrentSystem() {
return systems.find(s => s.id === gameState.currentSystemId);
}
function updatePlanetPositions(system) {
if (!system) return;
let now = Date.now() / 1000;
for (let p of system.planets) {
let ang = (p.angle + now * 0.45) % 360;
let rad = p.orbitRadius;
p.posX = Math.cos(ang * Math.PI / 180) * rad;
p.posY = Math.sin(ang * Math.PI / 180) * rad;
}
}
const DOCK_DIST = 22;
const DOCK_ZONE = 40;
function handleSmoothDocking(deltaSec) {
if (gameState.view !== "system") return;
let sys = getCurrentSystem();
if (!sys) return;
for (let planet of sys.planets) {
let dx = planet.posX - gameState.ship.posX;
let dy = planet.posY - gameState.ship.posY;
let dist = Math.hypot(dx, dy);
if (dist < DOCK_ZONE) {
let angle = Math.atan2(dy, dx);
let targetX = planet.posX - Math.cos(angle) * DOCK_DIST;
let targetY = planet.posY - Math.sin(angle) * DOCK_DIST;
let toTargetX = targetX - gameState.ship.posX;
let toTargetY = targetY - gameState.ship.posY;
let force = 4.5 * deltaSec;
if (force > 0.8) force = 0.8;
gameState.ship.velX += toTargetX * force;
gameState.ship.velY += toTargetY * force;
let distToTarget = Math.hypot(toTargetX, toTargetY);
if (distToTarget < 3) {
gameState.ship.velX *= 0.95;
gameState.ship.velY *= 0.95;
}
}
}
}
function updatePhysics(deltaSec) {
if (deltaSec > 0.033) deltaSec = 0.033;
const ship = gameState.ship;
let targetX = null, targetY = null;
let isMoving = false;
if (gameState.view === "system") {
let sys = getCurrentSystem();
if (sys) updatePlanetPositions(sys);
handleSmoothDocking(deltaSec);
}
if (gameState.view === "galaxy" && gameState.targetSystemId) {
let targetSys = systems.find(s => s.id === gameState.targetSystemId);
if (targetSys) {
targetX = targetSys.x;
targetY = targetSys.y;
isMoving = true;
let dx = targetX - ship.posX;
let dy = targetY - ship.posY;
let dist = Math.hypot(dx, dy);
if (dist < 15) {
ship.posX = targetSys.x;
ship.posY = targetSys.y;
ship.velX = 0;
ship.velY = 0;
gameState.currentSystemId = targetSys.id;
gameState.targetSystemId = null;
addMessage(`✧ Прибыли в систему ${targetSys.name}`);
targetSys.visited = true;
if (gameState.pendingAutoEnter) {
gameState.pendingAutoEnter = false;
gameState.view = "system";
gameState.targetPlanet = null;
gameState.ship.posX = 0;
gameState.ship.posY = 0;
gameState.ship.velX = 0;
gameState.ship.velY = 0;
addMessage(`✨ Погружение в систему ${targetSys.name}.`);
}
updateUIStats();
isMoving = false;
}
} else {
gameState.targetSystemId = null;
}
}
else if (gameState.view === "system" && gameState.targetPlanet) {
let sys = getCurrentSystem();
if (sys && gameState.targetPlanet) {
let freshPlanet = sys.planets.find(p => p.id === gameState.targetPlanet.id);
if (freshPlanet) {
gameState.targetPlanet = freshPlanet;
targetX = freshPlanet.posX;
targetY = freshPlanet.posY;
isMoving = true;
let dx = targetX - ship.posX;
let dy = targetY - ship.posY;
let dist = Math.hypot(dx, dy);
if (dist < DOCK_DIST + 5) {
let angle = Math.atan2(dy, dx);
let finalX = freshPlanet.posX - Math.cos(angle) * DOCK_DIST;
let finalY = freshPlanet.posY - Math.sin(angle) * DOCK_DIST;
ship.posX = finalX;
ship.posY = finalY;
ship.velX = 0;
ship.velY = 0;
addMessage(`🔻 Корабль на орбите ${freshPlanet.name}`);
gameState.targetPlanet = null;
isMoving = false;
}
} else {
gameState.targetPlanet = null;
}
} else {
gameState.targetPlanet = null;
}
}
if (isMoving && targetX !== null && targetY !== null) {
let dx = targetX - ship.posX;
let dy = targetY - ship.posY;
let dist = Math.hypot(dx, dy);
if (dist > 0.5) {
let dirX = dx / dist;
let dirY = dy / dist;
let thrust = gameState.ship.accel * deltaSec;
ship.velX += dirX * thrust;
ship.velY += dirY * thrust;
}
let spd = Math.hypot(ship.velX, ship.velY);
if (spd > gameState.ship.maxSpeed) {
ship.velX = (ship.velX / spd) * gameState.ship.maxSpeed;
ship.velY = (ship.velY / spd) * gameState.ship.maxSpeed;
}
}
let dragForce = 1 - (gameState.ship.drag * deltaSec);
if (dragForce < 0) dragForce = 0;
ship.velX *= dragForce;
ship.velY *= dragForce;
ship.posX += ship.velX * deltaSec;
ship.posY += ship.velY * deltaSec;
if (gameState.view === "galaxy") {
ship.posX = Math.min(Math.max(ship.posX, -400), canvas.width + 400);
ship.posY = Math.min(Math.max(ship.posY, -300), canvas.height + 300);
}
if (gameState.view === "system") {
let limit = 950;
ship.posX = Math.min(Math.max(ship.posX, -limit), limit);
ship.posY = Math.min(Math.max(ship.posY, -limit), limit);
}
updateUIStats();
}
function teleportToPlanet(system, planet) {
if (gameState.currentSystemId !== system.id && gameState.targetSystemId !== system.id) {
gameState.ship.posX = system.x;
gameState.ship.posY = system.y;
gameState.currentSystemId = system.id;
gameState.targetSystemId = null;
addMessage(`📡 Гиперпрыжок в систему ${system.name}`);
}
if (gameState.view !== "system") {
gameState.view = "system";
gameState.targetPlanet = null;
gameState.pendingAutoEnter = false;
addMessage(`✨ Вход в систему ${system.name}`);
}
updatePlanetPositions(system);
let angleToPlanet = Math.atan2(planet.posY, planet.posX);
let orbitX = planet.posX - Math.cos(angleToPlanet) * DOCK_DIST;
let orbitY = planet.posY - Math.sin(angleToPlanet) * DOCK_DIST;
gameState.ship.posX = orbitX;
gameState.ship.posY = orbitY;
gameState.ship.velX = 0;
gameState.ship.velY = 0;
gameState.targetPlanet = null;
addMessage(`🪐 Телепорт на орбиту ${planet.name} (${planet.type})`);
updateUIStats();
}
function updateSystemInfoPanel() {
const panel = document.getElementById('systemInfoPanel');
if (gameState.view !== "galaxy" || !selectedSystem) {
panel.style.display = 'none';
return;
}
panel.style.display = 'block';
document.getElementById('infoSysName').innerHTML = `🌟 ${selectedSystem.name}`;
const container = document.getElementById('planetListContainer');
container.innerHTML = '';
for (let planet of selectedSystem.planets) {
const badge = document.createElement('div');
badge.className = 'planet-badge';
let resourceIcon = "";
if (planet.resource === 'metal') resourceIcon = "💎";
else if (planet.resource === 'gas') resourceIcon = "🌫";
else resourceIcon = "🔮";
badge.innerHTML = `${resourceIcon} ${planet.name} (${planet.type}) +${planet.yield}`;
badge.addEventListener('click', (e) => {
e.stopPropagation();
teleportToPlanet(selectedSystem, planet);
});
container.appendChild(badge);
}
}
function getMouseCanvasCoords(e) {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
let mouseX = (e.clientX - rect.left) * scaleX;
let mouseY = (e.clientY - rect.top) * scaleY;
if (gameState.view === "galaxy") {
let worldX = (mouseX - camera.offsetX) / camera.zoom;
let worldY = (mouseY - camera.offsetY) / camera.zoom;
return { worldX, worldY, mouseX, mouseY };
} else {
return { worldX: mouseX, worldY: mouseY, mouseX, mouseY };
}
}
function findSystemAt(worldX, worldY) {
for (let sys of systems) {
let dx = sys.x - worldX;
let dy = sys.y - worldY;
let dist = Math.hypot(dx, dy);
if (dist < HEX_SIZE * 0.85) return sys;
}
return null;
}
function handleGalaxyClick(e) {
let { worldX, worldY } = getMouseCanvasCoords(e);
let system = findSystemAt(worldX, worldY);
if (system) {
selectedSystem = system;
updateSystemInfoPanel();
addMessage(`🔍 Выбрана система ${system.name}. В панели планеты — кликните для телепорта.`);
} else {
selectedSystem = null;
updateSystemInfoPanel();
}
}
function handleSystemClick(e) {
const rect = canvas.getBoundingClientRect();
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
let mouseCanvasX = (e.clientX - rect.left) * scaleX;
let mouseCanvasY = (e.clientY - rect.top) * scaleY;
let sys = getCurrentSystem();
if (!sys) return;
updatePlanetPositions(sys);
for (let planet of sys.planets) {
let screenX = canvas.width / 2 + planet.posX;
let screenY = canvas.height / 2 + planet.posY;
let dx = mouseCanvasX - screenX;
let dy = mouseCanvasY - screenY;
if (Math.hypot(dx, dy) < 28) {
if (gameState.targetPlanet === planet) {
addMessage(`Уже направляемся к ${planet.name}`);
return;
}
gameState.targetPlanet = planet;
gameState.ship.velX = 0;
gameState.ship.velY = 0;
addMessage(`🪐 Курс на ${planet.name}`);
return;
}
}
}
function tryMine() {
if (gameState.view !== "system") {
addMessage("❌ Добыча доступна только внутри системы!", true);
return;
}
let sys = getCurrentSystem();
if (!sys) return;
let ship = gameState.ship;
let closePlanet = null;
for (let p of sys.planets) {
let dx = ship.posX - p.posX;
let dy = ship.posY - p.posY;
let dist = Math.hypot(dx, dy);
if (dist < 40) {
closePlanet = p;
break;
}
}
if (!closePlanet) {
addMessage("🌍 Нет планеты в зоне добычи. Используйте телепорт из панели или подлетите.", true);
return;
}
let yieldAmount = closePlanet.yield + Math.floor(Math.random() * 8);
let resource = closePlanet.resource;
let newVal = gameState.inventory[resource] + yieldAmount;
if (newVal > cargoCapacity) newVal = cargoCapacity;
gameState.inventory[resource] = newVal;
addMessage(`⛏ Добыто ${yieldAmount} ${resource === 'metal' ? 'металла' : (resource === 'gas' ? 'газа' : 'кристаллов')} с ${closePlanet.name}`);
updateUIStats();
}
function enterSystemWithAutoFlight() {
if (gameState.view !== "galaxy") {
addMessage("Вы уже внутри системы", true);
return;
}
let targetSys = selectedSystem;
if (!targetSys) {
addMessage("Кликните на систему, чтобы выбрать.", true);
return;
}
if (gameState.targetSystemId === targetSys.id) {
addMessage(`Перелёт к ${targetSys.name} уже идёт.`);
gameState.pendingAutoEnter = true;
return;
}
let distToSys = Math.hypot(gameState.ship.posX - targetSys.x, gameState.ship.posY - targetSys.y);
if (gameState.currentSystemId === targetSys.id || distToSys < 20) {
gameState.view = "system";
gameState.targetPlanet = null;
gameState.targetSystemId = null;
gameState.ship.posX = 0;
gameState.ship.posY = 0;
gameState.ship.velX = 0;
gameState.ship.velY = 0;
addMessage(`✨ Погружение в ${targetSys.name}`);
updateUIStats();
return;
}
gameState.targetSystemId = targetSys.id;
gameState.pendingAutoEnter = true;
addMessage(`🚀 Курс на ${targetSys.name}, после прибытия вход авто.`);
}
function exitToGalaxy() {
if (gameState.view !== "system") {
addMessage("Уже в галактике", true);
return;
}
let currentSys = getCurrentSystem();
if (currentSys) {
gameState.view = "galaxy";
gameState.targetPlanet = null;
gameState.targetSystemId = null;
gameState.pendingAutoEnter = false;
gameState.ship.posX = currentSys.x;
gameState.ship.posY = currentSys.y;
gameState.ship.velX = 0;
gameState.ship.velY = 0;
addMessage(`🌌 Выход к системе ${currentSys.name}`);
updateUIStats();
selectedSystem = currentSys;
updateSystemInfoPanel();
}
}
// Настройки UI
const speedSlider = document.getElementById('speedSlider');
const speedValue = document.getElementById('speedValue');
const capacitySlider = document.getElementById('capacitySlider');
const capacityValue = document.getElementById('capacityValue');
const systemsCountSlider = document.getElementById('systemsCountSlider');
const systemsCountValue = document.getElementById('systemsCountValue');
const planetCountSlider = document.getElementById('planetCountSlider');
const planetCountValue = document.getElementById('planetCountValue');
const regenerateWorldBtn = document.getElementById('regenerateWorldBtn');
const updatePlanetsBtn = document.getElementById('updatePlanetsBtn');
speedSlider.addEventListener('input', () => {
maxShipSpeed = parseInt(speedSlider.value);
speedValue.innerText = maxShipSpeed;
gameState.ship.maxSpeed = maxShipSpeed;
addMessage(`Максимальная скорость изменена на ${maxShipSpeed}`);
});
capacitySlider.addEventListener('input', () => {
cargoCapacity = parseInt(capacitySlider.value);
capacityValue.innerText = cargoCapacity;
applyCapacityLimit();
addMessage(`Вместимость хранилища: ${cargoCapacity}`);
});
systemsCountSlider.addEventListener('input', () => {
systemsCountValue.innerText = systemsCountSlider.value;
});
planetCountSlider.addEventListener('input', () => {
planetCountValue.innerText = planetCountSlider.value;
});
regenerateWorldBtn.addEventListener('click', () => {
let newCount = parseInt(systemsCountSlider.value);
let planetsCount = parseInt(planetCountSlider.value);
rebuildWorld(newCount, planetsCount);
});
updatePlanetsBtn.addEventListener('click', () => {
let planetsCount = parseInt(planetCountSlider.value);
updatePlanetsInSelectedSystem(planetsCount);
});
// Инициализация мира
function initWorld() {
let sysCount = parseInt(systemsCountSlider.value);
let planetsPer = parseInt(planetCountSlider.value);
axialCoords = generateHexGrid(sysCount);
rebuildWorld(sysCount, planetsPer);
}
// Отрисовка
function drawGalaxy() {
ctx.save();
ctx.translate(camera.offsetX, camera.offsetY);
ctx.scale(camera.zoom, camera.zoom);
ctx.fillStyle = "#010008";
ctx.fillRect(-camera.offsetX / camera.zoom, -camera.offsetY / camera.zoom, canvas.width / camera.zoom, canvas.height / camera.zoom);
for (let i = 0; i < 800; i++) {
ctx.fillStyle = `rgba(255,240,200,${Math.random() * 0.6 + 0.2})`;
ctx.beginPath();
ctx.arc((i * 131) % canvas.width - camera.offsetX / camera.zoom, (i * 253) % canvas.height - camera.offsetY / camera.zoom, 1 + Math.random() * 2, 0, Math.PI * 2);
ctx.fill();
}
for (let sys of systems) {
for (let nId of sys.neighbors) {
let neigh = systems.find(s => s.id === nId);
if (neigh && sys.id < neigh.id) {
ctx.beginPath();
ctx.moveTo(sys.x, sys.y);
ctx.lineTo(neigh.x, neigh.y);
ctx.strokeStyle = "#4f7fbf";
ctx.lineWidth = 2.5;
ctx.stroke();
}
}
}
for (let sys of systems) {
let angles = [];
for (let i = 0; i < 6; i++) {
let ang = i * Math.PI * 2 / 6 - Math.PI / 6;
let x = sys.x + Math.cos(ang) * HEX_SIZE;
let y = sys.y + Math.sin(ang) * HEX_SIZE;
angles.push({ x, y });
}
ctx.beginPath();
ctx.moveTo(angles[0].x, angles[0].y);
for (let i = 1; i < 6; i++) ctx.lineTo(angles[i].x, angles[i].y);
ctx.closePath();
ctx.fillStyle = "#071a2be6";
ctx.fill();
ctx.strokeStyle = "#3e7bb3";
ctx.lineWidth = 1.8;
ctx.stroke();
let gradient = ctx.createRadialGradient(sys.x - 4, sys.y - 4, 5, sys.x, sys.y, 20);
gradient.addColorStop(0, '#ffdd88');
gradient.addColorStop(1, '#aa7733');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(sys.x, sys.y, 13, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = "white";
ctx.font = `bold ${14}px "Orbitron"`;
ctx.shadowBlur = 4;
ctx.fillText(sys.name, sys.x - 28, sys.y - 15);
ctx.fillStyle = "#bbddff";
ctx.font = `10px monospace`;
ctx.fillText(`🪐${sys.planets.length}`, sys.x + 10, sys.y - 8);
if (selectedSystem && selectedSystem.id === sys.id) {
ctx.beginPath();
ctx.arc(sys.x, sys.y, HEX_SIZE * 0.9, 0, 2 * Math.PI);
ctx.strokeStyle = "#ffcc44";
ctx.lineWidth = 3;
ctx.setLineDash([6, 8]);
ctx.stroke();
ctx.setLineDash([]);
}
}
ctx.shadowBlur = 6;
ctx.beginPath();
ctx.arc(gameState.ship.posX, gameState.ship.posY, 9, 0, 2 * Math.PI);
ctx.fillStyle = "#6fcbff";
ctx.fill();
ctx.fillStyle = "white";
ctx.beginPath();
ctx.moveTo(gameState.ship.posX + 12, gameState.ship.posY);
ctx.lineTo(gameState.ship.posX + 18, gameState.ship.posY - 4);
ctx.lineTo(gameState.ship.posX + 18, gameState.ship.posY + 4);
ctx.fill();
ctx.fillStyle = "#c8ffff";
ctx.font = "bold 12px monospace";
ctx.fillText("⚡", gameState.ship.posX - 4, gameState.ship.posY - 4);
ctx.restore();
}
function drawSystemView() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
let sys = getCurrentSystem();
if (!sys) return;
updatePlanetPositions(sys);
ctx.fillStyle = "#030318";
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < 300; i++) {
ctx.fillStyle = `rgba(255,210,150,0.5)`;
ctx.beginPath();
ctx.arc((i * 97) % canvas.width, (i * 353) % canvas.height, 1.2, 0, Math.PI * 2);
ctx.fill();
}
let grad = ctx.createRadialGradient(canvas.width / 2 - 20, canvas.height / 2 - 20, 10, canvas.width / 2, canvas.height / 2, 70);
grad.addColorStop(0, '#ffaa55');
grad.addColorStop(1, '#cc4400');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(canvas.width / 2, canvas.height / 2, 45, 0, 2 * Math.PI);
ctx.fill();
for (let p of sys.planets) {
let x = canvas.width / 2 + p.posX;
let y = canvas.height / 2 + p.posY;
ctx.beginPath();
ctx.arc(x, y, 14, 0, 2 * Math.PI);
ctx.fillStyle = p.resource === 'metal' ? "#a07a4a" : (p.resource === 'gas' ? "#7fbbbb" : "#c9a0dc");
ctx.fill();
ctx.fillStyle = "#eef4ff";
ctx.font = "10px monospace";
ctx.fillText(p.name, x - 18, y - 12);
ctx.fillStyle = "#ddddaa";
ctx.fillText(`${p.type.substring(0, 3)}`, x - 10, y + 18);
if (gameState.targetPlanet && gameState.targetPlanet.id === p.id) {
ctx.beginPath();
ctx.arc(x, y, 22, 0, 2 * Math.PI);
ctx.strokeStyle = "#ffaa44";
ctx.lineWidth = 2.5;
ctx.setLineDash([4, 6]);
ctx.stroke();
ctx.setLineDash([]);
}
}
let shipX = canvas.width / 2 + gameState.ship.posX;
let shipY = canvas.height / 2 + gameState.ship.posY;
ctx.beginPath();
ctx.arc(shipX, shipY, 10, 0, 2 * Math.PI);
ctx.fillStyle = "#4ad4ff";
ctx.fill();
ctx.fillStyle = "white";
ctx.beginPath();
ctx.moveTo(shipX + 14, shipY);
ctx.lineTo(shipX + 22, shipY - 5);
ctx.lineTo(shipX + 22, shipY + 5);
ctx.fill();
if (gameState.targetPlanet) {
ctx.beginPath();
ctx.moveTo(shipX, shipY);
let tarX = canvas.width / 2 + gameState.targetPlanet.posX;
let tarY = canvas.height / 2 + gameState.targetPlanet.posY;
ctx.lineTo(tarX, tarY);
ctx.strokeStyle = "#ffaa66";
ctx.setLineDash([8, 6]);
ctx.stroke();
ctx.setLineDash([]);
}
ctx.font = "11px monospace";
ctx.fillStyle = "#bbddff";
ctx.fillText("Клик по планете — полёт. Панель в галактике — телепорт", 20, 40);
}
let lastTime = performance.now();
function animate() {
let now = performance.now();
let delta = Math.min(0.033, (now - lastTime) / 1000);
if (delta > 0.001) updatePhysics(delta);
lastTime = now;
if (gameState.view === "galaxy") {
let targetOffsetX = canvas.width / 2 - gameState.ship.posX * camera.zoom;
let targetOffsetY = canvas.height / 2 - gameState.ship.posY * camera.zoom;
camera.offsetX = targetOffsetX;
camera.offsetY = targetOffsetY;
drawGalaxy();
} else {
drawSystemView();
}
requestAnimationFrame(animate);
}
function init() {
initWorld();
document.getElementById('enterSystemBtn').addEventListener('click', enterSystemWithAutoFlight);
document.getElementById('exitToGalaxyBtn').addEventListener('click', exitToGalaxy);
document.getElementById('mineBtn').addEventListener('click', tryMine);
canvas.addEventListener('click', (e) => {
if (gameState.view === "galaxy") handleGalaxyClick(e);
else if (gameState.view === "system") handleSystemClick(e);
});
canvas.addEventListener('wheel', (e) => {
if (gameState.view !== "galaxy") return;
e.preventDefault();
let delta = e.deltaY > 0 ? 0.9 : 1.1;
let newZoom = camera.zoom * delta;
newZoom = Math.min(Math.max(newZoom, 0.5), 2.2);
camera.zoom = newZoom;
});
canvas.addEventListener('dblclick', (e) => {
if (gameState.view !== "galaxy") return;
let { worldX, worldY } = getMouseCanvasCoords(e);
let sys = findSystemAt(worldX, worldY);
if (sys) {
selectedSystem = sys;
updateSystemInfoPanel();
enterSystemWithAutoFlight();
}
});
updateUIStats();
addMessage("Добро пожаловать! Настройте скорость и мир под себя.");
animate();
}
init();
})();
</script>