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

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

五、扫雷


0x00 游戏介绍

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

c77f0fa4ecb1f925e7e23ca3fd9c08a7_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

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


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>

🚩 代码运行结果如下

7631c06d8d1ec431a070f4d2696a895c_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


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"); // 分界线
}


🚩 运行结果如下

fd3fa09d06f8d19f814d8cdf16848fcc_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png



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--;
  }
  }
}

🚩 运行结果

dc2478032a9b2d7547f6943b26bd4adb_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png



0x06 排查雷

📚 思路:


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


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


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


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


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

eae1fba663b3eb8b39fe318405e729d1_20210525084810325.png


💬 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 代码运行

🚩 排查雷的坐标

e1c943cd996e6acd455568e18fb701cc_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


🚩 非法输入坐标

fbb595126478dad8a0b9a1261694a0f2_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


🚩 很遗憾,你被炸死了


2596bb953f2c1f61c070db3a45ed1470_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


相关文章
|
4天前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
28 4
|
30天前
|
存储 编译器 C语言
【c语言】数组
本文介绍了数组的基本概念及一维和二维数组的创建、初始化、使用方法及其在内存中的存储形式。一维数组通过下标访问元素,支持初始化和动态输入输出。二维数组则通过行和列的下标访问元素,同样支持初始化和动态输入输出。此外,还简要介绍了C99标准中的变长数组,允许在运行时根据变量创建数组,但不能初始化。
37 6
|
1月前
|
存储 人工智能 BI
C语言:数组的分类
C语言中的数组分为一维数组、多维数组和字符串数组。一维数组是最基本的形式,用于存储一系列相同类型的元素;多维数组则可以看作是一维数组的数组,常用于矩阵运算等场景;字符串数组则是以字符为元素的一维数组,专门用于处理文本数据。
|
1月前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
1月前
|
存储 C语言
C语言:一维数组的不初始化、部分初始化、完全初始化的不同点
C语言中一维数组的初始化有三种情况:不初始化时,数组元素的值是随机的;部分初始化时,未指定的元素会被自动赋值为0;完全初始化时,所有元素都被赋予了初始值。
|
1月前
|
存储 数据管理 编译器
揭秘C语言:高效数据管理之数组
揭秘C语言:高效数据管理之数组
|
1月前
|
C语言 C++
保姆式教学C语言——数组
保姆式教学C语言——数组
17 0
保姆式教学C语言——数组
|
1月前
|
C语言
C语言数组
C语言数组
19 0
|
1月前
|
存储 C语言 索引
c语言回顾-数组(全网最详细,哈哈哈) (下)
c语言回顾-数组(全网最详细,哈哈哈) (下)
44 0
|
1月前
|
存储 编译器 C语言
c语言回顾-数组(全网最详细,哈哈哈)(上)
c语言回顾-数组(全网最详细,哈哈哈)(上)
56 0