top of page

Pong (the ball)


Acknowledgments


Before diving into the subject, I would like to express the acknowledge for the inspiration of the article to Eric Boez's publication on what he called: "A Sunday challenge." He set out to create a quick version of the well-known 1972 Atari Pong game using FUSION-C V1.3. You can find it on his GitHub [1] and his YouTube channel [2].


Objective


To see how with a few lines of code, step by step, we are capable of creating a simple version of the Pong game using FUSION-C V1.2.



Just like in the example of "Moving a Sprite," we will be working with Screen 5. However, here we will introduce a new concept: collisions between Sprites, so that the ball bounces off the players' paddles.


We have decided to divide the article into several installments, in order to gradually introduce each piece of the game in each new installment. We will start with the ball and then proceed to the player's paddle that we control. After the first two installments, we will build a fronton and conclude with the last installment by adding the paddle controlled by the CPU. Therefore, it would look like this.


  • First installment: Pong (the ball)

  • Second installment: Pong (the player's paddle)

  • Third installment: Fronton Pong

  • Fourth installment: Pong (the CPU's paddle)


Introduction


We begin the series of articles by first focusing on creating the game's ball and making it move on its own through space, bouncing off the four walls.


We are going to build the program step by step, gradually introducing the code. We will focus on the most relevant parts of the code; you can find the complete code at the end of the article, as well as a functional online version.


Open your beloved text editor. In my case, Sublime Text 3, and name and save the file as "ball.c." This is what we will be working with throughout this article. If you haven't read the article on "Automating Compilation with Sublime Text 3," now is a good time, as it can be very helpful.


In this first article, we will explain the different sections that make up a C program while gradually introducing the code for "Pong (the ball)." Let's begin without further delay.


Documentation Section


Every C file should be preceded by a brief description of its contents: the program's name, its creation date, author(s), and any other information you consider appropriate to include.


//
// ball.c
// Fusion-C Pong game example articles
// Here we will learn the movements of the ball
// MoltSXalats 2023
//

Preprocessor Section


In this section, we will include the headers of the libraries that will be used in the program to link the system. In our case, we will use the following: the main Fusion-C library, the Fusion-C Sprite management library, and the Fusion-C graphics management library for the MSX2 standard.


#include "fusion-c/header/msx_fusion.h"
#include "fusion-c/header/vdp_sprites.h"
#include "fusion-c/header/vdp_graph2.h"

Macro and Symbolic Constants Definition Section


In this section, macros and symbolic constants are included. As an example, we will create two constants TRUE and FALSE, associated with the integers 1 and 0, respectively.


#define TRUE 1
#define FALSE 0

Type Definition Section


In this section, we will create custom types beyond those predefined in the C language. For our example, we have created a type called "Sprite."


typedef struct { 
    char spr;
    char y;
    char x;
    char pat;
} Sprite;

Function Prototypes Definition Section


In this section, the prototypes of the functions that will be used within the main() function are defined. Declaring functions provides information to the compiler about a function that will be used but has not yet been implemented. In particular, it informs the compiler about the data types that need to be passed to the function and what it returns.


The Sprite type is a structure that consists of 4 variables related to Sprite data. These include the Sprite number out of the possible 32, the initial position of the Sprite on the screen (x, y), and the pattern in the pattern table where the Sprite definitions are stored.


The game's ball, which we will introduce shortly, will be of the Sprite type.


If you wish to go deeper into C structures, you can refer to [3].

void GameStart(void);
void DrawSprite(void);
void BallCal(void);
void AxisCal(void);
void FT_Wait(int);


Global Variables Definition Section


In this section, we will declare the variables that we will use within the main() function and that will be global throughout the module.



signed char DirX;
signed char DirY; 

char PlyScore,CpuScore;

Sprite TheBall;

const char PatternBall[]={
						0b11100000,
						0b11100000,
						0b11100000,
						0b00000000,
						0b00000000,
						0b00000000,
						0b00000000,
						0b00000000
};


Next, we explain the most notable variables.


We will use DirX and DirY to maintain the direction of the ball as we move it through the coordinates (x, y). These variables take a value of 1 or -1 based on whether we are moving to the right or left, or up or down.



In the figure above, we can see the coordinate axes on the screen. When moving to the right, the X values are positive, and when moving to the left, they are negative. For the Y axis, moving downwards is positive, while moving upwards is negative.


We have defined the Pong ball as a 3x3 Sprite, and we create a pattern for the pattern table using the variable PatternBall. The concept of the Sprite will be encapsulated by the variable TheBall of the Sprite type that we've created.


const char PatternBall[]={
						0b11100000,
						0b11100000,
						0b11100000,
						0b00000000,
						0b00000000,
						0b00000000,
						0b00000000,
						0b00000000
};


Main Function and Game Loop Section


In this section, we will implement the main function and within it, the game loop. This loop is comprised of the while loop that can be found within this function.


Before entering the game loop, the entire environment is prepared: the screen color, screen mode, type of Sprites to be used, initial position and direction of the ball, etc. The initial Pong screen is also drawn using the GameStart() function, after which an infinite loop is executed. This loop will continue repeating until the ESC key is pressed, which corresponds to the ASCII code 27 on the MSX.


The game loop consists of four functions: the function that calculates the ball's position and ensures it remains within the game area BallCall(), the function that draws the ball on the screen using the coordinates obtained from the previous function DrawSprite(), a function created as an example AxisCal() that displays the ball's coordinates on the screen during each iteration, and the FT_Wait() function that introduces a pause in the ball's movement to control the painting speed, neither too fast nor too slow.


void main (void)
{
	
	SetColors(15,0,0);
	Screen(5);
	Sprite8();
	SpriteDouble();
	KeySound(0);

	SetSpritePattern(0, PatternBall,8);

	// Defining Variables
	// Player Pad Sprite

	PlyScore=0;

	// Cpu Pad Sprite

	CpuScore=0;

	// Ball Sprite
	TheBall.x=128;
	TheBall.y=100;
	TheBall.spr=0;
	TheBall.pat=0;

	DirX=1;
	DirY=1;

	GameStart();

	while (Inkey()!=27) 	// Main loop, ESC to Exit
	{

		BallCal();
		DrawSprite();
		AxisCal();

		FT_Wait(170);
	}

	// Ending program, and return to DOS
	Screen(0);
	KeySound(1);
	Exit(0);
}


Implementation of User-Defined Functions


This section of a C program is where we implement the functions that we'll use within the main() function and for which we've defined the headers above.


// Print the initial game screen
void GameStart(void)
{
	char Temp[5];

	PutText((256-20*8)/2,4,"Press SPACE to START",0);

	// Initial Positions
	TheBall.x=128;
	TheBall.y=100;	

	DirX*=-1;

	DrawSprite();

	Itoa(PlyScore,Temp,10);
	PutText(10,4,Temp,0);

	Itoa(CpuScore,Temp,10);
	PutText(235,4,Temp,0);

	while (!IsSpace(WaitKey()))
	{}

	PutText((256-20*8)/2,4,"                    ",0);
}


// Put all sprite on screen
void DrawSprite(void)
{
	PutSprite(TheBall.spr,TheBall.pat,TheBall.x,TheBall.y,15);
}

// Calculates Ball Position 
void BallCal(void)
{

	TheBall.x+=DirX;
	TheBall.y+=DirY;

	if (TheBall.y>=204 || TheBall.y<=10)
		DirY*=-1;

	if (TheBall.x>=250 || TheBall.x<=6)
		DirX*=-1;
		
}

// Prints Ball Coordinates
void AxisCal(void)
{
	char Temp[5];

	Itoa(TheBall.x,Temp,10);
	if(StrLen(Temp)==1)
	{	
		if(TheBall.x==9)
			PutText(100,4,"   ",0);
		PutText(100,4,Temp,0);
	}

	if(StrLen(Temp)==2) {
		if(TheBall.x==99)
			PutText(100,4,"   ",0);
		PutText(100,4,Temp,0);
	}

	if(StrLen(Temp)==3)
	{
		PutText(100,4,Temp,0);
	}


	Itoa(TheBall.y,Temp,10);
	if(StrLen(Temp)==1)
	{
		if(TheBall.y==9)			
			PutText(140,4,"   ",0);
		PutText(140,4,Temp,0);
	}

	if(StrLen(Temp)==2) {
		if(TheBall.y==99)
			PutText(140,4,"   ",0);
		PutText(140,4,Temp,0);
	}

	if(StrLen(Temp)==3)
	{
		PutText(140,4,Temp,0);
	}

}


// Wait Routine
void FT_Wait(int cicles) {
  unsigned int i;

  for(i=0;i<cicles;i++)
  {	
    while(IsVsync()==1)
    // With FUSION 1.3 use...
    //while(Vsynch()==0)	
    {}
  }
}


Following, we present the complete code, and at the end, a link where you can run the example we've worked on online.



//Documentation section

//
// ball.c
// Fusion-C Pong game example articles
// Here we will learn the movements of the ball
// MoltSXalats 2023
//


// Preprocessor section

#include "fusion-c/header/msx_fusion.h"
#include "fusion-c/header/vdp_sprites.h"
#include "fusion-c/header/vdp_graph2.h"


// Definition section of macros and symbolic constants

#define TRUE 1
#define FALSE 0


// Type definition section

typedef struct { 
	char spr;	// Sprite ID
    char y;		// Y destination of the Sprite
    char x;		// X destination of the Sprite
    char pat;	// Pattern number to use
} Sprite;


// Definition section of function prototypes

void GameStart(void);
void DrawSprite(void);
void BallCal(void);
void AxisCal(void);
void FT_Wait(int);


// Definition section of global variables

signed char DirX;					// Direction of the ball
signed char DirY; 

char PlyScore,CpuScore;

Sprite TheBall;						// Definining the ball Sprite Structure

const char PatternBall[]={
						0b11100000,
						0b11100000,
						0b11100000,
						0b00000000,
						0b00000000,
						0b00000000,
						0b00000000,
						0b00000000
};


// Section of the main function and the game loop

void main (void)
{
	

	SetColors(15,0,0);
	Screen(5);				// 256 x 212 pixel
	Sprite8();
	SpriteDouble();			// Magnified to 16 x 16 pixels
	KeySound(0);

	SetSpritePattern(0, PatternBall,8);

	// Defining Variables

	// Player Pad Sprite

	PlyScore=0;

	// Cpu Pad Sprite

	CpuScore=0;

	// Ball Sprite
	TheBall.x=128;
	TheBall.y=100;
	TheBall.spr=0;
	TheBall.pat=0;

	DirX=1;
	DirY=1;

	GameStart();

	while (Inkey()!=27) 	// Main loop, ESC to Exit
	{

		BallCal();
		DrawSprite();
		AxisCal();
		FT_Wait(170);
	}

	// Ending program, and return to DOS
	Screen(0);
	KeySound(1);
	Exit(0);
}


// User defined functions section


// Print the initial game screen
void GameStart(void)
{
	char Temp[5];

	// El texto es de 5 x 7
	PutText((256-20*8)/2,4,"Press SPACE to START",0);

	// Initial Positions
	TheBall.x=128;
	TheBall.y=100;	

	DirX*=-1;

	DrawSprite();

	Itoa(PlyScore,Temp,10);
	PutText(10,4,Temp,0);

	Itoa(CpuScore,Temp,10);
	PutText(235,4,Temp,0);

	while (!IsSpace(WaitKey()))
	{}

	PutText((256-20*8)/2,4,"                    ",0);
}

// Put all sprites on screen
void DrawSprite(void)
{
		PutSprite(TheBall.spr,TheBall.pat,TheBall.x,TheBall.y,15);
}

// Calculates Ball Position 
void BallCal(void)
{

	TheBall.x+=DirX;
	TheBall.y+=DirY;

	if (TheBall.y>=204 || TheBall.y<=10)	// Douncing the Ball on the Top and Bottom border 
		DirY*=-1;

	if (TheBall.x>=250 || TheBall.x<=6)		// Douncing the Ball on the Left and Right border 
		DirX*=-1;
		
}

// Prints Ball Coordinates
void AxisCal(void)
{
	char Temp[5];

	Itoa(TheBall.x,Temp,10);
	if(StrLen(Temp)==1)
	{	
		if(TheBall.x==9)
			PutText(100,4,"   ",0);
		PutText(100,4,Temp,0);
	}

	if(StrLen(Temp)==2) {
		if(TheBall.x==99)
			PutText(100,4,"   ",0);
		PutText(100,4,Temp,0);
	}

	if(StrLen(Temp)==3)
	{
		PutText(100,4,Temp,0);
	}


	Itoa(TheBall.y,Temp,10);
	if(StrLen(Temp)==1)
	{
		if(TheBall.y==9)			
			PutText(140,4,"   ",0);
		PutText(140,4,Temp,0);
	}

	if(StrLen(Temp)==2) {
		if(TheBall.y==99)
			PutText(140,4,"   ",0);
		PutText(140,4,Temp,0);
	}

	if(StrLen(Temp)==3)
	{
		PutText(140,4,Temp,0);
	}

}

// Wait Routine
void FT_Wait(int cicles) {
  unsigned int i;

  for(i=0;i<cicles;i++)
  {	
    while(IsVsync()==1)
    // With FUSION 1.3 use...
    //while(Vsynch()==0)	
    {}
  }
}



Click here to see the example in action.







Comments


bottom of page