top of page

Pong (la pilota)


Agraïments


Abans d'entrar en matèria agraïr la inspiració de l’article a la publicació de l’Eric Boez sobre el que ell va anomenar: “Un repte de diumenge”. Es va proposar crear una versió ràpida del conegut joc d’Atari Pong del 1972 amb FUSION-C V1.3. Podeu trobar-lo al seu GitHub [1] i al seu canal de YouTube [2].



Objectiu


Veure com amb poques línies de codi, pas a pas, som capaços de crear mitjançant FUSION-C V1.2 una versió senzilla del joc Pong.



Igual que a l’exemple de “Movent un Sprite”, treballarem amb Screen 5. Aquí però introduirem un nou concepte, les col·lisions entre Sprites per tal que la pilota reboti a les pales dels jugadors.


Hem decidit dividir l’article en varies entregues, per tal d’anar introduint una a una les peces del joc a cada nova entrega. Començarem amb la pilota i seguirem per la pala del jugador que controlem. Després de les dues primeres entregues farem un frontó per acabar a la darrera afegint la pala controlada per la CPU. Per tant la cosa quedaria així.


  • Primer entrega: Pong (la pilota)

  • Segona entrega: Pong (la pala del jugador)

  • Tercera entrega: Front Prong

  • Quarta entrega: Pong (la pala de la CPU)



Introducció


Comencem la sèrie d'articles centrant-nos primer en crear la pilota del joc i fer que aquesta es mogui sola per l’espai tot rebotant per les quatre parets.


Anem a construir el programa pas a pas introduint el codi poc a poc. Ens centrarem en les parts del codi més rellevants, podreu trobar el codi sencer al final de l’article així com una versió online funcional.


Obriu l’editor de text que preferiu, en el meu cas Sublime Text 3 i anomeneu i deseu el fitxer com a ball.c. És amb el que anirem treballant durant aquest article. Sinó heu llegit l’article sobre “Automatitzant la compilació amb Sublime Text 3” ara és un bon moment ja que us pot ser de gran ajuda.


En aquest primer article explicarem les diferents seccions de que es composa un programa en C a la vegada que anirem introduint el codi del “Pong (la pilota)”. Comencem sense més dilació.



Secció de documentació


Tot fitxer de C hauria d’anar precedit per una breu descripció del què conté, nom del programa, la seva data de creació, autor o autors entre altra informació que cregueu oportú incloure.



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

Secció del preprocessador


Aquí Inclourem les capçaleres de les llibreries que s’utilitzaran al programa perquè el sistema les enllaci. En el nostre cas utilitzarem: la llibreria principal de fusion-c, la de gestió de Sprites de fusion-c i la de gestió de gràfics de fusion-c per a l'estàndard msx2.


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

Secció de definició de macros i constants simbòliques


En aquesta secció s’hi inclouen les macros i constants simbòliques. A mode d’exemple crearem dues constants TRUE i FALSE associades als enters 1 i 0 respectivament.


#define TRUE 1
#define FALSE 0

Secció de definició de tipus


Ens aquesta secció crearem tipus diferents als prestablerts en el llenguatge C. Pel nostre exemple hem cregut oportú crear un tipus Sprite.


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

El tipus Sprite és una estructura que consta de 4 variables relacionades amb les dades d’un Sprite. Aquestes són el número del Sprite dins dels 32 posibles, la posició inicial del Sprite en pantalla (x,y) i el patró de la taula de patrons on es desen les definicions dels Sprites.


La pilota del joc, que en breu introduirem, serà del tipus Sprite.


Si vols aprofundir sobre les estructures en C pots consultar-ho a [3].



Secció de definició dels prototipus de les funcions



Dins d’aquesta secció es defineixen els prototipus de les funcions que s’utilitzaran dins de la funció main(). La declaració de les funcions li dona informació al compilador d’una funció que sera utilitzada però que encara no s’ha implementat. En particular li diu al compilador els tipus de dades que se li han de passar i quins retorna.

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


Secció de definició de les variables globals



Dins d’aquesta secció declararem les variables que utilitzarem dins la funció main() i que seran globals a tot el mòdul.



signed char DirX;
signed char DirY; 

char PlyScore,CpuScore;

Sprite TheBall;

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


Tot seguit expliquem les variables més destacables.


DirX i DirY les utilitzarem per mantenir la direcció de la pilota mentre la movem per les coordenades (x,y). Aquestes variables prenen valor de 1 o de -1 en funció de si ens movem cap a la dreta o cap a la l'esquerra o cap a baix o cap a dalt.



A la figura de més amunt podem veure en pantalla, els eixos de coordenades. Si ens movem cap a la dreta les X són positives i cap a l’esquerra negatives. Per les Y si ens modem cap a baix són positives mentre que si ens movem cap a dalt són negatives.


Hem definit la pilota del Pong, com un Sprite de 3x3 i en creem un patró per la taula de patrons a través de la variable PatternBall. El concente de Sprite el recollirà la variable TheBall del tipus Sprite que hem creat nosaltres.


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


Secció de la funció main i el bucle de joc


En aquesta secció implementarem la funció main i dins aquesta el bucle de joc. Aquest està comprès pel while que podem trobar dins d’aquesta funció.


Abans d’entrar al bucle del joc es prepara tot l’entorn: el color de la pantalla, el mode de la pantalla, el tipus de Sprites que s’utilitzaran, posició inicial i direcció de la pilota, etc. També es pinta la pantalla inicial del Pong amb la funció GameStart() per entrar ja a executar un bucle infinit que s’anirà repetint fins que es premi la tecla ESC, que es correspon al codi ASCII 27 del MSX.


El bucle de joc consta de quatre funcions, la funció que calcula la posició de la pilota i controla que aquesta estigui dins l’escenari de joc BallCall(), la funció que pinta la pilota en pantalla amb les coordenades obtingudes amb la funció anterior DrawSprite(), una funció que hem creat a mode d’exemple AxisCal() i que ens pinta en pantalla les coordenades de la pilot a cada iteració i la funció FT_Wait() que imposa una pausa al moviment de la pilota per tal que el pintat flueixi a una velocitat controlada, ni molt ràpid ni molt lent.


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);
}


Implementació de les funcions definides per l’usuari


És en aquesta secció d’un programa C on s’implementen les funcions que usarem dins a la funció main() i que n’hem definit les capçaleres més amunt.


// 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)	
    {}
  }
}



Tot seguit us deixem el codi sencer i al final un enllaç on podreu executar online l’exemple que hem treballat.



//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)	
    {}
  }
}



Clica aquí per a veure l'exemple funcionant.







Comments


bottom of page