| |
| import gradio as gr |
| import copy |
| import json |
| |
| PASSABLE_NUMBERS = {16, 8, 14, 10} |
|
|
| def is_passable(position, dice, barriers, pits): |
| |
| return dice in PASSABLE_NUMBERS |
|
|
| def evaluate(locHo, winHo, baseHo, ai_id=1, player_id=0, N=120, barrier=None): |
| ai_score = 0 |
| player_score = 0 |
|
|
| for i in range(4): |
| pos_ai = locHo[ai_id][i] |
| pos_player = locHo[player_id][i] |
|
|
| |
| if pos_ai >= N: |
| ai_score += 1000 |
| elif pos_ai >= barrier[1]: |
| ai_score += 500 |
| elif pos_ai >= barrier[0]: |
| ai_score += 200 |
| else: |
| ai_score += pos_ai * 5 |
|
|
| if baseHo[ai_id][i] != -1: |
| ai_score += 5 |
| if winHo[ai_id][i] == 1: |
| ai_score += 100 |
|
|
| |
| if pos_player >= N: |
| player_score += 1000 |
| elif pos_player >= barrier[1]: |
| player_score += 500 |
| elif pos_player >= barrier[0]: |
| player_score += 200 |
| else: |
| player_score += pos_player * 5 |
|
|
| if baseHo[player_id][i] != -1: |
| player_score += 5 |
| if winHo[player_id][i] == 1: |
| player_score += 100 |
|
|
| return ai_score - player_score |
|
|
| def get_all_moves(locHo, winHo, baseHo, dice, player_id, N, barrier, pit, eat_time): |
| moves = [] |
| for i in range(4): |
| if winHo[player_id][i] == 1: |
| continue |
|
|
| start_pos = locHo[player_id][i] |
| raw_target = min(start_pos + dice, N) |
| path = list(range(start_pos, raw_target + 1)) |
|
|
| stop_pos = raw_target |
| for b in barrier: |
| if b in path and dice not in PASSABLE_NUMBERS: |
| stop_pos = b |
| break |
| for p in pit: |
| if p in path and dice not in PASSABLE_NUMBERS: |
| stop_pos = p |
| break |
|
|
| |
| group_candidates = [] |
| for j in range(4): |
| if j != i and locHo[player_id][j] == stop_pos and baseHo[player_id][j] == -1: |
| group_candidates.append(j) |
|
|
| |
| moves.append((i, stop_pos, -1)) |
|
|
| |
| for g in group_candidates: |
| moves.append((i, stop_pos, g)) |
|
|
| return moves |
| def simulate_move(N, locHo, winHo, baseHo, move_piece, target_pos, groupped, player_id, dice, barrier, pit, eat_time): |
| locHo = copy.deepcopy(locHo) |
| winHo = copy.deepcopy(winHo) |
| baseHo = copy.deepcopy(baseHo) |
| opponent_id = 1 - player_id |
|
|
| captured = [] |
|
|
| |
| start_pos = locHo[player_id][move_piece] |
| path = list(range(start_pos + 1, min(start_pos + dice, target_pos) + 1)) |
|
|
| |
| if groupped != -1: |
| baseHo[player_id][move_piece] = groupped |
| else: |
| baseHo[player_id][move_piece] = -1 |
|
|
| |
| if eat_time == "every": |
| for j in range(4): |
| opp_pos = locHo[opponent_id][j] |
| if opp_pos in path: |
| captured.append(j) |
| locHo[opponent_id][j] = 0 |
| baseHo[opponent_id][j] = -1 |
| elif eat_time == "end": |
| for j in range(4): |
| if locHo[opponent_id][j] == target_pos: |
| captured.append(j) |
| locHo[opponent_id][j] = 0 |
| baseHo[opponent_id][j] = -1 |
|
|
| |
| locHo[player_id][move_piece] = target_pos |
|
|
| |
| if target_pos == N: |
| winHo[player_id][move_piece] = 1 |
| has_bonus_turn = False |
| if captured or dice in PASSABLE_NUMBERS: |
| has_bonus_turn = True |
| return locHo, winHo, baseHo, groupped, captured, has_bonus_turn |
|
|
| def minimax(locHo, winHo, baseHo, dice, depth, maximizing, player_id, N, barrier, pit, eat_time): |
| if depth == 0: |
| return evaluate(locHo, winHo, baseHo, N=N, barrier=barrier), None |
|
|
| best_score = float('-inf') if maximizing else float('inf') |
| best_move = None |
|
|
| moves = get_all_moves(locHo, winHo, baseHo, dice, player_id, N, barrier, pit, eat_time) |
| for move_piece, target_pos, groupped in moves: |
| new_loc, new_win, new_base, _, _, has_bonus_turn = simulate_move( |
| N, |
| locHo, winHo, baseHo, |
| move_piece, target_pos, groupped, |
| player_id, dice, barrier, pit, eat_time |
| ) |
|
|
|
|
| if has_bonus_turn: |
| |
| for bonus_dice in [16,8,14,10, |
| 12,11,5,4,3,2]: |
| score, _ = minimax( |
| new_loc, new_win, new_base, |
| bonus_dice, depth - 1, |
| maximizing, player_id, |
| N, barrier, pit, eat_time |
| ) |
| if maximizing and score > best_score: |
| best_score = score |
| best_move = (move_piece, target_pos, groupped) |
| elif not maximizing and score < best_score: |
| best_score = score |
| best_move = (move_piece, target_pos, groupped) |
| else: |
| |
| score, _ = minimax( |
| new_loc, new_win, new_base, |
| dice, depth - 1, |
| not maximizing, 1 - player_id, |
| N, barrier, pit, eat_time |
| ) |
| if maximizing and score > best_score: |
| best_score = score |
| best_move = (move_piece, target_pos, groupped) |
| elif not maximizing and score < best_score: |
| best_score = score |
| best_move = (move_piece, target_pos, groupped) |
|
|
|
|
| return best_score, best_move |
|
|
| def compute_ai_move(N, barrier_text, playerCount, piecesPerPlayer, eat_time, maxRound, currentRound, locHo_text, winHo_text, baseHo_text, dice): |
| try: |
| barrier = json.loads(barrier_text) |
| locHo = json.loads(locHo_text) |
| winHo = json.loads(winHo_text) |
| baseHo = json.loads(baseHo_text) |
| pit = [barrier[0] - 1, barrier[0] + 1, barrier[1] - 1, barrier[1] + 1] |
| except Exception as e: |
| return {"error": f"输入格式错误,请检查是否是合法的JSON数组: {str(e)}"} |
| _, best_move = minimax(locHo, winHo, baseHo, dice, 2, True, 1, N, barrier, pit, eat_time) |
|
|
| if not best_move: |
| return { |
| "move_piece": -1, |
| "target_position": -1, |
| "groupped": -1, |
| "captured": [], |
| "locHo": locHo, |
| "winHo": winHo, |
| "baseHo": baseHo |
| } |
|
|
| move_piece, target_pos, groupped = best_move |
|
|
| new_loc, new_win, new_base, g, captured, has_bonus_turn = simulate_move( |
| N, |
| locHo, winHo, baseHo, |
| move_piece, target_pos, groupped, |
| player_id=1, dice=dice, |
| barrier=barrier, pit=pit, eat_time=eat_time |
| ) |
|
|
| return { |
| "move_piece": move_piece, |
| "target_position": target_pos, |
| "groupped": g, |
| "captured": captured, |
| "locHo": new_loc, |
| "winHo": new_win, |
| "baseHo": new_base, |
| "has_bonus_turn": has_bonus_turn |
| } |
|
|
|
|
| demo = gr.Interface( |
| fn=compute_ai_move, |
| inputs=[ |
| gr.Number(label="矢的数量", value=120), |
| gr.Textbox(label="关的位置", value="[40, 80]"), |
| gr.Number(label="玩家数量", value=2), |
| gr.Number(label="棋子数量", value=4), |
| gr.Textbox(label="吃子方式", value="end"), |
| gr.Number(label="终局判断", value=-1), |
| gr.Number(label="当前轮数", value=1), |
| gr.Textbox(label="locHo", value="[[0, 0, 0, 0],[0, 0, 0, 0]]"), |
| gr.Textbox(label="winHo", value="[[0, 0, 0, 0],[0, 0, 0, 0]]"), |
| gr.Textbox(label="baseHo", value="[[-1, -1, -1, -1],[-1, -1, -1, -1]]"), |
| gr.Number(label="采数", value=3) |
| ], |
| outputs=gr.JSON(label="人机策略 Result"), |
| title="樗蒲 bot", |
| description="输入当前棋盘状态……" |
| ) |
|
|
| demo.queue(api_open=True).launch() |
|
|