[C语言小白]三子棋小程序

博观而约取,厚积而薄发。这篇文章主要讲述[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彩蛋(我也不知道为什么会这样)本来程序是好好的,按照设计进行输入:
[C语言小白]三子棋小程序

文章图片

所以我自信满满地把它的exe文件发给了校友,让他玩玩看。
结果他输入了一个英文字符,然后敲下回车……
结果就这样了:
[C语言小白]三子棋小程序

文章图片

没错,无限打印。
这里是啥原理我就不明白了。
有大佬能解答一下咩?


    推荐阅读