#include #include "chess.hpp" #include "sh7091/serial.hpp" namespace chess { xy position_to_xy(int8_t position) { int8_t y = (position >> 4) & 0xf; int8_t x = (position >> 0) & 0xf; return {x, y}; } int8_t xy_to_position(int8_t x, int8_t y) { return ((y & 0xf) << 4) | ((x & 0xf) << 0); } bool xy_inside_board(int8_t x, int8_t y) { int8_t position = xy_to_position(x, y); return (position & 0x88) == 0; } int8_t piece_list_to_position(const chess::board_state& board_state, chess::piece * piece) { int8_t position = piece - &board_state.board[0]; return position; } constexpr inline bool turn_same_side(int8_t side, int8_t type) { return (type != 0) && ((side > 0) == (type > 0)); } bool speculative_check(game_state& game_state, int8_t from_position, int8_t to_position, int8_t side); template static move_t * moves_line(game_state& game_state, int8_t origin, move_t * moves, int dx, int dy) { board_state& board = game_state.board; piece& self = board.board[origin]; auto [ox, oy] = position_to_xy(origin); int8_t x = ox, y = oy; for (int limit = 0; limit < max_limit; limit++) { x += dx; y += dy; if (xy_inside_board(x, y)) { piece& other = board.board[xy_to_position(x, y)]; bool different_sides = ((self.type ^ other.type) < 0); if (other.type == 0 || different_sides) { int8_t to_position = xy_to_position(x, y); if (!speculative_check(game_state, origin, to_position, self.type)) *moves++ = {move_type::normal, to_position}; } if (other.type != 0) break; } else { break; } } return moves; } move_t * moves_pawn(game_state& game_state, int8_t origin, move_t * moves) { board_state& board = game_state.board; piece& self = board.board[origin]; int8_t direction = (self.type > 0) ? 1 : -1; auto [ox, oy] = position_to_xy(origin); int advance = 1; auto type = move_type::normal; do { int8_t x = ox; int8_t y = oy + direction * advance; if (xy_inside_board(x, y)) { piece& other = board.board[xy_to_position(x, y)]; if (other.type == 0) { int8_t to_position = xy_to_position(x, y); if (!speculative_check(game_state, origin, to_position, self.type)) { if (y == 0 || y == 7) { type = move_type::pawn_promote; } *moves++ = {type, to_position}; } } else { break; } } advance += 1; type = move_type::pawn_double_advance; } while (self.movement == movement_state::not_moved && advance <= 2); int8_t diagonal = 1; for (int i = 0; i < 2; i++) { int8_t x = ox + diagonal; int8_t y = oy + direction; if (xy_inside_board(x, y)) { piece& other = board.board[xy_to_position(x, y)]; bool different_sides = ((self.type ^ other.type) < 0); if (other.type != 0 && different_sides) { int8_t to_position = xy_to_position(x, y); if (!speculative_check(game_state, origin, to_position, self.type)) *moves++ = {move_type::normal, to_position}; } if (game_state.en_passant_target != -1 && xy_to_position(x, oy) == game_state.en_passant_target) { // en_passant_target is always the opposite side int8_t to_position = xy_to_position(x, y); if (!speculative_check(game_state, origin, to_position, self.type)) *moves++ = {move_type::pawn_en_passant_capture, to_position}; } } diagonal = -diagonal; } return moves; } move_t * moves_knight(game_state& game_state, int8_t origin, move_t * moves) { board_state& board = game_state.board; piece& self = board.board[origin]; xy delta[8] = { { 2, -1}, // up, left { 2, 1}, // up, right {-2, -1}, // down, left {-2, 1}, // down, right { 1, -2}, // left, up { 1, 2}, // right, up {-1, -2}, // left, down {-1, 2}, // right, down }; auto [ox, oy] = position_to_xy(origin); for (int i = 0; i < 8; i++) { uint8_t x = ox + delta[i].x; uint8_t y = oy + delta[i].y; if (xy_inside_board(x, y)) { piece& other = board.board[xy_to_position(x, y)]; bool different_sides = ((self.type ^ other.type) < 0); if (other.type == 0 || different_sides) { int8_t to_position = xy_to_position(x, y); if (!speculative_check(game_state, origin, to_position, self.type)) *moves++ = {move_type::normal, to_position}; } } } return moves; } move_t * moves_bishop(game_state& game_state, int8_t origin, move_t * moves) { // up-left moves = moves_line<8>(game_state, origin, moves, 1, 1); // up-right moves = moves_line<8>(game_state, origin, moves, -1, 1); // down-left moves = moves_line<8>(game_state, origin, moves, 1, -1); // down-right moves = moves_line<8>(game_state, origin, moves, -1, -1); return moves; } move_t * moves_rook(game_state& game_state, int8_t origin, move_t * moves) { // up moves = moves_line<8>(game_state, origin, moves, 0, 1); // down moves = moves_line<8>(game_state, origin, moves, 0, -1); // left moves = moves_line<8>(game_state, origin, moves, 1, 0); // right moves = moves_line<8>(game_state, origin, moves, -1, 0); return moves; } move_t * moves_queen(game_state& game_state, int8_t origin, move_t * moves) { moves = moves_bishop(game_state, origin, moves); moves = moves_rook(game_state, origin, moves); return moves; } move_t * moves_king(game_state& game_state, int8_t origin, move_t * moves) { // up-left moves = moves_line<1>(game_state, origin, moves, 1, 1); // up-right moves = moves_line<1>(game_state, origin, moves, -1, 1); // down-left moves = moves_line<1>(game_state, origin, moves, 1, -1); // down-right moves = moves_line<1>(game_state, origin, moves, -1, -1); // up moves = moves_line<1>(game_state, origin, moves, 0, 1); // down moves = moves_line<1>(game_state, origin, moves, 0, -1); // left moves = moves_line<1>(game_state, origin, moves, 1, 0); // right moves = moves_line<1>(game_state, origin, moves, -1, 0); // castle board_state& board = game_state.board; piece& self = board.board[origin]; if (self.movement == movement_state::not_moved && !speculative_check(game_state, origin, origin, self.type)) { int direction = 1; int distance = 2; move_type type = move_type::castle_short; auto [ox, oy] = position_to_xy(origin); do { int8_t rook_position = xy_to_position(direction == 1 ? 7 : 0, oy); piece& rook = board.board[rook_position]; if (__builtin_abs(rook.type) == piece_type::rook && rook.movement == movement_state::not_moved) { move_t * castle_moves = moves_line<8>(game_state, origin, moves, direction, 0); if (castle_moves - moves == distance) { int8_t to_position = xy_to_position(ox + (direction * 2), oy); *moves++ = {type, to_position}; } } distance += 1; direction = -direction; type = move_type::castle_long; } while (direction == -1); } return moves; } int moves_position(game_state& game_state, int8_t origin, move_t * moves) { board_state& board = game_state.board; piece& self = board.board[origin]; switch (__builtin_abs(self.type)) { case piece_type::pawn: return moves_pawn(game_state, origin, moves) - moves; case piece_type::knight: return moves_knight(game_state, origin, moves) - moves; case piece_type::bishop: return moves_bishop(game_state, origin, moves) - moves; case piece_type::rook: return moves_rook(game_state, origin, moves) - moves; case piece_type::queen: return moves_queen(game_state, origin, moves) - moves; case piece_type::king: return moves_king(game_state, origin, moves) - moves; default: return 0; } } static void board_init(game_state& game_state) { using namespace piece_type; constexpr int8_t types[8][8] = { { rook , knight, bishop, queen , king , bishop, knight, rook }, // white { pawn , pawn , pawn , pawn , pawn , pawn , pawn , pawn }, // white { empty , empty , empty , empty , empty , empty , empty , empty }, { empty , empty , empty , empty , empty , empty , empty , empty }, { empty , empty , empty , empty , empty , empty , empty , empty }, { empty , empty , empty , empty , empty , empty , empty , empty }, {-pawn , -pawn , -pawn , -pawn , -pawn , -pawn , -pawn , -pawn }, // black {-rook , -knight, -bishop, -queen , -king , -bishop, -knight, -rook }, // black }; game_state.piece_list.length = 0; for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { int8_t type = types[y][x]; int8_t position = xy_to_position(x, y); piece& p = game_state.board.board[position]; p.type = type; p.movement = movement_state::not_moved; p.piece_list_offset = game_state.piece_list.length; switch (__builtin_abs(type)) { case empty: break; default: game_state.piece_list.piece[game_state.piece_list.length++] = &p; break; } } } } void game_init(game_state& game_state) { game_state.turn = 1; // white game_state.en_passant_target = -1; game_state.halfmove_number = 0; game_state.fullmove_number = 0; game_state.interaction.selected_position = -1; game_state.interaction.last_move.from_position = -1; game_state.interaction.last_move.to_position = -1; game_state.interaction.promotion_ix[0] = 0; game_state.interaction.promotion_ix[1] = 0; board_init(game_state); } int position_in_moves(moves_list& moves, int8_t position) { for (int i = 0; i < moves.length; i++) { int8_t move_position = moves.moves[i].to_position; if (move_position == position) { return i; } } return -1; } void piece_list_delete(piece_list& list, int8_t offset) { for (int i = offset + 1; i < list.length; i++) { list.piece[i]->piece_list_offset = i - 1; list.piece[i - 1] = list.piece[i]; } list.length -= 1; } static int _speculative_check_depth = 0; bool speculative_check(game_state& game_state, int8_t from_position, int8_t to_position, int8_t side) { if (_speculative_check_depth > 0) return false; _speculative_check_depth++; piece& origin = game_state.board.board[from_position]; piece& destination = game_state.board.board[to_position]; const int8_t origin_type = origin.type; const int8_t destination_type = destination.type; origin.type = piece_type::empty; destination.type = origin_type; bool check = false; for (int i = 0; i < game_state.piece_list.length; i++) { if (i == origin.piece_list_offset) { continue; } piece * test_piece = game_state.piece_list.piece[i]; if (turn_same_side(side, test_piece->type)) { continue; } int8_t position = piece_list_to_position(game_state.board, test_piece); moves_list ml; ml.length = moves_position(game_state, position, ml.moves); for (int j = 0; j < ml.length; j++) { int8_t move_position = ml.moves[j].to_position; piece& move_piece = game_state.board.board[move_position]; if (__builtin_abs(move_piece.type) == piece_type::king) { serial::string("check\n"); check = true; goto exit; } } } exit: origin.type = origin_type; destination.type = destination_type; _speculative_check_depth--; return check; } void do_move(game_state& game_state, int8_t from_position, move_t& move) { piece& origin = game_state.board.board[from_position]; piece& destination = game_state.board.board[move.to_position]; if (destination.type != 0) { // capture piece_list_delete(game_state.piece_list, destination.piece_list_offset); } int8_t en_passant_target = game_state.en_passant_target; game_state.en_passant_target = -1; switch (move.type) { case move_type::pawn_double_advance: serial::string("pawn double advance\n"); game_state.en_passant_target = move.to_position; break; case move_type::castle_short: serial::string("move castle short\n"); { auto [ox, oy] = position_to_xy(move.to_position); int8_t rook_from = xy_to_position(7, oy); move_t rook_move = {move_type::normal, xy_to_position(5, oy)}; do_move(game_state, rook_from, rook_move); } break; case move_type::castle_long: serial::string("move castle long\n"); { auto [ox, oy] = position_to_xy(move.to_position); int8_t rook_from = xy_to_position(0, oy); move_t rook_move = {move_type::normal, xy_to_position(3, oy)}; do_move(game_state, rook_from, rook_move); } break; case move_type::pawn_en_passant_capture: { serial::string("move en passant capture\n"); piece& en_passant_piece = game_state.board.board[en_passant_target]; serial::integer(en_passant_target); serial::integer(en_passant_piece.type); en_passant_piece.type = piece_type::empty; piece_list_delete(game_state.piece_list, en_passant_piece.piece_list_offset); } break; case move_type::pawn_promote: { serial::string("move pawn promote\n"); if (origin.type > 0) origin.type = promotion_types[game_state.interaction.promotion_ix[0]]; else origin.type = -promotion_types[game_state.interaction.promotion_ix[1]]; } break; default: break; } destination.type = origin.type; destination.movement = true; destination.piece_list_offset = origin.piece_list_offset; origin.type = piece_type::empty; game_state.piece_list.piece[origin.piece_list_offset] = &destination; game_state.interaction.last_move.from_position = from_position; game_state.interaction.last_move.to_position = move.to_position; } void select_position(game_state& game_state, int8_t x, int8_t y) { int8_t position = xy_to_position(x, y); if (turn_same_side(game_state.turn, game_state.board.board[position].type)) { if (game_state.interaction.selected_position != position) { game_state.interaction.selected_position = position; game_state.interaction.moves.length = moves_position(game_state, position, game_state.interaction.moves.moves); return; } } else { // not same side or empty if (game_state.interaction.selected_position != -1) { int moves_ix = position_in_moves(game_state.interaction.moves, position); if (moves_ix >= 0) { do_move(game_state, game_state.interaction.selected_position, // from game_state.interaction.moves.moves[moves_ix]); // to game_state.turn = -game_state.turn; // fall through to deselect } } } game_state.interaction.selected_position = -1; game_state.interaction.moves.length = 0; } void clear_annotations(game_state& game_state) { game_state.interaction.annotation_list.length = 0; } void delete_annotation(annotation_list& annotation_list, int i) { annotation_list.length -= 1; for (int j = i; j < annotation_list.length; j++) { annotation_list.annotation[j] = annotation_list.annotation[j + 1]; } } void annotate_position(game_state& game_state, int8_t x, int8_t y) { int8_t position = xy_to_position(x, y); auto& annotation_list = game_state.interaction.annotation_list; for (int i = 0; i < annotation_list.length; i++) { if (annotation_list.annotation[i].from_position == position) { delete_annotation(annotation_list, i); return; } } annotation_list.annotation[annotation_list.length++].from_position = position; } }