Compare commits
15 Commits
1d5f0b7d75
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 4458fb6491 | |||
| 924e7d5eed | |||
| 6ccd2a4efe | |||
| 6e61a14cdb | |||
| 2c3d1bc021 | |||
| 6c5ef0586f | |||
| db958dcac1 | |||
| 4d2a86c7b7 | |||
| 5209dfec92 | |||
| a649db77ef | |||
| 1ab928cc6f | |||
| 6178e7c963 | |||
| 2d49f86517 | |||
| 7900546964 | |||
| f0f12be88d |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,6 +1,6 @@
|
||||
/*
|
||||
!/src
|
||||
/include/chess/generated/*
|
||||
/src/generated
|
||||
!/CMakeLists.txt
|
||||
!/README.md
|
||||
!/LICENSE
|
||||
@ -8,5 +8,6 @@
|
||||
!/lib
|
||||
!.gitkeep
|
||||
!/include
|
||||
/include/chess/generated/*
|
||||
!/.gitignore
|
||||
!/test
|
||||
|
||||
@ -1,29 +1,36 @@
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
project(chess C)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_C_STANDARD 99)
|
||||
include_directories(include)
|
||||
file(GLOB SOURCES "src/chess/*.c")
|
||||
file(GLOB LIB_SOURCES "src/common/*.c")
|
||||
file(GLOB CODE_GEN_LIB_SOURCES "src/common/preCodeGen/*.c")
|
||||
file(GLOB LIB_SOURCES "src/common/*/*.c" "src/generated/*.c")
|
||||
|
||||
add_executable(genMoveConsts src/generateCode/moveConsts.c src/common/bitboard.c
|
||||
src/common/print.c)
|
||||
add_executable(genMoveConsts src/generateCode/moveConsts.c ${CODE_GEN_LIB_SOURCES})
|
||||
add_custom_command(
|
||||
OUTPUT moveConsts.h # Output file from code generation
|
||||
COMMAND genMoveConsts > include/chess/generated/moveConsts.h
|
||||
DEPENDS genMoveConsts # Depends on the code generation executable
|
||||
)
|
||||
add_custom_target(generateCode DEPENDS moveConsts.h)
|
||||
|
||||
add_executable(genZobrist src/generateCode/zobrist.c ${CODE_GEN_LIB_SOURCES})
|
||||
add_custom_command(
|
||||
OUTPUT zobristConsts.c # Output file from code generation
|
||||
COMMAND genZobrist > src/generated/zobristConsts.c
|
||||
DEPENDS genZobrist # Depends on the code generation executable
|
||||
)
|
||||
|
||||
add_custom_target(generateCode DEPENDS moveConsts.h zobristConsts.c)
|
||||
|
||||
find_package(PkgConfig REQUIRED)
|
||||
pkg_check_modules(GTK4 REQUIRED IMPORTED_TARGET gtk4)
|
||||
pkg_check_modules(LIBRSVG REQUIRED IMPORTED_TARGET librsvg-2.0)
|
||||
|
||||
add_executable(findMagicNumber src/findMagicNumber.c ${LIB_SOURCES})
|
||||
set_property(TARGET findMagicNumber PROPERTY C_STANDARD 99)
|
||||
add_dependencies(findMagicNumber generateCode)
|
||||
|
||||
add_executable(${PROJECT_NAME} ${SOURCES} ${LIB_SOURCES})
|
||||
set_property(TARGET ${PROJECT_NAME} PROPERTY C_STANDARD 99)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE PkgConfig::GTK4 PkgConfig::LIBRSVG)
|
||||
add_dependencies(${PROJECT_NAME} generateCode)
|
||||
|
||||
@ -39,11 +46,9 @@ if(CMAKE_BUILD_TYPE MATCHES "Release")
|
||||
endif()
|
||||
|
||||
add_executable(chessNoComputer ${SOURCES} ${LIB_SOURCES})
|
||||
set_property(TARGET chessNoComputer PROPERTY C_STANDARD 99)
|
||||
target_link_libraries(chessNoComputer PRIVATE PkgConfig::GTK4 PkgConfig::LIBRSVG)
|
||||
add_dependencies(chessNoComputer generateCode)
|
||||
target_compile_definitions(chessNoComputer PUBLIC NO_COMPUTER)
|
||||
|
||||
add_executable(moveGenTest test/moveGen.c src/chess/move.c ${LIB_SOURCES})
|
||||
set_property(TARGET moveGenTest PROPERTY C_STANDARD 99)
|
||||
add_test(NAME moveGen COMMAND moveGenTest)
|
||||
|
||||
61
README.md
61
README.md
@ -19,13 +19,62 @@ make
|
||||
If you do not use make, replace it with ninja for example. The build script contains multiple
|
||||
targets:
|
||||
|
||||
- chess: This is the main target. It has a GUI and the user plays as white. The engine responds with
|
||||
- `chess`: This is the main target. It has a GUI and the user plays as white. The engine responds with
|
||||
a move for black.
|
||||
- chessNoComputer: The user can play both sides in a GUI. Mainly added for testing the move
|
||||
- `chessNoComputer`: The user can play both sides in a GUI. Mainly added for testing the move
|
||||
generator.
|
||||
- findMagicNumber: finds Magic Numbers for the magic bitboard.
|
||||
- `findMagicNumber`: finds Magic Numbers for the magic bitboard.
|
||||
- `moveGenTest`: tests the move generator,
|
||||
- `genMoveConsts`: produces constants to generate moves faster.
|
||||
|
||||
# sources
|
||||
## Testing
|
||||
|
||||
https://www.chessprogramming.org/Magic_Bitboards
|
||||
https://www.chessprogramming.org/Looking_for_Magics
|
||||
The target moveGenTest with its source in the test directory is a [perft](https://www.chessprogramming.org/Perft).
|
||||
It works by counting the number of leaf nodes of the move generation with a
|
||||
certain depth. The results where verified with [stockfish](https://stockfishchess.org/).
|
||||
|
||||
## Implementation
|
||||
|
||||
The engine is implemented using [bitboards](https://www.chessprogramming.org/Bitboards)
|
||||
to store the position. A bitboard is an array of 64-Bit unsigned integers. The bitboard contains one
|
||||
element per piece and square. Each Bit of the integer corresponds to one field
|
||||
on the board, whether the piece stands there (1) or not (0). This allows use to
|
||||
use bitwise operations to calculate information we need, really fast.
|
||||
|
||||
The move generation of pseudo-legal moves (moves without considering whether the
|
||||
king is in check) is mostly straightforward. We pre-generate some data in
|
||||
`src/generateCode/moveConsts.c`, so that we can calculate the moves faster.
|
||||
For the king and the knight the moves are pre-generated because they can only
|
||||
possibly move to fixed target squares for one given source square. For the other
|
||||
pieces we pre-generated the distance, the piece can move in one given direction.
|
||||
|
||||
For testing, if the king is in check, so we get legal moves, [magic
|
||||
Bitboards](https://www.chessprogramming.org/Magic_Bitboards) are used. This
|
||||
allows us to calculate really fast whether the king is in check compared to
|
||||
looping over all moves to see, if a piece can capture the king. Essentially we
|
||||
get the bitboard of all pieces and null all bits but the file and rank, the rook
|
||||
we want to the moves for, is on (or the relevant diagonals for the bishop). We
|
||||
can look up the moves by using this mask as a key of a hashmap. A hashing
|
||||
algorithm that multiplies with a so-called magic number and right shift, is used.
|
||||
The hashing is fast, and we can find magic numbers, so there are no hash
|
||||
collisions. To simplify the task of finding magic numbers, we use a different
|
||||
magic number for each square ([fancy magic
|
||||
bitboard](https://www.chessprogramming.org/Magic_Bitboards#Fancy)). The magic
|
||||
numbers for this engine are simply found by trying randomly generated one. The
|
||||
Implementation in `src/findMagicNumber.c` is based on ideas
|
||||
[there](https://www.chessprogramming.org/Looking_for_Magics#Feeding_in_Randoms).
|
||||
|
||||
For seraching the moves, we use a variant of [Alpha-Beta](https://www.chessprogramming.org/Alpha-Beta).
|
||||
Alpha-Beta is based on [minimax](https://en.wikipedia.org/wiki/Minimax) with an
|
||||
added upper and lower bound for the position score with makes it much faster
|
||||
than minimax.
|
||||
|
||||
The engine has a partly implemented UCI interface with its code in `src/uci.c`.
|
||||
|
||||
To detect repetitions we need a way to easily lookup, if the position already accrued. This done by
|
||||
calculating a [Zobrist Hash](https://www.chessprogramming.org/Zobrist_Hashing) for the board
|
||||
position. Zobrist Hashing is really nice because it allows incremental updating. With this hash the
|
||||
engine has implemented its own hash tables, which allow for detecting repetitions. Later on this can
|
||||
also be used for a [transposition table](https://www.chessprogramming.org/Transposition_Table).
|
||||
|
||||
The evaluation is currently simple. Here is currently the most room for optimizations.
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
#include <stdint.h>
|
||||
#include <chess/types.h>
|
||||
|
||||
bool bitsetGet(uint_least64_t bitset, uint_least8_t i);
|
||||
uint_least64_t bitsetClear(uint_least64_t bitset, uint_least8_t i);
|
||||
uint_least64_t bitsetSet(uint_least64_t bitset, uint_least8_t i);
|
||||
#define REPETETION_TABLE_LENGTH 1024
|
||||
#define START_FEN "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
||||
|
||||
bool bitboardGet(const uint_least64_t *board, struct piece_t piece, uint_least8_t i);
|
||||
uint_least64_t bitboardGetMask(const uint_least64_t *board, struct piece_t piece);
|
||||
void bitboardSetMask(uint_least64_t *board, struct piece_t piece, uint_least64_t value);
|
||||
@ -13,4 +13,6 @@ uint_least64_t bitboardMaskAllPieces(const uint_least64_t *board);
|
||||
void bitboardClear(uint_least64_t *board, struct piece_t piece, uint_least8_t i);
|
||||
void bitboardSet(uint_least64_t *board, struct piece_t piece, uint_least8_t i);
|
||||
struct piece_t pieceAtField(const uint_least64_t *board, uint_least8_t i);
|
||||
struct gameState_t newGameState(uint_least64_t *board, char *FEN);
|
||||
struct gameState_t newGameState(uint_least64_t *board,
|
||||
struct zobristTableElement *repetitionTableStore, const char *FEN);
|
||||
struct gameState_t parseFen(uint_least64_t *board, const char *FEN);
|
||||
|
||||
6
include/chess/bitset.h
Normal file
6
include/chess/bitset.h
Normal file
@ -0,0 +1,6 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
bool bitsetGet(uint_least64_t bitset, uint_least8_t i);
|
||||
uint_least64_t bitsetClear(uint_least64_t bitset, uint_least8_t i);
|
||||
uint_least64_t bitsetSet(uint_least64_t bitset, uint_least8_t i);
|
||||
@ -17,6 +17,7 @@ enum spezialMoves {
|
||||
struct move_t {
|
||||
uint_least8_t src, dst, spezialMove;
|
||||
uint_least8_t srcPiece, dstPiece, capturedPiece;
|
||||
uint_least64_t hash;
|
||||
};
|
||||
|
||||
void genDirectionConsts();
|
||||
@ -24,9 +25,11 @@ uint_least8_t validMoves(struct gameState_t gameState, struct move_t *moves);
|
||||
uint_least8_t pieceValidMoves(struct gameState_t gameState, struct piece_t piece, uint_least8_t src,
|
||||
struct move_t *moves, bool promotion);
|
||||
|
||||
void undoMove(uint_least64_t *board, struct move_t move, bool color);
|
||||
struct gameState_t makeMove(struct gameState_t gameState, struct move_t move);
|
||||
void undoMove(struct gameState_t gameState, struct move_t move, bool color);
|
||||
struct gameState_t makeMove(struct gameState_t gameState, const struct move_t move);
|
||||
void initMagicTable();
|
||||
bool kingInCheck(const uint_least64_t *board, const bool color);
|
||||
uint_least8_t getBaseRankI(bool color);
|
||||
uint_least8_t getBaseRank(bool color);
|
||||
|
||||
#endif
|
||||
|
||||
@ -2,26 +2,29 @@
|
||||
#include <stdint.h>
|
||||
#include <chess/move.h>
|
||||
|
||||
#define fprintArray(file, printer, arr) \
|
||||
#define fprintArrayCustomLength(file, printer, arr, length) \
|
||||
do { \
|
||||
fprintf(file, "{"); \
|
||||
for(size_t i = 0; i < LENGTH(arr); ++i) { \
|
||||
for(size_t i = 0; i < length; ++i) { \
|
||||
printer(file, arr[i]); \
|
||||
if (i < LENGTH(arr) - 1) { \
|
||||
if (i < length - 1) { \
|
||||
fprintf(file, ", "); \
|
||||
} \
|
||||
} \
|
||||
fprintf(file, " }"); \
|
||||
} while(0)
|
||||
#define fprintArray(file, printer, arr) fprintArrayCustomLength(file, printer, arr, LENGTH(arr))
|
||||
|
||||
#define fdefineArray(file, declaration, printer, arr) \
|
||||
#define fdefineArrayCustomLength(file, declaration, printer, arr, length) \
|
||||
do { \
|
||||
fprintf(file, "%s[%zu] = ", declaration, LENGTH(arr)); \
|
||||
fprintArray(file, printer, arr); \
|
||||
fprintf(file, "%s[%zu] = ", declaration, length); \
|
||||
fprintArrayCustomLength(file, printer, arr, length); \
|
||||
fprintf(file, ";\n"); \
|
||||
} while(0)
|
||||
#define fdefineArray(file, declaration, printer, arr) fdefineArrayCustomLength(file, declaration, printer, arr, LENGTH(arr))
|
||||
|
||||
#define defineArray(declaration, printer, arr) fdefineArray(stdout, declaration, printer, arr)
|
||||
#define defineArrayCustomLength(declaration, printer, arr, length) fdefineArrayCustomLength(stdout, declaration, printer, arr, length)
|
||||
#define defineArray(declaration, printer, arr) defineArrayCustomLength(declaration, printer, arr, LENGTH(arr))
|
||||
|
||||
void printerll(FILE *file, long long num);
|
||||
void printerull(FILE *file, unsigned long long num);
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#define BOARD_SIZE 8
|
||||
#define TOTAL_BOARD_SIZE (BOARD_SIZE * BOARD_SIZE)
|
||||
@ -35,6 +36,16 @@ enum directions {
|
||||
NORTH, NORTHWEST, WEST, SOUTHWEST, SOUTH, SOUTHEAST, EAST, NORTHEAST, DIRECTION_LENGTH
|
||||
};
|
||||
|
||||
struct zobristTableElement {
|
||||
uint_least64_t hash;
|
||||
int_least16_t value;
|
||||
};
|
||||
|
||||
struct zobristTable {
|
||||
struct zobristTableElement *arr;
|
||||
size_t length;
|
||||
};
|
||||
|
||||
struct moveDst_t {
|
||||
uint_least8_t length;
|
||||
uint_least8_t dst[8];
|
||||
@ -53,6 +64,8 @@ struct castle_t {
|
||||
|
||||
struct gameState_t {
|
||||
uint_least64_t *board;
|
||||
uint_least64_t zobrist;
|
||||
struct zobristTable repetitionTable;
|
||||
bool color; // color to move
|
||||
struct castle_t canCastle[2];
|
||||
|
||||
|
||||
18
include/chess/util.h
Normal file
18
include/chess/util.h
Normal file
@ -0,0 +1,18 @@
|
||||
#ifndef CHESS_UTIL_H
|
||||
#define CHESS_UTIL_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <chess/types.h>
|
||||
|
||||
uint_least64_t rand_64();
|
||||
uint_least64_t zobristPieceI(struct piece_t piece, uint_least8_t field);
|
||||
uint_least8_t getFile(const uint_least8_t field);
|
||||
uint_least8_t getRank(const uint_least8_t field);
|
||||
|
||||
struct zobristTable initZobirstTable(struct zobristTableElement *table, size_t length);
|
||||
void zobristTableIter(const struct zobristTable table, const uint_least64_t key, void *result,
|
||||
void (*callback)(struct zobristTableElement *element, const uint_least64_t key, void *result));
|
||||
|
||||
#endif
|
||||
16
include/chess/zobristConsts.h
Normal file
16
include/chess/zobristConsts.h
Normal file
@ -0,0 +1,16 @@
|
||||
#ifndef CHESS_ZOBRIST_CONSTS_H
|
||||
#define CHESS_ZOBRIST_CONSTS_H
|
||||
|
||||
#include "chess/types.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#define ZOBRIST_CASTLE_LENGTH 2
|
||||
#define ZOBRIST_PIECE_LENGTH (TOTAL_BOARD_SIZE * 2 * PIECES_LENGTH)
|
||||
|
||||
extern const uint_least64_t ZOBRIST_PIECE[];
|
||||
extern const uint_least64_t ZOBRIST_BLACK_MOVE;
|
||||
extern const uint_least64_t ZOBRIST_SHORT_CASTLE[ZOBRIST_CASTLE_LENGTH];
|
||||
extern const uint_least64_t ZOBRIST_LONG_CASTLE[ZOBRIST_CASTLE_LENGTH];
|
||||
extern const uint_least64_t ZOBRIST_EN_PASSENT_FILE[BOARD_SIZE];
|
||||
|
||||
#endif
|
||||
@ -3,23 +3,22 @@
|
||||
#include <stdint.h>
|
||||
#include <chess/bitboard.h>
|
||||
#include <chess/print.h>
|
||||
#include <stdio.h>
|
||||
|
||||
// These min and max values will not overflow, when negated
|
||||
#define INT_16_SAFE_MIN (INT_LEAST16_MIN + 1)
|
||||
#define INT_16_SAFE_MAX (INT_LEAST16_MAX - 1)
|
||||
|
||||
#ifdef __GNUC__ // Check if using GCC or compatible compiler
|
||||
static int_least16_t countOnes(uint_least64_t num) {
|
||||
static int_least16_t countOnes(const uint_least64_t num) {
|
||||
return __builtin_popcountll(num); // Use GCC built-in function if available
|
||||
}
|
||||
#elif defined(_MSC_VER) // Check if using Microsoft Visual C++
|
||||
#include <intrin.h>
|
||||
static int_least16_t countOnes(uint_least64_t num) {
|
||||
static int_least16_t countOnes(const uint_least64_t num) {
|
||||
return __popcnt64(num); // Use MSVC intrinsic if available
|
||||
}
|
||||
#else
|
||||
static int_least16_t countOnes(uint_least64_t num) {
|
||||
static int_least16_t countOnes(const uint_least64_t num) {
|
||||
int_least16_t count = 0;
|
||||
while(num) {
|
||||
count += num & 1;
|
||||
@ -30,6 +29,7 @@ static int_least16_t countOnes(uint_least64_t num) {
|
||||
#endif
|
||||
|
||||
static int_least16_t evaluate(const struct gameState_t gameState) {
|
||||
if(gameState.halfMoveCounter == 50) return 0;
|
||||
int_least16_t value = 0;
|
||||
const uint_least64_t *board = gameState.board;
|
||||
int_least16_t pieceValues[PIECES_LENGTH];
|
||||
@ -56,15 +56,15 @@ static int_least16_t alphaBeta(const struct gameState_t gameState, int_fast16_t
|
||||
uint_least8_t movesLength = validMoves(gameState, moves);
|
||||
if(movesLength == 0) {
|
||||
if(kingInCheck(gameState.board, gameState.color)) {
|
||||
return INT_16_SAFE_MIN + 1;
|
||||
return INT_16_SAFE_MIN + 1; // checkmate
|
||||
}
|
||||
return 0;
|
||||
return 0; // stalemate
|
||||
}
|
||||
for(uint_least8_t i = 0; i < movesLength; ++i) {
|
||||
const struct move_t move = moves[i];
|
||||
const struct gameState_t newGameState = makeMove(gameState, move);
|
||||
const int_least16_t score = -alphaBeta(newGameState, -beta, -alpha, depth - 1);
|
||||
undoMove(gameState.board, move, gameState.color);
|
||||
undoMove(newGameState, move, gameState.color);
|
||||
if(score >= beta) {
|
||||
return beta; // beta-cutoff
|
||||
}
|
||||
@ -85,7 +85,7 @@ static struct move_t searchRoot(const struct gameState_t gameState, uint_least8_
|
||||
const struct move_t move = moves[i];
|
||||
const struct gameState_t newGameState = makeMove(gameState, move);
|
||||
const int_least16_t score = -alphaBeta(newGameState, -beta, -alpha, depth - 1);
|
||||
undoMove(gameState.board, move, gameState.color);
|
||||
undoMove(newGameState, move, gameState.color);
|
||||
if(score > alpha) {
|
||||
alpha = score; // alpha acts like max in MiniMax
|
||||
selectedMove = move;
|
||||
|
||||
@ -10,11 +10,7 @@
|
||||
#include <chess/move.h>
|
||||
#include <chess/bitboard.h>
|
||||
#include "evaluate.h"
|
||||
|
||||
#define drawPiece(file, rank, piece) do { \
|
||||
const RsvgRectangle rect = {xOffset + (file) * fieldSize, yOffset + (rank) * fieldSize, fieldSize, fieldSize};\
|
||||
rsvg_handle_render_document(piecesSvg[pieceToSvgI(piece)], cr, &rect, NULL); \
|
||||
} while(0)
|
||||
#include <chess/util.h>
|
||||
|
||||
struct drawData_t {
|
||||
RsvgHandle **piecesSvg;
|
||||
@ -30,34 +26,47 @@ struct drawData_t {
|
||||
};
|
||||
|
||||
const uint_least8_t PROMOTION_PIECES[] = {QUEEN, KNIGHT, BISHOP, ROOK};
|
||||
static bool turn = WHITE;
|
||||
|
||||
static uint_least8_t pieceToSvgI(struct piece_t piece) {
|
||||
return piece.color == WHITE ? piece.type | 8 : piece.type;
|
||||
return piece.color == WHITE ? PIECES_LENGTH + piece.type : piece.type;
|
||||
}
|
||||
|
||||
static void selectCircle(cairo_t *cr, const struct drawData_t *drawData, uint_least8_t field, double radius) {
|
||||
uint_least8_t file = field % BOARD_SIZE;
|
||||
uint_least8_t rank = field / BOARD_SIZE;
|
||||
double xOffset = drawData->xOffset;
|
||||
double yOffset = drawData->yOffset;
|
||||
double fieldSize = drawData->fieldSize;
|
||||
cairo_arc(cr, xOffset + (file + 0.5) * fieldSize, yOffset + (rank + 0.5) * fieldSize, radius, 0, 2 * M_PI);
|
||||
static void drawPiece(struct drawData_t *ctx, cairo_t *cr, uint_least8_t field, struct piece_t piece) {
|
||||
const RsvgRectangle rect = {ctx->xOffset + getFile(field)* ctx->fieldSize,
|
||||
ctx->yOffset + getRank(field) * ctx->fieldSize, ctx->fieldSize, ctx->fieldSize};
|
||||
rsvg_handle_render_document(ctx->piecesSvg[pieceToSvgI(piece)], cr, &rect, NULL);
|
||||
}
|
||||
|
||||
|
||||
static void selectCircle(cairo_t *cr, const struct drawData_t *drawData, uint_least8_t field, double radius) {
|
||||
const double xOffset = drawData->xOffset;
|
||||
const double yOffset = drawData->yOffset;
|
||||
const double fieldSize = drawData->fieldSize;
|
||||
cairo_arc(cr, xOffset + (getFile(field) + 0.5) * fieldSize, yOffset + (getRank(field) + 0.5) * fieldSize, radius, 0, 2 * M_PI);
|
||||
}
|
||||
|
||||
static struct gameState_t userMove(struct gameState_t gameState, struct move_t move) {
|
||||
gameState = makeMove(gameState, move);
|
||||
#ifdef NO_COMPUTER
|
||||
turn = !turn;
|
||||
#else
|
||||
#ifndef NO_COMPUTER
|
||||
move = bestMove(gameState);
|
||||
gameState = makeMove(gameState, move);
|
||||
#endif
|
||||
return gameState;
|
||||
}
|
||||
|
||||
static uint_least8_t promotionUiPos(uint_least8_t dst) {
|
||||
return dst >= 1 ? dst - 1 : 0;
|
||||
static uint_least8_t promotionUiPos(uint_least8_t dst, bool color) {
|
||||
const uint_least8_t rankI = getBaseRankI(!color);
|
||||
uint_least8_t uiPos = dst >= rankI + 1 ? dst - 1 : dst;
|
||||
const uint_least8_t overflowI = rankI + BOARD_SIZE;
|
||||
if(uiPos + LENGTH(PROMOTION_PIECES) > overflowI) uiPos = overflowI - LENGTH(PROMOTION_PIECES);
|
||||
return uiPos;
|
||||
}
|
||||
|
||||
static void drawRetangle(cairo_t *cr, struct drawData_t *drawData, uint_least8_t field, double width) {
|
||||
const double fieldSize = drawData->fieldSize;
|
||||
cairo_rectangle(cr, drawData->xOffset + getFile(field) * fieldSize,
|
||||
drawData->yOffset + getRank(field) * fieldSize, width, fieldSize);
|
||||
cairo_fill(cr);
|
||||
}
|
||||
|
||||
static void draw_event(GtkDrawingArea *area, cairo_t *cr, int width, int height, gpointer data) {
|
||||
@ -65,7 +74,8 @@ static void draw_event(GtkDrawingArea *area, cairo_t *cr, int width, int height,
|
||||
const double xOffset = (width - fieldSize * BOARD_SIZE) / 2;
|
||||
const double yOffset = (height - fieldSize * BOARD_SIZE) / 2;
|
||||
struct drawData_t *drawData = (struct drawData_t*)data;
|
||||
const uint_least64_t *board = drawData->gameState->board;
|
||||
const struct gameState_t *gameState = drawData->gameState;
|
||||
const uint_least64_t *board = gameState->board;
|
||||
RsvgHandle **piecesSvg = drawData->piecesSvg;
|
||||
drawData->xOffset = xOffset;
|
||||
drawData->yOffset = yOffset;
|
||||
@ -73,13 +83,12 @@ static void draw_event(GtkDrawingArea *area, cairo_t *cr, int width, int height,
|
||||
for(uint_least8_t file = 0; file < BOARD_SIZE; ++file) {
|
||||
for(uint_least8_t rank = 0; rank < BOARD_SIZE; ++rank) {
|
||||
const uint_least8_t field = rank * BOARD_SIZE + file;
|
||||
cairo_rectangle(cr, xOffset + file * fieldSize, yOffset + rank * fieldSize, fieldSize, fieldSize);
|
||||
if((file + rank) & 1) cairo_set_source_rgb(cr, 0.46274, 0.5882, 0.3373);
|
||||
else cairo_set_source_rgb(cr, 0.9333, 0.9333, 0.8235);
|
||||
cairo_fill(cr);
|
||||
drawRetangle(cr, drawData, field, fieldSize);
|
||||
if(bitboardGetAllPieces(board, field)) {
|
||||
const struct piece_t piece = pieceAtField(board, rank * BOARD_SIZE + file);
|
||||
drawPiece(file, rank, piece);
|
||||
drawPiece(drawData, cr, field, piece);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,14 +103,12 @@ static void draw_event(GtkDrawingArea *area, cairo_t *cr, int width, int height,
|
||||
}
|
||||
}
|
||||
if(drawData->selectDst != NOT_SELECTED) {
|
||||
uint_least8_t uiPos = promotionUiPos(drawData->selectDst);
|
||||
if(uiPos + 3 >= BOARD_SIZE) uiPos = BOARD_SIZE - LENGTH(PROMOTION_PIECES);
|
||||
uint_least8_t uiPos = promotionUiPos(drawData->selectDst, gameState->color);
|
||||
cairo_set_source_rgb(cr, 0.5, 0.5, 0.5);
|
||||
cairo_rectangle(cr, xOffset + uiPos * fieldSize, yOffset, 4 * fieldSize, fieldSize);
|
||||
cairo_fill(cr);
|
||||
drawRetangle(cr, drawData, uiPos, 4 * fieldSize);
|
||||
for(uint_least8_t i = 0; i < LENGTH(PROMOTION_PIECES); ++i) {
|
||||
const struct piece_t piece = {PROMOTION_PIECES[i], true};
|
||||
drawPiece(uiPos + i, 0, piece);
|
||||
const struct piece_t piece = {PROMOTION_PIECES[i], gameState->color};
|
||||
drawPiece(drawData, cr, uiPos + i, piece);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -118,10 +125,11 @@ static void on_click(GtkGestureClick *gesture, int n_press, double x, double y,
|
||||
const uint_least8_t file = adjustedX / fieldSize;
|
||||
const uint_least8_t rank = adjustedY / fieldSize;
|
||||
const uint_least8_t field = file + rank * BOARD_SIZE;
|
||||
const bool onPromotionField = rank == getBaseRank(!gameState->color);
|
||||
if(drawData->selectDst == NOT_SELECTED) {
|
||||
const struct piece_t allPiece = {ALL_PIECES, turn};
|
||||
const struct piece_t allPiece = {ALL_PIECES, gameState->color};
|
||||
if(bitboardGet(board, allPiece, field)) {
|
||||
// deaktivated piece by clicking on it again
|
||||
// deactivated piece by clicking on it again
|
||||
if(field == drawData->clickedPiece) {
|
||||
drawData->clickedPiece = NOT_SELECTED;
|
||||
drawData->movesLength = 0;
|
||||
@ -141,8 +149,8 @@ static void on_click(GtkGestureClick *gesture, int n_press, double x, double y,
|
||||
}
|
||||
}
|
||||
if(isValidDst) {
|
||||
const struct piece_t piece = pieceAtField(board, field);
|
||||
if(piece.type == PAWN && field < BOARD_SIZE) { // promotion
|
||||
const struct piece_t piece = pieceAtField(board, drawData->clickedPiece);
|
||||
if(piece.type == PAWN && onPromotionField) { // promotion
|
||||
drawData->selectDst = field;
|
||||
}
|
||||
else {
|
||||
@ -162,12 +170,13 @@ static void on_click(GtkGestureClick *gesture, int n_press, double x, double y,
|
||||
drawData->movesLength = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(rank != 0) return;
|
||||
const uint_least8_t uiPos = promotionUiPos(drawData->selectDst);
|
||||
const uint_least8_t dstPiece = PROMOTION_PIECES[file - uiPos];
|
||||
else if(onPromotionField) {
|
||||
const uint_least8_t uiPos = promotionUiPos(drawData->selectDst, gameState->color);
|
||||
const uint_least8_t i = field - uiPos;
|
||||
if(i >= LENGTH(PROMOTION_PIECES)) return;
|
||||
const uint_least8_t dstPiece = PROMOTION_PIECES[i];
|
||||
const uint_least8_t capturedPiece = pieceAtField(board, drawData->selectDst).type;
|
||||
const struct move_t move = {drawData->clickedPiece, drawData->selectDst, 0, PAWN, dstPiece};
|
||||
const struct move_t move = {drawData->clickedPiece, drawData->selectDst, 0, PAWN, dstPiece, capturedPiece};
|
||||
*gameState = userMove(*gameState, move);
|
||||
drawData->selectDst = NOT_SELECTED;
|
||||
drawData->clickedPiece = NOT_SELECTED;
|
||||
@ -177,10 +186,11 @@ static void on_click(GtkGestureClick *gesture, int n_press, double x, double y,
|
||||
|
||||
static void app_activate(GApplication *app, gpointer data) {
|
||||
GtkWindow *window = GTK_WINDOW(gtk_window_new());
|
||||
static RsvgHandle *piecesSvg[16];
|
||||
static RsvgHandle *piecesSvg[2 * PIECES_LENGTH];
|
||||
static uint_least64_t board[BITBOARD_LENGTH];
|
||||
static struct zobristTableElement repetitionTableStore[REPETETION_TABLE_LENGTH];
|
||||
static struct gameState_t gameState;
|
||||
gameState = newGameState(board, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
|
||||
gameState = newGameState(board, repetitionTableStore, START_FEN);
|
||||
{
|
||||
static struct move_t moves[TOTAL_BOARD_SIZE];
|
||||
static struct drawData_t drawData = {piecesSvg, &gameState, NOT_SELECTED, moves, 0, NOT_SELECTED};
|
||||
|
||||
153
src/chess/move.c
153
src/chess/move.c
@ -9,6 +9,10 @@
|
||||
#include <stdio.h>
|
||||
#include "magicNumber.h"
|
||||
#include <chess/magic.h>
|
||||
#include <chess/util.h>
|
||||
#include <chess/bitset.h>
|
||||
#include <chess/zobristConsts.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
@ -29,7 +33,7 @@ static uint_least8_t trailingBits(uint_least64_t num) {
|
||||
unsigned long i;
|
||||
_BitScanForward(&i, num);
|
||||
return i;
|
||||
#elif
|
||||
#else
|
||||
uint_least8_t count = 0;
|
||||
while ((num & 1) == 0) { // Keep shifting num right until the least significant bit is 1
|
||||
num >>= 1;
|
||||
@ -133,24 +137,37 @@ bool kingInCheck(const uint_least64_t *board, const bool color) {
|
||||
return inCheck(board, bitboardGetMask(board, king), color);
|
||||
}
|
||||
|
||||
static uint_least8_t getCastleRankI(bool color) {
|
||||
uint_least8_t getBaseRank(bool color) {
|
||||
if(color == WHITE) {
|
||||
return (BOARD_SIZE - 1) * BOARD_SIZE;
|
||||
return BOARD_SIZE - 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint_least8_t getBaseRankI(bool color) {
|
||||
return getBaseRank(color) * BOARD_SIZE;
|
||||
}
|
||||
|
||||
static void castleMoveRook(uint_least64_t *board, uint_least8_t spezialMove, bool color, bool undo) {
|
||||
if(spezialMove == SHORT_CASTLE || spezialMove == LONG_CASTLE) {
|
||||
const uint_least8_t rookSrc = getCastleRankI(color) + (spezialMove == SHORT_CASTLE ? BOARD_SIZE - 1 : 0);
|
||||
const uint_least8_t rookDst = getCastleRankI(color) + (spezialMove == SHORT_CASTLE ? 5 : 3);
|
||||
const uint_least8_t rookSrc = getBaseRankI(color) + (spezialMove == SHORT_CASTLE ? BOARD_SIZE - 1 : 0);
|
||||
const uint_least8_t rookDst = getBaseRankI(color) + (spezialMove == SHORT_CASTLE ? 5 : 3);
|
||||
const struct piece_t rook = {ROOK, color};
|
||||
bitboardSet(board, rook, undo ? rookSrc : rookDst);
|
||||
bitboardClear(board, rook, undo ? rookDst : rookSrc);
|
||||
}
|
||||
}
|
||||
|
||||
void undoMove(uint_least64_t *board, struct move_t move, bool color) {
|
||||
static void zobristTableUndoMoveCallback(struct zobristTableElement *element, const uint_least64_t key,
|
||||
void *resultVoid) {
|
||||
if(element->hash == key) {
|
||||
if(--element->value == 0) element->hash = 0;
|
||||
}
|
||||
fprintf(stderr, "Error: Position does not in repetitionTable in undoMove (move.c)");
|
||||
}
|
||||
|
||||
void undoMove(const struct gameState_t gameState, struct move_t move, bool color) {
|
||||
uint_least64_t *board = gameState.board;
|
||||
const struct piece_t srcPiece = {move.srcPiece, color};
|
||||
const struct piece_t dstPiece = {move.dstPiece, color};
|
||||
bitboardSet(board, srcPiece, move.src);
|
||||
@ -160,38 +177,95 @@ void undoMove(uint_least64_t *board, struct move_t move, bool color) {
|
||||
bitboardSet(board, piece, getCapturePos(move.src, move.dst, move.spezialMove));
|
||||
}
|
||||
castleMoveRook(board, move.spezialMove, color, true);
|
||||
zobristTableIter(gameState.repetitionTable, gameState.zobrist, NULL, zobristTableUndoMoveCallback);
|
||||
}
|
||||
|
||||
static void setCastleInfoRook(struct castle_t *canCastle, uint_least8_t posOnRank) {
|
||||
switch(posOnRank) {
|
||||
case 0:
|
||||
canCastle->longCastle = false;
|
||||
break;
|
||||
case BOARD_SIZE - 1:
|
||||
canCastle->shortCastle = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void setCastleInfo(struct castle_t *canCastle, const bool color, const struct move_t move) {
|
||||
{
|
||||
struct castle_t *canCastleCurrentPlayer = canCastle + color;
|
||||
if(canCastleCurrentPlayer->longCastle || canCastleCurrentPlayer->shortCastle) {
|
||||
if(move.dstPiece == KING) {
|
||||
canCastleCurrentPlayer->longCastle = false;
|
||||
canCastleCurrentPlayer->shortCastle = false;
|
||||
}
|
||||
else {
|
||||
setCastleInfoRook(canCastleCurrentPlayer, move.src - getBaseRankI(color));
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
// if the rook is captured
|
||||
struct castle_t *canCastleOther = canCastle + !color;
|
||||
if(canCastleOther->longCastle || canCastleOther->shortCastle) {
|
||||
setCastleInfoRook(canCastleOther, move.dst - getBaseRankI(!color));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void addMove(struct addMoveCtx_t ctx, struct piece_t movedPiece, uint_least8_t src,
|
||||
uint_least8_t dst, uint_least8_t spezialMove, uint_least8_t promotionPiece) {
|
||||
uint_least64_t *board = ctx.gameState.board;
|
||||
uint_least8_t *movesLength = ctx.movesLength;
|
||||
uint_least8_t capturedPiece = NOT_SELECTED;
|
||||
const uint_least8_t dstPiece = promotionPiece == NOT_SELECTED ? movedPiece.type : promotionPiece;
|
||||
const struct piece_t pieceAll = {ALL_PIECES, movedPiece.color};
|
||||
if(bitboardGet(board, pieceAll, dst)) return;
|
||||
struct gameState_t gameState = ctx.gameState;
|
||||
uint_least64_t *board = gameState.board;
|
||||
{
|
||||
const struct piece_t pieceAllOtherColor = {ALL_PIECES, !movedPiece.color};
|
||||
const struct piece_t pieceAll = {ALL_PIECES, movedPiece.color};
|
||||
if(bitboardGet(board, pieceAll, dst)) return;
|
||||
}
|
||||
uint_least8_t *movesLength = ctx.movesLength;
|
||||
uint_least8_t capturedPieceType = NOT_SELECTED;
|
||||
const uint_least8_t dstPieceType = promotionPiece == NOT_SELECTED ? movedPiece.type : promotionPiece;
|
||||
uint_least64_t hash = ZOBRIST_PIECE[zobristPieceI(movedPiece, src)] ^ ZOBRIST_BLACK_MOVE;
|
||||
{
|
||||
const struct piece_t dstPiece = {dstPieceType, movedPiece.color};
|
||||
hash ^= ZOBRIST_PIECE[zobristPieceI(dstPiece, dst)];
|
||||
}
|
||||
{
|
||||
const bool otherColor = !movedPiece.color;
|
||||
const struct piece_t pieceAllOtherColor = {ALL_PIECES, otherColor};
|
||||
const uint_least8_t capturePos = getCapturePos(src, dst, spezialMove);
|
||||
if(bitboardGet(board, pieceAllOtherColor, capturePos)) {
|
||||
for(uint_least8_t pieceType = QUEEN; pieceType < PIECES_LENGTH; ++pieceType) {
|
||||
const struct piece_t piece = {pieceType, !movedPiece.color};
|
||||
if(bitboardGet(board, piece, capturePos)) {
|
||||
capturedPiece = pieceType;
|
||||
const struct piece_t capturedPiece = {pieceType, otherColor};
|
||||
hash ^= ZOBRIST_PIECE[zobristPieceI(capturedPiece, dst)];
|
||||
capturedPieceType = pieceType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const struct move_t move = {src, dst, spezialMove, movedPiece.type, dstPiece, capturedPiece};
|
||||
makeMove(ctx.gameState, move);
|
||||
if(gameState.enPassantTo != NOT_SELECTED) hash ^= ZOBRIST_EN_PASSENT_FILE[getFile(gameState.enPassantTo)];
|
||||
if(spezialMove == FUTURE_EN_PASSANT) hash ^= ZOBRIST_EN_PASSENT_FILE[getFile(src)];
|
||||
struct move_t move = {src, dst, spezialMove, movedPiece.type, dstPieceType, capturedPieceType, hash};
|
||||
{
|
||||
const bool color = ctx.gameState.color;
|
||||
struct castle_t canCastle[2];
|
||||
memcpy(canCastle, gameState.canCastle, sizeof canCastle);
|
||||
setCastleInfo(canCastle, gameState.color, move);
|
||||
for(uint_least8_t color = BLACK; color <= WHITE; ++color) {
|
||||
if(canCastle[color].shortCastle != gameState.canCastle[color].shortCastle) hash ^= ZOBRIST_SHORT_CASTLE[color];
|
||||
if(canCastle[color].longCastle != gameState.canCastle[color].longCastle) hash ^= ZOBRIST_LONG_CASTLE[color];
|
||||
}
|
||||
move.hash = hash;
|
||||
}
|
||||
struct gameState_t newGameState = makeMove(gameState, move);
|
||||
{
|
||||
const bool color = gameState.color;
|
||||
if(!kingInCheck(board, color)) {
|
||||
ctx.moves[*movesLength] = move;
|
||||
++*movesLength;
|
||||
}
|
||||
undoMove(board, move, color);
|
||||
undoMove(newGameState, move, color);
|
||||
}
|
||||
}
|
||||
|
||||
@ -248,7 +322,7 @@ uint_least8_t pieceValidMoves(struct gameState_t gameState, struct piece_t piece
|
||||
addPreGenMoves(addmoveCtx, KING_MOVES, src, piece);
|
||||
const struct castle_t canCastle = gameState.canCastle[piece.color];
|
||||
const uint_least64_t allPiecesMask = bitboardMaskAllPieces(board);
|
||||
const uint_least8_t rankShift = piece.color == WHITE ? BOARD_SIZE * (BOARD_SIZE - 1) : 0;
|
||||
const uint_least8_t rankShift = getBaseRankI(piece.color);
|
||||
const uint_least64_t shortCastleMask = (uint_least64_t)0b11 << (5 + rankShift);
|
||||
const uint_least64_t shortCastleCheckMask = (uint_least64_t)0b11 << (4 + rankShift);
|
||||
const uint_least64_t longCastleMask = (uint_least64_t)0b111 << (1 + rankShift);
|
||||
@ -299,7 +373,7 @@ uint_least8_t pieceValidMoves(struct gameState_t gameState, struct piece_t piece
|
||||
uint_least8_t dst = src + DIRECTION_MODIFIER[pawnDirection];
|
||||
if(!bitboardGetAllPieces(board, dst)) {
|
||||
movePawn(addmoveCtx, piece.color, src, dst, promotion, 0);
|
||||
if(src / BOARD_SIZE == (piece.color == WHITE ? BOARD_SIZE - 2 : 1)) {
|
||||
if(getRank(src) == (piece.color == WHITE ? BOARD_SIZE - 2 : 1)) {
|
||||
dst += DIRECTION_MODIFIER[pawnDirection];
|
||||
if(!bitboardGetAllPieces(board, dst)) {
|
||||
movePawn(addmoveCtx, piece.color, src, dst, promotion, FUTURE_EN_PASSANT);
|
||||
@ -333,18 +407,17 @@ uint_least8_t validMoves(struct gameState_t gameState, struct move_t *moves) {
|
||||
return movesLength;
|
||||
}
|
||||
|
||||
static void setCastleInfo(struct castle_t *canCastle, uint_least8_t posOnRank) {
|
||||
switch(posOnRank) {
|
||||
case 0:
|
||||
canCastle->longCastle = false;
|
||||
break;
|
||||
case BOARD_SIZE - 1:
|
||||
canCastle->shortCastle = false;
|
||||
break;
|
||||
static void zobristTableMakeMoveCallback(struct zobristTableElement *element, const uint_least64_t key,
|
||||
void *resultVoid) {
|
||||
if(element->hash == key) {
|
||||
++element->value;
|
||||
return;
|
||||
}
|
||||
element->hash = key;
|
||||
element->value = 1;
|
||||
}
|
||||
|
||||
struct gameState_t makeMove(struct gameState_t gameState, struct move_t move) {
|
||||
struct gameState_t makeMove(struct gameState_t gameState, const struct move_t move) {
|
||||
uint_least64_t *board = gameState.board;
|
||||
const struct piece_t srcPiece = {move.srcPiece, gameState.color};
|
||||
const struct piece_t dstPiece = {move.dstPiece, gameState.color};
|
||||
@ -355,25 +428,7 @@ struct gameState_t makeMove(struct gameState_t gameState, struct move_t move) {
|
||||
bitboardClear(board, capturedPiece, getCapturePos(move.src, move.dst, move.spezialMove));
|
||||
}
|
||||
castleMoveRook(board, move.spezialMove, gameState.color, false);
|
||||
{
|
||||
struct castle_t *canCastleCurrentPlayer = gameState.canCastle + gameState.color;
|
||||
if(canCastleCurrentPlayer->longCastle || canCastleCurrentPlayer->shortCastle) {
|
||||
if(move.dstPiece == KING) {
|
||||
canCastleCurrentPlayer->longCastle = false;
|
||||
canCastleCurrentPlayer->shortCastle = false;
|
||||
}
|
||||
else {
|
||||
setCastleInfo(canCastleCurrentPlayer, move.src - getCastleRankI(gameState.color));
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
// if the rook is captured
|
||||
struct castle_t *canCastleOther = gameState.canCastle + !gameState.color;
|
||||
if(canCastleOther->longCastle || canCastleOther->shortCastle) {
|
||||
setCastleInfo(canCastleOther, move.dst - getCastleRankI(!gameState.color));
|
||||
}
|
||||
}
|
||||
setCastleInfo(gameState.canCastle, gameState.color, move);
|
||||
{
|
||||
const int_least8_t dist = (int_least8_t)move.dst - (int_least8_t)move.src;
|
||||
gameState.enPassantTo = move.spezialMove == FUTURE_EN_PASSANT ? move.src + dist / 2 : NOT_SELECTED;
|
||||
@ -381,5 +436,7 @@ struct gameState_t makeMove(struct gameState_t gameState, struct move_t move) {
|
||||
if(move.capturedPiece != NOT_SELECTED || srcPiece.type == PAWN) gameState.halfMoveCounter = 0;
|
||||
else ++gameState.halfMoveCounter;
|
||||
gameState.color = !gameState.color;
|
||||
gameState.zobrist ^= move.hash;
|
||||
zobristTableIter(gameState.repetitionTable, gameState.zobrist, NULL, zobristTableMakeMoveCallback);
|
||||
return gameState;
|
||||
}
|
||||
|
||||
@ -1,20 +1,12 @@
|
||||
#include "chess/zobristConsts.h"
|
||||
#include <ctype.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <chess/types.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
bool bitsetGet(uint_least64_t bitset, uint_least8_t i) {
|
||||
return (bitset >> i) & 1u;
|
||||
}
|
||||
|
||||
uint_least64_t bitsetClear(uint_least64_t bitset, uint_least8_t i) {
|
||||
return bitset & ~((uint_least64_t)1 << i);
|
||||
}
|
||||
|
||||
uint_least64_t bitsetSet(uint_least64_t bitset, uint_least8_t i) {
|
||||
return bitset | ((uint_least64_t)1 << i);
|
||||
}
|
||||
#include <chess/util.h>
|
||||
#include <chess/bitset.h>
|
||||
#include <chess/bitboard.h>
|
||||
|
||||
uint_least64_t bitboardGetMask(const uint_least64_t *board, struct piece_t piece) {
|
||||
return board[piece.color * PIECES_LENGTH + piece.type];
|
||||
@ -75,62 +67,80 @@ struct piece_t pieceAtField(const uint_least64_t *board, uint_least8_t i) {
|
||||
}
|
||||
}
|
||||
|
||||
struct gameState_t newGameState(uint_least64_t *board, char *FEN) {
|
||||
struct gameState_t gameState = {board};
|
||||
for(uint_least8_t i = 0; i < BITBOARD_LENGTH; ++i) {
|
||||
board[i] = 0;
|
||||
}
|
||||
for(uint_least8_t field = 0; *FEN != ' '; ++FEN) {
|
||||
struct piece_t piece;
|
||||
switch(tolower(*FEN)) {
|
||||
case 'k':
|
||||
piece.type = KING;
|
||||
break;
|
||||
case 'q':
|
||||
piece.type = QUEEN;
|
||||
break;
|
||||
case 'r':
|
||||
piece.type = ROOK;
|
||||
break;
|
||||
case 'n':
|
||||
piece.type = KNIGHT;
|
||||
break;
|
||||
case 'b':
|
||||
piece.type = BISHOP;
|
||||
break;
|
||||
case 'p':
|
||||
piece.type = PAWN;
|
||||
break;
|
||||
case '/':
|
||||
continue;
|
||||
default:
|
||||
if(isdigit(*FEN)) {
|
||||
field += *FEN - '0';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if(isalpha(*FEN)) piece.color = isupper(*FEN);
|
||||
bitboardSet(board, piece, field);
|
||||
++field;
|
||||
}
|
||||
gameState.color = *++FEN == 'w' ? WHITE : BLACK;
|
||||
FEN += 2;
|
||||
if(*FEN == '-') ++FEN;
|
||||
else {
|
||||
for(; *FEN != ' '; ++FEN) {
|
||||
struct castle_t *playerCastle = gameState.canCastle + (isupper(*FEN) ? WHITE : BLACK);
|
||||
if(tolower(*FEN) == 'k') playerCastle->shortCastle = true;
|
||||
else playerCastle->longCastle = true;
|
||||
}
|
||||
}
|
||||
if(*++FEN == '-'){
|
||||
++FEN;
|
||||
gameState.enPassantTo = NOT_SELECTED;
|
||||
}
|
||||
else {
|
||||
gameState.enPassantTo = *FEN++ - 'a';
|
||||
gameState.enPassantTo += (BOARD_SIZE - *FEN++) * BOARD_SIZE;
|
||||
}
|
||||
gameState.halfMoveCounter = atoi(++FEN);
|
||||
return gameState;
|
||||
struct gameState_t parseFen(uint_least64_t *board, const char *FEN) {
|
||||
struct gameState_t gameState = {board, 0};
|
||||
for(uint_least8_t i = 0; i < BITBOARD_LENGTH; ++i) {
|
||||
board[i] = 0;
|
||||
}
|
||||
for(uint_least8_t field = 0; *FEN != ' '; ++FEN) {
|
||||
struct piece_t piece;
|
||||
switch(tolower(*FEN)) {
|
||||
case 'k':
|
||||
piece.type = KING;
|
||||
break;
|
||||
case 'q':
|
||||
piece.type = QUEEN;
|
||||
break;
|
||||
case 'r':
|
||||
piece.type = ROOK;
|
||||
break;
|
||||
case 'n':
|
||||
piece.type = KNIGHT;
|
||||
break;
|
||||
case 'b':
|
||||
piece.type = BISHOP;
|
||||
break;
|
||||
case 'p':
|
||||
piece.type = PAWN;
|
||||
break;
|
||||
case '/':
|
||||
continue;
|
||||
default:
|
||||
if(isdigit(*FEN)) {
|
||||
field += *FEN - '0';
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if(isalpha(*FEN)) piece.color = isupper(*FEN);
|
||||
bitboardSet(board, piece, field);
|
||||
gameState.zobrist ^= ZOBRIST_PIECE[zobristPieceI(piece, field)];
|
||||
++field;
|
||||
}
|
||||
gameState.color = *++FEN == 'w' ? WHITE : BLACK;
|
||||
if(gameState.color == BLACK) gameState.zobrist ^= ZOBRIST_BLACK_MOVE;
|
||||
FEN += 2;
|
||||
if(*FEN == '-') ++FEN;
|
||||
else {
|
||||
for(; *FEN != ' '; ++FEN) {
|
||||
const bool color = isupper(*FEN) ? WHITE : BLACK;
|
||||
struct castle_t *playerCastle = gameState.canCastle + color;
|
||||
if(tolower(*FEN) == 'k') {
|
||||
playerCastle->shortCastle = true;
|
||||
gameState.zobrist ^= ZOBRIST_SHORT_CASTLE[color];
|
||||
}
|
||||
else {
|
||||
playerCastle->longCastle = true;
|
||||
gameState.zobrist ^= ZOBRIST_LONG_CASTLE[color];
|
||||
}
|
||||
}
|
||||
}
|
||||
if(*++FEN == '-'){
|
||||
++FEN;
|
||||
gameState.enPassantTo = NOT_SELECTED;
|
||||
}
|
||||
else {
|
||||
const uint_least8_t enPassentFile = *FEN++ - 'a';
|
||||
gameState.zobrist ^= ZOBRIST_EN_PASSENT_FILE[enPassentFile];
|
||||
gameState.enPassantTo = enPassentFile;
|
||||
gameState.enPassantTo += (BOARD_SIZE - *FEN++) * BOARD_SIZE;
|
||||
}
|
||||
gameState.halfMoveCounter = atoi(++FEN);
|
||||
return gameState;
|
||||
}
|
||||
|
||||
struct gameState_t newGameState(uint_least64_t *board,
|
||||
struct zobristTableElement *repetitionTableStore, const char *FEN) {
|
||||
struct gameState_t gameState = parseFen(board, FEN);
|
||||
gameState.repetitionTable = initZobirstTable(repetitionTableStore, REPETETION_TABLE_LENGTH);
|
||||
return gameState;
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
#include <stdint.h>
|
||||
#include <chess/types.h>
|
||||
#include <chess/generated/moveConsts.h>
|
||||
#include <chess/bitboard.h>
|
||||
#include <chess/bitset.h>
|
||||
|
||||
uint_least64_t slidingMovementMask(const uint_least8_t *direction, uint_least8_t directionLength,
|
||||
uint_least8_t field, uint_least64_t blockMask) {
|
||||
14
src/common/preCodeGen/bitset.c
Normal file
14
src/common/preCodeGen/bitset.c
Normal file
@ -0,0 +1,14 @@
|
||||
#include <chess/bitset.h>
|
||||
|
||||
bool bitsetGet(uint_least64_t bitset, uint_least8_t i) {
|
||||
return (bitset >> i) & 1u;
|
||||
}
|
||||
|
||||
uint_least64_t bitsetClear(uint_least64_t bitset, uint_least8_t i) {
|
||||
return bitset & ~((uint_least64_t)1 << i);
|
||||
}
|
||||
|
||||
uint_least64_t bitsetSet(uint_least64_t bitset, uint_least8_t i) {
|
||||
return bitset | ((uint_least64_t)1 << i);
|
||||
}
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
#include "chess/types.h"
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <chess/bitboard.h>
|
||||
#include <chess/bitset.h>
|
||||
#include <chess/util.h>
|
||||
|
||||
void printerll(FILE *file, long long num) {
|
||||
fprintf(file, "%lld", num);
|
||||
@ -23,8 +24,8 @@ void printPieceMask(uint_least64_t mask) {
|
||||
}
|
||||
|
||||
void fieldToString(uint_least8_t field, char *output) {
|
||||
output[0] = 'a' + field % BOARD_SIZE;
|
||||
output[1] = '0' + BOARD_SIZE - field / BOARD_SIZE;
|
||||
output[0] = 'a' + getFile(field);
|
||||
output[1] = '0' + BOARD_SIZE - getRank(field);
|
||||
output[2] = '\0';
|
||||
}
|
||||
|
||||
78
src/common/preCodeGen/util.c
Normal file
78
src/common/preCodeGen/util.c
Normal file
@ -0,0 +1,78 @@
|
||||
#include "chess/types.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <chess/util.h>
|
||||
|
||||
uint_least64_t rand_64() {
|
||||
uint_least64_t u1, u2, u3, u4;
|
||||
u1 = (uint_least64_t)(rand()) & 0xFFFF; u2 = (uint_least64_t)(rand()) & 0xFFFF;
|
||||
u3 = (uint_least64_t)(rand()) & 0xFFFF; u4 = (uint_least64_t)(rand()) & 0xFFFF;
|
||||
return u1 | (u2 << 16) | (u3 << 32) | (u4 << 48);
|
||||
}
|
||||
|
||||
uint_least64_t zobristPieceI(struct piece_t piece, uint_least8_t field) {
|
||||
return field * 2 * PIECES_LENGTH + piece.color * PIECES_LENGTH + piece.type;
|
||||
}
|
||||
|
||||
uint_least8_t getFile(const uint_least8_t field) {
|
||||
return field % BOARD_SIZE;
|
||||
}
|
||||
|
||||
uint_least8_t getRank(const uint_least8_t field) {
|
||||
return field / BOARD_SIZE;
|
||||
}
|
||||
|
||||
struct zobristTable initZobirstTable(struct zobristTableElement *table, size_t length) {
|
||||
for(size_t i = 0; i < length; ++i) {
|
||||
table[i].hash = 0;
|
||||
}
|
||||
return (struct zobristTable){table, length};
|
||||
}
|
||||
|
||||
static size_t zobristTableInitialI(const struct zobristTable table, const uint_least64_t key) {
|
||||
return key % table.length;
|
||||
}
|
||||
|
||||
void zobristTableIter(const struct zobristTable table, const uint_least64_t key, void *result,
|
||||
void (*callback)(struct zobristTableElement *element, const uint_least64_t key, void *result)) {
|
||||
const size_t initI = zobristTableInitialI(table, key);
|
||||
size_t i = initI;
|
||||
do {
|
||||
struct zobristTableElement *element = table.arr + i;
|
||||
if(element->hash == 0 || element->hash == key) {
|
||||
callback(element, key, result);
|
||||
}
|
||||
++i;
|
||||
if(i >= table.length) i = 0;
|
||||
} while(i != initI);
|
||||
fprintf(stderr, "Error: zobrist table is full");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static void zobristTableItemPtrCallback(struct zobristTableElement *element, const uint_least64_t key,
|
||||
void *resultVoid) {
|
||||
struct zobristTableElement **result = resultVoid;
|
||||
if(element->hash == key) {
|
||||
*result = element;
|
||||
return;
|
||||
} // if(hash == 0)
|
||||
*result = NULL;
|
||||
}
|
||||
|
||||
static struct zobristTableElement* zobristTableItemPtr(const struct zobristTable table, const uint_least64_t key) {
|
||||
struct zobristTableElement *result;
|
||||
zobristTableIter(table, key, &result, zobristTableItemPtrCallback);
|
||||
return result;
|
||||
}
|
||||
|
||||
void zobristTableDelete(struct zobristTable table, const uint_least64_t key) {
|
||||
struct zobristTableElement *element = zobristTableItemPtr(table, key);
|
||||
if(element != NULL) element->hash = 0;
|
||||
}
|
||||
|
||||
int_least16_t* zobristTableGetPtr(const struct zobristTable table, const uint_least64_t key) {
|
||||
struct zobristTableElement *element = zobristTableItemPtr(table, key);
|
||||
if(element == NULL) return NULL;
|
||||
return &element->value;
|
||||
}
|
||||
@ -9,18 +9,13 @@
|
||||
#include <chess/magic.h>
|
||||
#include <chess/generated/moveConsts.h>
|
||||
#include <chess/bitboard.h>
|
||||
#include <chess/bitset.h>
|
||||
#include <chess/util.h>
|
||||
|
||||
#define MAX_BITS 12
|
||||
#define MAX_SIZE (1 << MAX_BITS)
|
||||
#define ATTACK_TABLE_LENGTH (2 * MAX_SIZE * TOTAL_BOARD_SIZE)
|
||||
|
||||
static uint_least64_t rand_64() {
|
||||
uint_least64_t u1, u2, u3, u4;
|
||||
u1 = (uint_least64_t)(rand()) & 0xFFFF; u2 = (uint_least64_t)(rand()) & 0xFFFF;
|
||||
u3 = (uint_least64_t)(rand()) & 0xFFFF; u4 = (uint_least64_t)(rand()) & 0xFFFF;
|
||||
return u1 | (u2 << 16) | (u3 << 32) | (u4 << 48);
|
||||
}
|
||||
|
||||
static uint_least64_t randFewBits() {
|
||||
return rand_64() & rand_64() & rand_64();
|
||||
}
|
||||
|
||||
@ -2,8 +2,9 @@
|
||||
#include <chess/types.h>
|
||||
#include <stdint.h>
|
||||
#include <inttypes.h>
|
||||
#include <chess/bitboard.h>
|
||||
#include <chess/bitset.h>
|
||||
#include <chess/print.h>
|
||||
#include <chess/util.h>
|
||||
|
||||
static void printerMoveDst(FILE *file, struct moveDst_t moveDst) {
|
||||
fprintf(file, "{ %" PRIuLEAST8 ", ", moveDst.length);
|
||||
@ -36,8 +37,8 @@ int main() {
|
||||
);
|
||||
uint_least8_t directionOffset[TOTAL_BOARD_SIZE * DIRECTION_LENGTH];
|
||||
for(uint_least16_t field = 0; field < TOTAL_BOARD_SIZE; ++field) {
|
||||
uint_least8_t file = field % BOARD_SIZE;
|
||||
uint_least8_t rank = field / BOARD_SIZE;
|
||||
uint_least8_t file = getFile(field);
|
||||
uint_least8_t rank = getRank(field);
|
||||
uint_least8_t *offsetField = directionOffset + field * DIRECTION_LENGTH;
|
||||
offsetField[NORTH] = rank;
|
||||
offsetField[WEST] = file;
|
||||
|
||||
39
src/generateCode/zobrist.c
Normal file
39
src/generateCode/zobrist.c
Normal file
@ -0,0 +1,39 @@
|
||||
#include "chess/types.h"
|
||||
#include "chess/zobristConsts.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <chess/util.h>
|
||||
#include <stdlib.h>
|
||||
#include <chess/print.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
int main() {
|
||||
srand(0xc0229b8e);
|
||||
printf("#include <stdint.h>\n");
|
||||
{
|
||||
uint_least64_t zobristPiece[ZOBRIST_PIECE_LENGTH];
|
||||
for(uint_least8_t color = BLACK; color <= WHITE; ++color) {
|
||||
for(uint_least8_t pieceType = KING; pieceType < PIECES_LENGTH; ++pieceType) {
|
||||
const struct piece_t piece = {pieceType, color};
|
||||
for(uint_least8_t field = 0; field < TOTAL_BOARD_SIZE; ++field) {
|
||||
zobristPiece[zobristPieceI(piece, field)] = rand_64();
|
||||
}
|
||||
}
|
||||
}
|
||||
defineArray("const uint_least64_t ZOBRIST_PIECE", printerull, zobristPiece);
|
||||
}
|
||||
printf("const uint_least64_t ZOBRIST_BLACK_MOVE = %" PRIuLEAST64 "u;\n", rand_64());
|
||||
{
|
||||
uint_least64_t zobristRest[4 + BOARD_SIZE]; // 4 for castling
|
||||
for(uint_least8_t i = 0; i < LENGTH(zobristRest); ++i) {
|
||||
zobristRest[i] = rand_64();
|
||||
}
|
||||
uint_least64_t *zobristRestPtr = zobristRest;
|
||||
const size_t castleLength = 2;
|
||||
defineArrayCustomLength("const uint_least64_t ZOBRIST_SHORT_CASTLE", printerull, zobristRestPtr, castleLength);
|
||||
zobristRestPtr += castleLength;
|
||||
defineArrayCustomLength("const uint_least64_t ZOBRIST_LONG_CASTLE", printerull, zobristRestPtr, castleLength);
|
||||
zobristRestPtr += castleLength;
|
||||
defineArrayCustomLength("const uint_least64_t ZOBRIST_EN_PASSENT_FILE", printerull, zobristRestPtr, (size_t)BOARD_SIZE);
|
||||
}
|
||||
}
|
||||
72
src/uci.c
Normal file
72
src/uci.c
Normal file
@ -0,0 +1,72 @@
|
||||
#include "chess/move.h"
|
||||
#include "chess/types.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdatomic.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include <chess/bitboard.h>
|
||||
|
||||
atomic_bool searchShouldStop;
|
||||
|
||||
#define customStrCmp(input, cmd) customStrCmpFunc(&(input), cmd, sizeof(cmd))
|
||||
static bool customStrCmpFunc(char **input, const char *cmd, const size_t length) {
|
||||
bool result = strncmp(*input, cmd, length) == 0;
|
||||
if(result) *input += length + 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
int main() {
|
||||
char input[4096];
|
||||
struct gameState_t gameState;
|
||||
uint_least64_t board[TOTAL_BOARD_SIZE];
|
||||
gameState.repetitionTable = (struct zobristTable){NULL};
|
||||
initMagicTable();
|
||||
while(true) {
|
||||
char *inputPtr = input;
|
||||
if(fgets(input, sizeof input, stdin) == NULL) {
|
||||
perror("Error: reading stdin: ");
|
||||
continue;
|
||||
}
|
||||
if(customStrCmp(inputPtr, "uci")) {
|
||||
printf(
|
||||
"id name NoNameChessEngine\n"
|
||||
"id author MrGeorgen\n"
|
||||
"uciok\n"
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if(customStrCmp(inputPtr, "isready")) {
|
||||
printf("readyok\n");
|
||||
continue;
|
||||
}
|
||||
if(customStrCmp(inputPtr, "position")) {
|
||||
if(gameState.repetitionTable.arr == NULL) {
|
||||
fprintf(stderr, "Error: required uicnewgame before a position command\n");
|
||||
continue;
|
||||
}
|
||||
if(customStrCmp(inputPtr, "startpos")) {
|
||||
gameState = parseFen(board, START_FEN);
|
||||
continue;
|
||||
}
|
||||
if(customStrCmp(inputPtr, "fen")) {
|
||||
gameState = parseFen(board, inputPtr);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if(customStrCmp(inputPtr, "quit")) {
|
||||
return 0;
|
||||
}
|
||||
if(customStrCmp(inputPtr, "go")) {
|
||||
while(*inputPtr != '\0') {
|
||||
//if(customStrCmp(inputPtr, "wtime"))
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if(customStrCmp(inputPtr, "stop")) {
|
||||
searchShouldStop = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,14 +21,15 @@ static uint_least64_t perft(const struct gameState_t gameState, const uint_least
|
||||
for(uint_least8_t i = 0; i < movesLength; ++i) {
|
||||
const struct gameState_t newGameState = makeMove(gameState, moves[i]);
|
||||
nodes += perft(newGameState, depth - 1);
|
||||
undoMove(gameState.board, moves[i], gameState.color);
|
||||
undoMove(newGameState, moves[i], gameState.color);
|
||||
}
|
||||
return nodes;
|
||||
}
|
||||
|
||||
static void test(struct perf_t perf, const uint_least8_t i) {
|
||||
uint_least64_t board[BITBOARD_LENGTH];
|
||||
const struct gameState_t gameState = newGameState(board, perf.FEN);
|
||||
struct zobristTableElement repetitionTableStore[REPETETION_TABLE_LENGTH];
|
||||
const struct gameState_t gameState = newGameState(board, repetitionTableStore, perf.FEN);
|
||||
const uint_least64_t nodes = perft(gameState, perf.depth);
|
||||
if(perf.nodes != nodes) {
|
||||
printf("Test %" PRIuLEAST16 " failed: FEN: %s, depth: %" PRIuLEAST8 " calculated nodes: %" PRIuLEAST64
|
||||
@ -38,7 +39,7 @@ static void test(struct perf_t perf, const uint_least8_t i) {
|
||||
for(uint_least8_t j = 0; j < movesLength; ++j) {
|
||||
const struct gameState_t newGameState = makeMove(gameState, moves[j]);
|
||||
const uint_least64_t nodes = perft(newGameState, perf.depth - 1);
|
||||
undoMove(gameState.board, moves[j], gameState.color);
|
||||
undoMove(newGameState, moves[j], gameState.color);
|
||||
{
|
||||
printMove(moves[j]);
|
||||
printf(" %" PRIuLEAST64 "\n", nodes);
|
||||
|
||||
Reference in New Issue
Block a user