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

时间:2008-09-02 16:01:33

标签: chess

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

14 个答案:

答案 0 :(得分:12)

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

您可以使用此表示法开始编程。给出碎片的点值。例如:

**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

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

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

在位板中,您使用8个8位字来表示电路板。这种表示需要每个棋子的棋盘。在一个比特板中你将存储车的位置,而在另一个比特板中你将存储骑士的位置......等等

位板可以极大地提高应用程序的性能,因为使用位板操作这些部件非常简单快捷。

正如你所指出的那样,

  

今天的大多数国际象棋图,特别是   那些在64位CPU上运行的,使用一个   位图方法来表示一个   棋盘和生成动作。 x88是   机器的备用板模型   没有64位CPU。

答案 1 :(得分:12)

对于严肃的国际象棋引擎,使用位板是在内存中表示国际象棋棋盘的有效方式。位板比任何基于阵列的表示都要快,特别是在64位架构中,位板可以放在单个CPU寄存器中。

<强>位棋盘

位板的基本思想是以64位表示每个棋子类型。在C ++ / C#中它将是ulong/UInt64。因此,您将保留12个UInt64变量来代表您的棋盘:每个棋子类型有两个(一个黑色和一个白色),即典当,车,骑士,主教,女王和国王。 UInt64中的每一位都对应于棋盘上的方块。通常,最低有效位将是a1平方,下一个b1,然后是c1,依此类推行。与棋盘上棋子位置对应的位将设置为1,其他所有位置都设置为0.例如,如果你在a2和h1上有两个白车,那么白车的位置将如下所示:

0000000000000000000000000000000000000000000000000000000110000000

现在举例来说,如果你想在上面的例子中将你的车从a2移动到g2,你需要做的只是对你的位置进行异或:

0000000000000000000000000000000000000000000000000100000100000000

在移动生成方面,Bitboard具有性能优势。还有其他性能优势,弹簧自然地来自位板表示。例如,您可以使用lockless hash tables,这在并行化搜索算法时具有巨大的优势。

进一步阅读

国际象棋引擎开发的最终资源是Chess Programming Wiki。我最近写了this chess engine,用C#实现了位板。一个更好的开源象棋引擎是StockFish,它也在C ++中实现了位板。

答案 2 :(得分:9)

简单的方法是使用8x8整数数组。将0用于空方块并为各个部分指定值:

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的机器的备用板模型。

答案 3 :(得分:4)

当然,有许多不同的方式来代表棋盘,最好的方法取决于对你最重要的东西。

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

如果速度是您的首选,那么您必须为棋盘上的每组棋子使用64位数据类型(例如白棋子,黑棋子,传球棋子)。然后,您可以在生成移动和测试移动合法性时利用本机按位操作。

如果代码的清晰度是优先级,那么请忘记位改组,并像其他人已经建议的那样去寻找抽象的数据类型。请记住,如果你这样走,你可能会很快达到性能上限。

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

答案 4 :(得分:4)

在创建我的国际象棋引擎时,我最初使用[8] [8]方法,但最近我改变了我的代码以使用64项阵列代表国际象棋棋盘。我发现这个实现效率大约提高了1/3,至少在我的情况下如此。

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

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

Row =(byte)(index / 8)

Col =(byte)(索引%8)

虽然我发现在递归搜索过程中你永远不必这样做,这是性能敏感的。

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

Adam Berent

答案 5 :(得分:4)

120字节的数组。

这是一个由哨兵方块包围的8x8棋盘(例如255表示一块棋子不能移动到这个方格)。哨兵的深度为2,因此骑士无法跳过。

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

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

答案 6 :(得分:3)

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

这可能不相关,因为很可能,您的目标硬件上可能已有64位寄存器。

答案 7 :(得分:3)

标准8x8 board的替代方法是10x12邮箱(所谓的,因为,呃,我猜它看起来像邮箱)。这是一个一维数组,在其“边界”周围包含sentinels,以协助移动生成。它看起来像这样:

-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中):

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));
}

当然,在实际实现中,您将实际的棋子对象放在棋盘标签所在的位置。但你要保留负面的(或类似的东西)。这些位置使得移动生成不那么痛苦,因为通过检查特殊值,您可以轻松地判断您何时离开了。

让我们首先看看为骑士制作合法动作(非滑动动作):

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;
}

我们知道有效的骑士移动距离作品的起点是固定的距离,因此我们只需要检查这些位置是否有效(即不是哨兵方格)。

滑动件不太难。让我们来看看主教:

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;
}

以下是一些例子:

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

请注意,slide函数是一般实现。您应该能够非常轻松地模拟其他滑动件的合法移动。

答案 8 :(得分:2)

使用位板将是表示国际象棋棋盘状态的有效方式。基本思想是使用64位位组来表示电路板上的每个方块,其中第一位通常表示A1(左下方),第64位表示H8(对角方形对角线)。每个玩家(黑色,白色)的每种类型的棋子(棋子,王等)都有自己的位板,所有12个棋盘都构成游戏状态。有关更多信息,请查看此维基百科article

答案 9 :(得分:1)

我会使用多维数组,以便数组中的每个元素都是对电路板上正方形的网格引用。

因此

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

然后板[A] [1] 就是板方A1。

实际上,你会使用数字而不是字母来帮助保持你的数学逻辑,以便允许片段变得简单。

答案 10 :(得分:0)

int[8][8]

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

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

答案 11 :(得分:0)

我实际上不会对棋盘进行建模,我只是模拟棋子的位置。 那么你可以为国际象棋棋盘设置界限。

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

答案 12 :(得分:0)

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

我想说这是棋盘代表的最简单方法......但当然这是一种基本方法。

1D棋盘阵列结构是否比2D数组更有效(它需要嵌套的for循环来访问和操作索引)?

也可以使用具有多于64个正方形的1D阵列来表示OffBoard正方形。棋盘[120]; (正确初始化阵列哨兵和棋盘正方形)。

对于这篇文章的完整性,我觉得我必须提到0x88板阵列表示。这是一种非常流行的方式来表示棋盘,这也是角落的正方形。

答案 13 :(得分:-3)

一个数组可能没问题。如果您想要更方便的“遍历”电路板,您可以轻松地构建方法来抽象出数据结构实现的细节。