JavaScript: Intro to Web Game Development – Part 6: add win screen and replay

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: