1

Attached picture

As part of an assignment I have to implement a reinforcement learning algorithm. I started by designing some visualisation for myself so that once I actually start implementing the algorithm, I can tell what is happening. I want to use Tkinter because it appeared to be easy to do iterations of images depending on what action the agent (red circle, see attached picture) takes. Essentially I just want images where the agent has moved to another square. It mustn't happen through user input though, once I press run, it should execute the entire algorithm by itself. The issue is that because of the root.mainloop(), it appears to be impossible to update the view. How should I go about this, is there a different visualisation tool I should use, or is there some way I can simply show a new screen on every iteration (without user input)? Finally, I tried using .update() but using that function is apparently discouraged.

class GraphicalInterface:
##Initialisation of the GUI class.
    def __init__(self, height, width):
        self.blockColor = 'DarkSeaGreen3'
        self.block_size=75
        self.padding=1
        self.frame_height = height*self.block_size
        self.frame_width = width*self.block_size
        self.root = Tk()
        self.root.resizable(width=0, height=0)

        self.frm = Frame(self.root, height=self.frame_height+8+self.padding*(height*2),
                         width=self.frame_width+8+self.padding*(width*2), background='lightgray',
                         cursor='circle', relief='sunken', borderwidth=4)
        self.frm.grid_propagate(0)
        self.frm.grid(column=0, row=0, padx=20, pady=20)
        for i in range(0,width):
            for j in range(0,height):
                cur_frame = Frame(self.frm, background=self.blockColor, height=self.block_size, width=self.block_size)
                cur_frame.grid(column=i,row=j, padx=self.padding, pady=self.padding)

    def create_circle(self, x, y, r, canvasName, color):  # center coordinates, radius       https://stackoverflow.com/questions/17985216/simpler-way-to-draw-a-circle-with-tkinter
        x0 = x - r
        y0 = y - r
        x1 = x + r
        y1 = y + r
        return canvasName.create_oval(x0, y0, x1, y1, fill=color)

    def setObstacle(self, x, y):
        cv = Canvas(self.frm, height=self.block_size-15, width=self.block_size-15, background= self.blockColor, highlightthickness=0)
        self.create_circle(x = cv.winfo_reqheight()/2, y=cv.winfo_reqwidth()/2, r=30, canvasName=cv, color='black')
        cv.grid(column=x, row=y, padx=0, pady=0)

    def setAgent(self, x, y):
        cv = Canvas(self.frm, height=self.block_size-15, width=self.block_size-15, background=self.blockColor, highlightthickness=0)
        self.create_circle(x=cv.winfo_reqheight()/2, y=cv.winfo_reqwidth()/2, r=25, canvasName=cv, color='firebrick4')
        cv.grid(column=x, row=y)

    def run(self):
        self.root.mainloop()
martineau
  • 119,623
  • 25
  • 170
  • 301
L. vd Hurk
  • 11
  • 4
  • The most straightforward way is to move items on canvas, or update shown calculations every time your algorithm changes them. Another approach, is to store every step of the algorithm you would like to display into a generator (or a sequential collection), then iterate over the stored steps, and update the progress of the algorithm. This last method allows you to separate the algorithm from the implementation of the GUI mechanism, and have better control over the speed of execution of the animation. – Reblochon Masque Dec 10 '21 at 00:16
  • 1
    Have you tried using `update_idletasks` to refresh the screen? – Bryan Oakley Dec 10 '21 at 00:30
  • if you run all in one function then you have to use `update()` from time to time to force `mainloop` to redraw all in window. If you have steps in separated files then you can use `root.after(milliseconds, functions_name)` to run next function with some delay - so you can see current tate before it changes it - and it will automatically redraw window. – furas Dec 10 '21 at 02:08
  • @bryanOakley Yepp this helped. I needed to setup my own loop rather than the mainloop(). In run(), i started a loop that calls root.after(millis, fname) and root.update. It appears to be a bit slow right now, but maybe that's just inherent to the library which is fine. Thank you very much :) – L. vd Hurk Dec 10 '21 at 09:19

1 Answers1

2

This example uses root.after(milliseconds, function_name) to start function after starting windows. This function runs loop which add circles - it need root.update() to force mainloop() to redraw window with new elements. It also use root.after(milliseconds) instead of time.sleep(). All this looks like animation.

But it could use root.after(milliseconds, another_function_name) to run other function with delay - so it would run action which uses after() to run another function which get results (reward, state) from game/environment, and this function would use after() to run again action, etc. - and this could look like reinforcement learning loop.

from tkinter import *

class GraphicalInterface:
##Initialisation of the GUI class.
    def __init__(self, height, width):
        self.blockColor = 'DarkSeaGreen3'
        self.block_size=75
        self.padding=1
        self.frame_height = height*self.block_size
        self.frame_width = width*self.block_size
        self.root = Tk()
        self.root.resizable(width=0, height=0)
        self.height = height
        self.width = width
        
        self.frm = Frame(self.root, height=self.frame_height+8+self.padding*(height*2),
                         width=self.frame_width+8+self.padding*(width*2), background='lightgray',
                         cursor='circle', relief='sunken', borderwidth=4)
        self.frm.grid_propagate(0)
        self.frm.grid(column=0, row=0, padx=20, pady=20)
        for i in range(0,width):
            for j in range(0,height):
                cur_frame = Frame(self.frm, background=self.blockColor, height=self.block_size, width=self.block_size)
                cur_frame.grid(column=i,row=j, padx=self.padding, pady=self.padding)

    def create_circle(self, x, y, r, canvasName, color):  # center coordinates, radius       https://stackoverflow.com/questions/17985216/simpler-way-to-draw-a-circle-with-tkinter
        x0 = x - r
        y0 = y - r
        x1 = x + r
        y1 = y + r
        return canvasName.create_oval(x0, y0, x1, y1, fill=color)

    def setObstacle(self, x, y):
        cv = Canvas(self.frm, height=self.block_size-15, width=self.block_size-15, background= self.blockColor, highlightthickness=0)
        self.create_circle(x = cv.winfo_reqheight()/2, y=cv.winfo_reqwidth()/2, r=30, canvasName=cv, color='black')
        cv.grid(column=x, row=y, padx=0, pady=0)

    def setAgent(self, x, y):
        cv = Canvas(self.frm, height=self.block_size-15, width=self.block_size-15, background=self.blockColor, highlightthickness=0)
        self.create_circle(x=cv.winfo_reqheight()/2, y=cv.winfo_reqwidth()/2, r=25, canvasName=cv, color='firebrick4')
        cv.grid(column=x, row=y)

    def set_all(self):
        for i in range(self.width):
            for j in range(self.height):
                self.setObstacle(i, j)
                self.root.update()
                self.root.after(250)  # like time.sleep()
                
    def run(self):
        #self.root.after(100, self.setObstacle, 0, 0)
        self.root.after(100, self.set_all)
        self.root.mainloop()
        
GraphicalInterface(5, 5).run()        
furas
  • 134,197
  • 12
  • 106
  • 148