モンハンのクエスト同行メンバーをランダムで決めるプログラムをPythonで作りたい

## はじめに
モンスターハンターワールド：アイスボーンの集会所は16人参加できるようになっていますが、1つのクエストに同行できるメンバーは4人までになっています。
一緒にモンハンを遊んでいる友人から、「集会所に5人以上集まったときのクエスト同行者をランダムに決められるようにしたい」との要望があったので、Pythonでプログラムを作ってみました。今回は作成したプログラムを紹介していきたいと思います。
※GUIでの実装ではなく**ターミナルで動作させるのみ**となります。
※内容は非常に簡素なものとなっております。もっと綺麗な書き方等思いついたら修正していきます。
※自己紹介を除くと初投稿なので読んでいて違和感があるかもしれませんがご了承ください。

## 目次
- [はじめに](#はじめに)
- [実装したい内容](#実装したい内容)
- [実装したコード](#実装したコード)
- [コードの解説](#コードの解説)
    - [モジュールのインポート](#モジュールのインポート)
    - [decision_teamの定義](#decision_teamの定義)
    - [プレイヤー数の入力](#プレイヤー数の入力)
    - [チームの決定と諸設定](#チームの決定と諸設定)
    - [結果の表示](#結果の表示)
- [実行結果](#実行結果)
    - [実行結果1](#実行結果1)
    - [実行結果2](#実行結果2)
- [さいごに](#さいごに)
    - [参考](#参考)

## 実装したい内容
* 集会所に参加しているメンバーの指定
* メンバーの名前の入力
* 集会所に参加しているメンバーから最大4人をランダムに選択する機能
* チームカラーの指定（ランダム）
* クエストの指定（ランダム）

## 実装したコード
まずは作成したコードをお見せします。
```python:selection.py
import random
import sys
import math

# ランダムのクエストの同行者を決める関数の定義
def decision_team(members, num_members):

    # チームの数を決める（最大4チーム）
    num_teams = math.ceil(float(num_members)/4)

    # メンバーをランダムにシャッフル
    random.shuffle(members)

    # メンバーを各チームに分ける
    teams = [[] for _ in range(num_teams)]
    for i, member in enumerate(members):
        teams[i % num_teams].append(member)

    return teams

print("1~16の範囲でプレイヤー数を入力してください。終了する場合はqを入力してください。")
while True:
    num_members = input("プレイヤー数：")
    if num_members == "q":
        print("プログラムを終了します。")
        sys.exit()
    try:
        num_members = int(num_members)
    except:
        print("数字以外は入力しないでください。")
        continue
    if num_members >= 1 and num_members <= 16:
        break
    else:
        print("不正な入力です。メンバー数は1~16の範囲で入力してください。")

members = []
print("プレイヤー名を入力してください。")
for i in range(num_members):
    player = input(f"プレイヤー{i+1}の名前：")
    members.append(player)
teams = decision_team(members, num_members)

color_lst = ["白", "黒", "赤", "青", "黄色", "緑"]
quest_lst = ["ミラボレアス", "王カーナ", "激ラー", "滅日", "ベヒーモス", "アルバトリオン", "王ネロミェール", "モス", "高難易度調査クエスト", "エンシェント・レーシェン"]

print("------------")
print("    結果")
print("------------")
quest = random.choice(quest_lst)
print(f"クエスト：{quest}")
for team in teams:
    random_index = random.randint(0,len(color_lst)-1)
    color = color_lst.pop(random_index)
    print(f"{color}チーム : {team}")
```

## コードの解説
次にコードの解説をしていきます。
かなり丁寧に解説を書いたつもりです。解説がいらない方は[実行結果](#実行結果)へどうぞ。
### モジュールのインポート
```python
import random
import sys
import math
```
今回作成したコードでは`random`、`sys`、`math`の３つのモジュールを使用しています。

### decision_teamの定義
```python
# ランダムのクエストの同行者を決める関数の定義
def decision_team(members, num_members):

    # チームの数を決める（最大4チーム）
    num_teams = math.ceil(float(num_members)/4)

    # メンバーをランダムにシャッフル
    random.shuffle(members)

    # メンバーを各チームに分ける
    teams = [[] for _ in range(num_teams)]
    for i, member in enumerate(members):
        teams[i % num_teams].append(member)

    return teams
```
小分けにして説明します。
```python:decision_team
num_teams = math.ceil(float(num_members)/4)
```
`num_teams`：チームの数
`num_members`：メンバーの数
ここでは受け取ったnum_membersを4で割った値を切り上げすることでチーム数を決定しています。
ex) num_members=7のとき、4で割ると1.75になる。それを切り上げすると2になるのでチーム数は2になる。

（余談ですが最初はif文で場合分けしてnum_teamsを決定しましたが、この記事を書いている途中に<font color="red">4で割って切り上げればいいのでは...！？</font>と天啓が降ってきたので変えてみました。）

```python:decision_team
random.shuffle(members)
```
`members`：参加メンバーの名前が並んだリスト
書いてある通りですがメンバーリストを順番をランダムに入れ替えています。なんだかんだここが一番重要。
```python:decision_team
teams = [[] for _ in range(num_teams)]
    for i, member in enumerate(members):
        teams[i % num_teams].append(member)

    return teams
```
`teams`：各チームのメンバーリストが格納されているリスト
ここでは各チームにメンバーを振り分けています。teamsという二重の空リストを内包表記で定義しています。appendする際には余りを使ってindexを指定することで、メンバーの振り分けを動的に行っています。また、振り分ける際は各チームのメンバー数が大きく離れないようにしています（詳しくは最後の実行結果にて）。最後に出来上がったteamsをreturnします。

### プレイヤー数の入力
```python
print("1~16の範囲でプレイヤー数を入力してください。終了する場合はqを入力してください。")
while True:
    num_members = input("プレイヤー数：")
    if num_members == "q":
        print("プログラムを終了します。")
        sys.exit()
    try:
        num_members = int(num_members)
    except:
        print("数字以外は入力しないでください。")
        continue
    if num_members >= 1 and num_members <= 16:
        break
    else:
        print("不正な入力です。メンバー数は1~16の範囲で入力してください。")
```
まずは`input`を使って`num_members`を決定します。
この時「q」を入力したら`sys.exit()`でプログラムを強制的に停止させるようにしています。~~正直これくらいのプログラムなら実装しなくてもよさそうだけど~~

続いて`try`で数値以外をはじきます。intに変換できない=数値ではないを利用し、数値でなければ再入力させます。
入力された数値が1~16の範囲に入っていれば合格です。範囲外なら再入力になります。

余談ですが大学のC言語の授業の課題で、毎回のように不正入力を無限ループでチェックすることをやっていたので、不正入力検知が習慣化してしまいました。

### チームの決定と諸設定
```python
members = []
print("プレイヤー名を入力してください。")
for i in range(num_members):
    player = input(f"プレイヤー{i+1}の名前：")
    members.append(player)
teams = decision_team(members, num_members)

color_lst = ["白", "黒", "赤", "青", "黄色", "緑"]
quest_lst = ["ミラボレアス", "王カーナ", "激ラー", "滅日", 
             "ベヒーモス", "アルバトリオン", "王ネロミェール", 
             "モス", "高難易度調査クエスト", "エンシェント・レーシェン"
            ]
```
ここでは先ほど入力したメンバーの数だけメンバーの名前を入力していきます。入力結果が見やすいようにf文字列を使って整形しています（詳しくは最後の実行結果にて）。入力したメンバーの名前を`members`に追加していきます。
出来上がった`members`と`num_members`を`decision_team`の引数に入れ、チームを決定します。

また、チームカラーを指定するための`color_lst`や、`quest_lst`を定義しています。この2つはお好みで決められます。
### 結果の表示
```python
print("------------")
print("    結果")
print("------------")
quest = random.choice(quest_lst)
print(f"クエスト：{quest}")
for team in teams:
    random_index = random.randint(0,len(color_lst)-1)
    color = color_lst.pop(random_index)
    print(f"{color}チーム : {team}")
```
最後に結果の表示を行います。
先ほど定義した`quest_lst`からランダムに1つクエストを選択します。
※ここではどのチームも同じクエストに出発する前提です。ただチーム分けするだけならクエスト選択部分はいらないですね。
チーム、及びチームカラーの表示はfor文で行います。
チームカラーの選択では`randint`と`.pop`を使用することで`color_lst`の中から被ることなく色を取り出すことができます。

## 実行結果
Pythonのバージョンは`3.12.5`で、正常動作を確認しています。
#### 実行結果1
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3493131/a52fd0a3-214d-21a1-38a1-bf1224b3a420.png)
プレイヤー数5で実行した場合、このような結果画面になります。
#### 実行結果2
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3493131/b12a2df0-44c5-1be5-2278-32a72b9eeb79.png)
[プレイヤー数の入力](#プレイヤー数の入力)のときに実装した厳格な不正入力検知もしっかり機能しています。

## さいごに
今回はモンスターハンターワールド：アイスボーンにおけるクエスト同行メンバーをランダムで決めるプログラムを作りました。今でも友人たちとモンハンをする際、5人以上メンバーが集まったときはこのプログラムを使ってチーム分けを行っています。

モンハンがメインになっていますが、このプログラムのほとんどは基礎的なことしか使っていません（内包表記はちょっと難しいかも？）。そういう意味ではPythonの基礎の確認として使うこともできるかもしれません。

長々とした記事でしたが、ここまで読んでくださった皆様、ありがとうございました。
ワイルズが待ち遠しい！！

#### 参考
* [モンスターハンター公式サイト](https://www.monsterhunter.com/ja/)
* [モンスターハンターライズ公式サイト](https://www.monsterhunter.com/rise/ja/)
* [モンスターハンターワールド：アイスボーン公式サイト](https://www.monsterhunter.com/world-iceborne/ja/)
* [モンスターハンターワイルズ公式サイト](https://www.monsterhunter.com/wilds/ja-jp/)
※本プログラムで使用しているクエストリストには、モンスターハンターワールド：アイスボーンに出てくるモンスターの名前または俗称が含まれています。
※実行結果にプレイヤー2~5としてでてきたキャラクターは、モンスターハンターライズで実際にゲームに登場するキャラクター名となります。