diff --git a/frontend/static/js/index.js b/frontend/static/js/index.js
index 10e0878..3d58006 100644
--- a/frontend/static/js/index.js
+++ b/frontend/static/js/index.js
@@ -6,6 +6,9 @@ import Chat from "./views/Chat.js";
import HomeView from "./views/HomeView.js";
import RegisterView from "./views/accounts/RegisterView.js";
import LogoutView from "./views/accounts/LogoutView.js";
+import GameView from "./views/Game.js"
+
+
import AbstractRedirectView from "./views/AbstractRedirectView.js";
import MeView from "./views/MeView.js";
import ProfilePageView from "./views/profiles/ProfilePageView.js";
@@ -44,6 +47,7 @@ const router = async (uri) => {
{ path: "/home", view: HomeView },
{ path: "/me", view: MeView },
{ path: "/matchmaking", view: MatchMakingView },
+ { path: "/game", view: GameView },
];
// Test each route for potential match
@@ -96,4 +100,4 @@ document.addEventListener("DOMContentLoaded", () => {
router(location.pathname);
});
-export { client, navigateTo }
\ No newline at end of file
+export { client, navigateTo }
diff --git a/frontend/static/js/views/Game.js b/frontend/static/js/views/Game.js
new file mode 100644
index 0000000..87dd0cb
--- /dev/null
+++ b/frontend/static/js/views/Game.js
@@ -0,0 +1,250 @@
+import AbstractView from './AbstractView.js'
+
+export default class extends AbstractView {
+ constructor(params) {
+ super(params, 'Game');
+ this.game = null;
+ }
+
+ async getHtml() {
+ return `
+
Game
+
+
+ `;
+ }
+
+ async postInit() {
+ document.getElementById('startGameButton').onclick = this.startGame.bind(this);
+ document.getElementById('stopGameButton').onclick = this.stopGame.bind(this);
+ }
+
+ startGame() {
+ if (this.game == null) {
+ document.getElementById('startGameButton').innerHTML = 'Reset Game';
+ this.game = new Game;
+ }
+ else {
+ document.getElementById('app').removeChild(this.game.canvas);
+ this.game.cleanup();
+ this.game = new Game;
+ }
+ }
+
+ stopGame() {
+ if (!this.game)
+ return;
+ document.getElementById('app').removeChild(this.game.canvas);
+ document.getElementById('app').removeChild(this.game.scoresDisplay);
+ this.game.cleanup();
+ this.game = null;
+ document.getElementById('startGameButton').innerHTML = 'Start Game';
+ }
+}
+
+class Game {
+ constructor() {
+ //Global variables
+ this.def = {
+ CANVASHEIGHT: 270,
+ CANVASWIDTH: 480,
+ PADDLEHEIGHT: 70,
+ PADDLEWIDTH: 10,
+ PADDLEMARGIN: 5,
+ PADDLESPEED: 3,
+ BALLRADIUS: 5,
+ BALLSPEED: 2,
+ BALLSPEEDINCR: 0.15,
+ MAXBOUNCEANGLE: 10 * (Math.PI / 12),
+ MAXSCORE: 6
+ };
+
+ this.canvas = document.createElement('canvas');
+ this.canvas.id = 'gameCanvas';
+ this.canvas.width = this.def.CANVASWIDTH;
+ this.canvas.height = this.def.CANVASHEIGHT;
+ this.canvas.style.border = '1px solid #d3d3d3';
+ this.canvas.style.backgroundColor = '#f1f1f1';
+ this.context = this.canvas.getContext('2d');
+ document.getElementById('app').appendChild(this.canvas);
+ this.scoresDisplay = document.createElement('p');
+ this.scoresDisplay.innerHTML = 'Scores: 0 - 0';
+ document.getElementById('app').appendChild(this.scoresDisplay);
+
+ this.players = [
+ {
+ paddle: new Paddle(this.context,
+ this.def.PADDLEMARGIN,
+ this.def),
+ score: 0
+ },
+ {
+ paddle: new Paddle(this.context,
+ this.def.CANVASWIDTH - this.def.PADDLEMARGIN - this.def.PADDLEWIDTH,
+ this.def),
+ score: 0
+ }
+ ];
+ this.ballStartSide = 0;
+ this.ballRespawned = false;
+ this.ball = new Ball(this.context, this.def, this.ballStartSide);
+
+ this.interval = setInterval(this.updateGame.bind(this), 10);
+
+ this.keys = [];
+ this.keyUpHandler = this.keyUpHandler.bind(this);
+ this.keyDownHandler = this.keyDownHandler.bind(this);
+ document.addEventListener('keydown', this.keyDownHandler);
+ document.addEventListener('keyup', this.keyUpHandler);
+ }
+
+ cleanup() {
+ clearInterval(this.interval);
+ document.removeEventListener('keydown', this.keyDownHandler);
+ document.removeEventListener('keyup', this.keyUpHandler);
+ this.canvas.style.display = 'none';
+ }
+
+ clear() {
+ this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
+ }
+
+ updateGame() {
+ //Paddle movement
+ if (this.keys.includes('s') &&
+ this.players[0].paddle.y + this.def.PADDLEHEIGHT < this.def.CANVASHEIGHT - this.def.PADDLEMARGIN)
+ this.players[0].paddle.y += this.def.PADDLESPEED;
+ if (this.keys.includes('w') &&
+ this.players[0].paddle.y > 0 + this.def.PADDLEMARGIN)
+ this.players[0].paddle.y -= this.def.PADDLESPEED;
+
+ if (this.keys.includes('ArrowDown') &&
+ this.players[1].paddle.y + this.def.PADDLEHEIGHT < this.def.CANVASHEIGHT - this.def.PADDLEMARGIN)
+ this.players[1].paddle.y += this.def.PADDLESPEED;
+ if (this.keys.includes('ArrowUp') &&
+ this.players[1].paddle.y > 0 + this.def.PADDLEMARGIN)
+ this.players[1].paddle.y -= this.def.PADDLESPEED;
+
+ //GOOAAAAL
+ if (this.ball.x <= 0)
+ this.updateScore(this.players[0].score, ++this.players[1].score);
+ else if (this.ball.x >= this.def.CANVASWIDTH)
+ this.updateScore(++this.players[0].score, this.players[1].score);
+
+ //Ball collisions
+ if (this.detectCollision(this.players[0].paddle, this.ball))
+ this.calculateBallVelocity(this.players[0].paddle.getCenter().y, this.ball);
+ else if (this.detectCollision(this.players[1].paddle, this.ball))
+ this.calculateBallVelocity(this.players[1].paddle.getCenter().y, this.ball, -1);
+
+ if (this.ball.y - this.ball.radius <= 0)
+ this.ball.vy *= -1;
+ else if (this.ball.y + this.ball.radius >= this.canvas.height)
+ this.ball.vy *= -1;
+
+ if (!this.ballRespawned) {
+ this.ball.x += this.ball.vx;
+ this.ball.y += this.ball.vy;
+ }
+
+ this.clear();
+ this.players[0].paddle.update();
+ this.players[1].paddle.update();
+ this.ball.update();
+ }
+
+ updateScore(p1Score, p2Score) {
+ if (p1Score > this.def.MAXSCORE) {
+ this.scoresDisplay.innerHTML = 'Player 1 wins!! GGS';
+ this.cleanup();
+ }
+ else if (p2Score > this.def.MAXSCORE) {
+ this.scoresDisplay.innerHTML = 'Player 2 wins!! GGS';
+ this.cleanup();
+ } else {
+ this.scoresDisplay.innerHTML = `Scores: ${p1Score} - ${p2Score}`;
+ this.ballStartSide = 1 - this.ballStartSide;
+ this.ball = new Ball(this.context, this.def, this.ballStartSide);
+ this.ballRespawned = true;
+ new Promise(r => setTimeout(r, 300))
+ .then(_ => this.ballRespawned = false);
+ }
+ }
+
+ detectCollision(paddle, ball) {
+ let paddleCenter = paddle.getCenter();
+ let dx = Math.abs(ball.x - paddleCenter.x);
+ let dy = Math.abs(ball.y - paddleCenter.y);
+ if (dx <= ball.radius + paddle.width / 2 &&
+ dy <= ball.radius + paddle.height / 2)
+ return true;
+ return false;
+ }
+
+ calculateBallVelocity(paddleCenterY, ball, side = 1) {
+ let relativeIntersectY = paddleCenterY - ball.y;
+ let normRelIntersectY = relativeIntersectY / this.def.PADDLEHEIGHT / 2;
+ let bounceAngle = normRelIntersectY * this.def.MAXBOUNCEANGLE;
+
+ ball.speed += this.def.BALLSPEEDINCR;
+ ball.vx = ball.speed * side * Math.cos(bounceAngle);
+ ball.vy = ball.speed * -Math.sin(bounceAngle);
+ }
+
+ keyUpHandler(ev) {
+ const idx = this.keys.indexOf(ev.key);
+ if (idx != -1)
+ this.keys.splice(idx, 1);
+ }
+
+ keyDownHandler(ev) {
+ if (!this.keys.includes(ev.key))
+ this.keys.push(ev.key);
+ }
+}
+
+class Paddle {
+ constructor(context, paddleSide, def) {
+ this.width = def.PADDLEWIDTH;
+ this.height = def.PADDLEHEIGHT;
+ this.x = paddleSide;
+ this.y = def.CANVASHEIGHT / 2 - this.height / 2;
+ this.ctx = context;
+ this.update();
+ }
+
+ update() {
+ this.ctx.fillStyle = 'black';
+ this.ctx.fillRect(this.x, this.y, this.width, this.height);
+ }
+
+ getCenter() {
+ return {
+ x: this.x + this.width / 2,
+ y: this.y + this.height / 2
+ };
+ }
+}
+
+class Ball {
+ constructor(context, def, startSide) {
+ this.radius = def.BALLRADIUS;
+ this.speed = def.BALLSPEED;
+ this.x = def.CANVASWIDTH / 2;
+ this.y = def.CANVASHEIGHT / 2;
+ this.vy = 0;
+ if (startSide === 0)
+ this.vx = -this.speed;
+ else
+ this.vx = this.speed;
+ this.ctx = context;
+ this.update();
+ }
+
+ update() {
+ this.ctx.fillStyle = 'black';
+ this.ctx.beginPath();
+ this.ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI);
+ this.ctx.fill();
+ }
+}