2024.11.05(火) / 00:48

Pythonで作る「ビンゴゲーム」

ID
25642
Published
2024-11-05 00:48
Modified
2026-07-17 04:43
Author
khiro
Categories
デフォルト, 技術ブログ
Tags

chat GPTの力も借りつつ、Pythonで「ビンゴゲーム」を作ってみた。

このプログラムでは、次のようなPythonの基本要素を使用しています。

  • クラスとインスタンス
  • 関数とメソッド
  • リストと集合
  • for文とwhile
  • 条件分岐
  • randomモジュール
  • all()関数
  • リスト内包表記

ビンゴゲームの仕様

スクリプトを実行すると、以下の画面が表示される。

Enterを押すと、引かれた番号が表示される。カード内に同じ番号があった場合は、その場所がマークされます。

縦・横・斜めのいずれか1列がすべてマークされると、ビンゴとなってゲームが終了します。

中央のマスは、ゲーム開始時から有効になっているFREE枠です。

ゲーム全体の処理の流れ

ゲームは、次の順番で進みます。

  1. play_bingo()関数を呼び出す
  2. BingoCardクラスからインスタンスを生成する
  3. ビンゴカードを表示する
  4. Enterキーが押されたら番号を1つ抽選する
  5. カード内に同じ番号があればマークする
  6. マーク後のカードを表示する
  7. ビンゴが成立しているか判定する
  8. ビンゴになるまで4〜7を繰り返す

play_bingo()は通常の関数です。

一方、次の処理はBingoCardクラスの中に定義されたメソッドです。

  • display_card():カードを表示する
  • generate_card():カードを生成する
  • mark_number():抽選された番号をマークする
  • check_bingo():ビンゴを判定する

BingoCardクラスを定義する

まず、ビンゴカードの生成や状態管理を担当するBingoCardクラスを定義します。

import random


class BingoCard:
    def __init__(self):
        self.card = self.generate_card()
        self.marked = [
            [False] * 5
            for _ in range(5)
        ]

        # 中央のFREE枠は最初からマーク済みにする
        self.marked[2][2] = True

__init__()は、インスタンスを生成したときに自動的に実行されるメソッドです。

ここでは、次の2つの変数を初期化しています。

self.card

ビンゴカードに配置する数字を保存します。

generate_card()メソッドの戻り値が代入されます。

self.marked

カード内の各マスが、すでにマークされているかを管理します。

初期状態では、すべてのマスにFalseが設定されています。ただし、中央のFREE枠だけはゲーム開始時から有効なので、Trueを設定しています。

ビンゴカードを生成する

ビンゴカードでは、列ごとに使用できる数字の範囲が決まっています。

数字の範囲
B1〜15
I16〜30
N31〜45
G46〜60
O61〜75

このルールに従ってカードを生成します。

def generate_card(self):
    card = []

    ranges = [
        (1, 15),
        (16, 30),
        (31, 45),
        (46, 60),
        (61, 75),
    ]

    for start, end in ranges:
        column = random.sample(
            range(start, end + 1),
            5,
        )
        card.append(column)

    # 中央をFREE枠にする
    card[2][2] = "FREE"

    return card

rangesには、それぞれの列で使用する数字の範囲をタプルとして保存しています。

(1, 15)

この値は、for文の中でstartendに分けて取り出されます。

for start, end in ranges:

random.sample()を使用すると、指定した範囲から重複しない数字をランダムに取得できます。

random.sample(range(start, end + 1), 5)

例えば、B列では1〜15の中から5つの数字が選ばれます。

生成された列は、append()を使用してcardに追加します。

最後に、中央のマスを文字列の"FREE"に変更して、完成したカードを返します。

ビンゴカードを表示する

生成したカードをコンソールに表示します。

def display_card(self):
    print("\n B    I    N    G    O")

    for row in range(5):
        for column in range(5):
            value = self.card[column][row]

            if self.marked[column][row]:
                if value == "FREE":
                    display_value = "FREE"
                else:
                    display_value = " X "
            else:
                display_value = str(value)

            print(f"{display_value:>4}", end=" ")

        print()

self.cardは、列ごとに数字を保存しています。

そのため、表示するときは次のようにアクセスします。

self.card[column][row]

外側のfor文で行を移動し、内側のfor文で列を移動することで、通常のビンゴカードと同じ並びで表示できます。

マーク済みの番号はXとして表示します。中央のマスだけはFREEと表示します。

抽選された番号をマークする

抽選された番号と同じ数字がカード内にあるか調べます。

def mark_number(self, number):
    for column in range(5):
        for row in range(5):
            if self.card[column][row] == number:
                self.marked[column][row] = True

二重のfor文を使用して、カード内の25マスを順番に確認します。

カード内の数字と抽選番号が一致した場合は、同じ位置にあるself.markedの値をTrueに変更します。

self.marked[column][row] = True

数字そのものを書き換えるのではなく、数字を保存するcardと、マーク状態を保存するmarkedを分けて管理しています。

ビンゴが成立したか判定する

縦・横・斜めのいずれか1列が、すべてマークされているかを確認します。

def check_bingo(self):
    # 横方向を確認
    for row in range(5):
        if all(
            self.marked[column][row]
            for column in range(5)
        ):
            return True

    # 縦方向を確認
    for column in range(5):
        if all(self.marked[column]):
            return True

    # 左上から右下への斜め
    if all(
        self.marked[index][index]
        for index in range(5)
    ):
        return True

    # 右上から左下への斜め
    if all(
        self.marked[index][4 - index]
        for index in range(5)
    ):
        return True

    return False

all()は、渡された値がすべてTrueの場合にTrueを返す関数です。

例えば、次の5つの値がすべてTrueであれば、横方向のビンゴが成立します。

self.marked[0][row]
self.marked[1][row]
self.marked[2][row]
self.marked[3][row]
self.marked[4][row]

縦・横・2方向の斜めを確認し、1列でも揃っていればTrueを返します。

どの列も揃っていない場合は、最後にFalseを返します。

ゲームを進行する

ゲーム全体の進行は、play_bingo()関数が担当します。

def play_bingo():
    card = BingoCard()
    card.display_card()

    numbers = list(range(1, 76))
    random.shuffle(numbers)

    while numbers:
        input("\nEnterで次の番号を引きます...")

        number = numbers.pop()

        print(f"引かれた番号: {number}")

        card.mark_number(number)
        card.display_card()

        if card.check_bingo():
            print("\nビンゴ!おめでとうございます!")
            break

最初に、BingoCardクラスからインスタンスを生成します。

card = BingoCard()

これにより、cardを通してBingoCardクラスのメソッドを使用できるようになります。

続いて、1〜75の番号が入ったリストを作成します。

numbers = list(range(1, 76))

random.shuffle()を使用して、番号の順番をランダムに並べ替えます。

random.shuffle(numbers)

Enterキーが押されるたびに、pop()でリストの末尾から番号を1つ取り出します。

number = numbers.pop()

一度取り出した番号はリストから削除されるため、同じ番号が重複して抽選されることはありません。

抽選後は、次の順番で処理します。

card.mark_number(number)
card.display_card()

カードの番号をマークし、更新されたカードを表示します。

最後に、check_bingo()の戻り値を確認します。

if card.check_bingo():

ビンゴが成立していれば、メッセージを表示してbreakでループを終了します。

すべてのソースコード

import random


class BingoCard:
    def __init__(self):
        self.card = self.generate_card()
        self.marked = [
            [False] * 5
            for _ in range(5)
        ]

        # 中央のFREE枠は最初からマーク済みにする
        self.marked[2][2] = True

    def generate_card(self):
        card = []

        ranges = [
            (1, 15),
            (16, 30),
            (31, 45),
            (46, 60),
            (61, 75),
        ]

        for start, end in ranges:
            column = random.sample(
                range(start, end + 1),
                5,
            )
            card.append(column)

        # 中央をFREE枠にする
        card[2][2] = "FREE"

        return card

    def mark_number(self, number):
        for column in range(5):
            for row in range(5):
                if self.card[column][row] == number:
                    self.marked[column][row] = True

    def check_bingo(self):
        # 横方向を確認
        for row in range(5):
            if all(
                self.marked[column][row]
                for column in range(5)
            ):
                return True

        # 縦方向を確認
        for column in range(5):
            if all(self.marked[column]):
                return True

        # 左上から右下への斜め
        if all(
            self.marked[index][index]
            for index in range(5)
        ):
            return True

        # 右上から左下への斜め
        if all(
            self.marked[index][4 - index]
            for index in range(5)
        ):
            return True

        return False

    def display_card(self):
        print("\n B    I    N    G    O")

        for row in range(5):
            for column in range(5):
                value = self.card[column][row]

                if self.marked[column][row]:
                    if value == "FREE":
                        display_value = "FREE"
                    else:
                        display_value = " X "
                else:
                    display_value = str(value)

                print(f"{display_value:>4}", end=" ")

            print()


def play_bingo():
    card = BingoCard()
    card.display_card()

    numbers = list(range(1, 76))
    random.shuffle(numbers)

    while numbers:
        input("\nEnterで次の番号を引きます...")

        number = numbers.pop()

        print(f"引かれた番号: {number}")

        card.mark_number(number)
        card.display_card()

        if card.check_bingo():
            print("\nビンゴ!おめでとうございます!")
            break


if __name__ == "__main__":
    play_bingo()

まとめ

今回のビンゴゲームでは、BingoCardクラスにカードの生成・表示・マーク・判定をまとめました。

ゲーム全体の進行は、play_bingo()関数が担当しています。

役割ごとに処理を分けることで、プログラムの流れを確認しやすくなり、機能の追加や修正もしやすくなります。

今回のプログラムを通して、クラスやインスタンスだけでなく、リスト、ループ、条件分岐、ランダム処理など、Pythonの基本的な仕組みをまとめて確認できました。