""" tic_tac_toe.py A object-oriented graphical version of Tic Tac Toe using Zelle's graphics library. The objects are : pieces : Empty , X , O players: User , Computer game: Board The User & Computer objects have the same API, and so can either can be used to get a move. Likewise, the Empty, X, O objects have the same API, including a method to draw themselves. The Board object stores both the pieces and the players, and its methods play the game. Jim Mahoney | Nov 2018 | cs.marlboro.college | MIT License """ from graphics import * from random import randint DEBUG = True def print_debug(message): if DEBUG: print(message) class Empty: """ An empty mark on the board """ def __init__(self, x, y): self.what = 'Empty' def draw(self): pass class O: """ An O mark on the board """ def __init__(self, x, y): self.x = x self.y = y self.what = 'O' def draw(self, window): size = 0.6 o = Circle(Point(self.x, self.y), size/2) o.setOutline('black') o.setWidth(6) o.draw(window) class X: """ An X mark on the board. """ def __init__(self, x, y): self.x = x self.y = y self.what = 'X' def draw(self, window): size = 0.6 (top, bottom) = (self.y + size/2, self.y - size/2) (left, right) = (self.x - size/2, self.x + size/2) for (x1,y1, x2,y2) in ( (left,top, right,bottom), (right,top, left,bottom ) ): line = Line(Point(x1,y1), Point(x2,y2)) line.setFill('black') line.setWidth(7) line.draw(window) class Board: """ a tic tac toe game """ # # board coordinates & place numbers # # 1=x 2=x 3=x 4=x # # 0 | 1 | 2 4=y # | | # -----+------+----- 3=y # 3 | 4 | 5 # -----+------+----- 2=y # | | # 6 | 7 | 8 1=y # # def __init__(self, players): # Create & store the graphics window. self.window = GraphWin('tic tac toe', 500, 500) self.window.setCoords(0.5, 0.5, 4.5, 4.5) # Draw the horizontal & vertical lines for (top,left,bottom,right) in ( (1,3, 4,3), (1,2, 4,2), (2,4, 2,1), (3,4, 3,1) ): line = Line(Point(left,top), Point(right,bottom)) line.setFill('blue') line.setWidth(4) line.draw(self.window) # Define the nine coordinate locations. self.places = [None]*9 i = 0 for y in (3.5, 2.5, 1.5): for x in (1.5, 2.5, 3.5): self.places[i] = (x, y) i = i + 1 # Set up storage to put a "mark" (i.e. X, O, Empty) # in each of the nine locations, and fill them with Empty objects. self.marks = [None]*9 for i in range(9): (x, y) = (self.places[i][0], self.places[i][1]) self.marks[i] = Empty(x, y) # Set up the game variables. self.turn = 'X' self.moves = 0 # Set objects for each player, one for X and one for O. if players: self.players = players else: # TODO : # some sort of graphical toggle to choose X, O as user or computer self.players = {'X': User(), 'O': Computer()} print_debug('\n--- debug output ---') print_debug('players: X is {}, O is {}'.format( players['X'].name, players['O'].name)) def get_closest_place(self, point): """ Given a coordinate point (i.e. a click),n return the index (0..8) the closest location """ # I'll loop over all the places and remember which is closest. closest_distance = 100 # start with something big. result = -1 # start with a nonsensical result. for i in range(9): (x, y) = (self.places[i][0], self.places[i][1]) # one of 9 places distance = (x - point.x)**2 + (y - point.y)**2 if distance < closest_distance: closest_distance = distance result = i return result def winner(self): """ return 'X' if X has won, 'Y' if Y has won, 'draw' if tie, False otherwise """ # There are eight possible triples to check for a winning line. # The numbers here are the places i.e. 0 .. 8. lines = ((0, 1, 2), # horizontal (3, 4, 5), (6, 7, 8), (0, 3, 6), # vertical (1, 4, 7), (2, 5, 8), (0, 4, 8), # diagonal (2, 4, 6)) for (a, b, c) in lines: what_a = self.marks[a].what # The three things in that line.n what_b = self.marks[b].what what_c = self.marks[c].what if what_a != 'Empty' and \ (what_a == what_b) and \ (what_b == what_c): return what_a # return winner, 'X' or 'Y' if self.moves >= 9: return 'draw' # No winner, 9 moves => draw return False def show_final(self, winner): """ Display a message at the bottom of the window. """ if winner == 'X': message = 'X wins!!' elif winner == 'O': message = 'O wins!!' elif winner == 'draw': message = 'Draw!!' else: message = winner + ' ??? ' # ... shouldn't get here. message = message + ' (Click to quit.)' text = Text(Point(2.5, 0.75), message) text.setSize(36) text.setTextColor('red') text.draw(self.window) def play(self): """ Would you like to play a game? """ while True: self.make_mark() # Mark X or O. winner = self.winner() # Did someone win? print_debug('winner = {}'.format(winner)) if winner: # Game over man. self.show_final(winner) wait = self.window.getMouse() return def make_mark(self): """ Get the next move (either from a user click or computer player), then put an X or Y at that position. """ whose_turn = self.players[self.turn] # a User or Computer object print_debug('\n{} player is {}'.format(self.turn, whose_turn.name)) where = whose_turn.get_move(self) # 0..8 location of next mark. print_debug('where = {}'.format(where)) (x, y) = self.places[where] # coords of where mark goes print_debug('place x,y = {},{}'.format(x,y)) print_debug('old mark is {}'.format(self.marks[where])) if self.marks[where].what == 'Empty': if self.turn == 'X': mark = X(x, y) self.turn = 'O' else: mark = O(x,y) self.turn = 'X' self.marks[where] = mark mark.draw(self.window) # draw this mark on the board self.moves = self.moves + 1 class User: """ Get moves from user clicks on the board """ def __init__(self): self.name = 'user' def get_move(self, board): """ return which place (0 .. 8) the user wants to move at. """ click = board.window.getMouse() print_debug('click: {}, {}'.format(click.x, click.y)) where = board.get_closest_place(click) return where class Computer: """ Get moves from a computer player """ def __init__(self): self.name = 'computer' def get_move(self, board): """ return (0 .. 8) random empty location """ while True: # Keep picking a random spot until we find an empty one. # (A stupid method but it will work ... eventually.) where = randint(0, 8) if board.marks[where].what == 'Empty': return where def get_players(): """ Given information provided by user on terminal; return dictionary of player objects """ # TODO: make this part of the GUI; # perhaps a Choice graphical thingy within the Board. print("-- tic tac toe --") players = {} for who in ('X', 'O'): choice = input('{} is user or computer? '.format(who)) if not choice: choice = 'user' if choice.lower()[0] == 'u': players[who] = User() else: players[who] = Computer() return players def main(): players = get_players() board = Board(players) board.play() main()