def next_move(board: GameState) -> Move: """ returns best move calculated till timeout """ global timeout, stime, final_move initial_depth = 2 depth_extension_limit = 0 timeout = 100 # in seconds final_move = None stime = time() assert not board.is_game_over() final_score, final_move = next_move_restricted(board, max_depth=initial_depth) print(f"depth ({initial_depth}) chosen") for extension in range(1, depth_extension_limit): if time() - stime >= timeout: break score, move = next_move_restricted(board, max_depth=initial_depth + extension) if move is not None and score >= final_score: final_score, final_move = score, move print(f"depth ({initial_depth+extension}) chosen") return final_move
def main(): screen = p.display.set_mode((WIDTH, HEIGHT)) clock = p.time.Clock() screen.fill(p.Color(WHITE)) gs = GameState() # gs is the game state validMoves = gs.getValidMoves() moveMade = False loadImages() # we are only loading the images once in pygame running = True sqSelected = () # keeps track of the last user mouse clicks playerClicks = [] # keeps track of the last player clicks while running: for e in p.event.get(): if e.type == p.QUIT: running = False # mouse key handlers elif e.type == p.MOUSEBUTTONDOWN: location = p.mouse.get_pos() # location of x and y col = location[0] // SQ_SIZE row = location[1] // SQ_SIZE if sqSelected == (row, col): sqSelected = () playerClicks = [] # if the player clicks on a chess piece else: sqSelected = (row, col) playerClicks.append( sqSelected ) # this appends both the 1st and the 2nd clicks if len(playerClicks) == 2: # if the user made 2 mouse clicks move = Move(playerClicks[0], playerClicks[1], gs.board) print( str(move.getChessNotation()) + ' id: ' + str(move.moveID)) for i in range(len(validMoves)): if move == validMoves[i]: gs.makeMove(validMoves[i]) moveMade = True sqSelected = () playerClicks = [] # resets user clicks if not moveMade: playerClicks = [sqSelected] # keyboard handlers elif e.type == p.KEYDOWN: if e.key == p.K_z: # the undo key gs.undoMove() moveMade = True if moveMade: validMoves = gs.getValidMoves() moveMade = False drawGameState(screen, gs) clock.tick(MAX_FPS) p.display.flip()
def main(): p.init() screen = p.display.set_mode((WIDTH, HEIGHT)) clock = p.time.Clock() screen.fill(p.Color("white")) gs = GameState() valid_moves = gs.get_valid_moves() move_made = False # flag for when a move is made load_images() sq_selected = () player_clicks = [] running = True while running: for event in p.event.get(): if event.type == p.QUIT: running = False # mouse handlers elif event.type == p.MOUSEBUTTONDOWN: mouse_location = p.mouse.get_pos() col = mouse_location[0] // SQ_SIZE row = mouse_location[1] // SQ_SIZE if sq_selected == ( row, col): # the user clicked the same square twice sq_selected = () # clear selection player_clicks = [] # clear player clicks else: sq_selected = (row, col) # append for both 1st and 2nd clicks player_clicks.append(sq_selected) if len(player_clicks) == 2: move = Move(player_clicks[0], player_clicks[1], gs.board) print(move.get_chess_notation()) if move in valid_moves: gs.make_move(move) move_made = True sq_selected = () # reset user selection player_clicks = [] # reset player clicks else: player_clicks = [sq_selected] # key handlers elif event.type == p.KEYDOWN: if event.key == p.K_u: gs.undo_move() move_made = True if move_made: valid_moves = gs.get_valid_moves() move_made = False draw_game_state(screen, gs) clock.tick(MAX_FPS) p.display.flip()
def next_move_restricted(board: GameState, max_depth: int) -> Tuple[float, Move]: """ returns best move calculated till depth given """ depth_stime = time() global eval_time, moves_cnt, evals_cnt eval_time = 0 moves_cnt = 0 evals_cnt = 0 moves = board.getValidMoves() length = len(moves) step_size = max(1, length//6) moves_sets: List[List[Move]] = [] procs_list: List[Process] = [] conn_list: List[Connection] = [] for start in range(0, length, step_size): end = start+step_size if length-end < step_size: end = length moves_sets.append(moves[start: end]) for moves_sb_set in moves_sets: par_conn, ch_conn = Pipe() p = Process(target=minimax_handler, args=( ch_conn, board, moves_sb_set, max_depth)) p.start() procs_list.append(p) conn_list.append(par_conn) score, move, line = -inf, None, [] for conn in conn_list: curr_score: int = conn.recv() curr_move: Move = conn.recv() curr_line: List[Move] = conn.recv() if curr_score >= score: score, move, line = curr_score, curr_move, curr_line for p in procs_list: p.join() line_str = [" " if not move else move.getChessNotation() for move in line] print( f"depth [{max_depth}] done in {time()-depth_stime} score: {score}" f"\ndepth [{max_depth}] {line_str}" # f"evals_time : {eval_time}, eval_cnt: {evals_cnt}, moves_cnt: {moves_cnt}" ) if not move: return -inf, None return score, move
def minimax(board: GameState, alpha: float, beta: float, maximizer: bool, curDepth: int, max_depth: int) -> Tuple[float, Move]: """ returns an integer score and move which is the best current player can get """ if board.is_game_over(): if board.staleMate: return 0, None if board.checkMate: return (-inf if maximizer else +inf), None if curDepth == max_depth: return evaluate(board, not (board.whiteToMove ^ maximizer)), None # sending inf so that the branch is ignored by parent if final_move is not None and time() - stime > timeout: return +inf if maximizer else -inf, None moves = list(board.getValidMoves()) # moves.sort(key=move_score, reverse=True) assert moves != [] best_move = None if maximizer: best_score = -inf def is_better_score(curr, currbest): return curr >= currbest def update_AB(score): nonlocal alpha alpha = max(alpha, score) else: best_score = +inf def is_better_score(curr, currbest): return curr <= currbest def update_AB(score): nonlocal beta beta = min(beta, score) for move in moves: board.makeMove(move, by_AI=True) global moves_cnt moves_cnt += 1 curr_score, _ = minimax(board, alpha, beta, not maximizer, curDepth + 1, max_depth) board.undoMove() if is_better_score(curr_score, best_score): best_score = curr_score best_move = move update_AB(best_score) if alpha >= beta: break return best_score, best_move
def minimax(board: GameState, moves: List[Move], alpha: float, beta: float, maximizer: bool, curDepth: int, max_depth: int, moves_line: List[Move]) -> Tuple[float, Move, List[Move]]: """ returns an integer score and move which is the best current player can get """ if board.is_game_over(): moves_line.append(None) if board.staleMate: return 0, None, moves_line if board.checkMate: return (-inf if maximizer else +inf), None, moves_line if curDepth == max_depth: return evaluate(board, not(board.whiteToMove ^ maximizer)), None, moves_line # sending inf so that the branch is ignored by parent if final_move is not None and time() - stime > timeout: moves_line.append(None) return +inf if maximizer else -inf, None, moves_line # moves = list(board.getValidMoves()) assert moves != [] best_move = None best_line = [] best_score = -inf if maximizer else +inf for move in moves: board.makeMove(move, by_AI=True) moves_line.append(move) global moves_cnt moves_cnt += 1 curr_score, _, curr_line = minimax( board, board.getValidMoves(), alpha, beta, not maximizer, curDepth+1, max_depth, moves_line[:]) board.undoMove() moves_line.pop() if maximizer: if curr_score >= best_score: best_score, best_move, best_line = curr_score, move, curr_line alpha = max(alpha, best_score) else: if curr_score <= best_score: best_score, best_move, best_line = curr_score, move, curr_line beta = min(beta, best_score) if alpha >= beta: break return best_score, best_move, best_line