Clase CGraficos

La clase CGráficos se encargará de todo lo relacionado con la visualización de elementos en pantalla.

Fichero de cabecera "graficos.h":

// Serpiente con Clase
// (C) 2002 Con Clase http://www.conclase.net
// Salvador Pozo Coronado

// Declaración de clase "CGraficos":

#include <windows.h>
#include "coordena.h"
#include "ids.h"

#ifndef CL_GRAFICOS
#define CL_GRAFICOS

class CGraficos {
  public:
   CGraficos(HINSTANCE, HWND);
   ~CGraficos();
   
   void Mostrar(CCoordenada, int, HDC dc=NULL);
   void Texto(CCoordenada, int, HDC dc=NULL);
   void MostrarExtra(CCoordenada, int tipo, int estado, HDC dc=NULL);
   void Borrar();
   void MostrarFondo(CCoordenada coor, HDC dc);

  private:
   HINSTANCE hInstance;
   HWND hwnd;
   HBITMAP bmp;
   HBITMAP extras;
   HBITMAP marcador;
   HBITMAP num;
};
#endif

El constructor se limita a cargar en memoria todos los mapas de bits que necesitaremos: los de la selpiente y laberinto, los de los extras, el marcador y los dígitos de los contadores.

El destructor liberará esos objetos de la memoria del ordenador.

Disponemos de cinco funciones para manejar los gráficos. "Mostrar" nos permite visualizar en la coordenada elegida el elemento gráfico indicado. Los elementos gráficos disponibles para esta función son la serpiente, el laberinto y la comida.

"MostrarExtra" sirve para la misma función, pero sólo se aplica a Extras.

"MostrarFondo", muestra el fondo del marcador.

"Texto" sirve para mostrar un número en forma de cadena de cinco dígitos.

"Borrar" borra la ventana completa.

La implementación de esta clase se hace en el fichero "graficos.cpp":

// Serpiente con Clase
// (C) 2002 Con Clase http://www.conclase.net
// Salvador Pozo Coronado

// Implementación de la clase para CGraficos:

#include "graficos.h"

CGraficos::CGraficos(HINSTANCE hinst, HWND hw) : 
   hInstance(hinst), hwnd(hw)
{
   bmp = LoadBitmap(hInstance, MAKEINTRESOURCE(GRAFICOS));
   extras = LoadBitmap(hInstance, MAKEINTRESOURCE(EXTRAS));
   num = LoadBitmap(hInstance, MAKEINTRESOURCE(NUMERO));
   marcador= LoadBitmap(hInstance, MAKEINTRESOURCE(MARCADOR));
}

CGraficos::~CGraficos() 
{
   FreeResource(bmp);
   FreeResource(extras);
   FreeResource(num);
   FreeResource(marcador);
}

void CGraficos::Mostrar(CCoordenada coor, int tipo, HDC dc)
{
   HDC memdc, dc2;
   
   if(!dc) dc2 = GetDC(hwnd); else dc2 = dc;
   memdc = CreateCompatibleDC(dc2);
   SelectObject(memdc, bmp);
   BitBlt(dc2, 16*coor.X(), 16*coor.Y(), 16, 16, memdc, 16*tipo, 0, SRCCOPY);
   DeleteDC(memdc);
   if(!dc) ReleaseDC(hwnd, dc2);
}

void CGraficos::MostrarExtra(CCoordenada coor, int tipo, int estado, HDC dc)
{
   HDC memdc, dc2;
   
   if(!dc) dc2 = GetDC(hwnd); else dc2 = dc;
   memdc = CreateCompatibleDC(dc2);
   SelectObject(memdc, extras);
   BitBlt(dc2, 16*coor.X(), 16*coor.Y(), 16, 16, memdc, 16*(estado>>2), 16*tipo, SRCCOPY);
   DeleteDC(memdc);
   if(!dc) ReleaseDC(hwnd, dc2);
}

void CGraficos::MostrarFondo(CCoordenada coor, HDC dc)
{
   HDC memdc, dc2;
   
   if(!dc) dc2 = GetDC(hwnd); else dc2 = dc;
   memdc = CreateCompatibleDC(dc2);
   SelectObject(memdc, marcador);
   BitBlt(dc2, 16*coor.X(), 16*coor.Y(), 512, 32, memdc, 0, 0, SRCCOPY);
   DeleteDC(memdc);
   if(!dc) ReleaseDChwnd, dc2);
}

void CGraficos::Texto(CCoordenada coor, int v, HDC dc)
{
   HDC memdc, dc2;
   char cad[7];
   int i;
 
   wsprintf(cad, "%05d", v);
   if(!dc) dc2 = GetDC(hwnd); else dc2 = dc;
   memdc = CreateCompatibleDC(dc2);

   SelectObject(memdc, num);
   for(i = 0; i < 5; i++)
      BitBlt(dc2, 16*(i+coor.X()), 16*coor.Y(), 16, 16, memdc, 16*(cad[i]-'0'), 0, SRCCOPY);
   
   DeleteDC(memdc);
   if(!dc) ReleaseDC(hwnd, dc2);
}

void CGraficos::Borrar()
{
   RECT cliente;

   GetClientRect(hwnd, &cliente);
   InvalidateRect(hwnd, &cliente, TRUE);
}

Todas las funciones de mostrar gráficos toman como parámetro un manipulador de DC. Si vemos los prototipos de las funciones, veremos que en todos los casos se define el valor NULL como valor por defecto para ese parámetro.

En las definiciones de las funciones de comprueba si el parámetro es NULL. Si no lo es, se usa ese manipulador en la función, pero si es NULL se obtiene un DC de la ventana, y se usa ese.

Eso es porque estas funciones pueden ser invocadas desde dos lugares diferentes. Una es durante el desarrollo normal del juego, cada vez que necesitemos actualizar cualquier modificación en la pantalla; en estas llamadas no usaremos el parámetro dc, y por defecto se usará el DC de la ventana.

El otro lugar es durante el proceso del mensaje WM_PAINT. En este caso ya dispondremos de un DC, que se obtiene mediante la función BeginPaint. Ese DC se pasa como parámetro, y no necesitaremos obtener uno nuevo.

Otro punto común es el uso de la función del API32 "BitBlt":

BitBlt(dc2, 16*coor.X(), 16*coor.Y(), 16, 16, memdc, 16*(estado>>2), 16*tipo, SRCCOPY);

Esta función usa nueve parámetros:

  • El primero es un manipulador de DC.
  • El segundo y tercero son las coordenadas de la ventana donde se mostrará el mapa de bits. Tenemos que multiplicar por 16 porque las dimensiones de cada gráfico son 16x16 pixels.
  • El cuarto y quinto corresponden con las dimensiones del mapa de bits en pixels.
  • El sexto es un manipulador de DC de memoria, necesario para manejar mapas de bits, previamente habremos seleccionado el mapa de bits deseado dentro de ese DC de memoria.
  • El séptimo y octavo son las coordenadas dentro del mapa de bits de los gráficos donde empieza el gráfico que queremos visualizar. El ejemplo corresponde al la visualización de un Extra. La coordenada x se obtiene multiplicando 16 por el número del gráfico correspondiente. Si recordamos, antes hemos mencionado que existen 64 estados, pero que sólo tenemos 16 gráficos, por lo tanto hay que dividir por 4, o lo que es lo mismo, rotar dos bits a la derecha. La coordenada y se obtiene multiplicando 16 por el tipo del Extra.
  • El noveno es el modo en que se transcriben los bits a pantalla. SRCCOPY indica que simplemente se copian los bits.

Clase CSeccion

Se trata de otra clase sencilla, de hecho la implementación se hace en el propio fichero de cabecera, "seccion.h":

// Serpiente con Clase
// (C) 2002 Con Clase http://www.conclase.net
// Salvador Pozo Coronado

// Declaración de clase "CSeccion":

#include <windows.h>
#include "coordena.h"

#ifndef CL_SECCION
#define CL_SECCION

// Clase para secciones:
class CSeccion {
  public:
   // Constructores:
   CSeccion() : posicion(0,0), tipo(0) {}
   CSeccion(const CSeccion &sec) : 
      posicion(sec.posicion), tipo(sec.tipo) {}
   CSeccion(const CCoordenada &coor, int t) : 
      posicion(coor), tipo(t) {};
   CSeccion(int x, int y, int t) : 
      posicion(x,y), tipo(t) {};
   CSeccion(int v) : posicion(0,0), tipo(v) {};
   
   void ModificarTipo(int t) {tipo=t;}
   const CCoordenada Posicion() {return posicion;}
   const int Tipo() {return tipo;}
   CSeccion &operator=(const CSeccion &sec)
      {posicion = sec.posicion; tipo = sec.tipo; return *this;}
   
  private:
   CCoordenada posicion;
   int tipo;
};
#endif

Hemos creado cinco constructores diferentes, de hecho es muy probable que alguno de ellos no se use en el programa.

Hemos añadido funciones para modificar y obtener el tipo de sección y para obtener la posición de la sección.

También hemos sobrecargado el operador de asignación.

Clase CTanteo

Esta clase lleva la cuenta de los puntos del jugador. También es muy sencilla. El fichero de cabecera es "tanteo.h".

// Salvador Pozo Coronado

// Declaración de clase "CTanteo":

#include <windows.h>
#include "graficos.h"
#include "coordena.h"

#ifndef CL_TANTEO
#define CL_TANTEO

class CTanteo {
  public:
   CTanteo(CGraficos *graf) : 
      Graficos(graf), puntos(0) {}

   void Iniciar() {puntos=0;}
   void Actualizar(int incremento) { puntos += incremento; Mostrar();}
   void Mostrar(HDC dc=NULL) {Graficos->Texto(CCoordenada(5,ALTO), puntos);}
   const int Puntos() {return puntos;}
   
  private:
   int puntos; 
   CGraficos *Graficos;
};
#endif

Tenemos una función para poner a cero el tanteo: "Iniciar", otra para incrementar y mostrar el contador: "Actualizar", otra para mostrarlo en pantalla: "Mostrar" y una cuarta para leer el valor actual: "Puntos".

Clase CLaberinto

Bien, entramos en las clases que empiezan a controlar cosas más importantes, CLaberinto controla todo lo relacionado con el terreno de juego.

El fichero de cabecera es "laberint.h".

// Serpiente con Clase
// (C) 2002 Con Clase http://www.conclase.net
// Salvador Pozo Coronado

// Declaración de clase "CLaberinto":

#include <windows.h>
#include <fstream>
#include "ids.h"
#include "coordena.h"
#include "graficos.h"

using namespace std;

#ifndef CL_LABERINTO
#define CL_LABERINTO

class CLaberinto {
  public:
   CLaberinto(CGraficos *);
   
   void Iniciar(int);
   void Mostrar(HDC dc=NULL);
   bool Colision(CCoordenada pos);
   void Modificar(CCoordenada pos, int valor);
   int Tablero(CCoordenada pos) {return tablero[pos.X()][pos.Y()];}
   CCoordenada ObtenerLibre();
   
  private:
   int tablero[ANCHO][ALTO];
   CCoordenada *laberinto;
   int nMuros;
   int actual;
   CGraficos *Graficos;
   int libres;
};
#endif

Disponemos de un array de dos dimensiones para almacenar el laberinto, las secciones de la serpiente, la comida y los extras. Estos datos se usan para verificar colisiones.

Otro array, este dinámico: "laberinto", almacena las coordenadas de los muros que definen el laberínto. nMuros contiene el tamaño de ese array, y actual almacena el número del laberinto cargado actualmente.

libres se usa para saber cuantas casillas libres contiene el laberinto. Ese dato es inprescindible para buscar una casilla libre para situar la comida o los extras.

En cuanto a las funciones, "Iniciar" carga un laberinto desde un fichero externo.

"Mostrar", muestra el laberinto en pantalla, para eso usa el array dinámico que contiene las coordenadas de los muros.

"Colision" comprueba si una casilla está ocupada por algo que no sea comida, y si es así retorna un valor "true".

"Modificar" se usa para actualizar el valor de una casilla.

"Tablero" sirve para consultar el contenido de una casilla.

"ObtenerLibre" obtiene las coordenadas de una de las casillas libres.

// Serpiente con Clase
// (C) 2002 Con Clase http://www.conclase.net
// Salvador Pozo Coronado

// Implementación de la clase para Laberinto:

#include <stdlib.h>
#include "laberint.h"

int random(int num);

CLaberinto::CLaberinto(CGraficos *gra) : Graficos(gra), 
   laberinto(NULL), nMuros(0)
{
   Iniciar(0);
}
 
void CLaberinto::Iniciar(int n)
{
   int y, x;
   int i;
   ifstream datos;
   char cad[20]; 
   char *szX, *szY;
   
   actual = n;
   if(laberinto) delete[] laberinto;
   
   wsprintf(cad, "%04d.lab", n);
   datos.open(cad); 
   for(x=0; x < ANCHO; x++)
      for(y = 0; y < ALTO; y++)
         tablero[x][y] = 0;
         
   libres = ANCHO*ALTO;
   datos.getline(cad, 20); // Nombre
   datos.getline(cad, 20); // Cuenta
   nMuros = atoi(cad);
   laberinto = new CCoordenada[nMuros];
   for(i = 0; i < nMuros; i++) {
      datos.getline(cad, 20);
      szX = strtok(cad, ",");
      szY = strtok(NULL, "\n");
      laberinto[i] = CCoordenada(atoi(szX), atoi(szY));
      tablero[atoi(szX)][atoi(szY)] = MURO;
      libres--;
   }
}

void CLaberinto::Mostrar(HDC dc)
{
   HDC memdc, dc2;

   for(int i = 0; i < nMuros; i++)
      Graficos->Mostrar(laberinto[i], MURO, dc);
   
   Graficos->MostrarFondo(CCoordenada(0, ALTO), dc);
}

bool CLaberinto::Colision(CCoordenada pos)
{
   return ((Tablero(pos) != LIMPIA) && (Tablero(pos) != COMIDA));
}

void CLaberinto::Modificar(CCoordenada pos, int valor)
{
   tablero[pos.X()][pos.Y()] = valor;
   if(valor) libres--; else libres++;
}

CCoordenada CLaberinto::ObtenerLibre()
{
   int v;
   CCoordenada pos(0,0);
   
   v = 1+random(libres);
 
   while(v > 0) {
      if(!Tablero(pos)) v--; 
      if(v) pos++;
   }
   return pos;
}

La función "random" se define en otro fichero, concretamente en "extra.cpp".