Pong game

The pong game consists of a ball bouncing on a screen.
A paddle (controlled from a mouse here) enables the user to make the ball bounce back up.

Drawing a useful picture
Building up on our VGA generator, we rewrite the sync generator as an HDL module where we generate R, G and B outside.
Also the X and Y counters are more useful if they start counting from the drawing area.
The new file can be found here.
Now we can use it to draw a border around the screen.
|
module pong( input clk, output vga_h_sync, vga_v_sync, output reg vga_R, vga_G, vga_B ); wire inDisplayArea; wire [9:0] CounterX; wire [8:0] CounterY; hvsync_generator syncgen(.clk(clk), .vga_h_sync(vga_h_sync), .vga_v_sync(vga_v_sync), .inDisplayArea(inDisplayArea), .CounterX(CounterX), .CounterY(CounterY)); // Draw a border around the screen wire border = (CounterX[9:3]==0) || (CounterX[9:3]==79) || (CounterY[8:3]==0) || (CounterY[8:3]==59); wire R = border; wire G = border; wire B = border; always @(posedge clk) begin vga_R <= R & inDisplayArea; vga_G <= G & inDisplayArea; vga_B <= B & inDisplayArea; end endmodule |
Drawing a paddle
Let's use a mouse to move the paddle left and right on the screen.
The quadrature decoder page shows the secret. The code is as follows:
|
reg [8:0] PaddlePosition; reg [2:0] quadAr, quadBr; always @(posedge clk) quadAr <= {quadAr[1:0], quadA}; always @(posedge clk) quadBr <= {quadBr[1:0], quadB}; always @(posedge clk) if(quadAr[2] ^ quadAr[1] ^ quadBr[2] ^ quadBr[1]) begin if(quadAr[2] ^ quadBr[1]) begin if(~&PaddlePosition) // make sure the value doesn't overflow PaddlePosition <= PaddlePosition + 1; end else begin if(|PaddlePosition) // make sure the value doesn't underflow PaddlePosition <= PaddlePosition - 1; end end |
Now that "PaddlePosition" value is known, we can display the paddle.
|
wire border = (CounterX[9:3]==0) || (CounterX[9:3]==79) || (CounterY[8:3]==0) || (CounterY[8:3]==59); wire paddle = (CounterX>=PaddlePosition+8) && (CounterX<=PaddlePosition+120) && (CounterY[8:4]==27); wire R = border | (CounterX[3] ^ CounterY[3]) | paddle; wire G = border | paddle; wire B = border | paddle; |
Drawing the ball
The ball needs to move around the screen, and bounce back when it touches an object (border or paddle).
First we display the ball. It is a 16x16 pixel square. We activate the drawing of the ball when CounterX and CounterY reach its coordinates.
|
reg [9:0] ballX; reg [8:0] ballY; reg ball_inX, ball_inY; always @(posedge clk) if(ball_inX==0) ball_inX <= (CounterX==ballX) & ball_inY; else ball_inX <= !(CounterX==ballX+16); always @(posedge clk) if(ball_inY==0) ball_inY <= (CounterY==ballY); else ball_inY <= !(CounterY==ballY+16); wire ball = ball_inX & ball_inY; |
Now for the collisions. That's the difficult part of this project.
We could check the coordinate of the ball against each object on the screen and determine if there is a collision. But that would quickly become a nightmare as the number of objects increases.
Instead we define 4 "hot-spot" pixels, one in the middle of each side of the ball. If an object (border or paddle) redraws itself at the same time that the ball draws one of its "hot-spots", we know that there is a collision on that side of the ball.
|
wire border = (CounterX[9:3]==0) || (CounterX[9:3]==79) || (CounterY[8:3]==0) || (CounterY[8:3]==59); wire paddle = (CounterX>=PaddlePosition+8) && (CounterX<=PaddlePosition+120) && (CounterY[8:4]==27); wire BouncingObject = border | paddle; // active if the border or paddle is redrawing itself reg CollisionX1, CollisionX2, CollisionY1, CollisionY2; always @(posedge clk) if(BouncingObject & (CounterX==ballX ) & (CounterY==ballY+ 8)) CollisionX1<=1; always @(posedge clk) if(BouncingObject & (CounterX==ballX+16) & (CounterY==ballY+ 8)) CollisionX2<=1; always @(posedge clk) if(BouncingObject & (CounterX==ballX+ 8) & (CounterY==ballY )) CollisionY1<=1; always @(posedge clk) if(BouncingObject & (CounterX==ballX+ 8) & (CounterY==ballY+16)) CollisionY2<=1; |
(I simplified the above code a little by never resetting the collision flops, the complete code is available below).
Now we update the ball position, but only once for every video frame.
|
reg UpdateBallPosition; // active only once for every video frame always @(posedge clk) UpdateBallPosition <= (CounterY==500) & (CounterX==0); reg ball_dirX, ball_dirY; always @(posedge clk) if(UpdateBallPosition) begin if(~(CollisionX1 & CollisionX2)) // if collision on both X-sides, don't move in the X direction begin ballX <= ballX + (ball_dirX ? -1 : 1); if(CollisionX2) ball_dirX <= 1; else if(CollisionX1) ball_dirX <= 0; end if(~(CollisionY1 & CollisionY2)) // if collision on both Y-sides, don't move in the Y direction begin ballY <= ballY + (ball_dirY ? -1 : 1); if(CollisionY2) ball_dirY <= 1; else if(CollisionY1) ball_dirY <= 0; end end |
And finally we can draw all that together.
|
wire R = BouncingObject | ball | (CounterX[3] ^ CounterY[3]); wire G = BouncingObject | ball; wire B = BouncingObject | ball; always @(posedge clk) begin vga_R <= R & inDisplayArea; vga_G <= G & inDisplayArea; vga_B <= B & inDisplayArea; end |
Whoa, that wasn't so difficult after all.
The complete file is pong.zip, and works with hvsync_generator.zip
It is also possible to use HDMI to run the pong game.