スロットマシン
絵文字を使った3リールスロット。当選パターン判定とコイン管理機能付きのカジノゲーム風アプリ。
1. アプリ概要
絵文字を使った3リールスロット。当選パターン判定とコイン管理機能付きのカジノゲーム風アプリ。
このアプリはgamesカテゴリの実践的なPythonアプリです。使用ライブラリは tkinter(標準ライブラリ)、難易度は ★★☆ です。
このアプリは「ゲーム」カテゴリです。ゲーム実装は GUI イベント処理・状態管理・描画ループを同時に学べる教材で、画面・入力・ロジックの三層分離の感覚が掴めます。tkinter(標準ライブラリ) を活かして実装するこの構造は、他のアプリにも応用が効きます。
コピー&実行で動作確認 → コード解説で構造理解 → カスタマイズで応用、の 3 ステップで進めると、短時間で本質を押さえられます。
アプリを完成させた後は、自分の使い方に合わせて改造するのが学びを定着させる近道です。カスタマイズ章のアイデアを足がかりに、独自機能を一つ追加してみてください。
2. 機能一覧
- 「スピン」ボタン(
spin())・「コイン補充(+50)」ボタン(add_coins())・「リセット」ボタン(reset())で操作 after(60, ...)による60ミリ秒間隔の_animate()呼び出し(画面の自動更新)messagebox.showwarning()によるダイアログ通知- ウィンドウはタイトル「スロットマシン」・サイズ 600x450 で起動
3. 事前準備・環境
Python 3.10 以上 / Windows 11(実機)で起動・基本操作を確認 / Linux Mint 22.3(仮想マシン)で起動・画面表示を確認(macOS は未検証)
Windows 11(実機)で起動と基本操作を確認しています(全機能の網羅テストではありません)。Linux Mint 22.3(仮想マシン)では起動と画面表示を確認しました。macOS は標準ライブラリの範囲で動作する想定ですが、未検証です。
- Python 3.10 以上
- OS: Windows 11(実機で起動・基本操作を確認)・Linux Mint 22.3(仮想マシンで起動・画面表示を確認)
4. 完全なソースコード
右上の「コピー」ボタンをクリックするとコードをクリップボードにコピーできます。
import tkinter as tk
from tkinter import ttk, messagebox
import random
class App067:
"""スロットマシン: 3リール絵文字スロット + コイン管理"""
SYMBOLS = ["🍒", "🍋", "🍊", "🔔", "⭐", "💎", "7️⃣"]
# 当選時の獲得倍率(揃った絵文字に対する払い戻し倍率)
PAYOUT = {
"7️⃣": 50,
"💎": 20,
"⭐": 10,
"🔔": 5,
"🍊": 3,
"🍋": 2,
"🍒": 2,
}
BET = 10 # 1回あたりのベット額
def __init__(self, root):
self.root = root
self.root.title("スロットマシン")
self.root.geometry("600x450")
self.root.configure(bg="#f8f9fc")
self.coins = 100 # 初期所持コイン
self.spinning = False
self._build_ui()
self._update_status()
def _build_ui(self):
"""UI を構築する"""
# タイトル
title_frame = tk.Frame(self.root, bg="#3776ab", pady=12)
title_frame.pack(fill=tk.X)
tk.Label(
title_frame, text="🎰 スロットマシン",
font=("Noto Sans JP", 16, "bold"),
bg="#3776ab", fg="white"
).pack()
main = tk.Frame(self.root, bg="#f8f9fc", padx=20, pady=20)
main.pack(fill=tk.BOTH, expand=True)
# ステータス(コイン数)
self.status_var = tk.StringVar()
tk.Label(
main, textvariable=self.status_var,
font=("Noto Sans JP", 12, "bold"), bg="#f8f9fc"
).pack(pady=(0, 10))
# リール表示
reel_frame = tk.Frame(main, bg="#222", padx=20, pady=20, bd=4, relief=tk.RIDGE)
reel_frame.pack(pady=10)
self.reel_vars = [tk.StringVar(value="❓") for _ in range(3)]
self.reel_labels = []
for i, var in enumerate(self.reel_vars):
lbl = tk.Label(
reel_frame, textvariable=var, font=("Arial", 48),
width=2, bg="white", fg="#222", bd=2, relief=tk.SUNKEN
)
lbl.grid(row=0, column=i, padx=6)
self.reel_labels.append(lbl)
# 結果メッセージ
self.message_var = tk.StringVar(value=f"スピン1回 = {self.BET}コイン消費")
tk.Label(
main, textvariable=self.message_var,
font=("Noto Sans JP", 12), bg="#f8f9fc", fg="#333"
).pack(pady=10)
# 操作ボタン
btn = tk.Frame(main, bg="#f8f9fc")
btn.pack(pady=8)
self.spin_btn = ttk.Button(btn, text="スピン", command=self.spin)
self.spin_btn.pack(side=tk.LEFT, padx=4)
ttk.Button(btn, text="コイン補充(+50)", command=self.add_coins).pack(side=tk.LEFT, padx=4)
ttk.Button(btn, text="リセット", command=self.reset).pack(side=tk.LEFT, padx=4)
def spin(self):
"""スピン開始"""
if self.spinning:
return
if self.coins < self.BET:
messagebox.showwarning("コイン不足", "コインが足りません。補充してください。")
return
self.coins -= self.BET
self._update_status()
self.spinning = True
self.spin_btn.config(state=tk.DISABLED)
# アニメーション: 各リールを順に止める
self._animate(0, frames=15)
def _animate(self, reel_index, frames):
"""リールを回転表示してから停止"""
if frames > 0:
for i in range(reel_index, 3):
self.reel_vars[i].set(random.choice(self.SYMBOLS))
self.root.after(60, lambda: self._animate(reel_index, frames - 1))
else:
# 現在のリールを確定
if reel_index < 2:
self._animate(reel_index + 1, frames=12)
else:
self._judge()
def _judge(self):
"""当選判定"""
symbols = [v.get() for v in self.reel_vars]
if symbols[0] == symbols[1] == symbols[2]:
mult = self.PAYOUT.get(symbols[0], 2)
payout = self.BET * mult
self.coins += payout
self.message_var.set(f"🎉 大当たり! {symbols[0]} x3 → +{payout} コイン")
elif symbols[0] == symbols[1] or symbols[1] == symbols[2] or symbols[0] == symbols[2]:
payout = self.BET # ベット額返却
self.coins += payout
self.message_var.set(f"おしい!2つ揃い → +{payout} コイン返還")
else:
self.message_var.set("ハズレ… もう一度!")
self._update_status()
self.spinning = False
self.spin_btn.config(state=tk.NORMAL)
def add_coins(self):
"""コインを補充する"""
self.coins += 50
self._update_status()
def reset(self):
"""初期状態に戻す"""
self.coins = 100
for v in self.reel_vars:
v.set("❓")
self.message_var.set(f"スピン1回 = {self.BET}コイン消費")
self._update_status()
def _update_status(self):
"""所持コイン表示を更新"""
self.status_var.set(f"💰 所持コイン: {self.coins}")
if __name__ == "__main__":
root = tk.Tk()
app = App067(root)
root.mainloop()
5. コード解説
スロットマシン: 3リール絵文字スロット + コイン管理のコードを、実際に書かれている実装に沿って解説します。
クラス設計とコンストラクタ
App067 クラスにアプリの全機能をまとめています(メソッド8個・全147行)。__init__ ではタイトル「スロットマシン」とウィンドウサイズ 600x450 を設定します。あわせて状態を保持する変数(self.coins・self.spinning)を初期化し、そのあと _build_ui() → _update_status() の順に呼び出して初期状態を作ります。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
ウィジェット構成
画面は tk.Frame×4・tk.Label×3(ほかにループでも生成)・ttk.Button×3 で構成しています。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
イベント処理とボタンの接続
「スピン」ボタン(spin())・「コイン補充(+50)」ボタン(add_coins())・「リセット」ボタン(reset())を command= で接続しています。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
after() による定期処理
_animate() の末尾で after(60, ...) を呼び、60ミリ秒後に自分自身の再実行を予約しています。tkinter では while ループで待つと画面が固まるため、この「予約の連鎖」で定期処理を作るのが定石です。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
クラス定数によるデータ定義
クラスの先頭でデータを定数として定義しています:SYMBOLS = ['🍒', '🍋', '🍊', '🔔', '⭐', '💎', '7️⃣']、PAYOUT = {'7️⃣': 50, '💎': 20, '⭐': 10, '🔔': 5, '🍊': 3, '🍋': 2, '🍒': 2}。ロジックの中に直接書かず定数にまとめておくと、内容を変えたいときに1か所を書き換えるだけで済みます。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
例外処理を書いていない理由
このコードには try-except による例外処理がありません。テキスト入力欄がなくボタン操作だけで完結し、ファイルやネットワークへのアクセスも無い設計のため、例外を投げうる処理(型変換・ファイル/通信)がそもそも無いからです。入力欄やファイル保存を追加する改造をするときは、例外処理もセットで検討しましょう。
※ 該当部分のコード本体は 「4. 完全なソースコード」 をご参照ください(重複表示を避けるため再掲を省略しています)。
6. ステップバイステップガイド
このアプリをゼロから自分で作る手順を解説します。コードをコピーするだけでなく、実際に手順を追って自分で書いてみましょう。
-
1ファイルを作成する
新しいファイルを作成して app067.py と保存します。使うのは
random・tkinterだけなので、追加インストールは不要です。 -
2クラスの骨格を作る
App067クラスを定義し、__init__と、末尾のroot = tk.Tk()~mainloop()の最小構成を書いて、まず空のウィンドウが出ることを確認します。 -
3ウィンドウを設定する
title("スロットマシン")・geometry("600x450")・configure(bg="#f8f9fc")をコンストラクタで設定します。 -
4画面部品を並べる
_build_ui()の中で、tk.Frame×4・tk.Label×3(ほかにループでも生成)・ttk.Button×3 を作って配置します(掲載コードと同じ並び順で書くとレイアウトが一致します)。まず表示だけ確認しましょう。 -
5イベントを接続する
「2. 機能一覧」で挙げた各ボタンを
command=で対応するメソッド(spin()・add_coins()・reset())につなぎます。 -
6中心になるメソッドを実装する
アプリの本体である
_animate()(12行)・spin()(13行)・reset()(7行)・add_coins()(4行) を実装します。定期処理はafter(60, ...)で自分自身を予約し続ける形にします。 -
7動作確認する
python app067.pyで起動し、各ボタンが反応すること・表示が自動で更新されることを確認します。
7. カスタマイズアイデア
基本機能を習得したら、以下のカスタマイズに挑戦してみましょう。
💡 ダークモードを追加する
bg色・fg色を辞書で管理し、ボタン1つでダークモード・ライトモードを切り替えられるようにしましょう。
💡 更新間隔を変えてみる
after(60, ...) の 60 が更新間隔(ミリ秒)です。大きくすると更新頻度が下がって CPU 負荷が軽くなり、小さくすると更新が細かくなります(表示の刻み幅より細かくしても見た目は変わりません)。数値を変えて違いを確かめてみましょう。
💡 設定ダイアログ
フォントサイズや色などの設定をユーザーが変更できるオプションダイアログを追加しましょう。
8. よくある問題と解決法
❌ 文字のフォントが崩れる・見た目が違う
原因:このコードは font=("Noto Sans JP", ...) のようにフォント名を直接指定しています。環境にこのフォントが入っていないと、OS が代わりのフォントで表示するため、見本と見た目が変わることがあります。
解決法:動作には支障ありません。気になる場合は font 引数の名前を自分の OS に入っているフォントへ書き換えるか、font 引数を省略してください。
❌ 改造したら表示の更新が二重になった・速くなった
原因:_animate() は after(60, ...) で自分自身を予約し続ける構造です。予約を止めずにもう一度開始経路を通ると、予約の連鎖が2本になり、更新が倍速になります。
解決法:開始処理の前に動作中かどうかをフラグで判定してください。
❌ ウィンドウの大きさが画面に合わない
原因:geometry("600x450") の固定サイズで起動するためです。
解決法:この数値を書き換えて起動サイズを調整してください。なお、このコードにはウィンドウサイズを固定する設定(resizable の指定)が無いため、ウィンドウの端をドラッグしたサイズ変更は既定どおり可能です。
9. 練習問題
アプリの理解を深めるための練習問題です。気になるものから挑戦してみてください(課題3は定型の発展課題です)。画面のコインは得点として数えるだけの数字で、ファイルへの保存も外部への通信もありません。どちらの課題も新しいライブラリは要らず、「スピン」と「コイン補充(+50)」を押すだけで確かめられます。
-
課題1:所持コインの最高記録を並べて表示する
いまのステータス欄には、そのときの所持コインしか出ていません。これまでで一番多かった数を横に並べ、増えたときだけ更新されるようにしましょう。
期待結果:起動直後は「💰 所持コイン: 100 最高: 100」と出る。「コイン補充(+50)」を押すと「💰 所持コイン: 150 最高: 150」になる。そこからスピンを1回してそろわなければ「💰 所持コイン: 140 最高: 150」になる
合格条件
- コインが減っても「最高」の数字は下がらない
- 「リセット」を押すと所持コインは 100 に戻る
- 「最高」は起動した直後から数字が入っている(空欄にならない)
ヒント①(どこを触るか): 触るのは2か所です。
__init__()のself.coins = 100の下と、_update_status()の中です。
ヒント②(使うもの):__init__()にself.best = self.coinsを1行足し、_update_status()の先頭でself.best = max(self.best, self.coins)としてから表示をf"💰 所持コイン: {self.coins} 最高: {self.best}"に伸ばします。_update_status()はspin()・_judge()・add_coins()・reset()から呼ばれるので、ここだけで全部の場面をカバーできます。
つまずきやすい点:__init__()にself.bestを用意せず_update_status()の中だけで比べると、起動のときにAttributeError: 'App067' object has no attribute 'best'が出てウィンドウが開きません。__init__()は_build_ui()のあとで_update_status()を呼んでおり、その1回目にはまだself.bestが無いためです。 -
課題2:回転中はリセットを受け付けないようにする
3つのリールが回っている最中でも「リセット」は効いてしまい、所持コインだけが 100 に戻ります。回転が止まるまでは受け付けないようにしましょう。
期待結果:所持コイン 100 の状態で「スピン」を押し、リールが回っている間に「リセット」を押す。すると「回転が止まってからリセットしてください」と出て、所持コインは 90 のまま変わらない。いまは同じ操作でその場で 100 に戻る
合格条件
- 回転が止まっているときは今までどおりリセットできる(コイン 100・リールが ❓ に戻る)
- 回転中に押しても所持コインの数字は変わらない
- 回転が終わったあとにもう一度押せばリセットできる
ヒント①(どこを触るか): 触るのは1か所です。
reset()の中のself.coins = 100の上に足します。
ヒント②(使うもの): 回転中かどうかはself.spinningが持っています(spin()でTrueになり、_judge()の最後でFalseに戻ります)。if self.spinning:の中でmessagebox.showinfo("情報", "回転が止まってからリセットしてください")を出してreturnします。
つまずきやすい点:returnを書き忘れて知らせだけを出すと、ダイアログを閉じたあとにそのままリセットが走り、コインは 100 に戻ります。またreset()はリールの絵柄も ❓ に戻しますが、_animate()が書き直すのはrange(reel_index, 3)=まだ止まっていないリールだけです。1本目が止まっただけの段階ではまだ2本が回転中なので ❓ は1個しか残りませんが、2本目まで止まって残り1本になってからリセットを押すと、止まった2本とも ❓ のまま最後まで残り、_judge()がその ❓ 同士を絵柄一致として数えて「おしい!2つ揃い」の返還が必ず起きます。 -
課題3:保存機能の追加
処理結果をファイルに保存する機能を追加しましょう。
写経中に赤いエラー文が出たら、Pythonエラー一覧&解決法(英語メッセージ逆引き)で原因と直し方をすぐ確認できます。
写経しながら「なぜこう書くのか」が気になり始めたら、入門書を1冊通して読むと断片的な知識がつながります。Python入門書のおすすめ2冊(当サイトの参考書ランキング総合1〜2位)で、独学者向けの最初の1冊を比較しています。