Exercise 07-12:

Write a tic-tac-toe game for two players. The game should ask you where you want to play and switch between players. With each move, check if the position is free. Also, check when a player has won the match. A tic-tac-toe game can be seen as a list of three elements, each element being another list with three elements.

Game example:

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

Each position can be viewed as a number. Below is an example of the positions mapped to the same position on your numeric keypad.

 7 | 8 | 9
---+---+---
 4 | 5 | 6
---+---+---
 1 | 2 | 3

Answer:

##############################################################################
# Python From Scratch
# Author: Nilo Ney Coutinho Menezes
# Editora Novatec (c) 2010-2025 - LogiKraft 2025
# Site: https://pythonfromscratch.com
# ISBN: 978-85-7522-949-1 (Paperback), 978-85-7522-950-7 (hardcover), 978-85-7522-951-4 (ebook)
#
# File: chapter 07/exercise-07-12.py.py
##############################################################################
#
# Tic Tac Toe
#

# The board
board = """               Positions
   |   |      7 | 8 | 9
---+---+---  ---+---+---
   |   |      4 | 5 | 6
---+---+---  ---+---+---
   |   |      1 | 2 | 3
"""
# A list of positions (row and column) for each valid game position
# An extra element was added to facilitate index manipulation
# and so that they have the same value as the position
#
#  7 | 8 | 9
# ---+---+---
#  4 | 5 | 6
# ---+---+---
#  1 | 2 | 3

positions = [
    None,  # Element added to facilitate indices
    (5, 1),  # 1
    (5, 5),  # 2
    (5, 9),  # 3
    (3, 1),  # 4
    (3, 5),  # 5
    (3, 9),  # 6
    (1, 1),  # 7
    (1, 5),  # 8
    (1, 9),  # 9
]

# Positions that lead to winning the game
# Moves making a row, column or diagonals win
# The numbers represent the winning positions
winning = [
    [1, 2, 3],  # Rows
    [4, 5, 6],
    [7, 8, 9],
    [7, 4, 1],  # Columns
    [8, 5, 2],
    [9, 6, 3],
    [7, 5, 3],  # Diagonals
    [1, 5, 9],
]

# Build the board from strings
# generating a list of lists that can be modified
board_grid = []
for line in board.splitlines():
    board_grid.append(list(line))

player = "X"  # Start playing with X
playing = True
moves = 0  # Move counter - used to know if it's a draw
while True:
    for t in board_grid:  # Print the board
        print("".join(t))
    if not playing:  # End after printing the last board
        break
    if moves == 9:  # If 9 moves were made, all positions have been filled
        print("It's a draw! No one won.")
        break
    move = int(input(f"Enter position to play 1-9 (player {player}):"))
    if move < 1 or move > 9:
        print("Invalid position")
        continue
    # Check if the position is free
    if board_grid[positions[move][0]][positions[move][1]] != " ":
        print("Position occupied.")
        continue
    # Mark the move for the player
    board_grid[positions[move][0]][positions[move][1]] = player
    # Check if won
    for p in winning:
        for x in p:
            if board_grid[positions[x][0]][positions[x][1]] != player:
                break
        else:  # If the for loop ends without break, all positions in p belong to the same player
            print(f"Player {player} won ({p}): ")
            playing = False
            break
    player = "X" if player == "O" else "O"  # Switch player
    moves += 1  # Move counter

# About coordinate conversion:
# board_grid[positions[x][0]][positions[x][1]]
#
# Since board_grid is a list of lists, we can access each character
# by specifying a row and column. To get the row and column based on
# the played position, we use the positions list which returns a tuple with 2 elements:
# row and column. Where row is element [0] and column is element [1].
# What these lines do is convert a game position (1-9)
# into board rows and columns. Note that in this example we use the board as
# move memory, in addition to displaying the current game state.
Click here to download the file