目录
一、流程图
二、实现思路
程序主要分为:菜单,棋盘初始化,打印棋盘,玩家下棋,电脑下棋,胜负判断,几个模块。
那么我们先声明好函数。(棋盘使用3行3列的二维char数组来表示,如果使用全局变量的话可以不用传参,憋别问我为什么传)
#define ROW 3
#define COL 3
int menu(); //菜单
void init(char che[ROW][COL]); //初始化棋盘
void display(char che[ROW][COL], int row, int col); //打印棋盘
void player(char che[ROW][COL]); // 玩家回合
void computer(char che[ROW][COL]); //电脑回合
int detect(char che[ROW][COL]); //输赢检测
三、函数实现
1、菜单
一个好的菜单往往是一款......(省略1万字精髓),所以我相信,大家一定能轻松地完成一个精美的菜单,以下只是一个参考。
int menu(){
puts("--------------------------");
puts(" *** 三子棋 *** ");
puts("--------------------------");
puts("--------1.开始游戏--------");
puts("--------0.退出游戏--------");
puts("--------------------------");
int choice = 0;
puts("请作出你的选择:");
scanf("%d", &choice);
return choice;
}
2、初始化
错误的初始化:char che[3][3]={' '};
数组初始化没写的地方默认为0,这也是为什么int arr[5]={0};数组就全为零的原因,所以要用for循环来一一赋值。
void init(char che[ROW][COL])
{
int i, j;
for (j = 0; j < ROW; j++)
for (i = 0; i < COL; i++)
che[j][i] = ' ';
}
3. 打印棋盘
需要注意的地方只有,换行,printf()不会自己加换行,puts()会,大多数时候我单独加换行时用的是puts("");而不是printf("\n");
void display(char che[ROW][COL], int row, int col)
{
int i, j;
for (j = 0; j < row; j++)
{
for (i = 0; i < col; i++)
{
printf("%c ", che[j][i]);
if (i != col - 1)
printf("|");
}
puts("");
if (j != row - 1)
for (i = 0; i < col; i++)
{
printf("--");
if (i != col - 1)
printf("|");
}
puts("");
}
return;
}
4、玩家下棋
除了下的位置不能超出棋盘,记得别让玩家下在已经下过的位置了,不然三步秒刷电脑就没意思了。(*号是玩家下的,#号是电脑下的)
void player(char che[ROW][COL])
{
int i, j;
printf("请输入你要走的位置(例:1,3)\n");
scanf("%d,%d", &i, &j);
while (1)
{
if (i > 0 && i <= ROW && j > 0 && j <= COL && che[i - 1][j - 1] != '#' && che[i - 1][j - 1] != '*')
{
che[i - 1][j - 1] = '*';
break;
}
printf("棋盘上没有此位置,或此位置已被占用,请重新输入(例:1,3)\n");
scanf("%d,%d", &i, &j);
}
}
5、电脑下棋
相信不少人是为了这里来看的,我就不废话了,我的思路是给电脑制定一个底层逻辑,第一步统计一行一列一个对角线的玩家棋子和电脑棋子的个数,然后先判断是否有电脑将赢的情况(三个只差一个了),再判断是否有玩家将赢的情况,再去防守。(毕竟,我能推你家水晶还要高地干嘛)。
相信大家也看出来了,这个逻辑只能能处理两种情况,那么第三种没有‘成双’的情况(比如说电脑的第一步),我考虑了两种方式来解决:
(1)、随机放<doge>哎呀,别走呀!我不会搞出这种第二步乱走的笨比的。
直接讲方法二吧,我已经提前把第一步的最优解求出来了,走了第一步,如果玩家想赢走出来的结果就大概率会出现前面的逻辑能处理的问题。
那么如果1进入特殊最后几步(没有成双,但只有一个棋子位),2或者玩家故意放海了捏?
放心,
第一个问题解决方案,因为在我写的逻辑里,判断成双之后是否能下的原理是,遇到空格就记录并标记这行,空格的位置存下,到了下一行只是把flag变0,空格位置不删。
所以只要在最后逻辑判断不成立之后直接用m,n里的不知道从哪一行拐过来的空位就行了。
(棋子满了是检测函数该干的是,管我什么事)
第二个问题解决方案(提前考虑)
玩家放水太多,那我直接在上一个解决方案随机空位放置之前试一试能不能下中间,能下中间就放了,这样成双的概率特别大,如果不能,那就不叫放海了!
函数代码
void computer(char che[ROW][COL])
{
int i, j;
if (round == 0) //第一步得帮电脑走
{
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
if (che[i][j] == '*') //找到玩家的第一步
break;
if (j < COL) //二层跳出
break;
}
if (i == j)
che[0][2] = '#';
else if (i == 0 && j == 2)
che[1][1] = '#';
else if (i == 2 && j == 0)
che[1][1] = '#';
else if (i - j == 1 || j - i == 1)
che[1][1] = '#';
round++; //第一步结束
return;
}
else
{
int m, n, flag = 0; //m,n存储空位,flag判断空位是否位于这一回2
int p = 0, c = 0; //电脑和玩家的棋子数
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
if (che[i][j] == '*')
p++;
if (che[i][j] == '#')
c++;
if (che[i][j] == ' ')
m = i, n = j, flag = 1;//此时标记这个空位是这一行的,每过一行flag变0,m,n不丢
}
if (c == 2 && flag) //优先选择胜出路线
{
che[m][n] = '#';
return;
}
if (p == 2 && flag) //第二优先防御路线
{
che[m][n] = '#';
return;
}
p = 0, c = 0, flag = 0;
}
p = 0, c = 0, flag = 0;
for (i = 0; i < COL; i++) //竖直
{
for (j = 0; j < ROW; j++)
{
if (che[j][i] == '*')
p++;
if (che[j][i] == '#')
c++;
if (che[j][i] == ' ')
m = j, n = i, flag = 1;
}
if (c == 2 && flag) //优先选择胜出路线
{
che[m][n] = '#';
return;
}
if (p == 2 && flag) //第二优先防御路线
{
che[m][n] = '#';
return;
}
p = 0, c = 0, flag = 0;
}
p = 0, c = 0;
for (i = 0, j = 0; i < 3; i++, j++)
{
if (che[i][j] == '*')
p++;
if (che[i][j] == '#')
c++;
if (che[i][j] == ' ')
m = i, n = j;
}
if (c == 2) //优先选择胜出路线
{
che[m][n] = '#';
return;
}
if (p == 2) //第二优先防御路线
{
che[m][n] = '#';
return;
}
p = 0, c = 0;
for (i = 0, j = 2; i < 3; i++, j--)
{
if (che[i][j] == '*')
p++;
if (che[i][j] == '#')
c++;
if (che[i][j] == ' ')
m = i, n = j;
}
if (c == 2) //优先选择胜出路线
{
che[m][n] = '#';
return;
}
if (che[1][1] == ' ') //判断到到这里说明多半是平局或者是玩家的迷惑操作,如果是玩家想让电脑赢那么走中就可以抓住机会
che[1][1] = '#';
else
che[m][n] = '#'; //最后m,n里一定储存了一个空位
return;
}
}
但聪明的的你一定看出来了,这样逻辑上还是有一些漏洞吧,但经过我的测试,这样电脑已经不能算是笨比了,它已经成功的进化成了二比,大多数该抓的机会已经会抓住了。
本人能力有限,只能做到这个地步了(其实我还有一个大胆的想法,穷举,但我觉得这样写就没有思考意义了)
6、胜负判断
不多说,不难(对角线我直接判断的)返回值决定胜负
int detect(char che[ROW][COL])
{
int p = 0, c = 0, count = 0;
int i, j;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
if (che[i][j] == '*')
p++, count++; //这里统计了所有棋子数
if (che[i][j] == '#')
c++, count++;
}
if (p == 3)
return 1; //玩家胜
if (c == 3)
return -1;//电脑胜
p = 0, c = 0;
}
p = 0, c = 0;
for (i = 0; i < COL; i++)
{
for (j = 0; j < ROW; j++)
{
if (che[j][i] == '*')
p++;
if (che[j][i] == '#')
c++;
}
if (p == 3)
return 1;//玩家胜
if (c == 3)
return -1;//电脑胜
p = 0, c = 0;
}
if (che[0][0] == '*' && che[1][1] == '*' && che[2][2] == '*')
return 1;
if (che[0][2] == '*' && che[1][1] == '*' && che[2][0] == '*')
return 1;
if (che[0][0] == '#' && che[1][1] == '#' && che[2][2] == '#')
return -1;
if (che[0][2] == '#' && che[1][1] == '#' && che[2][0] == '#')
return -1;
if (count == 9)
return 55;//平局
else
return 0;
}
四、全部代码
#include <stdio.h>
#include <windows.h>
#define ROW 3
#define COL 3
int round; //回合数,用于电脑出招判断
int menu(); //菜单
void init(char che[ROW][COL]); //初始化棋盘,回合数。。
void display(char che[ROW][COL], int row, int col); //打印棋盘
void player(char che[ROW][COL]); // 玩家回合
void computer(char che[ROW][COL]); //电脑回合(仅适用于三子棋)
int detect(char che[ROW][COL]); //输赢检测(仅用于三子棋)
int main()
{
char che[ROW][COL];
char i; //游戏是否继续的判断
while (menu())
{
system("cls");//清屏更舒适
init(che);
display(che, ROW, COL);
while (1)
{
player(che);
puts("玩家下");
display(che, ROW, COL);
Sleep(1000);
puts("电脑下");
computer(che);
display(che, ROW, COL);
Sleep(1000);
if (detect(che))
break;
}
if (detect(che) == 1)
{
system("cls");
puts("恭喜你,获胜了!");
}
else if (detect(che) == -1)
{
system("cls");
puts("很遗憾,你输了。");
}
Sleep(1000);
system("pause");
system("cls");
puts("是否继续游戏(y/n)");
fflush(stdin);
scanf("%c", &i);
if (i == 'n')
break;
}
return 0;
}
int menu(){
puts("--------------------------");
puts(" *** 三子棋 *** ");
puts("--------------------------");
puts("--------1.开始游戏--------");
puts("--------0.退出游戏--------");
puts("--------------------------");
int choice = 0;
puts("请作出你的选择:");
scanf("%d", &choice);
return choice;
}
void display(char che[ROW][COL], int row, int col)
{
int i, j;
for (j = 0; j < row; j++)
{
for (i = 0; i < col; i++)
{
printf("%c ", che[j][i]);
if (i != col - 1)
printf("|");
}
puts("");
if (j != row - 1)
for (i = 0; i < col; i++)
{
printf("--");
if (i != col - 1)
printf("|");
}
puts("");
}
return;
}
void init(char che[ROW][COL])
{
int i, j;
for (j = 0; j < ROW; j++)
for (i = 0; i < COL; i++)
che[j][i] = ' ';
round = 0; //回合数归零
}
void player(char che[ROW][COL])
{
int i, j;
printf("请输入你要走的位置(例:1,3)\n");
scanf("%d,%d", &i, &j);
while (1)
{
if (i > 0 && i <= ROW && j > 0 && j <= COL && che[i - 1][j - 1] != '#' && che[i - 1][j - 1] != '*')
{
che[i - 1][j - 1] = '*';
break;
}
printf("棋盘上没有此位置,或此位置已被占用,请重新输入(例:1,3)\n");
scanf("%d,%d", &i, &j);
}
}
void computer(char che[ROW][COL])
{
int i, j;
if (round == 0) //第一步得帮电脑走
{
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
if (che[i][j] == '*') //找到玩家的第一步
break;
if (j < COL) //二层跳出
break;
}
if (i == j)
che[0][2] = '#';
else if (i == 0 && j == 2)
che[1][1] = '#';
else if (i == 2 && j == 0)
che[1][1] = '#';
else if (i - j == 1 || j - i == 1)
che[1][1] = '#';
round++; //第一步结束
return;
}
else
{
int m, n, flag = 0; //m,n存储空位,flag判断空位是否位于这一回2
int p = 0, c = 0; //电脑和玩家的棋子数
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
if (che[i][j] == '*')
p++;
if (che[i][j] == '#')
c++;
if (che[i][j] == ' ')
m = i, n = j, flag = 1;//此时标记这个空位是这一行的,每过一行flag变0,m,n不丢
}
if (c == 2 && flag) //优先选择胜出路线
{
che[m][n] = '#';
return;
}
if (p == 2 && flag) //第二优先防御路线
{
che[m][n] = '#';
return;
}
p = 0, c = 0, flag = 0;
}
p = 0, c = 0, flag = 0;
for (i = 0; i < COL; i++) //竖直
{
for (j = 0; j < ROW; j++)
{
if (che[j][i] == '*')
p++;
if (che[j][i] == '#')
c++;
if (che[j][i] == ' ')
m = j, n = i, flag = 1;
}
if (c == 2 && flag) //优先选择胜出路线
{
che[m][n] = '#';
return;
}
if (p == 2 && flag) //第二优先防御路线
{
che[m][n] = '#';
return;
}
p = 0, c = 0, flag = 0;
}
p = 0, c = 0;
for (i = 0, j = 0; i < 3; i++, j++)
{
if (che[i][j] == '*')
p++;
if (che[i][j] == '#')
c++;
if (che[i][j] == ' ')
m = i, n = j;
}
if (c == 2) //优先选择胜出路线
{
che[m][n] = '#';
return;
}
if (p == 2) //第二优先防御路线
{
che[m][n] = '#';
return;
}
p = 0, c = 0;
for (i = 0, j = 2; i < 3; i++, j--)
{
if (che[i][j] == '*')
p++;
if (che[i][j] == '#')
c++;
if (che[i][j] == ' ')
m = i, n = j;
}
if (c == 2) //优先选择胜出路线
{
che[m][n] = '#';
return;
}
if (che[1][1] == ' ') //判断到到这里说明多半是平局或者是玩家的迷惑操作,如果是玩家想让电脑赢那么走中就可以抓住机会
che[1][1] = '#';
else
che[m][n] = '#'; //最后m,n里一定储存了一个空位
return;
}
}
int detect(char che[ROW][COL])
{
int p = 0, c = 0, count = 0;
int i, j;
for (i = 0; i < ROW; i++)
{
for (j = 0; j < COL; j++)
{
if (che[i][j] == '*')
p++, count++; //这里统计了所有棋子数
if (che[i][j] == '#')
c++, count++;
}
if (p == 3)
return 1; //玩家胜
if (c == 3)
return -1;
p = 0, c = 0;
}
p = 0, c = 0;
for (i = 0; i < COL; i++)
{
for (j = 0; j < ROW; j++)
{
if (che[j][i] == '*')
p++;
if (che[j][i] == '#')
c++;
}
if (p == 3)
return 1;
if (c == 3)
return -1;
p = 0, c = 0;
}
if (che[0][0] == '*' && che[1][1] == '*' && che[2][2] == '*')
return 1;
if (che[0][2] == '*' && che[1][1] == '*' && che[2][0] == '*')
return 1;
if (che[0][0] == '#' && che[1][1] == '#' && che[2][2] == '#')
return -1;
if (che[0][2] == '#' && che[1][1] == '#' && che[2][0] == '#')
return -1;
if (count == 9)
return 55;
else
return 0; //平局
}