百度文库-让每个人平等地提升自我
1
中国象棋游戏开发设计报告
班级:
小组编号:
小组成员:
指导老师:
1
一、开发的目的和意义
面向对象程序设计作为一门软件设计的课程,具有极强的实践性,要求学
生具备灵活应用理论知识的能力及面向对象程序设计技能的基础。通过游戏开
发,学生能了解C++面向对象的设计方法与技巧,有效地、深刻地理解课程内
容,体会理论、方法和设计原则,培养分析实际问题和解决问题的能力,具备
使用面向对象程序设计开发工具设计实际系统的能力。还能够了解并通过使用
MFC,掌握一种可视化编程的方法,并通过游戏的开发加深对可视化编程的理
解。同时,可以提高运用C++编程语言解决实际问题的能力。
棋牌游戏属于休闲类游戏,具有上手快、游戏时间短的特点,更利于用户
进行放松休闲,为人们所喜爱,特别是棋类游戏,方便、快捷、操作简单,在
休闲娱乐中占主要位置。中国象棋作为中国自古以来的经典棋牌游戏之一,一
直都是人之间的较量,将中国象棋制作成游戏,可以实现人与计算机之间的对
弈。而且人工智能是综合性很强的一门边缘学科,它的中心任务是研究如何使
计算机去做那些过去只能靠人的智力才能做的工作。开发出了计算机象棋游戏,
以后不仅仅可以进行休闲游戏,还能锻炼自己的智力和象棋技术,更加方便了
人们的日常生活。
二、功能描述和分析(用户需求分析)
开发背景
我们周围有许多同学喜欢下象棋,尤其是男同学,希望能有人可以和自己下
象棋,但这种意愿常因为受到条件的限制而难以如愿,比如说需要身边刚好有
现成的棋盘棋子,比如说需要是同样懂得中国象棋的对手,但是大家都知道我
们这所大学男性同学占少数,即便是条件都满足了,还要考虑这位对手是否有何
自己下棋的心情。这时,如果有一台计算机,一个能够支持人机对弈的程序,
上面的问题迎刃而解。
而我们小组的这个想起游戏设计,正是希望能够做出一款拥有良好性能,
良好的智能,能够满足大多数爱好象棋的同学的需求中国象棋人机对弈程序。
用户需求分析
一款能够与用户对弈,满足用户需求的中国象棋程序,需要有棋盘棋子的
局面、鼠标响应控制棋子移动、棋子的走法规则、人机对弈的搜索算法、避免
异常引入的多线程、胜负判断,具体分析如下:
2.2.1棋盘棋子的局面
作为中国象棋的这项游戏,其必不可少的是就是棋子和棋盘,没有这两个
部分,想起功能无法实现,不仅仅如此,如果,仅仅有棋子和棋盘,而没有将
两者结合起来,那么,也将无法实现中国象棋的游戏功能,所以,棋子和棋盘
2
的设计在这个游戏设计中至关重要。
2.2.2鼠标响应
在对弈中,棋子是必须可以移动的,不然游戏无法进行。因此,鼠标左键
点击是必不可少的一部分。
2.2.3棋子的功能分析:
中国象棋中各色的象棋棋子的功能使象棋具有了真正的趣味性,中国象棋
的棋子的类型大致分为:帅(将)、士、象、马、车、炮、兵(卒)等几个类型。
帅(将):红方中的帅和黑方中的将的功能相同,都是只能在九宫格中进行
横向和竖向的移动,每次移动一格,并且不能移动超出九宫格,帅和将不能见
面。
士:士在整片棋盘中,和帅的移动范围类似,也是只能在九宫格中移动,
不过士的移动方向是对角线,并且每次只能在一个格子中移动。
象:象的走法遵循“象走田”的原则,不能绊象腿。
马:马的走法遵循“马走日”的原则,不能绊马腿。
车:在整块棋盘中,车可以横向或纵向3移动任意格。
炮:每次移动和车的类似,但是在吃对方棋子的时候必须中间有且只能有
一个棋子的间隔。
兵(卒):红方的兵和黑方的卒的功能相同,特点是只能向对方前进,而不
能后退,过河之前不能横向移动,过河之后可以横向移动,不管是前进还是横
向移动,每次都只能移动一格。
2.2.4良好的人机对弈
要实现人机的对弈,搜索算法是很重要的一部分。关于棋类对弈程序中的
搜索算法,已有成熟的Alpha-Beta搜索算法。我们在程序中直接借鉴了
Alpha-Beta搜索算法并辅以历史启发。
Alpha-Beta搜索算法:在中国象棋里,双方棋手获得相同的棋盘信息。他
们轮流走棋,目的就是吃掉对方的将或帅,或者避免自己的将或帅被吃。搜索
算法的搜索过程很漫长,因此对搜索算法进行简化是有必要的。
2.2.5多线程的必要性
由于程序在进行搜索时会占用大量的CPU时间,因而阻塞了位于同一线程
内的其他指令,使之无法正常工作,因而引入了多线程的思想另外开一个线程,
让各程序分开于多个线程。就可以解决程序异常的问题了,因此,多线程思想
的引入是有必要的。
2.2.6判断胜负
游戏需要判断最后由谁胜出
3
三、采用的开发工具和技术,开发环境,适用环境
开发工具:VisualC++MFC工程;
开发环境:win7;
适用环境:windows系统;
四、小组成员分工
初始化、局面设计部分(贺景);
判断胜负、棋子走法部分(邹京甫);
鼠标响应、绘图部分(吴鑫);
搜索引擎部分等由组员共同完成。
五、具体开发方法和过程
初始化部分
OnInitDialog()负责的是对话框的初始化。可以把有关中国象棋的棋局初始
化情况也放在了这里面。初始化的内容包括:
对引擎部分所用到的变量的初始化。包括对棋盘上的棋子位置进行初始化
(棋盘数组的初始化),对搜索深度、当前走棋方标志、棋局是否结束标志等的
初始化;
对棋盘、棋子的贴图位置(即棋盘、棋子在程序中实际显示位置)的初始
化;
对程序辅助部分所用到的一些变量的初始化。棋盘、棋子样式的默认形式,
以及着法名称列表的初始化等。
1.对棋盘的初始化memcpy(m_byChessBoard,InitChessBoard,90);
2.对棋盘、棋子的贴图位置(即棋盘、棋子在程序中实际显示位置)的初
始化;(&pOldBmp);程序辅助部分所用到的一些变量的初始化
棋盘、棋子样式的默认形式,下棋模式的默认选择,以及着法名称列表的
初始化等。
初始化部分的代码如下:
BOOLCChessDlg::OnInitDialog()
{
CDialog::OnInitDialog();
."menuitemtosystemmenu.
ASSERT((IDM_ABOUTBOX&0xFFF0)==IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX<0xF000);
4
CMenu*pSysMenu=GetSystemMenu(FALSE);
if(pSysMenu!=NULL)
{
CStringstrAboutMenu;
(IDS_ABOUTBOX);
if(!())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING,IDM_ABOUTBOX,
strAboutMenu);
}
}
SetIcon(m_hIcon,TRUE);//Setbigicon
SetIcon(m_hIcon,FALSE);//Setsmallicon
//彩色进度条设置
(RGB(0xFF,0xFF,0x00));//黄色
(RGB(0x00,0x93,0x00));//绿色
(RGB(0xE6,0xE6,0xFA));//淡紫色
(RGB(0,0,255));
(1);
(this);
(1);
(IDB_CHESSMAN,36,14,RGB(0,255,0));//创建含有棋子图形的ImgList,
用于绘制棋子
//下面这段代码取棋盘图形的宽,高
BITMAPBitMap;
(IDB_CHESSBOARD);
(&BitMap);//取BitMap对象
m_nBoardWidth=;//棋盘宽度
m_nBoardHeight=;//棋盘高度
();
memcpy(m_byChessBoard,InitChessBoard,90);//初始化棋盘
memcpy(m_byShowChessBoard,InitChessBoard,90);
memcpy(m_byBackupChessBoard,InitChessBoard,90);
m_pSE->SetSearchDepth(3);//设定搜索层数为3
m_pSE->SetMoveGenerator(m_pMG);//给搜索引擎设定走法产生器
m_pSE->SetEveluator(m_pEvel);//给搜索引擎设定估值核心
m_pSE->SetUrChessColor(m_nUrChessColor);
//设定用户为黑方或红方
m_pSE->SetThinkProgress(&m_progressThink);
//设定进度条
5
=NOCHESS;//将移动的棋子清空
returnTRUE;//returnTRUEunlessyoutthefocustoacontrol
}
局面设计
游戏设计中,我们的象棋棋盘采用的是直接加载位图生成棋盘,图片的大
小是宽度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_ELE
PHANT,B_HORSE,B_CAR},
{NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOC
HESS,NOCHESS,NOCHESS},
{NOCHESS,B_CANON,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOC
HESS,B_CANON,NOCHESS},
{B_PAWN,NOCHESS,B_PAWN,NOCHESS,B_PAWN,NOCHESS,B_PAWN,
NOCHESS,B_PAWN},
{NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOC
HESS,NOCHESS,NOCHESS},
//楚河//汉界
{NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOC
HESS,NOCHESS,NOCHESS},
{R_PAWN,NOCHESS,R_PAWN,NOCHESS,R_PAWN,NOCHESS,R_PAWN,
NOCHESS,R_PAWN},
{NOCHESS,R_CANON,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOC
HESS,R_CANON,NOCHESS},
{NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOC
HESS,NOCHESS,NOCHESS},
{R_CAR,R_HORSE,R_ELEPHANT,R_BISHOP,R_KING,R_BISHOP,R_ELE
PHANT,R_HORSE,R_CAR}
};
6
图1局面设计图
棋子的定义:
#defineNOCHESS0//没有棋子
#defineB_KING1//黑帅
#defineB_CAR2//黑车
#defineB_HORSE3//黑马
#defineB_CANON4//黑炮
#defineB_BISHOP5//黑士
#defineB_ELEPHANT6//黑象
#defineB_PAWN7//黑卒
#defineB_BEGINB_KING
#defineB_ENDB_PAWN
#defineR_KING8//红将
#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)//判断某个棋子是不是黑
色。
7
#defineIsRed(x)(x>=R_BEGIN&&x<=R_END)//判断某个棋子是不是
红色。
//判断两个棋子是不是同色
#defineIsSameSide(x,y)((IsBlack(x)&&IsBlack(y))||(IsRed(x)&&
IsRed(y)))
//棋子位置
typedefstruct
{
BYTEx;
BYTEy;
}CHESSMANPOS;
绘图部分
对于绘图部分,主要实现的是程序界面的绘图因此我们在这里将要完成棋
盘、棋子的显示走棋起始位置和目标位置的提示框的显示。而要实现这些我们
必须通过voidCChessDlg::OnPaint()这个函数实现
voidCChessDlg::OnPaint()
{
CPaintDCdc(this);
CDCMemDC;
inti,j;
POINTpt;
CBitmap*pOldBmp;
(&dc);
(IDB_CHESSBOARD);
pOldBmp=(&m_BoardBmp);
//绘制棋盘上的棋子
for(i=0;i<10;i++)
for(j=0;j<9;j++)
{
if(m_byShowChessBoard[i][j]==NOCHESS)
continue;
=j*GRILLEHEIGHT+14;
=i*GRILLEWIDTH+15;
(&MemDC,m_byShowChessBoard[i][j]-1,pt,ILD_TRANSPARENT);
}
//绘制用户正在拖动的棋子
if!=NOCHESS)
8
(&MemDC,,,ILD_TRANSPARENT);
(0,0,m_nBoardWidth,m_nBoardHeight,&MemDC,0,0,SRCCOPY);
//将绘制的内容刷新到屏幕
(&pOldBmp);//恢复内存Dc的原位图
();//释放内存
();//删除棋盘位图对象
}
鼠标响应部分
鼠标响应部分包括LButtonDown和LButtonUp两个功能,
LButtonDown实现的主要功能是拖动棋子在棋盘上的移动,他的重要性是,
如果没有这个功能,将无法走棋,其函数实现通过:
voidCChessDlg::OnLButtonDown(UINTnFlags,CPointpoint)
{
……
}
LButtonUp这个函数主要实现的功能是:拖动棋子完毕后放置到拖动后的
位置,在进行放置的过程中,需要使用一个Drop的释放函数。函数实现通过:
voidCChessDlg::OnLButtonUp(UINTnFlags,CPointpoint)
{
……
}
棋子走法
typedefstruct
{
shortnChessID;//表明是什么棋子
CHESSMANPOSFrom;//起始位置
CHESSMANPOSTo;//走到什么位置
intScore;//走法的分数
}CHESSMOVE;
在着法生成器中,采用的基本思想就是遍历整个棋盘(一个接一个地查看
棋盘上的每个位置点),当发现有当前下棋方的棋子时先判断它是何种类型的棋
子,然后根据其棋子类型而相应地找出其所有合法着法并存入着法队列。
这里谈到的“合法着法”包括以下几点:
1、各棋子按其行子规则行子。诸如马跳“日”字、象走“田”字、士在九
宫内斜行等等(这里需要特别注意的是卒(兵)的行子规则会随其所在位置的
不同而发生变化——过河后可以左右平移)。
2、行子不能越出棋盘的界限。当然所有棋子都不能走到棋盘的外面,同时
某些特定的棋子还有自己的行棋界限,如将、士不能出九宫,象不能过河。
9
3、行子的半路上不能有其它子阻拦(除了炮需要隔一个子才能打子之外)
以及行子的目的点不能有本方的棋子。
4、将帅不能碰面(本程序中只在生成计算机的着法时认为将帅碰面是非法
的,而对用户所走的导致将帅碰面的着法并不认为其非法,而只是产生败局罢
了)。
产生了着法后要将其存入着法队列以供搜索之用,由于搜索会搜索多层,
所以在把着法存入着法队列的时候还要同时存储该着法所属的搜索层数。因此
可以将着法队列定义为二维数组,其中第一个数组下标为层数,第二个数组下
标为每一层的全部着法数。着法生成中的各个棋子走法以及其他规则代码见。
棋子的移动由以下的函数分别执行:
帅(将):
VoidCMoveGenerator::Gen_KingMove()
{
}
士:
红士voidCMoveGenerator::Gen_RBishopMove()
{
}
黑士voidCMoveGenerator::Gen_BBishopMove()
{
}
象:
voidCMoveGenerator::Gen_ElephantMove()
{
}
马:
voidCMoveGenerator::Gen_HorMove()
{
}
车:
voidCMoveGenerator::Gen_CarMove()
{
}
炮:
voidCMoveGenerator::Gen_CanonMove()
{
}
兵(卒):
红兵voidCMoveGenerator::Gen_RPawnMove()
{
}
黑卒voidCMoveGenerator::Gen_BPawnMove()
{
10
}
搜索算法
我们用一棵象棋树来表示下棋的过程:树中每一个结点代表棋盘上的一个
局面,对每一个局面根据不同的走法又产生不同的局面。
…
……
……
该象棋树包含三种类型的结点:奇数层的中间结点以及根结点,表示轮到
红方走棋;偶数层的中间结点,表示轮到黑方走棋;叶子结点,表示棋局结束。
结合上面所讲的树,若给每个结点都打一个分值来评价其对应的局面,我
们通过估值引擎SetEveluator()来实现,过比较该分值的大小来判断局面的优劣。
voidSetEveluator(CEveluation*pEval)
{
m_pEval=pEval;
};
假定甲乙两方下棋,甲胜的局面是一个极大值(一个很大的正数),那么乙
胜的局面就是一个极小值(极大值的负值),和棋的局面则是零值(或是接近零
的值)。如此,当轮到甲走棋时他会尽可能地让局面上的分值大,相反轮到乙走
棋时他会选尽可能地让局面上的分值小。反映到博弈树上,即如果假设奇数层
表示轮到甲方走棋,偶数层表示轮到乙方走棋。那么由于甲方希望棋盘上的分
值尽可能大,则在偶数层上会挑选分值最大的结点——偶数层的结点是甲走完
一步棋之后的棋盘局面,反映了甲方对棋局形势的要求。同样道理,由于乙方
希望棋盘上的分值尽可能小,那么在奇数层上会选择分值最小的结点。这是“最
小-最大”(Minimax)的基本思想。这样搜索函数在估值函数的协助下可以通过
在奇数层选择分值最大(最小)的结点,在偶数层选择分值最小(最大)的结
点的方式来搜索以当前局面为根结点、限定搜索层数以内的整棵树来获得一个
最佳的着法。下面是“最大-最小”的主要代码
intCNegaMaxEngine::NegaMax(intnDepth)
{
intcurrent=-20000;
intscore;
intCount,i;
BYTEtype;
11
i=IsGameOver(CurPosition,nDepth);//检查棋局是否结束
if(i!=0)
returni;//棋局结束,返回极大/极小值
if(nDepth<=0)//叶子节点取估值
return
m_pEval->Eveluate(CurPosition,(m_nMaxDepth-nDepth)%2,m_nUrChessColor);
//列举当前棋局下一步所有可能的走法
Count=m_pMG->CreatePossibleMove(CurPosition,nDepth,(m_nMaxDepth
-nDepth)%2,m_nUrChessColor);
if(nDepth==m_nMaxDepth)
{
//在根节点设定进度条
m_pThinkProgress->SetRange(0,Count);
m_pThinkProgress->SetStep(1);
}
for(i=0;i
{
if(nDepth==m_nMaxDepth)
m_pThinkProgress->StepIt();//走进度条
type=MakeMove(&m_pMG->m_MoveList[nDepth][i]);//根据走
法产生新局面
score=-NegaMax(nDepth-1);//递归调用负极
大值搜索下一层节点
UnMakeMove(&m_pMG->m_MoveList[nDepth][i],type);//恢复当
前局面
if(score>current)//如果score大于已
知的最大值
{
current=score;//修改当前最大
值为score
if(nDepth==m_nMaxDepth)
m_cmBestMove=m_pMG->m_MoveList[nDepth][i];//靠近根
部时保存最佳走法
}
}
returncurrent;//返回极大值
}
12
“最小-最大”思想再加上“树的裁剪”就是Alpha-Beta搜索算法的核心。
最基本的Alpha-Beta算法的代码如下:
intCAlphaBetaEngine::AlphaBeta(intnDepth,intalpha,intbeta)
{
intscore;
intCount,i;
BYTEtype;
i=IsGameOver(CurPosition,nDepth);//检查是否游戏结束
if(i!=0)
returni;//结束,返回估值
//叶子节点取估值
if(nDepth<=0)
return
m_pEval->Eveluate(CurPosition,(m_nMaxDepth-nDepth)%2,m_nUrChessColor);
//此函数找出当前局面所有可能的走法,然后放进m_pMG
->m_MoveList当中
Count=m_pMG->CreatePossibleMove(CurPosition,nDepth,(m_nMaxDepth
-nDepth)%2,m_nUrChessColor);
if(nDepth==m_nMaxDepth)
{
//在根节点设定进度条
m_pThinkProgress->SetRange(0,Count);
m_pThinkProgress->SetStep(1);
}
//对所有可能的走法
for(i=0;i
{
if(nDepth==m_nMaxDepth)
m_pThinkProgress->StepIt();//走进度条
type=MakeMove(&m_pMG->m_MoveList[nDepth][i]);//将当前局
面应用此走法,变为子节点的局面
score=-AlphaBeta(nDepth-1,-beta,-alpha);//递归搜索子节点
UnMakeMove(&m_pMG->m_MoveList[nDepth][i],type);//将此节点
的局面恢复为当前节点
if(score>alpha)
{
alpha=score;//保留极大值
13
//靠近根节点时保留最佳走法
if(nDepth==m_nMaxDepth)
m_cmBestMove=m_pMG->m_MoveList[nDepth][i];
}
if(alpha>=beta)
break;//剪枝,放弃搜索剩下的节点
}
returnalpha;//返回极大值
}
Alpha-Beta搜索算法是在“最小-最大”的基础上引入“树的裁剪”的思想
以期提高效率,它的效率将在很大程度上取决于树的结构——如果搜索了没多
久就发现可以进行“裁剪”了,那么需要分析的工作量将大大减少,效率自然
也就大大提高;而如果直至分析了所有的可能性之后才能做出“裁剪”操作,
那此时“裁剪”也已经失去了它原有的价值(因为你已经分析了所有情况,这
时的Alpha-Beta搜索已和“最小-最大”搜索别无二致了)。因而,要想保证
Alpha-Beta搜索算法的效率就需要调整树的结构,即调整待搜索的结点的顺序,
使得“裁剪”可以尽可能早地发生。
可以根据部分已经搜索过的结果来调整将要搜索的结点的顺序。因为,通
常当一个局面经过搜索被认为较好时,其子结点中往往有一些与它相似的局面
(如个别无关紧要的棋子位置有所不同)也是较好的。由所提出的“历史启发”
(HistoryHeuristic)就是建立在这样一种观点之上的。在搜索的过程中,每当
发现一个好的走法,就给该走法累加一个增量以记录其“历史得分”,一个多次
被搜索并认为是好的走法的“历史得分”就会较高。对于即将搜索的结点,按
照“历史得分”的高低对它们进行排序,保证较好的走法(“历史得分”高的走
法)排在前面,这样Alpha-Beta搜索就可以尽可能早地进行“裁剪”,从而保证
了搜索的效率。
对于着法的排序可以使用各种排序算法,在程序中采用了归并排序。归并
排序的空间复杂度为O(n),时间复杂度为O(nlog2n),具有较高的效率。
历史启发部分的主要代码如下:
voidCHistoryHeuristic::RetHistoryTable()
{
memt(m_HistoryTable,10,8100*4);
}
intCHistoryHeuristic::GetHistoryScore(CHESSMOVE*move)
{
intnFrom,nTo;
nFrom=move->*9+move->;//原始位置
nTo=move->*9+move->;//目标位置
14
returnm_HistoryTable[nFrom][nTo];//返回历史得分
}
多线程
由于程序出现了异常:有时对用户方的功能完全正确,而对电脑方的有些
功能却不起作用,这是由于程序在进行搜索时会占用大量的CPU时间,因而阻
塞了位于同一线程内的其他指令,使之无法正常工作,因而我们引入了多线程
的思想另外开一个线程,让各程序分开于多个线程。函数原型:
CWinThread*AfxBeginThread(
AFX_THREADPROCThinkProc,
LPVOIDpParam,
intnPriority=THREAD_PRIORITY_NORMAL,
UINTnStackSize=0,
DWORDdwCreateFlags=0,
LPSECURITY_ATTRIBUTESlpSecurityAttrs=NULL);
该函数启动一个新的线程并返回一个指向该新线程对象的指针,然后新的
线程与启动该新线程的线程同时运行。该函数的第一个参数
AFX_THREADPROCThinkProc指定了线程函数。线程函数的内容即为新线程
所要执行的内容,线程函数执行完毕,新线程结束(自动销毁)。
线程函数必须被定义为全局函数,其返回值类型必须是UINT,必须有一
个LPVOID类型的参数。可以把调用引擎部分的搜索函数的代码以及完成走棋
动作的代码放入所定义的思考线程内,如下:
DWORDWINAPIThinkProc(LPVOIDpParam)
{
CChessDlg*pDlg=(CChessDlg*)pParam;
pDlg->Think();
return0;
}
然后,只要将原先调搜索函数并完成走棋的代码代之以调用
AfxBeginThread来启动新线程即可,实现了程序的多线程,不能正常工作的问
题也就随之解决了。
判断胜负
红方的帅被吃,或者黑方的将被吃,游戏结束,判断被吃的一方输。具体
代码如下:
intCChessDlg::IsGameOver(BYTEposition[][9])
{
inti,j;
BOOLRedLive=FALSE,BlackLive=FALSE;
//检查红方九宫是否有帅
for(i=7;i<10;i++)
for(j=3;j<6;j++)
15
{
if(position[i][j]==B_KING)
BlackLive=TRUE;
if(position[i][j]==R_KING)
RedLive=TRUE;
}
//检查黑方九宫是否有将
for(i=0;i<3;i++)
for(j=3;j<6;j++)
{
if(position[i][j]==B_KING)
BlackLive=TRUE;
if(position[i][j]==R_KING)
RedLive=TRUE;
}
if(m_nUrChessColor==REDCHESS)
{
if(!RedLive)
return1;
if(!BlackLive)
return-1;
}
el
{
if(!RedLive)
return-1;
if(!BlackLive)
return1;
}
return0;
}
游戏退出
玩家如果不想继续游戏,可以通过两种方式退出,一是在选项栏点击“退
出”,二是直接点击右上角的“关闭”按钮。具体代码如下:
voidCChessDlg::OnClo()
{
//TODO:Addyourmessagehandlercodehereand/orcalldefault
if(MessageBox("是否确定退出游戏?","提醒",MB_YESNO|
MB_ICONQUESTION|MB_DEFBUTTON2)==IDNO)
return;
EndDialog(IDOK);
16
CDialog::OnClo();
}
voidCChessDlg::OnExit()
{
OnClo();
}
系统实现
游戏开始时,由执红棋的一方先走,双方轮流各走一着,直至分出胜、负、
和,对局即终了。双方各走一着,称为一个回合。如果有一方的主帅被对方吃
了,就算那一方输。
六、总结与心得体会
我们小组(001小组成员:吴鑫,贺景,邹京甫)选择的这次游戏设计开
发小项目是做一个主要应用于人机对战的中国象棋程序,要求这个程序有能输
入并使用经典对局棋谱的能力,有在游戏过程中自我提高的力,以及拥有一套
完备的智能算法。之所以选择这个题目,一则是对中国象棋的喜爱,也希望籍
此机会,将大一大二这两年的知识积累学以致用,并以此提高自己的编程能力。
我们小组在这次游戏设计中选用VisualC++作为开发工具,是因为平
时接触C++语言比较多,可能更便于表达自己的一些编程想法,也因为时间的
紧促(十八周就要检测我们做的游戏),但是由于对MFC的不熟悉,我们不得
17
不花很多的时间在网上书上学习有关这方面的知识,直到现在才大体了解了
MFC的基本框架原理,但仍不能很得心应手的用于实现自己的编程想法中。所
以程序显得比较浅薄,有些方面还不够严密,
从设计的选题、需求分析、总体设计、实现、调试,我们按照自己的思路
以及网络上一些编程大师们的有利思想,通过一次又一次的修改,添加,组合,
才使得我们的程序运行成功。
真是万事开头难啊,我在图像处理方面就遇到了困难,因为之前没编写过
这样的具有图形界面的游戏,先是画面的初始化、显示和刷新原理,都翻看了
不少书籍,再实际操作时,许多方面需要处理的细节问题在书本上难以找到,
我甚至到网上胡乱搜索,随便看到凡是和图像显示的代码就复制下来自己试验,
居然克服了其中许多问题。
总而言之,这次的游戏的确让我们小组每一个人受益不浅,不仅仅学到了
计算机人工智能的丰富的知识,软件开发的方法,提高了我分析问题和解决问
题的能力,并将专业理论知识应用到实践中去,培养了我们独立完成项目规划
和实现的能力。虽然这次游戏设计遇到了许多问题,但是我们在解决这些问题
的过程中,明白了自身的不足和差距,认识到学习是一个不能放松的漫长的过
程,而且自学是一种必须掌握的技能。这是我们在大学的第一次学习与实践,
它为我将来的学习中提供了一次很好的锻炼机会,是一次宝贵的经验。
本文发布于:2023-03-06 03:06:54,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/1678043214152729.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:中国象棋棋盘图片.doc
本文 PDF 下载地址:中国象棋棋盘图片.pdf
留言与评论(共有 0 条评论) |