This commit is contained in:
Minecon724 2025-04-01 12:56:30 +02:00
parent 5c8fe77204
commit 6f63f92d3c
Signed by: Minecon724
GPG key ID: A02E6E67AB961189
18 changed files with 543 additions and 326 deletions

View file

@ -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);
}

View file

@ -12,4 +12,6 @@ export function resizeCanvas() {
canvas.width = rect.width;
canvas.height = rect.height;
console.log("canvas size", canvas.width, canvas.height);
}

View file

@ -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

7
src/game/fastRandom.ts Normal file
View file

@ -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];
}

102
src/game/game.ts Normal file
View file

@ -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);
}

36
src/game/gui.ts Normal file
View file

@ -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
}

View file

@ -10,10 +10,14 @@ const keys: Record<string, boolean> = {};
*/
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;
});
}

9
src/game/keys.ts Normal file
View file

@ -0,0 +1,9 @@
export const keys = {
left: 'KeyA',
right: 'KeyD',
forward: 'KeyW',
backward: 'KeyS',
leap: 'Space',
sneak: 'ShiftLeft',
sense: 'AltLeft'
}

84
src/game/movement.ts Normal file
View file

@ -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;
}
}

92
src/game/objects/ball.ts Normal file
View file

@ -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();
}
}

123
src/game/sense.ts Normal file
View file

@ -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);
}

View file

@ -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);
}

View file

@ -4,10 +4,22 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TypeScript HTML Project</title>
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="index.css">
</head>
<body>
<canvas id="gameCanvas"></canvas>
<div id="gameControls">
<label for="username">Username:</label>
<input type="text" id="username" value="Player" />
<br>
<label for="colorPicker">Choose Color:</label>
<input type="color" id="colorPicker" value="#ffffff" />
<button id="playButton">Play</button>
</div>
<canvas id="gameCanvas" style="display: none;"></canvas>
<script src="index.js"></script>
</body>

View file

@ -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);

View file

@ -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
}

View file

@ -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: {