import shogi import time import random import gradio as gr import re # --- AI 思考ロジック (守備・持久戦特化) --- BASE_VALUES = { shogi.PAWN: 100, shogi.LANCE: 300, shogi.KNIGHT: 400, shogi.SILVER: 500, shogi.GOLD: 600, shogi.BISHOP: 800, shogi.ROOK: 1000, shogi.KING: 20000, shogi.PROM_PAWN: 300, shogi.PROM_LANCE: 500, shogi.PROM_KNIGHT: 500, shogi.PROM_SILVER: 600, shogi.PROM_BISHOP: 1100, shogi.PROM_ROOK: 1300, } def get_dynamic_multipliers(move_count): if move_count < 40: return {"NAME": "序盤: 陣形構築", "LEFT": 1.1, "CENTER": 1.0, "RIGHT": 1.1, "DEFENSE": 1.4, "ATTACK": 0.7} elif move_count < 100: return {"NAME": "中盤: 鉄壁ガード", "LEFT": 1.0, "CENTER": 1.8, "RIGHT": 1.0, "DEFENSE": 2.5, "ATTACK": 0.2} else: return {"NAME": "終盤: 極限持久戦", "LEFT": 1.3, "CENTER": 3.0, "RIGHT": 1.3, "DEFENSE": 5.0, "ATTACK": 0.0} def evaluate_board(board, my_color): opp_color = not my_color move_count = len(board.move_stack) dm = get_dynamic_multipliers(move_count) score = 0 for square in shogi.SQUARES: piece = board.piece_at(square) if piece is None: continue file = 9 - (square % 9) rank = (square // 9) + 1 area = "LEFT" if file >= 7 else "CENTER" if file >= 4 else "RIGHT" val = BASE_VALUES.get(piece.piece_type, 0) * dm[area] if piece.color == my_color: rel_rank = rank if my_color == shogi.BLACK else (10 - rank) val *= dm["DEFENSE"] if rel_rank <= 3 else dm["ATTACK"] if rel_rank >= 7 else 1.0 score += val else: score -= val hand_weight = 1.8 + (move_count / 120.0) for pt in [shogi.PAWN, shogi.LANCE, shogi.KNIGHT, shogi.SILVER, shogi.GOLD, shogi.BISHOP, shogi.ROOK]: score += board.pieces_in_hand[my_color][pt] * BASE_VALUES.get(pt, 0) * hand_weight score -= board.pieces_in_hand[opp_color][pt] * BASE_VALUES.get(pt, 0) * (hand_weight * 0.8) return score def alpha_beta(board, depth, alpha, beta, is_max, color, start_time, limit): if time.time() - start_time > limit or board.is_game_over() or depth == 0: return evaluate_board(board, color) moves = list(board.legal_moves) if is_max: v = -float('inf') for m in moves: board.push(m) v = max(v, alpha_beta(board, depth-1, alpha, beta, False, color, start_time, limit)) board.pop() alpha = max(alpha, v) if beta <= alpha: break return v else: v = float('inf') for m in moves: board.push(m) v = min(v, alpha_beta(board, depth-1, alpha, beta, True, color, start_time, limit)) board.pop() beta = min(beta, v) if beta <= alpha: break return v def select_best_move(board): start = time.time() best = None best_v = -float('inf') moves = list(board.legal_moves) if not moves: return None for m in moves: board.push(m) v = alpha_beta(board, 2, -float('inf'), float('inf'), False, board.turn, start, 2.5) board.pop() if v > best_v: best_v, best = v, m return best # --- 日本語指し手解析 --- KANJI_NUM = {"一": 1, "二": 2, "三": 3, "四": 4, "五": 5, "六": 6, "七": 7, "八": 8, "九": 9, "1": 1, "2": 2, "3": 3, "4": 4, "5": 5, "6": 6, "7": 7, "8": 8, "9": 9} PIECE_NAME_MAP = {"歩": shogi.PAWN, "香": shogi.LANCE, "桂": shogi.KNIGHT, "銀": shogi.SILVER, "金": shogi.GOLD, "角": shogi.BISHOP, "飛": shogi.ROOK, "玉": shogi.KING, "王": shogi.KING, "と": shogi.PROM_PAWN, "杏": shogi.PROM_LANCE, "圭": shogi.PROM_KNIGHT, "全": shogi.PROM_SILVER, "馬": shogi.PROM_BISHOP, "龍": shogi.PROM_ROOK, "竜": shogi.PROM_ROOK} def get_candidates(board, jp_text): jp_text = jp_text.strip().replace(" ", "") if not jp_text: return [] target_square = None if jp_text.startswith("同"): if not board.move_stack: return [] target_square = board.move_stack[-1].to_square rest = jp_text[1:] else: m = re.match(r"([1-91-9一二三四五六七八九])([1-91-9一二三四五六七八九])", jp_text) if not m: return [] f_num = KANJI_NUM.get(m.group(1), int(m.group(1)) if m.group(1).isdigit() else 0) r_num = KANJI_NUM.get(m.group(2), int(m.group(2)) if m.group(2).isdigit() else 0) target_square = (r_num - 1) * 9 + (9 - f_num) rest = jp_text[2:] piece_type = next((v for k, v in PIECE_NAME_MAP.items() if k in rest), None) if not piece_type: return [] is_promote = "成" in rest and "不" not in rest is_unpromote = "不成" in rest is_drop = "打" in rest candidates = [] for move in board.legal_moves: if move.to_square == target_square: if move.drop_piece_type: if move.drop_piece_type == piece_type and not ("不成" in rest or "成" in rest): candidates.append(move) else: p = board.piece_at(move.from_square) if p and p.piece_type == piece_type: if is_promote and move.promotion: candidates.append(move) elif is_unpromote and not move.promotion: candidates.append(move) elif not is_promote and not is_unpromote: candidates.append(move) return candidates # --- UI 描画 --- def render_board_html(board): html = "
| " + "".join([f" | {9-i} | " for i in range(9)]) + "||
| {ranks[r]} | " for f in range(9): p = board.piece_at(r * 9 + f) bg = "#f3d291" if (r+f)%2==0 else "#ebc881" if p: sym = {1:"歩",2:"香",3:"桂",4:"銀",5:"金",6:"角",7:"飛",8:"玉",11:"と",12:"杏",13:"圭",14:"全",16:"馬",17:"龍"}.get(p.piece_type, "?") color = "black" if p.color == shogi.BLACK else "#d00" mark = "▲" if p.color == shogi.BLACK else "△" html += f"{mark}{sym} | " else: html += f"" html += f" | {r+1} |