diff --git a/src/game.ts b/src/game.ts deleted file mode 100644 index 57110c8..0000000 --- a/src/game.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { isKeyPressed } from './input'; -import { ctx, canvas, resizeCanvas } from './canvas'; -import { Paddle, paddleProperties, drawPaddle } from './objects/paddle'; -import { Ball, createBall, drawBall, updateBall, ballProperties } from './objects/ball'; -import { sweptCollision, handleSweptCollision, handleWallCollisions } from './collision'; -import { drawPowerBar } from './gui'; - -let lastTime: number; -let paddle: Paddle; -let ball: Ball; -let prevPaddleX: number; -let prevPaddleY: number; -let prevPaddleRotation: number; - -export function initGame(initialPaddle: Paddle, initialBall: Ball) { - paddle = initialPaddle; - ball = initialBall; - lastTime = performance.now(); - prevPaddleX = paddle.x; - prevPaddleY = paddle.y; - prevPaddleRotation = paddle.rotation; - requestAnimationFrame(gameLoop); -} - -function gameLoop() { - // Get delta time since last frame - const currentTime = performance.now(); - const deltaTime = (currentTime - lastTime) / 1000; // Convert to seconds - lastTime = currentTime; - - resizeCanvas(); - - // Fill canvas with black background - ctx.fillStyle = "black"; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - // Calculate paddle velocity from previous position - const paddleVelocityX = (paddle.x - prevPaddleX) / deltaTime; - const paddleVelocityY = (paddle.y - prevPaddleY) / deltaTime; - const paddleAngularVelocity = (paddle.rotation - prevPaddleRotation) / deltaTime; - - // Save current position for next frame - prevPaddleX = paddle.x; - prevPaddleY = paddle.y; - prevPaddleRotation = paddle.rotation; - - // Check input states first - const rotatingLeft = isKeyPressed('KeyA'); - const rotatingRight = isKeyPressed('KeyD'); - const movingForward = isKeyPressed('KeyW'); - const movingBackward = isKeyPressed('KeyS'); - const leaping = isKeyPressed('Space'); - const sneaking = isKeyPressed('ShiftLeft'); - - const rotating = rotatingLeft || rotatingRight; - const moving = movingForward || movingBackward || rotating; - - let currentMoveSpeed = paddleProperties.moveSpeed; - let currentRotationSpeed = paddleProperties.rotationSpeed; - - if (sneaking) { - currentMoveSpeed *= 0.5; - currentRotationSpeed *= 0.5; - } else if (moving && leaping) { - currentMoveSpeed *= paddle.currentPower; - - paddle.currentPower = Math.max(1, paddle.currentPower - paddleProperties.powerDecrease * deltaTime); - } else { - paddle.currentPower = Math.min(paddleProperties.maxPower, paddle.currentPower + paddleProperties.powerIncrease * deltaTime); - } - - if (rotating) { - currentMoveSpeed /= Math.sqrt(2); - } - - // Handle rotation first - if (rotating) { - const rotationDirection = rotatingLeft ? -1 : 1; - const rotationAmount = currentRotationSpeed * deltaTime; - - // Store original rotation - const originalRotation = paddle.rotation; - - // Apply rotation - paddle.rotation += rotationDirection * rotationAmount; - - // Calculate edge positions based on original rotation - let edgeX, edgeY; - - // Determine which edge to fix based on rotation direction and movement direction - const fixLeftEdge = (rotationDirection === -1 && !movingBackward) || - (rotationDirection === 1 && movingBackward); - - if (fixLeftEdge) { - // Calculate left edge position before rotation - edgeX = paddle.x - (paddleProperties.width / 2) * Math.cos(originalRotation); - edgeY = paddle.y - (paddleProperties.width / 2) * Math.sin(originalRotation); - - // Recalculate paddle position to keep left edge in place - paddle.x = edgeX + (paddleProperties.width / 2) * Math.cos(paddle.rotation); - paddle.y = edgeY + (paddleProperties.width / 2) * Math.sin(paddle.rotation); - } else { - // Calculate right edge position before rotation - edgeX = paddle.x + (paddleProperties.width / 2) * Math.cos(originalRotation); - edgeY = paddle.y + (paddleProperties.width / 2) * Math.sin(originalRotation); - - // Recalculate paddle position to keep right edge in place - paddle.x = edgeX - (paddleProperties.width / 2) * Math.cos(paddle.rotation); - paddle.y = edgeY - (paddleProperties.width / 2) * Math.sin(paddle.rotation); - } - } - - // Handle movement after rotation - if (movingForward) { - paddle.x += Math.sin(paddle.rotation) * currentMoveSpeed * deltaTime; - paddle.y -= Math.cos(paddle.rotation) * currentMoveSpeed * deltaTime; - } - - if (movingBackward) { - paddle.x -= Math.sin(paddle.rotation) * currentMoveSpeed * deltaTime; - paddle.y += Math.cos(paddle.rotation) * currentMoveSpeed * deltaTime; - } - - // Calculate expected paddle velocity for the current frame (prediction) - const currentPaddleVelocityX = (paddle.x - prevPaddleX) / deltaTime; - const currentPaddleVelocityY = (paddle.y - prevPaddleY) / deltaTime; - const currentPaddleAngularVelocity = (paddle.rotation - prevPaddleRotation) / deltaTime; - - // Check for swept paddle collision using relative velocity - const paddleCollision = sweptCollision( - ball, - ball.velocityX, - ball.velocityY, - paddle, - currentPaddleVelocityX, - currentPaddleVelocityY, - currentPaddleAngularVelocity, - deltaTime - ); - - // Handle ball movement and collisions - if (paddleCollision.time >= 0) { - // Ball collides with paddle in this frame - handleSweptCollision( - ball, - paddleCollision, - paddle, - currentPaddleVelocityX, - currentPaddleVelocityY, - currentPaddleAngularVelocity, - deltaTime, - leaping - ); - } else { - // No paddle collision, update ball normally - updateBall(ball, deltaTime); - } - - // Handle wall collisions - handleWallCollisions(ball, deltaTime); - - // Draw game objects - drawPaddle(ctx, paddle); - drawBall(ctx, ball); - - drawPowerBar(paddle); - - requestAnimationFrame(gameLoop); -} \ No newline at end of file diff --git a/src/canvas.ts b/src/game/canvas.ts similarity index 86% rename from src/canvas.ts rename to src/game/canvas.ts index 384cf1d..72787e1 100644 --- a/src/canvas.ts +++ b/src/game/canvas.ts @@ -12,4 +12,6 @@ export function resizeCanvas() { canvas.width = rect.width; canvas.height = rect.height; + + console.log("canvas size", canvas.width, canvas.height); } diff --git a/src/collision.ts b/src/game/collision.ts similarity index 86% rename from src/collision.ts rename to src/game/collision.ts index 31038c2..fee2163 100644 --- a/src/collision.ts +++ b/src/game/collision.ts @@ -85,6 +85,40 @@ export function sweptCollision( const worldNormalX = normalX * Math.cos(paddle.rotation) - normalY * Math.sin(paddle.rotation); const worldNormalY = normalX * Math.sin(paddle.rotation) + normalY * Math.cos(paddle.rotation); + // Ensure the ball is pushed out of the paddle based on its velocity + const pushOutDistance = Math.max(ball.radius - minDist, 0); + ball.x += worldNormalX * pushOutDistance; + ball.y += worldNormalY * pushOutDistance; + + // Adjust for paddle velocity to prevent phasing + const paddlePushOutDistance = Math.max(0, Math.abs(paddleVelocityX) + Math.abs(paddleVelocityY)) * deltaTime; + ball.x += worldNormalX * paddlePushOutDistance; + ball.y += worldNormalY * paddlePushOutDistance; + + // Additional check for fast-moving paddles + if (Math.abs(paddleVelocityX) > 0 || Math.abs(paddleVelocityY) > 0) { + // Calculate the distance the ball would move in the next frame + const ballFutureX = ball.x + ballVelocityX * deltaTime; + const ballFutureY = ball.y + ballVelocityY * deltaTime; + + // Check if the future position would still be inside the paddle's area + const futureRelativeX = ballFutureX - paddle.x; + const futureRelativeY = ballFutureY - paddle.y; + + const futureRotatedX = futureRelativeX * Math.cos(-paddle.rotation) - futureRelativeY * Math.sin(-paddle.rotation); + const futureRotatedY = futureRelativeX * Math.sin(-paddle.rotation) + futureRelativeY * Math.cos(-paddle.rotation); + + const futureInsideX = futureRotatedX >= expandedLeft && futureRotatedX <= expandedRight; + const futureInsideY = futureRotatedY >= expandedTop && futureRotatedY <= expandedBottom; + + if (futureInsideX && futureInsideY) { + // If the future position is still inside, push the ball out further + const futurePushOutDistance = Math.max(ball.radius - minDist, 0) + paddlePushOutDistance; + ball.x += worldNormalX * futurePushOutDistance; + ball.y += worldNormalY * futurePushOutDistance; + } + } + return { time: 0, normalX: worldNormalX, @@ -173,8 +207,7 @@ export function handleSweptCollision( paddleVelocityX: number, paddleVelocityY: number, paddleAngularVelocity: number, - deltaTime: number, - isLeapPressed: boolean + deltaTime: number ) { // Check if collision data is valid before processing if (!collision || collision.time === undefined || !collision.hitPoint) { @@ -244,17 +277,6 @@ export function handleSweptCollision( // Combine new normal and tangential components ball.velocityX = newNormalVelX + newTangentVelX; ball.velocityY = newNormalVelY + newTangentVelY; - - // Add paddle's momentum to the ball if using leap - if (isLeapPressed) { - const leapBoostFactor = 0.7; - const paddleSpeed = paddleProperties.moveSpeed * paddle.currentPower; - const paddleDirectionX = Math.sin(paddle.rotation); - const paddleDirectionY = -Math.cos(paddle.rotation); - - ball.velocityX += paddleDirectionX * paddleSpeed * leapBoostFactor; - ball.velocityY += paddleDirectionY * paddleSpeed * leapBoostFactor; - } } // Slightly move the ball along the normal to prevent sticking diff --git a/src/game/fastRandom.ts b/src/game/fastRandom.ts new file mode 100644 index 0000000..9c9a1a3 --- /dev/null +++ b/src/game/fastRandom.ts @@ -0,0 +1,7 @@ +const lookupTable = Array.from({ length: 1e4 }, () => Math.random()); +let index = 0; + +export function random() { + index = (index + 1) % lookupTable.length; + return lookupTable[index]; +} \ No newline at end of file diff --git a/src/game/game.ts b/src/game/game.ts new file mode 100644 index 0000000..5e4d6d4 --- /dev/null +++ b/src/game/game.ts @@ -0,0 +1,102 @@ +import { isKeyPressed } from './input'; +import { ctx, canvas, resizeCanvas } from './canvas'; +import { Paddle, paddleProperties, drawPaddle } from './objects/paddle'; +import { Ball, createBall, drawBall, updateBall, ballProperties } from './objects/ball'; +import { sweptCollision, handleSweptCollision, handleWallCollisions } from './collision'; +import { drawArrow, drawPowerBar } from './gui'; +import { senseLoop } from './sense'; +import { keys } from './keys'; +import { movementTick } from './movement'; + +const backgroundColor = "#111"; + +let lastTime: number; +let paddle: Paddle; +let ball: Ball; +let prevPaddleX: number; +let prevPaddleY: number; +let prevPaddleRotation: number; + +export function initGame(initialPaddle: Paddle, initialBall: Ball) { + paddle = initialPaddle; + ball = initialBall; + lastTime = performance.now(); + prevPaddleX = paddle.x; + prevPaddleY = paddle.y; + prevPaddleRotation = paddle.rotation; + requestAnimationFrame(gameLoop); +} + + +function gameLoop() { + // Get delta time since last frame + const currentTime = performance.now(); + const deltaTime = (currentTime - lastTime) / 1000; // Convert to seconds + lastTime = currentTime; + + resizeCanvas(); + + // Fill canvas with black background + ctx.fillStyle = backgroundColor; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + // Calculate paddle velocity from previous position + const paddleVelocityX = (paddle.x - prevPaddleX) / deltaTime; + const paddleVelocityY = (paddle.y - prevPaddleY) / deltaTime; + const paddleAngularVelocity = (paddle.rotation - prevPaddleRotation) / deltaTime; + + // Save current position for next frame + prevPaddleX = paddle.x; + prevPaddleY = paddle.y; + prevPaddleRotation = paddle.rotation; + + movementTick(paddle, deltaTime); + + // Calculate expected paddle velocity for the current frame (prediction) + const currentPaddleVelocityX = (paddle.x - prevPaddleX) / deltaTime; + const currentPaddleVelocityY = (paddle.y - prevPaddleY) / deltaTime; + const currentPaddleAngularVelocity = (paddle.rotation - prevPaddleRotation) / deltaTime; + + // Check for swept paddle collision using relative velocity + const paddleCollision = sweptCollision( + ball, + ball.velocityX, + ball.velocityY, + paddle, + currentPaddleVelocityX, + currentPaddleVelocityY, + currentPaddleAngularVelocity, + deltaTime + ); + + // Handle ball movement and collisions + if (paddleCollision.time >= 0) { + // Ball collides with paddle in this frame + handleSweptCollision( + ball, + paddleCollision, + paddle, + currentPaddleVelocityX, + currentPaddleVelocityY, + currentPaddleAngularVelocity, + deltaTime + ); + } else { + // No paddle collision, update ball normally + updateBall(ball, deltaTime); + } + + // Handle wall collisions + handleWallCollisions(ball, deltaTime); + + // Draw game objects + drawPaddle(ctx, paddle); + drawBall(ctx, ball); + + drawPowerBar(paddle); + drawArrow(paddle); + + senseLoop(ctx, ball, deltaTime); + + requestAnimationFrame(gameLoop); +} \ No newline at end of file diff --git a/src/game/gui.ts b/src/game/gui.ts new file mode 100644 index 0000000..ba340b7 --- /dev/null +++ b/src/game/gui.ts @@ -0,0 +1,36 @@ +import { ctx } from "./canvas"; + +import { canvas } from "./canvas"; +import { Paddle, paddleProperties } from "./objects/paddle"; + +const powerBarProperties = { + width: 500, + height: 10, + borderColor: "white" +} + +export function drawBar(x: number, y: number, width: number, height: number, color: string, borderColor: string, fillPercentage: number) { + ctx.fillStyle = borderColor; + ctx.fillRect(x, y, width, height); + + ctx.fillStyle = color; + ctx.fillRect(x, y, width * fillPercentage, height); + + ctx.strokeStyle = borderColor; + ctx.strokeRect(x, y, width, height); +} + +export function drawPowerBar(paddle: Paddle) { + const x = 20; + const y = canvas.height - 20; + + // Draw current leap strength + const normalizedStrength = (paddle.currentPower - 1) / (paddleProperties.maxPower - 1); // Convert from 1-3 range to 0-1 range + const color = normalizedStrength > 0.7 ? "green" : normalizedStrength > 0.3 ? "yellow" : "red"; + + drawBar(x, y, powerBarProperties.width, powerBarProperties.height, color, powerBarProperties.borderColor, normalizedStrength); +} + +export function drawArrow(paddle: Paddle) { + // TODO: Draw arrow +} diff --git a/src/input.ts b/src/game/input.ts similarity index 90% rename from src/input.ts rename to src/game/input.ts index 3459b5d..c727e23 100644 --- a/src/input.ts +++ b/src/game/input.ts @@ -10,10 +10,14 @@ const keys: Record = {}; */ export function initKeyboardInput(): void { window.addEventListener('keydown', (e) => { + e.preventDefault(); + e.stopPropagation(); keys[e.code] = true; }); window.addEventListener('keyup', (e) => { + e.preventDefault(); + e.stopPropagation(); keys[e.code] = false; }); } diff --git a/src/game/keys.ts b/src/game/keys.ts new file mode 100644 index 0000000..1b58175 --- /dev/null +++ b/src/game/keys.ts @@ -0,0 +1,9 @@ +export const keys = { + left: 'KeyA', + right: 'KeyD', + forward: 'KeyW', + backward: 'KeyS', + leap: 'Space', + sneak: 'ShiftLeft', + sense: 'AltLeft' +} diff --git a/src/game/movement.ts b/src/game/movement.ts new file mode 100644 index 0000000..ccd4305 --- /dev/null +++ b/src/game/movement.ts @@ -0,0 +1,84 @@ +import { isKeyPressed } from "./input"; +import { keys } from "./keys"; +import { Paddle } from "./objects/paddle"; + +import { paddleProperties } from "./objects/paddle"; + +export function movementTick(paddle: Paddle, deltaTime: number) { + // Check input states first + const rotatingLeft = isKeyPressed(keys.left); + const rotatingRight = isKeyPressed(keys.right); + const movingForward = isKeyPressed(keys.forward); + const movingBackward = isKeyPressed(keys.backward); + const leaping = isKeyPressed(keys.leap); + const sneaking = isKeyPressed(keys.sneak); + + const rotating = rotatingLeft || rotatingRight; + const moving = movingForward || movingBackward || rotating; + + let currentMoveSpeed = paddleProperties.moveSpeed; + let currentRotationSpeed = paddleProperties.rotationSpeed; + + if (sneaking) { + currentMoveSpeed *= 0.5; + currentRotationSpeed *= 0.5; + } else if (moving && leaping) { + currentMoveSpeed *= paddle.currentPower; + + paddle.currentPower = Math.max(1, paddle.currentPower - paddleProperties.powerDecrease * deltaTime); + } else { + paddle.currentPower = Math.min(paddleProperties.maxPower, paddle.currentPower + paddleProperties.powerIncrease * deltaTime); + } + + if (rotating) { + currentMoveSpeed /= Math.sqrt(2); + } + + // Handle rotation first + if (rotating) { + const rotationDirection = rotatingLeft ? -1 : 1; + const rotationAmount = currentRotationSpeed * deltaTime; + + // Store original rotation + const originalRotation = paddle.rotation; + + // Apply rotation + paddle.rotation += rotationDirection * rotationAmount; + + // Calculate edge positions based on original rotation + let edgeX, edgeY; + + // Determine which edge to fix based on rotation direction and movement direction + const fixLeftEdge = (rotationDirection === -1 && !movingBackward) || + (rotationDirection === 1 && movingBackward); + + if (fixLeftEdge) { + // Calculate left edge position before rotation + edgeX = paddle.x - (paddleProperties.width / 2) * Math.cos(originalRotation); + edgeY = paddle.y - (paddleProperties.width / 2) * Math.sin(originalRotation); + + // Recalculate paddle position to keep left edge in place + paddle.x = edgeX + (paddleProperties.width / 2) * Math.cos(paddle.rotation); + paddle.y = edgeY + (paddleProperties.width / 2) * Math.sin(paddle.rotation); + } else { + // Calculate right edge position before rotation + edgeX = paddle.x + (paddleProperties.width / 2) * Math.cos(originalRotation); + edgeY = paddle.y + (paddleProperties.width / 2) * Math.sin(originalRotation); + + // Recalculate paddle position to keep right edge in place + paddle.x = edgeX - (paddleProperties.width / 2) * Math.cos(paddle.rotation); + paddle.y = edgeY - (paddleProperties.width / 2) * Math.sin(paddle.rotation); + } + } + + // Handle movement after rotation + if (movingForward) { + paddle.x += Math.sin(paddle.rotation) * currentMoveSpeed * deltaTime; + paddle.y -= Math.cos(paddle.rotation) * currentMoveSpeed * deltaTime; + } + + if (movingBackward) { + paddle.x -= Math.sin(paddle.rotation) * currentMoveSpeed * deltaTime; + paddle.y += Math.cos(paddle.rotation) * currentMoveSpeed * deltaTime; + } +} \ No newline at end of file diff --git a/src/game/objects/ball.ts b/src/game/objects/ball.ts new file mode 100644 index 0000000..ad443ff --- /dev/null +++ b/src/game/objects/ball.ts @@ -0,0 +1,92 @@ +import { random } from "../fastRandom"; + +export interface Ball { + x: number; + y: number; + radius: number; + color: string; + velocityX: number; + velocityY: number; + pastPositions: { x: number; y: number }[]; // Array of past positions +} + +export const ballProperties = { + radius: 10, + color: "white", + friction: 0.9, + maxPositions: 10, + trailWidth: 30 +} + +export function createBall(x: number, y: number): Ball { + return { + x: x, + y: y, + radius: ballProperties.radius, + color: ballProperties.color, + velocityX: 0, + velocityY: 0, + pastPositions: [] + } +} + +export function createBallWithVelocity(x: number, y: number, velocityX: number, velocityY: number): Ball { + return { + x: x, + y: y, + radius: ballProperties.radius, + color: ballProperties.color, + velocityX: velocityX, + velocityY: velocityY, + pastPositions: [] + } +} + +export function drawBall(ctx: CanvasRenderingContext2D, ball: Ball) { + ctx.save(); + + ctx.shadowColor = ball.color; + ctx.shadowBlur = 10; + ctx.shadowOffsetX = 0; // No offset + ctx.shadowOffsetY = 0; // No offset + + ctx.fillStyle = ball.color; + ctx.beginPath(); + + ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2); + ctx.fill(); + + ctx.restore(); + + drawTrail(ctx, ball); +} + +export function drawTrail(ctx: CanvasRenderingContext2D, ball: Ball) { + ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)'; // Set a darker trail color with transparency + ctx.beginPath(); + for (let i = 0; i < ball.pastPositions.length - 1; i++) { + const randomnessX = (random() - 0.5) * ballProperties.trailWidth; // Random offset in X direction + const randomnessY = (random() - 0.5) * ballProperties.trailWidth; // Random offset in Y direction + ctx.moveTo(ball.pastPositions[i].x + randomnessX, ball.pastPositions[i].y + randomnessY); + ctx.lineTo(ball.pastPositions[i + 1].x + randomnessX, ball.pastPositions[i + 1].y + randomnessY); + } + ctx.stroke(); +} + +export function updateBall(ball: Ball, deltaTime: number) { + // Update position based on velocity + ball.x += ball.velocityX * deltaTime; + ball.y += ball.velocityY * deltaTime; + + // Apply friction based on delta time + ball.velocityX *= Math.pow(ballProperties.friction, deltaTime); + ball.velocityY *= Math.pow(ballProperties.friction, deltaTime); + + // Add current position to past positions array + ball.pastPositions.push({ x: ball.x, y: ball.y }); + + // Limit past positions array to 10 entries + if (ball.pastPositions.length > ballProperties.maxPositions) { + ball.pastPositions.shift(); + } +} \ No newline at end of file diff --git a/src/objects/paddle.ts b/src/game/objects/paddle.ts similarity index 100% rename from src/objects/paddle.ts rename to src/game/objects/paddle.ts diff --git a/src/game/sense.ts b/src/game/sense.ts new file mode 100644 index 0000000..bdb919a --- /dev/null +++ b/src/game/sense.ts @@ -0,0 +1,123 @@ +import { isKeyPressed } from "./input"; +import { Ball, createBall, createBallWithVelocity } from "./objects/ball"; +import { keys } from "./keys"; +import { random } from "./fastRandom"; +import { canvas } from "./canvas"; +import { drawBar } from "./gui"; +/** + * Properties for the sensing mechanism in the game + * @const senseProperties + * @property {number} maxPower - The maximum sensing power that can be achieved + * @property {number} powerIncrease - The rate at which sensing power increases per second + * @property {number} powerDecrease - The rate at which sensing power decreases per second + * @property {number} maxWallHits - The maximum number of wall hits to predict + */ +const senseProperties = { + maxPower: 1, + powerIncrease: 0.1, + powerDecrease: 2, + maxWallHits: 3 +} + +var currentSensedBall: Ball | null = null; +var currentSensingPower: number = senseProperties.maxPower; + +export function senseLoop(ctx: CanvasRenderingContext2D, ball: Ball, deltaTime: number) { + const sensing = isKeyPressed(keys.sense); + + if (sensing) { + if (currentSensingPower == senseProperties.maxPower) { + currentSensedBall = createBallWithVelocity(ball.x, ball.y, ball.velocityX, ball.velocityY); + } + + if (currentSensedBall) { + currentSensingPower -= senseProperties.powerDecrease * deltaTime; + drawPredictionLine(ctx, currentSensedBall); + } + } else { + currentSensedBall = null; + currentSensingPower = Math.min(currentSensingPower + senseProperties.powerIncrease * deltaTime, senseProperties.maxPower); + } + + if (currentSensingPower <= 0) { + currentSensedBall = null; + } + + drawSenseBar(); +} + +export function drawPredictionLine(ctx: CanvasRenderingContext2D, ball: Ball) { + const canvas = ctx.canvas; + const timeStep = 0.2; // Time increment for each prediction step + + // Create a clone of the ball for prediction + const predictedBall = { + x: ball.x, + y: ball.y, + radius: ball.radius, + velocityX: ball.velocityX, + velocityY: ball.velocityY + }; + + // Set up line drawing + ctx.beginPath(); + ctx.moveTo(ball.x, ball.y); + const fadeFactor = currentSensingPower / senseProperties.maxPower; + ctx.strokeStyle = `rgba(255, 255, 255, ${Math.max(0, fadeFactor / 3)})`; + ctx.setLineDash([5, 5]); // Dashed line + ctx.lineWidth = 2; + + // Predict multiple steps of ball movement + let wallHits = 0; // Initialize wall hit counter + let steps = 0; // Initialize a step counter + while (wallHits < senseProperties.maxWallHits && steps < 100) { // Add a maximum step limit to prevent infinite loop + // Move the predicted ball + predictedBall.x += predictedBall.velocityX * timeStep; + predictedBall.y += predictedBall.velocityY * timeStep; + + // Check for wall collisions + if (predictedBall.x - predictedBall.radius < 0) { + predictedBall.x = predictedBall.radius; + predictedBall.velocityX = Math.abs(predictedBall.velocityX); + wallHits++; // Increment wall hit counter + } else if (predictedBall.x + predictedBall.radius > canvas.width) { + predictedBall.x = canvas.width - predictedBall.radius; + predictedBall.velocityX = -Math.abs(predictedBall.velocityX); + wallHits++; // Increment wall hit counter + } + + if (predictedBall.y - predictedBall.radius < 0) { + predictedBall.y = predictedBall.radius; + predictedBall.velocityY = Math.abs(predictedBall.velocityY); + wallHits++; // Increment wall hit counter + } else if (predictedBall.y + predictedBall.radius > canvas.height) { + predictedBall.y = canvas.height - predictedBall.radius; + predictedBall.velocityY = -Math.abs(predictedBall.velocityY); + wallHits++; // Increment wall hit counter + } + + // Add point to the line + ctx.lineTo(predictedBall.x + 10 - random() * 20, predictedBall.y + 10 - random() * 20); + steps++; // Increment the step counter to avoid infinite loop + } + + // Draw the prediction line + ctx.stroke(); + ctx.setLineDash([]); // Reset line dash + } + + const senseBarProperties = { + width: 500, + height: 10, + borderColor: "lightgray" +} + + export function drawSenseBar() { + const x = 20; + const y = canvas.height - 60; + + const percentage = currentSensingPower / senseProperties.maxPower; + const color = percentage > 0.7 ? "green" : percentage > 0.3 ? "yellow" : "red"; + + drawBar(x, y, senseBarProperties.width, senseBarProperties.height, color, senseBarProperties.borderColor, percentage); +} \ No newline at end of file diff --git a/src/gui.ts b/src/gui.ts deleted file mode 100644 index af7d024..0000000 --- a/src/gui.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ctx } from "./canvas"; - -import { canvas } from "./canvas"; -import { Paddle, paddleProperties } from "./objects/paddle"; - -const barProperties = { - width: 500, - height: 10, - x: 20, - y: canvas.height - 20, - color: "gray", - borderColor: "white" -} - -export function drawPowerBar(paddle: Paddle) { - // Draw bar background - ctx.fillStyle = "gray"; - ctx.fillRect(barProperties.x, barProperties.y, barProperties.width, barProperties.height); - - // Draw current leap strength - const normalizedStrength = (paddle.currentPower - 1) / (paddleProperties.maxPower - 1); // Convert from 1-3 range to 0-1 range - ctx.fillStyle = normalizedStrength > 0.7 ? "green" : normalizedStrength > 0.3 ? "yellow" : "red"; - ctx.fillRect(barProperties.x, barProperties.y, barProperties.width * normalizedStrength, barProperties.height); - - // Draw bar border - ctx.strokeStyle = "white"; - ctx.strokeRect(barProperties.x, barProperties.y, barProperties.width, barProperties.height); -} \ No newline at end of file diff --git a/src/styles.css b/src/index.css similarity index 100% rename from src/styles.css rename to src/index.css diff --git a/src/index.html b/src/index.html index 8fe8ea4..eb82f4b 100644 --- a/src/index.html +++ b/src/index.html @@ -4,10 +4,22 @@ TypeScript HTML Project - + - +
+ + + +
+ + + + + +
+ + diff --git a/src/index.ts b/src/index.ts index 846245a..c18ea6a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,35 @@ -import { initKeyboardInput } from './input'; -import { canvas, setupCanvas } from './canvas'; -import { createPaddle } from './objects/paddle'; -import { initGame } from './game'; -import { createBall } from './objects/ball'; -// Setup canvas and initialize input -setupCanvas(); -initKeyboardInput(); +import { initKeyboardInput } from './game/input'; +import { canvas, setupCanvas } from './game/canvas'; +import { createPaddle } from './game/objects/paddle'; +import { initGame } from './game/game'; +import { createBall } from './game/objects/ball'; + +const playButton = document.getElementById('playButton'); +const usernameInput = document.getElementById('username'); + +playButton?.addEventListener('click', () => { + const username = (usernameInput as HTMLInputElement)?.value; + if (!username) { + alert('Please enter a username'); + return; + } + + const colorPicker = document.getElementById('colorPicker'); + const color = (colorPicker as HTMLInputElement)?.value; + + if (document.getElementById('gameControls')) { + document.getElementById('gameControls')!.style.display = 'none'; + } + const gameCanvas = document.getElementById('gameCanvas') as HTMLCanvasElement; + gameCanvas.style.display = ""; + + // Setup canvas and initialize input + setupCanvas(); + initKeyboardInput(); + + // Create paddle and start game + const paddle = createPaddle(canvas.width / 2, canvas.height - 200, color, username); + const ball = createBall(canvas.width / 2, canvas.height / 2); + initGame(paddle, ball); +}); -// Create paddle and start game -const paddle = createPaddle(canvas.width / 2, canvas.height - 200, "white", "Player"); -const ball = createBall(canvas.width / 2, canvas.height / 2); -ball.velocityX = 200; -ball.velocityY = 200; -initGame(paddle, ball); \ No newline at end of file diff --git a/src/objects/ball.ts b/src/objects/ball.ts deleted file mode 100644 index de55e23..0000000 --- a/src/objects/ball.ts +++ /dev/null @@ -1,99 +0,0 @@ -export interface Ball { - x: number; - y: number; - radius: number; - color: string; - velocityX: number; - velocityY: number; -} - -export const ballProperties = { - radius: 10, - color: "white", - friction: 0.99, - predictionSteps: 0 -} - -export function createBall(x: number, y: number): Ball { - return { - x: x, - y: y, - radius: ballProperties.radius, - color: ballProperties.color, - velocityX: 0, - velocityY: 0 - } -} - -export function drawBall(ctx: CanvasRenderingContext2D, ball: Ball) { - ctx.fillStyle = ball.color; - ctx.beginPath(); - ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2); - ctx.fill(); - - if (ballProperties.predictionSteps > 0) { - drawPredictionLine(ctx, ball); - } -} - -export function updateBall(ball: Ball, deltaTime: number) { - // Update position based on velocity - ball.x += ball.velocityX * deltaTime; - ball.y += ball.velocityY * deltaTime; - - // Apply friction based on delta time - ball.velocityX *= Math.pow(ballProperties.friction, deltaTime); - ball.velocityY *= Math.pow(ballProperties.friction, deltaTime); -} - -export function drawPredictionLine(ctx: CanvasRenderingContext2D, ball: Ball) { - const canvas = ctx.canvas; - const timeStep = 0.02; // Time increment for each prediction step - - // Create a clone of the ball for prediction - const predictedBall = { - x: ball.x, - y: ball.y, - radius: ball.radius, - velocityX: ball.velocityX, - velocityY: ball.velocityY - }; - - // Set up line drawing - ctx.beginPath(); - ctx.moveTo(ball.x, ball.y); - ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)'; // Semi-transparent white - ctx.setLineDash([5, 5]); // Dashed line - ctx.lineWidth = 2; - - // Predict multiple steps of ball movement - for (let i = 0; i < ballProperties.predictionSteps; i++) { - // Move the predicted ball - predictedBall.x += predictedBall.velocityX * timeStep; - predictedBall.y += predictedBall.velocityY * timeStep; - - // Check for wall collisions - if (predictedBall.x - predictedBall.radius < 0) { - predictedBall.x = predictedBall.radius; - predictedBall.velocityX = Math.abs(predictedBall.velocityX); - } else if (predictedBall.x + predictedBall.radius > canvas.width) { - predictedBall.x = canvas.width - predictedBall.radius; - predictedBall.velocityX = -Math.abs(predictedBall.velocityX); - } - - if (predictedBall.y - predictedBall.radius < 0) { - predictedBall.y = predictedBall.radius; - predictedBall.velocityY = Math.abs(predictedBall.velocityY); - } else if (predictedBall.y + predictedBall.radius > canvas.height) { - predictedBall.y = canvas.height - predictedBall.radius; - predictedBall.velocityY = -Math.abs(predictedBall.velocityY); - } - - // Add point to the line - ctx.lineTo(predictedBall.x, predictedBall.y); - } - - // Draw the prediction line - ctx.stroke(); - ctx.setLineDash([]); // Reset line dash - } \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 9fd99bd..0256a56 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -27,7 +27,7 @@ module.exports = { template: "src/index.html", }), new CopyWebpackPlugin({ - patterns: [{ from: "src/styles.css", to: "styles.css" }], + patterns: [{ from: "src/index.css", to: "index.css" }], }), ], devServer: {