1. 题目描述

五子棋的游戏规则是两人对弈,使用黑白两色棋子,轮流下在棋盘上,当一方先在横线、竖线、斜对角线方向形成五子连线,则取得胜利。

2. 设计要求

⑴ 在内存中,设计数据结构存储游戏需要的数据。

⑵ 满足五子棋游戏的游戏规则。

⑶ 实现简单的人机对战功能。

3. 数据结构设计

在游戏运行过程中,需要在内存中保存游玩的状态,也就是棋盘,可以设计以下结构保存关卡信息。

struct BoardType

{

    // 棋盘,0表示空位,1表示玩家棋子,2表示电脑棋子

       int map[BOARDSIZE][BOARDSIZE]; 

    // AI下棋的权重,权重取对AI最有利和对玩家最有害两者的最大值

       int weight[BOARDSIZE][BOARDSIZE]; 

}

4. 算法设计

对于该款游戏,我们需要以下函数提供基本功能:

InitGame:初始化游戏的游玩状态

PlayerTurn:处理玩家回合的输入

UpdateWeight:根据棋盘局势更新电脑的下棋权重

ComputerTurn:电脑回合的处理

Update:负责根据内存中的游玩状态更新游戏的画面函数

CheckWin:判断是否有一方取得胜利的函数

人机对抗部分主要由UpdateWeight与ComputerTurn函数组成,其中UpdateWeight函数负责计算棋盘上每个空位的权重,便于电脑判断在哪里下棋是较为合理的。这里给出了一种简单的设计思路,UpdateWeight算法设计如下:

其中board数据类型为前文定义的BoardType,保存了棋盘当前的状态以及对应位置上的权重。


UpdateWeight:根据棋盘状态更新权重

输入:棋盘状态board

输出:无

   1. 逐行逐列判断棋盘上该位置是否为空位,记行号为i,列号为j:

          1.1. 若board.map[i][j]!=0,该位置不为空,board.weight[i][j]=-1;

          1.2. 若board.map[i][j]==0,该位置为空:

                 1.2.1. 记对AI有利的权重为aiWeight=0;

                 1.2.2. 记对玩家有害的权重为playerWeight=0;

                 1.2.3. 若该位置放置AI的棋子,分别判断周围8个方向

                         相连的AI棋子个数,每个方向相连的个数记作cnt,

                         令aiWeight+=10^cnt;

                 1.2.4. 若该位置放置玩家的棋子,分别判断周围8个方向

                         相连的玩家棋子个数,每个方向相连的个数记作cnt,

                         令playerWeight+=10^cnt;

                 1.2.5. board.weight[i][j]=max(aiWeight,playerWeight);



ComputerTurn函数负责根据处理电脑的回合,模拟人类与玩家进行对弈,其中出现的UpdateWeight函数为上文中的更新权重算法, board数据类型为前文定义的BoardType,保存了棋盘当前的状态。


ComputerTurn:电脑回合的处理

输入:无

输出:无

   1. 调用UpdateWeight函数,更新权重;

   2. 记maxVal=0,maxX=0,maxY=0,记录最大权重出现的位置;

   3. 逐行逐列比较棋盘上的权值与maxVal的关系,记行为i,列为j:

          3.1. 若board.weight[i][j]>maxVal:

                 3.1.1. maxVal= board.weight[i][j];

                 3.1.2. maxX=i;

                 3.1.3. maxY=j;

   4. 点(maxX,maxY)为权值最大的位置,board.map[maxX][maxY]=2;


CheckWin函数负责判断是否有一方获胜,其中board数据类型为前文定义的BoardType,保存了棋盘当前的状态。


CheckWin:根据棋盘状态更新权重

输入:棋盘状态board

输出:胜利方

   1. 逐行逐列判断棋盘上该位置是否为空位,记行号为i,列号为j:

      若board.map[i][j]!=0,该位置不为空:

          1.1. 分别判断点(i,j)与周围8个方向相连且相同的棋子个数,

                 记作cnt;

          1.2. 若cnt>=5,判断board.map[i][j]的类型:

                 1.2.1. 若board.map[i][j==1,玩家的棋子,返回玩家胜利;

                 1.2.2. 若board.map[i][j==2,电脑的棋子,返回电脑胜利;

   2. 返回无人胜利;


5. 测试样例

无测试样例

6. 代码实现

// 编译器版本 g++8.1.0
// 编译参数 -Weffc++ -Wextra -Wall -std=c++11

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>

using namespace std;

/**
 * 常量定义
**/
const int BOARDSIZE = 9;
const int DX[8]= {1,1,0,-1,-1,-1,0,1};
const int DY[8]= {0,1,1,1,0,-1,-1,-1};

/**
 * 数据类型定义
**/
// 关卡数据类型
struct BoardType
{
    int map[BOARDSIZE][BOARDSIZE];  // 棋盘,0表示空位,1表示玩家棋子,2表示电脑棋子
    int weight[BOARDSIZE][BOARDSIZE];  // AI下棋的权重,权重取对AI最有利和对玩家最有害两者的最大值
};

/**
 * 全局变量定义
**/
BoardType Gboard;

/**
 * 游戏初始化
**/
void InitGame()
{
    // 初始化显示画面高度和宽度
    system("mode con cols=40 lines=20");
    system("cls");
    // 初始化变量
    memset(Gboard.map,0,sizeof(Gboard.map));
    return;
}

/**
 * 更新画面
**/
void Update()
{
    system("cls");
    printf("     ");
    for(int i=0; i<BOARDSIZE; i++)
        printf("%c ",'a'+i);
    putchar('\n');
    for(int i=0; i<BOARDSIZE; i++)
    {
        printf(" %2d ",i);
        for(int j=0; j<BOARDSIZE; j++)
        {
            if(Gboard.map[i][j]==1)
                printf(" B");
            else if(Gboard.map[i][j]==2)
                printf(" W");
            else
                printf(" .");
        }
        putchar('\n');
    }
    putchar('\n');
    return;
}

/**
 * 胜利画面
**/
void UpdateWin()
{
    printf("YOU WIN!\n");
    system("pause");
    return;
}

/**
 * 失败画面
**/
void UpdateLose()
{
    printf("YOU LOSE!\n");
    system("pause");
    return;
}

/**
 * 用户回合
**/
void PlayerTurn()
{
    char inputStr[255];
    int x,y;
    printf("Input(a 1 / restart / exit):");
    scanf("%s",inputStr);
    if(strcmp(inputStr,"restart")==0)
    {
        InitGame();
        Update();
        PlayerTurn();
        return;
    }
    if(strcmp(inputStr,"exit")==0)
    {
        system("exit");
        return;
    }
    scanf("%d",&x);
    y=inputStr[0]-'a';
    if(Gboard.map[x][y]!=0)
    {
        printf("Error.");
        system("pause");
        Update();
        PlayerTurn();
    }
    Gboard.map[x][y]=1;
    return;
}

/**
 * 更新下棋权重
**/
void UpdateAIWeight()
{
    for(int x=0; x<BOARDSIZE; x++)
        for(int y=0; y<BOARDSIZE; y++)
            if(Gboard.map[x][y]!=0)
                Gboard.weight[x][y]=0;
            else
            {
                int aiWeight=0;
                int playerWeight=0;
                // 计算八个方向上的棋子个数
                for(int i=0; i<8; i++)
                {
                    // 对自己有利
                    int pX=x;
                    int pY=y;
                    int cnt=1;
                    for(int j=0; j<4; j++)
                    {
                        pX+=DX[i];
                        pY+=DY[i];
                        if(pX>=0&&pX<BOARDSIZE&&pY>=0&&pY<BOARDSIZE&&2==Gboard.map[pX][pY])
                            cnt++;
                        else break;
                    }
                    aiWeight+=pow(10,cnt)+1;
                    // 对玩家有害
                    pX=x;
                    pY=y;
                    cnt=1;
                    for(int j=0; j<4; j++)
                    {
                        pX+=DX[i];
                        pY+=DY[i];
                        if(pX>=0&&pX<BOARDSIZE&&pY>=0&&pY<BOARDSIZE&&1==Gboard.map[pX][pY])
                            cnt++;
                        else break;
                    }
                    playerWeight+=pow(10,cnt);
                }
                // 更新权重,对自己最有利或者对玩家最有害,当相同时选择对自己最有利的
                Gboard.weight[x][y]=aiWeight>playerWeight?aiWeight:playerWeight;
            }
    return;
}

/**
 * 电脑回合
**/
void ComputerTurn()
{
    // 计算权重
    UpdateAIWeight();
    // 寻找权重最大的点
    int maxX;
    int maxY;
    int maxWeight=-1;
    for(int i=0; i<BOARDSIZE; i++)
        for(int j=0; j<BOARDSIZE; j++)
            if(Gboard.weight[i][j]>maxWeight)
            {
                maxX=i;
                maxY=j;
                maxWeight=Gboard.weight[i][j];
            }
    // 下棋
    Gboard.map[maxX][maxY]=2;
    return;
}

/**
 * 判断胜利
**/
bool CheckWin(int x,int y)
{
    // 从上方顺时针向八个方向判断
    for(int i=0; i<8; i++)
    {
        int pX=x;
        int pY=y;
        int cnt=1;
        for(int j=0; j<4; j++)
        {
            pX+=DX[i];
            pY+=DY[i];
            if(pX>=0&&pX<BOARDSIZE&&pY>=0&&pY<BOARDSIZE&&Gboard.map[x][y]==Gboard.map[pX][pY])
                cnt++;
        }
        if(cnt==5)
            return true;
    }
    return false;
}

/**
 * 判断玩家胜利
**/
bool CheckPlayerWin()
{
    for(int i=0; i<BOARDSIZE; i++)
        for(int j=0; j<BOARDSIZE; j++)
            if(Gboard.map[i][j]==1&&CheckWin(i,j))
                return true;
    return false;
}

/**
 * 判断电脑胜利
**/
bool CheckPlayerLose()
{
    for(int i=0; i<BOARDSIZE; i++)
        for(int j=0; j<BOARDSIZE; j++)
            if(Gboard.map[i][j]==2&&CheckWin(i,j))
                return true;
    return false;
}

/**
 * 程序入口
**/
int main()
{
    // 初始化游戏
    InitGame();
    Update();
    while(true)
    {
        // 用户输入
        PlayerTurn();
        if(CheckPlayerWin())
        {
            UpdateWin();
            InitGame();
            Update();
        }
        // 电脑输入
        ComputerTurn();
        if(CheckPlayerLose())
        {
            UpdateLose();
            InitGame();
            Update();
        }
        // 更新画面
        Update();
    }
    return 0;
}

7. 思考题

⑴ 在玩家游玩游戏的过程中,可能会出现误操作等需要“悔棋”的情况,需要用到何种数据结构保存玩家的上一步状态,又如何实现呢?

⑵ 上述算法设计中给出了一种简单的人机对抗算法的设计思路,请自行查阅资料,设计一款更加高效合理的权重算法。