diff --git a/rogueviz/gobot.cpp b/rogueviz/gobot.cpp new file mode 100644 index 00000000..41a2debb --- /dev/null +++ b/rogueviz/gobot.cpp @@ -0,0 +1,621 @@ +// 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) + +/** \file gobot.cpp + * \brief bot to play Go via Discord + */ + +#define AEGIS 0 + +#if AEGIS +#include +#endif + +#include "../hyper.h" + +namespace hr { + +EX namespace gobot { + +eWall empty = waChasm; + +bool in = false; + +vector ac; +map indices; + +const int Free = 2; + +struct boarddata { + vector taken, owner; + array captures; + }; + +boarddata current; + +int labels_value = 1; + +vector history; + +void init_go() { + ac = currentmap->allcells(); + current.taken.resize(isize(ac), 2); + current.owner.resize(isize(ac), 2); + current.captures[0] = 0; + current.captures[1] = 0; + 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() { + current = history.back(); + history.pop_back(); + } + +#if AEGIS +aegis::gateway::events::message_create* cur; +#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(move(sh)); + old_shots = 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; + + aegis::create_message_t msg; + aegis::rest::aegis_file f; + f.name = "go-board.png"; + + FILE *ff = fopen("go-temp.png", "r"); + if(!ff) { + println(hlog, "file missing?!"); + return; + } + int c; + while((c = fgetc(ff)) >= 0) f.data.push_back(c); + fclose(ff); + println(hlog, "file size = ", int(f.data.size())); + + msg.file(f); + println(hlog, "file attached"); + clean_old_shots(); + // old_shots.push_back(); + cur->msg.get_channel().create_message(msg); + 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) + cur->msg.get_channel().create_message(s); + #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" + "score - view the score\n" + "hires - take a 1000x1000 screenshot\n" + "restart - restart\n" + "undo - undo last move\n" + ); + + if(tokens[0] == "save") { + save_backup(); + fhstream f("go.saved-game", "wb"); + f.write(history); + undo(); + } + + if(tokens[0] == "die") die_at(tokens); + + if(tokens[0] == "clear" && t == 1) + clear_owner_marks(); + + if(tokens[0] == "ow" || tokens[0] == "ob") + set_owner(tokens, tokens[0][1] == 'w'); + + 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 lk(lock); + cur = &obj; + accept_command(obj.msg.get_content()); + cur = nullptr; + }); + bot.run(); + bot.yield(); + }); +#endif + } + +void go_menu() { + + getcstat = '-'; + cmode = 0; + cmode = sm::SIDE | sm::DIALOG_STRICT_X; + gamescreen(0); + + 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