/* * play.c - Lite Blue's web frontend: a stateless, click-only CGI (C89). * * The whole game is in the URL and is replayed from the start position on * every request; nothing is stored on the server. The only input accepted is * a list of legal moves, a selected square, a colour and a format, all from * small fixed alphabets. Anything else gets a fixed 400 page and is never * echoed. The engine (DeepSeek's Lite Blue core) is linked in: no shell, no * pipes, no exec. * * play.cgi?g=[&s=[&t=]][&c=b][&v=pgn|gif|2d] * * g two characters per ply from ALPHA below (from, to as 0-63, a1 = 0), * plus one of q r b n after a promotion. Whether a third character * belongs to the ply is decided by the replayed position, not the text. * s selected square (first click); t promotion target awaiting a piece * c b if the visitor plays Black (default White) * v pgn: the game as PGN text; gif: the 3D board picture; 2d: the page * with the flat board instead of the 3D one * * When it is the engine's move, play.cgi searches a fixed number of nodes * (deterministic: a fresh process has an empty hash table and the clock is * not consulted) and answers with a 302 to the display URL, so a reload never * searches again. One search runs at a time (mkdir lock in /tmp). * * Claude Opus 5.5, October 2026. Engine: DeepSeek (deepseek-v4.1-flash). */ #include #include #include #include #include #include #include #include #include #include "board.h" #include "movegen.h" #include "search.h" #include "version.h" #include "board3d.h" #define MAX_GAME 300 /* plies */ #define MAX_QUERY 1024 #ifndef NODE_BUDGET #define NODE_BUDGET 40000 #endif #ifndef LOCK_DIR #define LOCK_DIR "/var/liteblue/lock" /* /var/liteblue is www-only */ #endif #define LOCK_STALE 30 /* seconds */ #define PIC_SLOTS 3 /* board pictures composed at once */ #define CGI_ALARM 20 /* seconds: backstop for a runaway request */ #define TILE 48 #ifndef ATLAS #define ATLAS ".htdata/board.lba" /* relative to the CGI's directory */ #endif static const char ALPHA[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._"; static const char PROMO_CH[] = " nbrq"; /* index = piece type 2..5 */ typedef struct { Position pos; Move mv[MAX_GAME]; char san[MAX_GAME][10]; U64 key[MAX_GAME + 1]; int n; } Game; /* ------------------------------------------------------------------ */ /* Output: everything is assembled in one buffer and written at the end. */ static char out[65536]; static int outn; static int out_overflow; static int locked; static int flat; /* flat board (v=2d) */ static void put(const char *s) { int l = (int) strlen(s); if (outn + l >= (int) sizeof(out)) { out_overflow = 1; return; } memcpy(out + outn, s, (size_t) l); outn += l; out[outn] = '\0'; } static void puti(long v) { char b[24]; sprintf(b, "%ld", v); put(b); } static char held[64]; static void lock_release(void) { if (locked) { rmdir(held); locked = 0; } } /* Page frame for Netscape 4.75: colours on BODY and table cells (its CSS * backgrounds leave gaps), the font set on each element type (it doesn't * inherit into tables), sizes it has bitmap fonts for (10, 12, 14, 18 pt). * The page colour #101828 is the board picture's background, exactly. */ /* (split: C89 only promises 509-character string literals) */ static const char *PAGE_TOP[] = { "Lite Blue\n" "\n" "\n" "\n", "\n", NULL }; #define REPO "https://github.com/nfinit/liteblue" static const char *PAGE_END[] = { "\n
\"Lite

Lite Blue " LITEBLUE_VERSION " is a chess engine" " by DeepSeek (deepseek-v4.1-flash), running on obsidian, an IBM RS/6000 43P with one" " 250 MHz PowerPC 604e. Its source is on GitHub (MIT licence).", " The board and pieces were rendered once on the machine's own graphics card, a" " Matrox-based GXT120P, and each picture is put together for your move." " Page by Claude Opus 5.5.

\n\n", NULL }; /* A status bar: one row of the layout table holding a coloured box. */ static const char BAR_TOP[] = "" "
"; static const char BAR_END[] = "

\n"; static void fixed_page(const char *status, const char *title, const char *msg) { const char **t; printf("Status: %s\r\nContent-Type: text/html\r\n\r\n", status); for (t = PAGE_TOP; *t; t++) fputs(*t, stdout); printf("%s%s%s" "

%s

Start a new game

\n", BAR_TOP, title, BAR_END, msg); for (t = PAGE_END; *t; t++) fputs(*t, stdout); } static void bad_request(void) { lock_release(); fixed_page("400 Bad Request", "That isn't a game I recognise", "The link may have been cut short or edited."); exit(0); } static void on_alarm(int sig) { (void) sig; if (locked) rmdir(held); /* Nothing has been written yet: output is only flushed at the end. */ fixed_page("503 Service Unavailable", "Lite Blue took too long", "Please try again in a moment."); _exit(0); } /* ------------------------------------------------------------------ */ /* Squares */ static int sq88(int s64) { return (s64 >> 3) * 16 + (s64 & 7); } static int sq64(int s88) { return (s88 >> 4) * 8 + (s88 & 7); } static int alpha_index(int c) { const char *p; if (c == '\0') return -1; p = strchr(ALPHA, c); return p ? (int)(p - ALPHA) : -1; } static int parse_sq(const char *s) /* "e2" -> 0-63, else -1 */ { if (strlen(s) != 2 || s[0] < 'a' || s[0] > 'h' || s[1] < '1' || s[1] > '8') return -1; return (s[1] - '1') * 8 + (s[0] - 'a'); } static void sq_text(int s64, char *b) { b[0] = (char)('a' + (s64 & 7)); b[1] = (char)('1' + (s64 >> 3)); b[2] = '\0'; } /* ------------------------------------------------------------------ */ /* Rules around the engine */ static int in_check(Position *p) { return sq_attacked(p, p->king_sq[p->side], p->side ^ 1); } /* Standard algebraic notation for m in p (p is restored). */ static void make_san(Position *p, Move m, char *b) { MoveList ml, reply; Undo u; int from = M_FROM(m), to = M_TO(m), pt = PC_TYPE(p->board[from]); int i, ambig = 0, samefile = 0, samerank = 0; char *q = b; if (M_FLAGS(m) & FLAG_CASTLE) { strcpy(b, (to & 7) > (from & 7) ? "O-O" : "O-O-O"); q = b + strlen(b); } else { if (pt != T_PAWN) { *q++ = " NBRQK"[pt]; gen_legal(p, &ml); for (i = 0; i < ml.n; i++) { int f2 = M_FROM(ml.m[i]); if (f2 != from && M_TO(ml.m[i]) == to && PC_TYPE(p->board[f2]) == pt) { ambig = 1; if ((f2 & 7) == (from & 7)) samefile = 1; if ((f2 >> 4) == (from >> 4)) samerank = 1; } } if (ambig) { if (!samefile) *q++ = (char)('a' + (from & 7)); else if (!samerank) *q++ = (char)('1' + (from >> 4)); else { *q++ = (char)('a' + (from & 7)); *q++ = (char)('1' + (from >> 4)); } } } if (M_FLAGS(m) & (FLAG_CAPTURE | FLAG_EP)) { if (pt == T_PAWN) *q++ = (char)('a' + (from & 7)); *q++ = 'x'; } *q++ = (char)('a' + (to & 7)); *q++ = (char)('1' + (to >> 4)); if (M_PROMO(m)) { *q++ = '='; *q++ = " NBRQ"[M_PROMO(m)]; } } make_move(p, m, &u); if (in_check(p)) { gen_legal(p, &reply); *q++ = reply.n ? '+' : '#'; } unmake_move(p, m, &u); *q = '\0'; } static void game_push(Game *g, Move m) { Undo u; make_san(&g->pos, m, g->san[g->n]); make_move(&g->pos, m, &u); g->mv[g->n++] = m; g->key[g->n] = g->pos.key; } /* Dead positions: bare kings, a single minor piece, or bishops only, all on * squares of one colour (either side). */ static int insufficient(const Position *p) { int sq, minors = 0, knights = 0, bcolour[2]; bcolour[0] = bcolour[1] = 0; for (sq = 0; sq < 128; sq++) { int t; if (sq & 0x88) continue; t = PC_TYPE(p->board[sq]); if (p->board[sq] == EMPTY || t == T_KING) continue; if (t == T_KNIGHT) knights++; else if (t == T_BISHOP) bcolour[((sq >> 4) + (sq & 7)) & 1]++; else return 0; minors++; } if (minors <= 1) return 1; return knights == 0 && (bcolour[0] == 0 || bcolour[1] == 0); } /* * 0 = game on; otherwise the game is over and *why / *result say how. * Draws by repetition and the fifty-move rule are applied automatically. */ static int game_over(Game *g, const char **why, const char **result) { MoveList ml; int i, reps = 0; gen_legal(&g->pos, &ml); if (ml.n == 0) { if (in_check(&g->pos)) { *why = "checkmate"; *result = g->pos.side == WHITE ? "0-1" : "1-0"; } else { *why = "stalemate"; *result = "1/2-1/2"; } return 1; } for (i = 0; i <= g->n; i++) if (g->key[i] == g->key[g->n]) reps++; *result = "1/2-1/2"; if (reps >= 3) { *why = "threefold repetition"; return 1; } if (g->pos.halfmove >= 100) { *why = "the fifty-move rule"; return 1; } if (insufficient(&g->pos)) { *why = "insufficient material"; return 1; } if (g->n >= MAX_GAME) { *why = "the length limit of this page"; return 1; } *result = "*"; return 0; } /* Replay the g parameter. Returns 0 on success, -1 on any illegal input. */ static int replay(Game *g, const char *s) { const char *why, *res; pos_start(&g->pos); g->n = 0; g->key[0] = g->pos.key; while (*s) { MoveList ml; int f, t, i, nmatch = 0; Move m = 0; if (g->n >= MAX_GAME || game_over(g, &why, &res)) return -1; f = alpha_index(s[0]); t = alpha_index(s[1]); /* s[1] may be the terminating NUL */ if (f < 0 || t < 0) return -1; s += 2; gen_legal(&g->pos, &ml); for (i = 0; i < ml.n; i++) if (M_FROM(ml.m[i]) == sq88(f) && M_TO(ml.m[i]) == sq88(t)) { nmatch++; m = ml.m[i]; } if (nmatch == 0) return -1; if (M_PROMO(m)) { /* promotions: a piece letter follows */ const char *pc = *s ? strchr(PROMO_CH + 2, *s) : NULL; if (pc == NULL) return -1; m = 0; for (i = 0; i < ml.n; i++) if (M_FROM(ml.m[i]) == sq88(f) && M_TO(ml.m[i]) == sq88(t) && M_PROMO(ml.m[i]) == (int)(pc - PROMO_CH)) m = ml.m[i]; if (m == 0) return -1; s++; } game_push(g, m); } return 0; } /* ------------------------------------------------------------------ */ /* URLs: always rebuilt from the replayed game, never copied from input. */ static void enc_game(const Game *g, int nplies, char *b) { int i; for (i = 0; i < nplies; i++) { Move m = g->mv[i]; *b++ = ALPHA[sq64(M_FROM(m))]; *b++ = ALPHA[sq64(M_TO(m))]; if (M_PROMO(m)) *b++ = PROMO_CH[M_PROMO(m)]; } *b = '\0'; } /* Append play.cgi?... for the first nplies of g, then extra (may be ""). */ static void put_url(const Game *g, int nplies, int black, const char *extra) { static char gs[MAX_GAME * 3 + 1]; enc_game(g, nplies, gs); put("play.cgi?g="); put(gs); if (black) put("&c=b"); if (flat) put("&v=2d"); put(extra); } /* ------------------------------------------------------------------ */ /* The engine's move */ /* mkdir is atomic: whoever creates the directory holds the lock. One left by * a crashed request (older than LOCK_STALE) is removed; LOCK_DIR's parent is * writable only by www, so nobody else can create or squat one. */ static int lock_name(const char *name) { struct stat st; int tries; for (tries = 0; tries < 2; tries++) { if (mkdir(name, 0700) == 0) { strcpy(held, name); locked = 1; return 1; } if (errno != EEXIST) return 0; if (stat(name, &st) == 0 && time(NULL) - st.st_mtime > LOCK_STALE) rmdir(name); else return 0; } return 0; } static int lock_take(void) { return lock_name(LOCK_DIR); } /* one of PIC_SLOTS slots for composing a picture: LOCK_DIR0, LOCK_DIR1, ... */ static int pic_slot(void) { char name[64]; int i; for (i = 0; i < PIC_SLOTS; i++) { sprintf(name, "%s%d", LOCK_DIR, i); if (lock_name(name)) return 1; } return 0; } static Move engine_move(Game *g) { SearchLimit lim; SearchResult res; MoveList ml; Move m; search_limit_default(&lim); /* nothing set; then only a node budget */ lim.nodes = NODE_BUDGET; lim.silent = 1; lim.wtime_ms = lim.btime_ms = -1; /* explicit, whatever the default */ lim.winc_ms = lim.binc_ms = -1; search_init(); search_set_history(g->key, g->n + 1); m = search_go(&g->pos, &lim, &res); if (m == 0) { /* no iteration finished: any legal move */ gen_legal(&g->pos, &ml); m = ml.m[0]; } return m; } /* The path for Location (SCRIPT_NAME, validated). Never the client's Host * header: behind a caching proxy, a redirect built from it could be cached * for everyone. With an explicit "Status: 302", Apache 1.3 passes a * path-only Location to the browser instead of redirecting internally. */ static int base_url(char *b, int size) { const char *script = getenv("SCRIPT_NAME"); const char *p; if (script == NULL || strlen(script) > 200 || script[0] != '/') return -1; for (p = script; *p; p++) if (!strchr("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" "0123456789./_~-", *p)) return -1; if ((int) strlen(script) >= size) return -1; strcpy(b, script); return 0; } /* ------------------------------------------------------------------ */ /* Pages */ static void put_pgn(Game *g, int black, const char *result) { int i, col = 0; char num[16]; put("[Event \"Lite Blue on obsidian\"]\n[Site \"obsidian, IBM RS/6000 43P-150\"]\n"); put("[White \""); put(black ? "Lite Blue" : "Visitor"); put("\"]\n"); put("[Black \""); put(black ? "Visitor" : "Lite Blue"); put("\"]\n"); put("[Result \""); put(result); put("\"]\n\n"); for (i = 0; i < g->n; i++) { int l = 0; num[0] = '\0'; if ((i & 1) == 0) sprintf(num, "%d. ", i / 2 + 1); l = (int)(strlen(num) + strlen(g->san[i]) + 1); if (col + l > 72) { put("\n"); col = 0; } put(num); put(g->san[i]); put(" "); col += l; } put(result); put("\n"); } /* Tile image for a square: t/

.gif */ static void put_tile(const Position *p, int s64, char state) { char name[16]; int pc = p->board[sq88(s64)]; int light = ((s64 >> 3) + (s64 & 7)) & 1; name[0] = light ? 'l' : 'd'; if (pc == EMPTY) { name[1] = 'e'; name[2] = 'e'; } else { name[1] = PC_COLOR(pc) == WHITE ? 'w' : 'b'; name[2] = " pnbrqk"[PC_TYPE(pc)]; } name[3] = state; name[4] = '\0'; put("\"\""); } /* What each square shows and what clicking it does. */ typedef struct { char state; /* n m s t, as in the tile names */ int link; /* 0 none, 1 link to the game plus extra, 2 the game plus mv */ Move mv; char extra[32]; } SqLink; static void square_links(Game *g, int black, int sel, int promo_to, int over, SqLink *L) { MoveList ml; int visitor = black ? BLACK : WHITE; int yours = !over && g->pos.side == visitor; int s, i, lf = -1, lt = -1; char a[3], b[3]; if (g->n > 0) { lf = sq64(M_FROM(g->mv[g->n - 1])); lt = sq64(M_TO(g->mv[g->n - 1])); } gen_legal(&g->pos, &ml); for (s = 0; s < 64; s++) { SqLink *q = &L[s]; int pc = g->pos.board[sq88(s)]; q->state = (s == lf || s == lt) ? 'm' : 'n'; q->link = 0; q->mv = 0; q->extra[0] = '\0'; if (!yours) continue; if (promo_to >= 0) { /* choosing a piece: no board links */ if (s == sel) q->state = 's'; if (s == promo_to) q->state = 't'; continue; } if (sel >= 0 && s == sel) { q->state = 's'; q->link = 1; /* click again to put it back */ continue; } if (sel >= 0) { for (i = 0; i < ml.n; i++) if (sq64(M_FROM(ml.m[i])) == sel && sq64(M_TO(ml.m[i])) == s) break; if (i < ml.n) { q->state = 't'; if (M_PROMO(ml.m[i])) { sq_text(sel, a); sq_text(s, b); sprintf(q->extra, "&s=%s&t=%s", a, b); q->link = 1; } else { q->link = 2; q->mv = ml.m[i]; } continue; } } if (pc != EMPTY && PC_COLOR(pc) == visitor) { for (i = 0; i < ml.n; i++) if (sq64(M_FROM(ml.m[i])) == s) break; if (i < ml.n) { /* a piece that can move: select it */ sq_text(s, a); sprintf(q->extra, "&s=%s", a); q->link = 1; } } } } static void put_link(Game *g, int black, const SqLink *q) { if (q->link == 2) { g->mv[g->n] = q->mv; /* scratch slot: the game plus this move */ put_url(g, g->n + 1, black, ""); } else put_url(g, g->n, black, q->extra); } static void put_coords(const int *xy, int n) { int i; for (i = 0; i < 2 * n; i++) { if (i) put(","); puti(xy[i]); } } static void board_flat(Game *g, int black, const SqLink *L) { int row, col; put("\n"); for (row = 0; row < 8; row++) { int rank = black ? row : 7 - row; put(""); for (col = 0; col < 8; col++) { int s = rank * 8 + (black ? 7 - col : col); put(""); } put("\n"); } put(""); for (col = 0; col < 8; col++) { char f[2]; f[0] = (char)('a' + (black ? 7 - col : col)); f[1] = '\0'; put(""); } put("\n
"); puti(rank + 1); put(""); if (L[s].link) { put(""); } put_tile(&g->pos, s, L[s].state); if (L[s].link) put(""); put("
"); put(f); put("
\n"); } /* The 3D picture and its image map. Netscape takes the first AREA that * contains the click, so shapes go nearest first: each piece's outline (it * hides the squares behind it), then its square. While a piece is selected, * a piece that can't be clicked gets a NOHREF outline, so a stray click on it * can't land on a square behind it and make a move; with nothing selected it * gets none, so a click on it reaches the piece behind (the queen and king * stand in front of the d- and e-pawns at the start). */ static void board_3d(Game *g, int black, int sel, int promo_to, const SqLink *L) { char extra[48], a[3], b[3]; int r, f, xy[64], n; extra[0] = '\0'; if (sel >= 0) { sq_text(sel, a); sprintf(extra, "&s=%s", a); if (promo_to >= 0) { sq_text(promo_to, b); sprintf(extra + strlen(extra), "&t=%s", b); } } strcat(extra, "&v=gif"); put("n, black, extra); put("\" WIDTH="); puti(b3_width()); put(" HEIGHT="); puti(b3_height()); put(" BORDER=0 USEMAP=\"#board\" ALT=\"\">\n\n"); for (r = 0; r < 8; r++) for (f = 0; f < 8; f++) { int pos = r * 8 + f, s = black ? 63 - pos : pos, pc = g->pos.board[sq88(s)]; if (pc != EMPTY && (L[s].link || sel >= 0) && (n = b3_outline(PC_COLOR(pc), PC_TYPE(pc), pos, xy)) > 0) { put("\n"); } else put("\" NOHREF>\n"); } if (L[s].link) { b3_square(pos, xy); put("\n"); } } put("\n"); } static void put_side_link(Game *g, int nplies, int black, const char *extra, const char *text) { put(""); put(text); put("
\n"); } static void display(Game *g, int black, int sel, int promo_to, int over, const char *why, const char *result, const SqLink *L) { MoveList ml; int visitor = black ? BLACK : WHITE; int i, keep; gen_legal(&g->pos, &ml); { const char **t; for (t = PAGE_TOP; *t; t++) put(*t); } /* status bar */ put(BAR_TOP); if (over) { put("Game over: "); put(why); put(". "); if (strcmp(result, "1/2-1/2") == 0) put("A draw."); else if ((strcmp(result, "1-0") == 0) == (visitor == WHITE)) put("You win."); else put("Lite Blue wins."); put(""); } else if (promo_to >= 0) { static const char *names[] = { "", "", "Knight", "Bishop", "Rook", "Queen" }; int t; put("Promote to:   "); for (t = 5; t >= 2; t--) { for (i = 0; i < ml.n; i++) if (sq64(M_FROM(ml.m[i])) == sel && sq64(M_TO(ml.m[i])) == promo_to && M_PROMO(ml.m[i]) == t) break; if (i == ml.n) continue; g->mv[g->n] = ml.m[i]; /* scratch slot: URL for game + this move */ put("n + 1, black, ""); put("\">"); put(names[t]); put("   "); } put("  n, black, ""); put("\">cancel"); } else if (sel >= 0) { int first = 1, s; char san[10]; put("Where to?   "); for (s = 0; s < 64; s++) { /* the same moves as text links */ if (L[s].state != 't') continue; for (i = 0; i < ml.n; i++) if (sq64(M_FROM(ml.m[i])) == sel && sq64(M_TO(ml.m[i])) == s) break; if (i == ml.n) continue; make_san(&g->pos, ml.m[i], san); if (M_PROMO(ml.m[i])) /* the piece is chosen on the next page */ san[strlen(san) - (san[strlen(san) - 1] == '+' || san[strlen(san) - 1] == '#' ? 3 : 2)] = '\0'; put(first ? "" : "   "); first = 0; put(""); put(san); put(""); } put("   (or click the piece again to put it back)"); } else { if (in_check(&g->pos)) put("Check! "); put("Your move."); if (g->n > 0) { put("   Lite Blue played "); put(g->san[g->n - 1]); put("."); } else put("   Click a piece, then the square it should go to."); } put(BAR_END); /* board | gap | side panel */ put("\n"); if (flat) board_flat(g, black, L); else board_3d(g, black, sel, promo_to, L); put("\n"); put("
\n"); put(""); put(black ? "You (Black) vs Lite Blue" : "You (White) vs Lite Blue"); put("

\n"); if (g->n == 0) put("No moves yet.\n"); else { put("\n"); for (i = 0; i < g->n; i += 2) { char num[16]; sprintf(num, "%d.", i / 2 + 1); put("\n"); } put("
"); put(num); put(""); put(g->san[i]); put(""); if (i + 1 < g->n) put(g->san[i + 1]); put("
\n"); } put("
\n"); if (!over && g->n >= 2 && (g->n - 2 > 0 || !black)) put_side_link(g, g->n - 2, black, "", "Take back a move"); put(flat ? "New game as White
\n" : "New game as White
\n"); put(flat ? "New game as Black
\n" : "New game as Black
\n"); keep = flat; flat = 0; if (g->n > 0) put_side_link(g, g->n, black, "&v=pgn", "This game as PGN"); put_side_link(g, g->n, black, keep ? "" : "&v=2d", keep ? "Show the 3D board" : "Show a flat board"); flat = keep; put("
Engine: Lite Blue " LITEBLUE_VERSION ". Each reply is a search of "); puti(NODE_BUDGET); put(" positions, a little under a second on the 604e.\n"); put("
\n\n"); { const char **t; for (t = PAGE_END; *t; t++) put(*t); } } /* ------------------------------------------------------------------ */ static void get_param(char *qs, char **g, char **s, char **t, char **c, char **v) { char *p = qs, *amp, *eq; if (*p == '\0') return; for (;;) { char **slot; amp = strchr(p, '&'); if (amp) *amp = '\0'; eq = strchr(p, '='); if (eq == NULL || eq - p != 1) bad_request(); switch (p[0]) { case 'g': slot = g; break; case 's': slot = s; break; case 't': slot = t; break; case 'c': slot = c; break; case 'v': slot = v; break; default: bad_request(); return; } if (*slot != NULL) bad_request(); /* each parameter at most once */ *slot = eq + 1; if (amp == NULL) break; p = amp + 1; } } int main(void) { static Game game; static char qs[MAX_QUERY + 1]; const char *method = getenv("REQUEST_METHOD"); const char *q = getenv("QUERY_STRING"); char *gp = NULL, *sp = NULL, *tp = NULL, *cp = NULL, *vp = NULL; const char *why = "", *result = "*"; int black = 0, sel = -1, promo_to = -1, over, head, gif = 0, pgn = 0, have3d; static SqLink links[64]; signal(SIGALRM, on_alarm); alarm(CGI_ALARM); if (method == NULL || (strcmp(method, "GET") != 0 && strcmp(method, "HEAD") != 0)) bad_request(); head = strcmp(method, "HEAD") == 0; q = getenv("PATH_INFO"); /* play.cgi/anything: relative links would break */ if (q != NULL && *q != '\0') bad_request(); q = getenv("QUERY_STRING"); if (q == NULL) q = ""; if (strlen(q) > MAX_QUERY) bad_request(); strcpy(qs, q); get_param(qs, &gp, &sp, &tp, &cp, &vp); if (cp != NULL) { if (strcmp(cp, "b") == 0) black = 1; else if (strcmp(cp, "w") != 0) bad_request(); } if (vp != NULL) { if (strcmp(vp, "pgn") == 0) pgn = 1; else if (strcmp(vp, "gif") == 0) gif = 1; else if (strcmp(vp, "2d") == 0) flat = 1; else bad_request(); } if (sp != NULL && (sel = parse_sq(sp)) < 0) bad_request(); if (tp != NULL && (sp == NULL || (promo_to = parse_sq(tp)) < 0)) bad_request(); board_init(); if (replay(&game, gp ? gp : "") < 0) bad_request(); over = game_over(&game, &why, &result); if (pgn) { put_pgn(&game, black, result); if (out_overflow) bad_request(); printf("Content-Type: text/plain\r\n\r\n"); if (!head) fputs(out, stdout); return 0; } if (!over && game.pos.side != (black ? BLACK : WHITE)) { /* The engine's turn: search once, then send the visitor to the * display URL for the new position. */ char base[400]; static char gs[MAX_GAME * 3 + 1]; if (sp != NULL || tp != NULL || gif) bad_request(); if (base_url(base, sizeof(base)) < 0) bad_request(); if (!lock_take()) { put("Lite Blue is thinking\n" "\n" "\n" "\"Lite

\n" "Lite Blue is thinking about another" " game.
\nThis page will try again" " in a moment.\n"); printf("Status: 503 Service Unavailable\r\nRetry-After: 2\r\n" "Content-Type: text/html\r\n\r\n"); if (!head) fputs(out, stdout); return 0; } game_push(&game, engine_move(&game)); lock_release(); enc_game(&game, game.n, gs); printf("Status: 302 Found\r\nLocation: %s?g=%s%s%s\r\n" "Content-Type: text/html\r\n\r\n", base, gs, black ? "&c=b" : "", flat ? "&v=2d" : ""); if (!head) printf("Continue\n", gs, black ? "&c=b" : "", flat ? "&v=2d" : ""); return 0; } /* A selection must be one of the visitor's movable pieces; a promotion * target must be a promotion square for it. */ if (sel >= 0 || promo_to >= 0) { MoveList ml; int i, ok = 0; if (over) bad_request(); gen_legal(&game.pos, &ml); for (i = 0; i < ml.n; i++) if (sq64(M_FROM(ml.m[i])) == sel && (promo_to < 0 || (sq64(M_TO(ml.m[i])) == promo_to && M_PROMO(ml.m[i])))) ok = 1; if (!ok) bad_request(); } square_links(&game, black, sel, promo_to, over, links); have3d = (gif || !flat) && b3_open(ATLAS) == 0; if (gif) { static int pc[64], pt[64]; static unsigned char tints[64]; int i; if (!have3d) { fixed_page("503 Service Unavailable", "The 3D board is unavailable", "Try the flat board instead."); return 0; } for (i = 0; i < 64; i++) { int p = game.pos.board[sq88(i)]; pc[i] = p == EMPTY ? 0 : PC_COLOR(p); pt[i] = p == EMPTY ? 0 : PC_TYPE(p); tints[i] = links[i].state == 'm' ? 1 : links[i].state == 's' ? 2 : links[i].state == 't' ? 3 : 0; } if (!pic_slot()) { printf("Status: 503 Service Unavailable\r\nRetry-After: 1\r\n" "Content-Type: text/plain\r\n\r\nbusy\n"); return 0; } i = b3_compose(pc, pt, black, tints); lock_release(); if (i < 0) { fixed_page("500 Internal Server Error", "The board picture failed", "Try the flat board instead."); return 0; } /* the picture depends only on the URL: let browsers and proxies keep it */ printf("Content-Type: image/gif\r\nCache-Control: public, max-age=604800\r\n\r\n"); if (!head) b3_write_gif(stdout); return 0; } if (!have3d) flat = 1; display(&game, black, sel, promo_to, over, why, result, links); if (out_overflow) bad_request(); printf("Content-Type: text/html\r\n\r\n"); if (!head) fputs(out, stdout); return 0; }