Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

人狼知能セミナー資料案20170624

3,239 views

Published on

人狼知能セミナー@神戸電子専門学校用の資料

Published in: Education
  • Be the first to comment

人狼知能セミナー資料案20170624

  1. 1. 人狼知能エージェント作成方法 人狼知能プロジェクト 2017/6/24版
  2. 2. 目次 • 開発のセットアップ • 自作プレイヤーを用いた人狼の実行方法 – プロジェクトの作成 – ビルド・パスの構成 – Javadocを参照 – 自作プレイヤーでゲーム実行 • 実際にPlayerを実装 – 占い師を実装
  3. 3. セットアップ 人狼知能エージェントを開発するための環境を整える (事前準備) • Eclipse をダウンロードする • Java SDK をダウンロードする
  4. 4. 人狼知能開発プラットフォームの ダウンロード 1. 人狼知能プロジェクト のHPへアクセスする 2. 開発関連をクリック 3. 人狼知能プラットフォー ムをクリック 4. aiwolf-ver0.4.Xと (JavaDocのDL)をクリッ クし,ダウンロード 5. aiwolf-ver0.4.Xを解凍 http://aiwolf.org
  5. 5. プラットフォームのダウンロード 1. 人狼知能プロジェクト のHPへアクセスする 2. 開発関連をクリック 3. 人狼知能プラットフォー ムをクリック 4. aiwolf-ver0.4.Xと (JavaDocのDL)をクリッ クし,ダウンロード 5. aiwolf-ver0.4.Xを解凍 http://aiwolf.org
  6. 6. ファイルのダウンロード 1. 人狼知能プロジェクト のHPへアクセスする 2. 開発関連をクリック 3. 人狼知能プラット フォームをクリック 4. aiwolf-ver0.4.Xと (JavaDocのDL)をクリッ クし,ダウンロード 5. aiwolf-ver0.4.X.zipを 解凍
  7. 7. 「AIWolf-ver0.4.X.zip」の内容 AIWolf-ver0.4.Xフォルダーの中身 • 5つのJARファイル このうち開発に使用するのは以下の2つ aiwolf-client.jar , aiwolf-common.jar • 4つのシェルスクリプトファイル(*.sh) • 4つのバッチファイル(*.bat) • 2つのAutoStarter用設定ファイル (AutoStarter.ini, SampleSetting.cfg)
  8. 8. プロジェクトの新規作成 クリック Eclipseの画面 Ecliseを起動 左上にある「ファイル」から「新規」「Javaプロジェクト」の順で選択
  9. 9. プロジェクト名をつける ① 1. チーム名を考える. 決まらなければ名前等 2. プロジェクト名(チーム名+Agent)を入れる 図ではDemoAgentだが,例えば“名前+Agent”を入力 他の人と被ったらアウト! 3. 「完了」をクリック ②
  10. 10. ライブラリを入れるフォルダ作成 1. 作成したプロジェクト(例ではDemoAgent)を右 クリック 2. 新規>フォルダー とマウスで選択
  11. 11. 「lib」フォルダの作成
  12. 12. 「lib」フォルダにjarファイルをコピー 1. AIWolf-ver0.4.X.zip を解凍したフォルダ で以下の2つをマウスで選択する – aiwolf-client.jar – aiwolf-commn.jar 2. それらを「lib」にコピーする 3. JavaDocファイルは解凍しないで移動
  13. 13. 「lib」にコピーしたjarファイルは? 開発に使うJARファイルになります • aiwolf-client.jar プレイヤーを作成する際に用いる • aiwolf-common.jar プレイヤー作成とゲーム実行の両方で用いる
  14. 14. ビルド・パスを構築する(1) 開発ライブラリへパスをつなげる 自分のプロジェクト(例:DemoAgent)を右クリック ビルド・パス>ビルド・パスの構成を選択 クリック
  15. 15. ビルド・パスの構成(2) JARファイルのライブラリを追加する 1. 「ライブラリー」タブを選択 2. 「JARの追加」をクリック ① ②
  16. 16. ビルド・パスの構成(3) jarファイルを選択して完了 1. 「lib」フォルダーにコピーした2つのjarファイ ルを選択 2. 下にある「OK」をクリックして終了 ① ②
  17. 17. JavaDocを使えるようにする • JavaDoc には開発用APIの簡単な説明が書か れています. • これを使えるようにすることで,Eclipse上での 人狼知能エージェントの開発が少しだけらく になります.
  18. 18. Javadocを参照できるようにする(1) 1. 新たに表示されたaiwolf-client.jar の左の▶をクリックして,折りたたま れているものを開く 2. 「Javadocロケーション」を選択 3. 「編集」をクリック ② ① ③
  19. 19. Javadocを参照できるようにする(2/3) 1. 「アーカイブ内のJavadoc」を選択 2. 「ワークスペースファイル」を選択 3. 「アーカイブパス」 例: DemoAgent/lib/docs.zip 4. 「アーカイブ内のパス」 例: docs 5. 「検証」をクリックして確認 ① ② ③ ④ ⑤
  20. 20. Javadocを参照できるようにする(3/3) • ロケーションが有効であることが確認できたら [OK] • aiwolf-common.jarも同様にJavadocを参照
  21. 21. 自分の人狼知能エージェントを作る エージェントを作成するための準備は完了した!次は!!
  22. 22. 自分のプレイヤを作るには 以下の手順が必要! 1. AbstractRoleAssignPlayerを継承したクラスを 作成 2. ゲーム実行の準備 3. 各役職のPlayerを作成 4. RoleAssignPlayerに各役職のPlayerをセット
  23. 23. 自作プレイヤーでゲーム実行 以下の手順が必要! 1. AbstractRoleAssignPlayerを継承したクラスを 作成 2. ゲーム実行の準備 3. 各役職のPlayerを作成 4. RoleAssignPlayerで各役職のPlayerをセット まず実行できる環境を整える
  24. 24. 新規パッケージを作成する(1/2) 自分のであることを示すためにパッケージを指定 DemoAgent>srcを右クリック>新規>パッケージ
  25. 25. 新規パッケージを作成する(2/2) パッケージ名を入力して[完了] パッケージ名はユニークになるようにする. 例) demo@aiwolf.org → org.aiwolf.demo 自分のメールアドレスを逆から入力など
  26. 26. 自分のプレイヤのクラスを作成 1. 自分のパッケージ(例:org.aiwolf.demo)を右ク リック 2. 新規>クラス を選択
  27. 27. プレイヤの抽象クラスを指定する スーパークラス>参照から org.aiwolf.sample.libのAbstractRoleAssignPlayerを選択 ※org.aiwolf.client.base.playerの方は非推奨 検索欄で「ARAP」と打つこと で見つかります
  28. 28. 新規クラス作成(3) クラス名(例: DemoRoleAssignPlayer)を 入力して[完了] これだけでサンプル プレイヤー(基本的な人 狼がプレイできるエー ジェント)が完成!!
  29. 29. 対戦してみる! ここまでで,何もプログラミングはしていないけど,サンプルプログラムを 継承することで同じ動作をするエージェントが出来たので戦わせよう!
  30. 30. 人狼知能での対戦とは • 1つのサーバに複数のクライアントを接続し, ゲームを実行(サーバ・クライアント方式) 自分の プログラム 人狼知能サーバ jar と呼ばれる Java で扱い やすいファイル形式で出力
  31. 31. 対戦のためのファイル作成 エージェントをjarファイルに出力(エクスポート)する 1. 自分のプロジェクト(例:DemoAgent)を右クリック 2. エクスポートをクリックする
  32. 32. JARファイル作成方法(2) JAVA>JARファイル を選択して次へ
  33. 33. JARファイル作成方法(3) • エクスポート先はAIWolf-ver0.4.Xフォルダ – 最初に解凍したフォルダ • JARファイルの名前はdemoAgent.jarとする
  34. 34. 設定ファイルを書き換える 自分のエージェントを起動するよう設定する! • AIWolf-ver0.4.X内にある AutoStarter.iniをテキスト エディタで開く • Sample1とSample2の行末 のWEREWOLFとSEERを削 除 • Sample5から始まる行の 頭に#を挿入 • 末尾に以下の行を追加 サンプル4体と 自分のエージェント1体で ゲームを行う設定 lib=./ log=./log/ port=10000 game=10 view=true setting=./SampleSetting.cfg #agent=5 Sample1,org.aiwolf.sample.player.SampleRoleAssignPlayer Sample2,org.aiwolf.sample.player.SampleRoleAssignPlayer Sample3,org.aiwolf.sample.player.SampleRoleAssignPlayer Sample4,org.aiwolf.sample.player.SampleRoleAssignPlayer #Sample5,org.aiwolf.sample.player.SampleRoleAssignPlayer Demo,org.aiwolf.demo.DemoRoleAssignPlayer [エージェント名(下の例ではDemo)],[パッケージ名].[クラス名]
  35. 35. ゲームの実行 • AIWolf-ver0.4.Xのフォルダに移動 – Windowsの場合 :AutoStarter.batをダブルクリックで実行 – Mac/Linuxの場合 : AutoStarter.shを実行 • コマンドプロンプトでは % bash Autostarter.sh
  36. 36. 人狼ゲームビューワの操作 Next: 1つ次に進む Auto: 自動的に一日進める SkipALL: 1日分スキップする
  37. 37. 占い師エージェントを作る前に・・・ ここまでで,何もしないエージェントだけど,対戦ができるようになってい るはず! でも,何もしないのでは,人狼ゲームは戦えない!
  38. 38. 自作プレイヤーでゲーム実行 1. AbstractRoleAssignPlayerを継承したクラスを 作成 2. ゲーム実行の準備 3. 各役職のPlayerを作成 4. RoleAssignPlayerで各役職のPlayerをセット 例として占い師を作ってみましょう
  39. 39. 役職の元となる RoleAssignPlayer DemoRole AssignPlayer 占い師 クラス 霊能者 クラス 人狼 クラス 村人 クラス サーバーから要求された役の行 動を呼び出せるようにしている プログラマーは,役ごとに行動を 実装する
  40. 40. 占い師クラスのファイルを作成 1. 新規クラスを作成 2. クラス名: 「DemoSeer」と設定 3. スーパークラスの横 の参照をクリック 4. AbstractSeerを選択 しOKを押す 5. クラスを作成する
  41. 41. 占い師用の新規クラスの作成 1. 新規クラスを作成 2. クラス名: 「DemoSeer」と設定 3. スーパークラスの横 の参照をクリック 4. AbstractSeerを選択 しOKを押す 5. クラスを作成する
  42. 42. RoleAssignPlayer クラスに自分の占い 師を登録する • RoleAssignPlayerに次のコンストラクタを追加 (DemoRoleAssignPlayerの場合) – 役職として占い師を振り分けられた時に DemoSeerを呼び出す – 他の役職の場合はデフォルトでサンプルプレイ ヤーが呼び出される public DemoRoleAssignPlayer(){ setSeerPlayer(new DemoSeer()); }
  43. 43. その他の役職を登録するには • 狩人・ボディガード – setBodyguardPlayer(bodyGuardPlayer) • 霊媒師 – setMediumPlayer(mediumPlayer) • 狂人 – setPossessedPlayer(possesedPlayer) • 村人 – setVillagerPlayer(villagerPlayer) • 人狼 – setWerewolfPlayer(werewolfPlayer)
  44. 44. ゲーム実行 • もう一度JARをエクスポートし,AutoStarterを 実行して動けばOK • DemoSeerはまだ実装すべきメソッドを実装し ていないから何もしない 実装すべきメソッドとは?
  45. 45. 占い師を実装しよう
  46. 46. 人狼知能プレイヤの活動 人狼知能サーバ ゲーム情報(GameInfo) ゲーム状況の認識 行動の選択肢の評価 行動を決定アクション(talk,vote等) 記憶
  47. 47. 占い師であれば!? 人狼知能サーバ ゲーム情報(GameInfo) ゲーム状況の認識 行動の選択肢の評価 行動を決定アクション(talk,vote等) 記憶 他にCOして ないか? 誰が最も怪 しいか? COする! 占い結果
  48. 48. 実装の流れ 1. フィールド 2. getName, updateの実装 3. initializeの実装 4. dayStartの実装 5. voteの実装:占い結果を考慮して投票 ユーティリティメソッドの定義 6. divineの実装:ランダムに占う 7. talkの実装:カミングアウト,占い結果の報告 「記憶」の定義 「記憶」の処理 「認識」の処理 「評価」 「決定」の処理
  49. 49. フィールドの定義 /** 自分自身の参照のための変数 */ Agent me; /** 最新のゲーム場面の状況を格納する変数 */ GameInfo currentGameInfo; /** 未報告の占い結果が入る待ち行列 */ Deque<Judge> myDivinationQueue = new LinkedList<>(); /** 白(人間)リスト (占い結果から得られる) */ List<Agent> whiteList = new ArrayList<>(); /** 黒(人狼)リスト (占い結果から得られる) */ List<Agent> blackList = new ArrayList<>(); /** 灰色(未確定)リスト */ List<Agent> grayList; /** カミングアウト済みか */ boolean saidCO = false; /** 会話リストの読み込んだ場所を保存 */ int talkListHead;
  50. 50. 実装の流れ 1. フィールド 2. getName, updateの実装 3. initializeの実装 4. dayStartの実装 5. voteの実装: 占い結果を考慮して投票 ユーティリティメソッドの定義 6. divineの実装: ランダムに占う 7. talkの実装: カミングアウト,占い結果の報告
  51. 51. エージェント名の設定 ゲーム情報の更新 public String getName() { return "DemoSeer"; } public void update(GameInfo gameInfo) { // currentGameInfoをアップデート currentGameInfo = gameInfo; } 自分のチーム名+Seerを入れる
  52. 52. 実装の流れ 1. フィールド 2. getName, updateの実装 3. initializeの実装 4. dayStartの実装 5. voteの実装: 占い結果を考慮して投票 ユーティリティメソッドの定義 6. divineの実装: ランダムに占う 7. talkの実装: カミングアウト,占い結果の報告
  53. 53. ゲーム開始時の初期化の実装 public void initialize(GameInfo gameInfo, GameSetting gameSetting) { // フィールドの初期化 me = gameInfo.getAgent(); //GameInfoから自分の情報を得る grayList = new ArrayList<>(gameInfo.getAgentList()); grayList.remove(me); whiteList.clear(); blackList.clear(); myDivinationQueue.clear(); } 人狼知能ではゲームを繰り返し行うため,初期 化を行わないと前のゲームの情報が残るため
  54. 54. 実装の流れ 1. フィールド 2. getName, updateの実装 3. initializeの実装 4. dayStartの実装 5. voteの実装: 占い結果を考慮して投票 ユーティリティメソッドの定義 6. divineの実装: ランダムに占う 7. talkの実装: カミングアウト,占い結果の報告
  55. 55. ゲーム情報(GameInfo)から 状況を認識して記憶する public void dayStart() { // 占い結果をGameInfoから取り出す Judge divination = currentGameInfo.getDivineResult(); if (divination != null) { myDivinationQueue.offer(divination); // 占い結果の詳細(誰を占い,その結果は?)を取り出す Agent target = divination.getTarget(); Species result = divination.getResult(); // 灰色リスト・白リスト・黒リストのアップデート grayList.remove(target); if (result == Species.HUMAN) { whiteList.add(target); // 結果が人間(白)なら記憶 } else { blackList.add(target); // 結果が人狼(黒)なら記憶 } } //今日の発話リストですでに読み込んだ内容を覚えておく変数 talkListHead = 0; } 待ち行列の最後に要素を追加する関数
  56. 56. 実装の流れ 1. フィールド 2. getName, updateの実装 3. initializeの実装 4. dayStartの実装 5. voteの実装: 占い結果を考慮して投票 ユーティリティメソッドの定義 6. divineの実装: ランダムに占う 7. talkの実装: カミングアウト,占い結果の報告
  57. 57. 投票する行動を実装する 人狼ゲームでは,疑わしいプレイヤに投票する のだが,そのためには“疑わしい”プレイヤを見 つける必要がある.そこで.... 1. 生きている人狼の中からランダムに投票 2. 生きている人狼がいなければ,生きている灰色 のプレイヤーからランダムに投票
  58. 58. 生きているかどうかの判定する関数 リストからランダムに選ぶ関数 /** エージェントが生きているかどうかを返す */ boolean isAlive(Agent agent) { return currentGameInfo.getAliveAgentList().contains(agent); } /** リストからランダムに選んで返す */ <T> T randomSelect(List<T> list) { if (list.isEmpty()) { return null; } else { return list.get((int) (Math.random() * list.size())); } }
  59. 59. 投票行動の(vote)の実装(1/2) public Agent vote() { //投票行動を定義する関数 // 候補者リスト List<Agent> candidates = new ArrayList<>(); // 生きている人狼を候補者リストに加える for (Agent agent : blackList) { if (isAlive(agent)) { //生きているかどうかを判定 candidates.add(agent); //候補リストへの追加 } } // 候補者がいない場合は生きている灰色のプレイヤーを候補者リストに加える if (candidates.isEmpty()) { for (Agent agent : grayList) { if (isAlive(agent)) { candidates.add(agent); } } } 続く
  60. 60. 投票行動の(vote)の実装(2/2) // 候補者がいない場合はnullを返す(自分以外の生存プレイヤーからランダム) if (candidates.isEmpty()) { return null; } // 候補者リストからランダムに投票先を選ぶ return randomSelect(candidates); }
  61. 61. vote()の実装(全体) public Agent vote() { // 候補者リスト List<Agent> candidates = new ArrayList<>(); // 生きている人狼を候補者リストに加える for (Agent agent : blackList) { if (isAlive(agent)) { candidates.add(agent); } } // 候補者がいない場合は生きている灰色のプレイヤーを候補者リストに加える if (candidates.isEmpty()) { for (Agent agent : grayList) { if (isAlive(agent)) { candidates.add(agent); } } } // 候補者がいない場合はnullを返す(自分以外の生存プレイヤーからランダム) if (candidates.isEmpty()) { return null; } // 候補者リストからランダムに投票先を選ぶ return randomSelect(candidates); } 次はdivine()を実装してみましょう
  62. 62. 実装の流れ 1. フィールド 2. getName, updateの実装 3. initializeの実装 4. dayStartの実装 5. voteの実装: 占い結果を考慮して投票 ユーティリティメソッドの定義 6. divineの実装: ランダムに占う 7. talkの実装: カミングアウト,占い結果の報告
  63. 63. 占ってない相手をランダムに占う public Agent divine() { // 候補者リスト List<Agent> candidates = new ArrayList<>(); // 生きている灰色のプレイヤーを候補者リストに加える for (Agent agent : grayList) { if (isAlive(agent)) { candidates.add(agent); } } // 候補者がいない場合は誰も占わない if (candidates.isEmpty()) { return null; } // 候補者リストからランダムに占う return randomSelect(candidates); }
  64. 64. 実装の流れ 1. フィールド 2. getName, updateの実装 3. initializeの実装 4. dayStartの実装 5. voteの実装: 占い結果を考慮して投票 ユーティリティメソッドの定義 6. divineの実装: ランダムに占う 7. talkの実装: カミングアウト,占い結果の報告
  65. 65. 発話生成の例 //占いの情報の取得 Judge judge = getLatestDayGameInfo().getDivineResult(); //発話の作成 ContentBuilder builder = new DivinedResultContentBuilder(judge.getTarget(), judge.getResult(); String talk = new Content(builder).getText(); 発話内容は ContentBuilder を使って生成します. 例えば,今日の占い結果を報告する発話を生成には, DivinedResultContentBuilder で占った相手と結果を 引数に与えて生成します
  66. 66. 発話(talk())を実装する 1. 占いで人狼を見つ けたらカミングアウ ト 2. カミングアウトした 後は占い結果を報 告する 未報告の結 果に人狼が いる NO YES 占いCO済み 占いCO人狼を報告 NO YES
  67. 67. talk()で人狼をみつけたのでCOする public String talk() { // 占いで人狼を見つけたらカミングアウトする if (!saidCO) { // COをしていないのであれば //占い結果が空ではなく,最後の占い結果が狼である場合 if (!myDivinationQueue.isEmpty() && myDivinationQueue.peekLast().getResult() == Species.WEREWOLF) { saidCO = true; ContentBuilder builder = new ComingoutContentBuilder(me, Role.SEER); return new Content(builder).getText(); } } 続く 待ち行列の最後の要素を取り出さずに取得 COをする発話を生成するクラス
  68. 68. talk()で占い結果を発言する // カミングアウトしていれば、まだ報告していない占い結果を順次報告 else { if (!myDivinationQueue.isEmpty()) { Judge divination = myDivinationQueue.poll(); ContentBuilder builder = new DivinedResultContentBuilder(divination.getTarget(), divination.getResult()); return new Content(builder).getText(); } // これまでの占い結果が空になるまで発言する } return Content.OVER.getText(); } 待ち行列の先頭の要素を取り出す
  69. 69. talk()の実装(全体) public String talk() { // 占いで人狼を見つけたらカミングアウトする if (!saidCO) { if (!myDivinationQueue.isEmpty() && myDivinationQueue.peekLast().getResult() == Species.WEREWOLF) { saidCO = true; ContentBuilder builder = new ComingoutContentBuilder(me, Role.SEER); return new Content(builder).getText(); } } else {// カミングアウトした後は、まだ報告していない占い結果を順次報告 if (!myDivinationQueue.isEmpty()) { Judge divination = myDivinationQueue.poll(); ContentBuilder builder = new DivinedResultContentBuilder(divination.getTarget(), divination.getResult()); return new Content(builder).getText(); } } return Content.OVER.getText(); //以降発言しないことを宣言する }
  70. 70. 他のプレイヤの話を聞くには!? • 会話のリストはGameInfo内にある. – GameInfo.getTalkList()でList<Talk>型として取得 • Talkクラスのメソッド – getAgent(): 発話したAgentのIDを取得 – getText(): 発話内容(String)を取得 • 取得される文字列は人狼知能プロトコルに準拠 – 例:“DIVINED Agent[04] HUMAN” (Agent[04]を占った結果は白) – getDay(): 発話日(int)を取得 – getIdx(): その日の何番目の発話か(int)を取得 – getTurn(): その日の何番目のターンの発話か (int)を取得
  71. 71. 発言の内容を処理するためには Talk.getText()で得られる文字列は以下のような感じ 例:“DIVINED Agent[04] HUMAN” このままでは,処理が簡単ではないので,Contentクラスを用い て処理(parse)をする //Contentクラスのコンストラクタの引数に発話内容の文字列(String)を 入れるとparseする Talk talk = currentGameInfo.getTalkList().get(i); Content content = new Content(talk.getText());
  72. 72. update()に発言を聴く処理を追加(1) /** update メソッドは,他のメソッドの前に読み込まれるので, なにか行動する前に情報を更新するのに用いる */ public void update(GameInfo gameInfo) { currentGameInfo = gameInfo; // GameInfo.talkListからカミングアウト・占い報告・霊媒報告を抽出 for (int i = talkListHead; i < currentGameInfo.getTalkList().size(); i++) { Talk talk = currentGameInfo.getTalkList().get(i); Agent talker = talk.getAgent(); if (talker == me) { // 発言者が自分であれば除く continue; } Content content = new Content(talk.getText()); // 発話をparse 続く
  73. 73. update()に発言を聴く処理を追加(2) switch (content.getTopic()) { case COMINGOUT: // カミングアウト発話の処理 break; case DIVINED: // 占い結果報告発話の処理 break; case IDENTIFIED: // 霊媒結果報告発話の処理 break; default: break; } } talkListHead = currentGameInfo.getTalkList().size(); }
  74. 74. update()に対話処理を追加(全体) public void update(GameInfo gameInfo) { currentGameInfo = gameInfo; // GameInfo.talkListからカミングアウト・占い報告・霊媒報告を抽出 for (int i = talkListHead; i < currentGameInfo.getTalkList().size(); i++) { Talk talk = currentGameInfo.getTalkList().get(i); Agent talker = talk.getAgent(); if (talker == me) { continue; } Content content = new Content(talk.getText()); // 発話をparse switch (content.getTopic()) { case COMINGOUT: // カミングアウト発話の処理 break; case DIVINED: // 占い結果報告発話の処理 break; case IDENTIFIED: // 霊媒結果報告発話の処理 break; default: break; } } talkListHead = currentGameInfo.getTalkList().size(); }
  75. 75. Contentクラスの使い方 戻り値 メソッド名 説明 String getText() 発話内容をそのまま返す Operator getOperator() 発話内容の演算子を返す.発話が単文の場合はnull Agent getSubject() 発話内容の主語を返す Topic getTopic() 発話内容のトピックを返す(COMINGOUTやDIVINED等) Agent getTarget() 発話内容の目的語となるプレイヤーを返す(例えば”DIVINED Agent[01] HUMAN” → Agent[01]) Role getRole() 発話の目的語となる役職を返す(例えば”COMINGOUT Agent[02] SEER” → SEER) Species getResult() 占い(霊媒)の結果を返す(例えば”IDENTIFIED Agent[03] WEREWOLF” → WEREWOLF) TalkType getTalkType() TopicがAGREE/DISAGREEの時,対象発話のタイプ(TALK/WHISPER)を返す int getTalkDay() TopicがAGREE/DISAGREEの時,対象発話の発話日を返す int getTalkID() TopicがAGREE/DISAGREEの時,対象発話の発話IDを返す List<Content> getContentList() 発話内容が複文・重文の場合,節のリストを返す
  76. 76. カミングアウト情報の取り込み例 /** カミングアウト状況 */ Map<Agent, Role> comingoutMap = new HashMap<>(); // initialize()に comingoutMap.clear(); を追加する public void update(GameInfo gameInfo) { currentGameInfo = gameInfo; // GameInfo.talkListからカミングアウト・占い報告・霊媒報告を抽出 for (int i = talkListHead; i < currentGameInfo.getTalkList().size(); i++) { Talk talk = currentGameInfo.getTalkList().get(i); Agent talker = talk.getAgent(); if (talker == me) { continue; } Content content = new Content(talk.getText()); // 発話をparse switch (content.getTopic()) { case COMINGOUT: // カミングアウト情報の取り込み comingoutMap.put(talker, content.getRole()); break; case DIVINED: // 占い結果報告発話の処理 break; case IDENTIFIED: // 霊媒結果報告発話の処理 break; default: break; } } talkListHead = currentGameInfo.getTalkList().size(); } 実際にカミングアウトの情報を Mapクラスのデータとして保存
  77. 77. カミングアウト情報が取り込めたら • 例えば,偽の占い師が現れたことがわかる – 自分のカミングアウト前だったらどうする? – 偽占い師は人狼か,それとも裏切り者か・・・ などについて,対応を考えてみましょう
  78. 78. 人狼知能プレイヤエージェントのプ ログラミングのための基礎知識
  79. 79. ゲームの流れ initialize 夜のフェーズ divine(占い師のみ) whisper(人狼のみ) guard(狩人のみ) attack(人狼のみ) 昼のフェーズ talk vote finish 1日 dayStart ゲーム終了? 初日? whisper(人狼のみ) divine(占い師のみ) YES NO NO YES 夜のフェーズではvoteによる追放結果を利用可能
  80. 80. Playerインターフェース Playerインターフェース内で定義されるメソッド • ゲームの各タイミングでサーバがプレイヤーのこれ らのメソッドを呼び出す • updateはgetName, initialize以外のメソッドの直前  initialize(GameInfo, GameSetting)  update(GameInfo)  getName()  dayStart()  talk()  whisper()  vote()  attack()  divine()  guard()  finish()
  81. 81. Playerの各メソッドの説明 • initialize(GameInfo) – ゲーム開始時に一度だけ 呼ばれる – サーバから送られてくる GameInfoを取得 • update(GameInfo) – 各行動の前に呼ばれる – サーバから送られてくる GameInfoを取得 • dayStart() – 日の初めに呼ばれる • finish() – ゲームが終了した時に呼 ばれる
  82. 82. Playerの各メソッドの説明 • vote() – 投票する相手を選択する • attack() – 襲撃する相手を選択する – 人狼のみ呼ばれる • divine() – 占いする相手を選択する – 占い師のみ呼ばれる • guard() – 護衛する相手を選択する – 狩人のみ呼ばれる 1日の終わりに呼ばれるメソッド Agentを返す必要あり
  83. 83. Playerの各メソッドの説明 • talk() – 全体に対して発話する – 全員呼ばれるメソッド • whisper() – 人狼だけに対して発話す る – 人狼のプレイヤーだけが 使用するメソッド 発話のメソッド Stringを返す必要あり
  84. 84. 各メソッドの戻り値 • void • String • Agent(対象プレイヤーを選択するメソッド) initialize(GameInfo, GameSetting) update(GameInfo) dayStart() finish() vote() attack() divine() guard() getName() talk() whisper()
  85. 85. GameInfo から得られる情報一覧 返り値 メソッド 概要 Agent getAgent() 自分自身の情報 List<Agent> getAgentList() ゲームに参加しているエージェントのリスト List<Agent> getAliveAgentList() その時点で生きているエージェントのリスト int getDay() その時点が何日目か Agent getExecutedAgent() 前日に追放されたエージェントの情報 List<Role> getExistingRoles() ゲームに参加している役職のリスト List<Agent> getLastDeadAgentList() 前日に死亡したエージェントのリスト Agent getLatestExecutedAgent() 昼のターンに決まった追放者の情報(夜のみ可) List<Vote> getLatestVoteList() 投票が同数の場合のその投票結果のリスト Role getRole() 自分自身の役職の情報 Map<Agent, Status> getStatusMap() 参加エージェントの生死状態のリスト List<Talk> getTalkList() その日の発話のリスト List<Vote> getVoteList() 前日の投票のリスト 全役職共通の内容
  86. 86. 返り値 メソッド 概要 Agent getAttackedAgent() List<Vote> getAttackedVote() List<Vote> getLatestAttackedVote() Map<Agent,Role> getRoleMap() List<Talk> getWhisperList() 人狼のみ(それ以外の役職ではnull) 返り値 メソッド 概要 List<Agent> getDivineResult() 返り値 メソッド 概要 List<Agent> getGuardedAgent() 返り値 メソッド 概要 List<Agent> getMediumResult() 占いのみ(それ以外の役職ではnull) 狩人のみ(それ以外の役職ではnull) 霊媒のみ(それ以外の役職ではnull)
  87. 87. talk, whisperでの発話 org.aiwolf.client.lib.Contentクラスと org.aiwolf.client.lib.ContentBuilderのサブクラスで生成 以下の手順で得られるtextが発話テキストとなる ContentBuilder builder = 発話の種類に応じたContentBuilder; Content content = new Content(builder); String text = content.getText();
  88. 88. 各種ContentBuilderクラス(1) 1. EstimateContentBuilder(target, role): targetの役職はroleだと思う 2. ComingoutContentBuilder (target, role) : targetの役職はroleだ 3. DivinationContentBuilder(target) : targetを占う 4. DivinedResultContentBuilder(target, result) : targetを占った結果result だった 5. IdentContentBuilder(target, result) : targetは霊媒の結果resultだった 6. GuardCandidateContentBuilder(target) : targetを護衛する 7. GuardedAgentContentBuilder(target) : targetを護衛した 8. VoteContentBuilder(target) : targetに投票する 9. AttackContentBuilder(target) : targetに襲撃投票する 10. AgreeContentBuilder(talkType, talkDay, talkID) : talkDay日目,種類 talkType,talkID番目の発話に同意する
  89. 89. 各種ContentBuilderクラス(2) 11. DisagreeContentBuilder(talkType, talkDay, talkID) : talkDay日目,種類 talkType,talkID番目の発話に反対する 12. RequestContentBuilder(agent, content) : agentにcontentを要求する 13. OverContentBuilder() : もう話すことはない 以下で定義される定数Content.OVERが用意されている OVER = new Content(new OverContentBuilder()); 14. SkipContentBuilder() : 様子を見る 以下で定義される定数Content.SKIPが用意されている SKIP = new Content(new SkipContentBuilder()); 詳細は,aiwolf-ver0.4.4.zip(1つ前のリリース)内の 「 0.4.4での発話生成の方法(修正版2).pdf」を参照
  90. 90. 役職ごとの行動の実装を考える! ここでは,5人の人狼ゲームを前提として進めます
  91. 91. 人狼の村の人数による違い • 人狼知能大会では,現在以下の2つの村の構成 でゲームを行っています – 5人人狼 ※今回の模擬大会の構成 • 村2,占1,狼1,裏1 • 最初に狼陣営を追放しないとパワープレイになる可能性 – 15人人狼 • 村9,占1,狩1,霊1,狼2,裏1 • 村の人数と役職の構成によって戦略が(おそら く)異なるので,どのように実装するのがよいの か考えてください.
  92. 92. 村人(Villager)の行動を考える • 他の役職の発言を聞く. – 占い師の占い結果を聞いてまとめる • 複数人出てきたときにどちらを信じるのか – 誰を信じているか・疑っているかを発言してみる – 誰に疑われているかを認識する • 他のプレイヤを評価してみる – プレイヤを評価するMapを作成する – まずは,そこにランダムに数字を入れる • 誰に投票するかを決める – 評価のMapの得点の低いプレイヤにいれる? – 他のプレイヤの投票動向も気にする イラスト: 石黒正数
  93. 93. import しておくとよいパッケージ // Java 関連のライブラリ import java.util.ArrayList; import java.util.Deque; import java.util.HashMap; import java.util.LinedList; import java.util.List; // ArrayListクラス等の上位クラス import java.util.Map; // 人狼知能関連のライブラリ import org.aiwolf.client.lib.*; // “*”をつけるとそれ以下のライブラリが利用可 import org.aiwolf.common.data.*; // ただし何でもかんでもつけると重くなる import org.aiwolf.common.net.*;
  94. 94. 占い結果を会話から保存する // ゲームが始まった時点で役職とタイミングを決めるなら List<Judge> divitionList = new ArrayList<>(); public void update(GameInfo gameInfo) { .....(中略)..... for(int i = talkListHead; i < gameInfo.getTalkList().size(); i++){ Talk talk = gameInfo.getTalkList().get(i); Agent talker = talk.getAgent(); .....(中略)..... Content content = new Content(talk.getText()); switch(content.getTopic()) { case DIVINED: Judge j = new Judge(day,talker,content.getTarget(),content.getResult()) divinationList.add(j); break; .....(中略)..... } .....(中略)..... } .....(中略)..... }
  95. 95. 人狼(Werewolf)の行動を考える • 裏切者を信じるのか,どうか – 自分が偽の占い師を演じるのか,裏切者に任せ るのか • 自分が占い師だとCOする場合 – どの順番でCOするのかが,ゲームに影響を与え るのかなど,周りのプレイヤの能力見極める必要 がある • 基本的な実装しかしていないのであれば,COの順番と かまでは考慮していない可能性もあるため イラスト: 石黒正数
  96. 96. 人狼が裏切者を占いから発見する // ゲームが始まった時点で役職とタイミングを決めるなら public void update() { .....(中略)..... for(Judge j : divitionList){ //占い結果のリストをfor文で確認する Agent agent = j.getAgent(); Agent target = j.getTarget(); //占いを発言したのが狼ではなく,矛盾した結果を発言しているエージェントは裏切者? if (!werewolves.contains(agent) && ((humans.contains(target) && j.getResult() == Species.WEREWOLF) || (werewolves.contains(target) && j.getResult() == Species.HUMAN))) { if(!possessedList.contains(agent)) { //すでに裏切者候補がいなければ possessedList.add(agent); whisperQueue.offer(new Content(new EstimateContantBuilder(agent, Role.POSSESSED))); //狼同士のささやきで裏切者ぽいと伝える準備 } } } .....(中略)..... }
  97. 97. 裏切者(Possessed)の行動を考える • 5人人狼では,基本的に占いCOをするケースが 多い. – なぜなら,本物の占い師が確定してしまうと,占い師 は結果が白でも,黒でも信頼を得やすい • 占い師COする場合 – どのタイミングでCOをするのか – 誰を占ったことにして,白 or 黒を出すのか • 白を出した場合,その相手を味方につけて,狼以外に投票 できるか • 黒を出した場合には狼にあててしまわないかどうか • 人狼が何人いるのか確認をする int numWolves = gameSetting.getRoleNumMap().get(Role.WEREWOLF); イラスト: 石黒正数
  98. 98. 偽のCOをするタイミングを決める // ゲームが始まった時点で役職とタイミングを決めるなら Role fakeRole = null; int comingOutDay = -1; int comingOutTurn = -1; public void initialize(GameInfo gameInfo, GameSetting gameSetting) { .....(中略)..... List<Role> roles = new ArrayList<>(); // COする予定の役職リスト for(Role r : Array.asList(Role.VILLAGER, Role.SEER, Role.MEDIUM)){ if(gameInfo.getExistingRoles().contains(r)){ //役職にあるか確認 roles.add(r); } } fakeRole = randomSelect(roles) // 要実装(後述) comingOutDay = (int)(Math.random()*2+1); // 1日目か2日目にCO comingOutTurn = (int)(Math.random()*5); // 会話の前半にCOする .....(中略)..... }
  99. 99. 占い師(Seer)の行動を考える • COを行うかどうかを決める – COを行わないと,黒を引いても信用は得られない • 対抗COが出てきたときにどのような行動をと るのか – 先にCOされてしまったら,どの順番でCOするのが 信じてもらえるのか – あとからCOされた場合には,誰を疑うと決めるの かが重要になります. イラスト: 石黒正数
  100. 100. 狩人(Bodyguard)の行動を考える • 5人人狼では出てこない役職です • 狩人の基本的な行動は – 預言者を守る • 占いCOが一人の時には素直に守るのがよいでしょう • 複数COしている場合には、狼の襲撃先の予想が必要 – 狼の襲撃先を予想する • 狼が誰を襲おうと思うのかを狼の立場から考える – 投票候補にも襲撃候補にもなりにくい立ち位置を確 保する – COはゲームの終盤以外には、基本的には行わない イラスト: 石黒正数
  101. 101. 霊媒師(Medium)の行動を考える • 5人人狼では出てこない役職です • 霊媒師の基本的な行動は,霊媒結果を村人 に伝える以外には,村人と変わりません. – ただし,人狼からすれば何匹人狼が追放されず に村にいるかがわからないようするために,はや めに襲撃したい役職になります. イラスト: 石黒正数
  102. 102. 基本的な関数 // リストからランダムな値を返す関数 protected <T> randomSelect(List<T> list) { if (list.isEmpty()) { return null; } else { return list.get((int)(Math.random() * list.size()); } } // ある参加者が生きているかどうかを確認する protected boolean isAlive(Agent agent){ return currentGameInfo.getStatusMap.get(agent) == Status.ALIVE; }
  103. 103. 改訂履歴 • 20170624版: – 0527福井@愛工大の修正を追加 – 5人人狼のアルゴリズム例を追加 – オブジェクト指向・エージェント指向,機械学習の説明の追加 – Javaに関する補足の追加 • 20170406版: ベース資料
  104. 104. 補足
  105. 105. オブジェクト指向とは エージェント指向とは
  106. 106. オブジェクトとは入れ物である (C)Ghost in The Shell President Jomeson 身体・道具 情報・知識 + からっぽの箱 =オブジェクト (メソッド) (変数) なにかできる箱
  107. 107. Javaにおけるオブジェクトとは • データと手続きをクラスとしてまとめられる 犬 class  データ: 名前  データ: 年齢  データ:所有物  手続き:つかむ  手続き:移動する(徒歩) 人間 class  データ: 名前  データ: 年齢  データ:所有物  手続き:つかむ  手続き:移動する(徒歩)
  108. 108. オブジェクトは引き継げる(継承) 「人間」というクラスを引き継いで「職 業」ごとのクラスを作成することで, 「人間」に関する基本的な行動は 「人間」クラスで管理し,職業固有の 行動は「職業」グラスで定義できる
  109. 109. エージェントとは • もともとは,オブジェクト指向と呼ばれていた概念を 発展させたもの – オブジェクト指向とは • 「オブジェクト」というモノが存在することを前提として,その単 位でオブジェクトの行為を管理する仕組み • 例えば – オブジェクト「人」 » 行為「歩く」 » 行為「つかむ」 – オブジェクト「犬」 » 行為「歩く」 – それ以前は • 「人間が歩く」という関数 • 「人間がつかむ」という関数 を定義してきた 人間 犬 歩く歩く つかむ
  110. 110. オブジェクト指向からエージェント指向へ • エージェント指向のキーワードは「能動性」 – オブジェクト指向はプリセットされた情報と手段をもって ふるまう – それに対して,エージェント志向では能動的に • 情報を集める • 目的を設定する • 行動を生成・最適化する ことが期待されている – でも,現状で可能なのは • 最適化ぐらい – 行動パラメータ – マルチエージェントのタスク分割 • 行動生成とか結構難しい なので,現状はオブジェクトと同義 疲れたから帰 りたい どこまで散歩 するか? 手を離さない
  111. 111. 付録:GUIによるゲームの実行
  112. 112. GUIによるゲームの実行 • サーバの起動 – Windowsの場合 : StartServer.batをダブルクリック – Mac/Linuxの場合 : StartSerber.shをダブルクリック
  113. 113. サーバの設定 • Num of playersを5に設定 • Connectをクリックしてサーバを起動
  114. 114. クライアントの接続 (1/3) • クライアント接続用プログラム – Windowsの場合 :StartGuiClient.batを起動 – Mac/Linuxの場合 : StartGuiClient.shを起動
  115. 115. クライアントの接続 (2/3) • 先ほど作成したjarとaiwolf-client.jarをJarFiles 欄にドラッグアンドドロップ
  116. 116. クライアントの接続 (3/3) • Jarファイル,接続するプレイヤークラスを選択 • Connect Agentを選択 • Role欄で役職をリクエストすることもできる
  117. 117. ゲームの実行 • 自分のエージェント1体と SampleRoleAssignPlayerを4体Connect • Server StarterでStart Gameをクリック 5体のエージェントが接続されて いるのを確認(ServerStarter)
  118. 118. 実行結果 • 実行結果が右側の欄に表示される この例では村人側(Villager)の勝利
  119. 119. 人狼知能エージェントに学習させる には?
  120. 120. 強化学習(Reinforcement Learning) • 強化学習とは、ある条件下における対応に対 する尤度(もっともらしさ)を獲得するための手 法 • 出力に対する結果の良し悪しで報酬を与える ことで、よりよい結果を得られる行動系列を獲 得しようとする http://spider.art.coocan.jp/biology2/behavior2012.htm より
  121. 121. エージェントのQ学習(1/2) • エージェントが観測した環境 s において,行動 a を 実行する確率を、報酬によって最適化する – エージェントが,行動を決定する方策πに基づいて、行動 a を選択できるような確率分布を獲得するのがQ学習 環境 行動 観測・報酬
  122. 122. エージェントのQ学習(2/2) • ある時刻tでの環境と行動の組み合わせ (s, a) に 対して、次の時刻 t + 1 の環境 st+1 をもとに報酬 r を決定して、 (s, a) に対して報酬rを与える。 – Q(s,a) ← Q(s,a) + α[r + γ max Q(st+1,p)- Q(s,a)] α 学習係 数 – π(s,a): ランダム, ε-グリーディ, ルーレット、soft max, 等 • そのため、環境をすべて観測できるか、できない かによって、学習した尤度への信頼性がことなる – MDP: マルコフ行動決定過程 – POMDP: 部分観測マルコフ行動決定過程
  123. 123. 部分観測状態とは… 全体が把握できないで、一部 しか観測できない状態 人狼ゲームにおける観測情報は部分的かつ確率的なものである!
  124. 124. 人狼知能を強化学習するには • 環境 S を定義する – 投票結果 – CO状況 – 会話内容 – (役職) • 行動 A を定義する – 投票 – 占い・護衛・襲撃 対象の決定 – 発話 • 報酬 R を定義する – ゲームの勝利 – 人狼・占師の追放 – 騙せたとき これらを定義したうえで,人狼BBSのデー タなどを用いて学習させる!
  125. 125. JAVA 言語に関する資料
  126. 126. パッケージの宣言、インポート • パッケージとは – 複数あるクラスファイルをまとめるための仕組み – ファイル名(名前)の衝突回避 – ライブラリとしての公開 • パッケージの定義 package org.aiwolf.common.util; 同じファイル構造(org/aiwolf/common/util/)に設置が必要 • パッケージの読み込み import org.aiwolf.common.util.*; import org.aiwolf.common.util.Sorter.java; * とすることで、そのフォルダにあるものをすべてインポート可 能であるが、実行ファイル・速度に影響がある
  127. 127. Javaの変数のスコープ • スコープ修飾子 – public: インスタンスを通して直接アクセス可能 – protected: クラス内、継承したサブクラスからアク セス可能 – 無し: パッケージ内から – private: クラス内から – メソッド内、 {} 内 • static – 異なるインスタンスどうしで値を共有
  128. 128. ジェネリクス型 • AiWolfCommon/src/org/aiwolf/common/util/ 以下などで使用 – Counter.java – Sorter.java – BidiMap.java • Java の配列データは、基本的に任意の型を格納できる。 – でも、実際に一つの配列にいろんな方を入れることはない – データを取り出すときに、キャストするのが煩わしい – 特定のデータ型(クラス)を格納する宣言をしてしまいたい • ジェネリクスを用いた実装 public class ArrayList<E> extends AbstractLink<E> … { … public <T> T[] toArray(T[] a) { … } …. }
  129. 129. データの格納・アクセス • List<Object> • ArrayList<Object> • LinkedList<Object>: FIFO • Map<Key, Value> • LinkedHashMap<Key, Value> • TreeMap<Key, Value> • BidiMap<Key, Value>: Key、Valueどちらの要素でもアク セス可能なMap • Counter<V> : 要素Vに対する投票や数え上げ
  130. 130. イテレータ・拡張for文 List<Objet> list; • 通常: データに要素番号でアクセスできないと難しい for(int i = 0; i < MAX; i++) { Object obj = list[i]; // list.get(i); System.out.println(obj); } • イテレータ: データ構造から直接取得するので高速 for(Iterator<Object> it = list.iterator(); it.hasNext(); ){ Object obj = it.next(); System.out.println(); } • 拡張for文(for-each文): イテレータを活用して実装してある For(Object obj: list) { System.out.println(obj); }
  131. 131. インスタンスの生成方法 • 一般的なインスタンス生成 – Object obj = new Object(); • クラス名:String からのインスタンス生成 – Object obj = (Object)Class.forName(クラス 名:String).newInstance();
  132. 132. アノテーション、JavaDoc @Override – メソッドのオーバライド時にアノテーションを加えるこ とで、タイプミスなどをコンパイル時に警告される @Deprecated – メソッドやクラスが、バージョンアップで変更されて、 推奨されないものであるものに対して警告をする @SuppressWarning – コンパイル時の警告を抑制するためのアノテーション。
  133. 133. 命名・コーディング規則の重要性 以下のソースコードを目にした時の感想は? ---------------------------------------------------------------------- public class FileCopy(){ private String f1; private String f2; public void main( String[] args ) throws Exception{ FileCopy a = new FileCopy(); a.f1 = new File( args[0] ); a.f2 = new File( args[1] ); a.run( 1024 );} private void run( int i ) throws Exception{ FileInputStream s1 = new FileInputStream( f1 ); FileOutputStream s2 = new FileOutputStream( f2 ); byte[] b = new byte[i]; Int j; while( (j=s1.read(b))>0) s2.write( b,0,j); s1.close(); s2.close(); }}
  134. 134. ソースコードを読みやすくするには • 意味が分かる(伝わる)ように書く – クラスなのか、メソッドなのか、変数なのかわかる ように – メソッド名は動詞+名詞のように機能がわかるよ うに • スコープがわかるように書く – スコープ接頭語をつけたりする • static なら s_*, メンバ変数であれば m_*, 引数なら a_* と – 定数は大文字にする
  135. 135. Javaコーディング規則(1) • パッケージ名は小文字 java.io.*; • クラス名 – 先頭は大文字、複数の単語からなる場合には単語の先頭は大文字 class Sample {} – 意味ある名前にして略さない • メソッド名 – コンストラクタと同じ名前のメソッドは作らない – メソッド名は文字の区切りのみ大文字 int getParameter() {} void setParameter(int value) {} – メソッドの役割を表す名前は統一する – 返り値が Boolean の値の時にはTrue/Falseがわかるようにする boolean isSampleClass() {} Boolean hasStock() {} – 引数に入れる変数とメンバ変数を同じ名前にしない Int num = 0; void setParameter(int num){ this.num = num; }
  136. 136. Java コーディング規則(2) • 変数 – boolen 変数は、true/false がわかるような名前に boolean hasTree; boolean isMan; – 定数は、なるべく static final で宣言して大文字に static final int NUM_TRIAL; – 変数名は役割がわかるように命名 – スコープの狭い変数はわかる範囲で略してもよい

×