【维生素C语言】第四章 - 数组

160 阅读14分钟

​​「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。

前言

本章将对C语言的数组进行讲解,从一维数组开始讲起。已经学了三个章节了,所以本章还附加了三子棋和扫雷两个简单的小游戏,读者可以试着写一写,增加编程兴趣,提高模块化编程思想。

一、一维数组

0x00 何为数组

📚 数组,即为一组相同类型的元素的集合;

0x01 一维数组的创建

📚 数组的创建

① type_t:数组的元素类型;

② arr_name:数组名;

③ const_n:常量表达式,用于指定数组大小;

📌 注意事项

① 数组创建,[ ] 中要给定常量,不能使用变量;

② 数组 [ ] 中的内容如果不指定大小(不填),则需要初始化;

💬 一维数组创建方法演示

💬 const_n中要给定一个常量,不能使用变量

int main()
{
    int count = 10;
    int arr[count]; // error
    
    return 0;
}

#define N 10

int main()
{
    int arr2[N]; // yes
    
    return 0;
}

0x02 一维数组的初始化

📚 初始化:在创建数组的同时给数组的内容置一些合理的初始值;

💬 初始化演示

int main()
{
    int arr1[10];  // 创建一个大小为10的int类型数组
    char arr2[20]; // 创建一个大小为20的char类型数组
    float arr3[1]; // 创建一个大小为1的float类型数组
    double arr4[] = {0}; // 创建一个不指定大小的double类型数组(需要初始化)

    return 0;
}

💬 字符数组初始化

int main()
{
    char ch1[5] = {'b', 'i', 't'};
    char ch2[] = {'b', 'i', 't'};

    char ch3[5] = "bit";  // 'b', 'i', 't', '\0', '0'
    char ch4[] = "bit";  // 'b', 'i', ''t, '\0'

    return 0;
}

💬 字符数组初始化的两种写法

双引号写法自带斜杠0,花括号写法不自带斜杠0(需要手动添加)

int main()
{
    char ch5[] = "bit"; // b, i, t, \0      【自带斜杠0】
    char ch6[] = {'b', 'i', 't'}; // b i t  【不自带斜杠0】
    printf("%s\n", ch5);
    printf("%s\n", ch6);

    return 0;
}

没有 \0 时,strlen读取时并不会知道什么时候结束,strlen:遇到斜杠0就停止

int main()
{
    char ch5[] = "bit"; // b, i, t, \0      【自带斜杠0】
    char ch6[] = {'b', 'i', 't'}; // b i t  【不自带斜杠0】
    printf("%d\n", strlen(ch5));
    printf("%d\n", strlen(ch6));

    return 0;
}

🚩 >>> 3

随机值

💡 当然,你可以给他手动加上一个斜杠0,这样就不会是随机值了;

int main()
{
    char ch5[] = "bit"; // b, i, t, \0      【自带斜杠0】
    char ch6[] = {'b', 'i', 't', '\0'}; // b, i, t, + '\0' 【手动加上斜杠0】
    printf("%d\n", strlen(ch5));
    printf("%d\n", strlen(ch6));

    return 0;
}

🚩 >>> 3 3

0x03 一维数组的使用

📚 下标引用操作符: [ ] ,即数组访问操作符;

📚 数组的大小计算方法:整个数组的大小除以一个字母的大小

💬 打印一维数组

可以利用 for 循环,逐一打印数组

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int sz = sizeof(arr) / sizeof(arr[0]);

    int i = 0;
    for(i = 0; i < sz; i++)
        printf("%d ", arr[i]);
    
    return 0;
}

🚩 >>> 1 2 3 4 5 6 7 8 9 10

🔺 总结:

① 数组是使用下标来访问的,下标从0开始;

② 可以通过计算得到数组的大小;

0x04 一维数组在内存中的存储

📚 按地址的格式打印:%p (十六进制的打印)

💬 一维数组的存储方式

int main()
{
    int arr[10] = {0};
    
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);
    for(i = 0; i < sz; i++)
        printf("&arr[%d] = %p\n", i, &arr[i]);

    return 0;
}

🚩 运行结果如下:

💡 仔细检视输出结果可知:随着数组下标的增长,元素的地址也在有规律的递增;

🔺 结论:数组在内存中时连续存放的;

二、二维数组

0x00 二维数组的创建

📚 二维数组 [行] [列]

① const_n1:行

② const_n2: 列

💬 二维数组的创建

int main()
{
    int arr[3][4];     // 创建一个3行4列的int型二维数组;

    /*
        0 0 0 0
        0 0 0 0
        0 0 0 0
    */

    char arr[3][5];    // 创建一个3行5列的char型二维数组;
    double arr[2][4];  // 创建一个2行4列的double型二维数组;

    return 0;
}

0x01 二维数组的初始化

📚 初始化:在创建数组的同时给数组的内容置一些合理的初始值;

📌 注意事项:

① 二维数组初始化时,行可以省略,但是列不可以省略;

② 二维数组在内存中也是连续存放的;

💬 初始化演示

int main()
{
    int arr[3][4] = {1,2,3,4,5};
    
    /*
        1 2 3 4
        5 0 0 0
        0 0 0 0
    */

    int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; // 完全初始化
    int arr2[3][4] = {1,2,3,4,5,6,7}; // 不完全初始化 - 后面补0;

    int arr3[3][4] = {{1,2}, {3,4}, {4,5}}; // 指定;

    /*
        1 2 0 0
        3 4 0 0
        4 5 0 0
    */

    return 0;
}

💬 关于 " 行可以省略,列不可以省略 "

int main()
{
    int arr1[][] = {{2,3}, {4,5}};  // error
    int arr2[3][] = {{2,3}, {4,5}}; // error
    int arr2[][4] = {{2,3}, {4,5}}; // √

    return 0;
}

0x03 二维数组的使用

💬 打印二维数组

同样是通过下标的方式,利用两个 for 循环打印

int main()
{
    int i = 0;
    int j = 0;
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 4; j++)
            printf("%d", arr4[i][j]); // 二维数组[行][列];
        printf("\n"); // 换行;
    }
}

💬 二维数组在内存中的存储

int main()
{
    int arr[3][4];
    int i = 0;
    int j = 0;
    for(i = 0; i < 3; i++) {
        for(j = 0; j < 4; j++)
            printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);
    }

    return 0;
}

🚩 运行结果如下:

💡 仔细检视输出结果,我们可以分析到其实二维数组在内存中也是连续存存放的;

🔺 结论:二维数组在内存中也是连续存放的;

三、数组作为函数参数

0x00 关于数组名

📚 数组名是首元素的地址(有两个例外)

⭕ 例外1:

sizeof(数组名) 计算的是整个数组的大小

💬 验证

int main()
{
    int arr[10] = {0};
    printf("%d\n", sizeof(arr));

    return 0;
}

🚩 >>> 40

⭕ 例外2:

& 数组名 表示整个数组,取出的是整个数组的地址

0x01 冒泡排序(Bubble Sort)

📚 冒泡排序核心思想:两两相邻元素进行比较,满足条件则交换;

① 先确认趟数;

② 写下一趟冒泡排序的过程;

③ 最后进行交换;

📌 注意事项:

① int arr [ ] 本质上是指针,int * arr ;

② 数组传参时,实际上传递的是数组的首元素地址;

③ sz 变量不能在 bubble_sort内部计算,需要在外部计算好再传递进去;

💬 冒泡排序:请编写一个bubble_sort ( ) 函数,升序,int arr[] = {9,8,7,6,5,4,3,2,1,0} ;

#include <stdio.h>

void bubble_sort (int arr[], int sz) // 形参arr本质上是指针 int* arr
{
    /* 确认趟数 */
    int i = 0;
    for(i = 0; i < sz; i++)
    {
        /* 一趟冒泡排序干的活 */
        int j = 0;
        for(j = 0; j <= (sz-1-i); j++) // -1:最后一趟不用排,-i:减去已经走过的趟
        {
            /* 如果前面数比后面数大,就交换 */
            if(arr[j] > arr[j + 1])
            {
                /* 创建临时变量交换法 */
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
            }
        }
    }
}

int main(void)
{
    int arr[] = {9,8,7,6,5,4,3,2,1,0};
    int sz = sizeof(arr) / sizeof(arr[0]);

    /* 冒泡排序 */
    bubble_sort(arr, sz); // 数组传参的时候,传递的是首元素的地址
    
    /* 打印数组 */
    int i = 0;
    for(i=0; i<=sz; i++)
        printf("%d ", arr[i]);

    return (0);
}

🚩 >>> 0 1 2 3 4 5 6 7 8 9 10

⚡ 算法优化:我们可以置一个变量来判断数组是否有序,如果已经有序,就不需要再冒泡排序了;

#include <stdio.h>

void bubble_sort (int arr[], int sz)
{
    int i = 0;
    for(i = 0; i < sz; i++)
    {
        int j = 0;
        int falg = 1; // 标记1,假设这一趟冒泡排序已经有序
        for(j = 0; j <= (sz-1-i); j++)
        {
            if(arr[j] > arr[j + 1])
            {
                int tmp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = tmp;
                flag = 0; // 仍然不有序,标记为0
            }
        }
        if(flag == 1)
            break; // 已经有序了,就不需要再冒泡排序了
    }
}

int main(void)
{
    int arr[] = {9,8,7,6,5,4,3,2,1,0};
    int sz = sizeof(arr) / sizeof(arr[0]);

    /* 冒泡排序 */
    bubble_sort(arr, sz);
    
    /* 打印数组 */
    int i = 0;
    for(i=0; i<=sz; i++)
        printf("%d ", arr[i]);

    return (0);
}

四、实现三子棋(Tic-Tac-Toe)

0x00 游戏介绍

三子棋是一种民间传统游戏,又叫九宫棋、圈圈叉叉、一条龙、井字棋等。将正方形对角线连起来,相对两边依次摆上三个双方棋子,只要将自己的三个棋子走成一条线,对方就算输了。但是,有很多时候会出现和棋的情况。

0x01 实现思路

📚 分模块:当代码量较大,功能较多时,我们可以拆分代码,分模块来实现各个功能;

① test.c 测试游戏的逻辑;

② game.c 游戏相关函数的实现;

② game.h 关于游戏相关的函数声明、符号声明以及头文件的包含;

0x02 游戏界面

💬 test.c

该代码实现游戏界面部分

#define _CRT_SECURE_NO_WARNINGS
#include "game.h"

void load_game_menu()
{
    printf("\n");
    printf("*********************************\n");
    printf("********** 1. 开始游戏 ***********\n");
    printf("********** 0. 退出游戏 ***********\n");
    printf("*********************************\n");
}

int main(int argc, char const* argv[])
{
    int input = 0;
    do
    {
        /* 载入游戏菜单 */
        load_game_menu();
        printf("请选择: ");
        scanf("%d", &input);
        switch (input)
        {
            case 1:
                printf("\ntest:游戏开始\n");
                // game();
                break;
            case 0:
                printf("\n退出游戏\n");
                break;
            default:
                printf("\n输入错误,请重新输入!\n");
                break;
        }
    } while (input);

    return( 0 );
}

🚩 该部分运行结果如下(完成一部分功能就运行一下看看,及时发现BUG,越早发现越容易找到BUG)

0x03 创建棋盘&初始化棋盘

写game() 函数,创建棋盘,然后将初始化棋盘函数

💬 test.c

void game()
{
    /* 创建棋盘 */
    char board[ROW][COL];
    /* 初始化棋盘 - 初始化空格 */
    init_board(board, ROW, COL);
}

💬 game.h

#include <stdio.h>

/* 宏定义 */
#define ROW 3
#define COL 3

/* 函数声明 */
void init_board(char board[ROW][COL], int row, int col);
void print_board(char board[ROW][COL], int row, int col);

💬 game.c

#include "game.h"

void init_board(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] = ' '; // 初始化为空格
		}
	}
}

0x04 打印棋盘

print_board()函数,本质上是打印数组的内容

利用循环画一个棋盘

💬 test.c

void game()
{
	//存储数据 - 二维数组
	char board[ROW][COL];
	//初始化棋盘 - 初始化空格
	init_board(board, ROW, COL);

	//打印一下棋盘 - 本质是打印数组的内容
	print_board(board, ROW, COL);
}

💬 game.h

#include <stdio.h>

/* 宏定义 */
#define ROW 3
#define COL 3

/* 函数声明 */
void init_board(char board[ROW][COL], int row, int col);
void print_board(char board[ROW][COL], int row, int col);

💬 game.c

void print_board(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]);
		if (i < row - 1)
			printf("---|---|---\n");
	}
}

🚩 运行结果:

❓如果修改了棋盘大小,怎么办?

💡 代码优化

void print_board(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)
		{
			int j = 0;
			for (j = 0; j < col; j++)
			{
				printf("---");
				if (j < col - 1)
					printf("|");
			}
		printf("\n");
		}
	}
}

🚩 我们将 ROW 和 COL 修改为5试试看能否生成一个5x5的棋盘;

0x05 设计玩家回合

💬 test.c game ( )

void game()
{
	//存储数据 - 二维数组
	char board[ROW][COL];
	//初始化棋盘 - 初始化空格
	init_board(board, ROW, COL);

	//打印一下棋盘 - 本质是打印数组的内容
	print_board(board, ROW, COL);

	//玩家下棋
	player_round(board, ROW, COL);
}

💬 game.h player_round ( )

void player_round(char board[ROW][COL], int row, int col);

💬 game.c player_round ( )

void player_round(char board[ROW][COL], int row, int col)
{
	/* 创建坐标 */
	int x = 0;
	int y = 0;
	/* 要求玩家落子 */
	printf("\n[玩家回合]\n");

	while (1)
	{
		printf("请落子: ");
		scanf("%d %d", &x, &y);
		/* 判断坐标的合法性 */
		if (x >= 1 && x <= row && y >= 1 && y <= col)
		{
			/* 判断坐标是否被占用 */
			if (board[x - 1][y - 1] == ' ') // 玩家输的坐标 -1
			{
				/* 下棋 */
				board[x - 1][y - 1] = '*';
				break;
			}
			else
				printf("[提示] 该处已经有棋子,请重新输入!\n");
		}
		else
			printf("[提示] 坐标非法,请重新输入!\n");
	}
}

0x06 设计电脑回合

💬 test.c main ( ) 中存放随机数种子,game ( )

int main(int argc, char const* argv[])
{
    srand((unsigned int)time(NULL)); // 置随机数种子
    ...
}
void game()
{
    /* 创建棋盘 */
    char board[ROW][COL];
    /* 初始化棋盘 - 初始化空格 */
    init_board(board, ROW, COL);
    /* 打印棋盘 */
    print_board(board, ROW, COL);
    /* 玩家下棋 */
    player_round(board, ROW, COL);
    /* 电脑下棋 */
    computer_round(board, ROW, COL);
}

💬 game.h 引入随机数必要的头文件,computer_round ( )

#include <time.h>
#include <stdlib.h>
void computer_round(char board[ROW][COL], int row, int col);

💬 game.c computer_round ( )

void computer_round(char board[ROW][COL], int row, int col)
{
	printf("[电脑回合]");

	while (1)
	{
		/* 随机坐标 */
		int x = rand() % row;
		int y = rand() % col;
		/* 判断坐标坐标是否被占用 */
		if (board[x][y] == ' ')
		{
			board[x][y] = '#';
			break;
		}
	}
}

0x07 游戏状态设计(整合玩家回合和电脑回合)

💬 test.c game ( ) 玩家和电脑走完走打印一下棋盘,更新最新的棋盘数据

void game()
{
	//存储数据 - 二维数组
	char board[ROW][COL];
	//初始化棋盘 - 初始化空格
	init_board(board, ROW, COL);

	//打印一下棋盘 - 本质是打印数组的内容
	print_board(board, ROW, COL);

	while (1)
	{
		//玩家下棋
		player_round(board, ROW, COL);
		print_board(board, ROW, COL);

		//电脑下棋
		computer_round(board, ROW, COL);
		print_board(board, ROW, COL);

	}

0x08 判断输赢&宣布胜利条件

💬 test.c game ( )

void game()
{
	//存储数据 - 二维数组
	char board[ROW][COL];
	//初始化棋盘 - 初始化空格
	init_board(board, ROW, COL);

	//打印一下棋盘 - 本质是打印数组的内容
	print_board(board, ROW, COL);
	char ret = 0;//接受游戏状态
	while (1)
	{
		//玩家下棋
		player_round(board, ROW, COL);
		print_board(board, ROW, COL);
		//判断玩家是否赢得游戏
		ret = is_win(board, ROW, COL);
		if (ret != 'C')
			break;
		//电脑下棋
		computer_round(board, ROW, COL);
		print_board(board, ROW, COL);
		//判断电脑是否赢得游戏
		ret = is_win(board, ROW, COL);
		if (ret != 'C')
			break;
	}
	if (ret == '*')
	{
		printf("玩家赢了\n");
	}
	else if (ret == '#')
	{
		printf("电脑赢了\n");
	}
	else
	{
		printf("平局\n");
	}
	print_board(board, ROW, COL);
}

💬 game.h is_win ( )

char is_win(char board[ROW][COL], int row, int col);

💬 game.c is_win ( )

char is_win(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][1] != ' ')
		{
			return  board[i][1];//
		}
	}

	/* 判断三列 */
	for (i = 0; i < col; i++)
	{
		if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[1][i] != ' ')
		{
			return board[1][i];
		}
	}

	/* 判断对角线 */
	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];
	}

	/* 判断平局 */
	//如果棋盘满了返回1, 不满返回0
	int ret = is_full(board, row, col);
	if (ret == 1)
	{
		return 'Q';
	}

	/* 继续 */
	return 'C';
}

💬 game.c is_full

int is_full(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; // 棋盘满了
}

0x09 代码运行

🚩 玩家获胜

🚩 电脑获胜

🚩 平局

五、扫雷

0x00 游戏介绍

扫雷

是一款大众类的益智小游戏,于1992年发行。

游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。

0x01 实现思路

📚 分模块:

① test.c 测试游戏的逻辑;

② game.c 游戏相关函数的实现;

② game.h 关于游戏相关的函数声明、符号声明以及头文件的包含;

0x02 游戏界面

📚 思路:

① 设计开始页面,提供选择以下选择:开始游戏、退出游戏(并且检查是否输入错误);

② 为了实现玩一把还能继续玩,使用do...while函数,用户不输入0程序就一直运行;

③ 引入头文件 game.h,将头文件、函数声明、宏定义等全部置于game.h中;

💬 test.c main函数、LoadGameMenu函数

#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"

void LoadGameMenu()
{
    printf("\n");
    printf("*********************************\n");
    printf("********** 1. 开始游戏 ***********\n");
    printf("********** 0. 退出游戏 ***********\n");
    printf("*********************************\n");
}

int main()
{
    int input = 0;
    
    do {
        LoadGameMenu();
        printf("请选择: ");
        scanf("%d", &input);
        switch (input) {
            case 1:
                printf("开始游戏\n");
                break;
            case 0:
                printf("退出游戏\n");
                break;
            default:
                printf("选择错误,请重新选择\n");
                break;
        }
    } while (input);

    return (0);
}

💬 game.h

#include <stdio.h>

🚩 代码运行结果如下

0x03 初始化9x9的棋盘

📚 思路:

① 设计Game函数,用来调用实现游戏功能的函数;

② 创建两个二维数组,分别存放布置好的雷的信息和已排查的雷的信息;

③ 将他们初始化,布置雷的信息用0表示(暂且设定0为非雷,1为雷),已排查的雷的信息用 * 表示;

④ 由于需要一个9x9的扫雷棋盘,外面还需要显示对应的坐标,所以实际数组的大小应该为11x11;

⑤ 定义ROW和COL,为了后续可以修改棋盘,ROWS = ROW+2,COLS = COL+2;

💬 test.c ( main函数、Game函数 )

void Game()
{
    char mine[ROWS][COLS] = { 0 }; // 存放布置好雷的信息
    char show[ROWS][COLS] = { 0 }; // 存放排查好雷的信息
    /* 初始化棋盘 */
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(show, ROWS, COLS, '*');
}

int main()
{
    ...
    
            case 1:
                Game(); // 扫雷游戏
                break;
    ...

    return (0);
}

💬 game.h

#include <stdio.h>

#define ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2

/* 初始化棋盘 */
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);

💬 test.c ( InitBoard 函数 )

void InitBoard(
	char board[ROWS][COLS],
	int rows,
	int cols,
	char set
	)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++) {
		for (j = 0; j < cols; j++) {
			board[i][j] = set;
		}	
	}
}

0x04 打印棋盘

📚 思路:

① 设计一个打印棋盘的函数,把棋盘打印出来;

② 虽然mine棋盘是不能被玩家看到的,但是为了测试我们把mine棋盘也打印出来;

③ 由于棋盘为9x9,不能出9x9之外,所以传入的应该是ROW和COL,而不是ROWS和COLS;

💬 test.c ( Game 函数 )

void Game()
{
    char mine[ROWS][COLS] = { 0 }; // 存放布置好雷的信息
    char show[ROWS][COLS] = { 0 }; // 存放排查好雷的信息
    /* 初始化棋盘 */
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(show, ROWS, COLS, '*');
    /* 打印棋盘 */
    DisplayBoard(mine, ROW, COL);
    DisplayBoard(show, ROW, COL);
}

int main() {...}

💬 game.h

...
/* 打印棋盘 */
void DisplayBoard(char board[ROWS][COLS], int row, int col);

💬 game.c ( DisplayBoard 函数 )

void DisplayBoard (
	char board[ROWS][COLS],
	int row,
	int col
	)
{
	int i = 0;
	int j = 0;
	printf("\n---------------------\n"); // 分界线
	/* 打印列号 */
	for (i = 0; i <= col; i++) {
		if (i == 0) {
			printf("  "); // 去除左上角xy的交接零点部分
			continue;
		}
		printf("%d ", i);
		if (i == 9)
		{
			printf("┆"); // 打印竖边框
		}
	}
	printf("\n");
	for (i = 1; i <= row; i++) {
		/* 打印行号 */
		printf("%d ", i);
		for (j = 1; j <= col; j++) {
			/* 打印内容 */
			printf("%c ", board[i][j]);
			if (j == 9) {
				printf("┆");  // 打印竖边框
			}
		} 
		printf("\n"); // 打印完一行的内容后换行
	}
	printf("---------------------\n"); // 分界线
}

🚩 运行结果如下

0x05 布置雷

📚 思路:

① 随机生成若干个雷(暂且定为10个),可以通过 rand 函数实现;( srand放在主函数中 );

② 由于布雷要在9x9棋盘内部布置,不能出9x9之外,所以传入的应该是ROW和COL;

③ 为了测试雷是否布置成功,我们把mine棋盘先打印出来;

④ define 雷的个数,为了测试,布置10个雷;

💬 test.c ( Game 函数 )

void Game()
{
    char mine[ROWS][COLS] = { 0 }; // 存放布置好雷的信息
    char show[ROWS][COLS] = { 0 }; // 存放排查好雷的信息
    /* 初始化棋盘 */
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(show, ROWS, COLS, '*');
    /* 打印棋盘 */
    // DisplayBoard(mine, ROW, COL);
    DisplayBoard(show, ROW, COL);
    /* 布置雷 */
    SetMine(mine, ROW, COL);
    DisplayBoard(mine, ROW, COL); // 暂时打印出来
}

int main()
{
    srand((unsigned int)time(NULL)); // 置随机数种子
    ...
}

💬 game.h

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define EASY_COUNT 10

...

/* 设置雷 */
void SetMine(char mine[ROWS][COLS], int row, int col);

💬 game.c ( SetMine 函数 )

void SetMine (
	char mine[ROWS][COLS],
	int row, 
	int col
	)
{
	/* 布置10个雷 */
	int count = EASY_COUNT;
	while (count) {
		/* 生成随机的下标 */
		int x = rand() % row + 1; // 余上row变成个位数
		int y = rand() % col + 1; // 余上col变成个位数
		if (mine[x][y] == '0') {  // 判断某个坐标是否已经有雷
			mine[x][y] = '1'; // 设1为雷
			count--;
		}
	}
}

🚩 运行结果

0x06 排查雷

📚 思路:

① 让玩家排查雷,输入雷的坐标进行排查,并且判断玩家输入的坐标是否合法;

② 如果输入的坐标上有雷(为1)则宣告游戏失败,打印出棋盘让玩家死个明白;

③ 如果输入的坐标上没有雷,那么统计周围有几个雷,并且将雷的个数显示在该坐标上,显示排查出雷的信息;

④ 统计周围雷的方法如下图所示,以xy为中心的上下左右、上左上右、下左下右的坐标进行count;

⑤ 这里要传入 ROWS 和 COLS ,就算xy在边上,计算xy周围时,也不会导致数组越界;

💬 test.c ( Game 函数 )

void Game()
{
    char mine[ROWS][COLS] = { 0 }; // 存放布置好雷的信息
    char show[ROWS][COLS] = { 0 }; // 存放排查好雷的信息
    /* 初始化棋盘 */
    InitBoard(mine, ROWS, COLS, '0');
    InitBoard(show, ROWS, COLS, '*');
    /* 打印棋盘 */
    //DisplayBoard(mine, ROW, COL);
    DisplayBoard(show, ROW, COL);
    /* 布置雷 */
    SetMine(mine, ROW, COL);
    //DisplayBoard(mine, ROW, COL);
    /* 排查雷 */
    FindMine(mine, show, ROW, COL);
}

int main() {...}

💬 game.h

...
/* 排查雷 */
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row, int col);

💬 game.c ( FineMine 函数 和 get_mine_count 函数 )

static int get_mine_count (
	char mine[ROWS][COLS], 
	int x, 
	int y
	)
{
	/*
	*    (x-1, y-1)  (x-1, y)  (x-1, y+1)
	* 
	*    ( x , y-1)  ( x , y)  ( x , y+1)
	* 
	*    (x+1, y-1)  (x+1, y)  (x+1, y+1)
	*/

	return (
		mine[x - 1][y] +
		mine[x - 1][y - 1] +
		mine[x][y - 1] +
		mine[x + 1][y - 1] +
		mine[x + 1][y] +
		mine[x + 1][y + 1] +
		mine[x][y + 1] +
		mine[x - 1][y + 1] - 8 * '0'
		);
}

void FindMine (
	char mine[ROWS][COLS],
	char show[ROWS][COLS],
	int row,int col
	)
{
	/*
	* 注释:
	*	1. 输入排查的坐标
	*	2. 检查坐标处是不是雷
	*		(1)是雷  - 很遗憾炸死了 - 游戏结束
	*		(2)不是雷 - 统计坐标周围有几个雷 - 存储排查类的信息
	*/
	
	int x = 0;
	int y = 0;

	while (1) {
		printf_s("\n请输入要排查雷的坐标: "); // x(1~9) y(1~9)
		scanf_s("%d%d", &x, &y);
		/* 判断坐标的合法性 */
		if (x >= 1 && x <= row && y >= 1 && y <= col) {
			if (mine[x][y] == '1') {
				/* 是雷,宣告游戏失败 */
				printf_s("\n很遗憾,你被炸死了\n");
				DisplayBoard(mine, row, col);
				break;
			}
			else {
				/* 不是雷,统计x,y坐标有几个雷 */
				int count = get_mine_count(mine, x, y);
				show[x][y] = count+'0'; // ASCII化为字符
				/* 显示排查出的信息 */
				DisplayBoard(show, row, col);
			}
		}
		else {
			printf("\n坐标非法,请重新输入!\n");
		}
	}
}

0x07 设置胜利条件

📚 思路:

① 加入一个计数器win,统计排查的雷的个数,当个数等于雷数时,说明雷都被排完了,宣告游戏胜利;

② while 循环的条件可以设置为 只要 win 仍然小于 9x9 减雷数,就进入循环;

💬 game.c ( FineMine 函数 )

void FindMine (
	char mine[ROWS][COLS],
	char show[ROWS][COLS],
	int row,int col
	)
{
	/*
	* 注释:
	*	1. 输入排查的坐标
	*	2. 检查坐标处是不是雷
	*		(1)是雷  - 很遗憾炸死了 - 游戏结束
	*		(2)不是雷 - 统计坐标周围有几个雷 - 存储排查类的信息
	*/
	
	int x = 0;
	int y = 0;
	int win = 0;

	while (win<row*col - EASY_COUNT) {
		printf_s("\n请输入要排查雷的坐标: "); // x(1~9) y(1~9)
		scanf_s("%d%d", &x, &y);
		/* 判断坐标的合法性 */
		if (x >= 1 && x <= row && y >= 1 && y <= col) {
			if (mine[x][y] == '1') {
				/* 是雷,宣告游戏失败 */
				printf_s("\n很遗憾,你被炸死了\n");
				DisplayBoard(mine, row, col);
				break;
			}
			else {
				/* 不是雷,统计x,y坐标有几个雷 */
				int count = get_mine_count(mine, x, y);
				show[x][y] = count+'0'; // ASCII化为字符
				/* 显示排查出的信息 */
				DisplayBoard(show, row, col);
				win++;
			}
		}
		else {
			printf("\n坐标非法,请重新输入!\n");
		}
	}
	
	if (win == row * col - EASY_COUNT) {
		printf("恭喜你,排雷成功!\n");
		DisplayBoard(mine, row, col);
	}
}

0x08 代码运行

🚩 排查雷的坐标

🚩 非法输入坐标

🚩 很遗憾,你被炸死了

参考资料:

Microsoft. MSDN(Microsoft Developer Network)[EB/OL]. []. .

比特科技. C语言基础[EB/OL]. 2021[2021.8.31]. .

📌 本文作者: 柠檬叶子

📃 更新记录: 2021.5.27

勘误记录:

📜 本文声明: 由于作者水平有限,本文有错误和不准确之处在所难免,本人也很想知道这些错误,恳望读者批评指正!

本章完。