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 }