GD50 Lecture 00 - Pong

This is part of a series where I talk about how I approach the assignment portion of the GD50 lecture. These are written so that I can review them in the future without going through the code! Since this is an assignment, I won’t be posting the code unless it is not related to the assignment. If you are stuck at one of these assignments, these posts should contain enough information to help you progress. Feel free to let me know if there is an error. :)

Video games have always been a huge part of my life, which is why I am beyond excited to begin taking this online course: ‘CS50’s Introduction to Game Development’ offered by Colton Ogden & David J. Malan. IMO, It is very beginner friendly and most importantly, free! I highly recommend this to anyone who has a bit of programming experience and wanted to learn more about game development.

I will try to make a post for every assignment to talk about my approach and any interesting stuff that I encounter along the way. Without further ado, let’s begin!

  1. AI in Pong
  2. Reverse Pong
  3. Bugs & Changes

AI in Pong

The first assignment for this course is to implement an AI/Bot in Pong. Now, this might sound like a complicated task but it is actually very simple! I am going to show two different ways of implementing the AI and finally combine both of them together at the end.

followBot VS followBot

The first one simply follows the ball around (due to this complex reason, it will be named ‘FollowBot’). If the ball is higher than the paddle, it will move up and vice versa. Surprisingly, this implementation performs so much better than I initially anticipated, considering the game will continue until the ball reaches a velocity where it will phase through the paddle! You can read more about this in the bug section. Moving on to the second bot…

predictBot VS predictBot

Slightly more complicated, I decided to use magic mathematics to calculate where the ball will end up and move the paddle to that position (its name is ‘PredictBotbecause it predicts…). This can be done by using the X velocity to calculate the time required by the ball to travel between the paddles, and then the time can be used with Y velocity to calculate the final position. Now if the ball does not bounce off the top or bottom edge of the screen, the calculation would be so much more simple but that is not the case. That begin said, I actually had a lot of fun solving this, so I will leave this as a challenge for you (hint: remainder).

FollowBot has the advantage of being simple yet effective, while PredictBot is complex but ‘smart’. You might wonder which bot is better just like me. The answer is … it depends. There will be scenarios where the FollowBot can deflect the ball back while PredictBot can’t, and vice versa. The result ultimately depends on how the game is implemented but in my case, FollowBot performs slightly better.

followBot VS predictBot

To make it even better, we can combine both of them together. How? This can be achieved by using a boolean variable to keep track of the ball direction. When the ball is moving away from the paddle, the paddle will follow the ball’s movement. Then, when the ball is moving toward the paddle, it will switch to the second implementation and move to the ball’s final calculated position. With all of these, we will have an AI that beats any human player 99.99% of the time. I shall name it the ‘Terminator

Terminator VS Terminator

Reverse Pong

At some point, I thought it would be kinda fun to implement a Reverse Pong so I went ahead and did it! Players would have to avoid the ball instead of hitting it. But that would be kinda easy, right? What if there are multiple balls?

Reverse Pong with multiple balls

Or there is a giant ball?

Reverse Pong with gigantic ball

To make the gigantic ball version slightly more interesting, I modified some of the attributes:

Bugs & Changes

Vsync

The first issue I noticed: Vsync is not implemented in the push library even though our code is using it. This can be fixed by simply inserting the following code in the push library.

function push:setupScreen(WWIDTH, WHEIGHT, RWIDTH, RHEIGHT, settings)
    self._vsync = settings.vsync or self._vsync or false

    love.window.setMode(self._RWIDTH, self._RHEIGHT, {
        vsync = self._vsync,
    })
end

Audio

While testing the AI, I found out that the sound effects are occasionally missing when the ball is travelling at a high velocity. Originally, the audio is played from a single audio source that was created at the beginning of the program. And if that audio source is playing, it cannot be played again until it finishes. So whenever the ball travels ‘faster-than-light’ and the audio started to overlap, this issue occurs. This would be fine if the game is played by humans as it is very unlikely the ball will reach that velocity.

This can be fixed by implementing a better audio library, stop the audio and play it again, or creating a new audio source everytime an audio is played (not super efficient, but for a simple game like Pong, this will do the job).

// sounds['paddle_hit']:play()
love.audio.newSource('sounds/paddle_hit.wav', 'static'):play()

Ball phasing through paddles

Another issue arises when the ball is travelling at a high velocity: It will phase through the paddle! This happens because unlike real life, the ball doesn’t travel in a straight line in our game. Instead, the ball’s position is calculated and rendered frame by frame; in other words, it is discrete not continuous. I made a gif to demonstrate this:

Ball phasing through paddle in slow mo
Ball phasing through paddle in slow mo

I thought of a couple ways to fix/improve this:

Ultimately, I only used the first option and my frames jumped from 60 to 300++ (about 5 times less likely to phase through) which is more than enough for me.

Minor fixed bugs

Speedometer

I also added a speedometer as I thought it would be useful for observing the AI and any unexpected behaviour. The Pythagorean theorem was used to calculate the velocity of the ball.

function displaySpeed()
    local speed = math.floor(math.sqrt(ball.dy * ball.dy + ball.dx * ball.dx))
    love.graphics.setFont(smallFont)
    love.graphics.setColor(0, 1, 0, 1)
    love.graphics.print('Speed: ' .. tostring(speed), VIRTUAL_WIDTH-55, 10)
    love.graphics.setColor(1, 1, 1, 1)
end

That is it! I hope you learn something from this post. Till next time :)