Python の勉強 スロットゲーム編 〜その16:サードパーティライブラリ「matplotlib」を使ってみる〜

プログラミング

サードパーティライブラリをインストールしたり、使ったりする練習をします。グラフ描画用のライブラリ「matplotlib」をインストールして、プレーヤーの所持コイン数をグラフで表示できるようにしましょう。ゲーム 5 回毎に以下のように所持コイン数の推移を表示できるようにします。

「pip」の更新と「matplotlib」のインストール

前回の記事でご紹介したとおり Python でサードパーティーライブラリをインストールするには基本的に「pip」というパッケージ管理のツールを使用します。まずはその管理情報を最新にします。
一般的に pip の更新は以下のコマンドで行います。コマンドの実行はコマンドプロンプトでも可能ですが、せっかく VS Code を使用しているので、このコマンド実行も VS Code 上で行いましょう。

pip install --upgrade pip
最新バージョン 21..1.3 に更新された様子(※ 2021.7.20 現在)

Windows で作業されている方の中には、上記コマンドでエラーになる場合があります。その場合は以下のコマンドを実行して下さい。

python -m pip install --upgrade pip
コマンドプロンプトで実行した様子

※すでに最新バージョンに更新されているため、特に変化はありません

次に「matplotlib」をインストールします。サードパーティライブラリのインストールは「pip install {ライブラリ名}」で行います。

pip install matplotlib
matplotlib をインストールする様子

またインストールしたライブラリの情報を知りたい場合は「pip show {ライブラリ名}」で確認できます。

matplotlib の詳細確認と、インストール先の各人

matplotlib の簡単な紹介

「matplotlib」はグラフ描画用のライブラリです。描画できるグラフは、折れ線、棒、散布図等の単純なものから、3D のものまで多種多様です。公式サイトのに掲載されている例を見れば、その表現能力の高さに驚くことと思います。そもそも機能が多い上に「コマンドスタイル」と「オブジェクト指向スタイル」の 2 種類の使い方が用意されているため、すべてを紹介するのは無理です。ここでは、これからスロットゲームに適用する部分に絞って簡単に紹介いたします。
先ず、登場する属性とグラフの関係は以下のイメージです。subplot は 1 つでもいいし、そもそも使用しなくてもいいです。
※スロットゲームに適用する際は「オブジェクト指向スタイル」で subplot を 1 つ使うパターンを使用します。

matplotlib に登場する属性のイメージ

ちなみにスタイルの違いは……「コマンドスタイル」が全てを自分で操作するの対して、「オブジェクト指向スタイル」では、各担当者に依頼するという感じになります。「オブジェクト指向スタイル」の方が、操作している対象が分かりやすいので理解もしやすいのではないでしょうか。

コマンドスタイル
オブジェクト思考スタイル

先ず、ライブラリの読み込み時には「as」を用いて「plt」と別名をつけるのが一般的です。
オブジェクト指向スタイルでは、「plt」から「fig」を作って、「fig」から「ax」を作って、「ax」にグラフの情報を設定する。最後に「plt」に描画を依頼する…という流れになります。下記では少々複雑に subplot を使用する例を記載しました。(私が当初、理解するのに時間がかかったので多少でもお役に立てればと思い複雑さを持たせました)

import matplotlib.pyplot as plt


# 図を表示する領域を確保します
fig = plt.figure()

# 全体を、縦1横2に分けた1つ目に描画
# |-----+-----|
# | /// |     |
# | /// |     |
# | /1/ |  2  |
# | /// |     |
# | /// |     |
# |-----+-----|
ax1 = fig.add_subplot(1, 2, 1, title='121')
ax1.plot([1, 2, 4, 8, 16])

# 全体を、縦2横2に分けた2つ目に描画
# |-----+-----|
# |     | /// |
# |  1  | /2/ |
# |     | /// |
# |-----+-----|
# |     |     |
# |  3  |  4  |
# |     |     |
# |-----+-----|
ax2 = fig.add_subplot(2, 2, 2, title='222')
ax2.plot([1, 4, 8, 4, 1])

# 全体を、縦2横2に分けた4つ目に描画
# |-----+-----|
# |     |     |
# |  1  |  2  |
# |     |     |
# |-----+-----|
# |     | /// |
# |  3  | /4/ |
# |     | /// |
# |-----+-----|
ax3 = fig.add_subplot(2, 2, 4, title='224')
ax3.plot([0, 2, 1, -4, -2])

# 全体のタイトルを設定
plt.suptitle('matplotlib test')

# 全体を表示
plt.show()
matplotlib.pyplot を使用してグラフを描画した様子

matplotlib を使ったグラフ描画の基本を確認したので、スロットゲームにも適用しましょう。
5 回に一度グラフを表示するようにしたいので、簡単に思いつくのは以下 2 パターンになります。

「余り 0」を利用する方法

import random

import matplotlib.pyplot as plt

x = 0
y = []
while x < 20:
    y.append(random.randint(0, 10))
    x += 1
    if x % 5 == 0:
        fig = plt.figure()
        ax = fig.add_subplot(1, 1, 1)
        ax.plot(y)
        plt.show()

5 回ループを利用する方法

import random

import matplotlib.pyplot as plt

x = 0
y = []
while x < 20:
    for _ in range(5):
        y.append(random.randint(0, 10))
        x += 1
    fig = plt.figure()
    ax = fig.add_subplot(1, 1, 1)
    ax.plot(y)
    plt.show()

※無限に続かないように 20 プロットしたら終了するようにしています

どちらもコード量にさほどの違いはありません。ちなみに「for」を使用した方は、変数に「_」を使用しました。これは “使いません” という意思表示になります。Python 以外の言語でも使用される慣例のようなものです。今回はこれに慣れるために「5 回ループを利用する方法」を採用してください。

ただしここで注意なのは、単純に 5 回ループしてしまうと、所持コインが 0 枚以下になってもゲームが続いてしまうという点です。5 回ループ中も所持コイン数を監視するようにして、 0 枚以下になったら while ループから抜け出す(break)するようにします。

最終的なプログラムを下記いたしますので、ご自身のものと比較してみて下さい。

"""スロットゲーム

簡単なスロットゲームです。ゲームスタート時に保有しているコインを増やしましょう。
掛けコイン数を入力すると、スロットの結果が表示されます。
絵柄が揃うと、そのパターンに応じた配当倍率でコインを獲得することができます。
"""


import random  # "乱数" を生成するために必要なライブラリをメモリ上に読み込む(標準ライブラリ)
import re  # "正規表現" を使用するために必要なライブラリをメモリ上に読み込む(標準ライブラリ)

import matplotlib.pyplot as plt  # グラフを表示するために必要なライブラリ(サードパーティライブラリ)
from matplotlib import rcParams
rcParams['font.family'] = 'sans-serif'
rcParams['font.sans-serif'] = ['Hackgen35', 'Jetbrains Mono', 'Droid Sans Mono', 'monospace', 'Noto Sans CJK JP']

# スロット(リール)の中身を "定数" で定義する
# 配列のインデックスは 0 から始まるので、0 〜 9 番目に格納されている
# 定数は全て大文字で記載する
REEL_MARK_LIST = ('♠', '♥', '♥', '◆', '◆', '◆', '♣', '♣', '♣', '♣')

# 特別なプレーヤーの名前と、その名前を使用した際のボーナスコイン数
BONUS_PLAYER_AND_COINS = {'king': 100, 'queen': 150}

# スペシャルサンクスの対象者名
# ボーナスコインは一律 50 枚とする
SPECIAL_THANKS = {'akira',      # 弊社社長。詳細は社長が作成最多ブログ記事のプロフィール欄をご確認下さい。
                  'hurry',      # 詳細はプロフィール欄から twitter をご確認下さい。
                  'shachi',     # https://shachi-web.com/
                  }
SPECIAL_THANKS_COIN = 50


# ============================================================
# ゲームに必要な関数を定義する
# ============================================================

def show_start_message():
    """ゲーム開始のメッセージを表示する
    (This function is to be called to show the start message.)
    """
    print('--------------')
    print('スロットゲーム')
    print('--------------')

def ask_player_name():
    """プレーヤーの名前を尋ねる
    (This function is to be called to ask player's name.)

    プレーヤーの名前を入力(input)してもらい、挨拶を表示(print)する
    入力されたプレーヤーの名前を返す(return)

    Returns:
        str: プレーヤーの名前
    """
    player_name = input('あなたの名前を入力して下さい: ')
    print(f'こんにちは {player_name} さん')
    return player_name

def ask_bets(player_coin):
    """現在の所持コイン数を表示した後、掛けるコイン数(bets)を尋ねる
    (This function is to be called to ask bets.)

    所持コイン数(player_coin)は数値なので、文字列に含めたい場合は文字列(string)に変換する必要がある ==> str() 関数
    input() 関数で入力した値は文字列(string)になるので整数(integer)に変換する ==> int() 関数

    Args:
        player_coin (int): プレーヤーの所持コイン数

    Returns:
        int: 掛けコイン数
    """
    print('------------------------------')
    print(f'現在の所持コイン数は {player_coin:,} 枚です。')
    while True:
        str_bets = input('掛けコイン数を入力して下さい: ')
        (valid, error_message) = is_bets_valid(str_bets, player_coin)
        if valid:
            return int(str_bets)
        else:
            print(error_message)
            continue

def is_bets_valid(str_bets, player_coin):
    """掛けコイン数が有効(valid)な場合は True を返す(return)
    --> The function returns true if the bets is valid.

    Args:
        str_bets (str): 掛けコイン数
        player_coin (int): プレーヤーの所持コイン数

    Returns:
        tuple: [0] 掛けコイン数が有効な場合 True、それ以外は False
               [1] 検証結果が False だった場合のエラーメッセージ
    """
    if not re.search(r'^[0-9]+$', str_bets):
        return (False, '掛けコイン数は半角数字で入力して下さい。')

    bets = int(str_bets)
    if bets == 0:
        return (False, '掛けコイン数は 1 枚以上を指定して下さい。')
    elif bets > player_coin:
        return (False, '掛けコイン数は所持コイン数以下を指定して下さい。')
    else:
        return (True, None)

def show_and_get_result():
    """スロットの結果(絵柄)を表示し、結果を取得する
    (This function is to be called to show and get result.)

    Returns:
        str: スロットの結果(絵柄)
    """
    result_list = []
    for _ in range(3):
        index = random.randint(0, 9)
        result = REEL_MARK_LIST[index]
        result_list.append(result)
    result_all = ''.join(result_list)
    print(result_all)
    return result_all

def get_division(marks):
    """絵柄に応じた配当倍率を取得する
    (This function is to be called to get division.)

    Args:
        marks (str): スロットの結果(絵柄)

    Returns:
        int: 配当倍率
    """
    if marks == '♠♠♠':
        print('超大当たり!!')
        return 15
    elif marks == '♥♥♥':
        print('大当たり!')
        return 12
    elif marks == '◆◆◆':
        print('大当たり!')
        return 10
    elif marks == '♣♣♣':
        print('大当たり!')
        return 8
    elif marks[0:2] == '♠♠':
        print('当たり!')
        return 5
    else:
        print('ハズレ...')
        return -1

def calculate_coin(coin, bets, division):
    """掛けたコイン数(bets)と、配当倍率(division)を使用してプレーヤーの所持コイン数を精算する
    (This function is to be called to calculate (players's) coin.)

    Args:
        coin (int): プレーヤーの精算前の所持コイン数
        bets (int): 掛けコイン数
        division (int): 配当倍率

    Returns:
        int: 精算後のプレーヤーの所持コイン数
    """
    coin = coin + bets * division
    return coin

def show_gameover_message(player_name):
    """ゲーム終了のメッセージを表示する
    (This function is to be called to show a game-over message.)

    Args:
        player_name (string): プレーヤー名前
    """
    print('ゲームオーバーです')
    print(f'さようなら {player_name} さん')


# ============================================================
# 実際にゲームを実行する
# ============================================================

def main():
    # プレーヤーの所持コイン数
    player_coin = 123

    show_start_message()
    player_name = ask_player_name()

    if player_name in BONUS_PLAYER_AND_COINS:
        player_coin = player_coin + BONUS_PLAYER_AND_COINS[player_name]
    elif player_name in SPECIAL_THANKS:
        player_coin = player_coin + SPECIAL_THANKS_COIN

    coin_transition = [player_coin]
    while player_coin > 0:
        for _ in range(5):
            bets = ask_bets(player_coin)
            marks = show_and_get_result()
            division = get_division(marks)
            player_coin = calculate_coin(player_coin, bets, division)
            coin_transition.append(player_coin)
            if player_coin <= 0:
                break
        # 所持コインの遷移をグラフで表示する
        fig = plt.figure()                                              # 描画領域を生成
        ax1 = fig.add_subplot(1, 1, 1,                                  # 描画領域で1行1列の1個目のグラフを指定
                              title='スロットゲーム結果',                  # グラフのタイトル
                              xticks=list(range(len(coin_transition))), # x軸メモリ(1,2,3,...)
                              xlabel='回数',                             # x軸ラベル
                              ylabel='所持コイン数')                      # y軸ラベル
        ax1.plot(coin_transition)
        plt.show()

    show_gameover_message(player_name)


if __name__ == '__main__':
    main()

おまけ(特に Linux 利用者用)

ご自身の環境で使える日本語フォントが不明な場合、以下のプログラムを実行することで確認できますので参考にして下さい。「日本語のフォントがインストールされているはずなのに……」という場合は、プログラム下部にも記載したように、キャッシュ情報を削除すると正しく表示されるようになる可能性がありますので、合わせてご確認下さい。

import matplotlib
import matplotlib.font_manager
import matplotlib.pyplot as plt

print(matplotlib.rcParams['font.family'])       # 現在使用しているフォントを表示
print(matplotlib.rcParams['font.sans-serif'] )  # 現在の設定内容を表示
print(matplotlib.get_configdir())               # 設定ディレクトリを表示 ==> /root/.config/matplotlib
print(matplotlib.get_cachedir())                # キャッシュディレクトリを表示 ==> /root/.cache/matplotlib

# フォントを全て読み込み
fonts = set([f.name for f in matplotlib.font_manager.fontManager.ttflist])
 
# 描画領域のサイズ調整
plt.figure(figsize=(10,len(fonts)/4))
 
# フォントの表示
for i, font in enumerate(fonts):
    plt.text(0, i, f"日本語:{font}", fontname=font)
    
# 見やすいように軸を消す
plt.ylim(0, len(fonts))
plt.axis("off")
    
plt.show()

# これでも日本語が表示されない場合
# 上記の "キャッシュディレクトリ" に含まれるファイルを削除して再度、実行してみる
# (Linux をお使いの方へ)以下のコマンドを参考して日本語のフォントをインストールしておいてください
# apt update
# apt install fonts-noto-cjk

コメント

タイトルとURLをコピーしました