Conecta 4 en C++ - Jugar partida contra CPU (inteligencia artificial)

Conecta 4 en C++ con IA, ranking y estadísticas de jugadores

En este post te mostraré el juego de Conecta 4 en C++ con opción para jugar contra el CPU con un algoritmo y una pequeña inteligencia artificial.

Además del juego de Conecta 4 en CPP llevaremos la gestión de usuarios con estadísticas, un ranking de mejores jugadores, opción para cambiar el tamaño del tablero y una manera de repetir la última partida.

Por cierto, he mejorado un poco el algoritmo del CPU, de modo que es más complejo ganarle una partida de Conecta 4. Pero bueno, vayamos a la explicación y el código fuente.

Descripción del problema

Se trata del conocido juego ‘Conecta 4’. Las partidas se jugarán sobre un tablero de a lo sumo 10×10 casillas, en el que tendremos que ir situando piezas alternativamente con el ordenador hasta conectar cuatro de ellas en horizontal, en vertical o en diagonal.

Ganará quien primer consiga el objetivo (ordenador o jugador), y se considerará un empate cuando se llene el tablero sin haberlo conseguido ninguno de los dos.

El funcionamiento del juego se apoyará en un fichero ranking.txt para almacenar las puntuaciones de los 10 mejores jugadores, y en tres ficheros para cada jugador, cuyo nombre será el nick del jugador, una barra baja y las siguientes terminaciones:

  • configuracion.txt, que almacenará el tamaño del tablero incluyendo el número de casillas (filas y columnas del tablero inicialmente 10 x 10).
  • resultados.txt, que contendrá los resultados conseguidos en las partidas.
  • ultimapartida.dat, que almacenará los datos necesarios para reproducir la última partida jugada.

Diseño del juego: menú

Menú para jugar Conecta 4 en C++
Menú para jugar Conecta 4 en C++

El juego comenzará solicitando al usuario su nick

Dame tu nick para comenzar el juego:

A continuación, mostrará por pantalla el siguiente menú:

  1. Visualizar configuración del tablero.
  2. Cambiar configuración del tablero.
  3. Ver estadísticas.
  4. Reproducir última partida.
  5. Mostrar top 10.
  6. Jugar partida.
  7. Salir

Dame opción:

Opciones del menú

La opción 1 (visualizar configuración) mostrará la información referente al tamaño del tablero, leyendo para ello el fichero correspondiente al jugador, de extensión txt y cuyo nombre será nombre la concatenación de su nick con la cadena “_configuración” con su nick.

Por ejemplo, para el nick Maria, el fichero será Maria_configuracion.txt

La opción 2 (cambiar configuración) permitirá modificar el contenido del fichero de configuración correspondiente al jugador, teniendo en cuenta el tamaño máximo del tablero y cambiando su tamaño para las partidas que se jueguen a partir de ese momento.

La opción 3 (ver estadísticas), se apoyará en la información contenida en el fichero de resultados asociado al nick del jugador, y mostrará el porcentaje de partidas ganadas, perdidas y empatadas; y el número promedio de movimientos del jugador por partida.

La opción 4 reproducirá los movimientos sobre el tablero de la última partida, de acuerdo a la información almacenada en el fichero de última partida asociado al jugador, que será de tipo binario.

Los movimientos se reproducirán uno a uno, pidiendo al usuario que pulse una tecla tras cada movimiento.

La opción 5 mostrará el ranking de los 10 mejores jugadores. A este fin, se calculará la puntuación de cada jugador como (número de partidas ganadas – números de partidas perdidas) / partidas totales.

En caso de empate entre jugadores, se considerará mejor jugador el que tenga el menor número de movimientos por partida.

Ejemplo de partida

La opción 6 iniciará el juego. A este fin, el ordenador mostrará un tablero como el que se muestra debajo (para un caso de ejemplo compuesto de 6 filas y 10 columnas), y solicitará al usuario el número de columna en el que quiere depositar su ficha.

| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
+-+-+-+-+-+-+-+-+-+-+
|0|1|2|3|4|5|6|7|8|9|

Seleccione la columna para su tirada:

La opción 7, obviamente, sale del juego.

El ordenador introducirá la ficha del jugador en la columna indicada (una X mayúscula) y a continuación introducirá la suya propia (una O mayúscula), diseñando para ello un algoritmo apropiado.

El diseño de este algoritmo influirá en la nota del proyecto (1 punto).

A continuación, se muestra un ejemplo de un posible resultado tras pedir depositar la ficha en la columna 3:

| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | | | | | | | | |
| | | |X| | | | | | |
+-+-+-+-+-+-+-+-+-+-+
|0|1|2|3|4|5|6|7|8|9|

Seleccione la columna para su tirada:

El programa deberá continuar solicitando al usuario su tirada hasta que el ordenador o el usuario consigan ganar la partida (o el tablero se llene).

En ese momento, se actualizará el fichero terminado en _resultados.txt con la nueva información, para permitir visualizar las estadísticas en la opción 3 en el futuro; y también el fichero terminado en _ultimapartida.dat, para permitir utilizar posteriormente la opción 4 si el usuario desea revisar sus movimientos.

Finalmente, se regresará al menú principal.

Declaración de constantes y encabezados

Comencemos con el código. En este caso primero veamos algunas constantes necesarias.

Por ejemplo, tenemos las fichas necesarias para ganar el juego, los caracteres que identifican al jugador, delimitadores, máximas columnas y filas, nombres de archivos, etcétera.

#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>
#include <stdlib.h> // rand y RAND_MAX
#include <unistd.h> // getpid
using namespace std;
const string ARCHIVO_RANKING = "ranking.txt",
			 RESULTADO_EMPATE = "e",
			 RESULTADO_GANA = "g",
			 RESULTADO_PIERDE = "p";
const int COLUMNAS_DEFECTO = 10,
		  FILAS_DEFECTO = 10,
		  MAXIMO_NUMERO_COLUMNAS = 10,
		  MAXIMO_NUMERO_FILAS = 10,
		  FICHAS_JUNTAS_NECESARIAS_PARA_GANAR = 4,
		  MAXIMO_JUGADORES_RANKING = 10;
const char ESPACIO_VACIO = ' ',
		   JUGADOR_HUMANO = 'X',
		   JUGADOR_CPU = 'O',
		   DELIMITADOR_RESULTADOS = ',',
		   DELIMITADOR_MOVIMIENTOS = ',',
		   DELIMITADOR_RANKING = ',';

Nota: si quisieras jugar a Conecta 5, Conecta 10 o cualquier otra variante solo debes cambiar la constante de FICHAS_JUNTAS_NECESARIAS_PARA_GANAR.

Estructuras necesarias

Necesitamos declarar algunos struct para este juego de Connect 4 en C++, por ejemplo la configuración del tablero o las estadísticas de un jugador.

struct ConfiguracionTablero
{
	int columnas, filas;
};

struct ConteoConColumna
{
	int conteo, columna;
};

struct EstadisticasDeJugador
{
	double partidas_ganadas, partidas_perdidas, empates, total_movimientos;
	double porcentaje_ganadas, porcentaje_perdidas, porcentaje_empatadas, promedio_movimientos;
};

struct Movimiento
{
	char jugador;
	int columna;
};

struct JugadorParaRanking
{
	string nombre;
	double puntuacion, movimientos;
};

Esto es más que nada para facilitar las cosas y abstraer mejor cada concepto, aprovechando las características del lenguaje.

Configuración del tablero de juego

Guardar y mostrar configuración del tablero
Guardar y mostrar configuración del tablero

Veamos los ajustes del tablero de juego. Como se dijo anteriormente, se debe permitir ver y cambiar el tamaño del tablero, guardando los ajustes en un archivo.

Así que veamos las siguientes funciones ayudantes:

void cambiar_configuracion(string nick, ConfiguracionTablero configuracion)
{
	ofstream archivo;
	archivo.open(nombre_archivo_configuracion(nick).c_str(), fstream::out);
	archivo << configuracion.columnas;
	archivo << endl;
	archivo << configuracion.filas;
	archivo.close();
}

ConfiguracionTablero obtener_configuracion_tablero(string nick)
{
	ConfiguracionTablero configuracion;
	ifstream archivo(nombre_archivo_configuracion(nick).c_str());
	// Leemos la línea dentro de "linea" y convertimos
	string linea;
	getline(archivo, linea);
	configuracion.columnas = atoi(linea.c_str());
	// Lo mismo pero para la segunda línea, misma que representa las columnas
	getline(archivo, linea);
	configuracion.filas = atoi(linea.c_str());
	return configuracion;
}

Lo que estamos haciendo es leer y escribir un struct como lo vimos en mi otro post. En cuanto a las funciones que solicitan las nuevas filas y columnas y modifican la configuración las veremos más adelante.

Estadísticas del jugador

Estadísticas de jugador - Partidas de Conecta 4 ganadas, perdidas y empatadas
Estadísticas de jugador – Partidas de Conecta 4 ganadas, perdidas y empatadas

Tal y como lo solicita el ejercicio debemos guardar las estadísticas del jugador con sus movimientos y el resultado del juego.

En este caso lo que debemos hacer es agregar contenido al fichero, y no sobrescribirlo. Aquí tenemos las funciones que nos ayudan:

void guardarPartidaTerminada(string nick, string resultado, int movimientos)
{
	ofstream archivo;
	archivo.open(nombre_archivo_resultados(nick).c_str(), ios_base::app);
	archivo << resultado << DELIMITADOR_RESULTADOS << movimientos;
	archivo << endl;
	archivo.close();
	actualizarJugadorEnRanking(nick);
}

void jugar(string nick)
{
  /*...*/
		if (jugador_gana(jugadorActual, tablero))
		{
			imprimir_tablero(tablero);
			anunciar_victoria(jugadorActual, nick);
			if (jugadorActual == JUGADOR_HUMANO)
			{
				guardarPartidaTerminada(nick, RESULTADO_GANA, conteo_movimientos);
			}
			else
			{
				guardarPartidaTerminada(nick, RESULTADO_PIERDE, conteo_movimientos);
			}
			break;
		}
		else if (esEmpate(tablero))
		{
			imprimir_tablero(tablero);
			anunciar_empate();
			if (jugadorActual == JUGADOR_HUMANO)
			{
				guardarPartidaTerminada(nick, RESULTADO_EMPATE, conteo_movimientos);
			}
			break;
		}
  /*...*/
}

Aquí te estoy mostrando la función que guarda la partida terminada junto con su modo de uso. Y ya para ver las estadísticas del jugador simplemente leemos de ese archivo:

EstadisticasDeJugador obtener_estadisticas(string nick)
{
	EstadisticasDeJugador estadisticas;
	estadisticas.empates = 0;
	estadisticas.partidas_ganadas = 0;
	estadisticas.partidas_perdidas = 0;
	estadisticas.total_movimientos = 0;
	ifstream archivo(nombre_archivo_resultados(nick).c_str());
	string linea, resultado, movimientos;
	while (getline(archivo, linea))
	{
		stringstream input_stringstream(linea);
		getline(input_stringstream, resultado, DELIMITADOR_RESULTADOS);
		getline(input_stringstream, movimientos, DELIMITADOR_RESULTADOS);
		if (resultado == RESULTADO_EMPATE)
		{
			estadisticas.empates++;
		}
		else if (resultado == RESULTADO_GANA)
		{
			estadisticas.partidas_ganadas++;
		}
		else if (resultado == RESULTADO_PIERDE)
		{
			estadisticas.partidas_perdidas++;
		}
		estadisticas.total_movimientos += atoi(movimientos.c_str());
	}
	double total_partidas = estadisticas.partidas_ganadas + estadisticas.partidas_perdidas + estadisticas.empates;
	estadisticas.porcentaje_ganadas = (estadisticas.partidas_ganadas * 100) / total_partidas;
	estadisticas.porcentaje_perdidas = (estadisticas.partidas_perdidas * 100) / total_partidas;
	estadisticas.porcentaje_empatadas = (estadisticas.empates * 100) / total_partidas;
	estadisticas.promedio_movimientos = estadisticas.total_movimientos / total_partidas;
	return estadisticas;
}

void ver_estadisticas(string nick)
{
	EstadisticasDeJugador estadisticas = obtener_estadisticas(nick);
	cout << "Mostrando estadisticas para " << nick << "\n";
	cout << "Porcentaje de partidas ganadas: " << estadisticas.porcentaje_ganadas << " %\n";
	cout << "Porcentaje de partidas perdidas: " << estadisticas.porcentaje_perdidas << " %\n";
	cout << "Porcentaje de partidas empatadas: " << estadisticas.porcentaje_empatadas << " %\n";
	cout << "Promedio de movimientos " << estadisticas.promedio_movimientos << "\n";
}

Si te fijas, estamos calculando los porcentajes en la función, ya que en el archivo solo guardamos la cantidad de partidas ganadas, perdidas, empatadas y movimientos.

Reproducir última partida

Reproducir última partida - Programación de juego Conecta 4 en C++
Reproducir última partida – Programación de juego Conecta 4 en C++

Otra opción que el juego solicita es la de reproducir la última partida. Para ello decidí crear un vector de structs con movimientos, indicando el jugador y la columna que elige.

	vector<Movimiento> movimientos; // <- Movimientos
	Movimiento movimiento;
	while (true)
	{
		imprimir_tablero(tablero);
		if (jugadorActual == JUGADOR_HUMANO)
		{
			cout << nick << ", elige: " << endl;
			columna = solicitar_columna(tablero);
			cout << nick << " elige la columna " << columna << endl;
			conteo_movimientos++;
		}
		else
		{
			cout << "CPU, elige:" << endl;
			columna = elegir_mejor_columna(jugadorActual, tablero);
		}
		tablero = colocar_pieza(columna, tablero, jugadorActual);
		movimiento.columna = columna;
		movimiento.jugador = jugadorActual;
		movimientos.push_back(movimiento); // <- Agregarlo al vector
    
// ...

void guardar_movimientos_de_partida(string nick, vector<Movimiento> movimientos)
{
	ofstream archivo;
	archivo.open(nombre_archivo_ultima_partida(nick).c_str(), fstream::out);
	int i;
	for (i = 0; i < movimientos.size(); i++)
	{
		Movimiento movimiento = movimientos[i];
		archivo << movimiento.jugador << DELIMITADOR_MOVIMIENTOS << movimiento.columna << "\n";
	}
	archivo.close();
}

Obviamente solo te estoy mostrando los fragmentos importantes en donde tenemos el vector y vamos agregando los movimientos.

Ya después de terminar la partida se invoca a la función que guarda los movimientos de la partida de Conecta 4 usando C++.

Nota: si el tamaño del tablero al momento de reproducir la última partida es distinto al que se usó al momento de guardarla, puede haber problemas. Yo no lo validé porque el ejercicio no lo requería.

En el caso de obtener los movimientos y reproducir la última partida tenemos esto:

vector<Movimiento> obtener_movimientos_de_partida(string nick)
{
	vector<Movimiento> movimientos;
	Movimiento movimiento;
	ifstream archivo(nombre_archivo_ultima_partida(nick).c_str());
	string linea, jugador, columna;
	while (getline(archivo, linea))
	{
		stringstream input_stringstream(linea);
		getline(input_stringstream, jugador, DELIMITADOR_MOVIMIENTOS);
		getline(input_stringstream, columna, DELIMITADOR_MOVIMIENTOS);
		movimiento.jugador = jugador[0];
		movimiento.columna = atoi(columna.c_str());
		movimientos.push_back(movimiento);
	}
	return movimientos;
}

void repetir_ultima_partida(string nick)
{
	// Consumimos el último salto de línea que seguramente está en el búfer
	getchar();
	cout << "Repitiendo ultima partida guardada para '" << nick << "'"
		 << "\n";
	vector<Movimiento> movimientos = obtener_movimientos_de_partida(nick);
	ConfiguracionTablero configuracion = obtener_configuracion_tablero(nick);
	vector<vector<char> > tablero = inicializarTablero(configuracion);
	int i;
	for (i = 0; i < movimientos.size(); i++)
	{
		char jugador = movimientos[i].jugador;
		int columna = movimientos[i].columna;
		if (jugador == JUGADOR_HUMANO)
		{
			cout << "El jugador elige la columna " << columna << "\n";
		}
		else
		{
			cout << "El CPU elige la columna " << columna << "\n";
		}
		tablero = colocar_pieza(columna, tablero, jugador);
		imprimir_tablero(tablero);
		if (jugador_gana(jugador, tablero))
		{
			anunciar_victoria(jugador, nick);
		}
		else if (esEmpate(tablero))
		{
			anunciar_empate();
		}
		cout << "Presiona Enter para ver el siguiente movimiento...";
		getchar();
	}
	cout << "Se ha terminado la repeticion"
		 << "\n";
}

Simplemente leemos el archivo y simulamos el juego. Como diseñé todas las funciones de manera que reciban el jugador y la columna pude reutilizarlas y hacer como si fuera un juego normal.

Ranking de jugadores

Ranking de jugadores de Conecta 4 en CPP
Ranking de jugadores de Conecta 4 en CPP

Esto fue un poco complejo de hacer y pensar, pues nunca lo había hecho como lo pide el ejercicio. Aquí, después de cada partida, debemos calcular el ranking y guardarlo en un archivo de texto.

Entonces el algoritmo es: leer el ranking del archivo, agregar el jugador si no existe (si ya existe, lo actualizas), luego ordenar el vector tomando en cuenta las 2 propiedades y finalmente guardar solo las primeras 10 posiciones.

void actualizarJugadorEnRanking(string nick)
{
	JugadorParaRanking jugador = calcular_puntaje(nick);
	vector<JugadorParaRanking> jugadores = obtenerJugadoresRanking();
	int posibleIndice = indiceDeJugador(jugadores, jugador.nombre);
	if (posibleIndice == -1)
	{
		jugadores.push_back(jugador);
	}
	else
	{
		jugadores[posibleIndice] = jugador;
	}
	guardarJugadoresRanking(ordenar(jugadores));
}

void guardarJugadoresRanking(vector<JugadorParaRanking> jugadores)
{
	ofstream archivo;
	archivo.open(ARCHIVO_RANKING.c_str(), fstream::out);
	// Solo escribir [MAXIMO_JUGADORES_RANKING] jugadores
	int verdaderoFin = MAXIMO_JUGADORES_RANKING;
	if (jugadores.size() < verdaderoFin)
	{
		verdaderoFin = jugadores.size();
	}
	int i;
	for (i = 0; i < verdaderoFin; i++)
	{
		JugadorParaRanking jugador = jugadores[i];
		archivo << jugador.nombre << DELIMITADOR_RANKING << jugador.puntuacion << DELIMITADOR_RANKING << jugador.movimientos << "\n";
	}
	archivo.close();
}

vector<JugadorParaRanking> obtenerJugadoresRanking()
{
	vector<JugadorParaRanking> jugadores;
	JugadorParaRanking jugadorParaRanking;
	ifstream archivo(ARCHIVO_RANKING.c_str());
	string linea, nombre, puntuacion, movimientos;
	while (getline(archivo, linea))
	{
		stringstream input_stringstream(linea);
		getline(input_stringstream, nombre, DELIMITADOR_RANKING);
		getline(input_stringstream, puntuacion, DELIMITADOR_RANKING);
		getline(input_stringstream, movimientos, DELIMITADOR_RANKING);
		jugadorParaRanking.nombre = nombre;
		jugadorParaRanking.puntuacion = atof(puntuacion.c_str());
		jugadorParaRanking.movimientos = atof(movimientos.c_str());
		jugadores.push_back(jugadorParaRanking);
	}
	return jugadores;
}

vector<JugadorParaRanking> ordenar(vector<JugadorParaRanking> jugadores)
{
	int i, j;
	for (i = 0; i < jugadores.size() - 1; i++)
	{
		for (j = i + 1; j < jugadores.size(); j++)
		{
			JugadorParaRanking jugadorActual = jugadores[i];
			JugadorParaRanking jugadorDerecha = jugadores[j];
			if (jugadorActual.puntuacion < jugadorDerecha.puntuacion)
			{
				jugadores[i] = jugadorDerecha;
				jugadores[j] = jugadorActual;
			}
			else if (jugadorActual.puntuacion == jugadorDerecha.puntuacion)
			{
				if (jugadorActual.movimientos > jugadorDerecha.movimientos)
				{
					jugadores[i] = jugadorDerecha;
					jugadores[j] = jugadorActual;
				}
			}
		}
	}
	return jugadores;
}

El ordenamiento tomando en cuenta los dos parámetros (si la puntuación es igual, se toma en cuenta la cantidad de movimientos) está en la función de la línea 56.

Estoy usando el algoritmo de ordenamiento por selección que ya publiqué anteriormente en C.

Jugar

Conecta 4 en C++ - Jugar partida contra CPU (inteligencia artificial)
Conecta 4 en C++ – Jugar partida contra CPU (inteligencia artificial)

Ahora viene la más importante: jugar a Conecta 4 programado en C++ contra la CPU. No voy a tardarme exponiendo todas las funciones aquí, ya que he explicado esto anteriormente en varios lenguajes.

El algoritmo es simple: iniciamos el tablero con espacios vacíos, luego solicitamos la columna ya sea al CPU o al jugador y dejamos caer la pieza, calculando la primera fila vacía.

Después de que un jugador ha colocado su pieza comprobamos si hay algún ganador o si hay empate, y en ese caso se termina el juego.

void jugar(string nick)
{
	ConfiguracionTablero configuracion = obtener_configuracion_tablero(nick);
	vector<vector<char> > tablero = inicializarTablero(configuracion);
	int jugadorActual = JUGADOR_HUMANO;
	int columna;
	int conteo_movimientos = 0;
	vector<Movimiento> movimientos;
	Movimiento movimiento;
	while (true)
	{
		imprimir_tablero(tablero);
		if (jugadorActual == JUGADOR_HUMANO)
		{
			cout << nick << ", elige: " << endl;
			columna = solicitar_columna(tablero);
			cout << nick << " elige la columna " << columna << endl;
			conteo_movimientos++;
		}
		else
		{
			cout << "CPU, elige:" << endl;
			columna = elegir_mejor_columna(jugadorActual, tablero);
		}
		tablero = colocar_pieza(columna, tablero, jugadorActual);
		movimiento.columna = columna;
		movimiento.jugador = jugadorActual;
		movimientos.push_back(movimiento);
		if (jugador_gana(jugadorActual, tablero))
		{
			imprimir_tablero(tablero);
			anunciar_victoria(jugadorActual, nick);
			if (jugadorActual == JUGADOR_HUMANO)
			{
				guardarPartidaTerminada(nick, RESULTADO_GANA, conteo_movimientos);
			}
			else
			{
				guardarPartidaTerminada(nick, RESULTADO_PIERDE, conteo_movimientos);
			}
			break;
		}
		else if (esEmpate(tablero))
		{
			imprimir_tablero(tablero);
			anunciar_empate();
			if (jugadorActual == JUGADOR_HUMANO)
			{
				guardarPartidaTerminada(nick, RESULTADO_EMPATE, conteo_movimientos);
			}
			break;
		}
		jugadorActual = obtener_oponente(jugadorActual);
	}
	guardar_movimientos_de_partida(nick, movimientos);
}

Me parece que el código se explica por sí mismo. Y para el algoritmo de la CPU inteligente para que pueda ganar Conecta 4 el código es el siguiente:

int elegir_mejor_columna(char jugador, vector<vector<char> > tablero)
{
	// Voy a comprobar si puedo ganar...
	int posibleColumnaGanadora = obtener_columna_ganadora(jugador, tablero);
	if (posibleColumnaGanadora != -1)
	{
		cout << "*elijo ganar*\n";
		return posibleColumnaGanadora;
	}
	// Si no, voy a comprobar si mi oponente gana con el siguiente movimiento, para evitarlo
	char oponente = obtener_oponente(jugador);
	int posibleColumnaGanadoraDeOponente = obtener_columna_ganadora(oponente, tablero);
	if (posibleColumnaGanadoraDeOponente != -1)
	{
		cout << "*elijo evitar que mi oponente gane*\n";
		return posibleColumnaGanadoraDeOponente;
	}
	// En caso de que nadie pueda ganar en el siguiente movimiento, buscaré en dónde se obtiene el mayor
	// puntaje al colocar la pieza
	ConteoConColumna conteoConColumnaJugador = obtener_columna_en_la_que_se_obtiene_mayor_puntaje(jugador, tablero);
	ConteoConColumna conteoConColumnaOponente = obtener_columna_en_la_que_se_obtiene_mayor_puntaje(oponente, tablero);
	if (conteoConColumnaOponente.conteo > conteoConColumnaJugador.conteo)
	{
		cout << "*elijo quitarle el puntaje a mi oponente*\n";
		return conteoConColumnaOponente.columna;
	}
	else if (conteoConColumnaJugador.conteo > 1)
	{
		cout << "*elijo colocarla en donde obtengo un mayor puntaje*\n";
		return conteoConColumnaJugador.columna;
	}
	// Si no, regresar la central por si está desocupada

	int columnaCentral = obtener_columna_central(jugador, tablero);
	if (columnaCentral != -1)
	{
		cout << "*elijo ponerla en el centro*\n";
		return columnaCentral;
	}
	// Finalmente, devolver la primera disponible de manera aleatoria
	int columna = obtener_columna_aleatoria(jugador, tablero);
	if (columna != -1)
	{
		cout << "*elijo la primera vacía aleatoria*\n";
		return columna;
	}
	cout << "Esto no debería suceder\n";
	return 0;
}

Dicho algoritmo ya lo he explicado antes en Algoritmo para ganar Conecta 4.

Por cierto, esta misma función puede ser invocada para el jugador humano, y así tendríamos al CPU enfrentándose contra sí mismo (en donde siempre ganará el jugador que tiene el primer turno).

Poniendo todo junto

Ya te expliqué el código más relevante, aunque falta mostrarte el código completo; mismo que te dejaré en mi GitHub. Todo está en el archivo main.cpp.

Para compilarlo te recomiendo usar el compilador g++ de MinGW, aunque también es compatible con el viejo compilador incluido en DevC++.

Recuerda que ya he hecho este juego en C, C#, Python y JavaScript.

Para terminar te dejo con más tutoriales de C++.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *