# HG changeset patch # User Laman # Date 2015-09-01 22:44:11 # Node ID 42f4533a50292a43c51f9c68191d33506dea64a0 # Parent ec97d7dc66015d52de400798e4b1396d61a513ca workable static image analysis included testing images added ImageAnalyzer added Go diff --git a/.hgignore b/.hgignore --- a/.hgignore +++ b/.hgignore @@ -1,2 +1,1 @@ -^images/ ^src/__pycache__/ \ No newline at end of file diff --git a/src/go.py b/src/go.py new file mode 100644 --- /dev/null +++ b/src/go.py @@ -0,0 +1,51 @@ +class Go: + EMPTY=0 + BLACK=1 + WHITE=-1 + + ## Initializes self.board to a list[r][c]=Go.EMPTY. + def __init__(self,boardSize=19): + self.boardSize=boardSize + self.board=[[Go.EMPTY]*self.boardSize for x in self.board] + + ## Executes a move. + # + # Doesn't check for kos. Suicide not allowed. + # + # @param color Go.BLACK or Go.WHITE + # @return True on success, False on failure (illegal move) + def move(self,color,row,col): + if self.board[row][col]!=Go.EMPTY: return False + + self.board[row][col]=color + + # capture neighbors + for r,c in ((-1,0),(1,0),(0,-1),(0,1)): + self.temp=[[False]*self.boardSize for x in self.board] + if not self._floodFill(-color,row+r,col+c): self._remove() + + # check for suicide + self.temp=[[False]*self.boardSize for x in self.board] + if not self._floodFill(color,row,col): + self.board[row][col]=Go.EMPTY + return False + return True + + ## Checks for liberties of a stone at given coordinates. + # + # The stone's group is marked with True in self.temp, ready for capture if needed. Recursively called for stone's neighbors. + # + # @return True if alive, False if captured + def _floodFill(self,color,row,col): + if col<0 or col>=self.boardSize or row<0 or row>=self.boardSize: return False # out of range + if self.temp[row][col]: return False # already visited + if self.board[row][col]==Go.EMPTY: return True # found a liberty + if self.board[row][col]!=color: return False # opponent's stone + self.temp[row][col]=True # set visited + return self._floodFill(color,row,col-1) or self._floodFill(color,row,col+1) or self._floodFill(color,row-1,col) or self._floodFill(color,row+1,col) # check neighbors + + ## Removes stones at coordinates marked with True in self.temp. + def _remove(self): + for r in range(self.boardSize): + for c in range(self.boardSize): + if self.temp[r][c]: self.board[r][c]=Go.EMPTY diff --git a/src/gui.py b/src/gui.py --- a/src/gui.py +++ b/src/gui.py @@ -5,6 +5,8 @@ import math from epoint import * from corners import * from grid import * +from image_analyzer import * +from go import * class Application(tk.Frame): @@ -20,24 +22,26 @@ class Application(tk.Frame): # a captured frame with overlay graphics self.imgView=tk.Canvas(self) self.imgView.configure(width=480,height=360) - imgOrig=PIL.Image.open("../images/7.jpg") + self.imgOrig=PIL.Image.open("../images/7.jpg") - self.img=ImageTk.PhotoImage(imgOrig.resize((int(self.imgView['width']),int(self.imgView['height'])),resample=PIL.Image.BILINEAR)) + self.img=ImageTk.PhotoImage(self.imgOrig.resize((int(self.imgView['width']),int(self.imgView['height'])),resample=PIL.Image.BILINEAR)) self.imgView.bind('<1>',lambda e: self.addCorner(e.x,e.y)) - self.redrawImgView() self.imgView.grid(column=0,row=0) # board with detected stones self.boardView=BoardView(self) self.boardView.grid(column=1,row=0) + + self.redrawImgView() ## Stores a grid corner located at x,y coordinates. def addCorner(self,x,y): self.corners.add(x,y) if self.corners.canonizeOrder(): self.boardGrid=Grid(self.corners) + self.boardView.setBoardGrid(self.boardGrid) self.redrawImgView() @@ -59,6 +63,15 @@ class Application(tk.Frame): self.imgView.grid() + origWidth,origHeight=self.imgOrig.size + widthRatio=origWidth/self.img.width() + heightRatio=origHeight/self.img.height() + sizeCoef=max(widthRatio,heightRatio) + + shift=EPoint(origWidth-self.img.width()*sizeCoef,origHeight-self.img.height()*sizeCoef)/2 + + self.boardView.redrawState(self.imgOrig,sizeCoef,shift) + ## Marks a point at the image with a green cross. Used for corners. def markPoint(self,x,y): self.imgView.create_line(x-3,y-3,x+4,y+4,fill="#00ff00") @@ -70,12 +83,23 @@ class Application(tk.Frame): ## Handles and presents the game state as detected by the program. class BoardView(tk.Canvas): def __init__(self, master=None): + self.detector=ImageAnalyzer() + self.boardGrid=None + tk.Canvas.__init__(self, master) self.configure(width=360,height=360,background="#ffcc00") self.drawGrid() - self.drawStones() + + self.grid() + def redrawState(self,img,sizeCoef,shift): + self.detector.analyze(img,30,65,sizeCoef,shift) + + for r,row in enumerate(self.detector.board): + for c,point in enumerate(row): + self.drawStone(r,c,point) + self.grid() def drawGrid(self): @@ -90,16 +114,24 @@ class BoardView(tk.Canvas): for c in range(4,19,6): self.create_oval(r*18-2,c*18-2,r*18+2,c*18+2,fill='#000000') - def drawStones(self): - pass - ## Draws a stone at provided coordinates. # + # For an unknown color draws nothing and returns False. + # # @param r row coordinate, [0-18], counted from top # @param c column coordinate, [0-18], counted from left - # @param color color indicator, {'b','w'} + # @param color color indicator, Go.BLACK or Go.WHITE def drawStone(self,r,c,color): - self.create_oval(r*18-9,c*18-9,r*18+9,r*18+9,'#000000' if color=='b' else '#ffffff') + if color==Go.BLACK: hexCode='#000000' + elif color==Go.WHITE: hexCode='#ffffff' + else: return False + r+=1 + c+=1 + self.create_oval(c*18-9,r*18-9,c*18+9,r*18+9,fill=hexCode) + + def setBoardGrid(self,boardGrid): + self.boardGrid=boardGrid + self.detector.setGrid(boardGrid) root = tk.Tk() diff --git a/src/image_analyzer.py b/src/image_analyzer.py new file mode 100644 --- /dev/null +++ b/src/image_analyzer.py @@ -0,0 +1,46 @@ +from go import * + + +class ImageAnalyzer: + + def __init__(self): + self.board=[[Go.EMPTY]*19 for r in range(19)] + self.grid=None + + def analyze(self,image,tresB,tresW,sizeCoef,shift): + if self.grid==None: return False + + for r in range(19): + for c in range(19): + intersection=self.grid.intersections[r][c] + + if c>0: stoneWidth=sizeCoef*(intersection.x-self.grid.intersections[r][c-1].x) + else: stoneWidth=sizeCoef*(self.grid.intersections[r][c+1].x-intersection.x) + if r>0: stoneHeight=sizeCoef*(intersection.y-self.grid.intersections[r-1][c].y) + else: stoneHeight=sizeCoef*(self.grid.intersections[r+1][c].y-intersection.y) + + self.board[r][c]=self.analyzePoint(image,intersection*sizeCoef+shift,stoneWidth,stoneHeight,tresB,tresW) + + def analyzePoint(self,image,imageCoords,stoneWidth,stoneHeight,tresB,tresW): + b=w=e=0 + + cmax=max(int(stoneWidth*2//5),2) + rmax=max(int(stoneHeight*2//5),2) + + for r in range(-rmax,rmax+1): + for c in range(-cmax,cmax+1): + red,green,blue=image.getpixel((imageCoords.x+c,imageCoords.y+r)) + + I=(red+green+blue)/255/3 + m=min(red,green,blue) + S=1-m/I + if 100*ItresW: w+=1 + else: e+=1 + + if b>=w and b>=e: return Go.BLACK + if w>=b and w>=e: return Go.WHITE + return Go.EMPTY + + def setGrid(self,grid): + self.grid=grid