Vous êtes sur la page 1sur 14

Relatório de Avaliação Intercalar do primeiro trabalho prático da

cadeira de Programação em Lógica

Trabalho escolhido: Luta de Cavalos

Elementos do Grupo:

Carlos Eduardo Mesquita Frias: carlos.frias@fe.up.pt

Nuno Ricardo Mesquita Pereira da Mota: ee05154@fe.up.pt

Programação em Lógica Página 1


Resumo:
Com este trabalho pretende-se implementar um jogo de tabuleiro, Luta de Cavalos, em
Prolog. O jogo irá ter diversos níveis de dificuldade, bem como diferentes modos de
jogo, nomeadamente, Humano vs Humano, Humano vs Computador e Computador vs
Computador. Posteriormente, o jogo será implementado num ambiente tri-
dimensional.

1. Introdução:
Os principais objectivos do trabalho são os seguintes: familiarização com a linguagem
Prolog e os paradigmas de programação em lógica; desenvolvimento de um jogo muito
interessante e a possibilidade de conseguir trabalhar já um pouco com inteligência
artificial, que é algo muito motivador.

Efectuamos a escolha deste jogo pois achamos que, não tendo um elevado grau de
dificuldade, é desafiante tanto no âmbito do jogador humano como para o
programador que terá de desenvolver o programa de modo que o computador
“pense” e efectue a cada momento a melhor jogada possível.

Pensamos que tratando-se uma linguagem não procedimental, e nunca por nós
utilizada até ao momento, e o grau de “inteligência” que terá de ser fornecida ao
computador, nos trará motivação extra para a implementação deste jogo.

Programação em Lógica Página 2


2. Descrição do Problema:
O Jogo a desenvolver, Luta de Cavalos, foi inventado por Andy Lewicki e é um jogo que
combina estratégia e gestão de território com a contagem de pontos.

Como se desenrola o jogo?

Num tabuleiro quadrangular com cem casas são colocados, aleatoriamente, os


números inteiros entre 0 e 99. A Figura 1 representa um estado inicial possível para o
jogo:

Figura 1: Estado inicial do jogo.

O jogo envolve dois jogadores que utilizam como única peça de jogo um cavalo de
Xadrez. Os movimentos dessa peça serão idênticos aos movimentos num tabuleiro de
Xadrez, isto é, cada cavalo movimenta-se em L, duas casas para direita e uma casa para
baixo, por exemplo. Os objectivos do jogo são, fundamentalmente, dois: acumular
mais pontos que o adversário e ir prejudicando ou limitando as movimentações
possíveis do adversário.

Como se acumula os pontos?

A primeira jogada consiste em colocar o cavalo branco numa das casas da primeira
linha, isto é, numa das casas entre A1 e J1. O número da casa escolhida ficará a ser a
pontuação inicial do cavalo branco. Se esse número for um número com dois
algarismos diferentes, por exemplo 35, então o número “trocado”, isto é, o número
que resulta da troca dos dois algarismos, neste caso o 53 é retirado do tabuleiro,
ficando essa casa inacessível a cada um dos dois cavalos durante o resto do jogo. Se o
número da casa escolhida pelo cavalo for um número “duplo”, isto é, com dois
algarismos iguais, por exemplo 44, então esse jogador terá a possibilidade de retirar, à

Programação em Lógica Página 3


sua escolha, outro qualquer número duplo que ainda se encontre disponível no
tabuleiro. Essa posição ficará também inacessível a cada um dos cavalos durante o
restante jogo.

Figura 2: Estado do tabuleiro após colocação do cavalo branco.

À primeira jogada do cavalo branco segue-se a primeira jogada do cavalo preto que irá
ser colocado numa das casas da última linha do tabuleiro, isto é, numa das casas entre
A10 e J10. O número da casa será a pontuação inicial do cavalo preto e a casa
correspondente ao número trocado será removida do tabuleiro e a posição ficará
inacessível durante o resto do jogo.

Figura 3: Estado do tabuleiro após colocação do cavalo preto.

Como se efectuam os movimentos das peças?

Após a colocação das duas peças, as jogadas seguintes serão efectuadas através do
típico saldo de cavalo, utilizado no Xadrez tradicional.

Programação em Lógica Página 4


Os números correspondentes às casas pelas quais os dois cavalos vão passando vão
sendo acumulados às pontuações do respectivo cavalo. As posições já percorridas e as
posições dos respectivos números trocados irão sendo removidas e tornadas
inacessíveis no resto do jogo.

Figura 4: Estado do tabuleiro após quatro jogadas.

Obviamente, um cavalo nunca poderá saltar para uma casa que esteja inacessível nem
para a casa onde se encontre o cavalo adversário.

Como termina o jogo?

O jogo termina quando um dos jogadores estiver impedido de movimento, isto é,


quando o seu cavalo não poder saltar para outra casa por estar inacessível e/ou por lá
estar o cavalo adversário.
Neste momento é determinado o vencedor do jogo que será aquele com máxima
pontuação. Se ambos os jogadores tiverem as mesmas pontuações o jogo termina
empatado.

3. Representação do Estado do Jogo:

Como o jogo se desenrola num tabuleiro 10X10 entendemos representar os valores


para cada posição do tabuleiro numa estrutura de dados do tipo Lista. Como cada linha
do tabuleiro tem dez elementos é relativamente fácil trabalhar as posições como
sendo as posições de uma lista com cem elementos. Poderíamos ter utilizado ao invés
de uma lista com cem elementos, uma lista com dez sub-listas com dez elementos

Programação em Lógica Página 5


cada, representando cada sub-lista, cada uma das linhas do tabuleiro. O grau de
dificuldade na utilização destas duas estruturas é praticamente o mesmo1.

O tabuleiro inicial é construído da seguinte forma: é criada uma lista com os números
de 0 a 99 por ordem decrescente, em seguida essa lista é baralhada para que fiquem
distribuídos de forma aleatória.

Figura 5: Um tabuleiro de início2

Com o desenrolar do jogo os elementos da lista, a qual vamos designar de tabuleiro,


vão sendo substituídos pelo símbolo “##” nas casas correspondentes às casas que vão
ficando inacessíveis e aquelas pelas quais um dos cavalos passou. Os elementos da
lista correspondentes às posições de cada cavalo num dado momento são substituídos
por “cb” e “cp”, respectivamente, cavalo branco e cavalo preto.

1
Se porventura posteriormente entendermos ser mais fácil utilizar uma lista de listas, efectuaremos as
alterações necessárias.
2
Representamos a lista referida em modo de texto de forma que se visualize um tabuleiro 10X10.
Representamos as colunas por letras de A a J e as linhas com números de 1 a 10.

Programação em Lógica Página 6


Figura 6: Tabuleiro após três jogadas do cavalo branco e duas jogadas do cavalo preto.

4. Representação de um movimento:

Apesar de ainda não termos implementado, em concreto, os movimentos que cada


jogador pode efectuar, já podemos adiantar sumariamente como irão ser processados
em termos do algoritmo a utilizar. Como as peças efectuam “saltos de cavalo” e como
usamos apenas uma lista para representar o tabuleiro, então na maior das
possibilidades um cavalo poderá efectuar 8 saltos possíveis, isto considerando todas as
casas estarem acessíveis. Se um cavalo está na posição “Pos” então poderá saltar para
as posições {Pos-8; Pos+8; Pos-12; Pos+12; Pos-19; Pos+19; Pos-21; Pos+21}.
Evidentemente que, para cada uma destas possibilidades, é necessário verificar se é
uma posição válida, isto é, se está entre 0 e 99 (os índices da lista), verificar se a casa
está acessível e analisar casos particulares, por exemplo, o caso do cavalo estar nas
linhas ou colunas das margens do tabuleiro. Em termos de escolha da melhor jogada,
isto é do melhor “salto de cavalo” a cada momento, haverá duas abordagens: aquela
que possibilita uma maior acumulação de pontos e aquela que impede o adversário de
jogar.

Estamos a pensar utilizar os seguintes predicados3 para efectuar os movimentos:

• lista_jogadas(+Tab,+Cavalo,-ListaJogadas): predicado que devolverá no


terceiro argumento uma lista contendo as posições possíveis para a próxima
jogada, consoante o estado do tabuleiro (Tab) e o Cavalo que irá efectuar o
salto. Este predicado poderá também ser utilizado para determinar o final do
jogo, pois se devolver uma lista vazia significa que o jogo terminou.
• movimento_valido(+Coluna,+Linha,+Cavalo,+Tab): predicado que determinará
se determinado movimento a efectuar será ou não válido. Os dois primeiros
argumentos indicam, no fundo, as coordenadas da nova posição para o cavalo
no tabuleiro.
• executa_movimento(+Coluna,+Linha,+Cavalo,+Tab,-NovoTab): predicado que
se satisfizer o anterior irá efectuar o movimento e devolver no último
argumento o estado do tabuleiro após o movimento.

3
Estes predicados poderão sofrer alterações até ao final do trabalho, na medida em que se encontre ou
não, alternativas mais eficientes.

Programação em Lógica Página 7


5. Visualização do Tabuleiro:

A representação do tabuleiro consiste em imprimir no ecrã de uma forma mais ou


menos ou menos linear todos os elementos da lista “Tabuleiro” à medida que esta vai
sofrendo alterações, isto é, no inicio do jogo e imediatamente após cada jogada.

A tabela seguinte mostra como estão implementados os principais predicados que


permitem a visualização do tabuleiro a cada instante.

visualiza_estado(Tabuleiro):- escreve([],_).
write(' Tabuleiro de Jogo '),nl, escreve([A,B,C,D,E,F,G,H,I,J|Tail],N):-
write(' Luta de Cavalos '),nl, N =< 10,
write(' A B C D E F G H I J'),nl, M is N,
write(' ______________________________'),nl, write1(M),write(' |'),
escreve(Tabuleiro,1), write1(A),write('|'),
write(' ______________________________'),nl, write1(B),write('|'),
write(' A B C D E F G H I J'),nl. write1(C),write('|'),
write1(D),write('|'),
write1(E),write('|'),
write1(F),write('|'),
write1(G),write('|'),
write1(H),write('|'),
write1(I),write('|'),
write1(J),write('| '),write1(M),nl,
escreve(Tail,M+1).
lista(0,[]):-!. % cria a lista ordenada. write1(X):-
lista(N,[A|Tail]):- integer(X),
X >= 10, write(X).
A is N-1, write1(X):-
lista(A,Tail). integer(X),
%cria uma lista que representa o estado X < 10, write('0'),write(X).
inicial do tabuleiro write1(X):-
write(X).
cria_tabuleiro(Tab):-
lista(100,TabOrdenado),
shuffle(TabOrdenado,Tab).
Tabela 1: Predicados que permite visualizar o tabuleiro

O predicado visualiza_estado(+Tabuleiro) recebe como argumento a lista contendo os


elementos a colocar no tabuleiro a cada instante. Este predicado socorre-se de outro
predicado que é escreve(+Tab,+N) que recebe dois argumentos, a lista a imprimir no
ecrã e um número que corresponderá, em cada iteração, ao número da linha que está
a ser impressa. Este predicado é, portanto, recursivo e imprime a cada chamada de si
mesmo cada uma das linhas do tabuleiro, este predicado termina a sua execução
quando a lista a imprimir for a lista vazia.

Programação em Lógica Página 8


De salientar que, foi necessário implementar um predicado write1(+X) com duas
finalidades, a saber: imprimir com um zero à esquerda os números com apenas um
algarismo e imprimir os átomos ‘cb’, ‘cp’ e ‘##’, respectivamente, representando o
cavalo branco, o cavalo preto e uma casa inacessível.

Finalmente o predicado cria_tabuleiro(-Tab) que surge na tabela 1, é utilizado no início


de jogo quando se pretende preencher o tabuleiro com os números de 0 a 99,
distribuídos aleatoriamente. Este predicado, basicamente, cria primeiramente uma
lista ordenada decrescentemente com os números de 99 a 0 e em seguida baralha essa
lista ao invocar o predicado shuffle(+TabOrdenado,-Tab).

As figuras 1 até 6 representam estados dos tabuleiros à medida que o jogo vai
decorrendo.

6. Conclusões e Perspectivas de Desenvolvimento:

Analisando o trabalho efectuado até ao momento podemos concluir que está correr
como previsto e de uma forma satisfatória. Tratando-se de uma linguagem e de um
paradigma de programação para os quais não estavam habituados em utilizar, a sua
assimilação está a ser feita um pouco lentamente, no entanto, estamos optimistas que
conseguiremos alcançar os resultados desejados.

Quanto a possibilidades de desenvolvimento, será interessante após o


desenvolvimento e visualização em modo “texto”, dar-lhe um aspecto mais atractivo
com uma visualização em três dimensões. Interessante, também seria, implementar o
jogo numa plataforma on-line, possibilitando que pessoas à distância pudessem jogá-lo
de forma interactiva.

Bibliografia:

[Bramer, Max] Logic Programming with Prolog, Springer, 2005.

[Stobo, John] Problem Solving with Prolog, Taylor & Francis e-Library, 2005.

Eugénio Oliveira e Luís Paulo Reis, Materiais da Disciplina de Programação em Lógica,


disponível online a partir de http://paginas.fe.up.pt/~eol/LP/0809/ (Consultado em
Setembro e em Outubro de 2008).

Programação em Lógica Página 9


Anexos
4
Código :

/*==========================================================================
=

Jogo de Tabuleiro - Luta de Cavalos

===========================================================================
==*/

/*criar uma lista com os n.os interios de 0 a 99 e baralhar os elementos dessa lista*/

%lista(+N,-L).

%devolve no segundo argumento uma lista com N inteiros por ordem decrescente

%a partir de N-1 até 0.

lista(0,[]):-!.

lista(N,[A|Tail]):-

A is N-1,

lista(A,Tail).

%cria uma lista que representa o estado inicial do tabuleiro

cria_tabuleiro(Tab):-

lista(100,TabOrdenado),

shuffle(TabOrdenado,Tab),

visualiza_estado(Tab),!.

/*esta ultima instrução é para teste apenas*/

%Transforma uma lista(com 100 elemento no caso em concreto)

%em listas com sublistas de 10 elementos e vise versa

lista_de_listas([],[]):-!.

lista_de_listas([A,B,C,D,E,F,G,H,I,J|Tail1],[[A,B,C,D,E,F,G,H,I,J]|Tail2]):-

lista_de_listas(Tail1,Tail2).

/*baralha os elementos de uma lista*/

/*os dois predicados seguintes foram retirados de:

http://ozone.wordpress.com/2006/02/22/little-prolog-challenge/ */

%% shuffle(+ListIn,-ListOut) - randomly shuffles

4
O código apresentado está ainda numa fase inicial, muitos dos predicados apresentados são apenas
para efectuar testes.

Programação em Lógica Página 10


%% ListIn and unifies it with ListOut

shuffle([], []).

shuffle(List, [Element|Rest]) :-

choose(List, Element),

delete(List, Element, NewList),

shuffle(NewList, Rest).

%% choose(+List,-Elt) - chooses a random element

%% in List and unifies it with Elt.

choose([], []).

choose(List, Elt) :-

length(List, Length),

random(0, Length, Index),

nth0(Index, List, Elt).

visualiza_estado(Tabuleiro):-

write(' Tabuleiro de Jogo '),nl,

write(' Luta de Cavalos '),nl,

write(' A B C D E F G H I J'),nl,

write(' ______________________________'),nl,

escreve(Tabuleiro,1),

write(' ______________________________'),nl,

write(' A B C D E F G H I J'),nl.

escreve([],_).

escreve([A,B,C,D,E,F,G,H,I,J|Tail],N):-

N =< 10,

M is N,

write1(M),write(' |'),

write1(A),write('|'),

write1(B),write('|'),

write1(C),write('|'),

write1(D),write('|'),

write1(E),write('|'),

Programação em Lógica Página 11


write1(F),write('|'),

write1(G),write('|'),

write1(H),write('|'),

write1(I),write('|'),

write1(J),write('| '),write1(M),nl,

escreve(Tail,M+1).

write1(X):-

integer(X),

X >= 10, write(X).

write1(X):-

integer(X),

X < 10, write('0'),write(X).

write1(X):-

write(X).

%calcula o número Y com dois algarismos que é o número que resulta da troca dos

%dois algarimos do número X. Se X tem apenas um algarismo o valor de Y será dez

%vezes o valor de X.

troca(X,Y):-

X > 9,X < 100,

DigitoUnidades is X mod 10,

DigitoDezenas is (X-DigitoUnidades)/10,

Y is DigitoDezenas+10*DigitoUnidades.

troca(X,Y):-

X > 0,X =< 9,

Y is X*10.

/*==========================================================================
==

zona de testes

===========================================================================
===*/

%troca os algarismos de todos os elementos de uma lista

% n serve pra nada...

Programação em Lógica Página 12


troca_lista([],[]).

troca_lista([X|Cauda1],[Y|Cauda2]):-

troca(X,Y),

troca_lista(Cauda1, Cauda2).

%predicado de teste que serve para apresentar o tabuleiro inicial

joga_teste:-

cria_tabuleiro(_).

inicio:-

cria_tabuleiro(Tab),

primeira_jogada(Tab,Tab2),

visualiza_estado(Tab2),

lista_jogadas(Tab2,ListaJogadas),

nl,write(ListaJogadas).

primeira_jogada([X|Tail1],[c1|Tail2]):-

troca(X,Y),

subst(Y,'##',Tail1,Tail2).

jogada(Coluna,Linha,Cavalo,Tab1,Tab2):-

Pos is 10*(Linha-1)+(Coluna-1),

nth0(Pos,Tab1,Num),

subst(Num,Cavalo,Tab1,Tab3),

troca(Num,NumTrocado),

subst(NumTrocado,'##',Tab3,Tab2).

teste:-

cria_tabuleiro(Tab),

jogada(5,1,cb,Tab,Tab2),

jogada(2,10,cp,Tab2,Tab3),

subst(cb,'##',Tab3,Tab4),

jogada(6,3,cb,Tab4,Tab5),

subst(cp,'##',Tab5,Tab6),

jogada(4,9,cp,Tab6,Tab7),

subst(cb,'##',Tab7,Tab8),

Programação em Lógica Página 13


jogada(8,4,cb,Tab8,Tab9),

visualiza_estado(Tab9).

/*substituir um número X de uma lista por o atomo Y */

subst(_,_,[],[]):-!.

subst(X,Y,[Cab1|Tail1],[Cab1|Tail2]):-X \= Cab1, subst(X,Y,Tail1,Tail2).

subst(X,Y,[X|Tail1],[Y|Tail2]):- subst(X,Y,Tail1,Tail2).

lista_jogadas(Tab,ListaJogadas):-

nth0(Pos,Tab,c1),

P1 is Pos-8,P2 is Pos+8,

P3 is Pos-12,P4 is Pos+12,

P5 is Pos-19,P6 is Pos+19,

P7 is Pos-21,P8 is Pos+21,

L=[P1,P2,P3,P4,P5,P6,P7,P8],

findall(X, (member(X,L),X >= 0,X < 100),ListaJogadas).

Programação em Lógica Página 14