• c语言中数组的介绍(以及三子棋扫雷游戏程序逻辑的介绍!血书25000字!!!!!)


    数组

    一:一维数组的创建和初始化

    1.数组的创建

    定义:数组是一组相同类型元素的集合。
    创建方式:

    type_t   arr_name   [const_n];
    //type_t 是指数组的元素类型
    //const_n 是一个常量表达式,用来指定数组的大小
    
    • 1
    • 2
    • 3

    实例:

    int arr1[10];//int表示数组内的元素为整形
    char arr2[20];//arr2表示数组名
    float arr3[2+3];//在C89标准下,[]内为常量或常量表达式,2+3属于常量表达式
    double arr4[5] = 10;//浮点数在内存中无法精确保存
    int arr5[num];//此时[]内为变量,在C99标准中可以支持这种写法,创建的数组叫做变长数组
    
    • 1
    • 2
    • 3
    • 4
    • 5

    注意:变长数组支持变长数组的大小用变量来指定,变长数组不是数组的长度可以变化,而是数组的大小可以用变量来指定。

    2.数组的初始化

    数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值。

    数组初始化又分为完全初始化和不完全初始化

    完全初始化:表示对数组的每一个元素进行赋值。
    
    不完全初始化:表示对数组的部分元素进行赋值,没有被赋值的元素设为0。
    
    • 1
    • 2
    • 3
    int arr1[10] = { 1,2,3 };
    //将前三个元素初始化为1,2,3,后面的元素为0,这种初始化叫做不完全初始化
    int arr2[] = { 1,2,3,4 };
    //没有给定元素个数时,内存会默认分配足够放置元素的空间,此时元素个数为4
    int arr3[5] = { 12345 };
    //每个元素都被赋值,完全初始化
    char arr4[3] = { 'a',98, 'c' };
    //98代表ASCII码值对应的字符b
    char arr5[] = { 'a','b','c' };
    //这里只储存了a、b、c三个字符,共三个元素
    char arr6[] = "abc";
    //字符串有一个结束标志\0也要储存到数组中,共4个元素
    //注意:arr6中如果叫你算元素多少种(数组的大小)?sizeof=4
    //如果是算字符串长度strlen那就=3(\0的长度为0)
    char arr7[5] = {'a','b','c'};
    //这里储存了a,b,c,0,0五个元素
    char arr7[5] = "abc";
    //这里储存了a,b,c,'\0'(ASCII码值还是0),0
    //arr7的两者性质不同,前者是内存默认分配abc三个元素,后者是内存默认分配a,b,c,'\0'四个元素
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3.一维数组的使用

    对于数组的使用我们之前介绍了一个操作符: [] ,下标引用操作符。它其实就数组访问的操作符。

    #include 
    int main()
    {
         int arr[10] = {0};//数组的不完全初始化
         //计算数组的元素个数
         int sz = sizeof(arr)/sizeof(arr[0]);//sz为元素个数
         //对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以:
         int i = 0;//做下标
         for(i=0; i<sz; i++)//这里写sz表明循环sz次,并且保证数组不会越界
         {
             arr[i] = i;//把数组的下标赋给对应元素
         } 
         //输出数组的内容
         for(i=0; i<10; ++i)
         {
             printf("%d ", arr[i]);//打印
         }
         return 0; 
    }
    //输出:0 1 2 3 4 5 6 7 8 9 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    总结:

    1. 数组是使用下标来访问的,下标是从0开始。
    2. 数组的大小可以通过计算得到
    int arr[10];
    int sz = sizeof(arr)/sizeof(arr[0]);
    
    • 1
    • 2

    4.一维数组在内存中的存储

    接下来我们来探讨数组在内存中的存储

    #include 
    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]);//这个i不能少,是打印arr[%d]里面的
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述
    由图我们可以看出
    随着数组下标的增长,由于每个元素都为int类型,所以下标每增加1,数组元素的地址会加4,也就是说,元素的地址,也在有规律的递增。
    由此可以得出结论:数组在内存中是连续存放的。元素下标越大它的地址也越高。
    在这里插入图片描述

    二、二维数组

    二:二维数组的创建和初始化

    1.数组的创建

    //数组创建,其中前面的[]表示行,后面的[]表示列
    int arr[3][4];
    char arr[3][5];
    
    • 1
    • 2
    • 3

    2.数组的初始化

    //数组初始化
    int arr1[3][4] = {1,2,3,4,5};
    //元素表示:
    // 1 2 3 4 
    // 5 0 0 0
    // 0 0 0 0
    // 0 0 0 0
    int arr2[3][4] = {{1,2},{3,4}};
    //元素表示:
    // 1 2 0 0
    // 3 4 0 0
    // 0 0 0 0
    // 0 0 0 0
    int arr3[][4] = {{2,3},{4,5}};//二维数组如果有初始化,行可以省略,列不能省略
    //元素表示:
    // 2 3 0 0
    // 4 5 0 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    提示:

    • 在二维数组中,如果只有一个{},那么数组会先按行来赋值,一直到赋满行为止,如果有多个括号,不同情况不同分析,来看看下面的这个例子。
    • arr2是个三行四列的数组,这个时候我们来看括号内的元素,它是一个嵌套括号,所以我们应该先排第一个内括号的元素,也就是{1,2},第一行排完1 2后发现还少了两个元素,由于第一个内括号只有两个元素,所以也是不完全初始化,从而我们需要在第一行的剩下两个元素中补0,所以第一行是1 2 0 0,以两个括号之间的逗号为分界点 我们开始排第二个内括号的元素{4,5},与上面一样,同理可得第二行的元素是4 5 0 0。
    • 在arr3中行没有初始化,那我们就看列数以及{}内有没有嵌套{}(也就是{ { } } ),如果有嵌套括号,比如arr3中的{{2,3},{4,5}},先将第一个内括号的{2,3}排在第一行,由于是四列元素,第一行只不完全初始化两个数,所以剩下的第一行剩下的两个数都为0。同理第二行排{4,5},剩下两个数都为0,由于行数没有初始化,内存会根据你内部的情况分配相应的行数。所以最终arr3就是一个两行四列的二维数组。

    3.二维数组的使用

    二维数组的使用也是通过下标的方式

    #include 
    int main()
    {
        int arr[3][4] = { 0 };
        int i = 0;
            for (i = 0; i < 3; i++)
            {
                int j = 0;
                for (j = 0; j < 4; j++)
                {
                    arr[i][j] = i * 4 + j;          
                }
            }
            for (i = 0; i < 3; i++)
            {
                int j = 0;
                for (j = 0; j < 4; j++)
                {
                    printf("%d ", arr[i][j]);
                }
            }
            return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    4.二维数组在内存中的存储

    像一维数组一样,这里我们尝试打印二维数组的每个元素

    #include 
    int main()
    {
        int arr[3][4];
        int i = 0;
        for (i = 0; i < 3; i++)
        {
            int j = 0;
            for (j = 0; j < 4; j++)
            {
                printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);
            }
        }
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这个代码可以打印数组的每一个元素的地址,输出结果为:
    在这里插入图片描述

    通过结果我们可以分析到:

    • 与一维数组的规律一样,其实二维数组在内存中也是连续存储的。(可以看下图1)
    • 上图中程序先排列再排行。或者可以这么理解——如果第一行我们想象成一维数组的话,它的数组名就是(arr[0]), 然后arr[0][j]中的j就是它的下标(看下图2)

    在这里插入图片描述

    在这里插入图片描述

    三:数组越界

    数组的下标是有范围限制的。

    数组的下标规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。 [0,n-1]
    所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。

    C语言本身不会做数组下标的越界检查,所以当我们越界访问时编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,所以程序员写代码时,最好自己做越界的检查。

    #include 
    int main()
    {
        int arr[10] = {1,2,3,4,5,6,7,8,9,10};
        int i = 0;
        for(i = 0; i <= 10; i++)
        {
            printf("%d\n", arr[i]);//当i等于10的时候越界访问了
        }
        return 0; 
    }
    //输出:1 2 3 4 5 6 7 8 9 10 -858993460(最后的数字是个随机值)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    四:数组作为函数参数

    1.数组名是什么?

    一般情况下
    除了下面两个特殊情况外,所有的数组名只表示数组首元素的地址。

    特殊情况下
    (1)sizeof(数组名)——这里计算的是整个数组的大小

    (2)&数组名——这里拿出的也是整个数组的地址

    来看看下面的代码

    #include
    int main()
    {
        int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
        printf("%p\n", arr);//数组首元素地址
        printf("%p\n", &arr[0]);//也是数组首元素地址
        printf("%d\n", *arr);//数组首元素地址对应的元素
        printf("%p\n", &arr);//数组的地址,虽然这个地址也是数组首元素的地址但还是有所区别
        printf("%p\n", &arr+1);//取出整个数组的地址,指针指向一个数组大小后的元素
        printf("%d",arr[i])<==>printf("%d",*(p+i));//arr[i]==*(arr+i)
        //p+i就是下标为i元素的地址,通过对p+i的解引用,找到下标为i的元素。
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2.冒泡排序函数的正确设计

    冒泡排序的思想:两两相邻的元素进行比较
    分析:假设我们要排10个元素 9 8 7 6 5 4 3 2 1 0将其排成顺序
    那么我们根据冒泡排序思想:9和8先进行比较,发现9更大,则两者换序,然后9和7比较…以此类推我们可以把9放到最后一位去。这是一趟冒泡排序。(N个元素,最坏的情况有N-1趟冒泡排序)…以此类推从而将这10个元素排成顺序

    则我们来看下面这个代码

    //冒泡排序
    #include 
    void bubble_sort(int arr[],int sz)
    {
        int i = 0;
        for (i = 0; i < sz - 1; i++)//每进入一次,就可以排一个元素
        //i表示一个数字冒泡排序的趟数
        //在排完n-1个元素后,最后一个元素实际上也排好了,
        //所以进入n-1时即可
        {
            int j = 0;
            //for循环的判断条件就是一趟里面数字j里面交换了多少次,
            //然后再减去趟数即可
            for (j = 0; j < sz - 1 - i; j++)
                //每一次把较大的数字往后放,
                //因为后面的数字已经排好了,
                //所以只需要比较前面乱序的部分就够了
                //-i的原因是因为随着每次交换,
                //还没被交换的数字越来越少,
                //趟数在逐渐减少,所以我们得减去第i趟
            {
                if (arr[j] > arr[j + 1])//前大于后则前后换位
                {
                    int tmp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = tmp;
                }
            }
        }
    }
    int main()
    {
        int arr[] = { 2,1,4,5,9,8,6,3,7,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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    五:数组实例

    1.三子棋

    写一个程序项目需要很多个代码,创建一个文件可能会太复杂不方便管理,因此我们就通过创建多文件来方便代码的优化与管理。

    (1):文件的编排

    我们需要创建三个文件:
    test.c:放置实现/测试该游戏程序逻辑的内部细节函数
    game.h:放置游戏函数的声明,头文件的引用和标识符常量的定义
    game.c:放置主程序的实现,包括进入、退出游戏的程序和游戏的整体实现的函数

    game的模块被test.c声明引用,最终形成了一个游戏。

    (2):实现游戏的进入与退出

    分析逻辑:

    1:首先我们需要设计程序的菜单和界面,要让玩家知道怎么选择,从而我们设计一个menu函数
    2.为了让这个游戏至少执行一次,我们运用do....while循环.
    3.这个时候我们要定义一个input变量(在do…while上面定义),需要用scanf来输入这个变量,用switch语句来匹配输入input的值的想法,输入1开始游戏,0退出游戏,输入其他数值就重新输入。
    4.判断条件为input,input输入值为0时,判断为假,跳出循环。
    5.如果我们选择1,开始玩游戏,这个时候可以设置一个game函数,在game函数里进行简单的三子棋游戏。
    6.我们可以把需要使用的头文件的引用放在game.h这个文件中,在每个源文件中再引用这个头文件即可。

    代码:

    test.c

    #include"game.h"
    void menu()
    {
    	printf("*********************************\n");
    	printf("********      1. play    ********\n");
    	printf("********      0. exit    ********\n");
    	printf("*********************************\n");
    }
    void game()
    {
    	printf("三子棋游戏\n");
    }
    
    
    #include
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    game.h

    #pragma once
    #include
    
    • 1
    • 2

    这个时候只是打印了界面,它的游戏功能还没有实现,那么我们接下来就要实现这个功能

    (3):对棋盘的设计与打印

    分析逻辑:

    1.我们可以创建一个3x3的简式棋盘,这个时候我们就联想到了可以通过二维数组的思想来设计棋盘,玩家下棋下*,电脑下棋用的是#,所以我们需要定义一个char类型的二维数组,不过3*3设定的太死板,所以我们可以运用#define这个语法来定义行(ROW)和列(COL)。
    2.我们这个时候对棋盘初始化,我们设定一个初始化棋盘的函数init_board(),我们初始化数组的每一个元素都为空格(如果初始化为0,那么会导致棋盘上下左右长度大小不一样)。
    3.这个时候我们开始对棋盘打印,我们设定一个打印棋盘的函数display_board(),
    下面就是我们想要打印出来的棋盘

       |   |   
    ---|---|---
       |   |   
    ---|---|---
       |   |   
    //共五行()
    //我们可以把第一行进行拆分:
    //每一个元素就是:空格+元素+空格 和 |的交替,而最后一个|不需要打印
    //我们再把第二行进行拆分:
    //每一个元素就是:---- 和 |的交替,同样最后一个|不需要打印
    //第一行和第二行算一组row,一共有三组row,而第三组row里面没有---
    //所以这个程序可以通过选择和循环进行实现
    //列也是这样的方法来拆分的
    //我们可以把第一列进行拆分
    //也就是  两个---为一组col,|为另一组col,一共三组col,而第三组col里面没有竖线
    //	     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    代码:

    test.c

    #include"game.h"
    void menu()
    {
    	printf("*********************************\n");
    	printf("********      1. play    ********\n");
    	printf("********      0. exit    ********\n");
    	printf("*********************************\n");
    }
    void game()
    {
    	//数据的存储需要一个3*3的二维数组
    	char board[ROW][COL] = { 0 };
    	init_board(board,ROW,COL);
    	display_board(board,ROW,COL);
    }
    
    
    #include
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    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] = ' ';
    		}
    	}
    }
    //void display_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
    //		if (i < row - 1)
    //			printf("---|---|---\n");
    //	}	
    //}
    //但实际这个代码还是不好,如果我改成10行10列,最后的结果还是10行3列,
    //因为第21行代码把列数写死了
    //所以我们这样修改
    void display_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)
    		{
    			/*printf("---|---|---");*/
    			for (j = 0; j < col; j++)
    			{
    				printf("---");
    				if (j < col - 1)
    					printf("|");
    			}
    			printf("\n");
    		}
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    game.h

    #pragma once
    #include
    
    #define ROW 3
    #define COL 3
    
    //初始化棋盘
    void init_board(char board[ROW][COL], int row, int col);
    
    //打印棋盘
    void display_board(char board[ROW][COL], int row, int col);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    (4):玩家与电脑开始玩游戏

    分析逻辑:

    1.设置一个玩家下棋的函数play_move(),由于我们下棋还是要下到数组里面去,所以我们把board,ROW,COL放到play_move函数里面作形参。
    2.这个时候发现我们只光有一个棋盘,玩家没法找地方落子,这个时候我们在game.c主函数里面设置横纵坐标x,y,由于存在越界的可能,这个时候我们要判断坐标的合法性->坐标是否被占用
    3.由于棋盘是由二维数组的思想创建的,第一行第一列的下标为0,但可能有些玩家小白会认为第一行第一列的坐标为(1,1),所以我们利用if.....else语句,横坐标、纵坐标的范围我们就确定了。由于我们是下棋,要多次输入下棋的坐标,所以我们要用循环语句while(1)死循环,直到我们输入合法了才会跳出死循环,这个时候我们要在棋盘(数组)里面下棋,但其实我们第一行第一列的坐标是(0,0),所以我们在if…else语句里面再用if语句判断坐标是不是空格,如果是空格,则我们可以在里面下棋(也就是玩家将*填进数组里面去)然后用break跳出循环,如果不是,那说明该点已经被占用了。
    4.下完棋后棋盘需要重新打印(把玩家函数和打印函数放到test.c内部细节函数的while(1)里面)
    5.同理我们也要设置个电脑下棋函数computer_move()函数,与玩家不同的是,电脑是随机下棋的,同样我们先设置横纵坐标x,y,生成随机的下标,我们利用rand函数(rand函数可以生成一个随机数,这个数字的范围是0~RAND_MAX(32767)) 但想调用rand需要先在主函数里调用srand函数
    调用完后,由于横纵坐标范围都是[0,2],所以我们分别对行列取余,这样就可以确保横纵坐标不会越界,然后与玩家下棋的逻辑一样,用if语句判断坐标是不是空格,如果是空格,则电脑可以在里面下棋(也就是电脑将#填进数组里面去)然后用break跳出循环,如果不是,那说明该点已经被占用了。(注意:与玩家下棋不同的之处在于:玩家下棋他可能不知道第一行第一列的坐标其实是(0,0),而电脑的横纵坐标已经确定范围是[0,2]了,所以if语句判断坐标的时候,不需要再像玩家下棋那样横纵坐标减1来判断)

    代码:

    game.h

    #pragma once
    #include
    
    #define ROW 3
    #define COL 3
    
    //初始化棋盘
    void init_board(char board[ROW][COL], int row, int col);
    
    //打印棋盘
    void display_board(char board[ROW][COL], int row, int col);
    
    //玩家下棋
    void player_move(char board[ROW][COL], int row, int col);
    
    //电脑下棋
    void computer_move(char board[ROW][COL], int row, int col);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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] = ' ';
    		}
    	}
    }
    //void display_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
    //		if (i < row - 1)
    //			printf("---|---|---\n");
    //	}	
    //}
    //但实际这个代码还是不好,如果我改成10行10列,最后的结果还是10行3列,
    //因为第21行代码把列数写死了
    //所以我们这样修改
    void display_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)
    		{
    			/*printf("---|---|---");*/
    			for (j = 0; j < col; j++)
    			{
    				printf("---");
    				if (j < col - 1)
    					printf("|");
    			}
    			printf("\n");
    		}
    	}
    }
    //玩家下棋
    void player_move(char board[ROW][COL], int row, int col)
    {
    	int x = 0;
    	int y = 0;
    	printf("玩家下棋:>\n");
    	while (1)
    	{
    		printf("请输入要下棋的坐标:>");
    		scanf("%d %d", &x, &y);
    		//1.坐标的合法性
    		//2.坐标是否被占用
    		if (x >= 1 && x <= row && y >= 1 && y <= col)
    		{
    			if (board[x - 1][y - 1] == ' ')
    			{
    				board[x - 1][y - 1] = '*';
    				break;
    			}
    			else
    			{
    				printf("该坐标被占用,请重新输入\n");
    			}
    		}
    		else
    		{
    			printf("坐标非法,重新输入\n");
    		}
    	}
    }
    
    
    
    //电脑随机下棋
    void computer_move(char board[ROW][COL], int row, int col)
    {
    	printf("电脑下棋:>\n");
    	//0~32726
    	//%3-->0~2
    	while (1)
    	{
    		int x = rand() % row;
    		int y = rand() % col;
    		if (board[x][y] == ' ')
    		{
    			board[x][y] = '#';
    			break;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104

    test.c

    #include"game.h"
    void menu()
    {
    	printf("*********************************\n");
    	printf("********      1. play    ********\n");
    	printf("********      0. exit    ********\n");
    	printf("*********************************\n");
    }
    void game()
    {
    	//数据的存储需要一个3*3的二维数组
    	char board[ROW][COL] = { 0 };
    	init_board(board,ROW,COL);
    	display_board(board,ROW,COL);
    	//玩游戏
    	while (1)
    	{
    		player_move(board, ROW, COL);
    		display_board(board, ROW, COL);
    		computer_move(board, ROW, COL);
    		display_board(board, ROW, COL);
    	}
    }	
    
    
    #include
    int main()
    {
    	int input = 0;
    	//设置随机数的生成器
    	srand((unsigned int)time(NULL));
    	//调用srand函数确定一个生成随机数的起始位置
    	//(srand函数里面有个变化值即可,因为括号里随机输入一个数,结果都不一样)
    	//srand函数需要一个unsigned int类型的变动的值。
    	//我们要产生一个随机值,而这个随机值的生成需要一个随机值
    	//我们似乎进入了一个先有鸡先有蛋的问题,这时就需要引入一个概念叫做时间戳
    	//时间戳表示一个时间,
    	//这个时间就是相对于1970年1月1日到现在的时刻我们经过了多少秒,然后将它转化为一个数字,
    	//其实这个数字指的是:
    	//电脑此时此刻运行这个代码的时间和计算机的起始时间的一个差值,
    	//单位为s,就是一个时间戳
    	//由于时间是不断变动的,它就可以满足我们的要求,
    	//time函数可以实现当前的这个时间戳,可以返回这个数字,
    	//time函数需要一个指针类型的参数,
    	//如果不想要那个参数,那我们就给它一个空指针.
    	//它的返回类型是time_t,需要强制类型转换为unsigned int
    	//让我们回到rand函数那里
    	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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    (5):胜负的判定

    分析逻辑:

    1.既然是三子棋,是一个竞技类游戏,那就必然有胜负,是不是有三种状态:玩家赢,电脑赢,平局? 错 其实按照计算机思维,有四种状态:“玩家赢、电脑赢、平局、游戏继续”
    2.这四种状态我们应该在玩游戏那一行代码开始检测,玩家/电脑每走一步棋就要判定棋局是否满足那四种状态,——在玩家/电脑每走一步棋play_move()函数和棋盘打印函数display_move()函数之间判定四种状态,所以我们加入状态函数is_win(),来判定四种状态,我们在循环外面用字符变量 ret来接收四种状态,而状态函数要告诉我们4种状态,我们规定:玩家赢:‘*’;电脑赢:‘#’,平局:‘Q’,游戏继续:‘C’ 。
    3.“游戏继续”这个状态出现的次数最多,而且走完棋后我们要判断四种状态,所以在test.c上利用if语句——如果状态变量不等于’C’,则跳出循环语句,再来用多分支语句判断是赢是输还是平局,判断完后打印最终棋盘。如果状态变量等于’C’,则游戏继续。
    4.然后我们要在game.c的游戏实现函数里面用状态函数来实现赢法。
    三行我们可以利用循环语句,里面再用if语句通过判断行数,列数。然后再用两个if语句来判断左对角线和右对角线。从而来判断谁赢谁输。
    5.平局——(没人赢且没空格)得单独拿出来讨论。我们再写一个函数is_full(),判断它是否空格满了,如果满了返回1
    6.为了支持is_win函数,从而写了(简单分装了)is_full函数(),我们不想把这个接口(函数)暴露到头文件里面去,那么我们不用在头文件里面声明这个函数,如果不想让其他源文件看到接口,那么我们直接在int is_full()前面加static,从而只能在game.c里面看到它,别人想用?No way!
    7.与第四步不同,还有一种方法(可以不需要把is_win函数的代码写死):判断行,定义对应的count变量对棋子进行计数,若有一个变量等于行数代表有人胜利,返回对应的棋子。(其实我们可以直接把每一个元素进行比较,并且它们还需要满足不为空格)。判断列,判断对角线也是同样的道理。

    (最终) 代码:

    game.h

    #pragma once
    #include
    #include
    #include
    
    #define ROW 3
    #define COL 3
    
    //初始化棋盘
    void init_board(char board[ROW][COL], int row, int col);
    
    //打印棋盘
    void display_board(char board[ROW][COL], int row, int col);
    
    //玩家下棋
    void player_move(char board[ROW][COL], int row, int col);
    
    //电脑下棋
    void computer_move(char board[ROW][COL], int row, int col);
    
    //判断游戏状态
    char is_win(char board[ROW][COL], int row, int col);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    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] = ' ';
    		}
    	}
    }
    //void display_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
    //		if (i < row - 1)
    //			printf("---|---|---\n");
    //	}	
    //}
    //但实际这个代码还是不好,如果我改成10行10列,最后的结果还是10行3列,
    //因为第21行代码把列数写死了
    //所以我们这样修改
    void display_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)
    		{
    			/*printf("---|---|---");*/
    			for (j = 0; j < col; j++)
    			{
    				printf("---");
    				if (j < col - 1)
    					printf("|");
    			}
    			printf("\n");
    		}
    	}
    }
    //玩家下棋
    //方法一:
    void player_move(char board[ROW][COL], int row, int col)
    {
    	int x = 0;
    	int y = 0;
    	printf("玩家下棋:>\n");
    	while (1)
    	{
    		printf("请输入要下棋的坐标:>");
    		scanf("%d %d", &x, &y);
    		//1.坐标的合法性
    		//2.坐标是否被占用
    		if (x >= 1 && x <= row && y >= 1 && y <= col)
    		{
    			if (board[x - 1][y - 1] == ' ')
    			{
    				board[x - 1][y - 1] = '*';
    				break;
    			}
    			else
    			{
    				printf("该坐标被占用,请重新输入\n");
    			}
    		}
    		else
    		{
    			printf("坐标非法,重新输入\n");
    		}
    	}
    }
    //方法二:
    void player_move(char board[ROW][COL], int row, int col)//玩家下棋
    {
    	printf("玩家下棋\n");
    	while (1)
    	{
    		int i = 0;
    		int j = 0;
    		printf("请输入坐标:");
    		scanf("%d %d", &i, &j);
    		if (board[i - 1][j - 1] == ' ')
    		{
    			board[i - 1][j - 1] = '*';
    			break;
    		}
    		else
    		{
    			printf("请重新输入\n");
    		}
    	}
    }
    
    
    //电脑随机下棋
    //方法一:
    void computer_move(char board[ROW][COL], int row, int col)
    {
    	printf("电脑下棋:>\n");
    	//0~32726
    	//%3-->0~2
    	while (1)
    	{
    		int x = rand() % row;
    		int y = rand() % col;
    		if (board[x][y] == ' ')
    		{
    			board[x][y] = '#';
    			break;
    		}
    	}
    }
    //方法二:
    void computermove(char board[ROW][COL], int row, int col)//电脑下棋
    {
    	printf("电脑下棋\n");
    	while (1)
    	{
    		int i = rand() % ROW;
    		int j = rand() % COL;
    		if (board[i][j] == ' ')
    		{
    			board[i][j] = '#';
    			break;
    		}
    	}
    }
    
    //如果棋盘满了返回1,不满返回0
    static int is_full(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;
    }
    
    
    //判断游戏状态(与上面static连在一起)
    //方法一:
    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][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[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 (is_full(board, row, col) == 1)
    		return 'Q';
    	//继续
    	else
    		return 'C';
    }
    
    //方法二:(把static去掉了)
    //判断胜负
    //玩家胜利return *
    //电脑胜利return #
    //没人胜利同时还可以继续游戏return 'c'
    //所有的位置都被占满了,return 'q'
    char is_win(char board[ROW][COL], int row, int col)
    {
    	int i = 0;
    	//判断行
    	for (i = 0; i < row; i++)
    	{
    		int j = 0;
    		int count1 = 0;
    		int count2 = 0;
    		for (j = 0; j < col; j++)
    		{
    			if (board[i][j] == '*')
    			{
    				count1++;
    			}
    			if (board[i][j] == '#')
    			{
    				count2++;
    			}
    			if (count1 == row)
    				return '*';
    			if (count2 == row)
    				return '#';
    		}
    	}
    
    
    	//判断列
    	for (i = 0; i < col; i++)
    	{
    		int j = 0;
    		int count1 = 0;
    		int count2 = 0;
    		for (j = 0; j < row; j++)
    		{
    			if (board[j][i] == '*')
    			{
    				count1++;
    			}
    			if (board[j][i] == '#')
    			{
    				count2++;
    			}
    			if (count1 == col)
    				return '*';//玩家赢
    			if (count2 == col)
    				return '#';//电脑赢
    		}
    	}
    
    
    	//判断对角线
    	int count1 = 0;
    	int count2 = 0;
    	for (i = 0; i < row; i++)
    	{
    		if (board[i][i] == '*')
    		{
    			count1++;
    		}
    		if (board[i][i] == '#')
    		{
    			count2++;
    		}
    		if (count1 == col)
    			return '*';//玩家赢
    		if (count2 == col)
    			return '#';//电脑赢
    	}
    	int count3 = 0;
    	int count4 = 0;
    	for (i = 0; i < row; i++)
    	{
    		if (board[row - 1 - i][i] == '*')
    		{
    			count3++;
    		}
    		if (board[row - 1 - i][i] == '#')
    		{
    			count4++;
    		}
    		if (count3 == col)
    			return '*';//玩家赢
    		if (count4 == col)
    			return '#';//电脑赢
    	}
    
    
    	//判断是否平局
    	int count = 0;
    	for (i = 0; i < row; i++)
    	{
    		int j = 0;
    
    		for (j = 0; j < col; j++)
    		{
    			if (board[i][j] == ' ')
    			{
    				count++;
    			}
    		}
    	}
    	if (count == 0)
    	{
    		return 'q';//平局
    	}
    	return 'c';//都不满足,继续游戏
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314

    test.c

    #include"game.h"
    void menu()
    {
    	printf("*********************************\n");
    	printf("********      1. play    ********\n");
    	printf("********      0. exit    ********\n");
    	printf("*********************************\n");
    }
    void game()
    {
    	char ret = 0;
    	//数据的存储需要一个3*3的二维数组
    	char board[ROW][COL] = { 0 };
    	init_board(board,ROW,COL);
    	display_board(board,ROW,COL);
    	//玩游戏
    	while (1)
    	{
    		player_move(board, ROW, COL);
    		display_board(board, ROW, COL);
    		ret = is_win(board, ROW, COL);
    		if (ret != 'C')
    			break;
    		computer_move(board, ROW, COL);
    		display_board(board, ROW, COL);
    		ret = is_win(board, ROW, COL);
    		if (ret != 'C')
    			break;
    	}
    	if (ret == '*')
    	{
    		printf("玩家赢\n");
    	}
    	else if (ret == '#')
    	{
    		printf("电脑赢\n");
    	}
    	else if (ret == 'Q')
    	{
    		printf("平局\n");
    	}
    	display_board(board, ROW, COL);
    }	
    
    
    #include
    int main()
    {
    	int input = 0;
    	//设置随机数的生成器
    	srand((unsigned int)time(NULL));
    	//调用srand函数确定一个生成随机数的起始位置
    	//(srand函数里面有个变化值即可,因为括号里随机输入一个数,结果都不一样)
    	//srand函数需要一个unsigned int类型的变动的值。
    	//我们要产生一个随机值,而这个随机值的生成需要一个随机值
    	//我们似乎进入了一个先有鸡先有蛋的问题,这时就需要引入一个概念叫做时间戳
    	//时间戳表示一个时间,
    	//这个时间就是相对于1970年1月1日到现在的时刻我们经过了多少秒,然后将它转化为一个数字,
    	//其实这个数字指的是:
    	//电脑此时此刻运行这个代码的时间和计算机的起始时间的一个差值,
    	//单位为s,就是一个时间戳
    	//由于时间是不断变动的,它就可以满足我们的要求,
    	//time函数可以实现当前的这个时间戳,可以返回这个数字,
    	//time函数需要一个指针类型的参数,
    	//如果不想要那个参数,那我们就给它一个空指针.
    	//它的返回类型是time_t,需要强制类型转换为unsigned int
    	//让我们回到rand函数那里
    	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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    2.扫雷游戏

    (1):文件的编排

    我们同样需要创建三个文件:
    play.c:放置实现/测试该游戏程序逻辑的内部细节函数
    game.h:放置游戏函数的声明,头文件的引用和标识符常量的定义
    game.c:放置主程序的实现,包括进入、退出游戏的程序和游戏的整体实现的函数

    game的模块被play.c声明引用,最终形成了一个游戏。

    (2):实现游戏的进入与退出

    分析逻辑:

    1:首先我们需要设计程序的菜单和界面,要让玩家知道怎么选择,从而我们设计一个menu函数
    2.为了让这个游戏至少执行一次,我们运用do....while循环.
    3.这个时候我们要定义一个input变量(在do…while上面定义),需要用scanf来输入这个变量,用switch语句来匹配输入input的值的想法,输入1开始游戏,0退出游戏,输入其他数值就重新输入。
    4.判断条件为input,input输入值为0时,判断为假,跳出循环。
    5.如果我们选择1,开始玩游戏,这个时候可以设置一个game函数,在game函数里进行简单的扫雷游戏。
    6.我们可以把需要使用的头文件的引用放在game.h这个文件中,在每个源文件中再引用这个头文件即可。

    代码和上面三子棋的一样。

    (3):对棋盘的设计与打印

    分析逻辑:

    我们以这个9*9的格子为例:
    在这里插入图片描述
    由图我们可以看出,1表示周围的8个坐标里面有一个雷,同理2,3表示也一样。

    1.我们可以根据9x9的棋盘,创建一个9x9的二维数组
    2.接下来我们来排雷,当你点完一个键后,出来1个/多个数字,这个时候我们就要排查数字周围的空格(坐标)有几个地雷,最多会有8个空格给你排查,但如果你点击最边上的键的时候,就会发现我们如果在程序中遍历排查8个空格的话,会存在数组越界的情况,如果用if语句判断这些特殊的情况,未免太麻烦了。所以我们把数组的范围扩大,把9x9的数组换成11x11的数组。
    3.如果想增大难度,那就没必要把数组的下标规定死,所以我们在game.h里面利用#define来定义ROW(9)与COL(9),然后再用#define定义一个ROWS ROW+2,COLS, COL+2。 这样就可以使用两种不同类型的二维数组了。
    4.我们这个时候我们开始对棋盘打印,我们设定一个打印棋盘的函数display_board(),但实际我们棋盘用的行列数是ROW和COL,在game文件里,char board数组里传参的还是ROWS,COLS(游戏运行时防止数组越界),这个时候打印的不好看,所以我们一开始先打印列号(j)(j=0开始循环(数组下标开始为0),防止数组列号错位)

    (4):设置地雷

    分析逻辑

    1.将地雷设置成1,非雷设置成0。但会出现数字1与地雷1无法区分的情况。所以这个时候我们再创建一个棋盘(二维数组),棋盘1/数组mine用来放布置好的地雷的信息,棋盘2/数组show用来放排查出的地雷的信息.

    2.mine数组的初始化全为0,布置地雷的时候改为1。show数组初始化为‘*’,排查出地雷后,具体位置改为数字字符,比如:‘3’,这个时候我们设置init_board()函数,对mine和show数组初始化(本例子中要初始化到11行11列)

    3.对二维数组初始化我们用遍历,由于两个数组初始化的内容不一样,我们对init_board()函数的形参定义一个字符变量set接收这两个初始化内容

    4.打印完棋盘后,我们就开始布置地雷函数set_mine()(对地雷初始化),我们只在9x9的格子里布置地雷,因此我们在play.c里面将ROW和COL放到地雷函数里面去,在game文件里面,char board数组里传参的还是ROWS,COLS,在game.c文件里面,我们要思考布置几个地雷,假设我们布置10个地雷,我们可以用#define定义一个变量可以让我们改变布置地雷数,我们利用while(count)循环来布置,我们在9x9格子里的81个坐标找10个坐标来放置地雷,我们先设置横纵坐标x,y,生成随机的下标,我们利用rand函数(rand函数可以生成一个随机数,这个数字的范围是0~RAND_MAX(32767)) 但想调用rand需要先在play.c主函数里调用srand函数,调用完后,由于我们发现数组下标范围[0,8],但真正在棋盘上布置地雷的时候,行列分别是从1~9,所以我们利用rand函数分别对ROW,COL取余再+1,再来用if语句判断这个位置有没有布置过雷。

    5.上述步骤4打印棋盘后,其实玩扫雷游戏的时候我们不需要打印mine数组的棋盘,布置完雷打印后,发现雷的位置显示了出来,我们将play.c里面的display_board函数先屏蔽掉,布置完雷后我们就开始排雷。

    6.我们设定一个排雷函数find_mine(),我们将mine,show,ROW,COL设置为实参(mine数组里面找,然后放到show数组里面)(从9x9的mine数组传到9x9的show数组里面去),形参里面的数组是ROWS和COLS。

    7.我们再次运用while循环来排雷,然后用if语句圈定雷的坐标范围,假如你输入的坐标合法,假如你坐标输入为(3,3),我们得统计一下mine数组里面(3,3)这个坐标附近究竟有多少个雷,所以我们再令int count 变量来接收地雷计数器函数get_mine_count(),我们将mine,x,y列为实际参数(因为我们要去mine数组里统计雷的个数),统计结束后还得放到show数组里面去,所以让show数组来接收count,假设count是3,但我们实际show数组接收的是字符’3’,所以我们在count后面加'0'(数字字符减去'0'得到对应的数字,所以反过来看对应的数字+'0'就能得到相应的字符)即可。

    8.那么地雷计数器函数get_mine_count()怎么来统计地雷的个数呢?我们把周围的(挨着你输入的坐标数)坐标给遍历一圈,如果这个坐标叫(x,y),周围的坐标分别是(x-1,y),(x-1,y-1)…我们要判断周围的坐标是否等于’1’,然后count++ 但这样做要判断8个坐标,实在是太麻烦了。我们发现,非雷里面如果放的是数字0的话,假设你的坐标输入,得到的反馈是那个空格显示了1(周围8个坐标里面有一个是地雷),我们把这9个坐标加起来得到了数字1,即1个地雷,但实际上非雷放的是字符'0',上面我们介绍了字符转数字的方法,我们就减去里面的8个坐标再×上字符'0’,就能成功统计出地雷个数了,我们直接返回这个值优化代码。

    (5):判断胜利

    分析逻辑

    1.假如我们布置了10个雷,一共有81个坐标,那么我们找到71个位置,游戏就结束了,这个时候我们要在圈定排雷范围的if语句下面再用一个if…else语句在mine数组里面判断你是赢还是输,在分支语句中一边是被炸死了 一边是还在用count来接收地雷计数函数(排雷),排完雷游戏也就结束了,此时定义一个win变量,我们还是要整体定义一个while循环来包住整个if语句,循环的条件我们可以通过第一行说的规律=>(ROW×COL-地雷数),只要排除掉一个雷,我们离胜利越来越近了,就win++,循环条件不满足后,跳出循环,循环外面我们再利用if语句来判断当win变量等于无雷的坐标的时候,就判断出游戏胜利了。

    2.但代码还是有缺陷,我们一直输入同一个坐标,游戏居然能够胜利
    所以我们在坐标合法的前提下(if语句下面)再用个if…else语句来套住上面的if…else语句,我们通过show数组的坐标是否等于’*'来排查这个bug

    说明:

    上述逻辑是点进去一次只排查了一个坐标,但真正的扫雷游戏,一次可能展开一片,比如下图在这里插入图片描述

    一次展开一片运用到了递归的思想

    递归函数的思路为只要找到满足

    • 该地址合法
    • 该地址不是雷
    • 该地址周围没有雷
    • 该地址没有被排查过
      满足上面的条件,就可以递归去看周围的8个坐标

    这里展开一片的代码我以后再补充,先给大家推送一个我觉得写的特别好的完整的扫雷程序的文章
    扫雷程序完整版

    代码:

    game.h

    #pragma once
    #include
    #include 
    #include 
    
    #define ROW 9
    #define COL 9
    
    #define ROWS ROW+2
    #define COLS COL+2
    
    #define EASY_COUNT 10
    
    //初始化棋盘
    void init_board(char board[ROWS][COLS], int rows, int cols, char set);
    
    //打印棋盘
    void display_board(char board[ROWS][COLS], int row, int col);
    
    //布置地雷
    void set_mine(char mine[ROWS][COLS], int row, int col);
    
    //排查雷
    void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    game.c

    #include "game.h"
    void init_board(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;
    		}
    	}
    }
    
    void display_board(char board[ROWS][COLS], int row, int col)
    {
    	//我们只打印11*11数组中的9*9,行列从1开始
    	int i = 0;
    	int j = 0;
    	//列号
    	for (j = 0; j <= col; j++)
    	{
    		printf("%d ", j);
    	}
    	printf("\n");
    	for (i = 1; i <= row; i++)
    	{
    		printf("%d ", i);
    		for (j = 1; j <= col; j++)
    		{
    			printf("%c ", board[i][j]);
    		}
    		printf("\n");
    	}
    }
    
    void set_mine(char mine[ROWS][COLS], int row, int col)
    {
    	//假设我们布置10个地雷
    	int count = EASY_COUNT;//用计数器来统计地雷的个数
    	while (count)
    	{
    		int x = rand() % row + 1;//0~8+1=1~9
    		int y = rand() % col + 1;
    		//判断这个位置有没有布置雷,
    		//没有雷为'0',有雷为'1'.
    		if (mine[x][y] == '0')
    		{
    			mine[x][y] = '1';
    			count--;//每布置一个地雷,计数器递减,
    			//直到count减为0跳出循环
    		}
    	}
    }
    int get_mine_count(char mine[ROWS][COLS], int x, int y)
    {
    	//非雷里面如果放的是数字0的话,
    	//假设你的坐标输入,
    	//得到的反馈是那个空格显示了1(周围8个坐标里面有一个是地雷),
    	//我们把这9个坐标加起来得到了数字1,即1个地雷,
    	//但实际上非雷放的是字符'0',
    	//我们介绍过字符转数字的方法—数字字符减去'0'得到对应的数字,
    	//我们就减去里面的8个坐标再×上字符'0'
    	//就能成功统计出地雷个数了
    	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 find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
    {
    	//怀疑雷是xx坐标
    	int x = 0;
    	int y = 0;
    	int win = 0;
    
    	while (win < row * col - EASY_COUNT)
    	{
    		printf("请输入要排查雷的坐标:>");
    		scanf("%d %d", &x, &y);
    		if (x >= 1 && x <= row && y >= 1 && y <= col)
    		{
    			//坐标被排查过
    			if (show[x][y] == '*')
    			{
    				if (mine[x][y] == '1')
    				{
    					printf("很遗憾,你被炸死了\n");
    					//每一次排完雷后重新打印棋盘
    					display_board(mine, ROW, COL);
    					break;
    				}
    				else
    				{
    					//统计mine数组里面有多少个雷,
    					//然后传到show数组里面去
    					int count = get_mine_count(mine, x, y);
    					//接收的是字符
    					//数字字符减去'0'得到对应的数字,
    					//所以反过来看对应的数字+'0'就能得到相应的字符
    					show[x][y] = count + '0';
    					display_board(show, ROW, COL);//重新打印棋盘
    					win++;
    				}
    			}
    			else
    			{
    				printf("该坐标已经被排查过了\n");
    			}
    		}
    		else
    		{
    			printf("坐标非法,请重新输入\n");
    		}
    	}
    	if (win == row * col - EASY_COUNT)
    	{
    		printf("恭喜你,排雷成功\n");
    		display_board(mine, ROW, COL);
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127

    play.c

    #include"game.h"
    void menu()
    {
    	printf("*********************************\n");
    	printf("********      1. play    ********\n");
    	printf("********      0. exit    ********\n");
    	printf("*********************************\n");
    }
    void game()
    {
    	//设计两个数组存储信息
    	char mine[ROW][COL] = { 0 };
    	char show[ROW][COL] = { 0 };
    	//初始化棋盘
    	//mine初始化为全‘0’
    	//show初始化为全‘*’
    	init_board(mine, ROWS, COLS, '0');
    	init_board(show, ROWS, COLS, '*');
    	//打印棋盘
    	//玩扫雷游戏的时候我们不需要打印mine数组的棋盘,
    	//所以我们将下面这一行代码屏蔽掉 
    	//display_board(mine, ROW, COL);
    	//布置好的雷显示了出来,
    	//所以我们将这一行代码屏蔽掉 
    	//display_board(show, ROW, COL);
    	//布置雷
    	set_mine(mine, ROW, COL);
    
    	//排雷
    	//display_board(mine, ROW, COL);//不能开透视模式
    	display_board(show, ROW, COL);
    
    	find_mine(mine, show, ROW, COL);
    }
    
    
    #include
    int main()
    {
    	int input = 0;
    	//设置随机数的生成器
    	srand((unsigned int)time(NULL));
    	//调用srand函数确定一个生成随机数的起始位置
    	//(srand函数里面有个变化值即可,因为括号里随机输入一个数,结果都不一样)
    	//srand函数需要一个unsigned int类型的变动的值。
    	//我们要产生一个随机值,而这个随机值的生成需要一个随机值
    	//我们似乎进入了一个先有鸡先有蛋的问题,
    	//这时就需要引入一个概念叫做时间戳
    	//时间戳表示一个时间,
    	//这个时间就是相对于1970年1月1日到现在的时刻我们经过了多少秒,
    	//然后将它转化为一个数字,
    	//其实这个数字指的是:
    	//电脑此时此刻运行这个代码的时间和计算机的起始时间的一个差值,
    	//单位为s,就是一个时间戳
    	//由于时间是不断变动的,它就可以满足我们的要求,
    	//time函数可以实现当前的这个时间戳,可以返回这个数字,
    	//time函数需要一个指针类型的参数,
    	//如果不想要那个参数,那我们就给它一个空指针.
    	//它的返回类型是time_t,需要强制类型转换为unsigned int
    	//让我们回到rand函数那里
    	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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    这里展开一片的代码我以后再补充,先给大家推送一个我觉得写的特别好的完整的扫雷程序的文章
    扫雷程序完整版

  • 相关阅读:
    什么是 eCPM?它与 CPM 有何不同?
    nginx参数调优能提升多少性能
    6. SpringBoot 整合 RabbitMQ
    设计模式-01 设计模式简介之分类
    C++日期和时间编程总结|程序性能优化基础
    Java的反射
    Linux 安装 Python
    报错解决:java.security.InvalidKeyException: Illegal key size(微信支付v3遇到的问题)
    ITSS认证系统运维范围定义
    设计模式:中介者模式
  • 原文地址:https://blog.csdn.net/yanghuagai2311/article/details/126016895