devstruggle

Kata 0: PONG

I recently started hanging out in the electronics section of Walmart during family grocery runs. I came across the concept of code kata a while back and decided to apply the practice to game programming. While my wife shops I find a working display laptop and see how much of PONG i can build before it's time to go.

PONG is the perfect game to practice game construction reflexes. In this build I've decided I'm going to avoid OOP for building the data model of the game. We're talking straight variables, constants, and procedural code where possible, of course the HTML Canvas API is object-oriented.

I start off by sketching out the most basic data to get something on screen. PONG consists of two rectangular paddles and a ball, which I'll also represent as a small rectangle. I'll define a few constants to hold their dimensions:



I'll also need to keep track of the position of the game objects in the play field so I need to create position variables for them. This reminds me that I need to give them all initial positions as well. The customary start position for the ball in the center of the play field so I need to store the dimensions of the canvas as well:


...

...

This is enough information to draw everything on screen, so I'll actually turn my attention to setting up the canvas now. The HTML Canvas API is really nice to work with once you know the basic pieces you need. Of course I have the canvas element in my HTML with dimensions defined:

The tabindex attribute is a later addition that helps with allowing the canvas element to receive input events from the keyboard.
To begin drawing we need a few things. First I need to store a reference to the canvas element then use it to get a 2d graphics context which will expose the methods needed to actually draw something to the canvas:

Now I can use the methods of graphics context to draw the game objects. First I'll draw a black rectangle the full size of the canvas to act as the background. Then I'll change the drawing color with fillStyle (it's black by default) then draw the paddles and ball in white:

This is what I end up with:

Now I need to atart thinking about animation. Before I concern myself with the code to draw the animations, I need to setup the machinery to update the data model of the game to make things change. This means making the program respond to input.

The only real chcnages to the data maodel related to animation are the updates to the position of the ball each frame or the positions of the paddles in response to player input. The motion of the paddles is simplest as they only move vertically so I'll start there. vertical movement happens on the y axis as is typical with the origin in the upper left corner so downward motion is positve, upward negative. I need to apply movement at a constant rate each update so I'll create a variable to represent the rate of change or speed and two others to represent the direction of motion for each paddle. This value will always be either -1, 0, or 1 and get multiplied by the speed before being added to the position of the paddle resulting in movement.


In Javascript you can make an element take some action based on an event by adding an event listener to the element. I'll make the canvas respond to key presses using the method addEventListener() twice for keydown and keyup events. The second argument is the name of a function I'll define next that will determine what happens when a key is pressed:

I need both event types since I want the game to respond both when a key is held down and when it is released to move the paddle and stop it from moving respectively. When a key is pressed down, I want the direction applied to the corresponding paddle to change accordingly. I'll use 'w' and 's' for the left paddle and the arrow keys for the right. When the key is released the direction gets set back to 0 via the else clause so the paddle stops moving. I define a function with the smae name as the one the event listeners call and use the event object the listeners pass to check which key was pressed. if its one of the movement keys, I check if the event was a keydown event and modify the paddle direction accordingly, otherwise I set the direction to 0 since the event must've been a keyup:


Now I need to set up the animation code. At the simplest animation is just a series of frames drawn repeatedly on screen displaying any changes that have happened since the last frame was drawn. At the most basic, I need the game objects to get drawn repeatedly. JS offers a method named setInterval() that will call a function at a certain interval of milliseconds. I will use it both to update the game state and to draw. To do this I'll create a new function to act as the main function of the game and just use setInterval() to make it a loop:

Since it accpets millisecond for the interval length, I pass it 1000 miliseconds divided by 24, which is 1/24th of a second or 24 times per second. Now I'll define the function it'll call. Before each draw, I need the game objects' positions to be updated. To make the main function clean, I'll define another function to do all the updating and place the drawing code in a separate function so the main function is nice and semantic:

Now the main function becomes:

I also need to modify the drawing code since it will actually be running in a loop now. Before it was only being drawn once, with all the objects static. now the objects will change position so I will need a blank canvas each time I draw or else the objects will appear smeared across the canvas with each old frame still displayed beneath the new one. I can do this by just drawing a blank rectangle over the whole canvas before drawing the new frame:

With those pieces in place I get basic movement of the paddles:

To prevent the paddles going beyond the edges of the screen I'll add a simple check to catch when a paddle's y value reaches zero or when the bottom of the paddle (y plus the paddle height) reaches the bottom of the canvas. If that happens, I just set the position to one pixel before the edge rather than allowing it to go beyond. I'll add that code to update():

Almost there, but the most complex pieces are still to come. First I'll get the ball moving. This is pretty straight-forward since the ball is moving pretty much all the time, so I can just create ball speed and direction variables and keep applying them every update:

Then this goes into update():

And, I guess this is progress...

The ball is moving but it only goes one direction and it starts moving as soon as the game starts. I can make this a little better by making the game respond to another button to kick things off. This will allow the ball to start off in the center of the screen and start when the players are ready. I think spacebar is a good button for that so I'll add a case to the input handling function to respond to spacebar by setting the ball direction. I'll probably make it choose randomly later but for now I'll just make it go right every time:

Now the initial ball direction can be zero:

Now the ball will sit still until we hit spacebar. But....when it does start moving, it just keeps going forever. It's time for some collision detection. This is the complex part, but it's not difficult. Just to make testing simpler, I'll start with detecting collisions with the edges of the game field. This is the game state where the ball has passed either paddle and its forward edge (its x value going left or x plus width going right) is less than zero (left edge) or greater than the canvas width (right edge). This check will need to happen at every update so I will wrap all collision detection in a function that I can call in the main loop:

In the main loop, I'll place the call after the update, but before drawing:

Now I have something like a game. If the ball hts the edge, it resets the ball direction to zero so when the ball speed is multiplied by direction in update(), zero gets added to the ball's position so it stays put when I set it back to center screen until spacebar gets pressed:

OKAY, now the big moment. Again, this isn't difficult at all, I just have to think carefully about what has to happen. I need the ball to change directions when it collides with a paddle. I'll take one side at a time. Since the ball starts off going right, I'll start there. Going right, the front edge if the ball is its x value plus its width. the front edge of the paddle is its x value. This is the case since all shapes in HTML5 Canvas by default have their origin at the top left corner. With that in mind, a ball/paddle collision happens when the ball's front edge is greater than the front edge of the right paddle. This makes the condition required for the check pretty simple:

So far, so good. But all this does is basically move the right edge out by the width of the paddle since the paddle's x value sits in front of the right edge. We need to add a check that determines if the ball is actually colliding with the paddle, meaning it passes the front edge of the paddle while also between the top and bottom edges of the paddle. The top of the paddle is just its y value. The bottom of the paddle is its y value plus its height. This condition is also simple:

In the case that these conditions are all true, the ball is colliding with the right paddle. In this state we need to reverse the direction of the ball:

And...we have collisions!

But there's a small problem. If we run into the situation where the top edge of the ball is just above the top edge of the paddle, even though the body of the ball is still within the body of the paddle, the collision is not detected since I don't check that the bottom edge of the ball is within the bounds of the paddle. I can fix that by adding an OR condition to my check that does the same bounds test as before, but for the bottom edge of the ball:

Now even 'clips' of the ball off the edge of the paddle are detected. This covers all the collision conditions so all I need to do is modify the values for calculating the edges to apply to the left paddle. It's basically the same for the top and bottom edges, but the front edge of the left paddle is its x value plus its width. The ball's leading edge is its x value in this case. So the completed collision detection function ends up like this:

And badabing, it's a game.

NOW IS WHERE IT GETS INTERESTING. Part of what makes PONG a game of skill is the unpredictability of vollies. This is where vertical motion of the ball comes in. In the original arcade PONG, the ball was divided into eight segments, each giving the ball a different amount of vertical movement. Add to this that the speed of the ball increases the longer the ball stays in play and you can see how the player's reflexes come into play.
To add vertical motion to the ball, I'll need to make a couple modifications to the code. First I'll change the name of the ball speed variable to make clear that it affects the ball's x movement. Next, I'll add a variable to hold the modifier for the ball's y movement:

WORK IN PROGRESS. INITIAL POST 10/15/2022 8:26PM