# L-12 MCS 275 Mon 8 Feb 2010 : sliding_puzzle.py # To introduce GUIs we look at a simple sliding puzzle. # The GUI contains several elements: # (1) buttons to start, scramble, unscramble, and clear # (2) a canvas widget to display the sliding puzzle # (3) a numpy array stores the puzzle data # (4) the scramble/unscramble buttons are animations # (5) mouse events allow user to solve the puzzle from Tkinter import * from numpy import * from random import randint, shuffle class Sliding_Puzzle(): """ The start button of the GUI displays a board of sorted rectangles. The scramble button perturbs the sorted configuration. Pushing unscramble shows how the computer solves the puzzle. The user can move rectangles by clicking on a rectangle that can slide to the free position. """ def DrawBoundingBox(self): """ Draws a bounding box on canvas. """ x0 = self.mag; y0 = self.mag x1 = self.mag*self.rows+self.mag y1 = self.mag*self.cols+self.mag self.c.create_line(y0,x0,y0,x1,width=1) self.c.create_line(y0,x0,y1,x0,width=1) self.c.create_line(y1,x0,y1,x1,width=1) self.c.create_line(y0,x1,y1,x1,width=1) def BindMouseEvents(self): """ Binds the mouse events to the canvas. """ self.c.bind("", self.buttonPressed) self.c.bind("", self.buttonReleased) self.c.bind("", self.EnteredWindow) self.c.bind("", self.ExitedWindow) self.c.bind("", self.mouseDragged) def __init__(self,wdw,r,c): """ The mouse is bound to the canvas. There are r rows of blocks and c columns of blocks. A label displays the mouse position. """ wdw.title("a sliding puzzle") self.mag = 100 # magnification factor self.rows = r # number of rows on canvas self.cols = c # number of columns on canvas self.moves = [] # list of scrambling moves self.c = Canvas(wdw,\ width=self.mag*self.cols+2*self.mag,\ height = self.mag*self.rows+2*self.mag,\ bg='white') self.c.grid(row=1,column=0,columnspan=4) # to display mouse position : self.mousePosition = StringVar() self.mousePosition.set("put mouse inside box to move it") self.positionLabel = Label(wdw, \ textvariable = self.mousePosition) self.positionLabel.grid(row=2,column=0,columnspan=3) # draw bounding box and bind mouse events self.DrawBoundingBox() self.BindMouseEvents() self.filled = zeros((r,c),int) # three buttons self.b1 = Button(wdw,text='start',command=self.StartFill) self.b1.grid(row=0,column=0,sticky=W+E) self.b2 = Button(wdw,text='scramble',command=self.Scramble) self.b2.grid(row=0,column=1,sticky=W+E) self.b3 = Button(wdw,text='unscramble',command=self.unScramble) self.b3.grid(row=0,column=2,sticky=W+E) self.b4 = Button(wdw,text='clear',command=self.Clear) self.b4.grid(row=0,column=3,sticky=W+E) def Movable(self,i,j): """ Returns a tuple (a,b,c) where a is a boolean, indicating whether the rectangle at (i,j) can be moved. If a is True, then (b,c) are the new coordinates for the rectangle. """ if i < self.rows-1: if self.filled[i+1,j] == 0: return (True,i+1,j) if i > 0: if self.filled[i-1,j] == 0: return (True,i-1,j) if j < self.cols-1: if self.filled[i,j+1] == 0: return (True,i,j+1) if j > 0: if self.filled[i,j-1] == 0: return (True,i,j-1) return (False,i,j) def Move(self,i,j): """ Verifies first if the (i,j)-th rectangle can move and if so, then it moves the (i,j)-th rectangle. """ (a,b,c) = self.Movable(i,j) if not a: self.mousePosition.set("nowhere to move") else: self.mousePosition.set('move to ' + str(b) + ',' + str(c)) n = self.filled[i,j] self.filled[i,j] = 0 name = '('+str(i)+','+str(j)+')' self.c.delete(name) lbl = 't' + name self.c.delete(lbl) y0 = (c+1)*self.mag; x0 = (b+1)*self.mag y1 = y0 + self.mag; x1 = x0 + self.mag name = '('+str(b)+','+str(c)+')' self.c.create_rectangle(y0,x0,y1,x1,fill="green",tags=name) self.filled[b,c] = n lbl = 't' + name y1 = y0 + self.mag/2; x1 = x0 + self.mag/2 v = str(n) self.c.create_text(y1,x1,text=v,tags=lbl) def DrawRectangle(self,x,y): """ Draws a green rectangle on canvas, with coordinates given at (x,y) by the mouse. """ x0 = y - y % self.mag y0 = x - x % self.mag i = x0/self.mag - 1 j = y0/self.mag - 1 if self.filled[i,j] == 0: self.mousePosition.set("cannot move empty box") else: self.Move(i,j) def MovableSpot(self): """ Returns (i,j) of a rectangle that can be moved and the (i,j) of the free spot. """ for i in range(self.filled.shape[0]): for j in range(self.filled.shape[1]): if self.filled[i,j] == 0: while True: r = randint(0,3) if r == 0: if i > 0: return (i-1,j,i,j) if r == 1: if j > 0: return (i,j-1,i,j) if r == 2: if i < self.rows-1: return (i+1,j,i,j) if r == 3: if j < self.cols-1: return (i,j+1,i,j) def Scramble(self): """ Makes random moves and stores the moves. """ for i in range(0,100): (i,j,ii,jj) = self.MovableSpot() if self.filled[i,j] != 0: self.Move(i,j) self.c.after(60) self.c.update() self.moves.insert(0,(ii,jj)) def unScramble(self): """ Uses the stored moves to unscramble. """ while self.moves != []: (i,j) = self.moves.pop(0) self.Move(i,j) self.c.after(60) self.c.update() def StartFill(self): """ Fills the canvas with boxes placed in order. """ self.moves = [] m = self.rows*self.cols - 1 k = 0 for i in range(0,self.filled.shape[0]): for j in range(0,self.filled.shape[1]): name = '('+str(i)+','+str(j)+')' n = i*self.rows + j; lbl = 't'+ name if k < m: k = k + 1 x0 = (i+1)*self.mag; y0 = (j+1)*self.mag x1 = x0 + self.mag; y1 = y0 + self.mag self.c.create_rectangle(y0,x0,y1,x1,fill="green",tags=name) x1 = x0 + self.mag/2; y1 = y0 + self.mag/2 v = str(k) self.c.create_text(y1,x1,text=v,tags=lbl) self.filled[i,j] = k else: self.c.delete(name) self.c.delete(lbl) self.filled[i,j] = 0 def Clear(self): """ Clears the canvas, emptying all boxes. """ self.moves = [] for i in range(0,self.filled.shape[0]): for j in range(0,self.filled.shape[1]): name = '('+str(i)+','+str(j)+')' self.c.delete(name) lbl = 't' + name self.c.delete(lbl) self.filled[i,j] = 0 def buttonPressed(self,event): """ Displays the coordinates of button pressed. """ self.mousePosition.set("currently at [ " + \ str(event.x) + ", " + str(event.y) + " ]" +\ " release to play") def buttonReleased(self,event): """ Display the coordinates of button release. """ self.mousePosition.set("released at [ " + \ str(event.x) + ", " + str(event.y) + " ]" + \ " redo to clear") self.DrawRectangle(event.x,event.y) def EnteredWindow(self,event): """ Display the message that mouse entered window. """ self.mousePosition.set("press mouse to grab a number") def ExitedWindow(self,event): """ Display the message that mouse exited window. """ self.mousePosition.set("put mouse inside box to play") def mouseDragged(self,event): """ Displays coordinates of a moving mouse. """ self.mousePosition.set("dragging at [ " + \ str(event.x) + ", " + str(event.y) + " ]" + \ " release to draw") def main(): top = Tk() r = input('Give #rows : ') c = input('Give #columns : ') # r = 4; c = 4 show = Sliding_Puzzle(top,r,c) top.mainloop() if __name__ == "__main__": main()