1

The problem is that I made a short algorithm that finds it's way through a maze and marks all squares that it has visited in blue. The core program works fine but the problem is that the GUI only displays the maze with it's visited squares after the whole process is done. This would not normally be a problem but I need to be able to visibly see the algorithm traverse the maze as it goes. The problem is that when I call the UpdateMaze function every iteration of the searching algorithm, it doesn't appear to take affect until the whole traversal is finished.

  • Wall images are just a black square GIF
  • Space images are just a white square GIF
  • Edge images are just a Red square GIF
  • Finish is a Green square GIF
  • Visited is a blue square GIF
import tkinter as tk
from tkinter import *
import time

class MazeGUI():
    def __init__(self):
        self.maze =[
            [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
            [4, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 4],
            [4, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 4],
            [4, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 4],
            [4, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 4],
            [4, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 4],
            [4, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 4],
            [4, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 4],
            [4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 4],
            [4, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 4],
            [4, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 4],
            [4, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 4],
            [4, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 4],
            [4, 0, 1, 2, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 4],
            [4, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 4],
            [4, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 4],
            [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
        ]
        self.wall = tk.PhotoImage(file = "MazePiece_Wall.gif")
        self.space = tk.PhotoImage(file = "MazePiece_Space.gif")
        self.edge = tk.PhotoImage(file = "MazePiece_Outer.gif")
        self.visited = tk.PhotoImage(file = "MazePiece_Visited.gif")
        self.finish = tk.PhotoImage(file = "MazePiece_Finish.gif")

    def UpdateMaze(self):
        for y in range(len(self.maze)):
            for x in range(len(self.maze[y])):
                if self.maze[y][x] == 0:
                    label = Label(root, image=self.space,
                                  width=20, height=20).grid(row=y, column=x)
                elif self.maze[y][x] == 1:
                    label = Label(root, image=self.wall,
                                  width=20, height=20).grid(row=y, column=x)
                elif self.maze[y][x] == 2:
                    label = Label(root, image=self.finish,
                                  width=20, height=20).grid(row=y, column=x)
                elif self.maze[y][x] == 3:
                    label = Label(root, image=self.visited,
                                  width=20, height=20).grid(row=y, column=x)
                elif self.maze[y][x] == 4:
                    label = Label(root, image=self.edge,
                                  width=20, height=20).grid(row=y, column=x)


def Move(Maze,x,y):
    if Maze.maze[y][x] == 2:
        return True
    elif Maze.maze[y][x] == 1:
        return False
    elif Maze.maze[y][x] == 3:
        return False
    elif Maze.maze[y][x] == 4:
        return False
    Maze.maze[y][x] = 3
    if ((x < len(Maze.maze)-1 and Move(Maze,x+1, y))
        or (y > 0 and Move(Maze,x, y-1))
        or (x > 0 and Move(Maze,x-1, y))
        or (y < len(Maze.maze)-1 and Move(Maze,x, y+1))):
        return True
    return False

root = Tk()
Maze = MazeGUI()
root.lift()
StartPosX = 1
StartPosY = 1
Move(Maze,StartPosX,StartPosY)
Maze.UpdateMaze()
root.mainloop()
martineau
  • 119,623
  • 25
  • 170
  • 301
Oscar Wilson
  • 59
  • 1
  • 1
  • 8
  • 1
    When using a GUI it seems its traditional to exit back to the main thread that the GUI runs in to allow the framework to update the screen. This means that your search algorithm will have to be iterative, ie called multiple times, and at each return the framework will have to take over to update the screen. btw: I only see one call to `UpdateMaze()` and `Move()` seems to be recursive, not iterative. – quamrana Jun 21 '18 at 16:38
  • You probably need to use `update()` per change. – Mike - SMT Jun 21 '18 at 16:38
  • Thanks for the quick reply. I'll try the update() function and look into iterative methods but ideally it was meant to be recursive. Sorry for not mentioning this to start with – Oscar Wilson Jun 21 '18 at 16:46
  • What does the return value from the `Move()` function indicate exactly? How is (or should) the next move be determined after one is made? – martineau Jun 21 '18 at 17:31
  • Event-handling GUI programs generally expect your program to iteratively forfeit control back to the GUI. A recursive approach is still feasible, but you may have to implement some form of [trampolining](https://stackoverflow.com/a/489860/2288659) to get Tkinter to play nice. – Silvio Mayolo Jun 21 '18 at 18:21
  • your move method calls itself before it has a chance to finish. This is likely your problem. – Mike - SMT Jun 21 '18 at 21:23

1 Answers1

0

I think the following is something that shows the basics of how to do what you want. It uses the tkinter universal after() method to periodically call the make_move() method which eventually calls update_maze() to redisplay the maze following each move.

It may not look like anything is going on, but after a while you should see the maze being displayed change. It's unpredictable exactly how long it take due to the use of the random module I introduced to generate what the next move should be — since I really don't understand the logic for doing that in the Move() function in your sample code. This is what will likely need "fixing" to taylor it to for exactly whatever it is you're trying to accomplish.

Note: I also changed your code so it follows the PEP 8 - Style Guide for Python Code more closely with respect to code formatting and the naming of classes, functions, variables, etc, to make it more readable (IMO).

from random import randint
import tkinter as tk
from tkinter import *
import time

MOVE_DELAY = 1000  # Time delay between moves in millisec.

class MazeGUI():
    SPACE = 0
    WALL = 1
    FINISH = 2
    VISITED = 3
    EDGE = 4

    def __init__(self):
        self.maze = [
            [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
            [4, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 4],
            [4, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 4],
            [4, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 4],
            [4, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 4],
            [4, 0, 1, 0, 0, 0, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 4],
            [4, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 4],
            [4, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 4],
            [4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 4],
            [4, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 4],
            [4, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 4],
            [4, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 4],
            [4, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 4],
            [4, 0, 1, 2, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 4],
            [4, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 4],
            [4, 0, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 4],
            [4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]
        ]
        self.wall = tk.PhotoImage(file="MazePiece_Wall.gif")        # black
        self.space = tk.PhotoImage(file="MazePiece_Space.gif")      # white
        self.edge = tk.PhotoImage(file="MazePiece_Outer.gif")       # red
        self.finish = tk.PhotoImage(file="MazePiece_Finish.gif")    # green
        self.visited = tk.PhotoImage(file="MazePiece_Visited.gif")  # blue
        self.create_maze()

    def create_maze(self):
        global root

        self.maze_frame = tk.Frame(root)
        self.create_maze_labels()
        self.maze_frame.pack()

    def update_maze(self):
        global root

        self.maze_frame.grid_forget()  # Remove all the existing Labels.
        self.create_maze_labels()
        self.maze_frame.update_idletasks()  # Update display.

    def create_maze_labels(self):
        for y in range(len(self.maze)):
            for x in range(len(self.maze[y])):
                if self.maze[y][x] == self.SPACE:
                    Label(self.maze_frame, image=self.space,
                          width=20, height=20).grid(row=y, column=x)
                elif self.maze[y][x] == self.WALL:
                    Label(self.maze_frame, image=self.wall,
                          width=20, height=20).grid(row=y, column=x)
                elif self.maze[y][x] == self.FINISH:
                    Label(self.maze_frame, image=self.finish,
                          width=20, height=20).grid(row=y, column=x)
                elif self.maze[y][x] == self.VISITED:
                    Label(self.maze_frame, image=self.visited,
                          width=20, height=20).grid(row=y, column=x)
                elif self.maze[y][x] == self.EDGE:
                    Label(self.maze_frame, image=self.edge,
                          width=20, height=20).grid(row=y, column=x)

    def make_move(self, x, y):
        global root

        status = self.move(x, y)
        # if status:  # Not sure what to do with return value...
        self.update_maze()

        # Select a random adjoining cell (dx & dy both +/-1).
        dx, dy = randint(0, 2) - 1, randint(0, 2) - 1
        x, y = x+dx, y+dy  # Next position.
        root.after(MOVE_DELAY, self.make_move, x, y)  # Repeat...

    def move(self, x, y):
        if self.maze[y][x] == self.FINISH:
            return True
        elif self.maze[y][x] == self.WALL:
            return False
        elif self.maze[y][x] == self.VISITED:
            return False
        elif self.maze[y][x] == self.EDGE:
            return False

        # Spot is empty (self.SPACE).
        self.maze[y][x] = self.VISITED

        if ((x < len(self.maze)-1 and self.move(x+1, y))
             or (y > 0 and self.move(x, y-1))
             or (x > 0 and self.move(x-1, y))
             or (y < len(self.maze)-1 and self.move(x, y+1))):
            return True
        return False

root = Tk()
maze = MazeGUI()

start_posx, start_posy = 1, 1
maze.make_move(start_posx, start_posy)
maze.update_maze()
root.mainloop()

P.S. For anyone interested, here are the .gif images I made and used for testing (so you can download them to run the code):

  • MazePiece_Finish.gif     MazePiece_Finish.gif
  • MazePiece_Outer.gif!     MazePiece_Outer.gif
  • MazePiece_Space.gif     MazePiece_Space.gif
  • MazePiece_Visited.gif   MazePiece_Visited.gif
  • MazePiece_Wall.gif        MazePiece_Wall.gif
martineau
  • 119,623
  • 25
  • 170
  • 301
  • Thanks a lot. The return of the 'move' function basically returns false if the 'branch' of the maze being tested is a dead end. It does this by testing the neighbour cells of the target cell, and then running itself again on one of the neighbour cells. It does this recursively until a target cell has no other options (dead end). At this point it returns False which in turn makes all the other iterations of the function return False as well. Basically, if it returns false its a dead end. If it returns true its not. (I'm not the best at explaining) – Oscar Wilson Jun 22 '18 at 08:11
  • Oscar: Your explanation of your `Move()` function is appreciated—it now makes a lot more sense to me. However the code in my posted answer isn't really doing what I now think you want (which is to animate the function's processing). The problem is that the function only changes the maze's contents in one place when it does a `Maze.maze[y][x] = 3`, and it only does that occasionally. I think you need to devise some sort of animated way to show what its internal logic is doing. If you can do that, I should be able to update my answer, if you can't figure out how to do it from the existing code. – martineau Jun 22 '18 at 16:39
  • Whenever the `move()` function is called for a specific cell, the value of that cell is always changed to '3' no matter the contents of the cell. Ideally I would just call an updateGUI() function every single time the move() function is called which would just display the list which had been changed for visited cells. This is for a school project and, at the end of the day, although it would be great to animate the process, it would not be the end of the world if the maze traversal was displayed instantly (as it currently does) – Oscar Wilson Jun 25 '18 at 08:20