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

ctrl.c (46028B)


      1 /* ctrl.c - interaccion: que tiene que elegir el jugador local y como. */
      2 #include <string.h>
      3 #include "ctrl.h"
      4 
      5 static void robber_confirm(Ctrl *c, int hex, int victim);
      6 
      7 /* ------------------------------------------------------------ texto simple */
      8 
      9 typedef struct { char *b; int n, pos; } Sb;
     10 
     11 static void sb_s(Sb *s, const char *t) { int p = str_put(s->b, s->n, s->pos, t); if (p >= 0) s->pos = p; }
     12 static void sb_i(Sb *s, long v)        { int p = str_int(s->b, s->n, s->pos, v); if (p >= 0) s->pos = p; }
     13 
     14 static void sb_res(Sb *s, const uint8_t r[NRES])
     15 {
     16     bool first = true;
     17     for (int i = 0; i < NRES; i++) {
     18         if (!r[i]) continue;
     19         if (!first) sb_s(s, ", ");
     20         sb_i(s, r[i]); sb_s(s, " "); sb_s(s, res_name(i));
     21         first = false;
     22     }
     23     if (first) sb_s(s, tr(S_NOTHING));
     24 }
     25 
     26 static void set_label(MenuItem *it, const char *a, const char *b)
     27 {
     28     Sb s = { it->label, sizeof it->label, 0 };
     29     it->label[0] = 0;
     30     sb_s(&s, a);
     31     if (b) { sb_s(&s, " "); sb_s(&s, b); }
     32 }
     33 
     34 /* ---------------------------------------------------------------- helpers */
     35 
     36 static bool is_local(const Ctrl *c, int s) { return s >= 0 && s < MAXP && ((c->local_mask >> s) & 1); }
     37 
     38 static int humans_local(const Ctrl *c)
     39 {
     40     int n = 0;
     41     for (int s = 0; s < MAXP; s++) if (is_local(c, s)) n++;
     42     return n;
     43 }
     44 
     45 static int pick_actor(const Ctrl *c)
     46 {
     47     const Game *g = c->g;
     48     if (g->phase == PH_OVER) return -1;
     49     /* descartar (7) y elegir el oro los hace cada uno, no solo el del turno */
     50     for (int s = 0; s < g->o.np; s++)
     51         if (is_local(c, s) && ((g->phase == PH_DISCARD && g->p[s].discard) || (g->phase == PH_GOLD && g->p[s].gold))) return s;
     52     if (g->offer.active)
     53         for (int s = 0; s < g->o.np; s++)
     54             if (is_local(c, s) && s != g->cur && game_needs(g, s)) return s;
     55     if (is_local(c, g->cur) && game_needs(g, g->cur)) return g->cur;
     56     return -1;
     57 }
     58 
     59 static uint32_t context_sig(const Ctrl *c)
     60 {
     61     const Game *g = c->g;
     62     uint32_t h = 2166136261u;
     63     uint32_t v[12] = { g->phase, g->cur, g->setup_sub, g->setup_i, (uint32_t)(c->actor + 1), g->give_back,
     64                        g->neutral_need, g->offer.active, g->free_roads, g->dev_played, g->rolled, g->turn };
     65     for (int i = 0; i < 12; i++) { h ^= v[i]; h *= 16777619u; }
     66     return h;
     67 }
     68 
     69 static void target_pos(const Ctrl *c, int id, int *x, int *y)
     70 {
     71     const Topo *t = game_topo(c->g);
     72     if (c->mode == CM_VERT) { *x = 2 * t->vx[id]; *y = 2 * t->vy[id]; }
     73     else if (c->mode == CM_EDGE) {
     74         *x = t->vx[t->edge_v[id][0]] + t->vx[t->edge_v[id][1]];
     75         *y = t->vy[t->edge_v[id][0]] + t->vy[t->edge_v[id][1]];
     76     } else { *x = 2 * t->hx[id]; *y = 2 * t->hy[id]; }
     77 }
     78 
     79 static void cursor_nearest(Ctrl *c)
     80 {
     81     int best = 0;
     82     long bd = -1;
     83     for (int i = 0; i < c->ntargets; i++) {
     84         int x, y;
     85         target_pos(c, c->targets[i], &x, &y);
     86         long dx = (long)(x - c->last_x) * 7, dy = (long)(y - c->last_y) * 4;
     87         long d = dx * dx + dy * dy;
     88         if (bd < 0 || d < bd) { bd = d; best = i; }
     89     }
     90     c->cursor = best;
     91     c->pos = c->ntargets ? c->targets[best] : -1;
     92 }
     93 
     94 /* arista con tierra de algun lado: ruta; si no, barco (Navegantes) */
     95 static bool land_edge(const Game *g, int e)
     96 {
     97     const Topo *t = game_topo(g);
     98     for (int k = 0; k < 2; k++) {
     99         int h = t->edge_hex[e][k];
    100         if (h >= 0 && ter_land(g->terrain[h])) return true;
    101     }
    102     return false;
    103 }
    104 
    105 static int edge_piece(const Game *g, int e) { return !game_has_sea(g) || land_edge(g, e) ? M_ROAD : M_SHIP; }
    106 
    107 static void fill_targets(Ctrl *c)
    108 {
    109     const Game *g = c->g;
    110     const Topo *t = game_topo(g);
    111     int p = c->actor;
    112     c->ntargets = 0;
    113     switch (c->intent) {
    114     case I_SETUP_S: case I_SETTLE:
    115         for (int v = 0; v < game_topo(g)->nvert; v++)
    116             if (game_can_settle(g, p, v, c->intent == I_SETUP_S)) c->targets[c->ntargets++] = (int16_t)v;
    117         break;
    118     case I_CITY:
    119         for (int v = 0; v < game_topo(g)->nvert; v++)
    120             if (game_can_city(g, p, v)) c->targets[c->ntargets++] = (int16_t)v;
    121         break;
    122     case I_SETUP_R: {
    123         int sv = c->draft_v >= 0 ? c->draft_v : g->setup_v;     /* el pueblo en borrador */
    124         for (int k = 0; k < 3 && sv >= 0; k++) {
    125             int e = t->vert_edge[sv][k];
    126             if (e < 0 || g->eown[e] >= 0) continue;
    127             if (!game_has_sea(g) || land_edge(g, e) || g->p[p].ships) c->targets[c->ntargets++] = (int16_t)e;
    128         }
    129         break;
    130     }
    131     case I_ROAD: case I_FREE_ROAD:            /* con las rutas gratis tambien van barcos */
    132         for (int e = 0; e < game_topo(g)->nedge; e++)
    133             if ((g->p[p].roads && game_can_road(g, p, e)) ||
    134                 (c->intent == I_FREE_ROAD && g->p[p].ships && game_can_ship(g, p, e, false)))
    135                 c->targets[c->ntargets++] = (int16_t)e;
    136         break;
    137     case I_SHIP:
    138         for (int e = 0; e < game_topo(g)->nedge; e++)
    139             if (game_can_ship(g, p, e, false)) c->targets[c->ntargets++] = (int16_t)e;
    140         break;
    141     case I_MOVESHIP_FROM:
    142         for (int e = 0; e < game_topo(g)->nedge; e++)
    143             if (game_ship_movable(g, p, e)) c->targets[c->ntargets++] = (int16_t)e;
    144         break;
    145     case I_MOVESHIP_TO:
    146         for (int e = 0; e < game_topo(g)->nedge; e++)
    147             if (game_can_moveship(g, p, c->ship_from, e)) c->targets[c->ntargets++] = (int16_t)e;
    148         break;
    149     case I_NEUTRAL:
    150         if (c->mode == CM_VERT) {
    151             for (int v = 0; v < game_topo(g)->nvert; v++)
    152                 for (int n = 0; n < g->o.np; n++)
    153                     if (g->p[n].neutral && g->p[n].settles && game_can_settle(g, n, v, false)) {
    154                         c->targets[c->ntargets++] = (int16_t)v; break;
    155                     }
    156         } else {
    157             for (int e = 0; e < game_topo(g)->nedge; e++)
    158                 for (int n = 0; n < g->o.np; n++)
    159                     if (g->p[n].neutral && g->p[n].roads && game_can_road(g, n, e)) {
    160                         c->targets[c->ntargets++] = (int16_t)e; break;
    161                     }
    162         }
    163         break;
    164     case I_ROBBER:
    165         for (int h = 0; h < game_topo(g)->nhex; h++)
    166             if (game_can_robber(g, p, h)) c->targets[c->ntargets++] = (int16_t)h;
    167         break;
    168     }
    169     if (c->cursor >= c->ntargets) c->cursor = 0;
    170 }
    171 
    172 static void enter_pick(Ctrl *c, int mode, int intent)
    173 {
    174     c->mode = mode;
    175     c->intent = intent;
    176     fill_targets(c);
    177     cursor_nearest(c);
    178 }
    179 
    180 static void enter_cards(Ctrl *c, int intent, int need)
    181 {
    182     c->mode = CM_CARDS;
    183     c->intent = intent;
    184     c->need = need;
    185     c->row = 0;
    186     c->rescur = 0;
    187     memset(c->sel, 0, NRES);
    188     memset(c->sel2, 0, NRES);
    189 }
    190 
    191 static void menu_begin(Ctrl *c, int intent, int title)
    192 {
    193     c->mode = CM_MENU;
    194     c->intent = intent;
    195     c->menu_title = title;
    196     c->nitems = 0;
    197     c->icursor = 0;
    198 }
    199 
    200 static MenuItem *menu_add(Ctrl *c, int action, int arg, bool enabled, char key, const char *a, const char *b)
    201 {
    202     if (c->nitems >= (int)(sizeof c->items / sizeof c->items[0])) return 0;
    203     MenuItem *it = &c->items[c->nitems++];
    204     it->action = (uint8_t)action; it->arg = (int16_t)arg; it->enabled = enabled; it->key = key;
    205     set_label(it, a, b);
    206     return it;
    207 }
    208 
    209 /* ------------------------------------------------------------ acciones */
    210 
    211 static bool can_any(const Ctrl *c, int intent)
    212 {
    213     const Game *g = c->g;
    214     int p = c->actor;
    215     if (intent == I_ROAD) {
    216         for (int e = 0; e < game_topo(g)->nedge; e++) if (game_can_road(g, p, e)) return true;
    217         return false;
    218     }
    219     if (intent == I_SHIP) {
    220         for (int e = 0; e < game_topo(g)->nedge; e++) if (game_can_ship(g, p, e, false)) return true;
    221         return false;
    222     }
    223     for (int v = 0; v < game_topo(g)->nvert; v++)
    224         if (intent == I_CITY ? game_can_city(g, p, v) : game_can_settle(g, p, v, false)) return true;
    225     return false;
    226 }
    227 
    228 static bool dev_playable(const Game *g, int p, int d)
    229 {
    230     return g->p[p].dev[d] > 0 && !g->dev_played && d != D_VP;
    231 }
    232 
    233 static void add_action(Ctrl *c, int a, int arg, bool en, char key, const char *label, const char *extra)
    234 {
    235     if (c->nactions >= (int)(sizeof c->actions / sizeof c->actions[0])) return;
    236     MenuItem *it = &c->actions[c->nactions++];
    237     it->action = (uint8_t)a; it->arg = (int16_t)arg; it->enabled = en; it->key = key;
    238     set_label(it, label, extra);
    239 }
    240 
    241 static void build_actions(Ctrl *c)
    242 {
    243     const Game *g = c->g;
    244     int p = c->actor;
    245     c->nactions = 0;
    246     if (p < 0 || p != g->cur) return;
    247     const Player *pl = &g->p[p];
    248     if (g->phase == PH_ROLL) {
    249         add_action(c, A_ROLL, 0, true, 'r', tr(S_A_ROLL), 0);
    250         if (pl->dev[D_KNIGHT]) add_action(c, A_KNIGHT, 0, !g->dev_played, 'k', tr(S_A_KNIGHT), 0);
    251         if (g->o.variant2p)
    252             add_action(c, A_TOKROBBER, 0, game_check(g, &(Move){ .type = M_TOKROBBER, .p = (int8_t)p }) == E_OK,
    253                        'z', tr(S_A_TOKROBBER), 0);
    254         return;
    255     }
    256     if (g->phase != PH_MAIN && g->phase != PH_PAIRED) return;
    257     bool own = g->phase == PH_MAIN;          /* en el turno de pareja: sin cartas ni trueques */
    258     add_action(c, A_ROAD, 0, pl->roads && game_can_afford(g, p, COST_ROAD) && can_any(c, I_ROAD), '1', tr(S_A_ROAD), 0);
    259     add_action(c, A_SETTLE, 0, pl->settles && game_can_afford(g, p, COST_SETTLE) && can_any(c, I_SETTLE), '2', tr(S_A_SETTLE), 0);
    260     add_action(c, A_CITY, 0, pl->cities && game_can_afford(g, p, COST_CITY) && can_any(c, I_CITY), '3', tr(S_A_CITY), 0);
    261     add_action(c, A_BUYDEV, 0, g->deck_n && game_can_afford(g, p, COST_DEV), '4', tr(S_A_BUYDEV), 0);
    262     if (game_has_sea(g)) {                    /* Navegantes */
    263         add_action(c, A_SHIP, 0, pl->ships && game_can_afford(g, p, COST_SHIP) && can_any(c, I_SHIP), 'n', tr(S_A_SHIP), 0);
    264         Move mv = { .type = M_MOVESHIP, .p = (int8_t)p };
    265         bool movable = false;
    266         for (int e = 0; e < game_topo(g)->nedge && !movable; e++) if (game_ship_movable(g, p, e)) movable = true;
    267         (void)mv;
    268         if (own) add_action(c, A_MOVESHIP, 0, movable && !g->ship_moved, 'm', tr(S_A_MOVESHIP), 0);
    269     }
    270     bool anydev = false;
    271     for (int d = 0; d < D_VP; d++) if (dev_playable(g, p, d)) anydev = true;
    272     if (own) add_action(c, A_DEV, 0, anydev, 'd', tr(S_A_DEV), 0);
    273     bool bank = false;
    274     for (int r = 0; r < NRES; r++) if (pl->res[r] >= game_bank_ratio(g, p, r)) bank = true;
    275     add_action(c, A_BANK, 0, bank, 'b', tr(S_A_BANK), 0);
    276     if (own) add_action(c, A_OFFER, 0, hand_size(g, p) > 0, 't', tr(S_A_OFFER), 0);
    277     if (g->offer.active && g->offer.from == p) {
    278         for (int s = 0; s < g->o.np; s++)
    279             if (g->offer.resp[s] == 1) add_action(c, A_CONFIRM, s, true, (char)('5' + s), tr(S_A_CONFIRM), c->names[s]);
    280             else if (g->offer.resp[s] == 2) {      /* contraoferta: se ven los terminos */
    281                 char t[48];
    282                 Sb b = { t, sizeof t, 0 };
    283                 t[0] = 0;
    284                 sb_s(&b, c->names[s]); sb_s(&b, ": "); sb_s(&b, tr(S_GIVES)); sb_s(&b, " "); sb_res(&b, g->offer.cgive[s]);
    285                 sb_s(&b, ", "); sb_s(&b, tr(S_WANTS)); sb_s(&b, " "); sb_res(&b, g->offer.cget[s]);
    286                 add_action(c, A_CONFIRM, s, game_can_afford(g, p, g->offer.cget[s]), (char)('5' + s), t, 0);
    287             }
    288         add_action(c, A_CANCEL, 0, true, 'x', tr(S_A_CANCEL), 0);
    289     }
    290     if (g->o.variant2p) {
    291         Move m = { .type = M_TOKTRADE, .p = (int8_t)p };
    292         add_action(c, A_TOKTRADE, 0, game_check(g, &m) == E_OK, 'f', tr(S_A_TOKTRADE), 0);
    293         m.type = M_TOKROBBER;
    294         add_action(c, A_TOKROBBER, 0, game_check(g, &m) == E_OK, 'z', tr(S_A_TOKROBBER), 0);
    295     }
    296     add_action(c, A_UNDO, 0, g->undo_n > 0, 'u', tr(S_A_UNDO), 0);
    297     add_action(c, A_END, 0, true, 'e', tr(S_A_END), 0);
    298 }
    299 
    300 static void enter_default(Ctrl *c);
    301 
    302 static void setup_confirm(Ctrl *c)
    303 {
    304     menu_begin(c, I_SETUP_OK, S_P_SETUP_OK);
    305     menu_add(c, 1, 0, true, 'e', tr(S_SETUP_OK), 0);
    306     menu_add(c, 2, 0, true, 'u', tr(S_UNDO_LAST), 0);
    307 }
    308 
    309 /* u en la colocacion: saca la ruta del borrador y, si no hay ruta, el pueblo */
    310 static bool setup_back(Ctrl *c)
    311 {
    312     if (c->g->phase != PH_SETUP || c->g->setup_sub || c->draft_v < 0) return false;
    313     if (c->draft_e >= 0) c->draft_e = -1;
    314     else c->draft_v = -1;
    315     enter_default(c);
    316     return true;
    317 }
    318 
    319 static void enter_default(Ctrl *c)
    320 {
    321     const Game *g = c->g;
    322     int p = c->actor;
    323     c->mode = CM_IDLE;
    324     c->intent = I_NONE;
    325     if (g->phase == PH_OVER) { c->mode = CM_OVER; return; }
    326     if (p < 0) return;
    327     if (c->handoff && humans_local(c) > 1 && p != c->shown) { c->mode = CM_HANDOFF; return; }
    328     c->shown = p;
    329 
    330     if (g->phase == PH_DISCARD) { enter_cards(c, I_DISCARD, g->p[p].discard); return; }
    331     if (g->phase == PH_GOLD) { enter_cards(c, I_GOLD, g->p[p].gold); return; }
    332     if (p != g->cur) {
    333         if (g->offer.active) {
    334             menu_begin(c, I_RESPOND, S_P_RESPOND);
    335             menu_add(c, 1, 0, game_can_afford(g, p, g->offer.get), 'a', tr(S_ACCEPT), 0);
    336             menu_add(c, 0, 0, true, 'r', tr(S_REJECT), 0);
    337             menu_add(c, 2, 0, hand_size(g, p) > 0, 'o', tr(S_A_COUNTER), 0);
    338         }
    339         return;
    340     }
    341     switch (g->phase) {
    342     case PH_SETUP:
    343         if (g->setup_sub) enter_pick(c, CM_EDGE, I_SETUP_R);            /* pueblo ya mandado */
    344         else if (c->draft_v < 0) enter_pick(c, CM_VERT, I_SETUP_S);
    345         else if (c->draft_e < 0) enter_pick(c, CM_EDGE, I_SETUP_R);
    346         else setup_confirm(c);
    347         break;
    348     case PH_ROBBER:    enter_pick(c, CM_HEX, I_ROBBER); break;
    349     case PH_ROADBUILD: enter_pick(c, CM_EDGE, I_FREE_ROAD); break;
    350     case PH_NEUTRAL:
    351         enter_pick(c, (g->neutral_need == NEED_SETTLE && game_neutral_possible(g, NEED_SETTLE)) ? CM_VERT : CM_EDGE, I_NEUTRAL);
    352         break;
    353     case PH_GIVEBACK:  enter_cards(c, I_GIVEBACK, g->give_back); break;
    354     default: break;
    355     }
    356     if (c->icursor >= c->nactions) c->icursor = 0;
    357 }
    358 
    359 void ctrl_init(Ctrl *c, uint8_t local_mask, bool handoff)
    360 {
    361     memset(c, 0, sizeof *c);
    362     c->local_mask = local_mask;
    363     c->handoff = handoff;
    364     c->actor = c->shown = -1;
    365     for (int s = 0; s < MAXP; s++)
    366         if (is_local(c, s)) { c->shown = s; break; }
    367     c->ctx_sig = 0xFFFFFFFFu;
    368     c->last_x = 0; c->last_y = 0;
    369     c->draft_v = c->draft_e = -1;
    370     c->nqueue = 0;
    371 }
    372 
    373 void ctrl_sync(Ctrl *c, const Game *g)
    374 {
    375     c->g = g;
    376     c->actor = pick_actor(c);
    377     uint32_t sig = context_sig(c);
    378     build_actions(c);
    379     if (sig != c->ctx_sig) {
    380         if (c->ctx_sig != 0xFFFFFFFFu || g->phase != PH_SETUP) c->draft_v = c->draft_e = -1;   /* cambio de verdad */
    381         c->ctx_sig = sig;
    382         enter_default(c);
    383     } else if (c->mode == CM_VERT || c->mode == CM_EDGE || c->mode == CM_HEX) {
    384         int keep = c->pos;
    385         fill_targets(c);
    386         for (int i = 0; i < c->ntargets; i++) if (c->targets[i] == keep) c->cursor = i;
    387         c->pos = keep;
    388     }
    389     if (c->nactions && c->icursor >= c->nactions) c->icursor = 0;
    390 }
    391 
    392 void ctrl_error(Ctrl *c, int err)
    393 {
    394     Sb s = { c->msg, sizeof c->msg, 0 };
    395     c->msg[0] = 0;
    396     sb_s(&s, err_text(err));
    397     c->msg_err = err != E_OK;
    398 }
    399 
    400 static void info(Ctrl *c, int id)
    401 {
    402     Sb s = { c->msg, sizeof c->msg, 0 };
    403     c->msg[0] = 0;
    404     sb_s(&s, tr(id));
    405     c->msg_err = true;
    406 }
    407 
    408 bool ctrl_is_target(const Ctrl *c, int id)
    409 {
    410     for (int i = 0; i < c->ntargets; i++) if (c->targets[i] == id) return true;
    411     return false;
    412 }
    413 
    414 bool ctrl_next(Ctrl *c, Move *out)
    415 {
    416     if (c->nqueue <= 0) return false;
    417     *out = c->queue[0];
    418     for (int i = 1; i < c->nqueue; i++) c->queue[i - 1] = c->queue[i];
    419     c->nqueue--;
    420     return true;
    421 }
    422 
    423 void ctrl_draft(const Ctrl *c, int *v, int *e)
    424 {
    425     bool on = c->g && c->g->phase == PH_SETUP && !c->g->setup_sub && c->actor == c->g->cur;
    426     *v = on ? c->draft_v : -1;
    427     *e = on ? c->draft_e : -1;
    428 }
    429 
    430 int ctrl_cursor_pos(const Ctrl *c)
    431 {
    432     if (c->mode != CM_VERT && c->mode != CM_EDGE && c->mode != CM_HEX) return -1;
    433     return c->pos;
    434 }
    435 
    436 int ctrl_cursor_target(const Ctrl *c)
    437 {
    438     int p = ctrl_cursor_pos(c);
    439     return p >= 0 && ctrl_is_target(c, p) ? p : -1;
    440 }
    441 
    442 /* ------------------------------------------------------------ comandos */
    443 
    444 static Move base_move(const Ctrl *c, int type)
    445 {
    446     Move m;
    447     memset(&m, 0, sizeof m);
    448     m.type = (uint8_t)type;
    449     m.p = (int8_t)c->actor;
    450     return m;
    451 }
    452 
    453 static bool finish(Ctrl *c, Move *out, Move m)
    454 {
    455     *out = m;
    456     c->ctx_sig = 0xFFFFFFFFu;   /* al sincronizar vuelve al modo por defecto */
    457     c->mode = CM_IDLE;
    458     return true;
    459 }
    460 
    461 static bool do_action(Ctrl *c, int a, int arg, Move *out)
    462 {
    463     const Game *g = c->g;
    464     int p = c->actor;
    465     switch (a) {
    466     case A_ROLL:   return finish(c, out, base_move(c, M_ROLL));
    467     case A_KNIGHT: return finish(c, out, base_move(c, M_KNIGHT));
    468     case A_ROAD:   enter_pick(c, CM_EDGE, I_ROAD); break;
    469     case A_SHIP:   enter_pick(c, CM_EDGE, I_SHIP); break;
    470     case A_MOVESHIP: enter_pick(c, CM_EDGE, I_MOVESHIP_FROM); break;
    471     case A_SETTLE: enter_pick(c, CM_VERT, I_SETTLE); break;
    472     case A_CITY:   enter_pick(c, CM_VERT, I_CITY); break;
    473     case A_BUYDEV: return finish(c, out, base_move(c, M_BUYDEV));
    474     case A_DEV:
    475         menu_begin(c, I_DEV, S_P_DEV);
    476         for (int d = 0; d < D_VP; d++)
    477             if (g->p[p].dev[d] || g->p[p].dev_new[d]) {
    478                 char key = (char)('1' + d);
    479                 char what[64] = "(";
    480                 { Sb b = { what, sizeof what, 1 }; sb_s(&b, tr(S_DD_KNIGHT + d)); sb_s(&b, ")"); }
    481                 menu_add(c, d, d, dev_playable(g, p, d), key, dev_name(d), what);   /* que hace cada carta */
    482             }
    483         break;
    484     case A_BANK:
    485         menu_begin(c, I_BANK_GIVE, S_P_BANK_GIVE);
    486         for (int r = 0; r < NRES; r++) {
    487             int ratio = game_bank_ratio(g, p, r);
    488             char num[8];
    489             Sb s = { num, sizeof num, 0 };
    490             num[0] = 0;
    491             sb_i(&s, ratio);
    492             MenuItem *it = menu_add(c, r, r, g->p[p].res[r] >= ratio, (char)('1' + r), num, res_name(r));
    493             (void)it;
    494         }
    495         break;
    496     case A_OFFER:  enter_cards(c, I_OFFER, 0); break;
    497     case A_CONFIRM: { Move m = base_move(c, M_CONFIRM); m.a = (int16_t)arg; return finish(c, out, m); }
    498     case A_CANCEL:  return finish(c, out, base_move(c, M_CANCEL));
    499     case A_TOKTRADE: return finish(c, out, base_move(c, M_TOKTRADE));
    500     case A_TOKROBBER: return finish(c, out, base_move(c, M_TOKROBBER));
    501     case A_UNDO:   return finish(c, out, base_move(c, M_UNDO));
    502     case A_END:
    503         if (!g->undo_n) return finish(c, out, base_move(c, M_END));
    504         menu_begin(c, I_CONFIRM_END, S_P_CONFIRM_END);
    505         menu_add(c, 1, 0, true, 'e', tr(S_CONFIRM_END), 0);
    506         menu_add(c, 2, 0, true, 'u', tr(S_UNDO_LAST), 0);
    507         menu_add(c, 0, 0, true, 'v', tr(S_BACK), 0);
    508         break;
    509     }
    510     if ((c->mode == CM_VERT || c->mode == CM_EDGE) && !c->ntargets) {
    511         info(c, S_NO_TARGETS);
    512         enter_default(c);
    513     }
    514     return false;
    515 }
    516 
    517 static bool robber_victims(const Ctrl *c, int h, int8_t out[MAXP])
    518 {
    519     const Game *g = c->g;
    520     const Topo *t = game_topo(g);
    521     int n = 0;
    522     for (int k = 0; k < 6; k++) {
    523         int o = g->vown[t->hex_vert[h][k]];
    524         if (o < 0 || o == c->actor || g->p[o].neutral || hand_size(g, o) == 0) continue;
    525         bool dup = false;
    526         for (int i = 0; i < n; i++) if (out[i] == o) dup = true;
    527         if (!dup) out[n++] = (int8_t)o;
    528     }
    529     for (int i = n; i < MAXP; i++) out[i] = -1;
    530     return n > 0;
    531 }
    532 
    533 static bool commit_target(Ctrl *c, int id, Move *out)
    534 {
    535     const Game *g = c->g;
    536     Move m;
    537     switch (c->intent) {
    538     case I_SETUP_S:
    539         c->draft_v = (int16_t)id;            /* borrador: se manda al confirmar */
    540         c->draft_e = -1;
    541         enter_default(c);
    542         return false;
    543     case I_SETTLE: m = base_move(c, M_SETTLE); m.a = (int16_t)id; return finish(c, out, m);
    544     case I_CITY:    m = base_move(c, M_CITY); m.a = (int16_t)id; return finish(c, out, m);
    545     case I_SETUP_R:
    546         if (c->draft_v >= 0 && !g->setup_sub) {
    547             c->draft_e = (int16_t)id;
    548             enter_default(c);
    549             return false;
    550         }
    551         m = base_move(c, edge_piece(g, id)); m.a = (int16_t)id; return finish(c, out, m);
    552     case I_ROAD:
    553         m = base_move(c, M_ROAD); m.a = (int16_t)id; return finish(c, out, m);
    554     case I_FREE_ROAD:                         /* ruta si se puede, si no barco */
    555         m = base_move(c, g->p[c->actor].roads && game_can_road(g, c->actor, id) ? M_ROAD : M_SHIP);
    556         m.a = (int16_t)id;
    557         return finish(c, out, m);
    558     case I_SHIP:
    559         m = base_move(c, M_SHIP); m.a = (int16_t)id; return finish(c, out, m);
    560     case I_MOVESHIP_FROM:
    561         c->ship_from = id;
    562         enter_pick(c, CM_EDGE, I_MOVESHIP_TO);
    563         return false;
    564     case I_MOVESHIP_TO:
    565         m = base_move(c, M_MOVESHIP); m.a = (int16_t)c->ship_from; m.b = (int16_t)id; return finish(c, out, m);
    566     case I_NEUTRAL:
    567         m = base_move(c, M_NEUTRAL);
    568         m.b = c->mode == CM_VERT ? 1 : 0;
    569         m.c = (int16_t)id;
    570         for (int n = 0; n < g->o.np; n++) {
    571             if (!g->p[n].neutral) continue;
    572             bool ok = m.b ? (g->p[n].settles && game_can_settle(g, n, id, false)) : (g->p[n].roads && game_can_road(g, n, id));
    573             if (ok) { m.a = (int16_t)n; return finish(c, out, m); }
    574         }
    575         return false;
    576     case I_ROBBER: {
    577         int8_t v[MAXP];
    578         m = base_move(c, M_ROBBER);
    579         m.a = (int16_t)id;
    580         m.b = -1;
    581         m.c = -1;
    582         if (!robber_victims(c, id, v)) { robber_confirm(c, id, -1); return false; }
    583         if (v[1] < 0) { robber_confirm(c, id, v[0]); return false; }
    584         c->pending_hex = id;
    585         menu_begin(c, I_VICTIM, S_P_VICTIM);
    586         for (int i = 0; i < MAXP && v[i] >= 0; i++)
    587             menu_add(c, 0, v[i], true, (char)('1' + i), c->names[v[i]], 0);
    588         return false;
    589     }
    590     }
    591     return false;
    592 }
    593 
    594 static void offer_to_menu(Ctrl *c);
    595 static Move offer_move(const Ctrl *c, int mask);
    596 
    597 /* Antes de que el ladron robe se confirma (un toque de mas no puede deshacerse despues:
    598  * el robo es al azar). Esc o "elegir otro lugar" vuelven a elegir el hexagono. */
    599 static void robber_confirm(Ctrl *c, int hex, int victim)
    600 {
    601     c->pending_hex = hex;
    602     c->pending_victim = victim;
    603     menu_begin(c, I_ROBBER_OK, S_P_ROBBER_OK);
    604     if (victim >= 0) menu_add(c, 1, 0, true, 'e', tr(S_ROBBER_OK), c->names[victim]);
    605     else menu_add(c, 1, 0, true, 'e', tr(S_ROBBER_OK_NO), 0);
    606     menu_add(c, 0, 0, true, 'v', tr(S_ROBBER_ELSE), 0);
    607 }
    608 
    609 static bool select_item(Ctrl *c, int idx, Move *out)
    610 {
    611     if (idx < 0 || idx >= c->nitems) return false;
    612     MenuItem *it = &c->items[idx];
    613     if (!it->enabled) return false;
    614     Move m;
    615     switch (c->intent) {
    616     case I_VICTIM:
    617         robber_confirm(c, c->pending_hex, it->arg);
    618         return false;
    619     case I_ROBBER_OK:
    620         if (!it->action) { enter_pick(c, CM_HEX, I_ROBBER); return false; }
    621         m = base_move(c, M_ROBBER); m.a = (int16_t)c->pending_hex; m.b = (int16_t)c->pending_victim; m.c = -1;
    622         return finish(c, out, m);
    623     case I_RESPOND:
    624         if (it->action == 2) {              /* contraoferta: arranca de la oferta al reves */
    625             enter_cards(c, I_COUNTER, 0);
    626             memcpy(c->sel, c->g->offer.get, NRES);
    627             memcpy(c->sel2, c->g->offer.give, NRES);
    628             for (int r = 0; r < NRES; r++)
    629                 if (c->sel[r] > c->g->p[c->actor].res[r]) c->sel[r] = c->g->p[c->actor].res[r];
    630             return false;
    631         }
    632         return finish(c, out, base_move(c, it->action ? M_ACCEPT : M_REJECT));
    633     case I_DEV:
    634         switch (it->arg) {
    635         case D_KNIGHT: return finish(c, out, base_move(c, M_KNIGHT));
    636         case D_ROADS:  return finish(c, out, base_move(c, M_ROADS));
    637         case D_PLENTY: enter_cards(c, I_PLENTY, 2); return false;
    638         case D_MONO:
    639             menu_begin(c, I_MONO, S_P_MONO);
    640             for (int r = 0; r < NRES; r++) menu_add(c, r, r, true, (char)('1' + r), res_name(r), 0);
    641             return false;
    642         }
    643         return false;
    644     case I_MONO:
    645         m = base_move(c, M_MONO); m.a = it->arg;
    646         return finish(c, out, m);
    647     case I_BANK_GIVE:
    648         c->bank_give = it->arg;
    649         menu_begin(c, I_BANK_GET, S_P_BANK_GET);
    650         for (int r = 0; r < NRES; r++)
    651             if (r != c->bank_give) menu_add(c, r, r, c->g->bank[r] > 0, (char)('1' + r), "1", res_name(r));
    652         return false;
    653     case I_BANK_GET:
    654         m = base_move(c, M_BANK); m.a = (int16_t)c->bank_give; m.b = it->arg; m.c = 1;
    655         return finish(c, out, m);
    656     case I_SETUP_OK:
    657         if (it->action == 2) return setup_back(c), false;
    658         m = base_move(c, M_SETTLE); m.a = c->draft_v;
    659         c->queue[0] = base_move(c, edge_piece(c->g, c->draft_e)); c->queue[0].a = c->draft_e;
    660         c->nqueue = 1;
    661         c->draft_v = c->draft_e = -1;
    662         return finish(c, out, m);
    663     case I_CONFIRM_END:
    664         if (it->action == 1) return finish(c, out, base_move(c, M_END));
    665         if (it->action == 2) return finish(c, out, base_move(c, M_UNDO));
    666         enter_default(c);
    667         return false;
    668     case I_OFFER_TO:
    669         if (it->action == 1) {
    670             if (!c->offer_mask) { info(c, S_NOBODY); return false; }
    671             return finish(c, out, offer_move(c, c->offer_mask));
    672         }
    673         c->offer_mask ^= (uint8_t)(1 << it->arg);
    674         c->icursor = idx;
    675         offer_to_menu(c);
    676         return false;
    677     }
    678     return false;
    679 }
    680 
    681 /* En modo cartas: limites para sumar una carta en la fila actual. */
    682 static bool card_can_inc(const Ctrl *c, int row, int r)
    683 {
    684     const Game *g = c->g;
    685     const Player *pl = &g->p[c->actor];
    686     const uint8_t *sel = row ? c->sel2 : c->sel;
    687     switch (c->intent) {
    688     case I_DISCARD: case I_GIVEBACK:
    689         return res_total(c->sel) < c->need && c->sel[r] < pl->res[r];
    690     case I_PLENTY:
    691         return res_total(c->sel) < 2 && c->sel[r] < g->bank[r];
    692     case I_GOLD:
    693         return res_total(c->sel) < c->need && c->sel[r] < g->bank[r];
    694     case I_OFFER: case I_COUNTER:
    695         if (row == 0) return c->sel[r] < pl->res[r] && !c->sel2[r];
    696         return sel[r] < 19 && !c->sel[r];
    697     }
    698     return false;
    699 }
    700 
    701 static void card_change(Ctrl *c, int row, int r, int delta)
    702 {
    703     uint8_t *sel = row ? c->sel2 : c->sel;
    704     if (delta > 0 && card_can_inc(c, row, r)) sel[r]++;
    705     if (delta < 0 && sel[r]) sel[r]--;
    706 }
    707 
    708 static void offer_to_menu(Ctrl *c)
    709 {
    710     const Game *g = c->g;
    711     int keep = c->icursor;
    712     menu_begin(c, I_OFFER_TO, S_P_OFFER_TO);
    713     for (int s = 0, k = 0; s < g->o.np; s++) {
    714         if (!((game_offer_candidates(g, c->actor) >> s) & 1)) continue;
    715         menu_add(c, 0, s, true, (char)('1' + k++), (c->offer_mask >> s) & 1 ? "[x]" : "[ ]", c->names[s]);
    716     }
    717     menu_add(c, 1, 0, c->offer_mask != 0, 'e', tr(S_SEND_OFFER), 0);
    718     c->icursor = keep < c->nitems ? keep : 0;
    719 }
    720 
    721 static Move offer_move(const Ctrl *c, int mask)
    722 {
    723     Move m = base_move(c, M_OFFER);
    724     m.a = (int16_t)mask;
    725     memcpy(m.r, c->sel, NRES);
    726     memcpy(m.r2, c->sel2, NRES);
    727     return m;
    728 }
    729 
    730 static bool cards_commit(Ctrl *c, Move *out)
    731 {
    732     Move m;
    733     switch (c->intent) {
    734     case I_DISCARD: case I_GIVEBACK:
    735         if (res_total(c->sel) != c->need) return false;
    736         m = base_move(c, c->intent == I_DISCARD ? M_DISCARD : M_GIVEBACK);
    737         memcpy(m.r, c->sel, NRES);
    738         return finish(c, out, m);
    739     case I_GOLD:
    740         if (res_total(c->sel) != c->need) return false;
    741         m = base_move(c, M_GOLD);
    742         memcpy(m.r, c->sel, NRES);
    743         return finish(c, out, m);
    744     case I_PLENTY: {
    745         if (res_total(c->sel) != 2) return false;
    746         m = base_move(c, M_PLENTY);
    747         int k = 0;
    748         for (int r = 0; r < NRES; r++)
    749             for (int i = 0; i < c->sel[r]; i++) { if (k++ == 0) m.a = (int16_t)r; else m.b = (int16_t)r; }
    750         return finish(c, out, m);
    751     }
    752     case I_COUNTER:
    753         if (!res_total(c->sel) || !res_total(c->sel2)) return false;
    754         m = base_move(c, M_COUNTER);
    755         memcpy(m.r, c->sel, NRES);
    756         memcpy(m.r2, c->sel2, NRES);
    757         return finish(c, out, m);
    758     case I_OFFER: {
    759         if (!res_total(c->sel) || !res_total(c->sel2)) return false;
    760         uint8_t cand = game_offer_candidates(c->g, c->actor);
    761         if (!(cand & (cand - 1))) return finish(c, out, offer_move(c, cand));   /* un solo rival */
    762         c->offer_mask = cand;
    763         c->icursor = 0;
    764         offer_to_menu(c);
    765         return false;
    766     }
    767     }
    768     return false;
    769 }
    770 
    771 /* Las flechas mueven el cursor al vecino del tablero (vertice por arista, arista que
    772  * comparte un vertice, hexagono de al lado) que mejor sigue esa direccion, sea valido o
    773  * no: asi se camina como por el tablero de verdad. Tab salta al proximo valido. */
    774 static int neighbors(const Ctrl *c, int id, int out[6])
    775 {
    776     const Topo *t = game_topo(c->g);
    777     int n = 0;
    778     if (c->mode == CM_VERT) {
    779         for (int k = 0; k < 3; k++) if (t->vert_adj[id][k] >= 0) out[n++] = t->vert_adj[id][k];
    780     } else if (c->mode == CM_EDGE) {
    781         for (int s = 0; s < 2; s++) {
    782             int v = t->edge_v[id][s];
    783             for (int k = 0; k < 3; k++) {
    784                 int e = t->vert_edge[v][k];
    785                 if (e >= 0 && e != id) out[n++] = e;
    786             }
    787         }
    788     } else {
    789         for (int k = 0; k < 6; k++) if (t->hex_adj[id][k] >= 0) out[n++] = t->hex_adj[id][k];
    790     }
    791     return n;
    792 }
    793 
    794 static void nav(Ctrl *c, int key)
    795 {
    796     if (c->pos < 0) { if (c->ntargets) { c->cursor = 0; c->pos = c->targets[0]; } return; }
    797     int x0, y0, nb[6], n = neighbors(c, c->pos, nb), best = -1;
    798     long bs = 0;
    799     target_pos(c, c->pos, &x0, &y0);
    800     for (int i = 0; i < n; i++) {
    801         int x, y;
    802         target_pos(c, nb[i], &x, &y);
    803         /* coords de topologia -> proporcion real (x en sqrt3/2, y en 1/2) */
    804         long dx = (long)(x - x0) * 866, dy = (long)(y - y0) * 500, along, perp;
    805         switch (key) {
    806         case K_RIGHT: along = dx;  perp = dy; break;
    807         case K_LEFT:  along = -dx; perp = dy; break;
    808         case K_DOWN:  along = dy;  perp = dx; break;
    809         default:      along = -dy; perp = dx; break;
    810         }
    811         if (perp < 0) perp = -perp;
    812         if (along <= perp / 2) continue;             /* mas de ~63 grados de desvio: no */
    813         long sc = along * 1000 / (along + perp);     /* lo mas derecho posible */
    814         if (best < 0 || sc > bs) { bs = sc; best = nb[i]; }
    815     }
    816     if (best < 0) return;
    817     c->pos = best;
    818     for (int i = 0; i < c->ntargets; i++) if (c->targets[i] == best) c->cursor = i;
    819     target_pos(c, c->pos, &c->last_x, &c->last_y);
    820 }
    821 
    822 static bool mandatory(const Ctrl *c)
    823 {
    824     switch (c->intent) {
    825     case I_SETUP_S: case I_SETUP_R: case I_ROBBER: case I_NEUTRAL: case I_DISCARD: case I_GOLD:
    826     case I_GIVEBACK: case I_RESPOND: case I_VICTIM: case I_ROBBER_OK:
    827         return true;
    828     }
    829     return false;
    830 }
    831 
    832 /* Si Esc (K_BACK) hace algo en este estado: cancelar lo que se estaba eligiendo, cerrar
    833  * un menu, deshacer el borrador de la colocacion inicial. Lo usan las interfaces tactiles
    834  * para mostrar "Volver" solo cuando sirve. */
    835 bool ctrl_can_back(const Ctrl *c)
    836 {
    837     if (c->mode == CM_IDLE || c->mode == CM_OVER || c->mode == CM_HANDOFF) return false;
    838     if (c->g && c->g->phase == PH_SETUP && !c->g->setup_sub && c->draft_v >= 0) return true;   /* deshacer un paso */
    839     if (c->mode == CM_MENU && c->intent == I_OFFER_TO) return true;                           /* vuelve a las cartas */
    840     if (c->mode == CM_MENU && (c->intent == I_VICTIM || c->intent == I_ROBBER_OK)) return true;   /* otro lugar para el ladron */
    841     if (c->mode == CM_CARDS && mandatory(c)) return false;                                     /* solo borra la seleccion */
    842     return !mandatory(c);
    843 }
    844 
    845 bool ctrl_key(Ctrl *c, int key, Move *out)
    846 {
    847     c->msg[0] = 0;
    848     if (!c->g) return false;
    849     if (c->mode == CM_HANDOFF) {
    850         if (key == K_OK || key == ' ') { c->shown = c->actor; enter_default(c); }
    851         return false;
    852     }
    853     if (c->actor < 0) return false;
    854     if ((key == 'u' || key == K_BACK) && setup_back(c)) return false;
    855     if (key == 'u' && c->actor == c->g->cur && c->mode != CM_CARDS && c->intent != I_OFFER_TO) {
    856         Move u = base_move(c, M_UNDO);
    857         if (game_check(c->g, &u) == E_OK) return finish(c, out, u);
    858     }
    859 
    860     switch (c->mode) {
    861     case CM_IDLE:
    862         if (key == K_UP && c->nactions) c->icursor = (c->icursor + c->nactions - 1) % c->nactions;
    863         else if (key == K_DOWN && c->nactions) c->icursor = (c->icursor + 1) % c->nactions;
    864         else if (key == K_OK && c->nactions) {
    865             MenuItem *it = &c->actions[c->icursor];
    866             if (it->enabled) return do_action(c, it->action, it->arg, out);
    867         } else if (key == ' ' && c->g->phase == PH_ROLL) {
    868             return do_action(c, A_ROLL, 0, out);
    869         } else {
    870             for (int i = 0; i < c->nactions; i++)
    871                 if (c->actions[i].key == key && c->actions[i].enabled) {
    872                     c->icursor = i;
    873                     return do_action(c, c->actions[i].action, c->actions[i].arg, out);
    874                 }
    875         }
    876         return false;
    877 
    878     case CM_VERT: case CM_EDGE: case CM_HEX:
    879         if (key == K_UP || key == K_DOWN || key == K_LEFT || key == K_RIGHT) nav(c, key);
    880         else if (key == K_TAB && c->ntargets) {
    881             int k = ctrl_cursor_target(c) >= 0 ? (c->cursor + 1) % c->ntargets : c->cursor;
    882             c->cursor = k;
    883             c->pos = c->targets[k];
    884             target_pos(c, c->pos, &c->last_x, &c->last_y);
    885         } else if (key == K_OK || key == ' ') {
    886             int id = ctrl_cursor_target(c);
    887             if (id >= 0) return commit_target(c, id, out);
    888             if (c->pos >= 0) info(c, S_NOT_HERE);
    889         } else if (key == K_BACK) {
    890             if (c->intent == I_FREE_ROAD) return finish(c, out, base_move(c, M_END));
    891             if (!mandatory(c)) enter_default(c);
    892         }
    893         return false;
    894 
    895     case CM_MENU:
    896         if (key == K_UP && c->nitems) c->icursor = (c->icursor + c->nitems - 1) % c->nitems;
    897         else if (key == K_DOWN && c->nitems) c->icursor = (c->icursor + 1) % c->nitems;
    898         else if (key == K_OK || (key == ' ' && c->intent != I_OFFER_TO)) return select_item(c, c->icursor, out);
    899         else if (key == K_BACK) {
    900             if (c->intent == I_OFFER_TO) { c->mode = CM_CARDS; c->intent = I_OFFER; }
    901             else if (c->intent == I_VICTIM || c->intent == I_ROBBER_OK) enter_pick(c, CM_HEX, I_ROBBER);   /* otro lugar */
    902             else if (!mandatory(c)) enter_default(c);
    903         } else if (key == ' ' && c->intent == I_OFFER_TO) return select_item(c, c->icursor, out);
    904         else
    905             for (int i = 0; i < c->nitems; i++)
    906                 if (c->items[i].key == key) { c->icursor = i; return select_item(c, i, out); }
    907         return false;
    908 
    909     case CM_CARDS:
    910         if (key == K_LEFT) c->rescur = (c->rescur + NRES - 1) % NRES;
    911         else if (key == K_RIGHT) c->rescur = (c->rescur + 1) % NRES;
    912         else if (key == K_UP || key == K_PLUS || key == '+') card_change(c, c->row, c->rescur, 1);
    913         else if (key == K_DOWN || key == K_MINUS || key == '-') card_change(c, c->row, c->rescur, -1);
    914         else if (key == K_TAB && (c->intent == I_OFFER || c->intent == I_COUNTER)) c->row ^= 1;
    915         else if (key >= '1' && key <= '5') { c->rescur = key - '1'; card_change(c, c->row, c->rescur, 1); }
    916         else if (key == K_OK) return cards_commit(c, out);
    917         else if (key == K_BACK) { if (!mandatory(c)) enter_default(c); else { memset(c->sel, 0, NRES); } }
    918         return false;
    919     }
    920     return false;
    921 }
    922 
    923 bool ctrl_pick(Ctrl *c, int kind, int id, Move *out)
    924 {
    925     c->msg[0] = 0;
    926     if (c->mode == CM_HANDOFF) return ctrl_key(c, K_OK, out);
    927     if (c->actor < 0) return false;
    928     switch (kind) {
    929     case PK_VERT: case PK_EDGE: case PK_HEX: {
    930         int want = kind == PK_VERT ? CM_VERT : kind == PK_EDGE ? CM_EDGE : CM_HEX;
    931         if (c->mode != want) return false;
    932         for (int i = 0; i < c->ntargets; i++)
    933             if (c->targets[i] == id) {
    934                 c->cursor = i;
    935                 c->pos = id;
    936                 target_pos(c, id, &c->last_x, &c->last_y);
    937                 return commit_target(c, id, out);
    938             }
    939         return false;
    940     }
    941     case PK_ITEM:
    942         if (c->mode != CM_MENU) return false;
    943         c->icursor = id;
    944         return select_item(c, id, out);
    945     case PK_ACTION:
    946         if (c->mode != CM_IDLE && !(c->mode == CM_MENU && !mandatory(c)) &&
    947             !((c->mode == CM_VERT || c->mode == CM_EDGE || c->mode == CM_CARDS) && !mandatory(c))) return false;
    948         if (id < 0 || id >= c->nactions || !c->actions[id].enabled) return false;
    949         c->icursor = id;
    950         return do_action(c, c->actions[id].action, c->actions[id].arg, out);
    951     case PK_CARD: {
    952         if (c->mode != CM_CARDS) return false;
    953         if (id == 100) return cards_commit(c, out);
    954         if (id == 101) { if (!mandatory(c)) enter_default(c); return false; }
    955         int row = id / (NRES * 2), r = (id / 2) % NRES, minus = id & 1;
    956         if (row && c->intent != I_OFFER && c->intent != I_COUNTER) return false;
    957         c->row = row;
    958         c->rescur = r;
    959         card_change(c, row, r, minus ? -1 : 1);
    960         return false;
    961     }
    962     }
    963     return false;
    964 }
    965 
    966 /* ------------------------------------------------------------- textos */
    967 
    968 const char *ctrl_prompt(const Ctrl *c)
    969 {
    970     static char buf[128];
    971     Sb s = { buf, sizeof buf, 0 };
    972     buf[0] = 0;
    973     const Game *g = c->g;
    974     if (!g) return buf;
    975     if (c->mode == CM_OVER || g->phase == PH_OVER) {
    976         if (g->winner >= 0) { sb_s(&s, c->names[g->winner]); sb_s(&s, " "); sb_s(&s, tr(S_WINS)); }
    977         else sb_s(&s, tr(S_P_OVER));
    978         return buf;
    979     }
    980     if (c->mode == CM_HANDOFF) {
    981         sb_s(&s, tr(S_P_HANDOFF)); sb_s(&s, " "); sb_s(&s, c->names[c->actor]); sb_s(&s, " — "); sb_s(&s, tr(S_P_ENTER));
    982         return buf;
    983     }
    984     if (c->actor < 0) {
    985         int w = g->cur;
    986         if (g->phase == PH_DISCARD)
    987             for (int p = 0; p < g->o.np; p++) if (g->p[p].discard) { w = p; break; }
    988         sb_s(&s, tr(S_P_WAIT)); sb_s(&s, " "); sb_s(&s, c->names[w]); sb_s(&s, " ("); sb_s(&s, phase_text(g->phase)); sb_s(&s, ")");
    989         return buf;
    990     }
    991     switch (c->intent) {
    992     case I_SETUP_S:   sb_s(&s, tr(S_P_SETUP_S)); break;
    993     case I_SETUP_R:   sb_s(&s, tr(S_P_SETUP_R)); if (c->draft_v >= 0) { sb_s(&s, " "); sb_s(&s, tr(S_P_SETUP_U)); } break;
    994     case I_SETUP_OK:  sb_s(&s, tr(S_P_SETUP_OK)); break;
    995     case I_SETTLE:    sb_s(&s, tr(S_P_SETTLE)); break;
    996     case I_ROAD:      sb_s(&s, tr(S_P_ROAD)); break;
    997     case I_CITY:      sb_s(&s, tr(S_P_CITY)); break;
    998     case I_FREE_ROAD: sb_s(&s, tr(S_P_FREE_ROAD)); break;
    999     case I_NEUTRAL:   sb_s(&s, tr(c->mode == CM_VERT ? S_P_NEUTRAL_S : S_P_NEUTRAL_R)); break;
   1000     case I_ROBBER:    sb_s(&s, tr(S_P_ROBBER)); break;
   1001     case I_VICTIM:    sb_s(&s, tr(S_P_VICTIM)); break;
   1002     case I_ROBBER_OK: sb_s(&s, tr(S_P_ROBBER_OK)); break;
   1003     case I_DISCARD:   sb_s(&s, tr(S_P_DISCARD)); sb_s(&s, " "); sb_i(&s, res_total(c->sel)); sb_s(&s, "/"); sb_i(&s, c->need); break;
   1004     case I_GIVEBACK:  sb_s(&s, tr(S_P_GIVEBACK)); sb_s(&s, " "); sb_i(&s, res_total(c->sel)); sb_s(&s, "/"); sb_i(&s, c->need); break;
   1005     case I_PLENTY:    sb_s(&s, tr(S_P_PLENTY)); break;
   1006     case I_GOLD:      sb_s(&s, tr(S_P_GOLD)); sb_s(&s, " "); sb_i(&s, res_total(c->sel)); sb_s(&s, "/"); sb_i(&s, c->need); break;
   1007     case I_SHIP:      sb_s(&s, tr(S_P_SHIP)); break;
   1008     case I_MOVESHIP_FROM: sb_s(&s, tr(S_P_MOVESHIP_FROM)); break;
   1009     case I_MOVESHIP_TO:   sb_s(&s, tr(S_P_MOVESHIP_TO)); break;
   1010     case I_MONO:      sb_s(&s, tr(S_P_MONO)); break;
   1011     case I_BANK_GIVE: sb_s(&s, tr(S_P_BANK_GIVE)); break;
   1012     case I_BANK_GET:  sb_s(&s, tr(S_P_BANK_GET)); break;
   1013     case I_OFFER:     sb_s(&s, tr(S_P_OFFER)); break;
   1014     case I_COUNTER:   sb_s(&s, tr(S_P_COUNTER)); break;
   1015     case I_OFFER_TO:  sb_s(&s, tr(S_P_OFFER_TO)); break;
   1016     case I_DEV:       sb_s(&s, tr(S_P_DEV)); break;
   1017     case I_RESPOND:
   1018         sb_s(&s, c->names[g->offer.from]); sb_s(&s, " "); sb_s(&s, tr(S_P_RESPOND)); sb_s(&s, ": ");
   1019         sb_s(&s, tr(S_GIVES_YOU)); sb_s(&s, " "); sb_res(&s, g->offer.give); sb_s(&s, ", ");
   1020         sb_s(&s, tr(S_WANTS_YOU)); sb_s(&s, " "); sb_res(&s, g->offer.get);
   1021         break;
   1022     default:
   1023         if (g->phase == PH_ROLL) sb_s(&s, tr(S_P_ROLL));
   1024         else if (g->offer.active && g->offer.from == c->actor) {
   1025             int acc = 0, pending = 0;
   1026             for (int q = 0; q < g->o.np; q++) {
   1027                 if (q == g->offer.from || !((g->offer.tomask >> q) & 1)) continue;
   1028                 if (g->offer.resp[q] == 1) acc++;
   1029                 else if (g->offer.resp[q] == 0) pending++;
   1030             }
   1031             sb_s(&s, tr(S_OFFERS)); sb_s(&s, ": "); sb_s(&s, tr(S_GIVES)); sb_s(&s, " "); sb_res(&s, g->offer.give); sb_s(&s, ", ");
   1032             sb_s(&s, tr(S_WANTS)); sb_s(&s, " "); sb_res(&s, g->offer.get); sb_s(&s, " — ");
   1033             sb_s(&s, tr(acc >= 2 ? S_PICK_DEAL : pending ? S_WAITING_RESP : S_ANSWERED));
   1034         } else sb_s(&s, tr(g->phase == PH_PAIRED ? S_P_PAIRED : S_P_MAIN));
   1035         break;
   1036     }
   1037     return buf;
   1038 }
   1039 
   1040 /* Como move_describe, pero contado para quien mira (viewers = asientos de esta
   1041  * pantalla): que carta se robo lo saben solo el ladron y la victima. */
   1042 int move_describe_for(const Game *g, const Move *m, const char *const names[MAXP], unsigned viewers, char *buf, int n)
   1043 {
   1044     Move pm;
   1045     move_public(m, viewers, &pm);
   1046     return move_describe(g, &pm, names, buf, n);
   1047 }
   1048 
   1049 int move_describe(const Game *g, const Move *m, const char *const names[MAXP], char *buf, int n)
   1050 {
   1051     Sb s = { buf, n, 0 };
   1052     buf[0] = 0;
   1053     const char *who = (m->p >= 0 && m->p < MAXP && names[m->p]) ? names[m->p] : "?";
   1054     sb_s(&s, who);
   1055     sb_s(&s, " ");
   1056     switch (m->type) {
   1057     case M_SETTLE: sb_s(&s, tr(S_BUILT)); sb_s(&s, " "); sb_s(&s, tr(S_SETTLE)); break;
   1058     case M_ROAD:   sb_s(&s, tr(S_BUILT)); sb_s(&s, " "); sb_s(&s, tr(S_ROAD)); break;
   1059     case M_CITY:   sb_s(&s, tr(S_BUILT)); sb_s(&s, " "); sb_s(&s, tr(S_CITY)); break;
   1060     case M_ROLL:
   1061         sb_s(&s, tr(S_ROLLED)); sb_s(&s, " ");
   1062         sb_i(&s, m->r[0]); sb_s(&s, "+"); sb_i(&s, m->r[1]); sb_s(&s, "="); sb_i(&s, m->r[0] + m->r[1]);
   1063         if (m->r[2]) {
   1064             sb_s(&s, " "); sb_s(&s, tr(S_AND)); sb_s(&s, " ");
   1065             sb_i(&s, m->r[2]); sb_s(&s, "+"); sb_i(&s, m->r[3]); sb_s(&s, "="); sb_i(&s, m->r[2] + m->r[3]);
   1066         }
   1067         break;
   1068     case M_DISCARD: sb_s(&s, tr(S_DISCARDED)); sb_s(&s, " "); sb_res(&s, m->r); break;
   1069     case M_ROBBER:
   1070         sb_s(&s, tr(S_MOVED_ROBBER)); sb_s(&s, " ("); sb_s(&s, terrain_name(g->terrain[m->a]));
   1071         if (g->num[m->a]) { sb_s(&s, " "); sb_i(&s, g->num[m->a]); }
   1072         sb_s(&s, ")");
   1073         if (m->b >= 0 && m->c >= 0) {          /* "y le robo Trigo a Pepe" (solo lo ven los dos) */
   1074             sb_s(&s, " "); sb_s(&s, tr(S_STOLE)); sb_s(&s, " "); sb_s(&s, res_name(m->c));
   1075             sb_s(&s, " "); sb_s(&s, tr(S_FROM_WHO)); sb_s(&s, " "); sb_s(&s, names[m->b]);
   1076         } else if (m->b >= 0) {
   1077             sb_s(&s, " "); sb_s(&s, tr(S_STOLE_CARD)); sb_s(&s, " "); sb_s(&s, names[m->b]);
   1078         }
   1079         break;
   1080     case M_BUYDEV: sb_s(&s, tr(S_BOUGHT_DEV)); break;
   1081     case M_SHIP:   sb_s(&s, tr(S_BUILT)); sb_s(&s, " "); sb_s(&s, tr(S_A_SHIP)); break;
   1082     case M_MOVESHIP: sb_s(&s, tr(S_MOVED_SHIP)); break;
   1083     case M_GOLD:   sb_s(&s, tr(S_TOOK_GOLD)); sb_s(&s, " "); sb_res(&s, m->r); break;
   1084     case M_KNIGHT: sb_s(&s, tr(S_PLAYED)); sb_s(&s, " "); sb_s(&s, dev_name(D_KNIGHT)); break;
   1085     case M_ROADS:  sb_s(&s, tr(S_PLAYED)); sb_s(&s, " "); sb_s(&s, dev_name(D_ROADS)); break;
   1086     case M_PLENTY:
   1087         sb_s(&s, tr(S_PLAYED)); sb_s(&s, " "); sb_s(&s, dev_name(D_PLENTY)); sb_s(&s, ": ");
   1088         sb_s(&s, res_name(m->a)); sb_s(&s, ", "); sb_s(&s, res_name(m->b));
   1089         break;
   1090     case M_MONO:
   1091         sb_s(&s, tr(S_PLAYED)); sb_s(&s, " "); sb_s(&s, dev_name(D_MONO)); sb_s(&s, ": "); sb_s(&s, res_name(m->a));
   1092         break;
   1093     case M_BANK:
   1094         sb_s(&s, tr(S_TRADED_BANK)); sb_s(&s, ": ");
   1095         sb_i(&s, game_bank_ratio(g, m->p, m->a) * m->c); sb_s(&s, " "); sb_s(&s, res_name(m->a));
   1096         sb_s(&s, " → "); sb_i(&s, m->c); sb_s(&s, " "); sb_s(&s, res_name(m->b));
   1097         break;
   1098     case M_OFFER:
   1099         sb_s(&s, tr(S_OFFERS)); sb_s(&s, ": "); sb_s(&s, tr(S_GIVES)); sb_s(&s, " "); sb_res(&s, m->r); sb_s(&s, ", ");
   1100         sb_s(&s, tr(S_WANTS)); sb_s(&s, " "); sb_res(&s, m->r2);
   1101         if (m->a != -1 && m->a != game_offer_candidates(g, m->p)) {
   1102             sb_s(&s, " ("); sb_s(&s, tr(S_TO));
   1103             bool first = true;
   1104             for (int q = 0; q < MAXP; q++)
   1105                 if ((m->a >> q) & 1) { sb_s(&s, first ? " " : ", "); sb_s(&s, names[q]); first = false; }
   1106             sb_s(&s, ")");
   1107         }
   1108         break;
   1109     case M_ACCEPT: sb_s(&s, tr(S_ACCEPTS)); break;
   1110     case M_COUNTER:
   1111         sb_s(&s, tr(S_COUNTERS)); sb_s(&s, ": "); sb_s(&s, tr(S_GIVES)); sb_s(&s, " "); sb_res(&s, m->r); sb_s(&s, ", ");
   1112         sb_s(&s, tr(S_WANTS)); sb_s(&s, " "); sb_res(&s, m->r2);
   1113         break;
   1114     case M_REJECT: sb_s(&s, tr(S_REJECTS)); break;
   1115     case M_CONFIRM: sb_s(&s, tr(S_TRADED_WITH)); sb_s(&s, " "); sb_s(&s, names[m->a]); break;
   1116     case M_CANCEL: sb_s(&s, tr(S_CANCELS)); break;
   1117     case M_END:    sb_s(&s, tr(S_ENDS_TURN)); break;
   1118     case M_NEUTRAL:
   1119         sb_s(&s, tr(S_BUILT)); sb_s(&s, " "); sb_s(&s, tr(m->b ? S_SETTLE : S_ROAD));
   1120         if (m->p != m->a) { sb_s(&s, " "); sb_s(&s, tr(S_FOR_NEUTRAL)); }
   1121         break;
   1122     case M_TOKTRADE:
   1123         sb_s(&s, tr(S_FORCED_TRADE)); sb_s(&s, " "); sb_s(&s, names[game_rival(g, m->p) < 0 ? 0 : game_rival(g, m->p)]);
   1124         break;
   1125     case M_GIVEBACK: sb_s(&s, tr(S_GAVE_BACK)); sb_s(&s, " "); sb_res(&s, m->r); break;
   1126     case M_TOKROBBER: sb_s(&s, tr(S_ROBBER_DESERT)); break;
   1127     case M_WIN: sb_s(&s, tr(S_WINS)); sb_s(&s, "!"); break;
   1128     case M_UNDO: sb_s(&s, tr(S_UNDID)); break;
   1129     }
   1130     return s.pos;
   1131 }