Files
@ 630c42e6d376
Branch filter:
Location: OneEye/src/go/engine.py
630c42e6d376
4.2 KiB
text/x-python
requirements.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | 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.
: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]:
self._helper.clear()
self._helper.floodFill(color if action=="-" else 1-color, r, c)
res[colorKey].union(self._helper.getContinuousArea())
for (ri,ci) in self._helper.getContinuousArea():
res[colorKey].add((ri,ci))
res[1-colorKey].add((ri,ci))
for (rj,cj) in self.listNeighbours(ri,ci):
res[colorKey].add((rj,cj))
res[1-colorKey].add((rj,cj))
return res
def listNeighbours(self,r,c):
if r>0: yield (r-1,c)
if r+1<self.boardSize: yield (r+1,c)
if c>0: yield (r,c-1)
if c+1<self.boardSize: yield (r,c+1)
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.
: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):
"""Search for a "limit" move sequence from the loaded state to state2."""
g=self._g
moveSet=self._moveList[(1-g.toMove)>>1]
transKey=(g.hash()*state2.hash()*limit)&0xffffffff
transSeq=self._transpositions.get(transKey)
if transSeq is not None:
return transSeq[:] if transSeq else transSeq
for m in moveSet.copy():
if not g.doMove(g.toMove,*m): continue
captured=g.captures[:g.captureCount]
moveSet.remove(m)
if m==PASS: # no reason for both players to pass
self._moveList[(1-g.toMove)>>1].remove(m)
if limit>1:
seq=self._dfs(state2,limit-1)
if seq:
self._undoMove(m,captured)
seq.append((g.toMove,*m))
self._transpositions.put(transKey,seq[:])
return seq
else: self._transpositions.put(transKey,False)
if limit==1 and g.hash()==state2.hash():
self._undoMove(m,captured)
return [(g.toMove,*m)]
self._undoMove(m,captured)
return None
def _undoMove(self,move,captured):
g=self._g
g.undoMove(*move,captured)
k=(1-g.toMove)>>1
self._moveList[k].add(move)
if move==PASS:
self._moveList[1-k].add(move)
eng=Engine()
|