Make Pong with JavaScript & Collider.JAM
Tags: javascript, gamedev, tutorial, beginners
Published: 2020-11-30 20:38:11

> Tools Needed:
> * NodeJS and NPM
> * any modern web browser
> * any text editor
> 
> Full Source: [GitHub](https://github.com/invider/pong-ce.mod).
> Framework: [Collider.JAM](http://collider.land), run `npm i -g collider.jam` to install.

## Intro

Pong was created in 1972 by Allan Alcorn, the 2nd non-founding employee of Atari, as an exercise. Nolan Bushnell, the Atari co-founder, reasoned that an electronic version of ping-pong will be a perfect way to get familiar with arcade game development. Especially considering that a similar concept had already been implemented in Magnavox Odyssey. 

Little did they know, that Pong would become a booster that brought Atari into a multi-billion corporation orbit.

Today, Pong is considered to be an arcade classic. Also, with its iconic gameplay, it still works as a good exercise in game development.

Thus, this tutorial covers all the steps necessary to implement a web-based clone of Pong with Collider.JAM and JavaScript.

## Core Concepts

We need at least 3 components to implement a viable Pong version:

* a puck moved by its speed vector
* two paddles controlled by players
* a score counter

There is also an invisible, but nevertheless present, simple physics simulation to detect the puck collisions with the paddles and the edges.

The game scene is going to be implemented in a single *mod*.

Inside, we will place the */dna* folder to keep our prototypes.

> Collider.JAM has the concept of *mods*, which can represent modules, plugins, scenes, levels, game layers, and game states among other things.
>
> You can view a *mod* as a minigame with its own code, structure, resources... It can be the main menu or a player configuration screen, or maybe a map screen.
>
> Usually the *mod* structure is determined by the corresponding directory. A *mod* has it's own *dna*, *lab*, *trap* and other folders.


There are going to be only two prototypes in */dna* - Puck and Paddle.

A single object in */lab/score.js* will represent the score indicator. The indicator is a singleton existing for the whole game lifespan. So instead of putting it in *dna* and creating in _setup()_, we just put it directly in *lab*.

The */res* folder will keep all sound effects. We don't need any bitmap or vector graphics in this tutorial, since everything is drawn by code.

The *trap* folder keeps event handlers. Control events like _keyDown_ and _keyUp_ as well as custom events like _newGame_ and _spawnPunk_ are trapped here.

> Note, that a file location and name are important in Collider.JAM.
>
> For example, there won't be any score if *score.js* will be placed outside of */lab*. Objects in */lab* are considered to be "alive".
>
> Puck and Paddle MUST be placed in */dna* for similar reason. Constructors and factories are expected to be in */dna*. Constructor names MUST be capitalized - that is how Collider.JAM understands it deals with constructors.
>
> So be careful with the file and object naming and placement. It often affects the object's behavior.


## Foundation

Create a folder named _pong.mod_. The _.mod_ extension is necessary for Collider.JAM to determine the root of the project.

Once created, you can _cd_ into it:

    cd pong.mod

And run the empty mod with:

    jam -d

The <i>-d</i> flag tells Collider.JAM to enable development and debug features.

It is very useful in development.

> Make sure you have [_Collider.JAM_ installed](http://collider.land) before starting this tutorial. The easiest way is to run `npm i -g collider.jam` in a terminal emulator.
>
> Collider.JAM makes all kind of JavaScript magic to hide the boilerplate and make development smooth. You can find more about Collider.JAM in [online documentation](http://collider.land/help/)


## Puck

This prototype is located in _/dna/Puck.js_ and implements the puck behavior and visuals.

### defaults

Let's declare some constants and default values:
```javascript
// dna/Puck.js

const MAX_SPEED = 1500
const HIT_ACCELERATION = 1.07

const df = {
    x: 0,
    y: 0,
    r: 10,
    hold: 1,
    speed: 100,
}
```


### constructor

We'll use the _class_ syntax to declare our prototype. It's concise and works well except for a few special cases:

```javascript
// dna/Puck.js
// ...

class Puck {

    constructor(st) {
        augment(this, df, st) // set default and init values

        // select a random direction
        let fi = ( rnd() * .4*PI - .2*PI ) - ( PI * floor(rnd(2)) )
        this.dx = cos(fi)
        this.dy = sin(fi)
    }
}
```

The _augment()_ function is provided by Collider.JAM and augments the target object with values from source objects.

We assign default values from *df* first and init values form *st* later (so the init values can override the default ones). It is a common idiom in Collider.JAM object initialization - declare an object with default values, pass an object with init values, and then augment the constructed object with both.


### behavior

The puck needs the _evo(dt)_ function to determine its behavior and the _draw()_ function to define its look.

The _evo(dt)_ is the most complex one:

```javascript
    evo(dt) {
        if (lab.score.countdown) return // the counter is still on

        // we need a hold timer to prevent the puck
        // from moving the moment it's been created
        if (this.hold > 0) {
            // hold on
            this.hold -= dt
            // start the movement when the time is up
            if (this.hold <= 0) lib.sfx(res.sfx.slide, .5)
            return
        }

        // save previous x/y
        const px = this.x
        const py = this.y

        // move
        let touched = false
        this.x += this.dx * this.speed * dt
        this.y += this.dy * this.speed * dt

        // detect edge collisions
        const r = this.r
        if (this.x < r) {
            // hit the left edge
            kill(this)
            trap('score', 'right')
        } else if (this.x > rx(1)-r) {
            // hit the right edge
            kill(this)
            trap('score', 'left')
        }
        if (this.y < r) {
            // reflect from the top edge
            this.y = r
            this.dy *= -1
            touched = true
            lib.sfx(res.sfx.boing, .2)
        } else if (this.y > ry(1)-r) {
            // reflect from the bottom edge
            this.y = ry(1)-r
            this.dy *= -1
            touched = true
            lib.sfx(res.sfx.boing, .2)
        }

        // detect collision with paddles
        const puck = this
        lab._ls.forEach(e => {
            if (e.touch && e.touch(puck)) {
                touched = true
                this.speed = min(this.speed * HIT_ACCELERATION, MAX_SPEED)
            }
        })

        if (touched) {
            // move back to previous coordinates
            this.x = px
            this.y = py
        }
    }
```

First, we need two guards to prevent evolution from happening while the game countdown is still on or we are holding the puck. If the countdown value in _lab.score_ object is anything, but 0, we skip the evolution. We let the _score_ object itself handle the countdown behavior.

The _hold_ value tracks the time left to keep the puck frozen - we don't want to launch the puck the moment it's created. We have to reduce the timer until it's 0 or less, then we play a sound effect and the puck evolution begins.

The evolution itself has two main components - movement and collision detection.

We preserve coordinates before the movement to jump back in case of collision. That way we can prevent the tunnel effect through the edges and paddles. It is a crude and not exactly precise approach, but it works fine in our case.

The collision detection itself is split into two phases - collision with the edges and collision with the paddles.

The edge collision is handled by Puck locally. Notice the difference between the left/right and the top/bottom edges. For the top and the bottom edge we have to reflect the puck vector over the Y-axis:

     this.dy *= -1

and play the boing sound effect.

In the case of the left or the right edge, we kill the puck and score the corresponding side. The _score_ logic is moved out into an external trap. It is a game-level event and it is a good practice to keep it in a separate function instead of being hidden in Puck.

The collision detection with paddles is different since it is handled mostly by the paddles.

We iterate over all nodes in _/lab_ and find the ones with *touch* (we assume that *touch()* will be a function here).
It means the *touch()* function MUST be defined on all entities the puck can touch (paddles in our case).

When the paddle hit is detected, we raise the _touched_ flag and increase the speed. The puck movement vector reflection is done in the paddle itself since it depends on the place on the paddle we hit.


### rendering

The _draw()_ procedure of Puck is pretty simple - we just have to draw a circle:

```javascript
    draw() {
        lineWidth(2)
        stroke(.55, .5, .5)
        circle(this.x, this.y, this.r)
    }
```

To setup the drawing, we set the line width in pixels and the stroke HSL color. Then we call the _circle()_ function to draw the circle.



## Paddle

This *class* represents the left and the right paddles.

Its _draw()_ and _evo(dt)_ functions are quite simple. The _touch()_ method is the most complex one and handles the collision detection with the puck. It is also responsible for the puck movement vector reflection according to the _REFLECT\_VECTORS_ table.


### defaults

First, we declare the *df* default object with Paddle width and height. Then we declare the _REFLECT\_VECTORS_ table - it contains the angles to the normal vector for each Paddle contact area.

```javascript
// dna/Paddle.js

const df = {
    w: 15,
    h: 100,
}

const REFLECT_VECTORS = [
     .25,
     .20,
     .15,
     .10,
      0,
      0,
    -.10,
    -.15,
    -.20,
    -.25,
]
```


### constructor

```javascript
class Paddle {

    constructor(st) {
        augment(this, df, st) // set default and init values
        this.actions = {}     // a storage object for up and down actions
        this.speed = ry(1)    // speed = screen width in pixels
    }

    init() {
        this.left = (this.name === 'left')
    }
}
```

The constructor augments default and init values, creates a holder for actions and defines the speed.

The speed is defined as screen height in pixels/second.
Which means a paddle can travel from top to bottom in one second.

The _init()_ function checks the name of the paddle
and raises the _left_ flag if it is 'left'.

We can't place that in the constructor, since the object
might not be named yet during the construction. The _init()_ is called by Collider.JAM after the node
is named and attached to the scene tree.

### collisions

This is where the most of the math happening:
```javascript
    rect() {
        return {
            x1: this.x-this.w/2,
            y1: this.y-this.h/2,
            x2: this.x+this.w/2,
            y2: this.y+this.h/2,
        }
    }

    touch(puck) {
        const { x1, y1, x2, y2 } = this.rect()
        const x = this.left? x2 : x1
        const d = lib.math.distanceToSegment(puck.x, puck.y, x, y1, x, y2)

        if (d < puck.r) {
            // calculate normal vector components
            const nvec = lib.math.normalVector(x, y1, x, y2) 
            // normal vector is inverted for the left paddle
            // |           |
            // |-->  o  <--|
            // |           |
            const nx = this.left? -nvec[0] : nvec[0]
            const ny = this.left? -nvec[1] : nvec[1]

            // calculate relative vertical hit point
            const dy = puck.y - this.y

            // reflection angles are inverted for the left paddle
            const dir = this.left? -1 : 1
            let fi = atan2(ny, nx)
            const zone = limit(floor((dy + 50)/10), 0, 9)
            fi += dir * REFLECT_VECTORS[zone] * PI

            puck.dx = cos(fi)
            puck.dy = sin(fi)

            lib.sfx(res.sfx.boing, .3)
            return true
        }
        return false
    }
```

The _rect()_ is a utility function that calculates the top-left and the bottom-right coordinates.

The _touch(puck)_ function accepts the puck and tries to detect collision.

The collision is determined simply by calculating the distance between the puck center and the active segment of the paddle (the one facing the game field). If the distance is less than the puck radius, we consider the collision test positive.

Once the collision is detected, we calculate the angle of the normal vector. Then we calculate the impact zone and use it to determine the angle of the reflection vector to the normal vector.

The reflection angle is used to set the new movement vector for the puck.


### behavior

Here the paddle x coordinate gets dynamically adjusted. That way, the game continues to function properly even when the browser window size is changed.

The second part of the function takes care of the movement
if the corresponding action is triggered.

```javascript
    evo(dt) {
        // adjust x coordinate
        if (this.left) this.x = rx(.05)
        else this.x = rx(.95)

        // move according to pressed keys
        if (this.actions.up) {
            this.y -= this.speed * dt
            if (this.y < this.h/2) this.y = this.h/2 // top edge
        }
        if (this.actions.down) {
            this.y += this.speed * dt
            if (this.y > ry(1)-this.h/2) this.y = ry(1)-this.h/2 // bottom edge
        }
    }
```

### rendering

The _draw()_ just fills a rectangle with HSL-specified color:

```javascript
    draw() {
        save()
        translate(this.x, this.y)

        fill(.6, .35, .45)
        rect(-this.w/2, -this.h/2, this.w, this.h)

        restore()
    }
```
We use _translate()_ to get into the paddle coordinate system (with 0:0 at the paddle center). That is why we MUST _save()_ the context and _restore()_ it afterward.


### movement control

The functions _up()_ and _down()_ are used by keyboard event traps to trigger the movement:

```javascript
    up(active) {
        this.actions.up = active
    }

    down(active) {
        this.actions.down = active
    }
```



## Control

Keys are traped by the following 2 functions in 2 files:

```javascript
// trap/keyDown.js

function keyDown(e) {
    switch(e.code) {
        case 'Escape':
            trap('newGame')
            break

        case 'KeyW': case 'KeyA': lab.left.up(true); break;
        case 'KeyS': case 'KeyZ': lab.left.down(true); break;
        case 'ArrowUp':   case 'PageUp':   lab.right.up(true); break;
        case 'ArrowDown': case 'PageDown': lab.right.down(true); break;
    }
}
```


```javascript
// trap/keyUp.js

function keyUp(e) {
    switch(e.code) {
        case 'KeyW': case 'KeyA': lab.left.up(false); break;
        case 'KeyS': case 'KeyZ': lab.left.down(false); break;
        case 'ArrowUp':   case 'PageUp':   lab.right.up(false); break;
        case 'ArrowDown': case 'PageDown': lab.right.down(false); break;
    }
}
```
Here we access the paddles directly through the lab with _lab.left_ and _lab.right_. We raise movement flags in _keyDown()_ and reset them in _keyUp()_.



## Game Events


### new game

The _"new game"_ event is traped by:

```javascript
// trap/newGame.js
function newGame() {

    // reset the score
    env.score = {
        left: 0,
        right: 0,
    }

    // move paddles to the center
    lab.left.y = ry(.5)
    lab.right.y = ry(.5)

    // replace the puck
    kill(lab.puck)
    trap('spawnPuck')

    // show the start timer
    lab.score.countdown = 3
    lab.puck.hold = 0 // puck shouldn't wait
}
```

Here we do the initial setup and object spawning. It is triggered by _setup.js_ when the game starts and also fired manually by pressing the Escape key.


### score

The following trap counts the score:

```javascript
// trap/score.js
function score(player) {
    env.score[player] ++
    trap('spawnPuck')
    lib.sfx(res.sfx.score, .8)
}
```

We use a global-level node *env* to keep the *score* object. The player argument can be _left_ or _right_. And we rise the score accordingly.

### spawn puck

_spawnPuck_ creates a new Puck in _/lab_:


```javascript
// trap/spawnPuck.js

function spawnPuck() {
    lab.spawn('Puck', {
        name: 'puck',
        x: rx(.5),
        y: ry(.5),
        speed: ry(.8),
    })
}
```
The puck is created by the _spawn()_ function in *lab*. We pass a DNA name and an init object there.

The provided init object sets the name, the speed, and the puck's coordinates. With screen-relative functions _rx()_ and _ry()_ we place it in the middle of the screen.


## Setup

_setup.js_ contains the function to setup the game before it starts:

```javascript
function setup() {
    trap('newGame')
}
```
It just traps the new game event.



## Source Code

Check out the full source [on GitHub](https://github.com/invider/pong-ce.mod). It also contains the sound effect files used for countdown and collisions.

Clone it with git:

    git@github.com:invider/pong-ce.mod.git

And then run it with [Collider.JAM](http://collider.land):

```bash
cd pong-ce.mod
jam play
```


## Ideas for Improvement

There are multiple directions you can go from here:

* You can introduce more variety into the gameplay by providing some kind of random powerups to modify game properties - like increase paddle speed or size, slow down the puck, etc...
* You can improve visuals by adding bitmapped graphics or particle effects on collisions.
* You can implement simple AI to be able to play against the computer.
* You can add the ability to play over the network.

There might be other interesting ideas waiting to be implemented. Even the old and familiar gameplay from Pong can be rethought and refreshed.


## Summary

[Collider.JAM](http://collider.land) makes a good job of hiding complexity and boilerplate.

* You don't need any imports and exports.
* You don't bother yourself with a bunch of load() calls to obtain necessary resources.
* You don't extend any of the framework prototypes
in order to place something on the screen -
any plain JS objects will do the trick.

You just name the functions and place the files in appropriate folders according to Collider.JAM conventions. And everything is just magically mixed together.

That is the magic of [Collider.JAM](http://collider.land)!

Also notice how straightforward drawing and other common operations like _sin()_, _cos()_, _trap()_, _augment()_, and _kill()_. They are just functions available from the global context. No need to access those features from the bunch of incomprehensive utility objects like Context and Math. They are just there under your tips when you need them.

It makes JavaScript almost as clear and straightforward as BASIC. The code looks like a polite conversation with the framework instead of a rude arguing with the object system.