This is a completely functional python script of the Tic-Tac-Toe game. I've used python's set
container for almost all data structure and operation. The code is small and somewhat readable. I would very much appreciate your opinion and suggestions to improve it in terms of speed, readability and refactoring wherever necessary. Please post in your answer any modifications needed in the algorithm used.
- The grid is marked from 1 to 9 horizontally.
- The
TTT
class handles all the tasks. self.ploys
is a dictionary containing all the solution combinations.{1: [{1,2,3}, {1,4,7}, {1,5,9}], 2:[{1,2,3}, {2,5,8}], ...}
The Main Loop displays the output, cycles through each player's turn and checks the result.
self.PlayUser
andself.PlayCom
plays respective turns.
#!/usr/bin/env python3
import os
erase = 'cls' if os.name == 'nt' else 'clear'
# 3x3 grid and 8 solution sets
grid = set(range(1,10))
sols = ({1,2,3}, {4,5,6},
{7,8,9}, {1,4,7},
{2,5,8}, {3,6,9},
{1,5,9}, {3,5,7})
class TTT(object):
def __init__(self):
# Valid combinations (shortens during game)
self.ploys = {k:[s for s in sols if k in s] for k in grid}
# Holds two sets; user moves and com moves
self.umoves = set()
self.cmoves = set()
def GameOver(self, moves):
"""Returns True if the puzzle is solved, False if it's a tie."""
if any(s.issubset(moves) for s in sols):
return True
elif (self.umoves|self.cmoves) == grid:
return False
def PlayUser(self):
"""Plays the user's turn"""
umove = int(input())
# Filter invalid moves
while umove not in grid or (umove in self.umoves or
umove in self.cmoves):
umove = int(input("Invalid Move "))
self.umoves.add(umove)
# Modify ploys
del self.ploys[umove]
for p, s in self.ploys.items():
for i in [i for i,n in enumerate(s) if umove in n]:
del self.ploys[p][i]
def PlayCom(self, cmove=None, best=None):
"""Plays the com's turn"""
for p, s in self.ploys.items():
# Check if com can win
if any(n.issubset({p}|self.cmoves) for n in s):
cmove = p
break
# Check if user can win
elif any(n.issubset({p}|self.umoves) for n in sols if p in n):
cmove = p
# Get the next best move
elif best is None or len(s) > len(self.ploys[best]):
best = p
cmove = cmove or best
self.cmoves.add(cmove)
del self.ploys[cmove]
def Show(self, cols=3, com="X", usr="O", fill=" "):
"""Prints the grid in the command-line."""
os.system(erase)
for g in grid:
if g in self.cmoves:
print(com, fill, end="") if g % cols else print(com)
elif g in self.umoves:
print(usr, fill, end="") if g % cols else print(usr)
else:
# Replace g with fill for clean grid
print(g, fill, end="") if g % cols else print(g)
def Main(self):
"""Stars the game."""
from itertools import cycle
text = ("You Won", "You Lost")
move = (self.umoves, self.cmoves)
play = (self.PlayUser, self.PlayCom)
for t, m, p in cycle(zip(text,move,play)):
self.Show()
p()
self.Show()
status = self.GameOver(m)
if status is not None:
print(t) if status is True else print("Game Over.")
exit()
if __name__ == "__main__":
game = TTT()
game.Main()