Pong y los números mágicos

Hola de nuevo

Después de una temporada sin publicaciones debido al trabajo y a unas merecidas vacaciones, volvemos a la carga con el esperado juego Pong, una demostración muy sencilla de cómo en alrededor de 200 líneas de código se puede realizar un juego entretenido usando sólo el control DrawingArea de Gambas.

Pong (o Tele-Pong) fue un videojuego de la primera generación de videoconsolas publicado por Atari, creado por Nolan Bushnell y lanzado el 29 de noviembre de 1972. Pong está basado en el deporte de tenis de mesa (o ping pong). La palabra Pong es una marca registrada por Atari Interactive, mientras que la palabra genérica «pong» se usa para describir el género de videojuegos «pala y bola». Podéis ver la información completa en Wikipedia.

Magic numbers

Antes de meternos en harina con el código, me gustaría hacer un apunte respecto de algo que seguramente os sonará: magic numbers o números mágicos. ¿Y eso qué es?… os preguntaréis algunos. Bien, simplemente son esos valores numéricos que se asignan en nuestro código y que realmente nadie sabe de dónde vienen, pero hacen que el programa funcione. Si, efectivamente estoy en «modo irónico», porque a decir verdad, estamos hablando de una mala práctica. Veamos un ejemplo:

DrawingArea1.Width = 800
DrawingArea1.Height = 600
$ball_pos = [800 / 2, 600 / 2]
$paddle1_vel = [0, 0]
$paddle2_vel = [0, 0]
$paddle1_pos = [10, (600 / 2) - 50]
$paddle2_pos = [800 - 10, (600 / 2) - 50]

¿Qué significan todos esos números y de dónde vienen? Un programador que lea nuestro código tendría problemas para entenderlo y mantenerlo; incluso nosotros mismos aunque lo hayamos escrito, olvidaremos su significado tras un periodo de tiempo corto.

Entonces, ¿cómo podemos mejorar nuestro código de algún modo? Lo más inteligente, ya que esos números parecen ser constantes es precisamente el uso de «Constantes» para almacenar valores que no van a cambiar durante la ejecución de nuestro programa y que además describirán perfectamente el significado de su valor. Tradicionalmente los identificadores de constantes se escriben en mayúsculas en casi todos los lenguajes de programación y gambas no va a ser diferente. Veamos ahora cómo quedaría ese fragmento de código usando constantes:

 
Private Const CANVAS_WIDTH As Integer = 800
Private Const CANVAS_HEIGHT As Integer = 600
Private Const PADDLE_WIDTH As Integer = 10
Private Const PADDLE_HEIGHT As Integer = 100
Private Const HALF_PADDLE_HEIGHT As Integer = 50
 
 DrawingArea1.Width = CANVAS_WIDTH
 DrawingArea1.Height = CANVAS_HEIGHT
 $ball_pos = [CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2]
 $paddle1_vel = [0, 0]
 $paddle2_vel = [0, 0]
 $paddle1_pos = [PADDLE_WIDTH, (CANVAS_HEIGHT / 2) - HALF_PADDLE_HEIGHT]
 $paddle2_pos = [CANVAS_WIDTH - PADDLE_WIDTH, (CANVAS_HEIGHT / 2) - HALF_PADDLE_HEIGHT]

Realmente la diferencia es notable. Seguro que ahora todo el mundo podría leer el programa y saber exactamente qué es lo que hace y lo que significan los valores. Pues bien, dicho esto, vamos a ver cuáles son nuestras necesidades para construir nuestro juego de Pong.

Sabemos que el juego consiste en una pelota y dos palas o «raquetas» al estilo del tenis, objetos que tendrán movimiento. Un campo o terreno de juego donde se desarrolla la acción y que no permite que la pelota se salga por las paredes superior e inferior, pero sí por las paredes derecha e izquierda en caso de no ser interceptada por alguno de los jugadores con su pala o raqueta. Si recordamos en entradas anteriores del blog, vimos cómo poner en movimiento una pelota dentro de un drawingArea y explicamos cómo a partir de un punto en las coordenadas podíamos moverlo sumando un vector de velocidad a ese punto. También vimos cómo reflejar la trayectoria de un objeto en movimiento cuando éste colisionaba con un borde o con otro objeto. Pues bien, todo esto es más de lo mismo, por lo que si leíste dichas entradas, entenderás fácilmente todo el proceso. De manera que para comenzar vamos a declarar las variables y constantes necesarias para cubrir nuestras necesidades:

1
2
3
4
5
6
7
8
Private $ball_pos As New Float[]
Private $ball_vel As New Float[]
Private $paddle1_pos As New Float[]
Private $paddle2_pos As New Float[]
Private $paddle1_vel As New Float[]
Private $paddle2_vel As New Float[]
Private $score1 As Integer = 0
Private $score2 As Integer = 0

Los nombres, aunque en inglés, son autoexplicativos (es prácticamente una obligación escribir los programas en inglés si queremos hacernos entender por otros desarrolladores a nivel global). Necesitamos una variable para representar la posición de la pelota y otra para su velocidad. Igualmente para cada pala y finalmente otras dos para almacenar las puntuaciones o goles. Hemos elegido el tipo Float[] porque las coordenadas y vectores deben tener dos componentes, X e Y y además Float para mayor precisión. Es decir, usamos arrays float de dos elementos para representar puntos XY.

En cuanto a las constantes, es decir, valores que nunca van a cambiar a lo largo del programa, usaremos las que sean necesarias para evitar magic numbers dispersos entre nuestro código. A saber, las dimensiones de nuestro lienzo o DrawingArea, las dimensiones de las palas, el radio de la pelota,  aceleración, etc:

10
11
12
13
14
15
16
17
18
Private Const CANVAS_WIDTH As Integer = 800
Private Const CANVAS_HEIGHT As Integer = 600
Private Const BALL_RADIUS As Integer = 10
Private Const PADDLE_WIDTH As Integer = 10
Private Const PADDLE_HEIGHT As Integer = 100
Private Const PADDLE_VEL As Integer = 10
Private Const HALF_PADDLE_HEIGHT As Integer = 50
Private Const HALF_PADDLE_WIDTH As Integer = 5
Private Const ACC As Float = -1.10

Para comprenderlo mejor, veamos un boceto del aspecto de nuestro juego:

Esquema del juego Pong

El primer método de nuestro programa, el primero que se ejecuta al abrir el formulario es Form_Open y dentro del mismo inicializaremos algunas variables con los valores iniciales: pelota al centro, palas centradas en el eje Y, contadores a 0 y vectores de velocidad a 0.

18
19
20
21
22
23
24
25
26
27
28
29
Public Sub Form_Open()
    ' inicializamos las variables globales
     Timer1.Delay = 16 ' ~60 fps
     DrawingArea1.Width = CANVAS_WIDTH
     DrawingArea1.Height = CANVAS_HEIGHT
     $ball_pos = [CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2]
     $paddle1_vel = [0, 0]
     $paddle2_vel = [0, 0]
     $paddle1_pos = [PADDLE_WIDTH, (CANVAS_HEIGHT / 2) - HALF_PADDLE_HEIGHT]
     $paddle2_pos = [CANVAS_WIDTH - PADDLE_WIDTH, (CANVAS_HEIGHT / 2) - HALF_PADDLE_HEIGHT]
     $ball_vel = [0, 0]
End

En esta situación, los elementos están parados en los lugares iniciales. Para que la magia comience, debemos dar valores a las variables de velocidad, por ejemplo para poner en movimiento la pelota, usaremos este método que además nos permitirá elegir hacia qué lado de la pantalla se moverá la bola en el saque:

31
32
33
34
35
36
Public Sub ball_init(Optional toRight As Boolean = False)
    Randomize
    $ball_vel = [0, 0]
    $ball_pos = [CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2]
    $ball_vel = [IIf(toRight, 1, -1) * Rnd(1, 4), Rnd(-1, -3)] 
End

Observa el argumento opcional booleano, que indica mediante su valor si la bola irá hacia la derecha o la izquierda cada vez que se saque desde el centro.

Otras rutinas de este programa son las que controlan las palas mediante el teclado. Usando los eventos de formulario KeyPress y KeyRelease, indicaremos mediante dos pares de teclas los movimientos verticales de cada pala independientes para cada jugador:

150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
Public Sub Form_KeyPress()
 
    Select Case Key.Code
        Case Key.Up
            $paddle2_vel[1] -= PADDLE_VEL
        Case Key.Down
            $paddle2_vel[1] += PADDLE_VEL
        Case Key["W"]
            $paddle1_vel[1] -= PADDLE_VEL
        Case Key["S"]
            $paddle1_vel[1] += PADDLE_VEL
    End Select
 
End
 
Public Sub Form_KeyRelease()
 
    If Key.code = Key.Up Or If Key.code = Key.Down Then
        $paddle2_vel[1] = 0
    Endif
    If Key.code = Key["W"] Or If Key.code = Key["S"] Then
        $paddle1_vel[1] = 0
    Endif
 
End

Cuando se produzcan pulsaciones de teclas se dispararán los eventos correspondientes, permitiendo filtrar las teclas escogidas para que realicen la acción correspondiente. En el evento KeyPress sumamos o restamos una constante de velocidad a la velocidad inicial de cada pala en el eje vertical ‘Y’ para conseguir su desplazamiento por la pantalla y en el evento KeyRelease, cuando soltamos la tecla, volvemos a asignar una velocidad cero a la pala para detener su movimiento. Sencillo hasta ahora, ¿no?.

En la siguiente entrada veremos el corazón del programa, donde realmente se produce la magia del movimiento que es dentro del evento Draw del DrawingArea y el código de los botones que añaden utilidad al programa. Vuelvo a recomendar la lectura de entradas anteriores relativas al control DrawingArea, donde se explica la base del movimiento y animación y que es necesario asimilar para comprender el siguiente paso.

1 thought on “Pong y los números mágicos

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *