BETA

Rails + Heroku + LINE Messaging APIで人狼のゲームマスターを代行するbotを作った

投稿日:2019-02-16
最終更新:2019-02-16

はじめに

プログラミングの勉強がてら何か作ってみたくなり、「人狼GMくん」という人狼のGM代行LINEbotを製作しました。

自己紹介

  • プログラミングとは無関係の文系社会人
  • 中学生時代にC++、Perlなどを齧り、CGIの掲示板製作などに挑戦するも、成果物を発表できる場所がなかったのと、当時はお金をかけずにできることの範囲も限られていたことから、モチベーションが維持できず挫折
  • その後は大学の教養科目でRuby(だったかな?うろ覚え)に触れた以外、特にプログラミングとは無縁のまま今に至る

成果物

人狼GMくん(β) 〜ゲームマスター不要のLINE人狼を楽しもう!〜
https://wolfbot.netlify.com/

概要

一緒に遊ぶメンバーでLINEグループを作成して、そのグループに人狼GMくんを招待するだけで、ゲームマスターの役割をbotに代行させることができます。

特徴

  • 長期人狼向け(短期人狼やリアル人狼にも使えますが、タイマー機能はないので、手動で時間管理が必要)
  • 操作はすべてCUI(慣れてしまえばGUIより楽かも?)

操作イメージとコード

操作はすべてコマンド入力です。
「@」から始める文字列をコマンドとして認識するようになっています。

ゲームの開始

ゲームを開始する際は、「@村作成」で新規の村を立てます。
村を作成するとプレイヤー募集モードになるので、参加するプレイヤーは「(プレイヤー名)@参加」でゲームに参加します。

「@村作成」コマンドが入力されると、以下のコードでVillageモデルクラスのインスタンスを生成します。
Villageモデルクラスではゲームの進行状況やルール設定などを管理しています。
(多値を取る設定値は、Jinro以下の名前空間に定数として定義しています。)
また、owner_groupにLINEグループごとに一意に割り振られる識別情報(を念のため暗号化したもの)を記録することで、LINEグループへの紐付けを行っています。

unless Village.exists?(:owner_group => @owner_group)  
      @village = Village.create(:day => 0,  
                                :status => Jinro::States::PREPARING,  
                                :owner_group => @owner_group,  
                                :continuous_guard => false,  
                                :self_guard => false,  
                                :opening_divination => Jinro::Regulation::RANDOM_WHITE_DIVINATION,  
                                :tie_vote_handling => Jinro::Regulation::REVOTE,  
                                :limit_speech_times => 0,  
                                :limit_speech_chars => 0,  
                                :is_debug => false )  
      message = <<~"EOS"  
      村を作成しました。  
      この村に参加するプレイヤーは、このグループにて、  

      プレイヤー名@参加  

      と発言してください。  

      全員が村に参加したら、このグループにて、  

      @募集終了  

      と発言してください。  
      EOS  
else  
      message = '既に村が作成されています。'  
end

次に、「@参加」コマンドが入力されると、以下のコードでPlayerモデルクラスのインスタンスを生成します。
Playerモデルクラスでは各プレイヤーのユーザー情報や役職、生死、投票先、能力の発動先などを管理しています。
line_useridにLINEユーザーごとに一意に割り振られる識別情報(を暗号化したもの)を保存することでユーザーとの紐付けを行っています。
上述のVillageモデルとの間では、a village has many players の関係で紐付けしています。

player = Player.create(:village_id => @village.id,  
                                  :name => player_name,  
                                  :line_userid => line_userid,  
                                  :role => "未設定",  
                                  :life_status => true,  
                                  :vote_to => "unvoted",  
                                  :act_to => "unacted",  
                                  :login_status => true )  
message = "プレイヤー名:#{player.name}\nにて村に参加しました。"

配役設定

参加者の募集を終えたら、配役を設定します。
配役の設定は、「占霊狩狼狼@配役設定」のようなコマンドを入力することで行います。
例えば、上記のコマンドの場合、占い師が1人、霊能者が1人、狩人が1人、人狼が2人に設定され、残りの人数が自動で村人になります。

以下のコードで「占霊狩狼狼」などの文字列をパースして、number_of[’占い師]=1のようにハッシュへと格納しています。

def parse_casting(casting_preference)  
    number_of = Hash.new  
    Jinro::Roles::ALL.values.each { |role_abbr|  
      number_of[Jinro::Roles::ALL.key(role_abbr)] = casting_preference.count(role_abbr)  
    }  

    number_of[Jinro::Roles::VILLAGER] = 0  
    number_of[Jinro::Roles::SHARER] = 2 if number_of[Jinro::Roles::SHARER] == 1 #共有者は自動で2人に設定する  
    number_of[Jinro::Roles::IMMORALIST] = 0 if number_of[Jinro::Roles::FOX] == 0 #妖狐がいないときに背徳者を設定できないようにする  

    number_of_players = @village.players.count  
    number_of_ability_persons = number_of.values.inject(:+)  
    number_of[Jinro::Roles::VILLAGER] = number_of_players - number_of_ability_persons  

    return number_of  
end

なお、役職の名称および略称は、Jinro::Rolesに定数として定義しています。

module Jinro  
  module Roles  
    VILLAGER = '村人'  
    DIVINER = '占い師'  
    PSYCHIC = '霊能者'  
    HUNTER = '狩人'  
    SHARER = '共有者'  
    WOLF = '人狼'  
    MINION = '狂人'  
    FANATIC = '狂信者'  
    WHISPERER = 'ささやく狂人'  
    FOX = '妖狐'  
    IMMORALIST = '背徳者'  
    CAT = '猫又'  
    HANGEDMAN = 'てるてる坊主'  

    ALL = {  
      VILLAGER => '村',  
      DIVINER => '占',  
      PSYCHIC => '霊',  
      HUNTER => '狩',  
      SHARER => '共',  
      WOLF => '狼',  
      MINION => '狂',  
      FANATIC => '信',  
      WHISPERER => 'さ',  
      FOX => '狐',  
      IMMORALIST => '背',  
      CAT => '猫',  
      HANGEDMAN => 'て'  
    }  
  end  
end

この他、指定された配役が人狼のルールに適合しているかも合わせて確認しています。例えば、人狼が0人の配役や、人狼が半数以上のような配役が指定された場合はエラーを返すようにしています。

この後、オプションルール(初日占い、同数投票時の処理、狩人連続ガードの可否)などを設定する流れがあるが、省略します。

ゲーム開始〜投票〜昼ターン終了

議論自体は通常のLINEの機能を使って行うため、botとして特段の機能はない。
投票は、人狼GMくんのアカウントに対する個別トークで「(投票先プレイヤー名)@投票」と発言することで行います。

グループで「@投票終了」と発言すると、投票が締め切られ、最多得票者が処刑されます。

処刑の実行後、以下のコードにより勝敗を判定しています。
ちなみに、テストプレイでは狐を人数カウントから除外する処理を書き忘れてゲーム崩壊するなどしました。

num_players = 0  
num_werewolves = 0  
num_foxes = 0  
result = String.new  
@village.players.each { |player|  
  if player.life_status == true  
    case player.role  
    when Jinro::Roles::WOLF  
      num_players += 1  
      num_werewolves += 1  
    when Jinro::Roles::FOX  
      num_foxes += 1  
    else  
      num_players += 1  
    end  
  end  
}  
if num_foxes == 0  
  if num_werewolves == 0  
    result = 'villagers'  
  elsif num_players > num_werewolves * 2  
    result = 'continue'  
  else  
    result = 'werewolves'  
  end  
else  
  if num_werewolves == 0  
    result = 'foxes'  
  elsif num_players > num_werewolves * 2  
    result = 'continue'  
  else  
    result = 'foxes'  
  end  
end  
lynched_player = @village.players.find_by(:name => @village.last_executed_player)  
if lynched_player.role?(Jinro::Roles::HANGEDMAN)  
  result = 'hangedman'  
end

役職実行〜夜ターン終了

投票と同様に、人狼GMくんのアカウントへの個別トークで「(プレイヤー名)@占う」などと発言することにより、能力を実行できます。
以下の画像は、人狼を引き当てた場合の占い結果の例です。

すべての能力持ちプレイヤーが能力を実行したら、グループで「@夜明け」と発言することにより、翌日の昼ターンに移行します。
このタイミングで、人狼に襲撃されたプレイヤーが死亡し、勝敗判定が行われます(下の画像は狩人GJのため犠牲者なし)。
勝敗判定のロジックは先程の昼ターン終了時のものと同じです。

あとは、決着が着くまで同じ処理の繰り返しとなります。

今後の課題

使用可能な役職やオプションルールの拡張、役欠けや闇鍋モードの実装を行っていきたいです。
また、コードが稚拙(特に、製作初期に書いた部分)なので、随時書き直していきたいです。

参考

Ruby + Line Messaging API によるbot作成に基礎については、以下の記事を参考にしました。

終わりに

人狼GMくんは以下のURLに記載のQRコードから友達登録が可能ですので、是非使ってみてください。
https://wolfbot.netlify.com/

技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
駆け出しエンジニアからエキスパートまで全ての方々のアウトプットを歓迎しております!
or 外部アカウントで 登録 / ログイン する
クランチについてもっと詳しく

この記事が掲載されているブログ

@myskの技術ブログ

よく一緒に読まれる記事

0件のコメント

ブログ開設 or ログイン してコメントを送ってみよう
目次をみる
技術ブログをはじめよう Qrunch(クランチ)は、プログラマの技術アプトプットに特化したブログサービスです
or 外部アカウントではじめる
10秒で技術ブログが作れます!