Clase CJuego

La clase CJuego es la encargada de crear y destruir todos los objetos que se necesitan a lo largo del juego. Además, actualiza los menús y los procesa.

También mueve la serpiente y actualiza los extras.

El fichero de cabecera es "juego.h":

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

// Declaración de clase "CJuego":

#include <windows.h>
#include <stdlib.h>
#include "coordena.h"
#include "direccio.h"
#include "extra.h"
#include "graficos.h"
#include "laberint.h"
#include "serpient.h"
#include "tanteo.h"

#ifndef CL_JUEGO
#define CL_JUEGO

class CJuego {
  public:
   CJuego(HWND, HINSTANCE);
   ~CJuego();
   // Funciones auxiliares:
   void LeerMenu(HWND);
   void LeerRegistro();
   void GuardarRegistro();
   void Mostrar(HDC);
   void CambiaLaberinto(int);
   void CambiaVelocidad(int);
   void ActivarMenus(bool);
   void Movimiento();
   const int Velocidad() {return velocidad;}
   void Empezar();
   const bool Parado() {return pausa;}
   void MoverSerpiente(eDireccion);

  private:
   // Variables y objetos globales:
   CSerpiente *Serpiente;
   CGraficos *Graficos;
   CLaberinto *Laberinto;
   CTanteo *Tanteo;
   CComida *Comida;
   CExtra *Extra;

   bool pausa;
   int nLaberintos;
   HWND hwnd;

   // Datos que se almacenan en el registro:
   int laberinto;
   int velocidad;
   int *puntuacion;
};
#endif

Dispone de punteros a los objetos para la serpiente, los gráficos, el laberinto, el tanteo, la comida y los extras. Además almacena el estado del juego, si está o no en pausa, el número de laberintos disponibles y la tabla de puntuaciones máximas.

La implementación de las funciones está en el fichero "juego.cpp":

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

// Implementación de la clase para CJuego:

#include "juego.h"

CJuego::CJuego(HWND hw, HINSTANCE instancia) : hwnd(hw), pausa(true)
{
   HMENU menu, submenu;

   // Crear objetos:
   Graficos = new CGraficos(instancia, hwnd);
   Laberinto = new CLaberinto(Graficos);
   Comida = new CComida(Graficos, Laberinto);
   Extra = new CExtra(Graficos, Laberinto);
   Tanteo = new CTanteo(Graficos);
   Comida->Situar(CCoordenada(16,6));
   Serpiente = new CSerpiente(Graficos, Laberinto, Comida, Extra, Tanteo);
   // Actualizar menú de laberintos:
   LeerMenu(hwnd);
   // Crear un array para puntuaciones máximas:
   puntuacion = new int[nLaberintos];
   // Leer puntuaciones máximas y opciones:
   LeerRegistro();
   // Actualizar menú:
   menu = GetMenu(hwnd);
   submenu = GetSubMenu(menu, 1);
   CheckMenuItem(GetSubMenu(submenu, 0), laberinto, MF_BYPOSITION | MF_CHECKED);
   CheckMenuItem(GetSubMenu(submenu, 1), velocidad, MF_BYPOSITION | MF_CHECKED);
   // Iniciar laberinto y serpiente:
   Laberinto->Iniciar(laberinto);
   Serpiente->Iniciar(laberinto);
}

CJuego::~CJuego()
{
   GuardarRegistro();
   delete Serpiente;
   delete Graficos;
   delete Laberinto;
   delete Comida;
   delete Tanteo;
   delete puntuacion;
}

void CJuego::LeerMenu(HWND hwnd)
{
   char cad[30];
   HMENU menu, submenu;
   ifstream fmenu("laberintos.dat");

   fmenu.(cad, 30); // Cuenta
   nLaberintos = atoi(cad);
   menu = GetMenu(hwnd);
   submenu = GetSubMenu(menu, 1);
   RemoveMenu(GetSubMenu(submenu, 0), 0, MF_BYPOSITION); // Borrar el actual
   for(int i = 0; i < nLaberintos; i++) {
      fmenu.getline(cad, 30); // Nombre
      AppendMenu(GetSubMenu(submenu, 0), MF_ENABLED | MF_STRING, 100+i, cad);
   }
}

void CJuego::LeerRegistro()
{
   HKEY clave;
   DWORD disposicion;
   DWORD tamanio;
   char contador[32];

   // Crear o abrir la clave de registro:
   RegCreateKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\ConClase\\Serpiente",
       0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
       &clave, &disposicion);

   if(disposicion == REG_CREATED_NEW_KEY) { // Es necesario crear los valores
      for(int i = 0; i < nLaberintos; i++) {
         wsprintf(contador, "Laberinto%04d", i);
         puntuacion[i] = 0; // Valor inicial a cero.
         RegSetValueEx(clave, contador, 0, REG_DWORD,
            (BYTE *)&puntuacion[i], sizeof(int));
      }
      laberinto = 0;
      RegSetValueEx(clave, "Laberinto", 0, REG_DWORD,
         (BYTE *)&laberinto, sizeof(int));
      velocidad = 2;
      RegSetValueEx(clave, "Velocidad", 0, REG_DWORD,
         (BYTE *)&velocidad, sizeof(int));
   }
   else { // Leer valores desde el registro:
      tamanio = sizeof(int);
      for(int i = 0; i < nLaberintos; i++) {
         wsprintf(contador, "Laberinto%04d", i);
         RegQueryValueEx(clave, contador, NULL, NULL,
            (BYTE*)&puntuacion[i], &tamanio);
      }
      RegQueryValueEx(clave, "Laberinto", NULL, NULL,
         (BYTE*)&laberinto, &tamanio);
      RegQueryValueEx(clave, "Velocidad", NULL, NULL,
         (BYTE*)&velocidad, &tamanio);
   }
   RegCloseKey(clave);
}

void CJuego::GuardarRegistro()
{
   HKEY clave;
   DWORD disposicion;
   DWORD tamanio;
   char contador[32];

   // Crear o abrir la clave de registro:
   RegCreateKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\ConClase\\Serpiente",
       0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL,
       &clave, &disposicion);

   for(int i = 0; i < nLaberintos; i++) {
      wsprintf(contador, "Laberinto%04d", i);
      RegSetValueEx(clave, contador, 0, REG_DWORD,
         (BYTE *)&puntuacion[i], sizeof(DWORD));
   }
   RegSetValueEx(clave, "Laberinto", 0, REG_DWORD,
      (BYTE *)&laberinto, sizeof(DWORD));
   RegSetValueEx(clave, "Velocidad", 0, REG_DWORD,
      (BYTE *)&velocidad, sizeof(DWORD));
   RegCloseKey(clave);
}

void CJuego::Mostrar(HDC dc)
{
   Serpiente->Mostrar(dc);
   Comida->Mostrar(dc);
   Laberinto->Mostrar(dc);
   Tanteo->Mostrar(dc);
   Graficos->Texto(CCoordenada(13,ALTO), puntuacion[laberinto]);
}

void CJuego::CambiaLaberinto(int valor)
{
   HMENU menu, submenu;

   menu = GetMenu(hwnd);
   submenu = GetSubMenu(menu, 1);

   CheckMenuItem(GetSubMenu(submenu, 0), laberinto, MF_BYPOSITION | MF_UNCHECKED);
   laberinto = valor;
   CheckMenuItem(GetSubMenu(submenu, 0), laberinto, MF_BYPOSITION | MF_CHECKED);
   Laberinto->Iniciar(laberinto);
   Serpiente->Iniciar(laberinto);
   Graficos->Borrar();
}

void CJuego::CambiaVelocidad(int valor)
{
   HMENU menu, submenu;

   menu = GetMenu(hwnd);
   submenu = GetSubMenu(menu, 1);

   CheckMenuItem(GetSubMenu(submenu, 1), velocidad, MF_BYPOSITION | MF_UNCHECKED);
   velocidad = valor;
   CheckMenuItem(GetSubMenu(submenu, 1), velocidad, MF_BYPOSITION | MF_CHECKED);
}

void CJuego::ActivarMenus(bool activar)
{
   HMENU menu, submenu;

   menu = GetMenu(hwnd);
   submenu = GetSubMenu(menu, 1);
   if(activar) {
      EnableMenuItem(submenu, 0, MF_BYPOSITION | MF_ENABLED);
      EnableMenuItem(submenu, 1, MF_BYPOSITION | MF_ENABLED);
   }
   else {
      EnableMenuItem(submenu, 0, MF_BYPOSITION | MF_GRAYED);
      EnableMenuItem(submenu, 1, MF_BYPOSITION | MF_GRAYED);
   }
}

void CJuego::Movimiento()
{
   if(!Serpiente->Avanzar()) {
      // Detener juego:
      pausa = true;
      KillTimer(hwnd, 1);
      // Actualizar puntuación máxima:
      if(Tanteo->Puntos() > puntuacion[laberinto])
         puntuacion[laberinto] = Tanteo->Puntos();
      // Enable menus:
      ActivarMenus(true);
   }
   if(Extra->Activo()) Extra->Siguiente();
   else Extra->Situar();
}

void CJuego::Empezar()
{
   if(pausa) {
      // Iniciar objetos
      Laberinto->Iniciar(laberinto);
      Serpiente->Iniciar(laberinto);
      Tanteo->Iniciar();
      Graficos->Borrar();
      Extra->Anular();
      SetTimer(hwnd, 1, 300-50*velocidad, NULL);
      pausa = false;
      // Disable menus:
      ActivarMenus(false);
   }
}

void CJuego::MoverSerpiente(eDireccion dir)
{
   Serpiente->Deseada(dir);
}

El constructor crea los objetos y los menús. Además toma del registro los últimos valores seleccionados de laberinto y velocidad y actualiza los menús. También se leen las puntuaciones máximas de cada laberinto.

El destructor destruye los objetos creados y guarda los valores actuales de laberinto y velocidad y las puntuaciones máximas en el registro.

La función LeerMenu, lee los nombres de los laberintos desde el fichero "laberintos.dat", y crea el menú a partir de él.

LeerRegistro, básicamente lee las variables desde el registro o las crea si no existen. GuardarRegistro actualiza esos valores en el registro.

Mostrar actualiza la ventana del juego completamente.

CambiaLaberinto y CambiaVelocidad actualiza las variables laberinto y velocidad cuando el usuario cambie de opciones, y actualiza también los menús.

ActivarMenus se usa para ocultar los menús durante el juego, de modo que no sea posible modificar la velocidad o el laberinto mientras se juega.

Movimiento procesa el movimiento de la serpiente, y si detecta una colisión detiene el juego, actualiza la puntuación máxima y activa los menús. También procesa el extra, decrementando su vida o el tiempo que queda para que aparezca.

Empezar comienza una nueva partida, iniciando todos los objetos, ajustando la velocidad y desactivando los menús.

Programa principal

Por último, el programa principal, "princip.cpp", que se encarga de las tareas propias de cualquier aplicación Windows.

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

#include <windows.h>
#include <fstream>
#include "ids.h"
#include "juego.h"

using namespace std;

CJuego *Juego;

/*  Procedimientos de ventana y diálogo  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);
bool CALLBACK DlgSobreProc(HWND, UINT, WPARAM, LPARAM);

/*  Nombre de la clase de ventana  */
char szNombreClase[ ] = "SerpienteApp";

int WINAPI
WinMain (HINSTANCE hInstance,
         HINSTANCE hPrevInstance,
         LPSTR lpszArgument,
         int nFunsterStil)

{
    HWND hwnd;               /* El manipulador de nuestra ventana */
    MSG messages;            /* Mensajes que nuestra aplicación manipulará */
    WNDCLASSEX wincl;        /* Estructura de datos para windowclass */
    RECT ventana, cliente;

    /* The Window structure */
    wincl.hInstance = hInstance;
    wincl.lpszClassName = szNombreClase;
    wincl.lpfnWndProc = WindowProcedure;      /* Procedimiento de ventana */
    wincl.style = CS_DBLCLKS;                 /* Trata los double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Usar el icono que hemos definico */
    wincl.hIcon = LoadIcon (hInstance, "Icono");
    wincl.hIconSm = LoadIcon (hInstance, "Icono");
    /* Y el cursor por defecto */
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = "Menu";  /* Menú */
    wincl.cbClsExtra = 0;         /* No hay información extra para la clase */
    wincl.cbWndExtra = 0;         /* estructura o instancia de ventana */
    /* Color de fondo para la ventana */
    wincl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);

    /* Registrar la clase de ventana, si falla salir del programa */
    if (!RegisterClassEx (&wincl)) return 0;

    /* La clase ha sido registrada, creamos el programa */
    hwnd = CreateWindowEx (
           0,                       /* Posibilidades añadidas */
           szNombreClase,           /* Nombre de la clase */
           "Serpiente con Clase",   /* Título */
           WS_SYSMENU | WS_MINIMIZEBOX,     /* Tipo de ventana */
           CW_USEDEFAULT,           /* Windows decide la posición */
           CW_USEDEFAULT,           /* de la ventana en pantalla */
           100,                     /* Anchura (no importa) */
           100,                     /* Altura  (no importa) */
           HWND_DESKTOP,            /* Ventana hija del escritorio */
           NULL,                    /* menu de la clase */
           hInstance,               /* Manipulador de la instancia */
           NULL                     /* No hay datos de creación de ventana */
           );

    /* Calcular las dimensiones de la ventana en función del área de cliente
       que queremos tener */
    GetWindowRect(hwnd, &ventana);
    GetClientRect(hwnd, &cliente);
    ventana.right  = ventana.right-ventana.left-cliente.right+cliente.left+ANCHO*16;
    ventana.bottom = ventana.bottom-ventana.top-cliente.bottom+cliente.top+(ALTO+1)*16;
    MoveWindow(hwnd, ventana.left, ventana.top, ventana.right, ventana.bottom, FALSE);

    /* Mostrar la ventana */
    ShowWindow (hwnd, nFunsterStil);

    /* Ejecutar el bucle de mensajes */
    while (true == GetMessage (&messages, NULL, 0, 0))
    {
        /* Traducir mensajes de teclas virtuales a mensajes de carácter */
        TranslateMessage(&messages);
        /* Enviar el mensaje al procedimiento de ventana */
        DispatchMessage(&messages);
    }

    /* El valor de retorno es el dado por PostQuitMessage() */
    return messages.wParam;
}


/*  This function is called by the Windows function DispatchMessage()  */

LRESULT CALLBACK
WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   PAINTSTRUCT ps;
   HDC dc;
   static HINSTANCE instancia;

   switch (message)                  /* handle the messages */
   {
      case WM_CREATE:
         instancia = ((LPCREATESTRUCT)lParam)->hInstance;
         Juego = new CJuego(hwnd, instancia);
         break;
      case WM_PAINT:
         dc = BeginPaint(hwnd, &ps);
         Juego->Mostrar(dc);
         EndPaint(hwnd, &ps);
         break;
      case WM_COMMAND:
         switch(LOWORD(wParam)) {
            case CM_SALIR:
               DestroyWindow(hwnd);
               break;
            case CM_SOBRE:
               DialogBox(instancia, "DialogoSobre", hwnd, DlgSobreProc);
               break;
            default:
                if(LOWORD(wParam) >= 100 && LOWORD(wParam) < 200)  // laberinto
                   Juego->CambiaLaberinto(LOWORD(wParam)-100);
                else  // Velocidad
                   Juego->CambiaVelocidad(LOWORD(wParam)-200);
         }
         break;
      case WM_KILLFOCUS:
         if(!Juego->Parado()) KillTimer(hwnd,1);
         break;
      case WM_SETFOCUS:
         if(!Juego->Parado()) SetTimer(hwnd, 1, 300-50*Juego->Velocidad(), NULL);
         break;
      case WM_TIMER:
         if(wParam == 1) {
            Juego->Movimiento();
            return 0;
         }
         break;
      case wm:WM_KEYDOWN:
         switch((int)wParam) {
            case VK_LEFT:  Juego->MoverSerpiente(izquierda); break;
            case VK_UP:    Juego->MoverSerpiente(arriba); break;
            case VK_RIGHT: Juego->MoverSerpiente(derecha); break;
            case VK_DOWN:  Juego->MoverSerpiente(abajo); break;
            // Si el juego está parado, debe empezar
            default:       Juego->Empezar();
         }
         break;
      case WM_DESTROY:
         KillTimer(hwnd, 1);
         delete Juego;
         PostQuitMessage(0);       /* send a WM_QUIT to the message queue */
         break;
      default:                      /* for messages that we don't deal with */
         return DefWindowProc (hwnd, message, wParam, lParam);
   }
   return 0;
}

bool CALLBACK DlgSobreProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
    if(msg == WM_COMMAND)                  /* manipulador del mensaje */
    {
       EndDialog(hDlg, FALSE);
       return true;
    }
    return FALSE;
}

Además de registrar el tipo de ventana y crear una para el juego, ajusta el tamaño de la ventana. Para el juego creamos una ventana que no puede cambiar de tamaño, de modo que tenemos que asignar el tamaño necesario para que quepa todo. Eso se hace con las instrucciones:

    /* Calcular las dimensiones de la ventana en función del área de cliente
       que queremos tener */
    GetWindowRect(hwnd, &ventana);
    GetClientRect(hwnd, &cliente);
    ventana.right  = ventana.right-ventana.left-cliente.right+cliente.left+ANCHO*16;
    ventana.bottom = ventana.bottom-ventana.top-cliente.bottom+cliente.top+(ALTO+1)*16;
    MoveWindow(hwnd, ventana.left, ventana.top, ventana.right, ventana.bottom, FALSE);

Ajustamos el tamaño de la ventana para que el área de cliente tenga ANCHO*16 pixels de ancho y (ALTO+1)*16 pixels de alto.

El corazón del juego es la función del procedimento de ventana que procesa los mensajes de Windows.

Al procesar el mensaje WM_CREATE se crea el objeto Juego, que a su vez crea el resto de los objetos.

WM_PAINT actualiza toda la ventana.

WM_COMMAND procesa los menús.

WM_KILLFOCUS y WM_SETFOCUS paran y reanudan el juego si la ventana pierde el foco o lo recupera, respectivamente.

WM_TIMER actualiza el juego. Este mensaje se recibe con una frecuencia proporcional a la velocidad del juego.

WM_KEYDOWN procesa las pulsaciones del teclado.

Conclusión

Eso es todo. Por supuesto, quedan cosas por contar, este documento será revisado, y si al leerlo crees que falta algo importante, o que se puede explicar algo mejor (seguro que sí), no dudes en enviarme un mensaje, la dirección está en la cabecera de la página.

Ficheros

Nombre Fichero Fecha Tamaño Contador Descarga
Ficheros fuente serpiente.zip 2002-11-03 26564 bytes 54