I was looking at a code review of a Tic Tac Toe game in Python when I recalled a talk about not using classes, and I wanted to see if I could write a readable, working implementation of the game without classes.
In order, I am interested in improving the code's:
- readability,
- simplicity,
- idiomatic ("Pythonic") usage.
I think there's probably room for improvement around the print statements (stars
feels clunky to me) and improving the readability around the code using potential_win_sequences
, but it's not immediately obvious how to improve it.
import itertools
import numpy as np
players = ["X", "O"]
numbered_positions = np.array([str(i) for i in range(1, 10)]).reshape(3, 3)
# Start a clean board.
board = np.array(["?"] * 9).reshape(3, 3)
# Take turns.
for player in itertools.cycle(players):
valid_choice = False
while valid_choice is False:
print(np.where(board == "?", numbered_positions, board), "\n")
print(f"{player}: Pick a spot [1-9]: ")
user_input = input()
try:
spot = int(user_input)
if spot < 1 or spot > 9:
raise ValueError
spot_index = spot - 1
except ValueError:
error_msg = "Invalid entry, please enter a number 1-9."
stars = "*" * len(error_msg)
print(stars, error_msg, stars, sep="\n")
else:
if board.ravel()[spot_index] == "?":
valid_choice = True
board.ravel()[spot_index] = player
else:
error_msg = "Choose an empty spot."
stars = "*" * len(error_msg)
print(stars, error_msg, stars, sep="\n")
# Check for end of game.
potential_win_sequences = np.vstack(
[
board, # Check rows.
board.T, # Check columns.
board.diagonal(), # Check diagonal.
np.fliplr(board).diagonal(), # Check antidiagonal.
]
)
if any((i == player).all() for i in potential_win_sequences):
print(board)
print(f"{player} wins!")
break
elif "?" not in board:
print(board)
print("Tie game.")
break