Python の勉強 スロットゲーム編 〜その14:入力検証(バリデーション)を導入〜

プログラミング

前回、”半角数字のみを許可する “正規表現” による入力検証(バリデーション)を採用” することを確認しましたので、今回は実際にスロットゲームに適用していきたいと思います。

前回の記事に記載したフローチャートには、入力検証部分にループ記号は存在していませんが、実際には while を使って正しい入力がされるまでをループさせてやる必要があります。これは前回の記事最後に記載した “エラーが発生してもプログラムが終了しない仕組み” と同じ形式になります。
入力検証(バリデーション)に関しては、関数として独立させることが主で、関数名には「is_valid」とつけることがほとんどです。そのためスロットゲームに適用する際も、この慣例に従うことにします。
ただし、そのまま「is_valid」としてしまうと何に対しての入力検証なのか分かりづらくなってしまうため今回は「is_bets_valid」としました。(引数や関数の説明文から推測することはできると思いますが念のため)

プログラムに適用

先ずは正規表現を使用できるように re モジュールを読み込みます。

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

この時、上の一行は「import random」のすぐ下に記載するようにして下さい。
とても細かい話になってしまいますが、Python では「こう書くと、みんなが読みやすいよね」というガイドラインがあります。代表的なのは「PEP8」と「Google Python Style Guide」です。その9で確認した「for ループ内での文字列結合は非推奨」も PEP8 に記載されている内容です。(こちら
ちなみに、ガイドラインに従っているかどうかをチェックするツール(静的解析ツール、lintツール)もありますので後日、記事にしたいと思います。

実際に入力検証(バリデーション)する関数は以下のように定義しました。少し特徴的な内容として、戻り値(return の後に書かれる内容)がタプル型になっており、検証結果と合わせて、検証NGだった場合のエラーメッセージを返すようにしました。

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)

前回は半角数字であることを確認しましたが、ここでは「半角数字ではない」ことを確認するために「not」(否定)を使用しております。これにより「not True ==> False」、「not False ==> True」の変換を行っております。
これを「ask_bets()」内で使ってみましょう。

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

動作確認

最終的には以下のようになるはずです。(「014.py」として作成)
プログラム修正後は動作確認もしておきましょう。

"""スロットゲーム

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


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

# スロット(リール)の中身を "定数" で定義する
# 配列のインデックスは 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} さん')


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

# プレーヤーの所持コイン数
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

while player_coin > 0:
    bets = ask_bets(player_coin)
    marks = show_and_get_result()
    division = get_division(marks)
    player_coin = calculate_coin(player_coin, bets, division)

show_gameover_message(player_name)
入力検証(バリデーション)が正しく機能している様子

コメント

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