Upload
kengo009
View
1.232
Download
4
Embed Size (px)
DESCRIPTION
How to make AIWolf agent
Citation preview
人狼知能エージェント作成方法
東京大学工学系研究科 システム創成学専攻
大橋・鳥海研究室
梶原健吾
目次
• 自作Playerを用いた人狼の実行方法
–ビルド・パスの構成,実行の構成
• 実際にPlayerを実装
–占い師を実装
新規プロジェクトの作成
ファイル>新規>Javaプロジェクト
新規プロジェクトの作成
• 適当なプロジェクト名を入力して完了(ここではMyAgent)
ビルド・パスの構成
• プレイヤー作成に必要なjarファイルをビルド・パスに追加
– http://www.aiwolf.org/から「資料>人狼知能サーバ>aiwolf-ver0.1.13」をダウンロード
–中身は3つのjarファイル
Jarファイルの概要
• aiwolf-client
–プレイヤーを作成する際に用いる
• aiwolf-common
– serverとclient両方で用いる
• aiwolf-server
–ゲームを実行する際に用いる
新規フォルダーの作成
プロジェクトを右クリック>新規>フォルダー
新規フォルダーの作成
• 適当なフォルダー名を入力して完了
新規フォルダーの作成
• フォルダー内に3つのjarファイルをドラッグ&ドロップ
→これらのjarファイルをビルド・パスに追加
ビルド・パスの構成
プロジェクトを右クリック>ビルド・パス>ビルド・パスの構成
ビルド・パスの構成
ライブラリー>Jar追加
ビルド・パスの構成
フォルダー内に入れた3つのjarファイルを選択して「OK」
自作プレイヤーでゲーム実行
1. AbstractRoleAssignPlayerを継承したクラスを作成
2. 実行の構成を変更
3. 各役職のPlayerを作成
4. RoleAssignPlayerで各役職のPlayerをセット
自作プレイヤーでゲーム実行
1. AbstractRoleAssignPlayerを継承したクラスを作成
2. 実行の構成を変更
3. 各役職のPlayerを作成
4. RoleAssignPlayerで各役職のPlayerをセット
まず実行できる環境を整える
新規パッケージ作成
MyAgent>srcを右クリック>新規>パッケージ
新規パッケージ作成
• 適当なパッケージ名を入力して完了
新規クラス作成
作成したパッケージを右クリック>新規>クラス
新規クラス作成
スーパークラス>参照から
”AbstractRoleAssignPlayer”を選択
新規クラス作成
• 適当なクラス名を入力して完了
実行の構成
プロジェクトを右クリック>実行>実行の構成
実行の構成
• メイン・クラス>検索から
“RoleRequestStarter”を選択
実行の構成
引数>プログラムの引数に下記を入力
「-n 11 -c org.aiwolf.myAgent.MyRoleAssignPlayer SEER」
プレイヤー数 パッケージ名 クラス名 希望役職
実行の構成
10人のサンプルプレイヤーと自分のプレイヤーでゲームを行う状態(自分は占い師だと設定)
プレイヤー
プレイヤー
プレイヤー
自分・・・
サーバ
実行結果
• 適用→実行 を押すと実行結果が表示されてゲームが実行出来る状態に
Playerの実装
自作プレイヤーでゲーム実行
1. AbstractRoleAssignPlayerを継承したクラスを作成
2. 実行の構成を変更
3. 各役職のPlayerを作成
4. RoleAssignPlayerで各役職のPlayerをセット
例として占い師を作ってみよう
新規クラスの作成
• MyRoleAssignPlayerの作成と同様に新規クラスを作成
–スーパークラスはAbstractSeerPlayerを選択
MyRoleAssignPlayerを変更
• MyRoleAssignPlayerに次のようなコンストラクタを追加
–役職として占い師を振り分けられた時にMySeerPlayerを呼び出す
–他の役職の場合はデフォルトでサンプルプレイヤーが呼び出される
public MyRoleAssignPlayer(){setSeerPlayer(new MySeerPlayer());
}
ゲーム実行
• もう一度ゲームを実行→エラーが出ればOK
• MySeerPlayerはまだ実装すべきメソッドを実装していないからエラーが発生
実装すべきメソッドとは?
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日
Playerインターフェース
Playerインターフェース内のAbstract Method
上記6つのメソッドは適切な値を返さないとエラー発生
– 今は全部nullを返しているからエラー
• getName()• update(GameInfo gameinfo)• initialize(GameInfo gameinfo)• dayStart()• talk()• whisper()
• vote()• attack()• divine()• guard()• finish()全体,もしくは囁き
で発話するメソッド戻り値:String
対象プレイヤーを選択するメソッド戻り値:Agent
MySeerPlayerの実装
• MySeerPlayerが継承したAbstractSeerPlayer
–占い師に必要ないメソッド(attack, guard等)を事前に消してある
–毎朝,サーバから送られた占い結果をListに格納する等の簡単な実装がしてある
• talk, divine, voteを実装すればちゃんと動く
実際に実装してみましょう
戻り値を適切な値に変更
とりあえずエラーが出ないように占い師はtalk()でString型を,vote()とdivine()でAgent型を返せば良い
↓
talk()で空白文字を返す
vote(),divine()で自分自身を返す
@Override
public Agent divine() {
return getMe();
}
@Override
public void finish() {
}
@Override
public String talk() {
return "";
}
@Override
public Agent vote() {
return getMe();
}
これで実行は可能に.次はvote()をちゃんと実装してみる
←getMe()はAbstractPlayer内のメソッドで,自分自身を返す
戻り値を適切な値に変更
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()を実装してみよう
divine()の実装
実装例
–自分を除いて,まだ占っていないプレイヤーからランダムに選択
※既に死んでいるプレイヤーから選択しないように注意
divine()の実装
@Override
public 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()の実装(全体像)
@Override
public 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();
}
}
次は占い結果を投票に反映させてみましょう
vote()の実装2
実装例– 占いで人狼を見つけていたらその中からランダム
– 人狼を見つけていなければ,自分と白判定が出たプレイヤー以外からランダム
– まだ占っていない場合は,自分以外からランダム
※既に死んでいるプレイヤーから選択しないように注意
vote()の実装2@Override
public 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()の実装2if(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(全体像)@Override
public 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);
}
}
次は占い結果を発話してみましょう
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;
@Override
public 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クラスでパース
//コンストラクタに発話内容の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を返す
@Override
public 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;
@Override
public void dayStart(){
super.dayStart();
readTalkNum = 0;
}
オレンジ部分:追加赤部分:修正
@Override
public 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;
@Override
public void dayStart(){
super.dayStart();
readTalkNum = 0;
}
@Override
public 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(全体像)
補足
Playerの各メソッドの説明
• initialize(GameInfo)– ゲーム開始時に一度だけ呼ばれる
– サーバから送られてくるGameInfoを取得
• update(GameInfo)– 各行動の前に呼ばれる
– サーバから送られてくるGameInfoを取得
• dayStart()– 日の初めに呼ばれる
• finish()– ゲームが終了した時に呼ばれる
Playerの各メソッドの説明
• vote()– 投票する相手を選択する
• attack()– 襲撃する相手を選択する
– 人狼のみ呼ばれる
• divine()– 占いする相手を選択する
– 占い師のみ呼ばれる
• guard()– 護衛する相手を選択する
– 狩人のみ呼ばれる
1日の終わりに呼ばれるメソッド
Agentを返す必要あり
Playerの各メソッドの説明
• talk()– 全体に対して発話する
– 全員呼ばれるメソッド
• whisper()– 人狼だけに対して発話する
– 人狼のプレイヤーだけが使用するメソッド
発話のメソッド
Stringを返す必要あり
GameInfoの説明戻り値 メソッド名と説明
intgetDay()
日にちを返す
RolegetRole()
自分の役職を返す
AgentgetAgent()
自分(Agent型)を返す
List<Agent>getAgentList()
全プレイヤーのリストを返す
SpeciesgetMediumResult()
霊能結果を返す(霊能者のみ)
SpeciesgetDivineResult()
占い結果を返す(占い師のみ)
AgentgetExecutedAgent()
処刑されたプレイヤーを返す
AgentgetAttackedAgent()
襲撃されたプレイヤーを返す
List<Vote>getVoteList()
処刑の際の投票リストを返す
List<Vote>getAttackVoteList()
襲撃の際の人狼による投票のリストを返す(人狼のみ)
List<Talk>getTalkList()
会話のログを返す
List<Talk>getWhisperList()
囁きのログを返す(人狼のみ)
List<Agent>getHumanList()
人間のプレイヤーのリストを返す(ゲーム終了時のみ)
List<Agent>getWolfList()
人狼のプレイヤーのリストを返す(ゲーム終了時のみ)
List<Agent>getAliveAgentList()
生きているプレイヤーのリストを返す
Map<Agent, Status>getStatusMap()
各プレイヤーの生死の状態を返す
Map<Agent, Role>getRoleMap()
各プレイヤーの役職を返す(ゲーム終了時のみ)