chess_game.c (7687B)
1 /* chess_game.c - el ajedrez como GameModule del servidor de comun/: el servidor 2 * es la autoridad, lleva el reloj y hace jugar a los bots. */ 3 #include <stdio.h> 4 #include <string.h> 5 #include "server.h" 6 7 typedef struct { 8 Game g; 9 int wseat; /* asiento que juega con blancas */ 10 bool bot[2]; /* por color */ 11 int level; 12 long long turn_start; 13 long long next_bot; 14 SrvEmit emit; 15 void *ctx; 16 } ChessSt; 17 18 /* un solo motor para todas las partidas del servidor (se usa de a una) */ 19 static TTEntry tt[1 << 18]; 20 static Engine eng; 21 static bool eng_ready; 22 23 static uint32_t now32(void) { return (uint32_t)srv_now_ms(); } 24 25 static int color_of(const ChessSt *c, int seat) { return seat == c->wseat ? WHITE : BLACK; } 26 27 static void emit(ChessSt *c, const Act *a) 28 { 29 char line[64]; 30 act_fmt(a, line, sizeof line); 31 c->emit(c->ctx, line, false); /* todo va al log: los bots dependen del tiempo */ 32 } 33 34 static void apply(ChessSt *c, const Act *a, long long now) 35 { 36 int before = c->g.nply; 37 game_apply(&c->g, a); 38 emit(c, a); 39 if (c->g.nply != before) { c->turn_start = now; c->next_bot = now + 300; } 40 } 41 42 /* el bot contesta: tablas solo si viene perdiendo, deshacer siempre */ 43 static void bot_answer(ChessSt *c, long long now) 44 { 45 Game *g = &c->g; 46 if (g->draw_offer && g->result == R_NONE && c->bot[(g->draw_offer - 1) ^ 1]) { 47 int to = (g->draw_offer - 1) ^ 1; 48 int ev = evaluate(&g->pos); 49 if (g->pos.side != to) ev = -ev; 50 Act a = { (uint8_t)(ev < -300 || pos_insufficient(&g->pos) ? ACT_DRAWOK : ACT_DRAWNO), (int8_t)to, 0, 0 }; 51 apply(c, &a, now); 52 } 53 if (g->undo_req && c->bot[(g->undo_req - 1) ^ 1]) { 54 Act a = { ACT_UNDOOK, (int8_t)((g->undo_req - 1) ^ 1), 0, 0 }; 55 apply(c, &a, now); 56 } 57 } 58 59 static int cg_create(const char *args, char *opts, int optsn, char *seats) 60 { 61 int color, base, inc, per, undo, level; 62 char st[8]; 63 if (sscanf(args, "%d %d %d %d %d %d %7s", &color, &base, &inc, &per, &undo, &level, st) != 7) return -1; 64 if (color < 0 || color > 2 || base < 0 || base > 3 * 3600000 || inc < 0 || inc > 60000 || 65 per < 0 || per > 14 * 86400000 || (base && per) || level < 0 || level >= LEVEL_COUNT || strlen(st) != 2 || 66 (st[1] != 'H' && st[1] != 'B')) 67 return -1; 68 snprintf(opts, (size_t)optsn, "%d %d %d %d %d %d", color, base, inc, per, undo != 0, level); 69 memcpy(seats, st, 3); 70 return 2; 71 } 72 73 static bool cg_start(void *vst, const char *opts, uint64_t seed, char *kinds, char (*names)[20], SrvEmit em, void *ctx) 74 { 75 ChessSt *c = vst; 76 int color, base, inc, per, undo, level; 77 if (sscanf(opts, "%d %d %d %d %d %d", &color, &base, &inc, &per, &undo, &level) != 6) return false; 78 memset(c, 0, sizeof *c); 79 c->emit = em; 80 c->ctx = ctx; 81 c->wseat = color == 2 ? (int)(seed & 1) : color == 0 ? 0 : 1; 82 c->level = level; 83 TimeCtl tc = { base, inc, per }; 84 game_init(&c->g, 0, &tc, undo != 0); 85 for (int seat = 0; seat < 2; seat++) { 86 c->bot[color_of(c, seat)] = kinds[seat] == 'B'; 87 if (kinds[seat] == 'B') snprintf(names[seat], 20, "Bot (%d)", level + 1); 88 } 89 c->turn_start = srv_now_ms(); 90 c->next_bot = c->turn_start + 300; 91 if (!eng_ready) { engine_init(&eng, tt, 1 << 18, now32); eng_ready = true; } 92 return true; 93 } 94 95 static int cg_submit(void *vst, int seat, const char *line) 96 { 97 ChessSt *c = vst; 98 Act a; 99 if (!act_parse(line, &a) || a.type == ACT_FLAG) return E_ARGS; 100 if (a.c != color_of(c, seat)) return E_TURN; 101 if (a.type == ACT_MOVE && a.c != c->g.pos.side) return c->g.result != R_NONE ? E_OVER : E_TURN; 102 if (!act_resolve(&c->g, &a)) return E_ILLEGAL; 103 long long now = srv_now_ms(); 104 if (a.type == ACT_MOVE) a.ms = (int32_t)(now - c->turn_start); 105 int e = game_check(&c->g, &a); 106 if (e) return e; 107 apply(c, &a, now); 108 bot_answer(c, now); 109 return 0; 110 } 111 112 static int cg_replay(void *vst, const char *line) 113 { 114 ChessSt *c = vst; 115 Act a; 116 if (!act_parse(line, &a)) return 0; 117 if (!act_resolve(&c->g, &a)) return E_ILLEGAL; 118 int e = game_check(&c->g, &a); 119 if (e) return e; 120 apply(c, &a, srv_now_ms()); 121 return 0; 122 } 123 124 static void cg_redact(const char *line, int viewer, char *out, int n) 125 { 126 (void)viewer; /* en el ajedrez no hay nada oculto */ 127 snprintf(out, (size_t)n, "%s", line); 128 } 129 130 static void cg_header(const void *vst, char *out, int n) 131 { 132 const ChessSt *c = vst; 133 char h[160]; 134 game_hdr_fmt(&c->g, h, sizeof h); 135 snprintf(out, (size_t)n, "S %d %lld %s", c->wseat, srv_now_ms() - c->turn_start, h); 136 } 137 138 static void cg_status(const void *vst, int seat, char *out, int n) 139 { 140 const ChessSt *c = vst; 141 const Game *g = &c->g; 142 int me = color_of(c, seat); 143 bool needs = g->result == R_NONE && 144 (g->pos.side == me || (g->draw_offer && g->draw_offer - 1 != me) || (g->undo_req && g->undo_req - 1 != me)); 145 snprintf(out, (size_t)n, "%d %d %d %d", g->nply, needs ? 1 : 0, g->result, me); 146 } 147 148 static bool cg_is_over(const void *vst) { return ((const ChessSt *)vst)->g.result != R_NONE; } 149 150 static long long cg_next_wake(const void *vst) 151 { 152 const ChessSt *c = vst; 153 const Game *g = &c->g; 154 if (g->result != R_NONE) return -1; 155 long long t = -1; 156 if (game_has_clock(g)) t = c->turn_start + game_clock(g, g->pos.side) + 1; 157 if (c->bot[g->pos.side] && (t < 0 || c->next_bot < t)) t = c->next_bot; 158 return t; 159 } 160 161 static void cg_wake(void *vst, long long now) 162 { 163 ChessSt *c = vst; 164 Game *g = &c->g; 165 if (g->result != R_NONE) return; 166 if (game_has_clock(g) && game_clock(g, g->pos.side) - (now - c->turn_start) <= 0) { 167 Act a = { ACT_FLAG, (int8_t)g->pos.side, 0, (int32_t)(now - c->turn_start) }; 168 apply(c, &a, now); 169 return; 170 } 171 if (c->bot[g->pos.side] && now >= c->next_bot) { 172 SearchLimits lim; 173 level_limits(c->level, &lim); 174 if (!lim.movetime || lim.movetime > 800) lim.movetime = 800; /* el servidor no se puede colgar pensando */ 175 if (game_has_clock(g)) { 176 int32_t left = game_clock(g, g->pos.side) - (int32_t)(now - c->turn_start); 177 int32_t budget = g->tc.per_move > 0 ? left / 4 : left / 30 + g->tc.inc / 2; 178 if (budget < 50) budget = 50; 179 if (lim.movetime > budget) lim.movetime = budget; 180 } 181 SearchResult r; 182 engine_search(&eng, g, &lim, &r); 183 long long t2 = srv_now_ms(); 184 Act a = { ACT_MOVE, (int8_t)g->pos.side, r.best, (int32_t)(t2 - c->turn_start) }; 185 if (r.best && game_check(g, &a) == E_OK) apply(c, &a, t2); 186 else c->next_bot = t2 + 1000; 187 } 188 } 189 190 static void cg_summary(const void *vst, char *out, int n) 191 { 192 const ChessSt *c = vst; 193 const TimeCtl *tc = &c->g.tc; 194 if (tc->per_move) snprintf(out, (size_t)n, "correspondencia, %d dias por jugada%s", tc->per_move / 86400000, c->bot[0] || c->bot[1] ? ", contra bot" : ""); 195 else if (tc->base) snprintf(out, (size_t)n, "%ds+%ds%s", tc->base / 1000, tc->inc / 1000, c->bot[0] || c->bot[1] ? ", contra bot" : ""); 196 else snprintf(out, (size_t)n, "sin reloj%s", c->bot[0] || c->bot[1] ? ", contra bot" : ""); 197 } 198 199 const GameModule chess_module = { 200 .magic = "AJEDREZ-GAME 1", 201 .state_size = sizeof(ChessSt), 202 .err_args = E_ARGS, 203 .err_turn = E_TURN, 204 .create = cg_create, 205 .start = cg_start, 206 .submit = cg_submit, 207 .replay = cg_replay, 208 .redact = cg_redact, 209 .header = cg_header, 210 .status = cg_status, 211 .is_over = cg_is_over, 212 .next_wake = cg_next_wake, 213 .wake = cg_wake, 214 .summary = cg_summary, 215 };