diff --git a/chess/board.c b/chess/board.c new file mode 100644 index 0000000..559bf68 --- /dev/null +++ b/chess/board.c @@ -0,0 +1,173 @@ +#include "board.h" +#include "piece.h" +#include "fmt_parser.h" +#include +#include +#include + +#define ERROR -1 + +#define FREE_AND_FAIL(x) \ + free(x); \ + return -1; + +board * +init_board(char *fen) +{ + board *game; + game = malloc(sizeof(board)); + if (game == NULL) + exit(1); + game->game_flags = FLAG_TURN_WHITE; + + /* init empty cells */ + for (int i = 0; i < SIZE_STD; i++) { + for (int j = 0; j < 8; j++) { + game->game[i][j] = (cell) { + NULL, NONE, 0 + }; + } + } + /* game will be imported from FEN notation (standard if omitted) */ + fen_parser(fen, game); + + return game; +} + +void +switch_turn(board * game) +{ + game->game_flags ^= FLAG_TURN_WHITE; + game->game_flags ^= FLAG_TURN_BLACK; +} + +void +free_board(board * game) +{ + free(game); +} + +/* + * we will use a position instead of a piece, as we just store all the + * currently utilized cells (or positions) in the game array. They will just + * point at the piece they hold, as there is only one instance of each piece. + * We will just have a shared pointer between all cells for the same piece, + * for example, the four bishops will just be stored as four entries in the + * array, with their position as the array indices, and the data they point + * to is the same *bishop variable. + */ +int +validate_move(position origin, position target, board * game, position * amoves) +{ + cell origin_cell = game->game[origin.rank][origin.file]; + int allocated_by_me = 0; + /* quick check for turn */ + if (((game->game_flags & FLAG_TURN_WHITE) && origin_cell.side != WHITE) || + (game->game_flags & FLAG_TURN_BLACK && origin_cell.side != BLACK)) + return ERROR; + + /* no piece in the selected cell */ + if (origin_cell.piece == NULL) + return ERROR; + /* piece is pinned, so can't move */ + if (origin_cell.flags & FLAG_PIN) + return ERROR; + /* check if move would not actually move */ + if (origin.file == target.file && origin.rank == target.rank) + return ERROR; + /* + * current side has an ongoing check so cannot move (TODO: check if + * move would un-check) + */ + if ((game->game_flags & FLAG_TURN_BLACK && game->game_flags & FLAG_CHECK_BLACK) || + (game->game_flags & FLAG_TURN_WHITE && game->game_flags & FLAG_CHECK_WHITE)) + return ERROR; + + /* assign passed moves array, else calculate it */ + position *valid_moves = amoves; + if (valid_moves == NULL) { + valid_moves = moves(origin_cell.piece, origin, game->game, &game->game_flags); + allocated_by_me = 1; + } + + int found_move = ERROR; + found_move = is_in(target, valid_moves); + if (allocated_by_me) + free(valid_moves); /* free move list if WE called the + * moves() function */ + return found_move; +} + +position * +move_piece(position origin, position target, board * game, position * amoves) +{ + cell origin_cell = game->game[origin.rank][origin.file]; + cell target_cell = game->game[target.rank][target.file]; + position *moved_by_castle = NULL; + position *moved_pieces = NULL; + + int valid = validate_move(origin, target, game, amoves); + if (valid == ERROR) + return NULL; + /* check if promotion is available for pawn */ + if (origin_cell.piece->ident == 'p' && + ((origin_cell.side == BLACK && target.rank == 0) + || (origin_cell.side == WHITE && target.rank == SIZE_STD - 1))) { + char promoted_sel = 'q'; + /* TODO: ask for piece input */ + origin_cell.piece = ident_to_piece(promoted_sel); + } + /* check if the king is castling, if yes then move the rook too */ + if ((origin_cell.piece->ident == 'k') + && (origin_cell.flags & FLAG_FIRSTMOVE)) { + if (target.file == origin.file + 2) { + moved_by_castle = move_piece(coords_to_pos(origin.rank, origin.file + 3), coords_to_pos(origin.rank, origin.file + 1), game, NULL); + } else { + moved_by_castle = move_piece(coords_to_pos(origin.rank, origin.file - 4), coords_to_pos(origin.rank, origin.file - 1), game, NULL); + } + switch_turn(game); + } + + target_cell.piece = origin_cell.piece; + origin_cell.piece = NULL; /* erase the piece from the origin */ + target_cell.side = origin_cell.side; /* copy piece side */ + origin_cell.side = NONE; + /* toggle flags */ + /* toggle first move to 0 */ + target_cell.flags = origin_cell.flags & ~(FLAG_FIRSTMOVE); + origin_cell.flags = 0; + + + /* TODO: make use of FLAG_FIRSTMOVE to check for pawn movements */ + game->game[target.rank][target.file] = target_cell; + game->game[origin.rank][origin.file] = origin_cell; + + switch_turn(game); + + int i = 0; + int malloc_size = 4; /* origin & end, plus more + * space for castles or en + * passants */ + moved_pieces = malloc(sizeof(position) * malloc_size + sizeof(position)); /* one more for sentinel */ + moved_pieces[i++] = origin; + moved_pieces[i++] = target; + if (moved_by_castle != NULL) { + moved_pieces[i++] = moved_by_castle[0]; + moved_pieces[i++] = moved_by_castle[1]; + } + moved_pieces[i] = SENTINEL; + free(moved_by_castle); + return moved_pieces; +} + +int +is_in(position m, position * moves) +{ + int i = 0; + while (moves[i].file != -1 && moves[i].rank != -1) { + if (m.file == moves[i].file && m.rank == moves[i].rank) + return 0; + i++; + } + return -1; +} diff --git a/chess/board.h b/chess/board.h new file mode 100644 index 0000000..4b75802 --- /dev/null +++ b/chess/board.h @@ -0,0 +1,23 @@ +#ifndef BOARD_H +#define BOARD_H +#include "piece.h" + +#define FLAG_CHECK_WHITE 0x1 +#define FLAG_CHECK_BLACK 0x2 +#define FLAG_TURN_WHITE 0x4 +#define FLAG_TURN_BLACK 0x8 + +typedef struct { + uint8_t game_flags; + position *last_check_line; + cell last_checking_piece; + cell game[SIZE_STD][SIZE_STD]; +} board; + +board *init_board(); +int is_in(position m, position * moves); +void free_board(board * game); +position *move_piece(position origin, position target, board * game, position * amoves); +int validate_move(position origin, position target, board * game, position * amoves); +void switch_turn(board * game); +#endif diff --git a/chess/fmt_parser.c b/chess/fmt_parser.c new file mode 100644 index 0000000..6e71261 --- /dev/null +++ b/chess/fmt_parser.c @@ -0,0 +1,55 @@ +#include "piece.h" +#include "board.h" +#include +#include +#include +#include + +void +fen_parser(char *fen_string, board * game) +{ + const char *fen_pattern = "[rRnNbBkKqQpP0-9]{1,8}/[rRnNbBkKqQpP0-9]{1,8}/[rRnNbBkKqQpP0-9]{1,8}/[rRnNbBkKqQpP0-9]{1,8}/[rRnNbBkKqQpP0-9]{1,8}/[rRnNbBkKqQpP0-9]{1,8}/[rRnNbBkKqQpP0-9]{1,8}/[rRnNbBkKqQpP0-9]{1,8}/"; + regex_t fen_match; + regmatch_t pmatch[10]; + int res_comp = regcomp(&fen_match, fen_pattern, REG_EXTENDED | REG_NOSUB); + if (res_comp != 0) { + printf("Fatal error composing regex\n"); + exit(1); + } + int result = regexec(&fen_match, fen_string, 10, pmatch, 0); + if (result != 0) { + printf("Provided FEN is invalid\n"); + exit(1); + } + regfree(&fen_match); + int row = 0, col = 0, j = 0; + for (int i = 0; fen_string[i] != 0; i++) { + if (fen_string[i] == '/') { + col = 0; + row++; + continue; + } + if (fen_string[i] >= 49 && fen_string[i] <= 57) { + int count = fen_string[i] - 48; /* turn number to int */ + while (count - j > 0) { + game->game[row][col++] = (cell) { + NULL, NONE, 0 + }; + j++; + } + j = 0; + /* lowercase letters (white) */ + } else if (fen_string[i] >= 0x61 && fen_string[i] <= 0x7a) { + game->game[row][col++] = (cell) { + ident_to_piece(fen_string[i]), WHITE, FLAG_FIRSTMOVE + }; + /* uppercase letters (black) */ + } else if (fen_string[i] >= 0x41 && fen_string[i] <= 0x5a) { + game->game[row][col++] = (cell) { + ident_to_piece(fen_string[i]), BLACK, FLAG_FIRSTMOVE + }; + } else { + continue; + } + } +} diff --git a/chess/fmt_parser.h b/chess/fmt_parser.h new file mode 100644 index 0000000..290df5e --- /dev/null +++ b/chess/fmt_parser.h @@ -0,0 +1,9 @@ +#ifndef FMT_PARSER_H +#define FMT_PARSER_H +#include "piece.h" +#include "board.h" + + +void fen_parser(char *fen_string, board * game); + +#endif diff --git a/chess/main.c b/chess/main.c new file mode 100644 index 0000000..6bf0d1a --- /dev/null +++ b/chess/main.c @@ -0,0 +1,205 @@ +#include "board.h" +#include "piece.h" +#include +#include +#include +#include +#include + +#define WHITE_CELL 1 +#define BLUE_CELL 2 +#define CHECK_CELL 3 + +int +main() +{ + board *game_board = init_board("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR/"); + if (game_board == NULL) + printf("error initializing board...\n"); + setlocale(LC_ALL, ""); + initscr(); + noecho(); + cbreak(); + start_color(); + + init_pair(WHITE_CELL, COLOR_BLACK, COLOR_CYAN); + init_pair(BLUE_CELL, COLOR_WHITE, COLOR_BLACK); + init_pair(CHECK_CELL, COLOR_WHITE, COLOR_RED); + + int min_size = fmin(COLS, LINES); + int cell_size = min_size / SIZE_STD; + int padding = (COLS - cell_size * SIZE_STD * 2) / 2; + + WINDOW *win = newwin(cell_size * SIZE_STD, cell_size * SIZE_STD * 2, 0, padding); + WINDOW *ui_board[SIZE_STD][SIZE_STD]; + + keypad(win, 1); + box(win, 0, 0); + touchwin(win); + int input = 0; + int sel_row = 0, sel_col = 0; + int selected = 0; + position origin; + position target; + position *amoves = NULL; + for (int row = 0; row < SIZE_STD; row++) { + for (int col = 0; col < SIZE_STD; col++) { + ui_board[row][col] = subwin(win, cell_size, cell_size * 2, row * cell_size, padding + cell_size * col * 2); + //box(ui_board[row][col], 0, 0); + wbkgd(ui_board[row][col], COLOR_PAIR(((row + col) % 2 == 0) ? BLUE_CELL : WHITE_CELL)); + touchwin(win); + if (game_board->game[row][col].piece == NULL) + continue; + wmove(ui_board[row][col], cell_size / 2, cell_size / 2); + waddstr(ui_board[row][col], game_board->game[row][col].piece->pretty); + } + } + while (input != 'q') { + int x, y; + getparyx(ui_board[sel_row][sel_col], y, x); + wmove(win, y + cell_size / 2, x + cell_size / 2); + switch (input = wgetch(win)) { + case KEY_UP: + sel_row = fmin(sel_row + 1, SIZE_STD - 1); + break; + case KEY_DOWN: + sel_row = fmax(0, sel_row - 1); + break; + case KEY_LEFT: + sel_col = fmax(0, sel_col - 1); + break; + case KEY_RIGHT: + sel_col = fmin(SIZE_STD - 1, sel_col + 1); + break; + case 'c': + if (selected) { + target = coords_to_pos(sel_row, sel_col); + int valid = validate_move(origin, target, game_board, amoves); + + /* clean all the hints */ + for (int i = 0; amoves[i].rank != -1; i++) { + wmove(ui_board[amoves[i].rank][amoves[i].file], cell_size / 2, cell_size / 2); + waddstr(ui_board[amoves[i].rank][amoves[i].file], + game_board->game[amoves[i].rank][amoves[i].file].piece == NULL ? " " : + game_board->game[amoves[i].rank][amoves[i].file].piece->pretty); + + wrefresh(ui_board[amoves[i].rank][amoves[i].file]); + } + + if (valid == -1) { + free(amoves); + amoves = NULL; + selected = 0; + break; + } + + /* move the piece */ + position *to_refresh = move_piece(origin, target, game_board, amoves); + + /* + * clean the possible moves as we do not need + * them anymore after validation + */ + free(amoves); + amoves = NULL; + selected = 0; + + /* + * calculate moves a second time to find + * following checks + */ + free(moves(game_board->game[target.rank][target.file].piece, target, + game_board->game, &game_board->game_flags)); + /* + * we do not actually need those moves, so + * that's why we just free them afterwards, + * but it will update any checks it finds if + * that piece were to move + */ + int i = 0; + while (to_refresh != NULL && to_refresh[i].rank != -1 && to_refresh[i].file != -1) { + char *sym = game_board->game[to_refresh[i].rank][to_refresh[i].file].piece == NULL ? + " " : + game_board->game[to_refresh[i].rank][to_refresh[i].file].piece->pretty; + wmove(ui_board[to_refresh[i].rank][to_refresh[i].file], cell_size / 2, cell_size / 2); + waddstr(ui_board[to_refresh[i].rank][to_refresh[i].file], sym); + wrefresh(ui_board[to_refresh[i].rank][to_refresh[i].file]); + i++; + } + free(to_refresh); + /* + * /\* fill original cell with an empty piece + * *\/ + */ + /* + * wmove(ui_board[origin.rank][origin.file], + * cell_size / 2, cell_size / 2); + */ + /* + * waddstr(ui_board[origin.rank][origin.file], + * " "); + */ + + /* + * /\* fill target cell with the new piece + * symbol *\/ + */ + /* + * wmove(ui_board[target.rank][target.file], + * cell_size / 2, cell_size / 2); + */ + /* + * waddstr(ui_board[target.rank][target.file], + * game_board->game[target.rank][target.file].piece->pretty); + */ + + /* /\* refresh both subwindows *\/ */ + /* + * wrefresh(ui_board[origin.rank][origin.file]); + */ + /* + * wrefresh(ui_board[target.rank][target.file]); + */ + + } else { + origin = coords_to_pos(sel_row, sel_col); + /* if selected piece cell is empty, ignore it */ + if (game_board->game[sel_row][sel_col].piece == NULL) + break; + + /* + * generate all possible valid moves for + * chosen piece + */ + amoves = moves(game_board->game[sel_row][sel_col].piece, coords_to_pos(sel_row, sel_col), + game_board->game, &game_board->game_flags); + /* if no moves have been returned, ignore */ + if (amoves == NULL) + break; + + /* draw all possible moves as hints */ + for (int i = 0; amoves[i].rank != -1; i++) { + wmove(ui_board[amoves[i].rank][amoves[i].file], cell_size / 2, cell_size / 2); + /* + * draw an "x" if there would be a + * capturing move, else draw a "o" + */ + if (game_board->game[amoves[i].rank][amoves[i].file].piece != NULL) { + waddstr(ui_board[amoves[i].rank][amoves[i].file], "x"); + } else { + waddstr(ui_board[amoves[i].rank][amoves[i].file], "o"); + } + /* refresh hint subwindow */ + wrefresh(ui_board[amoves[i].rank][amoves[i].file]); + } + selected = 1; + } + default: + continue; + } + wrefresh(win); + } + endwin(); + free_board(game_board); + return 0; +} diff --git a/chess/piece.c b/chess/piece.c new file mode 100644 index 0000000..fc98d6a --- /dev/null +++ b/chess/piece.c @@ -0,0 +1,367 @@ +#include "piece.h" +#include +#include +#include +#include + +position +coords_to_pos(short rank, short file){ + position p = {rank, file}; + return p; +} + +SIDE +opposite(SIDE orig) { + switch (orig) { + case WHITE: + return BLACK; + case BLACK: + return WHITE; + default: + return NONE; + } +} + +uint8_t +check_if_king(position * valid_moves, int move_idx, cell game[SIZE_STD][SIZE_STD], + uint8_t game_flags){ + if (move_idx == 0 || valid_moves == NULL) + return game_flags; + uint8_t flags = game_flags; + if (game[valid_moves[move_idx - 1].rank][valid_moves[move_idx - 1].file].piece != NULL && + game[valid_moves[move_idx - 1].rank][valid_moves[move_idx - 1].file].piece->ident == 'k') { + switch (game[valid_moves[move_idx - 1].rank][valid_moves[move_idx - 1].file].side) { + case BLACK: + flags |= FLAG_CHECK_BLACK; + break; + case WHITE: + flags |= FLAG_CHECK_WHITE; + break; + default: + break; + } + } + return flags; +} + +position * +rook_valid(position pos, cell game[SIZE_STD][SIZE_STD], uint8_t * game_flags) +{ + const int max_length = (SIZE_STD - 1) * 2; /* a rook can move ( not + * counting other pieces + * ) a total of size of + * one side - 1 (to + * exclude current + * position) */ + position *valid_moves = malloc(sizeof(position) * max_length + sizeof(position)); /* allocate vertical + + * horizontal moves, + * intentionally left + * the + + * sizeof(position) to + * clarify that's + * reserved for the + * sentinel */ + int move_idx = 0; + + /* Line up */ + for (int row = pos.rank + 1; row < SIZE_STD; row++) { + if (game[row][pos.file].piece != NULL) { + if (game[row][pos.file].side == opposite(game[pos.rank][pos.file].side)) { + valid_moves[move_idx++] = coords_to_pos(row, pos.file); + break; + } + break; + } + valid_moves[move_idx++] = coords_to_pos(row, pos.file); + } + *game_flags = check_if_king(valid_moves, move_idx, game, *game_flags); + /* Line down */ + for (int row = pos.rank - 1; row >= 0; row--) { + if (game[row][pos.file].piece != NULL) { + if (game[row][pos.file].side == opposite(game[pos.rank][pos.file].side)) { + valid_moves[move_idx++] = coords_to_pos(row, pos.file); + break; + } + break; + } + valid_moves[move_idx++] = coords_to_pos(row, pos.file); + } + *game_flags = check_if_king(valid_moves, move_idx, game, *game_flags); + /* Line left */ + for (int col = pos.file - 1; col >= 0; col--) { + if (game[pos.rank][col].piece != NULL) { + if (game[pos.rank][col].side == opposite(game[pos.rank][pos.file].side)) { + valid_moves[move_idx++] = coords_to_pos(pos.rank, col); + break; + } + break; + } + valid_moves[move_idx++] = coords_to_pos(pos.rank, col); + } + *game_flags = check_if_king(valid_moves, move_idx, game, *game_flags); + /* Line right */ + for (int col = pos.file + 1; col < SIZE_STD; col++) { + if (game[pos.rank][col].piece != NULL) { + if (game[pos.rank][col].side == opposite(game[pos.rank][pos.file].side)) { + valid_moves[move_idx++] = coords_to_pos(pos.rank, col); + break; + } + break; + } + valid_moves[move_idx++] = coords_to_pos(pos.rank, col); + } + *game_flags = check_if_king(valid_moves, move_idx, game, *game_flags); + valid_moves[move_idx] = SENTINEL; + return valid_moves; +} + +position * +knight_valid(position pos, cell game[SIZE_STD][SIZE_STD], uint8_t * game_flags) +{ + const int max_length = 8; + position *valid_moves = malloc(sizeof(position) * max_length + sizeof(position)); + int i, j, move_idx = 0; + for (i = -2; i <= 2; i += 4) { + for (j = -1; j <= 1; j += 2) { + position coords_v = coords_to_pos(pos.rank + i, pos.file + j); + position coords_h = coords_to_pos(pos.rank + j, pos.file + i); + if (!(coords_v.file < 0 || coords_v.rank < 0 || + coords_v.file >= SIZE_STD || coords_v.rank >= SIZE_STD || game[pos.rank + i][pos.file + j].side == game[pos.rank][pos.file].side)) { + valid_moves[move_idx++] = coords_v; + } + if (!(coords_h.file < 0 || coords_h.rank < 0 || + coords_h.file >= SIZE_STD || coords_h.rank >= SIZE_STD || game[pos.rank + j][pos.file + i].side == game[pos.rank][pos.file].side)) { + valid_moves[move_idx++] = coords_h; + } + *game_flags = check_if_king(valid_moves, move_idx, game, *game_flags); + } + } + valid_moves[move_idx] = SENTINEL; + return valid_moves; +} + +position * +bishop_valid(position pos, cell game[SIZE_STD][SIZE_STD], uint8_t * game_flags) +{ + /* + * we can approximate, in the worst case the bishop will take + * (board's diagonal length * 2) - 3 + */ + const int max_length = SIZE_STD * 2; + position *valid_moves = malloc(sizeof(position) * max_length + sizeof(position)); /* allocate vertical + + * horizontal moves + + * sentinel */ + int i = 1, move_idx = 0; + + /* first diagonal: top right */ + while (pos.rank + i < SIZE_STD && pos.file + i < SIZE_STD) { + if (game[pos.rank + i][pos.file + i].piece != NULL) { + if (game[pos.rank + i][pos.file + i].side == opposite(game[pos.rank][pos.file].side)) { + valid_moves[move_idx++] = coords_to_pos(pos.rank + i, pos.file + i); + break; + } + break; + } + valid_moves[move_idx++] = coords_to_pos(pos.rank + i, pos.file + i); + i++; + } + *game_flags = check_if_king(valid_moves, move_idx, game, *game_flags); + /* second diagonal: top left */ + i = 1; + while (pos.rank + i < SIZE_STD && pos.file - i >= 0) { + if (game[pos.rank + i][pos.file - i].piece != NULL) { + if (game[pos.rank + i][pos.file - i].side == opposite(game[pos.rank][pos.file].side)) { + valid_moves[move_idx++] = coords_to_pos(pos.rank + i, pos.file - i); + break; + } + break; + } + valid_moves[move_idx++] = coords_to_pos(pos.rank + i, pos.file - i); + i++; + } + *game_flags = check_if_king(valid_moves, move_idx, game, *game_flags); + /* third diagonal: bottom left */ + i = 1; + while (pos.rank - i >= 0 && pos.file - i >= 0) { + if (game[pos.rank - i][pos.file - i].piece != NULL) { + if (game[pos.rank - i][pos.file - i].side == opposite(game[pos.rank][pos.file].side)) { + valid_moves[move_idx++] = coords_to_pos(pos.rank - i, pos.file - i); + break; + } + break; + } + valid_moves[move_idx++] = coords_to_pos(pos.rank - i, pos.file - i); + i++; + } + *game_flags = check_if_king(valid_moves, move_idx, game, *game_flags); + /* fourth diagonal: bottom right */ + i = 1; + while (pos.rank - i >= 0 && pos.file + i < SIZE_STD) { + if (game[pos.rank - i][pos.file + i].piece != NULL) { + if (game[pos.rank - i][pos.file + i].side == opposite(game[pos.rank][pos.file].side)) { + valid_moves[move_idx++] = coords_to_pos(pos.rank - i, pos.file + i); + break; + } + break; + } + valid_moves[move_idx++] = coords_to_pos(pos.rank - i, pos.file + i); + i++; + } + *game_flags = check_if_king(valid_moves, move_idx, game, *game_flags); + valid_moves[move_idx] = SENTINEL; + return valid_moves; +} + +position * +queen_valid(position pos, cell game[SIZE_STD][SIZE_STD], uint8_t * game_flags) +{ + position *bishop_moves = bishop_valid(pos, game, game_flags); + position *rook_moves = rook_valid(pos, game, game_flags); + int bishop_moves_len, rook_moves_len; + for (bishop_moves_len = 0; bishop_moves[bishop_moves_len].file != -1; bishop_moves_len++) + ; + for (rook_moves_len = 0; rook_moves[rook_moves_len].file != -1; rook_moves_len++) + ; + int total_len = bishop_moves_len + rook_moves_len; + position *valid_moves = malloc(sizeof(position) * total_len + sizeof(position)); + memcpy(valid_moves, bishop_moves, bishop_moves_len * sizeof(position)); + memcpy(valid_moves + bishop_moves_len, rook_moves, rook_moves_len * sizeof(position)); + free(bishop_moves); + free(rook_moves); + valid_moves[bishop_moves_len + rook_moves_len] = SENTINEL; + return valid_moves; +} + +position * +king_valid(position pos, cell game[SIZE_STD][SIZE_STD], uint8_t * game_flags) +{ + int move_idx = 0; + const int max_length = 8; + position *valid_moves = malloc(sizeof(position) * max_length + sizeof(position)); + for (int i = -1; i <= 1; i++) { + for (int j = -1; j <= 1; j++) { + if (i == 0 && j == 0) + continue; + if (pos.rank + i >= 0 && pos.file + j >= 0 && + pos.rank + i < SIZE_STD && pos.file + j < SIZE_STD && + (game[pos.rank + i][pos.file + j].side != game[pos.rank][pos.file].side)) { + valid_moves[move_idx++] = coords_to_pos(pos.rank + i, pos.file + j); + } + } + } + /* check if castling is possible */ + if ((game[pos.rank][pos.file].flags & FLAG_FIRSTMOVE) + && (game[pos.rank][pos.file - 4].piece->ident = 'r') + && (game[pos.rank][pos.file - 4].flags & FLAG_FIRSTMOVE) + && (game[pos.rank][pos.file - 3].piece == NULL) + && (game[pos.rank][pos.file - 2].piece == NULL) + && (game[pos.rank][pos.file - 1].piece == NULL)) { + valid_moves[move_idx++] = coords_to_pos(pos.rank, pos.file - 2); + } + if ((game[pos.rank][pos.file].flags & FLAG_FIRSTMOVE) + && (game[pos.rank][pos.file + 3].piece->ident = 'r') + && (game[pos.rank][pos.file + 3].flags & FLAG_FIRSTMOVE) + && (game[pos.rank][pos.file + 2].piece == NULL) + && (game[pos.rank][pos.file + 1].piece == NULL)) { + valid_moves[move_idx++] = coords_to_pos(pos.rank, pos.file + 2); + } + valid_moves[move_idx] = SENTINEL; + return valid_moves; +} + +position * +pawn_valid(position pos, cell game[SIZE_STD][SIZE_STD], uint8_t * game_flags) +{ + int max_length = 4; + position *valid_moves = + malloc(sizeof(position) * max_length + sizeof(position)); + int move_idx = 0; + int side_sign = 1; + if (game[pos.rank][pos.file].side == BLACK) + side_sign = -1; + if (game[pos.rank + side_sign][pos.file].piece == NULL + && pos.rank + side_sign >= 0 + && pos.rank + side_sign < SIZE_STD) { + valid_moves[move_idx++] = coords_to_pos(pos.rank + side_sign, pos.file); + } + if (game[pos.rank][pos.file].flags & FLAG_FIRSTMOVE + && game[pos.rank + 2 * side_sign][pos.file].piece == NULL + && game[pos.rank + 1 * side_sign][pos.file].piece == NULL) { + valid_moves[move_idx++] = coords_to_pos(pos.rank + 2 * side_sign, pos.file); + } + if (game[pos.rank + side_sign][pos.file + side_sign].piece != NULL + && game[pos.rank + side_sign][pos.file + side_sign].side == opposite(game[pos.rank][pos.file].side)) { + valid_moves[move_idx++] = coords_to_pos(pos.rank + side_sign, pos.file + side_sign); + } + *game_flags = check_if_king(valid_moves, move_idx, game, *game_flags); + + if (game[pos.rank + side_sign][pos.file - side_sign].piece != NULL + && game[pos.rank + side_sign][pos.file - side_sign].side == opposite(game[pos.rank][pos.file].side)) { + valid_moves[move_idx++] = coords_to_pos(pos.rank + side_sign, pos.file - side_sign); + } + *game_flags = check_if_king(valid_moves, move_idx, game, *game_flags); + + valid_moves[move_idx] = SENTINEL; + return valid_moves; +} + +position * +moves(piece * piece, position pos, cell game[SIZE_STD][SIZE_STD], uint8_t * game_flags) +{ + switch (piece->ident) { + case 'r': + return rook_valid(pos, game, game_flags); + break; + case 'b': + return bishop_valid(pos, game, game_flags); + break; + case 'n': + return knight_valid(pos, game, game_flags); + break; + case 'q': + return queen_valid(pos, game, game_flags); + break; + case 'k': + return king_valid(pos, game, game_flags); + break; + case 'p': + return pawn_valid(pos, game, game_flags); + break; + default: + return NULL; + } +} + +piece * +ident_to_piece(char ident) +{ + switch (tolower(ident)) { + case 'r': + return &rook; + case 'n': + return &knight; + case 'b': + return &bishop; + case 'q': + return &queen; + case 'k': + return &king; + case 'p': + return &pawn; + default: + return NULL; + } +} + +piece rook = {'r', "♖"}; +piece knight = {'n', "♘"}; +piece bishop = {'b', "♗"}; +piece queen = {'q', "♕"}; +piece king = {'k', "♔"}; +piece pawn = {'p', "♙"}; + +/* + * piece rook = {'r', "R"}; piece knight = {'n', "N"}; + * piece bishop = {'b', "B"}; piece queen = {'q', "Q"}; + * piece king = {'k', "K"}; piece pawn = {'p', "P"}; + */ diff --git a/chess/piece.h b/chess/piece.h new file mode 100644 index 0000000..50a0d6b --- /dev/null +++ b/chess/piece.h @@ -0,0 +1,36 @@ +#ifndef PIECE_H +#define PIECE_H +#include + +#define SIZE_STD 8 +#define SENTINEL coords_to_pos(-1, -1); +#define FLAG_CHECK_WHITE 0x1 +#define FLAG_CHECK_BLACK 0x2 +#define FLAG_FIRSTMOVE 0x4 /* for castles and pawn starts */ +#define FLAG_PIN 0x8 /* block piece when it would discover a check */ + +typedef enum { + BLACK, WHITE, NONE +} SIDE; + +typedef struct { + short rank; /* row */ + short file; /* column */ +} position; + +typedef struct { + char ident; /* FEN style piece identification */ + char *pretty; +} piece; + +typedef struct { + piece *piece; + SIDE side; + uint8_t flags; +} cell; + +position * moves(piece * piece, position pos, cell game[SIZE_STD][SIZE_STD], uint8_t * game_flags); +extern piece rook, bishop, knight, queen, pawn, king; +position coords_to_pos(short rank, short file); +piece *ident_to_piece(char ident); +#endif