diff --git a/src/chess.c b/src/chess.c new file mode 100644 index 0000000..2a8c621 --- /dev/null +++ b/src/chess.c @@ -0,0 +1,219 @@ +#include "chess.h" + +#include +#include +#include + +int **create_board(int rows, int cols) { + int **board = (int **)malloc(sizeof(int*) * rows); + if (!board) return NULL; + for (int i = 0; i < cols; i++) { + board[i] = (int *)malloc(sizeof(int)); + if (!board[i]) return NULL; // should probably free all the previously allocated ones as well as the ** pointer + } + return board; +} + +move_t get_move(state_t state) { + move_t move; + int row_from, row_to; + char col_from, col_to; + printf("move (e.g. b1 a3) > "); + scanf("%c%d %c%d", &col_from, &row_from, &col_to, &row_to); + + move.from = (v2_t){ + .x = 97-col_from, + .y = state.board_rows-row_from}; + move.to = (v2_t){ + .x = 97-col_to, + .y = state.board_rows-row_to}; + + return move; +} + +int board_get(state_t state, v2_t pos) { + return state.board[pos.y][pos.x]; +} + +void board_set(state_t *state, v2_t pos, int piece) { + state->board[pos.y][pos.x] = piece; +} + +bool is_move_dest_in_bounds(state_t state, move_t move) { + return + 0 <= move.to.x && move.to.x < state.board_cols && + 0 <= move.to.y && move.to.y < state.board_rows; +} + +bool is_dest_empty(state_t state, move_t move) { + return board_get(state, move.to) == 0; +} + +bool is_dest_opposite_color(state_t state, move_t move) { + return (board_get(state, move.from) * board_get(state, move.to) < 0); // < 0 means different colors +} + +// white pieces are positive ints and black are negative +bool is_dest_empty_or_opposite_color(state_t state, move_t move) { + return + is_dest_empty(state, move) || + is_dest_opposite_color(state, move); +} + +v2_t v2(int x, int y) { + return (v2_t){x, y}; +} + +/** + * 0 -> a == b + * -1 -> a < b + * 1 -> a > b + */ +int v2_cmp(v2_t a, v2_t b) { + if (a.x == b.x && a.y == b.y) { + return 0; + } else if (a.x < b.x && a.y < b.y) { + return -1; + } else if (a.x > b.x && a.y > b.y) { + return 1; + } else { + return 99; // error code + } +} + +bool is_piece_at_origin_able_to_make_move(state_t state, move_t move) { + // need to check all legal moves for the piece at move.from + + unsigned int idx = abs(board_get(state, move.from)); + for (int i = 0; i < state.valid_moves.piece_numbers[idx]; i++) { + if (v2_cmp(move.to, state,valid_moves.table[idx][i]) == 0) { + return true; + } + } + return false; +} + +bool is_move_valid(state_t state, move_t move) { + return is_move_dest_in_bounds(state, move) && + is_dest_empty_or_opposite_color(state, move) && + is_piece_at_origin_able_to_make_move(state, move); +} + +// this only ever must get called AFTER made sure it is legal +// with is_move_legal() +void perform_move(state_t *state, move_t move) { + board_set(state, move.to, board_get(*state, move.from)); + board_set(state, move.from, 0); +} + +player_e get_dest_color(state_t state, move_t move) { + if (board_get(state, move.to) < 0) { + return BLACK; + } else if (board_get(state, move.to) > 0) { + return WHITE; + } else { + return EMPTY; + } +} + +void check_captures(state_t *state, move_t move) { + // king cannot be captured + if (is_dest_opposite_color(*state, move) && abs(board_get(*state, move.to)) != 6) { + switch (get_dest_color(*state, move)) { + case WHITE: + state->captured_by_black[state->by_black_capture_num++] = + board_get(*state, move.to); + break; + case BLACK: + state->captured_by_white[state->by_white_capture_num++] = + board_get(*state, move.to); + break; + case EMPTY: + break; + } + } +} + +// if a pawn has reached the opposite side, change it into a queen +// TODO: let the player choose the next piece +void check_promotions(state_t *state, move_t move) { + if (board_get(*state, move.to) == 1 && move.to.y == 0) { + // 1, y = 0 => white pawn has reached the end + board_set(state, move.to, 5); // 5 -> Wqueen + } else if (board_get(*state, move.to) == -1 && move.to.y == state->board_cols-1) { + // -1, y = board_cols-1 => black pawn has reached the end + board_set(state, move.to, -5); // -5 -> Bqueen + } +} + +// go through all the valid moves for all the pieces +// of the opposite player and see if 'pos' is in there +bool is_position_endangered(state_t state, v2_t pos) { + /** + * for every valid move of KING: + * for every OPPOSITE COLOR PIECE: + * check if the valid moves contain the KING's pos + * -> + * 1. iterate through the board + * 2. if found opposite color piece + * 3. -> for every valid move of this piece, check if it matches any of the kings positions + */ + for (int col = 0; col < state.board_cols; col++) { + for (int row = 0; row < state.board_rows; row++) { + v2_t pos = (v2_t){row, col}; + int piece = board_get(state, pos); + switch (state.active_player) { + case WHITE: + if (piece < 0) { + for (int i = 0; i < state.valid_moves.piece_numbers[piece]; i++) { + for (int j = 0; j < state.valid_moves.piece_numbers[6]; j++) { + if (v2_cmp( + state.valid_moves.table[piece][i], + state.valid_moves.table[6][j]) == 0 + ) { + return true; + } + } + } + } + break; + case BLACK: + if (piece > 0) { + for (int i = 0; i < state.valid_moves.piece_numbers[piece]; i++) { + for (int j = 0; j < state.valid_moves.piece_numbers[6]; j++) { + if (v2_cmp( + state.valid_moves.table[piece][i], + state.valid_moves.table[6][j]) == 0 + ) { + return true; + } + } + } + } + break; + case EMPTY: + break; + } + } + } + + return false; +} + +/** TODO: unimplemented */ +bool check_end_conditions(state_t state, move_t move) { + return false; +} + +void switch_active_player(state_t *state) { + switch (state->active_player) { + case WHITE: + state->active_player = BLACK; + break; + case BLACK: + state->active_player = WHITE; + break; + case EMPTY: + break; + } +} diff --git a/src/chess.h b/src/chess.h new file mode 100644 index 0000000..3dcb76c --- /dev/null +++ b/src/chess.h @@ -0,0 +1,66 @@ +#pragma once + +#include + +#define repeat(var, n) for (int var = 0; var < n; var++) + +// types of players +typedef enum { + EMPTY, + WHITE, + BLACK +} player_e; + +// 2d vector struct (used e.g. for directions or coords) +typedef struct { + int x; + int y; +} v2_t; + +// everything we need to identify and perform a move +typedef struct { + v2_t from; + v2_t to; +} move_t; + +typedef struct { + v2_t table[7][8]; + int piece_numbers[7]; +} valid_moves_t; + +// appstate struct +#define CAPTURABLE 16 +typedef struct { + unsigned int board_cols; + unsigned int board_rows; + int captured_by_white[CAPTURABLE]; + unsigned int by_white_capture_num; + int captured_by_black[CAPTURABLE]; + unsigned int by_black_capture_num; + player_e active_player; + valid_moves_t valid_moves; + int **board; +} state_t; + +/** + * pieces: + * 0 = empty + * +-1 = pawn + * +-2 = rook + * +-3 = knight + * +-4 = bishop + * +-5 = queen + * +-6 = king + */ + +int **create_board(int rows, int cols); +move_t get_move(state_t state); +bool is_move_valid(state_t state, move_t move); +void perform_move(state_t *state, move_t move); +void check_captures(state_t *state, move_t move); +void check_promotions(state_t *state, move_t move); +bool check_end_conditions(state_t state, move_t move); +void switch_active_player(state_t *state); + +v2_t v2(int x, int y); +int v2_cmp(v2_t a, v2_t b); diff --git a/src/main.c b/src/main.c index 30c2f52..d9b64e4 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,67 @@ -#include +/** + * algo: + * 1. create a chess board + * 2. loop + * 2a. select the player to play + * 2b. they try to make a move + * 2c. the move gets validated + * 2d. if it can be made and it is legal, go ahead + * 2e. check for captures and promotions + * 2f. after each move, check for a stalemate or a checkmate condition => if there is -> end of the game + * 2g. if there is no end condition => it's the other player's turn + */ + +#include "chess.h" +#include + int main() { - puts("Hello, World!\n"); + state_t state; + state.valid_moves = (valid_moves_t){ + .table = { + {}, // 0 + + {v2( 0,-1), v2( 0,-2), v2(-1,-1), v2( 1,-1), + v2( 0, 1), v2( 0, 2), v2(-1, 1), v2( 1, 1)}, // 1 + + {v2( 0,-1), v2( 0, 1), v2(-1, 0), v2( 1, 0)}, // ... + + {v2(-1,-2), v2(-1, 2), v2(-2,-1), v2(-2, 1), + v2( 1,-2), v2( 1, 2), v2( 2,-1), v2( 2, 1)}, + + {v2(-1,-1), v2(-1, 1), v2( 1,-1), v2( 1, 1)}, + + {v2(-1,-1), v2(-1, 1), v2( 1,-1), v2( 1, 1), + v2( 0,-1), v2( 0, 1), v2(-1, 0), v2( 1, 0)}, + + {v2(-1,-1), v2(-1, 1), v2( 1,-1), v2( 1, 1), + v2( 0,-1), v2( 0, 1), v2(-1, 0), v2( 1, 0)} + }, + .piece_numbers = {0, 8, 4, 8, 4, 8, 8} + }; + memset(state.captured_by_white, 0, CAPTURABLE); + memset(state.captured_by_black, 0, CAPTURABLE); + state.by_white_capture_num = 0; + state.by_black_capture_num = 0; + state.board_rows = 8; + state.board_cols = 8; + state.board = create_board(state.board_rows, state.board_cols); // creates a constant struct => no need to check for memory issues + + state.active_player = WHITE; + + while (true) { + move_t move = get_move(state); + + if (is_move_valid(state, move)) { + check_captures(&state, move); // checks for captures and modifies the state to keep track of the captures pieces + + check_promotions(&state, move); // same thing as above but for promotions: pawn -> queen when at the end of the board, etc. + + if (check_end_conditions(state, move)) break; // returns false if the game is supposed to end and true if it is supposed tu continue + perform_move(&state, move); + } + + switch_active_player(&state); + } + return 0; }