Pong

Documentation is on the right hand side of this page.

Documentation

Important Tips

How To Skip Levels
This version of pong now has levels that you reach after a given number of points. If you just want to see the functionality that occurs at each level, you can skip levels by clicking anywhere in the canvas. Note that this will change the colour of the score and notify you upon winning that a click was made, to incentivise normal players (who are not testing the game) not to cheat

How To Get Basic Pong
To get the basic version of Pong, simply do the following in pong.ts then recompile with tsc:

  • Comment out lines 1028,1029,1030 (winningScore, ballCount, useLevels)
  • Uncomment out lines 1031,1032,1033 (same variables but setup for basic pong)

Instructions

Objective
The objective is to reach the maximum score of 199 before the AI does, or reach as high a score as you can

Movement
To move the paddle, simply move your mouse over the canvas and your paddle will follow your cursor.

Trajectory
To make the ball bounce at an angle, use the upper and lower parts of your paddle (the further from the center, the sharper the angle). To make the ball bounce straight, use the center of your paddle.

Features

The basic features are:

  • Move the paddle with the mouse
  • Ball moves by itself and ricochets back from the paddle and top and bottom walls
  • Player furthest from the ball scores a point when the ball goes out of bounds (left or right edges)
  • Opponent's paddle is controlled by an AI
  • Game ends when a player scores 11 points

Below are extra features that have been implemented in the game.

(Extra) (New Obs Func) Starting Counter

Before the game begins, a countdown timer signifies that the game is about to start. It counts down for 3 seconds before allowing the game to start, after which point the game commences.
Note: It is was made deliberately not possible to skip this even when the player tries to click to skip a level.

How it was coded: A timeout function was created in the Observable class, based on the RxJs function of the same name. The RxJs documentation was used as a reference. As these timeouts occur, a TextNode and SVGTextElement are adjusted in size, colour, and position to alert the user in a colourful display.

(Unused) (New Obs Func) Take While Function

A purely observable related addition, the 'takeWhile' function was added to the Observable class. Unfortunately I later realised this was not necessary, as I was able to achieve the same result using takeUntil to unsubscribe observers once a given Observable fired. However the function can still be tested by commenting out line 1285 in pong.ts, which will unsub the AI paddle when the human player scores 1 point (you can compare to commenting out line 1286, with the same condition on a filter, here the AI paddle resumes when the human player scores another point unlike takeWhile)

(Extra) Levels

Every 5 points you score up to 20, and every 10 points you score thereafter will take you to the next level, increasing the difficulty of the game. Difficulty is increased by adding more balls, increasing the speed of the balls, and increasing the speed of the AI's paddle.

How it was coded: Using a filter that checks for the player score to be within the set of 'level up' scores (e.g. 5,10,15,20,30,...). It sets the AI and ball paddle speeds for the level using input arrays that determine the intended parameters for each level. Ball observers are filtered to only fire when score is >= the score for that ball.

(Extra) Multiple Balls

After reaching higher levels, more balls will be added to increase the difficulty.

How it was coded:Multiple balls were created by creating a function called repeatNtimes, feeding it the main Interval observer, then allowing it to create N number of branches for the N total balls we have allowed in the game. Each ball is then appended ball movement mapping function, as well as a ball scoring function. An array of these balls was also used, in order to pass it to the paddles and other observables for collision detection.

(Extra) AI Upgrade

AI has been improved to cater to multiple balls. NOTE: For clarity, a cyan circle has been added around the ball that the AI is currently moving to hit. This also shows the target changing as another ball is determined.

How it was coded: Formulas 1 & 2 below were used to calculate the distance from all balls to the AI's paddle. Formula 1 is for when the ball is moving towards the paddle, while formula 2 is for when it is moving away from it, since it also factors in a bounce on the opposing wall. This is achieved by feeding in an array of all balls to the AI paddle movement observable, allowing it to continually check against all balls.

1. (AIpaddleX - ballX) / ballXspeed

2. (AIpaddleX - ballX + (rightEdge - ballX)*2) / ballXspeed

(Extra) Trajectory

When the ball collides with the paddle, the ratio formula 3 below is used to calculate the amount of Y speed and X speed to give the ball based on which part of the paddle it hits. The further top and bottom of the paddle the ball hits, the wider the trajectory (more Y speed, less X speed). Conversely the closer to the middle of the paddle, the flatter the trajectory (more X speed, less or no Y speed). To prevent the balls having 1 Y speed and 0 X speed (only goes straight up and down), trajectory scaler has been capped at 80%, meaning you can have at most 80% Y speed and 20% X speed. As described, formula 4 and 5 are used to calculate Y and X speed respectively.

3. min(abs(ballY - paddleCenterY) / HalfPaddleHeight, 0.8)

4. ballYspeed * trajectory

5. ballXspeed * (1-trajectory)

(Extra) Vertical Collision

Using an 'overlapping intervals' function and treating collision on horizontal axis separate to the collision on vertical axis, it was possible to enable collision with the top and bottom edges of the paddle, rather than just the left and right. This was done by segmenting the paddle into smaller collision boxes. Note there are some areas that are covered by both collision boxes. This was done to minimise the ball getting stuck, and in practice does not negatively impact collision detection.

3. min(abs(ballY - paddleCenterY) / HalfPaddleHeight, 0.8)

4. ballYspeed * trajectory

5. ballXspeed * (1-trajectory)

(Extra) Generic SVG functions

Originally I wanted to use a circle for a ball, but then moved to a rectangle. I realised that the attributes were different for a circle than for a rectangle, for example: 'width' did not exist on a circle and 'r' did not exist on a rectangle (naturally since it does not have a radius). However this meant all physics functions could not be generalised to work with multiple shapes. There were also no "left", "right" or other derived attributes readily available. So I added a 'getGeneric' attribute getter, and generalised 'getX', 'getY', etc. functions that specified which attributes to use for which SVG element types, with an optional functional applied to the output, as well as converting it to a number. This greatly simplified the physics code.

3. min(abs(ballY - paddleCenterY) / HalfPaddleHeight, 0.8)

4. ballYspeed * trajectory

5. ballXspeed * (1-trajectory)