中国象棋游戏开发设计报告

时间:2024.5.9

0691001282848229 

中国象棋游戏开发设计报告

班级:                           

小组编号:                              

小组成员:               

指导老师:                              


一、开发的目的和意义

面向对象程序设计作为一门软件设计的课程,具有极强的实践性,要求学生具备灵活应用理论知识的能力及面向对象程序设计技能的基础。通过游戏开发,学生能了解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=-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_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的基本框架原理,但仍不能很得心应手的用于实现自己的编程想法中。所以程序显得比较浅薄,有些方面还不够严密,

从设计的选题、需求分析、总体设计、实现、调试,我们按照自己的思路以及网络上一些编程大师们的有利思想,通过一次又一次的修改,添加,组合,才使得我们的程序运行成功。

真是万事开头难啊,我在图像处理方面就遇到了困难,因为之前没编写过这样的具有图形界面的游戏,先是画面的初始化、显示和刷新原理,都翻看了不少书籍,再实际操作时,许多方面需要处理的细节问题在书本上难以找到,我甚至到网上胡乱搜索,随便看到凡是和图像显示的代码就复制下来自己试验,居然克服了其中许多问题。

总而言之,这次的游戏的确让我们小组每一个人受益不浅,不仅仅学到了计算机人工智能的丰富的知识,软件开发的方法,提高了我分析问题和解决问题的能力,并将专业理论知识应用到实践中去,培养了我们独立完成项目规划和实现的能力。虽然这次游戏设计遇到了许多问题,但是我们在解决这些问题的过程中,明白了自身的不足和差距,认识到学习是一个不能放松的漫长的过程,而且自学是一种必须掌握的技能。这是我们在大学的第一次学习与实践,它为我将来的学习中提供了一次很好的锻炼机会 ,是一次宝贵的经验。

更多相关推荐:
游戏程序设计报告

上海xxxxx游戏程序设计题目小鸡历险记学号姓名xxxx院系计算机与信息工程学院专业年级软件工程20xx级20xx年6月3日第一章引言211研究背景2第二章需求分析与系统设计221设计目的222设计要求323实...

FLASH页面游戏课程设计报告

FLASH页面游戏课程设计报告第一章绪论11课题背景意义随着社会的不断发展人们的生活水平在不断地提高网络游戏也越来越多的融入到人们的生活中去游戏的出现带来了网络上的优势扩宽了人们平时玩的传统游戏的界限给人们带来...

游戏设计开题报告

一、选题的目的、意义和实用价值随着人们生活质量的不断提高以及个人电脑和网络的普及,人们的业余生活质量要求也在不断提高,选择一款好玩,精美,画面和音质,品质优良的休闲游戏已经成为一种流行的休闲方式。可以说在人们的…

《游戏网站设计》课程报告

吉林工程技术师范学院网站设计与建设课程设计报告设计题目游戏网站设计专业班级学生姓名学号指导教师20xx年12月信息工程学院信息工程学院网站设计与建设课程设计摘要20世纪以来科技进步和社会生产力的提高使人类得以创...

扫雷游戏 实践设计报告

题目学院职业技术学院专业计算机科学与技术班级计科职112班学号1120xx0363学生姓名毛朋艺20xx年7月2号目录第一章设计目的3第二章需求分析421硬件环境422软件环境423扫雷游戏概述4231扫雷的操...

纸牌游戏设计报告

题目纸牌游戏编号为152张牌正面向上从第二张开始以2为基数是2的倍数的牌翻一次直到最后一张牌然后从第三张牌开始以3为基数是3的倍数的牌翻一次直到最后一张牌直到以52为基数的翻过输出这时输出正面向上的牌有哪些一问...

游戏程序设计报告

游戏程序设计课程报告报告题目太空大战游戏程序设计学院名称信息科学与技术学院专业名称软件工程学生姓名学生学号任课教师报告成绩教务处制20xx年07月04日目录第1章引言211游戏设计背景212游戏规则313主要研...

游戏设计基础实验报告

1游戏引擎游戏引擎是指一些已编写好的可编辑游戏系统或者一些互交式实时图像应用程序的核心组件这些系统为游戏设计者提供各种编写游戏所需的各种工具其目的在于让游戏设计者能容易和快速地做出游戏程式而不用由零开始优秀的游...

课程设计报告。计算24游戏

课程设计报告课程设计报告题目计算24游戏姓名吴青山学号20xx20xx0211专业数学与应用数学班级1123602指导教师郭树蕻职称讲师20xx年06月18日1目录1摘要32问题分析33算法描述44源代码及其分...

手机游戏设计毕业及论文

摘要随着互联网的发展网页游戏在近年发展迅速进入二十一世纪互联网的发展更为迅速据20xx年国家数据统计中国网游市场的利润总额占整个互联网市场的13目前在国家政策和资金支持下中国游戏行业以惊人的速度蓬勃发展游戏用户...

扫雷游戏设计报告

青岛理工大学琴岛学院设计报告课题名称:扫雷游戏设计学院:青岛理工大学琴岛学院专业班级:计算机科学与技术102班学号:学生:指导教师:青岛理工大学琴岛学院教务处20##年7月8日一、调研材料扫雷游戏是Window…

打字游戏的设计报告

打字游戏的设计报告姓名赵香俊班级中北11级net专业软件设计日期20xx1025目录简介目的范围参考资料概述定义121排行版22自由模式23计时赛24生存模式25难度选择26游戏帮助定义233333344445...

游戏设计报告(34篇)