迷路ゲーム
キーボード操作でキャラクターをゴールへ導く迷路ゲーム。Canvas描画とキーイベント処理を組み合わせて実装。
1. アプリ概要
キーボード操作でキャラクターをゴールへ導く迷路ゲーム。Canvas描画とキーイベント処理を組み合わせて実装。
このアプリはgamesカテゴリの実践的なPythonアプリです。使用ライブラリは tkinter(標準ライブラリ)、難易度は ★★★ です。
このアプリは「ゲーム」カテゴリです。ゲーム実装は GUI イベント処理・状態管理・描画ループを同時に学べる教材で、画面・入力・ロジックの三層分離の感覚が掴めます。tkinter(標準ライブラリ) を活かして実装するこの構造は、他のアプリにも応用が効きます。
コピー&実行で動作確認 → コード解説で構造理解 → カスタマイズで応用、の 3 ステップで進めると、短時間で本質を押さえられます。
アプリを完成させた後は、自分の使い方に合わせて改造するのが学びを定着させる近道です。カスタマイズ章のアイデアを足がかりに、独自機能を一つ追加してみてください。
2. 機能一覧
- 迷路ゲームのメイン機能
- 直感的なGUIインターフェース
- 入力値のバリデーション
- エラーハンドリング
- 結果の見やすい表示
- クリア機能付き
3. 事前準備・環境
Python 3.10 以上 / Windows・Mac・Linux すべて対応
以下の環境で動作確認しています。
- Python 3.10 以上
- OS: Windows 10/11・macOS 12+・Ubuntu 20.04+
4. 完全なソースコード
右上の「コピー」ボタンをクリックするとコードをクリップボードにコピーできます。
import random
import tkinter as tk
from tkinter import messagebox, ttk
CELL = 28 # セルのピクセルサイズ
class App083:
"""迷路ゲーム"""
def __init__(self, root):
self.root = root
self.root.title("迷路ゲーム")
self.root.configure(bg="#f8f9fc")
self.cols = 21
self.rows = 15
self.maze = [] # 1=壁, 0=通路
self.player = (1, 1)
self.goal = (self.cols - 2, self.rows - 2)
self.steps = 0
self.cleared = False
self._build_ui()
self.new_game()
def _build_ui(self):
"""UI を構築する"""
title = tk.Frame(self.root, bg="#3776ab", pady=10)
title.pack(fill=tk.X)
tk.Label(title, text="迷路ゲーム", font=("Noto Sans JP", 16, "bold"),
bg="#3776ab", fg="white").pack()
ctrl = tk.Frame(self.root, bg="#f8f9fc")
ctrl.pack(fill=tk.X, pady=6)
ttk.Label(ctrl, text="サイズ:").pack(side=tk.LEFT, padx=(20, 4))
self.size_var = tk.StringVar(value="中(21x15)")
cb = ttk.Combobox(ctrl, textvariable=self.size_var, state="readonly", width=12,
values=["小(15x11)", "中(21x15)", "大(27x21)"])
cb.pack(side=tk.LEFT)
ttk.Button(ctrl, text="新しい迷路", command=self.new_game).pack(side=tk.LEFT, padx=10)
self.status = tk.Label(ctrl, text="", bg="#f8f9fc",
font=("Noto Sans JP", 11))
self.status.pack(side=tk.RIGHT, padx=20)
self.canvas = tk.Canvas(self.root, bg="white", highlightthickness=0)
self.canvas.pack(padx=10, pady=10)
# キーバインド (矢印 / WASD)
for k in ("Up", "Down", "Left", "Right", "w", "a", "s", "d", "W", "A", "S", "D"):
self.root.bind(f"<{k}>", self._on_key)
self.root.focus_set()
def _set_size_from_combo(self):
"""コンボボックスからサイズを決定する"""
v = self.size_var.get()
if v.startswith("小"):
self.cols, self.rows = 15, 11
elif v.startswith("大"):
self.cols, self.rows = 27, 21
else:
self.cols, self.rows = 21, 15
def new_game(self):
"""新しい迷路を生成する"""
self._set_size_from_combo()
self.maze = self._generate_maze(self.cols, self.rows)
self.player = (1, 1)
self.goal = (self.cols - 2, self.rows - 2)
self.steps = 0
self.cleared = False
self.canvas.config(width=self.cols * CELL, height=self.rows * CELL)
self._draw()
self.status.config(text="矢印キーで操作 / 歩数: 0", fg="#333")
def _generate_maze(self, w, h):
"""深さ優先探索で迷路を生成する (奇数サイズ前提)"""
if w % 2 == 0:
w += 1
if h % 2 == 0:
h += 1
self.cols, self.rows = w, h
m = [[1] * w for _ in range(h)]
def carve(x, y):
m[y][x] = 0
dirs = [(2, 0), (-2, 0), (0, 2), (0, -2)]
random.shuffle(dirs)
for dx, dy in dirs:
nx, ny = x + dx, y + dy
if 0 < nx < w - 1 and 0 < ny < h - 1 and m[ny][nx] == 1:
m[y + dy // 2][x + dx // 2] = 0
carve(nx, ny)
carve(1, 1)
m[h - 2][w - 2] = 0
return m
def _draw(self):
"""迷路を描画する"""
self.canvas.delete("all")
for y, row in enumerate(self.maze):
for x, v in enumerate(row):
x0, y0 = x * CELL, y * CELL
color = "#1f2933" if v == 1 else "#f8f9fc"
self.canvas.create_rectangle(x0, y0, x0 + CELL, y0 + CELL,
fill=color, outline="")
# ゴール
gx, gy = self.goal
self.canvas.create_rectangle(gx * CELL + 4, gy * CELL + 4,
(gx + 1) * CELL - 4, (gy + 1) * CELL - 4,
fill="#ffd43b", outline="")
# プレイヤー
px, py = self.player
self.canvas.create_oval(px * CELL + 4, py * CELL + 4,
(px + 1) * CELL - 4, (py + 1) * CELL - 4,
fill="#3776ab", outline="")
def _on_key(self, event):
"""キー入力でプレイヤーを移動する"""
if self.cleared:
return
key = event.keysym.lower()
dx, dy = 0, 0
if key in ("up", "w"):
dy = -1
elif key in ("down", "s"):
dy = 1
elif key in ("left", "a"):
dx = -1
elif key in ("right", "d"):
dx = 1
if dx == 0 and dy == 0:
return
x, y = self.player
nx, ny = x + dx, y + dy
if 0 <= nx < self.cols and 0 <= ny < self.rows and self.maze[ny][nx] == 0:
self.player = (nx, ny)
self.steps += 1
self._draw()
self.status.config(text=f"歩数: {self.steps}")
if self.player == self.goal:
self.cleared = True
self.status.config(text=f"クリア! 歩数: {self.steps}", fg="green")
self.root.after(200,
lambda: messagebox.showinfo(
"クリア!", f"{self.steps} 歩でゴールしました"))
if __name__ == "__main__":
root = tk.Tk()
app = App083(root)
root.mainloop()
5. コード解説
迷路ゲームのコードを詳しく解説します。クラスベースの設計で各機能を整理して実装しています。
クラス設計とコンストラクタ
App083クラスにアプリの全機能をまとめています。__init__でウィンドウ設定、_build_ui()でUI構築、process()でメイン処理を担当します。責任の分離により、コードが読みやすくなります。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
UIレイアウトの構築
LabelFrameで入力エリアと結果エリアを視覚的に分けています。pack()で縦に並べ、expand=Trueで結果エリアが画面いっぱいに広がるよう設定しています。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
イベント処理
ボタンのcommand引数でクリックイベントを、bind('
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
Textウィジェットでの結果表示
tk.Textウィジェットをstate=DISABLED(読み取り専用)で作成し、更新時はNORMALに変更してinsert()で内容を書き込み、再びDISABLEDに戻します。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
例外処理とエラーハンドリング
try-exceptでValueErrorとExceptionを捕捉し、messagebox.showerror()でエラーメッセージを表示します。予期しないエラーも処理することで、アプリの堅牢性が向上します。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
6. ステップバイステップガイド
このアプリをゼロから自分で作る手順を解説します。コードをコピーするだけでなく、実際に手順を追って自分で書いてみましょう。
-
1ファイルを作成する
新しいファイルを作成して app083.py と保存します。
-
2クラスの骨格を作る
App083クラスを定義し、__init__とmainloop()の最小構成を作ります。
-
3タイトルバーを作る
Frameを使ってカラーバー付きのタイトルエリアを作ります。
-
4入力フォームを実装する
LabelFrameとEntryウィジェットで入力エリアを作ります。
-
5処理ロジックを実装する
_execute()メソッドにメインロジックを実装します。
-
6結果表示を実装する
TextウィジェットかLabelに結果を表示する_show_result()を実装します。
-
7エラー処理を追加する
try-exceptとmessageboxでエラーハンドリングを追加します。
7. カスタマイズアイデア
基本機能を習得したら、以下のカスタマイズに挑戦してみましょう。
💡 ダークモードを追加する
bg色・fg色を辞書で管理し、ボタン1つでダークモード・ライトモードを切り替えられるようにしましょう。
💡 データの保存機能
処理結果をCSV・TXTファイルに保存する機能を追加しましょう。filedialog.asksaveasfilename()でファイル保存ダイアログが使えます。
💡 設定ダイアログ
フォントサイズや色などの設定をユーザーが変更できるオプションダイアログを追加しましょう。
8. よくある問題と解決法
❌ 日本語フォントが表示されない
原因:システムに日本語フォントが見つからない場合があります。
解決法:font引数を省略するかシステムに合ったフォントを指定してください。
❌ ライブラリのインポートエラー
原因:必要なライブラリがインストールされていません。
解決法:pip install コマンドで必要なライブラリをインストールしてください。
❌ ウィンドウサイズが合わない
原因:画面解像度や表示スケールによって異なる場合があります。
解決法:root.geometry()で適切なサイズに調整してください。
9. 練習問題
アプリの理解を深めるための練習問題です。
-
課題1:機能拡張
迷路ゲームに新しい機能を1つ追加してみましょう。
-
課題2:UIの改善
色・フォント・レイアウトを変更して、より使いやすいUIにカスタマイズしましょう。
-
課題3:保存機能の追加
処理結果をファイルに保存する機能を追加しましょう。