En este post de programación en C te mostraré el código fuente para el juego conocido como tres en línea, tres en raya, tic tac toe, gatitos, etcétera.
He programado el juego en C estándar así que se puede compilar en varios sistemas operativos, y también se puede compilar con un compilador de C++.
El juego de tres en línea que he desarrollado en C soporta el modo jugador contra jugador, jugador contra CPU (con una pequeña inteligencia artificial) y CPU contra CPU. A lo largo del post te explicaré el código y cómo es que funciona cada parte del programa.
Por cierto, en este caso el juego se desarrolla en la consola pero se le puede agregar una interfaz usando alguna librería como Allegro.
Todo el juego se desarrolla en una matriz de tipo char en donde estará un espacio vacío, la X
o la O
. En cada turno, se eligen unas coordenadas (ya sea que se le soliciten al usuario o el programa las elija) y se coloca la pieza.
Inmediatamente después de colocar la pieza se evalúa si hay un ganador. Se dice que hay un ganador cuando se pueden conectar 3 piezas en una línea recta sin importar la dirección de ésta, así que es necesario hacer un conteo en todas las direcciones para saber si alguien gana en este juego de tic tac toe en C.
Si no hay un ganador, se comprueba si hay un empate. Para saber si hay empate se recorre todo el tablero verificando que no haya ningún espacio vacío.
Finalmente si no hay ganador ni un empate, se le pasa el turno al oponente del jugador actual. Así hasta que alguna condición que termina el juego se cumple.
Lo que expliqué arriba se traduce en el siguiente código de C:
// Loop principal del juego
void iniciarJuego(int modo)
{
if (modo != JUGADOR_JUGADOR && modo != JUGADOR_CPU && modo != CPU_CPU)
{
printf("Modo de juego no permitido");
return;
}
// Para que salgan cosas aleatorias
srand(getpid());
// Iniciar tablero de juego
char tablero[FILAS][COLUMNAS];
// Y limpiarlo
limpiarTablero(tablero);
// Elegir jugador que inicia al azar
char jugadorActual = jugadorAleatorio();
printf("El jugador que inicia es: %c\n", jugadorActual);
int x, y;
// Y allá vamos
while (1)
{
imprimirTablero(tablero);
if (modo == JUGADOR_JUGADOR || (modo == JUGADOR_CPU && jugadorActual == JUGADOR_X))
{
printf("Jugador %c. Ingresa coordenadas (x,y) para colocar la pieza separadas por una coma. Por ejemplo: 5,5\n", jugadorActual);
scanf("%d,%d", &x, &y);
// Al usuario se le solicitan números comenzando a contar en 1, pero en los arreglos comenzamos desde el 0
// así que necesitamos restar uno en ambas variables
x--;
y--;
}
else if (modo == CPU_CPU || (modo == JUGADOR_CPU && jugadorActual == JUGADOR_CPU_O))
{
// Si es modo CPU contra CPU o es el turno del CPU, dejamos que las coordenadas las elija
// el programa
elegirCoordenadasCpu(jugadorActual, tablero, &y, &x);
}
// Sin importar cuál modo haya sido, colocamos la pieza según las coordenadas elegidas
colocarPieza(y, x, jugadorActual, tablero);
// Puede que después de colocar la pieza el jugador gane o haya un empate, así que comprobamos
if (comprobarSiGana(jugadorActual, tablero))
{
imprimirTablero(tablero);
printf("El jugador %c gana\n", jugadorActual);
return;
}
else if (empate(tablero))
{
imprimirTablero(tablero);
printf("Empate");
return;
}
// Si no, es turno del otro jugador
jugadorActual = oponenteDe(jugadorActual);
}
}
El código es muy simple y los comentarios proporcionan detalles extra de lo que se está haciendo (por si los nombres de las funciones no son obvios).
Como mencioné al inicio, este juego tiene tres modos. En el primer caso se puede jugar un humano contra un humano. En cada turno se solicitan las coordenadas (números separados por coma) en donde se coloca la pieza que puede ser O
o X
.
Para el modo de CPU contra humano, el CPU tiene que pensar en su mejor movimiento; justo ahí es en donde se hace una pequeña simulación del lugar donde le conviene colocar la pieza.
Finalmente en el modo CPU contra CPU se enfrenta a la computadora contra sí misma.
Para que el CPU elija en dónde colocar las piezas debe hacer algunas simulaciones de lo que pasa si coloca la pieza en todas las opciones posibles. El orden que sigue es:
En el peor de los casos elegirá alguna posición aleatoria, pero también es capaz de atacar y defenderse.
Por cierto, esto está altamente inspirado en el juego de Conecta 4. El código para este caso es el siguiente:
// Hace que el CPU elija unas coordenadas para ganar
void elegirCoordenadasCpu(char jugador, char tablero[FILAS][COLUMNAS], int *yDestino, int *xDestino)
{
hablar("Estoy pensando...", jugador);
/*
El orden en el que el CPU infiere las coordenadas que toma es:
1. Ganar si se puede
2. Hacer perder al oponente si está a punto de ganar
3. Tomar el mejor movimiento del oponente (en donde obtiene el mayor puntaje)
4. Tomar mi mejor movimiento (en donde obtengo mayor puntaje)
5. Elegir la de la esquina superior izquierda (0,0)
6. Coordenadas aleatorias
*/ int y, x, conteoJugador, conteoOponente;
char oponente = oponenteDe(jugador);
// 1
coordenadasParaGanar(jugador, tablero, &y, &x);
if (y != -1 && x != -1)
{
hablar("Ganar", jugador);
*yDestino = y;
*xDestino = x;
return;
}
// 2
coordenadasParaGanar(oponente, tablero, &y, &x);
if (y != -1 && x != -1)
{
hablar("Tomar victoria de oponente", jugador);
*yDestino = y;
*xDestino = x;
return;
}
// 3
coordenadasParaMayorPuntaje(jugador, tablero, &y, &x, &conteoJugador);
coordenadasParaMayorPuntaje(oponente, tablero, &y, &x, &conteoOponente);
if (conteoOponente > conteoJugador)
{
hablar("Tomar puntaje mayor del oponente", jugador);
*yDestino = y;
*xDestino = x;
return;
}
else
{
hablar("Tomar mi mayor puntaje", jugador);
*yDestino = y;
*xDestino = x;
return;
}
// 4
if (coordenadasVacias(0, 0, tablero))
{
hablar("Tomar columna superior izquierda", jugador);
*yDestino = 0;
*xDestino = 0;
return;
}
// 5
hablar("Coordenadas aleatorias", jugador);
obtenerCoordenadasAleatorias(jugador, tablero, yDestino, xDestino);
}
Es importante que mencione que se reciben dos apuntadores a dos enteros (variables por referencia) porque en C no podemos devolver dos valores en una función, así que mejor se reciben los apuntadores.
Dentro del código también vemos algunas funciones ayudantes, por ejemplo la función que devuelve el oponente de un jugador.
Para saber si un jugador gana se verifica si se forma una línea recta y si el conteo es de 3 (por eso es que se llama tres en línea). Las funciones que cuentan y verifican son:
/*
Funciones de conteo. Simplemente cuentan cuántas piezas del mismo jugador están
alineadas
*/
int contarHaciaArriba(int x, int y, char jugador, char tablero[FILAS][COLUMNAS])
{
int yInicio = (y - CONTEO_PARA_GANAR >= 0) ? y - CONTEO_PARA_GANAR + 1 : 0;
int contador = 0;
for (; yInicio <= y; yInicio++)
{
if (tablero[yInicio][x] == jugador)
{
contador++;
}
else
{
contador = 0;
}
}
return contador;
}
int contarHaciaDerecha(int x, int y, char jugador, char tablero[FILAS][COLUMNAS])
{
int xFin = (x + CONTEO_PARA_GANAR < COLUMNAS) ? x + CONTEO_PARA_GANAR - 1 : COLUMNAS - 1;
int contador = 0;
for (; x <= xFin; x++)
{
if (tablero[y][x] == jugador)
{
contador++;
}
else
{
contador = 0;
}
}
return contador;
}
int contarHaciaArribaDerecha(int x, int y, char jugador, char tablero[FILAS][COLUMNAS])
{
int xFin = (x + CONTEO_PARA_GANAR < COLUMNAS) ? x + CONTEO_PARA_GANAR - 1 : COLUMNAS - 1;
int yInicio = (y - CONTEO_PARA_GANAR >= 0) ? y - CONTEO_PARA_GANAR + 1 : 0;
int contador = 0;
while (x <= xFin && yInicio <= y)
{
if (tablero[y][x] == jugador)
{
contador++;
}
else
{
contador = 0;
}
x++;
y--;
}
return contador;
}
int contarHaciaAbajoDerecha(int x, int y, char jugador, char tablero[FILAS][COLUMNAS])
{
int xFin = (x + CONTEO_PARA_GANAR < COLUMNAS) ? x + CONTEO_PARA_GANAR - 1 : COLUMNAS - 1;
int yFin = (y + CONTEO_PARA_GANAR < FILAS) ? y + CONTEO_PARA_GANAR - 1 : FILAS - 1;
int contador = 0;
while (x <= xFin && y <= yFin)
{
if (tablero[y][x] == jugador)
{
contador++;
}
else
{
contador = 0;
}
x++;
y++;
}
return contador;
}
// Indica si el jugador gana
int comprobarSiGana(char jugador, char tablero[FILAS][COLUMNAS])
{
int y;
for (y = 0; y < FILAS; y++)
{
int x;
for (x = 0; x < COLUMNAS; x++)
{
if (
contarHaciaArriba(x, y, jugador, tablero) >= CONTEO_PARA_GANAR ||
contarHaciaDerecha(x, y, jugador, tablero) >= CONTEO_PARA_GANAR ||
contarHaciaArribaDerecha(x, y, jugador, tablero) >= CONTEO_PARA_GANAR ||
contarHaciaAbajoDerecha(x, y, jugador, tablero) >= CONTEO_PARA_GANAR)
{
return 1;
}
}
}
// Terminamos de recorrer y no conectó
return 0;
}
Es un algoritmo simple pero tedioso, pues se tiene que recorrer el tablero desde una posición hacia todas las direcciones posibles. En caso de que se conecte hacia arriba, la derecha, arriba derecha o abajo derecha, se devuelve un 1 indicando que el jugador gana.
Si el jugador no gana, se devuelve un 0. Y así es como se define si un jugador obtiene la victoria en este juego de gatitos en C. Ya para el caso de la verificación del empate el código queda así:
// Debería llamarse después de verificar si alguien gana
// Indica si hay un empate
int empate(char tableroOriginal[FILAS][COLUMNAS])
{
int y;
for (y = 0; y < FILAS; y++)
{
int x;
for (x = 0; x < COLUMNAS; x++)
{
// Si hay al menos un espacio vacío se dice que no hay empate
if (tableroOriginal[y][x] == ESPACIO_VACIO)
{
return 0;
}
}
}
return 1;
}
Hasta este punto te he explicado el funcionamiento general del juego. Obviamente te dejo el código fuente completo en mi GitHub, para que puedas analizarlo, mejorarlo, ejecutarlo, etcétera.
El código completo es un poco más extenso pero igualmente se explica por sí mismo. Y así es como se puede tener el juego de Tic Tac Toe completamente en C.
Recuerda que este código se puede compilar con gcc
, clang
y g++
. También lo puedes importar en Dev C++, CodeBlocks, CLion, etcétera.
Te dejo con más tutoriales de C en mi blog.
Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…
En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…
En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…
Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…
En este artículo te voy a enseñar cómo usar un "top level await" esperando a…
Ayer estaba editando unos archivos que son servidos con el servidor Apache y al visitarlos…
Esta web usa cookies.