In the previous article, scoring was introduced, but there was no win detection that would end the game. The previous article in the series can be read here:
JavaScript: Intro to Web Game Development – Part 5: ball handling, scoring, and A.I.
In this article, I will show how to detect when a game ends and display a win screen that if clicked will start a new game.
Step 1: Create new variables
We need to add these two new variables to the top of the script:
const WINNING_SCORE = 3; var showingWinScreen = false;
The WINNING_SCORE variable will be used to determine when the game has a winner. The showWinScreen variable will be used to tell the game when to display the “game winner” screen.
Step 2: Add logic to check for the WINNING_SCORE and display the winner screen
Inside the ballReset() function, add this logic at the top:
if(player1Score >= WINNING_SCORE || player2Score >= WINNING_SCORE) { showingWinScreen = true; }
At the top of the ballMovement() function, place this logic:
if(showingWinScreen) { return; }
The return;
there essentially means that if the condition is met, exit out of the current function. Finally, inside the drawComponents() function, add the following logic:
if(showingWinScreen) { gameOverScreen(); } else {
Everything that was previously in this function should now be indented in the else block. This means that when it comes time to draw to the screen, if showingWinScreen equals true, then display the gameOverScreen() instead.
Step 3: Create gameOverScreen() function
This function will display who won the game, while leaving the score and the net on the screen.
function gameOverScreen() { canvasContext.fillStyle = 'white'; if(player1Score >= WINNING_SCORE) { canvasContext.font="54px monospace"; canvasContext.fillText("Winner", canvas_width/8, canvas_height/4); canvasContext.font="44px monospace"; canvasContext.fillText("Left Player", canvas_width/12, canvas_height/3); canvasContext.fillText("Play Again", (canvas_width/10), (0 + canvas_height/2)); } else if(player2Score >= WINNING_SCORE) { canvasContext.font="54px monospace"; canvasContext.fillText("Winner", (canvas_width/2 + canvas_width/8), (0 + canvas_height/4)); canvasContext.font="44px monospace"; canvasContext.fillText("Right Player", (canvas_width/2 + canvas_width/24), (0 + canvas_height/3)); canvasContext.fillText("Play Again", (canvas_width/2 + canvas_width/12), (0 + canvas_height/2)); } return; }
There is text that says “Play again”. All the player has to do is click anywhere on the screen to start a new game. This is implemented with a ‘mousedown’ addEventListener.
Step 4: Create addEventListener and function for mouse clicks on the winner screen
Inside the window.onload function, add this addEventListener code:
canvas.addEventListener('mousedown', handleMouseClick);
This addEventListener will look for mouse clicks. If there is a mouse click it will launch a function called handleMouseClick(). Then the handleMouseClick() function will make sure that the mouse click is coming from the winner screen, reset the scores, and set the showWinScreen variable back to false. Since all of this is happening inside the game loop, the other functions will then be free to launch the game play again, since they are actively checking the status of this variable. Here is what the function looks like:
function handleMouseClick(evt) { if(showingWinScreen) { player1Score = 0; player2Score = 0; showingWinScreen = false; } }
Here is what the complete code looks like so far:
var canvas; var canvasContext; var canvas_width; var canvas_height; // offsets to account for the rounded corners var offsetA = 30; var offsetB = 60; // starting y position of the left paddle var paddle1Y = 100; var paddle2Y = 100; // defaults for paddle thickness and height const PADDLE_THICKNESS = 10; const PADDLE_HEIGHT = 70; // ball variables var ballX = offsetA; var ballY = 0; var ballSpeedX = 10; var ballSpeedY = 4; var player1Score = 0; var player2Score = 0; const WINNING_SCORE = 3; var showingWinScreen = false; window.onload = function() { canvas = document.getElementById("gameCanvas"); canvasContext = canvas.getContext("2d"); canvas_width = canvas.width; canvas_height = canvas.height; var framesPerSecond = 30; setInterval( function() { ballMovement(); drawComponents(); }, 1000/framesPerSecond); canvas.addEventListener('mousedown', handleMouseClick); canvas.addEventListener('mousemove', function(evt) { var mousePos = calculateMousePos(evt); paddle1Y = mousePos.y - (PADDLE_HEIGHT/2); }); } // Function to create rounded corners on the rectangle CanvasRenderingContext2D.prototype.roundRect = function(x, y, width, height, radius, fill, stroke) { // If stroke argument not provided, provide a stroke by default if (typeof stroke == "undefined" ) { stroke = true; } // If no radius is defined, then give it a default of 5 if (typeof radius === "undefined") { radius = 5; } // Start of shape definition this.beginPath(); this.moveTo(x + radius, y); // Draw the top border line this.lineTo(x + width - radius, y); // Draw the top right curve this.quadraticCurveTo(x + width, y, x + width, y + radius); // Draw the right border line this.lineTo(x + width, y + height - radius); // Draw the bottom right curve this.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); // Draw the bottom border line this.lineTo(x + radius, y + height); // Draw the bottom left curve this.quadraticCurveTo(x, y + height, x, y + height - radius); // Draw the left border line this.lineTo(x, y + radius); // Draw the final (top left) curve this.quadraticCurveTo(x, y, x + radius, y); // End of shape definition this.closePath(); // Draw the shape with a line if (stroke) { this.stroke(); } // Fill in the shape with color if (fill) { this.fill(); } } function calculateMousePos(evt) { var rect = canvas.getBoundingClientRect(); var root = document.documentElement; var mouseX = evt.clientX - rect.left - root.scrollLeft; var mouseY = evt.clientY - rect.top - root.scrollTop; return { x:mouseX, y:mouseY }; } function handleMouseClick(evt) { if(showingWinScreen) { player1Score = 0; player2Score = 0; showingWinScreen = false; } } function ballMovement() { if(showingWinScreen) { return; } computerPaddleAI(); ballX = ballX + ballSpeedX; ballY = ballY + ballSpeedY; // left wall if(ballX < offsetA + PADDLE_THICKNESS) { // Detect a hit. Check if the ball is between the top and bottom of the paddle if(ballY > paddle1Y && ballY < (paddle1Y + PADDLE_HEIGHT)) { // console.log("HIT! Ball position: " + ballY) ballSpeedX = -ballSpeedX; // change ball speed of Y to change the angle of return var deltaY = ballY - (paddle1Y + PADDLE_HEIGHT/2); ballSpeedY = deltaY * 0.35; } else if (ballX < offsetA) { player2Score++; // must be BEFORE ballReset() ballReset(); } } // right wall if(ballX > canvas_width-40 - PADDLE_THICKNESS) { // Detect a hit. If the ball is between the top and bottom of the paddle if(ballY > paddle2Y && ballY < (paddle2Y + PADDLE_HEIGHT)) { // console.log("HIT! Ball position: " + ballY) ballSpeedX = -ballSpeedX; // change ball speed of Y to change the angle of return var deltaY = ballY - (paddle2Y + PADDLE_HEIGHT/2 ); ballSpeedY = deltaY * 0.35; } else if (ballX > canvas_width-40) { player1Score++; // must be BEFORE ballReset() ballReset(); } } // top (ceiling) if(ballY < 0) { // bounce off by reversing course ballSpeedY = -ballSpeedY; } // bottom (floor) if(ballY > canvas_height-10) { // bounce off by reversing course ballSpeedY = -ballSpeedY; } } function computerPaddleAI() { var paddle2YCenter = paddle2Y + (PADDLE_HEIGHT/2); if(paddle2YCenter < ballY - 35) { paddle2Y = paddle2Y + 6; } else if(paddle2YCenter > ballY + 35) { paddle2Y = paddle2Y - 6; } } function ballReset() { if(player1Score >= WINNING_SCORE || player2Score >= WINNING_SCORE) { showingWinScreen = true; } computerPaddleAI(); ballSpeedX = -ballSpeedX; ballX = canvas_width/2; ballY = canvas_height/2; } function drawComponents() { // this line blacks out the background to give the appearance of animation canvasContext.fillStyle = 'black'; canvasContext.roundRect(0, 0, canvas_width, canvas_height, 40, "black", 0); if(showingWinScreen) { gameOverScreen(); } else { // Left paddle canvasContext.fillStyle = 'white'; canvasContext.fillRect(offsetA, paddle1Y, PADDLE_THICKNESS, PADDLE_HEIGHT); // Right paddle canvasContext.fillStyle = 'white'; // canvasContext.fillRect(canvas_width-40, canvas_height-120, PADDLE_THICKNESS, PADDLE_HEIGHT); canvasContext.fillRect(canvas_width-40, paddle2Y, PADDLE_THICKNESS, PADDLE_HEIGHT); // Draw the ball canvasContext.fillStyle = 'white'; canvasContext.fillRect(ballX, ballY, 10, 10); } // Draw scores canvasContext.font="40px monospace"; // Left side score - make room for more digits if (player1Score < 10) { canvasContext.fillText(player1Score, (canvas_width/2)-60, 40); } else if (player1Score > 99) { canvasContext.fillText(player1Score, (canvas_width/2)-100, 40); } else { canvasContext.fillText(player1Score, (canvas_width/2)-80, 40); } // Right side score canvasContext.fillText(player2Score, (canvas_width/2)+35, 40); drawNet(); } function gameOverScreen() { canvasContext.fillStyle = 'white'; if(player1Score >= WINNING_SCORE) { canvasContext.font="54px monospace"; canvasContext.fillText("Winner", canvas_width/8, canvas_height/4); canvasContext.font="44px monospace"; canvasContext.fillText("Left Player", canvas_width/12, canvas_height/3); canvasContext.fillText("Play Again", (canvas_width/10), (0 + canvas_height/2)); } else if(player2Score >= WINNING_SCORE) { canvasContext.font="54px monospace"; canvasContext.fillText("Winner", (canvas_width/2 + canvas_width/8), (0 + canvas_height/4)); canvasContext.font="44px monospace"; canvasContext.fillText("Right Player", (canvas_width/2 + canvas_width/24), (0 + canvas_height/3)); canvasContext.fillText("Play Again", (canvas_width/2 + canvas_width/12), (0 + canvas_height/2)); } return; } function drawNet() { for(var i=0; i < canvas_height; i += 30) { canvasContext.fillStyle = 'white'; canvasContext.fillRect(canvas_width/2, i, 2, 10, 'white'); } }
Here is the game so far: