Add time-bombs
parent
4196d4a781
commit
a2866a71d7
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
@ -0,0 +1,389 @@
|
|||||||
|
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();
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Time Bomb</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color: gainsboro;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
border: 2px solid black;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
width: 50%;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
font-family: Verdana;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
.explanation {
|
||||||
|
width: 50%;
|
||||||
|
text-align: left;
|
||||||
|
font-family: Verdana;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.buttons {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
margin: 0 5px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div><h2 class="title">AVOID THE TIME BOMBS</h2></div>
|
||||||
|
<div class="explanation">
|
||||||
|
<p>Control the player with WSAD or Arrow keys. The clocks will start ticking as soon as the player moves.</p>
|
||||||
|
</div>
|
||||||
|
<canvas id="gameCanvas" width="500" height="500"></canvas>
|
||||||
|
<div class="buttons">
|
||||||
|
<button id="resetButton">Reset</button>
|
||||||
|
<button id="retryButton">Retry</button>
|
||||||
|
</div>
|
||||||
|
<script src="../js/time-bomb.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Reference in New Issue