1Navigate to the Notebook

Open the tic-tac-toe notebook file:

_notebooks/Foundation/F-projects/2025-08-18-tictactoe-game.ipynb

You can do this by using VSCode's file explorer

2Set Up Virtual Environment

Run the virtual environment setup script:

./scripts/venv.sh

This script will:

⚠️ Important

Make sure you're in your project root directory when running this command!

3Select the Correct Kernel

In VS Code or Jupyter, select your virtual environment kernel:

  1. Click on "Select Kernel" (usually in the top-right of the notebook)
  2. Choose "Python Environments"
  3. Select your venv kernel from the list

💡 Pro Tip

The kernel should show your venv path, not system Python!

4Run the Game

Execute the code cells to start playing:

🎉 Success!

You're ready to play! Choose positions 1-9 to make your moves.

🔧Troubleshooting

Common Issues

If the game doesn't run, check that you've selected the correct venv kernel and that all packages are installed in your virtual environment.

class Player:
    def __init__(self, name, symbol):
        self.name = name
        self.symbol = symbol


class Board:
    def __init__(self, size=3):  # changed so can change board size
        self.size = size
        self.grid = [" "] * (size * size)

    def display(self):
        print("\n")
        for row in range(self.size):
            start = row * self.size
            row_cells = " | ".join(self.grid[start:start + self.size])
            print(" " + row_cells)
            if row < self.size - 1:
                print("---+" * (self.size - 1) + "---")
        print("\n")

    def display_reference(self):
        reference = [str(i + 1) for i in range(self.size * self.size)]
        print("Board positions:\n")
        for row in range(self.size):
            start = row * self.size
            row_cells = " | ".join(reference[start:start + self.size])
            print(" " + row_cells)
            if row < self.size - 1:
                print("---+" * (self.size - 1) + "---")
        print("\n")

    def is_full(self):
        return " " not in self.grid

    def make_move(self, position, symbol):
        index = position - 1
        if index < 0 or index >= len(self.grid):
            print(f"Invalid position. Choose a number between 1 and {len(self.grid)}.")
            return False
        if self.grid[index] != " ":
            print("That spot is already taken. Try again.")
            return False
        self.grid[index] = symbol
        return True

    def check_winner(self, symbol):
        win_combinations = self.generate_win_combinations()
        for combo in win_combinations:
            if all(self.grid[i] == symbol for i in combo):
                return True
        return False
    def generate_win_combinations(self):
        combos = []

        # win combos for rows
        for r in range(self.size):
            start = r * self.size
            combos.append([start + c for c in range(self.size)])

        # win combos for columns
        for c in range(self.size):
            combos.append([c + r * self.size for r in range(self.size)])

        # win comboes for diagonals
        combos.append([i * (self.size + 1) for i in range(self.size)])  # Top-left to bottom-right
        combos.append([i * (self.size - 1) + (self.size - 1) for i in range(self.size)])  # Top-right to bottom-left

        return combos


class TicTacToe:
    def __init__(self, player1, player2, size=3):  # changed to add the size variable
        self.board = Board(size)
        self.players = [player1, player2]
        self.current_player = player1
        self.move_history = [] # added move tracker to the game itself

    def switch_player(self):
        self.current_player = (
            self.players[1] if self.current_player == self.players[0] else self.players[0]
        )
    def show_move_history(self): # added to define the command.
        print("\nMove History:")
        for move in self.move_history:
            print(f"Turn {move['turn']}: {move['player']} ({move['symbol']}) -> Position {move['position']}")
        print()

    def play(self):
        print("Welcome to Tic-Tac-Toe!")
        print(f"{self.players[0].name} is '{self.players[0].symbol}'")
        print(f"{self.players[1].name} is '{self.players[1].symbol}'")
        print("Players take turns choosing a position (1–9).\n")

        self.board.display_reference()
        self.board.display()

        while True:
            try:
                move = int(input(f"{self.current_player.name} ({self.current_player.symbol}), enter your move (1-9): "))
            except ValueError:
                print("Invalid input. Please enter a number from 1 to 9.")
                continue

            if not self.board.make_move(move, self.current_player.symbol):
                continue

            # logs the move made
            self.move_history.append({
                "turn": len(self.move_history) + 1,
                "player": self.current_player.name,
                "symbol": self.current_player.symbol,
                "position": move
            })

            self.board.display()

            self.show_move_history() # this shows move hisotry after every turn.

            if self.board.check_winner(self.current_player.symbol):
                print(f"{self.current_player.name} ({self.current_player.symbol}) wins!")
                self.show_move_history() # this shows the move history if the player wins
                break

            if self.board.is_full():
                print("It's a tie!")
                self.show_move_history() # this shows the move history if theres a tie
                break

            self.switch_player()


if __name__ == "__main__":
    size = int(input("Enter board size (3 for 3x3, 5 for 5x5, etc.): "))  # allows for the size to be chosen
    player1 = Player("Player 1", "X")
    player2 = Player("Player 2", "O")
    game = TicTacToe(player1, player2, size)  # gives the size to the game to be used
    game.play()

Welcome to Tic-Tac-Toe!
Player 1 is 'X'
Player 2 is 'O'
Players take turns choosing a position (1–9).

Board positions:

 1 | 2 | 3 | 4
---+---+---+---
 5 | 6 | 7 | 8
---+---+---+---
 9 | 10 | 11 | 12
---+---+---+---
 13 | 14 | 15 | 16




   |   |   |  
---+---+---+---
   |   |   |  
---+---+---+---
   |   |   |  
---+---+---+---
   |   |   |  




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



Move History:
Turn 1: Player 1 (X) -> Position 1



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



Move History:
Turn 1: Player 1 (X) -> Position 1
Turn 2: Player 2 (O) -> Position 6



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



Move History:
Turn 1: Player 1 (X) -> Position 1
Turn 2: Player 2 (O) -> Position 6
Turn 3: Player 1 (X) -> Position 2



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



Move History:
Turn 1: Player 1 (X) -> Position 1
Turn 2: Player 2 (O) -> Position 6
Turn 3: Player 1 (X) -> Position 2
Turn 4: Player 2 (O) -> Position 11



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



Move History:
Turn 1: Player 1 (X) -> Position 1
Turn 2: Player 2 (O) -> Position 6
Turn 3: Player 1 (X) -> Position 2
Turn 4: Player 2 (O) -> Position 11
Turn 5: Player 1 (X) -> Position 3



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



Move History:
Turn 1: Player 1 (X) -> Position 1
Turn 2: Player 2 (O) -> Position 6
Turn 3: Player 1 (X) -> Position 2
Turn 4: Player 2 (O) -> Position 11
Turn 5: Player 1 (X) -> Position 3
Turn 6: Player 2 (O) -> Position 16



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



Move History:
Turn 1: Player 1 (X) -> Position 1
Turn 2: Player 2 (O) -> Position 6
Turn 3: Player 1 (X) -> Position 2
Turn 4: Player 2 (O) -> Position 11
Turn 5: Player 1 (X) -> Position 3
Turn 6: Player 2 (O) -> Position 16
Turn 7: Player 1 (X) -> Position 4

Player 1 (X) wins!

Move History:
Turn 1: Player 1 (X) -> Position 1
Turn 2: Player 2 (O) -> Position 6
Turn 3: Player 1 (X) -> Position 2
Turn 4: Player 2 (O) -> Position 11
Turn 5: Player 1 (X) -> Position 3
Turn 6: Player 2 (O) -> Position 16
Turn 7: Player 1 (X) -> Position 4