Electronza
Published in

Electronza

Mikromedia for PIC18FJ: Pong game

Games. Who doesn’t like to play them… But did you ever try to venture behind the game screen? You’ll find out things are quite different. In fact, writing a game is more complicated and difficult than you have ever thought.

To make a successful game you need some good ingredients: nice graphics, a good game engine, and entertaining sound. Each of those is complicated in its own, least to say putting all together. And how about a game that runs on a PIC microcontroller, even if that particular microcontroller is picked from the high-range of Microchip 8-bit PIC microcontrollers: the PIC18F87J50 has 128kb of FLASH, 3904 bytes of RAM, and it’s capable of reaching 12MIPS at a click frequency of 48MHz. Is this enough to play a game? We shall see…

As in many of my projects I will start with a short presentation of the hardware: I used an Mikromedia for PIC18FJ board, the simplest of the Mikromedia range from the Serbian manufacturer MikroElektronika.

Mikromedia for PIC18FJ

On the back side of this board we notice the PIC18F87J50 microcontroller, the VS1053 Stereo mp3 coder/decoder, microSD Card Slot, ADXL345 accelerometer, and one M25P80–8Mb, Low-Voltage Serial Flash.

The board can be powered via Li-Polymer, an on-board battery connector being provided. The battery can be charged via the same micro-USB connector used for programming, with a charge regulator implemented with an MCP73832. The Mikromedia boards are 3.3V only, and a regardless if USB or battery power used an LD29080DT33 regulator is used to provide this voltage.

On the font side we will find a 320 x 240 touch-screen TFT display, and the reset button. Quite an impressive hardware for a board this size.

To make for an even better game experience I placed the Mikromedia for PIC18FJ on an Mikromedia GAMING Shield, which provides an extra set of buttons, and it fits nicely in my hands.

Pong Game

To gain some autonomy I also used one Battery Boost Shield, which allows for the whole thing to be powered from a set of 1.5V, AAA batteries.

And now it’s time to venture into the realm of gaming…

The game I chose is a version of the Pong game, an old and simple game that can be run even on an 8-bit microcontroller with ease. While in its initial form the Pong game was designed to run in two player mode, in my version it will run in single player mode, sort of “play against the wall”. The controls are simple: start button (re)starts game, left and right buttons are used to move the paddle. If the ball reaches the bottom of the screen, the game ends.

The game engine is simple, it relies on only two functions: one that moves the ball (and also checks for bounces and for touching the paddle), and one function that reads the button status and moves the paddle. The game logic is inspired from the Arduino TFT pong tutorial, with a bit of personal touch that makes the game more difficult.

Moving the paddle

This is the simple thing: just read the button status, and move the paddle accordingly:

// Variables
int paddleX, paddleY, oldPaddleX, oldPaddleY;
int paddleWidth;
int paddleHeight;

// ...

// Initial values
paddleX = 120;
paddleY = 229;
oldPaddleX = 120;
oldPaddleY = 229;
paddleWidth = 79;
paddleHeight = 10;

// ...

// Draw initial paddle
TFT_Set_Brush(1, CL_WHITE, 0, LEFT_TO_RIGHT, CL_NAVY, CL_NAVY);
TFT_Rectangle(paddleX, paddleY, paddleX + paddleWidth, paddleY + paddleHeight);
Delay_ms(500);

// ...

// Move paddle function
void MovePaddle(){
// Is one of the buttons pressed?
// Buttons are active high
// Is left button pressed?
if (Button(&PORTE, 3, 1, 1) | Button(&PORTE, 7, 1, 1)){
PaddleX--;
// Is the paddle out of the screen?
if (PaddleX < 3){
PaddleX = 3;
}
else{ // move paddle one position to the left
// we add one rectangle of width 1 px and height paddleHeight to the left
TFT_Set_Brush(1, CL_WHITE, 0, LEFT_TO_RIGHT, CL_NAVY, CL_NAVY);
TFT_Rectangle(paddleX, paddleY, oldPaddleX, paddleY + paddleHeight);
// we clear one rectangle of width 1 px and height paddleHeight to the right
TFT_Set_Brush(1, CL_NAVY, 0, LEFT_TO_RIGHT, CL_NAVY, CL_NAVY);
TFT_Rectangle(paddleX + paddleWidth, paddleY, oldPaddleX + paddleWidth, paddleY + paddleHeight);
}
}
// Is right button pressed?
if (Button(&PORTE, 6, 1, 1) | Button(&PORTG, 2, 1, 1)){
PaddleX++;
// Is the paddle out of the screen?
if (PaddleX > (317 - paddleWidth)){
PaddleX = 317 - paddleWidth;
}
else{
// we add one rectangle of width 1 px and height paddleHeight to the right
TFT_Set_Brush(1, CL_WHITE, 0, LEFT_TO_RIGHT, CL_NAVY, CL_NAVY);
TFT_Rectangle(oldPaddleX + paddleWidth, paddleY, paddleX + paddleWidth, paddleY + paddleHeight);
// we clear one rectangle of width 1 px and height paddleHeight to the left
TFT_Set_Brush(1, CL_NAVY, 0, LEFT_TO_RIGHT, CL_NAVY, CL_NAVY);
TFT_Rectangle(oldPaddleX, paddleY, paddleX, paddleY + paddleHeight);
}
}
oldPaddleX = PaddleX;
oldPaddleY = PaddleY;
}

The paddle has a rectangular shape of size [paddleWidth, paddleHeight]. When the game starts the paddle is set somewhere close to the screen center, with the paddle coordinates given by the top-left corner.

Then we start monitoring for changes in the button states. If one button is pressed we update the paddle position, and we also update the TFT screen. There are basically three ways to update the TFT screen: first, I can clear the whole screen and redraw it every time something changes, This method is very slow and should be avoided. Second, I can clear only the paddle and the ball areas by first drawing rectangles with the background color, then redraw it. This is faster, but the changed areas will experience some nasty flicker. The final method that I have chosen is to redraw only the changed area of the paddle, by exploiting the fact that each paddle movement means that we add only one pixel width to the side facing the direction of movement, while clearing the same area on the other side. This is the fastest method, and it also looks better, although some artifacts remain for sudden movements.

Moving the ball

Moving the ball requires an additional function that checks for interaction with the paddle:

unsigned char inPaddle(int x, int y, int rectX, int rectY, int rectWidth, int rectHeight) {
unsigned char result;
result = 0;
if ((x >= (rectX-8)) &
(x <= (rectX + rectWidth + 8)) &
(y >= (rectY - 9)) &
(y <= (rectY + rectHeight - 9))) {
result = 1;
}
else {
}
return result;
}

This function takes as arguments the center coordinates of the ball and those of the upper-left corner of the paddle. The ball radius is fixed and has the value 8.

With the above considerations, if the ball touches the paddle the function returns 1, and returns 0 is the paddle and the ball are not in contact.

void moveBall() {
if ((ballX > 307) | (ballX < 12)) {
ballDirectionX = - ballDirectionX;
}
if (ballY < 13) {
ballDirectionY = - ballDirectionY;
}
if (ballY > 239){ // game lost
TFT_Fill_Screen(CL_BLACK);
TFT_Set_Font(TFT_defaultFont, CL_WHITE, FO_HORIZONTAL);
TFT_Write_Text("GAME OVER !", 110, 60);
TFT_Write_Text("PRESS START TO BEGIN A NEW GAME", 50, 90);
while(Button(&PORTG, 1, 1, 1) == 0);
Init_Game();
}
is_in_paddle= inPaddle(ballX, ballY, paddleX, paddleY, paddleWidth, paddleHeight);
if (is_in_paddle == 1) {
ballDirectionY = - ballDirectionY;
// Let's add some difficulty
count++;
if (count == max_count){
count = 0;
// Clear Paddle
TFT_Set_Brush(1, CL_NAVY, 0, LEFT_TO_RIGHT, CL_NAVY, CL_NAVY);
TFT_Rectangle(paddleX, paddleY, paddleX + paddleWidth, paddleY + paddleHeight);
paddleWidth = paddleWidth - 5;
// If we don't take some precautions
// The paddle will be smaller than the ball
if (paddleWidth < 20){
paddleWidth = 20;
}
// Redraw paddle
TFT_Set_Brush(1, CL_WHITE, 0, LEFT_TO_RIGHT, CL_NAVY, CL_NAVY);
TFT_Rectangle(paddleX, paddleY, paddleX + paddleWidth, paddleY + paddleHeight);
}
}
ballX += ballDirectionX;
ballY += ballDirectionY;

if ((oldBallX != ballX) | (oldBallY != ballY)) {
// erase last ball position
TFT_Set_Brush(1, CL_NAVY, 0, LEFT_TO_RIGHT, CL_NAVY, CL_NAVY);
TFT_Circle(oldBallX, oldBallY, 8);
// draw new ball
TFT_Set_Brush(1, CL_WHITE, 0, LEFT_TO_RIGHT, CL_NAVY, CL_NAVY);
TFT_Circle(ballX, ballY, 8);
}
oldBallX = ballX;
oldBallY = ballY;
Delay_us(40);
}

The things are a bit more complex here. If the ball touches one of the walls, it’s direction is changed. If the ball bounces off the paddle it’s direction is also changed, and a counter is incremented. When the counter reaches a given value (here that value is 5) the paddeWidth is reduced and the paddle is redrawn, thus making the game more difficult. A check is performed to prevent the paddleWidth to go beyond a minimum size.

Updating the ball position is also different, as it relies on painting an empty ball first (we draw a circle filled with the background color) than we redraw the ball in the new position. The disadvantage is that the ball experiences some flicker, but there’s nothing I can do to prevent this.

The code listing

The complete code listing is here:

The game is far from being perfect, and there are many ways to improve it. So, if you are coding just for fun, or you’re looking for a source of inspiration for your student project, please feel free to improve this code. I would be happy if you share your results…

One way to improve the game is to add sound. You can take a look to my talking drunkometer project — with some minor changes the code for adding sound will work with the Mikromedia too.

Considering the current processing power of todays microcontrollers and with the added benefit of development tools such as Mikromedia boards one should consider this blog post just a taste opener. Many other old games are waiting to be recreated. Just a word of caution: some of the old games are still protected by copyright laws, and you should check this aspect before releasing a commercial version — see this post from Wold of Spectrum for some aspects regarding retro-gaming.

Originally published at https://electronza.com on April 28, 2016. Moved to Medium on May 6, 2020.

--

--

--

Arduino, ESP8266, PIC, PIC32 projects and other electronics stuff

Recommended from Medium

MetaFame x Velhalla, Conquering the MetaVerse

A Story about Roblox, 13 Years Ago…

Shaking the Tailfeather: Rooster Wars' Journey to Ingenuity

The Good Games of the Year

Knife Hit Mod Apk All Knives Unlocked Download 2022

Knife Hit Mod Apk All Knives Unlocked Download 2022

Elden Ring: New Definiton to Souls-like Games

Review-Fort Triumph

Lord Mobile Mod Apk Unlimited Gems Download 2021

Lord Mobile Mod Apk Unlimited Gems Download 2021

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Teodor Costăchioiu

Teodor Costăchioiu

More from Medium

Multiprocessing on 3-Dimensional Array

Fourth Unit Project Lybrate.com

Qualified electronic signature

ZkPad project