diff --git a/Expedience.Web/Expedience.Web.csproj b/Expedience.Web/Expedience.Web.csproj index 5abeb4c..ae59c19 100644 --- a/Expedience.Web/Expedience.Web.csproj +++ b/Expedience.Web/Expedience.Web.csproj @@ -8,6 +8,11 @@ Linux + + + + + diff --git a/Expedience.Web/wwwroot/images/vuln-stack.png b/Expedience.Web/wwwroot/images/vuln-stack.png new file mode 100644 index 0000000..b3c35bb Binary files /dev/null and b/Expedience.Web/wwwroot/images/vuln-stack.png differ diff --git a/Expedience.Web/wwwroot/js/time-bomb.js b/Expedience.Web/wwwroot/js/time-bomb.js new file mode 100644 index 0000000..83b5a66 --- /dev/null +++ b/Expedience.Web/wwwroot/js/time-bomb.js @@ -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(); diff --git a/Expedience.Web/wwwroot/time-bomb.html b/Expedience.Web/wwwroot/time-bomb.html new file mode 100644 index 0000000..63436b1 --- /dev/null +++ b/Expedience.Web/wwwroot/time-bomb.html @@ -0,0 +1,53 @@ + + + + + + Time Bomb + + + +

AVOID THE TIME BOMBS

+
+

Control the player with WSAD or Arrow keys. The clocks will start ticking as soon as the player moves.

+
+ +
+ + +
+ + +