# PFL_TP2_T08_Yoxii_4 ## Elementos: - André Morais (50% contribuição) - Lucas Sousa (50% contribuição) ## Instalação e Execução: - Após instalar o SICTUS PROLOG é apenas preciso de consultar o ficheiro `main.pl`:`consult('main.pl')` e rodar o comando `play.` para poder começar a jogar. ## Descrição do jogo: - Yoxii é um jogo que para ganhar devemos de circular o totem com as nossas peças mais fortes. - O totem começa no centro do tabuleiro de jogo, que possui 37 quadrados e se assemelha a um retângulo sem cantos. - O primeiro jogador é o que possui as peças vermelhas (representadas por letras minúsculas) e o segundo jogador tem as peças brancas (representadas por letras maiúsculas). - Em cada turno, o jogador move o totem em qualquer das suas 8 direções, seja para um espaço vazio adjacente ou então sobre qualquer número de peças alinhadas da sua cor para pousar em um espaço vazio. E nesse mesmo turno, coloca uma das suas peças em um espaço vazio adjacente ao tote, caso todos os espaços adjacentes estejam ocupados, pode colocar a peça escolhida em qualquer espaço vazio do tabuleiro. - Além de partilharem o movimento do totem, cada jogador, possui 18 peças da sua cor, possuindo cada uma dessas peças diferentes valores. Dessas 18 peças, cinco são representadas por `O` e têm valor 1, outras cinco são representadas por `II` e tem valor 2, cinco são representadas por `Y` e têm valor 3, e apenas três são representadas por `X` com valor 4. - O jogo termina quando o totem não tem nenhum sítio pra onde se mover, e o vencedor é determinado pelo que tiver um somatório de maior valor das peças da sua cor adjacentes ao totem. Em caso de empate, o vencedor é aquele que possuir o maior número de peças adjacentes ao totem. ## Lógica do jogo ### Representação interna do jogo - O estado do jogo é representado da seguinte forma: `gameState(Turn, Board, Player1, Player2, P1pieces, P2pieces, Level)`, guardamos informações do número do turno, da disposição do tabuleiro, de quem são o 1º e 2º jogadores, ou seja, se são humanos ou computadores. E também, das peças na posse dos 1º e 2º jogadores e do nível de dificuldade da jogada do computador, 1 para a dificuldade mais fácil com movimentos aleatórios, e 2 para a dificuldade mais difícil que segue uma estratégia greedy para calcular a melhor jogada no momento. - O tabuleiro é representado por uma lista de listas com diferentes átomos, sendo que cada lista representa uma linha. Como o tabuleiro não é completamente quadrangular, decidimos utilizar o espaço vazio `' '` para representar todos os espaços que não pertencem ao tabuleiro de 37 casas e cada uma das casas válidas é representada por um `#`. Utilizámos também um átomo `t` para o totem e para cada uma das peças, utilizámos átomos com a primeira letra a indicar a cor do jogador ('r' para as peças vermelhas ou 'w' para as peças brancas) e a segunda letra a indicar um dos tipos de peças seguintes: `y`, `o`, `x`, ou `i` (`i` para a peça 'II'). - O estado de jogo pode então ter várias representações ao longo do jogo: - No estado inicial, terá, por exemplo, num jogo humano contra computador no nível 2, o seguinte formato ```gameState(1,[[' ', ' ', #, #, #, ' ', ' '],[' ', #, #, #, #, #, ' '],[#, #, #, #, #, #, #],[#, #, #, t, #, #, #],[#, #, #, #, #, #, #],[' ', #, #, #, #, #,' '],[' ',' ', #, #, #,' ',' ']],human,computer,[rx,rx,rx,ry,ry,ry,ry,ry,ri,ri,ri,ri,ri,ro,ro,ro,ro,ro],[wx,wx,wx,wy,wy,wy,wy,wy,wi,wi,wi,wi,wi,wo,wo,wo,wo,wo],2)```. - Em um estado intermédio, num jogo computador contra computador, no nível 1, teria algo semelhante a ```gameState(10,computer,computer,[[' ', ' ', #, #, #, ' ', ' '],[' ', #, #, #, #, #, ' '],[#,rx, #, wx, ro, #, #],[ro, #, wo,rx, #, wi, #],[wo, rx, ry, ri, wo, #, #],[' ', wi, wy, wo, wx, ro,' '],[' ',' ', t, ri, #,' ',' ']],[ry,ry,ry,ry,ri,ri,ri,ro,ro],[wx,wy,wy,wy,wy,wi,wi,wi,wo],1)```. - E em um estado final, num jogo computador contra computador, no nível 2, teria algo semelhante a ```gameState(17,computer,computer,[[' ', ' ', #, ri, wi, ' ', ' '],[' ', #, wy, wy, wi, #, ' '],[wo, ry, wy, t, wo, rx, #],[#, ri, ro, wo, wo, #, #],[ro, wo, rx, wi, ri, ry, #],[' ', ro, ro, wx, #, #,' '],[' ',' ', wo, #, #,' ',' ']],[rx,ry,ry,ry,ri,ro],[wx,wx,wy,wi],2)```. ### Visualização do estado do jogo - Antes do começo do jogo, o predicado `game_menu` apresenta um menu com todas os modos de jogo que podemos selecionar e envia essa informação para o `menu_handler` que envia os parâmetros para o `start` que inicializa o game state e começa o game cycle. - De seguida, o predicado da visualização do estado do jogo ` display_game(+GameState)` faz display do jogo da seguinte forma, para cada ciclo: - O predicado `display_board` dá display do tabuleiro junto de um sistema de coordenadas, e o predicado `display_pieces` dá display da lista de peças de cada jogador, no formato indicado abaixo. - Os átomos utilizados no tabuleiro, na representação interna, são depois formatados na visualização para o jogador. O espaço vazio `' '` é mantido, o `#` é representado por um `□`, o totem `t` é representado por um `T`. - E na representação do tabuleiro, para cada uma das peças, como não conseguimos colocar cores diferentes para cada um dos jogadores, decidimos dar display das peças dos jogadores sendo as peças vermelhas do jogador 1 em minúsculas e as peças brancas do jogador 2 em maiúsculas, como por exemplo, wx representa a peça X branca. - O número do turno é ainda apresentado no topo do ecrã, seguido do jogador 1 ou 2 que está a jogar naquele turno, no seguinte formato: `Turn 1 - Player 1's turn`. - Junto do tabuleiro representamos um sistema de coordenadas em que o eixo vertical tem letras de A a G e o eixo horizontal tem números de 1 a 7, que usamos para receber input da jogada do jogador. De seguida mostramos a lista de peças na posse de ambos os jogadores, ao longo do jogo. E para cada ronda, mostramos as possíveis jogadas do Totem, e as possíveis colocações da peça depois de selecionada, através de predicados fora do `display_board`. - O utilizador escreve o input como lhe é questionado no ecrã através de prints pelos predicados usados pelo `choose_move`, e esse input é depois verificado por predicados auxiliares como `input_verify`, `input_piece_verify` que dão display de diferentes mensagens em caso de inputs inválidos. ### Execução de jogadas - A execucão de jogadas é realizada pelo predicado `move`, esse predicado recebe o estado de jogo, o nível, o jogador e a jogada a efetuar e retorna o novo estado, este é precedido do predicado `move_totem` que é responsável pela parte da jogada relativa ao totem e que tem o mesmo comportamento. Para receber as jogadas que o utilizador efetua que são passadas para os predicados, utiliza-se os predicados `choose_move_totem`, `choose_move_piece` e `choose_move`, que recebem o input do jogador do movimento do totem e da peça, e a peça em si, no tabuleiro, após a verificação da validade da jogada em si. ### Lista de jogadas válidas - A listagem de jogadas válidas é feita apartir dos predicados `valid_moves_totem` e `valid_moves`. - O predicado `valid_move_totem` retorna a lista das posições adjacentes ao totem, que têm um espaço vazio e, para além destas, as posições com um espaço vazio que estão separadas do totem apenas por peças da cor do jogador, numa mesma direção. - O predicado `valid_moves` retorna a lista das posições adjacentes ao totem, que têm um espaço vazio. ### Final do jogo - Sabe-se que o jogo termina quando o totem não têm movimentos válidos. - Para o detetar, no final de cada turno, obtém-se a lista de jogadas possíveis para o totem, no novo estado de jogo. Se esta lista for vazia, através do predicado `check_end_state`, muda-se a variável `Status` do predicado `game_cycle` para 1, em vez de 0, passando a ser executado o predicado `game_cycle(GameState, 1)` que executa os predicados de fim de jogo. - Esse predicado é `game_over/2`, que calcula as pontuações dos jogadores, e depois, chama o predicado `print_winner/5`, que desenha no ecrã o vencedor e as pontuações dos jogadores. Estes predicados retornam o vencedor: 1->Player1, 2->Player2. ### Avaliação do tabuleiro Há duas ocasiões onde avaliamos o tabuleiro. - No final do jogo - Durante o turno do computador no maior nível de dificuldade Para avaliar o tabuleiro, num dado estado de jogo: - Encontramos a localização do totem. - Obtemos a lista das peças adjacentes ao totem - Calculamos a pontuação de cada jogador com base nas suas peças na lista de adjacentes - Se tiverem a mesma pontuação, o vencedor é aquele que tiver mais peças na lista de adjacentes Para o computador decidir a sua jogada, no nível difícil: - Obtém a lista de todos os possíveis movimentos do totem - Para cada, cria um estado de jogo temporário - Avalia cada estado de jogo, obtendo o seu valor com `value/3` - `value/3` calcula as pontuações de cada jogador, como se o jogo fosse acabar, e retorna o valor como `Value = Pontuação do Jogador - Pontuação do adversário` - O computador escolhe o movimento e peça a jogar que lhe proporciona maior valor. ### Jogada do computador - No nível 1 de dificuldade, o computador através da lista de jogadas válidas recebida, no predicado `choose_move_totem`, escolhe, com um índice aleatório, uma posição do totem válida, e no predicado `choose_move_piece` escolhe também, com outro índice aleatório, uma peça da lista das peças do jogador. Depois com o predicado `choose_move`, escolhe, da mesma forma que é efetuada a escolha do totem, com um índice aleatório, uma posição válida para colocar a peça. - No nível 2 de dificuldade, para mover o totem, determina, apartir de avaliações do estado de jogo, o movimento válido que tenha maior valor. Para escolher a peça, a partir do valor do estado atual (após mover o totem), escolhe a peça que lhe mais favoreçe. Para isto, se o valor obtido do estado atual for maior ou igual a 0, escolhe a sua peça de menor valor (a lista das peças está ordenada de forma decrescente do valor das peças). Se for maior ou igual a -1 e se tiver uma peça `i`, escolhe-a. Se for maior ou igual a -2 e se tiver uma peça `y`, escolhe-a. Caso contrário, escolhe a sua peça de maior valor. Isto porque a sua estratégia é jogar a menor peça que lhe dê vantagem, guardando as peças de maior valor para quando necessário. - No nível 2 de dificuldade, para escolher a posição da peça escolhida, usa-se a mesma estratégia que no nível 1. Isto porque a posição da peça não tem influência na pontuação desde que seja adjacente. ## Conclusões - Com este trabalho, obtivemos um melhor conhecimento da linguagem Sicstus Prolog, através da aplicação prática da mesma, trabalhando com manipulação de listas, através do uso de predicados com recursão, receção e validação de input, e de algoritmos greedy. Desta forma, estamos satisfeitos com o resultado final do nosso trabalho, que consideramos funcionar adequadamente, para todos os modos de jogo propostos. Para melhorar o nosso trabalho poderíamos apenas tentar desenvolver um algoritmo para o nível 2 de dificuldade mais complexo que o utilizado. - Finalmente, consideramos que este projeto nos aprimorou os conhecimentos e capacidades da programação no paradigma lógico. ## Bibliografia - Documentação de predicados: - https://sicstus.sics.se/sicstus/docs/3.7.1/html/sicstus_10.html - https://www.swi-prolog.org (apesar do prolog usado ter sido o sicstus, muitos dos seus predicados são iguais ao swi, e este tem melhor documentação) - Tutoriais e exemplos básicos: - https://www.educba.com/prolog-programming/ - https://www.javatpoint.com/starting-of-prolog - https://www.geeksforgeeks.org/lists-in-prolog/ - Regras do jogo: - https://boardgamegeek.com/boardgame/361084/yoxii - https://en.boardgamearena.com/gamepanel?game=yoxii