【Python】tkinterでシンプルなマインスイーパーゲームを作ってみた

プログラミング
広告
広告
広告

こんにちは!今回は、Pythonを使って10×10のマインスイーパーゲームを作成しました。

Pythonの標準ライブラリであるtkinterを使用して、シンプルなゲームを作ります。
Pyhonのプログラミングの練習にもなりますので是非参考にしてみてください!

広告
広告
広告

ゲーム画面

完成したゲームの画面は以下の通りです。

初期画面
プレイ画面
ゲームオーバー画面

コード全体

完成したコードを以下に載せます。

import tkinter as tk
import random
from tkinter import messagebox

# 定数の設定
GRID_SIZE = 10  # グリッドのサイズ

# 難易度に応じた地雷の数
MINES = {
    'easy': 10,
    'normal': 20,
    'hard': 30
}

# グリッドの初期化
def create_grid():
    grid = [[' ' for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
    return grid

# 地雷の配置
def place_mines(grid, num_mines):
    mines = set()
    while len(mines) < num_mines:
        x = random.randint(0, GRID_SIZE - 1)
        y = random.randint(0, GRID_SIZE - 1)
        if (x, y) not in mines:
            mines.add((x, y))
            grid[x][y] = 'M'  # 地雷を'M'で表示
    return grid

# 地雷の数を数える
def count_mines_around(grid, x, y):
    count = 0
    for i in range(max(0, x - 1), min(GRID_SIZE, x + 2)):
        for j in range(max(0, y - 1), min(GRID_SIZE, y + 2)):
            if grid[i][j] == 'M':
                count += 1
    return count

# ヒントの配置
def place_hints(grid):
    for x in range(GRID_SIZE):
        for y in range(GRID_SIZE):
            if grid[x][y] == ' ':
                mines_count = count_mines_around(grid, x, y)
                if mines_count > 0:
                    grid[x][y] = str(mines_count)
    return grid

# セルを開く
def open_cell(x, y):
    if buttons[x][y]['state'] == 'normal':
        if grid[x][y] == 'M':
            buttons[x][y].config(text='M', bg='red')
            game_over(False)
        else:
            buttons[x][y]['state'] = 'disabled'
            if grid[x][y] != ' ':
                buttons[x][y].config(text=grid[x][y])
            else:
                buttons[x][y].config(text='', bg='lightgrey')
                for i in range(max(0, x - 1), min(GRID_SIZE, x + 2)):
                    for j in range(max(0, y - 1), min(GRID_SIZE, y + 2)):
                        if buttons[i][j]['state'] == 'normal':
                            open_cell(i, j)
            check_win()

# ゲームオーバー処理
def game_over(win):
    if win:
        messagebox.showinfo("ゲームクリア", "おめでとうございます!すべての地雷を見つけました!")
    else:
        messagebox.showinfo("ゲームオーバー", "残念!地雷を踏んでしまいました。")
    for x in range(GRID_SIZE):
        for y in range(GRID_SIZE):
            if grid[x][y] == 'M':
                buttons[x][y].config(text='M', bg='red')
            buttons[x][y]['state'] = 'disabled'

# ゲームクリアのチェック
def check_win():
    for x in range(GRID_SIZE):
        for y in range(GRID_SIZE):
            if grid[x][y] != 'M' and buttons[x][y]['state'] == 'normal':
                return
    game_over(True)

# GUIの設定
def setup_gui(root):
    for x in range(GRID_SIZE):
        row = []
        for y in range(GRID_SIZE):
            button = tk.Button(root, width=2, height=1, command=lambda x=x, y=y: open_cell(x, y))
            button.grid(row=x+1, column=y)  # 行を1つ下げる
            row.append(button)
        buttons.append(row)
    # リスタートボタンを追加
    restart_button = tk.Button(root, text="リスタート", command=restart_game)
    restart_button.grid(row=GRID_SIZE+1, columnspan=GRID_SIZE)

# ゲームの再スタート
def restart_game():
    global grid, buttons, num_mines
    for x in range(GRID_SIZE):
        for y in range(GRID_SIZE):
            buttons[x][y].destroy()
    buttons = []
    grid = create_grid()
    grid = place_mines(grid, num_mines)
    grid = place_hints(grid)
    setup_gui(root)

# 難易度設定
def set_difficulty(difficulty):
    global num_mines
    num_mines = MINES[difficulty]
    restart_game()

# メイン関数
def main():
    global grid, buttons, root, num_mines
    num_mines = MINES['normal']  # デフォルトの難易度をnormalに設定
    grid = create_grid()
    grid = place_mines(grid, num_mines)
    grid = place_hints(grid)

    root = tk.Tk()
    root.title("マインスイーパー")

    buttons = []

    # 難易度選択ボタンの追加
    tk.Button(root, text="Easy", command=lambda: set_difficulty('easy')).grid(row=0, column=0, columnspan=3, sticky='ew')
    tk.Button(root, text="Normal", command=lambda: set_difficulty('normal')).grid(row=0, column=3, columnspan=3, sticky='ew')
    tk.Button(root, text="Hard", command=lambda: set_difficulty('hard')).grid(row=0, column=6, columnspan=4, sticky='ew')

    setup_gui(root)

    root.mainloop()

if __name__ == "__main__":
    main()

コードの解説

インポートと定数の設定

  • tkinterとmessageboxをインポートしてGUIを作成し、メッセージボックスを使用できるようにします。
  • randomをインポートして地雷のランダム配置に使用します。
  • GRID_SIZEはグリッドのサイズ(10×10)を設定し、NUM_MINESは地雷の数を設定します。
  • MINESの中に難易度ごとに地雷の数を入れています。
import tkinter as tk
import random
from tkinter import messagebox

# 定数の設定
GRID_SIZE = 10  # グリッドのサイズ
NUM_MINES = 20  # 地雷の数

# 難易度に応じた地雷の数
MINES = {
    'easy': 10,
    'normal': 20,
    'hard': 30
}

グリッドの初期化

create_grid関数は10×10のグリッドを作成し、各セルを空白で初期化します。

def create_grid():
    grid = [[' ' for _ in range(GRID_SIZE)] for _ in range(GRID_SIZE)]
    return grid

地雷の配置

place_mines関数はグリッドにランダムに地雷を配置します。地雷は'M'で表示されます。

def place_mines(grid):
    mines = set()
    while len(mines) < NUM_MINES:
        x = random.randint(0, GRID_SIZE - 1)
        y = random.randint(0, GRID_SIZE - 1)
        if (x, y) not in mines:
            mines.add((x, y))
            grid[x][y] = 'M'  # 地雷を'M'で表示
    return grid

地雷の数を数える

count_mines_around関数は指定されたセルの周囲にある地雷の数を数えます。

def count_mines_around(grid, x, y):
    count = 0
    for i in range(max(0, x - 1), min(GRID_SIZE, x + 2)):
        for j in range(max(0, y - 1), min(GRID_SIZE, y + 2)):
            if grid[i][j] == 'M':
                count += 1
    return count

ヒントの配置

place_hints関数は地雷でないセルに対して、周囲の地雷の数を計算し、その数をセルに設定します。

def place_hints(grid):
    for x in range(GRID_SIZE):
        for y in range(GRID_SIZE):
            if grid[x][y] == ' ':
                mines_count = count_mines_around(grid, x, y)
                if mines_count > 0:
                    grid[x][y] = str(mines_count)
    return grid

セルを開く

open_cell関数はセルを開きます。地雷があればゲームオーバーとなり、地雷を赤で表示します。地雷でない場合はその数を表示します。空白セルの場合は周囲のセルも自動的に開きます。

def open_cell(x, y):
    if buttons[x][y]['state'] == 'normal':
        if grid[x][y] == 'M':
            buttons[x][y].config(text='M', bg='red')
            game_over(False)
        else:
            buttons[x][y]['state'] = 'disabled'
            if grid[x][y] != ' ':
                buttons[x][y].config(text=grid[x][y])
            else:
                buttons[x][y].config(text='', bg='lightgrey')
                for i in range(max(0, x - 1), min(GRID_SIZE, x + 2)):
                    for j in range(max(0, y - 1), min(GRID_SIZE, y + 2)):
                        if buttons[i][j]['state'] == 'normal':
                            open_cell(i, j)
            check_win()

ゲームオーバー処理

game_over関数はゲームオーバー時にメッセージボックスで「ゲームオーバー」または「ゲームクリア」のメッセージを表示し、全てのセルを開いて地雷を表示します。

def game_over(win):
    if win:
        messagebox.showinfo("ゲームクリア", "おめでとうございます!すべての地雷を見つけました!")
    else:
        messagebox.showinfo("ゲームオーバー", "残念!地雷を踏んでしまいました。")
    for x in range(GRID_SIZE):
        for y in range(GRID_SIZE):
            if grid[x][y] == 'M':
                buttons[x][y].config(text='M', bg='red')
            buttons[x][y]['state'] = 'disabled'

ゲームクリアのチェック

check_win関数は全ての非地雷セルが開かれたかをチェックし、ゲームクリアとしてgame_over(True)を呼び出します。

def check_win():
    for x in range(GRID_SIZE):
        for y in range(GRID_SIZE):
            if grid[x][y] != 'M' and buttons[x][y]['state'] == 'normal':
                return
    game_over(True)

GUIの設定

setup_gui関数はグリッド上にボタンを配置し、クリックイベントを設定します。
ゲームを再開しやすいようにリスタートボタンを設置しました。

# GUIの設定
def setup_gui(root):
    for x in range(GRID_SIZE):
        row = []
        for y in range(GRID_SIZE):
            button = tk.Button(root, width=2, height=1, command=lambda x=x, y=y: open_cell(x, y))
            button.grid(row=x+1, column=y)  # 行を1つ下げる
            row.append(button)
        buttons.append(row)
    # リスタートボタンを追加
    restart_button = tk.Button(root, text="リスタート", command=restart_game)
    restart_button.grid(row=GRID_SIZE+1, columnspan=GRID_SIZE)

ゲームの再スタート

restart_game関数でゲーム再スタートします。

# ゲームの再スタート
def restart_game():
    global grid, buttons, num_mines
    for x in range(GRID_SIZE):
        for y in range(GRID_SIZE):
            buttons[x][y].destroy()
    buttons = []
    grid = create_grid()
    grid = place_mines(grid, num_mines)
    grid = place_hints(grid)
    setup_gui(root)

難易度設定

最初に定数として設定した地雷の数を利用して難易度を調整する処理です。

# 難易度設定
def set_difficulty(difficulty):
    global num_mines
    num_mines = MINES[difficulty]
    restart_game()

メイン関数

main関数は全体の流れを管理し、グリッドを生成して地雷の数(デフォルトはnomal)とヒントと難易度ボタンを配置しました。

# メイン関数
def main():
    global grid, buttons, root, num_mines
    num_mines = MINES['normal']  # デフォルトの難易度をnormalに設定
    grid = create_grid()
    grid = place_mines(grid, num_mines)
    grid = place_hints(grid)

    root = tk.Tk()
    root.title("マインスイーパー")

    buttons = []

    # 難易度選択ボタンの追加
    tk.Button(root, text="Easy", command=lambda: set_difficulty('easy')).grid(row=0, column=0, columnspan=3, sticky='ew')
    tk.Button(root, text="Normal", command=lambda: set_difficulty('normal')).grid(row=0, column=3, columnspan=3, sticky='ew')
    tk.Button(root, text="Hard", command=lambda: set_difficulty('hard')).grid(row=0, column=6, columnspan=4, sticky='ew')

    setup_gui(root)

    root.mainloop()

if __name__ == "__main__":
    main()

最後に

シンプルなゲームですが、いざプログラムでコードを書いてみると思ったより長いコードになりました…

今回ブログで紹介したコードを見てゲームを実際に作成したり、コードを改修したりするとよりプログラミングの知識が身につくと思います。(現在プレイしている難易度を画面に表示するなど)

また、pythonでゲームを作ってみたい方は以下の本を参考にしてください。
プログラミング未経験者にもおすすめの本です。

Pythonではじめるゲーム制作 超入門 知識ゼロからのプログラミング&アルゴリズムと数学

新品価格
¥2,376から
(2024/6/11 11:45時点)

人気ブログランキング

クリックするとブログランキングサイトに移動します。

にほんブログ村 IT技術ブログへ
インターネット・コンピュータランキング
広告
プログラミング
広告
広告
technyankoをフォローする
広告
広告
プロフィール
technyanko

元情シス・SEです。
当ブログではPCに関して困ったことや役立つ情報を発信していきます。
たまにバッチスクリプトやPythonに関する記事も投稿します。

technyankoをフォローする
広告
タイトルとURLをコピーしました