4irpucks is a multiplayer air hockey game which can be played in the browser with up to 3 other people. It was built as a group project by myself and 3 others for our University course. It features 4 different game modes and 3 different input devices, supporting keyboard, mouse and leap motion. If 4 players aren't available then any spaces will be filled with AI opponents with different play styles.

The game is still available online at 4irpucks.com.

Rendering the Graphics

The menu itself is fairly basic and is built using nothing more than static html with a very thin layer of JavaScript to provide functionality.

The graphics for the game were all rendered using three.js. This is a really well supported library for rendering 3D graphics with a good following and pleanty of example available online.

We built up each element within the game are created by joining basic shapes together to form objects which represent their real life form, for example the legs for the table are just a cylinder with a sphere placed on top. The only exception to this is the mallets which needed a more complex mesh to give it a realistic look.

The frames at which the graphics will render is uncapped, making the gameplay as smooth as the underlying hardware will allow.

The Physics

Collision detection and physics was all homebrewed by our team. Initially the goal was to create something mathematically correct, but we quickly found that this didn't translate to enjoyable gameplay. Instead we iterated on a few different designs until the game started to behave in a way we as players would expect.


The multiplayer was handled using socket.io, a javascript framework which uses websockets to push data to clients in real-time.

We found that synchronising the gameplay between 4 players on a game of this pace is something which is incredibly complicated, so it took several iterations to decide on an approach which gave the best results.

In each lobby there is a single player who is the host, meaning that they are the source of truth for the position of the puck.

Initial Implementation

We knew that it would take a few iterations to get the muliplater aspect into a position we were happy with, but the following was the basis we worked off of to achieve this:

  • The host player periodically broadcasts to the other players the position of the puck. If any other players have the puck in their own simluation they will self correct
  • Each player periodically broadcasts the position of their own mallet to all other players within the game so that it can be rendered on their screen

The above was a very basic implementation which showed us that things were possible but it wasn't fun to play. The puck would regularly self correct, causing it to jump around on the screen.

Selecting the best host

To ensure smooth gameplay, the host player was changed to be the player with the least latency with the server. To calculate this, whenever a new player joins the server will use the websocket connection to ping the player and calculate their average ping. The player with the least ping will be the host.

Reducing latency

The first improvement we made was to reduce the latency with the puck messages getting through to the other players in the game. This started off as an investigation into what was causing latency by sending off various ping messages to the clients. From this we were able to determine that the latency was much higher during the game compared with the initial ping message we used when deciding the host.

We found two ways to improve this:

  • Reducing the size of the message payloads masively reduced the latency
  • Reducing the frequency of the messages reduced the variance in latency

As a result we stripped everything out of the payloads except the core information and tweaked the frequency of messages until things were smooth.

Replaying physics

The next improvement we made was to allow the clients to replay any physics which should have applied while a message was in transit. To do this we altered the payloads to include the position of the puck, as well as the velocity and direction. We also added a timestamp to the message so that we could work out how long it took to get through. When a client receives a puck position message, they can then calculate where the puck should be based on the movement which would have happened during transit. This prevented the puck from flickering backwards.