DEV Community

Cover image for Tic Tac Toe 🎮 with Python tkinter - part 2
Jothin kumar
Jothin kumar

Posted on

Tic Tac Toe 🎮 with Python tkinter - part 2

This tutorial is part of a series. Make sure to check out the previous tutorial, if you haven't already.

In this tutorial, we will add a functionality to the Tic-Tac-Toe game: Player vs Computer 👀

Logic in Tic-Tac-Toe 🎮

Let's go ahead and create a function for the computer to determine the moves in this game.

def auto_play():
Enter fullscreen mode Exit fullscreen mode

Before completing the code, let's take a look at the logic 🤔:
Logic in Tic-Tac-Toe game

1. When the game can be won on the next move, either by the computer or the player

Tic Tac Toe with Python tkinter - Game can be won in next move
When the game can be won on the next move by computer, the move should be done. If the game can be won by player on the next move, the best option is to prevent it. It can be implemented in Python as:

# If winning of computer is possible on the next move, go ahead and win the game
for winning_possibility in winning_possibilities:
    winning_possibility.check('O')  # Check how many conditions for winning of computer are satisfied
    if winning_possibility.p1_satisfied and winning_possibility.p2_satisfied:  # If condition 1 and 2 are satisfied, satisfy condition 3
        for point in XO_points:
            if point.x == winning_possibility.x3 and point.y == winning_possibility.y3 and point not in X_points + O_points:  # Find the point that needs to be occupied to satisfy condtion 3 and make sure that it is not already occupied
                point.set()  # Occupy point
                return  # End the function
     elif winning_possibility.p2_satisfied and winning_possibility.p3_satisfied:  # If condition 2 and 3 are satisfied, satisfy condition 1
        for point in XO_points:
            if point.x == winning_possibility.x1 and point.y == winning_possibility.y1 and point not in X_points + O_points:  # Find the point that needs to be occupied to satisfy condition 1 and make sure that it is not already occupied
                point.set()  # Occupy point
                return  # End the function
     elif winning_possibility.p3_satisfied and winning_possibility.p1_satisfied:  # If condition 1 and 3 are satisfied, satisfy condition 2
        for point in XO_points:
            if point.x == winning_possibility.x2 and point.y == winning_possibility.y2 and point not in X_points + O_points:  # Find the point that needs to be occupied to satisfy condition 2 and make sure that it is not already occupied
                point.set()  # Occupy point
                return  # End the function

# If the player might win on the next move, prevent it
for winning_possibility in winning_possibilities:  
    winning_possibility.check('X')  # Check how many conditions for winning of player are satisfied
    if winning_possibility.p1_satisfied and winning_possibility.p2_satisfied:  # If condition 1 and 2 are satisfied, prevent condition 3 from being satisfied
        for point in XO_points:
            if point.x == winning_possibility.x3 and point.y == winning_possibility.y3 and point not in X_points + O_points:  # Find the point and make sure that it is not already occupied
                point.set()  # Occupy point
                return  # End function
    elif winning_possibility.p2_satisfied and winning_possibility.p3_satisfied:  # If condition 2 and 3 are satisfied, prevent condition 1 from being satisfied
        for point in XO_points:
            if point.x == winning_possibility.x1 and point.y == winning_possibility.y1 and point not in X_points + O_points:  # Find the point and make sure that it is not already occupied
                point.set()  # Occupy point
                return  # End function
    elif winning_possibility.p3_satisfied and winning_possibility.p1_satisfied:  # If condition 1 and 3 are satisfied, prevent condition 2 from being satisfied
        for point in XO_points:
            if point.x == winning_possibility.x2 and point.y == winning_possibility.y2 and point not in X_points + O_points:  # Find the point and make sure that it is not already occupied
                point.set()  # Occupy point
                return  # End function
Enter fullscreen mode Exit fullscreen mode

Some changes to function "check" in class "WinningPossibility"

To check how many conditions are currently satisfied to win the game, we need to make variables p1_satisfied, p2_satisfied and p3_satisfied accessible outside the function. So, let's rename them as self.p1_satisfied, self.p2_satisfied and self.p3_satisfied respectively. The new code for the function would be:

def check(self, for_chr):  
    self.p1_satisfied = False  
    self.p2_satisfied = False  
    self.p3_satisfied = False  
    if for_chr == 'X':  
        for point in X_points:  
            if point.x == self.x1 and point.y == self.y1:  
                self.p1_satisfied = True  
    elif point.x == self.x2 and point.y == self.y2:  
                self.p2_satisfied = True  
    elif point.x == self.x3 and point.y == self.y3:  
                self.p3_satisfied = True  
    elif for_chr == 'O':  
        for point in O_points:  
            if point.x == self.x1 and point.y == self.y1:  
                self.p1_satisfied = True  
    elif point.x == self.x2 and point.y == self.y2:  
                self.p2_satisfied = True  
    elif point.x == self.x3 and point.y == self.y3:  
                self.p3_satisfied = True  
    return all([self.p1_satisfied, self.p2_satisfied, self.p3_satisfied])
Enter fullscreen mode Exit fullscreen mode

2. If center is not currently occupied

Tic Tac Toe with Python tkinter - Center not occupied
It's a good idea to occupy the center if it's not currently occupied. It can be implemented in Python as:

center_occupied = False
for point in X_points + O_points:  # Check if center is already occupied
    if point.x == 2 and point.y == 2:
        center_occupied = True
        break
 if not center_occupied:  # If center is not occupied
    for point in XO_points:
        if point.x == 2 and point.y == 2:
            point.set()  # Occupy center
            return  # End the function
Enter fullscreen mode Exit fullscreen mode

3. If the center is already occupied, and currently there is no winning possibility

Tic Tac Toe with Python tkinter - center occupied and no current winning possibilities
In this case, we need to occupy either a corner point or a middle one. If fewer than two corners are occupied by the player, "O" must try to occupy the rest. If 2 or more corners are occupied by the player, it is safer to occupy the middle points instead. This can be implemented in Python as:

corner_points = [(1, 1), (1, 3), (3, 1), (3, 3)]
middle_points = [(1, 2), (2, 1), (2, 3), (3, 2)]
num_of_corner_points_occupied_by_X = 0
for point in X_points:  # Iterate over all points occupied by the player
    if (point.x, point.y) in corner_points:
        num_of_corner_points_occupied_by_X += 1
if num_of_corner_points_occupied_by_X >= 2:  # If two or more corner points are occupied by the player
    for point in XO_points:
        if (point.x, point.y) in middle_points and point not in X_points + O_points:  # Find a middle point and make sure that it is not already occupied
            point.set()  # Occupy the point
            return  # End the function
elif num_of_corner_points_occupied_by_X < 2:  # If less than two corner points are occupied by the player
    for point in XO_points:
        if (point.x, point.y) in corner_points and point not in X_points + O_points:  # Find a corner point and make sure that it is not already occupied
            point.set()  # Occupy the point
            return  # End the function
Enter fullscreen mode Exit fullscreen mode

Combining all code, let's write the function auto_play:

def auto_play():  

    # If winning is possible in the next move  
    for winning_possibility in winning_possibilities:  
        winning_possibility.check('O')  
        if winning_possibility.p1_satisfied and winning_possibility.p2_satisfied:  
            for point in XO_points:  
                if point.x == winning_possibility.x3 and point.y == winning_possibility.y3 and point not in X_points + O_points:  
                    point.set()  
                    return  
        elif winning_possibility.p2_satisfied and winning_possibility.p3_satisfied:  
            for point in XO_points:  
                if point.x == winning_possibility.x1 and point.y == winning_possibility.y1 and point not in X_points + O_points:  
                    point.set()  
                    return  
        elif winning_possibility.p3_satisfied and winning_possibility.p1_satisfied:  
            for point in XO_points:  
                if point.x == winning_possibility.x2 and point.y == winning_possibility.y2 and point not in X_points + O_points:  
                    point.set()  
                    return  

    # If the opponent can win in the next move  
    for winning_possibility in winning_possibilities:  
        winning_possibility.check('X')  
        if winning_possibility.p1_satisfied and winning_possibility.p2_satisfied:  
            for point in XO_points:  
                if point.x == winning_possibility.x3 and point.y == winning_possibility.y3 and point not in X_points + O_points:  
                    point.set()  
                    return  
        elif winning_possibility.p2_satisfied and winning_possibility.p3_satisfied:  
            for point in XO_points:  
                if point.x == winning_possibility.x1 and point.y == winning_possibility.y1 and point not in X_points + O_points:  
                    point.set()  
                    return  
        elif winning_possibility.p3_satisfied and winning_possibility.p1_satisfied:  
            for point in XO_points:  
                if point.x == winning_possibility.x2 and point.y == winning_possibility.y2 and point not in X_points + O_points:  
                    point.set()  
                    return  

    # If the center is free...  
    center_occupied = False  
    for point in X_points + O_points:  
        if point.x == 2 and point.y == 2:  
            center_occupied = True  
            break
     if not center_occupied:  
        for point in XO_points:  
            if point.x == 2 and point.y == 2:  
                point.set()  
                return  

    # Occupy corner or middle based on what opponent occupies  
    corner_points = [(1, 1), (1, 3), (3, 1), (3, 3)]  
    middle_points = [(1, 2), (2, 1), (2, 3), (3, 2)]  
    num_of_corner_points_occupied_by_X = 0  
    for point in X_points:
        if (point.x, point.y) in corner_points:  
            num_of_corner_points_occupied_by_X += 1  
    if num_of_corner_points_occupied_by_X >= 2:  
        for point in XO_points:  
            if (point.x, point.y) in middle_points and point not in X_points + O_points:  
                point.set()  
                return  
    elif num_of_corner_points_occupied_by_X < 2:  
        for point in XO_points:  
            if (point.x, point.y) in corner_points and point not in X_points + O_points:  
                point.set()  
                return
Enter fullscreen mode Exit fullscreen mode

Creating a button to toggle play with a computer or play with a human

Let’s create a button that can switch opponents as either human or computer, as well as make a callback to change the toggle button text and command.

play_with = "Computer"
def play_with_human():
    global play_with
    play_with = "Human"  # switch opponent to human
    play_with_button['text'] = "Play with computer"  # switch text
    play_with_button['command'] = play_with_computer  # switch command so that the user can play with the computer again, if required
    play_again()  # restart game
def play_with_computer():
    global play_with
    play_with = "Computer"  # switch opponent to computer
    play_with_button['text'] = "Play with human"  # switch text
    play_with_button['command'] = play_with_human  # switch command so that the user can play with a human again, if required
    play_again()  # restart game
play_with_button = tk.Button(root, text='Play with human', font=('Ariel', 15), command=play_with_human)
play_with_button.pack()
Enter fullscreen mode Exit fullscreen mode

We need to call the function auto_play whenever it's O's turn and the value of play_with is "Computer". For this, append the following code to the function set in class XOPoint:

if play_with == "Computer" and status_label['text'] == "O's turn":
    auto_play()
Enter fullscreen mode Exit fullscreen mode

Result

Tic Tac Toe with Python tkinter demo
If you find this article useful, drop a like ⭐ and follow me to get all my latest content.

Full code in GitHub repository: https://github.com/Jothin-kumar/tic-tac-toe/

Latest comments (0)