Add tennis minigame
This commit is contained in:
parent
15cf01aae0
commit
26ea586130
3 changed files with 232 additions and 4 deletions
228
js/tennis.js
Normal file
228
js/tennis.js
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
/**
|
||||
* @file Implements a tennis game.
|
||||
*
|
||||
* @licstart The following is the entire license notice for the JavaScript code
|
||||
* in this page.
|
||||
*
|
||||
* Copyright (C) 2026 Madison L.
|
||||
*
|
||||
* The JavaScript code in this page is free software: you can redistribute it
|
||||
* and/or modify it under the terms of the GNU General Public License (GNU GPL)
|
||||
* as published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version. The code is distributed
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
|
||||
*
|
||||
* As additional permission under GNU GPL version 3 section 7, you may
|
||||
* distribute non-source (e.g., minimized or compacted) forms of that code
|
||||
* without the copy of the GNU GPL normally required by section 4, provided you
|
||||
* include this license notice and a URL through which recipients can access the
|
||||
* Corresponding Source.
|
||||
*
|
||||
* @licend The above is the entire license notice for the JavaScript code in
|
||||
* this page.
|
||||
*
|
||||
* Behold! My latest contribution to the site's growing technical debt, this
|
||||
* time with 80% more duct tape and glue. Bask in the constant recomputations of
|
||||
* the same values, repeated logic, and magic numbers galore! I just want to get
|
||||
* this working, I'll fix it later.
|
||||
*/
|
||||
|
||||
const gameState = {
|
||||
START_MENU: 0,
|
||||
IN_GAME: 1,
|
||||
PAUSED: 2,
|
||||
}
|
||||
|
||||
function rectsIntersecting(a, b) {
|
||||
return (a.x-(a.w/2) < b.x+(b.w/2)) && (a.x+(a.w/2) > b.x-(b.w/2))
|
||||
&& (a.y-(a.w/2) < b.y+(b.w/2)) && (a.y+(a.w/2) > b.y-(b.w/2));
|
||||
}
|
||||
|
||||
function initGame() {
|
||||
let canvas = document.getElementById("canvas");
|
||||
let gameData = {
|
||||
canvas: canvas,
|
||||
canvasContext: canvas.getContext("2d"),
|
||||
paddleLeft: {
|
||||
rectangle: { x: 10, y: canvas.height/2, w: 10, h: 20 }, // px
|
||||
controller: updatePaddleP,
|
||||
},
|
||||
paddleRight: {
|
||||
rectangle: {
|
||||
x: canvas.width-10, // px
|
||||
y: canvas.height/2, // px
|
||||
w: 10, // px
|
||||
h: 20, // px
|
||||
},
|
||||
controller: updatePaddleCPU,
|
||||
},
|
||||
paddleSpeed: 1, // px/s
|
||||
ball: {
|
||||
rectangle: {
|
||||
x: canvas.width/2,
|
||||
y: canvas.height/2,
|
||||
w: 10,
|
||||
h: 10
|
||||
},
|
||||
direction: { x: -1, y: -1 },
|
||||
speed: 0.5, // px/s
|
||||
increment: 0.01, // px/collision
|
||||
paddleHit: false,
|
||||
},
|
||||
score: { left: 0, right: 0 },
|
||||
gameState: gameState.START_MENU,
|
||||
targetFrameTime: 1/30, // 30 FPS
|
||||
upPressed: false,
|
||||
downPressed: false,
|
||||
};
|
||||
window.addEventListener("keydown", (event) => {
|
||||
if (event.code == "KeyW")
|
||||
gameData.upPressed = true;
|
||||
else if (event.code == "KeyS")
|
||||
gameData.downPressed = true;
|
||||
else if ((event.code == "Escape")
|
||||
&& (gameData.gameState != gameState.START_MENU))
|
||||
gameData.gameState = (gameData.gameState == gameState.IN_GAME)
|
||||
? gameState.PAUSED : gameState.IN_GAME;
|
||||
else if ((event.code == "Enter")
|
||||
&& (gameData.gameState == gameState.START_MENU))
|
||||
gameData.gameState = gameState.IN_GAME;
|
||||
});
|
||||
window.addEventListener("keyup", (event) => {
|
||||
if (event.code == "KeyW")
|
||||
gameData.upPressed = false;
|
||||
else if (event.code == "KeyS")
|
||||
gameData.downPressed = false;
|
||||
});
|
||||
return gameData;
|
||||
}
|
||||
|
||||
function updatePaddleP(paddle, gameData) {
|
||||
if ((gameData.upPressed) && ((paddle.rectangle.y-paddle.rectangle.w) >= 0))
|
||||
paddle.rectangle.y -= gameData.paddleSpeed;
|
||||
if ((gameData.downPressed)
|
||||
&& ((paddle.rectangle.y+paddle.rectangle.w) <= gameData.canvas.height))
|
||||
paddle.rectangle.y += gameData.paddleSpeed;
|
||||
}
|
||||
|
||||
function updatePaddleCPU(paddle, gameData) {
|
||||
if (gameData.ball.rectangle.x < gameData.canvas.width/2)
|
||||
return;
|
||||
if (paddle.rectangle.y < gameData.ball.rectangle.y) {
|
||||
paddle.rectangle.y += gameData.paddleSpeed;
|
||||
}
|
||||
if (paddle.rectangle.y > gameData.ball.rectangle.y) {
|
||||
paddle.rectangle.y -= gameData.paddleSpeed;
|
||||
}
|
||||
}
|
||||
|
||||
function resetBall(ball) {
|
||||
ball.rectangle.x = canvas.width/2;
|
||||
ball.rectangle.y = canvas.height/2;
|
||||
ball.direction.x *= -1;
|
||||
ball.direction.y *= -1;
|
||||
ball.speed = 0.5;
|
||||
}
|
||||
|
||||
function updateBall(gameData) {
|
||||
gameData.ball.rectangle.x += gameData.ball.direction.x*gameData.ball.speed;
|
||||
gameData.ball.rectangle.y += gameData.ball.direction.y*gameData.ball.speed;
|
||||
// r/programminghorror:
|
||||
if ((gameData.ball.rectangle.y <= 0)
|
||||
|| (gameData.ball.rectangle.y >= gameData.canvas.height)) {
|
||||
gameData.ball.direction.y *= -1;
|
||||
gameData.ball.paddleHit = false;
|
||||
gameData.ball.speed += 0.01;
|
||||
} else if ((rectsIntersecting(gameData.ball.rectangle,
|
||||
gameData.paddleLeft.rectangle)
|
||||
|| rectsIntersecting(gameData.ball.rectangle,
|
||||
gameData.paddleRight.rectangle))
|
||||
&& !gameData.ball.paddleHit) {
|
||||
/*
|
||||
* HACK: Ball gets stuck in paddle sometimes. This kinda fixes it, but
|
||||
* it results in occasional weird behaviour where the ball is
|
||||
* reflected when it probably shouldn't be. I think that's the
|
||||
* least of the problems with this function though.
|
||||
*/
|
||||
gameData.ball.paddleHit = true;
|
||||
gameData.ball.direction.x *= -1;
|
||||
gameData.ball.speed += 0.01;
|
||||
} else if (gameData.ball.rectangle.x <= 0) {
|
||||
++gameData.score.right;
|
||||
resetBall(gameData.ball);
|
||||
} else if (gameData.ball.rectangle.x >= gameData.canvas.width) {
|
||||
++gameData.score.left;
|
||||
resetBall(gameData.ball);
|
||||
}
|
||||
}
|
||||
|
||||
function drawPaddle(paddle, gameData) {
|
||||
gameData.canvasContext.fillStyle = "white";
|
||||
const halfWidth = paddle.rectangle.w/2;
|
||||
const halfHeight = paddle.rectangle.h/2;
|
||||
gameData.canvasContext.fillRect(paddle.rectangle.x-halfWidth,
|
||||
paddle.rectangle.y-halfHeight,
|
||||
paddle.rectangle.w,
|
||||
paddle.rectangle.h);
|
||||
}
|
||||
|
||||
function drawBall(gameData) {
|
||||
gameData.canvasContext.fillStyle = "white";
|
||||
const halfWidth = gameData.ball.rectangle.w/2;
|
||||
gameData.canvasContext.fillRect(gameData.ball.rectangle.x-halfWidth,
|
||||
gameData.ball.rectangle.y-halfWidth,
|
||||
gameData.ball.rectangle.w,
|
||||
gameData.ball.rectangle.h);
|
||||
}
|
||||
|
||||
function drawUI(gameData) {
|
||||
gameData.canvasContext.fillStyle = "white";
|
||||
gameData.canvasContext.font = "20px 'Courier New'";
|
||||
if (gameData.gameState == gameState.START_MENU) {
|
||||
gameData.canvasContext.fillText("Press Enter to Play", 45, 90, 250);
|
||||
} else if (gameData.gameState == gameState.PAUSED) {
|
||||
gameData.canvasContext.fillText("PAUSED", 120, 100, 100);
|
||||
} else {
|
||||
gameData.canvasContext.fillRect(gameData.canvas.width/2, 0, 2,
|
||||
gameData.canvas.height);
|
||||
gameData.canvasContext.fillText(gameData.score.left,
|
||||
(gameData.canvas.width/2)-(20*2)-5,
|
||||
20,
|
||||
80);
|
||||
gameData.canvasContext.fillText(gameData.score.right,
|
||||
(gameData.canvas.width/2)+(20*2),
|
||||
20,
|
||||
80);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function updateGame(gameData) {
|
||||
gameData.paddleLeft.controller(gameData.paddleLeft, gameData);
|
||||
gameData.paddleRight.controller(gameData.paddleRight, gameData);
|
||||
updateBall(gameData);
|
||||
}
|
||||
|
||||
function drawGame(gameData) {
|
||||
gameData.canvasContext.fillStyle = "black";
|
||||
gameData.canvasContext.fillRect(0, 0, gameData.canvas.width,
|
||||
gameData.canvas.height);
|
||||
if (gameData.gameState == gameState.IN_GAME) {
|
||||
drawPaddle(gameData.paddleLeft, gameData);
|
||||
drawPaddle(gameData.paddleRight, gameData);
|
||||
drawBall(gameData);
|
||||
}
|
||||
drawUI(gameData);
|
||||
}
|
||||
|
||||
function runGame() {
|
||||
let gameData = initGame();
|
||||
setInterval(function() {
|
||||
if (gameData.gameState == gameState.IN_GAME)
|
||||
updateGame(gameData);
|
||||
drawGame(gameData);
|
||||
}, gameData.targetFrameTime);
|
||||
}
|
||||
|
||||
runGame();
|
||||
Loading…
Add table
Add a link
Reference in a new issue