Ahora vamos a dar otro paso importante: meter el tablero. El tablero es el “campo de juego” en el que aparecen, caen y se acumulan las fichas.
Desde el punto de vista gráfico, el tablero es un rectángulo de N x M casillas. Concretamente, en nuestro caso lo vamos a hacer de 20 filas x 15 columnas. Sin embargo, desde el punto de vista de la programación, ¿qué estructura le damos? ¿Qué estructura de datos utilizamos?
En el tablero va a haber casillas vacías y fichas acumuladas. Por tanto, podríamos pensar en una lista de fichas con su tipo, su rotación y su posición, por ejemplo. Sin embargo, cuando una fila se llena y se colapsa, algunas de sus fichas desaparecen parcialmente, quedando sólo algunos de sus bloques a modo de restos.
Por tanto, la mejor opción es que el tablero sea una tabla de 20 x 15 casillas. Estas casillas empezarán el juego vacías y, según vayan cayendo y acumulándose fichas, iremos copiando esas fichas “acumuladas” al tablero. Pero no como fichas, sino como bloques individuales con su color correspondiente. De este modo, cuando una fila se llene y se colapse, será tan fácil como hacer caer o mover hacia abajo en la tabla todas las filas y bloques que están por encima.
Como podéis ver, esto significa que la ficha activa y el tablero se van a manejar como estructuras de datos independientes, aunque la ficha activa luego se pinte sobre el tablero. Ambas estructuras sólo se relacionan cuando la ficha activa cae y se acumula. En ese momento habrá una copia de datos desde la ficha activa hasta el tablero. Y vuelta a empezar con otra ficha.
También es interesante observar que vamos a separar claramente entre las estructuras de datos para guardar la información de la ficha activa y el tablero, y la pantalla y su memoria ($0400 – $07e7). Es decir, el juego controlará el tipo de ficha, su rotación, su posición y el contenido del tablero mediante unas estructuras de datos en memoria, y luego pintará esa información a pantalla cuando sea necesario, pero ambas informaciones, variables y pantalla, no van a estar mezcladas ni van a ser la misma.
Aclaro esto porque esta separación tan clara entre “capa de datos” y “capa de presentación” no se estilaba tanto en los 80, sino que es una técnica de programación que empezó a utilizarse bastante más tarde, por ejemplo, en aplicaciones web. En los 80, en ordenadores como el C64 y similares, era bastante habitual usar la memoria de pantalla tanto para pintar y comunicar información al usuario, como para guardar información de estado del programa y hacer controles sobre ella.
Me refiero a cosas del tipo, si un jugador está en la posición (X = 20, Y = 12), que equivale a la posición de pantalla $05f4, y la posición de al lado, que es la $05f5 está libre, entonces el jugador puede moverse a la derecha. Obsérvese que este control se hace directamente sobre la pantalla (posición $05f5), no sobre una estructura de datos con una copia del contenido de la pantalla.
En nuestro caso, los controles no los haremos directamente contra la pantalla, sino contra la estructura de datos del tablero, que luego copiaremos a la pantalla cuando toque.
Ficheros tetris_ficha.h y tetris_ficha.c:
El punto anterior (que una cosa es la estructura de datos del tablero y otra la pantalla), tiene una implicación muy importante:
Hasta ahora, la ficha tenía una posición (x, y) que venía marcada por las variables ficha_x y ficha_y. Pero ahora, nuestro interés no es tanto la posición (x, y) de pantalla donde está o se va a pintar la ficha, sino la posición (fila, columna) de la ficha con relación al tablero.
Si el tablero lo pintáramos directamente en la esquina superior izquierda de la pantalla, es decir, en la posición (0, 0), ambas cosas coincidirían, pero en general querremos pintar el tablero en una zona central de la pantalla.
Por tanto, para distinguir claramente entre la posición que ocupa la ficha dentro del tablero (este es el dato más importante a efectos del juego y los controles asociados) y la posición de pantalla donde se pinta la ficha (este dato es menos relevante), usaremos la notación (fila, columna) para el primer caso, y la notación (x, y) para el segundo.
Y este es el principal cambio en los ficheros tetris_ficha.h y tetris_ficha.c: donde antes se hablaba de ficha_x y ficha_y ahora se habla de ficha_f y ficha_c, porque el dato que más nos interesa es la (fila, columna) que ocupa la ficha respecto al tablero.
Por otro lado, también desaparece la variable ficha_color, ya que el color ahora lo vamos a vincular al tipo de ficha mediante la nueva función ficha_color().
Ficheros tetris_tablero.h y tetris_tablero.c:
En el fichero tetris_tablero.h tenemos la definición de constantes importantes, como el tamaño del tablero (TABLERO_MAX_F y TABLERO_MAX_C), la posición del tablero respecto a la pantalla (TABLERO_X0 y TABLERO_Y0), es decir, dónde se va a empezar a pintar el tablero dentro de la pantalla, y cómo vamos a codificar las posiciones vacías (TABLERO_VACIO).
Respecto a las posiciones vacías, parece que lo que le pide a uno el cuerpo es codificar la posición vacía con un cero pero, como cuando caigan las fichas y se acumulen en la parte inferior las copiaremos al tablero, y como los tipos de fichas van del cero al seis (siete tipos de fichas), resulta práctico identificar las fichas (o más bien sus bloques) mediante su tipo, que de paso nos servirá para deducir el color a pintar. Por eso se toma TABLERO_VACIO = 9, ya que el 9 no es un tipo de ficha válido, por mucho que pueda resultar extraño u original…
Por lo demás, en tetris_tablero.h también tenemos los prototipos de las funciones, que lógicamente se implementan en tetris_tablero.c. Respecto a éstas, la función tablero_inicializa_vacio() inicializa el tablero vacío, lo cual será lo normal, y tablero_inicializa_contenido() lo inicializa con fichas de tipos variados, lo cual nos servirá para probar que otras funciones como tablero_pinta() funcionan bien.
De hecho, tablero_pinta() es la función estrella, ya que se encarga de pintar el tablero en pantalla (sin la ficha activa que se maneja y pinta aparte). En el fondo, es fácil; se trata de recorrer la matriz del tablero por columnas y filas y pintar espacios allí donde el tablero está vacío (el espacio no necesita un color, porque el espacio es transparente) y un cuadrado allí donde hay una ficha o bloque (el bloque sí necesita color):

Por lo demás, la función tablero_pinta_borde() y funciones asociadas sirven para dotar al tablero de un borde.
Y la función tablero_pinta_datos() es similar a tablero_pinta() pero, en vez de pintar espacio o un bloque de color en función del contenido del tablero, pinta el valor numérico de cada posición, de modo que se puede depurar el programa, ver si las fichas se copian bien al tablero al caer y acumularse, comprobar si la función tablero_pinta() pinta lo que corresponde, etc. En definitiva, vale para depurar el programa.
Fichero tetris_main.c:
Una vez que tenemos los mimbres nos falta tejer el cesto. Y el “cesto” es el tetris_main.c.
Los principales cambios respecto a la versión anterior son que, al inicializar el juego, además de borrar la pantalla vamos a ponerla en negro (tanto el borde como el fondo), y vamos a inicializar el tablero (de momento con contenido variado, no vacío) y pintarlo, incluyendo el tablero propiamente dicho, su borde, y sus datos para depurar.
De momento, el bucle de juego lo comentamos, ya que la ficha la meteremos en la siguiente iteración.
El resultado es algo así:

Cosas a observar:
- A la izquierda tenemos el tablero con un contenido inicial, no vacío. En el juego real el tablero empezará vacío, y se irá llenando según caen y se acumulan las fichas.
- Además del tablero propiamente dicho, tenemos el borde. En el juego real, quitaremos el borde superior porque es por donde “entran” las fichas.
- A la derecha, tenemos el contenido del tablero. Es otra forma de verlo que nos vale para depurar. Obsérvese que donde hay un 9 vemos un cuadrado negro, es decir, un espacio. Y donde hay 0 – 6, vemos un bloque del color correspondiente.
Siguiente paso: empezar con un tablero vacío y añadirle la ficha.
Código de ejemplo: tetris04.zip
