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.
El día de hoy te mostraré cómo crear un servidor HTTP (servidor web) en Android…
En este post te voy a enseñar a designar una carpeta para imprimir todos los…
En este artículo te voy a enseñar la guía para imprimir en una impresora térmica…
Hoy te voy a mostrar un ejemplo de programación para agregar un módulo de tasa…
Los usuarios del plugin para impresoras térmicas pueden contratar licencias, y en ocasiones me han…
Hoy voy a enseñarte cómo imprimir el € en una impresora térmica. Vamos a ver…
Esta web usa cookies.