前言
今天我们来简单了解一下一款经典的游戏--扫雷!文末附上完整代码。
扫雷作为微软开发并发行的一款大众类的益智小游戏,于1992年发行。玩家的目标是在最短时间内识别出所有非雷区的格子,同时避免触碰到地雷。游戏区域由多个隐藏格子组成,每个格子可能隐藏着地雷或数字,数字表示周围八个格子中地雷的数量。玩家通过点击格子揭示内容,使用逻辑推理逐步排除雷区。游戏考验玩家的观察力和推理能力,任何一次误点地雷都会导致游戏失败。
扫雷分为初级,中级,高级等难度,玩家也可以自行定义雷的数量与方格的大小。免费在线扫雷 (minesweeper.cn)这是网页版的扫雷游戏,大家感兴趣可以点击进入。
游戏分析与设计:
1.数据结构分析
布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些信息。初阶的扫雷(如上图)大小是9*9, 因为我们需要在9*9的棋盘上布置雷的信息和排查雷,我们⾸先想到的就是创建⼀个9*9的数组来存放信息。
• 使⽤控制台实现经典的扫雷游戏
• 游戏可以通过菜单实现继续玩或者退出游戏
• 扫雷的棋盘是9*9的格⼦ • 默认随机布置10个雷
• 可以排查雷 ◦ 如果位置不是雷,就显⽰周围有⼏个雷
◦ 如果位置是雷,就炸死游戏结束
◦ 把除10个雷之外的所有⾮雷都找出来,排雷成功,游戏结束
2.⽂件结构设计
为了便于代码的创建,我们分成三个文件来实现。
代码实现
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,为什么要创建这两个常量?下面我通过画图为大家分析
我们知道当选择A时,格子如果没有雷,则会显示周围八格的地雷数,如果没有雷则显示空格。那么如果选择C时,则显示周围五格,这将会大大提高我们的代码难度,难不成特地为边缘格子开发一个判断循环结构?所以我们就在9*9格子的基础上增加两行两列,也就是周围补上一圈!
如下图,D点也判断周围八格的地雷数!这样便于我们构造判断语句,即如果周围八格有地雷,则地雷数依次++,
所以在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开始,依次打印九个内容。那么为了美观,我们在头行头列加入数字便于我们知道这是哪行哪列,这也为我们后续的代码实现奠定基础!也可以加入扫雷字样。
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';
}
因为相同字符相减为 0 ,所以方法就是让周围八个位置的相加再减去8* ' 0 ' ,下图是进阶算法。
循环的终止条件就是当win等于除了雷以外的格子数,而每次排查没踩到雷,win++。
每次排查后,都生成下一次的棋盘!
下面是效果图与源码!
效果图:
全部源码:
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;
}
拓展:
作为一个经典的游戏,我们的代码还是不完善的,许多功能并没有实现,如:
1.在判断为雷的格子上插旗子标记!
2.加入排雷的时间显示!
3.如果排查位置不是雷,周围也没有雷,可以展开周围的一片!
4.选择游戏的难度!
。简单 9*9 10个雷
。中等 16*16 40个雷
。困难 30*16 99个雷
。自定义
小结:
本次分享就到此结束了,如果你有关于扫雷拓展与优化的想法,或者本文章中的错误与不足,欢迎在评论区中留言!!如果觉得小编写的不错,记得一键三连支持一下!!非常感谢!
标签:ROWS,int,mine,COLS,char,初识,扫雷,printf From: https://blog.csdn.net/2401_83779763/article/details/142431409