class Position(namedtuple('Position', 'board score')): def gen_moves(self): for i, p in enumerate(self.board): if p == 'K': for scanpos in range(i - 16,A9,-16): if self.board[scanpos] == 'k': yield (i,scanpos) elif self.board[scanpos] != '.': break if not p.isupper(): continue if p == 'C': for d in directions[p]: cfoot = 0 for j in count(i+d, d): q = self.board[j] if q.isspace():break if cfoot == 0 and q == '.':yield (i,j) elif cfoot == 0 and q != '.':cfoot += 1 elif cfoot == 1 and q.islower(): yield (i,j);break elif cfoot == 1 and q.isupper(): break; continue for d in directions[p]: for j in count(i+d, d): q = self.board[j] if q.isspace() or q.isupper(): break if p == 'P' and d in (E, W) and i > 128: break elif p in ('Q','K') and (j < 160 or j & 15 > 8 or j & 15 < 6): break elif p == 'B' and j < 128: break elif p == 'N': n_diff_x = (j - i) & 15 if n_diff_x == 14 or n_diff_x == 2: if self.board[i + (1 if n_diff_x == 2 else -1)] != '.': break else: if j > i and self.board[i + 16] != '.': break elif j < i and self.board[i - 16] != '.': break elif p == 'B' and self.board[i + d / 2] != '.':break yield (i, j) if p in 'PNBQK' or q.islower(): break def rotate(self): return Position( self.board[-2::-1].swapcase() + " ", -self.score) def nullmove(self): return self.rotate() def move(self, move): i, j = move p, q = self.board[i], self.board[j] put = lambda board, i, p: board[:i] + p + board[i+1:] board = self.board score = self.score + self.value(move) board = put(board, j, board[i]) board = put(board, i, '.') return Position(board, score).rotate() def value(self, move): i, j = move p, q = self.board[i], self.board[j] score = pst[p][j] - pst[p][i] if q.islower(): score += pst[q.upper()][255-j-1] return score Entry = namedtuple('Entry', 'lower upper') class Searcher: def __init__(self): self.tp_score = {} self.tp_move = {} self.history = set() self.nodes = 0 def bound(self, pos, gamma, depth, root=True): self.nodes += 1 depth = max(depth, 0) if pos.score <= -MATE_LOWER: return -MATE_UPPER if DRAW_TEST: if not root and pos in self.history: return 0 entry = self.tp_score.get((pos, depth, root), Entry(-MATE_UPPER, MATE_UPPER)) if entry.lower >= gamma and (not root or self.tp_move.get(pos) is not None): return entry.lower if entry.upper < gamma: return entry.upper def moves(): if depth > 0 and not root and any(c in pos.board for c in 'RNC'): yield None, -self.bound(pos.nullmove(), 1-gamma, depth-3, root=False) if depth == 0: yield None, pos.score killer = self.tp_move.get(pos) if killer and (depth > 0 or pos.value(killer) >= QS_LIMIT): yield killer, -self.bound(pos.move(killer), 1-gamma, depth-1, root=False) for move in sorted(pos.gen_moves(), key=pos.value, reverse=True): if depth > 0 or pos.value(move) >= QS_LIMIT: yield move, -self.bound(pos.move(move), 1-gamma, depth-1, root=False) best = -MATE_UPPER for move, score in moves(): best = max(best, score) if best >= gamma: if len(self.tp_move) > TABLE_SIZE: self.tp_move.clear() self.tp_move[pos] = move break if best < gamma and best < 0 and depth > 0: is_dead = lambda pos: any(pos.value(m) >= MATE_LOWER for m in pos.gen_moves()) if all(is_dead(pos.move(m)) for m in pos.gen_moves()): in_check = is_dead(pos.nullmove()) best = -MATE_UPPER if in_check else 0 if len(self.tp_score) > TABLE_SIZE: self.tp_score.clear() if best >= gamma: self.tp_score[pos, depth, root] = Entry(best, entry.upper) if best < gamma: self.tp_score[pos, depth, root] = Entry(entry.lower, best) return best def search(self, pos, history=()): self.nodes = 0 if DRAW_TEST: self.history = set(history) self.tp_score.clear() for depth in range(1, 1000): lower, upper = -MATE_UPPER, MATE_UPPER while lower < upper - EVAL_ROUGHNESS: gamma = (lower+upper+1)//2 score = self.bound(pos, gamma, depth) if score >= gamma: lower = score if score < gamma: upper = score self.bound(pos, lower, depth) yield depth, self.tp_move.get(pos), self.tp_score.get((pos, depth, True)).lower