时间:2022-12-14 10:29:12 | 栏目:C代码 | 点击:次
本文主要是对前面所学内容进行复习和练习,学习内容包括但不限于:
本文要通过编写三子棋的游戏来进行知识点的再学习。
三子棋是黑白棋的一种。三子棋是一种民间传统游戏,又叫九宫棋、圈圈叉叉、一条龙、井字棋等。将正方形对角线连起来,相对两边依次摆上三个双方棋子,只要将自己的三个棋子走成一条线,对方就算输了。但是,有很多时候会出现和棋的情况。
通过观察上图,三子棋是下在一个井字形或者九宫格的棋盘内的。因此,我们可以先从打印一个棋盘入手开始编写程序。为了方便起见,我们规定输出下面这样的九宫格棋盘。
参照三子棋的下棋规则,制定初步的编程思路:
1、在玩游戏开始前输出一些符号和文字,让界面更加有仪式感,例如:
printf("*********************\n"); printf("*****1. 开始游戏*****\n"); printf("*****0. 退出游戏*****\n"); printf("*********************\n");
2、提示玩家选择:
3、下棋开始前先初始化棋盘,在打印出棋盘
4、玩家下棋后,再次打印出棋盘
5、电脑下棋后,再次打印出棋盘
6,如此循环往复几步后,判断赢家是谁,下棋结束
7、玩家可选择继续玩还是退出程序
我们首先搭建程序框架,让程序能够跑起来,再接着修改程序,实现基本功能。
因为游戏是多次进行的,选择do-while循环结构,编写程序主题结构。
void menu() {//输出提示字符 printf("*********************\n"); printf("*****1. 开始游戏*****\n"); printf("*****0. 退出游戏*****\n"); printf("*********************\n"); } void play() {//玩游戏的代码 printf("玩家选择玩游戏!\n"); } int main() {//利用do-while循环,写成主体结构 int input = 0; do { menu();//输出提示信息 printf("请选择1或者0==> ");//1代表玩,0代表退出游戏 scanf("%d", &input);//玩家输入 switch (input) {//通过分支结构,决定输入是什么 case 1://玩游戏 play();//调用玩游戏的函数 break; case 0://退出游戏 printf("退出游戏!\n"); break; default://重新输入 printf("输入错误,请重新输入1或0 \n"); break; }//先进入循环输出提示信息,当输入1时,满足循环条件,接着执行循环里面的程序,也就是玩游戏 //当输入0时,不满足循环条件,也就是退出游戏 //使用do-while循环符合逻辑 } while (input); return 0; }
运行程序,分别输入1 2 0,输出结果如下所示,达到了预期的界面效果:
上面一小节的代码搭建了程序的框架,能实现程序的基础功能,我们接着进一步play函数中添加代码,完善玩游戏的功能
由于代码较多,将采用模块化编程,这一使代码可读性更高一点。将不同功能的函数实现放在不同的源文件中。
将函数main 、test、menu、play放入源文件test.c中,这里只是初步规划,代码会随着实现功能慢慢增加。
//后续将会逐步添加,不表明只有这些 #include "play.h"//头文件必须添加,printf等函数正常工作 void menu() {//输出提示字符 printf("*********************\n"); printf("*****1. 开始游戏*****\n"); printf("*****0. 退出游戏*****\n"); printf("*********************\n"); } void play() {//玩游戏的具体实现代码 printf("玩家选择玩游戏!\n"); //存放下棋的数据 char board[ROW][COL] = { 0 }; //初始化棋盘为全空格 InitBoard(board, ROW, COL); } void test() {//游戏界面代码 int input = 0; do { menu(); printf("请选择1或者0==> "); scanf("%d", &input); switch (input) { case 1: play(); break; case 0: printf("退出游戏!\n"); break; default: printf("输入错误,请重新输入1或0 \n"); break; } } while (input); } int main() { test(); return 0; }
将函数初始化棋盘、打印棋盘、玩家下棋,等等放入源文件play.c,这里只是初步规划,代码会随着实现功能慢慢增加。
//后续将会逐步添加,不表明只有这些 #include "play.h"//头文件必须添加,包含stdio.h,使printf等函数正常工作 //初始化棋盘 void InitBoard(char board[ROW][COL], int row, int col) { } //打印棋盘 void DisplayBoard(char board[ROW][COL], int row, int col) { } //玩家下棋 void PlayMover(char board[ROW][COL], int row, int col) { } //电脑下棋 void ComputerMove(char board[ROW][COL], int row, int col) { }
将宏定义,函数声明放在这个头文件件play.h,这里只是初步规划,代码会随着实现功能慢慢增加。
//后续将会逐步添加,不表明只有这些 #include<stdio.h>//添加头文件 #define ROW 3//棋盘行数 #define COL 3//棋盘列数 void play(); //初始化棋盘 void InitBoard(char board[ROW][COL], int row, int col); //打印棋盘 void DisplayBoard(char board[ROW][COL], int row, int col); //玩家下棋 void PlayMover(char board[ROW][COL], int row, int col); //电脑下棋 void ComputerMove(char board[ROW][COL], int row, int col);
在玩家下棋前,需要完成两件事情
//test.c void play() { printf("玩家选择玩游戏!\n"); //存放下棋的数据 ROW代表棋盘行数,COL代表棋盘列数 char board[ROW][COL] = { 0 }; //初始化棋盘为全空格 InitBoard(board, ROW, COL); //展示棋盘,打印 DisplayBoard(board, ROW, COL); } //play.c 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] = ' ';//赋值空格,进行格式化 } } } //第一稿 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]);//都是空格看不见 } printf("\n"); } }
运行结果显示,函数InitBoard使用空格完成初始化,但是空格直接打印都是空白,看不见,因此需要修改打印函数DisplayBoard
//第二稿 void DisplayBoard(char board[ROW][COL], int row, int col) { int i = 0; for (i = 0; i < row; i++) { printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]); //打印分隔符 printf("---|---|---\n"); }ruxiat }
打印结果如上图,基本上是一个九宫格棋盘了。但是第三行的字符是多余的,需要再次修改。
//第三稿 void DisplayBoard(char board[ROW][COL], int row, int col) { int i = 0; for (i = 0; i < row; i++) {//但是这里有问题,每行的输出是固定的3个,当行列改变时,这里就不能用了 printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]); //打印分隔符 if(i<ROW-1)//第三行不打印 printf("---|---|---\n"); } }
运行结果如上所示。但是上面的代码也存在问题,每行字符输出是固定的3个,当行列改变时,这里就会出问题。
printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
将棋盘的行数、列数改为10 ,运行结果如下:
因此,这行代码需要修改。
//第四稿 void DisplayBoard(char board[ROW][COL], int row, int col) { int i = 0; for (i = 0; i < row; i++) { for (int j = 0; j < col; j++) {//将这里改为自动化输出 printf(" %c ", board[i][j]); printf("|"); } printf("\n"); //打印分隔符 if (i < row - 1)//第三行不打印 printf("---|---|---\n"); } }
上图为显示结果,第三列有问题,对代码进行修改。
//第五稿 void DisplayBoard(char board[ROW][COL], int row, int col) { int i = 0; for (i = 0; i < row; i++) { for (int j = 0; j < col; j++) { printf(" %c ", board[i][j]); if(j<col-1)第三列不打印 printf("|"); } printf("\n"); //打印分隔符 if (i < row - 1)//第三行不打印 printf("---|---|---\n");//这里也有问题,写成固定的 } }
上面是运行结果,**程序代码也存在问题,同前面一样,输出的字符是固定的,**因此再次修改代码。
printf("---|---|---\n");//这里也有问题,写成固定的
//第六稿,至此改完 void DisplayBoard(char board[ROW][COL], int row, int col) { int i = 0; for (i = 0; i < row; i++) { for (int j = 0; j < col; j++) { printf(" %c ", board[i][j]); if (j < col - 1)第三列不打印 printf("|"); } printf("\n");//每一行打完就换行 //打印分隔符 if (i < row - 1)//第三行不打印 { for (int j = 0; j < col; j++) {//此时可以测试10行10列了 printf("---");//纯粹的符号打印,没有输入的字符 if (j < col - 1)//第三列不打印 printf("|"); } printf("\n");//每一行打完就换行 } } }
运行结果如上图。将棋盘行数、列数改为10,再次运行,结果见下图,由此,打印函数DisplayBoard修改结束,符合使用要求了。
在初始化并打印棋盘后,接着就要邀请玩家下棋了,下完后也要打印出棋盘。在函数 paly 里添加 PlayMover 和 DisplayBoard。
void play() { printf("玩家选择玩游戏!\n"); //存放下棋的数据 char board[ROW][COL] = { 0 }; //初始化棋盘为全空格 InitBoard(board, ROW, COL); //展示棋盘,打印 DisplayBoard(board, ROW, COL); //玩家下棋 PlayMover(board, ROW, COL); //展示棋盘,打印 DisplayBoard(board, ROW, COL); }
玩家下棋函数 PlayMover,根据玩家输入的坐标信息,在对应的位置输入*,代表落下棋子。
void PlayMover(char board[ROW][COL], int row, int col) { int x = 0; int y = 0; printf("游戏已开始,请玩家下棋\n"); while (1) { printf("请输入落棋子位置 ==> "); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) {//玩家的角度,行列都是1开始的 //下棋 if (board[x - 1][y - 1] == ' ')//数组角度,代码要减1 {//落棋位置,就是前面打印棋盘 %c 的位置 board[x - 1][y - 1] = '*';//输入字符,代表落下棋子 break; } else { printf("该位置已有棋子,请重新选择落子位置\n"); } } else { printf("坐标非法,请重新输入\n"); } } }
输入坐标1 1,结果显示:在对应位置输出*,代表落下棋子。由此表明,玩家下棋函数 PlayMover符合预期效果。
玩家下棋后,打印出棋盘,紧接着就轮到电脑下棋了,同样打印出棋盘。在函数 paly 里添加 ComputerMove 和 DisplayBoard。
void play() { printf("玩家选择玩游戏!\n"); //存放下棋的数据 char board[ROW][COL] = { 0 }; //初始化棋盘为全空格 InitBoard(board, ROW, COL); //展示棋盘,打印 DisplayBoard(board, ROW, COL); //玩家下棋 PlayMover(board, ROW, COL); //展示棋盘,打印 DisplayBoard(board, ROW, COL); //电脑下棋 ComputerMove(board, ROW, COL); //展示棋盘,打印 DisplayBoard(board, ROW, COL); }
电脑是自己随机下棋的,电脑的行数、列数先随机产生一个0-2的坐标位置,再判断这个位置是否为空:
下面将在源文件 play.c 中添加函数 ComputerMove 的实现代码,并在头文件 play.h 中添加函数声明。
//头文件 play.h中 void ComputerMove(char board[ROW][COL], int row, int col); //源文件 play.c中 void ComputerMove(char board[ROW][COL], int row, int col) { int x = 0; int y = 0; printf("电脑下棋 ==> \n"); while (1) {//电脑下棋是随机的 x = rand() % row;//输出0-2之间的随机数 0 1 2 y = rand() % col;//输出0-2之间的随机数 0 1 2 if (board[x][y]==' ')//先判断对应坐标是否为空格 {//空格代表没有棋子,则电脑将落下棋子 board[x][y] = '#';//#代表电脑下棋 break;//下完就跳出循环 } } }
由于坐标位置随机产生的,要在 头文件 play.h 中添加两个头文件
#include <stdlib.h>//库函数 #include <time.h>//与系统时间有关的
在源文件 test.c 中的函数test中,添加函数srand,放在do-while循环之前,这样电脑下棋的位置将是真正的随机产生。
srand((unsigned int)time(NULL));
运行结果见上图,由于下棋是双方轮流下棋的,所以玩家下棋和电脑下棋是一个循环的动作。在此在函数play 添加while循环:
void play() { printf("玩家选择玩游戏!\n"); //存放下棋的数据 char board[ROW][COL] = { 0 }; //初始化棋盘为全空格 InitBoard(board, ROW, COL); //展示棋盘,打印 DisplayBoard(board, ROW, COL); while (1) { //玩家下棋 PlayMover(board, ROW, COL); //展示棋盘,打印 DisplayBoard(board, ROW, COL); //电脑下棋 ComputerMove(board, ROW, COL); //展示棋盘,打印 DisplayBoard(board, ROW, COL); } }
运行结果见上图。到此,代码已经实现了正常的下棋功能并显示出来。
三子棋棋盘一共只能下9个棋子,因此经过双方几轮下棋后,势必会分出胜负,结果分为三种:
当然,以上结果是不包括掀了棋盘的。
基于前面的代码基础上,在函数 paly 里添加判断赢家的函数WhoIsWin ,修改后的函数 play逻辑是:
(1)若返回的字符是c,则程序往下运行,轮到电脑下棋
(2)若返回的字符不是c,说明下棋出结果了,break直接跳出while循环,进行剩下的3个if判断
(3)返回 *,代表玩家赢; 返回 #,代表电脑赢;返回 c,代表平局;
void play() { printf("玩家选择玩游戏!\n"); char result = 0;//棋盘最终结果根据result判断 //存放下棋的数据 char board[ROW][COL] = { 0 }; //初始化棋盘为全空格 InitBoard(board, ROW, COL); //展示棋盘,打印 DisplayBoard(board, ROW, COL); while (1) { //玩家下棋 PlayMover(board, ROW, COL); //展示棋盘,打印 DisplayBoard(board, ROW, COL); result = WhoIsWin(board, ROW, COL);//函数判断下棋结果后,返回对应的字符 if (result !='c')//玩家下棋后进行一次判断 {//不继续下棋,说明已经分出胜负了 break; } //电脑下棋 ComputerMove(board, ROW, COL); //展示棋盘,打印 DisplayBoard(board, ROW, COL); result = WhoIsWin(board, ROW, COL);//函数判断下棋结果后,返回对应的字符 if (result != 'c')//电脑下棋后进行一次判断 {//不继续下棋,说明已经分出胜负了 break; } } //根据函数WhoIsWin返回的字符,输出下棋结果 if (result == '*') { printf("玩家赢了\n"); } else if (result == '#') { printf("电脑赢了\n"); } else { printf("平局\n"); } }
在源文件 play.c 添加 WhoIsWin 的代码,并在**头文件 play.h **添加函数声明:
//头文件 play.h中 char WhoIsWin(char board[ROW][COL], int row, int col); int IfFull(char board[ROW][COL], int row, int col);
函数 WhoIsWin 的逻辑:
//源文件 play.c中 //判断棋盘是否满了? 此函数包含在 WhoIsWin 中 int IfFull(char board[ROW][COL], int row, int col) { for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { if (board[i][j] == ' ') { return 0;//存在空格棋盘没有满 } } } } //判断赢家 char WhoIsWin(char board[ROW][COL], int row, int col) { //判断行,每一行连续三个棋子相同 for (int i = 0; i < row; i++) { if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][1] != ' ') {//只要连续三个棋子相同,就直接返回这个棋子,这里不用区分* 还是# return board[i][1]; } } //判断列,每一列连续三个棋子相同 for (int j = 0; j < row; j++) { if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[1][j] != ' ') { return board[1][j]; } } //对角线,连续三个棋子相同 if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ') {//主对角线 return board[1][1]; } if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ') {//副对角线 return board[1][1]; } //判断平局,棋盘满了,不分胜负 if (IfFull(board, row, col) == 1) { return 'q';//quit表示平局 } //没有赢家,棋盘也没满,继续下棋 return 'c';//continue,表示继续 }
运行结果见下图,基本上一个完整的三子棋游戏代码已经实现了。
但是上述代码还有一个小问题,在判断每一行、每一列、对角线上,三个棋子是否相同时,代码直接写固定的3个,如果将棋盘改成5行5列的五子棋时,这里就会出现问题了。这里留到以后在修改成能适应五子棋的游戏。三子棋的代码实现就告一段落了。
完整代码放入gitee中:
本文主要是游戏三子棋的实现,从零开始,介绍了代码实现的思路,应该从简单的代码实现,然后再逐步添加代码功能,及时打印结果进行调试,查看代码是否达到预期要求,最终完善代码。
代码是从简单代码慢慢增加,删减、优化变成复杂的代码,不可能是从上往下的模式去设计代码,这不科学。要熟悉三子棋编写代码的流程,培养好的写代码的习惯。