1. O Jogo

Transcrição

1. O Jogo
Algoritmo
do Kalah
Romildo Martins da S Bezerra
Julho 2001
Índice
1. O Jogo .........................................................................................3
2. Mudanças para Implementação .......................................................3
3. O Algoritmo ..................................................................................4
3.1 MINIMAX..................................................................................4
3.2 Poda Alpha-Beta .......................................................................4
3.3 Estrutura Utilizada.....................................................................5
4. A linguagem .................................................................................5
5. Análises .......................................................................................5
5.1 Análise de Jogadas x Altura da Árvore no MINIMAX .......................5
5.2 Cálculo do tempo de resposta no MINIMAX...................................7
5.3 Análise da Poda Alpha-Beta ........................................................9
5.4 Discussão sobre a função utilidade ..............................................9
6. Anexos .......................................... Erro! Indicador não definido.
1. O Jogo
O objetivo do jogo é colocar pelo menos metade das pedras (18) em seu
Kalah. Uma jogada consiste em escolher um buraco não-vazio, tomar todas as
pedras na mão e distribuí-las, em sentido anti-horário. Na distribuição devem ser
incluídos os buracos do jogador e os do oponente, além do Kalah do jogador. Já
o Kalah do oponente não recebe pedras. O que acontece a seguir depende de
onde a última pedra é colocada:
•
Caso a distribuição termine no Kalah do jogador, este tem o direito de
jogar novamente. Por exemplo (é a vez do jogador superior e o buraco
escolhido está marcado pelo símbolo “*”)
*
(3 3 3 3 3 3)
0
0
(3 3 3 3 3 3)
•
(4 4 0 3 3 3)
1
0
(3 3 3 3 3 3)
Caso a distribuição termine em um buraco de um jogador que não
contenha nenhuma pedra, a última pedra e todas as pedras que se
encontrarem no buraco do oponente, diretamente em frente ao buraco
onde terminou a distribuição, são colocadas no Kalah do jogador. Por
exemplo (a vez é do jogador inferior)
(6 6 1 4 6 0)
1
4
(3 3 3 0 0 1)
*
(6 6 1 0 6 0)
1
9
(0 4 4 0 0 1)
Duas condições terminam o jogo:
•
se um dos jogadores obtiver mais da metade das peças em seu Kalah,
ele é o vencedor;
•
caso todos os buracos de um jogador fiquem vazios, o outro jogador
coloca todas as peças restantes em seu Kalah e a vitória é daquele que
tiver mais pedras no Kalah.
2. Mudanças para Implementação
Com o objetivo de tornar justa a medição do tempo de uma jogada, optei por
remover a condição de jogada adicional do jogo original.
Isto também torna possível visualizar árvores de busca de alturas maiores,
beneficiando a analise do Kalah.
3. O Algoritmo
Para a implementação e análise do Jogo do Kalah utilizei o MINIMAX e a Poda
Alpha-Beta.
3.1 MINIMAX
O MINIMAX é utilizado num jogo de dois jogadores (MIN e MAX). Um jogo pode
ser a definido como um problema de busca com os componentes abaixo:
•
Estado inicial - inclui o tabuleiro e o dono da próxima jogada
•
Um conjunto de operadores - define os movimentos legais do jogo
•
Um teste terminal - determina o término do jogo
•
A função utilidade - fornece um valor numérico à saída do jogo. Geralmente 1
para vitória, 0 para empate e -1 para derrota
Para decidir qual o melhor movimento, o algoritmo deve seguir as etapas:
•
Gerar toda árvore do jogo até os estados terminais
•
Aplicar a função utilidade aos nós terminais
•
Nos nós superiores, usar a função utilidade dos estados terminais propagando
os valores até o nó raiz
•
Quando a propagação destes valores chegar a um ponto onde seja a vez de
MAX jogar, este deverá optar por maximizar sua jogada
A complexidade deste algoritmo é O(bm) onde b é a quantidade de movimentos a
cada instante (no caso do Kalah 6) e m a altura da árvore.
Nota-se que este algoritmo é teoricamente perfeito em relação a qualidade das
jogadas, porém o custo associado degrada o desempenho do algoritmo fazendo com que
ele nào seja usado em jogos reais.
No Kalah, será utilizado a proposta feita por Shannon. O algoritmo cortará a
árvore de busca em um nível e aplicará a função heurística de avaliação, denominada de
função utilidade, às folhas da árvore.
Mas nos perguntamos : Que função heurística usar? No caso do Kalah está sendo
usada uma função que calcula a diferença entre os "Kalares". A escolha desta função
será discutida no capítulo 5.
3.2 Poda Alpha-Beta
Com o objetivo de recorrer a uma quantidade menor de nós de busca utilizamos o
algoritmo Poda Alpha-Beta. A Poda deve retornar o mesmo movimento que o MINIMAX,
mas sem levar em consideração todos os nós da árvore.
Para fazer isto, o algoritmo baseia-se no princípio de que se um jogador tem uma
melhor escolha num nó pai ou outro do mesmo nível do pai de n, este nó n nunca será
alcançado, logo podemos podá-lo.
A eficiência da Poda depende da ordem que seus filhos serão acessados. Mas em
todos os casos o custo ainda continua exponicial, O(bd/2).
3.3 Estrutura Utilizada
Foi utilizada uma estrutura denominada tab_struct que contém quatorze (14)
posições de números inteiros distribuídos da seguinte forma :
00
(13 12 11 10 09 08)
07
(01 02 03 04 05 06)
Para visualizar o código completo em linguagem C, vide Anexo 1.
4. A linguagem
A linguagem C foi escolhida para implementação do Kalah devido as seguintes
características :
•
Velocidade - O código produzido pelo compilador C tende a ser muito eficiente
chegando ater velocidade próxima ao seu equivalente assembly.
•
Suporte a programação modular - Com isso a quantidade de tempo exigida
para compilar/executar um programa grande ou que exija processamento é
menor.
•
Eficiência de memória - Os programas em C tendem a ser muito eficientes no
tratamento da memória. Certamente um item muito importante para o
algoritmo do Kalah usando Poda Alpha-Beta de altura variável.
A portabilidade também é uma característica importante, mas não foi colocada
aqui, pois foram usadas bibliotecas fora do padrão ANSI C. Com pequenas modificações o
algoritmo pode ser compilado em qualquer plataforma.
5. Análises
5.1 Análise de Jogadas x Altura da Árvore no MINIMAX
O algoritmo do Kalah mostrou-se bastante eficiente para árvores de altura maior
ou igual a seis (h >= 6). Com h=6 o tempo de resposta para essa altura é de 0,1099
segundos e com essa altura é possível vencer uma grande quantidade de jogadores.
Vamos analisar a tabela a seguir:
Jogada
Altura 1
Altura 2
Altura 3
Altura 4
Altura 5
Altura 6
Altura 7
6
2
4
5
6
1
4
3
1x1
3x1
8x2
9x4
10x6
12x7
13x7
16x12
0x1
6x1
7x4
11x5
12x6
13x7
15x7
16x8
0x1
1x1
9x2
12x4
13x5
13x6
16x6
18x7
0x1
1x1
9x2
12x4
13x5
13x6
16x6
18x7
0x1
1x1
9x2
12x4
13x5
13x6
16x6
18x7
0x1
1x1
9x2
12x4
13x5
13x6
16x6
18x7
0x1
1x1
9x2
12x4
13x5
13x6
16x6
18x7
Observa-se que neste caso, a partir da altura 3, as jogadas passam a convergir.
Com uma grande quantidade de testes, concluímos que em mais de 92% dos casos as
jogadas tendem a convergir a partir da altura 5. Podemos denominar este fenômeno de
convergência de jogadas.
Outro fenômeno interessante é verificado na tabela a seguir :
Jogada
Altura 1
Altura 2
Altura 3
Altura 4
Altura 5
Altura 6
Altura 7
5
2
1
3
4
5
6
2
1x1
3x5
5x6
6x13
7x15
9x16
11x18
12x18
0x1
6x2
9x3
10x4
11x5
11x6
11x7
13x8
0x1
1x6
2x6
3x13
4x16
4x17
9x18
9x19
0x1
1x6
2x6
3x13
4x16
4x17
9x18
9x19
0x1
6x2
9x3
10x4
11x5
11x6
12x7
19x9
0x1
6x2
9x3
10x4
11x5
11x6
12x7
19x9
0x1
6x2
9x3
10x4
11x5
11x6
12x7
19x9
Teoricamente a medida que aumentamos a altura da árvore de busca as jogadas
conduziriam a um melhor resultado do computador. Nesta tabela acima observa-se que
para as alturas 3 e 4, o caminho da árvore percorrido pelas jogadas não conduzem a um
melhor resultado, isso é denominado como anomalia da Poda. Podemos atribuir este
problema à duas causas:
•
Análise incorreta - Critica-se este modo de avaliação da altura da árvore x
eficiência do algoritmo, pois com cada altura diferente as jogadas tendem a
mudar, logo dificilmente um jogador entraria com a mesma sequ6encia de
dados para jogos com alturas diferentes, simplesmente pelo fato de que o
estado dos tabuleiros são também diferentes.
•
Falha da função de avaliação - A função criada retorna a diferença entre o
Kalah do computador e do jogador. Talvez exista uma função mais sofisticada
baseada na distribuição das pedras no tabuleiro e retorne um resultado mais
eficaz. Sinceramente não acredito muito nesta hipótese. A função de avaliação
será discutida em outro tópico deste capítulo.
Depois destas análises recomendo a execução do jogo com altura 7.
5.2 Cálculo do tempo de resposta no MINIMAX
Vencer o jogo é o mais importante. Entretanto devemos analisar o custo para tal.
Usamos as seguintes linhas de código para medir o tempo :
else {
// computador joga
start = clock();
tab
= kalah_arvore (tab, altura, 0);
end
= clock();
printf("\nMinha Jogada ");
exibe_tab(tab);
placar(tab);
printf("\nExecutado em %.4f segundos", (end-start)/(CLK_TCK));
}
A função clock retorna o número de pulsos no relógio do sistema desde o início da
execução do programa.
Ao subtrairmos end por start, calculamos a quantidade de pulsos para executar a
função kalah_arvore. E quando dividimos pela macro CLK_TCK o valor de retorno é em
segundos
Com o uso desse algoritmo acima, obtivemos a seguinte tabela abaixo usando um
Processador AMD K6-2 400MHz e 64Mb de RAM.
Altura
Tempo (s)
Razão*
1
2
3
4
5
6
7
8
9
10
11
12
0,0000
0,0000
0,0000
0,0000
0,0349
0,1099
1,0989
3,9011
39,9451
136,9780
1379,5604
4840,3846
3,1490
9,9991
3,5501
10,2395
3,4292
10,0715
3,5087
* Corresponde a quantidade de vezes que o tempo de uma altura n é maior que o de uma altura n-1
Observe que a razão 6-5, 8-7, 10-9 e 12-11 giram em torno de 3. Enquanto 7-6,
9-8 e 11-10 estão na faixa de 10. Com o uso do software matemático Maple 5.1 e os
conceitos de interpolação numérica, achamos uma função que representa esta razão e
montamos a tabela a seguir.
Altura
Tempo (s)
Razão
Aproximação*
Erro**
1
2
3
4
5
6
7
8
9
10
11
12
0,0000
0,0000
0,0000
0,0000
0,0349
0,1099
1,0989
3,9011
39,9451
136,9780
1379,5604
4840,3846
3,1490
9,9991
3,5501
10,2395
3,4292
10,0715
3,5087
3,4093
10,1034
3,4093
10,1034
3,4093
10,1034
3,4093
8,27%
1,04%
-3,97%
-1,33%
-0,58%
0,32%
-2,83%
* Corresponde à razão calculada por interpolação
** Indica o erro da nova razão calculada
Partindo do princípio de que teríamos memória infinita, podemos estimar os
futuros valores do tempo de resposta do algoritmo do Kalah.
Altura
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Novos Tempos
0,0000 s
0,0000 s
0,0000 s
0,0000 s
0,0349 s
0,1100 s
1,0990 s
3,9013 s
39,9454 s
136,9798 s
22,99 min
1,34 h
13,58 h
1,93 dias
19,50 dias
66,47 dias
1,84 anos
6,27 anos
63,38 anos
216,07 anos
2,18 milênios
Caso o Kalah fosse um jogo com limitação de tempo, bastava apenas criar uma
função que parasse de construir a árvore de busca no momento de passar a jogada, e
depois a função kalah_arvore retornava o melhor valor.
Ainda continuo recomendando a execução com altura 7.
Quando o aplicativo executava utilizando árvores maiores que nove (9) o seguinte
código era emitido pelo compilador.
---------------------------------------------------------------------------UNHANDLED EXCEPTION 0C at 00A7:2FC3 Error code: 00008934 CX=0000
SI = 0000 DI = 09C9 BP = 0F98 SP = 0F8E
CS = 00A7 Limit =FFFF
DS = 009F Limit =FFFF
ES = 00E7 Limit =FFFF
SS = 0087 Limit =0FFF
- 00A7:2FBB 00 00 00 00 00 00 00 0F 00 02 00 05 00 00 00 04
DX=0000
You may hit [T] to terminate, but you should reboot as soon as possible,
as the system is unstable ...
5.3 Análise da Poda Alpha-Beta
5.4 Discussão sobre a função utilidade
A função utilidade escolhida é a diferença entre os dois kalah. Como saber se esta
função é a melhor função?
Teoricamente ela seria a melhor função se conduzir o algoritmo para os mesmos
caminhos do MINIMAX. Como implementar a árvore inteira do jogo não foi possível,
nunca saberemos o quanto esta função utilidade é boa.
Nota-se que a função escolhida não representa a real situação do jogo por não
levar em consideração a distribuição das pedras no kalah. Dessa distribuição verificamos:
•
A importância de manter o maior número de pedras no seu lado, pois se o
oponente não tiver jogadas por falta de pedras, todas as pedras do seu lado,
irão para o seu kalah.
•
A importância de manter os primeiros buracos sempre ocupados, para que o
computador nunca consiga acabar sua distribuição em kalah seu vazio.
Como garantir que o oponente não vença adotando o critério de acabar a
distribuição das pedras em um buraco vazio no lado dele do tabuleiro e vença o jogo?
Temos que distribuir o peso dessas funções, mas como faremos isso?
Talvez nunca conseguiremos fazer uma função heurística imbatível, pois ela
deveria analisar todos os tipos de estratégia dos oponentes.