The first lecture on CS50’s introduction to game development is about creating a small Pong game using Love2D. While the video runs you through the basics of Lua and a step-by -step process of creating a Pong game from scratch, the post-lecture assignment is where things get a bit more interesting.

What I like the most about these projects, though, is the ability to continue extending them as one pleases. I was able to complete the task (in an exceedingly crude manner) of adding computer controlled movement to one of the paddles in about 5 min. But to do it well took much longer (and by well I mean it’s acceptable but still horribly jittery!). I also added a few touches that I thought were kinda fun.

AI Update

The first problem I tackled was the one directly stated in the project: to create an AI-controlled paddle. Like I said above, my first solution was horrible crude, but technically it worked:

-- In love.update(dt) function
player2.y = ball.y

Of course, simply setting the right paddle’s y value equal to the ball’s is not very interesting (and also makes the game impossible to win). This also doesn’t account for the fact that the ball’s velocity increases with every bounce, while the paddle’s should maintain a constant speed to make the game more challenging.

To solve this, I made ample use of conditionals. The idea is to check whether the ball is above or bellow the paddle and then to move that paddle accordingly. I also added a small check so only move the right paddle if the ball is going towards it, since the ball’s direction is always random after being deflected:

-- In love.update(dt) function
if ball.dx < 0 then
	if ball.y > player2.y then
		player2.dy = PADDLE_SPEED
	elseif ball.y < player2.y then
		player2.dy = -PADDLE_SPEED
	else
		player2.dy = 0
	end
end 	

This worked much better and gave Player 1 a chance to win. But the AI-controlled paddle was horrible jittery: it tried to update it’s position constantly such that the centre of the paddle would always try to match the centre of the ball. I tried some far too complicated solutions - such as trying to implement a low-pass filter to smooth out the paddle’s movement - but ultimate decided that simply modifying the conditionals to include a buffer would be enough.

Because PADDLE_SPEED is constant, the paddle itself displays an acceptable level of jitter when the game starts, but as the ball speeds up the paddle’s movement looks smoother and smoother:

-- In love.update(dt) function
if ball.dx > 0 then
	if (ball.y + ball.height) > (player2.y + 5) then
		player2.dy = PADDLE_SPEED
	elseif (ball.y - ball.height) < (player2.y - 5) then
		player2.dy = -PADDLE_SPEED
	else
		player2.dy = 0
	end
end

PVP Update

The second feature I wanted to add was the ability for a human player to join at any point - much like in fighting arcade games, where the option for a player to jump in was always available. As part of adding this functionality I was also able to include the option for a human player to take either paddle, rather than always playing as Player 1.

The result is that the game can be run with one, two, or nil players.

For this to work I added an empty players table to love.load() function. When the game runs, a conditional looks through the players table to see if it finds player1. If it doesn’t, the left paddle continues to be controlled automatically. This check happens every single frame. If at any time the the w or s keys are pressed, then player1 is added to the players table and the computer surrenders control of the left paddle on the following frame. The functionality works exactly the same for player 2.

Whenever either player reaches 10 points, the players table is reset to empty, thereby allowing a follow-up game to be played again between AI, PVE, or PVP.

-- In love.update(dt) function
if not string.find(table.concat(players), 'player2') then
	if ball.dx > 0 then
		if (ball.y + ball.height) > (player2.y + 5) then
			player2.dy = PADDLE_SPEED
		elseif (ball.y - ball.height) < (player2.y - 5) then
			player2.dy = -PADDLE_SPEED
		else
			player2.dy = 0
		end
	end
end

Given that I already have a table which tracks players that join, it was fairly trivial to implement a small Ready Player 1 or Ready Player 2 message on the top of the screen. The logic is to use the players table and display an invitation for any player not in the table to join:

-- in playerJoin()
if not string.find(table.concat(players), 'player2') then
	love.graphics.printf('Ready Player 2', 
						  0, 
						  10, 
						  VIRTUAL_WIDTH + (VIRTUAL_WIDTH= / 2), 
						  'center')
end

The trickiest part here was to make sure that the message wasn’t just solid text at the top of the screen. I wanted the text to flash slowly as the game continues, like it does in arcade games. My first idea was to change the opacity of the text on every frame, but that would mean changing it, for example, 60 times per second!

What I did instead was to initialise two variables, timer and alpha at love.load(). Then, in love.update(dt), I used a conditional that checks whether 0.75 seconds have passed since alpha was last updated. If true, then alpha is set to alpha = 1 - alpha, effectively changing it’s value between 0 and 1 every 0.75 seconds.

Then it’s simply a matter of including playerJoin() in the love.draw() function:

function love.load()
	...
	...
	timer = 0
	alpha = 1
end
 
function love.update(dt)
	...
	...
	timer = timer + dt
	if timer > .75 then
		alpha = 1 - alpha
		timer = 0
	end
end
 
function love.draw()
	...
	...
	playerJoin()
	...
	...
end
 
function playerJoin()
	love.graphics.setFont(smallFont)
	love.graphics.setColor(1, 1, 1, alpha)
	if not string.find(table.concat(players), 'player2') then
		love.graphics.printf('Ready Player 2', 
							  0, 
							  10, 
							  VIRTUAL_WIDTH + (VIRTUAL_WIDTH / 2), 
							  'center')
	end
	if not string.find(table.concat(players), 'player1') then
		love.graphics.printf('Ready Player 1', 
							  0, 
							  10, 
							  VIRTUAL_WIDTH - (VIRTUAL_WIDTH / 2), 
							  'center')
	end
end

Future Update

The result is a charming little game that is quite fun to play, but not particularly complex. I more have ideas that I want to implement here: boosters, powers, more balls, different game modes etc. But rather than do that now, I’d rather move on to Flappy Bird and once I have a better idea of how things work in Love2D come back to this.