Conecta 4 en JavaScript y HTML

En este post te mostraré el juego de Conecta 4 programado en JavaScript con HTML y Vue, con estilos de Bootstrap.

Conecta 4 en JavaScript, HTML – Versión web

Es el juego de Conecta 4 pero versión web con opción jugador contra jugador, así como jugador contra CPU que usa una pequeña inteligencia artificial.

A lo largo del post te mostraré cómo funciona el juego, qué tecnologías he usado, estilos, etcétera. También te mostraré cómo descargar el código fuente, pues el juego es totalmente gratuito y open source. Finalmente te dejaré una demostración para jugar conecta 4 en línea.

Tablero de juego

El tablero de juego es una tabla dinámica que se dibuja a partir de un arreglo. Le he quitado los bordes y he reducido el padding, además de agregarle un color de fondo azul para que parezca el tablero del juego real (es decir, el del mundo real, el físico).

<style>
  td {
      background-color: #638CFF;
      border: 0px ;
      padding: 5px ;
  }
  .img-player{
      max-width: 100px;
  }
</style>

Dentro de cada celda de la tabla (o td) he colocado una imagen que puede ser:

  • Espacio vacío
  • Jugador 1
  • Jugador 2, que a su vez es el CPU cuando se juega contra él

Estas imágenes se pueden cambiar ya sea en el código o realmente en el sistema de archivos. La imagen viene dada por una función que dependiendo del valor devuelve una imagen distinta.

Dibujando tablero de juego

Como lo dije anteriormente, el tablero es una tabla que se ve así:

<table class="table table-bordered">
    <thead>
        <tr>
            <th v-for="i in COLUMNS">
                <button :disabled="!canPlay" @click="makeMove(i)" class="btn btn-warning">Make move
                    here&nbsp;<i class="fa fa-arrow-down"></i></button>
            </th>
        </tr>
    </thead>
    <tbody>
        <tr v-for="row in board">
            <td v-for="cell in row">
                <img class="img-fluid img-player" :src="cellImage(cell)" alt="">
            </td>
        </tr>
    </tbody>
</table>

El encabezado de la tabla tiene un botón que hace que la pieza se coloque en esa columna, es decir, deja caer la pieza.

Debido a que es una matriz o arreglo de dos dimensiones, primero recorremos toda la matriz para tener la fila. Y dentro de esa fila repetimos una celda por cada valor que haya en la fila.

En cada celda habrá una imagen que tendrá como fuente lo que devuelva el método cellImage, el cual veremos a continuación cuando veamos la programación en Vue.

Eso es todo en cuanto al diseño de la interfaz, es momento de pasar a ver el código JavaScript para este juego de Conecta 4 versión web.

Imagen

El método que evalúa la imagen y devuelve la ruta es el siguiente:

cellImage(cell) {
    if (cell === this.PLAYER_1) {
        return "img/player1.png";
    } else if (cell === this.PLAYER_2) {
        return "img/player2.png";
    } else {
        return "img/empty.png"
    }
},

Como lo dije, es un simple if. Lo dejé de esa manera para que sea expresivo, aunque si tú quieres, puedes usar diccionarios, concatenar la celda, etcétera, así como usar distintas imágenes.

Constantes personalizables

Podemos definir el tamaño del tablero o el número de piezas que se deben conectar, de este modo se podría jugar a Conecta 3, conecta 5, etcétera.

const COLUMNS = 7,
ROWS = 6,
EMPTY_SPACE = " ",
PLAYER_1 = "o",
PLAYER_2 = "x",
PLAYER_CPU = PLAYER_2,
CONNECT = 4; // <-- Change this and you can play connect 5, connect 3, connect 100 and so on!

Si te fijas, cuando se juega conecta 4 contra el CPU, se toma al mismo como el jugador 2.

Resetear juego

Tenemos la siguiente función que resetea el juego. Lo que hace primero es preguntar el modo de juego: jugador contra jugador, o jugador contra CPU.

El siguiente paso es limpiar el tablero de juego, rellenándolo con espacios vacíos. Después, selecciona un jugador al azar, que es el que toma el primer turno.

async resetGame() {
    await this.askUserGameMode();
    this.fillBoard();
    this.selectPlayer();
    this.makeCpuMove();
},

Finalmente le indica al CPU que haga su movimiento en caso de que sea su turno.

Hacer movimiento de usuario

Cuando el jugador o usuario presiona el botón para colocar la pieza, se ejecuta el siguiente código en donde se valida si la columna todavía no está llena, y se comprueba si hay un empate o si el jugador ha ganado.

El método recibe el número de columna.

async makeMove(columnNumber) {
    const columnIndex = columnNumber - 1;
    const firstEmptyRow = this.getFirstEmptyRow(columnIndex, this.board);
    if (firstEmptyRow === -1) {
        Swal.fire('Cannot put here, it is full');
        return;
    }
    Vue.set(this.board[firstEmptyRow], columnIndex, this.currentPlayer);
    const status = await this.checkGameStatus();
    if (!status) {
        this.togglePlayer();
        this.makeCpuMove();
    } else {
        this.askUserForAnotherMatch();
    }
},

La pieza es colocada en la matriz, usando la columna indicada (menos 1) y en la fila superior vacía. Es decir, simplemente se cambia el valor que hay en esa celda y Vue se encarga, automáticamente, de refrescarlo en la tabla.

Finalmente se intercambia de jugador y se le indica al CPU que haga su movimiento en caso de que sea su turno y de que el modo de juego sea CPU contra jugador.

Por cierto, en caso de que exista un empate o un ganador, se le pregunta al usuario si quiere jugar de nuevo. En ese caso, se resetea el juego.

Saber si hay un ganador

En este juego hay un ganador cuando hay 4 piezas conectadas en cualquier dirección en línea recta. Por lo tanto simplemente hay que contar en todas las posibles direcciones para ver si en la matriz existe un Conecta 4 para el jugador en cuestión.

Para ello he creado algunas funciones que realizan el conteo en varias direcciones:

countUp(x, y, player, board) {
    let startY = (y - CONNECT >= 0) ? y - CONNECT + 1 : 0;
    let counter = 0;
    for (; startY <= y; startY++) {
        if (board[startY][x] === player) {
            counter++;
        } else {
            counter = 0;
        }
    }
    return counter;
},
countRight(x, y, player, board) {
    let endX = (x + CONNECT < COLUMNS) ? x + CONNECT - 1 : COLUMNS - 1;
    let counter = 0;
    for (; x <= endX; x++) {
        if (board[y][x] === player) {
            counter++;
        } else {
            counter = 0;
        }
    }
    return counter;
},
countUpRight(x, y, player, board) {
    let endX = (x + CONNECT < COLUMNS) ? x + CONNECT - 1 : COLUMNS - 1;
    let startY = (y - CONNECT >= 0) ? y - CONNECT + 1 : 0;
    let counter = 0;
    while (x <= endX && startY <= y) {
        if (board[y][x] === player) {
            counter++;
        } else {
            counter = 0;
        }
        x++;
        y--;
    }
    return counter;
},
countDownRight(x, y, player, board) {
    let endX = (x + CONNECT < COLUMNS) ? x + CONNECT - 1 : COLUMNS - 1;
    let endY = (y + CONNECT < ROWS) ? y + CONNECT - 1 : ROWS - 1;
    let counter = 0;
    while (x <= endX && y <= endY) {
        if (board[y][x] === player) {
            counter++;
        } else {
            counter = 0;
        }
        x++;
        y++;
    }
    return counter;
},

Lo que hace el código es recorrer el tablero de un punto a otro y aumentar un contador en caso de encontrar piezas adyacentes. Si encuentra una pieza que no es del mismo color, entonces reinicia el contador a cero.

La función que decide si es un ganador es la siguiente:

isWinner(player, board) {
    for (let y = 0; y < ROWS; y++) {
        for (let x = 0; x < COLUMNS; x++) {
            let count = 0;
            count = this.countUp(x, y, player, board);
            if (count >= CONNECT) return true;
            count = this.countRight(x, y, player, board);
            if (count >= CONNECT) return true;
            count = this.countUpRight(x, y, player, board);
            if (count >= CONNECT) return true;
            count = this.countDownRight(x, y, player, board);
            if (count >= CONNECT) return true;
        }
    }
    return false;
},

Lo que hace es contar en todas las direcciones, y si detecta que hay una fila seguida de piezas que conectan y que son mayor al número necesario para ganar, entonces regresa true. De lo contrario, false.

Empate en conecta 4

Por otro lado, la función para saber si hay un empate en este juego, se utiliza una función que recorre toda la matriz. Si se encuentra un espacio vacío, significa que todavía no es empate, así que se regresa false.

En caso de terminar de recorrer toda la matriz y no encontrar un espacio vacío, se regresa true.

isTie(board) {
    for (let y = 0; y < ROWS; y++) {
        for (let x = 0; x < COLUMNS; x++) {
            const currentCell = board[y][x];
            if (currentCell === EMPTY_SPACE) {
                return false;
            }
        }
    }
    return true;
},

Inteligencia artificial de conecta 4

Como lo dije anteriormente, este juego soporta jugar contra el CPU. He programado una pequeña inteligencia artificial que elige la mejor columna basándose en el algoritmo para intentar ganar conecta 4.

async makeCpuMove() {
    if (!this.isCpuPlaying || this.currentPlayer !== PLAYER_CPU) {
        return;
    }
    const bestColumn = this.getBestColumnForCpu();
    const firstEmptyRow = this.getFirstEmptyRow(bestColumn, this.board);
    console.log({ firstEmptyRow });
    Vue.set(this.board[firstEmptyRow], bestColumn, this.currentPlayer);
    const status = await this.checkGameStatus();
    if (!status) {
        this.togglePlayer();
    } else {
        this.askUserForAnotherMatch();
    }
},

Primero se verifica si es el turno del CPU y si el modo de juego es jugador contra cpu. Después, se elige la mejor columna y se coloca el valor en el tablero, es decir, se coloca la pieza.

Después de eso, se revisa el estado del juego para saber si hay un empate o un ganador. Si no hay ganador ni empate, se intercambia el jugador y es el turno del humano.

Las funciones que permiten elegir la mejor columna se ven a continuación y se basan en una serie de reglas:

getBestColumnForCpu() {
    const winnerColumn = this.getWinnerColumn(this.board, this.currentPlayer);
    if (winnerColumn !== -1) {
        console.log("Cpu chooses winner column");
        return winnerColumn;
    }
    // Check if adversary wins in the next move, if so, we take it
    const adversary = this.getAdversary(this.currentPlayer);

    const winnerColumnForAdversary = this.getWinnerColumn(this.board, adversary);
    if (winnerColumnForAdversary !== -1) {
        console.log("Cpu chooses take adversary's victory");
        return winnerColumnForAdversary;
    }
    const cpuStats = this.getColumnWithHighestScore(this.currentPlayer, this.board);
    const adversaryStats = this.getColumnWithHighestScore(adversary, this.board);
    console.log({ adversaryStats });
    console.log({ cpuStats });
    if (adversaryStats.highestCount > cpuStats.highestCount) {
        console.log("CPU chooses take adversary highest score");
        // We take the adversary's best move if it is higher than CPU's
        return adversaryStats.columnIndex;
    } else if (cpuStats.highestCount > 1) {
        console.log("CPU chooses highest count");
        return cpuStats.columnIndex;
    }
    const centralColumn = this.getCentralColumn(this.board);
    if (centralColumn !== -1) {
        console.log("CPU Chooses central column");
        return centralColumn;
    }
    // Finally we return a random column
    console.log("CPU chooses random column");
    return this.getRandomColumn(this.board);

},
getWinnerColumn(board, player) {
    for (let i = 0; i < COLUMNS; i++) {
        const boardClone = JSON.parse(JSON.stringify(board));
        const firstEmptyRow = this.getFirstEmptyRow(i, boardClone);
        //Proceed only if row is ok
        if (firstEmptyRow !== -1) {
            boardClone[firstEmptyRow][i] = player;

            // If this is winner, return the column
            if (this.isWinner(player, boardClone)) {
                return i;
            }
        }
    }
    return -1;
},
getColumnWithHighestScore(player, board) {
    const returnObject = {
        highestCount: -1,
        columnIndex: -1,
    };
    for (let i = 0; i < COLUMNS; i++) {
        const boardClone = JSON.parse(JSON.stringify(board));
        const firstEmptyRow = this.getFirstEmptyRow(i, boardClone);
        if (firstEmptyRow !== -1) {
            boardClone[firstEmptyRow][i] = player;
            const firstFilledRow = this.getFirstFilledRow(i, boardClone);
            if (firstFilledRow !== -1) {
                let count = 0;
                count = this.countUp(i, firstFilledRow, player, boardClone);
                if (count > returnObject.highestCount) {
                    returnObject.highestCount = count;
                    returnObject.columnIndex = i;
                }
                count = this.countRight(i, firstFilledRow, player, boardClone);
                if (count > returnObject.highestCount) {
                    returnObject.highestCount = count;
                    returnObject.columnIndex = i;
                }
                count = this.countUpRight(i, firstFilledRow, player, boardClone);
                if (count > returnObject.highestCount) {
                    returnObject.highestCount = count;
                    returnObject.columnIndex = i;
                }
                count = this.countDownRight(i, firstFilledRow, player, boardClone);
                if (count > returnObject.highestCount) {
                    returnObject.highestCount = count;
                    returnObject.columnIndex = i;
                }
            }
        }
    }
    return returnObject;
},
getRandomColumn(board) {
    while (true) {
        const boardClone = JSON.parse(JSON.stringify(board));
        const randomColumnIndex = this.getRandomNumberBetween(0, COLUMNS - 1);
        const firstEmptyRow = this.getFirstEmptyRow(randomColumnIndex, boardClone);
        if (firstEmptyRow !== -1) {
            return randomColumnIndex;
        }
    }
},
getCentralColumn(board) {
    const boardClone = JSON.parse(JSON.stringify(board));
    const centralColumn = parseInt((COLUMNS - 1) / 2);
    if (this.getFirstEmptyRow(centralColumn, boardClone) !== -1) {

        return centralColumn;
    }
    return -1;
},

Lo que se hace es simular un movimiento en el tablero (clonando primero al original, para no modificarlo) y a partir del mismo saber:

  • Si hay una columna para que el CPU gane
  • Si hay una columna para que el humano gane, para tomarla
  • El mayor puntaje, para conocer en dónde colocar la pieza para tener más oportunidades de ganar

Poniendo todo junto

Conecta 4 – Preguntar si se desea jugar de nuevo

Finalmente el código completo de JavaScript queda como se ve a continuación (el proyecto completo lo dejaré al final del post).

No he detallado algunas funciones, pues me parece que son muy simples, por ejemplo, en donde se muestra al ganador o se pregunta el modo de juego usando Sweet Alert 2.

/*

  ____          _____               _ _           _       
 |  _ \        |  __ \             (_) |         | |      
 | |_) |_   _  | |__) |_ _ _ __ _____| |__  _   _| |_ ___ 
 |  _ <| | | | |  ___/ _` | '__|_  / | '_ \| | | | __/ _ \
 | |_) | |_| | | |  | (_| | |   / /| | |_) | |_| | ||  __/
 |____/ \__, | |_|   \__,_|_|  /___|_|_.__/ \__, |\__\___|
         __/ |                               __/ |        
        |___/                               |___/         
    
____________________________________
/ 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.
*/const COLUMNS = 7,
    ROWS = 6,
    EMPTY_SPACE = " ",
    PLAYER_1 = "o",
    PLAYER_2 = "x",
    PLAYER_CPU = PLAYER_2,
    CONNECT = 4; // <-- Change this and you can play connect 5, connect 3, connect 100 and so on!
new Vue({
    el: "#app",
    data: () => ({
        board: [],
        COLUMNS,
        ROWS,
        PLAYER_1,
        PLAYER_2,
        PLAYER_CPU,
        EMPTY_SPACE,
        currentPlayer: null,
        isCpuPlaying: true,
        canPlay: false,
    }),
    async mounted() {
        await Swal.fire(
            'Connect 4 game',
            'Brought to you by parzibyte - https://parzibyte.me',
            'info'
        );
        this.resetGame();
    },
    methods: {
        async resetGame() {
            await this.askUserGameMode();
            this.fillBoard();
            this.selectPlayer();
            this.makeCpuMove();
        },
        async askUserGameMode() {
            this.canPlay = false;
            const result = await Swal.fire({
                title: 'Choose game mode',
                text: "Do you want to play against another player or against CPU?",
                icon: 'question',
                showCancelButton: true,
                confirmButtonColor: '#fdbf9c',
                cancelButtonColor: '#4A42F3',
                cancelButtonText: 'Me Vs another player',
                confirmButtonText: 'Me Vs CPU'
            });
            this.canPlay = true;
            this.isCpuPlaying = !!result.value;
        },
        countUp(x, y, player, board) {
            let startY = (y - CONNECT >= 0) ? y - CONNECT + 1 : 0;
            let counter = 0;
            for (; startY <= y; startY++) {
                if (board[startY][x] === player) {
                    counter++;
                } else {
                    counter = 0;
                }
            }
            return counter;
        },
        countRight(x, y, player, board) {
            let endX = (x + CONNECT < COLUMNS) ? x + CONNECT - 1 : COLUMNS - 1;
            let counter = 0;
            for (; x <= endX; x++) {
                if (board[y][x] === player) {
                    counter++;
                } else {
                    counter = 0;
                }
            }
            return counter;
        },
        countUpRight(x, y, player, board) {
            let endX = (x + CONNECT < COLUMNS) ? x + CONNECT - 1 : COLUMNS - 1;
            let startY = (y - CONNECT >= 0) ? y - CONNECT + 1 : 0;
            let counter = 0;
            while (x <= endX && startY <= y) {
                if (board[y][x] === player) {
                    counter++;
                } else {
                    counter = 0;
                }
                x++;
                y--;
            }
            return counter;
        },
        countDownRight(x, y, player, board) {
            let endX = (x + CONNECT < COLUMNS) ? x + CONNECT - 1 : COLUMNS - 1;
            let endY = (y + CONNECT < ROWS) ? y + CONNECT - 1 : ROWS - 1;
            let counter = 0;
            while (x <= endX && y <= endY) {
                if (board[y][x] === player) {
                    counter++;
                } else {
                    counter = 0;
                }
                x++;
                y++;
            }
            return counter;
        },
        isWinner(player, board) {
            for (let y = 0; y < ROWS; y++) {
                for (let x = 0; x < COLUMNS; x++) {
                    let count = 0;
                    count = this.countUp(x, y, player, board);
                    if (count >= CONNECT) return true;
                    count = this.countRight(x, y, player, board);
                    if (count >= CONNECT) return true;
                    count = this.countUpRight(x, y, player, board);
                    if (count >= CONNECT) return true;
                    count = this.countDownRight(x, y, player, board);
                    if (count >= CONNECT) return true;
                }
            }
            return false;
        },
        isTie(board) {
            for (let y = 0; y < ROWS; y++) {
                for (let x = 0; x < COLUMNS; x++) {
                    const currentCell = board[y][x];
                    if (currentCell === EMPTY_SPACE) {
                        return false;
                    }
                }
            }
            return true;
        },
        getRandomNumberBetween(min, max) {
            return Math.floor(Math.random() * (max - min + 1)) + min;
        },
        selectPlayer() {
            if (this.getRandomNumberBetween(0, 1) === 0) {
                this.currentPlayer = PLAYER_1;
            } else {
                this.currentPlayer = PLAYER_2;
            }
        },
        togglePlayer() {
            this.currentPlayer = this.getAdversary(this.currentPlayer);
        },
        getAdversary(player) {
            if (player === PLAYER_1) {
                return PLAYER_2;
            } else {
                return PLAYER_1;
            }
        },
        fillBoard() {
            this.board = [];
            for (let i = 0; i < ROWS; i++) {
                this.board.push([]);
                for (let j = 0; j < COLUMNS; j++) {
                    this.board[i].push(EMPTY_SPACE);
                }
            }
        },
        cellImage(cell) {
            if (cell === this.PLAYER_1) {
                return "img/player1.png";
            } else if (cell === this.PLAYER_2) {
                return "img/player2.png";
            } else {
                return "img/empty.png"
            }
        },
        async makeMove(columnNumber) {
            const columnIndex = columnNumber - 1;
            const firstEmptyRow = this.getFirstEmptyRow(columnIndex, this.board);
            if (firstEmptyRow === -1) {
                Swal.fire('Cannot put here, it is full');
                return;
            }
            Vue.set(this.board[firstEmptyRow], columnIndex, this.currentPlayer);
            const status = await this.checkGameStatus();
            if (!status) {
                this.togglePlayer();
                this.makeCpuMove();
            } else {
                this.askUserForAnotherMatch();
            }
        },
        // Returns true if there's a winner or a tie. False otherwise
        async checkGameStatus() {
            if (this.isWinner(this.currentPlayer, this.board)) {
                await this.showWinner();
                return true;
            } else if (this.isTie(this.board)) {
                await this.showTie();
                return true;
            }
            return false;
        },
        async askUserForAnotherMatch() {
            this.canPlay = false;
            const result = await Swal.fire({
                title: 'Play again?',
                text: "Do you want to play again?",
                icon: 'question',
                showCancelButton: true,
                confirmButtonColor: '#fdbf9c',
                cancelButtonColor: '#4A42F3',
                cancelButtonText: 'No',
                confirmButtonText: 'Yes'
            });
            if (result.value) {
                this.resetGame();
            }
        },
        async makeCpuMove() {
            if (!this.isCpuPlaying || this.currentPlayer !== PLAYER_CPU) {
                return;
            }
            const bestColumn = this.getBestColumnForCpu();
            const firstEmptyRow = this.getFirstEmptyRow(bestColumn, this.board);
            console.log({ firstEmptyRow });
            Vue.set(this.board[firstEmptyRow], bestColumn, this.currentPlayer);
            const status = await this.checkGameStatus();
            if (!status) {
                this.togglePlayer();
            } else {
                this.askUserForAnotherMatch();
            }
        },
        getBestColumnForCpu() {
            const winnerColumn = this.getWinnerColumn(this.board, this.currentPlayer);
            if (winnerColumn !== -1) {
                console.log("Cpu chooses winner column");
                return winnerColumn;
            }
            // Check if adversary wins in the next move, if so, we take it
            const adversary = this.getAdversary(this.currentPlayer);

            const winnerColumnForAdversary = this.getWinnerColumn(this.board, adversary);
            if (winnerColumnForAdversary !== -1) {
                console.log("Cpu chooses take adversary's victory");
                return winnerColumnForAdversary;
            }
            const cpuStats = this.getColumnWithHighestScore(this.currentPlayer, this.board);
            const adversaryStats = this.getColumnWithHighestScore(adversary, this.board);
            console.log({ adversaryStats });
            console.log({ cpuStats });
            if (adversaryStats.highestCount > cpuStats.highestCount) {
                console.log("CPU chooses take adversary highest score");
                // We take the adversary's best move if it is higher than CPU's
                return adversaryStats.columnIndex;
            } else if (cpuStats.highestCount > 1) {
                console.log("CPU chooses highest count");
                return cpuStats.columnIndex;
            }
            const centralColumn = this.getCentralColumn(this.board);
            if (centralColumn !== -1) {
                console.log("CPU Chooses central column");
                return centralColumn;
            }
            // Finally we return a random column
            console.log("CPU chooses random column");
            return this.getRandomColumn(this.board);

        },
        getWinnerColumn(board, player) {
            for (let i = 0; i < COLUMNS; i++) {
                const boardClone = JSON.parse(JSON.stringify(board));
                const firstEmptyRow = this.getFirstEmptyRow(i, boardClone);
                //Proceed only if row is ok
                if (firstEmptyRow !== -1) {
                    boardClone[firstEmptyRow][i] = player;

                    // If this is winner, return the column
                    if (this.isWinner(player, boardClone)) {
                        return i;
                    }
                }
            }
            return -1;
        },
        getColumnWithHighestScore(player, board) {
            const returnObject = {
                highestCount: -1,
                columnIndex: -1,
            };
            for (let i = 0; i < COLUMNS; i++) {
                const boardClone = JSON.parse(JSON.stringify(board));
                const firstEmptyRow = this.getFirstEmptyRow(i, boardClone);
                if (firstEmptyRow !== -1) {
                    boardClone[firstEmptyRow][i] = player;
                    const firstFilledRow = this.getFirstFilledRow(i, boardClone);
                    if (firstFilledRow !== -1) {
                        let count = 0;
                        count = this.countUp(i, firstFilledRow, player, boardClone);
                        if (count > returnObject.highestCount) {
                            returnObject.highestCount = count;
                            returnObject.columnIndex = i;
                        }
                        count = this.countRight(i, firstFilledRow, player, boardClone);
                        if (count > returnObject.highestCount) {
                            returnObject.highestCount = count;
                            returnObject.columnIndex = i;
                        }
                        count = this.countUpRight(i, firstFilledRow, player, boardClone);
                        if (count > returnObject.highestCount) {
                            returnObject.highestCount = count;
                            returnObject.columnIndex = i;
                        }
                        count = this.countDownRight(i, firstFilledRow, player, boardClone);
                        if (count > returnObject.highestCount) {
                            returnObject.highestCount = count;
                            returnObject.columnIndex = i;
                        }
                    }
                }
            }
            return returnObject;
        },
        getRandomColumn(board) {
            while (true) {
                const boardClone = JSON.parse(JSON.stringify(board));
                const randomColumnIndex = this.getRandomNumberBetween(0, COLUMNS - 1);
                const firstEmptyRow = this.getFirstEmptyRow(randomColumnIndex, boardClone);
                if (firstEmptyRow !== -1) {
                    return randomColumnIndex;
                }
            }
        },
        getCentralColumn(board) {
            const boardClone = JSON.parse(JSON.stringify(board));
            const centralColumn = parseInt((COLUMNS - 1) / 2);
            if (this.getFirstEmptyRow(centralColumn, boardClone) !== -1) {

                return centralColumn;
            }
            return -1;
        },
        async showWinner() {
            if (this.currentPlayer === PLAYER_1) {
                await Swal.fire('Winner is player 1');
            } else {
                await Swal.fire('Winner is player 2');
            }
        },
        async showTie() {
            await Swal.fire('Tie');
        },
        getFirstFilledRow(columnIndex, board) {
            for (let i = ROWS - 1; i >= 0; i--) {
                if (board[i][columnIndex] !== EMPTY_SPACE) {
                    return i;
                }
            }
            return -1;
        },
        getFirstEmptyRow(columnIndex, board) {
            for (let i = ROWS - 1; i >= 0; i--) {
                if (board[i][columnIndex] === EMPTY_SPACE) {
                    return i;
                }
            }
            return -1;
        }
    }
});

Descargas y código fuente

Como varios de mis proyectos, este software es open source y gratuito. Puedes descargar el código del repositorio de GitHub.

Demostración de Conecta 4

Puedes ver un vídeo en el que se realiza una demostración, además de una explicación con palabras:

Finalmente, puedes probar la versión en línea.

Conclusión

Me emocionó bastante portar este juego a JavaScript con mi framework favorito: Vue. Recuerda que anteriormente ya hice este juego en lenguaje C.

Siempre me ha gustado cómo es que hice que el CPU elija la mejor columna y que, si bien no es invencible, sí que puede ganar algunas veces.

¿Quieres ver más videojuegos programados por mí? click aquí.

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

Imprimir ñ en impresora térmica

En este post te enseñaré a imprimir la letra ñ en una impresora térmica. Voy…

2 días hace

Tramitar acta de nacimiento en línea de manera instantánea

En este post te quiero compartir mi experiencia tramitando un acta de nacimiento de México…

3 días hace

Creador de credenciales web – Aplicación gratuita

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

2 semanas 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…

3 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…

3 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…

3 semanas hace

Esta web usa cookies.