Santorini is a board game for 2–4 players. It is played on a $$5 \times 5$$ grid, on a board whose theme is inspired by the architecture of cliffside villages on Santorini1 island in Greece.
Each player has two pieces that represent workers. At the start of the game, the board is empty and each player in turn places the two workers of his chosen color into any unoccupied spaces on the board. Players then take turns, starting with the player who first placed his workers on the board. On his turn, a player selects one of his workers that first makes a move and then builds.
A worker may move into one of the (up to) eight neighboring spaces.
A worker may move up a maximum of one level higher, move down any number of levels lower, or move along the same level. A worker may not move up more than one level.
The space a workers moves into must not be occupied by another worker or a dome.
A worker can build part of a tower (a block or a dome) on one of the (up to) eight neighboring spaces around him that is not occupied by another worker or a dome.
A worker can build onto a level of any height. For example, if the worker stands on the ground, he can still build a block on the second or third level, or even a dome. He must always choose the correct shape of block or dome for the level being built (see diagram below). A tower with three blocks and a dome is considered a complete tower.
A player who is the first to place one of his workers on the third level of a tower wins the game.
In order to refer to spaces on the Santorini board, its rows are numbered from top to bottom and its columns from left to right, starting from zero. As such, the position of a space on the board can be represented as a tuple $$(r, c)$$, with $$r \in \mathbb{N}$$ (int) the number of the row and $$c \in \mathbb{N}$$ (int) the number of the column containing the space.
The eight possible directions in which a worker can move to or build on neighboring spaces are indicated by one or two uppercase letters (str) as shown in the figure above.
Define a class Santorini that can simulate the game play of Santorini games. When creating a new game (Santorini), a sequence (list or tuple) must be passed that contains the positions where the players have placed their workers at the start of the game (4, 6 or 8 workers for 2, 3 or 4 players). The workers are identified by consecutive uppercase letters of the alphabet, in the order in which they are listed in the sequence: worker A is placed at the first position, worker B at the second position, and so on. For example, this is the starting configuration that corresponds to the sequence $$\left[(3, 0), (4, 1), (1, 1), (2, 2)\right]$$:
Each game (Santorini) must have a property workers: a dictionary (dict) that maps the letter (str) of each worker onto the position of that worker on the board.
If a game (Santorini) is passed to the built-in function str, a string representation (str) of the board must be returned. Each row forms a separate line (listed from top to bottom) containing characters that describe the consecutive spaces (listed from left to right). A space not occupied by a worker is represented by a digit that indicates the level of the tower on that space: 0 if no blocks have been built on the space, 1 for a one-block tower, 2 for a two-block tower, 3 for a three-block tower and 4 for a three-block tower with a dome. A space occupied by a worker is represented by the uppercase letter identifying the worker (regardless of the level of the tower the worker is standing on).
In addition, each game (Santorini) must support at least the following methods:
A method level that takes an uppercase letter (str) identifying a worker on the board. The method must return the level (int) of the tower the worker is standing on: 0 if the worker is on the ground, 1 if the worker is on a one-block tower, 2 if the worker is on a two-block tower and 3 if the worker is on a three-block tower (workers are not allowed to stand on a complete tower).
A method move_worker that takes an uppercase letter (str) identifying a worker on the board and a direction (str). The method must move the worker to the neighboring space in the given direction and return a reference to the object on which it was called. The worker can only move to a neighboring space
inside the board
not occupied by another worker
not occupied by a complete tower
where he should not climb more than one level
If any of these conditions does not hold, the worker should not move and the method must raise an AssertionError with the message invalid move.
A method build_tower that takes an uppercase letter (str) identifying a worker on the board and a direction (str). The method must let the worker build a block or a dome on the neighboring space in the given direction and return a reference to the object on which it was called. The worker can only build on a neighboring space
inside the board
not occupied by another worker
not occupied by a complete tower
If any of these conditions does not hold, nothing should be built and the method must raise an AssertionError with the message invalid move.
>>> board = Santorini([(3, 0), (4, 1), (1, 1), (2, 2)])
>>> print(board)
00000
0C000
00D00
A0000
0B000
>>> board.workers
{'A': (3, 0), 'B': (4, 1), 'C': (1, 1), 'D': (2, 2)}
>>> board.winning_worker()
>>> board.level('B')
0
>>> print(board.move_worker('B', 'E'))
00000
0C000
00D00
A0000
00B00
>>> print(board.build_tower('B', 'W'))
00000
0C000
00D00
A0000
01B00
>>> board.level('B')
0
>>> board.level('C')
0
>>> print(board.move_worker('C', 'SW').build_tower('C', 'E'))
00000
00000
C1D00
A0000
01B00
>>> board.level('C')
0
>>> board.move_worker('A', 'W')
Traceback (most recent call last):
AssertionError: invalid move