C++实现五子棋小游戏(控制台)
前言
就业学长组织的项目比赛个人赛阶段完成的小游戏,参考了一些网上现有的五子棋项目,发现网上部分项目都是把所有代码放在一个源文件里而且有的功能不是很齐全,所以我做了一个分文件和多个类的包含人机对战和悔棋功能的小项目。
项目分析
功能流程
可以看着流程图理解具体代码逻辑,看代码卡住了记得回来看看哦
运行结果
(视频演示时对比悔棋功能把人机对战的悔棋功能先注释了)
C++实现五子棋小游戏
重要类和函数的声明
函数和类的声明我都分别放在了不同的头文件中,这里只是列出接口函数与变量,在下面代码的具体实现总再详细介绍每个函数和变量的作用。
- 棋盘类主要是用来打印棋盘和打印菜单,实现较为简单
class GameChess
{
public:
void DrawMenu();
void DrawBoard(char gameBoard[HEIGHT + 1][WIDTH + 1]);
};
- 判断类是用来判断下棋位置是否合法和判断是否输赢,其中判断输赢较为麻烦
class GameCheck
{
public:
bool CheckWin(int x, int y, char flag, char gameBoard[HEIGHT + 1][WIDTH + 1]);
bool CheckBound(int x, int y, char gameBoard[HEIGHT + 1][WIDTH + 1]);
};
- 玩家类用来对玩家初始化和实现下棋悔棋
class GamePlayer
{
public :
void Init(char flag);
void Do(char gameBoard[HEIGHT + 1][WIDTH + 1]);// 下棋函数
void Undo(char gameBoard[HEIGHT + 1][WIDTH + 1]);// 悔棋函数
public:
bool win;// 表示输赢
stack<pair<int, int>> undo;// 存储下过棋的位置
char flag;// 表示用的棋子符号
};
4.电脑类主要实现AI下棋,下面具体解释
class GameComputer
{
public:
void Init(char flag);// 初始化
void CalculateScore(char gameBoard[HEIGHT + 1][WIDTH + 1]);// 计算计分数组
void AI(char gameBoard[HEIGHT + 1][WIDTH + 1]);// 下棋
void Undo(char gameBoard[HEIGHT + 1][WIDTH + 1]);// 悔棋
public:
stack<pair<int, int>> undo;// 存储下棋的位置
int ChessScore[HEIGHT + 1][WIDTH + 1];// 计分数组,后文详细介绍
int personNum;//表示玩家棋数,作用后文详细介绍
int computerNum;// 同上
int emptyNum;// 同上
bool win;// 表示输赢
char flag;// 表示电脑用的棋子
};
具体类和函数的实现
头文件
#include <cmath>
#include <stack>
#include <algorithm>
#include <Windows.h>// 清屏函数在这个头文件里
#include <iostream>
using namespace std;
// 把棋盘的长和宽用全局的常量定义这样修改棋盘尺寸的时候只需要修改这里即可
const int WIDTH = 15;
const int HEIGHT = 15;
主框架
void Game()
{
int input;
do
{
GameChess().DrawMenu();// 直接匿名对象调用打印菜单函数
cout << "请选择:" << endl;
cin >> input;
switch (input) {
case 1:
PersonPlay();// 人人对战
break;
case 2:
ComputerPlay();// 人机对战
break;
case 0:
cout << "退出游戏" << endl;
break;
default:
cout << "输入错误,请重新选择!" << endl;
break;
}
} while (input);
}
人机对战框架
悔棋的功能试了好几种方案最后选择了goto语句,实现逻辑有点复杂,所以在下面人机对战中暂时去掉悔棋功能只看下棋的逻辑在人人对战中详细解释悔棋功能。首先对棋盘和两个玩家对象分别初始化,用do…while循环轮流下棋直到有人获胜跳出循环。
void InitBoard(char gameBoard[HEIGHT + 1][WIDTH + 1])
{
memset(gameBoard, ' ', ((HEIGHT + 1)) * (WIDTH + 1));// 初始化全部为空格
GameChess().DrawBoard(gameBoard);// 展示初始化好的棋盘
}
void ComputerPlay()
{
char gameBoard[HEIGHT + 1][WIDTH + 1];// 创建棋盘(其实就是二维数组存对战双方的符号)
InitBoard(gameBoard);
// 创建玩家对象,初始化棋子为*
GamePlayer gamePlayer;
gamePlayer.Init('*');
// 创建电脑对象,初始化棋子为#
GameComputer gameComputer;
gameComputer.Init('#');
do
{
// 玩家下棋,下棋的Do函数在后面的类中详细讲解,这里先理解简单逻辑
gamePlayer.Do(gameBoard);
if (gamePlayer.win) break;// 赢了就跳出
// 电脑下棋
gameComputer.AI(gameBoard);
if (gameComputer.win) break;// 赢了就跳出
} while (1);
}
人人对战框架
void InitBoard(char gameBoard[HEIGHT + 1][WIDTH + 1])
{
memset(gameBoard, ' ', ((HEIGHT + 1)) * (WIDTH + 1));
GameChess().DrawBoard(gameBoard);// 展示初始化好的棋盘
}
void PersonPlay()
{
char gameBoard[HEIGHT + 1][WIDTH + 1];// 创建棋盘
InitBoard(gameBoard);// 初始化棋盘把棋盘置为空
// 创建玩家1使用*棋子
GamePlayer gamePlayer1;
gamePlayer1.Init('*');
// 创建玩家2使用#棋子
GamePlayer gamePlayer2;
gamePlayer2.Init('#');
do
{
next1: gamePlayer1.Do(gameBoard);// 此处的next1和下面的next2,undo1,undo2都为goto语句的标志
if (gamePlayer1.win) break;
while (1)
{
// 玩家1是否悔棋
cout << "悔棋请按'r',下棋请按'c'" << endl;
char input1; cin >> input1;
if (input1 == 'r')
{
// 这里实现悔棋的Undo函数在后边的类中详细介绍,这里先理解大致逻辑
undo1: gamePlayer1.Undo(gameBoard);// 玩家1悔棋
cout << "悔棋请按'r',下棋请按'c'" << endl;
char input2; cin >> input2;
// 仍然悔棋就需要改变为玩家2了
// 如果玩家2也要悔棋就直接跳到下面的undo2,undo2即是玩家2悔棋,逻辑是一样的
if (input2 == 'r') goto undo2;
else goto next1;// 如果玩家2不悔棋就玩家1下棋,回到next1
}
else break;// 不悔棋直接跳出
}
next2: gamePlayer2.Do(gameBoard);
if (gamePlayer2.win) break;
while (1)
{
// 玩家2是否悔棋
cout << "悔棋请按'r',继续游戏请按'c'" << endl;
char input1; cin >> input1;
if (input1 == 'r')
{
// 类比上面的,如果是goto的就继续这个逻辑,如果继续悔棋就到undo1,这样就能构成连续的悔棋
undo2: gamePlayer2.Undo(gameBoard);
cout << "悔棋请按'r',继续游戏请按'c'" << endl;
char input2; cin >> input2;
if (input2 == 'r') goto undo1;
else goto next2;// 如果继续游戏则还是该玩家下棋,所以回到next2
}
else break;
}
} while (1);
}
棋盘类
- 实现打印菜单函数
void GameChess::DrawMenu()
{
cout << "*****************\n";
cout << "** 1. 人人对战 **\n";
cout << "** 2. 人机对战 **\n";
cout << "** 0. 退出游戏 **\n";
cout << "*****************\n";
}
- 实现打印棋盘函数,其实就是打印分割线跟行号列号符号来展示棋盘情况,我们传进来棋盘,如果棋盘对应的位置为空就打印空格,是哪个棋子就打印这个棋子对应的符号,这块如果感兴趣的话大家也可以自己来实现,怎么美观直观怎么来,这里的实现打印出来就是如下图
void GameChess::DrawBoard(char gameBoard[HEIGHT + 1][WIDTH + 1])
{
// 打印列号
printf("|");
for (int W = 0; W <= WIDTH; W++)
{
printf(" %2d|", W);
}
printf("\n");
for (int i = 1; i <= HEIGHT; i++)
{
// 打印行号
printf("|");
for (int W = 0; W <= WIDTH; W++)
{
printf("---|");
}
printf("\n| %2d|", i);
// 棋盘初始化为空
for (int j = 1; j <= WIDTH; j++)
{
printf(" %c |", gameBoard[i][j]);
}
printf("\n");
}
}
判断类
- 判断下棋位置是否合法函数较为简单,只需要判断是否超出棋盘边界并且当前位置是否为空
bool GameCheck::CheckBound(int x, int y, char gameBoard[HEIGHT + 1][WIDTH + 1])
{
return ((x <= WIDTH && y <= HEIGHT && x >= 1 && y >= 1 && gameBoard[x][y] == ' ') ? true : false);
}
- 判断输赢函数较为麻烦,主要思路就是看看最后下棋位置的棋子水平方向竖直方向主副对角线四个方向上最近的几颗棋子有没有连成 5 5 5 个或 5 5 5 个以上,具体的实现大家可以八仙过海各显神通。我在这里的思路是让棋子先朝一个方向走直到空位置或者别的棋子停下开始 180 ° 180° 180° 掉头往回走,这个时候开始计数走一步计数加一直到再次走到空位置或者空位置停下,计算此时的计数是否大于等于 5 5 5,大于则获胜,否则没有获胜,可以结合下面代码理解
bool GameCheck::CheckWin(int x, int y, char flag, char gameBoard[HEIGHT + 1][WIDTH + 1])
{
// 判断行是否符合
int rowNum = 0, rowHead = y;
for (int i = y; i >= 1; i--)
{
if (gameBoard[x][i] != flag) break;
rowHead = i;
}
for (int i = rowHead; gameBoard[x][i] == flag && i <= WIDTH ; i++)
rowNum++;
if (rowNum >= 5) return true;
// 判断列是否符合
int colNum = 0, colHead = x;
for (int i = x; i >= 1; i--)
{
if (gameBoard[i][y] != flag) break;
colHead = i;
}
for (int i = colHead; gameBoard[i][y] == flag && i <= HEIGHT; i++)
colNum++;
if (colNum >= 5) return true;
// 判断主对角线是否符合
int leadingHeadX = x, leadingHeadY = y, leadingNum = 0;
for (int i = x, j = y; x >= 1 && y >= 1; i--, j--)
{
if (gameBoard[i][j] != flag) break;
leadingHeadX = i;
leadingHeadY = j;
}
for (int i = leadingHeadX, j = leadingHeadY; gameBoard[i][j] == flag && i <= HEIGHT && j <= WIDTH; i++, j++)
leadingNum++;
if (leadingNum >= 5) return true;
// 判断副对角线是否符合
int counterHeadX = x, counterHeadY = y, counterNum = 0;
for (int i = x, j = y; x <= HEIGHT && y >= 1; i++, j--)
{
if (gameBoard[i][j] != flag) break;
counterHeadX = i;
counterHeadY = j;
}
for (int i = counterHeadX, j = counterHeadY; gameBoard[i][j] == flag && i >= 1 && y <= WIDTH + 1; i--, j++)
counterNum++;
if (counterNum >= 5) return true;
// 都不满足返回false
return false;
}
玩家类
- 初始化函数,将输赢属性置为false,什么时候胜利改为true就会跳出,棋子属性为初始化传进来赋值
void GamePlayer::Init(char flag)
{
this->win = false;
this->flag = flag;
}
- 下棋函数,首先输入横纵坐标,判断是否合法,合法后我们用系统自带的清屏函数system(“cls”),这样可以更美观(当然不加也行)。因为是对象调用的 D o Do Do 函数,所以直接把 t h i s . f l a g this.flag this.flag赋值在棋盘(也就是创建的二维数组)对应的位置上。接着把这个位置用一个pair对组存起来push到我们的栈 u n d o undo undo中。接着展示我们的棋盘,判断如果有人胜利则把 w i n win win数据成员改为true并打印相关信息。
void GamePlayer::Do(char gameBoard[HEIGHT + 1][WIDTH + 1])
{
again: cout << "\n请输入玩家" << this->flag << "要选择的坐标(横纵坐标间用空格空开):\n" << endl;
int x, y;
cin >> x >> y;// 输入坐标
if (!GameCheck().CheckBound(x, y, gameBoard)) // 调用判断边界函数,可以参考流程图
{
cout << "\n您选择的坐标非法,请重新选择!\n" << endl;
goto again;
}
else
{
system("cls");
gameBoard[x][y] = this->flag;
this->undo.push(make_pair(x, y));
GameChess().DrawBoard(gameBoard);
if (GameCheck().CheckWin(x, y, flag, gameBoard))// 调用判断胜负函数
{
cout << "\n恭喜玩家" << this->flag << "获得游戏的胜利!\n" << endl;
this->win = true;
}
}
}
- 悔棋函数,悔棋函数我们这里用到了数据结构栈,用了C++STL中自带的stack,栈这种数据结构就类似装子弹的弹夹,我们最先压进去的子弹会到最下面,最后压进去的子弹在最上面,我们可以利用这个特性来实现悔棋函数,可以保证最上面的永远是最近的下棋坐标。只需要用top即可询问栈顶元素,然后把该位置上的棋子改为空格即完成了悔棋,然后pop删掉这个元素那么它下面的就是新的栈顶元素了。
void GamePlayer::Undo(char gameBoard[HEIGHT + 1][WIDTH + 1])
{
if (this->undo.empty()) return;
int x = this->undo.top().first;
int y = this->undo.top().second;
gameBoard[x][y] = ' ';
GameChess().DrawBoard(gameBoard);
this->undo.pop();
}
电脑类
- 初始化函数, w i n win win 和 f l a g flag flag 同上,这里的 p e r s o n N u m personNum personNum, c o m p u t e r N u m computerNum computerNum, e m p t y N u m emptyNum emptyNum都是下面的计数函数要用到的在那里细讲,对计分数组清零初始化
void GameComputer::Init(char flag)
{
this->win = false;
this->flag = flag;
this->personNum = 0;
this->computerNum = 0;
this->emptyNum = 0;
for (int row = 1; row <= WIDTH; row++)
{
for (int col = 1; col <= HEIGHT; col++)
{
ChessScore[row][col] = 0;
}
}
}
- 悔棋函数,同上
void GameComputer::Undo(char gameBoard[HEIGHT + 1][WIDTH + 1])
{
if (this->undo.empty()) return;
int x = this->undo.top().first;
int y = this->undo.top().second;
gameBoard[x][y] = ' ';
GameChess().DrawBoard(gameBoard);
this->undo.pop();
}
- 计分函数,对整个棋盘遍历,遇到空格的话再开始计算,因为已经有棋子的位置当然不用考虑。这里的计分跟我们的判断输赢函数有几分相似,判断这个空格水平竖直主副对角线四个方向遇到对方棋子或者空白就返回,首先计算周围玩家下棋的情况,根据情况判断相应的分数,对方棋子造成的危险系数越大评分越高(具体情况可以看下面代码),接着计算周围电脑自己的下棋情况,自己的棋子获胜系数越高评分越高,只需要把这两个分数加在一起就是这个空格的分数,可以看着代码注释理解。
void GameComputer::CalculateScore(char gameBoard[HEIGHT + 1][WIDTH + 1])
{
// 遍历每个空位置计算该位置分数
for (int row = 1; row <= WIDTH; row++)
{
for (int col = 1; col <= HEIGHT; col++)
{
if (gameBoard[row][col] == ' ')
{
// 这里的for加下面的两个if语句continue语句是为了先让棋子朝四个方向扩展,
// 再分别计算对面的另外四个方向,和我们上面实现的判断输赢函数是同一个效果,
// 只是换了一种代码实现方式,都是为了分别找四个方向上的对应数据的个数
for (int y = -1; y <= 0; y++)
{
for (int x = -1; x <= 1; x++)
{
if (x == 0 && y == 0) continue;
if (y == 0 && x != 1) continue;
// 每次进来都是四个方向其中之一
// 三个变量分别统计遇到的玩家棋子数和电脑棋子数和空格数,初始化为0
personNum = 0;
computerNum = 0;
emptyNum = 0;
// 先朝一个方向计算
for (int i = 1; i <= 4; i++)
{
int curRow = row + i * y;
int curCol = col + i * x;
if (curRow >= 1 && curRow <= WIDTH &&
curCol >= 1 && curCol <= HEIGHT &&
gameBoard[curRow][curCol] == this->flag)
{
computerNum++;// 此处是电脑棋子数量++,感谢评论区Iawfy0同学指正
}
else if (curRow >= 1 && curRow <= WIDTH &&
curCol >= 1 && curCol <= HEIGHT &&
gameBoard[curRow][curCol] == ' ')
{
emptyNum++;// 如果不越界并且是空格就emptyNum计数加一
break;
}
else break;
}
// 掉头转向继续计算
for (int i = 1; i <= 4; i++)
{
int curRow = row - i * y;
int curCol = col - i * x;
if (curRow >= 1 && curRow <= WIDTH &&
curCol >= 1 && curCol <= HEIGHT &&
gameBoard[curRow][curCol] == this->flag)
{
computerNum++;// 同理
}
else if (curRow >= 1 && curRow <= WIDTH &&
curCol >= 1 && curCol <= HEIGHT &&
gameBoard[curRow][curCol] == ' ')
{
emptyNum++;// 同理
break;
}
else break;
}
// 经过上面的计算我们已经找到几个方向上玩家棋子的个数,下面根据棋子个数的不同打分,玩家棋子越多当然也就越危险,玩家棋子一样时空白的个数越多危险系数也越大
if (personNum == 1)
{
ChessScore[row][col] += 10;
}
else if (personNum == 2)
{
if (emptyNum == 1)
{
ChessScore[row][col] += 30;
}
else if (emptyNum == 2)
{
ChessScore[row][col] += 40;
}
}
else if (personNum == 3)
{
if (emptyNum == 1)
{
ChessScore[row][col] = 60;
}
else if (emptyNum == 2)
{
ChessScore[row][col] = 5000;
}
}
else if (personNum == 4)
{
ChessScore[row][col] = 20000;
}
// 接下来我们开始计算电脑棋子个数,如果遇到玩家棋子跳出
for (int i = 1; i <= 4; i++)
{
int curRow = row + i * y;
int curCol = col + i * x;
if (curRow >= 1 && curRow <= WIDTH &&
curCol >= 1 && curCol <= HEIGHT &&
gameBoard[curRow][curCol] != this->flag && gameBoard[curRow][curCol] != ' ')
{
personNum++;
}
else break;
}
for (int i = 1; i <= 4; i++)
{
int curRow = row - i * y;
int curCol = col - i * x;
if (curRow >= 1 && curRow <= WIDTH &&
curCol >= 1 && curCol <= HEIGHT &&
gameBoard[curRow][curCol] != this->flag && gameBoard[curRow][curCol] != ' ')
{
personNum++;
}
else break;
}
// 上面我们计算完成了该方向上电脑的棋子个数,下面根据棋子个数统计分数跟计算玩家棋同理
if (computerNum == 0)
{
ChessScore[row][col] += 5;
}
else if (computerNum == 1)
{
ChessScore[row][col] += 10;
}
else if (computerNum == 2)
{
if (emptyNum == 1)
{
ChessScore[row][col] += 25;
}
else if (emptyNum == 2)
{
ChessScore[row][col] += 50;
}
}
else if (computerNum == 3)
{
if (emptyNum == 1)
{
ChessScore[row][col] += 55;
}
else if (emptyNum == 2)
{
ChessScore[row][col] += 10000;
}
}
else if (computerNum >= 4)
{
ChessScore[row][col] += 30000;
}
}
}
}
}
}
}
- AI下棋,进行了初始化和计分操作后,我们只需要遍历一遍计分数组找到那个分数最高的也就是值最大的对应的位置即是AI要下棋的位置,接下来的悔棋,判断和我们的玩家类差不多。最后为了更清晰我们可以加一句话把电脑下棋的位置打印出来让用户看。
void GameComputer::AI(char gameBoard[HEIGHT + 1][WIDTH + 1])
{
Init('#');
CalculateScore(gameBoard);
int posRow = 1, posCol = 1, maxScore = -1;
for (int row = 1; row <= WIDTH; row++)
{
for (int col = 1; col <= HEIGHT; col++)
{
if (ChessScore[row][col] >= maxScore)
{
maxScore = ChessScore[row][col];
posRow = row;
posCol = col;
}
}
}
Sleep(1000);
system("cls");
gameBoard[posRow][posCol] = '#';
this->undo.push(make_pair(posRow, posCol));
GameChess().DrawBoard(gameBoard);
cout << "电脑下棋的位置为" << posRow << " " << posCol << endl;
if (GameCheck().CheckWin(posRow, posCol, this->flag, gameBoard))
{
cout << "\n恭喜电脑获得游戏的胜利!玩家失败!\n" << endl;
this->win = true;
}
}
完整代码
头文件
- Game.h
#pragma once
const int WIDTH = 15;
const int HEIGHT = 15;
#include <cmath>
#include <stack>
#include <algorithm>
#include <Windows.h>
#include <iostream>
using namespace std;
void PersonPlay();
void ComputerPlay();
void Game();
- GameChess.h
#pragma once
#include "Game.h"
class GameChess
{
public:
void DrawMenu();
void DrawBoard(char gameBoard[HEIGHT + 1][WIDTH + 1]);
};
- GameCheck.h
#pragma once
#include "Game.h"
class GameCheck
{
public:
bool CheckWin(int x, int y, char flag, char gameBoard[HEIGHT + 1][WIDTH + 1]);
bool CheckBound(int x, int y, char gameBoard[HEIGHT + 1][WIDTH + 1]);
};
- GamePlayer.h
#pragma once
#include "Game.h"
class GamePlayer
{
public :
void Init(char flag);
void Do(char gameBoard[HEIGHT + 1][WIDTH + 1]);
void Undo(char gameBoard[HEIGHT + 1][WIDTH + 1]);
public:
bool win;
stack<pair<int, int>> undo;
char flag;
};
- GameComputer.h
#pragma once
#include "Game.h"
#include "GamePlayer.h"
class GameComputer
{
public:
void Init(char flag);
void CalculateScore(char gameBoard[HEIGHT + 1][WIDTH + 1]);
void AI(char gameBoard[HEIGHT + 1][WIDTH + 1]);
void Undo(char gameBoard[HEIGHT + 1][WIDTH + 1]);
public:
stack<pair<int, int>> undo;
int ChessScore[HEIGHT + 1][WIDTH + 1];
int personNum;
int computerNum;
int emptyNum;
bool win;
char flag;
};
源文件
- Game.cpp
#include "GameChess.h"
#include "GamePlayer.h"
#include "GameCheck.h"
#include "GameComputer.h"
#include "Game.h"
void InitBoard(char gameBoard[HEIGHT + 1][WIDTH + 1])
{
memset(gameBoard, ' ', ((HEIGHT + 1)) * (WIDTH + 1));
GameChess().DrawBoard(gameBoard);
}
void PersonPlay()
{
char gameBoard[HEIGHT + 1][WIDTH + 1];
InitBoard(gameBoard);
GamePlayer gamePlayer1;
gamePlayer1.Init('*');
GamePlayer gamePlayer2;
gamePlayer2.Init('#');
do
{
next1: gamePlayer1.Do(gameBoard);
if (gamePlayer1.win) break;
while (1)
{
cout << "悔棋请按'r',下棋请按'c'" << endl;
char input1; cin >> input1;
if (input1 == 'r')
{
undo1: gamePlayer1.Undo(gameBoard);
cout << "悔棋请按'r',下棋请按'c'" << endl;
char input2; cin >> input2;
if (input2 == 'r') goto undo2;
else goto next1;
}
else break;
}
next2: gamePlayer2.Do(gameBoard);
if (gamePlayer2.win) break;
while (1)
{
cout << "悔棋请按'r',继续游戏请按'c'" << endl;
char input1; cin >> input1;
if (input1 == 'r')
{
undo2: gamePlayer2.Undo(gameBoard);
cout << "悔棋请按'r',继续游戏请按'c'" << endl;
char input2; cin >> input2;
if (input2 == 'r') goto undo1;
else goto next2;
}
else break;
}
} while (1);
}
void ComputerPlay()
{
char gameBoard[HEIGHT + 1][WIDTH + 1];
InitBoard(gameBoard);
GamePlayer gamePlayer;
gamePlayer.Init('*');
GameComputer gameComputer;
gameComputer.Init('#');
do
{
/*next3:*/ gamePlayer.Do(gameBoard);
if (gamePlayer.win) break;
/*while (1)
{
cout << "悔棋请按'r',继续游戏请按'c'" << endl;
char input1; cin >> input1;
if (input1 == 'r')
{
gamePlayer.Undo(gameBoard);
cout << "悔棋请按'r',继续游戏请按'c'" << endl;
char input2; cin >> input2;
if (input2 == 'r')
{
gameComputer.Undo(gameBoard);
}
else goto next3;
}
else break;
}*/
/*next4:*/ gameComputer.AI(gameBoard);
if (gameComputer.win) break;
/*while (1)
{
cout << "悔棋请按'r',继续游戏请按'c'" << endl;
char input1; cin >> input1;
if (input1 == 'r')
{
gameComputer.Undo(gameBoard);
cout << "仍要悔棋请按'r',继续游戏请按'c'" << endl;
char input2; cin >> input2;
if (input2 == 'r')
{
gamePlayer.Undo(gameBoard);
}
else goto next4;
}
else break;
}*/
} while (1);
}
void Game()
{
int input;
do
{
GameChess().DrawMenu();
cout << "请选择:" << endl;
cin >> input;
switch (input) {
case 1:
PersonPlay();
break;
case 2:
ComputerPlay();
break;
case 0:
cout << "退出游戏" << endl;
break;
default:
cout << "输入错误,请重新选择!" << endl;
break;
}
} while (input);
}
- GameChess.cpp
#include "GameChess.h"
void GameChess::DrawMenu()
{
cout << "*****************\n";
cout << "** 1. 人人对战 **\n";
cout << "** 2. 人机对战 **\n";
cout << "** 0. 退出游戏 **\n";
cout << "*****************\n";
}
void GameChess::DrawBoard(char gameBoard[HEIGHT + 1][WIDTH + 1])
{
// 打印列号
printf("|");
for (int W = 0; W <= WIDTH; W++)
{
printf(" %2d|", W);
}
printf("\n");
for (int i = 1; i <= HEIGHT; i++)
{
// 打印行号
printf("|");
for (int W = 0; W <= WIDTH; W++)
{
printf("---|");
}
printf("\n| %2d|", i);
// 棋盘初始化为空
for (int j = 1; j <= WIDTH; j++)
{
printf(" %c |", gameBoard[i][j]);
}
printf("\n");
}
}
- GameCheck.cpp
#include "GameCheck.h"
bool GameCheck::CheckWin(int x, int y, char flag, char gameBoard[HEIGHT + 1][WIDTH + 1])
{
// 判断行是否符合
int rowNum = 0, rowHead = y;
for (int i = y; i >= 1; i--)
{
if (gameBoard[x][i] != flag) break;
rowHead = i;
}
for (int i = rowHead; gameBoard[x][i] == flag && i <= WIDTH ; i++)
rowNum++;
if (rowNum >= 5) return true;
// 判断列是否符合
int colNum = 0, colHead = x;
for (int i = x; i >= 1; i--)
{
if (gameBoard[i][y] != flag) break;
colHead = i;
}
for (int i = colHead; gameBoard[i][y] == flag && i <= HEIGHT; i++)
colNum++;
if (colNum >= 5) return true;
// 判断主对角线是否符合
int leadingHeadX = x, leadingHeadY = y, leadingNum = 0;
for (int i = x, j = y; x >= 1 && y >= 1; i--, j--)
{
if (gameBoard[i][j] != flag) break;
leadingHeadX = i;
leadingHeadY = j;
}
for (int i = leadingHeadX, j = leadingHeadY; gameBoard[i][j] == flag && i <= HEIGHT && j <= WIDTH; i++, j++)
leadingNum++;
if (leadingNum >= 5) return true;
// 判断副对角线是否符合
int counterHeadX = x, counterHeadY = y, counterNum = 0;
for (int i = x, j = y; x <= HEIGHT && y >= 1; i++, j--)
{
if (gameBoard[i][j] != flag) break;
counterHeadX = i;
counterHeadY = j;
}
for (int i = counterHeadX, j = counterHeadY; gameBoard[i][j] == flag && i >= 1 && y <= WIDTH + 1; i--, j++)
counterNum++;
if (counterNum >= 5) return true;
// 都不满足返回false
return false;
}
bool GameCheck::CheckBound(int x, int y, char gameBoard[HEIGHT + 1][WIDTH + 1])
{
return ((x <= WIDTH && y <= HEIGHT && x >= 1 && y >= 1 && gameBoard[x][y] == ' ') ? true : false);
}
- GamePlayer.cpp
#include "GamePlayer.h"
#include "GameCheck.h"
#include "GameChess.h"
void GamePlayer::Init(char flag)
{
this->win = false;
this->flag = flag;
}
void GamePlayer::Undo(char gameBoard[HEIGHT + 1][WIDTH + 1])
{
if (this->undo.empty()) return;
int x = this->undo.top().first;
int y = this->undo.top().second;
gameBoard[x][y] = ' ';
GameChess().DrawBoard(gameBoard);
this->undo.pop();
}
void GamePlayer::Do(char gameBoard[HEIGHT + 1][WIDTH + 1])
{
again: cout << "\n请输入玩家" << this->flag << "要选择的坐标(横纵坐标间用空格空开):\n" << endl;
int x, y;
cin >> x >> y;
if (!GameCheck().CheckBound(x, y, gameBoard))
{
cout << "\n您选择的坐标非法,请重新选择!\n" << endl;
goto again;
}
else
{
system("cls");
gameBoard[x][y] = this->flag;
this->undo.push(make_pair(x, y));
GameChess().DrawBoard(gameBoard);
if (GameCheck().CheckWin(x, y, flag, gameBoard))
{
cout << "\n恭喜玩家" << this->flag << "获得游戏的胜利!\n" << endl;
this->win = true;
}
}
}
- GameComputer.cpp
#include "GameComputer.h"
#include "GameCheck.h"
#include "GameChess.h"
void GameComputer::Init(char flag)
{
this->win = false;
this->flag = flag;
this->personNum = 0;
this->computerNum = 0;
this->emptyNum = 0;
for (int row = 1; row <= WIDTH; row++)
{
for (int col = 1; col <= HEIGHT; col++)
{
ChessScore[row][col] = 0;
}
}
}
void GameComputer::Undo(char gameBoard[HEIGHT + 1][WIDTH + 1])
{
if (this->undo.empty()) return;
int x = this->undo.top().first;
int y = this->undo.top().second;
gameBoard[x][y] = ' ';
GameChess().DrawBoard(gameBoard);
this->undo.pop();
}
void GameComputer::CalculateScore(char gameBoard[HEIGHT + 1][WIDTH + 1])
{
// 遍历每个空位置计算该位置分数
for (int row = 1; row <= WIDTH; row++)
{
for (int col = 1; col <= HEIGHT; col++)
{
if (gameBoard[row][col] == ' ')
{
// 这里的for加下面的两个if语句continue语句是为了先让棋子朝四个方向扩展,
// 再分别计算对面的另外四个方向,和我们上面实现的判断输赢函数是同一个效果,
// 只是换了一种代码实现方式,都是为了分别找四个方向上的对应数据的个数
for (int y = -1; y <= 0; y++)
{
for (int x = -1; x <= 1; x++)
{
if (x == 0 && y == 0) continue;
if (y == 0 && x != 1) continue;
// 每次进来都是四个方向其中之一
// 三个变量分别统计遇到的玩家棋子数和电脑棋子数和空格数,初始化为0
personNum = 0;
computerNum = 0;
emptyNum = 0;
// 先朝一个方向计算
for (int i = 1; i <= 4; i++)
{
int curRow = row + i * y;
int curCol = col + i * x;
if (curRow >= 1 && curRow <= WIDTH &&
curCol >= 1 && curCol <= HEIGHT &&
gameBoard[curRow][curCol] == this->flag)
{
computerNum++;
}
else if (curRow >= 1 && curRow <= WIDTH &&
curCol >= 1 && curCol <= HEIGHT &&
gameBoard[curRow][curCol] == ' ')
{
emptyNum++;// 如果不越界并且是空格就emptyNum计数加一
break;
}
else break;
}
// 掉头转向继续计算
for (int i = 1; i <= 4; i++)
{
int curRow = row - i * y;
int curCol = col - i * x;
if (curRow >= 1 && curRow <= WIDTH &&
curCol >= 1 && curCol <= HEIGHT &&
gameBoard[curRow][curCol] == this->flag)
{
personNum++;// 同理
}
else if (curRow >= 1 && curRow <= WIDTH &&
curCol >= 1 && curCol <= HEIGHT &&
gameBoard[curRow][curCol] == ' ')
{
computerNum++;// 同理
break;
}
else break;
}
// 经过上面的计算我们已经找到几个方向上玩家棋子的个数,下面根据棋子个数的不同打分,玩家棋子越多当然也就越危险,玩家棋子一样时空白的个数越多危险系数也越大
if (personNum == 1)
{
ChessScore[row][col] += 10;
}
else if (personNum == 2)
{
if (emptyNum == 1)
{
ChessScore[row][col] += 30;
}
else if (emptyNum == 2)
{
ChessScore[row][col] += 40;
}
}
else if (personNum == 3)
{
if (emptyNum == 1)
{
ChessScore[row][col] = 60;
}
else if (emptyNum == 2)
{
ChessScore[row][col] = 5000;
}
}
else if (personNum == 4)
{
ChessScore[row][col] = 20000;
}
// 接下来我们开始计算电脑棋子个数,如果遇到玩家棋子跳出
for (int i = 1; i <= 4; i++)
{
int curRow = row + i * y;
int curCol = col + i * x;
if (curRow >= 1 && curRow <= WIDTH &&
curCol >= 1 && curCol <= HEIGHT &&
gameBoard[curRow][curCol] != this->flag && gameBoard[curRow][curCol] != ' ')
{
personNum++;
}
else break;
}
for (int i = 1; i <= 4; i++)
{
int curRow = row - i * y;
int curCol = col - i * x;
if (curRow >= 1 && curRow <= WIDTH &&
curCol >= 1 && curCol <= HEIGHT &&
gameBoard[curRow][curCol] != this->flag && gameBoard[curRow][curCol] != ' ')
{
personNum++;
}
else break;
}
// 上面我们计算完成了该方向上电脑的棋子个数,下面根据棋子个数统计分数跟计算玩家棋同理
if (computerNum == 0)
{
ChessScore[row][col] += 5;
}
else if (computerNum == 1)
{
ChessScore[row][col] += 10;
}
else if (computerNum == 2)
{
if (emptyNum == 1)
{
ChessScore[row][col] += 25;
}
else if (emptyNum == 2)
{
ChessScore[row][col] += 50;
}
}
else if (computerNum == 3)
{
if (emptyNum == 1)
{
ChessScore[row][col] += 55;
}
else if (emptyNum == 2)
{
ChessScore[row][col] += 10000;
}
}
else if (computerNum >= 4)
{
ChessScore[row][col] += 30000;
}
}
}
}
}
}
}
void GameComputer::AI(char gameBoard[HEIGHT + 1][WIDTH + 1])
{
Init('#');
CalculateScore(gameBoard);
int posRow = 1, posCol = 1, maxScore = -1;
for (int row = 1; row <= WIDTH; row++)
{
for (int col = 1; col <= HEIGHT; col++)
{
if (ChessScore[row][col] >= maxScore)
{
maxScore = ChessScore[row][col];
posRow = row;
posCol = col;
}
}
}
Sleep(1000);
system("cls");
gameBoard[posRow][posCol] = '#';
this->undo.push(make_pair(posRow, posCol));
GameChess().DrawBoard(gameBoard);
cout << "电脑下棋的位置为" << posRow << " " << posCol << endl;
if (GameCheck().CheckWin(posRow, posCol, this->flag, gameBoard))
{
cout << "\n恭喜电脑获得游戏的胜利!玩家失败!\n" << endl;
this->win = true;
}
}
总结
大家在大学学习一定要多上手多做一些实战项目,哪怕是跟着敲慢慢理解,不仅可以巩固我们的语言基础,更能锻炼代码能力和处理业务的逻辑能力,而且找工作中也是十分注重项目经历的,一个好的拿手项目绝对是加分项。还做了个easyx的图形化的五子棋以后可能会更新(doge)。