// vim: set ts=4 sw=4 et tw=80: #include #include #include #include #include "./chessboard.h" #define DEBUG_BUILD #ifdef DEBUG_BUILD #define DEBUG(...) fprintf(stderr, __VA_ARGS__); #else #define DEBUG(...) (void) 0; #endif static inline enum pieces cb_move(struct chessboard* cb, int from_row, int from_col, int to_row, int to_col); static inline void cb_move_restore(struct chessboard* cb, enum pieces piece, int ex_from_row, int ex_from_col, int ex_to_row, int ex_to_col); static inline int sign(int n); static inline enum player player(struct chessboard* cb, int row, int col); static inline bool is_empty(struct chessboard* cb, int row, int col); static bool check_empty_paths(struct chessboard* cb, int from_row, int from_col, int to_row, int to_col); static bool can_move_pawn(struct chessboard* cb, enum player p, int from_row, int from_col, int to_row, int to_col); static bool can_move_rook(struct chessboard* cb, int from_row, int from_col, int to_row, int to_col); static bool can_move_knight(struct chessboard* cb, int from_row, int from_col, int to_row, int to_col); static bool can_move_bishop(struct chessboard* cb, int from_row, int from_col, int to_row, int to_col); static bool can_move_queen(struct chessboard* cb, int from_row, int from_col, int to_row, int to_col); static bool can_move_king(struct chessboard* cb, int from_row, int from_col, int to_row, int to_col); void init_chessboard(struct chessboard * cb) { int i; for (i = 0; i < 8; i++) { cb->position[1][i] = WHITE_PAWN; cb->position[6][i] = BLACK_PAWN; } cb->position[0][0] = WHITE_ROOK; cb->position[0][1] = WHITE_KNIGHT; cb->position[0][2] = WHITE_BISHOP; cb->position[0][3] = WHITE_QUEEN; cb->position[0][4] = WHITE_KING; cb->position[0][5] = WHITE_BISHOP; cb->position[0][6] = WHITE_KNIGHT; cb->position[0][7] = WHITE_ROOK; cb->position[7][0] = BLACK_ROOK; cb->position[7][1] = BLACK_KNIGHT; cb->position[7][2] = BLACK_BISHOP; cb->position[7][3] = BLACK_QUEEN; cb->position[7][4] = BLACK_KING; cb->position[7][5] = BLACK_BISHOP; cb->position[7][6] = BLACK_KNIGHT; cb->position[7][7] = BLACK_ROOK; int j; for (i = 0; i < 8; i++) { for (j = 2; j < 6; j++) { cb->position[j][i] = EMPTY; } } } void print_chessboard(struct chessboard * cb) { printf(" a b c d e f g h\n" " ┌───┬───┬───┬───┬───┬───┬───┬───┐\n"); int i; for (i = 7; i >= 0; i--) { printf("%d │", i + 1); int j; for (j = 0; j < 8; j++) { printf(" %s │", utf_8_pieces[cb->position[i][j]]); } putchar('\n'); if (i != 0) { printf(" ├───┼───┼───┼───┼───┼───┼───┼───┤\n"); } } printf(" └───┴───┴───┴───┴───┴───┴───┴───┘\n" " a b c d e f g h\n"); } inline enum pieces cb_move(struct chessboard* cb, int from_row, int from_col, int to_row, int to_col) { enum pieces tmp = cb->position[to_row][to_col]; cb->position[to_row][to_col] = cb->position[from_row][from_col]; cb->position[from_row][from_col] = EMPTY; return tmp; } inline void cb_move_restore(struct chessboard* cb, enum pieces piece, int ex_from_row, int ex_from_col, int ex_to_row, int ex_to_col) { cb->position[ex_from_row][ex_from_col] = cb->position[ex_to_row][ex_to_col]; cb->position[ex_to_row][ex_to_col] = piece; } inline bool is_empty(struct chessboard* cb, int row, int col) { return cb->position[row][col] == EMPTY; } inline enum player player(struct chessboard* cb, int row, int col) { return cb->position[row][col] < 7 ? WHITE : BLACK; } inline int sign(int n) { return n == 0 ? 0 : (n > 0 ? 1 : -1); } bool can_move_pawn(struct chessboard* cb, enum player p, int from_row, int from_col, int to_row, int to_col) { DEBUG("can_move_pawn: from: row %d col %d to: row %d col %d\n", from_row, from_col, to_row, to_col); // two forward if in the starting position, no capture is valid if (((p == WHITE && from_row == 1 && to_row == 3 && is_empty(cb, 2, from_col)) || (p == BLACK && from_row == 6 && to_row == 4 && is_empty(cb, 5, from_col))) && from_col == to_col && is_empty(cb, to_row, to_col)) { DEBUG("can_move_pawn: two forward, valid\n"); return true; // one forward, no capture is valid } else if (((p == WHITE && to_row - from_row == 1) || (p == BLACK && from_row - to_row == 1)) && from_col == to_col && is_empty(cb, to_row, to_col)) { DEBUG("can_move_pawn: one forward no capture, valid\n"); return true; // one forward, one left or right, with capture, is valid } else if (to_row == from_row + (p == WHITE ? 1 : -1) && abs(from_col - to_col) == 1 && !is_empty(cb, to_row, to_col)) { DEBUG("can_move_pawn: one forward diagonal capture, valid\n"); return true; } else { DEBUG("can_move_pawn: move pawn not valid\n"); return false; } } bool check_empty_paths(struct chessboard* cb, int from_row, int from_col, int to_row, int to_col) { int ii = sign(to_row - from_row), jj = sign(to_col - from_col); int i = from_row + ii, j = from_col + jj; DEBUG("check_empty_paths: from: row %d col %d, to: row %d col %d, ii: %d," " jj: %d\n", from_row, from_col, to_row, to_col, ii, jj); for (; !(i == to_row && j == to_col); i += ii, j += jj) { if (!is_empty(cb, i, j)) { DEBUG("check_empty_paths: position row %d col %d" " is occupied\n", i, j); return false; } } return true; } bool can_move_rook(struct chessboard* cb, int from_row, int from_col, int to_row, int to_col) { if (to_row != from_row && to_col != from_col) { return false; } return check_empty_paths(cb, from_row, from_col, to_row, to_col); } bool can_move_bishop(struct chessboard* cb, int from_row, int from_col, int to_row, int to_col) { if (abs(from_row - to_row) != abs(from_col - to_col)) { return false; } return check_empty_paths(cb, from_row, from_col, to_row, to_col); } bool can_move_queen(struct chessboard* cb, int from_row, int from_col, int to_row, int to_col) { return can_move_bishop(cb, from_row, from_col, to_row, to_col) || can_move_rook(cb, from_row, from_col, to_row, to_col); } bool can_move_knight(struct chessboard* cb, int from_row, int from_col, int to_row, int to_col) { return (to_row == from_row + 2 && to_col == from_col + 1) || (to_row == from_row + 2 && to_col == from_col - 1) || (to_row == from_row - 2 && to_col == from_col + 1) || (to_row == from_row - 2 && to_col == from_col - 1) || (to_row == from_row + 1 && to_col == from_col + 2) || (to_row == from_row - 1 && to_col == from_col + 2) || (to_row == from_row + 1 && to_col == from_col - 2) || (to_row == from_row - 1 && to_col == from_col + 2); } bool can_move_king(struct chessboard* cb, int from_row, int from_col, int to_row, int to_col) { if (abs(to_row - from_row) > 1) { DEBUG("can_move_king: row movement too big (from %d to %d)\n", from_row, to_row); return false; } if (abs(to_col - from_col) > 1) { DEBUG("can_move_king: column movement too big (from %d to %d)\n", from_col, to_col); return false; } return true; } bool can_move(struct chessboard* cb, enum player p, int from_row, int from_col, int to_row, int to_col) { // try to understand which piece we are moving switch (cb->position[from_row][from_col]) { case WHITE_PAWN: case BLACK_PAWN: DEBUG("can_move: moving a pawn\n"); return can_move_pawn(cb, p, from_row, from_col, to_row, to_col); case WHITE_ROOK: case BLACK_ROOK: DEBUG("can_move: moving a rook\n"); return can_move_rook(cb, from_row, from_col, to_row, to_col); case WHITE_BISHOP: case BLACK_BISHOP: DEBUG("can_move: moving a bishop\n"); return can_move_bishop(cb, from_row, from_col, to_row, to_col); case WHITE_QUEEN: case BLACK_QUEEN: DEBUG("can_move: moving a queen\n"); return can_move_queen(cb, from_row, from_col, to_row, to_col); case WHITE_KNIGHT: case BLACK_KNIGHT: DEBUG("can_move: moving a knight\n"); return can_move_knight(cb, from_row, from_col, to_row, to_col); case WHITE_KING: case BLACK_KING: DEBUG("can_move: moving a king\n"); return can_move_king(cb, from_row, from_col, to_row, to_col); default: DEBUG("can_move: piece not implemented\n"); return false; } } void find_other_king(struct chessboard *cb, enum player p, int* k_row, int* k_col) { for (*k_row = 0; *k_row < 8; (*k_row)++) { for (*k_col = 0; *k_col < 8; (*k_col)++) { if ((p == BLACK && cb->position[*k_row][*k_col] == WHITE_KING) || (p == WHITE && cb->position[*k_row][*k_col] == BLACK_KING)) { DEBUG("find_other_king: %s king in position row %d col %d\n", p == WHITE ? "black" : "white", *k_row, *k_col); return; } } } DEBUG("find_king: king not found"); } bool player_checks(struct chessboard *cb, enum player p) { int k_row, k_col; find_other_king(cb, p, &k_row, &k_col); int i, j; for (i = 0; i < 8; i++) { for (j = 0; j < 8; j++) { if (!is_empty(cb, i, j) && player(cb, i, j) == p) { DEBUG("player_checks: checking row %d col %d to attack king\n", i, j); if (can_move(cb, p, i, j, k_row, k_col)) { DEBUG("player_checks: row %d col %d can attack %s king\n", i, j, p == WHITE ? "black" : "white"); return true; } } } } return false; } bool player_checkmates(struct chessboard *cb, enum player p) { int i, j; // for every opponent's piece for (i = 0; i < 8; i++) { for (j = 0; j < 8; j++) { if (is_empty(cb, i, j) || player(cb, i, j) == p) { continue; } int k, l; // for every possible position not equal to the current position of // that piece, which is empty or occupied by our pieces (implying // capture) for (k = 0; k < 8; k++) { for (l = 0; l < 8; l++) { if ((k == i && l == j) || is_empty(cb, k, l) || player(cb, k, l) != p) { continue; } // check if the move is valid bool can = can_move(cb, p == BLACK ? WHITE : BLACK, i, j, k, l); // if not, carry on if (!can) { continue; } // if valid, perform the move and check for check. enum pieces tmp = cb_move(cb, i, j, k, l); bool check = player_checks(cb, p); cb_move_restore(cb, tmp, i, j, k, l); // if check is no longer there, then this is not a checkmate if (!check) { return false; } } } } } // if no such move exists, then this is checkmate return true; } enum mstatus move( struct chessboard * cb, enum player p, const char * from, const char * to) { // if you are giving us garbage, we behave like Rome's municipal services if (strlen(from) != 2 || from[0] < 'a' || from[0] > 'h' || from[1] < '1' || from[1] > '8') { DEBUG("move: 'from' input '%s' is invalid\n", from); return INVALID; } if (to[0] < 'a' || to[0] > 'h' || to[1] < '1' || to[1] > '8' || strlen(to) != 2) { DEBUG("move: 'to' input '%s' is invalid\n", to); return INVALID; } int from_col = from[0] - 'a', to_col = to[0] - 'a'; int from_row = from[1] - '1', to_row = to[1] - '1'; // if you are moving thin air, EH VOLEVIH if (is_empty(cb, from_row, from_col)) { DEBUG("move: from is empty\n"); return INVALID; } // if you are moving someone else's pieces, "thou shall not steal" if (player(cb, from_row, from_col) != p) { DEBUG("move: moving piece of opponent\n"); return INVALID; } // if you are trying to suicide (capture your piecies), sorry but assisted // suicide is not legal in italy if (cb->position[to_row][to_col] != EMPTY && cb->position[from_row][from_col] / 7 == cb->position[to_row][to_col] / 7) { DEBUG("move: capturing piece from moving player\n"); return INVALID; } // if you try to move on the same spot, don't do it you buffoon if (from_row == to_row && from_col == to_col) { DEBUG("move: moving to same position\n"); return INVALID; } // check if we are currently in check state bool check_before = player_checks(cb, p == WHITE ? BLACK : WHITE); // determine if move is valid bool can = can_move(cb, p, from_row, from_col, to_row, to_col); if (!can) { return INVALID; } else { // try to move, and revert if invalid due to check in progress enum pieces tmp = cb_move(cb, from_row, from_col, to_row, to_col); if (player_checks(cb, p == WHITE ? BLACK : WHITE) && check_before) { DEBUG("move: move does not end check state, invalid"); cb_move_restore(cb, tmp, from_row, from_col, to_row, to_col); return INVALID; } else if (player_checks(cb, p)) { if (player_checkmates(cb, p)) { return CHECK_MATE; } else { return CHECK; } } else { return VALID; } } } int main() { struct chessboard cb; init_chessboard(&cb); enum player p = WHITE; char from[5], to[5]; while (true) { print_chessboard(&cb); printf("%s moves.\n", p == WHITE ? "White" : "Black"); printf("From: "); fgets(from, 5, stdin); // remove trailing \n: https://stackoverflow.com/q/2693776 strtok(from, "\n"); if (strcmp(from, "exit") == 0) { printf("Exiting...\n"); return 0; } printf("To: "); fgets(to, 5, stdin); strtok(to, "\n"); enum mstatus s = move(&cb, p, from, to); switch (s) { case INVALID: printf("Invalid move.\n"); break; case VALID: printf("No check/checkmate.\n"); break; case CHECK: printf("Check.\n"); break; case CHECK_MATE: printf("Checkmate, %s wins.\n", p == WHITE ? "white" : "black"); } if (s != INVALID) { p = !p; // switch player: this works since WHITE = 0 and BLACK = 1 } } }