top of page

Pong (la pelota)


Agradecimientos


Antes de entrar en materia, quiero agradecer la inspiración del artículo a la publicación de Eric Boez sobre lo que él llamó: "Un reto de domingo". Se propuso crear una versión rápida del conocido juego de Atari Pong de 1972 con FUSION-C V1.3. Podéis encontrarlo en su GitHub [1] y en su canal de YouTube [2].


Objetivo


Ver cómo con pocas líneas de código, paso a paso, somos capaces de crear mediante FUSION-C V1.2 una versión sencilla del juego Pong.



Igual que en el ejemplo de "Moviendo un Sprite", trabajaremos con Screen 5. Aquí, sin embargo, introduciremos un nuevo concepto: las colisiones entre Sprites, para que la pelota rebote en las paletas de los jugadores.


Hemos decidido dividir el artículo en varias entregas, para introducir de una a una las piezas del juego en cada nueva entrega. Empezaremos con la pelota y luego continuaremos con la paleta del jugador que controlamos. Después de las dos primeras entregas haremos un frontón para finalizar en la última agregando la paleta controlada por la CPU. Por lo tanto, quedaría de la siguiente manera:


  • Primera entrega: Pong (la pelota)

  • Segunda entrega: Pong (la paleta del jugador)

  • Tercera entrega: Front Pong

  • Cuarta entrega: Pong (la paleta de la CPU)


Introducción


Comenzamos la serie de artículos centrándonos primero en crear la pelota del juego y hacer que esta se mueva por sí sola en el espacio, rebotando en las cuatro paredes.


Vamos a construir el programa paso a paso, introduciendo el código poco a poco. Nos enfocaremos en las partes más relevantes del código, podréis encontrar el código completo al final del artículo, así como una versión en línea funcional.


Abre el editor de texto que prefieras, en mi caso Sublime Text 3, y nombra y guarda el archivo como "ball.c". Es con este con lo que trabajaremos durante este artículo. Si no has leído el artículo sobre "Automatizando la compilación con Sublime Text 3", ahora es un buen momento, ya que puede ser de gran ayuda.


En este primer artículo explicaremos las diferentes secciones de las que se compone un programa en C, al mismo tiempo que iremos introduciendo el código del "Pong (la pelota)". Comenzamos sin más dilación.


Sección de documentación


Todo archivo de C debería ir precedido por una breve descripción de lo que contiene, el nombre del programa, su fecha de creación, el autor o autores, entre otra información que creas conveniente incluir.



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

Sección del preprocesador


Aquí incluiremos las cabeceras de las bibliotecas que se utilizarán en el programa para que el sistema las enlace. En nuestro caso, utilizaremos: la biblioteca principal de fusion-c, la de gestión de Sprites de fusion-c y la de gestión de gráficos de fusion-c para el estándar msx2.


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

Sección de definición de macros y constantes simbólicas


En esta sección se incluyen las macros y constantes simbólicas. A modo de ejemplo, crearemos dos constantes TRUE y FALSE asociadas a los enteros 1 y 0 respectivamente.


#define TRUE 1
#define FALSE 0

Sección de definición de tipos


En esta sección crearemos tipos distintos a los preestablecidos en el lenguaje C. Para nuestro ejemplo, hemos considerado oportuno crear un tipo Sprite.


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

El tipo Sprite es una estructura que consta de 4 variables relacionadas con los datos de un Sprite. Estas son el número del Sprite dentro de los 32 posibles, la posición inicial del Sprite en pantalla (x, y) y el patrón de la tabla de patrones donde se guardan las definiciones de los Sprites.


La pelota del juego, que introduciremos en breve, será del tipo Sprite.


Si deseas profundizar sobre las estructuras en C, puedes consultar [3].


Sección de definición de los prototipos de las funciones


En esta sección se definen los prototipos de las funciones que se utilizarán dentro de la función main(). La declaración de las funciones le proporciona información al compilador sobre una función que será utilizada pero aún no ha sido implementada. En particular, le dice al compilador los tipos de datos que deben pasarse y qué valores devuelve cada función.

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


Sección de definición de las variables globales


En esta sección declararemos las variables que utilizaremos dentro de la función main() y que serán globales a todo el módulo.



signed char DirX;
signed char DirY; 

char PlyScore,CpuScore;

Sprite TheBall;

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


A continuación, explicamos las variables más destacadas.


DirX y DirY las utilizaremos para mantener la dirección de la pelota mientras la movemos por las coordenadas (x, y). Estas variables toman valores de 1 o -1 en función de si nos movemos hacia la derecha o hacia la izquierda, o hacia abajo o hacia arriba.



En la figura de más arriba podemos ver en pantalla los ejes de coordenadas. Si nos movemos hacia la derecha, las X son positivas, y si nos movemos hacia la izquierda, son negativas. Para las Y, si nos movemos hacia abajo son positivas, mientras que si nos movemos hacia arriba, son negativas.


Hemos definido la pelota del Pong como un Sprite de 3x3 y creamos un patrón para la tabla de patrones a través de la variable PatternBall. El concepto de Sprite será capturado por la variable TheBall de tipo Sprite que hemos creado nosotros.


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


Sección de la función main y el bucle de juego


En esta sección implementaremos la función main y dentro de ella el bucle de juego. Este bucle está compuesto por el while que podemos encontrar dentro de esta función.


Antes de entrar al bucle del juego se prepara todo el entorno: el color de la pantalla, el modo de la pantalla, el tipo de Sprites que se utilizarán, posición inicial y dirección de la pelota, etc. También se pinta la pantalla inicial del Pong con la función GameStart() para entrar en la ejecución de un bucle infinito que se repetirá hasta que se presione la tecla ESC, que corresponde al código ASCII 27 en el MSX.


El bucle de juego consta de cuatro funciones, la función que calcula la posición de la pelota y controla que esta esté dentro del escenario de juego BallCall(), la función que pinta la pelota en pantalla con las coordenadas obtenidas de la función anterior DrawSprite(), una función que hemos creado como ejemplo AxisCal() y que nos muestra en pantalla las coordenadas de la pelota en cada iteración, y la función FT_Wait() que impone una pausa al movimiento de la pelota para que el pintado fluya a una velocidad controlada, ni muy rápido ni muy lento.


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ón de las funciones definidas por el usuario


Es en esta sección de un programa en C donde se implementan las funciones que usaremos dentro de la función main() y cuyas cabeceras hemos definido anteriormente.


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



A continuación, os dejamos el código completo y al final un enlace donde podréis ejecutar en línea el ejemplo en el que hemos trabajado.



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



Haz clic aquí para ver el ejemplo en funcionamiento.







Comments


bottom of page