chupu-bot / app.py
Raycosine
update AI
bcb887a
# 樗蒲AI,规则 + 2层 Minimax
import gradio as gr
import copy
import json
# 王采可能的采数为16, 8, 14, 10,可以过关或坑
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()