def parseFAN (board, fan): """ Parse a Long/Expanded Algebraic Notation string """ san = fan2SanRegex.sub(fan2SanFunc, fan) pawnFan = FAN_PIECES[board.color][PAWN] if san[0] == pawnFan: san = san.replace(pawnFan, "") # If the pawn file has been omitted from a capture fan notation, it # means that there was only one pawn able to move to the end cord. We # just need to find it. if san[0] == "x": # We need to find the endcord ourselves. Can't wait for parseSAN i = san.find("=") if i >= 0: tocord = san[1:i] else: tocord = san[1:] tcord = cordDic[tocord] from lmovegen import genAllMoves board_clone = board.clone() for altmove in genAllMoves(board_clone): if board_clone.arBoard[FCORD(altmove)] == PAWN and \ TCORD(altmove) == tcord: board_clone.applyMove(altmove) if not board_clone.opIsChecked(): san = reprFile(mfcord) + san board_clone.popMove() # We know there is only one pawn which can move to tcord, so # we stop work here break return parseSAN (board, san)
def alphaBeta (board, depth, alpha=-MATE_VALUE, beta=MATE_VALUE, ply=0): """ This is a alphabeta/negamax/quiescent/iterativedeepend search algorithm Based on moves found by the validator.py findmoves2 function and evaluated by eval.py. The function recalls itself "depth" times. If the last move in range depth was a capture, it will continue calling itself, only searching for captures. It returns a tuple of * a list of the path it found through the search tree (last item being the deepest) * a score of your standing the the last possition. """ global last, searching, nodes, movesearches, table, endtime, timecheck_counter foundPv = False hashf = hashfALPHA amove = [] ############################################################################ # Look up transposition table # ############################################################################ table.setHashMove (ply, -1) probe = table.probe (board.hash, ply, alpha, beta) if probe: move, score, hashf = probe table.setHashMove (ply, move) if hashf == hashfEXACT: return [move], score elif hashf == hashfBETA: beta = min(score, beta) elif hashf == hashfALPHA: alpha = score if alpha >= beta: return [move], score if ldraw.test(board): return [], 0 ############################################################################ # Cheking the time # ############################################################################ timecheck_counter -= 1 if timecheck_counter == 0: if time() > endtime: searching = False timecheck_counter = TIMECHECK_FREQ ############################################################################ # Break itereation if interupted or if times up # ############################################################################ if not searching: last = 1 return [], evaluateComplete(board, board.color) ############################################################################ # Go for quiescent search # ############################################################################ isCheck = board.isChecked() if depth <= 0: if isCheck: # Being in check is that serious, that we want to take a deeper look depth += 1 else: last = 0 return quiescent(board, alpha, beta, ply) ############################################################################ # Find and sort moves # ############################################################################ movesearches += 1 # TODO: Using heap is slower than simply doing a list.sort() heap = [] if isCheck: for move in genCheckEvasions(board): heappush(heap, (-getMoveValue (board, table, ply, move), move)) else: for move in genAllMoves(board): heappush(heap, (-getMoveValue (board, table, ply, move), move)) # This is needed on checkmate catchFailLow = None ############################################################################ # Loop moves # ############################################################################ while heap: nodes += 1 v, move = heappop(heap) board.applyMove(move) if not isCheck: if board.opIsChecked(): board.popMove() continue catchFailLow = move if foundPv: mvs, val = alphaBeta (board, depth-1, -alpha-1, -alpha, ply+1) val = -val if val > alpha and val < beta: mvs, val = alphaBeta (board, depth-1, -beta, -alpha, ply+1) val = -val else: mvs, val = alphaBeta (board, depth-1, -beta, -alpha, ply+1) val = -val board.popMove() if val > alpha: if val >= beta: table.record (board.hash, move, beta, hashfBETA, ply) # We don't want to use our valuable killer move spaces for # captures and promotions, as these are searched early anyways. if board.arBoard[move&63] == EMPTY and \ not move>>12 in PROMOTIONS: table.addKiller (ply, move) last = 2 return [move]+mvs, beta alpha = val amove = [move]+mvs hashf = hashfEXACT foundPv = True ############################################################################ # Return # ############################################################################ if amove: last = 3 table.record (board, amove[0], alpha, hashf, ply) if board.arBoard[amove[0]&63] == EMPTY: table.addKiller (ply, amove[0]) return amove, alpha if catchFailLow: last = 4 return [catchFailLow], alpha # If no moves were found, this must be a mate or stalemate last = 5 if isCheck: return [], -MATE_VALUE+ply-2 last = 6 return [], 0
def alphaBeta (board, depth, alpha=-MATE_VALUE, beta=MATE_VALUE, ply=0): """ This is a alphabeta/negamax/quiescent/iterativedeepend search algorithm Based on moves found by the validator.py findmoves2 function and evaluated by eval.py. The function recalls itself "depth" times. If the last move in range depth was a capture, it will continue calling itself, only searching for captures. It returns a tuple of * a list of the path it found through the search tree (last item being the deepest) * a score of your standing the the last possition. """ global last, searching, nodes, movesearches, table, endtime, timecheck_counter foundPv = False hashf = hashfALPHA amove = [] ############################################################################ # Look in the end game table ############################################################################ if useegtb: egtb = probeEndGameTable(board) if egtb: move, state, steps = egtb[0] if state == DRAW: score = 0 elif board.color == WHITE: if state == WHITEWON: score = MATE_VALUE-steps+2 else: score = -MATE_VALUE+steps-2 else: if state == WHITEWON: score = -MATE_VALUE+steps-2 else: score = MATE_VALUE-steps+2 last = 1 return [move], score ########################################################################### # We don't save repetition in the table, so we need to test draw before # # table. # ########################################################################### # We don't adjudicate draws. Clients may have different rules for that. if ply > 0: if ldraw.test(board): last = 2 return [], 0 ############################################################################ # Look up transposition table # ############################################################################ table.setHashMove (depth, -1) probe = table.probe (board, depth, alpha, beta) hashmove = None if probe: move, score, hashf = probe hashmove = move table.setHashMove (depth, move) if hashf == hashfEXACT: last = 3 return [move], score elif hashf == hashfBETA: beta = min(score, beta) elif hashf == hashfALPHA: alpha = score if hashf != hashfBAD and alpha >= beta: last = 4 return [move], score ############################################################################ # Cheking the time # ############################################################################ timecheck_counter -= 1 if timecheck_counter == 0: if time() > endtime: searching = False timecheck_counter = TIMECHECK_FREQ ############################################################################ # Break itereation if interupted or if times up # ############################################################################ if not searching: last = 5 return [], -evaluateComplete(board, 1-board.color) ############################################################################ # Go for quiescent search # ############################################################################ isCheck = board.isChecked() if depth <= 0: if isCheck: # Being in check is that serious, that we want to take a deeper look depth += 1 else: last = 6 mvs, val = quiescent(board, alpha, beta, ply) return mvs, val ############################################################################ # Find and sort moves # ############################################################################ movesearches += 1 if isCheck: moves = [(-getMoveValue(board,table,depth,m),m) for m in genCheckEvasions(board)] else: moves = [(-getMoveValue(board,table,depth,m),m) for m in genAllMoves(board)] moves.sort() # This is needed on checkmate catchFailLow = None ############################################################################ # Loop moves # ############################################################################ for moveValue, move in moves: nodes += 1 board.applyMove(move) if not isCheck: if board.opIsChecked(): board.popMove() continue catchFailLow = move if foundPv: mvs, val = alphaBeta (board, depth-1, -alpha-1, -alpha, ply+1) val = -val if val > alpha and val < beta: mvs, val = alphaBeta (board, depth-1, -beta, -alpha, ply+1) val = -val else: mvs, val = alphaBeta (board, depth-1, -beta, -alpha, ply+1) val = -val board.popMove() if val > alpha: if val >= beta: if searching: table.record (board, move, beta, hashfBETA, depth) # We don't want to use our valuable killer move spaces for # captures and promotions, as these are searched early anyways. if board.arBoard[move&63] == EMPTY and \ not move>>12 in PROMOTIONS: table.addKiller (depth, move) table.addButterfly(move, depth) last = 7 return [move]+mvs, beta alpha = val amove = [move]+mvs hashf = hashfEXACT foundPv = True ############################################################################ # Return # ############################################################################ if amove: if searching: table.record (board, amove[0], alpha, hashf, depth) if board.arBoard[amove[0]&63] == EMPTY: table.addKiller (depth, amove[0]) last = 8 return amove, alpha if catchFailLow: if searching: table.record (board, catchFailLow, alpha, hashf, depth) last = 9 return [catchFailLow], alpha # If no moves were found, this must be a mate or stalemate if isCheck: last = 10 return [], -MATE_VALUE+ply-2 last = 11 return [], 0
def alphaBeta(board, depth, alpha=-MATE_VALUE, beta=MATE_VALUE, ply=0): """ This is a alphabeta/negamax/quiescent/iterativedeepend search algorithm Based on moves found by the validator.py findmoves2 function and evaluated by eval.py. The function recalls itself "depth" times. If the last move in range depth was a capture, it will continue calling itself, only searching for captures. It returns a tuple of * a list of the path it found through the search tree (last item being the deepest) * a score of your standing the the last possition. """ global last, searching, nodes, movesearches, table, endtime, timecheck_counter foundPv = False hashf = hashfALPHA amove = [] ############################################################################ # Look in the end game table ############################################################################ if useegtb: egtb = probeEndGameTable(board) if egtb: move, state, steps = egtb[0] if state == DRAW: score = 0 elif board.color == WHITE: if state == WHITEWON: score = MATE_VALUE - steps + 2 else: score = -MATE_VALUE + steps - 2 else: if state == WHITEWON: score = -MATE_VALUE + steps - 2 else: score = MATE_VALUE - steps + 2 last = 1 return [move], score ########################################################################### # We don't save repetition in the table, so we need to test draw before # # table. # ########################################################################### # We don't adjudicate draws. Clients may have different rules for that. if ply > 0: if ldraw.test(board): last = 2 return [], 0 ############################################################################ # Look up transposition table # ############################################################################ table.setHashMove(depth, -1) probe = table.probe(board, depth, alpha, beta) hashmove = None if probe: move, score, hashf = probe hashmove = move table.setHashMove(depth, move) if hashf == hashfEXACT: last = 3 return [move], score elif hashf == hashfBETA: beta = min(score, beta) elif hashf == hashfALPHA: alpha = score if hashf != hashfBAD and alpha >= beta: last = 4 return [move], score ############################################################################ # Cheking the time # ############################################################################ timecheck_counter -= 1 if timecheck_counter == 0: if time() > endtime: searching = False timecheck_counter = TIMECHECK_FREQ ############################################################################ # Break itereation if interupted or if times up # ############################################################################ if not searching: last = 5 return [], -evaluateComplete(board, 1 - board.color) ############################################################################ # Go for quiescent search # ############################################################################ isCheck = board.isChecked() if depth <= 0: if isCheck: # Being in check is that serious, that we want to take a deeper look depth += 1 else: last = 6 mvs, val = quiescent(board, alpha, beta, ply) return mvs, val ############################################################################ # Find and sort moves # ############################################################################ movesearches += 1 if isCheck: moves = [(-getMoveValue(board, table, depth, m), m) for m in genCheckEvasions(board)] else: moves = [(-getMoveValue(board, table, depth, m), m) for m in genAllMoves(board)] moves.sort() # This is needed on checkmate catchFailLow = None ############################################################################ # Loop moves # ############################################################################ for moveValue, move in moves: nodes += 1 board.applyMove(move) if not isCheck: if board.opIsChecked(): board.popMove() continue catchFailLow = move if foundPv: mvs, val = alphaBeta(board, depth - 1, -alpha - 1, -alpha, ply + 1) val = -val if val > alpha and val < beta: mvs, val = alphaBeta(board, depth - 1, -beta, -alpha, ply + 1) val = -val else: mvs, val = alphaBeta(board, depth - 1, -beta, -alpha, ply + 1) val = -val board.popMove() if val > alpha: if val >= beta: if searching: table.record(board, move, beta, hashfBETA, depth) # We don't want to use our valuable killer move spaces for # captures and promotions, as these are searched early anyways. if board.arBoard[move&63] == EMPTY and \ not move>>12 in PROMOTIONS: table.addKiller(depth, move) table.addButterfly(move, depth) last = 7 return [move] + mvs, beta alpha = val amove = [move] + mvs hashf = hashfEXACT foundPv = True ############################################################################ # Return # ############################################################################ if amove: if searching: table.record(board, amove[0], alpha, hashf, depth) if board.arBoard[amove[0] & 63] == EMPTY: table.addKiller(depth, amove[0]) last = 8 return amove, alpha if catchFailLow: if searching: table.record(board, catchFailLow, alpha, hashf, depth) last = 9 return [catchFailLow], alpha # If no moves were found, this must be a mate or stalemate if isCheck: last = 10 return [], -MATE_VALUE + ply - 2 last = 11 return [], 0
def parseSAN (board, san): """ Parse a Short/Abbreviated Algebraic Notation string """ if not san: raise ParsingError, (san, _("the move is an empty string"), board.asFen()) elif len(san) < 2: raise ParsingError, (san, _("the move is too short"), board.asFen()) notat = san if notat[-1] in ("+", "#"): notat = notat[:-1] flag = NORMAL_MOVE # If last char is a piece char, we assue it the promote char c = notat[-1].lower() if c in chr2Sign: flag = chr2Sign[c] + 2 if notat[-2] == "=": notat = notat[:-2] else: notat = notat[:-1] if len(notat) < 2: raise ParsingError, (san, _("the move needs a piece and a cord"), board.asFen()) notat = notat.replace("0","O").replace("o","O") if notat.startswith("O-O"): if board.color == WHITE: fcord = board.ini_kings[0] #E1 if notat == "O-O": flag = KING_CASTLE if board.variant == FISCHERRANDOMCHESS: tcord = board.ini_rooks[0][1] else: tcord = G1 else: flag = QUEEN_CASTLE if board.variant == FISCHERRANDOMCHESS: tcord = board.ini_rooks[0][0] else: tcord = C1 else: fcord = board.ini_kings[1] #E8 if notat == "O-O": flag = KING_CASTLE if board.variant == FISCHERRANDOMCHESS: tcord = board.ini_rooks[1][1] else: tcord = G8 else: flag = QUEEN_CASTLE if board.variant == FISCHERRANDOMCHESS: tcord = board.ini_rooks[1][0] else: tcord = C8 return newMove (fcord, tcord, flag) if notat[0] in ("Q", "R", "B", "K", "N"): piece = chr2Sign[notat[0].lower()] notat = notat[1:] else: piece = PAWN if "x" in notat: notat, tcord = notat.split("x") if not tcord in cordDic: raise ParsingError, ( san, _("the captured cord (%s) is incorrect") % tcord, board.asFen()) tcord = cordDic[tcord] if piece == PAWN: # If a pawn is attacking an empty cord, we assue it an enpassant if board.arBoard[tcord] == EMPTY: flag = ENPASSANT else: if not notat[-2:] in cordDic: raise ParsingError, ( san, "the end cord (%s) is incorrect" % notat[-2:], board.asFen()) tcord = cordDic[notat[-2:]] notat = notat[:-2] # If there is any extra location info, like in the move Bexd1 or Nh3f4 we # want to know frank = None ffile = None if notat and notat[0] in reprRank: frank = int(notat[0])-1 notat = notat[1:] if notat and notat[0] in reprFile: ffile = ord(notat[0]) - ord("a") notat = notat[1:] if notat and notat[0] in reprRank: frank = int(notat[0])-1 notat = notat[1:] # We find all pieces who could have done it. (If san was legal, there should # never be more than one) from lmovegen import genAllMoves for move in genAllMoves(board): if TCORD(move) != tcord: continue f = FCORD(move) if board.arBoard[f] != piece: continue if frank != None and frank != RANK(f): continue if ffile != None and ffile != FILE(f): continue if flag in PROMOTIONS and FLAG(move) != flag: continue board_clone = board.clone() board_clone.applyMove(move) if board_clone.opIsChecked(): continue return move errstring = "no %s is able to move to %s" % (reprPiece[piece], reprCord[tcord]) raise ParsingError, (san, errstring, board.asFen())
def toSAN (board, move, localRepr=False): """ Returns a Short/Abbreviated Algebraic Notation string of a move The board should be prior to the move """ # Has to be importet at calltime, as lmovegen imports lmove from lmovegen import genAllMoves flag = move >> 12 if flag == KING_CASTLE: return "O-O" elif flag == QUEEN_CASTLE: return "O-O-O" fcord = (move >> 6) & 63 tcord = move & 63 fpiece = board.arBoard[fcord] tpiece = board.arBoard[tcord] part0 = "" part1 = "" if fpiece != PAWN: if localRepr: part0 += localReprSign[fpiece] else: part0 += reprSign[fpiece] part1 = reprCord[tcord] if not fpiece in (PAWN, KING): xs = [] ys = [] board_clone = board.clone() for altmove in genAllMoves(board_clone): mfcord = FCORD(altmove) if board_clone.arBoard[mfcord] == fpiece and \ mfcord != fcord and \ TCORD(altmove) == tcord: board_clone.applyMove(altmove) if not board_clone.opIsChecked(): xs.append(FILE(mfcord)) ys.append(RANK(mfcord)) board_clone.popMove() x = FILE(fcord) y = RANK(fcord) if ys or xs: if y in ys and not x in xs: # If we share rank with another piece, but not file part0 += reprFile[x] elif x in xs and not y in ys: # If we share file with another piece, but not rank part0 += reprRank[y] elif x in xs and y in ys: # If we share both file and rank with other pieces part0 += reprFile[x] + reprRank[y] else: # If we doesn't share anything, it is standard to put file part0 += reprFile[x] if tpiece != EMPTY or flag == ENPASSANT: part1 = "x" + part1 if fpiece == PAWN: part0 += reprFile[FILE(fcord)] notat = part0 + part1 if flag in PROMOTIONS: if localRepr: notat += "="+localReprSign[PROMOTE_PIECE(flag)] else: notat += "="+reprSign[PROMOTE_PIECE(flag)] board_clone = board.clone() board_clone.applyMove(move) if board_clone.isChecked(): for altmove in genAllMoves (board_clone): board_clone.applyMove(altmove) if board_clone.opIsChecked(): board_clone.popMove() continue notat += "+" break else: notat += "#" return notat
def alphaBeta (board, depth, alpha=-MATE_VALUE, beta=MATE_VALUE, ply=0): """ This is a alphabeta/negamax/quiescent/iterativedeepend search algorithm Based on moves found by the validator.py findmoves2 function and evaluated by eval.py. The function recalls itself "depth" times. If the last move in range depth was a capture, it will continue calling itself, only searching for captures. It returns a tuple of * a list of the path it found through the search tree (last item being the deepest) * a score of your standing the the last possition. """ global searching, nodes, table, endtime, timecheck_counter foundPv = False hashf = hashfALPHA amove = [] ############################################################################ # Mate distance pruning ############################################################################ MATED = -MATE_VALUE+ply MATE_IN_1 = MATE_VALUE-ply-1 if beta <= MATED: return [], MATED if beta >= MATE_IN_1: beta = MATE_IN_1 if alpha >= beta: return [], MATE_IN_1 if board.variant == ATOMICCHESS: if bin(board.boards[board.color][KING]).count("1") == 0: return [], MATED ############################################################################ # Look in the end game table ############################################################################ global egtb if egtb: tbhits = egtb.scoreAllMoves(board) if tbhits: move, state, steps = tbhits[0] if state == DRAW: score = 0 elif board.color == WHITE: if state == WHITEWON: score = MATE_VALUE-steps else: score = -MATE_VALUE+steps else: if state == WHITEWON: score = -MATE_VALUE+steps else: score = MATE_VALUE-steps return [move.move], score ########################################################################### # We don't save repetition in the table, so we need to test draw before # # table. # ########################################################################### # We don't adjudicate draws. Clients may have different rules for that. if ply > 0: if ldraw.test(board): return [], 0 ############################################################################ # Look up transposition table # ############################################################################ # TODO: add holder to hash if board.variant != CRAZYHOUSECHESS: if ply == 0: table.newSearch() table.setHashMove (depth, -1) probe = table.probe (board, depth, alpha, beta) hashmove = None if probe: move, score, hashf = probe score = VALUE_AT_PLY(score, ply) hashmove = move table.setHashMove (depth, move) if hashf == hashfEXACT: return [move], score elif hashf == hashfBETA: beta = min(score, beta) elif hashf == hashfALPHA: alpha = score if hashf != hashfBAD and alpha >= beta: return [move], score ############################################################################ # Cheking the time # ############################################################################ timecheck_counter -= 1 if timecheck_counter == 0: if time() > endtime: searching = False timecheck_counter = TIMECHECK_FREQ ############################################################################ # Break itereation if interupted or if times up # ############################################################################ if not searching: return [], -evaluateComplete(board, 1-board.color) ############################################################################ # Go for quiescent search # ############################################################################ isCheck = board.isChecked() if depth <= 0: if isCheck: # Being in check is that serious, that we want to take a deeper look depth += 1 elif board.variant in (LOSERSCHESS, SUICIDECHESS, ATOMICCHESS): return [], evaluateComplete(board, board.color) else: mvs, val = quiescent(board, alpha, beta, ply) return mvs, val ############################################################################ # Find and sort moves # ############################################################################ if board.variant in (LOSERSCHESS, SUICIDECHESS): mlist = [m for m in genCaptures(board)] if board.variant == LOSERSCHESS and isCheck: evasions = [m for m in genCheckEvasions(board)] eva_cap = [m for m in evasions if m in mlist] mlist = eva_cap if eva_cap else evasions if not mlist and not isCheck: mlist = [m for m in genAllMoves(board)] moves = [(-getMoveValue(board,table,depth,m),m) for m in mlist] elif board.variant == ATOMICCHESS: if isCheck: mlist = [m for m in genCheckEvasions(board) if not kingExplode(board, m, board.color)] else: mlist = [m for m in genAllMoves(board) if not kingExplode(board, m, board.color)] moves = [(-getMoveValue(board,table,depth,m),m) for m in mlist] else: if isCheck: moves = [(-getMoveValue(board,table,depth,m),m) for m in genCheckEvasions(board)] else: moves = [(-getMoveValue(board,table,depth,m),m) for m in genAllMoves(board)] moves.sort() # This is needed on checkmate catchFailLow = None ############################################################################ # Loop moves # ############################################################################ for moveValue, move in moves: nodes += 1 board.applyMove(move) if not isCheck: if board.opIsChecked(): board.popMove() continue catchFailLow = move if foundPv: mvs, val = alphaBeta (board, depth-1, -alpha-1, -alpha, ply+1) val = -val if val > alpha and val < beta: mvs, val = alphaBeta (board, depth-1, -beta, -alpha, ply+1) val = -val else: mvs, val = alphaBeta (board, depth-1, -beta, -alpha, ply+1) val = -val board.popMove() if val > alpha: if val >= beta: if searching and move>>12 != DROP: table.record (board, move, VALUE_AT_PLY(beta, -ply), hashfBETA, depth) # We don't want to use our valuable killer move spaces for # captures and promotions, as these are searched early anyways. if board.arBoard[move&63] == EMPTY and \ not move>>12 in PROMOTIONS: table.addKiller (depth, move) table.addButterfly(move, depth) return [move]+mvs, beta alpha = val amove = [move]+mvs hashf = hashfEXACT foundPv = True ############################################################################ # Return # ############################################################################ if amove: if searching: table.record (board, amove[0], VALUE_AT_PLY(alpha, -ply), hashf, depth) if board.arBoard[amove[0]&63] == EMPTY: table.addKiller (depth, amove[0]) return amove, alpha if catchFailLow: if searching: table.record (board, catchFailLow, VALUE_AT_PLY(alpha, -ply), hashf, depth) return [catchFailLow], alpha # If no moves were found, this must be a mate or stalemate if isCheck: return [], MATED return [], 0