2

我正在开发一个国际象棋应用程序,需要创建一个开放的书,一个可能包含数百万个移动和位置的文件。我们有 64 个方格,其中一些已经被碎片占据,而另一些则是空的。让我们用以下位表示我们的片段(使用霍夫曼编码技术)。

              White          Black
-Empty        0

-Pawn         110           100

-Rook         11111         11110

-Knight       10110         10101

-Bishop       10100         11100

-Queen        111010        111011

-King         101110        101111

在初始位置,我们有 32 个正方形被不同的棋子占据,32 个正方形是空的。为了提高效率,我必须将位置存储在连续位中。块位将按顺序放置在位数组中,从 a1 方格开始,然后是 a2 方格,..a8,然后是 b1,b2...b8 方格,依此类推。

因此,对于起始位置,这等于 32 x 1 + 16 x 3 + 12 x 5 + 4 x 6 = 164 位。

不同的游戏情况还需要额外的 16 位,例如是否启用了castling,并在适用时定义了enpassant square。所以我需要大约 180 位(或 23 字节 = 184 位)来存储棋盘的单个位置。

现在的问题是我必须执行一些按位运算,因此想知道该方案如何在我的代码中操作它以及如何存储在文件中。表示我应该使用哪种数据结构。例如,最大 long(数据类型)将仅包含 4 个字节 = 64 位。我想避免使用字符串。任何机构都可以建议如何进行此操作。

我正在使用 C#.Net,框架 3.5。

4

4 回答 4

6

对于国际象棋应用程序,最常用的结构是这样的;

  • 一个 ulong 64 位变量,其中每个位代表板上的一个位置
  • 为每种颜色的棋子、骑士等单独变量。

所以

ulong WHITE_PAWNS = xxx;
ulong BLACK_PAWNS = xxx;

等等

这不是存储效率最高的版本,但可以让您非常快速地做很多事情。为了让棋子和骑士在一起,你可以做

white_pawns_and_knights = WHITE_PAWNS | WHITE_KNIGHTS;

在此处(以及其他地方)阅读有关它的更多信息。

我已经编写了自己的国际象棋程序(在 Delphi 和部分 C# 中),那里有很多好东西。这些是我保存在这个主题上的书签;

如果你想要的话,我还有一个用于对 ulongs 进行快速位操作的 C# 单元。如果您需要,请通过 ivotops#gmail#com 给我发邮件,并随时询问。

不幸的是,我的时间太少,从未完成 C# 版本;-)

于 2012-10-12T08:09:42.850 回答
3

对于一本书,您可以使用这种方法;

为每块板生成一个好的 64 位哈希码(谷歌 zobrist 哈希)

有一本字典,您可以在其中使用哈希码作为键。

所以你不存储实际的电路板。如果哈希码匹配,您可以假设它是相同的板(64 位冲突的可能性在小数点后十五位之后开始)。作为最后的检查测试是否允许在棋盘上进行开局,您可以开始了。

然后编写代码将字典保存为一个整体。这段代码可以做到这一点并适用于所有可枚举的容器;

 // save
 using (var fs = new FileStream(fileName, FileMode.Create))
        {
            var bw = new BinaryWriter(fs);
            foreach (var kvp in this)
            {
                kvp.Key.AddToStream(bw);
                kvp.Value.AddToStream(bw);
            }
        }

 // load
 using (var fs = new FileStream(fileName, FileMode.Open))
            {
                var fslen = fs.Length;
                var br = new BinaryReader(fs);
                while (fs.Position < fslen)
                {
                    var k = new Pattern();
                    var v = new BestMove();
                    k.ReadFromStream(br);
                    v.ReadFromStream(br);
                    Add(k, v);
                }
            }

这是生成 64 位 Zobrist 哈希的方法。这些应该存储在某个永久位置,以便以后可以在此答案底部显示的代码方法中重用它们。在这里,它们被存储为静态类的静态成员:

internal static class HashKeys
{
    internal static readonly UInt64[,] PieceSquareKeys = new UInt64[64,16];
    internal static readonly UInt64[] EnPassantKeys = new UInt64[64];
    internal static readonly UInt64 SideToMoveKey;
    internal static readonly UInt64 WhiteCastlingKingSideKey;
    internal static readonly UInt64 WhiteCastlingQueenSideKey;
    internal static readonly UInt64 BlackCastlingKingSideKey;
    internal static readonly UInt64 BlackCastlingQueenSideKey;

    // Constructor - generates pseudo-random numbers for Zobrist hashing.
    // The use of a CSPRNG is a good guaranteee of genuinely random numbers.
    static HashKeys()
    {
        RNGCryptoServiceProvider randomGenerator = new RNGCryptoServiceProvider();
        byte[] eightRandomBytes = new byte[8];

        try
        {
            for (Int32 i1 = 0; i1 < 64; i1++)
            {
                for (Int32 i2 = 0; i1 < 16; i1++)
                {
                    randomGenerator.GetBytes(eightRandomBytes);
                    PieceSquareKeys[i1, i2] = BitConverter.ToUInt64(eightRandomBytes, 0);
                }
                randomGenerator.GetBytes(eightRandomBytes);
                EnPassantKeys[i1] = BitConverter.ToUInt64(eightRandomBytes, 0);
            }

            randomGenerator.GetBytes(eightRandomBytes);
            SideToMoveKey = BitConverter.ToUInt64(eightRandomBytes, 0);
            randomGenerator.GetBytes(eightRandomBytes);
            WhiteCastlingKingSideKey = BitConverter.ToUInt64(eightRandomBytes, 0);

            randomGenerator.GetBytes(eightRandomBytes);
            WhiteCastlingQueenSideKey = BitConverter.ToUInt64(eightRandomBytes, 0);

            randomGenerator.GetBytes(eightRandomBytes);
            BlackCastlingKingSideKey = BitConverter.ToUInt64(eightRandomBytes, 0);

            randomGenerator.GetBytes(eightRandomBytes);
            BlackCastlingQueenSideKey = BitConverter.ToUInt64(eightRandomBytes, 0);
        }
        finally
        {
            randomGenerator.Dispose();
        }
    }
}

这是如何生成代表棋盘位置的 64 位 Zobrish 散列(包括边移动、易位权、过路人等:

// Init Zobrist position hash, used in transposition table and draw detection.
// This will be incrementally updated during move make/unmake.
internal static UInt64 InitPositionHash(byte[] squares, ComplexProperties propertyStore, byte sideToMove)
{
    UInt64 positionHash = 0;

    // Calculate piece/square hashes.
    for (Int32 i = 0; i < 64; i++)
    {
        if (squares[i] != Constants.EMPTY)
        {
            positionHash ^= HashKeys.PieceSquareKeys[i, squares[i]];
        }
    }

    // Add side to move only if Black.
    if (sideToMove == Constants.BLACK)
    {
        positionHash ^= HashKeys.SideToMoveKey;
    }

    // Add en-passant square if applicable.
    if (propertyStore.EpSquare != 0)
    {
        positionHash ^= HashKeys.EnPassantKeys[propertyStore.EpSquare];
    }

    // White castling.
    switch (propertyStore.WhiteCastlingStatus)
    {
        case Constants.EnumCastlingStatus.CAN_CASTLE_BOTH:
             positionHash ^= HashKeys.WhiteCastlingKingSideKey;
             positionHash ^= HashKeys.WhiteCastlingQueenSideKey;
             break;
        case Constants.EnumCastlingStatus.CAN_CASTLE_OO:
             positionHash ^= HashKeys.WhiteCastlingKingSideKey;
             break;
        case Constants.EnumCastlingStatus.CAN_CASTLE_OOO:
             positionHash ^= HashKeys.WhiteCastlingQueenSideKey;
             break;
        case Constants.EnumCastlingStatus.CANT_CASTLE:
             break;
        default:
             Debug.Assert(false, "White has an invalid castling status!");
             break;
    }

    // Black castling.
    switch (propertyStore.BlackCastlingStatus)
    {
        case Constants.EnumCastlingStatus.CAN_CASTLE_BOTH:
             positionHash ^= HashKeys.BlackCastlingKingSideKey;
             positionHash ^= HashKeys.BlackCastlingQueenSideKey;
             break;
        case Constants.EnumCastlingStatus.CAN_CASTLE_OO:
             positionHash ^= HashKeys.BlackCastlingKingSideKey;
             break;
        case Constants.EnumCastlingStatus.CAN_CASTLE_OOO:
             positionHash ^= HashKeys.BlackCastlingQueenSideKey;
             break;
        case Constants.EnumCastlingStatus.CANT_CASTLE:
             break;
        default:
             Debug.Assert(false, "Black has an invalid castling status!");
             break;
    }

    return positionHash;
}
于 2012-10-12T08:54:47.557 回答
2

I would go for at BitArray. It is specialized in bit manipulation but wrappes the operations.

But it sounds like your needs are sufficiently specialized that it could be relevant to implement the collection you self.

于 2012-10-12T07:46:32.070 回答
0

During runtime you can use BitArray class. It's very efficient since you can store custom size bit arrays

Updated

Save BitArray to file:

private void save()
{
    BitArray bits = new BitArray(164, true);
    byte[] bytes = new byte[21]; // 21 * 8 bits = 168 bits (last 4 bits are unused)
    bits.CopyTo(bytes, 0);
    for (int i = 0; i < 21; i++)
    {
        bytes[i] = ToByte(bits, i);
    }

    // now save your byte array to file
}

private byte ToByte(BitArray bits, int start) 
{
    int sum = 0;

    if (bits[start])
        sum += 8;

    if (bits[start + 1])
        sum += 4;

    if (bits[start + 2])
        sum += 2;

    if (bits[start + 3])
        sum += 1;

    return Convert.ToByte(sum);
}
于 2012-10-12T07:47:50.927 回答