Clase base Jugador

La clase que cada competidor creará debe estar basada en esta clase base virtual pura. La declaración está incluida en el fichero "jugador.h", y la definición depende de ti.

Con el proyecto se suministran dos clases derivadas, una como ejemplo: "Jugador1", y otra para depuración, que permite jugar de forma interactiva: "Humano".

Clase virtual Jugador

class Jugador {
  public:
   virtual void NuevaPartida()=0; 
   virtual DatosBarco PedirBarco(int n)=0; 
   virtual Coordenada PedirCoordenada()=0;
   virtual void Informar(Coordenada)=0;
   virtual void Responder(Coordenada, contenido)=0;
};

Estas funciones definen el interfaz entre el programa que enfrenta a los distintos jugadores y el jugador.

NuevaPartida será invocada por el programa para que el jugador prepare su estado para comenzar una nueva partida. Deberá inicializar sus variables, y realizar las tareas que consideres necesarias como diseñador de tu clase derivada.

PedirBarco será invocada repetidamente por el programa para obtener las coordenadas de cada uno de los barcos del jugador. El parámetro indica el número del barco, según las constantes definidas en el espacio con nombre "cte". Es recomendable que parte de las tareas a realizar por NuevaPartida sea colocar todos los barcos, y esta función se limite a consultarlas y devolverlas. Para la devolución de los datos del barco se usa una estructura DatosBarco que contiene la coordenada de inicio y dirección.

PedirCoordenada será invocada por el programa para obtener la coordenada de disparo del "jugador". La coordenada debe estar dentro del tablero, y no puede pertenecer a una coordenada de un barco donde se haya disparado previamente.

Informar informa al jugador de la coordenada donde ha disparado el jugador contrario. Esto no influye, en principio, en el juego del jugador que recibe la información, a no ser que se use esa información en un algoritmo de inteligencia artificial con el fin de adaptarse a la estrategia del jugador contrario.

Responder sirve para informar a un jugador del contenido de la casilla donde ha disparado. El programa invocará esta función después de haber llamado a PedirCoordenada, usando como parámetros la misma coordenada obtenida mediante PerdirCoordenada y con el contenido de esa coordenada en el tablero de jugador contrario.

Clases de ejemplo derivadas de Jugador

Con lo anterior deberías tener suficiente información para diseñar tu clase derivada de Jugador, sin embargo creemos que es interesante proporcionar dos clases como ejemplo. La clase Humano es una clase interactiva, que permite a un jugador humano participar en el juego. Te será muy útil para depurar tu jugador, viendo dónde falla y por dónde se puede mejorar.

Clase Humano

class Humano : public Jugador {
  public:
   void NuevaPartida();
   DatosBarco PedirBarco(int n);
   Coordenada PedirCoordenada();
   void Informar(Coordenada);
   void Responder(Coordenada, contenido);
   
  private:
   contenido tablero[2+cte::alto][2+cte::ancho];
   contenido contrario[2+cte::alto][2+cte::ancho];
   Coordenada coor[cte::nBarcos];
   direccion dir[cte::nBarcos];
   
   // Devuleve un valor aleatorio entre min y max-1
   int Alea(int min, int max);
   // Comprueba si un barco colisiona con los existentes
   bool Colision(int); 
};

Se trata de una clase derivada de Jugador, por supuesto. A las funciones heredadas añadimos dos más, además de las variables necerasias para mantener el juego.

Alea es una función imprescindible en cualquier juego, nos devuelve un valor aleatorio entre "min" y "max". El valor "max" nunca se alcanza, por ejemplo Alea(1,10) proporcionará valores entre 1 y 9 (incluidos).

Colision la usamos para colocar cada uno de los barcos. La técnica consiste en colocar cada uno de los barcos de forma aleatoria. Una vez elegidos las coordenadas y la dirección de un barco, si entra en colisión con alguno de los anteriores, se vuelve a intentar colocar.

colocar

Esto es una representación del tablero que se usa para colocar los barcos de forma automática. Los cuadros blancos son los que se usan para jugar, los cuadros de alrededor, en gris claro, se usan para evitar casos especiales.

En el tablero se ha colocado un barco, marcado en oscuro, y alrededor de él se han marcado todas las casillas como usadas. Cualquier barco que intente colocarse después generará una colisión si cualquiera de sus casillas cae en una casilla usada. Es decir, para colocar el resto de los barcos sólo disponemos de las casillas en blanco.

Como se ve, tener una casilla alrededor del tablero visible nos facilita el marcado de las casillas alrededor de cada barco, ya que no existen excepciones cuando el barco toca un borde.

Los datos que se almacenan son cuatro arrays:

tablero es un array que contiene las casillas del jugador, incluyendo los barcos y si el algoritmo del jugador lo requiere, el estado de la partida del jugador contrario (que se desarrolla en nuestro tablero).

contrario es el array que contiene las casillas del jugador contrario, en ese array se almacena el estado de la partida que jugamos, indicando el resultado de cada una de nuestras jugadas.

Observarás que los dos arrays de los tableros tienen dos casillas más de ancho y alto que el tablero de juego real. Esto es porque de ese modo es más sencillo rellenar de agua las casillas de alrededor de cada barco, sin preocuparse de si el barco está o no junto al borde. Las casillas del borde de los arrays no pueden nunca contener barcos.

Los otros dos arrays coor y dir contienen las coordendas y direcciones de cada barco, que en esta versión se colocan automáticamente.

En cuanto a la funciones heredadas, realizan las siguientes tareas:

NuevaPartida inicializa los arrays tablero y contrario y coloca los barcos.

PedirBarco se limita a consultar los arrays de coordenadas y direcciones.

PedirCoordenada lo primero que hace es visualizar los dos tableros tal como los ve el jugador, es decir, el estado del tablero del jugador contrario, con las casillas donde hemos disparado, y si son o no barcos. También se muestra el nuestro tablero, con las posiciones de nuestros barcos y su estado, así como las posiciones donde ha disparado el jugador contrario.

Turno de jugador: 1: 0,5...Agua
Turno de jugador: 2
   ABCDEFGHIJ    ABCDEFGHIJ
 1   . .       1 .X. . OOO
 2   .X        2   .O    ..
 3 . . .       3         .
 4 .  ....     4  O. OO.OO
 5 .           5  O      .
 6   ......    6 .X O
 7             7  O O. O .
 8    ..       8    O .X
 9      .      9 O   .    .
10       .    10      .  O
Introduce coordendas (Ejemplo A8, C1, J10): _

El tablero de la izquierda es el del jugador contrario, el de la derecha el del que está jugando. Los puntos representan disparos que han fallado, las 'X' disparos acertados y las 'O' barcos sin tocar.

Esto nos permite elegir nuestra próxima coordenada y ver el estado de nuestra flota.

Finalmente se leen las coordenadas de disparo, en el caso del jugador humano no se permite elegir coordenadas prohibidas: fuera del tablero o donde cuyo contenido ya se conoce.

Responder actualiza el tablero contrario en función del contenido que nos indica el programa. Si se trata de un tocado o hundido además de marcarlo, marcaremos con agua las cuatro esquinas. En el caso de hundido, marcaremos como agua las casillas alrededor que no estén marcadas ya.

agua automática

En este ejemplo se ve que alrededor de cualquier barco todas las casillas son "agua", por lo tanto, una vez "hundido" el barco podemos marcarlas como "agua" y no necesitamos volver a disparar sobre ellas.

Del mismo modo, cuando "tocamos" un barco por primera vez, podemos estar seguros de que no existirán casillas con barco en ninguna de las diagonales. Podemos, por lo tanto, marcarlas como "agua".

Informar marca en el tablero propio los resultados de los disparos del jugador contrario.

Clase Jugador1

class Jugador1 : public Jugador {
  public:
   void NuevaPartida();
   DatosBarco PedirBarco(int n); 
   Coordenada PedirCoordenada();
   void Informar(Coordenada);
   void Responder(Coordenada, contenido);
   
  private:
   contenido tablero[2+cte::alto][2+cte::ancho];
   contenido contrario[2+cte::alto][2+cte::ancho];
   Coordenada coor[cte::nBarcos];
   direccion dir[cte::nBarcos];
   
   // Devuleve un valor aleatorio entre min y max-1
   int Alea(int min, int max);
   bool Colision(int);
};

Como verás, la declaración de la clase es idéntica a la de la clase Humano, se trata de un jugador automático sin ninguna inteligencia para el juego, se limitará a elegir casillas vacías de forma aleatoria para cada disparo.

Las únicas funciones diferentes son:

NuevaPartida, no necesitamos colocar nuestros barcos en el tablero, ya que no monitorizamos la partida del contrario.

PedirCoordenada, no necesitamos mostrar los tableros, nos limitamos a elegir una coordenada correspondiente a una casilla libre.

Informar, no hace nada, ya que no tenemos en cuenta qué hace el jugador contrario.