Exemplo n.º 1
0
        /// <summary>
        /// Calcula o valor da captura.
        /// </summary>
        /// <remarks>
        /// Calcula um valor baseado no valor da peça atacante e da peça atacada (vítima).
        /// Esta técnica é chamada MVV / LVA (most valuable victim, least valuable attacker).
        /// </remarks>
        /// <param name="movimento">Movimento de captura.</param>
        /// <returns>Valor da captura.</returns>
        private int ValorCaptura(Movimento movimento)
        {
            Debug.Assert(movimento.Captura());

            var tipo_peca_captura  = movimento.PecaCaptura.ParaTipo();
            var tipo_peca_atacante = movimento.Peca.ParaTipo();

            var valor = (int)tipo_peca_captura * 6 + 5 - (int)tipo_peca_atacante;

            if (movimento.Promocao())
            {
                valor -= 5;
            }

            return(valor);
        }
Exemplo n.º 2
0
        /// <summary>
        /// Retorna a lista ordenada de movimentos.
        /// </summary>
        /// <remarks>
        /// Ordem de classificação:
        /// 1. Movimento melhor vindo da tabela de transposição.
        /// 2. Capturas
        /// 3. Movimentos simples classificados pela tabela de valores.
        ///
        /// Falta aqui os movimentos matadores (killer moves). Quase todos os programas de xadrez
        /// usam esta técnica. Eu me pergunto se alguém poderia fazer isso e se faria o Enxadrista
        /// um pouco melhor.
        /// </remarks>
        /// <param name="cor">Cor do lado com movimentos a ordenar.</param>
        /// <param name="lista">List de movimentos.</param>
        /// <param name="melhor">Melhor movimento que será ordenado primeiro, normalmente da tabela de transposição</param>
        /// <returns>Lista ordenada de movimentos.</returns>
        public List <Movimento> Orderna(Cor cor, List <Movimento> lista, Movimento melhor)
        {
            foreach (var movimento in lista)
            {
                if (movimento.Equals(melhor))
                {
                    movimento.ValorOrdenacao = 100000000;
                    continue;
                }
                if (movimento.Captura())
                {
                    movimento.ValorOrdenacao = ValorCaptura(movimento) * 10000;
                    continue;
                }
                int indice_peca = IndicePeca(movimento.Peca);
                int indice_casa = Defs.Converte12x12Para8x8(movimento.IndiceDestino);
                movimento.ValorOrdenacao = Tabela[indice_peca][indice_casa];
            }

            return(lista.OrderByDescending(m => m.ValorOrdenacao).ToList());
        }
Exemplo n.º 3
0
        /// <summary>
        /// Inicia uma nova pesquisa para a posição atual no tabuleiro, e encontra o melhor
        /// movimento para o jogador na vez.
        /// O movimento encontrado será salvo em MelhorMovimento.
        /// </summary>
        /// <remarks>
        /// Este processe repete a pesquisa incrementando a profundidade a cada iteração.
        /// Sim, é exatamente como você leu. Vamos procurar com depth = 1, depois repetir com depth = 2,
        /// repetir com depth = 3 e assim por diante. Pode parecer ineficiente, mas cada iteração usará
        /// informações coletadas na iteração anterior e acelera a proxima iteração.
        /// Esta repetição será limitada por tempo ou profundidade. Então, verificaremos o fim da pesquisa
        /// durante o processo.
        /// Algo que pode melhorar aqui, é usar um controle de tempo melhor, onde você aloca mais ou menos
        /// tempo com base no estágio do jogo, ou estende se o movimento retornou uma pontuação ruim, etc.
        /// O gerenciamento de tempo é um capítulo a parte para motores de xadrez. É como um jogador de xadrez
        /// vai alocar mais tempo para avaliar movimentos importantes.
        /// Note que aqui recebemos o tempo limite já calculado, o gerenciamento de tempo deve ser feito
        /// antes de entrar na pesquisa, e pode ser ajustado de acordo com desenrolar da pesquisa.
        /// Outra técnica é a janela de aspiração, onde você pode começar com pequenas janelas e ampliar,
        /// conforme necessário, isso deve economizar tempo porque você não pesquisa as posições com valores
        /// fora da janela. A janela de pesquisa é definida pelo intervalo entre os valores de alfa e beta.
        /// </remarks>
        /// <param name="milisegundo_limite">Tempo limite para a pesquisa em milisegundos.</param>
        /// <param name="profundidade_limite">Profundidade limite para a pesquisa.</param>
        public void LoopAprofundamentoIterativo(int milisegundo_limite, int profundidade_limite)
        {
            Debug.Assert(milisegundo_limite >= 0);
            Debug.Assert(profundidade_limite > 0 && profundidade_limite <= Defs.PROFUNDIDADE_MAXIMA);

            ControleTempo.Restart();

            Transposicao.IncrementaGeracao();
            Ordenacao.Inicia();

            ContadorPosicoes   = 0;
            MilisegundosLimite = milisegundo_limite;
            ProfundidadeLimite = profundidade_limite;
            EncerraProcura     = false;
            MelhorMovimento    = null;

            var variacao_principal = new List <Movimento>();

            for (ProfundidadeAtual = 1; ProfundidadeAtual <= Defs.PROFUNDIDADE_MAXIMA; ProfundidadeAtual++)
            {
                AlfaBeta(Defs.VALOR_MINIMO, Defs.VALOR_MAXIMO, 0, ProfundidadeAtual, variacao_principal);
                if (EncerraProcura)
                {
                    break;
                }
                // Não inicia um novo loop se já usamos 60% do tempo disponível. É provável que não possamos terminar.
                if (ControleTempo.ElapsedMilliseconds > (int)(MilisegundosLimite * 0.60))
                {
                    break;
                }
                // Libera memoria aos poucos. Provavelmente esta parte pode ser melhorada.
                GC.Collect();
            }

            ControleTempo.Stop();

            MelhorMovimento = variacao_principal.Count > 0 ? variacao_principal[0] : null;
        }
Exemplo n.º 4
0
        /// <summary>
        /// Atualiza informação sempre que um bom movimento é encontrada.
        /// </summary>
        /// <remarks>
        /// Note que consideramos somente os movimentos simples. Captura já possui
        /// um valor maior do que movimentos simples.
        /// </remarks>
        /// <param name="cor">Cor do lado fazendo o movimento.</param>
        /// <param name="movimento">Movimento considerado bom.</param>
        /// <param name="profundidade">Profundidade que o movimento foi pesquisado.</param>
        public void AtualizaHistoria(Cor cor, Movimento movimento, int profundidade)
        {
            if (movimento.Tatico())
            {
                return;
            }

            int indice_peca = IndicePeca(movimento.Peca);
            int indice_casa = Defs.Converte12x12Para8x8(movimento.IndiceDestino);

            Tabela[indice_peca][indice_casa] += profundidade;

            // Se o valor na tabela estiver muito alto, é feito um ajuste a todos valores.
            if (Tabela[indice_peca][indice_casa] > 9000)
            {
                for (int peca = 0; peca < NUMERO_PECAS; peca++)
                {
                    for (int casa = 0; casa < NUMERO_CASAS; casa++)
                    {
                        Tabela[peca][casa] /= 8;
                    }
                }
            }
        }
Exemplo n.º 5
0
        /// <summary>
        /// Salva nova informação na tabela de transposição.
        /// </summary>
        /// <param name="chave">Chave Zobrist da posição atual.</param>
        /// <param name="profundidade">Profundidade da pesquisa.</param>
        /// <param name="valor">Valor a ser salvo.</param>
        /// <param name="nivel">Nivel atual para ajustar os valores de mate.</param>
        /// <param name="tipo">Tipo do valor as ser salvo.</param>
        /// <param name="melhor">Melhor movimento para a posição ou null.</param>
        public void Salva(ulong chave, int profundidade, int valor, int nivel, byte tipo, Movimento melhor)
        {
            Debug.Assert(profundidade >= 0 && profundidade <= Defs.PROFUNDIDADE_MAXIMA);
            Debug.Assert(valor >= Defs.VALOR_MINIMO && valor <= Defs.VALOR_MAXIMO);
            Debug.Assert(nivel >= 0 && nivel < Defs.NIVEL_MAXIMO);
            Debug.Assert(tipo == Transposicao.Tipo.INFERIOR || tipo == Transposicao.Tipo.EXATO || tipo == Transposicao.Tipo.SUPERIOR);

            // Define o índice para a localização da entrada.
            int indice_entrada = (int)(chave % NUMERO_ENTRADAS);

            Debug.Assert(indice_entrada >= 0 && indice_entrada < Transposicao.NUMERO_ENTRADAS);

            var entrada = Tabela[indice_entrada];

            // Tenta achar um registro existente para esta posição.
            var registro = entrada.FirstOrDefault(r => r.Chave == chave);

            // Evita perder os melhores movimentos que estão na tabela.
            if (registro != null && melhor == null)
            {
                melhor = registro.Movimento;
            }

            // Se é uma posição nova, então tenta substituir o registro mais antigo, com a menor profundidade.
            if (registro == null)
            {
                registro = entrada.OrderBy(r => r.Geracao).ThenBy(r => r.Profundidade).First();
            }

            // Salva os dados.
            registro.Chave        = chave;
            registro.Geracao      = Geracao;
            registro.Profundidade = profundidade;
            registro.Tipo         = tipo;
            registro.Movimento    = melhor;
            registro.Valor        = Transposicao.AjustaValorParaTabela(valor, nivel);
        }
Exemplo n.º 6
0
        /// <summary>
        /// Atualiza variação principal. Copia uma lista de movimentos.
        /// </summary>
        /// <param name="destino">Lista de movimentos destino.</param>
        /// <param name="origem">Lista de movimentos origem.</param>
        /// <param name="movimento">Movimento a ser inserido na variação principal.</param>
        private void AtualizaVariacaoPrincipal(List <Movimento> destino, List <Movimento> origem, Movimento movimento)
        {
            Debug.Assert(destino != null);
            Debug.Assert(origem != null);
            Debug.Assert(movimento != null);

            destino.Clear();
            destino.Add(movimento);
            foreach (var novo_movimento in origem)
            {
                destino.Add(novo_movimento);
            }
        }
Exemplo n.º 7
0
        /// <summary>
        /// Executa a pesquisa alfa beta.
        /// </summary>
        /// <remarks>
        /// Essa função deve ser a mais executada para o motor de xadrez. A maior parte da diversão
        /// acontece aqui, o que significa que há muitas técnicas que podem ser tentadas.
        /// Vamos comentar as técnicas diretamente no código abaixo, mas a idéia geral é passar mais
        /// tempo procurando movimentos promissores e passar menos tempo buscando possíveis movimentos
        /// ruins.
        /// A busca às vezes descarta alguns movimentos, e às vezes estende outros. Por exemplo, estender
        /// a busca por um movimento de xeque é geralmente bom. Evitar pesquisar movimentos no final da
        /// lista de movimentos também é geralmente bom.
        /// Há sempre algumas idéias que podem ser tentadas, quando você no código de outros motores você
        /// pode obter alguma inspiração para suas próprias idéias e tentar no seu programa. A maioria das
        /// ideias em outros motores tem que ser adaptada ao seu programa, às vezes eles simplesmente não
        /// funcionam, porque seu programa possui uma estrutura diferente.
        /// E um ponto importante, que é considerado cortesia, quando você menciona de onde você teve a
        /// idéia / inspiração.
        /// </remarks>
        /// <param name="alfa">Limite inferior da pesquisa.</param>
        /// <param name="beta">Limite superior da pesquisa.</param>
        /// <param name="nivel">Distância da posição inicial (conhecido como ply). Aumentada a cada chamada.</param>
        /// <param name="profundidade">Profundidade da pesquisa (depth), número de movimentos para olhar a frente. Diminui a cada chamada.</param>
        /// <param name="variacao_principal">Lista dos melhores movimentos localizados durante a pesquisa.</param>
        /// <returns>Melhor valor encontrado para a posição.</returns>
        public int AlfaBeta(int alfa, int beta, int nivel, int profundidade, List <Movimento> variacao_principal)
        {
            Debug.Assert(alfa >= Defs.VALOR_MINIMO);
            Debug.Assert(beta <= Defs.VALOR_MAXIMO);
            Debug.Assert(beta > alfa);
            Debug.Assert(nivel >= 0 && nivel <= Defs.NIVEL_MAXIMO);
            Debug.Assert(profundidade >= 0 && profundidade <= Defs.PROFUNDIDADE_MAXIMA);
            Debug.Assert(variacao_principal != null);

            // Final da pesquisa por causa do tempo ou profundidade ?
            VerificaTerminoProcura();
            if (EncerraProcura)
            {
                return(0);
            }

            // Programação defensiva para evitar erros de índice fora da faixa da tabela.
            if (nivel > Defs.NIVEL_MAXIMO - 1)
            {
                return(Avaliacao.ObtemPontuacao());
            }

            // Chegamos a uma posição que é empate por causa das regras do xadrez.
            if (nivel > 0 && Tabuleiro.EmpatePorRegra50())
            {
                return(0);
            }
            if (nivel > 0 && Tabuleiro.EmpatePorRepeticao())
            {
                return(0);
            }

            // Ao chegar ao final da pesquisa, vai para a pesquisa quiescente para obter o resultado final.
            if (profundidade <= 0)
            {
                return(Quiescente(alfa, beta, nivel, variacao_principal));
            }

            // Contador de posição. Apenas para fins informativos, para que você veja o quão rápido é o seu motor.
            ContadorPosicoes++;

            // Prepara lista the movimentos encontrados.
            if (nivel > 0)
            {
                variacao_principal.Clear();
            }


            // Accesso a informações da tabela de transposição (Transposition Table Probe).
            // Nota: alguns programas usam Hash Table ao inves the Transposition Table.
            // Se esta posição foi visitada antes, a pontuação pode estar na tabela de transposição,
            // podemos reutilizá-la e retornar daqui. Esta é uma grande economia.
            // Esta informação será salva durante a pesquisa, quando tivermos a informação sobre a pontuação.
            // É importante usar o valor somente se a profundidade na tabela for maior ou igual à profundidade atual.
            // Se não podemos usar a pontuação, podemos usar o melhor movimento, que pode causar um corte beta e
            // também economizar tempo. Lembre-se de que isso ajuda na ordenação do movimentos.
            Movimento movimento_transposicao = null;
            var       registro = Transposicao.Recupera(Tabuleiro.Chave, profundidade);

            if (registro != null)
            {
                if (registro.PodeUsarValor(alfa, beta))
                {
                    return(Transposicao.AjustaValorParaProcura(registro.Valor, nivel));
                }
                movimento_transposicao = registro.Movimento;
            }

            // Preparação de informações para a pesquisa.
            var cor_jogar_esta_em_cheque = Tabuleiro.CorJogarEstaEmCheque();
            var valor_avaliacao          = Avaliacao.ObtemPontuacao();
            var nova_variacao_principal  = new List <Movimento>();

            // "Passar a navalha" (Razoring).
            // Talvez a tradução de razoring não seja boa, mas aqui vamos tentar remover posições
            // que não são muito promissoras, com uma busca reduzida. Vamos tentar passar a navalha nessas posições !
            // O valor de avaliação mais um valor estimado já é menor do que o alfa, então, se a busca reduzida
            // confirmar que não há uma boa captura, podemos descartar essa posição e ignorar a pesquisa.
            if (profundidade <= 3 && !cor_jogar_esta_em_cheque && valor_avaliacao + 150 * profundidade < alfa)
            {
                int alfa_reduzido = alfa - 150 * profundidade;
                int valor         = Quiescente(alfa_reduzido, alfa_reduzido + 1, nivel, nova_variacao_principal);
                if (EncerraProcura)
                {
                    return(0);
                }
                if (valor <= alfa_reduzido)
                {
                    return(valor);
                }
            }

            // Este é o famoso movimento nulo (movimento nulo). Praticamente todos os motores de xadrez o implementam.
            // A idéia é que você está em uma posição que é tão boa (valor maior do que beta) que mesmo se você passar
            // o direito de mover para o outro jogador, dando-lhe dois movimentos em sequência, ele não será capaz de recuperar.
            // Então, você pode descartar essa pesquisa. É um bom ganho.
            // Existem algumas boas práticas para movimentos nulos:
            // - Não faça dois movimentos nulos em seqüência.
            // - Não pode estar no xeque, pois pode gerar posições ilegais.
            // - É bom que você tenha algumas peças.
            // - Também não é recomendado para pesquisa de variação principal.
            // O movimento nulo deve ser confirmado com uma busca reduzida.
            // O valor obtido pode ser salvo na tabela de transposição.
            if (profundidade > 3 && !cor_jogar_esta_em_cheque && alfa == beta - 1 && valor_avaliacao >= beta)
            {
                if (!Tabuleiro.MovimentoAnteriorFoiNulo() && Tabuleiro.TemPecas(Tabuleiro.CorJogar))
                {
                    Tabuleiro.FazMovimentoNulo();
                    int valor = -AlfaBeta(-beta, -beta + 1, nivel + 1, profundidade - 3, nova_variacao_principal);
                    Tabuleiro.DesfazMovimentoNulo();
                    if (EncerraProcura)
                    {
                        return(0);
                    }
                    if (valor >= beta)
                    {
                        if (valor > Defs.AVALIACAO_MAXIMA)
                        {
                            valor = beta;                                // // Valor de mate, não muito confiável neste caso.
                        }
                        Transposicao.Salva(Tabuleiro.Chave, profundidade, valor, nivel, Transposicao.Tipo.INFERIOR, null);
                        return(valor);
                    }
                }
            }

            // Prepara a próxima profundidade. Se estiver em xeque, estendemos a pesquisa.
            // Esta é uma extensão de xeque simples, pode ser implementada em diferentes formas.
            int nova_profundidade = profundidade - 1;

            if (cor_jogar_esta_em_cheque)
            {
                nova_profundidade += 1;
            }

            // Mais itens de controle de pesquisa.
            int       melhor_valor        = Defs.VALOR_MINIMO;
            int       contador_movimentos = 0;
            Movimento melhor_movimento    = null;

            // Gera e classifica a lista de movimentos. Usa o movimento da tabela de transposição, se disponível.
            var lista = Tabuleiro.GeraMovimentos();

            lista = Ordenacao.Orderna(Tabuleiro.CorJogar, lista, movimento_transposicao);

            // Loop dos movimentos.
            foreach (var movimento in lista)
            {
                Tabuleiro.FazMovimento(movimento);
                if (!Tabuleiro.MovimentoFeitoLegal())
                {
                    Tabuleiro.DesfazMovimento();
                    continue;
                }
                Debug.Assert(Tabuleiro.Chave == Zobrist.ObtemChave(Tabuleiro));

                contador_movimentos += 1;

                // Início da pesquisa recursiva. O objectivo é obter o valor para este movimento nesta profundidade.
                int valor_procura = 0;
                if (melhor_valor == Defs.VALOR_MINIMO)
                {
                    // O primeiro movimento será pesquisado com o tamanho da janela inteira, ou seja, os valores alfa / beta invertidos.
                    valor_procura = -AlfaBeta(-beta, -alfa, nivel + 1, nova_profundidade, nova_variacao_principal);
                    // Os movimentos seguintes são pesquisados com uma pesquisa de janela zero. Em vez de usar -beta, -alfa para a
                    // janela de pesquisa, usaremos -alfa-1, -alfa. Isso deve descartar mais movimentos porque os valores
                    // alfa / beta da próxima iteração serão próximos. Mas antes podemos aplicar algumas técnicas para reduzir a
                    // quantidade de movimentos pesquisados. Veja abaixo na parte do "else".
                }
                else
                {
                    // Poda de futilidade - Futility Pruning.
                    // Se o valor da avaliação mais um valor estimado for inferior ao valor alfa, podemos ignorar esse movimento.
                    // Mas temos que considerar algumas restrições como abaixo. Existem muitas maneiras diferentes de implementar
                    // esta técnica, mais uma vez você pode ajustar de acordo com sua preferência e se ela funcionar para o seu motor.
                    if (!cor_jogar_esta_em_cheque && nova_profundidade == 1 && !movimento.Tatico() && alfa == beta - 1 && valor_avaliacao + 100 < alfa)
                    {
                        Tabuleiro.DesfazMovimento();
                        continue;
                    }

                    // Redução de movimento tardios - Late move reduction ou LMR.
                    // Para movimentos que estão mais próximos do fim da lista, podemos reduzir a profundidade em que são pesquisados.
                    // Esta técnica também pode ser adaptada de muitas formas diferentes.
                    int reducao = 0;
                    if (!cor_jogar_esta_em_cheque && nova_profundidade > 1 && contador_movimentos > 4 && !movimento.Tatico() && alfa == beta - 1 && valor_avaliacao < alfa)
                    {
                        reducao = 1;
                    }

                    // Outras técnicas de conhecimento podem ser aplicadas aqui. E talvez você possa criar uma nova técnica!

                    // Executa a pesquisa de janela zero.
                    valor_procura = -AlfaBeta(-alfa - 1, -alfa, nivel + 1, nova_profundidade - reducao, nova_variacao_principal);

                    // Quando a busca foi reduzida e o valor retornado é maior do que o alfa, significa que podemos ter um bom
                    // movimento, e temos que pesquisar sem a redução para confirmar. Esperava-se que não tivesse um bom movimento
                    // neste caso.
                    if (!EncerraProcura && valor_procura > alfa && reducao != 0)
                    {
                        valor_procura = -AlfaBeta(-alfa - 1, -alfa, nivel + 1, nova_profundidade, nova_variacao_principal);
                    }

                    // Este é outro caso de re-pesquisa, depois de pesquisar com janela zero. Esperava-se não encontrar bons movimentos
                    // neste caso. Se o valor retornado for maior que o alfa, significa que devemos pesquisar novamente com a janela
                    // completa, para confirmar o movimento bom.
                    if (!EncerraProcura && valor_procura > alfa && valor_procura < beta)
                    {
                        valor_procura = -AlfaBeta(-beta, -alfa, nivel + 1, nova_profundidade, nova_variacao_principal);
                    }
                    // Nota: Pode parecer que o motor pode pesquisar a mesma posição duas vezes, mas se você seguir a lógica acima,
                    // será uma ou outra.
                }

                Tabuleiro.DesfazMovimento();
                if (EncerraProcura)
                {
                    return(0);
                }
                Debug.Assert(Tabuleiro.Chave == Zobrist.ObtemChave(Tabuleiro));

                // Agora que temos o valor para este movimento nesta posição, podemos comparar a alfa e beta tomar decisões.
                // Se o valor for maior que o beta, temos um corte beta. Isso significa que não precisamos pesquisar o resto dos
                // movimentos. O objetivo é obter muitos cortes beta, especialmente no primeiro movimento.
                // É importante atualizar o histórico de movimentos e a tabela de transposição, então quando pesquisamos essa
                // posição novamente, possivelmente também podemos ter outro corte beta.
                if (valor_procura >= beta)
                {
                    Ordenacao.AtualizaHistoria(Tabuleiro.CorJogar, movimento, profundidade);
                    Transposicao.Salva(Tabuleiro.Chave, profundidade, valor_procura, nivel, Transposicao.Tipo.INFERIOR, movimento);
                    return(valor_procura);
                }

                // Se não tivermos um corte beta, temos que gravar o melhor resultado até agora, e aumentar o valor da alfa. Isso significa
                // que temos uma boa jogada para o cargo, e podemos atualizar a variação principal.
                if (valor_procura > melhor_valor)
                {
                    melhor_valor = valor_procura;
                    if (valor_procura > alfa)
                    {
                        alfa             = valor_procura;
                        melhor_movimento = movimento;
                        AtualizaVariacaoPrincipal(variacao_principal, nova_variacao_principal, movimento);
                        if (nivel == 0)
                        {
                            ImprimeVariacaoPrincipal(valor_procura, profundidade, variacao_principal);
                        }
                    }
                }
            }

            // Acabamos de pesquisar todos movimentos. Precisamos fazer mais alguns passos.

            // Se todos os movimentos forem ilegais, significa que não podemos mover nessa posição.
            // Se o nosso rei estiver em xeque, então é xeque mate, esta posição está perdida, e nós retornamos uma pontuação de xeque.
            // Se o nosso rei não estiver em xeque, então é empate. Não podemos fazer um movimento sem colocar o rei no xeque.
            if (melhor_valor == Defs.VALOR_MINIMO)
            {
                return(cor_jogar_esta_em_cheque ? -Defs.VALOR_MATE + nivel : 0);
            }

            // Aqui vamos atualizar o histórico de movimentos e a tabela de transposição de acordo com o escore.
            // Podemos encontrar uma bom movimento, ou nenhum movimento foi capaz de melhorar alfa.
            if (melhor_movimento != null)
            {
                Ordenacao.AtualizaHistoria(Tabuleiro.CorJogar, melhor_movimento, profundidade);
                Transposicao.Salva(Tabuleiro.Chave, profundidade, melhor_valor, nivel, Transposicao.Tipo.EXATO, melhor_movimento);
            }
            else
            {
                Transposicao.Salva(Tabuleiro.Chave, profundidade, melhor_valor, nivel, Transposicao.Tipo.SUPERIOR, null);
            }

            // Retorna o valor da posição.
            return(melhor_valor);
        }