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.")
2 Answers 2
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.
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
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\$