Mac って何?それって美味しいの?

マイコンと呼ばれた時代から現在まで、使用記から何から何でも。

Python知識ゼロから始めるchatGPTを利用した超簡単ゲームプログラミング(応用編)

 

yummac.hatenablog.jp

 

前回作成?生成?した、CUIで遊べるカードゲームをGUI画面で遊べるようにする。

Pythonの知識がない私が頼るのはchatGPT様だ。

とりあえずわがまま言ってみる。

「このゲームをGUI画面で遊べるようにコードを作成してください」

回答:

以下は、Tkinterを使用して作成したカードバトルゲームのGUIの一部です。このコードを基にして、GUI画面で遊べるカードバトルゲームを構築することができます。

import tkinter as tk
import random

# カードデッキを作成
suits = ['ハート', 'ダイヤ', 'スペード', 'クラブ']
ranks = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'ジャック', 'クイーン', 'キング', 'エース']
deck = [{'スート': suit, 'ランク': rank} for suit in suits for rank in ranks]

# プレイヤーを作成
player1 = {'名前': 'プレイヤー1', '手札': []}
player2 = {'名前': 'コンピュータ', '手札': []}

# ゲームを初期化
def initialize_game():
    player1['手札'] = []
    player2['手札'] = []
    for _ in range(5):
        player1['手札'].append(deck.pop(random.randint(0, len(deck) - 1)))
        player2['手札'].append(deck.pop(random.randint(0, len(deck) - 1)))
    update_display()

# ゲームの進行
def play_round():
    player1_card = player1['手札'].pop(random.randint(0, len(player1['手札']) - 1))
    player2_card = player2['手札'].pop(random.randint(0, len(player2['手札']) - 1))
    winner = battle(player1_card, player2_card)
    if winner == player1:
        result_label.config(text=f"{player1['名前']}の勝利!")
    elif winner == player2:
        result_label.config(text=f"{player2['名前']}の勝利!")
    else:
        result_label.config(text="引き分け")
    update_display()

# カードのバトル
def battle(player1_card, player2_card):
    rank_order = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'ジャック', 'クイーン', 'キング', 'エース']
    if rank_order.index(player1_card['ランク']) > rank_order.index(player2_card['ランク']):
        return player1
    elif rank_order.index(player1_card['ランク']) < rank_order.index(player2_card['ランク']):
        return player2
    else:
        return None

# 画面の更新
def update_display():
    player1_label.config(text=f"{player1['名前']}の手札: {', '.join([card['ランク'] for card in player1['手札']])}")
    player2_label.config(text=f"{player2['名前']}の手札: {', '.join([card['ランク'] for card in player2['手札']])}")

# Tkinterウィンドウを作成
root = tk.Tk()
root.title("カードバトルゲーム")

# ボタンとラベルを配置
start_button = tk.Button(root, text="ゲームを始める", command=initialize_game)
start_button.pack()
play_button = tk.Button(root, text="ラウンドをプレイ", command=play_round)
play_button.pack()
result_label = tk.Label(root, text="")
result_label.pack()
player1_label = tk.Label(root, text="")
player1_label.pack()
player2_label = tk.Label(root, text="")
player2_label.pack()

# ゲームを初期化
initialize_game()

# Tkinterウィンドウを表示
root.mainloop()

こりゃ楽ちんだぜ!と喜んだのも束の間、実行してみるとコレジャナイ感が。

でも、これをベースにカスタマイズして作り上げるってことだね。

どんどんchatGPTに要望や質問を投げてコードをカスタマイズしていく。

「手札のカードを横に並べて表示し、その中からプレイヤーが選んでバトルするようにしたい」

「プレイヤー1のカードをマウスでクリックして選択できるようにしたい」

「カードに画像を表示したい」

カード画像はStableDiffusionで生成しますが、サイズが小さいとコレジャナイ画像が生成されてしまうので、512 x 768で生成しました。

ゲームで表示するにはデカすぎるので縮小します。

画像変形ツールを使って縮小しておくのが常套手段ですが、

ここはあえてPythonでやります。

「カードの画像サイズを幅128、高さ192にしたい」

回答:

〜 Pillow(PIL)ライブラリを使用して画像をリサイズすることができます。

ここで、生成されたコードの動作には、追加のライブラリをインストールする作業が必要となりました。

Windowsならコマンドプロンプトから、Macならターミナルから、Pillowライブラリをインストールします。

pip install Pillow

うまくいかないときはググってみましょう。

まだまだchatGPT様のお世話になります。

canvasの背景色を深緑にしたい」

「カード画像5枚横に並べて表示し、クリックされたカード画像に外枠を表示、前にクリックし表示していた枠は消すようにしてください」

「それぞれのカード画像の下にランクを表示したい」

・・・

そんなこんなで出来上がった「ドラゴンカードゲーム」

ドラゴンカードゲーム
import tkinter as tk
from tkinter import messagebox
from tkinter import PhotoImage
from PIL import Image, ImageTk  # Pillowライブラリをインポート
import random

# カードデッキを作成
suits = ['ハート', 'ダイヤ', 'スペード', 'クラブ']
ranks = ['ドラコ', 'リザードマン', 'ドレイク', 'ヒドラ', 'サーペント', 'ワイバーン', 'アジュールドラゴン', 'クリムゾンドラゴン', '西洋ドラゴン', '東洋ドラゴン']
deck = [{'スート': suit, 'ランク': rank} for suit in suits for rank in ranks]
card_width = 128
card_height = 192
card_interval = 20
card_p1_y = 310
card_p2_y = 40

# プレイヤーを作成
player1 = {'名前': 'あなた', '手札': []}
player2 = {'名前': 'コンピュータ', '手札': []}

#プレイヤー1カード(表示用)
player1_cards = {}

# 選択されたカードを格納する変数
selected_card = None

# 外枠の色を指定
frame_color = "red"

# 選択されたカードのインデックスを保持する変数
selected_card_index = None

# ゲームの設定
rounds = 5  # ラウンド数を指定
round_num = 0
player1_score = 0
player2_score = 0

# 各ランクに対応する画像ファイルを辞書に格納
original_images = {}
card_images = {}
text_objects = []

# ゲームを初期化
def initialize_game():
    global round_num
    global player1_score
    global player2_score
    global player1_cards
    round_num = 0
    player1_score = 0
    player2_score = 0
    player1_cards = []
    player1['手札'] = []
    player2['手札'] = []
    deck = []
    deck = [{'スート': suit, 'ランク': rank} for suit in suits for rank in ranks]
    for _ in range(5):
        player1['手札'].append(deck.pop(random.randint(0, len(deck) - 1)))
        player2['手札'].append(deck.pop(random.randint(0, len(deck) - 1)))

    # 選択されたカードをクリア
    global selected_card
    selected_card = None
    global selected_card_index
    canvas.delete(selected_card_index)
    selected_card_index = None
    global text_objects
    for text_object in text_objects:
        canvas.delete(text_object)
    text_objects = []

    #update_display()
    # プレイヤー1の手札を描画
    canvas.delete("player1_hand")
    x = card_interval
    for card in player1['手札']:
        draw_card(canvas, x, card_p1_y, card, 1)
        x += (card_width + card_interval)

    # プレイヤー2(コンピュータ)の手札を描画
    canvas.delete("player2_hand")
    x = card_interval
    for card in player2['手札']:
        draw_card(canvas, x, card_p2_y, card, 0)
        x += (card_width + card_interval)
    
    result_label1.config(text="")
    result_label2.config(text="")
    result_label3.config(text="")

# ゲームの進行
def play_round():
    global player1_score
    global player2_score
    global round_num

    if round_num > rounds:
        messagebox.showinfo("エラー", "「ゲームを始める」をクリックしてください")
        return

    # プレイヤー1(人間)がカードを選ぶ
    global selected_card  # グローバル変数として選択されたカードを保持
    if selected_card is None:
        messagebox.showinfo("エラー", "カードを選択してください。")
        return
    player1_card = selected_card

    round_num += 1

    # プレイヤー2(コンピュータ)が一番強いカードを選ぶ
    player2_card = select_strongest_card(player2['手札'])

    result_label1.config(text=f"{player2['名前']}のカード : {player2_card['ランク']} " + "★" * (ranks.index(player2_card['ランク']) + 1))
    result_label2.config(text=f"{player1['名前']}のカード : {player1_card['ランク']} " + "★" * (ranks.index(player1_card['ランク']) + 1))
    winner = battle(player1_card, player2_card)
    if winner == player1:
        player1_score += 1
        result_label3.config(text=f"{player1['名前']}の勝利!")
    elif winner == player2:
        player2_score += 1
        result_label3.config(text=f"{player2['名前']}の勝利!")
    else:
        result_label3.config(text="引き分け")

    if round_num >= rounds:
        round_num += 1
        # 最終結果を表示
        if player1_score > player2_score:
            messagebox.showinfo("ゲーム終了",f"結果発表\n{player1['名前']} : {player1_score}勝\n{player2['名前']} : {player2_score}勝\n{player1['名前']}の勝利!")
        elif player1_score < player2_score:
            messagebox.showinfo("ゲーム終了",f"結果発表\n{player1['名前']} : {player1_score}勝\n{player2['名前']} : {player2_score}勝\n{player2['名前']}の勝利!")
        else:
            messagebox.showinfo("ゲーム終了",f"結果発表\n{player1['名前']} : {player1_score}勝\n{player2['名前']} : {player2_score}勝\n引き分け!")
        return
    
    # プレイヤー1の手札から選択されたカードを削除
    player1['手札'].remove(selected_card)
    
    # プレイヤー2の手札から選択されたカードを削除
    player2['手札'].remove(player2_card)

    # カードをデッキからランダムに選び、手札に加える
    player1['手札'].append(deck.pop(random.randint(0, len(deck) - 1)))
    player2['手札'].append(deck.pop(random.randint(0, len(deck) - 1)))

    # 選択されたカードをクリア
    selected_card = None
    global selected_card_index
    canvas.delete(selected_card_index)
    selected_card_index = None
    global text_objects
    for text_object in text_objects:
        canvas.delete(text_object)
    text_objects = []

    # プレイヤー1の手札を描画
    canvas.delete("player1_hand")
    x = card_interval
    for card in player1['手札']:
        draw_card(canvas, x, card_p1_y, card, 1)
        x += (card_width + card_interval)

    # プレイヤー2(コンピュータ)の手札を描画
    canvas.delete("player2_hand")
    x = card_interval
    for card in player2['手札']:
        draw_card(canvas, x, card_p2_y, card, 0)
        x += (card_width + card_interval)

def battle(player1_card, player2_card):
    global ranks
    if ranks.index(player1_card['ランク']) > ranks.index(player2_card['ランク']):
        return player1
    elif ranks.index(player1_card['ランク']) < ranks.index(player2_card['ランク']):
        return player2
    else:
        return None

def select_strongest_card(player_hand):
    global ranks
    strongest_card = None
    for card in player_hand:
        if not strongest_card or ranks.index(card['ランク']) > ranks.index(strongest_card['ランク']):
            strongest_card = card
    return strongest_card

def draw_card(canvas, x, y, card, sw):
    suit = card['スート']
    rank = card['ランク']
    rank_i = "★" * (ranks.index(rank) + 1)
    card_text = f"{rank}\n{rank_i}"

    # カード画像を配置
    card_object = canvas.create_image(x, y, anchor=tk.NW, image=card_images[rank])
    if sw > 0:
        canvas.tag_bind(card_object, "", select_card)

    # カードテキストを配置
    text_object = canvas.create_text(x, (y + card_height + 5), text=card_text, anchor="nw", fill="white", font=("Helvetica", 12))
    text_objects.append(text_object)

# クリックされたカードの処理
def select_card(event):
    global selected_card_index
    # 前に選択されたカードの外枠を削除
    if selected_card_index is not None:
        canvas.delete(selected_card_index)
    
    global selected_card
    x, y = event.x, event.y  # クリックされた位置のx座標とy座標を取得
    for card in player1['手札']:
        card_x, card_y = get_card_position(player1['手札'].index(card))
        if card_x <= x <= card_x + card_width and card_y <= y <= card_y + card_height:
            selected_card = card
            break

    # クリックされたカードの外枠を表示
    selected_card_index = canvas.create_rectangle((card_x - 2), (card_y - 2), (card_x + card_width + 2), (card_y + card_height + 2), outline=frame_color, width=2)

def get_card_position(index):
    x = card_interval + index * (card_width + card_interval)
    return x, card_p1_y

# Tkinterウィンドウを作成
root = tk.Tk()
root.title("カードバトルゲーム")

# Canvasを作成
canvas = tk.Canvas(root, width=800, height=550, bg="dark green")
canvas.pack()

# デフォルトカードに対応する画像ファイルを読み込む
default_card_image = Image.open('default.png')

# Stable Diffusion で生成した画像のサイズが大きいため縮小します
# 幅と高さ比2:3で512*768とかで生成(サイズ小さいとコレジャナイ画像になるため)
# 各ランクの画像ファイルを読み込み、Tkinter PhotoImageオブジェクトに変換して格納
for rank in ranks:
    image_file = f"{rank}.png"  # ランクごとの画像ファイル名を生成
    original_images[rank] = Image.open(image_file)

    # 対応するランクの画像を辞書から取得
    original_image = original_images.get(rank)
    
    if original_image:
        # 画像を指定のサイズにリサイズ
        resized_image = original_image.resize((card_width, card_height), Image.LANCZOS)

        # リサイズした画像をTkinter PhotoImageオブジェクトに変換
        card_images[rank] = ImageTk.PhotoImage(resized_image)
    else:
        # デフォルト画像を指定のサイズにリサイズ
        resized_image = default_card_image.resize((card_width, card_height), Image.LANCZOS)

        # リサイズした画像をTkinter PhotoImageオブジェクトに変換
        card_images[rank] = ImageTk.PhotoImage(resized_image)

#テキスト配置
canvas.create_text(card_interval, card_p1_y - 20, text=f"{player1['名前']}のカード", anchor="w", fill="white", font=("Helvetica", 14))
canvas.create_text(card_interval, card_p2_y - 20, text=f"{player2['名前']}のカード", anchor="w", fill="white", font=("Helvetica", 14))

# ボタンとラベルを配置
play_button = tk.Button(root, text="選んだカードで対決!", command=play_round)
play_button.pack()
start_button = tk.Button(root, text="ゲームを始める", command=initialize_game)
start_button.pack()
result_label1 = tk.Label(root, text="")
result_label1.pack()
result_label2 = tk.Label(root, text="")
result_label2.pack()
result_label3 = tk.Label(root, text="")
result_label3.pack()

# ゲームを初期化
initialize_game()

# Tkinterウィンドウを表示
root.mainloop()

生成されたコードを付け加えたり変更したりで大変だった感じはあるが

これを知識が無いところから作成することを考えたら

もう、素晴らしいね。

出来上がったコードは、まだ変なところとかあったり、

コンピュータのカードを隠せるようにするなど、改良の余地はあるけど、

なんか、標題の目的を達成できた感がある。

今回はドラゴンで男の子向けだが、女の子向けの画像も用意したいなと思っている。

オトナ向けは・・・やめておこう。