Skip to main content
in Javascript

Let's make a video game

In the year of the rat 1972, atari released one of the earliest arcade games. The tennis-themed Pong.

The structure for this project is

  • an entrypoint, app.js
  • a ball, ball.js
  • two paddles, paddle.js
  • the game, pong.js

The ball

The ball needs to tell us the position, if it hits a given coorinate. We need an api for changing the direction and angle.

// ball.js
export default class Ball
{
    constructor(left, top, width, speed = 14, color = 'white')
    {
        this.left = left;
        this.top = top;
        this.speed = speed;
        this.angle = 0;
        this.direction = 'left';
        this.canvasWidth = width;
        this.color = color;
    }

    draw(context)
    {
        if (this.direction === 'left') {
            this.left -= this.speed;
        } else {
            this.left += this.speed;
        }

        this.top += this.angle;

        context.beginPath();
        context.arc(this.left, this.top, 20, 0, 2 * Math.PI);
        context.fillStyle = this.color;
        context.fill();
    }

    hits(paddle) {
        return (
            (this.xStart() <= paddle.xEnd() && this.xStart() >= paddle.xStart())
            || (this.xEnd() >= paddle.xStart() && this.xEnd() <= paddle.xEnd())
        ) && (
            (this.yStart() >= paddle.yStart() && this.yStart() <= paddle.yEnd())
            || (this.yEnd() <= paddle.yEnd() && this.yEnd() >= paddle.yStart())
        );
    }

    changeDirection() {
        if (this.direction === 'left') {
            this.direction = 'right';
        } else {
            this.direction = 'left';
        }
    }

    changeAngle(angle = 0) {
        this.angle = angle;
    }

    respawn() {
        this.changeDirection();
        this.changeAngle(0);
        this.left = this.canvasWidth / 2;
    }

    yStart() {
        return this.top - 20;
    }

    yEnd() {
        return this.top + 20;
    }

    xStart() {
        return this.left - 20;
    }

    xEnd() {
        return this.left + 20;
    }
}

The paddle

We need to be able to move the paddle and know if it hit's the edges of our game.

// paddle.js
export default class Paddle
{
    constructor(left, top, width = 20, height = 100, speed = 1, canvasHeight, color = 'white')
    {
        this.left = left;
        this.top = top;
        this.speed = speed;
        this.width = width;
        this.height = height;
        this.moving = null;
        this.canvasHeight = canvasHeight;
        this.color = color;
    }

    draw(context)
    {
        if (this.moving === 'up') {
            this.moveUp()
        } else if (this.moving === 'down') {
            this.moveDown(context.canvas.height)
        }

        context.beginPath();
        context.rect(this.left, this.top - (this.height / 2), this.width, this.height);
        context.fillStyle = this.color;
        context.fill();
    }

    moveUp()
    {
        this.top -= this.speed;

        if (this.isOverCanvas()) {
            this.moveToStart();
        }
    }

    moveDown()
    {
        this.top += this.speed;

        if (this.isBelowCanvas()) {
            this.moveToEnd();
        }
    }

    angle(top) {
        let center = this.top + (this.height / 2);
        return ((center - top) / (this.height / 2)) * 2;
    }

    moveY(position) {
        let center = this.top + (this.height / 2);

        if (position > center) {
            this.top += this.speed;
        } else if (position < center) {
            this.top -= this.speed;
        }

        if (this.isOverCanvas()) {
            this.moveToStart();
        } else if (this.isBelowCanvas()) {
            this.moveToEnd();
        }
    }

    isBelowCanvas() {
        return this.top > this.canvasHeight - (this.height / 2);
    }

    moveToEnd() {
        this.top = this.canvasHeight - (this.height / 2);
    }

    isOverCanvas() {
        return (this.top - (this.height / 2)) < 0;
    }

    moveToStart() {
        this.top = this.height / 2;
    }

    yStart() {
        return this.top - (this.height / 2);
    }

    yEnd() {
        return this.top + (this.height / 2);
    }

    xStart() {
        return this.left;
    }

    xEnd() {
        return this.left + this.width;
    }
}

The game

The game is responsible for all the game calculations, spawning and moving the ball and paddles, keeping track of the score and the state of the game.

// pong.js
import Ball from "./ball";
import Paddle from "./paddle";

export class Pong
{
    constructor(context)
    {

        this.score = {
            user: 0,
            computer: 0
        }

        this.paused = false;
        this.context = context;
        this.ball = new Ball(
            this.context.canvas.width / 2,
            this.context.canvas.height / 2,
            this.context.canvas.width,
            12,
            '#fff'
        );

        this.paddle = new Paddle(
            10,
            this.context.canvas.height / 2,
            20,
            100,
            6,
            this.context.canvas.height,
            '#e77936'
        );

        this.computer = new Paddle(
            this.context.canvas.width - 30,
            this.context.canvas.height / 2,
            20,
            100,
            1,
            this.context.canvas.height,
            '#e77936'
        );

        addEventListener('keydown', e => {
            if (e.key === 'ArrowUp') {
                this.paddle.moving = 'up';
            } else if (e.key === 'ArrowDown') {
                this.paddle.moving = 'down';
            } else if (e.key === 'Escape') {
                if (this.paused) {
                    this.paused = false;
                    this.run();
                } else {
                    this.paused = true;
                    // Set pause screen
                    // Set speed
                    // Set theme
                    // Save game
                    // Set computer speed
                    // Scoreboard
                }
            }
        });

        addEventListener('keyup', e => {
            if (e.key === 'ArrowUp') {
                this.paddle.moving = null;
            } else if (e.key === 'ArrowDown') {
                this.paddle.moving = null;
            }
        });

        window.requestAnimationFrame(() => this.run());
    }

    run()
    {
        if (this.paused) {
            return;
        }

        this.computer.moveY(this.ball.top);

        if (this.ball.hits(this.paddle)) {
            this.ball.changeDirection();
            this.ball.changeAngle(-this.paddle.angle(this.ball.top));
        } else if (this.ball.hits(this.computer)) {
            this.ball.changeDirection();
            this.ball.changeAngle(-this.computer.angle(this.ball.top));
        }

        if (this.ball.top <= 0) {
            this.ball.angle -= this.ball.angle * 2;
        } else if (this.ball.top >= this.context.canvas.height) {
            this.ball.angle -= this.ball.angle * 2;
        }

        // is out to the left of canvas
        if (this.isRightOfCanvas(this.ball.xStart())) {
            this.score.user++;
            this.ball.respawn();
        } else if (this.isLeftOfCanvas(this.ball.xEnd())) {
            this.score.computer++;
            this.ball.respawn();
        }

        this.context.clearRect(
            0, 0, this.context.canvas.width, this.context.canvas.height
        );

        this.paddle.draw(this.context);
        this.ball.draw(this.context);
        this.computer.draw(this.context);

        // Draw score
        // this.context.fontSize = '200px';
        this.context.font = '40px Arial';
        this.context.fillStyle = 'rgba(255, 255, 255, .4)';
        this.context.fillText(
            `${this.score.user} - ${this.score.computer}`,
            (this.context.canvas.width / 2) - 60,
            40
        );

        window.requestAnimationFrame(() => this.run());
    }

    isLeftOfCanvas(x) {
        return x <= 0;
    }

    isRightOfCanvas(x) {
        return x >= this.context.canvas.width;
    }
}

The entrypoint

This is where we start our game.

// app.js
import {Pong} from "./pong";

new Pong(document.getElementById('game').getContext('2d'));

Have fun