Search code examples
pythontkintera-star

Use tkinter to visualize A Star search algorithm


I've written an A* search algorithm and want to use tkinter to graphically visualize the search (in real time) like in the picture below, just a simpler version without menus, numbers in cells, previously tried paths or diagonal pathing.

In other words, I just need to display a grid with 7 possible textures (start, finish, path, obstacle, open node, closed node, untried) which updates automatically when the underlying matrix is changed.

How would the outline, so to speak, or architecture of this look? I already have a 2x2 matrix containing values for each node status (start.. obstacle and so forth), how can I use tkinter to display this matrix each time it's changed?

a

So far, I got this:

def main():
    theMap = .. # initial map with start, finish and obstacles
    root = Tk()
    my_gui = CellGrid(root,n,m,40,theMap)
    root.mainloop()
class CellGrid(Canvas):
    def __init__(self,master, rowNumber, columnNumber, cellSize, theMap):
        Canvas.__init__(self, master, width = cellSize * columnNumber , height = cellSize * rowNumber)

        self.cellSize = cellSize

        self.grid = []
        for row in range(rowNumber):
            line = []
            for column in range(columnNumber):
                line.append(Cell(self, column, row, cellSize, theMap[row][column]))
            self.grid.append(line)

        print self.grid[0][0].value
        self.draw()


    def draw(self):
        for row in self.grid:
            for cell in row:
                cell.draw()

class Cell():
    START_COLOR = "green"
    FINISH_COLOR = "red"
    UNTRIED_COLOR = "white"
    CLOSED_COLOR = "gray"
    OPEN_COLOR = "blue"
    OBSTACLE_COLOR = "black"
    PATH_COLOR = "orange"

    def __init__(self, master, x, y, size, value):
        self.master = master
        self.abs = x
        self.ord = y
        self.size= size
        self.fill = "white"
        self.value = value

    def setValue(self, value):
        self.value = value

    def draw(self):
        """ order to the cell to draw its representation on the canvas """
        if self.master != None :
            if self.value == 0:
                self.fill = self.UNTRIED_COLOR
            elif self.value == 1:
                self.fill = self.OBSTACLE_COLOR
            elif self.value == 2:
                self.fill = self.START_COLOR
            elif self.value == 3:
                self.fill = self.FINISH_COLOR
            elif self.value == 4:
                self.fill = self.OPEN_COLOR
            elif self.value == 5:
                self.fill = self.CLOSED_COLOR
            elif self.value == 6:
                self.fill = self.PATH_COLOR

            xmin = self.abs * self.size
            xmax = xmin + self.size
            ymin = self.ord * self.size
            ymax = ymin + self.size

            self.master.create_rectangle(xmin, ymin, xmax, ymax, fill = self.fill, outline = "black")

The idea is to draw the initial map (theMap) which only contains start, finish and obstacle nodes. Then, as A* changes the node, it can call a method in CellGrid with a new value for the node, which then updates the value and draws the corresponding texture.

However I can't get that far. When I run the code above, I get nothing, just an empty Tk window. What's wrong with it?


Solution

  • The code posted does not run. It does (with 3.x), when it begins, for instance, with

    from tkinter import *
    
    def main():
        Map = [
                [2, 0, 0, 0, 0],
                [0, 1, 1, 1, 1],
                [0, 1, 3, 0, 0],
                [0, 1, 1, 1, 0],
                [0, 0, 0, 0, 0],
              ]
        root = Tk()
        my_gui = CellGrid(root, len(Map), len(Map[0]), 40, Map)
        root.mainloop()
    

    and parentheses are added to the print statement.

        print(self.grid[0][0].value)  # harmless in 2.7
    

    and ends with main(). Using simple data is very helpful for development.

    To answer your immediate question, making the canvas visible requires the addition of self.pack after Canvas initialization. With that, the Map above is displayed as expected.

    To answer your architecture question, I suggest a different approach. Define a custom Map class that has the list of lists that you currently call theMap, a Canvas, and getitem and setitem methods that take a tuple of indexes. Write your A* functions in terms of amap[(i,j)]. The setitem method would take care of setting both the array and canvas.

    Python style note: all the color code above can be replaced with

    colors = {
                0: 'white',    # untried
                1: 'black',    # obstacle
                2: 'green',    # start
                3: 'red',      # finish
                4: 'blue',     # open
                5: 'gray',     # closed
                6: 'orange',   # path
             }
    

    followed by fill=colors[self.value] in the create_rectangle call.