├── .gitignore ├── README.ja.md ├── README.md ├── aiwolfpy ├── __init__.py ├── agentproxy.py ├── common │ ├── __init__.py │ └── base.py ├── gameinfoparser.py ├── protocol │ ├── __init__.py │ ├── abstractcontent.py │ ├── contentfactory.py │ ├── contentfactory_test.py │ ├── contents.py │ ├── protocolparser.py │ └── protocolparser_test.py ├── read_log.py └── util │ ├── __init__.py │ └── singleton.py └── python_simple_sample.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | db.sqlite3-journal 62 | 63 | # Flask stuff: 64 | instance/ 65 | .webassets-cache 66 | 67 | # Scrapy stuff: 68 | .scrapy 69 | 70 | # Sphinx documentation 71 | docs/_build/ 72 | 73 | # PyBuilder 74 | target/ 75 | 76 | # Jupyter Notebook 77 | .ipynb_checkpoints 78 | 79 | # IPython 80 | profile_default/ 81 | ipython_config.py 82 | 83 | # pyenv 84 | .python-version 85 | 86 | # pipenv 87 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 88 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 89 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 90 | # install all needed dependencies. 91 | #Pipfile.lock 92 | 93 | # celery beat schedule file 94 | celerybeat-schedule 95 | 96 | # SageMath parsed files 97 | *.sage.py 98 | 99 | # Environments 100 | .env 101 | .venv 102 | env/ 103 | venv/ 104 | ENV/ 105 | env.bak/ 106 | venv.bak/ 107 | 108 | # Spyder project settings 109 | .spyderproject 110 | .spyproject 111 | 112 | # Rope project settings 113 | .ropeproject 114 | 115 | # mkdocs documentation 116 | /site 117 | 118 | # mypy 119 | .mypy_cache/ 120 | .dmypy.json 121 | dmypy.json 122 | 123 | # Pyre type checker 124 | .pyre/ 125 | -------------------------------------------------------------------------------- /README.ja.md: -------------------------------------------------------------------------------- 1 | # AIWolfPy 2 | 3 | aiwolf.orgさんの人狼知能サーバーに、pythonから接続するためのパッケージです。   4 | 5 | 詳しくはこちらの[スライド](https://www.slideshare.net/HaradaKei/aiwolfpy-v049)をご確認ください 6 | 7 | * version0.4.0 の主な変更点 8 | * python3対応しました 9 | * ファイル構成がかなりシンプルになりました 10 | 11 | * version0.4.4 の主な変更点 12 | * daily_finishの廃止 13 | * updateの追加(requestつき) 14 | * connectするものをクラスでなくインスタンスに変更 15 | 16 | * version0.4.9 の主な変更点 17 | * 情報連携をDataFrameがデフォルトになるように変更 18 | 19 | * 必要な環境 20 | * ローカルで対戦するためにJDK 21 | * 参加するだけならpythonのみでも大丈夫です 22 | * Python 23 | * サーバー環境はこちら http://aiwolf.org/python_modules 24 | * 標準パッケージ+numpy, scipy, pandas, sciki-learnの使用を想定しています 25 | * パッケージ等は大会の前に運営さんに確認しましょう、特にtensorflow等、すごいパッケージを使うと大会の運営さんが大変になりますので、早めにお願いしましょう 26 | * スレッドの立ち上げは禁止です。numpy, chainerのオプションはサーバー側でみますが、tensorflowは自己責任で対処してください。参考: http://aiwolf.org/archives/1951 27 | 28 | * 基本的な動かし方(Mac OSX) 29 | * 人狼知能プロジェクトの公式サイト(http://www.aiwolf.org/server/ ) から人狼知能プラットフォーム0.4.Xをダウンロード 30 | * サーバーアプリ起動 ./StartServer.sh 31 | * Javaアプリが起動するので、人数とportを指定して、Connect 32 | * 別のターミナルwindowから、クライアントアプリ起動 ./StartGUIClient.sh 33 | * 別のアプリが起動するので、サーパーのアプリで指定したportにプレイヤーをConnect 34 | * サーバーアプリ側のStart Gameを押す 35 | * サーバーを起動したターミナルに完全なログが出ます 36 | * サーバーアプリ側のログが見やすいです 37 | 38 | * python版の動かし方 39 | * これをクローン 40 | * クライアント接続のタイミングで、別プロセスで./python_sample.pyを実行 41 | * 例: ./python_sample.py -h localhost -p 10000 42 | 43 | * 自分でエージェントを作るには / Creating your own client 44 | * javaな人の記事を参考に、直接python_simple_sample.pyをコピーして書き換えてください 45 | 46 | * 大会に参加するには 47 | * アカウント登録して、名前とか整合するように書き換えてください 48 | * 詳しくは[Slideshareの方](https://www.slideshare.net/HaradaKei/aiwolfpy-v049)をご参考 49 | 50 | * やる予定のこと 51 | * 対戦機能 52 | * デバッグ方法の整理 53 | * 大会頑張る 54 | 55 | * やらない予定のこと 56 | * AIWolfServer, AIWolfCommon相当のpython版の作成 57 | * pythonな人が多数派になって、Server開発する人がいれば考えてもいいかもですが・・・ 58 | * Jython対応 59 | * 私はnumpy大好きなのでやる気はありません 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AIWolfPy 2 | 3 | Create python agents that can play Werewolf, following the specifications of the [AIWolf Project](http://aiwolf.org) 4 | 5 | This has been forked from the official repository by the AIWolf project, and was originally created by [Kei Harada](https://github.com/k-harada). 6 | 7 | # Changelog: 8 | 9 | ## Version 0.4.9a 10 | * Added support material in English 11 | 12 | ## Version 0.4.9 13 | * Changed differential structure (diff_data) into a DataFrame 14 | 15 | ## Version 0.4.4 16 | * removed daily_finish 17 | * Added update callback (with request parameter) 18 | * Connecting is now done through a instance, not a class 19 | 20 | ## Version 0.4.0 21 | * Support for python3 22 | * Made file structure much simpler 23 | 24 | # Running the agent and the server locally: 25 | * Download the AIWolf platform from the [AIWolf public website] (http://www.aiwolf.org/server/) 26 | * Don't forget that the local AIWolf server requires JDK 11 27 | * Start the server with `./StartServer.sh` 28 | * This runs a Java application. Select the number of players, the connection port, and press "Connect". 29 | * In another terminal, run the client management application `./StartGUIClient.sh` 30 | * Another Java application is started. Select the client jar file (sampleclient.jar), the sample client pass, and the port configured for the server. 31 | * Press "Connect" for each instance of the sample agent you wish to connect. 32 | * Run the python agent from this repository, with the command: `./python_sample.py -h [hostname] -p [port]` 33 | * On the server application, press "Start Game". 34 | * The server application will print the log to the terminal, and also to the application window. Also, a log file will be saved on "./log". 35 | * You can see a fun visualization using the "log viewer" program. 36 | 37 | # Running the agent on the AIWolf competition server: 38 | * After you create your account in the competition server, make sure your client's name is the same as your account's name. 39 | * The python packages available at the competition server are listed in this [page](http://aiwolf.org/python_modules) 40 | * You can expect that the usual packages + numpy, scipy, pandas, scikit-learn are available. 41 | * Make sure to check early with the competition runners, specially if you want to use something like an specific version of tensorflow. 42 | * The competition rules forbid running multiple threads. Numpy and Chainer are correctly set-up server side, but for tensorflow you must make sure that your program follows this rule. Please see the following [post](http://aiwolf.org/archives/1951) 43 | * For more information, a tutorial from the original author of this package can be seen in this [slideshare](https://www.slideshare.net/HaradaKei/aiwolfpy-v049) (in Japanese). 44 | -------------------------------------------------------------------------------- /aiwolfpy/__init__.py: -------------------------------------------------------------------------------- 1 | from aiwolfpy.protocol.contentfactory import ContentFactory 2 | from aiwolfpy.protocol.protocolparser import ProtocolParser 3 | from aiwolfpy.common.base import agent 4 | from aiwolfpy.common.base import Role 5 | from aiwolfpy.common.base import Species 6 | from aiwolfpy.gameinfoparser import GameInfoParser 7 | from aiwolfpy.agentproxy import AgentProxy 8 | from aiwolfpy.read_log import read_log 9 | -------------------------------------------------------------------------------- /aiwolfpy/agentproxy.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import json 3 | from aiwolfpy.gameinfoparser import GameInfoParser 4 | 5 | 6 | # decorator / proxy 7 | class AgentProxy(object): 8 | 9 | def __init__(self, agent, my_name, host_name, port, role, logger, parse="pandas"): 10 | self.agent = agent 11 | self.my_name = my_name 12 | self.host_name = host_name 13 | self.port = port 14 | self.role = role 15 | self.sock = None 16 | self.parser = GameInfoParser() 17 | self.base_info = dict() 18 | self.parse_choice = parse 19 | self.logger = logger 20 | self.len_whisper_list = 0 21 | 22 | # parse and run 23 | def initialize_agent(self, game_info, game_setting): 24 | if self.parse_choice == "pandas": 25 | self.parser.initialize(game_info, game_setting) 26 | self.base_info = dict() 27 | self.base_info['agentIdx'] = game_info['agent'] 28 | self.base_info['myRole'] = game_info["roleMap"][str(game_info['agent'])] 29 | self.base_info["roleMap"] = game_info["roleMap"] 30 | diff_data = self.parser.get_game_df_diff() 31 | self.logger.debug("INITIALIZE") 32 | self.logger.debug(self.base_info) 33 | self.logger.debug(diff_data) 34 | self.agent.initialize(self.base_info, diff_data, game_setting) 35 | return None 36 | else: 37 | self.agent.initialize(game_info, game_setting) 38 | 39 | # parse and run 40 | def update_agent(self, game_info, talk_history, whisper_history, request): 41 | if self.parse_choice == "pandas": 42 | for k in ["day", "remainTalkMap", "remainWhisperMap", "statusMap"]: 43 | if k in game_info.keys(): 44 | self.base_info[k] = game_info[k] 45 | self.parser.update(game_info, talk_history, whisper_history, request) 46 | diff_data = self.parser.get_game_df_diff() 47 | self.logger.debug(request) 48 | self.logger.debug(self.base_info) 49 | self.logger.debug(diff_data) 50 | self.agent.update(self.base_info, diff_data, request) 51 | return None 52 | else: 53 | self.agent.update(game_info, talk_history, whisper_history, request) 54 | 55 | def send_response(self, json_received): 56 | res_txt = self._get_json(json_received) 57 | if res_txt is None: 58 | pass 59 | else: 60 | self.sock.send((res_txt + '\n').encode('utf-8')) 61 | return None 62 | 63 | def _get_json(self, json_received): 64 | game_info = json_received['gameInfo'] 65 | if game_info is None: 66 | game_info = dict() 67 | # talk_history and whisper_history 68 | talk_history = json_received['talkHistory'] 69 | if talk_history is None: 70 | talk_history = [] 71 | whisper_history = json_received['whisperHistory'] 72 | if whisper_history is None: 73 | whisper_history = [] 74 | 75 | # delete unnecessary talk and whisper 76 | if 'talkList' in game_info.keys(): 77 | del game_info['talkList'] 78 | if 'whisperList' in game_info.keys(): 79 | whisper_history = game_info['whisperList'][self.len_whisper_list:] 80 | self.len_whisper_list = len(game_info['whisperList']) 81 | del game_info['whisperList'] 82 | 83 | # request must exist 84 | request = json_received['request'] 85 | self.logger.log(1, request) 86 | self.logger.log(1, game_info) 87 | self.logger.log(1, talk_history) 88 | self.logger.log(1, whisper_history) 89 | if request == 'INITIALIZE': 90 | game_setting = json_received['gameSetting'] 91 | self.logger.log(1, game_setting) 92 | else: 93 | game_setting = None 94 | 95 | # run_request 96 | if request == 'NAME': 97 | return self.my_name 98 | elif request == 'ROLE': 99 | return self.role 100 | elif request == 'INITIALIZE': 101 | self.initialize_agent(game_info, game_setting) 102 | return None 103 | else: 104 | # UPDATE 105 | self.update_agent(game_info, talk_history, whisper_history, request) 106 | if request == 'DAILY_INITIALIZE': 107 | self.len_whisper_list = 0 108 | self.agent.dayStart() 109 | return None 110 | elif request == 'DAILY_FINISH': 111 | return None 112 | elif request == 'FINISH': 113 | self.agent.finish() 114 | return None 115 | elif request == 'VOTE': 116 | return json.dumps({'agentIdx': int(self.agent.vote())}, separators=(',', ':')) 117 | elif request == 'ATTACK': 118 | return json.dumps({'agentIdx': int(self.agent.attack())}, separators=(',', ':')) 119 | elif request == 'GUARD': 120 | return json.dumps({'agentIdx': int(self.agent.guard())}, separators=(',', ':')) 121 | elif request == 'DIVINE': 122 | return json.dumps({'agentIdx': int(self.agent.divine())}, separators=(',', ':')) 123 | elif request == 'TALK': 124 | return self.agent.talk().__str__() 125 | elif request == 'WHISPER': 126 | return self.agent.whisper().__str__() 127 | 128 | def connect_server(self): 129 | # socket 130 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 131 | self.sock.settimeout(0.001) 132 | # connect 133 | self.sock.connect((self.host_name, self.port)) 134 | line = '' 135 | 136 | while True: 137 | 138 | try: 139 | line += self.sock.recv(8192).decode('utf-8') 140 | if line == '': 141 | break 142 | except socket.timeout: 143 | pass 144 | 145 | line_list = line.split("\n", 1) 146 | 147 | for i in range(len(line_list) - 1): 148 | if len(line_list[i]) > 0: 149 | json_received = json.loads(line_list[i]) 150 | self.send_response(json_received) 151 | line = line_list[-1] 152 | 153 | try: 154 | # check if valid json 155 | json_received = json.loads(line) 156 | self.send_response(json_received) 157 | line = '' 158 | except ValueError: 159 | pass 160 | 161 | self.sock.close() 162 | return None 163 | -------------------------------------------------------------------------------- /aiwolfpy/common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiwolf/AIWolfPy/29cef501927135a1af28f3ec51ed00de22c6bf09/aiwolfpy/common/__init__.py -------------------------------------------------------------------------------- /aiwolfpy/common/base.py: -------------------------------------------------------------------------------- 1 | from aiwolfpy.util.singleton import Singleton 2 | 3 | 4 | class Species(metaclass=Singleton): 5 | 6 | HUMAN = 'HUMAN' 7 | WEREWOLF = 'WEREWOLF' 8 | ANY = 'ANY' 9 | 10 | # Alias 11 | human = HUMAN 12 | werewolf = WEREWOLF 13 | any = ANY 14 | 15 | 16 | class Role(metaclass=Singleton): 17 | 18 | VILLAGER = 'VILLAGER' 19 | SEER = 'SEER' 20 | MEDIUM = 'MEDIUM' 21 | BODYGUARD = 'BODYGUARD' 22 | WEREWOLF = 'WEREWOLF' 23 | POSSESSED = 'POSSESSED' 24 | ANY = 'ANY' 25 | 26 | # Alias 27 | villager = VILLAGER 28 | seer = SEER 29 | medium = MEDIUM 30 | bodyguard = BODYGUARD 31 | werewolf = WEREWOLF 32 | possessed = POSSESSED 33 | any = ANY 34 | 35 | 36 | def agent(i: int): 37 | assert 0 <= i <= 99 38 | return 'Agent[%s]' % "{0:02d}".format(i) 39 | 40 | 41 | # test 42 | if __name__ == "__main__": 43 | 44 | assert Species.human == "HUMAN" 45 | assert Species.WEREWOLF == "WEREWOLF" 46 | assert Species.ANY == "ANY" 47 | sp = Species() 48 | assert sp.HUMAN == "HUMAN" 49 | assert sp.werewolf == "WEREWOLF" 50 | assert sp.any == "ANY" 51 | 52 | assert Role.villager == "VILLAGER" 53 | assert Role.SEER == "SEER" 54 | assert Role.medium == "MEDIUM" 55 | assert Role.ANY == "ANY" 56 | rl = Role() 57 | assert rl.BODYGUARD == "BODYGUARD" 58 | assert rl.werewolf == "WEREWOLF" 59 | assert rl.POSSESSED == "POSSESSED" 60 | assert rl.any == "ANY" 61 | 62 | assert agent(0) == "Agent[00]" 63 | assert agent(1) == "Agent[01]" 64 | assert agent(71) == "Agent[71]" 65 | 66 | print("OK") 67 | -------------------------------------------------------------------------------- /aiwolfpy/gameinfoparser.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | 3 | 4 | class GameInfoParser(object): 5 | 6 | def __init__(self): 7 | self.pd_dict = {"day": [], "type": [], "idx": [], "turn": [], "agent": [], "text": []} 8 | self.agent_idx = 1 9 | self.finish_cnt = 0 10 | self.night_info = 0 11 | self.rows_returned = 0 12 | 13 | # pandas 14 | def initialize(self, game_info, game_setting): 15 | # me 16 | self.agent_idx = game_info['agent'] 17 | # ROLE MAP on INITIAL 18 | self.pd_dict = {"day": [], "type": [], "idx": [], "turn": [], "agent": [], "text": []} 19 | self.finish_cnt = 0 20 | self.night_info = 0 21 | 22 | # for diff 23 | self.rows_returned = 0 24 | 25 | for k in game_info["roleMap"].keys(): 26 | self.pd_dict["day"].append(game_info["day"]) 27 | self.pd_dict["type"].append('initialize') 28 | self.pd_dict["idx"].append(int(k)) 29 | self.pd_dict["turn"].append(0) 30 | self.pd_dict["agent"].append(int(k)) 31 | self.pd_dict["text"].append('COMINGOUT Agent[' + "{0:02d}".format(int(k)) + '] ' + game_info["roleMap"][k]) 32 | 33 | def get_game_df(self): 34 | return pd.DataFrame(self.pd_dict) 35 | 36 | def get_game_df_diff(self): 37 | ret_df = pd.DataFrame({ 38 | "day":self.pd_dict["day"][self.rows_returned:], 39 | "type":self.pd_dict["type"][self.rows_returned:], 40 | "idx":self.pd_dict["idx"][self.rows_returned:], 41 | "turn":self.pd_dict["turn"][self.rows_returned:], 42 | "agent":self.pd_dict["agent"][self.rows_returned:], 43 | "text":self.pd_dict["text"][self.rows_returned:] 44 | }) 45 | self.rows_returned = len(self.pd_dict["day"]) 46 | return ret_df 47 | 48 | def update(self, game_info, talk_history, whisper_history, request): 49 | 50 | # talk 51 | for t in talk_history: 52 | self.pd_dict["day"].append(t["day"]) 53 | self.pd_dict["type"].append("talk") 54 | self.pd_dict["idx"].append(t["idx"]) 55 | self.pd_dict["turn"].append(t["turn"]) 56 | self.pd_dict["agent"].append(t["agent"]) 57 | self.pd_dict["text"].append(t["text"]) 58 | 59 | # whisper 60 | for w in whisper_history: 61 | self.pd_dict["day"].append(w["day"]) 62 | self.pd_dict["type"].append("whisper") 63 | self.pd_dict["idx"].append(w["idx"]) 64 | self.pd_dict["turn"].append(w["turn"]) 65 | self.pd_dict["agent"].append(w["agent"]) 66 | self.pd_dict["text"].append(w["text"]) 67 | 68 | if request == 'DAILY_INITIALIZE': 69 | 70 | # VOTE 71 | if self.night_info == 0: 72 | # valid vote 73 | for v in game_info['voteList']: 74 | self.pd_dict["day"].append(v["day"]) 75 | self.pd_dict["type"].append("vote") 76 | self.pd_dict["idx"].append(v["agent"]) 77 | self.pd_dict["turn"].append(0) 78 | self.pd_dict["agent"].append(v["target"]) 79 | self.pd_dict["text"].append('VOTE Agent[' + "{0:02d}".format(v["target"]) + ']') 80 | 81 | # EXECUTE 82 | if game_info['executedAgent'] != -1 and self.night_info == 0: 83 | self.pd_dict["day"].append(game_info['day'] - 1) 84 | self.pd_dict["type"].append("execute") 85 | self.pd_dict["idx"].append(0) 86 | self.pd_dict["turn"].append(0) 87 | self.pd_dict["agent"].append(game_info['executedAgent']) 88 | self.pd_dict["text"].append('Over') 89 | 90 | # IDENTIFY 91 | if game_info['mediumResult'] is not None: 92 | m = game_info['mediumResult'] 93 | self.pd_dict["day"].append(m['day']) 94 | self.pd_dict["type"].append("identify") 95 | self.pd_dict["idx"].append(m['agent']) 96 | self.pd_dict["turn"].append(0) 97 | self.pd_dict["agent"].append(game_info['executedAgent']) 98 | self.pd_dict["text"].append('IDENTIFIED Agent[' + "{0:02d}".format(m['target']) + '] ' + m['result']) 99 | 100 | # DIVINE 101 | if game_info['divineResult'] is not None: 102 | d = game_info['divineResult'] 103 | self.pd_dict["day"].append(d['day'] - 1) 104 | self.pd_dict["type"].append("divine") 105 | self.pd_dict["idx"].append(d['agent']) 106 | self.pd_dict["turn"].append(0) 107 | self.pd_dict["agent"].append(d['target']) 108 | self.pd_dict["text"].append('DIVINED Agent[' + "{0:02d}".format(d['target']) + '] ' + d['result']) 109 | 110 | # GUARD 111 | if game_info['guardedAgent'] != -1: 112 | self.pd_dict["day"].append(game_info['day'] - 1) 113 | self.pd_dict["type"].append("guard") 114 | self.pd_dict["idx"].append(self.agent_idx) 115 | self.pd_dict["turn"].append(0) 116 | self.pd_dict["agent"].append(game_info['guardedAgent']) 117 | self.pd_dict["text"].append('GUARDED Agent[' + "{0:02d}".format(game_info['guardedAgent']) + ']') 118 | 119 | # ATTACK_VOTE 120 | # valid attack_vote 121 | for v in game_info['attackVoteList']: 122 | self.pd_dict["day"].append(v["day"]) 123 | self.pd_dict["type"].append("attack_vote") 124 | self.pd_dict["idx"].append(v["agent"]) 125 | self.pd_dict["turn"].append(0) 126 | self.pd_dict["agent"].append(v["target"]) 127 | self.pd_dict["text"].append('ATTACK Agent[' + "{0:02d}".format(v["target"]) + ']') 128 | 129 | # ATTACK 130 | if game_info['attackedAgent'] != -1: 131 | self.pd_dict["day"].append(game_info['day'] - 1) 132 | self.pd_dict["type"].append("attack") 133 | self.pd_dict["idx"].append(0) 134 | self.pd_dict["turn"].append(0) 135 | self.pd_dict["agent"].append(game_info['attackedAgent']) 136 | self.pd_dict["text"].append('ATTACK Agent[' + "{0:02d}".format(game_info['attackedAgent']) + ']') 137 | 138 | # DEAD 139 | for i in range(len(game_info['lastDeadAgentList'])): 140 | self.pd_dict["day"].append(game_info['day']) 141 | self.pd_dict["type"].append("dead") 142 | self.pd_dict["idx"].append(i) 143 | self.pd_dict["turn"].append(0) 144 | self.pd_dict["agent"].append(game_info['lastDeadAgentList'][i]) 145 | self.pd_dict["text"].append('Over') 146 | 147 | self.night_info = 0 148 | 149 | # VOTE/EXECUTE before action 150 | elif request in ['DIVINE', 'GUARD', 'ATTACK', 'WHISPER'] and self.night_info == 0: 151 | # VOTE 152 | if 'latestVoteList' in game_info.keys(): 153 | # valid vote 154 | for v in game_info['latestVoteList']: 155 | self.pd_dict["day"].append(v["day"]) 156 | self.pd_dict["type"].append("vote") 157 | self.pd_dict["idx"].append(v["agent"]) 158 | self.pd_dict["turn"].append(0) 159 | self.pd_dict["agent"].append(v["target"]) 160 | self.pd_dict["text"].append('VOTE Agent[' + "{0:02d}".format(v["target"]) + ']') 161 | 162 | # EXECUTE 163 | if 'latestExecutedAgent' in game_info.keys(): 164 | if game_info['latestExecutedAgent'] != -1: 165 | self.pd_dict["day"].append(game_info['day']) 166 | self.pd_dict["type"].append("execute") 167 | self.pd_dict["idx"].append(0) 168 | self.pd_dict["turn"].append(0) 169 | self.pd_dict["agent"].append(game_info['latestExecutedAgent']) 170 | self.pd_dict["text"].append('Over') 171 | 172 | self.night_info = 1 173 | 174 | # RE_VOTE 175 | elif request == 'VOTE': 176 | # VOTE 177 | if 'latestVoteList' in game_info.keys(): 178 | # valid vote 179 | for v in game_info['latestVoteList']: 180 | self.pd_dict["day"].append(v["day"]) 181 | self.pd_dict["type"].append("vote") 182 | self.pd_dict["idx"].append(v["agent"]) 183 | self.pd_dict["turn"].append(-1) 184 | self.pd_dict["agent"].append(v["target"]) 185 | self.pd_dict["text"].append('VOTE Agent[' + "{0:02d}".format(v["target"]) + ']') 186 | 187 | # RE_ATTACK_VOTE 188 | elif request == 'ATTACK': 189 | # ATTACK_VOTE 190 | if 'latestAttackVoteList' in game_info.keys(): 191 | for v in game_info['latestAttackVoteList']: 192 | self.pd_dict["day"].append(v["day"]) 193 | self.pd_dict["type"].append("attack_vote") 194 | self.pd_dict["idx"].append(v["agent"]) 195 | self.pd_dict["turn"].append(-1) 196 | self.pd_dict["agent"].append(v["target"]) 197 | self.pd_dict["text"].append('ATTACK Agent[' + "{0:02d}".format(v["target"]) + ']') 198 | 199 | # FINISH 200 | elif request == 'FINISH' and self.finish_cnt == 0: 201 | # get full roleMap 202 | for k in game_info["roleMap"].keys(): 203 | self.pd_dict["day"].append(game_info["day"]) 204 | self.pd_dict["type"].append('finish') 205 | self.pd_dict["idx"].append(int(k)) 206 | self.pd_dict["turn"].append(0) 207 | self.pd_dict["agent"].append(int(k)) 208 | self.pd_dict["text"].append('COMINGOUT Agent[' + "{0:02d}".format(int(k)) + '] ' + game_info["roleMap"][k]) 209 | self.finish_cnt += 1 210 | -------------------------------------------------------------------------------- /aiwolfpy/protocol/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiwolf/AIWolfPy/29cef501927135a1af28f3ec51ed00de22c6bf09/aiwolfpy/protocol/__init__.py -------------------------------------------------------------------------------- /aiwolfpy/protocol/abstractcontent.py: -------------------------------------------------------------------------------- 1 | from abc import ABCMeta, abstractmethod 2 | 3 | 4 | class Content(metaclass=ABCMeta): 5 | """Abstract content class : ver.3.6(2019)""" 6 | 7 | def __init__( 8 | self, subject='UNSPEC', target=None, role=None, species=None, verb=None, talk_number=(None, None), 9 | operator=None, day=None, children=[] 10 | ): 11 | # subject 12 | if str(subject) in ['UNSPEC', 'ANY']: 13 | self.subject = subject 14 | elif str(subject).isdigit(): 15 | assert 0 <= int(subject) <= 99 16 | self.subject = 'Agent[%s]' % "{0:02d}".format(int(subject)) 17 | else: 18 | assert str(subject)[6:8].isdigit() 19 | assert str(subject) == 'Agent[%s]' % str(subject)[6:8] 20 | self.subject = subject 21 | 22 | # target 23 | if target is None: 24 | self.target = None 25 | elif str(target) in ['ANY']: 26 | self.target = target 27 | elif str(target).isdigit(): 28 | assert 0 <= int(target) <= 99 29 | self.target = 'Agent[%s]' % "{0:02d}".format(int(target)) 30 | else: 31 | assert str(target)[6:8].isdigit() 32 | assert str(target) == 'Agent[%s]' % str(target)[6:8] 33 | self.target = target 34 | 35 | # role 36 | # add FOX and FREEMASON if necessary 37 | if role is None: 38 | self.role = None 39 | else: 40 | assert str(role) in ['VILLAGER', 'SEER', 'MEDIUM', 'BODYGUARD', 'WEREWOLF', 'POSSESSED', 'ANY'] 41 | self.role = role 42 | 43 | # species 44 | # add something if necessary 45 | if species is None: 46 | self.species = None 47 | else: 48 | assert str(species) in ['HUMAN', 'WEREWOLF', 'ANY'] 49 | self.species = species 50 | 51 | # verb 52 | if verb is None: 53 | self.verb = None 54 | else: 55 | assert str(verb) in [ 56 | 'ESTIMATE', 'COMINGOUT', 'DIVINATION', 'GUARD', 'VOTE', 57 | 'ATTACK', 'DIVINED', 'IDENTIFIED', 'GUARDED', 'VOTED', 58 | 'ATTACKED', 'AGREE', 'DISAGREE', 'Skip', 'Over' 59 | ] 60 | self.verb = verb 61 | 62 | # TODO : maybe talktype necessary 63 | # talk number 64 | assert len(talk_number) == 2 65 | if talk_number[0] is None: 66 | self.talk_number = (None, None) 67 | else: 68 | assert str(talk_number[0]).isdigit() 69 | assert str(talk_number[1]).isdigit() 70 | self.talk_number = talk_number 71 | 72 | # day 73 | self.day = day 74 | 75 | # operator 76 | if operator is None: 77 | self.operator = None 78 | else: 79 | assert str(operator) in [ 80 | 'REQUEST', 'INQUIRE', 'BECAUSE', 'DAY', 81 | 'NOT', 'AND', 'OR', 'XOR' 82 | ] 83 | self.operator = str(operator) 84 | if operator is None: 85 | self.is_operator = False 86 | else: 87 | self.is_operator = True 88 | self.is_control = False 89 | self.children = children 90 | 91 | @abstractmethod 92 | def _get_text(self): 93 | return '' 94 | 95 | def get_text(self): 96 | res = self._get_text() 97 | 98 | if 'Skip' in res: 99 | assert res == 'Skip' 100 | return res 101 | elif 'Over' in res: 102 | assert res == 'Over' 103 | return res 104 | elif str(self.subject) == 'UNSPEC': 105 | return res 106 | else: 107 | return str(self.subject) + ' ' + res 108 | 109 | def __str__(self): 110 | return self.get_text() 111 | 112 | def get_children(self): 113 | return self.children 114 | -------------------------------------------------------------------------------- /aiwolfpy/protocol/contentfactory.py: -------------------------------------------------------------------------------- 1 | from aiwolfpy.util.singleton import Singleton 2 | from aiwolfpy.protocol.contents import * 3 | 4 | 5 | # content factory 6 | class ContentFactory(metaclass=Singleton): 7 | """Factory class for content 8 | Example c = ContentFactory.estimate(3, "SEER") 9 | c.get_text() returns "ESTIMATE Agent[03] SEER" 10 | """ 11 | 12 | @classmethod 13 | def verb(cls, *args): 14 | assert args[0] in [ 15 | 'ESTIMATE', 'COMINGOUT', 'DIVINATION', 'GUARD', 'VOTE', 16 | 'ATTACK', 'DIVINED', 'IDENTIFIED', 'GUARDED', 'VOTED', 17 | 'ATTACKED', 'AGREE', 'DISAGREE', 'Skip', 'Over' 18 | ] 19 | if args[0] == 'ESTIMATE': 20 | return cls.estimate(*args[1:]) 21 | elif args[0] == 'COMINGOUT': 22 | return cls.comingout(*args[1:]) 23 | elif args[0] == 'DIVINATION': 24 | return cls.divination(*args[1:]) 25 | elif args[0] == 'GUARD': 26 | return cls.guard(*args[1:]) 27 | elif args[0] == 'VOTE': 28 | return cls.vote(*args[1:]) 29 | elif args[0] == 'ATTACK': 30 | return cls.attack(*args[1:]) 31 | elif args[0] == 'DIVINED': 32 | return cls.divined(*args[1:]) 33 | elif args[0] == 'IDENTIFIED': 34 | return cls.identified(*args[1:]) 35 | elif args[0] == 'GUARDED': 36 | return cls.guarded(*args[1:]) 37 | elif args[0] == 'VOTED': 38 | return cls.voted(*args[1:]) 39 | elif args[0] == 'ATTACKED': 40 | return cls.attacked(*args[1:]) 41 | elif args[0] == 'AGREE': 42 | return cls.agree(*args[1:]) 43 | elif args[0] == 'DISAGREE': 44 | return cls.disagree(*args[1:]) 45 | elif args[0] == 'Skip': 46 | return cls.skip(*args[1:]) 47 | elif args[0] == 'Over': 48 | return cls.over(*args[1:]) 49 | 50 | # 2.1 51 | @classmethod 52 | def estimate(cls, *args): 53 | assert len(args) == 2 or len(args) == 3 54 | if len(args) == 2: 55 | subject, verb, target, role = 'UNSPEC', 'ESTIMATE', args[0], args[1] 56 | else: 57 | subject, verb, target, role = args[0], 'ESTIMATE', args[1], args[2] 58 | 59 | return SVTRContent(subject, verb, target, role) 60 | 61 | @classmethod 62 | def comingout(cls, *args): 63 | assert len(args) == 2 or len(args) == 3 64 | if len(args) == 2: 65 | subject, verb, target, role = 'UNSPEC', 'COMINGOUT', args[0], args[1] 66 | else: 67 | subject, verb, target, role = args[0], 'COMINGOUT', args[1], args[2] 68 | 69 | return SVTRContent(subject, verb, target, role) 70 | 71 | # 2.2 72 | @classmethod 73 | def divination(cls, *args): 74 | assert len(args) == 1 or len(args) == 2 75 | if len(args) == 1: 76 | subject, verb, target = 'UNSPEC', 'DIVINATION', args[0] 77 | else: 78 | subject, verb, target = args[0], 'DIVINATION', args[1] 79 | 80 | return SVTContent(subject, verb, target) 81 | 82 | @classmethod 83 | def guard(cls, *args): 84 | assert len(args) == 1 or len(args) == 2 85 | if len(args) == 1: 86 | subject, verb, target = 'UNSPEC', 'GUARD', args[0] 87 | else: 88 | subject, verb, target = args[0], 'GUARD', args[1] 89 | 90 | return SVTContent(subject, verb, target) 91 | 92 | @classmethod 93 | def vote(cls, *args): 94 | assert len(args) == 1 or len(args) == 2 95 | if len(args) == 1: 96 | subject, verb, target = 'UNSPEC', 'VOTE', args[0] 97 | else: 98 | subject, verb, target = args[0], 'VOTE', args[1] 99 | 100 | return SVTContent(subject, verb, target) 101 | 102 | @classmethod 103 | def attack(cls, *args): 104 | assert len(args) == 1 or len(args) == 2 105 | if len(args) == 1: 106 | subject, verb, target = 'UNSPEC', 'ATTACK', args[0] 107 | else: 108 | subject, verb, target = args[0], 'ATTACK', args[1] 109 | 110 | return SVTContent(subject, verb, target) 111 | 112 | # 2.3 113 | @classmethod 114 | def divined(cls, *args): 115 | assert len(args) == 2 or len(args) == 3 116 | if len(args) == 2: 117 | subject, verb, target, species = 'UNSPEC', 'DIVINED', args[0], args[1] 118 | else: 119 | subject, verb, target, species = args[0], 'DIVINED', args[1], args[2] 120 | 121 | return SVTSContent(subject, verb, target, species) 122 | 123 | @classmethod 124 | def identified(cls, *args): 125 | assert len(args) == 2 or len(args) == 3 126 | if len(args) == 2: 127 | subject, verb, target, species = 'UNSPEC', 'IDENTIFIED', args[0], args[1] 128 | else: 129 | subject, verb, target, species = args[0], 'IDENTIFIED', args[1], args[2] 130 | 131 | return SVTSContent(subject, verb, target, species) 132 | 133 | @classmethod 134 | def guarded(cls, *args): 135 | assert len(args) == 1 or len(args) == 2 136 | if len(args) == 1: 137 | subject, verb, target = 'UNSPEC', 'GUARDED', args[0] 138 | else: 139 | subject, verb, target = args[0], 'GUARDED', args[1] 140 | 141 | return SVTContent(subject, verb, target) 142 | 143 | @classmethod 144 | def voted(cls, *args): 145 | assert len(args) == 1 or len(args) == 2 146 | if len(args) == 1: 147 | subject, verb, target = 'UNSPEC', 'VOTED', args[0] 148 | else: 149 | subject, verb, target = args[0], 'VOTED', args[1] 150 | 151 | return SVTContent(subject, verb, target) 152 | 153 | @classmethod 154 | def attacked(cls, *args): 155 | assert len(args) == 1 or len(args) == 2 156 | if len(args) == 1: 157 | subject, verb, target = 'UNSPEC', 'ATTACKED', args[0] 158 | else: 159 | subject, verb, target = args[0], 'ATTACKED', args[1] 160 | 161 | return SVTContent(subject, verb, target) 162 | 163 | @classmethod 164 | def agree(cls, *args): 165 | assert len(args) == 1 or len(args) == 2 166 | if len(args) == 1: 167 | subject, verb, talk_number = 'UNSPEC', 'AGREE', args[0] 168 | else: 169 | subject, verb, talk_number = args[0], 'AGREE', args[1] 170 | 171 | return AgreeContent(subject, verb, talk_number) 172 | 173 | @classmethod 174 | def disagree(cls, *args): 175 | assert len(args) == 1 or len(args) == 2 176 | if len(args) == 1: 177 | subject, verb, talk_number = 'UNSPEC', 'DISAGREE', args[0] 178 | else: 179 | subject, verb, talk_number = args[0], 'DISAGREE', args[1] 180 | 181 | return AgreeContent(subject, verb, talk_number) 182 | 183 | @classmethod 184 | def skip(cls, *args): 185 | assert len(args) == 0 or len(args) == 1 186 | if len(args) == 0: 187 | subject, verb = 'UNSPEC', 'Skip' 188 | else: 189 | subject, verb = args[0], 'Skip' 190 | 191 | return ControlContent(subject, verb) 192 | 193 | @classmethod 194 | def over(cls, *args): 195 | assert len(args) == 0 or len(args) == 1 196 | if len(args) == 0: 197 | subject, verb = 'UNSPEC', 'Over' 198 | else: 199 | subject, verb = args[0], 'Over' 200 | 201 | return ControlContent(subject, verb) 202 | 203 | # 3.1 204 | @classmethod 205 | def request(cls, *args): 206 | assert len(args) == 2 or len(args) == 3 207 | if len(args) == 2: 208 | subject, operator, target, content = 'UNSPEC', "REQUEST", args[0], args[1] 209 | else: 210 | subject, operator, target, content = args[0], "REQUEST", args[1], args[2] 211 | 212 | return SOTSContent(subject, operator, target, content) 213 | 214 | @classmethod 215 | def inquire(cls, *args): 216 | assert len(args) == 2 or len(args) == 3 217 | if len(args) == 2: 218 | subject, operator, target, content = 'UNSPEC', "INQUIRE", args[0], args[1] 219 | else: 220 | subject, operator, target, content = args[0], "INQUIRE", args[1], args[2] 221 | 222 | return SOTSContent(subject, operator, target, content) 223 | 224 | # 3.2 225 | @classmethod 226 | def because(cls, *args): 227 | assert len(args) == 2 or len(args) == 3 228 | if len(args) == 2: 229 | subject, operator, content_list = 'UNSPEC', "BECAUSE", [args[0], args[1]] 230 | else: 231 | subject, operator, content_list = args[0], "BECAUSE", [args[1], args[2]] 232 | 233 | return SOS2Content(subject, operator, content_list) 234 | 235 | # 3.3 236 | @classmethod 237 | def day(cls, *args): 238 | assert len(args) == 2 or len(args) == 3 239 | if len(args) == 2: 240 | subject, operator, day, content = 'UNSPEC', "DAY", args[0], args[1] 241 | else: 242 | subject, operator, day, content = args[0], "DAY", args[1], args[2] 243 | 244 | return DayContent(subject, operator, day, content) 245 | 246 | # 3.4 247 | @classmethod 248 | def not_(cls, *args): 249 | assert len(args) == 1 or len(args) == 2 250 | if len(args) == 1: 251 | subject, operator, content_list = 'UNSPEC', "NOT", [args[0]] 252 | else: 253 | subject, operator, content_list = args[0], "NOT", [args[1]] 254 | 255 | return SOS1Content(subject, operator, content_list) 256 | 257 | @classmethod 258 | def and_(cls, *args): 259 | assert len(args) == 1 or len(args) == 2 260 | if len(args) == 1: 261 | subject, operator, content_list = 'UNSPEC', "AND", args[0] 262 | else: 263 | subject, operator, content_list = args[0], "AND", args[1] 264 | 265 | return SOSSContent(subject, operator, content_list) 266 | 267 | @classmethod 268 | def or_(cls, *args): 269 | assert len(args) == 1 or len(args) == 2 270 | if len(args) == 1: 271 | subject, operator, content_list = 'UNSPEC', "OR", args[0] 272 | else: 273 | subject, operator, content_list = args[0], "OR", args[1] 274 | 275 | return SOSSContent(subject, operator, content_list) 276 | 277 | @classmethod 278 | def xor_(cls, *args): 279 | assert len(args) == 2 or len(args) == 3 280 | if len(args) == 2: 281 | subject, operator, content_list = 'UNSPEC', "XOR", [args[0], args[1]] 282 | else: 283 | subject, operator, content_list = args[0], "XOR", [args[1], args[2]] 284 | 285 | return SOS2Content(subject, operator, content_list) 286 | -------------------------------------------------------------------------------- /aiwolfpy/protocol/contentfactory_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import aiwolfpy 3 | from aiwolfpy import agent as ag 4 | 5 | 6 | cf = aiwolfpy.ContentFactory() 7 | rl = aiwolfpy.Role() 8 | sp = aiwolfpy.Species() 9 | 10 | 11 | # unit test 12 | class MyTestCase(unittest.TestCase): 13 | 14 | def test_case1(self): 15 | self.assertEqual( 16 | 'ESTIMATE Agent[10] BODYGUARD', 17 | cf.estimate(ag(10), rl.bodyguard).get_text() 18 | ) 19 | 20 | def test_case2(self): 21 | self.assertEqual( 22 | 'Agent[01] COMINGOUT Agent[03] POSSESSED', 23 | cf.comingout(1, 3, 'POSSESSED').get_text() 24 | ) 25 | 26 | def test_case3(self): 27 | self.assertEqual( 28 | 'Agent[12] REQUEST Agent[07] (DIVINATION Agent[09])', 29 | cf.request('12', '07', cf.divination('09')).get_text() 30 | ) 31 | 32 | def test_case4(self): 33 | self.assertEqual( 34 | 'INQUIRE Agent[29] (GUARD ANY)', 35 | cf.inquire(ag(29), cf.guard('ANY')).get_text() 36 | ) 37 | 38 | def test_case5(self): 39 | self.assertEqual( 40 | 'AND (VOTE Agent[01]) (REQUEST ANY (VOTE Agent[01]))', 41 | cf.and_([cf.vote(1), cf.request('ANY', cf.vote(1))]).get_text() 42 | ) 43 | 44 | def test_case6(self): 45 | self.assertEqual( 46 | 'XOR (ATTACK Agent[01]) (ATTACK Agent[07])', 47 | cf.xor_(cf.attack('1'), cf.attack('7')).get_text() 48 | ) 49 | 50 | def test_case7(self): 51 | self.assertEqual( 52 | 'AND (COMINGOUT Agent[02] SEER) (DIVINED Agent[11] WEREWOLF)', 53 | cf.and_([cf.comingout(ag(2), rl.SEER), cf.divined(ag(11), sp.WEREWOLF)]).get_text() 54 | ) 55 | 56 | def test_case8(self): 57 | self.assertEqual( 58 | 'BECAUSE (Agent[09] IDENTIFIED Agent[01] HUMAN) (ATTACK Agent[09])', 59 | cf.because(cf.identified(9, 1, 'HUMAN'), cf.attack('Agent[09]')).get_text() 60 | ) 61 | 62 | def test_case9(self): 63 | self.assertEqual( 64 | 'OR (Agent[02] GUARDED ANY) (Agent[04] GUARDED ANY) (Agent[14] GUARDED ANY)', 65 | cf.or_([cf.guarded(2, 'ANY'), cf.guarded(4, 'ANY'), cf.guarded(14, 'ANY')]).get_text() 66 | ) 67 | 68 | def test_case10(self): 69 | self.assertEqual( 70 | 'DAY 1 (VOTED Agent[12])', 71 | cf.day(1, cf.voted(ag(12))).get_text() 72 | ) 73 | 74 | def test_case11(self): 75 | self.assertEqual( 76 | 'BECAUSE (NOT (ANY ATTACKED Agent[02])) (NOT (ESTIMATE Agent[02] BODYGUARD))', 77 | cf.because(cf.not_(cf.attacked('ANY', 'Agent[02]')), cf.not_(cf.estimate(2, rl.BODYGUARD))).get_text() 78 | ) 79 | 80 | def test_case12(self): 81 | self.assertEqual( 82 | 'AND (ESTIMATE Agent[02] VILLAGER) (AGREE day3 ID:28)', 83 | cf.and_([cf.estimate('2', 'VILLAGER'), cf.agree((3, 28))]).get_text() 84 | ) 85 | 86 | def test_case13(self): 87 | self.assertEqual( 88 | 'ANY XOR (DISAGREE day2 ID:34) (ESTIMATE Agent[03] SEER)', 89 | cf.xor_('ANY', cf.disagree(('2', '34')), cf.estimate(3, rl.seer)).get_text() 90 | ) 91 | 92 | def test_case14(self): 93 | self.assertEqual( 94 | 'Over', 95 | cf.over().get_text() 96 | ) 97 | 98 | def test_case15(self): 99 | self.assertEqual( 100 | 'Skip', 101 | cf.skip(23).get_text() 102 | ) 103 | 104 | 105 | if __name__ == '__main__': 106 | unittest.main() 107 | -------------------------------------------------------------------------------- /aiwolfpy/protocol/contents.py: -------------------------------------------------------------------------------- 1 | from aiwolfpy.protocol.abstractcontent import Content 2 | 3 | 4 | # children of Content 5 | class SVTRContent(Content): 6 | """Content class in the form [subject] [verb] [target] [role] 7 | ESTIMATE and COMINGOUT 8 | """ 9 | 10 | def __init__(self, subject, verb, target, role): 11 | super().__init__(subject=subject, verb=verb, target=target, role=role) 12 | 13 | def _get_text(self): 14 | return '%s %s %s' % (str(self.verb), str(self.target), str(self.role)) 15 | 16 | 17 | class SVTContent(Content): 18 | """Content class in the form [subject] [verb] [target] 19 | DIVINATION, GUARD, VOTE, ATTACK, GUARDED, VOTED and ATTACKED 20 | """ 21 | 22 | def __init__(self, subject, verb, target): 23 | super().__init__(subject=subject, verb=verb, target=target) 24 | 25 | def _get_text(self): 26 | return '%s %s' % (str(self.verb), str(self.target)) 27 | 28 | 29 | class SVTSContent(Content): 30 | """Content class in the form [subject] [verb] [target] [species] 31 | DIVINED and IDENTIFIED 32 | """ 33 | 34 | def __init__(self, subject, verb, target, species): 35 | super().__init__(subject=subject, verb=verb, target=target, species=species) 36 | 37 | def _get_text(self): 38 | return '%s %s %s' % (str(self.verb), str(self.target), str(self.species)) 39 | 40 | 41 | class AgreeContent(Content): 42 | """Content class in the form [subject] [verb] [talk_number] 43 | AGREE and DISAGREE 44 | """ 45 | 46 | def __init__(self, subject, verb, talk_number): 47 | super().__init__(subject=subject, verb=verb, talk_number=talk_number) 48 | 49 | def _get_text(self): 50 | return '%s day%s ID:%s' % (str(self.verb), str(self.talk_number[0]), str(self.talk_number[1])) 51 | 52 | 53 | class ControlContent(Content): 54 | """Content class for Skip and Over""" 55 | 56 | def __init__(self, subject, verb): 57 | super().__init__(subject=subject, verb=verb) 58 | self.is_control = True 59 | 60 | def _get_text(self): 61 | return self.verb 62 | 63 | 64 | class SOTSContent(Content): 65 | """Content class in the form [subject] [operator] [target] [sentence] 66 | REQUEST and INQUIRE 67 | """ 68 | 69 | def __init__(self, subject, operator, target, content): 70 | super().__init__(subject=subject, operator=operator, target=target, children=[content]) 71 | 72 | def _get_text(self): 73 | return '%s %s (%s)' % (self.operator, self.target, self.get_child().get_text()) 74 | 75 | def get_child(self): 76 | return self.children[0] 77 | 78 | 79 | class SOS1Content(Content): 80 | """Content class in the form [subject] [operator] [sentence] 81 | NOT 82 | """ 83 | 84 | def __init__(self, subject, operator, content_list): 85 | super().__init__(subject=subject, operator=operator, children=content_list) 86 | 87 | def _get_text(self): 88 | return '%s ' % self.operator + " ".join(['(%s)' % c.get_text() for c in self.children]) 89 | 90 | def get_child(self): 91 | return self.children[0] 92 | 93 | 94 | class SOS2Content(Content): 95 | """Content class in the form [subject] [operator] [sentence_1] [sentence_2] 96 | BECAUSE, XOR 97 | """ 98 | 99 | def __init__(self, subject, operator, content_list): 100 | super().__init__(subject=subject, operator=operator, children=content_list) 101 | 102 | def _get_text(self): 103 | return '%s ' % self.operator + " ".join(['(%s)' % c.get_text() for c in self.children]) 104 | 105 | def get_child_1(self): 106 | return self.children[0] 107 | 108 | def get_child_2(self): 109 | return self.children[1] 110 | 111 | 112 | class SOSSContent(Content): 113 | """Content class in the form [subject] [operator] [sentence_1] [sentence_2] 114 | AND, OR 115 | """ 116 | 117 | def __init__(self, subject, operator, content_list): 118 | super().__init__(subject=subject, operator=operator, children=content_list) 119 | 120 | def _get_text(self): 121 | return '%s ' % self.operator + " ".join(['(%s)' % c.get_text() for c in self.children]) 122 | 123 | 124 | class DayContent(Content): 125 | """Content class for day""" 126 | 127 | def __init__(self, subject, operator, day, content): 128 | super().__init__(subject=subject, day=day, operator=operator, children=[content]) 129 | 130 | def _get_text(self): 131 | return '%s %s (%s)' % (self.operator, str(self.day), self.get_child().get_text()) 132 | 133 | def get_child(self): 134 | return self.children[0] 135 | -------------------------------------------------------------------------------- /aiwolfpy/protocol/protocolparser.py: -------------------------------------------------------------------------------- 1 | from aiwolfpy.util.singleton import Singleton 2 | from aiwolfpy.protocol.contentfactory import ContentFactory 3 | 4 | 5 | # parser 6 | class ProtocolParser(metaclass=Singleton): 7 | 8 | @classmethod 9 | def parse_bracket_one_level(cls, sentence): 10 | sub_sentence_list = [] 11 | i0 = 0 12 | level = 0 13 | for i in range(len(sentence)): 14 | if sentence[i] == '(': 15 | level += 1 16 | if level == 1: 17 | i0 = i+1 18 | elif sentence[i] == ')': 19 | level -= 1 20 | if level == 0: 21 | sub_sentence_list.append(sentence[i0:i]) 22 | return sub_sentence_list 23 | 24 | @classmethod 25 | def parse(cls, sentence): 26 | 27 | word_list = sentence.split() 28 | 29 | # subject 30 | subject = 'UNSPEC' 31 | if word_list[0] == 'ANY': 32 | subject = word_list[0] 33 | word_list = word_list[1:] 34 | elif word_list[0][:5] == 'Agent': 35 | subject = word_list[0] 36 | assert str(subject)[6:8].isdigit() 37 | assert str(subject) == 'Agent[%s]' % str(subject)[6:8] 38 | subject = word_list[0] 39 | word_list = word_list[1:] 40 | 41 | # operator 42 | if word_list[0] in ['REQUEST', 'INQUIRE', 'BECAUSE', 'DAY', 'NOT', 'AND', 'OR', 'XOR']: 43 | operator = word_list[0] 44 | parsed_list = cls.parse_bracket_one_level(sentence) 45 | if operator == 'REQUEST': 46 | assert len(parsed_list) == 1 47 | c = ContentFactory.request(subject, word_list[1], cls.parse(parsed_list[0])) 48 | elif operator == 'INQUIRE': 49 | assert len(parsed_list) == 1 50 | c = ContentFactory.inquire(subject, word_list[1], cls.parse(parsed_list[0])) 51 | elif operator == 'BECAUSE': 52 | assert len(parsed_list) == 2 53 | c = ContentFactory.because(subject, cls.parse(parsed_list[0]), cls.parse(parsed_list[1])) 54 | elif operator == 'DAY': 55 | assert len(parsed_list) == 1 56 | c = ContentFactory.day(subject, word_list[1], cls.parse(parsed_list[0])) 57 | elif operator == 'NOT': 58 | assert len(parsed_list) == 1 59 | c = ContentFactory.not_(subject, cls.parse(parsed_list[0])) 60 | elif operator == 'AND': 61 | c = ContentFactory.and_(subject, [cls.parse(t) for t in parsed_list]) 62 | elif operator == 'OR': 63 | c = ContentFactory.or_(subject, [cls.parse(t) for t in parsed_list]) 64 | else: 65 | assert operator == 'XOR' 66 | assert len(parsed_list) == 2 67 | c = ContentFactory.xor_(subject, cls.parse(parsed_list[0]), cls.parse(parsed_list[1])) 68 | return c 69 | 70 | # verb 71 | assert word_list[0] in [ 72 | 'ESTIMATE', 'COMINGOUT', 'DIVINATION', 'GUARD', 'VOTE', 73 | 'ATTACK', 'DIVINED', 'IDENTIFIED', 'GUARDED', 'VOTED', 74 | 'ATTACKED', 'AGREE', 'DISAGREE', 'Skip', 'Over' 75 | ] 76 | verb = word_list[0] 77 | word_list[0] = subject 78 | 79 | if verb == "AGREE": 80 | c = ContentFactory.agree(word_list[0], (int(word_list[1][3:]), int(word_list[2][3:]))) 81 | elif verb == "DISAGREE": 82 | c = ContentFactory.disagree(word_list[0], (int(word_list[1][3:]), int(word_list[2][3:]))) 83 | else: 84 | c = ContentFactory.verb(verb, *word_list) 85 | 86 | return c 87 | 88 | 89 | if __name__ == "__main__": 90 | 91 | con = ProtocolParser.parse("ANY ESTIMATE Agent[01] SEER") 92 | print(con) 93 | con = ProtocolParser.parse("Agent[04] AGREE day4 ID:7") 94 | print(con) 95 | con = ProtocolParser.parse("Over") 96 | print(con) 97 | con = ProtocolParser.parse("VOTE Agent[01]") 98 | print(con) 99 | con = ProtocolParser.parse("NOT (VOTE Agent[01])") 100 | print(con) 101 | con = ProtocolParser.parse("AND (NOT (VOTE Agent[01])) (VOTE Agent[02])") 102 | print(con) 103 | -------------------------------------------------------------------------------- /aiwolfpy/protocol/protocolparser_test.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import aiwolfpy 3 | 4 | pr = aiwolfpy.ProtocolParser() 5 | 6 | 7 | # unit test 8 | class MyTestCase(unittest.TestCase): 9 | 10 | def test_case1(self): 11 | sentence = 'ESTIMATE Agent[10] BODYGUARD' 12 | self.assertEqual( 13 | sentence, 14 | pr.parse(sentence).get_text() 15 | ) 16 | 17 | def test_case2(self): 18 | sentence = 'Agent[01] COMINGOUT Agent[03] POSSESSED' 19 | self.assertEqual( 20 | sentence, 21 | pr.parse(sentence).get_text() 22 | ) 23 | 24 | def test_case3(self): 25 | sentence = 'Agent[12] REQUEST Agent[07] (DIVINATION Agent[09])' 26 | self.assertEqual( 27 | sentence, 28 | pr.parse(sentence).get_text() 29 | ) 30 | 31 | def test_case4(self): 32 | sentence = 'INQUIRE Agent[29] (GUARD ANY)' 33 | self.assertEqual( 34 | sentence, 35 | pr.parse(sentence).get_text() 36 | ) 37 | 38 | def test_case5(self): 39 | sentence = 'AND (VOTE Agent[01]) (REQUEST ANY (VOTE Agent[01]))' 40 | self.assertEqual( 41 | sentence, 42 | pr.parse(sentence).get_text() 43 | ) 44 | 45 | def test_case6(self): 46 | sentence = 'XOR (ATTACK Agent[01]) (ATTACK Agent[07])' 47 | self.assertEqual( 48 | sentence, 49 | pr.parse(sentence).get_text() 50 | ) 51 | 52 | def test_case7(self): 53 | sentence = 'AND (COMINGOUT Agent[02] SEER) (DIVINED Agent[11] WEREWOLF)' 54 | self.assertEqual( 55 | sentence, 56 | pr.parse(sentence).get_text() 57 | ) 58 | 59 | def test_case8(self): 60 | sentence = 'BECAUSE (Agent[09] IDENTIFIED Agent[01] HUMAN) (ATTACK Agent[09])' 61 | self.assertEqual( 62 | sentence, 63 | pr.parse(sentence).get_text() 64 | ) 65 | 66 | def test_case9(self): 67 | sentence = 'OR (Agent[02] GUARDED ANY) (Agent[04] GUARDED ANY) (Agent[14] GUARDED ANY)' 68 | self.assertEqual( 69 | sentence, 70 | pr.parse(sentence).get_text() 71 | ) 72 | 73 | def test_case10(self): 74 | sentence = 'DAY 1 (VOTED Agent[12])' 75 | self.assertEqual( 76 | sentence, 77 | pr.parse(sentence).get_text() 78 | ) 79 | 80 | def test_case11(self): 81 | sentence = 'BECAUSE (NOT (ANY ATTACKED Agent[02])) (NOT (ESTIMATE Agent[02] BODYGUARD))' 82 | self.assertEqual( 83 | sentence, 84 | pr.parse(sentence).get_text() 85 | ) 86 | 87 | def test_case12(self): 88 | sentence = 'AND (ESTIMATE Agent[02] VILLAGER) (AGREE day3 ID:28)' 89 | self.assertEqual( 90 | sentence, 91 | pr.parse(sentence).get_text() 92 | ) 93 | 94 | def test_case13(self): 95 | sentence = 'ANY XOR (DISAGREE day2 ID:34) (ESTIMATE Agent[03] SEER)' 96 | self.assertEqual( 97 | sentence, 98 | pr.parse(sentence).get_text() 99 | ) 100 | 101 | def test_case14(self): 102 | sentence = 'Over' 103 | self.assertEqual( 104 | sentence, 105 | pr.parse(sentence).get_text() 106 | ) 107 | 108 | def test_case15(self): 109 | sentence = 'Skip' 110 | self.assertEqual( 111 | sentence, 112 | pr.parse(sentence).get_text() 113 | ) 114 | 115 | 116 | if __name__ == '__main__': 117 | unittest.main() 118 | -------------------------------------------------------------------------------- /aiwolfpy/read_log.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import pandas as pd 3 | 4 | def read_log(log_path): 5 | 6 | with open(log_path, newline='') as csvfile: 7 | log_reader = csv.reader(csvfile, delimiter=',') 8 | agent_ = [] 9 | day_ = [] 10 | type_ = [] 11 | idx_ = [] 12 | turn_ = [] 13 | text_ = [] 14 | 15 | # for medium result 16 | medium = 0 17 | for row in log_reader: 18 | if row[1] == "status" and int(row[0]) == 0: 19 | agent_.append(int(row[2])), 20 | day_.append(int(row[0])), 21 | type_.append('initialize'), 22 | idx_.append(int(row[2])), 23 | turn_.append(0), 24 | text_.append('COMINGOUT Agent[' + "{0:02d}".format(int(row[2])) + '] ' + row[3]) 25 | # medium 26 | if row[3] == "MEDIUM": 27 | medium = row[2] 28 | elif row[1] == "status": 29 | pass 30 | elif row[1] == "talk": 31 | agent_.append(int(row[4])), 32 | day_.append(int(row[0])), 33 | type_.append('talk'), 34 | idx_.append(int(row[2])), 35 | turn_.append(int(row[3])), 36 | text_.append(row[5]) 37 | elif row[1] == "whisper": 38 | agent_.append(int(row[4])), 39 | day_.append(int(row[0])), 40 | type_.append('whisper'), 41 | idx_.append(int(row[2])), 42 | turn_.append(int(row[3])), 43 | text_.append(row[5]) 44 | elif row[1] == "vote": 45 | agent_.append(int(row[3])), 46 | day_.append(int(row[0])), 47 | type_.append('vote'), 48 | idx_.append(int(row[2])), 49 | turn_.append(0), 50 | text_.append('VOTE Agent[' + "{0:02d}".format(int(row[3])) + ']') 51 | elif row[1] == "attackVote": 52 | agent_.append(int(row[3])), 53 | day_.append(int(row[0])), 54 | type_.append('attack_vote'), 55 | idx_.append(int(row[2])), 56 | turn_.append(0), 57 | text_.append('ATTACK Agent[' + "{0:02d}".format(int(row[3])) + ']') 58 | elif row[1] == "divine": 59 | agent_.append(int(row[3])), 60 | day_.append(int(row[0])), 61 | type_.append('divine'), 62 | idx_.append(int(row[2])), 63 | turn_.append(0), 64 | text_.append('DIVINED Agent[' + "{0:02d}".format(int(row[3])) + '] ' + row[4]) 65 | elif row[1] == "execute": 66 | # for all 67 | agent_.append(int(row[2])), 68 | day_.append(int(row[0])), 69 | type_.append('execute'), 70 | idx_.append(0), 71 | turn_.append(0), 72 | text_.append('Over') 73 | # for medium 74 | res = 'HUMAN' 75 | if row[3] == 'WEREWOLF': 76 | res = 'WEREWOLF' 77 | agent_.append(int(row[2])), 78 | day_.append(int(row[0])), 79 | type_.append('identify'), 80 | idx_.append(medium), 81 | turn_.append(0), 82 | text_.append('IDENTIFIED Agent[' + "{0:02d}".format(int(row[2])) + '] ' + res) 83 | elif row[1] == "guard": 84 | agent_.append(int(row[3])), 85 | day_.append(int(row[0])), 86 | type_.append('guard'), 87 | idx_.append(int(row[2])), 88 | turn_.append(0), 89 | text_.append('GUARDED Agent[' + "{0:02d}".format(int(row[3])) + ']') 90 | elif row[1] == "attack": 91 | agent_.append(int(row[2])), 92 | day_.append(int(row[0])), 93 | type_.append('attack'), 94 | idx_.append(0), 95 | turn_.append(0), 96 | text_.append('ATTACK Agent[' + "{0:02d}".format(int(row[2])) + ']') 97 | if row[3] == 'true': 98 | # dead 99 | agent_.append(int(row[2])), 100 | day_.append(int(row[0])), 101 | type_.append('dead'), 102 | idx_.append(0), 103 | turn_.append(0), 104 | text_.append('Over') 105 | elif row[1] == "result": 106 | pass 107 | else: 108 | pass 109 | 110 | 111 | return pd.DataFrame({"day":day_, "type":type_, "idx":idx_, "turn":turn_, "agent":agent_, "text":text_}) 112 | -------------------------------------------------------------------------------- /aiwolfpy/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aiwolf/AIWolfPy/29cef501927135a1af28f3ec51ed00de22c6bf09/aiwolfpy/util/__init__.py -------------------------------------------------------------------------------- /aiwolfpy/util/singleton.py: -------------------------------------------------------------------------------- 1 | class Singleton(type): 2 | 3 | _instances = {} 4 | 5 | def __call__(cls, *args, **kwargs): 6 | if cls not in cls._instances: 7 | cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs) 8 | return cls._instances[cls] 9 | -------------------------------------------------------------------------------- /python_simple_sample.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This sample script connects to the AIWolf server, but 4 | # does not do anything else. It will choose itself as the 5 | # target for any actions requested by the server, (voting, 6 | # attacking ,etc) forcing the server to choose a random target. 7 | import logging 8 | from logging import getLogger, StreamHandler, Formatter, FileHandler 9 | import aiwolfpy 10 | import argparse 11 | 12 | # name 13 | myname = 'sample_python' 14 | 15 | # content factory 16 | cf = aiwolfpy.ContentFactory() 17 | 18 | # logger 19 | logger = getLogger("aiwolfpy") 20 | logger.setLevel(logging.NOTSET) 21 | # handler 22 | stream_handler = StreamHandler() 23 | stream_handler.setLevel(logging.NOTSET) 24 | handler_format = Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 25 | stream_handler.setFormatter(handler_format) 26 | 27 | 28 | logger.addHandler(stream_handler) 29 | 30 | # file_handler = FileHandler('aiwolf_game.log') 31 | # file_handler.setLevel(logging.WARNING) 32 | # file_handler.setFormatter(handler_format) 33 | # logger.addHandler(file_handler) 34 | 35 | 36 | class SampleAgent(object): 37 | 38 | def __init__(self): 39 | # my name 40 | self.base_info = dict() 41 | self.game_setting = dict() 42 | 43 | def getName(self): 44 | return self.my_name 45 | 46 | # new game (no return) 47 | def initialize(self, base_info, diff_data, game_setting): 48 | self.base_info = base_info 49 | self.game_setting = game_setting 50 | 51 | # new information (no return) 52 | def update(self, base_info, diff_data, request): 53 | self.base_info = base_info 54 | 55 | # Start of the day (no return) 56 | def dayStart(self): 57 | return None 58 | 59 | # conversation actions: require a properly formatted 60 | # protocol string as the return. 61 | def talk(self): 62 | return cf.over() 63 | 64 | def whisper(self): 65 | return cf.over() 66 | 67 | # targetted actions: Require the id of the target 68 | # agent as the return 69 | def vote(self): 70 | return self.base_info['agentIdx'] 71 | 72 | def attack(self): 73 | return self.base_info['agentIdx'] 74 | 75 | def divine(self): 76 | return self.base_info['agentIdx'] 77 | 78 | def guard(self): 79 | return self.base_info['agentIdx'] 80 | 81 | # Finish (no return) 82 | def finish(self): 83 | return None 84 | 85 | 86 | agent = SampleAgent() 87 | 88 | # read args 89 | parser = argparse.ArgumentParser(add_help=False) 90 | parser.add_argument('-p', type=int, action='store', dest='port') 91 | parser.add_argument('-h', type=str, action='store', dest='hostname') 92 | parser.add_argument('-r', type=str, action='store', dest='role', default='none') 93 | parser.add_argument('-n', type=str, action='store', dest='name', default=myname) 94 | input_args = parser.parse_args() 95 | 96 | 97 | client_agent = aiwolfpy.AgentProxy( 98 | agent, input_args.name, input_args.hostname, input_args.port, input_args.role, logger, "pandas" 99 | ) 100 | 101 | # run 102 | if __name__ == '__main__': 103 | client_agent.connect_server() 104 | --------------------------------------------------------------------------------