diff --git a/src/core.py b/src/core.py --- a/src/core.py +++ b/src/core.py @@ -49,11 +49,15 @@ class Core: def analyze(self): if self.detector.analyze(self._frame): if isLegalPosition(self.detector.board): - self.states.pushState(self.detector.board) - self._guiMessages.send("setGameState",(self.detector.board,)) + state=self.states.pushState(self.detector.board) + rec=[] + if state: + rec=state.exportRecord() + log.debug("progressive game record: %s",rec) + self._guiMessages.send("setGameState", (self.detector.board,rec)) self.go.transitionMove(self.detector.board) - log.debug("game record: %s",self.go._record) + log.debug("conservative game record: %s",self.go._record) else: log.info("illegal position detected") diff --git a/src/go/engine.py b/src/go/engine.py --- a/src/go/engine.py +++ b/src/go/engine.py @@ -7,6 +7,7 @@ from . import core # # @param colorIn {BLACK,WHITE}: color to start the sequence # @param colorOut {BLACK,WHITE}: color to close the sequence +# @return [(c,row,col), ...] or None. c in {BLACK,WHITE} == {1,-1} def getTransitionSequence(state1,state2,colorIn,colorOut,diff): eng.load(state1) return eng.iterativelyDeepen(state2,diff,colorIn,colorOut) @@ -22,7 +23,9 @@ class SpecGo(core.Go): Deletions and replacements had to be captured, so we add their liberties. Also any non-missing stones of partially deleted (or replaced) groups had to be replayed, so add them too. Needs to handle snapback, throw-in. - There's no end to what could be theoretically relevant, but such sequences are long and we will pretend they won't happen.""" + There's no end to what could be theoretically relevant, but such sequences are long and we will pretend they won't happen. + + :return: (blackMoves,whiteMoves) == ({(row,col), ...}, {(row,col), ...})""" res=({PASS},{PASS}) for d in diff: (r,c,action,color)=d @@ -64,7 +67,9 @@ class Engine: self._g.load(state1) def iterativelyDeepen(self,state2,diff,colorIn,colorOut): - """Search for a move sequence from the loaded state to state2. Tries progressively longer sequences.""" + """Search for a move sequence from the loaded state to state2. Tries progressively longer sequences. + + :return: [(c,row,col), ...] or None. c in {BLACK,WHITE} == {1,-1}""" self._moveList=self._g.listRelevantMoves(diff) startDepth=1 if colorIn==colorOut else 2 self._g.toMove=colorIn diff --git a/src/gui/__init__.py b/src/gui/__init__.py --- a/src/gui/__init__.py +++ b/src/gui/__init__.py @@ -48,7 +48,8 @@ class GUI: self.mainWindow.setCurrentFrame(newFrame) self.root.event_generate("<>") - def _stateHandler(self,gameState): - self.mainWindow.boardView.redrawState(gameState) + def _stateHandler(self,gameState,moves): + labels={(row,col):(i+1) for (i,(c,row,col)) in enumerate(moves)} + self.mainWindow.boardView.redrawState(gameState,labels) gui=GUI() diff --git a/src/gui/boardview.py b/src/gui/boardview.py --- a/src/gui/boardview.py +++ b/src/gui/boardview.py @@ -7,19 +7,21 @@ class BoardView(ResizableCanvas): def __init__(self, master=None): super().__init__(master) + self._padding=24 self.configure(width=360,height=360,background="#ffcc00") - self._padding=24 - self._cellWidth=(self._width-2*self._padding)/18 - self._cellHeight=(self._height-2*self._padding)/18 - self._drawGrid() - def redrawState(self,gameState): - self.delete("black","white") + def redrawState(self,gameState,labels=dict()): + self.delete("black","white","label") for r,row in enumerate(gameState): for c,point in enumerate(row): - self._drawStone(r, c, point) + self._drawStone(r, c, point, labels.get((r,c))) + + def configure(self,*args,**kwargs): + super().configure(*args,**kwargs) + self._cellWidth=(self._width-2*self._padding)/18 + self._cellHeight=(self._height-2*self._padding)/18 def _drawGrid(self): padding=self._padding @@ -57,12 +59,14 @@ class BoardView(ResizableCanvas): # @param r row coordinate, [0-18], counted from top # @param c column coordinate, [0-18], counted from left # @param color color indicator, go.BLACK or go.WHITE - def _drawStone(self, r, c, color): + def _drawStone(self, r, c, color, label=""): if color==BLACK: hexCode='#000000' + altCode='#ffffff' tag="black" elif color==WHITE: hexCode='#ffffff' + altCode='#000000' tag="white" else: return False @@ -70,6 +74,8 @@ class BoardView(ResizableCanvas): y=r*self._cellHeight+self._padding radius=self._cellWidth/2 self.create_oval(x-radius,y-radius,x+radius,y+radius,tags=tag,fill=hexCode) + if label: + self.create_text(x,y,text=label,tags="label",fill=altCode) def _onResize(self,event): super()._onResize(event) diff --git a/src/statebag/__init__.py b/src/statebag/__init__.py --- a/src/statebag/__init__.py +++ b/src/statebag/__init__.py @@ -60,7 +60,7 @@ class StateBag: def pushState(self,board): sn=BoardState(board) if len(self._states)>0: - if sn==self._states[-1]: return None # no change + if sn==self._states[-1]: return self._states[-1] # no change sn.cachedDiff=sn-self._states[-1] else: sn.setWeight(1) diff --git a/src/statebag/boardstate.py b/src/statebag/boardstate.py --- a/src/statebag/boardstate.py +++ b/src/statebag/boardstate.py @@ -31,6 +31,19 @@ class GameTreeNode: self.prev=v self.weight=w + def exportRecord(self): + """:return: [(c,row,col), ...]. c in {BLACK,WHITE} == {1,-1}""" + sequence=[] + v=self + while v is not None: + sequence.append(v) + v=v.prev + + res=[] + for v in reversed(sequence): + res.extend(v.moves) + return res + class BoardState: def __init__(self,board): @@ -59,6 +72,11 @@ class BoardState: def exportDiff(self,s2): return "vvv\n{0}\n=== {1} ===\n{2}\n^^^".format(self.export(), s2-self, s2.export()) + def exportRecord(self): + """:return: [(c,row,col), ...]. c in {BLACK,WHITE} == {1,-1}""" + v=self.nodes[0] if self.nodes[0].weight>self.nodes[1].weight else self.nodes[1] + return v.exportRecord() + def __iter__(self): return iter(self._board) def __getitem__(self,key): return self._board[key]