// Hyperbolic Rogue -- Go // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details // compile the Discord version: // ./mymake rogueviz/gobot.cpp -std=c++17 -lssl -lz -lcrypto -I rogueviz/aegis/include/ -I rogueviz/aegis/lib/json/include/ -I rogueviz/aegis/lib/spdlog/include/ -I rogueviz/aegis/lib/websocketpp/ -I rogueviz/aegis/lib/asio/asio/include/ // to run on an interesting board, run RogueViz with parameters: // -canvas i -fillmodel ff801080 -noscr -geo Bring -gp 5 1 -unrectified -go-local -smart 1 // (add -run before -go-local if you want to select your board manually (press F10 after selecting the board) // run online: ./hyper-go -canvas i -fillmodel ff801080 -noscr -geo Bring -gp 5 1 -unrectified -smart 3 -shot-1000 -shotxy 500 500 -shott 1 -gobot -go-discord /** \file gobot.cpp * \brief bot to play Go via Discord */ #define AEGIS 0 #if AEGIS #include #endif #include "rogueviz.h" namespace hr { #if CAP_THREAD EX namespace gobot { eWall empty = waChasm; bool in = false; vector ac; map indices; const int Free = 2; const int Unowned = 3; struct boarddata { vector taken, owner; array captures; string geom; }; boarddata current; int labels_value = 1; vector history; bool draw_go(cell *c, const shiftmatrix& V); void init_go_board() { ac = currentmap->allcells(); current.taken.clear(); current.owner.clear(); current.taken.resize(isize(ac), 2); current.owner.resize(isize(ac), 2); current.captures[0] = 0; current.captures[1] = 0; shstream f; mapstream::save_geometry(f); current.geom = f.s; for(int i=0; i neigh_indices(int i) { vector res; forCellEx(c1, ac[i]) if(indices.count(c1)) res.push_back(indices[c1]); return res; } string chars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ123456789"; bool valid_index(int q) { return q >= 0 && q < isize(ac); } string index_to_str(int k) { if(k < isize(chars)) return s0 + chars[k]; else return chars[k % isize(chars)] + index_to_str(k / isize(chars) - 1); } int str_to_index(string s) { int val = -1; for(int i=0; iwall == waSea) c->wall = waChasm; if(!indices.count(c)) return false; int id = indices[c]; int lv_needed; forCellIdCM(c1, i, c) if(indices.count(c1) && indices[c1] < id) { vid.linewidth *= 2; gridline(V, C0, currentmap->adj(c, i) * C0, 0xFF, 4); vid.linewidth /= 2; } if(current.taken[id] != 2) { vid.linewidth *= 2; poly_outline = 0xFF; queuepolyat(V, cgi.shHugeDisk, player_colors[current.taken[id]], PPR::SUPERLINE); vid.linewidth /= 2; lv_needed = 3; } else if(current.owner[id] == 2) { lv_needed = 1; } else { queuepoly(V, cgi.shGem[0], player_colors[current.owner[id]]); lv_needed = 2; } if(labels_value >= lv_needed) { string s = index_to_str(id); queuestr(V, isize(s) == 1 ? 0.8 : 0.5, s, 0xFFD500); } return false; } void save_backup() { history.push_back(current); } void undo() { if(current.geom != history.back().geom) { shstream f; f.s = history.back().geom; stop_game(); mapstream::load_geometry(f); start_game(); init_go(); } current = history.back(); history.pop_back(); } #if AEGIS dpp::message_create_t* cur; dpp::cluster *pbot; #endif int shot_state; #if AEGIS // std::vector > old_shots; #endif void clean_old_shots() { /* std::vector > remaining; for(auto& sh: old_shots) if(sh.available()) { sh.get().delete_message(); } else remaining.emplace_back(std::move(sh)); old_shots = std::move(remaining); */ } bool menubased; void take_shot() { #if AEGIS if(cur) { println(hlog, "taking test screenshot"); shot_state = 1; while(shot_state == 1) usleep(1000); shot_state = 0; dpp::message msg(cur->msg.channel_id, ""); msg.add_file("go-board.png", dpp::utility::read_file("go-temp.png")); pbot->message_create(msg); clean_old_shots(); // old_shots.push_back(); println(hlog, "message sent"); } #else if(0) {} #endif else if(!menubased) { println(hlog, "taking test screenshot"); shot::take("go-test.png"); } } void go_message(string s) { println(hlog, s); addMessage(s); #if AEGIS if(cur) { dpp::message msg(cur->msg.channel_id, s); pbot->message_create(msg); } #endif } struct dfs { vector visited; vector q; dfs() { visited.resize(isize(ac), false); } void visit(int i) { if(visited[i]) return; visited[i] = true; q.push_back(i); }; }; int count_breath(int pos) { int who = current.taken[pos]; dfs d; int result = 0; d.visit(pos); for(int i=0; i tokens) { int t = isize(tokens); save_backup(); bool ok = false; for(int i=1; i tokens, int who) { int t = isize(tokens); save_backup(); bool ok = false; for(int i=1; i tokens; string ctoken; for(char c: s + " ") if(among(c, ' ', '\n', '\r', '\t')) { if(ctoken != "") tokens.push_back(ctoken), ctoken = ""; } else ctoken += c; int t = isize(tokens); if(t == 0) return; if(tokens[0] == "b" && t == 2) { try_to_play(tokens[1], 0); } if(tokens[0] == "w" && t == 2) { try_to_play(tokens[1], 1); } if(tokens[0] == "center" && t == 2) { int pos = str_to_index(tokens[1]); if(!valid_index(pos)) go_message("invalid cell: " + tokens[1]); else { centerover = ac[pos]; // fullcenter(); take_shot(); } } if(tokens[0] == "rotate" && t == 2) { try { ld angle = parseld(tokens[1]); View = spin(angle * degree) * View; take_shot(); } catch(hr_parse_exception& exc) { go_message(exc.s); } } if(tokens[0] == "labels" && t == 2) { try { labels_value = parseld(tokens[1]); take_shot(); } catch(hr_parse_exception& exc) { go_message(exc.s); } } if(tokens[0] == "hires") { dynamicval dx(shot::shotx, 1000); dynamicval dy(shot::shoty, 1000); take_shot(); } if(tokens[0] == "help") go_message( "b [where] - play as black\n" "w [where] - play as white\n" "center [where] - center the screen\n" "rotate [degrees] - rotate the screen\n" "die [where] - kill a group\n" "clear - clear owner marks\n" "labels 0..3 - show (0) no labels, (1) labels on unowned, (2) labels on empty, (3) all labels\n" "ob [where] - own area as black\n" "ow [where] - own area as white\n" "of [where] - the area is free\n" "oc [where] - the area is common\n" "oauto - own automatically\n" "score - view the score\n" "hires - take a 1000x1000 screenshot\n" "restart - restart\n" "bring-unrectified x y -- restart on unrectified GP(x,y) Bring surface\n" "disk-unrectified x y size -- restart on unrectified GP(x,y) {4,5} disk of given size\n" "undo - undo last move\n" "export - export board to string (no history, owners, captures)\n" "import [string] - import board from string\n" ); if(tokens[0] == "save") save_go(); if(tokens[0] == "die") die_at(tokens); if(tokens[0] == "clear" && t == 1) clear_owner_marks(); if(tokens[0] == "ob") set_owner(tokens, 0); if(tokens[0] == "ow") set_owner(tokens, 1); if(tokens[0] == "of") set_owner(tokens, 2); if(tokens[0] == "oc") set_owner(tokens, 3); if(tokens[0] == "oauto") set_owner_auto(); if(tokens[0] == "undo") { if(history.empty()) go_message("no undo history"); else { undo(); take_shot(); } } if(tokens[0] == "score") { array owned_by, stones; for(int i=0; i<2; i++) owned_by[i] = stones[i] = 0; for(int i=0; i 8 || y > 8 || x < 0 || y < 0 || x+y == 0) { go_message("illegal parameters"); return; } save_backup(); stop_game(); geometry = gBring; variation =eVariation::unrectified; gp::param = {x, y}; start_game(); init_go(); go_message("Bring surface, size = " + its(isize(ac))); take_shot(); } if(tokens[0] == "disk-unrectified" && t == 4) { int x = atoi(tokens[1].c_str()); int y = atoi(tokens[2].c_str()); int size = atoi(tokens[3].c_str()); if(x > 8 || y > 8 || x < 0 || y < 0 || x+y == 0 || size > 1000 || size < 10) { go_message("illegal parameters"); return; } save_backup(); stop_game(); geometry = g45; req_disksize = size; variation =eVariation::unrectified; gp::param = {x, y}; start_game(); init_go(); go_message("disk, size = " + its(isize(ac))); take_shot(); } if(tokens[0] == "export" && t == 1) { string ex; for(int i=0; i lk(lock); cur = &obj; accept_command(obj.msg.content); cur = nullptr; }); println(hlog, "starting bot"); bot.start(dpp::st_wait); println(hlog, "done"); // bot.yield(); }); #endif } void go_menu() { getcstat = '-'; cmode = sm::SIDE | sm::DIALOG_STRICT_X; gamescreen(); dialog::init("HyperGo", iinf[itPalace].color, 150, 100); mousing = false; /* always display letters */ array owned_by, stones; for(int i=0; i<2; i++) owned_by[i] = stones[i] = 0; for(int i=0; i