JS-Games/space-drifter/game.js
2023-12-02 19:45:57 +01:00

965 lines
28 KiB
JavaScript

// Parameters
const rotationSpeedChange = 0.1;
const movementSpeedChange = 0.01;
const torpedoLaunchSpeed = 3;
const asteroidSpeedCoefficient = 1.5;
const updateFrequency = 1000 / 60;
const dangerousTorpedoTimeout = 1000;
const explosionDuration = 1000;
const explosionMaxRadius = 100;
const explosionMaxWidth = 20;
let score = 0;
let isGameRunning = false;
let isGamePaused = false;
let gameOver = false;
const canvas = document.getElementById("game-canvas");
const ctx = canvas.getContext("2d");
ctx.canvas.width = 1920;
ctx.canvas.height = 1080;
const getDegToRad = (degrees) => degrees * (Math.PI / 180);
const getRadToDeg = (radians) => radians * (180 / Math.PI);
const getDistance = (
{ position: { x: x1, y: y1 } },
{ position: { x: x2, y: y2 } }
) => ((x1 - x2) ** 2 + (y1 - y2) ** 2) ** 0.5;
const getRandomDirection = () => Math.random() * 360;
const getRandomAsteroidPosition = (starship) => {
const distanceFromStarship = ctx.canvas.height / 2;
const randomDirection = getRandomDirection();
let x =
starship.position.x +
Math.cos(getDegToRad(randomDirection)) * distanceFromStarship;
let y =
starship.position.y +
Math.sin(getDegToRad(randomDirection)) * distanceFromStarship;
if (x < 0) {
x += ctx.canvas.width;
}
if (x > ctx.canvas.width) {
x -= ctx.canvas.width;
}
if (y < 0) {
y += ctx.canvas.height;
}
if (y > ctx.canvas.height) {
y -= ctx.canvas.height;
}
return { x, y };
};
let starship;
let torpedoes;
let asteroids;
let explosions;
const starshipImage = new Image();
starshipImage.src = "./assets/starship.svg";
const torpedoImage = new Image();
torpedoImage.src = "./assets/torpedo.svg";
const asteroidBigImage = new Image();
asteroidBigImage.src = "./assets/asteroid-big.svg";
const asteroidMediumImage = new Image();
asteroidMediumImage.src = "./assets/asteroid-medium.svg";
const asteroidSmallImage = new Image();
asteroidSmallImage.src = "./assets/asteroid-small.svg";
const draw = () => {
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw the starship
if (!starship.exploded) {
ctx.save();
ctx.translate(starship.position.x, starship.position.y);
ctx.rotate(-1 * getDegToRad(starship.rotation - 90));
ctx.translate(-starship.position.x, -starship.position.y);
ctx.drawImage(
starshipImage,
starship.position.x - starshipImage.naturalWidth / 2,
starship.position.y - starshipImage.naturalHeight / 2,
starshipImage.naturalWidth,
starshipImage.naturalHeight
);
ctx.restore();
// Left edge
if (starship.position.x <= starshipImage.naturalWidth) {
ctx.save();
ctx.translate(
starship.position.x + ctx.canvas.width,
starship.position.y
);
ctx.rotate(-1 * getDegToRad(starship.rotation - 90));
ctx.translate(-starship.position.x, -starship.position.y);
ctx.drawImage(
starshipImage,
starship.position.x - starshipImage.naturalWidth / 2,
starship.position.y - starshipImage.naturalHeight / 2,
starshipImage.naturalWidth,
starshipImage.naturalHeight
);
ctx.restore();
}
// Right edge
if (starship.position.x >= ctx.canvas.width - starshipImage.naturalWidth) {
ctx.save();
ctx.translate(
starship.position.x - ctx.canvas.width,
starship.position.y
);
ctx.rotate(-1 * getDegToRad(starship.rotation - 90));
ctx.translate(-starship.position.x, -starship.position.y);
ctx.drawImage(
starshipImage,
starship.position.x - starshipImage.naturalWidth / 2,
starship.position.y - starshipImage.naturalHeight / 2,
starshipImage.naturalWidth,
starshipImage.naturalHeight
);
ctx.restore();
}
// Top edge
if (starship.position.y <= starshipImage.naturalHeight) {
ctx.save();
ctx.translate(
starship.position.x,
starship.position.y + ctx.canvas.height
);
ctx.rotate(-1 * getDegToRad(starship.rotation - 90));
ctx.translate(-starship.position.x, -starship.position.y);
ctx.drawImage(
starshipImage,
starship.position.x - starshipImage.naturalWidth / 2,
starship.position.y - starshipImage.naturalHeight / 2,
starshipImage.naturalWidth,
starshipImage.naturalHeight
);
ctx.restore();
}
// Bottom edge
if (
starship.position.y >=
ctx.canvas.height - starshipImage.naturalHeight
) {
ctx.save();
ctx.translate(
starship.position.x,
starship.position.y - ctx.canvas.height
);
ctx.rotate(-1 * getDegToRad(starship.rotation - 90));
ctx.translate(-starship.position.x, -starship.position.y);
ctx.drawImage(
starshipImage,
starship.position.x - starshipImage.naturalWidth / 2,
starship.position.y - starshipImage.naturalHeight / 2,
starshipImage.naturalWidth,
starshipImage.naturalHeight
);
ctx.restore();
}
// Top left corner
if (
starship.position.x <= starshipImage.naturalWidth &&
starship.position.y <= starshipImage.naturalHeight
) {
ctx.save();
ctx.translate(
starship.position.x + ctx.canvas.width,
starship.position.y + ctx.canvas.height
);
ctx.rotate(-1 * getDegToRad(starship.rotation - 90));
ctx.translate(-starship.position.x, -starship.position.y);
ctx.drawImage(
starshipImage,
starship.position.x - starshipImage.naturalWidth / 2,
starship.position.y - starshipImage.naturalHeight / 2,
starshipImage.naturalWidth,
starshipImage.naturalHeight
);
ctx.restore();
}
// Bottom left corner
if (
starship.position.x <= starshipImage.naturalWidth &&
starship.position.y >= ctx.canvas.height - starshipImage.naturalHeight
) {
ctx.save();
ctx.translate(
starship.position.x + ctx.canvas.width,
starship.position.y - ctx.canvas.height
);
ctx.rotate(-1 * getDegToRad(starship.rotation - 90));
ctx.translate(-starship.position.x, -starship.position.y);
ctx.drawImage(
starshipImage,
starship.position.x - starshipImage.naturalWidth / 2,
starship.position.y - starshipImage.naturalHeight / 2,
starshipImage.naturalWidth,
starshipImage.naturalHeight
);
ctx.restore();
}
// Top right corner
if (
starship.position.x >= ctx.canvas.width - starshipImage.naturalWidth &&
starship.position.y <= starshipImage.naturalHeight
) {
ctx.save();
ctx.translate(
starship.position.x - ctx.canvas.width,
starship.position.y + ctx.canvas.height
);
ctx.rotate(-1 * getDegToRad(starship.rotation - 90));
ctx.translate(-starship.position.x, -starship.position.y);
ctx.drawImage(
starshipImage,
starship.position.x - starshipImage.naturalWidth / 2,
starship.position.y - starshipImage.naturalHeight / 2,
starshipImage.naturalWidth,
starshipImage.naturalHeight
);
ctx.restore();
}
// Bottom right corner
if (
starship.position.y >= ctx.canvas.height - starshipImage.naturalHeight &&
starship.position.x >= ctx.canvas.width - starshipImage.naturalWidth
) {
ctx.save();
ctx.translate(
starship.position.x - ctx.canvas.width,
starship.position.y - ctx.canvas.height
);
ctx.rotate(-1 * getDegToRad(starship.rotation - 90));
ctx.translate(-starship.position.x, -starship.position.y);
ctx.drawImage(
starshipImage,
starship.position.x - starshipImage.naturalWidth / 2,
starship.position.y - starshipImage.naturalHeight / 2,
starshipImage.naturalWidth,
starshipImage.naturalHeight
);
ctx.restore();
}
}
// Draw the torpedoes
torpedoes.forEach((torpedo) => {
ctx.drawImage(
torpedoImage,
torpedo.position.x - torpedoImage.naturalWidth / 2,
torpedo.position.y - torpedoImage.naturalHeight / 2,
torpedoImage.naturalWidth,
torpedoImage.naturalHeight
);
});
// Draw the asteroids
asteroids.forEach((asteroid) => {
let asteroidImage;
if (asteroid.size === 2) {
asteroidImage = asteroidBigImage;
}
if (asteroid.size === 1) {
asteroidImage = asteroidMediumImage;
}
if (asteroid.size === 0) {
asteroidImage = asteroidSmallImage;
}
ctx.save();
ctx.translate(asteroid.position.x, asteroid.position.y);
ctx.rotate(-1 * getDegToRad(asteroid.rotation - 90));
ctx.translate(-asteroid.position.x, -asteroid.position.y);
ctx.drawImage(
asteroidImage,
asteroid.position.x - asteroidImage.naturalWidth / 2,
asteroid.position.y - asteroidImage.naturalHeight / 2,
asteroidImage.naturalWidth,
asteroidImage.naturalHeight
);
ctx.restore();
// Left edge
if (asteroid.position.x <= asteroidImage.naturalWidth) {
ctx.save();
ctx.translate(
asteroid.position.x + ctx.canvas.width,
asteroid.position.y
);
ctx.rotate(-1 * getDegToRad(asteroid.rotation - 90));
ctx.translate(-asteroid.position.x, -asteroid.position.y);
ctx.drawImage(
asteroidImage,
asteroid.position.x - asteroidImage.naturalWidth / 2,
asteroid.position.y - asteroidImage.naturalHeight / 2,
asteroidImage.naturalWidth,
asteroidImage.naturalHeight
);
ctx.restore();
}
// Right edge
if (asteroid.position.x >= ctx.canvas.width - asteroidImage.naturalWidth) {
ctx.save();
ctx.translate(
asteroid.position.x - ctx.canvas.width,
asteroid.position.y
);
ctx.rotate(-1 * getDegToRad(asteroid.rotation - 90));
ctx.translate(-asteroid.position.x, -asteroid.position.y);
ctx.drawImage(
asteroidImage,
asteroid.position.x - asteroidImage.naturalWidth / 2,
asteroid.position.y - asteroidImage.naturalHeight / 2,
asteroidImage.naturalWidth,
asteroidImage.naturalHeight
);
ctx.restore();
}
// Top edge
if (asteroid.position.y <= asteroidImage.naturalHeight) {
ctx.save();
ctx.translate(
asteroid.position.x,
asteroid.position.y + ctx.canvas.height
);
ctx.rotate(-1 * getDegToRad(asteroid.rotation - 90));
ctx.translate(-asteroid.position.x, -asteroid.position.y);
ctx.drawImage(
asteroidImage,
asteroid.position.x - asteroidImage.naturalWidth / 2,
asteroid.position.y - asteroidImage.naturalHeight / 2,
asteroidImage.naturalWidth,
asteroidImage.naturalHeight
);
ctx.restore();
}
// Bottom edge
if (
asteroid.position.y >=
ctx.canvas.height - asteroidImage.naturalHeight
) {
ctx.save();
ctx.translate(
asteroid.position.x,
asteroid.position.y - ctx.canvas.height
);
ctx.rotate(-1 * getDegToRad(asteroid.rotation - 90));
ctx.translate(-asteroid.position.x, -asteroid.position.y);
ctx.drawImage(
asteroidImage,
asteroid.position.x - asteroidImage.naturalWidth / 2,
asteroid.position.y - asteroidImage.naturalHeight / 2,
asteroidImage.naturalWidth,
asteroidImage.naturalHeight
);
ctx.restore();
}
// Top left corner
if (
asteroid.position.x <= asteroidImage.naturalWidth &&
asteroid.position.y <= asteroidImage.naturalHeight
) {
ctx.save();
ctx.translate(
asteroid.position.x + ctx.canvas.width,
asteroid.position.y + ctx.canvas.height
);
ctx.rotate(-1 * getDegToRad(asteroid.rotation - 90));
ctx.translate(-asteroid.position.x, -asteroid.position.y);
ctx.drawImage(
asteroidImage,
asteroid.position.x - asteroidImage.naturalWidth / 2,
asteroid.position.y - asteroidImage.naturalHeight / 2,
asteroidImage.naturalWidth,
asteroidImage.naturalHeight
);
ctx.restore();
}
// Bottom left corner
if (
asteroid.position.x <= asteroidImage.naturalWidth &&
asteroid.position.y >= ctx.canvas.height - asteroidImage.naturalHeight
) {
ctx.save();
ctx.translate(
asteroid.position.x + ctx.canvas.width,
asteroid.position.y - ctx.canvas.height
);
ctx.rotate(-1 * getDegToRad(asteroid.rotation - 90));
ctx.translate(-asteroid.position.x, -asteroid.position.y);
ctx.drawImage(
asteroidImage,
asteroid.position.x - asteroidImage.naturalWidth / 2,
asteroid.position.y - asteroidImage.naturalHeight / 2,
asteroidImage.naturalWidth,
asteroidImage.naturalHeight
);
ctx.restore();
}
// Top right corner
if (
asteroid.position.x >= ctx.canvas.width - asteroidImage.naturalWidth &&
asteroid.position.y <= asteroidImage.naturalHeight
) {
ctx.save();
ctx.translate(
asteroid.position.x - ctx.canvas.width,
asteroid.position.y + ctx.canvas.height
);
ctx.rotate(-1 * getDegToRad(asteroid.rotation - 90));
ctx.translate(-asteroid.position.x, -asteroid.position.y);
ctx.drawImage(
asteroidImage,
asteroid.position.x - asteroidImage.naturalWidth / 2,
asteroid.position.y - asteroidImage.naturalHeight / 2,
asteroidImage.naturalWidth,
asteroidImage.naturalHeight
);
ctx.restore();
}
// Bottom right corner
if (
asteroid.position.x >= ctx.canvas.width - asteroidImage.naturalWidth &&
asteroid.position.y >= ctx.canvas.height - asteroidImage.naturalHeight
) {
ctx.save();
ctx.translate(
asteroid.position.x - ctx.canvas.width,
asteroid.position.y - ctx.canvas.height
);
ctx.rotate(-1 * getDegToRad(asteroid.rotation - 90));
ctx.translate(-asteroid.position.x, -asteroid.position.y);
ctx.drawImage(
asteroidImage,
asteroid.position.x - asteroidImage.naturalWidth / 2,
asteroid.position.y - asteroidImage.naturalHeight / 2,
asteroidImage.naturalWidth,
asteroidImage.naturalHeight
);
ctx.restore();
}
});
// Draw the explosions
const now = Date.now();
explosions.forEach((explosion) => {
const explosionProgress = (now - explosion.startedAt) / explosionDuration;
const explosionRadius =
torpedoImage.naturalWidth + explosionProgress * explosionMaxRadius;
ctx.beginPath();
ctx.arc(
explosion.position.x,
explosion.position.y,
explosionRadius,
0,
2 * Math.PI
);
ctx.strokeStyle = `hsla(0, 0%, 100%, ${Math.max(
0.75 * 1 - explosionProgress,
0
)})`;
ctx.lineWidth = 1 + explosionProgress * explosionMaxWidth;
ctx.stroke();
});
// Request next frame
if (isGameRunning && !isGamePaused) {
requestAnimationFrame(draw);
}
};
let rotationMomentumChangeInterval = null;
let forwardSpeedChangeInterval = null;
const fireTorpedo = () => {
starship.canFire = false;
setTimeout(() => {
starship.canFire = true;
}, 500);
const moveVector = {
x:
(Math.cos(getDegToRad(starship.rotation)) || 0) *
(starship.forwardSpeed + torpedoLaunchSpeed),
y:
(Math.sin(getDegToRad(starship.rotation)) || 0) *
(starship.forwardSpeed + torpedoLaunchSpeed),
};
const driftVector = {
x:
(Math.cos(getDegToRad(starship.driftDirection)) || 0) *
starship.driftSpeed,
y:
(Math.sin(getDegToRad(starship.driftDirection)) || 0) *
starship.driftSpeed,
};
const deltaXDrift = driftVector.x + moveVector.x;
const deltaYDrift = driftVector.y + moveVector.y;
let direction;
if (starship.driftSpeed === 0) {
direction = starship.rotation;
} else {
direction =
getRadToDeg(Math.atan(deltaYDrift / deltaXDrift || 0)) +
(deltaXDrift < 0 ? 180 : 0);
}
const speed = (deltaXDrift ** 2 + deltaYDrift ** 2) ** 0.5;
torpedoes.push({
position: {
x: starship.position.x,
y: starship.position.y,
},
direction,
speed,
detonated: false,
launchedAt: Date.now(),
});
};
window.addEventListener("keydown", (event) => {
if (!isGameRunning) {
return;
}
if (event.key === "p" || event.key === "P") {
if (isGamePaused) {
document.getElementById("paused").classList.add("hidden");
isGamePaused = false;
clock = setInterval(tick, updateFrequency);
requestAnimationFrame(draw);
} else {
if (gameOver) {
return;
}
document.getElementById("paused").classList.remove("hidden");
isGamePaused = true;
clearInterval(clock);
}
}
if (isGamePaused) {
return;
}
if (event.key === "ArrowLeft" || event.key === "a" || event.key === "A") {
starship.rotationMomentum += rotationSpeedChange;
clearInterval(rotationMomentumChangeInterval);
rotationMomentumChangeInterval = setInterval(() => {
starship.rotationMomentum += rotationSpeedChange;
}, 50);
}
if (event.key === "ArrowRight" || event.key === "d" || event.key === "D") {
starship.rotationMomentum -= rotationSpeedChange;
clearInterval(rotationMomentumChangeInterval);
rotationMomentumChangeInterval = setInterval(() => {
starship.rotationMomentum -= rotationSpeedChange;
}, 50);
}
if (event.key === "ArrowUp" || event.key === "w" || event.key === "W") {
if (starship.forwardSpeed < 0) {
starship.forwardSpeed = 0;
}
starship.forwardSpeed += movementSpeedChange;
clearInterval(forwardSpeedChangeInterval);
forwardSpeedChangeInterval = setInterval(() => {
starship.forwardSpeed += movementSpeedChange;
}, 50);
}
if (event.key === "ArrowDown" || event.key === "s" || event.key === "S") {
if (starship.forwardSpeed > 0) {
starship.forwardSpeed = 0;
}
starship.forwardSpeed -= movementSpeedChange;
clearInterval(forwardSpeedChangeInterval);
forwardSpeedChangeInterval = setInterval(() => {
starship.forwardSpeed -= movementSpeedChange;
}, 50);
}
if (event.key === " " && starship.canFire) {
fireTorpedo();
}
});
document.addEventListener("click", () => {
if (!isGameRunning) {
return;
}
if (starship.canFire) {
fireTorpedo();
}
});
window.addEventListener("keyup", (event) => {
if (!isGameRunning) {
return;
}
if (event.key === "ArrowLeft" || event.key === "a" || event.key === "A") {
clearInterval(rotationMomentumChangeInterval);
}
if (event.key === "ArrowRight" || event.key === "d" || event.key === "D") {
clearInterval(rotationMomentumChangeInterval);
}
if (event.key === "ArrowUp" || event.key === "w" || event.key === "W") {
clearInterval(forwardSpeedChangeInterval);
starship.forwardSpeed = 0;
}
if (event.key === "ArrowDown" || event.key === "s" || event.key === "S") {
clearInterval(forwardSpeedChangeInterval);
starship.forwardSpeed = 0;
}
});
const tick = () => {
// Move starship
starship.rotation += starship.rotationMomentum;
const moveVector = {
x: (Math.cos(getDegToRad(starship.rotation)) || 0) * starship.forwardSpeed,
y: (Math.sin(getDegToRad(starship.rotation)) || 0) * starship.forwardSpeed,
};
const driftVector = {
x:
(Math.cos(getDegToRad(starship.driftDirection)) || 0) *
starship.driftSpeed,
y:
(Math.sin(getDegToRad(starship.driftDirection)) || 0) *
starship.driftSpeed,
};
const deltaXDrift = driftVector.x + moveVector.x;
const deltaYDrift = driftVector.y + moveVector.y;
if (starship.driftSpeed === 0) {
starship.driftDirection = starship.rotation;
} else {
starship.driftDirection =
getRadToDeg(Math.atan(deltaYDrift / deltaXDrift || 0)) +
(deltaXDrift < 0 ? 180 : 0);
}
starship.driftSpeed = (deltaXDrift ** 2 + deltaYDrift ** 2) ** 0.5;
starship.position.x +=
starship.driftSpeed * Math.cos(getDegToRad(starship.driftDirection)) || 0;
if (starship.position.x < 0) {
starship.position.x += ctx.canvas.width;
}
if (starship.position.x > ctx.canvas.width) {
starship.position.x -= ctx.canvas.width;
}
starship.position.y -=
starship.driftSpeed * Math.sin(getDegToRad(starship.driftDirection)) || 0;
if (starship.position.y < 0) {
starship.position.y += ctx.canvas.height;
}
if (starship.position.y > ctx.canvas.height) {
starship.position.y -= ctx.canvas.height;
}
// Move torpedoes
torpedoes.forEach((torpedo) => {
torpedo.position.x +=
torpedo.speed * Math.cos(getDegToRad(torpedo.direction)) || 0;
if (torpedo.position.x < 0) {
torpedo.position.x += ctx.canvas.width;
}
if (torpedo.position.x > ctx.canvas.width) {
torpedo.position.x -= ctx.canvas.width;
}
torpedo.position.y -=
torpedo.speed * Math.sin(getDegToRad(torpedo.direction)) || 0;
if (torpedo.position.y < 0) {
torpedo.position.y += ctx.canvas.height;
}
if (torpedo.position.y > ctx.canvas.height) {
torpedo.position.y -= ctx.canvas.height;
}
});
// Move asteroids
asteroids.forEach((asteroid) => {
const asteroidSpeed = (3 - asteroid.size) * asteroidSpeedCoefficient;
const deltaPosition = {
x: asteroidSpeed * Math.cos(getDegToRad(asteroid.direction)) || 0,
y: asteroidSpeed * Math.sin(getDegToRad(asteroid.direction)) || 0,
};
asteroid.position.x += deltaPosition.x;
if (asteroid.position.x < 0) {
asteroid.position.x += ctx.canvas.width;
}
if (asteroid.position.x > ctx.canvas.width) {
asteroid.position.x -= ctx.canvas.width;
}
asteroid.position.y -= deltaPosition.y;
if (asteroid.position.y < 0) {
asteroid.position.y += ctx.canvas.height;
}
if (asteroid.position.y > ctx.canvas.height) {
asteroid.position.y -= ctx.canvas.height;
}
asteroid.rotation += asteroidSpeed;
if (asteroid.rotation > 360) {
asteroid.rotation -= 360;
}
});
const now = Date.now();
// Detect torpedo collisions
torpedoes.forEach((torpedo, torpedoIndex) => {
// Torpedo hitting asteroid
asteroids.forEach((asteroid) => {
let asteroidImage;
if (asteroid.size === 2) {
asteroidImage = asteroidBigImage;
}
if (asteroid.size === 1) {
asteroidImage = asteroidMediumImage;
}
if (asteroid.size === 0) {
asteroidImage = asteroidSmallImage;
}
if (getDistance(torpedo, asteroid) <= asteroidImage.naturalWidth / 2) {
torpedo.detonated = true;
asteroid.exploded = true;
explosions.push({
position: {
x: torpedo.position.x,
y: torpedo.position.y,
},
startedAt: now,
});
if (!starship.exploded) {
score++;
}
document.getElementById("score").innerHTML = score;
}
});
// Torpedo hitting another torpedo
torpedoes.forEach((otherTorpedo, otherTorpedoIndex) => {
if (torpedoIndex !== otherTorpedoIndex) {
if (
getDistance(torpedo, otherTorpedo) <=
torpedoImage.naturalWidth / 2
) {
torpedo.detonated = true;
otherTorpedo.detonated = true;
explosions.push({
position: {
x: torpedo.position.x,
y: torpedo.position.y,
},
startedAt: now,
});
explosions.push({
position: {
x: otherTorpedo.position.x,
y: otherTorpedo.position.y,
},
startedAt: now,
});
}
}
});
// Torpedo hitting starship
if (
!torpedo.detonated &&
!starship.exploded &&
now - torpedo.launchedAt > dangerousTorpedoTimeout
) {
if (getDistance(torpedo, starship) <= starshipImage.naturalWidth / 2) {
starship.exploded = true;
torpedo.detonated = true;
explosions.push({
position: {
x: starship.position.x,
y: starship.position.y,
},
startedAt: now,
});
}
}
});
asteroids.forEach((asteroid) => {
if (!asteroid.exploded && !starship.exploded) {
// Asteroid hitting starship
let asteroidImage;
if (asteroid.size === 2) {
asteroidImage = asteroidBigImage;
}
if (asteroid.size === 1) {
asteroidImage = asteroidMediumImage;
}
if (asteroid.size === 0) {
asteroidImage = asteroidSmallImage;
}
if (
getDistance(asteroid, starship) <=
asteroidImage.naturalWidth / 2 + starshipImage.naturalWidth / 2
) {
starship.exploded = true;
explosions.push({
position: {
x: starship.position.x,
y: starship.position.y,
},
startedAt: now,
});
}
}
});
// Get rid of detonated torpedoes
torpedoes = torpedoes.filter((torpedo) => !torpedo.detonated);
// Get rid of invisible explosions
explosions = explosions.filter(
(explosion) => now - explosion.startedAt <= explosionDuration
);
asteroids = asteroids
.map((asteroid) => {
if (!asteroid.exploded) {
return asteroid;
}
// Remove exploded small asteroids
if (asteroid.size === 0) {
return [];
}
// Split exploded asteroids
return [
{
size: asteroid.size - 1,
position: { x: asteroid.position.x, y: asteroid.position.y },
direction: getRandomDirection(),
rotation: getRandomDirection(),
exploded: false,
},
{
size: asteroid.size - 1,
position: { x: asteroid.position.x, y: asteroid.position.y },
direction: getRandomDirection(),
rotation: getRandomDirection(),
exploded: false,
},
];
})
.flat();
// Create new asteroid if the last one has exploded
if (asteroids.length === 0) {
asteroids = [
{
size: 2,
position: getRandomAsteroidPosition(starship),
direction: getRandomDirection(),
rotation: getRandomDirection(),
exploded: false,
},
];
}
// Game over when starship has exploded
if (starship.exploded && !gameOver) {
gameOver = true;
clearInterval(rotationMomentumChangeInterval);
clearInterval(rotationMomentumChangeInterval);
clearInterval(forwardSpeedChangeInterval);
clearInterval(forwardSpeedChangeInterval);
setTimeout(() => {
isGameRunning = false;
clearInterval(clock);
document.getElementById("gameOver").classList.remove("hidden");
document.getElementById("finalScore").innerHTML = score;
document.getElementById("restart").tabIndex = 0;
document.getElementById("restart").focus();
}, explosionDuration * 2);
}
};
const startGame = () => {
// Hide splash screen
document.getElementById("splash").classList.add("hidden");
document.getElementById("start").tabIndex = -1;
document.getElementById("start").blur();
document.getElementById("gameOver").classList.add("hidden");
document.getElementById("restart").tabIndex = -1;
document.getElementById("restart").blur();
// Reset score
score = 0;
document.getElementById("score").innerHTML = "0";
// Reset ship
starship = {
position: {
x: ctx.canvas.width / 2,
y: ctx.canvas.height / 2,
},
rotation: 90,
driftDirection: 0,
driftSpeed: 0,
rotationMomentum: 0,
forwardSpeed: 0,
canFire: true,
exploded: false,
};
// Reset torpedoes
torpedoes = [];
// Reset asteroids
asteroids = [
{
size: 2,
position: getRandomAsteroidPosition(starship),
direction: getRandomDirection(),
rotation: getRandomDirection(),
exploded: false,
},
];
explosions = [];
// Start clock
isGameRunning = true;
isGamePaused = false;
gameOver = false;
clock = setInterval(tick, updateFrequency);
requestAnimationFrame(draw);
};
// Set up splash screen
document.getElementById("start").focus();
document.getElementById("start").addEventListener("click", (event) => {
event.stopPropagation();
startGame();
});
// Set up game over screen
document.getElementById("restart").addEventListener("click", (event) => {
event.stopPropagation();
startGame();
});