トランプゲーム
ここで作成するのはPC上で楽しめる、トランプゲームの定番「神経衰弱ゲーム」のアプリです。
今回Pythonの実行は、Tkinterを使ってプログラムを作成します。
Tkinterは、Pythonに標準搭載されているGUIライブラリで、ボタンやテキストボックスなどの画面要素を持つGUIアプリケーションを簡単に作成できます。Tcl/TkというGUIツールキットのPython版であり、クロスプラットフォーム対応でWindows、macOSなど動作します。
トランプの画像を用意する
kenneyというサイト(https://kenney.nl/)が配布している無料素材を使います。
「GetFreeAsets」からCardで検索し「BoardGamePack」をクリックします。
Downloadボタンを押し「Continue without donating…」を押すとダウンロードがはじまります。
「kenny_boardgame-pack.zip」というファイルがダウンロードされます。このzipファイルを展開します。今回はこの中の「ping」フォルダーにある「Card」フォルダー中の画像を利用します。
今回使うのはクラブと、ハートを使います。
プログラムしやすいように、画像のエースのファイル名をAから1にリネームします。
CardClubsA.pngをCardClubs1.pngに変更
CardHeartsA.pngをCardHearts1.pngに変更
これらの画像をCardsというフォルダを作って入れておきます。
プログラムの.pyファイルは、同じ場所で実行します。
Ping画像を読み込む
今回Pythonの実行は、Tkinterを使ってプログラムを作成します。このため最初に「import tkinter as tk」を記入します。以下はPNG画像を読み込んでCanvasウイジェットに表示するプログラムです。
import tkinter as tk
root = tk.Tk()
root.title('PhotoImage')
root.geometry('250x220')
# カードの裏面の画像ファイルを読み込む
card_back_img = tk.PhotoImage(
file='Cards\cardBack_blue1.png')
canvas_1 = tk.Canvas(
root,
width=240,
height=200)
canvas_1.pack()
canvas_1.create_image(120, 100,
image=card_back_img,
anchor='center')
root.mainloop()
以上のプログラムを「photoimage.py」として保存します。
神経衰弱プログラム
10枚のカードを使って神経衰弱ゲームができます。今回使うのはクラブのA~5と、ハートのA~5を使います。
import tkinter as tk
from tkinter import messagebox
import random
import re
# カードの画像ファイル名
files = ['cardClubs1.png',
'cardClubs2.png',
'cardClubs3.png',
'cardClubs4.png',
'cardClubs5.png',
'cardHearts1.png',
'cardHearts2.png',
'cardHearts3.png',
'cardHearts4.png',
'cardHearts5.png']
# グローバル変数
# 1枚目に選択したカードの数字
first_card = None
# 1枚目に選択したカードの番号
first_index = None
card_img = [] # カードの表面の画像
cards = [] # カードを格納
count = 0
# 初期化関数
def initialise():
global first_card
global first_index
global count
first_card = None
first_index = None
count = len(files) / 2
random.shuffle(files) # カードのシャッフル
# カードの生成
card_img.clear()
cards.clear()
for num, img_name in enumerate(files):
card_img.append(
tk.PhotoImage(file='Cards\\'+img_name))
# カードの表示にCanvasウィジェットを使用
cards.append([tk.Canvas(
root,
width=140,
height=190),
None])
card_set(num, False)
def card_set(num, flag):
if flag:
cards[num][0].delete('all')
cards[num][0].create_image(
0,
0,
anchor='nw',
image=card_img[num])
else:
cards[num][0].delete('all')
cards[num][0].create_image(
0,
0,
anchor='nw',
image=card_back_img)
cards[num][0].bind(
'<ButtonPress-1>',
lambda event: click_img(event, num))
r, n = 0, num
if num > 4:
r, n = 1, num - 5
cards[num][0].grid(row=r, column=n)
cards[num][1] = flag
# ハンドラ関数
def click_img(event, num):
global first_card
global first_index
global count
# すでに表面になっている場合は終了
if cards[num][1]:
return
# カードを表面(True)にする
card_set(num, True)
if first_card != None:
# ファイル名から数字を抜き出す
second_card = re.sub(r'\D', '', files[num])
# 同じ数字なら
if second_card == first_card:
count = count - 1
if count > 0:
messagebox.showinfo(
'Memory App',
'同じです!')
else:
messagebox.showinfo(
'Memory App',
'すべてそろいました!')
initialise()
else:
messagebox.showinfo(
'Memory App',
'違います!')
card_set(num, False)
card_set(first_index, False)
first_card, first_index = None, None
else:
first_index = num
# ファイル名から数字を抜き出す
first_card = re.sub(r'\D', '', files[num])
root = tk.Tk()
# トランプの裏面の画像ファイルを読み込む
card_back_img = tk.PhotoImage(
file='Cards\cardBack_blue1.png')
initialise()
# タイトルバーのアイコンを設定
root.iconphoto(False, card_img[0])
root.title('Memory App')
root.mainloop()10
以上のプログラムを「card10mai.py」として保存し、ダブルクリックして実行すればゲームを楽しめます。
経過時間とスコアを追加
結果としては、上段に「経過時間:01:13 スコア:3ペア」などと返します。
手順1:表示ラベルを作る
117行目「root = tk.Tk()」 のすぐ下あたりに次を追加
# タイマーとスコアを表示するラベル
status_label = tk.Label(root, text="経過時間:00:00 スコア:0ペア", font=("Arial", 16))
status_label.grid(row=0, column=0, columnspan=5, pady=10)
カード配置は row=1 から始まるように、あとで微調整します。
手順2:グローバル変数を追加
26行目あたり、グローバル変数 の下に次を追加
start_time = None
elapsed_time = 0
score = 0
timer_running = False
手順3:タイマー関数を追加
その下27行目「initialise()」 の前か後に次の関数を追加
def update_timer():
global elapsed_time
if timer_running:
elapsed_time += 1
mins, secs = divmod(elapsed_time, 60)
time_str = f"{mins:02}:{secs:02}"
status_label.config(text=f"経過時間:{time_str} スコア:{score}ペア")
root.after(1000, update_timer)
手順4:ゲーム開始時にタイマーをリセット
51行目に赤字部分のみ「update_timer() #←ここを追加」を追加する
def initialise():
global first_card, first_index, count, elapsed_time, score, timer_running
first_card = None
first_index = None
count = len(files) // 2
elapsed_time = 0
score = 0
timer_running = True
status_label.config(text="経過時間:00:00 スコア:0ペア")
random.shuffle(files)
card_img.clear()
cards.clear()
for num, img_name in enumerate(files):
card_img.append(tk.PhotoImage(file='Cards/' + img_name))
cards.append([tk.Canvas(root, width=140, height=190), None])
card_set(num, False)
update_timer() # ←ここを追加
手順5:ペアが揃ったときにスコアを更新
79行目~115行目「 click_img()」以下
下記のコードを、そのまま既存の click_img() 全体と差し替えます。
def click_img(event, num):
global first_card
global first_index
global count
global score
global timer_running
# すでに表面になっている場合は終了
if cards[num][1]:
return
# カードを表面(True)にする
card_set(num, True)
if first_card is not None:
# ファイル名から数字を抜き出す
second_card = re.sub(r'\D', '', files[num])
# 同じ数字なら
if second_card == first_card:
score += 1 # ← スコア加算
count -= 1
# 経過時間を再表示
mins, secs = divmod(elapsed_time, 60)
time_str = f"{mins:02}:{secs:02}"
status_label.config(text=f"経過時間:{time_str} スコア:{score}ペア")
if count > 0:
messagebox.showinfo('Memory App', '同じです!')
else:
timer_running = False # ← タイマー停止
messagebox.showinfo('Memory App', 'すべてそろいました!')
initialise()
else:
messagebox.showinfo('Memory App', '違います!')
card_set(num, False)
card_set(first_index, False)
first_card, first_index = None, None
else:
first_index = num
# ファイル名から数字を抜き出す
first_card = re.sub(r'\D', '', files[num])
手順6:カード配置を1行下げる
75行目、ラベルが row=0 を使うので、card_set() の配置を次のように変更します。
cards[num][0].grid(row=r+1, column=n, padx=5, pady=5)
エラー内容を確認する方法(今後のため)
次のように .py ファイルの最後の行を変えると、
ウィンドウが閉じずにエラー内容を見られます
root.mainloop()
input("Enterキーを押して終了します...")
これで、ウィンドウが閉じたあとにコンソール(黒い画面)が残るので、
「何のエラーか」がちゃんと見えるようになります。
カード数を20枚に
カードの画像を追加します。
6行目~16行目の# カードの画像ファイル名を下記のように入れ替えます。
# カードの画像ファイル名
files = ['cardClubs1.png',
'cardClubs2.png',
'cardClubs3.png',
'cardClubs4.png',
'cardClubs5.png',
'cardClubs6.png',
'cardClubs7.png',
'cardClubs8.png',
'cardClubs9.png',
'cardClubs10.png',
'cardHearts1.png',
'cardHearts2.png',
'cardHearts3.png',
'cardHearts4.png',
'cardHearts5.png',
'cardHearts6.png',
'cardHearts7.png',
'cardHearts8.png',
'cardHearts9.png',
'cardHearts10.png']
「手動マッピング(4行×5列)」にする修正
28行目~76行目「initialise() と card_set()」 の該当部分を差し替えてください。
(元のコードと差し替えるだけで動作します)
# --- initialise() の重要な修正点 ---
def initialise():
global first_card
global first_index
global count
first_card = None
first_index = None
# ここは整数に(//)
count = len(files) // 2
random.shuffle(files)
# カードの生成(既存と同じ)
card_img.clear()
cards.clear()
for num, img_name in enumerate(files):
card_img.append(
tk.PhotoImage(file='Cards/' + img_name))
cards.append([tk.Canvas(root, width=140, height=190), None])
card_set(num, False)
# --- card_set() を「手動4行対応」に変える ---
def card_set(num, flag):
if flag:
cards[num][0].delete('all')
cards[num][0].create_image(0, 0, anchor='nw', image=card_img[num])
else:
cards[num][0].delete('all')
cards[num][0].create_image(0, 0, anchor='nw', image=card_back_img)
cards[num][0].bind('<ButtonPress-1>', lambda event, i=num: click_img(event, i))
# ---------- 手動マッピング(確実) ----------
# num: 0..19 -> 4行×5列にマップする(行0は0-4、行1は5-9、行2は10-14、行3は15-19)
if 0 <= num <= 4:
r = 0; n = num
elif 5 <= num <= 9:
r = 1; n = num - 5
elif 10 <= num <= 14:
r = 2; n = num - 10
else:
r = 3; n = num - 15
# -------------------------------------------
# optional: 少し余白を付けるなら padx, pady を指定できます
cards[num][0].grid(row=r, column=n, padx=5, pady=5)
cards[num][1] = flag
コメント