C语言实现扫雷游戏

简介: C语言实现扫雷游戏

引言

在这个数字化的时代,游戏已经成为我们生活中不可或缺的一部分。无论是复杂的3D大作,还是简单的桌面小游戏,它们都能带给我们无尽的乐趣和挑战。今天,我们要一起回到那个经典的桌面游戏时代,探索如何用C语言编写一个充满怀旧气息的扫雷小游戏。

一、游戏规则

游戏目标:

盘面上随机分布着一定数量的地雷。你的目标是避开这些地雷,打开其他所有格子。

地雷数量:

选择相应的难度后,电脑会在雷区随机布置一定数量的地雷。

排雷:输入需要排查的坐标。如果点击的是地雷,则游戏失败;如果点击的是非雷方格,会显示周围八个方格内地雷的数。

标记:在怀疑的方格上放置旗帜(本游戏中用$符号代替),标记为地雷。

游戏结束:

当所有非雷方格都被揭开,且所有地雷都被正确标记时,游戏胜利。
如果揭开了一个地雷,游戏失败。

二、设计思路

1. 游戏概述

首先,明确游戏的基本框架和玩法。扫雷游戏主要包括一个雷区、地雷的随机分布、玩家的点击操作以及游戏胜负的判定。

2. 数据结构设计

雷区表示:使用一个二维数组来表示雷区,每个元素对应一个方格。棋盘可操作的区域是9*9的二维字符数组,实际的棋盘要多出两行两列(防止越界,简化设计操作)。

棋盘有两个,一个用来埋雷,一个用来显示排查雷的情况

埋雷数组(逻辑层):这个数组存储了游戏的真实状态,即哪些位置埋有地雷,哪些位置是安全的。这个数组对玩家是不可见的,它用于游戏的内部逻辑处理。其中用‘1’表示有雷,‘0’表示无雷。

展示数组(视图层):这个数组用于向玩家展示游戏当前的状态。它包含了玩家已经点击的方格、标记的地雷以及显示的数字提示。其中‘*’表示待排查的地理,可进行标记操作和排雷操作,‘$’表示已标记的地理,盘面上的数字表示该位置周围一圈格子雷的数量。

3. 游戏流程设计

难度选择:开始游戏前可以选择难度,简单,一般,困难三个级别难度

初始化:初始化两个二维数组

生成雷区:随机布置地雷

显示盘面:打印展示数组供玩家操作

玩家操作:玩家可以选择排雷,标记或是删除标记

逻辑判断:根据玩家的点击,更新显示数组,并进行游戏胜负的判断。

游戏结束:当玩家触发地雷或成功标记所有地雷时,游戏结束。

4. 功能模块划分

难度模块:供玩家选择对应难度。

初始化模块:负责初始化雷区和显示数组。

布雷模块:随机在雷区布置地雷。

显示模块:根据玩家的操作更新显示数组,并打印当前雷区的状态。

标记模块:玩家可以在怀疑的地方做说标记。

胜负判定模块:判断游戏是否结束,并给出相应的提示。

5. 主要算法设计

布雷算法:使用随机数生成器来确定地雷的位置。

计算周围地雷数量:对于每个非雷方格,计算其周围八个方格内地雷的数量。

递归扫雷:当一个格子显示‘0’即周围没有雷时,进行递归扫雷,展开一片区域

三、游戏设计

1.菜单函数

首先,我们需要制作一个简易的游戏菜单,代码如下:

void Menu()
{
  printf("****************************\n");
  printf("*******    1.play    *******\n");
  printf("*******    0.exit    *******\n");
  printf("****************************\n");
  //玩家按1开始游戏,按0则结束游戏
}

2.主函数

主函数实现代码框架,用来控制按1开始游戏/按0退出游戏,并且多次进行直到玩家退出。

这里我们用switch来实现玩家的选择,用do...while循环语句保证游戏的多次进行。代码如下:

其中的srand函数功能以及解释请看上一篇博客中的介绍。代码如下:

int main()
{
  srand((unsigned int)time(NULL));//随机种子
  int option;
  do
  {
    system("cls");//用于清除缓冲区,后一次玩的时候清除前面记录
    Menu();
    printf("请做出你的选择:");
    scanf("%d", &option);
    switch (option)
    {
    case 1:
      system("cls");//清除缓冲区
      game();
      break;
    case 0:
      printf("游戏结束\n");
      break;
    default:
      printf("输入有误,请重新输入\n");
      system("pause");//用来暂停程序,按下后继续运行,即运行下面的清除缓冲区
      break;
    }
  } while (option);
}

现在完成了框架,接下来是各个模块代码的实现

3.选择难度函数

返回值为地理的数量,简单模式8个地雷,正常模式12个地雷,困难模式16个地雷。代码如下:

int SelectDiff() {//难度选择
  int difficulty;
    printf("请选择难度:\n");
    printf("1. 简单         8个雷\n");
    printf("2. 正常(默认)   12个雷\n");
    printf("3. 困难         16个雷\n");
    scanf("%d", &difficulty);
    switch (difficulty) {
    case 1:
      return MINE - 4;//简单模式8个雷
      break;
    case 2:
      return MINE;//正常模式12个雷
      break;
    case 3:
      return MINE + 4;//困难模式16个雷
      break;
    case 0:
      break;
    default:
      printf("输入有误,请重新选择\n");
      break;
    }
  return MINE;
}

4.初始化函数

该函数用来初始化mine数组和show数组。代码如下:

void InitBoard(char arr[ROWS][COLS], char set)//初始化棋盘
{
  int i, j;
  for (i = 0; i < ROWS; i++)
  {
    for (j = 0; j < ROWS; j++)
    {
      arr[i][j] = set;//初始化棋盘,mine数组中全部初始化为'0',show数组中全部初始化为'*'
    }
  }
}

5.布置地雷函数

利用生成的随机数,在棋盘上随机位置布置地雷。代码如下:

void SetMine(char arr[ROWS][COLS], int count)//布置地雷
{
  while (count)
  {
    int x = rand() % ROW + 1;//产生1~9的随机数
    int y = rand() % COL + 1;//产生1~9的随机数
 
    if (arr[x][y] == '0')
    {
      arr[x][y] = '1';
      count--;
    }
  }
}

6.打印函数

用来打印show数组供玩家在盘面上进行操作以及游戏结束时打印mine数组供玩家查看雷区位。代码如下:

void PrintBoard(char arr[ROWS][COLS])//打印棋盘
{
  int i, j;
  printf("\n=====Minesweeper=====\n");
  for (j = 0; j <= ROW; j++)
  {
    printf("%2d", j);//打印列标
  }
  printf("\n");
  for (i = 1; i <= ROW; i++)
  {
    printf("%2d", i);//打印行标
    for (j = 1; j <= COL; j++)
    {
      printf("%2c", arr[i][j]);
    }
    printf("\n");
  }
  printf("=====================\n");
}

7.计算雷数函数

玩家在该位置排雷后,若该位置没有雷,则计算周围地雷个数,展示在show数组的该位置上。代码如下:

int GetMineCount(char mine[ROWS][COLS], int x, int y)//计算周围雷的个数雷
{
  return (mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1]
    + mine[x][y - 1] + mine[x][y + 1]
    + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1]
    - 8 * '0');
}

8.递归排雷函数

当排查的地方周围无雷时,进行递归,自动深度排查周围区域。代码如下:

void DeepSweep(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)//递归连续排雷
{
  if (x == 0 || y == 0 || x == ROW + 1 || y == ROW + 1)
    return;
  if (show[x][y] != '*' && show[x][y] != '$')
    return;
  int count = GetMineCount(mine, x, y);
  if (count == 0)
  {
    show[x][y] = '0';
    DeepSweep(mine, show, x - 1, y - 1);
    DeepSweep(mine, show, x - 1, y);
    DeepSweep(mine, show, x - 1, y + 1);
    DeepSweep(mine, show, x, y - 1);
    DeepSweep(mine, show, x, y + 1);
    DeepSweep(mine, show, x + 1, y - 1);
    DeepSweep(mine, show, x + 1, y);
    DeepSweep(mine, show, x + 1, y + 1);
  }
  else
  {
    show[x][y] = count + '0';
  }
}

9.标记(删除标记)函数

供玩家在怀疑的地方标记为地雷,或者删除某一位置的标记,其中标记符号为$。代码如下:

void MarkMine(char show[ROWS][COLS], int x, int y) {
  if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
    if (show[x][y] == '*')
    {
      show[x][y] = '$';
    }
  }
}
 
void UnmarkMine(char show[ROWS][COLS], int x, int y) {
  if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
    if (show[x][y] == '$')
    {
      show[x][y] = '*';
    }
  }
}

10.操作函数

游戏中可供玩家进行排雷、标记、删除标记的选择,在玩家操作后更新展示show数组,并且根据玩家的一系列操作判断来玩家是胜利还是失败。代码如下:

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int count)
{
  int x, y;
  int win = 0;
  int flag = count;
  while (win < ROW * COL - MINE) {//排查的安全地方个数
    // 显示菜单
    printf("1. 排雷\n2. 标记雷(当前还可标记%d处)\n3. 删除标记\n0. 退出\n请选择操作:",flag); 
    int choice;
    scanf("%d", &choice);
 
    switch (choice) {
    case 1: // 排雷
      printf("输入你要排查的位置(输入坐标:行 列):");
      scanf("%d %d", &x, &y);
      if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
      {
        if (show[x][y] == '*' || show[x][y] == '$')
        {
          if (mine[x][y] == '1')
          {
            printf("很遗憾,你踩到雷了!\n");
            PrintBoard(mine);
            system("pause");//用来暂停程序,按下后继续运行
            break;
          }
          else
          {
            DeepSweep(mine, show, x, y);//递归排雷
            int count = GetMineCount(mine, x, y);
            show[x][y] = count + '0';
            system("cls");//清除缓冲区
            PrintBoard(show);
            win++;
          }
 
        }
        else
        {
          printf("该位置已被排除过,请重新输入!\n");
        }
      }
      break;
    case 2: // 标记雷
      if(flag>0)
      {
        printf("输入你要标记的位置(输入坐标:行 列):");
        scanf("%d %d", &x, &y);
        if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
          if (show[x][y] == '*')
          {
            system("cls");//清除缓冲区
            MarkMine(show, x, y);
            PrintBoard(show);
            flag--;
          }
          else
          {
            printf("该位置已被排查,无法标记!\n");
          }
        }
        else {
          printf("输入位置不合法,请重新输入\n");
        }
      }
      else
      {
        printf("标记达上限,无法再标记!");
      }
      break;
    case 3: // 删除标记
      printf("输入你要删除标记的位置(输入坐标:行 列):");
      scanf("%d %d", &x, &y);
      if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
        if (show[x][y] == '$')
        {
          system("cls");//清除缓冲区
          UnmarkMine(show, x, y);
          PrintBoard(show);
        }
        else {
          printf("该位置未被标记,无法删除!\n");
        }
 
      }
      else {
        printf("输入位置不合法,请重新输入\n");
      }
      break;
    case 0: // 退出
      return;
    default:
      printf("输入有误,请重新输入\n");
      break;
    }
  }
 
  if (win == ROW * COL - MINE) {//排查完所有非雷区游戏胜利
    printf("我嘞个雷!\n");
    printf("恭喜你已经排完了所有的雷!\n");
    PrintBoard(mine);
  }
}

11.游戏函数

即整合实现游戏运行的分模块。代码如下:

void game()
{
  int minecount = SelectDiff();//难度选择
  char mine[ROWS][COLS] = { 0 };//mine数组全部初始化为'0'
  char show[ROWS][COLS] = { 0 };//show数组中全部初始化为'*'
  InitBoard(mine, '0');
  InitBoard(show, '*');
  SetMine(mine, minecount);
  //PrintBoard(mine);雷区,取消注释可以作弊查看
  PrintBoard(show);
  FindMine(mine, show,minecount);
}

四、完整代码及运行效果图

完整源代码:

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9//9*9的棋盘
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define MINE 12//正常难度雷的数量
void Menu()
{
  printf("****************************\n");
  printf("*******    1.play    *******\n");
  printf("*******    0.exit    *******\n");
  printf("****************************\n");
  //玩家按1开始游戏,按0则结束游戏
}
int SelectDiff() {//难度选择
  int difficulty;
    printf("请选择难度:\n");
    printf("1. 简单         8个雷\n");
    printf("2. 正常(默认)   12个雷\n");
    printf("3. 困难         16个雷\n");
    scanf("%d", &difficulty);
    switch (difficulty) {
    case 1:
      return MINE - 4;//简单模式8个雷
      break;
    case 2:
      return MINE;//正常模式12个雷
      break;
    case 3:
      return MINE + 4;//困难模式16个雷
      break;
    case 0:
      break;
    default:
      printf("输入有误,请重新选择\n");
      break;
    }
  return MINE;
}
void InitBoard(char arr[ROWS][COLS], char set)//初始化棋盘
{
  int i, j;
  for (i = 0; i < ROWS; i++)
  {
    for (j = 0; j < ROWS; j++)
    {
      arr[i][j] = set;//初始化棋盘,mine数组中全部初始化为'0',show数组中全部初始化为'*'
    }
  }
}
void PrintBoard(char arr[ROWS][COLS])//打印棋盘
{
  int i, j;
  printf("\n=====Minesweeper=====\n");
  for (j = 0; j <= ROW; j++)
  {
    printf("%2d", j);//打印列标
  }
  printf("\n");
  for (i = 1; i <= ROW; i++)
  {
    printf("%2d", i);//打印行标
    for (j = 1; j <= COL; j++)
    {
      printf("%2c", arr[i][j]);
    }
    printf("\n");
  }
  printf("=====================\n");
}
void SetMine(char arr[ROWS][COLS], int count)//布置地雷
{
  while (count)
  {
    int x = rand() % ROW + 1;//产生1~9的随机数
    int y = rand() % COL + 1;//产生1~9的随机数
 
    if (arr[x][y] == '0')
    {
      arr[x][y] = '1';
      count--;
    }
  }
}
int GetMineCount(char mine[ROWS][COLS], int x, int y)//计算周围雷的个数雷
{
  return (mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1]
    + mine[x][y - 1] + mine[x][y + 1]
    + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1]
    - 8 * '0');
}
void DeepSweep(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y)//递归连续排雷
{
  if (x == 0 || y == 0 || x == ROW + 1 || y == ROW + 1)
    return;
  if (show[x][y] != '*' && show[x][y] != '$')
    return;
  int count = GetMineCount(mine, x, y);
  if (count == 0)
  {
    show[x][y] = '0';
    DeepSweep(mine, show, x - 1, y - 1);
    DeepSweep(mine, show, x - 1, y);
    DeepSweep(mine, show, x - 1, y + 1);
    DeepSweep(mine, show, x, y - 1);
    DeepSweep(mine, show, x, y + 1);
    DeepSweep(mine, show, x + 1, y - 1);
    DeepSweep(mine, show, x + 1, y);
    DeepSweep(mine, show, x + 1, y + 1);
  }
  else
  {
    show[x][y] = count + '0';
  }
}
void MarkMine(char show[ROWS][COLS], int x, int y) {
  if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
    if (show[x][y] == '*')
    {
      show[x][y] = '$';
    }
  }
}
 
void UnmarkMine(char show[ROWS][COLS], int x, int y) {
  if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
    if (show[x][y] == '$')
    {
      show[x][y] = '*';
    }
  }
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int count)
{
  int x, y;
  int win = 0;
  int flag = count;
  while (win < ROW * COL - MINE) {//排查的安全地方个数
    // 显示菜单
    printf("1. 排雷\n2. 标记雷(当前还可标记%d处)\n3. 删除标记\n0. 退出\n请选择操作:",flag); 
    int choice;
    scanf("%d", &choice);
 
    switch (choice) {
    case 1: // 排雷
      printf("输入你要排查的位置(输入坐标:行 列):");
      scanf("%d %d", &x, &y);
      if (x >= 1 && x <= ROW && y >= 1 && y <= COL)
      {
        if (show[x][y] == '*' || show[x][y] == '$')
        {
          if (mine[x][y] == '1')
          {
            printf("很遗憾,你踩到雷了!\n");
            PrintBoard(mine);
            system("pause");//用来暂停程序,按下后继续运行
            break;
          }
          else
          {
            DeepSweep(mine, show, x, y);//递归排雷
            int count = GetMineCount(mine, x, y);
            show[x][y] = count + '0';
            system("cls");//清除缓冲区
            PrintBoard(show);
            win++;
          }
 
        }
        else
        {
          printf("该位置已被排除过,请重新输入!\n");
        }
      }
      break;
    case 2: // 标记雷
      if(flag>0)
      {
        printf("输入你要标记的位置(输入坐标:行 列):");
        scanf("%d %d", &x, &y);
        if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
          if (show[x][y] == '*')
          {
            system("cls");//清除缓冲区
            MarkMine(show, x, y);
            PrintBoard(show);
            flag--;
          }
          else
          {
            printf("该位置已被排查,无法标记!\n");
          }
        }
        else {
          printf("输入位置不合法,请重新输入\n");
        }
      }
      else
      {
        printf("标记达上限,无法再标记!");
      }
      break;
    case 3: // 删除标记
      printf("输入你要删除标记的位置(输入坐标:行 列):");
      scanf("%d %d", &x, &y);
      if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
        if (show[x][y] == '$')
        {
          system("cls");//清除缓冲区
          UnmarkMine(show, x, y);
          PrintBoard(show);
        }
        else {
          printf("该位置未被标记,无法删除!\n");
        }
 
      }
      else {
        printf("输入位置不合法,请重新输入\n");
      }
      break;
    case 0: // 退出
      return;
    default:
      printf("输入有误,请重新输入\n");
      break;
    }
  }
 
  if (win == ROW * COL - MINE) {//排查完所有非雷区游戏胜利
    printf("我嘞个雷!\n");
    printf("恭喜你已经排完了所有的雷!\n");
    PrintBoard(mine);
  }
}
 
void game()
{
  int minecount = SelectDiff();//难度选择
  char mine[ROWS][COLS] = { 0 };//mine数组全部初始化为'0'
  char show[ROWS][COLS] = { 0 };//show数组中全部初始化为'*'
  InitBoard(mine, '0');
  InitBoard(show, '*');
  SetMine(mine, minecount);
  //PrintBoard(mine);雷区,取消注释可以作弊查看
  PrintBoard(show);
  FindMine(mine, show,minecount);
}
int main()
{
  srand((unsigned int)time(NULL));//随机种子
  int option;
  do
  {
    system("cls");//用于清除缓冲区,后一次玩的时候清除前面记录
    Menu();
    printf("请做出你的选择:");
    scanf("%d", &option);
    switch (option)
    {
    case 1:
      system("cls");//清除缓冲区
      game();
      break;
    case 0:
      printf("游戏结束\n");
      break;
    default:
      printf("输入有误,请重新输入\n");
      system("pause");//用来暂停程序,按下后继续运行,即运行下面的清除缓冲区
      break;
    }
  } while (option);
}

运行效果图:

总结

通过这次C语言扫雷小游戏的开发,我们不仅重温了一个经典的桌面游戏,而且在实践中加深了对C语言编程的理解。从设计思路到具体实现,每一步都是对逻辑思维和编程技能的锻炼。在这个过程中,我们学到了如何利用二维数组管理复杂的游戏状态,如何处理用户输入,以及如何在游戏中实现递归和条件判断等高级功能。这次实践不仅让我们体验了从零到一构建游戏的成就感,也为未来的编程学习奠定了坚实的基础。扫雷游戏虽小,但它背后的编程智慧无穷,让我们继续探索,创造更多有趣的作品。


相关文章
|
2月前
|
C语言
扫雷游戏(用C语言实现)
扫雷游戏(用C语言实现)
104 0
|
21天前
|
C语言 Windows
C语言课设项目之2048游戏源码
C语言课设项目之2048游戏源码,可作为课程设计项目参考,代码有详细的注释,另外编译可运行文件也已经打包,windows电脑双击即可运行效果
31 1
|
2月前
|
编译器 C语言
猜数字游戏实现#C语言
猜数字游戏实现#C语言
87 1
|
2月前
|
存储 C语言
揭秘C语言:泊舟的猜数字游戏
揭秘C语言:泊舟的猜数字游戏
|
2月前
|
C语言
初学者指南:使用C语言实现简易版扫雷游戏
初学者指南:使用C语言实现简易版扫雷游戏
38 0
|
2月前
|
C语言
C语言扫雷游戏(详解)
C语言扫雷游戏(详解)
40 0
|
2月前
|
程序员 C语言
初识C语言之三子棋游戏
初识C语言之三子棋游戏
36 0
|
2月前
|
C语言
初识C语言3——函数(以猜数字游戏为例)
初识C语言3——函数(以猜数字游戏为例)
71 0
|
2月前
|
存储 编译器 C语言
【C语言篇】数组和函数的实践:扫雷游戏(附源码)
【C语言篇】数组和函数的实践:扫雷游戏(附源码)
38 0
【C语言】函数递归实现——扫雷小游戏(详细步骤)
【C语言】函数递归实现——扫雷小游戏(详细步骤)
【C语言】函数递归实现——扫雷小游戏(详细步骤)