初识扫雷(含详细源码及指导)

初识扫雷(含详细源码及指导)扫雷游戏 扫雷

大家好,欢迎来到IT知识分享网。

                      前言

dace5c9c4dd2472aa8ddf7e468bbd42c.jpeg

  今天我们来简单了解一下一款经典的游戏–扫雷!文末附上完整代码。

扫雷作为微软开发并发行的一款大众类的益智小游戏,于1992年发行。玩家的目标是在最短时间内识别出所有非雷区的格子,同时避免触碰到地雷。游戏区域由多个隐藏格子组成,每个格子可能隐藏着地雷或数字,数字表示周围八个格子中地雷的数量。玩家通过点击格子揭示内容,使用逻辑推理逐步排除雷区。游戏考验玩家的观察力和推理能力,任何一次误点地雷都会导致游戏失败。

扫雷分为初级,中级,高级等难度,玩家也可以自行定义雷的数量与方格的大小。免费在线扫雷 (minesweeper.cn)这是网页版的扫雷游戏,大家感兴趣可以点击进入。

4adf41243c84422e9f210e4fa8bf73f3.png

            游戏分析与设计:

1.数据结构分析

布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些信息。初阶的扫雷(如上图)大小是9*9, 因为我们需要在9*9的棋盘上布置雷的信息和排查雷,我们⾸先想到的就是创建⼀个9*9的数组来存放信息

• 使⽤控制台实现经典的扫雷游戏

• 游戏可以通过菜单实现继续玩或者退出游戏

• 扫雷的棋盘是9*9的格⼦ • 默认随机布置10个雷

• 可以排查雷 ◦ 如果位置不是雷,就周围有⼏个雷

         ◦  如果位置是雷,就炸死游戏结束

         ◦ 把除10个雷之外的所有⾮雷都找出来,排雷成功,游戏结束

2.⽂件结构设计

7a7b00d2f1194afcb287289339a3256f.png

为了便于代码的创建,我们分成三个文件来实现。

                 代码实现

1.菜单的创建

void menu() { printf("*\n"); printf("* 1.play \n"); printf("* 0.exit \n"); printf("*\n"); } int main() { srand( (unsigned int) time ( NULL ) ); int input = 0 ; do { menu(); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: printf("\n"); game(); break; case 0: printf("\n"); printf("退出\n"); break; default: printf("\n"); printf("输入有误,请重新输入\n"); break; } } while (input); return 0; }

细节分析:

创建menu函数来简单的放置我们的菜单栏,并添加选项1,0。1是开始游戏,而0是退出游戏!   使用do while循环,这样我们就能至少进行一次菜单栏操作,输入选项进入switch函数,为了让我们玩过一次后还能玩,当input等于0时,我们才结束循环退出!

 

2.初始化数组

game.h

#define ROW 9 #define COL 9 #define ROWS ROW+2 #define COLS COL+2 //ROW 行 COL 列

test.c

char mine[ROWS][COLS] = { 0 };//放布置好的雷‘0’ char show[ROWS][COLS] = { 0 };//放排查出的雷‘*’ //初始化数组 InitBoard(mine, ROWS, COLS,'0'); InitBoard(show, ROWS, COLS, '*');

game.c

#include "game.h" void InitBoard(char board[ROWS][COLS], int row, int col, char set) { int i, j; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { board[i][j] = set; } } }

细节分析:

在开始前,我们需要知道若要引用game.h的函数与声明,我们要在文件头加入#include “game.h”, 这样我们就只需要在game.h中放置函数声明与定义!

board有寄存的意思。

为什么在game.h中声明行列的数目?其实是为了自定义方格大小时便于修改

相信大家注意到了,ROWS和COLS,为什么要创建这两个常量?下面我通过画图为大家分析

                                  c9e8f3f4116d47daa306fd21aff63575.png

我们知道当选择A时,格子如果没有雷,则会显示周围八格的地雷数,如果没有雷则显示空格。那么如果选择C时,则显示周围五格,这将会大大提高我们的代码难度,难不成特地为边缘格子开发一个判断循环结构?所以我们就在9*9格子的基础上增加两行两列,也就是周围补上一圈!

如下图,D点也判断周围八格的地雷数!这样便于我们构造判断语句,即如果周围八格有地雷,则地雷数依次++

a0374d801aa94992bc179719d52c3b4b.png

所以在test.c中把数组mine和show传入函数InitBoard,当然了,我们在展示格子和雷的时候只需要9*9的范围。

3.打印棋盘

game.h

void DisplayBoard(char board[ROWS][COLS], int row, int col);

test.c

DisplayBoard(mine, ROW, COL); DisplayBoard(show, ROW, COL);

game.c

void DisplayBoard(char board[ROWS][COLS], int row, int col) { printf("-------扫雷-------\n"); printf("\n"); printf(" "); for (int i = 1; i <= row; i++) { printf("%d ", i); } printf("\n"); for (int i = 1; i <= row; i++) { printf("%d ", i); for (int j = 1; j <= col; j++) { printf("%c ", board[i][j]); } printf("\n"); } printf("-------扫雷-------\n"); }

细节分析:

前面我们讲到构造了一个11*11的二维数组,且提到了在打印棋盘时,我们其实只需要9*9的空间

所以在test.c中我们传入ROW,COL,就是9。那么打印数组时,我们就从下标1开始,依次打印九个内容。那么为了美观,我们在头行头列加入数字便于我们知道这是哪行哪列,这也为我们后续的代码实现奠定基础!也可以加入扫雷字样

e705b93d550248c08ad764fae1ac4dda.png

4.布置地雷

game.h

#include<stdlib.h> #include<time.h> void SetMine(char board[ROWS][COLS], int row, int col); 

test.c

//布置雷 SetMine(mine, ROW, COL);

game.c

void SetMine(char board[ROWS][COLS], int row, int col) { int count = EASY_COUNT; while (count) { //随机生成下标 int x = rand() % row + 1; int y = rand() % col + 1; if (board[x][y] != '1') { board[x][y] = '1'; count--; } } }

细节分析:

在这里我们用到了rand函数,这是一个生成伪随机数的函数,就是生成的数并不是真正随机,生成后再次打开,我们会发现数字与上次的一模一样,细心的你一定发现了,在菜单创建的主函数,有这么一串代码:srand( (unsigned int) time ( NULL ) );这便是生成随机数的关键。我们知道,在计算机中,时间是不断变化的,那么我们利用时间戳(大家可以搜看看)。time的参数如果是NULL,就能只返回时间的差值,这就是前面说的时间戳。srand函数能生成种子,而time让srand生成的是随机种子!

定义r=rand()的返回值,(由公式可知,如果要生成0-n的随机数,就让r%(n+1),即生成0-99的随机数,是r%100)现在我们要生成0-9的随机数,就是r%9+1.

rand函数与time函数需要头文件。

 

一切安排妥当后,我们就在数组中放入地雷,当这个位置不是雷时,我们就放入雷,并且雷的总数–。雷的数量在game.h中定义。

5.排查雷

关键的一步!

game.h

#define EASY_COUNT 10 void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);

test.c

//排查雷 FindMine(mine, show, ROW, COL);

game.c

int GetMineCount(char mine[ROWS][COLS], int x, int y) { 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) { int x = 0; int y = 0; int win = 0; while (win<row*col-EASY_COUNT) { printf("请输入要排查的坐标(行 列): "); scanf("%d%d", &x, &y); if (x>0 && x<=row && y>0 && y<=col) { if (mine[x][y] == '1') { printf("炸了\n"); DisplayBoard(mine, ROW, COL); break; } else { win++; printf("还需排查%d个位置\n", row * col - EASY_COUNT-win); //统计x,y坐标周围的8个坐标中雷的个数 int c=GetMineCount(mine,x,y); show[x][y] = c + '0'; DisplayBoard(show, ROW, COL); } } else { printf("输入的坐标有误,请重新输入"); } } if (win == row * col - EASY_COUNT) { printf("恭喜你,排雷成功!!!\n"); DisplayBoard(mine, ROW, COL); } }

细节分析:

根据代码,我们容易知道,当我们输入坐标后,若该位置恰好有雷,那么游戏失败,退出。

否则,排查出附近八个位置雷的数目!

int GetMineCount(char mine[ROWS][COLS], int x, int y) { 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'; }

11992b4850994258898f3fec310463a9.jpega09b1fcfd8434c8d8fdf7e91703a4671.jpeg

因为相同字符相减为 0 ,所以方法就是让周围八个位置的相加再减去8* ‘ 0 ‘ ,下图是进阶算法。

acc71689338e4872ade4f96ad64ac075.jpeg

循环的终止条件就是当win等于除了雷以外的格子数,而每次排查没踩到雷,win++。

每次排查后,都生成下一次的棋盘!

下面是效果图与源码!

效果图:

2947d4ccd5014c18a7d03394300ab55d.pngdfe15bf7b48342e380a76be9368b7cc1.pngc866f2d2199345a3a8f957b472c88bea.png

全部源码:

game.h

#pragma once #include<stdio.h> #include<stdlib.h> #include<time.h> #define ROW 9 #define COL 9 #define ROWS ROW+2 #define COLS COL+2 #define EASY_COUNT 10 void InitBoard(char board[ROWS][COLS], int row, int col, char set); void DisplayBoard(char board[ROWS][COLS], int row, int col); void SetMine(char board[ROWS][COLS], int row, int col); void FindMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col);

game.c

#define _CRT_SECURE_NO_WARNINGS 1 #include "game.h" void InitBoard(char board[ROWS][COLS], int row, int col, char set) { int i, j; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { board[i][j] = set; } } } void DisplayBoard(char board[ROWS][COLS], int row, int col) { printf("-------扫雷-------\n"); printf("\n"); printf(" "); for (int i = 1; i <= row; i++) { printf("%d ", i); } printf("\n"); for (int i = 1; i <= row; i++) { printf("%d ", i); for (int j = 1; j <= col; j++) { printf("%c ", board[i][j]); } printf("\n"); } printf("-------扫雷-------\n"); } void SetMine(char board[ROWS][COLS], int row, int col) { int count = EASY_COUNT; while (count) { //随机生成下标 int x = rand() % row + 1; int y = rand() % col + 1; if (board[x][y] != '1') { board[x][y] = '1'; count--; } } } int GetMineCount(char mine[ROWS][COLS], int x, int y) { 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) { int x = 0; int y = 0; int win = 0; while (win<row*col-EASY_COUNT) { printf("请输入要排查的坐标(行 列): "); scanf("%d%d", &x, &y); if (x>0 && x<=row && y>0 && y<=col) { if (mine[x][y] == '1') { printf("炸了\n"); DisplayBoard(mine, ROW, COL); break; } else { win++; printf("还需排查%d个位置\n", row * col - EASY_COUNT-win); //统计x,y坐标周围的8个坐标中雷的个数 int c=GetMineCount(mine,x,y); show[x][y] = c + '0'; DisplayBoard(show, ROW, COL); } } else { printf("输入的坐标有误,请重新输入"); } } if (win == row * col - EASY_COUNT) { printf("恭喜你,排雷成功!!!\n"); DisplayBoard(mine, ROW, COL); } }

test.c

#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include "game.h" void menu() { printf("*\n"); printf("* 1.play \n"); printf("* 0.exit \n"); printf("*\n"); } void game() { char mine[ROWS][COLS] = { 0 };//放布置好的雷‘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() { srand( (unsigned int) time ( NULL ) ); int input = 0 ; do { menu(); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: printf("\n"); game(); break; case 0: printf("\n"); printf("退出\n"); break; default: printf("\n"); printf("输入有误,请重新输入\n"); break; } } while (input); return 0; }

 

439db5401f4b47ed86fa9f5ba9dda993.webp

拓展:

作为一个经典的游戏,我们的代码还是不完善的,许多功能并没有实现,如:

1.在判断为雷的格子上插旗子标记

2.加入排雷的时间显示

3.如果排查位置不是雷,周围也没有雷,可以展开周围的一片

4.选择游戏的难度

       。简单    9*9  10个雷

       。中等    16*16   40个雷

       。困难    30*16  99个雷

       。自定义

ce2869ce50064cef9bbeacb391d8fb57.png

小结:

  本次分享就到此结束了,如果你有关于扫雷拓展与优化的想法,或者本文章中的错误与不足,欢迎在评论区中留言!!如果觉得小编写的不错,记得一键三连支持一下!!非常感谢!

17c6a006d9a947b89abd5950a881ad9b.webp

 

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/157267.html

(0)
上一篇 2025-02-06 22:05
下一篇 2025-02-06 22:10

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信