Conecta 4 – Juego programado en C sobre Linux Ubuntu

En este post te mostraré el juego Conecta 4 programado en C por consola. Se trata del juego que lleva el mismo nombre en donde se deben conectar cuatro fichas del mismo color de manera que formen una línea, ya sea horizontal, vertical o diagonal.

Las características del mismo son:

  • Al iniciar se elige un color aleatorio que inicia el juego, es decir, no siempre comienza el mismo jugador
  • Modo humano contra humano
  • Modo humano contra CPU (computadora); se ha programado una pequeña IA para jugar contra el humano
  • Modo computadora contra computadora (divertido de observar)
  • Opción para cambiar la longitud del tablero de juego
  • Se puede cambiar la cantidad de fichas que se conectan. Es decir, se puede jugar a conecta 3, conecta 5, conecta 6, etcétera.
  • Escrito totalmente en C y compilable para C++. Compila en Windows con gcc, en Linux Ubuntu con gcc e igualmente en Android con Termux (de nuevo, usando gcc).

A lo largo del post te mostraré cómo es que programé el juego, dónde se puede probar, descargar, etcétera.

Nota: mira este juego portado para JavaScript, es decir, en su versión web.

Configuración del juego

He definido el encabezado de todo el juego con el prototipo de las funciones así como con las constantes que ajustan el juego.

Lo que se puede cambiar (lo demás no lo recomiendo si no lo entiendes) es el número de filas y columnas para cambiar el tamaño del tablero de juego. Debido a que no he implementado un color para cada jugador, un color es la letra o y el otro color es la letra x.

Finalmente la constante de CONECTA indica cuántas piezas se deben conectar para ganar. Justo aquí se puede modificar para que se juegue a conecta 3, por ejemplo.

/*

  ____          _____               _ _           _       
 |  _ \        |  __ \             (_) |         | |      
 | |_) |_   _  | |__) |_ _ _ __ _____| |__  _   _| |_ ___ 
 |  _ <| | | | |  ___/ _` | '__|_  / | '_ \| | | | __/ _ \
 | |_) | |_| | | |  | (_| | |   / /| | |_) | |_| | ||  __/
 |____/ \__, | |_|   \__,_|_|  /___|_|_.__/ \__, |\__\___|
         __/ |                               __/ |        
        |___/                               |___/         
    
____________________________________
/ Si necesitas ayuda, contáctame en \
\ https://parzibyte.me               /
 ------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
Creado por Parzibyte (https://parzibyte.me). Este encabezado debe mantenerse intacto,
excepto si este es un proyecto de un estudiante.
*/#define FILAS 6
#define COLUMNAS 7
#define TAMANIO_MATRIZ sizeof(char)*FILAS*COLUMNAS
#define JUGADOR_1 'o'
#define JUGADOR_2 'x'
#define JUGADOR_CPU_1 JUGADOR_1
#define JUGADOR_CPU_2 JUGADOR_2
#define ESPACIO_VACIO ' '
#define FILA_NO_ENCONTRADA -1
#define ERROR_COLUMNA_LLENA 2
#define ERROR_FILA_INVALIDA 4
#define ERROR_NINGUNO 3
#define CONECTA 4
#define CONECTA_ARRIBA 1
#define CONECTA_DERECHA 2
#define CONECTA_ABAJO_DERECHA 3
#define CONECTA_ARRIBA_DERECHA 4
#define NO_CONECTA 0
#define COLUMNA_GANADORA_NO_ENCONTRADA -1
#define MODO_HUMANO_CONTRA_HUMANO 1
#define MODO_HUMANO_CONTRA_CPU 2
#define MODO_CPU_CONTRA_CPU 3

int contarArriba(int x, int y, char jugador, char tablero[FILAS][COLUMNAS]);

int contarDerecha(int x, int y, char jugador, char tablero[FILAS][COLUMNAS]);

int contarArribaDerecha(int x, int y, char jugador, char tablero[FILAS][COLUMNAS]);

int contarAbajoDerecha(int x, int y, char jugador, char tablero[FILAS][COLUMNAS]);

int obtenerFilaDesocupada(int columna, char tablero[FILAS][COLUMNAS]);

int colocarPieza(char jugador, int columna, char tablero[FILAS][COLUMNAS]);

void limpiarTablero(char tablero[FILAS][COLUMNAS]);

void dibujarEncabezado(int columnas);

int dibujarTablero(char tablero[FILAS][COLUMNAS]);

int esEmpate(char tablero[FILAS][COLUMNAS]);

char obtenerOponente(char jugador);

void clonarMatriz(char tableroOriginal[FILAS][COLUMNAS], char destino[FILAS][COLUMNAS]);

int obtenerColumnaGanadora(char jugador, char tableroOriginal[FILAS][COLUMNAS]);

int obtenerPrimeraFilaLlena(int columna, char tablero[FILAS][COLUMNAS]);

void obtenerColumnaEnLaQueSeObtieneMayorPuntaje(char jugador, char tableroOriginal[FILAS][COLUMNAS], int *conteo,
                                                int *indice);

int aleatorio_en_rango(int minimo, int maximo);

int obtenerColumnaAleatoria(char jugador, char tableroOriginal[FILAS][COLUMNAS]);

int obtenerColumnaCentral(char jugador, char tableroOriginal[FILAS][COLUMNAS]);

int elegirColumnaCpu(char jugador, char tablero[FILAS][COLUMNAS]);

int ganador(char jugador, char tablero[FILAS][COLUMNAS]);

char elegirJugadorAlAzar();

int solicitarColumnaAJugador();

void jugar(int modo);

Además de los ajustes, se pueden ver todas las funciones del programa. A continuación iré explicando a las mismas.

Tablero de juego

Para mostrar el tablero de juego de Conecta 4 en C he decidido por usar una matriz de tipo char. Cada campo de la matriz tiene tres estados:

  • Vacío
  • Ocupado por jugador 1
  • Ocupado por jugador 2

Recuerda que igualmente la CPU se toma como el jugador 1, y la CPU 2 como el jugador 2. Al inicio del juego se debe limpiar el tablero, colocando los espacios en vacíos.

También tenemos otras funciones como colocarPieza que coloca la pieza en determinada columna y verifica que sea posible además de que sobre espacio.

Otra función interesante es obtenerFilaDesocupada que devuelve la fila en donde se debería colocar la pieza, normalmente debe ser en la primera desocupada de arriba hacia abajo, pero esta función también valida que haya espacio disponible.

/*

  ____          _____               _ _           _       
 |  _ \        |  __ \             (_) |         | |      
 | |_) |_   _  | |__) |_ _ _ __ _____| |__  _   _| |_ ___ 
 |  _ <| | | | |  ___/ _` | '__|_  / | '_ \| | | | __/ _ \
 | |_) | |_| | | |  | (_| | |   / /| | |_) | |_| | ||  __/
 |____/ \__, | |_|   \__,_|_|  /___|_|_.__/ \__, |\__\___|
         __/ |                               __/ |        
        |___/                               |___/         
    
____________________________________
/ Si necesitas ayuda, contáctame en \
\ https://parzibyte.me               /
 ------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
Creado por Parzibyte (https://parzibyte.me). Este encabezado debe mantenerse intacto,
excepto si este es un proyecto de un estudiante.
*/#include "conecta4.h"
#include <stdio.h>

int obtenerFilaDesocupada(int columna, char tablero[FILAS][COLUMNAS]) {
    int i;
    for (i = FILAS - 1; i >= 0; i--) {
        if (tablero[i][columna] == ESPACIO_VACIO) {
            return i;
        }
    }
    return FILA_NO_ENCONTRADA;
}

int colocarPieza(char jugador, int columna, char tablero[FILAS][COLUMNAS]) {
    if (columna < 0 || columna >= COLUMNAS) {
        return ERROR_FILA_INVALIDA;
    }
    int fila = obtenerFilaDesocupada(columna, tablero);
    if (fila == FILA_NO_ENCONTRADA) {
        return ERROR_COLUMNA_LLENA;
    }
    tablero[fila][columna] = jugador;
    return ERROR_NINGUNO;
}


void limpiarTablero(char tablero[FILAS][COLUMNAS]) {
    int i;
    for (i = 0; i < FILAS; ++i) {
        int j;
        for (j = 0; j < COLUMNAS; ++j) {
            tablero[i][j] = ESPACIO_VACIO;
        }
    }
}

void dibujarEncabezado(int columnas) {
    printf("\n");
    int i;
    for (i = 0; i < columnas; ++i) {
        printf("|%d", i + 1);
        if (i + 1 >= columnas) {
            printf("|");
        }

    }
}

int dibujarTablero(char tablero[FILAS][COLUMNAS]) {
    dibujarEncabezado(COLUMNAS);
    printf("\n");
    int i;
    for (i = 0; i < FILAS; ++i) {
        int j;
        for (j = 0; j < COLUMNAS; ++j) {
            printf("|%c", tablero[i][j]);
            if (j + 1 >= COLUMNAS) {
                printf("|");
            }
        }
        printf("\n");
    }
    return 0;
}


int esEmpate(char tablero[FILAS][COLUMNAS]) {
    int i;
    for (i = 0; i < COLUMNAS; ++i) {
        int resultado = obtenerFilaDesocupada(i, tablero);
        if (resultado != FILA_NO_ENCONTRADA) {
            return 0;
        }
    }
    return 1;
}

Finalmente contamos con la función esEmpate que devuelve un booleano si ya no quedan espacios vacíos en el tablero.

Conteo de conexiones

En este juego de conecta 4 necesitamos contar cuántas piezas del mismo color hay en línea, y justo así se detecta si existe un ganador. Para ello simplemente hacemos ciclos y tomamos en cuenta las siguientes ubicaciones:

  • Arriba
  • Derecha
  • Arriba derecha
  • Abajo derecha

Esto es debido a que las otras ubicaciones ya son tomadas en cuenta con las anteriores. Por ejemplo, para saber si conecta hacia abajo, es lo mismo que conectar hacia arriba pero desde la fila inferior. Lo mismo para las demás.

Sé que debe existir una función que permita navegar de un punto a otro, de manera que elimine el código repetido, pero no he investigado.

/*

  ____          _____               _ _           _       
 |  _ \        |  __ \             (_) |         | |      
 | |_) |_   _  | |__) |_ _ _ __ _____| |__  _   _| |_ ___ 
 |  _ <| | | | |  ___/ _` | '__|_  / | '_ \| | | | __/ _ \
 | |_) | |_| | | |  | (_| | |   / /| | |_) | |_| | ||  __/
 |____/ \__, | |_|   \__,_|_|  /___|_|_.__/ \__, |\__\___|
         __/ |                               __/ |        
        |___/                               |___/         
    
____________________________________
/ Si necesitas ayuda, contáctame en \
\ https://parzibyte.me               /
 ------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
Creado por Parzibyte (https://parzibyte.me). Este encabezado debe mantenerse intacto,
excepto si este es un proyecto de un estudiante.
*/#include "conecta4.h"

int contarArriba(int x, int y, char jugador, char tablero[FILAS][COLUMNAS]) {
    int yInicio = (y - CONECTA >= 0) ? y - CONECTA + 1 : 0;
    int contador = 0;
    for (; yInicio <= y; yInicio++) {
        if (tablero[yInicio][x] == jugador) {
            contador++;
        } else {
            contador = 0;
        }
    }
    return contador;
}

int contarDerecha(int x, int y, char jugador, char tablero[FILAS][COLUMNAS]) {
    int xFin = (x + CONECTA < COLUMNAS) ? x + CONECTA - 1 : COLUMNAS - 1;
    int contador = 0;
    for (; x <= xFin; x++) {
        if (tablero[y][x] == jugador) {
            contador++;
        } else {
            contador = 0;
        }
    }
    return contador;
}

int contarArribaDerecha(int x, int y, char jugador, char tablero[FILAS][COLUMNAS]) {
    int xFin = (x + CONECTA < COLUMNAS) ? x + CONECTA - 1 : COLUMNAS - 1;
    int yInicio = (y - CONECTA >= 0) ? y - CONECTA + 1 : 0;
    int contador = 0;
    while (x <= xFin && yInicio <= y) {
        if (tablero[y][x] == jugador) {
            contador++;
        } else {
            contador = 0;
        }
        x++;
        y--;
    }
    return contador;
}

int contarAbajoDerecha(int x, int y, char jugador, char tablero[FILAS][COLUMNAS]) {
    int xFin = (x + CONECTA < COLUMNAS) ? x + CONECTA - 1 : COLUMNAS - 1;
    int yFin = (y + CONECTA < FILAS) ? y + CONECTA - 1 : FILAS - 1;
    int contador = 0;
    while (x <= xFin && y <= yFin) {
        if (tablero[y][x] == jugador) {
            contador++;
        } else {
            contador = 0;
        }
        x++;
        y++;
    }
    return contador;
}

int ganador(char jugador, char tablero[FILAS][COLUMNAS]) {
/*
 * Solo necesitamos
 * Arriba
 * Derecha
 * Arriba derecha
 * Abajo derecha
 *
 * */    int y;
    for (y = 0; y < FILAS; y++) {
        int x;
        for (x = 0; x < COLUMNAS; x++) {
            int conteoArriba = contarArriba(x, y, jugador, tablero);
            if (conteoArriba >= CONECTA) {
                return CONECTA_ARRIBA;
            }
            if (contarDerecha(x, y, jugador, tablero) >= CONECTA) {
                return CONECTA_DERECHA;
            }
            if (contarArribaDerecha(x, y, jugador, tablero) >= CONECTA) {
                return CONECTA_ARRIBA_DERECHA;
            }
            if (contarAbajoDerecha(x, y, jugador, tablero) >= CONECTA) {
                return CONECTA_ABAJO_DERECHA;
            }
        }
    }
    return NO_CONECTA;
}

Por cierto la función que cuenta regresa varios estados para saber si se conecta hacia determinado lugar (y así determinar el ganador).

Inteligencia artificial de CPU

Ahora veamos el montón de if’s que hacen que la CPU piense. Ya expliqué anteriormente el algoritmo, ahora vengo a mostrarlo convertido en código. Queda así:

/*

  ____          _____               _ _           _       
 |  _ \        |  __ \             (_) |         | |      
 | |_) |_   _  | |__) |_ _ _ __ _____| |__  _   _| |_ ___ 
 |  _ <| | | | |  ___/ _` | '__|_  / | '_ \| | | | __/ _ \
 | |_) | |_| | | |  | (_| | |   / /| | |_) | |_| | ||  __/
 |____/ \__, | |_|   \__,_|_|  /___|_|_.__/ \__, |\__\___|
         __/ |                               __/ |        
        |___/                               |___/         
    
____________________________________
/ Si necesitas ayuda, contáctame en \
\ https://parzibyte.me               /
 ------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
Creado por Parzibyte (https://parzibyte.me). Este encabezado debe mantenerse intacto,
excepto si este es un proyecto de un estudiante.
*/#include "conecta4.h"
#include <string.h>
#include <stdio.h>

void clonarMatriz(char tableroOriginal[FILAS][COLUMNAS], char destino[FILAS][COLUMNAS]) {
    memcpy(destino, tableroOriginal, TAMANIO_MATRIZ);
}

int obtenerColumnaGanadora(char jugador, char tableroOriginal[FILAS][COLUMNAS]) {
    char tablero[FILAS][COLUMNAS];
    int i;
    for (i = 0; i < COLUMNAS; i++) {
        clonarMatriz(tableroOriginal, tablero);
        int resultado = colocarPieza(jugador, i, tablero);
        if (resultado == ERROR_NINGUNO) {
            int gana = ganador(jugador, tablero);
            if (gana != NO_CONECTA) {
                return i;
            }
        }
    }
    return COLUMNA_GANADORA_NO_ENCONTRADA;
}

int obtenerPrimeraFilaLlena(int columna, char tablero[FILAS][COLUMNAS]) {
    int i;
    for (i = 0; i < FILAS; ++i) {
        if (tablero[i][columna] != ESPACIO_VACIO) {
            return i;
        }
    }
    return FILA_NO_ENCONTRADA;
}

/*
 * Los dos últimos apuntadores son porque no podemos regresar dos variables
 * */void obtenerColumnaEnLaQueSeObtieneMayorPuntaje(char jugador, char tableroOriginal[FILAS][COLUMNAS], int *conteo,
                                                int *indice) {

    int conteoMayor = 0,
            indiceColumnaConConteoMayor = -1;
    char tablero[FILAS][COLUMNAS];
    int i;
    for (i = 0; i < COLUMNAS; ++i) {
        clonarMatriz(tableroOriginal, tablero);
        int estado = colocarPieza(jugador, i, tablero);
        if (estado == ERROR_NINGUNO) {
            int filaDePiezaRecienColocada = obtenerPrimeraFilaLlena(i, tablero);
            if (filaDePiezaRecienColocada != FILA_NO_ENCONTRADA) {
                int c = contarArriba(i, filaDePiezaRecienColocada, jugador, tablero);
                if (c > conteoMayor) {
                    conteoMayor = c;
                    indiceColumnaConConteoMayor = i;
                }
                c = contarArribaDerecha(i, filaDePiezaRecienColocada, jugador, tablero);
                if (c > conteoMayor) {
                    conteoMayor = c;
                    indiceColumnaConConteoMayor = i;
                }
                c = contarDerecha(i, filaDePiezaRecienColocada, jugador, tablero);
                if (c > conteoMayor) {
                    conteoMayor = c;
                    indiceColumnaConConteoMayor = i;
                }
                c = contarAbajoDerecha(i, filaDePiezaRecienColocada, jugador, tablero);
                if (c > conteoMayor) {
                    conteoMayor = c;
                    indiceColumnaConConteoMayor = i;
                }
            }
        }
    }
    *conteo = conteoMayor;
    *indice = indiceColumnaConConteoMayor;
}

int obtenerColumnaAleatoria(char jugador, char tableroOriginal[FILAS][COLUMNAS]) {
    while (1) {
        char tablero[FILAS][COLUMNAS];
        clonarMatriz(tableroOriginal, tablero);
        int columna = aleatorio_en_rango(0, COLUMNAS - 1);
        int resultado = colocarPieza(jugador, columna, tablero);
        if (resultado == ERROR_NINGUNO) {
            return columna;
        }
    }
}

int obtenerColumnaCentral(char jugador, char tableroOriginal[FILAS][COLUMNAS]) {
    char tablero[FILAS][COLUMNAS];
    clonarMatriz(tableroOriginal, tablero);
    int mitad = (COLUMNAS - 1) / 2;
    int resultado = colocarPieza(jugador, mitad, tablero);
    if (resultado == ERROR_NINGUNO) {
        return mitad;
    }
    return COLUMNA_GANADORA_NO_ENCONTRADA;
}

int elegirColumnaCpu(char jugador, char tablero[FILAS][COLUMNAS]) {
    // Voy a comprobar si puedo ganar...
    int posibleColumnaGanadora = obtenerColumnaGanadora(jugador, tablero);
    if (posibleColumnaGanadora != COLUMNA_GANADORA_NO_ENCONTRADA) {
        printf("*elijo ganar*\n");
        return posibleColumnaGanadora;
    }
    // Si no, voy a comprobar si mi oponente gana con el siguiente movimiento, para evitarlo
    char oponente = obtenerOponente(jugador);
    int posibleColumnaGanadoraDeOponente = obtenerColumnaGanadora(oponente, tablero);
    if (posibleColumnaGanadoraDeOponente != COLUMNA_GANADORA_NO_ENCONTRADA) {
        printf("*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
    int conteoCpu, columnaCpu;
    obtenerColumnaEnLaQueSeObtieneMayorPuntaje(jugador, tablero, &conteoCpu, &columnaCpu);
    int conteoOponente, columnaOponente;
    obtenerColumnaEnLaQueSeObtieneMayorPuntaje(oponente, tablero, &conteoOponente, &columnaOponente);
    if (conteoOponente > conteoCpu) {
        printf("*elijo quitarle el puntaje a mi oponente*\n");
        return columnaOponente;
    } else if (conteoCpu > 1) {
        printf("*elijo colocarla en donde obtengo un mayor puntaje*\n");
        return columnaCpu;
    }
    // Si no, regresar la central por si está desocupada

    int columnaCentral = obtenerColumnaCentral(jugador, tablero);
    if (columnaCentral != COLUMNA_GANADORA_NO_ENCONTRADA) {
        printf("*elijo ponerla en el centro*\n");
        return columnaCentral;
    }
    // Finalmente, devolver la primera disponible de manera aleatoria
    int columna = obtenerColumnaAleatoria(jugador, tablero);
    if (columna != FILA_NO_ENCONTRADA) {
        printf("*elijo la primera vacía aleatoria*\n");
        return columna;
    }
    printf("Esto no debería suceder\n");
    return 0;
}

Como puedes ver, todo se basa en clonar el tablero original, simular colocar las piezas y elegir el mejor camino además del balance entre la defensa y el ataque.

Funciones extra

Tenemos más funciones “misceláneas” por ejemplo para obtener un número aleatorio, solicitar una columna, etcétera.

/*

  ____          _____               _ _           _       
 |  _ \        |  __ \             (_) |         | |      
 | |_) |_   _  | |__) |_ _ _ __ _____| |__  _   _| |_ ___ 
 |  _ <| | | | |  ___/ _` | '__|_  / | '_ \| | | | __/ _ \
 | |_) | |_| | | |  | (_| | |   / /| | |_) | |_| | ||  __/
 |____/ \__, | |_|   \__,_|_|  /___|_|_.__/ \__, |\__\___|
         __/ |                               __/ |        
        |___/                               |___/         
    
____________________________________
/ Si necesitas ayuda, contáctame en \
\ https://parzibyte.me               /
 ------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
Creado por Parzibyte (https://parzibyte.me). Este encabezado debe mantenerse intacto,
excepto si este es un proyecto de un estudiante.
*/#include "conecta4.h"
#include <stdlib.h> // rand y RAND_MAX

char obtenerOponente(char jugador) {
    if (jugador == JUGADOR_1) {
        return JUGADOR_2;
    } else {
        return JUGADOR_1;
    }
}


int aleatorio_en_rango(int minimo, int maximo) {
    return minimo + rand() / (RAND_MAX / (maximo - minimo + 1) + 1);
}

char elegirJugadorAlAzar() {
    int numero = aleatorio_en_rango(0, 1);
    if (numero) {
        return JUGADOR_1;
    } else {
        return JUGADOR_2;
    }
}


int solicitarColumnaAJugador() {
    int columna = 0;
    printf("Escribe la columna en donde colocar la pieza: ");
    scanf("%d", &columna);
    // Necesitamos índices de arreglos
    columna--;
    return columna;
}

Otra función interesante es la que devuelve el oponente del jugador actual, y aunque es simple, es muy útil para evitar repetición de código.

Juego y función principal

Ahora en la función main o principal iniciamos el juego dependiendo de lo que el usuario seleccione:

/*

  ____          _____               _ _           _       
 |  _ \        |  __ \             (_) |         | |      
 | |_) |_   _  | |__) |_ _ _ __ _____| |__  _   _| |_ ___ 
 |  _ <| | | | |  ___/ _` | '__|_  / | '_ \| | | | __/ _ \
 | |_) | |_| | | |  | (_| | |   / /| | |_) | |_| | ||  __/
 |____/ \__, | |_|   \__,_|_|  /___|_|_.__/ \__, |\__\___|
         __/ |                               __/ |        
        |___/                               |___/         
    
____________________________________
/ Si necesitas ayuda, contáctame en \
\ https://parzibyte.me               /
 ------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
Creado por Parzibyte (https://parzibyte.me). Este encabezado debe mantenerse intacto,
excepto si este es un proyecto de un estudiante.
*/#include <stdio.h>
#include <stdlib.h> // rand y RAND_MAX
#include <unistd.h> // getpid

#include "conecta4.h"
#include "conteo.c"
#include "tablero.c"
#include "ia.c"
#include "misc.c"
#include "juego.c"


int main() {
    // Hay que alimentar a rand, solamente una vez (seed rand)
    srand(getpid());
    printf("*** Conecta 4 ***\n\nBy Parzibyte\n\t\thttps://parzibyte.me/blog\n");
    int modo;
    printf("1 => Humano contra Humano\n"
           "2 => Humano contra CPU\n"
           "3 => CPU contra CPU\n"
           "4 => Salir\n"
           "Seleccione una opción: ");
    scanf("%d", &modo);
    if (modo <= 0 || modo > 3) {
        return 0;
    }
    jugar(modo);
    return 0;
}

La función de juego simplemente recibe el modo de juego que puede ser humano contra humano, humano contra CPU, CPU contra CPU. Se ve así:

/*

  ____          _____               _ _           _
 |  _ \        |  __ \             (_) |         | |
 | |_) |_   _  | |__) |_ _ _ __ _____| |__  _   _| |_ ___
 |  _ <| | | | |  ___/ _` | '__|_  / | '_ \| | | | __/ _ \
 | |_) | |_| | | |  | (_| | |   / /| | |_) | |_| | ||  __/
 |____/ \__, | |_|   \__,_|_|  /___|_|_.__/ \__, |\__\___|
         __/ |                               __/ |
        |___/                               |___/

____________________________________
/ Si necesitas ayuda, contáctame en \
\ https://parzibyte.me               /
 ------------------------------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
Creado por Parzibyte (https://parzibyte.me). Este encabezado debe mantenerse intacto,
excepto si este es un proyecto de un estudiante.
*/#include "conecta4.h"

void jugar(int modo) {
    char tablero[FILAS][COLUMNAS];
    limpiarTablero(tablero);
    char jugadorActual = elegirJugadorAlAzar();
    printf("Comienza el jugador %c\n", jugadorActual);
    while (1) {
        int columna = 0;
        printf("\nTurno del jugador %c\n", jugadorActual);
        dibujarTablero(tablero);
        if (modo == MODO_HUMANO_CONTRA_CPU) {
            if (jugadorActual == JUGADOR_CPU_2) {
                printf("CPU 2 pensando...");
                columna = elegirColumnaCpu(jugadorActual, tablero);
            } else {
                columna = solicitarColumnaAJugador();
            }
        } else if (modo == MODO_CPU_CONTRA_CPU) {
            printf("CPU %d pensando...", jugadorActual == JUGADOR_CPU_1 ? 1 : 2);
            columna = elegirColumnaCpu(jugadorActual, tablero);
        } else if (modo == MODO_HUMANO_CONTRA_HUMANO) {
            columna = solicitarColumnaAJugador();
        }
        int estado = colocarPieza(jugadorActual, columna, tablero);
        if (estado == ERROR_COLUMNA_LLENA) {
            printf("Error: columna llena");
        } else if (estado == ERROR_FILA_INVALIDA) {
            printf("Fila no correcta");
        } else if (estado == ERROR_NINGUNO) {
            int g = ganador(jugadorActual, tablero);
            if (g != NO_CONECTA) {
                dibujarTablero(tablero);
                printf("Gana el jugador %c.", jugadorActual);
                break;
            } else if (esEmpate(tablero)) {
                dibujarTablero(tablero);
                printf("Empate");
                break;
            }
        }
        jugadorActual = obtenerOponente(jugadorActual);
    }
}

De este modo termina todo el código de este maravilloso juego.

Descargas y compilación

Compilar código fuente en C para Conecta 4 – Humano vs Humano

Ahora viene la parte interesante. Puedes descargar el código desde mi repositorio de GitHub (sígueme y déjale una estrella si quieres). Si te fijas, son varios archivos, pues he separado conceptos.

Yo recomiendo usar MinGW en Windows.

En la carpeta que lo has descargado ejecuta simplemente con gcc:

gcc main.c -o conecta4.exe

Y luego puedes ejecutar el archivo conecta4.exe. Si estás en Linux (Ubuntu en este caso) ejecuta:

gcc main.c -o conecta4

Después puedes ejecutar ./conecta4

También debería compilar como un encanto en otros compiladores decentes. Yo lo he probado en la nube con replit (que usa clang) y en local (con gcc); todo funciona de maravilla. Por cierto, si en tu caso quieres todo el código en un mismo archivo, mira el ejemplo en línea.

Vídeo con demo y explicación

He grabado un vídeo en donde explico igualmente las funciones del juego conecta 4 en C además de hacer una pequeña demostración.

Conclusión

Muy pronto traeré este juego portado a otro lenguaje y con interfaz gráfica.

Como siempre lo digo, escribo el código en C para que pueda portarlo a otros lenguajes de manera fácil, pues no hay algo en C que no exista en otros lenguajes (sin contar los macros o apuntadores, de los cuales solo usamos los primeros pero se pueden remplazar por constantes).

Por cierto, este no es el único videojuego que he creado. Aquí hay más.

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.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

1 semana hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

2 semanas hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

2 semanas hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

2 semanas hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

2 semanas hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

2 semanas hace

Esta web usa cookies.