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 elif board.variant == LOSERSCHESS: if pieceCount(board, board.color) == 1: return [], -MATED elif board.variant == SUICIDECHESS or board.variant == GIVEAWAYCHESS: if pieceCount(board, board.color) == 0: return [], -MATED elif board.variant == KINGOFTHEHILLCHESS: if testKingInCenter(board): return [], MATED elif board.variant == THREECHECKCHESS: if checkCount(board) == 3: return [], MATED elif board.variant == RACINGKINGSCHESS: if testKingInEightRow(board): 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], 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 # ############################################################################ if ply == 0: table.newSearch() table.setHashMove(depth, -1) probe = table.probe(board, depth, alpha, beta) if probe: move, score, hashf = probe score = VALUE_AT_PLY(score, ply) 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, GIVEAWAYCHESS, ATOMICCHESS, RACINGKINGSCHESS): 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, GIVEAWAYCHESS): mlist = [m for m in genCaptures(board)] if board.variant == LOSERSCHESS: if 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 else: valid_captures = [] for move in mlist: board.applyMove(move) if not board.opIsChecked(): valid_captures.append(move) board.popMove() mlist = valid_captures 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] elif board.variant == RACINGKINGSCHESS: mlist = [m for m in genAllMoves(board) if not board.willGiveCheck(m)] 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
def getStatus(board): lboard = board.board if board.variant == LOSERSCHESS: if testKingOnly(lboard): if board.color == WHITE: status = WHITEWON else: status = BLACKWON return status, WON_NOMATERIAL elif board.variant == SUICIDECHESS or board.variant == GIVEAWAYCHESS: if pieceCount(lboard, lboard.color) == 0: if board.color == WHITE: status = WHITEWON else: status = BLACKWON return status, WON_NOMATERIAL elif board.variant == HORDECHESS: if pieceCount(lboard, lboard.color) == 0 and board.color == WHITE: status = BLACKWON return status, WON_WIPEOUT elif board.variant == ATOMICCHESS: if lboard.boards[board.color][KING] == 0: if board.color == WHITE: status = BLACKWON else: status = WHITEWON return status, WON_KINGEXPLODE elif board.variant == KINGOFTHEHILLCHESS: if testKingInCenter(lboard): if board.color == BLACK: status = WHITEWON else: status = BLACKWON return status, WON_KINGINCENTER elif board.variant == THREECHECKCHESS: if checkCount(lboard) == 3: if board.color == BLACK: status = WHITEWON else: status = BLACKWON return status, WON_THREECHECK elif board.variant == RACINGKINGSCHESS: if testKingInEightRow(lboard): if board.color == BLACK: status = WHITEWON else: status = BLACKWON return status, WON_KINGINEIGHTROW else: if ldraw.testMaterial(lboard): return DRAW, DRAW_INSUFFICIENT hasMove = False for move in lmovegen.genAllMoves(lboard): if board.variant == ATOMICCHESS: if kingExplode(lboard, move, 1 - board.color) and not kingExplode( lboard, move, board.color): hasMove = True break elif kingExplode(lboard, move, board.color): continue lboard.applyMove(move) if lboard.opIsChecked(): lboard.popMove() continue hasMove = True lboard.popMove() break if not hasMove: if lboard.isChecked(): if board.variant == LOSERSCHESS: if board.color == WHITE: status = WHITEWON else: status = BLACKWON else: if board.color == WHITE: status = BLACKWON else: status = WHITEWON return status, WON_MATE else: if board.variant == LOSERSCHESS or board.variant == GIVEAWAYCHESS: if board.color == WHITE: status = WHITEWON else: status = BLACKWON return status, DRAW_STALEMATE elif board.variant == SUICIDECHESS: if pieceCount(lboard, WHITE) == pieceCount(lboard, BLACK): return status, DRAW_EQUALMATERIAL else: if board.color == WHITE and pieceCount( lboard, WHITE) < pieceCount(lboard, BLACK): status = WHITEWON else: status = BLACKWON return status, WON_LESSMATERIAL else: return DRAW, DRAW_STALEMATE if lboard.repetitionCount() >= 3: return DRAW, DRAW_REPITITION if ldraw.testFifty(lboard): return DRAW, DRAW_50MOVES return RUNNING, UNKNOWN_REASON
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 elif board.variant == LOSERSCHESS: if pieceCount(board, board.color) == 1: return [], -MATED elif board.variant == SUICIDECHESS or board.variant == GIVEAWAYCHESS: if pieceCount(board, board.color) == 0: return [], -MATED elif board.variant == KINGOFTHEHILLCHESS: if testKingInCenter(board): return [], MATED elif board.variant == THREECHECKCHESS: if checkCount(board) == 3: return [], MATED elif board.variant == RACINGKINGSCHESS: if testKingInEightRow(board): 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], 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 # ############################################################################ if ply == 0: table.newSearch() table.setHashMove(depth, -1) probe = table.probe(board, depth, alpha, beta) if probe: move, score, hashf = probe score = VALUE_AT_PLY(score, ply) 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, GIVEAWAYCHESS, ATOMICCHESS, RACINGKINGSCHESS): 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, GIVEAWAYCHESS): 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] elif board.variant == RACINGKINGSCHESS: mlist = [m for m in genAllMoves(board) if not board.willGiveCheck(m)] 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