#! /usr/bin/env python ######################################################################### ## ## NAME: iceGUI.py ## ## DESCRIPTION: The 2010Sp CS61C "Bits on Ice" performace contest GUI ## ## EXAMPLE: Run with 3 arguments "./iceGUI.py start.pbm end.pbm 100" ## Start PBM file ------------------------^ ^ ^ ## End PBM file ----------------------------------| | ## Size of a bit (in pixels) ---------------------------+ ## ## AUTHORS: Dan Garcia (version 1.0) ## ## DISCLAIMER: This code needs a TON of refactoring. I wrote it during ## a flight to Qatar as fast as I could to get it working. ## There are tons of DAVs and other poor design decisions ## throughout; it doesn't have any variable hiding among ## classes. Also, each class should be in a separate file. ## There isn't even any error checking on the argv inputs. ## ## DATE: 2010-05-01 ## ## UPDATE HIST: ## ## 2010-05-01 : v1.0 Release ## 2010-05-02 : v1.1 Added "STUCK" when the puzzle is stuck, smaller arrows ## 2010-05-05 : v1.2 Fixed comments, added disclaimer ## 2010-05-05 : v1.3 Added Green color arrows when a bit is over a rest bit ## Changed mouseover color to grey, can now read PBM files ## that have P1 on first line, on next ## ########################################################################### import sys import Tkinter ######################################################################## ## ## CLASS NAME: Data ## ######################################################################## class Data: """This class stores all the shared data structures for the bits""" def __init__(self, startFile, endFile, size): self.startFile, self.endFile, self.size = startFile, endFile, size self.bits, self.xMax, self.yMax = self.ReadPBM(startFile) self.end , self.xMax, self.yMax = self.ReadPBM(endFile) #self.PrintPBM(startFile,self.start) #self.PrintPBM(endFile ,self.end ) def ReadPBM(self,filename): FP = open(filename) header = FP.readline() firstline = header.split() if len(firstline) == 3: # 1st line is P1 format,xMaxStr,yMaxStr = firstline else: # 1st line is P1, 2nd line is format,xMaxStr,yMaxStr = firstline + FP.readline().split() xMax = int(xMaxStr) yMax = int(yMaxStr) bits={} for y in range(yMax): row = FP.readline().split() for x in range(xMax): bits[x,y]=int(row[x]) FP.close() return bits,xMax,yMax def PrintPBM(self,filename,bits): print filename for y in range(self.yMax): for x in range(self.xMax): print bits[x,y], print return ######################################################################## ## ## CLASS NAME: CanvasItem ## ######################################################################## class CanvasItem: """This class is for all items of a Canvas""" id = None color0 = 'Red' color1 = 'grey' def __init__(self, ice, x, y): self.ice, self.x, self.y = ice, x, y ######################################################################## ## ## CLASS NAME: CanvasLine ## ######################################################################## class CanvasLine(CanvasItem): """This class is for all Line items for a Canvas""" def __init__(self, ice, x, y, *pos, **key): CanvasItem.__init__(self, ice, x, y) self.id = self.ice.canvas.create_line(*pos, **key) self.ice.canvas.addtag_withtag('arrow',self.id) self.make_binds() def make_binds(self): self.ice.canvas.tag_bind(self.id, '', self.clicked) self.ice.canvas.tag_bind(self.id, '', self.color_change) self.ice.canvas.tag_bind(self.id, '', self.color_original) def clicked(self, event): # Remove this arrow self.ice.canvas.delete(self.id) # Update the counter self.ice.count.set(int(self.ice.count.get())+1) # Get the bit widget id, set up from & to coords for move, compass bid = self.ice.d.xytobid[(self.x, self.y)] fromx, fromy = self.x, self.y tox,toy,nsew = self.ice.d.xyaidtonewxynsew[(self.x, self.y, self.id)] # Emit this step in the solution to stdout print str(fromx) + " " + str(fromy) + " " + nsew # Animate the piece moving steps = 10 for i in range(steps+1): t = i / (steps + 0.0) tx, ty = (1 - t) * fromx + t * tox, (1 - t) * fromy + t * toy self.ice.canvas.coords(bid, tx*self.ice.d.size+1, ty*self.ice.d.size+1, (tx+1)*self.ice.d.size, (ty+1)*self.ice.d.size) self.ice.canvas.update_idletasks() ### Move the bit over in the bit array #print "self.bits before : " + str(self.ice.d.bits) self.ice.d.bits[fromx,fromy] = 0 self.ice.d.bits[tox ,toy ] = 1 #print "self.ice.d.bits after : " + str(self.ice.d.bits) ### Delete the xyaidtonewxynsew array, since all arrows are gone self.ice.d.xyaidtonewxynsew.clear() ### Move the bit over in the xytobid array #print "self.ice.d.xytobid before : " + str(self.ice.d.xytobid) del(self.ice.d.xytobid[fromx,fromy]) self.ice.d.xytobid[tox,toy] = bid #print "self.ice.d.xytobid after : " + str(self.ice.d.xytobid) # Remove all the old arrows self.ice.canvas.delete('arrow') if self.ice.d.bits == self.ice.d.end: # Solved!! #self.ice.parent.quit() self.ice.count.set(self.ice.count.get() + " ... SOLVED!") else: # Regenerate arrows self.ice.d.xyaidtonewxynsew = self.ice.generate_arrows_return_xyaidtonewxynsew() if len(self.ice.d.xyaidtonewxynsew) == 0: self.ice.count.set(self.ice.count.get() + " ... STUCK!") def color_original(self,event): self.ice.canvas.itemconfigure(self.id, fill=self.color0) def color_change(self,event): self.color0 = self.ice.canvas.itemconfigure(self.id)['fill'][4] self.ice.canvas.itemconfigure(self.id, fill=self.color1) ######################################################################## ## ## CLASS NAME: Ice ## ######################################################################## class Ice(Tkinter.Frame): """This class provides the user interface for a virtual ice rink for bits""" def __init__(self, parent, d, **options): # d = Data Tkinter.Frame.__init__(self, parent, **options) self.parent, self.d = parent, d self.canvas = Tkinter.Canvas(self, width=d.xMax*d.size, height=d.yMax*d.size) self.canvas.pack() self.generate_background() self.d.xytobid = self.generate_bits_return_xytobid() self.d.xyaidtonewxynsew = self.generate_arrows_return_xyaidtonewxynsew() parent.title("UC Berkeley CS61C Bits-on-Ice GUI : " + d.startFile + " --> " + d.endFile) Tkinter.Button(self, text='quit', command=parent.quit).pack(side=Tkinter.LEFT) Tkinter.Label(self, text="Steps:").pack(side=Tkinter.LEFT) self.count = Tkinter.StringVar() self.count.set("0") Tkinter.Label(self, textvariable=self.count).pack(side=Tkinter.LEFT) def generate_background(self): for y in range(self.d.yMax): for x in range(self.d.xMax): if self.d.end[x,y]: self.canvas.create_rectangle(x*self.d.size,y*self.d.size,(x+1)*self.d.size,(y+1)*self.d.size,fill='Black',outline='White') else: self.canvas.create_rectangle(x*self.d.size,y*self.d.size,(x+1)*self.d.size,(y+1)*self.d.size,fill='White',outline='White') def generate_bits_return_xytobid(self): xytobid = {} size = self.d.size for y in range(self.d.yMax): for x in range(self.d.xMax): if self.d.bits[x,y]: bitid = self.canvas.create_oval(x*size+1,y*size+1,(x+1)*size,(y+1)*size,fill='Blue',outline='') xytobid[(x,y)] = bitid #print "xytobid (BITS IDs): " + str(xytobid) return xytobid def generate_arrows_return_xyaidtonewxynsew(self): xyaidtonewxynsew = {} size = self.d.size width = size * 0.15 for x,y,xd,yd,xdest,ydest,nsew in self.find_movable_bits(self.d.xytobid.keys()): if self.d.end[x,y]: # already a bit there fill = 'Green' else: fill = 'Red' line = CanvasLine(self, x, y, size/2+size*x,size/2+size*y, size/2+size*(x+xd*0.9),size/2+size*(y+yd*0.9), fill=fill,arrow='last',width=width, arrowshape=(width*2,width*2,width)) xyaidtonewxynsew[(x,y,line.id)] = (xdest,ydest,nsew) #print "xyaidtonewxynsew (ARROW IDs): " + str(xyaidtonewxynsew) return xyaidtonewxynsew def in_bounds(self,x,y): return x >= 0 and y >= 0 and x < self.d.xMax and y < self.d.yMax def find_movable_bits(self, bit_index_list): movable_bits_coords = [] increment_list = [(1,0,'E'),(-1,0,'W'),(0,1,'S'),(0,-1,'N')] for xorig,yorig in bit_index_list: for xd,yd,nsew in increment_list: x,y = xorig + xd, yorig + yd if self.in_bounds(x,y) and not self.d.bits[x,y]: x += xd; y += yd while self.in_bounds(x,y): if self.d.bits[x,y]: movable_bits_coords.append((xorig,yorig,xd,yd,x-xd,y-yd,nsew)) break x += xd; y += yd return movable_bits_coords ######################################################################## ## ## main loop ## ######################################################################## tk=Tkinter.Tk() if len(sys.argv) != 4: print "Usage: ./iceGUI.py " else: Ice(tk, Data(sys.argv[1], sys.argv[2], int(sys.argv[3]))).pack() tk.mainloop()