文章目录
-
-
- 1. 三子棋实现的基本思路
- 2. 主函数
- 3. menu函数和game函数
- 4. game.c中的具体实现函数
-
- 4.1 初始化函数 InitBoard()
- 4.2 打印棋盘函数 DispayBoard()
- 4.3 玩家操作函数 PlayerMove()
- 4.4 电脑下棋函数 ComputerMove()
- 4.5 判断胜负函数 IsWin()
- 4.6 判断棋盘是否已满函数 IsFull()
- 5. 头文件
- 6. 实现效果
-
1. 三子棋实现的基本思路
三子棋是我们在儿时玩过的最经典的游戏之一,游戏的规则很简单,在一个 3×3 的棋盘中,两人交替下棋,当某一方的三个棋子连成三点一线,那么即为胜利。
那么问题来了,我们怎样巧妙的用编程的思维去完成这样一个小项目呢?首先,我们清楚,如果我们在一个源文件中将所有相关的代码全部挤进去,那么写到一半的时候你可能会窒息,“我写到哪了?”“我在写什么?”,一连串发自灵魂的自问可能会让你明白这世界并不美好。既然这样我们何不把写的代码按功能分类,分别放到不同的源文件中去呢?因此我们就可以在主函数 main.c 、功能函数 game.c 、以及头文件 game.h 中进行编译啦。
有了这样的一个大前提,我们知道游戏一般都会有菜单,让你选择玩游戏还是退出,当你进入了游戏,棋盘会出现在你的面前,等待你下的第一步棋,你下完了,电脑会自动下完第二步,这样一直循环,一直等到决出胜负。
这个时候你不会天真的以为事情就这么的顺风顺水吧,让我们来看一看我们到底要实现几个函数:初始化棋盘、打印棋盘、玩家下棋、电脑下棋、判断胜负,况且我们如何存储这个棋盘的棋子的数据呢?显然我们可以用一个二维数组来存储每一个格子的信息,接下来让我们一步一步地来实现。
2. 主函数
int main()
{ int input = 0;
//初始化输入变量
srand((unsigned int)time(NULL));
//配置随机函数
do
{menu();
//显示菜单
printf("请选择:");
scanf("%d", &input);
switch (input)//做出选择:玩还是退出
{case 1:
game();
//选择玩,进入游戏函数
break;
case 0:
printf("退出游戏\n");
//选择不玩,退出游戏,程序终止
break;
default:
printf("选择错误,请重新输入\n");
//输入不合法,再次输入
break;
}
} while (input);
return 0;
}
有了以上的主函数作为参考,接下来的实现就是要我们一步一个脚印写出来了。首先我们观察上述代码是否有未知的需求,显然 menu() 函数和 game() 函数我们都是未知的,接下来我们来实现。
3. menu函数和game函数
就像在第一点说的那样,游戏的实现主要在这一部分,我们需要将各种功能都封装成函数。我们设棋盘有 ROW 行,COL 列,而不是死板的将行和列都设置为 3 ,这样我们把ROW和COL定义在头文件中 ,就优化了代码的可更改性。另外,在所有要封装的函数中,判断胜负的函数是需要返回值的,根据返回值的不同,通过 if 语句的判断从而打印出不同的结果。我们假设返回字符 * 代表玩家胜利,返回 # 代表电脑胜利,返回 C 代表游戏继续(没有分出胜负),返回 Q 代表平局。
void menu()//打印游戏菜单界面
{ printf("*********************\n");
printf("****1.PLAY*****\n");
printf("****0.EXIT*****\n");
printf("*********************\n");
}
void game()
{ char ret;
//初始化返回变量
char board[ROW][COL];
//定义棋盘数组
InitBoard(board, ROW, COL);
//将棋盘数组内的元素都初始化为空格
DisplayBoard(board, ROW, COL);
//打印出棋盘,让棋盘可视化
while (1)//进入游戏循环
{PlayerMove(board, ROW, COL);
//玩家下棋
DisplayBoard(board, ROW, COL);
//打印出棋盘,让棋盘可视化
ret = IsWin(board, ROW, COL);
//判断是否分出了胜负
if (ret != 'C')//只有返回C时游戏才没有分出胜负
{break;
}
ComputerMove(board, ROW, COL);
//电脑下棋
DisplayBoard(board, ROW, COL);
//打印出棋盘,让棋盘可视化
ret = IsWin(board, ROW, COL);
//判断是否分出了胜负
if (ret != 'C')//只有返回C时游戏才没有分出胜负
{break;
}
}
//退出循环则是已经分出胜负的情况
if (ret == '*')
{printf("玩家赢!\n");
}
else if (ret == '#')
{printf("电脑赢!\n");
}
else
{printf("平局!\n");
}
4. game.c中的具体实现函数
4.1 初始化函数 InitBoard() 在游戏开始之前,我们需要将表示棋盘的数组里面的元素全部初始化,大家想一想,在什么都没有操作的棋盘上是不是什么都没有,但是我们不能把数组初始化为没有呀,什么都没有只是视觉上的表现,我们不妨将数组每个元素初始化为空格。
//char board[ROW][COL]用于接收棋盘数据的数组
//row用于接收棋盘的行数
//col用于接收棋盘的列数
void InitBoard(char board[ROW][COL], int row, int col)
{ int i = 0;
for (i = 0;
i < row;
i++)
{int j = 0;
for (j = 0;
j < col;
j++)
{board[i][j] = ' ';
//将每一个数组元素初始化为空格
}
}
}
4.2 打印棋盘函数 DispayBoard() 虽然我们已经将棋盘的每个元素初始化为了空格,但只是在计算机内存完成的操作,我们的肉眼是无法很好的观察到的,既然要下棋,我们总要将整个棋盘可视化吧,我们计划将棋盘设计成以下格式:
文章图片
我们观察到棋盘一共有五行,第一三五行放置了数组元素与分割线,二四行放置了分隔线,这是一个 3×3 的棋盘,我们试想,如果我们仅仅将棋盘打印出只符合 3×3 棋盘的分隔线,你那未必也太挫了吧,我们想要修改头文件中的数据,而棋盘的大小也跟着改变,这样也就方便了以后程序的优化升级。
我们观察这个棋盘的规律:第一行是由九个空格两条竖线组成的,我们不妨将三个空格和一条竖线看作一组,循环打印,当打印到第三组的时候利用条件语句不打印不就可以了吗?再继续往下观察,可见第一行和第二行的布局与第三行和第四行的布局完全一样,我们也不妨将这两行也看做一组循环打印,至于最后一行,利用相同的方法,即打印到最后一次时不打印下面横竖线分隔线的那一部分。
void DisplayBoard(char board[ROW][COL], int row, int col)
{ int i = 0;
//循环变量
for (i = 0;
i < row;
i++)
{int j = 0;
//嵌套循环变量
//打印有数组元素的行
for (j = 0;
j < col;
j++)
{printf(" %c ", board[i][j]);
// "空格%c空格"
if (j < col - 1)//当没有打印到最后一次时,打印竖线
{printf("|");
}
}
printf("\n");
if (i < row - 1)//最后一组的分隔线不用打印
{int j = 0;
for (j = 0;
j < col;
j++)
{printf("---");
if (j < col - 1)//最后一次的竖线不用打印
{printf("|");
}
}
printf("\n");
}
}
}
打印出的结果如图所示
文章图片
4.3 玩家操作函数 PlayerMove() 玩家的操作无非就是将棋子放入想要放入的位置中,我们设玩家棋子为字符 * ,我们的目的就是将这颗星号放入数组中,这就需要用到二维数组的坐标作为传参,让玩家输入坐标,从而将对应的二维数组元素置为星号。此时我们就得考虑了,输入的坐标大于了二维数组本身的范围怎么办?下过棋子的地方还能不能继续下呢?对应的输入坐标和二维数组的坐标时相同的吗?
显然我们需要给定一个输入坐标的范围;下过棋子的地方再下棋子就会报错,利用循环重新输入;输入的坐标减一才是对应的二维数组的坐标。
void PlayerMove(char board[ROW][COL], int row, int col)
{ printf("玩家走:");
while (1)
{printf("请输入坐标:\n");
int x, y;
scanf("%d%d", &x, &y);
//给定输入坐标的合法范围
if (x >= 1 && x <= row && y >= 1 && y <= col)
{//该坐标下元素不是空格,说明已经被占用
if (board[x - 1][y - 1] != ' ')
{printf("该格子被占用,请重新输入:\n");
}
else
{//没有被占用,将其置为 *
board[x - 1][y - 1] = '*';
break;
}
}
else
//其他不合法的输入
{printf("输入错误,请重新输入\n");
}
}
}
4.4 电脑下棋函数 ComputerMove() 不会吧不会吧,不会有人真的以为电脑会动脑子吧,这里的电脑下棋只不过是利用计算机产生两个随机数作为坐标,然后将电脑的棋子放置到棋盘中,除了要生成随机的坐标,其他的操作都与玩家下棋相似。这时的随机数就成问题了,随机数那么大,坐标那么小怎么办?我们将随机数模上行数或者列数,假如行列都是 3 ,我们不就得到 0,1,2 了吗?当然了,随机数函数 rand() 是需要 srand进行配置的,将时间戳作为变量,可以得到不同的随机数, srand 的配置在主函数中。
void ComputerMove(char board[ROW][COL], int row, int col)
{ printf("电脑走:\n");
while (1)
{int x = rand() % row;
//生成随机的行坐标
int y = rand() % col;
//生成随机的列坐标
//坐标没有被占用,电脑下棋
if (board[x][y] == ' ')
{board[x][y] = '#';
break;
}
}
}
4.5 判断胜负函数 IsWin() 既然要胜利,就要棋子连成三点一线,无非只有四种情况:三个在同一行;三个在同一列;三个在对角线上;三个在斜对角线上。如果没有分出胜负呢?没有分出胜负,要么是平局,要么还要继续下棋直到分出胜负为止。那么我们怎么才能知道是否平局,是否要继续呢?可以知道,如果连棋盘都满了都没有分出胜负,你还不服想耍赖想继续下,可惜棋盘不允许,这时就是平局了;若棋盘还没满,也没有哪一方赢,游戏就得继续。看来有这么多的情况,这个函数是需要返回值的,我们按照第三节所说的那样来设置返回值。在上面我们提到要判断是否棋盘已满,我们不妨将判断棋盘是否已满也封装成一个函数。
char IsWin(char board[ROW][COL], int row, int col)
{ int i = 0;
for (i = 0;
i < row;
i++)
{if (board[i][0] == board[i][1] && board[i][0] == board[i][2] && board[i][0] != ' ')//当行上出现三点一线
{return board[i][0];
//谁赢就返回什么
}
}
for (i = 0;
i < col;
i++)
{if (board[0][i] == board[1][i] && board[0][i] == board[2][i] && board[0][i] != ' ')//当列上出现三点一线
{return board[0][i];
}
}
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0] != ' ')//当对角线上出现三点一线
{return board[0][0];
}
if (board[0][2] == board[1][1] && board[2][0] == board[1][1] && board[0][2] != ' ')//当斜对角线上出现三点一线
{return board[0][2];
}
if (IsFull(board, ROW, COL) == 1)//棋盘满了,平局
return 'Q';
if (IsFull(board, ROW, COL) == 0)//继续
return 'C';
}
4.6 判断棋盘是否已满函数 IsFull() 我们遍历整个数组,如果出现了空格,则棋盘没满。用 0 和 1 作为返回值,0代表没满,1代表已满。
static int IsFull(char board[ROW][COL], int row, int col)//判断棋盘是否已满
{ int i = 0;
int j = 0;
for (i = 0;
i < row;
i++)
{for (j = 0;
j < col;
j++)
{if (board[i][j] == ' ')//若棋盘上出现空格,则棋盘还没满
return 0;
}
}
return 1;
}
5. 头文件
我们实现完了所有的具体功能函数,需要在头文件中添加函数声明,并且在 game.c 和 main.c 中引入 头文件 game.h
#include
#include
#include#define ROW 3
#define COL 3void InitBoard(char board[ROW][COL], int row, int col);
//初始化数组
void DisplayBoard(char board[ROW][COL], int row, int col);
//打印棋盘
void PlayerMove(char board[ROW][COL], int row, int col);
//玩家下棋
void ComputerMove(char board[ROW][COL], int row, int col);
//电脑下棋
char IsWin(char board[ROW][COL], int row, int col);
//判断输赢
6. 实现效果
【游戏|看完这个即便你是菜鸟你也会用C语言实现三子棋】我们来看一看玩家赢得效果
文章图片
文章图片
试一试退出游戏
文章图片
平局
文章图片
电脑赢
文章图片
以上就是本篇博客得全部内容,感谢老爷们得观看,记得赞赞点一手哦~
推荐阅读
- 算法习题|[leetcode] 138.复制带随机指针的链表(C语言)
- C语言系列|零基础C语言学习
- 嵌入式|想成为嵌入式程序员应知道的0x10个基本问题(面试必备)
- 游戏|2020级C语言大作业 - 小球进框
- C语言实现通讯录系统
- 关于递归循环的总结(包含题目解析与思路)
- 随想随写|归并排序(MergeSort)
- 游戏|UnityShader 表面着色器简单例程集合
- [??]各种语言入门|[2021-08-11]C语言入门简明教程第6章-数组、字符串和指针