Changeset - 9ab11204b0f9
[Not reviewed]
default
0 6 0
Laman - 6 years ago 2018-12-03 22:08:58

StateBag exportuje záznam partie, BoardView zobrazuje pořadí tahů
6 files changed with 50 insertions and 16 deletions:
0 comments (0 inline, 0 general)
src/core.py
Show inline comments
 
@@ -40,29 +40,33 @@ class Core:
 
		self.analyze()
 

	
 
	def relativeFrame(self,step):
 
		self._imgIndex=(self._imgIndex+step)%len(self._imgs)
 
		imgPath=os.path.join(cfg.misc.imgDir,self._imgs[self._imgIndex])
 
		self._frame=PIL.Image.open(imgPath)
 
		self._guiMessages.send("setCurrentFrame",(self._frame.copy(),))
 
		self.analyze()
 

	
 
	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")
 

	
 
	def listen(self):
 
		listenerThread=threading.Thread(target=lambda: self._ownMessages.listen())
 
		listenerThread.start()
 

	
 
	def joinGui(self):
 
		self._guiProc.join()
 
		self._ownMessages.send("!kill",("core",))
 

	
 
	def _handleEvent(self,e):
src/go/engine.py
Show inline comments
 
from .core import PASS
 
from .transpositiontable import TranspositionTable
 
from . import core
 

	
 

	
 
## Compute move sequence from state1 to state2.
 
#
 
# @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)
 

	
 

	
 
class SpecGo(core.Go):
 
	def __init__(self,boardSize=19):
 
		super().__init__(boardSize)
 

	
 
	def listRelevantMoves(self,diff):
 
		"""There can be 3 different changes in the diff: additions, deletions and replacements.
 
		Additions can be taken as relevant right away.
 
		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
 
			colorKey=(1-color)>>1 # {-1,1}->{1,0}
 
			if action!="-" and (r,c) not in res[colorKey]:
 
				res[colorKey].add((r,c))
 
				if action=="*":
 
					for (ri,ci) in self.listNeighbours(r,c): # in case a stone was played and captured. !! might want to add even more
 
						res[1-colorKey].add((ri,ci))
 
			# this is rather sloppy but correct. the time will show if it is effective enough
 
			# just floodFill from the current intersection, add everything you find and also all the neighbours to be sure
 
			if action!="+" and (r,c) not in res[colorKey] and (r,c) not in res[1-colorKey]:
 
@@ -55,25 +58,27 @@ class SpecGo(core.Go):
 

	
 
class Engine:
 
	"""Class searching for move sequences changing one board state into another."""
 
	def __init__(self,g=None):
 
		self._g=g or SpecGo()
 
		self._moveList=(set(),set())
 
		self._transpositions=TranspositionTable()
 

	
 
	def load(self,state1):
 
		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
 

	
 
		for i in range(startDepth,5,2):
 
			seq=self._dfs(state2,i)
 
			if seq:
 
				seq.reverse()
 
				return seq
 
		return None
 

	
 
	def _dfs(self,state2,limit):
src/gui/__init__.py
Show inline comments
 
@@ -39,16 +39,17 @@ class GUI:
 
		self._coreMessages.send(actionName,args,kwargs)
 

	
 
	def _handleEvent(self,e):
 
		actions={"setCurrentFrame":self._frameHandler, "setGameState":self._stateHandler}
 
		(actionName,args,kwargs)=e
 

	
 
		return actions[actionName](*args,**kwargs)
 

	
 
	def _frameHandler(self,newFrame):
 
		self.mainWindow.setCurrentFrame(newFrame)
 
		self.root.event_generate("<<redrawImgView>>")
 

	
 
	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()
src/gui/boardview.py
Show inline comments
 
from .resizablecanvas import ResizableCanvas
 
from go.core import BLACK,WHITE
 

	
 

	
 
## Handles and presents the game state as detected by the program.
 
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
 
		for i in range(19):
 
			self.create_line(padding,self._cellHeight*i+padding,self._width-padding,self._cellHeight*i+padding,tags="row",fill="#000000") # rows
 
			self.create_line(self._cellWidth*i+padding,padding,self._cellWidth*i+padding,self._height-padding,tags="col",fill="#000000") # cols
 

	
 
		self._drawStars()
 
		self._drawCoordinates()
 

	
 
	def _drawStars(self):
 
		radius=2
 
@@ -48,30 +50,34 @@ class BoardView(ResizableCanvas):
 
			self.create_text(self._padding+i*self._cellWidth,self._height-self._padding+textWidth,text=letters[i],tags="coords")
 
			# rows
 
			self.create_text(self._padding-textWidth,self._padding+i*self._cellHeight,text=str(19-i),tags="coords")
 
			self.create_text(self._width-self._padding+textWidth,self._padding+i*self._cellHeight,text=str(19-i),tags="coords")
 

	
 
	## 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, 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
 

	
 
		x=c*self._cellWidth+self._padding
 
		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)
 
		self._cellWidth=(self._width-2*self._padding)/18
 
		self._cellHeight=(self._height-2*self._padding)/18
src/statebag/__init__.py
Show inline comments
 
@@ -51,25 +51,25 @@ def transformSingle(action1,item1,action
 
	elif action2=="-": return ("-",item2)
 
	elif (action1=="*" and item1==item2) or (action1=="-" and item1!=item2): return ("*",item2)
 
	return None
 

	
 

	
 
class StateBag:
 
	def __init__(self):
 
		self._states=[]
 

	
 
	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=sn.cachedDiff
 
		for s in reversed(self._states):
 
			sn.tryConnect(s,diff)
 
			diff=updateDiff(s.cachedDiff,diff)
 
		self._states.append(sn)
 
		return sn
 

	
 
	def merge(self,branch):
 
		pass
src/statebag/boardstate.py
Show inline comments
 
@@ -22,24 +22,37 @@ class GameTreeNode:
 
		self.weight=0
 

	
 
	## Connect itself after v if it gains itself more weight.
 
	def tryConnect(self,v,diff):
 
		moves=getTransitionSequence(v.parent,self.parent,-1*v.color,self.color,diff)
 
		if not moves: return
 
		w=v.weight+2-len(moves) # proper one move transition increases the weight by 1
 
		if w>self.weight:
 
			self.moves=moves
 
			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):
 
		self._board=tuple(tuple(x for x in row) for row in board)
 
		self.nodes=(GameTreeNode(self,BLACK),GameTreeNode(self,WHITE))
 
		self.cachedDiff=[]
 
		self._hash=None
 

	
 
	def tryConnect(self,s,diff=None):
 
		if diff is None: diff=self-s
 
		distEst=estimateDistance(diff)
 
		if distEst>3: return # we couldn't find every such move sequence anyway without a clever algorithm
 
@@ -50,24 +63,29 @@ class BoardState:
 
				v2.tryConnect(v1,diff)
 

	
 
	def hash(self):
 
		if self._hash is None: self._hash=hashBoard(self._board)
 
		return self._hash
 

	
 
	def export(self):
 
		return exportBoard(self._board)
 

	
 
	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]
 

	
 
	## Compute difference self-s.
 
	def __sub__(self,s):
 
		res=[]
 

	
 
		for (r,(row,rowS)) in enumerate(zip(self._board,s)):
 
			for (c,(item,itemS)) in enumerate(zip(row,rowS)):
 
				if item==itemS: continue
 
				elif itemS==EMPTY: res.append((r,c,"+",item))
0 comments (0 inline, 0 general)