juegos

Juegos de terminal de Pancho: Catan (TUI, GUI, web, servidor, PicoCalc), ajedrez, calculadora y minijuegos
git clone https://git.lu3dhn.xyz/juegos.git
Log | Files | Refs

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 }