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?
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?
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.