2 Santorini Player
Due: February 12
Santorni is a strategy game that can be played in base mode or with cards that effectively adjust the rules. It works best with 2 players, which is the only configuration that we will consider. At first, we’ll stick to the basic rules, leaving cards as a future addition to the assignment.
2.1 The Rules
The base rules are shown on the first page of the official rules. To recapitulate and to build up some shared notation:
The game is played on a 5-by-5 grid of spaces.
We’ll identify a space in the grid by pairing a row number 1 to 5 with a column number 1 to 5.
Each space in the grid has a level describing a tower that is up to 4 elements tall. Spaces all start out without any tower. A player’s goal will be to move a token onto a tower with 3 elements—
one less than a full tower, because a full tower is considered capped. We’ll represent the tower on each space as a level number from 0 to 4. In the picture below, the level at (1, 1) and most other spaces is 0, the level at (2, 1) and (2, 2) and (3, 1) and (5, 4) is 1, the level at (2, 3) and (1, 5) is 2, the level at (3, 4) and (4, 3) is 3, and the level at (5, 5) is 4.
Each player has two tokens, and each token is located on some space on the grid. A token cannot be on space that has a full tower. No two tokens are ever on the same space, whether the tokens belong to the same player or not. To start the game, the first place puts tokens on any two spaces of the empty grid, and then the second player puts tokens on any two other spaces.
In the picture below, each player’s tokens are shown using the player’s number. At least for the base game, a player’s two tokens are indistinguishable other than being at different spaces. The underlines for “1” in the picture indicate that it’s player 1’s turn.
Each player’s turn (in the base game) is always two steps: move one token to an adjacent space, and build on a space that is adjacent the moved token’s new space. A space’s adjacent spaces are the (up to) 8 spaces that surround it on the grid.
In the following new board, player 1 moved the token at (2, 3) to space (3, 3) and then built a level on space (4, 3).
A token cannot move to an adjacent space in any of the following cases:
The space’s level is more than 1 higher than the level of the token’s current space (i.e., move at the same level, jump up by at most one level, or jump down by any number of levels).
The space has a complete tower (i.e., its level is the maximum value).
The space is occupied by another token.
In the following board configurations, the circled token could move to any of the adjacent spaces with ✔, and it cannot move to the adjacent spaces that have ✘.
When building on a space adjacent to the token, the build space must not have a token or a complete tower. A token can build on the space it just vacated in its move step.
If the circled token had just moved, it could build on any of the adjacent spaces with ✔, and it cannot build on the adjacent spaces that have ✘.
During a player’s turn, if either of the player’s tokens moves to a space with a tower of three elements, that player wins (without having to build). Otherwise, if a player cannot both move and build with either token, the player loses.
The move shown previously for player 1 is a poor choice, because player 1 can win by moving to the level-3 space (3, 4), instead:
2.2 Board Representation
Your Santorini player can use any representation internally that you find convenient, but to play against other Santorini players, we’ll need to pick a common exchange representation. For that purpose, we’ll represent a board using JSON:
A board is a dictionary with three keys:
"turn" mapped to a nonnegative integer, where 0 corresponds to the first turn of the game
"spaces" mapped to an array of 5 rows in order
"players" mapped to an array of 2 players, where the first player in the array takes the next turn
A row is an array of 5 levels, one for each column in order
A level is 0, 1, 2, 3, or 4
A player is an array of 2 spaces, where the order of the spaces in the array does not matter
A space is an array of 2 dimensions, row then column
A dimension is 1, 2, 3, 4, or 5
For example, the JSON representation of
is
{"turn":18, |
"players":[[[2,3],[4,4]],[[2,5],[3,5]]], |
"spaces":[[0,0,0,0,2],[1,1,2,0,0],[1,0,0,3,0],[0,0,3,0,0],[0,0,0,1,4]]} |
After a turn to produce
the JSON representation is
{"turn":19, |
"players":[[[2,5],[3,5]],[[3,3],[4,4]]], |
"spaces":[[0,0,0,0,2],[1,1,2,0,0],[1,0,0,3,0],[0,0,4,0,0],[0,0,0,1,4]]} |
while the alternative, winning turn
produces the JSON representation
{"turn":19, |
"players":[[[2,5],[3,5]],[[3,4],[4,4]]], |
"spaces":[[0,0,0,0,2],[1,1,2,0,0],[1,0,0,3,0],[0,0,3,0,0],[0,0,0,1,4]]} |
2.3 Game Protocol
A Santorini player program must read game state as JSON from standard input and write the result of a move as JSON to standard output.
To set up the game, your Santorini player program will first receive a JSON array of one of the following shapes:
An array with 0 players: Your program is the first player, and it should return an array of 1 player to represent the starting placement of the program’s tokens.
An array with 1 player: Your program is the second player, the given player represents the opponent’s tokens, and your program should return an array of 2 players. The first player in that result array must be the same as the provided one, and the second player represents the starting placement of your program’s tokens.
Thereafter, your player program should loop: read a JSON board for the current game state, then write a JSON board for the state after your player’s turn.
A referee program that runs the game will check that the JSON board produced by a player program represents a valid turn relative to the given JSON board. Note that it is not necessary for a player program to describe separate move and build steps; only the resulting board. Note also that the board for a winning move should not include an unnecessary build step. A player program will not be sent a board if there are no valid moves or if the other player has already won.
Each player program must be runnable as a exectuable (i.e., runnable by the OS’s execve system call) with no command-line arguments. When one player wins, both player-program processes will be forcibly terminated. “Tournament mode” will include a restriction (to be determined) on how long a player program can take to complete its turn.
The relevant JSON encodings are self-delimiting—
Even if your player writes a newline after JSON output, beware the common mistake of forgetting to flush standard output, especially since the default flushing mode is typically different when writing to a terminal versus writing to an operating-system pipe that is connected to a referee program.
2.4 Referee Program
A referee program and a board-drawing program will become available on January 29. Meanwhile, you should make your own tools, because that’s a good exercise.
2.5 Handin Procedure
Email the instructor an archive or a pointer to an archive (such as a link to Google Drive) that contains your source and a compiled version of your program. The compiled version should run on the CADE lab1-X.eng.utah.edu machines. Include a "README.txt" file that describes the path within your archive for the player executable. You can have multiple player executables in the archive, but choose one to enter into the tournament.