Build Your First Python Game - Tic Tac Toe

by Ansh, Mihir, Yash

๐ŸŽฏWhat You'll Build

By the end of this lesson, you'll have created a fully functional game that looks like this:

Welcome to Tic-Tac-Toe! Players take turns choosing a position (1โ€“9). Board positions: 1 | 2 | 3 ---+---+--- 4 | 5 | 6 ---+---+--- 7 | 8 | 9 Game board: | | ---+---+--- | | ---+---+--- | | Player X, enter your move (1-9): 5 | | ---+---+--- | X | ---+---+--- | |

๐Ÿ“šWhat You'll Learn

  • Lists: Store game data
  • Functions: Organize your code
  • Conditionals: Make decisions
  • Loops: Repeat actions
  • Input/Output: Interact with players
  • Boolean Logic: Detect wins and ties

๐ŸŽฏ Important

We'll understand the LOGIC first, then code it step by step!

๐Ÿ—บ๏ธThe Game Master Plan

Before we write a single line of code, let's understand exactly what our game needs to do. This flowchart shows the complete logic:

+----------------+ | START GAME | +-------+--------+ | v +----------------+ invalid โ†’ retry | GET INPUT |---------------------+ +-------+--------+ | | valid | v | +----------------+ no +---------------+ | SPOT EMPTY? |-----------> | SAY "TAKEN" | +-------+--------+ +-------+-------+ | yes | v | +----------------+ | | PLACE SYMBOL | | +-------+--------+ | | | v | +----------------+ yes +------------+ | PLAYER WON? |------> | ANNOUNCE | +-------+--------+ | WIN | | no +------------+ v +----------------+ yes +------------+ | BOARD FULL? |------> | ANNOUNCE | +-------+--------+ | TIE | | no +------------+ v +----------------+ | SWITCH PLAYER | +-------+--------+ | | +----------------> LOOP BACK TO GET INPUT

๐Ÿค” Think About This

Look at the flowchart above and trace through what happens when:

  1. Player X wants to place their symbol in position 5
  2. Player O tries to place their symbol in a spot that's already taken
  3. Player X gets three in a row

โœ‹ Checkpoint

Can you follow the flowchart without getting lost? If not, study it until it makes sense - this is your roadmap!

๐ŸงฉBreaking Down the Plan

Our master flowchart has 4 main sections. We'll code each one separately:

Section 1: Input & Validation

+----------------+ invalid โ†’ retry | GET INPUT |---------------------+ +-------+--------+ | | valid | v | Continue... | | +----------------------------------+

What it does: Gets a number from the player and makes sure it's valid (1-9)

Section 2: Move Processing

+----------------+ no +---------------+ | SPOT EMPTY? |-----------> | SAY "TAKEN" | +-------+--------+ +-------+-------+ | yes | v | +----------------+ | | PLACE SYMBOL | | +-------+--------+ |

What it does: Checks if the chosen spot is empty and places the symbol

Section 3: Win/Tie Detection

+----------------+ yes +------------+ | PLAYER WON? |------> | ANNOUNCE | +-------+--------+ | WIN | | no +------------+ v +----------------+ yes +------------+ | BOARD FULL? |------> | ANNOUNCE | +-------+--------+ | TIE | | no +------------+

What it does: Checks if someone won or if it's a tie

Section 4: Game State Management

+----------------+ | SWITCH PLAYER | +-------+--------+ | +-------> LOOP BACK

What it does: Switches between X and O players and keeps the game going

๐ŸŽฏ Our Plan

Code each section one at a time, test it, then move to the next!

๐Ÿ“‹Phase 1: Understanding the Game Board

Let's start with the most basic building block - how do we represent a tic-tac-toe board in code?

The Board Layout

A tic-tac-toe board has 9 positions arranged like this:

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

In Python: A List

We'll use a list to store our board. Think of it like 9 boxes in a row:

Position: 0 1 2 3 4 5 6 7 8 Value: [" "," "," "," "," "," "," "," "," "]

โš ๏ธ Important

Notice that lists start counting at 0, but players think in terms of 1-9. We'll need to handle this!

Position Mapping

Player says: 1 2 3 4 5 6 7 8 9 List index: 0 1 2 3 4 5 6 7 8

So when a player says "5" (center), we need list position 4.

# ๐Ÿ”ง Mini-Challenge 1: Create and Explore the Board

# Create an empty board (9 empty spaces)
board = [" "] * 9

print("Our empty board list:")
print(board)
print()

# Let's see what's at different positions
print("Position 0 (top-left):", repr(board[0]))
print("Position 4 (center):", repr(board[4]))  
print("Position 8 (bottom-right):", repr(board[8]))
print()

# Now let's place some symbols and see what happens
board[0] = "X"  # Top-left
board[4] = "O"  # Center  
board[8] = "X"  # Bottom-right

print("After placing some symbols:")
print(board)

๐Ÿค” Think About This

  1. What does [" "] * 9 do? Try changing the 9 to a different number and see what happens.
  2. Why do we use repr(board[0]) instead of just board[0]?
  3. If a player wants to place their symbol at position 7, what list index should we use?

โœ‹ Checkpoint

Make sure you understand how list indexing works before continuing!

๐ŸŽจPhase 2: Making the Board Look Like Tic-Tac-Toe

Our list [" ", " ", "X", " ", "O", " ", " ", " ", " "] doesn't look much like a tic-tac-toe board! Let's fix that.

The Display Pattern

We need to turn this:

[" ", " ", "X", " ", "O", " ", " ", " ", " "]

Into this:

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

The Mapping

List positions: Display positions: 0 1 2 โ†’ top row 3 4 5 โ†’ middle row 6 7 8 โ†’ bottom row
# ๐Ÿ”ง Mini-Challenge 2: Build the Display Pattern

# Let's start with a board that has some moves
board = [" ", " ", "X", " ", "O", " ", " ", " ", "X"]

print("Current board list:", board)
print()
print("Let's display it as a tic-tac-toe board:")
print()

# First, let's just print the positions manually to understand the pattern
print("Row 1 - positions 0, 1, 2:")
print(f" {board[0]} | {board[1]} | {board[2]}")

print("Separator:")
print("---+---+---")

print("Row 2 - positions 3, 4, 5:")  
print(f" {board[3]} | {board[4]} | {board[5]}")

print("Separator:")
print("---+---+---")

print("Row 3 - positions 6, 7, 8:")
print(f" {board[6]} | {board[7]} | {board[8]}")

๐ŸŽฏ Making It Automatic

Typing out the board display every time would be tedious! This is where functions come in.

A function is like a recipe - you write the steps once, then you can use them over and over.

def function_name(): # Steps go here # These steps run every time you "call" the function

Let's create a print_board() function!

# ๐Ÿ”ง Mini-Challenge 3: Create Your First Function

def print_board(board):
    """This function takes a board list and displays it as tic-tac-toe"""
    print()  # Empty line for spacing
    print(f" {board[0]} | {board[1]} | {board[2]}")
    print("---+---+---")
    print(f" {board[3]} | {board[4]} | {board[5]}")
    print("---+---+---")
    print(f" {board[6]} | {board[7]} | {board[8]}")
    print()  # Empty line for spacing

# Test it out!
test_board = ["X", " ", "O", " ", "X", " ", "O", " ", " "]
print("Testing our function:")
print_board(test_board)

# Try with a different board
another_board = [" "] * 9  # Empty board
print("Empty board:")
print_board(another_board)

๐ŸŽฎPhase 3: Getting Player Input

Now we tackle the first section of our flowchart:

+----------------+ invalid โ†’ retry | GET INPUT |---------------------+ +-------+--------+ | | valid | v | Continue... | | +----------------------------------+

What We Need To Handle:

  1. Get input from player: "Enter your move (1-9)"
  2. Validate it's a number: What if they type "hello"?
  3. Check it's in range: What if they type "15"?
  4. Convert to list index: Player says "5", we need index 4
# ๐Ÿ”ง Mini-Challenge 4: Understanding Input

# Let's see what happens with basic input
player_input = input("Enter a position (1-9): ")

print(f"You entered: '{player_input}'")
print(f"Type of input: {type(player_input)}")

# Try to use it as a number
try:
    position = int(player_input)
    print(f"As a number: {position}")
    
    # Convert to list index (subtract 1)
    list_index = position - 1
    print(f"List index: {list_index}")
    
except ValueError:
    print("Oops! That's not a number!")

๐Ÿšจ What Could Go Wrong?

Run the cell above and try these inputs to see what happens:

  • 5 (normal input)
  • hello (not a number)
  • 15 (too big)
  • 0 (too small)
  • 3.5 (decimal)

๐Ÿค” Think About This

What problems did you find? How should our game handle each case?

The Solution: We need to check for these problems and ask again if there's an issue!

# ๐Ÿ”ง Mini-Challenge 5: Build Input Validation

def get_valid_input():
    """Get a valid position (1-9) from the player"""
    while True:  # Keep asking until we get valid input
        player_input = input("Enter your move (1-9): ")
        
        # Check if it's a number
        if not player_input.isdigit():
            print("Invalid input! Please enter a number.")
            continue  # Go back to start of loop
            
        # Convert to integer
        position = int(player_input)
        
        # Check if it's in the right range
        if position < 1 or position > 9:
            print("Invalid position! Choose a number between 1 and 9.")
            continue  # Go back to start of loop
            
        # If we get here, the input is valid!
        return position - 1  # Convert to list index (0-8)

# Test it!
print("Testing input validation:")
print("Try entering invalid inputs to see what happens!")
valid_index = get_valid_input()
print(f"Great! You chose list index: {valid_index}")

๐ŸŽฏPhase 4: Processing Moves

Now for section 2 of our flowchart:

+----------------+ no +---------------+ | SPOT EMPTY? |-----------> | SAY "TAKEN" | +-------+--------+ +-------+-------+ | yes | v | +----------------+ | | PLACE SYMBOL | | +-------+--------+ |

What We Need:

  1. Check if spot is empty: Is board[index] == " "?
  2. Handle taken spots: Tell player and let them try again
  3. Place the symbol: board[index] = "X" or "O"
# ๐Ÿ”ง Mini-Challenge 6: Handle Move Placement

def make_move(board, position, player):
    """Try to place a player's symbol at the given position"""
    if board[position] != " ":
        print("That spot is already taken! Try again.")
        return False  # Move failed
    else:
        board[position] = player
        return True   # Move succeeded

# Test it!
test_board = [" "] * 9
print("Starting with empty board:")
print_board(test_board)

# Try to place X at position 4 (center, list index 4)
print("Player X chooses center (position 5):")
success = make_move(test_board, 4, "X")
if success:
    print_board(test_board)

# Try to place O in the same spot
print("Player O also tries center:")
success = make_move(test_board, 4, "O")
if success:
    print_board(test_board)
else:
    print("Move was blocked!")

๐Ÿ†Phase 5: Detecting Wins

Time for section 3 of our flowchart - the tricky part!

+----------------+ yes +------------+ | PLAYER WON? |------> | ANNOUNCE | +-------+--------+ | WIN | | no +------------+ v +----------------+ yes +------------+ | BOARD FULL? |------> | ANNOUNCE | +-------+--------+ | TIE | | no +------------+

All the Ways to Win

There are exactly 8 ways to win tic-tac-toe:

Rows: Columns: Diagonals: 1|2|3 1|4|7 1|5|9 -+-+- -+-+- -+-+- 4|5|6 2|5|8 3|5|7 -+-+- -+-+- 7|8|9 3|6|9

In list indices (0-8):

[0,1,2] [3,4,5] [6,7,8] [0,3,6] [1,4,7] [2,5,8] [0,4,8] [2,4,6]
# ๐Ÿ”ง Mini-Challenge 7: Build Win Detection

def check_winner(board, player):
    """Check if the given player has won"""
    # All possible winning combinations (as list indices)
    win_combinations = [
        [0, 1, 2],  # Top row
        [3, 4, 5],  # Middle row
        [6, 7, 8],  # Bottom row
        [0, 3, 6],  # Left column
        [1, 4, 7],  # Middle column
        [2, 5, 8],  # Right column
        [0, 4, 8],  # Top-left to bottom-right diagonal
        [2, 4, 6]   # Top-right to bottom-left diagonal
    ]
    
    # Check each winning combination
    for combo in win_combinations:
        if (board[combo[0]] == player and 
            board[combo[1]] == player and 
            board[combo[2]] == player):
            return True
    
    return False

# Test it!
# Create a winning board for X
winning_board = ["X", "X", "X", " ", "O", " ", " ", "O", " "]
print("Test board:")
print_board(winning_board)

print("Does X win?", check_winner(winning_board, "X"))
print("Does O win?", check_winner(winning_board, "O"))
# ๐Ÿ”ง Mini-Challenge 8: Detect Ties

def is_tie(board):
    """Check if the board is full (tie game)"""
    return " " not in board

# Test it!
full_board = ["X", "O", "X", "O", "X", "O", "O", "X", "O"]
print("Full board:")
print_board(full_board)
print("Is it a tie?", is_tie(full_board))

partial_board = ["X", "O", " ", "O", "X", " ", " ", " ", " "]
print("\nPartial board:")
print_board(partial_board)
print("Is it a tie?", is_tie(partial_board))

๐Ÿ”„Phase 6: The Game Loop

Final section of our flowchart:

+----------------+ | SWITCH PLAYER | +-------+--------+ | +-------> LOOP BACK TO START

The Game Loop Structure:

  1. Initialize: Empty board, start with X
  2. Loop until game ends:
    • Show board
    • Get valid input
    • Try to make move
    • Check for win/tie
    • Switch players
  3. Announce result
# ๐ŸŽฎ The Complete Game!

def play_game():
    """Run a complete game of tic-tac-toe"""
    # Initialize the game
    board = [" "] * 9
    current_player = "X"
    
    print("๐ŸŽฎ Welcome to Tic-Tac-Toe!")
    print("Players take turns choosing a position (1โ€“9).")
    print()
    
    # Show reference board
    reference = ["1", "2", "3", "4", "5", "6", "7", "8", "9"]
    print("Board positions:")
    print_board(reference)
    
    # Main game loop
    while True:
        print(f"Current board:")
        print_board(board)
        
        # Get valid input
        print(f"Player {current_player}'s turn!")
        position = get_valid_input()
        
        # Try to make the move
        if make_move(board, position, current_player):
            # Move successful - check for win/tie
            if check_winner(board, current_player):
                print_board(board)
                print(f"๐ŸŽ‰ Player {current_player} wins!")
                break
                
            if is_tie(board):
                print_board(board)
                print("๐Ÿค It's a tie!")
                break
                
            # Switch players
            current_player = "O" if current_player == "X" else "X"
        
        # If move failed, player tries again (don't switch players)

# Run the game!
if __name__ == "__main__":
    play_game()

๐ŸŽ‰Congratulations!

You've built a complete tic-tac-toe game! Let's reflect on what you accomplished:

What You Learned

๐Ÿ“‹ Lists and Indexing

  • Used a list to represent the game board
  • Learned about 0-based indexing vs 1-based user input
  • Manipulated list elements

๐Ÿ”ง Functions

  • print_board() - Organized code into reusable blocks
  • get_valid_input() - Handled user input and validation
  • make_move() - Processed game moves
  • check_winner() - Implemented game logic

๐ŸŽฏ Control Flow

  • Conditionals: if/else for decision making
  • Loops: while for input validation and game loop
  • Boolean logic: Combined conditions for win detection

๐Ÿ’ฌ Input/Output

  • input() for getting user data
  • print() for displaying information
  • String formatting with f-strings

๐Ÿค” Reflection Questions

  1. Which part was most challenging? Why?
  2. How does the flowchart help in understanding the code?
  3. What would you improve about this game?

๐Ÿš€Enhancement Ideas

Ready for more? Try adding these features:

Easy Enhancements:

  • Player names: Let players enter custom names instead of X/O
  • Score tracking: Keep track of wins across multiple games
  • Better display: Add colors or emojis

Medium Enhancements:

  • Computer player: Add an AI opponent that makes random moves
  • Input shortcuts: Allow 'q' to quit, 'r' to restart

Challenge Enhancements:

  • Smart AI: Make the computer player strategic
  • Different board sizes: 4x4 or 5x5 tic-tac-toe
  • Network play: Play against someone online

๐Ÿ” Code Review

Go back through your code and see if you can:

  • Trace through the flowchart with your actual code
  • Identify each function's purpose
  • Understand why each loop and condition is necessary
  • Spot potential improvements

You're now a game developer! ๐ŸŽฎ

๐Ÿ“šKey Takeaways

The Development Process We Used:

  1. ๐Ÿ“‹ Plan First: Created flowchart before coding
  2. ๐Ÿงฉ Break It Down: Divided into manageable pieces
  3. ๐Ÿ”ง Build Incrementally: Coded and tested each part
  4. ๐Ÿ”„ Integrate: Combined pieces into final game
  5. ๐ŸŽฏ Test & Reflect: Verified it works and considered improvements

Programming Concepts Mastered:

  • Data Structures: Lists for storing game state
  • Functions: Organizing code into reusable blocks
  • Control Flow: Loops and conditionals for game logic
  • Input Validation: Handling user errors gracefully
  • Boolean Logic: Complex condition checking
  • Code Organization: Breaking complex problems into simple parts

Most Important Lesson:

Understand the problem before you code the solution!

The flowchart was your roadmap. Every piece of code maps directly to a step in that flowchart. This is how professional developers work on complex projects.

๐ŸŽ‰ You're ready for your next coding adventure!