/* algcheck.c * */ /* fics - An internet chess server. Copyright (C) 1993 Richard V. Nash This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ /* Revision history: name email yy/mm/dd Change Richard Nash 93/10/22 Created Markus Uhlin 24/01/04 Reformatted functions Markus Uhlin 24/01/06 Switched to usage of strlcpy, strlcat and snprintf. Also added truncation checks. */ #include "stdinclude.h" #include "common.h" #include "algcheck.h" #include "board.h" #include "movecheck.h" #include "utils.h" #if __linux__ #include #endif /* Well, lets see if I can list the possibilities * Piece moves * Ne4 * Nxe4 * Nce4 * Ncxe4 * R2f3 * R2xf3 * Special pawn moves * e4 * ed * exd * exd5 * ed5 * Drop moves (bughouse, board edit) * P@f7 P*f7 * #f7 #Nf7 * (o-o, o-o-o) Castling is handled earlier, so don't worry about that * Of course any of these can have a + or ++ or = string on the end, just * cut that off. */ /* f - file * r - rank * p - piece * x - x * @ - drop character (bughouse) */ char *alg_list[] = { "fxfr", "pxfr", /* These two get confused in case of bishop */ "ffr", "pfr", /* These two get confused in case of bishop */ "pffr", "pfxfr", "prfr", "prxfr", "fr", "ff", "fxf", "p@fr", "#fr", "#pfr", NULL }; PRIVATE int get_move_info(char *str, int *piece, int *ff, int *fr, int *tf, int *tr, int *bishconfusion) { char *s; char c; char tmp[1024] = { '\0' }; int i, j, len; int lpiece, lff, lfr, ltf, ltr; int matchVal = -1; *bishconfusion = 0; strlcpy(tmp, str, sizeof tmp); if ((s = strchr(tmp, '+')) != NULL) { // Cut off any check marks *s = '\0'; } if ((s = strchr(tmp, '=')) != NULL) { // Cut off any promotion marks *s = '\0'; } *piece = *ff = *fr = *tf = *tr = ALG_UNKNOWN; len = strlen(tmp); for (i = 0; alg_list[i]; i++) { lpiece = lff = lfr = ltf = ltr = ALG_UNKNOWN; if (strlen(alg_list[i]) != len) continue; for (j = len - 1; j >= 0; j--) { switch (alg_list[i][j]) { case 'f': if (tmp[j] < 'a' || tmp[j] > 'h') goto nomatch; if (ltf == ALG_UNKNOWN) ltf = (tmp[j] - 'a'); else lff = (tmp[j] - 'a'); break; case 'r': if (tmp[j] < '1' || tmp[j] > '8') goto nomatch; if (ltr == ALG_UNKNOWN) ltr = (tmp[j] - '1'); else lfr = (tmp[j] - '1'); break; case 'p': if (isupper(tmp[j])) c = tolower(tmp[j]); else c = tmp[j]; if (c == 'k') lpiece = KING; else if (c == 'q') lpiece = QUEEN; else if (c == 'r') lpiece = ROOK; else if (c == 'b') lpiece = BISHOP; else if (c == 'n') lpiece = KNIGHT; else if (c == 'p') lpiece = PAWN; else goto nomatch; break; case 'x': if (tmp[j] != 'x' && tmp[j] != 'X') goto nomatch; break; case '@': if (tmp[j] != '@' && tmp[j] != '*') goto nomatch; lff = lfr = ALG_DROP; break; case '#': if (tmp[j] != '#') goto nomatch; lff = lfr = ALG_DROP; break; default: fprintf(stderr, "Unknown character in " "algebraic parsing\n"); break; } } if (lpiece == ALG_UNKNOWN) lpiece = PAWN; if (lpiece == PAWN && lfr == ALG_UNKNOWN) { // ffr or ff if (lff != ALG_UNKNOWN) { if (lff == ltf) goto nomatch; if ((lff - ltf) != 1 && (ltf - lff) != 1) goto nomatch; } } *piece = lpiece; // We have a match *tf = ltf; *tr = ltr; *ff = lff; *fr = lfr; if (matchVal != -1) { /* * We have two matches, it must be that Bxc4 * vs. bxc4 problem. Or it could be the Bc4 vs * bc4 problem. */ *bishconfusion = 1; } matchVal = i; nomatch:; } if (matchVal != -1) return MS_ALG; else return MS_NOTMOVE; } PUBLIC int alg_is_move(char *mstr) { int piece, ff, fr, tf, tr, bc; return get_move_info(mstr, &piece, &ff, &fr, &tf, &tr, &bc); } /* * We already know it is algebraic. Get the move squares. */ PUBLIC int alg_parse_move(char *mstr, game_state_t *gs, move_t *mt) { int f, r, tmpr, posf, posr, posr2; int piece, ff, fr, tf, tr, bc; if (get_move_info(mstr, &piece, &ff, &fr, &tf, &tr, &bc) != MS_ALG) { fprintf(stderr, "FICS: Shouldn't try to algebraically parse " "non-algebraic move string.\n"); return MOVE_ILLEGAL; } if (tf == ALG_UNKNOWN) return MOVE_AMBIGUOUS; /* Must always know to file */ if (tr == ALG_UNKNOWN) { posr = posr2 = ALG_UNKNOWN; if (piece != PAWN) return MOVE_AMBIGUOUS; if (ff == ALG_UNKNOWN) return MOVE_AMBIGUOUS; /* * Need to find pawn on ff that can take to tf and * fill in ranks. */ for (InitPieceLoop(gs->board, &f, &r, gs->onMove); NextPieceLoop(gs->board, &f, &r, gs->onMove);) { if (ff != ALG_UNKNOWN && ff != f) continue; if (piecetype(gs->board[f][r]) != piece) continue; if (gs->onMove == WHITE) { tmpr = r + 1; } else { tmpr = r - 1; } if (gs->board[tf][tmpr] == NOPIECE) { const int is_black = (gs->onMove == WHITE ? 0 : 1); if (gs->ep_possible[is_black][ff] != (tf - ff)) continue; } else { if (iscolor(gs->board[tf][tmpr], gs->onMove)) continue; } if (legal_andcheck_move(gs, f, r, tf, tmpr)) { if (posr != ALG_UNKNOWN && posr2 != ALG_UNKNOWN) return MOVE_AMBIGUOUS; posr = tmpr; posr2 = r; } } tr = posr; fr = posr2; } else if (bc) { /* Could be bxc4 or Bxc4, tr is known. */ ff = ALG_UNKNOWN; fr = ALG_UNKNOWN; for (InitPieceLoop(gs->board, &f, &r, gs->onMove); NextPieceLoop(gs->board, &f, &r, gs->onMove);) { if (piecetype(gs->board[f][r]) != PAWN && piecetype(gs->board[f][r]) != BISHOP) continue; if (legal_andcheck_move(gs, f, r, tf, tr)) { if (piecetype(gs->board[f][r]) == PAWN && f != 1) continue; if (ff != ALG_UNKNOWN && fr != ALG_UNKNOWN) return (MOVE_AMBIGUOUS); ff = f; fr = r; } } } else { /* The from position is unknown */ posf = ALG_UNKNOWN; posr = ALG_UNKNOWN; if (ff == ALG_UNKNOWN || fr == ALG_UNKNOWN) { /* * Need to find a piece that can go to tf, tr. */ for (InitPieceLoop(gs->board, &f, &r, gs->onMove); NextPieceLoop(gs->board, &f, &r, gs->onMove);) { if (ff != ALG_UNKNOWN && ff != f) continue; if (fr != ALG_UNKNOWN && fr != r) continue; if (piecetype(gs->board[f][r]) != piece) continue; if (legal_andcheck_move(gs, f, r, tf, tr)) { if (posf != ALG_UNKNOWN && posr != ALG_UNKNOWN) return MOVE_AMBIGUOUS; posf = f; posr = r; } } } else if (ff == ALG_DROP) { if (legal_andcheck_move(gs, ALG_DROP, piece, tf, tr)) { posf = ALG_DROP; posr = piece; } } ff = posf; fr = posr; } if (tf == ALG_UNKNOWN || tr == ALG_UNKNOWN || ff == ALG_UNKNOWN || fr == ALG_UNKNOWN) return MOVE_ILLEGAL; mt->fromFile = ff; mt->fromRank = fr; mt->toFile = tf; mt->toRank = tr; return MOVE_OK; } PRIVATE void not_pawn(game_state_t *gs, move_t *mt, game_state_t *fakeMove, const int piece, char *tmp, char *mStr, size_t tmp_size, size_t mStr_size) { int ambig, r_ambig, f_ambig; ambig = r_ambig = f_ambig = 0; for (int r = 0; r < 8; r++) { for (int f = 0; f < 8; f++) { if (gs->board[f][r] != NOPIECE && iscolor(gs->board[f][r], gs->onMove) && piecetype(gs->board[f][r]) == piece && (f != mt->fromFile || r != mt->fromRank)) { if (legal_move(gs, f, r, mt->toFile, mt->toRank)) { fakeMove = gs; fakeMove->board[f][r] = NOPIECE; fakeMove->onMove = CToggle(fakeMove->onMove); gs->onMove = CToggle(gs->onMove); /* * New bracketing below to try * to fix 'ambiguous move' * bug. */ if (in_check(gs) || !in_check(fakeMove)) ambig++; if (f == mt->fromFile) { f_ambig++; ambig++; } if (r == mt->fromRank) { r_ambig++; ambig++; } gs->onMove = CToggle(gs->onMove); } } } /* for */ } /* for */ if (ambig > 0) { /* * Ambiguity in short notation. Need to add file, rank * or _both_ in notation. */ if (f_ambig == 0) { snprintf(tmp, tmp_size, "%c", (mt->fromFile + 'a')); strlcat(mStr, tmp, mStr_size); } else if (r_ambig == 0) { snprintf(tmp, tmp_size, "%d", (mt->fromRank + 1)); strlcat(mStr, tmp, mStr_size); } else { snprintf(tmp, tmp_size, "%c%d", (mt->fromFile + 'a'), (mt->fromRank + 1)); strlcat(mStr, tmp, mStr_size); } } } /* * Soso: rewrote alg_unparse function. Algebraic deparser - sets the * 'mStr' variable with move description in short notation. Used in * last move report and in 'moves' command. */ PUBLIC char * alg_unparse(game_state_t *gs, move_t *mt) { char tmp[20] = { '\0' }; game_state_t fakeMove; int piece; size_t ret = 0; static char mStr[20] = { '\0' }; if (mt->fromFile == ALG_DROP) { piece = mt->fromRank; } else { piece = piecetype(gs->board[mt->fromFile][mt->fromRank]); } if (piece == KING && (mt->fromFile == 4 && mt->toFile == 6)) { strlcpy(mStr, "O-O", sizeof mStr); goto check; } if (piece == KING && (mt->fromFile == 4 && mt->toFile == 2)) { strlcpy(mStr, "O-O-O", sizeof mStr); goto check; } mStr[0] = '\0'; switch (piece) { case PAWN: if (mt->fromFile == ALG_DROP) { strlcpy(mStr, "P", sizeof mStr); } else if (mt->fromFile != mt->toFile) { snprintf(tmp, sizeof tmp, "%c", (mt->fromFile + 'a')); strlcpy(mStr, tmp, sizeof mStr); } break; case KNIGHT: strlcpy(mStr, "N", sizeof mStr); break; case BISHOP: strlcpy(mStr, "B", sizeof mStr); break; case ROOK: strlcpy(mStr, "R", sizeof mStr); break; case QUEEN: strlcpy(mStr, "Q", sizeof mStr); break; case KING: strlcpy(mStr, "K", sizeof mStr); break; default: strlcpy(mStr, "", sizeof mStr); break; } /* switch */ if (mt->fromFile == ALG_DROP) { strlcat(mStr, DROP_STR, sizeof mStr); } else { /* * Checks for ambiguity in short notation (Ncb3, R8e8 * or so.) */ if (piece != PAWN) { not_pawn(gs, mt, &fakeMove, piece, &tmp[0], &mStr[0], sizeof tmp, sizeof mStr); } /* not pawn */ if (gs->board[mt->toFile][mt->toRank] != NOPIECE || (piece == PAWN && mt->fromFile != mt->toFile)) strlcat(mStr, "x", sizeof mStr); } snprintf(tmp, sizeof tmp, "%c%d", (mt->toFile + 'a'), (mt->toRank + 1)); ret = strlcat(mStr, tmp, sizeof mStr); if (ret >= sizeof mStr) { fprintf(stderr, "FICS: %s (line %d): warning: " "strlcat() truncated\n", __func__, __LINE__); } if (piece == PAWN && mt->piecePromotionTo != NOPIECE) { strlcat(mStr, "=", sizeof mStr); /* = before promoting piece */ switch (piecetype(mt->piecePromotionTo)) { case KNIGHT: strlcat(mStr, "N", sizeof mStr); break; case BISHOP: strlcat(mStr, "B", sizeof mStr); break; case ROOK: strlcat(mStr, "R", sizeof mStr); break; case QUEEN: strlcat(mStr, "Q", sizeof mStr); break; default: break; } } check:; fakeMove = *gs; execute_move(&fakeMove, mt, 0); fakeMove.onMove = CToggle(fakeMove.onMove); if (in_check(&fakeMove)) { ret = strlcat(mStr, "+", sizeof mStr); if (ret >= sizeof mStr) { fprintf(stderr, "FICS: %s (line %d): warning: " "strlcat() truncated\n", __func__, __LINE__); } } return mStr; }