14
\$\begingroup\$

I wrote a mediocre Tic Tac Toe game. Take a look.

# Tic-Tac-Toe

# Plays the game of tic-tac-toe against a human opponent

# global constants

X = "X"

O = "O"

EMPTY = " "

TIE = "TIE"

NUM_SQUARES = 9

def display_instruct():
    """Display game instructions."""


    print(

    """
    Welcome to the greatest intellectual challenge of all time: Tic-Tac-Toe.
    This will be a showdown between your human brain and my silicon processor.
    You will make your move known by entering a number, 0 - 8. The number
    will correspond to the board position as illustrated:
    0 | 1 | 2
    ---------
    3 | 4 | 5
    ---------
    6 | 7 | 8

    Prepare yourself, human. The ultimate battle is about to begin. \n
    """
    )
def ask_yes_no(question):
    """Ask a yes or no question."""
    response = None
    while response not in ("y", "n"):
        response = input(question).lower()
    return response

def ask_number(question, low, high):

    """Ask for a number within a range."""
    response = None
    while response not in range(low, high):
        response = int(input(question))
    return response

def pieces():

    """Determine if player or computer goes first."""
    go_first = ask_yes_no("Do you require the first move? (y/n): ")
    if go_first == "y":
        print("\nThen take the first move. You will need it.")
        human = X
        computer = O
    else:
         print("\nYour bravery will be your undoing... I will go first.")
         computer = X
         human = O
    return computer, human

def new_board():

    """Create new game board."""
    board = []
    for square in range(NUM_SQUARES):
        board.append(EMPTY)
    return board

def display_board(board):

    """Display game board on screen."""
    print("\n\t", board[0], "|", board[1], "|", board[2])
    print("\t", "---------")
    print("\t", board[3], "|", board[4], "|", board[5])
    print("\t", "---------")
    print("\t", board[6], "|", board[7], "|", board[8], "\n")

def legal_moves(board):

    """Create list of legal moves."""
    moves = []
    for square in range(NUM_SQUARES):
        if board[square] == EMPTY:
            moves.append(square)
    return moves

def winner(board):

    """Determine the game winner."""
    WAYS_TO_WIN = ((0, 1, 2),
    (3, 4, 5),
    (6, 7, 8),
    (0, 3, 6),
    (1, 4, 7),
    (2, 5, 8),
    (0, 4, 8),
    (2, 4, 6))
    for row in WAYS_TO_WIN:
        if board[row[0]] == board[row[1]] == board[row[2]] != EMPTY:
            winner = board[row[0]]
            return winner
    if EMPTY not in board:
        return TIE
        return None
def human_move(board, human):

    """Get human move."""
    legal = legal_moves(board)
    move = None
    while move not in legal:
        move = ask_number("Where will you move? (0 - 8):", 0, NUM_SQUARES)
        if move not in legal:
            print("\nThat square is already occupied, foolish human. Choose another.\n")
            print("Fine...")
    return move

def computer_move(board, computer, human):

    """Make computer move."""
    # make a copy to work with since function will be changing list
    board = board[:]
    # the best positions to have, in order
    BEST_MOVES = (0, 7, 6, 3, 8, 1, 4, 5, 2)
    print("I shall take square number", end=" ")
    # if computer can win, take that move
    for move in legal_moves(board):
        board[move] = computer
        if winner(board) == computer:
            print(move)
            return move
    # done checking this move, undo it
    board[move] = EMPTY
    # if human can win, block that move
    for move in legal_moves(board):
        board[move] = human
        if winner(board) == human:
            print(move)
            return move
    # done checking this move, undo it
    board[move] = EMPTY
    # since no one can win on next move, pick best open square
    for move in BEST_MOVES:
        if move in legal_moves(board):
            print(move)
        return move

def next_turn(turn):

    """Switch turns."""
    if turn == X:
        return O
    else:
        return X

def congrat_winner(the_winner, computer, human):

    """Congratulate the winner."""
    if the_winner != TIE:
        print(the_winner, "won!\n")
    else:
        print("It's a tie!\n")
    if the_winner == computer:
        print("As I predicted, human, I am triumphant once more. \n" \
        "Proof that computers are superior to humans in all regards.")
    elif the_winner == human:
        print("No, no! It cannot be! Somehow you tricked me, human. \n" \
        "But never again! I, the computer, so swear it!")
    elif the_winner == TIE:
        print("You were most lucky, human, and somehow managed to tie me. \n" \
        "Celebrate today... for this is the best you will ever achieve.")

def main():

    display_instruct()
    computer, human = pieces()
    turn = X
    board = new_board()
    display_board(board)
    while not winner(board):
        if turn == human:
            move = human_move(board, human)
            board[move] = human
        else:
            move = computer_move(board, computer, human)
            board[move] = computer
        display_board(board)
        turn = next_turn(turn)
    the_winner = winner(board)
    congrat_winner(the_winner, computer, human)

# start the program

main()
input("\n\nPress the enter key to quit.")
\$\endgroup\$
2
  • \$\begingroup\$ You could implement some sort of ai into your program. I managed to win the first game I played. Because the program didn't saw the 'X X' - position. \$\endgroup\$
    – Jakube
    Dec 3, 2014 at 17:53
  • 1
    \$\begingroup\$ There's a Python book called Python Programming Third Edition: For The Absolute Beginner by Michael Dawson where this exact code features, although this guy has changed the order for BEST_MOVES presumably to make an unbeatable opponent, as one of the 'challenges' the book provides is to do this (I was doing this task myself and became a bit unsure by the challenge). This might be why there is a lot of repetition in the coding, as the book structures its chapters in a different way to the 'most efficient route' possible from the start. \$\endgroup\$
    – user77951
    Jul 14, 2015 at 19:32

2 Answers 2

4
\$\begingroup\$

WAYS_TO_WIN could be a global constant too, like the other variables you named with all caps.

X and O are not great names. Single-letter variable names are usually not great, and O is easy to mistake for 0 (zero). I'd recommend something like this instead:

MOVE_X = "X"
MOVE_O = "O"

Or, considering that EMPTY doesn't explain itself very much either, and all these represent possible tile values in the game:

TILE_X = "X"
TILE_O = "O"
TILE_EMPTY = " "

This is not very efficient:

while response not in range(low, high):
    response = int(input(question))

It generates a range every time, and if the value is not in the range, it iterates over all items every time. It would be better to check using comparisons with low and high:

while not low <= response < high:
    response = int(input(question))

legal_moves could be written much simpler using a list comprehension:

def legal_moves(board):    
    """Create list of legal moves."""
    return [x for x in range(NUM_SQUARES) if board[x] == EMPTY]

This code checks not in legal unnecessarily many times:

move = None
while move not in legal:
    move = ask_number("Where will you move? (0 - 8):", 0, NUM_SQUARES)
    if move not in legal:
        print("\nThat square is already occupied, foolish human. Choose another.\n")
        print("Fine...")

It could be refactored to avoid wasted operations:

while True:
    move = ask_number("Where will you move? (0 - 8):", 0, NUM_SQUARES)
    if move not in legal:
        print("\nThat square is already occupied, foolish human. Choose another.\n")
        print("Fine...")
    else:
        break

You have several other similar loops with wasted operations that could be refactored similarly.

\$\endgroup\$
1
\$\begingroup\$

Remember that in programming you should never repeat yourself.

Now look the following two functions that you wrote:

def ask_yes_no(question):
    """Ask a yes or no question."""
    response = None
    while response not in ("y", "n"):
        response = input(question).lower()
    return response

def ask_number(question, low, high):

    """Ask for a number within a range."""
    response = None
    while response not in range(low, high):
        response = int(input(question))
    return response

You can easily create one function that does the job of both.

def ask(question,choices):
    """Ask a question, keep asking again
    until the user enters a valid choice."""
    response = None
    while response not in choices:
        response = input(question).lower()
    return response

You are repeating yourself down here too: (too many print functions)

def display_board(board):

    """Display game board on screen."""
    print("\n\t", board[0], "|", board[1], "|", board[2])
    print("\t", "---------")
    print("\t", board[3], "|", board[4], "|", board[5])
    print("\t", "---------")
    print("\t", board[6], "|", board[7], "|", board[8], "\n")

You should use triple quoted string and .format()

def display_board(board):

    """Display game board on screen."""
    print("""
    {0} | {1} | {2}
    ------------
    {3} | {4} | {5}
    ------------
    {6} | {7} | {8} """.format(board[0],board[1],board[2],board[3],board[4],
    board[5],board[6],board[7],board[8]))

If you were following, you may have noted that this code still repeats the same thing board over and over again. Try and make it less repetitive.

"""The board... """.format(*board) * unpacks a list

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.