3D 国际象棋(opengl)与人工智能






4.64/5 (12投票s)
一款可以由计算机和人类对弈的 3D 国际象棋游戏

引言
国际象棋是一种两人对弈的策略游戏,玩家轮流尝试将对方的王将死。每回合,玩家必须移动自己的一个棋子。每个棋子都有特定的移动方式,在64个黑白相间的棋盘格上移动。有关游戏规则的更多信息,请访问维基百科上的国际象棋条目。
背景
这个国际象棋游戏主要分为5个阶段开发
- 棋盘表示:由于编译器是32位,使用两个无符号
int
数字来表示棋盘。通过重载所有必需的运算符,创建了一个具有64位功能的类。 - 移动生成:所有不同棋子的可能移动都预先计算并存储在位棋盘中。由于只需要移位操作,计算变得容易。
- 评估函数:为棋子分配了点数,并为保护和攻击对方棋子分配了点数。为将死和将军国王分配了点数。
- 深度搜索:添加评估函数还不够,需要深度思考,实现了Minmax树。
- 残局:阻止导致王被将死的可能移动,并实现了将死条件,以便游戏结束。
Using the Code
该类定义了一个位棋盘,包含两个U32数字。这构成了一个64位的棋盘。所有位运算符都已重载,以便与两个32位数字一起使用。
class U64
{
public:
U32 l, h;
U64()
{
l = 0x0;
h = 0x0;
}
U64 operator |(U64);
U64 operator &(U64);
U64 operator ^(U64);
U64 operator ~(void);
U64 operator <<(int a);
U64 operator >>(int b);
void operator =(int sq);
void operator |=(int sq);
void display();
int getAndClearLSB();
bool check(){
if( !( l | h) )
return false;
else
return true;
}
};
定义各种棋子位棋盘的类还定义了预先计算和存储的各种棋盘,例如可能的移动。构造函数声明了各种棋子的位棋盘的初始状态。
class CBoard {
public:
int side;
U64 Pieces[2][7];
U64 Knightsmove[64];
U64 Kingmove[64];
U64 right_board[64];
U64 left_board[64];
U64 up_board[64];
U64 down_board[64];
U64 _45deg_board[64];
U64 _225deg_board[64];
U64 _135deg_board[64];
U64 _315deg_board[64];
U64 Pawnsmove[64][2];
U64 Pawnsdoublemove[64][2];
U64 Pawnsattackmove[64][2];
U64 fullboard[2];
U64 occupiedboard[2];
U64 unoccupiedboard[2];
U64 enemyboard[2];
U64 notfriendlyboard[2];
U64 friendlyboard[2];
U64 attackboard[2];
U64 doubledpawn[2];
U64 isolatedpawn[2];
U64 backwardpawn[2];
U64 kngchk[7];
void validmove();
CBoard();
}bitboard;
我想演示使用位移操作生成车可能的移动方式。首先,预先计算以下位棋盘
right_board[64]
- 对于每个格子,将格子右侧的所有格子设置为1的位棋盘left_board[64]
- 对于每个格子,将格子左侧的所有格子设置为1的位棋盘up_board[64]
- 对于每个格子,将格子上方的所有格子设置为1的位棋盘down_board[64]
- 对于每个格子,将格子下方的所有格子设置为1的位棋盘
要计算车可以移动到的右侧格子,我们执行以下操作
//right_moves = right_board[sq] AND occupiedboard
我们这样做是因为第一个棋子会阻止车向右移动。由于第一个棋子会阻止车,我们需要填充棋子右侧的所有格子(<< 1
表示左移一位,< 2
表示左移2位,等等)
right_moves = (right_moves<<1) OR (right_moves<<2) OR (right_moves<<3) OR
(right_moves<<4) OR (right_moves<<5) OR (right_moves<<6)
为了消除溢出到下一行的位,我们使用AND
运算符将结果与right_board
相与。
right_moves = right_moves AND right_board
这是车不能移动到的所有右侧格子。要获得车可以移动到的格子,我们对棋盘与right_board
进行异或运算OR
。
right_moves = right_moves XOR right_board
您会注意到,这些是车可以移动到的右侧的所有格子。但是,如果F3上的兵是一个黑色的兵呢?那么我们不能吃掉它,因此我们需要进行最后一步操作:right_moves = right moves AND enemy_and_empty_squares
。
inline U64 genRookAttackBoard(int sq)
{
bool side=bitboard.side;
U64 leftboard = (bitboard.occupiedboard[side] & bitboard.left_board[sq]);
leftboard = (leftboard>>1)|(leftboard>>2)|(leftboard>>3)|
(leftboard>>4)|(leftboard>>5)|(leftboard>>6);
leftboard = (leftboard & bitboard.left_board[sq])^bitboard.left_board[sq];
U64 rightboard = (bitboard.occupiedboard[side] & bitboard.right_board[sq]);
rightboard = (rightboard<<1)|(rightboard<<2)|(rightboard<<3)|
(rightboard<<4)|(rightboard<<5)|(rightboard<<6);
rightboard = (rightboard & bitboard.right_board[sq]) ^ bitboard.right_board[sq];
U64 upboard = (bitboard.occupiedboard[side] & bitboard.up_board[sq]);
upboard = (upboard<<8)|(upboard<<16)|(upboard<<24)|
(upboard<<32)|(upboard<<40)|(upboard<<48);
upboard = (upboard & bitboard.up_board[sq]) ^ bitboard.up_board[sq];
U64 downboard = (bitboard.occupiedboard[side] & bitboard.down_board[sq]);
downboard = (downboard>>8)|(downboard>>16)|(downboard>>24)|
(downboard>>32)|(downboard>>40)|(downboard>>48);
downboard = (downboard & bitboard.down_board[sq]) ^ bitboard.down_board[sq];
return (leftboard | rightboard | upboard | downboard) &
bitboard.notfriendlyboard[side];
}