├── .gitignore ├── LICENSE ├── README.md ├── botzone ├── __init__.py ├── agent.py ├── agents │ ├── human.py │ └── tictactoe.py ├── env.py ├── envs │ ├── README.md │ ├── __init__.py │ ├── botzone │ │ ├── amazons.py │ │ ├── ataxx.py │ │ ├── chinesestandardmahjong.py │ │ ├── fightthelandlord.py │ │ ├── go.py │ │ ├── gomoku.py │ │ ├── kingz.py │ │ ├── minesweeper.py │ │ ├── nogo.py │ │ ├── renju.py │ │ ├── reversi.py │ │ ├── snake.py │ │ └── tictactoe.py │ └── registration.py ├── error.py └── online │ ├── __init__.py │ ├── api.py │ ├── bot.py │ ├── bot │ └── .gitignore │ ├── compiler.py │ ├── game.py │ ├── game │ ├── Amazons.cpp11 │ ├── Ataxx.cpp │ ├── ChineseStandardMahjong.elfbin │ ├── FightTheLandlord.py3 │ ├── FightTheLandlord2.py36 │ ├── Go.py3 │ ├── Gomoku.js │ ├── Kingz.cpp11 │ ├── Minesweeper.cpp │ ├── NoGo.cpp17 │ ├── Pacman.cpp │ ├── Pacman2.cpp │ ├── Renju.js │ ├── Reversi.cpp │ ├── Snake.cpp │ ├── Tank.cpp17 │ ├── Tank2.cpp17 │ ├── Tank2S.cpp17 │ ├── Tetris.cpp11 │ ├── Tetris2.cpp11 │ └── TicTacToe.cpp17 │ ├── games.py │ ├── sandbox.py │ └── viewer │ ├── amazons.py │ ├── ataxx.py │ ├── chinesestandardmahjong.py │ ├── fightthelandlord.py │ ├── fightthelandlord2.py │ ├── go.py │ ├── gomoku.py │ ├── kingz.py │ ├── minesweeper.py │ ├── nogo.py │ ├── renju.py │ ├── reversi.py │ ├── snake.py │ ├── tictactoe.py │ └── viewer.py ├── demo.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | **/__pycache__ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Botzone本地环境 2 | 3 | 本项目旨在构建通用的多智能体游戏环境,同时能够对接botzone上已有的游戏和bot。 4 | 5 | ## 背景 6 | 7 | [Botzone](https://botzone.org)是一个在线的程序对战平台,提供了一系列游戏。用户可以提交自己的程序(被称为Bot),开启游戏桌与他人的Bot进行自动对战。一些课程或者比赛小组中可以用比赛的形式进行批量对局。 8 | 9 | 然而,由于该平台的对局均在线上进行,在下列场景中会带来一些不便之处: 10 | 11 | - 用户写了两个Bot版本,想要多次运行对局来比较两个Bot的水平高低,而在网站上只能多次手动开启游戏桌进行对局 12 | - 用户Bot正在开发中,存在一些bug需要反复修改,而在网站上需要反复上传Bot新版本并运行对局 13 | - 用户想要训练基于学习算法的Bot,目前的在线对战方式无法支持接入基于函数调用的智能体,也无法做到高效收集数据,用户通常需要自己实现本地游戏环境进行智能体训练 14 | 15 | 因此本库的目的在于提供一套本地化的、可离线运行对局的Botzone环境。 16 | 17 | 本库与之前的[Botzone本地调试工具](https://github.com/zhouhaoyu/botzone-local-runner)的区别: 18 | - **Botzone本地调试工具**中虽然本地程序是在本地运行的,但每回合都需要向服务器发起请求以调用服务器上的游戏裁判程序,没有真正做到完全在本地运行对局,并且效率较低 19 | - **Botzone本地环境**中将游戏裁判程序和用户Bot都下载到本地,因此能够做到在用户主机上运行完整的对局,并且效率很高 20 | 21 | ## 介绍 22 | 23 | 本项目的最初目的是为了支持在本地运行botzone形式的对局,但在具体实现中为了可扩展性考虑,我们将其实现成了一个通用的多智能体游戏环境库。具体来说: 24 | 25 | - 定义了多智能体博弈场景下**游戏环境**(Env)以及**智能体**(Agent)的通用接口规范,使得可以用非常简洁的代码运行对局 26 | - 用户可以自己开发符合接口规范的新环境,也可以实现自定义的智能体(如用于算法研究) 27 | - 通过在轻量级沙盒(Docker)中运行游戏裁判程序和Bot程序,本库直接对线上已有的游戏和Bot进行了支持,并将其包装成符合上述通用接口规范的组件 28 | - 为了游戏环境的运行效率,本库中使用Python重新实现了线上各游戏的裁判程序,用户可以用它们来代替包装的线上游戏环境,可以大幅提升本地对局效率 29 | 30 | ## 开始使用 31 | 32 | ### 安装 33 | 34 | 本库需要Python 3.5+的Python环境。目前由于本库仍然在完善中,暂时不支持pip安装,未来会以Python库的形式发布。 35 | 36 | 在下载本库后,你需要安装相应的Python依赖: 37 | ``` 38 | git clone https://github.com/ailab-pku/botzone-local.git 39 | cd botzone-local 40 | pip install -r requirements.txt 41 | ``` 42 | 43 | ### 使用线上游戏和Bot 44 | 45 | 为了支持线上Bot,你需要先安装docker(用于创建本地沙箱运行代码)以及NodeJS(用于支持简单交互)。 46 | 47 | 用户很容易可以使用线上已有的游戏和Bot运行对局。下面给出了一个简洁的例子: 48 | ``` 49 | try: 50 | # 创建名叫NoGo的游戏环境 51 | from botzone.online.game import Game, GameConfig 52 | env = Game(GameConfig.fromName('NoGo')) 53 | # 创建两个Bot实例,ID均为5fede20fd9383f7579afff06(这是样例Bot) 54 | from botzone.online.bot import Bot, BotConfig 55 | bots = [Bot(BotConfig.fromID('5fede20fd9383f7579afff06')) for i in range(env.player_num)] 56 | # 指定对局玩家 57 | env.init(bots) 58 | # 运行对局并渲染画面 59 | score = env.reset() 60 | env.render() 61 | while score is None: 62 | score = env.step() # 对局结束时,step会以tuple的形式返回各玩家得分 63 | env.render() 64 | print(score) 65 | finally: 66 | # 对于包装的游戏和Bot,必须保证程序结束前调用close以释放沙盒资源,将该代码放在finally块中可以保证在程序出错退出前仍然能够执行。如果不释放沙盒资源,一段时间后你的电脑中会运行着许多docker容器实例,需要手动杀死。 67 | # 对于自定义的Env和Agent也建议在结束前调用close,因为它们可能需要释放资源 68 | env.close() 69 | for bot in bots: bot.close() 70 | ``` 71 | 72 | 一些注意点: 73 | 74 | - 第一次使用沙盒运行游戏或Bot时,本库会提示下载docker镜像文件。我们对于每种语言的运行环境预编译了一个镜像,以及一个可以支持所有语言的镜像,该镜像与服务器评测环境几乎一致。你可以选择只下载常用语言的镜像(比较小),也可以选择下载通用镜像(10G左右)。 75 | - 在试图创建Bot时,本库会尝试从botzone网站下载Bot代码并缓存到默认路径(botzone/online/bot/)下,你可以通过向BotConfig传递参数```path```以改变缓存路径。 76 | - 有的Bot运行时需要读写用户空间中的文件。你可以通过向BotConfig中传递参数```userfile```以开启这一行为,本库会尝试从botzone网站下载用户空间的数据并缓存到默认路径(botzone/online/bot/user_id/)下,你可以通过向BotConfig传递参数```userfile_path```以改变缓存路径。 77 | - 下载非公开bot以及用户空间的文件时需要用户权限,在这种情况下本库会自动提示输入你的邮箱和密码来登录账号。 78 | 79 | ### 环境 80 | 81 | 本库使用唯一ID来标识不同的游戏环境。可以直接用以下代码来获取环境实例: 82 | ``` 83 | import botzone 84 | env = botzone.make('Ataxx-v0') # Ataxx的Python环境 85 | ``` 86 | 与以下代码的效果是相同的: 87 | ``` 88 | from botzone.envs.botzone.ataxx import AtaxxEnv 89 | env = AtaxxEnv() 90 | ``` 91 | 92 | 所有沙盒包装的游戏环境均采用GameName-wrap的ID形式,当前本库支持的完整环境列表可以用```botzone.all()```获取,[这里](https://github.com/ailab-pku/botzone-local/tree/master/botzone/envs)提供了对各环境的简单描述。 93 | 94 | ### 自定义环境和智能体 95 | 96 | 本库中实现了统一的[环境接口](https://github.com/ailab-pku/botzone-local/blob/master/botzone/env.py)和[Agent接口](https://github.com/ailab-pku/botzone-local/blob/master/botzone/agent.py)。 97 | 用户只需要继承```botzone.Env```和```botzone.Agent```即可实现自定义的环境和智能体,并且在统一的接口下自由交互。 98 | 99 | 在另一种应用场景中,用户在本地创建了打算上传到botzone的Bot程序或者游戏裁判程序,但想要先在本地进行调试。这种场景中用户可以自己构造BotConfig或GameConfig对象,并用它们创建只存在于本地的Bot或者Game。详细信息可以查看相关类的文档注释:[Bot](https://github.com/ailab-pku/botzone-local/blob/master/botzone/online/bot.py), [Game](https://github.com/ailab-pku/botzone-local/blob/master/botzone/online/game.py) 100 | -------------------------------------------------------------------------------- /botzone/__init__.py: -------------------------------------------------------------------------------- 1 | from botzone.agent import Agent 2 | from botzone.env import Env 3 | from botzone.envs.registration import all, make 4 | from botzone.error import * -------------------------------------------------------------------------------- /botzone/agent.py: -------------------------------------------------------------------------------- 1 | class Agent(object): 2 | 3 | def reset(self): 4 | ''' 5 | Called by Env when a new episode starts. The agent should initialize 6 | its internal state in this method. 7 | Note: this method can be called when last episode has NOT finished from 8 | this agent's perspective, and it should do necessary cleanup before 9 | preparing for new episode. 10 | 11 | Raises: 12 | AlreadyClosed - If close() is ever called. 13 | ''' 14 | raise NotImplementedError 15 | 16 | def step(self, request): 17 | ''' 18 | Called by Env when requested to make a decision. 19 | Note: if last episode has finished and step() is mistakenly called 20 | before reset(), a robust implementation is responsible to check for this 21 | situation and raise an `ResetNeeded` Error, e.g. by checking some 22 | internal state which is cleared whenever an episode is finished and is 23 | reset in reset(). 24 | 25 | Parameters: 26 | request - Observation provided by Env 27 | 28 | Returns: 29 | Response by this agent. 30 | 31 | Raises: 32 | AlreadyClosed - If close() is ever called. 33 | ResetNeeded - If called before reset() when last episode has 34 | finished. 35 | ''' 36 | raise NotImplementedError 37 | 38 | def close(self): 39 | ''' 40 | Perform necessary cleanup. This method must be called before program 41 | exits as soon as Agent instance is successfully created. After close() 42 | is called, calling reset() or step() will always raise an AlreadyClosed 43 | Error. Multiple calls of close() are possible and should not raise 44 | Errors. 45 | ''' 46 | pass 47 | -------------------------------------------------------------------------------- /botzone/agents/human.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from botzone import * 4 | 5 | class Human(Agent): 6 | 7 | def reset(self): 8 | pass 9 | 10 | def step(self, request): 11 | print('Request:', request) 12 | return json.loads(input('Input response:')) -------------------------------------------------------------------------------- /botzone/agents/tictactoe.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from botzone import * 4 | 5 | class TicTacToeAgent(Agent): 6 | ''' 7 | Random strategy. 8 | ''' 9 | 10 | def __init__(self): 11 | self.board = None 12 | self.closed = False 13 | 14 | def reset(self): 15 | if self.closed: 16 | raise AlreadyClosed() 17 | self.board = [[0 for j in range(3)] for i in range(3)] 18 | self.role = 2 19 | 20 | def step(self, request): 21 | if self.closed: 22 | raise AlreadyClosed() 23 | if self.board is None: 24 | raise ResetNeeded() 25 | x = request['x'] 26 | y = request['y'] 27 | if x < 0 and y < 0: 28 | self.role = 1 29 | else: 30 | self.board[x][y] = 1 31 | l = [i for i in range(9) if not self.board[i // 3][i % 3]] 32 | pos = random.choice(l) 33 | x = pos // 3 34 | y = pos % 3 35 | self.board[x][y] = 1 36 | return {'x' : x, 'y' : y} 37 | 38 | def close(self): 39 | self.closed = True -------------------------------------------------------------------------------- /botzone/env.py: -------------------------------------------------------------------------------- 1 | from botzone.agent import Agent 2 | from botzone.error import * 3 | 4 | class Env(object): 5 | 6 | @property 7 | def player_num(self): 8 | ''' 9 | Number of players of this game. 10 | 11 | Returns: 12 | A constant indicating number of players needed. 13 | ''' 14 | raise NotImplementedError 15 | 16 | def init(self, agents): 17 | ''' 18 | Initialize agents used by this environment. 19 | 20 | Parameters: 21 | agents - A list of botzone.Agent instances. 22 | ''' 23 | assert isinstance(agents, list), 'Parameter `agents` must be a list!' 24 | assert len(agents) == self.player_num, '%d agents expected but %d given!' % (self.player_num, len(agents)) 25 | for agent in agents: 26 | assert issubclass(type(agent), Agent), 'Parameter `agents` must be a list of botzone.Agent instances!' 27 | self.agents = agents 28 | 29 | def reset(self, initdata = None): 30 | ''' 31 | Prepare for a new episode. The env should initialize its internal state 32 | in this method. In trivial cases where the episode ends at once, return 33 | scores of each agent. 34 | Note: this method can be called when last episode has NOT finished, and 35 | it should do necessary cleanup before preparing for new episode. 36 | 37 | Parameters: 38 | initdata - Data needed to initialize an episode, e.g. some initial 39 | configuration of the environment. 40 | 41 | Returns: 42 | A tuple contains the score of each agent if the episode ends 43 | immediately, or None otherwise. 44 | 45 | Raises: 46 | AlreadyClosed - If close() is ever called. 47 | AgentsNeeded - If init() is never called. 48 | ''' 49 | raise NotImplementedError 50 | 51 | def step(self): 52 | ''' 53 | One step of the environment. In this method the env will call some of 54 | the agents to request a response, and return scores of each agent if 55 | this episode ends. 56 | Note: if last episode has finished and step() is mistakenly called 57 | before reset(), a robust implementation is responsible to check for this 58 | situation and raise an `ResetNeeded` Error, e.g. by checking some 59 | internal state which is cleared whenever an episode is finished and is 60 | reset in reset(). 61 | 62 | Returns: 63 | A tuple contains the score of each agent if the episode ends, or 64 | None otherwise. 65 | 66 | Raises: 67 | AlreadyClosed - If close() is ever called. 68 | ResetNeeded - If called before reset() when last episode has 69 | finished. 70 | ''' 71 | raise NotImplementedError 72 | 73 | def render(self, mode = 'human'): 74 | ''' 75 | Render current state. This method can be called any time after reset() 76 | and before close(), rendering states updated by last call of step(). 77 | Note: Even if an episode has finished, render() should still be able to 78 | render the states by not clearing states needed when episode ends. 79 | 80 | Parameters: 81 | mode - Render mode used. Modes supported should be specified in 82 | Env.metadata['render.modes']. 83 | 84 | Raises: 85 | AlreadyClosed - If close() is ever called. 86 | UnsupportedMode - If mode is not supported. 87 | 88 | Example: 89 | 90 | class MyEnv(Env): 91 | metadata = {'render.modes': ['human', 'ansi']} 92 | 93 | def render(self, mode = 'human'): 94 | if mode == 'human': 95 | ... # render by a popup window 96 | elif mode == 'ansi': 97 | ... # render by printing a terminal-style text 98 | else: 99 | super(MyEnv, self).render(mode) 100 | ''' 101 | raise UnsupportedMode() 102 | 103 | def close(self): 104 | ''' 105 | Perform necessary cleanup. This method must be called before program 106 | exits as soon as Env instance is successfully created. After close() is 107 | called, calling reset(), step() or render() will always raise an 108 | AlreadyClosed Error. Multiple calls of close() are possible and should 109 | not raise Errors. 110 | Note: this method will NOT call close() of each agent. The user must 111 | close agents on their own. 112 | ''' 113 | pass 114 | 115 | def seed(self, seed = None): 116 | ''' 117 | Sets the seed for this env's random number generator. Override this 118 | method if environment contains no randomness. 119 | 120 | Parameters: 121 | seed - Seed specified. 122 | ''' 123 | if seed is not None: assert isinstance(seed, int) and seed >= 0, 'Seed must be a non-negative integer or omitted' 124 | self._seed = seed 125 | 126 | def __enter__(self): 127 | '''Support with-statement for the environment.''' 128 | return self 129 | 130 | def __exit__(self, *args): 131 | '''Support with-statement for the environment.''' 132 | self.close() 133 | return False 134 | -------------------------------------------------------------------------------- /botzone/envs/README.md: -------------------------------------------------------------------------------- 1 | # 目前支持的环境 2 | 3 | | ID | 环境描述 | 4 | | -- | ------ | 5 | | Amazons-v8 | 亚马逊棋,棋盘大小8*8 | 6 | | Amazons-v10 | 亚马逊棋,棋盘大小10*10 | 7 | | Ataxx-v0 | 同化棋 | 8 | | ChineseStandardMahjong-v0 | 国标麻将标准版 | 9 | | ChineseStandardMahjong-v0-dup | 国标麻将,采用复式赛制,取消花牌,各玩家牌墙独立 | 10 | | FightTheLandlord-v0 | 斗地主,固定位置,计入出牌小分 | 11 | | FightTheLandlord-v1 | 斗地主,固定位置,不计入出牌小分 | 12 | | Go-v8 | 围棋,棋盘大小8*8,贴目1.5 | 13 | | Go-v9 | 围棋,棋盘大小9*9,贴目7.5 | 14 | | Go-v13 | 围棋,棋盘大小13*13,贴目7.5 | 15 | | Go-v19 | 围棋,棋盘大小19*19,贴目7.5 | 16 | | Gomoku-v15 | 五子棋,棋盘大小15*15 | 17 | | Kingz-v0 | 双人战棋,改编自[generals.io](https://generals.io),无视野 | 18 | | MineSweeper-v0 | 扫雷 | 19 | | NoGo-v9 | 不围棋,棋盘大小9*9 | 20 | | Renju-v15 | 五子棋,第三手可以换手,棋盘大小15*15 | 21 | | Reversi-v0 | 翻转棋 | 22 | | Snake-v0 | 贪吃蛇对战版 | 23 | | TicTacToe-v0 | 井字棋 | 24 | | Amazons-wrap | 线上游戏Amazons的包装环境,行为同Amazons-v8 | 25 | | Ataxx-wrap | 线上游戏Ataxx的包装环境,行为同Ataxx-v0 | 26 | | ChineseStandardMahjong-wrap | 线上游戏ChineseStandardMahjong的包装环境,行为同ChineseStandardMahjong-v0-dup | 27 | | FightTheLandlord-wrap | 线上游戏FightTheLandlord的包装环境,行为同FightTheLandlord-v0 | 28 | | Go-wrap | 线上游戏Go的包装环境,行为同Go-v8 | 29 | | Gomoku-wrap | 线上游戏Gomoku的包装环境,行为同Gomoku-v15 | 30 | | Kingz-wrap | 线上游戏Kingz的包装环境,行为同Kingz-v0 | 31 | | Minesweeper-wrap | 线上游戏Minesweeper的包装环境,行为同MineSweeper-v0 | 32 | | NoGo-wrap | 线上游戏NoGo的包装环境,行为同NoGo-v9 | 33 | | Pacman-wrap | 线上游戏Pacman的包装环境 | 34 | | Pacman2-wrap | 线上游戏Pacman2的包装环境 | 35 | | Renju-wrap | 线上游戏Renju的包装环境,行为同Renju-v15 | 36 | | Reversi-wrap | 线上游戏Reversi的包装环境,行为同Reversi-v0 | 37 | | Snake-wrap | 线上游戏Snake的包装环境,行为同Snake-v0 | 38 | | Tank-wrap | 线上游戏Tank的包装环境 | 39 | | Tank2-wrap | 线上游戏Tank2的包装环境 | 40 | | Tank2S-wrap | 线上游戏Tank2S的包装环境 | 41 | | Tetris-wrap | 线上游戏Tetris的包装环境 | 42 | | Tetris2-wrap | 线上游戏Tetris2的包装环境 | 43 | | TicTacToe-wrap | 线上游戏TicTacToe的包装环境,行为同TicTacToe-v0 | -------------------------------------------------------------------------------- /botzone/envs/__init__.py: -------------------------------------------------------------------------------- 1 | from botzone.envs.registration import register 2 | from botzone.online import games 3 | 4 | # Wrapper Env from Botzone Online 5 | for game in games.all(): 6 | register(id = '%s-wrap' % game) 7 | 8 | # Python Env for botzone 9 | register( 10 | id = 'Amazons-v8', 11 | entry_point = 'botzone.envs.botzone.amazons:AmazonsEnv', 12 | size = 8 13 | ) 14 | register( 15 | id = 'Amazons-v10', 16 | entry_point = 'botzone.envs.botzone.amazons:AmazonsEnv', 17 | size = 10 18 | ) 19 | register( 20 | id = 'Ataxx-v0', 21 | entry_point = 'botzone.envs.botzone.ataxx:AtaxxEnv', 22 | limit = 400 23 | ) 24 | register( 25 | id = 'FightTheLandlord-v0', 26 | entry_point = 'botzone.envs.botzone.fightthelandlord:FightTheLandlordEnv', 27 | small = True 28 | ) 29 | register( 30 | id = 'FightTheLandlord-v1', 31 | entry_point = 'botzone.envs.botzone.fightthelandlord:FightTheLandlordEnv', 32 | small = False 33 | ) 34 | register( 35 | id = 'Go-v8', 36 | entry_point = 'botzone.envs.botzone.go:GoEnv', 37 | size = 8, 38 | komi = 0.75 39 | ) 40 | register( 41 | id = 'Go-v9', 42 | entry_point = 'botzone.envs.botzone.go:GoEnv', 43 | size = 9, 44 | komi = 3.25 45 | ) 46 | register( 47 | id = 'Go-v13', 48 | entry_point = 'botzone.envs.botzone.go:GoEnv', 49 | size = 13, 50 | komi = 3.25 51 | ) 52 | register( 53 | id = 'Go-v19', 54 | entry_point = 'botzone.envs.botzone.go:GoEnv', 55 | size = 19, 56 | komi = 3.25 57 | ) 58 | register( 59 | id = 'Gomoku-v15', 60 | entry_point = 'botzone.envs.botzone.gomoku:GomokuEnv', 61 | size = 15 62 | ) 63 | register( 64 | id = 'MineSweeper-v0', 65 | entry_point = 'botzone.envs.botzone.minesweeper:MineSweeperEnv' 66 | ) 67 | register( 68 | id = 'NoGo-v9', 69 | entry_point = 'botzone.envs.botzone.nogo:NoGoEnv', 70 | size = 9 71 | ) 72 | register( 73 | id = 'Renju-v15', 74 | entry_point = 'botzone.envs.botzone.renju:RenjuEnv', 75 | size = 15 76 | ) 77 | register( 78 | id = 'Reversi-v0', 79 | entry_point = 'botzone.envs.botzone.reversi:ReversiEnv' 80 | ) 81 | register( 82 | id = 'Snake-v0', 83 | entry_point = 'botzone.envs.botzone.snake:SnakeEnv' 84 | ) 85 | register( 86 | id = 'TicTacToe-v0', 87 | entry_point = 'botzone.envs.botzone.tictactoe:TicTacToeEnv' 88 | ) 89 | 90 | register( 91 | id = 'ChineseStandardMahjong-v0-dup', 92 | entry_point = 'botzone.envs.botzone.chinesestandardmahjong:ChineseStandardMahjongEnv', 93 | duplicate = True 94 | ) 95 | 96 | register( 97 | id = 'ChineseStandardMahjong-v0', 98 | entry_point = 'botzone.envs.botzone.chinesestandardmahjong:ChineseStandardMahjongEnv', 99 | duplicate = False 100 | ) 101 | 102 | register( 103 | id = 'Kingz-v0', 104 | entry_point = 'botzone.envs.botzone.kingz:KingzEnv', 105 | limit = 300 106 | ) -------------------------------------------------------------------------------- /botzone/envs/botzone/amazons.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | 3 | from botzone import * 4 | from botzone.online.viewer.amazons import AmazonsTextViewer 5 | 6 | class AmazonsEnv(Env): 7 | ''' 8 | Description: 9 | Traditional Amazons game with `size`*`size` board. (default = 8) 10 | 11 | Decision order: In each turn only one player decide. 12 | Request: {x0, y0, x1, y1, x2, y2} for each player, -1 for all for the 13 | first turn. 14 | Response: {x0, y0, x1, y1, x2, y2} for my decision. 15 | 16 | Episode Termination: 17 | The first player who is unable to move loses. 18 | Score: 19 | 2 for winner and 0 for loser. 20 | ''' 21 | 22 | metadata = {'render.modes': ['ansi']} 23 | 24 | def __init__(self, size = 8): 25 | # Initialize configurations, possible viewers and state 26 | self.agents = None 27 | self.round = None 28 | self.closed = False 29 | self.display = [] 30 | self.viewer = None 31 | self.size = size 32 | 33 | @property 34 | def player_num(self): 35 | return 2 36 | 37 | def reset(self, initdata = None): 38 | if self.closed: 39 | raise AlreadyClosed() 40 | if self.agents is None: 41 | raise AgentsNeeded() 42 | # Initialize each agent 43 | for agent in self.agents: 44 | agent.reset() 45 | # Initialize state for new episode 46 | self.round = 0 47 | self.board = b = [[0 for j in range(self.size)] for i in range(self.size)] 48 | p = (self.size + 2) // 3 49 | b[0][p - 1] = b[p - 1][0] = b[-p][0] = b[-1][p - 1] = 1 # 1 for black 50 | b[0][-p] = b[p - 1][-1] = b[-p][-1] = b[-1][-p] = 2 # 2 for white 51 | self.last_action = { 52 | 'x0' : -1, 'y0' : -1 53 | , 'x1' : -1, 'y1' : -1 54 | , 'x2' : -1, 'y2' : -1 55 | } 56 | if self.viewer: self.viewer.reset() 57 | self.valid_move = v = self._valid_move() 58 | self.display = [dict(blackCount = len(v[0]), whiteCount = len(v[1]))] 59 | 60 | def step(self): 61 | if self.closed: 62 | raise AlreadyClosed() 63 | if self.round is None: 64 | raise ResetNeeded() 65 | 66 | cur_player = self.round % 2 67 | action = deepcopy(self.agents[cur_player].step(self.last_action)) 68 | # check validity of action 69 | try: 70 | x0 = action['x0'] 71 | y0 = action['y0'] 72 | x1 = action['x1'] 73 | y1 = action['y1'] 74 | x2 = action['x2'] 75 | y2 = action['y2'] 76 | assert (x0, y0, x1, y1, x2, y2) in self.valid_move[cur_player] 77 | except: 78 | # Invalid action 79 | self.round = None 80 | self.display.append(dict( 81 | winner = 1 - cur_player, 82 | blackCount = len(self.valid_move[0]), 83 | whiteCount = len(self.valid_move[1]) 84 | )) 85 | return (2, 0) if cur_player else (0, 2) 86 | color = cur_player + 1 87 | self.board[x0][y0] = 0 88 | self.board[x1][y1] = color 89 | self.board[x2][y2] = -1 90 | self.round += 1 91 | self.last_action = action 92 | # check episode ends 93 | self.valid_move = v = self._valid_move() 94 | if not v[1 - cur_player]: 95 | self.round = None 96 | self.display.append(dict( 97 | winner = cur_player, 98 | blackCount = len(v[0]), 99 | whiteCount = len(v[1]), 100 | x0 = x0, y0 = y0, 101 | x1 = x1, y1 = y1, 102 | x2 = x2, y2 = y2 103 | )) 104 | return (0, 2) if cur_player else (2, 0) 105 | self.display.append(dict( 106 | blackCount = len(v[0]), 107 | whiteCount = len(v[1]), 108 | x0 = x0, y0 = y0, 109 | x1 = x1, y1 = y1, 110 | x2 = x2, y2 = y2 111 | )) 112 | 113 | def render(self, mode = 'ansi'): 114 | if mode == 'ansi': 115 | if self.viewer is None: 116 | self.viewer = AmazonsTextViewer(size = self.size) 117 | self.viewer.reset() 118 | self.viewer.render(self.display) 119 | self.display = [] 120 | else: 121 | super(AmazonsEnv, self).render(mode) 122 | 123 | def close(self): 124 | self.closed = True 125 | 126 | def seed(self, seed = None): 127 | # No randomness 128 | pass 129 | 130 | def _valid_move(self): 131 | valid = [[], []] 132 | b = self.board 133 | for x0 in range(self.size): 134 | for y0 in range(self.size): 135 | if b[x0][y0] > 0: 136 | for dx1 in range(-1, 2): 137 | for dy1 in range(-1, 2): 138 | if dx1 or dy1: 139 | x1 = x0 + dx1 140 | y1 = y0 + dy1 141 | while self._in_board(x1, y1) and b[x1][y1] == 0: 142 | for dx2 in range(-1, 2): 143 | for dy2 in range(-1, 2): 144 | if dx2 or dy2: 145 | x2 = x1 + dx2 146 | y2 = y1 + dy2 147 | while self._in_board(x2, y2) and (b[x2][y2] == 0 or x2 == x0 and y2 == y0): 148 | valid[b[x0][y0] - 1].append((x0, y0, x1, y1, x2, y2)) 149 | x2 = x2 + dx2 150 | y2 = y2 + dy2 151 | x1 = x1 + dx1 152 | y1 = y1 + dy1 153 | return valid 154 | 155 | def _in_board(self, x, y): 156 | return 0 <= x < self.size and 0 <= y < self.size -------------------------------------------------------------------------------- /botzone/envs/botzone/ataxx.py: -------------------------------------------------------------------------------- 1 | from botzone import * 2 | from botzone.online.viewer.ataxx import AtaxxTextViewer 3 | 4 | class AtaxxEnv(Env): 5 | ''' 6 | Description: 7 | Traditional ataxx with empty board. 8 | 9 | Decision order: In each turn only one player decide. 10 | Request: {"x": Number, "y": Number} for each player, {"x": -1, "y": -1} 11 | for the first turn. 12 | Response: {"x": Number, "y": Number} for my decision. 13 | 14 | Episode Termination: 15 | (1) When one player has no position to move. All empty positions are 16 | considered to be the opponent's. The player with more stones on the 17 | board wins. 18 | (2) When the game exceeds `limit=400` rounds. The player with more stones on 19 | the board wins. 20 | Score: 21 | 2 for winner and 0 for loser. 22 | ''' 23 | 24 | metadata = {'render.modes': ['ansi']} 25 | 26 | def __init__(self, limit = 400): 27 | # Initialize configurations, possible viewers and state 28 | self.agents = None 29 | self.round = None 30 | self.closed = False 31 | self.display = [] 32 | self.viewer = None 33 | self.limit = limit 34 | 35 | @property 36 | def player_num(self): 37 | return 2 38 | 39 | def reset(self, initdata = None): 40 | if self.closed: 41 | raise AlreadyClosed() 42 | if self.agents is None: 43 | raise AgentsNeeded() 44 | # Initialize each agent 45 | for agent in self.agents: 46 | agent.reset() 47 | # Initialize state for new episode 48 | self.round = 0 49 | self.board = b = [[0 for i in range(7)] for j in range(7)] 50 | b[0][0] = b[-1][-1] = 1 # 1 for black 51 | b[0][-1] = b[-1][0] = 2 # 2 for white 52 | self.last_action = {'x0' : -1, 'y0' : -1, 'x1' : -1, 'y1' : -1} 53 | self.display = [None] 54 | if self.viewer: self.viewer.reset() 55 | 56 | def step(self): 57 | if self.closed: 58 | raise AlreadyClosed() 59 | if self.round is None: 60 | raise ResetNeeded() 61 | 62 | cur_player = self.round % 2 63 | color = cur_player + 1 64 | action = self.agents[cur_player].step(self.last_action) 65 | # check validity of action 66 | try: 67 | x0 = int(action['x0']) 68 | y0 = int(action['y0']) 69 | x1 = int(action['x1']) 70 | y1 = int(action['y1']) 71 | assert self._in_board(x0, y0) and self._in_board(x1, y1) 72 | assert self.board[x0][y0] == color 73 | assert x0 != x1 or y0 != y1 74 | assert abs(x0 - x1) <= 2 and abs(y0 - y1) <= 2 75 | except: 76 | # Invalid action 77 | self.round = None 78 | self.display.append({'winner' : 1 - cur_player}) 79 | return (2, 0) if cur_player else (0, 2) 80 | if abs(x0 - x1) == 2 or abs(y0 - y1) == 2: 81 | self.board[x0][y0] = 0 82 | self.board[x1][y1] = color 83 | for i in range(-1, 2): 84 | for j in range(-1, 2): 85 | nx = x1 + i 86 | ny = y1 + j 87 | if self._in_board(nx, ny) and self.board[nx][ny] == 3 - color: 88 | self.board[nx][ny] = color 89 | self.round += 1 90 | self.last_action = dict(x0 = x0, y0 = y0, x1 = x1, y1 = y1) 91 | # check episode ends 92 | if not self._has_valid_move(3 - color): 93 | winner = cur_player 94 | if sum(x.count(3 - color) for x in self.board) > 24: 95 | winner = 1 - winner 96 | self.display.append(dict(x0 = x0, y0 = y0, x1 = x1, y1 = y1, winner = winner)) 97 | self.round = None 98 | return ((2, 0), (0, 2))[winner] 99 | if self.round > self.limit: 100 | winner = cur_player 101 | if sum(x.count(3 - color) for x in self.board) > sum(x.count(color) for x in self.board): 102 | winner = 1 - winner 103 | self.display.append(dict(x0 = x0, y0 = y0, x1 = x1, y1 = y1, winner = winner)) 104 | self.round = None 105 | return ((2, 0), (0, 2))[winner] 106 | self.display.append(self.last_action) 107 | 108 | def render(self, mode = 'ansi'): 109 | if mode == 'ansi': 110 | if self.viewer is None: 111 | self.viewer = AtaxxTextViewer() 112 | self.viewer.reset() 113 | self.viewer.render(self.display) 114 | self.display = [] 115 | else: 116 | super(AtaxxEnv, self).render(mode) 117 | 118 | def close(self): 119 | self.closed = True 120 | 121 | def seed(self, seed = None): 122 | # No randomness 123 | pass 124 | 125 | def _in_board(self, x, y): 126 | return 0 <= x < 7 and 0 <= y < 7 127 | 128 | def _has_valid_move(self, color): 129 | for i in range(7): 130 | for j in range(7): 131 | if self.board[i][j] == color: 132 | for dx in range(-2, 3): 133 | for dy in range(-2, 3): 134 | if self._in_board(i + dx, j + dy) and self.board[i + dx][j + dy] == 0: 135 | return True 136 | return False -------------------------------------------------------------------------------- /botzone/envs/botzone/go.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | 3 | from botzone import * 4 | from botzone.online.viewer.go import GoTextViewer 5 | 6 | class GoEnv(Env): 7 | ''' 8 | Description: 9 | Traditional Go with: 10 | `size`*`size` board, (default = 8) 11 | komi = `komi`, (default = 0.75) 12 | area scoring rule, 13 | positional superko rule. 14 | 15 | Decision order: In each turn only one player decide. 16 | Request: {"x": Number, "y": Number} for each player, {"x": -2, "y": -2} 17 | for the first turn. 18 | Response: {"x": Number, "y": Number} for my decision, {"x": -1, "y": -1} 19 | for pass. 20 | 21 | Episode Termination: 22 | When both players pass. 23 | Score: 24 | 2 for winner and 0 for loser. 25 | ''' 26 | 27 | metadata = {'render.modes': ['ansi']} 28 | 29 | def __init__(self, size = 8, komi = 0.75): 30 | # Initialize configurations, possible viewers and state 31 | self.agents = None 32 | self.round = None 33 | self.closed = False 34 | self.display = [] 35 | self.viewer = None 36 | self.size = size 37 | self.komi = komi 38 | 39 | @property 40 | def player_num(self): 41 | return 2 42 | 43 | def reset(self, initdata = None): 44 | if self.closed: 45 | raise AlreadyClosed() 46 | if self.agents is None: 47 | raise AgentsNeeded() 48 | # Initialize each agent 49 | for agent in self.agents: 50 | agent.reset() 51 | # Initialize state for new episode 52 | self.round = 0 53 | self.board = b = [[0 for i in range(self.size + 2)] for j in range(self.size + 2)] 54 | for i in range(self.size + 2): 55 | b[i][0] = b[i][-1] = b[0][i] = b[-1][i] = -1 56 | self.history = set() 57 | self.history.add(self._hash()) 58 | self.last_action = {'x' : -2, 'y' : -2} 59 | self.display = [''] 60 | if self.viewer: self.viewer.reset() 61 | 62 | def step(self): 63 | if self.closed: 64 | raise AlreadyClosed() 65 | if self.round is None: 66 | raise ResetNeeded() 67 | 68 | cur_player = self.round % 2 69 | action = deepcopy(self.agents[cur_player].step(self.last_action)) 70 | # check validity of action 71 | try: 72 | x = int(action['x']) 73 | y = int(action['y']) 74 | assert x == y == -1 or self._in_board(x, y) and self.board[x][y] == 0 75 | except: 76 | # Invalid action 77 | self.round = None 78 | self.display.append({'winner' : 1 - cur_player, 'error_info' : 'Invalid input!'}) 79 | return (2, 0) if cur_player else (0, 2) 80 | color = cur_player + 1 81 | self.round += 1 82 | if x == y == -1: 83 | # pass 84 | if self.last_action['x'] == -1: 85 | # episode ends 86 | ax, ay = self._count_area() 87 | winner = 0 if ax - ay > self.komi else 1 88 | self.round = None 89 | self.display.append({'winner' : winner, 'stones' : {'0' : ax, '1' : ay, 'komi' : self.komi}}) 90 | return (0, 2) if winner else (2, 0) 91 | else: 92 | self.last_action = dict(x = x, y = y) 93 | self.display.append('') 94 | else: 95 | self.board[x][y] = color 96 | # check remove 97 | surround = [] 98 | for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)): 99 | nx = x + dx 100 | ny = y + dy 101 | if self.board[nx][ny] == 3 - color: 102 | for liberty, group in surround: 103 | if (nx, ny) in group: 104 | break 105 | else: 106 | surround.append(self._count_liberty(nx, ny)) 107 | remove = [] 108 | for liberty, group in surround: 109 | if liberty == 0: 110 | for nx, ny in group: 111 | self.board[nx][ny] = 0 112 | remove.append(dict(x = nx, y = ny)) 113 | # check my liberty 114 | liberty, group = self._count_liberty(x, y) 115 | if liberty == 0: 116 | # Invalid action 117 | self.round = None 118 | self.display.append({'winner' : 1 - cur_player, 'error_info' : 'Suicide forbidden!', 'color' : cur_player, 'x' : x, 'y' : y}) 119 | return (2, 0) if cur_player else (0, 2) 120 | # check superko 121 | h = self._hash() 122 | if h in self.history: 123 | # Invalid action 124 | self.round = None 125 | self.display.append({'winner' : 1 - cur_player, 'error_info' : 'Superko violation!', 'color' : cur_player, 'x' : x, 'y' : y}) 126 | return (2, 0) if cur_player else (0, 2) 127 | self.history.add(h) 128 | self.last_action = dict(x = x, y = y) 129 | self.display.append(dict(color = cur_player, x = x, y = y, remove = remove)) 130 | 131 | def _hash(self): 132 | h = 0 133 | for i in range(1, self.size + 1): 134 | for j in range(1, self.size + 1): 135 | h = h * 3 + self.board[i][j] 136 | return hash(h) 137 | 138 | def _in_board(self, x, y): 139 | return 0 < x <= self.size and 0 < y <= self.size 140 | 141 | def _count_liberty(self, x, y): 142 | color = self.board[x][y] 143 | group = set() 144 | liberty = 0 145 | visited = [[0 for i in range(self.size + 2)] for j in range(self.size + 2)] 146 | q = [(x, y)] 147 | visited[x][y] = 1 148 | while q: 149 | x, y = q.pop(0) 150 | group.add((x, y)) 151 | for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)): 152 | nx = x + dx 153 | ny = y + dy 154 | if self.board[nx][ny] == 0 and not visited[nx][ny]: 155 | # liberty 156 | visited[nx][ny] = 2 157 | liberty += 1 158 | if self.board[nx][ny] == color and not visited[nx][ny]: 159 | # in group 160 | visited[nx][ny] = 1 161 | q.append((nx, ny)) 162 | return liberty, group 163 | 164 | def _count_area(self): 165 | area = [0, 0] 166 | visited = [[0 for i in range(self.size + 2)] for j in range(self.size + 2)] 167 | for i in range(1, self.size + 1): 168 | for j in range(1, self.size + 1): 169 | if self.board[i][j]: 170 | # stone 171 | area[self.board[i][j] - 1] += 1 172 | continue 173 | if visited[i][j]: continue 174 | # new connected area 175 | reach = [0, 0] 176 | cnt = 0 177 | q = [(i, j)] 178 | visited[i][j] = 1 179 | while q: 180 | x, y = q.pop(0) 181 | cnt += 1 182 | for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)): 183 | nx = x + dx 184 | ny = y + dy 185 | if self.board[nx][ny] == 0 and not visited[nx][ny]: 186 | visited[nx][ny] = 1 187 | q.append((nx, ny)) 188 | if self.board[nx][ny] > 0: 189 | reach[self.board[nx][ny] - 1] = 1 190 | if not(reach[0] ^ reach[1]): reach = [0.5, 0.5] 191 | area[0] += reach[0] * cnt 192 | area[1] += reach[1] * cnt 193 | return area 194 | 195 | def render(self, mode = 'ansi'): 196 | if mode == 'ansi': 197 | if self.viewer is None: 198 | self.viewer = GoTextViewer(size = self.size) 199 | self.viewer.reset() 200 | self.viewer.render(self.display) 201 | self.display = [] 202 | else: 203 | super(GoEnv, self).render(mode) 204 | 205 | def close(self): 206 | self.closed = True 207 | 208 | def seed(self, seed = None): 209 | # No randomness 210 | pass -------------------------------------------------------------------------------- /botzone/envs/botzone/gomoku.py: -------------------------------------------------------------------------------- 1 | from botzone import * 2 | from botzone.online.viewer.gomoku import GomokuTextViewer 3 | 4 | class GomokuEnv(Env): 5 | ''' 6 | Description: 7 | Freestyle gomoku, with no restricted move, with board `size`*`size`. (default = 15) 8 | 9 | Decision order: In each turn only one player decide. 10 | Request: {"x": Number, "y": Number} for each player, {"x": -1, "y": -1} 11 | for the first turn. 12 | Response: {"x": Number, "y": Number} for my decision. 13 | 14 | Episode Termination: 15 | The one who first forms five-in-a-row wins, or a tie if the board is filled. 16 | Score: 17 | 2 for winner and 0 for loser, 1 for both in case of a tie. 18 | ''' 19 | 20 | metadata = {'render.modes': ['ansi']} 21 | 22 | def __init__(self, size = 15): 23 | # Initialize configurations, possible viewers and state 24 | self.agents = None 25 | self.round = None 26 | self.closed = False 27 | self.display = [] 28 | self.viewer = None 29 | self.size = size 30 | 31 | @property 32 | def player_num(self): 33 | return 2 34 | 35 | def reset(self, initdata = None): 36 | if self.closed: 37 | raise AlreadyClosed() 38 | if self.agents is None: 39 | raise AgentsNeeded() 40 | # Initialize each agent 41 | for agent in self.agents: 42 | agent.reset() 43 | # Initialize state for new episode 44 | self.round = 0 45 | self.board = b = [[0 for i in range(self.size)] for j in range(self.size)] 46 | self.last_action = {'x' : -1, 'y' : -1} 47 | self.display = [None] 48 | if self.viewer: self.viewer.reset() 49 | 50 | def step(self): 51 | if self.closed: 52 | raise AlreadyClosed() 53 | if self.round is None: 54 | raise ResetNeeded() 55 | 56 | cur_player = self.round % 2 57 | action = self.agents[cur_player].step(self.last_action) 58 | # check validity of action 59 | try: 60 | x = action['x'] 61 | y = action['y'] 62 | assert self._in_board(x, y) and self.board[x][y] == 0 63 | except: 64 | # Invalid action 65 | self.round = None 66 | self.display.append({'winner' : 1 - cur_player}) 67 | return (2, 0) if cur_player else (0, 2) 68 | color = cur_player + 1 69 | self.board[x][y] = color 70 | self.round += 1 71 | self.last_action = action 72 | # check episode ends 73 | for dx, dy in ((0, 1), (1, 0), (1, 1), (1, -1)): 74 | cnt = 1 75 | nx = x + dx 76 | ny = y + dy 77 | while self._in_board(nx, ny) and self.board[nx][ny] == color: 78 | cnt += 1 79 | nx += dx 80 | ny += dy 81 | nx = x - dx 82 | ny = y - dy 83 | while self._in_board(nx, ny) and self.board[nx][ny] == color: 84 | cnt += 1 85 | nx -= dx 86 | ny -= dy 87 | if cnt >= 5: 88 | self.round = None 89 | self.display.append(dict(color = cur_player, x = x, y = y, winner = cur_player)) 90 | return (0, 2) if cur_player else (2, 0) 91 | if self.round == self.size * self.size: 92 | self.round = None 93 | self.display.append(dict(color = cur_player, x = x, y = y, winner = 'none')) 94 | return (1, 1) 95 | self.display.append(dict(color = cur_player, x = x, y = y)) 96 | 97 | def _in_board(self, x, y): 98 | return 0 <= x < self.size and 0 <= y < self.size 99 | 100 | def render(self, mode = 'ansi'): 101 | if mode == 'ansi': 102 | if self.viewer is None: 103 | self.viewer = GomokuTextViewer(size = self.size) 104 | self.viewer.reset() 105 | self.viewer.render(self.display) 106 | self.display = [] 107 | else: 108 | super(GomokuEnv, self).render(mode) 109 | 110 | def close(self): 111 | self.closed = True 112 | 113 | def seed(self, seed = None): 114 | # No randomness 115 | pass -------------------------------------------------------------------------------- /botzone/envs/botzone/kingz.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from copy import deepcopy 3 | from random import randint as rand 4 | 5 | from botzone import * 6 | from botzone.online.viewer.kingz import KingzTextViewer 7 | 8 | class KingzEnv(Env): 9 | ''' 10 | Description: 11 | A popular SLG. 12 | 13 | Initdata: int=4/6/8 (size of the board) 14 | 15 | Request: 16 | First round: [map0, map1, ..., map99, x, y] 17 | Other rounds: [x, y, w, d] 18 | 19 | Response: 20 | [x, y, w, d] 21 | 22 | Episode Termination: 23 | Player whose base is conquered loses. When round exceeds 'limit'(default = 400), the total amount of troops is compared. 24 | Score: 25 | 2 for winner and 0 for loser, 1 for both in case of a draw. 26 | ''' 27 | 28 | metadata = {'render.modes': ['ansi']} 29 | 30 | def __init__(self, limit = 300): 31 | # Initialize configurations, possible viewers and state 32 | self.agents = None 33 | self.round = None 34 | self.closed = False 35 | self.initdata = {} 36 | self._seed = None 37 | self.display = [] 38 | self.viewer = None 39 | self.limit = limit 40 | 41 | @property 42 | def player_num(self): 43 | return 2 44 | 45 | def reset(self, initdata = None): 46 | if self.closed: 47 | raise AlreadyClosed() 48 | if self.agents is None: 49 | raise AgentsNeeded() 50 | # Initialize each agent 51 | for agent in self.agents: 52 | agent.reset() 53 | # Initialize state for new episode 54 | self.round = 0 55 | if initdata: initdata = int(initdata) 56 | else: initdata = 10 57 | if initdata not in range(4, 12, 2): 58 | initdata = 10 59 | self.initdata = self.size = initdata 60 | if self._seed: random.seed(self._seed) 61 | # -1 for obstacle, 0 for empty, 1 for castle, 2 for base 62 | self.info = [[0 for i in range(10)] for i in range(10)] 63 | # number of troops 64 | self.board = [[0 for i in range(10)] for i in range(10)] 65 | # territory 66 | self.color = [[0 for i in range(10)] for i in range(10)] 67 | self._generate_map() 68 | self.display = [dict( 69 | status = 'opening', 70 | count = [self.board[i][j] for i in range(10) for j in range(10)], 71 | type = [self.info[i][j] for i in range(10) for j in range(10)], 72 | belong = [self.color[i][j] for i in range(10) for j in range(10)] 73 | )] 74 | if self.viewer: self.viewer.reset(self.initdata) 75 | 76 | def step(self): 77 | if self.closed: 78 | raise AlreadyClosed() 79 | if self.round is None: 80 | raise ResetNeeded() 81 | 82 | if self.round == 0: 83 | self.requests = [[-1 if self.info[i][j] == -1 else self.board[i][j] for i in range(10) for j in range(10)] for p in range(2)] 84 | x, y = self.base 85 | self.requests[0].extend((x, y)) 86 | self.requests[1].extend((9 - x, 9 - y)) 87 | else: 88 | self.requests = self.last_actions 89 | self.requests.reverse() 90 | self.last_actions = actions = [deepcopy(self.agents[p].step(self.requests[p])) for p in range(2)] 91 | self.round += 1 92 | # check validity of action 93 | battle = defaultdict(lambda : [0, 0, 0]) 94 | invalid = [0, 0] 95 | for p in range(2): 96 | try: 97 | x, y, w, d = actions[p] # check format 98 | w = int(w) # check format 99 | assert 0 <= x < 10 and 0 <= y < 10 # in range 100 | assert self.color[x][y] == p + 1 # my territory 101 | assert 0 <= w < self.board[x][y] # enough troops 102 | self.board[x][y] -= w 103 | x += (-1, 1, 0, 0, 0)[d] 104 | y += (0, 0, -1, 1, 0)[d] 105 | assert 0 <= x < 10 and 0 <= y < 10 # in range 106 | assert self.info[x][y] != -1 # not obstacle 107 | battle[(x, y)][p + 1] += w 108 | except: 109 | invalid[p] = 1 110 | if invalid[0] or invalid[1]: 111 | self.round = None 112 | winner = 3 - invalid[0] - invalid[1] * 2 113 | self.display.append(dict( 114 | status = 'end', 115 | winner = winner, 116 | err = ('BOTH INVALID', 'PLAYER 1 INVALID', 'PLAYER 2 INVALID')[winner] 117 | )) 118 | return ((1, 1), (2, 0), (0, 2))[winner] 119 | # process move 120 | for x, y in battle: 121 | battle[(x, y)][self.color[x][y]] += self.board[x][y] 122 | self.board[x][y] = 0 123 | for x, y in battle: 124 | neutral, red, blue = self._battle(*battle[(x, y)]) 125 | self.board[x][y] = neutral + red + blue 126 | if neutral: self.color[x][y] = 0 127 | elif red: self.color[x][y] = 1 128 | elif blue: self.color[x][y] = 2 129 | for i in range(10): 130 | for j in range(10): 131 | if self.color[i][j] == 0: continue 132 | if self.info[i][j] == 2: self.board[i][j] += 2 133 | if self.info[i][j] == 1: self.board[i][j] += 1 134 | if self.info[i][j] == 0 and self.round % 8 == 0: self.board[i][j] += 1 135 | display = dict( 136 | status = 'combat', 137 | #count = [self.board[i][j] for i in range(10) for j in range(10)], 138 | #type = [self.info[i][j] for i in range(10) for j in range(10)], 139 | #belong = [self.color[i][j] for i in range(10) for j in range(10)], 140 | operation = deepcopy(self.last_actions) 141 | ) 142 | # check episode ends 143 | x, y = self.base 144 | if self.color[x][y] == 2 or self.color[9 - x][9 - y] == 1: 145 | if self.color[x][y] == 2: 146 | if self.color[9 - x][9 - y] == 1: 147 | winner = 0 148 | else: 149 | winner = 2 150 | else: 151 | winner = 1 152 | self.round = None 153 | display['status'] = 'finish' 154 | display['winner'] = winner 155 | display['err'] = ('BOTH KILL', 'PLAYER 1 KILL', 'PLAYER 2 KILL')[winner] 156 | self.display.append(display) 157 | return ((1, 1), (2, 0), (0, 2))[winner] 158 | if self.round == self.limit: 159 | t = 0 160 | for i in range(10): 161 | for j in range(10): 162 | t += self.board[i][j] * ((self.color[i][j] == 1) - (self.color[i][j] == 2)) 163 | if t > 0: winner = 1 164 | elif t < 0: winner = 2 165 | else: winner = 0 166 | self.round = None 167 | display['status'] = 'finish' 168 | display['winner'] = winner 169 | display['err'] = ('DRAW', 'PLAYER 1 WIN', 'PLAYER 2 WIN')[winner] 170 | self.display.append(display) 171 | return ((1, 1), (2, 0), (0, 2))[winner] 172 | self.display.append(display) 173 | 174 | def render(self, mode = 'ansi'): 175 | if mode == 'ansi': 176 | if self.viewer is None: 177 | self.viewer = KingzTextViewer() 178 | self.viewer.reset(self.initdata) 179 | self.viewer.render(self.display) 180 | self.display = [] 181 | else: 182 | super(KingzEnv, self).render(mode) 183 | 184 | def close(self): 185 | self.closed = True 186 | 187 | def _generate_map(self): 188 | info = self.info 189 | b = self.board 190 | # This is a copy of original implementation on botzone.org 191 | while True: 192 | for i in range(0, 5): 193 | for j in range(0, 10): 194 | t = 6 195 | if i and j and info[i - 1][j - 1] == -1: t -= 2 196 | if i and j != 9 and info[i - 1][j + 1] == -1: t -= 2 197 | if i and info[i - 1][j] == -1: t += 2 198 | if j and info[i][j - 1] == -1: t += 2 199 | info[i][j] = info[9 - i][9 - j] = 0 if rand(0, t - 1) else -1 200 | if self.size == 8: 201 | for i in range(10): 202 | info[i][0] = info[i][-1] = info[0][i] = info[-1][i] = -1 203 | if self.size == 6: 204 | for i in range(10): 205 | info[i][1] = info[i][-2] = info[1][i] = info[-2][i] = -1 206 | if self.size == 4: 207 | for i in range(10): 208 | info[i][2] = info[i][-3] = info[2][i] = info[-3][i] = -1 209 | x = rand(0, 9) 210 | y = rand(0, 9) 211 | if x >= 5: 212 | x = 9 - x 213 | y = 9 - y 214 | self._dfs(x, y) 215 | if info[x][y] == 1 and info[9 - x][9 - y] == 1: break 216 | self.base = x, y 217 | info[x][y] = info[9 - x][9 - y] = 2 218 | b[x][y] = b[9 - x][9 - y] = 10 219 | self.color[x][y] = 1 220 | self.color[9 - x][9 - y] = 2 221 | for i in range(0, 5): 222 | for j in range(0, 10): 223 | if info[i][j] == 1: 224 | if rand(0, 9) == 0: 225 | b[i][j] = b[9 - i][9 - j] = rand(0, 24) + rand(0, 24) + rand(0, 24) + rand(0, 24) + 5 226 | else: 227 | info[i][j] = info[9 - i][9 - j] = 0 228 | 229 | def _dfs(self, x, y): 230 | if x < 0 or x >= 10 or y < 0 or y >= 10 or self.info[x][y] != 0: return 231 | self.info[x][y] = 1 232 | self._dfs(x - 1, y) 233 | self._dfs(x + 1, y) 234 | self._dfs(x, y - 1) 235 | self._dfs(x, y + 1) 236 | 237 | def _battle(self, neutral, red, blue): 238 | t = min(red, blue) 239 | red -= t 240 | blue -= t 241 | if red > 0: 242 | t = min(red, neutral) 243 | red -= t 244 | neutral -= t 245 | else: 246 | t = min(blue, neutral) 247 | blue -= t 248 | neutral -= t 249 | return neutral, red, blue -------------------------------------------------------------------------------- /botzone/envs/botzone/minesweeper.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | from botzone import * 4 | from botzone.online.viewer.minesweeper import MineSweeperTextViewer 5 | 6 | class MineSweeperEnv(Env): 7 | ''' 8 | Description: 9 | Traditional minesweeper game. 10 | 11 | Initdata: { 12 | "width": Number of [11, 80], default 30 13 | "height": Number of [11, 40], default 20 14 | "minecount": Number of [11, 100], default 80 15 | "seed": random seed 16 | } 17 | 18 | Request: { 19 | "width": Number, 20 | "height": Number, 21 | "minecount": Number, 22 | "changed": [{ 23 | "row": Number, 24 | "col": Number, 25 | "val": Number # 9 for mine 26 | }] 27 | } 28 | Response: {"row": Number, "col": Number} 29 | 30 | Episode Termination: 31 | When the number of mines left equals to the number of unopened cells. 32 | Score: 33 | The percentage of mines left. 34 | ''' 35 | 36 | metadata = {'render.modes': ['ansi']} 37 | 38 | def __init__(self): 39 | # Initialize configurations, possible viewers and state 40 | self.agents = None 41 | self.round = None 42 | self.closed = False 43 | self.initdata = {} 44 | self._seed = None 45 | self.display = [] 46 | self.viewer = None 47 | 48 | @property 49 | def player_num(self): 50 | return 1 51 | 52 | def reset(self, initdata = None): 53 | if self.closed: 54 | raise AlreadyClosed() 55 | if self.agents is None: 56 | raise AgentsNeeded() 57 | # Initialize each agent 58 | for agent in self.agents: 59 | agent.reset() 60 | # Initialize state for new episode 61 | self.round = 0 62 | if not initdata: initdata = {} 63 | self.width = int(initdata.get('width', 30)) 64 | if not 10 < self.width <= 80: 65 | self.width = 30 66 | self.height = int(initdata.get('height', 20)) 67 | if not 10 < self.height <= 40: 68 | self.height = 20 69 | self.minecount = int(initdata.get('minecount', 80)) 70 | if not 10 < self.minecount <= 100: 71 | self.minecount = 80 72 | if 'seed' in initdata: 73 | self.seed(initdata['seed']) 74 | self.initdata = dict(width = self.width, height = self.height, minecount = self.minecount) 75 | if self._seed: random.seed(self._seed) 76 | mines = [(x, y) for x in range(self.height) for y in range(self.width)] 77 | random.shuffle(mines) 78 | mines = mines[ : self.minecount] 79 | self.board = [[0 for i in range(self.width)] for j in range(self.height)] 80 | for x, y in mines: 81 | for dx in range(-1, 2): 82 | for dy in range(-1, 2): 83 | nx = x + dx 84 | ny = y + dy 85 | if self._in_board(nx, ny): 86 | self.board[nx][ny] += 1 87 | for x, y in mines: 88 | self.board[x][y] = 9 89 | self.reveal = [[False for i in range(self.width)] for j in range(self.height)] 90 | self.cellleft = self.width * self.height 91 | self.mineleft = self.minecount 92 | self.status = None 93 | self.display = [{'status' : None}] 94 | if self.viewer: self.viewer.reset(self.initdata) 95 | 96 | def step(self): 97 | if self.closed: 98 | raise AlreadyClosed() 99 | if self.round is None: 100 | raise ResetNeeded() 101 | 102 | action = self.agents[0].step(dict( 103 | width = self.width, 104 | height = self.height, 105 | minecount = self.minecount, 106 | changed = self.status 107 | )) 108 | # check validity of action 109 | try: 110 | row = action['row'] 111 | col = action['col'] 112 | assert not self.reveal[row][col] 113 | except: 114 | # Invalid action 115 | self.round = None 116 | self.display.append(dict(msg = 'INVALIDMOVE')) 117 | return (0, ) 118 | # process move 119 | self.status = [] 120 | q = [(row, col)] 121 | self.reveal[row][col] = True 122 | while q: 123 | x, y = q.pop(0) 124 | val = self.board[x][y] 125 | self.status.append(dict(row = x, col = y, val = val)) 126 | if val == 9: self.mineleft -= 1 127 | if val == 0: 128 | for dx in range(-1, 2): 129 | for dy in range(-1, 2): 130 | nx = x + dx 131 | ny = y + dy 132 | if self._in_board(nx, ny) and not self.reveal[nx][ny]: 133 | q.append((nx, ny)) 134 | self.reveal[nx][ny] = True 135 | self.cellleft -= len(self.status) 136 | self.round += 1 137 | # check episode ends 138 | if self.cellleft == self.mineleft: 139 | self.round = None 140 | self.display.append(dict(msg = 'FINISH', status = self.status)) 141 | return (self.mineleft / self.minecount * 100, ) 142 | self.display.append(dict(status = self.status)) 143 | 144 | def render(self, mode = 'ansi'): 145 | if mode == 'ansi': 146 | if self.viewer is None: 147 | self.viewer = MineSweeperTextViewer() 148 | self.viewer.reset(self.initdata) 149 | self.viewer.render(self.display) 150 | self.display = [] 151 | else: 152 | super(MineSweeperEnv, self).render(mode) 153 | 154 | def close(self): 155 | self.closed = True 156 | 157 | def _in_board(self, x, y): 158 | return 0 <= x < self.height and 0 <= y < self.width -------------------------------------------------------------------------------- /botzone/envs/botzone/nogo.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | 3 | from botzone import * 4 | from botzone.online.viewer.nogo import NoGoTextViewer 5 | 6 | class NoGoEnv(Env): 7 | ''' 8 | Description: 9 | Traditional NoGo with `size`*`size` board. (default = 9) 10 | 11 | Decision order: In each turn only one player decide. 12 | Request: {"x": Number, "y": Number} for each player, {"x": -1, "y": -1} 13 | for the first turn. 14 | Response: {"x": Number, "y": Number} for my decision. 15 | 16 | Episode Termination: 17 | The first player who cannot move loses. 18 | Score: 19 | 2 for winner and 0 for loser. 20 | ''' 21 | 22 | metadata = {'render.modes': ['ansi']} 23 | 24 | def __init__(self, size = 9): 25 | # Initialize configurations, possible viewers and state 26 | self.agents = None 27 | self.round = None 28 | self.closed = False 29 | self.display = [] 30 | self.viewer = None 31 | self.size = size 32 | 33 | @property 34 | def player_num(self): 35 | return 2 36 | 37 | def reset(self, initdata = None): 38 | if self.closed: 39 | raise AlreadyClosed() 40 | if self.agents is None: 41 | raise AgentsNeeded() 42 | # Initialize each agent 43 | for agent in self.agents: 44 | agent.reset() 45 | # Initialize state for new episode 46 | self.round = 0 47 | self.board = b = [[0 for i in range(self.size + 2)] for j in range(self.size + 2)] 48 | for i in range(self.size + 2): 49 | b[i][0] = b[i][-1] = b[0][i] = b[-1][i] = -1 50 | self.last_action = {'x' : -1, 'y' : -1} 51 | self.display = [''] 52 | if self.viewer: self.viewer.reset() 53 | 54 | def step(self): 55 | if self.closed: 56 | raise AlreadyClosed() 57 | if self.round is None: 58 | raise ResetNeeded() 59 | 60 | cur_player = self.round % 2 61 | action = deepcopy(self.agents[cur_player].step(self.last_action)) 62 | # check validity of action 63 | try: 64 | x = int(action['x']) + 1 65 | y = int(action['y']) + 1 66 | assert self._in_board(x, y) and self.board[x][y] == 0 67 | except: 68 | # Invalid action 69 | self.round = None 70 | self.display.append({'winner' : 1 - cur_player, 'err' : 'Invalid input!'}) 71 | return (2, 0) if cur_player else (0, 2) 72 | color = cur_player + 1 73 | self.round += 1 74 | self.board[x][y] = color 75 | self.last_action = dict(x = x - 1, y = y - 1) 76 | if not self._check_valid(x, y): 77 | self.round = None 78 | self.display.append({'winner' : 1 - cur_player, 'err' : 'Invalid move!', 'color' : cur_player, 'x' : x - 1, 'y' : y - 1}) 79 | return (2, 0) if cur_player else (0, 2) 80 | # check episode ends 81 | if not self._has_valid_move(3 - color): 82 | self.round = None 83 | self.display.append({'winner' : cur_player, 'color' : cur_player, 'x' : x - 1, 'y' : y - 1}) 84 | return (0, 2) if cur_player else (2, 0) 85 | self.display.append({'color' : cur_player, 'x' : x - 1, 'y' : y - 1}) 86 | 87 | def _in_board(self, x, y): 88 | return 0 < x <= self.size and 0 < y <= self.size 89 | 90 | def _count_liberty(self, x, y): 91 | color = self.board[x][y] 92 | group = set() 93 | liberty = 0 94 | visited = [[0 for i in range(self.size + 2)] for j in range(self.size + 2)] 95 | q = [(x, y)] 96 | visited[x][y] = 1 97 | while q: 98 | x, y = q.pop(0) 99 | group.add((x, y)) 100 | for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)): 101 | nx = x + dx 102 | ny = y + dy 103 | if self.board[nx][ny] == 0 and not visited[nx][ny]: 104 | # liberty 105 | visited[nx][ny] = 2 106 | liberty += 1 107 | if self.board[nx][ny] == color and not visited[nx][ny]: 108 | # in group 109 | visited[nx][ny] = 1 110 | q.append((nx, ny)) 111 | return liberty, group 112 | 113 | def _check_valid(self, x, y): 114 | color = self.board[x][y] 115 | # check my liberty 116 | liberty, group = self._count_liberty(x, y) 117 | if liberty == 0: return False 118 | # check capture 119 | surround = [] 120 | for dx, dy in ((-1, 0), (1, 0), (0, -1), (0, 1)): 121 | nx = x + dx 122 | ny = y + dy 123 | if self.board[nx][ny] == 3 - color: 124 | for liberty, group in surround: 125 | if (nx, ny) in group: 126 | break 127 | else: 128 | surround.append(self._count_liberty(nx, ny)) 129 | if surround[-1][0] == 0: return False 130 | return True 131 | 132 | def _has_valid_move(self, color): 133 | for i in range(1, self.size + 1): 134 | for j in range(1, self.size + 1): 135 | if self.board[i][j] == 0: 136 | # try it 137 | self.board[i][j] = color 138 | if self._check_valid(i, j): 139 | self.board[i][j] = 0 140 | return True 141 | self.board[i][j] = 0 142 | return False 143 | 144 | def render(self, mode = 'ansi'): 145 | if mode == 'ansi': 146 | if self.viewer is None: 147 | self.viewer = NoGoTextViewer(size = self.size) 148 | self.viewer.reset() 149 | self.viewer.render(self.display) 150 | self.display = [] 151 | else: 152 | super(NoGoEnv, self).render(mode) 153 | 154 | def close(self): 155 | self.closed = True 156 | 157 | def seed(self, seed = None): 158 | # No randomness 159 | pass -------------------------------------------------------------------------------- /botzone/envs/botzone/renju.py: -------------------------------------------------------------------------------- 1 | from botzone import * 2 | from botzone.online.viewer.renju import RenjuTextViewer 3 | 4 | class RenjuEnv(Env): 5 | ''' 6 | Description: 7 | Freestyle gomoku, with exchange after 3rd move, with board `size`*`size`. (default = 15) 8 | 9 | Decision order: In each turn only one player decide. 10 | Request: {"x": Number, "y": Number} for each player, {"x": -1, "y": -1} 11 | for the first turn and for the turn after exchange. 12 | Response: {"x": Number, "y": Number} for my decision, {"x": -1, "y": -1} 13 | for exchange. 14 | 15 | Episode Termination: 16 | The one who first forms five-in-a-row wins, or a tie if the board is filled. 17 | Score: 18 | 2 for winner and 0 for loser, 1 for both in case of a tie. 19 | ''' 20 | 21 | metadata = {'render.modes': ['ansi']} 22 | 23 | def __init__(self, size = 15): 24 | # Initialize configurations, possible viewers and state 25 | self.agents = None 26 | self.round = None 27 | self.closed = False 28 | self.display = [] 29 | self.viewer = None 30 | self.size = size 31 | 32 | @property 33 | def player_num(self): 34 | return 2 35 | 36 | def reset(self, initdata = None): 37 | if self.closed: 38 | raise AlreadyClosed() 39 | if self.agents is None: 40 | raise AgentsNeeded() 41 | # Initialize each agent 42 | for agent in self.agents: 43 | agent.reset() 44 | # Initialize state for new episode 45 | self.round = 0 46 | self.board = b = [[0 for i in range(self.size)] for j in range(self.size)] 47 | self.last_action = {'x' : -1, 'y' : -1} 48 | self.display = [None] 49 | if self.viewer: self.viewer.reset() 50 | 51 | def step(self): 52 | if self.closed: 53 | raise AlreadyClosed() 54 | if self.round is None: 55 | raise ResetNeeded() 56 | 57 | cur_player = self.round % 2 58 | action = self.agents[cur_player].step(self.last_action) 59 | # check validity of action 60 | try: 61 | x = action['x'] 62 | y = action['y'] 63 | assert self._in_board(x, y) and self.board[x][y] == 0 or self.round == 3 and x == y == -1 64 | except: 65 | # Invalid action 66 | self.round = None 67 | self.display.append({'winner' : 1 - cur_player}) 68 | return (2, 0) if cur_player else (0, 2) 69 | color = cur_player + 1 70 | self.round += 1 71 | self.last_action = action 72 | if x == y == -1: 73 | # exchange 74 | for i in range(self.size): 75 | for j in range(self.size): 76 | if self.board[i][j]: 77 | self.board[i][j] = 3 - self.board[i][j] 78 | self.display.append({'msg' : 'exchange'}) 79 | else: 80 | self.board[x][y] = color 81 | # check episode ends 82 | for dx, dy in ((0, 1), (1, 0), (1, 1), (1, -1)): 83 | cnt = 1 84 | nx = x + dx 85 | ny = y + dy 86 | while self._in_board(nx, ny) and self.board[nx][ny] == color: 87 | cnt += 1 88 | nx += dx 89 | ny += dy 90 | nx = x - dx 91 | ny = y - dy 92 | while self._in_board(nx, ny) and self.board[nx][ny] == color: 93 | cnt += 1 94 | nx -= dx 95 | ny -= dy 96 | if cnt >= 5: 97 | self.round = None 98 | self.display.append(dict(color = cur_player, x = x, y = y, winner = cur_player)) 99 | return (0, 2) if cur_player else (2, 0) 100 | if self.round == self.size * self.size: 101 | self.round = None 102 | self.display.append(dict(color = cur_player, x = x, y = y, winner = 'none')) 103 | return (1, 1) 104 | self.display.append(dict(color = cur_player, x = x, y = y)) 105 | 106 | def _in_board(self, x, y): 107 | return 0 <= x < self.size and 0 <= y < self.size 108 | 109 | def render(self, mode = 'ansi'): 110 | if mode == 'ansi': 111 | if self.viewer is None: 112 | self.viewer = RenjuTextViewer() 113 | self.viewer.reset() 114 | self.viewer.render(self.display) 115 | self.display = [] 116 | else: 117 | super(RenjuEnv, self).render(mode) 118 | 119 | def close(self): 120 | self.closed = True 121 | 122 | def seed(self, seed = None): 123 | # No randomness 124 | pass -------------------------------------------------------------------------------- /botzone/envs/botzone/reversi.py: -------------------------------------------------------------------------------- 1 | from botzone import * 2 | from botzone.online.viewer.reversi import ReversiTextViewer 3 | 4 | class ReversiEnv(Env): 5 | ''' 6 | Description: 7 | Traditional reversi game. 8 | 9 | Decision order: In each turn only one player decide. 10 | Request: {"x": Number, "y": Number} for each player, {"x": -1, "y": -1} 11 | for the first turn. 12 | Response: {"x": Number, "y": Number} for my decision, {"x": -1, "y": -1} 13 | for skipping. 14 | 15 | Episode Termination: 16 | When both players have no position to move. The player with more 17 | stones on the board wins. 18 | Score: 19 | 2 for winner and 0 for loser, 1 for both in case of a tie. 20 | ''' 21 | 22 | metadata = {'render.modes': ['ansi']} 23 | 24 | def __init__(self): 25 | # Initialize configurations, possible viewers and state 26 | self.agents = None 27 | self.round = None 28 | self.closed = False 29 | self.display = [] 30 | self.viewer = None 31 | 32 | @property 33 | def player_num(self): 34 | return 2 35 | 36 | def reset(self, initdata = None): 37 | if self.closed: 38 | raise AlreadyClosed() 39 | if self.agents is None: 40 | raise AgentsNeeded() 41 | # Initialize each agent 42 | for agent in self.agents: 43 | agent.reset() 44 | # Initialize state for new episode 45 | self.round = 0 46 | self.board = b = [[0 for i in range(8)] for j in range(8)] 47 | b[3][3] = b[4][4] = 2 # 2 for white 48 | b[3][4] = b[4][3] = 1 # 1 for black 49 | self.last_action = {'x' : -1, 'y' : -1} 50 | self.display = [None] 51 | if self.viewer: self.viewer.reset() 52 | self.valid_move = [self._valid_move(color) for color in (1, 2)] 53 | 54 | def step(self): 55 | if self.closed: 56 | raise AlreadyClosed() 57 | if self.round is None: 58 | raise ResetNeeded() 59 | 60 | cur_player = self.round % 2 61 | action = self.agents[cur_player].step(self.last_action) 62 | # check validity of action 63 | try: 64 | x = action['x'] 65 | y = action['y'] 66 | assert x == -1 and y == -1 or (x, y) in self.valid_move[cur_player] 67 | except: 68 | # Invalid action 69 | self.round = None 70 | self.display.append({'winner' : 1 - cur_player}) 71 | return (2, 0) if cur_player else (0, 2) 72 | color = cur_player + 1 73 | if x != -1 and y != -1: 74 | self.board[x][y] = color 75 | for i, j in self.valid_move[cur_player][(x, y)]: 76 | self.board[i][j] = color 77 | self.round += 1 78 | self.last_action = action 79 | # check episode ends 80 | self.valid_move = [self._valid_move(color) for color in (1, 2)] 81 | if not self.valid_move[0] and not self.valid_move[1]: 82 | # both players have no valid moves 83 | self.round = None 84 | black, white = (len([0 for i in range(8) for j in range(8) if self.board[i][j] == color]) for color in (1, 2)) 85 | if black > white: 86 | self.display.append({'x' : x, 'y' : y, 'winner' : 0}) 87 | return (2, 0) 88 | if white > black: 89 | self.display.append({'x' : x, 'y' : y, 'winner' : 1}) 90 | return (0, 2) 91 | self.display.append({'x' : x, 'y' : y}) 92 | return (1, 1) 93 | self.display.append({'x' : x, 'y' : y}) 94 | 95 | def render(self, mode = 'ansi'): 96 | if mode == 'ansi': 97 | if self.viewer is None: 98 | self.viewer = ReversiTextViewer() 99 | self.viewer.reset() 100 | self.viewer.render(self.display) 101 | self.display = [] 102 | else: 103 | super(ReversiEnv, self).render(mode) 104 | 105 | def close(self): 106 | self.closed = True 107 | 108 | def seed(self, seed = None): 109 | # No randomness 110 | pass 111 | 112 | def _valid_move(self, color): 113 | valid = {} 114 | for i in range(8): 115 | for j in range(8): 116 | if self.board[i][j] == 0: 117 | change = self._test_change(i, j, color) 118 | if change: 119 | valid[(i, j)] = change 120 | return valid 121 | 122 | def _test_change(self, x, y, color): 123 | b = self.board 124 | change = [] 125 | for dx, dy in ((-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)): 126 | t = [] 127 | nx = x + dx 128 | ny = y + dy 129 | while self._in_board(nx, ny) and b[nx][ny] == 3 - color: 130 | t.append((nx, ny)) 131 | nx += dx 132 | ny += dy 133 | if not (self._in_board(nx, ny) and b[nx][ny] == color): continue 134 | change.extend(t) 135 | return change 136 | 137 | def _in_board(self, x, y): 138 | return 0 <= x < 8 and 0 <= y < 8 -------------------------------------------------------------------------------- /botzone/envs/botzone/snake.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | import random 3 | 4 | from botzone import * 5 | from botzone.online.viewer.snake import SnakeTextViewer 6 | 7 | class SnakeEnv(Env): 8 | ''' 9 | Description: 10 | Two-player Snake game. 11 | Rule: https://wiki.botzone.org.cn/index.php?title=Snake 12 | 13 | Decision order: Two players act simutaneously in each turn. 14 | Initdata: { 15 | width: Number of 10~12 16 | height: Number of 10~16 17 | "0": {x: Number, y: Number} # initial pos 18 | "1": {x: Number, y: Number} # initial pos 19 | obstacle: list of {x: Number, y: Number} 20 | } 21 | Request: 22 | First: {width, height, x, y, obstacle} 23 | Others: {direction: 0..3} of the other player in the last turn 24 | Response: 25 | {direction: 0..3} for (-1,0), (0,1), (1,0), (0,-1) 26 | 27 | Episode Termination: 28 | The first player to give an invalid move loses, or a tie if both. 29 | Score: 30 | 2 for winner and 0 for loser, 1 for both in case of a tie. 31 | ''' 32 | 33 | metadata = {'render.modes': ['ansi']} 34 | 35 | def __init__(self): 36 | self.agents = None 37 | self.round = None 38 | self.closed = False 39 | self.initdata = {} 40 | self._seed = None 41 | self.display = [] 42 | self.viewer = None 43 | self.dx = [-1, 0, 1, 0] 44 | self.dy = [0, 1, 0, -1] 45 | 46 | @property 47 | def player_num(self): 48 | return 2 49 | 50 | def reset(self, initdata = None): 51 | if self.closed: 52 | raise AlreadyClosed() 53 | if self.agents is None: 54 | raise AgentsNeeded() 55 | # Initialize each agent 56 | for agent in self.agents: 57 | agent.reset() 58 | # Initialize state for new episode 59 | self.round = 0 60 | if not initdata: initdata = {} 61 | if 'seed' in initdata: 62 | self.seed(initdata['seed']) 63 | if self._seed: 64 | random.seed(self._seed) 65 | self.width = initdata.get('width', random.randint(10, 12)) 66 | self.height = initdata.get('height', random.randint(10, 16)) 67 | self.body = [[(1, 1)], [(self.height, self.width)]] 68 | for i in range(2): 69 | if str(i) in initdata: 70 | self.body[i] = [(initdata[str(i)]['x'], initdata[str(i)]['y'])] 71 | if 'obstacle' in initdata: 72 | obs = [(d['x'], d['y']) for d in initdata['obstacle']] 73 | else: 74 | obs = [(x, y) for x in range(1, self.height + 1) for y in range(1, self.width + 1)] 75 | obs = obs[1 : (len(obs) + 1) // 2] 76 | random.shuffle(obs) 77 | obs = obs[: self.height * self.width // 20] 78 | obs.extend([(self.height + 1 - x, self.width + 1 - y) for x, y in obs]) 79 | self.board = [[0 for i in range(self.width + 2)] for j in range(self.height + 2)] 80 | for i in range(self.width + 2): self.board[0][i] = self.board[-1][i] = -2 81 | for j in range(self.height + 2): self.board[j][0] = self.board[j][-1] = -2 82 | for x, y in obs: 83 | self.board[x][y] = -1 84 | for i in range(2): 85 | for x, y in self.body[i]: 86 | self.board[x][y] = i + 1 87 | self.initdata = { 88 | 'width' : self.width, 89 | 'height' : self.height, 90 | '0' : {'x' : self.body[0][0][0], 'y' : self.body[0][0][1]}, 91 | '1' : {'x' : self.body[1][0][0], 'y' : self.body[1][0][1]}, 92 | 'obstacle' : [{'x' : x, 'y' : y} for x, y in obs] 93 | } 94 | self.display = [self.initdata] 95 | if self.viewer: self.viewer.reset(self.initdata) 96 | self.last_actions = (0, 0) 97 | 98 | def step(self): 99 | if self.closed: 100 | raise AlreadyClosed() 101 | if self.round is None: 102 | raise ResetNeeded() 103 | # provide request 104 | if self.round == 0: 105 | requests = [{ 106 | 'width' : self.width, 107 | 'height' : self.height, 108 | 'obstacle' : self.initdata['obstacle'], 109 | 'x' : self.initdata[str(i)]['x'], 110 | 'y' : self.initdata[str(i)]['y'] 111 | } for i in range(2)] 112 | else: 113 | requests = [{'direction' : d} for d in reversed(self.last_actions)] 114 | # deep copy for safety 115 | actions = [deepcopy(self.agents[i].step(deepcopy(requests[i]))) for i in range(2)] 116 | self.last_actions = [] 117 | grow = self.round < 10 or self.round % 3 == 0 118 | self.round += 1 119 | # check validity 120 | for i in range(2): 121 | try: 122 | d = actions[i].get('direction', None) 123 | assert isinstance(d, int) and 0 <= d < 4 124 | except: 125 | score = ((0, 2), (2, 0))[i] 126 | self.display.append({'winner' : 2 - i, 'grow' : 'true' if grow else 'false'}) 127 | self.round = None 128 | return score 129 | self.last_actions.append(d) 130 | x, y = self.body[i][-1] 131 | x += self.dx[d] 132 | y += self.dy[d] 133 | self.body[i].append((x, y)) 134 | if not grow: 135 | x, y = self.body[i].pop(0) 136 | self.board[x][y] = 0 137 | # move 138 | e = 0 139 | if self.body[0][-1] == self.body[1][-1]: e = 3 140 | else: 141 | for i in range(2): 142 | x, y = self.body[i][-1] 143 | if self.board[x][y] == 0: 144 | self.board[x][y] = i + 1 145 | else: 146 | e += i + 1 147 | if e: 148 | score = ((0, 2), (2, 0), (1, 1))[e - 1] 149 | d = {'grow' : 'true' if grow else 'false', 150 | '0' : self.last_actions[0], 151 | '1' : self.last_actions[1] 152 | } 153 | if e < 3: d['winner'] = e - 1 154 | self.display.append(d) 155 | self.round = None 156 | return score 157 | self.display.append({ 158 | 'grow' : 'true' if grow else 'false', 159 | '0' : self.last_actions[0], 160 | '1' : self.last_actions[1] 161 | }) 162 | 163 | def render(self, mode = 'ansi'): 164 | if mode == 'ansi': 165 | if self.viewer is None: 166 | self.viewer = SnakeTextViewer() 167 | self.viewer.reset(self.initdata) 168 | self.viewer.render(self.display) 169 | self.display = [] 170 | else: 171 | super(SnakeEnv, self).render(mode) 172 | 173 | def close(self): 174 | self.closed = True -------------------------------------------------------------------------------- /botzone/envs/botzone/tictactoe.py: -------------------------------------------------------------------------------- 1 | from botzone import * 2 | from botzone.online.viewer.tictactoe import TicTacToeTextViewer 3 | 4 | class TicTacToeEnv(Env): 5 | ''' 6 | Description: 7 | Traditional TicTacToe. 8 | 9 | Decision order: In each turn only one player decide. 10 | Request: {"x": Number, "y": Number} for each player, {"x": -1, "y": -1} for the first turn. 11 | Response: {"x": Number, "y": Number} for my decision. 12 | 13 | Episode Termination: 14 | (1) One player fills three blanks in a line and wins or 15 | (2) A tie if all blanks are filled without a winner. 16 | Score: 17 | 2 for winner and 0 for loser, 1 for both in case of a tie. 18 | ''' 19 | 20 | metadata = {'render.modes': ['ansi']} 21 | 22 | def __init__(self): 23 | # Initialize configurations, possible viewers and state 24 | self.agents = None 25 | self.round = None 26 | self.closed = False 27 | self.display = [] 28 | self.viewer = None 29 | 30 | @property 31 | def player_num(self): 32 | return 2 33 | 34 | def reset(self, initdata = None): 35 | if self.closed: 36 | raise AlreadyClosed() 37 | if self.agents is None: 38 | raise AgentsNeeded() 39 | # Initialize each agent 40 | for agent in self.agents: 41 | agent.reset() 42 | # Initialize state for new episode 43 | self.round = 0 44 | self.board = [[0 for j in range(3)] for i in range(3)] 45 | self.last_action = {'x' : -1, 'y' : -1} 46 | self.display = [''] 47 | if self.viewer: self.viewer.reset() 48 | 49 | def step(self): 50 | if self.closed: 51 | raise AlreadyClosed() 52 | if self.round is None: 53 | raise ResetNeeded() 54 | 55 | cur_player = self.round % 2 56 | action = self.agents[cur_player].step(self.last_action) 57 | # check validity of action 58 | try: 59 | x = action['x'] 60 | y = action['y'] 61 | assert 0 <= x < 3 and 0 <= y < 3 62 | assert self.board[x][y] == 0 63 | except: 64 | # Invalid action 65 | self.round = None 66 | self.display.append(dict(winner = 1 - cur_player, err = 'Invalid move!')) 67 | return (2, 0) if cur_player else (0, 2) 68 | self.board[x][y] = cur_player + 1 69 | self.round += 1 70 | self.last_action = action 71 | # check winner 72 | if (self.board[x][0] == self.board[x][1] == self.board[x][2] 73 | or self.board[0][y] == self.board[1][y] == self.board[2][y] 74 | or x == y and self.board[0][0] == self.board[1][1] == self.board[2][2] 75 | or x + y == 2 and self.board[0][2] == self.board[1][1] == self.board[2][0]): 76 | self.round = None 77 | self.display.append(dict(winner = cur_player, x = x, y = y)) 78 | return (0, 2) if cur_player else (2, 0) 79 | # check tie 80 | if self.round == 9: 81 | self.round = None 82 | self.display.append(dict(x = x, y = y)) 83 | return (1, 1) 84 | self.display.append(dict(x = x, y = y)) 85 | 86 | def render(self, mode = 'ansi'): 87 | if mode == 'ansi': 88 | if self.viewer is None: 89 | self.viewer = TicTacToeTextViewer() 90 | self.viewer.reset() 91 | self.viewer.render(self.display) 92 | self.display = [] 93 | else: 94 | super(TicTacToeEnv, self).render(mode) 95 | 96 | def close(self): 97 | self.closed = True 98 | 99 | def seed(self, seed = None): 100 | # No randomness 101 | pass -------------------------------------------------------------------------------- /botzone/envs/registration.py: -------------------------------------------------------------------------------- 1 | from copy import deepcopy 2 | import importlib 3 | 4 | from botzone.error import Error 5 | 6 | def load(name): 7 | mod_name, attr_name = name.split(':') 8 | mod = importlib.import_module(mod_name) 9 | fn = getattr(mod, attr_name) 10 | return fn 11 | 12 | class EnvSpec: 13 | ''' 14 | A specification for a particular instance of the environment. 15 | 16 | Args: 17 | id: environment ID 18 | entry_point: Python entry point of the environment class (module:class) 19 | kwargs: dict of kwargs to pass to environment class 20 | ''' 21 | 22 | def __init__(self, id, entry_point = None, **kwargs): 23 | self.id = id 24 | self.entry_point = entry_point 25 | self.kwargs = kwargs 26 | if id.find('-') == -1: 27 | raise Error('Wrong format of Env ID!') 28 | self.name, *ver = id.split('-') 29 | self.ver = '-'.join(ver) 30 | 31 | def make(self, **kwargs): 32 | if self.ver == 'wrap': 33 | # For wrapper env in Online Botzone 34 | from botzone.online.game import GameConfig, Game 35 | env = Game(GameConfig.fromName(self.name)) 36 | return env 37 | if self.entry_point is None: 38 | raise Error('No entry point for deprecated Env %s' % self.id) 39 | newkwargs = self.kwargs.copy() 40 | newkwargs.update(kwargs) 41 | if callable(self.entry_point): 42 | env = self.entry_point(**newkwargs) 43 | else: 44 | cls = load(self.entry_point) 45 | env = cls(**newkwargs) 46 | spec = deepcopy(self) 47 | spec.kwargs = newkwargs 48 | env.spec = spec 49 | return env 50 | 51 | def __repr__(self): 52 | return 'EnvSpec(%s)' % self.id 53 | 54 | class EnvRegistry: 55 | ''' 56 | Register an env by ID. 57 | ''' 58 | 59 | def __init__(self): 60 | self.env_specs = {} 61 | 62 | def register(self, id, **kwargs): 63 | if id in self.env_specs: 64 | raise Error('Cannot re-register id: %s' % id) 65 | self.env_specs[id] = EnvSpec(id, **kwargs) 66 | 67 | def make(self, id, **kwargs): 68 | if id not in self.env_specs: 69 | raise Error('Env id %s not found!' % id) 70 | spec = self.env_specs[id] 71 | return spec.make(**kwargs) 72 | 73 | def all(self): 74 | return self.env_specs.values() 75 | 76 | # Global registry 77 | registry = EnvRegistry() 78 | 79 | def register(id, **kwargs): 80 | return registry.register(id, **kwargs) 81 | 82 | def make(id, **kwargs): 83 | return registry.make(id, **kwargs) 84 | 85 | def all(): 86 | return registry.all() 87 | -------------------------------------------------------------------------------- /botzone/error.py: -------------------------------------------------------------------------------- 1 | class Error(Exception): 2 | pass 3 | 4 | class ResetNeeded(Error): 5 | ''' 6 | Raised when user tries to step an environment or an agent after an episode 7 | has finished. 8 | ''' 9 | pass 10 | 11 | class AlreadyClosed(Error): 12 | ''' 13 | Raised when user tries to operate an environment or an agent after calling 14 | close(). 15 | ''' 16 | pass 17 | 18 | class AgentsNeeded(Error): 19 | ''' 20 | Raised when user tries to reset or step an environment before specifying 21 | agents used. 22 | ''' 23 | pass 24 | 25 | class UnsupportedMode(Error): 26 | ''' 27 | Raised when user requests a rendering mode not supported by the environment. 28 | ''' 29 | pass -------------------------------------------------------------------------------- /botzone/online/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailab-pku/botzone-local/7c804286373f54ee1994d383cd2b3a1c7ed6b1cd/botzone/online/__init__.py -------------------------------------------------------------------------------- /botzone/online/api.py: -------------------------------------------------------------------------------- 1 | import getpass 2 | import os 3 | import requests 4 | import zipfile 5 | 6 | class BotzoneAPI: 7 | ''' 8 | Botzone API used. 9 | ''' 10 | 11 | cookies = None 12 | 13 | def login(): 14 | email = input('Input email:') 15 | password = getpass.getpass('Input password:') 16 | try: 17 | r = requests.post('https://botzone.org.cn/login', data = dict(email = email, password = password)) 18 | except: 19 | raise RuntimeError('Failed to connect to botzone.org!') 20 | if not r.ok: 21 | raise RuntimeError('Failed to connect to botzone.org, Reason: ' + r.reason) 22 | d = r.json() 23 | if not d['success']: 24 | print('Failed to log in to botzone.org, Reason:', d['message']) 25 | return False 26 | BotzoneAPI.cookies = r.cookies 27 | return True 28 | 29 | def get_bot(ver_id): 30 | try: 31 | r = requests.get('https://botzone.org.cn/mybots/detail/version/' + ver_id, cookies = BotzoneAPI.cookies) 32 | except: 33 | raise RuntimeError('Failed to connect to botzone.org!') 34 | if not r.ok: 35 | raise RuntimeError('Failed to connect to botzone.org, Reason: ' + r.reason) 36 | d = r.json() 37 | if not d['success']: 38 | raise RuntimeError('Failed to get bot %s, Reason: %s' % (ver_id, d['message'])) 39 | if d['bot'] is None: 40 | # mistakenly use bot_id as ver_id 41 | raise RuntimeError('Failed to get bot %s, please use ver_id instead of bot_id!' % ver_id) 42 | return d['bot'] 43 | 44 | def download_bot(bot_id, ver, target): 45 | try: 46 | r = requests.get('https://botzone.org.cn/mybots/viewsrc/%s/%d?download=true' % (bot_id, ver), cookies = BotzoneAPI.cookies) 47 | except: 48 | raise RuntimeError('Failed to connect to botzone.org!') 49 | if not r.ok: 50 | raise RuntimeError('Failed to connect to botzone.org, Reason: ' + r.reason) 51 | try: 52 | # Try to decode by json in case of failure 53 | d = r.json() 54 | except: 55 | # success 56 | with open(target, mode = 'wb') as f: 57 | f.write(r.content) 58 | return True 59 | if d['message'] == '你还没有登录,无法访问此页。': 60 | # allowing caller to request login 61 | return False 62 | raise RuntimeError('Failed to download bot, Reason: ' + d['message']) 63 | 64 | def download_userfile(user_id, path): 65 | try: 66 | r = requests.get('https://botzone.org.cn/downloaduserfiles/?uid=%s' % user_id, cookies = BotzoneAPI.cookies, stream = True) 67 | except: 68 | raise RuntimeError('Failed to connect to botzone.org!') 69 | if not r.ok: 70 | raise RuntimeError('Failed to connect to botzone.org, Reason: ' + r.reason) 71 | if r.url == 'https://botzone.org.cn/?msg=notlogin': 72 | # allowing caller to request login 73 | return False 74 | target = os.path.join(path, 'tmp.zip') 75 | print('Downloading...') 76 | cnt = 0 77 | with open(target, mode = 'wb') as f: 78 | for chunk in r.iter_content(chunk_size = 4096): 79 | f.write(chunk) 80 | cnt += len(chunk) 81 | print(cnt, 'bytes downloaded', end = '\r') 82 | print() 83 | if not zipfile.is_zipfile(target): 84 | os.remove(target) 85 | raise RuntimeError('Failed to download userfile, Reason: ' + r.json()['message']) 86 | with zipfile.ZipFile(target) as f: 87 | for item in f.namelist(): 88 | print('Extracting', item) 89 | f.extract(item, path) 90 | os.remove(target) 91 | return True -------------------------------------------------------------------------------- /botzone/online/bot/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore -------------------------------------------------------------------------------- /botzone/online/compiler.py: -------------------------------------------------------------------------------- 1 | config = dict( 2 | cpp = dict( 3 | suffix = 'cpp', 4 | images = ['botzone/env-full', 'botzone/env-cpp'], 5 | time_limit = 1000, 6 | sub_time_limit = 1000, 7 | memory_limit = 256, 8 | compile_command = 'g++ -D_BOTZONE_ONLINE -O2 -D_GLIBCXX_USE_CXX11_ABI=0 -Wall -mavx {code} -ljson -lpthread -o {target}', 9 | run_command = '{target}' 10 | ), 11 | cpp11 = dict( 12 | suffix = 'cpp', 13 | images = ['botzone/env-full', 'botzone/env-cpp'], 14 | time_limit = 1000, 15 | sub_time_limit = 1000, 16 | memory_limit = 256, 17 | compile_command = 'g++ -D_BOTZONE_ONLINE -D_GLIBCXX_USE_CXX11_ABI=0 -O2 -Wall -mavx -std=c++11 -x c++ {code} -ljson -lpthread -o {target}', 18 | run_command = '{target}' 19 | ), 20 | cpp17 = dict( 21 | suffix = 'cpp', 22 | images = ['botzone/env-full', 'botzone/env-cpp17a', 'botzone/env-cpp17'], 23 | time_limit = 1000, 24 | sub_time_limit = 1000, 25 | memory_limit = 256, 26 | compile_command = 'g++-7 -D_BOTZONE_ONLINE -D_GLIBCXX_USE_CXX11_ABI=0 -O2 -Wall -mavx -std=c++1z -x c++ {code} -ljson -lpthread -o {target}', 27 | run_command = '{target}' 28 | ), 29 | cpp17a = dict( 30 | suffix = 'cpp', 31 | images = ['botzone/env-full', 'botzone/env-cpp17a'], 32 | time_limit = 1000, 33 | sub_time_limit = 1000, 34 | memory_limit = 256, 35 | compile_command = 'g++-7 -D_BOTZONE_ONLINE -O2 -Wall -mavx -std=c++1z -I /usr/local/include/jsoncpp_cxx11abi -isystem /usr/local/include/tensorflow -isystem /usr/local/include/tensorflow/bazel-genfiles -isystem /usr/local/include/tensorflow/tensorflow/contrib/makefile/downloads -isystem /usr/local/include/tensorflow/tensorflow/contrib/makefile/downloads/eigen -isystem /usr/local/include/tensorflow/tensorflow/contrib/makefile/downloads/gemmlowp -isystem /usr/local/include/tensorflow/tensorflow/contrib/makefile/downloads/nsync/public -isystem /usr/local/include/tensorflow/tensorflow/contrib/makefile/gen/protobuf-host/include -isystem /usr/lib/libtorch/include -isystem /usr/lib/libtorch/include/torch/csrc/api/include -x c++ {code} -x none -rdynamic /usr/local/lib/tensorflow_cc/libtensorflow_cc.so -ldl -lpthread -lopenblas -ljsoncpp /usr/local/lib/tensorflow_cc/libprotobuf.a -lMahjongGB -Wl,-rpath,/usr/local/lib/tensorflow_cc -rdynamic /usr/lib/libtorch/lib/libtorch.so /usr/lib/libtorch/lib/libc10.so -Wl,--no-as-needed,/usr/lib/libtorch/lib/libtorch_cpu.so -Wl,--as-needed /usr/lib/libtorch/lib/libc10.so -lpthread -Wl,--no-as-needed,/usr/lib/libtorch/lib/libtorch.so -Wl,--as-needed -Wl,-rpath,/usr/lib/libtorch/lib -o {target}', 36 | run_command = '{target}' 37 | ), 38 | cppo0 = dict( 39 | suffix = 'cpp', 40 | images = ['botzone/env-full', 'botzone/env-cpp'], 41 | time_limit = 1000, 42 | sub_time_limit = 1000, 43 | memory_limit = 256, 44 | compile_command = 'g++ -D_BOTZONE_ONLINE -D_GLIBCXX_USE_CXX11_ABI=0 -x c++ -Wall -mavx {code} -ljson -lpthread -o {target}', 45 | run_command = '{target}' 46 | ), 47 | java = dict( 48 | suffix = 'java', 49 | images = ['botzone/env-full', 'botzone/env-java'], 50 | time_limit = 3000, 51 | sub_time_limit = 2000, 52 | memory_limit = 256, 53 | compile_command = 'javac -encoding utf-8 {code}', 54 | run_command = '/usr/bin/java Main' 55 | ), 56 | cs = dict( 57 | suffix = 'cs', 58 | images = ['botzone/env-full', 'botzone/env-cs'], 59 | time_limit = 6000, 60 | sub_time_limit = 4000, 61 | memory_limit = 256, 62 | compile_command = 'mcs -lib:/usr/lib/mono/Botzone -r:Newtonsoft.Json {code} -out:{target}', 63 | run_command = '/usr/bin/mono {target}' 64 | ), 65 | js = dict( 66 | suffix = 'js', 67 | images = ['botzone/env-full', 'botzone/env-js'], 68 | time_limit = 2000, 69 | sub_time_limit = 2000, 70 | memory_limit = 256, 71 | compile_command = None, 72 | run_command = '/usr/bin/nodejs {target}' 73 | ), 74 | py = dict( 75 | suffix = 'py', 76 | images = ['botzone/env-full', 'botzone/env-py'], 77 | time_limit = 6000, 78 | sub_time_limit = 4000, 79 | memory_limit = 256, 80 | compile_command = None, 81 | run_command = '/usr/bin/python {target}' 82 | ), 83 | py3 = dict( 84 | suffix = 'py', 85 | images = ['botzone/env-full', 'botzone/env-py3'], 86 | time_limit = 6000, 87 | sub_time_limit = 4000, 88 | memory_limit = 256, 89 | compile_command = None, 90 | run_command = '/usr/bin/python3 {target}' 91 | ), 92 | py36 = dict( 93 | suffix = 'py', 94 | images = ['botzone/env-full', 'botzone/env-py36'], 95 | time_limit = 6000, 96 | sub_time_limit = 4000, 97 | memory_limit = 256, 98 | compile_command = None, 99 | run_command = '/usr/bin/python3.6 {target}' 100 | ), 101 | pas = dict( 102 | suffix = 'pas', 103 | images = ['botzone/env-full', 'botzone/env-pas'], 104 | time_limit = 1000, 105 | sub_time_limit = 1000, 106 | memory_limit = 256, 107 | compile_command = 'fpc {code} -o{target}', 108 | run_command = '{target}' 109 | ), 110 | elfbin = dict( 111 | suffix = 'elfbin', 112 | images = ['botzone/env-full', 'botzone/env-cpp17a', 'botzone/env-cpp17', 'botzone/env-cpp'], 113 | time_limit = 1000, 114 | sub_time_limit = 1000, 115 | memory_limit = 256, 116 | compile_command = None, 117 | run_command = '{target}' 118 | ) 119 | ) 120 | 121 | from collections import defaultdict 122 | images = defaultdict(list) 123 | for ext in config: 124 | for image in config[ext]['images']: 125 | images[image].append(ext) 126 | -------------------------------------------------------------------------------- /botzone/online/game/Amazons.cpp11: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailab-pku/botzone-local/7c804286373f54ee1994d383cd2b3a1c7ed6b1cd/botzone/online/game/Amazons.cpp11 -------------------------------------------------------------------------------- /botzone/online/game/ChineseStandardMahjong.elfbin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailab-pku/botzone-local/7c804286373f54ee1994d383cd2b3a1c7ed6b1cd/botzone/online/game/ChineseStandardMahjong.elfbin -------------------------------------------------------------------------------- /botzone/online/game/Gomoku.js: -------------------------------------------------------------------------------- 1 | // 2 | // Leedy 3 | // 2014-10-7 4 | // 5 | 6 | var SIZE = 15; // 棋盘大小 15*15 7 | var Grid = new Array(SIZE); 8 | for (var i = Grid.length - 1; i >= 0; i--) { 9 | Grid[i] = new Array(SIZE); 10 | } 11 | var inGrid = function(x, y) { 12 | return x >= 0 && x < SIZE && y >= 0 && y < SIZE; 13 | }; 14 | var gridFull = function() { 15 | for (var i = 0; i < SIZE; i++) { 16 | for (var j = 0; j < SIZE; j++) { 17 | if (Grid[i][j] === undefined) { 18 | return false; 19 | } 20 | } 21 | } 22 | return true; 23 | }; 24 | var placeAt = function(color, x, y) { 25 | if (inGrid(x, y) && Grid[x][y] === undefined) { 26 | Grid[x][y] = color; 27 | return true; 28 | } else { 29 | return false; 30 | } 31 | }; 32 | var winAfterPlaceAt = function(color, x, y) { 33 | var checkTowards = function(xx, yy) { 34 | var x_, y_; 35 | for (var i = 0; i < 5; i++) { 36 | x_ = x + xx*i; 37 | y_ = y + yy*i; 38 | if (!inGrid(x_, y_) || Grid[x_][y_] !== color) { 39 | break; 40 | } 41 | } 42 | for (var j = 0; j < 5; j++) { 43 | x_ = x - xx*j; 44 | y_ = y - yy*j; 45 | if (!inGrid(x_, y_) || Grid[x_][y_] !== color) { 46 | break; 47 | } 48 | } 49 | return i + j - 1; 50 | }; 51 | return checkTowards(1, 0) >= 5 || checkTowards(0, 1) >= 5 || checkTowards(1, 1) >= 5 || checkTowards(1, -1) >= 5; 52 | }; 53 | 54 | process.stdin.resume(); 55 | process.stdin.setEncoding('utf8'); 56 | 57 | var fullInput = ""; 58 | process.stdin.on('data', function(chunk) { 59 | fullInput += chunk; 60 | }); 61 | 62 | process.stdin.on('end', function() { 63 | var input = JSON.parse(fullInput).log; // Array 64 | var output; // undefined 65 | if (input.length === 0) { 66 | output = { 67 | command: "request", 68 | content: { 69 | 0: {x: -1, y: -1} 70 | } 71 | }; 72 | } else { 73 | for (var i = 1; i < input.length; i += 2) { // length = 2, 4, 6, 8 ... 74 | var color = (i-1) / 2 % 2; // 1 => 0, 3 => 1, 5 => 0 ..., 0: black, 1: white 75 | var response = input[i][color].response || input[i][color].content || {}; 76 | var isLast = (i === (input.length - 1)); 77 | if (parseInt(response.x) !== response.x || parseInt(response.y) !== response.y) { 78 | output = { 79 | display: { 80 | winner: +!color, 81 | error: "INVALID INPUT", 82 | error_data: response 83 | }, 84 | command: "finish", 85 | content: { 86 | 0: color * 2, 87 | 1: !color * 2 88 | } 89 | }; 90 | break; 91 | } 92 | if (placeAt(color, response.x, response.y)) { 93 | if (isLast) { 94 | output = { 95 | display: { // 用于实时对战 96 | color: color, 97 | x: response.x, 98 | y: response.y 99 | } 100 | }; 101 | if (winAfterPlaceAt(color, response.x, response.y)) { 102 | output.command = "finish"; 103 | output.display.winner = color; 104 | output.content = { 105 | 0: !color * 2, 106 | 1: color * 2 107 | }; 108 | } else if (gridFull()) { 109 | output.command = "finish"; 110 | output.content = { 0: 1, 1: 1 }; 111 | } else { 112 | output.command = "request"; 113 | output.content = {}; 114 | output.content[+!color] = { 115 | x: response.x, 116 | y: response.y 117 | }; 118 | } 119 | } 120 | } else { // invalid input 121 | output = { 122 | display: { 123 | winner: +!color, 124 | error: "INVALID MOVE", 125 | error_data: response 126 | }, 127 | command: "finish", 128 | content: { 129 | 0: color * 2, 130 | 1: !color * 2 131 | } 132 | }; 133 | break; 134 | } 135 | } 136 | } 137 | console.log(JSON.stringify(output)); 138 | }); 139 | 140 | /********************************** TEST ********************************** 141 | 142 | console.log(inGrid(1, 1) === true); 143 | console.log(inGrid(-1, 1) === false); 144 | placeAt(0, 0, 1); 145 | placeAt(0, 0, 3); 146 | placeAt(0, 0, 4); 147 | placeAt(0, 0, 5); 148 | console.log(winAfterPlaceAt(0, 0, 5) === false); 149 | placeAt(0, 0, 2); 150 | console.log(winAfterPlaceAt(0, 0, 2) === true); 151 | 152 | ************************************ END **********************************/ 153 | -------------------------------------------------------------------------------- /botzone/online/game/Kingz.cpp11: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailab-pku/botzone-local/7c804286373f54ee1994d383cd2b3a1c7ed6b1cd/botzone/online/game/Kingz.cpp11 -------------------------------------------------------------------------------- /botzone/online/game/Minesweeper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "jsoncpp/json.h" 7 | 8 | #define MAX_WIDTH 80 9 | #define MAX_HEIGHT 40 10 | 11 | using namespace std; 12 | int fieldHeight, fieldWidth, mineCount, seed, unrevealedCount, nonActiveMineCount; 13 | int mineField[MAX_HEIGHT][MAX_WIDTH]; 14 | // 0-8:数字 15 | // 9:雷 16 | bool revealed[MAX_HEIGHT][MAX_WIDTH]; 17 | 18 | // 输出用 19 | int changedCount = 0; 20 | bool isLast = false; 21 | Json::Value currChangedPoses; 22 | 23 | void InitializeField() 24 | { 25 | for (int currMine = 0; currMine < mineCount;) 26 | { 27 | int row = rand() % fieldHeight, col = rand() % fieldWidth; 28 | if (mineField[row][col] != 9) 29 | { 30 | mineField[row][col] = 9; 31 | currMine++; 32 | } 33 | } 34 | 35 | for (int row = 0; row < fieldHeight; row++) 36 | for (int col = 0; col < fieldWidth; col++) 37 | if (mineField[row][col] != 9) 38 | { 39 | int surroundingMineCount = 0; 40 | for (int dr = -1; dr < 2; dr++) 41 | for (int dc = -1; dc < 2; dc++) 42 | if (row + dr >= 0 && row + dr < fieldHeight && col + dc >= 0 && col + dc < fieldWidth && 43 | (dr != dc || dr != 0) && mineField[row + dr][col + dc] == 9) 44 | surroundingMineCount++; 45 | mineField[row][col] = surroundingMineCount; 46 | } 47 | } 48 | 49 | void ReadSavedField(string field) 50 | { 51 | for (int i = 0; i < field.size(); i++) 52 | { 53 | mineField[i / fieldWidth][i % fieldWidth] = field[i] - '0'; 54 | } 55 | } 56 | 57 | Json::Value DisplayField() 58 | { 59 | return currChangedPoses; 60 | } 61 | 62 | string SaveField() 63 | { 64 | char result[MAX_HEIGHT * MAX_WIDTH + 1]; 65 | for (int row = 0; row < fieldHeight; row++) 66 | for (int col = 0; col < fieldWidth; col++) 67 | result[row * fieldWidth + col] = mineField[row][col] + '0'; 68 | result[fieldHeight * fieldWidth] = 0; 69 | return result; 70 | } 71 | 72 | bool ProcClick(int row, int col) 73 | { 74 | if (isLast && !revealed[row][col]) 75 | { 76 | currChangedPoses[changedCount]["val"] = mineField[row][col]; 77 | currChangedPoses[changedCount]["row"] = row; 78 | currChangedPoses[changedCount++]["col"] = col; 79 | } 80 | revealed[row][col] = true; 81 | unrevealedCount--; 82 | if (mineField[row][col] == 9) 83 | return false; 84 | if (mineField[row][col] == 0) 85 | { 86 | for (int dr = -1; dr < 2; dr++) 87 | for (int dc = -1; dc < 2; dc++) 88 | if (row + dr >= 0 && row + dr < fieldHeight && col + dc >= 0 && col + dc < fieldWidth && 89 | (dr != dc || dr != 0) && !revealed[row + dr][col + dc]) 90 | ProcClick(row + dr, col + dc); 91 | } 92 | return true; 93 | } 94 | 95 | int main() 96 | { 97 | string str; 98 | getline(cin, str); 99 | Json::Reader reader; 100 | Json::Value input, output, temp, initdata; 101 | reader.parse(str, input); 102 | 103 | initdata = input["initdata"]; 104 | if (initdata.isString()) 105 | reader.parse(initdata.asString(), initdata); 106 | if (initdata.isString()) 107 | reader.parse("{}", initdata); 108 | 109 | // 按照初始化参数进行初始化 110 | temp = initdata["width"]; 111 | if (temp.isInt()) 112 | { 113 | fieldWidth = temp.asInt(); 114 | if (fieldWidth <= 10 || fieldWidth > 100) 115 | fieldWidth = 40; 116 | } 117 | else 118 | fieldWidth = 30; 119 | 120 | output["initdata"]["width"] = fieldWidth; 121 | 122 | temp = initdata["height"]; 123 | if (temp.isInt()) 124 | { 125 | fieldHeight = temp.asInt(); 126 | if (fieldHeight <= 10 || fieldHeight > 60) 127 | fieldHeight = 20; 128 | } 129 | else 130 | fieldHeight = 20; 131 | 132 | output["initdata"]["height"] = fieldHeight; 133 | 134 | unrevealedCount = fieldHeight * fieldWidth; 135 | 136 | temp = initdata["minecount"]; 137 | if (temp.isInt()) 138 | { 139 | nonActiveMineCount = mineCount = temp.asInt(); 140 | if (mineCount <= 10 || mineCount > 100) 141 | mineCount = 40; 142 | } 143 | else 144 | nonActiveMineCount = mineCount = 80; 145 | 146 | output["initdata"]["minecount"] = mineCount; 147 | 148 | temp = initdata["seed"]; 149 | if (temp.isInt()) 150 | { 151 | int seed = temp.asInt(); 152 | srand(seed); 153 | output["initdata"]["seed"] = seed; 154 | } 155 | else 156 | { 157 | int seed = time(0); 158 | srand(seed); 159 | output["initdata"]["seed"] = seed; 160 | } 161 | 162 | temp = initdata["field"]; 163 | if (temp.isString()) 164 | { 165 | string field = temp.asString(); 166 | output["initdata"]["field"] = field; 167 | ReadSavedField(field); 168 | } 169 | else 170 | { 171 | InitializeField(); 172 | output["initdata"]["field"] = SaveField(); 173 | } 174 | 175 | temp = initdata["firstclick"]; 176 | if (!temp.isNull()) 177 | { 178 | ProcClick(temp["row"].asInt(), temp["col"].asInt()); 179 | } 180 | 181 | input = input["log"]; 182 | 183 | // 根据历史输入恢复状态 184 | bool gameEnd = false; 185 | if (input.size() == 0) 186 | { 187 | temp = initdata["skipfirst"]; 188 | if (temp.isBool() && temp.asBool()) 189 | { 190 | // 找出一个保证没有雷的点 191 | int safeCol, safeRow; 192 | for (safeRow = 0; safeRow < fieldHeight; safeRow++) 193 | for (safeCol = 0; safeCol < fieldWidth; safeCol++) 194 | { 195 | if (mineField[safeRow][safeCol] == 0) 196 | goto FoundSafePoint; 197 | } 198 | do 199 | { 200 | safeCol = rand() % fieldWidth; 201 | safeRow = rand() % fieldHeight; 202 | } while (mineField[safeRow][safeCol] == 9); 203 | FoundSafePoint: 204 | isLast = true; 205 | ProcClick(safeRow, safeCol); 206 | output["initdata"]["firstclick"]["row"] = safeRow; 207 | output["initdata"]["firstclick"]["col"] = safeCol; 208 | } 209 | output["display"]["status"] = DisplayField(); 210 | output["command"] = "request"; 211 | output["content"]["0"]["height"] = fieldHeight; 212 | output["content"]["0"]["width"] = fieldWidth; 213 | output["content"]["0"]["minecount"] = mineCount; 214 | output["content"]["0"]["changed"] = DisplayField(); 215 | } 216 | else 217 | { 218 | output.removeMember("initdata"); 219 | for (int i = 1; i < input.size(); i += 2) 220 | { 221 | isLast = i == input.size() - 1; 222 | Json::Value content; 223 | Json::Value display; 224 | Json::Value answer = input[i]["0"]["response"].isNull() ? input[i]["0"]["content"] : input[i]["0"]["response"]; 225 | if (((answer.isString() && 226 | reader.parse(answer.asString(), content)) || 227 | (answer.isObject() && 228 | (content = answer, true))) && 229 | content["row"].isInt() && content["col"].isInt()) // 保证输入格式正确 230 | { 231 | int currCol = content["col"].asInt(); 232 | int currRow = content["row"].asInt(); 233 | if (currCol >= 0 && currCol < fieldWidth && currRow >= 0 && currRow < fieldHeight && !revealed[currRow][currCol]) // 合法点击 234 | { 235 | if (!ProcClick(currRow, currCol)) 236 | { 237 | nonActiveMineCount--; 238 | } 239 | 240 | if (nonActiveMineCount == unrevealedCount) 241 | { 242 | // 游戏结束 243 | output["display"]["msg"] = "FINISH"; 244 | output["command"] = "finish"; 245 | output["content"]["0"] = nonActiveMineCount * 100.0 / mineCount; 246 | gameEnd = true; 247 | } 248 | } 249 | else 250 | { 251 | output["display"]["msg"] = "INVALIDMOVE"; 252 | output["command"] = "finish"; // 判输 253 | output["content"]["0"] = 0; 254 | gameEnd = true; 255 | } 256 | } 257 | else 258 | { 259 | output["display"]["msg"] = "INVALIDMOVE"; 260 | output["command"] = "finish"; // 判输 261 | output["content"]["0"] = 0; 262 | gameEnd = true; 263 | } 264 | } 265 | } 266 | output["display"]["status"] = DisplayField(); 267 | if (!gameEnd) 268 | { 269 | output["command"] = "request"; 270 | output["content"]["0"]["height"] = fieldHeight; 271 | output["content"]["0"]["width"] = fieldWidth; 272 | output["content"]["0"]["minecount"] = mineCount; 273 | output["content"]["0"]["changed"] = DisplayField(); 274 | } 275 | Json::FastWriter writer; 276 | cout << writer.write(output) << endl; 277 | } 278 | -------------------------------------------------------------------------------- /botzone/online/game/NoGo.cpp17: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "jsoncpp/json.h" 6 | using namespace std; 7 | 8 | // 注意黑方为0号玩家 9 | int currBotColor; // 正在等待输出结果的BOT 10 | int gridInfo[9][9] = { 0 }; // 先x后y 11 | int blackPieceCount = 0, whitePieceCount = 0; 12 | string blackName = "", whiteName = ""; 13 | 14 | bool dfs_air_visit[9][9]; 15 | const int cx[] = { -1,0,1,0 }; 16 | const int cy[] = { 0,-1,0,1 }; 17 | 18 | bool inBorder(int x, int y) { return x >= 0 && y >= 0 && x<9 && y<9; } 19 | 20 | //true: has air 21 | bool dfs_air(int fx, int fy) 22 | { 23 | dfs_air_visit[fx][fy] = true; 24 | bool flag = false; 25 | for (int dir = 0; dir < 4; dir++) 26 | { 27 | int dx = fx + cx[dir], dy = fy + cy[dir]; 28 | if (inBorder(dx, dy)) 29 | { 30 | if (gridInfo[dx][dy] == 0) 31 | flag = true; 32 | if (gridInfo[dx][dy] == gridInfo[fx][fy] && !dfs_air_visit[dx][dy]) 33 | if (dfs_air(dx, dy)) 34 | flag = true; 35 | } 36 | } 37 | return flag; 38 | } 39 | 40 | //true: available 41 | bool judgeAvailable(int fx, int fy, int col) 42 | { 43 | gridInfo[fx][fy] = col; 44 | memset(dfs_air_visit, 0, sizeof(dfs_air_visit)); 45 | if (!dfs_air(fx, fy)) 46 | { 47 | gridInfo[fx][fy] = 0; 48 | return false; 49 | } 50 | for (int dir = 0; dir < 4; dir++) 51 | { 52 | int dx = fx + cx[dir], dy = fy + cy[dir]; 53 | if (inBorder(dx, dy)) 54 | { 55 | if (gridInfo[dx][dy] && !dfs_air_visit[dx][dy]) 56 | if (!dfs_air(dx, dy)) 57 | { 58 | gridInfo[fx][fy] = 0; 59 | return false; 60 | } 61 | } 62 | } 63 | gridInfo[fx][fy] = 0; 64 | return true; 65 | } 66 | 67 | //true: game end 68 | int CheckIfHasValidMove(int col) 69 | { 70 | for (int i = 0; i < 9; i++) 71 | for (int j = 0; j < 9; j++) 72 | if (gridInfo[i][j] == 0 && judgeAvailable(i, j, col)) 73 | return false; 74 | return true; 75 | } 76 | 77 | bool ProcStep(int x, int y, int color, bool checkOnly = false) 78 | { 79 | if (!inBorder(x, y) || gridInfo[x][y]) 80 | return false; 81 | if (!judgeAvailable(x, y, color)) 82 | return false; 83 | if (!checkOnly) 84 | gridInfo[x][y] = color; 85 | return true; 86 | } 87 | 88 | int main() 89 | { 90 | string str; 91 | getline(cin, str); 92 | Json::Reader reader; 93 | Json::Value input, output; 94 | reader.parse(str, input); 95 | input = input["log"]; 96 | 97 | currBotColor = 1; // 先手为黑 98 | if (input.size() == 0) 99 | { 100 | output["display"] = ""; 101 | output["command"] = "request"; 102 | output["content"]["0"]["x"] = -1; 103 | output["content"]["0"]["y"] = -1; 104 | } 105 | else 106 | { 107 | for (int i = 1; i < input.size(); i += 2) 108 | { 109 | bool isLast = i == input.size() - 1; 110 | Json::Value content; 111 | Json::Value display; 112 | if (currBotColor == 1) // 0号玩家 / 黑方 113 | { 114 | Json::Value answer = input[i]["0"]["response"].isNull() ? input[i]["0"]["content"] : input[i]["0"]["response"]; 115 | if (((answer.isString() && 116 | reader.parse(answer.asString(), content)) || 117 | (answer.isObject() && 118 | (content = answer, true))) && 119 | content["x"].isInt() && content["y"].isInt()) // 保证输入格式正确 120 | { 121 | int currX = content["x"].asInt(); 122 | int currY = content["y"].asInt(); 123 | if (!ProcStep(currX, currY, currBotColor) && isLast) // 不合法棋步! 124 | { 125 | stringstream ss; 126 | ss << "INVALID_MOVE (" << currX << "," << currY << ")"; 127 | if (inBorder(currX, currY) && gridInfo[currX][currY]) 128 | ss << " is not empty"; 129 | else 130 | ss << " is forbidden position"; 131 | string s; getline(ss, s); 132 | output["display"]["err"] = s; 133 | output["display"]["winner"] = 1; 134 | output["command"] = "finish"; // 判输 135 | output["content"]["0"] = 0; 136 | output["content"]["1"] = 1; 137 | } 138 | else if (isLast) // 正常棋步 139 | { 140 | output["display"]["x"] = currX; 141 | output["display"]["y"] = currY; 142 | output["display"]["color"] = 0; 143 | if (CheckIfHasValidMove(-currBotColor)) // 游戏结束 144 | { 145 | output["display"]["winner"] = 0; 146 | output["command"] = "finish"; 147 | output["content"]["0"] = 1; 148 | output["content"]["1"] = 0; 149 | } 150 | else 151 | { 152 | output["command"] = "request"; 153 | output["content"]["1"]["x"] = currX; 154 | output["content"]["1"]["y"] = currY; 155 | } 156 | } 157 | } 158 | else if (isLast) 159 | { 160 | output["display"]["err"] = "INVALID_INPUT_VERDICT_" + input[i]["0"]["verdict"].asString(); 161 | output["display"]["winner"] = 1; 162 | output["command"] = "finish"; // 判输 163 | output["content"]["0"] = 0; 164 | output["content"]["1"] = 1; 165 | } 166 | } 167 | else 168 | { 169 | Json::Value answer = input[i]["1"]["response"].isNull() ? input[i]["1"]["content"] : input[i]["1"]["response"]; 170 | if (((answer.isString() && 171 | reader.parse(answer.asString(), content)) || 172 | (answer.isObject() && 173 | (content = answer, true))) && 174 | content["x"].isInt() && content["y"].isInt()) // 保证输入格式正确 175 | { 176 | int currX = content["x"].asInt(); 177 | int currY = content["y"].asInt(); 178 | if (!ProcStep(currX, currY, currBotColor) && isLast) // 不合法棋步! 179 | { 180 | stringstream ss; 181 | ss << "INVALID_MOVE (" << currX << "," << currY << ")"; 182 | if (inBorder(currX, currY) && gridInfo[currX][currY]) 183 | ss << " is not empty"; 184 | else 185 | ss << " is forbidden position"; 186 | string s; getline(ss, s); 187 | output["display"]["err"] = s; 188 | output["display"]["winner"] = 0; 189 | output["command"] = "finish"; // 判输 190 | output["content"]["0"] = 1; 191 | output["content"]["1"] = 0; 192 | } 193 | else if (isLast) // 正常棋步 194 | { 195 | output["display"]["x"] = currX; 196 | output["display"]["y"] = currY; 197 | output["display"]["color"] = 1; 198 | if (CheckIfHasValidMove(-currBotColor)) // 游戏结束 199 | { 200 | output["display"]["winner"] = 1; 201 | output["command"] = "finish"; 202 | output["content"]["0"] = 0; 203 | output["content"]["1"] = 1; 204 | } 205 | else 206 | { 207 | output["command"] = "request"; 208 | output["content"]["0"]["x"] = currX; 209 | output["content"]["0"]["y"] = currY; 210 | } 211 | } 212 | } 213 | else if (isLast) 214 | { 215 | output["display"]["err"] = "INVALID_INPUT_VERDICT_" + input[i]["1"]["verdict"].asString(); 216 | output["display"]["winner"] = 0; 217 | output["command"] = "finish"; // 判输 218 | output["content"]["0"] = 1; 219 | output["content"]["1"] = 0; 220 | } 221 | } 222 | currBotColor *= -1; 223 | } 224 | } 225 | Json::FastWriter writer; 226 | cout << writer.write(output) << endl; 227 | return 0; 228 | } -------------------------------------------------------------------------------- /botzone/online/game/Pacman.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailab-pku/botzone-local/7c804286373f54ee1994d383cd2b3a1c7ed6b1cd/botzone/online/game/Pacman.cpp -------------------------------------------------------------------------------- /botzone/online/game/Pacman2.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ailab-pku/botzone-local/7c804286373f54ee1994d383cd2b3a1c7ed6b1cd/botzone/online/game/Pacman2.cpp -------------------------------------------------------------------------------- /botzone/online/game/Renju.js: -------------------------------------------------------------------------------- 1 | // 2 | // Leedy 3 | // 2014-10-7 4 | // 5 | 6 | var SIZE = 15; // 棋盘大小 15*15 7 | var Grid = new Array(SIZE); 8 | for (var i = Grid.length - 1; i >= 0; i--) { 9 | Grid[i] = new Array(SIZE); 10 | } 11 | var inGrid = function(x, y) { 12 | return x >= 0 && x < SIZE && y >= 0 && y < SIZE; 13 | }; 14 | var gridFull = function() { 15 | for (var i = 0; i < SIZE; i++) { 16 | for (var j = 0; j < SIZE; j++) { 17 | if (Grid[i][j] === undefined) { 18 | return false; 19 | } 20 | } 21 | } 22 | return true; 23 | }; 24 | var reverseGrid = function() { 25 | for (var i = 0; i < SIZE; i++) { 26 | for (var j = 0; j < SIZE; j++) { 27 | if (Grid[i][j] !== undefined) { 28 | Grid[i][j] = +!Grid[i][j]; 29 | } 30 | } 31 | } 32 | }; 33 | var placeAt = function(color, x, y) { 34 | if (inGrid(x, y) && Grid[x][y] === undefined) { 35 | Grid[x][y] = color; 36 | return true; 37 | } else { 38 | return false; 39 | } 40 | }; 41 | var winAfterPlaceAt = function(color, x, y) { 42 | var checkTowards = function(xx, yy) { 43 | var x_, y_; 44 | for (var i = 0; i < 5; i++) { 45 | x_ = x + xx*i; 46 | y_ = y + yy*i; 47 | if (!inGrid(x_, y_) || Grid[x_][y_] !== color) { 48 | break; 49 | } 50 | } 51 | for (var j = 0; j < 5; j++) { 52 | x_ = x - xx*j; 53 | y_ = y - yy*j; 54 | if (!inGrid(x_, y_) || Grid[x_][y_] !== color) { 55 | break; 56 | } 57 | } 58 | return i + j - 1; 59 | }; 60 | return checkTowards(1, 0) >= 5 || checkTowards(0, 1) >= 5 || checkTowards(1, 1) >= 5 || checkTowards(1, -1) >= 5; 61 | }; 62 | 63 | process.stdin.resume(); 64 | process.stdin.setEncoding('utf8'); 65 | 66 | var fullInput = ""; 67 | process.stdin.on('data', function(chunk) { 68 | fullInput += chunk; 69 | }); 70 | 71 | process.stdin.on('end', function() { 72 | var input = JSON.parse(fullInput).log; // Array 73 | var output; // undefined 74 | if (input.length === 0) { 75 | output = { 76 | command: "request", 77 | content: { 78 | 0: {x: -1, y: -1} 79 | } 80 | }; 81 | } else { 82 | for (var i = 1; i < input.length; i += 2) { // length = 2, 4, 6, 8 ... 83 | var color = input[i][0] ? 0 : 1; // 0: black, 1: white 84 | var response = input[i][color].response || input[i][color].content || {}; 85 | if (response === undefined) { 86 | output = { 87 | display: { 88 | winner: +!color, 89 | error: input[i][color].verdict 90 | }, 91 | command: "finish", 92 | content: { 93 | 0: color * 2, 94 | 1: !color * 2 95 | } 96 | }; 97 | break; 98 | } 99 | var isLast = (i === (input.length - 1)); 100 | if (parseInt(response.x) !== response.x || parseInt(response.y) !== response.y) { 101 | output = { 102 | display: { 103 | winner: +!color, 104 | error: "INVALID INPUT", 105 | error_data: response 106 | }, 107 | command: "finish", 108 | content: { 109 | 0: color * 2, 110 | 1: !color * 2 111 | } 112 | }; 113 | break; 114 | } 115 | if (placeAt(color, response.x, response.y)) { 116 | if (isLast) { 117 | output = { 118 | display: { // 用于观战 119 | color: color, 120 | x: response.x, 121 | y: response.y 122 | } 123 | }; 124 | if (winAfterPlaceAt(color, response.x, response.y)) { 125 | output.command = "finish"; 126 | output.display.winner = color; 127 | output.content = { 128 | 0: !color * 2, 129 | 1: color * 2 130 | }; 131 | } else if (gridFull()) { 132 | output.command = "finish"; 133 | output.display.winner = "none"; 134 | output.content = { 0: 1, 1: 1 }; 135 | } else { 136 | output.command = "request"; 137 | output.content = {}; 138 | output.content[+!color] = { 139 | x: response.x, 140 | y: response.y 141 | }; 142 | } 143 | } 144 | } else if (i === 7) { // 白方换手 145 | reverseGrid(); 146 | if (isLast) { 147 | output = { 148 | display: { 149 | msg: "exchange" 150 | }, 151 | command: "request", 152 | content: {} 153 | }; 154 | output.content[+!color] = { x: -1, y: -1 } 155 | } 156 | } else { // invalid input 157 | output = { 158 | display: { 159 | winner: +!color, 160 | error: "INVALID MOVE", 161 | error_data: response 162 | }, 163 | command: "finish", 164 | content: { 165 | 0: color * 2, 166 | 1: !color * 2 167 | } 168 | }; 169 | break; 170 | } 171 | } 172 | } 173 | console.log(JSON.stringify(output)); 174 | }); 175 | 176 | /********************************** TEST ********************************** 177 | 178 | console.log(inGrid(1, 1) === true); 179 | console.log(inGrid(-1, 1) === false); 180 | placeAt(0, 0, 1); 181 | placeAt(0, 0, 3); 182 | placeAt(0, 0, 4); 183 | placeAt(0, 0, 5); 184 | console.log(winAfterPlaceAt(0, 0, 5) === false); 185 | placeAt(0, 0, 2); 186 | console.log(winAfterPlaceAt(0, 0, 2) === true); 187 | 188 | ************************************ END **********************************/ 189 | -------------------------------------------------------------------------------- /botzone/online/game/Reversi.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "jsoncpp/json.h" 5 | using namespace std; 6 | 7 | 8 | // 注意黑方为0号玩家 9 | int currBotColor; // 正在等待输出结果的BOT 10 | int gridInfo[8][8] = { 0 }; // 先x后y 11 | int blackPieceCount = 2, whitePieceCount = 2; 12 | string blackName = "", whiteName = ""; 13 | 14 | static bool MoveStep(int &x, int &y, int Direction) 15 | { 16 | if (Direction == 0 || Direction == 6 || Direction == 7) 17 | x++; 18 | if (Direction == 0 || Direction == 1 || Direction == 2) 19 | y++; 20 | if (Direction == 2 || Direction == 3 || Direction == 4) 21 | x--; 22 | if (Direction == 4 || Direction == 5 || Direction == 6) 23 | y--; 24 | if (x < 0 || x > 7 || y < 0 || y > 7) 25 | return false; 26 | return true; 27 | } 28 | 29 | bool ProcStep(int xPos, int yPos, int color, bool checkOnly = false) 30 | { 31 | if (xPos == -1) 32 | return true; 33 | if (xPos < 0 || xPos > 7 || yPos < 0 || yPos > 7) 34 | return false; 35 | int effectivePoints[8][2]; 36 | int dir, x, y, currCount; 37 | bool isValidMove = false; 38 | if (gridInfo[xPos][yPos] != 0) 39 | return false; 40 | for (dir = 0; dir < 8; dir++) 41 | { 42 | x = xPos; 43 | y = yPos; 44 | currCount = 0; 45 | while (1) 46 | { 47 | if (!MoveStep(x, y, dir)) 48 | { 49 | currCount = 0; 50 | break; 51 | } 52 | if (gridInfo[x][y] == -color) 53 | { 54 | currCount++; 55 | effectivePoints[currCount][0] = x; 56 | effectivePoints[currCount][1] = y; 57 | } 58 | else if (gridInfo[x][y] == 0) 59 | { 60 | currCount = 0; 61 | break; 62 | } 63 | else 64 | { 65 | break; 66 | } 67 | } 68 | if (currCount != 0) 69 | { 70 | isValidMove = true; 71 | if (checkOnly) 72 | return true; 73 | if (color == 1) 74 | { 75 | blackPieceCount += currCount; 76 | whitePieceCount -= currCount; 77 | } 78 | else 79 | { 80 | whitePieceCount += currCount; 81 | blackPieceCount -= currCount; 82 | } 83 | while (currCount > 0) 84 | { 85 | x = effectivePoints[currCount][0]; 86 | y = effectivePoints[currCount][1]; 87 | gridInfo[x][y] *= -1; 88 | currCount--; 89 | } 90 | } 91 | } 92 | if (isValidMove) 93 | { 94 | gridInfo[xPos][yPos] = color; 95 | if (color == 1) 96 | blackPieceCount++; 97 | else 98 | whitePieceCount++; 99 | return true; 100 | } 101 | else 102 | return false; 103 | } 104 | 105 | bool CheckIfHasValidMove(int color) 106 | { 107 | int x, y; 108 | for (y = 0; y < 8; y++) 109 | for (x = 0; x < 8; x++) 110 | if (ProcStep(x, y, color, true)) 111 | return true; 112 | return false; 113 | } 114 | 115 | int main() 116 | { 117 | string str; 118 | getline(cin, str); 119 | Json::Reader reader; 120 | Json::Value input, output; 121 | reader.parse(str, input); 122 | input = input["log"]; 123 | 124 | 125 | gridInfo[3][4] = gridInfo[4][3] = 1; //|白|黑| 126 | gridInfo[3][3] = gridInfo[4][4] = -1; //|黑|白| 127 | currBotColor = 1; // 先手为黑 128 | if (input.size() == 0) 129 | { 130 | output["display"] = ""; 131 | output["command"] = "request"; 132 | output["content"]["0"]["x"] = -1; 133 | output["content"]["0"]["y"] = -1; 134 | } 135 | else 136 | { 137 | for (int i = 1; i < input.size(); i += 2) 138 | { 139 | bool isLast = i == input.size() - 1; 140 | Json::Value content; 141 | Json::Value display; 142 | if (currBotColor == 1) // 0号玩家 / 黑方 143 | { 144 | Json::Value answer = input[i]["0"]["response"].isNull() ? input[i]["0"]["content"] : input[i]["0"]["response"]; 145 | if (((answer.isString() && 146 | reader.parse(answer.asString(), content)) || 147 | (answer.isObject() && 148 | (content = answer, true))) && 149 | content["x"].isInt() && content["y"].isInt()) // 保证输入格式正确 150 | { 151 | int currX = content["x"].asInt(); 152 | int currY = content["y"].asInt(); 153 | if (currX == -1 && isLast) // bot选择PASS 154 | { 155 | if (!CheckIfHasValidMove(currBotColor)) // 他应该PASS 156 | { 157 | output["display"]["x"] = -1; 158 | output["display"]["y"] = -1; 159 | output["command"] = "request"; 160 | output["content"]["1"]["x"] = -1; 161 | output["content"]["1"]["y"] = -1; 162 | } 163 | else // 他不应该PASS 164 | { 165 | // Todo: Bug here-- 166 | output["display"]["err"] = "HE_THOUGHT_THAT_HE_COULDNOT_MOVE_BUT_IN_FACT_HE_CAN"; 167 | output["display"]["winner"] = 1; 168 | output["command"] = "finish"; // 判输 169 | output["content"]["0"] = 0; 170 | output["content"]["1"] = 2; 171 | } 172 | } 173 | else if (!ProcStep(currX, currY, currBotColor) && isLast) // 不合法棋步! 174 | { 175 | output["display"]["err"] = "INVALID_MOVE"; 176 | output["display"]["winner"] = 1; 177 | output["command"] = "finish"; // 判输 178 | output["content"]["0"] = 0; 179 | output["content"]["1"] = 2; 180 | } 181 | else if (isLast) // 正常棋步 182 | { 183 | output["display"]["x"] = currX; 184 | output["display"]["y"] = currY; 185 | if (!CheckIfHasValidMove(currBotColor) && !CheckIfHasValidMove(-currBotColor)) // 游戏结束 186 | { 187 | if (blackPieceCount > whitePieceCount) 188 | { 189 | output["display"]["winner"] = 0; 190 | output["command"] = "finish"; 191 | output["content"]["0"] = 2; 192 | output["content"]["1"] = 0; 193 | } 194 | else if (blackPieceCount < whitePieceCount) 195 | { 196 | output["display"]["winner"] = 1; 197 | output["command"] = "finish"; 198 | output["content"]["0"] = 0; 199 | output["content"]["1"] = 2; 200 | } 201 | else 202 | { 203 | output["command"] = "finish"; 204 | output["content"]["0"] = 1; 205 | output["content"]["1"] = 1; 206 | } 207 | } 208 | else 209 | { 210 | 211 | output["display"]["x"] = currX; 212 | output["display"]["y"] = currY; 213 | output["command"] = "request"; 214 | output["content"]["1"]["x"] = currX; 215 | output["content"]["1"]["y"] = currY; 216 | } 217 | } 218 | } 219 | else if (isLast) 220 | { 221 | output["display"]["err"] = "INVALID_INPUT_VERDICT_" + input[i]["0"]["verdict"].asString(); 222 | output["display"]["winner"] = 1; 223 | output["command"] = "finish"; // 判输 224 | output["content"]["0"] = 0; 225 | output["content"]["1"] = 2; 226 | } 227 | } 228 | else 229 | { 230 | Json::Value answer = input[i]["1"]["response"].isNull() ? input[i]["1"]["content"] : input[i]["1"]["response"]; 231 | if (((answer.isString() && 232 | reader.parse(answer.asString(), content)) || 233 | (answer.isObject() && 234 | (content = answer, true))) && 235 | content["x"].isInt() && content["y"].isInt()) // 保证输入格式正确 236 | { 237 | int currX = content["x"].asInt(); 238 | int currY = content["y"].asInt(); 239 | if (currX == -1 && isLast) // bot选择PASS 240 | { 241 | if (!CheckIfHasValidMove(currBotColor)) // 他应该PASS 242 | { 243 | output["display"]["x"] = -1; 244 | output["display"]["y"] = -1; 245 | output["command"] = "request"; 246 | output["content"]["0"]["x"] = -1; 247 | output["content"]["0"]["y"] = -1; 248 | } 249 | else // 他不应该PASS 250 | { 251 | output["display"]["err"] = "HE_THOUGHT_THAT_HE_COULDNOT_MOVE_BUT_IN_FACT_HE_CAN"; 252 | output["display"]["winner"] = 0; 253 | output["command"] = "finish"; // 判输 254 | output["content"]["0"] = 2; 255 | output["content"]["1"] = 0; 256 | } 257 | } 258 | else if (!ProcStep(currX, currY, currBotColor) && isLast) // 不合法棋步! 259 | { 260 | output["display"]["err"] = "INVALID_MOVE"; 261 | output["display"]["winner"] = 0; 262 | output["command"] = "finish"; // 判输 263 | output["content"]["0"] = 2; 264 | output["content"]["1"] = 0; 265 | } 266 | else if (isLast) // 正常棋步 267 | { 268 | output["display"]["x"] = currX; 269 | output["display"]["y"] = currY; 270 | if (!CheckIfHasValidMove(currBotColor) && !CheckIfHasValidMove(-currBotColor)) // 游戏结束 271 | { 272 | if (blackPieceCount > whitePieceCount) 273 | { 274 | output["display"]["winner"] = 0; 275 | output["command"] = "finish"; 276 | output["content"]["0"] = 2; 277 | output["content"]["1"] = 0; 278 | } 279 | else if (blackPieceCount < whitePieceCount) 280 | { 281 | output["display"]["winner"] = 1; 282 | output["command"] = "finish"; 283 | output["content"]["0"] = 0; 284 | output["content"]["1"] = 2; 285 | } 286 | else 287 | { 288 | output["command"] = "finish"; 289 | output["content"]["0"] = 1; 290 | output["content"]["1"] = 1; 291 | } 292 | } 293 | else 294 | { 295 | output["command"] = "request"; 296 | output["content"]["0"]["x"] = currX; 297 | output["content"]["0"]["y"] = currY; 298 | } 299 | } 300 | } 301 | else if (isLast) 302 | { 303 | output["display"]["err"] = "INVALID_INPUT_VERDICT_" + input[i]["1"]["verdict"].asString(); 304 | output["display"]["winner"] = 0; 305 | output["command"] = "finish"; // 判输 306 | output["content"]["0"] = 2; 307 | output["content"]["1"] = 0; 308 | } 309 | } 310 | currBotColor *= -1; 311 | } 312 | } 313 | Json::FastWriter writer; 314 | cout << writer.write(output) << endl; 315 | } 316 | -------------------------------------------------------------------------------- /botzone/online/game/Snake.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include"jsoncpp/json.h" 9 | using namespace std; 10 | int n; 11 | int m; 12 | int obstacle; 13 | const int maxn=25; 14 | const int dx[4]={-1,0,1,0}; 15 | const int dy[4]={0,1,0,-1}; 16 | bool vis[maxn][maxn]; 17 | bool grid[maxn][maxn]; 18 | int lose[2],reason[2]; 19 | int tot,width,height; 20 | 21 | Json::Value obs; 22 | 23 | struct point 24 | { 25 | int x,y; 26 | point(int _x,int _y) 27 | { 28 | x=_x; 29 | y=_y; 30 | } 31 | }; 32 | 33 | list snake[2]; 34 | 35 | bool isInBody(int x,int y) 36 | { 37 | for (int id=0;id<=1;id++) 38 | for (list::iterator iter=snake[id].begin();iter!=snake[id].end();++iter) 39 | if (x==iter->x && y==iter->y) 40 | return true; 41 | return false; 42 | } 43 | 44 | bool whetherGrow(int num) 45 | { 46 | if (num<=9) return true; 47 | if ((num-9)%3==0) return true; 48 | return false; 49 | } 50 | 51 | bool validDirection(int id,int k) 52 | { 53 | if (k<0 || k>3) return false; 54 | point p=*(snake[id].begin()); 55 | int x=p.x+dx[k]; 56 | int y=p.y+dy[k]; 57 | 58 | //printf(" get 0\n"); 59 | if (x>n || y>m || x<1 || y<1) return false; 60 | //printf(" get 1\n"); 61 | //iprintf(" $$ %d %d %d\n",x,y,grid[x][y]); 62 | if (grid[x][y]) return false; 63 | //printf(" get 2\n"); 64 | if (isInBody(x,y)) return false; 65 | //printf(" get 3\n"); 66 | return true; 67 | } 68 | 69 | void move(int id,int dire,int num) 70 | { 71 | point p=*(snake[id].begin()); 72 | int x=p.x+dx[dire]; 73 | int y=p.y+dy[dire]; 74 | snake[id].push_front(point(x,y)); 75 | } 76 | 77 | int otherPlayerID(int id) 78 | { 79 | return 1-id; 80 | } 81 | 82 | string playerString(int id) 83 | { 84 | if (id==0) return "0"; 85 | return "1"; 86 | } 87 | 88 | void deleteEnd(int id) 89 | { 90 | snake[id].pop_back(); 91 | } 92 | 93 | void outputSnakeBody(int id) 94 | { 95 | cout<<"Snake No."<::iterator iter=snake[id].begin();iter!=snake[id].end();++iter) 97 | cout<x<<" "<y<0 && yy>0 && xx<=n && yy<=m) 114 | if (vis[xx][yy]==false && grid[xx][yy]==false) 115 | dfs(xx,yy); 116 | } 117 | } 118 | 119 | int main() 120 | { 121 | //srand(time(0)); 122 | 123 | string str; 124 | string temps; 125 | getline(cin,temps); 126 | str+=temps; 127 | 128 | Json::Reader reader; 129 | Json::Value input, output, initdata, obstacles,temp; 130 | reader.parse(str, input); 131 | 132 | initdata=input["initdata"]; 133 | 134 | if (initdata.isString()) 135 | reader.parse(initdata.asString(), initdata); 136 | if (initdata.isString()) 137 | reader.parse("{}", initdata); 138 | 139 | temp=initdata["seed"]; 140 | if (temp.isInt()) 141 | srand(temp.asInt()); 142 | else 143 | srand(time(0)); 144 | 145 | temp=initdata["width"]; 146 | width=9+Rand(3); 147 | if (temp.isInt()) 148 | { 149 | width=temp.asInt(); 150 | if (width>12 || width<10) 151 | width=12; 152 | } 153 | output["initdata"]["width"]=width; 154 | 155 | temp=initdata["height"]; 156 | height=10+Rand(6); 157 | if ((width+height)%2==0) 158 | height--; 159 | if (temp.isInt()) 160 | { 161 | height=temp.asInt(); 162 | if (height>16 || height<10) 163 | height=12; 164 | } 165 | output["initdata"]["height"]=height; 166 | 167 | obs=initdata["obstacle"]; 168 | output["initdata"]["obstacle"]=obs; 169 | 170 | int obsCount=obs.size(); 171 | for (int i=0;i=0 && dire<=3 && id==1 && snake[0].begin()->x==snake[1].begin()->x+dx[dire] && snake[0].begin()->y==snake[1].begin()->y+dy[dire]) 288 | { 289 | isOver=true; 290 | lose[0]=lose[1]=1; 291 | continue; 292 | } 293 | if (validDirection(id,dire)) 294 | { 295 | move(id,dire,num); 296 | if (isLast && !isOver) 297 | { 298 | output["command"]="request"; 299 | output["content"][playerString(otherPlayerID(id))]["direction"]=dire; 300 | } 301 | } 302 | else // invalid direction 303 | { 304 | isOver=true; 305 | reason[id]=0; 306 | lose[id]=1; 307 | } 308 | } 309 | else // invalid input 310 | { 311 | isOver=true; 312 | reason[id]=1; 313 | lose[id]=1; 314 | } 315 | } 316 | 317 | if (lose[0] || lose[1]) 318 | { 319 | if (lose[0]+lose[1]==1) 320 | { 321 | if (reason[0]+reason[1]==0) 322 | output["display"]["err"]="INVALIDMOVE"; 323 | else 324 | output["display"]["err"] = "INVALID_INPUT_VERDICT_" + input[i][playerString(lose[1])]["verdict"].asString(); 325 | 326 | int winner=lose[0]?1:0; 327 | output["display"]["winner"]=playerString(winner); 328 | output["command"]="finish"; 329 | output["content"].clear(); 330 | output["content"][playerString(winner)]=2; 331 | output["content"][playerString(otherPlayerID(winner))]=0; 332 | } 333 | if (lose[0]+lose[1]==2) 334 | { 335 | output["command"]="finish"; 336 | output["content"].clear(); 337 | output["display"]["err"]="BothSnakeDie"; 338 | output["content"]["0"]=1; 339 | output["content"]["1"]=1; 340 | } 341 | break; 342 | } 343 | 344 | //outputSnakeBody(0); 345 | //outputSnakeBody(1); 346 | } 347 | 348 | 349 | cout< 2 | #include 3 | #include 4 | #include 5 | #include "jsoncpp/json.h" 6 | using namespace std; 7 | 8 | // 说明:井字棋的简单judge 9 | 10 | int currBotColor; // 我所执子颜色(1为黑,-1为白,棋盘状态亦同) 11 | int gridInfo[3][3] = { 0 }; // 先x后y,记录棋盘状态 12 | int blackPieceCount = 0, whitePieceCount = 0; 13 | string blackName = "", whiteName = ""; 14 | 15 | inline bool inMap(int x, int y) 16 | { 17 | if (x < 0 || x > 2 || y < 0 || y > 2) 18 | return false; 19 | return true; 20 | } 21 | 22 | int ProcStep(int x, int y, int color) // 返回0就是出错,否则就是正确 23 | { 24 | if (color == 0) 25 | return false; 26 | if (!inMap(x, y)) 27 | return false; 28 | if (gridInfo[x][y] != 0) 29 | return false; 30 | 31 | if (color == 1) 32 | blackPieceCount++; 33 | else 34 | whitePieceCount++; 35 | gridInfo[x][y] = color; 36 | return true; 37 | } 38 | 39 | int checkIfWin() { // ret val: 0 No one wins, 1/-1 black/white wins 40 | vector sum; 41 | int s1 = 0, s2 = 0; 42 | for (int i = 0; i < 3; i++) { 43 | for (int j = 0; j < 3; j++) { 44 | s1 += gridInfo[i][j]; 45 | s2 += gridInfo[j][i]; 46 | } 47 | sum.push_back(s1); 48 | sum.push_back(s2); 49 | s1 = 0, s2 = 0; 50 | } 51 | 52 | for (int i = 0; i < 3; i++) { 53 | s1 += gridInfo[i][i]; 54 | s2 += gridInfo[i][2 - i]; 55 | } 56 | sum.push_back(s1); 57 | sum.push_back(s2); 58 | 59 | for (auto & v : sum) { 60 | if (v == 3) 61 | return 1; 62 | if (v == -3) 63 | return -1; 64 | } 65 | return 0; 66 | } 67 | 68 | int main() 69 | { 70 | string str; 71 | getline(cin, str); 72 | 73 | //freopen("in.txt", "r", stdin); 74 | //cin >> str; 75 | 76 | Json::Reader reader; 77 | Json::Value input, output; 78 | reader.parse(str, input); 79 | input = input["log"]; 80 | 81 | currBotColor = 1; 82 | if (input.size() == 0) 83 | { 84 | output["display"] = ""; 85 | output["command"] = "request"; 86 | output["content"]["0"]["x"] = -1; 87 | output["content"]["0"]["y"] = -1; 88 | } 89 | else 90 | { 91 | for (int i = 1; i < input.size(); i += 2) 92 | { 93 | bool isLast = i == input.size() - 1; // 是不是历史序列的最后一个 94 | 95 | Json::Value content; 96 | Json::Value display; 97 | if (currBotColor == 1) 98 | { 99 | Json::Value answer = input[i]["0"]["response"].isNull() ? input[i]["0"]["content"] : input[i]["0"]["response"]; 100 | if (((answer.isString() && 101 | reader.parse(answer.asString(), content)) || 102 | (answer.isObject() && 103 | (content = answer, true))) && 104 | content["x"].isInt() && content["y"].isInt()) 105 | { 106 | int x = content["x"].asInt(); 107 | int y = content["y"].asInt(); 108 | int procresult = ProcStep(x, y, currBotColor); 109 | 110 | if (!procresult && isLast) 111 | { 112 | output["display"]["err"] = "INVALIDMOVE"; 113 | output["display"]["winner"] = 1; 114 | output["command"] = "finish"; 115 | output["content"]["0"] = 0; 116 | output["content"]["1"] = 2; 117 | } 118 | else if (isLast) 119 | { 120 | output["display"]["x"] = x; 121 | output["display"]["y"] = y; 122 | 123 | int res = checkIfWin(); 124 | if (res == 0) { 125 | if (blackPieceCount + whitePieceCount == 9) { 126 | output["winner"] = -1; 127 | output["command"] = "finish"; 128 | output["content"]["0"] = 1; 129 | output["content"]["1"] = 1; 130 | } 131 | else { 132 | output["command"] = "request"; 133 | output["content"]["1"]["x"] = x; 134 | output["content"]["1"]["y"] = y; 135 | } 136 | } 137 | else { 138 | output["display"]["winner"] = 1 * (res == -1); 139 | output["command"] = "finish"; 140 | output["content"]["0"] = 2 * (res == 1); 141 | output["content"]["1"] = 2 * (res == -1); 142 | } 143 | } 144 | } 145 | else if (isLast) 146 | { 147 | output["display"]["err"] = "INVALID_INPUT_VERDICT_" + input[i]["0"]["verdict"].asString(); 148 | output["display"]["winner"] = 1; 149 | output["command"] = "finish"; 150 | output["content"]["0"] = 0; 151 | output["content"]["1"] = 2; 152 | } 153 | } 154 | else 155 | { 156 | Json::Value answer = input[i]["1"]["response"].isNull() ? input[i]["1"]["content"] : input[i]["1"]["response"]; 157 | if (((answer.isString() && 158 | reader.parse(answer.asString(), content)) || 159 | (answer.isObject() && 160 | (content = answer, true))) && 161 | content["x"].isInt() && content["y"].isInt()) 162 | { 163 | int x = content["x"].asInt(); 164 | int y = content["y"].asInt(); 165 | int procresult = ProcStep(x, y, currBotColor); 166 | 167 | if (!procresult && isLast) 168 | { 169 | output["display"]["err"] = "INVALIDMOVE"; 170 | output["display"]["winner"] = 0; 171 | output["command"] = "finish"; 172 | output["content"]["0"] = 2; 173 | output["content"]["1"] = 0; 174 | } 175 | else if (isLast) 176 | { 177 | output["display"]["x"] = x; 178 | output["display"]["y"] = y; 179 | 180 | int res = checkIfWin(); 181 | if (res == 0) { 182 | if (blackPieceCount + whitePieceCount == 9) { 183 | output["winner"] = -1; 184 | output["command"] = "finish"; 185 | output["content"]["0"] = 1; 186 | output["content"]["1"] = 1; 187 | } 188 | else { 189 | output["command"] = "request"; 190 | output["content"]["0"]["x"] = x; 191 | output["content"]["0"]["y"] = y; 192 | } 193 | } 194 | else { 195 | output["display"]["winner"] = 1 * (res == -1); 196 | output["command"] = "finish"; 197 | output["content"]["0"] = 2 * (res == 1); 198 | output["content"]["1"] = 2 * (res == -1); 199 | } 200 | } 201 | } 202 | else if (isLast) 203 | { 204 | output["display"]["err"] = "INVALID_INPUT_VERDICT_" + input[i]["1"]["verdict"].asString(); 205 | output["display"]["winner"] = 0; 206 | output["command"] = "finish"; 207 | output["content"]["0"] = 2; 208 | output["content"]["1"] = 0; 209 | } 210 | } 211 | currBotColor *= -1; 212 | } 213 | } 214 | Json::FastWriter writer; 215 | cout << writer.write(output) << endl; 216 | cout << endl; 217 | } -------------------------------------------------------------------------------- /botzone/online/sandbox.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import subprocess 4 | 5 | from botzone.online import compiler 6 | 7 | class SandBox: 8 | ''' 9 | SandBox for running judge programs and bots locally. Use underlying docker. 10 | ''' 11 | 12 | CONFIG_TIME_RATIO = 2 # To compromise the performance between docker and online judges 13 | 14 | def __init__(self, code, ext): 15 | assert os.path.exists(code) 16 | assert ext in compiler.config 17 | self.code = code 18 | self.config = compiler.config[ext] 19 | self.cwd = '/home/user/' 20 | self.path_code = self.cwd + 'code.' + self.config['suffix'] 21 | self.path_target = self.cwd + 'prog' if self.config['compile_command'] else self.path_code 22 | self.container = None 23 | self.proc = None 24 | self.compiled = False 25 | 26 | def create(self): 27 | if self.container: 28 | print('Warning: container already created') 29 | return 30 | # choose image (optionally pull), run container and copy files 31 | local_images = self._get_local_images() 32 | for image in self.config['images']: 33 | available_images = [] 34 | for local_image in local_images: 35 | if local_image['repo'] == image: 36 | available_images.append(local_image) 37 | if available_images: break 38 | if available_images: 39 | # find local images, choose latest 40 | image = max(available_images, key = lambda x: x['tag']) 41 | image = image['repo'] + ':' + image['tag'] 42 | else: 43 | available_images = self.config['images'] 44 | # not found, pull? 45 | print('No docker image found for %s, type `i` to pull ith image from remote server, or else quit' % self.config['suffix']) 46 | for i, image in enumerate(available_images): print(i, image, 'Supported language: %s' % ','.join(compiler.images[image]), sep = '\t') 47 | try: 48 | i = int(input()) 49 | assert 0 <= i < len(available_images) 50 | except: 51 | raise RuntimeError('No docker image found for %s locally, options:' % self.suffix, available_images) 52 | image = available_images[i] 53 | self._pull_remote_image(image) 54 | self._run_container(image) 55 | self._copy_to_container(self.code, self.path_code) 56 | 57 | def copy(self, folder): 58 | assert os.path.isdir(folder) 59 | self._copy_to_container(folder, '/data') 60 | self._prepare_userdata() 61 | 62 | def compile(self): 63 | if self.compiled: return 64 | if self.config['compile_command'] is None: return 65 | # compile the code 66 | compile_command = self.config['compile_command'].format(code = self.path_code, target = self.path_target) 67 | command = 'docker exec -it -u user %s /bin/bash -c "%s"' % (self.container, compile_command) 68 | p = subprocess.run(command, shell = True) 69 | if p.returncode != 0: 70 | raise RuntimeError('Compile error!') 71 | print('Compile successfully!') 72 | self.compiled = True 73 | self._update_container(1, self.config['memory_limit']) 74 | 75 | def run(self, input, keep_running = False): 76 | # run the code, give it the input and return the output from wrapper 77 | assert isinstance(input, str) 78 | if not self.proc: 79 | run_command = self.config['run_command'].format(target = self.path_target) 80 | wrapper_command = '~/wrapper -d {cwd} -t {tl} {stl} -m {ml} -o {ol} {command}'.format( 81 | cwd = self.cwd, 82 | tl = self.config['time_limit'] * SandBox.CONFIG_TIME_RATIO, 83 | stl = '-k {tl}'.format(tl = self.config['sub_time_limit'] * SandBox.CONFIG_TIME_RATIO) if keep_running else '', 84 | ml = self.config['memory_limit'], 85 | ol = 1048576, 86 | command = run_command 87 | ) 88 | command = 'docker exec -i -u user %s /bin/bash -c "%s"' % (self.container, wrapper_command) 89 | p = subprocess.Popen(command, shell = True, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.STDOUT) 90 | else: 91 | p = self.proc 92 | p.stdin.write(bytearray(input, 'utf-8')) 93 | p.stdin.write(b'\n>>>BOTZONE_CONTROLLER_INPUT_END<<<\n') 94 | try: 95 | p.stdin.flush() 96 | except (OSError, BrokenPipeError): 97 | # when p exits before flush, an Error is triggered on windows 98 | print('Warning: Broken pipe when flushing stdin in run') 99 | output = p.stdout.readline() 100 | output = json.loads(output) 101 | if output['keep_running']: 102 | self.proc = p 103 | else: 104 | # wait for it die gracefully 105 | try: 106 | p.communicate(timeout = 1) 107 | except: 108 | # or not 109 | p.kill() 110 | self.proc = None 111 | return output 112 | 113 | def run_kill(self): 114 | # kill child that still keeps running 115 | p = self.proc 116 | p.stdin.write(b'>>>BOTZONE_CONTROLLER_KILL<<<\n') 117 | try: 118 | p.stdin.flush() 119 | except (OSError, BrokenPipeError): 120 | # when p exits before flush, an Error is triggered on windows 121 | print('Warning: Broken pipe when flushing stdin in run_kill') 122 | # wait for it die gracefully 123 | try: 124 | p.communicate(timeout = 1) 125 | except: 126 | # or not 127 | p.kill() 128 | self.proc = None 129 | 130 | def close(self): 131 | # necessary clean up or container will keep running 132 | if self.container: 133 | if self.proc: 134 | self.run_kill() 135 | self._kill_container() 136 | self.container = None 137 | 138 | def _get_local_images(self): 139 | p = subprocess.run('docker images --no-trunc', capture_output = True, shell = True, encoding = 'utf-8') 140 | if p.returncode != 0: 141 | raise RuntimeError('No docker installation found!') 142 | l = p.stdout.strip().split('\n') 143 | images = [] 144 | for line in l[1:]: 145 | repo, tag, id, *time, size = line.split() 146 | images.append(dict( 147 | repo = repo, 148 | tag = tag, 149 | id = id[7:], 150 | time = ' '.join(time), 151 | size = size 152 | )) 153 | return images 154 | 155 | def _pull_remote_image(self, image): 156 | p = subprocess.run('docker pull %s' % image, shell = True) 157 | if p.returncode != 0: 158 | raise RuntimeError('Failed to pull image %s!' % image) 159 | 160 | def _run_container(self, image): 161 | p = subprocess.run('docker run -td --rm %s /bin/bash' % image, capture_output = True, shell = True, encoding = 'utf-8') 162 | if p.returncode != 0: 163 | raise RuntimeError('Failed to run container with image %s! Detail:\n%s' % (image, p.stderr)) 164 | self.container = p.stdout.strip() 165 | print('Container %s created successfully' % self.container) 166 | 167 | def _copy_to_container(self, src, dst): 168 | p = subprocess.run('docker cp "%s" "%s:%s"' % (src, self.container, dst), capture_output = True, shell = True, encoding = 'utf-8') 169 | if p.returncode != 0: 170 | raise RuntimeError('Failed to copy file %s to container %s! Detail:\n%s' % (src, self.container, p.stderr)) 171 | 172 | def _prepare_userdata(self): 173 | p = subprocess.run('docker exec %s /bin/ln -s /data /home/user/data' % self.container, capture_output = True, shell = True, encoding = 'utf-8') 174 | if p.returncode != 0: 175 | raise RuntimeError('Failed to create symbolic link to /data in container %s! Detail:\n%s' % (self.container, p.stderr)) 176 | p = subprocess.run('docker exec %s /bin/chown -R user /data' % self.container, capture_output = True, shell = True, encoding = 'utf-8') 177 | if p.returncode != 0: 178 | raise RuntimeError('Failed to chown /data to user in container %s! Detail:\n%s' % (self.container, p.stderr)) 179 | 180 | def _update_container(self, cpu, memory): 181 | p = subprocess.run('docker update --cpus="{cpu}" --memory="{memory}m" --memory-swap="{memory}m" {container}'.format(cpu = cpu, memory = memory, container = self.container), capture_output = True, shell = True, encoding = 'utf-8') 182 | if p.returncode != 0: 183 | raise RuntimeError('Failed to update config of container %s! Detail:\n%s' % (self.container, p.stderr)) 184 | 185 | def _kill_container(self): 186 | p = subprocess.run('docker kill %s' % self.container, capture_output = True, shell = True, encoding = 'utf-8') 187 | if p.returncode != 0: 188 | print('Failed to kill container %s! Detail:\n%s' % (self.container, p.stderr)) 189 | else: 190 | print('Container %s killed successfully' % self.container) 191 | -------------------------------------------------------------------------------- /botzone/online/viewer/amazons.py: -------------------------------------------------------------------------------- 1 | from rich import box, print 2 | from rich.panel import Panel 3 | from rich.table import Table 4 | from rich.text import Text 5 | 6 | from botzone.online.viewer.viewer import TextViewer 7 | 8 | class AmazonsTextViewer(TextViewer): 9 | ''' 10 | {blackCount, whiteCount} in first round, 11 | {blackCount, whiteCount[,x0, y0, x1, y1, x2, y2][, winner, err]} in other rounds. 12 | ''' 13 | 14 | def __init__(self, size = 8): 15 | self.stones = ' ●○●' 16 | self.size = size 17 | 18 | def reset(self, initdata = None): 19 | self.board = b = [[0 for j in range(self.size)] for i in range(self.size)] 20 | self.round = -1 21 | p = (self.size + 2) // 3 22 | b[0][p - 1] = b[p - 1][0] = b[-p][0] = b[-1][p - 1] = 1 # 1 for black 23 | b[0][-p] = b[p - 1][-1] = b[-p][-1] = b[-1][-p] = 2 # 2 for white 24 | self.count = [0, 0] 25 | 26 | def render(self, displays, bootstrap = True): 27 | b = self.board 28 | # Recover state 29 | for display in displays: 30 | self.round += 1 31 | self.count = [display['blackCount'], display['whiteCount']] 32 | if 'x0' not in display: continue 33 | x0 = display['x0'] 34 | if x0 == -1: continue 35 | y0 = display['y0'] 36 | x1 = display['x1'] 37 | y1 = display['y1'] 38 | x2 = display['x2'] 39 | y2 = display['y2'] 40 | color = 2 - self.round % 2 41 | b[x1][y1] = b[x0][y0] 42 | b[x0][y0] = 0 43 | b[x2][y2] = -1 44 | 45 | message = 'Round: %d\n%s: %d\n%s: %d\nNext: %s' % ( 46 | self.round, 47 | self.stones[1], 48 | self.count[0], 49 | self.stones[2], 50 | self.count[1], 51 | self.stones[1 + self.round % 2] 52 | ) 53 | styles = [[self.stones[b[i][j]] if b[i][j] >= 0 else Text(self.stones[b[i][j]], style = 'blue') for j in range(self.size)] for i in range(self.size)] 54 | if displays: 55 | d = displays[-1] 56 | if not d: d = {} 57 | x0 = d.get('x0', -1) 58 | y0 = d.get('y0', -1) 59 | if x0 != -1 and isinstance(styles[x0][y0], str): 60 | styles[x0][y0] = Text(styles[x0][y0], 'on red') 61 | x1 = d.get('x1', -1) 62 | y1 = d.get('y1', -1) 63 | if x1 != -1: 64 | styles[x1][y1] = Text(styles[x1][y1], 'on green') 65 | x2 = d.get('x2', -1) 66 | y2 = d.get('y2', -1) 67 | if x2 != -1: 68 | styles[x2][y2].style = 'blue on yellow' 69 | if 'winner' in d: 70 | message += '\n%s wins!' % self.stones[d['winner'] + 1] 71 | t = Table.grid(padding = (0, 1)) 72 | t.add_row('', *map(chr, range(65, 65 + self.size))) 73 | for j in range(self.size): 74 | t.add_row(str(1 + j), *[styles[i][j] for i in range(self.size)]) 75 | tt = Table.grid(padding = (0, 4)) 76 | tt.add_row(t, message) 77 | print(Panel.fit(tt, box = box.SQUARE)) -------------------------------------------------------------------------------- /botzone/online/viewer/ataxx.py: -------------------------------------------------------------------------------- 1 | from rich import box, print 2 | from rich.panel import Panel 3 | from rich.table import Table 4 | from rich.text import Text 5 | 6 | from botzone.online.viewer.viewer import TextViewer 7 | 8 | class AtaxxTextViewer(TextViewer): 9 | ''' 10 | Empty string in first round, {x0, y0, x1, y1[, winner, err]} in other rounds. 11 | ''' 12 | 13 | def __init__(self): 14 | self.stones = ' ●○' 15 | 16 | def reset(self, initdata = None): 17 | self.board = b = [[0 for j in range(7)] for i in range(7)] 18 | self.round = -1 19 | self.count = [0, 2, 2] 20 | b[0][0] = b[-1][-1] = 1 # 1 for black 21 | b[0][-1] = b[-1][0] = 2 # 2 for white 22 | 23 | def render(self, displays, bootstrap = True): 24 | b = self.board 25 | # Recover state 26 | for display in displays: 27 | self.round += 1 28 | if not display: display = {} 29 | change = [] 30 | if 'x0' not in display: continue 31 | x0 = display['x0'] 32 | y0 = display['y0'] 33 | x1 = display['x1'] 34 | y1 = display['y1'] 35 | if x0 == -1: continue 36 | color = 2 - self.round % 2 37 | if abs(x1 - x0) == 2 or abs(y1 - y0) == 2: 38 | b[x0][y0] = 0 39 | self.count[color] -= 1 40 | b[x1][y1] = color 41 | self.count[color] += 1 42 | for dx, dy in ((-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)): 43 | nx = x1 + dx 44 | ny = y1 + dy 45 | if self._in_board(nx, ny) and b[nx][ny] == 3 - color: 46 | change.append((nx, ny)) 47 | for nx, ny in change: 48 | b[nx][ny] = color 49 | self.count[color] += len(change) 50 | self.count[3 - color] -= len(change) 51 | 52 | message = 'Round: %d\n%s: %d\n%s: %d\nNext: %s' % ( 53 | self.round, 54 | self.stones[1], 55 | self.count[1], 56 | self.stones[2], 57 | self.count[2], 58 | self.stones[1 + self.round % 2] 59 | ) 60 | style_before = lambda s: Text(s, style = 'on blue') 61 | style_cur = lambda s: Text(s, style = 'on red') 62 | style_change = lambda s: Text(s, style = 'yellow') 63 | style_normal = lambda s: s 64 | styles = [[style_normal for i in range(7)] for j in range(7)] 65 | if displays: 66 | d = displays[-1] 67 | if not d: d = {} 68 | x0 = d.get('x0', -1) 69 | y0 = d.get('y0', -1) 70 | if x0 != -1: 71 | styles[x0][y0] = style_before 72 | x1 = d.get('x1', -1) 73 | y1 = d.get('y1', -1) 74 | if x1 != -1: 75 | styles[x1][y1] = style_cur 76 | if 'winner' in d: 77 | message += '\n%s wins!' % self.stones[d['winner'] + 1] 78 | for x, y in change: styles[x][y] = style_change 79 | t = Table.grid(padding = (0, 1)) 80 | t.add_row('', *map(chr, range(65, 65 + 7))) 81 | for j in range(7): 82 | t.add_row(str(1 + j), *[styles[i][j](self.stones[b[i][j]]) for i in range(7)]) 83 | tt = Table.grid(padding = (0, 4)) 84 | tt.add_row(t, message) 85 | print(Panel.fit(tt, box = box.SQUARE)) 86 | 87 | def _in_board(self, x, y): 88 | return 0 <= x < 7 and 0 <= y < 7 -------------------------------------------------------------------------------- /botzone/online/viewer/chinesestandardmahjong.py: -------------------------------------------------------------------------------- 1 | from rich import box 2 | from rich import print as rprint 3 | from rich.panel import Panel 4 | from rich.table import Table 5 | from rich.text import Text 6 | 7 | from botzone.online.viewer.viewer import TextViewer 8 | 9 | class ChineseStandardMahjongTextViewer(TextViewer): 10 | ''' 11 | {action = INIT, quan = 0..3} 12 | {action = DEAL, hand, tileCnt} 13 | {action = BUHUA, player, tile, tileCnt} 14 | {action = DRAW, player, tile, tileCnt} 15 | {action = PLAY, player, tile, tileCnt} 16 | {action = CHI, player, tile, tileCHI, tileCnt} 17 | {action = PENG, player, tile, tileCnt} 18 | {action = GANG, player, tile, tileCnt} 19 | {action = BUGANG, player, tile, tileCnt} 20 | {action = HU, player, fan = [{name, cnt, value}], fanCnt, score} 21 | {action = HUANG, tileCnt} 22 | {action = WA, player, score} 23 | ''' 24 | 25 | def __init__(self, duplicate = True): 26 | self.duplicate = duplicate 27 | r = lambda s: Text(s, style = 'red') 28 | g = lambda s: Text(s, style = 'green') 29 | b = lambda s: Text(s, style = 'blue') 30 | draw = dict( 31 | J1 = r('\n中 \n'), 32 | J2 = g('\n發 \n'), 33 | J3 = b('╔═╗\n║ ║\n╚═╝'), 34 | T1 = r('\n | \n'), 35 | T2 = g(' | \n\n | '), 36 | T3 = g(' | \n\n| |'), 37 | T4 = g('| |\n\n| |'), 38 | T5 = g('| |\n') + r(' | ') + g('\n| |'), 39 | T6 = g('|||\n\n|||'), 40 | T7 = r(' | ') + g('\n|||\n|||'), 41 | T8 = g('|Ʌ|\n\n|V|'), 42 | T9 = g('|') + r('|') + g('|\n|') + r('|') + g('|\n|') + r('|') + g('|'), 43 | B1 = r('\n ● \n'), 44 | B2 = g(' ● ') + b('\n\n ● '), 45 | B3 = g('● \n') + r(' ● \n') + b(' ●'), 46 | B4 = g('●') + b(' ●\n\n● ') + g('●'), 47 | B5 = g('●') + b(' ●\n ') + r('●') + b(' \n● ') + g('●'), 48 | B6 = g('● ●') + r('\n● ●\n● ●'), 49 | B7 = g('●●●') + r('\n● ●\n● ●'), 50 | B8 = b('●●●\n●●●\n●●'), 51 | B9 = b('●●●') + r('\n●●●') + g('\n●●●') 52 | ) 53 | for i in range(4): draw['F%d' % (i + 1)] = b('\n%s \n' % '東南西北'[i]) 54 | for i in range(4): draw['H%d' % (i + 1)] = r('\n%s \n' % '春夏秋冬'[i]) 55 | for i in range(4): draw['H%d' % (i + 5)] = b('\n%s \n' % '梅蘭竹菊'[i]) 56 | for i in range(9): draw['W%d' % (i + 1)] = b('一二三四五六七八九'[i]) + r('\n\n 萬') 57 | draw['back'] = Text(' \n \n ', style = 'on green') 58 | self.draw = draw 59 | self.wind = ['East', 'South', 'West', 'North'] 60 | 61 | def reset(self, initdata = None): 62 | self.round = -1 63 | self.player = -1 64 | self.tile = None 65 | self.score = None 66 | self.hands = None 67 | self.fan = None 68 | self.discards = [[] for i in range(4)] 69 | 70 | def render(self, displays, bootstrap = True): 71 | if not displays: return 72 | # Recover state 73 | for display in displays: 74 | self.round += 1 75 | if display['action'] == 'INIT': 76 | self.prevalentWind = display['quan'] 77 | elif display['action'] == 'DEAL': 78 | self.tileCnt = display['tileCnt'] 79 | self.hands = [[x for x in hand if not x.startswith('H')] for hand in display['hand']] 80 | self.flowers = [[x for x in hand if x.startswith('H')] for hand in display['hand']] 81 | self.packs = [[] for i in range(4)] 82 | elif display['action'] == 'BUHUA': 83 | self.tileCnt = display['tileCnt'] 84 | self.player = display['player'] 85 | tile = display['tile'] 86 | self.msg = 'BUHUA ' + tile 87 | self.flowers[self.player].append(tile) 88 | elif display['action'] == 'DRAW': 89 | self.tileCnt = display['tileCnt'] 90 | if self.tile and not self.msg.startswith('BUGANG'): self.discards[self.player].append(self.tile) 91 | self.player = display['player'] 92 | self.tile = display['tile'] 93 | self.msg = 'DRAW ' + self.tile 94 | elif display['action'] == 'PLAY': 95 | self.tileCnt = display['tileCnt'] 96 | self.hands[self.player].append(self.tile) 97 | self.player = display['player'] 98 | self.tile = display['tile'] 99 | self.hands[self.player].remove(self.tile) 100 | self.msg = 'PLAY ' + self.tile 101 | elif display['action'] == 'CHI': 102 | self.tileCnt = display['tileCnt'] 103 | self.player = display['player'] 104 | tileChow = display['tileCHI'] 105 | color = tileChow[0] 106 | num = int(tileChow[1]) 107 | for i in range(-1, 2): 108 | tile = color + str((num + i)) 109 | if tile != self.tile: self.hands[self.player].remove(tile) 110 | self.packs[self.player].append(tile) 111 | self.msg = 'CHI ' + self.tile 112 | self.tile = display['tile'] 113 | self.hands[self.player].remove(self.tile) 114 | self.msg += '\nPLAY ' + self.tile 115 | elif display['action'] == 'PENG': 116 | self.tileCnt = display['tileCnt'] 117 | self.player = display['player'] 118 | for i in range(2): self.hands[self.player].remove(self.tile) 119 | for i in range(3): self.packs[self.player].append(self.tile) 120 | self.msg = 'PENG ' + self.tile 121 | self.tile = display['tile'] 122 | self.hands[self.player].remove(self.tile) 123 | self.msg += '\nPLAY ' + self.tile 124 | elif display['action'] == 'GANG': 125 | self.tileCnt = display['tileCnt'] 126 | player = display['player'] 127 | tile = display['tile'] 128 | self.hands[player].append(self.tile) 129 | for i in range(4): self.hands[player].remove(tile) 130 | if player == self.player: 131 | # in secret 132 | self.packs[player].extend(['back', 'back', tile, 'back']) 133 | else: 134 | for i in range(4): self.packs[player].append(tile) 135 | self.player = player 136 | self.msg = 'GANG ' + tile 137 | self.tile = None 138 | elif display['action'] == 'BUGANG': 139 | self.tileCnt = display['tileCnt'] 140 | player = display['player'] 141 | tile = display['tile'] 142 | self.hands[self.player].append(self.tile) 143 | self.hands[player].remove(tile) 144 | self.packs[player].insert(self.packs[player].index(tile), tile) 145 | self.msg = 'BUGANG ' + tile 146 | elif display['action'] == 'HU': 147 | self.player = display['player'] 148 | self.fan = display['fan'] 149 | self.fanCnt = display['fanCnt'] 150 | self.score = display['score'] 151 | self.hands[self.player].append(self.tile) 152 | self.msg = 'HU' 153 | self.tile = None 154 | elif display['action'] == 'HUANG': 155 | self.tileCnt = display['tileCnt'] 156 | self.score = [0, 0, 0, 0] 157 | if self.tile: self.discards[self.player].append(self.tile) 158 | self.tile = None 159 | self.player = -1 160 | else: 161 | self.player = display['player'] 162 | self.score = display['score'] 163 | self.msg = display['action'] 164 | self.tile = None 165 | 166 | # Render 167 | if not self.hands: 168 | # Round 0 169 | print(self.wind[self.prevalentWind], 'Wind, Round', self.round) 170 | return 171 | if self.duplicate: 172 | print(self.wind[self.prevalentWind], 'Wind, Round', self.round) 173 | else: 174 | print(self.wind[self.prevalentWind], 'Wind, Round', self.round, ',', self.tileCnt, 'tiles left.') 175 | captions = ['\nPlayer %d\n\n%s' % (i + 1, self.wind[i]) for i in range(4)] 176 | hands = [] 177 | for i in range(4): 178 | t = Table(show_header = False, box = box.SQUARE, padding = 0) 179 | t.add_row(*(self.draw[tile] for tile in sorted(self.hands[i]))) 180 | hands.append(t) 181 | packs = [] 182 | for i in range(4): 183 | t = Table(show_header = False, box = box.SQUARE, padding = 0) 184 | t.add_row(*(self.draw[tile] for tile in sorted(self.flowers[i])), *(self.draw[tile] for tile in self.packs[i])) 185 | packs.append(t) 186 | tiles = ['' for i in range(4)] 187 | if self.player >= 0 and self.tile: tiles[self.player] = Panel.fit(self.draw[self.tile], box = box.SQUARE, padding = 0) 188 | msgs = ['' for i in range(4)] 189 | if self.duplicate: 190 | for i in range(4): msgs[i] += '%d tiles left.\n\n' % self.tileCnt[i] 191 | if self.score: 192 | for i in range(4): msgs[i] += 'Score: %d\n\n' % self.score[i] 193 | if self.player >= 0: 194 | msgs[self.player] += self.msg 195 | t = Table(show_lines = True, show_header = False, box = box.SQUARE, padding = 0) 196 | for i in range(4): 197 | t.add_row(captions[i], hands[i], packs[i], tiles[i], msgs[i]) 198 | rprint(t) 199 | print('Tiles discarded:') 200 | t = Table(show_lines = True, show_header = False, box = box.SQUARE, padding = 0) 201 | for i in range(4): 202 | t.add_row(*(self.draw[tile] for tile in self.discards[i])) 203 | rprint(t) 204 | if self.fan: 205 | print('Total Fan:', self.fanCnt) 206 | for fan in self.fan: 207 | print('%s %d Fan * %d' % (fan['name'], fan['value'], fan['cnt'])) 208 | -------------------------------------------------------------------------------- /botzone/online/viewer/fightthelandlord.py: -------------------------------------------------------------------------------- 1 | from rich import box 2 | from rich import print as rprint 3 | from rich.panel import Panel 4 | from rich.table import Table 5 | from rich.text import Text 6 | 7 | from botzone.online.viewer.viewer import TextViewer 8 | 9 | class FightTheLandlordTextViewer(TextViewer): 10 | ''' 11 | First round: {allocation: list of 3*17, publiccard: list of 3} 12 | Other rounds: { 13 | 0, 1, 2: score in the last round 14 | event: {player: 0..2, action: list} 15 | errorInfo: errorinfo of { 16 | INVALID_INPUT_VERDICT_*: Agent failure! 17 | BAD_FORMAT: Bad format! 18 | INVALID_CARDTYPE: Invalid card type! 19 | INVALID_PASS: Invalid pass! 20 | MISMATCH_CARDTYPE: Card type mismatch! 21 | MISMATCH_CARDLENGTH: Card length mismatch! 22 | LESS_COMPARE: Smaller card! 23 | MISSING_CARD: Fake card! 24 | OUT_OF_RANGE: Invalid card! 25 | REPEATED_CARD: Repeated card! 26 | } 27 | } 28 | ''' 29 | 30 | def __init__(self): 31 | self.draw = [suit + num for num in ['3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A', '2'] for suit in '♥♦♠♣'] 32 | self.draw.extend(['JOKER', 'JOKER']) 33 | self.draw = ['\n'.join(s.ljust(5)) for s in self.draw] 34 | for i in range(52): 35 | if i % 4 < 2: 36 | self.draw[i] = Text(self.draw[i], style = 'red') 37 | self.draw[-1] = Text(self.draw[-1], style = 'red') 38 | self.errMsg = dict( 39 | BAD_FORMAT = 'Bad format!', 40 | INVALID_CARDTYPE = 'Invalid card type!', 41 | INVALID_PASS = 'Invalid pass!', 42 | MISMATCH_CARDTYPE = 'Card type mismatch!', 43 | MISMATCH_CARDLENGTH = 'Card length mismatch!', 44 | LESS_COMPARE = 'Smaller card!', 45 | MISSING_CARD = 'Fake card!', 46 | OUT_OF_RANGE = 'Invalid card!', 47 | REPEATED_CARD = 'Repeated card!' 48 | ) 49 | self.defaultErrMsg = 'Agent failure!' 50 | self.title = ['Landlord', 'Peasant', 'Peasant'] 51 | 52 | def reset(self, initdata = None): 53 | self.round = -1 54 | 55 | def render(self, displays, bootstrap = True): 56 | # Recover state 57 | display = {} 58 | for display in displays: 59 | self.round += 1 60 | cur = (self.round - 1) % 3 61 | if self.round == 0: 62 | # First round 63 | self.hand = display['allocation'] 64 | self.publiccard = display['publiccard'] 65 | self.history = [None, None, None] 66 | for h in self.hand: h.sort() 67 | else: 68 | action = display['event']['action'] 69 | self.hand[cur] = [x for x in self.hand[cur] if x not in action] 70 | if 'errorInfo' in display: 71 | self.history[cur] = self._get_err_msg(display['errorInfo']) 72 | else: 73 | self.history[cur] = action 74 | # Render 75 | msg = ['\nPlayer %d\n\n%s\n\n%d left' % (i, self.title[i], len(self.hand[i])) for i in range(3)] 76 | if '0' in display: 77 | for i in range(3): 78 | msg[i] += '\n\nScore: %f' % display[str(i)] 79 | status = [] 80 | for i in range(3): 81 | if '0' not in display and i == self.round % 3: 82 | status.append('\nNext') 83 | elif self.history[i] is None: 84 | status.append('') 85 | elif isinstance(self.history[i], str): 86 | status.append(self.history[i]) 87 | elif self.history[i]: 88 | status.append(self._get_cards_draw(self.history[i])) 89 | else: 90 | status.append('\nPass') 91 | table = Table(show_header = False, show_lines = True, box = box.SQUARE) 92 | for i in range(3): 93 | table.add_row(msg[i], self._get_cards_draw(self.hand[i]), status[i]) 94 | print('Round:', self.round) 95 | rprint(table) 96 | 97 | def _get_err_msg(self, errorInfo): 98 | return self.errMsg.get(errorInfo, self.defaultErrMsg) 99 | 100 | def _get_cards_draw(self, cards): 101 | if not cards: return '' 102 | table = Table(show_header = False, padding = 0, style = 'black on white', box = box.SQUARE) 103 | table.add_row(*(self.draw[card] for card in cards)) 104 | table.add_row(*(Text(' ', style = 'on red') if card in self.publiccard else ' ' for card in cards)) 105 | table.columns[-1].min_width = 6 106 | return table -------------------------------------------------------------------------------- /botzone/online/viewer/fightthelandlord2.py: -------------------------------------------------------------------------------- 1 | from rich import box 2 | from rich import print as rprint 3 | from rich.panel import Panel 4 | from rich.table import Table 5 | from rich.text import Text 6 | 7 | from botzone.online.viewer.viewer import TextViewer 8 | 9 | class FightTheLandlord2TextViewer(TextViewer): 10 | ''' 11 | First 1~3 round: {allocation: list of 3*17, publiccard: list of 3, bid: list of 0~2} 12 | Other rounds: { 13 | 0, 1, 2: score in the last round 14 | event: {player: 0..2, action: list} 15 | landlord: int 16 | bid: list of 1~3 17 | errored: list of errorinfo of { 18 | INVALID_INPUT_VERDICT_*: Agent failure! 19 | BAD_FORMAT: Bad format! 20 | INVALID_CARDTYPE: Invalid card type! 21 | INVALID_PASS: Invalid pass! 22 | MISMATCH_CARDTYPE: Card type mismatch! 23 | MISMATCH_CARDLENGTH: Card length mismatch! 24 | LESS_COMPARE: Smaller card! 25 | MISSING_CARD: Fake card! 26 | OUT_OF_RANGE: Invalid card! 27 | REPEATED_CARD: Repeated card! 28 | INVALID_BID: Invalid bid! 29 | } 30 | } 31 | ''' 32 | 33 | def __init__(self): 34 | self.draw = [suit + num for num in ['3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A', '2'] for suit in '♥♦♠♣'] 35 | self.draw.extend(['JOKER', 'JOKER']) 36 | self.draw = ['\n'.join(s.ljust(5)) for s in self.draw] 37 | for i in range(52): 38 | if i % 4 < 2: 39 | self.draw[i] = Text(self.draw[i], style = 'red') 40 | self.draw[-1] = Text(self.draw[-1], style = 'red') 41 | self.errMsg = dict( 42 | BAD_FORMAT = 'Bad format!', 43 | INVALID_CARDTYPE = 'Invalid card type!', 44 | INVALID_PASS = 'Invalid pass!', 45 | MISMATCH_CARDTYPE = 'Card type mismatch!', 46 | MISMATCH_CARDLENGTH = 'Card length mismatch!', 47 | LESS_COMPARE = 'Smaller card!', 48 | MISSING_CARD = 'Fake card!', 49 | OUT_OF_RANGE = 'Invalid card!', 50 | REPEATED_CARD = 'Repeated card!', 51 | INVALID_BID = 'Invalid bid!' 52 | ) 53 | self.defaultErrMsg = 'Agent failure!' 54 | 55 | def reset(self, initdata = None): 56 | self.round = -1 57 | 58 | def render(self, displays, bootstrap = True): 59 | # Recover state 60 | display = {} 61 | cur = -2 62 | for display in displays: 63 | self.round += 1 64 | if self.round == 0: 65 | # First round 66 | self.hand = display['allocation'] 67 | self.publiccard = display['publiccard'] 68 | self.history = [None, None, None] 69 | self.errored = [None] * 3 70 | self.landlord = -1 71 | self.bid = [] 72 | for h in self.hand: h.sort() 73 | else: 74 | if 'bid' in display: self.bid = display['bid'] 75 | if 'allocation' not in display: 76 | if 'errored' in display: self.errored = display['errored'] 77 | if self.round == len(self.bid): 78 | # Bidding just over 79 | self.landlord = display['landlord'] 80 | self.hand[self.landlord].extend(self.publiccard) 81 | self.hand[self.landlord].sort() 82 | elif 'event' in display: 83 | cur = display['event']['player'] 84 | action = display['event']['action'] 85 | self.hand[cur] = [x for x in self.hand[cur] if x not in action] 86 | self.history[cur] = action 87 | # Render 88 | msg = ['\nPlayer %d%s\n\n%s\n\n%d left' % ( 89 | i, 90 | '\n[Robot] ' + self._get_err_msg(self.errored[i]) if self.errored[i] else '', 91 | 'Unknown' if self.landlord == -1 else 'Landlord' if i == self.landlord else 'Peasant', 92 | len(self.hand[i])) for i in range(3) 93 | ] 94 | if '0' in display: 95 | for i in range(3): 96 | msg[i] += '\n\nScore: %f' % display[str(i)] 97 | if self.round == len(self.bid): 98 | status = ['\nBid %d points' % x for x in self.bid] 99 | while len(status) < 3: status.append('') 100 | else: 101 | status = [] 102 | for i in range(3): 103 | if '0' not in display and i == (cur + 1) % 3: 104 | status.append('\nNext') 105 | elif self.history[i] is None: 106 | status.append('') 107 | elif isinstance(self.history[i], str): 108 | status.append(self.history[i]) 109 | elif self.history[i]: 110 | status.append(self._get_cards_draw(self.history[i])) 111 | else: 112 | status.append('\nPass') 113 | table = Table(show_header = False, show_lines = True, box = box.SQUARE) 114 | for i in range(3): 115 | table.add_row(msg[i], self._get_cards_draw(self.hand[i]), status[i]) 116 | print('Round:', self.round) 117 | rprint(table) 118 | 119 | def _get_err_msg(self, errorInfo): 120 | return self.errMsg.get(errorInfo, self.defaultErrMsg) 121 | 122 | def _get_cards_draw(self, cards): 123 | if not cards: return '' 124 | table = Table(show_header = False, padding = 0, style = 'black on white', box = box.SQUARE) 125 | table.add_row(*(self.draw[card] for card in cards)) 126 | table.add_row(*(Text(' ', style = 'on red') if card in self.publiccard else ' ' for card in cards)) 127 | table.columns[-1].min_width = 6 128 | return table -------------------------------------------------------------------------------- /botzone/online/viewer/go.py: -------------------------------------------------------------------------------- 1 | from rich import box, print 2 | from rich.panel import Panel 3 | from rich.table import Table 4 | from rich.text import Text 5 | 6 | from botzone.online.viewer.viewer import TextViewer 7 | 8 | class GoTextViewer(TextViewer): 9 | ''' 10 | Empty string in first and pass round, 11 | {color, x, y, remove: [{x, y}]} in other rounds, 12 | {winner[, color, x, y][, stones: {'0', '1', komi}][, error, error_info]} in final round. 13 | ''' 14 | 15 | def __init__(self, size = 8): 16 | self.stones = ' ●○' 17 | self.size = size 18 | 19 | def reset(self, initdata = None): 20 | self.board = [[0 for j in range(self.size)] for i in range(self.size)] 21 | self.round = -1 22 | 23 | def render(self, displays, bootstrap = True): 24 | b = self.board 25 | x = y = -1 26 | # Recover state 27 | for display in displays: 28 | self.round += 1 29 | if not display: continue 30 | if 'x' not in display: continue 31 | x = display['x'] - 1 32 | y = display['y'] - 1 33 | color = display['color'] 34 | self.board[x][y] = color + 1 35 | for d in display['remove']: 36 | self.board[d['x'] - 1][d['y'] - 1] = 0 37 | 38 | message = 'Round: %d' % self.round 39 | if displays: 40 | if 'x' not in display: 41 | x = y = -1 42 | if 'winner' in display: 43 | message += '\n\nwinner: %s' % self.stones[display['winner'] + 1] 44 | if 'stones' in display: 45 | display = display['stones'] 46 | for i in range(2): 47 | message += ('\n\n%s: %f' % (self.stones[i + 1], display[str(i)])).rstrip('0').rstrip('.') 48 | message += ('\n\nkomi: %f' % display['komi']).rstrip('0').rstrip('.') 49 | if 'error_info' in display: 50 | message += '\n\nError: %s' % display['error_info'] 51 | elif 'x' not in display and self.round > 0: 52 | message += '\n\n%s pass!' % self.stones[2 - self.round % 2] 53 | else: 54 | message += '\n\nNext: %s' % self.stones[self.round % 2 + 1] 55 | t = Table.grid(padding = (0, 1)) 56 | t.add_row('', *map(chr, range(65, 65 + self.size))) 57 | for j in range(self.size): 58 | t.add_row(str(1 + j), *[Text(self.stones[b[i][j]], style = 'red') if i == x and j == y else self.stones[b[i][j]] for i in range(self.size)]) 59 | tt = Table.grid(padding = (0, 4)) 60 | tt.add_row(t, message) 61 | print(Panel.fit(tt, box = box.SQUARE)) -------------------------------------------------------------------------------- /botzone/online/viewer/gomoku.py: -------------------------------------------------------------------------------- 1 | from rich import box, print 2 | from rich.panel import Panel 3 | from rich.table import Table 4 | from rich.text import Text 5 | 6 | from botzone.online.viewer.viewer import TextViewer 7 | 8 | class GomokuTextViewer(TextViewer): 9 | ''' 10 | None in first round, {color, x, y} in other rounds, 11 | {color, x, y, winner=0/1/none} in final round. 12 | ''' 13 | 14 | def __init__(self, size = 15): 15 | self.stones = ' ●○' 16 | self.size = size 17 | 18 | def reset(self, initdata = None): 19 | self.board = [[0 for j in range(self.size)] for i in range(self.size)] 20 | self.round = -1 21 | 22 | def render(self, displays, bootstrap = True): 23 | b = self.board 24 | x = y = -1 25 | # Recover state 26 | for display in displays: 27 | self.round += 1 28 | if not display: continue 29 | if 'x' not in display: continue 30 | x = display['x'] 31 | y = display['y'] 32 | color = display['color'] 33 | self.board[x][y] = color + 1 34 | 35 | message = 'Round: %d' % self.round 36 | if displays and displays[-1]: 37 | winner = displays[-1].get('winner', None) 38 | else: 39 | winner = None 40 | if winner is not None: 41 | if isinstance(winner, int): 42 | message += '\n%s wins!' % self.stones[winner + 1] 43 | else: 44 | message += '\nA tie!' 45 | else: 46 | message += '\nNext: %s' % self.stones[self.round % 2 + 1] 47 | t = Table.grid(padding = (0, 1)) 48 | t.add_row('', *map(chr, range(65, 65 + self.size))) 49 | for j in range(self.size): 50 | t.add_row(str(1 + j), *[Text(self.stones[b[i][j]], style = 'red') if i == x and j == y else self.stones[b[i][j]] for i in range(self.size)]) 51 | tt = Table.grid(padding = (0, 4)) 52 | tt.add_row(t, message) 53 | print(Panel.fit(tt, box = box.SQUARE)) -------------------------------------------------------------------------------- /botzone/online/viewer/kingz.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from rich import box, print 3 | from rich.panel import Panel 4 | from rich.table import Table 5 | from rich.text import Text 6 | 7 | from botzone.online.viewer.viewer import TextViewer 8 | 9 | class KingzTextViewer(TextViewer): 10 | ''' 11 | { 12 | status: opening/combat/end/finish 13 | count: list of 100 int, 14 | type: list of 100 int, 15 | belong: list of 100 int, 16 | operation: list of 2 list of 4 int (x, y, w, d), 17 | [winner: int,] 18 | [err: message] 19 | } 20 | ''' 21 | 22 | def reset(self, initdata = None): 23 | self.color = ('default', 'bright_yellow', 'bright_cyan') 24 | self.round = -1 25 | 26 | def render(self, displays, bootstrap = True): 27 | operation = None 28 | display = {} 29 | for display in displays: 30 | self.round += 1 31 | status = display['status'] 32 | if status == 'opening': 33 | self.count = display['count'] 34 | self.info = display['type'] 35 | self.belong = display['belong'] 36 | continue 37 | operation = display.get('operation', None) 38 | if 'count' in display: 39 | self.count = display['count'] 40 | self.info = display['type'] 41 | self.belong = display['belong'] 42 | elif operation: 43 | # Process state 44 | battle = defaultdict(lambda : [0, 0, 0]) 45 | for p in range(2): 46 | x, y, w, d = operation[p] 47 | self.count[x * 10 + y] -= w 48 | x += (-1, 1, 0, 0, 0)[d] 49 | y += (0, 0, -1, 1, 0)[d] 50 | battle[(x, y)][p + 1] += w 51 | for x, y in battle: 52 | battle[(x, y)][self.belong[x * 10 + y]] += self.count[x * 10 + y] 53 | self.count[x * 10 + y] = 0 54 | for x, y in battle: 55 | neutral, red, blue = self._battle(*battle[(x, y)]) 56 | self.count[x * 10 + y] = neutral + red + blue 57 | if neutral: self.belong[x * 10 + y] = 0 58 | elif red: self.belong[x * 10 + y] = 1 59 | elif blue: self.belong[x * 10 + y] = 2 60 | for i in range(10): 61 | for j in range(10): 62 | if self.belong[i * 10 + j] == 0: continue 63 | if self.info[i * 10 + j] == 2: self.count[i * 10 + j] += 2 64 | if self.info[i * 10 + j] == 1: self.count[i * 10 + j] += 1 65 | if self.info[i * 10 + j] == 0 and self.round % 8 == 0: self.count[i * 10 + j] += 1 66 | 67 | troops = [0, 0, 0] 68 | growth = [0, 0, 0] 69 | for i in range(100): 70 | troops[self.belong[i]] += self.count[i] 71 | growth[self.belong[i]] += (0.125, 1, 2, 0)[self.info[i]] 72 | message = (Text('\nRound : %d\n\nTroops: ' % self.round) 73 | + Text(str(troops[1]), style = 'on ' + self.color[1]) 74 | + ' : ' 75 | + Text(str(troops[2]), style = 'on ' + self.color[2]) 76 | + ' : ' 77 | + Text(str(troops[0]), style = 'on ' + self.color[0]) 78 | + '\n\nGrowth: ' 79 | + Text(str(growth[1]), style = 'on ' + self.color[1]) 80 | + ' : ' 81 | + Text(str(growth[2]), style = 'on ' + self.color[2]) 82 | ) 83 | err = display.get('err', None) 84 | if err: message += '\n\n' + err 85 | 86 | draws = [[self._draw(self.info[i * 10 + j], self.belong[i * 10 + j], self.count[i * 10 + j]) 87 | for j in range(10)] 88 | for i in range(10)] 89 | if operation: 90 | for p in range(2): 91 | x, y, w, d = operation[p] 92 | if d < 4 and w: 93 | draws[x][y].style = 'red ' + draws[x][y].style 94 | x += (-1, 1, 0, 0, 0)[d] 95 | y += (0, 0, -1, 1, 0)[d] 96 | draws[x][y].style = 'green ' + draws[x][y].style 97 | t = Table.grid() 98 | for row in draws: t.add_row(*row) 99 | for column in t.columns: column.width = 5 100 | tt = Table.grid(padding = (0, 4)) 101 | tt.add_row(t, message) 102 | print(Panel.fit(tt, box = box.SQUARE)) 103 | 104 | def _battle(self, neutral, red, blue): 105 | t = min(red, blue) 106 | red -= t 107 | blue -= t 108 | if red > 0: 109 | t = min(red, neutral) 110 | red -= t 111 | neutral -= t 112 | else: 113 | t = min(blue, neutral) 114 | blue -= t 115 | neutral -= t 116 | return neutral, red, blue 117 | 118 | def _draw(self, info, belong, count): 119 | if info == -1: return Text('\n\n', justify = 'center', style = 'on bright_black') 120 | if info == 1: 121 | return Panel(Text(str(count), justify = 'center'), padding = 0, style = 'on ' + self.color[belong], box = box.SQUARE) 122 | if info == 2: 123 | return Panel(Text(str(count), justify = 'center'), padding = 0, style = 'on ' + self.color[belong], box = box.DOUBLE) 124 | return Text('\n%d\n' % count if count else '\n\n', justify = 'center', style = 'on ' + self.color[belong]) -------------------------------------------------------------------------------- /botzone/online/viewer/minesweeper.py: -------------------------------------------------------------------------------- 1 | import json 2 | from rich import box, print 3 | from rich.panel import Panel 4 | from rich.table import Table 5 | from rich.text import Text 6 | 7 | from botzone.online.viewer.viewer import TextViewer 8 | 9 | class MineSweeperTextViewer(TextViewer): 10 | ''' 11 | {status: [{row, col, val}], msg: FINISH/INVALIDMOVE} 12 | ''' 13 | 14 | def __init__(self): 15 | fgs = ['default', '#0000ff', '#008000', '#ff0000', '#000080', '#800000', '#008080', '#000000', '#808080', '#000000', 'default'] 16 | chars = [' ', '1', '2', '3', '4', '5', '6', '7', '8', '●', ' '] 17 | bgs = ['#c0c0c0'] * 10 + ['#ffffff'] 18 | self.draw = [Text(chars[i], style = '%s on %s' % (fgs[i], bgs[i])) for i in range(11)] 19 | 20 | def reset(self, initdata = None): 21 | if not initdata: initdata = {} 22 | self.width = initdata.get('width', 30) 23 | self.height = initdata.get('height', 20) 24 | self.mineleft = self.minecount = initdata.get('minecount', 80) 25 | self.board = [[10 for i in range(self.width)] for j in range(self.height)] 26 | self.round = -1 27 | 28 | def render(self, displays, bootstrap = True): 29 | b = self.board 30 | # Recover state 31 | for display in displays: 32 | self.round += 1 33 | if not display: continue 34 | status = display.get('status', None) 35 | if not status: status = [] 36 | for pos in status: 37 | x = pos['row'] 38 | y = pos['col'] 39 | val = pos['val'] 40 | b[x][y] = val 41 | if val == 9: self.mineleft -= 1 42 | 43 | message = 'Round: %d\nMines total: %d\nMines left: %d' % ( 44 | self.round, 45 | self.minecount, 46 | self.mineleft 47 | ) 48 | if displays: 49 | d = displays[-1] 50 | if not d: d = {} 51 | if 'msg' in d: 52 | if d['msg'] == 'INVALIDMOVE': 53 | message += '\nInvalid move!' 54 | elif d['msg'] == 'FINISH': 55 | message += '\nFinish!' 56 | t = Table.grid() 57 | for x in range(self.height): 58 | t.add_row(*(self.draw[b[x][y]] for y in range(self.width))) 59 | tt = Table.grid(padding = (0, 4)) 60 | tt.add_row(t, message) 61 | print(Panel.fit(tt, box = box.SQUARE)) -------------------------------------------------------------------------------- /botzone/online/viewer/nogo.py: -------------------------------------------------------------------------------- 1 | from rich import box, print 2 | from rich.panel import Panel 3 | from rich.table import Table 4 | from rich.text import Text 5 | 6 | from botzone.online.viewer.viewer import TextViewer 7 | 8 | class NoGoTextViewer(TextViewer): 9 | ''' 10 | Empty string in first and pass round, 11 | {color, x, y} in other rounds, 12 | {winner[, color, x, y][, err]} in final round. 13 | ''' 14 | 15 | def __init__(self, size = 9): 16 | self.stones = ' ●○' 17 | self.size = size 18 | 19 | def reset(self, initdata = None): 20 | self.board = [[0 for j in range(self.size)] for i in range(self.size)] 21 | self.round = -1 22 | 23 | def render(self, displays, bootstrap = True): 24 | b = self.board 25 | x = y = -1 26 | # Recover state 27 | for display in displays: 28 | self.round += 1 29 | if not display: continue 30 | if 'x' not in display: continue 31 | x = display['x'] 32 | y = display['y'] 33 | color = display['color'] 34 | self.board[x][y] = color + 1 35 | 36 | message = 'Round: %d' % self.round 37 | if displays: 38 | if 'x' not in display: 39 | x = y = -1 40 | if 'winner' in display: 41 | message += '\n\nwinner: %s' % self.stones[display['winner'] + 1] 42 | if 'err' in display: 43 | message += '\n\nError: %s' % display['err'] 44 | else: 45 | message += '\n\nNext: %s' % self.stones[self.round % 2 + 1] 46 | t = Table.grid(padding = (0, 1)) 47 | t.add_row('', *map(chr, range(65, 65 + self.size))) 48 | for j in range(self.size): 49 | t.add_row(str(1 + j), *[Text(self.stones[b[i][j]], style = 'red') if i == x and j == y else self.stones[b[i][j]] for i in range(self.size)]) 50 | tt = Table.grid(padding = (0, 4)) 51 | tt.add_row(t, message) 52 | print(Panel.fit(tt, box = box.SQUARE)) -------------------------------------------------------------------------------- /botzone/online/viewer/renju.py: -------------------------------------------------------------------------------- 1 | from rich import box, print 2 | from rich.panel import Panel 3 | from rich.table import Table 4 | from rich.text import Text 5 | 6 | from botzone.online.viewer.viewer import TextViewer 7 | 8 | class RenjuTextViewer(TextViewer): 9 | ''' 10 | None in first round, {color, x, y} in other rounds, 11 | {color, x, y, winner=0/1/none} in final round. 12 | ''' 13 | 14 | def __init__(self, size = 15): 15 | self.stones = ' ●○' 16 | self.size = size 17 | 18 | def reset(self, initdata = None): 19 | self.board = [[0 for j in range(self.size)] for i in range(self.size)] 20 | self.round = -1 21 | 22 | def render(self, displays, bootstrap = True): 23 | b = self.board 24 | x = y = -1 25 | # Recover state 26 | for display in displays: 27 | self.round += 1 28 | if not display: continue 29 | if display.get('msg', None) == 'exchange': 30 | # exchange 31 | for i in range(self.size): 32 | for j in range(self.size): 33 | if self.board[i][j]: 34 | self.board[i][j] = 3 - self.board[i][j] 35 | if 'x' not in display: continue 36 | x = display['x'] 37 | y = display['y'] 38 | color = display['color'] 39 | self.board[x][y] = color + 1 40 | 41 | message = 'Round: %d' % self.round 42 | if displays and displays[-1]: 43 | winner = displays[-1].get('winner', None) 44 | if displays[-1].get('msg', None) == 'exchange': 45 | message += '\n%s exchanges!' % self.stones[2] 46 | else: 47 | winner = None 48 | if winner is not None: 49 | if isinstance(winner, int): 50 | message += '\n%s wins!' % self.stones[winner + 1] 51 | else: 52 | message += '\nA tie!' 53 | else: 54 | message += '\nNext: %s' % self.stones[self.round % 2 + 1] 55 | t = Table.grid(padding = (0, 1)) 56 | t.add_row('', *map(chr, range(65, 65 + self.size))) 57 | for j in range(self.size): 58 | t.add_row(str(1 + j), *[Text(self.stones[b[i][j]], style = 'red') if i == x and j == y else self.stones[b[i][j]] for i in range(self.size)]) 59 | tt = Table.grid(padding = (0, 4)) 60 | tt.add_row(t, message) 61 | print(Panel.fit(tt, box = box.SQUARE)) -------------------------------------------------------------------------------- /botzone/online/viewer/reversi.py: -------------------------------------------------------------------------------- 1 | from rich import box, print 2 | from rich.panel import Panel 3 | from rich.table import Table 4 | from rich.text import Text 5 | 6 | from botzone.online.viewer.viewer import TextViewer 7 | 8 | class ReversiTextViewer(TextViewer): 9 | ''' 10 | Empty string in first round, {x=-1, y=-1} in skipped round, {x, y, winner} in other rounds. 11 | ''' 12 | 13 | def __init__(self): 14 | self.stones = ' ●○' 15 | 16 | def reset(self, initdata = None): 17 | self.board = b = [[0 for j in range(8)] for i in range(8)] 18 | self.round = -1 19 | self.count = [0, 2, 2] 20 | b[3][3] = b[4][4] = 2 # 2 for white 21 | b[3][4] = b[4][3] = 1 # 1 for black 22 | 23 | def render(self, displays, bootstrap = True): 24 | b = self.board 25 | # Recover state 26 | for display in displays: 27 | self.round += 1 28 | if not display: display = {} 29 | change = [] 30 | if 'x' not in display: continue 31 | if 'x' in display: 32 | x = display['x'] 33 | y = display['y'] 34 | if x == -1: continue 35 | color = 2 - self.round % 2 36 | b[x][y] = color 37 | self.count[color] += 1 38 | for dx, dy in ((-1, -1), (-1, 0), (-1, 1), (0, -1), (0, 1), (1, -1), (1, 0), (1, 1)): 39 | t = [] 40 | nx = x + dx 41 | ny = y + dy 42 | while self._in_board(nx, ny) and b[nx][ny] == 3 - color: 43 | t.append((nx, ny)) 44 | nx += dx 45 | ny += dy 46 | if not (self._in_board(nx, ny) and b[nx][ny] == color): continue 47 | change.extend(t) 48 | for nx, ny in change: 49 | b[nx][ny] = color 50 | self.count[color] += len(change) 51 | self.count[3 - color] -= len(change) 52 | 53 | message = 'Round: %d\n%s: %d\n%s: %d\nNext: %s' % ( 54 | self.round, 55 | self.stones[1], 56 | self.count[1], 57 | self.stones[2], 58 | self.count[2], 59 | self.stones[1 + self.round % 2] 60 | ) 61 | style_cur = lambda s: Text(s, style = 'red') 62 | style_change = lambda s: Text(s, style = 'yellow') 63 | style_normal = lambda s: s 64 | styles = [[style_normal for i in range(8)] for j in range(8)] 65 | if displays: 66 | d = displays[-1] 67 | if not d: d = {} 68 | x = d.get('x', -1) 69 | y = d.get('y', -1) 70 | if x == -1 and self.round: 71 | message += '\n%s skipped!' % self.stones[2 - self.round % 2] 72 | else: 73 | styles[x][y] = style_cur 74 | if 'winner' in d: 75 | message += '\n%s wins!' % self.stones[d['winner'] + 1] 76 | for x, y in change: styles[x][y] = style_change 77 | t = Table.grid(padding = (0, 1)) 78 | t.add_row('', *map(chr, range(65, 65 + 8))) 79 | for j in range(8): 80 | t.add_row(str(1 + j), *[styles[i][j](self.stones[b[i][j]]) for i in range(8)]) 81 | tt = Table.grid(padding = (0, 4)) 82 | tt.add_row(t, message) 83 | print(Panel.fit(tt, box = box.SQUARE)) 84 | 85 | def _in_board(self, x, y): 86 | return 0 <= x < 8 and 0 <= y < 8 -------------------------------------------------------------------------------- /botzone/online/viewer/snake.py: -------------------------------------------------------------------------------- 1 | from rich import box 2 | from rich import print 3 | from rich.panel import Panel 4 | from rich.table import Table 5 | from rich.text import Text 6 | 7 | from botzone.online.viewer.viewer import TextViewer 8 | 9 | class SnakeTextViewer(TextViewer): 10 | ''' 11 | First round: {'0': {x, y}, '1': {x, y}, width, height, obstacle: list of {x, y}} 12 | Other rounds: {'0': 0..3, '1': 0..3, 'grow': 'true'/'false'} 13 | Last round in case of crash: {['0': 0..3], ['1': 0..3], 'grow': 'true'/'false', 'winner', 'err'} 14 | ''' 15 | 16 | def __init__(self): 17 | self.dx = [-1, 0, 1, 0] 18 | self.dy = [0, 1, 0, -1] 19 | self.draw_init = ['├:', ':┤'] 20 | self.draw_head = [' !', ':─', ' ¡', '─:'] 21 | self.draw_tail = [' ┴', '├─', ' ┬', '─┤'] 22 | self.draw_body = {3 : ' └', 5 : ' │', 9 : '─┘', 6 : ' ┌', 12 : '─┐', 10 : '──'} 23 | self.draw_style = ['on green', 'on red'] 24 | pass 25 | 26 | def reset(self, initdata = None): 27 | self.round = -1 28 | 29 | def render(self, displays, bootstrap = True): 30 | # Recover state 31 | display = {} 32 | for display in displays: 33 | self.round += 1 34 | if self.round == 0: 35 | self.width = display['width'] 36 | self.height = display['height'] 37 | self.board = [[0 for i in range(self.height)] for j in range(self.width)] 38 | for p in display['obstacle']: 39 | self.board[p['y'] - 1][p['x'] - 1] = 1 40 | self.body = [[], []] 41 | for i in range(2): 42 | p = display[str(i)] 43 | self.body[i].append((p['y'] - 1, p['x'] - 1)) 44 | else: 45 | for i in range(2): 46 | x, y = self.body[i][-1] 47 | if str(i) in display: 48 | d = 3 - display[str(i)] 49 | self.body[i].append((x + self.dx[d], y + self.dy[d])) 50 | if display['grow'] == 'false': 51 | self.body[i].pop(0) 52 | grid = [['▓▓' if i else ' ' for i in row] for row in self.board] 53 | for i in range(2): 54 | body = self.body[i] 55 | l = len(body) 56 | if l == 1: 57 | x, y = body[0] 58 | # draw initial 59 | grid[x][y] = Text(self.draw_init[i], style = self.draw_style[i]) 60 | continue 61 | # draw head 62 | x, y = body[-1] 63 | if 0 <= x < self.width and 0 <= y < self.height: 64 | # in case of invalid move 65 | grid[x][y] = Text(self.draw_head[self._direction(body[-1], body[-2])], style = self.draw_style[i]) 66 | # draw body 67 | for j in range(1, l - 1): 68 | x, y = body[j] 69 | grid[x][y] = Text(self.draw_body.get((1 << self._direction(body[j], body[j - 1])) + (1 << self._direction(body[j], body[j + 1])), '??'), style = self.draw_style[i]) 70 | # draw tail 71 | x, y = body[0] 72 | grid[x][y] = Text(self.draw_tail[self._direction(body[0], body[1])], style = self.draw_style[i]) 73 | message = '\n\nRound: %d\n\nLength: %d' % (self.round, len(self.body[0])) 74 | if 'winner' in display: 75 | message += '\n\nPlayer %s wins!' % display['winner'] 76 | t = Table.grid() 77 | for row in grid: t.add_row(*row) 78 | t = Panel.fit(t, box = box.SQUARE, padding = 0) 79 | tt = Table.grid(padding = (0, 4)) 80 | tt.add_row(t, message) 81 | print(tt) 82 | 83 | def _direction(self, p1, p2): 84 | x, y = p1 85 | for k in range(4): 86 | if (x + self.dx[k], y + self.dy[k]) == p2: 87 | return k -------------------------------------------------------------------------------- /botzone/online/viewer/tictactoe.py: -------------------------------------------------------------------------------- 1 | from rich import box, print 2 | from rich.panel import Panel 3 | from rich.table import Table 4 | from rich.text import Text 5 | 6 | from botzone.online.viewer.viewer import TextViewer 7 | 8 | class TicTacToeTextViewer(TextViewer): 9 | ''' 10 | Empty string in first round, 11 | {x, y} in other rounds, 12 | {winner[, x, y][, err]} in final round. 13 | ''' 14 | 15 | def __init__(self): 16 | self.stones = ' OX' 17 | 18 | def reset(self, initdata = None): 19 | self.board = [[0 for j in range(3)] for i in range(3)] 20 | self.round = -1 21 | 22 | def render(self, displays, bootstrap = True): 23 | b = self.board 24 | x = y = -1 25 | # Recover state 26 | for display in displays: 27 | self.round += 1 28 | if not display: continue 29 | if 'x' not in display: continue 30 | x = display['x'] 31 | y = display['y'] 32 | color = 2 - self.round % 2 33 | self.board[x][y] = color 34 | 35 | message = 'Round: %d' % self.round 36 | if displays and displays[-1]: 37 | winner = displays[-1].get('winner', None) 38 | elif self.round == 9: 39 | winner = '' 40 | else: 41 | winner = None 42 | if winner is not None: 43 | if isinstance(winner, int): 44 | message += '\n%s wins!' % self.stones[winner + 1] 45 | else: 46 | message += '\nA tie!' 47 | else: 48 | message += '\nNext: %s' % self.stones[self.round % 2 + 1] 49 | t = Table.grid(padding = (0, 1)) 50 | t.add_row('', *map(chr, range(65, 65 + 3))) 51 | for j in range(3): 52 | t.add_row(str(1 + j), *[Text(self.stones[b[i][j]], style = 'red') if i == x and j == y else self.stones[b[i][j]] for i in range(3)]) 53 | tt = Table.grid(padding = (0, 4)) 54 | tt.add_row(t, message) 55 | print(Panel.fit(tt, box = box.SQUARE)) -------------------------------------------------------------------------------- /botzone/online/viewer/viewer.py: -------------------------------------------------------------------------------- 1 | class Viewer(object): 2 | 3 | def reset(self, initdata = None): 4 | raise NotImplementedError 5 | 6 | def render(self, displays, bootstrap): 7 | raise NotImplementedError 8 | 9 | class TextViewer(Viewer): 10 | 11 | def reset(self, initdata = None): 12 | raise NotImplementedError 13 | 14 | def render(self, displays, bootstrap = True): 15 | raise NotImplementedError 16 | 17 | def make(name, *args, **kargs): 18 | if name == 'Reversi': 19 | from botzone.online.viewer.reversi import ReversiTextViewer 20 | return ReversiTextViewer(*args, **kargs) 21 | elif name == 'Minesweeper': 22 | from botzone.online.viewer.minesweeper import MineSweeperTextViewer 23 | return MineSweeperTextViewer(*args, **kargs) 24 | elif name == 'Gomoku': 25 | from botzone.online.viewer.gomoku import GomokuTextViewer 26 | return GomokuTextViewer(*args, **kargs) 27 | elif name == 'Renju': 28 | from botzone.online.viewer.renju import RenjuTextViewer 29 | return RenjuTextViewer(*args, **kargs) 30 | elif name == 'FightTheLandlord': 31 | from botzone.online.viewer.fightthelandlord import FightTheLandlordTextViewer 32 | return FightTheLandlordTextViewer(*args, **kargs) 33 | elif name == 'Snake': 34 | from botzone.online.viewer.snake import SnakeTextViewer 35 | return SnakeTextViewer(*args, **kargs) 36 | elif name == 'Ataxx': 37 | from botzone.online.viewer.ataxx import AtaxxTextViewer 38 | return AtaxxTextViewer(*args, **kargs) 39 | elif name == 'Amazons': 40 | from botzone.online.viewer.amazons import AmazonsTextViewer 41 | return AmazonsTextViewer(*args, **kargs) 42 | elif name == 'Go': 43 | from botzone.online.viewer.go import GoTextViewer 44 | return GoTextViewer(*args, **kargs) 45 | elif name == 'NoGo': 46 | from botzone.online.viewer.nogo import NoGoTextViewer 47 | return NoGoTextViewer(*args, **kargs) 48 | elif name == 'TicTacToe': 49 | from botzone.online.viewer.tictactoe import TicTacToeTextViewer 50 | return TicTacToeTextViewer(*args, **kargs) 51 | elif name == 'ChineseStandardMahjong': 52 | from botzone.online.viewer.chinesestandardmahjong import ChineseStandardMahjongTextViewer 53 | return ChineseStandardMahjongTextViewer(*args, **kargs) 54 | elif name == 'Kingz': 55 | from botzone.online.viewer.kingz import KingzTextViewer 56 | return KingzTextViewer(*args, **kargs) 57 | elif name == 'FightTheLandlord2': 58 | from botzone.online.viewer.fightthelandlord2 import FightTheLandlord2TextViewer 59 | return FightTheLandlord2TextViewer(*args, **kargs) 60 | return None -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | def runMatch(env, bots): 2 | env.init(bots) 3 | score = env.reset() 4 | env.render() 5 | while score is None: 6 | score = env.step() 7 | env.render() 8 | else: 9 | print('Score:', score) 10 | 11 | def testInterface(): 12 | try: 13 | import botzone 14 | env_wrap = botzone.make('TicTacToe-wrap') 15 | env_impl = botzone.make('TicTacToe-v0') 16 | 17 | from botzone.online.bot import BotConfig, Bot 18 | bot_cpp = Bot(BotConfig.fromID('6017e6b10b59850f61c42b0a')) # cpp17+json+keep_running 19 | bot_pas = Bot(BotConfig.fromID('5ff96b0d38843939e7477556')) # pas+simple_io+no_keep_running 20 | from botzone.agents.tictactoe import TicTacToeAgent 21 | bot_impl = TicTacToeAgent() 22 | 23 | print('Using wrapper env + wrapper bot:') 24 | runMatch(env_wrap, [bot_cpp, bot_pas]) 25 | 26 | print('Using wrapper env + python bot:') 27 | runMatch(env_wrap, [bot_impl, bot_impl]) 28 | 29 | print('Using python env + wrapper bot:') 30 | runMatch(env_impl, [bot_cpp, bot_pas]) 31 | 32 | print('Using python env + python bot:') 33 | runMatch(env_impl, [bot_impl, bot_impl]) 34 | finally: 35 | env_wrap.close() 36 | bot_cpp.close() 37 | bot_pas.close() 38 | 39 | def testBot(): 40 | try: 41 | from botzone.online.bot import BotConfig, Bot 42 | bots = [] 43 | bots.append(Bot(BotConfig.fromID('5ff9514438843939e7476086'))) # cpp17 44 | bots.append(Bot(BotConfig.fromID('5ff952f438843939e7476202'))) # cpp17a 45 | bots.append(Bot(BotConfig.fromID('5ff9528a38843939e74761c5'))) # py 46 | bots.append(Bot(BotConfig.fromID('601cecd85d7325256064e52d'))) # py3 47 | bots.append(Bot(BotConfig.fromID('5ff9534b38843939e7476296'))) # py36 48 | bots.append(Bot(BotConfig.fromID('5ff9650038843939e7476f1a'))) # java 49 | bots.append(Bot(BotConfig.fromID('5ff95d8738843939e74769b9'))) # js 50 | bots.append(Bot(BotConfig.fromID('5ff9599638843939e74766cb'))) # cs 51 | bots.append(Bot(BotConfig.fromID('5ff96b0d38843939e7477556'))) # pas 52 | 53 | import botzone 54 | env = botzone.make('TicTacToe-v0') 55 | from botzone.agents.tictactoe import TicTacToeAgent 56 | bot_impl = TicTacToeAgent() 57 | 58 | for bot in bots: 59 | print('Trying bot with language', bot.config.extension) 60 | runMatch(env, [bot_impl, bot]) 61 | finally: 62 | for bot in bots: 63 | bot.close() 64 | 65 | def testGame(): 66 | samples = { 67 | 'Amazons': '5bd136e00681335cc1f4d000', 68 | 'Ataxx': '58d8c01327d1065e145ee3f7', 69 | 'ChineseStandardMahjong': '5e918db24a3d0c0568f34083', 70 | 'FightTheLandlord': '5af06df6a5858d0880e515f2', 71 | 'Go': '5b00f35d765c7d10b6600016', 72 | 'Gomoku': '543b462e71a5d5647363cbb0', 73 | 'Kingz': '5e3539ae4019f43051e2e3f5', 74 | 'Minesweeper': '547e71f4012273d146ac0e2d', 75 | 'NoGo': '5fede20fd9383f7579afff06', 76 | 'Pacman': '5724f78f8b8b8ad504b23e01', 77 | 'Pacman2': '57fdc7900167d91505cda684', 78 | 'Renju': '5444a5414319ba6866ff41ff', 79 | 'Reversi': '5afe9e6f6cc5394385d40062', 80 | 'Snake': '555ef3aa7110330007712994', 81 | 'Tank': '5bc836510681335cc1ef467b', 82 | 'Tank2': '5cb5abf18aa8bb6e75cc9b84', 83 | 'Tank2S': '5cd5818a86d50d05a0095859', 84 | 'Tetris': '58fddf2233b28604c34e2af5', 85 | 'Tetris2': '59f860eb5a11ed72c933561e', 86 | 'TicTacToe': '5ff9514438843939e7476086' 87 | } 88 | envs = { 89 | 'Amazons': 'Amazons-v8', 90 | 'Ataxx': 'Ataxx-v0', 91 | 'ChineseStandardMahjong': 'ChineseStandardMahjong-v0-dup', 92 | 'FightTheLandlord': 'FightTheLandlord-v0', 93 | 'Go': 'Go-v8', 94 | 'Gomoku': 'Gomoku-v15', 95 | 'Kingz': 'Kingz-v0', 96 | 'Minesweeper': 'MineSweeper-v0', 97 | 'NoGo': 'NoGo-v9', 98 | 'Pacman': 'Pacman-wrap', 99 | 'Pacman2': 'Pacman2-wrap', 100 | 'Renju': 'Renju-v15', 101 | 'Reversi': 'Reversi-v0', 102 | 'Snake': 'Snake-v0', 103 | 'Tank': 'Tank-wrap', 104 | 'Tank2': 'Tank2-wrap', 105 | 'Tank2S': 'Tank2S-wrap', 106 | 'Tetris': 'Tetris-wrap', 107 | 'Tetris2': 'Tetris2-wrap', 108 | 'TicTacToe': 'TicTacToe-v0' 109 | } 110 | import botzone 111 | from botzone.online.bot import BotConfig, Bot 112 | for game in sorted(samples.keys()): 113 | print('Trying game', game) 114 | try: 115 | env = botzone.make(envs[game]) 116 | bots = [Bot(BotConfig.fromID(samples[game])) for i in range(env.player_num)] 117 | runMatch(env, bots) 118 | finally: 119 | env.close() 120 | for bot in bots: 121 | bot.close() 122 | 123 | def testMahjong(): 124 | top4 = ['5fed4d34d9383f7579af41fc', '5f0fc0f37e197a05f07f616a', '5f9d308c8121aa566529ab03', '5fe2cc74d9383f7579a4334b'] 125 | from botzone.online.sandbox import SandBox 126 | SandBox.CONFIG_TIME_RATIO = 3 127 | try: 128 | import botzone 129 | env = botzone.make('ChineseStandardMahjong-v0-dup') 130 | from botzone.online.bot import BotConfig, Bot 131 | bots = [Bot(BotConfig.fromID(id, userfile = True)) for id in top4] 132 | runMatch(env, bots) 133 | finally: 134 | env.close() 135 | for bot in bots: 136 | bot.close() 137 | 138 | if __name__ == '__main__': 139 | testInterface() 140 | testBot() 141 | testGame() 142 | testMahjong() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests 2 | PyExecJS 3 | rich>=10.0.0 4 | PyMahjongGB --------------------------------------------------------------------------------