From 319428e1bc3160a27968746b795471affa0e9688 Mon Sep 17 00:00:00 2001 From: Ole Morud Date: Sun, 26 Mar 2023 19:15:46 +0200 Subject: [PATCH] Update .clang-format --- Makefile | 34 +- compile_flags.txt | 1 - doxygen-config | 142 ----- include/common.h | 26 - include/graphics.h | 8 - include/pieces.h | 12 - include/util.h | 11 - src/board.h | 56 ++ src/chess.c | 1136 +++++++++++++++++++++++++++++++++++--- src/cool_assert.h | 45 ++ src/graphics.c | 95 ---- src/map.h | 20 + src/pieces.c | 230 -------- src/util.c | 130 ----- testing/test_threatmap.c | 60 ++ 15 files changed, 1256 insertions(+), 750 deletions(-) delete mode 100644 doxygen-config delete mode 100644 include/common.h delete mode 100644 include/graphics.h delete mode 100644 include/pieces.h delete mode 100644 include/util.h create mode 100644 src/board.h create mode 100644 src/cool_assert.h delete mode 100644 src/graphics.c create mode 100644 src/map.h delete mode 100644 src/pieces.c delete mode 100644 src/util.c create mode 100644 testing/test_threatmap.c diff --git a/Makefile b/Makefile index a609b6d..083b0cd 100644 --- a/Makefile +++ b/Makefile @@ -1,40 +1,40 @@ -CC = clang -LD = ld.lld -CFLAGS = -Iinclude +CC = gcc +#CFLAGS += -O0 -ggdb3 CFLAGS += -Ofast -ggdb3 -CFLAGS += -Wall -Wextra -Werror -CFLAGS += -fsanitize=address -LDFLAGS = -fuse-ld=lld -LDFLAGS = -fsanitize=address +CFLAGS += -Wall -Wextra -Wno-unused-function -rdynamic +#CFLAGS += -fsanitize=address +LDFLAGS = -fuse-ld=lld -rdynamic +#LDFLAGS += -fsanitize=address -_OBJ = chess.o common.o graphics.o pieces.o util.o +_OBJ = chess.o OBJ = $(addprefix obj/, $(_OBJ)) +TEST_DIR = testing + all: bin/chess +test: $(TEST_DIR)/bin/test_threatmap + obj: mkdir -p $@ bin: mkdir -p $@ -docs: - doxygen doxygen-config +$(TEST_DIR): + mkdir -p $@/bin clean: rm bin/* obj/*.o -gdb: bin/chess - gdb $< - -run: bin/chess - ./$< - obj/%.o: src/%.c $(CC) -o $@ $(CFLAGS) -c $< bin/chess: $(OBJ) $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^ -.PHONY: all run gdb clean docs +$(TEST_DIR)/bin/test_%: testing/test_%.c obj/%.o obj/util.o + $(CC) -o $@ $(CFLAGS) $(LDFLAGS) $^ + +.PHONY: all clean docs test diff --git a/compile_flags.txt b/compile_flags.txt index e6efa87..ededa11 100644 --- a/compile_flags.txt +++ b/compile_flags.txt @@ -1,4 +1,3 @@ -Iinclude -Wall -Wextra --Werror diff --git a/doxygen-config b/doxygen-config deleted file mode 100644 index 7ed4b2e..0000000 --- a/doxygen-config +++ /dev/null @@ -1,142 +0,0 @@ -DOXYFILE_ENCODING = UTF-8 -PROJECT_NAME = "Terminal Chess" -PROJECT_BRIEF = Chess application for ANSI terminals -OUTPUT_DIRECTORY = docs -CREATE_SUBDIRS = NO -ALLOW_UNICODE_NAMES = NO -OUTPUT_LANGUAGE = English -BRIEF_MEMBER_DESC = YES -REPEAT_BRIEF = YES -ALWAYS_DETAILED_SEC = NO -INLINE_INHERITED_MEMB = NO -FULL_PATH_NAMES = YES -SHORT_NAMES = NO -JAVADOC_AUTOBRIEF = NO -JAVADOC_BANNER = NO -QT_AUTOBRIEF = NO -MULTILINE_CPP_IS_BRIEF = NO -PYTHON_DOCSTRING = YES -INHERIT_DOCS = YES -SEPARATE_MEMBER_PAGES = NO -TAB_SIZE = 4 -OPTIMIZE_OUTPUT_FOR_C = YES -MARKDOWN_SUPPORT = YES -TOC_INCLUDE_HEADINGS = 5 -AUTOLINK_SUPPORT = YES -IDL_PROPERTY_SUPPORT = YES -DISTRIBUTE_GROUP_DOC = NO -GROUP_NESTED_COMPOUNDS = NO -SUBGROUPING = YES -NUM_PROC_THREADS = 0 -EXTRACT_ALL = YES -CASE_SENSE_NAMES = SYSTEM -SHOW_HEADERFILE = YES -SHOW_INCLUDE_FILES = YES -SHOW_GROUPED_MEMB_INC = NO -FORCE_LOCAL_INCLUDES = NO -INLINE_INFO = YES -SORT_MEMBER_DOCS = YES -SORT_BRIEF_DOCS = NO -SORT_MEMBERS_CTORS_1ST = NO -SORT_GROUP_NAMES = NO -SORT_BY_SCOPE_NAME = NO -STRICT_PROTO_MATCHING = NO -GENERATE_TODOLIST = YES -GENERATE_TESTLIST = YES -GENERATE_BUGLIST = YES -GENERATE_DEPRECATEDLIST= YES -MAX_INITIALIZER_LINES = 30 -SHOW_USED_FILES = YES -SHOW_FILES = YES -SHOW_NAMESPACES = YES -QUIET = NO -WARNINGS = YES -WARN_IF_UNDOCUMENTED = YES -WARN_IF_DOC_ERROR = YES -WARN_IF_INCOMPLETE_DOC = YES -WARN_NO_PARAMDOC = NO -WARN_IF_UNDOC_ENUM_VAL = NO -WARN_AS_ERROR = NO -WARN_FORMAT = "$file:$line: $text" -WARN_LINE_FORMAT = "at line $line of file $file" -INPUT = src -INPUT_ENCODING = UTF-8 -FILE_PATTERNS = *.c *.h -RECURSIVE = YES -EXCLUDE_SYMLINKS = NO -EXAMPLE_PATTERNS = * -EXAMPLE_RECURSIVE = NO -FILTER_SOURCE_FILES = NO -USE_MDFILE_AS_MAINPAGE = README.md -SOURCE_BROWSER = NO -INLINE_SOURCES = NO -STRIP_CODE_COMMENTS = YES -REFERENCED_BY_RELATION = NO -REFERENCES_RELATION = NO -REFERENCES_LINK_SOURCE = YES -SOURCE_TOOLTIPS = YES -VERBATIM_HEADERS = YES -ALPHABETICAL_INDEX = YES -GENERATE_HTML = YES -HTML_OUTPUT = html -HTML_FILE_EXTENSION = .html -HTML_COLORSTYLE = AUTO_LIGHT -HTML_COLORSTYLE_HUE = 220 -HTML_COLORSTYLE_SAT = 100 -HTML_COLORSTYLE_GAMMA = 80 -HTML_TIMESTAMP = NO -HTML_DYNAMIC_MENUS = YES -HTML_DYNAMIC_SECTIONS = NO -HTML_INDEX_NUM_ENTRIES = 100 -GENERATE_DOCSET = NO -DOCSET_FEEDNAME = "Doxygen generated docs" -DOCSET_BUNDLE_ID = org.doxygen.Project -DOCSET_PUBLISHER_ID = org.doxygen.Publisher -DOCSET_PUBLISHER_NAME = Publisher -GENERATE_HTMLHELP = NO -GENERATE_CHI = NO -BINARY_TOC = NO -TOC_EXPAND = NO -GENERATE_QHP = NO -QHP_NAMESPACE = org.doxygen.Project -QHP_VIRTUAL_FOLDER = doc -GENERATE_ECLIPSEHELP = NO -ECLIPSE_DOC_ID = org.doxygen.Project -DISABLE_INDEX = NO -GENERATE_TREEVIEW = NO -FULL_SIDEBAR = NO -ENUM_VALUES_PER_LINE = 4 -TREEVIEW_WIDTH = 250 -EXT_LINKS_IN_WINDOW = NO -OBFUSCATE_EMAILS = YES -HTML_FORMULA_FORMAT = png -FORMULA_FONTSIZE = 10 -USE_MATHJAX = NO -MATHJAX_VERSION = MathJax_2 -MATHJAX_FORMAT = HTML-CSS -SEARCHENGINE = YES -SERVER_BASED_SEARCH = NO -EXTERNAL_SEARCH = NO -SEARCHDATA_FILE = searchdata.xml -GENERATE_LATEX = YES -LATEX_OUTPUT = latex -MAKEINDEX_CMD_NAME = makeindex -LATEX_MAKEINDEX_CMD = makeindex -COMPACT_LATEX = NO -PAPER_TYPE = a4 -PDF_HYPERLINKS = YES -USE_PDFLATEX = YES -LATEX_BATCHMODE = NO -LATEX_HIDE_INDICES = NO -LATEX_BIB_STYLE = plain -LATEX_TIMESTAMP = NO -ENABLE_PREPROCESSING = YES -MACRO_EXPANSION = NO -EXPAND_ONLY_PREDEF = NO -SEARCH_INCLUDES = YES -SKIP_FUNCTION_MACROS = YES -TAGFILES = tags -ALLEXTERNALS = NO -EXTERNAL_GROUPS = YES -EXTERNAL_PAGES = YES - diff --git a/include/common.h b/include/common.h deleted file mode 100644 index 54e6d53..0000000 --- a/include/common.h +++ /dev/null @@ -1,26 +0,0 @@ - -#include /* true, false, bool */ -#include /* ptrdiff_t */ -#include /* int32_t */ - -/** Type representing piece/tile on a chessboard */ -typedef int32_t tile_t; - -/** Type representing index of a chessboard tile */ -typedef ptrdiff_t index_t; - -#define BOARD_SIZE ((index_t)(8 * 8)) - -#define WHITE ((tile_t)1) -#define BLACK ((tile_t)-1) - -#define E ((tile_t)0) ///< empty tile -#define K ((tile_t)1) ///< king -#define Q ((tile_t)2) ///< queen -#define R ((tile_t)3) ///< rook -#define B ((tile_t)4) ///< bishop -#define N ((tile_t)5) ///< knight -#define P ((tile_t)6) ///< pawn - -#define ROW ((index_t)8) -#define COL ((index_t)1) diff --git a/include/graphics.h b/include/graphics.h deleted file mode 100644 index f4b2c41..0000000 --- a/include/graphics.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef GRAPHICS_H -#define GRAPHICS_H - -#include "common.h" - -void print_board(const tile_t board[BOARD_SIZE]); - -#endif diff --git a/include/pieces.h b/include/pieces.h deleted file mode 100644 index 7da6d80..0000000 --- a/include/pieces.h +++ /dev/null @@ -1,12 +0,0 @@ - -#ifndef PIECES_H -#define PIECES_H - -#include "common.h" - -bool move_ok(const tile_t board[BOARD_SIZE], - index_t from, - index_t to, - int player); - -#endif diff --git a/include/util.h b/include/util.h deleted file mode 100644 index e0adfd8..0000000 --- a/include/util.h +++ /dev/null @@ -1,11 +0,0 @@ -#include "common.h" - -int64_t get_sign(int64_t n); -bool tile_empty(tile_t t); -index_t abs_index(index_t i); -index_t column(index_t i); -index_t row(index_t i); -tile_t abs_tile(tile_t t); -tile_t get_color(tile_t t); -bool same_color(tile_t a, tile_t b); -bool opposite_color(tile_t a, tile_t b); diff --git a/src/board.h b/src/board.h new file mode 100644 index 0000000..756b31e --- /dev/null +++ b/src/board.h @@ -0,0 +1,56 @@ + +#pragma once + +#include +#include +#include +#include + +typedef uint64_t bitmap_t; +typedef uint_fast8_t index_t; +typedef int_fast8_t piece_t; + +enum tile_info { + EMPTY = 0b0000, + KING = 0b0001, + QUEEN = 0b0010, + ROOK = 0b0011, + BISHOP = 0b0100, + KNIGHT = 0b0101, + PAWN = 0b0110, + COLOR_BIT = 0b1000, + + PIECE_UNKNOWN=-1 +}; + +struct board { + static_assert(sizeof(bitmap_t) == 64/8, "bitmap_t must be 64 bits long"); + bitmap_t pieces[4]; // 4 bits correspond to 1 tile +}; + +static inline bitmap_t bit(index_t i) +{ + return 0b111UL << 3*i; +} + +static bool is_white(piece_t piece) +{ + return piece % 2 == 0; +} + +static bool is_black(piece_t piece) +{ + return piece % 2 == 1; +} + +static piece_t piece_at(struct board* board, index_t tile) +{ + for (size_t i = 0; i < sizeof board->pieces / board->pieces[0]; i++) { + if (bit(tile) & board->pieces[i]) { + return i; + } + } + return PIECE_UNKNOWN; +} + + diff --git a/src/chess.c b/src/chess.c index f4eebd5..02e74d8 100644 --- a/src/chess.c +++ b/src/chess.c @@ -1,127 +1,1107 @@ -#include "common.h" -#include "graphics.h" -#include "pieces.h" +#include -#include /* isalpha, isdigit ... */ -#include /* setlocale */ -#include /* uint64_t */ -#include /* printf, scanf */ -#include /* memcpy */ +#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 -#define CLEAR_SCREEN() printf("\033[2J") +#define MAX_DEPTH 4 +#define CHECKMATE_SCORE 100000 -int do_turn(int turn_no, tile_t board[BOARD_SIZE]); -void init_board(tile_t board[BOARD_SIZE]); -index_t input_to_index(char input[2]); +#define RANK ((index_t)8) +#define COL ((index_t)1) -int main() -{ - setlocale(LC_ALL, "C.UTF-8"); +typedef int8_t piece_t; +typedef ptrdiff_t index_t; +typedef uint64_t bitmap_t; - tile_t board[BOARD_SIZE] = { 0 }; +static const char * const bool_str[] = {"true", "false"}; - init_board(board); +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, +}; - int turn = 0; +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", +}; - while (true) { - CLEAR_SCREEN(); - print_board(board); +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, +}; - // turn increments on valid inputs, - turn += do_turn(turn, board); - } +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, +}; - return 0; +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[] = { + [EMPTY] = 0, + [PAWN] = 1, + [BISHOP] = 3, + [KNIGHT] = 3, + [ROOK] = 5, + [QUEEN] = 9, + [KING] = 10, +}; + +static const double piece_position_bonus[PIECE_COUNT][BOARD_SIZE] = { + [EMPTY] = {0}, + [PAWN] = { + /* 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] = { + /* 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] = { + /* 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] = { + /* 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] = { + /* 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 */ 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, +}; + +enum attr_color { + ATTR_WHITE = 0, + ATTR_BLACK = 1, +}; + +inline size_t attr_index(enum color color) { + return color == WHITE ? ATTR_WHITE : ATTR_BLACK; } -/** - * Resets/initializes the board - * - * Sets all the tiles of the board to match the starting position of chess. - * - * \param board Pointer to list of tiles representing board state - */ -void init_board(tile_t board[BOARD_SIZE]) +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) +{ + return a * b > 0; +} + +static inline piece_t piece_abs(piece_t t) +{ + if (t < 0) + return -t; + return t; +} + +static int signum(int t) +{ + if (t == 0) + return 0; + if (t >= 0) + return 1; + return -1; +} + +static inline index_t rank(index_t i) +{ + return (i / RANK)*8; +} + +static inline index_t file(index_t i) +{ + return i % RANK; +} + +static inline bool enemies(piece_t a, piece_t b) +{ + return a * b < 0; +} + +static inline piece_t piece_color(piece_t t) +{ + return (piece_t)signum(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", + }; + const size_t pa = piece_abs(p); + return table[pa] + (piece_color(p) == WHITE ? 1 : 0); +} + +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 + 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*RANK+j == highlight1 || i*RANK+j == highlight2) + BG_RED(); + else if ((i + j) % 2) + BG_DARKBLUE(); + else + BG_LIGHTBLUE(); + if (t == EMPTY) { + printf(" "); + continue; + } + if (t > 0) + FG_WHITE(); + else + FG_BLACK(); + printf("%lc ", UNICODE_CHESS_SYMBOL + piece_abs(t)); + } + //setcolor(2, 0, 0, 0); // reset text attributes + printf("\033[0m"); + } + /* horizontal letter coordinates */ + 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) +{ + 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"); + + 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) { + g->turns_without_captures += 1; + } else { + g->turns_without_captures = 0; + } + + // 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; + } +} + +static bitmap_t pawn_threatmap(struct game_state* g, index_t index) +{ + const index_t left = bit(index + RANK*g->player - 1); + const index_t right = bit(index + RANK*g->player + 1); + + if (file(index) == FILE_A) + return right; + + if (file(index) == FILE_H) + return left; + + return left | right; +} + +static bitmap_t diagonal_threatmap(struct game_state* g, index_t index) +{ + bitmap_t threatened = 0; + + //index_t directions[] = { RANK+1, RANK-1, -RANK+1, -RANK-1 }; + + 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+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-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-RANK-1; i >= 0 && file(i+1) != 0; i += -RANK-1) { + threatened |= bit(i); + if (g->board[i] != EMPTY) { + break; + } + } + return threatened; +} + +static bitmap_t cardinal_threatmap(struct game_state* g, index_t index) +{ + bitmap_t threatened = 0; + + 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-RANK; i >= 0; i -= RANK) { + threatened |= bit(i); + if (g->board[i] != EMPTY) + break; + } + 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 && file(i) != 7; i--) { + threatened |= bit(i); + if (g->board[i] != EMPTY) + break; + } + + return threatened; +} + +static inline bitmap_t bishop_threatmap(struct game_state* g, index_t index) +{ + return diagonal_threatmap(g, index); +} + +static inline bitmap_t rook_threatmap(struct game_state* g, index_t index) +{ + return cardinal_threatmap(g, index); +} + +static bitmap_t knight_threatmap(index_t index) +{ + bitmap_t threatened = 0L; + + //clang-format off + index_t knight_wheel[8*2] = { + /* x, y */ + 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 < 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(index + knight_wheel[i] + knight_wheel[i+1]); + } + + return threatened; +} + +static bitmap_t king_threatmap(index_t index) +{ + // I fucking hate this function so much + if (rank(index) == RANK_1) { + if (file(index) == FILE_A) { + return bit(index+1) | bit(index+RANK+1) | bit (index+RANK); + } + 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+RANK-1) | bit(index+RANK) | bit(index+RANK+1); + } + } + if (rank(index) == RANK_8) { + if (file(index) == FILE_A) { + return bit(index+1) | bit(index-RANK+1) | bit (index-RANK); + } + 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-RANK-1) | bit(index-RANK) | bit(index-RANK+1); + } + } else { + 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 (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-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); + } + } +} + +static inline bitmap_t queen_threatmap(struct game_state* g, index_t index) +{ + return diagonal_threatmap(g, index) | cardinal_threatmap(g, index); +} + +static void print_threatmap(bitmap_t threatmap) +{ + for (ssize_t i=7; i >= 0; i--) { + printf("\n %ld ", i+1); // number coordinates + for (size_t j = 0; j < 8; j++) { + fputc(' ', stdout); + 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); +} + +static bitmap_t piece_threatmap(struct game_state* g, index_t index) +{ + switch (piece_abs(g->board[index])) { + case EMPTY: + return 0L; + case PAWN: + return pawn_threatmap(g, index); + case BISHOP: + return bishop_threatmap(g, index); + case ROOK: + return rook_threatmap(g, index); + case KNIGHT: + return knight_threatmap(index); + case KING: + return king_threatmap(index); + case QUEEN: + return queen_threatmap(g, index); + default: + return 0L; + } +} + +static bitmap_t threatmap(struct game_state* g, enum color attacker) +{ + 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)) { + t |= piece_threatmap(g, i); + } + } + g->player = p; + return t; +} + +static bool pawn_move_ok(struct game_state* g, index_t from, index_t to) +{ + //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 RANK: /* single move */ + return g->board[to] == EMPTY; + + 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 * RANK: /* double move */ + return g->board[to] == EMPTY + && g->board[from + RANK*g->player] == EMPTY + && rank(from) == starting_rank; + + 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) +{ + 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); + } + } + return bit(to) & king_threatmap(from) + && bit(to) & ~threatmap(g, -piece_color(g->board[from])); +} + +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 (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 check = is_check(g, -g->player); + *g = restore; + if (check) { + //printf("move causes check!\n"); + return false; + } + + 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) +{ + bitmap_t output = 0; + for (index_t j=0; jturns_without_captures >= 50; +} + +// TODO: fix this garbage +static bool checkmate(struct game_state* g) +{ + 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 (-) - const tile_t start[] - = { -R, -N, -B, -Q, -K, -B, -N, -R, -P, -P, -P, -P, -P, -P, -P, -P, - E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, - E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, - P, P, P, P, P, P, P, P, R, N, B, Q, K, B, N, R }; + // clang-format off +#if 1 + 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, + }; +#else + 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 - memcpy(board, start, sizeof(start)); + *g = start; + sigint_state_copy = *g; +} + +static index_t input_to_index(char input[2]) +{ + const int file = toupper(input[0])-'A'; + const int rank = RANK*(input[1] - '1'); + + //printf("evaluated to index %d\n", file + rank); + + if (file < FILE_A || file > FILE_H || rank < RANK_1 || rank > RANK_8) + return -1; + + return file + rank; } // TODO: Implement algebaric notation -/** Asks for move, validates move and does move if valid - * - * \param turn_no Turn number - * \param board Pointer to list of tiles representing board state - * - * \return (int)1 if successful, (int)0 if invalid input from player - * */ -int do_turn(int turn_no, tile_t board[BOARD_SIZE]) +static bool player_move(struct game_state* g) { char input[3] = { 0 }; int from = -1, to = -1; - printf("\nPlayer %i, your turn to move", 1 + turn_no % 2); 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)); printf("\nto: "); scanf(" %2s", input); to = input_to_index(input); if (to == -1) - return 0; + return false; - if (! move_ok(board, from, to, turn_no % 2 ? BLACK : WHITE)) - return 0; + if (!move_ok(g, from, to)) + return false; - board[to] = board[from]; - board[from] = E; + move(g, from, to); - /* increment to make it next turn */ - return 1; + return true; } -/** - * Translates A1, 3B etc. to the 1D index of the board - * - * \param input string of length 2 representing tile, e.g. "A3" - * - * \return index value on valid input, -1 on invalid input - * - * */ -index_t input_to_index(char input[2]) +static double heuristic(struct game_state* g, int depth) { - int x = -1, y = -1; + if (draw(g)) + return 0; - for (int i = 0; i < 2; i++) { - const char c = input[i]; + if (checkmate(g)) + return g->player * -9999; - const char v = isalpha(c) ? toupper(c) : c; + double score = 0; + for (index_t i=0; iboard[i]; + const piece_t type = piece_abs(piece); + 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; +} - if ('A' <= v && v <= 'H') - x = v - 'A'; - else if ('1' <= v && v <= '8') - y = v - '1'; +static void print_debug(struct game_state* g) +{ + 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)]); } - if (x != -1 && y != -1) - return 8 * (7 - y) + x; - else - return -1; + 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); } + + +static void sigint_handler(int signal) +{ + 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; + + // TODO: avoid doubly nested for loop + for (int i=0; iboard[i], g->player)) + continue; + + for (int j=0; j m ? alpha : m), depth-1); + *g = restore; + m = m > x ? m : x; + if (m >= beta) { + return m; + } + } + } + + return m; +} + +static void computer_move(struct game_state* g, int depth, index_t* from, index_t* to) +{ + double m = -INFINITY; + *from = -1; + *to = -1; + + // TODO: avoid doubly nested for loop + for (int i=0; iboard[i], g->player)) + continue; + for (int j=0; j m) { + //printf("considering %s to %s with score %lf\n", tile_str[i], tile_str[j], x); + m = x; + *to = j; + *from = i; + } + } + } + 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 = {}; + + game_init(&state); + +#if 0 + paint_board(&state); + print_debug(&state, WHITE); + printf("white threatmap:\n"); + print_threatmap(threatmap(&state, WHITE)); + printf("black threatmap:\n"); + print_threatmap(threatmap(&state, BLACK)); +} +#else + bool player_intervention = false; + //double sum = debug_sum_pieces(&state); + index_t from = -1, to = -1; + + while (true) { + 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(&state) == 0) { + printf("Valid moves for %s:\n", state.player == WHITE ? "white" : "black"); + } + } else { + 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"); + player_intervention = true; + goto intervene; + } + assert(move_ok(&state, from, to)); + move(&state, from, to); + printf("Did %s to %s\n", tile_str[from], tile_str[to]); + } + + sigint_state_copy = state; + + 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/graphics.c b/src/graphics.c deleted file mode 100644 index 8c6842b..0000000 --- a/src/graphics.c +++ /dev/null @@ -1,95 +0,0 @@ - -#include "graphics.h" - -#include "common.h" -#include "util.h" - -#include - -/** Set background to dark blue */ -#define BG_DARKBLUE() setcolor(0, 100, 100, 150) - -/** Set background to light blue */ -#define BG_LIGHTBLUE() setcolor(0, 150, 150, 200) - -/** Set foreground to black */ -#define FG_BLACK() setcolor(1, 0, 0, 0) - -/** Set foreground to white */ -#define FG_WHITE() setcolor(1, 0xff, 0xff, 0xff) - -/** 0x2659 == ♙ */ -#define UNICODE_CHESS_SYMBOL 0x2659 - -static inline void -setcolor(const int mode, const int r, const int g, const int b); - -/** - * Sets the foreground or background color for subsequent writes. - * - * Uses Select Graphic Renditions (SGR) to set the color of the terminal output. - * See https://en.wikipedia.org/wiki/ANSI_escape_code#24-bit for more details. - * - * \param mode 0 - change background, 1 - change foreground, 2 - reset colors - * \param r amount of red (0 to 255) - * \param b amount of blue (0 to 255) - * \param g amount of green (0 to 255) - */ -static inline void -setcolor(const int mode, const int r, const int g, const int b) -{ - if (mode == 2) - printf("\033[0m"); - else - printf("\033[%i;2;%i;%i;%im", mode ? 38 : 48, r, g, b); -}; - -/** - * Prints the board - * - * Uses unicode symbols and ANSI escape features to print a chessboard on the - * display. - * - * \param board A pointer to a list of tiles representing the board state - * - * */ -void print_board(const tile_t board[BOARD_SIZE]) -{ - /* 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. */ - - for (size_t i = 0; i < 8; i++) { - printf("\n %zu ", 8 - i); // number coordinates - - for (size_t j = 0; j < 8; j++) { - tile_t t = board[i * 8 + j]; - - if ((i + j) % 2) - BG_DARKBLUE(); - else - BG_LIGHTBLUE(); - - if (tile_empty(t)) { - printf(" "); - continue; - } - - if (t > 0) - FG_WHITE(); - else - FG_BLACK(); - - printf("%lc ", UNICODE_CHESS_SYMBOL + abs_tile(t)); - } - - setcolor(2, 0, 0, 0); // reset text attributes - } - - /* horizontal letter coordinates */ - printf("\n "); - - for (int i = 0; i < 8; i++) - printf(" %c", 'a' + i); -} diff --git a/src/map.h b/src/map.h new file mode 100644 index 0000000..a91f5a5 --- /dev/null +++ b/src/map.h @@ -0,0 +1,20 @@ + + +enum chess_piece { + EMPTY = 0, + KING = 1, + QUEEN = 2, + ROOK = 3, + BISHOP = 4, + KNIGHT = 5, + PAWN = 6, + PIECE_COUNT, +}; + +static bool board_equals( + + + +struct board_map { + +}; diff --git a/src/pieces.c b/src/pieces.c deleted file mode 100644 index 88611e8..0000000 --- a/src/pieces.c +++ /dev/null @@ -1,230 +0,0 @@ - -#include "common.h" -#include "util.h" - -bool bishop_move_ok(const tile_t board[BOARD_SIZE], index_t from, index_t to); -bool cardinal_move_ok(const tile_t board[BOARD_SIZE], index_t from, index_t to); -bool diagonal_move_ok(const tile_t board[BOARD_SIZE], index_t from, index_t to); -bool king_move_ok(const tile_t board[BOARD_SIZE], index_t from, index_t to); -bool knight_move_ok(index_t from, index_t to); -bool pawn_move_ok(const tile_t board[BOARD_SIZE], index_t from, index_t to); -bool queen_move_ok(const tile_t board[BOARD_SIZE], index_t from, index_t to); -bool rook_move_ok(const tile_t board[BOARD_SIZE], index_t from, index_t to); - -/** - * Check if a move is valid - * - * \param board Pointer to list of tiles representing board state - * \param from Tile to move piece from - * \param to Tile to move piece to - * \param player The current player to move - * - * \return true if move is valid, false otherwise - * */ -bool move_ok(const tile_t board[BOARD_SIZE], - index_t from, - index_t to, - int player) -{ - /* player must own piece it moves */ - if (board[from] * player < 0) - return false; - - /* empty tiles can't be moved */ - if (tile_empty(board[from])) - return false; - - /* player can't take their own pieces or move a piece onto itself */ - if (same_color(board[from], board[to])) - return false; - - /* check piece specific moves */ - switch (abs_tile(board[from])) { - case P: - return pawn_move_ok(board, from, to); - - case B: - return bishop_move_ok(board, from, to); - - case R: - return rook_move_ok(board, from, to); - - case N: - return knight_move_ok(from, to); - - case K: - return king_move_ok(board, from, to); - - case Q: - return queen_move_ok(board, from, to); - } - - return false; -} - -/** - * Check if move is a valid pawn move - * - * \param board Array of tiles representing chess board state - * \param from Index of board piece starts at - * \param to Index of board piece wants to move to - * - * \return true if move is valid, false otherwise - */ -bool pawn_move_ok(const tile_t board[BOARD_SIZE], index_t from, index_t to) -{ - const index_t direction = -1 * get_color(board[from]), - diff = (to - from) * direction; - - switch (diff) { - case ROW: /* single move */ - return tile_empty(board[to]); - - case ROW - COL: /* diagonal attack */ - case ROW + COL: - return opposite_color(board[to], board[from]); - - case 2 * ROW: /* double move */ - return tile_empty(board[to]) - && tile_empty(board[from + ROW * direction]) - && (row(from) == 1 || row(from) == 6); - - default: /* any other move is illegal */ - return false; - } -} - -/** - * Check if `to` is on a diagonal line of `from` - * - * \param board Array of tiles representing chess board state - * \param from Index of board piece is at - * \param to Index of board piece tries to move to - * - * \return true if \p to is on a diagonal line of \p from - */ -bool diagonal_move_ok(const tile_t board[BOARD_SIZE], index_t from, index_t to) -{ - // clang-format off - const index_t col_diff = column(to) - column(from), - row_diff = row(to) - row(from), - x = get_sign(col_diff), - y = get_sign(row_diff) * ROW, - step = x + y; - // clang-format on - - if (abs_index(col_diff) != abs_index(row_diff)) - return false; - - for (index_t p = from + step; p != to; p += step) - if (! tile_empty(board[p])) - return false; - - return true; -} - -/** - * Check if index `to` is on a cardinal line of `from` - * - * \param board Array of tiles representing chess board state - * \param from Index of board piece is at - * \param to Index of board piece tries to move to - * - * \return true if \p to and \p from share a column or row - */ -bool cardinal_move_ok(const tile_t board[BOARD_SIZE], index_t from, index_t to) -{ - const index_t col_diff = column(to) - column(from), - row_diff = row(to - from); - - if (row_diff > 0 && col_diff > 0) - return false; - - const index_t step = - row_diff ? ROW * get_sign(row_diff) : get_sign(col_diff); - - for (index_t p = from + step; p != to; p += step) - if (! tile_empty(board[p])) - return false; - - return true; -} - -/** - * Check if move is a valid bishop move - * - * \param board Array of tiles representing chess board state - * \param from Index of board piece is at - * \param to Index of board piece tries to move to - * - * \return true if move is valid, false otherwise - */ -bool bishop_move_ok(const tile_t board[BOARD_SIZE], index_t from, index_t to) -{ - return diagonal_move_ok(board, from, to); -} - -/** - * Check if move is a valid rook move - * - * \param board Array of tiles representing chess board state - * \param from Index of board piece is at - * \param to Index of board piece tries to move to - * - * \return true if move is valid, false otherwise - */ -bool rook_move_ok(const tile_t board[BOARD_SIZE], index_t from, index_t to) -{ - return cardinal_move_ok(board, from, to); -} - -/** - * Check if move is a valid knight move - * - * \param from Index of board piece is at - * \param to Index of board piece tries to move to - * - * \return true if move is valid, false otherwise - */ -bool knight_move_ok(index_t from, index_t to) -{ - const index_t x = abs_index(column(to) - column(from)), - y = abs_index(row(to - from)); - - return (x == 1 && y == 2) || (x == 2 && y == 1); -} - -/** - * Check if move is a valid king move - * - * \param board Array of tiles representing chess board state - * \param from Index of board piece is at - * \param to Index of board piece tries to move to - * - * \return true if move is valid, false otherwise - */ -bool king_move_ok(const tile_t board[BOARD_SIZE], index_t from, index_t to) -{ - const index_t abs_x = abs_index(column(to) - column(from)), - abs_y = abs_index(row(to) - row(from)); - - // TODO: check if move causes check - (void)board; - - return abs_x <= 1 && abs_y <= 1; -} - -/** - * Check if move is a valid queen move - * - * \param board Array of tiles representing chess board state - * \param from Index of board piece is at - * \param to Index of board piece tries to move to - * - * \return true if move is valid, false otherwise - */ -bool queen_move_ok(const tile_t board[BOARD_SIZE], index_t from, index_t to) -{ - return diagonal_move_ok(board, from, to) - || cardinal_move_ok(board, from, to); -} diff --git a/src/util.c b/src/util.c deleted file mode 100644 index eb58453..0000000 --- a/src/util.c +++ /dev/null @@ -1,130 +0,0 @@ - -#include "util.h" - -#include "common.h" - -/** - * Get sign of a number - * - * \param n positive or negative integer - * - * \return 1 if number is positive - * -1 if number is negative - * 0 if number is zero - */ -int64_t get_sign(int64_t n) -{ - if (n == 0) - return 0; - - if (n >= 0) - return 1; - - return -1; -} - -/** - * Get the color of a tile - * - * \param t tile to check - * - * \return WHITE (1) if tile is white, - * BLACK (-1) if tile is black, - * 0 if the tile is empty - */ -tile_t get_color(tile_t t) -{ - return (tile_t)get_sign(t); -} - -/** - * Calculate the absolute value of an index_t value - * - * \param p positive or negative index_t - * - * \return the absolute value of p - */ -index_t abs_index(index_t i) -{ - if (i < 0) - return -1 * i; - - return i; -} - -/** - * Calculate the absolute value of a tile_t value - * - * \param t positive or negative tile_t - * - * \return the absolute value of t - * */ -tile_t abs_tile(tile_t t) -{ - if (t < 0) - return -1 * t; - - return t; -} - -/** - * Check if tile is has no piece on it - * - * \param t tile to check if empty - * - * \return true if tile is empty, false otherwise - * */ -bool tile_empty(tile_t t) -{ - return t == E; -} - -/** - * Get row of index - * - * \param i index to get row number of - * - * \return row number of i - * */ -index_t row(index_t i) -{ - return i / ROW; -} - -/** - * Get column of index - * - * \param i index to get column number of - * - * \return column number of i - * */ -index_t column(index_t i) -{ - return i % ROW; -} - -/** - * Check if two tiles have pieces of the opposite color - * - * \param a Tile to compare - * \param b Tile to compare it with - * - * \return true if a and b are opposite colors, false otherwise - * */ -bool opposite_color(tile_t a, tile_t b) -{ - return a * b < 0; -} - -/** - * Check if two tiles have pieces of the same color - * - * \param a Tile to compare - * \param b Tile to compare it with - * - * \return true if a and b are opposite colors, false otherwise - * */ -bool same_color(tile_t a, tile_t b) -{ - return a * b > 0; -} diff --git a/testing/test_threatmap.c b/testing/test_threatmap.c new file mode 100644 index 0000000..87dd47c --- /dev/null +++ b/testing/test_threatmap.c @@ -0,0 +1,60 @@ + +#include "threatmap.h" + +// uint64_t pawn_threatmap(const tile_t board[BOARD_SIZE], index_t index); +// uint64_t bishop_threatmap(const tile_t board[BOARD_SIZE], index_t index); +// uint64_t rook_threatmap(const tile_t board[BOARD_SIZE], index_t index); +// uint64_t knight_threatmap(index_t index); +// uint64_t king_threatmap(const tile_t board[BOARD_SIZE], index_t index); +// uint64_t queen_threatmap(const tile_t board[BOARD_SIZE], index_t index); +// uint64_t threatmap(const tile_t board[BOARD_SIZE], int color_attacking); +// void print_threatmap(uint64_t threatmap); + +void test_threatmap() +{ + const tile_t e4[] = { + -R, -N, -B, -Q, -K, -B, -N, -R, + -P, -P, -P, -P, -P, -P, -P, -P, + E, E, E, E, E, E, E, E, + E, E, E, E, E, E, E, E, + E, E, E, P, E, E, E, E, + E, E, E, E, E, E, E, E, + P, P, P, E, P, P, P, P, + R, N, B, Q, K, B, N, R + }; + + const tile_t lonely_bishop[] = { + E, E, E, E, E, E, E, E, + E, E, E, E, E, E, E, E, + E, E, E, E, E, E, E, E, + E, E, E, B, E, E, E, E, + E, E, E, E, E, E, E, E, + E, E, E, E, E, E, E, E, + E, E, E, E, E, E, E, E, + E, E, E, E, E, E, E, E, + }; + + const tile_t lonely_rook[] = { + E, E, E, E, E, E, E, E, + E, E, E, E, E, E, E, E, + E, E, E, E, E, E, E, E, + E, E, E, R, E, E, E, E, + E, E, E, E, E, E, E, E, + E, E, E, E, E, E, E, E, + E, E, E, E, E, E, E, E, + E, E, E, E, E, E, E, E, + }; + + (void)lonely_rook; (void)lonely_bishop; (void)e4; + + //print_threatmap(threatmap(e4, WHITE)); + print_threatmap(threatmap(lonely_bishop, WHITE)); + //print_threatmap(threatmap(lonely_rook, WHITE)); +} + +int main() +{ + test_threatmap(); + + return 0; +}