中国象棋游戏开发设计报告Word格式.doc
《中国象棋游戏开发设计报告Word格式.doc》由会员分享,可在线阅读,更多相关《中国象棋游戏开发设计报告Word格式.doc(18页珍藏版)》请在冰点文库上搜索。
![中国象棋游戏开发设计报告Word格式.doc](https://file1.bingdoc.com/fileroot1/2023-4/29/ec74854f-776f-43a3-8153-0e910e7e623c/ec74854f-776f-43a3-8153-0e910e7e623c1.gif)
士:
士在整片棋盘中,和帅的移动范围类似,也是只能在九宫格中移动,不过士的移动方向是对角线,并且每次只能在一个格子中移动。
象:
象的走法遵循“象走田”的原则,不能绊象腿。
马:
马的走法遵循“马走日”的原则,不能绊马腿。
车:
在整块棋盘中,车可以横向或纵向3移动任意格。
炮:
每次移动和车的类似,但是在吃对方棋子的时候必须中间有且只能有一个棋子的间隔。
兵(卒):
红方的兵和黑方的卒的功能相同,特点是只能向对方前进,而不能后退,过河之前不能横向移动,过河之后可以横向移动,不管是前进还是横向移动,每次都只能移动一格。
2.2.4良好的人机对弈
要实现人机的对弈,搜索算法是很重要的一部分。
关于棋类对弈程序中的搜索算法,已有成熟的Alpha-Beta搜索算法。
我们在程序中直接借鉴了Alpha-Beta搜索算法并辅以历史启发。
Alpha-Beta搜索算法:
在中国象棋里,双方棋手获得相同的棋盘信息。
他们轮流走棋,目的就是吃掉对方的将或帅,或者避免自己的将或帅被吃。
搜索算法的搜索过程很漫长,因此对搜索算法进行简化是有必要的。
2.2.5多线程的必要性
由于程序在进行搜索时会占用大量的CPU时间,因而阻塞了位于同一线程内的其他指令,使之无法正常工作,因而引入了多线程的思想另外开一个线程,让各程序分开于多个线程。
就可以解决程序异常的问题了,因此,多线程思想的引入是有必要的。
2.2.6判断胜负
游戏需要判断最后由谁胜出
三、采用的开发工具和技术,开发环境,适用环境
开发工具:
VisualC++MFC工程;
开发环境:
win7;
适用环境:
windows系统;
四、小组成员分工
初始化、局面设计部分(贺景);
判断胜负、棋子走法部分(邹京甫);
鼠标响应、绘图部分(吴鑫);
搜索引擎部分等由组员共同完成。
五、具体开发方法和过程
5.1初始化部分
OnInitDialog()负责的是对话框的初始化。
可以把有关中国象棋的棋局初始化情况也放在了这里面。
初始化的内容包括:
对引擎部分所用到的变量的初始化。
包括对棋盘上的棋子位置进行初始化(棋盘数组的初始化),对搜索深度、当前走棋方标志、棋局是否结束标志等的初始化;
对棋盘、棋子的贴图位置(即棋盘、棋子在程序中实际显示位置)的初始化;
对程序辅助部分所用到的一些变量的初始化。
棋盘、棋子样式的默认形式,以及着法名称列表的初始化等。
1.对棋盘的初始化memcpy(m_byChessBoard,InitChessBoard,90);
2.对棋盘、棋子的贴图位置(即棋盘、棋子在程序中实际显示位置)的初始化;
MemDC.SelectObject(&
pOldBmp);
//恢复内存Dc的原位图
3.对程序辅助部分所用到的一些变量的初始化
棋盘、棋子样式的默认形式,下棋模式的默认选择,以及着法名称列表的初始化等。
初始化部分的代码如下:
BOOLCChessDlg:
:
OnInitDialog()
{
CDialog:
OnInitDialog();
//Add"
About..."
menuitemtosystemmenu.
//IDM_ABOUTBOXmustbeinthesystemcommandrange.
ASSERT((IDM_ABOUTBOX&
0xFFF0)==IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX<
0xF000);
CMenu*pSysMenu=GetSystemMenu(FALSE);
if(pSysMenu!
=NULL)
{
CStringstrAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if(!
strAboutMenu.IsEmpty())
{
pSysMenu->
AppendMenu(MF_SEPARATOR);
AppendMenu(MF_STRING,IDM_ABOUTBOX,strAboutMenu);
}
}
SetIcon(m_hIcon,TRUE);
//Setbigicon
SetIcon(m_hIcon,FALSE);
//Setsmallicon
//彩色进度条设置
m_progressThink.SetStartColor(RGB(0xFF,0xFF,0x00));
//黄色
m_progressThink.SetEndColor(RGB(0x00,0x93,0x00));
//绿色
m_progressThink.SetBkColor(RGB(0xE6,0xE6,0xFA));
//淡紫色
m_progressThink.SetTextColor(RGB(0,0,255));
m_progressThink.ShowPercent
(1);
m_tooltip.Create(this);
m_tooltip.Activate
(1);
m_Chessman.Create(IDB_CHESSMAN,36,14,RGB(0,255,0));
//创建含有棋子图形的ImgList,用于绘制棋子
//下面这段代码取棋盘图形的宽,高
BITMAPBitMap;
m_BoardBmp.LoadBitmap(IDB_CHESSBOARD);
m_BoardBmp.GetBitmap(&
BitMap);
//取BitMap对象
m_nBoardWidth=BitMap.bmWidth;
//棋盘宽度
m_nBoardHeight=BitMap.bmHeight;
//棋盘高度
m_BoardBmp.DeleteObject();
memcpy(m_byChessBoard,InitChessBoard,90);
//初始化棋盘
memcpy(m_byShowChessBoard,InitChessBoard,90);
memcpy(m_byBackupChessBoard,InitChessBoard,90);
m_pSE->
SetSearchDepth(3);
//设定搜索层数为3
SetMoveGenerator(m_pMG);
//给搜索引擎设定走法产生器
SetEveluator(m_pEvel);
//给搜索引擎设定估值核心
SetUserChessColor(m_nUserChessColor);
//设定用户为黑方或红方
SetThinkProgress(&
m_progressThink);
//设定进度条
m_MoveChess.nChessID=NOCHESS;
//将移动的棋子清空
returnTRUE;
//returnTRUEunlessyousetthefocustoacontrol
}
5.2局面设计
游戏设计中,我们的象棋棋盘采用的是直接加载位图生成棋盘,图片的大小是宽度377*高度417,棋盘上每个格子的大小:
39*39,图片格式为:
BMP。
棋子部分是通过加载位图实现的,图片的大小是:
宽度32*高度32,图片的格式也是BMP。
我们用一个10*9的数组来存储棋盘上的信息,数组的每个元素存储棋盘上是否有棋子。
棋盘的初始情形如下所示(图1是整个棋盘与棋子的局面图):
constBYTEInitChessBoard[10][9]=
{B_CAR,B_HORSE,B_ELEPHANT,B_BISHOP,B_KING,B_BISHOP,B_ELEPHANT,B_HORSE,B_CAR},
{NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS},
{NOCHESS,B_CANON,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,B_CANON,NOCHESS},
{B_PAWN,NOCHESS,B_PAWN,NOCHESS,B_PAWN,NOCHESS,B_PAWN,NOCHESS,B_PAWN},
//楚河//汉界
{R_PAWN,NOCHESS,R_PAWN,NOCHESS,R_PAWN,NOCHESS,R_PAWN,NOCHESS,R_PAWN},
{NOCHESS,R_CANON,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,R_CANON,NOCHESS},
{R_CAR,R_HORSE,R_ELEPHANT,R_BISHOP,R_KING,R_BISHOP,R_ELEPHANT,R_HORSE,R_CAR}
};
图1局面设计图
棋子的定义:
#defineNOCHESS0//没有棋子
#defineB_KING 1//黑帅
#defineB_CAR 2//黑车
#defineB_HORSE 3//黑马
#defineB_CANON 4//黑炮
#defineB_BISHOP5//黑士
#defineB_ELEPHANT6//黑象
#defineB_PAWN7//黑卒
#defineB_BEGINB_KING
#defineB_ENDB_PAWN
#defineR_KING 8//红将
#defineR_CAR9//红车
#defineR_HORSE10//红马
#defineR_CANON11//红炮
#defineR_BISHOP12//红士
#defineR_ELEPHANT13//红相
#defineR_PAWN14//红兵
#defineR_BEGINR_KING
#defineR_ENDR_PAWN
#defineIsBlack(x)(x>
=B_BEGIN&
&
x<
=B_END)//判断某个棋子是不是黑色。
#defineIsRed(x)(x>
=R_BEGIN&
=R_END)//判断某个棋子是不是红色。
//判断两个棋子是不是同色
#defineIsSameSide(x,y)((IsBlack(x)&
IsBlack(y))||(IsRed(x)&
IsRed(y)))
//棋子位置
typedefstruct
BYTEx;
BYTEy;
}CHESSMANPOS;
5.3绘图部分
对于绘图部分,主要实现的是程序界面的绘图因此我们在这里将要完成棋盘、棋子的显示走棋起始位置和目标位置的提示框的显示。
而要实现这些我们必须通过voidCChessDlg:
OnPaint()这个函数实现
voidCChessDlg:
OnPaint()
CPaintDCdc(this);
CDCMemDC;
inti,j;
POINTpt;
CBitmap*pOldBmp;
MemDC.CreateCompatibleDC(&
dc);
pOldBmp=MemDC.SelectObject(&
m_BoardBmp);
//绘制棋盘上的棋子
for(i=0;
i<
10;
i++)
for(j=0;
j<
9;
j++)
{
if(m_byShowChessBoard[i][j]==NOCHESS)
continue;
pt.x=j*GRILLEHEIGHT+14;
pt.y=i*GRILLEWIDTH+15;
m_Chessman.Draw(&
MemDC,m_byShowChessBoard[i][j]-1,pt,ILD_TRANSPARENT);
//绘制用户正在拖动的棋子
if(m_MoveChess.nChessID!
=NOCHESS)
m_Chessman.Draw(&
MemDC,m_MoveChess.nChessID-1,m_MoveChess.ptMovePoint,ILD_TRANSPARENT);
dc.BitBlt(0,0,m_nBoardWidth,m_nBoardHeight,&
MemDC,0,0,SRCCOPY);
//将绘制的内容刷新到屏幕
MemDC.SelectObject(&
//恢复内存Dc的原位图
MemDC.DeleteDC();
//释放内存
//删除棋盘位图对象
5.4鼠标响应部分
鼠标响应部分包括LButtonDown和LButtonUp两个功能,
LButtonDown实现的主要功能是拖动棋子在棋盘上的移动,他的重要性是,如果没有这个功能,将无法走棋,其函数实现通过:
OnLButtonDown(UINTnFlags,CPointpoint)
……
LButtonUp这个函数主要实现的功能是:
拖动棋子完毕后放置到拖动后的位置,在进行放置的过程中,需要使用一个Drop的释放函数。
函数实现通过:
OnLButtonUp(UINTnFlags,CPointpoint)
……
5.5棋子走法
shortnChessID;
//表明是什么棋子
CHESSMANPOSFrom;
//起始位置
CHESSMANPOSTo;
//走到什么位置
intScore;
//走法的分数
}CHESSMOVE;
在着法生成器中,采用的基本思想就是遍历整个棋盘(一个接一个地查看棋盘上的每个位置点),当发现有当前下棋方的棋子时先判断它是何种类型的棋子,然后根据其棋子类型而相应地找出其所有合法着法并存入着法队列。
这里谈到的“合法着法”包括以下几点:
1、各棋子按其行子规则行子。
诸如马跳“日”字、象走“田”字、士在九宫内斜行等等(这里需要特别注意的是卒(兵)的行子规则会随其所在位置的不同而发生变化——过河后可以左右平移)。
2、行子不能越出棋盘的界限。
当然所有棋子都不能走到棋盘的外面,同时某些特定的棋子还有自己的行棋界限,如将、士不能出九宫,象不能过河。
3、行子的半路上不能有其它子阻拦(除了炮需要隔一个子才能打子之外)以及行子的目的点不能有本方的棋子。
4、将帅不能碰面(本程序中只在生成计算机的着法时认为将帅碰面是非法的,而对用户所走的导致将帅碰面的着法并不认为其非法,而只是产生败局罢了)。
产生了着法后要将其存入着法队列以供搜索之用,由于搜索会搜索多层,所以在把着法存入着法队列的时候还要同时存储该着法所属的搜索层数。
因此可以将着法队列定义为二维数组,其中第一个数组下标为层数,第二个数组下标为每一层的全部着法数。
着法生成中的各个棋子走法以及其他规则代码见MoveGenerator.cpp。
棋子的移动由以下的函数分别执行:
VoidCMoveGenerator:
Gen_KingMove()
红士voidCMoveGenerator:
Gen_RBishopMove()
黑士voidCMoveGenerator:
Gen_BBishopMove()
voidCMoveGenerator:
Gen_ElephantMove()
Gen_HorseMove()
Gen_CarMove()
Gen_CanonMove()
红兵voidCMoveGenerator:
Gen_RPawnMove()
}
黑卒voidCMoveGenerator:
Gen_BPawnMove()
5.6搜索算法
我们用一棵象棋树来表示下棋的过程:
树中每一个结点代表棋盘上的一个局面,对每一个局面根据不同的走法又产生不同的局面。
该象棋树包含三种类型的结点:
奇数层的中间结点以及根结点,表示轮到红方走棋;
偶数层的中间结点,表示轮到黑方走棋;
叶子结点,表示棋局结束。
结合上面所讲的树,若给每个结点都打一个分值来评价其对应的局面,我们通过估值引擎SetEveluator()来实现,过比较该分值的大小来判断局面的优劣。
voidSetEveluator(CEveluation*pEval)
m_pEval=pEval;
假定甲乙两方下棋,甲胜的局面是一个极大值(一个很大的正数),那么乙胜的局面就是一个极小值(极大值的负值),和棋的局面则是零值(或是接近零的值)。
如此,当轮到甲走棋时他会尽可能地让局面上的分值大,相反轮到乙走棋时他会选尽可能地让局面上的分值小。
反映到博弈树上,即如果假设奇数层表示轮到甲方走棋,偶数层表示轮到乙方走棋。
那么由于甲方希望棋盘上的分值尽可能大,则在偶数层上会挑选分值最大的结点——偶数层的结点是甲走完一步棋之后的棋盘局面,反映了甲方对棋局形势的要求。
同样道理,由于乙方希望棋盘上的分值尽可能小,那么在奇数层上会选择分值最小的结点。
这是“最小-最大”(Minimax)的基本思想。
这样搜索函数在估值函数的协助下可以通过在奇数层选择分值最大(最小)的结点,在偶数层选择分值最小(最大)的结点的方式来搜索以当前局面为根结点、限定搜索层数以内的整棵树来获得一个最佳的着法。
下面是“最大-最小”的主要代码
intCNegaMaxEngine:
NegaMax(intnDepth)
intcurrent=-20000;
intscore;
intCount,i;
BYTEtype;
i=IsGameOver(CurPosition,nDepth);
//检查棋局是否结束
if(i!
=0)
returni;
//棋局结束,返回极大/极小值
if(nDepth<
=0)//叶子节点取估值
returnm_pEval->
Eveluate(CurPosition,(m_nMaxDepth-nDepth)%2,m_nUserChessColor);
//列举当前棋局下一步所有可能的走法
Count=m_pMG->
CreatePossibleMove(CurPosition,nDepth,(m_nMaxDepth-nDepth)%2,m_nUserChessColor);
if(nDepth==m_nMaxDepth)
//在根节点设定进度条
m_pThinkProgress->
SetRange(0,Count);
SetStep
(1);
Count