没有一蹴而就,只有百炼成钢。
目录
1.三子棋简介
2.设计思路
3.代码实现
3.1 初始化
3.2 打印棋盘
?3.3 玩家下棋
?3.4 电脑下棋
?3.5 条件判定
3.5.1 定义变量
3.5.2 右对角线判定
3.5.3 左对角线判定
3.5.4 行和列判定
?4.组合
5.源代码
6.总结
1.三子棋简介
三子棋是黑白棋的一种。三子棋是一种民间传统游戏,又叫九宫棋、圈圈叉叉、一条龙、井字棋等。将正方形对角线连起来,相对两边依次摆上三个双方棋子,只要将自己的三个棋子走成一条线,对方就算输了。但是,有很多时候会出现和棋的情况。常见的三子棋如图所示
?知道了三子棋的规则后,现在我们来设计怎么用C语言来实现它。我们采用的是软件工程模块化思路设计,在把功能一一设计完毕时,最后进行封装。所以说小程序菜单我们放在最后设计。本次代码编程软件使用的是Vs2022。
2.设计思路
我们想要下棋,则必须要有棋盘,所以首先就是打印棋盘。我们在下棋的时候,我们需要有什么东西来保存我们下好的“棋子”,看到这个棋盘,我们首先想到的就是二维数组。当我们有了棋盘,有了二维数组来存储我们的数据时,接下来就是自己下棋和电脑下棋。每当下一次棋完毕时,我们又需要来判断一下是否构成了赢得游戏的条件,如果哪边达成条件,则游戏结束,输出该方胜利。如果棋盘下满都还没有分出胜负,则输出平局。
综上,我们需要做的大概就是用C语言实现以下功能
1.打印棋盘功能
2.玩家下棋功能
3.电脑下棋功能
4.判定功能
3.代码实现
3.1 初始化
首先我们先定义一个二维数组用于保存我们的数据,并对二维数组进行初始化?
char arr[Row][Col] = { 0 };
init_chess(arr); //初始化
注意:这里的Row和Col分别是棋盘的行和列,这样定义方便将棋盘进行修改,是一个很妙的地方,实现N子棋
#define Row 3
#define Col 3
初始化功能代码:
void init_chess(char arr[Row][Col]) // 初始化棋盘
{
int i = 0;
int j = 0;
for (i = 0; i < Row; i++)
{
for (j = 0; j < Col; j++)
{
arr[i][j] = ' ';
}
}
}
二维数组的初始化我们是通过两个for循环嵌套遍历二维数组的每一格,将其初始化为空格。?
3.2 打印棋盘
初始化好了之后,正式开始打印我们的棋盘。首先我们先看看打印出来的棋盘。
先行放上打印棋盘功能代码:
void input_chess(char arr[Row][Col]) //打印棋盘
{
int i = 0;
int j = 0;
for (i = 0; i < Row; i++)
{
for (j = 0; j < Col; j++)
{
printf(" %c ", arr[i][j]);
if (j != Col - 1)
{
printf("|");
}
}
printf("\n");
if (i != Row - 1)
{
for (j = 0; j < Col; j++)
{
printf("---");
if (j != Col - 1)
{
printf("|");
}
}
printf("\n");
}
}
}
?接下来我们对该代码进行逐步分析,如下图,两个嵌套for循环对二维数组进行遍历打印,注意打印格式为空格,arr[i][j],空格。空格是为了方便我们更好的观察。
?下面代码的意思是每当打印到每一行的最后一列的时候,就不用打印 “ | ”? 分割线。这个根据我们打印出的棋盘也很好理解。最后打印完一行后进行换行。
if (j != Col - 1)
{
printf("|");
}
?值得一提的是我们这个打印方式不限于三子棋棋盘的打印,可以实现n子棋棋盘的打印,这里又体现出两个define定义的行列Row,Col的好处了。
?3.3 玩家下棋
?接下来我们就是实现下棋功能,还是一样,我们先行放上代码然后逐步分析。
?玩家下棋功能代码:
void player_chess(char arr[Row][Col]) //玩家下棋
{
int i = 0;
int j = 0;
while (1)
{
printf("你下:\n");
scanf("%d %d", &i, &j);
if (i > 0 && i <= Row && j > 0 && j <= Col)
{
if (arr[i - 1][j - 1] == ' ')
{
arr[i - 1][j - 1] = '*';
break;
}
else
{
printf("已被占用,请重新下棋:>\n");
}
}
else
{
printf("输入的坐标不合法,请重新输入\n");
}
}
input_chess(arr);
}
由于数组下标是从0开始,出于程序面向用户友好性考虑,用户输入的坐标是从1开始,所以在数组中我们需要把坐标 i 和 j 减去一个1。??
首先我们先输出信息提示下棋,然后输入两个坐标,接下来用到 if 语句判断,如果坐标合法(坐标都在行列范围内),则进入 if 语句内继续执行,如果坐标不合法,则输出提示信息,要求重新输入。当我们坐标合法进入 if 语句内继续执行到第二个 if 语句判定时,这个 if 语句的意思是如果该坐标为空格,代表当前坐标没有棋子,那就将 * 赋值给该坐标。( * 表示玩家下棋的棋子)如果这个坐标不是空格,那就表示这个坐标已经有棋子被占用,那我们就输出提示信息,要求重新下棋。由于涉及到重复下棋,所以我们用 while(1)?循环,当坐标合法时,break 跳出循环。然后调用打印棋盘函数,打印出我们当前下的棋。如下图所示:
?3.4 电脑下棋
电脑下棋和玩家下棋相似,我们先行放出代码。
电脑下棋功能代码:
void computer_chess(char arr[Row][Col])
{
int i = 0;
int j = 0;
printf("电脑下:>\n");
while (1)
{
i = rand() % Row;
j = rand() % Col;
if (arr[i][j] == ' ')
{
arr[i][j] = '#';
break;
}
}
input_chess(arr);
}
?一样的,我们还是先输出提示信息,电脑下棋。然后坐标用rand()函数生成随机数模上我们的行列坐标,生成的数刚好在行列范围内。坐标生成好之后,我们也需要判定该坐标是不是空格,如果是的话我们就将 # 赋值给该坐标,由于是电脑下棋,如果该坐标被占用,我们不需要输出提示信息,只需要继续生成随机数直到满足条件为止,因为这次下棋也涉及到重复下棋,所以我们还是用while(1) 循环,满足条件赋值后break跳出,然后打印结果。如下图所示:
这里rand()函数的使用我们需要在主函数设置一个随机数种子srand((unsigned int)time(NULL)),至于rand函数的具体使用方法本次文章就不多加叙述,读者自行搜索。
?3.5 条件判定
结果条件判定我们还是先把代码放上来,然后逐步分析。但是值得一提的是,当我们行列定义为不相等的时候,例如3行5列,5行4列等,这种情况我们认为是构成连续行和列中较小的棋子数就获胜,如3行5列中,当3颗棋子连续时,就获胜。5行4列中,当4颗棋子连续时,就获胜。判定函数的类型为char类型,拥有返回值,我们用 * 返回表示玩家赢,用 # 返回表示电脑赢,用 @ 返回表示平局。
条件判定代码:
char think_chess(char arr[Row][Col]) //输赢判断函数
{
int i = 0;
int j = 0;
int num = Row > Col ? Col : Row;
int flag = 0;
int sum1 = 0;
int sum2 = 0;
int sum3 = 0;
int sum4 = 0;
j = Col - 1;
while (i < Row && j >= 0)
{
if (arr[i][j] == '*')
{
sum1++;
if (sum1 == num)
{
return '*';
}
}
if (arr[i][j] == '#')
{
sum2++;
if (sum2 == num)
{
return '#';
}
}
i++;
j--;
}
//
sum1 = 0;
sum2 = 0;
for (i = 0; i < (Row > Col ? Col : Row); i++)
{
if (arr[i][i] == '*')
{
sum1++;
if (sum1 == num)
{
return '*';
}
}
if (arr[i][i] == '#')
{
sum2++;
if (sum2 == num)
{
return '#';
}
}
}
for (i = 0; i < Row; i++)
{
sum1 = 0;
sum2 = 0;
sum3 = 0;
sum4 = 0;
for (j = 0; j < Col; j++)
{
if (arr[i][j] == ' ')
{
flag = 1;
}
//列
if (arr[i][j] == '*')
{
sum1++;
if (sum1 == num)
{
return '*';
}
}
if (arr[i][j] == '#')
{
sum2++;
if (sum2 == num)
{
return '#';
}
}
//行
if (arr[j][i] == '*')
{
sum3++;
if (sum3 == num)
{
return '*';
}
}
if (arr[j][i] == '#')
{
sum4++;
if (sum4 == num)
{
return '#';
}
}
}
}
if (flag == 0)
{
return '@';
}
return '1';
}
?接下来我们进行逐步分析。
3.5.1 定义变量
?这里的num记录行和列中较小的数,方便后面的条件判定。
3.5.2 右对角线判定
?这次循环我们判定的是棋盘中的右对角线连续,如图,我们将Col-1的值赋值给j,这里我们用i表示行,j表示列。当同时满足条件i<Row和j>=0时,进入循环。我们用sum1和sum2分别统计玩家的棋子数和电脑的棋子数。并在后面跟着if语句判定,当玩家棋子数达到规定赢棋数目时,return返回 * ,当电脑棋子数达到规定赢棋数目时,return返回 #?,如果都没有达到,则继续i++,j--,直到满足return返回条件时返回跳出或者不满足while循环条件时,跳出循环。
3.5.3 左对角线判定
?这次循环我们判定的是棋盘左对角线,如图。与右对角线判定相似,这次我们用for循环,同样是用sum1和sum2分别记录玩家和电脑棋子数,接着if语句判定,当棋子数达到规定赢棋数目时就返回对应玩家棋子,否则就继续执行循环,直到跳出循环。
3.5.4 行和列判定
?这次判定中,第一个if语句用于判定棋盘中是否还有空格,当棋盘中还存在空格时,就将flag置为1。与第六个if语句相对应,当flag等于0时,也就是说棋盘中不存在空格,并且前面的判定条件都不满足时,返回?@表示平局。第二个if和第三个if语句表示对每一行进行判定,用sum1统计玩家棋子数,用sum2统计电脑棋子数,当达到规定赢棋数目时,返回对应符号。 第四个if和第五个if语句表示对每一列进行判定,用sum3统计玩家棋子数,sum4统计电脑棋子数,当达到相应棋子数时,返回对应符号。
?4.组合
?当所有功能模块都完成好,我们就只需要进行最后的组合。首先就是打印菜单,我们用menu函数打印出我们的菜单。
?menu函数:
void menu()
{
printf("~~~~~~~~~~~~~~~~~~~~~~~~~\n");
printf("~~~~~~~~~1.start~~~~~~~~~\n");
printf("~~~~~~~~~0.exit ~~~~~~~~~\n");
printf("~~~~~~~~~~~~~~~~~~~~~~~~~\n");
}
当菜单打印好之后,我们在main函数中调用menu函数,然后输出相应的文字提示信息,让用户进行选择输入并赋值给变量i,然后用switch语句进行选择,因为用户可能输入错误或者想连续玩游戏需要重复输入,所以在这里用while(i)循环,只有当我们用户想要退出程序输入0时,i = 0,循环结束,程序退出。
int main()
{
int i = 1;
srand((unsigned int)time(NULL));
while (i)
{
menu();
printf("请输入你的选择\n");
scanf("%d", &i);
switch (i)
{
case 1:
game();
break;
case 0:
printf("成功退出!\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
}
return 0;
}
再之后就是我们玩游戏的game函数,在棋盘的初始化和打印之后,就开始下棋了,下棋是一个重复循环过程,所以这里用到了while(1)循环,只有当结果出来时,才会跳出循环。这里我们用ch接收判定函数的返回值,当返回值为 * ,表示玩家赢,输出相应信息结束游戏。当返回值为 # ,表示电脑赢,输出相应信息结束游戏。当返回值为 @ ,表示平局,输出相应信息结束游戏。当返回值为 ‘1’,表示没有结果,继续下棋。
这里我们还对程序进行了一个小小的优化,我们定义了i,j,val变量,用i统计玩家下棋次数,用j统计电脑下棋次数,val统计规定赢棋数,当下棋数未达到规定赢棋数时,就不需要进行判定,节省进行判定的时间。
void game()
{
int i = 0;
int j = 0;
char ch = 0;
int val = Row > Col ? Col : Row;
char arr[Row][Col] = { 0 };
init_chess(arr); //初始化
input_chess(arr); //打印
while (1)
{
player_chess(arr); //玩家下
i++;
if (i >= val)
{
ch = think_chess(arr);
if (ch == '*')
{
printf("玩家赢\n");
break;
}
if (ch == '#')
{
printf("电脑赢\n");
break;
}
if (ch == '@')
{
printf("平局\n");
break;
}
}
computer_chess(arr); // 电脑下
j++;
if (j >= val)
{
ch = think_chess(arr);
if (ch == '*')
{
printf("玩家赢\n");
break;
}
if (ch == '#')
{
printf("电脑赢\n");
break;
}
if (ch == '@')
{
printf("平局\n");
break;
}
}
}
}
5.源代码
本次编程分为3个文件,head.h,test.c,chess.c。head.h用于存放头文件和函数声明,test.c用于放主函数进行测试,chess.c用于放功能模块代码,以下是3个文件源代码。
head.h
#define _CRT_SECURE_NO_WARNINGS 1
#define Row 3
#define Col 3
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
void init_chess(char arr[Row][Col]); //初始化
void input_chess(char arr[Row][Col]); //打印棋盘
void player_chess(char arr[Row][Col]); //玩家下
void computer_chess(char arr[Row][Col]); //电脑下
char think_chess(char arr[Row][Col]); //判输赢
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "head.h"
void game()
{
int i = 0;
int j = 0;
char ch = 0;
int val = Row > Col ? Col : Row;
char arr[Row][Col] = { 0 };
init_chess(arr); //初始化
input_chess(arr); //打印
while (1)
{
player_chess(arr); //玩家下
i++;
if (i >= val)
{
ch = think_chess(arr);
if (ch == '*')
{
printf("玩家赢\n");
break;
}
if (ch == '#')
{
printf("电脑赢\n");
break;
}
if (ch == '@')
{
printf("平局\n");
break;
}
}
computer_chess(arr); // 电脑下
j++;
if (j >= val)
{
ch = think_chess(arr);
if (ch == '*')
{
printf("玩家赢\n");
break;
}
if (ch == '#')
{
printf("电脑赢\n");
break;
}
if (ch == '@')
{
printf("平局\n");
break;
}
}
}
}
void menu()
{
printf("~~~~~~~~~~~~~~~~~~~~~~~~~\n");
printf("~~~~~~~~~1.start~~~~~~~~~\n");
printf("~~~~~~~~~0.exit ~~~~~~~~~\n");
printf("~~~~~~~~~~~~~~~~~~~~~~~~~\n");
}
int main()
{
int i = 1;
srand((unsigned int)time(NULL));
while (i)
{
menu();
printf("请输入你的选择\n");
scanf("%d", &i);
switch (i)
{
case 1:
game();
break;
case 0:
printf("成功退出!\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
}
return 0;
}
chess.c
#pragma once
#include "head.h"
void init_chess(char arr[Row][Col]) // 初始化棋盘
{
int i = 0;
int j = 0;
for (i = 0; i < Row; i++)
{
for (j = 0; j < Col; j++)
{
arr[i][j] = ' ';
}
}
}
void input_chess(char arr[Row][Col]) //打印棋盘
{
int i = 0;
int j = 0;
for (i = 0; i < Row; i++)
{
for (j = 0; j < Col; j++)
{
printf(" %c ", arr[i][j]);
if (j != Col - 1)
{
printf("|");
}
}
printf("\n");
if (i != Row - 1)
{
for (j = 0; j < Col; j++)
{
printf("---");
if (j != Col - 1)
{
printf("|");
}
}
printf("\n");
}
}
}
void player_chess(char arr[Row][Col]) //玩家下棋
{
int i = 0;
int j = 0;
while (1)
{
printf("你下:\n");
scanf("%d %d", &i, &j);
if (i > 0 && i <= Row && j > 0 && j <= Col)
{
if (arr[i - 1][j - 1] == ' ')
{
arr[i - 1][j - 1] = '*';
break;
}
else
{
printf("已被占用,请重新下棋:>\n");
}
}
else
{
printf("输入的坐标不合法,请重新输入\n");
}
}
input_chess(arr);
}
void computer_chess(char arr[Row][Col])
{
int i = 0;
int j = 0;
printf("电脑下:>\n");
while (1)
{
i = rand() % Row;
j = rand() % Col;
if (arr[i][j] == ' ')
{
arr[i][j] = '#';
break;
}
}
input_chess(arr);
}
char think_chess(char arr[Row][Col]) //输赢判断函数
{
int i = 0;
int j = 0;
int num = Row > Col ? Col : Row;
int flag = 0;
int sum1 = 0;
int sum2 = 0;
int sum3 = 0;
int sum4 = 0;
j = Col - 1;
while (i < Row && j >= 0)
{
if (arr[i][j] == '*')
{
sum1++;
if (sum1 == num)
{
return '*';
}
}
if (arr[i][j] == '#')
{
sum2++;
if (sum2 == num)
{
return '#';
}
}
i++;
j--;
}
//
sum1 = 0;
sum2 = 0;
for (i = 0; i < num; i++)
{
if (arr[i][i] == '*')
{
sum1++;
if (sum1 == num)
{
return '*';
}
}
if (arr[i][i] == '#')
{
sum2++;
if (sum2 == num)
{
return '#';
}
}
}
for (i = 0; i < Row; i++)
{
sum1 = 0;
sum2 = 0;
sum3 = 0;
sum4 = 0;
for (j = 0; j < Col; j++)
{
if (arr[i][j] == ' ')
{
flag = 1;
}
//行
if (arr[i][j] == '*')
{
sum1++;
if (sum1 == num)
{
return '*';
}
}
if (arr[i][j] == '#')
{
sum2++;
if (sum2 == num)
{
return '#';
}
}
//列
if (arr[j][i] == '*')
{
sum3++;
if (sum3 == num)
{
return '*';
}
}
if (arr[j][i] == '#')
{
sum4++;
if (sum4 == num)
{
return '#';
}
}
}
}
if (flag == 0)
{
return '@';
}
return '1';
}
本次代码也已经上传到了gitee上,也可以自行到gitee上下载代码
test_7_28 三子棋 · 牛爷爷爱写代码/学习垃圾 - 码云 - 开源中国 (gitee.com)
6.总结
?本次代码部分地方任存在不足,还有很多地方任然可以继续优化,因为时间原因就先写到这,后期再加以修改,争取为读者提供更加优秀易懂的代码,本篇文章介绍我个人感觉还是有很多地方没有把我本来想说的意思完全表达出来,因为作者本人也不是很善于表达,因此文章如果有词不达意的地方还希望读者多多包含,大家可以在评论区提问,也可以给作者提供一些意见或者建议。作者本人也会认真看读者提供的意见和建议,努力改进,给大家提供更加优秀有价值的文章。代码如果有不足之处也希望大家可以在评论区指出,欢迎大家多多讨论评价。
?
|