Python の勉強 スロットゲーム編 〜その9:for による繰り返し〜

プログラミング

Python では繰り返しのために forwhile という構文を用意しています。今回は先ず for の使い方について勉強したいと思います。

for を使った繰り返しの例

Python の for には以下に示すように色んな使い方があります。
ただどれも共通しているのは「塊(かたまり)から一つずつ取り出して使っている」という点です。

  • 文字列は1文字ずつ繰り返しに使われます
  • range は初登場です。使い方は難しくないのですが「0」から始まることに注意!
  • 辞書(dict)は、そのままではキー(key)しか取れない
    • keys()」を使うとキー(key)のみを順番に取り出す
    • values()」を使うとバリュー(value)のみを順番に取り出す
    • items()」を使うと、キー(key)とバリュー(value)をタプル(tuple)で取り出す
      さらに「(変数1, 変数2)」と記載することでキーとバリューを直接、変数に代入できる
  • 集合(set)は取り出す順番がバラバラ

という点が特徴です。

for を使って一つずつ取り出すイメージ

このような使い方ができることを “イテラブル(iterable)” といいます。Python に限らず、プログラム界隈ではよく登場する言葉なので覚えておきましょう。では使い方を実際のプログラムでご紹介します。

print('---------- string')
for s in 'abc':
    print(s)                            #==> 「a」「b」「c」

print('---------- range')
for r in range(3):
    print(r)                            #==> 「0」「1」「2」  ※数値で出力

print('---------- list')
for l in ['a', 'b', 'c']:
    print(l)                            #==> 「a」「b」「c」

print('---------- tuple',)
for t in ('a', 'b', 'c'):
    print(t)                            #==> 「a」「b」「c」

print('---------- dict')
for d in {'a': 1, 'b': 2, 'c': 3}:
    print(d)                            #==> 「a」「b」「c」

print('---------- dict with keys()')
for k in {'a': 1, 'b': 2, 'c': 3}.keys():
    print(k)                            #==> 「a」「b」「c」

print('---------- dict with values()')
for v in {'a': 1, 'b': 2, 'c': 3}.values():
    print(v)                            #==> 「1」「2」「3」

print('---------- dict with items()')
for (k, v) in {'a': 1, 'b': 2, 'c': 3}.items():
    print(f"{k}: {v}")                  #==> 「a: 1」「b: 2」「c: 3」

print('---------- set')
for s in {'a', 'b', 'c'}:
    print(s)                            #==> 「a」「c」「b」や、「c」「a」「b」など、順番はランダム

ちなみに、繰り返されるのは for の以降で、インデント(字下げ)されている部分になります。スロットゲーム中でも 2 箇所に使われています。
この様に Python はインデント(字下げ)で処理のまとまりを表すという特徴があります。

この内、2つ目の for はゲーム回数や、メッセージに関わっていることが予想できます。現在 3 回しか挑戦できないスロットゲームを 4回、または 5 回やれるようにするには、どの様に修正したらいいか分かりますでしょうか?ここまでで、皆さんは for の使い方と、フォーマットを用いた文字列の操作について勉強してきましたので、それらを使って応用することができるはずです。「009.py」を作って、是非、色んなパターンで試してみて下さい。

以下では一部の例を記載いたしました。

# リストの要素を増やすパターン

for message in ['1回目', '2回目', '3回目', '4回目', '最後だよ']:
    print(message)
    bets = ask_bets(player_coin)
    marks = show_and_get_result()
    division = get_division(marks)
    player_coin = calculate_coin(player_coin, bets, division)
# range を使うパターン(1)
# ただし、最後のメッセージは「最後だよ」とはなりません

for r in range(5):
    message = f'{r + 1}回目'
    print(message)
    bets = ask_bets(player_coin)
    marks = show_and_get_result()
    division = get_division(marks)
    player_coin = calculate_coin(player_coin, bets, division)
# range を使うパターン(2)

game_count = range(5)
for r in game_count:
    message = f'全 {len(game_count)} 回中 {r + 1} 回目の挑戦'
    print(message)
    bets = ask_bets(player_coin)
    marks = show_and_get_result()
    division = get_division(marks)
    player_coin = calculate_coin(player_coin, bets, division)

(応用)for と「enumerate」「zip」の合わせ技

応用編になります。スロットゲーム中では使用していないですが、よく登場する形なので予習のつもりで「enumerate()」「zip()」の使い方を確認します。
enumerate の方は、対象のリストから
zip の方は、ついつい “圧縮” をイメージしてしまいますが、ジッパー……つまり”合わせて固定する”……方のイメージですね。

enumerate() と zip() のイメージ
print('---------- list with enumerate()')
for (i, v) in enumerate(['a', 'b', 'c']):
    print(f'{i}: {v}')                  #==> 「0: a」「1: b」「2: c」

print('---------- list with zip()')
for (v1, v2, v3) in zip(['a', 'b', 'c'],
                        ['1', '2', '3', '4'],
                        ['!', '?']):
    print(f'{v1}, {v2}, {v3}')          #==> 「a, 1, !」「b, 2, ?」  ※一番短いリストに合わせる

“アンチパターン” 〜for ループ内での文字列結合は非推奨!〜

Avoid using the + and += operators to accumulate a string within a loop.

3.10 Strings | Google Python Style Guide

Google 翻訳の結果は「+ および += 演算子を使用して、ループ内に文字列を累積することは避けてください。」になります。

「結果よりも過程が大事!」とまでは言いません。しかし、プログラムにも、たとえ同じ結果になったとしても良い書き方と、悪い書き方というものが存在します。良い慣例は「ベストプラクティス」、良くない慣例は「アンチパターン」と呼ばれます。スロットゲーム中には、このアンチパターンが含まれています。内容は上記英語(または、Google 翻訳)の通りで、この書き方をすると処理にかかる負荷が大きくなります。

では、どのようにするのが良いのでしょうか?答えは、引用元に記載されているとおり

add each substring to a list and ''.join the list after the loop terminates

3.10 Strings | Google Python Style Guide

つまり list 型の変数に要素を追加して、ループが終わった後に join を使って文字列にしなさい……ということです。list 型の変数に要素を追加するには「append()」を使います。その使い方も合わせてここで覚えて下さい。修正後のプログラムは以下のようになります。

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

他にも文字列にはなるべくシングルクォーテーション「'」を使うようにとの記載もあります。スロットゲーム中ではそのようにしています。

ここまでで、最終的に以下のようなプログラムになっているかと思います。ただし 151 行目辺りの for の書き方は、人によって違いますので適宜、読み替えて下さい。

"""スロットゲーム

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


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


# スロット(リール)の中身を "定数" で定義する
# 配列のインデックスは 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:,} 枚です。')
    str_bets = input('掛けコイン数を入力して下さい: ')
    bets = int(str_bets)
    return bets

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

    Returns:
        str: スロットの結果(絵柄)
    """
    # TODO(佐藤宏行): ループ内で文字列を結合するのは避ける。代わりにリストに貯めて、最後に結合(join)すること
    # 参照: https://google.github.io/styleguide/pyguide.html#310-strings
    result_all = ''
    for i in range(3):
        index = random.randint(0, 9)
        result = REEL_MARK_LIST[index]
        result_all = result_all + result
    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


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

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

for r in range(5):
    message = f'{r + 1}回目'
    print(message)
    bets = ask_bets(player_coin)
    marks = show_and_get_result()
    division = get_division(marks)
    player_coin = calculate_coin(player_coin, bets, division)

print('終了です。{} さんの所持コイン数は...'.format(player_name))
print(f'最終的に {player_coin:,} 枚になりました!')

(余談)なぜ “for” なのか……?

繰り返しには「repeat」、ループには「loop」という単語があるのに、何故 for という単語を使っているのか?折角なので調べてみました。
しかし、なかなか答えが見つからない!多分、私の英語力が足りないせい……。結局、下記 2 つのメッセージにたどり着きました。

つまりは”A for loop statement repeats for a certain number of times.”のforでした。

For文の”For”って何だろう?という疑問 | Unityでインディゲーム道!

The loop body is executed “for” the given values of the loop variable

For loop

1つ目は一定の回数の間……つまり “期間” を表すものとして、2つ目は与えられた値……つまり “対象” を表すものとして「for」を使用しているということです。Python の構文的には2つ目の使い方の方がしっくりくるかな〜、JavaScript など他のプログラミング言語では1つ目の使い方がしっくりくるかな〜という感じです。皆さんはどう感じますか?

コメント

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