/* main.c - handles game logic and input */ #include "chess.h" #ifdef CHESS_ALLEGRO #include #endif struct castles { bool whiteQueenSide; bool whiteKingSide; bool blackQueenSide; bool blackKingSide; } castle; square_t board[8][8]; piece_t pieces[PIECE_TOTAL_PIECES]; team_t nextMove; square_t en_passant; int errno; bool logging = true; moveHistory_t *moveHistory = NULL; bool playing; char out[80]; char **names; FILE *inputstream; conclusion_t conclusion = ERROR; team_t team(square_t piece) { if(piece >= PIECE_WHITE_APAWN && piece < PIECE_BLACK_APAWN) return TEAM_WHITE; if(piece >= PIECE_BLACK_APAWN && piece < PIECE_TOTAL_PIECES) return TEAM_BLACK; return 0; } pieceType_t type(square_t piece) { if(pieces[piece].promotion) piece = pieces[piece].promotion; if((piece >= PIECE_WHITE_APAWN && piece <= PIECE_WHITE_HPAWN) || (piece >= PIECE_BLACK_APAWN && piece <= PIECE_BLACK_HPAWN)) return TYPE_PAWN; if(piece == PIECE_WHITE_QROOK || piece == PIECE_WHITE_KROOK || piece == PIECE_BLACK_QROOK || piece == PIECE_BLACK_KROOK) return TYPE_ROOK; if(piece == PIECE_WHITE_QKNIGHT || piece == PIECE_WHITE_KKNIGHT || piece == PIECE_BLACK_QKNIGHT || piece == PIECE_BLACK_KKNIGHT) return TYPE_KNIGHT; if(piece == PIECE_WHITE_QBISHOP || piece == PIECE_WHITE_KBISHOP || piece == PIECE_BLACK_QBISHOP || piece == PIECE_BLACK_KBISHOP) return TYPE_BISHOP; if(piece == PIECE_WHITE_QUEEN || piece == PIECE_BLACK_QUEEN) return TYPE_QUEEN; if(piece == PIECE_WHITE_KING || piece == PIECE_BLACK_KING) return TYPE_KING; return TYPE_NONE; } bool canMove(square_t piece, int rank, int file) { /* ignoring check rules * this is pretty big, so maybe give it its own file */ int i; int srank = pieces[piece].rank; int sfile = pieces[piece].file; if(piece == PIECE_NOTHING || pieces[piece].captured || rank > 7 || rank < 0 || file > 7 || file < 0 || (srank == rank && sfile == file)) return false; if(sameTeam(piece, board[rank][file])) return false; switch(type(piece)) { case TYPE_PAWN: { int dir = 0, start; if(team(piece) == TEAM_WHITE) { dir = -1; start = 6; } if(team(piece) == TEAM_BLACK) { dir = 1; start = 1; } if(file == sfile) { /* non-capture */ if(srank == start && rank == start + dir * 2 && !board[start + dir][file] && !board[start + dir * 2][file]) return true; /* first move */ if(rank == srank + dir && !board[rank][file]) return true; /* normal move */ } else if(abs(file - sfile) == 1 && rank == srank + dir && (board[rank][file] || (en_passant && board[srank][file] == en_passant && !sameTeam(en_passant, piece)))) /* capture! */ return true; else return false; } case TYPE_KNIGHT: if((abs(rank - srank) == 2 && abs(file - sfile) == 1) || (abs(rank - srank) == 1 && abs(file - sfile) == 2)) return true; return false; case TYPE_QUEEN: case TYPE_ROOK: if(file == sfile) { if(rank > srank) { for(i = 1; srank + i < rank; i++) if(board[srank + i][sfile]) return false; return true; } else { for(i = 1; srank - i > rank; i++) if(board[srank - i][sfile]) return false; return true; } } else if(rank == srank) { if(file > sfile) { for(i = 1; sfile + i < file; i++) if(board[srank][sfile + i]) return false; return true; } else { for(i = 1; sfile - i > file; i++) if(board[srank][sfile - i]) return false; return true; } } else if(type(piece) == TYPE_ROOK) return false; else /* shouldn't be any rooks left at this point, use the bishop logic * for queens */ case TYPE_BISHOP: { if(rank - srank != file - sfile && rank - srank != sfile - file && srank - rank != file - sfile && srank - rank != sfile - file) return false; if(rank > srank) { if(file > sfile) { for(i = 1; srank + i < rank && sfile + i < file; i++) if(board[srank + i][sfile + i]) return false; return true; } else if(file < sfile) { for(i = 1; srank + i < rank && sfile - i > file; i++) if(board[srank + i][sfile - i]) return false; return true; } else return false; } else if(rank < srank) { if(file > sfile) { for(i = 1; srank - i > rank && sfile + i < file; i++) if(board[srank - i][sfile + i]) return false; return true; } else if(file < sfile) { for(i = 1; srank - i > rank && sfile - i > file; i++) if(board[srank - i][sfile - i]) return false; return true; } else return false; } } case TYPE_KING: if(rank > srank + 1 || rank < srank - 1 || file > sfile + 1 || file < sfile - 1) return false; default: return true; } } bool canTeamMove(team_t targteam, int rank, int file) { /* canMoves for every piece on given team, used for check logic */ int i, end, srank, sfile; square_t caught = PIECE_NOTHING; if(targteam == TEAM_WHITE) i = PIECE_WHITE_APAWN; else if(targteam == TEAM_BLACK) i = PIECE_BLACK_APAWN; else return false; end = i + 16; for(;i < end; i++) if(canMove(i, rank, file)) { srank = pieces[i].rank; sfile = pieces[i].file; pieces[i].rank = rank; pieces[i].file = file; if((caught = board[rank][file])) pieces[caught].captured = true; /* it's not immediately obvious that this next bit works, but we * know it's a valid move because we've already canMoved, and we * know it's a capture because the starting file and final file * are different on a pawn move, we know the piece being captured * is not on the square we're moving to (else we'd have caught it * above) so the only remaining possibility is an en passant */ else if(type(i) == TYPE_PAWN && file != sfile) pieces[caught = board[srank][file]].captured = true; updateBoard(); if(!check(targteam, NULL)) { pieces[i].rank = srank; pieces[i].file = sfile; /* if caught is still NOTHING, is this ok? */ pieces[caught].captured = false; updateBoard(); return true; } pieces[i].rank = srank; pieces[i].file = sfile; pieces[caught].captured = false; updateBoard(); } return false; } void updateBoard() { /* syncs the board array with the pieces array * this MUST be called after mucking about with the pieces array for * functions like canMove() to work properly */ int i; memset(board, 0, sizeof(board)); for(i = 0; i < PIECE_TOTAL_PIECES; i++) if(!pieces[i].captured) board[pieces[i].rank][pieces[i].file] = i; } int check(team_t targteam, square_t *attackers) { /* attackers is an array at least 16 in size * function fills it with pieces putting the given team's king in check * returns number of attacking pieces unless attackers is NULL, then * just returns whether king can be checked */ int i, end, count = 0, krank, kfile; square_t king; if(targteam == TEAM_WHITE) { king = PIECE_WHITE_KING; i = PIECE_BLACK_APAWN; } else if(targteam == TEAM_BLACK) { king = PIECE_BLACK_KING; i = PIECE_WHITE_APAWN; } else return false; end = i + 16; krank = pieces[king].rank; kfile = pieces[king].file; for(;i < end; i++) { if(canMove(i, krank, kfile)) { if(attackers) attackers[count++] = i; else return true; } } return count; } bool checkMate(team_t targteam) { /* look for ways the targteam can get out of check or return true if none * found * relies heavily on canTeamMove() and check(), if this is slow then * work on their efficiency */ square_t checkers[16], king; int i, num, krank, kfile, drank, dfile, crank, cfile; num = check(targteam, checkers); if(targteam == TEAM_WHITE) king = PIECE_WHITE_KING; else if(targteam == TEAM_BLACK) king = PIECE_BLACK_KING; else return false; krank = pieces[king].rank; kfile = pieces[king].file; if(num == 0) /* not even in check */ return false; /* try to move king out of check */ for(i = 0; i < 9; i++) { drank = krank + (i % 3) - 1; dfile = kfile + (i / 3) - 1; /* they go -1 -1, 0 -1, 1 -1, -1 0, 0 0, 1 0, etc. */ if(canMove(king, drank, dfile)) { pieces[king].rank = drank; pieces[king].file = dfile; updateBoard(); if(!check(targteam, NULL)) { /* no longer in check here */ pieces[king].rank = krank; pieces[king].file = kfile; updateBoard(); return false; } } } /* undo any damage that remains */ pieces[king].rank = krank; pieces[king].file = kfile; updateBoard(); if(num == 1) { /* interpose or capture check, will not do any good if attacked by two * pieces simultaneously */ int j, rdir = 0, fdir = 0; /* direction of king from attacking piece */ pieceType_t ctype; crank = pieces[*checkers].rank; cfile = pieces[*checkers].file; if(canTeamMove(targteam, crank, cfile)) return false; /* can capture the offender */ if((ctype = type(*checkers)) == TYPE_PAWN || ctype == TYPE_KNIGHT) /* only hope with knights or pawns is a capture, just failed */ return true; /* this logic should apply to bishops, rooks, and queens */ if(krank > crank) rdir = 1; if(krank < crank) rdir = -1; if(kfile > cfile) fdir = 1; if(kfile < cfile) fdir = -1; for(i = crank, j = cfile; (!rdir || rdir * (krank - i) > 0) && (!fdir || fdir * (kfile - j) > 0); i += rdir, j += fdir) /* bit of an obfuscated for loop, but bear with me */ if(canTeamMove(targteam, i, j)) return false; } return true; } void newGame(void) { int i; memset(pieces, 0, sizeof(pieces)); for(i = PIECE_WHITE_APAWN; i <= PIECE_WHITE_HPAWN; i++) { pieces[i].rank = 6; pieces[i].file = i - PIECE_WHITE_APAWN; } for(;i <= PIECE_WHITE_KROOK; i++) { pieces[i].rank = 7; pieces[i].file = i - PIECE_WHITE_QROOK; } for(;i <= PIECE_BLACK_HPAWN; i++) { pieces[i].rank = 1; pieces[i].file = i - PIECE_BLACK_APAWN; } for(;i <= PIECE_BLACK_KROOK; i++) { pieces[i].rank = 0; pieces[i].file = i - PIECE_BLACK_QROOK; } nextMove = TEAM_WHITE; moveMessage(); castle.whiteQueenSide = castle.whiteKingSide = castle.blackQueenSide = castle.blackKingSide = true; playing = true; } void moveMessage(void) { /* to prompt the next player to move */ strncpy(out, names[nextMove], sizeof(out)); if(out[strlen(out) - 1] == 's') strncat(out, "' move", sizeof(out)); else strncat(out, "'s move", sizeof(out)); } int move(char *in) { char *dstbuf, *pointer; int i, srank, sfile, drank, dfile; square_t piece, target = PIECE_NOTHING, promotion = PIECE_NOTHING; square_t base = (nextMove == TEAM_WHITE) ? PIECE_WHITE_APAWN : PIECE_BLACK_APAWN; if(!*in) return 1; /* check against the command list */ for(i = 0; i < numCmds; i++) { if(strncmp(in, commands[i].name, strlen(commands[i].name)) == 0) { if(logging && commands[i].flags & CMD_LOGGED) { logCommand(in); #define MOVE_INTERVAL 1.5f * CLOCKS_PER_SEC /* unlogged moves are always CMD_INSTANT */ if(~commands[i].flags & CMD_INSTANT && inputstream != stdin) { /* when playing back from a file, pause between moves */ timer = clock(); while(clock() < timer + MOVE_INTERVAL); } } if(commands[i].flags & CMD_PLAYING && !playing) { strncpy(out, "No game in progress: use \"new\" or \"quit\"", sizeof out); return 1; } if(commands[i].flags & CMD_NOTPLAYING && playing) { strncpy(out, "End this game first: use \"resign\" or \"draw\"", sizeof out); return 1; } #ifndef DEVELOPER if(~commands[i].flags & CMD_DEVELOPER) #endif if(commands[i].handler) return commands[i].handler(in); else return 1; } } if(inputstream != stdin) { timer = clock(); while(clock() < timer + MOVE_INTERVAL); } /* not a UI command, so parse it as an algebraic move */ logCommand(in); memset(out, 0, sizeof(out)); if(!playing) { strncpy(out, "No game in progress: use \"new\" or \"quit\"", sizeof(out)); return 1; } /* castling, should probably be functionalised or moved to canMove */ if(!strncmp(in, "0-0", 3) || !strncmp(in, "O-O", 3) || !strncmp(in, "o-o", 3)) { int dir = 1; square_t king, rook; if(!strncmp(in, "0-0-0", 5) || !strncmp(in, "O-O-O", 5) || !strncmp(in, "o-o-o", 5)) dir = -1; if(nextMove == TEAM_WHITE) { king = PIECE_WHITE_KING; if(dir > 0) rook = PIECE_WHITE_KROOK; else rook = PIECE_WHITE_QROOK; } else if(nextMove == TEAM_BLACK) { king = PIECE_BLACK_KING; if(dir > 0) rook = PIECE_BLACK_KROOK; else rook = PIECE_BLACK_QROOK; } if((dir < 0 && ((!castle.whiteQueenSide && nextMove == TEAM_WHITE) || (!castle.blackQueenSide && nextMove == TEAM_BLACK))) || (dir > 0 && ((!castle.whiteKingSide && nextMove == TEAM_WHITE) || (!castle.blackKingSide && nextMove == TEAM_BLACK)))) /* four closing brackets, nice */ { strncpy(out, "Illegal castle: player has already moved castling " "piece", sizeof(out)); return 1; } for(i = pieces[king].file + dir; i > 0 && i < 7; i += dir) if(board[pieces[king].rank][i]) { strncpy(out, "Illegal castle: path blocked", sizeof(out)); return 1; } pieces[king].file += dir; updateBoard(); if(check(nextMove, NULL)) { strncpy(out, "Illegal castle: king would move through check", sizeof(out)); pieces[king].file -= dir; updateBoard(); return 1; } pieces[king].file += dir; updateBoard(); if(check(nextMove, NULL)) { strncpy(out, "Illegal castle: king would move into check", sizeof(out)); pieces[king].file -= 2 * dir; return 1; } pieces[rook].file = pieces[king].file - dir; if(nextMove == TEAM_WHITE) { castle.whiteKingSide = castle.whiteQueenSide = false; nextMove = TEAM_BLACK; moveMessage(); } else { castle.blackKingSide = castle.blackQueenSide = false; nextMove = TEAM_WHITE; moveMessage(); } return 1; } /* make sure none of the coords can be used unless given a valid value */ srank = sfile = drank = dfile = -1; dstbuf = in + strlen(in) - 2; drank = '8' - dstbuf[1]; dfile = dstbuf[0] - 'a'; if(*in >= 'a' && *in <= 'h') { /* pawn */ sfile = *in - 'a'; for(i = base; i < base + 8; i++) if(pieces[i].file == sfile && canMove(i, drank, dfile)) { srank = pieces[i].rank; break; } if(srank < 0) { strncpy(out, "Illegal pawn move", sizeof(out)); return 1; } } pointer = "RNBQK"; /* FIXME: what if sizeof(char*) != sizeof(square_t) */ if((piece = (square_t)strchr(pointer, *in))) { int num = 0; /* it is actually possible to have the 2 starting and 8 pawn promotion * pieces of each type for a total of 10, but they can never all move * to one square */ square_t matches[8]; piece -= (square_t)pointer; piece += PIECE_WHITE_QROOK; for(i = base + 8; i < base + 16; i++) if(type(i) == type(piece) && canMove(i, drank, dfile)) matches[num++] = i; if(num == 0) { strncpy(out, "Illegal move", sizeof(out)); return 1; } if(num == 1) { srank = pieces[*matches].rank; sfile = pieces[*matches].file; } if(num >= 2) { bool sameRank = true, sameFile = true; srank = pieces[*matches].rank; sfile = pieces[*matches].file; for(i = 1; i < num; i++) { if(pieces[matches[i]].rank != srank) sameRank = false; if(pieces[matches[i]].file != sfile) sameFile = false; } pointer = in + 1; if(sameRank) sfile = *pointer - 'a'; else if(sameFile) srank = '8' - *pointer; else { if(pointer != in + strlen(in) - 2) { sfile = srank = -1; if(*pointer >= 'a' || *pointer <= 'h') sfile = *pointer++ - 'a'; if(*pointer >= '1' || *pointer <= '8') srank = '8' - *pointer; for(i = 0; i < num; i++) { if(pieces[matches[i]].file == sfile) { srank = pieces[matches[i]].rank; break; } if(pieces[matches[i]].rank == srank) { sfile = pieces[matches[i]].file; break; } } } else { char ambuf[12]; strncpy(out, "Invalid move: more than one piece can move " "to that square: use", sizeof(out)); for(i = 0; i < num; i++) { snprintf(ambuf, sizeof(ambuf), " %c%c%c%s%c%c%s", *in, pieces[matches[i]].file + 'a', '8' - pieces[matches[i]].rank, board[drank][dfile] ? "x" : "", dfile + 'a', '8' - drank, i < num - 1 ? (i < num - 2 ? "," : ", or") : ""); strncat(out, ambuf, sizeof(out) - strlen(out)); } return 1; } } } } if(srank < 0 || srank > 7 || sfile < 0 || sfile > 7 || drank < 0 || drank > 7 || dfile < 0 || dfile > 7) { strncpy(out, "Invalid move", sizeof(out)); return 1; } piece = board[srank][sfile]; if(!canMove(piece, drank, dfile)) { strncpy(out, "Illegal move", sizeof(out)); return 1; } pieces[piece].rank = drank; pieces[piece].file = dfile; if((target = board[drank][dfile])) pieces[target].captured = true; if(type(piece) == TYPE_PAWN) { if(board[srank][dfile] == en_passant && !sameTeam(piece, en_passant)) pieces[target = en_passant].captured = true; if((drank == 7 && team(piece) == TEAM_BLACK) || (drank == 0 && team(piece) == TEAM_WHITE)) { const char *options = "rnbq"; char got; square_t base = (team(piece) == TEAM_WHITE) ? PIECE_WHITE_QROOK : PIECE_BLACK_QROOK; /* pawn promotion * FIXME: not logged!! D: */ strncpy(out, "Promote this pawn to:", sizeof(out)); while(1) { /* hackery ahoy */ char gotten[2]; output(); input(gotten, 2); got = gotten[0]; if(got >= 'A' && got <= 'Z') got += 'a' - 'A'; if(got < 'a' || got > 'z') continue; if((promotion = (square_t)(strchr(options, got)))) /* FIXME: if sizeof(square_t) != sizeof(char *) this * could go wrong */ { promotion += base - (square_t)options; pieces[piece].promotion = promotion; break; } } } } updateBoard(); if(check(team(piece), NULL)) { snprintf(out, sizeof(out), "Illegal move: would place player in check"); pieces[piece].rank = srank; pieces[piece].file = sfile; if(promotion) pieces[piece].promotion = PIECE_NOTHING; pieces[target].captured = false; return 1; } for(i = 0; i < PIECE_TOTAL_PIECES && (pieces[i].captured == true || type(i) == TYPE_KING || type(i) == TYPE_NONE); i++); if(i == PIECE_TOTAL_PIECES) { conclusion = DRAW; snprintf(out, sizeof(out), "Stalemate: only kings remain"); playing = false; return 1; } if(type(piece) == TYPE_PAWN && abs(srank - drank) == 2) en_passant = piece; else en_passant = PIECE_NOTHING; if(nextMove == TEAM_WHITE) { if(piece == PIECE_WHITE_KING) castle.whiteKingSide = castle.whiteQueenSide = false; else if(piece == PIECE_WHITE_QROOK) castle.whiteQueenSide = false; else if(piece == PIECE_WHITE_KROOK) castle.whiteKingSide = false; nextMove = TEAM_BLACK; if(check(TEAM_BLACK, NULL)) { if(checkMate(TEAM_BLACK)) { conclusion = VICTORY_WHITE; snprintf(out, sizeof(out), "Checkmate: %s wins!", names[TEAM_WHITE]); playing = false; return 1; } strncpy(out, "Check!", sizeof(out)); } else moveMessage(); } else { if(piece == PIECE_BLACK_KING) castle.blackKingSide = castle.blackQueenSide = false; else if(piece == PIECE_BLACK_QROOK) castle.blackQueenSide = false; else if(piece == PIECE_BLACK_KROOK) castle.blackKingSide = false; nextMove = TEAM_WHITE; if(check(TEAM_WHITE, NULL)) { if(checkMate(TEAM_WHITE)) { conclusion = VICTORY_BLACK; snprintf(out, sizeof(out), "Checkmate: %s wins!", names[TEAM_BLACK]); playing = false; return 1; } strncpy(out, "Check!", sizeof(out)); } else moveMessage(); } return 1; } int main(int argc, char *argv[]) { int result = 1; char in[64]; inputstream = stdin; numCmds = 0; /* alphabetical list of commands */ addCommand("clear", cmd_clear, 0); addCommand("die", cmd_quit, CMD_DEVELOPER); addCommand("draw", cmd_draw, CMD_LOGGED | CMD_PLAYING); addCommand("dump", cmd_dump, CMD_DEVELOPER); addCommand("new", cmd_new, CMD_LOGGED | CMD_NOTPLAYING); addCommand("open", cmd_open, 0); addCommand("player1", cmd_playername, CMD_LOGGED); addCommand("player2", cmd_playername, CMD_LOGGED); addCommand("quit", cmd_quit, CMD_NOTPLAYING); addCommand("replay", cmd_replay, 0); addCommand("resign", cmd_resign, CMD_LOGGED | CMD_PLAYING); addCommand("save", cmd_save, 0); addCommand("undo", cmd_undo, CMD_LOGGED); addCommand("//", NULL, CMD_LOGGED | CMD_INSTANT); /* maybe not these yet addCommand("0-0", cmd_castle, CMD_LOGGED | CMD_PLAYING); addCommand("O-O", cmd_castle, CMD_LOGGED | CMD_PLAYING); addCommand("o-o", cmd_castle, CMD_LOGGED | CMD_PLAYING); */ names = calloc(sizeof(char *), 2); if(!names) { perror("calloc"); return ERROR; } names--; /* I could for loop this but it's hardly worth it */ names[TEAM_WHITE] = malloc(32); names[TEAM_BLACK] = malloc(32); if(argc > 1) strncpy(names[TEAM_WHITE], argv[TEAM_WHITE], 32); else strcpy(names[TEAM_WHITE], "White"); if(argc > 2) strncpy(names[TEAM_BLACK], argv[TEAM_BLACK], 32); else strcpy(names[TEAM_BLACK], "Black"); output_init(); logging = false; cmd_open("open autorun.cfg"); logging = true; newGame(); while(result) { updateBoard(); output(); memset(in, 0, sizeof(in)); input(in, sizeof(in)); result = move(in); } return conclusion; } #ifdef CHESS_ALLEGRO END_OF_MAIN() #endif