From 97cb43a2187e99772d5574559f91f5e11c87be53 Mon Sep 17 00:00:00 2001 From: jfreegman Date: Sat, 19 Dec 2020 15:40:00 -0500 Subject: [PATCH] Begin implementing chess --- .gitignore | 1 + Makefile | 6 +- src/game_base.c | 53 +- src/game_base.h | 28 +- src/game_centipede.c | 19 +- src/game_chess.c | 1631 ++++++++++++++++++++++++++++++++++++++++++ src/game_chess.h | 30 + src/game_snake.c | 5 +- src/game_util.h | 4 +- src/toxic.c | 2 +- src/windows.c | 2 +- src/windows.h | 5 +- 12 files changed, 1752 insertions(+), 34 deletions(-) create mode 100644 src/game_chess.c create mode 100644 src/game_chess.h diff --git a/.gitignore b/.gitignore index b1b9759..f4ee2a1 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ build/toxic build/*.o build/*.d apidoc/python/build +*.vim diff --git a/Makefile b/Makefile index b01f21a..894fcc3 100644 --- a/Makefile +++ b/Makefile @@ -13,9 +13,9 @@ LDFLAGS ?= LDFLAGS += ${USER_LDFLAGS} OBJ = autocomplete.o avatars.o bootstrap.o chat.o chat_commands.o configdir.o curl_util.o execute.o -OBJ += file_transfers.o friendlist.o game_base.o game_centipede.o game_util.o game_snake.o global_commands.o conference_commands.o -OBJ += conference.o help.o input.o line_info.o log.o message_queue.o misc_tools.o name_lookup.o notify.o -OBJ += prompt.o qr_code.o settings.o term_mplex.o toxic.o toxic_strings.o windows.o +OBJ += file_transfers.o friendlist.o game_base.o game_centipede.o game_chess.o game_util.o game_snake.o +OBJ += global_commands.o conference_commands.o conference.o help.o input.o line_info.o log.o message_queue.o +OBJ += misc_tools.o name_lookup.o notify.o prompt.o qr_code.o settings.o term_mplex.o toxic.o toxic_strings.o windows.o # Check if debug build is enabled RELEASE := $(shell if [ -z "$(ENABLE_RELEASE)" ] || [ "$(ENABLE_RELEASE)" = "0" ] ; then echo disabled ; else echo enabled ; fi) diff --git a/src/game_base.c b/src/game_base.c index 3bc3df9..be23c78 100644 --- a/src/game_base.c +++ b/src/game_base.c @@ -27,6 +27,7 @@ #include "game_centipede.h" #include "game_base.h" +#include "game_chess.h" #include "game_snake.h" #include "line_info.h" #include "misc_tools.h" @@ -44,7 +45,6 @@ #define GAME_MAX_UPDATE_INTERVAL 50 -#define GAME_BORDER_COLOUR BAR_TEXT /* Determines if window is large enough for a respective window type */ #define WINDOW_SIZE_LARGE_SQUARE_VALID(max_x, max_y)((((max_y) - 4) >= (GAME_MAX_SQUARE_Y))\ @@ -69,6 +69,7 @@ struct GameList { static struct GameList game_list[] = { { "centipede", GT_Centipede }, + { "chess", GT_Chess }, { "snake", GT_Snake }, { NULL, GT_Invalid }, }; @@ -120,7 +121,7 @@ void game_list_print(ToxWindow *self) const char *name = NULL; for (size_t i = 0; (name = game_list[i].name); ++i) { - line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%d: %s", i + 1, name); + line_info_add(self, false, NULL, NULL, SYS_MSG, 0, 0, "%zu: %s", i + 1, name); } } @@ -178,6 +179,11 @@ static int game_initialize_type(GameData *game) break; } + case GT_Chess: { + ret = chess_initialize(game); + break; + } + default: { break; } @@ -495,18 +501,23 @@ static void game_draw_status(const GameData *game, const int max_x, const int ma int y = ((max_y - game->game_max_y) / 2) - 1; wattron(win, A_BOLD); - mvwprintw(win, y, x, "Score: %zu", game->score); - mvwprintw(win, y + game->game_max_y + 2, x, "High Score: %zu", game->high_score); + if (game->show_score) { + mvwprintw(win, y, x, "Score: %ld", game->score); + } + + if (game->show_high_score) { + mvwprintw(win, y + game->game_max_y + 2, x, "High Score: %zu", game->high_score); + } x = ((max_x / 2) + (game->game_max_x / 2)) - 7; - if (game->level > 0) { + if (game->show_level) { mvwprintw(win, y, x, "Level: %zu", game->level); } - if (game->lives >= 0) { - mvwprintw(win, y + game->game_max_y + 2, x, "Lives: %zu", game->lives); + if (game->show_lives) { + mvwprintw(win, y + game->game_max_y + 2, x, "Lives: %d", game->lives); } wattroff(win, A_BOLD); @@ -617,11 +628,11 @@ void game_onDraw(ToxWindow *self, Tox *m) { UNUSED_VAR(m); // Note: This function is not thread safe if we ever need to use `m` - curs_set(0); - game_draw_help_bar(self->window); draw_window_bar(self); + curs_set(0); + GameData *game = self->game; int max_x; @@ -663,8 +674,6 @@ void game_onDraw(ToxWindow *self, Tox *m) } game_draw_messages(game, true); - - wnoutrefresh(self->window); } bool game_onKey(ToxWindow *self, Tox *m, wint_t key, bool is_printable) @@ -830,6 +839,26 @@ int game_x_left_bound(const GameData *game) return ((max_x - game->game_max_x) / 2) + 1; } +void game_show_score(GameData *game, bool show_score) +{ + game->show_score = show_score; +} + +void game_show_high_score(GameData *game, bool show_high_score) +{ + game->show_high_score = show_high_score; +} + +void game_show_lives(GameData *game, bool show_lives) +{ + game->show_lives = show_lives; +} + +void game_show_level(GameData *game, bool show_level) +{ + game->show_level = show_level; +} + void game_update_score(GameData *game, long int points) { game->score += points; @@ -851,7 +880,6 @@ void game_increment_level(GameData *game) void game_update_lives(GameData *game, int lives) { - fprintf(stderr, "%d\n", lives); game->lives += lives; } @@ -912,3 +940,4 @@ void game_set_cb_on_pause(GameData *game, cb_game_pause *func, void *cb_data) game->cb_game_pause = func; game->cb_game_pause_data = cb_data; } + diff --git a/src/game_base.h b/src/game_base.h index 2aae676..678932a 100644 --- a/src/game_base.h +++ b/src/game_base.h @@ -31,6 +31,8 @@ #include "game_util.h" #include "windows.h" +#define GAME_BORDER_COLOUR BAR_TEXT + /* Max size of a default size square game window */ #define GAME_MAX_SQUARE_Y 26 #define GAME_MAX_SQUARE_X (GAME_MAX_SQUARE_Y * 2) @@ -44,7 +46,7 @@ #define GAME_MAX_RECT_X (GAME_MAX_RECT_Y * 4) /* Max size of a small rectangle game window */ -#define GAME_MAX_RECT_Y_SMALL 12 +#define GAME_MAX_RECT_Y_SMALL 14 #define GAME_MAX_RECT_X_SMALL (GAME_MAX_RECT_Y_SMALL * 4) /* Maximum length of a game message set with game_set_message() */ @@ -60,13 +62,13 @@ typedef void cb_game_key_press(GameData *game, int key, void *cb_data); typedef enum GameWindowShape { - GW_ShapeSquare = 0, + GW_ShapeSquare = 0u, GW_ShapeRectangle, GW_ShapeInvalid, } GameWindowShape; typedef enum GameStatus { - GS_None = 0, + GS_None = 0u, GS_Paused, GS_Running, GS_Finished, @@ -74,8 +76,9 @@ typedef enum GameStatus { } GameStatus; typedef enum GameType { - GT_Snake = 0, - GT_Centipede, + GT_Centipede = 0u, + GT_Chess, + GT_Snake, GT_Invalid, } GameType; @@ -103,6 +106,11 @@ struct GameData { GameStatus status; GameType type; + bool show_lives; + bool show_score; + bool show_high_score; + bool show_level; + GameMessage *messages; size_t messages_size; @@ -196,7 +204,7 @@ bool game_coordinates_in_bounds(const GameData *game, int x, int y); void game_random_coords(const GameData *game, Coords *coords); /* - *Gets the current max dimensions of the game window. + * Gets the current max dimensions of the game window. */ void game_max_x_y(const GameData *game, int *x, int *y); @@ -208,6 +216,14 @@ int game_y_top_bound(const GameData *game); int game_x_right_bound(const GameData *game); int game_x_left_bound(const GameData *game); +/* + * Toggle whether the respective game info is shown around the game window. + */ +void game_show_score(GameData *game, bool show_score); +void game_show_high_score(GameData *game, bool show_high_score); +void game_show_lives(GameData *game, bool show_lives); +void game_show_level(GameData *game, bool show_level); + /* * Updates game score. */ diff --git a/src/game_centipede.c b/src/game_centipede.c index 60d74db..1ea4161 100644 --- a/src/game_centipede.c +++ b/src/game_centipede.c @@ -53,7 +53,7 @@ /* Max speed of an enemy agent */ #define CENT_MAX_ENEMY_AGENT_SPEED 8 -/* How often a head that reachest he bottom can repdoduce */ +/* How often a head that reaches the bottom can repdoduce */ #define CENT_REPRODUCE_TIMEOUT 10 #define CENT_CENTIPEDE_DEFAULT_SPEED 5 @@ -194,7 +194,7 @@ typedef struct CentState { #define CENT_LEVEL_COLOURS_SIZE 19 -const static int cent_level_colours[] = { +static const int cent_level_colours[] = { RED, CYAN, MAGENTA, @@ -487,6 +487,8 @@ static int cent_birth_centipede(const GameData *game, CentState *state, size_t l new_head->display_char = CENT_CENTIPEDE_HEAD_CHAR; new_head->prev = NULL; + centipedes->heads[head_idx] = new_head; + Segment *prev = new_head; for (size_t i = 0; i < length; ++i) { @@ -510,8 +512,6 @@ static int cent_birth_centipede(const GameData *game, CentState *state, size_t l prev = new_seg; } - centipedes->heads[head_idx] = new_head; - return 0; } @@ -1077,8 +1077,7 @@ static void cent_set_head_direction(CentState *state, Segment *head, int y_botto } /* - * If a head has reached the bottom it reproduces (spawns an additional head) every time it - * makes two round-trips across the screen. + * If a head has reached the bottom it reproduces (spawns an additional head) on a timer. */ static void cent_do_reproduce(const GameData *game, CentState *state, Segment *head, int x_right, int x_left, int y_bottom) @@ -1510,6 +1509,10 @@ void cent_cb_update_game_state(GameData *game, void *cb_data) CentState *state = (CentState *)cb_data; + if (state->game_over) { + return; + } + TIME_MS cur_time = get_time_millis(); cent_blaster_collision_check(game, state); @@ -1720,6 +1723,10 @@ int centipede_initialize(GameData *game) return -1; } + game_show_level(game, true); + game_show_score(game, true); + game_show_lives(game, true); + game_show_high_score(game, true); game_increment_level(game); game_set_update_interval(game, 10); diff --git a/src/game_chess.c b/src/game_chess.c new file mode 100644 index 0000000..ab820a3 --- /dev/null +++ b/src/game_chess.c @@ -0,0 +1,1631 @@ +/* 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 . + * + */ + +#include +#include +#include + +#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 struct ChessCoords { + char L; + int N; +} ChessCoords; + +typedef enum ChessColour { + White = 0u, + Black, +} ChessColour; + +typedef enum ChessStatus { + Playing = 0u, + Checkmate, + Stalemate, +} 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; +} 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 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; +} + +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 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; +} + +/* + * 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; + } +} + +/* + * 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; +} + +/* + * 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(from->chess_coords.N - to->chess_coords.N); + + return 0; +} + +/* + * 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; + 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; + + // 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_clear_en_passant(Player *player) +{ + if (player->en_passant_move_number == -1) { + chess_set_piece(&player->en_passant->piece, NoPiece, White); + 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; + } + + bool ret = to_piece.type != NoPiece && to_piece.colour != from->piece.colour; + + if (ret && (to->chess_coords.N == 1 || to->chess_coords.N == 8)) { // promote to queen + chess_set_piece(&from->piece, Queen, from->piece.colour); + } + + return ret; + } + + 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); + } else if (ret && (to->chess_coords.N == 1 || to->chess_coords.N == 8)) { + chess_set_piece(&from->piece, Queen, from->piece.colour); + } + + 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; +} + +/* + * Copies `piece_b` to `piece_a`. + */ +static void chess_copy_piece(Piece *piece_a, Piece *piece_b) +{ + memcpy(piece_a, piece_b, sizeof(Piece)); +} + +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; +} + +/* + * 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. + */ +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; + bool is_pawn = false; + + Board *board = &state->board; + + switch (from->piece.type) { + case Pawn: + is_pawn = true; + 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; + + // make mock move and see if we're in check + if (player != NULL && valid) { + 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; + + if (chess_player_in_check(state, player)) { + ret = 0; + } + + from->piece.type = from_piece.type; + chess_copy_piece(&to->piece, &to_piece); + + // if we promoted a pawn and we're in check we have to turn it back into a pawn + // TODO: Maybe validating functions shouldn't be allowed to modify the game state + if (ret == 0 && is_pawn && from->piece.type == Queen) { + chess_set_piece(&from->piece, Pawn, player->colour); + } + } + + 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; + } + + 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 *to) +{ + if (!player->can_castle_ks && !player->can_castle_qs) { + return false; + } + + Board *board = &state->board; + + Tile *holding_tile = player->holding_tile; + + if (holding_tile == NULL) { + return false; + } + + if (!(holding_tile->piece.type == King && to->piece.type == NoPiece)) { + return false; + } + + int l_diff; + int n_diff; + + if (chess_get_chess_coords_diff(holding_tile, to, &l_diff, &n_diff) == -1) { + 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, &holding_tile->piece); + + chess_set_piece(&holding_tile->piece, NoPiece, White); + 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(&holding_tile->piece, King, player->colour); + return false; + } + + player->can_castle_qs = false; + player->can_castle_ks = false; + + return true; +} + +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; + } +} + +static void chess_try_move_piece(ChessState *state, Player *player) +{ + Tile *tile = chess_get_tile(state, state->curs_x, state->curs_y); + + if (tile == NULL) { + return; + } + + Tile *holding_tile = player->holding_tile; + + if (holding_tile == NULL) { + return; + } + + if (chess_chess_coords_overlap(&holding_tile->chess_coords, &tile->chess_coords)) { + state->message_length = 0; + player->holding_tile = NULL; + return; + } + + int valid = chess_valid_move(state, player, holding_tile, tile); + + if (valid != 1) { + if (!player->in_check && chess_player_castle(state, player, tile)) { + chess_set_piece(&holding_tile->piece, NoPiece, White); + goto on_success; + } + + player->holding_tile = NULL; + + const char *message = valid == -1 ? "Invalid move" : "Invalid move (check)"; + chess_set_status_message(state, message, strlen(message)); + return; + } + + Tile tmp_holding; + memcpy(&tmp_holding, holding_tile, sizeof(Tile)); + + if (tile->piece.type != NoPiece) { + chess_capture_piece(player, &tile->piece); + } + + chess_copy_piece(&tile->piece, &player->holding_tile->piece); + + player->holding_tile = NULL; + chess_set_piece(&holding_tile->piece, NoPiece, White); + + chess_player_set_can_castle(player, &tmp_holding); + +on_success: + chess_player_clear_en_passant(player); + + player->in_check = false; + + Player *other = chess_get_other_player(state); + + if (chess_player_in_check(state, other)) { + other->in_check = true; + } + + state->message_length = 0; + state->black_to_move ^= 1; + + if (state->black_to_move) { + ++state->move_number; + } +} + +static void chess_pick_up_piece(ChessState *state, Player *player) +{ + 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 *self = chess_get_player_to_move(state); + + return !chess_any_piece_can_move(state, self); +} + +/* + * 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 = "Game over: Stalemate"; + 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(ChessState *state) +{ + if (state->status == Checkmate || state->status == Stalemate) { + return; + } + + Player *player = chess_get_player_to_move(state); + + if (player->holding_tile == NULL) { + chess_pick_up_piece(state, player); + } else { + chess_try_move_piece(state, player); + chess_update_status(state); + } +} + +static void chess_move_curs_left(ChessState *state) +{ + Board *board = &state->board; + + size_t new_x = state->curs_x - CHESS_TILE_SIZE_X; + + 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; + + size_t new_x = state->curs_x + CHESS_TILE_SIZE_X; + + 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; + + size_t new_y = state->curs_y - CHESS_TILE_SIZE_Y; + + 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; + + size_t new_y = state->curs_y + CHESS_TILE_SIZE_Y; + + 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 + } +} + +static void chess_draw_board_white(WINDOW *win, const Board *board) +{ + 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_black(WINDOW *win, const Board *board) +{ + size_t l_idx = CHESS_NUM_BOARD_LETTERS - 1; + + 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]); + } + + size_t n_idx = CHESS_BOARD_ROWS; + + 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, "%zu", n_idx); + } +} + +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, COLOR_PAIR(colour)); + mvwaddch(win, tile.coords.y, tile.coords.x + 1, piece.display_char); + wattroff(win, COLOR_PAIR(colour)); + } + } + + + if (state->self.colour == White) { + chess_draw_board_white(win, board); + } else { + chess_draw_board_black(win, board); + } + + // 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]; + snprintf(message, sizeof(message), "%s to move %s", state->black_to_move ? "Black" : "White", + player->in_check ? "(check)" : ""); + + 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, message); + + 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, state->status_message); + } + + 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; + + int self_top_y_start = board->y_bottom_bound - (CHESS_TILE_SIZE_Y * 3); + int other_top_y_start = board->y_top_bound; + + 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 colour = self->colour == White ? YELLOW : WHITE; + + wattron(win, COLOR_PAIR(colour)); + + 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(colour)); + + colour = colour == YELLOW ? WHITE : YELLOW; + idx = 0; + + wattron(win, COLOR_PAIR(colour)); + + 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, COLOR_PAIR(colour)); +} + +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 '\r': { + chess_do_input(state); + break; + } + + default: { + return; + } + } +} + +void chess_cb_kill(GameData *game, void *cb_data) +{ + if (!cb_data) { + return; + } + + ChessState *state = (ChessState *)cb_data; + + free(state); + + game_set_cb_render_window(game, NULL, NULL); + game_set_cb_kill(game, NULL, NULL); +} + +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; +} + +int chess_initialize(GameData *game) +{ + if (game_set_window_shape(game, GW_ShapeSquare) == -1) { + return -1; + } + + ChessState *state = calloc(1, sizeof(ChessState)); + + if (state == NULL) { + return -1; + } + + bool self_is_white = rand() % 2 == 0; + 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 -1; + } + + state->self.can_castle_ks = true; + state->self.can_castle_qs = true; + state->other.can_castle_ks = true; + state->other.can_castle_qs = true; + + game_set_update_interval(game, 20); + + 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); + + return 0; +} + diff --git a/src/game_chess.h b/src/game_chess.h new file mode 100644 index 0000000..1ec8fec --- /dev/null +++ b/src/game_chess.h @@ -0,0 +1,30 @@ +/* game_chess.h + * + * + * 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 . + * + */ + +#ifndef GAME_CHESS +#define GAME_CHESS + +#include "game_base.h" + +int chess_initialize(GameData *game); + +#endif // GAME_CHESS diff --git a/src/game_snake.c b/src/game_snake.c index 3765cff..b3a547a 100644 --- a/src/game_snake.c +++ b/src/game_snake.c @@ -886,7 +886,10 @@ int snake_initialize(GameData *game) state->last_powerup_time = get_unix_time(); - game_update_lives(game, -1); + game_show_level(game, true); + game_show_score(game, true); + game_show_high_score(game, true); + game_increment_level(game); game_set_update_interval(game, SNAKE_DEFAULT_UPDATE_INTERVAL); game_random_coords(game, &state->food); diff --git a/src/game_util.h b/src/game_util.h index 5ad3fc8..44b30e6 100644 --- a/src/game_util.h +++ b/src/game_util.h @@ -32,7 +32,7 @@ typedef struct Coords { // don't change these typedef enum Direction { - NORTH = 0, + NORTH = 0u, SOUTH = 1, EAST = 3, WEST = 4, @@ -60,7 +60,7 @@ typedef time_t TIME_S; /* * Return true if dir is a valid Direction. */ -#define GAME_UTIL_DIRECTION_VALID(dir)(((dir) >= 0) && ((dir) < (INVALID_DIRECTION))) +#define GAME_UTIL_DIRECTION_VALID(dir)((dir) < (INVALID_DIRECTION)) /* * Returns cardinal direction mapped to `key`. diff --git a/src/toxic.c b/src/toxic.c index f3963e0..99dd0bd 100644 --- a/src/toxic.c +++ b/src/toxic.c @@ -376,10 +376,10 @@ static void init_term(void) init_pair(YELLOW, COLOR_YELLOW, bg_color); init_pair(MAGENTA, COLOR_MAGENTA, bg_color); init_pair(BLACK, COLOR_BLACK, COLOR_BLACK); - init_pair(BLUE_BLACK, COLOR_BLUE, COLOR_BLACK); init_pair(WHITE_BLUE, COLOR_WHITE, COLOR_BLUE); init_pair(BLACK_WHITE, COLOR_BLACK, COLOR_WHITE); init_pair(WHITE_BLACK, COLOR_WHITE, COLOR_BLACK); + init_pair(WHITE_GREEN, COLOR_WHITE, COLOR_GREEN); init_pair(BLACK_BG, COLOR_BLACK, bar_bg_color); init_pair(PURPLE_BG, COLOR_MAGENTA, bar_bg_color); init_pair(BAR_TEXT, bar_fg_color, bar_bg_color); diff --git a/src/windows.c b/src/windows.c index 8104a33..bbcc161 100644 --- a/src/windows.c +++ b/src/windows.c @@ -838,7 +838,7 @@ void kill_all_windows(Tox *m) kill_chat_window(w, m); } else if (w->type == WINDOW_TYPE_CONFERENCE) { free_conference(w, w->num); - } else if (w->type == WINDOW_TYPE_GAME) { + } else if (w->type == WINDOW_TYPE_GAME) { game_kill(w); } } diff --git a/src/windows.h b/src/windows.h index 7d8c765..b5bbb5a 100644 --- a/src/windows.h +++ b/src/windows.h @@ -53,10 +53,10 @@ typedef enum { YELLOW, MAGENTA, BLACK, - BLUE_BLACK, BLACK_WHITE, WHITE_BLACK, WHITE_BLUE, + WHITE_GREEN, BAR_TEXT, STATUS_ONLINE, BAR_ACCENT, @@ -290,4 +290,5 @@ void draw_window_bar(ToxWindow *self); call at least once per second */ void refresh_inactive_windows(void); -#endif /* WINDOWS_H */ +#endif // WINWDOWS_H +