Python Forum

Full Version: Tkinter Tic Tac Toe updated
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
Here is the 2nd go.
Hope you enjoy.
As always, I welcome feedback.

#! /usr/bin/env python3
'''Tkinter TIC TAC TOE'''

# Do the imports
import tkinter as tk
from tkinter import ttk
import os
import random as rnd


# This is so rgb colors can be used
def rgbcolor(rgb):
    return '#%02x%02x%02x' % rgb


class Game:
    ''' Game Class '''
    def __init__(self, parent):
        # Setup the root window
        self.parent = parent
        self.parent.columnconfigure(0, weight=1)
        self.parent.rowconfigure(0, weight=1)

        # Create the main container frame
        self.mainframe = tk.Frame(self.parent, height=510, bg=rgbcolor((80, 40, 35)))
        self.mainframe.grid(row=0, column=0, sticky='news')
        self.mainframe.grid_columnconfigure(0, weight=3)
        self.mainframe.grid_rowconfigure(0, weight=3, )

        # Create the header frame
        self.headerframe = tk.Frame(self.mainframe, bg=rgbcolor((80, 40, 35)), height=40)
        self.headerframe.grid(row=0, column=0, sticky='new')
        self.headerframe.grid_columnconfigure(0, weight=3)

        # This will display the header text
        self.header = tk.Canvas(self.headerframe, height=100, width=600, bg=rgbcolor((80, 40, 35)), highlightthickness=0)
        self.header.create_text(600/2.12, 100/1.93, text='TIC TAC TOE', fill=rgbcolor((45,10,5)), font=('sans', int(600/15), 'bold'))
        self.header.create_text(600/2.09, 100/1.87, text='TIC TAC TOE', fill='white', font=('sans', int(600/15), 'bold'))
        self.header.grid(row=0, column=0)

        # Container frame for the buttons
        self.btnframe = tk.Frame(self.mainframe, bg=rgbcolor((80,40,35)), pady=15)
        self.btnframe.grid(row=3, column=0, sticky='s')
        self.btnframe.grid_columnconfigure(0, weight=3)

        # Create the buttons
        btn_style = ttk.Style()
        btn_style.configure('Wild.TButton', foreground='tomato', borderwidth=1, background=rgbcolor((100,60,55)), \
                            highlightthickness=1, highlightcolor=rgbcolor((100,60,55)), font='sans 16 bold')
        btn_style.map('Wild.TButton',
                background = [('active',rgbcolor((125,85,80)))],
                foreground = [('active', 'orange')]
                )
        self.btn = ttk.Button(self.btnframe, text=' Reset ', cursor='hand2', style='Wild.TButton', \
                              command=self.reset)
        self.btn.grid(row=0, column=0, sticky='s', padx=32)

        self.ext_btn = ttk.Button(self.btnframe, text=' Quit ', style='Wild.TButton', command=os.sys.exit)
        self.ext_btn.grid(row=0, column=1, sticky='s', padx=32)

        # Create a message container frame
        self.msgframe = tk.Frame(self.mainframe, borderwidth=1, bg=rgbcolor((80,40,35)))
        self.msgframe.grid(row=2, column=0, pady=8, sticky='n')

        # The label will display game winner
        self.msg_label = tk.Label(self.msgframe, width=44, anchor='w', fg='tomato', bg=rgbcolor((80,40,35)))
        self.msg_label.grid(row=0, column=0, sticky='nw')

        # Initiate the game frame
        self.play()


    # This function will create the game board
    def play(self):
        # Create the container frame for the grid
        self.gameframe = tk.Frame(self.mainframe, relief = 'solid', highlightthickness=3, \
                                  highlightcolor='brown', highlightbackground='tomato', \
                                  borderwidth=3, width=350, bg=rgbcolor((180,140,135)), height=300)
        self.gameframe.grid(row=1,column=0, sticky='n')
        self.gameframe.grid_columnconfigure(0, weight=3)
        self.gameframe.grid_rowconfigure(0, weight=3)

        # This will create a 3x3 label grid and bind each label to the left mouse button
        for i in range(3):
            for j in range(3):
                self.label = tk.Frame(self.gameframe, relief='solid', width=150, height=90, \
                                      borderwidth=1, bg=rgbcolor((180,140,135)))
                self.label.grid(row=j, column=i, sticky='news')
                self.label.bind('<Button-1>', self.player_pick)

        # Create the what is needed for a win. Three 1's for player and three 2's for computer
        self.x_win = [1, 1, 1]
        self.o_win = [2, 2, 2]

        # This is where either a 1 or two will be placed depending on which player went.
        # This represents our 3x3 grid
        self.grid = [[0, 0, 0],[0, 0, 0],[0, 0, 0]]

        # This is the grid coordinates used for the computer to make a random choice.
        # This gets updated when each player makes a choice. e.g. 1 gets removed
        # so it can not be chosen again.
        self.grid_cords = [(0, 0), (0, 1), (0, 2),
                           (1, 0), (1, 1), (1, 2),
                           (2, 0), (2, 1), (2, 2)]

    # Resets the game board
    def reset(self):
        self.winner_label.destroy()
        self.gameframe.destroy()
        self.play()
        # self.msg_label['text'] = ''
        self.gameframe.update()

    # This places either X or O on the games board
    def do_entries(self, text,row, col, fg='brown'):
        label = tk.Label(self.gameframe, fg=fg, bg=rgbcolor((180,140,135)), text=text, \
                         font=('serif', int(150/2 - 85/3), 'bold'))
        label.grid(column = col, row=row)
        # self.labels[col][row]['text'] = text

    # Used for player turn
    def player_pick(self, event):

        # Get the coordinates for the clicked grid
        x_cord = event.x_root - self.gameframe.winfo_rootx()
        y_cord = event.y_root - self.gameframe.winfo_rooty()

        # Store the x and y coordinates in a varible
        loc = self.gameframe.grid_location(x_cord, y_cord)
        try:
            # Call the function to place the entry to the board
            self.do_entries('X',loc[1],loc[0])

            # Remove the grid coordinats from the grid cords above
            self.grid_cords.remove(loc)

            # Store one in the self.grid so we will be able to check for a winneror tie
            self.grid[loc[1]][loc[0]] = 1
        except ValueError as error:
            pass

        # Computers turn
        self.computer()

        # Check to see if any player has won or if there is a tie.
        # Display message of the outcome
        if self.x_win in self.possibilities():
            self.gameframe.destroy()
            self.gameframe.update()
            self.winner()
        elif self.o_win in self.possibilities():
            self.gameframe.destroy()
            self.mainframe.update()
            self.winner()
        else:
            if not self.grid_cords:
                self.gameframe.destroy()
                self.gameframe.update()
                self.winner()

    def winner(self):
        if self.x_win in self.possibilities():
            text = 'Congradulations! You have won!'
        elif self.o_win in self.possibilities():
            text = 'Oh! I\'m sorry, you have lost'
        else:
            text = 'Wow! A tie game!'

        self.winner_label = tk.Label(self.parent, text=text, fg='tomato', \
                         font = 'sans 18 bold', bg=rgbcolor((80,40,35)))
        self.winner_label.grid(column=0, row=0)

    # The computer player setup. All is the same as for player
    def computer(self):
        if self.grid_cords:
            try:
                loc = rnd.choice(self.grid_cords)
                self.grid_cords.remove(loc)
                self.do_entries('0', loc[1], loc[0], fg=rgbcolor((150,60,20)))
                self.grid[loc[1]][loc[0]] = 2
            except ValueError as error:
                print(error)

    # Setup the win possibilities and return
    def possibilities(self):
        # Columns
        col1 = [self.grid[0][0], self.grid[0][1], self.grid[0][2]]
        col2 = [self.grid[0][1], self.grid[1][1], self.grid[2][1]]
        col3 = [self.grid[0][2], self.grid[1][2], self.grid[2][2]]

        # Rows
        row1 = [self.grid[0][0], self.grid[1][0], self.grid[2][0]]
        row2 = [self.grid[1][0], self.grid[1][1], self.grid[1][2]]
        row3 = [self.grid[2][0], self.grid[2][1], self.grid[2][2]]

        # Diagonals
        diag1 = [self.grid[0][0], self.grid[1][1], self.grid[2][2]]
        diag2 = [self.grid[2][0], self.grid[1][1], self.grid[0][2]]

        wins = [col1, col2, col3, row1, row2, row3, diag1, diag2]
        return wins


def main():
    root = tk.Tk()
    root.title('TIC TAC TOE')
    root['borderwidth'] = 2
    root['relief'] = 'solid'
    root['highlightthickness'] = 10
    root['highlightcolor'] = 'tomato'
    root['highlightbackground'] = 'tomato'
    root['bg'] = rgbcolor((80,40,35))
    root.geometry('600x510+150+150')
    root.resizable(width=False, height=False)
    Game(root)
    root.mainloop()

if __name__ == '__main__':
    main()
dude the game is awesome
My first thought would be rather than creating new labels in the grid could you just change the text of the existing label instead
Something like
# This will create a 3x3 label grid and bind each label to the left mouse button
        self.labels = [] # add this
        for i in range(3):
            inner_labels = [] # add this
            for j in range(3):
                self.label = tk.Label(self.gameframe, relief='solid', \
                                      borderwidth=1, width=int(350/25), height=int(300/50.1), \
                                      bg=rgbcolor((180,140,135)))
                self.label.grid(row=j, column=i, sticky='news')
                self.label.bind('<Button-1>', self.player_pick)
                inner_labels.append(self.label) # add this
            self.labels.append(inner_labels) # add this
    # This places either X or O on the games board
    def do_entries(self, text,row, col, fg='brown'):
        # label = tk.Label(self.gameframe, fg=fg, bg=rgbcolor((180,140,135)), text=text, \
        #                  font=('serif', 51, 'bold'))
        # label.grid(column = col, row=row)
         self.labels[col][row]['text'] = text # add this
this works but the font size needs sorting out.
Going to look into your suggestion. I did away with the first label creation. Now it's just frame. Label gets made when the player clicks.