Files @ 52d1a214c032
Branch filter:

Location: OneEye/src/go/core.py - annotation

Laman
refactoring and documenting API
7f6fac7f6d8e
3798475f45c1
32221aadca28
07f3fda1cbd7
3798475f45c1
3798475f45c1
7f6fac7f6d8e
3798475f45c1
7ef3360afbe5
7ef3360afbe5
7ef3360afbe5
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
7ef3360afbe5
7ef3360afbe5
7ef3360afbe5
7ef3360afbe5
7ef3360afbe5
07f3fda1cbd7
32221aadca28
32221aadca28
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
32221aadca28
3798475f45c1
7ef3360afbe5
7ef3360afbe5
7ef3360afbe5
7ef3360afbe5
32221aadca28
3798475f45c1
32221aadca28
32221aadca28
3798475f45c1
3798475f45c1
7ef3360afbe5
32221aadca28
07f3fda1cbd7
7ef3360afbe5
7ef3360afbe5
7ef3360afbe5
7ef3360afbe5
3798475f45c1
0cb3fbe06b5d
07f3fda1cbd7
32221aadca28
32221aadca28
32221aadca28
3798475f45c1
32221aadca28
3798475f45c1
32221aadca28
3798475f45c1
3798475f45c1
077600f0c5f8
7ef3360afbe5
7ef3360afbe5
f9ab2070bd69
7ef3360afbe5
7ef3360afbe5
7ef3360afbe5
7ef3360afbe5
7ef3360afbe5
f9ab2070bd69
7ef3360afbe5
f9ab2070bd69
077600f0c5f8
32221aadca28
07f3fda1cbd7
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
4e67f9e9f027
3798475f45c1
0cb3fbe06b5d
0cb3fbe06b5d
0cb3fbe06b5d
0cb3fbe06b5d
32221aadca28
32221aadca28
0cb3fbe06b5d
077600f0c5f8
32221aadca28
077600f0c5f8
07f3fda1cbd7
07f3fda1cbd7
07f3fda1cbd7
3798475f45c1
07f3fda1cbd7
a98de30efa51
32221aadca28
07f3fda1cbd7
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
3798475f45c1
import logging

from util import EMPTY,BLACK,WHITE, colorNames,hashBoard,diffHash
from .helperboard import HelperBoard
from .gamerecord import GameRecord

log=logging.getLogger(__name__)

PASS=(99,99)


class Go:
	## Initializes self.board to a list[r][c]=EMPTY.
	def __init__(self,boardSize=19):
		self.boardSize=boardSize
		self.board=[[EMPTY]*boardSize for x in range(boardSize)]
		self.toMove=BLACK

		# utility field to allow undoing moves. but only useful right after doMove. this class doesn't need it, so it is not kept correct at other times
		self.captures=[None]*4
		self.captureCount=0

		self._helper=HelperBoard(self.board)
		self._freshHash=hashBoard(self.board) # always reflecting current state of the board
		self._hashes=[self._freshHash]
		self._record=GameRecord()

	## Executes a move.
	#
	#  Doesn't check for kos. Suicide not allowed.
	#
	#  @param color BLACK or WHITE
	#  @return True on success, False on failure (illegal move)
	def doMove(self,color,r,c):
		if color!=self.toMove: log.warning("move by %s out of order",colorNames[color])
		if (r,c)==PASS:
			self.toMove*=-1 # pass
			self._hashes.append(self._freshHash)
			return True
		if self.board[r][c]!=EMPTY: return False

		self.board[r][c]=color
		self._freshHash^=diffHash(r,c,EMPTY,color)

		# capture neighbors
		self.captureCount=0
		for dr,dc in ((-1,0),(1,0),(0,-1),(0,1)):
			self._helper.clear()
			if not self._helper.floodFill(-color,r+dr,c+dc,EMPTY):
				self.captures[self.captureCount]=(r+dr,c+dc)
				self.captureCount+=1
				self._remove()

		# check for suicide and prevent it
		self._helper.clear()
		if not self._helper.floodFill(color,r,c,EMPTY):
			self.board[r][c]=EMPTY
			self._freshHash=self._hashes[-1]
			return False
		self._record.move(color,r,c)
		self.toMove=-1*color
		self._hashes.append(self._freshHash)
		return True

	def undoMove(self,r,c,captures):
		if (r,c)!=PASS:
			assert self.board[r][c]==-1*self.toMove, "{0}!={1}".format(self.board[r][c],-1*self.toMove)

			if len(captures)>0:
				self._helper.clear()
				for (ri,ci) in captures:
					self._helper.floodFill(EMPTY,ri,ci)
				self._fill(self.toMove)

			self.board[r][c]=EMPTY
		self.toMove*=-1
		self._hashes.pop()
		self._freshHash=self._hashes[-1]

	def transitionMove(self,board):
		res=transitionMove(self.board,board)
		if not res: return res
		(r,c,color)=res
		return self.doMove(color,r,c)

	def load(self,board):
		for (r,row) in enumerate(board):
			for (c,x) in enumerate(row):
				self.board[r][c]=x
		self._freshHash=hashBoard(self.board)
		self._hashes=[self._freshHash]

	def hash(self):
		return self._hashes[-1]

	## Removes stones at coordinates marked with True in self.helper.
	def _remove(self):
		self._fill(EMPTY)

	def _fill(self,filling):
		for (r,c) in self._helper.getContinuousArea():
			self._freshHash^=diffHash(r,c,self.board[r][c],filling)
			self.board[r][c]=filling


def isLegalPosition(board):
	boardSize=len(board)
	temp=[[None]*boardSize for x in range(boardSize)]

	for r in range(boardSize):
		for c in range(boardSize):
			if board[r][c]==EMPTY: continue
			if temp[r][c]: continue
			if not dfs([(r,c)],board,temp): return False
	return True


def transitionMove(state1,state2):
	moves=[]
	for (r,(row1,row2)) in enumerate(zip(state1,state2)):
		for (c,(item1,item2)) in enumerate(zip(row1,row2)):
			if item1==EMPTY and item2!=EMPTY:
				moves.append((r,c,item2))

	if len(moves)==0:
		log.info("no new stone")
		return None
	elif len(moves)==1:
		log.info("new stone: %s",moves[0])
		return moves[0]
	else:
		log.warning("too many new stones: %s",moves)
		return False


def dfs(stack,board,mask):
	boardSize=len(board)
	(r,c)=stack[0]
	color=board[r][c]

	while len(stack)>0:
		(r,c)=stack.pop()
		if board[r][c]==EMPTY: return True
		elif board[r][c]!=color: continue
		elif mask[r][c]: return True
		mask[r][c]=True
		for (x,y) in ((0,-1),(-1,0),(0,1),(1,0)):
			if 0<=r+y<boardSize and 0<=c+x<boardSize:
				stack.append((r+y,c+x))
	return False