diff --git a/ChessAndroid/app/CMakeLists.txt b/ChessAndroid/app/CMakeLists.txt index 6f921b1..54d1e6d 100644 --- a/ChessAndroid/app/CMakeLists.txt +++ b/ChessAndroid/app/CMakeLists.txt @@ -5,11 +5,6 @@ cmake_minimum_required(VERSION 3.4.1) -# Creates and names a library, sets it as either STATIC -# or SHARED, and provides the relative paths to its source code. -# You can define multiple libraries, and CMake builds them for you. -# Gradle automatically packages shared libraries with your APK. - set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS} -g -O0 -std=c++17 -Wall -Wextra -pedantic") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS} -O3 -std=c++17 -Wall -Wextra -pedantic -DNDEBUG") @@ -37,10 +32,6 @@ find_library( # Sets the name of the path variable. # you want CMake to locate. log) -# Specifies libraries CMake should link to your target library. You -# can link multiple libraries, such as libraries you define in this -# build script, prebuilt third-party libraries, or system libraries. - target_link_libraries( # Specifies the target library. chess diff --git a/ChessAndroid/app/build.gradle b/ChessAndroid/app/build.gradle index d529987..a6329a6 100644 --- a/ChessAndroid/app/build.gradle +++ b/ChessAndroid/app/build.gradle @@ -9,8 +9,8 @@ android { applicationId "net.theluckycoder.chess" minSdkVersion 21 targetSdkVersion 29 - versionCode 26 - versionName "0.26" + versionCode 100 + versionName "1.0" } buildTypes { @@ -31,6 +31,8 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2" + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3' + + implementation 'androidx.preference:preference:1.1.0' implementation 'com.jaredrummler:colorpicker:1.1.0' } diff --git a/ChessAndroid/app/src/main/cpp/Main.cpp b/ChessAndroid/app/src/main/cpp/Main.cpp index 8a7fedb..00e1723 100644 --- a/ChessAndroid/app/src/main/cpp/Main.cpp +++ b/ChessAndroid/app/src/main/cpp/Main.cpp @@ -9,37 +9,36 @@ #include "chess/BoardManager.h" #include "chess/Stats.h" #include "chess/data/Board.h" -#include "chess/algorithm/Evaluation.h" #include "chess/persistence/MovesPersistence.h" -#include "chess/algorithm/NegaMax.h" -#include "chess/algorithm/PieceAttacks.h" +#include "chess/algorithm/Search.h" -JavaVM *jvm = nullptr; -jobject gameManagerInstance; +static JavaVM *jvm = nullptr; +static jobject gameManagerInstance; const BoardManager::PieceChangeListener listener = [](State state, bool shouldRedraw, - const StackVector &moved) + const std::vector> &moved) { JNIEnv *env; int getEnvStat = jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); if (getEnvStat == JNI_EDETACHED) { jvm->AttachCurrentThread(&env, nullptr); - LOGI("ChessCpp", "Attached to Thread"); + LOGD("ChessCpp", "Attached to Thread"); } env->ExceptionClear(); - jobjectArray result = env->NewObjectArray(static_cast(moved.size()), Cache::posPairClass, nullptr); + jobjectArray result = env->NewObjectArray(static_cast(moved.size()), Cache::posPairClass, + nullptr); - const static auto constructorId = env->GetMethodID(Cache::posPairClass, "", "(BBBB)V"); + const static auto constructorId = env->GetMethodID(Cache::posPairClass, "", "(IIII)V"); for (unsigned i = 0; i < moved.size(); ++i) { - const Pos &startPos = moved[i].first; - const Pos &destPos = moved[i].second; + const Pos &startPos = Pos(moved[i].first); + const Pos &destPos = Pos(moved[i].second); jobject obj = env->NewObject(Cache::posPairClass, constructorId, - startPos.x, startPos.y, destPos.x, destPos.y); + startPos.x, startPos.y, destPos.x, destPos.y); env->SetObjectArrayElement(result, i, obj); } @@ -47,12 +46,12 @@ const BoardManager::PieceChangeListener listener = [](State state, bool shouldRe const static auto callbackId = env->GetMethodID(Cache::gameManagerClass, "callback", "(IZ[Lnet/theluckycoder/chess/PosPair;)V"); env->CallVoidMethod(gameManagerInstance, callbackId, - static_cast(state), static_cast(shouldRedraw), result); + static_cast(state), static_cast(shouldRedraw), result); if (getEnvStat == JNI_EDETACHED) { jvm->DetachCurrentThread(); - LOGI("ChessCpp", "Detached from Thread"); + LOGD("ChessCpp", "Detached from Thread"); } }; @@ -61,7 +60,7 @@ external JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void */*reserved*/) LOGI("ChessCpp", "JNI_OnLoad"); JNIEnv *env; - if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) return -1; jvm = vm; @@ -87,7 +86,8 @@ external JNIEXPORT void JNI_OnUnload(JavaVM *vm, void */*reserved*/) external JNIEXPORT void JNICALL Java_net_theluckycoder_chess_GameManager_initBoardNative(JNIEnv *pEnv, jobject instance, - jboolean restartGame, jboolean isPlayerWhite) + jboolean restartGame, + jboolean isPlayerWhite) { static bool boardManagerInitialized = false; pEnv->ExceptionClear(); @@ -96,7 +96,8 @@ Java_net_theluckycoder_chess_GameManager_initBoardNative(JNIEnv *pEnv, jobject i { pEnv->DeleteGlobalRef(gameManagerInstance); gameManagerInstance = pEnv->NewGlobalRef(instance); - Cache::gameManagerClass = Cache::cacheClass(pEnv, pEnv->GetObjectClass(gameManagerInstance)); + Cache::gameManagerClass = Cache::cacheClass(pEnv, + pEnv->GetObjectClass(gameManagerInstance)); boardManagerInitialized = true; BoardManager::initBoardManager(listener); @@ -120,35 +121,51 @@ Java_net_theluckycoder_chess_Native_isPlayerWhite(JNIEnv */*pEnv*/, jclass /*typ return static_cast(BoardManager::isPlayerWhite()); } +// region Stats -external JNIEXPORT jstring JNICALL -Java_net_theluckycoder_chess_Native_getStats(JNIEnv *pEnv, jclass /*type*/) +external JNIEXPORT jdouble JNICALL +Java_net_theluckycoder_chess_Native_getSearchTime(JNIEnv */*pEnv*/, jclass /*type*/) { - return pEnv->NewStringUTF(Stats::formatStats('\n').c_str()); + return static_cast(Stats::getElapsedTime()); } external JNIEXPORT jint JNICALL -Java_net_theluckycoder_chess_Native_getBoardValue(JNIEnv */*pEnv*/, jclass /*type*/) +Java_net_theluckycoder_chess_Native_getCurrentBoardValue(JNIEnv */*pEnv*/, jclass /*type*/) { return static_cast(BoardManager::getBoard().score); } +external JNIEXPORT jint JNICALL +Java_net_theluckycoder_chess_Native_getBestMoveFound(JNIEnv */*pEnv*/, jclass /*type*/) +{ + return static_cast(Search::getBestMoveFound()); +} + +external JNIEXPORT jstring JNICALL +Java_net_theluckycoder_chess_Native_getAdvancedStats(JNIEnv *pEnv, jclass /*type*/) +{ + return pEnv->NewStringUTF(Stats::formatStats('\n').c_str()); +} + +// endregion Stats + external JNIEXPORT _jobjectArray *JNICALL Java_net_theluckycoder_chess_Native_getPieces(JNIEnv *pEnv, jclass /*type*/) { pEnv->ExceptionClear(); - const static auto constructorId = pEnv->GetMethodID(Cache::pieceClass, "", "(BBB)V"); + const static auto constructorId = pEnv->GetMethodID(Cache::pieceClass, "", "(IIB)V"); const auto pieces = BoardManager::getBoard().getAllPieces(); - auto *array = pEnv->NewObjectArray(static_cast(pieces.size()), Cache::pieceClass, nullptr); + auto *array = pEnv->NewObjectArray(static_cast(pieces.size()), Cache::pieceClass, + nullptr); jsize i = 0; for (const auto &it : pieces) { const Pos &pos = it.first; - auto pieceType = static_cast(it.second.type); - if (!it.second.isWhite) + auto pieceType = static_cast(it.second.type()); + if (it.second.color() == BLACK) pieceType += 6; jobject obj = pEnv->NewObject(Cache::pieceClass, constructorId, pos.x, pos.y, pieceType); @@ -165,15 +182,20 @@ Java_net_theluckycoder_chess_Native_getPossibleMoves(JNIEnv *pEnv, jclass /*type { pEnv->ExceptionClear(); - const static auto constructorId = pEnv->GetMethodID(Cache::posClass, "", "(BB)V"); + const static auto constructorId = pEnv->GetMethodID(Cache::posClass, "", "(II)V"); - const Pos pos(getByte(pEnv, Cache::posClass, dest, "x"), getByte(pEnv, Cache::posClass, dest, "y")); + const Pos pos(getInt(pEnv, Cache::posClass, dest, "x"), + getInt(pEnv, Cache::posClass, dest, "y")); const auto possibleMoves = BoardManager::getPossibleMoves(pos); - auto *result = pEnv->NewObjectArray(static_cast(possibleMoves.size()), Cache::posClass, nullptr); + auto *result = pEnv->NewObjectArray(static_cast(possibleMoves.size()), Cache::posClass, + nullptr); for (unsigned i = 0; i < possibleMoves.size(); ++i) { - jobject obj = pEnv->NewObject(Cache::posClass, constructorId, possibleMoves[i].x, possibleMoves[i].y); + jobject obj = pEnv->NewObject(Cache::posClass, + constructorId, + static_cast(possibleMoves[i].x), + static_cast(possibleMoves[i].y)); pEnv->SetObjectArrayElement(result, i, obj); } @@ -188,8 +210,10 @@ Java_net_theluckycoder_chess_Native_enableStats(JNIEnv */*pEnv*/, jclass /*type* } external JNIEXPORT void JNICALL -Java_net_theluckycoder_chess_Native_setSettings(JNIEnv */*pEnv*/, jclass /*type*/, jint baseSearchDepth, - jint threadCount, jint cacheSizeMb, jboolean performQuiescenceSearch) +Java_net_theluckycoder_chess_Native_setSettings(JNIEnv */*pEnv*/, jclass /*type*/, + jint baseSearchDepth, + jint threadCount, jint cacheSizeMb, + jboolean performQuiescenceSearch) { BoardManager::setSettings(Settings(static_cast(baseSearchDepth), static_cast(threadCount), @@ -200,16 +224,12 @@ Java_net_theluckycoder_chess_Native_setSettings(JNIEnv */*pEnv*/, jclass /*type* external JNIEXPORT void JNICALL Java_net_theluckycoder_chess_Native_movePiece(JNIEnv */*pEnv*/, jclass /*type*/, - jbyte selectedX, jbyte selectedY, jbyte destX, jbyte destY) -{ - BoardManager::movePiece(Pos(static_cast(selectedX), static_cast(selectedY)), - Pos(static_cast(destX), static_cast(destY))); -} - -external JNIEXPORT jint JNICALL -Java_net_theluckycoder_chess_Native_getBestMoveFound(JNIEnv */*pEnv*/, jclass /*type*/) + jbyte selectedX, jbyte selectedY, jbyte destX, + jbyte destY) { - return static_cast(NegaMax::getBestMoveFound()); + BoardManager::movePiece( + toSquare(static_cast(selectedX), static_cast(selectedY)), + toSquare(static_cast(destX), static_cast(destY))); } @@ -223,7 +243,7 @@ external JNIEXPORT void JNICALL Java_net_theluckycoder_chess_Native_loadMoves(JNIEnv *pEnv, jclass /*type*/, jstring moves) { const char *nativeString = pEnv->GetStringUTFChars(moves, nullptr); - const MovesPersistence savedMoves = MovesPersistence(nativeString); + const MovesPersistence savedMoves(nativeString); BoardManager::loadGame(savedMoves.getMoves(), savedMoves.isPlayerWhite()); @@ -233,11 +253,12 @@ Java_net_theluckycoder_chess_Native_loadMoves(JNIEnv *pEnv, jclass /*type*/, jst external JNIEXPORT jstring JNICALL Java_net_theluckycoder_chess_Native_saveMoves(JNIEnv *pEnv, jclass /*type*/) { - const std::string string = MovesPersistence::saveToString(BoardManager::getMovesHistory(), BoardManager::isPlayerWhite()); + const std::string string = MovesPersistence::saveToString(BoardManager::getMovesHistory(), + BoardManager::isPlayerWhite()); return pEnv->NewStringUTF(string.c_str()); } -static U64 perft(const Board &board, int depth) +static U64 perft(const Board &board, const int depth) { if (depth == 0) return 1; @@ -256,7 +277,10 @@ static U64 perft(const Board &board, int depth) external JNIEXPORT void JNICALL Java_net_theluckycoder_chess_Native_perft(JNIEnv */*pEnv*/, jclass /*type*/, jint depth) { - constexpr std::array perftResults { + using namespace std::chrono; + + constexpr auto TAG = "Perft Test"; + constexpr std::array perftResults{ 1, 20, 400, 8902, 197281, 4865609, 119060324 }; constexpr int maxSize = static_cast(perftResults.size()); @@ -269,17 +293,19 @@ Java_net_theluckycoder_chess_Native_perft(JNIEnv */*pEnv*/, jclass /*type*/, jin for (int i = 0; i <= depth; ++i) { - LOGV("Perft Test", "Starting Depth %d Test", i); + LOGV(TAG, "Starting Depth %d Test", i); - const auto startTime = std::chrono::high_resolution_clock::now(); + const auto startTime = high_resolution_clock::now(); const U64 nodesCount = perft(board, i); + const auto endTime = high_resolution_clock::now(); - const auto currentTime = std::chrono::high_resolution_clock::now(); - const double timeNeeded = std::chrono::duration(currentTime - startTime).count(); + const auto timeNeeded = duration(endTime - startTime).count(); - LOGV("Perft Test", "Time needed: %lf", timeNeeded); - LOGV("Perft Test", "Nodes count: %llu", nodesCount); + LOGV(TAG, "Time needed: %lf", timeNeeded); + LOGV(TAG, "Nodes count: %llu/%llu", nodesCount, perftResults[i]); if (nodesCount != perftResults[i]) - LOGE("Perft Test Error", "Nodes count do not match at depth %d", i); + LOGE(TAG, "Nodes count do not match at depth %d", i); } + + LOGV(TAG, "Test Finished"); } diff --git a/ChessAndroid/app/src/main/cpp/chess/BoardManager.cpp b/ChessAndroid/app/src/main/cpp/chess/BoardManager.cpp index 27435c2..0141c50 100644 --- a/ChessAndroid/app/src/main/cpp/chess/BoardManager.cpp +++ b/ChessAndroid/app/src/main/cpp/chess/BoardManager.cpp @@ -1,12 +1,9 @@ #include "BoardManager.h" -#include - #include "Stats.h" #include "data/Board.h" -#include "algorithm/Evaluation.h" #include "algorithm/Hash.h" -#include "algorithm/NegaMax.h" +#include "algorithm/Search.h" #include "algorithm/PieceAttacks.h" Settings BoardManager::s_Settings(4u, std::thread::hardware_concurrency() - 1u, 100, true); @@ -20,11 +17,12 @@ void BoardManager::initBoardManager(const PieceChangeListener &listener, const b PieceAttacks::init(); s_Board.initDefaultBoard(); + //s_Board.setToFen("r1b1k2r/ppppnppp/2n2q2/2b5/3NP3/2P1B3/PP3PPP/RN1QKB1R w KQkq - 0 1"); s_Listener = listener; s_MovesHistory.clear(); s_MovesHistory.reserve(200); - s_MovesHistory.emplace_back(Pos(), Pos(), s_Board); + s_MovesHistory.emplace_back(64u, 64u, s_Board); Stats::resetStats(); @@ -34,247 +32,83 @@ void BoardManager::initBoardManager(const PieceChangeListener &listener, const b s_WorkerThread = std::thread(moveComputerPlayer, s_Settings); } -void BoardManager::loadGame(const std::vector &moves, const bool isPlayerWhite) +void BoardManager::loadGame(const std::vector> &moves, const bool isPlayerWhite) { s_IsPlayerWhite = isPlayerWhite; s_Board.initDefaultBoard(); - s_MovesHistory.emplace_back(Pos(), Pos(), s_Board); - - for (const PosPair &move : moves) - { - movePieceInternal(move.first, move.second, s_Board); - s_Board.score = Evaluation::evaluate(s_Board); - s_MovesHistory.emplace_back(move.first, move.second, s_Board); + s_MovesHistory.clear(); + s_MovesHistory.emplace_back(64u, 64u, s_Board); + + try { + for (const auto &move : moves) + { + s_Board.doMove(move.first, move.second); + s_Board.score = Evaluation::evaluate(s_Board); + s_MovesHistory.emplace_back(move.first, move.second, s_Board); + } + } catch (...) { + // Couldn't load all moves correctly, fallback to the original board + s_Board.initDefaultBoard(); + s_MovesHistory.clear(); + s_MovesHistory.emplace_back(64u, 64u, s_Board); } s_Listener(s_Board.state, true, {}); } -Piece::MaxMovesVector BoardManager::getPossibleMoves(const Pos &selectedPos) +std::vector BoardManager::getPossibleMoves(const Pos &selectedPos) { - Piece::MaxMovesVector moves; + std::vector moves; + moves.reserve(27); - const Piece &piece = s_Board[selectedPos]; - const auto possibleMoves = piece.getPossibleMoves(selectedPos, s_Board); + const byte startSq = selectedPos.toSquare(); + const Piece &piece = s_Board.getPiece(startSq); + U64 possibleMoves = piece.getPossibleMoves(startSq, s_Board); - for (const Pos &destPos : possibleMoves) + // Make sure we are not capturing the king + possibleMoves &= ~s_Board.getType(s_Board.colorToMove, KING); + + while (possibleMoves) { - const Piece &destPiece = s_Board[destPos]; - if (destPiece.type == Type::KING) - continue; + const byte destSq = Bitboard::findNextSquare(possibleMoves); Board board = s_Board; - BoardManager::movePieceInternal(selectedPos, destPos, board); + board.doMove(startSq, destSq); - if (board.state == State::INVALID) - continue; - if (piece.isWhite && (board.state == State::WHITE_IN_CHECK || board.state == State::WINNER_BLACK)) - continue; - if (!piece.isWhite && (board.state == State::BLACK_IN_CHECK || board.state == State::WINNER_WHITE)) + if (!board.hasValidState()) continue; - int count = 1; - - for (const auto &game : BoardManager::getMovesHistory()) { - if (board.whiteToMove == game.board.whiteToMove && - board.state == game.board.state && - board.key == game.board.key) - count++; - - if (count == 3) - { - board.score = 0; - board.state = State::DRAW; - break; - } - } - - moves.emplace_back(destPos); + moves.emplace_back(Pos(destSq)); } return moves; } -void BoardManager::movePiece(const Pos &selectedPos, const Pos &destPos, const bool movedByPlayer) +void BoardManager::movePiece(const byte startSq, const byte destSq, const bool movedByPlayer) { - assert(selectedPos.isValid() && destPos.isValid()); - s_Board.whiteToMove = !s_Board.whiteToMove; - s_Board.isPromotion = s_Board.isCapture = false; - bool shouldRedraw = false; - - const Pos enPassantPos = s_Board.enPassantPos; - s_Board.enPassantPos = Pos(); - Piece &selectedPiece = s_Board[selectedPos]; - - StackVector piecesMoved{ {selectedPos, destPos} }; - - const U64 selectedPosBitboard = selectedPos.toBitboard(); - const U64 destPosBitboard = destPos.toBitboard(); - const bool selectedPieceColor = selectedPiece.isWhite; - - switch (selectedPiece.type) - { - case PAWN: - shouldRedraw = movePawn(s_Board, selectedPos, destPos, enPassantPos); - s_Board.pawns[selectedPieceColor] &= ~selectedPosBitboard; - s_Board.pawns[selectedPieceColor] |= destPosBitboard; - break; - case KNIGHT: - s_Board.knights[selectedPieceColor] &= ~selectedPosBitboard; - s_Board.knights[selectedPieceColor] |= destPosBitboard; - break; - case BISHOP: - s_Board.bishops[selectedPieceColor] &= ~selectedPosBitboard; - s_Board.bishops[selectedPieceColor] |= destPosBitboard; - break; - case ROOK: - s_Board.rooks[selectedPieceColor] &= ~selectedPosBitboard; - s_Board.rooks[selectedPieceColor] |= destPosBitboard; - break; - case QUEEN: - s_Board.queens[selectedPieceColor] &= ~selectedPosBitboard; - s_Board.queens[selectedPieceColor] |= destPosBitboard; - break; - case KING: - { - s_Board.kingSquare[selectedPiece.isWhite] = destPos.toSquare(); - - if (!selectedPiece.moved) - { - const PosPair &posPair = piecesMoved.emplace_back(moveKing(selectedPiece, selectedPos, destPos, s_Board)); - if (posPair.first.isValid()) - { - if (selectedPiece.isWhite) - s_Board.whiteCastled = true; - else - s_Board.blackCastled = true; - - U64 &pieces = s_Board.allPieces[selectedPiece.isWhite]; - pieces &= ~posPair.first.toBitboard(); - pieces |= posPair.second.toBitboard(); - } - } - break; - } - case NONE: - break; - } - - s_Board.allPieces[selectedPiece.isWhite] &= ~selectedPosBitboard; // Remove selected Piece - s_Board.allPieces[selectedPiece.isWhite] |= destPosBitboard; // Add the selected Piece to destination - - selectedPiece.moved = true; + assert(startSq != destSq); + assert(startSq < 64 && destSq < 64); - if (const Piece &destPiece = s_Board[destPos]; destPiece) - { - s_Board.allPieces[destPiece.isWhite] &= ~destPos.toBitboard(); // Remove destination Piece - s_Board.npm -= Evaluation::getPieceValue(destPiece.type); - s_Board.isCapture = true; - } + const byte castledBefore = (s_Board.castlingRights & CASTLED_WHITE) | (s_Board.castlingRights & CASTLED_BLACK); + s_Board.doMove(startSq, destSq); + assert(s_Board.hasValidState()); + const byte castledAfter = (s_Board.castlingRights & CASTLED_WHITE) | (s_Board.castlingRights & CASTLED_BLACK); - s_Board[destPos] = selectedPiece; - s_Board[selectedPos] = Piece(); - - s_Board.key = Hash::compute(s_Board); - s_Board.updateState(); s_Board.score = Evaluation::evaluate(s_Board); + s_Board.zKey = Hash::compute(s_Board); + + const std::vector piecesMoved{ std::make_pair(startSq, destSq) }; + const bool shouldRedraw = s_Board.isPromotion || (castledBefore != castledAfter); - s_MovesHistory.emplace_back(selectedPos, destPos, s_Board); + s_MovesHistory.emplace_back(startSq, destSq, s_Board); s_Listener(s_Board.state, shouldRedraw, piecesMoved); if (movedByPlayer && (s_Board.state == State::NONE || s_Board.state == State::WHITE_IN_CHECK || s_Board.state == State::BLACK_IN_CHECK)) s_WorkerThread = std::thread(moveComputerPlayer, s_Settings); } -void BoardManager::movePieceInternal(const Pos &selectedPos, const Pos &destPos, Board &board, const bool updateState) -{ - board.whiteToMove = !board.whiteToMove; - Piece &selectedPiece = board[selectedPos]; - Piece &destPiece = board[destPos]; - bool hashHandled = false; - board.isPromotion = board.isCapture = false; - - const Pos enPassantPos = board.enPassantPos; - board.enPassantPos = Pos(); - - const U64 selectedPosBitboard = selectedPos.toBitboard(); - const U64 destPosBitboard = destPos.toBitboard(); - const bool selectedPieceColor = selectedPiece.isWhite; - - switch (selectedPiece.type) - { - case PAWN: - hashHandled = movePawn(board, selectedPos, destPos, enPassantPos); - board.pawns[selectedPieceColor] &= ~selectedPosBitboard; - board.pawns[selectedPieceColor] |= destPosBitboard; - break; - case KNIGHT: - board.knights[selectedPieceColor] &= ~selectedPosBitboard; - board.knights[selectedPieceColor] |= destPosBitboard; - break; - case BISHOP: - board.bishops[selectedPieceColor] &= ~selectedPosBitboard; - board.bishops[selectedPieceColor] |= destPosBitboard; - break; - case ROOK: - board.rooks[selectedPieceColor] &= ~selectedPosBitboard; - board.rooks[selectedPieceColor] |= destPosBitboard; - break; - case QUEEN: - board.queens[selectedPieceColor] &= ~selectedPosBitboard; - board.queens[selectedPieceColor] |= destPosBitboard; - break; - case KING: - { - board.kingSquare[selectedPieceColor] = destPos.toSquare(); - - if (!selectedPiece.moved) - { - const PosPair posPair = moveKing(selectedPiece, selectedPos, destPos, board); - if (posPair.first.isValid()) // Castling - { - if (selectedPiece.isWhite) - board.whiteCastled = true; - else - board.blackCastled = true; - - Hash::makeMove(board.key, posPair.first, posPair.second, Piece(Type::ROOK, selectedPieceColor)); - - U64 &pieces = board.allPieces[selectedPieceColor]; - pieces &= ~posPair.first.toBitboard(); - pieces |= posPair.second.toBitboard(); - } - } - break; - } - case NONE: - break; - } - - board.allPieces[selectedPieceColor] &= ~selectedPosBitboard; // Remove selected Piece - board.allPieces[selectedPieceColor] |= destPosBitboard; // Add the selected Piece to destination - - Hash::flipSide(board.key); - if (!hashHandled) - Hash::makeMove(board.key, selectedPos, destPos, selectedPiece, destPiece); - - if (destPiece) - { - board.allPieces[destPiece.isWhite] &= ~destPos.toBitboard(); // Remove destination Piece - board.npm -= Evaluation::getPieceValue(destPiece.type); - board.isCapture = true; - } - - selectedPiece.moved = true; - destPiece = selectedPiece; - board[selectedPos] = Piece(); - - if (updateState) - board.updateState(); -} - // This function should only be called through the Worker Thread void BoardManager::moveComputerPlayer(const Settings &settings) { @@ -282,90 +116,15 @@ void BoardManager::moveComputerPlayer(const Settings &settings) Stats::resetStats(); Stats::startTimer(); - assert(s_Board.whiteToMove != s_IsPlayerWhite); - const RootMove bestMove = NegaMax::getBestMove(s_Board, settings); + const RootMove bestMove = Search::findBestMove(s_Board, settings); Stats::stopTimer(); - s_IsWorking = false; - movePiece(bestMove.start, bestMove.dest, false); + movePiece(bestMove.startSq, bestMove.destSq, false); + s_IsWorking = false; s_WorkerThread.detach(); } -bool BoardManager::movePawn(Board &board, const Pos &startPos, const Pos &destPos, const Pos &enPassantPos) -{ - Piece &pawn = board[startPos]; - - if (pawn.moved) { - if (destPos.y == 0 || destPos.y == 7) - { - pawn.type = Type::QUEEN; - board.isPromotion = true; - - Hash::promotePawn(board.key, startPos, destPos, pawn.isWhite, Type::QUEEN); - return true; - } else if (destPos == enPassantPos) { - board.isCapture = true; - - const Pos capturedPos(enPassantPos.x, enPassantPos.y + static_cast(pawn.isWhite ? -1 : 1)); - Piece &capturedPiece = board[capturedPos]; - - // Remove the captured Pawn - Hash::xorPiece(board.key, capturedPos, capturedPiece); - board.allPieces[!pawn.isWhite] = ~capturedPos.toBitboard(); - board.npm -= Evaluation::getPieceValue(Type::PAWN); - capturedPiece = Piece(); - return true; - } - } else { - const int distance = static_cast(destPos.y) - static_cast(startPos.y); - if (distance == 2 || distance == -2) - board.enPassantPos = Pos(destPos.x, destPos.y - static_cast(distance / 2)); - } - - return false; -} - -PosPair BoardManager::moveKing(Piece &king, const Pos &selectedPos, const Pos &destPos, Board &board) -{ - if (destPos.x == 6u) - { - constexpr byte startX = 7u; - const byte y = selectedPos.y; - - Piece &rook = board.getPiece(startX, y); - if (rook.type == Type::ROOK && king.isSameColor(rook) && !rook.moved) - { - rook.moved = true; - - constexpr byte destX = 5; - board.getPiece(destX, y) = rook; - board.getPiece(startX, y) = Piece::EMPTY; - - return std::make_pair(Pos(startX, y), Pos(destX, y)); - } - } - else if (destPos.x == 2u) - { - constexpr byte startX = 0u; - const byte y = selectedPos.y; - - Piece &rook = board.getPiece(startX, y); - if (rook.type == Type::ROOK && king.isSameColor(rook) && !rook.moved) - { - rook.moved = true; - - constexpr byte destX = 3u; - board.getPiece(destX, y) = rook; - board.getPiece(startX, y) = Piece::EMPTY; - - return std::make_pair(Pos(startX, y), Pos(destX, y)); - } - } - - return std::make_pair(Pos(), Pos()); -} - void BoardManager::undoLastMoves() { if (isWorking() || s_MovesHistory.size() < 3) return; @@ -386,12 +145,12 @@ void BoardManager::undoLastMoves() // Redraw if a Promotion or castling happened in the last three moves const bool shouldRedraw = engineBoard.isPromotion || engineBoard.isCapture || playerBoard.isPromotion || playerBoard.isCapture || - engineBoard.whiteCastled != previousBoard.whiteCastled || - engineBoard.blackCastled != previousBoard.whiteCastled; + engineBoard.isCastled(WHITE) != previousBoard.isCastled(WHITE) || + engineBoard.isCastled(BLACK) != previousBoard.isCastled(BLACK); s_Board = previousBoard; s_Listener(previousBoard.state, shouldRedraw, - { { engineMove.dest, engineMove.start }, { playerMove.dest, playerMove.start } }); + { { engineMove.destSq, engineMove.startSq }, { playerMove.destSq, playerMove.startSq } }); // Remove the last two moves from the vector s_MovesHistory.pop_back(); diff --git a/ChessAndroid/app/src/main/cpp/chess/BoardManager.h b/ChessAndroid/app/src/main/cpp/chess/BoardManager.h index b20b075..5ac0304 100644 --- a/ChessAndroid/app/src/main/cpp/chess/BoardManager.h +++ b/ChessAndroid/app/src/main/cpp/chess/BoardManager.h @@ -6,8 +6,7 @@ #include #include "Settings.h" -#include "containers/StackVector.h" -#include "data/Enums.h" +#include "data/Defs.h" #include "data/Piece.h" class RootMove; @@ -16,7 +15,7 @@ class Board; class BoardManager final { public: - using PieceChangeListener = std::function &moved)>; + using PieceChangeListener = std::function> &moved)>; private: static Settings s_Settings; @@ -29,20 +28,17 @@ class BoardManager final public: static void initBoardManager(const PieceChangeListener &listener, bool isPlayerWhite = true); - static void loadGame(const std::vector &moves, bool isPlayerWhite); + static void loadGame(const std::vector> &moves, bool isPlayerWhite); static void undoLastMoves(); static Board &getBoard() { return s_Board; } static const auto &getMovesHistory() { return s_MovesHistory; } static bool isWorking() { return s_IsWorking; } static bool isPlayerWhite() { return s_IsPlayerWhite; } - static Piece::MaxMovesVector getPossibleMoves(const Pos &selectedPos); - static void movePiece(const Pos &selectedPos, const Pos &destPos, bool movedByPlayer = true); - static void movePieceInternal(const Pos &selectedPos, const Pos &destPos, Board &board, bool updateState = true); + static std::vector getPossibleMoves(const Pos &selectedPos); + static void movePiece(byte startSq, byte destSq, bool movedByPlayer = true); static void setSettings(const Settings &settings) { s_Settings = settings; } private: static void moveComputerPlayer(const Settings &settings); - static bool movePawn(Board &board, const Pos &startPos, const Pos &destPos, const Pos &enPassantPos); - static PosPair moveKing(Piece &king, const Pos &selectedPos, const Pos &destPos, Board &board); }; diff --git a/ChessAndroid/app/src/main/cpp/chess/Stats.cpp b/ChessAndroid/app/src/main/cpp/chess/Stats.cpp index 78cc33a..45ee6f3 100644 --- a/ChessAndroid/app/src/main/cpp/chess/Stats.cpp +++ b/ChessAndroid/app/src/main/cpp/chess/Stats.cpp @@ -2,45 +2,80 @@ #include -std::chrono::time_point Stats::_startTime; -std::atomic_size_t Stats::boardsEvaluated; -std::atomic_size_t Stats::nodesSearched; +std::chrono::time_point Stats::s_StartTime; +std::atomic_size_t Stats::s_BoardsEvaluated; +std::atomic_size_t Stats::s_NodesSearched; +std::atomic_size_t Stats::s_NodesGenerated; void Stats::setEnabled(const bool enabled) noexcept { - _statsEnabled = enabled; + s_StatsEnabled = enabled; } void Stats::resetStats() noexcept { - boardsEvaluated = 0; - nodesSearched = 0; + s_BoardsEvaluated = 0; + s_NodesSearched = 0; + s_NodesGenerated = 0; +} + +size_t Stats::getBoardsEvaluated() noexcept +{ + return s_BoardsEvaluated; +} + +size_t Stats::getNodesSearched() noexcept +{ + return s_NodesSearched; +} + +size_t Stats::getNodesGenerated() noexcept +{ + return s_NodesGenerated; +} + +void Stats::incrementBoardsEvaluated() noexcept +{ + if (s_StatsEnabled) + ++s_BoardsEvaluated; +} + +void Stats::incrementNodesSearched(const std::size_t amount) noexcept +{ + if (s_StatsEnabled) + s_NodesSearched += amount; +} + +void Stats::incrementNodesGenerated(const std::size_t amount) noexcept +{ + if (s_StatsEnabled) + s_NodesGenerated += amount; } void Stats::startTimer() noexcept { - _startTime = std::chrono::high_resolution_clock::now(); - _elapsedTime = 0; + s_StartTime = std::chrono::high_resolution_clock::now(); + s_ElapsedTime = 0; } void Stats::stopTimer() noexcept { const auto currentTime = std::chrono::high_resolution_clock::now(); - _elapsedTime = std::chrono::duration(currentTime - _startTime).count(); + s_ElapsedTime = std::chrono::duration(currentTime - s_StartTime).count(); } double Stats::getElapsedTime() noexcept { - return _elapsedTime; + return s_ElapsedTime; } std::string Stats::formatStats(const char separator) noexcept(false) { std::stringstream stream; - stream << "Boards Evaluated: " << static_cast(boardsEvaluated) << separator - << "Nodes Searched: " << static_cast(nodesSearched) << separator - << "Time Needed: " << _elapsedTime << " millis" << separator; + stream << "Boards Evaluated: " << static_cast(s_BoardsEvaluated) << separator + << "Nodes Searched: " << static_cast(s_NodesSearched) << separator + << "Nodes Generated: " << static_cast(s_NodesGenerated) << separator; return stream.str(); } diff --git a/ChessAndroid/app/src/main/cpp/chess/Stats.h b/ChessAndroid/app/src/main/cpp/chess/Stats.h index 1ad63fe..3a44bb4 100644 --- a/ChessAndroid/app/src/main/cpp/chess/Stats.h +++ b/ChessAndroid/app/src/main/cpp/chess/Stats.h @@ -6,22 +6,34 @@ class Stats final { - inline static bool _statsEnabled = false; - inline static double _elapsedTime = 0; - static std::chrono::time_point _startTime; + inline static bool s_StatsEnabled = false; + inline static double s_ElapsedTime = 0; + static std::chrono::time_point s_StartTime; + + static std::atomic_size_t s_BoardsEvaluated; + static std::atomic_size_t s_NodesSearched; + static std::atomic_size_t s_NodesGenerated; public: Stats() = delete; + Stats(const Stats&) = delete; + Stats(Stats&&) = delete; - static std::atomic_size_t boardsEvaluated; - static std::atomic_size_t nodesSearched; - static std::atomic_size_t allocationsCount; - - static bool enabled() noexcept { return _statsEnabled; } + static bool isEnabled() noexcept { return s_StatsEnabled; } static void setEnabled(bool enabled) noexcept; static void resetStats() noexcept; + + static size_t getBoardsEvaluated() noexcept; + static size_t getNodesSearched() noexcept; + static size_t getNodesGenerated() noexcept; + + static void incrementBoardsEvaluated() noexcept; + static void incrementNodesSearched(std::size_t amount = 1u) noexcept; + static void incrementNodesGenerated(std::size_t amount = 1u) noexcept; + static void startTimer() noexcept; static void stopTimer() noexcept; static double getElapsedTime() noexcept; + static std::string formatStats(char separator) noexcept(false); }; diff --git a/ChessAndroid/app/src/main/cpp/chess/algorithm/Evaluation.cpp b/ChessAndroid/app/src/main/cpp/chess/algorithm/Evaluation.cpp index 402d637..08f2dd5 100644 --- a/ChessAndroid/app/src/main/cpp/chess/algorithm/Evaluation.cpp +++ b/ChessAndroid/app/src/main/cpp/chess/algorithm/Evaluation.cpp @@ -3,36 +3,42 @@ #include "MoveGen.h" #include "../Stats.h" #include "../data/Psqt.h" -#include "../data/Board.h" -#include "../data/Enums.h" #define S Score +constexpr short TEMPO_BONUS = 20; +constexpr S CHECK_BONUS(30, 42); + /* * These values were imported from the Stockfish Chess Engine */ constexpr S MINOR_THREATS[] = { - S(0, 0), S(0, 31), S(39, 42), S(57, 44), S(68, 112), S(62, 120), S(0, 0) + S(0, 0), S(6, 32), S(59, 41), S(79, 56), S(90, 119), S(79, 161), S(0, 0) }; constexpr S OVERLOAD(12, 6); constexpr S PAWN_DOUBLED(11, 56); constexpr S PAWN_ISOLATED(5, 15); +constexpr short PAWN_CONNECTED[] = +{ + 0, 7, 8, 12, 29, 48, 86 +}; constexpr S PASSED_PAWN_RANK[] = { - S(0, 0), S(5, 18), S(12, 23), S(10, 31), S(57, 62), S(163, 167), S(271, 250) + S(0, 0), S(10, 28), S(17, 33), S(15, 41), S(62, 72), S(168, 177), S(276, 260) }; +constexpr S ROOK_ON_QUEEN_FILE(7, 6); constexpr S ROOK_ON_PAWN(10, 28); constexpr S ROOK_ON_FILE[] = { - S(0, 0), S(18, 7), S(44, 20) + S(0, 0), S(21, 4), S(47, 25) }; constexpr S ROOK_THREATS[] = { - S(0, 0), S(0, 24), S(38, 71), S(38, 61), S(0, 38), S(51, 38), S(0, 0) + S(0, 0), S(3, 44), S(38, 71), S(38, 61), S(0, 38), S(51, 38), S(0, 0) }; -constexpr S THREAT_SAFE_PAWN(169, 99); -constexpr S WEAK_QUEEN(51, 10); +constexpr S THREAT_SAFE_PAWN(173, 94); +constexpr S WEAK_QUEEN(49, 15); constexpr S KNIGHT_MOBILITY[] = { @@ -64,365 +70,227 @@ constexpr S QUEEN_MOBILITY[] = short Evaluation::simpleEvaluation(const Board &board) noexcept { - if (board.state == State::DRAW) + if (board.state == State::INVALID || board.state == State::DRAW) return 0; if (board.state == State::WINNER_WHITE) return VALUE_WINNER_WHITE; if (board.state == State::WINNER_BLACK) return VALUE_WINNER_BLACK; - - short score{}; - for (byte x = 0; x < 8; x++) - for (byte y = 0; y < 8; y++) - if (const Piece &piece = board.getPiece(x, y); piece) - { - const short points = [&]() -> short { - switch (piece.type) - { - case Type::PAWN: - return Psqt::s_PawnSquares[x][y].mg; - case Type::KNIGHT: - return Psqt::s_KnightSquares[x][y].mg; - case Type::BISHOP: - return Psqt::s_BishopSquares[x][y].mg; - case Type::ROOK: - return Psqt::s_RookSquares[x][y].mg; - case Type::QUEEN: - return Psqt::s_QueenSquares[x][y].mg; - case Type::KING: - return Psqt::s_KingSquares[x][y].mg; - default: - return 0; - } - }(); - - if (piece.isWhite) score += points; else score -= points; - } - return score; + Score score[2]{}; + for (byte i = 0; i < SQUARE_NB; i++) + { + const Piece &piece = board.getPiece(i); + score[piece.color()] += Psqt::s_Bonus[piece.type()][i]; + } + + const Phase phase = board.getPhase(); + const Score finalScore = score[WHITE] - score[BLACK]; + return phase == Phase::MIDDLE ? finalScore.mg : finalScore.eg; } short Evaluation::evaluate(const Board &board) noexcept { - if (board.state == State::DRAW) + if (board.state == State::INVALID || board.state == State::DRAW) return 0; if (board.state == State::WINNER_WHITE) return VALUE_WINNER_WHITE; if (board.state == State::WINNER_BLACK) return VALUE_WINNER_BLACK; - if (Stats::enabled()) - ++Stats::boardsEvaluated; - - Score totalScore; + Stats::incrementBoardsEvaluated(); + Score totalScore[2]{}; byte pawnCount[2]{}; byte bishopCount[2]{}; - short whiteNpm = 0; - short blackNpm = 0; + short npm[2]{}; - const auto whiteMoves = MoveGen::getAttacksPerColor(true, board); - const auto blackMoves = MoveGen::getAttacksPerColor(false, board); + const AttacksMap whiteMoves = Player::getAttacksPerColor(true, board); + const AttacksMap blackMoves = Player::getAttacksPerColor(false, board); - for (byte x = 0; x < 8; x++) - for (byte y = 0; y < 8; y++) - if (const auto &piece = board.getPiece(x, y); piece && piece.type != Type::KING) + for (byte i = 0u; i < SQUARE_NB; i++) + if (const auto &piece = board.getPiece(i); piece && piece.type() != KING) + { + const Color color = piece.color(); + if (piece.type() == PAWN) { - if (piece.type == Type::PAWN) - { - pawnCount[piece.isWhite]++; - continue; - } + pawnCount[color]++; + continue; + } - const bool isWhite = piece.isWhite; - const Pos pos(x, y); - const auto defendedValue = isWhite ? whiteMoves.map[pos] : blackMoves.map[pos]; - const auto attackedValue = isWhite ? blackMoves.map[pos] : whiteMoves.map[pos]; - Score points; + const Pos pos(i); + const auto defendedValue = color ? whiteMoves.map[pos] : blackMoves.map[pos]; + const auto attackedValue = color ? blackMoves.map[pos] : whiteMoves.map[pos]; + Score points; - if (defendedValue < attackedValue) - points -= OVERLOAD * (attackedValue - defendedValue); + if (defendedValue < attackedValue) + points -= OVERLOAD * (attackedValue - defendedValue); - if (piece.type == Type::BISHOP) - bishopCount[isWhite]++; + if (piece.type() == BISHOP) + bishopCount[color]++; - const auto &theirAttacks = isWhite ? whiteMoves.board : blackMoves.board; - const U64 square = pos.toBitboard(); + const auto &theirAttacks = color ? whiteMoves.board : blackMoves.board; + const U64 bb = pos.toBitboard(); - for (byte i = Type::KNIGHT; i < Type::BISHOP; i++) - if (theirAttacks[!isWhite][i] & square) - points -= MINOR_THREATS[piece.type]; + for (byte i = KNIGHT; i < BISHOP; i++) + if (theirAttacks[!color][i] & bb) + points -= MINOR_THREATS[piece.type()]; - if (theirAttacks[!isWhite][Type::ROOK] & square) - points -= ROOK_THREATS[piece.type]; + if (theirAttacks[!color][ROOK] & bb) + points -= ROOK_THREATS[piece.type()]; - if (isWhite) { - whiteNpm += getPieceValue(piece.type); - totalScore += points; - } else { - blackNpm += getPieceValue(piece.type); - totalScore -= points; - } - } + npm[color] += getPieceValue(piece.type()); + totalScore[color] += points; + } // 2 Bishops receive a bonus - if (bishopCount[1] >= 2) - totalScore += 10; - if (bishopCount[0] >= 2) - totalScore -= 10; + if (bishopCount[WHITE] >= 2) + totalScore[WHITE] += 10; + if (bishopCount[BLACK] >= 2) + totalScore[BLACK] += 10; - for (byte x = 0; x < 8; x++) - for (byte y = 0; y < 8; y++) - if (const Piece &piece = board.getPiece(x, y); piece) - { - const Score points = [&] { - const Pos pos(x, y); - switch (piece.type) - { - case Type::PAWN: - return evaluatePawn(piece, pos, board, piece.isWhite ? whiteMoves : blackMoves, - piece.isWhite ? blackMoves : whiteMoves); - case Type::KNIGHT: - return evaluateKnight(piece, pos, board); - case Type::BISHOP: - return evaluateBishop(piece, pos, board); - case Type::ROOK: - return evaluateRook(piece, pos, board); - case Type::QUEEN: - return evaluateQueen(piece, pos, board); - case Type::KING: - return evaluateKing(piece, pos, board); - default: - return Score(); - } - }(); - - if (piece.isWhite) totalScore += points; else totalScore -= points; - } + for (byte i = 0u; i < SQUARE_NB; i++) + if (const Piece &piece = board.getPiece(i); piece) + { + const Score points = [&] { + switch (piece.type()) + { + case PAWN: + return evaluatePawn(piece, i, board, piece.color() ? whiteMoves : blackMoves, + piece.color() ? blackMoves : whiteMoves); + case KNIGHT: + return evaluateKnight(piece, i, board); + case BISHOP: + return evaluateBishop(piece, i, board); + case ROOK: + return evaluateRook(piece, i, board); + case QUEEN: + return evaluateQueen(piece, i, board); + case KING: + return evaluateKing(piece, i, board); + default: + return Score(); + } + }(); - if (board.whiteCastled) - totalScore.mg += 60; - if (board.blackCastled) - totalScore.mg -= 60; + totalScore[piece.color()] += points; + } if (board.state == State::BLACK_IN_CHECK) - { - totalScore.mg += 30; - totalScore.eg += 40; - } + totalScore[WHITE] += CHECK_BONUS; else if (board.state == State::WHITE_IN_CHECK) - { - totalScore.mg -= 30; - totalScore.eg -= 40; - } + totalScore[BLACK] += CHECK_BONUS; - // TODO: Scale Factor - /*int sf = 64; - { - if (pawnCount.white == 0 && npm.white - npm.black <= BISHOP_SCORE.mg) sf = npm.white < ROOK_SCORE.mg ? 0 : (npm.black <= BISHOP_SCORE.mg ? 4 : 14); - if (sf == 64) - { - const bool ob = true;// = opposite_bishops(pos); - if (ob && npm.white == BISHOP_SCORE.mg && npm.black == BISHOP_SCORE.mg) { - int asymmetry = 0; - for (const auto &x : board.data) - { - int open[]{ 0, 0 }; - - for (const Piece &piece : x) - if (piece.type == Type::PAWN) open[!piece.isWhite] = 1; - - if (open[0] + open[1] == 1) asymmetry++; - } - //asymmetry += candidate_passed_w + candidate_passed_b; - sf = 8 + 4 * asymmetry; - } else { - sf = std::min(40 + (ob ? 2 : 7) * pawnCount.white, sf); - } - } - } - totalScore.eg *= sf / 64;*/ + totalScore[board.colorToMove] += TEMPO_BONUS; - // Tempo - const int tempo = board.whiteToMove ? 20 : -20; - totalScore += tempo; + const Score finalScore = totalScore[WHITE] - totalScore[BLACK]; const Phase phase = board.getPhase(); - - return phase == Phase::MIDDLE ? totalScore.mg : totalScore.eg; - //const short p = static_cast(board.getPhase()); - //return ((totalScore.mg * p + totalScore.eg * (128 - p)) / 128) + tempo; + return phase == Phase::MIDDLE ? finalScore.mg : finalScore.eg; } -Score Evaluation::evaluatePawn(const Piece &piece, const Pos &pos, const Board &board, const Attacks &ourAttacks, const Attacks &theirAttacks) noexcept +Score Evaluation::evaluatePawn(const Piece &piece, const byte square, const Board &board, + const AttacksMap &ourAttacks, const AttacksMap &theirAttacks) noexcept { - Score value = Psqt::s_PawnSquares[pos.x][pos.y]; + using namespace Bitboard; - const byte behind = piece.isWhite ? -1 : 1; - const int supported = (board.at(pos.x - 1u, pos.y + behind).isSameType(piece) + - board.at(pos.x + 1u, pos.y + behind).isSameType(piece)); + const Color color = piece.color(); + const Color oppositeColor = ~color; + const Pos pos(square); + Score value = Psqt::s_Bonus[PAWN][square]; - bool isolated = !static_cast(supported); + const Dir forwardDir = color ? NORTH : SOUTH; + const byte rank = (color ? pos.y : 7u - pos.y) - 1u; + const byte behind = color ? -1 : 1; - if (board.getPiece(pos.x, pos.y + behind).isSameType(piece)) - value -= PAWN_DOUBLED; + const U64 adjacentFiles = getAdjacentFiles(square); + const U64 opposed = board.getType(~piece) & getRay(forwardDir, square); + const U64 neighbours = board.getType(piece) & adjacentFiles; + const U64 connected = neighbours & getRank(square); + const U64 support = neighbours & getRank(toSquare(pos.x + behind, pos.y)); + const bool isDoubled = board.at(pos.x, pos.y - behind) == piece; - if (isolated) + if (support | connected) { - for (int y = 0 ; y < 8; y++) { - if (board.at(pos.x - 1u, y).isSameType(piece) || - board.at(pos.x + 1u, y).isSameType(piece)) - { - isolated = false; - break; - } - } - } + const int connectedScore = PAWN_CONNECTED[rank] * (2 + bool(connected) - bool(opposed)) + + 21 * Bitboard::popCount(support); - if (isolated) + value += Score(connectedScore, connectedScore * (rank - 2) / 4); + } else if (!neighbours) value -= PAWN_ISOLATED; + if (!support && isDoubled) + value -= PAWN_DOUBLED; + // Threat Safe Pawn - if (ourAttacks.map[pos] || theirAttacks.map[pos] == 0) // check if the pawn is safe + if (ourAttacks.map[square] || theirAttacks.map[square] == 0) // check if the pawn is safe { - const auto isEnemyPiece = [&] (const byte x, const byte y) { - const Pos newPos(pos, x, y); - if (!newPos.isValid()) return false; - - const Piece &other = board[newPos]; - const byte type = other.type; - return other.isWhite != piece.isWhite && type > Type::PAWN && type < Type::KING; - }; + const U64 enemyPieces = MoveGen::getPawnMoves(color, square, board) + & ~(board.getType(oppositeColor, PAWN) | board.getType(oppositeColor, KING)); - byte i = 0; - if (isEnemyPiece(-1, 1u)) i++; - if (isEnemyPiece(1u, 1u)) i++; + const int count = Bitboard::popCount(enemyPieces); - value += THREAT_SAFE_PAWN * i; + value += THREAT_SAFE_PAWN * count; } - const auto isPassedPawn = [&] { - const Piece enemyPawn(Type::PAWN, !piece.isWhite); - - if (piece.isWhite) - { - byte x = pos.x; - for (byte y = pos.y + 1u; y < 7; y++) - if (board.getPiece(x, y).isSameType(enemyPawn)) - return false; - if (x > 0) - { - --x; - for (byte y = pos.y + 1u; y < 7; y++) - if (board.getPiece(x, y).isSameType(enemyPawn)) - return false; - } - - if (x < 7) - { - ++x; - for (byte y = pos.y + 1u; y < 7; y++) - if (board.getPiece(x, y).isSameType(enemyPawn)) - return false; - } - } else { - byte x = pos.x; - for (byte y = pos.y - 1u; y > 1; y--) - if (board.getPiece(x, y).isSameType(enemyPawn)) - return false; + { // Passed Pawn + U64 rays = getRay(forwardDir, square); + const U64 adjacentRays = shift(rays) | shift(rays); + rays |= color ? shift(adjacentRays) : shift(adjacentRays); - if (x > 0) - { - --x; - for (byte y = pos.y - 1u; y > 1; y--) - if (board.getPiece(x, y).isSameType(enemyPawn)) - return false; - } + const bool isPassedPawn = !static_cast(rays & board.getType(oppositeColor, PAWN)); - if (x < 7) - { - ++x; - for (byte y = pos.y - 1u; y > 1; y--) - if (board.getPiece(x, y).isSameType(enemyPawn)) - return false; - } - } - - return true; - }(); - - if (isPassedPawn) - { - const auto rank = (piece.isWhite ? pos.y : 7u - pos.y) - 1u; - value += PASSED_PAWN_RANK[rank]; + if (isPassedPawn) + value += PASSED_PAWN_RANK[rank]; } return value; } -inline Score Evaluation::evaluateKnight(const Piece &piece, const Pos &pos, const Board &board) noexcept +inline Score Evaluation::evaluateKnight(const Piece &piece, const byte square, const Board &board) noexcept { - Score value = Psqt::s_KnightSquares[pos.x][pos.y]; + const Color color = piece.color(); + Score value = Psqt::s_Bonus[KNIGHT][square]; - const int mobility = Bitboard::popCount(MoveGen::generateKnightMoves(piece, pos, board)); + const int mobility = Bitboard::popCount(MoveGen::getKnightMoves(color, square, board)); value += KNIGHT_MOBILITY[mobility]; return value; } -Score Evaluation::evaluateBishop(const Piece &piece, const Pos &pos, const Board &board) noexcept +Score Evaluation::evaluateBishop(const Piece &piece, const byte square, const Board &board) noexcept { - Score value = Psqt::s_BishopSquares[pos.x][pos.y]; - - value += BISHOP_MOBILITY[MoveGen::generateBishopMoves(piece, pos, board).size()]; + const Color color = piece.color(); + Score value = Psqt::s_Bonus[BISHOP][square]; - // Long Diagonal Bishop - const auto isLongDiagonalBishop = [&] { - if (pos.y - pos.x == 0 || pos.y - (7 - pos.x) == 0) - { - byte x = pos.x, y = pos.y; - if (std::min(x, 7u - x) > 2) return false; - for (byte i = std::min(x, 7u - x); i < 4; i++) { - if (board.getPiece(x, y).type == Type::PAWN) return false; - if (x < 4) x++; else x--; - if (y < 4) y++; else y--; - } - } + const int mobility = Bitboard::popCount(MoveGen::getBishopMoves(color, square, board)); + value += BISHOP_MOBILITY[mobility]; - return true; - }(); + constexpr U64 centerFiles = FILE_D | FILE_E; + constexpr U64 center = centerFiles & (RANK_4 | RANK_5); - if (isLongDiagonalBishop) value.mg += 44; + // Long Diagonal Bishop + const U64 centerAttacks = PieceAttacks::getBishopAttacks(square, board.getType(~piece.color(), PAWN)) & center; + if (Bitboard::popCount(centerAttacks) > 1) + value.mg += 45; return value; } -Score Evaluation::evaluateRook(const Piece &piece, const Pos &pos, const Board &board) noexcept +Score Evaluation::evaluateRook(const Piece &piece, const byte square, const Board &board) noexcept { - Score value = Psqt::s_RookSquares[pos.x][pos.y]; + const Color color = piece.color(); + const Pos pos(square); + Score value = Psqt::s_Bonus[ROOK][pos.toSquare()]; - value += ROOK_MOBILITY[MoveGen::generateRookMoves(piece, pos, board).size()]; - - if (piece.moved) - { - if ((piece.isWhite && !board.whiteCastled) || - (!piece.isWhite && !board.blackCastled)) - value.mg -= 10; - } + const int mobility = Bitboard::popCount(MoveGen::getRookMoves(color, pos.toSquare(), board)); + value += ROOK_MOBILITY[mobility]; const int rookOnPawn = [&] { - if ((piece.isWhite && pos.y < 5) || (!piece.isWhite && pos.y > 4)) return 0; + if ((color && pos.y < 5) || (!color && pos.y > 4)) return 0; - const Piece enemyPawn(PAWN, !piece.isWhite); - int count = 0; + const U64 rays = Bitboard::getRank(square) | Bitboard::getFile(square); - for (const auto &x : board.data) - if (x[pos.y].isSameType(enemyPawn)) - count++; - for (byte y = 0; y < 8; y++) - if (board.getPiece(pos.x, y).isSameType(enemyPawn)) - count++; - - return count; + return Bitboard::popCount(rays & board.getType(~color, PAWN)); }(); value += ROOK_ON_PAWN * rookOnPawn; @@ -431,8 +299,8 @@ Score Evaluation::evaluateRook(const Piece &piece, const Pos &pos, const Board & for (byte y = 0; y < 8; y++) { - const auto &other = board.getPiece(pos.x, y); - if (other.type == PAWN) + const Piece &other = board.getPiece(pos.x, y); + if (other.type() == PAWN) { if (piece.isSameColor(other)) return 0; @@ -444,16 +312,24 @@ Score Evaluation::evaluateRook(const Piece &piece, const Pos &pos, const Board & }(); value += ROOK_ON_FILE[rookOnFile]; + const U64 queens = board.getType(piece.color(), QUEEN) | board.getType(~piece.color(), QUEEN); + if (Bitboard::getFile(square) & queens) + value += ROOK_ON_QUEEN_FILE; + return value; } -Score Evaluation::evaluateQueen(const Piece &piece, const Pos &pos, const Board &board) noexcept +Score Evaluation::evaluateQueen(const Piece &piece, const byte square, const Board &board) noexcept { - Score value = Psqt::s_QueenSquares[pos.x][pos.y]; + const Color color = piece.color(); + const Pos pos(square); + Score value = Psqt::s_Bonus[QUEEN][square]; - value += QUEEN_MOBILITY[MoveGen::generateQueenMoves(piece, pos, board).size()]; + const int mobility = Bitboard::popCount(MoveGen::getQueenMoves(color, square, board)); + value += QUEEN_MOBILITY[mobility]; - if (piece.moved) + if (const U64 initialPosition = FILE_D & (color ? RANK_1 : RANK_8); + (initialPosition & Bitboard::shiftedBoards[square]) == 0ull) value.mg -= 30; const auto weak = [&] { @@ -465,9 +341,9 @@ Score Evaluation::evaluateQueen(const Piece &piece, const Pos &pos, const Board Pos dPos(pos, d * ix, d * iy); if (dPos.isValid()) { - const auto &other = board[dPos]; - if (other.type == Type::ROOK && (ix == 0 || iy == 0) && count == 1) return true; - if (other.type == Type::BISHOP && (ix != 0 && iy != 0) && count == 1) return true; + const Piece &other = board.getPiece(dPos.toSquare()); + if (other.type() == ROOK && (ix == 0 || iy == 0) && count == 1) return true; + if (other.type() == BISHOP && (ix != 0 && iy != 0) && count == 1) return true; if (other) count++; } else @@ -482,15 +358,16 @@ Score Evaluation::evaluateQueen(const Piece &piece, const Pos &pos, const Board return value; } -inline Score Evaluation::evaluateKing(const Piece &piece, const Pos &pos, const Board &board) noexcept +inline Score Evaluation::evaluateKing(const Piece &piece, const byte square, const Board &board) noexcept { - Score value = Psqt::s_KingSquares[pos.x][pos.y]; - - if (piece.moved) - { - if ((piece.isWhite && !board.whiteCastled) || - (!piece.isWhite && !board.blackCastled)) - value.mg -= 35; + Score value = Psqt::s_Bonus[KNIGHT][square]; + const Color color = piece.color(); + + if (board.isCastled(color)) + value.mg += 57; + else { + const short count = short(board.canCastleKs(color)) + short(board.canCastleQs(color)); + value.mg += count * 20; } return value; diff --git a/ChessAndroid/app/src/main/cpp/chess/algorithm/Evaluation.h b/ChessAndroid/app/src/main/cpp/chess/algorithm/Evaluation.h index 57c6349..46a4549 100644 --- a/ChessAndroid/app/src/main/cpp/chess/algorithm/Evaluation.h +++ b/ChessAndroid/app/src/main/cpp/chess/algorithm/Evaluation.h @@ -8,7 +8,7 @@ class Board; class Evaluation final { - constexpr static short PIECE_VALUE[] = { 0, 136, 782, 830, 1289, 2529, 0 }; + constexpr static short PIECE_VALUE[] = { 0, 128, 781, 825, 1276, 2538, 0 }; public: Evaluation() = delete; @@ -17,16 +17,17 @@ class Evaluation final static short simpleEvaluation(const Board &board) noexcept; static short evaluate(const Board &board) noexcept; - static short getPieceValue(const Type type) noexcept + static short getPieceValue(const PieceType type) noexcept { return PIECE_VALUE[type]; } private: - static Score evaluatePawn(const Piece &piece, const Pos &pos, const Board &board, const Attacks &ourAttacks, const Attacks &theirAttacks) noexcept; - inline static Score evaluateKnight(const Piece &piece, const Pos &pos, const Board &board) noexcept; - static Score evaluateBishop(const Piece &piece, const Pos &pos, const Board &board) noexcept; - static Score evaluateRook(const Piece &piece, const Pos &pos, const Board &board) noexcept; - static Score evaluateQueen(const Piece &piece, const Pos &pos, const Board &board) noexcept; - inline static Score evaluateKing(const Piece &piece, const Pos &pos, const Board &board) noexcept; + static Score evaluatePawn(const Piece &piece, byte square, const Board &board, + const AttacksMap &ourAttacks, const AttacksMap &theirAttacks) noexcept; + inline static Score evaluateKnight(const Piece &piece, byte square, const Board &board) noexcept; + static Score evaluateBishop(const Piece &piece, byte square, const Board &board) noexcept; + static Score evaluateRook(const Piece &piece, byte square, const Board &board) noexcept; + static Score evaluateQueen(const Piece &piece, const byte square, const Board &board) noexcept; + inline static Score evaluateKing(const Piece &piece, const byte square, const Board &board) noexcept; }; diff --git a/ChessAndroid/app/src/main/cpp/chess/algorithm/Hash.cpp b/ChessAndroid/app/src/main/cpp/chess/algorithm/Hash.cpp index 6982873..994183e 100644 --- a/ChessAndroid/app/src/main/cpp/chess/algorithm/Hash.cpp +++ b/ChessAndroid/app/src/main/cpp/chess/algorithm/Hash.cpp @@ -4,9 +4,9 @@ #include "../data/Board.h" -Hash::HashArray Hash::array{}; -std::array Hash::castlingRights; -U64 Hash::whiteToMove; +Hash::HashArray Hash::s_Pieces{}; +std::array Hash::s_CastlingRights; +U64 Hash::s_WhiteToMove; void Hash::init() { @@ -18,71 +18,73 @@ void Hash::init() std::mt19937_64 mt(rd()); std::uniform_int_distribution dist(0, UINT64_MAX); - for (byte x = 0; x < 8; x++) - for (byte y = 0; y < 8; y++) - for (byte p = 0; p < 8; p++) - array[x][y][p] = dist(mt); + for (auto &i : s_Pieces) + for (auto &color : i) + for (auto &piece : color) + piece = dist(mt); - for (byte i = 0; i < 4; i++) - castlingRights[i] = dist(mt); + for (auto &rights : s_CastlingRights) + rights = dist(mt); - whiteToMove = dist(mt); -} - -U64 Hash::getHash(const Pos &pos, const Piece &piece) -{ - return array[pos.x][pos.y][indexOf(piece)]; + s_WhiteToMove = dist(mt); } U64 Hash::compute(const Board &board) { U64 hash{}; - for (byte x = 0; x < 8; x++) - for (byte y = 0; y < 8; y++) - if (const Piece &piece = board.getPiece(x, y); piece) - hash ^= array[x][y][indexOf(piece)]; + for (byte i = 0; i < SQUARE_NB; ++i) + if (const Piece &piece = board.getPiece(i); piece) + hash ^= s_Pieces[i][piece.color()][piece.type()]; - if (board.whiteToMove) - hash ^= whiteToMove; + if (board.colorToMove) + hash ^= s_WhiteToMove; - return hash; -} + xorCastlingRights(hash, static_cast(board.castlingRights)); -byte Hash::indexOf(const Piece& piece) -{ - byte type = static_cast(piece.type) - 1u; - if (piece.isWhite) type += 6u; - return type; + return hash; } -void Hash::makeMove(U64 &key, const Pos &selectedPos, const Pos &destPos, const Piece &selectedPiece, const Piece &destPiece) +void Hash::makeMove(U64 &key, const byte selectedSq, const byte destSq, const Piece &selectedPiece, const Piece &destPiece) { // Remove Selected Piece - key ^= Hash::getHash(selectedPos, selectedPiece); + Hash::xorPiece(key, selectedSq, selectedPiece); if (destPiece) // Remove Destination Piece if any - key ^= Hash::getHash(destPos, destPiece); + Hash::xorPiece(key, destSq, destPiece); // Add Selected Piece to Destination - key ^= Hash::getHash(destPos, selectedPiece); + Hash::xorPiece(key, destSq, selectedPiece); } -void Hash::promotePawn(U64 &key, const Pos &startPos, const Pos &destPos, const bool isWhite, const Type promotedType) +void Hash::promotePawn(U64 &key, const byte sq, const Color color, const PieceType promotedType) { // Remove the Pawn - key ^= array[startPos.x][startPos.y][indexOf(Piece(Type::PAWN, isWhite))]; + key ^= s_Pieces[sq][color][PAWN]; - // Add Promoted Piece - key ^= array[destPos.x][destPos.y][indexOf(Piece(promotedType, isWhite))]; + // Add the Promoted Piece + key ^= s_Pieces[sq][color][promotedType]; } -void Hash::xorPiece(U64 &key, const Pos &pos, const Piece &piece) +void Hash::xorPiece(U64 &key, const byte sq, const Piece &piece) { - key ^= array[pos.x][pos.y][indexOf(piece)]; + key ^= s_Pieces[sq][piece.color()][piece.type()]; } void Hash::flipSide(U64 &key) { - key ^= Hash::whiteToMove; + key ^= Hash::s_WhiteToMove; +} + +void Hash::xorCastlingRights(U64 &key, const CastlingRights rights) +{ + if (rights & CASTLE_WHITE_KING) + key ^= s_CastlingRights[0]; + if (rights & CASTLE_WHITE_QUEEN) + key ^= s_CastlingRights[1]; + + if (rights & CASTLE_BLACK_KING) + key ^= s_CastlingRights[2]; + if (rights & CASTLE_BLACK_QUEEN) + key ^= s_CastlingRights[3]; } diff --git a/ChessAndroid/app/src/main/cpp/chess/algorithm/Hash.h b/ChessAndroid/app/src/main/cpp/chess/algorithm/Hash.h index f01d5bc..24519dd 100644 --- a/ChessAndroid/app/src/main/cpp/chess/algorithm/Hash.h +++ b/ChessAndroid/app/src/main/cpp/chess/algorithm/Hash.h @@ -2,35 +2,33 @@ #include -#include "../data/Pos.h" +#include "../data/Defs.h" #include "../data/Piece.h" using U64 = std::uint64_t; class Hash final { -private: - using HashArray = std::array, 8>, 8>; + using HashArray = std::array, 2>, 64>; - static HashArray array; - static U64 whiteToMove; + static HashArray s_Pieces; + static U64 s_WhiteToMove; + static std::array s_CastlingRights; public: - static std::array castlingRights; - Hash() = delete; Hash(const Hash&) = delete; Hash(Hash&&) = delete; + Hash &operator=(const Hash&) = delete; + Hash &operator=(Hash&&) = delete; + static void init(); - static U64 getHash(const Pos &pos, const Piece &piece); static U64 compute(const Board &board); - static void makeMove(U64 &key, const Pos &selectedPos, const Pos &destPos, const Piece &selectedPiece, const Piece &destPiece = Piece::EMPTY); - static void promotePawn(U64 &key, const Pos &startPos, const Pos &destPos, bool isWhite, Type promotedType); - static void xorPiece(U64 &key, const Pos &pos, const Piece &piece); + static void makeMove(U64 &key, byte selectedSq, byte destSq, const Piece &selectedPiece, const Piece &destPiece = Piece::EMPTY); + static void promotePawn(U64 &key, byte sq, Color color, PieceType promotedType); + static void xorPiece(U64 &key, byte sq, const Piece &piece); static void flipSide(U64 &key); - -private: - static byte indexOf(const Piece &piece); + static void xorCastlingRights(U64 &key, const CastlingRights rights); }; diff --git a/ChessAndroid/app/src/main/cpp/chess/algorithm/MoveGen.h b/ChessAndroid/app/src/main/cpp/chess/algorithm/MoveGen.h index 9ac12bc..9303fc4 100644 --- a/ChessAndroid/app/src/main/cpp/chess/algorithm/MoveGen.h +++ b/ChessAndroid/app/src/main/cpp/chess/algorithm/MoveGen.h @@ -1,16 +1,13 @@ #pragma once -#include "../containers/Containers.h" - enum GenType : unsigned char { ALL, CAPTURES, - ATTACKS_DEFENSES, - KING_DANGER + ATTACKS_DEFENSES }; -template +template class MoveGen final { public: @@ -18,16 +15,12 @@ class MoveGen final MoveGen(const MoveGen&) = delete; MoveGen(MoveGen&&) = delete; - static auto generatePawnMoves(const Piece &piece, Pos pos, const Board &board); - static auto generateKnightMoves(const Piece &piece, const Pos &pos, const Board &board); - static auto generateBishopMoves(const Piece &piece, const Pos &pos, const Board &board); - static auto generateRookMoves(const Piece &piece, const Pos &pos, const Board &board); - static auto generateQueenMoves(const Piece &piece, const Pos &pos, const Board &board); - static auto generateKingMoves(const Piece &piece, const Pos &pos, const Board &board); - - template - static void forEachAttack(bool white, const Board &board, Func &&func); - static Attacks getAttacksPerColor(bool white, const Board &board); + static U64 getPawnMoves(Color color, byte square, const Board &board) noexcept; + static U64 getKnightMoves(Color color, byte square, const Board &board) noexcept; + static U64 getBishopMoves(Color color, byte square, const Board &board) noexcept; + static U64 getRookMoves(Color color, byte square, const Board &board) noexcept; + static U64 getQueenMoves(Color color, byte square, const Board &board) noexcept; + static U64 getKingMoves(Color color, byte square, const Board &board) noexcept; }; #include "MoveGen.inl" diff --git a/ChessAndroid/app/src/main/cpp/chess/algorithm/MoveGen.inl b/ChessAndroid/app/src/main/cpp/chess/algorithm/MoveGen.inl index 8a8b40c..60cde69 100644 --- a/ChessAndroid/app/src/main/cpp/chess/algorithm/MoveGen.inl +++ b/ChessAndroid/app/src/main/cpp/chess/algorithm/MoveGen.inl @@ -6,317 +6,182 @@ #include "../data/Board.h" #include "../data/Bitboard.h" -template -static void convertBitboardToMoves(PosVector &moves, U64 attacks) +template +U64 MoveGen::getPawnMoves(const Color color, const byte square, const Board &board) noexcept { - while (attacks) - { - const byte index = Bitboard::bitScanForward(attacks); - attacks &= ~Bitboard::shiftedBoards[index]; - - moves.emplace_back(row(index), col(index)); - } -} - -template -auto MoveGen::generatePawnMoves(const Piece &piece, Pos pos, const Board &board) -{ - PosVector<4> moves; + using namespace Bitboard; + U64 attacks = PieceAttacks::getPawnAttacks(color, square); - piece.isWhite ? pos.y++ : pos.y--; - if constexpr (T == ALL) + if constexpr (T == ALL || T == CAPTURES) { - if (!board[pos]) - { - moves.push_back(pos); + U64 captures = attacks & board.allPieces[~color]; - if (!piece.moved) { - Pos posCopy = pos; + if (board.enPassantSq < 64u) + { + const U64 enPassant = shiftedBoards[board.enPassantSq]; + const U64 capturedPawn = color ? shift(enPassant) : shift(enPassant); - piece.isWhite ? posCopy.y++ : posCopy.y--; - if (posCopy.isValid() && !board[posCopy]) - moves.push_back(posCopy); + if (board.getType(~color, PAWN) & capturedPawn) + { + // Keep the en-passant capture if it intersect with one of our potential attacks + captures |= attacks & enPassant; } } + + attacks = captures; } + else if constexpr (T == ATTACKS_DEFENSES) + attacks &= board.occupied; - const auto handleCapture = [&] { - const Piece &other = board[pos]; - if constexpr (T == ALL || T == CAPTURES || T == KING_DANGER) - { - if ((other && !piece.isSameColor(other)) || board.enPassantPos == pos) - moves.push_back(pos); - } - else if (T == ATTACKS_DEFENSES) - { - if (other) - moves.push_back(pos); - } - }; - - piece.isWhite ? pos.x-- : pos.x++; - if (pos.isValid()) - handleCapture(); - - pos.x += piece.isWhite ? 2 : -2; - if (pos.isValid()) - handleCapture(); + if constexpr (T == ALL) + { + const U64 bitboard = Bitboard::shiftedBoards[square]; + const U64 move = color ? shift(bitboard) : shift(bitboard); - if constexpr (ToList) - return moves; - else { - U64 attacks{}; + if (!(board.occupied & move)) + { + attacks |= move; - for (const Pos &move : moves) - attacks |= move.toBitboard(); + const U64 initialRank = color ? RANK_2 : RANK_7; + if (initialRank & bitboard) + { + const U64 doubleMove = color ? shift(move) : shift(move); - return attacks; + if (!(board.occupied & doubleMove)) + attacks |= doubleMove; + } + } } + + return attacks; } -template -auto MoveGen::generateKnightMoves(const Piece &piece, const Pos &pos, const Board &board) +template +U64 MoveGen::getKnightMoves(const Color color, const byte square, const Board &board) noexcept { - PosVector<8> moves; - - U64 attacks = PieceAttacks::getKnightAttacks(pos.toSquare()); + U64 attacks = PieceAttacks::getKnightAttacks(square); if constexpr (T == ALL) - attacks &= ~board.allPieces[piece.isWhite]; // Remove our pieces - else if (T == KING_DANGER) { - // Do nothing - } else if (T == CAPTURES) - attacks &= board.allPieces[!piece.isWhite]; // Keep only their pieces - else if (T == ATTACKS_DEFENSES) - attacks &= (board.allPieces[0] | board.allPieces[1]); // Keep only the pieces - - if constexpr (ToList) - { - convertBitboardToMoves(moves, attacks); - return moves; - } - else - return attacks; + attacks &= ~board.allPieces[color]; // Remove our pieces + else if constexpr (T == CAPTURES) + attacks &= board.allPieces[~color]; // Keep only their pieces + else if constexpr (T == ATTACKS_DEFENSES) + attacks &= board.occupied; // Keep only the pieces + + return attacks; } -template -auto MoveGen::generateBishopMoves(const Piece &piece, const Pos &pos, const Board &board) +template +U64 MoveGen::getBishopMoves(const Color color, const byte square, const Board &board) noexcept { - PosVector<13> moves; - U64 allPieces = board.allPieces[0] | board.allPieces[1]; - - if constexpr (T == KING_DANGER) - allPieces &= ~Bitboard::shiftedBoards[board.kingSquare[piece.isWhite]]; // Remove the king - - U64 attacks = PieceAttacks::getBishopAttacks(pos.toSquare(), allPieces); + U64 attacks = PieceAttacks::getBishopAttacks(square, board.occupied); if constexpr (T == ALL) - attacks &= ~board.allPieces[piece.isWhite]; // Remove our pieces - else if (T == CAPTURES) - attacks &= board.allPieces[!piece.isWhite]; // Keep only their pieces - else if (T == ATTACKS_DEFENSES) - attacks &= allPieces; // Keep only the pieces + attacks &= ~board.allPieces[color]; // Remove our pieces + else if constexpr (T == CAPTURES) + attacks &= board.allPieces[~color]; // Keep only their pieces + else if constexpr (T == ATTACKS_DEFENSES) + attacks &= board.occupied; // Keep only the pieces - if constexpr (ToList) - { - convertBitboardToMoves(moves, attacks); - return moves; - } - else - return attacks; + return attacks; } -template -auto MoveGen::generateRookMoves(const Piece &piece, const Pos &pos, const Board &board) +template +U64 MoveGen::getRookMoves(const Color color, const byte square, const Board &board) noexcept { - PosVector<14> moves; - U64 allPieces = board.allPieces[0] | board.allPieces[1]; - - if constexpr (T == KING_DANGER) - allPieces &= ~Bitboard::shiftedBoards[board.kingSquare[!piece.isWhite]]; // Remove their king - - U64 attacks = PieceAttacks::getRookAttacks(pos.toSquare(), allPieces); + U64 attacks = PieceAttacks::getRookAttacks(square, board.occupied); if constexpr (T == ALL) - attacks &= ~board.allPieces[piece.isWhite]; // Remove our pieces - else if (T == CAPTURES) - attacks &= board.allPieces[!piece.isWhite]; // Keep only their pieces - else if (T == ATTACKS_DEFENSES) - attacks &= allPieces; // Keep only the pieces + attacks &= ~board.allPieces[color]; // Remove our pieces + else if constexpr (T == CAPTURES) + attacks &= board.allPieces[~color]; // Keep only their pieces + else if constexpr (T == ATTACKS_DEFENSES) + attacks &= board.occupied; // Keep only the pieces - if constexpr (ToList) - { - convertBitboardToMoves(moves, attacks); - return moves; - } - else - return attacks; + return attacks; } -template -auto MoveGen::generateQueenMoves(const Piece &piece, const Pos &pos, const Board &board) +template +U64 MoveGen::getQueenMoves(const Color color, const byte square, const Board &board) noexcept { - PosVector<27> moves; - U64 allPieces = board.allPieces[0] | board.allPieces[1]; - - if constexpr (T == KING_DANGER) - allPieces &= ~Bitboard::shiftedBoards[board.kingSquare[!piece.isWhite]]; // Remove their king - - U64 attacks = PieceAttacks::getBishopAttacks(pos.toSquare(), allPieces) - | PieceAttacks::getRookAttacks(pos.toSquare(), allPieces); + U64 attacks = PieceAttacks::getBishopAttacks(square, board.occupied) + | PieceAttacks::getRookAttacks(square, board.occupied); if constexpr (T == ALL) - attacks &= ~board.allPieces[piece.isWhite]; // Remove our pieces - else if (T == CAPTURES) - attacks &= board.allPieces[!piece.isWhite]; // Keep only their pieces - else if (T == ATTACKS_DEFENSES) - attacks &= allPieces; // Keep only the pieces + attacks &= ~board.allPieces[color]; // Remove our pieces + else if constexpr (T == CAPTURES) + attacks &= board.allPieces[~color]; // Keep only their pieces + else if constexpr (T == ATTACKS_DEFENSES) + attacks &= board.occupied; // Keep only the pieces - if constexpr (ToList) - { - convertBitboardToMoves(moves, attacks); - return moves; - } - else - return attacks; + return attacks; } -template -auto MoveGen::generateKingMoves(const Piece &piece, const Pos &pos, const Board &board) +template +U64 MoveGen::getKingMoves(const Color color, const byte square, const Board &board) noexcept { - PosVector<10> moves; - - U64 attacks = PieceAttacks::getKingAttacks(pos.toSquare()); + U64 attacks = PieceAttacks::getKingAttacks(square); if constexpr (T == ALL) - attacks &= ~board.allPieces[piece.isWhite]; // Remove our pieces - else if (T == KING_DANGER) { - // Do Nothing - } else if (T == CAPTURES) - attacks &= board.allPieces[!piece.isWhite]; // Keep only their pieces - else if (T == ATTACKS_DEFENSES) - attacks &= (board.allPieces[0] | board.allPieces[1]); // Keep only the pieces - - if constexpr (T == CAPTURES || T == ATTACKS_DEFENSES || T == KING_DANGER) + attacks &= ~board.allPieces[color]; // Remove our pieces + else if constexpr (T == CAPTURES) + attacks &= board.allPieces[~color]; // Keep only their pieces + else if constexpr (T == ATTACKS_DEFENSES) + attacks &= board.occupied; // Keep only the pieces + + if constexpr (T == CAPTURES || T == ATTACKS_DEFENSES) + return attacks; + + U64 opponentsMoves{}; { - if constexpr (ToList) + const Color opponentColor = ~color; + U64 attacksCopy = attacks; + + while (attacksCopy) { - convertBitboardToMoves(moves, attacks); - return moves; + const byte currentSquare = Bitboard::findNextSquare(attacksCopy); + if (Player::isAttacked(opponentColor, currentSquare, board)) + opponentsMoves |= Bitboard::shiftedBoards[currentSquare]; } - else - return attacks; - } - if (attacks == 0ull) - { - if constexpr (ToList) - return moves; - else - return attacks; - } - U64 opponentsMoves{}; - MoveGen::forEachAttack(!piece.isWhite, board, [&] (const U64 attack) -> bool { - opponentsMoves |= attack; - return false; - }); + attacks &= ~opponentsMoves; + } - attacks &= ~opponentsMoves; // Remove Attacked Positions + if (!attacks) + return 0ull; // Castling - if (!piece.moved && !(opponentsMoves & pos.toBitboard())) + const U64 bitboard = Bitboard::shiftedBoards[square]; + const State checkState = color ? State::WHITE_IN_CHECK : State::BLACK_IN_CHECK; + + if (board.state == checkState || !board.canCastle(color) || opponentsMoves & bitboard) + return attacks; + + const byte y = row(square); + const auto isEmptyAndCheckFree = [&, y](const byte x) { + const byte sq = toSquare(x, y); + const U64 bb = Bitboard::shiftedBoards[sq]; + + return !board.getPiece(x, y) && !(opponentsMoves & bb) + && !Player::isAttacked(~color, sq, board); + }; + + // King Side + if (board.canCastleKs(color) + && isEmptyAndCheckFree(5) + && isEmptyAndCheckFree(6)) { - const auto y = pos.y; - const auto isEmptyAndCheckFree = [&, y](const byte x) { - return !board.getPiece(x, y) && !(opponentsMoves & Pos(x, y).toBitboard()); - }; - - if (isEmptyAndCheckFree(5) && isEmptyAndCheckFree(6)) - if (const auto &other = board.getPiece(7, y); - other.type == Type::ROOK && piece.isSameColor(other) && !other.moved) - attacks |= Pos(6, pos.y).toBitboard(); - - if (isEmptyAndCheckFree(3) && isEmptyAndCheckFree(2) && !board.getPiece(1, y)) - if (const auto &other = board.getPiece(0, y); - other.type == Type::ROOK && piece.isSameColor(other) && !other.moved) - attacks |= Pos(2, pos.y).toBitboard(); + attacks |= Pos(6, y).toBitboard(); } - if constexpr (ToList) + // Queen Side + if (board.canCastleQs(color) + && isEmptyAndCheckFree(3) + && isEmptyAndCheckFree(2) + && !board.getPiece(1, y)) { - convertBitboardToMoves(moves, attacks); - return moves; + attacks |= Pos(2, y).toBitboard(); } - else - return attacks; -} - -template // Class Template -template // Function Template -void MoveGen::forEachAttack(const bool white, const Board &board, Func &&func) -{ - for (byte x = 0; x < 8; x++) - for (byte y = 0; y < 8; y++) - { - const Pos pos(x, y); - const auto &piece = board[pos]; - if (piece && piece.isWhite == white) - { - using MoveTypes = std::conditional_t; - - MoveTypes moves{}; - switch (piece.type) - { - case Type::PAWN: - moves = MoveGen::generatePawnMoves(piece, pos, board); - break; - case Type::KNIGHT: - moves = generateKnightMoves(piece, pos, board); - break; - case Type::BISHOP: - moves = generateBishopMoves(piece, pos, board); - break; - case Type::ROOK: - moves = generateRookMoves(piece, pos, board); - break; - case Type::QUEEN: - moves = generateQueenMoves(piece, pos, board); - break; - case Type::KING: - moves = MoveGen::generateKingMoves(piece, pos, board); - break; - case Type::NONE: - break; - } - - // The function should return true in order to stop the loop - if constexpr (ToList) - { - for (const auto &move : moves) - { - if (func(piece, move)) - return; - } - } else { - if (func(moves)) - return; - } - } - } -} - -template -Attacks MoveGen::getAttacksPerColor(const bool white, const Board &board) -{ - Attacks attacks{}; - - forEachAttack(white, board, [&] (const Piece &piece, const Pos &move) -> bool { - const byte square = move.toSquare(); - - attacks.map[square]++; - attacks.board[piece.isWhite][piece.type - 1u] |= (1ull << square); - return false; - }); return attacks; } diff --git a/ChessAndroid/app/src/main/cpp/chess/algorithm/PieceAttacks.cpp b/ChessAndroid/app/src/main/cpp/chess/algorithm/PieceAttacks.cpp index 9e7aaa9..a767259 100644 --- a/ChessAndroid/app/src/main/cpp/chess/algorithm/PieceAttacks.cpp +++ b/ChessAndroid/app/src/main/cpp/chess/algorithm/PieceAttacks.cpp @@ -1,9 +1,8 @@ #include "PieceAttacks.h" #include "../data/Pos.h" -#include "../data/Rays.h" -constexpr std::array rookIndexBits = { +constexpr std::array rookIndexBits = { 12, 11, 11, 11, 11, 11, 11, 12, 11, 10, 10, 10, 10, 10, 10, 11, 11, 10, 10, 10, 10, 10, 10, 11, @@ -14,7 +13,7 @@ constexpr std::array rookIndexBits = { 12, 11, 11, 11, 11, 11, 11, 12 }; -constexpr std::array bishopIndexBits = { +constexpr std::array bishopIndexBits = { 6, 5, 5, 5, 5, 5, 5, 6, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 7, 7, 7, 7, 5, 5, @@ -29,34 +28,35 @@ constexpr std::array bishopIndexBits = { * Rook and bishop magic values for magic table lookups */ constexpr std::array bishopMagics = { - 0x89a1121896040240ULL, 0x2004844802002010ULL, 0x2068080051921000ULL, 0x62880a0220200808ULL, 0x4042004000000ULL, - 0x100822020200011ULL, 0xc00444222012000aULL, 0x28808801216001ULL, 0x400492088408100ULL, 0x201c401040c0084ULL, - 0x840800910a0010ULL, 0x82080240060ULL, 0x2000840504006000ULL, 0x30010c4108405004ULL, 0x1008005410080802ULL, - 0x8144042209100900ULL, 0x208081020014400ULL, 0x4800201208ca00ULL, 0xf18140408012008ULL, 0x1004002802102001ULL, - 0x841000820080811ULL, 0x40200200a42008ULL, 0x800054042000ULL, 0x88010400410c9000ULL, 0x520040470104290ULL, - 0x1004040051500081ULL, 0x2002081833080021ULL, 0x400c00c010142ULL, 0x941408200c002000ULL, 0x658810000806011ULL, - 0x188071040440a00ULL, 0x4800404002011c00ULL, 0x104442040404200ULL, 0x511080202091021ULL, 0x4022401120400ULL, - 0x80c0040400080120ULL, 0x8040010040820802ULL, 0x480810700020090ULL, 0x102008e00040242ULL, 0x809005202050100ULL, - 0x8002024220104080ULL, 0x431008804142000ULL, 0x19001802081400ULL, 0x200014208040080ULL, 0x3308082008200100ULL, - 0x41010500040c020ULL, 0x4012020c04210308ULL, 0x208220a202004080ULL, 0x111040120082000ULL, 0x6803040141280a00ULL, - 0x2101004202410000ULL, 0x8200000041108022ULL, 0x21082088000ULL, 0x2410204010040ULL, 0x40100400809000ULL, - 0x822088220820214ULL, 0x40808090012004ULL, 0x910224040218c9ULL, 0x402814422015008ULL, 0x90014004842410ULL, - 0x1000042304105ULL, 0x10008830412a00ULL, 0x2520081090008908ULL, 0x40102000a0a60140ULL, + 0x89a1121896040240ull, 0x2004844802002010ull, 0x2068080051921000ull, 0x62880a0220200808ull, 0x4042004000000ull, + 0x100822020200011ull, 0xc00444222012000aull, 0x28808801216001ull, 0x400492088408100ull, 0x201c401040c0084ull, + 0x840800910a0010ull, 0x82080240060ull, 0x2000840504006000ull, 0x30010c4108405004ull, 0x1008005410080802ull, + 0x8144042209100900ull, 0x208081020014400ull, 0x4800201208ca00ull, 0xf18140408012008ull, 0x1004002802102001ull, + 0x841000820080811ull, 0x40200200a42008ull, 0x800054042000ull, 0x88010400410c9000ull, 0x520040470104290ull, + 0x1004040051500081ull, 0x2002081833080021ull, 0x400c00c010142ull, 0x941408200c002000ull, 0x658810000806011ull, + 0x188071040440a00ull, 0x4800404002011c00ull, 0x104442040404200ull, 0x511080202091021ull, 0x4022401120400ull, + 0x80c0040400080120ull, 0x8040010040820802ull, 0x480810700020090ull, 0x102008e00040242ull, 0x809005202050100ull, + 0x8002024220104080ull, 0x431008804142000ull, 0x19001802081400ull, 0x200014208040080ull, 0x3308082008200100ull, + 0x41010500040c020ull, 0x4012020c04210308ull, 0x208220a202004080ull, 0x111040120082000ull, 0x6803040141280a00ull, + 0x2101004202410000ull, 0x8200000041108022ull, 0x21082088000ull, 0x2410204010040ull, 0x40100400809000ull, + 0x822088220820214ull, 0x40808090012004ull, 0x910224040218c9ull, 0x402814422015008ull, 0x90014004842410ull, + 0x1000042304105ull, 0x10008830412a00ull, 0x2520081090008908ull, 0x40102000a0a60140ull, }; + constexpr std::array rookMagics = { - 0xa8002c000108020ULL, 0x6c00049b0002001ULL, 0x100200010090040ULL, 0x2480041000800801ULL, 0x280028004000800ULL, - 0x900410008040022ULL, 0x280020001001080ULL, 0x2880002041000080ULL, 0xa000800080400034ULL, 0x4808020004000ULL, - 0x2290802004801000ULL, 0x411000d00100020ULL, 0x402800800040080ULL, 0xb000401004208ULL, 0x2409000100040200ULL, - 0x1002100004082ULL, 0x22878001e24000ULL, 0x1090810021004010ULL, 0x801030040200012ULL, 0x500808008001000ULL, - 0xa08018014000880ULL, 0x8000808004000200ULL, 0x201008080010200ULL, 0x801020000441091ULL, 0x800080204005ULL, - 0x1040200040100048ULL, 0x120200402082ULL, 0xd14880480100080ULL, 0x12040280080080ULL, 0x100040080020080ULL, - 0x9020010080800200ULL, 0x813241200148449ULL, 0x491604001800080ULL, 0x100401000402001ULL, 0x4820010021001040ULL, - 0x400402202000812ULL, 0x209009005000802ULL, 0x810800601800400ULL, 0x4301083214000150ULL, 0x204026458e001401ULL, - 0x40204000808000ULL, 0x8001008040010020ULL, 0x8410820820420010ULL, 0x1003001000090020ULL, 0x804040008008080ULL, - 0x12000810020004ULL, 0x1000100200040208ULL, 0x430000a044020001ULL, 0x280009023410300ULL, 0xe0100040002240ULL, - 0x200100401700ULL, 0x2244100408008080ULL, 0x8000400801980ULL, 0x2000810040200ULL, 0x8010100228810400ULL, - 0x2000009044210200ULL, 0x4080008040102101ULL, 0x40002080411d01ULL, 0x2005524060000901ULL, 0x502001008400422ULL, - 0x489a000810200402ULL, 0x1004400080a13ULL, 0x4000011008020084ULL, 0x26002114058042ULL + 0xa8002c000108020ull, 0x6c00049b0002001ull, 0x100200010090040ull, 0x2480041000800801ull, 0x280028004000800ull, + 0x900410008040022ull, 0x280020001001080ull, 0x2880002041000080ull, 0xa000800080400034ull, 0x4808020004000ull, + 0x2290802004801000ull, 0x411000d00100020ull, 0x402800800040080ull, 0xb000401004208ull, 0x2409000100040200ull, + 0x1002100004082ull, 0x22878001e24000ull, 0x1090810021004010ull, 0x801030040200012ull, 0x500808008001000ull, + 0xa08018014000880ull, 0x8000808004000200ull, 0x201008080010200ull, 0x801020000441091ull, 0x800080204005ull, + 0x1040200040100048ull, 0x120200402082ull, 0xd14880480100080ull, 0x12040280080080ull, 0x100040080020080ull, + 0x9020010080800200ull, 0x813241200148449ull, 0x491604001800080ull, 0x100401000402001ull, 0x4820010021001040ull, + 0x400402202000812ull, 0x209009005000802ull, 0x810800601800400ull, 0x4301083214000150ull, 0x204026458e001401ull, + 0x40204000808000ull, 0x8001008040010020ull, 0x8410820820420010ull, 0x1003001000090020ull, 0x804040008008080ull, + 0x12000810020004ull, 0x1000100200040208ull, 0x430000a044020001ull, 0x280009023410300ull, 0xe0100040002240ull, + 0x200100401700ull, 0x2244100408008080ull, 0x8000400801980ull, 0x2000810040200ull, 0x8010100228810400ull, + 0x2000009044210200ull, 0x4080008040102101ull, 0x40002080411d01ull, 0x2005524060000901ull, 0x502001008400422ull, + 0x489a000810200402ull, 0x1004400080a13ull, 0x4000011008020084ull, 0x26002114058042ull }; /** @@ -64,34 +64,39 @@ constexpr std::array rookMagics = { * These do not include edge squares */ constexpr auto bishopMasks = [] { - std::array masks{}; + using namespace Bitboard; - for (byte square = 0u; square < 64u; ++square) + const U64 edgeSquares = FILE_A | FILE_H | RANK_1 | RANK_8; + std::array masks{}; + + for (byte square = 0u; square < SQUARE_NB; ++square) { - masks[square] = Rays::getRay(Rays::NORTH_EAST, square) - | Rays::getRay(Rays::NORTH_WEST, square) - | Rays::getRay(Rays::SOUTH_EAST, square) - | Rays::getRay(Rays::SOUTH_WEST, square); + masks[square] = (getRay(NORTH_EAST, square) + | getRay(NORTH_WEST, square) + | getRay(SOUTH_WEST, square) + | getRay(SOUTH_EAST, square)) & ~(edgeSquares); } return masks; }(); constexpr auto rookMasks = [] { - std::array masks{}; + using namespace Bitboard; + + std::array masks{}; for (byte square = 0u; square < 64u; ++square) { - masks[square] = (Rays::getRay(Rays::NORTH, square) & ~RANK_8) - | (Rays::getRay(Rays::SOUTH, square) & ~RANK_1) - | (Rays::getRay(Rays::EAST, square) & ~FILE_H) - | (Rays::getRay(Rays::WEST, square) & ~FILE_A); + masks[square] = (getRay(NORTH, square) & ~RANK_8) + | (getRay(SOUTH, square) & ~RANK_1) + | (getRay(EAST, square) & ~FILE_H) + | (getRay(WEST, square) & ~FILE_A); } return masks; }(); -constexpr U64 getBlockersFromIndex(const int index, U64 mask) +U64 getBlockersFromIndex(const int index, U64 mask) { U64 blockers{}; const int bits = Bitboard::popCount(mask); @@ -106,58 +111,49 @@ constexpr U64 getBlockersFromIndex(const int index, U64 mask) return blockers; } -constexpr U64 getRayAttacksForwards(const byte square, const U64 occupied, const Rays::Dir direction) +U64 getRayAttacksForwards(const byte square, const U64 occupied, const Dir direction) { - const U64 attacks = Rays::getRay(direction, square); + const U64 attacks = Bitboard::getRay(direction, square); const U64 blocker = attacks & occupied; const byte index = Bitboard::bitScanForward(blocker | 0x8000000000000000); - return attacks ^ Rays::getRay(direction, index); + return attacks ^ Bitboard::getRay(direction, index); } -constexpr U64 getRayAttacksBackwards(const byte square, const U64 occupied, const Rays::Dir direction) +U64 getRayAttacksBackwards(const byte square, const U64 occupied, const Dir direction) { - const U64 attacks = Rays::getRay(direction, square); + const U64 attacks = Bitboard::getRay(direction, square); const U64 blocker = attacks & occupied; const byte index = Bitboard::bitScanReverse(blocker | 1ULL); - return attacks ^ Rays::getRay(direction, index); + return attacks ^ Bitboard::getRay(direction, index); } -constexpr U64 generateBishopAttacks(const byte square, const U64 blockers) noexcept +U64 generateBishopAttacks(const byte square, const U64 blockers) noexcept { - U64 attacks{}; - - attacks |= getRayAttacksForwards(square, blockers, Rays::NORTH_WEST); - attacks |= getRayAttacksForwards(square, blockers, Rays::NORTH_EAST); - attacks |= getRayAttacksBackwards(square, blockers, Rays::SOUTH_EAST); - attacks |= getRayAttacksBackwards(square, blockers, Rays::SOUTH_WEST); - - return attacks; + return getRayAttacksForwards(square, blockers, NORTH_WEST) + | getRayAttacksForwards(square, blockers, NORTH_EAST) + | getRayAttacksBackwards(square, blockers, SOUTH_EAST) + | getRayAttacksBackwards(square, blockers, SOUTH_WEST); } -constexpr U64 generateRookAttacks(const byte square, const U64 blockers) noexcept +U64 generateRookAttacks(const byte square, const U64 blockers) noexcept { - U64 attacks{}; - - attacks |= getRayAttacksForwards(square, blockers, Rays::NORTH); - attacks |= getRayAttacksForwards(square, blockers, Rays::EAST); - attacks |= getRayAttacksBackwards(square, blockers, Rays::SOUTH); - attacks |= getRayAttacksBackwards(square, blockers, Rays::WEST); - - return attacks; + return getRayAttacksForwards(square, blockers, NORTH) + | getRayAttacksForwards(square, blockers, EAST) + | getRayAttacksBackwards(square, blockers, SOUTH) + | getRayAttacksBackwards(square, blockers, WEST); } const std::array, 2> PieceAttacks::s_PawnAttacks = [] { + using namespace Bitboard; std::array, 2> moves{}; - for (byte i = 0u; i < 64u; i++) + for (byte i = 0u; i < SQUARE_NB; i++) { - const U64 start = Bitboard::shiftedBoards[i]; - - const U64 whiteAttackBb = ((start << 9) & ~FILE_A) | ((start << 7) & ~FILE_H); - const U64 blackAttackBb = ((start >> 9) & ~FILE_H) | ((start >> 7) & ~FILE_A); + const U64 bb = shiftedBoards[i]; + const U64 attacks = shift(bb) | shift(bb); - moves[true][i] = whiteAttackBb; - moves[false][i] = blackAttackBb; + moves[true][i] = shift(attacks); + moves[false][i] = shift(attacks); } return moves; @@ -167,13 +163,13 @@ const std::array PieceAttacks::s_KnightAttacks = [] { std::array moves{}; const auto addAttack = [&](const byte startSquare, const byte x, const byte y) { - const Pos pos(row(startSquare) + x, col(startSquare) + y); + const Pos pos(col(startSquare) + x, row(startSquare) + y); if (pos.isValid()) moves[startSquare] |= pos.toBitboard(); }; - for (byte i = 0u; i < 64u; i++) + for (byte i = 0u; i < SQUARE_NB; i++) { addAttack(i, -2, -1); addAttack(i, -2, 1u); @@ -191,33 +187,26 @@ const std::array PieceAttacks::s_KnightAttacks = [] { return moves; }(); -std::array, 64> PieceAttacks::s_BishopAttacks{}; +std::array, SQUARE_NB> PieceAttacks::s_BishopAttacks{}; -std::array, 64> PieceAttacks::s_RookAttacks{}; +std::array, SQUARE_NB> PieceAttacks::s_RookAttacks{}; -const std::array PieceAttacks::s_KingAttacks = [] { - std::array moves{}; - - const auto addAttack = [&](const byte startSquare, const byte x, const byte y) { - const Pos pos(row(startSquare) + x, col(startSquare) + y); +const std::array PieceAttacks::s_KingAttacks = [] { + using namespace Bitboard; - if (pos.isValid()) - moves[startSquare] |= pos.toBitboard(); - }; + std::array moves{}; - for (byte i = 0u; i < 64u; i++) + for (byte i = 0u; i < SQUARE_NB; i++) { + const U64 bb = shiftedBoards[i]; + // Vertical and Horizontal - addAttack(i, -1, 0u); - addAttack(i, 1u, 0u); - addAttack(i, 0u, -1); - addAttack(i, 0u, 1u); + U64 attacks = shift(bb) | shift(bb) | shift(bb) | shift(bb); // Diagonals - addAttack(i, 1u, -1); - addAttack(i, 1u, 1u); - addAttack(i, -1, -1); - addAttack(i, -1, 1u); + attacks |= shift(bb) | shift(bb) | shift(bb) | shift(bb); + + moves[i] = attacks; } return moves; @@ -230,7 +219,7 @@ void PieceAttacks::init() noexcept initialized = true; // Init Bishop Moves - for (byte square = 0u; square < 64u; square++) + for (byte square = 0u; square < SQUARE_NB; square++) { const int indexBit = bishopIndexBits[square]; @@ -246,7 +235,7 @@ void PieceAttacks::init() noexcept } // Init Rook Moves - for (byte square = 0u; square < 64u; square++) + for (byte square = 0u; square < SQUARE_NB; square++) { const int indexBit = rookIndexBits[square]; @@ -274,7 +263,9 @@ U64 PieceAttacks::getKnightAttacks(const byte square) noexcept U64 PieceAttacks::getBishopAttacks(const byte square, U64 blockers) noexcept { - return generateBishopAttacks(square, blockers); + blockers &= bishopMasks[square]; + const U64 key = (blockers * bishopMagics[square]) >> (64u - bishopIndexBits[square]); + return s_BishopAttacks[square][key]; } U64 PieceAttacks::getRookAttacks(const byte square, U64 blockers) noexcept diff --git a/ChessAndroid/app/src/main/cpp/chess/algorithm/Player.cpp b/ChessAndroid/app/src/main/cpp/chess/algorithm/Player.cpp new file mode 100644 index 0000000..1605f2a --- /dev/null +++ b/ChessAndroid/app/src/main/cpp/chess/algorithm/Player.cpp @@ -0,0 +1,123 @@ +#include "Player.h" + +#include "../data/Board.h" +#include "MoveGen.h" + +bool Player::isAttacked(const Color colorAttacking, const byte targetSquare, const Board &board) +{ + if (board.getType(colorAttacking, PAWN) & PieceAttacks::getPawnAttacks(~colorAttacking, targetSquare)) + return true; + + if (board.getType(colorAttacking, KNIGHT) & PieceAttacks::getKnightAttacks(targetSquare)) + return true; + + if (board.getType(colorAttacking, KING) & PieceAttacks::getKingAttacks(targetSquare)) + return true; + + if (const U64 bishopsQueens = board.getType(colorAttacking, BISHOP) | board.getType(colorAttacking, QUEEN); + bishopsQueens & PieceAttacks::getBishopAttacks(targetSquare, board.occupied)) + return true; + + if (const U64 rooksQueens = board.getType(colorAttacking, ROOK) | board.getType(colorAttacking, QUEEN); + rooksQueens & PieceAttacks::getRookAttacks(targetSquare, board.occupied)) + return true; + + return false; +} + +bool Player::onlyKingsLeft(const Board &board) +{ + if (board.npm != 0) + return false; + + for (const Piece &piece : board.data) + if (piece && piece.type() != PieceType::KING) + return false; + + return true; +} + +bool Player::hasNoValidMoves(const Color color, const Board &board) +{ + for (byte startSq = 0u; startSq < SQUARE_NB; ++startSq) + { + const Piece &attacker = board.getPiece(startSq); + if (!attacker || attacker.color() != board.colorToMove) + continue; + + U64 possibleMoves = attacker.getPossibleMoves(startSq, board); + possibleMoves &= ~board.getType(~color, KING); + + while (possibleMoves) + { + const byte destSq = Bitboard::findNextSquare(possibleMoves); + + Board tempBoard = board; + tempBoard.doMove(startSq, destSq, false); + + if (isInCheck(color, tempBoard)) + continue; + + return false; + } + } + + return true; +} + +bool Player::isInCheck(const Color color, const Board &board) +{ + const U64 king = board.getType(color, KING); + if (!king) return false; + + return isAttacked(~color, Bitboard::bitScanForward(king), board); +} + +AttacksMap Player::getAttacksPerColor(const bool white, const Board &board) +{ + AttacksMap attacks{}; + + for (byte startSq = 0u; startSq < SQUARE_NB; ++startSq) + { + const Piece &piece = board.getPiece(startSq); + if (piece && piece.color() == white) + { + const Color color = piece.color(); + U64 moves{}; + using Generator = MoveGen; + + switch (piece.type()) + { + case PieceType::PAWN: + moves = Generator::getPawnMoves(color, startSq, board); + break; + case PieceType::KNIGHT: + moves = Generator::getKnightMoves(color, startSq, board); + break; + case PieceType::BISHOP: + moves = Generator::getBishopMoves(color, startSq, board); + break; + case PieceType::ROOK: + moves = Generator::getRookMoves(color, startSq, board); + break; + case PieceType::QUEEN: + moves = Generator::getQueenMoves(color, startSq, board); + break; + case PieceType::KING: + moves = Generator::getKingMoves(color, startSq, board); + break; + default: + break; + } + + while (moves) + { + const byte destSq = Bitboard::findNextSquare(moves); + attacks.map[destSq]++; + attacks.board[piece.color()][piece.type() - 1u] |= Bitboard::getSquare64(destSq); + } + } + } + + return attacks; +} diff --git a/ChessAndroid/app/src/main/cpp/chess/algorithm/Player.h b/ChessAndroid/app/src/main/cpp/chess/algorithm/Player.h new file mode 100644 index 0000000..e0d8315 --- /dev/null +++ b/ChessAndroid/app/src/main/cpp/chess/algorithm/Player.h @@ -0,0 +1,22 @@ +#pragma once + +#include "../containers/Containers.h" +#include "../data/Defs.h" +#include "../data/Pos.h" + +class Piece; +class Board; + +class Player +{ +public: + Player() = delete; + Player(const Player&) = delete; + Player(Player&&) = delete; + + static bool isAttacked(Color colorAttacking, byte targetSquare, const Board &board); + static bool onlyKingsLeft(const Board &board); + static bool hasNoValidMoves(Color color, const Board &board); + static bool isInCheck(Color color, const Board &board); + static AttacksMap getAttacksPerColor(bool white, const Board &board); +}; diff --git a/ChessAndroid/app/src/main/cpp/chess/algorithm/NegaMax.cpp b/ChessAndroid/app/src/main/cpp/chess/algorithm/Search.cpp similarity index 65% rename from ChessAndroid/app/src/main/cpp/chess/algorithm/NegaMax.cpp rename to ChessAndroid/app/src/main/cpp/chess/algorithm/Search.cpp index a9d63b4..95d5c4e 100644 --- a/ChessAndroid/app/src/main/cpp/chess/algorithm/NegaMax.cpp +++ b/ChessAndroid/app/src/main/cpp/chess/algorithm/Search.cpp @@ -1,20 +1,18 @@ -#include "NegaMax.h" - -#include -#include +#include "Search.h" #include "../Stats.h" #include "../data/Board.h" -#include "../threads/NegaMaxThreadPool.h" -bool NegaMax::s_QuiescenceSearchEnabled{}; -TranspositionTable NegaMax::s_SearchCache(1); -short NegaMax::s_BestMoveFound{}; +ThreadPool Search::s_ThreadPool(1); +TranspositionTable Search::s_SearchCache(1); +bool Search::s_QuiescenceSearchEnabled{}; +short Search::s_BestMoveFound{}; -RootMove NegaMax::getBestMove(const Board &board, const Settings &settings) +RootMove Search::findBestMove(const Board &board, const Settings &settings) { const auto validMoves = board.listValidMoves(); - + assert(!validMoves.empty()); + for (const RootMove &move : validMoves) if (move.board.state == State::WINNER_WHITE || move.board.state == State::WINNER_BLACK) return move; @@ -24,10 +22,13 @@ RootMove NegaMax::getBestMove(const Board &board, const Settings &settings) const auto threadCount = settings.getThreadCount(); s_QuiescenceSearchEnabled = settings.performQuiescenceSearch(); - // If the Transposition Table wasn't resized, clean it + // If the Transposition Table wasn't resized, increment its age if (!s_SearchCache.setSize(settings.getCacheTableSizeMb())) - s_SearchCache.clear(); - NegaMaxThreadPool::updateThreadCount(threadCount); + s_SearchCache.incrementAge(); + + // Update ThreadPool size if needed + if (s_ThreadPool.getThreadCount() != threadCount) + s_ThreadPool.updateThreadCount(threadCount); if (board.getPhase() == Phase::ENDING) ++depth; @@ -35,29 +36,31 @@ RootMove NegaMax::getBestMove(const Board &board, const Settings &settings) return negaMaxRoot(validMoves, std::min(threadCount, validMoves.size()), depth); } -short NegaMax::getBestMoveFound() +short Search::getBestMoveFound() { return s_BestMoveFound; } -RootMove NegaMax::negaMaxRoot(StackVector validMoves, const unsigned jobCount, const short ply) +RootMove Search::negaMaxRoot(const std::vector &validMoves, const unsigned jobCount, const short ply) { - std::vector> futures; + std::vector> futures; futures.reserve(jobCount); - std::mutex mutex; + RootMove bestMove = validMoves.front(); + auto currentMove = validMoves.begin(); + const auto lastMove = validMoves.end(); short alpha = VALUE_MIN; - const auto doWork = [&] { + const auto worker = [&] { mutex.lock(); - while (!validMoves.empty()) + while (currentMove != lastMove) { // Make a copy of the needed variables while locked + const RootMove &move = *currentMove; + ++currentMove; const short beta = -alpha; - const RootMove move = validMoves.front(); - validMoves.pop_front(); mutex.unlock(); // Process the result asynchronously const short result = -negaMax(move.board, ply, VALUE_MIN, beta, 1, false); @@ -73,26 +76,29 @@ RootMove NegaMax::negaMaxRoot(StackVector validMoves, const unsig mutex.unlock(); }; - futures.emplace_back(NegaMaxThreadPool::submitJob(doWork)); + // Initially create only use thread + futures.emplace_back(s_ThreadPool.submitTask(worker)); - if (jobCount != 1) { + if (jobCount > 1) + { // Wait until an alpha bound has been found while (alpha == VALUE_MIN) std::this_thread::yield(); + // After an alpha bound was found start searching using multiple threads for (unsigned i = 1u; i < jobCount; ++i) - futures.emplace_back(NegaMaxThreadPool::submitJob(doWork)); + futures.emplace_back(s_ThreadPool.submitTask(worker)); } for (auto &future : futures) - future.get(); // Wait for the Search to finish + future.get(); // Wait for the search to finish - s_BestMoveFound = alpha; + s_BestMoveFound = validMoves.front().board.colorToMove ? -alpha : alpha; return bestMove; } -short NegaMax::negaMax(const Board &board, const short ply, short alpha, short beta, const short depth, const bool moveCountPruning) +short Search::negaMax(const Board &board, const short ply, short alpha, short beta, const short depth, const bool moveCountPruning) { if (board.state == State::DRAW) return 0; @@ -108,8 +114,11 @@ short NegaMax::negaMax(const Board &board, const short ply, short alpha, short b } const short originalAlpha = alpha; - if (const SearchCache cache = s_SearchCache[board.key]; - board.key == cache.key && board.score == cache.boardScore && cache.ply == ply) + if (const SearchCache cache = s_SearchCache[board.zKey]; + cache.age == s_SearchCache.currentAge() + && board.zKey == cache.key + && board.score == cache.boardScore + && cache.ply == ply) { if (cache.flag == Flag::EXACT) return cache.value; @@ -122,12 +131,11 @@ short NegaMax::negaMax(const Board &board, const short ply, short alpha, short b return cache.value; } - if (Stats::enabled()) - ++Stats::nodesSearched; - const auto validMoves = board.listValidMoves(); short bestScore = VALUE_MIN; - short movesCount = 0; + size_t movesCount{}; + + Stats::incrementNodesGenerated(validMoves.size()); for (const Board &move : validMoves) { @@ -138,13 +146,21 @@ short NegaMax::negaMax(const Board &board, const short ply, short alpha, short b if (mateValue < beta) { beta = mateValue; - if (alpha >= mateValue) return mateValue; + if (alpha >= mateValue) + { + bestScore = mateValue; + break; + } } if (mateValue > alpha) { alpha = mateValue; - if (beta <= mateValue) return mateValue; + if (beta <= mateValue) + { + bestScore = mateValue; + break; + } } if (bestScore > mateValue) @@ -163,23 +179,25 @@ short NegaMax::negaMax(const Board &board, const short ply, short alpha, short b move.state != State::BLACK_IN_CHECK && !move.isCapture && !move.isPromotion) - moveScore = -negaMax(move, ply - 2, -moveScore, -alpha, depth + 1, true); + moveScore = -negaMax(move, ply - 2, -moveScore, -alpha, depth + 1, true); if (moveScore > alpha) - moveScore = -negaMax(move, ply - 1, -beta, -alpha, depth + 1, false); + moveScore = -negaMax(move, ply - 1, -beta, -alpha, depth + 1, false); if (moveScore > bestScore) bestScore = moveScore; // Alpha-Beta Pruning - if (bestScore > alpha) - alpha = bestScore; if (bestScore >= beta) break; + if (bestScore > alpha) + alpha = bestScore; ++movesCount; } + Stats::incrementNodesSearched(movesCount); + // Store the result in the transposition table Flag flag = Flag::EXACT; if (bestScore < originalAlpha) @@ -187,14 +205,15 @@ short NegaMax::negaMax(const Board &board, const short ply, short alpha, short b else if (bestScore > beta) flag = Flag::BETA; - s_SearchCache.insert({ board.key, board.score, bestScore, ply, flag }); + s_SearchCache.insert({ board.zKey, board.score, bestScore, ply, flag }); if (bestScore > alpha) alpha = bestScore; + return alpha; } -short NegaMax::quiescence(const Board &board, short alpha, const short beta) +short Search::quiescence(const Board &board, short alpha, const short beta) { if (board.state == State::DRAW) return 0; @@ -209,8 +228,8 @@ short NegaMax::quiescence(const Board &board, short alpha, const short beta) // Delta Pruning if (board.getPhase() != Phase::ENDING) // Turn it off in the Endgame { - constexpr short QUEEN_VALUE = 2529; - constexpr short PAWN_VALUE = 136; + constexpr short QUEEN_VALUE = 2538; + constexpr short PAWN_VALUE = 128; short bigDelta = QUEEN_VALUE; if (board.isPromotion) @@ -221,9 +240,9 @@ short NegaMax::quiescence(const Board &board, short alpha, const short beta) } const auto validMoves = board.listQuiescenceMoves(); + size_t movesCount{}; - if (Stats::enabled()) - ++Stats::nodesSearched; + Stats::incrementNodesGenerated(validMoves.size()); for (const Board &move : validMoves) { @@ -236,12 +255,16 @@ short NegaMax::quiescence(const Board &board, short alpha, const short beta) return moveScore; if (moveScore > alpha) alpha = moveScore; + + ++movesCount; } + Stats::incrementNodesSearched(movesCount); + return alpha; } -short NegaMax::negaScout(const Board &board, const short ply, short alpha, const short beta, const bool isWhite, const short depth) +short Search::negaScout(const Board &board, const short ply, short alpha, const short beta, const bool isWhite, const short depth) { if (board.state == State::DRAW) return 0; @@ -278,8 +301,8 @@ short NegaMax::negaScout(const Board &board, const short ply, short alpha, const return bestScore; } -inline short NegaMax::sideToMove(const Board &board) +inline short Search::sideToMove(const Board &board) { const short value = Evaluation::evaluate(board); - return board.whiteToMove ? value : -value; + return board.colorToMove ? value : -value; } diff --git a/ChessAndroid/app/src/main/cpp/chess/algorithm/NegaMax.h b/ChessAndroid/app/src/main/cpp/chess/algorithm/Search.h similarity index 64% rename from ChessAndroid/app/src/main/cpp/chess/algorithm/NegaMax.h rename to ChessAndroid/app/src/main/cpp/chess/algorithm/Search.h index 244ed04..3d30968 100644 --- a/ChessAndroid/app/src/main/cpp/chess/algorithm/NegaMax.h +++ b/ChessAndroid/app/src/main/cpp/chess/algorithm/Search.h @@ -1,28 +1,29 @@ #pragma once -#include "../containers/StackVector.h" #include "../containers/TranspositionTable.h" +#include "../threads/ThreadPool.h" class Board; class RootMove; class Settings; -class NegaMax final +class Search final { - static bool s_QuiescenceSearchEnabled; + static ThreadPool s_ThreadPool; static TranspositionTable s_SearchCache; + static bool s_QuiescenceSearchEnabled; static short s_BestMoveFound; public: - NegaMax() = delete; - NegaMax(const NegaMax&) = delete; - NegaMax(NegaMax&&) = delete; + Search() = delete; + Search(const Search&) = delete; + Search(Search&&) = delete; - static RootMove getBestMove(const Board &board, const Settings &settings); + static RootMove findBestMove(const Board &board, const Settings &settings); static short getBestMoveFound(); private: - static RootMove negaMaxRoot(StackVector validMoves, unsigned int jobCount, short ply); + static RootMove negaMaxRoot(const std::vector &validMoves, unsigned int jobCount, short ply); static short negaMax(const Board &board, short ply, short alpha, short beta, short depth, bool moveCountPruning); static short quiescence(const Board &board, short alpha, short beta); static short negaScout(const Board &board, short ply, short alpha, short beta, bool isWhite, short depth); diff --git a/ChessAndroid/app/src/main/cpp/chess/containers/Containers.h b/ChessAndroid/app/src/main/cpp/chess/containers/Containers.h index 7a4a833..2363f47 100644 --- a/ChessAndroid/app/src/main/cpp/chess/containers/Containers.h +++ b/ChessAndroid/app/src/main/cpp/chess/containers/Containers.h @@ -1,14 +1,15 @@ #pragma once +#include + #include "PosMap.h" -#include "StackVector.h" -class Attacks +using U64 = std::uint64_t; + +class AttacksMap { public: - U64 board[2][6]{}; // Color[2] and Piece Types[6] + std::array, 2> board{}; // Color[2] and Piece Types[6] PosMap map; }; -template -using PosVector = StackVector; diff --git a/ChessAndroid/app/src/main/cpp/chess/containers/PosMap.h b/ChessAndroid/app/src/main/cpp/chess/containers/PosMap.h index d8c3069..539c0b3 100644 --- a/ChessAndroid/app/src/main/cpp/chess/containers/PosMap.h +++ b/ChessAndroid/app/src/main/cpp/chess/containers/PosMap.h @@ -1,8 +1,6 @@ #pragma once -using byte = unsigned char; - -class Pos; +#include "../data/Pos.h" class PosMap { diff --git a/ChessAndroid/app/src/main/cpp/chess/containers/StackVector.h b/ChessAndroid/app/src/main/cpp/chess/containers/StackVector.h deleted file mode 100644 index 2ab04a1..0000000 --- a/ChessAndroid/app/src/main/cpp/chess/containers/StackVector.h +++ /dev/null @@ -1,309 +0,0 @@ -#pragma once - -#include -#include -#include - -template -class StackVector -{ -public: - using size_type = std::size_t; - using value_type = T; - using pointer = value_type * ; - using const_pointer = const value_type*; - using reference = value_type & ; - using const_reference = const value_type&; - - class iterator - { - public: - using iterator_category = std::forward_iterator_tag; - using value_type = T; - using difference_type = std::ptrdiff_t; - using pointer = value_type * ; - using reference = value_type & ; - - constexpr iterator() noexcept - : _ptr(nullptr) {} - constexpr explicit iterator(const_pointer item) noexcept - : _ptr(const_cast(item)) {} - constexpr iterator(const iterator &iter) noexcept - : _ptr(iter._ptr) {} - ~iterator() = default; - - iterator &operator=(const iterator&) = default; - - constexpr void operator+=(const size_type n) noexcept { _ptr += n; } - constexpr void operator+=(const iterator &other) noexcept { _ptr += other._ptr; } - constexpr iterator operator+(const size_type n) const noexcept - { - iterator temp = *this; - temp += n; - return temp; - } - constexpr difference_type operator+(const iterator &other) const noexcept { return _ptr + other._ptr; } - - constexpr void operator-=(const size_type n) noexcept { _ptr -= n; } - constexpr void operator-=(const iterator &other) noexcept { _ptr -= other._ptr; } - constexpr iterator operator-(const size_type n) const noexcept - { - iterator temp = *this; - temp._ptr -= n; - return temp; - } - constexpr difference_type operator-(const iterator &other) const noexcept { return _ptr - other._ptr; } - - constexpr bool operator==(const iterator &iter) const noexcept { return _ptr == iter._ptr; } - constexpr bool operator!=(const iterator &iter) const noexcept { return _ptr != iter._ptr; } - constexpr bool operator<(const iterator &iter) const noexcept { return _ptr < iter._ptr; } - constexpr bool operator<=(const iterator &iter) const noexcept { return _ptr <= iter._ptr; } - constexpr bool operator>(const iterator &iter) const noexcept { return _ptr > iter._ptr; } - constexpr bool operator>=(const iterator &iter) const noexcept { return _ptr >= iter._ptr; } - - constexpr iterator &operator++() noexcept - { - ++_ptr; - return *this; - } - constexpr iterator operator++(int) noexcept - { - iterator temp = *this; - ++_ptr; - return temp; - } - constexpr iterator &operator--() noexcept - { - --_ptr; - return *this; - } - constexpr iterator operator--(int) noexcept - { - iterator temp = *this; - --_ptr; - return temp; - } - - constexpr reference operator[](const difference_type n) noexcept { return *(_ptr + n); } - constexpr reference operator*() const noexcept { return *_ptr; } - constexpr pointer operator->() const noexcept { return _ptr; } - - private: - pointer _ptr; - }; - - using const_iterator = iterator; - using reverse_iterator = std::reverse_iterator; - using const_reverse_iterator = std::reverse_iterator; - - constexpr StackVector() noexcept - : _size(0) {} - - constexpr explicit StackVector(const size_type size) noexcept - : _size(_size = size > N ? N : size) {} - - constexpr StackVector(std::initializer_list list) noexcept - : _size(_size = list.size() > N ? N : list.size()) - { - std::copy(list.begin(), list.begin() + _size, begin()); - } - constexpr StackVector(const StackVector&) noexcept = default; - constexpr StackVector(StackVector&&) noexcept = default; - - ~StackVector() - { - destroyAll(begin(), end()); - } - - constexpr StackVector &operator=(std::initializer_list list) noexcept - { - _size = list.size() > N ? N : list.size(); - - std::copy(list.begin(), list.begin() + _size, begin()); - - return *this; - } - - template - constexpr StackVector &operator=(const StackVector &other) noexcept - { - _size = other.size() > N ? N : other.size(); - - std::copy(other.begin(), other.begin() + _size, begin()); - - return *this; - } - - - template - constexpr StackVector &operator+=(StackVector &&other) noexcept - { - size_t otherSize = other.size() > N ? N : other.size(); - - if (_size + otherSize > N) - otherSize = N - (_size + otherSize); - - std::move(other.begin(), other.begin() + otherSize, begin() + _size); - - _size += otherSize; - - return *this; - } - - constexpr StackVector &operator=(const StackVector&) noexcept = default; - constexpr StackVector &operator=(StackVector&&) noexcept = default; - - // Element Access - constexpr reference at(size_type pos) noexcept(false) - { - if (pos >= _size) throwLengthException(); - return _array[pos]; - } - constexpr const_reference at(size_type pos) const noexcept(false) - { - if (pos >= _size) throwLengthException(); - return _array[pos]; - } - - constexpr reference operator[](size_type pos) noexcept(false) - { - return _array[pos]; - } - constexpr const_reference operator[](size_type pos) const noexcept(false) - { - return _array[pos]; - } - - constexpr reference front() noexcept { return _array[0]; } - constexpr const_reference front() const noexcept { return _array[0]; } - - constexpr reference back() noexcept { return _array[_size - 1]; } - constexpr const_reference back() const noexcept { return _array[_size - 1]; } - - constexpr pointer data() noexcept { return _array; } - constexpr const_pointer data() const noexcept { return _array; } - - // Iterators - constexpr iterator begin() noexcept { return iterator(_array); } - constexpr const_iterator begin() const noexcept { return const_iterator(_array); } - - constexpr iterator end() noexcept { return iterator(_array + _size); } - constexpr const_iterator end() const noexcept { return const_iterator(_array + _size); } - - constexpr reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } - constexpr const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } - - constexpr reverse_iterator rend() noexcept { return reverse_iterator(begin()); } - constexpr const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } - - // Capacity - constexpr bool empty() const noexcept { return _size == 0; } - constexpr bool full() const noexcept { return _size == N; } - constexpr size_type size() const noexcept { return _size; } - static constexpr size_type capacity() noexcept { return N; } - - // Modifiers - constexpr void clear() noexcept - { - destroyAll(begin(), end()); - _size = 0; - } - - template - constexpr reference emplace(const size_type pos, Args&&... args) noexcept(false) - { - if (++_size > N) throwLengthException(); - std::move(_array + pos, _array + _size - 1, _array + pos + 1); - - reference ref = _array[_size - 1]; - if constexpr (!std::is_trivially_destructible_v) - ref.~T(); - - new (&ref) T(std::forward(args)...); - - return ref; - } - - constexpr iterator erase(iterator pos) noexcept - { - if constexpr (std::is_destructible::value) - (*pos).~T(); - std::move(pos + 1, end(), pos); - --_size; - - return pos; - } - - constexpr iterator erase(iterator first, iterator last) noexcept - { - destroyAll(first, last); - std::move(last, end(), first); - - _size -= last - first; - - return last; - } - - constexpr void push_back(T &&value) noexcept(false) - { - if (++_size > N) throwLengthException(); - _array[_size - 1] = std::move(value); - } - - constexpr void push_back(const T &value) noexcept(false) - { - if (++_size > N) throwLengthException(); - _array[_size - 1] = value; - } - - template - constexpr reference emplace_back(Args&&... args) noexcept(false) - { - if (++_size > N) throwLengthException(); - - reference ref = _array[_size - 1]; - new (&ref) T(std::forward(args)...); - - return ref; - } - - constexpr void pop_front() noexcept - { - if (_size > 0) - erase(begin()); - } - - constexpr void pop_back() noexcept - { - if (_size > 0) - { - --_size; - if constexpr (!std::is_trivially_destructible_v) - _array[_size].~T(); - } - } - -private: - union - { - bool _hidden[N * sizeof(T)]; - value_type _array[N]; - }; - size_type _size; - - constexpr void destroyAll(iterator first, iterator last) - { - if constexpr (!std::is_trivially_destructible_v) - { - while (first != last) - { - first->~T(); - ++first; - } - } - } - - static constexpr void throwLengthException() noexcept(false) - { - } -}; diff --git a/ChessAndroid/app/src/main/cpp/chess/containers/TranspositionTable.cpp b/ChessAndroid/app/src/main/cpp/chess/containers/TranspositionTable.cpp index c48d162..8b7e055 100644 --- a/ChessAndroid/app/src/main/cpp/chess/containers/TranspositionTable.cpp +++ b/ChessAndroid/app/src/main/cpp/chess/containers/TranspositionTable.cpp @@ -21,8 +21,11 @@ void TranspositionTable::insert(const SearchCache &value) noexcept std::lock_guard lock(m_Mutexes[index % MUTEX_COUNT]); SearchCache &ref = m_Values[index]; - if (ref.ply <= value.ply) + if (ref.ply <= value.ply || ref.age != m_CurrentAge) + { ref = value; + ref.age = m_CurrentAge; + } } bool TranspositionTable::setSize(const std::size_t sizeMb) noexcept(false) @@ -34,11 +37,23 @@ bool TranspositionTable::setSize(const std::size_t sizeMb) noexcept(false) m_Size = newSize; delete[] m_Values; m_Values = new SearchCache[m_Size](); + m_CurrentAge = 0u; return true; } +void TranspositionTable::incrementAge() noexcept +{ + ++m_CurrentAge; +} + +byte TranspositionTable::currentAge() const noexcept +{ + return m_CurrentAge; +} + void TranspositionTable::clear() noexcept { std::memset(m_Values, 0, sizeof(SearchCache) * m_Size); + m_CurrentAge = 0u; } diff --git a/ChessAndroid/app/src/main/cpp/chess/containers/TranspositionTable.h b/ChessAndroid/app/src/main/cpp/chess/containers/TranspositionTable.h index e9fa7f2..eeaf84a 100644 --- a/ChessAndroid/app/src/main/cpp/chess/containers/TranspositionTable.h +++ b/ChessAndroid/app/src/main/cpp/chess/containers/TranspositionTable.h @@ -4,39 +4,44 @@ #include #include -#include "../data/Enums.h" +#include "../data/Defs.h" using U64 = std::uint64_t; +using byte = unsigned char; struct SearchCache { - U64 key = 0; - short boardScore = 0, value = 0; - short ply = 0; - Flag flag = Flag::EXACT; + U64 key = 0; + short boardScore = 0, value = 0; + short ply = 0; + Flag flag = Flag::EXACT; + byte age = 0; }; class TranspositionTable { constexpr static int MUTEX_COUNT = 1024; - std::size_t m_Size; + std::size_t m_Size; + byte m_CurrentAge{}; SearchCache *m_Values = new SearchCache[m_Size](); mutable std::shared_mutex m_Mutexes[MUTEX_COUNT]; public: - explicit TranspositionTable(std::size_t sizeMb) noexcept; + explicit TranspositionTable(std::size_t sizeMb) noexcept; - TranspositionTable(const TranspositionTable&) = delete; - TranspositionTable(TranspositionTable&&) = delete; + TranspositionTable(const TranspositionTable &) = delete; + TranspositionTable(TranspositionTable &&) = delete; ~TranspositionTable() noexcept; - TranspositionTable &operator=(const TranspositionTable&) = delete; - TranspositionTable &operator=(TranspositionTable&&) = delete; + TranspositionTable &operator=(const TranspositionTable &) = delete; + TranspositionTable &operator=(TranspositionTable &&) = delete; SearchCache operator[](U64 key) const noexcept; - void insert(const SearchCache &value) noexcept; + void insert(const SearchCache &value) noexcept; bool setSize(std::size_t sizeMb) noexcept(false); + void incrementAge() noexcept; + byte currentAge() const noexcept; void clear() noexcept; }; diff --git a/ChessAndroid/app/src/main/cpp/chess/data/Bitboard.h b/ChessAndroid/app/src/main/cpp/chess/data/Bitboard.h index ea10163..9e97a4e 100644 --- a/ChessAndroid/app/src/main/cpp/chess/data/Bitboard.h +++ b/ChessAndroid/app/src/main/cpp/chess/data/Bitboard.h @@ -2,32 +2,14 @@ #include #include -#include -using byte = unsigned char; -using U64 = std::uint64_t; +#include "Defs.h" -constexpr U64 RANK_1 = 0xffull; -constexpr U64 RANK_2 = 0xff00ull; -constexpr U64 RANK_3 = 0xff0000ull; -constexpr U64 RANK_4 = 0xff000000ull; -constexpr U64 RANK_5 = 0xff00000000ull; -constexpr U64 RANK_6 = 0xff0000000000ull; -constexpr U64 RANK_7 = 0xff000000000000ull; -constexpr U64 RANK_8 = 0xff00000000000000ull; +#ifdef _MSC_VER +# include // Microsoft header for _BitScanForward64() +#endif -constexpr U64 FILE_H = 0x8080808080808080ull; -constexpr U64 FILE_G = 0x4040404040404040ull; -constexpr U64 FILE_F = 0x2020202020202020ull; -constexpr U64 FILE_E = 0x1010101010101010ull; -constexpr U64 FILE_D = 0x808080808080808ull; -constexpr U64 FILE_C = 0x404040404040404ull; -constexpr U64 FILE_B = 0x202020202020202ull; -constexpr U64 FILE_A = 0x101010101010101ull; - -constexpr byte row(const byte pos) noexcept { return static_cast(pos / 8u); } - -constexpr byte col(const byte pos) noexcept { return static_cast(pos % 8u); } +//#define USE_CUSTOM_POPCNT namespace Bitboard { @@ -50,63 +32,114 @@ namespace Bitboard /** * Table of precalculated shifted bitboards indexed by the times 1 has been shifted to the left */ - constexpr std::array shiftedBoards = [] { - std::array array{}; + constexpr auto shiftedBoards = [] + { + std::array array{}; + + for (auto i = 0u; i < SQUARE_NB; ++i) + array[i] = 1ULL << i; - for (auto i = 0u; i < 64u; ++i) - array[i] = 1ULL << i; + return array; + }(); + + constexpr U64 getSquare64(const byte square) noexcept + { + return shiftedBoards[square]; + } - return array; - }(); +#if defined(__GNUC__) // GCC, Clang + + inline byte bitScanForward(const U64 bb) noexcept + { + assert(bb); + return static_cast(__builtin_ctzll(bb)); + } + + inline byte bitScanReverse(U64 bb) noexcept + { + assert(bb); + return static_cast(63 ^ __builtin_clzll(bb)); + } + +#elif defined(_MSC_VER) + + inline byte bitScanForward(const U64 bb) noexcept + { + assert(bb); + unsigned long index{}; + +# ifdef _WIN64 // 64-bit + _BitScanForward64(&index, bb); + return static_cast(index); +# else // 32-bit + if (bb & 0xffffffff) // If the bit is in the lower 32 bits + { + _BitScanForward(&index, static_cast(bb)); + return static_cast(index); + } + + _BitScanForward(&index, static_cast(bb >> 32)); + return static_cast(index + 32u); // The bit is in the upper 32 bits +# endif + } + + inline byte bitScanReverse(const U64 bb) noexcept + { + assert(bb); + unsigned long index{}; + +# ifdef _WIN64 // 64-bit + _BitScanReverse64(&index, bb); + return static_cast(index); +# else // 32-bit + if (bb >> 32u) // If the bit is in the upper 32 bits + { + _BitScanReverse(&index, static_cast(bb >> 32u)); + return static_cast(index + 32u); + } + + _BitScanReverse(&index, static_cast(bb)); + return static_cast(index); +# endif + } + +#else // No specific compiler found, fallback to general functions +# undef USE_CUSTOM_POPCNT +# define USE_CUSTOM_POPCNT + + constexpr static U64 debruijn64{ 0x03f79d71b4cb0a89 }; + constexpr static byte bitScanIndex64[64] = { + 0, 47, 1, 56, 48, 27, 2, 60, + 57, 49, 41, 37, 28, 16, 3, 61, + 54, 58, 35, 52, 50, 42, 21, 44, + 38, 32, 29, 23, 17, 11, 4, 62, + 46, 55, 26, 59, 40, 36, 15, 53, + 34, 51, 20, 43, 31, 22, 10, 45, + 25, 39, 14, 33, 19, 30, 9, 24, + 13, 18, 8, 12, 7, 6, 5, 63 + }; /** - * bitScanForward * @author Kim Walisch (2012) * @param bb bitboard to scan * @precondition bb != 0 * @return index (0..63) of least significant one bit */ - constexpr byte bitScanForward(const U64 bb) noexcept + inline byte bitScanForward(const U64 bb) noexcept { - assert (bb != 0ull); - - constexpr U64 debruijn64{ 0x03f79d71b4cb0a89 }; - constexpr byte index64[64] = { - 0, 47, 1, 56, 48, 27, 2, 60, - 57, 49, 41, 37, 28, 16, 3, 61, - 54, 58, 35, 52, 50, 42, 21, 44, - 38, 32, 29, 23, 17, 11, 4, 62, - 46, 55, 26, 59, 40, 36, 15, 53, - 34, 51, 20, 43, 31, 22, 10, 45, - 25, 39, 14, 33, 19, 30, 9, 24, - 13, 18, 8, 12, 7, 6, 5, 63 - }; - - return index64[((bb ^ (bb - 1)) * debruijn64) >> 58]; + assert(bb); + return bitScanIndex64[((bb ^ (bb - 1)) * debruijn64) >> 58]; } /** - * bitScanReverse * @authors Kim Walisch, Mark Dickinson * @param bb bitboard to scan * @precondition bb != 0 * @return index (0..63) of most significant one bit */ - constexpr byte bitScanReverse(U64 bb) noexcept - { - assert (bb != 0); - - constexpr U64 debruijn64{ 0x03f79d71b4cb0a89 }; - constexpr byte index64[64] = { - 0, 47, 1, 56, 48, 27, 2, 60, - 57, 49, 41, 37, 28, 16, 3, 61, - 54, 58, 35, 52, 50, 42, 21, 44, - 38, 32, 29, 23, 17, 11, 4, 62, - 46, 55, 26, 59, 40, 36, 15, 53, - 34, 51, 20, 43, 31, 22, 10, 45, - 25, 39, 14, 33, 19, 30, 9, 24, - 13, 18, 8, 12, 7, 6, 5, 63 - }; + inline byte bitScanReverse(U64 bb) noexcept + { + assert(bb); bb |= bb >> 1; bb |= bb >> 2; @@ -115,32 +148,161 @@ namespace Bitboard bb |= bb >> 16; bb |= bb >> 32; - return index64[(bb * debruijn64) >> 58]; + return bitScanIndex64[(bb * debruijn64) >> 58]; } - constexpr byte popLsb(U64 &bb) noexcept - { - const byte lsbIndex = bitScanForward(bb); - bb &= bb - 1; - return lsbIndex; - } +#endif +#if defined(USE_CUSTOM_POPCNT) /** - * popCount * @author Brian Kernighan * @param x bitboard to count the bits from * @return number of 1 bits in the bitboard */ - constexpr int popCount(U64 x) noexcept - { - int count = 0; + inline int popCount(U64 bb) noexcept + { + int count = 0; - while (x) + while (bb) { - x &= x - 1; // clear the least significant bit set + bb &= bb - 1; // clear the least significant bit set ++count; } - return count; - } + return count; + } +# elif defined(__GNUC__) // GCC, Clang + + inline int popCount(const U64 bb) noexcept + { + return __builtin_popcountll(bb); + } + +# elif defined(_MSC_VER) + + inline int popCount(const U64 bb) noexcept + { +# ifdef _WIN64 // 64-bit + return static_cast(__popcnt64(bb)); +# else // 32-bit + return static_cast(__popcnt(unsigned(bb)) + __popcnt(unsigned(bb >> 32u))); +# endif + } + +# endif + + inline byte popLsb(U64 &bb) noexcept + { + const byte lsbIndex = bitScanForward(bb); + bb &= bb - 1; + return lsbIndex; + } + + inline byte findNextSquare(U64 &bb) + { + const byte square = Bitboard::bitScanForward(bb); + bb ^= Bitboard::shiftedBoards[square]; + return square; + } + +// region Rays + /** + * Table of precalculated ray bitboards indexed by direction and square + */ + constexpr static auto rays = [] { + std::array, 8> rays{}; + + using namespace Bitboard; + + for (byte square = 0u; square < SQUARE_NB; ++square) + { + rays[NORTH][square] = 0x0101010101010100ULL << square; + + rays[SOUTH][square] = 0x0080808080808080ULL >> (63u - square); + + rays[EAST][square] = 2u * (shiftedBoards[square | 7u] - shiftedBoards[square]); + + rays[WEST][square] = shiftedBoards[square] - shiftedBoards[square & 56u]; + + rays[NORTH_WEST][square] = westN(0x102040810204000ULL, 7u - col(square)) << (row(square) * 8u); + + rays[NORTH_EAST][square] = eastN(0x8040201008040200ULL, col(square)) << (row(square) * 8u); + + rays[SOUTH_WEST][square] = westN(0x40201008040201ULL, 7u - col(square)) >> ((7u - row(square)) * 8u); + + rays[SOUTH_EAST][square] = eastN(0x2040810204080ULL, col(square)) >> ((7u - row(square)) * 8u); + } + + return rays; + }(); + + constexpr static auto ranks = [] { + std::array ranks{}; + + for (byte r = 0u; r < 8u; ++r) + ranks[r] = 0b1111'1111ull << (8u * r); + + return ranks; + }(); + + constexpr static auto files = [] { + std::array files{}; + + for (byte f = 0u; f < 8u; ++f) + files[f] = 0x101010101010101ull << f; + + return files; + }(); + + template + constexpr U64 shift(const U64 bb) noexcept + { + if constexpr (D == NORTH) + return bb << 8u; + else if constexpr (D == SOUTH) + return bb >> 8u; + else if constexpr (D == EAST) + return (bb & ~FILE_H) << 1u; + else if constexpr (D == WEST) + return (bb & ~FILE_A) >> 1u; + + else if constexpr (D == NORTH_EAST) + return (bb & ~FILE_H) << 9u; + else if constexpr (D == NORTH_WEST) + return (bb & ~FILE_A) << 7u; + else if constexpr (D == SOUTH_EAST) + return (bb & ~FILE_H) >> 7u; + else if constexpr (D == SOUTH_WEST) + return (bb & ~FILE_A) >> 9u; + + return {}; + } + + /** + * Returns a bitboard containing the given ray in the given direction. + * + * @param direction Direction of ray to return + * @param square Square to get ray starting from (in little endian rank file mapping form) + */ + constexpr U64 getRay(const Dir direction, const byte square) noexcept + { + return rays[direction][square]; + } + + constexpr U64 getRank(const byte square) noexcept + { + return ranks[row(square)]; + } + + constexpr U64 getFile(const byte square) noexcept + { + return files[col(square)]; + } + + constexpr U64 getAdjacentFiles(const byte square) noexcept + { + return shift(getFile(square)) | shift(getFile(square)); + } + +// endregion Rays } diff --git a/ChessAndroid/app/src/main/cpp/chess/data/Board.cpp b/ChessAndroid/app/src/main/cpp/chess/data/Board.cpp index 22afe3e..54f0407 100644 --- a/ChessAndroid/app/src/main/cpp/chess/data/Board.cpp +++ b/ChessAndroid/app/src/main/cpp/chess/data/Board.cpp @@ -1,36 +1,57 @@ #include "Board.h" #include "../algorithm/Hash.h" -#include "../algorithm/Evaluation.h" +#include "../persistence/FenParser.h" -Piece &Board::operator[](const Pos &pos) noexcept +void Board::initDefaultBoard() { - return data[pos.x][pos.y]; + setToFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"); } -const Piece &Board::operator[](const Pos &pos) const noexcept +void Board::setToFen(const std::string &fen) { - return data[pos.x][pos.y]; + FenParser parser(*this); + parser.parseFen(fen); } -bool Board::operator<(const Board &other) const noexcept +bool Board::canCastle(const Color color) const noexcept { - return score < other.score; + return castlingRights & (color == BLACK ? CASTLE_BLACK_BOTH : CASTLE_WHITE_BOTH); } -bool Board::operator>(const Board &other) const noexcept +bool Board::canCastleKs(const Color color) const noexcept { - return score > other.score; + return castlingRights & (color == BLACK ? CASTLE_BLACK_KING : CASTLE_WHITE_KING); +} + +bool Board::canCastleQs(const Color color) const noexcept +{ + return castlingRights & (color == BLACK ? CASTLE_BLACK_QUEEN : CASTLE_WHITE_QUEEN); +} + +bool Board::isCastled(const Color color) const noexcept +{ + return castlingRights & (color == BLACK ? CASTLED_BLACK : CASTLED_WHITE); +} + +Piece &Board::getPiece(const byte squareIndex) noexcept +{ + return data[squareIndex]; +} + +const Piece &Board::getPiece(const byte squareIndex) const noexcept +{ + return data[squareIndex]; } Piece &Board::getPiece(const byte x, const byte y) noexcept { - return data[x][y]; + return data[toSquare(x, y)]; } const Piece &Board::getPiece(const byte x, const byte y) const noexcept { - return data[x][y]; + return data[toSquare(x, y)]; } /* @@ -38,108 +59,53 @@ const Piece &Board::getPiece(const byte x, const byte y) const noexcept */ const Piece &Board::at(const byte x, const byte y) const noexcept { - if (x < 8 && y < 8) - return data[x][y]; + const Pos pos(x, y); + if (pos.isValid()) + return data[pos.toSquare()]; return Piece::EMPTY; } -void Board::initDefaultBoard() noexcept +U64 &Board::getType(const Piece piece) noexcept { - std::memset(&data, 0, sizeof(data)); - npm = 0; - - for (auto &x : data) - x[1] = Piece(Type::PAWN, true); - - for (auto &x : data) - x[6] = Piece(Type::PAWN, false); - - npm += 16 * Evaluation::getPieceValue(Type::PAWN); - - data[1][0] = Piece(Type::KNIGHT, true); - data[6][0] = Piece(Type::KNIGHT, true); - data[1][7] = Piece(Type::KNIGHT, false); - data[6][7] = Piece(Type::KNIGHT, false); - npm += 4 * Evaluation::getPieceValue(Type::KNIGHT); - - data[2][0] = Piece(Type::BISHOP, true); - data[5][0] = Piece(Type::BISHOP, true); - data[2][7] = Piece(Type::BISHOP, false); - data[5][7] = Piece(Type::BISHOP, false); - npm += 4 * Evaluation::getPieceValue(Type::BISHOP); - - data[0][0] = Piece(Type::ROOK, true); - data[7][0] = Piece(Type::ROOK, true); - data[0][7] = Piece(Type::ROOK, false); - data[7][7] = Piece(Type::ROOK, false); - npm += 4 * Evaluation::getPieceValue(Type::ROOK); - - data[3][0] = Piece(Type::QUEEN, true); - data[3][7] = Piece(Type::QUEEN, false); - npm += 2 * Evaluation::getPieceValue(Type::QUEEN); + return pieces[piece.color()][piece.type()]; +} - data[4][0] = Piece(Type::KING, true); - data[4][7] = Piece(Type::KING, false); +U64 Board::getType(const Piece piece) const noexcept +{ + return pieces[piece.color()][piece.type()]; +} - key = Hash::compute(*this); - whiteCastled = blackCastled = false; - whiteToMove = true; - state = State::NONE; - score = 0; - isPromotion = isCapture = false; - enPassantPos = Pos(); +U64 &Board::getType(const Color color, const PieceType type) noexcept +{ + return pieces[color][type]; +} - constexpr byte whiteKingLocation = Pos(4, 0).toSquare(); - constexpr byte blackKingLocation = Pos(4, 7).toSquare(); - kingSquare[1] = whiteKingLocation; - kingSquare[0] = blackKingLocation; +U64 Board::getType(const Color color, const PieceType type) const noexcept +{ + return pieces[color][type]; +} - memset(allPieces, 0, sizeof(U64) * 2); +bool Board::operator<(const Board &other) const noexcept +{ + return score < other.score; +} - for (byte x = 0u; x < 8u; x++) - { - for (byte y = 0u; y < 8u; y++) - { - const Piece &piece = getPiece(x, y); - const U64 bitboard = Pos(x, y).toBitboard(); - if (piece) - allPieces[piece.isWhite] |= bitboard; - - switch (piece.type) - { - case PAWN: - pawns[piece.isWhite] |= bitboard; - break; - case KNIGHT: - knights[piece.isWhite] |= bitboard; - break; - case BISHOP: - bishops[piece.isWhite] |= bitboard; - break; - case ROOK: - rooks[piece.isWhite] |= bitboard; - break; - case QUEEN: - queens[piece.isWhite] |= bitboard; - break; - default: - break; - } - } - } +bool Board::operator>(const Board &other) const noexcept +{ + return score > other.score; } void Board::updateState() noexcept { state = State::NONE; - if (Player::onlyKingsLeft(*this)) + if (Player::onlyKingsLeft(*this) || halfMoveClock == 50u) { state = State::DRAW; return; } - const bool whiteInCheck = Player::isInCheck(true, *this); - const bool blackInCheck = Player::isInCheck(false, *this); + const bool whiteInCheck = Player::isInCheck(WHITE, *this); + const bool blackInCheck = Player::isInCheck(BLACK, *this); if (whiteInCheck && blackInCheck) { @@ -152,64 +118,128 @@ void Board::updateState() noexcept else if (blackInCheck) state = State::BLACK_IN_CHECK; - if (whiteToMove) + if (colorToMove) { - if (Player::hasNoValidMoves(true, *this)) + if (Player::hasNoValidMoves(WHITE, *this)) state = whiteInCheck ? State::WINNER_BLACK : State::DRAW; } else { - if (Player::hasNoValidMoves(false, *this)) + if (Player::hasNoValidMoves(BLACK, *this)) state = blackInCheck ? State::WINNER_WHITE : State::DRAW; } } Phase Board::getPhase() const noexcept { - constexpr short midGameLimit = 15258, endGameLimit = 3915; + constexpr short midGameLimit = 15258; + constexpr short endGameLimit = 3915; - const int limit = std::max(endGameLimit, std::min(npm, midGameLimit)); + const short limit = std::max(endGameLimit, std::min(npm, midGameLimit)); return static_cast(((limit - endGameLimit) * 128) / (midGameLimit - endGameLimit)); } -StackVector, 32> Board::getAllPieces() const noexcept +bool Board::hasValidState() const noexcept +{ + const Color previousPlayer = ~colorToMove; + + if (state == State::INVALID) + return false; + if (previousPlayer == WHITE && (state == State::WHITE_IN_CHECK || state == State::WINNER_BLACK)) + return false; + if (previousPlayer == BLACK && (state == State::BLACK_IN_CHECK || state == State::WINNER_WHITE)) + return false; + + return true; +} + +std::vector> Board::getAllPieces() const { - StackVector, 32> pieces; + std::vector> pieces; + pieces.reserve(32); - for (byte x = 0; x < 8; x++) - for (byte y = 0; y < 8; y++) - if (const Piece &piece = getPiece(x, y)) - pieces.emplace_back(Pos(x, y), piece); + for (byte square = 0u; square < 64u; ++square) + if (const Piece &piece = data[square]) + pieces.emplace_back(Pos(square), piece); return pieces; } -std::vector Board::listQuiescenceMoves() const noexcept +void Board::doMove(const byte startSq, const byte destSq, const bool updateState) noexcept +{ + const Piece &startPiece = getPiece(startSq); + const Piece &destPiece = getPiece(destSq); + const U64 startBb = Bitboard::shiftedBoards[startSq]; + const U64 destBb = Bitboard::shiftedBoards[destSq]; + + score = 0; + state = State::NONE; + isPromotion = isCapture = false; + ++halfMoveClock; + + getType(colorToMove, startPiece.type()) &= ~startBb; + getType(~colorToMove, destPiece.type()) &= ~destBb; + getType(colorToMove, startPiece.type()) |= destBb; + + Hash::flipSide(zKey); + Hash::makeMove(zKey, startSq, destSq, startPiece, destPiece); + + if (startPiece.type() == PieceType::PAWN) + { + movePawn(startSq, destSq); + halfMoveClock = 0u; + } + else + { + enPassantSq = 64u; + if (startPiece.type() == PieceType::ROOK) + moveRook(startSq); + else if (startPiece.type() == PieceType::KING) + moveKing(startPiece, startSq, destSq); + } + + if (destPiece) + { + if (destPiece.type() != PAWN) + npm -= Evaluation::getPieceValue(destPiece.type()); + isCapture = true; + halfMoveClock = 0u; + } + + getPiece(destSq) = startPiece; + getPiece(startSq) = Piece(); + updateNonPieceBitboards(); + + colorToMove = ~colorToMove; + + if (updateState) + this->updateState(); +} + +std::vector Board::listQuiescenceMoves() const { - const auto pieces = Player::getAllOwnedPieces(whiteToMove, *this); std::vector moves; moves.reserve(50); - for (const auto &pair : pieces) + for (byte startSq = 0u; startSq < SQUARE_NB; ++startSq) { - const Pos &startPos = pair.first; - const Piece &selectedPiece = pair.second; - const auto possibleMoves = selectedPiece.getPossibleCaptures(startPos, *this); + const Piece &attacker = getPiece(startSq); + if (!attacker || attacker.color() != colorToMove) + continue; + + U64 possibleMoves = attacker.getPossibleCaptures(startSq, *this); - for (const Pos &destPos : possibleMoves) + // Make sure we are not capturing the king + possibleMoves &= ~getType(~colorToMove, KING); + + while (possibleMoves) { - const Piece &destPiece = (*this)[destPos]; - if (!destPiece || destPiece.type == Type::KING) - continue; + const byte destSq = Bitboard::findNextSquare(possibleMoves); Board board = *this; - BoardManager::movePieceInternal(startPos, destPos, board); + board.doMove(startSq, destSq); - if (board.state == State::INVALID) - continue; - if (whiteToMove && (board.state == State::WHITE_IN_CHECK || board.state == State::WINNER_BLACK)) - continue; - if (!whiteToMove && (board.state == State::BLACK_IN_CHECK || board.state == State::WINNER_WHITE)) + if (!board.hasValidState()) continue; board.score = Evaluation::simpleEvaluation(board); @@ -218,10 +248,158 @@ std::vector Board::listQuiescenceMoves() const noexcept } } - if (whiteToMove) + if (colorToMove) std::sort(moves.begin(), moves.end(), std::greater<>()); else std::sort(moves.begin(), moves.end()); return moves; } + +void Board::movePawn(const byte startSq, const byte destSq) +{ + Piece &pawn = getPiece(startSq); + + if (const byte y = row(destSq); + y == 0u || y == 7u) + { + const PieceType newPieceType = PieceType::QUEEN; + isPromotion = true; + + Hash::promotePawn(zKey, destSq, pawn.color(), newPieceType); + const U64 destBb = Bitboard::shiftedBoards[destSq]; + + getType(pawn.color(), PieceType::PAWN) &= ~destBb; + getType(pawn.color(), newPieceType) |= destBb; + + getPiece(startSq) = Piece(newPieceType, pawn.color()); + } + + if (enPassantSq < 64u && destSq == enPassantSq) + { + isCapture = true; + + Pos capturedPos(enPassantSq); + capturedPos.y += static_cast(pawn.color() ? -1 : 1); + Piece &capturedPiece = getPiece(capturedPos.toSquare()); + + // Remove the captured Pawn + Hash::xorPiece(zKey, capturedPos.toSquare(), capturedPiece); + getType(capturedPiece) &= ~capturedPos.toBitboard(); + capturedPiece = Piece(); + } + + enPassantSq = 64u; + + const int distance = static_cast(row(destSq)) - static_cast(row(startSq)); + if (distance == 2 || distance == -2) + { + Pos newEnPassant(destSq); + newEnPassant.y -= static_cast(distance / 2); + enPassantSq = newEnPassant.toSquare(); + } +} + +void Board::moveRook(const byte startSq) +{ + const bool pieceColor = getPiece(startSq).color(); + + Hash::xorCastlingRights(zKey, static_cast(castlingRights)); + + if (col(startSq) == 0u) + castlingRights &= ~(pieceColor ? CASTLE_WHITE_QUEEN : CASTLE_BLACK_QUEEN); + else if (col(startSq) == 7u) + castlingRights &= ~(pieceColor ? ~CASTLE_WHITE_KING : CASTLE_BLACK_KING); + + Hash::xorCastlingRights(zKey, static_cast(castlingRights)); +} + +void Board::moveKing(const Piece &king, const byte startSq, const byte destSq) +{ + const Color color = king.color(); + if (!canCastle(color)) return; + + bool castled = false; + + if (col(destSq) == 6u) + { + constexpr byte startX = 7u; + const byte y = row(startSq); + + Piece &rook = getPiece(startX, y); + if (canCastleKs(color) && rook == Piece(ROOK, color)) + { + constexpr byte destX = 5; + getPiece(destX, y) = rook; + + const Pos startPos(startX, y); + const Pos destPos(destX, y); + + getType(color, ROOK) &= ~startPos.toBitboard(); + getType(color, ROOK) |= destPos.toBitboard(); + + castled = true; + Hash::makeMove(zKey, startPos.toSquare(), destPos.toSquare(), rook); + rook = Piece::EMPTY; + } + } + else if (col(destSq) == 2u) + { + constexpr byte startX = 0u; + const byte y = row(startSq); + + Piece &rook = getPiece(startX, y); + if (canCastleQs(color) && rook == Piece(ROOK, color)) + { + constexpr byte destX = 3u; + getPiece(destX, y) = rook; + + const Pos startPos(startX, y); + const Pos destPos(destX, y); + + getType(color, ROOK) &= ~startPos.toBitboard(); + getType(color, ROOK) |= destPos.toBitboard(); + + castled = true; + Hash::makeMove(zKey, startPos.toSquare(), destPos.toSquare(), rook); + rook = Piece::EMPTY; + } + } + + if (castled) + { + Hash::xorCastlingRights(zKey, static_cast(castlingRights)); + + if (color) + { + castlingRights &= ~CASTLE_WHITE_BOTH; + castlingRights |= CASTLED_WHITE; + } else { + castlingRights &= ~CASTLE_BLACK_BOTH; + castlingRights |= CASTLED_BLACK; + } + + Hash::xorCastlingRights(zKey, static_cast(castlingRights)); + } else { + castlingRights &= ~(color ? CASTLE_WHITE_BOTH : CASTLE_BLACK_BOTH); + } +} + +void Board::updateNonPieceBitboards() +{ + allPieces[BLACK] = getType(BLACK, PAWN) + | getType(BLACK, KNIGHT) + | getType(BLACK, BISHOP) + | getType(BLACK, ROOK) + | getType(BLACK, QUEEN) + | getType(BLACK, KING); + + allPieces[WHITE] = getType(WHITE, PAWN) + | getType(WHITE, KNIGHT) + | getType(WHITE, BISHOP) + | getType(WHITE, ROOK) + | getType(WHITE, QUEEN) + | getType(WHITE, KING); + + occupied = allPieces[BLACK] | allPieces[WHITE]; +} diff --git a/ChessAndroid/app/src/main/cpp/chess/data/Board.h b/ChessAndroid/app/src/main/cpp/chess/data/Board.h index 9f1f94c..439f3e2 100644 --- a/ChessAndroid/app/src/main/cpp/chess/data/Board.h +++ b/ChessAndroid/app/src/main/cpp/chess/data/Board.h @@ -1,9 +1,10 @@ #pragma once -#include +#include +#include #include -#include "Player.h" +#include "../algorithm/Player.h" #include "Piece.h" #include "../BoardManager.h" #include "../algorithm/Evaluation.h" @@ -12,68 +13,82 @@ using U64 = std::uint64_t; class Board final { + friend class FenParser; + public: - Piece data[8][8]; - U64 key = 0u; - bool whiteCastled = false; - bool blackCastled = false; + std::array data{}; + U64 zKey{}; + U64 occupied{}; + std::array allPieces{}; + std::array, 2> pieces{}; + short score{}; + short npm{}; State state = State::NONE; - bool whiteToMove = true; - short score = 0; - short npm = 0; - int moveOrder = 0; - Pos enPassantPos{}; + Color colorToMove = WHITE; bool isPromotion = false; bool isCapture = false; - - // Index 0 -> black, index 1 -> white - U64 allPieces[2]{}; - U64 pawns[2]{}; - U64 knights[2]{}; - U64 bishops[2]{}; - U64 rooks[2]{}; - U64 queens[2]{}; - byte kingSquare[2]{}; + byte castlingRights = CastlingRights::CASTLE_WHITE_BOTH | CastlingRights::CASTLE_BLACK_BOTH; + byte halfMoveClock{}; + byte enPassantSq{}; Board() = default; - Board(Board&&) = default; + Board(Board&&) noexcept = default; Board(const Board &board) = default; ~Board() = default; - Board &operator=(Board&&) = default; + Board &operator=(Board&&) noexcept = default; Board &operator=(const Board &other) = default; - Piece &operator[](const Pos &pos) noexcept; - const Piece &operator[](const Pos &pos) const noexcept; - bool operator<(const Board &other) const noexcept; bool operator>(const Board &other) const noexcept; + void initDefaultBoard(); + void setToFen(const std::string &fen); + + bool canCastle(Color color) const noexcept; + bool canCastleKs(Color color) const noexcept; + bool canCastleQs(Color color) const noexcept; + bool isCastled(Color color) const noexcept; + + Piece &getPiece(byte squareIndex) noexcept; + const Piece &getPiece(byte squareIndex) const noexcept; Piece &getPiece(byte x, byte y) noexcept; const Piece &getPiece(byte x, byte y) const noexcept; const Piece &at(byte x, byte y) const noexcept; + U64 &getType(Piece piece) noexcept; + U64 getType(Piece piece) const noexcept; + U64 &getType(Color color, PieceType type) noexcept; + U64 getType(Color color, PieceType type) const noexcept; - void initDefaultBoard() noexcept; void updateState() noexcept; + bool hasValidState() const noexcept; Phase getPhase() const noexcept; - StackVector, 32> getAllPieces() const noexcept; + std::vector> getAllPieces() const; + void doMove(byte startSq, byte destSq, bool updateState = true) noexcept; template // RootMove or Board - StackVector listValidMoves() const noexcept; - std::vector listQuiescenceMoves() const noexcept; + std::vector listValidMoves() const noexcept; + std::vector listQuiescenceMoves() const; + +private: + void movePawn(byte startSq, byte destSq); + void moveRook(byte startSq); + void moveKing(const Piece &king, byte startSq, byte destSq); + + void updateNonPieceBitboards(); }; class RootMove final { public: - Pos start; - Pos dest; + byte startSq; + byte destSq; Board board; RootMove() = default; - RootMove(const Pos start, const Pos dest, const Board &board) noexcept - : start(start), dest(dest), board(board) {} + RootMove(const byte startSq, const byte destSq, const Board &board) noexcept + : startSq(startSq), destSq(destSq), board(board) {} bool operator<(const RootMove &other) const noexcept { @@ -87,31 +102,30 @@ class RootMove final }; template -StackVector Board::listValidMoves() const noexcept +std::vector Board::listValidMoves() const noexcept { - const auto pieces = Player::getAllOwnedPieces(whiteToMove, *this); - StackVector moves; + std::vector moves; + moves.reserve(100); - for (const auto &pair : pieces) + for (byte startSq = 0u; startSq < SQUARE_NB; ++startSq) { - const Pos &startPos = pair.first; - const Piece &selectedPiece = pair.second; - const auto possibleMoves = selectedPiece.getPossibleMoves(startPos, *this); + const Piece &attacker = getPiece(startSq); + if (!attacker || attacker.color() != colorToMove) + continue; + + U64 possibleMoves = attacker.getPossibleMoves(startSq, *this); + + // Make sure we are not capturing the king + possibleMoves &= ~getType(~colorToMove, KING); - for (const auto &destPos : possibleMoves) + while (possibleMoves) { - const auto &destPiece = (*this)[destPos]; - if (destPiece.type == Type::KING) - continue; + const byte destSq = Bitboard::findNextSquare(possibleMoves); Board board = *this; - BoardManager::movePieceInternal(startPos, destPos, board); + board.doMove(startSq, destSq); - if (board.state == State::INVALID) - continue; - if (whiteToMove && (board.state == State::WHITE_IN_CHECK || board.state == State::WINNER_BLACK)) - continue; - if (!whiteToMove && (board.state == State::BLACK_IN_CHECK || board.state == State::WINNER_WHITE)) + if (!board.hasValidState()) continue; board.score = Evaluation::simpleEvaluation(board); @@ -122,9 +136,9 @@ StackVector Board::listValidMoves() const noexcept for (const auto &game : BoardManager::getMovesHistory()) { - if (board.whiteToMove == game.board.whiteToMove && + if (board.colorToMove == game.board.colorToMove && board.state == game.board.state && - board.key == game.board.key) + board.zKey == game.board.zKey) count++; if (count == 3) @@ -135,14 +149,14 @@ StackVector Board::listValidMoves() const noexcept } } - moves.emplace_back(startPos, destPos, board); + moves.emplace_back(startSq, destSq, board); } else if (std::is_same_v) moves.push_back(board); } } - if (whiteToMove) + if (colorToMove) std::sort(moves.begin(), moves.end(), std::greater<>()); else std::sort(moves.begin(), moves.end()); diff --git a/ChessAndroid/app/src/main/cpp/chess/data/Defs.h b/ChessAndroid/app/src/main/cpp/chess/data/Defs.h new file mode 100644 index 0000000..45c8da0 --- /dev/null +++ b/ChessAndroid/app/src/main/cpp/chess/data/Defs.h @@ -0,0 +1,119 @@ +#pragma once + +#include + +using byte = std::uint8_t; +using U64 = std::uint64_t; + +enum Color : bool +{ + BLACK = false, + WHITE = true +}; + +constexpr Color operator~(const Color c) noexcept +{ + return Color(c ^ WHITE); // Toggle color +} + +enum Dir +{ + NORTH, + SOUTH, + EAST, + WEST, + NORTH_EAST, + NORTH_WEST, + SOUTH_EAST, + SOUTH_WEST +}; + +enum class State : unsigned char +{ + NONE, + WINNER_WHITE, + WINNER_BLACK, + DRAW, + WHITE_IN_CHECK, + BLACK_IN_CHECK, + INVALID = 10 +}; + +enum CastlingRights : unsigned char +{ + CASTLE_NONE = 0, + + CASTLE_BLACK_KING = 0b0001, + CASTLE_BLACK_QUEEN = 0b0010, + CASTLE_BLACK_BOTH = CASTLE_BLACK_KING | CASTLE_BLACK_QUEEN, + CASTLED_BLACK = 0b0100, + + CASTLE_WHITE_KING = 0b0'1000, + CASTLE_WHITE_QUEEN = 0b1'0000, + CASTLE_WHITE_BOTH = CASTLE_WHITE_KING | CASTLE_WHITE_QUEEN, + CASTLED_WHITE = 0b10'0000 +}; + +enum class Phase : short +{ + ENDING, + MIDDLE = 128 +}; + +enum Value : short +{ + VALUE_MAX = 32767, + VALUE_MIN = -VALUE_MAX, + + VALUE_WINNER_WHITE = VALUE_MAX - 1, + VALUE_WINNER_BLACK = -VALUE_WINNER_WHITE +}; + +enum class Flag : unsigned char +{ + EXACT, + ALPHA, + BETA +}; + +enum PieceType : unsigned char +{ + NO_PIECE_TYPE = 0, + PAWN = 1, + KNIGHT = 2, + BISHOP = 3, + ROOK = 4, + QUEEN = 5, + KING = 6, + + PIECE_TYPE_NB = 8 +}; + +constexpr U64 RANK_1 = 0xffull; +constexpr U64 RANK_2 = 0xff00ull; +constexpr U64 RANK_3 = 0xff0000ull; +constexpr U64 RANK_4 = 0xff000000ull; +constexpr U64 RANK_5 = 0xff00000000ull; +constexpr U64 RANK_6 = 0xff0000000000ull; +constexpr U64 RANK_7 = 0xff000000000000ull; +constexpr U64 RANK_8 = 0xff00000000000000ull; + +constexpr U64 FILE_H = 0x8080808080808080ull; +constexpr U64 FILE_G = 0x4040404040404040ull; +constexpr U64 FILE_F = 0x2020202020202020ull; +constexpr U64 FILE_E = 0x1010101010101010ull; +constexpr U64 FILE_D = 0x808080808080808ull; +constexpr U64 FILE_C = 0x404040404040404ull; +constexpr U64 FILE_B = 0x202020202020202ull; +constexpr U64 FILE_A = 0x101010101010101ull; + +constexpr byte SQUARE_NB = 64u; + +constexpr byte row(const byte pos) noexcept { return static_cast(pos >> 3u); } + +constexpr byte col(const byte pos) noexcept { return static_cast(pos & 7u); } + +constexpr byte toSquare(const byte x, const byte y) noexcept +{ + return static_cast((y << 3u) + x); +} diff --git a/ChessAndroid/app/src/main/cpp/chess/data/Enums.h b/ChessAndroid/app/src/main/cpp/chess/data/Enums.h deleted file mode 100644 index 8b96361..0000000 --- a/ChessAndroid/app/src/main/cpp/chess/data/Enums.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -enum class State : unsigned char -{ - NONE, - WINNER_WHITE, - WINNER_BLACK, - DRAW, - WHITE_IN_CHECK, - BLACK_IN_CHECK, - INVALID = 10 -}; - -enum class CastlingRights : unsigned char -{ - NONE, - CAN_CASTLE_KING, - CAN_CASTLE_QUEEN, - CAN_CASTLE_BOTH, - CASTLED -}; - -enum class Phase : short -{ - ENDING, - MIDDLE = 128 -}; - -enum Value : short -{ - VALUE_MAX = 32767, - VALUE_MIN = -VALUE_MAX, - - VALUE_WINNER_WHITE = VALUE_MAX - 1, - VALUE_WINNER_BLACK = -VALUE_WINNER_WHITE -}; - -enum class Flag : unsigned char -{ - EXACT, - ALPHA, - BETA -}; diff --git a/ChessAndroid/app/src/main/cpp/chess/data/Piece.cpp b/ChessAndroid/app/src/main/cpp/chess/data/Piece.cpp index c35cb01..d9a5582 100644 --- a/ChessAndroid/app/src/main/cpp/chess/data/Piece.cpp +++ b/ChessAndroid/app/src/main/cpp/chess/data/Piece.cpp @@ -4,62 +4,64 @@ const Piece Piece::EMPTY{}; -Piece::MaxMovesVector Piece::getPossibleMoves(const Pos &pos, const Board &board) const noexcept +U64 Piece::getPossibleMoves(const byte square, const Board &board) const noexcept { - MaxMovesVector result; + const Color c = color(); + U64 result{}; - switch (type) + switch (type()) { - case Type::NONE: + case PieceType::PAWN: + result = MoveGen::getPawnMoves(c, square, board); break; - case Type::PAWN: - result = MoveGen::generatePawnMoves(*this, pos, board); + case PieceType::KNIGHT: + result = MoveGen::getKnightMoves(c, square, board); break; - case Type::KNIGHT: - result = MoveGen::generateKnightMoves(*this, pos, board); + case PieceType::BISHOP: + result = MoveGen::getBishopMoves(c, square, board); break; - case Type::BISHOP: - result = MoveGen::generateBishopMoves(*this, pos, board); + case PieceType::ROOK: + result = MoveGen::getRookMoves(c, square, board); break; - case Type::ROOK: - result = MoveGen::generateRookMoves(*this, pos, board); + case PieceType::QUEEN: + result = MoveGen::getQueenMoves(c, square, board); break; - case Type::QUEEN: - result = MoveGen::generateQueenMoves(*this, pos, board); + case PieceType::KING: + result = MoveGen::getKingMoves(c, square, board); break; - case Type::KING: - result = MoveGen::generateKingMoves(*this, pos, board); + default: break; } - + return result; } -Piece::MaxMovesVector Piece::getPossibleCaptures(const Pos &pos, const Board &board) const noexcept +U64 Piece::getPossibleCaptures(const byte square, const Board &board) const noexcept { - MaxMovesVector result; + const Color c = color(); + U64 result{}; - switch (type) + switch (type()) { - case Type::NONE: + case PieceType::PAWN: + result = MoveGen::getPawnMoves(c, square, board); break; - case Type::PAWN: - result = MoveGen::generatePawnMoves(*this, pos, board); + case PieceType::KNIGHT: + result = MoveGen::getKnightMoves(c, square, board); break; - case Type::KNIGHT: - result = MoveGen::generateKnightMoves(*this, pos, board); + case PieceType::BISHOP: + result = MoveGen::getBishopMoves(c, square, board); break; - case Type::BISHOP: - result = MoveGen::generateBishopMoves(*this, pos, board); + case PieceType::ROOK: + result = MoveGen::getRookMoves(c, square, board); break; - case Type::ROOK: - result = MoveGen::generateRookMoves(*this, pos, board); + case PieceType::QUEEN: + result = MoveGen::getQueenMoves(c, square, board); break; - case Type::QUEEN: - result = MoveGen::generateQueenMoves(*this, pos, board); + case PieceType::KING: + result = MoveGen::getKingMoves(c, square, board); break; - case Type::KING: - result = MoveGen::generateKingMoves(*this, pos, board); + default: break; } diff --git a/ChessAndroid/app/src/main/cpp/chess/data/Piece.h b/ChessAndroid/app/src/main/cpp/chess/data/Piece.h index 0322eea..5404705 100644 --- a/ChessAndroid/app/src/main/cpp/chess/data/Piece.h +++ b/ChessAndroid/app/src/main/cpp/chess/data/Piece.h @@ -1,36 +1,38 @@ #pragma once #include "Pos.h" -#include "../containers/StackVector.h" class Board; -enum Type : unsigned char -{ - NONE = 0, - PAWN = 1, - KNIGHT = 2, - BISHOP = 3, - ROOK = 4, - QUEEN = 5, - KING = 6 -}; - class Piece final { public: - const static Piece EMPTY; - - using MaxMovesVector = StackVector; + enum Type : byte + { + NO_PIECE, + W_PAWN = 1, W_KNIGHT, W_BISHOP, W_ROOK, W_QUEEN, W_KING, + B_PAWN = 9, B_KNIGHT, B_BISHOP, B_ROOK, B_QUEEN, B_KING, + PIECE_NB = 16 + }; - Type type; - bool isWhite; - bool moved; +private: + /* + * The first 3 bits are use to store the specific Piece Type, eg. PAWN, KNIGHT, BISHOP + * The 4-th bit is use to indicate the color of this piece + */ + Type content; +public: + const static Piece EMPTY; constexpr Piece() noexcept - : type(Type::NONE), isWhite(false), moved(false) {} - constexpr Piece(const Type type, const bool isWhite, const bool moved = false) noexcept - : type(type), isWhite(isWhite), moved(moved) {} + : content(NO_PIECE) {} + + constexpr Piece(const PieceType type, const Color color) noexcept + : content(static_cast((color << 3u) | type)) {} + + explicit constexpr Piece(const Type type) noexcept + : content(type) {} + Piece(Piece&&) = default; Piece(const Piece&) = default; ~Piece() = default; @@ -38,24 +40,47 @@ class Piece final Piece &operator=(const Piece &other) = default; Piece &operator=(Piece &&other) = default; - MaxMovesVector getPossibleMoves(const Pos &pos, const Board &board) const noexcept; - MaxMovesVector getPossibleCaptures(const Pos &pos, const Board &board) const noexcept; + constexpr bool operator==(const Piece &other) const noexcept + { + return content == other.content; + } - /* - * Checks if the type and color match - */ - constexpr bool isSameType(const Piece &other) const noexcept + constexpr bool operator==(const Type type) const noexcept + { + return content == type; + } + + constexpr Color color() const noexcept + { + return static_cast(content >> 3u); + } + + constexpr PieceType type() const noexcept { - return type == other.type && isWhite == other.isWhite; + return static_cast(content & 7u); } constexpr bool isSameColor(const Piece &other) const noexcept { - return isWhite == other.isWhite; + return color() == other.color(); + } + + constexpr Piece operator~() const noexcept + { + // Flip the 4-th bit + return Piece(static_cast(content ^ 8u)); + } + + constexpr operator byte() const noexcept + { + return static_cast(content); } constexpr operator bool() const noexcept { - return type != Type::NONE; + return content != Type::NO_PIECE; } + + U64 getPossibleMoves(byte square, const Board &board) const noexcept; + U64 getPossibleCaptures(byte square, const Board &board) const noexcept; }; diff --git a/ChessAndroid/app/src/main/cpp/chess/data/Player.cpp b/ChessAndroid/app/src/main/cpp/chess/data/Player.cpp deleted file mode 100644 index 4f6f546..0000000 --- a/ChessAndroid/app/src/main/cpp/chess/data/Player.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "Player.h" - -#include "Board.h" -#include "Piece.h" -#include "../algorithm/MoveGen.h" -#include "../BoardManager.h" - -bool Player::isAttacked(const bool whiteAttacking, const byte targetSquare, const Board &board) -{ - if (const U64 pawns = board.pawns[whiteAttacking]; - PieceAttacks::getPawnAttacks(!whiteAttacking, targetSquare) & pawns) - return true; - - if (const U64 knights = board.knights[whiteAttacking]; - PieceAttacks::getKnightAttacks(targetSquare) & knights) - return true; - - if (Bitboard::shiftedBoards[board.kingSquare[whiteAttacking]] & PieceAttacks::getKingAttacks(targetSquare)) - return true; - - const U64 occupied = board.allPieces[0] | board.allPieces[1]; - - if (const U64 bishopsQueens = board.bishops[whiteAttacking] | board.queens[whiteAttacking]; - PieceAttacks::getBishopAttacks(targetSquare, occupied) & bishopsQueens) - return true; - - if (const U64 rooksQueens = board.rooks[whiteAttacking] | board.queens[whiteAttacking]; - PieceAttacks::getRookAttacks(targetSquare, occupied) & rooksQueens) - return true; - - return false; -} - -bool Player::onlyKingsLeft(const Board &board) -{ - if (board.npm != 0) - return false; - - for (const auto &x : board.data) - for (const Piece &piece : x) - if (piece && piece.type != Type::KING) - return false; - - return true; -} - -bool Player::hasNoValidMoves(const bool isWhite, const Board &board) -{ - const auto pieces = getAllOwnedPieces(isWhite, board); - - for (const auto &pair : pieces) - { - const Pos &startPos = pair.first; - const Piece &selectedPiece = pair.second; - const auto possibleMoves = selectedPiece.getPossibleMoves(startPos, board); - - for (const Pos &destPos : possibleMoves) - { - if (board[destPos].type == Type::KING) - continue; - - Board newBoard = board; - BoardManager::movePieceInternal(startPos, destPos, newBoard, false); - - if (isInCheck(isWhite, newBoard)) - continue; - - return false; - } - } - - return true; -} - -bool Player::isInCheck(const bool isWhite, const Board &board) -{ - const U64 king = Bitboard::shiftedBoards[board.kingSquare[isWhite]]; - bool check = false; - - MoveGen::forEachAttack(!isWhite, board, [&] (const U64 attacks) -> bool { - check = static_cast(king & attacks); - return check; - }); - return check; -} - -StackVector, 16> Player::getAllOwnedPieces(const bool isWhite, const Board &board) -{ - StackVector, 16> pieces; - - for (byte x = 0; x < 8; x++) - for (byte y = 0; y < 8; y++) - if (const Piece &piece = board.getPiece(x, y); piece && piece.isWhite == isWhite) - pieces.emplace_back(Pos(x, y), piece); - - return pieces; -} diff --git a/ChessAndroid/app/src/main/cpp/chess/data/Player.h b/ChessAndroid/app/src/main/cpp/chess/data/Player.h deleted file mode 100644 index 22bd6e6..0000000 --- a/ChessAndroid/app/src/main/cpp/chess/data/Player.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once - -#include "../containers/StackVector.h" -#include "../data/Pos.h" - -class Piece; -class Board; - -class Player -{ -public: - Player() = delete; - Player(const Player&) = delete; - Player(Player&&) = delete; - - static bool isAttacked(bool whiteAttacking, byte targetSquare, const Board &board); - static bool onlyKingsLeft(const Board &board); - static bool hasNoValidMoves(bool isWhite, const Board &board); - static bool isInCheck(bool isWhite, const Board &board); - static StackVector, 16> getAllOwnedPieces(bool isWhite, const Board &board); -}; diff --git a/ChessAndroid/app/src/main/cpp/chess/data/Pos.h b/ChessAndroid/app/src/main/cpp/chess/data/Pos.h index dd12af7..0ea2713 100644 --- a/ChessAndroid/app/src/main/cpp/chess/data/Pos.h +++ b/ChessAndroid/app/src/main/cpp/chess/data/Pos.h @@ -4,8 +4,6 @@ #include "Bitboard.h" -using byte = unsigned char; - class Pos { public: @@ -16,7 +14,7 @@ class Pos : x(8), y(8) {} constexpr explicit Pos(const byte square) noexcept - : x(row(square)), y(col(square)) {} + : x(col(square)), y(row(square)) {} constexpr Pos(const byte x, const byte y) noexcept : x(x), y(y) {} @@ -28,6 +26,7 @@ class Pos { return x == other.x && y == other.y; } + constexpr bool operator!=(const Pos &other) const noexcept { return !(*this == other); @@ -40,6 +39,7 @@ class Pos return *this; } + constexpr Pos &operator-=(const Pos &other) noexcept { x -= other.x; @@ -47,19 +47,21 @@ class Pos return *this; } - constexpr Pos &operator*=(const Pos &other) noexcept + + constexpr Pos operator+(Pos other) const noexcept { - x *= other.x; - y *= other.y; + other.x += x; + other.y += y; - return *this; + return other; } - constexpr Pos &operator/=(const Pos &other) noexcept + + constexpr Pos operator-(Pos other) const noexcept { - x /= other.x; - y /= other.y; + other.x -= x; + other.y -= y; - return *this; + return other; } constexpr bool isValid() const noexcept @@ -69,7 +71,7 @@ class Pos constexpr byte toSquare() const noexcept { - return static_cast(x * 8u + y); + return ::toSquare(x, y); } constexpr U64 toBitboard() const noexcept @@ -77,5 +79,3 @@ class Pos return Bitboard::shiftedBoards[toSquare()]; } }; - -using PosPair = std::pair; diff --git a/ChessAndroid/app/src/main/cpp/chess/data/Psqt.cpp b/ChessAndroid/app/src/main/cpp/chess/data/Psqt.cpp index 80e1ead..c3bf54f 100644 --- a/ChessAndroid/app/src/main/cpp/chess/data/Psqt.cpp +++ b/ChessAndroid/app/src/main/cpp/chess/data/Psqt.cpp @@ -1,140 +1,102 @@ #include "Psqt.h" -#define S Score +#include "Piece.h" -constexpr S PAWN_SCORE(136, 208); -constexpr S KNIGHT_SCORE(782, 865); -constexpr S BISHOP_SCORE(830, 918); -constexpr S ROOK_SCORE(1289, 1378); -constexpr S QUEEN_SCORE(2529, 2687); +#define S Score -constexpr S PAWN_SQUARE[][4] = -{ - { S(0, 0), S(0, 0), S(0, 0), S(0, 0) }, - { S(-11, -3), S(7, -1), S(7, 7), S(17, 2) }, - { S(-16, -2), S(-3, 2), S(23, 6), S(23, -1) }, - { S(-14, 7), S(-7, -4), S(20, -8), S(24, 2) }, - { S(-5, 13), S(-2, 10), S(-1, -1), S(12, -8) }, - { S(-11, 16), S(-12, 6), S(-2, 1), S(4, 16) }, - { S(-2, 1), S(20, -12), S(-10, 6), S(-2, 25) }, - { S(0, 0), S(0, 0), S(0, 0), S(0, 0) } +/* + * These values were imported from the Stockfish Chess Engine + */ +constexpr static S PAWN_SCORE(128, 213); +constexpr static S KNIGHT_SCORE(781, 854); +constexpr static S BISHOP_SCORE(825, 915); +constexpr static S ROOK_SCORE(1276, 1380); +constexpr static S QUEEN_SCORE(2538, 2682); + +constexpr static S PAWN_SQUARE[][4] = { + { }, + { S(-11, -3), S( 7, -1), S( 7, 7), S(17, 2) }, + { S(-16, -2), S( -3, 2), S( 23, 6), S(23, -1) }, + { S(-14, 7), S( -7, -4), S( 20, -8), S(24, 2) }, + { S( -5, 13), S( -2, 10), S( -1, -1), S(12, -8) }, + { S(-11, 16), S(-12, 6), S( -2, 1), S( 4, 16) }, + { S( -2, 1), S( 20, -12), S(-10, 6), S(-2, 25) }, + { } }; -constexpr S KNIGHT_SQUARE[][4] = -{ - { S(-169, -105), S(-96, -74), S(-80, -46), S(-79, -18) }, - { S(-79, -70), S(-39, -56), S(-24, -15), S(-9, 6) }, - { S(-64, -38), S(-20, -33), S(4, -5), S(19, 27) }, - { S(-28, -36), S(5, 0), S(41, 13), S(47, 34) }, - { S(-29, -41), S(13, -20), S(42, 4), S(52, 35) }, - { S(-11, -51), S(28, -38), S(63, -17), S(55, 19) }, - { S(-67, -64), S(-21, -45), S(6, -37), S(37, 16) }, - { S(-200, -98), S(-80, -89), S(-53, -53), S(-32, -16) } + +constexpr static S KNIGHT_SQUARE[][4] = { + { S(-175, -96), S(-92, -65), S(-74, -49), S(-73, -21) }, + { S( -77, -67), S(-41, -54), S(-27, -18), S(-15, 8) }, + { S( -61, -40), S(-17, -27), S( 6, -8), S( 12, 29) }, + { S( -35, -35), S( 8, -2), S( 40, 13), S( 49, 28) }, + { S( -34, -45), S( 13, -16), S( 44, 9), S( 51, 39) }, + { S( -9, -51), S( 22, -44), S( 58, -16), S( 53, 17) }, + { S( -67, -69), S(-27, -50), S( 4, -51), S( 37, 12) }, + { S(-201, -100), S(-83, -88), S(-56, -56), S(-26, -17) } }; -constexpr S BISHOP_SQUARE[][4] = -{ - { S(-44, -63), S(-4, -30), S(-11, -35), S(-28, -8) }, - { S(-18, -38), S(7, -13), S(14, -14), S(3, 0) }, - { S(-8, -18), S(24, 0), S(-3, -7), S(15, 13) }, - { S(1, -26), S(8, -3), S(26, 1), S(37, 16) }, - { S(-7, -24), S(30, -6), S(23, -10), S(28, 17) }, - { S(-17, -26), S(4, 2), S(-1, 1), S(8, 16) }, - { S(-21, -34), S(-19, -18), S(10, -7), S(-6, 9) }, - { S(-48, -51), S(-3, -40), S(-12, -39), S(-25, -20) } + +constexpr static S BISHOP_SQUARE[][4] = { + { S(-53, -57), S( -5, -30), S( -8, -37), S(-23, -12) }, + { S(-15, -37), S( 8, -13), S( 19, -17), S( 4, 1) }, + { S( -7, -16), S( 21, -1), S( -5, -2), S( 17, 10) }, + { S( -5, -20), S( 11, -6), S( 25, 0), S( 39, 17) }, + { S(-12, -17), S( 29, -1), S( 22, -14), S( 31, 15) }, + { S(-16, -30), S( 6, 6), S( 1, 4), S( 11, 6) }, + { S(-17, -31), S(-14, -20), S( 5, -1), S( 0, 1) }, + { S(-48, -46), S( 1, -42), S(-14, -37), S(-23, -24) } }; -constexpr S ROOK_SQUARE[][4] = -{ - { S(-24, -2), S(-13, -6), S(-7, -3), S(2,-2) }, - { S(-18, -10), S(-10, -7), S(-5, 1), S(9, 0) }, - { S(-21, 10), S(-7, -4), S(3, 2), S(-1,-2) }, - { S(-13, -5), S(-5, 2), S(-4, -8), S(-6, 8) }, - { S(-24, -8), S(-12, 5), S(-1, 4), S(6, -9) }, - { S(-24, 3), S(-4, -2), S(4, -10), S(10, 7) }, - { S(-8, 1), S(6, 2), S(10, 17), S(12,-8) }, - { S(-22, 12), S(-24, -6), S(-6, 13), S(4, 7) } + +constexpr static S ROOK_SQUARE[][4] = { + { S(-31, -9), S(-20, -13), S(-14, -10), S(-5, -9) }, + { S(-21, -12), S(-13, -9), S( -8, -1), S( 6, -2) }, + { S(-25, 6), S(-11, -8), S( -1, -2), S( 3, -6) }, + { S(-13, -6), S( -5, 1), S( -4, -9), S(-6, 7) }, + { S(-27, -5), S(-15, 8), S( -4, 7), S( 3, -6) }, + { S(-22, 6), S( -2, 1), S( 6, -7), S(12, 10) }, + { S( -2, 4), S( 12, 5), S( 16, 20), S(18, -5) }, + { S(-17, 18), S(-19, 0), S( -1, 19), S( 9, 13) } }; -constexpr S QUEEN_SQUARE[][4] = -{ - { S(3, -69), S(-5, -57), S(-5, -47), S(4, -26) }, - { S(-3, -55), S(5, -31), S(8, -22), S(12, -4) }, - { S(-3, -39), S(6, -18), S(13, -9), S(7, 3) }, - { S(4, -23), S(5, -3), S(9, 13), S(8, 24) }, - { S(0, -29), S(14, -6), S(12, 9), S(5, 21) }, - { S(-4, -38), S(10, -18), S(6, -12), S(8, 1) }, - { S(-5, -50), S(6, -27), S(10, -24), S(8, -8) }, - { S(-2, -75), S(-2, -52), S(1, -43), S(-2, -36) } + +constexpr static S QUEEN_SQUARE[][4] = { + { S( 3, -69), S(-5, -57), S(-5, -47), S( 4, -26) }, + { S(-3, -55), S( 5, -31), S( 8, -22), S(12, -4) }, + { S(-3, -39), S( 6, -18), S(13, -9), S( 7, 3) }, + { S( 4, -23), S( 5, -3), S( 9, 13), S( 8, 24) }, + { S( 0, -29), S(14, -6), S(12, 9), S( 5, 21) }, + { S(-4, -38), S(10, -18), S( 6, -12), S( 8, 1) }, + { S(-5, -50), S( 6, -27), S(10, -24), S( 8, -8) }, + { S(-2, -75), S(-2, -52), S( 1, -43), S(-2, -36) } }; -constexpr S KING_SQUARE[][4] = -{ - { S(272, 0), S(325, 41), S(273, 80), S(190, 93) }, - { S(277, 57), S(305, 98), S(241, 138), S(183, 131) }, - { S(198, 86), S(253, 138), S(168, 165), S(120, 173) }, - { S(169, 103), S(191, 152), S(136, 168), S(108, 169) }, - { S(145, 98), S(176, 166), S(112, 197), S(69, 194) }, - { S(122, 87), S(159, 164), S(85, 174), S(36, 189) }, - { S(87, 40), S(120, 99), S(64, 128), S(25, 141) }, - { S(64, 5), S(87, 60), S(49, 75), S(0, 75) } + +constexpr static S KING_SQUARE[][4] = { + { S(271, 1), S(327, 45), S(270, 85), S(192, 76) }, + { S(278, 53), S(303, 100), S(230, 133), S(174, 135) }, + { S(195, 88), S(258, 130), S(169, 169), S(120, 175) }, + { S(164, 103), S(190, 156), S(138, 172), S( 98, 172) }, + { S(154, 96), S(179, 166), S(105, 199), S( 70, 199) }, + { S(123, 92), S(145, 172), S( 81, 184), S( 31, 191) }, + { S( 88, 47), S(120, 121), S( 65, 116), S( 33, 131) }, + { S( 59, 11), S( 89, 59), S( 45, 73), S( -1, 78) } }; #undef S -using byte = unsigned char; - -const Psqt::ScoreArray Psqt::s_PawnSquares = [] { - ScoreArray array{}; - - for (byte x = 0; x < 8; x++) - for (byte y = 0; y < 8; y++) - array[x][y] = PAWN_SCORE + PAWN_SQUARE[7u - x][std::min(y, 7u - y)]; - - return array; -}(); - -const Psqt::ScoreArray Psqt::s_KnightSquares = [] { - ScoreArray array{}; - - for (byte x = 0; x < 8; x++) - for (byte y = 0; y < 8; y++) - array[x][y] = KNIGHT_SCORE + KNIGHT_SQUARE[7u - x][std::min(y, 7u - y)]; - - return array; -}(); - -const Psqt::ScoreArray Psqt::s_BishopSquares = [] { - ScoreArray array{}; - - for (byte x = 0; x < 8; x++) - for (byte y = 0; y < 8; y++) - array[x][y] = BISHOP_SCORE + BISHOP_SQUARE[7u - x][std::min(y, 7u - y)]; - - return array; -}(); - -const Psqt::ScoreArray Psqt::s_RookSquares = [] { - ScoreArray array{}; - - for (byte x = 0; x < 8; x++) - for (byte y = 0; y < 8; y++) - array[x][y] = ROOK_SCORE + ROOK_SQUARE[7u - x][std::min(y, 7u - y)]; - - return array; -}(); - -const Psqt::ScoreArray Psqt::s_QueenSquares = [] { - ScoreArray array{}; - - for (byte x = 0; x < 8; x++) - for (byte y = 0; y < 8; y++) - array[x][y] = QUEEN_SCORE + QUEEN_SQUARE[7u - x][std::min(y, 7u - y)]; - - return array; -}(); +const Psqt::ScoreArray Psqt::s_Bonus = [] { + ScoreArray bonuses{}; -const Psqt::ScoreArray Psqt::s_KingSquares = [] { - ScoreArray array{}; + for (byte i = 0; i < 64; ++i) + { + const byte x = col(i); + const byte y = row(i); + const byte queen_side_y = std::min(y, 7u - y); - for (byte x = 0; x < 8; x++) - for (byte y = 0; y < 8; y++) - array[x][y] = KING_SQUARE[7u - x][std::min(y, 7u - y)]; + bonuses[PAWN][i] = PAWN_SCORE + PAWN_SQUARE[x][queen_side_y]; + bonuses[KNIGHT][i] = KNIGHT_SCORE + KNIGHT_SQUARE[x][queen_side_y]; + bonuses[BISHOP][i] = BISHOP_SCORE + BISHOP_SQUARE[x][queen_side_y]; + bonuses[ROOK][i] = ROOK_SCORE + ROOK_SQUARE[x][queen_side_y]; + bonuses[QUEEN][i] = QUEEN_SCORE + QUEEN_SQUARE[x][queen_side_y]; + bonuses[KING][i] = KING_SQUARE[x][queen_side_y]; + } - return array; + return bonuses; }(); diff --git a/ChessAndroid/app/src/main/cpp/chess/data/Psqt.h b/ChessAndroid/app/src/main/cpp/chess/data/Psqt.h index 3585480..4962a00 100644 --- a/ChessAndroid/app/src/main/cpp/chess/data/Psqt.h +++ b/ChessAndroid/app/src/main/cpp/chess/data/Psqt.h @@ -6,17 +6,12 @@ class Psqt { - using ScoreArray = std::array, 8>; + using ScoreArray = std::array, 7>; public: Psqt() = delete; Psqt(const Psqt&) = delete; Psqt(Psqt&&) = delete; - const static ScoreArray s_PawnSquares; - const static ScoreArray s_KnightSquares; - const static ScoreArray s_BishopSquares; - const static ScoreArray s_RookSquares; - const static ScoreArray s_QueenSquares; - const static ScoreArray s_KingSquares; + const static ScoreArray s_Bonus; }; diff --git a/ChessAndroid/app/src/main/cpp/chess/data/Rays.h b/ChessAndroid/app/src/main/cpp/chess/data/Rays.h deleted file mode 100644 index 1337e9d..0000000 --- a/ChessAndroid/app/src/main/cpp/chess/data/Rays.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -#include "Bitboard.h" - -namespace Rays -{ - - enum Dir - { - NORTH, - SOUTH, - EAST, - WEST, - NORTH_EAST, - NORTH_WEST, - SOUTH_EAST, - SOUTH_WEST - }; - - /** - * Table of precalculated ray bitboards indexed by direction and square - */ - constexpr static std::array, 8> rays = [] { - std::array, 8> rays{}; - - using namespace Bitboard; - - for (byte square = 0u; square < 64u; square++) - { - rays[NORTH][square] = 0x0101010101010100ULL << square; - - rays[SOUTH][square] = 0x0080808080808080ULL >> (63u - square); - - rays[EAST][square] = 2u * (shiftedBoards[square | 7u] - shiftedBoards[square]); - - rays[WEST][square] = shiftedBoards[square] - shiftedBoards[square & 56u]; - - rays[NORTH_WEST][square] = westN(0x102040810204000ULL, 7u - col(square)) << (row(square) * 8u); - - rays[NORTH_EAST][square] = eastN(0x8040201008040200ULL, col(square)) << (row(square) * 8u); - - rays[SOUTH_WEST][square] = westN(0x40201008040201ULL, 7u - col(square)) >> ((7u - row(square)) * 8u); - - rays[SOUTH_EAST][square] = eastN(0x2040810204080ULL, col(square)) >> ((7u - row(square)) * 8u); - } - - return rays; - }(); - - /** - * Returns a bitboard containing the given ray in the given direction. - * - * @param direction Direction of ray to return - * @param square Square to get ray starting from (in little endian rank file mapping form) - * @return A bitboard containing the given ray in the given direction - */ - constexpr U64 getRay(const Dir direction, const byte square) noexcept - { - return rays[direction][square]; - } -}; diff --git a/ChessAndroid/app/src/main/cpp/chess/data/Score.h b/ChessAndroid/app/src/main/cpp/chess/data/Score.h index 61298d8..a1e56a4 100644 --- a/ChessAndroid/app/src/main/cpp/chess/data/Score.h +++ b/ChessAndroid/app/src/main/cpp/chess/data/Score.h @@ -3,7 +3,7 @@ class Score final { public: - short mg = 0, eg = 0; + short mg{}, eg{}; Score() = default; diff --git a/ChessAndroid/app/src/main/cpp/chess/persistence/FenParser.cpp b/ChessAndroid/app/src/main/cpp/chess/persistence/FenParser.cpp new file mode 100644 index 0000000..0d10f13 --- /dev/null +++ b/ChessAndroid/app/src/main/cpp/chess/persistence/FenParser.cpp @@ -0,0 +1,129 @@ +#include "FenParser.h" + +#include "../data/Board.h" +#include "../algorithm/Hash.h" + +FenParser::FenParser(Board &board) + : board(board) +{ +} + +void FenParser::parseFen(const std::string &fen) +{ + std::istringstream fenStream(fen); + + // Clean board + board.data.fill({}); + board.pieces.fill({}); + board.occupied = 0ull; + board.npm = 0; + + parsePieces(fenStream); + + // Next to move + std::string token; + fenStream >> token; + board.colorToMove = token == "w" ? WHITE : BLACK; + + // Castling availability + fenStream >> token; + board.castlingRights = CastlingRights::CASTLE_NONE; + for (auto &currChar : token) { + switch (currChar) { + case 'K': board.castlingRights |= CastlingRights::CASTLE_WHITE_KING; + break; + case 'Q': board.castlingRights |= CastlingRights::CASTLE_WHITE_QUEEN; + break; + case 'k': board.castlingRights |= CastlingRights::CASTLE_BLACK_KING; + break; + case 'q': board.castlingRights |= CastlingRights::CASTLE_BLACK_QUEEN; + break; + default: + break; + } + } + + // TODO: En passant target square + fenStream >> token; + board.enPassantSq = 64u; + //board.enPassant = token == "-" ? 0ull : 1 << x; + + + // Halfmove Clock + int halfMove; + fenStream >> halfMove; + board.halfMoveClock = static_cast(halfMove); + + board.updateNonPieceBitboards(); + board.zKey = Hash::compute(board); +} + +std::string FenParser::exportToFen() +{ + return std::string(); +} + +void FenParser::parsePieces(std::istringstream &stream) +{ + std::string token; + + U64 boardPos = 56ull; // Fen string starts at a8 = index 56 + stream >> token; + for (auto currChar : token) + { + switch (currChar) + { + case 'p': + board.data[boardPos++] = Piece(PAWN, BLACK); + break; + case 'r': + board.data[boardPos++] = Piece(ROOK, BLACK); + break; + case 'n': + board.data[boardPos++] = Piece(KNIGHT, BLACK); + break; + case 'b': + board.data[boardPos++] = Piece(BISHOP, BLACK); + break; + case 'q': + board.data[boardPos++] = Piece(QUEEN, BLACK); + break; + case 'k': + board.data[boardPos++] = Piece(KING, BLACK); + break; + case 'P': + board.data[boardPos++] = Piece(PAWN, WHITE); + break; + case 'R': + board.data[boardPos++] = Piece(ROOK, WHITE); + break; + case 'N': + board.data[boardPos++] = Piece(KNIGHT, WHITE); + break; + case 'B': + board.data[boardPos++] = Piece(BISHOP, WHITE); + break; + case 'Q': + board.data[boardPos++] = Piece(QUEEN, WHITE); + break; + case 'K': + board.data[boardPos++] = Piece(KING, WHITE); + break; + case '/': boardPos -= 16u; // Go down one rank + break; + default: + boardPos += static_cast(currChar - '0'); + } + } + + for (byte i = 0u; i < 64u; ++i) + { + const Piece &piece = board.data[i]; + const U64 bb = Bitboard::shiftedBoards[i]; + + board.getType(piece) |= bb; + + if (piece.type() != PAWN) + board.npm += Evaluation::getPieceValue(piece.type()); + } +} diff --git a/ChessAndroid/app/src/main/cpp/chess/persistence/FenParser.h b/ChessAndroid/app/src/main/cpp/chess/persistence/FenParser.h new file mode 100644 index 0000000..4ed5957 --- /dev/null +++ b/ChessAndroid/app/src/main/cpp/chess/persistence/FenParser.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +class Board; + +class FenParser +{ + Board &board; + +public: + FenParser(Board &board); + + void parseFen(const std::string &fen); + std::string exportToFen(); + +private: + void parsePieces(std::istringstream &stream); +}; diff --git a/ChessAndroid/app/src/main/cpp/chess/persistence/MovesPersistence.cpp b/ChessAndroid/app/src/main/cpp/chess/persistence/MovesPersistence.cpp index 3b0b54f..91a74c6 100644 --- a/ChessAndroid/app/src/main/cpp/chess/persistence/MovesPersistence.cpp +++ b/ChessAndroid/app/src/main/cpp/chess/persistence/MovesPersistence.cpp @@ -1,7 +1,6 @@ #include "MovesPersistence.h" -#include -#include +#include #include "../data/Board.h" @@ -17,9 +16,9 @@ bool MovesPersistence::isPlayerWhite() const return m_Content[0] != 'B'; } -std::vector MovesPersistence::getMoves() const +std::vector> MovesPersistence::getMoves() const { - std::vector moves; + std::vector> moves; moves.reserve(20); size_t prefix = 1; @@ -44,20 +43,20 @@ std::string MovesPersistence::saveToString(const std::vector &movesHis stream << (isPlayerWhite ? 'W' : 'B'); for (const RootMove &moves : movesHistory) - savePosPair(stream, std::make_pair(moves.start, moves.dest)); + savePosPair(stream, std::make_pair(moves.startSq, moves.destSq)); return stream.str(); } -Pos MovesPersistence::getPos(const std::string_view str) +byte MovesPersistence::getSquare(std::string_view str) { - const byte x = static_cast(str[0] - 48); - const byte y = static_cast(str[2] - 48); + int value = -1; + std::from_chars(str.data(), str.data() + str.size(), value); - return Pos(x, y); + return static_cast(value); } -void MovesPersistence::parsePosPair(std::vector &moves, std::string_view str) +void MovesPersistence::parsePosPair(std::vector> &moves, std::string_view str) { const auto selectedEnd = str.find(';'); auto destEnd = str.find(')', selectedEnd + 1); @@ -65,16 +64,18 @@ void MovesPersistence::parsePosPair(std::vector &moves, std::string_vie if (destEnd == std::string_view::npos) destEnd = str.size() - 1; - moves.emplace_back(getPos(str.substr(0, selectedEnd)), getPos(str.substr(selectedEnd + 1, destEnd))); + moves.emplace_back(getSquare(str.substr(0, selectedEnd)), + getSquare(str.substr(selectedEnd + 1, destEnd))); } -void MovesPersistence::savePosPair(std::ostringstream &stream, const PosPair &pair) +void MovesPersistence::savePosPair(std::ostringstream &stream, const std::pair &pair) { - if (pair.first.isValid() && pair.second.isValid()) + if (pair.first < 64 && pair.second < 64) { stream << '(' - << static_cast(pair.first.x) << ',' << static_cast(pair.first.y) << ';' - << static_cast(pair.second.x) << ',' << static_cast(pair.second.y) + << static_cast(pair.first) + << ';' + << static_cast(pair.second) << ')'; } } diff --git a/ChessAndroid/app/src/main/cpp/chess/persistence/MovesPersistence.h b/ChessAndroid/app/src/main/cpp/chess/persistence/MovesPersistence.h index dfe0744..d2f8e99 100644 --- a/ChessAndroid/app/src/main/cpp/chess/persistence/MovesPersistence.h +++ b/ChessAndroid/app/src/main/cpp/chess/persistence/MovesPersistence.h @@ -18,12 +18,12 @@ class MovesPersistence final MovesPersistence(std::string content); bool isPlayerWhite() const; - std::vector getMoves() const; + std::vector> getMoves() const; static std::string saveToString(const std::vector &movesHistory, bool isPlayerWhite); private: - static Pos getPos(std::string_view str); - static void parsePosPair(std::vector &moves, std::string_view str); - static void savePosPair(std::ostringstream &stream, const PosPair &pair); + static byte getSquare(std::string_view str); + static void parsePosPair(std::vector> &moves, std::string_view str); + static void savePosPair(std::ostringstream &stream, const std::pair &pair); }; diff --git a/ChessAndroid/app/src/main/cpp/chess/threads/NegaMaxThreadPool.h b/ChessAndroid/app/src/main/cpp/chess/threads/NegaMaxThreadPool.h deleted file mode 100644 index c64012b..0000000 --- a/ChessAndroid/app/src/main/cpp/chess/threads/NegaMaxThreadPool.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "ThreadPool.hpp" - -class NegaMaxThreadPool final -{ - inline static ThreadPool pool{ 1 }; - -public: - NegaMaxThreadPool() = delete; - - static void updateThreadCount(const unsigned int threadCount) - { - if (pool.threadCount() != threadCount) - { - try - { - pool.updateThreadCount(threadCount); - } catch (...) - { - } - } - } - - template - static ThreadPool::TaskFuture submitJob(Func &&func, Args &&... args) - { - return pool.submit(std::forward(func), std::forward(args)...); - } -}; diff --git a/ChessAndroid/app/src/main/cpp/chess/threads/ThreadPool.h b/ChessAndroid/app/src/main/cpp/chess/threads/ThreadPool.h new file mode 100644 index 0000000..ee843bd --- /dev/null +++ b/ChessAndroid/app/src/main/cpp/chess/threads/ThreadPool.h @@ -0,0 +1,140 @@ +#pragma once + +#include "ThreadSafeQueue.h" + +#include +#include +#include +#include +#include +#include + +class ThreadPool +{ +public: + + explicit ThreadPool(const std::size_t numThreads = std::thread::hardware_concurrency()) + { + m_Threads.reserve(numThreads); + try + { + for (auto i = 0u; i < numThreads; ++i) + m_Threads.emplace_back(&ThreadPool::worker, this); + } + catch (...) + { + destroy(); + throw; + } + } + + ThreadPool(const ThreadPool&) = delete; + + ThreadPool& operator=(const ThreadPool&) = delete; + + ~ThreadPool() + { + destroy(); + } + + std::size_t getThreadCount() const noexcept + { + return m_Threads.size(); + } + + void updateThreadCount(const std::size_t numThreads) noexcept(false) + { + const std::size_t currentThreadCount = getThreadCount(); + + if (numThreads < currentThreadCount) + { + // Destroy all the threads and invalidate the queue + { + m_Done = true; + m_Queue.invalidate(); + for (auto &thread : m_Threads) + if (thread.joinable()) + thread.join(); + + m_Threads.clear(); + } + + { + m_Threads.reserve(numThreads); + m_Done = false; + + m_Queue.~ThreadSafeQueue(); + new(&m_Queue) QueueType(); + + for (auto i = 0u; i < numThreads; ++i) + m_Threads.emplace_back(&ThreadPool::worker, this); + } + } else if (numThreads > currentThreadCount) { + // Add the extra number of threads necessary + m_Threads.reserve(numThreads); + + for (auto i = currentThreadCount; i < numThreads; ++i) + m_Threads.emplace_back(&ThreadPool::worker, this); + } + + + // Otherwise the size should be the same + } + + template + void submitWork(Func &&func, Args &&...args) + { + auto work = [func, args...] { func(args...); }; + m_Queue.push(work); + } + + template + auto submitTask(Func &&func, Args &&...args) -> std::future> + { + using ReturnType = std::invoke_result_t; + auto task = std::make_shared>( + std::bind(std::forward(func), + std::forward(args)...) + ); + std::future result = task->get_future(); + + auto work = [task] { (*task)(); }; + m_Queue.push(work); + + return result; + } + +private: + /** + * Constantly running function each thread uses to acquire work items from the queue. + */ + void worker() + { + while (!m_Done) + { + Proc func; + if (m_Queue.waitPop(func)) + func(); + } + } + + /** + * Invalidates the queue and joins all running threads. + */ + void destroy() + { + m_Done = true; + m_Queue.invalidate(); + for (auto &thread : m_Threads) + if (thread.joinable()) + thread.join(); + } + +private: + using Proc = std::function; + using QueueType = ThreadSafeQueue; + + std::atomic_bool m_Done = false; + QueueType m_Queue; + std::vector m_Threads; +}; diff --git a/ChessAndroid/app/src/main/cpp/chess/threads/ThreadPool.hpp b/ChessAndroid/app/src/main/cpp/chess/threads/ThreadPool.hpp deleted file mode 100644 index 6e1c665..0000000 --- a/ChessAndroid/app/src/main/cpp/chess/threads/ThreadPool.hpp +++ /dev/null @@ -1,233 +0,0 @@ -#pragma once - -#include "ThreadSafeQueue.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -class ThreadPool -{ -private: - class IThreadTask - { - public: - IThreadTask() = default; - virtual ~IThreadTask() = default; - IThreadTask(const IThreadTask&) = delete; - IThreadTask &operator=(const IThreadTask&) = delete; - IThreadTask(IThreadTask&&) noexcept = default; - IThreadTask &operator=(IThreadTask&&) noexcept = default; - - /** - * Run the task. - */ - virtual void execute() = 0; - }; - - template - class ThreadTask : public IThreadTask - { - public: - explicit ThreadTask(Func &&func) - : m_func{ std::move(func) } {} - - ~ThreadTask() override = default; - ThreadTask(const ThreadTask &rhs) = delete; - ThreadTask &operator=(const ThreadTask &rhs) = delete; - ThreadTask(ThreadTask&&) noexcept = default; - ThreadTask &operator=(ThreadTask&&) noexcept = default; - - /** - * Run the task. - */ - void execute() override - { - m_func(); - } - - private: - Func m_func; - }; - -public: - /** - * A wrapper around a std::future that adds the behavior of futures returned from std::async. - * Specifically, this object will block and wait for execution to finish before going out of scope. - */ - template - class TaskFuture - { - public: - explicit TaskFuture(std::future &&future) - : m_future{ std::move(future) } {} - - TaskFuture(const TaskFuture &rhs) = delete; - TaskFuture &operator=(const TaskFuture &rhs) = delete; - TaskFuture(TaskFuture &&other) = default; - TaskFuture &operator=(TaskFuture &&other) = default; - ~TaskFuture() - { - if (m_future.valid()) - m_future.get(); - } - - auto get() - { - return m_future.get(); - } - - bool ready(const std::size_t millis) const - { - return m_future.wait_for(std::chrono::milliseconds(millis)) == std::future_status::ready; - } - - - private: - std::future m_future; - }; - -public: - /** - * Constructor. - */ - ThreadPool() - : ThreadPool { std::max(std::thread::hardware_concurrency() - 1u, 2u) } - { - /* - * Always create at least two threads. - */ - } - - /** - * Constructor. - */ - explicit ThreadPool(const std::size_t numThreads) - : m_done{ false } - { - m_threads.reserve(numThreads); - try - { - for (auto i = 0u; i < numThreads; ++i) - m_threads.emplace_back(&ThreadPool::worker, this); - } - catch (...) - { - destroy(); - throw; - } - } - - /** - * Non-copyable. - */ - ThreadPool(const ThreadPool&) = delete; - - /** - * Non-assignable. - */ - ThreadPool& operator=(const ThreadPool&) = delete; - - /** - * Destructor. - */ - ~ThreadPool() - { - destroy(); - } - - std::size_t threadCount() const noexcept - { - return m_threads.size(); - } - - void updateThreadCount(const std::size_t numThreads) noexcept(false) - { - const std::size_t currentThreadCount = threadCount(); - - if (numThreads < currentThreadCount) - { - m_done = true; - for (auto &thread : m_threads) - if (thread.joinable()) - thread.join(); - - m_threads.reserve(numThreads); - m_threads.clear(); - - try - { - for (auto i = 0u; i < numThreads; ++i) - m_threads.emplace_back(&ThreadPool::worker, this); - } - catch (...) - { - destroy(); - throw; - } - - m_done = false; - } else if (numThreads > currentThreadCount) { - m_threads.reserve(numThreads); - - for (auto i = currentThreadCount; i < numThreads; ++i) - m_threads.emplace_back(&ThreadPool::worker, this); - } - - // Else the size should be the same - } - - /** - * Submit a job to be run by the thread pool. - */ - template - TaskFuture submit(Func &&func, Args &&...args) - { - auto boundTask = std::bind(std::forward(func), std::forward(args)...); - using PackagedTask = std::packaged_task; - using TaskType = ThreadTask; - - PackagedTask task{ std::move(boundTask) }; - TaskFuture result{ task.get_future() }; - m_workQueue.push(std::make_unique(std::move(task))); - return result; - } - -private: - /** - * Constantly running function each thread uses to acquire work items from the queue. - */ - void worker() - { - while (!m_done) - { - std::unique_ptr pTask{ nullptr }; - if (m_workQueue.waitPop(pTask)) - pTask->execute(); - } - } - - /** - * Invalidates the queue and joins all running threads. - */ - void destroy() - { - m_done = true; - m_workQueue.invalidate(); - for (auto &thread : m_threads) - if (thread.joinable()) - thread.join(); - } - -private: - std::atomic_bool m_done; - ThreadSafeQueue> m_workQueue; - std::vector m_threads; -}; diff --git a/ChessAndroid/app/src/main/cpp/chess/threads/ThreadSafeQueue.h b/ChessAndroid/app/src/main/cpp/chess/threads/ThreadSafeQueue.h new file mode 100644 index 0000000..ecd011c --- /dev/null +++ b/ChessAndroid/app/src/main/cpp/chess/threads/ThreadSafeQueue.h @@ -0,0 +1,112 @@ +#pragma once + +#include +#include +#include +#include +#include + +template +class ThreadSafeQueue +{ +public: + + ~ThreadSafeQueue() + { + invalidate(); + } + + /** + * Attempt to get the first value in the queue. + * Returns true if a value was successfully written to the out parameter, false otherwise. + */ + bool tryPop(T &out) + { + std::lock_guard lock(m_Mutex); + if (m_Queue.empty() || !m_Valid) + { + return false; + } + out = std::move(m_Queue.front()); + m_Queue.pop(); + return true; + } + + /** + * Get the first value in the queue. + * Will block until a value is available unless clear is called or the instance is destructed. + * Returns true if a value was successfully written to the out parameter, false otherwise. + */ + bool waitPop(T &out) + { + std::unique_lock lock(m_Mutex); + m_Condition.wait(lock, [this]() + { + return !m_Queue.empty() || !m_Valid; + }); + /* + * Using the condition in the predicate ensures that spurious wakeups with a valid + * but empty queue will not proceed, so only need to check for validity before proceeding. + */ + if (!m_Valid) + return false; + + out = std::move(m_Queue.front()); + m_Queue.pop(); + return true; + } + + void push(const T &value) + { + std::lock_guard lock(m_Mutex); + m_Queue.push(value); + m_Condition.notify_one(); + } + + void push(T &&value) + { + std::lock_guard lock(m_Mutex); + m_Queue.push(std::move(value)); + m_Condition.notify_one(); + } + + bool empty() const + { + std::lock_guard lock(m_Mutex); + return m_Queue.empty(); + } + + void clear() + { + std::lock_guard lock(m_Mutex); + while (!m_Queue.empty()) + { + m_Queue.pop(); + } + m_Condition.notify_all(); + } + + /** + * Invalidate the queue. + * Used to ensure no conditions are being waited on in waitPop when + * a thread or the application is trying to exit. + */ + void invalidate() + { + std::lock_guard lock(m_Mutex); + m_Valid = false; + m_Condition.notify_all(); + } + + bool isValid() const + { + std::lock_guard lock(m_Mutex); + return m_Valid; + } + +private: + std::atomic_bool m_Valid = true; + mutable std::mutex m_Mutex; + std::queue m_Queue; + std::condition_variable m_Condition; +}; diff --git a/ChessAndroid/app/src/main/cpp/chess/threads/ThreadSafeQueue.hpp b/ChessAndroid/app/src/main/cpp/chess/threads/ThreadSafeQueue.hpp deleted file mode 100644 index 5ee1204..0000000 --- a/ChessAndroid/app/src/main/cpp/chess/threads/ThreadSafeQueue.hpp +++ /dev/null @@ -1,122 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -template -class ThreadSafeQueue -{ -public: - /** - * Destructor. - */ - ~ThreadSafeQueue() - { - invalidate(); - } - - /** - * Attempt to get the first value in the queue. - * Returns true if a value was successfully written to the out parameter, false otherwise. - */ - bool tryPop(T& out) - { - std::lock_guard lock{ m_mutex }; - if (m_queue.empty() || !m_valid) - { - return false; - } - out = std::move(m_queue.front()); - m_queue.pop(); - return true; - } - - /** - * Get the first value in the queue. - * Will block until a value is available unless clear is called or the instance is destructed. - * Returns true if a value was successfully written to the out parameter, false otherwise. - */ - bool waitPop(T& out) - { - std::unique_lock lock{ m_mutex }; - m_condition.wait(lock, [this]() - { - return !m_queue.empty() || !m_valid; - }); - /* - * Using the condition in the predicate ensures that spurious wakeups with a valid - * but empty queue will not proceed, so only need to check for validity before proceeding. - */ - if (!m_valid) - { - return false; - } - out = std::move(m_queue.front()); - m_queue.pop(); - return true; - } - - /** - * Push a new value onto the queue. - */ - void push(T value) - { - std::lock_guard lock{ m_mutex }; - m_queue.push(std::move(value)); - m_condition.notify_one(); - } - - /** - * Check whether or not the queue is empty. - */ - bool empty() const - { - std::lock_guard lock{ m_mutex }; - return m_queue.empty(); - } - - /** - * Clear all items from the queue. - */ - void clear() - { - std::lock_guard lock{ m_mutex }; - while (!m_queue.empty()) - { - m_queue.pop(); - } - m_condition.notify_all(); - } - - /** - * Invalidate the queue. - * Used to ensure no conditions are being waited on in waitPop when - * a thread or the application is trying to exit. - * The queue is invalid after calling this method and it is an error - * to continue using a queue after this method has been called. - */ - void invalidate() - { - std::lock_guard lock{ m_mutex }; - m_valid = false; - m_condition.notify_all(); - } - - /** - * Returns whether or not this queue is valid. - */ - bool isValid() const - { - std::lock_guard lock{ m_mutex }; - return m_valid; - } - -private: - std::atomic_bool m_valid{ true }; - mutable std::mutex m_mutex; - std::queue m_queue; - std::condition_variable m_condition; -}; diff --git a/ChessAndroid/app/src/main/cpp/external.h b/ChessAndroid/app/src/main/cpp/external.h index 48bcc40..b54ed7f 100644 --- a/ChessAndroid/app/src/main/cpp/external.h +++ b/ChessAndroid/app/src/main/cpp/external.h @@ -2,9 +2,7 @@ #define external extern "C" -using byte = unsigned char; - -byte getByte(JNIEnv *env, jclass type, jobject obj, const char *name) +int getInt(JNIEnv *env, jclass type, jobject obj, const char *name) { - return static_cast(env->GetByteField(obj, env->GetFieldID(type, name, "B"))); + return static_cast(env->GetIntField(obj, env->GetFieldID(type, name, "I"))); } diff --git a/ChessAndroid/app/src/main/ic_launcher-web.png b/ChessAndroid/app/src/main/ic_launcher-web.png new file mode 100644 index 0000000..552a89f Binary files /dev/null and b/ChessAndroid/app/src/main/ic_launcher-web.png differ diff --git a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/ChessActivity.kt b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/ChessActivity.kt index e34741b..c8d2014 100644 --- a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/ChessActivity.kt +++ b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/ChessActivity.kt @@ -4,13 +4,16 @@ import android.content.Intent import android.graphics.Point import android.os.Bundle import android.view.View +import android.view.animation.AccelerateDecelerateInterpolator import android.widget.FrameLayout import android.widget.RelativeLayout import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import kotlinx.android.synthetic.main.activity_chess.* import kotlinx.android.synthetic.main.dialog_restart.view.* +import net.theluckycoder.chess.utils.AppPreferences import net.theluckycoder.chess.utils.CapturedPieces +import net.theluckycoder.chess.utils.PieceResourceManager import net.theluckycoder.chess.views.CustomView import net.theluckycoder.chess.views.PieceView import net.theluckycoder.chess.views.TileView @@ -24,7 +27,7 @@ class ChessActivity : AppCompatActivity(), CustomView.ClickListener, GameManager private val capturedPieces = CapturedPieces() private var viewSize = 0 - val preferences = Preferences(this) + val preferences = AppPreferences(this) val pieces = HashMap(32) private var selectedPos = Pos() @@ -51,13 +54,21 @@ class ChessActivity : AppCompatActivity(), CustomView.ClickListener, GameManager startActivity(Intent(this, SettingsActivity::class.java)) } + iv_settings.setOnLongClickListener { + val isPlayerWhite = Native.isPlayerWhite() + redrawBoard(isPlayerWhite) + redrawPieces(Native.getPieces().toList(), isPlayerWhite) + + true + } + btn_restart_game.setOnClickListener { val view = View.inflate(this, R.layout.dialog_restart, null) AlertDialog.Builder(this) - .setTitle(R.string.restart_game) + .setTitle(R.string.new_game) .setView(view) - .setPositiveButton(R.string.action_restart) { _, _ -> + .setPositiveButton(R.string.action_start) { _, _ -> val playerWhite = when (view.sp_side.selectedItemPosition) { 0 -> true 1 -> false @@ -74,7 +85,7 @@ class ChessActivity : AppCompatActivity(), CustomView.ClickListener, GameManager preferences.firstStart = false // Set Default Settings preferences.settings = - Settings(4, Runtime.getRuntime().availableProcessors() - 1, 200, true) + Settings(4, Runtime.getRuntime().availableProcessors() - 1, 100, true) } gameManager.initBoard(false) @@ -86,7 +97,8 @@ class ChessActivity : AppCompatActivity(), CustomView.ClickListener, GameManager tiles.forEach { it.value.invalidate() } - gameManager.statsEnabled = preferences.debugInfo + gameManager.basicStatsEnabled = preferences.basicDebugInfo + gameManager.advancedStatsEnabled = preferences.advancedDebugInfo gameManager.updateSettings(preferences.settings) } @@ -113,15 +125,14 @@ class ChessActivity : AppCompatActivity(), CustomView.ClickListener, GameManager pieces.forEach { if (it.value.res == drawable) - it.value.isInChess = true + it.value.isInCheck = true } } private fun movePiece(view: TileView) { if (!selectedPos.isValid || !canMove) return - val startPos = selectedPos - gameManager.makeMove(startPos, view.pos) + gameManager.makeMove(selectedPos, view.pos) clearTiles(true) selectedPos = Pos() @@ -144,10 +155,19 @@ class ChessActivity : AppCompatActivity(), CustomView.ClickListener, GameManager private fun updateState(state: State) { canMove = true - if (gameManager.statsEnabled) { + if (gameManager.basicStatsEnabled) { tv_debug.visibility = View.VISIBLE - tv_debug.text = - getString(R.string.stats, Native.getStats(), Native.getBoardValue(), Native.getBestMoveFound()) + + val advancedStats = + if (gameManager.advancedStatsEnabled) "\n" + Native.getAdvancedStats() else "" + + tv_debug.text = getString( + R.string.basic_stats, + Native.getSearchTime(), + Native.getCurrentBoardValue(), + Native.getBestMoveFound(), + advancedStats + ) } else { tv_debug.visibility = View.GONE } @@ -177,8 +197,8 @@ class ChessActivity : AppCompatActivity(), CustomView.ClickListener, GameManager setKingInChess(false) } else { pieces.forEach { - if (it.value.isInChess) - it.value.isInChess = false + if (it.value.isInCheck) + it.value.isInCheck = false } } } @@ -213,23 +233,21 @@ class ChessActivity : AppCompatActivity(), CustomView.ClickListener, GameManager } tiles.clear() - for (i in 0..7) { - for (j in 0..7) { - val pos = Pos(invertIf(!isPlayerWhite, i), invertIf(isPlayerWhite, j)) - val isWhite = (i + j) % 2 == 0 - - val xSize = invertIf(!isPlayerWhite, pos.x.toInt()) * viewSize - val ySize = invertIf(isPlayerWhite, pos.y.toInt()) * viewSize + for (i in 0 until 64) { + val pos = Pos(i % 8, i / 8) + val isWhite = (pos.x + pos.y) % 2 == 1 - val tileView = TileView(this, isWhite, pos, this).apply { - layoutParams = FrameLayout.LayoutParams(viewSize, viewSize) - x = xSize.toFloat() - y = ySize.toFloat() - } + val xSize = invertIf(!isPlayerWhite, pos.x) * viewSize + val ySize = invertIf(isPlayerWhite, pos.y) * viewSize - tiles[pos] = tileView - layout_board.addView(tileView) + val tileView = TileView(this, isWhite, pos, this).apply { + layoutParams = FrameLayout.LayoutParams(viewSize, viewSize) + x = xSize.toFloat() + y = ySize.toFloat() } + + tiles[pos] = tileView + layout_board.addView(tileView) } } @@ -243,11 +261,12 @@ class ChessActivity : AppCompatActivity(), CustomView.ClickListener, GameManager val isWhite = it.type in 1..6 val resource = PieceResourceManager.piecesResources[it.type.toInt() - 1] val clickable = isWhite == isPlayerWhite - val xSize = invertIf(!isPlayerWhite, it.x.toInt()) * viewSize - val ySize = invertIf(isPlayerWhite, it.y.toInt()) * viewSize val pos = Pos(it.x, it.y) + val xSize = invertIf(!isPlayerWhite, pos.x) * viewSize + val ySize = invertIf(isPlayerWhite, pos.y) * viewSize + val pieceView = PieceView(this, clickable, resource, pos, this).apply { layoutParams = FrameLayout.LayoutParams(viewSize, viewSize) x = xSize.toFloat() @@ -272,13 +291,14 @@ class ChessActivity : AppCompatActivity(), CustomView.ClickListener, GameManager it.pos = destPos // Calculate the new View Position - val xPos = invertIf(!isPlayerWhite, destPos.x.toInt()) * viewSize - val yPos = invertIf(isPlayerWhite, destPos.y.toInt()) * viewSize + val xPos = invertIf(!isPlayerWhite, destPos.x) * viewSize + val yPos = invertIf(isPlayerWhite, destPos.y) * viewSize it.animate() .x(xPos.toFloat()) .y(yPos.toFloat()) .setDuration(250L) + .setInterpolator(AccelerateDecelerateInterpolator()) .start() } @@ -287,9 +307,16 @@ class ChessActivity : AppCompatActivity(), CustomView.ClickListener, GameManager layout_board.removeView(destView) val type = PieceResourceManager.piecesResources.indexOf(destView.res) + 1 - val piece = Piece(destView.pos.x, destView.pos.y, type.toByte()) - - if (isPlayerWhite) capturedPieces.addBlackPiece(piece) else capturedPieces.addWhitePiece(piece) + val piece = Piece( + invertIf(!isPlayerWhite, destView.pos.x), + invertIf(isPlayerWhite, destView.pos.y), + type.toByte() + ) + + if (isPlayerWhite) + capturedPieces.addBlackPiece(piece) + else + capturedPieces.addWhitePiece(piece) } pieceView?.let { diff --git a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/ChessApp.kt b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/ChessApp.kt index aa1624f..9178fb8 100644 --- a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/ChessApp.kt +++ b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/ChessApp.kt @@ -4,6 +4,7 @@ import android.app.Application @Suppress("unused") class ChessApp : Application() { + companion object { init { System.loadLibrary("chess") diff --git a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/Data.kt b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/Data.kt index 9b45059..1a40589 100644 --- a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/Data.kt +++ b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/Data.kt @@ -11,9 +11,9 @@ enum class State { data class Piece( @JvmField - val x: Byte, + val x: Int, @JvmField - val y: Byte, + val y: Int, @JvmField val type: Byte ) { @@ -30,13 +30,14 @@ data class Piece( data class Pos( @JvmField - val x: Byte, + val x: Int, @JvmField - val y: Byte + val y: Int ) { constructor() : this(8, 8) - constructor(x: Int, y: Int) : this(x.toByte(), y.toByte()) + + constructor(square: Byte) : this(square % 8, square / 8) val isValid get() = (x in 0..7 && y in 0..7) @@ -44,13 +45,13 @@ data class Pos( data class PosPair( @JvmField - val startX: Byte, + val startX: Int, @JvmField - val startY: Byte, + val startY: Int, @JvmField - val destX: Byte, + val destX: Int, @JvmField - val destY: Byte + val destY: Int ) class Settings( diff --git a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/GameManager.kt b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/GameManager.kt index e86956e..1cc4c06 100644 --- a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/GameManager.kt +++ b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/GameManager.kt @@ -26,7 +26,8 @@ class GameManager( private var initialized = false private var isPlayerWhite = true - var statsEnabled = false + var basicStatsEnabled = false + var advancedStatsEnabled = false val isWorking get() = Native.isWorking() @@ -34,6 +35,7 @@ class GameManager( fun initBoard(restartGame: Boolean, playerWhite: Boolean = true) { initBoardNative(restartGame, playerWhite) + Native.enableStats(advancedStatsEnabled) if (initialized) { if (isPlayerWhite != playerWhite) { @@ -67,8 +69,13 @@ class GameManager( } fun makeMove(startPos: Pos, destPos: Pos) { - Native.enableStats(statsEnabled) - Native.movePiece(startPos.x, startPos.y, destPos.x, destPos.y) + Native.enableStats(advancedStatsEnabled) + Native.movePiece( + startPos.x.toByte(), + startPos.y.toByte(), + destPos.x.toByte(), + destPos.y.toByte() + ) } private fun getPiecesList() = Native.getPieces().filter { it.type.toInt() != 0 } @@ -105,4 +112,4 @@ class GameManager( } private external fun initBoardNative(restartGame: Boolean, isPlayerWhite: Boolean) -} \ No newline at end of file +} diff --git a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/Native.kt b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/Native.kt index 1ab181d..6942b1e 100644 --- a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/Native.kt +++ b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/Native.kt @@ -7,11 +7,21 @@ object Native { @JvmStatic external fun isPlayerWhite(): Boolean + // region Stats + + @JvmStatic + external fun getSearchTime(): Double + + @JvmStatic + external fun getCurrentBoardValue(): Int + @JvmStatic - external fun getStats(): String + external fun getBestMoveFound(): Int @JvmStatic - external fun getBoardValue(): Int + external fun getAdvancedStats(): String + + // endregion Stats @JvmStatic external fun getPieces(): Array @@ -33,9 +43,6 @@ object Native { performQuiescenceSearch: Boolean ) - @JvmStatic - external fun getBestMoveFound(): Int - @JvmStatic external fun undoMoves() diff --git a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/SettingsActivity.kt b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/SettingsActivity.kt index 42fd349..09a21ac 100644 --- a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/SettingsActivity.kt +++ b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/SettingsActivity.kt @@ -1,11 +1,15 @@ package net.theluckycoder.chess import android.os.Bundle -import android.preference.PreferenceFragment import android.view.MenuItem import androidx.appcompat.app.AppCompatActivity +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SeekBarPreference +import net.theluckycoder.chess.utils.AppPreferences import net.theluckycoder.chess.utils.getColor import kotlin.concurrent.thread +import kotlin.math.min class SettingsActivity : AppCompatActivity() { @@ -13,35 +17,44 @@ class SettingsActivity : AppCompatActivity() { super.onCreate(savedInstanceState) supportActionBar?.setDisplayHomeAsUpEnabled(true) - fragmentManager + supportFragmentManager .beginTransaction() .replace(android.R.id.content, SettingsFragment()) .commit() } - class SettingsFragment : PreferenceFragment() { + class SettingsFragment : PreferenceFragmentCompat() { - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences) - findPreference(Preferences.KEY_RESET_COLORS).setOnPreferenceClickListener { + findPreference(AppPreferences.KEY_RESET_COLORS)?.setOnPreferenceClickListener { val activity = activity ?: return@setOnPreferenceClickListener false - Preferences(activity).apply { + AppPreferences(activity).apply { whiteTileColor = getColor(activity, R.color.tile_white) blackTileColor = getColor(activity, R.color.tile_black) possibleTileColor = getColor(activity, R.color.tile_possible) selectedTileColor = getColor(activity, R.color.tile_selected) lastMovedTileColor = getColor(activity, R.color.tile_last_moved) - kingInChessColor = getColor(activity, R.color.king_in_chess) + kingInChessColor = getColor(activity, R.color.king_in_check) } true } - findPreference(Preferences.KEY_PERFT_TEST).setOnPreferenceClickListener { + val threadCountPref = findPreference(AppPreferences.KEY_THREAD_COUNT) + if (threadCountPref != null) { + val defaultValue = min(Runtime.getRuntime().availableProcessors() - 1, 1) + + threadCountPref.setDefaultValue(defaultValue) + threadCountPref.max = Runtime.getRuntime().availableProcessors() + if (threadCountPref.value == 0) + threadCountPref.value = defaultValue + } + + findPreference(AppPreferences.KEY_PERFT_TEST)?.setOnPreferenceClickListener { thread { - Native.perft(5) + Native.perft(6) } true } diff --git a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/Preferences.kt b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/utils/AppPreferences.kt similarity index 63% rename from ChessAndroid/app/src/main/java/net/theluckycoder/chess/Preferences.kt rename to ChessAndroid/app/src/main/java/net/theluckycoder/chess/utils/AppPreferences.kt index f8e8e8e..0ead3a0 100644 --- a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/Preferences.kt +++ b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/utils/AppPreferences.kt @@ -1,11 +1,12 @@ -package net.theluckycoder.chess +package net.theluckycoder.chess.utils import android.content.Context import android.content.SharedPreferences -import android.preference.PreferenceManager -import net.theluckycoder.chess.utils.getColor +import androidx.preference.PreferenceManager +import net.theluckycoder.chess.R +import net.theluckycoder.chess.Settings -class Preferences(private val context: Context) { +class AppPreferences(private val context: Context) { companion object { const val KEY_FIRST_START = "key_first_start" @@ -15,14 +16,15 @@ class Preferences(private val context: Context) { const val KEY_TILE_POSSIBLE = "key_tile_possible" const val KEY_TILE_SELECTED = "key_tile_selected" const val KEY_TILE_LAST_MOVED = "key_tile_last_moved" - const val KEY_KING_IN_CHESS = "key_king_in_chess" + const val KEY_KING_IN_CHECK = "key_king_in_check" const val KEY_RESET_COLORS = "key_reset_colors" - const val KEY_DEPTH = "key_depth" - const val KEY_THREADS = "key_threads" + const val KEY_SEARCH_DEPTH = "key_search_depth" + const val KEY_THREAD_COUNT = "key_thread_count" const val KEY_CACHE_SIZE = "key_cache_size" const val KEY_QUIET_SEARCH = "key_quiet_search" - const val KEY_DEBUG_INFO = "key_debug_info" + const val KEY_DEBUG_INFO_BASIC = "key_debug_basic" + const val KEY_DEBUG_INFO_ADVANCED = "key_debug_advanced" const val KEY_PERFT_TEST = "key_perft_test" } @@ -54,26 +56,34 @@ class Preferences(private val context: Context) { set(value) = manager.edit().putInt(KEY_TILE_LAST_MOVED, value).apply() var kingInChessColor - get() = manager.getInt(KEY_KING_IN_CHESS, getColor(context, R.color.king_in_chess)) - set(value) = manager.edit().putInt(KEY_KING_IN_CHESS, value).apply() + get() = manager.getInt(KEY_KING_IN_CHECK, getColor(context, R.color.king_in_check)) + set(value) = manager.edit().putInt(KEY_KING_IN_CHECK, value).apply() var settings get() = Settings( - baseSearchDepth = manager.getString(KEY_DEPTH, null)?.toIntOrNull() ?: 4, - threadCount = manager.getString(KEY_THREADS, null)?.toIntOrNull() - ?: Runtime.getRuntime().availableProcessors() - 1, - cacheSize = manager.getString(KEY_CACHE_SIZE, null)?.toIntOrNull() ?: 200, + baseSearchDepth = manager.getInt(KEY_SEARCH_DEPTH, 4), + threadCount = manager.getInt( + KEY_THREAD_COUNT, + Runtime.getRuntime().availableProcessors() - 1 + ), + cacheSize = manager.getString( + KEY_CACHE_SIZE, + null + )?.toIntOrNull() ?: 100, performQuiescenceSearch = manager.getBoolean(KEY_QUIET_SEARCH, true) ) set(value) { manager.edit() - .putString(KEY_DEPTH, value.baseSearchDepth.toString()) - .putString(KEY_THREADS, value.threadCount.toString()) + .putInt(KEY_SEARCH_DEPTH, value.baseSearchDepth) + .putInt(KEY_THREAD_COUNT, value.threadCount) .putString(KEY_CACHE_SIZE, value.cacheSize.toString()) .putBoolean(KEY_QUIET_SEARCH, value.performQuiescenceSearch) .apply() } - val debugInfo - get() = manager.getBoolean(KEY_DEBUG_INFO, false) + val basicDebugInfo + get() = manager.getBoolean(KEY_DEBUG_INFO_BASIC, false) + + val advancedDebugInfo + get() = manager.getBoolean(KEY_DEBUG_INFO_ADVANCED, false) } \ No newline at end of file diff --git a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/utils/Extensions.kt b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/utils/Extensions.kt index a54a91b..b47ccf4 100644 --- a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/utils/Extensions.kt +++ b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/utils/Extensions.kt @@ -4,6 +4,9 @@ import android.content.Context import android.os.Build @Suppress("DEPRECATION") -fun getColor(context: Context, color: Int): Int = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - context.getColor(color) -} else context.resources.getColor(color) +fun getColor(context: Context, color: Int): Int { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + context.getColor(color) + } else context.resources.getColor(color) +} + diff --git a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/PieceResourceManager.kt b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/utils/PieceResourceManager.kt similarity index 63% rename from ChessAndroid/app/src/main/java/net/theluckycoder/chess/PieceResourceManager.kt rename to ChessAndroid/app/src/main/java/net/theluckycoder/chess/utils/PieceResourceManager.kt index f4b48f3..f094cfd 100644 --- a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/PieceResourceManager.kt +++ b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/utils/PieceResourceManager.kt @@ -1,19 +1,26 @@ -package net.theluckycoder.chess +package net.theluckycoder.chess.utils import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory import android.util.SparseArray +import net.theluckycoder.chess.R object PieceResourceManager { val piecesResources = arrayOf( - R.drawable.w_pawn, R.drawable.w_knight, - R.drawable.w_bishop, R.drawable.w_rook, - R.drawable.w_queen, R.drawable.w_king, - R.drawable.b_pawn, R.drawable.b_knight, - R.drawable.b_bishop, R.drawable.b_rook, - R.drawable.b_queen, R.drawable.b_king + R.drawable.w_pawn, + R.drawable.w_knight, + R.drawable.w_bishop, + R.drawable.w_rook, + R.drawable.w_queen, + R.drawable.w_king, + R.drawable.b_pawn, + R.drawable.b_knight, + R.drawable.b_bishop, + R.drawable.b_rook, + R.drawable.b_queen, + R.drawable.b_king ) private val piecesBitmaps = SparseArray(piecesResources.size) diff --git a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/views/PieceView.kt b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/views/PieceView.kt index 535d632..1798f16 100644 --- a/ChessAndroid/app/src/main/java/net/theluckycoder/chess/views/PieceView.kt +++ b/ChessAndroid/app/src/main/java/net/theluckycoder/chess/views/PieceView.kt @@ -6,7 +6,7 @@ import android.graphics.BlurMaskFilter import android.graphics.Canvas import android.graphics.Paint import net.theluckycoder.chess.ChessActivity -import net.theluckycoder.chess.PieceResourceManager +import net.theluckycoder.chess.utils.PieceResourceManager import net.theluckycoder.chess.Pos @SuppressLint("ViewConstructor") @@ -31,14 +31,14 @@ class PieceView( private val redBlurPaint = Paint().apply { maskFilter = BlurMaskFilter(10f, BlurMaskFilter.Blur.NORMAL) } - var isInChess = false + var isInCheck = false set(value) { field = value invalidate() } override fun onDraw(canvas: Canvas) { - if (isInChess) { + if (isInCheck) { redBlurPaint.color = activity.preferences.kingInChessColor canvas.drawCircle(width / 2f, height / 2f, width / 2.5f, redBlurPaint) } diff --git a/ChessAndroid/app/src/main/res/drawable/ic_pref_cache.xml b/ChessAndroid/app/src/main/res/drawable/ic_pref_cache.xml new file mode 100644 index 0000000..86e9e7a --- /dev/null +++ b/ChessAndroid/app/src/main/res/drawable/ic_pref_cache.xml @@ -0,0 +1,9 @@ + + + diff --git a/ChessAndroid/app/src/main/res/drawable/ic_pref_debug_info.xml b/ChessAndroid/app/src/main/res/drawable/ic_pref_debug_info.xml new file mode 100644 index 0000000..27ab79f --- /dev/null +++ b/ChessAndroid/app/src/main/res/drawable/ic_pref_debug_info.xml @@ -0,0 +1,9 @@ + + + diff --git a/ChessAndroid/app/src/main/res/drawable/ic_pref_stats.xml b/ChessAndroid/app/src/main/res/drawable/ic_pref_stats.xml new file mode 100644 index 0000000..d795910 --- /dev/null +++ b/ChessAndroid/app/src/main/res/drawable/ic_pref_stats.xml @@ -0,0 +1,9 @@ + + + diff --git a/ChessAndroid/app/src/main/res/drawable/ic_pref_thread_count.xml b/ChessAndroid/app/src/main/res/drawable/ic_pref_thread_count.xml new file mode 100644 index 0000000..ac68e6d --- /dev/null +++ b/ChessAndroid/app/src/main/res/drawable/ic_pref_thread_count.xml @@ -0,0 +1,9 @@ + + + diff --git a/ChessAndroid/app/src/main/res/drawable/ic_settings.xml b/ChessAndroid/app/src/main/res/drawable/ic_settings.xml index 7f1fc86..0276922 100644 --- a/ChessAndroid/app/src/main/res/drawable/ic_settings.xml +++ b/ChessAndroid/app/src/main/res/drawable/ic_settings.xml @@ -1,9 +1,9 @@ + android:width="32dp" + android:height="32dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:fillColor="?android:textColorPrimary" + android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z" /> diff --git a/ChessAndroid/app/src/main/res/drawable/ic_undo.xml b/ChessAndroid/app/src/main/res/drawable/ic_undo.xml index 1467bff..b877407 100644 --- a/ChessAndroid/app/src/main/res/drawable/ic_undo.xml +++ b/ChessAndroid/app/src/main/res/drawable/ic_undo.xml @@ -1,9 +1,9 @@ + android:width="32dp" + android:height="32dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + android:fillColor="?android:textColorPrimary" + android:pathData="M12.5,8c-2.65,0 -5.05,0.99 -6.9,2.6L2,7v9h9l-3.62,-3.62c1.39,-1.16 3.16,-1.88 5.12,-1.88 3.54,0 6.55,2.31 7.6,5.5l2.37,-0.78C21.08,11.03 17.15,8 12.5,8z" /> diff --git a/ChessAndroid/app/src/main/res/layout/activity_chess.xml b/ChessAndroid/app/src/main/res/layout/activity_chess.xml index 981c023..b424ca4 100644 --- a/ChessAndroid/app/src/main/res/layout/activity_chess.xml +++ b/ChessAndroid/app/src/main/res/layout/activity_chess.xml @@ -23,12 +23,12 @@ android:id="@+id/tv_debug" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_above="@id/btn_restart_game" android:layout_below="@id/pb_loading" android:gravity="center_horizontal" - android:textColor="@android:color/black" - android:textSize="15.5sp" android:scrollbars="vertical" - android:layout_above="@id/btn_restart_game" + android:textColor="?android:textColorPrimary" + android:textSize="15.5sp" tools:text="Stats:" /> + android:text="@string/new_game" /> + android:orientation="vertical" + android:padding="16dp"> + android:text="@string/side" + android:textColor="?colorAccent" /> + android:entries="@array/sides" + android:minHeight="40dp" /> - \ No newline at end of file + diff --git a/ChessAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/ChessAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 67820c5..83b52fc 100644 --- a/ChessAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/ChessAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/ChessAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/ChessAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..4ae7d12 --- /dev/null +++ b/ChessAndroid/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/ChessAndroid/app/src/main/res/mipmap-hdpi/ic_launcher.png b/ChessAndroid/app/src/main/res/mipmap-hdpi/ic_launcher.png index e019240..8016842 100644 Binary files a/ChessAndroid/app/src/main/res/mipmap-hdpi/ic_launcher.png and b/ChessAndroid/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/ChessAndroid/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/ChessAndroid/app/src/main/res/mipmap-hdpi/ic_launcher_background.png new file mode 100644 index 0000000..37e50d1 Binary files /dev/null and b/ChessAndroid/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ diff --git a/ChessAndroid/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/ChessAndroid/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png index fea66eb..a44962a 100644 Binary files a/ChessAndroid/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and b/ChessAndroid/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/ChessAndroid/app/src/main/res/mipmap-mdpi/ic_launcher.png b/ChessAndroid/app/src/main/res/mipmap-mdpi/ic_launcher.png index ef583aa..6aa0a85 100644 Binary files a/ChessAndroid/app/src/main/res/mipmap-mdpi/ic_launcher.png and b/ChessAndroid/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/ChessAndroid/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/ChessAndroid/app/src/main/res/mipmap-mdpi/ic_launcher_background.png new file mode 100644 index 0000000..5cd246a Binary files /dev/null and b/ChessAndroid/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ diff --git a/ChessAndroid/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/ChessAndroid/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png index 7bd66b3..39f86e5 100644 Binary files a/ChessAndroid/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and b/ChessAndroid/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/ChessAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/ChessAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher.png index 4684d1a..5effcda 100644 Binary files a/ChessAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher.png and b/ChessAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/ChessAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/ChessAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png new file mode 100644 index 0000000..910f909 Binary files /dev/null and b/ChessAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ diff --git a/ChessAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/ChessAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png index 1f667e6..68fa337 100644 Binary files a/ChessAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and b/ChessAndroid/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/ChessAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/ChessAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher.png index 2203bac..690934e 100644 Binary files a/ChessAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and b/ChessAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/ChessAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/ChessAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png new file mode 100644 index 0000000..5e0a79f Binary files /dev/null and b/ChessAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ diff --git a/ChessAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/ChessAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png index bf0c751..4ab6b81 100644 Binary files a/ChessAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and b/ChessAndroid/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/ChessAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/ChessAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png index 86e60d9..e9158ba 100644 Binary files a/ChessAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and b/ChessAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/ChessAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/ChessAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png new file mode 100644 index 0000000..5e97f4b Binary files /dev/null and b/ChessAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ diff --git a/ChessAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/ChessAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png index acc49dc..cb180b5 100644 Binary files a/ChessAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and b/ChessAndroid/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/ChessAndroid/app/src/main/res/values/colors.xml b/ChessAndroid/app/src/main/res/values/colors.xml index 9e38e2a..6cb9b81 100644 --- a/ChessAndroid/app/src/main/res/values/colors.xml +++ b/ChessAndroid/app/src/main/res/values/colors.xml @@ -1,9 +1,9 @@ #eeeed2 - #769656 + #769655 #ef4fc3f7 #CC32BF37 #8CFBC02D - #aadd0000 + #aadd0000 diff --git a/ChessAndroid/app/src/main/res/values/strings.xml b/ChessAndroid/app/src/main/res/values/strings.xml index 6df8386..9b5bdb8 100644 --- a/ChessAndroid/app/src/main/res/values/strings.xml +++ b/ChessAndroid/app/src/main/res/values/strings.xml @@ -1,10 +1,16 @@ Chess - %sCurrent Board is evaluated at: %d\nBest Move Found: %d - Restart Game + Time Needed: %.3f millis\nCurrent Board is evaluated at: %d\nBest Move Found: %d%s + New Game Side: - Restart + Start Settings Undo Move + + + Appearance + Difficulty + Other Algorithm Settings + Debugging diff --git a/ChessAndroid/app/src/main/res/values/styles.xml b/ChessAndroid/app/src/main/res/values/styles.xml index fadb461..4f712c5 100644 --- a/ChessAndroid/app/src/main/res/values/styles.xml +++ b/ChessAndroid/app/src/main/res/values/styles.xml @@ -1,6 +1,5 @@ - +