Clase CComida

De nuevo se trata de una clase sencilla, definida por completo en el fichero de cabecera, "comida.h":

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

// Declaración de clase "CComida":

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

#ifndef CL_COMIDA
#define CL_COMIDA

class CComida {
  public:
   CComida(CGraficos *graf, CLaberinto *lab) :
         Graficos(graf), Laberinto(lab) {}
    
   void Situar(const CCoordenada &amp;pos) {
      posicion = pos; 
      Laberinto->Modificar(posicion, COMIDA);
   }
   CCoordenada Posicion() {return posicion;}
   void Mostrar(HDC dc=NULL) {Graficos->Mostrar(posicion, COMIDA, dc);}

  private:
   CCoordenada posicion;
   CGraficos *Graficos;
   CLaberinto *Laberinto;
};
#endif

Dispone de un dato, las coordenadas de la posición en pantalla, y de tres funciones: "Situar", que cambia las coordenadas de la comida; "Posicion", que devuelve las coordenadas actuales de la comida, y "Mostrar", que muestra en pantalla la comida.

Clase CExtra

Los extras son algo más complicados de manejar que la comida corriente, por eso la clase es también más compleja.

Este es el fichero de cabecera, "extra.h".

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

// Declaración de clase "CExtra":

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

#ifndef CL_EXTRA
#define CL_EXTRA

class CExtra {
  public:
   CExtra(CGraficos *graf, CLaberinto *lab) :
         Graficos(graf), Laberinto(lab) { Anular();}
    
   void Situar();
   CCoordenada Posicion() {return posicion;}
   void Mostrar(HDC dc=NULL) {Graficos->MostrarExtra(posicion, tipo, estado, dc);}
   void Siguiente();
   int Valor() { return estado>>2; }
   void Anular();
   bool Activo() { return activo; }
   
  private:
   CCoordenada posicion;
   int tipo;   // Diferentes tipos de extras
   int estado; // 0 a 63, 64 indica que ha caducado
   int cuenta; // Lo que falta para que aparezca el próximo
   bool activo; // Si está o no activo
   CGraficos *Graficos;
   CLaberinto *Laberinto;
};
#endif

Además de las coordenadas, necesitamos mantener otros datos. El "tipo" de extra, que hace referencia al gráfico. El "estado" se va incrementando dependiendo del tiempo que lleve el extra activo, dependiendo del estado se mostrarán diferentes gráficos; al llegar a 64, el extra desaparece. La "cuenta" es una cuanta atrás que sirve para situar un nuevo extra, el valor de comienzo se establece aleatoriamente, al llegar a cero, se situa el extra. Por último, "activo" indica si el extra está o no activo.

En cuanto a las funciones, disponemos de las mismas que en la comida: "Situar", "Posicion", "Mostrar", con las mismas aplicaciones. Además, "Siguiente", obtiene el siguiente estado, si está entre 0 y 63 muestra el gráfico correspondiente, si no, lo anula, es decir, lo hace desaparecer; "Valor" obtiene el valor correspondiente al estado actual, recordemos que estado varía de 0 a 63 y el valor de 0 a 15, por lo tanto basta con dividir entre cuatro o rotar dos bits a la derecha; "Anular" desactiva el extra, calcula el tiempo para que aparezca otro y lo borra de pantalla; "Activo" devuelve el valor del dato "activo".

Fichero "extra.cpp":

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

// Implementación de la clase para CExtra:

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

inline int random(int num)
{ 
   return (int)(((long)rand()*num)/(long)(RAND_MAX+1)); 
}

// Clase para Extras:
void CExtra::Situar() 
{ 
   if(!cuenta) {
      estado = 0;
      activo = true; 
      posicion = Laberinto->ObtenerLibre();
      tipo = random(3);
      Laberinto->Modificar(posicion, COMIDA);
      Mostrar();
   }
   else cuenta--;
}

void CExtra::Siguiente()
{
   estado++; 
   if(estado < 64) Mostrar();
   else Anular();
}

void CExtra::Anular() 
{ 
   activo = false; 
   cuenta = 100+random(50); 
   estado = 64; 
   Mostrar(); 
}

También se define la función "random", que se usa en varios puntos diferentes del programa.

Clase CSerpiente

Ahora el turno de la clase para la serpiente. Esta ya es más complicada que las anteriores.

Comenzamos con el fichero de cabecera, "serpient.h".

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

// Declaración de clase "CSerpiente":

#include <windows.h>
#include "seccion.h"
#include "graficos.h"
#include "laberint.h"
#include "direccio.h"
#include "coordena.h"
#include "comida.h"
#include "extra.h"
#include "tanteo.h"
#include "cola_pla.h"

#ifndef CL_SERPIENTE
#define CL_SERPIENTE

class CSerpiente {
  public:
   // Constructor:
   CSerpiente(CGraficos *, CLaberinto *, CComida *, CExtra *, CTanteo *);   

   void Iniciar(int);
   void Mostrar(HDC dc=NULL);
   void Deseada(eDireccion dir) {deseada = dir;}
   bool Avanzar();
   
  private:
   int CalcularCola(int anterior, int ultimo);

   cola<CSeccion> ColaSecciones;
   CDireccion direccion;
   CDireccion deseada;
   int porCrecer;
   CGraficos *Graficos;
   CLaberinto *Laberinto;
   CComida *Comida;
   CExtra *Extra;
   CTanteo *Tanteo;
};
#endif

Entre los datos miembro, tenemos la "ColaSecciones", que es una estructura dínamica en cola donde almacenaremos las secciones que componen la serpiente. Luego están "direccion" y "deseada" que son las direcciones en que se mueve la serpiente y la que el jugador pretende que siga, respectivamente. "porCrecer" son las unidades que aún le quedan por crecer a la serpiente. Este valor se incrementa cada vez que la serpiente come.

Entre las funciones tenemos: "Iniciar", que lee las coordenadas de la serpiente según el laberinto actual; "Mostrar", que muestra la serpiente completa en pantalla, "Deseada", que modifica el valor de la dirección elegida por el usuario; y "Avanzar", que actualiza la serpiente en función de las direcciones actual y deseada, y del valor de "porCrecer".

En el fichero "serpient.cpp" se definen algunas de las funciones:

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

// Implementación de la clase para CSerpiente:

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

CSerpiente::CSerpiente(CGraficos *graf, CLaberinto *lab, 
   CComida *com, CExtra *ext, CTanteo *tan) : 
   ColaSecciones(), direccion(derecha),
   deseada(derecha), porCrecer(0), Comida(com), Extra(ext),
   Graficos(graf), Laberinto(lab), Tanteo(tan)
{
   Iniciar(0);
}

void CSerpiente::Iniciar(int n)
{ 
   CSeccion seccion;
   int i, cuenta;
   ifstream datos;
   char cad[20]; 
   char *szX, *szY, *szTipo;
   
   // Destruir serpiente:
   ColaSecciones.Vaciar();

   wsprintf(cad, "%04d.lab", n);
   datos.open(cad); 
         
   datos.getline(cad, 20); // Nombre
   datos.getline(cad, 20); // Cuenta laberinto
   cuenta = atoi(cad);
   for(i = 0; i < cuenta; i++) 
      datos.getline(cad, 20); // ignorar

   datos.getline(cad, 20); // Nombre
   datos.getline(cad, 20); // Cuenta serpiente
   cuenta = atoi(cad);

   for(i = 0; i < cuenta; i++) {
      datos.getline(cad, 20);
      szX = strtok(cad, ",");
      szY = strtok(NULL, ",");
      szTipo = strtok(NULL, "\n");
      seccion = CSeccion(CCoordenada(atoi(szX), atoi(szY)), atoi(szTipo));
      ColaSecciones.Anadir(seccion);
      Laberinto->Modificar(seccion.Posicion(), seccion.Tipo());
   }
   datos.getline(cad, 20); // Direccion
   direccion = deseada = (eDireccion)atoi(cad); 
   datos.getline(cad, 20); // Por crecer
   porCrecer = atoicad);
   Comida->Situar(Laberinto->ObtenerLibre());
}

void CSerpiente::Mostrar(HDC dc)
{
   CSeccion sec;
   
   if(ColaSecciones.Consultar(sec)) 
      do {
         Graficos->Mostrar(sec.Posicion(), sec.Tipo(), dc);
      } while(ColaSecciones.Siguiente(sec));
}

bool CSerpiente::Avanzar()
{
   CCoordenada proxima;
   CSeccion cola;
   int comida = 0;
   int previo;

   // Si el movimiento es ilegal, ignorarlo:
   if(deseada == !direccion) deseada = direccion;
   
   // Tipo de sección que debe ser la anterior a la nueva cabeza:
   CSeccion &cabeza = ColaSecciones.ObtenerUltimo();
   previo = cabeza.Tipo() & 0x10;
   cabeza.ModificarTipo(previo | (int)!direccion | (int)deseada);  

   // Calcular la nueva posición de la cabeza:
   proxima = CCoordenada(cabeza.Posicion());
   proxima.Avanzar((eDireccion)(int)deseada);
         
   // Comprobar si hay choque:
   if(Laberinto->Colision(proxima)) // Choque.
      return false;

   // Sección anterior a cabeza:
   Graficos->Mostrar(cabeza.Posicion(), cabeza.Tipo());

   // Comprobar si hay comida:
   if(proxima == Comida->Posicion()) {
      comida = 0x10;
      porCrecer += CRECER;
      // Nueva comida
      Comida->Situar(Laberinto->ObtenerLibre());
      Comida->Mostrar();
   }

   // Comprobar si hay extra:
   if(Extra->Activo() && proxima == Extra->Posicion()) {
      comida = 0x10;
      porCrecer += 16-Extra->Valor(); // Depende de estado...
      // Eliminar Extra
      Extra->Anular();
   }

   // Añadir nueva cabeza:
   ColaSecciones.Anadir(CSeccion(proxima, comida | (int)deseada));
   Laberinto->Modificar(proxima, comida | (int)deseada);

   // Mostrar nueva cabeza:
   Graficos->Mostrar(proxima, comida | (int)deseada);

   // Borrar cola, si es necesario:
   if(!porCrecer) {
      // Elimina la cola de la cola:
      cola = ColaSecciones.Leer();
      Graficos->Mostrar(cola.Posicion(), LIMPIA);
      // Convertir último actual en cola:
      CSeccion &cola2 = ColaSecciones.ObtenerPrimero();
      cola2.ModificarTipo(CalcularCola(cola.Tipo(), cola2.Tipo()));
      // Actualizar pantalla: 
      Graficos->Mostrar(cola2.Posicion(), cola2.Tipo());
      Laberinto->Modificar(cola.Posicion(), LIMPIA);
   }   
   else {
      Tanteo->Actualizar(1);
      porCrecer--;
   }

   direccion = deseada;
   return true;
}

int CSerpiente::CalcularCola(int anterior, int ultimo)
{
   int valor;

   // Obtener la parte común:
   valor = anterior & ultimo; // AND
   // Calcular el sentido contrario:
   if(valor < 4) valor <<= 2; else valor >>= 2;
   // Complementar a 2, sólo los cuatro últimos bits:
   valor = 0x0f - valor;
   if(ultimo & 0x10) valor |= 0x10;
   return valor;
}

"Iniciar" lee la serpiente desde el fichero de inicialización. La configuración de este fichero se explica al final, y contiene las coordenadas del laberinto y la posición inicial de la serpiente.

"Mostrar" muestra la serpiente completa leyendola desde la cola, usando el acceso secuencial que definimos al efecto.

La función más complicada en la de "Avanzar", que usa la función privada "CalcularCola" para obtener el valor de la nueva sección de cola.

Según se ve en los comentarios, la función sigue el algoritmo que comentamos más arriba. Primero se verifica si la dirección deseada es legal, si no lo es se ignora. A continuación se calcula el tipo de sección anterior a la cabeza.

Después se calcula la nueva posición de la cabeza y se comprueba si hay colosión, después se muestra la sección anterior a la cabeza.

Lo siguiente es comprobar si hay comida o un extra en la posición actual de la cabeza. En el primer caso se sitúa una nueva comida, en el segundo se anula el extra. En ambos casos se incrementa el valor "porCrecer".

Ahora es cuando se añade la cabeza en su nueva posición y se muestra en pantalla.

Si es necesario, es decir, si la serpiente no está creciendo, se borra la cola actual y se calcula la nueva cola. En caso contrario, se actualiza el contador.