将计算机编程为下棋时,如何为棋盘建模?

将计算机编程为下棋时,如何为棋盘建模?

How do I model a chessboard when programming a computer to play chess?

您将使用什么数据结构表示计算机国际象棋程序的国际象棋棋盘?


对于严肃的国际象棋引擎,使用位板是在内存中表示国际象棋棋盘的一种有效方法。位板比任何基于数组的表示形式都快,特别是在64位体系结构中,位板可以容纳在单个CPU寄存器中。

位板

位板的基本思想是用64位表示每种棋子类型。在C / C#中,它将为ulong/UInt64。因此,您将维护12个UInt64变量来代表您的国际象棋棋盘:每种棋子类型分别为两个(一个黑色和一个白色),分别为典当,车子,骑士,主教,皇后和国王。 UInt64中的每一位都将对应于棋chess上的一个正方形。通常,最低有效位将是a1平方,下一个b1,然后是c1,依此类推,以行为主。与棋子在棋盘上的位置相对应的位将设置为1,所有其他位都将设置为0。例如,如果在a2和h1上有两个白鸦,则白鸦的位板上将如下所示:

1
0000000000000000000000000000000000000000000000000000000110000000

现在,例如,如果您想在上面的示例中将车从a2移到g2,您要做的就是对位板进行XOR:

1
0000000000000000000000000000000000000000000000000100000100000000

位板在移动生成方面具有性能优势。从位板表示中自然也会产生其他性能优势。例如,您可以使用无锁哈希表,这在并行化搜索算法时具有巨大优势。

进一步阅读

国际象棋引擎开发的最终资源是国际象棋编程维基。我最近写了这个国际象棋引擎,该引擎在C#中实现了位板。更好的开源国际象棋引擎是StockFish,它也实现了位板,但使用C语言。


最初,使用8 * 8整数数组表示国际象棋棋盘。

您可以使用此符号开始编程。给出零件的点值。例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
**White**
9 = white queen
5 = white rook
3 = bishop
3 = knight
1 = pawn

**black**
-9 = white queen
-5 = white rook
-3 = bishop
-3 = knight
-1 = pawn

White King: very large positive number
Black King: very large negative number

等(请注意,以上给出的要点是每个棋子的交易能力的近似值)

在开发了应用程序的基本Backbone 并清楚地了解所使用算法的工作原理之后,请尝试通过使用位板来提高性能。

在位板中,使用八个8位字表示板。此表示法每个棋子都需要一块棋盘。在一个位板上,您将存储车的位置,而在另一个位中,您将存储骑士的位置...等等

位板可以极大地提高应用程序的性能,因为使用位板操纵零件非常容易和快速。

正如您指出的那样,

Most chessprograms today, especially
those that run on a 64 bit CPU, use a
bitmapped approach to represent a
chessboard and generate moves. x88 is
an alternate board model for machines
without 64 bit CPUs.


简单的方法是使用8x8整数数组。使用0表示空白方块,并为片段分配值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
1 white pawns
2 white knights
3 white bishops
4 white rooks
5 white queens
6 white king

Black pieces use negative values
-1 black pawn
-2 black knight
etc

8| -4 -2 -3 -5 -6 -3 -2 -4
7| -1 -1 -1 -1 -1 -1 -1 -1
6|  0  0  0  0  0  0  0  0
5|  0  0  0  0  0  0  0  0
4|  0  0  0  0  0  0  0  0
3|  0  0  0  0  0  0  0  0
2|  1  1  1  1  1  1  1  1
1|  4  2  3  5  6  3  2  4
  -------------------------
   1  2  3  4  5  6  7  8

可以通过使用数组索引来计算零件移动。例如,白色棋子通过将行索引增加1来移动,如果它是棋子的第一步,则将行索引增加2。因此,[2] [1]上的白色棋子可以移动到[3] [1]或[4] [1]。

但是,这种具有棋盘格的简单8x8数组表示法存在一些问题。最值得注意的是,当您移动"滑动"的棋子(如菜鸟,主教和皇后)时,您需要不断检查索引,以查看棋子是否已移出棋盘。

当今大多数国际象棋程序,特别是那些在64位CPU上运行的象棋程序,都使用位图表示法来表示棋盘并生成动作。 x88是用于没有64位CPU的计算机的备用主板型号。


120个字节的数组。

这是一个8x8的棋盘,周围有前哨正方形(例如255,表示棋子无法移动到该正方形)。前哨的深度为2,因此骑士无法跳过。

向右移动添加1.向左移动添加-1。上10,下-10,上和右对角线11等。正方形A1是索引21。H1是索引29。H8是索引99。

所有设计都是为了简化。但是它永远不会像位板那么快。


创建国际象棋引擎时,我最初采用[8] [8]方法,但是最近我更改了代码,以使用64个项目的数组表示国际象棋棋盘。我发现,至少就我而言,此实现的效率提高了大约1/3。

在进行[8] [8]方法时,您要考虑的一件事是描述职位。例如,如果您想描述棋子的有效举动,则需要2个字节。当使用[64]项目数组时,您可以用一个字节来完成。

要从[64]板上的位置转换为[8] [8]板上的位置,您只需使用以下计算即可:

行=(字节)(索引/ 8)

Col =(字节)(索引%8)

尽管我发现在性能敏感的递归移动搜索过程中,您不必这样做。

有关构建国际象棋引擎的更多信息,请随时访问我的博客,从头开始描述该过程:www.chessbin.com

亚当·贝伦特


好吧,不确定是否有帮助,但是Deep Blue使用单个6位数字表示板上的特定位置。与使用64位位板的竞争对手相比,这有助于节省芯片上的占用空间。

这可能无关紧要,因为很有可能您的目标硬件上已经有64位寄存器。


当然,代表棋盘的方式有很多种,最好的方式取决于对您来说最重要的东西。

您的两个主要选择是速度和代码清晰度。

如果速度是您的首要任务,那么您必须在板上的每组棋子中使用64位数据类型(例如,白色棋子,黑色皇后棋子,传人棋子)。然后,您可以在生成移动并测试移动合法性时利用本机按位操作。

如果代码的清晰性是优先考虑的事情,那么请忽略位改组,而像其他人已经建议的那样,选择很好的抽象数据类型。请记住,如果采用这种方式,您可能会更快达到性能极限。

首先,请查看Crafty(C)和SharpChess(C#)的代码。


10x12邮箱是标准8x8电路板的替代品(之所以这么称呼,是因为嗯,我想它看起来像一个邮箱)。这是一维数组,在其"边界"周围包括哨兵,以协助进行移动生成。看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
12
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1,"a8","b8","c8","d8","e8","f8","g8","h8", -1,
-1,"a7","b7","c7","d7","e7","f7","g7","h7", -1,
-1,"a6","b6","c6","d6","e6","f6","g6","h6", -1,
-1,"a5","b5","c5","d5","e5","f5","g5","h5", -1,
-1,"a4","b4","c4","d4","e4","f4","g4","h4", -1,
-1,"a3","b3","c3","d3","e3","f3","g3","h3", -1,
-1,"a2","b2","c2","d2","e2","f2","g2","h2", -1,
-1,"a1","b1","c1","d1","e1","f1","g1","h1", -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1

您可以这样生成数组(在JavaScript中):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function generateEmptyBoard() {
    var row = [];
    for(var i = 0; i < 120; i++) {
        row.push((i < 20 || i > 100 || !(i % 10) || i % 10 == 9)
            ? -1
            : i2an(i));
    }
    return row;
}

// converts an index in the mailbox into its corresponding value in algebraic notation
function i2an(i) {
    return"abcdefgh"[(i % 10) - 1] + (10 - Math.floor(i / 10));
}

当然,在实际的实现中,您会将实际的块对象放置在板标签所在的位置。但是您要保留负面的(或类似的东西)。这些位置使生成移动的痛苦减轻了很多,因为您可以通过检查该特殊值来轻松判断何时出局。

让我们首先看看为骑士(防滑的棋子)生成合法动作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function knightMoves(square, board) {
    var i = an2i(square);
    var moves = [];
    [8, 12, 19, 21].forEach(function(offset) {
        [i + offset, i - offset].forEach(function(pos) {
            // make sure we're on the board
            if (board[pos] != -1) {
                // in a real implementation you'd also check whether
                // the squares you encounter are occupied
                moves.push(board[pos]);
            }
        });
    });
    return moves;
}

// converts a position in algebraic notation into its location in the mailbox
function an2i(square) {
    return"abcdefgh".indexOf(square[0]) + 1 + (10 - square[1]) * 10;
}

我们知道有效的Knight移动距离棋子的起点有固定的距离,因此我们只需要检查这些位置是否有效(即不是前哨正方形)即可。

滑动件并不难。让我们看一下主教:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function bishopMoves(square, board) {
    var oSlide = function(direction) {
        return slide(square, direction, board);
    }
    return [].concat(oSlide(11), oSlide(-11), oSlide(9), oSlide(-9));
}

function slide(square, direction, board) {
    var moves = [];
    for(var pos = direction + an2i(square); board[pos] != -1; pos += direction) {
        // in a real implementation you'd also check whether
        // the squares you encounter are occupied
        moves.push(board[pos]);
    }
    return moves;
}

以下是一些示例:

1
2
knightMoves("h1", generateEmptyBoard()) => ["f2","g3"]
bishopMoves("a4", generateEmptyBoard()) => ["b3","c2","d1","b5","c6","d7","e8"]

请注意,slide函数是常规实现。您应该能够相当容易地为其他滑动件的合法移动建模。


使用位板是表示棋盘状态的有效方法。基本思想是,您使用64位位集表示板上的每个正方形,其中第一个位通常代表A1(左下角的正方形),而第64个位代表H8(对角相对的正方形)。每个玩家(黑,白)的每种棋子(棋子,国王等)都有自己的位板,所有这12个板都构成游戏状态。有关更多信息,请查看此Wikipedia文章。


我将使用多维数组,以便数组中的每个元素都是对板上正方形的网格参考。

因此

1
2
3
4
board = arrary(A = array (1,2,3,4,5,5,6,7,8),
               B = array (12,3,.... etc...
               etc...          
               )

然后板子[A] [1]是板子正方形A1。

实际上,您将使用数字而不是字母来帮助保持您的数学逻辑,以允许将片段移动到简单的地方。


我知道这是一个非常古老的帖子,在使用国际象棋编程时偶然发现了几次,但是我觉得我必须提到用一维数组(例如1D数组)对国际象棋棋盘建模是完全可行的。棋盘[64];

我会说这是最简单的棋盘表示方法……但是,这当然是一种基本方法。

一维棋盘阵列结构是否比二维数组(需要嵌套的for循环访问和操作索引)效率更高?

还可以使用具有超过64个正方形的1D数组来表示OffBoard正方形,例如棋盘[120]; (正确初始化了数组哨兵和棋盘游戏方块)。

最后,为了完整起见,我觉得我必须提到0x88电路板阵列表示形式。这是表示棋盘的一种很流行的方式,该棋盘也占了外侧的正方形。


我实际上不会为国际象棋棋盘建模,我只是为棋子的位置建模。
然后,您可以为国际象棋棋盘划界。

1
2
Piece.x= x position of piece
Piece.y= y position of piece

1
2
3
4
5
6
7
8
9
int[8][8]

0=no piece
1=king
2=queen
3=rook
4=knight
5=bishop
6=pawn

对白色使用正整数,对黑色使用负整数


数组可能很好。如果您想使用更方便的"遍历"电路板的方法,则可以轻松地构建一些方法来抽象出数据结构实现的细节。


推荐阅读