提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
- 一:游戏规则
- 二:代码实现
- 三:分布讲解
历史背景:
扫雷在科技历史上也扮演了相似的角色。这个基于数字的逻辑谜题最早来自20世纪六七十年代,当时Jerimac Ratliff推出的名为“Cube”的游戏已经非常受人欢迎。几十年后的1992年,扫雷游戏被加入了Windows3.1,这并不是为了展示Windows是游戏操作系统专家,而是为了训练用户的鼠标左右键操作能力,让这些动作变得非常自然,并培养鼠标移动的速度和准确性
一、游戏规则
1.输入(0)开始游戏,在输入一个坐标(X,Y),如果这个坐标不是雷则会显示该坐标周围有几个雷。
二、代码实现
# include "game.h"
void InitBoard(char chessboard[LINES][LISTS], int lines, int lists,char set)//初始化棋盘
{
int i = 0;//行
for (i = 0; i < lines; i++)
{
int j = 0;
for (j = 0; j < lists; j++)//列
{
chessboard[i][j] = set;
}
}
}
void DisplayBoard(char chessBoard [LINES][LISTS], int line, int list)//打印
{
int z = 0;//打印列号
for (z = 0; z <= list; z++)
{
printf("%d ", z);
}
printf("\n");
for ( int i = 1; i <= line; i++)
{
printf("%d ", i);//打印行号
for (int j = 1; j <= list; j++)//列
{
printf("%c ", chessBoard[i][j]);
}
printf("\n");
}
//printf("\n");
}
void set_mine(char chessboard[LINES][LISTS], int line, int list)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % line + 1;
int y = rand() % list + 1;
if (chessboard[x][y] == '0')
{
chessboard[x][y] = '1';
count = count - 1;
}
}
}
void Display_set_mine(char chessBoard[LINES][LISTS], int line, int list)
{
for (int i = 0; i <= line; i++)
{
for (int j = 0; j <= list; j++)
{
printf("%c ", chessBoard[i][j]);
}
printf("\n");
}
}
int Get_minefind(char mine[LINES][LISTS], 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 find_mine(char mine[LINES][LISTS], char show[LINES][LISTS], int line, int list)
{
int x = 0;
int y = 0;
int win = 0;
while (win < line * list - EASY_COUNT)
{
printf("请输入要排查的坐标\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= line && y >= 1 && y <= list)
{
if (show[x][y] == '*')
{
if (mine[x][y] == '1')
{
printf("你被炸死了\n");
DisplayBoard(mine, LINE, LIST);
break;
}
else
{
int count = Get_minefind(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, LINE, LIST);
win++;
}
}
else
{
printf("该坐标已被排查\n");
}
}
else
{
printf("坐标越界,请重新输入\n");
}
if (win == line * list - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, LINE, LIST);
}
}
}
# define _CRT_SECURE_NO_WARNINGS
# include "game.h"
void menu()
{
printf("********************\n");
printf("******1.play********\n");
printf("******0.exit********\n");
printf("********************\n");
}
void game()
{
char mine[LINES][LISTS] = { 0 };
char show[LINES][LISTS] = { 0 };
InitBoard( mine, LINES, LISTS, '0');//初始化
InitBoard(show, LINES, LISTS, '*');
//DisplayBoard(mine,LINE,LIST);//打印
DisplayBoard(show, LINE, LIST);
set_mine(mine, LINE, LIST);
//Display_set_mine(mine,LINE,LIST);
find_mine (mine,show, LINES, LISTS);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("玩游戏\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
# define _CRT_SECURE_NO_WARNINGS
#pragma once
#define LINE 9//行
#define LIST 9//列
#define LINES LINE + 2//行初始化棋盘
#define LISTS LIST + 2//列初始化棋盘
#define EASY_COUNT 10
# include<stdio.h>
# include<stdlib.h>
# include<time.h>
//初始化棋盘
void InitBoard(char chessborad[LINES][LISTS],int lines, int lists, char set);
//打印棋盘
void DisplayBoard(char chessboard[LINES][LISTS], int line, int list);
//布置地雷
void set_mine(char chessboard[LINES][LISTS],int line,int list );
//void Display_set_mine(char chessBoard[LINE][LIST], int line, int list);
int Get_minefind(char mine[LINES][LISTS], int x, int y);
void find_mine(char mine[LINES][LISTS], char show[LINES][LISTS], int line, int list);
三:分布讲解
这个扫雷游戏设计的总体思路是分成三个部分来写;
首先定义一个game.h的头文件在定义两个game.c,test.c的两个.c文件这样做的目的是为了在把各个功能分开,这样写代码的书写更规范,也更容易查出问题。test.c是用来测试程序的功能,game.c是为了实现每个函数的功能,game,h是用来包含整个程序需要的头文件以及声明的函数。
下面先从主函数说起。
首先书写主函数;
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请选择\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("玩游戏\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
这一段代码表示了程序运行的总体逻辑,首先input输入1就是进行游戏输入0就是退出,进行游戏之后就执行menu()菜单函数;这个函数就只是显示选项没有什么特殊意义,也不用返回什么值,所以就定义的是void
void menu()
{
printf("********************\n");
printf("******1.play********\n");
printf("******0.exit********\n");
printf("********************\n");
}
在接下来就如果选择1就进行游戏就执行game()函数选0就直接退出。
下面来讲一讲game()函数和game.c;
void game()
{
char mine[LINES][LISTS] = { 0 };
char show[LINES][LISTS] = { 0 };
InitBoard( mine, LINES, LISTS, '0');//初始化
InitBoard(show, LINES, LISTS, '*');
//DisplayBoard(mine,LINE,LIST);//打印
DisplayBoard(show, LINE, LIST);
set_mine(mine, LINE, LIST);
//Display_set_mine(mine,LINE,LIST);
find_mine (mine,show, LINES, LISTS);
}
这里game函数代表整个游戏的运行逻辑,他与game.c是配套使用的下面将把两个和在一起说
这个是game.c
# include "game.h"
void InitBoard(char chessboard[LINES][LISTS], int lines, int lists,char set)//初始化棋盘
{
int i = 0;//行
for (i = 0; i < lines; i++)
{
int j = 0;
for (j = 0; j < lists; j++)//列
{
chessboard[i][j] = set;
}
}
}
void DisplayBoard(char chessBoard [LINES][LISTS], int line, int list)//打印
{
int z = 0;//打印列号
for (z = 0; z <= list; z++)
{
printf("%d ", z);
}
printf("\n");
for ( int i = 1; i <= line; i++)
{
printf("%d ", i);//打印行号
for (int j = 1; j <= list; j++)//列
{
printf("%c ", chessBoard[i][j]);
}
printf("\n");
}
//printf("\n");
}
void set_mine(char chessboard[LINES][LISTS], int line, int list)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % line + 1;
int y = rand() % list + 1;
if (chessboard[x][y] == '0')
{
chessboard[x][y] = '1';
count = count - 1;
}
}
}
void Display_set_mine(char chessBoard[LINES][LISTS], int line, int list)
{
for (int i = 0; i <= line; i++)
{
for (int j = 0; j <= list; j++)
{
printf("%c ", chessBoard[i][j]);
}
printf("\n");
}
}
int Get_minefind(char mine[LINES][LISTS], 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 find_mine(char mine[LINES][LISTS], char show[LINES][LISTS], int line, int list)
{
int x = 0;
int y = 0;
int win = 0;
while (win < line * list - EASY_COUNT)
{
printf("请输入要排查的坐标\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= line && y >= 1 && y <= list)
{
if (show[x][y] == '*')
{
if (mine[x][y] == '1')
{
printf("你被炸死了\n");
DisplayBoard(mine, LINE, LIST);
break;
}
else
{
int count = Get_minefind(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, LINE, LIST);
win++;
}
}
else
{
printf("该坐标已被排查\n");
}
}
else
{
printf("坐标越界,请重新输入\n");
}
if (win == line * list - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, LINE, LIST);
}
}
}
讲之前先要看看game.h这个头文件
# define _CRT_SECURE_NO_WARNINGS
#pragma once
#define LINE 9//行
#define LIST 9//列
#define LINES LINE + 2//行初始化棋盘
#define LISTS LIST + 2//列初始化棋盘
#define EASY_COUNT 10
# include<stdio.h>
# include<stdlib.h>
# include<time.h>
//初始化棋盘
void InitBoard(char chessborad[LINES][LISTS],int lines, int lists, char set);
//打印棋盘
void DisplayBoard(char chessboard[LINES][LISTS], int line, int list);
//布置地雷
void set_mine(char chessboard[LINES][LISTS],int line,int list );
//void Display_set_mine(char chessBoard[LINE][LIST], int line, int list);
int Get_minefind(char mine[LINES][LISTS], int x, int y);
void find_mine(char mine[LINES][LISTS], char show[LINES][LISTS], int line, int list);
这里包含了整个程序运行的函数,先看看第一个 define LINE 9 和 define LIST 9,这里定义的是九行九列的棋盘,那为什么后面又要定义一个定义一个LINE + 2,和LIST+2呢,先来看看这张图;
如果定义9行9列那选择棋盘中间区域的格子进行排雷,那没问题,那如果选择最边上的呢,那数组不就越界了,要知道扫雷游戏的规则是如果你选的这个格子不是雷那就要统计他周围的一圈里面一共有多少个雷,所以说我们需要创建一个11*11的格子来防止数组越界所以说要定义define LINES LINE +2 define LISTS LIST + 2,LINE 表示行,LIST表示列。为什么要使用宏定义而不直接使用数字?那是因为如果要修改行和列,那么如果纯用数字来表示的话,那就非常麻烦就要修改整个函数里面要用到行和列的地方,那如果直接用宏定义就直接在宏定义里面修改就好了,非常的方便。
在下来就是define east count 10这个表示的是游戏模式是简单,在整个棋盘里面布置10个雷
下面就是包含要用到的库函数了
这里就不多做什么解释了。
在下来就是这个初始化棋盘函数(这个函数在game.c里)
void InitBoard(char chessboard[LINES][LISTS], int lines, int lists,char set)//初始化棋盘
{
int i = 0;//行
for (i = 0; i < lines; i++)
{
int j = 0;
for (j = 0; j < lists; j++)//列
{
chessboard[i][j] = set;
}
}
}
从这里开始就要就要用到函数传参了,这个初始化棋盘函数用到了从test.c里面的参数
InitBoard( mine, LINES, LISTS, '0')
test.c里面的InitBoard的参数称为实参而game.c里面的称为形参,什么意思呢
InitBoard( mine, LINES, LISTS, '0');//初始化
InitBoard(show, LINES, LISTS, '*');
那么为什么要这样做呢,因为我们需要创建两个棋盘一个用来展示给玩家看,一个用来布置地雷就像这样
void set_mine(char chessboard[LINES][LISTS], int line, int list)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % line + 1;
int y = rand() % list + 1;
if (chessboard[x][y] == '0')
{
chessboard[x][y] = '1';
count = count - 1;
}
}
}
EAST_COUNT就是难度为简单,布置10个雷,下面就进入while循环每布置一个地雷就减少一个直到while等于0时跳出这个循环,在下来就是产生随机数了
int rand (void);
void srand (unsigned int seed);
time_t time (time_t* timer);
time(NULL);
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
//使⽤time函数的返回值设置种⼦
//因为srand的参数是unsigned int类型,我们将time函数的返回值强制类型转换
srand((unsigned int)time(NULL));
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
return 0;
}
运行结果为;
如果想生成在一个指定范围内的数的话,可以这样写;
a + rand()%(b-a+1)
那所以说,要生成10个以内的地雷就可以这样写;
int x = rand() % line + 1;
int y = rand() % list + 1;
让程序能够生成随机地雷了以后然后就要布置雷了,那怎么判断里面到底有没有地雷呢,就可以用if判断语句用x,yx来代表横纵坐标如果某一个坐标里面没有雷就设置一个雷,雷用字符1来表示,每布置完一个地雷,地雷数量就减去一个。这就是整个布雷的思路
if (chessboard[x][y] == '0')
{
chessboard[x][y] = '1';
count = count - 1;
}
在接下来就是打印布置好的雷了,代码如下
void Display_set_mine(char chessBoard[LINES][LISTS], int line, int list)
{
for (int i = 0; i <= line; i++)
{
for (int j = 0; j <= list; j++)
{
printf("%c ", chessBoard[i][j]);
}
printf("\n");
}
}
打印出来就长这样,
在接下来最后一步就是如何排查地雷了
首先来说一下这个函数
int Get_minefind(char mine[LINES][LISTS], 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');
}
这一段表示 统计你所选的一块格子周围有多少个地雷(会用到函数嵌套,这个函数会嵌套到 find_mine这个函数里去使用),如下图黄色的区域;
因为在这个雷盘里面存放的是字符而不是数字,字符0表示没有地雷字符1表示有地雷,而0的ascii编码代号为48,字符1为49,那所以说如果要统计周围的地雷有几个就要让周围的坐标里面的字符全部加起来然后在减去8*0最后得到的结果就是有几个雷的数量。
下面再来看看这个代码,
void find_mine(char mine[LINES][LISTS], char show[LINES][LISTS], int line, int list)
{
int x = 0;
int y = 0;
int win = 0;
while (win < line * list - EASY_COUNT)
{
printf("请输入要排查的坐标\n");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= line && y >= 1 && y <= list)
{
if (show[x][y] == '*')
{
if (mine[x][y] == '1')
{
printf("你被炸死了\n");
DisplayBoard(mine, LINE, LIST);
break;
}
else
{
int count = Get_minefind(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, LINE, LIST);
win++;
}
}
else
{
printf("该坐标已被排查\n");
}
}
else
{
printf("坐标越界,请重新输入\n");
}
if (win == line * list - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, LINE, LIST);
}
}
}
这一段代码表示排雷的整体逻辑,首先要接收两个mine 和 show函数,还有行和列,定义整形x和y坐标还有win,当win等于0时就是所有坐标排查完了,就代表游戏胜利了,游戏内总共不埋地雷的个数为行*列在减去布雷数量,如果选择的每一个格子不是地雷的话win就加1,当布雷数量不在大于win时就代表游戏胜利。
在下来就进入while循环当win小于所有没布置雷的方格时就执行循环,输入要查找的坐标如下所示
if (x >= 1 && x <= line && y >= 1 && y <= list)
{
if (show[x][y] == '*')
{
if (mine[x][y] == '1')
{
printf("你被炸死了\n");
DisplayBoard(mine, LINE, LIST);
break;
如果输入的坐标刚好是字符1所在的位置,就代表你被炸死了直接退出游戏,并且打印出埋雷的棋盘,如果没有就进行下一步,如下图所示
else
{
int count = Get_minefind(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, LINE, LIST);
win++;
}
如果输入的坐标没有地雷,那么就统计以此为中心周围雷的个数如这段的第一行代码表示 ,执行Get_minefind函数(上面已经讲过),然后第二行代码就是表示统计并显示出周围雷的个数,为什么要用count +字符0来表示呢?因为这个count表示周围雷的个数,是一个整形数字,而show数组是一个字符形数组,字符类型里面存的是ascii码,假如count是2,那么字符2的ascii码就是等于字符0加3. 执行完然后win就自增1.
else
{
printf("该坐标已被排查\n");
}
}
else
{
printf("坐标越界,请重新输入\n");
}
下面这几行就是判断你输入的坐标对不对越没越界啥的 。
if (win == line * list - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, LINE, LIST);
}
最后这一行就是判断你雷扫没扫完,如果当win等于所有的格子数量减去布置的雷的个数,那么就排雷成果打印棋盘,至此扫雷游戏结束!、
下面就是展示游戏运行框图了
也可以加入一些dos命令比如清屏啥的,这里就不多做描述了。