这次写五子棋程序还是缘于一个机遇(某男子学院狂轰滥炸式上课的C++老师布置的作业)
然后我就开始用我蹩脚的C++语法知识,开始写五子棋程序的框架。这一篇的只是会写的比较基础一些,首先可以大致分为:模式选择、打印棋盘、玩家下棋,电脑计算并下棋,判断胜负。知识层的话可以分为:
1、格式化输出;
2、二维数组的访问和操作;
3、预测棋盘局势并判断最优解输出;
其实程序好像并不难写,并没有用到什么很难的语法或者是高级技巧,主要是一开始写的时候框架要清晰,一边写一边调试确保基本的函数没有错误,保证后面的调用也不会出错。剩下的就是对棋盘各种情况的识别和判断权重(其实就是疯狂码就对了)。
格式化输出
其实这算是很基础的知识了,这里特地要说一下主要是我的问题。。。每次要涉及对字符串的处理的时候总能写出一堆bug,一开始是想要用字符串数组写的?后来不行想带遍历二维整数数组,然后根据不同数字输出,这里就简单提一下:
先对二维数组全部赋值为0,当遍历到0时输出" (空) “,获取玩家输入的座标时对应二维数组的数字赋值为1,遍历到1时输出” o “,机器判断输出最优解时对应数字赋值为2,遍历到2时输出为"●”。
//打印棋盘函数
void print_chessboard()
{
printf(" 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15\n");
for (int i = 0; i < 15; i++)
{
printf(" |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n");
printf("%3d ", i + 1);
for (int j = 0; j < 15; j++)
{
printf("|");
if (chess_board[i][j] == 0)
{
printf("%3s", " ");
}
else if (chess_board[i][j] == 1)
{
printf("%3s", "○");
}
else if (chess_board[i][j] == 2)
{
printf("%3s", "●");
}
}
printf("|\n");
}
printf(" |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n");
}
想要居中输出的话,可以用cout和setw(),计算居中的位置做setw的参数就好啦。
打印出的棋盘效果是这样的:
判断胜负机制:
要达到胜利的情况其实只有四种情况:1、五子横(五个棋子横向相连);2、五子竖(五个棋子纵向相连);3、五子撇(五个棋子向左下斜);4、五子捺(五个棋子向右下斜);
所以只需要对二维数组进行遍历,对每个遍历点都进行这四个方向的检测就可以实现判断棋盘上是否产生赢家。
//判断双方胜负机制
int judgement(int player,int flag)
{
/******************************************
共有四种五子连线的情况,所以分为五种胜利条件
1:五子横 2:五子竖 3:五子撇 4:五子捺
********************************************/
//判断人类玩家是否胜利
for (int i = 0; i < 15; i++)
{
for (int j = 0; j < 15; j++)
{
if (chess_board[i][j] == flag)
{
//判断五子横是否成立
if (chess_board[i][j + 1] == flag)
{
if (chess_board[i][j + 2] == flag)
{
if (chess_board[i][j + 3] == flag)
{
if (chess_board[i][j + 4] == flag)
{
if (player == 1)return 1;
if (player == 2)return 2;
}
}
}
}
//判断五子竖是否成立
if (chess_board[i+1][j] == flag)
{
if (chess_board[i+2][j] == flag)
{
if (chess_board[i+3][j] == flag)
{
if (chess_board[i+4][j] == flag)
{
if (player == 1)return 1;
if (player == 2)return 2;
}
}
}
}
//判断五子撇是否成立
if (j>=5&&i<=10)
{
if (chess_board[i + 1][j - 1] == flag)
{
if (chess_board[i + 2][j - 2] == flag)
{
if (chess_board[i + 3][j - 3] == flag)
{
if (chess_board[i + 4][j - 4] == flag)
{
if (player == 1)return 1;
if (player == 2)return 2;
}
}
}
}
}
//判断五子捺是否成立
if (i <= 10 && j <= 10)
{
if (chess_board[i + 1][j + 1] == flag)
{
if (chess_board[i + 2][j + 2] == flag)
{
if (chess_board[i + 3][j + 3] == flag)
{
if (chess_board[i + 4][j + 4] == flag)
{
if (player == 1)return 1;
if (player == 2)return 2;
}
}
}
}
}
}
}
}
}
定义玩家结构体
//定义玩家结构体
typedef struct players
{
int x=0, y=0; //玩家本次下棋的座标
int precious_x=0, precious_y=0; //玩家上一次下棋的座标
}Player;
主要是为了记录本次要下棋的座标和上一次的座标,便于提供给电脑进行计算和检测座标的合法性。(后面会讲到)
计算最优解
这部分可以说最难但其实难不是难在技术,难在代码多······研究了五子棋的各种情况就可以开始拼命码了。
大体的话可以分为六个优先级
1、第一优先级:当电脑检测到己方棋子存在四个相连的情况时,第一优先响应练成五个获得胜利
2、第二优先级:当检测到玩家有四个相连时,对四个棋子的两边进行围堵,网上看到有人分为什么活四啊眠四啊,我觉得这里好像没什么吧必要,对四个的进行检测然后往两边空的堵上就好了,要是活四的话堵不堵反正也输了,而且只要优先级设置的正确的话,基本上是不会出现活四的情况的
3、第三优先级:当检测到己方有三个相连且两边为空时,连成四个,懂得游戏规则的都知道,这样的话下一步就赢了。
4、第四优先级:当检测到玩家有三个相连且两边为空时,对其两边进行围堵,防止其出现活四,当然要是不是为空的话可堵可不堵,要是这方面也要判断的话代码就写不完了hhh,所以这里看你的下棋风格吧
5、第五优先级:当检测到己方有两个相连且两边为空时,连成三个为下一次做准备
6、第六优先级:当检测到玩家有两个相连且两边为空时,进行围堵,以防后患(当然这些都是建立在前面不成立的前提)
如果上述都不存在的话怎么办呢?
为了不要让电脑随便乱走浪费次数,我们可以对前面记录的玩家下棋的座标
采样rand()让电脑在无风险时下在玩家下棋的周围,阻止玩家任意布局。这里没办法列出所有程序。因为有点多,就只写出最基本的随机预测:
int rand_num1 = (rand() % 3) - 1; //rand_num1介于[-1,1]
int rand_num2 = (rand() % 3) - 1; //rand_num2介于[-1,1]
if (chess_board[human.x + rand_num1][human.y + rand_num2] == 0)
{
computer.x = human.x + rand_num1;
computer.y = human.y + rand_num2;
return 1;
}
检测座标合法性
这一步其实也很重要,不写的话后面会有很多bug,而且后面很多要用到。
//判断玩家和电脑落子座标是否合法
bool isture(int x,int y)
{
if (x < 0 | x >= 15 | y < 0 | y >= 15)
{
cout << "输入座标位于棋盘外" << endl;
return false;
}
else if (chess_board[x][y] == 1 | chess_board[x][y] == 2)
{
cout << "输入座标已有棋子" << endl;
return false;
}
else return true;
}
详细的源码我放在这里啦,有需要可以下载 智能五子棋程序
不吹不黑,要下赢它还是需要斟酌的,之后我还会慢慢改进,有胆量的话就去跟他较量较量吧!
呜呜呜,可能是我太菜了,自己写的程序自己还赢不过