1
0
mirror of https://github.com/Tha14/toxic.git synced 2024-11-23 08:33:01 +01:00
toxic/src/game_chess.c

2165 lines
54 KiB
C
Raw Normal View History

2020-12-19 21:40:00 +01:00
/* game_chess.c
*
*
* Copyright (C) 2020 Toxic All Rights Reserved.
*
* This file is part of Toxic.
*
* Toxic is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Toxic is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Toxic. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "game_base.h"
#include "game_util.h"
#include "game_chess.h"
#include "misc_tools.h"
#define CHESS_WHITE_SQUARE_COLOUR WHITE_GREEN
#define CHESS_BLACK_SQUARE_COLOUR WHITE_BLUE
#define CHESS_BOARD_ROWS 8
#define CHESS_BOARD_COLUMNS 8
#define CHESS_TILE_SIZE_X 4
#define CHESS_TILE_SIZE_Y 2
#define CHESS_SQUARES (CHESS_BOARD_ROWS * CHESS_BOARD_COLUMNS)
#define CHESS_MAX_MESSAGE_SIZE 64
typedef enum ChessPacketType {
CHESS_PACKET_INIT_SEND_INVITE = 0x01,
CHESS_PACKET_INIT_ACCEPT_INVITE = 0x02,
CHESS_PACKET_MOVE_PIECE = 0xFE,
CHESS_PACKET_RESIGN = 0xFF,
} ChessPacketType;
2020-12-19 21:40:00 +01:00
typedef struct ChessCoords {
char L;
uint8_t N;
2020-12-19 21:40:00 +01:00
} ChessCoords;
typedef enum ChessColour {
White = 0u,
Black,
} ChessColour;
typedef enum ChessStatus {
Initializing = 0U,
Playing,
2020-12-19 21:40:00 +01:00
Checkmate,
Stalemate,
Resigned,
2020-12-19 21:40:00 +01:00
} ChessStatus;
typedef enum PieceType {
Pawn = 0u,
Rook,
Knight,
Bishop,
King,
Queen,
NoPiece,
} PieceType;
typedef struct Piece {
char display_char;
ChessColour colour;
PieceType type;
} Piece;
typedef struct Tile {
Piece piece;
Coords coords; // xy position on window
ChessCoords chess_coords; // chess notation pair
int colour; // display colour (not to be confused with White/Black ChessColour)
} Tile;
typedef struct Board {
Tile tiles[CHESS_SQUARES];
int x_right_bound;
int x_left_bound;
int y_top_bound;
int y_bottom_bound;
} Board;
typedef struct Player {
Tile *holding_tile;
ChessColour colour;
bool can_castle_qs;
bool can_castle_ks;
bool in_check;
Tile *en_passant; // the tile holding the pawn that passed us
int en_passant_move_number; // the move number the last en passant was on
Piece captured[CHESS_SQUARES];
size_t number_captured;
int score; // total points of pieces captured
2020-12-19 21:40:00 +01:00
} Player;
typedef struct ChessState {
Player self;
Player other;
Board board;
int curs_x;
int curs_y;
char status_message[CHESS_MAX_MESSAGE_SIZE + 1];
size_t message_length;
bool black_to_move;
size_t move_number;
ChessStatus status;
} ChessState;
static int chess_packet_send_move(const GameData *game, const Tile *from, const Tile *to);
static int chess_packet_send_resign(const GameData *game);
2020-12-19 21:40:00 +01:00
static const char Board_Letters[] = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
#define CHESS_NUM_BOARD_LETTERS (sizeof(Board_Letters) / sizeof(char))
static int chess_get_letter_index(char letter)
{
for (int i = 0; i < CHESS_NUM_BOARD_LETTERS; ++i) {
if (Board_Letters[i] == letter) {
return i;
}
}
return -1;
}
/*
* Copies `piece_b` to `piece_a`.
*/
static void chess_copy_piece(Piece *piece_a, Piece *piece_b)
{
memcpy(piece_a, piece_b, sizeof(Piece));
}
2020-12-19 21:40:00 +01:00
static void chess_set_piece(Piece *piece, PieceType type, ChessColour colour)
{
piece->type = type;
piece->colour = colour;
switch (type) {
case Pawn:
piece->display_char = 'o';
break;
case Bishop:
piece->display_char = 'B';
break;
case Rook:
piece->display_char = 'R';
break;
case Knight:
piece->display_char = 'N';
break;
case King:
piece->display_char = 'K';
break;
case Queen:
piece->display_char = 'Q';
break;
default:
piece->display_char = '?';
break;
}
}
static size_t chess_get_piece_value(PieceType type)
{
switch (type) {
case Pawn:
return 1;
case Bishop:
return 3;
case Knight:
return 3;
case Rook:
return 5;
case Queen:
return 9;
default:
return 0;
}
}
/*
* Puts the absolute difference between `from` and `to` chess coordinates in `l_diff` and `n_diff`.
*
* Return 0 on success.
* Return -1 on failure.
*/
static int chess_get_chess_coords_diff(const Tile *from, const Tile *to, int *l_diff, int *n_diff)
{
int from_letter_idx = chess_get_letter_index(from->chess_coords.L);
int to_letter_idx = chess_get_letter_index(to->chess_coords.L);
if (from_letter_idx == -1 || to_letter_idx == -1) {
return -1;
}
*l_diff = abs(from_letter_idx - to_letter_idx);
*n_diff = abs((int)from->chess_coords.N - (int)to->chess_coords.N);
return 0;
}
2020-12-19 21:40:00 +01:00
static void chess_set_status_message(ChessState *state, const char *message, size_t length)
{
if (length > CHESS_MAX_MESSAGE_SIZE) {
return;
}
memcpy(state->status_message, message, length);
state->status_message[length] = 0;
state->message_length = length;
}
static void chess_print_move_notation(ChessState *state, const Tile *from, const Tile *to, bool check)
{
if (from->piece.type == King) { // special case if player castled
int l_diff;
int n_diff;
if (chess_get_chess_coords_diff(from, to, &l_diff, &n_diff) == -1) {
chess_set_status_message(state, "Error", strlen("Error"));
return;
}
if (n_diff == 0 && l_diff > 1 && (to->chess_coords.L == 'c' || to->chess_coords.L == 'g')) {
const char *message = to->chess_coords.L == 'c' ? "Last move: 0-0-0" : "Last move: 0-0";
chess_set_status_message(state, message, strlen(message));
return;
}
}
char message[CHESS_MAX_MESSAGE_SIZE + 1];
bool captured = (to->piece.type != NoPiece) || (from->piece.type == Pawn && from->chess_coords.L != to->chess_coords.L);
char tmp[2];
snprintf(tmp, sizeof(tmp), "%c", from->piece.display_char);
const char *from_char = from->piece.type != Pawn ? tmp : "";
char pawn_capture[2] = {0};
if (strcmp(from_char, "") == 0 && captured) {
snprintf(pawn_capture, sizeof(pawn_capture), "%c", from->chess_coords.L);
}
const char *capture = captured ? "x" : "";
const char *check_char = check ? "+" : "";
snprintf(message, sizeof(message), "Last move: %s%s%s%c%u%s",
pawn_capture, from_char, capture, to->chess_coords.L, to->chess_coords.N, check_char);
chess_set_status_message(state, message, strlen(message));
}
2020-12-19 21:40:00 +01:00
/*
* Return true if `pair_a` is the same as `pair_b`.
*/
static bool chess_chess_coords_overlap(const ChessCoords *pair_a, const ChessCoords *pair_b)
{
return pair_a->L == pair_b->L && pair_a->N == pair_b->N;
}
/*
* Return the player whose turn it currently is not.
*/
static Player *chess_get_other_player(ChessState *state)
{
if (state->black_to_move) {
return state->self.colour == Black ? &state->other : &state->self;
} else {
return state->self.colour == White ? &state->other : &state->self;
}
}
/*
* Return the player whose turn it currently is.
*/
static Player *chess_get_player_to_move(ChessState *state)
{
if (state->black_to_move) {
return state->self.colour == Black ? &state->self : & state->other;
} else {
return state->self.colour == Black ? &state->other : & state->self;
}
}
/*
* Return true if it's `player`'s turn to move.
*/
static bool chess_player_to_move(const ChessState *state, const Player *player)
{
return (player->colour == White && !state->black_to_move) || (player->colour == Black && state->black_to_move);
}
/*
* Removes `piece` from the board and puts it in `player`'s captured list. Also updates their score.
*/
static void chess_capture_piece(Player *player, Piece *piece)
{
size_t idx = player->number_captured;
if (idx < CHESS_SQUARES) {
chess_copy_piece(&player->captured[idx], piece);
++player->number_captured;
}
player->score += chess_get_piece_value(piece->type);
piece->type = NoPiece;
}
2020-12-19 21:40:00 +01:00
/*
* Puts coordinates associated with tile at x y coordinates in `chess_coords`.
*
* Return 0 on success.
* Return -1 if coordinates are out of bounds.
*/
static int chess_get_chess_coords(const Board *board, int x, int y, ChessCoords *chess_coords, bool self_is_white)
{
if (x < board->x_left_bound || x > board->x_right_bound || y < board->y_top_bound || y > board->y_bottom_bound) {
return -1;
}
size_t idx = (x - board->x_left_bound) / CHESS_TILE_SIZE_X;
if (idx >= CHESS_NUM_BOARD_LETTERS) {
return -1;
}
if (self_is_white) {
chess_coords->L = Board_Letters[idx];
chess_coords->N = ((board->y_bottom_bound + 1) - y) / CHESS_TILE_SIZE_Y;
} else {
chess_coords->L = Board_Letters[7 - idx];
chess_coords->N = 8 - (((board->y_bottom_bound + 1) - y) / CHESS_TILE_SIZE_Y) + 1;
}
return 0;
}
/*
* Returns the tile located at given coordinates.
*/
static Tile *chess_get_tile(ChessState *state, int x, int y)
{
Board *board = &state->board;
ChessCoords pair;
if (chess_get_chess_coords(board, x, y, &pair, state->self.colour == White) == -1) {
return NULL;
}
for (size_t i = 0; i < CHESS_SQUARES; ++i) {
Tile *tile = &board->tiles[i];
if (tile->chess_coords.N == pair.N && tile->chess_coords.L == pair.L) {
return tile;
}
}
return NULL;
}
/*
* Returns tile associated with `chess_coords`.
*/
static Tile *chess_get_tile_at_chess_coords(Board *board, const ChessCoords *chess_coords)
{
for (size_t i = 0; i < CHESS_SQUARES; ++i) {
Tile *tile = &board->tiles[i];
if (tile->chess_coords.N == chess_coords->N && tile->chess_coords.L == chess_coords->L) {
return tile;
}
}
return NULL;
}
/*
* Return true if `piece` can occupy `tile`.
*/
static bool chess_piece_can_occupy_tile(const Piece *piece, const Tile *tile)
{
return tile->piece.colour != piece->colour || tile->piece.type == NoPiece;
}
/*
* Return true if all squares in a horizontal or vertical line between `from` and `to`
* are vacant, excluding each square respectively.
*/
static bool chess_path_line_clear(Board *board, const Tile *from, const Tile *to, int l_diff, int n_diff)
{
if (l_diff < 0 || n_diff < 0) {
return false;
}
if (l_diff != 0 && n_diff != 0) {
return false;
}
ChessCoords chess_coords = (ChessCoords) {
0,
0
};
2020-12-19 21:40:00 +01:00
size_t start;
size_t end;
if (l_diff == 0) {
start = 1 + MIN(from->chess_coords.N, to->chess_coords.N);
end = start + n_diff - 1;
chess_coords.L = from->chess_coords.L;
} else {
int from_idx = chess_get_letter_index(from->chess_coords.L);
int to_idx = chess_get_letter_index(to->chess_coords.L);
if (to_idx == -1 || from_idx == -1) {
return false;
}
start = 1 + MIN(from_idx, to_idx);
end = start + l_diff - 1;
chess_coords.N = from->chess_coords.N;
}
for (size_t i = start; i < end; ++i) {
if (l_diff == 0) {
chess_coords.N = i;
} else {
if (i >= CHESS_NUM_BOARD_LETTERS) {
return false;
}
chess_coords.L = Board_Letters[i];
}
Tile *tile = chess_get_tile_at_chess_coords(board, &chess_coords);
if (tile == NULL) {
return false;
}
if (tile->piece.type != NoPiece) {
return false;
}
}
return true;
}
/*
* Return true if all tiles in a diagonal line between `from` and `to` are
* unoccupied, excluding each respective tile.
*/
static bool chess_path_diagonal_clear(Board *board, const Tile *from, const Tile *to, int l_diff, int n_diff)
{
if (l_diff < 0 || n_diff < 0) {
return false;
}
if (l_diff != n_diff || l_diff == 0) {
return false;
}
size_t start = 1 + MIN(from->chess_coords.N, to->chess_coords.N);
size_t end = start + n_diff - 1;
2020-12-19 21:40:00 +01:00
// we're caluclating from south-east to north-west, or from south-west to north-east
bool left_diag = (from->chess_coords.N > to->chess_coords.N && from->chess_coords.L < to->chess_coords.L)
|| (from->chess_coords.N < to->chess_coords.N && from->chess_coords.L > to->chess_coords.L);
size_t from_l_idx = chess_get_letter_index(from->chess_coords.L);
size_t to_l_idx = chess_get_letter_index(to->chess_coords.L);
size_t start_l_idx = left_diag ? MAX(from_l_idx, to_l_idx) - 1 : MIN(from_l_idx, to_l_idx) + 1;
if (start_l_idx == -1) {
return -1;
}
ChessCoords chess_coords;
for (size_t i = start; i < end; ++i) {
chess_coords.N = i;
if (start_l_idx >= CHESS_NUM_BOARD_LETTERS) {
return false;
}
chess_coords.L = Board_Letters[start_l_idx];
Tile *tile = chess_get_tile_at_chess_coords(board, &chess_coords);
if (tile == NULL) {
return false;
}
if (tile->piece.type != NoPiece) {
return false;
}
start_l_idx = left_diag ? start_l_idx - 1 : start_l_idx + 1;
}
return true;
}
/*
* Removes en passant'd pawn and resets player's en passant flag.
*
* Should be called after every successful move.
*/
static void chess_player_check_en_passant(Player *player)
2020-12-19 21:40:00 +01:00
{
if (player->en_passant_move_number == -1) {
chess_capture_piece(player, &player->en_passant->piece);
2020-12-19 21:40:00 +01:00
player->en_passant = NULL;
}
player->en_passant_move_number = 0;
}
/*
* Flags a pawn at `to` for a possible en passant take for other player.
*/
static void chess_pawn_en_passant_flag(ChessState *state, Tile *to)
{
Player *other = chess_get_other_player(state);
other->en_passant = to;
other->en_passant_move_number = state->move_number;
}
/*
* Return true and flag opposing pawn to be removed if `to` is a valid en passant move.
*/
static bool chess_pawn_en_passant_move(ChessState *state, Player *player, const Tile *to)
{
if (player->en_passant == NULL || player->en_passant_move_number <= 0) {
return false;
}
int delta = 0;
if (player->colour == White) {
if (player->en_passant_move_number != state->move_number) {
return false;
}
delta = 1;
} else {
if (player->en_passant_move_number != state->move_number - 1) {
return false;
}
delta = -1;
}
if (player->en_passant->piece.type == Pawn && to->chess_coords.N == player->en_passant->chess_coords.N + delta
&& to->chess_coords.L == player->en_passant->chess_coords.L) {
player->en_passant_move_number = -1; // flag opponent's pawn to be removed after move is validated
return true;
}
return false;
}
static bool chess_valid_pawn_move(ChessState *state, Tile *from, Tile *to)
{
Board *board = &state->board;
Piece from_piece = from->piece;
Piece to_piece = to->piece;
// Can't go backwards
if (from_piece.colour == Black && from->chess_coords.N <= to->chess_coords.N) {
return false;
}
if (from_piece.colour == White && from->chess_coords.N >= to->chess_coords.N) {
return false;
}
int l_diff;
int n_diff;
if (chess_get_chess_coords_diff(from, to, &l_diff, &n_diff) == -1) {
return false;
}
// can't move more than two spaces forward or one space diagonally
if (n_diff < 1 || n_diff > 2 || l_diff > 1) {
return false;
}
// If moving two spaces vertically it must be from starting position
if (n_diff == 2) {
if (l_diff != 0) {
return false;
}
if (from_piece.colour == Black && from->chess_coords.N != 7) {
return false;
}
if (from_piece.colour == White && from->chess_coords.N != 2) {
return false;
}
if (to_piece.type != NoPiece) {
return false;
}
}
// if moving diagonally, to square must contain an enemy piece or have a valid en passant
if (l_diff == 1) {
Player *self = chess_get_player_to_move(state);
if (chess_pawn_en_passant_move(state, self, to)) {
return true;
}
return to_piece.type != NoPiece && to_piece.colour != from->piece.colour;
2020-12-19 21:40:00 +01:00
}
if (to_piece.type != NoPiece) {
return false;
}
bool ret = chess_path_line_clear(board, from, to, l_diff, n_diff);
if (ret && n_diff == 2) {
chess_pawn_en_passant_flag(state, to);
}
return ret;
}
static bool chess_valid_rook_move(Board *board, const Tile *from, const Tile *to)
{
int l_diff;
int n_diff;
if (chess_get_chess_coords_diff(from, to, &l_diff, &n_diff) == -1) {
return false;
}
if (!chess_path_line_clear(board, from, to, l_diff, n_diff)) {
return false;
}
if (!chess_piece_can_occupy_tile(&from->piece, to)) {
return false;
}
if (from->chess_coords.N != to->chess_coords.N && from->chess_coords.L != to->chess_coords.L) {
return false;
}
return true;
}
static bool chess_valid_knight_move(const Tile *from, const Tile *to)
{
if (!chess_piece_can_occupy_tile(&from->piece, to)) {
return false;
}
int l_diff;
int n_diff;
if (chess_get_chess_coords_diff(from, to, &l_diff, &n_diff) == -1) {
return false;
}
if (!((l_diff == 2 && n_diff == 1) || (l_diff == 1 && n_diff == 2))) {
return false;
}
return true;
}
static bool chess_valid_bishop_move(Board *board, const Tile *from, const Tile *to)
{
if (!chess_piece_can_occupy_tile(&from->piece, to)) {
return false;
}
int l_diff;
int n_diff;
if (chess_get_chess_coords_diff(from, to, &l_diff, &n_diff) == -1) {
return false;
}
return chess_path_diagonal_clear(board, from, to, l_diff, n_diff);
}
static bool chess_valid_queen_move(Board *board, const Tile *from, const Tile *to)
{
if (!chess_piece_can_occupy_tile(&from->piece, to)) {
return false;
}
int l_diff;
int n_diff;
if (chess_get_chess_coords_diff(from, to, &l_diff, &n_diff) == -1) {
return false;
}
if (l_diff != 0 && n_diff != 0) {
return chess_path_diagonal_clear(board, from, to, l_diff, n_diff);
}
return chess_path_line_clear(board, from, to, l_diff, n_diff);
}
static bool chess_valid_king_move(const Tile *from, const Tile *to)
{
if (!chess_piece_can_occupy_tile(&from->piece, to)) {
return false;
}
int l_diff;
int n_diff;
if (chess_get_chess_coords_diff(from, to, &l_diff, &n_diff) == -1) {
return false;
}
if (l_diff > 1 || n_diff > 1) {
return false;
}
return true;
}
static bool chess_piece_attacking_square(ChessState *state, ChessColour colour, Tile *to);
/*
* Return true if `player` is in check.
*/
static bool chess_player_in_check(ChessState *state, const Player *player)
{
Board *board = &state->board;
for (size_t i = 0; i < CHESS_SQUARES; ++i) {
Tile *tile = &board->tiles[i];
if (tile->piece.type == King && tile->piece.colour == player->colour) {
return chess_piece_attacking_square(state, player->colour == Black ? White : Black, tile);
}
}
return false;
}
/*
* Makes a mock move on the board and tests if it puts the player in check.
*
* Return true if move is valid.
*
* This function assumes that the legality of the move has already been checked.
*/
static bool chess_mock_move_valid(ChessState *state, const Player *player, Tile *from, Tile *to)
{
Board *board = &state->board;
const bool en_passant = player->en_passant_move_number == -1;
bool in_check = false;
Tile *ep_tile = NULL;
Piece ep_piece;
memset(&ep_piece, 0, sizeof(ep_piece));
if (en_passant) { // remove piece that was captured via en passant
ChessCoords ep_coords;
ep_coords.N = player->colour == White ? to->chess_coords.N - 1 : to->chess_coords.N + 1;
ep_coords.L = to->chess_coords.L;
ep_tile = chess_get_tile_at_chess_coords(board, &ep_coords);
if (ep_tile == NULL) {
return false;
}
chess_copy_piece(&ep_piece, &ep_tile->piece);
ep_tile->piece.type = NoPiece; // temporarily remove piece
}
Piece from_piece;
Piece to_piece;
chess_copy_piece(&from_piece, &from->piece);
chess_copy_piece(&to_piece, &to->piece);
chess_copy_piece(&to->piece, &from->piece);
from->piece.type = NoPiece;
2021-06-28 19:54:25 +02:00
in_check = chess_player_in_check(state, player);
from->piece.type = from_piece.type;
chess_copy_piece(&to->piece, &to_piece);
if (en_passant) {
ep_tile->piece.type = ep_piece.type; // reset piece to original state
}
return !in_check;
}
2020-12-19 21:40:00 +01:00
/*
* Return 1 if we can legally move `from` to `to`.
* Return 0 if move is legal but we're in check.
* Return -1 if move is not legal.
*
* If `player` is null we don't check if move puts player in check.
*
* This function should not modify the game state.
2020-12-19 21:40:00 +01:00
*/
static int chess_valid_move(ChessState *state, const Player *player, Tile *from, Tile *to)
{
if (chess_chess_coords_overlap(&from->chess_coords, &to->chess_coords)) {
return false;
}
bool valid = false;
Board *board = &state->board;
switch (from->piece.type) {
case Pawn:
valid = chess_valid_pawn_move(state, from, to);
break;
case Rook:
valid = chess_valid_rook_move(board, from, to);
break;
case Knight:
valid = chess_valid_knight_move(from, to);
break;
case Bishop:
valid = chess_valid_bishop_move(board, from, to);
break;
case Queen:
valid = chess_valid_queen_move(board, from, to);
break;
case King:
valid = chess_valid_king_move(from, to);
break;
default:
valid = false;
break;
}
int ret = valid ? 1 : -1;
if (player != NULL && valid) {
if (!chess_mock_move_valid(state, player, from, to)) {
2020-12-19 21:40:00 +01:00
ret = 0;
}
}
return ret;
}
/*
* Return true if any piece of `colour` is attacking tile designated by `to`.
*/
static bool chess_piece_attacking_square(ChessState *state, ChessColour colour, Tile *to)
{
Board *board = &state->board;
for (size_t i = 0; i < CHESS_SQUARES; ++i) {
Tile *from = &board->tiles[i];
if (from->piece.colour != colour || from->piece.type == NoPiece) {
continue;
}
// We only need to know if a piece has line of sight so we don't
// care if the move puts the player in check
2020-12-19 21:40:00 +01:00
if (chess_valid_move(state, NULL, from, to) == 1) {
return true;
}
}
return false;
}
/*
* Disables castling if king or rook moves.
*/
static void chess_player_set_can_castle(Player *player, const Tile *tile)
{
if (!player->can_castle_ks && !player->can_castle_qs) {
return;
}
if (tile->piece.type == King) {
player->can_castle_ks = false;
player->can_castle_qs = false;
return;
}
if (tile->piece.type != Rook) {
return;
}
if ((player->colour == White && tile->chess_coords.N != 1) || (player->colour == Black && tile->chess_coords.N != 8)) {
return;
}
if (tile->chess_coords.L == 'a') {
player->can_castle_qs = false;
} else if (tile->chess_coords.L == 'h') {
player->can_castle_ks = false;
}
}
/*
* Attempts to castle king for `player`.
*
* Return true if successfully castled.
*/
static bool chess_player_castle(ChessState *state, Player *player, Tile *from, Tile *to)
2020-12-19 21:40:00 +01:00
{
if (!player->can_castle_ks && !player->can_castle_qs) {
return false;
}
Board *board = &state->board;
if (!(from->piece.type == King && to->piece.type == NoPiece)) {
2020-12-19 21:40:00 +01:00
return false;
}
int l_diff;
int n_diff;
if (chess_get_chess_coords_diff(from, to, &l_diff, &n_diff) == -1) {
2020-12-19 21:40:00 +01:00
return false;
}
if (!(l_diff == 2 && n_diff == 0)) {
return false;
}
ChessCoords coords;
coords.N = to->chess_coords.N;
bool queen_side = false;
Tile *rook_to_tile = NULL;
if (to->chess_coords.L == 'g') {
if (!player->can_castle_ks) {
return false;
}
coords.L = 'f';
rook_to_tile = chess_get_tile_at_chess_coords(board, &coords);
if (rook_to_tile == NULL) {
return false;
}
if (rook_to_tile->piece.type != NoPiece) {
return false;
}
} else if (to->chess_coords.L == 'c') {
if (!player->can_castle_qs) {
return false;
}
coords.L = 'd';
rook_to_tile = chess_get_tile_at_chess_coords(board, &coords);
coords.L = 'b';
const Tile *tmp_b = chess_get_tile_at_chess_coords(board, &coords);
if (rook_to_tile == NULL || tmp_b == NULL) {
return false;
}
if (!(rook_to_tile->piece.type == NoPiece && tmp_b->piece.type == NoPiece)) {
return false;
}
queen_side = true;
} else {
return false;
}
ChessColour other_colour = player->colour == Black ? White : Black;
// Make sure a piece isn't attacking either square the king traverses
if (chess_piece_attacking_square(state, other_colour, rook_to_tile)) {
return false;
}
if (chess_piece_attacking_square(state, other_colour, to)) {
return false;
}
Tile *rook_from_tile = NULL;
// move rook
coords.L = queen_side ? 'a' : 'h';
rook_from_tile = chess_get_tile_at_chess_coords(board, &coords);
if (rook_from_tile == NULL || rook_from_tile->piece.type != Rook) {
return false;
}
chess_copy_piece(&rook_to_tile->piece, &rook_from_tile->piece);
chess_set_piece(&rook_from_tile->piece, NoPiece, White);
// move king
Piece old_king;
chess_copy_piece(&old_king, &to->piece);
chess_copy_piece(&to->piece, &from->piece);
2020-12-19 21:40:00 +01:00
chess_set_piece(&from->piece, NoPiece, White);
2020-12-19 21:40:00 +01:00
player->holding_tile = NULL;
if (chess_player_in_check(state, player)) {
chess_copy_piece(&to->piece, &old_king);
chess_set_piece(&rook_to_tile->piece, NoPiece, White);
chess_set_piece(&rook_from_tile->piece, Rook, player->colour);
chess_set_piece(&from->piece, King, player->colour);
2020-12-19 21:40:00 +01:00
return false;
}
player->can_castle_qs = false;
player->can_castle_ks = false;
return true;
}
static void chess_update_state(ChessState *state, Player *self, Player *other, const Tile *from, const Tile *to)
2020-12-19 21:40:00 +01:00
{
chess_player_check_en_passant(self);
2020-12-19 21:40:00 +01:00
self->in_check = false;
2021-06-28 19:54:25 +02:00
other->in_check = chess_player_in_check(state, other);
state->message_length = 0;
state->black_to_move ^= 1;
if (state->black_to_move) {
++state->move_number;
2020-12-19 21:40:00 +01:00
}
chess_print_move_notation(state, from, to, other->in_check);
2020-12-19 21:40:00 +01:00
}
/*
* Attempts to make opponent's move.
*
* Return 0 on success.
* Return -1 on failure.
*
* This function shouldn't fail unless opponent is doing something fishy or there's a bug somewhere.
* On failure the game will abort and the player who made an invalid move will lose the game.
*/
static int chess_try_move_opponent(ChessState *state, Tile *from, Tile *to)
2020-12-19 21:40:00 +01:00
{
Player *opponent = &state->other;
2020-12-19 21:40:00 +01:00
if (!chess_player_to_move(state, opponent)) {
return -1;
}
Tile from_orig;
memcpy(&from_orig, from, sizeof(Tile));
Tile to_orig;
memcpy(&to_orig, to, sizeof(Tile));
int valid = chess_valid_move(state, opponent, from, to);
if (valid != 1) {
if (!opponent->in_check && chess_player_castle(state, opponent, from, to)) {
chess_set_piece(&from->piece, NoPiece, White);
goto on_success;
}
return -1;
}
if (to->piece.type != NoPiece) {
chess_capture_piece(opponent, &to->piece);
}
chess_copy_piece(&to->piece, &from->piece);
chess_player_set_can_castle(opponent, &from_orig);
chess_set_piece(&from->piece, NoPiece, White);
// check if we need to promote pawn to queen
if (from_orig.piece.type == Pawn) {
if (to->chess_coords.N == 1 || to->chess_coords.N == 8) {
chess_set_piece(&to->piece, Queen, to->piece.colour);
}
}
on_success:
chess_update_state(state, opponent, &state->self, &from_orig, &to_orig);
return 0;
}
static void chess_try_move_self(const GameData *game, ChessState *state, Player *self)
{
if (!chess_player_to_move(state, self)) {
return;
}
Tile *to_tile = chess_get_tile(state, state->curs_x, state->curs_y);
if (to_tile == NULL) {
2020-12-19 21:40:00 +01:00
return;
}
Tile *holding_tile = self->holding_tile;
2020-12-19 21:40:00 +01:00
if (holding_tile == NULL) {
return;
}
Tile from_orig;
memcpy(&from_orig, holding_tile, sizeof(Tile));
Tile to_orig;
memcpy(&to_orig, to_tile, sizeof(Tile));
if (chess_chess_coords_overlap(&holding_tile->chess_coords, &to_tile->chess_coords)) {
2020-12-19 21:40:00 +01:00
state->message_length = 0;
self->holding_tile = NULL;
2020-12-19 21:40:00 +01:00
return;
}
int valid = chess_valid_move(state, self, holding_tile, to_tile);
2020-12-19 21:40:00 +01:00
if (valid != 1) {
if (!self->in_check && chess_player_castle(state, self, holding_tile, to_tile)) {
if (chess_packet_send_move(game, &from_orig, to_tile) == -1) {
const char *message = "Connection error";
chess_set_status_message(state, message, strlen(message));
return;
}
2020-12-19 21:40:00 +01:00
chess_set_piece(&holding_tile->piece, NoPiece, White);
self->holding_tile = NULL;
2020-12-19 21:40:00 +01:00
goto on_success;
}
self->holding_tile = NULL;
2020-12-19 21:40:00 +01:00
const char *message = valid == -1 ? "Invalid move" : "Invalid move (check)";
chess_set_status_message(state, message, strlen(message));
return;
}
if (chess_packet_send_move(game, &from_orig, to_tile) == -1) {
const char *message = "Failed to move: Connection error";
chess_set_status_message(state, message, strlen(message));
return;
}
2020-12-19 21:40:00 +01:00
if (to_tile->piece.type != NoPiece) {
chess_capture_piece(self, &to_tile->piece);
2020-12-19 21:40:00 +01:00
}
chess_copy_piece(&to_tile->piece, &self->holding_tile->piece);
2020-12-19 21:40:00 +01:00
self->holding_tile = NULL;
2020-12-19 21:40:00 +01:00
chess_set_piece(&holding_tile->piece, NoPiece, White);
chess_player_set_can_castle(self, &from_orig);
2020-12-19 21:40:00 +01:00
// check if we need to promote pawn to queen
if (from_orig.piece.type == Pawn) {
if (to_tile->chess_coords.N == 1 || to_tile->chess_coords.N == 8) {
chess_set_piece(&to_tile->piece, Queen, to_tile->piece.colour);
}
2020-12-19 21:40:00 +01:00
}
on_success:
chess_update_state(state, self, &state->other, &from_orig, &to_orig);
2020-12-19 21:40:00 +01:00
}
static void chess_pick_up_piece(ChessState *state, Player *player)
{
if (!chess_player_to_move(state, player)) {
return;
}
2020-12-19 21:40:00 +01:00
Tile *tile = chess_get_tile(state, state->curs_x, state->curs_y);
if (tile == NULL) {
return;
}
if (tile->piece.type == NoPiece) {
return;
}
if (tile->piece.colour != player->colour) {
return;
}
player->holding_tile = tile;
}
/*
* Return 0 if `player` does not have sufficient material to check mate his opponent.
* Return 1 if `player` has sufficient material.
* Return 2 if `player` has sufficient material but no major or minor pieces.
*/
static int chess_player_can_mate(ChessState *state, const Player *player)
{
Board *board = &state->board;
size_t bishop_or_knight = 0;
bool has_pawn = false;
for (size_t i = 0; i < CHESS_SQUARES; ++i) {
Tile tile = board->tiles[i];
if (tile.piece.type == NoPiece || tile.piece.colour != player->colour) {
continue;
}
if (tile.piece.type == Queen || tile.piece.type == Rook) {
return 1;
}
if (tile.piece.type == Bishop || tile.piece.type == Knight) {
if (++bishop_or_knight > 1) {
return 1;
}
}
if (tile.piece.type == Pawn) {
has_pawn = true;
if (bishop_or_knight > 0) {
return 1;
}
}
}
return has_pawn ? 2 : 0;
}
/*
* Return true if piece on `from` tile can move to any other square on the board.
*/
static bool chess_piece_can_move(ChessState *state, const Player *player, Tile *from)
{
Board *board = &state->board;
for (size_t i = 0; i < CHESS_SQUARES; ++i) {
Tile *to = &board->tiles[i];
if (chess_valid_move(state, player, from, to) == 1) {
return true;
}
}
return false;
}
/*
* Return true if any piece for `player` can make a legal move.
*/
static bool chess_any_piece_can_move(ChessState *state, const Player *player)
{
Board *board = &state->board;
for (size_t i = 0; i < CHESS_SQUARES; ++i) {
Tile *from = &board->tiles[i];
if (from->piece.colour != player->colour || from->piece.type == NoPiece) {
continue;
}
if (chess_piece_can_move(state, player, from)) {
return true;
}
}
return false;
}
/*
* Return true if game is in stalemate.
*
* A game is considered to be in stalemate if neither side has sufficient material to checkmake
* the opponent's king, or if current turn's player is unable to move any pieces and is not in check.
*/
static bool chess_game_is_statemate(ChessState *state)
{
const Player *self = chess_get_player_to_move(state);
const Player *other = chess_get_other_player(state);
if (self->in_check || other->in_check) {
return false;
}
int self_can_mate = chess_player_can_mate(state, self);
int other_can_mate = chess_player_can_mate(state, other);
if (self_can_mate == 0 && other_can_mate == 0) {
return true;
}
if (self_can_mate == 1) {
return false;
}
// player only has pawns and/or king, see if any remaining piece can move
return !chess_any_piece_can_move(state, self);
}
/*
* Return true if game is in checkmate.
*/
static bool chess_game_checkmate(ChessState *state)
{
const Player *player = chess_get_player_to_move(state);
return !chess_any_piece_can_move(state, player);
2020-12-19 21:40:00 +01:00
}
/*
* Checks if we have a checkmate or stalemate and updates game status.
*/
static void chess_update_status(ChessState *state)
{
if (chess_game_is_statemate(state)) {
state->status = Stalemate;
const char *message = "Stalemate";
2020-12-19 21:40:00 +01:00
chess_set_status_message(state, message, strlen(message));
return;
}
if (chess_game_checkmate(state)) {
state->status = Checkmate;
const char *message = "Checkmate!";
chess_set_status_message(state, message, strlen(message));
return;
}
}
static void chess_do_input(const GameData *game, ChessState *state)
2020-12-19 21:40:00 +01:00
{
if (state->status != Playing) {
2020-12-19 21:40:00 +01:00
return;
}
Player *self = &state->self;
2020-12-19 21:40:00 +01:00
if (self->holding_tile == NULL) {
chess_pick_up_piece(state, self);
2020-12-19 21:40:00 +01:00
} else {
chess_try_move_self(game, state, self);
2020-12-19 21:40:00 +01:00
chess_update_status(state);
}
}
static void chess_move_curs_left(ChessState *state)
{
Board *board = &state->board;
2021-06-02 05:00:00 +02:00
int new_x = state->curs_x - CHESS_TILE_SIZE_X;
2020-12-19 21:40:00 +01:00
if (new_x < board->x_left_bound) {
return;
}
state->curs_x = new_x;
}
static void chess_move_curs_right(ChessState *state)
{
Board *board = &state->board;
2021-06-02 05:00:00 +02:00
int new_x = state->curs_x + CHESS_TILE_SIZE_X;
2020-12-19 21:40:00 +01:00
if (new_x > board->x_right_bound) {
return;
}
state->curs_x = new_x;
}
static void chess_move_curs_up(ChessState *state)
{
Board *board = &state->board;
2021-06-02 05:00:00 +02:00
int new_y = state->curs_y - CHESS_TILE_SIZE_Y;
2020-12-19 21:40:00 +01:00
if (new_y < board->y_top_bound) {
return;
}
state->curs_y = new_y;
}
static void chess_move_curs_down(ChessState *state)
{
Board *board = &state->board;
2021-06-02 05:00:00 +02:00
int new_y = state->curs_y + CHESS_TILE_SIZE_Y;
2020-12-19 21:40:00 +01:00
if (new_y >= board->y_bottom_bound) {
return;
}
state->curs_y = new_y;
}
static int chess_get_display_colour(ChessColour p_colour, int tile_colour)
{
if (tile_colour == CHESS_WHITE_SQUARE_COLOUR) {
if (p_colour == White) {
return BLACK_WHITE; // white square, white piece
} else {
return YELLOW; // white square, black piece
}
}
if (p_colour == White) {
return BLACK_WHITE; // black square, white piece
} else {
return YELLOW; // black square, black piece
}
}
void chess_move_curs_up_left(ChessState *state)
{
chess_move_curs_left(state);
chess_move_curs_up(state);
}
void chess_move_curs_up_right(ChessState *state)
{
chess_move_curs_right(state);
chess_move_curs_up(state);
}
void chess_move_curs_down_left(ChessState *state)
{
chess_move_curs_left(state);
chess_move_curs_down(state);
}
void chess_move_curs_down_right(ChessState *state)
{
chess_move_curs_right(state);
chess_move_curs_down(state);
}
static void chess_draw_board_coords_white(WINDOW *win, const Board *board)
2020-12-19 21:40:00 +01:00
{
for (size_t i = 0; i < CHESS_BOARD_COLUMNS; ++i) {
mvwaddch(win, board->y_bottom_bound, board->x_left_bound + 1 + (i * CHESS_TILE_SIZE_X), Board_Letters[i]);
}
for (size_t i = 0; i < CHESS_BOARD_ROWS; ++i) {
mvwprintw(win, board->y_bottom_bound - 1 - (i * CHESS_TILE_SIZE_Y), board->x_left_bound - 1, "%zu", i + 1);
}
}
static void chess_draw_board_coords_black(WINDOW *win, const Board *board)
2020-12-19 21:40:00 +01:00
{
int l_idx = CHESS_NUM_BOARD_LETTERS - 1;
2020-12-19 21:40:00 +01:00
for (size_t i = 0; i < CHESS_BOARD_COLUMNS && l_idx >= 0; ++i, --l_idx) {
mvwaddch(win, board->y_bottom_bound, board->x_left_bound + 1 + (i * CHESS_TILE_SIZE_X), Board_Letters[l_idx]);
}
int n_idx = CHESS_BOARD_ROWS;
2020-12-19 21:40:00 +01:00
for (size_t i = 0; i < CHESS_BOARD_ROWS && n_idx > 0; ++i, --n_idx) {
mvwprintw(win, board->y_bottom_bound - 1 - (i * CHESS_TILE_SIZE_Y), board->x_left_bound - 1, "%d", n_idx);
2020-12-19 21:40:00 +01:00
}
}
static void chess_draw_board(WINDOW *win, ChessState *state)
{
const Player *player = chess_get_player_to_move(state);
Board *board = &state->board;
for (size_t i = 0; i < CHESS_SQUARES; ++i) {
Tile tile = board->tiles[i];
wattron(win, COLOR_PAIR(tile.colour));
for (size_t x = 0; x < CHESS_TILE_SIZE_X; ++x) {
for (size_t y = 0; y < CHESS_TILE_SIZE_Y; ++y) {
mvwaddch(win, tile.coords.y + y, tile.coords.x + x, ' ');
}
}
wattroff(win, COLOR_PAIR(tile.colour));
// don't draw the piece we're currently holding
if (player->holding_tile != NULL) {
if (chess_chess_coords_overlap(&tile.chess_coords, &player->holding_tile->chess_coords)) {
continue;
}
}
Piece piece = tile.piece;
if (piece.type != NoPiece) {
int colour = chess_get_display_colour(piece.colour, tile.colour);
wattron(win, A_BOLD | COLOR_PAIR(colour));
2020-12-19 21:40:00 +01:00
mvwaddch(win, tile.coords.y, tile.coords.x + 1, piece.display_char);
wattroff(win, A_BOLD | COLOR_PAIR(colour));
2020-12-19 21:40:00 +01:00
}
}
if (state->self.colour == White) {
chess_draw_board_coords_white(win, board);
2020-12-19 21:40:00 +01:00
} else {
chess_draw_board_coords_black(win, board);
2020-12-19 21:40:00 +01:00
}
// if holding a piece draw it at cursor position
if (player->holding_tile != NULL) {
Tile *tile = player->holding_tile;
wattron(win, A_BOLD | COLOR_PAIR(BLACK));
mvwaddch(win, state->curs_y, state->curs_x, tile->piece.display_char);
wattroff(win, A_BOLD | COLOR_PAIR(BLACK));
}
}
static void chess_print_status(WINDOW *win, ChessState *state)
{
const Board *board = &state->board;
wattron(win, A_BOLD);
const Player *player = chess_get_player_to_move(state);
char message[CHESS_MAX_MESSAGE_SIZE + 1];
switch (state->status) {
case Playing: {
snprintf(message, sizeof(message), "%s to move %s", state->black_to_move ? "Black" : "White",
player->in_check ? "(check)" : "");
break;
}
case Initializing: {
snprintf(message, sizeof(message), "Waiting for opponent to connect");
break;
}
case Resigned: {
snprintf(message, sizeof(message), "Opponent resigned");
break;
}
case Stalemate:
/* fallthrough */
case Checkmate: {
const char *score_str = NULL;
if (state->self.in_check) {
score_str = state->self.colour == White ? "0 - 1" : "1 - 0";
} else if (state->other.in_check) {
score_str = state->other.colour == White ? "0 - 1" : "1 - 0";
} else {
score_str = "1/2 - 1/2";
}
snprintf(message, sizeof(message), "%s", score_str);
break;
}
default: {
snprintf(message, sizeof(message), "Invalid game state");
break;
}
}
2020-12-19 21:40:00 +01:00
int x_mid = (board->x_left_bound + (CHESS_TILE_SIZE_X * (CHESS_BOARD_COLUMNS / 2))) - (strlen(message) / 2);
mvwprintw(win, board->y_top_bound - 2, x_mid, "%s", message);
2020-12-19 21:40:00 +01:00
if (state->message_length > 0) {
x_mid = (board->x_left_bound + (CHESS_TILE_SIZE_X * (CHESS_BOARD_COLUMNS / 2))) - (state->message_length / 2);
mvwprintw(win, board->y_bottom_bound + 2, x_mid, "%s", state->status_message);
2020-12-19 21:40:00 +01:00
}
wattroff(win, A_BOLD);
}
static void chess_print_captured(const GameData *game, WINDOW *win, ChessState *state)
{
const Board *board = &state->board;
const Player *self = &state->self;
const Player *other = &state->other;
const int score_diff = self->score - other->score;
const int self_top_y_start = board->y_bottom_bound - (CHESS_TILE_SIZE_Y * 3) + 1;
const int other_top_y_start = board->y_top_bound + 1;
2020-12-19 21:40:00 +01:00
const int left_x_start = board->x_right_bound + 1;
const int right_x_border = game_x_right_bound(game) - 1;
size_t idx = 0;
int self_colour = self->colour == White ? WHITE : YELLOW;
int other_colour = self_colour == YELLOW ? WHITE : YELLOW;
wattron(win, A_BOLD);
if (score_diff > 0) {
wattron(win, COLOR_PAIR(self_colour));
mvwprintw(win, self_top_y_start - 1, left_x_start, "+%d", score_diff);
wattroff(win, COLOR_PAIR(self_colour));
}
wattron(win, COLOR_PAIR(other_colour));
2020-12-19 21:40:00 +01:00
for (size_t y = self_top_y_start; y < board->y_bottom_bound; ++y) {
for (size_t x = left_x_start; x < right_x_border && idx < self->number_captured; x += 2, ++idx) {
Piece piece = self->captured[idx];
mvwaddch(win, y, x, piece.display_char);
}
}
wattroff(win, COLOR_PAIR(other_colour));
2020-12-19 21:40:00 +01:00
idx = 0;
if (score_diff < 0) {
wattron(win, COLOR_PAIR(other_colour));
mvwprintw(win, other_top_y_start - 1, left_x_start, "+%d", abs(score_diff));
wattroff(win, COLOR_PAIR(other_colour));
}
wattron(win, COLOR_PAIR(self_colour));
2020-12-19 21:40:00 +01:00
for (size_t y = other_top_y_start; y < board->y_bottom_bound; ++y) {
for (size_t x = left_x_start; x < right_x_border && idx < other->number_captured; x += 2, ++idx) {
Piece piece = other->captured[idx];
mvwaddch(win, y, x, piece.display_char);
}
}
wattroff(win, A_BOLD | COLOR_PAIR(self_colour));
2020-12-19 21:40:00 +01:00
}
static void chess_draw_interface(const GameData *game, WINDOW *win, ChessState *state)
{
chess_print_status(win, state);
chess_print_captured(game, win, state);
}
void chess_cb_render_window(GameData *game, WINDOW *win, void *cb_data)
{
if (!cb_data) {
return;
}
ChessState *state = (ChessState *)cb_data;
move(state->curs_y, state->curs_x);
curs_set(1);
chess_draw_board(win, state);
chess_draw_interface(game, win, state);
}
void chess_cb_on_keypress(GameData *game, int key, void *cb_data)
{
if (!cb_data) {
return;
}
ChessState *state = (ChessState *)cb_data;
switch (key) {
case KEY_LEFT: {
chess_move_curs_left(state);
break;
}
case KEY_RIGHT: {
chess_move_curs_right(state);
break;
}
case KEY_DOWN: {
chess_move_curs_down(state);
break;
}
case KEY_UP: {
chess_move_curs_up(state);
break;
}
case KEY_HOME: {
chess_move_curs_up_left(state);
break;
}
case KEY_END: {
chess_move_curs_down_left(state);
break;
}
case KEY_PPAGE: {
chess_move_curs_up_right(state);
break;
}
case KEY_NPAGE: {
chess_move_curs_down_right(state);
break;
}
case '\r':
2021-04-22 23:05:04 +02:00
/* Intentional fallthrough */
case ' ': {
chess_do_input(game, state);
2020-12-19 21:40:00 +01:00
break;
}
default: {
return;
}
}
}
void chess_cb_kill(GameData *game, void *cb_data)
{
if (!cb_data) {
return;
}
ChessState *state = (ChessState *)cb_data;
free(state);
chess_packet_send_resign(game);
2020-12-19 21:40:00 +01:00
game_set_cb_render_window(game, NULL, NULL);
game_set_cb_kill(game, NULL, NULL);
game_set_cb_on_keypress(game, NULL, NULL);
game_set_cb_on_packet(game, NULL, NULL);
}
/*
* Attempts to handle opponent's move.
*
* Return 0 on success.
* Return -1 on failure.
*/
#define CHESS_PACKET_MOVE_SIZE 4
static int chess_handle_opponent_move_packet(const GameData *game, ChessState *state, const uint8_t *data,
size_t length)
{
2021-06-28 19:54:25 +02:00
if (length < CHESS_PACKET_MOVE_SIZE || data == NULL) {
return -1;
}
char from_l = data[0];
uint8_t from_n = data[1];
char to_l = data[2];
uint8_t to_n = data[3];
ChessCoords from_coords = (ChessCoords) {
from_l, from_n,
};
ChessCoords to_coords = (ChessCoords) {
to_l, to_n,
};
Board *board = &state->board;
Tile *from_tile = chess_get_tile_at_chess_coords(board, &from_coords);
if (from_tile == NULL) {
return -1;
}
Tile *to_tile = chess_get_tile_at_chess_coords(board, &to_coords);
if (to_tile == NULL) {
return -1;
}
if (chess_try_move_opponent(state, from_tile, to_tile) != 0) {
2021-06-28 19:54:25 +02:00
fprintf(stderr, "Chess opponent tried to make an illegal move: %c%d-%c%d\n", from_l, from_n, to_l, to_n);
return -1;
}
return 0;
}
static void chess_notify(const GameData *game, ChessPacketType type)
{
const char *msg = NULL;
switch (type) {
case CHESS_PACKET_INIT_ACCEPT_INVITE: {
msg = "Game on!";
break;
}
case CHESS_PACKET_RESIGN: {
msg = "Opponent has resigned";
break;
}
case CHESS_PACKET_MOVE_PIECE: {
msg = "Opponent has moved";
break;
}
default: {
return;
}
}
game_window_notify(game, msg);
}
static void chess_cb_on_packet(GameData *game, const uint8_t *data, size_t length, void *cb_data)
{
if (length == 0 || data == NULL) {
return;
}
2021-06-28 19:54:25 +02:00
if (cb_data == NULL) {
return;
}
ChessState *state = (ChessState *)cb_data;
ChessPacketType type = data[0];
switch (type) {
case CHESS_PACKET_INIT_ACCEPT_INVITE: {
if (state->status == Initializing) {
state->status = Playing;
}
break;
}
case CHESS_PACKET_RESIGN: {
if (state->status == Playing) {
state->status = Resigned;
}
break;
}
case CHESS_PACKET_MOVE_PIECE: {
if (state->status == Playing) {
int ret = chess_handle_opponent_move_packet(game, state, data + 1, length - 1);
if (ret != 0) {
state->status = Resigned;
}
chess_update_status(state);
}
break;
}
default: {
fprintf(stderr, "Got unknown chess packet type: %d\n", type);
2021-06-28 19:54:25 +02:00
return;
}
}
chess_notify(game, type);
2020-12-19 21:40:00 +01:00
}
static int chess_init_board(GameData *game, ChessState *state, bool self_is_white)
{
Board *board = &state->board;
const int x_left = game_x_left_bound(game);
const int x_right = game_x_right_bound(game);
const int y_top = game_y_top_bound(game);
const int y_bottom = game_y_bottom_bound(game);
const int x_mid = x_left + ((x_right - x_left) / 2);
const int y_mid = y_top + ((y_bottom - y_top) / 2);
const int board_width = CHESS_TILE_SIZE_X * CHESS_BOARD_COLUMNS;
const int board_height = CHESS_TILE_SIZE_Y * CHESS_BOARD_ROWS;
state->curs_x = x_mid + 1;
state->curs_y = y_mid;
board->x_left_bound = x_mid - (board_width / 2);
board->x_right_bound = x_mid + (board_width / 2);
board->y_bottom_bound = y_mid + (board_height / 2);
board->y_top_bound = y_mid - (board_height / 2);
if (board->y_bottom_bound > y_bottom || board->x_left_bound < x_left) {
return -1;
}
size_t colour_rotation = 1;
size_t board_idx = 0;
size_t letter_idx = self_is_white ? 0 : 7;
for (size_t i = board->x_left_bound; i < board->x_right_bound; i += CHESS_TILE_SIZE_X) {
size_t number_idx = self_is_white ? 8 : 1;
char letter = Board_Letters[letter_idx];
letter_idx = self_is_white ? letter_idx + 1 : letter_idx - 1;
colour_rotation ^= 1;
for (size_t j = board->y_top_bound; j < board->y_bottom_bound; j += CHESS_TILE_SIZE_Y) {
Tile *tile = &board->tiles[board_idx];
tile->colour = (board_idx + colour_rotation) % 2 == 0 ? CHESS_WHITE_SQUARE_COLOUR : CHESS_BLACK_SQUARE_COLOUR;
tile->coords.x = i;
tile->coords.y = j;
tile->chess_coords.L = letter;
tile->chess_coords.N = number_idx;
number_idx = self_is_white ? number_idx - 1 : number_idx + 1;
++board_idx;
}
}
for (size_t i = 0; i < CHESS_SQUARES; ++i) {
Tile *tile = &board->tiles[i];
chess_set_piece(&tile->piece, NoPiece, White);
if (tile->chess_coords.N == 2 || tile->chess_coords.N == 7) {
chess_set_piece(&tile->piece, Pawn, tile->chess_coords.N == 2 ? White : Black);
continue;
}
if (tile->chess_coords.N == 1 || tile->chess_coords.N == 8) {
PieceType type;
switch (tile->chess_coords.L) {
case 'a':
// fallthrough
case 'h':
type = Rook;
break;
case 'b':
// fallthrough
case 'g':
type = Knight;
break;
case 'c':
// fallthrough
case 'f':
type = Bishop;
break;
case 'd':
type = Queen;
break;
case 'e':
type = King;
break;
default:
type = NoPiece;
break;
}
chess_set_piece(&tile->piece, type, tile->chess_coords.N == 1 ? White : Black);
}
}
return 0;
}
static int chess_packet_send_resign(const GameData *game)
{
uint8_t data[1];
data[0] = CHESS_PACKET_RESIGN;
2021-06-28 19:54:25 +02:00
if (game_packet_send(game, data, 1, GP_Data) == -1) {
return -1;
}
return 0;
}
static int chess_packet_send_move(const GameData *game, const Tile *from, const Tile *to)
{
uint8_t data[5];
data[0] = CHESS_PACKET_MOVE_PIECE;
data[1] = from->chess_coords.L;
data[2] = from->chess_coords.N;
data[3] = to->chess_coords.L;
data[4] = to->chess_coords.N;
2021-06-28 19:54:25 +02:00
if (game_packet_send(game, data, 5, GP_Data) == -1) {
return -1;
}
return 0;
}
static int chess_packet_send_invite(const GameData *game, bool self_is_white)
{
uint8_t data[2];
data[0] = CHESS_PACKET_INIT_SEND_INVITE;
data[1] = self_is_white ? Black : White;
2021-06-28 19:54:25 +02:00
if (game_packet_send(game, data, 2, GP_Invite) == -1) {
return -1;
}
return 0;
}
static int chess_packet_send_accept(const GameData *game)
{
uint8_t data[1];
data[0] = CHESS_PACKET_INIT_ACCEPT_INVITE;
2021-06-28 19:54:25 +02:00
if (game_packet_send(game, data, 1, GP_Data) == -1) {
return -1;
}
return 0;
}
int chess_initialize(GameData *game, const uint8_t *init_data, size_t length)
2020-12-19 21:40:00 +01:00
{
if (game_set_window_shape(game, GW_ShapeSquare) == -1) {
return -1;
}
ChessState *state = calloc(1, sizeof(ChessState));
if (state == NULL) {
return -3;
}
bool self_is_host = false;
bool self_is_white = false;
if (length == 0) {
self_is_host = true;
self_is_white = rand() % 2 == 0;
} else {
if (length < 2 || init_data[0] != CHESS_PACKET_INIT_SEND_INVITE) {
free(state);
return -2;
}
ChessColour colour = (ChessColour)init_data[1];
if (colour != White && colour != Black) {
free(state);
return -2;
}
self_is_white = colour == White;
2020-12-19 21:40:00 +01:00
}
state->self.colour = self_is_white ? White : Black;
state->other.colour = !self_is_white ? White : Black;
if (chess_init_board(game, state, self_is_white) == -1) {
free(state);
return -3;
2020-12-19 21:40:00 +01:00
}
state->self.can_castle_ks = true;
state->self.can_castle_qs = true;
state->other.can_castle_ks = true;
state->other.can_castle_qs = true;
if (self_is_host) {
if (chess_packet_send_invite(game, self_is_white) == -1) {
free(state);
return -2;
}
} else {
if (chess_packet_send_accept(game) == -1) {
free(state);
return -2;
}
state->status = Playing;
}
2020-12-19 21:40:00 +01:00
game_set_cb_render_window(game, chess_cb_render_window, state);
game_set_cb_on_keypress(game, chess_cb_on_keypress, state);
game_set_cb_kill(game, chess_cb_kill, state);
game_set_cb_on_packet(game, chess_cb_on_packet, state);
2020-12-19 21:40:00 +01:00
return 0;
}