dreamcast/chess/chess.cpp
2024-05-31 23:09:55 -05:00

447 lines
14 KiB
C++

#include <cstdint>
#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 <int max_limit>
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)) {
*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;
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<uint8_t>(en_passant_target);
serial::integer<uint8_t>(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;
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.turn = -game_state.turn;
}
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
// fall through to deselect
}
}
}
game_state.interaction.selected_position = -1;
game_state.interaction.moves.length = 0;
}
}