基于MFC的对战象棋设计与实现毕业论文

毕业设计说明书

学生姓名

学院 学 号 计算机科学与技术学院

软件工程

基于MFC的

对战象棋设计与实现 专 业 题 目

指导教师

(姓 名) (专业技术职称/学位) 年 月

毕业论文独创性声明

本人郑重声明:

本论文是我个人在导师指导下进行的研究工作及取得的研究成果。本论文除引文外所有实验、数据和有关材料均是真实的。尽我所知,除了文中特别加以标注和致谢的地方外,论文中不包含其他人已经发表或撰写过的研究成果。其他同志对本研究所做的任何贡献均已在论文中作了明确的说明并表示了谢意。

作者签名:

日 期:

摘 要:中国象棋是我国历史悠久的智力对战游戏,发展至今已有数千年的历史,是中华民族智慧的结晶。然而,传统象棋存在着棋盘单一、棋子收拾繁琐、下棋场地单一、无法及时找到对弈玩家几大弊端。随着计算机技术的发展,电子版象棋游戏的诞生,很好的解决了这些问题。

本设计正是开发这样一款电子象棋游戏,它采用MFC文档视图体系结构和Visual C++开发工具,实现了具有背景棋盘和棋子种类的变换,走棋,悔棋,还原和网络对弈功能。本设计还进行了画面闪烁消除,视觉效果更加人性化,且鼠标操作,简单易用,无须安装,即开即用。

关键词:中国象棋,MFC文档视图,Visual C++,网络对弈,消除闪烁,即开即用

Abstract:Chinese Chess is our long history of intellectual battle game, the development has several thousand years of history, is the crystallization of the wisdom of the Chinese nation. However, the traditional chess there is a single board, the pieces pack cumbersome, chess venues single, not been able to find several major drawbacks of the chess players. With the development of computer technology, the electronic version of the birth of a chess game, a good solution to these problems.

The design is the development of an electronic chess game, which uses MFC document view architecture and Visual C + + development tools, background board and pieces kind of transformation, playing chess, undo, restore, and network chess function. This design also flickering eliminate, visual effects, more human, mouse operation, easy to use, no need to install.

Keywords:Chinese chess, MFC document view, Visual C++, network game, eliminate flicker, instant available

目 录

1 绪论 ..................................................................................................................... 4

1.1 课题背景 .......................................................................................................... 4

1.2 象棋简介 .......................................................................................................... 4

1.3 MFC及相关技术 ............................................................................................ 6

2 概要设计 ............................................................................................................. 9

2.1 设计思路分析 .................................................................................................. 9

2.2 主要功能 .......................................................................................................... 9

2.3 软件信息 ........................................................................................................ 10

2.4 流程图 ............................................................................................................ 10

3 程序详细设计说明 ........................................................................................... 12

3.1 界面设计 ........................................................................................................ 12

3.2 数据结构 ........................................................................................................ 15

3.3 棋子走法 ........................................................................................................ 18

3.3.1 車 ................................................................................................................. 19

3.3.2 馬 ................................................................................................................. 20

3.3.3 相(象) ..................................................................................................... 21

3.3.4 仕(士) ..................................................................................................... 22

3.3.5 帅(将) ..................................................................................................... 23

3.3.6 炮(砲) ..................................................................................................... 24

3.3.7 兵(卒) ..................................................................................................... 25

3.4 功能 ................................................................................................................ 26

3.4.1 走棋 ............................................................................................................. 26

3.4.2 悔棋 ............................................................................................................. 29

3.4.3 还原 ............................................................................................................. 30

3.4.4 认输 ............................................................................................................. 31

3.4.5 摆棋 ............................................................................................................. 31

3.5 网络 ................................................................................................................ 31

3.6 关键技术 ........................................................................................................ 34

3.6.1 遮罩技术 ....................................................................................................... 34

3.6.2 双缓冲技术 ................................................................................................... 34

4 总结和进一步的研究工作 ............................................................................... 35

结束语 ..................................................................................................................... 36

参 考 文 献 ........................................................................................................... 37

致 谢 ..................................................................................................................... 38

1 绪论

1.1 课题背景

随着时代的进步,电子技术日新月异的发展,单纯的户外实体游戏已经无法满足人们的需求,于是电子游戏诞生了,它创造出的虚拟游戏环境,帮助人们摆脱实体的束缚,更增添了游戏的趣味性。计算机的发明,无疑使电子游戏又多了一个新的载体,带动其不断地创新、发展。电脑游戏行业经过二十余年的发展,已成为与音乐、影视等并驾齐驱的全球最重要娱乐行业之一。

时下,与角色扮演类、即时战略类游戏相比,棋牌类游戏这种上手快、游戏时间短的传统游戏方式,仍在广大群众中占有举足轻重的地位。其中方便、快捷、操作简单的棋类游戏,更是占主要位置。

作为中华民族悠久文化的代表之一,中国象棋不仅源远流长,而且基础广泛,作为一项智力运动,中国象棋开始走向世界。为了方便其更好的推广,并且为了摆脱传统棋盘、棋子、场地的束缚,网络版象棋备受青睐,这也是本款象棋游戏开发的意义所在。相信未来几年,象棋这一中国国粹在各个领域将得以推广,产生巨大影响。

1.2 象棋简介

中国象棋历史悠久。《楚辞 • 招魂》中有“蓖蔽象棋,有六簿些;分曹并进,遒相迫些;成枭而牟,呼五白些。”。《说苑》载:雍门子周以琴见 孟尝 君,说:“足下千乘之君也, „„ 燕则斗象棋而舞郑女。”由此可见,远在战国时代,象棋已在贵族阶层中流行开来了。

象棋属于二人对抗性游戏的一种,暗棋阁也是其中一种传统玩法,由于用具简单,趣味性强,成为流行极为广泛的棋艺活动。象棋是我国正式开展的78个体育运动项目之一,为促进该项目在世界范围内的普及和推广,已将“中国象棋”项目名称更改为“象棋”。此外,高材质的象棋也具有收藏价值,如:高档木材、玉石等为材料的象棋。更有文人墨客为象棋谱写了诗篇,使象棋更具有一种文化色彩。

1.2.1 象棋棋盘

棋子活动的场所,叫作“棋盘”。在长方形的平面上,共有九条平行的竖线和十条平行的横线,共有九十个交叉点。

棋盘两端的中间,也就是第四条到第六条竖线之间的正方形部位,有斜交叉线“米”字方格的地方,叫作“九宫”,象征着中军帐,是将和帅的驻扎地。

整个棋盘以“楚河●汉界”分为相等的两部分。为了比赛记录和学习棋谱的统一,现在规则规定:从右至左的九条竖线,用中文数字一至九来表示红方,用阿拉伯数字1至9来表示黑方。己方的棋子始终使用己方的线路编号,无论棋子是否“过河”。

1.2.2 象棋棋子

象棋是一种双方对弈的竞技项目。棋子共有红黑16个,共32个棋子。

棋子种类说明如下表1-1:

将与帅;士与仕;象与相;卒与兵的功能实际上完全相同,仅是为了区分黑红双方。 帅(将)是棋中的首脑,也是战术核心。它只能在本方九宫之内活动,可上下左右沿着横线或者竖线移动一格。帅与将不能在同一直线上直接对面,否则走方判负。这个也就是判定函数ShuaiCanTo()实现的功能的依据。

仕(士)是将(帅)的贴身保镖,主要任务是防守,它也只能在九宫内走动,且只能斜走,不先后左右移动。

相(象)的主要作用也是防守,保护自己的帅(将)。它的走法是每次沿对角线走两格,俗称“象飞田”。相(象)的活动范围限于河界以内的本方阵地,不能过河,实际是7个特殊棋位,且若它走的田字中央有一个棋子,不管是对方还是本方棋子,都不能走,俗称“塞象眼”。

车在象棋中威力最猛,只要起始位置和目的位置中间没有其他棋子,它就可以到达它横竖线所在的任何地方。俗称“一车十子寒”。

炮不吃子时,移动走法与车完全一样。但当吃子时,它和目标棋子中间必须有一个且仅此一个棋子(无论己方或对方棋子)。炮是象棋里面唯一一个可以越子的棋种,威力也很大。

马是先直后斜,即先直着走一格,然后再斜着走一个对角线,俗称“马走日”。马一个可到周围八个点,故有“八面威风”之说。但如果在要去的方向的第一步有别的棋子挡住,马就无法走过去,俗称“蹩马腿”和“塞象眼”类似。

兵(卒)不能后退且只能一步一步走,未过河前,只能向前。过河以后,允许左右移动。虽过河后只能一步一步走,但兵(卒)的威力也大大增强,故有“过河的卒子顶半个车”之说。

1.2.3 术语[8]

(1) “马走日字,象飞田.车走直路,炮翻山.士走斜路护将边.小卒子一去不回还. 车走直路马踏斜,相飞田子炮打隔,卒子过河了不得。”

(2)“马后炮”:泛指炮在马后,以马限制对方将帅的退路,兼以炮向对方叫将。

(3)“天地炮”:一炮从中路牵制对方中士中象,另一炮从底线牵制对方底士底象。

(4)“铁门栓”:以炮镇中路限制对方士象的活动,兼以车或兵(卒)守着对方的将(帅)门。

(5)“夹车炮”:车炮或车双炮前后互相配合的一种战术。

(6)“炮碾丹沙”:车炮在底线成将, 从而杀去障碍 (主要是士象)成杀势。

(7)“车炮抽杀”:泛指炮在车后时,一面跳炮吃子,一面露车叫将,令对方顾此失彼的一种战术。

(8)“海底捞月”:车炮或车兵在对方底线逼使对方将帅离开中路的一种战术。

(9)“两头蛇”:把三路兵和七路兵挺起的阵式。

1.3 MFC及相关技术

MFC:微软基础类(Microsoft Foundation Classes),C++是一种应用程序框架,随微软Visual C++开发工具一起发布。该类库提供一组通用的可重用的类库供开发人员使用,大部分类均从CObject 直接或间接派生,只有少部分类例外。

MFC应用程序的总体结构通常由开发人员从MFC类派生的几个类和一个CWinApp类对象(应用程序对象)组成。MFC 提供了MFC AppWizard 自动生成框架。

Windows 应用程序中,MFC 的主包含文件为"Afxwin.h"。

此外MFC的部分类为MFC/ATL 通用,可以在Win32 应用程序中单独包含并使用这些类。

1.3.1 单文档程序

1.应用类及全局对象(CChess_mApp)

应用类封装了Windows应用的初始化,运行以及终止的全过程。对于每一个基于框架的应用,它必须有一个且只能有一个派生于CWinApp的类对象。这个对象是全局对象,因此它在创建任何窗口前首先被构造。类CWinApp提供了几个关键的可重载的虚成员函数,他们是InitInstance,Run,ExitInstance以及OnIdle等。而且,在程序中可以随时调用全局函数AfxGetApp,以便获得CWinApp类对象的指针。

2.文档类(CChess_mDoc)

文档类实际上是一种数据结构,该类实现了对这种结构的封装以利于管理,通常,它不但包含应用中所需的数据,而且也包含了处理这些数据的方法,另外,文档类还可以为应用提供与其存储的数据相关的服务。

3.视图类(CChess_mView)

该类占有框架窗口的客户区,主要负责显示文档数据,也为文档对象和用户之间提供了用以交互的可视接口,另外,也完成了与文档打印相关的操作,通常,一般的绘制操作都是在该类中完成,因此有时也称视图类窗口为“绘制窗口”。

4.框架类(CMainFrame)

框架类表示应用程序的主框架窗口,其主要作用是响应标准的窗口消息,不过,它通常先将消息按照一定的次序传递给视图类以及文档类等其他命令处理类,另外,它还为视图类提供可视化的边框,同时也包括标题栏,一些标准的窗口组件等。

5.“关于”对话框类(CAboutDlg)

该类封装了用于显示软件版本,版权等相关信息的“关于”对话框,通常不需要对它进行任何的编程。而只需要使用对话框资源编辑器对对话框模板进行简单的编辑即可。

1.3.2 GDI绘图

GDI是Graphics Device Interface的缩写,含义是图形设备接口,它的主要任务是负责系统与绘图程序之间的信息交换,处理所有Windows程序的图形输出。

在Windows操作系统下,绝大多数具备图形的的应用程序都离不开GDI,他们利用GDI所提供的众多函数可以方便地在屏幕,打印机及其他输出设备上输出图形、文本等。GDI的出现时程序员无需关心硬件设备及设备驱动,就可以将应用程序的输出转化为硬件设备上的输出。这实现了程序开发者与硬件设备的隔离,大大方便了开发工作。

想要在屏幕或者其他输出设备上输出图形或者文字,就必须先获得一个称为设备描述表(DC:Device Context)的对象的句柄,以它为参考,调用各种GDI函数来实现各种文字或图形的输出。

设备描述表是GDI内部保存数据的一种数据结构,此结构中的属性内容与特定的输出

设备(显示器、打印机等)相关,属性定义了GDI函数的工作细节。属性包括文字的颜色及x坐标和y坐标映射到窗体显示区域的方式等。

一旦获得设备描述表的句柄,系统就是使用默认的属性值填充设备描述表结构。

如果有必要,可以使用一些GDI函数获得和改变设备描述表中的属性值。

绘图结束后必须释放设备描述表句柄。

GDI函数大致可分类为:

设备上下文函数(如GetDC、CreateDC、DeleteDC)、 画线函数(如LineTo、Polyline、Arc)、填充画图函数(如Ellipse、FillRect、Pie)、画图属性函数(如SetBkColor、SetBkMode、SetTextColor)、文本、字体函数(如TextOut、GetFontData)、位图函数(如SetPixel、BitBlt、StretchBlt)、坐标函数(如DPtoLP、LPtoDP、ScreenToClient、ClientToScreen)、映射函数(如SetMapMode、SetWindowExtEx、SetViewportExtEx)、元文件函数(如PlayMetaFile、SetWinMetaFileBits)、区域函数(如FillRgn、FrameRgn、InvertRgn)、路径函数(如BeginPath、EndPath、StrokeAndFillPath)、裁剪函数(如SelectClipRgn、SelectClipPath)等。

1.3.3 消息机制

MFC传输的消息有三种类型:命令消息,控件通知和窗口消息。

命令消息一般与用户请求有关,当用户单击一个菜单项时,命令消息产生,并发送给能处理这个消息的类对象(如编辑文本等)。

控件消息在某些重要事件发送时,由控件窗口发送给父窗体处理,如打开一个对话框。 窗口消息(Window Message)一般与窗口的内部运作有关,如创建窗口消息W M PA I N T和绘制窗口W M C R E AT E等。通常,消息是从系统发送到窗口,或从窗口发送到窗口。每个窗口使用窗口进程来处理发送给它的消息。系统或应用程序有两种传输消息的方法:发送消息或寄送消息。

鼠标和键盘消息一般是寄送消息,而所有其他消息通常都是发送消息。发送消息时,直接调用窗口的窗口进程,通信是即时的,直到窗口进程为调用函数返回一个结果时,应用程序才能继续。寄送消息时,把消息发送到拥有那个窗口的应用程序的消息队列中。当应用程序空闲时,它会搜索消息队列,并处理优先级较高的消息,然后从队列中删除它,并把处理结果发送给既定窗口。所以通信可能延迟。在消息队列中,寄送的消息接受特殊的鼠标和键盘处理。通常,应该尽量发送一个消息,除非想把动作延迟到所有鼠标和键盘消息被处理之后。

2 概要设计 2.1 设计思路分析

本程序采用MVC模式,将棋子信息与显示分离,统一由control控制。棋盘用9*10的数组map存储(0~13表示各个棋子,14表为空),存储用于判断当前位置为何棋子或者空。32个棋子由一个统一的棋子型CChess数组存储,统一管理显示。每个棋子包含了自身的type,name,及其在棋盘中的位置x,y。

棋子走法是利用鼠标的消息处理函数,获得棋子种类,当前坐标和下一步坐标,共五个信息,再利用规定的CanGoTo()函数判断能否到达。若能到达,则把当前棋子的坐标改为下一步坐标,并把棋盘中当前位置的map改为空,即表示无棋子。如果下一步坐标的位置上有棋子,则将该棋子的type改为15,这样绘图时就不会显示该棋子的位图。

因为棋子位图周围有白色边框,通过“遮罩技术”可以消除边框,实现棋子透明覆在棋盘上,更具美感。

利用“内存DC双缓冲技术”消除闪烁,将棋盘和棋子信息一次性显示,并实现棋子黏在鼠标上,动态移动。

网络部分,继承MFC中的CAsyncSocket和CSocket进行开发。Socket编程分为两种:面向连接协议网络通信编程和面向无连接的网络通信编程。本设计采用连接协议的Socket编程模型,保证网络上传输的数据及时、正确的到达。

2.2 主要功能

通过计算机的鼠标键盘操作操作界面,必须按照象棋的规则进行对弈,如果在对弈过程中,一方出招错误,电脑会予以简单提示,并无视错误操作。对弈过程中只能按照电脑预订顺序,即红先黑后的顺序依次出招,指导一方胜出,电脑显示哪方胜出为止。

1) 可以采用鼠标操作进行对弈;

2) 可以实现走棋、悔棋、还原、认输功能; 3) 可以满足两人局域网内对弈; 4) 可以在任意时间重新开始游戏;

5) 可以进行简单错误判断,并直接返回到前一步棋子状态; 6) 可以进行背景棋盘和棋子种类的变换; 7) 可以实现自由设置棋谱(怪棋); 8) 可以实现棋子黏在鼠标上移动,增加美感

2.3 软件信息

1) 软件名称:基于MFC的局域网内对战象棋设计与实现 2) 开发平台:Microsoft Visual C++ 6.0 3) 用户:象棋爱好者

4) 本设计在各个方面均具有可行性,方便易用,无需安装即可使用

2.4 流程图

(1)游戏交互图,如图2-1所示

图2-1 游戏交互图

(2)程序流程图

首先,选择是否建立主机,否则为客户机。若建立主机,则建立一个端口,用于和客户机的交互连接,然后等待知道有客户机连上。客户机建立,则选择主机的ip地址和端口。游戏双方先后点击开始,开始游戏。红方、黑方在到自己状态时,可以移动自己的棋子,悔棋,还原,认输等等。走棋,点击鼠标左键选中棋子,移动,放到想到达的地方,系统调用CanGoTo函数,判断是否可到,若可到,则棋子放到目的地,否则返回棋子原来的地方能够。走棋成功后,发送消息给对方,对方接收消息后,进行相应的操作。进行若干步之后,若帅或者将被吃掉,则判定胜负。

具体流程,见图2-2程序流程图。

N

图2-2程序流程图

3 程序详细设计说明 3.1 界面设计

本程序界面简单明了,包括棋盘显示,listbox,开始、悔棋、还原、认输四个按钮,还有用于聊天的edit和发送按钮。菜单栏包括文件,控制,背景,帮助。

其中listbox 是记录每步棋子走棋的信息和聊天信息,也是实现悔棋功能的一个重要存储器。它是一个栈,可以记录,删除,提取每步棋子的信息。悔棋操作可以从中得到相应的数据来实现悔棋功能。文件内有新游戏、摆棋操作,控制内有悔棋、还原、认输操作,背景内有棋盘和棋子的切换操作。

主界面,如图3-1所示

图3-1 主界面

3.1.1 棋盘、棋子的位图载入与切换

(1)棋盘、棋子的位图载入

本设计实现了棋盘和棋子的自由无限制转换,采用了外部图片载入的方式,加载的位图,棋盘棋子一起加载,没有自己繁琐的自绘棋盘棋子过程。

本设计有6种棋盘,3种棋子可以搭配。若程序在开始时就全部加载,会浪费大量内存,因为游戏过程中只需要一副棋盘棋子,且不利于更多棋盘和棋子种类的更新,也不利于后期维护。故本程序采用外部图片载入的方式,载入棋盘棋子,每次切换调用SetBoardandPiece(CString strBoard , CString strPiece)重新对棋盘棋子的位图进行赋值。

strBoard,strPiece代表棋盘,棋子的存放文件夹相对物理地址。

HBITMAP CChess_mView::LoadFileBmp(CString filePath) {

Return(HBITMAP)LoadImage(AfxGetInstanceHandle(),filePath,IMAGE_BITMAP,0,0,LR_LOADFROMFILE);

}

void CChess_mView::SetBoardandPiece(CString strBoard, CString strPiece ) { //0红兵 1红袍 2红车 3红马 4红相 5红士 6红帅 7黑兵8 黑袍 9黑车10黑马11黑象12黑士13黑将 大于13不是棋子

ChessList[13] = LoadFileBmp(strPiece + "BK.BMP"); ChessList[12] = LoadFileBmp(strPiece + "BA.BMP"); ChessList[11] = LoadFileBmp(strPiece + "BB.BMP"); ChessList[10] = LoadFileBmp(strPiece + "BN.BMP"); ChessList[9] = LoadFileBmp(strPiece + "BR.BMP"); ChessList[8] = LoadFileBmp(strPiece + "BC.BMP"); ChessList[7] = LoadFileBmp(strPiece + "BP.BMP"); ChessList[6] = LoadFileBmp(strPiece + "RK.BMP"); ChessList[5] = LoadFileBmp(strPiece + "RA.BMP"); ChessList[4] = LoadFileBmp(strPiece + "RB.BMP"); ChessList[3] = LoadFileBmp(strPiece + "RN.BMP"); ChessList[2] = LoadFileBmp(strPiece + "RR.BMP"); ChessList[1] = LoadFileBmp(strPiece + "RC.BMP"); ChessList[0] = LoadFileBmp(strPiece + "RP.BMP"); ChessList[14] = LoadFileBmp(strPiece + "SEL.BMP");//光标位图 ChessList[15] = LoadFileBmp(strBoard); ChessList[16] = LoadFileBmp(strPiece + "mask.bmp"); //用于遮罩技术(见关键技术) }

(2)棋盘、棋子的切换

当点击菜单栏背景的棋盘或者棋子,可以选择不同的棋盘和棋子。减少玩家的视觉审美疲劳。每按一个按钮,其实是改变了存放棋盘或棋子的函数SetBoardandPiece(CString

strBoard , CString strPiece)中的strBoard 或strPiece,即棋谱棋子的存放文件夹相对物理地址,完成了棋盘棋子的种类切换,并用Invalidate(FALSE)及时更新显示。

例如点击棋盘的水滴棋盘

void CChess_mView::OnDrop() { m_strBoard = "res\\images\\drops.bmp"; SetBoardandPiece("res\\images\\drops.bmp",m_strPiece); Invalidate(FALSE); }

运行图,如图3-1所示:

图3-2 棋盘、棋子的切换

3.1.2 光标生成

双方局域网内对战,消息传递过来,执行操作了,但是我不能马上知道对方动了哪个棋子;可能已经动了,但是我不知道,浪费时间。

光标的生成,可以起到提醒对方我已经走过棋了,也利于在我方还没有出招之前始终知道对方的上一步是怎么走的,只有在我方出招之后对方的操作痕迹才可能被销毁,这样的设计增强了棋谱的可读性,也减轻了玩家的记忆负担。

光标在棋盘载入的时候就一起载入了ChessList[14] = LoadFileBmp(strPiece + "SEL.BMP");

在选中棋子时,即鼠标左键按下后,鼠标move的时候就把光标位图贴到选中子的周围,鼠标up再把位图贴到up的位置上。

具体代码见鼠标的消息处理函数,OnLButtonDown(),OnMouseMove(),OnLButtonUp() 运行结果,如图3-2所示:

图3-3 光标生成

3.2 数据结构

本设计主要包含CChess,CChess_mView,Cmanager,CNet,CNetControl四个类。 CNet,CNetControl两个类用于网络部分,后面网络功能部分再详解。 (1)CChess类是棋子类,存储了棋子的相关属性信息。 class CChess { public:

int type;//0红兵 1红袍 2红车 3红马 4红相 5红士 6红帅 7黑兵8 黑袍 9黑车

10黑马11黑象12黑士13黑将 大于13不是棋子

};

Type即棋子的类别用于判定属于哪种棋子。name棋子的名字,例如“炮”。x,y是棋子在逻辑坐标轴上的坐标。

(2)Cmanager类是棋子控制类,是游戏内核。 class CManager { public:

CChess nChessinfo[32];//记录32个棋子的信息 int map[9][10];//记录棋盘上的信息 int PlayerAc;//用来判断是哪个棋手 void InitData();//数据初始化

int m_State;// 游戏状态 -1、游戏等待中,等待开始

0、游戏中 红方走棋 1、

CString name; int x; int y;

游戏中 黑方走棋

信息

int MarryTop; //记录游戏步数,为了还原时能够判断 int count;

//记录listbox的步数 包括游戏步奏和聊天记录

int top; //记录游戏步数

int Marry[500][8]; //记录 前一个的标号,type,x,y 后一个的标号,type,x,y 共八个

public:

bool CanGoTo(int Old_Point_x,int Old_Point_y,int Now_Point_x,int Now_Point_y,int

chesstype);//判断棋子能否到达,是每个棋子的走法判断

bool

ShuaiCanGoTo(int

Old_Point_x,int

Old_Point_y,int

Now_Point_x,int

Now_Point_y,int chesstype); //是关于将帅问题的能否到达判断,将帅不共线

};

(3)CChess_mView 类是程序的显示类,也包含少部分的棋子控制操作功能和很多消息响应函数,由于篇幅问题没有全部显示。

class CChess_mView : public CView {

protected: CChess_mView();

DECLARE_DYNCREATE(CChess_mView)

// Attributes public:

CChess_mDoc* GetDocument();

CNetControl *m_pControl; //网络控制显示 int choosed; //记录前一个棋子是第几个棋子 int OldType; //记录前一个棋子的类型 int OldX; int OldY;

//记录前一个棋子的横坐标 //记录前一个棋子的纵坐标

///用于右键功能 int Rchoosed; int ROldX; int ROldY; //变量定义

HBITMAP ChessList[17];

//位图数组

//记录当前一个棋子是第几个棋子 //记录前一个棋子的横坐标 //记录前一个棋子的纵坐标

CString m_strPiece;//棋子根目录 CString m_strBoard;//棋盘根目录

int m_nBoardIndex,m_nPieceIndex; //背景设置选择 //定义画图用CDC

CDC Background;//背景句柄

CDC Buffer;//缓冲句柄 用于双缓冲

CListBox m_listbox; //用于记录每步棋子走棋的信息和聊天信息 CButton m_ok; //开始按钮 CButton m_goback; //悔棋按钮 CButton m_huanyuan; //还原按钮 CButton m_renshu; //认输按钮 CButton m_send;//聊天发送按钮 CEdit m_edit;//用于聊天的edit

int down_move;//用于鼠标按下时的控制 int move_up; //用于鼠标弹上时的控制

int goback_huanyuan; //用于只有在有悔棋额情况下才能还原的控制

// Operations public:

void OnOk();//开始 void OnGoBack();//悔棋 void OnHuanYuan();//还原 void OnRenShu();//认输 void OnSend();//发送聊天信息 //函数定义 void InitData(); void NewGame();

//初始化 //新游戏

void TextOutTop();//提示现在出牌 void PrintAll(); //把界面存储于buffer中 void PrintAllMsg(); //用于网络刷新和整体刷新 void PrintAllDMsg();//用于网络动棋子的时候整体刷新 HBITMAP LoadFileBmp(CString filePath); //载入位图

void SetBoardandPiece(CString strBoard,CString strPiece);//给棋盘棋子赋值

// Overrides

// ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CChess_mView)

virtual void OnDraw(CDC* pDC); // overridden to draw this view virtual BOOL PreCreateWindow(CREATESTRUCT& cs); protected:

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo); virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo); virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo); //}}AFX_VIRTUAL

// Implementation public:

virtual ~CChess_mView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif protected:

//{{AFX_MSG(CChess_mView)

„„„„„.(省略) };

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

3.3 棋子走法

棋子走棋规则在“1.2.2 象棋棋子”已有介绍,这里不再累述。

这里主要介绍各个棋子的程序具体实现。具体见CanGoTo(int Old_Point_x,int Old_Point_y,int

Now_Point_x,int

Now_Point_y,int

chesstype)和

ShuaiCanGoTo(int

Old_Point_x,int Old_Point_y,int Now_Point_x,int Now_Point_y,int chesstype)函数。

首先,不管是什么棋子,都不能吃自己一方的棋子。

if (map[Old_Point_x][Old_Point_y]/7==map[Now_Point_x][Now_Point_y]/7)

return false;

3.3.1 車

车只要中间没有棋子阻碍,可以到达它横竖线所在的任何地方。所以只需判断始末位置为端点的线段上的棋子个数,即判断map[][]不为空的个数。若只有一个,则表示目标位置没有棋子,可以直达。若有两个,且目标位置不为空,则表示吃子,可达。

//车

if (9==chesstype||2==chesstype)

{

//同一行

if (Old_Point_y==Now_Point_y)

{

int maxcol=(Old_Point_x>Now_Point_x)?Old_Point_x:Now_Point_x;

int mincol=(Old_Point_x>Now_Point_x)?Now_Point_x:Old_Point_x;

int n=0;

//判断移动前后位置之间有无其他子

for (int i=mincol;i

{

if(14!=map[i][Old_Point_y])

n++;

}

if(1==n)

return true;

else if((2==n)&&(14!=map[Now_Point_x][Old_Point_y]))

return true;

else return false;

}

//同一列

else

if (Old_Point_x==Now_Point_x)

{

int maxrow=(Old_Point_y>Now_Point_y)?Old_Point_y:Now_Point_y;

int minrow=(Old_Point_y>Now_Point_y)?Now_Point_y:Old_Point_y;

int n=0;

for(int i=minrow;i

{

if(14!=map[Old_Point_x][i])

n++;

}

if(1==n)

return true;

else if((2==n)&&(14!=map[Now_Point_x][Now_Point_y]))

return true;

else return false;

} }

3.3.2 馬

马走日,所以要判断始末位置的X和Y的差值的乘积是不是2或者-2。若是,则判断有没有蹩马腿,即同侧的第一个位置map[][]必须为空。

//马

if (3==chesstype||10==chesstype)

{

int row=Now_Point_x-Old_Point_x;

int col=Now_Point_y-Old_Point_y;

//日字的范围

if((2!=row*col)&&(-2!=row*col))

return false;

//是否有同侧的子堵塞

if (-2==row)

{

if (14==map[Old_Point_x-1][Old_Point_y])

return true;

else

return false;

}

else

if (2==row)

{

if(14==map[Old_Point_x+1][Old_Point_y])

return true;

else

return false;

}

else

if (-2==col)

{

if(14==map[Old_Point_x][Old_Point_y-1])

return true;

else

return false;

}

else

if (2==col)

{

if(14==map[Old_Point_x][Old_Point_y+1])

return true;

} return false; } else return false;

3.3.3 相(象)

相和象活动范围不同,所以要分开判断。但相(象)的走法判断和马类似,都是先判断差值,来确定田字范围,再判断有没有塞象眼,即田字中间有没有棋子。

//黑象 if (11==chesstype) { if((Now_Point_y>=0)&&(Now_Point_y

} { int row=Now_Point_x-Old_Point_x; int col=Now_Point_y-Old_Point_y; //田字范围 if ((-2==row)&&(-2==col)&&(14==map[Old_Point_x-1][Old_Point_y-1])) { return true; } else if ((-2==row)&&(2==col)&&(14==map[Old_Point_x-1][Old_Point_y+1])) { return true; } else if((2==row)&&(-2==col)&&(14==map[Old_Point_x+1][Old_Point_y-1])) { return true; } else if((2==row)&&(2==col)&&(14==map[Old_Point_x+1][Old_Point_y+1])) { return true; } else return false; }

3.3.4 仕(士)

仕(士)只能在各自九宫内走动,所以红黑士要分开判断范围,然后在判断棋子是否斜走,而不是直走,即始末位置的X,Y的差值绝对值要都是1。

//黑士 if (chesstype==12) { if ((Now_Point_x>=3)&&(Now_Point_x=0)&&(Now_Point_y

{

int row=abs(Now_Point_y-Old_Point_y);

int col=abs(Now_Point_x-Old_Point_x);

if ((0==row-col)&&(row==1))

{

return true;

}

else

return false;

}

else return false;

}

//红士

else if(chesstype==5)

{

if ((Now_Point_x>=3)&&(Now_Point_x=7)&&(Now_Point_y

{

int row=abs(Now_Point_y-Old_Point_y);

int col=abs(Now_Point_x-Old_Point_x);

if ((1==col)&&(row==1))

{

return true;

}

else

return false;

}

else return false;

}

3.3.5 帅(将)

帅或者将只能在自己的九宫内左右上下走一格。判断时,只需先判定在九宫内,再判断始末位置的X,Y的差值绝对值是不是一个为1,一个为0。

//黑将

if (13==chesstype)

{

if ((Now_Point_x>=3)&&(Now_Point_x=0)&&(Now_Point_y

{

int row=abs(Now_Point_y-Old_Point_y);

int col=abs(Now_Point_x-Old_Point_x);

if((0==row&&1==col)||(1==row&&0==col))

return true;

else return false;

}

else return false;

}

//红帅

else if(6==chesstype)

{

if ((Now_Point_x>=3)&&(Now_Point_x=7)&&(Now_Point_y

{

int row=abs(Now_Point_y-Old_Point_y);

} int col=abs(Now_Point_x-Old_Point_x); if((0==row&&1==col)||(1==row&&0==col)) return true; else return false; } else return false;

3.3.6 炮(砲)

炮不仅可以和车一样直走多步,还可以跳子吃子。炮首先判断是否在同一列或者同一行,然后判断当前位置和目标位置中间是否存在其他棋子,若中间无子且目标位置处无子,则表示炮在行走且可达;若中间有一个棋子且目标位置有子(不能为本方棋子,前面已经做过判断),则表示炮在吃子且可达。

//炮

if (1==chesstype||8==chesstype)

{

//同一列

if (Old_Point_x==Now_Point_x)

{

int maxcol=(Old_Point_y>Now_Point_y)?Old_Point_y:Now_Point_y;

int mincol=(Old_Point_y>Now_Point_y)?Now_Point_y:Old_Point_y;

int n=0;

//判断两位置之间是否有子间隔

for (int i=mincol;i

{

if(14!=map[Old_Point_x][i])

n++;

}

//无子,可以放置

if(1==n)

return true;

//炮吃子

else if((3==n)&&(14!=map[Now_Point_x][Now_Point_y]))

return true;

else return false;

}

//同一行

else

if (Old_Point_y==Now_Point_y)

{

int maxrow=(Old_Point_x>Now_Point_x)?Old_Point_x:Now_Point_x;

int minrow=(Old_Point_x>Now_Point_x)?Now_Point_x:Old_Point_x;

} { if(14!=map[i][Old_Point_y]) n++; } if(1==n) return true; else if((3==n)&&(14!=map[Now_Point_x][Now_Point_y])) return true; else return false; } else return false;

3.3.7 兵(卒)

兵(卒)不能后退且只能一步一步前进,未过河前,只能向前。过河以后,允许左右移动。所以首先除了要分黑卒和红兵以外,也要分未过河与过河。如黑卒,未过河前,X前后不变,Y比之前多1,可达。过河后,黑卒可以X前后不变,Y比之前多1,也可以前后Y不变,X比之前多1或者少1,均可达。

//黑卒

if (chesstype==7)

{

//未过河

if ((Old_Point_y>=3)&&(Old_Point_y

{

if ((Old_Point_x==Now_Point_x)&&(Now_Point_y-Old_Point_y==1))

{

return true;

}

else return false;

}

//已过河

else if ((Old_Point_y>=5)&&(Old_Point_y

{

if ((Old_Point_x==Now_Point_x)&&(Now_Point_y-Old_Point_y==1))

{

return true;

}

else if ((abs(Now_Point_x-Old_Point_x)==1)&&(Old_Point_y==Now_Point_y))

{

return true;

}

} else //红兵 if (chesstype==0) { //未过河 if ((Old_Point_y=5)) { if ((Old_Point_x==Now_Point_x)&&(Old_Point_y-Now_Point_y==1)) { return true; } else return false; } //已过河 else if ((Old_Point_y=0)) { if ((Old_Point_x==Now_Point_x)&&((Old_Point_y-Now_Point_y)==1)) { return true; } else if((abs(Old_Point_x-Now_Point_x)==1)&&(0==Old_Point_y-Now_Point_y)) { return true; } else return false;

}

}

3.4 功能

3.4.1 走棋

走棋功能包括三个部分,第一是选棋子,第二是移动棋子,第三是选择目的地。选棋子只要就是鼠标左键按下事件下进行,按下时要判断这里是否有棋子,即棋盘MAP[][]数组是否是小于14(棋子的type最大为13),否则不响应我选棋子的操作。移动棋子到达目的地时,要判断棋子是否能到达这里,即调用CanGoTo()和ShuaiCanGoTo()函数,若可以则让该棋子落在此处。

(1) LButtonDown操作

LButtonDown操作,除了要判断选棋子之外,还要得工作就是保存当前棋子的信息,因为鼠标移动该地方是没有棋子显示的,但是若走棋失败,还是要范围到选棋子之前的状

态。

void CChess_mView::OnLButtonDown(UINT nFlags, CPoint point)

{

if (manager.m_State==manager.PlayerAc)

{

if ((point.x=BOARD_EDGE)

&&(point.y=BOARD_EDGE))

{

OldX=(point.x-BOARD_EDGE)/CHESS_SIZE;

OldY=(point.y-BOARD_EDGE)/CHESS_SIZE;

if ((manager.m_State==0&&manager.map[OldX][OldY]

&&manager.map[OldX][OldY]>=0)||(manager.m_State==1

&&(manager.map[OldX][OldY]6)))

{

if (manager.m_State==0)//红方

{

if (manager.map[OldX][OldY]

{

choosed=manager.Search(point.x,point.y);

OldType=manager.nChessinfo[choosed].type;

manager.nChessinfo[choosed].type=15;

down_move=1;

}

}

if(manager.m_State==1) //黑方

{

if(manager.map[OldX][OldY]6) {

choosed=manager.Search(point.x,point.y);

OldType=manager.nChessinfo[choosed].type;

manager.nChessinfo[choosed].type=15;

//manager.map[OldX][OldY]=14;

down_move=1;

}

}

}

}

}

CView::OnLButtonDown(nFlags, point);

}

(2) LButtonMove操作

为了美观,LButtonMove操作需要做的就是选中的棋子跟着鼠标移动,主要要用到双

缓冲技术,这个在关键技术那部分做详细介绍。

(3) LButtonUp操作

LButtonUp操作要做的就是判断能否到达,保存数据,判断胜负的工作。 void CChess_mView::OnLButtonUp(UINT nFlags, CPoint point)

{

manager.nChessinfo[choosed].type=OldType;

if (1==move_up)

{

if(ShuaiCanGoTo(OldX,OldY,(point.x-BOARD_EDGE)/CHESS_SIZE,

(point.y-BOARD_EDGE)/CHESS_SIZE,OldType)

&&CanGoTo(OldX,OldY,(point.x-BOARD_EDGE)/CHESS_SIZE,

(point.y-BOARD_EDGE)/CHESS_SIZE,OldType))

{

Marry[top][0]=choosed;

Marry[top][1]=OldType;

Marry[top][2]=OldX;

Marry[top][3]=OldY;

Marry[top][4]=Search(point.x,point.y);

Marry[top][5]=nChessinfo[Search(point.x,point.y)].type;

Marry[top][6]=int (point.x-BOARD_EDGE)/CHESS_SIZE;

Marry[top][7]=int (point.y-BOARD_EDGE)/CHESS_SIZE;

if (Marry[top][4]

{

//如果这里有子,那么把他的类型赋为15 不显示

nChessinfo[Search(point.x,point.y)].type=15;

}

//把棋谱的map[OldX][OldY] 变空

map[OldX][OldY]=14;

nChessinfo[choosed].x=Marry[top][6];

nChessinfo[choosed].y=Marry[top][7];

map[nChessinfo[choosed].x][nChessinfo[choosed].y]=nChessinfo[choosed].type; PrintAll();

//记录数组Marry的顶部是多少,为"还原"时控制

manager.MarryTop=manager.top;

//listbox 显示

CString str;

str.Format("%d:%s(%d,%d)(%d,%d)",top++,nChessinfo[choosed].name,

Marry[top][2],Marry[top][3],Marry[top][6],Marry[top][7]);

m_listbox.AddString(str);

m_listbox.SetCurSel(count++);

if (nChessinfo[15].type!=6)

{

MessageBox("黑方获胜!!!");

m_State=-1;

}

else if (nChessinfo[31].type!=13)

{

MessageBox("红方获胜!!!");

m_State=-1;

}

if (m_State!=-1)

{

m_State=(m_State+1)%2;

}

char st[10];

sprintf(st,"ZQ%d%d%d%d",OldX,OldY,Marry[MarryTop][6],Marry[MarryTop][7]);

m_pControl->SendMsg(st);//传送本方操作给对方

}

else

{

MessageBeep(-1); //显示声音

nChessinfo[choosed].x=OldX;

nChessinfo[choosed].y=OldY;

map[nChessinfo[choosed].x][nChessinfo[choosed].y]=nChessinfo[choosed].type;

PrintAll();

}

}

ReleaseDC(pDC);

OldX=-1;

OldY=-1;

down_move=0;

move_up=0;

CView::OnLButtonUp(nFlags, point);

}

3.4.2 悔棋

悔棋功能靠的就是二维数组Marry记录的每步棋子的走棋步骤。Marry[500][8]记录 前一个的标号,type,x,y 后一个的标号,type,x,y,即记录走棋前的前一点的信息,和后一点的信息。这样只要用当前的Marry[][]的数据去重新赋值,即完成悔棋。

void CChess_mView::OnGoBack()

{

if (manager.m_State==(manager.PlayerAc+1)%2||goback_huanyuan==1)

{

if (manager.top>1)

{

count--;

} } top--; nChessinfo[Marry[top][0]].type=Marry[top][1]; nChessinfo[Marry[top][0]].x=Marry[top][2]; nChessinfo[Marry[op][0]].y=Marry[top][3]; map[Marry[top][2]][Marry[mtop][3]]=Marry[top][1]; nChessinfo[Marry[top][4]].type=Marry[top][5]; nChessinfo[Marry[top][4]].x=Marry[top][6]; nChessinfo[Marry[top][4]].y=Marry[top][7]; map[Marry[top][6]][Marry[top][7]]=mMarry[top][5]; PrintAllMsg();//重绘位图 CString str; int i=manager.count; m_listbox.GetText(i,str); while(!(strncmp(str,"你",2)&&strncmp(str,"他",2))) { i--; m_listbox.GetText(i,str); } m_listbox.DeleteString(i); manager.m_State=(manager.m_State+1)%2; m_pControl->SendMsg("HQ"); goback_huanyuan=1; } else { MessageBeep(-1); }

3.4.3 还原

还原功能也是借助Marry保存的记录,把第一个标号的属性换成第二个标号的数据,第一个标号的type设为无。

void CChess_mView::OnHuanYuan()

{

if (goback_huanyuan==1)

{

if (manager.top

{

nChessinfo[Marry[top][4]].type=15;

nChessinfo[Marry[top][4]].x=Marry[top][2];

nChessinfo[Marry[top][4]].y=Marry[top][3];

map[Marry[top][2]][Marry[top][3]]=14;

nChessinfo[Marry[top][0]].type=Marry[top][1];

nChessinfo[Marry[top][0]].x=Marry[top][6];

nChessinfo[Marry[top][0]].y=Marry[top][7];

map[Marry[top][6]][Marry[top][7]]=Marry[top][1];

PrintAllMsg();

CString str;

str.Format("%d:%s(%d,%d)(%d,%d)",top++,nChessinfo[Marry[top][0]].name,

Marry[top][2],Marry[top][3],Marry[top][6],Marry[top][7]);

m_listbox.AddString(str);

m_listbox.SetCurSel(manager.count++);

manager.m_State=(manager.m_State+1)%2;

m_pControl->SendMsg("HY");

}

else

{

MessageBeep(-1);

goback_huanyuan=0;

}

}

}

3.4.4 认输

认输比较简单,只需把游戏状态设为-1;

3.4.5 摆棋

你们双方其中有一个人很厉害吗?你想来点怪棋玩玩吗?那么你就右击鼠标。你可以摆棋,可以修改、删除任意棋子(除了将帅,不能修改,不能删除,因只此一个)。

3.5 网络

Socket的使用是基于CS系统的(客户机、服务器),客户机和服务器的Socket使用不太一样,分别有以下几个步骤。

服务器:

(1) 创建服务器Socket(Create);

(2) 服务器Socket进行信息绑定(Bind),并开始监听连接(Listen);

(3) 接受来自客户端的连接请求(Accept),创建接受进程;

(4) 开始交换数据(Send,Receive);

(5) 关闭Socket(Close)。

客户机:

(1) 创建客户机Socket(Create);

(2) 连接服务器(Connect),如果接收,创建接受进程;

(3) 开始交换数据(Send,Receive)

(4) 关闭Socket(Close)。

对于Socket对象,本设计对它进行继承,并重写函数,改造使它拥有更多功能。在Socket原有的基础上加入了virtual OnAccept(int nErrorCode)和virtual void OnReceive(int nErrorCode)的重写,同时加入了NetControl的指针*m_Nc,并用*m_Nc初始化CNet。改写后的函数其实是对NetControl进行操作,这样的好处就是直接把信息传入到了NetControl进行具体的响应。

class CNet : public CSocket

{

public:

};

class CNetControl

{

public:

CNetControl(CChess_mView *p);//用视图类指针作为参数初始化,为的就是可以在CNet(); CNet(CNetControl *m_Ncc); virtual ~CNet(); CNetControl *m_Nc; void OnAccept(int nErrorCode); void OnReceive(int nErrorCode); 网络端控制显示

void SendMsg(char const *pMsg); //发送操作消息 void OnSorc();//开始选择做服务器还是客户机 void OnSet();//设定服务器 void OnOpen();//判断Socket是否已经建立,是否连接到服务器 void FetchMsg(CNet *pRequest);//消息处理函数 CNet * ConnectServer();//连接服务器

}; CNetControl(); virtual ~CNetControl(); UINT m_port; //端口 LPCTSTR m_server;//服务器名称 CNet *m_pSocket;//请求连接 CNet *m_pListening;//监听Socket CChess_mView *m_pView;//绘图指针 bool m_Ready[2];//用户双方判断开始准备的

其中,其他的几个函数建立时都是大体一致,而消息处理函数要配合着消息发送函数一起。其中消息处理函数FetchMsg(CNet *pRequest)是网络编程的重点。这个函数要包括几乎所有的功能,走棋,悔棋,判断,聊天功能等等。

void CNetControl::FetchMsg(CNet *pRequest)

{

//接收数据

char Msg[10000],tempMsg[1000];

int ByteCount;

int End=0;

CStringArray *temp=new CStringArray;

strcpy(Msg,"");

do

{

strcpy(tempMsg,"");

ByteCount=pRequest->Receive(tempMsg,1000);

if(ByteCount>1000||ByteCount

{

MessageBox(GetActiveWindow(),"接受网络信息发生错误","警告信息",MB_OK);

return ;

}

else

if(ByteCount0)

{

End=1;

}

tempMsg[ByteCount]=0;

strcat(Msg,tempMsg);

}

while(End==0);

//处理相应的函数 代码省略

} if(!strncmp(Msg,"ZQ",2)) 走棋 if(!strncmp(Msg,"HQ",2)) 悔棋 if(!strncmp(Msg,"HY",2)) 还原 if(!strncmp(Msg,"ok",2)) 开始 if(!strncmp(Msg,"RH",2)) 认输 if(!strncmp(Msg,"LT",2)) 聊天 if(!strncmp(Msg,"XG",2)) 右击鼠标 设置任何棋谱 修改棋子 if(!strncmp(Msg,"SC",2)) 右击鼠标 设置任何棋谱 删除棋子

3.6 关键技术

3.6.1 遮罩技术

所谓“遮罩”就是一张和要透明绘制的位图相应的黑白双色的位图。原位图的透明部分即是“遮罩”的黑色部分,而不透明的部分在“遮罩”中是白色,我们可以先将“遮罩”和棋盘的位图做“MERGEPAINT”操作,然后再和要透明绘制的位图做“SRCAND”操作。这样就可以获得没有边框的棋子,圆润的附在棋子上了。

ChessAll.SelectObject(ChessList[16]);

Buffer.BitBlt(point.x-CHESS_SIZE/2-2,point.y-CHESS_SIZE/2-3,CHESS_SIZE,

CHESS_SIZE,&ChessAll,0,0,MERGEPAINT);

ChessAll.SelectObject(ChessList[OldType]);

Buffer.BitBlt(point.x-CHESS_SIZE/2,point.y-CHESS_SIZE/2,CHESS_SIZE,

CHESS_SIZE,&ChessAll,0,0,SRCAND);

pDC->BitBlt(0,0,BOARD_WIDTH,BOARD_HEIGHT,&Buffer,0,0,SRCCOPY);

ReleaseDC(pDC);

说明:MERGEPAINT使用布尔OR操作符合并特征与源位图

SRCCOPY 拷贝源位图到目标位图。

3.6.2 双缓冲技术

当我们想要选中的棋子黏在鼠标上面跟着移动时,我们发现界面会闪烁。我们知道GDI绘图过程大多放在OnDraw函数中,OnDraw在进行屏幕显示时是由OnPaint进行调用的。当窗口由于任何原因需要重绘时,比如MouseMove事件,总是先用背景色将显示区清除,然后才调用OnPaint,而背景色一般与重绘的内容相差很大,这样在很短的时间内背景色和重绘内容会反复出现,窗口看起来就像在闪。不过若将背景刷设置为NULL,这样就没有背景和重绘图形的交替,即没有闪烁。但是,这样重绘图形会反复叠加,效果很差。因为重绘时没有用背景色对绘图区域进行刷新,而仅是在原来图形上叠加上了新的图形。

利用双缓冲技术,在内存DC中事先把要显示的图画好,然后再一次性将内存中画好的图贴到窗口中。由于这种方法没有直接窗口上绘图、擦写,就是一整张一整张的贴上去

的,没有闪烁。

void CChess_mView::OnMouseMove(UINT nFlags, CPoint point)

{

if (1==down_move)

{

CDC *pDC=GetDC();

PrintAll();

ChessAll.SelectObject(ChessList[14]);//光标

Buffer.BitBlt(OldX*CHESS_SIZE+BOARD_EDGE,OldY*CHESS_SIZE+BOARD_EDGE,CHESS_SIZE,CHESS_SIZE,&ChessAll,0,0,SRCAND);

ChessAll.SelectObject(ChessList[16]);//遮罩

Buffer.BitBlt(point.x-CHESS_SIZE/2-2,point.y-CHESS_SIZE/2-3,CHESS_SIZE,

CHESS_SIZE,&ChessAll,0,0,MERGEPAINT);

ChessAll.SelectObject(ChessList[OldType]);

Buffer.BitBlt(point.x-CHESS_SIZE/2,point.y-CHESS_SIZE/2,CHESS_SIZE,

CHESS_SIZE,&ChessAll,0,0,SRCAND);//利用Buffer提前在内存中把想要绘制的图形画好,然后一次性的让pDC贴上去。

pDC->BitBlt(0,0,BOARD_WIDTH,BOARD_HEIGHT,&Buffer,0,0,SRCCOPY);

ReleaseDC(pDC);

}

CView::OnMouseMove(nFlags, point);

}

4 总结和进一步的研究工作

本设计相对完善的完成了局域网内的对战象棋的功能,界面友好,操作简单。象棋的设计当然还可以进一步的完善。进一步的工作可以从以下几个方面展开:

(1)人工智能

局域网双人对战还是需要有伙伴才能开始游戏,一个人的时候难免会有伤感,若能既有双人版,又有单机版,肯定就更好了。而且现在人工智能这么火热,人工智能的发展将给人类社会带来翻天覆地的变化,适应潮流。

(2)网络平台游戏

局域网内游戏还只是两个人之间的游戏,若想做的更好,可以往网络平台游戏方向发展。类似于腾讯的QQ游戏平台,多人网上对战。

结束语

经过了数月的学习与钻研,本人终于完成了此款电子版象棋游戏的设计与实现。在这之前,虽然也学过MFC相关技术,但对于MFC开发还是一个很模糊的概念。以往只注重C++理论而不重视实践,从来没有活用所学语言来完成一个实在的程序。一直以来,可视化程序设计是件可望而不可及的事情。感谢老师让本人了解了MFC,并且开始用MFC学着做设计。在此过程中虽然遇到了很多困难,但是经过充足的准备均能得到顺利的解决。

在程序设计之前本人做了大量的准备工作,收集了很多关于象棋和MFC资料。对于如何进行概要设计,如何设计数据结构,如何做程序设计,如何实现功能尤其是实现网络功能等等一系列问题,都通过查阅资料,请教老师得以解决。在前期工作做好后,动手操作时,由于不经常设计的原因,遇到很多从未见过的问题,但最终通过本人的努力,细心修改,克服了困难。

令本人感到收获最大的并不是能够完成整个程序的设计与实现,而是学会了如何去设计程序,如何在遇到问题时细心解决。总之一句话,通过毕业设计本人学会了如何进行程序设计。这一些也为本人以后的学习和工作做了良好的好铺垫。

参 考 文 献

[1] 候俊杰. 深入浅出 MFC. 武汉: 华中科技大学出版社, 2001.

[2] 孙鑫. VC++深入详解(修订版). 北京: 电子工业出版社, 2012.

[3] 荣钦科技, 刘晓华. Visual C++游戏编程基础. 北京: 电子工业出版社, 2005.

[4] 严蔚敏, 吴伟民. 数据结构. 北京: 清华大学出版社, 2008.

[5] 雷超然, 葛垚. Visual C++ MFC 棋牌类游戏编程实例. 北京: 人民邮电出版社, 2008.

[5] 宋国强, 韩冰. 象棋基本战术. 北京: 北京体育大学出版社, 2011.

[7] 王嘉良, 张志强. 中国象棋初级教程. 北京: 经济管理, 2012.

[8] 曹全忠. 象棋入门一本通. 河南: 河南科学技术出版社, 2008.

[9] 曹霖. 大师教你学象棋. 吉林: 吉林科学技术出版社, 2011.

致 谢

本论文的工作是在指导老师张勇老师的悉心指导和帮助下完成的。本论文从选题、需求分析、程序的设计与实现到最后的审阅定稿都得到张老师的悉心指导。值此论文完成之际,谨向张老师表示最衷心的感谢!同时也要感谢学院里的领导和老师们以及其他帮助过我的老师,是你们对我无私的帮助以及为我创造良好的学习环境,才使得本文能顺利完成。

最后,感谢参加本人论文评阅和答辩的各位老师,谢谢!

毕业设计说明书

学生姓名

学院 学 号 计算机科学与技术学院

软件工程

基于MFC的

对战象棋设计与实现 专 业 题 目

指导教师

(姓 名) (专业技术职称/学位) 年 月

毕业论文独创性声明

本人郑重声明:

本论文是我个人在导师指导下进行的研究工作及取得的研究成果。本论文除引文外所有实验、数据和有关材料均是真实的。尽我所知,除了文中特别加以标注和致谢的地方外,论文中不包含其他人已经发表或撰写过的研究成果。其他同志对本研究所做的任何贡献均已在论文中作了明确的说明并表示了谢意。

作者签名:

日 期:

摘 要:中国象棋是我国历史悠久的智力对战游戏,发展至今已有数千年的历史,是中华民族智慧的结晶。然而,传统象棋存在着棋盘单一、棋子收拾繁琐、下棋场地单一、无法及时找到对弈玩家几大弊端。随着计算机技术的发展,电子版象棋游戏的诞生,很好的解决了这些问题。

本设计正是开发这样一款电子象棋游戏,它采用MFC文档视图体系结构和Visual C++开发工具,实现了具有背景棋盘和棋子种类的变换,走棋,悔棋,还原和网络对弈功能。本设计还进行了画面闪烁消除,视觉效果更加人性化,且鼠标操作,简单易用,无须安装,即开即用。

关键词:中国象棋,MFC文档视图,Visual C++,网络对弈,消除闪烁,即开即用

Abstract:Chinese Chess is our long history of intellectual battle game, the development has several thousand years of history, is the crystallization of the wisdom of the Chinese nation. However, the traditional chess there is a single board, the pieces pack cumbersome, chess venues single, not been able to find several major drawbacks of the chess players. With the development of computer technology, the electronic version of the birth of a chess game, a good solution to these problems.

The design is the development of an electronic chess game, which uses MFC document view architecture and Visual C + + development tools, background board and pieces kind of transformation, playing chess, undo, restore, and network chess function. This design also flickering eliminate, visual effects, more human, mouse operation, easy to use, no need to install.

Keywords:Chinese chess, MFC document view, Visual C++, network game, eliminate flicker, instant available

目 录

1 绪论 ..................................................................................................................... 4

1.1 课题背景 .......................................................................................................... 4

1.2 象棋简介 .......................................................................................................... 4

1.3 MFC及相关技术 ............................................................................................ 6

2 概要设计 ............................................................................................................. 9

2.1 设计思路分析 .................................................................................................. 9

2.2 主要功能 .......................................................................................................... 9

2.3 软件信息 ........................................................................................................ 10

2.4 流程图 ............................................................................................................ 10

3 程序详细设计说明 ........................................................................................... 12

3.1 界面设计 ........................................................................................................ 12

3.2 数据结构 ........................................................................................................ 15

3.3 棋子走法 ........................................................................................................ 18

3.3.1 車 ................................................................................................................. 19

3.3.2 馬 ................................................................................................................. 20

3.3.3 相(象) ..................................................................................................... 21

3.3.4 仕(士) ..................................................................................................... 22

3.3.5 帅(将) ..................................................................................................... 23

3.3.6 炮(砲) ..................................................................................................... 24

3.3.7 兵(卒) ..................................................................................................... 25

3.4 功能 ................................................................................................................ 26

3.4.1 走棋 ............................................................................................................. 26

3.4.2 悔棋 ............................................................................................................. 29

3.4.3 还原 ............................................................................................................. 30

3.4.4 认输 ............................................................................................................. 31

3.4.5 摆棋 ............................................................................................................. 31

3.5 网络 ................................................................................................................ 31

3.6 关键技术 ........................................................................................................ 34

3.6.1 遮罩技术 ....................................................................................................... 34

3.6.2 双缓冲技术 ................................................................................................... 34

4 总结和进一步的研究工作 ............................................................................... 35

结束语 ..................................................................................................................... 36

参 考 文 献 ........................................................................................................... 37

致 谢 ..................................................................................................................... 38

1 绪论

1.1 课题背景

随着时代的进步,电子技术日新月异的发展,单纯的户外实体游戏已经无法满足人们的需求,于是电子游戏诞生了,它创造出的虚拟游戏环境,帮助人们摆脱实体的束缚,更增添了游戏的趣味性。计算机的发明,无疑使电子游戏又多了一个新的载体,带动其不断地创新、发展。电脑游戏行业经过二十余年的发展,已成为与音乐、影视等并驾齐驱的全球最重要娱乐行业之一。

时下,与角色扮演类、即时战略类游戏相比,棋牌类游戏这种上手快、游戏时间短的传统游戏方式,仍在广大群众中占有举足轻重的地位。其中方便、快捷、操作简单的棋类游戏,更是占主要位置。

作为中华民族悠久文化的代表之一,中国象棋不仅源远流长,而且基础广泛,作为一项智力运动,中国象棋开始走向世界。为了方便其更好的推广,并且为了摆脱传统棋盘、棋子、场地的束缚,网络版象棋备受青睐,这也是本款象棋游戏开发的意义所在。相信未来几年,象棋这一中国国粹在各个领域将得以推广,产生巨大影响。

1.2 象棋简介

中国象棋历史悠久。《楚辞 • 招魂》中有“蓖蔽象棋,有六簿些;分曹并进,遒相迫些;成枭而牟,呼五白些。”。《说苑》载:雍门子周以琴见 孟尝 君,说:“足下千乘之君也, „„ 燕则斗象棋而舞郑女。”由此可见,远在战国时代,象棋已在贵族阶层中流行开来了。

象棋属于二人对抗性游戏的一种,暗棋阁也是其中一种传统玩法,由于用具简单,趣味性强,成为流行极为广泛的棋艺活动。象棋是我国正式开展的78个体育运动项目之一,为促进该项目在世界范围内的普及和推广,已将“中国象棋”项目名称更改为“象棋”。此外,高材质的象棋也具有收藏价值,如:高档木材、玉石等为材料的象棋。更有文人墨客为象棋谱写了诗篇,使象棋更具有一种文化色彩。

1.2.1 象棋棋盘

棋子活动的场所,叫作“棋盘”。在长方形的平面上,共有九条平行的竖线和十条平行的横线,共有九十个交叉点。

棋盘两端的中间,也就是第四条到第六条竖线之间的正方形部位,有斜交叉线“米”字方格的地方,叫作“九宫”,象征着中军帐,是将和帅的驻扎地。

整个棋盘以“楚河●汉界”分为相等的两部分。为了比赛记录和学习棋谱的统一,现在规则规定:从右至左的九条竖线,用中文数字一至九来表示红方,用阿拉伯数字1至9来表示黑方。己方的棋子始终使用己方的线路编号,无论棋子是否“过河”。

1.2.2 象棋棋子

象棋是一种双方对弈的竞技项目。棋子共有红黑16个,共32个棋子。

棋子种类说明如下表1-1:

将与帅;士与仕;象与相;卒与兵的功能实际上完全相同,仅是为了区分黑红双方。 帅(将)是棋中的首脑,也是战术核心。它只能在本方九宫之内活动,可上下左右沿着横线或者竖线移动一格。帅与将不能在同一直线上直接对面,否则走方判负。这个也就是判定函数ShuaiCanTo()实现的功能的依据。

仕(士)是将(帅)的贴身保镖,主要任务是防守,它也只能在九宫内走动,且只能斜走,不先后左右移动。

相(象)的主要作用也是防守,保护自己的帅(将)。它的走法是每次沿对角线走两格,俗称“象飞田”。相(象)的活动范围限于河界以内的本方阵地,不能过河,实际是7个特殊棋位,且若它走的田字中央有一个棋子,不管是对方还是本方棋子,都不能走,俗称“塞象眼”。

车在象棋中威力最猛,只要起始位置和目的位置中间没有其他棋子,它就可以到达它横竖线所在的任何地方。俗称“一车十子寒”。

炮不吃子时,移动走法与车完全一样。但当吃子时,它和目标棋子中间必须有一个且仅此一个棋子(无论己方或对方棋子)。炮是象棋里面唯一一个可以越子的棋种,威力也很大。

马是先直后斜,即先直着走一格,然后再斜着走一个对角线,俗称“马走日”。马一个可到周围八个点,故有“八面威风”之说。但如果在要去的方向的第一步有别的棋子挡住,马就无法走过去,俗称“蹩马腿”和“塞象眼”类似。

兵(卒)不能后退且只能一步一步走,未过河前,只能向前。过河以后,允许左右移动。虽过河后只能一步一步走,但兵(卒)的威力也大大增强,故有“过河的卒子顶半个车”之说。

1.2.3 术语[8]

(1) “马走日字,象飞田.车走直路,炮翻山.士走斜路护将边.小卒子一去不回还. 车走直路马踏斜,相飞田子炮打隔,卒子过河了不得。”

(2)“马后炮”:泛指炮在马后,以马限制对方将帅的退路,兼以炮向对方叫将。

(3)“天地炮”:一炮从中路牵制对方中士中象,另一炮从底线牵制对方底士底象。

(4)“铁门栓”:以炮镇中路限制对方士象的活动,兼以车或兵(卒)守着对方的将(帅)门。

(5)“夹车炮”:车炮或车双炮前后互相配合的一种战术。

(6)“炮碾丹沙”:车炮在底线成将, 从而杀去障碍 (主要是士象)成杀势。

(7)“车炮抽杀”:泛指炮在车后时,一面跳炮吃子,一面露车叫将,令对方顾此失彼的一种战术。

(8)“海底捞月”:车炮或车兵在对方底线逼使对方将帅离开中路的一种战术。

(9)“两头蛇”:把三路兵和七路兵挺起的阵式。

1.3 MFC及相关技术

MFC:微软基础类(Microsoft Foundation Classes),C++是一种应用程序框架,随微软Visual C++开发工具一起发布。该类库提供一组通用的可重用的类库供开发人员使用,大部分类均从CObject 直接或间接派生,只有少部分类例外。

MFC应用程序的总体结构通常由开发人员从MFC类派生的几个类和一个CWinApp类对象(应用程序对象)组成。MFC 提供了MFC AppWizard 自动生成框架。

Windows 应用程序中,MFC 的主包含文件为"Afxwin.h"。

此外MFC的部分类为MFC/ATL 通用,可以在Win32 应用程序中单独包含并使用这些类。

1.3.1 单文档程序

1.应用类及全局对象(CChess_mApp)

应用类封装了Windows应用的初始化,运行以及终止的全过程。对于每一个基于框架的应用,它必须有一个且只能有一个派生于CWinApp的类对象。这个对象是全局对象,因此它在创建任何窗口前首先被构造。类CWinApp提供了几个关键的可重载的虚成员函数,他们是InitInstance,Run,ExitInstance以及OnIdle等。而且,在程序中可以随时调用全局函数AfxGetApp,以便获得CWinApp类对象的指针。

2.文档类(CChess_mDoc)

文档类实际上是一种数据结构,该类实现了对这种结构的封装以利于管理,通常,它不但包含应用中所需的数据,而且也包含了处理这些数据的方法,另外,文档类还可以为应用提供与其存储的数据相关的服务。

3.视图类(CChess_mView)

该类占有框架窗口的客户区,主要负责显示文档数据,也为文档对象和用户之间提供了用以交互的可视接口,另外,也完成了与文档打印相关的操作,通常,一般的绘制操作都是在该类中完成,因此有时也称视图类窗口为“绘制窗口”。

4.框架类(CMainFrame)

框架类表示应用程序的主框架窗口,其主要作用是响应标准的窗口消息,不过,它通常先将消息按照一定的次序传递给视图类以及文档类等其他命令处理类,另外,它还为视图类提供可视化的边框,同时也包括标题栏,一些标准的窗口组件等。

5.“关于”对话框类(CAboutDlg)

该类封装了用于显示软件版本,版权等相关信息的“关于”对话框,通常不需要对它进行任何的编程。而只需要使用对话框资源编辑器对对话框模板进行简单的编辑即可。

1.3.2 GDI绘图

GDI是Graphics Device Interface的缩写,含义是图形设备接口,它的主要任务是负责系统与绘图程序之间的信息交换,处理所有Windows程序的图形输出。

在Windows操作系统下,绝大多数具备图形的的应用程序都离不开GDI,他们利用GDI所提供的众多函数可以方便地在屏幕,打印机及其他输出设备上输出图形、文本等。GDI的出现时程序员无需关心硬件设备及设备驱动,就可以将应用程序的输出转化为硬件设备上的输出。这实现了程序开发者与硬件设备的隔离,大大方便了开发工作。

想要在屏幕或者其他输出设备上输出图形或者文字,就必须先获得一个称为设备描述表(DC:Device Context)的对象的句柄,以它为参考,调用各种GDI函数来实现各种文字或图形的输出。

设备描述表是GDI内部保存数据的一种数据结构,此结构中的属性内容与特定的输出

设备(显示器、打印机等)相关,属性定义了GDI函数的工作细节。属性包括文字的颜色及x坐标和y坐标映射到窗体显示区域的方式等。

一旦获得设备描述表的句柄,系统就是使用默认的属性值填充设备描述表结构。

如果有必要,可以使用一些GDI函数获得和改变设备描述表中的属性值。

绘图结束后必须释放设备描述表句柄。

GDI函数大致可分类为:

设备上下文函数(如GetDC、CreateDC、DeleteDC)、 画线函数(如LineTo、Polyline、Arc)、填充画图函数(如Ellipse、FillRect、Pie)、画图属性函数(如SetBkColor、SetBkMode、SetTextColor)、文本、字体函数(如TextOut、GetFontData)、位图函数(如SetPixel、BitBlt、StretchBlt)、坐标函数(如DPtoLP、LPtoDP、ScreenToClient、ClientToScreen)、映射函数(如SetMapMode、SetWindowExtEx、SetViewportExtEx)、元文件函数(如PlayMetaFile、SetWinMetaFileBits)、区域函数(如FillRgn、FrameRgn、InvertRgn)、路径函数(如BeginPath、EndPath、StrokeAndFillPath)、裁剪函数(如SelectClipRgn、SelectClipPath)等。

1.3.3 消息机制

MFC传输的消息有三种类型:命令消息,控件通知和窗口消息。

命令消息一般与用户请求有关,当用户单击一个菜单项时,命令消息产生,并发送给能处理这个消息的类对象(如编辑文本等)。

控件消息在某些重要事件发送时,由控件窗口发送给父窗体处理,如打开一个对话框。 窗口消息(Window Message)一般与窗口的内部运作有关,如创建窗口消息W M PA I N T和绘制窗口W M C R E AT E等。通常,消息是从系统发送到窗口,或从窗口发送到窗口。每个窗口使用窗口进程来处理发送给它的消息。系统或应用程序有两种传输消息的方法:发送消息或寄送消息。

鼠标和键盘消息一般是寄送消息,而所有其他消息通常都是发送消息。发送消息时,直接调用窗口的窗口进程,通信是即时的,直到窗口进程为调用函数返回一个结果时,应用程序才能继续。寄送消息时,把消息发送到拥有那个窗口的应用程序的消息队列中。当应用程序空闲时,它会搜索消息队列,并处理优先级较高的消息,然后从队列中删除它,并把处理结果发送给既定窗口。所以通信可能延迟。在消息队列中,寄送的消息接受特殊的鼠标和键盘处理。通常,应该尽量发送一个消息,除非想把动作延迟到所有鼠标和键盘消息被处理之后。

2 概要设计 2.1 设计思路分析

本程序采用MVC模式,将棋子信息与显示分离,统一由control控制。棋盘用9*10的数组map存储(0~13表示各个棋子,14表为空),存储用于判断当前位置为何棋子或者空。32个棋子由一个统一的棋子型CChess数组存储,统一管理显示。每个棋子包含了自身的type,name,及其在棋盘中的位置x,y。

棋子走法是利用鼠标的消息处理函数,获得棋子种类,当前坐标和下一步坐标,共五个信息,再利用规定的CanGoTo()函数判断能否到达。若能到达,则把当前棋子的坐标改为下一步坐标,并把棋盘中当前位置的map改为空,即表示无棋子。如果下一步坐标的位置上有棋子,则将该棋子的type改为15,这样绘图时就不会显示该棋子的位图。

因为棋子位图周围有白色边框,通过“遮罩技术”可以消除边框,实现棋子透明覆在棋盘上,更具美感。

利用“内存DC双缓冲技术”消除闪烁,将棋盘和棋子信息一次性显示,并实现棋子黏在鼠标上,动态移动。

网络部分,继承MFC中的CAsyncSocket和CSocket进行开发。Socket编程分为两种:面向连接协议网络通信编程和面向无连接的网络通信编程。本设计采用连接协议的Socket编程模型,保证网络上传输的数据及时、正确的到达。

2.2 主要功能

通过计算机的鼠标键盘操作操作界面,必须按照象棋的规则进行对弈,如果在对弈过程中,一方出招错误,电脑会予以简单提示,并无视错误操作。对弈过程中只能按照电脑预订顺序,即红先黑后的顺序依次出招,指导一方胜出,电脑显示哪方胜出为止。

1) 可以采用鼠标操作进行对弈;

2) 可以实现走棋、悔棋、还原、认输功能; 3) 可以满足两人局域网内对弈; 4) 可以在任意时间重新开始游戏;

5) 可以进行简单错误判断,并直接返回到前一步棋子状态; 6) 可以进行背景棋盘和棋子种类的变换; 7) 可以实现自由设置棋谱(怪棋); 8) 可以实现棋子黏在鼠标上移动,增加美感

2.3 软件信息

1) 软件名称:基于MFC的局域网内对战象棋设计与实现 2) 开发平台:Microsoft Visual C++ 6.0 3) 用户:象棋爱好者

4) 本设计在各个方面均具有可行性,方便易用,无需安装即可使用

2.4 流程图

(1)游戏交互图,如图2-1所示

图2-1 游戏交互图

(2)程序流程图

首先,选择是否建立主机,否则为客户机。若建立主机,则建立一个端口,用于和客户机的交互连接,然后等待知道有客户机连上。客户机建立,则选择主机的ip地址和端口。游戏双方先后点击开始,开始游戏。红方、黑方在到自己状态时,可以移动自己的棋子,悔棋,还原,认输等等。走棋,点击鼠标左键选中棋子,移动,放到想到达的地方,系统调用CanGoTo函数,判断是否可到,若可到,则棋子放到目的地,否则返回棋子原来的地方能够。走棋成功后,发送消息给对方,对方接收消息后,进行相应的操作。进行若干步之后,若帅或者将被吃掉,则判定胜负。

具体流程,见图2-2程序流程图。

N

图2-2程序流程图

3 程序详细设计说明 3.1 界面设计

本程序界面简单明了,包括棋盘显示,listbox,开始、悔棋、还原、认输四个按钮,还有用于聊天的edit和发送按钮。菜单栏包括文件,控制,背景,帮助。

其中listbox 是记录每步棋子走棋的信息和聊天信息,也是实现悔棋功能的一个重要存储器。它是一个栈,可以记录,删除,提取每步棋子的信息。悔棋操作可以从中得到相应的数据来实现悔棋功能。文件内有新游戏、摆棋操作,控制内有悔棋、还原、认输操作,背景内有棋盘和棋子的切换操作。

主界面,如图3-1所示

图3-1 主界面

3.1.1 棋盘、棋子的位图载入与切换

(1)棋盘、棋子的位图载入

本设计实现了棋盘和棋子的自由无限制转换,采用了外部图片载入的方式,加载的位图,棋盘棋子一起加载,没有自己繁琐的自绘棋盘棋子过程。

本设计有6种棋盘,3种棋子可以搭配。若程序在开始时就全部加载,会浪费大量内存,因为游戏过程中只需要一副棋盘棋子,且不利于更多棋盘和棋子种类的更新,也不利于后期维护。故本程序采用外部图片载入的方式,载入棋盘棋子,每次切换调用SetBoardandPiece(CString strBoard , CString strPiece)重新对棋盘棋子的位图进行赋值。

strBoard,strPiece代表棋盘,棋子的存放文件夹相对物理地址。

HBITMAP CChess_mView::LoadFileBmp(CString filePath) {

Return(HBITMAP)LoadImage(AfxGetInstanceHandle(),filePath,IMAGE_BITMAP,0,0,LR_LOADFROMFILE);

}

void CChess_mView::SetBoardandPiece(CString strBoard, CString strPiece ) { //0红兵 1红袍 2红车 3红马 4红相 5红士 6红帅 7黑兵8 黑袍 9黑车10黑马11黑象12黑士13黑将 大于13不是棋子

ChessList[13] = LoadFileBmp(strPiece + "BK.BMP"); ChessList[12] = LoadFileBmp(strPiece + "BA.BMP"); ChessList[11] = LoadFileBmp(strPiece + "BB.BMP"); ChessList[10] = LoadFileBmp(strPiece + "BN.BMP"); ChessList[9] = LoadFileBmp(strPiece + "BR.BMP"); ChessList[8] = LoadFileBmp(strPiece + "BC.BMP"); ChessList[7] = LoadFileBmp(strPiece + "BP.BMP"); ChessList[6] = LoadFileBmp(strPiece + "RK.BMP"); ChessList[5] = LoadFileBmp(strPiece + "RA.BMP"); ChessList[4] = LoadFileBmp(strPiece + "RB.BMP"); ChessList[3] = LoadFileBmp(strPiece + "RN.BMP"); ChessList[2] = LoadFileBmp(strPiece + "RR.BMP"); ChessList[1] = LoadFileBmp(strPiece + "RC.BMP"); ChessList[0] = LoadFileBmp(strPiece + "RP.BMP"); ChessList[14] = LoadFileBmp(strPiece + "SEL.BMP");//光标位图 ChessList[15] = LoadFileBmp(strBoard); ChessList[16] = LoadFileBmp(strPiece + "mask.bmp"); //用于遮罩技术(见关键技术) }

(2)棋盘、棋子的切换

当点击菜单栏背景的棋盘或者棋子,可以选择不同的棋盘和棋子。减少玩家的视觉审美疲劳。每按一个按钮,其实是改变了存放棋盘或棋子的函数SetBoardandPiece(CString

strBoard , CString strPiece)中的strBoard 或strPiece,即棋谱棋子的存放文件夹相对物理地址,完成了棋盘棋子的种类切换,并用Invalidate(FALSE)及时更新显示。

例如点击棋盘的水滴棋盘

void CChess_mView::OnDrop() { m_strBoard = "res\\images\\drops.bmp"; SetBoardandPiece("res\\images\\drops.bmp",m_strPiece); Invalidate(FALSE); }

运行图,如图3-1所示:

图3-2 棋盘、棋子的切换

3.1.2 光标生成

双方局域网内对战,消息传递过来,执行操作了,但是我不能马上知道对方动了哪个棋子;可能已经动了,但是我不知道,浪费时间。

光标的生成,可以起到提醒对方我已经走过棋了,也利于在我方还没有出招之前始终知道对方的上一步是怎么走的,只有在我方出招之后对方的操作痕迹才可能被销毁,这样的设计增强了棋谱的可读性,也减轻了玩家的记忆负担。

光标在棋盘载入的时候就一起载入了ChessList[14] = LoadFileBmp(strPiece + "SEL.BMP");

在选中棋子时,即鼠标左键按下后,鼠标move的时候就把光标位图贴到选中子的周围,鼠标up再把位图贴到up的位置上。

具体代码见鼠标的消息处理函数,OnLButtonDown(),OnMouseMove(),OnLButtonUp() 运行结果,如图3-2所示:

图3-3 光标生成

3.2 数据结构

本设计主要包含CChess,CChess_mView,Cmanager,CNet,CNetControl四个类。 CNet,CNetControl两个类用于网络部分,后面网络功能部分再详解。 (1)CChess类是棋子类,存储了棋子的相关属性信息。 class CChess { public:

int type;//0红兵 1红袍 2红车 3红马 4红相 5红士 6红帅 7黑兵8 黑袍 9黑车

10黑马11黑象12黑士13黑将 大于13不是棋子

};

Type即棋子的类别用于判定属于哪种棋子。name棋子的名字,例如“炮”。x,y是棋子在逻辑坐标轴上的坐标。

(2)Cmanager类是棋子控制类,是游戏内核。 class CManager { public:

CChess nChessinfo[32];//记录32个棋子的信息 int map[9][10];//记录棋盘上的信息 int PlayerAc;//用来判断是哪个棋手 void InitData();//数据初始化

int m_State;// 游戏状态 -1、游戏等待中,等待开始

0、游戏中 红方走棋 1、

CString name; int x; int y;

游戏中 黑方走棋

信息

int MarryTop; //记录游戏步数,为了还原时能够判断 int count;

//记录listbox的步数 包括游戏步奏和聊天记录

int top; //记录游戏步数

int Marry[500][8]; //记录 前一个的标号,type,x,y 后一个的标号,type,x,y 共八个

public:

bool CanGoTo(int Old_Point_x,int Old_Point_y,int Now_Point_x,int Now_Point_y,int

chesstype);//判断棋子能否到达,是每个棋子的走法判断

bool

ShuaiCanGoTo(int

Old_Point_x,int

Old_Point_y,int

Now_Point_x,int

Now_Point_y,int chesstype); //是关于将帅问题的能否到达判断,将帅不共线

};

(3)CChess_mView 类是程序的显示类,也包含少部分的棋子控制操作功能和很多消息响应函数,由于篇幅问题没有全部显示。

class CChess_mView : public CView {

protected: CChess_mView();

DECLARE_DYNCREATE(CChess_mView)

// Attributes public:

CChess_mDoc* GetDocument();

CNetControl *m_pControl; //网络控制显示 int choosed; //记录前一个棋子是第几个棋子 int OldType; //记录前一个棋子的类型 int OldX; int OldY;

//记录前一个棋子的横坐标 //记录前一个棋子的纵坐标

///用于右键功能 int Rchoosed; int ROldX; int ROldY; //变量定义

HBITMAP ChessList[17];

//位图数组

//记录当前一个棋子是第几个棋子 //记录前一个棋子的横坐标 //记录前一个棋子的纵坐标

CString m_strPiece;//棋子根目录 CString m_strBoard;//棋盘根目录

int m_nBoardIndex,m_nPieceIndex; //背景设置选择 //定义画图用CDC

CDC Background;//背景句柄

CDC Buffer;//缓冲句柄 用于双缓冲

CListBox m_listbox; //用于记录每步棋子走棋的信息和聊天信息 CButton m_ok; //开始按钮 CButton m_goback; //悔棋按钮 CButton m_huanyuan; //还原按钮 CButton m_renshu; //认输按钮 CButton m_send;//聊天发送按钮 CEdit m_edit;//用于聊天的edit

int down_move;//用于鼠标按下时的控制 int move_up; //用于鼠标弹上时的控制

int goback_huanyuan; //用于只有在有悔棋额情况下才能还原的控制

// Operations public:

void OnOk();//开始 void OnGoBack();//悔棋 void OnHuanYuan();//还原 void OnRenShu();//认输 void OnSend();//发送聊天信息 //函数定义 void InitData(); void NewGame();

//初始化 //新游戏

void TextOutTop();//提示现在出牌 void PrintAll(); //把界面存储于buffer中 void PrintAllMsg(); //用于网络刷新和整体刷新 void PrintAllDMsg();//用于网络动棋子的时候整体刷新 HBITMAP LoadFileBmp(CString filePath); //载入位图

void SetBoardandPiece(CString strBoard,CString strPiece);//给棋盘棋子赋值

// Overrides

// ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CChess_mView)

virtual void OnDraw(CDC* pDC); // overridden to draw this view virtual BOOL PreCreateWindow(CREATESTRUCT& cs); protected:

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo); virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo); virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo); //}}AFX_VIRTUAL

// Implementation public:

virtual ~CChess_mView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif protected:

//{{AFX_MSG(CChess_mView)

„„„„„.(省略) };

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

3.3 棋子走法

棋子走棋规则在“1.2.2 象棋棋子”已有介绍,这里不再累述。

这里主要介绍各个棋子的程序具体实现。具体见CanGoTo(int Old_Point_x,int Old_Point_y,int

Now_Point_x,int

Now_Point_y,int

chesstype)和

ShuaiCanGoTo(int

Old_Point_x,int Old_Point_y,int Now_Point_x,int Now_Point_y,int chesstype)函数。

首先,不管是什么棋子,都不能吃自己一方的棋子。

if (map[Old_Point_x][Old_Point_y]/7==map[Now_Point_x][Now_Point_y]/7)

return false;

3.3.1 車

车只要中间没有棋子阻碍,可以到达它横竖线所在的任何地方。所以只需判断始末位置为端点的线段上的棋子个数,即判断map[][]不为空的个数。若只有一个,则表示目标位置没有棋子,可以直达。若有两个,且目标位置不为空,则表示吃子,可达。

//车

if (9==chesstype||2==chesstype)

{

//同一行

if (Old_Point_y==Now_Point_y)

{

int maxcol=(Old_Point_x>Now_Point_x)?Old_Point_x:Now_Point_x;

int mincol=(Old_Point_x>Now_Point_x)?Now_Point_x:Old_Point_x;

int n=0;

//判断移动前后位置之间有无其他子

for (int i=mincol;i

{

if(14!=map[i][Old_Point_y])

n++;

}

if(1==n)

return true;

else if((2==n)&&(14!=map[Now_Point_x][Old_Point_y]))

return true;

else return false;

}

//同一列

else

if (Old_Point_x==Now_Point_x)

{

int maxrow=(Old_Point_y>Now_Point_y)?Old_Point_y:Now_Point_y;

int minrow=(Old_Point_y>Now_Point_y)?Now_Point_y:Old_Point_y;

int n=0;

for(int i=minrow;i

{

if(14!=map[Old_Point_x][i])

n++;

}

if(1==n)

return true;

else if((2==n)&&(14!=map[Now_Point_x][Now_Point_y]))

return true;

else return false;

} }

3.3.2 馬

马走日,所以要判断始末位置的X和Y的差值的乘积是不是2或者-2。若是,则判断有没有蹩马腿,即同侧的第一个位置map[][]必须为空。

//马

if (3==chesstype||10==chesstype)

{

int row=Now_Point_x-Old_Point_x;

int col=Now_Point_y-Old_Point_y;

//日字的范围

if((2!=row*col)&&(-2!=row*col))

return false;

//是否有同侧的子堵塞

if (-2==row)

{

if (14==map[Old_Point_x-1][Old_Point_y])

return true;

else

return false;

}

else

if (2==row)

{

if(14==map[Old_Point_x+1][Old_Point_y])

return true;

else

return false;

}

else

if (-2==col)

{

if(14==map[Old_Point_x][Old_Point_y-1])

return true;

else

return false;

}

else

if (2==col)

{

if(14==map[Old_Point_x][Old_Point_y+1])

return true;

} return false; } else return false;

3.3.3 相(象)

相和象活动范围不同,所以要分开判断。但相(象)的走法判断和马类似,都是先判断差值,来确定田字范围,再判断有没有塞象眼,即田字中间有没有棋子。

//黑象 if (11==chesstype) { if((Now_Point_y>=0)&&(Now_Point_y

} { int row=Now_Point_x-Old_Point_x; int col=Now_Point_y-Old_Point_y; //田字范围 if ((-2==row)&&(-2==col)&&(14==map[Old_Point_x-1][Old_Point_y-1])) { return true; } else if ((-2==row)&&(2==col)&&(14==map[Old_Point_x-1][Old_Point_y+1])) { return true; } else if((2==row)&&(-2==col)&&(14==map[Old_Point_x+1][Old_Point_y-1])) { return true; } else if((2==row)&&(2==col)&&(14==map[Old_Point_x+1][Old_Point_y+1])) { return true; } else return false; }

3.3.4 仕(士)

仕(士)只能在各自九宫内走动,所以红黑士要分开判断范围,然后在判断棋子是否斜走,而不是直走,即始末位置的X,Y的差值绝对值要都是1。

//黑士 if (chesstype==12) { if ((Now_Point_x>=3)&&(Now_Point_x=0)&&(Now_Point_y

{

int row=abs(Now_Point_y-Old_Point_y);

int col=abs(Now_Point_x-Old_Point_x);

if ((0==row-col)&&(row==1))

{

return true;

}

else

return false;

}

else return false;

}

//红士

else if(chesstype==5)

{

if ((Now_Point_x>=3)&&(Now_Point_x=7)&&(Now_Point_y

{

int row=abs(Now_Point_y-Old_Point_y);

int col=abs(Now_Point_x-Old_Point_x);

if ((1==col)&&(row==1))

{

return true;

}

else

return false;

}

else return false;

}

3.3.5 帅(将)

帅或者将只能在自己的九宫内左右上下走一格。判断时,只需先判定在九宫内,再判断始末位置的X,Y的差值绝对值是不是一个为1,一个为0。

//黑将

if (13==chesstype)

{

if ((Now_Point_x>=3)&&(Now_Point_x=0)&&(Now_Point_y

{

int row=abs(Now_Point_y-Old_Point_y);

int col=abs(Now_Point_x-Old_Point_x);

if((0==row&&1==col)||(1==row&&0==col))

return true;

else return false;

}

else return false;

}

//红帅

else if(6==chesstype)

{

if ((Now_Point_x>=3)&&(Now_Point_x=7)&&(Now_Point_y

{

int row=abs(Now_Point_y-Old_Point_y);

} int col=abs(Now_Point_x-Old_Point_x); if((0==row&&1==col)||(1==row&&0==col)) return true; else return false; } else return false;

3.3.6 炮(砲)

炮不仅可以和车一样直走多步,还可以跳子吃子。炮首先判断是否在同一列或者同一行,然后判断当前位置和目标位置中间是否存在其他棋子,若中间无子且目标位置处无子,则表示炮在行走且可达;若中间有一个棋子且目标位置有子(不能为本方棋子,前面已经做过判断),则表示炮在吃子且可达。

//炮

if (1==chesstype||8==chesstype)

{

//同一列

if (Old_Point_x==Now_Point_x)

{

int maxcol=(Old_Point_y>Now_Point_y)?Old_Point_y:Now_Point_y;

int mincol=(Old_Point_y>Now_Point_y)?Now_Point_y:Old_Point_y;

int n=0;

//判断两位置之间是否有子间隔

for (int i=mincol;i

{

if(14!=map[Old_Point_x][i])

n++;

}

//无子,可以放置

if(1==n)

return true;

//炮吃子

else if((3==n)&&(14!=map[Now_Point_x][Now_Point_y]))

return true;

else return false;

}

//同一行

else

if (Old_Point_y==Now_Point_y)

{

int maxrow=(Old_Point_x>Now_Point_x)?Old_Point_x:Now_Point_x;

int minrow=(Old_Point_x>Now_Point_x)?Now_Point_x:Old_Point_x;

} { if(14!=map[i][Old_Point_y]) n++; } if(1==n) return true; else if((3==n)&&(14!=map[Now_Point_x][Now_Point_y])) return true; else return false; } else return false;

3.3.7 兵(卒)

兵(卒)不能后退且只能一步一步前进,未过河前,只能向前。过河以后,允许左右移动。所以首先除了要分黑卒和红兵以外,也要分未过河与过河。如黑卒,未过河前,X前后不变,Y比之前多1,可达。过河后,黑卒可以X前后不变,Y比之前多1,也可以前后Y不变,X比之前多1或者少1,均可达。

//黑卒

if (chesstype==7)

{

//未过河

if ((Old_Point_y>=3)&&(Old_Point_y

{

if ((Old_Point_x==Now_Point_x)&&(Now_Point_y-Old_Point_y==1))

{

return true;

}

else return false;

}

//已过河

else if ((Old_Point_y>=5)&&(Old_Point_y

{

if ((Old_Point_x==Now_Point_x)&&(Now_Point_y-Old_Point_y==1))

{

return true;

}

else if ((abs(Now_Point_x-Old_Point_x)==1)&&(Old_Point_y==Now_Point_y))

{

return true;

}

} else //红兵 if (chesstype==0) { //未过河 if ((Old_Point_y=5)) { if ((Old_Point_x==Now_Point_x)&&(Old_Point_y-Now_Point_y==1)) { return true; } else return false; } //已过河 else if ((Old_Point_y=0)) { if ((Old_Point_x==Now_Point_x)&&((Old_Point_y-Now_Point_y)==1)) { return true; } else if((abs(Old_Point_x-Now_Point_x)==1)&&(0==Old_Point_y-Now_Point_y)) { return true; } else return false;

}

}

3.4 功能

3.4.1 走棋

走棋功能包括三个部分,第一是选棋子,第二是移动棋子,第三是选择目的地。选棋子只要就是鼠标左键按下事件下进行,按下时要判断这里是否有棋子,即棋盘MAP[][]数组是否是小于14(棋子的type最大为13),否则不响应我选棋子的操作。移动棋子到达目的地时,要判断棋子是否能到达这里,即调用CanGoTo()和ShuaiCanGoTo()函数,若可以则让该棋子落在此处。

(1) LButtonDown操作

LButtonDown操作,除了要判断选棋子之外,还要得工作就是保存当前棋子的信息,因为鼠标移动该地方是没有棋子显示的,但是若走棋失败,还是要范围到选棋子之前的状

态。

void CChess_mView::OnLButtonDown(UINT nFlags, CPoint point)

{

if (manager.m_State==manager.PlayerAc)

{

if ((point.x=BOARD_EDGE)

&&(point.y=BOARD_EDGE))

{

OldX=(point.x-BOARD_EDGE)/CHESS_SIZE;

OldY=(point.y-BOARD_EDGE)/CHESS_SIZE;

if ((manager.m_State==0&&manager.map[OldX][OldY]

&&manager.map[OldX][OldY]>=0)||(manager.m_State==1

&&(manager.map[OldX][OldY]6)))

{

if (manager.m_State==0)//红方

{

if (manager.map[OldX][OldY]

{

choosed=manager.Search(point.x,point.y);

OldType=manager.nChessinfo[choosed].type;

manager.nChessinfo[choosed].type=15;

down_move=1;

}

}

if(manager.m_State==1) //黑方

{

if(manager.map[OldX][OldY]6) {

choosed=manager.Search(point.x,point.y);

OldType=manager.nChessinfo[choosed].type;

manager.nChessinfo[choosed].type=15;

//manager.map[OldX][OldY]=14;

down_move=1;

}

}

}

}

}

CView::OnLButtonDown(nFlags, point);

}

(2) LButtonMove操作

为了美观,LButtonMove操作需要做的就是选中的棋子跟着鼠标移动,主要要用到双

缓冲技术,这个在关键技术那部分做详细介绍。

(3) LButtonUp操作

LButtonUp操作要做的就是判断能否到达,保存数据,判断胜负的工作。 void CChess_mView::OnLButtonUp(UINT nFlags, CPoint point)

{

manager.nChessinfo[choosed].type=OldType;

if (1==move_up)

{

if(ShuaiCanGoTo(OldX,OldY,(point.x-BOARD_EDGE)/CHESS_SIZE,

(point.y-BOARD_EDGE)/CHESS_SIZE,OldType)

&&CanGoTo(OldX,OldY,(point.x-BOARD_EDGE)/CHESS_SIZE,

(point.y-BOARD_EDGE)/CHESS_SIZE,OldType))

{

Marry[top][0]=choosed;

Marry[top][1]=OldType;

Marry[top][2]=OldX;

Marry[top][3]=OldY;

Marry[top][4]=Search(point.x,point.y);

Marry[top][5]=nChessinfo[Search(point.x,point.y)].type;

Marry[top][6]=int (point.x-BOARD_EDGE)/CHESS_SIZE;

Marry[top][7]=int (point.y-BOARD_EDGE)/CHESS_SIZE;

if (Marry[top][4]

{

//如果这里有子,那么把他的类型赋为15 不显示

nChessinfo[Search(point.x,point.y)].type=15;

}

//把棋谱的map[OldX][OldY] 变空

map[OldX][OldY]=14;

nChessinfo[choosed].x=Marry[top][6];

nChessinfo[choosed].y=Marry[top][7];

map[nChessinfo[choosed].x][nChessinfo[choosed].y]=nChessinfo[choosed].type; PrintAll();

//记录数组Marry的顶部是多少,为"还原"时控制

manager.MarryTop=manager.top;

//listbox 显示

CString str;

str.Format("%d:%s(%d,%d)(%d,%d)",top++,nChessinfo[choosed].name,

Marry[top][2],Marry[top][3],Marry[top][6],Marry[top][7]);

m_listbox.AddString(str);

m_listbox.SetCurSel(count++);

if (nChessinfo[15].type!=6)

{

MessageBox("黑方获胜!!!");

m_State=-1;

}

else if (nChessinfo[31].type!=13)

{

MessageBox("红方获胜!!!");

m_State=-1;

}

if (m_State!=-1)

{

m_State=(m_State+1)%2;

}

char st[10];

sprintf(st,"ZQ%d%d%d%d",OldX,OldY,Marry[MarryTop][6],Marry[MarryTop][7]);

m_pControl->SendMsg(st);//传送本方操作给对方

}

else

{

MessageBeep(-1); //显示声音

nChessinfo[choosed].x=OldX;

nChessinfo[choosed].y=OldY;

map[nChessinfo[choosed].x][nChessinfo[choosed].y]=nChessinfo[choosed].type;

PrintAll();

}

}

ReleaseDC(pDC);

OldX=-1;

OldY=-1;

down_move=0;

move_up=0;

CView::OnLButtonUp(nFlags, point);

}

3.4.2 悔棋

悔棋功能靠的就是二维数组Marry记录的每步棋子的走棋步骤。Marry[500][8]记录 前一个的标号,type,x,y 后一个的标号,type,x,y,即记录走棋前的前一点的信息,和后一点的信息。这样只要用当前的Marry[][]的数据去重新赋值,即完成悔棋。

void CChess_mView::OnGoBack()

{

if (manager.m_State==(manager.PlayerAc+1)%2||goback_huanyuan==1)

{

if (manager.top>1)

{

count--;

} } top--; nChessinfo[Marry[top][0]].type=Marry[top][1]; nChessinfo[Marry[top][0]].x=Marry[top][2]; nChessinfo[Marry[op][0]].y=Marry[top][3]; map[Marry[top][2]][Marry[mtop][3]]=Marry[top][1]; nChessinfo[Marry[top][4]].type=Marry[top][5]; nChessinfo[Marry[top][4]].x=Marry[top][6]; nChessinfo[Marry[top][4]].y=Marry[top][7]; map[Marry[top][6]][Marry[top][7]]=mMarry[top][5]; PrintAllMsg();//重绘位图 CString str; int i=manager.count; m_listbox.GetText(i,str); while(!(strncmp(str,"你",2)&&strncmp(str,"他",2))) { i--; m_listbox.GetText(i,str); } m_listbox.DeleteString(i); manager.m_State=(manager.m_State+1)%2; m_pControl->SendMsg("HQ"); goback_huanyuan=1; } else { MessageBeep(-1); }

3.4.3 还原

还原功能也是借助Marry保存的记录,把第一个标号的属性换成第二个标号的数据,第一个标号的type设为无。

void CChess_mView::OnHuanYuan()

{

if (goback_huanyuan==1)

{

if (manager.top

{

nChessinfo[Marry[top][4]].type=15;

nChessinfo[Marry[top][4]].x=Marry[top][2];

nChessinfo[Marry[top][4]].y=Marry[top][3];

map[Marry[top][2]][Marry[top][3]]=14;

nChessinfo[Marry[top][0]].type=Marry[top][1];

nChessinfo[Marry[top][0]].x=Marry[top][6];

nChessinfo[Marry[top][0]].y=Marry[top][7];

map[Marry[top][6]][Marry[top][7]]=Marry[top][1];

PrintAllMsg();

CString str;

str.Format("%d:%s(%d,%d)(%d,%d)",top++,nChessinfo[Marry[top][0]].name,

Marry[top][2],Marry[top][3],Marry[top][6],Marry[top][7]);

m_listbox.AddString(str);

m_listbox.SetCurSel(manager.count++);

manager.m_State=(manager.m_State+1)%2;

m_pControl->SendMsg("HY");

}

else

{

MessageBeep(-1);

goback_huanyuan=0;

}

}

}

3.4.4 认输

认输比较简单,只需把游戏状态设为-1;

3.4.5 摆棋

你们双方其中有一个人很厉害吗?你想来点怪棋玩玩吗?那么你就右击鼠标。你可以摆棋,可以修改、删除任意棋子(除了将帅,不能修改,不能删除,因只此一个)。

3.5 网络

Socket的使用是基于CS系统的(客户机、服务器),客户机和服务器的Socket使用不太一样,分别有以下几个步骤。

服务器:

(1) 创建服务器Socket(Create);

(2) 服务器Socket进行信息绑定(Bind),并开始监听连接(Listen);

(3) 接受来自客户端的连接请求(Accept),创建接受进程;

(4) 开始交换数据(Send,Receive);

(5) 关闭Socket(Close)。

客户机:

(1) 创建客户机Socket(Create);

(2) 连接服务器(Connect),如果接收,创建接受进程;

(3) 开始交换数据(Send,Receive)

(4) 关闭Socket(Close)。

对于Socket对象,本设计对它进行继承,并重写函数,改造使它拥有更多功能。在Socket原有的基础上加入了virtual OnAccept(int nErrorCode)和virtual void OnReceive(int nErrorCode)的重写,同时加入了NetControl的指针*m_Nc,并用*m_Nc初始化CNet。改写后的函数其实是对NetControl进行操作,这样的好处就是直接把信息传入到了NetControl进行具体的响应。

class CNet : public CSocket

{

public:

};

class CNetControl

{

public:

CNetControl(CChess_mView *p);//用视图类指针作为参数初始化,为的就是可以在CNet(); CNet(CNetControl *m_Ncc); virtual ~CNet(); CNetControl *m_Nc; void OnAccept(int nErrorCode); void OnReceive(int nErrorCode); 网络端控制显示

void SendMsg(char const *pMsg); //发送操作消息 void OnSorc();//开始选择做服务器还是客户机 void OnSet();//设定服务器 void OnOpen();//判断Socket是否已经建立,是否连接到服务器 void FetchMsg(CNet *pRequest);//消息处理函数 CNet * ConnectServer();//连接服务器

}; CNetControl(); virtual ~CNetControl(); UINT m_port; //端口 LPCTSTR m_server;//服务器名称 CNet *m_pSocket;//请求连接 CNet *m_pListening;//监听Socket CChess_mView *m_pView;//绘图指针 bool m_Ready[2];//用户双方判断开始准备的

其中,其他的几个函数建立时都是大体一致,而消息处理函数要配合着消息发送函数一起。其中消息处理函数FetchMsg(CNet *pRequest)是网络编程的重点。这个函数要包括几乎所有的功能,走棋,悔棋,判断,聊天功能等等。

void CNetControl::FetchMsg(CNet *pRequest)

{

//接收数据

char Msg[10000],tempMsg[1000];

int ByteCount;

int End=0;

CStringArray *temp=new CStringArray;

strcpy(Msg,"");

do

{

strcpy(tempMsg,"");

ByteCount=pRequest->Receive(tempMsg,1000);

if(ByteCount>1000||ByteCount

{

MessageBox(GetActiveWindow(),"接受网络信息发生错误","警告信息",MB_OK);

return ;

}

else

if(ByteCount0)

{

End=1;

}

tempMsg[ByteCount]=0;

strcat(Msg,tempMsg);

}

while(End==0);

//处理相应的函数 代码省略

} if(!strncmp(Msg,"ZQ",2)) 走棋 if(!strncmp(Msg,"HQ",2)) 悔棋 if(!strncmp(Msg,"HY",2)) 还原 if(!strncmp(Msg,"ok",2)) 开始 if(!strncmp(Msg,"RH",2)) 认输 if(!strncmp(Msg,"LT",2)) 聊天 if(!strncmp(Msg,"XG",2)) 右击鼠标 设置任何棋谱 修改棋子 if(!strncmp(Msg,"SC",2)) 右击鼠标 设置任何棋谱 删除棋子

3.6 关键技术

3.6.1 遮罩技术

所谓“遮罩”就是一张和要透明绘制的位图相应的黑白双色的位图。原位图的透明部分即是“遮罩”的黑色部分,而不透明的部分在“遮罩”中是白色,我们可以先将“遮罩”和棋盘的位图做“MERGEPAINT”操作,然后再和要透明绘制的位图做“SRCAND”操作。这样就可以获得没有边框的棋子,圆润的附在棋子上了。

ChessAll.SelectObject(ChessList[16]);

Buffer.BitBlt(point.x-CHESS_SIZE/2-2,point.y-CHESS_SIZE/2-3,CHESS_SIZE,

CHESS_SIZE,&ChessAll,0,0,MERGEPAINT);

ChessAll.SelectObject(ChessList[OldType]);

Buffer.BitBlt(point.x-CHESS_SIZE/2,point.y-CHESS_SIZE/2,CHESS_SIZE,

CHESS_SIZE,&ChessAll,0,0,SRCAND);

pDC->BitBlt(0,0,BOARD_WIDTH,BOARD_HEIGHT,&Buffer,0,0,SRCCOPY);

ReleaseDC(pDC);

说明:MERGEPAINT使用布尔OR操作符合并特征与源位图

SRCCOPY 拷贝源位图到目标位图。

3.6.2 双缓冲技术

当我们想要选中的棋子黏在鼠标上面跟着移动时,我们发现界面会闪烁。我们知道GDI绘图过程大多放在OnDraw函数中,OnDraw在进行屏幕显示时是由OnPaint进行调用的。当窗口由于任何原因需要重绘时,比如MouseMove事件,总是先用背景色将显示区清除,然后才调用OnPaint,而背景色一般与重绘的内容相差很大,这样在很短的时间内背景色和重绘内容会反复出现,窗口看起来就像在闪。不过若将背景刷设置为NULL,这样就没有背景和重绘图形的交替,即没有闪烁。但是,这样重绘图形会反复叠加,效果很差。因为重绘时没有用背景色对绘图区域进行刷新,而仅是在原来图形上叠加上了新的图形。

利用双缓冲技术,在内存DC中事先把要显示的图画好,然后再一次性将内存中画好的图贴到窗口中。由于这种方法没有直接窗口上绘图、擦写,就是一整张一整张的贴上去

的,没有闪烁。

void CChess_mView::OnMouseMove(UINT nFlags, CPoint point)

{

if (1==down_move)

{

CDC *pDC=GetDC();

PrintAll();

ChessAll.SelectObject(ChessList[14]);//光标

Buffer.BitBlt(OldX*CHESS_SIZE+BOARD_EDGE,OldY*CHESS_SIZE+BOARD_EDGE,CHESS_SIZE,CHESS_SIZE,&ChessAll,0,0,SRCAND);

ChessAll.SelectObject(ChessList[16]);//遮罩

Buffer.BitBlt(point.x-CHESS_SIZE/2-2,point.y-CHESS_SIZE/2-3,CHESS_SIZE,

CHESS_SIZE,&ChessAll,0,0,MERGEPAINT);

ChessAll.SelectObject(ChessList[OldType]);

Buffer.BitBlt(point.x-CHESS_SIZE/2,point.y-CHESS_SIZE/2,CHESS_SIZE,

CHESS_SIZE,&ChessAll,0,0,SRCAND);//利用Buffer提前在内存中把想要绘制的图形画好,然后一次性的让pDC贴上去。

pDC->BitBlt(0,0,BOARD_WIDTH,BOARD_HEIGHT,&Buffer,0,0,SRCCOPY);

ReleaseDC(pDC);

}

CView::OnMouseMove(nFlags, point);

}

4 总结和进一步的研究工作

本设计相对完善的完成了局域网内的对战象棋的功能,界面友好,操作简单。象棋的设计当然还可以进一步的完善。进一步的工作可以从以下几个方面展开:

(1)人工智能

局域网双人对战还是需要有伙伴才能开始游戏,一个人的时候难免会有伤感,若能既有双人版,又有单机版,肯定就更好了。而且现在人工智能这么火热,人工智能的发展将给人类社会带来翻天覆地的变化,适应潮流。

(2)网络平台游戏

局域网内游戏还只是两个人之间的游戏,若想做的更好,可以往网络平台游戏方向发展。类似于腾讯的QQ游戏平台,多人网上对战。

结束语

经过了数月的学习与钻研,本人终于完成了此款电子版象棋游戏的设计与实现。在这之前,虽然也学过MFC相关技术,但对于MFC开发还是一个很模糊的概念。以往只注重C++理论而不重视实践,从来没有活用所学语言来完成一个实在的程序。一直以来,可视化程序设计是件可望而不可及的事情。感谢老师让本人了解了MFC,并且开始用MFC学着做设计。在此过程中虽然遇到了很多困难,但是经过充足的准备均能得到顺利的解决。

在程序设计之前本人做了大量的准备工作,收集了很多关于象棋和MFC资料。对于如何进行概要设计,如何设计数据结构,如何做程序设计,如何实现功能尤其是实现网络功能等等一系列问题,都通过查阅资料,请教老师得以解决。在前期工作做好后,动手操作时,由于不经常设计的原因,遇到很多从未见过的问题,但最终通过本人的努力,细心修改,克服了困难。

令本人感到收获最大的并不是能够完成整个程序的设计与实现,而是学会了如何去设计程序,如何在遇到问题时细心解决。总之一句话,通过毕业设计本人学会了如何进行程序设计。这一些也为本人以后的学习和工作做了良好的好铺垫。

参 考 文 献

[1] 候俊杰. 深入浅出 MFC. 武汉: 华中科技大学出版社, 2001.

[2] 孙鑫. VC++深入详解(修订版). 北京: 电子工业出版社, 2012.

[3] 荣钦科技, 刘晓华. Visual C++游戏编程基础. 北京: 电子工业出版社, 2005.

[4] 严蔚敏, 吴伟民. 数据结构. 北京: 清华大学出版社, 2008.

[5] 雷超然, 葛垚. Visual C++ MFC 棋牌类游戏编程实例. 北京: 人民邮电出版社, 2008.

[5] 宋国强, 韩冰. 象棋基本战术. 北京: 北京体育大学出版社, 2011.

[7] 王嘉良, 张志强. 中国象棋初级教程. 北京: 经济管理, 2012.

[8] 曹全忠. 象棋入门一本通. 河南: 河南科学技术出版社, 2008.

[9] 曹霖. 大师教你学象棋. 吉林: 吉林科学技术出版社, 2011.

致 谢

本论文的工作是在指导老师张勇老师的悉心指导和帮助下完成的。本论文从选题、需求分析、程序的设计与实现到最后的审阅定稿都得到张老师的悉心指导。值此论文完成之际,谨向张老师表示最衷心的感谢!同时也要感谢学院里的领导和老师们以及其他帮助过我的老师,是你们对我无私的帮助以及为我创造良好的学习环境,才使得本文能顺利完成。

最后,感谢参加本人论文评阅和答辩的各位老师,谢谢!


相关内容

  • 软件工程师要求)
  • 要求: 1) 1年以上Windows应用开发经验, 熟悉C++编程; 2) 熟悉MFC/ATL/STL; 3) 熟悉GUI开发设计, 有美工素养; 4) 有音频.视频及DirectX开发经验者优先; 5) 有IM开发经验者优先 工作职责: 使用MFC实现UI控件.功能等设计和开发 职位要求: 1. ...

  • 软件开发毕业论文
  • 软件开发毕业论文 学 生: 学 号: 专 业: 计算机科学与技术 导 师: 学校代码: 教育学院 电力监控软件开发 摘要 随着我国国民经济的快速发展,我国对电力的需求也越来越紧迫.尤其是在近几年里,我国每年的电力缺口逐年扩大,已经严重制约了各个行业的发展甚至影响到了居民的正常用电.正是由于这样,全国 ...

  • 一个游戏程序员的学习资料
  • 一个游戏程序员的学习资料 想起写这篇文章是在看侯杰先生的<深入浅出MFC >时, 突然觉得自己在大学这 几年关于游戏编程方面还算是有些心得 因此写出这篇小文, 介绍我眼中的游戏 程序员的书单与源代码参考.一则是作为自己今后两年学习目标的备忘录, 二来 没准对别人也有点参考价值.我的原则是 ...

  • 电子邮件系统的设计与开发
  • 山东农业大学 毕业论文 题目: 二○○八年六月 中英文摘要 第一章 电子邮件的发展背景和意义 电子邮件翻译自英文的E-mail ,它表示通过电子通讯系统进行信件的书写.发送和接收. 30多年前,人们发明了电子邮件这种便捷的信息传递方式,这是人类通信历史上的一次革命. 电子邮件的兴起是在20世纪80年 ...

  • 微生物燃料电池
  • 微生物燃料电池 摘要:微生物燃料电池的研究集中于产电细菌.电极材料和电池反应器构型等方面,同时,微生物燃料电池在废水处理.生物修复等方面具有广阔的应用前景.本文介绍了微生物燃料电池的原理.影响微生物燃料电池的因素及近几年微生物燃料电池在环境污染治理中的研究进展. 关键词:微生物燃料电池 双室 质子交 ...

  • 大学毕业论文计算机专业
  • 毕 业 论 文 论文题目: 姓 名: 学 号: 学习中心: 专 业: 指导教师: 二〇〇 年 月 毕业论文承诺书 提示:根据北京语言大学网络教育学院论文写作的规定,如发现论文有抄袭.网上下载.请人代写等情况,毕业论文一律不及格.同时取消学士学位申请资格.毕业论文不及格者,可申请重写一次,并按重修缴纳 ...

  • 微生物燃料电池内阻测试仪设计毕业论文
  • 微生物燃料电池内阻测试仪设计 摘要 微生物燃料电池(MFC) 是一个微生物催化有机化学能直接转化为电能的生物反应器.微生物燃料电池同时能净化污水和收获电能 ,这样可以降低污水处理的成本 ,因而近年来受到了广泛关注.然而 ,目前 MFC输出功率还很低,比普通的氢气燃料电池要低3~4个数量级,微生物燃料 ...

  • MFC用户界面设计
  • MFC用户界面设计 (2007-07-03 14:08:02) 转载 MFC用户界面设计 一 (创建一个MFC工程的框架) 打开VC++6.0选择New,出现如下界面: 然后选中MFC AppWizard[exe] ,即我们将创建一个MFC的可执行文件,然后在Project Name下面的框中写入这 ...

  • 微生物燃料电池产电性能的研究论文
  • 微生物燃料电池产电性能的研究 专 班业: 生物化工工艺 级: 学生姓名: 指导教师66 完成时间: 一. 课题分析 一篇优秀的论文是建立在一份出色的开题报告的基础上的,同时缺不 了许多价值很大的参考文献,所以参考文献则显得尤为重要,包括中文文 献和英文文献. (一)采取的检索工具1,中国知网(期刊) ...