Back to Basics - Tic Tac Toe

I've seen a few TTT related posts recently, and thought I might learn something or be reminded of certain techniques from attempting my own rendition. Here is that attempt! Hopefully any new coders can benefit from this as well. Any improvement suggestions are welcome, and numpy, scipy & bitwise operations are open to aid optimization.

ttt.py

import numpy as np
from random import randint

def print_board(board):
print("""
+---+---+---+
c | {}   {}   {} |
+   +   +   +
b | {}   {}   {} |
+   +   +   +
a | {}   {}   {} |
+---+---+---+
1   2   3
""".format(*list(x if x != '0' else ' ' for x in board)))

def check_for_win(board):
def check_diagonal(dia):
return '0' not in dia and len(set(dia)) == 1

a = np.array(board).reshape(3, 3)

# check lr diagonal
if check_diagonal(a.diagonal()):
return True

# check rl diagonal
if check_diagonal(np.fliplr(a).diagonal()):
return True

# check rows & cols
for matr in [a, np.transpose(a)]:
for row in matr:
if '0' not in row and len(set(row)) == 1:
return True

return False

if __name__ == "__main__":
board = ['0'] * 9
codes = ('c1', 'c2', 'c3', 'b1', 'b2', 'b3', 'a1', 'a2', 'a3')
turn, user = 1, 'X' if bool(randint(0, 1)) else 'O'
print('Welcome! First to play is %s!' % user)
print_board(board)

while True:
print('Player %s: ' % user)
code = input().strip().lower()

if code in codes:
idx = codes.index(code)
board[idx] = user
print_board(board)

if turn >= 5:
if '0' not in board:
print("Aw, it's a draw!")
exit()
elif check_for_win(board):
print('%s won in %d turns! Congratulations!' % (user, turn))
exit()

user = 'X' if user == 'O' else 'O'
turn = turn + 1
else:
print("Hmm, that's not a valid input.")

Input Validation

You aren’t completely validating input

• X: a1
• O: b2
• X: a2
• O: a3 (block)
• X: a3 For the Win!?

Invalid Win/Draw Detection Logic:

X  O  X
O     O
X  O  X

Last move in centre: “Aw, it’s a draw!”???

exit()

This is a bad function to use. It exits the Python interpreter. Unconditionally. Period.

When you want to change your code to play repeated games, run unit tests, etc., you won’t be able to. Nothing will execute after exit().

Better would be to move the code into a function, and simply return to force an exit of the current local context, instead of an exit of the entire interpreter.

f-strings

You are using {} and .format() in print_board, and using %s and %d in "..." % (...) expressions. Usually, you should try to avoid mixing formatting methods. Both are awkward to use, as the value and the place the value goes are far apart in the code. Avoid using the % style.