mirror of
https://github.com/HChaZZY/Stockfish.git
synced 2025-12-25 03:26:24 +08:00
7855 lines
250 KiB
C++
7855 lines
250 KiB
C++
/*
|
|
Stockfish, a UCI chess playing engine derived from Glaurung 2.1
|
|
Copyright (C) 2004-2021 The Stockfish developers (see AUTHORS file)
|
|
|
|
Stockfish is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Stockfish is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <cstdio>
|
|
#include <cassert>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <vector>
|
|
#include <memory>
|
|
#include <fstream>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <set>
|
|
#include <cstdio>
|
|
#include <cassert>
|
|
#include <array>
|
|
#include <limits>
|
|
#include <climits>
|
|
#include <optional>
|
|
|
|
#if (defined(_MSC_VER) || defined(__INTEL_COMPILER)) && !defined(__clang__)
|
|
#include <intrin.h>
|
|
#endif
|
|
|
|
namespace chess
|
|
{
|
|
#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
|
|
|
|
#define FORCEINLINE __attribute__((always_inline))
|
|
|
|
#elif defined(_MSC_VER)
|
|
|
|
// NOTE: for some reason it breaks the profiler a little
|
|
// keep it on only when not profiling.
|
|
//#define FORCEINLINE __forceinline
|
|
#define FORCEINLINE
|
|
|
|
#else
|
|
|
|
#define FORCEINLINE inline
|
|
|
|
#endif
|
|
|
|
#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
|
|
|
|
#define NOINLINE __attribute__((noinline))
|
|
|
|
#elif defined(_MSC_VER)
|
|
|
|
#define NOINLINE __declspec(noinline)
|
|
|
|
#else
|
|
|
|
#define NOINLINE
|
|
|
|
#endif
|
|
|
|
namespace intrin
|
|
{
|
|
[[nodiscard]] constexpr int popcount_constexpr(std::uint64_t value)
|
|
{
|
|
int r = 0;
|
|
while (value)
|
|
{
|
|
value &= value - 1;
|
|
++r;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
[[nodiscard]] constexpr int lsb_constexpr(std::uint64_t value)
|
|
{
|
|
int c = 0;
|
|
value &= ~value + 1; // leave only the lsb
|
|
if ((value & 0x00000000FFFFFFFFull) == 0) c += 32;
|
|
if ((value & 0x0000FFFF0000FFFFull) == 0) c += 16;
|
|
if ((value & 0x00FF00FF00FF00FFull) == 0) c += 8;
|
|
if ((value & 0x0F0F0F0F0F0F0F0Full) == 0) c += 4;
|
|
if ((value & 0x3333333333333333ull) == 0) c += 2;
|
|
if ((value & 0x5555555555555555ull) == 0) c += 1;
|
|
return c;
|
|
}
|
|
|
|
[[nodiscard]] constexpr int msb_constexpr(std::uint64_t value)
|
|
{
|
|
int c = 63;
|
|
if ((value & 0xFFFFFFFF00000000ull) == 0) { c -= 32; value <<= 32; }
|
|
if ((value & 0xFFFF000000000000ull) == 0) { c -= 16; value <<= 16; }
|
|
if ((value & 0xFF00000000000000ull) == 0) { c -= 8; value <<= 8; }
|
|
if ((value & 0xF000000000000000ull) == 0) { c -= 4; value <<= 4; }
|
|
if ((value & 0xC000000000000000ull) == 0) { c -= 2; value <<= 2; }
|
|
if ((value & 0x8000000000000000ull) == 0) { c -= 1; }
|
|
return c;
|
|
}
|
|
}
|
|
|
|
namespace intrin
|
|
{
|
|
[[nodiscard]] inline int popcount(std::uint64_t b)
|
|
{
|
|
#if (defined(_MSC_VER) || defined(__INTEL_COMPILER)) && !defined(__clang__)
|
|
|
|
return static_cast<int>(_mm_popcnt_u64(b));
|
|
|
|
#else
|
|
|
|
return static_cast<int>(__builtin_popcountll(b));
|
|
|
|
#endif
|
|
}
|
|
|
|
#if defined(_MSC_VER) && !defined(__clang__)
|
|
|
|
[[nodiscard]] inline int lsb(std::uint64_t value)
|
|
{
|
|
assert(value != 0);
|
|
|
|
unsigned long idx;
|
|
_BitScanForward64(&idx, value);
|
|
return static_cast<int>(idx);
|
|
}
|
|
|
|
[[nodiscard]] inline int msb(std::uint64_t value)
|
|
{
|
|
assert(value != 0);
|
|
|
|
unsigned long idx;
|
|
_BitScanReverse64(&idx, value);
|
|
return static_cast<int>(idx);
|
|
}
|
|
|
|
#else
|
|
|
|
[[nodiscard]] inline int lsb(std::uint64_t value)
|
|
{
|
|
assert(value != 0);
|
|
|
|
return __builtin_ctzll(value);
|
|
}
|
|
|
|
[[nodiscard]] inline int msb(std::uint64_t value)
|
|
{
|
|
assert(value != 0);
|
|
|
|
return 63 ^ __builtin_clzll(value);
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
template <typename IntT>
|
|
[[nodiscard]] constexpr IntT floorLog2(IntT value)
|
|
{
|
|
return intrin::msb_constexpr(value);
|
|
}
|
|
|
|
template <typename IntT>
|
|
constexpr auto computeMasks()
|
|
{
|
|
static_assert(std::is_unsigned_v<IntT>);
|
|
|
|
constexpr std::size_t numBits = sizeof(IntT) * CHAR_BIT;
|
|
std::array<IntT, numBits + 1u> nbitmasks{};
|
|
|
|
for (std::size_t i = 0; i < numBits; ++i)
|
|
{
|
|
nbitmasks[i] = (static_cast<IntT>(1u) << i) - 1u;
|
|
}
|
|
nbitmasks[numBits] = ~static_cast<IntT>(0u);
|
|
|
|
return nbitmasks;
|
|
}
|
|
|
|
template <typename IntT>
|
|
constexpr auto nbitmask = computeMasks<IntT>();
|
|
|
|
template <std::size_t N, typename FromT, typename ToT = std::make_signed_t<FromT>>
|
|
inline ToT signExtend(FromT value)
|
|
{
|
|
static_assert(std::is_signed_v<ToT>);
|
|
static_assert(std::is_unsigned_v<FromT>);
|
|
static_assert(sizeof(ToT) == sizeof(FromT));
|
|
|
|
constexpr std::size_t totalBits = sizeof(FromT) * CHAR_BIT;
|
|
|
|
static_assert(N > 0 && N <= totalBits);
|
|
|
|
constexpr std::size_t unusedBits = totalBits - N;
|
|
if constexpr (ToT(~FromT(0)) >> 1 == ToT(~FromT(0)))
|
|
{
|
|
return ToT(value << unusedBits) >> ToT(unusedBits);
|
|
}
|
|
else
|
|
{
|
|
constexpr FromT mask = (~FromT(0)) >> unusedBits;
|
|
value &= mask;
|
|
if (value & (FromT(1) << (N - 1)))
|
|
{
|
|
value |= ~mask;
|
|
}
|
|
return static_cast<ToT>(value);
|
|
}
|
|
}
|
|
|
|
namespace lookup
|
|
{
|
|
constexpr int nthSetBitIndexNaive(std::uint64_t value, int n)
|
|
{
|
|
for (int i = 0; i < n; ++i)
|
|
{
|
|
value &= value - 1;
|
|
}
|
|
return intrin::lsb_constexpr(value);
|
|
}
|
|
|
|
constexpr std::array<std::array<std::uint8_t, 8>, 256> nthSetBitIndex = []()
|
|
{
|
|
std::array<std::array<std::uint8_t, 8>, 256> t{};
|
|
|
|
for (int i = 0; i < 256; ++i)
|
|
{
|
|
for (int j = 0; j < 8; ++j)
|
|
{
|
|
t[i][j] = nthSetBitIndexNaive(i, j);
|
|
}
|
|
}
|
|
|
|
return t;
|
|
}();
|
|
}
|
|
|
|
inline int nthSetBitIndex(std::uint64_t v, std::uint64_t n)
|
|
{
|
|
std::uint64_t shift = 0;
|
|
|
|
std::uint64_t p = intrin::popcount(v & 0xFFFFFFFFull);
|
|
std::uint64_t pmask = static_cast<std::uint64_t>(p > n) - 1ull;
|
|
v >>= 32 & pmask;
|
|
shift += 32 & pmask;
|
|
n -= p & pmask;
|
|
|
|
p = intrin::popcount(v & 0xFFFFull);
|
|
pmask = static_cast<std::uint64_t>(p > n) - 1ull;
|
|
v >>= 16 & pmask;
|
|
shift += 16 & pmask;
|
|
n -= p & pmask;
|
|
|
|
p = intrin::popcount(v & 0xFFull);
|
|
pmask = static_cast<std::uint64_t>(p > n) - 1ull;
|
|
shift += 8 & pmask;
|
|
v >>= 8 & pmask;
|
|
n -= p & pmask;
|
|
|
|
return static_cast<int>(lookup::nthSetBitIndex[v & 0xFFull][n] + shift);
|
|
}
|
|
|
|
namespace util
|
|
{
|
|
inline std::size_t usedBits(std::size_t value)
|
|
{
|
|
if (value == 0) return 0;
|
|
return intrin::msb(value) + 1;
|
|
}
|
|
}
|
|
|
|
template <typename EnumT>
|
|
struct EnumTraits;
|
|
|
|
template <typename EnumT>
|
|
[[nodiscard]] constexpr auto hasEnumTraits() -> decltype(EnumTraits<EnumT>::cardinaliy, bool{})
|
|
{
|
|
return true;
|
|
}
|
|
|
|
template <typename EnumT>
|
|
[[nodiscard]] constexpr bool hasEnumTraits(...)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
template <typename EnumT>
|
|
[[nodiscard]] constexpr bool isNaturalIndex() noexcept
|
|
{
|
|
return EnumTraits<EnumT>::isNaturalIndex;
|
|
}
|
|
|
|
template <typename EnumT>
|
|
[[nodiscard]] constexpr int cardinality() noexcept
|
|
{
|
|
return EnumTraits<EnumT>::cardinality;
|
|
}
|
|
|
|
template <typename EnumT>
|
|
[[nodiscard]] constexpr const std::array<EnumT, cardinality<EnumT>()>& values() noexcept
|
|
{
|
|
return EnumTraits<EnumT>::values;
|
|
}
|
|
|
|
template <typename EnumT>
|
|
[[nodiscard]] constexpr EnumT fromOrdinal(int id) noexcept
|
|
{
|
|
assert(!EnumTraits<EnumT>::isNaturalIndex || (id >= 0 && id < EnumTraits<EnumT>::cardinality));
|
|
|
|
return EnumTraits<EnumT>::fromOrdinal(id);
|
|
}
|
|
|
|
template <typename EnumT>
|
|
[[nodiscard]] constexpr typename EnumTraits<EnumT>::IdType ordinal(EnumT v) noexcept
|
|
{
|
|
return EnumTraits<EnumT>::ordinal(v);
|
|
}
|
|
|
|
template <typename EnumT, typename... ArgsTs, typename SFINAE = std::enable_if_t<hasEnumTraits<EnumT>()>>
|
|
[[nodiscard]] constexpr decltype(auto) toString(EnumT v, ArgsTs&&... args)
|
|
{
|
|
return EnumTraits<EnumT>::toString(v, std::forward<ArgsTs>(args)...);
|
|
}
|
|
|
|
template <typename EnumT>
|
|
[[nodiscard]] constexpr decltype(auto) toString(EnumT v)
|
|
{
|
|
return EnumTraits<EnumT>::toString(v);
|
|
}
|
|
|
|
template <typename EnumT, typename FormatT, typename SFINAE = std::enable_if_t<!hasEnumTraits<FormatT>()>>
|
|
[[nodiscard]] constexpr decltype(auto) toString(FormatT&& f, EnumT v)
|
|
{
|
|
return EnumTraits<EnumT>::toString(std::forward<FormatT>(f), v);
|
|
}
|
|
|
|
template <typename EnumT>
|
|
[[nodiscard]] constexpr decltype(auto) toChar(EnumT v)
|
|
{
|
|
return EnumTraits<EnumT>::toChar(v);
|
|
}
|
|
|
|
template <typename EnumT, typename FormatT>
|
|
[[nodiscard]] constexpr decltype(auto) toChar(FormatT&& f, EnumT v)
|
|
{
|
|
return EnumTraits<EnumT>::toChar(std::forward<FormatT>(f), v);
|
|
}
|
|
|
|
template <typename EnumT, typename... ArgsTs>
|
|
[[nodiscard]] constexpr decltype(auto) fromString(ArgsTs&& ... args)
|
|
{
|
|
return EnumTraits<EnumT>::fromString(std::forward<ArgsTs>(args)...);
|
|
}
|
|
|
|
template <typename EnumT, typename... ArgsTs>
|
|
[[nodiscard]] constexpr decltype(auto) fromChar(ArgsTs&& ... args)
|
|
{
|
|
return EnumTraits<EnumT>::fromChar(std::forward<ArgsTs>(args)...);
|
|
}
|
|
|
|
template <>
|
|
struct EnumTraits<bool>
|
|
{
|
|
using IdType = int;
|
|
using EnumType = bool;
|
|
|
|
static constexpr int cardinality = 2;
|
|
static constexpr bool isNaturalIndex = true;
|
|
|
|
static constexpr std::array<EnumType, cardinality> values{
|
|
false,
|
|
true
|
|
};
|
|
|
|
[[nodiscard]] static constexpr int ordinal(EnumType c) noexcept
|
|
{
|
|
return static_cast<IdType>(c);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr EnumType fromOrdinal(IdType id) noexcept
|
|
{
|
|
return static_cast<EnumType>(id);
|
|
}
|
|
};
|
|
|
|
template <typename EnumT, typename ValueT, std::size_t SizeV = cardinality<EnumT>()>
|
|
struct EnumArray
|
|
{
|
|
static_assert(isNaturalIndex<EnumT>(), "Enum must start with 0 and end with cardinality-1.");
|
|
|
|
using value_type = ValueT;
|
|
using size_type = std::size_t;
|
|
using difference_type = std::ptrdiff_t;
|
|
using pointer = ValueT *;
|
|
using const_pointer = const ValueT*;
|
|
using reference = ValueT &;
|
|
using const_reference = const ValueT &;
|
|
|
|
using iterator = pointer;
|
|
using const_iterator = const_pointer;
|
|
|
|
using reverse_iterator = std::reverse_iterator<iterator>;
|
|
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
|
|
|
using KeyType = EnumT;
|
|
using ValueType = ValueT;
|
|
|
|
constexpr void fill(const ValueType& init)
|
|
{
|
|
for (auto& v : elements)
|
|
{
|
|
v = init;
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] constexpr ValueType& operator[](const KeyType& dir)
|
|
{
|
|
assert(static_cast<int>(ordinal(dir)) < static_cast<int>(SizeV));
|
|
|
|
return elements[ordinal(dir)];
|
|
}
|
|
|
|
[[nodiscard]] constexpr const ValueType& operator[](const KeyType& dir) const
|
|
{
|
|
assert(static_cast<int>(ordinal(dir)) < static_cast<int>(SizeV));
|
|
|
|
return elements[ordinal(dir)];
|
|
}
|
|
|
|
[[nodiscard]] constexpr ValueType& front()
|
|
{
|
|
return elements[0];
|
|
}
|
|
|
|
[[nodiscard]] constexpr const ValueType& front() const
|
|
{
|
|
return elements[0];
|
|
}
|
|
|
|
[[nodiscard]] constexpr ValueType& back()
|
|
{
|
|
return elements[SizeV - 1];
|
|
}
|
|
|
|
[[nodiscard]] constexpr const ValueType& back() const
|
|
{
|
|
return elements[SizeV - 1];
|
|
}
|
|
|
|
[[nodiscard]] constexpr pointer data()
|
|
{
|
|
return elements;
|
|
}
|
|
|
|
[[nodiscard]] constexpr const_pointer data() const
|
|
{
|
|
return elements;
|
|
}
|
|
|
|
[[nodiscard]] constexpr iterator begin() noexcept
|
|
{
|
|
return elements;
|
|
}
|
|
|
|
[[nodiscard]] constexpr const_iterator begin() const noexcept
|
|
{
|
|
return elements;
|
|
}
|
|
|
|
[[nodiscard]] constexpr iterator end() noexcept
|
|
{
|
|
return elements + SizeV;
|
|
}
|
|
|
|
[[nodiscard]] constexpr const_iterator end() const noexcept
|
|
{
|
|
return elements + SizeV;
|
|
}
|
|
|
|
[[nodiscard]] constexpr reverse_iterator rbegin() noexcept
|
|
{
|
|
return reverse_iterator(end());
|
|
}
|
|
|
|
[[nodiscard]] constexpr const_reverse_iterator rbegin() const noexcept
|
|
{
|
|
return const_reverse_iterator(end());
|
|
}
|
|
|
|
[[nodiscard]] constexpr reverse_iterator rend() noexcept
|
|
{
|
|
return reverse_iterator(begin());
|
|
}
|
|
|
|
[[nodiscard]] constexpr const_reverse_iterator rend() const noexcept
|
|
{
|
|
return const_reverse_iterator(begin());
|
|
}
|
|
|
|
[[nodiscard]] constexpr const_iterator cbegin() const noexcept
|
|
{
|
|
return begin();
|
|
}
|
|
|
|
[[nodiscard]] constexpr const_iterator cend() const noexcept
|
|
{
|
|
return end();
|
|
}
|
|
|
|
[[nodiscard]] constexpr const_reverse_iterator crbegin() const noexcept
|
|
{
|
|
return rbegin();
|
|
}
|
|
|
|
[[nodiscard]] constexpr const_reverse_iterator crend() const noexcept
|
|
{
|
|
return rend();
|
|
}
|
|
|
|
[[nodiscard]] constexpr size_type size() const noexcept
|
|
{
|
|
return SizeV;
|
|
}
|
|
|
|
ValueT elements[SizeV];
|
|
};
|
|
|
|
template <typename Enum1T, typename Enum2T, typename ValueT, std::size_t Size1V = cardinality<Enum1T>(), std::size_t Size2V = cardinality<Enum2T>()>
|
|
using EnumArray2 = EnumArray<Enum1T, EnumArray<Enum2T, ValueT, Size2V>, Size1V>;
|
|
|
|
enum struct Color : std::uint8_t
|
|
{
|
|
White,
|
|
Black
|
|
};
|
|
|
|
template <>
|
|
struct EnumTraits<Color>
|
|
{
|
|
using IdType = int;
|
|
using EnumType = Color;
|
|
|
|
static constexpr int cardinality = 2;
|
|
static constexpr bool isNaturalIndex = true;
|
|
|
|
static constexpr std::array<EnumType, cardinality> values{
|
|
Color::White,
|
|
Color::Black
|
|
};
|
|
|
|
[[nodiscard]] static constexpr int ordinal(EnumType c) noexcept
|
|
{
|
|
return static_cast<IdType>(c);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr EnumType fromOrdinal(IdType id) noexcept
|
|
{
|
|
assert(id >= 0 && id < cardinality);
|
|
|
|
return static_cast<EnumType>(id);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::string_view toString(EnumType c) noexcept
|
|
{
|
|
return std::string_view("wb" + ordinal(c), 1);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr char toChar(EnumType c) noexcept
|
|
{
|
|
return "wb"[ordinal(c)];
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::optional<Color> fromChar(char c) noexcept
|
|
{
|
|
if (c == 'w') return Color::White;
|
|
if (c == 'b') return Color::Black;
|
|
|
|
return {};
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::optional<Color> fromString(std::string_view sv) noexcept
|
|
{
|
|
if (sv.size() != 1) return {};
|
|
|
|
return fromChar(sv[0]);
|
|
}
|
|
};
|
|
|
|
constexpr Color operator!(Color c)
|
|
{
|
|
return fromOrdinal<Color>(ordinal(c) ^ 1);
|
|
}
|
|
|
|
enum struct PieceType : std::uint8_t
|
|
{
|
|
Pawn,
|
|
Knight,
|
|
Bishop,
|
|
Rook,
|
|
Queen,
|
|
King,
|
|
|
|
None
|
|
};
|
|
|
|
template <>
|
|
struct EnumTraits<PieceType>
|
|
{
|
|
using IdType = int;
|
|
using EnumType = PieceType;
|
|
|
|
static constexpr int cardinality = 7;
|
|
static constexpr bool isNaturalIndex = true;
|
|
|
|
static constexpr std::array<EnumType, cardinality> values{
|
|
PieceType::Pawn,
|
|
PieceType::Knight,
|
|
PieceType::Bishop,
|
|
PieceType::Rook,
|
|
PieceType::Queen,
|
|
PieceType::King,
|
|
PieceType::None
|
|
};
|
|
|
|
[[nodiscard]] static constexpr int ordinal(EnumType c) noexcept
|
|
{
|
|
return static_cast<IdType>(c);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr EnumType fromOrdinal(IdType id) noexcept
|
|
{
|
|
assert(id >= 0 && id < cardinality);
|
|
|
|
return static_cast<EnumType>(id);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::string_view toString(EnumType p, Color c) noexcept
|
|
{
|
|
return std::string_view("PpNnBbRrQqKk " + (chess::ordinal(p) * 2 + chess::ordinal(c)), 1);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr char toChar(EnumType p, Color c) noexcept
|
|
{
|
|
return "PpNnBbRrQqKk "[chess::ordinal(p) * 2 + chess::ordinal(c)];
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::optional<PieceType> fromChar(char c) noexcept
|
|
{
|
|
auto it = std::string_view("PpNnBbRrQqKk ").find(c);
|
|
if (it == std::string::npos) return {};
|
|
else return static_cast<PieceType>(it/2);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::optional<PieceType> fromString(std::string_view sv) noexcept
|
|
{
|
|
if (sv.size() != 1) return {};
|
|
|
|
return fromChar(sv[0]);
|
|
}
|
|
};
|
|
|
|
struct Piece
|
|
{
|
|
[[nodiscard]] static constexpr Piece fromId(int id)
|
|
{
|
|
return Piece(id);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr Piece none()
|
|
{
|
|
return Piece(PieceType::None, Color::White);
|
|
}
|
|
|
|
constexpr Piece() noexcept :
|
|
Piece(PieceType::None, Color::White)
|
|
{
|
|
|
|
}
|
|
|
|
constexpr Piece(PieceType type, Color color) noexcept :
|
|
m_id((ordinal(type) << 1) | ordinal(color))
|
|
{
|
|
assert(type != PieceType::None || color == Color::White);
|
|
}
|
|
|
|
constexpr Piece& operator=(const Piece& other) = default;
|
|
|
|
[[nodiscard]] constexpr friend bool operator==(Piece lhs, Piece rhs) noexcept
|
|
{
|
|
return lhs.m_id == rhs.m_id;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend bool operator!=(Piece lhs, Piece rhs) noexcept
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
|
|
[[nodiscard]] constexpr PieceType type() const
|
|
{
|
|
return fromOrdinal<PieceType>(m_id >> 1);
|
|
}
|
|
|
|
[[nodiscard]] constexpr Color color() const
|
|
{
|
|
return fromOrdinal<Color>(m_id & 1);
|
|
}
|
|
|
|
[[nodiscard]] constexpr std::pair<PieceType, Color> parts() const
|
|
{
|
|
return std::make_pair(type(), color());
|
|
}
|
|
|
|
[[nodiscard]] constexpr explicit operator int() const
|
|
{
|
|
return static_cast<int>(m_id);
|
|
}
|
|
|
|
private:
|
|
constexpr Piece(int id) :
|
|
m_id(id)
|
|
{
|
|
}
|
|
|
|
std::uint8_t m_id; // lowest bit is a color, 7 highest bits are a piece type
|
|
};
|
|
|
|
[[nodiscard]] constexpr Piece operator|(PieceType type, Color color) noexcept
|
|
{
|
|
return Piece(type, color);
|
|
}
|
|
|
|
[[nodiscard]] constexpr Piece operator|(Color color, PieceType type) noexcept
|
|
{
|
|
return Piece(type, color);
|
|
}
|
|
|
|
constexpr Piece whitePawn = Piece(PieceType::Pawn, Color::White);
|
|
constexpr Piece whiteKnight = Piece(PieceType::Knight, Color::White);
|
|
constexpr Piece whiteBishop = Piece(PieceType::Bishop, Color::White);
|
|
constexpr Piece whiteRook = Piece(PieceType::Rook, Color::White);
|
|
constexpr Piece whiteQueen = Piece(PieceType::Queen, Color::White);
|
|
constexpr Piece whiteKing = Piece(PieceType::King, Color::White);
|
|
|
|
constexpr Piece blackPawn = Piece(PieceType::Pawn, Color::Black);
|
|
constexpr Piece blackKnight = Piece(PieceType::Knight, Color::Black);
|
|
constexpr Piece blackBishop = Piece(PieceType::Bishop, Color::Black);
|
|
constexpr Piece blackRook = Piece(PieceType::Rook, Color::Black);
|
|
constexpr Piece blackQueen = Piece(PieceType::Queen, Color::Black);
|
|
constexpr Piece blackKing = Piece(PieceType::King, Color::Black);
|
|
|
|
static_assert(Piece::none().type() == PieceType::None);
|
|
|
|
template <>
|
|
struct EnumTraits<Piece>
|
|
{
|
|
using IdType = int;
|
|
using EnumType = Piece;
|
|
|
|
static constexpr int cardinality = 13;
|
|
static constexpr bool isNaturalIndex = true;
|
|
|
|
static constexpr std::array<EnumType, cardinality> values{
|
|
whitePawn,
|
|
blackPawn,
|
|
whiteKnight,
|
|
blackKnight,
|
|
whiteBishop,
|
|
blackBishop,
|
|
whiteRook,
|
|
blackRook,
|
|
whiteQueen,
|
|
blackQueen,
|
|
whiteKing,
|
|
blackKing,
|
|
Piece::none()
|
|
};
|
|
|
|
[[nodiscard]] static constexpr int ordinal(EnumType c) noexcept
|
|
{
|
|
return static_cast<IdType>(c);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr EnumType fromOrdinal(int id) noexcept
|
|
{
|
|
assert(id >= 0 && id < cardinality);
|
|
|
|
return Piece::fromId(id);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::string_view toString(EnumType p) noexcept
|
|
{
|
|
return std::string_view("PpNnBbRrQqKk " + ordinal(p), 1);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr char toChar(EnumType p) noexcept
|
|
{
|
|
return "PpNnBbRrQqKk "[ordinal(p)];
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::optional<Piece> fromChar(char c) noexcept
|
|
{
|
|
auto it = std::string_view("PpNnBbRrQqKk ").find(c);
|
|
if (it == std::string::npos) return {};
|
|
else return Piece::fromId(static_cast<int>(it));
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::optional<Piece> fromString(std::string_view sv) noexcept
|
|
{
|
|
if (sv.size() != 1) return {};
|
|
|
|
return fromChar(sv[0]);
|
|
}
|
|
};
|
|
|
|
template <typename TagT>
|
|
struct Coord
|
|
{
|
|
constexpr Coord() noexcept :
|
|
m_i(0)
|
|
{
|
|
}
|
|
|
|
constexpr explicit Coord(int i) noexcept :
|
|
m_i(i)
|
|
{
|
|
}
|
|
|
|
[[nodiscard]] constexpr explicit operator int() const
|
|
{
|
|
return static_cast<int>(m_i);
|
|
}
|
|
|
|
constexpr friend Coord& operator++(Coord& c)
|
|
{
|
|
++c.m_i;
|
|
return c;
|
|
}
|
|
|
|
constexpr friend Coord& operator--(Coord& c)
|
|
{
|
|
--c.m_i;
|
|
return c;
|
|
}
|
|
|
|
constexpr friend Coord& operator+=(Coord& c, int d)
|
|
{
|
|
c.m_i += d;
|
|
return c;
|
|
}
|
|
|
|
constexpr friend Coord& operator-=(Coord& c, int d)
|
|
{
|
|
c.m_i -= d;
|
|
return c;
|
|
}
|
|
|
|
constexpr friend Coord operator+(const Coord& c, int d)
|
|
{
|
|
Coord cpy(c);
|
|
cpy += d;
|
|
return cpy;
|
|
}
|
|
|
|
constexpr friend Coord operator-(const Coord& c, int d)
|
|
{
|
|
Coord cpy(c);
|
|
cpy -= d;
|
|
return cpy;
|
|
}
|
|
|
|
constexpr friend int operator-(const Coord& c1, const Coord& c2)
|
|
{
|
|
return c1.m_i - c2.m_i;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend bool operator==(const Coord& c1, const Coord& c2) noexcept
|
|
{
|
|
return c1.m_i == c2.m_i;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend bool operator!=(const Coord& c1, const Coord& c2) noexcept
|
|
{
|
|
return c1.m_i != c2.m_i;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend bool operator<(const Coord& c1, const Coord& c2) noexcept
|
|
{
|
|
return c1.m_i < c2.m_i;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend bool operator<=(const Coord& c1, const Coord& c2) noexcept
|
|
{
|
|
return c1.m_i <= c2.m_i;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend bool operator>(const Coord& c1, const Coord& c2) noexcept
|
|
{
|
|
return c1.m_i > c2.m_i;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend bool operator>=(const Coord& c1, const Coord& c2) noexcept
|
|
{
|
|
return c1.m_i >= c2.m_i;
|
|
}
|
|
|
|
private:
|
|
std::int8_t m_i;
|
|
};
|
|
|
|
struct FileTag;
|
|
struct RankTag;
|
|
using File = Coord<FileTag>;
|
|
using Rank = Coord<RankTag>;
|
|
|
|
constexpr File fileA = File(0);
|
|
constexpr File fileB = File(1);
|
|
constexpr File fileC = File(2);
|
|
constexpr File fileD = File(3);
|
|
constexpr File fileE = File(4);
|
|
constexpr File fileF = File(5);
|
|
constexpr File fileG = File(6);
|
|
constexpr File fileH = File(7);
|
|
|
|
constexpr Rank rank1 = Rank(0);
|
|
constexpr Rank rank2 = Rank(1);
|
|
constexpr Rank rank3 = Rank(2);
|
|
constexpr Rank rank4 = Rank(3);
|
|
constexpr Rank rank5 = Rank(4);
|
|
constexpr Rank rank6 = Rank(5);
|
|
constexpr Rank rank7 = Rank(6);
|
|
constexpr Rank rank8 = Rank(7);
|
|
|
|
template <>
|
|
struct EnumTraits<File>
|
|
{
|
|
using IdType = int;
|
|
using EnumType = File;
|
|
|
|
static constexpr int cardinality = 8;
|
|
static constexpr bool isNaturalIndex = true;
|
|
|
|
[[nodiscard]] static constexpr int ordinal(EnumType c) noexcept
|
|
{
|
|
return static_cast<IdType>(c);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr EnumType fromOrdinal(IdType id) noexcept
|
|
{
|
|
assert(id >= 0 && id < cardinality);
|
|
|
|
return static_cast<EnumType>(id);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::string_view toString(EnumType c) noexcept
|
|
{
|
|
assert(ordinal(c) >= 0 && ordinal(c) < 8);
|
|
|
|
return std::string_view("abcdefgh" + ordinal(c), 1);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::optional<File> fromChar(char c) noexcept
|
|
{
|
|
if (c < 'a' || c > 'h') return {};
|
|
return static_cast<File>(c - 'a');
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::optional<File> fromString(std::string_view sv) noexcept
|
|
{
|
|
if (sv.size() != 1) return {};
|
|
|
|
return fromChar(sv[0]);
|
|
}
|
|
};
|
|
|
|
template <>
|
|
struct EnumTraits<Rank>
|
|
{
|
|
using IdType = int;
|
|
using EnumType = Rank;
|
|
|
|
static constexpr int cardinality = 8;
|
|
static constexpr bool isNaturalIndex = true;
|
|
|
|
[[nodiscard]] static constexpr int ordinal(EnumType c) noexcept
|
|
{
|
|
return static_cast<IdType>(c);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr EnumType fromOrdinal(IdType id) noexcept
|
|
{
|
|
assert(id >= 0 && id < cardinality);
|
|
|
|
return static_cast<EnumType>(id);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::string_view toString(EnumType c) noexcept
|
|
{
|
|
assert(ordinal(c) >= 0 && ordinal(c) < 8);
|
|
|
|
return std::string_view("12345678" + ordinal(c), 1);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::optional<Rank> fromChar(char c) noexcept
|
|
{
|
|
if (c < '1' || c > '8') return {};
|
|
return static_cast<Rank>(c - '1');
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::optional<Rank> fromString(std::string_view sv) noexcept
|
|
{
|
|
if (sv.size() != 1) return {};
|
|
|
|
return fromChar(sv[0]);
|
|
}
|
|
};
|
|
|
|
// files east
|
|
// ranks north
|
|
struct FlatSquareOffset
|
|
{
|
|
std::int8_t value;
|
|
|
|
constexpr FlatSquareOffset() noexcept :
|
|
value(0)
|
|
{
|
|
}
|
|
|
|
constexpr FlatSquareOffset(int files, int ranks) noexcept :
|
|
value(files + ranks * cardinality<File>())
|
|
{
|
|
assert(files + ranks * cardinality<File>() >= std::numeric_limits<std::int8_t>::min());
|
|
assert(files + ranks * cardinality<File>() <= std::numeric_limits<std::int8_t>::max());
|
|
}
|
|
|
|
constexpr FlatSquareOffset operator-() const noexcept
|
|
{
|
|
return FlatSquareOffset(-value);
|
|
}
|
|
|
|
private:
|
|
constexpr FlatSquareOffset(int v) noexcept :
|
|
value(v)
|
|
{
|
|
}
|
|
};
|
|
|
|
struct Offset
|
|
{
|
|
std::int8_t files;
|
|
std::int8_t ranks;
|
|
|
|
constexpr Offset() :
|
|
files(0),
|
|
ranks(0)
|
|
{
|
|
}
|
|
|
|
constexpr Offset(int files_, int ranks_) :
|
|
files(files_),
|
|
ranks(ranks_)
|
|
{
|
|
}
|
|
|
|
[[nodiscard]] constexpr FlatSquareOffset flat() const
|
|
{
|
|
return { files, ranks };
|
|
}
|
|
|
|
[[nodiscard]] constexpr Offset operator-() const
|
|
{
|
|
return { -files, -ranks };
|
|
}
|
|
};
|
|
|
|
struct SquareCoords
|
|
{
|
|
File file;
|
|
Rank rank;
|
|
|
|
constexpr SquareCoords() noexcept :
|
|
file{},
|
|
rank{}
|
|
{
|
|
}
|
|
|
|
constexpr SquareCoords(File f, Rank r) noexcept :
|
|
file(f),
|
|
rank(r)
|
|
{
|
|
}
|
|
|
|
constexpr friend SquareCoords& operator+=(SquareCoords& c, Offset offset)
|
|
{
|
|
c.file += offset.files;
|
|
c.rank += offset.ranks;
|
|
return c;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend SquareCoords operator+(const SquareCoords& c, Offset offset)
|
|
{
|
|
SquareCoords cpy(c);
|
|
cpy.file += offset.files;
|
|
cpy.rank += offset.ranks;
|
|
return cpy;
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool isOk() const
|
|
{
|
|
return file >= fileA && file <= fileH && rank >= rank1 && rank <= rank8;
|
|
}
|
|
};
|
|
|
|
struct Square
|
|
{
|
|
private:
|
|
static constexpr std::int8_t m_noneId = cardinality<Rank>() * cardinality<File>();
|
|
|
|
static constexpr std::uint8_t fileMask = 0b111;
|
|
static constexpr std::uint8_t rankMask = 0b111000;
|
|
static constexpr std::uint8_t rankShift = 3;
|
|
|
|
public:
|
|
[[nodiscard]] static constexpr Square none()
|
|
{
|
|
return Square(m_noneId);
|
|
}
|
|
|
|
constexpr Square() noexcept :
|
|
m_id(0)
|
|
{
|
|
}
|
|
|
|
constexpr explicit Square(int idx) noexcept :
|
|
m_id(idx)
|
|
{
|
|
assert(isOk() || m_id == m_noneId);
|
|
}
|
|
|
|
constexpr Square(File file, Rank rank) noexcept :
|
|
m_id(ordinal(file) + ordinal(rank) * cardinality<File>())
|
|
{
|
|
assert(isOk());
|
|
}
|
|
|
|
constexpr explicit Square(SquareCoords coords) noexcept :
|
|
Square(coords.file, coords.rank)
|
|
{
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend bool operator<(Square lhs, Square rhs) noexcept
|
|
{
|
|
return lhs.m_id < rhs.m_id;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend bool operator>(Square lhs, Square rhs) noexcept
|
|
{
|
|
return lhs.m_id > rhs.m_id;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend bool operator<=(Square lhs, Square rhs) noexcept
|
|
{
|
|
return lhs.m_id <= rhs.m_id;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend bool operator>=(Square lhs, Square rhs) noexcept
|
|
{
|
|
return lhs.m_id >= rhs.m_id;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend bool operator==(Square lhs, Square rhs) noexcept
|
|
{
|
|
return lhs.m_id == rhs.m_id;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend bool operator!=(Square lhs, Square rhs) noexcept
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
|
|
constexpr friend Square& operator++(Square& sq)
|
|
{
|
|
++sq.m_id;
|
|
return sq;
|
|
}
|
|
|
|
constexpr friend Square& operator--(Square& sq)
|
|
{
|
|
--sq.m_id;
|
|
return sq;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend Square operator+(Square sq, FlatSquareOffset offset)
|
|
{
|
|
Square sqCpy = sq;
|
|
sqCpy += offset;
|
|
return sqCpy;
|
|
}
|
|
|
|
constexpr friend Square& operator+=(Square& sq, FlatSquareOffset offset)
|
|
{
|
|
assert(sq.m_id + offset.value >= 0 && sq.m_id + offset.value < Square::m_noneId);
|
|
sq.m_id += offset.value;
|
|
return sq;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend Square operator+(Square sq, Offset offset)
|
|
{
|
|
assert(sq.file() + offset.files >= fileA);
|
|
assert(sq.file() + offset.files <= fileH);
|
|
assert(sq.rank() + offset.ranks >= rank1);
|
|
assert(sq.rank() + offset.ranks <= rank8);
|
|
return operator+(sq, offset.flat());
|
|
}
|
|
|
|
constexpr friend Square& operator+=(Square& sq, Offset offset)
|
|
{
|
|
return operator+=(sq, offset.flat());
|
|
}
|
|
|
|
[[nodiscard]] constexpr explicit operator int() const
|
|
{
|
|
return m_id;
|
|
}
|
|
|
|
[[nodiscard]] constexpr File file() const
|
|
{
|
|
assert(isOk());
|
|
return File(static_cast<unsigned>(m_id) & fileMask);
|
|
}
|
|
|
|
[[nodiscard]] constexpr Rank rank() const
|
|
{
|
|
assert(isOk());
|
|
return Rank(static_cast<unsigned>(m_id) >> rankShift);
|
|
}
|
|
|
|
[[nodiscard]] constexpr SquareCoords coords() const
|
|
{
|
|
return { file(), rank() };
|
|
}
|
|
|
|
[[nodiscard]] constexpr Color color() const
|
|
{
|
|
assert(isOk());
|
|
return !fromOrdinal<Color>((ordinal(rank()) + ordinal(file())) & 1);
|
|
}
|
|
|
|
constexpr void flipVertically()
|
|
{
|
|
m_id ^= rankMask;
|
|
}
|
|
|
|
constexpr void flipHorizontally()
|
|
{
|
|
m_id ^= fileMask;
|
|
}
|
|
|
|
constexpr Square flippedVertically() const
|
|
{
|
|
return Square(m_id ^ rankMask);
|
|
}
|
|
|
|
constexpr Square flippedHorizontally() const
|
|
{
|
|
return Square(m_id ^ fileMask);
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool isOk() const
|
|
{
|
|
return m_id >= 0 && m_id < m_noneId;
|
|
}
|
|
|
|
private:
|
|
std::int8_t m_id;
|
|
};
|
|
|
|
constexpr Square a1(fileA, rank1);
|
|
constexpr Square a2(fileA, rank2);
|
|
constexpr Square a3(fileA, rank3);
|
|
constexpr Square a4(fileA, rank4);
|
|
constexpr Square a5(fileA, rank5);
|
|
constexpr Square a6(fileA, rank6);
|
|
constexpr Square a7(fileA, rank7);
|
|
constexpr Square a8(fileA, rank8);
|
|
|
|
constexpr Square b1(fileB, rank1);
|
|
constexpr Square b2(fileB, rank2);
|
|
constexpr Square b3(fileB, rank3);
|
|
constexpr Square b4(fileB, rank4);
|
|
constexpr Square b5(fileB, rank5);
|
|
constexpr Square b6(fileB, rank6);
|
|
constexpr Square b7(fileB, rank7);
|
|
constexpr Square b8(fileB, rank8);
|
|
|
|
constexpr Square c1(fileC, rank1);
|
|
constexpr Square c2(fileC, rank2);
|
|
constexpr Square c3(fileC, rank3);
|
|
constexpr Square c4(fileC, rank4);
|
|
constexpr Square c5(fileC, rank5);
|
|
constexpr Square c6(fileC, rank6);
|
|
constexpr Square c7(fileC, rank7);
|
|
constexpr Square c8(fileC, rank8);
|
|
|
|
constexpr Square d1(fileD, rank1);
|
|
constexpr Square d2(fileD, rank2);
|
|
constexpr Square d3(fileD, rank3);
|
|
constexpr Square d4(fileD, rank4);
|
|
constexpr Square d5(fileD, rank5);
|
|
constexpr Square d6(fileD, rank6);
|
|
constexpr Square d7(fileD, rank7);
|
|
constexpr Square d8(fileD, rank8);
|
|
|
|
constexpr Square e1(fileE, rank1);
|
|
constexpr Square e2(fileE, rank2);
|
|
constexpr Square e3(fileE, rank3);
|
|
constexpr Square e4(fileE, rank4);
|
|
constexpr Square e5(fileE, rank5);
|
|
constexpr Square e6(fileE, rank6);
|
|
constexpr Square e7(fileE, rank7);
|
|
constexpr Square e8(fileE, rank8);
|
|
|
|
constexpr Square f1(fileF, rank1);
|
|
constexpr Square f2(fileF, rank2);
|
|
constexpr Square f3(fileF, rank3);
|
|
constexpr Square f4(fileF, rank4);
|
|
constexpr Square f5(fileF, rank5);
|
|
constexpr Square f6(fileF, rank6);
|
|
constexpr Square f7(fileF, rank7);
|
|
constexpr Square f8(fileF, rank8);
|
|
|
|
constexpr Square g1(fileG, rank1);
|
|
constexpr Square g2(fileG, rank2);
|
|
constexpr Square g3(fileG, rank3);
|
|
constexpr Square g4(fileG, rank4);
|
|
constexpr Square g5(fileG, rank5);
|
|
constexpr Square g6(fileG, rank6);
|
|
constexpr Square g7(fileG, rank7);
|
|
constexpr Square g8(fileG, rank8);
|
|
|
|
constexpr Square h1(fileH, rank1);
|
|
constexpr Square h2(fileH, rank2);
|
|
constexpr Square h3(fileH, rank3);
|
|
constexpr Square h4(fileH, rank4);
|
|
constexpr Square h5(fileH, rank5);
|
|
constexpr Square h6(fileH, rank6);
|
|
constexpr Square h7(fileH, rank7);
|
|
constexpr Square h8(fileH, rank8);
|
|
|
|
static_assert(e1.color() == Color::Black);
|
|
static_assert(e8.color() == Color::White);
|
|
|
|
static_assert(e1.file() == fileE);
|
|
static_assert(e1.rank() == rank1);
|
|
|
|
static_assert(e1.flippedHorizontally() == d1);
|
|
static_assert(e1.flippedVertically() == e8);
|
|
|
|
template <>
|
|
struct EnumTraits<Square>
|
|
{
|
|
using IdType = int;
|
|
using EnumType = Square;
|
|
|
|
static constexpr int cardinality = chess::cardinality<Rank>() * chess::cardinality<File>();
|
|
static constexpr bool isNaturalIndex = true;
|
|
|
|
static constexpr std::array<EnumType, cardinality> values{
|
|
a1, b1, c1, d1, e1, f1, g1, h1,
|
|
a2, b2, c2, d2, e2, f2, g2, h2,
|
|
a3, b3, c3, d3, e3, f3, g3, h3,
|
|
a4, b4, c4, d4, e4, f4, g4, h4,
|
|
a5, b5, c5, d5, e5, f5, g5, h5,
|
|
a6, b6, c6, d6, e6, f6, g6, h6,
|
|
a7, b7, c7, d7, e7, f7, g7, h7,
|
|
a8, b8, c8, d8, e8, f8, g8, h8
|
|
};
|
|
|
|
[[nodiscard]] static constexpr int ordinal(EnumType c) noexcept
|
|
{
|
|
return static_cast<IdType>(c);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr EnumType fromOrdinal(IdType id) noexcept
|
|
{
|
|
assert(id >= 0 && id < cardinality + 1);
|
|
|
|
return static_cast<EnumType>(id);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::string_view toString(Square sq)
|
|
{
|
|
assert(sq.isOk());
|
|
|
|
return
|
|
std::string_view(
|
|
"a1b1c1d1e1f1g1h1"
|
|
"a2b2c2d2e2f2g2h2"
|
|
"a3b3c3d3e3f3g3h3"
|
|
"a4b4c4d4e4f4g4h4"
|
|
"a5b5c5d5e5f5g5h5"
|
|
"a6b6c6d6e6f6g6h6"
|
|
"a7b7c7d7e7f7g7h7"
|
|
"a8b8c8d8e8f8g8h8"
|
|
+ (ordinal(sq) * 2),
|
|
2
|
|
);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr std::optional<Square> fromString(std::string_view sv) noexcept
|
|
{
|
|
if (sv.size() != 2) return {};
|
|
|
|
const char f = sv[0];
|
|
const char r = sv[1];
|
|
if (f < 'a' || f > 'h') return {};
|
|
if (r < '1' || r > '8') return {};
|
|
|
|
return Square(static_cast<File>(f - 'a'), static_cast<Rank>(r - '1'));
|
|
}
|
|
};
|
|
|
|
static_assert(toString(d1) == std::string_view("d1"));
|
|
static_assert(values<Square>()[29] == f4);
|
|
|
|
enum struct MoveType : std::uint8_t
|
|
{
|
|
Normal,
|
|
Promotion,
|
|
Castle,
|
|
EnPassant
|
|
};
|
|
|
|
template <>
|
|
struct EnumTraits<MoveType>
|
|
{
|
|
using IdType = int;
|
|
using EnumType = MoveType;
|
|
|
|
static constexpr int cardinality = 4;
|
|
static constexpr bool isNaturalIndex = true;
|
|
|
|
static constexpr std::array<EnumType, cardinality> values{
|
|
MoveType::Normal,
|
|
MoveType::Promotion,
|
|
MoveType::Castle,
|
|
MoveType::EnPassant
|
|
};
|
|
|
|
[[nodiscard]] static constexpr int ordinal(EnumType c) noexcept
|
|
{
|
|
return static_cast<IdType>(c);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr EnumType fromOrdinal(IdType id) noexcept
|
|
{
|
|
assert(id >= 0 && id < cardinality);
|
|
|
|
return static_cast<EnumType>(id);
|
|
}
|
|
};
|
|
|
|
enum struct CastleType : std::uint8_t
|
|
{
|
|
Short,
|
|
Long
|
|
};
|
|
|
|
[[nodiscard]] constexpr CastleType operator!(CastleType ct)
|
|
{
|
|
return static_cast<CastleType>(static_cast<std::uint8_t>(ct) ^ 1);
|
|
}
|
|
|
|
template <>
|
|
struct EnumTraits<CastleType>
|
|
{
|
|
using IdType = int;
|
|
using EnumType = CastleType;
|
|
|
|
static constexpr int cardinality = 2;
|
|
static constexpr bool isNaturalIndex = true;
|
|
|
|
static constexpr std::array<EnumType, cardinality> values{
|
|
CastleType::Short,
|
|
CastleType::Long
|
|
};
|
|
|
|
[[nodiscard]] static constexpr int ordinal(EnumType c) noexcept
|
|
{
|
|
return static_cast<IdType>(c);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr EnumType fromOrdinal(IdType id) noexcept
|
|
{
|
|
assert(id >= 0 && id < cardinality);
|
|
|
|
return static_cast<EnumType>(id);
|
|
}
|
|
};
|
|
|
|
struct CompressedMove;
|
|
|
|
// castling is encoded as a king capturing rook
|
|
// ep is encoded as a normal pawn capture (move.to is empty on the board)
|
|
struct Move
|
|
{
|
|
Square from;
|
|
Square to;
|
|
MoveType type = MoveType::Normal;
|
|
Piece promotedPiece = Piece::none();
|
|
|
|
[[nodiscard]] constexpr friend bool operator==(const Move& lhs, const Move& rhs) noexcept
|
|
{
|
|
return lhs.from == rhs.from
|
|
&& lhs.to == rhs.to
|
|
&& lhs.type == rhs.type
|
|
&& lhs.promotedPiece == rhs.promotedPiece;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend bool operator!=(const Move& lhs, const Move& rhs) noexcept
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
|
|
[[nodiscard]] constexpr CompressedMove compress() const noexcept;
|
|
|
|
[[nodiscard]] constexpr static Move null()
|
|
{
|
|
return Move{ Square::none(), Square::none() };
|
|
}
|
|
|
|
[[nodiscard]] constexpr static Move castle(CastleType ct, Color c);
|
|
|
|
[[nodiscard]] constexpr static Move normal(Square from, Square to)
|
|
{
|
|
return Move{ from, to, MoveType::Normal, Piece::none() };
|
|
}
|
|
|
|
[[nodiscard]] constexpr static Move enPassant(Square from, Square to)
|
|
{
|
|
return Move{ from, to, MoveType::EnPassant, Piece::none() };
|
|
}
|
|
|
|
[[nodiscard]] constexpr static Move promotion(Square from, Square to, Piece piece)
|
|
{
|
|
return Move{ from, to, MoveType::Promotion, piece };
|
|
}
|
|
};
|
|
|
|
namespace detail::castle
|
|
{
|
|
constexpr EnumArray2<CastleType, Color, Move> moves = { {
|
|
{{ { e1, h1, MoveType::Castle }, { e8, h8, MoveType::Castle } }},
|
|
{{ { e1, a1, MoveType::Castle }, { e8, a8, MoveType::Castle } }}
|
|
} };
|
|
}
|
|
|
|
[[nodiscard]] constexpr Move Move::castle(CastleType ct, Color c)
|
|
{
|
|
return detail::castle::moves[ct][c];
|
|
}
|
|
|
|
static_assert(sizeof(Move) == 4);
|
|
|
|
struct CompressedMove
|
|
{
|
|
private:
|
|
// from most significant bits
|
|
// 2 bits for move type
|
|
// 6 bits for from square
|
|
// 6 bits for to square
|
|
// 2 bits for promoted piece type
|
|
// 0 if not a promotion
|
|
static constexpr std::uint16_t squareMask = 0b111111u;
|
|
static constexpr std::uint16_t promotedPieceTypeMask = 0b11u;
|
|
static constexpr std::uint16_t moveTypeMask = 0b11u;
|
|
|
|
public:
|
|
[[nodiscard]] constexpr static CompressedMove readFromBigEndian(const unsigned char* data)
|
|
{
|
|
CompressedMove move{};
|
|
move.m_packed = (data[0] << 8) | data[1];
|
|
return move;
|
|
}
|
|
|
|
constexpr CompressedMove() noexcept :
|
|
m_packed(0)
|
|
{
|
|
}
|
|
|
|
// move must be either valid or a null move
|
|
constexpr CompressedMove(Move move) noexcept :
|
|
m_packed(0)
|
|
{
|
|
// else null move
|
|
if (move.from != move.to)
|
|
{
|
|
assert(move.from != Square::none());
|
|
assert(move.to != Square::none());
|
|
|
|
m_packed =
|
|
(static_cast<std::uint16_t>(ordinal(move.type)) << (16 - 2))
|
|
| (static_cast<std::uint16_t>(ordinal(move.from)) << (16 - 2 - 6))
|
|
| (static_cast<std::uint16_t>(ordinal(move.to)) << (16 - 2 - 6 - 6));
|
|
|
|
if (move.type == MoveType::Promotion)
|
|
{
|
|
assert(move.promotedPiece != Piece::none());
|
|
|
|
m_packed |= ordinal(move.promotedPiece.type()) - ordinal(PieceType::Knight);
|
|
}
|
|
else
|
|
{
|
|
assert(move.promotedPiece == Piece::none());
|
|
}
|
|
}
|
|
}
|
|
|
|
void writeToBigEndian(unsigned char* data) const
|
|
{
|
|
*data++ = m_packed >> 8;
|
|
*data++ = m_packed & 0xFF;
|
|
}
|
|
|
|
[[nodiscard]] constexpr std::uint16_t packed() const
|
|
{
|
|
return m_packed;
|
|
}
|
|
|
|
[[nodiscard]] constexpr MoveType type() const
|
|
{
|
|
return fromOrdinal<MoveType>(m_packed >> (16 - 2));
|
|
}
|
|
|
|
[[nodiscard]] constexpr Square from() const
|
|
{
|
|
return fromOrdinal<Square>((m_packed >> (16 - 2 - 6)) & squareMask);
|
|
}
|
|
|
|
[[nodiscard]] constexpr Square to() const
|
|
{
|
|
return fromOrdinal<Square>((m_packed >> (16 - 2 - 6 - 6)) & squareMask);
|
|
}
|
|
|
|
[[nodiscard]] constexpr Piece promotedPiece() const
|
|
{
|
|
if (type() == MoveType::Promotion)
|
|
{
|
|
const Color color =
|
|
(to().rank() == rank1)
|
|
? Color::Black
|
|
: Color::White;
|
|
|
|
const PieceType pt = fromOrdinal<PieceType>((m_packed & promotedPieceTypeMask) + ordinal(PieceType::Knight));
|
|
return color | pt;
|
|
}
|
|
else
|
|
{
|
|
return Piece::none();
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] constexpr Move decompress() const noexcept
|
|
{
|
|
if (m_packed == 0)
|
|
{
|
|
return Move::null();
|
|
}
|
|
else
|
|
{
|
|
const MoveType type = fromOrdinal<MoveType>(m_packed >> (16 - 2));
|
|
const Square from = fromOrdinal<Square>((m_packed >> (16 - 2 - 6)) & squareMask);
|
|
const Square to = fromOrdinal<Square>((m_packed >> (16 - 2 - 6 - 6)) & squareMask);
|
|
const Piece promotedPiece = [&]() {
|
|
if (type == MoveType::Promotion)
|
|
{
|
|
const Color color =
|
|
(to.rank() == rank1)
|
|
? Color::Black
|
|
: Color::White;
|
|
|
|
const PieceType pt = fromOrdinal<PieceType>((m_packed & promotedPieceTypeMask) + ordinal(PieceType::Knight));
|
|
return color | pt;
|
|
}
|
|
else
|
|
{
|
|
return Piece::none();
|
|
}
|
|
}();
|
|
|
|
return Move{ from, to, type, promotedPiece };
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::uint16_t m_packed;
|
|
};
|
|
|
|
static_assert(sizeof(CompressedMove) == 2);
|
|
|
|
[[nodiscard]] constexpr CompressedMove Move::compress() const noexcept
|
|
{
|
|
return CompressedMove(*this);
|
|
}
|
|
|
|
static_assert(a4 + Offset{ 0, 1 } == a5);
|
|
static_assert(a4 + Offset{ 0, 2 } == a6);
|
|
static_assert(a4 + Offset{ 0, -2 } == a2);
|
|
static_assert(a4 + Offset{ 0, -1 } == a3);
|
|
|
|
static_assert(e4 + Offset{ 1, 0 } == f4);
|
|
static_assert(e4 + Offset{ 2, 0 } == g4);
|
|
static_assert(e4 + Offset{ -1, 0 } == d4);
|
|
static_assert(e4 + Offset{ -2, 0 } == c4);
|
|
|
|
enum struct CastlingRights : std::uint8_t
|
|
{
|
|
None = 0x0,
|
|
WhiteKingSide = 0x1,
|
|
WhiteQueenSide = 0x2,
|
|
BlackKingSide = 0x4,
|
|
BlackQueenSide = 0x8,
|
|
White = WhiteKingSide | WhiteQueenSide,
|
|
Black = BlackKingSide | BlackQueenSide,
|
|
All = WhiteKingSide | WhiteQueenSide | BlackKingSide | BlackQueenSide
|
|
};
|
|
|
|
[[nodiscard]] constexpr CastlingRights operator|(CastlingRights lhs, CastlingRights rhs)
|
|
{
|
|
return static_cast<CastlingRights>(static_cast<std::uint8_t>(lhs) | static_cast<std::uint8_t>(rhs));
|
|
}
|
|
|
|
[[nodiscard]] constexpr CastlingRights operator&(CastlingRights lhs, CastlingRights rhs)
|
|
{
|
|
return static_cast<CastlingRights>(static_cast<std::uint8_t>(lhs) & static_cast<std::uint8_t>(rhs));
|
|
}
|
|
|
|
[[nodiscard]] constexpr CastlingRights operator~(CastlingRights lhs)
|
|
{
|
|
return static_cast<CastlingRights>(~static_cast<std::uint8_t>(lhs) & static_cast<std::uint8_t>(CastlingRights::All));
|
|
}
|
|
|
|
constexpr CastlingRights& operator|=(CastlingRights& lhs, CastlingRights rhs)
|
|
{
|
|
lhs = static_cast<CastlingRights>(static_cast<std::uint8_t>(lhs) | static_cast<std::uint8_t>(rhs));
|
|
return lhs;
|
|
}
|
|
|
|
constexpr CastlingRights& operator&=(CastlingRights& lhs, CastlingRights rhs)
|
|
{
|
|
lhs = static_cast<CastlingRights>(static_cast<std::uint8_t>(lhs) & static_cast<std::uint8_t>(rhs));
|
|
return lhs;
|
|
}
|
|
// checks whether lhs contains rhs
|
|
[[nodiscard]] constexpr bool contains(CastlingRights lhs, CastlingRights rhs)
|
|
{
|
|
return (lhs & rhs) == rhs;
|
|
}
|
|
|
|
template <>
|
|
struct EnumTraits<CastlingRights>
|
|
{
|
|
using IdType = int;
|
|
using EnumType = CastlingRights;
|
|
|
|
static constexpr int cardinality = 4;
|
|
static constexpr bool isNaturalIndex = false;
|
|
|
|
static constexpr std::array<EnumType, cardinality> values{
|
|
CastlingRights::WhiteKingSide,
|
|
CastlingRights::WhiteQueenSide,
|
|
CastlingRights::BlackKingSide,
|
|
CastlingRights::BlackQueenSide
|
|
};
|
|
|
|
[[nodiscard]] static constexpr int ordinal(EnumType c) noexcept
|
|
{
|
|
return static_cast<IdType>(c);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr EnumType fromOrdinal(IdType id) noexcept
|
|
{
|
|
return static_cast<EnumType>(id);
|
|
}
|
|
};
|
|
|
|
struct CompressedReverseMove;
|
|
|
|
struct ReverseMove
|
|
{
|
|
Move move;
|
|
Piece capturedPiece;
|
|
Square oldEpSquare;
|
|
CastlingRights oldCastlingRights;
|
|
|
|
// We need a well defined case for the starting position.
|
|
constexpr ReverseMove() :
|
|
move(Move::null()),
|
|
capturedPiece(Piece::none()),
|
|
oldEpSquare(Square::none()),
|
|
oldCastlingRights(CastlingRights::All)
|
|
{
|
|
}
|
|
|
|
constexpr ReverseMove(const Move& move_, Piece capturedPiece_, Square oldEpSquare_, CastlingRights oldCastlingRights_) :
|
|
move(move_),
|
|
capturedPiece(capturedPiece_),
|
|
oldEpSquare(oldEpSquare_),
|
|
oldCastlingRights(oldCastlingRights_)
|
|
{
|
|
}
|
|
|
|
constexpr bool isNull() const
|
|
{
|
|
return move.from == move.to;
|
|
}
|
|
|
|
[[nodiscard]] constexpr CompressedReverseMove compress() const noexcept;
|
|
|
|
[[nodiscard]] constexpr friend bool operator==(const ReverseMove& lhs, const ReverseMove& rhs) noexcept
|
|
{
|
|
return lhs.move == rhs.move
|
|
&& lhs.capturedPiece == rhs.capturedPiece
|
|
&& lhs.oldEpSquare == rhs.oldEpSquare
|
|
&& lhs.oldCastlingRights == rhs.oldCastlingRights;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend bool operator!=(const ReverseMove& lhs, const ReverseMove& rhs) noexcept
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
};
|
|
|
|
static_assert(sizeof(ReverseMove) == 7);
|
|
|
|
struct CompressedReverseMove
|
|
{
|
|
private:
|
|
// we use 7 bits because it can be Square::none()
|
|
static constexpr std::uint32_t squareMask = 0b1111111u;
|
|
static constexpr std::uint32_t pieceMask = 0b1111u;
|
|
static constexpr std::uint32_t castlingRightsMask = 0b1111;
|
|
public:
|
|
|
|
constexpr CompressedReverseMove() noexcept :
|
|
m_move{},
|
|
m_oldState{}
|
|
{
|
|
}
|
|
|
|
constexpr CompressedReverseMove(const ReverseMove& rm) noexcept :
|
|
m_move(rm.move.compress()),
|
|
m_oldState{ static_cast<uint16_t>(
|
|
((ordinal(rm.capturedPiece) & pieceMask) << 11)
|
|
| ((ordinal(rm.oldCastlingRights) & castlingRightsMask) << 7)
|
|
| (ordinal(rm.oldEpSquare) & squareMask)
|
|
)
|
|
}
|
|
{
|
|
}
|
|
|
|
[[nodiscard]] constexpr Move move() const
|
|
{
|
|
return m_move.decompress();
|
|
}
|
|
|
|
[[nodiscard]] const CompressedMove& compressedMove() const
|
|
{
|
|
return m_move;
|
|
}
|
|
|
|
[[nodiscard]] constexpr Piece capturedPiece() const
|
|
{
|
|
return fromOrdinal<Piece>(m_oldState >> 11);
|
|
}
|
|
|
|
[[nodiscard]] constexpr CastlingRights oldCastlingRights() const
|
|
{
|
|
return fromOrdinal<CastlingRights>((m_oldState >> 7) & castlingRightsMask);
|
|
}
|
|
|
|
[[nodiscard]] constexpr Square oldEpSquare() const
|
|
{
|
|
return fromOrdinal<Square>(m_oldState & squareMask);
|
|
}
|
|
|
|
[[nodiscard]] constexpr ReverseMove decompress() const noexcept
|
|
{
|
|
const Piece capturedPiece = fromOrdinal<Piece>(m_oldState >> 11);
|
|
const CastlingRights castlingRights = fromOrdinal<CastlingRights>((m_oldState >> 7) & castlingRightsMask);
|
|
// We could pack the ep square more, but don't have to, because
|
|
// can't save another byte anyway.
|
|
const Square epSquare = fromOrdinal<Square>(m_oldState & squareMask);
|
|
|
|
return ReverseMove(m_move.decompress(), capturedPiece, epSquare, castlingRights);
|
|
}
|
|
|
|
private:
|
|
CompressedMove m_move;
|
|
std::uint16_t m_oldState;
|
|
};
|
|
|
|
static_assert(sizeof(CompressedReverseMove) == 4);
|
|
|
|
[[nodiscard]] constexpr CompressedReverseMove ReverseMove::compress() const noexcept
|
|
{
|
|
return CompressedReverseMove(*this);
|
|
}
|
|
|
|
// This can be regarded as a perfect hash. Going back is hard.
|
|
struct PackedReverseMove
|
|
{
|
|
static constexpr std::uint32_t mask = 0x7FFFFFFu;
|
|
static constexpr std::size_t numBits = 27;
|
|
|
|
private:
|
|
static constexpr std::uint32_t squareMask = 0b111111u;
|
|
static constexpr std::uint32_t pieceMask = 0b1111u;
|
|
static constexpr std::uint32_t pieceTypeMask = 0b111u;
|
|
static constexpr std::uint32_t castlingRightsMask = 0b1111;
|
|
static constexpr std::uint32_t fileMask = 0b111;
|
|
|
|
public:
|
|
constexpr PackedReverseMove(const std::uint32_t packed) :
|
|
m_packed(packed)
|
|
{
|
|
|
|
}
|
|
|
|
constexpr PackedReverseMove(const ReverseMove& reverseMove) :
|
|
m_packed(
|
|
0u
|
|
// The only move when square is none() is null move and
|
|
// then both squares are none(). No other move is like that
|
|
// so we don't lose any information by storing only
|
|
// the 6 bits of each square.
|
|
| ((ordinal(reverseMove.move.from) & squareMask) << 21)
|
|
| ((ordinal(reverseMove.move.to) & squareMask) << 15)
|
|
// Other masks are just for code clarity, they should
|
|
// never change the values.
|
|
| ((ordinal(reverseMove.capturedPiece) & pieceMask) << 11)
|
|
| ((ordinal(reverseMove.oldCastlingRights) & castlingRightsMask) << 7)
|
|
| ((ordinal(reverseMove.move.promotedPiece.type()) & pieceTypeMask) << 4)
|
|
| (((reverseMove.oldEpSquare != Square::none()) & 1) << 3)
|
|
// We probably could omit the squareMask here but for clarity it's left.
|
|
| (ordinal(Square(ordinal(reverseMove.oldEpSquare) & squareMask).file()) & fileMask)
|
|
)
|
|
{
|
|
}
|
|
|
|
constexpr std::uint32_t packed() const
|
|
{
|
|
return m_packed;
|
|
}
|
|
|
|
constexpr ReverseMove unpack(Color sideThatMoved) const
|
|
{
|
|
ReverseMove rmove{};
|
|
|
|
rmove.move.from = fromOrdinal<Square>((m_packed >> 21) & squareMask);
|
|
rmove.move.to = fromOrdinal<Square>((m_packed >> 15) & squareMask);
|
|
rmove.capturedPiece = fromOrdinal<Piece>((m_packed >> 11) & pieceMask);
|
|
rmove.oldCastlingRights = fromOrdinal<CastlingRights>((m_packed >> 7) & castlingRightsMask);
|
|
const PieceType promotedPieceType = fromOrdinal<PieceType>((m_packed >> 4) & pieceTypeMask);
|
|
if (promotedPieceType != PieceType::None)
|
|
{
|
|
rmove.move.promotedPiece = Piece(promotedPieceType, sideThatMoved);
|
|
rmove.move.type = MoveType::Promotion;
|
|
}
|
|
const bool hasEpSquare = static_cast<bool>((m_packed >> 3) & 1);
|
|
if (hasEpSquare)
|
|
{
|
|
// ep square is always where the opponent moved
|
|
const Rank rank =
|
|
sideThatMoved == Color::White
|
|
? rank6
|
|
: rank3;
|
|
const File file = fromOrdinal<File>(m_packed & fileMask);
|
|
rmove.oldEpSquare = Square(file, rank);
|
|
if (rmove.oldEpSquare == rmove.move.to)
|
|
{
|
|
rmove.move.type = MoveType::EnPassant;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
rmove.oldEpSquare = Square::none();
|
|
}
|
|
|
|
if (rmove.move.type == MoveType::Normal && rmove.oldCastlingRights != CastlingRights::None)
|
|
{
|
|
// If castling was possible then we know it was the king that moved from e1/e8.
|
|
if (rmove.move.from == e1)
|
|
{
|
|
if (rmove.move.to == h1 || rmove.move.to == a1)
|
|
{
|
|
rmove.move.type = MoveType::Castle;
|
|
}
|
|
}
|
|
else if (rmove.move.from == e8)
|
|
{
|
|
if (rmove.move.to == h8 || rmove.move.to == a8)
|
|
{
|
|
rmove.move.type = MoveType::Castle;
|
|
}
|
|
}
|
|
}
|
|
|
|
return rmove;
|
|
}
|
|
|
|
private:
|
|
// Uses only 27 lowest bits.
|
|
// Bit meaning from highest to lowest.
|
|
// - 6 bits from
|
|
// - 6 bits to
|
|
// - 4 bits for the captured piece
|
|
// - 4 bits for prev castling rights
|
|
// - 3 bits promoted piece type
|
|
// - 1 bit to specify if the ep square was valid (false if none())
|
|
// - 3 bits for prev ep square file
|
|
std::uint32_t m_packed;
|
|
};
|
|
|
|
struct MoveCompareLess
|
|
{
|
|
[[nodiscard]] bool operator()(const Move& lhs, const Move& rhs) const noexcept
|
|
{
|
|
if (ordinal(lhs.from) < ordinal(rhs.from)) return true;
|
|
if (ordinal(lhs.from) > ordinal(rhs.from)) return false;
|
|
|
|
if (ordinal(lhs.to) < ordinal(rhs.to)) return true;
|
|
if (ordinal(lhs.to) > ordinal(rhs.to)) return false;
|
|
|
|
if (ordinal(lhs.type) < ordinal(rhs.type)) return true;
|
|
if (ordinal(lhs.type) > ordinal(rhs.type)) return false;
|
|
|
|
if (ordinal(lhs.promotedPiece) < ordinal(rhs.promotedPiece)) return true;
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
struct ReverseMoveCompareLess
|
|
{
|
|
[[nodiscard]] bool operator()(const ReverseMove& lhs, const ReverseMove& rhs) const noexcept
|
|
{
|
|
if (MoveCompareLess{}(lhs.move, rhs.move)) return true;
|
|
if (MoveCompareLess{}(rhs.move, lhs.move)) return false;
|
|
|
|
if (ordinal(lhs.capturedPiece) < ordinal(rhs.capturedPiece)) return true;
|
|
if (ordinal(lhs.capturedPiece) > ordinal(rhs.capturedPiece)) return false;
|
|
|
|
if (static_cast<unsigned>(lhs.oldCastlingRights) < static_cast<unsigned>(rhs.oldCastlingRights)) return true;
|
|
if (static_cast<unsigned>(lhs.oldCastlingRights) > static_cast<unsigned>(rhs.oldCastlingRights)) return false;
|
|
|
|
if (ordinal(lhs.oldEpSquare) < ordinal(rhs.oldEpSquare)) return true;
|
|
if (ordinal(lhs.oldEpSquare) > ordinal(rhs.oldEpSquare)) return false;
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
struct BitboardIterator
|
|
{
|
|
using value_type = Square;
|
|
using difference_type = std::ptrdiff_t;
|
|
using reference = Square;
|
|
using iterator_category = std::input_iterator_tag;
|
|
using pointer = const Square*;
|
|
|
|
constexpr BitboardIterator() noexcept :
|
|
m_squares(0)
|
|
{
|
|
}
|
|
|
|
constexpr BitboardIterator(std::uint64_t v) noexcept :
|
|
m_squares(v)
|
|
{
|
|
}
|
|
|
|
constexpr BitboardIterator(const BitboardIterator&) = default;
|
|
constexpr BitboardIterator(BitboardIterator&&) = default;
|
|
constexpr BitboardIterator& operator=(const BitboardIterator&) = default;
|
|
constexpr BitboardIterator& operator=(BitboardIterator&&) = default;
|
|
|
|
[[nodiscard]] constexpr bool friend operator==(BitboardIterator lhs, BitboardIterator rhs) noexcept
|
|
{
|
|
return lhs.m_squares == rhs.m_squares;
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool friend operator!=(BitboardIterator lhs, BitboardIterator rhs) noexcept
|
|
{
|
|
return lhs.m_squares != rhs.m_squares;
|
|
}
|
|
|
|
[[nodiscard]] inline Square operator*() const
|
|
{
|
|
return first();
|
|
}
|
|
|
|
constexpr BitboardIterator& operator++() noexcept
|
|
{
|
|
popFirst();
|
|
return *this;
|
|
}
|
|
|
|
private:
|
|
std::uint64_t m_squares;
|
|
|
|
constexpr void popFirst() noexcept
|
|
{
|
|
m_squares &= m_squares - 1;
|
|
}
|
|
|
|
[[nodiscard]] inline Square first() const
|
|
{
|
|
assert(m_squares != 0);
|
|
|
|
return fromOrdinal<Square>(intrin::lsb(m_squares));
|
|
}
|
|
};
|
|
|
|
struct Bitboard
|
|
{
|
|
// bits counted from the LSB
|
|
// order is A1 B2 ... G8 H8
|
|
// just like in Square
|
|
|
|
public:
|
|
constexpr Bitboard() noexcept :
|
|
m_squares(0)
|
|
{
|
|
}
|
|
|
|
private:
|
|
constexpr explicit Bitboard(Square sq) noexcept :
|
|
m_squares(static_cast<std::uint64_t>(1ULL) << ordinal(sq))
|
|
{
|
|
assert(sq.isOk());
|
|
}
|
|
|
|
constexpr explicit Bitboard(Rank r) noexcept :
|
|
m_squares(static_cast<std::uint64_t>(0xFFULL) << (ordinal(r) * 8))
|
|
{
|
|
}
|
|
|
|
constexpr explicit Bitboard(File f) noexcept :
|
|
m_squares(static_cast<std::uint64_t>(0x0101010101010101ULL) << ordinal(f))
|
|
{
|
|
}
|
|
|
|
constexpr explicit Bitboard(Color c) noexcept :
|
|
m_squares(c == Color::White ? 0xAA55AA55AA55AA55ULL : ~0xAA55AA55AA55AA55ULL)
|
|
{
|
|
}
|
|
|
|
constexpr explicit Bitboard(std::uint64_t bb) noexcept :
|
|
m_squares(bb)
|
|
{
|
|
}
|
|
|
|
// files A..file inclusive
|
|
static constexpr EnumArray<File, std::uint64_t> m_filesUpToBB{
|
|
0x0101010101010101ULL,
|
|
0x0303030303030303ULL,
|
|
0x0707070707070707ULL,
|
|
0x0F0F0F0F0F0F0F0FULL,
|
|
0x1F1F1F1F1F1F1F1FULL,
|
|
0x3F3F3F3F3F3F3F3FULL,
|
|
0x7F7F7F7F7F7F7F7FULL,
|
|
0xFFFFFFFFFFFFFFFFULL
|
|
};
|
|
|
|
public:
|
|
|
|
[[nodiscard]] static constexpr Bitboard none()
|
|
{
|
|
return Bitboard{};
|
|
}
|
|
|
|
[[nodiscard]] static constexpr Bitboard all()
|
|
{
|
|
return ~none();
|
|
}
|
|
|
|
[[nodiscard]] static constexpr Bitboard square(Square sq)
|
|
{
|
|
return Bitboard(sq);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr Bitboard file(File f)
|
|
{
|
|
return Bitboard(f);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr Bitboard rank(Rank r)
|
|
{
|
|
return Bitboard(r);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr Bitboard color(Color c)
|
|
{
|
|
return Bitboard(c);
|
|
}
|
|
|
|
[[nodiscard]] static constexpr Bitboard fromBits(std::uint64_t bits)
|
|
{
|
|
return Bitboard(bits);
|
|
}
|
|
|
|
// inclusive
|
|
[[nodiscard]] static constexpr Bitboard betweenFiles(File left, File right)
|
|
{
|
|
assert(left <= right);
|
|
|
|
if (left == fileA)
|
|
{
|
|
return Bitboard::fromBits(m_filesUpToBB[right]);
|
|
}
|
|
else
|
|
{
|
|
return Bitboard::fromBits(m_filesUpToBB[right] ^ m_filesUpToBB[left - 1]);
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool isEmpty() const
|
|
{
|
|
return m_squares == 0;
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool isSet(Square sq) const
|
|
{
|
|
return !!((m_squares >> ordinal(sq)) & 1ull);
|
|
}
|
|
|
|
constexpr void set(Square sq)
|
|
{
|
|
*this |= Bitboard(sq);
|
|
}
|
|
|
|
constexpr void unset(Square sq)
|
|
{
|
|
*this &= ~(Bitboard(sq));
|
|
}
|
|
|
|
constexpr void toggle(Square sq)
|
|
{
|
|
*this ^= Bitboard(sq);
|
|
}
|
|
|
|
[[nodiscard]] constexpr BitboardIterator begin() const
|
|
{
|
|
return BitboardIterator(m_squares);
|
|
}
|
|
|
|
[[nodiscard]] constexpr BitboardIterator end() const
|
|
{
|
|
return BitboardIterator{};
|
|
}
|
|
|
|
[[nodiscard]] constexpr BitboardIterator cbegin() const
|
|
{
|
|
return BitboardIterator(m_squares);
|
|
}
|
|
|
|
[[nodiscard]] constexpr BitboardIterator cend() const
|
|
{
|
|
return BitboardIterator{};
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool friend operator==(Bitboard lhs, Bitboard rhs) noexcept
|
|
{
|
|
return lhs.m_squares == rhs.m_squares;
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool friend operator!=(Bitboard lhs, Bitboard rhs) noexcept
|
|
{
|
|
return lhs.m_squares != rhs.m_squares;
|
|
}
|
|
|
|
constexpr Bitboard shiftedVertically(int ranks) const
|
|
{
|
|
if (ranks >= 0)
|
|
{
|
|
return fromBits(m_squares << 8 * ranks);
|
|
}
|
|
else
|
|
{
|
|
return fromBits(m_squares >> -8 * ranks);
|
|
}
|
|
}
|
|
|
|
template <int files, int ranks>
|
|
constexpr void shift()
|
|
{
|
|
static_assert(files >= -7);
|
|
static_assert(ranks >= -7);
|
|
static_assert(files <= 7);
|
|
static_assert(ranks <= 7);
|
|
|
|
if constexpr (files != 0)
|
|
{
|
|
constexpr Bitboard mask =
|
|
files > 0
|
|
? Bitboard::betweenFiles(fileA, fileH - files)
|
|
: Bitboard::betweenFiles(fileA - files, fileH);
|
|
|
|
m_squares &= mask.m_squares;
|
|
}
|
|
|
|
constexpr int shift = files + ranks * 8;
|
|
if constexpr (shift == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if constexpr (shift < 0)
|
|
{
|
|
m_squares >>= -shift;
|
|
}
|
|
else
|
|
{
|
|
m_squares <<= shift;
|
|
}
|
|
}
|
|
|
|
template <int files, int ranks>
|
|
constexpr Bitboard shifted() const
|
|
{
|
|
Bitboard bbCpy(*this);
|
|
bbCpy.shift<files, ranks>();
|
|
return bbCpy;
|
|
}
|
|
|
|
constexpr void shift(Offset offset)
|
|
{
|
|
assert(offset.files >= -7);
|
|
assert(offset.ranks >= -7);
|
|
assert(offset.files <= 7);
|
|
assert(offset.ranks <= 7);
|
|
|
|
if (offset.files != 0)
|
|
{
|
|
const Bitboard mask =
|
|
offset.files > 0
|
|
? Bitboard::betweenFiles(fileA, fileH - offset.files)
|
|
: Bitboard::betweenFiles(fileA - offset.files, fileH);
|
|
|
|
m_squares &= mask.m_squares;
|
|
}
|
|
|
|
const int shift = offset.files + offset.ranks * 8;
|
|
if (shift < 0)
|
|
{
|
|
m_squares >>= -shift;
|
|
}
|
|
else
|
|
{
|
|
m_squares <<= shift;
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard shifted(Offset offset) const
|
|
{
|
|
Bitboard bbCpy(*this);
|
|
bbCpy.shift(offset);
|
|
return bbCpy;
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard operator~() const
|
|
{
|
|
Bitboard bb = *this;
|
|
bb.m_squares = ~m_squares;
|
|
return bb;
|
|
}
|
|
|
|
constexpr Bitboard& operator^=(Color c)
|
|
{
|
|
m_squares ^= Bitboard(c).m_squares;
|
|
return *this;
|
|
}
|
|
|
|
constexpr Bitboard& operator&=(Color c)
|
|
{
|
|
m_squares &= Bitboard(c).m_squares;
|
|
return *this;
|
|
}
|
|
|
|
constexpr Bitboard& operator|=(Color c)
|
|
{
|
|
m_squares |= Bitboard(c).m_squares;
|
|
return *this;
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard operator^(Color c) const
|
|
{
|
|
Bitboard bb = *this;
|
|
bb ^= c;
|
|
return bb;
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard operator&(Color c) const
|
|
{
|
|
Bitboard bb = *this;
|
|
bb &= c;
|
|
return bb;
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard operator|(Color c) const
|
|
{
|
|
Bitboard bb = *this;
|
|
bb |= c;
|
|
return bb;
|
|
}
|
|
|
|
constexpr Bitboard& operator^=(Square sq)
|
|
{
|
|
m_squares ^= Bitboard(sq).m_squares;
|
|
return *this;
|
|
}
|
|
|
|
constexpr Bitboard& operator&=(Square sq)
|
|
{
|
|
m_squares &= Bitboard(sq).m_squares;
|
|
return *this;
|
|
}
|
|
|
|
constexpr Bitboard& operator|=(Square sq)
|
|
{
|
|
m_squares |= Bitboard(sq).m_squares;
|
|
return *this;
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard operator^(Square sq) const
|
|
{
|
|
Bitboard bb = *this;
|
|
bb ^= sq;
|
|
return bb;
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard operator&(Square sq) const
|
|
{
|
|
Bitboard bb = *this;
|
|
bb &= sq;
|
|
return bb;
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard operator|(Square sq) const
|
|
{
|
|
Bitboard bb = *this;
|
|
bb |= sq;
|
|
return bb;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend Bitboard operator^(Square sq, Bitboard bb)
|
|
{
|
|
return bb ^ sq;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend Bitboard operator&(Square sq, Bitboard bb)
|
|
{
|
|
return bb & sq;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend Bitboard operator|(Square sq, Bitboard bb)
|
|
{
|
|
return bb | sq;
|
|
}
|
|
|
|
constexpr Bitboard& operator^=(Bitboard rhs)
|
|
{
|
|
m_squares ^= rhs.m_squares;
|
|
return *this;
|
|
}
|
|
|
|
constexpr Bitboard& operator&=(Bitboard rhs)
|
|
{
|
|
m_squares &= rhs.m_squares;
|
|
return *this;
|
|
}
|
|
|
|
constexpr Bitboard& operator|=(Bitboard rhs)
|
|
{
|
|
m_squares |= rhs.m_squares;
|
|
return *this;
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard operator^(Bitboard sq) const
|
|
{
|
|
Bitboard bb = *this;
|
|
bb ^= sq;
|
|
return bb;
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard operator&(Bitboard sq) const
|
|
{
|
|
Bitboard bb = *this;
|
|
bb &= sq;
|
|
return bb;
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard operator|(Bitboard sq) const
|
|
{
|
|
Bitboard bb = *this;
|
|
bb |= sq;
|
|
return bb;
|
|
}
|
|
|
|
[[nodiscard]] inline int count() const
|
|
{
|
|
return static_cast<int>(intrin::popcount(m_squares));
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool moreThanOne() const
|
|
{
|
|
return !!(m_squares & (m_squares - 1));
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool exactlyOne() const
|
|
{
|
|
return m_squares != 0 && !moreThanOne();
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool any() const
|
|
{
|
|
return !!m_squares;
|
|
}
|
|
|
|
[[nodiscard]] inline Square first() const
|
|
{
|
|
assert(m_squares != 0);
|
|
|
|
return fromOrdinal<Square>(intrin::lsb(m_squares));
|
|
}
|
|
|
|
[[nodiscard]] inline Square nth(int n) const
|
|
{
|
|
assert(count() > n);
|
|
|
|
Bitboard cpy = *this;
|
|
while (n--) cpy.popFirst();
|
|
return cpy.first();
|
|
}
|
|
|
|
[[nodiscard]] inline Square last() const
|
|
{
|
|
assert(m_squares != 0);
|
|
|
|
return fromOrdinal<Square>(intrin::msb(m_squares));
|
|
}
|
|
|
|
[[nodiscard]] constexpr std::uint64_t bits() const
|
|
{
|
|
return m_squares;
|
|
}
|
|
|
|
constexpr void popFirst()
|
|
{
|
|
assert(m_squares != 0);
|
|
|
|
m_squares &= m_squares - 1;
|
|
}
|
|
|
|
constexpr Bitboard& operator=(const Bitboard& other) = default;
|
|
|
|
private:
|
|
std::uint64_t m_squares;
|
|
};
|
|
|
|
[[nodiscard]] constexpr Bitboard operator^(Square sq0, Square sq1)
|
|
{
|
|
return Bitboard::square(sq0) ^ sq1;
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard operator&(Square sq0, Square sq1)
|
|
{
|
|
return Bitboard::square(sq0) & sq1;
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard operator|(Square sq0, Square sq1)
|
|
{
|
|
return Bitboard::square(sq0) | sq1;
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard operator""_bb(unsigned long long bits)
|
|
{
|
|
return Bitboard::fromBits(bits);
|
|
}
|
|
|
|
namespace bb
|
|
{
|
|
namespace fancy_magics
|
|
{
|
|
// Implementation based on https://github.com/syzygy1/Cfish
|
|
|
|
alignas(64) constexpr EnumArray<Square, std::uint64_t> g_rookMagics{ {
|
|
0x0A80004000801220ull,
|
|
0x8040004010002008ull,
|
|
0x2080200010008008ull,
|
|
0x1100100008210004ull,
|
|
0xC200209084020008ull,
|
|
0x2100010004000208ull,
|
|
0x0400081000822421ull,
|
|
0x0200010422048844ull,
|
|
0x0800800080400024ull,
|
|
0x0001402000401000ull,
|
|
0x3000801000802001ull,
|
|
0x4400800800100083ull,
|
|
0x0904802402480080ull,
|
|
0x4040800400020080ull,
|
|
0x0018808042000100ull,
|
|
0x4040800080004100ull,
|
|
0x0040048001458024ull,
|
|
0x00A0004000205000ull,
|
|
0x3100808010002000ull,
|
|
0x4825010010000820ull,
|
|
0x5004808008000401ull,
|
|
0x2024818004000A00ull,
|
|
0x0005808002000100ull,
|
|
0x2100060004806104ull,
|
|
0x0080400880008421ull,
|
|
0x4062220600410280ull,
|
|
0x010A004A00108022ull,
|
|
0x0000100080080080ull,
|
|
0x0021000500080010ull,
|
|
0x0044000202001008ull,
|
|
0x0000100400080102ull,
|
|
0xC020128200040545ull,
|
|
0x0080002000400040ull,
|
|
0x0000804000802004ull,
|
|
0x0000120022004080ull,
|
|
0x010A386103001001ull,
|
|
0x9010080080800400ull,
|
|
0x8440020080800400ull,
|
|
0x0004228824001001ull,
|
|
0x000000490A000084ull,
|
|
0x0080002000504000ull,
|
|
0x200020005000C000ull,
|
|
0x0012088020420010ull,
|
|
0x0010010080080800ull,
|
|
0x0085001008010004ull,
|
|
0x0002000204008080ull,
|
|
0x0040413002040008ull,
|
|
0x0000304081020004ull,
|
|
0x0080204000800080ull,
|
|
0x3008804000290100ull,
|
|
0x1010100080200080ull,
|
|
0x2008100208028080ull,
|
|
0x5000850800910100ull,
|
|
0x8402019004680200ull,
|
|
0x0120911028020400ull,
|
|
0x0000008044010200ull,
|
|
0x0020850200244012ull,
|
|
0x0020850200244012ull,
|
|
0x0000102001040841ull,
|
|
0x140900040A100021ull,
|
|
0x000200282410A102ull,
|
|
0x000200282410A102ull,
|
|
0x000200282410A102ull,
|
|
0x4048240043802106ull
|
|
} };
|
|
|
|
alignas(64) constexpr EnumArray<Square, std::uint64_t> g_bishopMagics{ {
|
|
0x40106000A1160020ull,
|
|
0x0020010250810120ull,
|
|
0x2010010220280081ull,
|
|
0x002806004050C040ull,
|
|
0x0002021018000000ull,
|
|
0x2001112010000400ull,
|
|
0x0881010120218080ull,
|
|
0x1030820110010500ull,
|
|
0x0000120222042400ull,
|
|
0x2000020404040044ull,
|
|
0x8000480094208000ull,
|
|
0x0003422A02000001ull,
|
|
0x000A220210100040ull,
|
|
0x8004820202226000ull,
|
|
0x0018234854100800ull,
|
|
0x0100004042101040ull,
|
|
0x0004001004082820ull,
|
|
0x0010000810010048ull,
|
|
0x1014004208081300ull,
|
|
0x2080818802044202ull,
|
|
0x0040880C00A00100ull,
|
|
0x0080400200522010ull,
|
|
0x0001000188180B04ull,
|
|
0x0080249202020204ull,
|
|
0x1004400004100410ull,
|
|
0x00013100A0022206ull,
|
|
0x2148500001040080ull,
|
|
0x4241080011004300ull,
|
|
0x4020848004002000ull,
|
|
0x10101380D1004100ull,
|
|
0x0008004422020284ull,
|
|
0x01010A1041008080ull,
|
|
0x0808080400082121ull,
|
|
0x0808080400082121ull,
|
|
0x0091128200100C00ull,
|
|
0x0202200802010104ull,
|
|
0x8C0A020200440085ull,
|
|
0x01A0008080B10040ull,
|
|
0x0889520080122800ull,
|
|
0x100902022202010Aull,
|
|
0x04081A0816002000ull,
|
|
0x0000681208005000ull,
|
|
0x8170840041008802ull,
|
|
0x0A00004200810805ull,
|
|
0x0830404408210100ull,
|
|
0x2602208106006102ull,
|
|
0x1048300680802628ull,
|
|
0x2602208106006102ull,
|
|
0x0602010120110040ull,
|
|
0x0941010801043000ull,
|
|
0x000040440A210428ull,
|
|
0x0008240020880021ull,
|
|
0x0400002012048200ull,
|
|
0x00AC102001210220ull,
|
|
0x0220021002009900ull,
|
|
0x84440C080A013080ull,
|
|
0x0001008044200440ull,
|
|
0x0004C04410841000ull,
|
|
0x2000500104011130ull,
|
|
0x1A0C010011C20229ull,
|
|
0x0044800112202200ull,
|
|
0x0434804908100424ull,
|
|
0x0300404822C08200ull,
|
|
0x48081010008A2A80ull
|
|
} };
|
|
|
|
alignas(64) static EnumArray<Square, Bitboard> g_rookMasks;
|
|
alignas(64) static EnumArray<Square, std::uint8_t> g_rookShifts;
|
|
alignas(64) static EnumArray<Square, const Bitboard*> g_rookAttacks;
|
|
|
|
alignas(64) static EnumArray<Square, Bitboard> g_bishopMasks;
|
|
alignas(64) static EnumArray<Square, std::uint8_t> g_bishopShifts;
|
|
alignas(64) static EnumArray<Square, const Bitboard*> g_bishopAttacks;
|
|
|
|
alignas(64) static std::array<Bitboard, 102400> g_allRookAttacks;
|
|
alignas(64) static std::array<Bitboard, 5248> g_allBishopAttacks;
|
|
|
|
inline Bitboard bishopAttacks(Square s, Bitboard occupied)
|
|
{
|
|
const std::size_t idx =
|
|
(occupied & fancy_magics::g_bishopMasks[s]).bits()
|
|
* fancy_magics::g_bishopMagics[s]
|
|
>> fancy_magics::g_bishopShifts[s];
|
|
|
|
return fancy_magics::g_bishopAttacks[s][idx];
|
|
}
|
|
|
|
inline Bitboard rookAttacks(Square s, Bitboard occupied)
|
|
{
|
|
const std::size_t idx =
|
|
(occupied & fancy_magics::g_rookMasks[s]).bits()
|
|
* fancy_magics::g_rookMagics[s]
|
|
>> fancy_magics::g_rookShifts[s];
|
|
|
|
return fancy_magics::g_rookAttacks[s][idx];
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard square(Square sq)
|
|
{
|
|
return Bitboard::square(sq);
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard rank(Rank rank)
|
|
{
|
|
return Bitboard::rank(rank);
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard file(File file)
|
|
{
|
|
return Bitboard::file(file);
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard color(Color c)
|
|
{
|
|
return Bitboard::color(c);
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard before(Square sq)
|
|
{
|
|
return Bitboard::fromBits(nbitmask<std::uint64_t>[ordinal(sq)]);
|
|
}
|
|
|
|
constexpr Bitboard lightSquares = bb::color(Color::White);
|
|
constexpr Bitboard darkSquares = bb::color(Color::Black);
|
|
|
|
constexpr Bitboard fileA = bb::file(chess::fileA);
|
|
constexpr Bitboard fileB = bb::file(chess::fileB);
|
|
constexpr Bitboard fileC = bb::file(chess::fileC);
|
|
constexpr Bitboard fileD = bb::file(chess::fileD);
|
|
constexpr Bitboard fileE = bb::file(chess::fileE);
|
|
constexpr Bitboard fileF = bb::file(chess::fileF);
|
|
constexpr Bitboard fileG = bb::file(chess::fileG);
|
|
constexpr Bitboard fileH = bb::file(chess::fileH);
|
|
|
|
constexpr Bitboard rank1 = bb::rank(chess::rank1);
|
|
constexpr Bitboard rank2 = bb::rank(chess::rank2);
|
|
constexpr Bitboard rank3 = bb::rank(chess::rank3);
|
|
constexpr Bitboard rank4 = bb::rank(chess::rank4);
|
|
constexpr Bitboard rank5 = bb::rank(chess::rank5);
|
|
constexpr Bitboard rank6 = bb::rank(chess::rank6);
|
|
constexpr Bitboard rank7 = bb::rank(chess::rank7);
|
|
constexpr Bitboard rank8 = bb::rank(chess::rank8);
|
|
|
|
constexpr Bitboard a1 = bb::square(chess::a1);
|
|
constexpr Bitboard a2 = bb::square(chess::a2);
|
|
constexpr Bitboard a3 = bb::square(chess::a3);
|
|
constexpr Bitboard a4 = bb::square(chess::a4);
|
|
constexpr Bitboard a5 = bb::square(chess::a5);
|
|
constexpr Bitboard a6 = bb::square(chess::a6);
|
|
constexpr Bitboard a7 = bb::square(chess::a7);
|
|
constexpr Bitboard a8 = bb::square(chess::a8);
|
|
|
|
constexpr Bitboard b1 = bb::square(chess::b1);
|
|
constexpr Bitboard b2 = bb::square(chess::b2);
|
|
constexpr Bitboard b3 = bb::square(chess::b3);
|
|
constexpr Bitboard b4 = bb::square(chess::b4);
|
|
constexpr Bitboard b5 = bb::square(chess::b5);
|
|
constexpr Bitboard b6 = bb::square(chess::b6);
|
|
constexpr Bitboard b7 = bb::square(chess::b7);
|
|
constexpr Bitboard b8 = bb::square(chess::b8);
|
|
|
|
constexpr Bitboard c1 = bb::square(chess::c1);
|
|
constexpr Bitboard c2 = bb::square(chess::c2);
|
|
constexpr Bitboard c3 = bb::square(chess::c3);
|
|
constexpr Bitboard c4 = bb::square(chess::c4);
|
|
constexpr Bitboard c5 = bb::square(chess::c5);
|
|
constexpr Bitboard c6 = bb::square(chess::c6);
|
|
constexpr Bitboard c7 = bb::square(chess::c7);
|
|
constexpr Bitboard c8 = bb::square(chess::c8);
|
|
|
|
constexpr Bitboard d1 = bb::square(chess::d1);
|
|
constexpr Bitboard d2 = bb::square(chess::d2);
|
|
constexpr Bitboard d3 = bb::square(chess::d3);
|
|
constexpr Bitboard d4 = bb::square(chess::d4);
|
|
constexpr Bitboard d5 = bb::square(chess::d5);
|
|
constexpr Bitboard d6 = bb::square(chess::d6);
|
|
constexpr Bitboard d7 = bb::square(chess::d7);
|
|
constexpr Bitboard d8 = bb::square(chess::d8);
|
|
|
|
constexpr Bitboard e1 = bb::square(chess::e1);
|
|
constexpr Bitboard e2 = bb::square(chess::e2);
|
|
constexpr Bitboard e3 = bb::square(chess::e3);
|
|
constexpr Bitboard e4 = bb::square(chess::e4);
|
|
constexpr Bitboard e5 = bb::square(chess::e5);
|
|
constexpr Bitboard e6 = bb::square(chess::e6);
|
|
constexpr Bitboard e7 = bb::square(chess::e7);
|
|
constexpr Bitboard e8 = bb::square(chess::e8);
|
|
|
|
constexpr Bitboard f1 = bb::square(chess::f1);
|
|
constexpr Bitboard f2 = bb::square(chess::f2);
|
|
constexpr Bitboard f3 = bb::square(chess::f3);
|
|
constexpr Bitboard f4 = bb::square(chess::f4);
|
|
constexpr Bitboard f5 = bb::square(chess::f5);
|
|
constexpr Bitboard f6 = bb::square(chess::f6);
|
|
constexpr Bitboard f7 = bb::square(chess::f7);
|
|
constexpr Bitboard f8 = bb::square(chess::f8);
|
|
|
|
constexpr Bitboard g1 = bb::square(chess::g1);
|
|
constexpr Bitboard g2 = bb::square(chess::g2);
|
|
constexpr Bitboard g3 = bb::square(chess::g3);
|
|
constexpr Bitboard g4 = bb::square(chess::g4);
|
|
constexpr Bitboard g5 = bb::square(chess::g5);
|
|
constexpr Bitboard g6 = bb::square(chess::g6);
|
|
constexpr Bitboard g7 = bb::square(chess::g7);
|
|
constexpr Bitboard g8 = bb::square(chess::g8);
|
|
|
|
constexpr Bitboard h1 = bb::square(chess::h1);
|
|
constexpr Bitboard h2 = bb::square(chess::h2);
|
|
constexpr Bitboard h3 = bb::square(chess::h3);
|
|
constexpr Bitboard h4 = bb::square(chess::h4);
|
|
constexpr Bitboard h5 = bb::square(chess::h5);
|
|
constexpr Bitboard h6 = bb::square(chess::h6);
|
|
constexpr Bitboard h7 = bb::square(chess::h7);
|
|
constexpr Bitboard h8 = bb::square(chess::h8);
|
|
|
|
[[nodiscard]] Bitboard between(Square s1, Square s2);
|
|
|
|
[[nodiscard]] Bitboard line(Square s1, Square s2);
|
|
|
|
template <PieceType PieceTypeV>
|
|
[[nodiscard]] Bitboard pseudoAttacks(Square sq);
|
|
|
|
[[nodiscard]] Bitboard pseudoAttacks(PieceType pt, Square sq);
|
|
|
|
template <PieceType PieceTypeV>
|
|
Bitboard attacks(Square sq, Bitboard occupied)
|
|
{
|
|
static_assert(PieceTypeV != PieceType::None && PieceTypeV != PieceType::Pawn);
|
|
|
|
assert(sq.isOk());
|
|
|
|
if constexpr (PieceTypeV == PieceType::Bishop)
|
|
{
|
|
return fancy_magics::bishopAttacks(sq, occupied);
|
|
}
|
|
else if constexpr (PieceTypeV == PieceType::Rook)
|
|
{
|
|
return fancy_magics::rookAttacks(sq, occupied);
|
|
}
|
|
else if constexpr (PieceTypeV == PieceType::Queen)
|
|
{
|
|
return
|
|
fancy_magics::bishopAttacks(sq, occupied)
|
|
| fancy_magics::rookAttacks(sq, occupied);
|
|
}
|
|
else
|
|
{
|
|
return pseudoAttacks<PieceTypeV>(sq);
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] inline Bitboard attacks(PieceType pt, Square sq, Bitboard occupied)
|
|
{
|
|
assert(sq.isOk());
|
|
|
|
switch (pt)
|
|
{
|
|
case PieceType::Bishop:
|
|
return attacks<PieceType::Bishop>(sq, occupied);
|
|
case PieceType::Rook:
|
|
return attacks<PieceType::Rook>(sq, occupied);
|
|
case PieceType::Queen:
|
|
return attacks<PieceType::Queen>(sq, occupied);
|
|
default:
|
|
return pseudoAttacks(pt, sq);
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] inline Bitboard pawnAttacks(Bitboard pawns, Color color);
|
|
|
|
[[nodiscard]] inline Bitboard westPawnAttacks(Bitboard pawns, Color color);
|
|
|
|
[[nodiscard]] inline Bitboard eastPawnAttacks(Bitboard pawns, Color color);
|
|
|
|
[[nodiscard]] inline bool isAttackedBySlider(
|
|
Square sq,
|
|
Bitboard bishops,
|
|
Bitboard rooks,
|
|
Bitboard queens,
|
|
Bitboard occupied
|
|
);
|
|
|
|
namespace detail
|
|
{
|
|
static constexpr std::array<Offset, 8> knightOffsets{ { {-1, -2}, {-1, 2}, {1, -2}, {1, 2}, {-2, -1}, {-2, 1}, {2, -1}, {2, 1} } };
|
|
static constexpr std::array<Offset, 8> kingOffsets{ { {-1, -1}, {-1, 0}, {-1, 1}, {0, -1}, {0, 1}, {1, -1}, {1, 0}, {1, 1} } };
|
|
|
|
enum Direction
|
|
{
|
|
North = 0,
|
|
NorthEast,
|
|
East,
|
|
SouthEast,
|
|
South,
|
|
SouthWest,
|
|
West,
|
|
NorthWest
|
|
};
|
|
|
|
constexpr std::array<Offset, 8> offsets = { {
|
|
{ 0, 1 },
|
|
{ 1, 1 },
|
|
{ 1, 0 },
|
|
{ 1, -1 },
|
|
{ 0, -1 },
|
|
{ -1, -1 },
|
|
{ -1, 0 },
|
|
{ -1, 1 }
|
|
} };
|
|
|
|
static constexpr std::array<Offset, 4> bishopOffsets{
|
|
offsets[NorthEast],
|
|
offsets[SouthEast],
|
|
offsets[SouthWest],
|
|
offsets[NorthWest]
|
|
};
|
|
static constexpr std::array<Offset, 4> rookOffsets{
|
|
offsets[North],
|
|
offsets[East],
|
|
offsets[South],
|
|
offsets[West]
|
|
};
|
|
|
|
[[nodiscard]] static EnumArray<Square, Bitboard> generatePseudoAttacks_Pawn()
|
|
{
|
|
// pseudo attacks don't make sense for pawns
|
|
return {};
|
|
}
|
|
|
|
[[nodiscard]] static EnumArray<Square, Bitboard> generatePseudoAttacks_Knight()
|
|
{
|
|
EnumArray<Square, Bitboard> bbs{};
|
|
|
|
for (Square fromSq = chess::a1; fromSq != Square::none(); ++fromSq)
|
|
{
|
|
Bitboard bb{};
|
|
|
|
for (auto&& offset : knightOffsets)
|
|
{
|
|
const SquareCoords toSq = fromSq.coords() + offset;
|
|
if (toSq.isOk())
|
|
{
|
|
bb |= Square(toSq);
|
|
}
|
|
}
|
|
|
|
bbs[fromSq] = bb;
|
|
}
|
|
|
|
return bbs;
|
|
}
|
|
|
|
[[nodiscard]] static Bitboard generateSliderPseudoAttacks(const std::array<Offset, 4> & offsets_, Square fromSq)
|
|
{
|
|
assert(fromSq.isOk());
|
|
|
|
Bitboard bb{};
|
|
|
|
for (auto&& offset : offsets_)
|
|
{
|
|
SquareCoords fromSqC = fromSq.coords();
|
|
|
|
for (;;)
|
|
{
|
|
fromSqC += offset;
|
|
|
|
if (!fromSqC.isOk())
|
|
{
|
|
break;
|
|
}
|
|
|
|
bb |= Square(fromSqC);
|
|
}
|
|
}
|
|
|
|
return bb;
|
|
}
|
|
|
|
[[nodiscard]] static EnumArray<Square, Bitboard> generatePseudoAttacks_Bishop()
|
|
{
|
|
EnumArray<Square, Bitboard> bbs{};
|
|
|
|
for (Square fromSq = chess::a1; fromSq != Square::none(); ++fromSq)
|
|
{
|
|
bbs[fromSq] = generateSliderPseudoAttacks(bishopOffsets, fromSq);
|
|
}
|
|
|
|
return bbs;
|
|
}
|
|
|
|
[[nodiscard]] static EnumArray<Square, Bitboard> generatePseudoAttacks_Rook()
|
|
{
|
|
EnumArray<Square, Bitboard> bbs{};
|
|
|
|
for (Square fromSq = chess::a1; fromSq != Square::none(); ++fromSq)
|
|
{
|
|
bbs[fromSq] = generateSliderPseudoAttacks(rookOffsets, fromSq);
|
|
}
|
|
|
|
return bbs;
|
|
}
|
|
|
|
[[nodiscard]] static EnumArray<Square, Bitboard> generatePseudoAttacks_Queen()
|
|
{
|
|
EnumArray<Square, Bitboard> bbs{};
|
|
|
|
for (Square fromSq = chess::a1; fromSq != Square::none(); ++fromSq)
|
|
{
|
|
bbs[fromSq] =
|
|
generateSliderPseudoAttacks(bishopOffsets, fromSq)
|
|
| generateSliderPseudoAttacks(rookOffsets, fromSq);
|
|
}
|
|
|
|
return bbs;
|
|
}
|
|
|
|
[[nodiscard]] static EnumArray<Square, Bitboard> generatePseudoAttacks_King()
|
|
{
|
|
EnumArray<Square, Bitboard> bbs{};
|
|
|
|
for (Square fromSq = chess::a1; fromSq != Square::none(); ++fromSq)
|
|
{
|
|
Bitboard bb{};
|
|
|
|
for (auto&& offset : kingOffsets)
|
|
{
|
|
const SquareCoords toSq = fromSq.coords() + offset;
|
|
if (toSq.isOk())
|
|
{
|
|
bb |= Square(toSq);
|
|
}
|
|
}
|
|
|
|
bbs[fromSq] = bb;
|
|
}
|
|
|
|
return bbs;
|
|
}
|
|
|
|
[[nodiscard]] static EnumArray2<PieceType, Square, Bitboard> generatePseudoAttacks()
|
|
{
|
|
return EnumArray2<PieceType, Square, Bitboard>{
|
|
generatePseudoAttacks_Pawn(),
|
|
generatePseudoAttacks_Knight(),
|
|
generatePseudoAttacks_Bishop(),
|
|
generatePseudoAttacks_Rook(),
|
|
generatePseudoAttacks_Queen(),
|
|
generatePseudoAttacks_King()
|
|
};
|
|
}
|
|
|
|
static const EnumArray2<PieceType, Square, Bitboard>& pseudoAttacks()
|
|
{
|
|
static const EnumArray2<PieceType, Square, Bitboard> s_pseudoAttacks = generatePseudoAttacks();
|
|
return s_pseudoAttacks;
|
|
}
|
|
|
|
[[nodiscard]] static Bitboard generatePositiveRayAttacks(Direction dir, Square fromSq)
|
|
{
|
|
assert(fromSq.isOk());
|
|
|
|
Bitboard bb{};
|
|
|
|
const auto offset = offsets[dir];
|
|
SquareCoords fromSqC = fromSq.coords();
|
|
for (;;)
|
|
{
|
|
fromSqC += offset;
|
|
|
|
if (!fromSqC.isOk())
|
|
{
|
|
break;
|
|
}
|
|
|
|
bb |= Square(fromSqC);
|
|
}
|
|
|
|
return bb;
|
|
}
|
|
|
|
// classical slider move generation approach https://www.chessprogramming.org/Classical_Approach
|
|
|
|
[[nodiscard]] static EnumArray<Square, Bitboard> generatePositiveRayAttacks(Direction dir)
|
|
{
|
|
EnumArray<Square, Bitboard> bbs{};
|
|
|
|
for (Square fromSq = chess::a1; fromSq != Square::none(); ++fromSq)
|
|
{
|
|
bbs[fromSq] = generatePositiveRayAttacks(dir, fromSq);
|
|
}
|
|
|
|
return bbs;
|
|
}
|
|
|
|
[[nodiscard]] static std::array<EnumArray<Square, Bitboard>, 8> generatePositiveRayAttacks()
|
|
{
|
|
std::array<EnumArray<Square, Bitboard>, 8> bbs{};
|
|
|
|
bbs[North] = generatePositiveRayAttacks(North);
|
|
bbs[NorthEast] = generatePositiveRayAttacks(NorthEast);
|
|
bbs[East] = generatePositiveRayAttacks(East);
|
|
bbs[SouthEast] = generatePositiveRayAttacks(SouthEast);
|
|
bbs[South] = generatePositiveRayAttacks(South);
|
|
bbs[SouthWest] = generatePositiveRayAttacks(SouthWest);
|
|
bbs[West] = generatePositiveRayAttacks(West);
|
|
bbs[NorthWest] = generatePositiveRayAttacks(NorthWest);
|
|
|
|
return bbs;
|
|
}
|
|
|
|
|
|
static const std::array<EnumArray<Square, Bitboard>, 8>& positiveRayAttacks()
|
|
{
|
|
static const std::array<EnumArray<Square, Bitboard>, 8> s_positiveRayAttacks = generatePositiveRayAttacks();
|
|
return s_positiveRayAttacks;
|
|
}
|
|
|
|
template <Direction DirV>
|
|
[[nodiscard]] static Bitboard slidingAttacks(Square sq, Bitboard occupied)
|
|
{
|
|
assert(sq.isOk());
|
|
|
|
Bitboard attacks = positiveRayAttacks()[DirV][sq];
|
|
|
|
if constexpr (DirV == NorthWest || DirV == North || DirV == NorthEast || DirV == East)
|
|
{
|
|
Bitboard blocker = (attacks & occupied) | h8; // set highest bit (H8) so msb never fails
|
|
return attacks ^ positiveRayAttacks()[DirV][blocker.first()];
|
|
}
|
|
else
|
|
{
|
|
Bitboard blocker = (attacks & occupied) | a1;
|
|
return attacks ^ positiveRayAttacks()[DirV][blocker.last()];
|
|
}
|
|
}
|
|
|
|
template Bitboard slidingAttacks<Direction::North>(Square, Bitboard);
|
|
template Bitboard slidingAttacks<Direction::NorthEast>(Square, Bitboard);
|
|
template Bitboard slidingAttacks<Direction::East>(Square, Bitboard);
|
|
template Bitboard slidingAttacks<Direction::SouthEast>(Square, Bitboard);
|
|
template Bitboard slidingAttacks<Direction::South>(Square, Bitboard);
|
|
template Bitboard slidingAttacks<Direction::SouthWest>(Square, Bitboard);
|
|
template Bitboard slidingAttacks<Direction::West>(Square, Bitboard);
|
|
template Bitboard slidingAttacks<Direction::NorthWest>(Square, Bitboard);
|
|
|
|
template <PieceType PieceTypeV>
|
|
[[nodiscard]] inline Bitboard pieceSlidingAttacks(Square sq, Bitboard occupied)
|
|
{
|
|
static_assert(
|
|
PieceTypeV == PieceType::Rook
|
|
|| PieceTypeV == PieceType::Bishop
|
|
|| PieceTypeV == PieceType::Queen);
|
|
|
|
assert(sq.isOk());
|
|
|
|
if constexpr (PieceTypeV == PieceType::Bishop)
|
|
{
|
|
return
|
|
detail::slidingAttacks<detail::NorthEast>(sq, occupied)
|
|
| detail::slidingAttacks<detail::SouthEast>(sq, occupied)
|
|
| detail::slidingAttacks<detail::SouthWest>(sq, occupied)
|
|
| detail::slidingAttacks<detail::NorthWest>(sq, occupied);
|
|
}
|
|
else if constexpr (PieceTypeV == PieceType::Rook)
|
|
{
|
|
return
|
|
detail::slidingAttacks<detail::North>(sq, occupied)
|
|
| detail::slidingAttacks<detail::East>(sq, occupied)
|
|
| detail::slidingAttacks<detail::South>(sq, occupied)
|
|
| detail::slidingAttacks<detail::West>(sq, occupied);
|
|
}
|
|
else // if constexpr (PieceTypeV == PieceType::Queen)
|
|
{
|
|
return
|
|
detail::slidingAttacks<detail::North>(sq, occupied)
|
|
| detail::slidingAttacks<detail::NorthEast>(sq, occupied)
|
|
| detail::slidingAttacks<detail::East>(sq, occupied)
|
|
| detail::slidingAttacks<detail::SouthEast>(sq, occupied)
|
|
| detail::slidingAttacks<detail::South>(sq, occupied)
|
|
| detail::slidingAttacks<detail::SouthWest>(sq, occupied)
|
|
| detail::slidingAttacks<detail::West>(sq, occupied)
|
|
| detail::slidingAttacks<detail::NorthWest>(sq, occupied);
|
|
}
|
|
}
|
|
|
|
static Bitboard generateBetween(Square s1, Square s2)
|
|
{
|
|
Bitboard bb = Bitboard::none();
|
|
|
|
if (s1 == s2)
|
|
{
|
|
return bb;
|
|
}
|
|
|
|
const int fd = s2.file() - s1.file();
|
|
const int rd = s2.rank() - s1.rank();
|
|
|
|
if (fd == 0 || rd == 0 || fd == rd || fd == -rd)
|
|
{
|
|
// s1 and s2 lie on a line.
|
|
const int fileStep = (fd > 0) - (fd < 0);
|
|
const int rankStep = (rd > 0) - (rd < 0);
|
|
const auto step = FlatSquareOffset(fileStep, rankStep);
|
|
s1 += step; // omit s1
|
|
while(s1 != s2) // omit s2
|
|
{
|
|
bb |= s1;
|
|
s1 += step;
|
|
}
|
|
}
|
|
|
|
return bb;
|
|
}
|
|
|
|
static Bitboard generateLine(Square s1, Square s2)
|
|
{
|
|
for (PieceType pt : { PieceType::Bishop, PieceType::Rook })
|
|
{
|
|
const Bitboard s1Attacks = pseudoAttacks()[pt][s1];
|
|
if (s1Attacks.isSet(s2))
|
|
{
|
|
const Bitboard s2Attacks = pseudoAttacks()[pt][s2];
|
|
return (s1Attacks & s2Attacks) | s1 | s2;
|
|
}
|
|
}
|
|
|
|
return Bitboard::none();
|
|
}
|
|
|
|
static const EnumArray2<Square, Square, Bitboard> between = []()
|
|
{
|
|
EnumArray2<Square, Square, Bitboard> between_;
|
|
|
|
for (Square s1 : values<Square>())
|
|
{
|
|
for (Square s2 : values<Square>())
|
|
{
|
|
between_[s1][s2] = generateBetween(s1, s2);
|
|
}
|
|
}
|
|
|
|
return between_;
|
|
}();
|
|
|
|
static const EnumArray2<Square, Square, Bitboard> line = []()
|
|
{
|
|
EnumArray2<Square, Square, Bitboard> line_;
|
|
|
|
for (Square s1 : values<Square>())
|
|
{
|
|
for (Square s2 : values<Square>())
|
|
{
|
|
line_[s1][s2] = generateLine(s1, s2);
|
|
}
|
|
}
|
|
|
|
return line_;
|
|
}();
|
|
}
|
|
|
|
namespace fancy_magics
|
|
{
|
|
enum struct MagicsType
|
|
{
|
|
Rook,
|
|
Bishop
|
|
};
|
|
|
|
template <MagicsType TypeV>
|
|
[[nodiscard]] inline Bitboard slidingAttacks(Square sq, Bitboard occupied)
|
|
{
|
|
if (TypeV == MagicsType::Rook)
|
|
{
|
|
return chess::bb::detail::pieceSlidingAttacks<PieceType::Rook>(sq, occupied);
|
|
}
|
|
|
|
if (TypeV == MagicsType::Bishop)
|
|
{
|
|
return chess::bb::detail::pieceSlidingAttacks<PieceType::Bishop>(sq, occupied);
|
|
}
|
|
|
|
return Bitboard::none();
|
|
}
|
|
|
|
template <MagicsType TypeV, std::size_t SizeV>
|
|
[[nodiscard]] inline bool initMagics(
|
|
const EnumArray<Square, std::uint64_t>& magics,
|
|
std::array<Bitboard, SizeV>& table,
|
|
EnumArray<Square, Bitboard>& masks,
|
|
EnumArray<Square, std::uint8_t>& shifts,
|
|
EnumArray<Square, const Bitboard*>& attacks
|
|
)
|
|
{
|
|
std::size_t size = 0;
|
|
for (Square sq : values<Square>())
|
|
{
|
|
const Bitboard edges =
|
|
((bb::rank1 | bb::rank8) & ~Bitboard::rank(sq.rank()))
|
|
| ((bb::fileA | bb::fileH) & ~Bitboard::file(sq.file()));
|
|
|
|
Bitboard* currentAttacks = table.data() + size;
|
|
|
|
attacks[sq] = currentAttacks;
|
|
masks[sq] = slidingAttacks<TypeV>(sq, Bitboard::none()) & ~edges;
|
|
shifts[sq] = 64 - masks[sq].count();
|
|
|
|
Bitboard occupied = Bitboard::none();
|
|
do
|
|
{
|
|
const std::size_t idx =
|
|
(occupied & masks[sq]).bits()
|
|
* magics[sq]
|
|
>> shifts[sq];
|
|
|
|
currentAttacks[idx] = slidingAttacks<TypeV>(sq, occupied);
|
|
|
|
++size;
|
|
occupied = Bitboard::fromBits(occupied.bits() - masks[sq].bits()) & masks[sq];
|
|
} while (occupied.any());
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool g_isRookMagicsInitialized =
|
|
initMagics<MagicsType::Rook>(g_rookMagics, g_allRookAttacks, g_rookMasks, g_rookShifts, g_rookAttacks);
|
|
|
|
static bool g_isBishopMagicsInitialized =
|
|
initMagics<MagicsType::Bishop>(g_bishopMagics, g_allBishopAttacks, g_bishopMasks, g_bishopShifts, g_bishopAttacks);
|
|
}
|
|
|
|
[[nodiscard]] inline Bitboard between(Square s1, Square s2)
|
|
{
|
|
return detail::between[s1][s2];
|
|
}
|
|
|
|
[[nodiscard]] inline Bitboard line(Square s1, Square s2)
|
|
{
|
|
return detail::line[s1][s2];
|
|
}
|
|
|
|
template <PieceType PieceTypeV>
|
|
[[nodiscard]] inline Bitboard pseudoAttacks(Square sq)
|
|
{
|
|
static_assert(PieceTypeV != PieceType::None && PieceTypeV != PieceType::Pawn);
|
|
|
|
assert(sq.isOk());
|
|
|
|
return detail::pseudoAttacks()[PieceTypeV][sq];
|
|
}
|
|
|
|
[[nodiscard]] inline Bitboard pseudoAttacks(PieceType pt, Square sq)
|
|
{
|
|
assert(sq.isOk());
|
|
|
|
return detail::pseudoAttacks()[pt][sq];
|
|
}
|
|
|
|
[[nodiscard]] inline Bitboard pawnAttacks(Bitboard pawns, Color color)
|
|
{
|
|
if (color == Color::White)
|
|
{
|
|
return pawns.shifted<1, 1>() | pawns.shifted<-1, 1>();
|
|
}
|
|
else
|
|
{
|
|
return pawns.shifted<1, -1>() | pawns.shifted<-1, -1>();
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] inline Bitboard westPawnAttacks(Bitboard pawns, Color color)
|
|
{
|
|
if (color == Color::White)
|
|
{
|
|
return pawns.shifted<-1, 1>();
|
|
}
|
|
else
|
|
{
|
|
return pawns.shifted<-1, -1>();
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] inline Bitboard eastPawnAttacks(Bitboard pawns, Color color)
|
|
{
|
|
if (color == Color::White)
|
|
{
|
|
return pawns.shifted<1, 1>();
|
|
}
|
|
else
|
|
{
|
|
return pawns.shifted<1, -1>();
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] inline bool isAttackedBySlider(
|
|
Square sq,
|
|
Bitboard bishops,
|
|
Bitboard rooks,
|
|
Bitboard queens,
|
|
Bitboard occupied
|
|
)
|
|
{
|
|
const Bitboard opponentBishopLikePieces = (bishops | queens);
|
|
const Bitboard bishopAttacks = bb::attacks<PieceType::Bishop>(sq, occupied);
|
|
if ((bishopAttacks & opponentBishopLikePieces).any())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const Bitboard opponentRookLikePieces = (rooks | queens);
|
|
const Bitboard rookAttacks = bb::attacks<PieceType::Rook>(sq, occupied);
|
|
return (rookAttacks & opponentRookLikePieces).any();
|
|
}
|
|
}
|
|
|
|
struct CastlingTraits
|
|
{
|
|
static constexpr EnumArray2<Color, CastleType, Square> rookDestination = { { {{ f1, d1 }}, {{ f8, d8 }} } };
|
|
static constexpr EnumArray2<Color, CastleType, Square> kingDestination = { { {{ g1, c1 }}, {{ g8, c8 }} } };
|
|
|
|
static constexpr EnumArray2<Color, CastleType, Square> rookStart = { { {{ h1, a1 }}, {{ h8, a8 }} } };
|
|
|
|
static constexpr EnumArray<Color, Square> kingStart = { { e1, e8 } };
|
|
|
|
static constexpr EnumArray2<Color, CastleType, Bitboard> castlingPath = {
|
|
{
|
|
{{ Bitboard::square(f1) | g1, Bitboard::square(b1) | c1 | d1 }},
|
|
{{ Bitboard::square(f8) | g8, Bitboard::square(b8) | c8 | d8 }}
|
|
}
|
|
};
|
|
|
|
static constexpr EnumArray2<Color, CastleType, Square> squarePassedByKing = {
|
|
{
|
|
{{ f1, d1 }},
|
|
{{ f8, d8 }}
|
|
}
|
|
};
|
|
|
|
static constexpr EnumArray2<Color, CastleType, CastlingRights> castlingRights = {
|
|
{
|
|
{{ CastlingRights::WhiteKingSide, CastlingRights::WhiteQueenSide }},
|
|
{{ CastlingRights::BlackKingSide, CastlingRights::BlackQueenSide }}
|
|
}
|
|
};
|
|
|
|
// Move has to be a legal castling move.
|
|
static constexpr CastleType moveCastlingType(const Move& move)
|
|
{
|
|
return (move.to.file() == fileH) ? CastleType::Short : CastleType::Long;
|
|
}
|
|
|
|
// Move must be a legal castling move.
|
|
static constexpr CastlingRights moveCastlingRight(Move move)
|
|
{
|
|
if (move.to == h1) return CastlingRights::WhiteKingSide;
|
|
if (move.to == a1) return CastlingRights::WhiteQueenSide;
|
|
if (move.to == h8) return CastlingRights::WhiteKingSide;
|
|
if (move.to == a8) return CastlingRights::WhiteQueenSide;
|
|
return CastlingRights::None;
|
|
}
|
|
};
|
|
|
|
namespace parser_bits
|
|
{
|
|
[[nodiscard]] constexpr bool isFile(char c)
|
|
{
|
|
return c >= 'a' && c <= 'h';
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool isRank(char c)
|
|
{
|
|
return c >= '1' && c <= '8';
|
|
}
|
|
|
|
[[nodiscard]] constexpr Rank parseRank(char c)
|
|
{
|
|
assert(isRank(c));
|
|
|
|
return fromOrdinal<Rank>(c - '1');
|
|
}
|
|
|
|
[[nodiscard]] constexpr File parseFile(char c)
|
|
{
|
|
assert(isFile(c));
|
|
|
|
return fromOrdinal<File>(c - 'a');
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool isSquare(const char* s)
|
|
{
|
|
return isFile(s[0]) && isRank(s[1]);
|
|
}
|
|
|
|
[[nodiscard]] constexpr Square parseSquare(const char* s)
|
|
{
|
|
const File file = parseFile(s[0]);
|
|
const Rank rank = parseRank(s[1]);
|
|
return Square(file, rank);
|
|
}
|
|
|
|
[[nodiscard]] constexpr std::optional<Square> tryParseSquare(std::string_view s)
|
|
{
|
|
if (s.size() != 2) return {};
|
|
if (!isSquare(s.data())) return {};
|
|
return parseSquare(s.data());
|
|
}
|
|
|
|
[[nodiscard]] constexpr std::optional<Square> tryParseEpSquare(std::string_view s)
|
|
{
|
|
if (s == std::string_view("-")) return Square::none();
|
|
return tryParseSquare(s);
|
|
}
|
|
|
|
[[nodiscard]] constexpr std::optional<CastlingRights> tryParseCastlingRights(std::string_view s)
|
|
{
|
|
if (s == std::string_view("-")) return CastlingRights::None;
|
|
|
|
CastlingRights rights = CastlingRights::None;
|
|
|
|
for (auto& c : s)
|
|
{
|
|
CastlingRights toAdd = CastlingRights::None;
|
|
switch (c)
|
|
{
|
|
case 'K':
|
|
toAdd = CastlingRights::WhiteKingSide;
|
|
break;
|
|
case 'Q':
|
|
toAdd = CastlingRights::WhiteQueenSide;
|
|
break;
|
|
case 'k':
|
|
toAdd = CastlingRights::BlackKingSide;
|
|
break;
|
|
case 'q':
|
|
toAdd = CastlingRights::BlackQueenSide;
|
|
break;
|
|
}
|
|
|
|
// If there are duplicated castling rights specification we bail.
|
|
// If there is an invalid character we bail.
|
|
// (It always contains None)
|
|
if (contains(rights, toAdd)) return {};
|
|
else rights |= toAdd;
|
|
}
|
|
|
|
return rights;
|
|
}
|
|
|
|
[[nodiscard]] constexpr CastlingRights readCastlingRights(const char*& s)
|
|
{
|
|
CastlingRights rights = CastlingRights::None;
|
|
|
|
while (*s != ' ')
|
|
{
|
|
switch (*s)
|
|
{
|
|
case 'K':
|
|
rights |= CastlingRights::WhiteKingSide;
|
|
break;
|
|
case 'Q':
|
|
rights |= CastlingRights::WhiteQueenSide;
|
|
break;
|
|
case 'k':
|
|
rights |= CastlingRights::BlackKingSide;
|
|
break;
|
|
case 'q':
|
|
rights |= CastlingRights::BlackQueenSide;
|
|
break;
|
|
}
|
|
|
|
++s;
|
|
}
|
|
|
|
return rights;
|
|
}
|
|
|
|
FORCEINLINE inline void appendCastlingRightsToString(CastlingRights rights, std::string& str)
|
|
{
|
|
if (rights == CastlingRights::None)
|
|
{
|
|
str += '-';
|
|
}
|
|
else
|
|
{
|
|
if (contains(rights, CastlingRights::WhiteKingSide)) str += 'K';
|
|
if (contains(rights, CastlingRights::WhiteQueenSide)) str += 'Q';
|
|
if (contains(rights, CastlingRights::BlackKingSide)) str += 'k';
|
|
if (contains(rights, CastlingRights::BlackQueenSide)) str += 'q';
|
|
}
|
|
}
|
|
|
|
FORCEINLINE inline void appendSquareToString(Square sq, std::string& str)
|
|
{
|
|
str += static_cast<char>('a' + ordinal(sq.file()));
|
|
str += static_cast<char>('1' + ordinal(sq.rank()));
|
|
}
|
|
|
|
FORCEINLINE inline void appendEpSquareToString(Square sq, std::string& str)
|
|
{
|
|
if (sq == Square::none())
|
|
{
|
|
str += '-';
|
|
}
|
|
else
|
|
{
|
|
appendSquareToString(sq, str);
|
|
}
|
|
}
|
|
|
|
FORCEINLINE inline void appendRankToString(Rank r, std::string& str)
|
|
{
|
|
str += static_cast<char>('1' + ordinal(r));
|
|
}
|
|
|
|
FORCEINLINE inline void appendFileToString(File f, std::string& str)
|
|
{
|
|
str += static_cast<char>('a' + ordinal(f));
|
|
}
|
|
|
|
[[nodiscard]] FORCEINLINE inline bool isDigit(char c)
|
|
{
|
|
return c >= '0' && c <= '9';
|
|
}
|
|
|
|
[[nodiscard]] inline std::uint16_t parseUInt16(std::string_view sv)
|
|
{
|
|
assert(sv.size() > 0);
|
|
assert(sv.size() <= 5);
|
|
|
|
std::uint16_t v = 0;
|
|
|
|
std::size_t idx = 0;
|
|
switch (sv.size())
|
|
{
|
|
case 5:
|
|
v += (sv[idx++] - '0') * 10000;
|
|
case 4:
|
|
v += (sv[idx++] - '0') * 1000;
|
|
case 3:
|
|
v += (sv[idx++] - '0') * 100;
|
|
case 2:
|
|
v += (sv[idx++] - '0') * 10;
|
|
case 1:
|
|
v += sv[idx] - '0';
|
|
break;
|
|
|
|
default:
|
|
assert(false);
|
|
}
|
|
|
|
return v;
|
|
}
|
|
|
|
[[nodiscard]] inline std::optional<std::uint16_t> tryParseUInt16(std::string_view sv)
|
|
{
|
|
if (sv.size() == 0 || sv.size() > 5) return std::nullopt;
|
|
|
|
std::uint32_t v = 0;
|
|
|
|
std::size_t idx = 0;
|
|
switch (sv.size())
|
|
{
|
|
case 5:
|
|
v += (sv[idx++] - '0') * 10000;
|
|
case 4:
|
|
v += (sv[idx++] - '0') * 1000;
|
|
case 3:
|
|
v += (sv[idx++] - '0') * 100;
|
|
case 2:
|
|
v += (sv[idx++] - '0') * 10;
|
|
case 1:
|
|
v += sv[idx] - '0';
|
|
break;
|
|
|
|
default:
|
|
assert(false);
|
|
}
|
|
|
|
if (v > std::numeric_limits<std::uint16_t>::max())
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
return static_cast<std::uint16_t>(v);
|
|
}
|
|
}
|
|
|
|
|
|
struct Board
|
|
{
|
|
constexpr Board() noexcept :
|
|
m_pieces{},
|
|
m_pieceBB{},
|
|
m_piecesByColorBB{},
|
|
m_pieceCount{}
|
|
{
|
|
m_pieces.fill(Piece::none());
|
|
m_pieceBB.fill(Bitboard::none());
|
|
m_pieceBB[Piece::none()] = Bitboard::all();
|
|
m_piecesByColorBB.fill(Bitboard::none());
|
|
m_pieceCount.fill(0);
|
|
m_pieceCount[Piece::none()] = 64;
|
|
}
|
|
|
|
[[nodiscard]] inline bool isValid() const
|
|
{
|
|
if (piecesBB(whiteKing).count() != 1) return false;
|
|
if (piecesBB(blackKing).count() != 1) return false;
|
|
if (((piecesBB(whitePawn) | piecesBB(blackPawn)) & (bb::rank(rank1) | bb::rank(rank8))).any()) return false;
|
|
return true;
|
|
}
|
|
|
|
[[nodiscard]] inline std::string fen() const;
|
|
|
|
[[nodiscard]] inline bool trySet(std::string_view boardState)
|
|
{
|
|
File f = fileA;
|
|
Rank r = rank8;
|
|
bool lastWasSkip = false;
|
|
for (auto c : boardState)
|
|
{
|
|
Piece piece = Piece::none();
|
|
switch (c)
|
|
{
|
|
case 'r':
|
|
piece = Piece(PieceType::Rook, Color::Black);
|
|
break;
|
|
case 'n':
|
|
piece = Piece(PieceType::Knight, Color::Black);
|
|
break;
|
|
case 'b':
|
|
piece = Piece(PieceType::Bishop, Color::Black);
|
|
break;
|
|
case 'q':
|
|
piece = Piece(PieceType::Queen, Color::Black);
|
|
break;
|
|
case 'k':
|
|
piece = Piece(PieceType::King, Color::Black);
|
|
break;
|
|
case 'p':
|
|
piece = Piece(PieceType::Pawn, Color::Black);
|
|
break;
|
|
|
|
case 'R':
|
|
piece = Piece(PieceType::Rook, Color::White);
|
|
break;
|
|
case 'N':
|
|
piece = Piece(PieceType::Knight, Color::White);
|
|
break;
|
|
case 'B':
|
|
piece = Piece(PieceType::Bishop, Color::White);
|
|
break;
|
|
case 'Q':
|
|
piece = Piece(PieceType::Queen, Color::White);
|
|
break;
|
|
case 'K':
|
|
piece = Piece(PieceType::King, Color::White);
|
|
break;
|
|
case 'P':
|
|
piece = Piece(PieceType::Pawn, Color::White);
|
|
break;
|
|
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
{
|
|
if (lastWasSkip) return false;
|
|
lastWasSkip = true;
|
|
|
|
const int skip = c - '0';
|
|
f += skip;
|
|
if (f > fileH + 1) return false;
|
|
break;
|
|
}
|
|
|
|
case '/':
|
|
lastWasSkip = false;
|
|
if (f != fileH + 1) return false;
|
|
f = fileA;
|
|
--r;
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
if (piece != Piece::none())
|
|
{
|
|
lastWasSkip = false;
|
|
|
|
const Square sq(f, r);
|
|
if (!sq.isOk()) return false;
|
|
|
|
place(piece, sq);
|
|
++f;
|
|
}
|
|
}
|
|
|
|
if (f != fileH + 1) return false;
|
|
if (r != rank1) return false;
|
|
|
|
return isValid();
|
|
}
|
|
|
|
// returns side to move
|
|
[[nodiscard]] constexpr const char* set(const char* fen)
|
|
{
|
|
assert(fen != nullptr);
|
|
|
|
File f = fileA;
|
|
Rank r = rank8;
|
|
auto current = fen;
|
|
bool done = false;
|
|
while (*current != '\0')
|
|
{
|
|
Piece piece = Piece::none();
|
|
switch (*current)
|
|
{
|
|
case 'r':
|
|
piece = Piece(PieceType::Rook, Color::Black);
|
|
break;
|
|
case 'n':
|
|
piece = Piece(PieceType::Knight, Color::Black);
|
|
break;
|
|
case 'b':
|
|
piece = Piece(PieceType::Bishop, Color::Black);
|
|
break;
|
|
case 'q':
|
|
piece = Piece(PieceType::Queen, Color::Black);
|
|
break;
|
|
case 'k':
|
|
piece = Piece(PieceType::King, Color::Black);
|
|
break;
|
|
case 'p':
|
|
piece = Piece(PieceType::Pawn, Color::Black);
|
|
break;
|
|
|
|
case 'R':
|
|
piece = Piece(PieceType::Rook, Color::White);
|
|
break;
|
|
case 'N':
|
|
piece = Piece(PieceType::Knight, Color::White);
|
|
break;
|
|
case 'B':
|
|
piece = Piece(PieceType::Bishop, Color::White);
|
|
break;
|
|
case 'Q':
|
|
piece = Piece(PieceType::Queen, Color::White);
|
|
break;
|
|
case 'K':
|
|
piece = Piece(PieceType::King, Color::White);
|
|
break;
|
|
case 'P':
|
|
piece = Piece(PieceType::Pawn, Color::White);
|
|
break;
|
|
|
|
case ' ':
|
|
done = true;
|
|
break;
|
|
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
{
|
|
const int skip = (*current) - '0';
|
|
f += skip;
|
|
break;
|
|
}
|
|
|
|
case '/':
|
|
f = fileA;
|
|
--r;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (done)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (piece != Piece::none())
|
|
{
|
|
place(piece, Square(f, r));
|
|
++f;
|
|
}
|
|
|
|
++current;
|
|
}
|
|
|
|
return current;
|
|
}
|
|
|
|
static constexpr Board fromFen(const char* fen)
|
|
{
|
|
Board board;
|
|
(void)board.set(fen);
|
|
return board;
|
|
}
|
|
|
|
[[nodiscard]] constexpr friend bool operator==(const Board& lhs, const Board& rhs) noexcept
|
|
{
|
|
bool equal = true;
|
|
for (Square sq = a1; sq <= h8; ++sq)
|
|
{
|
|
if (lhs.m_pieces[sq] != rhs.m_pieces[sq])
|
|
{
|
|
equal = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
assert(bbsEqual(lhs, rhs) == equal);
|
|
|
|
return equal;
|
|
}
|
|
|
|
constexpr void place(Piece piece, Square sq)
|
|
{
|
|
assert(sq.isOk());
|
|
|
|
auto oldPiece = m_pieces[sq];
|
|
m_pieceBB[oldPiece] ^= sq;
|
|
if (oldPiece != Piece::none())
|
|
{
|
|
m_piecesByColorBB[oldPiece.color()] ^= sq;
|
|
}
|
|
m_pieces[sq] = piece;
|
|
m_pieceBB[piece] |= sq;
|
|
m_piecesByColorBB[piece.color()] |= sq;
|
|
--m_pieceCount[oldPiece];
|
|
++m_pieceCount[piece];
|
|
}
|
|
|
|
// returns captured piece
|
|
// doesn't check validity
|
|
inline constexpr Piece doMove(Move move)
|
|
{
|
|
if (move.type == MoveType::Normal)
|
|
{
|
|
const Piece capturedPiece = m_pieces[move.to];
|
|
const Piece piece = m_pieces[move.from];
|
|
|
|
const Bitboard frombb = Bitboard::square(move.from);
|
|
const Bitboard tobb = Bitboard::square(move.to);
|
|
const Bitboard xormove = frombb ^ tobb;
|
|
|
|
m_pieces[move.to] = piece;
|
|
m_pieces[move.from] = Piece::none();
|
|
|
|
m_pieceBB[piece] ^= xormove;
|
|
|
|
m_piecesByColorBB[piece.color()] ^= xormove;
|
|
|
|
if (capturedPiece == Piece::none())
|
|
{
|
|
m_pieceBB[Piece::none()] ^= xormove;
|
|
}
|
|
else
|
|
{
|
|
m_pieceBB[capturedPiece] ^= tobb;
|
|
m_pieceBB[Piece::none()] ^= frombb;
|
|
|
|
m_piecesByColorBB[capturedPiece.color()] ^= tobb;
|
|
|
|
--m_pieceCount[capturedPiece];
|
|
++m_pieceCount[Piece::none()];
|
|
}
|
|
|
|
return capturedPiece;
|
|
}
|
|
|
|
return doMoveColdPath(move);
|
|
}
|
|
|
|
inline constexpr Piece doMoveColdPath(Move move)
|
|
{
|
|
if (move.type == MoveType::Promotion)
|
|
{
|
|
// We split it even though it's similar just because
|
|
// the normal case is much more common.
|
|
const Piece capturedPiece = m_pieces[move.to];
|
|
const Piece fromPiece = m_pieces[move.from];
|
|
const Piece toPiece = move.promotedPiece;
|
|
|
|
m_pieces[move.to] = toPiece;
|
|
m_pieces[move.from] = Piece::none();
|
|
|
|
m_pieceBB[fromPiece] ^= move.from;
|
|
m_pieceBB[toPiece] ^= move.to;
|
|
|
|
m_pieceBB[capturedPiece] ^= move.to;
|
|
m_pieceBB[Piece::none()] ^= move.from;
|
|
|
|
m_piecesByColorBB[fromPiece.color()] ^= move.to;
|
|
m_piecesByColorBB[fromPiece.color()] ^= move.from;
|
|
if (capturedPiece != Piece::none())
|
|
{
|
|
m_piecesByColorBB[capturedPiece.color()] ^= move.to;
|
|
--m_pieceCount[capturedPiece];
|
|
++m_pieceCount[Piece::none()];
|
|
}
|
|
|
|
--m_pieceCount[fromPiece];
|
|
++m_pieceCount[toPiece];
|
|
|
|
return capturedPiece;
|
|
}
|
|
else if (move.type == MoveType::EnPassant)
|
|
{
|
|
const Piece movedPiece = m_pieces[move.from];
|
|
const Piece capturedPiece(PieceType::Pawn, !movedPiece.color());
|
|
const Square capturedPieceSq(move.to.file(), move.from.rank());
|
|
|
|
// on ep move there are 3 squares involved
|
|
m_pieces[move.to] = movedPiece;
|
|
m_pieces[move.from] = Piece::none();
|
|
m_pieces[capturedPieceSq] = Piece::none();
|
|
|
|
m_pieceBB[movedPiece] ^= move.from;
|
|
m_pieceBB[movedPiece] ^= move.to;
|
|
|
|
m_pieceBB[Piece::none()] ^= move.from;
|
|
m_pieceBB[Piece::none()] ^= move.to;
|
|
|
|
m_pieceBB[capturedPiece] ^= capturedPieceSq;
|
|
m_pieceBB[Piece::none()] ^= capturedPieceSq;
|
|
|
|
m_piecesByColorBB[movedPiece.color()] ^= move.to;
|
|
m_piecesByColorBB[movedPiece.color()] ^= move.from;
|
|
m_piecesByColorBB[capturedPiece.color()] ^= capturedPieceSq;
|
|
|
|
--m_pieceCount[capturedPiece];
|
|
++m_pieceCount[Piece::none()];
|
|
|
|
return capturedPiece;
|
|
}
|
|
else // if (move.type == MoveType::Castle)
|
|
{
|
|
const Square rookFromSq = move.to;
|
|
const Square kingFromSq = move.from;
|
|
|
|
const Piece rook = m_pieces[rookFromSq];
|
|
const Piece king = m_pieces[kingFromSq];
|
|
const Color color = king.color();
|
|
|
|
const CastleType castleType = CastlingTraits::moveCastlingType(move);
|
|
const Square rookToSq = CastlingTraits::rookDestination[color][castleType];
|
|
const Square kingToSq = CastlingTraits::kingDestination[color][castleType];
|
|
|
|
// 4 squares are involved
|
|
m_pieces[rookFromSq] = Piece::none();
|
|
m_pieces[kingFromSq] = Piece::none();
|
|
m_pieces[rookToSq] = rook;
|
|
m_pieces[kingToSq] = king;
|
|
|
|
m_pieceBB[rook] ^= rookFromSq;
|
|
m_pieceBB[rook] ^= rookToSq;
|
|
|
|
m_pieceBB[king] ^= kingFromSq;
|
|
m_pieceBB[king] ^= kingToSq;
|
|
|
|
m_pieceBB[Piece::none()] ^= rookFromSq;
|
|
m_pieceBB[Piece::none()] ^= rookToSq;
|
|
|
|
m_pieceBB[Piece::none()] ^= kingFromSq;
|
|
m_pieceBB[Piece::none()] ^= kingToSq;
|
|
|
|
m_piecesByColorBB[color] ^= rookFromSq;
|
|
m_piecesByColorBB[color] ^= rookToSq;
|
|
m_piecesByColorBB[color] ^= kingFromSq;
|
|
m_piecesByColorBB[color] ^= kingToSq;
|
|
|
|
return Piece::none();
|
|
}
|
|
}
|
|
|
|
constexpr void undoMove(Move move, Piece capturedPiece)
|
|
{
|
|
if (move.type == MoveType::Normal || move.type == MoveType::Promotion)
|
|
{
|
|
const Piece toPiece = m_pieces[move.to];
|
|
const Piece fromPiece = move.promotedPiece == Piece::none() ? toPiece : Piece(PieceType::Pawn, toPiece.color());
|
|
|
|
m_pieces[move.from] = fromPiece;
|
|
m_pieces[move.to] = capturedPiece;
|
|
|
|
m_pieceBB[fromPiece] ^= move.from;
|
|
m_pieceBB[toPiece] ^= move.to;
|
|
|
|
m_pieceBB[capturedPiece] ^= move.to;
|
|
m_pieceBB[Piece::none()] ^= move.from;
|
|
|
|
m_piecesByColorBB[fromPiece.color()] ^= move.to;
|
|
m_piecesByColorBB[fromPiece.color()] ^= move.from;
|
|
if (capturedPiece != Piece::none())
|
|
{
|
|
m_piecesByColorBB[capturedPiece.color()] ^= move.to;
|
|
++m_pieceCount[capturedPiece];
|
|
--m_pieceCount[Piece::none()];
|
|
}
|
|
|
|
if (move.type == MoveType::Promotion)
|
|
{
|
|
--m_pieceCount[toPiece];
|
|
++m_pieceCount[fromPiece];
|
|
}
|
|
}
|
|
else if (move.type == MoveType::EnPassant)
|
|
{
|
|
const Piece movedPiece = m_pieces[move.to];
|
|
const Piece capturedPiece_(PieceType::Pawn, !movedPiece.color());
|
|
const Square capturedPieceSq(move.to.file(), move.from.rank());
|
|
|
|
m_pieces[move.to] = Piece::none();
|
|
m_pieces[move.from] = movedPiece;
|
|
m_pieces[capturedPieceSq] = capturedPiece_;
|
|
|
|
m_pieceBB[movedPiece] ^= move.from;
|
|
m_pieceBB[movedPiece] ^= move.to;
|
|
|
|
m_pieceBB[Piece::none()] ^= move.from;
|
|
m_pieceBB[Piece::none()] ^= move.to;
|
|
|
|
// on ep move there are 3 squares involved
|
|
m_pieceBB[capturedPiece_] ^= capturedPieceSq;
|
|
m_pieceBB[Piece::none()] ^= capturedPieceSq;
|
|
|
|
m_piecesByColorBB[movedPiece.color()] ^= move.to;
|
|
m_piecesByColorBB[movedPiece.color()] ^= move.from;
|
|
m_piecesByColorBB[capturedPiece_.color()] ^= capturedPieceSq;
|
|
|
|
++m_pieceCount[capturedPiece_];
|
|
--m_pieceCount[Piece::none()];
|
|
}
|
|
else // if (move.type == MoveType::Castle)
|
|
{
|
|
const Square rookFromSq = move.to;
|
|
const Square kingFromSq = move.from;
|
|
|
|
const Color color = move.to.rank() == rank1 ? Color::White : Color::Black;
|
|
|
|
const CastleType castleType = CastlingTraits::moveCastlingType(move);
|
|
const Square rookToSq = CastlingTraits::rookDestination[color][castleType];
|
|
const Square kingToSq = CastlingTraits::kingDestination[color][castleType];
|
|
|
|
const Piece rook = m_pieces[rookToSq];
|
|
const Piece king = m_pieces[kingToSq];
|
|
|
|
// 4 squares are involved
|
|
m_pieces[rookFromSq] = rook;
|
|
m_pieces[kingFromSq] = king;
|
|
m_pieces[rookToSq] = Piece::none();
|
|
m_pieces[kingToSq] = Piece::none();
|
|
|
|
m_pieceBB[rook] ^= rookFromSq;
|
|
m_pieceBB[rook] ^= rookToSq;
|
|
|
|
m_pieceBB[king] ^= kingFromSq;
|
|
m_pieceBB[king] ^= kingToSq;
|
|
|
|
m_pieceBB[Piece::none()] ^= rookFromSq;
|
|
m_pieceBB[Piece::none()] ^= rookToSq;
|
|
|
|
m_pieceBB[Piece::none()] ^= kingFromSq;
|
|
m_pieceBB[Piece::none()] ^= kingToSq;
|
|
|
|
m_piecesByColorBB[color] ^= rookFromSq;
|
|
m_piecesByColorBB[color] ^= rookToSq;
|
|
m_piecesByColorBB[color] ^= kingFromSq;
|
|
m_piecesByColorBB[color] ^= kingToSq;
|
|
}
|
|
}
|
|
|
|
// Returns whether a given square is attacked by any piece
|
|
// of `attackerColor` side.
|
|
[[nodiscard]] inline bool isSquareAttacked(Square sq, Color attackerColor) const;
|
|
|
|
// Returns whether a given square is attacked by any piece
|
|
// of `attackerColor` side after `move` is made.
|
|
// Move must be pseudo legal.
|
|
[[nodiscard]] inline bool isSquareAttackedAfterMove(Move move, Square sq, Color attackerColor) const;
|
|
|
|
// Move must be pseudo legal.
|
|
// Must not be a king move.
|
|
[[nodiscard]] inline bool createsDiscoveredAttackOnOwnKing(Move move) const;
|
|
|
|
// Returns whether a piece on a given square is attacked
|
|
// by any enemy piece. False if square is empty.
|
|
[[nodiscard]] inline bool isPieceAttacked(Square sq) const;
|
|
|
|
// Returns whether a piece on a given square is attacked
|
|
// by any enemy piece after `move` is made. False if square is empty.
|
|
// Move must be pseudo legal.
|
|
[[nodiscard]] inline bool isPieceAttackedAfterMove(Move move, Square sq) const;
|
|
|
|
// Returns whether the king of the moving side is attacked
|
|
// by any enemy piece after a move is made.
|
|
// Move must be pseudo legal.
|
|
[[nodiscard]] inline bool isOwnKingAttackedAfterMove(Move move) const;
|
|
|
|
// Return a bitboard with all (pseudo legal) attacks by the piece on
|
|
// the given square. Empty if no piece on the square.
|
|
[[nodiscard]] inline Bitboard attacks(Square sq) const;
|
|
|
|
// Returns a bitboard with all squared that have pieces
|
|
// that attack a given square (pseudo legally)
|
|
[[nodiscard]] inline Bitboard attackers(Square sq, Color attackerColor) const;
|
|
|
|
[[nodiscard]] constexpr Piece pieceAt(Square sq) const
|
|
{
|
|
assert(sq.isOk());
|
|
|
|
return m_pieces[sq];
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard piecesBB(Color c) const
|
|
{
|
|
return m_piecesByColorBB[c];
|
|
}
|
|
|
|
[[nodiscard]] inline Square kingSquare(Color c) const
|
|
{
|
|
return piecesBB(Piece(PieceType::King, c)).first();
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard piecesBB(Piece pc) const
|
|
{
|
|
return m_pieceBB[pc];
|
|
}
|
|
|
|
[[nodiscard]] constexpr Bitboard piecesBB() const
|
|
{
|
|
Bitboard bb{};
|
|
|
|
// don't collect from null piece
|
|
return piecesBB(Color::White) | piecesBB(Color::Black);
|
|
|
|
return bb;
|
|
}
|
|
|
|
[[nodiscard]] constexpr std::uint8_t pieceCount(Piece pt) const
|
|
{
|
|
return m_pieceCount[pt];
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool isPromotion(Square from, Square to) const
|
|
{
|
|
assert(from.isOk() && to.isOk());
|
|
|
|
return m_pieces[from].type() == PieceType::Pawn && (to.rank() == rank1 || to.rank() == rank8);
|
|
}
|
|
|
|
const Piece* piecesRaw() const;
|
|
|
|
private:
|
|
EnumArray<Square, Piece> m_pieces;
|
|
EnumArray<Piece, Bitboard> m_pieceBB;
|
|
EnumArray<Color, Bitboard> m_piecesByColorBB;
|
|
EnumArray<Piece, uint8_t> m_pieceCount;
|
|
|
|
// NOTE: currently we don't track it because it's not
|
|
// required to perform ep if we don't need to check validity
|
|
// Square m_epSquare = Square::none();
|
|
|
|
[[nodiscard]] static constexpr bool bbsEqual(const Board& lhs, const Board& rhs) noexcept
|
|
{
|
|
for (Piece pc : values<Piece>())
|
|
{
|
|
if (lhs.m_pieceBB[pc] != rhs.m_pieceBB[pc])
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
struct Position;
|
|
|
|
struct CompressedPosition;
|
|
|
|
struct PositionHash128
|
|
{
|
|
std::uint64_t high;
|
|
std::uint64_t low;
|
|
};
|
|
|
|
struct Position;
|
|
|
|
struct MoveLegalityChecker
|
|
{
|
|
MoveLegalityChecker(const Position& position);
|
|
|
|
[[nodiscard]] bool isPseudoLegalMoveLegal(const Move& move) const;
|
|
|
|
private:
|
|
const Position* m_position;
|
|
Bitboard m_checkers;
|
|
Bitboard m_ourBlockersForKing;
|
|
Bitboard m_potentialCheckRemovals;
|
|
Square m_ksq;
|
|
};
|
|
|
|
struct Position : public Board
|
|
{
|
|
using BaseType = Board;
|
|
|
|
constexpr Position() noexcept :
|
|
Board(),
|
|
m_sideToMove(Color::White),
|
|
m_epSquare(Square::none()),
|
|
m_castlingRights(CastlingRights::All),
|
|
m_rule50Counter(0),
|
|
m_ply(0)
|
|
{
|
|
}
|
|
|
|
constexpr Position(const Board& board, Color sideToMove, Square epSquare, CastlingRights castlingRights) :
|
|
Board(board),
|
|
m_sideToMove(sideToMove),
|
|
m_epSquare(epSquare),
|
|
m_castlingRights(castlingRights),
|
|
m_rule50Counter(0),
|
|
m_ply(0)
|
|
{
|
|
}
|
|
|
|
inline void set(std::string_view fen);
|
|
|
|
// Returns false if the fen was not valid
|
|
// If the returned value was false the position
|
|
// is in unspecified state.
|
|
[[nodiscard]] inline bool trySet(std::string_view fen);
|
|
|
|
[[nodiscard]] static inline Position fromFen(std::string_view fen);
|
|
|
|
[[nodiscard]] static inline std::optional<Position> tryFromFen(std::string_view fen);
|
|
|
|
[[nodiscard]] static inline Position startPosition();
|
|
|
|
[[nodiscard]] inline std::string fen() const;
|
|
|
|
[[nodiscard]] MoveLegalityChecker moveLegalityChecker() const
|
|
{
|
|
return { *this };
|
|
}
|
|
|
|
constexpr void setEpSquareUnchecked(Square sq)
|
|
{
|
|
m_epSquare = sq;
|
|
}
|
|
|
|
void setEpSquare(Square sq)
|
|
{
|
|
m_epSquare = sq;
|
|
nullifyEpSquareIfNotPossible();
|
|
}
|
|
|
|
constexpr void setSideToMove(Color color)
|
|
{
|
|
m_sideToMove = color;
|
|
}
|
|
|
|
constexpr void addCastlingRights(CastlingRights rights)
|
|
{
|
|
m_castlingRights |= rights;
|
|
}
|
|
|
|
constexpr void setCastlingRights(CastlingRights rights)
|
|
{
|
|
m_castlingRights = rights;
|
|
}
|
|
|
|
constexpr void setRule50Counter(std::uint8_t v)
|
|
{
|
|
m_rule50Counter = v;
|
|
}
|
|
|
|
constexpr void setPly(std::uint16_t ply)
|
|
{
|
|
m_ply = ply;
|
|
}
|
|
|
|
inline ReverseMove doMove(const Move& move);
|
|
|
|
constexpr void undoMove(const ReverseMove& reverseMove)
|
|
{
|
|
const Move& move = reverseMove.move;
|
|
BaseType::undoMove(move, reverseMove.capturedPiece);
|
|
|
|
m_epSquare = reverseMove.oldEpSquare;
|
|
m_castlingRights = reverseMove.oldCastlingRights;
|
|
|
|
m_sideToMove = !m_sideToMove;
|
|
|
|
--m_ply;
|
|
if (m_rule50Counter > 0)
|
|
{
|
|
m_rule50Counter -= 1;
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] constexpr Color sideToMove() const
|
|
{
|
|
return m_sideToMove;
|
|
}
|
|
|
|
[[nodiscard]] inline std::uint8_t rule50Counter() const
|
|
{
|
|
return m_rule50Counter;
|
|
}
|
|
|
|
[[nodiscard]] inline std::uint16_t ply() const
|
|
{
|
|
return m_ply;
|
|
}
|
|
|
|
[[nodiscard]] inline std::uint16_t fullMove() const
|
|
{
|
|
return m_ply / 2 + 1;
|
|
}
|
|
|
|
inline void setFullMove(std::uint16_t hm)
|
|
{
|
|
m_ply = 2 * (hm - 1) + (m_sideToMove == Color::Black);
|
|
}
|
|
|
|
[[nodiscard]] inline bool isCheck() const;
|
|
|
|
[[nodiscard]] inline Bitboard checkers() const;
|
|
|
|
[[nodiscard]] inline bool isCheckAfterMove(Move move) const;
|
|
|
|
[[nodiscard]] inline bool isMoveLegal(Move move) const;
|
|
|
|
[[nodiscard]] inline bool isPseudoLegalMoveLegal(Move move) const;
|
|
|
|
[[nodiscard]] inline bool isMovePseudoLegal(Move move) const;
|
|
|
|
// Returns all pieces that block a slider
|
|
// from attacking our king. When two or more
|
|
// pieces block a single slider then none
|
|
// of these pieces are included.
|
|
[[nodiscard]] inline Bitboard blockersForKing(Color color) const;
|
|
|
|
[[nodiscard]] constexpr Square epSquare() const
|
|
{
|
|
return m_epSquare;
|
|
}
|
|
|
|
[[nodiscard]] constexpr CastlingRights castlingRights() const
|
|
{
|
|
return m_castlingRights;
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool friend operator==(const Position& lhs, const Position& rhs) noexcept
|
|
{
|
|
return
|
|
lhs.m_sideToMove == rhs.m_sideToMove
|
|
&& lhs.m_epSquare == rhs.m_epSquare
|
|
&& lhs.m_castlingRights == rhs.m_castlingRights
|
|
&& static_cast<const Board&>(lhs) == static_cast<const Board&>(rhs);
|
|
}
|
|
|
|
[[nodiscard]] constexpr bool friend operator!=(const Position& lhs, const Position& rhs) noexcept
|
|
{
|
|
return !(lhs == rhs);
|
|
}
|
|
|
|
// these are supposed to be used only for testing
|
|
// that's why there's this assert in afterMove
|
|
|
|
[[nodiscard]] constexpr Position beforeMove(const ReverseMove& reverseMove) const
|
|
{
|
|
Position cpy(*this);
|
|
cpy.undoMove(reverseMove);
|
|
return cpy;
|
|
}
|
|
|
|
[[nodiscard]] inline Position afterMove(Move move) const;
|
|
|
|
[[nodiscard]] constexpr bool isEpPossible() const
|
|
{
|
|
return m_epSquare != Square::none();
|
|
}
|
|
|
|
[[nodiscard]] inline CompressedPosition compress() const;
|
|
|
|
protected:
|
|
Color m_sideToMove;
|
|
Square m_epSquare;
|
|
CastlingRights m_castlingRights;
|
|
std::uint8_t m_rule50Counter;
|
|
std::uint16_t m_ply;
|
|
|
|
static_assert(sizeof(Color) + sizeof(Square) + sizeof(CastlingRights) + sizeof(std::uint8_t) == 4);
|
|
|
|
[[nodiscard]] inline bool isEpPossible(Square epSquare, Color sideToMove) const;
|
|
|
|
[[nodiscard]] inline bool isEpPossibleColdPath(Square epSquare, Bitboard pawnsAttackingEpSquare, Color sideToMove) const;
|
|
|
|
inline void nullifyEpSquareIfNotPossible();
|
|
};
|
|
|
|
struct CompressedPosition
|
|
{
|
|
friend struct Position;
|
|
|
|
// Occupied bitboard has bits set for
|
|
// each square with a piece on it.
|
|
// Each packedState byte holds 2 values (nibbles).
|
|
// First one at low bits, second one at high bits.
|
|
// Values correspond to consecutive squares
|
|
// in bitboard iteration order.
|
|
// Nibble values:
|
|
// these are the same as for Piece
|
|
// knights, bishops, queens can just be copied
|
|
// 0 : white pawn
|
|
// 1 : black pawn
|
|
// 2 : white knight
|
|
// 3 : black knight
|
|
// 4 : white bishop
|
|
// 5 : black bishop
|
|
// 6 : white rook
|
|
// 7 : black rook
|
|
// 8 : white queen
|
|
// 9 : black queen
|
|
// 10 : white king
|
|
// 11 : black king
|
|
//
|
|
// these are special
|
|
// 12 : pawn with ep square behind (white or black, depending on rank)
|
|
// 13 : white rook with coresponding castling rights
|
|
// 14 : black rook with coresponding castling rights
|
|
// 15 : black king and black is side to move
|
|
//
|
|
// Let N be the number of bits set in occupied bitboard.
|
|
// Only N nibbles are present. (N+1)/2 bytes are initialized.
|
|
|
|
static CompressedPosition readFromBigEndian(const unsigned char* data)
|
|
{
|
|
CompressedPosition pos{};
|
|
pos.m_occupied = Bitboard::fromBits(
|
|
(std::uint64_t)data[0] << 56
|
|
| (std::uint64_t)data[1] << 48
|
|
| (std::uint64_t)data[2] << 40
|
|
| (std::uint64_t)data[3] << 32
|
|
| (std::uint64_t)data[4] << 24
|
|
| (std::uint64_t)data[5] << 16
|
|
| (std::uint64_t)data[6] << 8
|
|
| (std::uint64_t)data[7]
|
|
);
|
|
std::memcpy(pos.m_packedState, data + 8, 16);
|
|
return pos;
|
|
}
|
|
|
|
constexpr CompressedPosition() :
|
|
m_occupied{},
|
|
m_packedState{}
|
|
{
|
|
}
|
|
|
|
[[nodiscard]] friend bool operator<(const CompressedPosition& lhs, const CompressedPosition& rhs)
|
|
{
|
|
if (lhs.m_occupied.bits() < rhs.m_occupied.bits()) return true;
|
|
if (lhs.m_occupied.bits() > rhs.m_occupied.bits()) return false;
|
|
|
|
return std::strcmp(reinterpret_cast<const char*>(lhs.m_packedState), reinterpret_cast<const char*>(rhs.m_packedState)) < 0;
|
|
}
|
|
|
|
[[nodiscard]] friend bool operator==(const CompressedPosition& lhs, const CompressedPosition& rhs)
|
|
{
|
|
return lhs.m_occupied == rhs.m_occupied
|
|
&& std::strcmp(reinterpret_cast<const char*>(lhs.m_packedState), reinterpret_cast<const char*>(rhs.m_packedState)) == 0;
|
|
}
|
|
|
|
[[nodiscard]] inline Position decompress() const;
|
|
|
|
[[nodiscard]] constexpr Bitboard pieceBB() const
|
|
{
|
|
return m_occupied;
|
|
}
|
|
|
|
void writeToBigEndian(unsigned char* data)
|
|
{
|
|
const auto occupied = m_occupied.bits();
|
|
*data++ = occupied >> 56;
|
|
*data++ = (occupied >> 48) & 0xFF;
|
|
*data++ = (occupied >> 40) & 0xFF;
|
|
*data++ = (occupied >> 32) & 0xFF;
|
|
*data++ = (occupied >> 24) & 0xFF;
|
|
*data++ = (occupied >> 16) & 0xFF;
|
|
*data++ = (occupied >> 8) & 0xFF;
|
|
*data++ = occupied & 0xFF;
|
|
std::memcpy(data, m_packedState, 16);
|
|
}
|
|
|
|
private:
|
|
Bitboard m_occupied;
|
|
std::uint8_t m_packedState[16];
|
|
};
|
|
|
|
namespace movegen
|
|
{
|
|
// For a pseudo-legal move the following are true:
|
|
// - the moving piece has the pos.sideToMove() color
|
|
// - the destination square is either empty or has a piece of the opposite color
|
|
// - if it is a pawn move it is valid (but may be illegal due to discovered checks)
|
|
// - if it is not a pawn move then the destination square is contained in attacks()
|
|
// - if it is a castling it is legal
|
|
// - a move other than castling may create a discovered attack on the king
|
|
// - a king may walk into a check
|
|
|
|
template <typename FuncT>
|
|
inline void forEachPseudoLegalPawnMove(const Position& pos, Square from, FuncT&& f)
|
|
{
|
|
const Color sideToMove = pos.sideToMove();
|
|
const Square epSquare = pos.epSquare();
|
|
const Bitboard ourPieces = pos.piecesBB(sideToMove);
|
|
const Bitboard theirPieces = pos.piecesBB(!sideToMove);
|
|
const Bitboard occupied = ourPieces | theirPieces;
|
|
|
|
Bitboard attackTargets = theirPieces;
|
|
if (epSquare != Square::none())
|
|
{
|
|
attackTargets |= epSquare;
|
|
}
|
|
|
|
const Bitboard attacks = bb::pawnAttacks(Bitboard::square(from), sideToMove) & attackTargets;
|
|
|
|
const Rank secondToLastRank = sideToMove == Color::White ? rank7 : rank2;
|
|
const auto forward = sideToMove == Color::White ? FlatSquareOffset(0, 1) : FlatSquareOffset(0, -1);
|
|
|
|
// promotions
|
|
if (from.rank() == secondToLastRank)
|
|
{
|
|
// capture promotions
|
|
for (Square toSq : attacks)
|
|
{
|
|
for (PieceType pt : { PieceType::Knight, PieceType::Bishop, PieceType::Rook, PieceType::Queen })
|
|
{
|
|
Move move{ from, toSq, MoveType::Promotion, Piece(pt, sideToMove) };
|
|
f(move);
|
|
}
|
|
}
|
|
|
|
// push promotions
|
|
const Square toSq = from + forward;
|
|
if (!occupied.isSet(toSq))
|
|
{
|
|
for (PieceType pt : { PieceType::Knight, PieceType::Bishop, PieceType::Rook, PieceType::Queen })
|
|
{
|
|
Move move{ from, toSq, MoveType::Promotion, Piece(pt, sideToMove) };
|
|
f(move);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// captures
|
|
for (Square toSq : attacks)
|
|
{
|
|
Move move{ from, toSq, (toSq == epSquare) ? MoveType::EnPassant : MoveType::Normal };
|
|
f(move);
|
|
}
|
|
|
|
const Square toSq = from + forward;
|
|
|
|
// single push
|
|
if (!occupied.isSet(toSq))
|
|
{
|
|
const Rank startRank = sideToMove == Color::White ? rank2 : rank7;
|
|
if (from.rank() == startRank)
|
|
{
|
|
// double push
|
|
const Square toSq2 = toSq + forward;
|
|
if (!occupied.isSet(toSq2))
|
|
{
|
|
Move move{ from, toSq2 };
|
|
f(move);
|
|
}
|
|
}
|
|
|
|
Move move{ from, toSq };
|
|
f(move);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <Color SideToMoveV, typename FuncT>
|
|
inline void forEachPseudoLegalPawnMove(const Position& pos, FuncT&& f)
|
|
{
|
|
const Square epSquare = pos.epSquare();
|
|
const Bitboard ourPieces = pos.piecesBB(SideToMoveV);
|
|
const Bitboard theirPieces = pos.piecesBB(!SideToMoveV);
|
|
const Bitboard occupied = ourPieces | theirPieces;
|
|
const Bitboard pawns = pos.piecesBB(Piece(PieceType::Pawn, SideToMoveV));
|
|
|
|
const Bitboard secondToLastRank = SideToMoveV == Color::White ? bb::rank7 : bb::rank2;
|
|
const Bitboard secondRank = SideToMoveV == Color::White ? bb::rank2 : bb::rank7;
|
|
|
|
const auto singlePawnMoveDestinationOffset = SideToMoveV == Color::White ? FlatSquareOffset(0, 1) : FlatSquareOffset(0, -1);
|
|
const auto doublePawnMoveDestinationOffset = SideToMoveV == Color::White ? FlatSquareOffset(0, 2) : FlatSquareOffset(0, -2);
|
|
|
|
{
|
|
const int backward = SideToMoveV == Color::White ? -1 : 1;
|
|
const int backward2 = backward * 2;
|
|
|
|
const Bitboard doublePawnMoveStarts =
|
|
pawns
|
|
& secondRank
|
|
& ~(occupied.shiftedVertically(backward) | occupied.shiftedVertically(backward2));
|
|
|
|
const Bitboard singlePawnMoveStarts =
|
|
pawns
|
|
& ~secondToLastRank
|
|
& ~occupied.shiftedVertically(backward);
|
|
|
|
for (Square from : doublePawnMoveStarts)
|
|
{
|
|
const Square to = from + doublePawnMoveDestinationOffset;
|
|
f(Move::normal(from, to));
|
|
}
|
|
|
|
for (Square from : singlePawnMoveStarts)
|
|
{
|
|
const Square to = from + singlePawnMoveDestinationOffset;
|
|
f(Move::normal(from, to));
|
|
}
|
|
}
|
|
|
|
{
|
|
const Bitboard lastRank = SideToMoveV == Color::White ? bb::rank8 : bb::rank1;
|
|
const FlatSquareOffset westCaptureOffset = SideToMoveV == Color::White ? FlatSquareOffset(-1, 1) : FlatSquareOffset(-1, -1);
|
|
const FlatSquareOffset eastCaptureOffset = SideToMoveV == Color::White ? FlatSquareOffset(1, 1) : FlatSquareOffset(1, -1);
|
|
|
|
const Bitboard pawnsWithWestCapture = bb::eastPawnAttacks(theirPieces & ~lastRank, !SideToMoveV) & pawns;
|
|
const Bitboard pawnsWithEastCapture = bb::westPawnAttacks(theirPieces & ~lastRank, !SideToMoveV) & pawns;
|
|
|
|
for (Square from : pawnsWithWestCapture)
|
|
{
|
|
f(Move::normal(from, from + westCaptureOffset));
|
|
}
|
|
|
|
for (Square from : pawnsWithEastCapture)
|
|
{
|
|
f(Move::normal(from, from + eastCaptureOffset));
|
|
}
|
|
}
|
|
|
|
if (epSquare != Square::none())
|
|
{
|
|
const Bitboard pawnsThatCanCapture = bb::pawnAttacks(Bitboard::square(epSquare), !SideToMoveV) & pawns;
|
|
for (Square from : pawnsThatCanCapture)
|
|
{
|
|
f(Move::enPassant(from, epSquare));
|
|
}
|
|
}
|
|
|
|
for (Square from : pawns & secondToLastRank)
|
|
{
|
|
const Bitboard attacks = bb::pawnAttacks(Bitboard::square(from), SideToMoveV) & theirPieces;
|
|
|
|
// capture promotions
|
|
for (Square to : attacks)
|
|
{
|
|
for (PieceType pt : { PieceType::Knight, PieceType::Bishop, PieceType::Rook, PieceType::Queen })
|
|
{
|
|
Move move{ from, to, MoveType::Promotion, Piece(pt, SideToMoveV) };
|
|
f(move);
|
|
}
|
|
}
|
|
|
|
// push promotions
|
|
const Square to = from + singlePawnMoveDestinationOffset;
|
|
if (!occupied.isSet(to))
|
|
{
|
|
for (PieceType pt : { PieceType::Knight, PieceType::Bishop, PieceType::Rook, PieceType::Queen })
|
|
{
|
|
Move move{ from, to, MoveType::Promotion, Piece(pt, SideToMoveV) };
|
|
f(move);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename FuncT>
|
|
inline void forEachPseudoLegalPawnMove(const Position& pos, FuncT&& f)
|
|
{
|
|
if (pos.sideToMove() == Color::White)
|
|
{
|
|
forEachPseudoLegalPawnMove<Color::White>(pos, std::forward<FuncT>(f));
|
|
}
|
|
else
|
|
{
|
|
forEachPseudoLegalPawnMove<Color::Black>(pos, std::forward<FuncT>(f));
|
|
}
|
|
}
|
|
|
|
template <PieceType PieceTypeV, typename FuncT>
|
|
inline void forEachPseudoLegalPieceMove(const Position& pos, Square from, FuncT&& f)
|
|
{
|
|
static_assert(PieceTypeV != PieceType::None);
|
|
|
|
if constexpr (PieceTypeV == PieceType::Pawn)
|
|
{
|
|
forEachPseudoLegalPawnMove(pos, from, f);
|
|
}
|
|
else
|
|
{
|
|
const Color sideToMove = pos.sideToMove();
|
|
const Bitboard ourPieces = pos.piecesBB(sideToMove);
|
|
const Bitboard theirPieces = pos.piecesBB(!sideToMove);
|
|
const Bitboard occupied = ourPieces | theirPieces;
|
|
const Bitboard attacks = bb::attacks<PieceTypeV>(from, occupied) & ~ourPieces;
|
|
|
|
for (Square toSq : attacks)
|
|
{
|
|
Move move{ from, toSq };
|
|
f(move);
|
|
}
|
|
}
|
|
}
|
|
|
|
template <PieceType PieceTypeV, typename FuncT>
|
|
inline void forEachPseudoLegalPieceMove(const Position& pos, FuncT&& f)
|
|
{
|
|
static_assert(PieceTypeV != PieceType::None);
|
|
|
|
if constexpr (PieceTypeV == PieceType::Pawn)
|
|
{
|
|
forEachPseudoLegalPawnMove(pos, f);
|
|
}
|
|
else
|
|
{
|
|
const Color sideToMove = pos.sideToMove();
|
|
const Bitboard ourPieces = pos.piecesBB(sideToMove);
|
|
const Bitboard theirPieces = pos.piecesBB(!sideToMove);
|
|
const Bitboard occupied = ourPieces | theirPieces;
|
|
const Bitboard pieces = pos.piecesBB(Piece(PieceTypeV, sideToMove));
|
|
for (Square fromSq : pieces)
|
|
{
|
|
const Bitboard attacks = bb::attacks<PieceTypeV>(fromSq, occupied) & ~ourPieces;
|
|
for (Square toSq : attacks)
|
|
{
|
|
Move move{ fromSq, toSq };
|
|
f(move);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename FuncT>
|
|
inline void forEachCastlingMove(const Position& pos, FuncT&& f)
|
|
{
|
|
CastlingRights rights = pos.castlingRights();
|
|
if (rights == CastlingRights::None)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const Color sideToMove = pos.sideToMove();
|
|
const Bitboard ourPieces = pos.piecesBB(sideToMove);
|
|
const Bitboard theirPieces = pos.piecesBB(!sideToMove);
|
|
const Bitboard occupied = ourPieces | theirPieces;
|
|
|
|
// we first reduce the set of legal castlings by checking the paths for pieces
|
|
if (sideToMove == Color::White)
|
|
{
|
|
if ((CastlingTraits::castlingPath[Color::White][CastleType::Short] & occupied).any()) rights &= ~CastlingRights::WhiteKingSide;
|
|
if ((CastlingTraits::castlingPath[Color::White][CastleType::Long] & occupied).any()) rights &= ~CastlingRights::WhiteQueenSide;
|
|
rights &= ~CastlingRights::Black;
|
|
}
|
|
else
|
|
{
|
|
if ((CastlingTraits::castlingPath[Color::Black][CastleType::Short] & occupied).any()) rights &= ~CastlingRights::BlackKingSide;
|
|
if ((CastlingTraits::castlingPath[Color::Black][CastleType::Long] & occupied).any()) rights &= ~CastlingRights::BlackQueenSide;
|
|
rights &= ~CastlingRights::White;
|
|
}
|
|
|
|
if (rights == CastlingRights::None)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// King must not be in check. Done here because it is quite expensive.
|
|
const Square ksq = pos.kingSquare(sideToMove);
|
|
if (pos.isSquareAttacked(ksq, !sideToMove))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Loop through all possible castlings.
|
|
for (CastleType castlingType : values<CastleType>())
|
|
{
|
|
const CastlingRights right = CastlingTraits::castlingRights[sideToMove][castlingType];
|
|
|
|
if (!contains(rights, right))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If we have this castling right
|
|
// we check whether the king passes an attacked square.
|
|
const Square passedSquare = CastlingTraits::squarePassedByKing[sideToMove][castlingType];
|
|
if (pos.isSquareAttacked(passedSquare, !sideToMove))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If it's a castling move then the change in square occupation
|
|
// cannot have an effect because otherwise there would be
|
|
// a slider attacker attacking the castling king.
|
|
if (pos.isSquareAttacked(CastlingTraits::kingDestination[sideToMove][castlingType], !sideToMove))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If not we can castle.
|
|
Move move = Move::castle(castlingType, sideToMove);
|
|
f(move);
|
|
}
|
|
}
|
|
|
|
// Calls a given function for all pseudo legal moves for the position.
|
|
// `pos` must be a legal chess position
|
|
template <typename FuncT>
|
|
inline void forEachPseudoLegalMove(const Position& pos, FuncT&& func)
|
|
{
|
|
forEachPseudoLegalPieceMove<PieceType::Pawn>(pos, func);
|
|
forEachPseudoLegalPieceMove<PieceType::Knight>(pos, func);
|
|
forEachPseudoLegalPieceMove<PieceType::Bishop>(pos, func);
|
|
forEachPseudoLegalPieceMove<PieceType::Rook>(pos, func);
|
|
forEachPseudoLegalPieceMove<PieceType::Queen>(pos, func);
|
|
forEachPseudoLegalPieceMove<PieceType::King>(pos, func);
|
|
forEachCastlingMove(pos, func);
|
|
}
|
|
|
|
// Calls a given function for all legal moves for the position.
|
|
// `pos` must be a legal chess position
|
|
template <typename FuncT>
|
|
inline void forEachLegalMove(const Position& pos, FuncT&& func)
|
|
{
|
|
auto funcIfLegal = [&func, checker = pos.moveLegalityChecker()](Move move) {
|
|
if (checker.isPseudoLegalMoveLegal(move))
|
|
{
|
|
func(move);
|
|
}
|
|
};
|
|
|
|
forEachPseudoLegalPieceMove<PieceType::Pawn>(pos, funcIfLegal);
|
|
forEachPseudoLegalPieceMove<PieceType::Knight>(pos, funcIfLegal);
|
|
forEachPseudoLegalPieceMove<PieceType::Bishop>(pos, funcIfLegal);
|
|
forEachPseudoLegalPieceMove<PieceType::Rook>(pos, funcIfLegal);
|
|
forEachPseudoLegalPieceMove<PieceType::Queen>(pos, funcIfLegal);
|
|
forEachPseudoLegalPieceMove<PieceType::King>(pos, funcIfLegal);
|
|
forEachCastlingMove(pos, func);
|
|
}
|
|
|
|
// Generates all pseudo legal moves for the position.
|
|
// `pos` must be a legal chess position
|
|
[[nodiscard]] std::vector<Move> generatePseudoLegalMoves(const Position& pos);
|
|
|
|
// Generates all legal moves for the position.
|
|
// `pos` must be a legal chess position
|
|
[[nodiscard]] std::vector<Move> generateLegalMoves(const Position& pos);
|
|
}
|
|
|
|
[[nodiscard]] inline bool Position::isCheck() const
|
|
{
|
|
return BaseType::isSquareAttacked(kingSquare(m_sideToMove), !m_sideToMove);
|
|
}
|
|
|
|
[[nodiscard]] inline Bitboard Position::checkers() const
|
|
{
|
|
return BaseType::attackers(kingSquare(m_sideToMove), !m_sideToMove);
|
|
}
|
|
|
|
[[nodiscard]] inline bool Position::isCheckAfterMove(Move move) const
|
|
{
|
|
return BaseType::isSquareAttackedAfterMove(move, kingSquare(!m_sideToMove), m_sideToMove);
|
|
}
|
|
|
|
[[nodiscard]] inline bool Position::isMoveLegal(Move move) const
|
|
{
|
|
return
|
|
isMovePseudoLegal(move)
|
|
&& isPseudoLegalMoveLegal(move);
|
|
}
|
|
|
|
[[nodiscard]] inline bool Position::isPseudoLegalMoveLegal(Move move) const
|
|
{
|
|
return
|
|
(move.type == MoveType::Castle)
|
|
|| !isOwnKingAttackedAfterMove(move);
|
|
}
|
|
|
|
[[nodiscard]] inline bool Position::isMovePseudoLegal(Move move) const
|
|
{
|
|
if (!move.from.isOk() || !move.to.isOk())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (move.from == move.to)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (move.type != MoveType::Promotion && move.promotedPiece != Piece::none())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const Piece movedPiece = pieceAt(move.from);
|
|
if (movedPiece == Piece::none())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (movedPiece.color() != m_sideToMove)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const Bitboard occupied = piecesBB();
|
|
const Bitboard ourPieces = piecesBB(m_sideToMove);
|
|
const bool isNormal = move.type == MoveType::Normal;
|
|
|
|
switch (movedPiece.type())
|
|
{
|
|
case PieceType::Pawn:
|
|
{
|
|
bool isValid = false;
|
|
// TODO: use iterators so we don't loop over all moves
|
|
// when we can avoid it.
|
|
movegen::forEachPseudoLegalPawnMove(*this, move.from, [&isValid, &move](const Move& genMove) {
|
|
if (move == genMove)
|
|
{
|
|
isValid = true;
|
|
}
|
|
});
|
|
return isValid;
|
|
}
|
|
|
|
case PieceType::Bishop:
|
|
return isNormal && (bb::attacks<PieceType::Bishop>(move.from, occupied) & ~ourPieces).isSet(move.to);
|
|
|
|
case PieceType::Knight:
|
|
return isNormal && (bb::pseudoAttacks<PieceType::Knight>(move.from) & ~ourPieces).isSet(move.to);
|
|
|
|
case PieceType::Rook:
|
|
return isNormal && (bb::attacks<PieceType::Rook>(move.from, occupied) & ~ourPieces).isSet(move.to);
|
|
|
|
case PieceType::Queen:
|
|
return isNormal && (bb::attacks<PieceType::Queen>(move.from, occupied) & ~ourPieces).isSet(move.to);
|
|
|
|
case PieceType::King:
|
|
{
|
|
if (move.type == MoveType::Castle)
|
|
{
|
|
bool isValid = false;
|
|
movegen::forEachCastlingMove(*this, [&isValid, &move](const Move& genMove) {
|
|
if (move == genMove)
|
|
{
|
|
isValid = true;
|
|
}
|
|
});
|
|
return isValid;
|
|
}
|
|
else
|
|
{
|
|
return isNormal && (bb::pseudoAttacks<PieceType::King>(move.from) & ~ourPieces).isSet(move.to);
|
|
}
|
|
}
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] inline Bitboard Position::blockersForKing(Color color) const
|
|
{
|
|
const Color attackerColor = !color;
|
|
|
|
const Bitboard occupied = piecesBB();
|
|
|
|
const Bitboard bishops = piecesBB(Piece(PieceType::Bishop, attackerColor));
|
|
const Bitboard rooks = piecesBB(Piece(PieceType::Rook, attackerColor));
|
|
const Bitboard queens = piecesBB(Piece(PieceType::Queen, attackerColor));
|
|
|
|
const Square ksq = kingSquare(color);
|
|
|
|
const Bitboard opponentBishopLikePieces = (bishops | queens);
|
|
const Bitboard bishopPseudoAttacks = bb::pseudoAttacks<PieceType::Bishop>(ksq);
|
|
|
|
const Bitboard opponentRookLikePieces = (rooks | queens);
|
|
const Bitboard rookPseudoAttacks = bb::pseudoAttacks<PieceType::Rook>(ksq);
|
|
|
|
const Bitboard xrayers =
|
|
(bishopPseudoAttacks & opponentBishopLikePieces)
|
|
| (rookPseudoAttacks & opponentRookLikePieces);
|
|
|
|
Bitboard allBlockers = Bitboard::none();
|
|
|
|
for (Square xrayer : xrayers)
|
|
{
|
|
const Bitboard blockers = bb::between(xrayer, ksq) & occupied;
|
|
if (blockers.exactlyOne())
|
|
{
|
|
allBlockers |= blockers;
|
|
}
|
|
}
|
|
|
|
return allBlockers;
|
|
}
|
|
|
|
inline MoveLegalityChecker::MoveLegalityChecker(const Position& position) :
|
|
m_position(&position),
|
|
m_checkers(position.checkers()),
|
|
m_ourBlockersForKing(
|
|
position.blockersForKing(position.sideToMove())
|
|
& position.piecesBB(position.sideToMove())
|
|
),
|
|
m_ksq(position.kingSquare(position.sideToMove()))
|
|
{
|
|
if (m_checkers.exactlyOne())
|
|
{
|
|
const Bitboard knightCheckers = m_checkers & bb::pseudoAttacks<PieceType::Knight>(m_ksq);
|
|
if (knightCheckers.any())
|
|
{
|
|
// We're checked by a knight, we have to remove it or move the king.
|
|
m_potentialCheckRemovals = knightCheckers;
|
|
}
|
|
else
|
|
{
|
|
// If we're not checked by a knight we can block it.
|
|
m_potentialCheckRemovals = bb::between(m_ksq, m_checkers.first()) | m_checkers;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Double check, king has to move.
|
|
m_potentialCheckRemovals = Bitboard::none();
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] inline bool MoveLegalityChecker::isPseudoLegalMoveLegal(const Move& move) const
|
|
{
|
|
if (m_checkers.any())
|
|
{
|
|
if (move.from == m_ksq || move.type == MoveType::EnPassant)
|
|
{
|
|
return m_position->isPseudoLegalMoveLegal(move);
|
|
}
|
|
else
|
|
{
|
|
// This means there's only one check and we either
|
|
// blocked it or removed the piece that attacked
|
|
// our king. So the only threat is if it's a discovered check.
|
|
return
|
|
m_potentialCheckRemovals.isSet(move.to)
|
|
&& !m_ourBlockersForKing.isSet(move.from);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (move.from == m_ksq)
|
|
{
|
|
return m_position->isPseudoLegalMoveLegal(move);
|
|
}
|
|
else if (move.type == MoveType::EnPassant)
|
|
{
|
|
return !m_position->createsDiscoveredAttackOnOwnKing(move);
|
|
}
|
|
else if (m_ourBlockersForKing.isSet(move.from))
|
|
{
|
|
// If it was a blocker it may have only moved in line with our king.
|
|
// Otherwise it's a discovered check.
|
|
return bb::line(m_ksq, move.from).isSet(move.to);
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
static_assert(sizeof(CompressedPosition) == 24);
|
|
static_assert(std::is_trivially_copyable_v<CompressedPosition>);
|
|
|
|
namespace detail
|
|
{
|
|
[[nodiscard]] FORCEINLINE constexpr std::uint8_t compressOrdinaryPiece(const Position&, Square, Piece piece)
|
|
{
|
|
return static_cast<std::uint8_t>(ordinal(piece));
|
|
}
|
|
|
|
[[nodiscard]] FORCEINLINE constexpr std::uint8_t compressPawn(const Position& position, Square sq, Piece piece)
|
|
{
|
|
const Square epSquare = position.epSquare();
|
|
if (epSquare == Square::none())
|
|
{
|
|
return static_cast<std::uint8_t>(ordinal(piece));
|
|
}
|
|
else
|
|
{
|
|
const Color sideToMove = position.sideToMove();
|
|
const Rank rank = sq.rank();
|
|
const File file = sq.file();
|
|
// use bitwise operators, there is a lot of unpredictable branches but in
|
|
// total the result is quite predictable
|
|
if (
|
|
(file == epSquare.file())
|
|
&& (
|
|
((rank == rank4) & (sideToMove == Color::Black))
|
|
| ((rank == rank5) & (sideToMove == Color::White))
|
|
)
|
|
)
|
|
{
|
|
return 12;
|
|
}
|
|
else
|
|
{
|
|
return static_cast<std::uint8_t>(ordinal(piece));
|
|
}
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] FORCEINLINE constexpr std::uint8_t compressRook(const Position& position, Square sq, Piece piece)
|
|
{
|
|
const CastlingRights castlingRights = position.castlingRights();
|
|
const Color color = piece.color();
|
|
|
|
if (color == Color::White
|
|
&& (
|
|
(sq == a1 && contains(castlingRights, CastlingRights::WhiteQueenSide))
|
|
|| (sq == h1 && contains(castlingRights, CastlingRights::WhiteKingSide))
|
|
)
|
|
)
|
|
{
|
|
return 13;
|
|
}
|
|
else if (
|
|
color == Color::Black
|
|
&& (
|
|
(sq == a8 && contains(castlingRights, CastlingRights::BlackQueenSide))
|
|
|| (sq == h8 && contains(castlingRights, CastlingRights::BlackKingSide))
|
|
)
|
|
)
|
|
{
|
|
return 14;
|
|
}
|
|
else
|
|
{
|
|
return static_cast<std::uint8_t>(ordinal(piece));
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] FORCEINLINE constexpr std::uint8_t compressKing(const Position& position, Square /* sq */, Piece piece)
|
|
{
|
|
const Color color = piece.color();
|
|
const Color sideToMove = position.sideToMove();
|
|
|
|
if (color == Color::White)
|
|
{
|
|
return 10;
|
|
}
|
|
else if (sideToMove == Color::White)
|
|
{
|
|
return 11;
|
|
}
|
|
else
|
|
{
|
|
return 15;
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace detail::lookup
|
|
{
|
|
static constexpr EnumArray<PieceType, std::uint8_t(*)(const Position&, Square, Piece)> pieceCompressorFunc = []() {
|
|
EnumArray<PieceType, std::uint8_t(*)(const Position&, Square, Piece)> pieceCompressorFunc_{};
|
|
|
|
pieceCompressorFunc_[PieceType::Knight] = detail::compressOrdinaryPiece;
|
|
pieceCompressorFunc_[PieceType::Bishop] = detail::compressOrdinaryPiece;
|
|
pieceCompressorFunc_[PieceType::Queen] = detail::compressOrdinaryPiece;
|
|
|
|
pieceCompressorFunc_[PieceType::Pawn] = detail::compressPawn;
|
|
pieceCompressorFunc_[PieceType::Rook] = detail::compressRook;
|
|
pieceCompressorFunc_[PieceType::King] = detail::compressKing;
|
|
|
|
pieceCompressorFunc_[PieceType::None] = [](const Position&, Square, Piece) -> std::uint8_t { /* should never happen */ return 0; };
|
|
|
|
return pieceCompressorFunc_;
|
|
}();
|
|
}
|
|
|
|
[[nodiscard]] inline CompressedPosition Position::compress() const
|
|
{
|
|
auto compressPiece = [this](Square sq, Piece piece) -> std::uint8_t {
|
|
if (piece.type() == PieceType::Pawn) // it's likely to be a pawn
|
|
{
|
|
return detail::compressPawn(*this, sq, piece);
|
|
}
|
|
else
|
|
{
|
|
return detail::lookup::pieceCompressorFunc[piece.type()](*this, sq, piece);
|
|
}
|
|
};
|
|
|
|
const Bitboard occ = piecesBB();
|
|
|
|
CompressedPosition compressed;
|
|
compressed.m_occupied = occ;
|
|
|
|
auto it = occ.begin();
|
|
auto end = occ.end();
|
|
for (int i = 0;; ++i)
|
|
{
|
|
if (it == end) break;
|
|
compressed.m_packedState[i] = compressPiece(*it, pieceAt(*it));
|
|
++it;
|
|
|
|
if (it == end) break;
|
|
compressed.m_packedState[i] |= compressPiece(*it, pieceAt(*it)) << 4;
|
|
++it;
|
|
}
|
|
|
|
return compressed;
|
|
}
|
|
|
|
[[nodiscard]] inline Position CompressedPosition::decompress() const
|
|
{
|
|
Position pos;
|
|
pos.setCastlingRights(CastlingRights::None);
|
|
|
|
auto decompressPiece = [&pos](Square sq, std::uint8_t nibble) {
|
|
switch (nibble)
|
|
{
|
|
case 0:
|
|
case 1:
|
|
case 2:
|
|
case 3:
|
|
case 4:
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
case 9:
|
|
case 10:
|
|
case 11:
|
|
{
|
|
pos.place(fromOrdinal<Piece>(nibble), sq);
|
|
return;
|
|
}
|
|
|
|
case 12:
|
|
{
|
|
const Rank rank = sq.rank();
|
|
if (rank == rank4)
|
|
{
|
|
pos.place(whitePawn, sq);
|
|
pos.setEpSquareUnchecked(sq + Offset{ 0, -1 });
|
|
}
|
|
else // (rank == rank5)
|
|
{
|
|
pos.place(blackPawn, sq);
|
|
pos.setEpSquareUnchecked(sq + Offset{ 0, 1 });
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 13:
|
|
{
|
|
pos.place(whiteRook, sq);
|
|
if (sq == a1)
|
|
{
|
|
pos.addCastlingRights(CastlingRights::WhiteQueenSide);
|
|
}
|
|
else // (sq == H1)
|
|
{
|
|
pos.addCastlingRights(CastlingRights::WhiteKingSide);
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 14:
|
|
{
|
|
pos.place(blackRook, sq);
|
|
if (sq == a8)
|
|
{
|
|
pos.addCastlingRights(CastlingRights::BlackQueenSide);
|
|
}
|
|
else // (sq == H8)
|
|
{
|
|
pos.addCastlingRights(CastlingRights::BlackKingSide);
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 15:
|
|
{
|
|
pos.place(blackKing, sq);
|
|
pos.setSideToMove(Color::Black);
|
|
return;
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
};
|
|
|
|
const Bitboard occ = m_occupied;
|
|
|
|
auto it = occ.begin();
|
|
auto end = occ.end();
|
|
for (int i = 0;; ++i)
|
|
{
|
|
if (it == end) break;
|
|
decompressPiece(*it, m_packedState[i] & 0xF);
|
|
++it;
|
|
|
|
if (it == end) break;
|
|
decompressPiece(*it, m_packedState[i] >> 4);
|
|
++it;
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
|
|
[[nodiscard]] bool Board::isSquareAttacked(Square sq, Color attackerColor) const
|
|
{
|
|
assert(sq.isOk());
|
|
|
|
const Bitboard occupied = piecesBB();
|
|
const Bitboard bishops = piecesBB(Piece(PieceType::Bishop, attackerColor));
|
|
const Bitboard rooks = piecesBB(Piece(PieceType::Rook, attackerColor));
|
|
const Bitboard queens = piecesBB(Piece(PieceType::Queen, attackerColor));
|
|
|
|
const Bitboard allSliders = (bishops | rooks | queens);
|
|
if ((bb::pseudoAttacks<PieceType::Queen>(sq) & allSliders).any())
|
|
{
|
|
if (bb::isAttackedBySlider(
|
|
sq,
|
|
bishops,
|
|
rooks,
|
|
queens,
|
|
occupied
|
|
))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const Bitboard king = piecesBB(Piece(PieceType::King, attackerColor));
|
|
if ((bb::pseudoAttacks<PieceType::King>(sq) & king).any())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const Bitboard knights = piecesBB(Piece(PieceType::Knight, attackerColor));
|
|
if ((bb::pseudoAttacks<PieceType::Knight>(sq) & knights).any())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const Bitboard pawns = piecesBB(Piece(PieceType::Pawn, attackerColor));
|
|
const Bitboard pawnAttacks = bb::pawnAttacks(pawns, attackerColor);
|
|
|
|
return pawnAttacks.isSet(sq);
|
|
}
|
|
|
|
[[nodiscard]] bool Board::isSquareAttackedAfterMove(Move move, Square sq, Color attackerColor) const
|
|
{
|
|
const Bitboard occupiedChange = Bitboard::square(move.from) | move.to;
|
|
|
|
Bitboard occupied = (piecesBB() ^ move.from) | move.to;
|
|
|
|
Bitboard bishops = piecesBB(Piece(PieceType::Bishop, attackerColor));
|
|
Bitboard rooks = piecesBB(Piece(PieceType::Rook, attackerColor));
|
|
Bitboard queens = piecesBB(Piece(PieceType::Queen, attackerColor));
|
|
Bitboard king = piecesBB(Piece(PieceType::King, attackerColor));
|
|
Bitboard knights = piecesBB(Piece(PieceType::Knight, attackerColor));
|
|
Bitboard pawns = piecesBB(Piece(PieceType::Pawn, attackerColor));
|
|
|
|
if (move.type == MoveType::EnPassant)
|
|
{
|
|
const Square capturedPawnSq(move.to.file(), move.from.rank());
|
|
occupied ^= capturedPawnSq;
|
|
pawns ^= capturedPawnSq;
|
|
}
|
|
else if (pieceAt(move.to) != Piece::none())
|
|
{
|
|
const Bitboard notCaptured = ~Bitboard::square(move.to);
|
|
bishops &= notCaptured;
|
|
rooks &= notCaptured;
|
|
queens &= notCaptured;
|
|
knights &= notCaptured;
|
|
pawns &= notCaptured;
|
|
}
|
|
|
|
// Potential attackers may have moved.
|
|
const Piece movedPiece = pieceAt(move.from);
|
|
if (movedPiece.color() == attackerColor)
|
|
{
|
|
switch (movedPiece.type())
|
|
{
|
|
case PieceType::Pawn:
|
|
pawns ^= occupiedChange;
|
|
break;
|
|
case PieceType::Knight:
|
|
knights ^= occupiedChange;
|
|
break;
|
|
case PieceType::Bishop:
|
|
bishops ^= occupiedChange;
|
|
break;
|
|
case PieceType::Rook:
|
|
rooks ^= occupiedChange;
|
|
break;
|
|
case PieceType::Queen:
|
|
queens ^= occupiedChange;
|
|
break;
|
|
case PieceType::King:
|
|
{
|
|
if (move.type == MoveType::Castle)
|
|
{
|
|
const CastleType castleType = CastlingTraits::moveCastlingType(move);
|
|
|
|
king ^= move.from;
|
|
king ^= CastlingTraits::kingDestination[attackerColor][castleType];
|
|
rooks ^= move.to;
|
|
rooks ^= CastlingTraits::rookDestination[attackerColor][castleType];
|
|
}
|
|
else
|
|
{
|
|
king ^= occupiedChange;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case PieceType::None:
|
|
assert(false);
|
|
}
|
|
}
|
|
|
|
// If it's a castling move then the change in square occupation
|
|
// cannot have an effect because otherwise there would be
|
|
// a slider attacker attacking the castling king.
|
|
// (It could have an effect in chess960 if the slider
|
|
// attacker was behind the rook involved in castling,
|
|
// but we don't care about chess960.)
|
|
|
|
const Bitboard allSliders = (bishops | rooks | queens);
|
|
if ((bb::pseudoAttacks<PieceType::Queen>(sq) & allSliders).any())
|
|
{
|
|
if (bb::isAttackedBySlider(
|
|
sq,
|
|
bishops,
|
|
rooks,
|
|
queens,
|
|
occupied
|
|
))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if ((bb::pseudoAttacks<PieceType::King>(sq) & king).any())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if ((bb::pseudoAttacks<PieceType::Knight>(sq) & knights).any())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const Bitboard pawnAttacks = bb::pawnAttacks(pawns, attackerColor);
|
|
|
|
return pawnAttacks.isSet(sq);
|
|
}
|
|
|
|
[[nodiscard]] bool Board::createsDiscoveredAttackOnOwnKing(Move move) const
|
|
{
|
|
Bitboard occupied = (piecesBB() ^ move.from) | move.to;
|
|
|
|
const Piece movedPiece = pieceAt(move.from);
|
|
const Color kingColor = movedPiece.color();
|
|
const Color attackerColor = !kingColor;
|
|
const Square ksq = kingSquare(kingColor);
|
|
|
|
Bitboard bishops = piecesBB(Piece(PieceType::Bishop, attackerColor));
|
|
Bitboard rooks = piecesBB(Piece(PieceType::Rook, attackerColor));
|
|
Bitboard queens = piecesBB(Piece(PieceType::Queen, attackerColor));
|
|
|
|
if (move.type == MoveType::EnPassant)
|
|
{
|
|
const Square capturedPawnSq(move.to.file(), move.from.rank());
|
|
occupied ^= capturedPawnSq;
|
|
}
|
|
else if (pieceAt(move.to) != Piece::none())
|
|
{
|
|
const Bitboard notCaptured = ~Bitboard::square(move.to);
|
|
bishops &= notCaptured;
|
|
rooks &= notCaptured;
|
|
queens &= notCaptured;
|
|
}
|
|
|
|
const Bitboard allSliders = (bishops | rooks | queens);
|
|
if ((bb::pseudoAttacks<PieceType::Queen>(ksq) & allSliders).any())
|
|
{
|
|
if (bb::isAttackedBySlider(
|
|
ksq,
|
|
bishops,
|
|
rooks,
|
|
queens,
|
|
occupied
|
|
))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
[[nodiscard]] bool Board::isPieceAttacked(Square sq) const
|
|
{
|
|
const Piece piece = pieceAt(sq);
|
|
|
|
if (piece == Piece::none())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return isSquareAttacked(sq, !piece.color());
|
|
}
|
|
|
|
[[nodiscard]] bool Board::isPieceAttackedAfterMove(Move move, Square sq) const
|
|
{
|
|
const Piece piece = pieceAt(sq);
|
|
|
|
if (piece == Piece::none())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (sq == move.from)
|
|
{
|
|
// We moved the piece we're interested in.
|
|
// For every move the piece ends up on the move.to except
|
|
// for the case of castling moves.
|
|
// But we know pseudo legal castling moves
|
|
// are already legal, so the king cannot be in check after.
|
|
if (move.type == MoveType::Castle)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// So update the square we're interested in.
|
|
sq = move.to;
|
|
}
|
|
|
|
return isSquareAttackedAfterMove(move, sq, !piece.color());
|
|
}
|
|
|
|
[[nodiscard]] bool Board::isOwnKingAttackedAfterMove(Move move) const
|
|
{
|
|
if (move.type == MoveType::Castle)
|
|
{
|
|
// Pseudo legal castling moves are already legal.
|
|
// This is ensured by the move generator.
|
|
return false;
|
|
}
|
|
|
|
const Piece movedPiece = pieceAt(move.from);
|
|
|
|
return isPieceAttackedAfterMove(move, kingSquare(movedPiece.color()));
|
|
}
|
|
|
|
[[nodiscard]] Bitboard Board::attacks(Square sq) const
|
|
{
|
|
const Piece piece = pieceAt(sq);
|
|
if (piece == Piece::none())
|
|
{
|
|
return Bitboard::none();
|
|
}
|
|
|
|
if (piece.type() == PieceType::Pawn)
|
|
{
|
|
return bb::pawnAttacks(Bitboard::square(sq), piece.color());
|
|
}
|
|
else
|
|
{
|
|
return bb::attacks(piece.type(), sq, piecesBB());
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] Bitboard Board::attackers(Square sq, Color attackerColor) const
|
|
{
|
|
// En-passant square is not included.
|
|
|
|
Bitboard allAttackers = Bitboard::none();
|
|
|
|
const Bitboard occupied = piecesBB();
|
|
|
|
const Bitboard bishops = piecesBB(Piece(PieceType::Bishop, attackerColor));
|
|
const Bitboard rooks = piecesBB(Piece(PieceType::Rook, attackerColor));
|
|
const Bitboard queens = piecesBB(Piece(PieceType::Queen, attackerColor));
|
|
|
|
const Bitboard bishopLikePieces = (bishops | queens);
|
|
const Bitboard bishopAttacks = bb::attacks<PieceType::Bishop>(sq, occupied);
|
|
allAttackers |= bishopAttacks & bishopLikePieces;
|
|
|
|
const Bitboard rookLikePieces = (rooks | queens);
|
|
const Bitboard rookAttacks = bb::attacks<PieceType::Rook>(sq, occupied);
|
|
allAttackers |= rookAttacks & rookLikePieces;
|
|
|
|
const Bitboard king = piecesBB(Piece(PieceType::King, attackerColor));
|
|
allAttackers |= bb::pseudoAttacks<PieceType::King>(sq) & king;
|
|
|
|
const Bitboard knights = piecesBB(Piece(PieceType::Knight, attackerColor));
|
|
allAttackers |= bb::pseudoAttacks<PieceType::Knight>(sq) & knights;
|
|
|
|
const Bitboard pawns = piecesBB(Piece(PieceType::Pawn, attackerColor));
|
|
allAttackers |= bb::pawnAttacks(Bitboard::square(sq), !attackerColor) & pawns;
|
|
|
|
return allAttackers;
|
|
}
|
|
|
|
inline const Piece* Board::piecesRaw() const
|
|
{
|
|
return m_pieces.data();
|
|
}
|
|
|
|
namespace detail::lookup
|
|
{
|
|
static constexpr EnumArray<Piece, char> fenPiece = []() {
|
|
EnumArray<Piece, char> fenPiece_{};
|
|
|
|
fenPiece_[whitePawn] = 'P';
|
|
fenPiece_[blackPawn] = 'p';
|
|
fenPiece_[whiteKnight] = 'N';
|
|
fenPiece_[blackKnight] = 'n';
|
|
fenPiece_[whiteBishop] = 'B';
|
|
fenPiece_[blackBishop] = 'b';
|
|
fenPiece_[whiteRook] = 'R';
|
|
fenPiece_[blackRook] = 'r';
|
|
fenPiece_[whiteQueen] = 'Q';
|
|
fenPiece_[blackQueen] = 'q';
|
|
fenPiece_[whiteKing] = 'K';
|
|
fenPiece_[blackKing] = 'k';
|
|
fenPiece_[Piece::none()] = 'X';
|
|
|
|
return fenPiece_;
|
|
}();
|
|
}
|
|
|
|
[[nodiscard]] inline std::string Board::fen() const
|
|
{
|
|
std::string fen;
|
|
fen.reserve(96); // longest fen is probably in range of around 88
|
|
|
|
Rank rank = rank8;
|
|
File file = fileA;
|
|
std::uint8_t emptyCounter = 0;
|
|
|
|
for (;;)
|
|
{
|
|
const Square sq(file, rank);
|
|
const Piece piece = m_pieces[sq];
|
|
|
|
if (piece == Piece::none())
|
|
{
|
|
++emptyCounter;
|
|
}
|
|
else
|
|
{
|
|
if (emptyCounter != 0)
|
|
{
|
|
fen.push_back(static_cast<char>(emptyCounter) + '0');
|
|
emptyCounter = 0;
|
|
}
|
|
|
|
fen.push_back(detail::lookup::fenPiece[piece]);
|
|
}
|
|
|
|
++file;
|
|
if (file > fileH)
|
|
{
|
|
file = fileA;
|
|
--rank;
|
|
|
|
if (emptyCounter != 0)
|
|
{
|
|
fen.push_back(static_cast<char>(emptyCounter) + '0');
|
|
emptyCounter = 0;
|
|
}
|
|
|
|
if (rank < rank1)
|
|
{
|
|
break;
|
|
}
|
|
fen.push_back('/');
|
|
}
|
|
}
|
|
|
|
return fen;
|
|
}
|
|
|
|
void Position::set(std::string_view fen)
|
|
{
|
|
(void)trySet(fen);
|
|
}
|
|
|
|
// Returns false if the fen was not valid
|
|
// If the returned value was false the position
|
|
// is in unspecified state.
|
|
[[nodiscard]] bool Position::trySet(std::string_view fen)
|
|
{
|
|
// Lazily splits by ' '. Returns empty string views if at the end.
|
|
auto nextPart = [fen, start = std::size_t{ 0 }]() mutable {
|
|
std::size_t end = fen.find(' ', start);
|
|
if (end == std::string::npos)
|
|
{
|
|
std::string_view substr = fen.substr(start);
|
|
start = fen.size();
|
|
return substr;
|
|
}
|
|
else
|
|
{
|
|
std::string_view substr = fen.substr(start, end - start);
|
|
start = end + 1; // to skip whitespace
|
|
return substr;
|
|
}
|
|
};
|
|
|
|
if (!BaseType::trySet(nextPart())) return false;
|
|
|
|
{
|
|
const auto side = nextPart();
|
|
if (side == std::string_view("w")) m_sideToMove = Color::White;
|
|
else if (side == std::string_view("b")) m_sideToMove = Color::Black;
|
|
else return false;
|
|
|
|
if (isSquareAttacked(kingSquare(!m_sideToMove), m_sideToMove)) return false;
|
|
}
|
|
|
|
{
|
|
const auto castlingRights = nextPart();
|
|
auto castlingRightsOpt = parser_bits::tryParseCastlingRights(castlingRights);
|
|
if (!castlingRightsOpt.has_value())
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
m_castlingRights = *castlingRightsOpt;
|
|
}
|
|
}
|
|
|
|
{
|
|
const auto epSquare = nextPart();
|
|
auto epSquareOpt = parser_bits::tryParseEpSquare(epSquare);
|
|
if (!epSquareOpt.has_value())
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
m_epSquare = *epSquareOpt;
|
|
}
|
|
}
|
|
|
|
{
|
|
const auto rule50 = nextPart();
|
|
if (!rule50.empty())
|
|
{
|
|
m_rule50Counter = std::stoi(rule50.data());
|
|
}
|
|
else
|
|
{
|
|
m_rule50Counter = 0;
|
|
}
|
|
}
|
|
|
|
{
|
|
const auto fullMove = nextPart();
|
|
if (!fullMove.empty())
|
|
{
|
|
m_ply = 2 * (std::stoi(fullMove.data()) - 1) + (m_sideToMove == Color::Black);
|
|
}
|
|
else
|
|
{
|
|
m_ply = 0;
|
|
}
|
|
}
|
|
|
|
nullifyEpSquareIfNotPossible();
|
|
|
|
return true;
|
|
}
|
|
|
|
[[nodiscard]] Position Position::fromFen(std::string_view fen)
|
|
{
|
|
Position pos{};
|
|
pos.set(fen);
|
|
return pos;
|
|
}
|
|
|
|
[[nodiscard]] std::optional<Position> Position::tryFromFen(std::string_view fen)
|
|
{
|
|
Position pos{};
|
|
if (pos.trySet(fen)) return pos;
|
|
else return {};
|
|
}
|
|
|
|
[[nodiscard]] Position Position::startPosition()
|
|
{
|
|
static const Position pos = fromFen("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
|
|
return pos;
|
|
}
|
|
|
|
[[nodiscard]] std::string Position::fen() const
|
|
{
|
|
std::string fen = Board::fen();
|
|
|
|
fen += ' ';
|
|
fen += m_sideToMove == Color::White ? 'w' : 'b';
|
|
|
|
fen += ' ';
|
|
parser_bits::appendCastlingRightsToString(m_castlingRights, fen);
|
|
|
|
fen += ' ';
|
|
parser_bits::appendEpSquareToString(m_epSquare, fen);
|
|
|
|
fen += ' ';
|
|
fen += std::to_string(m_rule50Counter);
|
|
|
|
fen += ' ';
|
|
fen += std::to_string(fullMove());
|
|
|
|
return fen;
|
|
}
|
|
|
|
namespace detail::lookup
|
|
{
|
|
static constexpr EnumArray<Square, CastlingRights> preservedCastlingRights = []() {
|
|
EnumArray<Square, CastlingRights> preservedCastlingRights_{};
|
|
for (CastlingRights& rights : preservedCastlingRights_)
|
|
{
|
|
rights = ~CastlingRights::None;
|
|
}
|
|
|
|
preservedCastlingRights_[e1] = ~CastlingRights::White;
|
|
preservedCastlingRights_[e8] = ~CastlingRights::Black;
|
|
|
|
preservedCastlingRights_[h1] = ~CastlingRights::WhiteKingSide;
|
|
preservedCastlingRights_[a1] = ~CastlingRights::WhiteQueenSide;
|
|
preservedCastlingRights_[h8] = ~CastlingRights::BlackKingSide;
|
|
preservedCastlingRights_[a8] = ~CastlingRights::BlackQueenSide;
|
|
|
|
return preservedCastlingRights_;
|
|
}();
|
|
}
|
|
|
|
inline ReverseMove Position::doMove(const Move& move)
|
|
{
|
|
assert(move.from.isOk() && move.to.isOk());
|
|
|
|
const PieceType movedPiece = pieceAt(move.from).type();
|
|
|
|
m_ply += 1;
|
|
m_rule50Counter += 1;
|
|
|
|
if (move.type != MoveType::Castle && (movedPiece == PieceType::Pawn || pieceAt(move.to) != Piece::none()))
|
|
{
|
|
m_rule50Counter = 0;
|
|
}
|
|
|
|
const Square oldEpSquare = m_epSquare;
|
|
const CastlingRights oldCastlingRights = m_castlingRights;
|
|
m_castlingRights &= detail::lookup::preservedCastlingRights[move.from];
|
|
m_castlingRights &= detail::lookup::preservedCastlingRights[move.to];
|
|
|
|
m_epSquare = Square::none();
|
|
// for double pushes move index differs by 16 or -16;
|
|
if((movedPiece == PieceType::Pawn) & ((ordinal(move.to) ^ ordinal(move.from)) == 16))
|
|
{
|
|
const Square potentialEpSquare = fromOrdinal<Square>((ordinal(move.to) + ordinal(move.from)) >> 1);
|
|
// Even though the move has not yet been made we can safely call
|
|
// this function and get the right result because the position of the
|
|
// pawn to be captured is not really relevant.
|
|
if (isEpPossible(potentialEpSquare, !m_sideToMove))
|
|
{
|
|
m_epSquare = potentialEpSquare;
|
|
}
|
|
}
|
|
|
|
const Piece captured = BaseType::doMove(move);
|
|
m_sideToMove = !m_sideToMove;
|
|
return { move, captured, oldEpSquare, oldCastlingRights };
|
|
}
|
|
|
|
[[nodiscard]] inline Position Position::afterMove(Move move) const
|
|
{
|
|
Position cpy(*this);
|
|
auto pc = cpy.doMove(move);
|
|
|
|
(void)pc;
|
|
//assert(cpy.beforeMove(move, pc) == *this); // this assert would result in infinite recursion
|
|
|
|
return cpy;
|
|
}
|
|
|
|
[[nodiscard]] inline bool Position::isEpPossible(Square epSquare, Color sideToMove) const
|
|
{
|
|
const Bitboard pawnsAttackingEpSquare =
|
|
bb::pawnAttacks(Bitboard::square(epSquare), !sideToMove)
|
|
& piecesBB(Piece(PieceType::Pawn, sideToMove));
|
|
|
|
if (!pawnsAttackingEpSquare.any())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (pieceAt(epSquare) != Piece::none())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const auto forward =
|
|
sideToMove == chess::Color::White
|
|
? FlatSquareOffset(0, 1)
|
|
: FlatSquareOffset(0, -1);
|
|
|
|
if (pieceAt(epSquare + forward) != Piece::none())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (pieceAt(epSquare + -forward) != Piece(PieceType::Pawn, !sideToMove))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return isEpPossibleColdPath(epSquare, pawnsAttackingEpSquare, sideToMove);
|
|
}
|
|
|
|
[[nodiscard]] inline bool Position::isEpPossibleColdPath(Square epSquare, Bitboard pawnsAttackingEpSquare, Color sideToMove) const
|
|
{
|
|
// only set m_epSquare when it matters, ie. when
|
|
// the opposite side can actually capture
|
|
for (Square sq : pawnsAttackingEpSquare)
|
|
{
|
|
// If we're here the previous move by other side
|
|
// was a double pawn move so our king is either not in check
|
|
// or is attacked only by the moved pawn - in which
|
|
// case it can be captured by our pawn if it doesn't
|
|
// create a discovered check on our king.
|
|
// So overall we only have to check whether our king
|
|
// ends up being uncovered to a slider attack.
|
|
|
|
const Square ksq = kingSquare(sideToMove);
|
|
|
|
const Bitboard bishops = piecesBB(Piece(PieceType::Bishop, !sideToMove));
|
|
const Bitboard rooks = piecesBB(Piece(PieceType::Rook, !sideToMove));
|
|
const Bitboard queens = piecesBB(Piece(PieceType::Queen, !sideToMove));
|
|
|
|
const Bitboard relevantAttackers = bishops | rooks | queens;
|
|
const Bitboard pseudoSliderAttacksFromKing = bb::pseudoAttacks<PieceType::Queen>(ksq);
|
|
if ((relevantAttackers & pseudoSliderAttacksFromKing).isEmpty())
|
|
{
|
|
// It's enough that one pawn can capture.
|
|
return true;
|
|
}
|
|
|
|
const Square capturedPawnSq(epSquare.file(), sq.rank());
|
|
const Bitboard occupied = ((piecesBB() ^ sq) | epSquare) ^ capturedPawnSq;
|
|
|
|
if (!bb::isAttackedBySlider(
|
|
ksq,
|
|
bishops,
|
|
rooks,
|
|
queens,
|
|
occupied
|
|
))
|
|
{
|
|
// It's enough that one pawn can capture.
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
inline void Position::nullifyEpSquareIfNotPossible()
|
|
{
|
|
if (m_epSquare != Square::none() && !isEpPossible(m_epSquare, m_sideToMove))
|
|
{
|
|
m_epSquare = Square::none();
|
|
}
|
|
}
|
|
|
|
namespace uci
|
|
{
|
|
[[nodiscard]] inline std::string moveToUci(const Position& pos, const Move& move);
|
|
[[nodiscard]] inline Move uciToMove(const Position& pos, std::string_view sv);
|
|
|
|
[[nodiscard]] inline std::string moveToUci(const Position& pos, const Move& move)
|
|
{
|
|
std::string s;
|
|
|
|
parser_bits::appendSquareToString(move.from, s);
|
|
|
|
if (move.type == MoveType::Castle)
|
|
{
|
|
const CastleType castleType = CastlingTraits::moveCastlingType(move);
|
|
|
|
const Square kingDestination = CastlingTraits::kingDestination[pos.sideToMove()][castleType];
|
|
parser_bits::appendSquareToString(kingDestination, s);
|
|
}
|
|
else
|
|
{
|
|
parser_bits::appendSquareToString(move.to, s);
|
|
|
|
if (move.type == MoveType::Promotion)
|
|
{
|
|
// lowercase piece symbol
|
|
s += EnumTraits<PieceType>::toChar(move.promotedPiece.type(), Color::Black);
|
|
}
|
|
}
|
|
|
|
return s;
|
|
}
|
|
|
|
[[nodiscard]] inline Move uciToMove(const Position& pos, std::string_view sv)
|
|
{
|
|
const Square from = parser_bits::parseSquare(sv.data());
|
|
const Square to = parser_bits::parseSquare(sv.data() + 2);
|
|
|
|
if (sv.size() == 5)
|
|
{
|
|
const PieceType promotedPieceType = *fromChar<PieceType>(sv[4]);
|
|
return Move::promotion(from, to, Piece(promotedPieceType, pos.sideToMove()));
|
|
}
|
|
else
|
|
{
|
|
if (
|
|
pos.pieceAt(from).type() == PieceType::King
|
|
&& std::abs(from.file() - to.file()) > 1
|
|
)
|
|
{
|
|
// uci king destinations are on files C or G.
|
|
const CastleType castleType =
|
|
(to.file() == fileG)
|
|
? CastleType::Short
|
|
: CastleType::Long;
|
|
|
|
return Move::castle(castleType, pos.sideToMove());
|
|
}
|
|
else if (pos.pieceAt(from).type() == PieceType::Pawn && pos.epSquare() == to)
|
|
{
|
|
return Move::enPassant(from, to);
|
|
}
|
|
else
|
|
{
|
|
return Move::normal(from, to);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
namespace binpack
|
|
{
|
|
constexpr std::size_t KiB = 1024;
|
|
constexpr std::size_t MiB = (1024*KiB);
|
|
constexpr std::size_t GiB = (1024*MiB);
|
|
|
|
constexpr std::size_t suggestedChunkSize = MiB;
|
|
constexpr std::size_t maxMovelistSize = 10*KiB; // a safe upper bound
|
|
constexpr std::size_t maxChunkSize = 100*MiB; // to prevent malformed files from causing huge allocations
|
|
|
|
using namespace std::literals;
|
|
|
|
namespace nodchip
|
|
{
|
|
// This namespace contains modified code from https://github.com/nodchip/Stockfish
|
|
// which is released under GPL v3 license https://www.gnu.org/licenses/gpl-3.0.html
|
|
|
|
using namespace std;
|
|
|
|
struct StockfishMove
|
|
{
|
|
[[nodiscard]] static StockfishMove fromMove(chess::Move move)
|
|
{
|
|
StockfishMove sfm;
|
|
|
|
sfm.m_raw = 0;
|
|
|
|
unsigned moveFlag = 0;
|
|
if (move.type == chess::MoveType::Promotion) moveFlag = 1;
|
|
else if (move.type == chess::MoveType::EnPassant) moveFlag = 2;
|
|
else if (move.type == chess::MoveType::Castle) moveFlag = 3;
|
|
|
|
unsigned promotionIndex = 0;
|
|
if (move.type == chess::MoveType::Promotion)
|
|
{
|
|
promotionIndex = static_cast<int>(move.promotedPiece.type()) - static_cast<int>(chess::PieceType::Knight);
|
|
}
|
|
|
|
sfm.m_raw |= static_cast<std::uint16_t>(moveFlag);
|
|
sfm.m_raw <<= 2;
|
|
sfm.m_raw |= static_cast<std::uint16_t>(promotionIndex);
|
|
sfm.m_raw <<= 6;
|
|
sfm.m_raw |= static_cast<int>(move.from);
|
|
sfm.m_raw <<= 6;
|
|
sfm.m_raw |= static_cast<int>(move.to);
|
|
|
|
return sfm;
|
|
}
|
|
|
|
[[nodiscard]] chess::Move toMove() const
|
|
{
|
|
const chess::Square to = static_cast<chess::Square>((m_raw & (0b111111 << 0) >> 0));
|
|
const chess::Square from = static_cast<chess::Square>((m_raw & (0b111111 << 6)) >> 6);
|
|
|
|
const unsigned promotionIndex = (m_raw & (0b11 << 12)) >> 12;
|
|
const chess::PieceType promotionType = static_cast<chess::PieceType>(static_cast<int>(chess::PieceType::Knight) + promotionIndex);
|
|
|
|
const unsigned moveFlag = (m_raw & (0b11 << 14)) >> 14;
|
|
chess::MoveType type = chess::MoveType::Normal;
|
|
if (moveFlag == 1) type = chess::MoveType::Promotion;
|
|
else if (moveFlag == 2) type = chess::MoveType::EnPassant;
|
|
else if (moveFlag == 3) type = chess::MoveType::Castle;
|
|
|
|
if (type == chess::MoveType::Promotion)
|
|
{
|
|
const chess::Color stm = to.rank() == chess::rank8 ? chess::Color::White : chess::Color::Black;
|
|
return chess::Move{from, to, type, chess::Piece(promotionType, stm)};
|
|
}
|
|
|
|
return chess::Move{from, to, type};
|
|
}
|
|
|
|
[[nodiscard]] std::string toString() const
|
|
{
|
|
const chess::Square to = static_cast<chess::Square>((m_raw & (0b111111 << 0) >> 0));
|
|
const chess::Square from = static_cast<chess::Square>((m_raw & (0b111111 << 6)) >> 6);
|
|
|
|
const unsigned promotionIndex = (m_raw & (0b11 << 12)) >> 12;
|
|
const chess::PieceType promotionType = static_cast<chess::PieceType>(static_cast<int>(chess::PieceType::Knight) + promotionIndex);
|
|
|
|
std::string r;
|
|
chess::parser_bits::appendSquareToString(from, r);
|
|
chess::parser_bits::appendSquareToString(to, r);
|
|
if (promotionType != chess::PieceType::None)
|
|
{
|
|
r += chess::EnumTraits<chess::PieceType>::toChar(promotionType, chess::Color::Black);
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
private:
|
|
std::uint16_t m_raw;
|
|
};
|
|
static_assert(sizeof(StockfishMove) == sizeof(std::uint16_t));
|
|
|
|
struct PackedSfen
|
|
{
|
|
uint8_t data[32];
|
|
};
|
|
|
|
struct PackedSfenValue
|
|
{
|
|
// phase
|
|
PackedSfen sfen;
|
|
|
|
// Evaluation value returned from Learner::search()
|
|
int16_t score;
|
|
|
|
// PV first move
|
|
// Used when finding the match rate with the teacher
|
|
StockfishMove move;
|
|
|
|
// Trouble of the phase from the initial phase.
|
|
uint16_t gamePly;
|
|
|
|
// 1 if the player on this side ultimately wins the game. -1 if you are losing.
|
|
// 0 if a draw is reached.
|
|
// The draw is in the teacher position generation command gensfen,
|
|
// Only write if LEARN_GENSFEN_DRAW_RESULT is enabled.
|
|
int8_t game_result;
|
|
|
|
// When exchanging the file that wrote the teacher aspect with other people
|
|
//Because this structure size is not fixed, pad it so that it is 40 bytes in any environment.
|
|
uint8_t padding;
|
|
|
|
// 32 + 2 + 2 + 2 + 1 + 1 = 40bytes
|
|
};
|
|
static_assert(sizeof(PackedSfenValue) == 40);
|
|
// Class that handles bitstream
|
|
|
|
// useful when doing aspect encoding
|
|
struct BitStream
|
|
{
|
|
// Set the memory to store the data in advance.
|
|
// Assume that memory is cleared to 0.
|
|
void set_data(uint8_t* data_) { data = data_; reset(); }
|
|
|
|
// Get the pointer passed in set_data().
|
|
uint8_t* get_data() const { return data; }
|
|
|
|
// Get the cursor.
|
|
int get_cursor() const { return bit_cursor; }
|
|
|
|
// reset the cursor
|
|
void reset() { bit_cursor = 0; }
|
|
|
|
// Write 1bit to the stream.
|
|
// If b is non-zero, write out 1. If 0, write 0.
|
|
void write_one_bit(int b)
|
|
{
|
|
if (b)
|
|
data[bit_cursor / 8] |= 1 << (bit_cursor & 7);
|
|
|
|
++bit_cursor;
|
|
}
|
|
|
|
// Get 1 bit from the stream.
|
|
int read_one_bit()
|
|
{
|
|
int b = (data[bit_cursor / 8] >> (bit_cursor & 7)) & 1;
|
|
++bit_cursor;
|
|
|
|
return b;
|
|
}
|
|
|
|
// write n bits of data
|
|
// Data shall be written out from the lower order of d.
|
|
void write_n_bit(int d, int n)
|
|
{
|
|
for (int i = 0; i <n; ++i)
|
|
write_one_bit(d & (1 << i));
|
|
}
|
|
|
|
// read n bits of data
|
|
// Reverse conversion of write_n_bit().
|
|
int read_n_bit(int n)
|
|
{
|
|
int result = 0;
|
|
for (int i = 0; i < n; ++i)
|
|
result |= read_one_bit() ? (1 << i) : 0;
|
|
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
// Next bit position to read/write.
|
|
int bit_cursor;
|
|
|
|
// data entity
|
|
uint8_t* data;
|
|
};
|
|
|
|
|
|
// Huffman coding
|
|
// * is simplified from mini encoding to make conversion easier.
|
|
//
|
|
// Huffman Encoding
|
|
//
|
|
// Empty xxxxxxx0
|
|
// Pawn xxxxx001 + 1 bit (Color)
|
|
// Knight xxxxx011 + 1 bit (Color)
|
|
// Bishop xxxxx101 + 1 bit (Color)
|
|
// Rook xxxxx111 + 1 bit (Color)
|
|
// Queen xxxx1001 + 1 bit (Color)
|
|
//
|
|
// Worst case:
|
|
// - 32 empty squares 32 bits
|
|
// - 30 pieces 150 bits
|
|
// - 2 kings 12 bits
|
|
// - castling rights 4 bits
|
|
// - ep square 7 bits
|
|
// - rule50 7 bits
|
|
// - game ply 16 bits
|
|
// - TOTAL 228 bits < 256 bits
|
|
|
|
struct HuffmanedPiece
|
|
{
|
|
int code; // how it will be coded
|
|
int bits; // How many bits do you have
|
|
};
|
|
|
|
// NOTE: Order adjusted for this library because originally NO_PIECE had index 0
|
|
constexpr HuffmanedPiece huffman_table[] =
|
|
{
|
|
{0b0001,4}, // PAWN 1
|
|
{0b0011,4}, // KNIGHT 3
|
|
{0b0101,4}, // BISHOP 5
|
|
{0b0111,4}, // ROOK 7
|
|
{0b1001,4}, // QUEEN 9
|
|
{-1,-1}, // KING - unused
|
|
{0b0000,1}, // NO_PIECE 0
|
|
};
|
|
|
|
// Class for compressing/decompressing sfen
|
|
// sfen can be packed to 256bit (32bytes) by Huffman coding.
|
|
// This is proven by mini. The above is Huffman coding.
|
|
//
|
|
// Internal format = 1-bit turn + 7-bit king position *2 + piece on board (Huffman coding) + hand piece (Huffman coding)
|
|
// Side to move (White = 0, Black = 1) (1bit)
|
|
// White King Position (6 bits)
|
|
// Black King Position (6 bits)
|
|
// Huffman Encoding of the board
|
|
// Castling availability (1 bit x 4)
|
|
// En passant square (1 or 1 + 6 bits)
|
|
// Rule 50 (6 bits)
|
|
// Game play (8 bits)
|
|
//
|
|
// TODO(someone): Rename SFEN to FEN.
|
|
//
|
|
struct SfenPacker
|
|
{
|
|
// Pack sfen and store in data[32].
|
|
void pack(const chess::Position& pos)
|
|
{
|
|
memset(data, 0, 32 /* 256bit */);
|
|
stream.set_data(data);
|
|
|
|
// turn
|
|
// Side to move.
|
|
stream.write_one_bit((int)(pos.sideToMove()));
|
|
|
|
// 7-bit positions for leading and trailing balls
|
|
// White king and black king, 6 bits for each.
|
|
stream.write_n_bit(static_cast<int>(pos.kingSquare(chess::Color::White)), 6);
|
|
stream.write_n_bit(static_cast<int>(pos.kingSquare(chess::Color::Black)), 6);
|
|
|
|
// Write the pieces on the board other than the kings.
|
|
for (chess::Rank r = chess::rank8; r >= chess::rank1; --r)
|
|
{
|
|
for (chess::File f = chess::fileA; f <= chess::fileH; ++f)
|
|
{
|
|
chess::Piece pc = pos.pieceAt(chess::Square(f, r));
|
|
if (pc.type() == chess::PieceType::King)
|
|
continue;
|
|
write_board_piece_to_stream(pc);
|
|
}
|
|
}
|
|
|
|
// TODO(someone): Support chess960.
|
|
auto cr = pos.castlingRights();
|
|
stream.write_one_bit(contains(cr, chess::CastlingRights::WhiteKingSide));
|
|
stream.write_one_bit(contains(cr, chess::CastlingRights::WhiteQueenSide));
|
|
stream.write_one_bit(contains(cr, chess::CastlingRights::BlackKingSide));
|
|
stream.write_one_bit(contains(cr, chess::CastlingRights::BlackQueenSide));
|
|
|
|
if (pos.epSquare() == chess::Square::none()) {
|
|
stream.write_one_bit(0);
|
|
}
|
|
else {
|
|
stream.write_one_bit(1);
|
|
stream.write_n_bit(static_cast<int>(pos.epSquare()), 6);
|
|
}
|
|
|
|
stream.write_n_bit(pos.rule50Counter(), 6);
|
|
|
|
stream.write_n_bit(pos.fullMove(), 8);
|
|
|
|
// Write high bits of half move. This is a fix for the
|
|
// limited range of half move counter.
|
|
// This is backwards compatibile.
|
|
stream.write_n_bit(pos.fullMove() >> 8, 8);
|
|
|
|
// Write the highest bit of rule50 at the end. This is a backwards
|
|
// compatibile fix for rule50 having only 6 bits stored.
|
|
// This bit is just ignored by the old parsers.
|
|
stream.write_n_bit(pos.rule50Counter() >> 6, 1);
|
|
|
|
assert(stream.get_cursor() <= 256);
|
|
}
|
|
|
|
// sfen packed by pack() (256bit = 32bytes)
|
|
// Or sfen to decode with unpack()
|
|
uint8_t *data; // uint8_t[32];
|
|
|
|
BitStream stream;
|
|
|
|
// Output the board pieces to stream.
|
|
void write_board_piece_to_stream(chess::Piece pc)
|
|
{
|
|
// piece type
|
|
chess::PieceType pr = pc.type();
|
|
auto c = huffman_table[static_cast<int>(pr)];
|
|
stream.write_n_bit(c.code, c.bits);
|
|
|
|
if (pc == chess::Piece::none())
|
|
return;
|
|
|
|
// first and second flag
|
|
stream.write_one_bit(static_cast<int>(pc.color()));
|
|
}
|
|
|
|
// Read one board piece from stream
|
|
[[nodiscard]] chess::Piece read_board_piece_from_stream()
|
|
{
|
|
int pr = static_cast<int>(chess::PieceType::None);
|
|
int code = 0, bits = 0;
|
|
while (true)
|
|
{
|
|
code |= stream.read_one_bit() << bits;
|
|
++bits;
|
|
|
|
assert(bits <= 6);
|
|
|
|
for (pr = static_cast<int>(chess::PieceType::Pawn); pr <= static_cast<int>(chess::PieceType::None); ++pr)
|
|
if (huffman_table[pr].code == code
|
|
&& huffman_table[pr].bits == bits)
|
|
goto Found;
|
|
}
|
|
Found:;
|
|
if (pr == static_cast<int>(chess::PieceType::None))
|
|
return chess::Piece::none();
|
|
|
|
// first and second flag
|
|
chess::Color c = (chess::Color)stream.read_one_bit();
|
|
|
|
return chess::Piece(static_cast<chess::PieceType>(pr), c);
|
|
}
|
|
};
|
|
|
|
|
|
[[nodiscard]] inline chess::Position pos_from_packed_sfen(const PackedSfen& sfen)
|
|
{
|
|
SfenPacker packer;
|
|
auto& stream = packer.stream;
|
|
stream.set_data(const_cast<uint8_t*>(reinterpret_cast<const uint8_t*>(&sfen)));
|
|
|
|
chess::Position pos{};
|
|
|
|
// Active color
|
|
pos.setSideToMove((chess::Color)stream.read_one_bit());
|
|
|
|
// First the position of the ball
|
|
pos.place(chess::Piece(chess::PieceType::King, chess::Color::White), static_cast<chess::Square>(stream.read_n_bit(6)));
|
|
pos.place(chess::Piece(chess::PieceType::King, chess::Color::Black), static_cast<chess::Square>(stream.read_n_bit(6)));
|
|
|
|
// Piece placement
|
|
for (chess::Rank r = chess::rank8; r >= chess::rank1; --r)
|
|
{
|
|
for (chess::File f = chess::fileA; f <= chess::fileH; ++f)
|
|
{
|
|
auto sq = chess::Square(f, r);
|
|
|
|
// it seems there are already balls
|
|
chess::Piece pc;
|
|
if (pos.pieceAt(sq).type() != chess::PieceType::King)
|
|
{
|
|
assert(pos.pieceAt(sq) == chess::Piece::none());
|
|
pc = packer.read_board_piece_from_stream();
|
|
}
|
|
else
|
|
{
|
|
pc = pos.pieceAt(sq);
|
|
}
|
|
|
|
// There may be no pieces, so skip in that case.
|
|
if (pc == chess::Piece::none())
|
|
continue;
|
|
|
|
if (pc.type() != chess::PieceType::King)
|
|
{
|
|
pos.place(pc, sq);
|
|
}
|
|
|
|
assert(stream.get_cursor() <= 256);
|
|
}
|
|
}
|
|
|
|
// Castling availability.
|
|
chess::CastlingRights cr = chess::CastlingRights::None;
|
|
if (stream.read_one_bit()) {
|
|
cr |= chess::CastlingRights::WhiteKingSide;
|
|
}
|
|
if (stream.read_one_bit()) {
|
|
cr |= chess::CastlingRights::WhiteQueenSide;
|
|
}
|
|
if (stream.read_one_bit()) {
|
|
cr |= chess::CastlingRights::BlackKingSide;
|
|
}
|
|
if (stream.read_one_bit()) {
|
|
cr |= chess::CastlingRights::BlackQueenSide;
|
|
}
|
|
pos.setCastlingRights(cr);
|
|
|
|
// En passant square. Ignore if no pawn capture is possible
|
|
if (stream.read_one_bit()) {
|
|
chess::Square ep_square = static_cast<chess::Square>(stream.read_n_bit(6));
|
|
pos.setEpSquare(ep_square);
|
|
}
|
|
|
|
// Halfmove clock
|
|
std::uint8_t rule50 = stream.read_n_bit(6);
|
|
|
|
// Fullmove number
|
|
std::uint16_t fullmove = stream.read_n_bit(8);
|
|
|
|
// Fullmove number, high bits
|
|
// This was added as a fix for fullmove clock
|
|
// overflowing at 256. This change is backwards compatibile.
|
|
fullmove |= stream.read_n_bit(8) << 8;
|
|
|
|
// Read the highest bit of rule50. This was added as a fix for rule50
|
|
// counter having only 6 bits stored.
|
|
// In older entries this will just be a zero bit.
|
|
rule50 |= stream.read_n_bit(1) << 6;
|
|
|
|
pos.setFullMove(fullmove);
|
|
pos.setRule50Counter(rule50);
|
|
|
|
assert(stream.get_cursor() <= 256);
|
|
|
|
return pos;
|
|
}
|
|
}
|
|
|
|
struct CompressedTrainingDataFile
|
|
{
|
|
struct Header
|
|
{
|
|
std::uint32_t chunkSize;
|
|
};
|
|
|
|
CompressedTrainingDataFile(std::string path, std::ios_base::openmode om = std::ios_base::app) :
|
|
m_path(std::move(path)),
|
|
m_file(m_path, std::ios_base::binary | std::ios_base::in | std::ios_base::out | om)
|
|
{
|
|
// Necessary for MAC because app mode makes it put the reading
|
|
// head at the end.
|
|
m_file.seekg(0);
|
|
}
|
|
|
|
void append(const char* data, std::uint32_t size)
|
|
{
|
|
writeChunkHeader({size});
|
|
m_file.write(data, size);
|
|
}
|
|
|
|
[[nodiscard]] bool hasNextChunk()
|
|
{
|
|
if (!m_file)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
m_file.peek();
|
|
return !m_file.eof();
|
|
}
|
|
|
|
[[nodiscard]] std::vector<unsigned char> readNextChunk()
|
|
{
|
|
auto size = readChunkHeader().chunkSize;
|
|
std::vector<unsigned char> data(size);
|
|
m_file.read(reinterpret_cast<char*>(data.data()), size);
|
|
return data;
|
|
}
|
|
|
|
private:
|
|
std::string m_path;
|
|
std::fstream m_file;
|
|
|
|
void writeChunkHeader(Header h)
|
|
{
|
|
unsigned char header[8];
|
|
header[0] = 'B';
|
|
header[1] = 'I';
|
|
header[2] = 'N';
|
|
header[3] = 'P';
|
|
header[4] = h.chunkSize;
|
|
header[5] = h.chunkSize >> 8;
|
|
header[6] = h.chunkSize >> 16;
|
|
header[7] = h.chunkSize >> 24;
|
|
m_file.write(reinterpret_cast<const char*>(header), 8);
|
|
}
|
|
|
|
[[nodiscard]] Header readChunkHeader()
|
|
{
|
|
unsigned char header[8];
|
|
m_file.read(reinterpret_cast<char*>(header), 8);
|
|
if (header[0] != 'B' || header[1] != 'I' || header[2] != 'N' || header[3] != 'P')
|
|
{
|
|
assert(false);
|
|
// throw std::runtime_error("Invalid binpack file or chunk.");
|
|
}
|
|
|
|
const std::uint32_t size =
|
|
header[4]
|
|
| (header[5] << 8)
|
|
| (header[6] << 16)
|
|
| (header[7] << 24);
|
|
|
|
if (size > maxChunkSize)
|
|
{
|
|
assert(false);
|
|
// throw std::runtime_error("Chunks size larger than supported. Malformed file?");
|
|
}
|
|
|
|
return { size };
|
|
}
|
|
};
|
|
|
|
[[nodiscard]] inline std::uint16_t signedToUnsigned(std::int16_t a)
|
|
{
|
|
std::uint16_t r;
|
|
std::memcpy(&r, &a, sizeof(std::uint16_t));
|
|
if (r & 0x8000)
|
|
{
|
|
r ^= 0x7FFF;
|
|
}
|
|
r = (r << 1) | (r >> 15);
|
|
return r;
|
|
}
|
|
|
|
[[nodiscard]] inline std::int16_t unsignedToSigned(std::uint16_t r)
|
|
{
|
|
std::int16_t a;
|
|
r = (r << 15) | (r >> 1);
|
|
if (r & 0x8000)
|
|
{
|
|
r ^= 0x7FFF;
|
|
}
|
|
std::memcpy(&a, &r, sizeof(std::uint16_t));
|
|
return a;
|
|
}
|
|
|
|
struct TrainingDataEntry
|
|
{
|
|
chess::Position pos;
|
|
chess::Move move;
|
|
std::int16_t score;
|
|
std::uint16_t ply;
|
|
std::int16_t result;
|
|
|
|
[[nodiscard]] bool isValid() const
|
|
{
|
|
return pos.isMoveLegal(move);
|
|
}
|
|
};
|
|
|
|
[[nodiscard]] inline TrainingDataEntry packedSfenValueToTrainingDataEntry(const nodchip::PackedSfenValue& psv)
|
|
{
|
|
TrainingDataEntry ret;
|
|
|
|
ret.pos = nodchip::pos_from_packed_sfen(psv.sfen);
|
|
ret.move = psv.move.toMove();
|
|
ret.score = psv.score;
|
|
ret.ply = psv.gamePly;
|
|
ret.result = psv.game_result;
|
|
|
|
return ret;
|
|
}
|
|
|
|
[[nodiscard]] inline nodchip::PackedSfenValue trainingDataEntryToPackedSfenValue(const TrainingDataEntry& plain)
|
|
{
|
|
nodchip::PackedSfenValue ret;
|
|
|
|
nodchip::SfenPacker sp;
|
|
sp.data = reinterpret_cast<uint8_t*>(&ret.sfen);
|
|
sp.pack(plain.pos);
|
|
|
|
ret.score = plain.score;
|
|
ret.move = nodchip::StockfishMove::fromMove(plain.move);
|
|
ret.gamePly = plain.ply;
|
|
ret.game_result = plain.result;
|
|
ret.padding = 0xff; // for consistency with the .bin format.
|
|
|
|
return ret;
|
|
}
|
|
|
|
[[nodiscard]] inline bool isContinuation(const TrainingDataEntry& lhs, const TrainingDataEntry& rhs)
|
|
{
|
|
return
|
|
lhs.result == -rhs.result
|
|
&& lhs.ply + 1 == rhs.ply
|
|
&& lhs.pos.afterMove(lhs.move) == rhs.pos;
|
|
}
|
|
|
|
struct PackedTrainingDataEntry
|
|
{
|
|
unsigned char bytes[32];
|
|
};
|
|
|
|
[[nodiscard]] inline std::size_t usedBitsSafe(std::size_t value)
|
|
{
|
|
if (value == 0) return 0;
|
|
return chess::util::usedBits(value - 1);
|
|
}
|
|
|
|
static constexpr std::size_t scoreVleBlockSize = 4;
|
|
|
|
struct PackedMoveScoreListReader
|
|
{
|
|
TrainingDataEntry entry;
|
|
std::uint16_t numPlies;
|
|
unsigned char* movetext;
|
|
|
|
PackedMoveScoreListReader(const TrainingDataEntry& entry_, unsigned char* movetext_, std::uint16_t numPlies_) :
|
|
entry(entry_),
|
|
numPlies(numPlies_),
|
|
movetext(movetext_),
|
|
m_lastScore(-entry_.score)
|
|
{
|
|
|
|
}
|
|
|
|
[[nodiscard]] std::uint8_t extractBitsLE8(std::size_t count)
|
|
{
|
|
if (count == 0) return 0;
|
|
|
|
if (m_readBitsLeft == 0)
|
|
{
|
|
m_readOffset += 1;
|
|
m_readBitsLeft = 8;
|
|
}
|
|
|
|
const std::uint8_t byte = movetext[m_readOffset] << (8 - m_readBitsLeft);
|
|
std::uint8_t bits = byte >> (8 - count);
|
|
|
|
if (count > m_readBitsLeft)
|
|
{
|
|
const auto spillCount = count - m_readBitsLeft;
|
|
bits |= movetext[m_readOffset + 1] >> (8 - spillCount);
|
|
|
|
m_readBitsLeft += 8;
|
|
m_readOffset += 1;
|
|
}
|
|
|
|
m_readBitsLeft -= count;
|
|
|
|
return bits;
|
|
}
|
|
|
|
[[nodiscard]] std::uint16_t extractVle16(std::size_t blockSize)
|
|
{
|
|
auto mask = (1 << blockSize) - 1;
|
|
std::uint16_t v = 0;
|
|
std::size_t offset = 0;
|
|
for(;;)
|
|
{
|
|
std::uint16_t block = extractBitsLE8(blockSize + 1);
|
|
v |= ((block & mask) << offset);
|
|
if (!(block >> blockSize))
|
|
{
|
|
break;
|
|
}
|
|
|
|
offset += blockSize;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
[[nodiscard]] TrainingDataEntry nextEntry()
|
|
{
|
|
entry.pos.doMove(entry.move);
|
|
auto [move, score] = nextMoveScore(entry.pos);
|
|
entry.move = move;
|
|
entry.score = score;
|
|
entry.ply += 1;
|
|
entry.result = -entry.result;
|
|
return entry;
|
|
}
|
|
|
|
[[nodiscard]] bool hasNext() const
|
|
{
|
|
return m_numReadPlies < numPlies;
|
|
}
|
|
|
|
[[nodiscard]] std::pair<chess::Move, std::int16_t> nextMoveScore(const chess::Position& pos)
|
|
{
|
|
chess::Move move;
|
|
std::int16_t score;
|
|
|
|
const chess::Color sideToMove = pos.sideToMove();
|
|
const chess::Bitboard ourPieces = pos.piecesBB(sideToMove);
|
|
const chess::Bitboard theirPieces = pos.piecesBB(!sideToMove);
|
|
const chess::Bitboard occupied = ourPieces | theirPieces;
|
|
|
|
const auto pieceId = extractBitsLE8(usedBitsSafe(ourPieces.count()));
|
|
const auto from = chess::Square(chess::nthSetBitIndex(ourPieces.bits(), pieceId));
|
|
|
|
const auto pt = pos.pieceAt(from).type();
|
|
switch (pt)
|
|
{
|
|
case chess::PieceType::Pawn:
|
|
{
|
|
const chess::Rank promotionRank = pos.sideToMove() == chess::Color::White ? chess::rank7 : chess::rank2;
|
|
const chess::Rank startRank = pos.sideToMove() == chess::Color::White ? chess::rank2 : chess::rank7;
|
|
const auto forward = sideToMove == chess::Color::White ? chess::FlatSquareOffset(0, 1) : chess::FlatSquareOffset(0, -1);
|
|
|
|
const chess::Square epSquare = pos.epSquare();
|
|
|
|
chess::Bitboard attackTargets = theirPieces;
|
|
if (epSquare != chess::Square::none())
|
|
{
|
|
attackTargets |= epSquare;
|
|
}
|
|
|
|
chess::Bitboard destinations = chess::bb::pawnAttacks(chess::Bitboard::square(from), sideToMove) & attackTargets;
|
|
|
|
const chess::Square sqForward = from + forward;
|
|
if (!occupied.isSet(sqForward))
|
|
{
|
|
destinations |= sqForward;
|
|
if (
|
|
from.rank() == startRank
|
|
&& !occupied.isSet(sqForward + forward)
|
|
)
|
|
{
|
|
destinations |= sqForward + forward;
|
|
}
|
|
}
|
|
|
|
const auto destinationsCount = destinations.count();
|
|
if (from.rank() == promotionRank)
|
|
{
|
|
const auto moveId = extractBitsLE8(usedBitsSafe(destinationsCount * 4ull));
|
|
const chess::Piece promotedPiece = chess::Piece(
|
|
chess::fromOrdinal<chess::PieceType>(ordinal(chess::PieceType::Knight) + (moveId % 4ull)),
|
|
sideToMove
|
|
);
|
|
const auto to = chess::Square(chess::nthSetBitIndex(destinations.bits(), moveId / 4ull));
|
|
|
|
move = chess::Move::promotion(from, to, promotedPiece);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
auto moveId = extractBitsLE8(usedBitsSafe(destinationsCount));
|
|
const auto to = chess::Square(chess::nthSetBitIndex(destinations.bits(), moveId));
|
|
if (to == epSquare)
|
|
{
|
|
move = chess::Move::enPassant(from, to);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
move = chess::Move::normal(from, to);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
case chess::PieceType::King:
|
|
{
|
|
const chess::CastlingRights ourCastlingRightsMask =
|
|
sideToMove == chess::Color::White
|
|
? chess::CastlingRights::White
|
|
: chess::CastlingRights::Black;
|
|
|
|
const chess::CastlingRights castlingRights = pos.castlingRights();
|
|
|
|
const chess::Bitboard attacks = chess::bb::pseudoAttacks<chess::PieceType::King>(from) & ~ourPieces;
|
|
const std::size_t attacksSize = attacks.count();
|
|
const std::size_t numCastlings = chess::intrin::popcount(ordinal(castlingRights & ourCastlingRightsMask));
|
|
|
|
const auto moveId = extractBitsLE8(usedBitsSafe(attacksSize + numCastlings));
|
|
|
|
if (moveId >= attacksSize)
|
|
{
|
|
const std::size_t idx = moveId - attacksSize;
|
|
|
|
const chess::CastleType castleType =
|
|
idx == 0
|
|
&& chess::contains(castlingRights, chess::CastlingTraits::castlingRights[sideToMove][chess::CastleType::Long])
|
|
? chess::CastleType::Long
|
|
: chess::CastleType::Short;
|
|
|
|
move = chess::Move::castle(castleType, sideToMove);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
auto to = chess::Square(chess::nthSetBitIndex(attacks.bits(), moveId));
|
|
move = chess::Move::normal(from, to);
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
const chess::Bitboard attacks = chess::bb::attacks(pt, from, occupied) & ~ourPieces;
|
|
const auto moveId = extractBitsLE8(usedBitsSafe(attacks.count()));
|
|
auto to = chess::Square(chess::nthSetBitIndex(attacks.bits(), moveId));
|
|
move = chess::Move::normal(from, to);
|
|
break;
|
|
}
|
|
}
|
|
|
|
score = m_lastScore + unsignedToSigned(extractVle16(scoreVleBlockSize));
|
|
m_lastScore = -score;
|
|
|
|
++m_numReadPlies;
|
|
|
|
return {move, score};
|
|
}
|
|
|
|
[[nodiscard]] std::size_t numReadBytes()
|
|
{
|
|
return m_readOffset + (m_readBitsLeft != 8);
|
|
}
|
|
|
|
private:
|
|
std::size_t m_readBitsLeft = 8;
|
|
std::size_t m_readOffset = 0;
|
|
std::int16_t m_lastScore = 0;
|
|
std::uint16_t m_numReadPlies = 0;
|
|
};
|
|
|
|
struct PackedMoveScoreList
|
|
{
|
|
std::uint16_t numPlies = 0;
|
|
std::vector<unsigned char> movetext;
|
|
|
|
void clear(const TrainingDataEntry& e)
|
|
{
|
|
numPlies = 0;
|
|
movetext.clear();
|
|
m_bitsLeft = 0;
|
|
m_lastScore = -e.score;
|
|
}
|
|
|
|
void addBitsLE8(std::uint8_t bits, std::size_t count)
|
|
{
|
|
if (count == 0) return;
|
|
|
|
if (m_bitsLeft == 0)
|
|
{
|
|
movetext.emplace_back(bits << (8 - count));
|
|
m_bitsLeft = 8;
|
|
}
|
|
else if (count <= m_bitsLeft)
|
|
{
|
|
movetext.back() |= bits << (m_bitsLeft - count);
|
|
}
|
|
else
|
|
{
|
|
const auto spillCount = count - m_bitsLeft;
|
|
movetext.back() |= bits >> spillCount;
|
|
movetext.emplace_back(bits << (8 - spillCount));
|
|
m_bitsLeft += 8;
|
|
}
|
|
|
|
m_bitsLeft -= count;
|
|
}
|
|
|
|
void addBitsVle16(std::uint16_t v, std::size_t blockSize)
|
|
{
|
|
auto mask = (1 << blockSize) - 1;
|
|
for(;;)
|
|
{
|
|
std::uint8_t block = (v & mask) | ((v > mask) << blockSize);
|
|
addBitsLE8(block, blockSize + 1);
|
|
v >>= blockSize;
|
|
if (v == 0) break;
|
|
}
|
|
}
|
|
|
|
|
|
void addMoveScore(const chess::Position& pos, chess::Move move, std::int16_t score)
|
|
{
|
|
const chess::Color sideToMove = pos.sideToMove();
|
|
const chess::Bitboard ourPieces = pos.piecesBB(sideToMove);
|
|
const chess::Bitboard theirPieces = pos.piecesBB(!sideToMove);
|
|
const chess::Bitboard occupied = ourPieces | theirPieces;
|
|
|
|
const std::uint8_t pieceId = (pos.piecesBB(sideToMove) & chess::bb::before(move.from)).count();
|
|
std::size_t numMoves = 0;
|
|
int moveId = 0;
|
|
const auto pt = pos.pieceAt(move.from).type();
|
|
switch (pt)
|
|
{
|
|
case chess::PieceType::Pawn:
|
|
{
|
|
const chess::Rank secondToLastRank = pos.sideToMove() == chess::Color::White ? chess::rank7 : chess::rank2;
|
|
const chess::Rank startRank = pos.sideToMove() == chess::Color::White ? chess::rank2 : chess::rank7;
|
|
const auto forward = sideToMove == chess::Color::White ? chess::FlatSquareOffset(0, 1) : chess::FlatSquareOffset(0, -1);
|
|
|
|
const chess::Square epSquare = pos.epSquare();
|
|
|
|
chess::Bitboard attackTargets = theirPieces;
|
|
if (epSquare != chess::Square::none())
|
|
{
|
|
attackTargets |= epSquare;
|
|
}
|
|
|
|
chess::Bitboard destinations = chess::bb::pawnAttacks(chess::Bitboard::square(move.from), sideToMove) & attackTargets;
|
|
|
|
const chess::Square sqForward = move.from + forward;
|
|
if (!occupied.isSet(sqForward))
|
|
{
|
|
destinations |= sqForward;
|
|
|
|
if (
|
|
move.from.rank() == startRank
|
|
&& !occupied.isSet(sqForward + forward)
|
|
)
|
|
{
|
|
destinations |= sqForward + forward;
|
|
}
|
|
}
|
|
|
|
moveId = (destinations & chess::bb::before(move.to)).count();
|
|
numMoves = destinations.count();
|
|
if (move.from.rank() == secondToLastRank)
|
|
{
|
|
const auto promotionIndex = (ordinal(move.promotedPiece.type()) - ordinal(chess::PieceType::Knight));
|
|
moveId = moveId * 4 + promotionIndex;
|
|
numMoves *= 4;
|
|
}
|
|
|
|
break;
|
|
}
|
|
case chess::PieceType::King:
|
|
{
|
|
const chess::CastlingRights ourCastlingRightsMask =
|
|
sideToMove == chess::Color::White
|
|
? chess::CastlingRights::White
|
|
: chess::CastlingRights::Black;
|
|
|
|
const chess::CastlingRights castlingRights = pos.castlingRights();
|
|
|
|
const chess::Bitboard attacks = chess::bb::pseudoAttacks<chess::PieceType::King>(move.from) & ~ourPieces;
|
|
const auto attacksSize = attacks.count();
|
|
const auto numCastlingRights = chess::intrin::popcount(ordinal(castlingRights & ourCastlingRightsMask));
|
|
|
|
numMoves += attacksSize;
|
|
numMoves += numCastlingRights;
|
|
|
|
if (move.type == chess::MoveType::Castle)
|
|
{
|
|
const auto longCastlingRights = chess::CastlingTraits::castlingRights[sideToMove][chess::CastleType::Long];
|
|
|
|
moveId = attacksSize - 1;
|
|
|
|
if (chess::contains(castlingRights, longCastlingRights))
|
|
{
|
|
// We have to add one no matter if it's the used one or not.
|
|
moveId += 1;
|
|
}
|
|
|
|
if (chess::CastlingTraits::moveCastlingType(move) == chess::CastleType::Short)
|
|
{
|
|
moveId += 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
moveId = (attacks & chess::bb::before(move.to)).count();
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
const chess::Bitboard attacks = chess::bb::attacks(pt, move.from, occupied) & ~ourPieces;
|
|
|
|
moveId = (attacks & chess::bb::before(move.to)).count();
|
|
numMoves = attacks.count();
|
|
}
|
|
}
|
|
|
|
const std::size_t numPieces = ourPieces.count();
|
|
addBitsLE8(pieceId, usedBitsSafe(numPieces));
|
|
addBitsLE8(moveId, usedBitsSafe(numMoves));
|
|
|
|
std::uint16_t scoreDelta = signedToUnsigned(score - m_lastScore);
|
|
addBitsVle16(scoreDelta, scoreVleBlockSize);
|
|
m_lastScore = -score;
|
|
|
|
++numPlies;
|
|
}
|
|
|
|
private:
|
|
std::size_t m_bitsLeft = 0;
|
|
std::int16_t m_lastScore = 0;
|
|
};
|
|
|
|
|
|
[[nodiscard]] inline PackedTrainingDataEntry packEntry(const TrainingDataEntry& plain)
|
|
{
|
|
PackedTrainingDataEntry packed;
|
|
|
|
auto compressedPos = plain.pos.compress();
|
|
auto compressedMove = plain.move.compress();
|
|
|
|
static_assert(sizeof(compressedPos) + sizeof(compressedMove) + 6 == sizeof(PackedTrainingDataEntry));
|
|
|
|
std::size_t offset = 0;
|
|
compressedPos.writeToBigEndian(packed.bytes);
|
|
offset += sizeof(compressedPos);
|
|
compressedMove.writeToBigEndian(packed.bytes + offset);
|
|
offset += sizeof(compressedMove);
|
|
std::uint16_t pr = plain.ply | (signedToUnsigned(plain.result) << 14);
|
|
packed.bytes[offset++] = signedToUnsigned(plain.score) >> 8;
|
|
packed.bytes[offset++] = signedToUnsigned(plain.score);
|
|
packed.bytes[offset++] = pr >> 8;
|
|
packed.bytes[offset++] = pr;
|
|
packed.bytes[offset++] = plain.pos.rule50Counter() >> 8;
|
|
packed.bytes[offset++] = plain.pos.rule50Counter();
|
|
|
|
return packed;
|
|
}
|
|
|
|
[[nodiscard]] inline TrainingDataEntry unpackEntry(const PackedTrainingDataEntry& packed)
|
|
{
|
|
TrainingDataEntry plain;
|
|
|
|
std::size_t offset = 0;
|
|
auto compressedPos = chess::CompressedPosition::readFromBigEndian(packed.bytes);
|
|
plain.pos = compressedPos.decompress();
|
|
offset += sizeof(compressedPos);
|
|
auto compressedMove = chess::CompressedMove::readFromBigEndian(packed.bytes + offset);
|
|
plain.move = compressedMove.decompress();
|
|
offset += sizeof(compressedMove);
|
|
plain.score = unsignedToSigned((packed.bytes[offset] << 8) | packed.bytes[offset+1]);
|
|
offset += 2;
|
|
std::uint16_t pr = (packed.bytes[offset] << 8) | packed.bytes[offset+1];
|
|
plain.ply = pr & 0x3FFF;
|
|
plain.pos.setPly(plain.ply);
|
|
plain.result = unsignedToSigned(pr >> 14);
|
|
offset += 2;
|
|
plain.pos.setRule50Counter((packed.bytes[offset] << 8) | packed.bytes[offset+1]);
|
|
|
|
return plain;
|
|
}
|
|
|
|
struct CompressedTrainingDataEntryWriter
|
|
{
|
|
static constexpr std::size_t chunkSize = suggestedChunkSize;
|
|
|
|
CompressedTrainingDataEntryWriter(std::string path, std::ios_base::openmode om = std::ios_base::app) :
|
|
m_outputFile(path, om),
|
|
m_lastEntry{},
|
|
m_movelist{},
|
|
m_packedSize(0),
|
|
m_packedEntries(chunkSize + maxMovelistSize),
|
|
m_isFirst(true)
|
|
{
|
|
m_lastEntry.ply = 0xFFFF; // so it's never a continuation
|
|
m_lastEntry.result = 0x7FFF;
|
|
}
|
|
|
|
void addTrainingDataEntry(const TrainingDataEntry& e)
|
|
{
|
|
bool isCont = isContinuation(m_lastEntry, e);
|
|
if (isCont)
|
|
{
|
|
// add to movelist
|
|
m_movelist.addMoveScore(e.pos, e.move, e.score);
|
|
}
|
|
else
|
|
{
|
|
if (!m_isFirst)
|
|
{
|
|
writeMovelist();
|
|
}
|
|
|
|
if (m_packedSize >= chunkSize)
|
|
{
|
|
m_outputFile.append(m_packedEntries.data(), m_packedSize);
|
|
m_packedSize = 0;
|
|
}
|
|
|
|
auto packed = packEntry(e);
|
|
std::memcpy(m_packedEntries.data() + m_packedSize, &packed, sizeof(PackedTrainingDataEntry));
|
|
m_packedSize += sizeof(PackedTrainingDataEntry);
|
|
|
|
m_movelist.clear(e);
|
|
|
|
m_isFirst = false;
|
|
}
|
|
|
|
m_lastEntry = e;
|
|
}
|
|
|
|
~CompressedTrainingDataEntryWriter()
|
|
{
|
|
if (m_packedSize > 0)
|
|
{
|
|
if (!m_isFirst)
|
|
{
|
|
writeMovelist();
|
|
}
|
|
|
|
m_outputFile.append(m_packedEntries.data(), m_packedSize);
|
|
m_packedSize = 0;
|
|
}
|
|
}
|
|
|
|
private:
|
|
CompressedTrainingDataFile m_outputFile;
|
|
TrainingDataEntry m_lastEntry;
|
|
PackedMoveScoreList m_movelist;
|
|
std::size_t m_packedSize;
|
|
std::vector<char> m_packedEntries;
|
|
bool m_isFirst;
|
|
|
|
void writeMovelist()
|
|
{
|
|
m_packedEntries[m_packedSize++] = m_movelist.numPlies >> 8;
|
|
m_packedEntries[m_packedSize++] = m_movelist.numPlies;
|
|
if (m_movelist.numPlies > 0)
|
|
{
|
|
std::memcpy(m_packedEntries.data() + m_packedSize, m_movelist.movetext.data(), m_movelist.movetext.size());
|
|
m_packedSize += m_movelist.movetext.size();
|
|
}
|
|
};
|
|
};
|
|
|
|
struct CompressedTrainingDataEntryReader
|
|
{
|
|
static constexpr std::size_t chunkSize = suggestedChunkSize;
|
|
|
|
CompressedTrainingDataEntryReader(std::string path, std::ios_base::openmode om = std::ios_base::app) :
|
|
m_inputFile(path, om),
|
|
m_chunk(),
|
|
m_movelistReader(std::nullopt),
|
|
m_offset(0),
|
|
m_isEnd(false)
|
|
{
|
|
if (!m_inputFile.hasNextChunk())
|
|
{
|
|
m_isEnd = true;
|
|
}
|
|
else
|
|
{
|
|
m_chunk = m_inputFile.readNextChunk();
|
|
}
|
|
}
|
|
|
|
[[nodiscard]] bool hasNext()
|
|
{
|
|
return !m_isEnd;
|
|
}
|
|
|
|
[[nodiscard]] TrainingDataEntry next()
|
|
{
|
|
if (m_movelistReader.has_value())
|
|
{
|
|
const auto e = m_movelistReader->nextEntry();
|
|
|
|
if (!m_movelistReader->hasNext())
|
|
{
|
|
m_offset += m_movelistReader->numReadBytes();
|
|
m_movelistReader.reset();
|
|
|
|
fetchNextChunkIfNeeded();
|
|
}
|
|
|
|
return e;
|
|
}
|
|
|
|
PackedTrainingDataEntry packed;
|
|
std::memcpy(&packed, m_chunk.data() + m_offset, sizeof(PackedTrainingDataEntry));
|
|
m_offset += sizeof(PackedTrainingDataEntry);
|
|
|
|
const std::uint16_t numPlies = (m_chunk[m_offset] << 8) | m_chunk[m_offset + 1];
|
|
m_offset += 2;
|
|
|
|
const auto e = unpackEntry(packed);
|
|
|
|
if (numPlies > 0)
|
|
{
|
|
m_movelistReader.emplace(e, reinterpret_cast<unsigned char*>(m_chunk.data()) + m_offset, numPlies);
|
|
}
|
|
else
|
|
{
|
|
fetchNextChunkIfNeeded();
|
|
}
|
|
|
|
return e;
|
|
}
|
|
|
|
private:
|
|
CompressedTrainingDataFile m_inputFile;
|
|
std::vector<unsigned char> m_chunk;
|
|
std::optional<PackedMoveScoreListReader> m_movelistReader;
|
|
std::size_t m_offset;
|
|
bool m_isEnd;
|
|
|
|
void fetchNextChunkIfNeeded()
|
|
{
|
|
if (m_offset + sizeof(PackedTrainingDataEntry) + 2 > m_chunk.size())
|
|
{
|
|
if (m_inputFile.hasNextChunk())
|
|
{
|
|
m_chunk = m_inputFile.readNextChunk();
|
|
m_offset = 0;
|
|
}
|
|
else
|
|
{
|
|
m_isEnd = true;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
inline void emitPlainEntry(std::string& buffer, const TrainingDataEntry& plain)
|
|
{
|
|
buffer += "fen ";
|
|
buffer += plain.pos.fen();
|
|
buffer += '\n';
|
|
|
|
buffer += "move ";
|
|
buffer += chess::uci::moveToUci(plain.pos, plain.move);
|
|
buffer += '\n';
|
|
|
|
buffer += "score ";
|
|
buffer += std::to_string(plain.score);
|
|
buffer += '\n';
|
|
|
|
buffer += "ply ";
|
|
buffer += std::to_string(plain.ply);
|
|
buffer += '\n';
|
|
|
|
buffer += "result ";
|
|
buffer += std::to_string(plain.result);
|
|
buffer += "\ne\n";
|
|
}
|
|
|
|
inline void emitBinEntry(std::vector<char>& buffer, const TrainingDataEntry& plain)
|
|
{
|
|
auto psv = trainingDataEntryToPackedSfenValue(plain);
|
|
const char* data = reinterpret_cast<const char*>(&psv);
|
|
buffer.insert(buffer.end(), data, data+sizeof(psv));
|
|
}
|
|
|
|
inline void convertPlainToBinpack(std::string inputPath, std::string outputPath, std::ios_base::openmode om, bool validate)
|
|
{
|
|
constexpr std::size_t reportEveryNPositions = 100'000;
|
|
|
|
std::cout << "Converting " << inputPath << " to " << outputPath << '\n';
|
|
|
|
CompressedTrainingDataEntryWriter writer(outputPath, om);
|
|
TrainingDataEntry e;
|
|
|
|
std::string key;
|
|
std::string value;
|
|
std::string move;
|
|
|
|
std::ifstream inputFile(inputPath);
|
|
const auto base = inputFile.tellg();
|
|
std::size_t numProcessedPositions = 0;
|
|
|
|
for(;;)
|
|
{
|
|
inputFile >> key;
|
|
if (!inputFile)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (key == "e"sv)
|
|
{
|
|
e.move = chess::uci::uciToMove(e.pos, move);
|
|
if (validate && !e.isValid())
|
|
{
|
|
std::cerr << "Illegal move " << chess::uci::moveToUci(e.pos, e.move) << " for position " << e.pos.fen() << '\n';
|
|
return;
|
|
}
|
|
|
|
writer.addTrainingDataEntry(e);
|
|
|
|
++numProcessedPositions;
|
|
const auto cur = inputFile.tellg();
|
|
if (numProcessedPositions % reportEveryNPositions == 0)
|
|
{
|
|
std::cout << "Processed " << (cur - base) << " bytes and " << numProcessedPositions << " positions.\n";
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
inputFile >> std::ws;
|
|
std::getline(inputFile, value, '\n');
|
|
|
|
if (key == "fen"sv) e.pos = chess::Position::fromFen(value.c_str());
|
|
if (key == "move"sv) move = value;
|
|
if (key == "score"sv) e.score = std::stoi(value);
|
|
if (key == "ply"sv) e.ply = std::stoi(value);
|
|
if (key == "result"sv) e.result = std::stoi(value);
|
|
}
|
|
|
|
std::cout << "Finished. Converted " << numProcessedPositions << " positions.\n";
|
|
}
|
|
|
|
inline void convertBinpackToPlain(std::string inputPath, std::string outputPath, std::ios_base::openmode om, bool validate)
|
|
{
|
|
constexpr std::size_t bufferSize = MiB;
|
|
|
|
std::cout << "Converting " << inputPath << " to " << outputPath << '\n';
|
|
|
|
CompressedTrainingDataEntryReader reader(inputPath);
|
|
std::ofstream outputFile(outputPath, om);
|
|
const auto base = outputFile.tellp();
|
|
std::size_t numProcessedPositions = 0;
|
|
std::string buffer;
|
|
buffer.reserve(bufferSize * 2);
|
|
|
|
while(reader.hasNext())
|
|
{
|
|
auto e = reader.next();
|
|
if (validate && !e.isValid())
|
|
{
|
|
std::cerr << "Illegal move " << chess::uci::moveToUci(e.pos, e.move) << " for position " << e.pos.fen() << '\n';
|
|
return;
|
|
}
|
|
|
|
emitPlainEntry(buffer, e);
|
|
|
|
++numProcessedPositions;
|
|
|
|
if (buffer.size() > bufferSize)
|
|
{
|
|
outputFile << buffer;
|
|
buffer.clear();
|
|
|
|
const auto cur = outputFile.tellp();
|
|
std::cout << "Processed " << (cur - base) << " bytes and " << numProcessedPositions << " positions.\n";
|
|
}
|
|
}
|
|
|
|
if (!buffer.empty())
|
|
{
|
|
outputFile << buffer;
|
|
|
|
const auto cur = outputFile.tellp();
|
|
std::cout << "Processed " << (cur - base) << " bytes and " << numProcessedPositions << " positions.\n";
|
|
}
|
|
|
|
std::cout << "Finished. Converted " << numProcessedPositions << " positions.\n";
|
|
}
|
|
|
|
|
|
inline void convertBinToBinpack(std::string inputPath, std::string outputPath, std::ios_base::openmode om, bool validate)
|
|
{
|
|
constexpr std::size_t reportEveryNPositions = 100'000;
|
|
|
|
std::cout << "Converting " << inputPath << " to " << outputPath << '\n';
|
|
|
|
CompressedTrainingDataEntryWriter writer(outputPath, om);
|
|
|
|
std::ifstream inputFile(inputPath, std::ios_base::binary);
|
|
const auto base = inputFile.tellg();
|
|
std::size_t numProcessedPositions = 0;
|
|
|
|
nodchip::PackedSfenValue psv;
|
|
for(;;)
|
|
{
|
|
inputFile.read(reinterpret_cast<char*>(&psv), sizeof(psv));
|
|
if (inputFile.gcount() != 40)
|
|
{
|
|
break;
|
|
}
|
|
|
|
auto e = packedSfenValueToTrainingDataEntry(psv);
|
|
if (validate && !e.isValid())
|
|
{
|
|
std::cerr << "Illegal move " << chess::uci::moveToUci(e.pos, e.move) << " for position " << e.pos.fen() << '\n';
|
|
std::cerr << static_cast<int>(e.move.type) << '\n';
|
|
return;
|
|
}
|
|
|
|
writer.addTrainingDataEntry(e);
|
|
|
|
++numProcessedPositions;
|
|
const auto cur = inputFile.tellg();
|
|
if (numProcessedPositions % reportEveryNPositions == 0)
|
|
{
|
|
std::cout << "Processed " << (cur - base) << " bytes and " << numProcessedPositions << " positions.\n";
|
|
}
|
|
}
|
|
|
|
std::cout << "Finished. Converted " << numProcessedPositions << " positions.\n";
|
|
}
|
|
|
|
inline void convertBinpackToBin(std::string inputPath, std::string outputPath, std::ios_base::openmode om, bool validate)
|
|
{
|
|
constexpr std::size_t bufferSize = MiB;
|
|
|
|
std::cout << "Converting " << inputPath << " to " << outputPath << '\n';
|
|
|
|
CompressedTrainingDataEntryReader reader(inputPath);
|
|
std::ofstream outputFile(outputPath, std::ios_base::binary | om);
|
|
const auto base = outputFile.tellp();
|
|
std::size_t numProcessedPositions = 0;
|
|
std::vector<char> buffer;
|
|
buffer.reserve(bufferSize * 2);
|
|
|
|
while(reader.hasNext())
|
|
{
|
|
auto e = reader.next();
|
|
if (validate && !e.isValid())
|
|
{
|
|
std::cerr << "Illegal move " << chess::uci::moveToUci(e.pos, e.move) << " for position " << e.pos.fen() << '\n';
|
|
return;
|
|
}
|
|
|
|
emitBinEntry(buffer, e);
|
|
|
|
++numProcessedPositions;
|
|
|
|
if (buffer.size() > bufferSize)
|
|
{
|
|
outputFile.write(buffer.data(), buffer.size());
|
|
buffer.clear();
|
|
|
|
const auto cur = outputFile.tellp();
|
|
std::cout << "Processed " << (cur - base) << " bytes and " << numProcessedPositions << " positions.\n";
|
|
}
|
|
}
|
|
|
|
if (!buffer.empty())
|
|
{
|
|
outputFile.write(buffer.data(), buffer.size());
|
|
|
|
const auto cur = outputFile.tellp();
|
|
std::cout << "Processed " << (cur - base) << " bytes and " << numProcessedPositions << " positions.\n";
|
|
}
|
|
|
|
std::cout << "Finished. Converted " << numProcessedPositions << " positions.\n";
|
|
}
|
|
|
|
inline void convertBinToPlain(std::string inputPath, std::string outputPath, std::ios_base::openmode om, bool validate)
|
|
{
|
|
constexpr std::size_t bufferSize = MiB;
|
|
|
|
std::cout << "Converting " << inputPath << " to " << outputPath << '\n';
|
|
|
|
std::ifstream inputFile(inputPath, std::ios_base::binary);
|
|
const auto base = inputFile.tellg();
|
|
std::size_t numProcessedPositions = 0;
|
|
|
|
std::ofstream outputFile(outputPath, om);
|
|
std::string buffer;
|
|
buffer.reserve(bufferSize * 2);
|
|
|
|
nodchip::PackedSfenValue psv;
|
|
for(;;)
|
|
{
|
|
inputFile.read(reinterpret_cast<char*>(&psv), sizeof(psv));
|
|
if (inputFile.gcount() != 40)
|
|
{
|
|
break;
|
|
}
|
|
|
|
auto e = packedSfenValueToTrainingDataEntry(psv);
|
|
if (validate && !e.isValid())
|
|
{
|
|
std::cerr << "Illegal move " << chess::uci::moveToUci(e.pos, e.move) << " for position " << e.pos.fen() << '\n';
|
|
return;
|
|
}
|
|
|
|
emitPlainEntry(buffer, e);
|
|
|
|
++numProcessedPositions;
|
|
|
|
if (buffer.size() > bufferSize)
|
|
{
|
|
outputFile << buffer;
|
|
buffer.clear();
|
|
|
|
const auto cur = outputFile.tellp();
|
|
std::cout << "Processed " << (cur - base) << " bytes and " << numProcessedPositions << " positions.\n";
|
|
}
|
|
}
|
|
|
|
if (!buffer.empty())
|
|
{
|
|
outputFile << buffer;
|
|
|
|
const auto cur = outputFile.tellp();
|
|
std::cout << "Processed " << (cur - base) << " bytes and " << numProcessedPositions << " positions.\n";
|
|
}
|
|
|
|
std::cout << "Finished. Converted " << numProcessedPositions << " positions.\n";
|
|
}
|
|
|
|
inline void convertPlainToBin(std::string inputPath, std::string outputPath, std::ios_base::openmode om, bool validate)
|
|
{
|
|
constexpr std::size_t bufferSize = MiB;
|
|
|
|
std::cout << "Converting " << inputPath << " to " << outputPath << '\n';
|
|
|
|
std::ofstream outputFile(outputPath, std::ios_base::binary | om);
|
|
std::vector<char> buffer;
|
|
buffer.reserve(bufferSize * 2);
|
|
|
|
TrainingDataEntry e;
|
|
|
|
std::string key;
|
|
std::string value;
|
|
std::string move;
|
|
|
|
std::ifstream inputFile(inputPath);
|
|
const auto base = inputFile.tellg();
|
|
std::size_t numProcessedPositions = 0;
|
|
|
|
for(;;)
|
|
{
|
|
inputFile >> key;
|
|
if (!inputFile)
|
|
{
|
|
break;
|
|
}
|
|
|
|
if (key == "e"sv)
|
|
{
|
|
e.move = chess::uci::uciToMove(e.pos, move);
|
|
if (validate && !e.isValid())
|
|
{
|
|
std::cerr << "Illegal move " << chess::uci::moveToUci(e.pos, e.move) << " for position " << e.pos.fen() << '\n';
|
|
return;
|
|
}
|
|
|
|
emitBinEntry(buffer, e);
|
|
|
|
++numProcessedPositions;
|
|
|
|
if (buffer.size() > bufferSize)
|
|
{
|
|
outputFile.write(buffer.data(), buffer.size());
|
|
buffer.clear();
|
|
|
|
const auto cur = outputFile.tellp();
|
|
std::cout << "Processed " << (cur - base) << " bytes and " << numProcessedPositions << " positions.\n";
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
inputFile >> std::ws;
|
|
std::getline(inputFile, value, '\n');
|
|
|
|
if (key == "fen"sv) e.pos = chess::Position::fromFen(value.c_str());
|
|
if (key == "move"sv) move = value;
|
|
if (key == "score"sv) e.score = std::stoi(value);
|
|
if (key == "ply"sv) e.ply = std::stoi(value);
|
|
if (key == "result"sv) e.result = std::stoi(value);
|
|
}
|
|
|
|
if (!buffer.empty())
|
|
{
|
|
outputFile.write(buffer.data(), buffer.size());
|
|
|
|
const auto cur = outputFile.tellp();
|
|
std::cout << "Processed " << (cur - base) << " bytes and " << numProcessedPositions << " positions.\n";
|
|
}
|
|
|
|
std::cout << "Finished. Converted " << numProcessedPositions << " positions.\n";
|
|
}
|
|
}
|