用C++实现扫雷游戏

扫雷游戏的游戏规则如下:

ce3b03cebac0465990471973f1ae0a1e.png

 每次排查一格,直到将所有的非雷排除,则游戏胜利。若中途扫到雷,则游戏失败。游戏过程中可以将玩家认为是雷的地方进行标记。排除时如果排查的位置周围的八个位置都不是雷则自动展开。


实现思路

先打印一个开始游戏的菜单,然后定义两个二维数组,一个用来存放雷的信息,另一个用来向玩家展示。然后定义一下雷的个数。然后对两个数组进行初始化操作。接下来写一个打印函数,用来打印棋盘。再写一个函数用来设置雷的位置。最后写一个函数来实现扫雷的操作。


一、引用头文件和定义宏

        #include <iostream> 为C++头文件

        #include <stdlib.h>#include <wime.h> 是为了用rand();生成随机数

        #include <windows.h> 是为了用Sleep();函数

        using namespace std; 为引用命名空间

        #define ROW 9#define COL 9   定义了玩家能看到的棋盘的长和宽

        #define ROWS 11  和  #define COLS 11   定义了程序员操作需要的棋盘大小,在后续操作中需要在玩家的棋盘外多放一圈数据以便于遍历某坐标的一周数据(是否有雷,有几个雷)

        #define MINE 10  定义了雷的数量

        五个#define在开头定义,便于后续修改,这样的话想改棋盘大小和雷的个数只要在开头改一下数字就可以了

#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <windows.h>

using namespace std;

//定义棋盘的长和宽
#define ROW 9
#define COL 9

//定义比棋盘大一圈的长和宽,便于实现一系列操作
#define ROWS 11
#define COLS 11

//定义雷的个数
#define MINE 10

二、打印一个开始游戏的菜单

        在最开始先打印出游戏菜单给玩家展示。这里写了一个函数,然后就是用了四个cout。

void menu()

{

 cout << "*********************" << endl;

 cout << "****** 1.play *****" << endl;

 cout << "****** 0.exit *****" << endl;

 cout << "*********************" << endl;

}

        代码及运行结果展示(因为我用了system("color fd");改变了字体和背景的颜色,所以这里是白底紫字)

三、定义两个数组和雷的个数

        定义两个二维数组一个用来存放雷的信息,一个用来给玩家展示。因为两个数组都是需要程序员进行操作的,而不是给玩家展示的(后续会写一个打印函数,打印函数会在数组中挑选出我们想给玩家看的部分单独打印),所以这里两个函数就都定义成了[ROWS][COLS]便于程序员操作。然后定义一个mine来存放雷的个数,便于后续的调用。

    //定义放雷的棋盘
    char mine_board[ROWS][COLS] = { 0 };

    //定义对玩家展示的棋盘
    char show_board[ROWS][COLS] = { 0 };

    //定义雷的个数
    int mine = MINE;

四、初始化两个数组

        这里写了一个函数用来初始化存放雷的棋盘(因为展示的棋盘不能全部初始化为某符号,在后续写展开一片的函数时会出现问题,所以这里多写了一个函数,分别来初始化两个数组)。

        函数内部嵌套了两个for循环来实现打印棋盘,打印范围为0~rows和0~cols,将整个数组全部填满。

        这里写了一个打印函数将初始化后的存放雷的棋盘打印出来给大家看一下。

        下面是初始化存放雷的棋盘的代码。

//初始化雷的棋盘

void init_mine_board(char board[ROWS][COLS], int rows, int cols, char symbol)

{

         int i = 0;

         int j = 0;

         for (i = 0; i <= rows; i++)

         {

                  for (j = 0; j <= cols; j++)

                  {

                           board[i][j] = symbol;

                  }

         }

 }


        初始化展示棋盘的函数与初始化存放雷的棋盘大同小异,只是这里的 i 和 j 的范围变成了1~row和1~col,只初始化玩家能看到的部分就可以了。

        为了便于观看,我先用存放雷的初始化函数将展示棋盘初始化一下,然后再将展示棋盘初始化,这样展示棋盘的初始化函数覆盖不到的地方会显示为 ‘0’ ,我这里用 # 来初始化展示棋盘。

        下面是运行出来的展示。

        下面是展示棋盘的初始化函数的代码。


//初始化展示棋盘

void init_show_board(char show_board[ROWS][COLS], int row, int col, char symbol)

{

 int i = 0;

 int j = 0;

 for (i = 1; i <= row; i++)

 {

  for (j = 1; j <= col; j++)

  {

   show_board[i][j] = symbol;

  }

 }

}

五、实现打印功能

        这里写了一个函数用于打印棋盘。

        首先用一个for循环实现分别打印每一行。

for (i = 0; i <= row; i++)
{
}

        在这个for循环中用一个if函数来判定是否为第一行,如果是第一行,则打印 j 作为纵坐标。两边的 ' ' 是为了美观。如果改变棋盘大小使之超过10之后会导致两位数与一位数的坐标显示出现问题,所以多写了个if语句来判断列数是否大于等于10。

 if (i == 0)
        {
            for (j = 0; j <= col; j++)
            {
                if (j < 10)
                {
                    cout << ' ' << ' ' << j << ' ' << ' ';
                }
                else
                {
                    cout << ' ' << ' ' << j << ' ';
                }
                if (j != col)
                {
                    cout << '|';
                }
            }
        }

        接下来是下边的 if (j != col)  语句是为了在除了第一列和最后一列加上 | ,为了美观,便于玩家观看。

if (j != col)
                {
                    cout << '|';
                }

        接下来是非第一行的打印。依旧是用一个for循环来循环打印信息。在for循环中用了一个if语句来判断是否为第一列,如果时第一列,则打印 i ,不是第一列,则打印棋盘信息。' ' 与 '|' 的作用与上文所讲一样。

        最后的cout << endl;是在每打印完一行数据之后进行换行操作。

        else
        {
            for (j = 0; j <= col; j++)
            {
                if (j == 0)
                {
                    if (i < 10)
                    {
                        cout << ' ' << ' ' << i << ' ' << ' ' << '|';
                    }
                    else
                    {
                        cout << ' ' << ' ' << i << ' ' << '|';
                    }
                }
                else
                {
                    cout << ' ' << ' ' << board[i][j] << ' ' << ' ';
                    if (j != col)
                        cout << '|';
                }
            }
        }
        cout << endl;

        为了美观,接下来在每两行之间打印出横线来进行分割。在最后进行一次换行操作,便于接下来的打印。

 for (j = 0; j <= col; j++)
        {
            cout << '-' << '-' << '-' << '-' << '-' << '-';
        }
        cout << endl;

        在棋盘打印完之后,打印一下未被标记的雷的数量,使玩家在游戏中的体验更好(标记在后面部分会写)。

cout << "未标记雷的个数为 -> " << mine << endl;

        接下来是打印展示(因为只需要打印展示棋盘给玩家看,所以这里只放了展示棋盘的打印展示)。

        下面是打印函数的代码。

//打印棋盘
void displayboard(char board[ROWS][COLS], int row, int col, int mine)
{
    int i = 0;
    int j = 0;
    for (i = 0; i <= row; i++)
    {
        if (i == 0)
        {
            for (j = 0; j <= col; j++)
            {
                if (j < 10)
                {
                    cout << ' ' << ' ' << j << ' ' << ' ';
                }
                else
                {
                    cout << ' ' << ' ' << j << ' ';
                }
                if (j != col)
                {
                    cout << '|';
                }
            }
        }
        else
        {
            for (j = 0; j <= col; j++)
            {
                if (j == 0)
                {
                    if (i < 10)
                    {
                        cout << ' ' << ' ' << i << ' ' << ' ' << '|';
                    }
                    else
                    {
                        cout << ' ' << ' ' << i << ' ' << '|';
                    }
                }
                else
                {
                    cout << ' ' << ' ' << board[i][j] << ' ' << ' ';
                    if (j != col)
                        cout << '|';
                }
            }
        }
        cout << endl;
        for (j = 0; j <= col; j++)
        {
            cout << '-' << '-' << '-' << '-' << '-' << '-';
        }
        cout << endl;
    }
    cout << "未标记雷的个数为 -> " << mine << endl;
}

六、设置雷

        在设置雷中需要用到随机数(为了这个随机数多引用了两个头文件),使用该随机数还需要先在主函数中声明一下,并且需要用到会随时改变的时间来时随机数足够随机,因为srand内部需要的值应为unsigned int 类型,所以这里要强行转化一下,这里是在MSDN中srand的介绍。

        这里简单写了个简化版代码供大家参考。

int main()
{
    srand((unsigned int)time(NULL));
    return 0;
}

        随机数是生成一个(0~好几万)的一个数字(这里忘记了具体范围是0到多少了,我所记不错是一个五位数,总之就是特别大就是了),但是我们只需要1~row和1~col就够了,所以这里进行了rand() % row的操作(rand() % col同理),得到0~(row-1)的随机数,然后再进行+1操作,得到1~row范围的随机数,然后在这里定义了两个整形来接收随机数。

        在函数的开头定义了一个整形count,配合while循环可以放置我们想要的个数的雷。下面用一个if语句来判断该坐标出是否已经被布置过雷了,如果没有布置则进行布置雷操作,然后使count++确保总共布置的雷数是我们想要的,如果已经布置了,则跳过该if语句,继续在while循环中循环,直到雷的个数布置足够。

        下面是布置完成之后的存放雷的棋盘打印出来的展示。

        下面是设置雷函数的代码。

//设置雷

void set_mine(char mine_board[ROWS][COLS], int row, int col)

{

 int count = 0;

 while (count < MINE)

 {

  int ret1 = rand() % row + 1;

  int ret2 = rand() % col + 1;

  if (mine_board[ret1][ret2] == '0')

  {

   mine_board[ret1][ret2] = '1';

   count++;

  }

 }

}

七、找雷

        首先,定义整形win和lose分别代表赢和输,赋值为棋盘总格数减去雷的个数。

        将lose放在while循环中用于判断循环是否继续进行。

 int win = row * col - MINE;

 int lose = row * col - MINE;

 while (lose)

 {
 }

        在while循环中定义三个整形变量,其中ret1和ret2用于存放玩家输入的坐标,chouse用于存放玩家的选择。

        接下来,为了美观,用system("cls");先把之前的东西清空。

        然后再打印棋盘,并引导玩家做出选择。

        运行展示。

        代码展示。


  int ret1 = 0;

  int ret2 = 0;

  int chouse = 0;

  //清空屏幕

  system("cls");

  //打印棋盘

  displayboard(show_board, ROW, COL,mine);

  cout << "请选择排雷或者标记" << endl;

  cout << "1.排雷" << endl;

  cout << "2.标记" << endl;

  cout << "请选择 -> ";

  cin >> chouse;

        再输入完成之后,这里用了一个switch语句来进行判断。

        //排查雷

        当玩家输入1时进入排查雷。

        首先引导玩家输入想要排查的坐标。然后判断该坐标是否在棋盘范围内,如果在范围内则执行接下来的代码。

        先判断该坐标处是否为&,若为&则该坐标已经被标记,提醒玩家重新选择。

        因为返回循环开始时会清空屏幕,所以这里用了Sleep();延时函数,使对玩家的提醒可以在屏幕上多停留一会。

        运行展示(这里我为了更为好的展示,就先把清空屏幕的函数屏蔽了)。

        代码展示。

switch (chouse)

  {

  case 1:

   //排查雷

   cout << "请输入要排查的坐标 -> ";

   cin >> ret1;

   cin >> ret2;

   //输入的坐标必须要在棋盘范围内

   if (ret1 >= 1 && ret1 <= ROW && ret2 >= 1 && ret2 <= COL)

   {

    //如果该坐标为&则表示该位置已经被标记过了,无法排查

    if (show_board[ret1][ret2] == '&')

    {

     cout << "该坐标已被标记,无法排查,请重新选择!" << endl;

     //让上边打印的提示在屏幕上保留一会,防止重新回到循环时直接被清楚,玩家无法看到

     Sleep(800);

    }

        如果该坐标没有被标记过,但是也不是#,则表示该坐标已经被排查过了,提醒玩家无法排查,让玩家重新进行选择。

        运行展示。

        代码展示。

 //如果该坐标没有被标记过,但是也不是#,则表示该坐标已经被排查过了,无法排查

    else if (show_board[ret1][ret2] != '#')

    {

     cout << "该坐标已经排查过了,请重新选择!" << endl;

     //让上边打印的提示在屏幕上保留一会,防止重新回到循环时直接被清楚,玩家无法看到

     Sleep(800);

    }

        如果该坐标不为&和#,则打印出该坐标的信息。

        这里用到了get_mine_count函数(后边会说)来计算排查的点周围一圈有几个雷。

        用unfold函数(后边会说)来实现展开一片的功能,并把在展开一片过程中排查了非雷的数量返回赋值给win。

        然后打印棋盘给玩家展示。

        最后执行win--将本次排查记录在win中。

        运行展示。

        代码展示。

 //如果该坐标不为&和#,则打印出该坐标的信息

    else

    {

     //如果该坐标不是雷

     if (mine_board[ret1][ret2] != '1')

     {

      //通过get_mine_board函数计算出该坐标周围一圈有几个雷,并把该信息存放到该坐标处

      show_board[ret1][ret2] = get_mine_count(mine_board, ret1, ret2) + '0';

      //实现展开一片 用win来接受该函数内展开的个数 便于计算玩家是否胜利

      win = unfold(show_board, ret1, ret2, mine_board, win);

      //打印棋盘

      displayboard(show_board, ROW, COL,mine);

      //开出一个非雷,win减一

      win--;

        当win等于0时,则表示玩家已经将所有的雷都已经排查出来了,玩家胜利,并将lose的值设置为0,跳出循环。

        代码展示。

 //当win等于0时,则证明除了雷之外的所有坐标都已经被排查过了,玩家胜利

      if (win == 0)

      {

       //将lose设置为0,跳出循环

       lose = 0;

      }

        如果玩家排查的坐标时雷,则把该坐标的信息改为 '*' 用来表示雷(因为后边判断玩家是否胜利的时候会再打印一遍棋盘,所以我把这里的打印函数删掉了)。然后将lose设置为0,跳出循环。

        运行展示。

        代码展示。

 //如果该坐标出是雷

     if (mine_board[ret1][ret2] == '1')

     {

      //把雷所在的位置打印出来给玩家看一眼,这里用*表示

      show_board[ret1][ret2] = '*';

      //踩到雷,游戏结束,将lose设置为0,跳出循环

      //注:此时win不为0

      lose = 0;

     }

        如果输入的坐标不在棋盘范围内,则提醒玩家重新选择。同样要用到一个延时函数,给玩家看一眼给玩家的提示。

        最后不要忘记了switch语句的break。

        运行展示。

        代码展示。

  //如果输入的坐标不在棋盘范围内,则提醒玩家重新输入

   else

   {

    cout << "输入的坐标不合规定,请重新选择!" << endl;

    //让上边打印的提示在屏幕上保留一会,防止重新回到循环时直接被清楚,玩家无法看到

    Sleep(800);

   }

   break;

        //标记雷

        首先引导玩家输入要标记的坐标。

  case 2:

   //标记雷

   cout << "请输入要标记的坐标-> ";

   cin >> ret1;

   cin >> ret2;

        当玩家所选的坐标未被排查过且未被标记过,并且可标记雷的数量大于0,则对该坐标出进行标记。

        然后再将可标记的数量减一。

        运行展示。

        

        代码展示。

 //当该坐标没有被标记并且可标记雷的数量大于0,则进行标记操作

   if (show_board[ret1][ret2] == '#' && mine > 0)

   {

    //进行标记操作,将该点展示的符号改为&

    show_board[ret1][ret2] = '&';

    //可标记雷的数量减一

    mine--;

   }

        如果该坐标处已经被标记了,则解除该坐标处的标记。

        然后使可标记的数量加一。

        运行展示。

        代码展示。

 //如果该坐标已经被标记过,则进行解除标记操作

   else if (show_board[ret1][ret2] == '&')

   {

    //进行解除标记操作,将该点展示的符号改为#

    show_board[ret1][ret2] = '#';

    //可标记的雷数量加一

    mine++;

   }

        如果该坐标处是一个数字,则表示该坐标处已经被排查过了,提示玩家重新选择。

        运行展示。

        代码展示。

 //如果该点展示的是一个数字,则表示该点已经被排查过了,提醒玩家重新选择

   else if(show_board[ret1][ret2] != '#' && show_board[ret1][ret2] != '&')

   {

    //提醒玩家重新选择

    cout << "该坐标已经排查过了,请重新选择!" << endl;

    //让上边打印的提示在屏幕上保留一会,防止重新回到循环时直接被清楚,玩家无法看到

    Sleep(800);

   }

        如果以上的情况都没有发生,则表示剩余的标记数量不足,提示玩家无法标记。

        运行展示。

        代码展示。

 //如果以上的可能都没有发生,则表示剩余标记雷的数量不足,提醒玩家无法标记

   else

   {

    //提醒玩家无法标记

    cout << "标记个数已达上线,请先取消其他标记!" << endl;

    //让上边打印的提示在屏幕上保留一会,防止重新回到循环时直接被清楚,玩家无法看到

    Sleep(800);

   }

   break;

        如果玩家输入并非1或者2,则提示玩家输入非法,让玩家重新进行输入。

        运行展示。

        代码展示。

  default :

   //输入非法,提示玩家重新输入

   cout << "输入错误,请重新输入!" << endl;

   break;

        如果跳出了循环,并且win等于0,则表示玩家已经排除了所有的雷,提示玩家胜利。

//如果win等于0,则表示玩家排除了所有的雷,玩家胜利

 if (win == 0)

 {

  //清空屏幕

  system("cls");

  //提示玩家胜利

  cout << "恭喜你,成功排出了所有的雷!" << endl;

  //打印棋盘,给玩家看一眼

  displayboard(show_board, ROW, COL, mine);

 }

        如果跳出了循环并且win不等于0,则表示踩到雷了提前跳出循环,提示玩家失败。

//如果win不等于0且跳出了循环,则表示玩家提前踩到雷了,玩家失败

 if (win != 0)

 {

  //清空屏幕

  system("cls");

  //提示玩家失败

  cout << "踩到雷了!游戏结束!" << endl;

  //打印棋盘,给玩家看一眼

  displayboard(show_board, ROW, COL, mine);

 }

}

八、实现遍历周围八个位置

        遍历该坐标周围一圈八个位置,将这八个位置的值加在一起(因为数组里存放的是字符,所以相加之后要减去8个字符0,然后再返回。

//遍历该坐标周围的八个位置,看看有几个雷

int get_mine_count(char mine_board[ROWS][COLS], int ret1, int ret2)

{

 //返回周围的雷数

 return (mine_board[ret1 - 1][ret2 - 1] +

   mine_board[ret1 - 1][ret2] +

   mine_board[ret1 - 1][ret2 + 1] +

   mine_board[ret1][ret2 - 1] +

   mine_board[ret1][ret2 + 1] +

   mine_board[ret1 + 1][ret2 - 1] +

   mine_board[ret1 + 1][ret2] +

   mine_board[ret1 + 1][ret2 + 1]) - (8 * '0');//每个数字加在一起后需要减去字符零

                 //因为mine_board函数为字符数组

}

九、实现展开一片

        遍历周围八个位置,先用一个if语句判断是否在棋盘内,然后在八个位置分别进行判断。

        首先,判断需要遍历的坐标处是否是雷,该坐标处展示的是否是#,本坐标处是否是0。如果满足以上三个条件,则进入判断语句,在判断语句中先引用get_mine_count函数,查看需要遍历的坐标周围有几个雷,并将雷数赋值给该坐标,然后递归本函数,最后win--记录本次排查。

//实现展开一片的函数

int unfold(char show_board[ROWS][COLS], int ret1, int ret2, char mine_board[ROWS][COLS], int win)

{

 //确保要展开的点在棋盘内

 if (ret1 >= 1 && ret1 <= ROW && ret2 >= 1 && ret2 <= COL)

 {

  //确保要巡查的点在棋盘内

  if (mine_board[ret1 - 1][ret2 - 1] != '1' && show_board[ret1 - 1][ret2 - 1] == '#' && show_board[ret1][ret2] == '0')

  {

   //通过get_mine_board函数计算出该坐标周围一圈有几个雷,并把该信息存放到该坐标处

   show_board[ret1 - 1][ret2 - 1] = get_mine_count(mine_board, ret1 - 1, ret2 - 1) + '0';

   //递归本函数 实现连续展开

   win = unfold(show_board, ret1 - 1, ret2 - 1, mine_board, win);

   //经过本if语句 展开了一个坐标 让win加一 便于计算是否胜利

   win--;

  }

  //以下同上

  if (mine_board[ret1 - 1][ret2] != '1' && show_board[ret1 - 1][ret2] == '#' && show_board[ret1][ret2] == '0')

  {

   show_board[ret1 - 1][ret2] = get_mine_count(mine_board, ret1 - 1, ret2) + '0';

   win = unfold(show_board, ret1 - 1, ret2, mine_board, win);

   win--;

  }

  if (mine_board[ret1 - 1][ret2 + 1] != '1' && show_board[ret1 - 1][ret2 + 1] == '#' && show_board[ret1][ret2] == '0')

  {

   show_board[ret1 - 1][ret2 + 1] = get_mine_count(mine_board, ret1 - 1, ret2 + 1) + '0';

   win = unfold(show_board, ret1 - 1, ret2 + 1, mine_board, win);

   win--;

  }

  if (mine_board[ret1][ret2 - 1] != '1' && show_board[ret1][ret2 - 1] == '#' && show_board[ret1][ret2] == '0')

  {

   show_board[ret1][ret2 - 1] = get_mine_count(mine_board, ret1, ret2 - 1) + '0';

   win = unfold(show_board, ret1, ret2 - 1, mine_board, win);

   win--;

  }

  if (mine_board[ret1][ret2 + 1] != '1' && show_board[ret1][ret2 + 1] == '#' && show_board[ret1][ret2] == '0')

  {

   show_board[ret1][ret2 + 1] = get_mine_count(mine_board, ret1, ret2 + 1) + '0';

   win = unfold(show_board, ret1, ret2 + 1, mine_board, win);

   win--;

  }

  if (mine_board[ret1 + 1][ret2 - 1] != '1' && show_board[ret1 + 1][ret2 - 1] == '#' && show_board[ret1][ret2] == '0')

  {

   show_board[ret1 + 1][ret2 - 1] = get_mine_count(mine_board, ret1 + 1, ret2 - 1) + '0';

   win = unfold(show_board, ret1 + 1, ret2 - 1, mine_board, win);

   win--;

  }

  if (mine_board[ret1 + 1][ret2] != '1' && show_board[ret1 + 1][ret2] == '#' && show_board[ret1][ret2] == '0')

  {

   show_board[ret1 + 1][ret2] = get_mine_count(mine_board, ret1 + 1, ret2) + '0';

   win = unfold(show_board, ret1 + 1, ret2, mine_board, win);

   win--;

  }

  if (mine_board[ret1 + 1][ret2 + 1] != '1' && show_board[ret1 + 1][ret2 + 1] == '#' && show_board[ret1][ret2] == '0')

  {

   show_board[ret1 + 1][ret2 + 1] = get_mine_count(mine_board, ret1 + 1, ret2 + 1) + '0';

   win = unfold(show_board, ret1 + 1, ret2 + 1, mine_board, win);

   win--;

  }

 }

 //返回win的值 便于计算玩家是否胜利

 return win;

}

十、主函数

        代码展示。

void game()
{
    //定义随机数
    srand((unsigned int)time(NULL));

    //定义放雷的棋盘
    char mine_board[ROWS][COLS] = { 0 };

    //定义对玩家展示的棋盘
    char show_board[ROWS][COLS] = { 0 };

    //定义雷的个数
    int mine = MINE;

    //初始化雷的棋盘
    init_mine_board(mine_board, ROWS, COLS, '0');

    //初始化展示棋盘
    init_show_board(show_board, ROW, COL, '#');

    //打印棋盘
    //displayboard(mine_board, ROW, COL,mine);
    displayboard(show_board, ROW, COL,mine);

    //设置雷
    set_mine(mine_board, ROW, COL);
    //displayboard(mine_board, ROW, COL,mine);

    //开始扫雷
    find_mine(mine_board, show_board, ROW, COL,mine);

}

整体代码:

#include <iostream>
#include <stdlib.h>
#include <time.h>
#include <windows.h>

using namespace std;

//定义棋盘的长和宽
#define ROW 9
#define COL 9

//定义比棋盘大一圈的长和宽,便于实现一系列操作
#define ROWS 11
#define COLS 11

//定义雷的个数
#define MINE 10

//开始菜单
void menu()
{
	cout << "*********************" << endl;
	cout << "******  1.play  *****" << endl;
	cout << "******  0.exit  *****" << endl;
	cout << "*********************" << endl;
}

//初始化雷的棋盘
void init_mine_board(char board[ROWS][COLS], int rows, int cols, char symbol)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			board[i][j] = symbol;
		}
	}
}

//初始化展示棋盘
void init_show_board(char show_board[ROWS][COLS], int row, int col, char symbol)
{
	int i = 0;
	int j = 0;
	for (i = 1; i <= row; i++)
	{
		for (j = 1; j <= col; j++)
		{
			show_board[i][j] = symbol;
		}
	}
}

//打印棋盘
void displayboard(char board[ROWS][COLS], int row, int col, int mine)
{
	int i = 0;
	int j = 0;
	for (i = 0; i <= row; i++)
	{
		if (i == 0)
		{
			for (j = 0; j <= col; j++)
			{
				if (j < 10)
				{
					cout << ' ' << ' ' << j << ' ' << ' ';
				}
				else
				{
					cout << ' ' << ' ' << j << ' ';
				}
				if (j != col)
				{
					cout << '|';
				}
			}
		}
		else
		{
			for (j = 0; j <= col; j++)
			{
				if (j == 0)
				{
					if (i < 10)
					{
						cout << ' ' << ' ' << i << ' ' << ' ' << '|';
					}
					else
					{
						cout << ' ' << ' ' << i << ' ' << '|';
					}
				}
				else
				{
					cout << ' ' << ' ' << board[i][j] << ' ' << ' ';
					if (j != col)
						cout << '|';
				}
			}
		}
		cout << endl;
		for (j = 0; j <= col; j++)
		{
			cout << '-' << '-' << '-' << '-' << '-' << '-';
		}
		cout << endl;
	}
	cout << "未标记雷的个数为 -> " << mine << endl;
}

//设置雷
void set_mine(char mine_board[ROWS][COLS], int row, int col)
{
	int count = 0;
	while (count < MINE)
	{
		int ret1 = rand() % row + 1;
		int ret2 = rand() % col + 1;
		if (mine_board[ret1][ret2] == '0')
		{
			mine_board[ret1][ret2] = '1';
			count++;
		}
	}
}

//找雷
void find_mine(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col, int mine)
{
	int win = row * col - MINE;
	int lose = row * col - MINE;
	while (lose)
	{
		int ret1 = 0;
		int ret2 = 0;
		int chouse = 0;

		//清空屏幕
		system("cls");

		//打印棋盘
		displayboard(show_board, ROW, COL,mine);
		cout << "请选择排雷或者标记" << endl;
		cout << "1.排雷" << endl;
		cout << "2.标记" << endl;
		cout << "请选择 -> ";
		cin >> chouse;
		switch (chouse)
		{
		case 1:
			//排查雷
			cout << "请输入要排查的坐标 -> ";
			cin >> ret1;
			cin >> ret2;
			
			//输入的坐标必须要在棋盘范围内
			if (ret1 >= 1 && ret1 <= ROW && ret2 >= 1 && ret2 <= COL)
			{
				//如果该坐标为&则表示该位置已经被标记过了,无法排查
				if (show_board[ret1][ret2] == '&')
				{
					cout << "该坐标已被标记,无法排查,请重新选择!" << endl;
					//让上边打印的提示在屏幕上保留一会,防止重新回到循环时直接被清楚,玩家无法看到
					Sleep(800);
				}
				//如果该坐标没有被标记过,但是也不是#,则表示该坐标已经被排查过了,无法排查
				else if (show_board[ret1][ret2] != '#')
				{
					cout << "该坐标已经排查过了,请重新选择!" << endl;
					//让上边打印的提示在屏幕上保留一会,防止重新回到循环时直接被清楚,玩家无法看到
					Sleep(800);
				}
				//如果该坐标不为&和#,则打印出该坐标的信息
				else
				{
					//如果该坐标不是雷
					if (mine_board[ret1][ret2] != '1')
					{
						//通过get_mine_board函数计算出该坐标周围一圈有几个雷,并把该信息存放到该坐标处
						show_board[ret1][ret2] = get_mine_count(mine_board, ret1, ret2) + '0';
						//实现展开一片 用win来接受该函数内展开的个数 便于计算玩家是否胜利
						win = unfold(show_board, ret1, ret2, mine_board, win);
						//打印棋盘
						displayboard(show_board, ROW, COL,mine);
						//开出一个非雷,win减一
						win--;
						//当win等于0时,则证明除了雷之外的所有坐标都已经被排查过了,玩家胜利
						if (win == 0)
						{
							//将lose设置为0,跳出循环
							lose = 0;
						}
					}
					//如果该坐标出是雷
					if (mine_board[ret1][ret2] == '1')
					{
						//把雷所在的位置打印出来给玩家看一眼,这里用*表示
						show_board[ret1][ret2] = '*';
						//踩到雷,游戏结束,将lose设置为0,跳出循环
						//注:此时win不为0
						lose = 0;
					}
				}
			}
			//如果输入的坐标不在棋盘范围内,则提醒玩家重新输入
			else
			{
				cout << "输入的坐标不合规定,请重新选择!" << endl;
				//让上边打印的提示在屏幕上保留一会,防止重新回到循环时直接被清楚,玩家无法看到
				Sleep(800);
			}
			break;
		case 2:
			//标记雷
			cout << "请输入要标记的坐标-> ";
			cin >> ret1;
			cin >> ret2;
			//当该坐标没有被标记并且可标记雷的数量大于0,则进行标记操作
			if (show_board[ret1][ret2] == '#' && mine > 0)
			{
				//进行标记操作,将该点展示的符号改为&
				show_board[ret1][ret2] = '&';
				//可标记雷的数量减一
				mine--;
			}
			//如果该坐标已经被标记过,则进行解除标记操作
			else if (show_board[ret1][ret2] == '&')
			{
				//进行解除标记操作,将该点展示的符号改为#
				show_board[ret1][ret2] = '#';
				//可标记的雷数量加一
				mine++;
			}
			//如果该点展示的是一个数字,则表示该点已经被排查过了,提醒玩家重新选择
			else if(show_board[ret1][ret2] != '#' && show_board[ret1][ret2] != '&')
			{
				//提醒玩家重新选择
				cout << "该坐标已经排查过了,请重新选择!" << endl;
				//让上边打印的提示在屏幕上保留一会,防止重新回到循环时直接被清楚,玩家无法看到
				Sleep(800);
			}
			//如果以上的可能都没有发生,则表示剩余标记雷的数量不足,提醒玩家无法标记
			else
			{
				//提醒玩家无法标记
				cout << "标记个数已达上线,请先取消其他标记!" << endl;
				//让上边打印的提示在屏幕上保留一会,防止重新回到循环时直接被清楚,玩家无法看到
				Sleep(800);
			}
			break;
		default :
			//输入非法,提示玩家重新输入
			cout << "输入错误,请重新输入!" << endl;
			break;
		}
	}
	//如果win等于0,则表示玩家排除了所有的雷,玩家胜利
	if (win == 0)
	{
		//清空屏幕
		system("cls");
		//提示玩家胜利
		cout << "恭喜你,成功排出了所有的雷!" << endl;
		//打印棋盘,给玩家看一眼
		displayboard(show_board, ROW, COL, mine);
	}
	//如果win不等于0且跳出了循环,则表示玩家提前踩到雷了,玩家失败
	if (win != 0)
	{
		//清空屏幕
		system("cls");
		//提示玩家失败
		cout << "踩到雷了!游戏结束!" << endl;
		//打印棋盘,给玩家看一眼
		displayboard(show_board, ROW, COL, mine);
	}
}

//遍历该坐标周围的八个位置,看看有几个雷
int get_mine_count(char mine_board[ROWS][COLS], int ret1, int ret2)
{
	//返回周围的雷数
	return (mine_board[ret1 - 1][ret2 - 1] +
			mine_board[ret1 - 1][ret2] +
			mine_board[ret1 - 1][ret2 + 1] +
			mine_board[ret1][ret2 - 1] +
			mine_board[ret1][ret2 + 1] +
			mine_board[ret1 + 1][ret2 - 1] +
			mine_board[ret1 + 1][ret2] +
			mine_board[ret1 + 1][ret2 + 1]) - (8 * '0');//每个数字加在一起后需要减去字符零
													    //因为mine_board函数为字符数组
}

//实现展开一片的函数
int unfold(char show_board[ROWS][COLS], int ret1, int ret2, char mine_board[ROWS][COLS], int win)
{
	//确保要展开的点在棋盘内
	if (ret1 >= 1 && ret1 <= ROW && ret2 >= 1 && ret2 <= COL)
	{
		//确保要巡查的点在棋盘内
		if (mine_board[ret1 - 1][ret2 - 1] != '1' && show_board[ret1 - 1][ret2 - 1] == '#' && show_board[ret1][ret2] == '0')
		{
			//通过get_mine_board函数计算出该坐标周围一圈有几个雷,并把该信息存放到该坐标处
			show_board[ret1 - 1][ret2 - 1] = get_mine_count(mine_board, ret1 - 1, ret2 - 1) + '0';
			//递归本函数 实现连续展开
			win = unfold(show_board, ret1 - 1, ret2 - 1, mine_board, win);
			//经过本if语句 展开了一个坐标 让win加一 便于计算是否胜利
			win--;
		}
		//以下同上
		if (mine_board[ret1 - 1][ret2] != '1' && show_board[ret1 - 1][ret2] == '#' && show_board[ret1][ret2] == '0')
		{
			show_board[ret1 - 1][ret2] = get_mine_count(mine_board, ret1 - 1, ret2) + '0';
			win = unfold(show_board, ret1 - 1, ret2, mine_board, win);
			win--;
		}
		if (mine_board[ret1 - 1][ret2 + 1] != '1' && show_board[ret1 - 1][ret2 + 1] == '#' && show_board[ret1][ret2] == '0')
		{
			show_board[ret1 - 1][ret2 + 1] = get_mine_count(mine_board, ret1 - 1, ret2 + 1) + '0';
			win = unfold(show_board, ret1 - 1, ret2 + 1, mine_board, win);
			win--;
		}
		if (mine_board[ret1][ret2 - 1] != '1' && show_board[ret1][ret2 - 1] == '#' && show_board[ret1][ret2] == '0')
		{
			show_board[ret1][ret2 - 1] = get_mine_count(mine_board, ret1, ret2 - 1) + '0';
			win = unfold(show_board, ret1, ret2 - 1, mine_board, win);
			win--;
		}
		if (mine_board[ret1][ret2 + 1] != '1' && show_board[ret1][ret2 + 1] == '#' && show_board[ret1][ret2] == '0')
		{
			show_board[ret1][ret2 + 1] = get_mine_count(mine_board, ret1, ret2 + 1) + '0';
			win = unfold(show_board, ret1, ret2 + 1, mine_board, win);
			win--;
		}
		if (mine_board[ret1 + 1][ret2 - 1] != '1' && show_board[ret1 + 1][ret2 - 1] == '#' && show_board[ret1][ret2] == '0')
		{
			show_board[ret1 + 1][ret2 - 1] = get_mine_count(mine_board, ret1 + 1, ret2 - 1) + '0';
			win = unfold(show_board, ret1 + 1, ret2 - 1, mine_board, win);
			win--;
		}
		if (mine_board[ret1 + 1][ret2] != '1' && show_board[ret1 + 1][ret2] == '#' && show_board[ret1][ret2] == '0')
		{
			show_board[ret1 + 1][ret2] = get_mine_count(mine_board, ret1 + 1, ret2) + '0';
			win = unfold(show_board, ret1 + 1, ret2, mine_board, win);
			win--;
		}
		if (mine_board[ret1 + 1][ret2 + 1] != '1' && show_board[ret1 + 1][ret2 + 1] == '#' && show_board[ret1][ret2] == '0')
		{
			show_board[ret1 + 1][ret2 + 1] = get_mine_count(mine_board, ret1 + 1, ret2 + 1) + '0';
			win = unfold(show_board, ret1 + 1, ret2 + 1, mine_board, win);
			win--;
		}
	}
	//返回win的值 便于计算玩家是否胜利
	return win;
}

void game()
{
	//定义随机数
	srand((unsigned int)time(NULL));

	//定义放雷的棋盘
	char mine_board[ROWS][COLS] = { 0 };

	//定义对玩家展示的棋盘
	char show_board[ROWS][COLS] = { 0 };

	//定义雷的个数
	int mine = MINE;

	//初始化雷的棋盘
	init_mine_board(mine_board, ROWS, COLS, '0');

	//初始化展示棋盘
	init_show_board(show_board, ROW, COL, '#');

	//打印棋盘
	//displayboard(mine_board, ROW, COL,mine);
	displayboard(show_board, ROW, COL,mine);

	//设置雷
	set_mine(mine_board, ROW, COL);
	//displayboard(mine_board, ROW, COL,mine);

	//开始扫雷
	find_mine(mine_board, show_board, ROW, COL,mine);

}

int main()
{
	//改变背景和字的颜色
	//(0 黑, 1 蓝, 2 绿, 3 浅绿, 4 红, 5 紫, 6 黄, 7 白, 8 灰, 9 淡蓝)
	//(a 淡绿, b 浅淡绿, c 淡红, d 淡紫, e 淡黄, f 亮白)
	system("color fd");
	int input = 0;
	do
	{
		//打印开始菜单
		menu();
		cin >> input;
		switch(input)
		{
		case 1:
			//开始游戏
			game();
			break;
		case 0:
			//退出游戏
			break;
		default :
			//重新选择
			cout << "输入错误,请重新输入!" << endl;
			break;
		}
	} while (input);
	return 0;
}

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个用C++实现扫雷游戏,具体实现思路是使用二维数组来表示扫雷游戏的格子,其中0表示空格,1表示有地雷的格子,2表示已经翻开的格子,3表示标记为地雷的格子。在游戏过程中,使用递归算法来实现翻开空格的效果,同时需要判断游戏是否结束。 ```cpp #include <iostream> #include <vector> #include <ctime> #include <cstdlib> using namespace std; // 定义常量 const int BOARD_SIZE = 10; // 扫雷游戏的大小 const int MINE_NUM = 15; // 地雷的数量 // 定义二维数组表示扫雷游戏的格子 int board[BOARD_SIZE][BOARD_SIZE]; // 在指定位置放置地雷 void placeMine(int x, int y) { board[x][y] = 1; } // 随机放置地雷 void placeMines() { srand(time(nullptr)); int count = 0; while (count < MINE_NUM) { int x = rand() % BOARD_SIZE; int y = rand() % BOARD_SIZE; if (board[x][y] != 1) { placeMine(x, y); count++; } } } // 计算指定位置周围地雷数量 int countMines(int x, int y) { int count = 0; for (int i = -1; i <= 1; ++i) { for (int j = -1; j <= 1; ++j) { int nx = x + i; int ny = y + j; if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE && board[nx][ny] == 1) { count++; } } } return count; } // 打印游戏面板 void printBoard(bool showMine) { for (int i = 0; i < BOARD_SIZE; ++i) { for (int j = 0; j < BOARD_SIZE; ++j) { if (board[i][j] == 2) { cout << countMines(i, j) << " "; } else if (board[i][j] == 3) { cout << "F "; } else if (showMine && board[i][j] == 1) { cout << "* "; } else { cout << "- "; } } cout << endl; } } // 翻开指定位置的格子 bool click(int x, int y) { if (board[x][y] == 1) { return false; } else if (board[x][y] == 2) { return true; } else { board[x][y] = 2; if (countMines(x, y) == 0) { for (int i = -1; i <= 1; ++i) { for (int j = -1; j <= 1; ++j) { int nx = x + i; int ny = y + j; if (nx >= 0 && nx < BOARD_SIZE && ny >= 0 && ny < BOARD_SIZE) { click(nx, ny); } } } } return true; } } // 标记指定位置的格子 void mark(int x, int y) { if (board[x][y] == 3) { board[x][y] = 0; } else { board[x][y] = 3; } } // 判断游戏是否结束 bool isGameOver() { for (int i = 0; i < BOARD_SIZE; ++i) { for (int j = 0; j < BOARD_SIZE; ++j) { if (board[i][j] == 2) { continue; } else if (board[i][j] == 1) { continue; } else { return false; } } } return true; } int main() { // 初始化游戏面板 for (int i = 0; i < BOARD_SIZE; ++i) { for (int j = 0; j < BOARD_SIZE; ++j) { board[i][j] = 0; } } placeMines(); // 开始游戏 bool gameOver = false; while (!gameOver) { printBoard(false); cout << "请输入坐标和操作(x y c/m):"; int x, y; char op; cin >> x >> y >> op; if (op == 'm') { mark(x, y); } else if (op == 'c') { if (!click(x, y)) { gameOver = true; printBoard(true); cout << "游戏结束,你输了!" << endl; } else if (isGameOver()) { gameOver = true; printBoard(true); cout << "恭喜你,你赢了!" << endl; } } } return 0; } ``` 该程序可以实现一个简单的扫雷游戏,支持放置地雷、随机放置地雷、计算周围地雷数量、打印游戏面板、翻开格子、标记格子、判断游戏是否结束等操作。需要注意的是,由于该程序使用了递归算法来实现翻开空格的效果,可能会出现栈溢出的情况,因此需要设置递归深度的限制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值