From d898a9b69109ba65cce590643993261424b89d45 Mon Sep 17 00:00:00 2001 From: Ole Morud Date: Sun, 31 Mar 2024 19:29:34 +0200 Subject: [PATCH] Basically working --- Makefile | 8 +- src/chess.c | 1080 ++++++++++++++++++++++++++------------------- src/cool_assert.h | 45 ++ src/map.h | 10 + 4 files changed, 683 insertions(+), 460 deletions(-) create mode 100644 src/cool_assert.h create mode 100644 src/map.h diff --git a/Makefile b/Makefile index 2aa7cbe..083b0cd 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,10 @@ -CC = clang -LD = ld.lld +CC = gcc #CFLAGS += -O0 -ggdb3 CFLAGS += -Ofast -ggdb3 -CFLAGS += -Wall -Wextra -Wno-unused-function +CFLAGS += -Wall -Wextra -Wno-unused-function -rdynamic #CFLAGS += -fsanitize=address -LDFLAGS = -fuse-ld=lld +LDFLAGS = -fuse-ld=lld -rdynamic #LDFLAGS += -fsanitize=address _OBJ = chess.o @@ -36,7 +35,6 @@ bin/chess: $(OBJ) $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^ $(TEST_DIR)/bin/test_%: testing/test_%.c obj/%.o obj/util.o - echo $^ $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^ .PHONY: all clean docs test diff --git a/src/chess.c b/src/chess.c index b130d8e..02e74d8 100644 --- a/src/chess.c +++ b/src/chess.c @@ -1,52 +1,105 @@ -#include +#include + +#include "cool_assert.h" #include /* isalpha, isdigit ... */ #include /* setlocale */ #include +#include #include /* true, false, bool */ #include /* ptrdiff_t */ #include /* int32_t */ #include /* printf, scanf */ -#include /* memcpy */ #include -typedef int32_t piece_t; +#define MAX_DEPTH 4 +#define CHECKMATE_SCORE 100000 + +#define RANK ((index_t)8) +#define COL ((index_t)1) + +typedef int8_t piece_t; typedef ptrdiff_t index_t; typedef uint64_t bitmap_t; -#define MAX_DEPTH 5 // TODO: don't hardcode this -#define N_ELEMS(x) (sizeof x / sizeof x[0]) -#define BOARD_SIZE ((index_t)64) -#define BIT(x) (1UL << (x)) +static const char * const bool_str[] = {"true", "false"}; -#define WHITE ((piece_t)1) -#define BLACK ((piece_t)-1) - -#define EMPTY ((piece_t)0) -#define KING ((piece_t)1) -#define QUEEN ((piece_t)2) -#define ROOK ((piece_t)3) -#define BISHOP ((piece_t)4) -#define KNIGHT ((piece_t)5) -#define PAWN ((piece_t)6) - -#define ROW ((index_t)8) -#define COL ((index_t)1) - -struct game_state { - index_t king_pos[2]; - bool a_rook_touched[2]; - bool h_rook_touched[2]; - bool king_touched[2]; - - int turns_without_captures; - - piece_t board[BOARD_SIZE]; +enum tile { + A1, B1, C1, D1, E1, F1, G1, H1, + A2, B2, C2, D2, E2, F2, G2, H2, + A3, B3, C3, D3, E3, F3, G3, H3, + A4, B4, C4, D4, E4, F4, G4, H4, + A5, B5, C5, D5, E5, F5, G5, H5, + A6, B6, C6, D6, E6, F6, G6, H6, + A7, B7, C7, D7, E7, F7, G7, H7, + A8, B8, C8, D8, E8, F8, G8, H8, + BOARD_SIZE, }; -enum castle_type { - CASTLE_KINGSIDE = 1, - CASTLE_QUEENSIDE = 2, +static const char * const tile_str[BOARD_SIZE] = { + "A1", "B1", "C1", "D1", "E1", "F1", "G1", "H1", + "A2", "B2", "C2", "D2", "E2", "F2", "G2", "H2", + "A3", "B3", "C3", "D3", "E3", "F3", "G3", "H3", + "A4", "B4", "C4", "D4", "E4", "F4", "G4", "H4", + "A5", "B5", "C5", "D5", "E5", "F5", "G5", "H5", + "A6", "B6", "C6", "D6", "E6", "F6", "G6", "H6", + "A7", "B7", "C7", "D7", "E7", "F7", "G7", "H7", + "A8", "B8", "C8", "D8", "E8", "F8", "G8", "H8", +}; + +enum board_rank { + RANK_1 = 0, + RANK_2 = 8, + RANK_3 = 16, + RANK_4 = 24, + RANK_5 = 32, + RANK_6 = 40, + RANK_7 = 48, + RANK_8 = 56, +}; + +enum board_file { + FILE_A = 0, + FILE_B = 1, + FILE_C = 2, + FILE_D = 3, + FILE_E = 4, + FILE_F = 5, + FILE_G = 6, + FILE_H = 7, +}; + +enum color { + BLACK = -1, + WHITE = 1, +}; + +static const char * const color_str[] = { + "WHITE", + "BLACK" +}; + +static const char * const color_str_lower[] = { + "white", + "black" +}; + +static const char * const color_str_capitalized[] = { + "White", + "Black" +}; + +typedef piece_t Board[BOARD_SIZE]; + +enum chess_piece { + EMPTY = 0, + KING = 1, + QUEEN = 2, + ROOK = 3, + BISHOP = 4, + KNIGHT = 5, + PAWN = 6, + PIECE_COUNT, }; static const double piece_value[] = { @@ -59,73 +112,112 @@ static const double piece_value[] = { [KING] = 10, }; -static const double piece_position_bonus[7][BOARD_SIZE * sizeof(double)] = { +static const double piece_position_bonus[PIECE_COUNT][BOARD_SIZE] = { [EMPTY] = {0}, [PAWN] = { - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.4, 1.4, 1.4, 1.4, 1.0, 1.0, - 1.0, 1.0, 1.4, 1.4, 1.4, 1.4, 1.0, 1.0, - 1.2, 1.0, 1.4, 1.4, 1.4, 1.4, 1.0, 1.2, - 1.7, 1.0, 1.4, 1.4, 1.4, 1.4, 1.0, 1.7, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* A B C D E F G H */ + /* 1 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 2 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 3 */ 1.0, 1.0, 1.4, 1.4, 1.4, 1.4, 1.0, 1.0, + /* 4 */ 1.0, 1.0, 1.4, 1.4, 1.4, 1.4, 1.0, 1.0, + /* 5 */ 1.2, 1.0, 1.4, 1.4, 1.4, 1.4, 1.0, 1.2, + /* 6 */ 1.7, 1.0, 1.4, 1.4, 1.4, 1.4, 1.0, 1.7, + /* 7 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 8 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, }, [BISHOP] = { - 1.2, 1.0, 1.0, 1.0, 1.0, 1.0, 1.2, 1.2, - 1.2, 1.2, 1.0, 1.0, 1.0, 1.2, 1.2, 1.0, - 1.0, 1.2, 1.2, 1.0, 1.2, 1.2, 1.0, 1.0, - 1.0, 1.0, 1.2, 1.2, 1.2, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.2, 1.2, 1.2, 1.0, 1.0, 1.0, - 1.0, 1.2, 1.2, 1.0, 1.2, 1.2, 1.0, 1.0, - 1.2, 1.2, 1.0, 1.0, 1.0, 1.2, 1.2, 1.0, - 1.2, 1.0, 1.0, 1.0, 1.0, 1.0, 1.2, 1.2, + /* A B C D E F G H */ + /* 1 */ 1.2, 1.0, 1.0, 1.0, 1.0, 1.0, 1.2, 1.2, + /* 2 */ 1.2, 1.2, 1.0, 1.0, 1.0, 1.2, 1.2, 1.0, + /* 3 */ 1.0, 1.2, 1.2, 1.0, 1.2, 1.2, 1.0, 1.0, + /* 4 */ 1.0, 1.0, 1.2, 1.2, 1.2, 1.0, 1.0, 1.0, + /* 5 */ 1.0, 1.0, 1.2, 1.2, 1.2, 1.0, 1.0, 1.0, + /* 6 */ 1.0, 1.2, 1.2, 1.0, 1.2, 1.2, 1.0, 1.0, + /* 7 */ 1.2, 1.2, 1.0, 1.0, 1.0, 1.2, 1.2, 1.0, + /* 8 */ 1.2, 1.0, 1.0, 1.0, 1.0, 1.0, 1.2, 1.2, }, [KNIGHT] = { - 0.5, 0.7, 0.8, 0.8, 0.8, 0.8, 0.7, 0.5, - 0.6, 0.7, 0.9, 0.9, 0.9, 0.9, 0.7, 0.6, - 0.6, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.6, - 0.6, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.6, - 0.6, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.6, - 0.6, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.6, - 0.6, 0.7, 0.9, 0.9, 0.9, 0.9, 0.7, 0.6, - 0.5, 0.7, 0.8, 0.8, 0.8, 0.8, 0.7, 0.5, + /* A B C D E F G H */ + /* 1 */ 0.5, 0.7, 0.8, 0.8, 0.8, 0.8, 0.7, 0.5, + /* 2 */ 0.6, 0.7, 0.9, 0.9, 0.9, 0.9, 0.7, 0.6, + /* 3 */ 0.6, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.6, + /* 4 */ 0.6, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.6, + /* 5 */ 0.6, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.6, + /* 6 */ 0.6, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.6, + /* 7 */ 0.6, 0.7, 0.9, 0.9, 0.9, 0.9, 0.7, 0.6, + /* 8 */ 0.5, 0.7, 0.8, 0.8, 0.8, 0.8, 0.7, 0.5, }, [ROOK] = { - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* A B C D E F G H */ + /* 1 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 2 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 3 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 4 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 5 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 6 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 7 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 8 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, }, [QUEEN] = { - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* A B C D E F G H */ + /* 1 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 2 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 3 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 4 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 5 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 6 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 7 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 8 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, }, [KING] = { - /*a b c d e f g h*/ - 1.4, 1.4, 1.4, 1.0, 1.0, 1.0, 99, 1.6, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, - 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* A B C D E F G H */ + /* 1 */ 1.1, 1.1, 1.1, 1.0, 1.0, 1.0, 1.15, 1.15, + /* 2 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 3 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 4 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 5 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 6 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 7 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, + /* 8 */ 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, }, }; +enum game_state_attr { + KING_POSITION = (1<<6)-1, // mask of king position + A_ROOK_TOUCHED = 1<<6, + H_ROOK_TOUCHED = 1<<7, + KING_TOUCHED = 1<<8, +}; -#define TILE(col, row) ((col)-'A'+ ROW*((row)-1)) +enum attr_color { + ATTR_WHITE = 0, + ATTR_BLACK = 1, +}; + +inline size_t attr_index(enum color color) { + return color == WHITE ? ATTR_WHITE : ATTR_BLACK; +} + +struct game_state { + Board board; + uint32_t attr[2]; + int last_pawn_double_move_file; + int turns_without_captures; + int turns; + enum color player; +}; +// hacky solution to pass game state to sigint handler +static struct game_state sigint_state_copy; + +enum castle_type { + CASTLE_KINGSIDE = 1, + CASTLE_QUEENSIDE = 2, +}; + +static inline bitmap_t bit(index_t i) +{ + return 1UL << i; +} static inline bool friends(piece_t a, piece_t b) { @@ -148,14 +240,14 @@ static int signum(int t) return -1; } -static inline index_t row(index_t i) +static inline index_t rank(index_t i) { - return i / ROW; + return (i / RANK)*8; } -static inline index_t column(index_t i) +static inline index_t file(index_t i) { - return i % ROW; + return i % RANK; } static inline bool enemies(piece_t a, piece_t b) @@ -171,37 +263,37 @@ static inline piece_t piece_color(piece_t t) static const char * piece_str(piece_t p) { static const char * const table[] = { [EMPTY] = "EMPTY", - [PAWN] = "PAWN", - [BISHOP] = "BISHOP", - [KNIGHT] = "KNIGHT", - [ROOK] = "ROOK", - [QUEEN] = "QUEEN", - [KING] = "KING", + [PAWN] = "-PAWN", + [BISHOP] = "-BISHOP", + [KNIGHT] = "-KNIGHT", + [ROOK] = "-ROOK", + [QUEEN] = "-QUEEN", + [KING] = "-KING", }; - p = piece_abs(p); - if (p >= 0 && (size_t)p < sizeof table / sizeof *table) { - return table[piece_abs(p)]; - } else { - return "UNKNOWN"; - } + const size_t pa = piece_abs(p); + return table[pa] + (piece_color(p) == WHITE ? 1 : 0); } -static void board_paint(struct game_state* g) +static void paint_board(struct game_state* g, index_t highlight1, index_t highlight2) { /* https://en.wikipedia.org/wiki/Chess_symbols_in_Unicode The unicode symbols for the pieces are calculated from adding 0x2653 (#define'd as UNICODE_CHESS_SYMBOL) with the piece value. */ +#define BG_RED() printf("\033[48;2;150;150;0m") #define BG_DARKBLUE() printf("\033[48;2;100;100;150m") #define BG_LIGHTBLUE() printf("\033[48;2;150;150;200m") #define FG_BLACK() printf("\033[38;2;0;0;0m") #define FG_WHITE() printf("\033[38;2;255;255;255m") #define UNICODE_CHESS_SYMBOL 0x2659 - for (ssize_t i = 7; i >= 0; i--) { - //for (ssize_t i = 0; i < 8; i++) { - printf("\n %zu ", i+1); // number coordinates - for (size_t j = 0; j < 8; j++) { + printf("%s's turn", g->player == WHITE ? "White" : "Black"); + for (index_t i = 7; i >= 0; i--) { + //for (index_t i = 0; i < 8; i++) { + printf("\n %ld ", i+1); // number coordinates + for (index_t j = 0; j < 8; j++) { piece_t t = g->board[i * 8 + j]; - if ((i + j) % 2) + if (i*RANK+j == highlight1 || i*RANK+j == highlight2) + BG_RED(); + else if ((i + j) % 2) BG_DARKBLUE(); else BG_LIGHTBLUE(); @@ -222,23 +314,31 @@ static void board_paint(struct game_state* g) printf("\n "); for (int i = 0; i < 8; i++) printf(" %c", 'a' + i); + + printf("\n"); } static void move(struct game_state* g, index_t from, index_t to) { - int p = (piece_color(g->board[from]) == WHITE ? 0 : 1); + static_assert(WHITE == 1, "`WHITE` must match direction of white pawns (1) for move() to work"); + static_assert(BLACK == -1, "`BLACK` must match direction of black pawns (-1) for move() to work"); - if (piece_abs(g->board[from]) == KING) { - g->king_pos[p] = to; - g->king_touched[p] = true; - } else if (from == TILE('A', 8)) { - g->a_rook_touched[0] = true; - } else if (from == TILE('A', 1)) { - g->a_rook_touched[1] = true; - } else if (from == TILE('H', 1)) { - g->h_rook_touched[0] = true; - } else if (from == TILE('H', 8)) { - g->h_rook_touched[1] = true; + const int piece = piece_abs(g->board[from]); + const enum color player = g->player; + const int p = attr_index(player); + + g->turns += 1; + g->player *= -1; + g->last_pawn_double_move_file = -1; + + if (from == A8 || to == A8) { + g->attr[ATTR_BLACK] |= A_ROOK_TOUCHED; + } else if (from == A1 || to == A1) { + g->attr[ATTR_WHITE] |= A_ROOK_TOUCHED; + } else if (from == H1 || to == H1) { + g->attr[ATTR_WHITE] |= H_ROOK_TOUCHED; + } else if (from == H8 || to == H8) { + g->attr[ATTR_BLACK] |= H_ROOK_TOUCHED; } if (g->board[to] == EMPTY) { @@ -247,39 +347,48 @@ static void move(struct game_state* g, index_t from, index_t to) g->turns_without_captures = 0; } - if (piece_abs(g->board[from]) == KING) { - switch (to) { - case TILE('G', 1): - g->board[TILE('E', 1)] = EMPTY; - g->board[TILE('F', 1)] = ROOK; - g->board[TILE('G', 1)] = KING; - g->board[TILE('H', 1)] = EMPTY; - break; - case TILE('C', 1): - g->board[TILE('A', 1)] = EMPTY; - g->board[TILE('B', 1)] = EMPTY; - g->board[TILE('C', 1)] = KING; - g->board[TILE('D', 1)] = ROOK; - g->board[TILE('E', 1)] = EMPTY; - break; - case TILE('G', 8): - g->board[TILE('E', 8)] = EMPTY; - g->board[TILE('F', 8)] = ROOK; - g->board[TILE('G', 8)] = KING; - g->board[TILE('H', 8)] = EMPTY; - break; - case TILE('C', 8): - g->board[TILE('A', 8)] = EMPTY; - g->board[TILE('B', 8)] = EMPTY; - g->board[TILE('C', 8)] = KING; - g->board[TILE('D', 8)] = ROOK; - g->board[TILE('E', 8)] = EMPTY; - break; - default: - g->board[to] = g->board[from]; - g->board[from] = EMPTY; - return; + // castle + if (piece == KING) { + g->attr[p] &= ~KING_POSITION; + g->attr[p] |= to; + g->attr[p] |= KING_TOUCHED; + + // castling + if (player == WHITE && to == G1) { + g->board[F1] = ROOK; + g->board[H1] = EMPTY; + } else if (player == BLACK && to == G8) { + g->board[F8] = -ROOK; + g->board[H8] = EMPTY; + } else if (player == WHITE && to == C1) { + g->board[A1] = EMPTY; + g->board[B1] = EMPTY; + g->board[D1] = ROOK; + } else if (player == BLACK && to == C8) { + g->board[A8] = EMPTY; + g->board[B8] = EMPTY; + g->board[D8] = -ROOK; } + g->board[to] = g->board[from]; + g->board[from] = EMPTY; + return; + } + // en passent + else if (piece == PAWN) { + if (g->last_pawn_double_move_file == file(to)) { + g->board[to-RANK * player] = EMPTY; + } + if (to - from == 2*RANK * player) { + g->last_pawn_double_move_file = file(to); + } + + if (rank(to) == RANK_1 || rank(to) == RANK_8) { + // promotion, TODO: implement other promotions + g->board[to] = player * QUEEN; + } else { + g->board[to] = g->board[from]; + } + g->board[from] = EMPTY; } else { g->board[to] = g->board[from]; g->board[from] = EMPTY; @@ -288,14 +397,13 @@ static void move(struct game_state* g, index_t from, index_t to) static bitmap_t pawn_threatmap(struct game_state* g, index_t index) { - const index_t direction = piece_color(g->board[index]); - const index_t left = BIT(index + ROW*direction - 1); - const index_t right = BIT(index + ROW*direction + 1); + const index_t left = bit(index + RANK*g->player - 1); + const index_t right = bit(index + RANK*g->player + 1); - if (column(index) == column(TILE('A', 1))) + if (file(index) == FILE_A) return right; - if (column(index) == column(TILE('H', 1))) + if (file(index) == FILE_H) return left; return left | right; @@ -305,28 +413,28 @@ static bitmap_t diagonal_threatmap(struct game_state* g, index_t index) { bitmap_t threatened = 0; - //index_t directions[] = { ROW+1, ROW-1, -ROW+1, -ROW-1 }; + //index_t directions[] = { RANK+1, RANK-1, -RANK+1, -RANK-1 }; - for (index_t i = index+ROW+1; i < BOARD_SIZE && column(i-1) != 7; i += ROW+1) { - threatened |= BIT(i); + for (index_t i = index+RANK+1; i < BOARD_SIZE && file(i-1) != 7; i += RANK+1) { + threatened |= bit(i); if (g->board[i] != EMPTY) { break; } } - for (index_t i = index+ROW-1; i < BOARD_SIZE && column(i+1) != 0; i += ROW-1) { - threatened |= BIT(i); + for (index_t i = index+RANK-1; i < BOARD_SIZE && file(i+1) != 0; i += RANK-1) { + threatened |= bit(i); if (g->board[i] != EMPTY) { break; } } - for (index_t i = index-ROW+1; i >= 0 && column(i-1) != 7; i += -ROW+1) { - threatened |= BIT(i); + for (index_t i = index-RANK+1; i >= 0 && file(i-1) != 7; i += -RANK+1) { + threatened |= bit(i); if (g->board[i] != EMPTY) { break; } } - for (index_t i = index-ROW-1; i >= 0 && column(i+1) != 0; i += -ROW-1) { - threatened |= BIT(i); + for (index_t i = index-RANK-1; i >= 0 && file(i+1) != 0; i += -RANK-1) { + threatened |= bit(i); if (g->board[i] != EMPTY) { break; } @@ -338,23 +446,23 @@ static bitmap_t cardinal_threatmap(struct game_state* g, index_t index) { bitmap_t threatened = 0; - for (index_t i = index+ROW; i < BOARD_SIZE; i += ROW) { - threatened |= BIT(i); + for (index_t i = index+RANK; i < BOARD_SIZE; i += RANK) { + threatened |= bit(i); if (g->board[i] != EMPTY) break; } - for (index_t i = index-ROW; i >= 0; i -= ROW) { - threatened |= BIT(i); + for (index_t i = index-RANK; i >= 0; i -= RANK) { + threatened |= bit(i); if (g->board[i] != EMPTY) break; } - for (index_t i = index+1; i < BOARD_SIZE && column(i) != 0; i++) { - threatened |= BIT(i); + for (index_t i = index+1; i < BOARD_SIZE && file(i) != 0; i++) { + threatened |= bit(i); if (g->board[i] != EMPTY) break; } - for (index_t i = index-1; i > 0 && column(i) != 7; i--) { - threatened |= BIT(i); + for (index_t i = index-1; i > 0 && file(i) != 7; i--) { + threatened |= bit(i); if (g->board[i] != EMPTY) break; } @@ -379,27 +487,25 @@ static bitmap_t knight_threatmap(index_t index) //clang-format off index_t knight_wheel[8*2] = { /* x, y */ - 1, 2, - 1, -2, - -1, 2, - -1, -2, - 2, 1, - 2, -1, - -2, 1, - -2, -1 + 1, 2*RANK, + 1, -2*RANK, + -1, 2*RANK, + -1, -2*RANK, + 2, 1*RANK, + 2, -1*RANK, + -2, 1*RANK, + -2, -1*RANK }; // clang-format on - for (size_t i = 0; i < N_ELEMS(knight_wheel); i += 2) { - index_t atks = index + knight_wheel[i] + knight_wheel[i+1] * ROW; - - if (column(index) + knight_wheel[i] < 0 - || column(index) + knight_wheel[i] > 7 - || row(index) + knight_wheel[i+1] < 0 - || row(index) + knight_wheel[i+1] > 7) + for (size_t i = 0; i < sizeof knight_wheel / sizeof knight_wheel[0]; i += 2) { + if (file(index) + knight_wheel[i] < FILE_A + || file(index) + knight_wheel[i] > FILE_H + || rank(index) + knight_wheel[i+1] < RANK_1 + || rank(index) + knight_wheel[i+1] > RANK_8) continue; - threatened |= BIT(atks); + threatened |= bit(index + knight_wheel[i] + knight_wheel[i+1]); } return threatened; @@ -408,37 +514,37 @@ static bitmap_t knight_threatmap(index_t index) static bitmap_t king_threatmap(index_t index) { // I fucking hate this function so much - if (row(index) == 0) { - if (column(index) == 0) { - return BIT(index+1) | BIT(index+ROW+1) | BIT (index+ROW); + if (rank(index) == RANK_1) { + if (file(index) == FILE_A) { + return bit(index+1) | bit(index+RANK+1) | bit (index+RANK); } - else if (column(index) == 7) { - return BIT(index-1) | BIT(index+ROW-1) | BIT (index+ROW); + else if (file(index) == FILE_H) { + return bit(index-1) | bit(index+RANK-1) | bit (index+RANK); } else { - return BIT(index-1) | BIT(index+1) | BIT(index+ROW-1) | BIT(index+ROW) | BIT(index+ROW+1); + return bit(index-1) | bit(index+1) | bit(index+RANK-1) | bit(index+RANK) | bit(index+RANK+1); } } - if (row(index) == 7) { - if (column(index) == 0) { - return BIT(index+1) | BIT(index-ROW+1) | BIT (index-ROW); + if (rank(index) == RANK_8) { + if (file(index) == FILE_A) { + return bit(index+1) | bit(index-RANK+1) | bit (index-RANK); } - else if (column(index) == 7) { - return BIT(index-1) | BIT(index-ROW-1) | BIT (index-ROW); + else if (file(index) == FILE_H) { + return bit(index-1) | bit(index-RANK-1) | bit (index-RANK); } else { - return BIT(index-1) | BIT(index+1) | BIT(index-ROW-1) | BIT(index-ROW) | BIT(index-ROW+1); + return bit(index-1) | bit(index+1) | bit(index-RANK-1) | bit(index-RANK) | bit(index-RANK+1); } } else { - if (column(index) == 0) { - return BIT(index-ROW) | BIT(index-ROW+1) | BIT(index+1) | BIT(index+ROW+1) | BIT(index+ROW); + if (file(index) == FILE_A) { + return bit(index-RANK) | bit(index-RANK+1) | bit(index+1) | bit(index+RANK+1) | bit(index+RANK); } - else if (column(index) == 7) { - return BIT(index-ROW) | BIT(index-ROW-1) | BIT(index-1) | BIT(index+ROW-1) | BIT(index+ROW); + else if (file(index) == FILE_H) { + return bit(index-RANK) | bit(index-RANK-1) | bit(index-1) | bit(index+RANK-1) | bit(index+RANK); } else { - return BIT(index-ROW-1) | BIT(index-ROW) | BIT(index-ROW+1) - | BIT(index-1) | BIT(index+1) - | BIT(index+ROW-1) | BIT(index+ROW) | BIT(index+ROW+1); + return bit(index-RANK-1) | bit(index-RANK) | bit(index-RANK+1) + | bit(index-1) | bit(index+1) + | bit(index+RANK-1) | bit(index+RANK) | bit(index+RANK+1); } } } @@ -451,15 +557,19 @@ static inline bitmap_t queen_threatmap(struct game_state* g, index_t index) static void print_threatmap(bitmap_t threatmap) { for (ssize_t i=7; i >= 0; i--) { - fputc('\n', stdout); + printf("\n %ld ", i+1); // number coordinates for (size_t j = 0; j < 8; j++) { fputc(' ', stdout); - if (threatmap & BIT(i*ROW+j)) + if (threatmap & bit(i*RANK+j)) fputc('x', stdout); else fputc('-', stdout); } } + /* horizontal letter coordinates */ + printf("\n "); + for (int i = 0; i < 8; i++) + printf(" %c", 'a' + i); fputc('\n', stdout); } @@ -485,176 +595,172 @@ static bitmap_t piece_threatmap(struct game_state* g, index_t index) } } -static bitmap_t threatmap(struct game_state* g, int attacker) +static bitmap_t threatmap(struct game_state* g, enum color attacker) { - bitmap_t threatened = 0; + enum color p = g->player; + g->player = attacker; + bitmap_t t = 0; for(index_t i = 0; i < BOARD_SIZE; i++) { if (friends(g->board[i], attacker)) { - threatened |= piece_threatmap(g, i); + t |= piece_threatmap(g, i); } } - - return threatened; + g->player = p; + return t; } static bool pawn_move_ok(struct game_state* g, index_t from, index_t to) { - const index_t direction = piece_color(g->board[from]); - const index_t diff = (to - from) * direction; - - //printf("\n"); + //printf("checking pawn move for %s\n", g->player == WHITE ? "WHITE" : "BLACK"); + const index_t diff = (to - from) * g->player; + const index_t starting_rank = g->player == WHITE ? RANK_2 : RANK_7; switch (diff) { - case ROW: /* single move */ - //if (!(g->board[to] == EMPTY)) { - // printf("pawn must move to empty tile, "); - //} + case RANK: /* single move */ return g->board[to] == EMPTY; - case ROW - COL: /* diagonal attack */ - case ROW + COL: - //if (!(enemies(g->board[to], g->board[from]))) { - // printf("pawn can't take its own pieces, "); - //} - return enemies(g->board[to], g->board[from]); + case RANK - COL: /* diagonal attack */ + case RANK + COL: + if ((file(from) == FILE_A && file(to) == FILE_H) + || (file(from) == FILE_H && file(to) == FILE_A) + ) { + return false; + } else if (file(to) == g->last_pawn_double_move_file + && rank(from) == (g->player == WHITE ? RANK_5 : RANK_4) + ) { + return true; + } else { + return enemies(g->board[to], g->board[from]); + } - case 2 * ROW: /* double move */ - //if (!( g->board[to] == EMPTY)) { - // printf("pawn must move to empty piece, "); - //} - //if (!( g->board[from + ROW * direction] == EMPTY)) { - // printf("pawn can't jump over piece, "); - //} - //if (!( (row(from) == 1 || row(from) == 6))) { - // printf("pawn can only move two steps from its starting row, "); - //} - //printf("\n"); + case 2 * RANK: /* double move */ return g->board[to] == EMPTY - && g->board[from + ROW * direction] == EMPTY - && (row(from) == 1 || row(from) == 6); + && g->board[from + RANK*g->player] == EMPTY + && rank(from) == starting_rank; - default: /* any other move is illegal */ - //printf("pawn move is generally illegal, "); + default: return false; } } +static bool is_check(struct game_state* g, enum color player) +{ + return bit(g->attr[attr_index(player)] & KING_POSITION) & threatmap(g, -player); +} + +static bool castle_kingside_ok(struct game_state* g) +{ + if (is_check(g, g->player)) { + return false; + } + const int p = attr_index(g->player); + const int rank = g->player == WHITE ? RANK_1 : RANK_8; + + return !(g->attr[p] & H_ROOK_TOUCHED) + && !(g->attr[p] & KING_TOUCHED) + && !(threatmap(g, -g->player) & (bit(FILE_F + rank) | bit(FILE_G + rank))) + && g->board[FILE_G + rank] == EMPTY + && g->board[FILE_F + rank] == EMPTY; +} + +static bool castle_queenside_ok(struct game_state* g) +{ + if (is_check(g, g->player)) { + return false; + } + const int p = attr_index(g->player); + const int rank = g->player == WHITE ? RANK_1 : RANK_8; + return !(g->attr[p] & A_ROOK_TOUCHED) + && !(g->attr[p] & KING_TOUCHED) + && !(threatmap(g, -g->player) & (bit(FILE_C + rank) | bit(FILE_D + rank))) + && g->board[FILE_B + rank] == EMPTY + && g->board[FILE_C + rank] == EMPTY + && g->board[FILE_D + rank] == EMPTY; +} + static bool king_move_ok(struct game_state* g, index_t from, index_t to) { - // castling check is implemented as a separate function - return BIT(to) & king_threatmap(from) - && BIT(to) & ~threatmap(g, -piece_color(g->board[from])); -} - -bool is_check(struct game_state* g, int player) -{ - return BIT(g->king_pos[player == WHITE ? 0 : 1]) & threatmap(g, -player); -} - -static bool castle_ok(struct game_state* g, int player, enum castle_type t) -{ - if (is_check(g, player)) { - return false; + if (g->player == WHITE && from == E1) { + if (to == G1) { + return castle_kingside_ok(g); + } else if (to == C1) { + return castle_queenside_ok(g); + } + } else if (g->player == BLACK && from == E8) { + if (to == G8) { + return castle_kingside_ok(g); + } else if (to == C8) { + return castle_queenside_ok(g); + } } - const int p = player == WHITE ? 0 : 1; - const int row = (player == WHITE ? 1 : 8); - - if (t == CASTLE_KINGSIDE) { - return g->h_rook_touched[p] == false - && g->king_touched[p] == false - && ~threatmap(g, -player) & (BIT(TILE('F', row)) | BIT(TILE('G', row))) - && g->board[TILE('G', row)] == EMPTY - && g->board[TILE('F', row)] == EMPTY; - } else if (t == CASTLE_QUEENSIDE) { - return g->a_rook_touched[p] == false - && g->king_touched[p] == false - && ~threatmap(g, -player) & (BIT(TILE('C', row)) | BIT(TILE('D', row))) - && g->board[TILE('B', row)] == EMPTY - && g->board[TILE('C', row)] == EMPTY - && g->board[TILE('D', row)] == EMPTY; - } - - fprintf(stderr, "invalid castle_type passed to %s", __func__); - exit(EXIT_FAILURE); + return bit(to) & king_threatmap(from) + && bit(to) & ~threatmap(g, -piece_color(g->board[from])); } - -bool move_ok(struct game_state* g, index_t from, index_t to, int player) +static bool move_ok(struct game_state* g, index_t from, index_t to) { + //printf("checking move for %s\n", player_str[g->player]); /* Player must own piece it moves and a player can't capture their own pieces. */ - if (enemies(player, g->board[from]) || friends(player, g->board[to])) { - return false; - } - - bool move_ok = false;; - - switch (piece_abs(g->board[from])) { - case EMPTY: - move_ok = false; - break; - case PAWN: - move_ok = pawn_move_ok(g, from, to); - break; - case KING: - if (to == TILE('G', 8) || to == TILE('G', 1)) { - move_ok = castle_ok(g, player, CASTLE_KINGSIDE); - } else if (to == TILE('C', 8) || to == TILE('C', 1)) { - move_ok = castle_ok(g, player, CASTLE_QUEENSIDE); - } else { - move_ok = king_move_ok(g, from, to); - } - break; - default: - move_ok = BIT(to) & piece_threatmap(g, from); - break; - } - - if (!move_ok) { + if (g->board[from] == EMPTY || enemies(g->player, g->board[from]) || friends(g->player, g->board[to])) { + //printf("must own piece it moves and can't attack its own pieces\n"); return false; } typeof(*g) restore = *g; move(g, from, to); - bool bad = is_check(g, player); + bool check = is_check(g, -g->player); *g = restore; - //memcpy(g, &restore, sizeof restore); - - if (bad) { + if (check) { + //printf("move causes check!\n"); return false; } - return true; + switch (piece_abs(g->board[from])) { + case EMPTY: + //printf("can't move empty tile\n"); + return false; + case PAWN: + //printf("checking pawn move...\n"); + return pawn_move_ok(g, from, to); + case KING: + //printf("checking king move...\n"); + return king_move_ok(g, from, to); + default: + //printf("checking other move...\n"); + return bit(to) & piece_threatmap(g, from); + } + + assert(false); } -static bitmap_t valid_moves(struct game_state* g, index_t i, int player) +static bitmap_t valid_moves(struct game_state* g, index_t i) { bitmap_t output = 0; for (index_t j=0; jturns_without_captures >= 50; } // TODO: fix this garbage -static bool checkmate(struct game_state* g, int player) +static bool checkmate(struct game_state* g) { - if (!is_check(g, player)) + if (!is_check(g, g->player)) return false; // TODO: avoid doubly nested for loop for (int i=0; iboard[i*RANK+j])); + for (int k=0; k<10-n; k++) { + printf(" "); + } + } + } + printf("\n },"); + printf("\n .attr = {"); + printf("\n [0] = 0x%x,", g->attr[0]); + printf("\n [1] = 0x%x,", g->attr[1]); + printf("\n },"); + printf("\n .last_pawn_double_move_file = %d,", g->last_pawn_double_move_file); + printf("\n .turns_without_captures = %d,", g->turns_without_captures); + printf("\n .turns = %d,", g->turns); + printf("\n .player = %s,", color_str[g->player]); + printf("\n};"); + printf("\n"); +} + static void game_init(struct game_state* g) { // black pieces are prefixed by a minus (-) // clang-format off #if 1 - static const piece_t start[BOARD_SIZE] = { - ROOK, KNIGHT, BISHOP, QUEEN, KING, BISHOP, KNIGHT, ROOK, - PAWN, PAWN, PAWN, PAWN, PAWN, PAWN, PAWN, PAWN, - EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - -PAWN, -PAWN, -PAWN, -PAWN, -PAWN, -PAWN, -PAWN, -PAWN, - -ROOK, -KNIGHT,-BISHOP,-QUEEN, -KING, -BISHOP,-KNIGHT,-ROOK, + static const struct game_state start = { + .board = { + /* A B C D E F G H */ + /* 1 */ ROOK, KNIGHT, BISHOP, QUEEN, KING, BISHOP, KNIGHT, ROOK, + /* 2 */ PAWN, PAWN, PAWN, PAWN, PAWN, PAWN, PAWN, PAWN, + /* 3 */ EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, + /* 4 */ EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, + /* 5 */ EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, + /* 6 */ EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, + /* 7 */ -PAWN, -PAWN, -PAWN, -PAWN, -PAWN, -PAWN, -PAWN, -PAWN, + /* 8 */ -ROOK, -KNIGHT, -BISHOP, -QUEEN, -KING, -BISHOP, -KNIGHT, -ROOK, + }, + .attr = { + [0] = E1 & KING_POSITION, + [1] = E8 & KING_POSITION, + }, + .last_pawn_double_move_file = -1, + .turns_without_captures = 0, + .turns = 0, + .player = WHITE, }; - // clang-format on #else - static const piece_t start[BOARD_SIZE] = { - ROOK, EMPTY, EMPTY, EMPTY, KING, EMPTY, EMPTY, ROOK, - EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, - EMPTY, EMPTY, EMPTY, EMPTY, -KING, EMPTY, EMPTY, EMPTY, - }; + static const struct game_state start = { + .board = { + EMPTY, EMPTY, EMPTY, EMPTY, KING, EMPTY, EMPTY, EMPTY, + -QUEEN, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, + PAWN, EMPTY, PAWN, EMPTY, EMPTY, EMPTY, EMPTY, PAWN, + EMPTY, PAWN, EMPTY, EMPTY, BISHOP, EMPTY, EMPTY, EMPTY, + EMPTY, -PAWN, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, EMPTY, + -PAWN, EMPTY, EMPTY, ROOK, EMPTY, EMPTY, EMPTY, EMPTY, + -KING, EMPTY, EMPTY, EMPTY, EMPTY, -KNIGHT, ROOK, EMPTY, + EMPTY, EMPTY, EMPTY, EMPTY, QUEEN, EMPTY, EMPTY, EMPTY, + }, + .attr = { + [0] = 0x1c4, + [1] = 0x1f0, + }, + .last_pawn_double_move_file = -1, + .turns_without_captures = 2, + .turns = 145, + .player = BLACK, +}; #endif + // clang-format on - memset(g, 0, sizeof *g); - memcpy(g->board, start, sizeof start); - - g->king_pos[0] = TILE('E', 1); - g->king_pos[1] = TILE('E', 8); + *g = start; + sigint_state_copy = *g; } static index_t input_to_index(char input[2]) { - const char col = toupper(input[0]); - const char row = input[1]; + const int file = toupper(input[0])-'A'; + const int rank = RANK*(input[1] - '1'); - if (col < 'A' || col > 'H' || row < '1' || row > '8') + //printf("evaluated to index %d\n", file + rank); + + if (file < FILE_A || file > FILE_H || rank < RANK_1 || rank > RANK_8) return -1; - return TILE(col, row - '0'); + return file + rank; } // TODO: Implement algebaric notation -static int player_move(int turn_no, struct game_state* g) +static bool player_move(struct game_state* g) { char input[3] = { 0 }; int from = -1, to = -1; - printf("\n%s's turn to move", turn_no % 2 ? "Black" : "White"); printf("\nMove piece\nfrom: "); scanf(" %2s", input); from = input_to_index(input); if (from == -1) - return 0; + return false; - print_threatmap(valid_moves(g, from, turn_no % 2 ? BLACK : WHITE)); + //print_threatmap(valid_moves(g, from)); printf("\nto: "); scanf(" %2s", input); to = input_to_index(input); if (to == -1) - return 0; + return false; - if (!move_ok(g, from, to, turn_no % 2 ? BLACK : WHITE)) - return 0; + if (!move_ok(g, from, to)) + return false; move(g, from, to); - return 1; + return true; } -static double heuristic(struct game_state* g, int player) +static double heuristic(struct game_state* g, int depth) { - if (draw(g, player)) + if (draw(g)) return 0; - if (checkmate(g, -player)) - return player * INFINITY; + if (checkmate(g)) + return g->player * -9999; double score = 0; for (index_t i=0; iboard[i]; - const int player = piece_color(piece); + const piece_t piece = g->board[i]; const piece_t type = piece_abs(piece); - if (player == WHITE) { - score += player * piece_value[type] * piece_position_bonus[type][i]; - } else { - score += player * piece_value[type] * piece_position_bonus[type][BOARD_SIZE-i-1]; - } + score += piece_color(piece) * piece_value[type] * piece_position_bonus[type][(g->player == WHITE ? i : BOARD_SIZE-i-1)]; + } + if (is_check(g, g->player)) { + score += g->player * -1.0; } return score; } -static void print_debug(struct game_state* g, int player) +static void print_debug(struct game_state* g) { - //struct game_state { - // piece_t board[BOARD_SIZE]; - // index_t king_pos[2]; - // bool a_rook_touched[2]; - // bool h_rook_touched[2]; - // bool king_touched[2]; - //}; - static const char * const bool_str[2] = {"False", "True"}; - - printf("White A king touched: %s\n", bool_str[g->king_touched[0]]); - printf("White A rook touched: %s\n", bool_str[g->a_rook_touched[0]]); - printf("White H rook touched: %s\n", bool_str[g->h_rook_touched[0]]); - printf("White can king side castle: %s\n", bool_str[castle_ok(g, WHITE, CASTLE_KINGSIDE)]); - printf("White can queen side castle: %s\n", bool_str[castle_ok(g, WHITE, CASTLE_QUEENSIDE)]); - printf("White king pos: %c%ld\n", 'A'+((char)column(g->king_pos[0])), 1+row(g->king_pos[0])); - - //printf("Black A king touched: %s\n", bool_str[g->king_touched[1]]); - //printf("Black A rook touched: %s\n", bool_str[g->a_rook_touched[1]]); - //printf("Black H rook touched: %s\n", bool_str[g->h_rook_touched[1]]); - //printf("Black can king side castle: %s\n", bool_str[castle_ok(g, BLACK, CASTLE_KINGSIDE)]); - //printf("Black can queen side castle: %s\n", bool_str[castle_ok(g, BLACK, CASTLE_QUEENSIDE)]); - //printf("Black king pos: %c%ld\n", 'A'+((char)column(g->king_pos[1])), 1+row(g->king_pos[1])); + for (int i=0; i<2; i++) { + printf("%s:\n", color_str_capitalized[i]); + printf(" A king touched: %s\n", bool_str[!!(g->attr[i] & KING_TOUCHED)]); + printf(" A rook touched: %s\n", bool_str[!!(g->attr[i] & A_ROOK_TOUCHED)]); + printf(" H rook touched: %s\n", bool_str[!!(g->attr[i] & H_ROOK_TOUCHED)]); + printf(" can king side castle: %s\n", bool_str[castle_kingside_ok(g)]); + printf(" can queen side castle: %s\n", bool_str[castle_queenside_ok(g)]); + printf(" king pos: %s\n", tile_str[g->attr[i] & KING_POSITION]); + printf(" king pos: %u\n", g->attr[i] & KING_POSITION); + printf(" in check: %s\n", bool_str[is_check(g, WHITE)]); + } + double score = heuristic(g, 1); + if (score == -INFINITY) + score = -999; + else if (score == INFINITY) + score = 999; + printf("Estimated score: %lf\n", score); printf("Turns with no capture: %d\n", g->turns_without_captures); - printf("Estimated score: %lf\n", heuristic(g, player)); } -static double alpha_beta(struct game_state* g, double alpha, double beta, int player, int depth) + +static void sigint_handler(int signal) { - if (depth == 0) - return heuristic(g, player) * player; + paint_board(&sigint_state_copy, -1, -1); + print_debug(&sigint_state_copy); + dump_game_state(&sigint_state_copy); + exit(0); +} + +static double alpha_beta(struct game_state* g, double alpha, double beta, int depth) +{ + if (checkmate(g)) { + return CHECKMATE_SCORE * (depth+1); + } + if (depth == 0) { + return heuristic(g, depth) * g->player; + } double m = alpha; - unsigned char restore[sizeof *g]; // TODO: avoid doubly nested for loop for (int i=0; iboard[i] == EMPTY || !friends(g->board[i], player)) + if (!friends(g->board[i], g->player)) continue; + for (int j=0; j m) { - m = x; - } + double x = -alpha_beta(g, -beta, -(alpha > m ? alpha : m), depth-1); + *g = restore; + m = m > x ? m : x; if (m >= beta) { return m; } @@ -828,38 +982,49 @@ static double alpha_beta(struct game_state* g, double alpha, double beta, int pl return m; } -static void computer_move(struct game_state* g, int player, int depth, index_t* from, index_t* to) +static void computer_move(struct game_state* g, int depth, index_t* from, index_t* to) { - unsigned char restore[sizeof *g]; - - double max = -INFINITY; + double m = -INFINITY; + *from = -1; + *to = -1; // TODO: avoid doubly nested for loop for (int i=0; iboard[i], player)) + if (!friends(g->board[i], g->player)) continue; for (int j=0; j max) { - *to = j; + if (checkmate(g)) { + *g = restore; + *to = j; + *from = i; + return; + } + double x = -alpha_beta(g, -INFINITY, -m, depth-1); + *g = restore; + + if (x > m) { + //printf("considering %s to %s with score %lf\n", tile_str[i], tile_str[j], x); + m = x; + *to = j; *from = i; - max = x; } - //printf("beta: %lf, max: %lf\n", beta, max); } } - //assert(max != -INFINITY); + assert(m != -INFINITY); } int main() { + if(signal(SIGINT, sigint_handler) == SIG_ERR) { + perror("Unable to catch SIGINT"); + exit(EXIT_FAILURE); + } + setlocale(LC_ALL, "C.UTF-8"); struct game_state state = {}; @@ -867,7 +1032,7 @@ int main() game_init(&state); #if 0 - board_paint(&state); + paint_board(&state); print_debug(&state, WHITE); printf("white threatmap:\n"); print_threatmap(threatmap(&state, WHITE)); @@ -875,59 +1040,64 @@ int main() print_threatmap(threatmap(&state, BLACK)); } #else - - int turn = 0; - int player = WHITE; bool player_intervention = false; + //double sum = debug_sum_pieces(&state); + index_t from = -1, to = -1; while (true) { - print_debug(&state, player); - //print_threatmap(threatmap(&state, turn % 2 ? BLACK : WHITE)); - board_paint(&state); - - if (is_check(&state, player)) { - printf("%s in check!\n", player == WHITE ? "White" : "Black"); - print_threatmap(piece_threatmap(&state, state.king_pos[player == WHITE ? 0 : 1])); - } + printf("============================\n"); + paint_board(&state, from, to); + printf("est. score: %lf", heuristic(&state, 0)); + //print_debug(&state); + //dump_game_state(&state); if (player_intervention) { intervene: - while (player_move(turn, &state) == 0) { - printf("Valid moves for %s:\n", player == WHITE ? "white" : "black"); - print_threatmap(valid_moves(&state, state.king_pos[player == WHITE ? 0 : 1], player)); + while (player_move(&state) == 0) { + printf("Valid moves for %s:\n", state.player == WHITE ? "white" : "black"); } } else { - printf("thinking...\n"); - index_t from = -1, to = -1; - computer_move(&state, player, MAX_DEPTH, &from, &to); + printf("%s to move, thinking...\n", state.player == WHITE ? "White" : "Black"); + computer_move(&state, MAX_DEPTH, &from, &to); if (from == -1 || to == -1) { printf("computer couldn't think, starting player intervention\n"); - printf("Valid moves for %s:\n", player == WHITE ? "white" : "black"); - print_threatmap(valid_moves(&state, state.king_pos[player == WHITE ? 0 : 1], player)); - printf("\nThreatened pieces by %s\n", -player == WHITE ? "white" : "black"); - print_threatmap(threatmap(&state, -player)); player_intervention = true; goto intervene; } + assert(move_ok(&state, from, to)); move(&state, from, to); - printf("Did %c%ld to %c%ld\n", (char)('A'+column(from)), 1+row(from), (char)('A'+column(to)), 1+row(to)); + printf("Did %s to %s\n", tile_str[from], tile_str[to]); } - turn += 1; - player = -player; + sigint_state_copy = state; - if (checkmate(&state, player)) { - printf("\nCheckmate. %s won!\n", -player == WHITE ? "White" : "Black"); - print_debug(&state, player); - board_paint(&state); - print_threatmap(valid_moves(&state, state.king_pos[player == WHITE ? 0 : 1], player)); - print_threatmap(threatmap(&state, -player)); + bool white_king = false; + bool black_king = false; + for (index_t i=0; i +#include +#include +#include + +#include +#undef assert + +#ifdef assert +#error "assert is already defined!" +#endif + +static void print_backtrace() +{ + void* array[10]; + char** strings; + int size, i; + + size = backtrace(array, 10); + strings = backtrace_symbols(array, size); + if (strings != NULL) + { + printf ("Obtained %d stack frames.\n", size); + for (i = 0; i < size; i++) + printf ("%s\n", strings[i]); + } + + free(strings); + exit(EXIT_FAILURE); +} + +static int assertion_fail(const char* msg) +{ + fprintf(stderr, "assertion `%s` failed!\n", msg); + print_backtrace(); + exit(EXIT_FAILURE); +} + +#ifndef NDEBUG +#define assert(expr) ((void)((expr) || (assertion_fail(# expr), 0))) +#else +#define assert(expt) (void)0 +#endif diff --git a/src/map.h b/src/map.h new file mode 100644 index 0000000..1fff232 --- /dev/null +++ b/src/map.h @@ -0,0 +1,10 @@ + + + +static bool board_equals( + + + +struct board_map { + +};