Skip to content

Module quickpython.examples.tictactoe

Ultimate Tic-Tac-Toe, by Al Sweigart al@inventwithpython.com Instead of a board with 9 spaces, this game has 9 boards with 81 spaces, the winner of each board placing their X or O on the big board! More info at: https://en.wikipedia.org/wiki/Ultimate_tic-tac-toe This and other games are available at https://nostarch.com/XX Tags: large, game, board game, two-player

View Source
"""Ultimate Tic-Tac-Toe, by Al Sweigart al@inventwithpython.com

Instead of a board with 9 spaces, this game has 9 boards with 81 spaces,

the winner of each board placing their X or O on the big board!

More info at: https://en.wikipedia.org/wiki/Ultimate_tic-tac-toe

This and other games are available at https://nostarch.com/XX

Tags: large, game, board game, two-player"""

__version__ = 0

import sys

# Set up the constants:

O_PLAYER = "O"

X_PLAYER = "X"

TIED = "tied"

EMPTY_SPACE = "."

BOARD_WIDTH = 3

BOARD_HEIGHT = 3

CANVAS_WIDTH = 15

CANVAS_HEIGHT = 9

SUBCANVAS_WIDTH = 5

SUBCANVAS_HEIGHT = 3

def main():

    print(

        """Ultimate Tic-Tac-Toe, by Al Sweigart al@inventwithpython.com

Instead of tic-tac-toe with 9 spaces, this game has a "big" board

made up of 9 "small" tic-tac-toe boards. Moving on a small board causes

the next player to move on that relative board. Winning on a small board

lets that player put their mark on the big board. The winner must get

three in a row on the big board.

"""

    )

    turn = X_PLAYER  # X will go first.

    gameBoard = getNewBoard()

    # focusX and focusY determine which small board the player moves on.

    # If they are both None, the player can choose a small board.

    focusX, focusY = None, None

    while True:  # Main game loop.

        displayBoard(gameBoard)

        focusX, focusY = askForPlayerMove(turn, gameBoard, focusX, focusY)

        # Check for a big board winner:

        bigBoard = makeBoardFromSmallBoards(gameBoard)

        bigWinner = getWinner(bigBoard)

        if bigWinner == TIED:

            displayBoard(gameBoard)

            print("The game is a tie!")

            print("Thanks for playing!")

            sys.exit()

        elif bigWinner != None:

            displayBoard(gameBoard)

            print(bigWinner, "has won!")

            print("Thanks for playing!")

            sys.exit()

        # Switch to the other player's turn:

        if turn == X_PLAYER:

            turn = O_PLAYER

        elif turn == O_PLAYER:

            turn = X_PLAYER

def getNewBoard():

    """Returns a dictionary that represents the big tic-tac-toe board.

    Keys are (x, y) int tuples that span from 0 to 2, the values are

    dictonaries that represent small tic-tac-toe boards. These

    dictionaries have (x, y) int tuples as well, and their values are

    either X_PLAYER, O_PLAYER, or EMPTY_SPACE."""

    board = {}

    # Loop over each small board:

    for x in range(BOARD_WIDTH):

        for y in range(BOARD_HEIGHT):

            board[(x, y)] = {}

            # Loop over each space on the small board:

            for smallX in range(BOARD_WIDTH):

                for smallY in range(BOARD_HEIGHT):

                    board[(x, y)][(smallX, smallY)] = EMPTY_SPACE

    return board

def displayBoard(board):

    """Displays the big tic-tac-toe board on the screen."""

    # The canvas is a dictionary that has keys of (x, y) tuples, and

    # the values are the character to print at that place on the screen.

    canvas = {}

    # First, put blank spaces on the entire canvas:

    for x in range(CANVAS_WIDTH):

        for y in range(CANVAS_HEIGHT):

            canvas[(x, y)] = " "

    # Second, fill in the big board Xs and Os on the canvas:

    for x in range(BOARD_WIDTH):

        for y in range(BOARD_HEIGHT):

            winner = getWinner(board[(x, y)])

            if winner == X_PLAYER:

                # Draw a large X for each small board X won:

                canvas[(x * 5 + 1, y * 3 + 0)] = "\\"

                canvas[(x * 5 + 3, y * 3 + 0)] = "/"

                canvas[(x * 5 + 2, y * 3 + 1)] = "X"

                canvas[(x * 5 + 1, y * 3 + 2)] = "/"

                canvas[(x * 5 + 3, y * 3 + 2)] = "\\"

            elif winner == O_PLAYER:

                # Draw a large O for each small board O won:

                canvas[(x * 5 + 2, y * 3 + 0)] = "_"

                canvas[(x * 5 + 1, y * 3 + 1)] = "/"

                canvas[(x * 5 + 3, y * 3 + 1)] = "\\"

                canvas[(x * 5 + 1, y * 3 + 2)] = "\\"

                canvas[(x * 5 + 2, y * 3 + 2)] = "_"

                canvas[(x * 5 + 3, y * 3 + 2)] = "/"

            elif winner == TIED:

                # Draw a large ### block for tied small boards:

                for scx in range(SUBCANVAS_WIDTH):

                    for scy in range(SUBCANVAS_HEIGHT):

                        canvas[(x * 5 + scx, y * 3 + scy)] = "#"

    # Third, fill in the Xs and Os of the small boards on the canvas:

    for ix, smallTopLeftX in enumerate([0, 5, 10]):

        for iy, smallTopLeftY in enumerate([0, 3, 6]):

            if getWinner(board[(ix, iy)]) != None:

                continue

            for x in range(3):

                for y in range(3):

                    canvasx = smallTopLeftX + (x * 2)

                    canvasy = smallTopLeftY + y

                    canvas[(canvasx, canvasy)] = board[(ix, iy)][(x, y)]

    # Print out the tic tac toe board:

    for y in range(9):

        for x in range(15):

            print(canvas[(x, y)], end="")

            if x == 4 or x == 9:

                print("|", end="")

        print()  # Print a newline.

        if y == 2 or y == 5:

            print("-----+-----+-----")

def getWinner(board):

    """Return X_PLAYER, O_PLAYER, or TIED depending on who won. Return

    None if there is no winner and the board isn't full yet."""

    # Create short-named variables for the spaces on this board.

    topL, topM, topR = board[(0, 0)], board[(1, 0)], board[(2, 0)]

    midL, midM, midR = board[(0, 1)], board[(1, 1)], board[(2, 1)]

    botL, botM, botR = board[(0, 2)], board[(1, 2)], board[(2, 2)]

    for player in (X_PLAYER, O_PLAYER):

        if (

            (topL == topM == topR == player)

            or (midL == midM == midR == player)  # Top row

            or (botL == botM == botR == player)  # Middle row

            or (topL == midL == botL == player)  # Bottom row

            or (topM == midM == botM == player)  # Left column

            or (topR == midR == botR == player)  # Middle column

            or (topL == midM == botR == player)  # Right column

            or (topR == midM == botL == player)  # \ diagonal

        ):  # / diagonal

            return player

    # Check for a tie:

    for x in range(BOARD_WIDTH):

        for y in range(BOARD_HEIGHT):

            if board[(x, y)] == EMPTY_SPACE:

                return None  # Return None since there is no winner yet.

    return TIED

def askForPlayerMove(player, board, focusX, focusY):

    """Asks the player which space on which small board to move on.

    The focusX and focusY values determine which small board the player

    can move on, but if they are both None the player can freely choose

    a small board. Returns the (x, y) of the small board the next player

    plays on.

    """

    # Check if the player can freely select any small board:

    if focusX == None and focusY == None:

        # Let the player pick which board they want to move on:

        print(player + ": Enter the BOARD you want to move on.")

        validBoardsToSelect = []

        for xyTuple, smallBoard in board.items():

            if getWinner(smallBoard) == None:

                validBoardsToSelect.append(xyTuple)

        selectedBoard = enter1Through9(validBoardsToSelect)

        focusX = selectedBoard % 3

        focusY = selectedBoard // 3

    # Select the space on the focused small board:

    smallXDesc = ["left", "middle", "right"][focusX]

    smallYDesc = ["top", "middle", "bottom"][focusY]

    print(player, "moves on the", smallYDesc, smallXDesc, "board.")

    validSpacesToSelect = []

    for xyTuple, tile in board[(focusX, focusY)].items():

        if tile == EMPTY_SPACE:

            validSpacesToSelect.append(xyTuple)

    selectedSpace = enter1Through9(validSpacesToSelect)

    x = selectedSpace % 3

    y = selectedSpace // 3

    board[(focusX, focusY)][(x, y)] = player

    # Figure out the small board that the next player must move on:

    if getWinner(board[(x, y)]) == None:

        return (x, y)

    else:

        # If the small board has a winner or is tied, the next player

        # can move on any small board:

        return (None, None)

def enter1Through9(validMoves):

    """Presents a "minimap" of a tic-tac-toe board's spaces, labeled

    with numbers 1 through 9. Returns the numeric space they chose.

    Valid moves is a list of (x, y) tuples representing the spaces

    the player can pick, e.g. [(0, 0), (0, 2)] means the player can

    only pick the top two corner spaces."""

    for i, move in enumerate(validMoves):

        # Convert the (x, y) tuple values to an integer 1 through 9:

        validMoves[i] = str((move[1] * 3 + move[0]) + 1)

    print("      1 2 3")

    print("      4 5 6")

    print("      7 8 9")

    print("Enter your move (1-9) or QUIT:")

    while True:  # Keep asking the player until they enter a valid move.

        response = input("> ").upper()

        if response == "QUIT":

            print("Thanks for playing!")

            sys.exit()

        if response in validMoves:

            # Return a int that is 0-8, not a string that is 1-9.

            return int(response) - 1

        print("You cannot select that space.")

def makeBoardFromSmallBoards(smallBoards):

    bigBoard = {}

    for x in range(BOARD_WIDTH):

        for y in range(BOARD_HEIGHT):

            winner = getWinner(smallBoards[(x, y)])

            if winner == None:

                bigBoard[(x, y)] = EMPTY_SPACE

            elif winner == TIED:

                bigBoard[(x, y)] = TIED

            elif winner in (X_PLAYER, O_PLAYER):

                bigBoard[(x, y)] = winner

    return bigBoard

# If the program is run (instead of imported), run the game:

if __name__ == "__main__":

    main()

Variables

BOARD_HEIGHT
BOARD_WIDTH
CANVAS_HEIGHT
CANVAS_WIDTH
EMPTY_SPACE
O_PLAYER
SUBCANVAS_HEIGHT
SUBCANVAS_WIDTH
TIED
X_PLAYER

Functions

askForPlayerMove

def askForPlayerMove(
    player,
    board,
    focusX,
    focusY
)

Asks the player which space on which small board to move on. The focusX and focusY values determine which small board the player can move on, but if they are both None the player can freely choose a small board. Returns the (x, y) of the small board the next player plays on.

View Source
def askForPlayerMove(player, board, focusX, focusY):

    """Asks the player which space on which small board to move on.

    The focusX and focusY values determine which small board the player

    can move on, but if they are both None the player can freely choose

    a small board. Returns the (x, y) of the small board the next player

    plays on.

    """

    # Check if the player can freely select any small board:

    if focusX == None and focusY == None:

        # Let the player pick which board they want to move on:

        print(player + ": Enter the BOARD you want to move on.")

        validBoardsToSelect = []

        for xyTuple, smallBoard in board.items():

            if getWinner(smallBoard) == None:

                validBoardsToSelect.append(xyTuple)

        selectedBoard = enter1Through9(validBoardsToSelect)

        focusX = selectedBoard % 3

        focusY = selectedBoard // 3

    # Select the space on the focused small board:

    smallXDesc = ["left", "middle", "right"][focusX]

    smallYDesc = ["top", "middle", "bottom"][focusY]

    print(player, "moves on the", smallYDesc, smallXDesc, "board.")

    validSpacesToSelect = []

    for xyTuple, tile in board[(focusX, focusY)].items():

        if tile == EMPTY_SPACE:

            validSpacesToSelect.append(xyTuple)

    selectedSpace = enter1Through9(validSpacesToSelect)

    x = selectedSpace % 3

    y = selectedSpace // 3

    board[(focusX, focusY)][(x, y)] = player

    # Figure out the small board that the next player must move on:

    if getWinner(board[(x, y)]) == None:

        return (x, y)

    else:

        # If the small board has a winner or is tied, the next player

        # can move on any small board:

        return (None, None)

displayBoard

def displayBoard(
    board
)

Displays the big tic-tac-toe board on the screen.

View Source
def displayBoard(board):

    """Displays the big tic-tac-toe board on the screen."""

    # The canvas is a dictionary that has keys of (x, y) tuples, and

    # the values are the character to print at that place on the screen.

    canvas = {}

    # First, put blank spaces on the entire canvas:

    for x in range(CANVAS_WIDTH):

        for y in range(CANVAS_HEIGHT):

            canvas[(x, y)] = " "

    # Second, fill in the big board Xs and Os on the canvas:

    for x in range(BOARD_WIDTH):

        for y in range(BOARD_HEIGHT):

            winner = getWinner(board[(x, y)])

            if winner == X_PLAYER:

                # Draw a large X for each small board X won:

                canvas[(x * 5 + 1, y * 3 + 0)] = "\\"

                canvas[(x * 5 + 3, y * 3 + 0)] = "/"

                canvas[(x * 5 + 2, y * 3 + 1)] = "X"

                canvas[(x * 5 + 1, y * 3 + 2)] = "/"

                canvas[(x * 5 + 3, y * 3 + 2)] = "\\"

            elif winner == O_PLAYER:

                # Draw a large O for each small board O won:

                canvas[(x * 5 + 2, y * 3 + 0)] = "_"

                canvas[(x * 5 + 1, y * 3 + 1)] = "/"

                canvas[(x * 5 + 3, y * 3 + 1)] = "\\"

                canvas[(x * 5 + 1, y * 3 + 2)] = "\\"

                canvas[(x * 5 + 2, y * 3 + 2)] = "_"

                canvas[(x * 5 + 3, y * 3 + 2)] = "/"

            elif winner == TIED:

                # Draw a large ### block for tied small boards:

                for scx in range(SUBCANVAS_WIDTH):

                    for scy in range(SUBCANVAS_HEIGHT):

                        canvas[(x * 5 + scx, y * 3 + scy)] = "#"

    # Third, fill in the Xs and Os of the small boards on the canvas:

    for ix, smallTopLeftX in enumerate([0, 5, 10]):

        for iy, smallTopLeftY in enumerate([0, 3, 6]):

            if getWinner(board[(ix, iy)]) != None:

                continue

            for x in range(3):

                for y in range(3):

                    canvasx = smallTopLeftX + (x * 2)

                    canvasy = smallTopLeftY + y

                    canvas[(canvasx, canvasy)] = board[(ix, iy)][(x, y)]

    # Print out the tic tac toe board:

    for y in range(9):

        for x in range(15):

            print(canvas[(x, y)], end="")

            if x == 4 or x == 9:

                print("|", end="")

        print()  # Print a newline.

        if y == 2 or y == 5:

            print("-----+-----+-----")

enter1Through9

def enter1Through9(
    validMoves
)

Presents a "minimap" of a tic-tac-toe board's spaces, labeled with numbers 1 through 9. Returns the numeric space they chose. Valid moves is a list of (x, y) tuples representing the spaces the player can pick, e.g. [(0, 0), (0, 2)] means the player can only pick the top two corner spaces.

View Source
def enter1Through9(validMoves):

    """Presents a "minimap" of a tic-tac-toe board's spaces, labeled

    with numbers 1 through 9. Returns the numeric space they chose.

    Valid moves is a list of (x, y) tuples representing the spaces

    the player can pick, e.g. [(0, 0), (0, 2)] means the player can

    only pick the top two corner spaces."""

    for i, move in enumerate(validMoves):

        # Convert the (x, y) tuple values to an integer 1 through 9:

        validMoves[i] = str((move[1] * 3 + move[0]) + 1)

    print("      1 2 3")

    print("      4 5 6")

    print("      7 8 9")

    print("Enter your move (1-9) or QUIT:")

    while True:  # Keep asking the player until they enter a valid move.

        response = input("> ").upper()

        if response == "QUIT":

            print("Thanks for playing!")

            sys.exit()

        if response in validMoves:

            # Return a int that is 0-8, not a string that is 1-9.

            return int(response) - 1

        print("You cannot select that space.")

getNewBoard

def getNewBoard(

)

Returns a dictionary that represents the big tic-tac-toe board. Keys are (x, y) int tuples that span from 0 to 2, the values are dictonaries that represent small tic-tac-toe boards. These dictionaries have (x, y) int tuples as well, and their values are either X_PLAYER, O_PLAYER, or EMPTY_SPACE.

View Source
def getNewBoard():

    """Returns a dictionary that represents the big tic-tac-toe board.

    Keys are (x, y) int tuples that span from 0 to 2, the values are

    dictonaries that represent small tic-tac-toe boards. These

    dictionaries have (x, y) int tuples as well, and their values are

    either X_PLAYER, O_PLAYER, or EMPTY_SPACE."""

    board = {}

    # Loop over each small board:

    for x in range(BOARD_WIDTH):

        for y in range(BOARD_HEIGHT):

            board[(x, y)] = {}

            # Loop over each space on the small board:

            for smallX in range(BOARD_WIDTH):

                for smallY in range(BOARD_HEIGHT):

                    board[(x, y)][(smallX, smallY)] = EMPTY_SPACE

    return board

getWinner

def getWinner(
    board
)

Return X_PLAYER, O_PLAYER, or TIED depending on who won. Return None if there is no winner and the board isn't full yet.

View Source
def getWinner(board):

    """Return X_PLAYER, O_PLAYER, or TIED depending on who won. Return

    None if there is no winner and the board isn't full yet."""

    # Create short-named variables for the spaces on this board.

    topL, topM, topR = board[(0, 0)], board[(1, 0)], board[(2, 0)]

    midL, midM, midR = board[(0, 1)], board[(1, 1)], board[(2, 1)]

    botL, botM, botR = board[(0, 2)], board[(1, 2)], board[(2, 2)]

    for player in (X_PLAYER, O_PLAYER):

        if (

            (topL == topM == topR == player)

            or (midL == midM == midR == player)  # Top row

            or (botL == botM == botR == player)  # Middle row

            or (topL == midL == botL == player)  # Bottom row

            or (topM == midM == botM == player)  # Left column

            or (topR == midR == botR == player)  # Middle column

            or (topL == midM == botR == player)  # Right column

            or (topR == midM == botL == player)  # \ diagonal

        ):  # / diagonal

            return player

    # Check for a tie:

    for x in range(BOARD_WIDTH):

        for y in range(BOARD_HEIGHT):

            if board[(x, y)] == EMPTY_SPACE:

                return None  # Return None since there is no winner yet.

    return TIED

main

def main(

)
View Source
def main():

    print(

        """Ultimate Tic-Tac-Toe, by Al Sweigart al@inventwithpython.com

Instead of tic-tac-toe with 9 spaces, this game has a "big" board

made up of 9 "small" tic-tac-toe boards. Moving on a small board causes

the next player to move on that relative board. Winning on a small board

lets that player put their mark on the big board. The winner must get

three in a row on the big board.

"""

    )

    turn = X_PLAYER  # X will go first.

    gameBoard = getNewBoard()

    # focusX and focusY determine which small board the player moves on.

    # If they are both None, the player can choose a small board.

    focusX, focusY = None, None

    while True:  # Main game loop.

        displayBoard(gameBoard)

        focusX, focusY = askForPlayerMove(turn, gameBoard, focusX, focusY)

        # Check for a big board winner:

        bigBoard = makeBoardFromSmallBoards(gameBoard)

        bigWinner = getWinner(bigBoard)

        if bigWinner == TIED:

            displayBoard(gameBoard)

            print("The game is a tie!")

            print("Thanks for playing!")

            sys.exit()

        elif bigWinner != None:

            displayBoard(gameBoard)

            print(bigWinner, "has won!")

            print("Thanks for playing!")

            sys.exit()

        # Switch to the other player's turn:

        if turn == X_PLAYER:

            turn = O_PLAYER

        elif turn == O_PLAYER:

            turn = X_PLAYER

makeBoardFromSmallBoards

def makeBoardFromSmallBoards(
    smallBoards
)
View Source
def makeBoardFromSmallBoards(smallBoards):

    bigBoard = {}

    for x in range(BOARD_WIDTH):

        for y in range(BOARD_HEIGHT):

            winner = getWinner(smallBoards[(x, y)])

            if winner == None:

                bigBoard[(x, y)] = EMPTY_SPACE

            elif winner == TIED:

                bigBoard[(x, y)] = TIED

            elif winner in (X_PLAYER, O_PLAYER):

                bigBoard[(x, y)] = winner

    return bigBoard