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
+