Skip to content

Commit

Permalink
Merge pull request #23 from Ca-moes/report-tp1
Browse files Browse the repository at this point in the history
Final Steps on Report TP1 and Delivery
  • Loading branch information
Ca-moes committed Dec 2, 2020
2 parents c813891 + 3f9a2a4 commit e871747
Show file tree
Hide file tree
Showing 10 changed files with 51 additions and 39 deletions.
Binary file added PLOG_TP1_FINAL_T6_Talpa_4.zip
Binary file not shown.
49 changes: 24 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
- `File` -> `Consult...` -> Selecionar ficheiro `talpa.pl`
- Na consola do SicStus: `play.`
### Linux
> Deve ser os mesmos passos, necessário testar
- Executar `SicStus Prolog`
- `File` -> `Consult...` -> Selecionar ficheiro `talpa.pl`
- Na consola do SicStus: `play.`
## Talpa - Descrição do Jogo

O objetivo do jogo é criar um caminho formado por lugares vazios no tabuleiro, que conecte lados opostos do tabuleiro, sendo cada par de lados opostos atribuido a um dos jogadores.
Expand All @@ -26,9 +27,9 @@ O jogo acaba quando um caminho é formado pelos espaços livres entre dois lados
O tabuleiro é representado a partir de uma lista com sublistas, sendo cada sublista uma linha do tabuleiro. Cada elemento, durante o jogo, pode ter 1 de 3 valores possiveis:
- `0` representa uma posição vazia
- `1` representa uma posição com uma peça pertencente ao jogador 1
- `2` representa uma posição com uma peça pertencente ao jogador 2
- `-1` representa uma posição com uma peça pertencente ao jogador 2

Fica também reservado o valor `9` que serve como caracter de enchimento usado no algoritmo `floodFill`, explicado numa próxima secção.
Fica também reservado o valor `9` que serve como caracter de enchimento usado no algoritmo `floodFill`, explicado numa [próxima secção](#floodfill).

```prolog
Possiveis estados de jogo:
Expand Down Expand Up @@ -60,7 +61,7 @@ Possiveis estados de jogo:
```

#### Player
O player tem dois estados possiveis, ambos strings: `Player 1` e `Player 2`.
O player tem dois estados possíveis, ambos strings: `Player 1` e `Player 2`.
Estas strings estão associadas ao elementos do board a partir do predicado `player_piece/2`.
```prolog
player_piece('Player 1', 1).
Expand All @@ -87,13 +88,13 @@ Estes valores são usados nos predicados `start_game` e `turn` para decidir a or

Após iniciar o jogo com o predicado `play.` o jogador tem ao seu dispor um menu inicial com as opções principais do jogo:

![placeholder image]()
![main menu](./README_files/main_menu.png)

Para realizar a escolha de uma opção o jogador apenas escreve o número relativo á opção que quer e prime `Enter`. Estando em qualquer ecrã de menu e escolhendo a opção `0`, o ecrã é limpo e o menu principal é exibido.
Para realizar a escolha de uma opção o jogador apenas escreve o número relativo à opção que quer e prime `Enter`. Estando em qualquer ecrã de menu e escolhendo a opção `0`, o ecrã é limpo e o menu principal é exibido.

As opções `4 - Game Instructions` e `5 - Information about project` contêm apenas texto sobre as suas secções.

As primeiras 3 opções correspondem a tipos de jogo disponiveis:
As primeiras 3 opções correspondem a tipos de jogo disponíveis:
```
1 - Player vs Player
2 - Player vs Computer
Expand All @@ -105,7 +106,7 @@ Para a primeira opção, após selecionar o tamanho do tabuleiro, o jogo inicia,

Para a segunda opção, para além do tamanho do tabuleiro, é também necessário escolher a **dificuldade** do computador, juntamente com qual `Player` é que o jogador quer ser, tendo as opções de ser o `Player 1` ou o `Player 2`.

Para a terceira opção, para além do tamanho do tabuleiro, é possivel escolher as dificuldades de ambos os computadores.
Para a terceira opção, para além do tamanho do tabuleiro, é possível escolher as dificuldades de ambos os computadores.

Assim que um jogo é iniciado é apresentado o tabuleiro:
```
Expand Down Expand Up @@ -157,12 +158,12 @@ Para analisar o "valor" de um tabuleiro, para um jogador, implementamos a funç
#### FloodFill
FloodFill é um algoritmo usado em arrays mutidimensionais para determinar "áreas" interligadas entre si. Para utilizar este algoritmo precisamos de uma matriz, uma posição inicial, um valor a substituir (A) e um valor que será substituido (B). O algoritmo começa na posição inicial e verifica se o valor que aí se encontra é igual a A, em caso positivo, a posição fica com o valor B e o algoritmo é aplicado nas células adjacentes á célula inicial. Nessas células faz-se a mesma verificação do valor A e substituição pelo valor B. Se a posição não tiver valor A, então o seu valor não é substituido e as posições adjacentes não são verificadas. O algoritmo termina quando não houver mais células a verificar.

![Flood Fill Demonstration](kadabra./README_files/Recursive_Flood_Fill.gif)
![Flood Fill Demonstration](./README_files/Recursive_Flood_Fill.gif)
##### Exemplo usando um board intermédio

| Before Flood Fill | After FloodFill |
|--------|-------|
| ![Flood Fill Demonstration](kadabra./README_files/Board_before_floodfill.png) | ![Flood Fill Demonstration](kadabra./README_files/Board_after_floodfill.png) |
| ![Flood Fill Demonstration](./README_files/Board_before_floodfill.png) | ![Flood Fill Demonstration](./README_files/Board_after_floodfill.png) |

> Implementação em Prolog
Expand All @@ -182,29 +183,28 @@ floodFill(Board, _, _, _, _, _, Board).
```
---

Continuando com a avaliação do tabuleiro, após ter sido realizado o algoritmo de **Flood Fill** no tabuleiro, é formada uma *mancha* pelos carácters de enchimento que será analisada na segunda parte. O valor da posição na qual foi feito o Flood Fill é guardado e este predicado é chamado recursivamente com o novo tabuleiro. Desta forma serão encontradas, e guardadas, todas as posições possiveis de sofrer Flood Fill, equivalentes a *manchas* independentes entre si. No final é retornada uma lista que contém sublistas da forma `[X-Y]`, correspondente ás posições possiveis de fazer Flood FIll.
Continuando com a avaliação do tabuleiro, após ter sido realizado o algoritmo de **Flood Fill** no tabuleiro, é formada uma *mancha* pelos carateres de enchimento que será analisada na segunda parte. O valor da posição na qual foi feito o Flood Fill é guardado e este predicado é chamado recursivamente com o novo tabuleiro. Desta forma serão encontradas, e guardadas, todas as posições possíveis de sofrer Flood Fill, equivalentes a *manchas* independentes entre si. No final é retornada uma lista que contém sublistas da forma `[X-Y]`, correspondente às posições possíveis de fazer Flood FIll.

```
Pseudocódigo parte 1:
Percorre célula a célula
Encontra lugar 0
FloodFill para obter novo GameState, Guarda Posição X-Y para depois retornar e chama mesmo predicado
com novo GameState
FloodFill para obter novo GameState, Guarda Posição X-Y para depois retornar e chama mesmo predicado com novo GameState
Dá append a X-Y á lista de Return de ter chamado o predicado e dá return da nova lista
```

Na segunda parte, a lista de posições recém formada é percorrida, uma a uma, para analisar cada *mancha*. Tendo uma posição, é realizado o algoritmo de **Flood Fill** para obter um tabuleiro com uma *mancha*. Este novo tabuleiro é passado ao predicado `values_in_all_columns(+GameState, +Value, -ListResult)` que percorrerá o tabuleiro e guardará numa lista, com cada elemento a simbolizar uma coluna, os valores da soma das ocorrência de cada carácter de enchimento por coluna, formando, por exemplo, uma lista semelhante a `[4,3,3,2,0,0]` que corresponde á lista retornada por este predicado se fosse passado como argumento o tabuleiro *After FloodFill* [da secção acima](#exemplo-usando-um-board-intermédio).
Na segunda parte, a lista de posições recém-formada é percorrida, uma a uma, para analisar cada *mancha*. Tendo uma posição, é realizado o algoritmo de **Flood Fill** para obter um tabuleiro com uma *mancha*. Este novo tabuleiro é passado ao predicado `values_in_all_columns(+GameState, +Value, -ListResult)` que percorrerá o tabuleiro e guardará numa lista, com cada elemento a simbolizar uma coluna, os valores da soma das ocorrência de cada carácter de enchimento por coluna, formando, por exemplo, uma lista semelhante a `[4,3,3,2,0,0]` que corresponde á lista retornada por este predicado se fosse passado como argumento o tabuleiro *After FloodFill* [da secção acima](#exemplo-usando-um-board-intermédio).

Esta lista é a seguir passada como argumento para o predicado `sequence(+List, -Result)` que retorna em `Result` o valor correspondente ao comprimento da maior sequência de números formados sem usar o número 0 que, usando o exemplo de cima, retornaria `4`. É assim obtido o **value** de uma possivel mancha.

Este **value** corresponde ao **alcance** da mancha após uma jogada. Se o **value** for igual ao comprimento do tabuleiro, significa que essa jogada criou uma mancha cujo alcance vai desde um lado ao lado oposto do tabuleiro, sendo esta uma jogada que abre um caminho vencedor para o jogador. Quanto maior for o valor de **value**, maior será o alcance de uma mancha e mais próximo está o jogador de abrir um caminho entre os seus dois lados do tabuleiro.

A segunda parte do predicado value acaba no fim de verificar todos as posições que formam manchas independentes, obtendo uma lista de **values** para cada mancha. Para obter o **value** de um tabuleiro o predicado retorna o maior dos **values** da lista, que corresponde à mancha que se consegue *estender* mais.
A segunda parte do predicado value acaba no fim de verificar todas as posições que formam manchas independentes, obtendo uma lista de **values** para cada mancha. Para obter o **value** de um tabuleiro o predicado retorna o maior dos **values** da lista, que corresponde à mancha que se consegue *estender* mais.

Esta explicação abranje apenas o caso relativo ao `Player 1` porque analisa o **value** de cada *mancha* na horizontal, significando que quanto mais *extensa*, na horizontal, for a *mancha*, maior será o seu **value**. Como, para representar o tabuleiro, estamos a usar uma lista composta por sublistas, podemos usar o predicado `transpose(?X, ?Y)` com o tabuleiro quando for para analisar o caso do `Player 2`. Isto transformará as linhas do tabuleiro em colunas e vice-versa, sendo agora possivel analizar o caminha na horizontal também para o `Player 2`.
Esta explicação abrange apenas o caso relativo ao `Player 1` porque analisa o **value** de cada *mancha* na horizontal, significando que quanto mais *extensa*, na horizontal, for a *mancha*, maior será o seu **value**. Como, para representar o tabuleiro, estamos a usar uma lista composta por sublistas, podemos usar o predicado `transpose(?X, ?Y)` com o tabuleiro quando for para analisar o caso do `Player 2`. Isto transformará as linhas do tabuleiro em colunas e vice-versa, sendo agora possivel analizar o caminho na horizontal também para o `Player 2`.
### Final do Jogo

Um caso possivel de acontecer é de um Jogador fazer uma jogada, tal que abra um caminho para si, mas também para o inimigo, sendo assim a vitória do inimigo. Para fazer esta verificação, no final de uma jogada, o predicado `game_over(+GameState, +Player , -Winner)` é chamado e é primeiro verificado se o jogador oposto tem um caminho formado entre os seus lados do tabuleiro. Caso não haja um caminho, então é que é verificado se existe um caminho para o jogador inicial.
Um caso possível de acontecer é de um Jogador fazer uma jogada, tal que abra um caminho para si, mas também para o inimigo, sendo assim a vitória do inimigo. Para fazer esta verificação, no final de uma jogada, o predicado `game_over(+GameState, +Player , -Winner)` é chamado e é primeiro verificado se o jogador oposto tem um caminho formado entre os seus lados do tabuleiro. Caso não haja um caminho, então é que é verificado se existe um caminho para o jogador inicial.

A verificação da vitória é feita no predicado `check_win(+PlayerS, +GameState, +K, -Result)`.
```prolog
Expand All @@ -228,25 +228,24 @@ Usando esta lógica, torna-se simples verificar se um jogador é vencedor ou nã

### Jogada do Computador

Neste Projeto criamos 2 dificuldades possiveis para o Computador: `Easy` e `Normal`.
Neste Projeto criamos 2 dificuldades possíveis para o Computador: `Easy` e `Normal`.

Se a dificuldade for `Easy`, então o Computador, com o auxilio do predicado `valid_moves(+GameState, +PlayerS, -List)`, tem à sua disposição uma lista de movimentos possiveis de executar e escolherá, aleatóriamente, um destes movimentos da lista, usando o predicado `random_member(-Elem, +List)` da biblioteca `random`, devolvendo o movimento no último argumento do predicado `choose_move(+GameState, +Player, +Level, -Move)`.
Se a dificuldade for `Easy`, então o Computador, com o auxílio do predicado `valid_moves(+GameState, +PlayerS, -List)`, tem à sua disposição uma lista de movimentos possiveis de executar e escolherá, aleatoriamente, um destes movimentos da lista, usando o predicado `random_member(-Elem, +List)` da biblioteca `random`, devolvendo o movimento no último argumento do predicado `choose_move(+GameState, +Player, +Level, -Move)`.

Se a dificuldade for `Normal`, após obter a lista dos movimentos possiveis, é usado o predicado `findall(+Template, +Generator, -List)` para gerar uma lista de elementos na forma `Value-X-Y-Direction-Index`:
Se a dificuldade for `Normal`, após obter a lista dos movimentos possíveis, é usado o predicado `findall(+Template, +Generator, -List)` para gerar uma lista de elementos na forma `Value-X-Y-Direction-Index`:
- `Value` - valor do board resultante da jogada
- `X-Y-Direction` - componentes de uma jogada
- `Index` - Indice da jogada na lista de todos os movimentos possiveis
- `Index` - Indice da jogada na lista de todos os movimentos possíveis

A lista resultante está ordenada pelo `Index` de forma crescente, sendo assim preciso efetuar um `sort(+List1, -List2)` para que a Lista fique ordenada por ordem crescente de `Value`. Para obter o `Move` a efetuar só falta retirar as componentes `X-Y-Direction` do último elemento da lista usando o predicado `last(+List, -Last)`.

## Conclusões

> A adicionar
Uma das dificuldades apresentadas no inicio do trabalho consistiu na representação pretendida das peças, sendo o SicStus executado em formato consola, ficamos com poucas opções relativamente aos caraters possíveis de usar. Uma possível melhoria ao projeto seria um acompanhamento mais especifico para o projeto ir de acordo com os critérios de avaliação. No momento em que este relatório está a ser escrito, não sabemos quais são as componentes que serão avaliadas, nem o peso de cada uma, o que fez com que não nos pudéssemos focar nas partes do programa que importam. Aliado ao facto de que a unidade curricular de Programação Lógica consiste em apenas 4.5 ECTS, foi dedicado demasiado tempo a este projeto, algo que poderia ter sido facilmente evitável.

### Possiveis Melhorias
É possível acrescentar 2 dificuldades ao nosso jogo:
1. Ao verificar o valor de cada mancha para o jogador, verifica o valor dessa mancha para o inimigo e no final faz a diferença entre os valores. Assim o Computador escolhe a melhor jogada para ele, mas que também não seja a melhor para o adversário.
2. Para cada movimento possivel, verificar os movimentos possiveis de fazer a seguir e analisar os tabuleiros resultantes. Assim o Computador consegue não fazer a melhor jogada no momento mas poderá ser uma jogada que lhe dará mais vantagem no futuro.
2. Para cada movimento possível, verificar os movimentos possíveis de fazer a seguir e analisar os tabuleiros resultantes. Assim o Computador consegue não fazer a melhor jogada no momento, mas poderá ser uma jogada que lhe dará mais vantagem no futuro.
## Bibliografia
- [Documentação SicStus 4.6.0](https://sicstus.sics.se/sicstus/docs/latest4/html/sicstus.html/)
- [Wikipedia - Flood Fill](https://en.wikipedia.org/wiki/Flood_fill)
Expand Down
Binary file added README_files/main_menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions bot.pl → src/bot.pl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
make_move(Difficulty, GameState, Player, NewGameState):-
choose_move(GameState, Player, Difficulty, X-Y-Direction),
row(Y, Letter), format("I'll move from X:~d Y:~s to the ~s Direction\n", [X, Letter, Direction]),
sleep(2),
sleep(3),
move(GameState, X-Y-Direction, NewGameState).

% remove(+Difficulty, +GameState, +Player, -NewGameState)
Expand All @@ -26,7 +26,7 @@
remove(Difficulty, GameState, Player, NewGameState):-
choose_remove(GameState, Player, Difficulty, X-Y),
row(Y, Letter), format("I'll remove my piece from X:~d Y:~s\n", [X, Letter]),
sleep(2),
sleep(3),
replace(GameState, X, Y, 0, NewGameState).

% choose_move(+GameState, +Player, +Level, -Move)
Expand Down
15 changes: 13 additions & 2 deletions display.pl → src/display.pl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@
[ 1,-1, 1,-1, 1,-1],
[-1, 1,-1, 1,-1, 1]
]).

initial(2, [
[ 1, 0, 1, 0, 1, 0, 1, 0],
[ 0,-1, 0,-1, 0,-1, 0,-1],
[ 1, 0, 1, 0, 1, 0, 1, 0],
[ 0,-1, 0,-1, 0,-1, 0,-1],
[ 1, 0, 1, 0, 1, 0, 1, 0],
[ 0,-1, 0,-1, 0,-1, 0,-1],
[ 1, 0, 1, 0, 1, 0, 1, 0],
[ 0,-1, 0,-1, 0,-1, 0,-1]
]).

initial(2, [
[ 1,-1, 1,-1, 1,-1, 1,-1],
[-1, 1,-1, 1,-1, 1,-1, 1],
Expand Down Expand Up @@ -123,8 +135,7 @@
code(C, P),put_code(P), write(' | '),
print_line(L).

print_header_numbers(Inicial, Final):-
Inicial = Final,
print_header_numbers(Inicial, Inicial):-
write('\n').
print_header_numbers(Inicial, Final):-
write(' '), write(Inicial), write(' |'), N1 is Inicial + 1, print_header_numbers(N1, Final).
Expand Down
5 changes: 1 addition & 4 deletions inputs.pl → src/inputs.pl
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
% checking rows
check_row(Rowread, RowreadUpper, Size) :-
(row(RowNumb, Rowread) ; row_lower(RowNumb, Rowread)), RowNumb < Size, RowNumb >= 0,
row(RowNumb, RowreadUpper). % caso lê minuscula, vai buscar maiuscula
row(RowNumb, RowreadUpper). % Gets Capital letter, ic case it reads lowercase letter
% if not between A-y then try again
check_row(_, CheckedRow, Size) :-
write('~ Invalid row\n| Select again\n'),
Expand All @@ -66,7 +66,6 @@
print_directions([Dir|Rest]):-
direction(Number, Dir),
menu_option_format(Number, Dir),
% format(' ~d - ~w |', [Number, Dir]),
print_directions(Rest).

% read_direction(+List, -DirSelected)
Expand All @@ -76,7 +75,6 @@
menu_empty_format,
menu_sec_header_format('Option', 'Direction'),
menu_empty_format,
% write('| Select Direction (number) to move to\n|'),
print_directions(List),
menu_empty_format,
menu_bottom_format,
Expand All @@ -101,7 +99,6 @@
menu_empty_format,
menu_sec_header_format('Option', 'Direction'),
menu_empty_format,
% write('| Select Direction (number) to move to\n|'),
print_directions(List),
menu_empty_format,
menu_bottom_format,
Expand Down
File renamed without changes.
15 changes: 10 additions & 5 deletions menus.pl → src/menus.pl
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@
% menu/0
% This is the main menu, with all the options available
menu :-
% clear,
menu_header_format('MAIN MENU'),
menu_empty_format,
menu_sec_header_format('Option', 'Details'),
Expand Down Expand Up @@ -137,19 +136,19 @@
% Player vs PLayer, need to choose Board Size
menu_option(1):-
menu_board_size_hidden_feature(Size),
pp_menu(Size).
clear, pp_menu(Size).
% Player vs Computer, need to choose Board Size
menu_option(2):-
banner('Player vs Computer'),
menu_board_size(Size),
pc_menu_1(Size),
menu.
clear, menu.
% Computer vs Computer, need to choose Board Size
menu_option(3):-
banner('Computer vs Computer'),
menu_board_size(Size),
cc_menu_1(Size),
menu.
clear, menu.
% Game Instructions
menu_option(4):-
clear,
Expand Down Expand Up @@ -198,7 +197,12 @@
menu.
% Information about the Project
menu_option(5):-
banner('Made By Andre Gomes and Goncalo Teixeira'),
menu_bottom_format,
menu_empty_format,
menu_text_format('Made By Andre Gomes and Goncalo Teixeira'),
menu_text_format('for PLOG Curricular Unit'),
menu_empty_format,
menu_bottom_format,
menu.

% pp_menu(+Size)
Expand All @@ -210,6 +214,7 @@
% Hidden Feature
pp_menu(4):-
write('You found a Hidden Feature, Have Fun!\n'),
sleep(3),
initial(4, GameState),
start_game(GameState, 'Player', 'Player'),
menu.
Expand Down
2 changes: 1 addition & 1 deletion play.pl → src/play.pl
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
clear,
display_game(NewGameState),
format('~n~`*t Winner - ~a ~`*t~57|~n', [Winner]),
sleep(2), clear.
sleep(5), clear.

% game_over(+GameState, +Player , -Winner)
% checks first if enemy is winner
Expand Down
File renamed without changes.

0 comments on commit e871747

Please sign in to comment.