Add tennis minigame
This commit is contained in:
parent
15cf01aae0
commit
26ea586130
3 changed files with 232 additions and 4 deletions
|
|
@ -111,8 +111,7 @@ dialog.window button {
|
||||||
background-color: rgba(0, 0, 0, 0.3);
|
background-color: rgba(0, 0, 0, 0.3);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
dialog.window iframe {
|
dialog.window canvas {
|
||||||
border-top: none;
|
|
||||||
border-bottom: solid;
|
border-bottom: solid;
|
||||||
border-left: solid;
|
border-left: solid;
|
||||||
border-right: solid;
|
border-right: solid;
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,17 @@
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/third-party/media/images/favicon.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="/third-party/media/images/favicon.png">
|
||||||
<script defer src="/js/include.js"></script>
|
<script defer src="/js/include.js"></script>
|
||||||
<script defer src="/js/window.js"></script>
|
<script defer src="/js/window.js"></script>
|
||||||
|
<script defer src="/js/tennis.js"></script>
|
||||||
<title>www.madstuff.net</title>
|
<title>www.madstuff.net</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div include="/html-prefabs/banner-index.html"></div>
|
<div include="/html-prefabs/banner-index.html"></div>
|
||||||
<dialog open closedby="none" id="window" class="window">
|
<dialog open closedby="none" id="window" class="window">
|
||||||
<div id="window-titlebar" class="titlebar">
|
<div id="window-titlebar" class="titlebar">
|
||||||
<small>Window</small>
|
<small>Legally Distinct Tennis Game</small>
|
||||||
<button command="close" commandfor="window">X</button>
|
<button command="close" commandfor="window">X</button>
|
||||||
</div>
|
</div>
|
||||||
<iframe src="/etc/placeholder.html" frameborder="0"></iframe>
|
<canvas id="canvas" width="320" height="180"></canvas>
|
||||||
</dialog>
|
</dialog>
|
||||||
<main>
|
<main>
|
||||||
<section>
|
<section>
|
||||||
|
|
|
||||||
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