カテゴリーの変更から
カテゴリー変更の説明がフォーマット化している点、ご容赦ください。今回は脱コロナをイメージしての変更ではなく、ゲームに関わる事柄を扱う新しい独立したカテゴリーとして「Game Trial Log」を立ち上げます。
ポンコツ夫婦の役割
Redkabagonはこれまで通り、G5Entertainmentの提供するアイテム探し&マッチ3を行い、その様子を録画・公開します。筆者Dr.takodemousは新たにゲームアプリを作成し、アプリのコード紹介と動作状況の録画公開に挑戦します
ゲームアプリ作成はGeminiのコーディングサポートを受けますが、時間がかかるため更新頻度は遅くなります。気長にお待ちください。また、機材に関わる内容は「The Gear」とクロスオーバーしますので、ゲームに興味はないけど機材には興味があるという方々のご来場もお待ちしております。
筆者の経歴
経歴の詳細は割愛しますが、2012年7月にサーティファイWEBクリエイター能力認定試験の上級(合格点65点に対し66点というブービー賞!)を45歳で取得。現在に至るまで、WEB業界で生計を立てられているとは言い難い状況です。他にIllustrator、Photoshop、Javascript、今では使われなくなったflashをかいつまんだ程度に理解している状況です。
Pythonのコード作成は未経験以前の無知。という状況でゲームを作成します。そこでR60世代に向けて是非ともメッセージしたい事があります。AI生成技術は産業革命に匹敵すると言われていますが、(筆者は産業革命を経験していないので匹敵するかどうかは不明!)AIは経験上、未知の世界へ飛び込むことに大いに役立ちます。R60世代の皆様も、一緒に飛び込みましょう!
いきなりのYou Tubeの動画リンクから
【動画】AIと共同制作したオセロ、Dr.takodemousが勝利! Geminiと共同制作したオセロの完成版を、筆者がAI相手に打ち勝った動画です。ほぼ強制的に設置されたヒント表示を使用せず勝利したのは、ある程度のゲームのコツを知っていたからです。(コツは本ブログの趣旨と異なるため省略します。)
伝えたいのは、無知なレベルであってもAIを駆使すると、コーダーと言えるレベルのプログラムがつくれるという事です。ゲームやプログラムに興味がある方は是非参考にしてください。
プロンプトの参考になるように、最初に作成したオセロのコードから完成版までの道のりをフォルダに添付しておきます。プログラムに興味のある方は最初から順番に、ゲームに興味のある方は完全版を試してください。全てPythonのコードとなります。
pyramid of mahjongその三
redkabagonは今まで通り、G5Entertainmentの提供するアイテム探し&マッチ3をゲームしてもらい、その状況を録画そしてYou Tubeに投稿&本ブログにリンクする流れを行ってもらっています。
CapCutでの動画編集は現在のチャンネル登録分で最終となります。今後はCyberlinkの提供するPowerDirector 365を使用して作成していきます。PowerDirector 365についても、機材説明の機会に「The Gear」カテゴリーで紹介したいと思います。
映像作成に興味のある方は、是非ご来場ください。
楽しみながら老いていく(総括)
前回まで五回にわたって記載してきたこの題目を、総括として終了したいと思います。コロナ禍が与えた影響は心身ともに、無意識下にダメージを与えていたと筆者は感じます。
漠然とした日々の不安や拠り所のない希望など、記載内容は意図的であれ無意識であれ、ネガティブな内容であった気がします。その当時はその状態が仕方ないとしても、現在の状況は自己選択が出来る状態です。
総じてカテゴリーの変更を行った後は、楽しみながら老いていくを実践しているようなものです。つまり最近のブログの内容は、R60のポンコツ夫婦が楽しみながら老いていく姿を紹介しているのです。やたら、参考にしてくださいというメッセージが多発するのは、自己選択の結果なような気がします。
やりがいや生きがいは、認知機能や肉体機能を維持するのに役立つと言われています。真意は不明といえばそれまでなのですが、信じてメッセージしていきたいと思います。
海馬を鍛える脳トレゲームその六
PR TIMESさんのサイトより”認知症予防の脳トレWebゲーム『Dr.脳トレ』をリリース。”のページを引用しています
今回からは、無知なAIコーダー(筆者)VSプロのコーダーの決戦の意味合いも含みます。脳科学には触れたことすらない筆者が「脳機能を鍛える」とプロンプトに指示して生まれたコードが、どこまでプロに肉薄できるか、自身でもウキウキしています。
増えた記号を選ぼう
第四回目も海馬を鍛えるシリーズになります。先ずはゲストモードで試してみて、効果を感じた場合アカウントを作成する感じで手軽に始めることが出来ます。このコナーのゲームは記憶の形成、空間学習、感情の制御を鍛えるというゲームとなっています。筆者も同類のアプリの作成にチャレンジするつもりです。
ポンコツ夫婦のGame Trial Log
動画に登場しているバージョンは**プロンプト No.6です。DEPTH=4
という設定で、レベルは中級者以上となっています。実行するには、Pythonを入手する必要があります。入手は自己責任ですが、世界中で使用されているプログラムを実行するためには必需品です。入手方法や設定方法などのリンクを貼っておきます。
どうぞGeminiのプロンプトの世界に、飛び込んでみてください。
プロンプトno1(外殻の設定と初期動作:相互手動)
# othello.py
# 1. ゲームボードの初期化
def create_board():
board = []
for i in range(8):
row = [' '] * 8
board.append(row)
board[3][3] = 'W'
board[3][4] = 'B'
board[4][3] = 'B'
board[4][4] = 'W'
return board
# 2. ボードの表示
def print_board(board):
print(' a b c d e f g h')
print(' +-+-+-+-+-+-+-+-+')
for i in range(8):
print(f'{i+1}|', end='')
for j in range(8):
print(f'{board[i][j]}|', end='')
print(f' {i+1}')
print(' +-+-+-+-+-+-+-+-+')
print(' a b c d e f g h')
# 3. 駒を置ける場所の判定と駒の反転
def is_valid_move(board, row, col, player, opponent):
if not (0 <= row < 8 and 0 <= col < 8) or board[row][col] != ' ':
return False
directions = [
(-1, -1), (-1, 0), (-1, 1),
(0, -1), (0, 1),
(1, -1), (1, 0), (1, 1)
]
found_flips = []
for dr, dc in directions:
r, c = row + dr, col + dc
path = []
while 0 <= r < 8 and 0 <= c < 8 and board[r][c] == opponent:
path.append((r, c))
r, c = r + dr, c + dc
if 0 <= r < 8 and 0 <= c < 8 and board[r][c] == player and path:
found_flips.extend(path)
return found_flips
def flip_pieces(board, pieces_to_flip, player):
for r, c in pieces_to_flip:
board[r][c] = player
# 4. メインゲームループ
def main():
board = create_board()
player_turn = 'B'
opponent_turn = 'W'
while True:
print_board(board)
print(f'現在のターン: {player_turn}')
try:
move_str = input('駒を置く場所を入力してください (例: d3): ')
col_char = move_str[0].lower()
row_char = move_str[1:]
# 入力を座標に変換
col = 'abcdefgh'.index(col_char)
row = int(row_char) - 1
if not (0 <= row < 8 and 0 <= col < 8):
print('無効な入力です。')
continue
# 有効な手か判定
pieces_to_flip = is_valid_move(board, row, col, player_turn, opponent_turn)
if pieces_to_flip:
board[row][col] = player_turn
flip_pieces(board, pieces_to_flip, player_turn)
# ターンを交代
player_turn, opponent_turn = opponent_turn, player_turn
else:
print('その場所には置けません。有効な手を打ってください。')
except (ValueError, IndexError):
print('無効な入力です。例: d3 のように入力してください。')
# プログラムの実行
if __name__ == '__main__':
main()
プロンプトno2(GUIの設定1と初期動作:相互手動)
# 1. ゲームボードの初期化
# 盤面を表すための定数を定義します
# '・' : 空, '⚫' : 黒, '⚪' : 白
EMPTY = '・'
BLACK = '⚫'
WHITE = '⚪'
def create_board():
"""
8x8の空の盤面を作成し、初期配置の石を配置します。
"""
board = []
for _ in range(8):
row = [EMPTY] * 8
board.append(row)
board[3][3] = WHITE
board[3][4] = BLACK
board[4][3] = BLACK
board[4][4] = WHITE
return board
# 2. ボードの表示
def print_board(board):
"""
現在の盤面をきれいに表示します。
"""
# 列の表記を修正
print(' a b c d e f g h')
# 罫線の表示
print(' +---+---+---+---+---+---+---+---+---+---')
for i in range(8):
# 行の表示と升目の調整
row_str = f'{i+1} |'
for j in range(8):
row_str += f' {board[i][j]} |'
print(row_str)
print(' +---+---+---+---+---+---+---+---+---+---')
# 3. 駒を置ける場所の判定と駒の反転
def is_valid_move(board, row, col, player, opponent):
"""
指定された位置に石を置くことができるか判定し、
ひっくり返せる石のリストを返します。
"""
if not (0 <= row < 8 and 0 <= col < 8) or board[row][col] != EMPTY:
return None
# 8つの方向を定義 (行の変化, 列の変化)
directions = [
(-1, -1), (-1, 0), (-1, 1),
(0, -1), (0, 1),
(1, -1), (1, 0), (1, 1)
]
pieces_to_flip = []
for dr, dc in directions:
r, c = row + dr, col + dc
path = []
while 0 <= r < 8 and 0 <= c < 8 and board[r][c] == opponent:
path.append((r, c))
r, c = r + dr, c + dc
# 挟み込んでいるかチェック
if 0 <= r < 8 and 0 <= c < 8 and board[r][c] == player and path:
pieces_to_flip.extend(path)
return pieces_to_flip if pieces_to_flip else None
def flip_pieces(board, pieces_to_flip, player):
"""
ひっくり返す石のリストを、指定されたプレイヤーの石に変換します。
"""
for r, c in pieces_to_flip:
board[r][c] = player
def get_valid_moves(board, player, opponent):
"""
現在のプレイヤーが石を置けるすべての場所(有効な手)のリストを返します。
"""
valid_moves = []
for r in range(8):
for c in range(8):
if board[r][c] == EMPTY:
if is_valid_move(board, r, c, player, opponent):
valid_moves.append((r, c))
return valid_moves
def count_pieces(board):
"""
盤面上の石の数を数え、勝敗を表示します。
"""
black_count = 0
white_count = 0
for r in range(8):
for c in range(8):
if board[r][c] == BLACK:
black_count += 1
elif board[r][c] == WHITE:
white_count += 1
print(f"最終結果:黒 = {black_count}、白 = {white_count}")
if black_count > white_count:
print("黒の勝ちです!")
elif white_count > black_count:
print("白の勝ちです!")
else:
print("引き分けです。")
# 4. メインゲームループ
def main():
"""
ゲームのメイン処理を実行します。
"""
board = create_board()
player_turn = BLACK
opponent_turn = WHITE
pass_count = 0 # 連続でパスした回数をカウント
while True:
print_board(board)
valid_moves = get_valid_moves(board, player_turn, opponent_turn)
if not valid_moves:
pass_count += 1
print(f'現在のターン: {"黒" if player_turn == BLACK else "白"}')
print("置ける場所がありません。パスします。")
if pass_count == 2:
print("両プレイヤーがパスしました。ゲームを終了します。")
break
player_turn, opponent_turn = opponent_turn, player_turn
continue
else:
pass_count = 0
print(f'現在のターン: {"黒" if player_turn == BLACK else "白"}')
try:
move_str = input('駒を置く場所を入力してください (例: d3): ')
if move_str.lower() == 'q':
print("ゲームを終了します。")
break
col_char = move_str[0].lower()
row_char = move_str[1:]
col = 'abcdefgh'.index(col_char)
row = int(row_char) - 1
if (row, col) not in valid_moves:
print('その場所には置けません。有効な手を打ってください。')
continue
pieces_to_flip = is_valid_move(board, row, col, player_turn, opponent_turn)
board[row][col] = player_turn
flip_pieces(board, pieces_to_flip, player_turn)
player_turn, opponent_turn = opponent_turn, player_turn
except (ValueError, IndexError):
print('無効な入力です。例: d3 のように入力してください。')
print("ゲーム終了!")
count_pieces(board)
input("結果を確認後、何かキーを押して終了してください...")
# プログラムの実行
if __name__ == '__main__':
main()
プロンプトno3(GUIの設定2と初期動作:相互手動:盤面ほぼ完成)
import tkinter as tk
from tkinter import messagebox
# 盤面を表すための定数
EMPTY = '・'
BLACK = '⚫'
WHITE = '⚪'
# ウィンドウとキャンバスのサイズ
BOARD_SIZE = 400
CELL_SIZE = BOARD_SIZE // 8
# グローバル変数としてボードの状態を保持
game_board = None
canvas = None
current_player = BLACK
pass_count = 0
def create_board():
"""
8x8の空の盤面を作成し、初期配置の石を配置します。
"""
board = []
for _ in range(8):
row = [EMPTY] * 8
board.append(row)
board[3][3] = WHITE
board[3][4] = BLACK
board[4][3] = BLACK
board[4][4] = WHITE
return board
def is_valid_move(board, row, col, player, opponent):
"""
指定された位置に石を置くことができるか判定し、
ひっくり返せる石のリストを返します。
"""
if not (0 <= row < 8 and 0 <= col < 8) or board[row][col] != EMPTY:
return None
directions = [
(-1, -1), (-1, 0), (-1, 1),
(0, -1), (0, 1),
(1, -1), (1, 0), (1, 1)
]
pieces_to_flip = []
for dr, dc in directions:
r, c = row + dr, col + dc
path = []
while 0 <= r < 8 and 0 <= c < 8 and board[r][c] == opponent:
path.append((r, c))
r, c = r + dr, c + dc
if 0 <= r < 8 and 0 <= c < 8 and board[r][c] == player and path:
pieces_to_flip.extend(path)
return pieces_to_flip if pieces_to_flip else None
def flip_pieces(board, pieces_to_flip, player):
"""
ひっくり返す石のリストを、指定されたプレイヤーの石に変換します。
"""
for r, c in pieces_to_flip:
board[r][c] = player
def get_valid_moves(board, player, opponent):
"""
現在のプレイヤーが石を置けるすべての場所(有効な手)のリストを返します。
"""
valid_moves = []
for r in range(8):
for c in range(8):
if board[r][c] == EMPTY:
if is_valid_move(board, r, c, player, opponent):
valid_moves.append((r, c))
return valid_moves
def count_pieces(board):
"""
盤面上の石の数を数え、勝敗を決定します。
"""
black_count = sum(row.count(BLACK) for row in board)
white_count = sum(row.count(WHITE) for row in board)
return black_count, white_count
def end_game_check():
"""
ゲームが終了したかチェックし、終了していれば勝敗を表示します。
"""
global pass_count
black_count, white_count = count_pieces(game_board)
if pass_count == 2 or (black_count + white_count) == 64:
# ゲーム終了
if black_count > white_count:
message = f"ゲーム終了!\n黒の勝ちです!\n(黒: {black_count}, 白: {white_count})"
elif white_count > black_count:
message = f"ゲーム終了!\n白の勝ちです!\n(黒: {black_count}, 白: {white_count})"
else:
message = f"ゲーム終了!\n引き分けです!\n(黒: {black_count}, 白: {white_count})"
messagebox.showinfo("ゲーム終了", message)
return True
return False
def draw_pieces():
"""
Pythonのボード情報に基づいて、canvas上に石を描画します。
"""
canvas.delete("all")
# 盤面の升目を再描画
for i in range(8):
for j in range(8):
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="green")
# 石を描画
for row_idx in range(8):
for col_idx in range(8):
piece = game_board[row_idx][col_idx]
if piece == BLACK:
color = "black"
elif piece == WHITE:
color = "white"
else:
continue
x1 = col_idx * CELL_SIZE + 5
y1 = row_idx * CELL_SIZE + 5
x2 = x1 + CELL_SIZE - 10
y2 = y1 + CELL_SIZE - 10
canvas.create_oval(x1, y1, x2, y2, fill=color, outline="black")
def on_click(event):
"""
マウスがクリックされたときに呼び出される関数です。
"""
global current_player, pass_count
col = event.x // CELL_SIZE
row = event.y // CELL_SIZE
opponent_player = WHITE if current_player == BLACK else BLACK
pieces_to_flip = is_valid_move(game_board, row, col, current_player, opponent_player)
if pieces_to_flip:
# 有効な手であれば、石を置く
game_board[row][col] = current_player
flip_pieces(game_board, pieces_to_flip, current_player)
# プレイヤーを交代
current_player = opponent_player
pass_count = 0 # パスカウントをリセット
else:
# 無効な手の場合、何もしない
return
draw_pieces()
# 相手プレイヤーに有効な手があるかチェック
if not get_valid_moves(game_board, current_player, opponent_player):
print(f'{current_player}は置ける場所がありません。パスします。')
pass_count += 1
current_player = opponent_player # プレイヤーを再度交代
# 連続パスの場合、ゲーム終了チェック
if pass_count == 2:
end_game_check()
else:
# パスしたので、盤面を再描画して次のプレイヤーの番
draw_pieces()
# ゲーム終了条件を常にチェック
if not end_game_check():
pass
else:
print("ゲームが終了しました。")
def create_gui_board():
"""
tkinterのウィンドウとオセロ盤のGUIを作成します。
"""
global game_board, canvas
root = tk.Tk()
root.title("オセロゲーム")
canvas = tk.Canvas(root, width=BOARD_SIZE, height=BOARD_SIZE, bg="green")
canvas.pack()
canvas.bind("<Button-1>", on_click)
game_board = create_board()
draw_pieces()
root.mainloop()
# プログラムの実行
if __name__ == "__main__":
create_gui_board()
プロンプトno4(AI対戦相手のプログラムと初期動作)
import tkinter as tk
from tkinter import messagebox
import random # <--- ここを追加
# 盤面を表すための定数
EMPTY = '・'
BLACK = '⚫'
WHITE = '⚪'
# ウィンドウとキャンバスのサイズ
BOARD_SIZE = 400
CELL_SIZE = BOARD_SIZE // 8
# グローバル変数としてボードの状態を保持
game_board = None
canvas = None
current_player = BLACK
pass_count = 0
# AIの設定
AI_PLAYER = WHITE # AIを白のプレイヤーに設定
IS_AI_TURN = False # AIのターンかどうかを示すフラグ
def create_board():
"""
8x8の空の盤面を作成し、初期配置の石を配置します。
"""
board = []
for _ in range(8):
row = [EMPTY] * 8
board.append(row)
board[3][3] = WHITE
board[3][4] = BLACK
board[4][3] = BLACK
board[4][4] = WHITE
return board
# ----------------- AIロジックの追加 -----------------
def get_ai_move(valid_moves):
"""
有効な手の中からランダムに手を選択します。
"""
return random.choice(valid_moves)
def handle_ai_turn():
"""
AIのターンを処理します。
"""
global current_player, pass_count, IS_AI_TURN
opponent_player = BLACK # AIは白なので、相手は黒
valid_moves = get_valid_moves(game_board, AI_PLAYER, opponent_player)
if not valid_moves:
pass_count += 1
print("AIは置ける場所がありません。パスします。")
current_player = BLACK # プレイヤーを交代
IS_AI_TURN = False
end_game_check()
draw_pieces()
else:
pass_count = 0
# AIがランダムに手を選択
row, col = get_ai_move(valid_moves)
# 石を置く処理
pieces_to_flip = is_valid_move(game_board, row, col, AI_PLAYER, opponent_player)
game_board[row][col] = AI_PLAYER
flip_pieces(game_board, pieces_to_flip, AI_PLAYER)
current_player = BLACK # プレイヤーを交代
IS_AI_TURN = False
draw_pieces()
if not end_game_check():
pass
else:
print("ゲームが終了しました。")
def on_click(event):
"""
マウスがクリックされたときに呼び出される関数です。
"""
global current_player, pass_count, IS_AI_TURN
if IS_AI_TURN:
return # AIのターン中はプレイヤーの入力を無視
col = event.x // CELL_SIZE
row = event.y // CELL_SIZE
opponent_player = WHITE # プレイヤーは黒なので、相手は白
pieces_to_flip = is_valid_move(game_board, row, col, current_player, opponent_player)
if pieces_to_flip:
game_board[row][col] = current_player
flip_pieces(game_board, pieces_to_flip, current_player)
pass_count = 0
current_player = opponent_player
IS_AI_TURN = True # 次のターンはAI
draw_pieces()
if not end_game_check():
canvas.after(500, handle_ai_turn) # 0.5秒後にAIを動かす
else:
print("ゲームが終了しました。")
else:
print("その場所には置けません。有効な手を打ってください。")
# ----------------- ここから下の関数は変更なし -----------------
def is_valid_move(board, row, col, player, opponent):
if not (0 <= row < 8 and 0 <= col < 8) or board[row][col] != EMPTY:
return None
directions = [(-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)]
pieces_to_flip = []
for dr, dc in directions:
r, c = row + dr, col + dc
path = []
while 0 <= r < 8 and 0 <= c < 8 and board[r][c] == opponent:
path.append((r, c))
r, c = r + dr, c + dc
if 0 <= r < 8 and 0 <= c < 8 and board[r][c] == player and path:
pieces_to_flip.extend(path)
return pieces_to_flip if pieces_to_flip else None
def flip_pieces(board, pieces_to_flip, player):
for r, c in pieces_to_flip:
board[r][c] = player
def get_valid_moves(board, player, opponent):
valid_moves = []
for r in range(8):
for c in range(8):
if board[r][c] == EMPTY:
if is_valid_move(board, r, c, player, opponent):
valid_moves.append((r, c))
return valid_moves
def count_pieces(board):
black_count = sum(row.count(BLACK) for row in board)
white_count = sum(row.count(WHITE) for row in board)
return black_count, white_count
def end_game_check():
global pass_count
black_count, white_count = count_pieces(game_board)
if pass_count == 2 or (black_count + white_count) == 64:
if black_count > white_count:
message = f"ゲーム終了!\n黒の勝ちです!\n(黒: {black_count}, 白: {white_count})"
elif white_count > black_count:
message = f"ゲーム終了!\n白の勝ちです!\n(黒: {black_count}, 白: {white_count})"
else:
message = f"ゲーム終了!\n引き分けです!\n(黒: {black_count}, 白: {white_count})"
messagebox.showinfo("ゲーム終了", message)
return True
return False
def draw_pieces():
canvas.delete("all")
for i in range(8):
for j in range(8):
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="green")
for row_idx in range(8):
for col_idx in range(8):
piece = game_board[row_idx][col_idx]
if piece == BLACK:
color = "black"
elif piece == WHITE:
color = "white"
else:
continue
x1 = col_idx * CELL_SIZE + 5
y1 = row_idx * CELL_SIZE + 5
x2 = x1 + CELL_SIZE - 10
y2 = y1 + CELL_SIZE - 10
canvas.create_oval(x1, y1, x2, y2, fill=color, outline="black")
def create_gui_board():
global game_board, canvas
root = tk.Tk()
root.title("オセロゲーム")
canvas = tk.Canvas(root, width=BOARD_SIZE, height=BOARD_SIZE, bg="green")
canvas.pack()
canvas.bind("<Button-1>", on_click)
game_board = create_board()
draw_pieces()
root.mainloop()
if __name__ == "__main__":
create_gui_board()
プロンプトno5(AI対戦相手のGUI改善:ヒント表示と初期動作)
import tkinter as tk
from tkinter import messagebox
import copy
import time
import sys
# --- ゲーム定数 ---
BOARD_SIZE = 8
CELL_SIZE = 60
WINDOW_SIZE = BOARD_SIZE * CELL_SIZE
SIDE_PANEL_WIDTH = 150
AI_PLAYER = 2 # 2: 黒 (後攻) / 1: 白 (先攻)
SEARCH_DEPTH = 4 # AIの探索深さ
# --- グローバル変数 ---
root = None
canvas = None
board = None
current_player = 1 # 1:白, 2:黒
game_running = True
score_label = None
turn_label = None
history = []
# --- 初期化 ---
def create_initial_board():
"""初期盤面を作成 (0:空, 1:白, 2:黒)"""
new_board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)]
new_board[3][3] = 1 # 白
new_board[3][4] = 2 # 黒
new_board[4][3] = 2 # 黒
new_board[4][4] = 1 # 白
return new_board
# --- ルールとAIロジック ---
def is_valid_move(r, c, player, current_board):
"""指定されたマスに駒を置けるかチェックし、裏返せる駒のリストを返す"""
if current_board[r][c] != 0:
return []
opponent = 3 - player
flips = []
# 8方向をチェック
directions = [(-1, 0), (1, 0), (0, -1), (0, 1), (-1, -1), (-1, 1), (1, -1), (1, 1)]
for dr, dc in directions:
line_flips = []
rr, cc = r + dr, c + dc
# 相手の駒が続く限り進む
while 0 <= rr < BOARD_SIZE and 0 <= cc < BOARD_SIZE and current_board[rr][cc] == opponent:
line_flips.append((rr, cc))
rr, cc = rr + dr, cc + dc
# 終点が自分の駒で、間に裏返せる駒がある場合
if line_flips and 0 <= rr < BOARD_SIZE and 0 <= cc < BOARD_SIZE and current_board[rr][cc] == player:
flips.extend(line_flips)
return flips
def get_valid_moves(player, current_board):
"""合法手の一覧を (r, c) のリストで返す"""
valid_moves = []
for r in range(BOARD_SIZE):
for c in range(BOARD_SIZE):
if is_valid_move(r, c, player, current_board):
valid_moves.append((r, c))
return valid_moves
def apply_move(r, c, player, current_board):
"""実際に駒を置き、盤面を更新する (新しい盤面を返す)"""
new_board = [row[:] for row in current_board]
flips = is_valid_move(r, c, player, current_board)
if not flips:
return new_board # 無効な手の場合は元の盤面を返す
new_board[r][c] = player
for fr, fc in flips:
new_board[fr][fc] = player
return new_board
def evaluate_board(current_board, player):
"""評価関数: 駒の数の差を計算"""
p1_count = sum(row.count(1) for row in current_board)
p2_count = sum(row.count(2) for row in current_board)
if player == 1:
return p1_count - p2_count
else:
return p2_count - p1_count
def minimax(current_board, depth, is_maximizing_player, alpha, beta):
"""Minimax with Alpha-Beta Pruning"""
current_player_minimax = 2 if is_maximizing_player else 1
# 終端条件
valid_moves = get_valid_moves(current_player_minimax, current_board)
if depth == 0 or (not valid_moves and not get_valid_moves(3 - current_player_minimax, current_board)):
return evaluate_board(current_board, 2), None # AI(黒=2)視点の評価を返す
best_move = None
if is_maximizing_player: # AI (黒=2) の手番
max_eval = -float('inf')
for r, c in valid_moves:
new_board = apply_move(r, c, 2, current_board)
current_eval, _ = minimax(new_board, depth - 1, False, alpha, beta)
if current_eval > max_eval:
max_eval = current_eval
best_move = (r, c)
alpha = max(alpha, max_eval)
if beta <= alpha:
break
return max_eval, best_move
else: # プレイヤー (白=1) の手番
min_eval = float('inf')
for r, c in valid_moves:
new_board = apply_move(r, c, 1, current_board)
current_eval, _ = minimax(new_board, depth - 1, True, alpha, beta)
if current_eval < min_eval:
min_eval = current_eval
best_move = (r, c)
beta = min(beta, min_eval)
if beta <= alpha:
break
return min_eval, best_move
def find_best_move(current_board, depth):
start_time = time.time()
_, best_move = minimax(current_board, depth, True, -float('inf'), float('inf'))
end_time = time.time()
print(f"Minimax探索時間: {end_time - start_time:.2f}秒 (深さ: {depth})")
return best_move
# --- UIと描画 ---
def draw_board():
"""盤面を描画し、駒を配置する"""
canvas.delete("all")
# 盤面のグリッド線を描画
for i in range(BOARD_SIZE + 1):
# 垂直線
canvas.create_line(i * CELL_SIZE, 0, i * CELL_SIZE, WINDOW_SIZE, fill="black")
# 水平線
canvas.create_line(0, i * CELL_SIZE, WINDOW_SIZE, i * CELL_SIZE, fill="black")
# 駒の配置
for r in range(BOARD_SIZE):
for c in range(BOARD_SIZE):
player = board[r][c]
if player != 0:
x1 = c * CELL_SIZE + 5
y1 = r * CELL_SIZE + 5
x2 = (c + 1) * CELL_SIZE - 5
y2 = (r + 1) * CELL_SIZE - 5
color = "white" if player == 1 else "black"
canvas.create_oval(x1, y1, x2, y2, fill=color, outline="black")
update_score_display()
def update_score_display():
"""スコアとターン表示を更新する"""
p1_count = sum(row.count(1) for row in board)
p2_count = sum(row.count(2) for row in board)
score_text = f"白: {p1_count}\n黒: {p2_count}"
turn_text = f"現在のターン: {'白' if current_player == 1 else '黒'}"
if score_label and turn_label:
score_label.config(text=score_text)
turn_label.config(text=turn_text)
def show_hint():
"""合法手があるマスをハイライト表示する"""
canvas.delete("hint")
valid_moves = get_valid_moves(current_player, board)
for r, c in valid_moves:
x1 = c * CELL_SIZE + 2
y1 = r * CELL_SIZE + 2
x2 = (c + 1) * CELL_SIZE - 2
y2 = (r + 1) * CELL_SIZE - 2
canvas.create_rectangle(x1, y1, x2, y2, outline="yellow", width=3, tags="hint")
# --- イベントハンドラ ---
def handle_click(event):
"""マウスがクリックされたときの処理"""
global board, current_player, game_running
if not game_running or current_player == AI_PLAYER:
return
c = event.x // CELL_SIZE
r = event.y // CELL_SIZE
if not (0 <= r < BOARD_SIZE and 0 <= c < BOARD_SIZE):
return
flips = is_valid_move(r, c, current_player, board)
if flips:
save_state() # 履歴保存
board = apply_move(r, c, current_player, board)
next_player = 3 - current_player
end_turn(next_player)
else:
messagebox.showinfo("無効な手", "そこには置けません。")
def end_turn(next_player):
"""ターン終了時の処理"""
global current_player, game_running
current_player = next_player
draw_board()
p1_moves = get_valid_moves(1, board)
p2_moves = get_valid_moves(2, board)
if not p1_moves and not p2_moves:
# 両者とも置けない = ゲーム終了
game_running = False
p1_count = sum(row.count(1) for row in board)
p2_count = sum(row.count(2) for row in board)
winner = "引き分け"
if p1_count > p2_count: winner = "白の勝利"
elif p2_count > p1_count: winner = "黒の勝利"
messagebox.showinfo("ゲーム終了", f"最終スコア 白:{p1_count}, 黒:{p2_count}\n{winner}")
return
elif not get_valid_moves(current_player, board):
# パス (合法手が無い場合は相手のターンに戻る)
messagebox.showinfo("パス", f"{'白' if current_player == 1 else '黒'}は置く場所がないためパスします。")
current_player = 3 - current_player
draw_board() # プレイヤーが変わったことを表示
show_hint()
if current_player == AI_PLAYER and game_running:
root.after(500, make_ai_move) # 0.5秒後にAIを起動
def make_ai_move():
"""AIが指し手を決定し、実行する"""
global board, current_player, game_running
if not game_running or current_player != AI_PLAYER:
return
save_state() # 履歴保存
best_move = find_best_move(board, SEARCH_DEPTH)
if best_move:
r, c = best_move
board = apply_move(r, c, AI_PLAYER, board)
next_player = 3 - AI_PLAYER
end_turn(next_player)
else:
# AIが置けない場合もパス処理で end_turn が実行される
end_turn(3 - AI_PLAYER)
def save_state():
"""現在のゲーム状態を履歴に保存する (Undo用)"""
global history
state = (
[row[:] for row in board],
current_player
)
history.append(state)
def undo_move():
"""一手前の状態に戻す"""
global board, current_player, game_running, history
if current_player == AI_PLAYER:
messagebox.showinfo("警告", "AIの手番中はUndoできません。")
return
if len(history) <= 1:
messagebox.showinfo("警告", "初期状態のため、これ以上戻せません。")
return
# プレイヤーとAIの2手分を戻す
if len(history) >= 2:
history.pop() # AIの手
history.pop() # プレイヤーの手
else:
# 初期盤面直後の履歴の場合
history.pop()
prev_board, prev_player = history[-1]
board = [row[:] for row in prev_board]
current_player = prev_player
game_running = True
draw_board()
show_hint()
if current_player == AI_PLAYER and game_running:
root.after(500, make_ai_move)
# --- GUIメイン関数 ---
def create_board_ui():
global root, canvas, board, score_label, turn_label
root = tk.Tk()
root.title(f"オセロゲーム (AI深さ: {SEARCH_DEPTH})")
# --- UI中央配置のための改善 ---
# 1. すべての要素を保持するメインフレームを作成
main_frame = tk.Frame(root)
# 2. main_frameを親ウィンドウの中央に配置
# expand=True で利用可能なスペースを広げ、anchor='center' で中央に寄せます。
main_frame.pack(expand=True, anchor='center')
# --------------------------------
# 3. ボード(キャンバス)を main_frame の中に配置 (親を root から main_frame に変更)
canvas = tk.Canvas(main_frame, width=WINDOW_SIZE, height=WINDOW_SIZE, bg="green")
canvas.pack(side=tk.LEFT)
# 4. スコアパネル(サイドパネル)を main_frame の中に配置 (親を root から main_frame に変更)
score_frame = tk.Frame(main_frame, width=SIDE_PANEL_WIDTH)
score_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=10, pady=10)
# --- スコアとターン表示 ---
score_label = tk.Label(score_frame, text="スコア", font=("Arial", 16))
score_label.pack(pady=20)
turn_label = tk.Label(score_frame, text="ターン", font=("Arial", 14))
turn_label.pack(pady=10)
# --- ボタン ---
undo_button = tk.Button(score_frame, text="一手戻る (Undo)", command=undo_move,
height=2, bg="#B0C4DE", fg="black")
undo_button.pack(pady=20)
hint_button = tk.Button(score_frame, text="ヒント表示", command=show_hint,
height=2, bg="#FFFACD", fg="black")
hint_button.pack(pady=10)
# --- ゲームの起動 ---
board = create_initial_board()
canvas.bind("<Button-1>", handle_click)
# 初期状態を履歴に保存
save_state()
draw_board()
show_hint()
def on_closing():
root.destroy()
sys.exit()
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()
if __name__ == "__main__":
create_board_ui()
プロンプトno6(AI対戦相手の完全版ヒント表示の選択機能の追加と初期動作)
import tkinter as tk
from tkinter import messagebox
import copy
import time
import sys
# --- ゲーム定数 ---
BOARD_SIZE = 8
CELL_SIZE = 60
WINDOW_SIZE = BOARD_SIZE * CELL_SIZE
SIDE_PANEL_WIDTH = 150
AI_PLAYER = 2 # 2: 黒 (後攻) / 1: 白 (先攻)
SEARCH_DEPTH = 4 # AIの探索深さ
# --- グローバル変数 ---
root = None
canvas = None
board = None
current_player = 1 # 1:白, 2:黒
game_running = True
score_label = None
turn_label = None
history = []
# --- 初期化 ---
def create_initial_board():
"""初期盤面を作成 (0:空, 1:白, 2:黒)"""
new_board = [[0] * BOARD_SIZE for _ in range(BOARD_SIZE)]
new_board[3][3] = 1 # 白
new_board[3][4] = 2 # 黒
new_board[4][3] = 2 # 黒
new_board[4][4] = 1 # 白
return new_board
# --- ルールとAIロジック ---
def is_valid_move(r, c, player, current_board):
"""指定されたマスに駒を置けるかチェックし、裏返せる駒のリストを返す"""
if current_board[r][c] != 0:
return []
opponent = 3 - player
flips = []
# 8方向をチェック
directions = [(-1, 0), (1, 0), (0, -1), (0, 1), (-1, -1), (-1, 1), (1, -1), (1, 1)]
for dr, dc in directions:
line_flips = []
rr, cc = r + dr, c + dc
# 相手の駒が続く限り進む
while 0 <= rr < BOARD_SIZE and 0 <= cc < BOARD_SIZE and current_board[rr][cc] == opponent:
line_flips.append((rr, cc))
rr, cc = rr + dr, cc + dc
# 終点が自分の駒で、間に裏返せる駒がある場合
if line_flips and 0 <= rr < BOARD_SIZE and 0 <= cc < BOARD_SIZE and current_board[rr][cc] == player:
flips.extend(line_flips)
return flips
def get_valid_moves(player, current_board):
"""合法手の一覧を (r, c) のリストで返す"""
valid_moves = []
for r in range(BOARD_SIZE):
for c in range(BOARD_SIZE):
if is_valid_move(r, c, player, current_board):
valid_moves.append((r, c))
return valid_moves
def apply_move(r, c, player, current_board):
"""実際に駒を置き、盤面を更新する (新しい盤面を返す)"""
new_board = [row[:] for row in current_board]
flips = is_valid_move(r, c, player, current_board)
if not flips:
return new_board # 無効な手の場合は元の盤面を返す
new_board[r][c] = player
for fr, fc in flips:
new_board[fr][fc] = player
return new_board
def evaluate_board(current_board, player):
"""評価関数: 駒の数の差を計算"""
p1_count = sum(row.count(1) for row in current_board)
p2_count = sum(row.count(2) for row in current_board)
if player == 1:
return p1_count - p2_count
else:
return p2_count - p1_count
def minimax(current_board, depth, is_maximizing_player, alpha, beta):
"""Minimax with Alpha-Beta Pruning"""
current_player_minimax = 2 if is_maximizing_player else 1
# 終端条件
valid_moves = get_valid_moves(current_player_minimax, current_board)
if depth == 0 or (not valid_moves and not get_valid_moves(3 - current_player_minimax, current_board)):
return evaluate_board(current_board, 2), None # AI(黒=2)視点の評価を返す
best_move = None
if is_maximizing_player: # AI (黒=2) の手番
max_eval = -float('inf')
for r, c in valid_moves:
new_board = apply_move(r, c, 2, current_board)
current_eval, _ = minimax(new_board, depth - 1, False, alpha, beta)
if current_eval > max_eval:
max_eval = current_eval
best_move = (r, c)
alpha = max(alpha, max_eval)
if beta <= alpha:
break
return max_eval, best_move
else: # プレイヤー (白=1) の手番
min_eval = float('inf')
for r, c in valid_moves:
new_board = apply_move(r, c, 1, current_board)
current_eval, _ = minimax(new_board, depth - 1, True, alpha, beta)
if current_eval < min_eval:
min_eval = current_eval
best_move = (r, c)
beta = min(beta, min_eval)
if beta <= alpha:
break
return min_eval, best_move
def find_best_move(current_board, depth):
start_time = time.time()
_, best_move = minimax(current_board, depth, True, -float('inf'), float('inf'))
end_time = time.time()
print(f"Minimax探索時間: {end_time - start_time:.2f}秒 (深さ: {depth})")
return best_move
# --- UIと描画 ---
def draw_board():
"""盤面を描画し、駒を配置する"""
canvas.delete("all")
# 盤面のグリッド線を描画
for i in range(BOARD_SIZE + 1):
# 垂直線
canvas.create_line(i * CELL_SIZE, 0, i * CELL_SIZE, WINDOW_SIZE, fill="black")
# 水平線
canvas.create_line(0, i * CELL_SIZE, WINDOW_SIZE, i * CELL_SIZE, fill="black")
# 駒の配置
for r in range(BOARD_SIZE):
for c in range(BOARD_SIZE):
player = board[r][c]
if player != 0:
x1 = c * CELL_SIZE + 5
y1 = r * CELL_SIZE + 5
x2 = (c + 1) * CELL_SIZE - 5
y2 = (r + 1) * CELL_SIZE - 5
color = "white" if player == 1 else "black"
canvas.create_oval(x1, y1, x2, y2, fill=color, outline="black")
update_score_display()
def update_score_display():
"""スコアとターン表示を更新する"""
p1_count = sum(row.count(1) for row in board)
p2_count = sum(row.count(2) for row in board)
score_text = f"白: {p1_count}\n黒: {p2_count}"
turn_text = f"現在のターン: {'白' if current_player == 1 else '黒'}"
if score_label and turn_label:
score_label.config(text=score_text)
turn_label.config(text=turn_text)
def show_hint():
"""合法手があるマスをハイライト表示する"""
canvas.delete("hint")
valid_moves = get_valid_moves(current_player, board)
for r, c in valid_moves:
x1 = c * CELL_SIZE + 2
y1 = r * CELL_SIZE + 2
x2 = (c + 1) * CELL_SIZE - 2
y2 = (r + 1) * CELL_SIZE - 2
canvas.create_rectangle(x1, y1, x2, y2, outline="yellow", width=3, tags="hint")
# --- イベントハンドラ ---
def handle_click(event):
"""マウスがクリックされたときの処理"""
global board, current_player, game_running
if not game_running or current_player == AI_PLAYER:
return
c = event.x // CELL_SIZE
r = event.y // CELL_SIZE
if not (0 <= r < BOARD_SIZE and 0 <= c < BOARD_SIZE):
return
flips = is_valid_move(r, c, current_player, board)
if flips:
save_state() # 履歴保存
board = apply_move(r, c, current_player, board)
next_player = 3 - current_player
end_turn(next_player)
else:
messagebox.showinfo("無効な手", "そこには置けません。")
def end_turn(next_player):
"""ターン終了時の処理"""
global current_player, game_running
current_player = next_player
draw_board()
p1_moves = get_valid_moves(1, board)
p2_moves = get_valid_moves(2, board)
if not p1_moves and not p2_moves:
# 両者とも置けない = ゲーム終了
game_running = False
p1_count = sum(row.count(1) for row in board)
p2_count = sum(row.count(2) for row in board)
winner = "引き分け"
if p1_count > p2_count: winner = "白の勝利"
elif p2_count > p1_count: winner = "黒の勝利"
messagebox.showinfo("ゲーム終了", f"最終スコア 白:{p1_count}, 黒:{p2_count}\n{winner}")
return
elif not get_valid_moves(current_player, board):
# パス (合法手が無い場合は相手のターンに戻る)
messagebox.showinfo("パス", f"{'白' if current_player == 1 else '黒'}は置く場所がないためパスします。")
current_player = 3 - current_player
draw_board() # プレイヤーが変わったことを表示
show_hint()
if current_player == AI_PLAYER and game_running:
root.after(500, make_ai_move) # 0.5秒後にAIを起動
def make_ai_move():
"""AIが指し手を決定し、実行する"""
global board, current_player, game_running
if not game_running or current_player != AI_PLAYER:
return
save_state() # 履歴保存
best_move = find_best_move(board, SEARCH_DEPTH)
if best_move:
r, c = best_move
board = apply_move(r, c, AI_PLAYER, board)
next_player = 3 - AI_PLAYER
end_turn(next_player)
else:
# AIが置けない場合もパス処理で end_turn が実行される
end_turn(3 - AI_PLAYER)
def save_state():
"""現在のゲーム状態を履歴に保存する (Undo用)"""
global history
state = (
[row[:] for row in board],
current_player
)
history.append(state)
def undo_move():
"""一手前の状態に戻す"""
global board, current_player, game_running, history
if current_player == AI_PLAYER:
messagebox.showinfo("警告", "AIの手番中はUndoできません。")
return
if len(history) <= 1:
messagebox.showinfo("警告", "初期状態のため、これ以上戻せません。")
return
# プレイヤーとAIの2手分を戻す
if len(history) >= 2:
history.pop() # AIの手
history.pop() # プレイヤーの手
else:
# 初期盤面直後の履歴の場合
history.pop()
prev_board, prev_player = history[-1]
board = [row[:] for row in prev_board]
current_player = prev_player
game_running = True
draw_board()
show_hint()
if current_player == AI_PLAYER and game_running:
root.after(500, make_ai_move)
# --- GUIメイン関数 ---
def create_board_ui():
global root, canvas, board, score_label, turn_label
root = tk.Tk()
root.title(f"オセロゲーム (AI深さ: {SEARCH_DEPTH})")
# --- UI中央配置のための改善 ---
# 1. すべての要素を保持するメインフレームを作成
main_frame = tk.Frame(root)
# 2. main_frameを親ウィンドウの中央に配置
# expand=True で利用可能なスペースを広げ、anchor='center' で中央に寄せます。
main_frame.pack(expand=True, anchor='center')
# --------------------------------
# 3. ボード(キャンバス)を main_frame の中に配置 (親を root から main_frame に変更)
canvas = tk.Canvas(main_frame, width=WINDOW_SIZE, height=WINDOW_SIZE, bg="green")
canvas.pack(side=tk.LEFT)
# 4. スコアパネル(サイドパネル)を main_frame の中に配置 (親を root から main_frame に変更)
score_frame = tk.Frame(main_frame, width=SIDE_PANEL_WIDTH)
score_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=10, pady=10)
# --- スコアとターン表示 ---
score_label = tk.Label(score_frame, text="スコア", font=("Arial", 16))
score_label.pack(pady=20)
turn_label = tk.Label(score_frame, text="ターン", font=("Arial", 14))
turn_label.pack(pady=10)
# --- ボタン ---
undo_button = tk.Button(score_frame, text="一手戻る (Undo)", command=undo_move,
height=2, bg="#B0C4DE", fg="black")
undo_button.pack(pady=20)
hint_button = tk.Button(score_frame, text="ヒント表示", command=show_hint,
height=2, bg="#FFFACD", fg="black")
hint_button.pack(pady=10)
# --- ゲームの起動 ---
board = create_initial_board()
canvas.bind("<Button-1>", handle_click)
# 初期状態を履歴に保存
save_state()
draw_board()
show_hint()
def on_closing():
root.destroy()
sys.exit()
root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()
if __name__ == "__main__":
create_board_ui()
次回はナンプレ(数独)のアプリを作成と動作確認を予定しています。楽しみにお待ちください。