# Tic-Tac-Toe

## Let’s Play a Game¶

Small games like Tic-Tac-Toe have a very easy ruleset and you can implement them in a Python program with what you already know. Since this is a rather long task, instead of letting you figure out all the details by yourself, you will be given some guidance.

## Preparations¶

Start by creating a separate folder `tic_tac_toe` for your project.

We will need the file `constants.py` inside to note down some constants to make our program a bit more readable. Further, a `__main__.py` will hold the central part of our program.

### Some Things never Change¶

In the `constants.py` you will have to define the following:

• Assign some symbols to `PLAYER_X` and `PLAYER_O` respectively
• These are used to identify the players on the board later, so using letters makes sense here
• Please use the exact names for these constants, some other code later depends on it.

## Other Peoples’ Code¶

One major hurdle would be how to represent the current state of the board. You have 9 cells which could either hold the symbol of `PLAYER_O` or `PLAYER_X` or none of both.

Luckily you already found some code that might help you online, which you are allowed to use. It seems to boil down to some mathematical shenannigans that allow you to encode the state of your board into an integer. While you do not need to understand all its intricacies, you will have to figure out how to use it.

The Code you found

``````from constants import PLAYER_O, PLAYER_X

# === Module internal constants ===
# They are only needed for the internal workings of the module
_COLUMNS_PER_ROW = 3
_BITS_PER_QUARTER_BYTE = 2
_ENCODING = {  # How to map a cell state to a quarter-byte
PLAYER_X: 0b01,
PLAYER_O: 0b10
}
_DECODING = {  # The reverse mapping for the ENCODING
value: key for key, value in _ENCODING.items()
}

# === Module internal functions ===
# They are only here as helpers
# for the internal working of the module
def _offset(row, column):
quarter_bytes = row * _COLUMNS_PER_ROW + column
return quarter_bytes * _BITS_PER_QUARTER_BYTE

def _print_state(board_state):
print(f"{board_state:032b}")

def _clear_cell(board_state, row, colum):
bitmask = ~(0b11 << _offset(row, column))

# === Module public functions ===
# You may import/ use them as you see fit.
def create_empty_board():
"""Create an empty 3×3 board.

Returns:
The state of an empty board encoded as an integer
"""
return 0

def set_player(board_state, row, column, player):
"""Set a cell of a board to be occupied by a player.

This does not check if the cell is already occupied.
Providing incorrect values for any of the arguments may have unexpected results.

Args:
board_state: An integer encoding the board state before the change.
row: The row of the cell to be set. Must be in the interval (0…2).
column: The column of the cell to be set. Must be in the interval (0…2).
player: The player which occupies the cell, either `PLAYER_X`, `PLAYER_O` or `None`.
Returns:
The board state encoded as integer after the change was made.
"""
if player is None:
return _clear_cell(board_state, row, column)

bitmask = _ENCODING[player] << _offset(row, column)

def get_player(board_state, row, column):
"""Get the player who occupies a given cell.

Providing incorrect values for any of the arguments may have unexpected results.

Args:
board_state: An integer encoding the board state from which to extract the player.
row: The row of the cell to be gotten. Must be in the interval (0…2).
column: The column of the cell to be gotten. Must be in the interval (0…2).
Returns:
The player which occupies the cell, either `PLAYER_X`, `PLAYER_O` or `None`.
"""
flags = (board_state >> _offset(row, column)) & 0b11
return _DECODING.get(flags, None)

def is_occupied(board_state, row, column):
"""Checks if a cell is occuoied by a player.

Providing incorrect values for any of the arguments may have unexpected results.

Args:
board_state: An integer encoding the board state which to check.
row: The row of the cell to be checked. Must be in the interval (0…2).
column: The column of the cell to be checked. Must be in the interval (0…2).
Returns:
`True` if a player occupies the field, `False` otherwise.
"""
return bool(get_player(board_state, row, column))
``````

Copy the new-found code into a separate file `board.py`. Consider which of the provided functions you will need in your program and import them into `__main__.py`.

## Bookkeeping¶

During the game you will need to keep track of some information. In `__main__.py`, create the appropriate variables for the following data. Consider which data types and initial values they might have.

• The `board_state` which encodes which fields are already taken and by whom
• The `current_player` who is about to make a move.
• The `winner`, as soon as there is one

## Little Helpers¶

You will need to repeatedly do certain things, so it might be a good idea to write some functions for those. If any of those functions becomes to unwieldy consider to split it into smaller pieces or move it to its own file. Don’t forget to add some documantation,this can also help with getting to grips with the problem at hand.

You can try out each of the functions in isolation to test if they do what they are supposed to.

Print the current state of the board.

It could look something like this:

`````` X |   | O
---+---+---
|   | X
---+---+---
O |   | X
``````

Note that you already have some utility functions to extract the current player at a given position.

• Parameters: `board_state`
• Returns: (Nothing)

Switch from one player to the next. Also print some nice text to inform the players who is next.

Note that you aleady have some constants for each of the players, make good use of them!

• Parameters: `current_player`
• Returns: The next player

Ask the current player to select a row / column respectively which can be used to specify the cell where the player wants to place their mark. Make sure that the inputs are integers in the intervals (0…2).

• Parameters: (Nothing)
• Returns: The row / column selected by the player as integer.

Check the board if one of the following conditions are fulfilled (in the given order):

• One player has occupied a complete row, column or diagonal
• That player would then be the winner
• All fields are occupied
• No more moves are possible, resulting in a draw
• Add a constant to represent a `DRAW`, since `None` would be used to represent that the game is not yet over

• Parameters: `board_state`
• Returns: The winning player, `DRAW` or `None` if the game is not over yet

## Tying it all Together¶

With all those building pieces, you can now implement a whole game in the `__main__.py`. Here is a suggested program flow to give you some inspiration. (Of course you may chose your own approach if you desire to do so.)

## Rematch¶

After some rounds, your fellow players have come up with some additional suggestions:

• Run the game in a loop so it automatically starts a new game once the old one is over
• Keep a score of
• How many games have been played
• How often each player won
• Print the `wins/total games` ratio in percent for each player

Closing Remarks

Simple games like theese are a good way to train programming. The rules are not overburdening and well-known so you can go directly to problem-solving. Breaking these problems down into step-by-step solutions and considering how to represent the game situations in a program is a very good exercise for training to think in the more strict ruleset that programming imposes.

It is strongly recommended to revisit this exercise once you have learned more. This first implementation is still very much guided and does not take advantage of all the possible features Python offers to you. Once you get to know more language features you will likely find new ways to represent the board and structure the program flow.