Maquina Inteligente Hundir la flota (Battleship) código C

Iniciado por Nakkuu, 29 Diciembre 2017, 13:30 PM

0 Miembros y 1 Visitante están viendo este tema.

Nakkuu

Hola, muy buenas fiestas a todos.

Aclaro que a continuación hablaré respecto al código C no C++.

Les comento, para el curso de este año nos han mandado la implementación (con unos requisitos) del juego de Hundir la flota (Battleship). Con su menú compacto/reducido, modalidades de juego (0 jugadores, 1 jugador o 2 jugadores) calcular la puntuación y la certeza de disparos, guardar datos en ficheros, mostrar top 10 de un fichero, etc.

El problema surje cuando tengo que programar la inteligencia del siguiente disparo de la máquina.

La maquina dispara en un tablero inicializado con todo agua ('?'). El primer disparo lo hace al azar en un tablero de 8x8, 9x9 o 10x10 decidido por el usuario.
En la modalidad 0, la máquina (el programa) juega toda sola, automaticamente: el programa genera disparos para descubrir la posición de todos los barcos i hundir la flota. El objetivo es hundirla con el menor numero de disparos posibles.

El programa decidirá el siguiente disparo llamando al procedimiento decide_disparo, para determinar fila y columna del siguiente disparo. El procedimiento debe decidir segun el contenido del tablero de disparos y de la estrategia a seguir, las coordenadas del siguiente disparo, que a continuación llamará a otro procedimiento ya implementado dispara() que nos efectua el tiro y devuelve el resultado de éste, si ha sido tocado, agua, hundido, etc.

Debemos seguir una estrategia de juego. Por ejemplo He programado que cuando se hunde un barco se envuelva el barco con el caracter agua tocada ('.').

Se me ocurren diferentes estrategias:
0. Disparos totalmente aleatorios, sin repetir casillas anteriores.
1. En caso que el ultimo disparo sea parte de un barco, disparar a casillas adyacentes. Si no se vuelve a tocar que dispare aleatoriamente.
2.En caso que sea barco, seguir una dirección de disparo (N,S,E,O) para decidir el siguiente disparo. Si no se encuentra ninguna disponible usamos la estrategia 0.
3. En caso de llegar al extremo de un barco, buscar en la dirección contraria. Si no se encuentra disponible, usar la estrategia 0.

A mejor estrategia mejor nota. He realizado la 1 con un correcto funcionamiento. Pero llevo dos semanas atascado en cómo poder implementar las otras dos estrategias (2-3). He pensado en usar variables auxiliares como memoria, para acordarse de los 2 disparos anteriores, guardando también sus coordenadas (en caso que fuese barco '@'). Y con esa información ir recorriendo usando el método BFS pero por lo que he encontrado por internet, éste metodo solo se utiliza en C++.
También pensé en una variable boleana que me permitiese saber cuando he tocado un barco por primera vez. Si miro a su alrededor y es todo (?) significa que lo he tocado por primera vez. Si alrededor de la casilla actual @ encuentro otro @ ya sabría la dirección que tomar.

El caso se encuentra cuando he tocado un barco y en alguna de las direcciones tengo 1, 2 o 3 aguas tocadas '.' (si tuviese 4 aguas tocadas significaría que el barco es de tamaño 1 casilla, y ya está hundido). Aquí es donde entraría en juego las variables aux de memoria.

Creo que se puede llegar a hacer más sencillo de cómo lo tengo planteado hacer.

Me gustaría leer ideas, aportes, consejos, y si alguien, con el espiritu Navideño, se ve con ganas de ayudar le podría pasar el proyecto entero.

Para decidir el disparo, nos exigen diseñar un procedimiento con la siguiente cabecera:

void P_decide_disparo (int *f, int *c, char tablero_disparos[][COL_MAX], unsigned int dim)

Dónde, *f= numero de la fila en la que hemos disparado (referencia)
*c= numero de la columna que hemos disparado (referencia)
tablero_disparos[][COL_MAX] --> mi tablero donde he disparado, y veo agua sin visitar (?), agua tocada (.), tocado (@). Cuando se hunde el barco se envuelve con agua tocada para saber que está hundido.
dim = valor de la dimensión del tablero 8, 9 o 10.

Podría colgar el código que tengo hecho del procedimiento por si a alguien le es mas sencillo seguir un planteamiento a raíz de éste.

Muchas gracias a toda la comunidad, soys de gran ayuda.

PD: antes de postear me he leido todo el foro y buscado ayuda sobre el tema, y al no encontrar nada sobre el tema no me ha quedado otra que abrir un post.

ivancea96

Bueno, lo primero, decir que ningún algoritmo es solo de "C++" o de "C"; los algoritmos no dependen del lenguaje.

Luego, hay muchas técnicas a usar, según la dificultad de la IA que quieras crear (dificultad para el jugador y dificultad para implementarla también).
En todo momento tienes que tener constancia de 2 cosas:
- Qué barcos le quedan al oponente.
- Un mapa ficticio con los lugares en los que puede haber un barco.

Como mucho va a haber 1 barco tocado sin hundir a la vez (supongamos que se hunde os barcos que se tocan), con lo cual puedes guardar una posición de ese barco en alguna variable de la IA, que limpiarías cuando el barco estuviera hundido.

Luego, el algoritmo sería el que propones. Si no hay barco tocado, disparos al "azar" (utilizando el mapa de lugares posibles). Si hay un barco tocado, en caso de que solo se haya descubierto 1 fragmento, buscaremos en qué dirección se extiende, y luegose hundirá al completo buscando en ambos sentidos hasta que se reciba la señal de hundido. En ese momento, se volvería al algoritmo al azar.

Acerca del mapa de posibilidades. En un principio, todo es posible. Sin embargo, según se vayan descubriendo fragmentos de barco, se irán tachando.
Lo primero, tachar los puntos adyacentes a barcos hundidos.
Luego, y esto es importante hacia el final de la partida, tachar los agujeros en los que no quepa ningún barco. por ejemplo, suponiendo que solo le queda 1 barco de longitud 4:
XXXX
X--X
XXXX

Aquí no cabría ningún barco de los restantes, así que lo tachamos todo.


Hecho el algoritmo básico, ya quedaría mejorarlo con temas de estadística. Pero bueno, en algo como esto, la estadística podría ser explotable si el jugador lo conoce, así que hasta aquí parece una IA suficientemente potable.

Serapis

#2
Al margen de como muestres por pantalla el mapa, es acertado que en tu array mantengas lo más coherente los datos referentes al estado del mapa.

No sé si te piden-puedes crear clases o si va todo en 'spaguetticode'... yo creraría una clase llamada mapa, que al inicio, al crearse recibe como parámetros el tamaño del mapa, y un array indicando cuantos buques tendrá y de qué tamaño... pero al caso puedes dejar las funciones fuera si no usas clases, duplicando datos para cada jugador...


Las ideas:
- Un array Mapa, contiene el estado del mapa, incialmente sus valoren valen 0, es decir todo agua, las casillas 'tocadas se marcan con valor 255, cualquier otro valor corresponde a la nave x.
- Un array de Naves, tiene tantos elementos como naves se alojarán en el mapa, y el valor de cada ínidice, indica cuantas casillas ocupa la nave... por ejmplo:
NavesA(0) = 2
NavesA(1) = 3
NavesA(2) = 3
NavesA(3) = 4
...etc... hay dos naves de 3 casillas, 1 de 2 casillas y una de 4... (no importa el orden en que estén).
- Hay dos flotas, luego hay dos arrays con el mapa de estado de cada jugador y del estado de sus naves.

Esto se refleja en el siguiente pseudocodigo.

// Datos jugador A
arrayBytes MapJA(x,y)    //x,y expresa dos dimensiones
arrayBytes NavesA()
entero NumNavesA

// Datos Jugador B
arrayBytes MapB(x,y)
arrayBytes NavesB()
entero NumNavesB


Funcion NuevoMapa(ancho, alto, buques() )
   alojar tamaño para MapA(ancho, alto)
   alojar tamaño para MapB(ancho, alto)

   numNavesA = buques.count  
   numNavesB = buques.count

   NavesA = buques
   NavesB = buques
Fin Funcion


- Cuando se posiciona una nave en el mapa, por ejemplo la nave de 4 casillas, donde se ubique en el mapa, tales casillas ocupadas, se rellenan con el valor del índice de dicho buque. por ejemplo: 3 3 3 3. Señala que la nave de índice 3 (NavesA(3), está ubicado en esas casillas).
- Cuando se toca una casilla de la nave, en el mapa se marca (por ejemplo), 255 (tocado), y  se resta una casilla en la nave.
- Cuando la nave llega a 0 casillas, la nave está hundida, se resta una nave del jugador.
- Cuando un jugador se queda sin naves, la partida acaba.

Esas reglas se reúnen en este fragmento de pseudocódigo:

entero = Funcion Disparar(x,y, Jugador)
   byte estado

   Si (jugador = 0) luego  // A dispara a B
       estado = MapB(x,y)  // valor de estado del mapa para esa casilla.

       Si ((estado > 0) y (estado < 255) )  // se acaba de tocar un barco.
           MapB(x,y) = 255            // casilla tocada, se marca en el mapa, el nuevo estado.
           NavesB(estado) -=1       // se resta una casilla a este barco.
           Si (NavesB(estado) = 0)  //Hundido, cuando a una nave no le quedan casillas, se considera hundido
               numNaves  -=1
               Si (numNaves = 0)   // no le quedan naves. 'jugador' ganó la partida.        
                   Devolver -1
               Fin si
           Fin si            
           Devolver 1 // esto es 'tocado/hundido' jugador sigue tirando, sigue en su turno.
      fin si
      Devolver 0  // 'agua' o ya tocada.
  Sino // jugador =1, B dispara a A
     // igual que arriba pero cambiando B, por A
  Fin si
Fin funcion



El bucle principal del juego (simplificado al máximo)...

funcion MainPartida
   entero d, t, n     //d=resultado del disparo, t=turno de qué jugador.

   DecidirTamañoMapa(x,y)  // jugadores indican que tamaño tendrá el mapa.
   ArrayBytes Buques = DecidirNumeroNavesYTamaños
   NuevoMapa(x,y, buques)
   
   PosicionarFlota(buques, 0) // 0=Jugador A, se le pide que posicione sus buques en el mapa.
   PosicionarFlota(buques, 1) // 0=Jugador B,    "          "

   t = ElegirTurnoalAzar(entre 0 y 1) //t indica el jugador que tiene el turno

   // Bucle del juego hasta fin de partida.
   Hacer
       ElegirNuevasCordenadasDeDisparo(t,x,y) // se usa 't', para que el jugador específico elija las cordenadas.
       d = Disparar(x,y, t)  //t hace referencia a que mapa del jugador afecta,
                                   // con clases 't' en vez de ser un parámetro sería un objeto y disparo un método de dicho objeto.
       Si (d = -1) luego
           llamar FinalPartida("Perdiste, no te quedan naves. Ganó el jugador xxxx (que tiene el turno de disparo"
       Osi (d = 0) luego // tocado o hundido
           //CambiarTurnoJugador
           t = ValorAbsoluto(1 - t) //cambiamos el turno de jugador: esto alterna cada vez entre 0,1,0,1,0,1,0,1
       sino
           // el jugador que tiene el turno, sigue disparando hasta que falle (d=0), o no queden naves (d=-1)
       Fin si
       n += 1
   Repetir mientras ( d<> -1) //  '<>' distinto de // ó: mientras FinalPartida(d) = False

   MostrarMensaje (La partida precisó 'n' disparos para llegar a su fin...) //no es ni mucho menos la estadística que requieres,
                       // pero no cuesta constar los disparos realizados por ambos jugadores.
Fin funcion



Queda a tu esfuerzo, el resto de funciones:
- DecidirTamañoMapa, DecidirNumeroNavesYTamaños: El trabajo de estas funciones podría quedar fijado por diseño o inicializarse con unos valores y al inicio de cada partida pedir al usuario si quiere cambiarlo.

- Con los valores previos establecidos (de las dos previas funciones), la función: NuevoMapa ya está descrita en pseudocódigo...

- PosicionarFlota: esta función se invoca dos veces. En cada una se pide al jugador que posicione sus naves en el mapa. Dicha función marcará en el mapa las casillas que ocupa cada nave, metiendo el valor de la nave que ahí se aloja.
 Debe impedirse que una nave solape otra, o que no quepa... ...luego es imprescindible que para ubicar una nave en el mapa, se consulte si la posición 'x,y' que indica el usuario como 'punto central de la nave' (o de comienzo) cabe la nave (dado su tamaño), en vertical u horizontal, contando las casillas 'agua' adhiacentes a la marcada, si cabe tanto en vertical como horizontal, luego pedir al usuario que decida la orientación, si solo cabe en una orientación se deja en esa, y al final se exige que pulse 'intro' para confirmar, o 'esc' para reposicionar en otro lado. Si pulsa 'esc', se borran las casillas marcadas para ese buque. Si pulsa intro, se avanza en el array para posicionar la siguiente nave.
 Cuando todas las naves en el array estén alojadas en el mapa, también se pedirá si confirmar con intro o si se desea cambiar una nave específica, para ello pulsar el índice de la misma....

- ElegirTurnoalAzar: esta función simplemente decide que jugador empieza a jugar. 0 = representa al jugador A, y es su turno y 1 =representa al jugador B y será su turno.
- CambiarTurnoJugador: es una función tan simple que ni se crea, se alterna siempre del uno al otro, luego una línea de código basta...

- ElegirNuevasCordenadasDeDisparo: Esta función es la que puede entretenerte un poco más, ya que es donde tu reparas en como operar sobre ella.
Cuando juega la máquina contra la máquina, una estrategia de disparar al azar, puede ser aceptable incialmente, sin embargo una mejor estrategia es elegir zonas y sobre zonas, una casilla en la zona... entendiendo por zonas la división del mapa en cuadrantes, del tamaño de la nave mayor, es decir supongamos que el mapa tiene 16x16 casillas y que la nave mayor ocupa 4 casillas, entonces sería útil considerar al mapa dividido en 4x4 zonas, entonces si disparas a distancias de zona, vas asegurando que tocarás la nave mayor si o sí, de forma más coherente que al azar. linea 3, linea 7, 11 y 15 verticales y horizontales. Una vez cubierto así el mapa habrás dado con todas las de 4, se divide cada zona en la mitad y así... naturalmente cuando tocas una nave, te ciñes sobre ella hasta hundirla...
 Al caso para cada jugador deberías guardar como va la estrategia. Como por ejemplo copia de donde ha disparado previamente el jugador, para no repetir disparos... es decir se elige al azar, pero si ya se disparó allí se elige otra posición.
Diseñado con objetos sería más sencillo de hacer y mantener el código.

- Disparar: Esta función evalúa sobre el mapa el resultado que afecta a la flota contra la que se dispara, y el resultado del disparo se evalúa tras dicha función como parte del bucle principal del juego.

- FinalPartida: En el pseudocódigo se usa para indicar un mensaje del resultado de la partida, pero podría trasladarse allí parte del código del bucle principal... y sería quien evaluara si al jugador le quedan naves o no...

En fin, ya tienes un planteamiento general y la distribución de funciones, ahora solo toca rellenar el código para cada función y probarlo...

Nakkuu

Cita de: NEBIRE en 29 Diciembre 2017, 20:23 PM
Al margen de como muestres por pantalla el mapa, es acertado que en tu array mantengas lo más coherente los datos referentes al estado del mapa.

No sé si te piden-puedes crear clases o si va todo en 'spaguetticode'... yo creraría una clase llamada mapa, que al inicio, al crearse recibe como parámetros el tamaño del mapa, y un array indicando cuantos buques tendrá y de qué tamaño... pero al caso puedes dejar las funciones fuera si no usas clases, duplicando datos para cada jugador...


Las ideas:
- Un array Mapa, contiene el estado del mapa, incialmente sus valoren valen 0, es decir todo agua, las casillas 'tocadas se marcan con valor 255, cualquier otro valor corresponde a la nave x.
- Un array de Naves, tiene tantos elementos como naves se alojarán en el mapa, y el valor de cada ínidice, indica cuantas casillas ocupa la nave... por ejmplo:
NavesA(0) = 2
NavesA(1) = 3
NavesA(2) = 3
NavesA(3) = 4
...etc... hay dos naves de 3 casillas, 1 de 2 casillas y una de 4... (no importa el orden en que estén).
- Hay dos flotas, luego hay dos arrays con el mapa de estado de cada jugador y del estado de sus naves.

Esto se refleja en el siguiente pseudocodigo.

// Datos jugador A
arrayBytes MapJA(x,y)    //x,y expresa dos dimensiones
arrayBytes NavesA()
entero NumNavesA

// Datos Jugador B
arrayBytes MapB(x,y)
arrayBytes NavesB()
entero NumNavesB


Funcion NuevoMapa(ancho, alto, buques() )
   alojar tamaño para MapA(ancho, alto)
   alojar tamaño para MapB(ancho, alto)

   numNavesA = buques.count  
   numNavesB = buques.count

   NavesA = buques
   NavesB = buques
Fin Funcion


- Cuando se posiciona una nave en el mapa, por ejemplo la nave de 4 casillas, donde se ubique en el mapa, tales casillas ocupadas, se rellenan con el valor del índice de dicho buque. por ejemplo: 3 3 3 3. Señala que la nave de índice 3 (NavesA(3), está ubicado en esas casillas).
- Cuando se toca una casilla de la nave, en el mapa se marca (por ejemplo), 255 (tocado), y  se resta una casilla en la nave.
- Cuando la nave llega a 0 casillas, la nave está hundida, se resta una nave del jugador.
- Cuando un jugador se queda sin naves, la partida acaba.

Esas reglas se reúnen en este fragmento de pseudocódigo:

entero = Funcion Disparar(x,y, Jugador)
   byte estado

   Si (jugador = 0) luego  // A dispara a B
       estado = MapB(x,y)  // valor de estado del mapa para esa casilla.

       Si ((estado > 0) y (estado < 255) )  // se acaba de tocar un barco.
           MapB(x,y) = 255            // casilla tocada, se marca en el mapa, el nuevo estado.
           NavesB(estado) -=1       // se resta una casilla a este barco.
           Si (NavesB(estado) = 0)  //Hundido, cuando a una nave no le quedan casillas, se considera hundido
               numNaves  -=1
               Si (numNaves = 0)   // no le quedan naves. 'jugador' ganó la partida.        
                   Devolver -1
               Fin si
           Fin si            
           Devolver 1 // esto es 'tocado/hundido' jugador sigue tirando, sigue en su turno.
      fin si
      Devolver 0  // 'agua' o ya tocada.
  Sino // jugador =1, B dispara a A
     // igual que arriba pero cambiando B, por A
  Fin si
Fin funcion



El bucle principal del juego (simplificado al máximo)...

funcion MainPartida
   entero d, t, n     //d=resultado del disparo, t=turno de qué jugador.

   DecidirTamañoMapa(x,y)  // jugadores indican que tamaño tendrá el mapa.
   ArrayBytes Buques = DecidirNumeroNavesYTamaños
   NuevoMapa(x,y, buques)
   
   PosicionarFlota(buques, 0) // 0=Jugador A, se le pide que posicione sus buques en el mapa.
   PosicionarFlota(buques, 1) // 0=Jugador B,    "          "

   t = ElegirTurnoalAzar(entre 0 y 1) //t indica el jugador que tiene el turno

   // Bucle del juego hasta fin de partida.
   Hacer
       ElegirNuevasCordenadasDeDisparo(t,x,y) // se usa 't', para que el jugador específico elija las cordenadas.
       d = Disparar(x,y, t)  //t hace referencia a que mapa del jugador afecta,
                                   // con clases 't' en vez de ser un parámetro sería un objeto y disparo un método de dicho objeto.
       Si (d = -1) luego
           llamar FinalPartida("Perdiste, no te quedan naves. Ganó el jugador xxxx (que tiene el turno de disparo"
       Osi (d = 0) luego // tocado o hundido
           //CambiarTurnoJugador
           t = ValorAbsoluto(1 - t) //cambiamos el turno de jugador: esto alterna cada vez entre 0,1,0,1,0,1,0,1
       sino
           // el jugador que tiene el turno, sigue disparando hasta que falle (d=0), o no queden naves (d=-1)
       Fin si
       n += 1
   Repetir mientras ( d<> -1) //  '<>' distinto de // ó: mientras FinalPartida(d) = False

   MostrarMensaje (La partida precisó 'n' disparos para llegar a su fin...) //no es ni mucho menos la estadística que requieres,
                       // pero no cuesta constar los disparos realizados por ambos jugadores.
Fin funcion



Queda a tu esfuerzo, el resto de funciones:
- DecidirTamañoMapa, DecidirNumeroNavesYTamaños: El trabajo de estas funciones podría quedar fijado por diseño o inicializarse con unos valores y al inicio de cada partida pedir al usuario si quiere cambiarlo.

- Con los valores previos establecidos (de las dos previas funciones), la función: NuevoMapa ya está descrita en pseudocódigo...

- PosicionarFlota: esta función se invoca dos veces. En cada una se pide al jugador que posicione sus naves en el mapa. Dicha función marcará en el mapa las casillas que ocupa cada nave, metiendo el valor de la nave que ahí se aloja.
 Debe impedirse que una nave solape otra, o que no quepa... ...luego es imprescindible que para ubicar una nave en el mapa, se consulte si la posición 'x,y' que indica el usuario como 'punto central de la nave' (o de comienzo) cabe la nave (dado su tamaño), en vertical u horizontal, contando las casillas 'agua' adhiacentes a la marcada, si cabe tanto en vertical como horizontal, luego pedir al usuario que decida la orientación, si solo cabe en una orientación se deja en esa, y al final se exige que pulse 'intro' para confirmar, o 'esc' para reposicionar en otro lado. Si pulsa 'esc', se borran las casillas marcadas para ese buque. Si pulsa intro, se avanza en el array para posicionar la siguiente nave.
 Cuando todas las naves en el array estén alojadas en el mapa, también se pedirá si confirmar con intro o si se desea cambiar una nave específica, para ello pulsar el índice de la misma....

- ElegirTurnoalAzar: esta función simplemente decide que jugador empieza a jugar. 0 = representa al jugador A, y es su turno y 1 =representa al jugador B y será su turno.
- CambiarTurnoJugador: es una función tan simple que ni se crea, se alterna siempre del uno al otro, luego una línea de código basta...

- ElegirNuevasCordenadasDeDisparo: Esta función es la que puede entretenerte un poco más, ya que es donde tu reparas en como operar sobre ella.
Cuando juega la máquina contra la máquina, una estrategia de disparar al azar, puede ser aceptable incialmente, sin embargo una mejor estrategia es elegir zonas y sobre zonas, una casilla en la zona... entendiendo por zonas la división del mapa en cuadrantes, del tamaño de la nave mayor, es decir supongamos que el mapa tiene 16x16 casillas y que la nave mayor ocupa 4 casillas, entonces sería útil considerar al mapa dividido en 4x4 zonas, entonces si disparas a distancias de zona, vas asegurando que tocarás la nave mayor si o sí, de forma más coherente que al azar. linea 3, linea 7, 11 y 15 verticales y horizontales. Una vez cubierto así el mapa habrás dado con todas las de 4, se divide cada zona en la mitad y así... naturalmente cuando tocas una nave, te ciñes sobre ella hasta hundirla...
 Al caso para cada jugador deberías guardar como va la estrategia. Como por ejemplo copia de donde ha disparado previamente el jugador, para no repetir disparos... es decir se elige al azar, pero si ya se disparó allí se elige otra posición.
Diseñado con objetos sería más sencillo de hacer y mantener el código.

- Disparar: Esta función evalúa sobre el mapa el resultado que afecta a la flota contra la que se dispara, y el resultado del disparo se evalúa tras dicha función como parte del bucle principal del juego.

- FinalPartida: En el pseudocódigo se usa para indicar un mensaje del resultado de la partida, pero podría trasladarse allí parte del código del bucle principal... y sería quien evaluara si al jugador le quedan naves o no...

En fin, ya tienes un planteamiento general y la distribución de funciones, ahora solo toca rellenar el código para cada función y probarlo...
Muchas gracias por toda la colaboración pero creo que, hubiese estado bien, dijese que todas las funciones de los jugadores, registros, campos, inicializaciones, procedimientos de disparar, crear tableros por dimensiones, colocación de barcos, rellenar tableros, rodear buques hundidos con aguas tocadas, etc, todo esto lo tengo hecho ya.

No usamos clases, ya que se trata de la asignatura fundamentos de programación y se nos explica funciones basicas(strcpy, toupper, etc), bucles, procedimientos (accion funcion) y registros.

Solamente lo que pido es como plantear la IA para que decida el siguiente disparo. Tengo ya una función que según la coordenada dispara en el tablero de barcos (rival) y actualiza el tablero de disparos (jugador) devolviendo (3 si hundido, 2 si tocado, 1 si agua, 0 si casilla repetida y -1 fuera de rango). Después saber el resultado del disparo, es aquí donde debo decidir el siguiente disparo.

Es decir empecemos con un barco de 3 casillas y tocamos.
? ? ?         ?  ?  ? 
? ? ?   --> ? @  ?
? ? ?         ?  ?  ?     

Primero miro las direcciones (arriba, abajo, izquierda, derecha) si son desconocidas ? disparo al azar a cualquiera de ellas.
?  ? ?         
? @ . 
?  ? ?     
En este caso hemos tocado agua, pero debemos recordar que hace 2 tiros hemos tocado, por lo tanto no debemos volver a disparar al azar en cualquier parte del tablero.
Por lo tanto miramos de nuevo direcciones desde el primer @ encontrado.
?  ? ?         
. @ . 
?  ? ?
En este caso, nos encontramos que hemos tocado tanto izquierda como derecha del @ y hemos obtenido agua. Por lo tanto ya sabemos que el buque se encuentra en modo Vertical, simplemente debemos generar un disparo aleatorio entre la fila anterior y la posterior y misma columna.

El problema es que tenemos muchas combinaciones con el agua tocado y el primer @ para descubrir su orientación y en lugar de poner una a una cada posible combinacion (creo que eso hace mi codigo ineficiente). Creo que la logica de la IA se puede simplificar mucho más de lo que lo estoy complicando. Lo que busco es consejos y ayudas, para poder lograr hacer la IA lo más inteligente posible sin poner todas las combinaciones posibles.

Por combinaciones posibles me refiero a:
?  .   ?         
.  @  .  (el buque esta en Vertical, si Norte es ? también)
?  ?  ?

?  ?  ?         
.  @ .  (buque esta en Vertical, si Sur es ? también)
?  .  ?

?  .  ?         
.  @ ?  (buque esta en Horizontal, si Oeste es ? también)
?  .  ?


?  .  ?         
?  @ .  (buque esta en Hoizontal, si Este es ? también)
?  .  ?

Solamente con éstas opciones ya son 8 combinaciones posibles. Hay que sumar las combinaciones de cuando tenemos solamente un . donde:

?  ?  ?         
.  @ ?  (buque esta en Vertical o Horizontal) <-- x4 combinaciones del .
?  ?  ?

Ésta es la forma en la que yo, al jugar como usuario, sigo para decidir el siguiente disparo. Lo que llevo tiempo intentando es pasar ésta logica de decisión a código C. Que tipo de estructuras serían las más apropiadas, etc. Aclarar también que la cabecera del procedimiento decide_disparo que he mencionado en el primer post, no se puede modificar por especificaciones del enunciado.

Gracias por su atención S2

Serapis

#4
Simplemente debes tener una estructura (sencilla) donde mantener dicha info, para seguir indagando.


Una imagen para que hagas una idea de como queda y luego la estructura, detrás las explicaciones

Cerco:

.|0 1 2 3 4 5 6
A|X X X 4 X X X
B|X X X 4 X X X
C|X X X 4 X X X
D|2 2 2 @ 1 1 1
E|X X X 3 X X X
F|X X X 3 X X X
G|X X X 3 X X X



Finalmente tendrías una estructura para mantener junto los datos tal que así:


Estructura DatosDisparo    
    byte Tocado                     // indica cuando se toca por vez primera una nave, y luego con cada nuevo 'tocado' aumenta en uno. asi este valor hasta hundirlo.
    byte Orientacion              // indica a qué lado estamos disparando del cerco.
    //byte cX                        // cordenada 'x' del primer tocado en la nave.
    //byte cY                        // cordenada 'y' del primer tocado en la nave.
    //arrayBytes Cerco(7,7)  // Array imaginario, operamos sobre un área de 7x7 (dado un tamaño de nave de 4 casillas)
Fin estructura

DatosDisparo dDisparo     // instancia de la estructura.


La arroba, representa tu primer disparo con acierto. Tocado se pone a 1, Luego siempre sigues la dirección del reloj, empiezas por donde quieras, yo he puesto 1 al este... si aciertas de nuevo sumas 1 a tocado y se avanza en la orientacion y sentido que lleva. Así si es 1, sigues en 1... pero si falla, continúa en la misma dirección pero en el otro sentido, y el valor de orientación se actualiza como toca...
El orden de 1,2,3 y 4, obedece a facilitar el código... otras maneras son posibles, pr supuesto.

La estrategia es que si el disparo ha tocado un buque (valor 2 en tu caso), por primera vez, se consdiera una zona centrada en dicha posición, del mapa de disparos,  centrnado el disparo en dicho array, es decir Cerco(3,3) = 2 // tocado
Y estableces un valor buleano Tocado=1, ahora la posición donde disparar la siguiente vez, va condicionado...

Voy mejor con pseudocódigo, sino con tantos condicionantes es fácil perderse...

buleano = Funcion SiguienteDisparo(byte resultado, byte x, byte y)  //tu llama la función como te dé la gana... o atendiendo a como te reclamen.  
   Si (resultado = 2)   // Tocado
       Si (dDisparo.Tocado > 0)
           Si (dDisparo.Tocado < 4)  
               dDisparo.Tocado += 1              // Otra casilla tocada de la nave (o de una nave contigua)
         
               Si (dDisparo.Orientacion = 1)    // avanzamos a derecha
                   x += 1
               OSi (dDisparo.Orientacion = 2)   // avanzamos hacia izquierda                
                   x -= 1                  
               OSi (dDisparo.Orientacion = 3)   // avanzamos hacia abajo                    
                   y += 1                              
               OSi (dDisparo.Orientacion = 4)   // avanzamos hacia arriba
                   y -= 1
               Fin si              
           Si no    
               dDisparo.Tocado = 0      
               Devolver FALSE          
               // NOTA2:  Si se ha tocado tantas veces como casillas tiene la nave mayor, cabe decir que hay un buque contiguo a éste (o más)...  
               // no se ha hundido, porque sino, se hubiera recibido un resultado 3.
               // Es decir: al menos dos buques tienen casillas en este área y están tocándose
               // Luego la suma de 'dDisparo.tocado' ha tocado casillas de al menos dos naves,...
               // Resolver este caso, lo dejo a tu esfuerzo y consideración...
           Fin si
       Sino  // aquí hay un buque (lo acabamos de descubrir)... insistiremos hasta hundirlo.
           // Reset valores de la estructura.
           dDisparo.Tocado = 1            
           dDisparo.Orientacion = 1
           //dDisparo.cX = x
           //dDisparo.cY = y
           //CopiarParteDelMapaACerco
           
           x += 1   // orientación: 1, avanzamos a derecha, 'y' no cambia.              
       Fin si      
       Devolver TRUE
   Osi (Resultado = 3)                // Hundido, se supone ya restada una nave.        
       dDisparo.Tocado = 0          // si esta nave está hundida, retiramos la marca para seguir buscando alrededor...
       Devolver FALSE
   Sino
       Si (dDisparo.Tocado > 0)              // un disparo previo acertó,                         
           Si (dDisparo.Orientacion < 4)      // si no se han agotado todas las posibilidades.
               dDisparo.Orientacion += 1      // cambiamos de orientación.
               Si (dDisparo.Orientacion = 2)  // ---> cambia de sentido en horizontal...  hacia izquierda                
                   x -= (dDisparo.Tocado + 1)  // 'y' no cambia                  
               OSi (dDisparo.Orientacion = 3) // ---> cambia a vertical abajo
                   x -= dDisparo.Tocado          // vuelve a la cordenada central (de Cerco) en el eje 'x'
                   y +=1                                // y baja una fila
               OSi (dDisparo.Orientacion = 4) // ---> cambia de sentido en vertical... hacia arriba...
                   y -= (dDisparo.Tocado + 1)  // 'x' no cambia.
               Fin si
               Devolver TRUE
           Sino                                                                              
               dDisparo.Tocado = 0      
               Devolver FALSE
               // NOTA2:  Agotada todas las orientaciones cabe decir que hay un buque contiguo a éste...  
               // no se ha hundido, porque sino, se hubiera recibido un resultado 3.
               // Es decir: al menos dos buques tienen casillas en este área y están tocándose
               // Luego la suma de 'dDisparo.tocado' ha tocado casillas de al menos dos naves,...
               // Resolver este caso, lo dejo a tu esfuerzo y consideración...
           Fin si
       Sino        
           Devolver FALSE
       Fin si
   Fin si
Fin funcion


Observa que en realidad el array 'cerco', no lo usamos para nada, ni copiamos ni escribimos, ni leemos de él, es una idealización para saber que estamos haciendo y 'no perder el norte'...
Los parámetros 'x' e 'y' son de entrada y salida...
La función devuelve un buleano, para indicar si el próximo disparo debe dirigirse el disparo a los valores x,y devueltos si la función devuelve TRUE, si devuelve false, se debe elegir otra posición... pero devlviendo TRUE, 'x' e 'y', contiene la dirección donde disparar para cercar al buque tocado hasta hundirlo.

- Una vez que se ha tocado, un barco, sigue la estrategia de recorrer en la misma orientación hasta el fallo, en cuyo caso se recorre en el sentido opuesto, acabado el otro sentido, cambia la orientación a vertical y finalmente invierte el sentido de este... ese es el orden de recorrido.

- Nota que caben posibilidades de que haya 2 o más naves que estén contiguas (paralelas), por lo que puede darse falso positivo (tocar dos naves distintas y no dos casillas de la misma nave (incluso 3, 4 naves...), con dos disparos seguidos). He dejado un comentario de //NOTA1 y //NOTA2, donde esto sucede... y aún puede darse un caso de NOTA3...

// NOTA2: al caso dDisparo.Tocado contiene el valor de cuantas naves han sido tocadas y la orientación actual descubre que están en una orientación atravesada.
//NOTA1: También cabe la posibilidad de una nave pongamos vertical (que tocamos con 'dDisparo.Tocado = 1, el centro del 'Cerco'), incluso alguna más también en vertical y luego otra en horizontal a continuación de áquella última tocada, luego llegamos a 4 tocados, pero no hay ninguna nave hundida (hubiéramos recibido un valor de resultado = 3)... luego hay que continuar la dirección más allá de las medidas de 'Cerco', para hundirlo y tras hundirlo regresar sus casillas hacia atrás, y continuar en otra dirección...
Ambos casos, los dejo en el limbo, a tu esfuerzo... es más de lo mismo, puede resolverse descontando los Tocados de la nave hundida y cambiar en la otra dirección vertical desde la posición previa (descontada en dicho eje de la nave hundida) //NOTA3: Todavía existe el caso de que dos naves estén colocadas 'a testa' una de otra, también dejo a tu esfuerzo incluso donde va este caso dentro del árbol de decisiones....

Si quieres evitar esos casos molestos, basta que añadas una regla al juego: dos naves no pueden posicionarse  tocándose, siempre debe haber al menos un casilla de espacio entre ellas (con la excusa de que eso impediría el movimiento en un caso real  :laugh: :laugh: :silbar: ). Esto elimina todas esa complicaciones...

Cerco:

.|0 1 2 3 4 5 6
A|X X X 4 X X X
B|X X X 4 X X X
C|X X X 4 X X X
D|2 2 2 @ 1 1 1
E|X X X 3 X X X
F|X X X 3 X X X
G|X X X 3 X X X


Nota que habiendo tocado una casilla y considerando a dicha casilla como el centro de un cerco, basta tocar otras 3, para hundir el barco más grande que haya (supuesto el caso de naves de tamaño 4  como máximo), sin embargo, el cerco aunque sea imaginario de 4 casillas en una dimensión en realidad queda determinado por el valor de Tocado... es decir mientras se toque en una dirección se avanza en ella, así podría a llegar a 5, 6, 10... con lo que en realidad no importa el tamaño de las naves. Pero es preciso, hacer siempre una idealización razonada y cuando procede un dibujo...
Para hacer el pseudocódigo independiente del tamaño de las naves habría que tener una variable donde está línea, para el valor 4, con el valor propio de casillas que tenga la nave de mayor tamaño.
Si (dDisparo.Tocado < 4)

Como el pseudocódigo está escrito al aire (sobre la marcha), es posible que se haya escapado algún gazapo...

-----------------------
Todo lo previo, respecto de tu interés, y ahora respecto de otras observaciones que pareces obviar...
- El valor de: "casilla repetida = 0", es absurdo... es obligado llevar dos mapas (por flota), aunque en realidad el segundo mapa de una flota, es el primero de la otra, luego pueden compartirse parcialmente.
Me explico: ¿quién en su sano juicio tira dos 'bombas' al mismo sitio, si ya fue agua, o tocado???. Y se sabe que fue agua o tocado, porque... en efecto llevas un mapa de donde has tirado previamente, no llevar un mapa es carecer de memoria, luego una IA, no tiene sentido donde no hay ni persistencia de la memoria. La IA es precisamente operar en consideración  de la memoria, de la 'experiencia', mediante la retroalimentación... si no hay memoria, no hay nada que retroalimentar... por eso mantener una estructura de lo que ha sucedido previamente es la IA mínima que se puede prestar...

Así los dos mapas son: Donde yo tiro a la flota enemiga y 2º, donde el enemigo me tira a mi.  es inverso para el caso del enemigo, excepto en que en el mapa propio, están todas las naves y 'el enemigo solo ve', lo que ha tocado o hundido, es decir... si tenemos un valor de nave intacta en el mapa, para el enemigo, se le reprseenta como agua... y si no, toca mantener dos mapas independientes por cada flota.

Es abusrdo hablar de IA, si luego uno va a volver a tirar a casillas a las que ya ha tirado. Hay un caso donde esto es posible, y es en una versión más compleja del juego, donde en cada turno, el que tira mueve una de sus naves (la que quiera) una casilla en uno de los dos sentidos de la orientación que tiene, por lo que en efecto, podrá dispararse a casillas ya disparadas, porque las flotas 'se mueven', pero es una regla que hace el juego mucho más complejo...
---------------

Siento todo el párrafo del mensaje anterior, que al parecer te resulta innecesario,  porque al hacer preguntas, los usuarios a menudo os dejais en el tintero detalles importantes... como "ya tengo listo todo lo demás, me falta solo esto". ...pero bueno, a alguien podrá servirle.

Nakkuu

#5
Cita de: NEBIRE en 31 Diciembre 2017, 02:13 AM
Simplemente debes tener una estructura (sencilla) donde mantener dicha info, para seguir indagando.


Una imagen para que hagas una idea de como queda y luego la estructura, detrás las explicaciones

Cerco:

.|0 1 2 3 4 5 6
A|X X X 4 X X X
B|X X X 4 X X X
C|X X X 4 X X X
D|2 2 2 @ 1 1 1
E|X X X 3 X X X
F|X X X 3 X X X
G|X X X 3 X X X



Finalmente tendrías una estructura para mantener junto los datos tal que así:


Estructura DatosDisparo    
    byte Tocado                     // indica cuando se toca por vez primera una nave, y luego con cada nuevo 'tocado' aumenta en uno. asi este valor hasta hundirlo.
    byte Orientacion              // indica a qué lado estamos disparando del cerco.
    //byte cX                        // cordenada 'x' del primer tocado en la nave.
    //byte cY                        // cordenada 'y' del primer tocado en la nave.
    //arrayBytes Cerco(7,7)  // Array imaginario, operamos sobre un área de 7x7 (dado un tamaño de nave de 4 casillas)
Fin estructura

DatosDisparo dDisparo     // instancia de la estructura.


La arroba, representa tu primer disparo con acierto. Tocado se pone a 1, Luego siempre sigues la dirección del reloj, empiezas por donde quieras, yo he puesto 1 al este... si aciertas de nuevo sumas 1 a tocado y se avanza en la orientacion y sentido que lleva. Así si es 1, sigues en 1... pero si falla, continúa en la misma dirección pero en el otro sentido, y el valor de orientación se actualiza como toca...
El orden de 1,2,3 y 4, obedece a facilitar el código... otras maneras son posibles, pr supuesto.

La estrategia es que si el disparo ha tocado un buque (valor 2 en tu caso), por primera vez, se consdiera una zona centrada en dicha posición, del mapa de disparos,  centrnado el disparo en dicho array, es decir Cerco(3,3) = 2 // tocado
Y estableces un valor buleano Tocado=1, ahora la posición donde disparar la siguiente vez, va condicionado...

Voy mejor con pseudocódigo, sino con tantos condicionantes es fácil perderse...

buleano = Funcion SiguienteDisparo(byte resultado, byte x, byte y)  //tu llama la función como te dé la gana... o atendiendo a como te reclamen.  
   Si (resultado = 2)   // Tocado
       Si (dDisparo.Tocado > 0)
           Si (dDisparo.Tocado < 4)  
               dDisparo.Tocado += 1              // Otra casilla tocada de la nave (o de una nave contigua)
         
               Si (dDisparo.Orientacion = 1)    // avanzamos a derecha
                   x += 1
               OSi (dDisparo.Orientacion = 2)   // avanzamos hacia izquierda                
                   x -= 1                  
               OSi (dDisparo.Orientacion = 3)   // avanzamos hacia abajo                    
                   y += 1                              
               OSi (dDisparo.Orientacion = 4)   // avanzamos hacia arriba
                   y -= 1
               Fin si              
           Si no    
               dDisparo.Tocado = 0      
               Devolver FALSE          
               // NOTA2:  Si se ha tocado tantas veces como casillas tiene la nave mayor, cabe decir que hay un buque contiguo a éste (o más)...  
               // no se ha hundido, porque sino, se hubiera recibido un resultado 3.
               // Es decir: al menos dos buques tienen casillas en este área y están tocándose
               // Luego la suma de 'dDisparo.tocado' ha tocado casillas de al menos dos naves,...
               // Resolver este caso, lo dejo a tu esfuerzo y consideración...
           Fin si
       Sino  // aquí hay un buque (lo acabamos de descubrir)... insistiremos hasta hundirlo.
           // Reset valores de la estructura.
           dDisparo.Tocado = 1            
           dDisparo.Orientacion = 1
           //dDisparo.cX = x
           //dDisparo.cY = y
           //CopiarParteDelMapaACerco
           
           x += 1   // orientación: 1, avanzamos a derecha, 'y' no cambia.              
       Fin si      
       Devolver TRUE
   Osi (Resultado = 3)                // Hundido, se supone ya restada una nave.        
       dDisparo.Tocado = 0          // si esta nave está hundida, retiramos la marca para seguir buscando alrededor...
       Devolver FALSE
   Sino
       Si (dDisparo.Tocado > 0)              // un disparo previo acertó,                        
           Si (dDisparo.Orientacion < 4)      // si no se han agotado todas las posibilidades.
               dDisparo.Orientacion += 1      // cambiamos de orientación.
               Si (dDisparo.Orientacion = 2)  // ---> cambia de sentido en horizontal...  hacia izquierda                
                   x -= (dDisparo.Tocado + 1)  // 'y' no cambia                  
               OSi (dDisparo.Orientacion = 3) // ---> cambia a vertical abajo
                   x -= dDisparo.Tocado          // vuelve a la cordenada central (de Cerco) en el eje 'x'
                   y +=1                                // y baja una fila
               OSi (dDisparo.Orientacion = 4) // ---> cambia de sentido en vertical... hacia arriba...
                   y -= (dDisparo.Tocado + 1)  // 'x' no cambia.
               Fin si
               Devolver TRUE
           Sino                                                                              
               dDisparo.Tocado = 0      
               Devolver FALSE
               // NOTA2:  Agotada todas las orientaciones cabe decir que hay un buque contiguo a éste...  
               // no se ha hundido, porque sino, se hubiera recibido un resultado 3.
               // Es decir: al menos dos buques tienen casillas en este área y están tocándose
               // Luego la suma de 'dDisparo.tocado' ha tocado casillas de al menos dos naves,...
               // Resolver este caso, lo dejo a tu esfuerzo y consideración...
           Fin si
       Sino        
           Devolver FALSE
       Fin si
   Fin si
Fin funcion


Observa que en realidad el array 'cerco', no lo usamos para nada, ni copiamos ni escribimos, ni leemos de él, es una idealización para saber que estamos haciendo y 'no perder el norte'...
Los parámetros 'x' e 'y' son de entrada y salida...
La función devuelve un buleano, para indicar si el próximo disparo debe dirigirse el disparo a los valores x,y devueltos si la función devuelve TRUE, si devuelve false, se debe elegir otra posición... pero devlviendo TRUE, 'x' e 'y', contiene la dirección donde disparar para cercar al buque tocado hasta hundirlo.

- Una vez que se ha tocado, un barco, sigue la estrategia de recorrer en la misma orientación hasta el fallo, en cuyo caso se recorre en el sentido opuesto, acabado el otro sentido, cambia la orientación a vertical y finalmente invierte el sentido de este... ese es el orden de recorrido.

- Nota que caben posibilidades de que haya 2 o más naves que estén contiguas (paralelas), por lo que puede darse falso positivo (tocar dos naves distintas y no dos casillas de la misma nave (incluso 3, 4 naves...), con dos disparos seguidos). He dejado un comentario de //NOTA1 y //NOTA2, donde esto sucede... y aún puede darse un caso de NOTA3...

// NOTA2: al caso dDisparo.Tocado contiene el valor de cuantas naves han sido tocadas y la orientación actual descubre que están en una orientación atravesada.
//NOTA1: También cabe la posibilidad de una nave pongamos vertical (que tocamos con 'dDisparo.Tocado = 1, el centro del 'Cerco'), incluso alguna más también en vertical y luego otra en horizontal a continuación de áquella última tocada, luego llegamos a 4 tocados, pero no hay ninguna nave hundida (hubiéramos recibido un valor de resultado = 3)... luego hay que continuar la dirección más allá de las medidas de 'Cerco', para hundirlo y tras hundirlo regresar sus casillas hacia atrás, y continuar en otra dirección...
Ambos casos, los dejo en el limbo, a tu esfuerzo... es más de lo mismo, puede resolverse descontando los Tocados de la nave hundida y cambiar en la otra dirección vertical desde la posición previa (descontada en dicho eje de la nave hundida) //NOTA3: Todavía existe el caso de que dos naves estén colocadas 'a testa' una de otra, también dejo a tu esfuerzo incluso donde va este caso dentro del árbol de decisiones....

Si quieres evitar esos casos molestos, basta que añadas una regla al juego: dos naves no pueden posicionarse  tocándose, siempre debe haber al menos un casilla de espacio entre ellas (con la excusa de que eso impediría el movimiento en un caso real  :laugh: :laugh: :silbar: ). Esto elimina todas esa complicaciones...

Cerco:

.|0 1 2 3 4 5 6
A|X X X 4 X X X
B|X X X 4 X X X
C|X X X 4 X X X
D|2 2 2 @ 1 1 1
E|X X X 3 X X X
F|X X X 3 X X X
G|X X X 3 X X X


Nota que habiendo tocado una casilla y considerando a dicha casilla como el centro de un cerco, basta tocar otras 3, para hundir el barco más grande que haya (supuesto el caso de naves de tamaño 4  como máximo), sin embargo, el cerco aunque sea imaginario de 4 casillas en una dimensión en realidad queda determinado por el valor de Tocado... es decir mientras se toque en una dirección se avanza en ella, así podría a llegar a 5, 6, 10... con lo que en realidad no importa el tamaño de las naves. Pero es preciso, hacer siempre una idealización razonada y cuando procede un dibujo...
Para hacer el pseudocódigo independiente del tamaño de las naves habría que tener una variable donde está línea, para el valor 4, con el valor propio de casillas que tenga la nave de mayor tamaño.
Si (dDisparo.Tocado < 4)

Como el pseudocódigo está escrito al aire (sobre la marcha), es posible que se haya escapado algún gazapo...

-----------------------
Todo lo previo, respecto de tu interés, y ahora respecto de otras observaciones que pareces obviar...
- El valor de: "casilla repetida = 0", es absurdo... es obligado llevar dos mapas (por flota), aunque en realidad el segundo mapa de una flota, es el primero de la otra, luego pueden compartirse parcialmente.
Me explico: ¿quién en su sano juicio tira dos 'bombas' al mismo sitio, si ya fue agua, o tocado???. Y se sabe que fue agua o tocado, porque... en efecto llevas un mapa de donde has tirado previamente, no llevar un mapa es carecer de memoria, luego una IA, no tiene sentido donde no hay ni persistencia de la memoria. La IA es precisamente operar en consideración  de la memoria, de la 'experiencia', mediante la retroalimentación... si no hay memoria, no hay nada que retroalimentar... por eso mantener una estructura de lo que ha sucedido previamente es la IA mínima que se puede prestar...

Así los dos mapas son: Donde yo tiro a la flota enemiga y 2º, donde el enemigo me tira a mi.  es inverso para el caso del enemigo, excepto en que en el mapa propio, están todas las naves y 'el enemigo solo ve', lo que ha tocado o hundido, es decir... si tenemos un valor de nave intacta en el mapa, para el enemigo, se le reprseenta como agua... y si no, toca mantener dos mapas independientes por cada flota.

Es abusrdo hablar de IA, si luego uno va a volver a tirar a casillas a las que ya ha tirado. Hay un caso donde esto es posible, y es en una versión más compleja del juego, donde en cada turno, el que tira mueve una de sus naves (la que quiera) una casilla en uno de los dos sentidos de la orientación que tiene, por lo que en efecto, podrá dispararse a casillas ya disparadas, porque las flotas 'se mueven', pero es una regla que hace el juego mucho más complejo...
---------------

Siento todo el párrafo del mensaje anterior, que al parecer te resulta innecesario,  porque al hacer preguntas, los usuarios a menudo os dejais en el tintero detalles importantes... como "ya tengo listo todo lo demás, me falta solo esto". ...pero bueno, a alguien podrá servirle.


Primero decir que muchas gracias por toda su colaboración. Hoy mismo me pongo con la implementación de dicha logica. He hechado partidas sobre papel, y a la hora de decidir el siguiente disparo he anotado paso a paso que es lo que hacía, como recorria las direcciones, como cambio el sentido, etc. Ahora sabiendo más o menos que tipo de estructura de datos seguir para guardar dicha información, me pongo a la implementación del codigo.

También aclarar, que el tema de los barcos contiguos no se observa en mi proyecto. A la hora de colcoarlos si o si debe de haber una casilla agua de separación.

El tema del valor 0 casilla repetida o -1 error. Cuando dispara la máquina no se contemplan, por lo que nunca se darán esas dos condiciones. Pero como uso el mismo procedimiento para los disparos del jugador, puede ser que hagas un "missclick" y teclees una coordenada ya repetida, desgraciadamente obligandote a perder turno y a sumar un intentos++; a los disparos necesarios para ganar.

Cada jugador (usuario y maquina) tiene sus dos mapas, mapas de barcos (suyos) y tablero de disparos del enemigo.

Yo disparo en el tablero de disparos y actualizo el resultado en el tablero de barcos del enemigo.

El procedimiento que yo tengo realizado para decidir el disparo es el siguiente:

Con ésto lo que consigo es, si es tocado disparo a las casillas vecinas. El problema se encuentra cuando en la casilla adyacente no hay un barco, cuando vuelve a entrar en el procedimiento, si no ha sido tocado, me dispara al azar nuevamente, en lugar de recorrer en busca de su orientación. Ahi es donde creo que deberia añadir si ha sido primero tocado ir en busca de una dirección por cada disparo realizado. Cómo al hundir un barco se rodea todo de Agua tocada "." de ésta forma puedo saber cuando un barco ha sido hundido y en el siguiente disparo, realizarlo aleatoriamente.

EDIT: Añadir también que, hay que tener en cuenta si el barco se encuentra en los laterales del talbero (columna 1 o 9) y (fila A o G). Entonces, en mi procedimiento, en lugar de generar valores aleatorios en las direcciones arriba abajo izq derecha, dependiendo de la situacion en la que se encuentre, se exactamente dónde disparar.

Serapis

#6
No dupliques todo el contenido de un mensaje si no vas a citar nada, si has de referirte a una persona simplemente copia su alias (y lo pones en negrita para llamar con claridad su atención) y responde simplemente... Imagina que ahora yo cito todo tu mensaje, y luego tu otra vez el mío... al anidarse los mensajes sería engorroso y largo (mucho scroll) para llegar al texto que uno escribe.




Ayer, olvide´al final, poner como se llemaba , aunque es claro... aprovecho, para resumir la lógica...
La lógica de un modo escuesta (más claro ahora que se sabe que aplicas la regla de espacio entre barcos):

Resultado solo debeiera tener dos valores: Agua o tocado, luego mejor un buleano para esto. Si ya fue tocado con anteroridad en sucesivas veces esa casilla debe devolver agua.
Como usamos una variable llamada 'tocado' al parámetro lo llamamos agua y entra con TRUe cuando se el resultado es 'agua' y con FALSE, cuando el resutado fue 'tocar la casilla a la que se disparó'.


buleano = Funcion SiguienteDisparo(buleano Agua, byte x, byte y)  
   Si (agua = FALSE)
       Si (Tocado > 0)             
           Aumentar tocado              // Otra casilla tocada de la nave (o de una nave contigua)          
           Avanzar la cordenada en la que se está  (según cada caso).          
           Si (ConsultarBarcoHundido(Tocado) = TRUE)
                Tocado = 0
                 Devolver FALSE
            Fin si
       Sino                                    // aquí hay un buque (lo acabamos de descubrir)... insistiremos hasta hundirlo.            
           Tocado = 1            
           Orientacion = 1            
           Avanzar cordenada x              
       Fin si      
       Devolver TRUE

   Sino  // agua = TRUE
       Si (Tocado > 0)              // un disparo previo acertó?,                                    
           Orientacion += 1      // cambiamos de orientación a la siguiente
           Cambiar las cordenadas a la casilla adhiacente a la 'arroba' en la orientación actual (que acabamos de saltar).        
           Devolver TRUE          
       Sino        
           Devolver FALSE
       Fin si
   Fin si
Fin funcion


Y la función sería invocada así:


   buleano agua, seguirPista

   Si (seguirPista = FALSE)
       x = Random(entre 0 y maxColumna)
       y = Random(entre 0 y maxFila)
   //Sino
       // 'x' e 'y', fueron actualizadas en la función SiguienteDisparo, se usan esos valores actualizados.
   Fin si
   agua = Disparar(x, y)
   seguirPista = SiguienteDisparo(agua, x, y)

   
...y para ser más precisos, elegir 'x' e 'y' de forma aleatoria, no debe ser del todo exacto, puede resultar muy costoso, ya que a medida que se juega, ya hay casillas que fueron elegidas, por tanto no tiene sentido volverlas a elegir... cuantas menos queden, más costará tomar al azar una casilla libre... no es nada óptimo.

Aquí la solución, si no la entiendes pregunta...:



1º Mantener datos de las casillas. Se crea un array con el número de casillas totales. y una variable indica cuanto son estas.
 
Al comenzar la partida, se meten todas las casillas en un array, que luego es barajado.

   ArrayEntero Casillas()
   entero maxCasillas, maxFilas, maxColumnas

   Funcion NuevaPartida(filas, columnas)
       entero k
   
       maxFilas =filas
       maxColumnas = columnas
       maxCasillas = ((filas * columnas) -1)  // -1 aquí, para evitar en todas partes poner -1...

       Alojar espacio para Casillas(0 a maxCasillas)
       Bucle para k desde 0 a maxCasillas
           Casillas(k) = k
       Siguiente
       
       BarajarCasillas        

       ... otras cosas en esta función necesarias antes de empezar la partida (como posicionar las flotas)
   fin funcion


2º La función BarajarCasillas, reordena de forma aleatoria el contenido en el array.
Funcion BarajarCasillas
   entero k, j, i

   Bucle para k desde maxCasillas hasta 1 hacia atrás
       j = Random(entre 0 y k)
       i = Casillas(j)
       Casillas(j) = Casillas(k)
       Casillas(k) = i
   Siguiente
Fin funcion



3º Elegir una casilla al azar entre las disponibles, luego la última disponible ocupa la posición de la tomada, y se descuenta 1 de las disponibles. Si no hay casillas disponibles daría error, pero eso implica que quedan barcos sin hundir pero se ha disparado a todas las casillas, luego hay un error de lógica en el juego (por ello no se contempla en lafunción que no haya casillas disponbles, el juego debe acabar antes que eso ocurra).


   entero = Funcion CasillaRandom
       entero z
       z = Random(entre 0 y maxCasillas)   // Elegir una casilla al azar entre las disponibles.

       Casillas(z) = Casillas(maxCasillas-1)  // movemos la última disponible al hueco de la casilla a devolver.
       maxCasillas -= 1                             // una casilla libre menos.

       Devolver z                                     // entregamos la casilla elegida (una libre)
   fin funcion


4º Derivar la casilla a las cordenadas que toque:
Básicamente es convertir un índice de un array unidimensional en los índices de un array bidimensional.
Funcion GetCordenadasFromCasilla (casilla, x, y)  // 'x' e 'y' son de salida...
   x = (casilla modulo maxFilas)  
   y = (casilla \ maxFilas)   // el '/' es una división entera, o bien una división (flotante) a la que se le retiran los decimales (convenrtido en entero)
Fin Funcion




Y por fin modificamos el fragmento de pseudocódigo de más arriba para elegir al azar casillas, pero solo entre las que aún están libres.

   buleano agua, seguirPista
   entero casilla

   Si (seguirPista = FALSE)
       casilla =  CasillaRandom
       GetCordenadasFromCasilla(casilla, x, y)
   Fin si

   agua = Disparar(x, y)
   seguirPista = SiguienteDisparo(agua, x, y)