はじめに
Python初学者のQiita初投稿になります。
拙いコードで見やすいとは言い難いですが、温かい目で見守ってくださると幸いです。
動作環境
Windows 11 Enterprise
Pythonのバージョン: 3.10.11
使用IDE:Thonny
コード
早速載せていきます。ファイルと参考資料はコードより下に添付してあります
import tkinter as tk
import random
import time
import winsound
win_flg = 0
hata_delete_flg = 0
reset_button_flg = 1
hatabtn_cnt = 0
unchis_positions = set()
unchi_sum = 12
buttons = {}
labels = {}
btn_hata = None
hatabuttons = {}
gameover_flg = 0
opened_positions = hatabuttons_set = unchis_positions_set = non_unchi_positions = set()
cvs = None
btn_hata_mode = None
elapsed_time = 0
running = 0
best_time = 0
win_streak = 0
best_time_label = None
game_cnt = 0
win_streak_label = None
flag_rest = 12
flag_rest_label = None
score = 0
score_label = None
best_score = 0
best_score_label = None
value_score = 0
reset_button = None
hatabuttons_cnt = 0
# マスをクリックしたときの関数
def masu_click(y, x):
global hatabtn_cnt, gameover_flg, flag_rest_label, flag_rest, hatabuttons_cnt
if gameover_flg == 0 and elapsed_time == 0:
start_stopwatch()
if hatabtn_cnt == 0:
if (x, y) in unchis_positions:
unchi_label = tk.Label(root, image=i_unchi)
unchi_label.grid(row=y, column=x)
labels[(x, y)] = unchi_label
buttons[(x, y)].config(state=tk.DISABLED)
buttons[(x, y)].grid_forget()
# 非同期処理
winsound.PlaySound("bakuha.wav", winsound.SND_FILENAME | winsound.SND_ASYNC)
if gameover_flg == 0:
game_over()
else:
if gameover_flg == 0:
open_rensa_0(y, x)
elif hatabtn_cnt == 1:
if flag_rest > 0 and win_flg == 0:
hata_button = tk.Button(root, image=i_hata, command=lambda:break_hatabutton(x, y))
hata_button.grid(row=y, column=x)
hatabuttons[(x, y)] = hata_button
flag_rest -= 1
flag_rest_label.config(text=f"FLAG : {flag_rest}")
hatabuttons_cnt += 1
# クリア判定
completed_judg()
# 旗ボタンが押されたときに旗をひっこめる関数
def break_hatabutton(x, y):
global flag_rest_label, flag_rest
if (x, y) in hatabuttons:
hatabuttons[(x, y)].grid_forget()
del hatabuttons[(x, y)]
flag_rest += 1
flag_rest_label.config(text=f"FLAG : {flag_rest}")
# うんちのマス以外を開けた時に周りのうんちの数を表示して、0のときは周りの8マス開けることを繰り返す関数
def open_rensa_0(y, x):
global opened_positions
unchis_cnt = 0
#周囲のうんちを数える処理
for dy in [-1, 0, 1]:
for dx in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
nx, ny = x + dx, y + dy
if 0 <= nx < 10 and 0 <= ny < 10:
if (nx, ny) in unchis_positions:
unchis_cnt += 1
if unchis_cnt == 0:
suuji_label = tk.Label(root, image=i_0)
suuji_label.grid(row=y, column=x)
labels[(x, y)] = suuji_label
buttons[(x, y)].config(state=tk.DISABLED)
buttons[(x, y)].grid_forget()
opened_positions.add((x, y))
# 周囲のマスを再帰的に開く処理
for dy in [-1, 0, 1]:
for dx in [-1, 0, 1]:
if dx == 0 and dy == 0:
continue
nx, ny = x + dx, y + dy
# 0 <= nx < 10 and 0 <= ny < 10でマスの場外にnxとnyがないかの確認
if 0 <= nx < 10 and 0 <= ny < 10 and (nx, ny) not in labels:
# 関数を再帰的に呼び出す
open_rensa_0(ny, nx)
else:
suuji_label = tk.Label(root, image=[i_0, i_1, i_2, i_3, i_4, i_5, i_6, i_7, i_8][unchis_cnt])
suuji_label.grid(row=y, column=x)
labels[(x, y)] = suuji_label
buttons[(x, y)].config(state=tk.DISABLED)
buttons[(x, y)].grid_forget()
if gameover_flg == 0:
opened_positions.add((x, y))
# マスとリセットボタンと旗を立てる状態にするボタンを作成する関数
def masu_create():
global btn_hata, btn_hata_mode, stopwatch_label, start_button, reset_button, best_time_label, game_cnt, win_streak, win_streak_label, flag_rest, flag_rest_label, score, score_label, best_score, best_score_label
# マスを作成する部分
for y in range(10):
for x in range(10):
btn = tk.Button(root, width=5, height=2, relief=tk.RIDGE, command=lambda y=y, x=x: masu_click(y, x))
btn.grid(row=y, column=x)
buttons[(x, y)] = btn
#旗に関するものを作成する部分
btn_hata = tk.Button(root, image=i_1, width=41, height=36, command=hata_set)
btn_hata.place(x=500, y=25)
btn_hata_mode = tk.Label(root, text="MODE:OPEN", bg="gray")
btn_hata_mode.place(x=485, y=70)
# ゲームのオプション的要素を作成する部分
stopwatch_label = tk.Label(root, text="TIME: 0 s", bg="gray", font=("Helvetica", 14))
stopwatch_label.place(x=475, y=150)
best_time_label = tk.Label(root, text=f"BEST TIME: {best_time} s", bg="gray", font=("Helvetica", 10))
best_time_label.place(x=475, y=200)
win_streak_label = tk.Label(root, text=f"WIN STREAK: {win_streak}", bg="gray", font=("Helvetica", 10))
win_streak_label.place(x=475, y=230)
flag_rest_label = tk.Label(root, text=f"FLAG : {flag_rest}", bg="gray", font=("Helvetica", 10))
flag_rest_label.place(x=475, y=270)
score_label = tk.Label(root, text=f"SCORE : {score}", bg="gray", font=("HElvetica", 10))
score_label.place(x=475, y=300)
best_score_label = tk.Label(root, text=f"BEST SCORE : {best_score}", bg="gray", font=("HElvetica", 10))
best_score_label.place(x=475, y=320)
# うんちの位置をランダムに設定
while len(unchis_positions) < unchi_sum:
unchi_x = random.randint(0, 9)
unchi_y = random.randint(0, 9)
if (unchi_x, unchi_y) not in unchis_positions:
unchis_positions.add((unchi_x, unchi_y))
reset_button = tk.Button(root, text="RESET", command=reset_game)
reset_button.place(x=500, y=100)
# 旗を出す状態とマスを開ける状態の切り替えを行う関数
def hata_set():
global hatabtn_cnt, btn_hata, btn_hata_mode
hatabtn_cnt += 1
if hatabtn_cnt == 1:
btn_hata.config(image=i_hata)
btn_hata_mode["text"] = "MODE:FLAG"
for (hx, hy) in hatabuttons.keys():
hatabuttons[(hx, hy)].config(state=tk.NORMAL)
elif hatabtn_cnt == 2:
hatabtn_cnt = 0
btn_hata.config(image=i_1)
btn_hata_mode["text"] = "MODE:OPEN"
for (hx, hy) in hatabuttons.keys():
hatabuttons[(hx, hy)].config(state=tk.DISABLED)
# ストップウォッチで秒数を数えて表示させる関数
def update_stopwatch():
global elapsed_time
if running == 1:
elapsed_time += 1
if elapsed_time > 999:
elapsed_time = 999
stopwatch_label.config(text=f"TIME: {elapsed_time} +s")
else:
stopwatch_label.config(text=f"TIME: {elapsed_time} s")
root.after(1000, update_stopwatch)
# ストップウォッチを開始する関数
def start_stopwatch():
global running
running = 1
update_stopwatch()
def stop_stopwatch():
global running
running = 0
# うんちを開けた時に実行される関数
def game_over():
global gameover_flg, win_streak, win_streak_label
gameover_flg = 1
stop_stopwatch()
time.sleep(1)
for (hx, hy) in list(hatabuttons.keys()):
hatabuttons[(hx, hy)].config(state=tk.NORMAL)
break_hatabutton(hx, hy)
# 全てのマスを開ける
for y in range(10):
for x in range(10):
if (x, y) in unchis_positions:
unchi_label = tk.Label(root, image=i_unchi)
unchi_label.grid(row=y, column=x)
labels[(x, y)] = unchi_label
else:
open_rensa_0(y, x)
win_streak = 0
win_streak_label.config(text=f"WIN STREAK: {win_streak}")
# クリアの状態かどうか判定する関数
def completed_judg():
hatabuttons_set = set(hatabuttons.keys())
unchis_positions_set = set(unchis_positions)
# 全てのマスを計算
all_positions = set((x, y) for x in range(10) for y in range(10))
non_unchi_positions = all_positions - unchis_positions
if hatabuttons_set == unchis_positions_set or opened_positions == non_unchi_positions:
stop_stopwatch()
all_hata = len(hatabuttons_set)
if all_hata > 0:
hata_delete_flg = 1
else:
hata_delete_flg = 0
completed_run()
# クリアの処理を行う関数
def completed_run():
global win_flg, cvs, best_time, elapsed_time, best_time_label, win_streak, win_streak_label, score, best_score, elapsed_time, reset_button_flg, reset_button, hatabuttons_cnt
if win_flg == 0:
reset_button_flg = 0
if reset_button_flg == 0:
reset_button.config(state=tk.DISABLED)
cvs = tk.Canvas(root, width=i_win.width()+10, height=i_win.height()+10)
cvs.place(x=42, y=165)
cvs.create_image(183, 25, image=i_win)
win_flg = 1
if best_time == 0 or elapsed_time < best_time:
best_time_label.place_forget()
best_time = elapsed_time
best_time_label = tk.Label(root, text=f"BEST TIME: {best_time} s", bg="gray", font=("Helvetica", 10))
best_time_label.place(x=475, y=200)
win_streak += 1
win_streak_label.config(text=f"WIN STREAK: {win_streak}")
#スコアの計算
if hatabuttons_cnt == 0:
score += 1000
if elapsed_time <= 60:
score += 1500
elif elapsed_time > 60:
score += 750
if win_streak == 1:
score += 100
elif win_streak == 2:
score += 200
# 連勝数が3なら
elif win_streak == 3:
score += 300
elif win_streak == 4:
score += 400
elif win_streak >= 5:
score += 600
if best_time <= 35:
score += 300
else:
score += 150
score_output()
# スコア表示のための関数
def score_output():
global best_score_label, score_label, value_score, score, best_score, win_flg, reset_button_flg
if win_flg == 1:
if value_score <= score:
score_label.config(text=f"SCORE : {value_score}")
else:
if score > best_score:
best_score = score
best_score_label.config(text=f"BEST SCORE : {best_score}")
reset_button_joutai()
if value_score <= score:
value_score += 10
if reset_button_flg == 0:
root.after(10, score_output)
# リセットボタンの状態を変えるためのフラグ
def reset_button_joutai():
global reset_button_flg, reset_button
reset_button_flg = 1
if reset_button_flg == 1:
reset_button.config(state=tk.NORMAL)
# リセットボタンが押されたときに実行される関数
def reset_game():
global gameover_flg, unchis_positions, hatabtn_cnt, buttons, labels, hatabuttons, hatabuttons_set, unchis_positions_set, opened_positions, non_unchi_positions, hata_delete_flg, cvs, win_flg, btn_hata_mode, elapsed_time, running, best_time_label, win_streak, win_streak_label, flag_rest, flag_rest_label, score, score_label, hatabuttons_cnt, value_score, game_cnt
# 全ての旗を消す(なぜか表示では消えてない)
if hata_delete_flg == 1:
for (hx, hy) in list(hatabuttons.keys()):
break_hatabutton(hx, hy)
if win_flg == 1:
cvs.place_forget()
win_flg = 0
else:
win_streak = 0
win_streak_label.place_forget()
game_cnt += 1
# ゲームの状態をリセット
win_flg = 0
gameover_flg = 0
hatabtn_cnt = 0
elapsed_time = 0
running = 0
score = 0
hatabuttons_cnt = 0
value_score = 0
flag_rest = 12
unchis_positions.clear()
hatabuttons_set.clear()
unchis_positions_set.clear()
opened_positions.clear()
non_unchi_positions.clear()
stopwatch_label.place_forget()
best_time_label.place_forget()
flag_rest_label.place_forget()
score_label.place_forget()
# すべてのマスのボタンをリセット
for (x, y) in list(buttons.keys()):
buttons[(x, y)].grid_forget()
buttons.clear()
# 開いたうんちの位置を保存したものをリセット
for label in list(labels.values()):
label.grid_forget()
labels.clear()
# 旗ボタンの位置を保存したものの中身を全て消す
hatabuttons.clear()
# 新しく始めるためにマスを改めて作成する
masu_create()
root = tk.Tk()
root.title("UnchiSweeper")
root.geometry("700x415")
root.configure(bg="gray")
# 画像の読み込み
i_0 = tk.PhotoImage(file="0_haiiro.png")
i_1 = tk.PhotoImage(file="1_blue.png")
i_2 = tk.PhotoImage(file="2_green.png")
i_3 = tk.PhotoImage(file="3_red.png")
i_4 = tk.PhotoImage(file="4_darkblue.png")
i_5 = tk.PhotoImage(file="5_murasaki.png")
i_6 = tk.PhotoImage(file="6_purple.png")
i_7 = tk.PhotoImage(file="7_tyairo.png")
i_8 = tk.PhotoImage(file="8_pink.png")
i_hata = tk.PhotoImage(file="hata.png")
i_unchi = tk.PhotoImage(file="unchi.png")
i_win = tk.PhotoImage(file="gameclear.png")
masu_create()
root.mainloop()
実行画面
画像と音声ファイル
i_0 | i_1 | i_2 | i_3 | i_4 | i_5 |
---|---|---|---|---|---|
i_6 | i_7 | i_8 | i_hata | i_unchi | |
音声ファイル
bakuha.wav
私が使用したものはこちらのリンクの爆発01をwav変換したものですが、パスさえあっていれば大丈夫ですので、お好みの音声ファイル(wav形式)をお使いください。
参考資料
おわりに
今回はマインスイーパー風のゲームをTkinterで作ってみました。
この記事で1人でもTkinterに触れていただく機会となりましたら幸いです。
classなどを使ってみたいという気もありますが、未だに私のclassについての理解ができていないので今回の使用は控えさせていただきました。ご了承ください。
またTkinterを使用したGUIを作りたいと思っておりますので完成し、気が向き次第投稿します。
最後までお読みいただきありがとうございました。