C++课程设计报告.docx
《C++课程设计报告.docx》由会员分享,可在线阅读,更多相关《C++课程设计报告.docx(25页珍藏版)》请在冰豆网上搜索。
C++课程设计报告
一、题目:
利用C++实现中国象棋程序
问题(或功能)描述:
1.走棋和吃子
对局时,由执红棋的一方先走,双方轮流各走一着,直至分出胜、负、和,对局即终了。
轮到走棋的一方,将某个棋子从一个交叉点走到另一个交叉点,或者吃掉对方的棋子而占领其交叉点,都算走一着。
双方各走一着,称为一个回合。
2、各种棋子的走法
•帅(将):
帅和将是棋中的首脑,是双方竭力争夺的目标。
它只能在"九宫"之内活动,可上可下,可左可右,每次走动只能按竖线或横线走动一格。
帅与将不能在同一直线上直接对面,否则走方判负。
•仕(士):
仕(士)是帅(将)的贴身保镖,它也只能在九宫内走动。
它的行棋路径只能是九宫内的斜线。
•相(象):
相(象)的主要作用是防守,保护自己的帅(将)。
它的走法是每次循对角线走两格,俗称"象走田"。
相(象)的活动范围限于"河界"以内的本方阵地,不能过河,且如果它走的"田"字中央有一个棋子,就不能走,俗称"塞象眼"。
•车:
车在象棋中威力最大,无论横线、竖线均可行走,只要无子阻拦,步数不受限制。
因此,一车可以控制十七个点,故有"一车十子寒"之称。
•炮:
炮在不吃子的时候,走动与车完全相同。
炮与被吃子之间必须隔一个棋子,进行跳吃,俗称"架炮"或"炮打隔子"。
•马:
马走动的方法是一直一斜,即先横着或直着走一格,然后再斜着走一个对角线,俗称"马走日"。
马一次可走的选择点可以达到四周的八个点,故有"八面威风"之说。
如果在要去的方向有别的棋子挡住,马就无法走过去,俗称"蹩马腿"。
•兵(卒):
兵(卒)在未过河前,只能向前一步步走,过河以后,除不能后退外,允许左右移动,但也只能一次一步。
3、吃子:
任何棋子走动时,如果目标位置上有对方的棋子,就可以把对方的棋子拿出棋盘,再换上自己的棋子(即"吃子")。
4问题的描述:
根据中国象棋的棋盘与规则,分别输入甲方与乙方的对弈要求(如车9进2,马5退4),实现人人网络方式的对弈(对不合适的操作可以识别)。
象棋是一种双方对阵的竞技项目。
棋子共有三十二个,分为红黑两组,各有十六个,由对弈的双方各执一组。
兵种是一样的,分为七种:
红方:
红方有帅一个,仕、相、车、马、炮各两个,兵五个。
黑方:
黑方有将一个,士、象、车、马、炮各两个,卒五个。
其中帅与将;仕与士;相与象;兵与卒的作用完全相同,仅仅是为了区别红棋和黑棋而已。
棋子活动的场所,叫作"棋盘"。
在长方形的平面上,绘有九条平行的竖线和十条平行的横线相交组成,共有九十个交叉点,棋子就摆在交叉点上。
中间部分,也就是棋盘的第五,第六两横线之间末画竖线的空白地带称为“河界”。
两端的中间,也就是两端第四条到第六条竖线之间的正方形部位,以斜交叉线构成“米”字方格的地方,叫作“九宫”(它恰好有九个交叉点)。
整个棋盘以“河界”分为相等的两部分。
为了比赛记录和学习棋谱方便起见,现行规则规定:
按九条竖线从右至左用中文数字一-九来表示红方的每条竖线,用阿拉伯数字‘1’~‘9’来表示黑方的每条竖线。
对弈开始之前,红黑双方应该把棋子摆放在规定的位置。
任何棋子每走一步,进就写“进”,退就写“退”,如果像车一样横着走,就写“平”。
任何棋子在走动时,如果乙方棋子可以到达的位置有对方的棋子,就可以把对方棋子拿出棋盘(称为吃子)而换上自己的棋子。
只有炮的"吃子"方式与它的走法不同:
它和对方棋子之间必须隔一个子(无论是自己的还是对方的),具备此条件才能"
吃掉"人家。
一定要注意,中隔一个棋子,这个棋子俗称“炮架子”。
帅和将被吃或不能动弹即输棋。
2、编程要点(或基本思路、算法分析与说明):
首先将棋盘的每一格坐标化,横坐标从01开始到09。
纵坐标从01开始到10,初始横坐标01行上摆放红子棋子,01放车、02放马、03放象、04放士、05放帅,06、07、08、09对称放士、象、马、车。
横坐标03行02、08列放炮,横坐标04行01、03、05、07、09列放兵。
绿子旗子和红子棋子对称放在对面。
在这个初始化的坐标上每一个棋子都对应的有一个点,并且对应一个数,红子棋子从车(i=0)开始一直到帅,for(i=0;i<5;i++);x[i][1]=x[10-i][1]=i+10;既从车到帅对应的数为:
11,12,13,14,15;兵为17,炮为16;绿子棋子:
x[i][10]=x[10-i][10]=i+20;既从车到帅对应的数为:
21,22,23,24,25;卒为27,炮为26;具体如下图:
1棋盘棋子表示
使用位棋盘技术,我们的棋盘是一个大小为16x16的二维数组,即程序里的ucpcSquares[256]。
象棋只有10x9的大小,用16x16的二维数组似乎有些浪费,然而这给程序带来了很多好处。
(1),免去了烦琐的边界判断(只要预先给10x9外的其他棋格赋值一个非空的虚拟棋子即可)。
(2),我们可以根据棋子的位置数字potion(0-255)快速得到行列信息,行号=potion>>4;列号=(potion<<4)>>4
2着法生成
由于采取了16x16的棋盘数组来表示棋子位置,所以一步着法可以表示成:
sGX,sGY,eGX,eGY4个[0到15]之间的数字,合并成一个则为0-65535之间的整数。
(1),我们将在图5.2这个棋盘上演绎马是如何走棋的
首先,我们预置一个常量数组ccInBoard[256],表示哪些格子在棋盘外(紫色格子,填0),哪些格子在棋盘内(浅色格子,填1),所以就没有必要使用x>=X_LEFT&&x<=X_RIGHT&&y>=Y_TOP&&y<=Y_BOTTOM之类的边界判断语句了,取而代之的是ccInBoard[sq]!
=0。
其次,一维数组的好就是上下左右关系非常简明——上面一格是sq-16,下面一格是sq+16,左面一格是sq-1,右面一格是sq+1。
马可以跳的点只有8个,终点相对起点的偏移值是固定的:
constcharccKnightDelta[4][2]={{-33,-31},{-18,14},{-14,18},{31,33}};
而对应的马腿的偏移值是:
constcharccKingDelta[4]={-16,-1,1,16};
这个数组之所以命名为ccKingDelta,是因为它也是帅(将)的偏移值。
这样,找到一个马的所有走法就容易很多了。
首先判断某个方向上的马腿是否有子,然后判断该方向上的两个走法是否能走:
(2),界面模块用户着法合法性判断
如果让用引擎的着法生成器生成所有着法看是否包含用户的着法则太浪费时间,“出棋制胜”用了一个小技巧,下面看最负责的马象着法合法性判断,(sGX,sGY)是着法起始坐标,(eGX,eGY)是着法结束坐标。
(3),判断将军
中国象棋的胜负标准就是帅(将)有没有被将死或困毙,我们的做法很简单——生成所有走法,如果走任意一步都会被将军,那么该局面就是将死或困毙的局面,棋局到此结束。
那么如何来判断是否被将军呢?
我们有两种做法:
A.让对方生成全部走法,看看其中有没有走法可以吃掉自己的帅(将);
B.按照判断走法是否符合规则的思路,采用更简单的做法。
第一种做法没有什么不对的,但电脑象棋程序每秒种需要分析上万个局面,对每个局面都去生成全部走法显然太花时间了,所以我们要尝试第二种做法。
其实判断帅(将)是否被将军的过程并不复杂:
(1)假设帅(将)是车,判断它是否能吃到对方的车和将(帅)(中国象棋中有将帅不能对脸的规则);
(2)假设帅(将)是炮,判断它是否能吃到对方的炮;
(3)假设帅(将)是马,判断它是否能吃到对方的马,需要注意的是,帅(将)的马腿用的数组是ccAdvisorDelta,而不是ccKingDelta;
⑷假设帅(将)是过河的兵(卒),判断它是否能吃到对方的卒(兵)。
这样,一个复杂的走法生成过程(方案A)就被简化成几个简单的走法判断过程(方案B)。
3搜索算法
(3.1)博弈树的基本概念
如果参加博弈的不是一个主体,而是对抗性的敌我双方,则搜索的进程不仅仅取决于其中一方的意愿,而是取决于对方应付的策略。
由此而产生的搜索树,通常称为博弈树[5]。
图2.2就是一颗红方走棋时展开的4层博弈树。
红方走棋时展开的4层博弈树
(3.2)极大极小算法
极大极小值算法是解决博弈树问题的基本方法。
在象棋博弈中,极大极小值算法体现在始终站在博弈一方的立场选择着法。
因为一方所追求的利益恰是另一方极力避免的,所以在这一方行棋的时候,选择价值极大的儿子节点着法,另一方行棋则选择价值极小的儿子节点着法。
这就是一个极大极小过程。
当然,程序不能也没有必要做到搜索整棵博弈树的所有节点,对于一些己经确定为不佳的走步可以将以它为根节点的子树剪掉。
在这个过程中,最为重要的是搜索算法,高效的搜索算法可以保证用尽量少的时间和空间损耗来达到寻找高价值的走步。
但是真的想要博弈程序棋力提高,还必须有一个好的局面评价机制,即估值算法作后。
就是说,用这个估值算法评价的局面价值必须是客观的、正确的,可以确凿的评价局面的优劣以及优劣的程度。
如图2.3就是一颗结合估值算法搜索5层的完全极大极小博弈树。
图极大极小搜索
在图2.3中,最底层为当前搜索深度(5层)下对各个可能出现的棋面进行估值的结果,按照由底向上推理,先由黑方选择从第5层各个子节点中选择较小的值推出第4层各个父节点,然后红方从第4各个节点中选择教大的值向上推出第3层各个父节点,依次交替类推,推出第一层的节点值,从而选择出博弈路径。
(3.3)负极大值法
普通的极大极小值算法有点麻烦,一方试图取极小值,而另一方试图取极大值,也就是说我们总要检查哪一方要取极大值。
负极大值法是一种可以避免此类判断的算法,负极大值算的值是各子节点的值的负数的极大值。
如要这个算法正确运作。
例如象棋,估值函数必须对谁走棋敏感,也就是说对于一正的估值的话,则对于一个该黑方走棋的局面返回负的估值。
这样才能保证原来取极大值还是取极大值,原来取极小值则转变成取儿子节点负数的极大值。
(3.4)Alpha-Beta搜索算法
Alpha-Beta算法的原理是根据当前的信息确定儿子节点的上下界Alpha,Beta,然后根据儿子节点的儿子节点返回值进行剪枝操作。
其具体过程如图2.4所示:
图Alpha-Beta剪枝示意图
4置换表
几乎所有的棋类计算机博弈程序都使用了置换表技术。
“出棋制胜”的置换表非常简单,以局面的ZobristKey%HASH_SIZE作为索引值。
每个置换表项存储的内容为:
A.深度,B.标志,C.分值,D.最佳走法,E.ZobristLock校验码。
置换表的处理函数ProbeHash和RecordHash,RecordHash在判断深度后,将节点的值赋值给Hash表项中的每个值。
ProbeHash的过程:
(1)检查局面所对应的置换表项,如果ZobristLock校验码匹配,那么我们就认为命中(Hit)了;
(2)是否能直接利用置换表中的结果,取决于两个因素:
A.深度是否达到要求,B.非PV节点还需要考虑边界。
第二种情况是最好的(完全利用),ProbeHash返回一个非-MATE_VALUE的值,这样就能不对该节点进行展开了。
如果仅仅符合第一种情况,那么该置换表项的信息仍旧是有意义的——它的最佳走法给了我们一定的启发(部分利用)。
三、实现过程:
(1)流程图
(2)主要功能程序代码
#include
usingnamespacestd;
#include
intx[11][12];
intmain()
{
inti,j;
intx1,y1,x2,y2,rg,gg;
intrgo(int,int,int,int);
intggo(int,int,int,int);
/*初始化棋子(开局)*/
for(i=1;i<=5;i++)
{x[i][1]=x[10-i][1]=i+10;
x[i][10]=x[10-i][10]=i+20;
if(i%2==1)
{x[i][4]=x[10-i][4]=17;
x[i][7]=x[10-i][7]=27;
}
}
x[2][3]=x[8][3]=16;
x[2][8]=x[8][8]=26;
for(i=0;i<=9;i++)
x[i][0]=i;
for(i=1;i<=10;i++)
x[0][i]=i;
cout<<"S27"<for(j=10;j>=0;j--)
{for(i=0;i<=9;i++)
{
if(i==0)
{
cout<continue;
}
if(j==0)
{
cout<continue;
}
switch(x[i][j])
{
case0:
printf("");break;
case11:
printf("车");break;
case12:
printf("马");break;
case13:
printf("相");break;
case14:
printf("士");break;
case15:
printf("帅");break;
case16:
printf("炮");break;
case17:
printf("兵");break;
case21:
printf("JU");break;
case22:
printf("MA");break;
case23:
printf("XN");break;
case24:
printf("SH");break;
case25:
printf("JN");break;
case26:
printf("PO");break;
case27:
printf("ZU");break;
}
}
if(j==1)
{cout<<"|"<elseif(j==0)
cout<else
{cout<<"|"<}
/*走子*/
for(;;)
{
/*红子走子*/
for(rg=0;rg==0;)
{
for(x1=0,y1=0,x2=0,y2=0;x[x1][y1]==0||x[x1][y1]>=20||x2<1||y2<1||x2>9||y2>10||x[x2][y2]<20&&x[x2][y2]>10||x[x2][y2]==x[x1][y1];)
{
cout<<"请输入红子坐标(x1,y1,x2,y2):
";
cin>>x1>>y1>>x2>>y2;//scanf("%d,%d,%d,%d",&x1,&y1,&x2,&y2);
}
if(x[x1][y1]>20)
{continue;
}
rg=rgo(x1,y1,x2,y2);
if(rg==0)
continue;
else
{if(x[x2][y2]==25)
{
x[x2][y2]=x[x1][y1],x[x1][y1]=0;
cout<<"绿棋输";}
else
x[x2][y2]=x[x1][y1],x[x1][y1]=0;
}
}
cout<<"S27制作"<cout<<"=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*="<for(j=10;j>=0;j--)
{
for(i=0;i<=9;i++)
{
if(i==0)
{
cout<continue;
}
if(j==0)
{
cout<continue;
}
switch(x[i][j])
{
case0:
printf("");break;
case11:
printf("车");break;
case12:
printf("马");break;
case13:
printf("相");break;
case14:
printf("士");break;
case15:
printf("帅");break;
case16:
printf("炮");break;
case17:
printf("兵");break;
case21:
printf("JU");break;
case22:
printf("MA");break;
case23:
printf("XN");break;
case24:
printf("SH");break;
case25:
printf("JN");break;
case26:
printf("PO");break;
case27:
printf("ZU");break;
}
}
if(j==1)
{cout<<"|"<elseif(j==0)
printf("\n");
else
{cout<<"|"<}
/*绿子走子*/
for(gg=0;gg==0;)
{
for(x1=0,y1=0,x2=0,y2=0;x[x1][y1]<10||x2<1||y2<1||x2>9||y2>10||x[x2][y2]>20||x[x2][y2]==x[x1][y1];)
{
cout<<"请输入绿子坐标(x1,y1,x2,y2):
";
cin>>x1>>y1>>x2>>y2;
}
if(x[x1][y1]<20)
{continue;
}
gg=ggo(x1,y1,x2,y2);
if(gg==0)
continue;
else
{if(x[x2][y2]==25)
{
x[x2][y2]=x[x1][y1],x[x1][y1]=0;
printf("hongqishule\n");
}
else
x[x2][y2]=x[x1][y1],x[x1][y1]=0;
}
}
cout<<"S27制作"<=0;j--)
{
for(i=0;i<=9;i++)
{
if(i==0)
{
printf("%2d|",x[i][j]);
continue;
}
if(j==0)
{
printf("0%d",x[i][j]);
continue;
}
switch(x[i][j])
{
case0:
printf("");break;
case11:
printf("车");break;
case12:
printf("马");break;
case13:
printf("相");break;
case14:
printf("士");break;
case15:
printf("帅");break;
case16:
printf("炮");break;
case17:
printf("兵");break;
case21:
printf("JU");break;
case22:
printf("MA");break;
case23:
printf("XN");break;
case24:
printf("SH");break;
case25:
printf("JN");break;
case26:
printf("PO");break;
case27:
printf("ZU");break;
}
}
if(j==1)
{cout<<"|"<elseif(j==0)
cout<else
{cout<<"|"<}
}
/*判定红棋是否有错*/
intrgo(intxa,intya,intxb,intyb)
{if(x[xa][ya]==11)/*车*/
{
intt;
if(xb-xa==0)
{
if(ybt=yb,yb=ya,ya=t;
for(t=1+ya;tif(x[xa][t]!
=0&&x[xa][t]<20)
return0;
}
elseif(yb-ya==0)
{if(xbt=xb,xb=xa,xa=t;
for(t=1+xa;tif(x[t][ya]!
=0&&x[xa][t]<20)
return0;
}
elsereturn0;
return1;
}
if(x[xa][ya]==12)/*马*/
{if(fabs(yb-ya)==2&&fabs(xb-xa)==1)
{
if(yb-ya>0&&x[xa][ya+1]!
=0)
return0;
if(yb-ya<0&&x[xa][ya-1]!
=0)
return0;
if(x[xb][yb]!
=0&&x[xb][yb]<20)
return0;
}
elseif(fabs(xb-xa)==2&&fabs(yb-ya)==1)
{
if(xb-xa>0&&x[xa+1][ya]!
=0)
return0;
if(xb-xa<0&&x[xa-1][ya]!
=0)
return0;
if(x[xb][yb]!
=0&&x[xb][yb]<20)
return0