2014年12月11日木曜日

Pythonを使ってTwitterでOAuthのログインをするためのクラスを作る

TwitterのOAuthでログインできるようにしておくと色々便利なのはAppEngineで経験済みなので,そのためのライブラリを作成してみる.

基本的にはこちらのページを参考にこしらえた.
pythonでoauth認証
事前準備は,
さくらインターネットでPython2.7を使ってTwitterAPIを叩くを参考のこと.
なお,Twitterとの接続に使うのは,python-twitter

まず,
http://syncer.jp/twitter-api-create-application
を参考に,Twitterのアプリを作成しておく.
ただし,アプリ作成の場所が,分かりづらくなったので,直接こちらにアクセスした方がよい.
https://apps.twitter.com/
Twitter社はアプリ作らせる気がないのか?と思うくらい分かりづらい.

このとき,CallBackURLにアプリのCGIのURLを指定しておくことを忘れずに.
後から変えられるけど.

次に,アプリ作成に入る.
まず,sqliteを使うので,oauth.dbというファイルを作っておいて,
$ sqlite3 oauth.db
sqlite> CREATE TABLE oauth (oauth_token text primary key,oauth_token_secret text);
sqlite> CREATE TABLE user (id integer primary key, consumer_key text, consumer_secret text, access_token text, access_token_secret text, screen_name text, image_url text, time datetime);
sqlite> .exit
として,データベースを作成しておく. 次に,TwitterLogin.pyを作成.
ここが今日のハイライト.


#!/usr/bin/env python
# -*- coding: utf-8 -*-

import site
site.addsitedir('/home/username/lib/python')

import os
import cgi
import cgitb
import oauth2 as oauth
import sqlite3
import twitter
import datetime
from Cookie import SimpleCookie
from twitter import TwitterError

request_token_url = 'https://api.twitter.com/oauth/request_token'
access_token_url = 'https://api.twitter.com/oauth/access_token'
authenticate_url = 'https://api.twitter.com/oauth/authorize'

class TwitterLogin:
    def __init__(self, db_name, consumer_key, consumer_secret):
        self.db_name = db_name
        self.consumer_key = consumer_key
        self.consumer_secret = consumer_secret

    def getOAuth(self):
        consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
        client = oauth.Client(consumer)
        # reqest_token    
        resp, content = client.request(request_token_url, 'GET')
        request_token = dict(self.parse_qsl(content))
       
        #call_back="http://torix.sakura.ne.jp/twitter/signin.cgi"
   
        # request_token    
        con = sqlite3.connect(self.db_name)
        con.execute(u'insert into oauth values (?, ?)', (request_token['oauth_token'], request_token['oauth_token_secret']))
        con.commit()
        con.close()

        #
        url = '%s?oauth_token=%s' % (authenticate_url, request_token['oauth_token'])
        print 'Content-type: text/html; charset: utf-8\n\n'
        print
        print '' % url


    def getApi(self):
        access_token, access_token_secret, screen_name, image_url = self.loadAccessToken()
        self.screen_name = screen_name
        self.image_url = image_url
        if not access_token == None:
            # Cookieを使った認証
            api = twitter.Api(consumer_key=self.consumer_key,
                              consumer_secret=self.consumer_secret,
                              access_token_key=access_token,
                              access_token_secret=access_token_secret)
            return api
        elif 'QUERY_STRING' in os.environ:
            # CallBackを使った認証
            query = cgi.parse_qs(os.environ['QUERY_STRING'])
            if len(query) == 0:
                return None
            # oauth_token_secret を取得
            con = sqlite3.connect(self.db_name)
            oauth_token_secret = con.execute(
                u'select oauth_token_secret from oauth where oauth_token = ?'
                , [query['oauth_token'][0]]).fetchone()[0]
            con.close()

            if oauth_token_secret == None:
                return None

            # Access_token と access_token_secret を取得
            consumer = oauth.Consumer(key=self.consumer_key, secret=self.consumer_secret)
            token = oauth.Token(query['oauth_token'][0], query['oauth_verifier'][0])
            client = oauth.Client(consumer, token)
            resp, content = client.request(
                access_token_url, "POST", body="oauth_verifier=%s" % query['oauth_verifier'][0])
            access_token = dict(self.parse_qsl(content))

            # access_tokenの保存
            api = twitter.Api(consumer_key=self.consumer_key,
                        consumer_secret=self.consumer_secret,
                        access_token_key=access_token['oauth_token'],
                        access_token_secret=access_token['oauth_token_secret'])
            try:
                user = api.VerifyCredentials()
                uid = user.GetId()
                self.screen_name = user.screen_name
                self.image_url = user.profile_image_url
                self.saveAccessToken(uid, access_token['oauth_token'], access_token['oauth_token_secret'], self.screen_name, self.image_url)
            except TwitterError:
                return None
            return api
        else:
            return None

    #save access token to db
    def saveAccessToken(self, uid, access_token, access_token_secret, screen_name, image_url):

        con = sqlite3.connect(self.db_name)
        c = con.cursor()
        #print datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        c.execute(u"replace into user (id, consumer_key, consumer_secret, access_token, access_token_secret, screen_name, image_url, time) values (?, ?, ?, ?, ?, ?, ?, datetime(?))", [uid, self.consumer_key, self.consumer_secret, access_token, access_token_secret, screen_name, image_url, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")])
        con.commit()
        c.close()

        ck = SimpleCookie()
        ck.load(os.environ.get("HTTP_COOKIE",""))
        ck["id"] = uid
        ck["token"] = access_token
       
        now_date = datetime.datetime.now()
        expires = now_date
        expires = expires + datetime.timedelta(seconds=+(60*60*24))
        expires = expires.strftime('%a, %d-%b-%Y %H:%M:%S %Z')
           
        ck["id"]["expires"] = expires
        ck["token"]["expires"] = expires
        print ck.output(["id", "expires"])
        print ck.output(["token", "expires"])

    #load data from data base
    def loadAccessToken(self):
        ck = SimpleCookie()
        ck.load(os.environ.get("HTTP_COOKIE",""))
        if ck.has_key("id") and ck.has_key("token"):
            id = int(ck["id"].value)
            token = ck["token"].value
            access_token = None

            con = sqlite3.connect(self.db_name)
            c = con.cursor()
            c.execute(u'select access_token, access_token_secret, screen_name, image_url from user where id=%d'% id)
            for row in c:
                access_token = row[0]
                access_token_secret = row[1]
                self.screen_name = row[2]
                self.image_url = row[3]
            con.close()

            if not access_token == None and access_token == token:
                return access_token, access_token_secret, self.screen_name, self.image_url
            else:
                return None, None, None, None
        else:
            return None, None, None, None



    def parse_qsl(url):
        param = {}
        for i in url.split('&'):
            _p = i.split('=')
            param.update({_p[0]: _p[1]})
        return param

    def parse_qsl(self, url):
        param = {}
        for i in url.split('&'):
            _p = i.split('=')
            param.update({_p[0]: _p[1]})
        return param

getOAuthで,OAuth認証を行うページに飛ばして,callbackで戻ってきたときにgetApiで捕まえる.
それ以降は,UserIDとAccessTokenをCookieに保存することでSession管理をすることにする.
本当は独自のsessionIDを発行した方がいいのかもしれない.

次に,実際にログインするところを作ってみよう. 使い方は以下の通り.
なお,consumer_keyとconsumer_secretはTwitterで登録したアプリのものをつかう.
callback urlがhttp://xxx.xx.xx/twitter/login.cgi
そのURLでアクセスできるcgiを以下のように作成する.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import site
site.addsitedir('/home/username/lib/python')
from TwitterLogin import TwitterLogin

consumer_key = 'consumer_key'
consumer_secret = 'consumer_secret'

if __name__ == '__main__':
    twitter = TwitterLogin('oauth.db', consumer_key, consumer_secret)
    api = twitter.getApi()
    print 'Content-type: text/html; charset: utf-8\n\n'
    if api == None:
        print 'Fail to login'
    else:
        user = api.VerifyCredentials()
        print user.screen_name

これで,認証したアカウントのスクリーンネームが表示されるはず.

APIの使い方は,以下を参照.
https://python-twitter.googlecode.com/hg/doc/twitter.html

色々問題はあるかもしれないけど,とりあえず動いたので,満足.




2014年12月8日月曜日

さくらインターネットでPython2.7を使ってTwitterAPIを叩く

タイトル長いな.
世界の中心で愛を叫ぶみたいになってもうた.

さて,TwitterAPIを使ったツイート収集だけど,最近検索APIで1週間分なら無制限にとれるっぽいことが分かったので,これを使って遊んでみよう計画.

最近Pythonも気になるので,Pythonでさくらサーバ使って遊んでみる.


PythonでTwitterのAPIをたたいてみよう-セットアップ偏
を参考にしてみる.

なお,Twitterとの接続に使うのは,python-twitter

$ setenv PYTHONPATH /home/username/lib/python

$ wget --no-check-certificate github.com/simplegeo/python-oauth2/archive/master.zip
$ unzip master.zip
$ cd  python-oauth2-master/
$ ./setup.py install --home=~

$ cd ../
$ rm master.zip
$ wget --no-check-certificate https://github.com/bear/python-twitter/archive/master.zip
$ unzip master.zip
$ cd  python-twitter-master/
$ ./setup.py install --home=~

$ cd ../
$ rm master.zip
$ wget --no-check-certificate https://github.com/jcgregorio/httplib2/archive/master.zip
$ unzip master.zip
$ cd  httplib2-master/
$ ./setup.py install --home=~

$ cd ../
$ rm master.zip
$ wget --no-check-certificate https://pypi.python.org/packages/source/s/simplejson/simplejson-3.6.5.tar.gz
$ tar zxvf simplejson-3.6.5.tar.gz
$ cd simplejson-3.6.5/
$ ./setup.py install --home=~

これで必要なファイルはダウンロード完了.
ここで適当なアプリを作成しておく.

ConsumerKeyとConsumerSecret,AccessToken, AccessTokenSecretを獲得して,
以下のようなcgiを作成.

#!/usr/bin/env python
# -*- coding: utf-8 -*-


#!/usr/bin/env python
# -*- coding: utf-8 -*-


import site
site.addsitedir('/home/username/lib/python')

import sys
import twitter

print ('Content-type: text/html; charset=UTF-8\n\n')

CONSUMER_KEY = ''                             # Consumer Key
CONSUMER_SECRET = ''         # Consumer Secret
ACCESS_TOKEN = '' # Access Token
ACCESS_TOKEN_SECRET = ''         # Accesss Token Secert

api = twitter.Api(consumer_key=CONSUMER_KEY,
                      consumer_secret=CONSUMER_SECRET,
                      access_token_key=ACCESS_TOKEN,
                      access_token_secret=ACCESS_TOKEN_SECRET)


tlAry = api.GetSearch(term="keyword",count=100)
tweetlist=[]
for s in tlAry:
    if not s.text.startswith('RT @'):
        tweetlist.append(s.text)
        print s.text.encode('utf-8')


これで,keywordを含むツイートを取得可能.
ポイントの一つが,
import site
site.addsitedir('/home/username/lib/python')
の部分.
これを書いておかないとライブラリを読みに行ってくれないので注意.
さくらインターネットでは,ライブラリはローカルに置くしかないので,この作業をやらないと,twitterライブラリがimportできません.




2014年12月2日火曜日

Windows7にCDH5.1.3でHadoop・断念編

というわけで,前回のエントリーでHDInsightでHadoopをと思ったけど,
その後どうやってもEclipseでは起動しない.
どうもライブラリその他の関係でうまくいかないようだ.

色々試した結果,EclipsePluginがHadoop2.x系には対応していないと言う結論に.
結局利用可能なのは,Hadoop0.21系まで.

ひとまずHadoop2.x系のPluginがリリースされるまでは,Hadoop0.21系を利用することに.
こちらのエントリーに書いた方法で概ねインストール可能.
変更点は,
hadoop-env.shが
hadoop/bin/hadoop-env.sh
から
hadoop/conf/hadoop-env.sh
へ移動になったことくらい.

結局2年前のHadoopを使うことになるとは.とほほ.


2014年11月25日火曜日

Windows7にCDH5.1.3でHadoop・その2 HDInsightインストール編

というわけで,ここを参考にHDInsightでWindows7にHadoopを導入してみようとトライ.

まずは,HDInsight Previewsのページへ・・・行ってみるとすでに404.

嫌な予感をはらみつつ,Microsoft Web Platform Installerからの導入を目指す.
直接Microsoft Web Platform InstallerをDLすると,Microsoft Web Platform Installer5.0となっている.
説明のページが4.0だったので,バージョンが上がっている.

ここで,

  1. インストーラー(wpilauncher.exe)を実行
  2. Microsoft HDInsight for Windows Server Community Technology Previewを選択

というのが流れのはずなのだが,Microsoft HDInsight for Windows Server Community Technology Previewがない.
あるのは,Microsoft HDInsight Emulator for Windows Azureのみ.
for Windows Azureというあたりに駄目そうな予感を感じながら,とりあえずダウンロードしてみる.

やたら長いインストールが終わると,C:\直下に大量のディレクトリができる.
C直下は綺麗にしておきたい身としては,勝手にフォルダ作りまくっていらっとするけど,しょうがない.

C:\Azul
C:\hadoop
C:\HadoopInstallFiles
C:\hdp
C:\hdpdata

が作成されたようだ.
このうち,C:\hdpがHadoopの各種ライブラリがインストールされているフォルダとなる.
各種アプリがすでにインストールされているが,
C:\hdp\hadoop-2.4.0.2.1.3.0-1981以下にあるのがHadoop本体.
とりあえず,C:\hdp\hadoop-2.4.0.2.1.3.0-1981でコマンドプロンプトを開き,
C:\hdp\hadoop-2.4.0.2.1.3.0-1981>bin\hadoop version
Hadoop 2.4.0.2.1.3.0-1981
Subversion git@github.com:hortonworks/hadoop-monarch.git -r 3c481b5d3c117181c4be
3d3a25697e2e0574f972
Compiled by jenkins on 2014-07-15T14:23Z
Compiled with protoc 2.5.0
From source with checksum 8a8b83aa11aad760f2a019f4cea188c
This command was run using /C:/hdp/hadoop-2.4.0.2.1.3.0-1981/share/hadoop/common
/hadoop-common-2.4.0.2.1.3.0-1981.jar

というわけで,hadoopコマンドが動くことは確認.
さて,これをどうやってEclipseから呼び出すか,それが問題だ.

ちなみに,参考ページにはProgram Filesの問題からJAVA_HOMEをHDInsightでインストールした物にしないと駄目だ,と書いてあるが,特に問題なくインストール完了.
Microsoft Web Platform Installer5.0になって,改良されたのかしらん?


Windows7にCDH5.1.3でHadoop・その1失敗編

Hadoopプログラミングをする上で,いちいちサーバにjarを持って行ってテストをするのは大変すぎるので,Windows上のEclipseでHadoopプログラムをデバッグしていたんだけど,新しいHadoopをCDH5.1.3で入れたので,新しいバージョン.
以前の情報はこちら

インストール

まず,
https://ccp.cloudera.com/display/SUPPORT/CDH+Downloads
で,CDH5.1.3のtarを取得.
CDH5.1.3なのはサーバとバージョンを併せるため.

とりあえず,
hadoop-2.3.0-cdh5.1.3.tar.gz
をダウンロード.
Cygwinの/usr/local/libに展開.

$ cd /usr/local/lib
$ tar zxvf hadoop-2.3.0-cdh5.1.3.tar.gz
$ ln -s hadoop-2.3.0-cdh5.1.3 hadoop
$ ln -s /cygdrive/c/Program\ Files\ /Java/jdk1.7.0_60/ jdk1.7
$ ln -s jdk1.7 java

Windowsの環境変数にHADOOP_HOMEを追加. Pathも追加.
HADOOP_HOME:C:\usr\cygwin\usr\local\lib\hadoop
PATH:...;%HADOOP_HOME%\bin

hadoop/bin-mapreduce1/hadoop-config.shに以下を追記
# the root of the Hadoop installation
if [ -z "$HADOOP_HOME" ]; then
  export HADOOP_HOME=`dirname "$this"`/..
else
  export HADOOP_HOME=$(cygpath -u "$HADOOP_HOME")
fi
hadoopにも以下を追記.
#cygwin=false
cygwin=true
# some Java parameters
export JAVA_HOME=/usr/local/lib/java

Windowsではローカルモードでしか起動しないので,以下のように設定する.
etc/hadoop/core-site.xml
<configuration>
    <property>
        <name>hadoop.tmp.dir</name>
        <value>C:\tmp\hadoop</value>
    </property>
</configuration>
etc/hadoop/mapred-site.xml
<configuration>
    <property>
        <name>mapred.job.tracker</name>
        <value>local</value>
    </property>
    <property>
         <name>dfs.replication</name>
         <value>1</value>
    </property>
</configuration>

Eclipseプラグイン

ここまで来て問題発生.
ここにあるとおり,プラグインの公式な物がHadoop2.x系では出ていないとのこと.
ということで,どうしようかと考えていたら.
Windows用HadoopHDInsightの記事を発見.
とりあえずこれを試してみることにする.
というところで,続きは次回.

2014年11月14日金曜日

人狼知能エージェントの作成・その5 Talk編

発話の生成

人狼知能ver0.1.x系では,コミュニケーションはかなり制限されています.
 話すことが出来るのは,

  1. カミングアウト
  2. 占い結果
  3. 霊媒結果
  4. 護衛先の報告
  5. 他人の職業の予想
  6. 他人の意見への同意
  7. 他人の意見への反対
  8. 投票先の宣言
  9. 襲撃先の宣言(人狼のみ)
  10. パス
の10種類となります.
そのため,それ以外の発話を行わないように,対話生成クラスを使って発話内容を作成することが推奨されています.
発話生成には,TemplateTalkFactoryを,人狼同士の囁きの生成には,TemplateWhisperFactoryを使います.
たとえば,占い師をカミングアウトしたい場合は,
String contents = TemplateTalkFactory.comingout(getMe(), Role.SEER);
// contents = "COMINGOUT Agent[00] SEER"
とします.
これによって,占い師であることや,霊媒師であることを宣言することが可能です.

以下,他のテンプレートによる発話の例を示します.
//カミングアウト
//「私は占い師です」
String comingout = TemplateTalkFactory.comingout(mySelf, Role.SEER);
// comingout = "COMINGOUT Agent[07] SEER"

//占い結果の報告
// 「targetは人狼だった」
String divine = TemplateTalkFactory.divined(target, Species.WEREWOLF);
// divine = "DIVINED Agent[01] WEREWOLF"

//霊媒結果の報告
//「targetは人間だった」
String inquest = TemplateTalkFactory.inquested(target, Species.HUMAN);
// inquest = "INQUESTED Agent[01] HUMAN"

//護衛先の報告
//「targetを護衛した」
String guard = TemplateTalkFactory.guarded(target);
// guard = "GUARDED Agent[01]"

//予想
//「targetは村人だと思う」
String estimate = TemplateTalkFactory.estimate(target, Role.VILLAGER);
// estimate = "ESTIMATE Agent[01] VILLAGER"

//投票先の宣言
//「targetに投票する」
String vote = TemplateTalkFactory.vote(target);
// vote = "VOTE Agent[01]"

//同意
//「1日目の発言001に賛成」
String agree = TemplateTalkFactory.agree(TalkType.TALK, 1, 1);
// agree = "AGREE TALK day1 ID:1"

//反対
//「4日目の発言020に反対」
String disagree = TemplateTalkFactory.disagree(TalkType.TALK, 4, 20);
// disagree = "DISAGREE TALK day4 ID:20"

//襲撃先の宣言(人狼のみ)  
//「targetを襲撃先にする」
String attack = TemplateWhisperFactory.attack(target);
// attack = "ATTACK Agent[03]"

//一回パス(他に発言者がいなくても,次の順番で発言を行う)
String skip = TemplateTalkFactory.skip();
//skip = "Skip"

//パス(他に発言者がいなければその日の発言は終了)
String over = TemplateTalkFactory.over();
// over = "Over"
なお,通常の会話に利用する発話は,
TemplateTalkFactory
を利用し,人狼が囁きを行う場合は,
TemplateWhisperFactory
を利用します.

発話の理解

自分を含めた他のエージェントの発話は,Talk型として渡されます. その具体的な中身は,
Talk talk = talkList.get(0);
String contents = talk.getContents();
として獲得できますが,contentsの中身は先に見たTemplateTalkFactoryで作成した文字列がそのまま獲得されるだけのため,何らかの形で文字列が表す意味を理解しなければいけません.
そこで,定型文を簡単に理解できるようにParserが用意されています.
たとえば,カミングアウトした発話のみを見つけて,カミングアウトしたエージェントとその職業を表示するには以下のようにします.
List talkList = gameInfo.getTalkList();
for(Talk talk:talkList){
 Utterance utterance = new Utterance(talk.getContent());
 Topic topic = utterance.getTopic();
 if(topic == Topic.COMINGOUT){
  Agent target = utterance.getTarget();
  Role role = utterance.getRole();
  System.out.println(target+" comingouts as "+role);
 }
}
もしエージェント10が占い師としてカミングアウトしていたら,コンソールに以下のように表示されます.
Agent[10] comingouts as SEER
Utteranceから抽出できるTopicとそれに付随する変数の一覧を以下に示します.
Topic内容利用可能な情報
ESTIMATE予想getTarget(), getRole()
COMINGOUTカミングアウトgetTarget(), getRole()
DIVINED占い結果getTarget(), getResult()
INQUESTED霊媒結果getTarget(), getResult()
GUARDED護衛対象getTarget()
VOTE投票宣言getTarget()
AGREE意見に賛成getTalkType(), getTalkDay(), getTalkId()
DISAGREE意見に反対getTalkType(), getTalkDay(), getTalkId()
ATTACK襲撃対象(人狼のみ)getTarget()
OVER現段階では話すことは無い
SKIP今回は発話しない
TemplateTalkFactory,TemplateWhisperFactory,Utteranceクラスを利用することで, 他のエージェントに発話を文字列として扱うこと無く人狼知能を構築することが出来ます.

今回まで5回にわたって人狼知能作成方法について書いてきました.
途中長い中断があったために,バージョンが変わって,若干仕様の変更がありましたが,おおむねこれで人狼知能が作成できるようになったはずです
次回は,自分が作った人狼知能と人間が対戦する方法について解説します.

2014年11月3日月曜日

人狼知能エージェントの作成・その4 番外編・人狼知能ver0.1.1xの変更点

人狼知能ver0.1.11にバージョンアップされました.
ダウンロードはこちらから.
ver0.1.1xから,以降からいくつか大きな変更がありました.
ここでは主な変更点についてお知らせします.

RoleBasePlayerからAbstarctRoleAssignPlayerへ変更

主な変更として, RoleBasePlayerのクラス名が AbstarctRoleAssignPlayer と変更になりました.
これに伴って, 人狼知能エージェントの作成・その1で紹介したMyPlayerは以下のようになります.
package org.aiwolf.tori.player;

import org.aiwolf.client.base.player.AbstarctRoleAssignPlayer;

public class MyPlayer extends AbstarctRoleAssignPlayer{
 
 public MyPlayer() {
  setVillagerPlayer(new MyVillagerPlayer());
 }

 @Override
 public String getName() {
  return MyPlayer.class.getSimpleName();
 }

}
となります.

enum型の変更

ver0.1.9までは,enum型の名称が大文字小文字混じりでしたが, すべて大文字で表記するように変更されました.
たとえば,
Status.ALIVE;
Status.DEAD;
の様に表記します.

人狼プロトコルパーサの改訂

人狼プロトコルのパーサの仕様が変更されました. 人狼知能エージェントの作成・その2 Talk編 ではProtocolクラスなどを利用していましたが, ver0.1.1xからはUtteranceクラスで一括管理するようになりました.
これについては,次回詳細を説明します.

その他,細かい変更点がありますが主な変更点は以上です. 対話部分については早めにブログでサンプルをお見せするようにいたします. ご迷惑おかけしますが,よろしくお願いします.

実行クラスの変更(2014-11-14追記)

実行クラスのパッケージが変更されました. RoleRequestStarterで開始する場合,
org.aiwolf.server.bin.RoleRequestStarter
をメインクラスとして指定してください.

2014年6月27日金曜日

人狼知能エージェントの作成・その3 スタート編

2015/02/09変更
サーバのバージョンの変更に従って,一部ソースコードが変更されています.
古いバージョンをお使いの方はver0.1.15以上にアップデートしてください.

さて,今日は人狼知能Talkする編を書こうと思ったけど,
その前に「テスト用に,自分で繰り返し実行する方法が知りたい」という意見があったので,
先にそちらを書こうと思う.

ゲーム実行のためのメインクラス

AIWolfのゲームを実行するには,

  • AIWolfGame:ゲーム実行サーバ本体クラス
  • GameSetting:ゲームの設定クラス
  • GameServer:エージェントとゲームサーバとの通信用クラス
の三つのクラスと,エージェントクラスを利用する.
以下は実行サンプルのソースコードである.
ただし,このソースコードは,人狼知能エージェントの作成・その1-ver0.1.15対応にある BasePlayerをつかっている.ので,作成していない場合は先にこちらを参照して作成して欲しい.

package org.aiwolf.player.bin;

import java.io.File;
import java.io.IOException;

import java.util.HashMap;
import java.util.Map;

import org.aiwolf.client.base.smpl.SampleRoleAssignPlayer;
import org.aiwolf.common.data.Player;
import org.aiwolf.common.data.Role;
import org.aiwolf.common.data.Team;
import org.aiwolf.common.net.GameSetting;
import org.aiwolf.player.BasePlayer;
import org.aiwolf.server.AIWolfGame;
import org.aiwolf.server.GameData;
import org.aiwolf.server.net.DirectConnectServer;



/**
 * テスト用に人狼をスタートするためのMainクラス
 * @author tori
 *
 */
public class TestStarter {

 /**
  * 他のエージェントの数
  */
 static protected int PLAYER_NUM = 12;
 
 /**
  * @param args
  * @throws IOException 
  */
 public static void main(String[] args) throws IOException {
  

  Map playerMap = new HashMap();//・・・(1)
  //BasePlayerはVillagerに固定
  playerMap.put(new BasePlayer(), Role.VILLAGER);
  for(int i = 0; i < PLAYER_NUM-1; i++){
   //そのほかのエージェントは,SamplePlayerで役職の希望は無し.
   playerMap.put(new SampleRoleAssignPlayer(), null);
  }
  
  
  DirectConnectServer gameServer = new DirectConnectServer(playerMap);//・・・(2)
  GameSetting gameSetting = GameSetting.getDefaultGame(playerMap.size());//・・・(3)
  AIWolfGame game = new AIWolfGame(gameSetting, gameServer);//・・・(4)
  game.setLogFile(new File("./log/TestStarter.log"));//・・・(5)
  game.start();//・・・(6)
  Team winner = game.getWinner();//・・・(7)
  if(winner == Team.VILLAGER){
   System.out.println("Win!");
  }
  GameData gameData = game.getGameData();//・・・(8)

 }

}

ソースについて説明する.

メインクラスの説明

(1)役職の登録
まずエージェントクラスと当該エージェントを付けたい役職をMap型クラスに登録する.
新しいインスタンスを作成し,当該エージェントを付けたRoleをMapに登録していくだけである.
この時,各役職の人数制限を超えたエージェントが同じ役職に希望していた場合,その中のいずれかのエージェントが希望する役職になり, それ以外のエージェントは余った役職に割り当てられる.
また,役職を特に指定したくないエージェントはnullをsetすればよい.
この例では,自作のBasePlayerだけVillagerを指定し,それ以外はSampleRoleAssignPlayerとして,いずれも特に役職を指定していない.
(2)Connectorの作成
人狼サーバは,TCPIPを使ってサーバとエージェントを接続するが,テスト用環境ではTCPIPを使う必要性がない. そのような場合は,DirectConnectServerを使う.
ここでは,先ほどの希望役職を指定したMapを引数として,DirectConnectServerを作成している.
なお,すべてのエージェントにランダムに役職を与えて実行する場合は,
List<Agent> playerList = new ArrayList<Agent>();
//中略
DirectConnectServer gameServer = new DirectConnectServer(playerList);
のように,AgentのListを引数としてDirectConnectServerを作成すればよい.
(3)GameSettingの作成
GameSettingクラスは,さまざまなゲームの条件を決めるクラスである.
各役職に付けるエージェント数や,各自の投票結果を他のエージェントに知らせるか,などを決定するものである.
現状では,
GameSetting.getDefaultGameエージェント数);
で取得できるデフォルトのGameSettingを利用することが推奨されている.
これ以外のGameSettingを利用した場合の動作の保証はない.
(4)AIWolfGameの作成
ここでは,ゲーム実行サーバ本体の作成を行っている.
GameSettingとGameServerを指定して作成する.
(5)LogFileの設定
ゲームログを出力する場合,ここでログファイルを指定する.
ログを設定しない場合,ゲームログはコンソールにしか表示されない.
(6)ゲームの開始
game.start();
によって,ゲームが開始される.以降,ゲームが終了するまで自動で処理される.
(7)勝利チームの獲得
ゲーム終了後,どちらが勝ったのかを取得する.
勝利したかどうかに応じてエージェントを進化させたい場合などに利用することが可能.
単純に何回も実行して勝率を確認するためにも利用できる.
(8)全ゲームデータの取得
GameData gameData = game.getGameData();
で獲得できるGameDataは,このゲームで行われたすべての行動が記録されているデータクラスである.
このデータクラスを使って,ゲームの解析を行うことが可能.

以上で,起動メインクラスの説明は終了.
いくつかエージェントを作って,Roleを割り当て方を変えながらエージェントの実力をテストしてみればよいと思う.

2014年6月26日木曜日

人狼知能エージェントの作成・その2 Talk編

Talkの仕様変更に伴い,本記事の内容は利用できなくなりました.人狼知能エージェントの作成・その5 Talk編をご覧ください.






さて,人狼知能作成その2である. 今回は会話にチャレンジする.
とりあえず,人狼知能のサーバ,クライアントなどをGitHubにUpしたので, 中身を見たい人は https://github.com/aiwolf/ を参照して欲しい.
この記事は,書いている時点で最新のライブラリを使っているので,古いバージョンだと一部動かないこともあるかもしれないし, 新しいバージョンになっても変更があるかもしれないので,その点はご了承ください.
まだ,開発中のライブラリということで,ご勘弁を・・・
一応動かない機能が発生する場合はマイナーバージョンをアップするつもりではいます.
ちなみに,現在これを書いている段階で,バージョンは0.1.6.

発話の取得

ところで,前回までは村人エージェントについて,適当に自分以外の人に投票するようにした.
今回は,ほかの人が何を言っているのか聞いてみたいと思う.
他の人が話したことは,GameInfo#gameInfo.getTalkList()で獲得可能である.
まずは,自分の話す番が来るたびにそれまでの会話を確認するようにしてみよう.
自分が話す番が来たら,talkメソッドが呼ばれるので,そこに以下のように記述する.
 @Override
 public String talk() {
  GameInfo gameInfo = getLatestDayGameInfo();
  List<Talk> talkList = gameInfo.getTalkList();
  
  System.out.println("Today's talk");
  for(Talk talk:talkList){
   System.out.println(talk);
  }
  return null;
 }
これを実行すると,
Today's talk
Day00[000] Agent[02] 5 werewolf
中略
Today's talk
Day01[000] Agent[02] 6 werewolf
Day01[001] Agent[04] ( 11 werewolf ) and ( 4 comingout seer ) and ( 3 inspected HUMAN ) 
Day01[002] Agent[09] 4 werewolf
Day01[003] Agent[10] 2 werewolf
Day01[004] Agent[01] 12 werewolf
Day01[005] Agent[03] 12 werewolf
Day01[006] Agent[12] 2 werewolf
中略
Today's talk
Day01[000] Agent[02] 6 werewolf
Day01[001] Agent[04] ( 11 werewolf ) and ( 4 comingout seer ) and ( 3 inspected HUMAN ) 
Day01[002] Agent[09] 4 werewolf
Day01[003] Agent[10] 2 werewolf
Day01[004] Agent[01] 12 werewolf
Day01[005] Agent[03] 12 werewolf
Day01[006] Agent[12] 2 werewolf
Day01[007] Agent[06] 1 werewolf
Day01[008] Agent[11] 10 werewolf
Day01[009] Agent[11] Over
Day01[010] Agent[10] Over
Day01[011] Agent[03] Over
Day01[012] Agent[04] Over
中略
Today's talk
Day01[000] Agent[02] 6 werewolf
Day01[001] Agent[04] ( 11 werewolf ) and ( 4 comingout seer ) and ( 3 inspected HUMAN ) 
Day01[002] Agent[09] 4 werewolf
Day01[003] Agent[10] 2 werewolf
Day01[004] Agent[01] 12 werewolf
Day01[005] Agent[03] 12 werewolf
Day01[006] Agent[12] 2 werewolf
Day01[007] Agent[06] 1 werewolf
Day01[008] Agent[11] 10 werewolf
Day01[009] Agent[11] Over
Day01[010] Agent[10] Over
Day01[011] Agent[03] Over
Day01[012] Agent[04] Over
Day01[013] Agent[06] Over
Day01[014] Agent[02] 9 werewolf
Day01[015] Agent[09] Over
Day01[016] Agent[12] Over
Day01[017] Agent[01] Over
Day01[018] Agent[09] Over
Day01[019] Agent[11] Over
Day01[020] Agent[01] Over
Day01[021] Agent[06] Over
Day01[022] Agent[12] Over
Day01[023] Agent[04] Over
というように表示されるはず.
これを見れば分かる通り,getTalkListは,gameInfoが指す日のすべてのTalkを返してくる. したがって,前回からの差分は取ることができない.
差分がとりたい場合は,前回どこまでのTalkを取得したか記憶しておく必要がある.
そこで,アップデートされたTalkだけを取得するメソッドgetUpdatedTalkを作成してみる.
 @Override
 public String talk() {
  List<Talk> talkList = getUpdatedTalk();
  
  System.out.println("Updated talk");
  for(Talk talk:talkList){
   System.out.println(talk);
  }
  return null;
 }

 /**
  * 前回最後に獲得したTalkのインデックス
  */
 int lastTalkIdx = -1;
 
 /**
  * 前回最後に獲得したTalkの日付
  */
 int lastTalkDay = -1;
 
 /**
  * 前回Talkを取得した後投稿されたTalkのみを獲得する
  * @return 前回獲得したTalkとの差分が入ったList
  */
 protected List<Talk> getUpdatedTalk(){
  GameInfo gameInfo = getLatestDayGameInfo();
  List<Talk> talkList = gameInfo.getTalkList();
  
  if(lastTalkDay != gameInfo.getDay()){
   lastTalkDay = gameInfo.getDay();
   if(!talkList.isEmpty()){
    lastTalkIdx = talkList.get(talkList.size()-1).getIdx();
   }
   return talkList;
  }
  for(int i = 0; i < talkList.size(); i++){
   if(talkList.get(i).getIdx() > lastTalkIdx){
    lastTalkIdx = talkList.get(talkList.size()-1).getIdx();
    return talkList.subList(i, talkList.size());
   }
  }
  return new ArrayList<>();
 }
Updated talk
Day00[000] Agent[04] 7 werewolf
Day00[001] Agent[08] 4 werewolf
Day00[002] Agent[05] 11 werewolf
Day00[003] Agent[10] 4 werewolf
Day00[004] Agent[11] 4 werewolf
Day00[005] Agent[01] 5 werewolf
Day00[006] Agent[12] 8 werewolf
Day00[007] Agent[07] 10 werewolf
Day00[008] Agent[02] 10 werewolf
Day00[009] Agent[06] 10 werewolf
中略
Updated talk
Day00[010] Agent[09] 6 werewolf
Day00[011] Agent[08] Over
中略
Updated talk
Day02[000] Agent[01] 10 werewolf
Day02[001] Agent[10] ( 2 werewolf ) and ( 10 comingout seer ) and ( 9 inspected HUMAN ) and ( 6 inspected werewolf ) 
Day02[002] Agent[06] ( 1 werewolf ) and ( 6 comingout medium ) and ( 3 medium_telled HUMAN ) and ( 4 medium_telled HUMAN ) 
Day02[003] Agent[02] 6 werewolf
Day02[004] Agent[09] 6 werewolf
Day02[005] Agent[11] 6 werewolf
というわけで,無事前回との差分を獲得できた.

発話の内容

さて,次に他のエージェントが何を言っているのかを理解しよう.
ログに表示される
Day00[000] Agent[04] 7 werewolf
は,0日目のTalkIndex000は,Agent04が,「7 werewolf」と発言していることを示している.
Day00[011] Agent[08] Over
は,エージェント08はもうこれ以上話すことはない,という発言をしている.
さらに,
Day02[001] Agent[10] ( 2 werewolf ) and ( 10 comingout seer ) and ( 9 inspected HUMAN ) and ( 6 inspected werewolf ) 
Day02[002] Agent[06] ( 1 werewolf ) and ( 6 comingout medium ) and ( 3 medium_telled HUMAN ) and ( 4 medium_telled HUMAN ) 
では,エージェント10が,「( 2 werewolf ) and ( 10 comingout seer ) and ( 9 inspected HUMAN ) and ( 6 inspected werewolf ) 」と発言し, 同様にAgent06も長い発言をしている.
このうち,Overは良いとして,それ以外の発話はどういう意味だろうか.
まず,
Day00[000] Agent[04] 7 werewolf
は,「Agent7が人狼ではないかと疑っている」宣言である.事実上,今日投票するのはAgent7であると同等のもの・・・らしい.
(これは,SamplePlayerの実装依存だが,ソースコードを見ると,そうなっている)
次に,長い発話についてみてみよう.
Day02[001] Agent[10] ( 2 werewolf ) and ( 10 comingout seer ) and ( 9 inspected HUMAN ) and ( 6 inspected werewolf ) 
については,
  • Agent2が人狼だと思う
  • Agent10は占い師だとカミングアウトする
  • Agent9は占いの結果人間だった
  • Agent6は占いの結果人狼だった
という4つの内容が含まれた発話である.
一方,
Day02[001] Agent[10] ( 2 werewolf ) and ( 10 comingout seer ) and ( 9 inspected HUMAN ) and ( 6 inspected werewolf ) 
Day02[002] Agent[06] ( 1 werewolf ) and ( 6 comingout medium ) and ( 3 medium_telled HUMAN ) and ( 4 medium_telled HUMAN ) 
は,
  • Agent1が人狼だと思う
  • Agent6は霊媒師だとカミングアウトする
  • Agent3は霊媒の結果人間だった
  • Agent4は霊媒の結果人間だった
という内容を含んだ発話である.

発話の理解

発話は上記のようにテキストとして送られてくるため,それをエージェントが理解する必要がある. このとき,直接発話のテキストを見て,何を言っているのか理解をしてもよいが, そのためのParser(構文解析機)を作るのは手間がかかるため,aiwolf-clientライブラリでは,発話をマシンリーダブルに変換するためのParserライブラリが用意されている.
そこで,実際にTalkの内容をパースしてみよう.
Talkのパースには,Protocolクラスを利用する.
Protocolクラスは,人狼プロトコルを理解するためのクラスで,複数のUtteranceから構成され, UtteranceはSentenceTypeとPassageからなり・・・という多層構造を持っている.
大まかなProtocolクラスの概略は以下の通り.

プロトコルクラスのインスタンスをパースした発話から作成すると,この構造に従って発話をパースしてくれる.
実際に以下のコードで実行してみる.
なお,ここではその日の会話を総合して投票先を決める,ということを考え,talkメソッドではなく,voteメソッドでtalkのパースを行う.
 @Override
 public Agent vote() {
  GameInfo gameInfo = getLatestDayGameInfo();
  Agent myself = gameInfo.getAgent();

  
  List<Talk> talkList = gameInfo.getTalkList();
  for(Talk talk:talkList){
   System.out.println(talk);
   Protocol protocol = new Protocol(talk.getContent());
   List<Utterance> utteranceList = protocol.getUtterances();
   System.out.println("Num of Utterance="+utteranceList.size());
   for(Utterance utterance:utteranceList){
    SentenceType sentenceType = utterance.getSentenceType();
    Passage passage = utterance.getPassage();
    
    if(sentenceType != null){
     System.out.println("sentenceType");
     System.out.println(sentenceType.getUv());
     System.out.println(sentenceType.getRate());
    }
    
    if(passage != null){
     System.out.println("passage");
     System.out.println("Action="+passage.getAction());
     System.out.println("Attribution="+passage.getAttribution());
     System.out.println("Category="+passage.getCategory());
     System.out.println("Object="+passage.getObject());
     System.out.println("State="+passage.getState());
     System.out.println("Subject="+passage.getSubject());
     System.out.println("Verb="+passage.getVerb());
    }
    
   }
  }
  
  List<Agent> agentList = gameInfo.getAliveAgentList();

  for(Agent agent:agentList){
   if(agent != myself){
    System.out.println(myself+" vote to "+agent);
    return agent;
   }
  }
  throw new AIWolfRuntimeException("Something wrong");
 }

すると,「7 werewolf」に関してだと,以下のような出力が得られる.
Day00[003] Agent[07] 7 werewolf
Num of Utterance=1
passage
Action=null
Attribution=null
Category=ESTIMATE
Object=null
State=werewolf
Subject=Agent[07]
Verb=is
特に内容がないところにはnullが入ってくるので注意が必要.
さて,この中身について具体的にみる. まず「7 werewolf」という発話は,「Agent7は人狼だと思う」という意味を持つ.
文としては単文のため,Utteranceは1つ.
そのUtteranceを分解すると,
  • カテゴリは推定(ESTIMATE)
  • 対象(Subject)はAgent07
  • 動詞はis
  • 状態は人狼(werewolf)
となっていることが分かる.

一方,長い発話の場合はどうなるか, 「( 2 werewolf ) and ( 4 comingout seer ) and ( 12 inspected HUMAN ) and ( 10 inspected 」を例に見てみよう.
Day03[005] Agent[04] ( 2 werewolf ) and ( 4 comingout seer ) and ( 12 inspected HUMAN ) and ( 10 inspected werewolf ) 
Num of Utterance=4
passage2 werewolf部分のUtterance
Action=null
Attribution=null
Category=ESTIMATE
Object=null
State=werewolf
Subject=Agent[02]
Verb=is
passage//4 comingout seer部分のUtterance
Action=null
Attribution=null
Category=COMINGOUT
Object=seer
State=null
Subject=Agent[04]
Verb=comingout
passage//12 inspected HUMAN部分のUtterance
Action=inspected
Attribution=Human
Category=RESULT
Object=null
State=null
Subject=Agent[12]
Verb=inspected
passage//10 inspected部分のUtterance
Action=inspected
Attribution=Werewolf
Category=RESULT
Object=null
State=null
Subject=Agent[10]
Verb=inspected
となる.二番目のPassageにおいて,このエージェントがseerであるとCOMINGOUTしていることが分かる.

他エージェントの発話を考慮した投票先決定

では,最後に他のエージェントの発話に基づいて投票先を決めるようにしてみよう.
ここでは,簡単のために
「seerだとカミングアウトしたエージェントの投票に合わせる」.
という戦略を実装する.

 /**
  * 人狼候補
  */
 Set werewolfCandidateSet = new HashSet<>();
 
 @Override
 public Agent vote() {
  GameInfo gameInfo = getLatestDayGameInfo();
  Agent myself = gameInfo.getAgent();

  List<Talk> talkList = gameInfo.getTalkList();
  for(Talk talk:talkList){
   Protocol protocol = new Protocol(talk.getContent());
   List<Utterance> utteranceList = protocol.getUtterances();
   for(Utterance utterance:utteranceList){
    SentenceType sentenceType = utterance.getSentenceType();
    Passage passage = utterance.getPassage();
    
    
    if(passage != null){
     if(passage.getCategory() == Category.COMINGOUT){
      if(passage.getObject() == Role.seer){
       Agent candidate = talk.getAgent();
       seerCandidateSet.add(candidate);
      }
     }
    }
   }
  }
  
  System.out.println("Find Candidate");
  //占い師の意見だけ採用
  for(Talk talk:talkList){
   if(!seerCandidateSet.contains(talk.getAgent())){
    continue;
   }
   Protocol protocol = new Protocol(talk.getContent());
   List<Utterance> utteranceList = protocol.getUtterances();
   for(Utterance utterance:utteranceList){
    Passage passage = utterance.getPassage();
    
    if(passage.getCategory() == Category.RESULT && passage.getVerb() == Verb.inspected){
     if(passage.getAttribution() == Species.Werewolf){
      System.out.println(talk);
      Agent candidate = passage.getSubject();
      if(candidate != myself){
       werewolfCandidateSet.add(candidate);
      }
      else{
       //自分を人狼だと言ってきたら,その占い師こそが人狼だ!
       seerCandidateSet.remove(talk.getAgent());
       werewolfCandidateSet.add(talk.getAgent());
      }
     }
    }
   }
  }
  
  for(Agent candidate:seerCandidateSet){
   System.out.println("Seer candidate:"+candidate+" "+gameInfo.getStatusMap().get(candidate));
  }
  for(Agent candidate:werewolfCandidateSet){
   System.out.println("Werewolf candidate:"+candidate+" "+gameInfo.getStatusMap().get(candidate));
  }
  
  //見つかった人狼候補に生きているものがいれば,そこに投票
  for(Agent candidate:werewolfCandidateSet){
   if(gameInfo.getStatusMap().get(candidate) == Status.alive){
    System.out.println(myself+" vote to "+candidate+" by seer's result");
    return candidate;
   }
  }
  
  //生きている人狼候補がいなければ,適当に投票
  List<Agent> agentList = gameInfo.getAliveAgentList();

  for(Agent agent:agentList){
   if(agent != myself || seerCandidateSet.contains(agent)){
    System.out.println(myself+" vote to "+agent);
    return agent;
   }
  }
  throw new AIWolfRuntimeException("Something wrong");
 }

この結果以下のようになった.
Find Candidate
Day02[008] Agent[04] ( 12 werewolf ) and ( 3 inspected werewolf ) 
Seer candidate:Agent[04] alive
Seer candidate:Agent[12] alive
Werewolf candidate:Agent[03] alive
Agent[10] vote to Agent[03] by seer's result

中略

===========
Day 03
Agent[03] executed
Agent[12] divine Agent[04]. Result is Human
Agent[07] guarded Agent[09]@2 guarded
null attacked
======
Agent[01] SamplePlayer dead medium
Agent[02] SamplePlayer alive villager
Agent[03] SamplePlayer dead werewolf executed
Agent[04] SamplePlayer alive possessed divined
Agent[05] SamplePlayer alive villager
Agent[06] SamplePlayer dead werewolf
Agent[07] SamplePlayer alive bodyguard
Agent[08] SamplePlayer alive villager
Agent[09] SamplePlayer alive villager guarded
Agent[10] BasePlayer alive villager
Agent[11] SamplePlayer alive villager
Agent[12] SamplePlayer alive seer
というわけで,seer(Agent12)の占い結果に基づいてAgent03に投票し, 結果として人狼を処刑することに成功した.
今回は,他のエージェントのTalkを理解して,それを投票につなげるコードを書いてみた.
次は,自分の意見をTalkで表明することを目指してみよう.

2014年6月20日金曜日

人狼知能エージェントの作成・その1

ver0.1.16に対応するよう改訂しました(2015/03/07).
ver0.1.15までとの最大の違いは,RoleAssignPlayerに挿入するPlayerがPlayerクラスを継承しなくなったことです.
AbstractVillagerPlayer→AbstractVillagerのようになりました.
ご了承ください.
今日は人狼知能エージェントを作ろうと思うので,その作り方をメモしていく.

まずは,http://www.aiwolf.org/の資料→人狼知能サーバから人狼知能0.1.15をダウンロード.

中身は簡易マニュアルとjarファイルが4つ.
aiwolf-client-0.1.16.jar
aiwolf-common-0.1.16.jar
aiwolf-server-0.1.16.jar
jsonic-1.3.2.jar

Eclipseで新しいプロジェクトを作って,これらのjarファイルをライブラリに登録しておく.

さて,人狼知能エージェントを作るには,まずRoleBasePlayerを継承したクラスを作るところから始まる.
とりあえず,面倒なのでBasePlayerという名前で作成.


BasePlayerは何をやるかというと,職業が割り当てられたときに,職業ごとに異なるPlayerを起動してくれるのである.
人狼においては,占い師になったときと人狼になったときでは,プレイ方法が全然異なる.
そこで,職業が割り当てられた時点で,職業ごとに対応するPlayerクラスを立ち上げるようにしておけば,一つのPlayerクラスに付き一つの職業についてのみ考えれば良くなるわけである.

ここでは,まず村人から作ってみよう.
というわけで,新しくMyVillagerPlayerを作成する.
自作村人エージェントを作るには,org.aiwolf.client.base.player.AbstractViillagerPlayerを継承したクラスを作れば良い.

package org.aiwolf.player;

import org.aiwolf.client.base.player.AbstractVillager;
import org.aiwolf.common.AIWolfRuntimeException;
import org.aiwolf.common.data.Agent;
import org.aiwolf.common.net.GameInfo;

public class MyVillagerPlayer extends AbstractVillager {

 @Override
 public void dayStart() {
  // TODO 自動生成されたメソッド・スタブ
  
 }

 @Override
 public void finish() {
  // TODO 自動生成されたメソッド・スタブ
  
 }

 @Override
 public String talk() {
  // TODO 自動生成されたメソッド・スタブ
  return null;
 }

 @Override
 public Agent vote() {
  // TODO 自動生成されたメソッド・スタブ
  return null;
 }

}


今のところ何もしないが,とりあえずこのMyVillagerPlayerをBasePlayerに登録する.
package org.aiwolf.player;

import org.aiwolf.client.base.player.AbstractRoleAssignPlayer;

public class BasePlayer extends AbstractRoleAssignPlayer {

 public BasePlayer() {
  setVillagerPlayer(new MyVillager());
 }

 @Override
 public String getName() {
  return BasePlayer.class.getSimpleName();
 }
}
まずは,ここで起動してみよう.
起動する場合は,Eclipseのメニューで実行→実行構成
名前はRoleRequestStarterとでもしておいて,メインクラスにRoleRequestStarterを選ぶ.
さらに,引数タグを選んで,プログラムの引数に
-n 12 -c org.aiwolf.player.BasePlayer VILLAGER
と書いておこう.






これで,実行.
Agent[04] request no role
Agent[08] request no role
Agent[01] request no role
Agent[05] request no role
Agent[03] request no role
Agent[10] request no role
Agent[02] request no role
Agent[11] request no role
Agent[09] request villager
Agent[12] request no role
Agent[06] request no role
Agent[07] request no role
===========
Day 00
======
Agent[01] SamplePlayer alive possessed
Agent[02] SamplePlayer alive villager
Agent[03] SamplePlayer alive villager
Agent[04] SamplePlayer alive bodyguard
Agent[05] SamplePlayer alive seer
Agent[06] SamplePlayer alive werewolf
Agent[07] SamplePlayer alive werewolf
Agent[08] SamplePlayer alive medium
Agent[09] BasePlayer alive villager
Agent[10] SamplePlayer alive villager
Agent[11] SamplePlayer alive villager
Agent[12] SamplePlayer alive villager
10-2
===========
こんな感じでBasePlayerがvilagerになっていれば起動は完了.
その後は自動でゲームが進行する. ところで,ここではvoteでnullを返している.つまり投票先を決めていないことになる.
この場合,ver0.1.16ではランダムに投票したことになっている.
別に問題は無いが,せっかくなのでnull以外を返すように変更してみよう.

変更するのは,MyVillagerのvoteメソッド.
 @Override
 public Agent vote() {
  // TODO 自動生成されたメソッド・スタブ
  return null;
 }
まずは,簡単に
・生きているエージェントの中で
・自分を除いて
・一番最初に見つけたエージェント
に投票することにしよう.

進行中のゲームに関する全ての情報は,
getGameInfoで取得可能.
特に,最新の情報は

GameInfo gameInfo = getLatestDayGameInfo();
で取得可能.
GameInfoからは様々な情報が取得可能だが,まずは自分自身のエージェントと状態,職業,そして日付を取得してみよう.
  GameInfo gameInfo = getLatestDayGameInfo();
  Agent myself = gameInfo.getAgent();
  System.out.println("I am "+gameInfo.getStatusMap().get(myself));
  System.out.println("I am "+gameInfo.getRole());
  System.out.println("Today is "+gameInfo.getDay()+" day");
と書いておくと,
I am alive
I am villager
Today is 0 day
と表示され,とりあえずちゃんと動いていることが分かる.
さて,次はいよいよ投票する対象を決めよう.
生きているエージェント一覧は,gameInfo.getAliveAgentList();で取得可能.
このとき,返ってきたエージェントの中で自分じゃない最初のエージェントに投票することにする.
 @Override
 public Agent vote() {
  GameInfo gameInfo = getLatestDayGameInfo();
  Agent myself = gameInfo.getAgent();
  List<agent> agentList = gameInfo.getAliveAgentList();

  for(Agent agent:agentList){
   if(agent != myself){
    System.out.println(myself+" vote to "+agent);
    return agent;
   }
  }
  throw new AIWolfRuntimeException("Something wrong");
 }
自分しかいなかった場合は誰も選ばれずにメソッドが終わってしまうが,ゲームの仕様上そんなことはないので, もしそんなことがあったらAIWolfRuntimeExceptionを投げて終了することにしておこう.
さて,コレで実行するとどうなるか.
6 20, 2014 4:24:48 午後 org.aiwolf.common.util.AiWolfLoggerFactory getSimpleLogger
構成: ログ設定: LogManagerを設定しました。
Agent[07] request no role
Agent[04] request no role
Agent[03] request no role
Agent[01] request no role
Agent[09] request no role
Agent[10] request no role
Agent[08] request no role
Agent[11] request no role
Agent[05] request villager
Agent[12] request no role
Agent[02] request no role
Agent[06] request no role
===========
Day 00
======
Agent[01] SamplePlayer alive seer
Agent[02] SamplePlayer alive werewolf
Agent[03] SamplePlayer alive possessed
Agent[04] SamplePlayer alive medium
Agent[05] BasePlayer alive villager
Agent[06] SamplePlayer alive werewolf
Agent[07] SamplePlayer alive bodyguard
Agent[08] SamplePlayer alive villager
Agent[09] SamplePlayer alive villager
Agent[10] SamplePlayer alive villager
Agent[11] SamplePlayer alive villager
Agent[12] SamplePlayer alive villager
10-2
===========

中略

Agent[05] vote to Agent[03]
6 20, 2014 4:24:48 午後 org.aiwolf.server.AIWolfGame vote
情報: 0,vote,5,3
6 20, 2014 4:24:48 午後 org.aiwolf.server.AIWolfGame vote
情報: 0,vote,6,6
6 20, 2014 4:24:48 午後 org.aiwolf.server.AIWolfGame vote
情報: 0,vote,11,6
6 20, 2014 4:24:48 午後 org.aiwolf.server.AIWolfGame vote
情報: 0,vote,12,1
6 20, 2014 4:24:48 午後 org.aiwolf.server.AIWolfGame vote
情報: 0,vote,9,2
6 20, 2014 4:24:48 午後 org.aiwolf.server.AIWolfGame vote
情報: 0,vote,10,8

中略

===========
Day 01
Agent[06] executed
Agent[01] divine Agent[09]. Result is Human
Agent[07] guarded Agent[08]@0 guarded
Agent[11] attacked
======
Agent[01] SamplePlayer alive seer
Agent[02] SamplePlayer alive werewolf
Agent[03] SamplePlayer alive possessed
Agent[04] SamplePlayer alive medium
Agent[05] BasePlayer alive villager
Agent[06] SamplePlayer dead werewolf executed
Agent[07] SamplePlayer alive bodyguard
Agent[08] SamplePlayer alive villager guarded
Agent[09] SamplePlayer alive villager divined
Agent[10] SamplePlayer alive villager
Agent[11] SamplePlayer dead villager attacked
Agent[12] SamplePlayer alive villager
9-1

中略

===========
Day 02
Agent[10] executed
Agent[01] divine Agent[12]. Result is Human
Agent[07] guarded Agent[01]@1 guarded
null attacked
======
Agent[01] SamplePlayer alive seer guarded
Agent[02] SamplePlayer alive werewolf
Agent[03] SamplePlayer alive possessed
Agent[04] SamplePlayer alive medium
Agent[05] BasePlayer alive villager
Agent[06] SamplePlayer dead werewolf
Agent[07] SamplePlayer alive bodyguard
Agent[08] SamplePlayer alive villager
Agent[09] SamplePlayer alive villager
Agent[10] SamplePlayer dead villager executed
Agent[11] SamplePlayer dead villager
Agent[12] SamplePlayer alive villager divined

中略
===========
Day 03
Agent[05] executed
Agent[01] divine Agent[04]. Result is Human
Agent[07] guarded Agent[02]@2 guarded
Agent[09] attacked
======
Agent[01] SamplePlayer alive seer
Agent[02] SamplePlayer alive werewolf guarded
Agent[03] SamplePlayer alive possessed
Agent[04] SamplePlayer alive medium divined
Agent[05] BasePlayer dead villager executed
Agent[06] SamplePlayer dead werewolf
Agent[07] SamplePlayer alive bodyguard
Agent[08] SamplePlayer alive villager
Agent[09] SamplePlayer dead villager attacked
Agent[10] SamplePlayer dead villager
Agent[11] SamplePlayer dead villager
Agent[12] SamplePlayer alive villager
中略
===========
Day 04
Agent[01] executed
Agent[01] divine Agent[08]. Result is Human
Agent[07] guarded Agent[08]@3 guarded
Agent[01] attacked
======
Agent[01] SamplePlayer dead seer executed attacked
Agent[02] SamplePlayer alive werewolf
Agent[03] SamplePlayer alive possessed
Agent[04] SamplePlayer alive medium
Agent[05] BasePlayer dead villager
Agent[06] SamplePlayer dead werewolf
Agent[07] SamplePlayer alive bodyguard
Agent[08] SamplePlayer alive villager divined guarded
Agent[09] SamplePlayer dead villager
Agent[10] SamplePlayer dead villager
Agent[11] SamplePlayer dead villager
Agent[12] SamplePlayer alive villager

中略

===========
Day 05
Agent[07] executed
Agent[07] guarded Agent[04]@4 guarded
null attacked
======
Agent[01] SamplePlayer dead seer
Agent[02] SamplePlayer alive werewolf
Agent[03] SamplePlayer alive possessed
Agent[04] SamplePlayer alive medium guarded
Agent[05] BasePlayer dead villager
Agent[06] SamplePlayer dead werewolf
Agent[07] SamplePlayer dead bodyguard executed
Agent[08] SamplePlayer alive villager
Agent[09] SamplePlayer dead villager
Agent[10] SamplePlayer dead villager
Agent[11] SamplePlayer dead villager
Agent[12] SamplePlayer alive villager

中略

===========
Day 06
Agent[12] executed
Agent[04] attacked
======
Agent[01] SamplePlayer dead seer
Agent[02] SamplePlayer alive werewolf
Agent[03] SamplePlayer alive possessed
Agent[04] SamplePlayer dead medium attacked
Agent[05] BasePlayer dead villager
Agent[06] SamplePlayer dead werewolf
Agent[07] SamplePlayer dead bodyguard
Agent[08] SamplePlayer alive villager
Agent[09] SamplePlayer dead villager
Agent[10] SamplePlayer dead villager
Agent[11] SamplePlayer dead villager
Agent[12] SamplePlayer dead villager executed

===========
Day 07
Agent[03] executed
Agent[03] attacked
======
Agent[01] SamplePlayer dead seer
Agent[02] SamplePlayer alive werewolf
Agent[03] SamplePlayer dead possessed executed attacked
Agent[04] SamplePlayer dead medium
Agent[05] BasePlayer dead villager
Agent[06] SamplePlayer dead werewolf
Agent[07] SamplePlayer dead bodyguard
Agent[08] SamplePlayer alive villager
Agent[09] SamplePlayer dead villager
Agent[10] SamplePlayer dead villager
Agent[11] SamplePlayer dead villager
Agent[12] SamplePlayer dead villager
===========
Send finish to Agent[03]
Send finish to Agent[04]
Send finish to Agent[01]
Send finish to Agent[02]
Send finish to Agent[07]
Send finish to Agent[08]
Send finish to Agent[05]
Send finish to Agent[06]
Send finish to Agent[11]
Send finish to Agent[12]
Send finish to Agent[09]
Send finish to Agent[10]
Winner:werewolf
というわけで,ちゃんと別のエージェントを指定して投票行動を行っていたことが分かります.
ただし,三日目に本人が処刑されちゃったみたいですね.
しかも,最後は人狼の勝利.
投票行動を適当に行うだけではやはり勝てそうもありません.
次回は,他の人が何を言っているのか理解して,占い師が人狼だと言っているエージェントに投票するように 改良してみたいと思います.
できるのかなあ?



ちなみに,今日作ったMyVillagerのソースコードは以下の通り.
package org.aiwolf.player;

import java.util.List;

import org.aiwolf.client.base.player.AbstractVillager;
import org.aiwolf.common.AIWolfRuntimeException;
import org.aiwolf.common.data.Agent;
import org.aiwolf.common.net.GameInfo;

public class MyVillager extends AbstractVillager {

 @Override
 public void dayStart() {
  // TODO 自動生成されたメソッド・スタブ
  
 }

 @Override
 public void finish() {
  // TODO 自動生成されたメソッド・スタブ
  
 }

 @Override
 public String talk() {
  // TODO 自動生成されたメソッド・スタブ
  return null;
 }

 @Override
 public Agent vote() {
  GameInfo gameInfo = getLatestDayGameInfo();
  Agent myself = gameInfo.getAgent();

  List agentList = gameInfo.getAliveAgentList();

  for(Agent agent:agentList){
   if(agent != myself){
    System.out.println(myself+" vote to "+agent);
    return agent;
   }
  }
  throw new AIWolfRuntimeException("Something wrong");
 }

}
BasePlayerのソースコードは以下の通り.
package org.aiwolf.player;

import org.aiwolf.client.base.player.AbstractRoleAssignPlayer;

public class BasePlayer extends AbstractRoleAssignPlayer {

 public BasePlayer() {
  setVillagerPlayer(new MyVillager());
 }

 @Override
 public String getName() {
  return BasePlayer.class.getSimpleName();
 }
}

2014年5月31日土曜日

www2014とか輪読会

昨年は誰も参加しなくて断念したwwwの輪読会をやったので,
せっかくなのでスライドをアップしてもらった.
なぜか,一部AAMAS2014とIJCAI2013の論文も混ざってるけどw

Modeling and Predicting the Growth and Death of Membership-based Websites
www2014

Fine-grained Data Partitioning Framework for Distributed Database Systems
www2014

Opinion Dybnamics of Skeptical Agent
AAMAS2014

Information Network or Social Network? The Structure of the Twitter Follow Graph
www2014

Timeline Generation: Tracking individuals on Twitter
www2014

The Bursty Dynamics of the Twitter Information Network
www2014

Decision Generalisation from Game Logs in No Limit Texas Hold’em
IJCAI2013

Who Proposed the Relationship? — Recovering the Hidden Directions of Undirected Social Networks
www2014

土曜日丸一日掛けての輪読会お疲れ様でした.
非常に興味深い&楽しい輪読会でした.
半年に一回くらいはやってもいいかも.