资源描述
中国象棋游戏开发设计汇报
班级:
小组编号:
小组组员:
指导老师:
一、开发目标和意义
面向对象程序设计作为一门软件设计课程,含有极强实践性,要求学生含有灵活应用理论知识能力及面向对象程序设计技能基础。经过游戏开发,学生能了解C++面向对象设计方法和技巧,有效地、深刻地了解课程内容,体会理论、方法和设计标准,培养分析实际问题和处理问题能力,含有使用面向对象程序设计开发工具设计实际系统能力。还能够了解并经过使用MFC,掌握一个可视化编程方法,并经过游戏开发加深对可视化编程了解。同时,能够提升利用C++编程语言处理实际问题能力。
棋牌游戏属于休闲类游戏,含有上手快、游戏时间短特点,更利于用户进行放松休闲,为大家所喜爱,尤其是棋类游戏,方便、快捷、操作简单,在休闲娱乐中占关键位置。中国象棋作为中国自古以来经典棋牌游戏之一,一直全部是人之间较量,将中国象棋制作成游戏,能够实现人和计算机之间对弈。而且人工智能是综合性很强一门边缘学科,它中心任务是研究怎样使计算机去做那些过去只能靠人智力才能做工作。开发出了计算机象棋游戏,以后不仅仅能够进行休闲游戏,还能锻炼自己智力和象棋技术,愈加方便了大家日常生活。
二、功效描述和分析(用户需求分析)
2.1开发背景
我们周围有很多同学喜爱下象棋,尤其是男同学,期望能有些人能够和自己下象棋,但这种意愿常因为受到条件限制而难以如愿,比如说需要身边刚好有现成棋盘棋子,比如说需要是一样知道中国象棋对手,不过大家全部知道我们这所大学男性同学占少数,即便是条件全部满足了,还要考虑这位对手是否有何自己下棋心情。 这时,假如有一台计算机,一个能够支持人机对弈程序,上面问题迎刃而解。
而我们小组这个想起游戏设计,正是期望能够做出一款拥有良好性能,良好智能,能够满足大多数爱好象棋同学需求中国象棋人机对弈程序。
2.2用户需求分析
一款能够和用户对弈,满足用户需求中国象棋程序,需要有棋盘棋子局面、鼠标响应控制棋子移动、棋子走法规则、人机对弈搜索算法、避免异常引入多线程、胜败判定,具体分析以下:
2.2.1棋盘棋子局面
作为中国象棋这项游戏,其必不可少是就是棋子和棋盘,没有这两个部分,想起功效无法实现,不仅仅如此,假如,仅仅有棋子和棋盘,而没有将二者结合起来,那么,也将无法实现中国象棋游戏功效,所以,棋子和棋盘设计在这个游戏设计中至关关键。
2.2.2鼠标响应
在对弈中,棋子是必需能够移动,不然游戏无法进行。所以,鼠标左键点击是必不可少一部分。
2.2.3棋子功效分析:
中国象棋中各色象棋棋子功效使象棋含有了真正趣味性,中国象棋棋子类型大致分为:帅(将)、士、象、马、车、炮、兵(卒)等多个类型。
帅(将):红方中帅和黑方中将功效相同,全部是只能在九宫格中进行横向和竖向移动,每次移动一格,而且不能移动超出九宫格,帅和将不能见面。
士:士在整片棋盘中,和帅移动范围类似,也是只能在九宫格中移动,不过士移动方向是对角线,而且每次只能在一个格子中移动。
象:象走法遵照“象走田”标准,不能绊象腿。
马:马走法遵照“马走日”标准,不能绊马腿。
车:在整块棋盘中,车能够横向或纵向3移动任意格。
炮:每次移动和车类似,不过在吃对方棋子时候必需中间有且只能有一个棋子间隔。
兵(卒):红方兵和黑方卒功效相同,特点是只能向对方前进,而不能后退,过河之前不能横向移动,过河以后能够横向移动,不管是前进还是横向移动,每次全部只能移动一格。
2.2.4良好人机对弈
要实现人机对弈,搜索算法是很关键一部分。相关棋类对弈程序中搜索算法,已经有成熟Alpha-Beta搜索算法。我们在程序中直接借鉴了Alpha-Beta搜索算法并辅以历史启发。
Alpha-Beta搜索算法:在中国象棋里,双方棋手取得相同棋盘信息。她们轮番走棋,目标就是吃掉对方将或帅,或避免自己将或帅被吃。搜索算法搜索过程很漫长,所以对搜索算法进行简化是有必需。
2.2.5多线程必需性
因为程序在进行搜索时会占用大量CPU时间,所以阻塞了在同一线程内其它指令,使之无法正常工作,所以引入了多线程思想另外开一个线程,让各程序分开于多个线程。就能够处理程序异常问题了,所以,多线程思想引入是有必需。
2.2.6判定胜败
游戏需要判定最终由谁胜出
三、采取开发工具和技术,开发环境,适用环境
开发工具:Visual C++ MFC工程;
开发环境:win7;
适用环境:windows系统;
四、小组组员分工
初始化、局面设计部分(贺景);
判定胜败、棋子走法部分(邹京甫);
鼠标响应、绘图部分(吴鑫);
搜索引擎部分等由组员共同完成。
五、具体开发方法和过程
5.1初始化部分
OnInitDialog()负责是对话框初始化。能够把相关中国象棋棋局初始化情况也放在了这里面。初始化内容包含:
对引擎部分所用到变量初始化。包含对棋盘上棋子位置进行初始化(棋盘数组初始化),对搜索深度、目前走棋方标志、棋局是否结束标志等初始化;
对棋盘、棋子贴图位置(即棋盘、棋子在程序中实际显示位置)初始化;
对程序辅助部分所用到部分变量初始化。棋盘、棋子样式默认形式,和着法名称列表初始化等。
1.对棋盘初始化memcpy(m_byChessBoard,InitChessBoard,90);
2.对棋盘、棋子贴图位置(即棋盘、棋子在程序中实际显示位置)初始化;MemDC.SelectObject(&pOldBmp);//恢复内存Dc原位图
3.对程序辅助部分所用到部分变量初始化
棋盘、棋子样式默认形式,下棋模式默认选择,和着法名称列表初始化等。
初始化部分代码以下:
BOOL CChessDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
//彩色进度条设置
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,用于绘制棋子
//下面这段代码取棋盘图形宽,高
BITMAP BitMap;
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
m_pSE->SetMoveGenerator(m_pMG);//给搜索引擎设定走法产生器
m_pSE->SetEveluator(m_pEvel); //给搜索引擎设定估值关键
m_pSE->SetUserChessColor(m_nUserChessColor);
//设定用户为黑方或红方
m_pSE->SetThinkProgress(&m_progressThink);
//设定进度条
m_MoveChess.nChessID=NOCHESS;//将移动棋子清空
return TRUE; // return TRUE unless you set the focus to a control
}
5.2局面设计
游戏设计中,我们象棋棋盘采取是直接加载位图生成棋盘,图片大小是宽度377*高度417,棋盘上每个格子大小:39*39,图片格式为:BMP。棋子部分是经过加载位图实现,图片大小是:宽度32*高度32,图片格式也是BMP。
我们用一个10*9数组来存放棋盘上信息,数组每个元素存放棋盘上是否有棋子。棋盘初始情形以下所表示(图1是整个棋盘和棋子局面图):
const BYTE InitChessBoard[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},
{NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS},
//楚河 //汉界
{NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS},
{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},
{NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS,NOCHESS},
{R_CAR,R_HORSE,R_ELEPHANT,R_BISHOP,R_KING,R_BISHOP,R_ELEPHANT,R_HORSE,R_CAR}
};
图1 局面设计图
棋子定义:
#define NOCHESS 0 //没有棋子
#define B_KING 1 //黑帅
#define B_CAR 2 //黑车
#define B_HORSE 3 //黑马
#define B_CANON 4 //黑炮
#define B_BISHOP 5 //黑士
#define B_ELEPHANT 6 //黑象
#define B_PAWN 7 //黑卒
#define B_BEGIN B_KING
#define B_END B_PAWN
#define R_KING 8 //红将
#define R_CAR 9 //红车
#define R_HORSE 10//红马
#define R_CANON 11//红炮
#define R_BISHOP 12//红士
#define R_ELEPHANT 13//红相
#define R_PAWN 14//红兵
#define R_BEGIN R_KING
#define R_END R_PAWN
#define IsBlack(x) (x>=B_BEGIN && x<=B_END)//判定某个棋子是不是黑色。
#define IsRed(x) (x>=R_BEGIN && x<=R_END)//判定某个棋子是不是红色。
//判定两个棋子是不是同色
#define IsSameSide(x,y) ((IsBlack(x) && IsBlack(y)) || (IsRed(x) && IsRed(y)))
//棋子位置
typedef struct
{
BYTE x;
BYTE y;
}CHESSMANPOS;
5.3绘图部分
对于绘图部分,关键实现是程序界面绘图所以我们在这里将要完成棋盘、棋子显示走棋起始位置和目标位置提醒框显示。而要实现这些我们必需经过void CChessDlg::OnPaint() 这个函数实现
void CChessDlg::OnPaint()
{
CPaintDC dc(this);
CDC MemDC;
int i,j;
POINT pt;
CBitmap* pOldBmp;
MemDC.CreateCompatibleDC(&dc);
m_BoardBmp.LoadBitmap(IDB_CHESSBOARD);
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(&pOldBmp);//恢复内存Dc原位图
MemDC.DeleteDC(); //释放内存
m_BoardBmp.DeleteObject(); //删除棋盘位图对象
}
5.4鼠标响应部分
鼠标响应部分包含LButtonDown和LButtonUp两个功效,
LButtonDown实现关键功效是拖动棋子在棋盘上移动,她关键性是,假如没有这个功效,将无法走棋,其函数实现经过:
void CChessDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
……
}
LButtonUp这个函数关键实现功效是:拖动棋子完成后放置到拖动后位置,在进行放置过程中,需要使用一个Drop释放函数。函数实现经过:
void CChessDlg::OnLButtonUp(UINT nFlags, CPoint point)
{
……
}
5.5棋子走法
typedef struct
{
short nChessID; //表明是什么棋子
CHESSMANPOS From;//起始位置
CHESSMANPOS To; //走到什么位置
int Score; //走法分数
}CHESSMOVE;
在着法生成器中,采取基础思想就是遍历整个棋盘(一个接一个地查看棋盘上每个位置点),当发觉有目前下棋方棋子时先判定它是何种类型棋子,然后依据其棋子类型而对应地找出其全部正当着法并存入着法队列。
这里谈到“正当着法”包含以下几点:
1、各棋子按其行子规则行子。诸如马跳“日”字、象走“田”字、士在九宫内斜行等等(这里需要尤其注意是卒(兵)行子规则会随其所在位置不一样而发生改变——过河后能够左右平移)。
2、行子不能越出棋盘界限。当然全部棋子全部不能走到棋盘外面,同时一些特定棋子还有自己行棋界限,如将、士不能出九宫,象不能过河。
3、行子半路上不能有其它子阻拦(除了炮需要隔一个子才能打子之外)和行子目标点不能有本方棋子。
4、将帅不能碰面(本程序中只在生成计算机着法时认为将帅碰面是非法,而对用户所走造成将帅碰面着法并不认为其非法,而只是产生败局罢了)。
产生了着法后要将其存入着法队列以供搜索之用,因为搜索会搜索多层,所以在把着法存入着法队列时候还要同时存放该着法所属搜索层数。所以能够将着法队列定义为二维数组,其中第一个数组下标为层数,第二个数组下标为每一层全部着法数。着法生成中各个棋子走法和其它规则代码见MoveGenerator.cpp。
棋子移动由以下函数分别实施:
帅(将):
Void CMoveGenerator::Gen_KingMove()
{
}
士:
红士void CMoveGenerator::Gen_RBishopMove()
{
}
黑士void CMoveGenerator::Gen_BBishopMove()
{
}
象:
void CMoveGenerator::Gen_ElephantMove()
{
}
马:
void CMoveGenerator::Gen_HorseMove()
{
}
车:
void CMoveGenerator::Gen_CarMove()
{
}
炮:
void CMoveGenerator::Gen_CanonMove()
{
}
兵(卒):
红兵void CMoveGenerator::Gen_RPawnMove()
{
}
黑卒 void CMoveGenerator::Gen_BPawnMove()
{
}
5.6搜索算法
我们用一棵象棋树来表示下棋过程:树中每一个结点代表棋盘上一个局面,对每一个局面依据不一样走法又产生不一样局面。
该象棋树包含三种类型结点:奇数层中间结点和根结点,表示轮到红方走棋;偶数层中间结点,表示轮到黑方走棋;叶子结点,表示棋局结束。
结合上面所讲树,若给每个结点全部打一个分值来评价其对应局面,我们经过估值引擎SetEveluator()来实现,过比较该分值大小来判定局面优劣。
void SetEveluator(CEveluation* pEval)
{
m_pEval=pEval;
};
假定甲乙两方下棋,甲胜局面是一个极大值(一个很大正数),那么乙胜局面就是一个极小值(极大值负值),和棋局面则是零值(或是靠近零值)。如此,当轮到甲走棋时她会尽可能地让局面上分值大,相反轮到乙走棋时她会选尽可能地让局面上分值小。反应到博弈树上,即假如假设奇数层表示轮到甲方走棋,偶数层表示轮到乙方走棋。那么因为甲方期望棋盘上分值尽可能大,则在偶数层上会挑选分值最大结点——偶数层结点是甲走完一步棋以后棋盘局面,反应了甲方对棋局形势要求。一样道理,因为乙方期望棋盘上分值尽可能小,那么在奇数层上会选择分值最小结点。这是“最小-最大”(Minimax)基础思想。这么搜索函数在估值函数帮助下能够经过在奇数层选择分值最大(最小)结点,在偶数层选择分值最小(最大)结点方法来搜索以目前局面为根结点、限定搜索层数以内整棵树来取得一个最好着法。下面是“最大-最小”关键代码
int CNegaMaxEngine::NegaMax(int nDepth)
{
int current=-0;
int score;
int Count,i;
BYTE type;
i=IsGameOver(CurPosition,nDepth);//检验棋局是否结束
if(i!=0)
return i;//棋局结束,返回极大/极小值
if(nDepth<=0)//叶子节点取估值
return m_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);
m_pThinkProgress->SetStep(1);
}
for(i=0;i<Count;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];//靠近根部时保留最好走法
}
}
return current;//返回极大值
}
“最小-最大”思想再加上“树裁剪”就是Alpha-Beta搜索算法关键。最基础Alpha-Beta算法代码以下:
int CAlphaBetaEngine::AlphaBeta(int nDepth,int alpha,int beta)
{
int score;
int Count,i;
BYTE type;
i=IsGameOver(CurPosition,nDepth);//检验是否游戏结束
if(i!=0)
return i;//结束,返回估值
//叶子节点取估值
if(nDepth<=0)
return m_pEval->Eveluate(CurPosition,(m_nMaxDepth-nDepth)%2,m_nUserChessColor);
//此函数找出目前局面全部可能走法,然后放进m_pMG ->m_MoveList当中
Count=m_pMG->CreatePossibleMove(CurPosition,nDepth,(m_nMaxDepth-nDepth)%2,m_nUserChessColor);
if(nDepth==m_nMaxDepth)
{
//在根节点设定进度条
m_pThinkProgress->SetRange(0,Count);
m_pThinkProgress->SetStep(1);
}
//对全部可能走法
for(i=0;i<Count;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;//保留极大值
//靠近根节点时保留最好走法
if(nDepth==m_nMaxDepth)
m_cmBestMove=m_pMG->m_MoveList[nDepth][i];
}
if(alpha>=beta)
break;//剪枝,放弃搜索剩下节点
}
return alpha;//返回极大值
}
Alpha-Beta搜索算法是在“最小-最大”基础上引入“树裁剪”思想以期提升效率,它效率将在很大程度上取决于树结构——假如搜索了没多久就发觉能够进行“裁剪”了,那么需要分析工作量将大大降低,效率自然也就大大提升;而假如直至分析了全部可能性以后才能做出“裁剪”操作,那此时“裁剪”也已经失去了它原有价值(因为你已经分析了全部情况,这时Alpha-Beta搜索已和“最小-最大”搜索别无二致了)。所以,要想确保Alpha-Beta搜索算法效率就需要调整树结构,即调整待搜索结点次序,使得“裁剪”能够尽可能早地发生。
能够依据部分已经搜索过结果来调整将要搜索结点次序。因为,通常当一个局面经过搜索被认为很好时,其子结点中往往有部分和它相同局面(如部分无关紧要棋子位置有所不一样)也是很好。由J.Schaeffer所提出“历史启发”(History Heuristic)就是建立在这么一个见解之上。在搜索过程中,每当发觉一个好走法,就给该走法累加一个增量以统计其“历史得分”,一个数次被搜索并认为是好走法“历史得分”就会较高。对于立即搜索结点,根据“历史得分”高低对它们进行排序,确保很好走法(“历史得分”高走法)排在前面,这么Alpha-Beta搜索就能够尽可能早地进行“裁剪”,从而确保了搜索效率。
对于着法排序能够使用多种排序算法,在程序中采取了归并排序。归并排序空间复杂度为O(n),时间复杂度为O(nlog2n),含有较高效率。
历史启发部分关键代码以下:
void CHistoryHeuristic::ResetHistoryTable()
{
memset(m_HistoryTable,10,8100*4);
}
int CHistoryHeuristic::GetHistoryScore(CHESSMOVE *move)
{
int nFrom,nTo;
nFrom=move->From.y*9+move->From.x;//原始位置
nTo=move->To.y*9+move->To.x; //目标位置
return m_HistoryTable[nFrom][nTo];//返回历史得分
}
5.7多线程
因为程序出现了异常:有时对用户方功效完全正确,而对电脑方有些功效却不起作用,这是因为程序在进行搜索时会占用大量CPU时间,所以阻塞了在同一线程内其它指令,使之无法正常工作,所以我们引入了多线程思想另外开一个线程,让各程序分开于多个线程。函数原型:
CWinThread* AfxBeginThread(
AFX_THREADPROC ThinkProc,
LPVOID pParam,
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
该函数开启一个新线程并返回一个指向该新线程对象指针,然后新线程和开启该新线程线程同时运行。该函数第一个参数AFX_THREADPROC ThinkProc指定了线程函数。线程函数内容即为新线程所要实施内容,线程函数实施完成,新线程结束(自动销毁)。
线程函数必需被定义为全局函数,其返回值类型必需是UINT,必需有一个LPVOID类型参数。能够把调用引擎部分搜索函数代码和完成走棋动作代码放入所定义思索线程内,以下:
DWORD WINAPI ThinkProc(LPVOID pParam)
{
CChessDlg* pDlg=(CChessDlg*)pParam;
pDlg->Think();
return 0;
}
然后,只要将原先调搜索函数并完成走棋代码代之以调用AfxBeginThread来开启新线程即可,实现了程序多线程,不能正常工作问题也就随之处理了。
5.8判定胜败
红方帅被吃,或黑方将被吃,游戏结束,判定被吃一方输。具体代码以下:
int CChessDlg::IsGameOver(BYTE position[][9])
{
int i,j;
BOOL RedLive=FALSE,BlackLive=FALSE;
//检验红方九宫是否有帅
for(i=7;i<10;i++)
for(j=3;j<6;j++)
{
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_nUserChessColor==REDCHESS)
{
if(!RedLive)
return 1;
if(!BlackLive)
return -1;
}
else
{
if(!RedLive)
return -1;
if(!BlackLive)
return 1;
}
return 0;
}
5.9游戏退出
玩家假如不想继续游戏,能够经过两种方法退出,一是在选项栏点击“退出”,二是直接点击右上角“关闭”按钮。具体代码以下:
void CChessDlg::OnClose()
{
// TODO: Add your message handler code here and/or call default
if(MessageBox("是否确定退出游戏?","提醒",MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2)==IDNO)
return;
EndDialog(IDOK);
CDialog::OnClose();
}
void CChessDlg::OnExit()
{
OnClose();
}
5.10系统实现
游戏开始时,由执红棋一方先走,双方轮番各走一着,直至分出胜、负、和,对局即终了。双方各走一着,称为一个回合。假如有一方主帅被对方吃了,就算那一方输。
六、总结和心得体会
我们小组(001小组组员:吴鑫,贺景,邹京甫)选择这次游戏设计开发小项目是做一个关键应用于人机对战中国象棋程序,要求这个程序有能输入并使用经典对局棋谱能力,有在游戏过程中自我提升力,和拥有一套完备智能算法。之所以选择这个题目, 一则是对中国象棋喜爱,也期望籍此机会,将大一大二这两年知识积累学以致用,并以此提升自己编程能力。
我们小组在这次游戏设计中选择Visual C++ 6.0作为开发工具,是因为平时接触C++语言比较多,可能更便于表示自己部分编程想法,也因为时间紧促(十八周就要检测我们做游戏),不过因为对MFC不熟悉,我们不得不花很多时间在网上书上学习相关这方面知识,直到现在才大致了解了MFC基础框架原理,但仍不能很得心应手用于实现自己编程想法中。所以程序显得比较浅薄,有些方面还不够严密,
从设计选题、需求分析、总体设计、实现、调试,我们根据自己思绪和网络上部分编程大师们有利思想,经过一次又一次修改,添加,组合,才使得我们程序运行成功。
真是万事开头难啊,我在图像处理方面就碰到了困难,因为之前没编写过这么含有图形界面游戏,先是画面初始化、显示和刷新原理,全部翻看了不少书籍,再实际操作时,很多方面需要处理细节问题在书本上难以找到,我甚至到网上胡乱搜索,随便看到通常和图像显示代码就复制下来自己试验,竟然克服了其中很多问题。
总而言之,这次游戏确实让我们小组每一个人受益不浅,不仅仅学到了计算机人工智能丰富知识,软件开发方法,提升了我分析问题和处理问题能力,并将专业理论知识应用到实践中去,培养了我们独立完成项目计划和实现能力。即使这次游戏设计碰到
展开阅读全文