You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

390 lines
10 KiB
JavaScript

const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const arenaSize = 500;
const subSquareSize = arenaSize / 3;
const clockRadius = subSquareSize / 9;
const playerSize = subSquareSize / 10;
const handLength = clockRadius;
const triangleOpacity = 0.5;
let rotationStep = 0;
const maxRotationSteps = 6;
let rotationStarted = false;
let rotationComplete = false;
let randomLayout = Math.floor(Math.random() * 5);
let lastRotationTime = performance.now();
let gameStopped = false;
let player = null;
let clocks = null;
let deathImage = new Image();
// Set the source of the image
deathImage.src = "../images/vuln-stack.png";
function rotateClocks(elapsedTime) {
if (rotationStep < maxRotationSteps && elapsedTime >= 1000) {
clocks.forEach(clock => clock.rotateHand());
rotationStep++;
lastRotationTime = performance.now();
} else if (rotationStep === maxRotationSteps) {
rotationComplete = true;
}
}
function resetRotation() {
rotationStep = 0;
rotationComplete = false;
}
class Clock {
constructor(x, y, handDirection) {
this.x = x;
this.y = y;
this.handDirection = handDirection;
}
drawArrow() {
ctx.lineWidth = 1;
ctx.strokeStyle = "black";
// Draw the line
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(
this.x + handLength * Math.cos(this.handDirection * Math.PI / 180),
this.y + handLength * Math.sin(this.handDirection * Math.PI / 180)
);
ctx.stroke();
// Draw the arrowhead
var arrowSize = 7; // Change the value to adjust the size of the arrowhead
var arrowAngle = Math.PI / 9; // Change the value to adjust the angle of the arrowhead
var angle = Math.atan2(handLength * Math.sin(this.handDirection * Math.PI / 180), handLength * Math.cos(this.handDirection * Math.PI / 180));
ctx.beginPath();
ctx.moveTo(
this.x + handLength * Math.cos(this.handDirection * Math.PI / 180),
this.y + handLength * Math.sin(this.handDirection * Math.PI / 180)
);
ctx.lineTo(
this.x + (handLength - arrowSize) * Math.cos(angle - arrowAngle),
this.y + (handLength - arrowSize) * Math.sin(angle - arrowAngle)
);
ctx.lineTo(
this.x + (handLength - arrowSize) * Math.cos(angle + arrowAngle),
this.y + (handLength - arrowSize) * Math.sin(angle + arrowAngle)
);
ctx.closePath();
ctx.fillStyle = "red"; // Set the fill color to red
ctx.fill();
}
draw() {
ctx.beginPath();
ctx.arc(this.x, this.y, clockRadius, 0, 2 * Math.PI);
ctx.fillStyle = "gold";
ctx.fill();
ctx.strokeStyle = "orange";
ctx.lineWidth = 3;
ctx.stroke();
this.drawArrow();
ctx.stroke();
}
rotateHand() {
this.handDirection = (this.handDirection + 90) % 360;
}
drawConal() {
const angle = 45;
const sideLength = 50;
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(
this.x + sideLength * handLength * Math.cos((this.handDirection - angle) * Math.PI / 180),
this.y + sideLength * handLength * Math.sin((this.handDirection - angle) * Math.PI / 180)
);
ctx.lineTo(
this.x + sideLength * handLength * Math.cos((this.handDirection + angle) * Math.PI / 180),
this.y + sideLength * handLength * Math.sin((this.handDirection + angle) * Math.PI / 180)
);
ctx.closePath();
ctx.fillStyle = `rgba(255, 255, 0, ${triangleOpacity})`;
ctx.fill();
// Draw red lines
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(
this.x + sideLength * handLength * Math.cos((this.handDirection - angle) * Math.PI / 180),
this.y + sideLength * handLength * Math.sin((this.handDirection - angle) * Math.PI / 180)
);
ctx.strokeStyle = 'red';
ctx.stroke();
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(
this.x + sideLength * handLength * Math.cos((this.handDirection + angle) * Math.PI / 180),
this.y + sideLength * handLength * Math.sin((this.handDirection + angle) * Math.PI / 180)
);
ctx.strokeStyle = 'red';
ctx.stroke();
ctx.strokeStyle = 'black';
}
}
const keysPressed = {
ArrowUp: false, W: false, w: false,
ArrowLeft: false, A: false, a: false,
ArrowDown: false, S: false, s: false,
ArrowRight: false, D: false, d: false
};
class Player {
constructor(x, y) {
this.x = x;
this.y = y;
this.hasMoved = false;
}
draw() {
ctx.fillStyle = 'blue';
ctx.fillRect(this.x - playerSize / 2, this.y - playerSize / 2, playerSize, playerSize);
}
move(dx, dy) {
const newX = this.x + dx;
const newY = this.y + dy;
if (newX >= playerSize / 2 && newX <= arenaSize - playerSize / 2) {
this.x = newX;
this.hasMoved = true;
}
if (newY >= playerSize / 2 && newY <= arenaSize - playerSize / 2) {
this.y = newY;
this.hasMoved = true;
}
}
}
function getClockPositions(layout) {
switch (layout) {
case 0:
return [
new Clock(subSquareSize, subSquareSize, 180),
new Clock(arenaSize - subSquareSize, subSquareSize * 2, 0)
];
case 1:
return [
new Clock(subSquareSize, subSquareSize, 180),
new Clock(subSquareSize, subSquareSize * 2, 180),
new Clock(arenaSize - subSquareSize, subSquareSize + (subSquareSize / 2), 0)
];
case 2:
return [
new Clock(subSquareSize, subSquareSize, 270),
new Clock(subSquareSize, subSquareSize * 2, 90),
new Clock(arenaSize - subSquareSize, subSquareSize + (subSquareSize / 2), 180)
];
case 3:
return [
new Clock(subSquareSize + (subSquareSize / 2), subSquareSize, 270),
new Clock(subSquareSize, subSquareSize * 2, 90),
new Clock(arenaSize - subSquareSize, subSquareSize + (subSquareSize / 2), 180)
];
case 4:
return [
new Clock(subSquareSize + (subSquareSize / 2), subSquareSize, 0),
new Clock(subSquareSize, subSquareSize * 2, 180),
new Clock(arenaSize - subSquareSize, subSquareSize + (subSquareSize / 2), 90)
]
}
}
function pointInTriangle(px, py, ax, ay, bx, by, cx, cy) {
const v0x = cx - ax;
const v0y = cy - ay;
const v1x = bx - ax;
const v1y = by - ay;
const v2x = px - ax;
const v2y = py - ay;
const d00 = v0x * v0x + v0y * v0y;
const d01 = v0x * v1x + v0y * v1y;
const d11 = v1x * v1x + v1y * v1y;
const d20 = v2x * v0x + v2y * v0y;
const d21 = v2x * v1x + v2y * v1y;
const denominator = d00 * d11 - d01 * d01;
const a = (d11 * d20 - d01 * d21) / denominator;
const b = (d00 * d21 - d01 * d20) / denominator;
return a >= 0 && b >= 0 && a + b <= 1;
}
function checkCollisions() {
const px = player.x;
const py = player.y;
let collision = false;
for (const clock of clocks) {
const ax = clock.x;
const ay = clock.y;
const angle = 45;
const sideLength = 50;
const bx = clock.x + sideLength * handLength * Math.cos((clock.handDirection - angle) * Math.PI / 180);
const by = clock.y + sideLength * handLength * Math.sin((clock.handDirection - angle) * Math.PI / 180);
const cx = clock.x + sideLength * handLength * Math.cos((clock.handDirection + angle) * Math.PI / 180);
const cy = clock.y + sideLength * handLength * Math.sin((clock.handDirection + angle) * Math.PI / 180);
if (pointInTriangle(px, py, ax, ay, bx, by, cx, cy)) {
collision = true;
break;
}
}
return collision;
}
function displaySafeStatus(isSafe) {
if (isSafe)
succeeded();
else
failed();
}
function succeeded() {
drawText("SUCCESS!", 'green');
}
function failed() {
ctx.drawImage(deathImage, player.x - playerSize, player.y - playerSize, playerSize * 2, playerSize * 2);
drawText("FAILED!", 'red');
}
function drawText(text, colour) {
ctx.font = '35px Verdana'; // Set the font size and type
ctx.fillStyle = colour; // Set the fill color
ctx.textAlign = 'center'; // Set the horizontal alignment
ctx.textBaseline = 'middle'; // Set the vertical alignment
const x = canvas.width / 2; // Set the x-coordinate of the text
const y = 80; // Set the y-coordinate of the text
ctx.fillText(text, x, y); // Draw the text using fillText()
}
function gameLoop(currentTime) {
if (gameStopped) {
requestAnimationFrame(gameLoop);
return;
}
ctx.fillStyle = "gray";
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (rotationStarted === false && player.hasMoved) {
rotationStarted = true;
}
const elapsedTime = currentTime - lastRotationTime;
if (rotationStarted && !rotationComplete) {
rotateClocks(elapsedTime);
}
const moveAmount = 4;
if (movingUp()) player.move(0, -moveAmount);
if (movingDown()) player.move(0, moveAmount);
if (movingLeft()) player.move(-moveAmount, 0);
if (movingRight()) player.move(moveAmount, 0);
player.draw();
clocks.forEach(clock => {
clock.draw();
if (rotationComplete) {
clock.drawConal();
}
});
if (rotationComplete) {
const isSafe = !checkCollisions();
displaySafeStatus(isSafe);
stopGame();
}
requestAnimationFrame(gameLoop);
}
function movingUp() { return keysPressed.ArrowUp || keysPressed.W || keysPressed.w; }
function movingDown() { return keysPressed.ArrowDown || keysPressed.S || keysPressed.s; }
function movingLeft() { return keysPressed.ArrowLeft || keysPressed.A || keysPressed.a; }
function movingRight() { return keysPressed.ArrowRight || keysPressed.D || keysPressed.d; }
function startGame() {
gameStopped = false;
rotationStarted = false;
rotationComplete = false;
player = new Player(arenaSize / 2, arenaSize / 2);
clocks = getClockPositions(randomLayout);
}
function stopGame() {
gameStopped = true;
}
function generateNewLayout() {
let newLayout;
do {
newLayout = Math.floor(Math.random() * 5);
} while (newLayout === randomLayout);
randomLayout = newLayout;
}
function keydownHandler(event) {
if (keysPressed.hasOwnProperty(event.key)) {
keysPressed[event.key] = true;
}
}
function keyupHandler(event) {
if (keysPressed.hasOwnProperty(event.key)) {
keysPressed[event.key] = false;
}
}
document.addEventListener('keydown', keydownHandler);
document.addEventListener('keyup', keyupHandler);
document.getElementById("resetButton").addEventListener("click", function() {
stopGame();
resetRotation();
generateNewLayout();
startGame();
});
document.getElementById("retryButton").addEventListener("click", function() {
stopGame();
resetRotation();
startGame();
});
startGame();
gameLoop();