Python Forum
[Tkinter] How to create a delay for AI without freezing the GUI - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: GUI (https://python-forum.io/forum-10.html)
+--- Thread: [Tkinter] How to create a delay for AI without freezing the GUI (/thread-18042.html)



How to create a delay for AI without freezing the GUI - kom2 - May-03-2019

I want to create an AI move into my game, after the player does one. Is there any function I could use in order to let AI wait for few seconds before it makes a move? I already tried a canvas.after(), but it didn´t work properly (it also freezed my move).


RE: How to create a delay for AI without freezing the GUI - Larz60+ - May-03-2019

see: https://python-forum.io/Thread-WxPython-How-to-deal-with-code-that-blocks-the-mainloop-freezing-the-gui


RE: How to create a delay for AI without freezing the GUI - kom2 - May-04-2019

So I have read the Threads Guide, but got stucked on this point:

class Program:

    def __init__(self):
        self.width = 1000
        self.height = 800
        self.panel_width = 200
        #self.root = Tk()
        self.canvas = tkinter.Canvas(width=self.width, height=self.height, bg="black")
        self.canvas.pack()
        self.canvas.bind("<ButtonPress>", self.click)
        self.figures_images = {"0": tkinter.PhotoImage(file="white_pawn.png"), ...}
        self.figures = {"0":[<class Pawn...>, (x,y), image_id],"1":[<class Pawn...>, (x,y), image_id],"2":[<class Pawn...>, (x,y), image_id],...}
        ...

    def create_layout(self):
        # not important for this problem

    def start_ai(self):
        ...
        move = Move_ai(self)

    # next methods are not important now

class Pawn:
    def __init__(self, ...):
        ...
        # not important for this problem

class Move_ai:
    def __init__(self, program):
        self.main = program
        thread = threading.Thread(target=self.move)
        thread.start()

    def move(self):
        time.sleep(3)    # just to pretend PC is thinking
        ... # some code calculating which figure to move
        selected_id = ...    # id of figure it decided to move with
        self.main.canvas.coords(self.main.figures_images[selected_id], tuple_of_coords)
       # ^ and this is that line raising an error to me, this one:
Error:
Exception in thread Thread-1: Traceback (most recent call last): File "C:\python\lib\threading.py", line 917, in _bootstrap_inner self.run() File "C:\python\lib\threading.py", line 865, in run self._target(*self._args, **self._kwargs) File "C:\Users\user\Documents\PYTHON\chess\hra.py", line 581, in move self.main.canvas.coords(self.main.figures_images_[selected_id], tuple_of_coords) File "C:\python\lib\tkinter\__init__.py", line 2469, in coords self.tk.call((self._w, 'coords') + args))] RuntimeError: main thread is not in main loop
To be honest, I don´t understand that solution described here very well, could you help me to solve this problem using and showing only that version of this solution, which my program really needs?


RE: How to create a delay for AI without freezing the GUI - Yoriz - May-04-2019

I've added headings to the various parts of the code in the link.
You have the error shown against the heading "Example of adding a thread but still getting a error"
The solution is in the following code examples one with and one without decorators.


RE: How to create a delay for AI without freezing the GUI - kom2 - May-04-2019

Well I´m looking at the part "Example of adding a thread but still getting a error" but I have no idea how to suit it to my program. I have no buttons there and as I see, self.listbox.insert(tk.END, item) works with listbox I´m not using in my program.


RE: How to create a delay for AI without freezing the GUI - Yoriz - May-04-2019

Look at "Example of a solution to the problem"


RE: How to create a delay for AI without freezing the GUI - kom2 - May-04-2019

Well, so I have changed my code so:

class Program:
 
    def __init__(self):
        self.width = 1000
        self.height = 800
        self.panel_width = 200
        #self.root = Tk()
        self.canvas = tkinter.Canvas(width=self.width, height=self.height, bg="black")
        self.canvas.pack()
        self.canvas.bind("<ButtonPress>", self.click)
        self.figures_images = {"0": tkinter.PhotoImage(file="white_pawn.png"), ...}
        self.figures = {"0":[<class Pawn...>, (x,y), image_id],"1":[<class Pawn...>, (x,y), image_id],"2":[<class Pawn...>, (x,y), image_id],...}
        ...
 
    def create_layout(self):
        # not important for this problem
 
    def start_ai(self):
        ...
        move = Move_ai(self)
 
    # next methods are not important now
 
class Pawn:
    def __init__(self, ...):
        ...
        # not important for this problem
 
class Move_ai:
    def __init__(self, program):
        self.main = program
        thread_pool_executor = futures.ThreadPoolExecutor(max_workers=1)
        thread_pool_executor.submit(self.move)
 
    def move(self):
        time.sleep(3)    # just to pretend PC is thinking
        ... # some code calculating which figure to move
        selected_id = ...    # id of figure it decided to move with
        self.main.canvas.coords(self.main.figures_images[selected_id], tuple_of_coords)
       # ^ and this is that line raising an error to me, this one:
And now it does not raise any error message, but it just freezes at point of canvas.coords (and no other moves are able to do). Did I forgot to implement something more from "Example of a solution to the problem"?


RE: How to create a delay for AI without freezing the GUI - Yoriz - May-04-2019

You have to use the canvas.after method for calling methods of the gui, it moves the calls back into the mainloops thread.
The follwing is an code from that thread
class MainFrame(tk.Frame):
 
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        ....
        ....

    def set_label_text(self, text=''):
        self.label['text'] = text

    def blocking_code(self):
        self.after(0, self.set_label_text, 'running')
 
        for number in range(5):
            self.after(0, self.listbox_insert, number)
            print(number)
            time.sleep(1)
 
        self.after(0, self.set_label_text, ' not running')
In the method blocking_code self.after(0, self.set_label_text, 'running') is calling self.set_label_text back in the mainloops thread.


RE: How to create a delay for AI without freezing the GUI - Yoriz - May-04-2019

If you just want to delay something the first argument to after is how long to wait in millisecs before calling the passed in callback.
import tkinter as tk
import time

class MainFrame(tk.Frame):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.label = tk.Label(self, text='Player 1 turn')
        self.label.pack()
        self.button = tk.Button(
            self, text='Player 1 Move', command=self.on_button)
        self.button.pack(pady=15)
        self.pack()

    def on_button(self):
        print('Button clicked')
        self.label['text'] = 'player 2 thinking'
        self.button['state'] = 'disabled'
        self.after(3000, self.delayed_player_2)


    def delayed_player_2(self):
        self.label['text'] = 'player 2 moving'
        self.after(1000, self.player_2_finsihed)

    def player_2_finsihed(self):
        self.label['text'] = 'player 1 turn'
        self.button['state'] = 'normal'


if __name__ == '__main__':
    app = tk.Tk()
    main_frame = MainFrame()
    app.mainloop()