#include /* isalpha, isdigit ... */ #include /* setlocale */ #include /* memcpy */ #include /* printf, scanf */ #include /* int32_t */ #include /* true, false */ typedef int32_t tile_t; typedef ssize_t pos_t; #define ROW ( (pos_t) 8 ) #define COL ( (pos_t) 1 ) #define BOARD_SIZE ( 8*8*sizeof(tile_t) ) #define E ( (tile_t) 0 ) /* empty */ #define K ( (tile_t) 1 ) /* king */ #define Q ( (tile_t) 2 ) /* queen */ #define R ( (tile_t) 3 ) /* rook */ #define B ( (tile_t) 4 ) /* bishop */ #define N ( (tile_t) 5 ) /* knight */ #define P ( (tile_t) 6 ) /* pawn */ #define BG_DARKBLUE() setcolor(0, 100, 100, 150) #define BG_LIGHTBLUE() setcolor(0, 150, 150, 200) #define FG_BLACK() setcolor(1, 0, 0, 0) #define FG_WHITE() setcolor(1, 0xff, 0xff, 0xff) #define WHITE 1 #define BLACK -1 #define UNICODE_CHESS_SYMBOL 0x2659 bool bishop_move_ok(tile_t const* board, pos_t from, pos_t to); bool cardinal_move_ok(tile_t const *board, pos_t from, pos_t to); bool diagonal_move_ok(tile_t const *board, pos_t from, pos_t to); bool king_move_ok(tile_t const *board, pos_t from, pos_t to); bool knight_move_ok(pos_t from, pos_t to); bool move_ok(tile_t* board, pos_t from, pos_t to, int player); bool pawn_move_ok(tile_t const* board, pos_t from, pos_t to, int direction); bool queen_move_ok(tile_t const *board, pos_t from, pos_t to); bool rook_move_ok(tile_t const *board, pos_t from, pos_t to); bool tile_empty(tile_t t); int get_piece(char* input); pos_t abs_pos(pos_t p); pos_t column(pos_t t); pos_t row(pos_t t); tile_t abs_tile(tile_t t); void do_turn(int turn_no, tile_t *board); void init_board(tile_t* board); void print_board(tile_t* board); void setcolor(int mode, int r, int g, int b); int main(){ int turn = 0; setlocale(LC_ALL, "C.UTF-8"); tile_t board[8*8] = { 0 }; init_board(board); while(1) { print_board(board); do_turn(turn++, board); } return 0; } /* Sets the foreground or background color for subsequent writes. Modes: 0: change background 1: change foreground 2: reset colors */ void setcolor(const int mode, const int r, const int g, const int b){ if( mode == 2 ) printf("\033[0m"); else printf("\033[%i;2;%i;%i;%im", mode?38:48, r, g, b); }; /* Prints the board */ void print_board(tile_t* board){ /* https://en.wikipedia.org/wiki/Chess_symbols_in_Unicode The unicode symbol is calculated from adding 0x2653 with the piece value. */ for (size_t i=0; i<8; i++) { printf("\n %zu ", 8-i); // print number coordinates on y-axis for (size_t j=0; j<8; j++) { tile_t p = board[i*8+j]; // Make tile dark and light if ((i+j) % 2) BG_DARKBLUE(); else BG_LIGHTBLUE(); // Print empty space and do nothing if tile is empty if (tile_empty(p)) { printf(" "); continue; } // Set piece color if (p > 0) FG_WHITE(); else FG_BLACK(); printf("%lc ", UNICODE_CHESS_SYMBOL + abs_tile(p)); } setcolor(2, 0, 0, 0); // reset text attributes } // Print horizontal letter coordinates printf("\n "); for(int i=0; i<8; i++) printf(" %c", 'a'+i); } /* * Resets/inits the board */ void init_board(tile_t *board) { // black pieces are prefixed by a minus (-) const tile_t start[] = { -R,-N,-B,-Q,-K,-B,-N,-R, -P,-P,-P,-P,-P,-P,-P,-P, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, P, P, P, P, P, P, P, P, R, N, B, Q, K, B, N, R }; memcpy(board, start, sizeof(start)); } // TODO: Implement algebaric notation /* Get move, check move and log move for turn */ void do_turn(int turn_no, tile_t *board) { char input[3] = { 0 }; int from = -1, to = -1, tmp; printf("\nPlayer %i, your turn to move", 1 + turn_no%2); /* temporary and ugly solution - read from and to */ while (from == -1 || to == -1) { from = -1; to = -1; printf("\nMove piece\nfrom: "); scanf(" %2s", input); tmp = get_piece(input); if (tmp == -1) continue; from = tmp; printf("\nto: "); scanf(" %2s", input); tmp = get_piece(input); if (tmp == -1) continue; to = tmp; if (!move_ok(board, from, to, turn_no % 2 ? BLACK : WHITE )) { from = -1; continue; } } board[to] = board[from]; board[from] = E; } /* Translates A1, 3B etc. to the 1D index of the board */ int get_piece(char *input) { int x = -1, y = -1, c; for (int i=0; i<2; i++) { c = input[i]; if (isalpha(c)) c = toupper(input[0]); if( 'A' <= c && c <= 'H' ) x = c - 'A'; else if('1' <= c && c <= '8') y = c - '1'; } if (x != -1 && y != -1) return 8*(7-y) + x; else return -1; } pos_t abs_pos(pos_t p) { if(p < 0) return -1 * p; return p; } tile_t abs_tile(tile_t t) { if(t < 0) return -1 * t; return t; } /* Returns true if tile is empty */ bool tile_empty(tile_t t) { return t == E; } /* Returns row number of board index */ pos_t row(pos_t t) { return t / ROW; } /* Returns column number of board index */ pos_t column(pos_t t) { return t % ROW; } /* Returns true if a and b are tiles of opposite player */ bool opposite_color(tile_t a, tile_t b) { return a*b < 0; } /* Returns true if a and b are pieces of the same color */ bool same_color(tile_t a, tile_t b) { return a*b > 0; } /* Returns true if a move is valid, false otherwise */ bool move_ok(tile_t* board, pos_t from, pos_t to, int const player) { /* player must own piece it moves */ if (board[from] * player < 0) { printf("\nYou do not own this piece"); return false; } /* player can't take own pieces or move piece onto itself*/ if (same_color(board[from], board[to])) { printf("\nYou can't take your own pieces"); return false; } /* check piece specific moves */ switch (abs_tile(board[from])) { /* PAWNS */ case P: return pawn_move_ok(board, from, to, player); /* BISHOPS */ case B: return bishop_move_ok(board, from, to); /* ROOKS */ case R: return rook_move_ok(board, from, to); /* KNIGHTS */ case N: return knight_move_ok(from, to); case K: return king_move_ok(board, from, to); case Q: return queen_move_ok(board, from, to); } return false; } /* Returns true if move is a valid pawn move board - array of tiles representing chess board state from - index of board piece starts at to - index of board piece wants to move to direction - pawns movement direction */ bool pawn_move_ok(tile_t const* board, pos_t from, pos_t to, int direction) { pos_t const diff = (to - from) * -direction; switch (diff) { default: return false; case ROW: /* single move */ return tile_empty(board[to]); case ROW - 1: case ROW+1: /* diagonal attack */ return opposite_color(board[to], board[from]); case 2 * ROW: /* double move */ return tile_empty(board[to]) && tile_empty(board[from - ROW * direction]) && (row(from) == 1 || row(from) == 6 ); } } /* Returns true if `to` is on a diagonal line of `from` board - array of tiles representing chess board state from - index of board piece is at to - index of board piece tries to move to */ bool diagonal_move_ok(tile_t const *board, pos_t from, pos_t to) { pos_t const col_diff = column(to) - column(from); pos_t const row_diff = row(to) - row(from); pos_t const x_step = col_diff / abs_pos(col_diff); pos_t const y_step = ROW * row_diff / abs_pos(row_diff); pos_t const step = x_step + y_step; if ( abs_pos(col_diff) != abs_pos(row_diff) ) { printf("\nnot a diagonal move"); return false; } for (pos_t p = from + step; p != to; p += step) { if (!tile_empty(board[p])) { printf("\ncan't jump over pieces"); return false; } } return true; } /* Returns true if `to` is in a cardinal line of `from` board - array of tiles representing chess board state from - index of board piece is at to - index of board piece tries to move to */ bool cardinal_move_ok(tile_t const *board, pos_t from, pos_t to) { pos_t const col_diff = column(to) - column(from); pos_t const row_diff = row(to) - row(from); if (row_diff > 0 && col_diff > 0) { printf("Must move in a straight line"); } pos_t step; if (row_diff) { step = ROW * row_diff / abs_pos(row_diff); } else { step = col_diff / abs_pos(col_diff); } for (pos_t p = from + step; p != to; p += step) { if (!tile_empty(board[p])) { printf("\ncan't jump over pieces"); return false; } } return true; } /* Returns true if move is a valid bishop move board - array of tiles representing chess board state from - index of board bishop is at to - index of board bishop wants to move to */ bool bishop_move_ok(tile_t const* board, pos_t from, pos_t to) { return diagonal_move_ok(board, from, to); } /* Returns true if move is a valid rook move board - array of tiles representing chess board state from - index of board rook is at to - index of board rook wants to move to */ bool rook_move_ok(tile_t const *board, pos_t from, pos_t to) { return cardinal_move_ok(board, from, to); } /* Returns true if move is a valid knight move board - array of tiles representing chess board state from - index of board knight is at to - index of board knight wants to move to */ bool knight_move_ok(pos_t from, pos_t to) { pos_t const abs_col_diff = abs_pos(column(to) - column(from)); pos_t const abs_row_diff = abs_pos(row(to) - row(from)); return (abs_col_diff == 1 && abs_row_diff == 2) || (abs_col_diff == 2 && abs_row_diff == 1); } /* Returns true if move is a valid king move board - array of tiles representing chess board state from - index of board king is at to - index of board king wants to move to */ bool king_move_ok(tile_t const *board, pos_t from, pos_t to) { pos_t const abs_col_diff = abs_pos(column(to) - column(from)); pos_t const abs_row_diff = abs_pos(row(to) - row(from)); (void)board; return abs_col_diff <= 1 && abs_row_diff <= 1; } /* Returns true if move is a valid queen move board - array of tiles representing chess board state from - index of board queen is at to - index of board queen wants to move to */ bool queen_move_ok(tile_t const *board, pos_t from, pos_t to) { return diagonal_move_ok(board, from, to) || cardinal_move_ok(board, from, to); }