🎡内容专栏:【C语言】初阶部分
🎡本文概括:学完函数和二维数组之后,立马上手写一个三子棋游戏的代码,提升自己的代码运用能力和知识点梳理。
🎡创作者:心已入冬_
🎡发布时间:2023.1.27
目录
一、代码编写文件的设计
首先,我们用VS2019编译器在编写小游戏之前,需要创建一个源(.h)文件和两个.c文件。
test.c文件:主要用于整体逻辑的测试
game.c文件:用于编写游戏相关的函数实现
game.h文件:用于编写游戏相关的函数声明及其头文件包含、定义标识符
二、游戏逻辑的设计
我们需要先从test.c文件开始编写,游戏开始前,需要有一个菜单页面,我们编写到menu()函数之中。
我们希望玩一把不过瘾,想接着继续玩,考虑使用do……while循环结构,选择“1”,我们Play游戏(先写个game()函数);当不想玩了,我们选择“0”,,退出游戏,跳出循环;输入其他值,显示“输入有误,请重新选择。”让你重新选择。所以考虑循环里面嵌套一个switch语句。
test.c文件
#define _CRT_SECURE_NO_WARNINGS //用于实现游戏的整体逻辑 #include"game.h" void menu() { printf("***************************\n"); printf("****** -- 三子棋 -- *******\n"); printf("*********1.Play************\n"); printf("*********0.Exit************\n"); printf("***************************\n"); } void game() { //code2 } int main() { int input = 0; 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; }
以上代码我们实现了简单的游戏菜单栏选择代码,接下来我们还是从test.c文件开始,在game()函数里面正式思考编写三子棋游戏逻辑的代码。
1.存储棋盘数据及初始化棋盘
首先我们需要编写一个二维数组来存储数据,用“*”表示玩家下的棋子,用“#”表示电脑下的棋子,每个棋子占用一个字符,所以我们使数组的每个元素用char类型存储。
1.我们使用ROW、COL标识符常量定义行和列,方便修改以后的棋盘大小。
2.初始化棋盘,即二维数组遍历的每个元素初始为空格,(初始为其他元素,会导致排列紊乱);
test.c文件
void game() { //存储数据用一个3*3的二维数组存储 char board[ROW][COL] = { 0 }; // 1. 初始化棋盘 InitBoard(board, ROW, COL); }
game.h文件
#define _CRT_SECURE_NO_WARNINGS //用于函数的声明及其头文件的包含 #include<stdio.h> #define ROW 3 #define COL 3 //初始化棋盘 void InitBoard(char board[ROW][COL], int row, int col);
game.c文件
#define _CRT_SECURE_NO_WARNINGS //用于函数的具体实现 #include"game.h" 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] = ' '; } } }
2.打印棋盘
紧接着,我们需要打印棋盘,分为数据行和分隔行,写一个DisplayBoard()函数打印棋盘。
game.h文件
#define _CRT_SECURE_NO_WARNINGS //用于函数的声明及其头文件的包含 #include<stdio.h> #define ROW 3 #define COL 3 //初始化棋盘 void InitBoard(char board[ROW][COL], int row, int col); //展示棋盘 void DisplayBoard(char board[ROW][COL], int row, int col);
game.c文件
#define _CRT_SECURE_NO_WARNINGS //用于函数的具体实现 #include"game.h" 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]); 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"); } } }
3.双方对局
双方对局过程无非是玩家下棋、电脑下棋、玩家下棋、电脑下棋……双方互相博弈、来回作用的过程其实也就是一个循环!
没有循环条件,我们用while(1)表示循环一直进行,直到游戏有人胜利,或者平局,则跳出循环,结束本次游戏。
编写一个PlayerMove()函数表示玩家下棋的逻辑
我们用输入坐标的形式来呈现玩家下的棋子,(x ,y),x表示行,y表示列
输入1 2 则表示落子在第一行第二列。
test.c文件
#define _CRT_SECURE_NO_WARNINGS //用于实现游戏的整体逻辑 #include"game.h" void menu() { printf("***************************\n"); printf("****** -- 三子棋 -- *******\n"); printf("*********1.Play************\n"); printf("*********0.Exit************\n"); printf("***************************\n"); } void game() { //存储数据用一个3*3的二维数组存储 char board[ROW][COL] = { 0 }; // 1. 初始化棋盘 InitBoard(board, ROW, COL); // 2.展示棋盘 DisplayBoard(board, ROW, COL); // 3.对局 while (1) { //玩家下棋 PlayerMove(board, ROW, COL); DisplayBoard(board, ROW, COL); //双方下棋是内部过程,因此之后需要展示棋盘。 } } int main() { int input = 0; 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; }
game.h文件
#define _CRT_SECURE_NO_WARNINGS //用于函数的声明及其头文件的包含 #include<stdio.h> #define ROW 3 #define COL 3 //初始化棋盘 void 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);
game.c文件
#define _CRT_SECURE_NO_WARNINGS //用于函数的具体实现 #include"game.h" 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]); 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 PlayerMove(char board[ROW][COL], int row, int col) { int x = 0; int y = 0; while (1) { printf("玩家下棋\n"); printf("请输入对应的坐标:>"); scanf("%d %d", &x, &y); //坐标范围要合法 if (x >= 1 && x <= row && y >= 1 && y <= col) { //下标从0开始,故坐标需要-1得到下标, if (board[x - 1][y - 1] == ' ') { board[x - 1][y - 1] = '*'; break; } else { //如果坐标不是' ',则表示被占用 printf("坐标被占用,请重新输入\n"); } } else { printf("坐标非法,请重新输入\n"); } } }
接下来,我们编写ComputerMove()是电脑下棋的逻辑, 与玩家下棋的逻辑相比更简单,电脑下棋是随机的,并不会输入坐标,只需引用库函数rand()和srand()随机数生成器、时间戳,引用头文件<stdlib.h>和<time.h>,来生成坐标,改变元素。
game.h文件
#define _CRT_SECURE_NO_WARNINGS //用于函数的声明及其头文件的包含 #include<stdio.h> #include<stdlib.h> #include<time.h> #define ROW 3 #define COL 3 //初始化棋盘 void 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);
game.c文件
#define _CRT_SECURE_NO_WARNINGS //用于函数的具体实现 #include"game.h" 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]); 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 PlayerMove(char board[ROW][COL], int row, int col) { int x = 0; int y = 0; while (1) { printf("玩家下棋\n"); printf("请输入对应的坐标:>"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { //下标从0开始,故坐标需要-1, if (board[x - 1][y - 1] == ' ') { board[x - 1][y - 1] = '*'; break; } else { printf("坐标被占用,请重新输入\n"); } } else { printf("坐标非法,请重新输入\n"); } } } void ComputerMove(char board[ROW][COL], int row, int col) { int x = 0; int y = 0; printf("电脑下棋\n"); while (1) { x = rand() % row; y = rand() % col; if (board[x][y] == ' ') { board[x][y] = '#'; break; } } }
test.c文件
#define _CRT_SECURE_NO_WARNINGS //用于实现游戏的整体逻辑 #include"game.h" void menu() { printf("***************************\n"); printf("****** -- 三子棋 -- *******\n"); printf("*********1.Play************\n"); printf("*********0.Exit************\n"); printf("***************************\n"); } void game() { //存储数据用一个3*3的二维数组存储 char board[ROW][COL] = { 0 }; // 1. 初始化棋盘 InitBoard(board, ROW, COL); // 2.展示棋盘 DisplayBoard(board, ROW, COL); // 3.对局 while (1) { //玩家下棋 PlayerMove(board, ROW, COL); // DisplayBoard(board, ROW, COL); //电脑下棋 ComputerMove(board, ROW, COL); // DisplayBoard(board, ROW, COL); } } 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; }
再接着,我们需要编写IsWin()函数来判断双方对局的输赢情况, 情况无非是玩家赢、电脑赢、游戏平局(棋盘满了)、游戏继续这四种情况。
那么IsWin函数就设计到返回值了,返回值为char类型。
玩家赢——>返回' * '
电脑赢——>返回' # '
游戏平局——>返回' Q ' (quit)
游戏继续——>返回' C ' (continue)
game.h文件
#define _CRT_SECURE_NO_WARNINGS //用于函数的声明及其头文件的包含 #include<stdio.h> #include<stdlib.h> #include<time.h> #define ROW 3 #define COL 3 //初始化棋盘 void 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);
game.c文件
#define _CRT_SECURE_NO_WARNINGS //用于函数的具体实现 #include"game.h" 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]); 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 PlayerMove(char board[ROW][COL], int row, int col) { int x = 0; int y = 0; while (1) { printf("玩家下棋\n"); printf("请输入对应的坐标:>"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { //下标从0开始,故坐标需要-1, if (board[x - 1][y - 1] == ' ') { board[x - 1][y - 1] = '*'; break; } else { printf("坐标被占用,请重新输入\n"); } } else { printf("坐标非法,请重新输入\n"); } } } void ComputerMove(char board[ROW][COL], int row, int col) { int x = 0; int y = 0; printf("电脑下棋\n"); while (1) { x = rand() % row; y = rand() % col; if (board[x][y] == ' ') { board[x][y] = '#'; break; } } } 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][2] != ' ') { return board[i][0]; } } //判断列 for (i = 0; i < col; i++) { if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[2][i] != ' ') { return board[0][i]; } } //判断对角线 if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[2][2] != ' ') { return board[0][0]; } if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != ' ') { return board[0][2]; } //判断平局(即棋盘是否满了? 满了IsFull函数返回1) if (IsFull(board ,row, col) == 1) { return 'Q'; } return 'C'; //除了以上情况,游戏继续 }
为了判断平局,我们再次写一个IsFull()函数,遍历整个数组,如果棋盘还有落子的地方,说明棋盘没有满,游戏继续,棋盘满了,IsFull函数返回1,return 'Q'
game.h文件
#define _CRT_SECURE_NO_WARNINGS //用于函数的声明及其头文件的包含 #include<stdio.h> #include<stdlib.h> #include<time.h> #define ROW 3 #define COL 3 //初始化棋盘 void 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); //判断平局 int IsFull(char board[ROW][COL], int row, int col);
game.c文件
#define _CRT_SECURE_NO_WARNINGS //用于函数的具体实现 #include"game.h" 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]); 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 PlayerMove(char board[ROW][COL], int row, int col) { int x = 0; int y = 0; while (1) { printf("玩家下棋\n"); printf("请输入对应的坐标:>"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { //下标从0开始,故坐标需要-1, if (board[x - 1][y - 1] == ' ') { board[x - 1][y - 1] = '*'; break; } else { printf("坐标被占用,请重新输入\n"); } } else { printf("坐标非法,请重新输入\n"); } } } void ComputerMove(char board[ROW][COL], int row, int col) { int x = 0; int y = 0; printf("电脑下棋\n"); while (1) { x = rand() % row; y = rand() % col; if (board[x][y] == ' ') { board[x][y] = '#'; break; } } } 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][2] != ' ') { return board[i][0]; } } //判断列 for (i = 0; i < col; i++) { if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[2][i] != ' ') { return board[0][i]; } } //判断对角线 if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[2][2] != ' ') { return board[0][0]; } if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[0][2] != ' ') { return board[0][2]; } //判断平局 if (IsFull(board ,row, col) == 1) { return 'Q'; } return 'C'; //除了以上情况,游戏继续 } int IsFull(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++) { if (board[i][j] == ' ') { return 0; //棋盘未满 } } } return 1;//棋盘满了 }
玩家落子,调用一次IsWin函数,判断输赢; 电脑落子,调用一次IsWin函数,判断输赢。
我们在while循环外创建一个变量ret ,将IsWin的返回值放到ret中。 最后,写一个if()语句,表示游戏结束(!= 'C' )的话,跳出循环,判断是玩家赢、电脑赢还是平局。
test.c文件
#define _CRT_SECURE_NO_WARNINGS //用于实现游戏的整体逻辑 #include"game.h" void menu() { printf("***************************\n"); printf("****** -- 三子棋 -- *******\n"); printf("*********1.Play************\n"); printf("*********0.Exit************\n"); printf("***************************\n"); } void game() { //存储数据用一个3*3的二维数组存储 char board[ROW][COL] = { 0 }; // 1. 初始化棋盘 InitBoard(board, ROW, COL); // 2.展示棋盘 DisplayBoard(board, ROW, COL); // 3.对局 char 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"); } else if (ret == '#') { printf("很遗憾,你输了!\n"); } else if(ret == 'Q') printf("游戏平局!\n"); } 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; }
☃️最后,将代码运行起来,三子棋小游戏就可以试玩了!
☃️不过,此文章还存在一定的缺陷,如:1.如果想让棋盘变得更大, 写成N*N的棋盘,代码段需要作一定的修改;
☃️2.电脑下棋并不能灵活地按游戏规则下棋(玩家自己很容易就赢了)。