commit 3528f3271236ebbbf4fce5f5f2612df62d8579b6
parent 0b40051a1a09805653363e4b6ab3176759f00312
Author: Francisco Armando Rosmino <franciscorosmino@rosmino.com.ar>
Date: Sat, 3 Oct 2026 12:57:19 -0300
Jugar otra al terminar una partida local
Al final de la partida, 'a' (o tocar la linea en el cuadro) arma otra con las
mismas opciones, asientos y nombres, con otro reparto del mismo mapa y otros
dados (app_play devuelve 1 y app_play_again rearma la sesion). Solo partidas
locales; en GUI, TUI y web.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Diffstat:
7 files changed, 55 insertions(+), 19 deletions(-)
diff --git a/catan/core/ctrl.h b/catan/core/ctrl.h
@@ -44,6 +44,7 @@ typedef struct {
int modal; /* dialogo de la app encima (salir, etc): 0 ninguno */
const char *modal_text;
bool can_free; /* al terminar se puede seguir en juego libre (partida local) */
+ bool can_again; /* al terminar: jugar otra con las mismas opciones y otro reparto (local) */
bool touch; /* pantalla tactil sin teclado: botones para lo que solo tenia tecla */
const char *preview; /* no NULL: vista previa del tablero antes de crear la partida (titulo);
abajo van los botones PK_APP 20 mapa anterior, 21 otro reparto,
diff --git a/catan/core/strings.h b/catan/core/strings.h
@@ -69,6 +69,7 @@
X(S_TURN_OF, "Turno de", "Turn:") \
X(S_WINS, "gana la partida", "wins the game") \
X(S_GO_FREE, "[c] seguir jugando (libre) · Enter salir", "[c] keep playing (free) · Enter quit") \
+ X(S_AGAIN, "[a] jugar otra (mismas opciones, otro reparto)", "[a] play again (same settings, new layout)") \
X(S_WON_FREE, "ganó · juego libre", "won · free play") \
X(S_ROLLED, "tiró", "rolled") \
X(S_BUILT, "construyó", "built") \
diff --git a/catan/gfx/screen.c b/catan/gfx/screen.c
@@ -260,7 +260,7 @@ static void modal_text(Surface *s, int bx, int by, int bw, int bh, const char *l
static void modal_over(Surface *s, const Game *g, const UiInfo *ui, int bx, int by, int bw, int bh)
{
- int w = bw * 4 / 5, h = 40 + g->o.np * 16 + (ui->can_free ? 16 : 0);
+ int w = bw * 4 / 5, h = 40 + g->o.np * 16 + (ui->can_free ? 16 : 0) + (ui->can_again ? 16 : 0);
int x = bx + (bw - w) / 2, y = by + (bh - h) / 2;
box(s, x, y, w, h, C_PANEL, C_ACCENT);
char buf[64] = "";
@@ -277,7 +277,8 @@ static void modal_over(Surface *s, const Game *g, const UiInfo *ui, int bx, int
gfx_text(s, &font_bold, x + w - 60, ly - 1, v, C_ACCENT);
ly += 16;
}
- if (ui->can_free) center_text(s, &font_normal, x + 4, ly + 2, w - 8, tr(S_GO_FREE), C_OK);
+ if (ui->can_free) { center_text(s, &font_normal, x + 4, ly + 2, w - 8, tr(S_GO_FREE), C_OK); ly += 16; }
+ if (ui->can_again) center_text(s, &font_normal, x + 4, ly + 2, w - 8, tr(S_AGAIN), C_ACCENT);
}
static void modal_help(Surface *s, int bx, int by, int bw, int bh)
@@ -319,10 +320,11 @@ static void modals(Surface *s, const Game *g, const Ctrl *c, const UiInfo *ui, i
hit_add(hits, bx, by, bw, bh, PK_ITEM, 0);
} else if (c->mode == CM_OVER || g->phase == PH_OVER) modal_over(s, g, ui, bx, by, bw, bh);
if (c->mode == CM_OVER || g->phase == PH_OVER) { /* tocar el cuadro: volver (9); la ultima linea: juego libre (10) */
- int w = bw * 4 / 5, h = 40 + g->o.np * 16 + (ui->can_free ? 16 : 0);
- int x = bx + (bw - w) / 2, y = by + (bh - h) / 2;
+ int w = bw * 4 / 5, h = 40 + g->o.np * 16 + (ui->can_free ? 16 : 0) + (ui->can_again ? 16 : 0);
+ int x = bx + (bw - w) / 2, y = by + (bh - h) / 2, ly = y + h - 2;
hit_add(hits, x, y, w, h, PK_APP, 9);
- if (ui->can_free) hit_add(hits, x, y + h - 18, w, 18, PK_APP, 10);
+ if (ui->can_again) { hit_add(hits, x, ly - 16, w, 16, PK_APP, 14); ly -= 16; }
+ if (ui->can_free) hit_add(hits, x, ly - 16, w, 16, PK_APP, 10);
}
if (ui->show_help) modal_help(s, bx, by, bw, bh);
if (ui->modal == 2) { /* menu de la app (tactil): ayuda, salir */
diff --git a/catan/linux/app.c b/catan/linux/app.c
@@ -267,7 +267,27 @@ static bool picking(const Ctrl *c)
return c->mode == CM_VERT || c->mode == CM_EDGE || c->mode == CM_HEX || c->mode == CM_MENU || c->mode == CM_CARDS;
}
-void app_play(Frontend *fe, Session *s, Settings *st)
+/* "Jugar otra": las mismas opciones, asientos y nombres de la partida local que
+ * termino, con otro reparto del tablero (y otros dados). */
+void app_play_again(Session *s, Settings *st)
+{
+ Opts o = s->g->o;
+ uint8_t kinds[MAXP];
+ char names[MAXP][20];
+ for (int i = 0; i < MAXP; i++) {
+ kinds[i] = s->host.kind[i] == SEAT_REMOTE ? SEAT_LOCAL : s->host.kind[i];
+ snprintf(names[i], sizeof names[i], "%s", s->names[i] ? s->names[i] : "");
+ }
+ Rng r;
+ rng_seed(&r, fresh_seed(), 0x3A9);
+ if (!o.beginner) o.bseed = 1 + rng_below(&r, 0x7FFFFFFE); /* otro reparto del mismo mapa */
+ sess_close(s);
+ sess_new_local(s, &o, fresh_seed(), kinds, names);
+ s->handoff = st->handoff;
+ sess_set_bots(s, st->bot_hard);
+}
+
+int app_play(Frontend *fe, Session *s, Settings *st)
{
Ctrl c;
ctrl_init(&c, s->local_mask, s->handoff);
@@ -299,13 +319,14 @@ void app_play(Frontend *fe, Session *s, Settings *st)
ui.show_help = help;
ui.touch = fe->touch;
ui.can_free = s->local && !s->net && s->save_path[0] && s->g->winner >= 0;
+ ui.can_again = s->local && !s->net && s->g->phase == PH_OVER;
fe->draw_game(fe, s->g, &c, &ui);
int fds[16];
int nf = sess_fds(s, fds, 16);
InEvent ev;
if (!fe->wait(fe, sess_timeout(s), fds, nf, &ev)) continue;
- if (ev.type == EV_QUIT) return;
+ if (ev.type == EV_QUIT) return 0;
Move m;
if (ev.type == EV_WHEEL) {
const Hit *h = hit_find(&fe->hits, ev.x, ev.y);
@@ -326,7 +347,7 @@ void app_play(Frontend *fe, Session *s, Settings *st)
if (ev.type == EV_CLICK && !modal && !help) { /* botones de la pantalla tactil: valen como teclas */
const Hit *h = hit_find(&fe->hits, ev.x, ev.y);
if (h && h->kind == PK_APP) {
- int k = h->id == 7 ? K_BACK : h->id == 9 ? K_OK : h->id == 10 ? 'c' : 0;
+ int k = h->id == 7 ? K_BACK : h->id == 9 ? K_OK : h->id == 10 ? 'c' : h->id == 14 ? 'a' : 0;
if (k) { ev.type = EV_KEY; ev.key = k; typing = false; }
}
}
@@ -366,13 +387,13 @@ void app_play(Frontend *fe, Session *s, Settings *st)
if (ev.type == EV_KEY) {
int k = ev.key;
if (modal) {
- if (modal == 1 && (k == 's' || k == 'S' || k == 'y' || k == 'Y' || k == K_OK)) return;
+ if (modal == 1 && (k == 's' || k == 'S' || k == 'y' || k == 'Y' || k == K_OK)) return 0;
modal = 0;
continue;
}
if (help) { help = false; continue; }
if (k == 'q' || k == 'Q') {
- if (s->g->phase == PH_OVER) return;
+ if (s->g->phase == PH_OVER) return 0;
modal = 1;
continue;
}
@@ -383,7 +404,8 @@ void app_play(Frontend *fe, Session *s, Settings *st)
ctrl_init(&c, s->local_mask, s->handoff);
continue;
}
- if (s->g->phase == PH_OVER && (k == K_OK || k == K_BACK)) return;
+ if (s->g->phase == PH_OVER && (k == 'a' || k == 'A') && ui.can_again) return 1; /* jugar otra */
+ if (s->g->phase == PH_OVER && (k == K_OK || k == K_BACK)) return 0;
if (k == '?') { help = true; continue; }
if (k == 'L') {
@@ -405,7 +427,7 @@ void app_play(Frontend *fe, Session *s, Settings *st)
if (modal) {
const Hit *h = hit_find(&fe->hits, ev.x, ev.y);
int id = h && h->kind == PK_APP ? h->id : -1;
- if (modal == 1 && id == 8) return; /* "si" del dialogo de salir */
+ if (modal == 1 && id == 8) return 0; /* "si" del dialogo de salir */
if (modal == 2 && id == 1) { modal = 0; help = true; continue; } /* menu de la app (tactil) */
if (modal == 2 && id == 0) { modal = 1; continue; }
modal = 0;
@@ -725,14 +747,17 @@ void app_menu(Frontend *fe, Settings *st)
static Session s;
switch (r) {
case M_NEW:
- if (new_game(fe, st, &s)) { app_play(fe, &s, st); sess_close(&s); }
+ if (new_game(fe, st, &s)) {
+ while (app_play(fe, &s, st) == 1) app_play_again(&s, st);
+ sess_close(&s);
+ }
break;
case M_CONTINUE: {
char err[128] = "";
if (have_save && sess_resume(&s, last, err, sizeof err)) {
s.handoff = st->handoff;
sess_set_bots(&s, st->bot_hard);
- app_play(fe, &s, st);
+ while (app_play(fe, &s, st) == 1) app_play_again(&s, st);
sess_close(&s);
}
break;
diff --git a/catan/linux/app.h b/catan/linux/app.h
@@ -27,7 +27,8 @@ int app_sim(int n, uint64_t seed0, int players, bool v2p);
extern int g_sim_scenario, g_sim_map;
int app_duel(int n, uint64_t seed0, int players, bool v2p, const SearchCfg *cfg);
void app_menu(Frontend *fe, Settings *st);
-void app_play(Frontend *fe, Session *s, Settings *st);
+int app_play(Frontend *fe, Session *s, Settings *st); /* 1: "jugar otra" (ver app_play_again) */
+void app_play_again(Session *s, Settings *st); /* misma partida local, otro reparto y otros dados */
int run_form(Frontend *fe, Form *f, void (*on_change)(Form *, void *), void *ctx);
uint64_t fresh_seed(void);
diff --git a/catan/linux/main.c b/catan/linux/main.c
@@ -117,7 +117,7 @@ int main(int argc, char **argv)
sess_new_local(&s, &o, fresh_seed(), kinds, names);
sess_set_bots(&s, st.bot_hard);
s.handoff = st.handoff;
- app_play(fe, &s, &st);
+ while (app_play(fe, &s, &st) == 1) app_play_again(&s, &st);
sess_close(&s);
} else if (!strcmp(cmd, "resume")) {
char path[512], err[160] = "";
@@ -126,7 +126,7 @@ int main(int argc, char **argv)
if (!sess_resume(&s, path, err, sizeof err)) { fe->close(fe); fprintf(stderr, "%s: %s\n", path, err); return 1; }
sess_set_bots(&s, st.bot_hard);
s.handoff = st.handoff;
- app_play(fe, &s, &st);
+ while (app_play(fe, &s, &st) == 1) app_play_again(&s, &st);
sess_close(&s);
} else if (!strcmp(cmd, "online")) {
online_menu(fe, &st, nargs ? args[0] : st.server);
diff --git a/catan/linux/tui.c b/catan/linux/tui.c
@@ -404,7 +404,7 @@ static void draw_cards_modal(Frontend *f, const Game *g, const Ctrl *c, int bx,
static void draw_over(const Game *g, const UiInfo *ui, int bx, int by, int bw, int bh)
{
- int w = 48, h = g->o.np + 6 + (ui->can_free ? 2 : 0);
+ int w = 48, h = g->o.np + 6 + (ui->can_free ? 2 : 0) + (ui->can_again ? 1 : 0);
int x = bx + (bw - w) / 2, y = by + (bh - h) / 2;
tk_box(y, x, h, w, tr(S_P_OVER), C_ACCENT);
char buf[96];
@@ -421,9 +421,15 @@ static void draw_over(const Game *g, const UiInfo *ui, int bx, int by, int bw, i
tk_att(255, 236, p == g->winner ? A_BOLD : 0);
tk_put(y + 4 + p, x + 6, buf, w - 8);
}
+ int ly = y + 5 + g->o.np;
if (ui->can_free) {
tk_att(tk_xterm(C_OK), 236, 0);
- tk_put(y + 5 + g->o.np, x + (w - tk_ulen(tr(S_GO_FREE))) / 2, tr(S_GO_FREE), w - 4);
+ tk_put(ly, x + (w - tk_ulen(tr(S_GO_FREE))) / 2, tr(S_GO_FREE), w - 4);
+ ly++;
+ }
+ if (ui->can_again) {
+ tk_att(tk_xterm(C_ACCENT), 236, 0);
+ tk_put(ly, x + (w - tk_ulen(tr(S_AGAIN))) / 2, tr(S_AGAIN), w - 4);
}
}