はじめに
discordのボイスチャットにいるメンバーを、コマンド一つでチーム分けを自動で行ってくれるbotを、
discord公式APIラッパーのdiscord.pyを利用してpythonで作成してみました!
なお、以下の内容については、他のところでたくさん触れられてりることもあり、本記事では触れません。
- discord botのアカウント作成、および登録方法
- discord.pyのインストール方法
「実装方法とかどうでもいい!今すぐチーム分けできるbotがほしい!」なんて方は、
以下のサイトを参考にしてみてください。
Forkするリポジトリを下記のmake-teamに変えて、作成すればすぐにチーム分けbotを利用できます。
Discord Bot 最速チュートリアル【Python&Heroku&GitHub】
https://github.com/Rabbit-from-hat/make-team
動作環境
- Python 3.8.1
- discord.py 1.3.4
仕様
基本的な仕様は、以下のサイトを参考にしました。 https://ojige.hatenablog.com/entry/2018/07/26/131120
チームをつくる方法は、とりあえず3つ用意しました。
- チーム数を指定して、同じメンバー数になるようチームを作成(余剰分はチームから除外)
- チーム数を指定して、人数差がある状態でチームを作成
- チームのメンバー数を指定して作成
実行コマンド
チームを作成したい人が、ボイスチャンネルに入った状態のまま、
以下のコマンドをテキストチャンネルに入力することで、チーム作成が可能です。
ボイスチャンネルに入らないまま実行すると、botから実行できなかった旨のメッセージが届きます。
また、指定した数が0だったり、ボイスチャンネルのメンバー以上の数を指定すると、
botから実行できなかった旨のメッセージが届きます。
/team チーム数
- 指定した数のチームを作成
- メンバー数が同じになるように作成
- チーム数を指定しなくても実行可。デフォルトで"2"を指定
/team_norem チーム数
- 指定した数のチームを作成
- 人数差は考慮されないまま、指定されたチーム数を作成
- チーム数を指定しなくても実行可。デフォルトで"2"を指定
/group メンバー数
- 指定したメンバー数でチームを作成
- メンバー数を指定しない場合、デフォルトとして"1"を指定
実行例
どれも以下のような形で、botからメッセージが届く。
- コマンド成功時
- コマンド失敗時
ファイル構成
make_team/ ├main.py └modules/ └grouping.py
一つのpythonファイルにまとめてもよかったのですが、以下の理由でこの構成にしました。
- 他のbotにチーム分けのアクションを追加したいときに、一つのファイルだと面倒。
- この構成なら「grouping.py」を移動させて、移動先のコマンドを受け取る処理(main.py)だけを書き直すだけで済む。
実装
全体の流れとしては、以下の通り。
一つ一つ説明していきます。
入力されたコマンドを取得
import os import traceback import discord from discord.ext import commands from modules.grouping import MakeTeam token = os.environ['DISCORD_BOT_TOKEN'] bot = commands.Bot(command_prefix='/') """起動処理""" @bot.event async def on_ready(): print('-----Logged in info-----') print(bot.user.name) print(bot.user.id) print(discord.__version__) print('------------------------') """コマンド実行""" # メンバー数が均等になるチーム分け @bot.command() async def team(ctx, specified_num=2): make_team = MakeTeam() remainder_flag = 'true' msg = make_team.make_party_num(ctx,specified_num,remainder_flag) await ctx.channel.send(msg) # メンバー数が均等にはならないチーム分け @bot.command() async def team_norem(ctx, specified_num=2): make_team = MakeTeam() msg = make_team.make_party_num(ctx,specified_num) await ctx.channel.send(msg) # メンバー数を指定してチーム分け @bot.command() async def group(ctx, specified_num=1): make_team = MakeTeam() msg = make_team.make_specified_len(ctx,specified_num) await ctx.channel.send(msg) """botの接続と起動""" bot.run(token)
bot = commands.Bot(command_prefix='/')
ここで、botがコマンドだと認識するためのプレフィックスとなります。
今回はスラッシュ'/'にしましたが、ここを円マーク'¥'にすると、「¥team チーム数」でコマンド入力することで実行されるようになります。
@bot.event async def on_ready(): # 処理
上記の形で書くと、botが起動したときに呼び出されます。
今回は呼び出されると、自身のbotの情報を標準出力するものとなってます。
@bot.command() async def XXXX(ctx, A): # 処理
「XXXX」に、ユーザに入力してもらうコマンドを指定します。
例えば「XXXX」に"command"を指定した場合、「/command」がbotを動作させるためのコマンドになります。
引数に指定されている「ctx」は、必須となります。
この「ctx」を利用することで、ユーザ名やボイスチャンネルのステータスやら、discord上の情報を取得できます。
「A」の部分に、コマンド入力時に指定した数が入ります。
(コマンド入力時に指定するチーム数やメンバー数に相当)
メンバーリストを取得
def set_mem(self, ctx): state = ctx.author.voice # コマンド実行者のVCステータスを取得 if state is None: return False self.channel_mem = [i.name for i in state.channel.members] # VCメンバリスト取得 self.mem_len = len(self.channel_mem) # 人数取得 return True
このset_memでは、コマンド入力者のボイスチャンネルのステータス(どかしらのボイスチャンネルに入っているか)を確認しています。
ボイスチャンネルに入っている場合のみ、コマンド入力者が入っているボイスチャンネルのメンバーリストを取得しています。
ctx.author.voice
コマンド入力者のボイスチャンネルのステータスを確認することができます。
ctx.author.voice.channel.members
コマンド入力者が入っているボイスチャンネルのメンバーリストを取得することができます。
チーム分けを実施
# チーム数を指定した場合のチーム分け def make_party_num(self, ctx, party_num, remainder_flag='false'): team = [] remainder = [] # メンバーリストを取得 if self.set_mem(ctx) is False: return self.vc_state_err # 指定数の確認 if party_num > self.mem_len or party_num <= 0: return '実行できません。チーム分けできる数を指定してください。(チーム数を指定しない場合は、デフォルトで2が指定されます)' # メンバーリストをシャッフル random.shuffle(self.channel_mem) # チーム分けで余るメンバーを取得 if remainder_flag: remainder_num = self.mem_len % party_num if remainder_num != 0: for r in range(remainder_num): remainder.append(self.channel_mem.pop()) team.append("=====余り=====") team.extend(remainder) # チーム分け for i in range(party_num): team.append("=====チーム"+str(i+1)+"=====") team.extend(self.channel_mem[i:self.mem_len:party_num]) return ('\n'.join(team))
さて、ようやく今回のメインです!
今回3つのチーム分けの方法を用意しましたが、どれも処理の方法は概ね同じです。
ここでは、「チームメンバー数が均等になるチーム分け」の処理を例に、説明したいと思います。
まず、set_mem()でメンバーリストを取得したあと、リストを一度シャッフルします。
random.shuffle(self.channel_mem) # [Aさん,Bさん,Cさん,Dさん,Eさん] => [Aさん,Dさん,Bさん,Eさん,Cさん]
次に、チーム数をもとにして、均等に分けたときに余る人数を算出します。
remainder_num = self.mem_len % party_num # 余る人数 = 全体のメンバー数 % 指定されたチーム数
余る人がいる場合は、余る人数分だけ、シャッフルしたリストから末尾にいる人を除きます。
for r in range(remainder_num): remainder.append(self.channel_mem.pop()) team.append("=====余り=====") team.extend(remainder) # [Aさん,Dさん,Bさん,Eさん,Cさん] => [Aさん,Dさん,Bさん,Eさん] # 待機する人 = Cさん
以下の形で、チーム分け完了後の状況をまとめる配列に格納します。
team = [ "=====余り=====", Cさん, ]
余る人数分除いた後は、残ったメンバーリストでチーム分けを行います。
チーム分けにはスライスを使いました。チーム数分だけスライスします。
for i in range(party_num): team.append("=====チーム"+str(i+1)+"=====") team.extend(self.channel_mem[i:self.mem_len:party_num]) # メンバーリスト[ 振り分けるチームナンバー(※) : 全体のメンバー数 : 指定したチーム数 ] # ※"0"始まり(チーム1の場合、"0"となる)
スライスを利用した場合の例は以下の通りです。 * 例1: 10人を2チームに分ける場合
メンバーリスト=[A,B,C,D,E,F,G,I,J,K] 1チーム目 スライス内容 -> メンバーリスト[ 0 : 10 : 2] スライス実行後 -> チーム1[A,C,E,G,J] 2チーム目 スライス内容 -> メンバーリスト[ 1 : 10 : 2] スライス実行後 -> チーム2[B,D,F,I,K]
- 例2: 9人を3チームに分ける場合
メンバーリスト=[A,B,C,D,E,F,G,I,J] 1チーム目 スライス内容 -> メンバーリスト[ 0 : 9 : 3] スライス実行後 -> チーム1[A,D,G] 2チーム目 スライス内容 -> メンバーリスト[ 1 : 9 : 3] スライス実行後 -> チーム2[B,E,I] 3チーム目 スライス内容 -> メンバーリスト[ 2 : 9 : 3] スライス実行後 -> チーム2[C,F,J]
スライスした後、最終的には以下の配列になります。
team = [ "=====余り=====", Cさん, "=====チーム1=====", Aさん, Bさん, "=====チーム1=====", Eさん, Dさん, ]
結果をメッセージとしてbotから送信
return ('\n'.join(team))
await ctx.channel.send(msg)
チームが分け終わると、チーム分けが完了している配列をメッセージとして送信します。
配列そのままを送信すると、改行されず一行の文面として送信されます。
見づらいメッセージとなってしまうため、配列の各要素の末尾に改行コードを付けて送信します。
ctx.channel.send
コマンドが入力されたチャンネルに、メッセージを送信します。
以上が、チーム分けの大まかな実装部分になります。
ソースはgithubに載せてあるので、よかったらみてください!
https://github.com/Rabbit-from-hat/make-team
おわりに
pythonをはじめて使ったのですが、すごい書きやすくて楽しかったです。
こんな書き方しないなど、規則的におかしいところを見つけ次第直していこうと思います。
今回は基本的な(?)チーム分けしかできなかったので、このチーム分けのbotを改修してきたいなーと思います。 機会があればまた記事にします。
【この先の展開メモ】
- チーム分けのメッセージを埋め込みする
- ゲーム内特有のランク等の帯域を指定してのチーム分け
- ゲームマスタを指定して、チーム分け
- 作ったチームが気に入らなかった時のリメイクコマンドの実装
- 特定のプレイヤーを予めチームに固定した状態でのチーム分け
参考
Pythonで実用Discord Bot(discordpy解説)
PythonでDiscordボットを作る時のFAQ
Pythonで簡単なDiscord Botの作り方
PythonでDiscordBotを書く方法
Discord.py ドキュメントの歩き方
pythonでdiscordのボイスチャット内メンバーのリストを作成したい
discord.pyとherokuでDiscordBot
Grouping_bot
Pythonで始めるHeroku 2018
Discord.pyでチーム分けを自動化
TeamMaker
Discord-shuffler