76
人人人人人人人人人人人人人人 人人人人人人人人人人 人人人人人人人人人 人人 人人人人人 人人人人

人狼知能エージェント作成方法

Embed Size (px)

Citation preview

人狼知能エージェント作成方法

東京大学工学系研究科 システム創成学専攻大橋・鳥海研究室

梶原健吾

目次• 自作 Player を用いた人狼の実行方法– プロジェクト作成– ビルド・パスの構成– 実行の構成

• 実際に Player を実装– 占い師を実装

新規プロジェクトの作成(1)ファイル>新規> Java プロジェクト

新規プロジェクトの作成(2)• 適当なプロジェクト名を入力して完了

(ここでは KajiwaraAgent )

ビルド・パスの構成• プレイヤー作成に必要な jar ファイルをビ

ルド・パスに追加– “ http://www.aiwolf.org/ ” から「資料>人狼知

能サーバ> aiwolf-ver0.2.1 」をダウンロード– 中身は 3 つの jar ファイル

Jar ファイルの概要• aiwolf-client– プレイヤーを作成する際に用いる

• aiwolf-common– server と client 両方で用いる

• aiwolf-server– ゲームを実行する際に用いる

新規フォルダーの作成(1)プロジェクトを右クリック>新規>フォルダー

新規フォルダーの作成(2)• 適当なフォルダー名を入力して完了(こ

こでは lib )

新規フォルダーの作成(3)• フォルダー内に 3 つの jar ファイルをド

ラッグ&ドロップ  →これらの jar ファイルをビルド・パスに追加

ビルド・パスの構成(1)プロジェクトを右クリック>ビルド・パス>ビルド・パスの構成

ビルド・パスの構成(2)ライブラリー> Jar 追加

ビルド・パスの構成(3)フォルダー内に入れた 3 つの jar ファイルを選択して「 OK 」

自作プレイヤーでゲーム実行1. AbstractRoleAssignPlayer を継承したクラス

を作成2. 実行の構成を変更3. 各役職の Player を作成4. RoleAssignPlayer に各役職の Player をセッ

自作プレイヤーでゲーム実行1. AbstractRoleAssignPlayer を継承したクラス

を作成2. 実行の構成を変更3. 各役職の Player を作成4. RoleAssignPlayer で各役職の Player をセッ

まず実行できる環境を整える

新規パッケージ作成(1)KajiwaraAgent > src を右クリック>新規>パッケージ

新規パッケージ作成(2)• パッケージ名を入力して完了– パッケージ名は自分のアドレスを逆から入力• 例) [email protected] → org.aiwolf.myAgent

新規クラス作成(1)作成したパッケージを右クリック>新規>クラス

新規クラス作成(2)スーパークラス>参照から

”AbstractRoleAssignPlayer” を選択

新規クラス作成(3)• 適当なクラス名を入力して完了(ここで

は, KajiwaraRoleAssignPlayer )

実行の構成プロジェクトを右クリック>実行>実行の構成

実行の構成(1)• メイン・クラス>検索から

“RoleRequestStarter” を選択

実行の構成(2)引数>プログラムの引数に下記を入力  「 -n 11 -c org.aiwolf.KajiwaraAgent.KajiwaraRoleAssignPlayer SEER 」  プレイヤー数 パッケージ名 クラス名    希望役職  

実行の構成(3)10 人のサンプルプレイヤーと自分のプレイヤーでゲームを行う状態(自分は占い師だと設定)

サンプル

サンプル

サンプル

自分・・・

サーバ

実行結果• 適用→実行 を押すと実行結果が表示さ

れてゲームが実行出来る状態に

Player の実装

自作プレイヤーでゲーム実行1. AbstractRoleAssignPlayer を継承したクラス

を作成2. 実行の構成を変更3. 各役職の Player を作成4. RoleAssignPlayer で各役職の Player をセッ

例として占い師を作ってみましょう

新規クラスの作成• MyRoleAssignPlayer の作成と同様に新規ク

ラスを作成– スーパークラスは AbstractSeer を選択

MyRoleAssignPlayer を変更• MyRoleAssignPlayer に次のコンストラクタを

追加

– 役職として占い師を振り分けられた時にKajiwaraSeerPlayer を呼び出す

– 他の役職の場合はデフォルトでサンプルプレイヤーが呼び出される

public MyRoleAssignPlayer(){ setSeerPlayer(new KajiwaraSeer());}

ゲーム実行• もう一度ゲームを実行して動けば OK• KajiwaraSeer はまだ実装すべきメソッドを

実装していないから適当な行動をとる

実装すべきメソッドとは?

Player インターフェースPlayer インターフェース内の Abstract Method

ゲームの各タイミングでサーバがプレイヤーのこれらのメソッドを呼び出す

• getName()• update(GameInfo gameinfo)• initialize(GameInfo gameinfo)• dayStart()• talk()• whisper()

• vote()• attack()• divine()• guard()• finish()

ゲームの流れinitialize(GameInfo)

vote()divine() (占い師のみ)attack() (人狼のみ)guard() (狩人のみ)

update(GameInfo)whisper() (人狼のみ)

talk() 

update(GameInfo)dayStart()

update(GameInfo)finish()

会話終了

ゲーム終了

1 日

各メソッドの戻り値

• getName()• update(GameInfo

gameinfo)• initialize(GameInfo

gameinfo)• dayStart()• talk()• whisper()

• vote()• attack()• divine()• guard()• finish()

全体,もしくは囁きで発話するメソッド戻り値: String

対象プレイヤーを選択するメソッド戻り値: Agent

MySeer の実装• MySeer が継承した AbstractSeer– 占い師に必要ないメソッド( attack, guard

等)を事前に消してある– 毎朝,サーバから送られた占い結果を List に

格納する 等の簡単な実装がしてある

• 実装する必要のあるメソッド– talk(), divine(), vote() の3つ

実際に実装してみましょう

実装の流れ1. vote の実装 (1)– ランダムに投票

2. divine の実装– ランダムに占う

3. vote の実装 (2)– 占い結果を考慮して投票

4. talk の実装– カミングアウト,占い結果の報告

実装の流れ1. vote の実装 (1)– ランダムに投票

2. divine の実装– ランダムに占う

3. vote の実装 (2)– 占い結果を考慮して投票

4. talk の実装– カミングアウト,占い結果の報告

vote() の実装実装例– 生きているプレイヤーから自分を除いてラン

ダムに投票してみる

@Override public Agent vote(){ // 投票対象の候補者リスト List<Agent> voteCandidates = new ArrayList<Agent>();

// 生きているプレイヤーを候補者リストに加える voteCandidates.addAll(getLatestDayGameInfo().getAliveAgentList());

// 自分自身と白判定のプレイヤーは候補から外す voteCandidates.remove(getMe());

return randomSelect(voteCandidates); }

vote() の実装

↑getLatestDayGameInfo() は AbstractPlayer 内のメソッドで,最新の GameInfo を返すGameInfo はサーバから送られてくるゲーム内情報.ログや占い結果等の全ての情報はここから取得する

続く

vote() の実装/** * 引数の Agent のリストからランダムに Agent を選択する * @param agentList * @return */private Agent randomSelect(List<Agent> agentList){ int num = new Random().nextInt(agentList.size()); return agentList.get(num);}

vote() の実装 ( 全体像 ) @Override public Agent vote(){ // 投票対象の候補者リスト List<Agent> voteCandidates = new ArrayList<Agent>();

// 生きているプレイヤーを候補者リストに加える voteCandidates.addAll(getLatestDayGameInfo().getAliveAgentList());

// 自分自身と白判定のプレイヤーは候補から外す voteCandidates.remove(getMe());

return randomSelect(voteCandidates); }

/** * 引数の Agent のリストからランダムに Agent を選択する * @param agentList * @return */ private Agent randomSelect(List<Agent> agentList){ int num = new Random().nextInt(agentList.size()); return agentList.get(num); }}

次は divine() を実装してみよう

実装の流れ1. vote の実装 (1)– ランダムに投票

2. divine の実装– ランダムに占う

3. vote の実装 (2)– 占い結果を考慮して投票

4. talk の実装– カミングアウト,占い結果の報告

divine() の実装実装例– 自分を除いて , まだ占っていないプレイヤー

からランダムに選択

※ 既に死んでいるプレイヤーから選択しないように注意

divine() の実装@Overridepublic Agent divine() { // 占い対象の候補者リスト List<Agent> divineCandidates = new ArrayList<Agent>();

// 生きているプレイヤーを候補者リストに加える divineCandidates.addAll(getLatestDayGameInfo().getAliveAgentList()); // 自分自身と既に占ったことのあるプレイヤーは候補から外す divineCandidates.remove(getMe()); for(Judge judge: getMyJudgeList()){ if(divineCandidates.contains(judge.getTarget())){ divineCandidates.remove(judge.getTarget()); } }

続く

divine() の実装 if(divineCandidates.size() > 0){ // 候補者リストからランダムに選択 return randomSelect(divineCandidates); }else{ // 候補者がいない場合は自分を占い return getMe(); }}

divine() の実装(全体像)@Overridepublic Agent divine() { // 占い対象の候補者リスト List<Agent> divineCandidates = new ArrayList<Agent>();

// 生きているプレイヤーを候補者リストに加える divineCandidates.addAll(getLatestDayGameInfo().getAliveAgentList()); // 自分自身と既に占ったことのあるプレイヤーは候補から外す divineCandidates.remove(getMe()); for(Judge judge: getMyJudgeList()){ if(divineCandidates.contains(judge.getTarget())){ divineCandidates.remove(judge.getTarget()); } }

if(divineCandidates.size() > 0){ // 候補者リストからランダムに選択 return randomSelect(divineCandidates); }else{ // 候補者がいない場合は自分を占い return getMe(); }}

次は占い結果を投票に反映させてみましょう

実装の流れ1. vote の実装 (1)– ランダムに投票

2. divine の実装– ランダムに占う

3. vote の実装 (2)– 占い結果を考慮して投票

4. talk の実装– カミングアウト,占い結果の報告

vote() の実装2実装例– 占いで人狼を見つけていたらその中からランダム– 人狼を見つけていなければ,自分と白判定が出たプ

レイヤー以外からランダム– まだ占っていない場合は,自分以外からランダム

※ 既に死んでいるプレイヤーから選択しないように注意

vote() の実装2@Overridepublic Agent vote() { List<Agent> whiteAgent = new ArrayList<Agent>(), // 白判定だったプレイヤー blackAgent = new ArrayList<Agent>(); //黒判定だったプレイヤー

//今まで占ったプレイヤーを whiteAgent と blackAgent に分ける for(Judge judge: getMyJudgeList()){ if(getLatestDayGameInfo().getAliveAgentList().contains(judge.getTarget())){ switch (judge.getResult()) { case HUMAN: whiteAgent.add(judge.getTarget());] break; case WEREWOLF: blackAgent.add(judge.getTarget()); } } }

続く

vote() の実装2 if(blackAgent.size() > 0){ //blackAgent がいればその中から選択 return randomSelect(blackAgent); }else{ // 投票対象の候補者リスト List<Agent> voteCandidates = new ArrayList<Agent>();

voteCandidates.addAll(getLatestDayGameInfo().getAliveAgentList()); //生きているプレイヤーを候補者リストに加える

// 自分自身と白判定のプレイヤーは候補から外す voteCandidates.remove(getMe()); voteCandidates.removeAll(whiteAgent);

return randomSelect(voteCandidates); }}

vote() の実装2(全体像)@Overridepublic Agent vote() { List<Agent> whiteAgent = new ArrayList<Agent>(), // 白判定だったプレイヤー blackAgent = new ArrayList<Agent>(); //黒判定だったプレイヤー

for(Judge judge: getMyJudgeList()){ if(getLatestDayGameInfo().getAliveAgentList().contains(judge.getTarget())){ switch (judge.getResult()) { case HUMAN: whiteAgent.add(judge.getTarget());] break; case WEREWOLF: blackAgent.add(judge.getTarget()); } } }

if(blackAgent.size() > 0){ return randomSelect(blackAgent); }else{ // 投票対象の候補者リスト List<Agent> voteCandidates = new ArrayList<Agent>();

voteCandidates.addAll(getLatestDayGameInfo().getAliveAgentList()); // 生きているプレイヤーを候補者リストに加える

// 自分自身と白判定のプレイヤーは候補から外す voteCandidates.remove(getMe()); voteCandidates.removeAll(whiteAgent);

return randomSelect(voteCandidates); }}

次は占い結果を発話してみましょう

実装の流れ1. vote の実装 (1)– ランダムに投票

2. divine の実装– ランダムに占う

3. vote の実装 (2)– 占い結果を考慮して投票

4. talk の実装– カミングアウト,占い結果の報告

talk, whisper での発話主な発話は  org.aiwolf.client.lib.TemplateTalkFactory( 全体用 )  org.aiwolf.client.lib.TemplateWhisperFactory( 囁き用 )で簡単に作成可能

TemplateTalkFactory クラス• estimate(Agent, Role)

– Agent の役職は Role だと思う

• comingout(Agent, Role)– Agent が Role をカミングアウト

する

• divined(Agent, Species)– Agent を占った結果 Species だっ

• inquested(Agent, Species)– Agent の霊能結果が Species だっ

• guarded(Agent)– Agent を守った

• vote(Agent)– Agent に投票する

• (dis)agree(TalkType, day, id)– 対象の発話に同意(反対)する

• skip()– まだ今日話したいことがある

• over()– もう今日は話すことない

TemplateTalkFactory の使用例

// 占いの情報の取得Judge judge = getLatestDayGameInfo().getDivineResult();

// 発話の作成String talk = TemplateTalkFactory.divined(judge.getTarget(), judge.getResult());

今日の占い結果を報告する発話を生成

talk() の実装実装例– 占いで人狼を見つけたらカミングアウト– カミングアウトした後は占い結果を報告する– 話すことが無ければ” Over”

(プレイヤーが全員 Over を返せば会話のターンが終了)

talk() の実装boolean isComingOut = false; // 既に役職のカミングアウトをしているか

// 報告済みの Judge を格納List<Judge> myToldJudgeList = new ArrayList<Judge>();

@Overridepublic String talk() {

// 占いで人狼を見つけたらカミングアウトする if(!isComingOut){ for(Judge judge: getMyJudgeList()){ if(judge.getResult() == Species.WEREWOLF){ // 占い結果が人狼の場合 String comingoutTalk = TemplateTalkFactory.comingout(getMe(), getMyRole()); isComingOut = true; return comingoutTalk; } } }

続く

talk() の実装 // カミングアウトした後は,まだ言っていない占い結果を順次報告 else{ for(Judge judge: getMyJudgeList()){ if(!myToldJudgeList.contains(judge)){ // まだ報告していない Judge の場合 String resultTalk = TemplateTalkFactory.divined(judge.getTarget(), judge.getResult()); myToldJudgeList.add(judge); return resultTalk; } } }

// 話すことが無ければ会話終了 return Talk.OVER;}

talk() の実装(全体像)//既に役職のカミングアウトをしているかboolean isComingOut = false;

@Overridepublic String talk() {

//占いで人狼を見つけたらカミングアウトする if(!isComingOut){ for(Judge judge: getMyJudgeList()){ if(judge.getResult() == Species.WEREWOLF){ //占い結果が人狼の場合 String comingoutTalk = TemplateTalkFactory.comingout(getMe(), getMyRole()); isComingOut = true; return comingoutTalk; } } }

//カミングアウトした後は,まだ言っていない占い結果を順次報告 else{ for(Judge judge: getMyJudgeList()){ if(!myToldJudgeList.contains(judge)){ //まだ報告していないJudgeの場合 String resultTalk = TemplateTalkFactory.divined(judge.getTarget(), judge.getResult()); myToldJudgeList.add(judge); return resultTalk; } } }

//話すことが無ければ会話終了 return Talk.OVER;}

次は他の人の発話を読み込んでみましょう

ログの読み込みログは GameInfo.getTalkList() で List<Talk>型として取得Talk クラス内のメソッドは 4 つ

– getAgent() :発話した Agent を取得– getContent() :発話内容( String )を取得– getDay() :発話日( int )を取得– getIdx() :その日の何番目の発話か( int )を取得

ログの読み込みTalk.getContent() で得られる String の中身は

“DIVINED Agent[04] HUMAN”のように人狼言語で記載   → Utterance クラスでパース//Utterance クラスのコンストラクタの引数に発話内容のString を入れると自動的にパースされるUtterance utterance = new Utterance(talk.getContent());

Utterance クラスの使い方戻り値 メソッド名と説明String getText()

 発話内容をそのまま返す

Topic getTopic() 発話の Topic を返す( COMINGOUT や DIVINED 等)

Agent getTarget() 発話内の目的語となるプレイヤーを返す(例えば” DIVINED Agent[01] HUMAN”  → Agent[01] )

Role getRole() 発話の目的語となる役職を返す(例えば” COMINGOUT Agent[02] SEER”  →  SEER )

Species getResult() 占い (霊能 ) の結果を返す(例えば” INQUESTED Agent[03] WEREWOLF”  →  WEREWOLF )

TalkType getTalkType()  Topic が AGREE,DISAGREE の時,対象発話の TalkType (全体ログ or 囁き)を返す

int getTalkDay()  Topic が AGREE,DISAGREE の時,対象発話の発話日を返す

int getTalkID()  Topic が AGREE,DISAGREE の時,対象発話の発話 ID を返す

@Overridepublic void update(GameInfo gameInfo) { super.update(gameInfo); //今日のログを取得 List<Talk> talkList = gameInfo.getTalkList();

for(int i = 0; i < talkList.size(); i++){ Talk talk = talkList.get(i); // 発話をパース Utterance utterance = new Utterance(talk.getContent()); // 発話のトピックごとに処理 switch (utterance.getTopic()) { case COMINGOUT: // カミングアウトの発話の処理 break; case DIVINED: // 占い結果の発話の処理 break; } }}

ログの読み込み

このままだと, update() が呼ばれる度に,その日のログを全部読み込む → 一度読み込んだ Talk は読まないように変更してみる

ログの読み込み2//その日のログの何番目まで読み込んだかint readTalkNum = 0;

@Overridepublic void dayStart(){ super.dayStart(); readTalkNum = 0;}

オレンジ部分:追加赤部分:修正

@Overridepublic void update(GameInfo gameInfo) { super.update(gameInfo); //今日のログを取得 List<Talk> talkList = gameInfo.getTalkList();

for(int i = readTalkNum; i < talkList.size(); i++){ Talk talk = talkList.get(i); // 発話をパース Utterance utterance = new Utterance(talk.getContent()); // 発話のトピックごとに処理 switch (utterance.getTopic()) { case COMINGOUT: // カミングアウトの発話の処理 break; case DIVINED: // 占い結果の発話の処理 break; } readTalkNum++; }}

オレンジ部分:追加赤部分:修正

ログの読み込み2

ログの読み込み2(全体像)//その日のログの何番目まで読み込んだかint readTalkNum = 0;

@Overridepublic void dayStart(){ super.dayStart(); readTalkNum = 0;}

@Overridepublic void update(GameInfo gameInfo) { super.update(gameInfo); //今日のログを取得 List<Talk> talkList = gameInfo.getTalkList();

for(int i = readTalkNum; i < talkList.size(); i++){ Talk talk = talkList.get(i); // 発話をパース Utterance utterance = new Utterance(talk.getContent()); // 発話のトピックごとに処理 switch (utterance.getTopic()) { case COMINGOUT: // カミングアウトの発話の処理 break; case DIVINED: // 占い結果の発話の処理 break; } readTalkNum++; }}

オレンジ部分:追加赤部分:修正

←自分以外に占い師 CO しているプレイヤーを敵リストに追加してみましょう

//偽占い師 CO しているプレイヤーのリスト List<Agent> fakeSeerCOAgent = new ArrayList<Agent>();

@Overridepublic void update(GameInfo gameInfo) { super.update(gameInfo); //今日のログを取得 List<Talk> talkList = gameInfo.getTalkList();

for(int i = readTalkNum; i < talkList.size(); i++){ Talk talk = talkList.get(i); // 発話をパース Utterance utterance = new Utterance(talk.getContent()); // 発話のトピックごとに処理 switch (utterance.getTopic()) { case COMINGOUT: // 自分以外で占い師 CO しているプレイヤーの場合 if(utterance.getRole() == Role.SEER && !talk.getAgent().equals(getMe())){ fakeSeerCOAgent.add(utterance.getTarget()); } break; case DIVINED: // 占い結果の発話の処理 break; } readTalkNum++; }}

オレンジ部分:追加赤部分:修正

ログの読み込み3 ( 全体像 )

エージェントを作り終えたら・・・

Jar ファイル作成方法(1)プロジェクトを右クリック>エクスポート

Jar ファイル作成方法(2)JAVA > JAR ファイル を選択して次へ

Jar ファイル作成方法(3)• プロジェクト全てを選択• エクスポート先を設定

完了

補足

Player の各メソッドの説明• initialize(GameInfo)– ゲーム開始時に一度だ

け呼ばれる– サーバから送られてく

る GameInfo を取得• update(GameInfo)– 各行動の前に呼ばれる– サーバから送られてく

る GameInfo を取得• dayStart()

– 日の初めに呼ばれる

• finish()– ゲームが終了した時に

呼ばれる

Player の各メソッドの説明

• vote()– 投票する相手を選択す

る• attack()– 襲撃する相手を選択す

る– 人狼のみ呼ばれる

• divine()– 占いする相手を選択す

る– 占い師のみ呼ばれる

• guard()– 護衛する相手を選択す

る– 狩人のみ呼ばれる

1 日の終わりに呼ばれるメソッド

Agent を返す必要あり

Player の各メソッドの説明

• talk()– 全体に対して発話する– 全員呼ばれるメソッド

• whisper()– 人狼だけに対して発話

する– 人狼のプレイヤーだけ

が使用するメソッド

発話のメソッド

String を返す必要あり

GameInfo の説明戻り値 メソッド名と説明int

getDay() 日にちを返す

RolegetRole() 自分の役職を返す

AgentgetAgent() 自分 (Agent型 ) を返す

List<Agent>getAgentList() 全プレイヤーのリストを返す

SpeciesgetMediumResult() 霊能結果を返す (霊能者のみ )

SpeciesgetDivineResult() 占い結果を返す ( 占い師のみ )

AgentgetExecutedAgent() 処刑されたプレイヤーを返す

AgentgetAttackedAgent() 襲撃されたプレイヤーを返す

List<Vote>getVoteList() 処刑の際の投票リストを返す

List<Vote>getAttackVoteList() 襲撃の際の人狼による投票のリストを返す ( 人狼のみ )

List<Talk>getTalkList() 会話のログを返す

List<Talk>getWhisperList() 囁きのログを返す ( 人狼のみ )

List<Agent>getAliveAgentList() 生きているプレイヤーのリストを返す

Map<Agent, Status>getStatusMap() 各プレイヤーの生死の状態を返す

Map<Agent, Role>getRoleMap() 各プレイヤーの役職を返す.ゲーム中は自分の分かる役

職のみ(村人なら自分だけ,人狼なら仲間の人狼も) ゲーム終了時は全員の役職が分かる.