你会使用哪些数据结构来代表计算机国际象棋程序中的棋盘?
对于一个严肃的国际象棋引擎,使用位棋盘是一种有效的方式来在内存中表示棋盘。位棋盘比任何基于数组的表示方法都要快,特别是在64位体系结构中,一个位棋盘可以适合单个CPU寄存器。
位棋盘
位棋盘的基本思想是用64位来表示每种棋子类型。在C++/C#中,它将是ulong/UInt64
。因此,您将维护12个UInt64
变量来表示您的棋盘:对于每种棋子类型,即兵、车、马、象、后和王,均有两个(一个黑色和一个白色)。 UInt64
中的每个位将对应于棋盘上的一个方格。通常,最低有效位将是a1方格,接下来是b1,然后是c1等等按行优先方式。与棋盘上棋子位置相对应的位将设置为1,所有其他位将设置为0。例如,如果您在a2和h1上有两个白色车,则白色车的位棋盘将如下所示:
0000000000000000000000000000000000000000000000000000000110000000
例如,在上面的例子中,如果您想将您的车从a2移动到g2,则只需将您的位棋盘与以下内容进行异或操作:
0000000000000000000000000000000000000000000000000100000100000000
当涉及到移动生成时,位棋盘具有性能优势。从位棋盘表示自然而然地出现其他性能优势。例如,您可以使用 无锁哈希表,这是并行搜索算法的巨大优势。
进一步阅读
国际象棋引擎开发的终极资源是国际象棋编程Wiki。我最近用C#编写了这个实现了位棋盘的国际象棋引擎。一个更好的开源国际象棋引擎是StockFish,它也在C++中实现了位棋盘。
一开始,使用一个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
最简单的方法是使用一个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
120字节的数组。
这是一个8x8的棋盘,周围被哨兵方块包围(例如255表示该位置无法移动)。哨兵具有两个深度,因此马不能跳过。
向右移动加1。向左移动加-1。上10,下-10,向右上对角线11等。A1方格的索引为21。H1的索引为29。H8的索引为99。
所有设计都是为了简单性。但它永远不会像位棋盘一样快速。
嗯,不确定这是否有帮助,但Deep Blue使用单个6位数字来表示棋盘上的特定位置。与使用64位位板的竞争对手相比,这有助于在芯片上节省占用空间。
由于您的目标硬件可能已经具有64位寄存器,因此这可能与您无关。
标准8x8棋盘的替代方案是10x12邮箱(因为它看起来像一个邮箱)。这是一个一维数组,包括哨兵在其“边界”周围以帮助生成移动。它看起来像这样:
-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
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
函数是一个通用实现。您应该能够相当容易地模拟其他滑动棋子的合法移动。board = arrary(A = array (1,2,3,4,5,5,6,7,8),
B = array (12,3,.... etc...
etc...
)
那么 board[A][1] 就是棋盘上 A1 格。
实际上,为了保持移动棋子的位置逻辑简单,您会使用数字而不是字母。