python编写⼈⼯智能-⽤Python写⼀个中国象棋AI?
/************************************************************** = 民间六⼦棋(六⼦冲)⼈机博弈引擎实现与教程 =** www.leilei.name** by LeiLei 2010.3.2 - 2010.3.5*** 本教程主要讲解六⼦冲棋的博弈引擎实现,不讲解界⾯实现部分。* 本教程共分四节讲解:** 第⼀节:局⾯表⽰ -- 构成可下棋的基本元素* 第⼆节:⾛法⽣成 -- 实现下棋的规则* 第三节:局⾯评估 -- 量化⼀个局⾯的双⽅优劣势* 第四节:局⾯搜索 -- 让电脑具备思考的能⼒** 本教程主要以便于理解为⽬标,不追求代码技巧,希望对写代码实践* 较少的你,会有所帮助。*/
#include
/************************************************************** = 第⼀节 局⾯表⽰ =** 1.1 棋⼦表⽰** 棋⼦可以随便⽤个数字表⽰,可以把它定义为常量,* 但是有时候为了⽅便运算和转换,也应该精⼼挑选⽤哪些数字表⽰棋⼦。* 这⾥演⽰就随便选两个数字。** (1)需要定义⽤来表⽰棋⼦的常量** 如下所⽰:*/
#define STONE 3//定义常量 ⽯头(⽩⾊棋⼦)#define LEAF 7// 树叶(⿊⾊棋⼦)#define WHITE 0// ⽩⽅⽤0表⽰#define BLACK
凄美的句子1// ⿊⽅⽤1表⽰#define NOPIECE 0// ⽆棋⼦⽤0
/
** 1.2 棋盘表⽰** 我们可以⽤数组来表⽰棋盘* ⽤4*4的⼆维数组就可以表⽰这个游戏的棋盘结构和棋⼦在棋盘上的分布了。如下(简单吧):* int board[4][4] = { // 对应坐标:* 3, 3, 3, 3, // (0,0), (1,0), (2,0), (3,0),* 3, 0, 0, 3, // (0,1), (1,1), (2,1), (3,1),* 7, 0, 0, 7, // (0,2), (1,2), (2,2), (3,2),* 7, 7, 7, 7 // (0,3), (1,3), (2,3), (3,3),* };** 数组下标构成的坐标表⽰棋⼦在棋盘上的位置。* 可以⽤0表⽰棋盘上⽆棋⼦。* 我们可以⽤4*4的数组表⽰棋盘,但是为了运算⽅便,这⾥我们采⽤8*8的⼀维数组来装棋盘,效果更好。* 我们可以把棋盘放在数组的中央。* 然后我们⽤⼀个8*8的inBoard数组来标识棋盘在数组中的位置。* 棋⼦在棋盘上的位置,我们直接⽤数组下标表⽰。** 所以,* (1)我们需要⼀个⽤来表⽰棋盘的数组* (2)我们⽤⼀个数组来标识棋盘在数组中的位置(⽤来判断棋⼦是否在棋盘上)* (3)写⼏个函数来转换坐标。** 如下所⽰:*/
//棋盘数组(带开局棋⼦位置)int board[64] = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 7, 7, 7, 7, 0, 0,
0, 0, 7, 0, 0, 7, 0, 0,
0, 0, 3, 0, 0, 3, 0, 0,
0, 0, 3, 3, 3, 3, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
//⽤来标记棋⼦是否在棋盘上的数组static const int inBoard[64] = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 1, 1, 1, 1, 0, 0,
河南风味小吃0, 0, 1, 1, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0
};
//根据棋盘的特征,我们写三个可以转换和取得棋盘坐标的函数//
//由棋盘数组下标获得棋⼦X坐标int getXFromLocation(int location){
return (location & 7) - 2;
gta5游民星空}
//由棋盘数组下标获得棋⼦Y坐标int getYFromLocation(int location){
return (location >> 3) - 2;
}
/
/由棋⼦X坐标,Y坐标获得棋盘数组下标int getLocationFromXY(int x, int y){
return (x + 2) + (y + 2 << 3);
}
/** 1.3 当前⾛棋⽅表⽰** (1)我们需要⼀个变量来表⽰当前该哪⽅⾛棋* (2)还需要⼀个⽤来改变⾛其⽅的函数changePlayer()** 如下所⽰:*/
int currentPlayer = WHITE; //初始化为⽩⽅⾛棋,BLACK表⽰⿊⽅⾛棋
void changePlayer(){
currentPlayer = 1 - currentPlayer; //改变⾛棋⽅,不是0 就是1}
/** 1.4 在棋盘上放棋⼦和拿⾛棋⼦** 有了棋盘,我们就可以在棋盘上放棋⼦了** (1)所以我们还需要⼏个函数⽤来在棋盘上放棋⼦和拿⾛棋⼦。** 如下所⽰:*/
//在棋盘上放⼀枚棋⼦的函数void addPiece(int location){ //根据位置和当前⾛棋⽅添加棋⼦ int piece;
piece = currentPlayer * 4 + 3; //由当前⾛棋⽅计算当前⾛棋⽅棋⼦
if(inBoard[location]){
board[location] = piece;
}
}
//在棋盘上拿⾛⼀枚棋⼦的函数void delPiece(int location){ //根据位置删除棋⼦ if(inBoard[location]){
board[location] = NOPIECE; //NOPIECE == 0表⽰⽆棋⼦ }
}
/************************************************************** = 第⼆节 ⾛法⽣成 =** 2.1 ⾛法表⽰** ⾛法表⽰就是⽤⼀个变量来表⽰棋⼦从棋盘上哪⾥⾛到哪⾥。* 我们可以定义⼀个结构体来表⽰⼀个⾛法,也可以⽤⼀串数字表⽰。* 在本程序⾥,我们⽤⼀个int类型来表⽰⼀个⾛法,低位表⽰起点位* 置(的棋盘数组下标),⾼位表⽰⽬的位置(的棋盘数组下标)。** 由此,* (1)我们需要写⼏个函数来处理⾛法的起点和终点。** 如下:*/
//根据⾛法⽣成⾛法起点位置(起点位置的棋盘数组下标)int generateMoveFrom(int move){
return move & 255;
}
//根据⾛法⽣成⾛法⽬的位置(⽬的位置的棋盘数组下标)int generateMoveTo(int move){
return move >> 8;
}
//由起点位置和⽬的位置合成⾛法int composeMove(int locationFrom, int locationTo){
return locationFrom + locationTo * 256;
}
/** 2.2 ⾛法⽣成** ⾛法⽣成就是⽣成⼀个局⾯可以有哪些⾛法,⼀般是⽤⼀个函数来⽣成所有可能的⾛法。* ⽣成的这些⾛法,我们可以保存在⼀个⾛法列表⾥,⽅⾯使⽤。* 我们可以⽤这个⾛法列表来判断⼀步棋是否合法,最重要的是,我们需要⽤这个⾛法列表来搜索所有可能的局⾯。* 六⼦冲的每颗棋⼦都是按上下左右四个⽅向⾛,通过对棋盘数组下标的观察,我们可以⽤原位置的数组下标分别*
加上-8、8、-1、1四个数就分别得到往上、下、左、右四个⽅向⾛⼀步的位置的数组下标。所以我们⽤⼀个数组* 存储这四个数。** (1)定义⼀个常量MAX_GEN_MOVES来表⽰最⼤⽣成的⾛法数* (2)我们需要声明⼀个数组⽤来保存⽣成的⾛法列表* (3)⽤⼀个数组来表⽰棋⼦可⾛的⽅向* (4)还需要写⼀个⼀次性⽣成所有⾛法的函数。*/
#define MAX_GEN_MOVES 32//这⾥顺便定义⼀个常量来表⽰⾛法的最⼤⽣成数
和平发展道路static int theMoves[MAX_GEN_MOVES]; //定义⼀个⾛法数组⽤来保存⽣成的所有⾛法,⼀个局⾯最多⾛法数不会超过
MAX_GEN_MOVES种
static const char movesTable[4] = {-8, 8, -1, 1}; //这个数组⽤来表⽰棋⼦的⾛⼦⽅向
//⾛法⽣成函数,产⽣⼀个局⾯的所有⾛法int generateAllMoves(int *moves){ //传递⼀个⾛法列表数组指针,返回⽣成的所有⾛法 int i, from, to, genCount;
int myPiece, pieceFrom, pieceTo;
//⾛法计数器清零 genCount = 0;
//获取当前⾛棋⽅标记 myPiece = currentPlayer * 4 + 3; //根据当前⾛棋⽅,确定当前⾛棋⽅棋⼦
古代人物//遍历棋盘到当前⾛棋⽅棋⼦ for(from = 0; from < 64; from++){
//取得当前位置的棋⼦ pieceFrom = board[from];
//1.如果到的不是本⽅棋⼦,继续 if(pieceFrom != myPiece){
continue;
新员工怎么培训}
//2.如果到本⽅棋⼦,探测四个⽅向是否可⾛ for(i = 0; i < 4; i++){
to = from + movesTable[i]; //⽬标位置 if(inBoard[to]){ //如果还在棋盘内 pieceTo = board[to]; //获取此位置棋⼦ if(pieceTo == NOPIECE){ //如果此位置⽆棋⼦,此⾛法可⾏ moves[genCount] = composeMove(from, to); //保存⾛法 genCount++; //计数 }
}
}//end for(i)
}//end for(from) return genCount; //返回⽣成的⾛法数}
/** 2.3 ⾛⼀步棋** 要⾛⼀步棋很简单,我们只需要先把要⾛的棋⼦从棋盘的原位置上拿* ⾛,然后再把这颗棋⼦放在棋盘上的⽬标位置上。我们可以利⽤前⾯* 的addPiece()和delPiece()函数来写⼀个⾛棋函数。** 在⾛⼀步棋之前,我们最好先判断这步棋是否符合⾛棋规则,以免引* 起以后的混乱,我们可以利⽤⾛法⽣成函数来判断⼀步棋是否合法。* 有的时候为了提⾼效率也可以单独写⼀个函数判断⾛法是否合法。* 在这⾥为了简单就⽤直接利⽤⾛法⽣成函数,⽣成所有⾛法,如果⾛* 法在⾛法列表⾥说明⾛法合法。** ⾛⼀步棋后往往会引发吃⼦,六⼦冲的吃⼦需要我们进⾏检查,六⼦* 冲的吃⼦不光与所⾛棋⼦有关,还与其他棋⼦的合作有关。** 在下⼀节的局⾯搜索中,我们还会⽤到⾛棋函数,并且在搜索的过程* 中,我们会在棋盘上⾛棋,以探测未来棋局的变化和⾛势,为了不破* 坏当前局⾯,每⾛⼀步棋我们就要记录当前的⾛法和所吃的⼦,以便* 还原,所以在吃⼦过程中,我们需要记录吃⼦的位置。** (1)写⼀个能在棋盘上⾛⼀步棋的函数。*/
/**吃⼦参考⽅案 - 检查序列法**//吃⼦检查序列数组static int eatSequence[2][2][4] = {{{7, 3, 3, 0},{0, 7, 3, 3}},{{3, 7, 7, 0},{0, 3, 7, 7}}};//检查吃⼦的函数,⽆返回吃⼦位置部分int checkEat(int to){int i, j, k, step, eat, check; //检查吃⼦专⽤int pieceSequence[4];
//pieceSequence⽤来保存棋⼦序列(检查吃⼦时⽤)// 检查吃⼦,从上往下、从下往上、从左往右、从右往左,沿四个⽅向检查吃⼦情
况// ⽐如:从左往右检查,只需检查是否呈“●○○_”或“_●○○”序列(假如⽩⽅为当前⽅)for(i = 0; i < 4; i++){//1. 取得焦点位置(焦点就是当前检查点)check = to; //check变量这时作为焦点位置//2. 取得步长step = movesTable[i];//3. 把焦点位置往反⽅向移动到棋盘边上while(inBoard[check]){check -= step; //往反⽅向移动焦点}//4. 往正⽅向取得棋⼦序列,保存在数组pieceSequence中for(j = 0; j < 4; j++){form += step; //往正⽅向移动焦点if(inBoard[check]){pieceSequence[j] = board[check];}}//5. 把焦点位置再次往反⽅向移动到棋盘边上,以便由焦点位置根据吃⼦序列数组取得吃⼦位置while(inBoard[check]){check -= step; //往反⽅向移动焦点}//6.检查棋⼦序列是否与两个吃⼦序列匹配,若匹配则吃⼦for(k = 0; k < 2; k++){//6.1 初始化为有⼦可吃eat = 1;//6.2 检查序列是否完全匹配for(j = 0; j < 4; j++){if(pieceSequence[j] != eatSequence[currentPlayer][k][j]){eat = 0;}}//6.3 检查到序列完全匹配则,按位置吃⼦if(eat == 1){delPiece(check + step * (k + 1)); //由焦点取得吃⼦位置}}}}*/
//能根据⾛法⾛⼀步棋的函数int makeOneMove(int move, int *eatTable){ //传递⼀个⽤来保存吃⼦位置的数组 int i, genCount, isLegalMove, from, to;
int step, focus, myPiece, oppPiece, eatCount; //吃⼦专⽤
isLegalMove = 1; //初始化⾛法为不合法 genCount = generateAllMoves(theMoves); //⽣成所有⾛法 //在所有⾛法中查当前⾛法是否存在 for(i = 0; i < genCount; i++){
/
/如果到⼀个⾛法等于当前⾛法 if(theMoves[i] == move){
isLegalMove = 0; //所有⾛法中有此⾛法,说明⾛法合法 break;
}
}
//1.⾸先判断⾛法是否合法 if(isLegalMove == 1){
return 0; //返回⾛棋失败 }
//2.分解⾛法,取得起点位置和⽬的位置 from = generateMoveFrom(move);
to = generateMoveTo(move);
//3.⾛⼀步棋 delPiece(from);
addPiece(to);
//4.检查吃⼦ eatCount = 0; //⼀步有可能同时吃掉两⼦ myPiece = currentPlayer * 4 + 3; //由当前⾛棋⽅计算当前⾛棋⽅棋⼦oppPiece = (1 - currentPlayer) * 4 + 3; //由当前⾛棋⽅计算对⽅棋⼦
//检查吃⼦,从上往下、从下往上、从左往右、从右往左,沿四个⽅向检查吃⼦情况 for(i = 0; i < 4; i++){
step = movesTable[i]; //取得步长
//检查是否为第⼀种吃⼦情况“○○●_” 所⾛棋⼦为序列中的第⼀个⽩⼦ focus = to + step; //把焦点移到棋⼦前⽅第⼀个位置
if(inBoard[focus] && board[focus] == myPiece){
focus += step; //把焦点移到棋⼦前⽅第⼆个位置 if(inBoard[focus] && board[focus] == oppPiece){
focus += step; //把焦点移到棋⼦前⽅第三个位置 if(inBoard[focus] && board[focus] == NOPIECE){
focus -= step; //把焦点移到吃⼦位置 delPiece(focus); //吃⼦ eatTable[eatCount] = focus; //记录吃⼦的位置 eatCount++; //计数}
}
}
/
/检查是否为第⼆种吃⼦情况“○○●_” 所⾛棋⼦为序列中的第⼆个⽩⼦ focus = to - step; //把焦点移到棋⼦后⽅位置
if(inBoard[focus] && board[focus] == myPiece){
focus = to + step; //把焦点移到棋⼦前⽅第⼀个位置 if(inBoard[focus] && board[focus] == oppPiece){
focus += step; //把焦点移到棋⼦前⽅第⼆个位置 if(inBoard[focus] && board[focus] == NOPIECE){
focus -= step; //把焦点移到吃⼦位置 delPiece(focus); //吃⼦ eatTable[eatCount] = focus; //记录吃⼦的位置 eatCount++; //计数}
}
}
//检查是否为第三种吃⼦情况“●○○_” 所⾛棋⼦为序列中的第⼆个⽩⼦ focus = to - step; //把焦点移到棋⼦后⽅位置
if(inBoard[focus] && board[focus] == oppPiece){
focus = to + step; //把焦点移到棋⼦前⽅第⼀个位置 if(inBoard[focus] && board[focus] == myPiece){
focus += step; //把焦点移到棋⼦前⽅第⼆个位置 if(inBoard[focus] && board[focus] == NOPIECE){
focus = to - step; //把焦点移到吃⼦位置 delPiece(focus); //吃⼦ eatTable[eatCount] = focus; //记录吃⼦的位置 eatCount++; //计数 }
}
}
//检查是否为第四种吃⼦情况“_○○●” 所⾛棋⼦为序列中的第⼆个⽩⼦ focus = to - step; //把焦点移到棋⼦后⽅位置
if(inBoard[focus] && board[focus] == NOPIECE){
focus = to + step; //把焦点移到棋⼦前⽅第⼀个位置 if(inBoard[focus] && board[focus] == myPiece){
focus += step; //把焦点移到棋⼦前⽅第⼆个位置 if(inBoard[focus] && board[focus] == oppPiece){
focus = focus; //焦点已经在吃⼦位置 delPiece(focus); //吃⼦ eatTable[eatCount] = focus; //记录吃⼦
的位置 eatCount++; //计数}
}
}
}
//5.交换⾛棋⽅ changePlayer();
return 1 + eatCount; //返回吃⼦的个数加1,我们调⽤此函数时,只需要⽤返回值减1就得到吃⼦个数}
/************************************************************** = 第三节 局⾯评估 =** 3.1 判断胜负** 有了能⽣成局⾯上所有能⾛的棋的函数,程序就能知道哪些棋可以⾛,哪些棋不能⾛。* 现在为了完善规则,我们还差⼀个判断胜负的函数了。已知⼀个局⾯,是否已经分出* 胜负呢?看下⾯函数。** (1)需要写⼀个判断胜负的函数。*/
//判断是否分出胜负的函数int isCurrentPlayerDie(){
//如果⽣成零个⾛法,则已被困死,将返回1 if(generateAllMoves(theMoves)){
return 0;
}
return 1;
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论