中国象棋游戏开发设计报告
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
中国象棋游戏开发设计报告
班级:
小组编号:
小组成员:
指导老师:
一、开发的目的和意义
面向对象程序设计作为一门软件设计的课程,具有极强的实践性,要求学生具备灵活应用理论知识的能力及面向对象程序设计技能的基础。
通过游戏开发,学生能了解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_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}
};
图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_TRANSP ARENT);
}
//绘制用户正在拖动的棋子
if(m_MoveChess.nChessID!=NOCHESS)
m_Chessman.Draw(&MemDC,m_MoveChess.nChessID-1,m_MoveChess.ptM ovePoint,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=-20000;
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_n
MaxDepth-
nDepth)%2,m_nUser
ChessColor);
//列举当前棋局下一步所有可能的走法
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 的基本框架原理,但仍不能很得心应手的用于实现自己的编程想法中。
所以程序显得比较浅薄,有些方面还不够严密,从设计的选题、需求分析、总体设计、实现、调试,我们按照自己的思路以及网络上一些编程大师们的有利思想,通过一次又一次的修改,添加,组合,才使得我们的程序运行成功。
真是万事开头难啊,我在图像处理方面就遇到了困难,因为之前没编写过这样的具有图形界面的游戏,先是画面的初始化、显示和刷新原理,都翻看了不少书籍,再实际操作时,许多方面需要处理的细节问题在书本上难以找到,我甚至到网上胡乱搜索,随便看到凡是和图像显示的代码就复制下来自己试验,居然克服了其中许多问题。
总而言之,这次的游戏的确让我们小组每一个人受益不浅,不仅仅学到了计算机人
工智能的丰富的知识,软件开发的方法,提高了我分析问题和解决问题的能力,并将专业理论知识应用到实践中去,培养了我们独立完成项目规划和实现的能力。
虽然这次游戏设计遇到了许多问题,但是我们在解决这些问题的过程中,明白了自身的不足和差距,认识到学习是一个不能放松的漫长的过程,而且自学是一种必须掌握的技能。
这是我们在大学的第一次学习与实践,它为我将来的学习中提供了一次很好的锻炼机会,是一次宝贵的经验。