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. 思考题
⑴ 在玩家游玩游戏的过程中,可能会出现误操作等需要“悔棋”的情况,需要用到何种数据结构保存玩家的上一步状态,又如何实现呢?
⑵ 上述算法设计中给出了一种简单的人机对抗算法的设计思路,请自行查阅资料,设计一款更加高效合理的权重算法。