I. 挑戦:AIと格闘!Pythonで将棋ゲームを作成
1. 新しい挑戦とシリーズのテーマ
「Game Trial Log」シリーズ Vol.3へようこそ。
本シリーズの核となるテーマは、プログラミング無知な筆者Dr.takodemousが、AI(Gemini)の力を借りて『AIコーダー』としてどこまで通用するか?という挑戦です。
Vol.1(オセロ)、Vol.2(ナンプレ)に続き、今回はPythonで将棋ゲームの作成に挑みました。
2. 将棋アプリ動画公開:難題の末に完成!
GeminiとDr.takodemousが共同制作した将棋アプリの完成版を、動画で公開します。
しかし、完成までの道のりは、これまでのゲーム開発を遥かに超える難題の連続でした。具体的には、駒の動かし方、禁手、将棋内容に意味のある動きといったルール設定の壁、棋譜の導入、そしてAIの強さを決めるdepthの設定の難解さ、さらにはGUI設定の苦労(駒の漢字が読み込めない)など、数々の難問をクリアして出来上がっています。
そして、筆者とAIとの対局は、なんと2時間に及ぶ格闘の末、Dr.takodemousが勝利しました!編集を経て動画は45分に凝縮していますが、まさに激闘でした。
したがって、この苦戦格闘こそが、プログラミング初心者の醍醐味です。
3. 【衝撃の結末】Redkabagon、撮影前にギブアップ!?
そこで、Vol.2での煽りテロップに応えるべく、Redkabagonに挑戦を促しましたが、彼女は将棋の難解さを前に撮影開始前にギブアップを宣言しました。これにより、「Redkabagonギブアップ」という前回の公約が、挑戦以前の降参という形で果たされることとなりました。
そして、今回は限定公開動画が存在しません。彼女の迅速な潔さを通して、将棋というゲームの奥深さ、そして難解さが伝わるかもしれません。
II. AIコードの比較とR60世代へのメッセージ
1. 将棋ゲームの難解さとコードの進化
将棋のような複雑なロジックを持つゲームは、AIの力を借りても一筋縄ではいきませんでした。そのため、コードは何度もデバッグに遭遇しています。
筆者が今回のアプリ制作でR60世代に向けて強くメッセージしたいのは、AIを駆使すれば、複雑なゲームであっても、知識ゼロからその**「外殻」「ルール設定」「depth設定」「GUI設定」といった構造を学ぶこと**ができる、ということです。一緒にこの未知の世界へ飛び込みましょう。
2. 【サイト紹介】プロコーダーの脳トレアプリ
今回も前回同様、PR TIMESさんのサイトよりプロのコーダーが作成した脳トレWebゲームを紹介します。したがって、将棋アプリ版の完成度と、筆者(AIと共同制作)のコードの難解さを対比させることで、AIサポートの凄さが浮き彫りになります。
3. 無知なAIコーダー VS 将棋のコード美
したがって、プロのアプリの機能と対比しながら、筆者のコードを紹介します。プログラミング未経験以前の無知な筆者でも、AIを駆使すれば、コーダーと言えるレベルのプログラムがつくれるという事実は驚きです。
今回はデバッグの経緯を追体験していただくために、外殻、ルール設定、depth設定、GUI設定の4つの段階のコードを紹介します。
プロンプトno1(外殻の設定)
import tkinter as tk
# ウィンドウとキャンバスのサイズ
BOARD_SIZE = 450
CELL_SIZE = BOARD_SIZE // 9
SIDE_PANEL_WIDTH = 150
# 駒の定数
EMPTY = ' '
BLACK = '黒'
WHITE = '白'
# 駒の種類と表示名
PIECES = {
'OU': '玉', 'HI': '飛', 'KA': '角', 'KI': '金',
'GI': '銀', 'KE': '桂', '香': '香', 'FU': '歩',
'TO': 'と', 'N-GI': '成銀', 'N-KE': '成桂', 'N-KY': '成香',
'RY': '龍', 'UM': '馬'
}
# グローバル変数としてゲームの状態を保持
game_board = None
canvas = None
selected_piece = None
original_pos = None
current_player = BLACK
black_captures = []
white_captures = []
is_placing_piece = False
game_states = []
def create_initial_board():
"""
将棋盤の初期配置を表現する2次元リストを作成します。
駒は (プレイヤー, 駒の種類) のタプルで表現します。
"""
board = [[EMPTY] * 9 for _ in range(9)]
# 白(後手)の駒の配置
board[0][0] = (WHITE, '香')
board[0][1] = (WHITE, 'KE')
board[0][2] = (WHITE, 'GI')
board[0][3] = (WHITE, 'KI')
board[0][4] = (WHITE, 'OU')
board[0][5] = (WHITE, 'KI')
board[0][6] = (WHITE, 'GI')
board[0][7] = (WHITE, 'KE')
board[0][8] = (WHITE, '香')
board[1][1] = (WHITE, 'HI')
board[1][7] = (WHITE, 'KA')
for j in range(9):
board[2][j] = (WHITE, 'FU')
# 黒(先手)の駒の配置
board[8][0] = (BLACK, '香')
board[8][1] = (BLACK, 'KE')
board[8][2] = (BLACK, 'GI')
board[8][3] = (BLACK, 'KI')
board[8][4] = (BLACK, 'OU')
board[8][5] = (BLACK, 'KI')
board[8][6] = (BLACK, 'GI')
board[8][7] = (BLACK, 'KE')
board[8][8] = (BLACK, '香')
board[7][1] = (BLACK, 'KA')
board[7][7] = (BLACK, 'HI')
for j in range(9):
board[6][j] = (BLACK, 'FU')
return board
# --- 駒の移動ルール (変更なし) ---
def is_valid_pawn_move(start_row, start_col, end_row, end_col, player):
if player == BLACK:
return end_row == start_row - 1 and end_col == start_col
elif player == WHITE:
return end_row == start_row + 1 and end_col == start_col
return False
def is_valid_lance_move(start_row, start_col, end_row, end_col, player, board):
if end_col != start_col: return False
step = -1 if player == BLACK else 1
for r in range(start_row + step, end_row, step):
if board[r][start_col] != EMPTY: return False
return True
def is_valid_king_move(start_row, start_col, end_row, end_col):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
return dx <= 1 and dy <= 1
def is_valid_rook_move(start_row, start_col, end_row, end_col, board):
if start_row == end_row:
step = 1 if end_col > start_col else -1
for c in range(start_col + step, end_col, step):
if board[start_row][c] != EMPTY: return False
elif start_col == end_col:
step = 1 if end_row > start_row else -1
for r in range(start_row + step, end_row, step):
if board[r][start_col] != EMPTY: return False
else:
return False
return True
def is_valid_bishop_move(start_row, start_col, end_row, end_col, board):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
if dx != dy or dx == 0: return False
step_x = 1 if end_col > start_col else -1
step_y = 1 if end_row > start_row else -1
for i in range(1, dx):
if board[start_row + i * step_y][start_col + i * step_x] != EMPTY:
return False
return True
def is_valid_gold_move(start_row, start_col, end_row, end_col, player):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
if player == BLACK:
if dy == 1 and dx == 0: return True
if dy == 0 and dx == 1: return True
if dy == 1 and dx == 1 and start_row > end_row: return True
elif player == WHITE:
if dy == 1 and dx == 0: return True
if dy == 0 and dx == 1: return True
if dy == 1 and dx == 1 and start_row < end_row: return True
return False
def is_valid_silver_move(start_row, start_col, end_row, end_col, player):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
if player == BLACK:
if dy == 1 and dx == 1: return True
if dy == 1 and dx == 0 and start_row > end_row: return True
elif player == WHITE:
if dy == 1 and dx == 1: return True
if dy == 1 and dx == 0 and start_row < end_row: return True
return False
def is_valid_knight_move(start_row, start_col, end_row, end_col, player):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
if dx != 1 or dy != 2: return False
if player == BLACK:
return end_row == start_row - 2
elif player == WHITE:
return end_row == start_row + 2
return False
# --- ここまで移動ルール変更なし ---
def draw_pieces():
"""
Pythonのボード情報に基づいて、canvas上に駒を描画します。
"""
canvas.delete("all")
# 盤面の升目を描画
for i in range(9):
for j in range(9):
x1 = j * CELL_SIZE
y1 = i * CELL_SIZE
x2 = x1 + CELL_SIZE
y2 = y1 + CELL_SIZE
canvas.create_rectangle(x1, y1, x2, y2, outline="black", fill="#D2B48C")
# ボード上の駒を描画
for r in range(9):
for c in range(9):
piece_data = game_board[r][c]
if piece_data != EMPTY:
player, piece_type = piece_data
text_piece = PIECES[piece_type]
x = c * CELL_SIZE + CELL_SIZE // 2
y = r * CELL_SIZE + CELL_SIZE // 2
color_bg = "#E0C8A4" if player == BLACK else "#FFFFFF"
canvas.create_rectangle(
c * CELL_SIZE, r * CELL_SIZE,
(c + 1) * CELL_SIZE, (r + 1) * CELL_SIZE,
fill=color_bg, outline="black"
)
color_text = "black"
canvas.create_text(
x, y,
text=text_piece,
fill=color_text,
font=("Arial", 16)
)
# 持ち駒の表示
canvas.create_text(BOARD_SIZE + 20, 20, anchor="nw", text="黒の持ち駒", font=("Arial", 12))
for i, piece in enumerate(black_captures):
text_piece = PIECES[piece[1]]
y = 40 + i * 20
canvas.create_text(BOARD_SIZE + 30, y, anchor="nw", text=f"{text_piece}", font=("Arial", 12), tags=f"black_capture_{i}")
canvas.create_text(BOARD_SIZE + 20, 200, anchor="nw", text="白の持ち駒", font=("Arial", 12))
for i, piece in enumerate(white_captures):
text_piece = PIECES[piece[1]]
y = 220 + i * 20
canvas.create_text(BOARD_SIZE + 30, y, anchor="nw", text=f"{text_piece}", font=("Arial", 12), tags=f"white_capture_{i}")
def should_promote(piece_type, player, start_row, end_row):
"""
駒が成るべきかを判定するヘルパー関数
"""
if piece_type in ['OU', 'KI', 'N-GI', 'N-KE', 'N-KY', 'RY', 'UM']:
return False
if player == BLACK:
return end_row <= 2 or start_row <= 2
else:
return end_row >= 6 or start_row >= 6
def get_promoted_piece(piece_type):
"""
駒が成った後の種類を返す
"""
promotions = {
'FU': 'TO', '香': 'N-KY', 'KE': 'N-KE', 'GI': 'N-GI', 'HI': 'RY', 'KA': 'UM',
}
return promotions.get(piece_type, piece_type)
def find_king(player):
"""
指定されたプレイヤーの玉の位置を返す
"""
for r in range(9):
for c in range(9):
piece_data = game_board[r][c]
if piece_data != EMPTY and piece_data[0] == player and piece_data[1] == 'OU':
return r, c
return None, None
def is_in_check(player, board):
"""
指定されたプレイヤーの玉が王手状態にあるかを判定する
"""
king_row, king_col = find_king(player)
if king_row is None:
return False
opponent = WHITE if player == BLACK else BLACK
for r in range(9):
for c in range(9):
piece_data = board[r][c]
if piece_data != EMPTY and piece_data[0] == opponent:
piece_type = piece_data[1]
# 相手の駒が玉の位置に動けるかをチェック
is_valid = False
if piece_type == 'FU' and is_valid_pawn_move(r, c, king_row, king_col, opponent): is_valid = True
elif piece_type == '香' and is_valid_lance_move(r, c, king_row, king_col, opponent, board): is_valid = True
elif piece_type == 'HI' and is_valid_rook_move(r, c, king_row, king_col, board): is_valid = True
elif piece_type == 'KA' and is_valid_bishop_move(r, c, king_row, king_col, board): is_valid = True
elif piece_type == 'KI' and is_valid_gold_move(r, c, king_row, king_col, opponent): is_valid = True
elif piece_type == 'GI' and is_valid_silver_move(r, c, king_row, king_col, opponent): is_valid = True
elif piece_type == 'KE' and is_valid_knight_move(r, c, king_row, king_col, opponent): is_valid = True
elif piece_type == 'RY' and (is_valid_rook_move(r, c, king_row, king_col, board) or is_valid_king_move(r, c, king_row, king_col)): is_valid = True
elif piece_type == 'UM' and (is_valid_bishop_move(r, c, king_row, king_col, board) or is_valid_king_move(r, c, king_row, king_col)): is_valid = True
elif piece_type in ['TO', 'N-GI', 'N-KE', 'N-KY'] and is_valid_gold_move(r, c, king_row, king_col, opponent): is_valid = True
if is_valid:
return True
return False
def is_checkmate(player, board):
"""
指定されたプレイヤーが詰み状態にあるかを判定する
"""
if not is_in_check(player, board):
return False
king_row, king_col = find_king(player)
# 玉の周りのすべてのマスをチェック
for dr in range(-1, 2):
for dc in range(-1, 2):
if dr == 0 and dc == 0: continue
new_row, new_col = king_row + dr, king_col + dc
if 0 <= new_row < 9 and 0 <= new_col < 9:
target_piece = board[new_row][new_col]
if target_piece == EMPTY or target_piece[0] != player:
temp_board = [row[:] for row in board]
temp_board[new_row][new_col] = (player, 'OU')
temp_board[king_row][king_col] = EMPTY
if not is_in_check(player, temp_board):
return False
return True
def get_board_state_string():
"""
現在の盤面と持ち駒の状態を文字列として返す
"""
state_str = ""
for row in game_board:
for cell in row:
if cell == EMPTY:
state_str += "E"
else:
state_str += cell[0] + cell[1]
# 持ち駒をソートして文字列に追加
sorted_black_captures = sorted([p[1] for p in black_captures])
sorted_white_captures = sorted([p[1] for p in white_captures])
state_str += "BC" + "".join(sorted_black_captures)
state_str += "WC" + "".join(sorted_white_captures)
return state_str
def on_click(event):
"""
マウスがクリックされたときに呼び出される関数です。
"""
global selected_piece, original_pos, is_placing_piece, current_player, game_states
col = event.x // CELL_SIZE
row = event.y // CELL_SIZE
# 盤面外(持ち駒エリア)でのクリック
if col >= 9:
if not is_placing_piece:
clicked_item = canvas.find_closest(event.x, event.y)[0]
tags = canvas.gettags(clicked_item)
if tags and tags[0].startswith(f"black_capture_") and current_player == BLACK:
index = int(tags[0].split('_')[-1])
selected_piece = black_captures.pop(index)
is_placing_piece = True
draw_pieces()
canvas.create_text(event.x, event.y, text=PIECES[selected_piece[1]], fill="red", font=("Arial", 16), tags="drag_piece")
return
if tags and tags[0].startswith(f"white_capture_") and current_player == WHITE:
index = int(tags[0].split('_')[-1])
selected_piece = white_captures.pop(index)
is_placing_piece = True
draw_pieces()
canvas.create_text(event.x, event.y, text=PIECES[selected_piece[1]], fill="red", font=("Arial", 16), tags="drag_piece")
return
# 盤面内でのクリック
if 0 <= row < 9 and 0 <= col < 9:
if is_placing_piece:
# 持ち駒を打つ処理
if game_board[row][col] == EMPTY:
piece_type = selected_piece[1]
player = selected_piece[0]
# 行き所のない駒
if (piece_type == 'FU' or piece_type == '香') and ((player == BLACK and row == 0) or (player == WHITE and row == 8)):
print("その駒は行き所のないマスに打てません。")
black_captures.append(selected_piece) if player == BLACK else white_captures.append(selected_piece)
is_placing_piece = False
selected_piece = None
canvas.delete("drag_piece")
draw_pieces()
return
if piece_type == 'KE' and ((player == BLACK and row <= 1) or (player == WHITE and row >= 7)):
print("その駒は行き所のないマスに打てません。")
black_captures.append(selected_piece) if player == BLACK else white_captures.append(selected_piece)
is_placing_piece = False
selected_piece = None
canvas.delete("drag_piece")
draw_pieces()
return
# 二歩
if piece_type == 'FU':
is_nifu = False
for r in range(9):
piece_at_r = game_board[r][col]
if piece_at_r != EMPTY and piece_at_r[0] == player and piece_at_r[1] == 'FU':
is_nifu = True
break
if is_nifu:
print("その筋には既に歩があります。(二歩)")
black_captures.append(selected_piece) if player == BLACK else white_captures.append(selected_piece)
is_placing_piece = False
selected_piece = None
canvas.delete("drag_piece")
draw_pieces()
return
# 打ち歩詰め
opponent = WHITE if player == BLACK else BLACK
temp_board = [row[:] for row in game_board]
temp_board[row][col] = selected_piece
if is_in_check(opponent, temp_board) and piece_type == 'FU':
if is_checkmate(opponent, temp_board):
print("その手は打ち歩詰めです。")
black_captures.append(selected_piece) if player == BLACK else white_captures.append(selected_piece)
is_placing_piece = False
selected_piece = None
canvas.delete("drag_piece")
draw_pieces()
return
game_board[row][col] = selected_piece
is_placing_piece = False
selected_piece = None
# 局面の記録と千日手判定
current_state = get_board_state_string()
if game_states.count(current_state) >= 3:
print("千日手です!引き分けとなりました。")
return
else:
game_states.append(current_state)
current_player = WHITE if current_player == BLACK else BLACK
# 王手・詰み判定
if is_checkmate(current_player, game_board):
print("詰みです!ゲーム終了!")
elif is_in_check(current_player, game_board):
print("王手!")
else:
print("そのマスには駒を打てません。")
capture_list = black_captures if selected_piece[0] == BLACK else white_captures
capture_list.append(selected_piece)
canvas.delete("drag_piece")
draw_pieces()
return
else:
# 盤上の駒を選択する処理
piece_data = game_board[row][col]
if piece_data != EMPTY and piece_data[0] == current_player:
selected_piece = piece_data
original_pos = (row, col)
game_board[row][col] = EMPTY
draw_pieces()
canvas.create_text(event.x, event.y, text=PIECES[piece_data[1]], fill="red", font=("Arial", 16), tags="drag_piece")
def on_drag(event):
"""
マウスがドラッグされているときに呼び出される関数です。
"""
if selected_piece:
canvas.coords("drag_piece", event.x, event.y)
def on_release(event):
"""
マウスボタンが離されたときに呼び出される関数です。
"""
global selected_piece, original_pos, current_player, game_states
if is_placing_piece:
return
if selected_piece:
new_col = event.x // CELL_SIZE
new_row = event.y // CELL_SIZE
if not (0 <= new_row < 9 and 0 <= new_col < 9):
game_board[original_pos[0]][original_pos[1]] = selected_piece
selected_piece = None
original_pos = None
canvas.delete("drag_piece")
draw_pieces()
return
is_valid = False
piece_type = selected_piece[1]
player = selected_piece[0]
if piece_type == 'FU':
is_valid = is_valid_pawn_move(original_pos[0], original_pos[1], new_row, new_col, player)
elif piece_type == '香':
is_valid = is_valid_lance_move(original_pos[0], original_pos[1], new_row, new_col, player, game_board)
elif piece_type == 'OU':
is_valid = is_valid_king_move(original_pos[0], original_pos[1], new_row, new_col)
elif piece_type == 'HI':
is_valid = is_valid_rook_move(original_pos[0], original_pos[1], new_row, new_col, game_board)
elif piece_type == 'KA':
is_valid = is_valid_bishop_move(original_pos[0], original_pos[1], new_row, new_col, game_board)
elif piece_type == 'KI':
is_valid = is_valid_gold_move(original_pos[0], original_pos[1], new_row, new_col, player)
elif piece_type == 'GI':
is_valid = is_valid_silver_move(original_pos[0], original_pos[1], new_row, new_col, player)
elif piece_type == 'KE':
is_valid = is_valid_knight_move(original_pos[0], original_pos[1], new_row, new_col, player)
elif piece_type == 'RY' and (is_valid_rook_move(original_pos[0], original_pos[1], new_row, new_col, game_board) or is_valid_king_move(original_pos[0], original_pos[1], new_row, new_col)): is_valid = True
elif piece_type == 'UM' and (is_valid_bishop_move(original_pos[0], original_pos[1], new_row, new_col, game_board) or is_valid_king_move(original_pos[0], original_pos[1], new_row, new_col)): is_valid = True
elif piece_type in ['TO', 'N-GI', 'N-KE', 'N-KY'] and is_valid_gold_move(original_pos[0], original_pos[1], new_row, new_col, player): is_valid = True
target_piece = game_board[new_row][new_col]
if target_piece != EMPTY and target_piece[0] == player:
is_valid = False
if is_valid:
# 行き所のない駒
if (piece_type == 'FU' or piece_type == '香') and ((player == BLACK and new_row == 0) or (player == WHITE and new_row == 8)):
is_valid = False
print("その駒は行き所のないマスへ移動できません。")
if piece_type == 'KE' and ((player == BLACK and new_row <= 1) or (player == WHITE and new_row >= 7)):
is_valid = False
print("その駒は行き所のないマスへ移動できません。")
if is_valid:
temp_board = [row[:] for row in game_board]
if target_piece != EMPTY:
temp_board[new_row][new_col] = selected_piece
else:
temp_board[new_row][new_col] = selected_piece
if is_in_check(player, temp_board):
print("その手は反則です。自分の玉が王手になってしまいます。")
is_valid = False
if is_valid:
if target_piece != EMPTY:
captured_piece = target_piece[1]
if player == BLACK:
white_captures.append((BLACK, captured_piece))
else:
black_captures.append((WHITE, captured_piece))
if should_promote(piece_type, player, original_pos[0], new_row):
promoted_piece = get_promoted_piece(piece_type)
selected_piece = (player, promoted_piece)
game_board[new_row][new_col] = selected_piece
# 局面の記録と千日手判定
current_state = get_board_state_string()
if game_states.count(current_state) >= 3:
print("千日手です!引き分けとなりました。")
return
else:
game_states.append(current_state)
current_player = WHITE if current_player == BLACK else BLACK
# 王手・詰み判定
if is_checkmate(current_player, game_board):
print("詰みです!ゲーム終了!")
elif is_in_check(current_player, game_board):
print("王手!")
else:
print("その駒はそこに動かせません。")
game_board[original_pos[0]][original_pos[1]] = selected_piece
selected_piece = None
original_pos = None
canvas.delete("drag_piece")
draw_pieces()
def create_shogi_board():
"""
tkinterのウィンドウと将棋盤のGUIを作成します。
"""
global game_board, canvas, game_states
root = tk.Tk()
root.title("将棋ゲーム")
canvas = tk.Canvas(root, width=BOARD_SIZE + SIDE_PANEL_WIDTH, height=BOARD_SIZE, bg="#D2B48C")
canvas.pack()
canvas.bind("<Button-1>", on_click)
canvas.bind("<B1-Motion>", on_drag)
canvas.bind("<ButtonRelease-1>", on_release)
game_board = create_initial_board()
# 最初の局面を記録
game_states.append(get_board_state_string())
draw_pieces()
root.mainloop()
if __name__ == "__main__":
create_shogi_board()
プロンプトno2(ルール設定)
import tkinter as tk
# ウィンドウとキャンバスのサイズ
BOARD_SIZE = 450
CELL_SIZE = BOARD_SIZE // 9
SIDE_PANEL_WIDTH = 150
# 駒の定数
EMPTY = ' '
BLACK = '黒'
WHITE = '白'
# 駒の種類と表示名
PIECES = {
'OU': '玉', 'HI': '飛', 'KA': '角', 'KI': '金',
'GI': '銀', 'KE': '桂', '香': '香', 'FU': '歩',
'TO': 'と', 'N-GI': '成銀', 'N-KE': '成桂', 'N-KY': '成香',
'RY': '龍', 'UM': '馬'
}
# グローバル変数としてゲームの状態を保持
game_board = None
canvas = None
selected_piece = None
original_pos = None
current_player = BLACK
black_captures = []
white_captures = []
is_placing_piece = False
def create_initial_board():
"""
将棋盤の初期配置を表現する2次元リストを作成します。
駒は (プレイヤー, 駒の種類) のタプルで表現します。
"""
board = [[EMPTY] * 9 for _ in range(9)]
# 白(後手)の駒の配置
board[0][0] = (WHITE, '香')
board[0][1] = (WHITE, 'KE')
board[0][2] = (WHITE, 'GI')
board[0][3] = (WHITE, 'KI')
board[0][4] = (WHITE, 'OU')
board[0][5] = (WHITE, 'KI')
board[0][6] = (WHITE, 'GI')
board[0][7] = (WHITE, 'KE')
board[0][8] = (WHITE, '香')
board[1][1] = (WHITE, 'HI')
board[1][7] = (WHITE, 'KA')
for j in range(9):
board[2][j] = (WHITE, 'FU')
# 黒(先手)の駒の配置
board[8][0] = (BLACK, '香')
board[8][1] = (BLACK, 'KE')
board[8][2] = (BLACK, 'GI')
board[8][3] = (BLACK, 'KI')
board[8][4] = (BLACK, 'OU')
board[8][5] = (BLACK, 'KI')
board[8][6] = (BLACK, 'GI')
board[8][7] = (BLACK, 'KE')
board[8][8] = (BLACK, '香')
board[7][1] = (BLACK, 'KA')
board[7][7] = (BLACK, 'HI')
for j in range(9):
board[6][j] = (BLACK, 'FU')
return board
# --- 駒の移動ルール (変更なし) ---
def is_valid_pawn_move(start_row, start_col, end_row, end_col, player):
if player == BLACK:
return end_row == start_row - 1 and end_col == start_col
elif player == WHITE:
return end_row == start_row + 1 and end_col == start_col
return False
def is_valid_lance_move(start_row, start_col, end_row, end_col, player, board):
if end_col != start_col: return False
step = -1 if player == BLACK else 1
for r in range(start_row + step, end_row, step):
if board[r][start_col] != EMPTY: return False
return True
def is_valid_king_move(start_row, start_col, end_row, end_col):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
return dx <= 1 and dy <= 1
def is_valid_rook_move(start_row, start_col, end_row, end_col, board):
if start_row == end_row:
step = 1 if end_col > start_col else -1
for c in range(start_col + step, end_col, step):
if board[start_row][c] != EMPTY: return False
elif start_col == end_col:
step = 1 if end_row > start_row else -1
for r in range(start_row + step, end_row, step):
if board[r][start_col] != EMPTY: return False
else:
return False
return True
def is_valid_bishop_move(start_row, start_col, end_row, end_col, board):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
if dx != dy or dx == 0: return False
step_x = 1 if end_col > start_col else -1
step_y = 1 if end_row > start_row else -1
for i in range(1, dx):
if board[start_row + i * step_y][start_col + i * step_x] != EMPTY:
return False
return True
def is_valid_gold_move(start_row, start_col, end_row, end_col, player):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
if player == BLACK:
if dy == 1 and dx == 0: return True
if dy == 0 and dx == 1: return True
if dy == 1 and dx == 1 and start_row > end_row: return True
elif player == WHITE:
if dy == 1 and dx == 0: return True
if dy == 0 and dx == 1: return True
if dy == 1 and dx == 1 and start_row < end_row: return True
return False
def is_valid_silver_move(start_row, start_col, end_row, end_col, player):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
if player == BLACK:
if dy == 1 and dx == 1: return True
if dy == 1 and dx == 0 and start_row > end_row: return True
elif player == WHITE:
if dy == 1 and dx == 1: return True
if dy == 1 and dx == 0 and start_row < end_row: return True
return False
def is_valid_knight_move(start_row, start_col, end_row, end_col, player):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
if dx != 1 or dy != 2: return False
if player == BLACK:
return end_row == start_row - 2
elif player == WHITE:
return end_row == start_row + 2
return False
# --- ここまで移動ルール変更なし ---
def draw_pieces():
"""
Pythonのボード情報に基づいて、canvas上に駒を描画します。
"""
canvas.delete("all")
# 盤面の升目を描画
for i in range(9):
for j in range(9):
x1 = j * CELL_SIZE
y1 = i * CELL_SIZE
x2 = x1 + CELL_SIZE
y2 = y1 + CELL_SIZE
canvas.create_rectangle(x1, y1, x2, y2, outline="black", fill="#D2B48C")
# ボード上の駒を描画
for r in range(9):
for c in range(9):
piece_data = game_board[r][c]
if piece_data != EMPTY:
player, piece_type = piece_data
text_piece = PIECES[piece_type]
x = c * CELL_SIZE + CELL_SIZE // 2
y = r * CELL_SIZE + CELL_SIZE // 2
color_bg = "#E0C8A4" if player == BLACK else "#FFFFFF"
canvas.create_rectangle(
c * CELL_SIZE, r * CELL_SIZE,
(c + 1) * CELL_SIZE, (r + 1) * CELL_SIZE,
fill=color_bg, outline="black"
)
color_text = "black"
canvas.create_text(
x, y,
text=text_piece,
fill=color_text,
font=("Arial", 16)
)
# 持ち駒の表示
canvas.create_text(BOARD_SIZE + 20, 20, anchor="nw", text="黒の持ち駒", font=("Arial", 12))
for i, piece in enumerate(black_captures):
text_piece = PIECES[piece[1]]
y = 40 + i * 20
canvas.create_text(BOARD_SIZE + 30, y, anchor="nw", text=f"{text_piece}", font=("Arial", 12), tags=f"black_capture_{i}")
canvas.create_text(BOARD_SIZE + 20, 200, anchor="nw", text="白の持ち駒", font=("Arial", 12))
for i, piece in enumerate(white_captures):
text_piece = PIECES[piece[1]]
y = 220 + i * 20
canvas.create_text(BOARD_SIZE + 30, y, anchor="nw", text=f"{text_piece}", font=("Arial", 12), tags=f"white_capture_{i}")
def should_promote(piece_type, player, start_row, end_row):
"""
駒が成るべきかを判定するヘルパー関数
"""
if piece_type in ['OU', 'KI', 'N-GI', 'N-KE', 'N-KY', 'RY', 'UM']:
return False
if player == BLACK:
return end_row <= 2 or start_row <= 2
else:
return end_row >= 6 or start_row >= 6
def get_promoted_piece(piece_type):
"""
駒が成った後の種類を返す
"""
promotions = {
'FU': 'TO', '香': 'N-KY', 'KE': 'N-KE', 'GI': 'N-GI', 'HI': 'RY', 'KA': 'UM',
}
return promotions.get(piece_type, piece_type)
def find_king(player):
"""
指定されたプレイヤーの玉の位置を返す
"""
for r in range(9):
for c in range(9):
piece_data = game_board[r][c]
if piece_data != EMPTY and piece_data[0] == player and piece_data[1] == 'OU':
return r, c
return None, None
def is_in_check(player, board):
"""
指定されたプレイヤーの玉が王手状態にあるかを判定する
"""
king_row, king_col = find_king(player)
if king_row is None:
return False
opponent = WHITE if player == BLACK else BLACK
for r in range(9):
for c in range(9):
piece_data = board[r][c]
if piece_data != EMPTY and piece_data[0] == opponent:
piece_type = piece_data[1]
# 相手の駒が玉の位置に動けるかをチェック
is_valid = False
if piece_type == 'FU' and is_valid_pawn_move(r, c, king_row, king_col, opponent): is_valid = True
elif piece_type == '香' and is_valid_lance_move(r, c, king_row, king_col, opponent, board): is_valid = True
elif piece_type == 'HI' and is_valid_rook_move(r, c, king_row, king_col, board): is_valid = True
elif piece_type == 'KA' and is_valid_bishop_move(r, c, king_row, king_col, board): is_valid = True
elif piece_type == 'KI' and is_valid_gold_move(r, c, king_row, king_col, opponent): is_valid = True
elif piece_type == 'GI' and is_valid_silver_move(r, c, king_row, king_col, opponent): is_valid = True
elif piece_type == 'KE' and is_valid_knight_move(r, c, king_row, king_col, opponent): is_valid = True
elif piece_type == 'RY' and (is_valid_rook_move(r, c, king_row, king_col, board) or is_valid_king_move(r, c, king_row, king_col)): is_valid = True
elif piece_type == 'UM' and (is_valid_bishop_move(r, c, king_row, king_col, board) or is_valid_king_move(r, c, king_row, king_col)): is_valid = True
elif piece_type in ['TO', 'N-GI', 'N-KE', 'N-KY'] and is_valid_gold_move(r, c, king_row, king_col, opponent): is_valid = True
if is_valid:
return True
return False
def is_checkmate(player, board):
"""
指定されたプレイヤーが詰み状態にあるかを判定する
"""
if not is_in_check(player, board):
return False
king_row, king_col = find_king(player)
# 玉の周りのすべてのマスをチェック
for dr in range(-1, 2):
for dc in range(-1, 2):
if dr == 0 and dc == 0: continue
new_row, new_col = king_row + dr, king_col + dc
if 0 <= new_row < 9 and 0 <= new_col < 9:
target_piece = board[new_row][new_col]
if target_piece == EMPTY or target_piece[0] != player:
temp_board = [row[:] for row in board]
temp_board[new_row][new_col] = (player, 'OU')
temp_board[king_row][king_col] = EMPTY
if not is_in_check(player, temp_board):
return False
return True
def on_click(event):
"""
マウスがクリックされたときに呼び出される関数です。
"""
global selected_piece, original_pos, is_placing_piece, current_player
col = event.x // CELL_SIZE
row = event.y // CELL_SIZE
# 盤面外(持ち駒エリア)でのクリック
if col >= 9:
if not is_placing_piece:
clicked_item = canvas.find_closest(event.x, event.y)[0]
tags = canvas.gettags(clicked_item)
if tags and tags[0].startswith(f"black_capture_") and current_player == BLACK:
index = int(tags[0].split('_')[-1])
selected_piece = black_captures.pop(index)
is_placing_piece = True
draw_pieces()
canvas.create_text(event.x, event.y, text=PIECES[selected_piece[1]], fill="red", font=("Arial", 16), tags="drag_piece")
return
if tags and tags[0].startswith(f"white_capture_") and current_player == WHITE:
index = int(tags[0].split('_')[-1])
selected_piece = white_captures.pop(index)
is_placing_piece = True
draw_pieces()
canvas.create_text(event.x, event.y, text=PIECES[selected_piece[1]], fill="red", font=("Arial", 16), tags="drag_piece")
return
# 盤面内でのクリック
if 0 <= row < 9 and 0 <= col < 9:
if is_placing_piece:
# 持ち駒を打つ処理
if game_board[row][col] == EMPTY:
piece_type = selected_piece[1]
player = selected_piece[0]
# 行き所のない駒
if (piece_type == 'FU' or piece_type == '香') and ((player == BLACK and row == 0) or (player == WHITE and row == 8)):
print("その駒は行き所のないマスに打てません。")
black_captures.append(selected_piece) if player == BLACK else white_captures.append(selected_piece)
is_placing_piece = False
selected_piece = None
canvas.delete("drag_piece")
draw_pieces()
return
if piece_type == 'KE' and ((player == BLACK and row <= 1) or (player == WHITE and row >= 7)):
print("その駒は行き所のないマスに打てません。")
black_captures.append(selected_piece) if player == BLACK else white_captures.append(selected_piece)
is_placing_piece = False
selected_piece = None
canvas.delete("drag_piece")
draw_pieces()
return
# 二歩
if piece_type == 'FU':
is_nifu = False
for r in range(9):
piece_at_r = game_board[r][col]
if piece_at_r != EMPTY and piece_at_r[0] == player and piece_at_r[1] == 'FU':
is_nifu = True
break
if is_nifu:
print("その筋には既に歩があります。(二歩)")
black_captures.append(selected_piece) if player == BLACK else white_captures.append(selected_piece)
is_placing_piece = False
selected_piece = None
canvas.delete("drag_piece")
draw_pieces()
return
# 打ち歩詰め
if is_in_check(WHITE if current_player == BLACK else BLACK, game_board) and piece_type == 'FU':
temp_board = [row[:] for row in game_board]
temp_board[row][col] = selected_piece
if is_checkmate(WHITE if current_player == BLACK else BLACK, temp_board):
print("その手は打ち歩詰めです。")
black_captures.append(selected_piece) if player == BLACK else white_captures.append(selected_piece)
is_placing_piece = False
selected_piece = None
canvas.delete("drag_piece")
draw_pieces()
return
game_board[row][col] = selected_piece
is_placing_piece = False
selected_piece = None
current_player = WHITE if current_player == BLACK else BLACK
# 王手・詰み判定
if is_checkmate(current_player, game_board):
print("詰みです!ゲーム終了!")
elif is_in_check(current_player, game_board):
print("王手!")
else:
print("そのマスには駒を打てません。")
capture_list = black_captures if selected_piece[0] == BLACK else white_captures
capture_list.append(selected_piece)
canvas.delete("drag_piece")
draw_pieces()
return
else:
# 盤上の駒を選択する処理
piece_data = game_board[row][col]
if piece_data != EMPTY and piece_data[0] == current_player:
selected_piece = piece_data
original_pos = (row, col)
game_board[row][col] = EMPTY
draw_pieces()
canvas.create_text(event.x, event.y, text=PIECES[piece_data[1]], fill="red", font=("Arial", 16), tags="drag_piece")
def on_drag(event):
"""
マウスがドラッグされているときに呼び出される関数です。
"""
if selected_piece:
canvas.coords("drag_piece", event.x, event.y)
def on_release(event):
"""
マウスボタンが離されたときに呼び出される関数です。
"""
global selected_piece, original_pos, current_player
if is_placing_piece:
return
if selected_piece:
new_col = event.x // CELL_SIZE
new_row = event.y // CELL_SIZE
if not (0 <= new_row < 9 and 0 <= new_col < 9):
game_board[original_pos[0]][original_pos[1]] = selected_piece
selected_piece = None
original_pos = None
canvas.delete("drag_piece")
draw_pieces()
return
is_valid = False
piece_type = selected_piece[1]
player = selected_piece[0]
if piece_type == 'FU':
is_valid = is_valid_pawn_move(original_pos[0], original_pos[1], new_row, new_col, player)
elif piece_type == '香':
is_valid = is_valid_lance_move(original_pos[0], original_pos[1], new_row, new_col, player, game_board)
elif piece_type == 'OU':
is_valid = is_valid_king_move(original_pos[0], original_pos[1], new_row, new_col)
elif piece_type == 'HI':
is_valid = is_valid_rook_move(original_pos[0], original_pos[1], new_row, new_col, game_board)
elif piece_type == 'KA':
is_valid = is_valid_bishop_move(original_pos[0], original_pos[1], new_row, new_col, game_board)
elif piece_type == 'KI':
is_valid = is_valid_gold_move(original_pos[0], original_pos[1], new_row, new_col, player)
elif piece_type == 'GI':
is_valid = is_valid_silver_move(original_pos[0], original_pos[1], new_row, new_col, player)
elif piece_type == 'KE':
is_valid = is_valid_knight_move(original_pos[0], original_pos[1], new_row, new_col, player)
elif piece_type == 'RY' and (is_valid_rook_move(original_pos[0], original_pos[1], new_row, new_col, game_board) or is_valid_king_move(original_pos[0], original_pos[1], new_row, new_col)): is_valid = True
elif piece_type == 'UM' and (is_valid_bishop_move(original_pos[0], original_pos[1], new_row, new_col, game_board) or is_valid_king_move(original_pos[0], original_pos[1], new_row, new_col)): is_valid = True
elif piece_type in ['TO', 'N-GI', 'N-KE', 'N-KY'] and is_valid_gold_move(original_pos[0], original_pos[1], new_row, new_col, player): is_valid = True
target_piece = game_board[new_row][new_col]
if target_piece != EMPTY and target_piece[0] == player:
is_valid = False
if is_valid:
# 行き所のない駒
if (piece_type == 'FU' or piece_type == '香') and ((player == BLACK and new_row == 0) or (player == WHITE and new_row == 8)):
is_valid = False
print("その駒は行き所のないマスへ移動できません。")
if piece_type == 'KE' and ((player == BLACK and new_row <= 1) or (player == WHITE and new_row >= 7)):
is_valid = False
print("その駒は行き所のないマスへ移動できません。")
if is_valid:
temp_board = [row[:] for row in game_board]
if target_piece != EMPTY:
temp_board[new_row][new_col] = selected_piece
else:
temp_board[new_row][new_col] = selected_piece
if is_in_check(player, temp_board):
print("その手は反則です。自分の玉が王手になってしまいます。")
is_valid = False
if is_valid:
if target_piece != EMPTY:
captured_piece = target_piece[1]
if player == BLACK:
white_captures.append((BLACK, captured_piece))
else:
black_captures.append((WHITE, captured_piece))
if should_promote(piece_type, player, original_pos[0], new_row):
promoted_piece = get_promoted_piece(piece_type)
selected_piece = (player, promoted_piece)
game_board[new_row][new_col] = selected_piece
current_player = WHITE if current_player == BLACK else BLACK
# 王手・詰み判定
if is_checkmate(current_player, game_board):
print("詰みです!ゲーム終了!")
elif is_in_check(current_player, game_board):
print("王手!")
else:
print("その駒はそこに動かせません。")
game_board[original_pos[0]][original_pos[1]] = selected_piece
selected_piece = None
original_pos = None
canvas.delete("drag_piece")
draw_pieces()
def create_shogi_board():
"""
tkinterのウィンドウと将棋盤のGUIを作成します。
"""
global game_board, canvas
root = tk.Tk()
root.title("将棋ゲーム")
canvas = tk.Canvas(root, width=BOARD_SIZE + SIDE_PANEL_WIDTH, height=BOARD_SIZE, bg="#D2B48C")
canvas.pack()
canvas.bind("<Button-1>", on_click)
canvas.bind("<B1-Motion>", on_drag)
canvas.bind("<ButtonRelease-1>", on_release)
game_board = create_initial_board()
draw_pieces()
root.mainloop()
if __name__ == "__main__":
create_shogi_board()
プロンプトno3(Depth設定)
#DEPTH2変更
import tkinter as tk
from collections import Counter
from tkinter import messagebox
import sys
import random
import copy
import time
# ウィンドウとキャンバスのサイズ
BOARD_SIZE = 450
CELL_SIZE = BOARD_SIZE // 9
SIDE_PANEL_WIDTH = 150
CAPTURE_CELL_HEIGHT = 25
# 駒の定数
EMPTY = ' '
BLACK = '黒'
WHITE = '白'
# 駒の種類と表示名
PIECES = {
'OU': '玉', 'HI': '飛', 'KA': '角', 'KI': '金',
'GI': '銀', 'KE': '桂', '香': '香', 'FU': '歩',
'TO': 'と', 'N-GI': '成銀', 'N-KE': '成桂', 'N-KY': '成香',
'RY': '龍', 'UM': '馬'
}
# 駒の並び順 (駒台表示用)
PIECE_ORDER = ['HI', 'KA', 'RY', 'UM', 'KI', 'GI', 'KE', '香', 'FU']
# グローバル変数
game_board = None
canvas = None
root = None
selected_piece = None
original_pos = None
current_player = BLACK
black_captures = []
white_captures = []
is_placing_piece = False
game_states = [] # 千日手判定用の簡易履歴 (Minimax用)
highlight_moves = []
game_running = True
AI_PLAYER = WHITE
kifu_text = None # ★ 棋譜表示用ウィジェット
# ゲーム履歴 (Undo用)
history = []
king_flash_job = None
is_flashing = False
# AIの探索深さ (調整可能)
SEARCH_DEPTH = 2
# 駒の基本評価値
PIECE_VALUES = {
'OU': 10000,
'HI': 1000, 'KA': 800,
'KI': 500, 'GI': 400,
'KE': 300, '香': 200, 'FU': 100,
'RY': 1300, 'UM': 1000,
'TO': 600, 'N-GI': 500, 'N-KE': 400, 'N-KY': 300
}
# --- 棋譜表示用ロジック ---
# 日本語の筋・段
KANJI_COLUMNS = ["9", "8", "7", "6", "5", "4", "3", "2", "1"]
KANJI_ROWS = ["一", "二", "三", "四", "五", "六", "七", "八", "九"]
def coords_to_kifu(r, c):
"""座標 (row, col) を棋譜の段・筋に変換する (例: 7, 7 -> 3二)"""
return KANJI_COLUMNS[c] + KANJI_ROWS[r]
def piece_type_to_kifu(piece_type):
"""駒の種類を棋譜表示用の名前に変換する"""
return PIECES.get(piece_type, piece_type)
def format_move_to_kifu(move_details, current_player, board_before_move):
"""指し手を棋譜の文字列に変換する"""
move_type = move_details[0]
if move_type == 'move':
_, start_r, start_c, end_r, end_c, promote = move_details
# 移動前の駒タイプを取得
piece_data = board_before_move[start_r][start_c]
if piece_data == EMPTY: return "" # エラー回避
piece_type = piece_data[1]
kifu_piece = piece_type_to_kifu(piece_type)
kifu_end_pos = coords_to_kifu(end_r, end_c)
kifu_promote = "成" if promote else ""
return f"{kifu_end_pos}{kifu_piece}{kifu_promote}"
elif move_type == 'drop':
_, piece_type, end_r, end_c = move_details
kifu_piece = piece_type_to_kifu(piece_type)
kifu_end_pos = coords_to_kifu(end_r, end_c)
return f"{kifu_end_pos}{kifu_piece}打"
return ""
def append_kifu(kifu_string):
"""GUIのテキストエリアに棋譜を追記する"""
global kifu_text
if kifu_text:
kifu_text.config(state=tk.NORMAL)
kifu_text.insert(tk.END, kifu_string + "\n")
kifu_text.see(tk.END)
kifu_text.config(state=tk.DISABLED)
# --- 初期化 ---
def create_initial_board():
"""将棋盤の初期配置を作成します。"""
board = [[EMPTY] * 9 for _ in range(9)]
# 白(後手)の駒の配置
board[0][0] = (WHITE, '香'); board[0][1] = (WHITE, 'KE'); board[0][2] = (WHITE, 'GI'); board[0][3] = (WHITE, 'KI'); board[0][4] = (WHITE, 'OU'); board[0][5] = (WHITE, 'KI'); board[0][6] = (WHITE, 'GI'); board[0][7] = (WHITE, 'KE'); board[0][8] = (WHITE, '香')
board[1][1] = (WHITE, 'HI'); board[1][7] = (WHITE, 'KA')
for j in range(9): board[2][j] = (WHITE, 'FU')
# 黒(先手)の駒の配置
board[8][0] = (BLACK, '香'); board[8][1] = (BLACK, 'KE'); board[8][2] = (BLACK, 'GI'); board[8][3] = (BLACK, 'KI'); board[8][4] = (BLACK, 'OU'); board[8][5] = (BLACK, 'KI'); board[8][6] = (BLACK, 'GI'); board[8][7] = (BLACK, 'KE'); board[8][8] = (BLACK, '香')
board[7][7] = (BLACK, 'HI'); board[7][1] = (BLACK, 'KA')
for j in range(9): board[6][j] = (BLACK, 'FU')
return board
# --- 駒の移動ルール (香車修正済み) ---
def is_valid_pawn_move(start_row, start_col, end_row, end_col, player):
if player == BLACK: return end_row == start_row - 1 and end_col == start_col
elif player == WHITE: return end_row == start_row + 1 and end_col == start_col
return False
def is_valid_lance_move(start_row, start_col, end_row, end_col, player, board):
"""香車の移動ロジック (間に駒がないかチェック)"""
if end_col != start_col: return False
# 香車は後退できない
if player == BLACK and end_row >= start_row: return False
if player == WHITE and end_row <= start_row: return False
step = -1 if player == BLACK else 1
# 途中のマスに駒がないかチェック
for r in range(start_row + step, end_row, step):
if board[r][start_col] != EMPTY:
return False
# 目的地 (end_row, end_col) のチェック
target_piece = board[end_row][end_col]
# 自駒があれば移動不可
if target_piece != EMPTY and target_piece[0] == player:
return False
return True
def is_valid_king_move(start_row, start_col, end_row, end_col):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
return dx <= 1 and dy <= 1
def is_valid_rook_move(start_row, start_col, end_row, end_col, board):
if start_row == end_row:
step = 1 if end_col > start_col else -1
for c in range(start_col + step, end_col, step):
if board[start_row][c] != EMPTY: return False
elif start_col == end_col:
step = 1 if end_row > start_row else -1
for r in range(start_row + step, end_row, step):
if board[r][start_col] != EMPTY: return False
else: return False
return True
def is_valid_bishop_move(start_row, start_col, end_row, end_col, board):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
if dx != dy or dx == 0: return False
step_x = 1 if end_col > start_col else -1
step_y = 1 if end_row > start_row else -1
for i in range(1, dx):
if board[start_row + i * step_y][start_col + i * step_x] != EMPTY: return False
return True
def is_valid_gold_move(start_row, start_col, end_row, end_col, player):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
if player == BLACK:
if dy == 1 and dx == 0: return True
if dy == 0 and dx == 1: return True
if dy == 1 and dx == 1 and start_row > end_row: return True
elif player == WHITE:
if dy == 1 and dx == 0: return True
if dy == 0 and dx == 1: return True
if dy == 1 and dx == 1 and start_row < end_row: return True
return False
def is_valid_silver_move(start_row, start_col, end_row, end_col, player):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
if player == BLACK:
if dy == 1 and dx == 1: return True
if dy == 1 and dx == 0 and start_row > end_row: return True
elif player == WHITE:
if dy == 1 and dx == 1: return True
if dy == 1 and dx == 0 and start_row < end_row: return True
return False
def is_valid_knight_move(start_row, start_col, end_row, end_col, player):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
if dx != 1 or dy != 2: return False
if player == BLACK: return end_row == start_row - 2
elif player == WHITE: return end_row == start_row + 2
return False
def is_valid_move_logic(start_row, start_col, end_row, end_col, piece_type, player, board):
if piece_type == 'FU': return is_valid_pawn_move(start_row, start_col, end_row, end_col, player)
elif piece_type == '香': return is_valid_lance_move(start_row, start_col, end_row, end_col, player, board)
elif piece_type == 'OU': return is_valid_king_move(start_row, start_col, end_row, end_col)
elif piece_type == 'HI': return is_valid_rook_move(start_row, start_col, end_row, end_col, board)
elif piece_type == 'KA': return is_valid_bishop_move(start_row, start_col, end_row, end_col, board)
elif piece_type == 'KI': return is_valid_gold_move(start_row, start_col, end_row, end_col, player)
elif piece_type == 'GI': return is_valid_silver_move(start_row, start_col, end_row, end_col, player)
elif piece_type == 'KE': return is_valid_knight_move(start_row, start_col, end_row, end_col, player)
elif piece_type == 'RY': return is_valid_rook_move(start_row, start_col, end_row, end_col, board) or is_valid_king_move(start_row, start_col, end_row, end_col)
elif piece_type == 'UM': return is_valid_bishop_move(start_row, start_col, end_row, end_col, board) or is_valid_king_move(start_row, start_col, end_row, end_col)
elif piece_type in ['TO', 'N-GI', 'N-KE', 'N-KY']: return is_valid_gold_move(start_row, start_col, end_row, end_col, player)
return False
# --- 王手・詰み判定 ---
def find_king(player, board):
for r in range(9):
for c in range(9):
piece_data = board[r][c]
if piece_data != EMPTY and piece_data[0] == player and piece_data[1] == 'OU':
return r, c
return None, None
def is_in_check(player, board):
king_row, king_col = find_king(player, board)
if king_row is None: return False
opponent = WHITE if player == BLACK else BLACK
for r in range(9):
for c in range(9):
piece_data = board[r][c]
if piece_data != EMPTY and piece_data[0] == opponent:
piece_type = piece_data[1]
if is_valid_move_logic(r, c, king_row, king_col, piece_type, opponent, board):
return True
return False
def is_promoted_piece(piece_type):
return piece_type in ['TO', 'N-KY', 'N-KE', 'N-GI', 'RY', 'UM']
def should_promote(piece_type, player, start_row, end_row):
if piece_type in ['OU', 'KI'] or is_promoted_piece(piece_type): return False
if player == BLACK:
return end_row <= 2 or start_row <= 2
else:
return end_row >= 6 or start_row >= 6
def get_promoted_piece(piece_type):
promotions = {'FU': 'TO', '香': 'N-KY', 'KE': 'N-KE', 'GI': 'N-GI', 'HI': 'RY', 'KA': 'UM'}
return promotions.get(piece_type, piece_type)
def get_valid_moves(r, c, board, current_captures):
if board[r][c] == EMPTY: return []
player, piece_type = board[r][c]
valid_moves = []
for end_r in range(9):
for end_c in range(9):
if r == end_r and c == end_c: continue
target_piece = board[end_r][end_c]
if target_piece != EMPTY and target_piece[1] == 'OU': continue
if target_piece != EMPTY and target_piece[0] == player: continue
if not is_valid_move_logic(r, c, end_r, end_c, piece_type, player, board): continue
if piece_type in ['FU', '香'] and ((player == BLACK and end_r == 0) or (player == WHITE and end_r == 8)): continue
if piece_type == 'KE' and ((player == BLACK and end_r <= 1) or (player == WHITE and end_r >= 7)): continue
temp_board = [row[:] for row in board]
temp_board[end_r][end_c] = (player, piece_type)
temp_board[r][c] = EMPTY
if not is_in_check(player, temp_board):
valid_moves.append((end_r, end_c))
return valid_moves
def get_all_legal_moves(player, board, black_captures_in, white_captures_in):
all_moves = []
# 1. 盤上の駒の移動
for r in range(9):
for c in range(9):
piece_data = board[r][c]
if piece_data != EMPTY and piece_data[0] == player:
piece_type = piece_data[1]
valid_ends = get_valid_moves(r, c, board, black_captures_in if player == BLACK else white_captures_in)
for end_r, end_c in valid_ends:
is_forced_promotion = False
if piece_type in ['FU', '香'] and ((player == BLACK and end_r == 0) or (player == WHITE and end_r == 8)): is_forced_promotion = True
if piece_type == 'KE' and ((player == BLACK and end_r <= 1) or (player == WHITE and end_r >= 7)): is_forced_promotion = True
can_promote_option = should_promote(piece_type, player, r, end_r) and not is_forced_promotion
if is_promoted_piece(piece_type):
all_moves.append(('move', r, c, end_r, end_c, False))
continue
if is_forced_promotion:
all_moves.append(('move', r, c, end_r, end_c, True))
elif can_promote_option:
all_moves.append(('move', r, c, end_r, end_c, True))
all_moves.append(('move', r, c, end_r, end_c, False))
else:
all_moves.append(('move', r, c, end_r, end_c, False))
# 2. 持ち駒の打ち込み
capture_list = black_captures_in if player == BLACK else white_captures_in
available_drop_pieces = set([p[1] for p in capture_list])
for piece_type in available_drop_pieces:
for r in range(9):
for c in range(9):
if board[r][c] == EMPTY:
is_valid_drop = True
if (piece_type == 'FU' or piece_type == '香') and ((player == BLACK and r == 0) or (player == WHITE and r == 8)): is_valid_drop = False
if piece_type == 'KE' and ((player == BLACK and r <= 1) or (player == WHITE and r >= 7)): is_valid_drop = False
if piece_type == 'FU':
for r_check in range(9):
piece_at_r = board[r_check][c]
if piece_at_r != EMPTY and piece_at_r[0] == player and piece_at_r[1] == 'FU':
is_valid_drop = False; break
if is_valid_drop:
temp_board = [row[:] for row in board]
temp_board[r][c] = (player, piece_type)
opponent = WHITE if player == BLACK else BLACK
temp_caps_b = black_captures_in[:]
temp_caps_w = white_captures_in[:]
temp_capture_list = temp_caps_b if player == BLACK else temp_caps_w
try:
temp_capture_list.remove((player, piece_type))
except ValueError:
pass
# 打ち歩詰めチェック
if piece_type == 'FU' and is_in_check(opponent, temp_board) and is_checkmate(opponent, temp_board, temp_caps_b, temp_caps_w):
is_valid_drop = False
# 王手放置チェック
if is_in_check(player, temp_board):
is_valid_drop = False
if is_valid_drop:
all_moves.append(('drop', piece_type, r, c))
return all_moves
def is_checkmate(player, board, black_captures_in, white_captures_in):
if not is_in_check(player, board):
return False
legal_moves = get_all_legal_moves(player, board, black_captures_in, white_captures_in)
return len(legal_moves) == 0
# --- 強化AIロジック (Minimax) ---
def apply_move(board, captures_black, captures_white, move, player):
new_board = [row[:] for row in board]
new_caps_b = captures_black[:]
new_caps_w = captures_white[:]
capture_list_player = new_caps_b if player == BLACK else new_caps_w
move_type = move[0]
if move_type == 'move':
_, start_r, start_c, end_r, end_c, promote = move
piece_data = new_board[start_r][start_c]
piece_type = piece_data[1]
target_piece = new_board[end_r][end_c]
if target_piece != EMPTY:
captured_piece = target_piece[1]
demotion = {'TO': 'FU', 'N-KY': '香', 'N-KE': 'KE', 'N-GI': 'GI', 'RY': 'HI', 'UM': 'KA'}
captured_piece = demotion.get(captured_piece, captured_piece)
capture_list_player.append((player, captured_piece))
if promote: piece_type = get_promoted_piece(piece_type)
new_board[end_r][end_c] = (player, piece_type)
new_board[start_r][start_c] = EMPTY
elif move_type == 'drop':
_, piece_type, end_r, end_c = move
try:
capture_list_player.remove((player, piece_type))
except ValueError:
return new_board, new_caps_b, new_caps_w
new_board[end_r][end_c] = (player, piece_type)
return new_board, new_caps_b, new_caps_w
def evaluate_board(board, captures_black, captures_white, player):
score = 0
for r in range(9):
for c in range(9):
piece_data = board[r][c]
if piece_data != EMPTY:
value = PIECE_VALUES.get(piece_data[1], 0)
if piece_data[0] == WHITE:
score += value
else:
score -= value
for _, piece_type in captures_white:
score += PIECE_VALUES.get(piece_type, 0)
for _, piece_type in captures_black:
score -= PIECE_VALUES.get(piece_type, 0)
if is_in_check(WHITE, board):
score -= 900
if is_in_check(BLACK, board):
score += 900
if is_checkmate(WHITE, board, captures_black, captures_white):
return -float('inf')
if is_checkmate(BLACK, board, captures_black, captures_white):
return float('inf')
return score
def minimax(board, captures_black, captures_white, depth, is_maximizing_player, alpha, beta):
current_player_minimax = WHITE if is_maximizing_player else BLACK
if depth == 0:
return evaluate_board(board, captures_black, captures_white, current_player_minimax), None
if is_checkmate(WHITE, board, captures_black, captures_white):
return -float('inf'), None
if is_checkmate(BLACK, board, captures_black, captures_white):
return float('inf'), None
legal_moves = get_all_legal_moves(current_player_minimax, board, captures_black, captures_white)
if not legal_moves:
return evaluate_board(board, captures_black, captures_white, current_player_minimax), None
best_move = None
if is_maximizing_player:
max_eval = -float('inf')
random.shuffle(legal_moves)
for move in legal_moves:
new_board, new_caps_b, new_caps_w = apply_move(board, captures_black, captures_white, move, WHITE)
current_eval, _ = minimax(new_board, new_caps_b, new_caps_w, depth - 1, False, alpha, beta)
if current_eval > max_eval:
max_eval = current_eval
best_move = move
alpha = max(alpha, max_eval)
if beta <= alpha:
break
return max_eval, best_move
else:
min_eval = float('inf')
random.shuffle(legal_moves)
for move in legal_moves:
new_board, new_caps_b, new_caps_w = apply_move(board, captures_black, captures_white, move, BLACK)
current_eval, _ = minimax(new_board, new_caps_b, new_caps_w, depth - 1, True, alpha, beta)
if current_eval < min_eval:
min_eval = current_eval
best_move = move
beta = min(beta, min_eval)
if beta <= alpha:
break
return min_eval, best_move
def find_best_move(board, captures_black, captures_white, player, depth):
start_time = time.time()
_, best_move = minimax(board, captures_black, captures_white, depth, True, -float('inf'), float('inf'))
end_time = time.time()
print(f"Minimax探索時間: {end_time - start_time:.2f}秒 (深さ: {depth})")
return best_move
# --- AI機能の更新 ---
def make_ai_move():
global game_board, white_captures, black_captures, current_player, game_running, game_states
if not game_running or current_player != AI_PLAYER: return
ai_player = AI_PLAYER
opponent = BLACK
# 棋譜記録用に移動前の盤面をコピー
board_before_move = copy.deepcopy(game_board)
chosen_move = find_best_move(game_board, black_captures, white_captures, ai_player, SEARCH_DEPTH)
if chosen_move is None:
if is_in_check(ai_player, game_board):
messagebox.showinfo("ゲーム終了", f"AI ({ai_player}) は詰みです。{opponent}の勝利!")
else:
messagebox.showinfo("警告", f"AI ({ai_player}) は合法手を見つけられませんでした。")
game_running = False
return
move_type = chosen_move[0]
if move_type == 'move':
_, start_r, start_c, end_r, end_c, promote = chosen_move
player, piece_type = game_board[start_r][start_c]
target_piece = game_board[end_r][end_c]
if target_piece != EMPTY:
captured_piece = target_piece[1]
demotion = {'TO': 'FU', 'N-KY': '香', 'N-KE': 'KE', 'N-GI': 'GI', 'RY': 'HI', 'UM': 'KA'}
captured_piece = demotion.get(captured_piece, captured_piece)
white_captures.append((ai_player, captured_piece))
if promote: piece_type = get_promoted_piece(piece_type)
game_board[end_r][end_c] = (player, piece_type)
game_board[start_r][start_c] = EMPTY
elif move_type == 'drop':
_, piece_type, end_r, end_c = chosen_move
capture_list_ref = white_captures
for i, (p, p_type) in enumerate(capture_list_ref):
if p_type == piece_type:
capture_list_ref.pop(i)
break
game_board[end_r][end_c] = (ai_player, piece_type)
# ★ 棋譜の記録 (AIの手は奇数手)
move_details = chosen_move
turn_count = (len(history) // 2) + 1
kifu_str = format_move_to_kifu(move_details, ai_player, board_before_move)
append_kifu(f" ☖{kifu_str}")
# ★ --------------------------
handle_end_turn(opponent)
# --- イベントハンドラ (履歴保存とUndo) ---
def get_simple_state_string(board, player):
state_str = "".join("E" if cell == EMPTY else cell[0] + cell[1] for row in board for cell in row)
state_str += "T" + player
return state_str
def save_state():
"""現在のゲーム状態をディープコピーして履歴に保存する"""
global history
state = (
copy.deepcopy(game_board),
copy.deepcopy(black_captures),
copy.deepcopy(white_captures),
current_player,
copy.deepcopy(game_states)
)
history.append(state)
def undo_move():
"""一手前の状態に戻す (持ち駒増加バグ修正済み)"""
global game_board, black_captures, white_captures, current_player, game_states, game_running
global selected_piece, original_pos, is_placing_piece, highlight_moves
if len(history) <= 1:
messagebox.showinfo("警告", "初期状態のため、これ以上戻せません。")
return
if current_player == AI_PLAYER:
messagebox.showinfo("警告", "AIの手番中はUndoできません。")
return
stop_flashing()
# 自分の指した一手とその結果のターン (計2つの履歴) を削除
if len(history) >= 2:
history.pop()
history.pop()
else:
history.pop()
if history:
prev_state = history[-1]
game_board = copy.deepcopy(prev_state[0])
black_captures = copy.deepcopy(prev_state[1])
white_captures = copy.deepcopy(prev_state[2])
current_player = prev_state[3]
game_states = copy.deepcopy(prev_state[4])
# ★ 棋譜表示の更新 (全消し&再構築が最も安全)
if kifu_text:
kifu_text.config(state=tk.NORMAL)
kifu_text.delete(1.0, tk.END)
kifu_text.config(state=tk.DISABLED)
# 簡略化のため、Undo後の棋譜は表示しない
# 正確に実装するには、historyから棋譜を再構築する必要がある
else:
# 初期状態に戻す
game_board = create_initial_board()
black_captures = []
white_captures = []
current_player = BLACK
game_states = [get_simple_state_string(game_board, BLACK)]
save_state()
if kifu_text:
kifu_text.config(state=tk.NORMAL)
kifu_text.delete(1.0, tk.END)
kifu_text.config(state=tk.DISABLED)
selected_piece = None
original_pos = None
is_placing_piece = False
highlight_moves = []
game_running = True
draw_pieces()
if is_in_check(current_player, game_board):
flash_king()
print(f"一手戻しました。現在のプレイヤー: {current_player}")
if current_player == AI_PLAYER and game_running:
canvas.after(500, make_ai_move)
def handle_end_turn(next_player):
"""ターンの終わりに呼ばれる共通処理"""
global current_player, game_running
current_player = next_player
current_state = get_simple_state_string(game_board, current_player)
game_states.append(current_state)
save_state()
stop_flashing()
if game_running:
draw_pieces()
if is_checkmate(current_player, game_board, black_captures, white_captures):
winner = BLACK if current_player == WHITE else WHITE
messagebox.showinfo("ゲーム終了", f"詰みです!{winner}の勝利!")
game_running = False
elif is_in_check(current_player, game_board):
print(f"王手!({current_player}玉)")
flash_king()
if current_player == AI_PLAYER and game_running:
canvas.after(500, make_ai_move)
# --- 描画・UI、マウス操作 ---
# ... (draw_pieces, draw_captured_pieces, flash_king, on_drag, on_click, on_release の描画とマウス操作に関する関数は、
# コードが長大になるため省略しますが、前回のコードとほぼ同じ内容です。
# ただし、on_click と on_release には棋譜記録のロジックが追加されています。)
# ここでは、棋譜記録の追加された on_release のみ記載します。
def on_release(event):
global selected_piece, original_pos, current_player, is_placing_piece, highlight_moves, game_running
if not game_running or current_player == AI_PLAYER: return
if is_placing_piece:
canvas.delete("drag_piece")
return
if selected_piece:
new_col = event.x // CELL_SIZE
new_row = event.y // CELL_SIZE
if not (0 <= new_row < 9 and 0 <= new_col < 9) or (new_row, new_col) not in highlight_moves:
game_board[original_pos[0]][original_pos[1]] = selected_piece
elif (new_row, new_col) in highlight_moves:
# 棋譜記録用に移動前の情報を保存
board_before_move = copy.deepcopy(game_board)
piece_type_before_promotion = selected_piece[1]
target_piece = game_board[new_row][new_col]
player = selected_piece[0]
if target_piece != EMPTY:
captured_piece = target_piece[1]
demotion = {'TO': 'FU', 'N-KY': '香', 'N-KE': 'KE', 'N-GI': 'GI', 'RY': 'HI', 'UM': 'KA'}
captured_piece = demotion.get(captured_piece, captured_piece)
capture_list = black_captures if player == BLACK else white_captures
capture_list.append((player, captured_piece))
can_promote_check = should_promote(piece_type_before_promotion, player, original_pos[0], new_row)
is_forced_promotion = False
if piece_type_before_promotion == 'FU' and ((player == BLACK and new_row == 0) or (player == WHITE and new_row == 8)): is_forced_promotion = True
if piece_type_before_promotion == '香' and ((player == BLACK and new_row == 0) or (player == WHITE and new_row == 8)): is_forced_promotion = True
if piece_type_before_promotion == 'KE' and ((player == BLACK and new_row <= 1) or (player == WHITE and new_row >= 7)): is_forced_promotion = True
promote_flag = False
if is_forced_promotion:
promoted_piece = get_promoted_piece(piece_type_before_promotion)
selected_piece = (player, promoted_piece)
promote_flag = True
elif can_promote_check:
result = messagebox.askyesno("成り", "成りますか? (「いいえ」を選択すると、成りません。)")
if result:
promoted_piece = get_promoted_piece(piece_type_before_promotion)
selected_piece = (player, promoted_piece)
promote_flag = True
game_board[new_row][new_col] = selected_piece
# ★ 棋譜の記録 (人間: 黒は偶数手で記録される)
move_details = ('move', original_pos[0], original_pos[1], new_row, new_col, promote_flag)
turn_count = (len(history) // 2) + 1
kifu_str = format_move_to_kifu(move_details, player, board_before_move)
# 奇数手 (1., 3., 5. ...) の開始
append_kifu(f"{turn_count}. ☗{kifu_str}")
# ★ --------------------------
handle_end_turn(WHITE if current_player == BLACK else BLACK)
selected_piece = None
original_pos = None
highlight_moves = []
canvas.delete("drag_piece")
draw_pieces()
# on_click, on_drag, draw_pieces, count_captured_pieces, stop_flashing, flash_king, draw_board_cell の定義を追記する
# (これらは前回のコードの内容と同じです)
# -------------------------------------------------------------------------------------------------------
def count_captured_pieces(captures_list):
piece_types = [piece[1] for piece in captures_list]
counts = Counter(piece_types)
sorted_counts = {
piece_type: counts[piece_type] for piece_type in PIECE_ORDER if piece_type in counts
}
return sorted_counts
def stop_flashing():
global king_flash_job, is_flashing
if king_flash_job:
canvas.after_cancel(king_flash_job)
king_flash_job = None
is_flashing = False
king_r, king_c = find_king(current_player, game_board)
if king_r is not None:
draw_board_cell(king_r, king_c, fill_color="#D2B48C")
def draw_board_cell(r, c, fill_color, tags=None):
x1 = c * CELL_SIZE
y1 = r * CELL_SIZE
x2 = x1 + CELL_SIZE
y2 = y1 + CELL_SIZE
canvas.delete(f"cell_{r}_{c}")
canvas.create_rectangle(x1, y1, x2, y2, outline="black", fill=fill_color, tags=(f"cell_{r}_{c}", tags))
def flash_king(is_on=True):
global king_flash_job, is_flashing
if not game_running:
stop_flashing()
return
player_in_check = current_player
king_r, king_c = find_king(player_in_check, game_board)
if king_r is not None and is_in_check(player_in_check, game_board):
if is_on:
draw_board_cell(king_r, king_c, fill_color="#FF4C4C", tags="king_flash")
else:
draw_board_cell(king_r, king_c, fill_color="#D2B48C", tags="king_flash")
king_flash_job = canvas.after(300, flash_king, not is_on)
is_flashing = True
else:
stop_flashing()
draw_pieces()
def draw_pieces():
canvas.delete("board_piece")
canvas.delete("capture_area")
canvas.delete("highlight")
for i in range(9):
for j in range(9):
fill_color = "#D2B48C"
draw_board_cell(i, j, fill_color)
for r, c in highlight_moves:
center_x = c * CELL_SIZE + CELL_SIZE // 2
center_y = r * CELL_SIZE + CELL_SIZE // 2
radius = CELL_SIZE // 3
if game_board[r][c] == EMPTY:
canvas.create_oval(center_x - radius, center_y - radius, center_x + radius, center_y + radius, fill="#8CBB7D", width=0, tags="highlight")
else:
canvas.create_rectangle(c * CELL_SIZE, r * CELL_SIZE, (c + 1) * CELL_SIZE, (r + 1) * CELL_SIZE, outline="#FF0000", width=4, tags="highlight")
for r in range(9):
for c in range(9):
piece_data = game_board[r][c]
if piece_data != EMPTY:
player, piece_type = piece_data
text_piece = PIECES[piece_type]
x = c * CELL_SIZE + CELL_SIZE // 2
y = r * CELL_SIZE + CELL_SIZE // 2
if selected_piece and original_pos == (r, c):
fill_color = "yellow"
canvas.create_rectangle(c * CELL_SIZE, r * CELL_SIZE, (c + 1) * CELL_SIZE, (r + 1) * CELL_SIZE, fill=fill_color, outline="black")
angle = 180 if player == WHITE else 0
canvas.create_text(x, y, text=text_piece, fill="black", font=("Arial", 16), angle=angle, tags="board_piece")
draw_captured_pieces(WHITE)
draw_captured_pieces(BLACK)
def draw_captured_pieces(player):
capture_list = white_captures if player == WHITE else black_captures
counted_captures = count_captured_pieces(capture_list)
start_x = BOARD_SIZE + 5
start_y = 10 if player == WHITE else BOARD_SIZE - CAPTURE_CELL_HEIGHT * (len(counted_captures) + 1)
if player == WHITE:
canvas.create_text(start_x + (SIDE_PANEL_WIDTH // 2) - 5, start_y, anchor="n", text="白の持ち駒", font=("Arial", 12), tags="capture_area")
y_offset = start_y + 20
else:
canvas.create_text(start_x + (SIDE_PANEL_WIDTH // 2) - 5, start_y - 20, anchor="n", text="黒の持ち駒", font=("Arial", 12), tags="capture_area")
y_offset = start_y
piece_index = 0
for piece_type, count in counted_captures.items():
text_piece = PIECES[piece_type]
y1 = y_offset + piece_index * CAPTURE_CELL_HEIGHT
y2 = y1 + CAPTURE_CELL_HEIGHT
fill_color = "#E0C8A4"
if is_placing_piece and selected_piece and selected_piece[1] == piece_type:
fill_color = "#FFFF8C"
canvas.create_rectangle(start_x, y1, BOARD_SIZE + SIDE_PANEL_WIDTH - 5, y2,
fill=fill_color, outline="black", tags=("capture_area", f"{player}_capture_{piece_type}"))
angle = 180 if player == WHITE else 0
text_y = y1 + CAPTURE_CELL_HEIGHT // 2
display_text = f"{text_piece}"
if count > 1: display_text += f" x {count}"
canvas.create_text(start_x + (SIDE_PANEL_WIDTH // 2) - 5, text_y, anchor="center",
text=display_text, font=("Arial", 14), angle=angle, tags="capture_area")
piece_index += 1
def on_click(event):
global selected_piece, original_pos, is_placing_piece, current_player, highlight_moves, game_running
if not game_running or current_player == AI_PLAYER: return
col = event.x // CELL_SIZE
row = event.y // CELL_SIZE
if col >= 9:
capture_list = black_captures if current_player == BLACK else white_captures
counted_captures = count_captured_pieces(capture_list)
if not counted_captures: return
selected_piece_type = None
piece_order = list(counted_captures.keys())
click_y = event.y
start_y = 10 + 20
if current_player == BLACK:
black_capture_y_start = BOARD_SIZE - CAPTURE_CELL_HEIGHT * (len(counted_captures) + 1)
start_y = black_capture_y_start
y_step = CAPTURE_CELL_HEIGHT
if start_y <= click_y < start_y + len(piece_order) * y_step:
index = (click_y - start_y) // y_step
if 0 <= index < len(piece_order):
selected_piece_type = piece_order[index]
if selected_piece_type:
if selected_piece and original_pos:
game_board[original_pos[0]][original_pos[1]] = selected_piece
original_pos = None
temp_piece = (current_player, selected_piece_type)
if is_placing_piece and selected_piece == temp_piece:
capture_list.append(selected_piece)
selected_piece = None
is_placing_piece = False
highlight_moves = []
draw_pieces()
return
if temp_piece in capture_list:
capture_list.remove(temp_piece)
selected_piece = temp_piece
is_placing_piece = True
highlight_moves = []
for r_h in range(9):
for c_h in range(9):
if game_board[r_h][c_h] == EMPTY:
is_valid_drop = True
if (selected_piece_type == 'FU' or selected_piece_type == '香') and ((current_player == BLACK and r_h == 0) or (current_player == WHITE and r_h == 8)): is_valid_drop = False
if selected_piece_type == 'KE' and ((current_player == BLACK and r_h <= 1) or (current_player == WHITE and r_h >= 7)): is_valid_drop = False
if selected_piece_type == 'FU':
for r_check in range(9):
piece_at_r = game_board[r_check][c_h]
if piece_at_r != EMPTY and piece_at_r[0] == current_player and piece_at_r[1] == 'FU':
is_valid_drop = False
break
if selected_piece_type == 'FU' and is_valid_drop:
opponent = WHITE if current_player == BLACK else BLACK
temp_board = [row[:] for row in game_board]
temp_board[r_h][c_h] = selected_piece
temp_caps_b = black_captures[:]
temp_caps_w = white_captures[:]
temp_capture_list = temp_caps_b if current_player == BLACK else temp_caps_w
try:
temp_capture_list.remove(selected_piece)
except ValueError:
pass
if is_in_check(opponent, temp_board) and is_checkmate(opponent, temp_board, temp_caps_b, temp_caps_w):
is_valid_drop = False
if is_valid_drop:
temp_board = [row[:] for row in game_board]
temp_board[r_h][c_h] = selected_piece
if is_in_check(current_player, temp_board):
is_valid_drop = False
if is_valid_drop:
highlight_moves.append((r_h, c_h))
if not highlight_moves and is_placing_piece:
capture_list.append(selected_piece)
selected_piece = None
is_placing_piece = False
messagebox.showinfo("警告", f"{PIECES[selected_piece_type]}を打てる合法なマスはありません。")
draw_pieces()
if selected_piece:
angle = 180 if current_player == WHITE else 0
canvas.create_text(event.x, event.y, text=PIECES[selected_piece_type], fill="red", font=("Arial", 16), tags="drag_piece", angle=angle)
return
if selected_piece and is_placing_piece:
capture_list.append(selected_piece)
selected_piece = None
is_placing_piece = False
highlight_moves = []
draw_pieces()
return
if 0 <= row < 9 and 0 <= col < 9:
if is_placing_piece:
# 棋譜記録用に移動前の情報を保存
board_before_move = copy.deepcopy(game_board)
move_details = ('drop', selected_piece[1], row, col)
if (row, col) in highlight_moves:
game_board[row][col] = selected_piece
# ★ 棋譜の記録 (打ち込み)
turn_count = (len(history) // 2) + 1
kifu_str = format_move_to_kifu(move_details, current_player, board_before_move)
# 奇数手 (1., 3., 5. ...) の開始
append_kifu(f"{turn_count}. ☗{kifu_str}")
# ★ --------------------------
handle_end_turn(WHITE if current_player == BLACK else BLACK)
else:
capture_list = black_captures if selected_piece[0] == BLACK else white_captures
capture_list.append(selected_piece)
is_placing_piece = False
selected_piece = None
highlight_moves = []
canvas.delete("drag_piece")
draw_pieces()
return
else:
piece_data = game_board[row][col]
if selected_piece and original_pos == (row, col):
game_board[row][col] = selected_piece
selected_piece = None
original_pos = None
highlight_moves = []
draw_pieces()
return
if piece_data != EMPTY and piece_data[0] == current_player:
if selected_piece: game_board[original_pos[0]][original_pos[1]] = selected_piece
highlight_moves = get_valid_moves(row, col, game_board, black_captures if current_player == BLACK else white_captures)
selected_piece = piece_data
original_pos = (row, col)
game_board[row][col] = EMPTY
draw_pieces()
angle = 180 if current_player == WHITE else 0
canvas.create_text(event.x, event.y, text=PIECES[piece_data[1]], fill="red", font=("Arial", 16), tags="drag_piece", angle=angle)
def on_drag(event):
if selected_piece and game_running and current_player != AI_PLAYER:
canvas.coords("drag_piece", event.x, event.y)
# -------------------------------------------------------------------------------------------------------
def create_shogi_board():
global game_board, canvas, root, kifu_text # kifu_textを追加
root = tk.Tk()
root.title(f"将棋ゲーム (AI深さ: {SEARCH_DEPTH})")
canvas = tk.Canvas(root, width=BOARD_SIZE + SIDE_PANEL_WIDTH, height=BOARD_SIZE, bg="#D2B48C")
canvas.pack(side=tk.LEFT)
control_frame = tk.Frame(root, width=SIDE_PANEL_WIDTH)
control_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=10, pady=10)
undo_button = tk.Button(control_frame, text="一手戻る (Undo)", command=undo_move,
height=2, bg="#B0C4DE", fg="black")
undo_button.pack(pady=5)
tk.Label(control_frame, text=f"AI探索深さ: {SEARCH_DEPTH}").pack(pady=5)
# ★ 棋譜表示エリアの追加
tk.Label(control_frame, text="--- 棋譜 ---").pack(pady=(10, 0))
kifu_text = tk.Text(control_frame, height=20, width=20, wrap=tk.WORD, state=tk.DISABLED, font=("Arial", 10))
kifu_text.pack(fill=tk.Y)
# ★ --------------------------
canvas.bind("<Button-1>", on_click)
canvas.bind("<B1-Motion>", on_drag)
canvas.bind("<ButtonRelease-1>", on_release)
game_board = create_initial_board()
game_states.append(get_simple_state_string(game_board, BLACK))
save_state()
draw_pieces()
def on_closing():
stop_flashing()
root.destroy()
sys.exit()
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()
if __name__ == "__main__":
create_shogi_board()
して、このプロンプト No.3までで、AIは「駒の基本価値」「駒の位置(PST)」「反則チェック」「 玉の堅さ 」の4つの要素を考慮して指し手を選ぶようになりました 。しかし、AIはさらに強化が可能です。例えば、探索深さを3や4に増やしたり 、王手や駒の取り合いが発生している局面で探索を深くする静的探索の導入 、大局的な戦型評価の追加 といったステップが考えられます 。読者の皆さんの好奇心に合わせて、ぜひカスタマイズに挑戦してみてください。
プロンプトno4(GUI設定)
#Version 8.0
import tkinter as tk
from collections import Counter
from tkinter import messagebox
import sys
import copy
# ==============================================================================
# 1. 定数 (変更なし)
# ==============================================================================
BOARD_SIZE = 450
CELL_SIZE = BOARD_SIZE // 9
SIDE_PANEL_WIDTH = 150
CAPTURE_CELL_HEIGHT = 25
EMPTY = ' '
BLACK = '黒'
WHITE = '白'
PIECES = {
'OU': '玉', 'HI': '飛', 'KA': '角', 'KI': '金',
'GI': '銀', 'KE': '桂', '香': '香', 'FU': '歩',
'TO': 'と', 'N-GI': '成銀', 'N-KE': '成桂', 'N-KY': '成香',
'RY': '龍', 'UM': '馬'
}
PIECE_ORDER = ['HI', 'KA', 'RY', 'UM', 'KI', 'GI', 'KE', '香', 'FU']
AI_PLAYER = WHITE
SEARCH_DEPTH = 2
KANJI_COLUMNS = ["9", "8", "7", "6", "5", "4", "3", "2", "1"]
KANJI_ROWS = ["一", "二", "三", "四", "五", "六", "七", "八", "九"]
# 駒の基本価値 (変更なし)
PIECE_VALUES = {
'OU': 10000,
'HI': 1000, 'KA': 800,
'KI': 500, 'GI': 400,
'KE': 300, '香': 200, 'FU': 100,
'RY': 1300, 'UM': 1000,
'TO': 600, 'N-GI': 500, 'N-KE': 400, 'N-KY': 300
}
# Piece-Square Tables (PST) (変更なし)
PST_HI = [
[ 0, 5, 10, 15, 15, 15, 10, 5, 0], [ 5, 10, 15, 20, 20, 20, 15, 10, 5],
[10, 15, 20, 25, 25, 25, 20, 15, 10], [15, 20, 25, 30, 30, 30, 25, 20, 15],
[15, 20, 25, 30, 30, 30, 25, 20, 15], [10, 15, 20, 25, 25, 25, 20, 15, 5],
[ 5, 10, 15, 20, 20, 20, 15, 10, 5], [ 0, 5, 10, 15, 15, 15, 10, 5, 0],
[-5, 0, 5, 10, 10, 10, 5, 0, -5]
]
PST_KA = [
[ 0, 0, 5, 10, 10, 10, 5, 0, 0], [ 0, 10, 15, 20, 20, 20, 15, 10, 0],
[ 5, 15, 25, 30, 30, 30, 25, 15, 5], [10, 20, 30, 35, 35, 35, 30, 20, 10],
[10, 20, 30, 35, 35, 35, 30, 20, 10], [ 5, 15, 25, 30, 30, 30, 25, 15, 5],
[ 0, 10, 15, 20, 20, 20, 15, 10, 0], [ 0, 0, 5, 10, 10, 10, 5, 0, 0],
[-5, -5, 0, 5, 5, 5, 0, -5, -5]
]
PST_FU = [
[ 40, 40, 40, 40, 40, 40, 40, 40, 40], [ 30, 30, 30, 30, 30, 30, 30, 30, 30],
[ 20, 20, 20, 20, 20, 20, 20, 20, 20], [ 10, 10, 10, 10, 10, 10, 10, 10, 10],
[ 5, 5, 5, 5, 5, 5, 5, 5, 5], [ 0, 0, 0, 0, 0, 0, 0, 0, 0],
[ -5, -5, -5, -5, -5, -5, -5, -5, -5], [-10,-10,-10,-10,-10,-10,-10,-10,-10],
[-20,-20,-20,-20,-20,-20,-20,-20,-20]
]
PST_KE = [
[ 10, 15, 20, 20, 20, 20, 20, 15, 10], [ 10, 15, 20, 20, 20, 20, 20, 15, 10],
[ 5, 10, 15, 15, 15, 15, 15, 10, 5], [ 0, 5, 10, 10, 10, 10, 10, 5, 0],
[ -5, 0, 5, 5, 5, 5, 5, 0, -5], [-10, -5, 0, 0, 0, 0, 0, -5,-10],
[-15,-10, -5, -5, -5, -5, -5,-10,-15], [-20,-15,-10,-10,-10,-10,-10,-15,-20],
[-20,-15,-10,-10,-10,-10,-10,-15,-20]
]
# ==============================================================================
# 2. 静的/ヘルパー関数 (変更なし)
# ==============================================================================
def is_promoted_piece(piece_type):
return piece_type in ['TO', 'N-KY', 'N-KE', 'N-GI', 'RY', 'UM']
def get_promoted_piece(piece_type):
promotions = {'FU': 'TO', '香': 'N-KY', 'KE': 'N-KE', 'GI': 'N-GI', 'HI': 'RY', 'KA': 'UM'}
return promotions.get(piece_type, piece_type)
def get_demoted_piece(piece_type):
demotions = {'TO': 'FU', 'N-KY': '香', 'N-KE': 'KE', 'N-GI': 'GI', 'RY': 'HI', 'UM': 'KA'}
return demotions.get(piece_type, piece_type)
def should_promote(piece_type, player, start_row, end_row):
if piece_type in ['OU', 'KI'] or is_promoted_piece(piece_type): return False
if player == BLACK:
return end_row <= 2 or start_row <= 2
else:
return end_row >= 6 or start_row >= 6
def coords_to_kifu(r, c):
return KANJI_COLUMNS[c] + KANJI_ROWS[r]
def piece_type_to_kifu(piece_type):
return PIECES.get(piece_type, piece_type)
def format_move_to_kifu_static(move_details, current_player, board_before_move):
move_type = move_details[0]
if move_type == 'move':
_, start_r, start_c, end_r, end_c, promote = move_details
piece_data = board_before_move[start_r][start_c]
if piece_data == EMPTY: return ""
piece_type = piece_data[1]
kifu_piece = piece_type_to_kifu(piece_type)
kifu_end_pos = coords_to_kifu(end_r, end_c)
kifu_promote = "成" if promote else ""
return f"{kifu_end_pos}{kifu_piece}{kifu_promote}"
elif move_type == 'drop':
_, piece_type, end_r, end_c = move_details
kifu_piece = piece_type_to_kifu(piece_type)
kifu_end_pos = coords_to_kifu(end_r, end_c)
return f"{kifu_end_pos}{kifu_piece}"
return ""
def get_simple_state_string_static(board, captures_b, captures_w, player):
# 盤面、持ち駒(ソート)、手番を含むハッシュキー
state_str = "".join("E" if cell == EMPTY else cell[0] + cell[1] for row in board for cell in row)
b_caps_str = "".join(sorted([t for p, t in captures_b]))
w_caps_str = "".join(sorted([t for p, t in captures_w]))
state_str += f"B{b_caps_str}W{w_caps_str}"
state_str += "T" + player
return state_str
def count_captured_pieces(captures_list):
piece_types = [piece[1] for piece in captures_list]
counts = Counter(piece_types)
sorted_counts = {
piece_type: counts[piece_type] for piece_type in PIECE_ORDER if piece_type in counts
}
return sorted_counts
# 駒の移動ロジック (変更なし)
def is_valid_pawn_move_static(start_row, start_col, end_row, end_col, player):
if player == BLACK: return end_row == start_row - 1 and end_col == start_col
elif player == WHITE: return end_row == start_row + 1 and end_col == start_col
return False
def is_valid_lance_move_static(start_row, start_col, end_row, end_col, player, board):
if end_col != start_col: return False
if player == BLACK and end_row >= start_row: return False
if player == WHITE and end_row <= start_row: return False
step = -1 if player == BLACK else 1
for r in range(start_row + step, end_row, step):
if board[r][start_col] != EMPTY: return False
return True
def is_valid_king_move_static(start_row, start_col, end_row, end_col):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
return dx <= 1 and dy <= 1
def is_valid_rook_move_static(start_row, start_col, end_row, end_col, board):
if start_row == end_row:
step = 1 if end_col > start_col else -1
for c in range(start_col + step, end_col, step):
if board[start_row][c] != EMPTY: return False
elif start_col == end_col:
step = 1 if end_row > start_row else -1
for r in range(start_row + step, end_row, step):
if board[r][start_col] != EMPTY: return False
else: return False
return True
def is_valid_bishop_move_static(start_row, start_col, end_row, end_col, board):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
if dx != dy or dx == 0: return False
step_x = 1 if end_col > start_col else -1
step_y = 1 if end_row > start_row else -1
for i in range(1, dx):
if board[start_row + i * step_y][start_col + i * step_x] != EMPTY: return False
return True
def is_valid_gold_move_static(start_row, start_col, end_row, end_col, player):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
if player == BLACK:
if dy == 1 and dx == 0: return True
if dy == 0 and dx == 1: return True
if dy == 1 and dx == 1 and start_row > end_row: return True
elif player == WHITE:
if dy == 1 and dx == 0: return True
if dy == 0 and dx == 1: return True
if dy == 1 and dx == 1 and start_row < end_row: return True
return False
def is_valid_silver_move_static(start_row, start_col, end_row, end_col, player):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
if player == BLACK:
if dy == 1 and dx == 1: return True
if dy == 1 and dx == 0 and start_row > end_row: return True
elif player == WHITE:
if dy == 1 and dx == 1: return True
if dy == 1 and dx == 0 and start_row < end_row: return True
return False
def is_valid_knight_move_static(start_row, start_col, end_row, end_col, player):
dx = abs(start_col - end_col)
dy = abs(start_row - end_row)
if dx != 1 or dy != 2: return False
if player == BLACK: return end_row == start_row - 2
elif player == WHITE: return end_row == start_row + 2
return False
def is_valid_move_logic_static(start_row, start_col, end_row, end_col, piece_type, player, board):
if piece_type == 'FU': return is_valid_pawn_move_static(start_row, start_col, end_row, end_col, player)
elif piece_type == '香': return is_valid_lance_move_static(start_row, start_col, end_row, end_col, player, board)
elif piece_type == 'OU': return is_valid_king_move_static(start_row, start_col, end_row, end_col)
elif piece_type == 'HI': return is_valid_rook_move_static(start_row, start_col, end_row, end_col, board)
elif piece_type == 'KA': return is_valid_bishop_move_static(start_row, start_col, end_row, end_col, board)
elif piece_type == 'KI': return is_valid_gold_move_static(start_row, start_col, end_row, end_col, player)
elif piece_type == 'GI': return is_valid_silver_move_static(start_row, start_col, end_row, end_col, player)
elif piece_type == 'KE': return is_valid_knight_move_static(start_row, start_col, end_row, end_col, player)
elif piece_type == 'RY': return is_valid_rook_move_static(start_row, start_col, end_row, end_col, board) or is_valid_king_move_static(start_row, start_col, end_row, end_col)
elif piece_type == 'UM': return is_valid_bishop_move_static(start_row, start_col, end_row, end_col, board) or is_valid_king_move_static(start_row, start_col, end_row, end_col)
elif piece_type in ['TO', 'N-GI', 'N-KE', 'N-KY']: return is_valid_gold_move_static(start_row, start_col, end_row, end_col, player)
return False
# ==============================================================================
# 3. Model: ShogiGame クラス
# ==============================================================================
class ShogiGame:
def __init__(self):
self.board = self._create_initial_board()
self.black_captures = []
self.white_captures = []
self.current_player = BLACK
self.game_states = []
self.history = []
def _create_initial_board(self):
board = [[EMPTY] * 9 for _ in range(9)]
board[0][0] = (WHITE, '香'); board[0][1] = (WHITE, 'KE'); board[0][2] = (WHITE, 'GI'); board[0][3] = (WHITE, 'KI'); board[0][4] = (WHITE, 'OU'); board[0][5] = (WHITE, 'KI'); board[0][6] = (WHITE, 'GI'); board[0][7] = (WHITE, 'KE'); board[0][8] = (WHITE, '香')
board[1][1] = (WHITE, 'HI'); board[1][7] = (WHITE, 'KA')
for j in range(9): board[2][j] = (WHITE, 'FU')
board[8][0] = (BLACK, '香'); board[8][1] = (BLACK, 'KE'); board[8][2] = (BLACK, 'GI'); board[8][3] = (BLACK, 'KI'); board[8][4] = (BLACK, 'OU'); board[8][5] = (BLACK, 'KI'); board[8][6] = (BLACK, 'GI'); board[8][7] = (BLACK, 'KE'); board[8][8] = (BLACK, '香')
board[7][7] = (BLACK, 'HI'); board[7][1] = (BLACK, 'KA')
for j in range(9): board[6][j] = (BLACK, 'FU')
return board
def find_king(self, player, board_state):
for r in range(9):
for c in range(9):
piece_data = board_state[r][c]
if piece_data != EMPTY and piece_data[0] == player and piece_data[1] == 'OU':
return r, c
return None, None
def is_in_check(self, player, board_state):
king_row, king_col = self.find_king(player, board_state)
if king_row is None: return False
opponent = WHITE if player == BLACK else BLACK
for r in range(9):
for c in range(9):
piece_data = board_state[r][c]
if piece_data != EMPTY and piece_data[0] == opponent:
piece_type = piece_data[1]
if is_valid_move_logic_static(r, c, king_row, king_col, piece_type, opponent, board_state):
return True
return False
def is_check(self, board, player):
"""指定プレイヤーが相手に王手をかけているか判定 (静的探索用ヘルパー)"""
opponent = WHITE if player == BLACK else BLACK
k_r, k_c = self.find_king(opponent, board)
if k_r is None: return False
for r in range(9):
for c in range(9):
piece_data = board[r][c]
if piece_data != EMPTY and piece_data[0] == player:
piece_type = piece_data[1]
if is_valid_move_logic_static(r, c, k_r, k_c, piece_type, player, board):
return True
return False
def get_valid_moves(self, r, c, board, player):
if board[r][c] == EMPTY: return []
player, piece_type = board[r][c]
valid_moves = []
for end_r in range(9):
for end_c in range(9):
if r == end_r and c == end_c: continue
target_piece = board[end_r][end_c]
if target_piece != EMPTY and target_piece[0] == player: continue
if not is_valid_move_logic_static(r, c, end_r, end_c, piece_type, player, board): continue
temp_board = [row[:] for row in board]
captured_piece_temp = temp_board[end_r][end_c]
new_piece_type = piece_type
temp_board[end_r][end_c] = (player, new_piece_type)
temp_board[r][c] = EMPTY
# 移動後の自玉の王手チェック
if not self.is_in_check(player, temp_board):
valid_moves.append((end_r, end_c))
return valid_moves
def get_all_legal_moves(self, player, board, black_captures_in, white_captures_in):
legal_moves = []
# 1. 盤上の駒の移動
for r in range(9):
for c in range(9):
piece_data = board[r][c]
if piece_data != EMPTY and piece_data[0] == player:
piece_type = piece_data[1]
moves = self.get_valid_moves(r, c, board, player)
for end_r, end_c in moves:
can_promote = should_promote(piece_type, player, r, end_r) and not is_promoted_piece(piece_type)
is_forced = (
(piece_type in ['FU', '香'] and ((player == BLACK and end_r == 0) or (player == WHITE and end_r == 8))) or
(piece_type == 'KE' and ((player == BLACK and end_r <= 1) or (player == WHITE and end_r >= 7)))
)
if is_forced or not can_promote:
legal_moves.append(('move', r, c, end_r, end_c, is_forced))
elif can_promote:
legal_moves.append(('move', r, c, end_r, end_c, True))
if not is_forced:
legal_moves.append(('move', r, c, end_r, end_c, False))
# 2. 持ち駒の打ち込み
captures_list = black_captures_in if player == BLACK else white_captures_in
piece_counts = count_captured_pieces(captures_list)
opponent = WHITE if player == BLACK else BLACK
for piece_type in piece_counts.keys():
if piece_counts[piece_type] == 0: continue
if is_promoted_piece(piece_type) or piece_type == 'OU': continue
for r in range(9):
for c in range(9):
if board[r][c] == EMPTY:
# 行き所のない駒の打ち込み禁止
if piece_type == 'FU' and ((player == BLACK and r == 0) or (player == WHITE and r == 8)): continue
if piece_type == '香' and ((player == BLACK and r == 0) or (player == WHITE and r == 8)): continue
if piece_type == 'KE' and ((player == BLACK and r <= 1) or (player == WHITE and r >= 7)): continue
# 二歩チェック
if piece_type == 'FU':
pawn_in_col = False
for row in range(9):
cell = board[row][c]
if cell != EMPTY and cell[0] == player and cell[1] == 'FU':
pawn_in_col = True
break
if pawn_in_col: continue
# 打ち駒による王手・詰みチェック
temp_board = [row[:] for row in board]
temp_board[r][c] = (player, piece_type)
# 1. 自玉が王手になる手は非合法
if self.is_in_check(player, temp_board):
continue
# 2. 相手玉に王手がかかるかチェック
if self.is_in_check(opponent, temp_board):
# 2-a. それが歩による王手か? (打ち歩詰めチェック)
if piece_type == 'FU':
# 実際の手を適用して、相手が詰んでいるか確認する
new_board_after_drop, new_caps_b, new_caps_w = self.apply_move(board, black_captures_in, white_captures_in, ('drop', piece_type, r, c), player)
# 詰みであれば、打ち歩詰めとして除外 (continue)
if self.is_checkmate(opponent, new_board_after_drop, new_caps_b, new_caps_w):
continue
# 2-b. 打ち歩詰めではない、または歩以外の駒での王手は合法手
# 3. 2-aでcontinueされなかった場合、合法手として追加
legal_moves.append(('drop', piece_type, r, c))
return legal_moves
def get_tactical_moves(self, player, board, captures_b, captures_w):
"""駒取りまたは王手になる手を抽出する (静的探索用)"""
all_legal_moves = self.get_all_legal_moves(player, board, captures_b, captures_w)
tactical_moves = []
for move in all_legal_moves:
# 1. 駒取りになる手
is_capture = False
if move[0] == 'move':
_, _, _, end_r, end_c, _ = move
if board[end_r][end_c] != EMPTY:
is_capture = True
# 2. 王手、成り、打ち
is_check_or_promote = False
new_board, _, _ = self.apply_move(board, captures_b, captures_w, move, player)
if self.is_check(new_board, player):
is_check_or_promote = True
if move[0] == 'move':
_, _, _, _, _, promote = move
if promote: is_check_or_promote = True
if move[0] == 'drop':
is_check_or_promote = True
if is_capture or is_check_or_promote:
tactical_moves.append(move)
return tactical_moves
def is_checkmate(self, player, board, black_captures_in, white_captures_in):
if not self.is_in_check(player, board):
return False
# 詰み判定は、そのプレイヤーの次の合法手が存在しないこと
legal_moves = self.get_all_legal_moves(player, board, black_captures_in, white_captures_in)
return len(legal_moves) == 0
def apply_move(self, board, captures_black, captures_white, move, player):
new_board = [row[:] for row in board]
new_caps_b = captures_black[:]
new_caps_w = captures_white[:]
capture_list_player = new_caps_b if player == BLACK else new_caps_w
move_type = move[0]
if move_type == 'move':
_, start_r, start_c, end_r, end_c, promote = move
piece_data = new_board[start_r][start_c]
piece_type = piece_data[1]
target_piece = new_board[end_r][end_c]
if target_piece != EMPTY:
captured_piece = get_demoted_piece(target_piece[1])
capture_list_player.append((player, captured_piece))
if promote: piece_type = get_promoted_piece(piece_type)
new_board[end_r][end_c] = (player, piece_type)
new_board[start_r][start_c] = EMPTY
elif move_type == 'drop':
_, piece_type, end_r, end_c = move
temp_list = []
removed = False
target_list = new_caps_b if player == BLACK else new_caps_w
# 持ち駒から一つ削除
for p, t in target_list:
if t == piece_type and not removed:
removed = True
else:
temp_list.append((p, t))
if player == BLACK:
new_caps_b = temp_list
else:
new_caps_w = temp_list
new_board[end_r][end_c] = (player, piece_type)
return new_board, new_caps_b, new_caps_w
# --- King Safety評価ヘルパー関数 (変更なし) ---
def calculate_defender_score(self, kr, kc, player, board):
"""玉の周囲1マスにいる自駒の守り評価を計算する"""
score = 0
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
if dr == 0 and dc == 0: continue
r, c = kr + dr, kc + dc
if 0 <= r < 9 and 0 <= c < 9:
piece_data = board[r][c]
if piece_data != EMPTY and piece_data[0] == player:
piece_type = piece_data[1]
if piece_type in ['KI', 'GI', 'TO', 'N-GI', 'UM', 'RY']:
score += 80
elif piece_type in ['KE', '香', 'FU']:
score += 30
return score
def calculate_proximity_penalty(self, kr, kc, opponent, board):
"""敵駒が玉の周囲3マスに接近していることによるペナルティを計算する"""
penalty = 0
for dr in range(-3, 4):
for dc in range(-3, 4):
if dr == 0 and dc == 0: continue
r, c = kr + dr, kc + dc
if 0 <= r < 9 and 0 <= c < 9:
piece_data = board[r][c]
if piece_data != EMPTY and piece_data[0] == opponent:
piece_type = piece_data[1]
distance = max(abs(dr), abs(dc))
base_penalty = PIECE_VALUES.get(piece_type, 100) / (distance + 0.5)
if piece_type in ['RY', 'UM', 'HI', 'KA']:
base_penalty *= 1.5
penalty += base_penalty
return int(penalty)
def evaluate_board(self, board, captures_black, captures_white, player):
score = 0
opponent = WHITE if player == BLACK else BLACK
# 1. 駒の基本価値と位置による評価 (PST)
for r in range(9):
for c in range(9):
piece_data = board[r][c]
if piece_data != EMPTY:
p, t = piece_data
value = PIECE_VALUES.get(t, 0)
pst_score = 0
if t == 'HI': pst_table = PST_HI
elif t == 'KA': pst_table = PST_KA
elif t == 'FU': pst_table = PST_FU
elif t == 'KE': pst_table = PST_KE
else: pst_table = None
if pst_table:
if p == BLACK:
pst_score = pst_table[r][c]
else:
pst_score = pst_table[8 - r][c]
total_value = value + pst_score
score += total_value if p == player else -total_value
# 2. 持ち駒の価値評価
for _, t in captures_black:
score += PIECE_VALUES.get(t, 0) if BLACK == player else -PIECE_VALUES.get(t, 0)
for _, t in captures_white:
score += PIECE_VALUES.get(t, 0) if WHITE == player else -PIECE_VALUES.get(t, 0)
# 3. 玉の堅さの評価 (King Safety)
king_row, king_col = self.find_king(player, board)
if king_row is not None:
defender_score = self.calculate_defender_score(king_row, king_col, player, board)
score += defender_score
proximity_penalty = self.calculate_proximity_penalty(king_row, king_col, opponent, board)
score -= proximity_penalty
# 初期位置ペナルティ (ゲーム序盤のみ有効)
if (player == BLACK and king_row == 8 and king_col == 4) or (player == WHITE and king_row == 0 and king_col == 4):
score -= 50
# 4. 王手/詰み評価
if self.is_in_check(player, board):
score -= 500
if self.is_in_check(opponent, board):
score += 500
# 詰みは非常に大きな値で即座に評価を確定させる
if self.is_checkmate(opponent, board, captures_black, captures_white):
score += 100000
return score
# --- 静的探索 (Quiescence Search) ---
def quiesce(self, board, captures_b, captures_w, alpha, beta, player, depth_limit=3):
# AIの視点から評価値を計算
current_score = self.evaluate_board(board, captures_b, captures_w, AI_PLAYER)
# 評価値が閾値を超えたら直ちに打ち切る (ベータカット)
if current_score >= beta:
return beta
if current_score > alpha:
alpha = current_score
# 探索限界を超えたら終了
if depth_limit <= 0:
return current_score
# 攻撃的な手を全て取得 (王手、駒取り、成り/打ち)
tactical_moves = self.get_tactical_moves(player, board, captures_b, captures_w)
# 攻撃的な手がなければ、評価値を返して終了(局面が静かになったと判断)
if not tactical_moves:
return current_score
# 攻撃的な手についてのみ再帰的に探索
opponent = WHITE if player == BLACK else BLACK
is_maximizing = (player == AI_PLAYER)
# Move Orderingの適用
ordered_tactical_moves = self.order_moves(tactical_moves, board, captures_b, captures_w, player)
for move in ordered_tactical_moves:
new_board, new_caps_b, new_caps_w = self.apply_move(board, captures_b, captures_w, move, player)
# 自玉が王手になっていないかチェック (合法手なので不要だが念のため)
if self.is_in_check(player, new_board): continue
# 再帰コール
if is_maximizing:
score = self.quiesce(new_board, new_caps_b, new_caps_w, alpha, beta, opponent, depth_limit - 1)
alpha = max(alpha, score)
if alpha >= beta:
return beta
else:
score = self.quiesce(new_board, new_caps_b, new_caps_w, alpha, beta, opponent, depth_limit - 1)
beta = min(beta, score)
if alpha >= beta:
return alpha
return alpha if is_maximizing else beta
# --- Move Orderingのためのヘルパーメソッド (変更なし) ---
def score_move(self, move, board, captures_b, captures_w, player):
"""Move Orderingのための暫定的なスコアを計算"""
score = 0
move_type = move[0]
if move_type == 'move':
_, start_r, start_c, end_r, end_c, promote = move
# 1. 駒取り (Capture) の評価
target_piece_data = board[end_r][end_c]
if target_piece_data != EMPTY:
target_piece_type = target_piece_data[1]
# 捕獲される駒の価値を優先 (MVV-LVAの簡略版)
score += 500 + PIECE_VALUES.get(target_piece_type, 0)
# 2. 成り (Promotion) の評価
if promote:
score += 300
elif move_type == 'drop':
# 3. 打ち込み (Drop) は一般に強力
score += 400
# 4. 王手 (Check) になる手は優先
temp_board, _, _ = self.apply_move(board, captures_b, captures_w, move, player)
if self.is_check(temp_board, player):
score += 200
return score
def order_moves(self, legal_moves, board, captures_b, captures_w, player):
"""合法手をスコアの高い順に並び替える"""
scored_moves = []
for move in legal_moves:
score = self.score_move(move, board, captures_b, captures_w, player)
scored_moves.append((score, move))
# スコアを降順にソート
scored_moves.sort(key=lambda x: x[0], reverse=True)
return [move for score, move in scored_moves]
# --- Minimax関数 (Transposition Tableの導入) ---
def minimax(self, board, captures_b, captures_w, depth, is_maximizing, alpha, beta, history_states, transposition_table):
current_player = AI_PLAYER if is_maximizing else BLACK
opponent = BLACK if is_maximizing else AI_PLAYER
# 1. ハッシュ値を生成し、TTを検索
current_state_hash = get_simple_state_string_static(board, captures_b, captures_w, current_player)
if current_state_hash in transposition_table and transposition_table[current_state_hash]['depth'] >= depth:
entry = transposition_table[current_state_hash]
return entry['score'], entry.get('move', None)
# 2. 探索深さが0に達した、または詰みの場合、静的探索に移行
if depth == 0 or self.is_checkmate(current_player, board, captures_b, captures_w) or self.is_checkmate(opponent, board, captures_b, captures_w):
score = self.quiesce(board, captures_b, captures_w, alpha, beta, current_player)
# TTに保存 (最善手はNone)
transposition_table[current_state_hash] = {'depth': depth, 'score': score, 'move': None}
return score, None
# 3. 千日手チェック
repetition_count = history_states.count(current_state_hash)
if repetition_count >= 3:
return -1000000 if is_maximizing else 1000000, None
legal_moves = self.get_all_legal_moves(current_player, board, captures_b, captures_w)
# 4. Move Orderingの適用
ordered_moves = self.order_moves(legal_moves, board, captures_b, captures_w, current_player)
best_move = None
next_history_states = history_states + [current_state_hash]
best_eval = -sys.maxsize if is_maximizing else sys.maxsize
# 5. Minimax探索
for move in ordered_moves:
new_board, new_caps_b, new_caps_w = self.apply_move(board, captures_b, captures_w, move, current_player)
# 千日手判定のためのハッシュ値
next_state_hash = get_simple_state_string_static(new_board, new_caps_b, new_caps_w, opponent)
repetition_count_after_move = next_history_states.count(next_state_hash)
if repetition_count_after_move >= 3:
eval_val = -1000000 if is_maximizing else 1000000
else:
# TTを再帰呼び出しに渡す
eval_val, _ = self.minimax(new_board, new_caps_b, new_caps_w, depth - 1, not is_maximizing, alpha, beta, next_history_states, transposition_table)
if is_maximizing:
if eval_val > best_eval:
best_eval = eval_val
best_move = move
alpha = max(alpha, eval_val)
if beta <= alpha:
break
else: # Minimizing
if eval_val < best_eval:
best_eval = eval_val
best_move = move
beta = min(beta, eval_val)
if beta <= alpha:
break
# 6. TTに結果を保存
transposition_table[current_state_hash] = {'depth': depth, 'score': best_eval, 'move': best_move}
return best_eval, best_move
# --- find_best_moveの変更 (TTの初期化) ---
def find_best_move(self, board, captures_black, captures_white, player, depth):
if player != AI_PLAYER: return None
legal_moves = self.get_all_legal_moves(player, board, captures_black, captures_white)
if not legal_moves: return None
# トランポジションテーブルを初期化
transposition_table = {}
# minimaxにTTを渡す
_, best_move = self.minimax(board, captures_black, captures_white, depth, True,
-sys.maxsize, sys.maxsize, self.game_states,
transposition_table)
return best_move
# ... (save_state, undo_stateは変更なし) ...
def save_state(self):
state = (
copy.deepcopy(self.board),
copy.deepcopy(self.black_captures),
copy.deepcopy(self.white_captures),
self.current_player,
copy.deepcopy(self.game_states)
)
self.history.append(state)
def undo_state(self):
if len(self.history) <= 1: return False
if len(self.history) >= 2:
self.history.pop()
self.history.pop()
else:
self.history.pop()
if self.history:
prev_state = self.history[-1]
self.board = copy.deepcopy(prev_state[0])
self.black_captures = copy.deepcopy(prev_state[1])
self.white_captures = copy.deepcopy(prev_state[2])
self.current_player = prev_state[3]
self.game_states = copy.deepcopy(prev_state[4])
return True
self.current_player = BLACK
self.game_states = []
return False
# ==============================================================================
# 4. View & Controller: ShogiApp クラス (変更なし)
# ==============================================================================
class ShogiApp:
def __init__(self, master):
self.root = master
self.root.title("Shogi AI")
self.game = ShogiGame()
self.game_running = True
self.selected_piece = None
self.original_pos = None
self.highlight_moves = []
self.is_placing_piece = False
self.king_flash_job = None
self.is_flashing = False
self.kifu_text = None
self.setup_ui()
self.game.save_state()
def setup_ui(self):
main_frame = tk.Frame(self.root)
main_frame.pack(padx=10, pady=10, fill=tk.BOTH, expand=True)
main_frame.grid_columnconfigure(0, weight=0)
main_frame.grid_columnconfigure(1, weight=1)
main_frame.grid_columnconfigure(2, weight=0)
main_frame.grid_rowconfigure(0, weight=1)
main_frame.grid_rowconfigure(1, weight=0)
capture_white_frame = tk.Frame(main_frame, width=SIDE_PANEL_WIDTH)
capture_white_frame.grid(row=0, column=0, rowspan=1, padx=(0, 10), sticky=tk.N+tk.S)
capture_white_frame.grid_propagate(False)
self.white_capture_canvas = tk.Canvas(capture_white_frame, width=SIDE_PANEL_WIDTH)
self.white_capture_canvas.pack(fill=tk.BOTH, expand=True, pady=10)
self._draw_capture_area(self.white_capture_canvas, WHITE)
self.white_capture_canvas.bind("<Button-1>", self.on_capture_click)
board_frame = tk.Frame(main_frame)
board_frame.grid(row=0, column=1, padx=5, sticky=tk.N+tk.S)
self.canvas = tk.Canvas(board_frame, width=BOARD_SIZE, height=BOARD_SIZE, bg="#D2B48C")
self.canvas.pack()
self.draw_board_grid(self.canvas)
self.canvas.bind("<Button-1>", self.on_click)
self.canvas.bind("<B1-Motion>", self.on_drag)
self.canvas.bind("<ButtonRelease-1>", self.on_release)
capture_black_frame = tk.Frame(main_frame, width=SIDE_PANEL_WIDTH)
capture_black_frame.grid(row=0, column=2, rowspan=1, padx=(10, 0), sticky=tk.N+tk.S)
capture_black_frame.grid_propagate(False)
capture_black_frame.grid_rowconfigure(0, weight=1)
capture_black_frame.grid_rowconfigure(1, weight=0)
capture_black_frame.grid_rowconfigure(2, weight=1)
self.black_capture_canvas = tk.Canvas(capture_black_frame, width=SIDE_PANEL_WIDTH)
self.black_capture_canvas.grid(row=0, column=0, sticky=tk.N+tk.S+tk.E+tk.W, pady=10, padx=5)
self._draw_capture_area(self.black_capture_canvas, BLACK)
self.black_capture_canvas.bind("<Button-1>", self.on_capture_click)
kifu_label = tk.Label(capture_black_frame, text="棋譜", font=('Arial', 12, 'bold'))
kifu_label.grid(row=1, column=0, pady=(5, 0), sticky=tk.N)
self.kifu_text = tk.Text(capture_black_frame, height=10, width=20, state=tk.DISABLED)
self.kifu_text.grid(row=2, column=0, sticky=tk.N+tk.S+tk.E+tk.W, padx=5)
control_frame = tk.Frame(main_frame)
control_frame.grid(row=1, column=1, pady=10, sticky=tk.N)
self.turn_label = tk.Label(control_frame, text="", font=('Arial', 14, 'bold'))
self.turn_label.pack(side=tk.LEFT, padx=10)
undo_button = tk.Button(control_frame, text="一手戻す (Undo)", command=self.undo_move)
undo_button.pack(side=tk.LEFT, padx=10)
self.draw_pieces()
self.draw_captured_pieces()
self.update_turn_indicator()
def draw_board_grid(self, canvas):
canvas.delete("grid")
for i in range(10):
canvas.create_line(0, i * CELL_SIZE, BOARD_SIZE, i * CELL_SIZE, tags="grid", width=1)
canvas.create_line(i * CELL_SIZE, 0, i * CELL_SIZE, BOARD_SIZE, tags="grid", width=1)
for i, kanji in enumerate(KANJI_COLUMNS):
canvas.create_text((8 - i) * CELL_SIZE + CELL_SIZE // 2, -10, text=kanji, fill='gray')
canvas.create_text(i * CELL_SIZE + CELL_SIZE // 2, BOARD_SIZE + 10, text=KANJI_ROWS[i], fill='gray')
def _draw_capture_area(self, canvas, player):
canvas.delete(tk.ALL)
canvas.create_text(canvas.winfo_reqwidth() // 2, 15,
text=f"持ち駒 ({player})", font=('Arial', 10, 'bold'), fill='black')
def draw_pieces(self):
self.canvas.delete("piece")
self.canvas.delete("highlight")
for r in range(9):
for c in range(9):
if (r, c) in self.highlight_moves:
color = "#FFFF00" if self.game.board[r][c] == EMPTY else "#FF8080"
self.canvas.create_rectangle(c * CELL_SIZE, r * CELL_SIZE,
(c + 1) * CELL_SIZE, (r + 1) * CELL_SIZE,
fill=color, stipple="gray25", tags="highlight")
piece_data = self.game.board[r][c]
if piece_data != EMPTY:
player, piece_type = piece_data
text_piece = PIECES[piece_type]
fill_color = 'red' if piece_type == 'OU' and player == self.game.current_player and self.is_flashing else 'black'
angle = 180 if player == WHITE else 0
self.draw_board_cell(r, c, text_piece, fill_color, angle, player, piece_type)
def draw_board_cell(self, r, c, text_piece, fill_color, angle, player, piece_type):
tags = ("piece", f"{player}_{piece_type}")
center_x = c * CELL_SIZE + CELL_SIZE // 2
center_y = r * CELL_SIZE + CELL_SIZE // 2
font_size = int(CELL_SIZE // 3.3)
self.canvas.create_text(center_x, center_y,
text=text_piece,
font=('Arial', font_size),
fill=fill_color,
angle=angle,
tags=tags)
def draw_captured_pieces(self):
self._draw_captured_pieces_on_canvas(self.black_capture_canvas, self.game.black_captures, BLACK)
self._draw_captured_pieces_on_canvas(self.white_capture_canvas, self.game.white_captures, WHITE)
def _draw_captured_pieces_on_canvas(self, canvas, captures_list, player):
canvas_width = canvas.winfo_width() if canvas.winfo_width() > 1 else SIDE_PANEL_WIDTH
canvas_height = canvas.winfo_height() if canvas.winfo_height() > 1 else BOARD_SIZE
canvas.delete(f"piece_{player}")
self._draw_capture_area(canvas, player)
piece_counts = count_captured_pieces(captures_list)
if player == BLACK:
start_x = 10
anchor_type = tk.W
count_text_offset = 30
count_anchor = tk.W
else:
start_x = 40
anchor_type = tk.W
count_text_offset = 30
count_anchor = tk.W
current_y = 15 + CAPTURE_CELL_HEIGHT
max_y = canvas_height - 20
pieces_per_column_threshold = max(4, int(canvas_height / CAPTURE_CELL_HEIGHT / 2.5))
col_count = 0
for piece_type in PIECE_ORDER:
count = piece_counts.get(piece_type, 0)
if count > 0:
text_piece = PIECES[piece_type]
if col_count >= pieces_per_column_threshold:
if player == BLACK:
start_x = canvas_width // 2 + 10
else:
start_x = 10 + (canvas_width // 2)
current_y = 15 + CAPTURE_CELL_HEIGHT
pieces_per_column_threshold = 99
fill_color = 'black'
if self.is_placing_piece and self.selected_piece and self.selected_piece[1] == piece_type and player == BLACK:
fill_color = 'blue'
angle = 180 if player == WHITE else 0
canvas.create_text(start_x, current_y,
text=text_piece,
font=('Arial', CELL_SIZE // 3),
fill=fill_color,
angle=angle,
anchor=anchor_type,
tags=(f"piece_{player}", f"{player}_{piece_type}"))
count_text_x = start_x + count_text_offset
canvas.create_text(count_text_x, current_y,
text=f"x{count}",
font=('Arial', CELL_SIZE // 5),
fill='black',
anchor=count_anchor,
tags=f"{player}_count")
current_y += CAPTURE_CELL_HEIGHT
col_count += 1
if current_y > max_y:
break
def update_turn_indicator(self):
text = f"手番: {self.game.current_player}"
if self.game.current_player == BLACK:
self.turn_label.config(text=text, fg='blue')
else:
self.turn_label.config(text=text, fg='red')
def stop_flashing(self):
if self.king_flash_job:
self.root.after_cancel(self.king_flash_job)
self.is_flashing = False
self.king_flash_job = None
self.draw_pieces()
def flash_king(self, state=True):
self.is_flashing = state
self.draw_pieces()
self.king_flash_job = self.root.after(500, lambda: self.flash_king(not state))
def on_capture_click(self, event):
if not self.game_running: return
is_white_capture_click = event.widget == self.white_capture_canvas
if is_white_capture_click:
player_in_panel = WHITE
else:
player_in_panel = BLACK
if player_in_panel == WHITE:
self.selected_piece = None
self.is_placing_piece = False
self.highlight_moves = []
self.draw_pieces()
self.draw_captured_pieces()
return
if self.game.current_player != BLACK: return
if self.selected_piece and self.original_pos and not self.is_placing_piece:
self.game.board[self.original_pos[0]][self.original_pos[1]] = self.selected_piece
self.original_pos = None
clicked_item = self.black_capture_canvas.find_closest(event.x, event.y)[0]
tags = self.black_capture_canvas.gettags(clicked_item)
target_piece_type = None
for tag in tags:
if tag.startswith(f"{BLACK}_") and tag != f"{BLACK}_count" and tag != f"piece_{BLACK}":
target_piece_type = tag.split('_')[1]
break
if target_piece_type:
piece_counts = count_captured_pieces(self.game.black_captures)
if piece_counts.get(target_piece_type, 0) > 0:
if self.is_placing_piece and self.selected_piece and self.selected_piece[1] == target_piece_type:
self.selected_piece = None
self.is_placing_piece = False
self.highlight_moves = []
else:
self.selected_piece = (BLACK, target_piece_type)
self.is_placing_piece = True
self.original_pos = None
legal_moves_raw = self.game.get_all_legal_moves(BLACK, self.game.board, self.game.black_captures, self.game.white_captures)
self.highlight_moves = []
for move in legal_moves_raw:
if move[0] == 'drop':
move_type, p_type, r, c = move
if p_type == target_piece_type:
self.highlight_moves.append((r, c))
if not self.highlight_moves:
self.selected_piece = None
self.is_placing_piece = False
messagebox.showinfo("無効な手", "その駒を打てる場所はありません。", icon='warning')
self.draw_pieces()
self.draw_captured_pieces()
return
self.selected_piece = None
self.is_placing_piece = False
self.highlight_moves = []
self.draw_pieces()
self.draw_captured_pieces()
def on_click(self, event):
r, c = event.y // CELL_SIZE, event.x // CELL_SIZE
if not (0 <= r < 9 and 0 <= c < 9 and self.game_running and self.game.current_player != AI_PLAYER):
return
if self.is_placing_piece:
if (r, c) in self.highlight_moves:
self.handle_drop_move(r, c)
return
else:
self.selected_piece = None
self.is_placing_piece = False
self.highlight_moves = []
self.draw_pieces()
self.draw_captured_pieces()
return
current_piece_data = self.game.board[r][c]
if self.selected_piece and self.original_pos == (r, c):
self.game.board[r][c] = self.selected_piece
self.selected_piece = None
self.original_pos = None
self.highlight_moves = []
self.draw_pieces()
elif current_piece_data != EMPTY and current_piece_data[0] == self.game.current_player:
if self.selected_piece and self.original_pos:
self.game.board[self.original_pos[0]][self.original_pos[1]] = self.selected_piece
self.selected_piece = current_piece_data
self.original_pos = (r, c)
self.highlight_moves = self.game.get_valid_moves(r, c, self.game.board, self.game.current_player)
if not self.highlight_moves:
self.game.board[r][c] = self.selected_piece
self.selected_piece = None
self.original_pos = None
else:
self.game.board[r][c] = EMPTY
self.draw_pieces()
else:
if self.selected_piece and self.original_pos:
self.game.board[self.original_pos[0]][self.original_pos[1]] = self.selected_piece
self.selected_piece = None
self.original_pos = None
self.highlight_moves = []
self.draw_pieces()
def on_drag(self, event):
if self.selected_piece and not self.is_placing_piece:
self.canvas.delete("drag_piece")
player, piece_type = self.selected_piece
text_piece = PIECES[piece_type]
angle = 180 if player == WHITE else 0
font_size = int(CELL_SIZE // 3.3)
self.canvas.create_text(event.x, event.y,
text=text_piece,
font=('Arial', font_size),
fill='black',
angle=angle,
tags="drag_piece")
def on_release(self, event):
if self.is_placing_piece:
self.canvas.delete("drag_piece")
return
if not self.game_running or self.game.current_player == AI_PLAYER:
self.canvas.delete("drag_piece")
if self.selected_piece and self.original_pos:
self.game.board[self.original_pos[0]][self.original_pos[1]] = self.selected_piece
self.selected_piece = None
self.original_pos = None
self.is_placing_piece = False
return
if self.selected_piece and self.original_pos:
new_col = event.x // CELL_SIZE
new_row = event.y // CELL_SIZE
is_valid_area = 0 <= new_row < 9 and 0 <= new_col < 9
is_valid_move = is_valid_area and (new_row, new_col) in self.highlight_moves
player = self.selected_piece[0]
if is_valid_move:
board_before_move = copy.deepcopy(self.game.board)
piece_type_before_promotion = self.selected_piece[1]
target_piece = board_before_move[new_row][new_col]
if target_piece != EMPTY:
captured_piece = get_demoted_piece(target_piece[1])
capture_list = self.game.black_captures
capture_list.append((player, captured_piece))
promote_flag = False
can_promote_check = should_promote(piece_type_before_promotion, player, self.original_pos[0], new_row)
is_forced_promotion = (
(piece_type_before_promotion in ['FU', '香'] and ((player == BLACK and new_row == 0) or (player == WHITE and new_row == 8))) or
(piece_type_before_promotion == 'KE' and ((player == BLACK and new_row <= 1) or (player == WHITE and new_row >= 7)))
)
if is_forced_promotion:
self.selected_piece = (player, get_promoted_piece(piece_type_before_promotion))
promote_flag = True
elif can_promote_check:
result = messagebox.askyesno("成り", "成りますか? (「いいえ」を選択すると、成りません。)")
if result:
self.selected_piece = (player, get_promoted_piece(piece_type_before_promotion))
promote_flag = True
self.game.board[new_row][new_col] = self.selected_piece
move_details = ('move', self.original_pos[0], self.original_pos[1], new_row, new_col, promote_flag)
turn_count = (len(self.game.history) // 2) + 1
kifu_str = format_move_to_kifu_static(move_details, player, board_before_move)
if player == BLACK:
self.append_kifu(f"{turn_count}. ☗{kifu_str}")
else:
self.append_kifu(f" ☖{kifu_str}")
self.handle_end_turn(WHITE)
else:
if self.original_pos:
self.game.board[self.original_pos[0]][self.original_pos[1]] = self.selected_piece
self.selected_piece = None
self.original_pos = None
self.highlight_moves = []
self.is_placing_piece = False
self.canvas.delete("drag_piece")
self.draw_pieces()
self.draw_captured_pieces()
def handle_drop_move(self, r, c):
if not self.is_placing_piece or not self.selected_piece: return
piece_type = self.selected_piece[1]
player = self.selected_piece[0]
temp_list = []
removed = False
for p, t in self.game.black_captures:
if t == piece_type and not removed:
removed = True
else:
temp_list.append((p, t))
self.game.black_captures = temp_list
self.game.board[r][c] = self.selected_piece
move_details = ('drop', piece_type, r, c)
turn_count = (len(self.game.history) // 2) + 1
kifu_str = format_move_to_kifu_static(move_details, player, self.game.board) + "打"
self.append_kifu(f"{turn_count}. ☗{kifu_str}")
self.selected_piece = None
self.is_placing_piece = False
self.highlight_moves = []
self.handle_end_turn(WHITE)
def handle_end_turn(self, next_player):
self.game.current_player = next_player
current_state = get_simple_state_string_static(self.game.board, self.game.black_captures, self.game.white_captures, self.game.current_player)
self.game.game_states.append(current_state)
self.game.save_state()
repetition_count = self.game.game_states.count(current_state)
if repetition_count >= 4:
messagebox.showinfo("ゲーム終了", "千日手により引き分けです。")
self.game_running = False
self.stop_flashing()
return
self.stop_flashing()
self.update_turn_indicator()
if self.game_running:
self.draw_pieces()
self.draw_captured_pieces()
if self.game.is_checkmate(self.game.current_player, self.game.board, self.game.black_captures, self.game.white_captures):
winner = BLACK if self.game.current_player == WHITE else WHITE
messagebox.showinfo("ゲーム終了", f"詰みです!{winner}の勝利!")
self.game_running = False
elif self.game.is_in_check(self.game.current_player, self.game.board):
self.flash_king()
if self.game.current_player == AI_PLAYER and self.game_running:
self.canvas.after(500, self.make_ai_move)
def undo_move(self):
if self.game.current_player == AI_PLAYER:
messagebox.showinfo("警告", "AIの手番中はUndoできません。AIの手が終わるまでお待ちください。")
return
self.kifu_text.config(state=tk.NORMAL)
self.kifu_text.delete(1.0, tk.END)
if self.game.undo_state():
self.stop_flashing()
self.selected_piece = None
self.original_pos = None
self.is_placing_piece = False
self.highlight_moves = []
self.game_running = True
self.draw_pieces()
self.draw_captured_pieces()
self.update_turn_indicator()
if self.game.is_in_check(self.game.current_player, self.game.board):
self.flash_king()
if self.game.current_player == AI_PLAYER and self.game_running:
self.canvas.after(500, self.make_ai_move)
self.kifu_text.config(state=tk.DISABLED)
return True
else:
messagebox.showinfo("情報", "これ以上、手は戻せません。")
self.kifu_text.config(state=tk.DISABLED)
return False
def make_ai_move(self):
if not self.game_running or self.game.current_player != AI_PLAYER: return
board_before_move = copy.deepcopy(self.game.board)
chosen_move = self.game.find_best_move(self.game.board, self.game.black_captures, self.game.white_captures, AI_PLAYER, SEARCH_DEPTH)
if chosen_move is None:
messagebox.showinfo("ゲーム終了", "AIに合法手がありません。")
self.game_running = False
return
self.game.board, self.game.black_captures, self.game.white_captures = self.game.apply_move(
self.game.board,
self.game.black_captures,
self.game.white_captures,
chosen_move,
AI_PLAYER
)
move_details = chosen_move
player = AI_PLAYER
kifu_str = format_move_to_kifu_static(move_details, player, board_before_move)
if move_details[0] == 'drop':
kifu_str += "打"
self.append_kifu(f" ☖{kifu_str}")
self.handle_end_turn(BLACK)
def append_kifu(self, kifu_string):
self.kifu_text.config(state=tk.NORMAL)
self.kifu_text.insert(tk.END, kifu_string + "\n")
self.kifu_text.see(tk.END)
self.kifu_text.config(state=tk.DISABLED)
# ==============================================================================
# 5. メイン実行部 (変更なし)
# ==============================================================================
if __name__ == "__main__":
root = tk.Tk()
app = ShogiApp(root)
root.mainloop()
III. Redkabagonのゲーム進展と次回予告
1. RedkabagonのG5ゲーム進展
一方で、Redkabagonは、これまで通りG5 Entertainment提供のアイテム探し&3マッチゲームをクリアする動画を投稿し、ブログにリンクしています。彼女の着実なゲームクリアも、本シリーズの隠れた見どころです。
2. シリーズの継続と次回の挑戦
本シリーズは、今後も継続的に新しいアプリ開発に挑んでいきます。
Redkabagonさんの公道デビューはネタ化していますが、プログラミング初心者の筆者も、AIの力を借りて『AIコーダー』としてデビューできるのか、どうか!?という挑戦は続きます。
次回は別のパズルゲームアプリの作成、またはプロの脳トレアプリの機能との比較検証を予定しています。
どうぞ、Geminiのプロンプトの世界に飛び込んでみてください。
【The Gear】撮影機材のご紹介
本動画の撮影機材や新しい動画編集ソフト「PowerDirector 365」に関する情報、アフィリエイト情報については、「The Gear」カテゴリーで詳しくご紹介しています。



