This is a basic implementation of the game of Quarto, inspired by this question to write my own:
Quarto is a board game for two players invented by Swiss mathematician Blaise Müller in 1991.
It is played on a 4×4 board. There are 16 unique pieces, each of which is either:
- tall or short;
- red or blue (or a different pair of colors, e.g. light- or dark-stained wood);
- square or circular; and
- hollow-top or solid-top.
Players take turns choosing a piece which the other player must then place on the board. A player wins by placing a piece on the board which forms a horizontal, vertical, or diagonal row of four pieces, all of which have a common attribute (all short, all circular, etc.). A variant rule included in many editions gives a second way to win by placing four matching pieces in a 2x2 square. [variant rule is not implemented by me]
Quarto is distinctive in that there is only one set of common pieces, rather than a set for one player and a different set for the other. It is therefore an impartial game.
A piece is represented as a tuple four binary digits, for example (1, 0, 1, 1)
. Each number represents a different attribute by virtue of its position, so the game is won if in at least one row, column or diagonal, an item at the given position is equal for all items.
This version is "barebones" because the names of the attributes are not taken into account because I considered them superfluous to the logic of the game.
import doctest
import itertools as it
def all_equal(xs):
"""
>>> all_equal([1, 1, 1])
True
>>> all_equal("foobar")
False
"""
return all(x == xs[0] for x in xs)
def at_least_one_element_all_in_common_at_same_index(xss):
"""
>>> at_least_one_element_all_in_common_at_same_index([ (1, 3), (5, 3), (0, 3, 9) ])
True
>>> at_least_one_element_all_in_common_at_same_index(["foobar", "zzz", "yyy"])
False
"""
return any(all_equal(items) for items in zip(*xss))
def is_won_rows(board):
"""
>> is_won_rows([[(1, 0), (4, 9)],
... [(3, 0), (3, 8)]]) # 3 at first position in second row
True
"""
return any(not None in row and at_least_one_element_all_in_common_at_same_index(row) for row in board)
def columns(xss):
"""
>>> list(columns([[1,2,3],
... [4,5,6],
... [7,8,9]]))
[(1, 4, 7), (2, 5, 8), (3, 6, 9)]
"""
return zip(* xss)
def diagonal(xss):
"""
>>> diagonal([[1,2,3],
... [4,5,6],
... [7,8,9]])
[1, 5, 9]
"""
return [xss[i][i] for i in range(len(xss))]
def diagonals(xss):
"""
>>> diagonals([[1,2,3],
... [4,5,6],
... [7,8,9]])
[[1, 5, 9], [3, 5, 7]]
"""
return [diagonal(xss), diagonal([row[::-1] for row in xss])]
def is_won(board):
"""
>>> is_won([ [(0, 1, 0, 0), (0, 1, 1, 1), (1, 1, 1, 1), (0, 1, 0, 1)], # 1 at second place at first row
... [(0, 0, 0, 0), (0, 1, 1, 1), (1, 1, 1, 1), (0, 1, 0, 1)] ])
True
"""
return is_won_rows(board) or is_won_rows(columns(board)) or is_won_rows(diagonals(board))
def ask_option(prompt, choices):
print(prompt)
return choices[int(input('\n'.join(str(i)+" "+str(o) for i,o in enumerate(choices))+"\n"))]
def ask_coordinates(prompt):
x, y = input(prompt).split(',')
return int(x)-1, int(y)-1 # 1 index to 0 index
def print_board(board):
"""
>>> print_board([[(0, 1, 0, 0), (0, 0, 0, 0), (0, 1, 1, 0), (1, 1, 0, 0)],
... [(0, 1, 0, 0), (0, 1, 0, 0), (0, 1, 0, 0), (0, 1, 0, 1)],
... [(0, 1, 0, 1), (0, 1, 0, 0), (0, 1, 0, 1), (0, 1, 0, 0)],
... [(0, 1, 0, 0), (1, 1, 0, 0), (0, 1, 0, 0), (0, 1, 0, 1)]]) # Fake board, in reality no repetitions are allowed
Y X 1 2 3 4
1 0100 0000 0110 1100
2 0100 0100 0100 0101
3 0101 0100 0101 0100
4 0100 1100 0100 0101
"""
print("Y X " + ''.join(n + " " for n in "1234"))
for y, row in enumerate(board, start=1):
print(str(y) +" "+ " ".join("...." if piece is None else ''.join(map(str, piece)) for piece in row))
def quarto_game():
board = [ [None for _ in range(4)] for _ in range(4)]
pieces = [(height, color, shape, top) for height in (0, 1) for color in (0, 1) for shape in (0, 1) for top in (0, 1)]
for player in it.cycle((1, 2)):
print_board(board)
piece = ask_option(
"Player {}, please select a piece for your opponent to place: ".format(1 if player == 2 else 2),
choices=pieces)
pieces.remove(piece)
x, y = ask_coordinates("Player {}, make your move [x, y] format: ".format(player))
board[y][x] = piece
if is_won(board):
print("Player {} has won!".format(player))
print_board(board)
break
if __name__ == "__main__":
doctest.testmod()
quarto_game()