Filipe Rodrigues Fonseca - up202003474 (50%)
Tiago André Monteiro Ribeiro - up202007589 (50%)
To play the game you first need to have SICStus Prolog 4.7 or a newer version currently installed on your machine plus the folder with the source code.
Next, on the SICStus interpreter, consult all the files located in the source root directory, for an example:
?- consult('./game.pl').
If you're using Windows, you can also do this by selecting File
-> Consult...
and selecting the file.
Finally, consult the file game.pl and run the the predicate start_game/0 to enter the game main menu:
?- play.
You will be presented with the menu after the execution.
The board is a 6x5 squared board, with a total of 30 spaces for pieces. The board starts off empty.
The players are Black and White, with White going first. Each has 12 of their color.
On each turn, a player can take one of the following actions:
- Drop a stone of their color onto an empty square.
- Shift a stone of their color already on a board to its left/right/up/down if the new square is available.
- Eat an adversary piece by moving two spaces to the left/right/up/down if that square is empty and the square that was jumped on has a piece from the other player. You must also choose another piece of the opposite color to remove from the board.
The winner is the first to eat all adversary pieces.
The game state is composed of the current state for the board, the colour of the current player and the number of pieces yet to be played by both players.
- The board is represented by a list of lists: each list is a line on the board and each element in the list is a board cell. A cell is represented by w if it has a white piece, b for a black piece and 0 for an empty space.
- The colour of the current player is an athom which takes the value of w if White is playing or b is Black is playing.
- The number of pieces each player still has is represented by two atoms who take the value of each players's stones.
[[0,0,0,0,0,0],
[0,0,0,0,0,0],
[0,0,0,0,0,0],
[0,0,0,0,0,0],
[0,0,0,0,0,0]], 12, 12, w
[[0,0,b,0,0,0],
[0,w,0,0,0,0],
[0,w,0,0,0,0],
[0,w,0,0,0,0],
[0,b,0,0,0,0]], 9, 8, w
[[0,0,0,0,0,0],
[0,w,0,0,0,0],
[0,w,0,0,0,0],
[0,0,0,0,0,0],
[0,0,0,0,0,0]], 2, 0, w
The predicates for the game visualization are separated into two different modules: menu
and game
, representing the menu and game state's interaction and display:
-
The
menu
module has a few helper predicates meant to reutilize the code and ease the creation of new menu sections. The main menu predicates are created using menu/0 and menu_option/1 ( menu.pl ). -
The
game
module has the main predicates play_game/4 and move_chosen/8. All the user interactions are validated (either in the I/O or move execution predicates) and inform the players of possible errors before asking for another input. -
The game board is displayed to the user by the predicate displayBoard/1:
displayBoard(+Board)
It uses other support predicates like display_first_row_cells/1, which displays the columns header, and display_middle_row_cells/1, whichs prints a line of the board.
The strategy for validating and applying a move was to create the predicate move/5:
move(+GameState, +Pos, +direction, +Color, -NewGameState)
The predicate will fail if the given move is not valid.
Since the game has three types of moves (placing a piece, shifting to the side or eating a pice), the predicate move needed to have a rule for each type.
The first move is applied with place/4. The second with move/5 and the third with move_piece_to_eat/5.
The strategy for checking if the game ended was to create the predicate game_over/2.
game_over(+Board, +Player, -Color)
Since the game has four ways to end the game, the predicate gameOver/2 needed to have a rule for each win condition.
When both players have 3 or fewer pieces each it's a draw and when a player doesn't have moves available.
In this last case, wins who has more pieces.
To ensure that a move is executable the predicate valid_moves/4 verifies all the conditions mentioned in the move execution section.
valid_moves(+GameState, +Pos, +Result, +Player, -Moves)
Using findall we are able to find all valid moves a player can make on a given turn.
To evaluate the state of the game the predicate chain_capture/5 verifies if there's any multiple capture available.
chain_capture(+Board, +Pos, +[Capture|Rest], -NewPos, -Player)
The predicate will succeed if the game has ended.
Due to problems with the game loop, when moving a piece belonging to a computer player, the game goes on an infinite loop.
Anyways, to control the decisions of these movements we used the predicate AllBestMoves/4 that gets us the best move for each placed piece of a player and we evaluate the best of those with the predicate higher_value/2, that does it by comparing their value (1 for simple movement, 2 for a single jump and 1 + number for jumps for a chain capture)
This can be seen in the testBestMoves/0 test
The board game Yoté was successfully implemented in the SicStus Prolog 4.7 language. The game can be played Player vs Player, Player vs Computer or Computer vs Computer (with the same or different levels).
One of the difficulties on the project was displaying an intuitive board in the SicStus terminal, which has a very limited set of characters and customization. This limits the game design, since it's hard to display black/white cells and black/white pieces at the same time. This issue was mitigated by using the characters 'b' and 'w', which isn't ideal.
Another limitation of the game is the bot's algorithm, which only looks at an immediate play, greatly reducing its cleverness. A possible improvement would be to implement another level with a better algorithm.