网络五子棋设计与实现

更新时间:2024-05-06 12:49:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

目 录

摘要..…………………………………….…..……………………….………………..1 关键词..……………………………………..………………..………………………..1 1 前言..…………………….……………..……….……….…………………………1 2 需求分析..……………………………..…….…………….………………………2 2.1 开发背景..……………………………..…………….….……………………….2 2.2 研究现状………………………………………………………………………...2 2.3 开发目的及意义..…………………………..….………….…………………….3 2.4 任务概述..…………………………………..….…………….………………….3 3 开发环境及运行环境..………………………..….…………….…………………4 3.1 开发环境..……………………………………...……………….……………….4 3.2 运行环境..……………………………………..….…………….……………….4 4 软件架构..……………………………………..….………………….……………4 4.1 棋盘类..……………………………………...…………………….…………….5 4.2 游戏模式类..…………………………….…..………………….……………….5 5 棋盘类—Ctable....……………………………………………………….………...6 5.1 主要成员变量说明………………………..……...………………….………….6 5.1.1 网络连接标志—m_bConnected....………………………………….………...6 5.1.2 棋盘等待标志—m_bWait与m_bOldWait…………….…………….……….6 5.1.3 网络套接字—m_sock和m_conn….……………...….…………….………...6 5.1.4 棋盘数据—m_data………………………………….……………….………..6 5.1.5 游戏模式指针—m_pGame ………………….……………………….…….6 5.2 主要成员函数说明.……………………………………………………….……6 5.2.1 套接字的回调处理—Accept、Connect、Receive……………….….………6 5.2.2 清空棋盘—Clear...…………………………………….………….….……….6 5.2.3 绘制棋子—Draw.……………………………………….………….…………7 5.2.4 左键消息—OnLButtonUp....…………………………..……………………..7 5.2.5 绘制棋盘—OnPaint.……………………………….…….…………….…...7

5.2.6 对方落子完毕—Over..……………………………….………………….……7 5.2.7设置游戏模式—SetGameMode..………………....……….………...…….……8 5.2.8胜负的判断—Win..……………………...…….….………………...……….….8 6 游戏模式类—Cgame……………..……………..…………………...……...…....8 6.1 主要成员变量说明….……………………...…...………………...……………9 6.1.1 棋盘指针—m_pTable….………….……….………....…………...……….…9 6.1.2 落子步骤—m_StepList.....…………………….….………...………………..9 6.2 主要成员函数说明….…………………………….….………...……………...9 6.2.1 悔棋操作—Back....…………………………….….……………...………….9 6.2.2 初始化操作—Init....…………...….……………….…….…….……………..9 6.2.3 接收来自对方的消息—ReceiveMsg ………….…………………………...10 6.2.4 发送落子消息—SendStep…..………………………..……………………..10 6.2.5 胜利后的处理—Win….……………………………………….……………10 7 消息机制….……………………………………………………………………..10 7.1 消息机制的架构…..…………………………….…………………………….10 7.2 各种消息说明….……………………………….……………………………..11 7.2.1 落子消息—MSG_PUTSTEP.…..…………………………………………..11 7.2.2 聊天消息—MSG_CHAT..…………………………………………………..11 7.2.3 悔棋消息—MSG_BACK………..………………………………………….12 7.2.4 同意悔棋消息—MSG_AGREEBACK.…….………………………………13 7.2.5 拒绝悔棋消息—MSG_REFUSEBACK..………..…………………………13 7.2.6 和棋消息—MSG_DRAW.………….………………………………………13 7.2.7 同意和棋消息—MSG_AGREEDRAW …………...……………………….13 7.2.8 拒绝和棋消息—MSG_REFUSEDRAW…………….……………………..14 7.2.9 认输消息—MSG_GIVEUP…………..…………………………………….14 7.2.10 对方信息消息—MSG_INFORMATION……………..…………………..15 7.2.11 再次开局消息—MSG_PLAYAGAIN……………………………………..15 7.2.12 同意再次开局消息—MSG_AGREEAGAIN…………..…………………15 8 主要算法………………..……………………………………………………….15

8.1 判断胜负………………………………………………………………………15 8.2 人机对弈算法………………..………………………………………………..18 8.2.1 获胜组合…………………………………………………………………….18 8.2.2 落子后处理………….………………………………………………………18 8.2.3 查找棋盘空位……………………………………………………………….19 8.2.4 落子打分…………….………………………………………………………19 8.2.5 防守策略…………………………………………………………………….21 8.2.6 选取最佳落子……………………………………………………………….22 9 总结…………..………………………………………………………………….23 参考文献…………………………………………………………………………….23 致 谢……………………………………………………………………………….24

网络五子棋设计与实现

摘 要:本文主要对五子棋网络游戏技术进行研究,以VC++为平台设计并实现了一个基于

C/S模式的五子棋网络对战游戏,本设计包括网络对弈与人机对弈两种模式,并通过MFC与网络编程实现各功能模块,包括网络聊天、战绩统计、悔棋操作、和棋操作、认输操作以及美观大 方的操作界面。

关键词:五子棋;网络游戏;人工智能;C/S模式

Design and Implementation of Renju Network Game

Abstract: This article mainly does the research to the Renju network game technology, taking

VC++ as the platform, I design and implement a network war game based on the C/S mode. The network design includes games with the man-machine games two models. And through MFC and network programming of the modules, including Internet chat, feat statistics, the operation afterwards, a draw in chess operation concede pleasing to the eye operation.

Keywords:Renju; network game; artificial intelligence; C/S mode

1 前言

随着互联网技术的发展,网络游戏已成国民经济的一部分并不段发展壮大。而网络五子棋游戏是起源于中国古代的传统黑白棋种之一,具有一定的历史渊源,且游戏易学易懂、上手快具有很大市场份额。

五子棋不仅能增强思维能力,提高智力,而且富含哲理,有助于修身养性。五子棋既有现代休闲的明显特征“短、平、快” ,又有古典哲学的高深学问“阴阳易理”它既有简单易学的特性,为人民群众所喜闻乐见,又有深奥的技巧和高水平的国际性比赛;它的棋文化渊源流长,具有东方的神秘和西方的直观;既有“场”的概念,亦有“点”的连接。它是中西文化的交流点,是古今哲理的结晶。一个好的五子棋游戏应该是集休闲、娱乐于一体;更应该成为玩家相互交流的一个重要窗口,作为计算机专业学生,见过玩过非常多的网络五子棋游戏,但大部分网络上流传的五子棋游戏功能并不尽善尽美,其中最主要的问题就是人机对战和网络对战不能够一起实现,所以本人决定借毕业设计开发一个既能够人机对战,又能够进行网络对战的五子棋系统,这

- 1 -

也正是选题的意义所在。

2 需求分析

2.1 开发背景

游戏作为日常休闲的娱乐活动之一,历经了电视游戏、电脑单机游戏、电脑网络游戏不同的发展阶段。互联网的出现为电脑游戏行业发展注入了新的活力,凭借信息双向交流、速度快、不受空间限制等优势,让真人参与游戏,提高了游戏的互动性、仿真性和竞技性,使玩家在虚拟世界里可以发挥现实世界无法展现的潜能,改变了单机版游戏固定、呆板、与机器对话的状况,而网络五子棋游戏更是具备这所有的特点。

当前网络上流传的五子棋游戏功能并不尽善尽美,其中最主要的问题就是人机对战和网络对战不能够一起实现,所以本人决定开发一个既能够人机对战,又能够进行网络对战的五子棋系统。

2.2 研究现状

网络作为一种新兴的传播方式,主要包括三大内容:娱乐、资讯、通讯。提到网络娱乐,过去主要指的是单机版游戏,没有引入网络的概念。但随着科技的发展,游戏娱乐产业也在成长。目前,国内的游戏娱乐产业正处于起步阶段,特点表现为:第一,它是一种文化的传播。娱乐产业可以潜移默化地改变人的观念,当前,很多多媒体的播放已被电脑网络所取代。第二,网络游戏加强了人与人的沟通。第三,网络游戏具有一定的教育意义。网络游戏所具有的角色扮演的功能,使得玩家能通过互助更好地完成游戏中的各项任务。网络无国界,游戏在网络文化产业世界的发展中地位会越来越高,因此开发一个功能强大界面美观的网络五子棋游戏是完全可行的。

目前在国外,休闲游戏如棋类等,玩家的年龄跨度非常大,这和我国目前网游市场以青少年为主要消费人群的状况截然不同。其实,网络可以解决空间的问题,网络和生活越来越息息相关,因此,开辟适合各个年龄层的游戏产品迫在眉睫。同时,这也涉及到一个企业开发的能力。娱乐产业发展到一定的程度,通过不断锻炼和经验的积累,完全可以通过融入娱乐的成分把教条的东西深入浅出地展现给消费者。

就国内的发展来看,最近的这两三年内国内的游戏公司如雨后春笋般的成立,所开发或代理的网络游戏更是不胜枚举。以全球游戏业界的发展来看,这几年韩国的表现最为突出,特別是在网络游戏的技术研发与游戏制作方面,其所发行的网络游戏更成为全球游戏产业重要的指标之一。去年在美国洛杉矶所举行的E3(Electronic Entertainment Exposition)展中,已经有几家韩国厂商挤入世界第一线的游戏开发厂商

- 2 -

之列。

近几年来,由于3D硬体绘图技术的突破,使得即时描绘的书面越来越精致,而且3D游戏性更多元化更逼近真实世界,因此在遊戏产业中,3D 游戏已经逐渐取代2D游戏为游戏市场的主流,即使是网络游戏,也慢慢趋向3D化。然而游戏3D化将会带来的游戏开发上的困难等问题,这些问题以后都需要逐步解决。

2.3 开发目的及意义

使用VC++开发一个极受大众欢迎的棋类游戏-网络五子棋游戏作为学习和掌握VC++的方法,从中学习VC++开发软件所要经历的过程,以便在以后的工作中能够熟练的掌握程序开发所具备的条件,选择网络五子棋这个课题是因为五子棋在中国的历史已经相当悠久,几乎人人都会玩,具有广阔的市场前景。

2.4 任务概述

本课题的主要任务为基于互联网络游戏娱乐而设计的,游戏的两种模式(单机版与网络版)给用户提供了不同的体验,网络聊天功能也给玩家提供了一个沟通平台,这也是与其他类别游戏的本质区别所在。本设计主要功能实现包含悔棋、和棋、认输、聊天、单机游戏与网络对战游戏、战绩统计、音乐欣赏以及优美的外观设计给用户提供了更好的选择权利、视觉效果以及更强的娱乐感;其中:

(1) 单人游戏模式,玩家与事先通过算法设置好的各种程序对弈。 (2) (3)

网络对弈模式,玩家通过网络IP绑定建立联接实现双方对弈。

更改玩家姓名功能,当玩家想在网络上下棋给时给自己取过好听的有吸引力的名字就可以通过此功能实现。 (4) (5) (6)

战绩统计功能,用来统计玩家在一定时间段的输赢情况。 关于功能,用来显示版权信息。

附加功能,当玩家累了想放松下就可以通过此功能来放些音乐听或者更改一下背景图案。 (7)

悔棋功能,在不同的游戏模式下悔棋的方式不同,在人机对弈模式下计算机允许玩家自由悔棋,而在网络对弈模式下则要向对方发送请求才能实现。 (8)

和棋功能,此功能是针对网络对弈模式下设计的,当玩家由于各种原因不能定输赢时就可以通过此功能向对方请求和棋。 (9)

认输功能,当玩家觉得自己败局已定时为了节约时间可以通过此功能向对方请求认输。

(10) 聊天功能,通过MFC的网络编程实现,双方可以通过此功能在下棋时互相交

- 3 -

流经验或感情。

本设计基于面向对象VC++程序设计,具有良好的通用性、兼容性、可操作性、可扩展性等,程序所有功能都做到风格统一、设计精巧、美观大方、能体现专业的美工和窗口设计水准;所有功能操作做到便捷实用能够对用户的各种操作提供友好提示;系统的设计和实施应遵循各类网络协议和国际标准,具备良好的开放性,充分考虑系统的可扩展性。

3 开发环境及运行环境

3.1 开发环境

(1) Intel? Pentium? 4 2.0GHz,512M内存,80G硬盘。 (2) Microsoft? Windows? 2000 Professional. (3) Microsoft? Visual C++ 6.0.

(4) Microsoft? Developer Network for Visual Studio.NET 2006. (5) Visual Assist X 10.1.1301.0.

3.2 运行环境

(1) Intel? Pentium? 2及以上处理器,32M以上内存,4G以上硬盘。 (2) Microsoft? Windows? 9X/NT操作系统。 (3) 800*600或以上的屏幕分辨率。

4 软件架构

考虑到整个的下棋过程(无论对方是电脑抑或其他网络玩家)可以分为:己方落子、等待对方落子、对方落子、设置己方棋盘数据这一系列过程,因此一人游戏类、二人游戏类和棋盘类之间的关系参考了AbstractFactory(抽象工厂)模式,以实现对两个不同模块进行一般化的控制。

软件的总体架构如图1:

一人游戏类 游戏类指针 棋盘类 主界面

- 4 -

二人游戏类 用户 图1 软件架构 Figure 1 software architecture

4.1 棋盘类

整个架构的核心部分,类名为CTable。封装了棋盘的各种可能用到的功能,如保存棋盘数据、初始化、判断胜负等。用户操作主界面,主界面与CTable进行交互来完成对游戏的操作。

4.2 游戏模式类

用来管理人机对弈/网络对弈两种游戏模式,类名为CGame。CGame是一个抽象类,经由它派生出一人游戏类COneGame和网络游戏类CtwoGame 。

这样,CTable类就可以通过一个CGame类的指针,在游戏初始化的时候根据具体游戏模式的要求实例化COneGame或CTwoGame类的对象;然后利用多态性,使用CGame类提供的公有接口就可以完成不同游戏模式下的不同功能了。如图2:

COneGame 图2 CGame类派生关系

Figure 2 CGame derived class relationship

CTwoGame 抽象类CGame 5 棋盘类——CTable

5.1 主要成员变量说明

5.1.1 网络连接标志——m_bConnected

用来表示当前网络连接的情况,在网络对弈游戏模式下客户端连接服务器的时候用来判断是否连接成功;事实上它也是区分当前游戏模式的唯一标志。 5.1.2 棋盘等待标志——m_bWait与m_bOldWait

由于在玩家落子后需要等待对方落子,m_bWait标志就用来标识棋盘的等待状态。当m_bWait为TRUE时,是不允许玩家落子的。

在网络对弈模式下,玩家之间需要互相发送诸如悔棋、和棋这一类的请求消息,

- 5 -

在发送请求后等待对方回应时,也是不允许落子的,所以需要将m_bWait标志置为TRUE。在收到对方回应后,需要恢复原有的棋盘等待状态,所以需要另外一个变量在发送请求之前保存棋盘的等待状态做恢复之用,也就是m_bOldWait。

等待标志的设置,由成员函数SetWait和RestoreWait完成。 5.1.3 网络套接字——m_sock和m_conn

在网络对弈游戏模式下,需要用到这两个套接字对象。其中m_sock对象用于做服务器时的监听之用,m_conn用于网络连接的传输。 5.1.4 棋盘数据——m_data

这是一个15*15的二维数组,用来保存当前棋盘的落子数据。其中对于每个成员来说,0表示落黑子,1表示落白子,-1表示无子。 5.1.5 游戏模式指针——m_pGame

这个CGame类的对象指针是CTable类的核心内容。它所指向的对象实体决定了CTable在执行一件事情时候的不同行为,具体的内容请参见“游戏模式”一节。

5.2 主要成员函数说明

5.2.1 套接字的回调处理——Accept、Connect、Receive

本程序的套接字派生自MFC的CAsyncSocket类,CTable的这三个成员函数就分别提供了对套接字回调事件OnAccept、OnConnect、OnReceive的实际处理,其中尤以Receive成员函数重要,它之中包含了对所有网络消息的分发处理。 5.2.2 清空棋盘——Clear

在每一局游戏开始的时候都需要调用这个函数将棋盘清空,也就是棋盘的初始化工作。此函数主要完成以下几件事情:

(1) 将m_data中每一个落子位都置无子状态为-1。

(2) 按照传入的参数设置棋盘等待标志m_bWait,以供先、后手的不同情况之用。 (3) 使用delete将m_pGame指针所指向的原有游戏模式对象从堆上删除。 5.2.3 绘制棋子——Draw

此函数根据参数给定的坐标和颜色绘制棋子。绘制的详细过程如下: (1) 将给定的棋盘坐标换算为绘图的像素坐标。 (2) 根据坐标绘制棋子位图。

(3) 如果先前曾下过棋子,则利用R2_NOTXORPEN将上一个绘制棋子上的最后落子指示矩形擦除。

(4) 在刚绘制完成的棋子四周绘制最后落子指示矩形。

- 6 -

5.2.4 左键消息——OnLButtonUp

作为棋盘唯一响应的左键消息,也需要做不少的工作:

(1) 如果棋盘等待标志m_bWait为TRUE,则直接发出警告声音并返回,即禁止落子。

(2) 如果点击时的鼠标坐标在合法坐标(0, 0)~(14, 14)之外亦禁止落子。 (3) 如果走的步数大于1步,方才允许悔棋。

(4) 进行胜利判断,如胜利则修改UI状态并增加胜利数的统计。 (5) 如未胜利,则向对方发送已经落子的消息。

(6) 落子完毕,将m_bWait标志置为TRUE,开始等待对方回应。 5.2.5 绘制棋盘——OnPaint

每当WM_PAINT消息触发时,都需要对棋盘进行重绘。OnPaint作为响应绘制消息的消息处理函数使用了双缓冲技术,减少了多次绘图可能导致的图像闪烁问题。此函数主要完成以下工作:

(1) 装载棋盘位图并进行绘制。 (2) 根据棋盘数据绘制棋子。 (3) 绘制最后落子指示矩形。 5.2.6 对方落子完毕——Over

在对方落子之后,仍然需要做一些判断工作

(1) 如果落子坐标在合法坐标(0, 0)~(14, 14)之外亦禁止落子。 (2) 如果走的步数大于1步,方才允许悔棋。

(3) 进行胜利判断,如胜利则修改UI状态并增加胜利数的统计。 (4) 如未胜利,则向对方发送已经落子的消息。

(5) 落子完毕,将m_bWait标志置为TRUE,开始等待对方回应。 5.2.7 设置游戏模式——SetGameMode

这个函数通过传入的游戏模式参数对m_pGame指针进行了初始化,代码如下: void CTable::SetGameMode( int nGameMode ) {

if ( 1 == nGameMode )

m_pGame = new COneGame( this ); else

m_pGame = new CTwoGame( this );

- 7 -

m_pGame->Init(); }

这之后,就可以利用OO的继承和多态性特点来使m_pGame指针使用相同的调用来完成不同的工作了,事实上,COneGame::Init和CTwoGame::Init都是不同的。 5.2.8 胜负的判断——Win

这是游戏中一个极其重要的算法,用来判断当前棋盘的形势是哪一方获胜。其详细内容请参见“主要算法”一节。

6 游戏模式类——CGame

此类负责对游戏模式进行管理,以及在不同的游戏模式下对不同的用户行为进行不同的响应。由于并不需要CGame本身进行响应,所以将其设计为了一个纯虚类,它的定义如下: class CGame { protected:

CTable *m_pTable; public:

list< STEP > m_StepList; // 落子步骤 public:

CGame( CTable *pTable ) : m_pTable( pTable ) {}// 构造函数 virtual ~CGame();// 析构函数 virtual void Init() = 0; // 初始化工作

// 处理胜利后的情况,CTwoGame需要改写此函数完成善后工作

virtual void Win( const STEP& stepSend );

// 发送己方落子

virtual void SendStep( const STEP& stepSend ) = 0;

virtual void ReceiveMsg( MSGSTRUCT *pMsg ) = 0; // 接收对方消息 virtual void Back() = 0; // 发送悔棋请求 };

6.1 主要成员变量说明

6.1.1 棋盘指针——m_pTable

由于在游戏中需要对棋盘以及棋盘的父窗口——主对话框进行操作及UI状态设

- 8 -

置,故为CGame类设置了这个成员。当对主对话框进行操作时,可以使用m_pTable->GetParent()得到它的窗口指针。 6.1.2 落子步骤——m_StepList

好的棋类程序必须要考虑悔棋功能,所以需要为游戏类设置一个落子步骤列表。而人机对弈和网络对弈中都需要这个功能,故将这个成员直接设置到基类CGame中。另外,考虑到使用的简便性,这个成员使用了C++标准模板库(Standard Template Library,STL)中的std::list,而不是MFC的CList。

6.2 主要成员函数说明

6.2.1 悔棋操作——Back

在不同的游戏模式下,悔棋的行为是不一样的。

(1) 人机对弈模式下,计算机是完全允许玩家悔棋的,但是出于对程序负荷的考虑,只允许玩家悔当前的两步棋(计算机一步,玩家一步)。

(2) 双人网络对弈模式下,悔棋的过程为:首先由玩家向对方发送悔棋请求(悔棋消息),然后由对方决定是否允许玩家悔棋,在玩家得到对方的响应消息(允许或者拒绝)之后,才进行悔棋与否的操作。 6.2.2 初始化操作——Init

对于不同的游戏模式而言,也就有不同的初始化方式。对于人机对弈模式而言,初始化操作包括以下几个步骤:

(1) 设置网络连接状态m_bConnected为FALSE。 (2) 设置主界面计算机玩家的姓名。 (3) 初始化所有的获胜组合。

(4) 如果是计算机先走,则占据天元(棋盘正中央)的位置。 网络对弈的初始化工作暂为空,以供以后扩展之用。 6.2.3 接收来自对方的消息——ReceiveMsg

这个成员函数由CTable棋盘类的Receive成员函数调用,用于接收来自对方的消息。在人机对弈游戏模式下,所能接收到的是本地模拟的落子消息MSG_PUTSTEP;在网络对弈游戏模式下,这个成员函数则负责从套接字读取对方发过来的数据,然后将这些数据解释为自定义的消息结构,并回到CTable::Receive来进行处理。 6.2.4 发送落子消息——SendStep

在玩家落子结束后,要向对方发送自己落子的消息。对于不同的游戏模式, 发送的目标不同:

- 9 -

(1) 在人机对弈游戏模式,直接把落子的信息(坐标、颜色)发送给COneGame类相应的计算函数。

(2) 在网络对弈游戏模式,则把落子消息发送给套接字,并由套接字转发给对方。

6.2.5 胜利后的处理——Win

这个成员函数主要针对CTwoGame网络对弈模式。在玩家赢得棋局后,这个函数游戏端经由CTable::Win来判定双方胜败。

7 消息机制

Windows系统拥有自己的消息机制,在不同事件发生的时候,系统可以提供不同的响应方式。五子棋程序模仿Windows系统实现了自己的消息机制,主要为网络对弈服务,以响应不同的网络消息。

7.1 消息机制的架构

当继承CAsyncSocket的套接字类CFiveSocket收到消息时,会触发CFiveSocket::OnReceive事件,在这个事件中调用CTable::Receive,CTable::Receive开始按照自定义的消息格式接收套接字发送的数据,并对不同的消息类型进行分发处理。

当CTable获得了来自网络的消息之后,就可以使用一个switch结构来进行消息的分发了。消息机制过程如图3:

CFiveSocket 网络数据 CFiveSocket::OnReceive 调用 CTable::Receive 分发处理 CFiveSocket::Receive - 10 -

图3 自定义的消息机制

Figure 3 defined mechanism for information

7.2 各种消息说明

网络间传递的消息,都遵循以下一个结构体的形式: typedef struct _tagMsgStruct { // 摘自Messages.h UINT uMsg; // 消息ID int x; // 落子信息 int y; int color;

TCHAR szMsg[128]; // 消息内容

} MSGSTRUCT;

随着uMsg表示消息ID,x 、y表示落子的坐标 ,color表示落子的颜色 ,szMsg随着uMsg的不同而有不同的含义。 7.2.1 落子消息——MSG_PUTSTEP

表明对方落下了一个棋子,其中x、y和color成员有效,szMsg成员无效。在人机对弈游戏模式下,亦会模拟发送此消息以达到程序模块一般化的效果。 7.2.2 聊天消息——MSG_CHAT

表明对方发送了一条聊天信息,szMsg表示对方的信息,其余成员无效。接到这个信息后,会将对方聊天的内容显示在主对话框的聊天记录窗口内。如图4:

- 11 -

图4 聊天窗口 Figure 4 chat window

7.2.3 悔棋消息——MSG_BACK

表明对方请求悔棋,除uMsg成员外其余成员皆无效。接到这个消息后,会弹出MessageBox询问是否接受对方的请求(如图5所示),并根据玩家的选择回返MSG_AGREEBACK或MSG_REFUSEBACK消息。另外,在发送这个消息之后,主界面上的某些元素将不再响应用户的操作。

图5 请求悔棋 Figure 5 calculate request

7.2.4 同意悔棋消息——MSG_AGREEBACK

表明对方接受了玩家的悔棋请求,除uMsg成员外其余成员皆无效。接到这个消息后,将进行正常的悔棋操作。

7.2.5 拒绝悔棋消息——MSG_REFUSEBACK

表明对方拒绝了玩家的悔棋请求(如图6所示),除uMsg成员外其余成员皆无效。接到这个消息后,整个界面将恢复发送悔棋请求前的状态。

- 12 -

图6 拒绝悔棋 Figure 6 refuse to calculate

7.2.6 和棋消息——MSG_DRAW

表明对方请求和棋,除uMsg成员外其余成员皆无效。接到这个消息后,会弹出MessageBox询问是否接受对方的请求(如图7所示),并根据玩家的选择回返MSG_AGREEDRAW或MSG_REFUSEDRAW消息。另外,在发送这个消息之后,主界面上的某些元素将不再响应用户的操作。

图7 请求和棋

Figure 7 request for a draw in chess

7.2.7 同意和棋消息——MSG_AGREEDRAW

表明对方接受了玩家的和棋请求(如图8所示),除uMsg成员外其余成员皆无效。接到这个消息后,双方和棋。

图8 同意和棋

Figure 8 agree to a draw in chess

7.2.8 拒绝和棋消息——MSG_REFUSEDRAW

表明对方拒绝了玩家的和棋请求(如图9所示),除uMsg成员外其余成员皆无

- 13 -

效。接到这个消息后,整个界面将恢复发送和棋请求前的状态。

图9 拒绝和棋

Figure 9 refused a draw in chess

7.2.9 认输消息——MSG_GIVEUP

表明对方已经投子认输(如图10所示),除uMsg成员外其余成员皆无效。接到这个消息后,整个界面将转换为胜利后的状态。

图10 认输 Figure 10 Lose

7.2.10 对方信息消息——MSG_INFORMATION

用来获取对方玩家的姓名,szMsg表示对方的姓名,其余成员无效。在开始游戏的时候,由客户端向服务端发送这条消息,服务端接到后设置对方的姓名,并将自己的姓名同样用这条消息回发给客户端。 7.2.11 再次开局消息——MSG_PLAYAGAIN

表明对方希望开始一局新的棋局,除uMsg成员外其余成员皆无效。接到这个消息后,会弹出MessageBox询问是否接受对方的请求(如图11所示),并根据玩家的选择回返MSG_AGREEAGAIN消息或直接断开网络。

- 14 -

图11 再次开局 Figure 11 start again

7.2.12 同意再次开局消息——MSG_AGREEAGAIN

表明对方同意了再次开局的请求,除uMsg成员外其余成员皆无效。接到这个消息后,将开启一局新游戏。

8 主要算法

无论是人机对弈,还是网络对弈,都需要合理算法的支持,本节中将详细介绍五子棋中使用的算法。

8.1 判断胜负

五子棋的胜负,在于判断棋盘上是否有一个点,从这个点开始的右、下、右下、左下四个方向是否有连续的五个同色棋子出现,如图12:

图12 判断胜负方向

Figure 12 judging the outcome direction

这个算法也就是CTable的Win成员函数。从设计的思想上,需要它接受一个棋子颜色的参数,然后返回一个布尔值,这个值来指示是否胜利,代码如下: BOOL CTable::Win( int color ) const {

int x, y;

for ( y = 0; y < 15; y++ ) // 判断横向 {

for ( x = 0; x < 11; x++ ) {

if ( color == m_data[x][y] &&

- 15 -

color == m_data[x + 1][y] &&

color == m_data[x + 2][y] &&

color == m_data[x + 3][y] &&

color == m_data[x + 4][y] ) {

return TRUE; } } }

for ( y = 0; y < 11; y++ ) // 判断纵向 {

for ( x = 0; x < 15; x++ ) {

if ( color == m_data[x][y] &&

color == m_data[x][y + 1] &&

color == m_data[x][y + 2] &&

color == m_data[x][y + 3] &&

color == m_data[x][y + 4] ) {

return TRUE; } } }

for ( y = 0; y < 11; y++ ) // 判断“\\”方向 {

for ( x = 0; x < 11; x++ ) {

if ( color == m_data[x][y] &&

color == m_data[x + 1][y + 1] &&

color == m_data[x + 2][y + 2] &&

color == m_data[x + 3][y + 3] &&

- 16 -

color == m_data[x + 4][y + 4] ) {

return TRUE; } } }

for ( y = 0; y < 11; y++ ) // 判断“/”方向 {

for ( x = 4; x < 15; x++ ) {

if ( color == m_data[x][y] &&

color == m_data[x - 1][y + 1] &&

color == m_data[x - 2][y + 2] &&

color == m_data[x - 3][y + 3] &&

color == m_data[x - 4][y + 4] ) {

return TRUE; } } }

return FALSE; // 不满足胜利条件 }

由于这个算法所遵循的搜索顺序是从左到右、自上而下,因此在每次循环的时候,都有一些坐标无需纳入考虑范围。例如对于横向判断而言,由于右边界所限,因而所有横坐标大于等于11的点,都构不成五子连的条件,所以横坐标的循环上界也就定为11,这样也就提高了搜索的速度。

8.2 人机对弈算法

人机对弈算法完全按照CGame基类定义的接口标准,封装在了COneGame派生类之中。下面将对这个算法进行详细地介绍。 8.2.1 获胜组合

获胜组合是一个三维数组,它记录了所有取胜的情况。也就是说,参考于

- 17 -

CTable::Win中的情况,对于每一个落子坐标,获胜的组合一共有15 * 11 * 2 + 11 * 11 * 2 = 572种。而对于每个坐标的获胜组合,应该设置一个[15][15][572]大小的三维数组。在拥有了这些获胜组合之后,就可以参照每个坐标的572种组合给自己的局面和玩家的局面进行打分,也就是根据当前盘面中某一方所拥有的获胜组合多少进行权值的估算,给出最有利于自己的一步落子坐标。由于是双方对弈,所以游戏的双方都需要一份获胜组合,也就是:

bool m_Computer[15][15][572]; // 电脑获胜组合 bool m_Player[15][15][572]; // 玩家获胜组合

在每次游戏初始化(COneGame::Init)的时候,需要将每个坐标下可能的获胜组合都置为true。此外,还需要设置计算机和玩家在各个获胜组合中所填入的棋子数定义数组为int m_Win[2][572];在初始化的时候,将每个棋子数置为0。 8.2.2 落子后处理

每当一方落子后,都需要作如下处理:

(1) 如果己方此坐标的获胜组合仍为true,且仍有可能在此获胜组合处添加棋子,则将此获胜组合添加棋子数加1。

(2) 如果对方此坐标的获胜组合仍为true,则将对方此坐标的获胜组合置为false,并将对方此获胜组合添加棋子数置为-1(不可能靠此组合获胜)。 以玩家落子为例,代码为: for ( i = 0; i < 572; i++ ) {

// 修改状态变化

if ( m_Player[stepPut.x][stepPut.y][i] &&m_Win[0][i] != -1 ) m_Win[0][i]++;

if ( m_Computer[stepPut.x][stepPut.y][i] ) {

m_Computer[stepPut.x][stepPut.y][i] = false; m_Win[1][i] = -1; } }

8.2.3 查找棋盘空位

在计算机落子之前,需要查找棋盘的空位,所以需要一个SearchBlank成员函数

- 18 -

完成此项工作,此函数需要进行不重复查找,也就是说,对已查找过的空位进行标记,并返回找到空位的坐标,其代码如下:

bool COneGame::SearchBlank( int &i, int &j,int nowTable[][15] ) {

int x, y;

for ( x = 0; x < 15; x++ ) {

for ( y = 0; y < 15; y++ ) {

if ( nowTable[x][y] == -1 && nowTable[x][y] != 2 ) { i = x; j = y; return true; } } }

return false; }

8.2.4 落子打分

找到空位后,需要对这个点的落子进行打分,这个分数是这个坐标重要性的体现,代码如下:

int COneGame::GiveScore( const STEP& stepPut ) {

int i, nScore = 0; for ( i = 0; i < 572; i++ ) {

if ( m_pTable->GetColor() == stepPut.color ) {

if ( m_Player[stepPut.x][stepPut.y][i] ) // 玩家下 {

- 19 -

switch ( m_Win[0][i] ) { case 1:

nScore -= 5; break; case 2:

nScore -= 50; break; case 3:

nScore -= 500; break; case 4:

nScore -= 5000; break; default: break; } } } else {

// 计算机下

if ( m_Computer[stepPut.x][stepPut.y][i] ) {

switch ( m_Win[1][i] ) { case 1:

nScore += 5; break; case 2:

nScore += 50;

- 20 -

break; case 3:

nScore += 100; break; case 4:

nScore += 10000; break; default: break; } } } }

return nScore; }

如代码所示,考虑到攻守两方面的需要,所以将玩家落子给的分数置为负值。 8.2.5 防守策略

落子的考虑不单单要从进攻考虑,还要从防守考虑。这一细节的实现是让计算机从玩家棋盘布局分析战况,然后找出对玩家最有利的落子位置。整个过程如下: for ( m = 0; m < 572; m++ ) {

if ( m_Player[i][j][m] ) // 暂时更改玩家信息 {

temp1[n] = m;

m_Player[i][j][m] = false; temp2[n] = m_Win[0][m]; m_Win[0][m] = -1; n++; } }

ptempTable[i][j] = 0;

- 21 -

pi = i; pj = j;

while ( SearchBlank( i, j, ptempTable ) ) {

ptempTable[i][j] = 2; // 标记已被查找 step.color = m_pTable->GetColor(); step.x = i; step.y = j;

ptemp = GiveScore( step );

// 此时为玩家下子,运用极小极大法时应选取最小值 if ( pscore > ptemp ) pscore = ptemp; }

for ( m = 0; m < n; m++ ) {

m_Player[pi][pj][temp1[m]] = true; // 恢复玩家信息 m_Win[0][temp1[m]] = temp2[m]; }

8.2.6 选取最佳落子

在循环结束的时候,就可以根据攻、守两方面的打分综合地考虑落子位置。代码如下:

if ( ctemp + pscore > cscore ) {

cscore = ctemp + pscore; bestx = pi; besty = pj; }

在这之后,重新改变一下棋盘的状态(6.2.2)即可。

9 总结

本设计有关网络Socket编程、博弈树算法的知识都参考了Internet上的相关源代码,系统的架构、程序窗口设计、功能模块设计完全靠自己独立完成,几千上万行的代

- 22 -

本文来源:https://www.bwwdw.com/article/aztg.html

Top