Connect 4 AI - Ajuda com implementação Alpha Beta

1 resposta
B

Pessoal... segue meu código abaixo para inteligência artificial para o jogo Connect 4..

Ele está implementado para um minimax (já esta funcionando perfeitamente) só que agora quero acrescentar poda Alpha Beta... só que estou com dificuldades..

Alguém pode olhar o código e me ajudar na implementação?

Fico extremamente grato!

package Jogadores;

import java.util.*;

/**
 * Inteligência Artificial para um jogador de Connect 4.
 *
 * @author Bruno Prado e Edgar Cintho
 */
public class JogadorBEst implements Jogador {

    /**
     * Profundidade atual da árvore.
     */
    private static int recurDepth = 0;
    /**
     * Profundidade máxima que o algorítmo irá percorrer a árvore.
     */
    private static final int MAX_RECURDEPTH = 7;
    /**
     * Largura e Altura do tabuleiro.
     */
    private static int WIDTH, HEIGHT;
    /**
     * Tabuleiro do jogo.
     */
    private int[][] board;
    /**
     * Armazena a quantidade de peças que uma coluna já possui.
     */
    private int[] columnHeight;
    /**
     * Armazena a cor da minha peça.
     */
    private int me;

    /**
     * Construtor vazio chamado pelo gerenciador da competição.
     */
    public JogadorBEst() {
    }

    /**
     * Método chamado pelo gerenciador da competição.
     *
     * Nele é inicializado alguns trechos principais necessários para a busca da melhor jogada
     * e é, também, chamado o método MiniMax que irá realizar a busca.
     *
     * @param tabuleiro Tabuleiro do jogo no momento atual.
     * @param corDaMinhaBola Cor da minha peça
     * @return Coluna que deverá ser jogada a minha peça
     */
    public int jogada(int[][] tabuleiro, int corDaMinhaBola) {
        //Armazena nas variáveis globais qual o tamanho do tabuleiro (Largura e Altura)
        getBoardLength(tabuleiro);
        //Inicializa o vetor com a quantidade de peças que cada coluna já possui no momento.
        getColumnHeight(tabuleiro);

        /**
         * Armazena o tabuleiro de jogo, realizando uma cópia do tabuleiro original.
         * Isto evita que os cálculos alterem o tabuleiro original durante a execução da busca.
         * Além de auxiliar o algoritmo de minimax na colocação de peças com apenas um valor (o da coluna),
         * sem que este precise verificar qual linha irá utilizar, por este fato que o tabuleiro é rotacionado.
         */
        this.board = copyBoard(tabuleiro);
        //Armazena a cor da minha peça
        this.me = corDaMinhaBola;

        //Variável que irá armazenar a coluna que irei jogar na próxima rodada.
        int action = 0;

        //Variável que armazena o valor da melhor jogada.
        int value = 0;

        //Vetor com os movimentos encontrados.
        //Utilizado caso mais de um movimento possua o mesmo valor, assim iremos
        //embaralhar o vetor e sortear um dos movimentos.
        Vector moves = new Vector();

        //Inicializa um valor mínimo que será utilizado para armazenar os valores
        //encontrados, e comperá-los com o melhor valor já encontrado.
        int prevValue = -MAX_RECURDEPTH - 1;

        //Realiza uma busca para cada uma das sete possibilidades de jogo.
        for (int col = 0; col < WIDTH; col++) {
            //Verifica se nesta coluna pode ser colocada uma peça ou se ela já esta cheia.
            if (columnHeight[col] >= HEIGHT) {
                continue; //Caso esteja cheia, ignora a coluna e continua.
            }

            //Verifica o valor deste movimento, jogando a peça nesta coluna.
            value = minimax(this.me, col);

            //Se o valor encontrado for maior que o valor anterior.
            if (value > prevValue) {
                //Remove todos os elementos da lista de movimentos iguais.
                moves.removeAllElements();
                //E armzena este valor atual.
                prevValue = value;
            }

            //Se o valor encontrado for igual ao valor anterior.
            if (value == prevValue) {
                //Então armazena na lista mais um movimento com valor igual.
                moves.add(new Integer(col));
            }
        }

        //Verifica a quantidade de movimentos encontrados.
        //Isto evita, em caso do algoritmo não encontrar um valor de destaque,
        //que o jogo fique sempre repetitivo.
        if (moves.size() > 0) {
            //Embaralha os movimentos encontrados.
            Collections.shuffle(moves);
            //Pega o primeiro da lista, sortenado entre os valores armazenados.
            action = ((Integer) moves.get(0)).intValue();
        }

        //Retorna a coluna escolhida.
        return action;
    }

    /**
     * Armazena a Largura e a Altura do tabuleiro.
     *
     * @param board Tabuleiro a ser analizado.
     */
    private void getBoardLength(int[][] board) {
        WIDTH = board.length;
        HEIGHT = board[0].length;
    }

    /**
     * Armazena a quantidade de peças que uma coluna já possui.
     *
     * @param board Tabuleiro a ser analizado.
     */
    private void getColumnHeight(int[][] board) {
        //Inicializa o vetor de quantidade de peças por coluna com o tamanho correto.
        columnHeight = new int[WIDTH];

        //Analiza o mapa e armazena a quantidade correta.
        int aux = 0;
        for (int col = 0; col < WIDTH; col++) {
            for (int row = 0; row < HEIGHT; row++) {
                //Contabiliza os espaços em branco de cada coluna.
                if (board[row][col] == 0) {
                    aux++;
                }
            }
            //Pega a altura total e subtrai com a quantidade de espaços em branco encontrados
            //na coluna.
            columnHeight[col] = HEIGHT - aux;
            aux = 0;
        }
    }

    /**
     * Copia o tabuleiro realizando uma rotação no mesmo para a direita.
     *
     * Isto é feito pois facilida o algoritmo de minimax na construção dos sucessores de um
     * estado, sendo que este apenas utiliza o valor da coluna, sem precisar buscar pelo
     * valor da linha, evitando cálculos desnecessários e diminuindo o tempo de execução do
     * mesmo.
     *
     * @param board Tabuleiro a ser copiado e rotacionado.
     * @return A cópia do tabuleiro.
     */
    private int[][] copyBoard(int[][] board) {
        int[][] copia = new int[WIDTH][HEIGHT];

        int aux = 0;
        for (int row = WIDTH - 1; row >= 0; row--) {
            for (int col = 0; col < HEIGHT; col++) {
                //Rotaciona o tabuleiro para a direita.
                copia[col][aux] = board[row][col];
            }
            aux++;
        }
        return copia;
    }

    /**
     * Método MiniMax implementado de forma recursiva (evitando a criação de dois métodos
     * Max e Min, e o gasto de tempo na criação de sucessores antes do tempo necessário).
     *
     * Ele sempre retorna o valor máximo para o meu jogador e o valor mínimo para o
     * meu oponente.
     *
     * @param player Número do jogador a ser utilizado na jogada atual.
     * @param column Coluna a ser jogada.
     * @return Valor desta jogada.
     */
    int minimax(int player, int column) {
        //Verifica se nesta coluna é possível colocar uma peça
        if (columnHeight[column] >= HEIGHT) {
            //Retorna valor 0 caso não seja possível (Indica que é uma folha e este movimento tem valor nulo).
            return 0;
        }

        //Inicializa a variável que armazenará o valor do movimento.
        //Ele irá depender do valor de seus filhos.
        int value = 0;

        /**
         * Trecho no qual iremos simular a movimentação.
         */
        recurDepth++; //Aumenta a profuncidade atual.
        this.board[column][columnHeight[column]] = player; //Coloca o jogador.
        columnHeight[column]++; //Aumenta a quantidade de peças que tem na coluna.
        /**
         * Movimentação Simulada.
         */

        //Verifica se neste estado simulado houve uma vitória de um dos jogadores.
        int victory = fourInARow();
        //Se for o meu jogador define um valor máximo ao movimento subtraindo à profundidade atual.
        //(Isto irá fazer com que meu algorítimo sempre procure pela vitória mais rápida).
        //Senão, define um valor mínimo para o movimento somando à profundidade atual.
        //(Isto irá fazer com que meu algorítimo evite movimentos que torne a vitória do oponente
        //mais próxima).
        if (victory == this.me) {
            value = MAX_RECURDEPTH + 1 - recurDepth;
        } else if (victory == 3 - this.me) {
            value = -MAX_RECURDEPTH - 1 + recurDepth;
        }

        //Irá prosseguir na árvore se a profundidade ainda não atingiu a profundidade máxima definida ou
        //se o valor continua igual a zero, ou seja, nenhum jogador está vencendo neste estado.
        //(Isto realiza uma pequena poda, pois não precisa continuar a busca nestes casos).
        if (recurDepth < MAX_RECURDEPTH && value == 0) {
            //Define um valor alto como padrão para o estado.
            value = MAX_RECURDEPTH + 1;

            //Realiza o mesmo procedimento para os filhos deste estado.
            for (int col = 0; col < WIDTH; col++) {
                //Verifica se é possível colocar uma peça nesta coluna.
                //Isso verifica se nesta determinada coluna ele possui um filho ou não.
                if (columnHeight[col] >= HEIGHT) {
                    continue; //Caso não seja possível, simplesmente continua na busca de outros filhos.
                }

                //Chama o método novamente para este filho passando o próximo jogador e a coluna definida.
                //E armazena o valor encontrado referente a uma relação completa de todos seus filhos, netos.....
                int sucValue = minimax(3 - player, col);

                /* Após a análise de todos os filhos iremos verificar o valor de cada um até que
                 * chegue no primeiro nó */

                //Se for a folha, ou seja, nenhum valor foi definido ainda (ele está com o valor padrão).
                if (value == (MAX_RECURDEPTH + 1)) {
                    value = sucValue; //Então define o valor atual como o valor encontrado
                } else if (player == this.me) { //Caso o jogador atual deste estado seja o meu.
                    if (value > sucValue) { //Se o valor atualmente armazenado for maior que o valor dos meus filhos.
                        //Atualiza este valor pois este tem uma relação direta com o valor dos filhos
                        //Ja que o jogo continua, e o valor dos filhos é relevante.
                        value = sucValue;
                    }
                } else if (sucValue > value) { //Caso seja meu oponente e o valor dos filhos for maior que o valor atualmente armazenado
                    //Então modifica atualiza este valor pois como é meu oponente e os filhos tem um valor
                    //alto referente ao meu jogador, então este movimento é mais valioso para mim, portanto
                    //eu maximizo este valor para minimizar as chances dele.
                    value = sucValue;
                }
            }
        }

        /**
         * Trecho no qual iremos remover a simulação da movimentação.
         * Iremos voltar na árvore.
         */
        recurDepth--; //Diminui a profundidade atual
        columnHeight[column]--; //Diminui o valor de peças na coluna
        this.board[column][columnHeight[column]] = 0; //Remove a peça colocada.

        //Retorna o valor total deste movimento. (Calculado juntamente com o valor de todos seus filhos).
        return value;
    }

    /**
     * Verifica se algum dos jogadores vence o jogo neste estado atual.
     *
     * @return O número do jogador que vence no estado atual.
     */
    int fourInARow() {
        int num, player;

        for (int y = 0; y < HEIGHT; y++) {
            num = 0;
            player = 0;
            for (int x = 0; x < WIDTH; x++) {
                if (this.board[x][y] == player) {
                    num++;
                } else {
                    num = 1;
                    player = this.board[x][y];
                }
                if (num == 4 && player > 0) {
                    return player;
                }
            }
        }

        for (int x = 0; x < WIDTH; x++) {
            num = 0;
            player = 0;
            for (int y = 0; y < HEIGHT; y++) {
                if (this.board[x][y] == player) {
                    num++;
                } else {
                    num = 1;
                    player = this.board[x][y];
                }
                if (num == 4 && player > 0) {
                    return player;
                }
            }
        }

        for (int xStart = 0, yStart = 2; xStart < 4;) {
            num = 0;
            player = 0;
            for (int x = xStart, y = yStart; x < WIDTH && y < HEIGHT; x++, y++) {
                if (this.board[x][y] == player) {
                    num++;
                } else {
                    num = 1;
                    player = this.board[x][y];
                }
                if (num == 4 && player > 0) {
                    return player;
                }
            }
            if (yStart == 0) {
                xStart++;
            } else {
                yStart--;
            }
        }

        for (int xStart = 0, yStart = 3; xStart < 4;) {
            num = 0;
            player = 0;
            for (int x = xStart, y = yStart; x < WIDTH && y >= 0; x++, y--) {
                if (this.board[x][y] == player) {
                    num++;
                } else {
                    num = 1;
                    player = this.board[x][y];
                }
                if (num == 4 && player > 0) {
                    return player;
                }
            }
            if (yStart == HEIGHT - 1) {
                xStart++;
            } else {
                yStart++;
            }
        }

        //Neste estado atual nenhum dos jogadores obtém uma vitória.
        return 0;
    }

    /**
     * Retorna o nome do nosso jogador para o gerenciador da
     * competição.
     *
     * @return O nome do nosso jogador.
     */
    public String getNome() {
        return "Jogador BEst";
    }

    /**
     * Método utilizado para DEBUG, onde imprime o estado atual
     * do tabuleiro.
     *
     * OBS.: Copiado do Algoritmo JogadorMinMax do professor
     * Fabrício.
     *
     * @return String com a representação do tabuleiro.
     */
    public String printTabuleiro() {
        String resultado = "";
        for (int i = 0; i < 7; i++) {
            for (int j = 0; j < 7; j++) {
                resultado = resultado + " | " + this.board[i][j];
            }
            resultado = resultado + " | " + "\n";
        }
        return resultado;
    }
}

EDIT: Tentei acrescentar dessa forma.. mas não funcionou.. ferrou todo o algoritmo e só estou perdendo rs..

int minimax(int player, int column, int alpha, int beta) {

        if (columnHeight[column] >= HEIGHT) {
            return 0;
        }

        int value = 0;

        recurDepth++;
        this.board[column][columnHeight[column]] = player;
        columnHeight[column]++;

        if (fourInARow() > 0) {
            if (player == this.me) {
                value = MAX_RECURDEPTH + 1 - recurDepth;
            } else {
                value = -MAX_RECURDEPTH - 1 + recurDepth;
            }
        }

        if (recurDepth < MAX_RECURDEPTH && value == 0) {
            if (player == this.me) {
                value = alpha;
            } else {
                value = beta;
            }

            for (int col = 0; col < WIDTH; col++) {

                if (columnHeight[col] >= HEIGHT) {
                    continue;
                }

                if (player == this.me) {
                    value = Math.max(value, minimax(3 - player, col, alpha, beta));
                    alpha = value;

                } else {
                    value = Math.min(value, minimax(3 - player, col, alpha, beta));
                    beta = value;
                }

                if (alpha >= beta) {
                    recurDepth--;
                    columnHeight[column]--;
                    this.board[column][columnHeight[column]] = 0;
                    return value;
                }
            }
        }

        recurDepth--;
        columnHeight[column]--;
        this.board[column][columnHeight[column]] = 0;

        return value;
    }

1 Resposta

B

Por favor, alguem pode me ajudar?

A entrega do trabalho é para sábado… :confused:

Criado 2 de novembro de 2009
Ultima resposta 3 de nov. de 2009
Respostas 1
Participantes 1