五子棋设计与实现

1 引言

1.1 五子棋介绍

五子棋是起源于中国古代的传统黑白棋种之一。现代五子棋日文称之为“連珠”,英译为“Renju”,英文称之为“Gobang”或“FIR”(Five in a Row的缩写),亦有“连五子”、“五子连”、“串珠”、“五目”、“五目碰”、“五格”等多种称谓。

五子棋不仅能增强思维能力,提高智力,而且富含哲理,有助于修身养性。五子棋既有现代休闲的明显特征“短、平、快”,又有古典哲学的高深学问“阴阳易理”;它既有简单易学的特性,为人民群众所喜闻乐见,又有深奥的技巧和高水平的国际性比赛;它的棋文化源渊流长,具有东方的神秘和西方的直观;既有“场”的概念,亦有“点”的连接。它是中西文化的交流点,是古今哲理的结晶。

1.2 开发背景

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

1.3 开发环境及运行环境

1.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 2003
  5. Visual Assist X 10.1.1301.0

1.3.2 运行环境

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

2 软件架构

软件的总体架构如图2.1:

图2.1 软件架构

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

2.1 棋盘类

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

2.2 游戏模式类

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

                          图2.2 CGame类派生关系

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

3 棋盘类——CTable

3.1 主要成员变量说明

3.1.1 网络连接标志——m_bConnected

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

3.1.2 棋盘等待标志——m_bWait与m_bOldWait

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

在网络对弈模式下,玩家之间需要互相发送诸如悔棋、和棋这一类的请求消息,在发送请求后等待对方回应时,也是不允许落子的,所以需要将m_bWait标志置为TRUE。在收到对方回应后,需要恢复原有的棋盘等待状态,所以需要另外一个变量在发送请求之前保存棋盘的等待状态做恢复之用,也就是m_bOldWait。

等待标志的设置,由成员函数SetWait和RestoreWait完成。

3.1.3 网络套接字——m_sock和m_conn

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

3.1.4 棋盘数据——m_data

这是一个15*15的二位数组,用来保存当前棋盘的落子数据。其中对于每个成员来说,0表示落黑子,1表示落白子,-1表示无子。

3.1.5 游戏模式指针——m_pGame

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

3.2 主要成员函数说明

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

本程序的套接字派生自MFC的CAsyncSocket类[6],CTable的这三个成员函数就分别提供了对套接字[7]回调事件OnAccept、OnConnect、OnReceive的实际处理,其中尤以Receive成员函数重要,它之中包含了对所有网络消息(参见“消息机制”一节)的分发处理。

3.2.2 清空棋盘——Clear

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

  1. 将m_data中每一个落子位都置为无子状态(-1)。
  2. 按照传入的参数设置棋盘等待标志m_bWait,以供先、后手的不同情况之用。
  3. 使用delete将m_pGame指针所指向的原有游戏模式对象从堆上删除。

3.2.3 绘制棋子——Draw

这无疑是很重要的一个函数,它根据参数给定的坐标和颜色绘制棋子。绘制的详细过程如下:

  1. 将给定的棋盘坐标换算为绘图的像素坐标。
  2. 根据坐标绘制棋子位图。
  3. 如果先前曾下过棋子,则利用R2_NOTXORPEN将上一个绘制棋子上的最后落子指示矩形擦除。
  4. 在刚绘制完成的棋子四周绘制最后落子指示矩形。

3.2.4 左键消息——OnLButtonUp

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

  1. 如果棋盘等待标志m_bWait为TRUE,则直接发出警告声音并返回,即禁止落子。
  2. 如果点击时的鼠标坐标在合法坐标(0, 0)~(14, 14)之外,亦禁止落子。
  3. 如果走的步数大于1步,方才允许悔棋。
  4. 进行胜利判断,如胜利则修改UI状态并增加胜利数的统计。
  5. 如未胜利,则向对方发送已经落子的消息。
  6. 落子完毕,将m_bWait标志置为TRUE,开始等待对方回应。

3.2.5 绘制棋盘——OnPaint

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

  1. 装载棋盘位图并进行绘制。
  2. 根据棋盘数据绘制棋子。
  3. 绘制最后落子指示矩形。

3.2.6 对方落子完毕——Over

在对方落子之后,仍然需要做一些判断工作,这些工作与OnLButtonUp中的类似,在此不再赘述。

3.2.7 设置游戏模式——SetGameMode

这个函数通过传入的游戏模式参数对m_pGame指针进行了初始化,代码如下:

void CTable::SetGameMode( int nGameMode )

{

    if ( 1 == nGameMode )

        m_pGame = new COneGame( this );

    else

        m_pGame = new CTwoGame( this );

    m_pGame->Init();

}

这之后,就可以利用OO的继承和多态特点[8]来使m_pGame指针使用相同的调用来完成不同的工作了,事实上,COneGame::Init和CTwoGame::Init都是不同的。

3.2.8 胜负的判断——Win

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

4 游戏模式类——CGame

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

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;

};

4.1 主要成员变量说明

4.1.1 棋盘指针——m_pTable

由于在游戏中需要对棋盘以及棋盘的父窗口——主对话框进行操作及UI状态设置,故为CGame类设置了这个成员。当对主对话框进行操作时,可以使用m_pTable->GetParent()得到它的窗口指针。

4.1.2 落子步骤——m_StepList

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

4.2 主要成员函数说明

4.2.1 悔棋操作——Back

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

  1. 人机对弈模式下,计算机是完全允许玩家悔棋的,但是出于对程序负荷的考虑(此原因请参见“几点补充说明”一节),只允许玩家悔当前的两步棋(计算机一步,玩家一步)。
  2. 双人网络对弈模式下,悔棋的过程为:首先由玩家向对方发送悔棋请求(悔棋消息),然后由对方决定是否允许玩家悔棋,在玩家得到对方的响应消息(允许或者拒绝)之后,才进行悔棋与否的操作。

4.2.2 初始化操作——Init

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

  1. 设置网络连接状态m_bConnected为FALSE。
  2. 设置主界面计算机玩家的姓名。
  3. 初始化所有的获胜组合。
  4. 如果是计算机先走,则占据天元(棋盘正中央)的位置。

网络对弈的初始化工作暂为空,以供以后扩展之用。

4.2.3 接收来自对方的消息——ReceiveMsg

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

4.2.4 发送落子消息——SendStep

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

  1. 对于人机对弈游戏模式,将直接把落子的信息(坐标、颜色)发送给COneGame类相应的计算函数。
  2. 对于网络对弈游戏模式,将把落子消息发送给套接字,并由套接字转发给对方。

4.2.5 胜利后的处理——Win

这个成员函数主要针对CTwoGame网络对弈模式。在玩家赢得棋局后,这个函数仍然会调用SendStep将玩家所下的制胜落子步骤发送给对方玩家,然后对方的游戏端经由CTable::Win来判定自己失败。

5 消息机制

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

5.1 消息机制的架构

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

                        图5.1 自定义的消息机制

如图5.1所示,当CTable获得了来自网络的消息之后,就可以使用一个switch结构来进行消息的分发了。

5.2 各种消息说明

网络间传递的消息,都遵循以下一个结构体的形式:

// 摘自Messages.h

typedef struct _tagMsgStruct {

    // 消息ID

    UINT uMsg;

    // 落子信息

    int x;

    int y;

    int color;

    // 消息内容

    TCHAR szMsg[128];

} MSGSTRUCT;

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

5.2.1 落子消息——MSG_PUTSTEP

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

5.2.2 悔棋消息——MSG_BACK

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

图5.2 请求悔棋

5.2.3 同意悔棋消息——MSG_AGREEBACK

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

5.2.4 拒绝悔棋消息——MSG_REFUSEBACK

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

图5.3 拒绝悔棋

5.2.5 和棋消息——MSG_DRAW

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

图5.4 请求和棋

5.2.6 同意和棋消息——MSG_AGREEDRAW

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

图5.5 同意和棋

5.2.7 拒绝和棋消息——MSG_REFUSEDRAW

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

图5.6 拒绝和棋

5.2.8 认输消息——MSG_GIVEUP

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

图5.7 认输

5.2.9 聊天消息——MSG_CHAT

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

5.2.10 对方信息消息——MSG_INFORMATION

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

5.2.11 再次开局消息——MSG_PLAYAGAIN

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

图5.8 再次开局

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

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

6 主要算法

五子棋游戏中,有相当的篇幅是算法的部分。无论是人机对弈,还是网络对弈,都需要合理算法的支持,本节中将详细介绍五子棋中使用的算法。[13]

6.1 判断胜负

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

图6.1 判断胜负方向

这个算法也就是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] &&

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] &&

                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,这样也就提高了搜索的速度。

6.2 人机对弈算法

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

6.2.1 获胜组合

获胜组合是一个三维数组,它记录了所有取胜的情况。也就是说,参考于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。

6.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;

    }

}

6.2.3 查找棋盘空位

在计算机落子之前,需要查找棋盘的空位,所以需要一个SearchBlank成员函数完成此项工作,此函数需要进行不重复的查找,也就是说,对已查找过的空位进行标记,并返回找到空位的坐标,其代码如下:

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;

}

6.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] )

            {

                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;

                    break;

                case 3:

                    nScore += 100;

                    break;

                case 4:

                    nScore += 10000;

                    break;

                default:

                    break;

                }

            }

        }

    }

    return nScore;

}

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

6.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;

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];

}

6.2.6 选取最佳落子

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

if ( ctemp + pscore > cscore )

{

    cscore = ctemp + pscore;

    bestx = pi;

    besty = pj;

}

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

7 几点补充说明

  1. 考虑到程序的响应速度,人机对弈算法只对玩家的棋子进行了一步的推测。
  2. 由于计算机在落子时选取的是得分最高的一步落子,所以如果玩家在开局的时候不改变落子步骤,那么将会获得从头至尾相同的棋局。
  3. 考虑到下棋同时还要聊天,所以并未对落子时间加入任何限制,同样如果玩家离开游戏也不会判负。
  4. 对于人机对弈的悔棋处理,由于这个算法的开销相当大,每一步落子都会存在不同的棋盘布局,所以实现从头到尾的悔棋不是很现实(将会存在过多的空间保存棋盘布局),因而在人机对弈模式下,只允许玩家悔最近的两步落子。

8 心得体会

通过编写这个程序,我体会最为深刻的一点是系统架构和设计模式的重要性。即使是对于一个并不大的程序,代码的组织都是非常重要的,因为这关系到日后的维护以及扩展。这个游戏之中,有关网络Socket编程或者博弈树算法的知识都可以直接从无所不包的Internet上获取,甚至可以直接获得一个完整的五子棋人机对弈算法的源代码级模块。但是对于系统的架构,却完全是自己的事情,几千上万行的代码需要通过合适的方法组织起来,使程序员编写代码更加有条理,更加符合软件工程的标准,这才是最重要的。

在刚开始编写这个程序的时候,我幼稚地认为其中最重要的是博弈树算法。但是头一个月编写程序的时候却发现程序越写越不容易维护,可见是我走错了方向。后来我向公司真正的软件设计人员及系统架构师讨教,他们告诉我:我们的先人早已为我们准备好了各种精良可用的现成算法,我们所要做的就是直接“拿来主义”罢了;但是对于代码的组织(也就是软件的架构)才是真正软件工业的核心部分,因为软件事实上是直接和经济挂钩的,因此我们必须在编写代码之前选择一种最为合适的方法来组织这些代码,否则我们将会失去更多的时间和金钱。[15]

于是,我将以前写的代码全部删除,认真地思考了三天的时间。我也在这三天内真正从一个学生程序员走入了软件开发的大门,我开始发现其实软件开发并不是纯数学——正相反,数学只占了很小的一部分。它其实是一种哲学,一种有着数学美感的哲学。

参考文献

    1. MSDN for Visual Studio 6.0
    2. 设计模式——可复用面向对象软件的基础,Erich Gamma/Richard Helm/Ralph Johnson/John Vlissides著,李英军/马晓星/蔡敏/刘建中 等译,机械工业出版社
    3. 深入浅出MFC(第2版),侯俊杰著,华中科技大学出版社
    4. A Beginner 's Guide to Pointers,Andrew Peace

http://www.codeproject.com/cpp/pointers.asp

    1. 水煮多态,titilima

http://home.nuc.edu.cn/~titilima/readarticle.php?id=53

    1. Microsoft® Visual C++.NET 技术内幕(第6版),George Shepherd/David Kruglinski著,潘爱民译,清华大学出版社
    2. Visual C++网络通信协议分析与应用实现,汪晓平/钟军 等编著,人民邮电出版社
    3. C++编程思想,Bruce Eckel著,刘宗田/邢大红/孙慧杰 等译,机械工业出版社
    4. 21天学通C++,Jesse Liberty著,康博创作室译,人民邮电出版社
    5. C++标准程序库,Nicolai M.Josuttis著,侯捷/孟岩 译,华中科技大学出版社
    6. Windows程序设计,Charles Petzold著,北京博彦科技发展有限公司译,北京大学出版社
    7. Visual C++.NET网络编程,易君 编著,中国铁道出版社
    8. 博弈树搜索

http://202.113.96.26/wlkc/rengongzhineng/rengongzhineng/kejian/AI/Ai/chapter3/33.htm

    1. 五子棋的核心算法,蝈蝈俊.net

http://blog.joycode.com/ghj/articles/12727.aspx

    1. 道法自然,王咏武/王咏刚 著,电子工业出版社

致谢

感谢我的父母,没有您们的包容和支持,就不会有我的今天。

感谢我的导师康珺老师,您开明地为我进京实习提供了那么多便利条件。

感谢北京灵图软件技术有限公司,为我无偿提供了开发环境。

感谢kedy,谢谢你为我提供的代理服务器,让我在学校的时候能够及时上网获取资料。在此祝福你能在金山开创美好的未来。

感谢中北大学信息中心的唐道光学长,为我解释了很多网络连接的知识。

感谢武汉大学的姜舸同学,谢谢你在工作之余和我一起进行CAsyncSocket部分的测试。

感谢502的所有兄弟,尤其是黑皮和RedBad2,谢谢你们在我离校期间为我所作的一切。

感谢不一样的鱼、hottey、赵晋辉、杜琳和赵序庚同学,谢谢你们在测试期提供的帮助和修改意见。

感谢APTX4869字幕组,谢谢你们提供的《名侦探柯南》。尤其是情报员源源,谢谢你一直以来为我提供的一切便利条件。

感谢Rockstar Games公司,你们的Grand Theft Auto: Vice City伴我度过了下班后的无聊时光。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
基于Android的五子棋游戏是一款经典的策略游戏,本文将介绍它的设计实现。 一、需求分析 1. 游戏规则:五子棋是一种双人对弈的纯策略型棋类游戏,棋盘为15×15的方格。 2. 游戏模式:单人模式和双人模式。 3. 游戏功能:开始游戏、悔棋、重新开始、退出游戏、胜负判断。 二、系统设计 1. 界面设计:主界面、游戏界面、设置界面。 2. 功能设计:开始游戏、悔棋、重新开始、退出游戏、胜负判断。 3. 数据存储:使用SQLite存储游戏记录、玩家信息等。 三、系统实现 1. 界面实现:使用Android Studio进行界面设计实现。 2. 功能实现: (1)开始游戏:点击开始游戏按钮,进入游戏界面。 (2)悔棋:在游戏过程中,点击悔棋按钮,可以撤销上一步棋的操作。 (3)重新开始:在游戏结束后,点击重新开始按钮,可以重新开始游戏。 (4)退出游戏:在游戏过程中或者游戏结束后,点击退出游戏按钮,可以退出游戏。 (5)胜负判断:在游戏过程中,每下一步棋都需要进行胜负判断,判断是否已经有一方获胜。 四、总结 通过本次Android五子棋游戏设计实现,不仅加深了对Android开发的理解,同时也提高了编程能力和设计能力。在实现过程中,需要注意界面的美观性和用户体验,同时也需要注意代码的可维护性和可扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

等天晴i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值