基于C语言扫雷游戏的设计与实现

简介: 基于C语言扫雷游戏的设计与实现


基于C语言扫雷游戏的设计与实现

作者信息

专业:软件工程


摘要

根据对Windows自带扫雷游戏规则的了解。我们可以模拟实现扫雷游戏。首先通过二维数组创建雷盘A和雷盘B。A盘主要用于计算机的循环对其遍历,判断其位置是否是雷,并计算该非雷处位置周围相邻的八个位置有多少个雷,或用递归对雷区进行展开,接着用条件选择语句判断最后是否能赢,是给开发人员看的。B盘主要用于显示从A盘获得的信息,即显示非雷处周围八个位置有多少个雷,是给玩家看的。然后通过A盘,进行埋雷,扫雷,判断输赢。通过B盘,进行显示游戏过程和结果直至游戏结束。


关键词

C语言 函数 数组


正文

1 引言

伴随着信息技术的快速发展,近年来,人们的生活已经离不开计算机。生活娱乐几乎都是在计算机上进行的。其中的扫雷游戏就是之一。扫雷游戏是微软公司在1992年在windows系统上发布的一款益智类小游戏,直到今天这款小游戏依然存在,可见此款游戏的成功。本文将用Visual Studio 2019作为开发工具来模拟实现扫雷游戏。

经过大一第一学期的学习,我们对C语言的理论及其相关知识有了一定的认识和了解。本文可以把我们所学的理论知识和实践动手能力相结合,另外也可以对我所学过的知识进行复习巩固。通过探索windows的扫雷小游戏,我们可以巧妙地和这学期学过的C语言相结合起来模拟实现无界面化简易版的扫雷小游戏。


2 相关工作

  • 准备Visual Studio 2019开发工具,了解数据类型,会使用选择语句、循环语句、函数、数组等内容。了解ASCII码值、随机数的生成。了解二维坐标相关的数学知识。

3 本文方法

3.1玩家游戏思路

进入菜单,进行选择是否进入,或退出游戏。

进入游戏模块,输入坐标进行扫雷。扫到雷,结束游戏,没扫到雷,进行标记该位置周围相邻八个位置有多少个雷,直到玩家失败或者赢掉此游戏。

3.2游戏构思细节

由于本游戏玩家看到的界面是由9* 9大小的方格构成并用字符* 对B雷盘进行覆盖的画面,但当我们计算一个非雷的位置周围八个相邻位置是否有雷时,这样就会导致数组越界,为了防止越界,我们将雷盘设置为11乘11。因为是字符* 覆盖,所以对B雷盘创建用的是字符类型的二维数组,同时为了方便实现一次声明,两次调用等操作。我们把a雷盘也用字符类型的二维数组进行创建。

3.3 游戏设计实现

3.3.1 游戏分三个模块,test.c,game.c和game.h。

(1)以模块化的函数思想进行设计,使游戏整体思路更加清晰。首先打开Visual Studio 2019,创建扫雷游戏的空项目,创建test.c源文件,和game.c源文件,一个game.h头文件。c语言中头文件中一般定义了函数的声明、结构体的定义、宏定义。(常量和全局变量最好放到源文件中)。C语言源文件中我们放置一些函数。来将游戏的具体实现。 源文件(test.c)里面放主函数和游戏的整体功能。

3.3.2 在头文件中进行准备工作

(1)#define进行宏定义,进行 InitMine,SetMine,DisplayMine,FindMine等函数的声明。

//宏定义便于后期的更改
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROWS ROW + 2
//设置11*11的格子防止数组越界。
#define COLS COL + 2
#define ROW 9
#define COL 9
//以下是对函数的声明
void InitMine(char mine[ROWS][COLS], int row, int col, char set);
void SetMine(char mine[ROWS][COLS], int row, int col);
void DisplayMine(char show[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//设置雷的个数
#define minecount 10

3.3.3 test.c中游戏大致功能的实现

(1)生成菜单栏

对于玩家来说,游戏的开始之前需要一个菜单栏进行选择游戏的开始或者退出,这时候就要用do语句生成菜单栏,并在其中使用switch语句进行选择进入或退出游戏。

test()
{
  //用rand()函数之前需要先用srand函数。
  srand((unsigned int)time(NULL));
  int n = 0;
  //游戏进去之后需要先有个菜单所以用上do语句
  do
  {
    //打印菜单
    menu();
    scanf("%d", &n);
    switch (n)
    {
    case 1:
      game();
      break;
    case 0:
      printf("退出游戏\n");
      break;
    default:
      printf("请重新选择\n");
      break;
    }
  } while (n);
}
//程序的入口
int main()
{
  test();
  return 0;
}

(2)调用game函数

我们将A盘有mine数组表示,B盘用show数组表示。

在switch中调用game函数进行char mine[ROWS][COLS] = { 0 };char show[ROWS][COLS] = { 0 };等数组的创建。用InitMine(mine, ROWS, COLS, ‘0’);InitMine(show, ROWS, COLS, ’ * );进行雷盘的初始化。我们设置雷为字符1,非雷为字符0。首先,把A盘初始化为非0,即全都不是雷,把B盘初始化为*号。接着用SetMine(mine, ROW, COL);DisplayMine(show, ROW, COL)运用随机数生成函数,向A盘随机布置雷;//用B盘进行展示雷的信息和结果,接着用FindMine(mine, show, ROW, COL);进行排雷

game()
{
  system("cls");//对画面进行清屏,使页面简洁。
  char mine[ROWS][COLS] = { 0 };//创建11*11数组
  char show[ROWS][COLS] = { 0 };
  //第一次传mine数组名
  InitMine(mine, ROWS, COLS, '0');
  //第二次传show数组名
  InitMine(show, ROWS, COLS, '*');
  //在A雷盘布置雷,所以需要传mine数组名
  SetMine(mine, ROW, COL);
  //用B盘进行展示雷的信息和结果
  DisplayMine(show, ROW, COL);
  //进行排雷
  FindMine(mine, show, ROW, COL);
}

3.3.4game.c中功能具体实现

(1)InitMine运用双重for循环,初始化棋盘。

(2)SetMine运用随机数的生成,rand函数()函数,布置雷。

(3)DisplayMine用双重for循环遍历,展示棋盘

(4)FindMine用while循环和if的嵌套,期间关键在于调用SpreadMine函数进行递归,再调用is_win函数进行判断输赢。


关键部分代码(递归)

void SpreadMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
  //调用CountMine函数进行查找周围八个位置雷的个数。
  int count = CountMine(mine, x, y);
  if (0 == count)
  {
    show[x][y] = ' ';
    if (show[x][y + 1] == '*')
      SpreadMine(mine, show, x, y + 1);
    if (show[x][y - 1] == '*')
      SpreadMine(mine, show, x, y - 1);
    if (show[x + 1][y] == '*')
      SpreadMine(mine, show, x + 1, y);
    if (show[x + 1][y - 1] == '*')
      SpreadMine(mine, show, x + 1, y - 1);
    if (show[x + 1][y + 1] == '*')
      SpreadMine(mine, show, x + 1, y + 1);
    if (show[x - 1][y] == '*')
      SpreadMine(mine, show, x - 1, y);
    if (show[x - 1][y + 1] == '*')
      SpreadMine(mine, show, x - 1, y + 1);
    if (show[x - 1][y - 1] == '*')
      SpreadMine(mine, show, x - 1, y - 1);
  }
  else
  {
    //如果该位置是雷,则将雷的个数显示在此位置
    show[x][y] = count + '0';
    //由于‘0’的的ASCII码值为48,‘1’的ASCII码值是49.
    // 所以
    //1 + '0' = '1'
    //2 + '0' = '2'
  }
}

4 结果与分析

本文实现了扫雷游戏的基本功能,根据提示,输入坐标从而实现扫雷。如果踩到雷就会退出游戏。如果剩下的*号等于雷数,则赢得游戏。如果输入坐标不对,则重新输入。最后通过递归实现了扫雷一片展开的功能。待有提高的是实现图形界面化和用鼠标操作界面可以学习EasyX来进一步优化。

5 总结

通过对此扫雷游戏的设计和实现,我们回顾了C语言的大半部分知识并对其中的细节进行了剖析。学习了C语言编程风格,了解了一些较常用的知识。我们对二维数组的应用,函数的声明,定义,和调用,以及if,switch选择语句和for,do,while循环语句有了更深刻的理解,提升了我们的逻辑思维能力。同时对ASCII码,随机数生成的应用也有了一些了解。另外,也提升了我们的模块化思维能力,比如我们用三个模块来构建此游戏。并且游戏中每个功能的实现都是由函数模块构成的。


GitHub原码链接:https://github.com/CaoCoder/C_code



参考文献

[1]里科. C和指针[M]. 人民邮电出版社, 2008.

[2]Andrew Koenig. C陷阱与缺陷[M]. 人民邮电出版社, 2002.

[3]RANDALE.BRYANT, DAVIDR.O‘HALLARON. 深入理解计算机系统[M]. 机械工业出版社, 2011.

[4]明解C语言,入门篇.第2版[M]. 人民邮电出版社, 2015.

[5]Code_Cao.扫雷1.0(递归实现)[EB/OL].[2021-11-13].

https://blog.csdn.net/qq2466200050/article/details/121300913

[6] Eli Bendersky.What does “static” mean in C? [EB/OL].[2009-02-21].

https://stackoverflow.com/questions/572547/what-does-static-mean-in-c


整体代码

  • 头文件
//宏定义便于后期的更改
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROWS ROW + 2
//设置11*11的格子防止数组越界。
#define COLS COL + 2
#define ROW 9
#define COL 9
//以下是对函数的声明
void InitMine(char mine[ROWS][COLS], int row, int col, char set);
void SetMine(char mine[ROWS][COLS], int row, int col);
void DisplayMine(char show[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//设置雷的个数
#define minecount 10

源文件

#define _CRT_SECURE_NO_WARNINGS
#include "12_9game.h"//引用自己创建的头文件用双引号
//对9*9雷区遍历查*的数量和minecount进行比较。
int is_win(char show[ROWS][COLS], int row, int col)
{
  int i = 0;
  int j = 0;
  int c = 0;
  for (i = 1; i <= row; i++)
  {
    for (j = 1; j <= col; j++)
    {
      if ('*' == show[i][j])
      {
        c++;
      }
    }
  }
  return c;
}
//用递归,进行实现一片展开的功能
void SpreadMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)
{
  //调用CountMine函数进行查找周围八个位置雷的个数。
  int count = CountMine(mine, x, y);
  if (0 == count)
  {
    show[x][y] = ' ';
    if (show[x][y + 1] == '*')
      SpreadMine(mine, show, x, y + 1);
    if (show[x][y - 1] == '*')
      SpreadMine(mine, show, x, y - 1);
    if (show[x + 1][y] == '*')
      SpreadMine(mine, show, x + 1, y);
    if (show[x + 1][y - 1] == '*')
      SpreadMine(mine, show, x + 1, y - 1);
    if (show[x + 1][y + 1] == '*')
      SpreadMine(mine, show, x + 1, y + 1);
    if (show[x - 1][y] == '*')
      SpreadMine(mine, show, x - 1, y);
    if (show[x - 1][y + 1] == '*')
      SpreadMine(mine, show, x - 1, y + 1);
    if (show[x - 1][y - 1] == '*')
      SpreadMine(mine, show, x - 1, y - 1);
  }
  else
  {
    //如果该位置是雷,则将雷的个数显示在此位置
    show[x][y] = count + '0';
    //由于‘0’的的ASCII码值为48,‘1’的ASCII码值是49.
    // 所以
    //1 + '0' = '1'
    //2 + '0' = '2'
  }
}
//查找非雷位置周围八个位置有多少雷
int CountMine(char mine[ROWS][COLS], int x, int y)
{
  return  (mine[x - 1][y + 1] + mine[x][y + 1]
    + mine[x + 1][y + 1] + mine[x - 1][y]
    + 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)
{
  int x = 0;
  int y = 0;
  int c = row * col - minecount;
  while (1)
  {
    printf("请输入坐标(x,y):");
    scanf("%d%d", &x, &y);
    //将输入的坐标限制在9*9的雷区
    if (x <= row && x >= 1 && y <= col && y >= 1)
    {
      if (mine[x][y] == '0')
      {
        SpreadMine(mine, show, x, y);
        system("cls");
        DisplayMine(show, ROW, COL);
        if (is_win(show, ROW, COL) == minecount)
        {
          printf("恭喜你赢了!\n");
          DisplayMine(mine, ROW, COL);
          break;
        }
      }
      else
      {
        printf("很遗憾你被炸死了~\n");
        DisplayMine(mine, ROW, COL);
        break;
      }
    }
    else
    {
      printf("输入坐标越界,请重新输入:");
    }
  }
}
//初始化棋盘,一个函数,两次调用。
void InitMine(char mine[ROWS][COLS], int row, int col, char set)
{
  int i = 0;
  int j = 0;
  for (i = 0; i < row; i++)
  {
    for (j = 0; j < col; j++)
    {
      mine[i][j] = set;
    }
  }
}
//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col)
{
  int c = 0;
  while (c != minecount)
  {
    int x = rand() % row + 1;
    //用rand()函数对9求余得到0~9这十个数,再加一就得到1~10.
    int y = rand() % col + 1;
    if (mine[x][y] == '0')
    {
      //如果此位置为‘0’,即非雷
      //则设置为‘1’,即雷
      mine[x][y] = '1';
      c++;
    }
  }
}
//运用双重循环打印9*9的B雷区
void DisplayMine(char show[ROWS][COLS], int row, int col)
{
  int i = 0;
  int j = 0;
  for (i = 0; i <= row; i++)
  {
    printf("%d ", i);
    for (j = 1; j <= col; j++)
    {
      if (0 == i)
      {
        printf("%d ", j);
      }
      else
      {
        printf("%c ", show[i][j]);
      }
    }
    printf("\n");
  }
}

源文件

#define _CRT_SECURE_NO_WARNINGS
#include "12_9game.h"
game()
{
  system("cls");//对画面进行清屏,使页面简洁。
  char mine[ROWS][COLS] = { 0 };//创建11*11数组
  char show[ROWS][COLS] = { 0 };
  //第一次传mine数组名
  InitMine(mine, ROWS, COLS, '0');
  //第二次传show数组名
  InitMine(show, ROWS, COLS, '*');
  //在A雷盘布置雷,所以需要传mine数组名
  SetMine(mine, ROW, COL);
  //用B盘进行展示雷的信息和结果
  DisplayMine(show, ROW, COL);
  //进行排雷
  FindMine(mine, show, ROW, COL);
}
menu()
{
  printf("┏━━━━━━┓━━━━━━━━━━━━━━━━━━━━┓━━━━━━━━━━━┓\n");
  printf("┣━━━━━━┫━━━━━━1.PLAY━━━━━━━━┫━━━━━━━━━━━┫\n");
  printf("┣━━━━━━┫━━━━━━1.EXIT━━━━━━━━┫━━━━━━━━━━━┫\n");
  printf("┗━━━━━━┛━━━━━━━━━━━━━━━━━━━━┛━━━━━━━━━━━┛\n");
}
test()
{
  //用rand()函数之前需要先用srand函数。
  srand((unsigned int)time(NULL));
  int n = 0;
  //游戏进去之后需要先有个菜单所以用上do语句
  do
  {
    //打印菜单
    menu();
    scanf("%d", &n);
    switch (n)
    {
    case 1:
      game();
      break;
    case 0:
      printf("退出游戏\n");
      break;
    default:
      printf("请重新选择\n");
      break;
    }
  } while (n);
}
//程序的入口
int main()
{
  test();
  return 0;
}
相关文章
|
2月前
|
机器学习/深度学习 C语言
九/十:《初学C语言》— 扫雷游戏实现和函数递归基础
【8月更文挑战第5天】本篇文章用C语言采用多文件编写实现了一个基础的扫雷游戏(附源码),并讲解了关于函数递归的基础概念及其相对应的习题练习(附源码)
36 1
九/十:《初学C语言》— 扫雷游戏实现和函数递归基础
|
3月前
|
存储 C语言 开发者
C语言实战 | Flappy Bird游戏
【7月更文挑战第4天】Flappy Bird是由越南开发者制作的简单却极具挑战性的游戏,玩家需控制小鸟穿越水管障碍。游戏涉及角色初始化、显示和更新。小鸟和水管结构体存储数据,使用变量和数组。初始化小鸟和水管,显示背景、小鸟和水管,更新小鸟位置及碰撞检测。代码示例展示了小鸟和水管的状态管理,当小鸟与管道碰撞或触地时,游戏结束。游戏的成功在于其独特的虐心体验。
54 0
C语言实战 | Flappy Bird游戏
|
13天前
|
存储 安全 算法
C 语言——实现扫雷小游戏
本文介绍了使用二维数组创建棋盘并实现扫雷游戏的方法。首先,通过初始化数组创建一个9x9的棋盘,并添加行列标识以便操作。接着,利用随机数在棋盘上布置雷。最后,通过判断玩家输入的坐标来实现扫雷功能,包括显示雷的数量和处理游戏胜利或失败的情况。文中提供了完整的代码实现。
35 1
C 语言——实现扫雷小游戏
|
2月前
|
算法 编译器 C语言
【C语言篇】猜数字游戏(赋源码)
rand函数会返回⼀个伪随机数,这个随机数的范围是在0~RAND_MAX之间,这个RAND_MAX的⼤⼩是依赖编译器上实现的,但是⼤部分编译器上是32767。
|
2月前
|
C语言
扫雷(C语言)
扫雷(C语言)
37 4
|
3月前
|
存储 编译器 C语言
|
3月前
|
存储 编译器 C语言
C语言实战 | “贪吃蛇”游戏
【7月更文挑战第5天】在C语言实战中,本文档介绍了如何构建一个简单的“贪吃蛇”游戏。游戏的核心是控制蛇移动并增长,当吃掉食物时,蛇的身体变长。数据结构使用固定大小的数组表示蛇的位置,变量存储食物位置和蛇的长度。初始化后,利用非阻塞式`getKey()`函数实现WASD键盘控制蛇的运动方向。虽然蛇的边界检测和吃食物后的增长尚未详细说明,但提到了这些问题作为练习留给读者解决,并预告将在后续章节讨论模块化编程以简化复杂代码。
82 0
C语言实战 | “贪吃蛇”游戏
|
3月前
|
存储 数据管理 C语言
C语言实战 | 使用链表完成“贪吃蛇”游戏
【7月更文挑战第1天】整体思维,即系统思维,强调以整体视角理解事物。在编程中,结构体体现这种思想,将相关变量打包处理。示例展示了如何用链表而非数组实现“贪吃蛇”游戏,链表提供了更灵活的动态数据管理。一系列代码图片详细描绘了链表结构体在游戏中的应用,包括节点定义、移动、碰撞检测等,凸显了使用链表的优势和代码的清晰组织。
36 0
C语言实战 | 使用链表完成“贪吃蛇”游戏
|
4月前
|
存储 C语言
C语言实战 | “贪吃蛇”游戏重构
在程序设计中,模块化思维至关重要,尤其对于复杂项目,它帮助分解任务,便于团队协作。以“贪吃蛇”游戏为例,游戏涉及两个角色:蛇和食物。使用数组存储蛇的位置,变量存储食物位置。游戏流程分为初始化、显示和更新数据。初始化时,食物位置随机,蛇的位置根据数组设定。显示数据则根据这些信息在屏幕上呈现角色。更新数据时,处理蛇的移动和增长以及食物的生成和消失。类似地,通过模块化方法可开发“打砖块”游戏,涉及球、球拍和砖墙,每个角色都有相应数据结构和更新逻辑。通过这种方式,游戏开发就像搭建积木,遵循框架逐步实现。
62 0
C语言实战 | “贪吃蛇”游戏重构
|
4月前
|
C语言
【海贼王编程冒险 - C语言海上篇】怎样用C语言实现简单的扫雷游戏?
【海贼王编程冒险 - C语言海上篇】怎样用C语言实现简单的扫雷游戏?
24 1