En este proyecto vamos a crear un juego de Batalla Naval en PSeInt para un jugador contra la computadora. Es un proyecto ideal para practicar matrices, números aleatorios, Funciones, SubProcesos, validaciones y manejo de estados.
La idea es construir un juego completo, pero manteniendo el código suficientemente claro como para poder entender qué hace cada parte.
¿Cómo se juega a Batalla Naval?
Cada jugador tiene un tablero oculto donde coloca sus barcos. Durante la partida, ambos se turnan para disparar indicando una fila y una columna.
- Si el disparo cae sobre agua, se marca como agua.
- Si el disparo alcanza una parte de un barco, se marca como impacto.
- Cuando todas las posiciones de un barco fueron alcanzadas, el barco queda hundido.
- Gana quien hunda todos los barcos del rival.
La versión que vamos a programar
Para que la partida sea ágil vamos a utilizar un tablero de 8 x 8.
Cada jugador tendrá:
- 1 barco de 3 casilleros;
- 2 barcos de 2 casilleros;
- 3 barcos de 1 casillero.
En total, cada jugador tendrá 6 barcos y 10 casilleros ocupados.
Los barcos se colocarán automáticamente en posiciones aleatorias y podrán estar en forma horizontal o vertical.
¿Cómo representaremos el tablero?
Usaremos una matriz de 8 filas por 8 columnas. Cada celda tendrá un número interno:
0 = agua sin disparar 1 a 6 = barco identificado por su número 10 = agua ya disparada 11 a 16 = parte impactada de los barcos 1 a 6
Este sistema tiene una ventaja importante: cuando una parte de un barco es alcanzada todavía podemos saber a qué barco pertenecía. Eso nos permite detectar cuándo quedó completamente hundido.
Mezcla y posiciones aleatorias
Para colocar los barcos usaremos Aleatorio(). El programa elegirá:
- una fila inicial;
- una columna inicial;
- una orientación horizontal o vertical.
Antes de colocar el barco comprobaremos que entre completamente dentro del tablero y que no se superponga con otro.
Código completo
Funcion puede <- PuedeColocar(tablero, fila, columna, tamano, direccion) Definir i Como Entero; puede <- Verdadero; Si direccion = 1 Entonces Si columna + tamano - 1 > 8 Entonces puede <- Falso; SiNo Para i <- 0 Hasta tamano - 1 Hacer Si tablero[fila,columna + i] <> 0 Entonces puede <- Falso; FinSi FinPara FinSi SiNo Si fila + tamano - 1 > 8 Entonces puede <- Falso; SiNo Para i <- 0 Hasta tamano - 1 Hacer Si tablero[fila + i,columna] <> 0 Entonces puede <- Falso; FinSi FinPara FinSi FinSi FinFuncion Funcion hundido <- EstaHundido(tablero, idBarco) Definir fila, columna Como Entero; hundido <- Verdadero; Para fila <- 1 Hasta 8 Hacer Para columna <- 1 Hasta 8 Hacer Si tablero[fila,columna] = idBarco Entonces hundido <- Falso; FinSi FinPara FinPara FinFuncion Funcion quedan <- QuedanBarcos(tablero) Definir fila, columna Como Entero; quedan <- Falso; Para fila <- 1 Hasta 8 Hacer Para columna <- 1 Hasta 8 Hacer Si tablero[fila,columna] >= 1 Y tablero[fila,columna] <= 6 Entonces quedan <- Verdadero; FinSi FinPara FinPara FinFuncion SubProceso InicializarTablero(tablero Por Referencia) Definir fila, columna Como Entero; Para fila <- 1 Hasta 8 Hacer Para columna <- 1 Hasta 8 Hacer tablero[fila,columna] <- 0; FinPara FinPara FinSubProceso SubProceso ColocarUnBarco(tablero Por Referencia, idBarco, tamano) Definir fila, columna, direccion, i Como Entero; Definir colocado Como Logico; colocado <- Falso; Mientras NO colocado Hacer fila <- Aleatorio(1,8); columna <- Aleatorio(1,8); direccion <- Aleatorio(1,2); Si PuedeColocar(tablero, fila, columna, tamano, direccion) Entonces Si direccion = 1 Entonces Para i <- 0 Hasta tamano - 1 Hacer tablero[fila,columna + i] <- idBarco; FinPara SiNo Para i <- 0 Hasta tamano - 1 Hacer tablero[fila + i,columna] <- idBarco; FinPara FinSi colocado <- Verdadero; FinSi FinMientras FinSubProceso SubProceso ColocarFlota(tablero Por Referencia) ColocarUnBarco(tablero, 1, 3); ColocarUnBarco(tablero, 2, 2); ColocarUnBarco(tablero, 3, 2); ColocarUnBarco(tablero, 4, 1); ColocarUnBarco(tablero, 5, 1); ColocarUnBarco(tablero, 6, 1); FinSubProceso SubProceso MostrarTablero(tablero, ocultarBarcos) Definir fila, columna, valor Como Entero; Escribir " 1 2 3 4 5 6 7 8"; Para fila <- 1 Hasta 8 Hacer Escribir Sin Saltar fila, " | "; Para columna <- 1 Hasta 8 Hacer valor <- tablero[fila,columna]; Si valor = 0 Entonces Escribir Sin Saltar "~ "; SiNo Si valor >= 1 Y valor <= 6 Entonces Si ocultarBarcos Entonces Escribir Sin Saltar "~ "; SiNo Escribir Sin Saltar "B "; FinSi SiNo Si valor = 10 Entonces Escribir Sin Saltar "O "; SiNo Escribir Sin Saltar "X "; FinSi FinSi FinSi FinPara Escribir ""; FinPara FinSubProceso SubProceso ProcesarDisparo(tablero Por Referencia, fila, columna, impacto Por Referencia, barcoHundido Por Referencia) Definir idBarco Como Entero; impacto <- Falso; barcoHundido <- Falso; Si tablero[fila,columna] = 0 Entonces tablero[fila,columna] <- 10; SiNo idBarco <- tablero[fila,columna]; Si idBarco >= 1 Y idBarco <= 6 Entonces impacto <- Verdadero; tablero[fila,columna] <- idBarco + 10; Si EstaHundido(tablero, idBarco) Entonces barcoHundido <- Verdadero; FinSi FinSi FinSi FinSubProceso SubProceso TurnoJugador(tableroCPU Por Referencia) Definir fila, columna Como Entero; Definir valido, impacto, barcoHundido Como Logico; valido <- Falso; Mientras NO valido Hacer Escribir "Fila (1 a 8):"; Leer fila; Escribir "Columna (1 a 8):"; Leer columna; Si fila >= 1 Y fila <= 8 Y columna >= 1 Y columna <= 8 Entonces Si tableroCPU[fila,columna] < 10 Entonces valido <- Verdadero; SiNo Escribir "Ya disparaste en esa posición."; FinSi SiNo Escribir "Coordenadas inválidas."; FinSi FinMientras ProcesarDisparo(tableroCPU, fila, columna, impacto, barcoHundido); Si impacto Entonces Escribir "¡IMPACTO!"; Si barcoHundido Entonces Escribir "¡HUNDISTE UN BARCO!"; FinSi SiNo Escribir "Agua."; FinSi FinSubProceso SubProceso TurnoCPU(tableroJugador Por Referencia) Definir fila, columna Como Entero; Definir valido, impacto, barcoHundido Como Logico; valido <- Falso; Mientras NO valido Hacer fila <- Aleatorio(1,8); columna <- Aleatorio(1,8); Si tableroJugador[fila,columna] < 10 Entonces valido <- Verdadero; FinSi FinMientras Escribir "La computadora dispara a fila ", fila, ", columna ", columna; ProcesarDisparo(tableroJugador, fila, columna, impacto, barcoHundido); Si impacto Entonces Escribir "La computadora impactó uno de tus barcos."; Si barcoHundido Entonces Escribir "La computadora hundió uno de tus barcos."; FinSi SiNo Escribir "La computadora disparó al agua."; FinSi FinSubProceso Proceso Batalla_Naval Definir tableroJugador, tableroCPU Como Entero; Definir jugando Como Logico; Dimension tableroJugador[8,8]; Dimension tableroCPU[8,8]; InicializarTablero(tableroJugador); InicializarTablero(tableroCPU); ColocarFlota(tableroJugador); ColocarFlota(tableroCPU); jugando <- Verdadero; Mientras jugando Hacer Limpiar Pantalla; Escribir "===================================="; Escribir " BATALLA NAVAL"; Escribir "===================================="; Escribir ""; Escribir "TU TABLERO"; MostrarTablero(tableroJugador, Falso); Escribir ""; Escribir "TABLERO ENEMIGO"; MostrarTablero(tableroCPU, Verdadero); Escribir ""; TurnoJugador(tableroCPU); Si NO QuedanBarcos(tableroCPU) Entonces Limpiar Pantalla; Escribir "===================================="; Escribir " ¡VICTORIA!"; Escribir "===================================="; Escribir "Hundiste toda la flota enemiga."; jugando <- Falso; SiNo Esperar Tecla; Escribir ""; TurnoCPU(tableroJugador); Si NO QuedanBarcos(tableroJugador) Entonces Limpiar Pantalla; Escribir "===================================="; Escribir " DERROTA"; Escribir "===================================="; Escribir "La computadora hundió toda tu flota."; jugando <- Falso; SiNo Esperar Tecla; FinSi FinSi FinMientras FinProceso
¿Cómo funciona la colocación de los barcos?
ColocarUnBarco() genera una posición y una orientación aleatorias. Después consulta a PuedeColocar().
La función comprueba dos cosas:
- que el barco no se salga del tablero;
- que todas las posiciones estén libres.
Si alguna condición falla, se generan nuevas coordenadas hasta encontrar una posición válida.
¿Cómo evitamos disparos repetidos?
Una posición que todavía no recibió disparos contiene agua (0) o el identificador de un barco (1 a 6).
Después de un disparo pasa a tener un valor igual o superior a 10. Por eso podemos verificar fácilmente si esa coordenada ya fue utilizada:
Si tablero[fila,columna] < 10 Entonces // La posición todavía no fue disparada FinSi
¿Cómo sabemos si un barco fue hundido?
Cada barco posee un identificador entre 1 y 6. Cuando una parte es impactada, sumamos 10 a ese identificador.
Por ejemplo, si una celda tenía el valor 3, después del impacto pasa a valer 13.
EstaHundido() recorre toda la matriz buscando posiciones que todavía tengan el identificador original del barco. Si no encuentra ninguna, significa que todas sus partes fueron alcanzadas.
¿Cómo mostramos el tablero enemigo sin revelar sus barcos?
MostrarTablero() recibe el parámetro ocultarBarcos.
Cuando mostramos nuestro propio tablero, las posiciones con barcos aparecen con B. En el tablero de la computadora esas mismas posiciones se muestran como agua hasta que reciben un disparo.
Los símbolos utilizados son:
~ = posición desconocida o agua B = uno de nuestros barcos O = disparo al agua X = impacto
La computadora
En esta versión la CPU dispara a una fila y columna aleatorias. Antes de hacerlo verifica que no haya disparado anteriormente a esa misma posición.
Esto hace que el rival sea funcional, aunque todavía no sea demasiado inteligente.
Ideas para mejorar la inteligencia de la CPU
Una mejora interesante sería hacer que, después de conseguir un impacto, la computadora pruebe primero las posiciones vecinas:
- arriba;
- abajo;
- izquierda;
- derecha.
De esta forma la CPU comenzaría a comportarse de manera parecida a un jugador real.
Otros desafíos para ampliar el juego
- permitir al jugador colocar manualmente sus barcos;
- usar un tablero de 10 x 10;
- agregar barcos más grandes;
- mostrar cuántos barcos quedan vivos;
- llevar un contador de disparos;
- calcular precisión: impactos / disparos;
- hacer que quien acierta vuelva a disparar;
- crear distintos niveles de dificultad;
- crear una modalidad para dos jugadores humanos;
- hacer una CPU que persiga un barco después del primer impacto.
Desafío
Modificá la computadora para que, cuando consiga un impacto, intente disparar alrededor de esa posición antes de volver a elegir coordenadas completamente al azar.
Después agregá un contador que indique cuántos disparos necesitó cada jugador para terminar la partida.
Repasos recomendados
Este proyecto utiliza matrices y SubProcesos de forma intensiva. Si necesitás repasar esos temas:
20 ejercicios resueltos de PSeInt con matrices →
Funciones y SubProcesos en PSeInt →
¿Te sirvió este proyecto? ☕
Si este contenido te ayudó y querés apoyar a Club Programador para seguir publicando ejercicios, juegos y guías gratuitas, podés colaborar mediante:
Seguí aprendiendo PSeInt
Encontrá todos los ejercicios, guías y proyectos en nuestra página principal de PSeInt:
Aprender PSeInt: guía completa y ejercicios resueltos →
Descubre más desde Club Programador
Suscríbete y recibe las últimas entradas en tu correo electrónico.