online.c (12880B)
1 /* online.c - menus de juego en red: servidor (VPS) y red local. */ 2 #include <arpa/inet.h> 3 #include <ctype.h> 4 #include <ifaddrs.h> 5 #include <netinet/in.h> 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include "app.h" 10 #include "net.h" 11 #include "../server/server.h" 12 13 bool parse_hostport(const char *in, char *host, int hn, int *port) 14 { 15 *port = CHESS_PORT; 16 while (*in == ' ') in++; 17 if (!*in) return false; 18 if (*in == '[') { /* [ipv6]:puerto */ 19 const char *e = strchr(in, ']'); 20 if (!e) return false; 21 snprintf(host, (size_t)hn, "%.*s", (int)(e - in - 1), in + 1); 22 if (e[1] == ':') *port = atoi(e + 2); 23 return true; 24 } 25 const char *c = strrchr(in, ':'); 26 if (c && strchr(in, ':') == c) { 27 snprintf(host, (size_t)hn, "%.*s", (int)(c - in), in); 28 *port = atoi(c + 1); 29 } else snprintf(host, (size_t)hn, "%s", in); 30 if (*port <= 0 || *port > 65535) *port = CHESS_PORT; 31 return host[0] != 0; 32 } 33 34 static void local_ip(char *out, int n) 35 { 36 snprintf(out, (size_t)n, "?"); 37 struct ifaddrs *ifs, *i; 38 if (getifaddrs(&ifs)) return; 39 for (i = ifs; i; i = i->ifa_next) { 40 if (!i->ifa_addr || i->ifa_addr->sa_family != AF_INET) continue; 41 struct sockaddr_in *a = (struct sockaddr_in *)i->ifa_addr; 42 if ((ntohl(a->sin_addr.s_addr) >> 24) == 127) continue; 43 const char *nm = i->ifa_name; 44 if (!strncmp(nm, "virbr", 5) || !strncmp(nm, "docker", 6) || !strncmp(nm, "veth", 4) || 45 !strncmp(nm, "br-", 3) || !strncmp(nm, "lxc", 3) || !strncmp(nm, "tun", 3) || !strncmp(nm, "wg", 2)) continue; 46 inet_ntop(AF_INET, &a->sin_addr, out, (socklen_t)n); 47 break; 48 } 49 freeifaddrs(ifs); 50 } 51 52 static void notice(Frontend *fe, Form *f, const char *msg, bool err) 53 { 54 form_msg(f, msg, err); 55 fe->draw_form(fe, f); 56 } 57 58 static void msg_form(Frontend *fe, const char *title, const char *msg) 59 { 60 Form f; 61 form_begin(&f, title); 62 form_label(&f, msg); 63 form_button(&f, 0, "OK"); 64 run_form(fe, &f, 0, 0); 65 } 66 67 /* ------------------------------------------------------------ jugar */ 68 69 static bool play_remote(Frontend *fe, Settings *st, Net *n, Form *f) 70 { 71 static Session s; 72 char err[200]; 73 notice(fe, f, g_lang ? "Loading game..." : "Cargando partida...", false); 74 if (!net_enter_game(n, &s, err, sizeof err)) { form_msg(f, err, true); return false; } 75 app_play(fe, &s, st); 76 net_send(n, "LEAVE"); 77 s.net = 0; 78 sess_close(&s); 79 return true; 80 } 81 82 /* ------------------------------------------------------------ crear */ 83 84 enum { C_BACK = 0, C_CREATE = 1, C_COLOR = 10, C_RIVAL, C_LEVEL, C_TC, C_UNDO }; 85 86 /* relojes para el online: los de siempre y correspondencia */ 87 static const struct { int32_t base, inc, per; } OTC[] = { 88 { 0, 0, 0 }, { 180000, 2000, 0 }, { 300000, 0, 0 }, { 300000, 5000, 0 }, { 600000, 5000, 0 }, { 900000, 10000, 0 }, 89 { 0, 0, 86400000 }, { 0, 0, 3 * 86400000 }, { 0, 0, 7 * 86400000 }, 90 }; 91 static const char *OTC_ES[] = { "Sin reloj", "3+2", "5+0", "5+5", "10+5", "15+10", "1 día por jugada", "3 días por jugada", "7 días por jugada" }; 92 static const char *OTC_EN[] = { "No clock", "3+2", "5+0", "5+5", "10+5", "15+10", "1 day per move", "3 days per move", "7 days per move" }; 93 static const char *COL_ES[] = { "Blancas", "Negras", "Al azar" }, *COL_EN[] = { "White", "Black", "Random" }; 94 static const char *RIV_ES[] = { "Una persona", "La máquina" }, *RIV_EN[] = { "A person", "The computer" }; 95 static const char *LVL_ES[] = { "Fácil", "Principiante", "Medio", "Difícil", "Máximo" }; 96 static const char *LVL_EN[] = { "Easy", "Beginner", "Medium", "Hard", "Maximum" }; 97 static const char *YN_ES[] = { "No", "Sí" }, *YN_EN[] = { "No", "Yes" }; 98 99 static void create_change(Form *f, void *ctx) 100 { 101 (void)ctx; 102 form_get(f, C_LEVEL)->hidden = form_get(f, C_RIVAL)->value == 0; 103 } 104 105 static bool create_form(Frontend *fe, Settings *st, char *cmd, int n, bool lan) 106 { 107 Form f; 108 bool en = g_lang; 109 form_begin(&f, en ? "Create game" : "Crear partida"); 110 form_choice(&f, C_COLOR, en ? "Your color" : "Tu color", en ? COL_EN : COL_ES, 3, st->color); 111 form_choice(&f, C_RIVAL, en ? "Against" : "Contra", en ? RIV_EN : RIV_ES, 2, 0); 112 form_choice(&f, C_LEVEL, en ? "Level" : "Nivel", en ? LVL_EN : LVL_ES, LEVEL_COUNT, st->level < LEVEL_COUNT ? st->level : LEVEL_COUNT - 1); 113 form_choice(&f, C_TC, en ? "Clock" : "Reloj", en ? OTC_EN : OTC_ES, lan ? 6 : 9, 0); 114 form_choice(&f, C_UNDO, en ? "Takebacks" : "Deshacer", en ? YN_EN : YN_ES, 2, st->undo); 115 form_label(&f, en ? "You get a 6-letter code to invite your rival." : "Te da un código de 6 letras para invitar a tu rival."); 116 form_button(&f, C_CREATE, en ? "Create" : "Crear"); 117 form_button(&f, C_BACK, en ? "Back" : "Volver"); 118 f.cur = f.n - 2; 119 if (run_form(fe, &f, create_change, 0) != C_CREATE) return false; 120 int t = form_get(&f, C_TC)->value; 121 snprintf(cmd, (size_t)n, "CREATE %d %d %d %d %d %d H%c", form_get(&f, C_COLOR)->value, OTC[t].base, OTC[t].inc, OTC[t].per, 122 form_get(&f, C_UNDO)->value, form_get(&f, C_LEVEL)->value, form_get(&f, C_RIVAL)->value ? 'B' : 'H'); 123 return true; 124 } 125 126 /* ------------------------------------------------------------ mis partidas */ 127 128 typedef struct { char code[8], token[33], label[96]; bool mine_turn, over; } GameRow; 129 130 static int fetch_status(Net *n, const char *key, GameRow *rows, int max) 131 { 132 SavedGame sg[64]; 133 int k = saved_games(key, sg, 64); 134 if (!k) return 0; 135 char cmd[SRV_LINE] = "STATUS"; 136 int used = 6, count = 0; 137 for (int i = 0; i < k && used < SRV_LINE - 60; i++) 138 used += snprintf(cmd + used, sizeof cmd - (size_t)used, " %s %s", sg[i].code, sg[i].token); 139 net_send(n, "%s", cmd); 140 char line[SRV_LINE]; 141 while (net_read_line(n, line, sizeof line, 5000)) { 142 if (!strcmp(line, "STATEND")) break; 143 char code[16], names[200] = ""; 144 int seat, nply, needs, result, mycolor, np; 145 if (sscanf(line, "STAT %15s %d %d %d %d %d %d %199[^\n]", code, &seat, &nply, &needs, &result, &mycolor, &np, names) < 7) continue; 146 if (count >= max) continue; 147 GameRow *r = &rows[count++]; 148 snprintf(r->code, sizeof r->code, "%.7s", code); 149 for (int i = 0; i < k; i++) if (!strcmp(sg[i].code, code)) snprintf(r->token, sizeof r->token, "%s", sg[i].token); 150 for (char *p = names; *p; p++) if (*p == '|') *p = ' '; 151 r->over = result != R_NONE; 152 r->mine_turn = needs; 153 const char *state = r->over ? (result == R_DRAW ? (g_lang ? "draw" : "tablas") : ((result == R_WHITE) == (mycolor == WHITE)) ? (g_lang ? "won" : "ganaste") : (g_lang ? "lost" : "perdiste")) 154 : needs ? (g_lang ? "YOUR TURN" : "¡TE TOCA!") 155 : (g_lang ? "waiting" : "esperando"); 156 snprintf(r->label, sizeof r->label, "%s · %s · %s %d · %.40s", code, state, g_lang ? "move" : "jugada", nply / 2 + 1, names); 157 } 158 return count; 159 } 160 161 static bool my_games(Frontend *fe, Settings *st, Net *n, const char *key, Form *parent) 162 { 163 GameRow rows[16]; 164 int k = fetch_status(n, key, rows, 16); 165 if (!k) { form_msg(parent, g_lang ? "You have no games on this server" : "No tenés partidas en este servidor", true); return false; } 166 Form f; 167 form_begin(&f, g_lang ? "My games" : "Mis partidas"); 168 /* primero las que te tocan */ 169 for (int pass = 0; pass < 3; pass++) 170 for (int i = 0; i < k; i++) { 171 int want = rows[i].mine_turn && !rows[i].over ? 0 : !rows[i].over ? 1 : 2; 172 if (want == pass) form_button(&f, 100 + i, rows[i].label); 173 } 174 form_button(&f, 0, g_lang ? "Back" : "Volver"); 175 int r = run_form(fe, &f, 0, 0); 176 if (r < 100) return false; 177 GameRow *g = &rows[r - 100]; 178 net_send(n, "OPEN %s %s", g->code, g->token); 179 return play_remote(fe, st, n, parent); 180 } 181 182 /* ------------------------------------------------------------ menu online */ 183 184 enum { O_BACK = 0, O_MINE = 1, O_CREATE, O_JOIN, O_SERVER = 10, O_NAME, O_CODE }; 185 186 bool online_menu(Frontend *fe, Settings *st, const char *server) 187 { 188 Form f; 189 bool en = g_lang; 190 char def[64]; 191 snprintf(def, sizeof def, "localhost:%d", CHESS_PORT); 192 form_begin(&f, en ? "Play online" : "Jugar online"); 193 form_text(&f, O_SERVER, en ? "Server" : "Servidor", server && *server ? server : st->server[0] ? st->server : def, 60); 194 form_text(&f, O_NAME, en ? "Your name" : "Tu nombre", st->name, 16); 195 form_button(&f, O_MINE, en ? "My games" : "Mis partidas"); 196 form_button(&f, O_CREATE, en ? "Create a game" : "Crear partida"); 197 form_text(&f, O_CODE, en ? "Game code" : "Código", "", 7); 198 form_button(&f, O_JOIN, en ? "Join with code" : "Unirse con el código"); 199 form_button(&f, O_BACK, en ? "Back" : "Volver"); 200 f.cur = 2; 201 for (;;) { 202 int r = run_form(fe, &f, 0, 0); 203 if (r == O_BACK || r == FORM_CANCEL) return false; 204 snprintf(st->server, sizeof st->server, "%s", form_get(&f, O_SERVER)->text); 205 snprintf(st->name, sizeof st->name, "%.19s", form_get(&f, O_NAME)->text); 206 settings_save(st); 207 char host[128], err[200], key[160]; 208 int port; 209 if (!parse_hostport(st->server, host, sizeof host, &port)) { form_msg(&f, en ? "Invalid server" : "Servidor inválido", true); continue; } 210 snprintf(key, sizeof key, "%s:%d", host, port); 211 char cmd[96] = ""; 212 if (r == O_CREATE && !create_form(fe, st, cmd, sizeof cmd, false)) continue; 213 if (r == O_JOIN) { 214 char *code = form_get(&f, O_CODE)->text; 215 for (char *p = code; *p; p++) *p = (char)toupper((unsigned char)*p); 216 if (strlen(code) != 6) { form_msg(&f, en ? "Enter the 6-letter code" : "Escribí el código de 6 letras", true); continue; } 217 snprintf(cmd, sizeof cmd, "JOIN %s", code); 218 } 219 notice(fe, &f, en ? "Connecting..." : "Conectando...", false); 220 Net *n = net_connect(host, port, st->name, 0, err, sizeof err); 221 if (!n) { form_msg(&f, err, true); continue; } 222 if (r == O_MINE) my_games(fe, st, n, key, &f); 223 else { net_send(n, "%s", cmd); play_remote(fe, st, n, &f); } 224 net_close(n); 225 } 226 } 227 228 /* ------------------------------------------------------------ red local */ 229 230 bool lan_host(Frontend *fe, Settings *st, int port) 231 { 232 if (port <= 0) port = CHESS_PORT; 233 char dir[600], err[200]; 234 snprintf(dir, sizeof dir, "%s/lan", data_dir()); 235 struct Server *srv = srv_open(&chess_module, port, dir, true, err, sizeof err); 236 if (!srv) { msg_form(fe, "Error", err); return false; } 237 Net *n = net_connect("127.0.0.1", port, st->name, srv, err, sizeof err); 238 if (!n) { srv_close(srv); msg_form(fe, "Error", err); return false; } 239 char ip[64], hint[160]; 240 local_ip(ip, sizeof ip); 241 snprintf(hint, sizeof hint, g_lang ? "The other player joins with: ajedrez join %s %d" : "El otro jugador entra con: ajedrez join %s %d", ip, port); 242 Form f; 243 form_begin(&f, g_lang ? "LAN game" : "Partida en red local"); 244 GameRow rows[8]; 245 int k = fetch_status(n, "lan", rows, 8), live = 0; 246 for (int i = 0; i < k; i++) 247 if (!rows[i].over) { form_button(&f, 100 + i, rows[i].label); live++; } 248 form_button(&f, 1, g_lang ? "New game" : "Partida nueva"); 249 form_button(&f, 0, g_lang ? "Back" : "Volver"); 250 form_label(&f, hint); 251 int r = live ? run_form(fe, &f, 0, 0) : 1; 252 bool ok = false; 253 if (r >= 100) { 254 net_send(n, "OPEN %s %s", rows[r - 100].code, rows[r - 100].token); 255 ok = play_remote(fe, st, n, &f); 256 } else if (r == 1) { 257 char cmd[96]; 258 if (create_form(fe, st, cmd, sizeof cmd, true)) { 259 msg_form(fe, g_lang ? "LAN game" : "Partida en red local", hint); 260 net_send(n, "%s", cmd); 261 ok = play_remote(fe, st, n, &f); 262 } 263 } 264 net_close(n); 265 return ok; 266 } 267 268 bool lan_join(Frontend *fe, Settings *st, const char *host, int port) 269 { 270 if (port <= 0) port = CHESS_PORT; 271 char err[200], key[160]; 272 Form f; 273 form_begin(&f, g_lang ? "Joining..." : "Uniéndose..."); 274 notice(fe, &f, g_lang ? "Connecting..." : "Conectando...", false); 275 Net *n = net_connect(host, port, st->name, 0, err, sizeof err); 276 if (!n) { msg_form(fe, "Error", err); return false; } 277 snprintf(key, sizeof key, "%s:%d", host, port); 278 GameRow rows[8]; 279 int k = fetch_status(n, key, rows, 8); 280 bool ok = false; 281 for (int i = 0; i < k && !ok; i++) 282 if (!rows[i].over) { 283 net_send(n, "OPEN %s %s", rows[i].code, rows[i].token); 284 ok = play_remote(fe, st, n, &f); 285 } 286 if (!ok) { 287 net_send(n, "JOIN -"); 288 ok = play_remote(fe, st, n, &f); 289 if (!ok) msg_form(fe, "Error", f.msg); 290 } 291 net_close(n); 292 return ok; 293 }