博观而约取,厚积而薄发。这篇文章主要讲述[C语言小白]三子棋小程序相关的知识,希望能为你提供帮助。
三子棋小程序
- 游戏主函数
- 代码实现的初步工作
- 游戏实现原理与程序优化
-
- 初始化棋盘
- 打印棋盘
- 玩家下棋
- 电脑下棋(这里挖个坑)
- 验证胜负机制与程序的继续进行
- BUG彩蛋(我也不知道为什么会这样)
游戏主函数首先,我们设计三子棋时,要设计选择进入游戏,退出游戏的选项,以及输入错误内容时,重新选择的程序。
将游戏标题函数设置为
将游戏主题函数设置为
若在选择时,输入内容不在程序设定范围内,则要求使用者重新输入。所以程序此处应有一个循环。
因此主函数程序设置如下:
int main()
{
int input = 0;
do
{
menu();
printf("请选择:");
scanf("%d", &
input);
switch (input)
{
case 1:
game();
break;
case 2:
printf("退出游戏\\n");
input -= 2;
break;
default:
printf("输入错误,请重新选择\\n");
break;
}
} while (input);
return 0;
}
当输入1时,进入case 1:运行game()函数;
当输入2时,进入case 2:打印“退出游戏”并跳出switch循环,在dowhile结尾进行检测时,检测结果为0,跳出循环,退出程序;
当输入其他字符时,进入default,打印‘“输入错误,请重新选择”。因为input非零,所以继续进行dowhile循环。
代码实现的初步工作首先我们创建游戏程序文件game.c,和头文件game.h
为了提高系统的可操作性,我们先将程序中的行与列定义为ROW和COL
所以头文件中预处理指令如下:
#define _CRT_SECURE_NO_WARNINGS 1//scanf函数防止报错
#include <
stdio.h>
//printf函数
#include <
time.h>
//time函数,产生随机值(后面讲)#define ROW 3//行
#define COL 3//列
然后在game.c和主函数所在文件test_tic_tac_toe.c中引用头文件:
接着设计主函数中的==menu()==函数。因为打印游戏菜单不需要返回值,所以函数类型void即可:
void menu()
{
printf("************************\\n");
printf("******** 1.play ********\\n");
printf("******** 2.exit ********\\n");
printf("************************\\n");
}
游戏实现原理与程序优化根据主函数的设计,我们继续设计game()函数:
同理不需要返回值,所以函数类型为void:
void game()
{
char board[ROW][COL];
//存储数据,二维数组
//初始化棋盘
//打印棋盘
while (1)
{
//玩家落子
//判断游戏能否继续进行
//电脑落子
//判断游戏能否继续进行(胜、负和平局,若存在三者之一则跳出while循环)
}
//判断输赢,打印胜负结果
}
如上文所示,我们先整理出了一个大致的代码设计方向。
初始化棋盘首先我们需要将数组(棋盘中9个落子点的位置)初始化为“ ”(空格)
void InitBoard(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++)
{
board[i][j] = \' \';
}
}
}
然后再在头文件game.h中声明,并做好注释:
//初始化棋盘
void InitBoard(char board[ROW][COL], int row, int col);
打印棋盘我们的棋盘由“|”(回车上面)和“-”(减号)组成
所以依次为
||
---|---|---//三个---和一个|
||
---|---|---
||
程序可以直接选择直接设置:
void DisplayBoard(char board) //打印棋盘
{
printf(" %c | %c | %c ", board[0][0],board[0][1],board[0][2]);
printf("---|---|---");
printf(" %c | %c | %c ", board[1][0],board[1][1],board[1][2]);
printf("---|---|---");
printf(" %c | %c | %c ", board[2][0],board[2][1],board[2][2]);
}
但是缺点很明显,如果棋盘大小不是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]);
//初次打印棋盘都为空格,之后玩家电脑落子后,打印的是落子内容(*或#)
if (j <
COL - 1)//打印的|次数要少一次
{
printf("|");
}
}
printf("\\n");
int k = 0;
if (i <
ROW - 1)
{
for (k = 0;
k <
COL;
k++)
{
printf("---");
if (k <
COL - 1)//打印的|次数要少一次
{
printf("|");
}
}
printf("\\n");
}
}
}
然后我们同样在头文件game.h中声明此函数,并做好注释:
//打印棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);
这样我们可以用此函数做到初次打印空棋盘,以及之后每次玩家/电脑落子后的棋盘。
玩家下棋玩家下棋时,我们可以让玩家输入坐标,以此确定落子位置。
这样具体到程序上,就是对上文初始化的二维数组进行赋值。
但是玩家的人类思维中,坐标是1,2,3这样顺序的,而程序是以0,1,2的次序排序。所以我们要将玩家输入的坐标-1,以此对标数组中的从0开始排序。
此外,我们还要玩家落子点有空位,且存在的问题。所以我们需要一个if的选择语句进行验证,若不满足条件则要求玩家重新输入。因此还要一个while循环
若玩家输入坐标存在,则对二维数组的此项赋值为“*”。
整理思路,我们可以敲出以下代码:
void PlayerMove(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("玩家落子,请输入坐标:\\n");
scanf("%d %d", &
x, &
y);
if (x >
= 1 &
&
x <
= ROW &
&
y >
= 1 &
&
y <
= COL)//判断坐标位置存在
{
if (board[x - 1][y - 1] == \' \')
{
board[x - 1][y - 1] = \'*\';
break;
}
else
{
printf("坐标已被占用,请重新输入");
}
}
else
{
printf("坐标错误,请重新输入");
}
}
同样,在头文件game.h中声明此函数,并做好注释:
//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col);
电脑下棋(这里挖个坑)电脑落子,因为编者目前还不会制作更好的程序,做出一个智能的对战AI,所以电脑落子采用随机的方式。
在头文件game.h中引用
再在主函数的dowhile循环前加入
【[C语言小白]三子棋小程序】这样,我们就可以产生随机数了。
但是由于此随机数的范围巨大,所以我们需要将它的范围缩小到0~2,以此对应二维数组中的位置。此处最简单的就是用取模符号了(%)。
(挖坑:以后学到如何做这么一个智能对战的程序后,我再对这里的电脑落子程序进行优化)
其他的整体思路和玩家落子程序的思路类似,但!是!:
因为我们不需要机器报出位置已被占用的信息,所以不需要让它像玩家输入的程序一样打印“请重新输入”,只需要让机器重新选择坐标进行落子(重新选择二维数组的某一项赋值成“#”)
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;
}
}
}
同样,在头文件game.h中声明此函数,并做好注释:
//电脑下棋
void ComputerMove(char board[ROW][COL], int row, int col);
验证胜负机制与程序的继续进行根据三子棋的规则,只要连续的三个子相同就可以判断胜负了。
所以对于程序是否需要,能否继续运行,总共存在以下四种情况:
所以此处我们设计一个返回类型为char的函数:
首先对每行,每列,两个对角线进行判断是否达成三点一线同一种棋子,且不为空格。
为了减少程序的运行次数,代码的总体量,我们将玩家/电脑的返回值分别设置为“*”和“#”,与玩家/电脑的落子一致(省去了一半的代码)
所以我们设计如下:
但是因为次函数体量较大,我们再设计一个新的函数来判断棋盘是否满了(是否平局)
方法很简单,只要每个位置都不为空格,则棋盘已满,代码如下:
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 <
ROW;
j++)
{
if (board[i][j] != \' \')
{
return 0;
}
}
}
return 1;
}
然后我们就可以设计出判断程序如何继续执行的函数了:
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][1] == board[i][2] &
&
board[i][0] != \' \')
{
return board[i][0];
}
}
//判断列
for (i = 0;
i <
COL;
i++)
{
if (board[0][i] == board[1][i] &
&
board[1][i] == board[2][i] &
&
board[0][i] != \' \')
{
return board[0][i];
}
}
//判断对角线
if ((board[0][0] == board[1][1] &
&
board[0][0] == board[2][2] &
&
board[0][0] != \' \') || (board[0][0] == board[1][1] &
&
board[0][0] == board[2][2] &
&
board[0][0] != \' \'))
{
return board[1][1];
}
//判断平局(棋盘满没满)平局返回1,还有空位返回0
int ret = IsFull(board, ROW, COL);
if (ret == 1)
{
return \'D\';
//返回值为1,平局
}
else
{
return \'C\';
//返回值为0,游戏仍在继续
}
}
同样,在头文件game.h中声明函数,并做好注释:
//判断输赢
//1.玩家胜利返回*
//2.电脑胜利返回#
//3.平局返回D
//4.游戏继续返回C
char IsWin(char board[ROW][COL], int row, int col);
//判断平局(棋盘满没满)平局返回0,还有空位返回1
int IsFull(char board[ROW][COL], int row, int col);
这时候我们回到开始的game()函数:
```c
void game()
{
char board[ROW][COL];
//存储数据,二维数组
//初始化棋盘
//打印棋盘
while (1)
{
//玩家落子
//判断游戏能否继续进行
//电脑落子
//判断游戏能否继续进行(胜、负和平局,若存在三者之一则跳出while循环)
}
//判断输赢,打印胜负结果
}
依次将设计好的函数进行代入,完善整理程序,结果如下:
void game()
{
char board[ROW][COL];
//存储数据,二维数组
InitBoard(board, ROW, COL);
//初始化棋盘
DisplayBoard(board, ROW, COL);
// 打印棋盘
int ret = 0;
while (1)
{
PlayerMove(board, ROW, COL);
//玩家落子
DisplayBoard(board, ROW, COL);
ret = IsWin(board, ROW, COL);
//判断玩家是否胜利
if (ret != \'C\')
{
break;
//若结果不为能继续游戏,则跳出循环
}
ComputerMove(board, ROW, COL);
//电脑落子
DisplayBoard(board, ROW, COL);
ret = IsWin(board, ROW, COL);
//判断电脑是否胜利
if (ret != \'C\')
{
break;
}
}
//判断输赢,是否平局
if (ret == \'*\')
{
printf("玩家胜利\\n");
}
if (ret == \'#\')
{
printf("玩家胜利\\n");
}
if (ret == \'D\')
{
printf("平局\\n");
}
DisplayBoard(board, ROW, COL);
// 游戏结束,再次打印棋盘
}
以上就是三子棋的整体思路了
BUG彩蛋(我也不知道为什么会这样)本来程序是好好的,按照设计进行输入:
文章图片
所以我自信满满地把它的exe文件发给了校友,让他玩玩看。
结果他输入了一个英文字符,然后敲下回车……
结果就这样了:
文章图片
没错,无限打印。
这里是啥原理我就不明白了。
有大佬能解答一下咩?
推荐阅读
- app 自动化测试 - 多设备并发 -appium+pytest+ 多线程
- 从源码角度分析 MyBatis 工作原理
- vCenter Server 7.0 Update 1在部署阶段2无法保存IP配置导致部署失败
- OpenHarmony HDF 驱动框架介绍和驱动加载过程分析
- 如何避免WordPress主题升级更改我的父主题functions.php()
- 每个产品摘要后,如何在WooCommerce上添加单独的html代码
- 添加WordPress编辑器
- 基于自定义客户字段的其他订单电子邮件
- 将短代码添加到WordPress模板文件