本地AI - Botzone Wiki
本地AI
本地AI是在用户电脑上运行的AI。Botzone允许本地AI与平台进行连接来代替人类玩家,从而达到在本地运行耗时较长算法的目的。
你需要手动创建游戏桌或参与游戏桌,并勾选“用本地AI替代我”,在对局开始后本地AI即可开始交互。
本功能尚未完善,样例和接口都可能发生变化,敬请留意!
目录
[隐藏]
交互方式
API的地址是形如https://www.botzone.org/api/【用户ID】/【密钥】/localai
的URL。
你的程序需要向该网址发送GET请求,然后在该接口返回后对相应对局的request做出response。该接口可能不会立即返回,而是直到有新的request或者超时的时候才会返回。
返回的文本中,第一行是两个数字,分别代表有request的对局数m和结束了的对局数n。
接下来是2*m行,每两行为一组,第一行是对局ID,第二行是本回合的request。这里会优先给出简单IO形式的request。
接下来是n行,每行是由空格分隔的若干元素。第一个元素是结束了的对局ID;第二个元素是一个整数,表示自己在该对局中的位置;第三个元素是一个整数,表示对局玩家数p(0表示对局是意外终止的);接下来的p个数字是这p个玩家的分数。
读取这些之后,你的程序应该根据request做出决策,生成response。
当程序准备好response之后,你的程序应当将response放在GET请求的Header里,形如X-Match-【对局ID】: 【response】
,比如:
X-Match-594553a2630d4c0758e2b2b1: 0 0 1 2 X-Match-594553a2630d4c0758e2b2b2: 0 0 2 2
快速创建对局
为了方便程序主动创建对局,我们同时还提供了创建对局的API,地址是形如https://www.botzone.org/api/【用户ID】/【密钥】/runmatch
的URL。
你可以复制本地AI的URL,然后将最后一段改成runmatch。
请注意该API仅允许本地AI与Botzone的现有AI进行对局,而且除了不需要验证码,也有着如创建游戏桌一样的相关限制。
你可以让你的程序或者脚本向该地址发起GET请求,请求需要有如下Header:
X-Game: 游戏名 X-Player-0: BotID(表示该位置放置一个现有AI)或者me(表示该位置放置你自己的本地AI) X-Player-1: 同上 ... X-Player-n: 同上 X-Initdata: (可选)初始化数据
比如:
X-Game: Reversi X-Player-0: 59d48afab4ae97385a73149e X-Player-1: me
指定的玩家中必须有且只有一个是me。
如果创建成功,接口会返回新创建的对局ID。
如果创建失败,接口会提示错误原因。
样例代码
请记得将代码中的https://www.botzone.org/api/53684e48a4428bf021bd2f16/test/localai
替换为 Botzone 上提示的地址!
Python 3.x 版本
''' Connects to Botzone ''' import urllib.request import time class Match: has_request = False has_response = False current_request = None current_response = None matchid = None def new_request(self, request): self.has_request = True self.has_response = False self.current_request = request # TODO:定义一种特化的对局数据类,比如存储棋盘状态等 class SomeKindOfMatch(Match): def __init__(self, matchid, first_request): self.has_request = True self.current_request = first_request self.matchid = matchid matches = {} # 从 Botzone 上拉取新的对局请求 def fetch(matchClass): req = urllib.request.Request("https://www.botzone.org/api/53684e48a4428bf021bd2f16/test/localai") for matchid, m in matches.items(): if m.has_response and m.has_request and m.current_response: print('> Response for match [%s]: %s' % (matchid, m.current_response)) m.has_request = False req.add_header("X-Match-" + matchid, m.current_response) while True: try: res = urllib.request.urlopen(req, timeout=None) botzone_input = res.read().decode() lines = botzone_input.split('\n') request_count, result_count = map(int, lines[0].split(' ')) for i in range(0, request_count): # 新的 Request matchid = lines[i * 2 + 1] request = lines[i * 2 + 2] if matchid in matches: print('Request for match [%s]: %s' % (matchid, request)) matches[matchid].new_request(request) else: print('New match [%s] with first request: %s' % (matchid, request)) matches[matchid] = matchClass(matchid, request) for i in range(0, result_count): # 结束的对局结果 matchid, slot, player_count, *scores = lines[request_count * 2 + 1 + i].split(' ') if player_count == "0": print("Match [%s] aborted:\n> I'm player %s" % (matchid, slot)) else: print("Match [%s] finished:\n> I'm player %s, and the scores are %s" % (matchid, slot, scores)) matches.pop(matchid) except (urllib.error.URLError, urllib.error.HTTPError): # 此时可能是长时间没有新的 request 导致连接超时,再试即可 print("Error reading from Botzone or timeout, retrying 5 seconds later...") time.sleep(5) continue break if __name__=='__main__': while True: fetch(SomeKindOfMatch) for mid, m in matches.items(): # 使用 m.current_request 模拟一步对局状态,然后产生动作 # 将自己的动作存入 m.current_response,同样进行一步模拟 m.has_response = True
C++ Windows 版本
#include <Windows.h> #include <Wininet.h> #pragma comment (lib, "Wininet.lib") #include <iostream> #include <sstream> #include <string> #include <map> #define MAX_BUF 8192 #define MAX_PLAYER_LEN 16 using namespace std; struct Match { bool hasRequest; string lastRequest; bool hasResponse; string currResponse; }; map<string, Match> matches; namespace BotzoneAPI { HINTERNET hInetRoot; char buf[MAX_BUF]; int NO_TIMEOUT = ~0; void Init() { hInetRoot = InternetOpen(NULL, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); if (!hInetRoot || !InternetSetOption(hInetRoot, INTERNET_OPTION_CONNECT_TIMEOUT, &NO_TIMEOUT, sizeof(NO_TIMEOUT))) { cerr << "Error starting internet session"; exit(-1); } } void ProcessInput(istream &is) { int requestCount, resultCount, i; string matchid, garbage; is >> requestCount >> resultCount; getline(is, garbage); for (i = 0; i < requestCount; i++) { getline(is, matchid); auto &match = matches[matchid]; getline(is, match.lastRequest); match.hasRequest = true; match.hasResponse = false; cout << "New request for [" << matchid << "]:" << match.lastRequest << endl; } for (i = 0; i < resultCount; i++) { int slot, len, j, score; is >> matchid >> slot >> len; if (len == 0) cout << "Match [" << matchid << "] aborted:" << endl << "I'm player " << slot << endl; else { cout << "Match [" << matchid << "] finished:" << endl << "I'm player " << slot << ", and the scores are"; for (j = 0; j < len; j++) { is >> score; cout << " " << score; } cout << endl; } matches.erase(matchid); getline(is, garbage); } } void FetchNewRequest() { cout << "Waiting for new request..." << endl; string headers; for (auto &pair : matches) if (pair.second.hasResponse && pair.second.hasRequest && pair.second.currResponse.size() > 0) { pair.second.hasRequest = false; headers += "X-Match-" + pair.first + ": " + pair.second.currResponse + "\r\n"; } // 等待对局 while (true) { auto hReq = InternetOpenUrl( hInetRoot, "http://localhost:15233/api/53684e48a4428bf021bd2f16/MY_SECRET/localai", headers.c_str(), headers.size(), 0, NULL); if (!hReq) { // 此时可能是长时间没有新的 request 导致连接超时,再试即可 Sleep(2000); continue; } DWORD len; if (!InternetReadFile(hReq, buf, MAX_BUF, &len) || len == 0) { cerr << "Error reading from Botzone, retrying 5 seconds later..." << endl; Sleep(5000); continue; } buf[len] = '\0'; istringstream iss(buf); ProcessInput(iss); break; } } } int main() { BotzoneAPI::Init(); while (true) { BotzoneAPI::FetchNewRequest(); for (auto &pair : matches) { if (pair.second.hasRequest && !pair.second.hasResponse) { cout << "Please enter the response for match [" << pair.first << "]:" << endl; getline(cin, pair.second.currResponse); pair.second.hasResponse = true; } } } }