扫雷网页版这是具体的扫雷游戏。
我们可以在这个游戏中借鉴参考,一步步地将其中的功能用C语言构思出来。
1.我们可以定义两个二维字符数组,分别是hid和sur,hid字符数组存储着'0'和'1',其中'0'代表不是地雷,'1'代表是地雷。sur字符数组最开始存储'#',当我们扫某个坐标的雷时,如果这个坐标是雷,就会结束游戏,否则会调用一个函数计算它周围8个坐标雷的数量,然后把这个数量存储到这个坐标中。
2.初始化数组(棋盘),把hid全部赋值为'0',sur全部赋值为'#'。
3.布置雷,随机在hid棋盘上赋值为'1',而这个'1'的数量就是雷的数量。
4.打印棋盘,给玩家看到。
5.计算雷的数量,玩家输入要扫雷的坐标,如果是雷,游戏结束,如果不是雷,计算它周围雷的数量,这里我们可以知道如果我们输入的坐标是棋盘的边界的话,就要分情况计算周围雷的数量,例如一个棋盘长宽都是9,如果坐标在(0,0),我们要计算周围3个坐标雷的数量,如果坐标在(0,1),我们要计算周围5个坐标雷的数量,这样要判断坐标的位置,十分麻烦,我们可以定义一个11x11大小的棋盘,最外一圈都是'0',然后取居中的9x9就好,这样就算坐在边界,也是一个方法计算,我们定义中间那块棋盘为居中棋盘。
代码是分成了3个文件,main.c是选择开始或者退出的函数,game.h则保存着宏定义和函数的定义,game.c是函数的具体实现。
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
system("cls");
menu1(); //菜单1
printf("请选择(0/1): ");
scanf("%d", &input);
system("cls");
switch (input)
{
case 1:
game();
break;
case 0:
printf("\n退出成功!");
Sleep(3000);
break;
default:
printf("选择非法,请选择(0/1): ");
Sleep(1000);
break;
}
} while (input);
return 0;
}
其中Sleep()函数可以让计算机程序休眠,不至于有些信息一闪而过,其中单位是ms,那么Sleep(1000)就是睡眠1s,头文件是windows.h。
system("cls")函数是清屏的意思,执行完这行代码就会将控制台的信息清空,头文件也是windows.h。
#pragma once
#include
#include
#include
#include
//基础模式
#define BaseR 11 //真实的棋盘长宽
#define BaseC 11
#define DisBaseR BaseR-2 //展示的棋盘长宽
#define DisBaseC BaseC-2
#define BaseThunder 10
//中级模式
#define Mid_R 16 //真实的棋盘长宽
#define Mid_C 16
#define DisMidR Mid_R-2 //展示的棋盘长宽
#define DisMidC Mid_C-2
#define MidThunder 40
//专家模式
#define expert_R 18 //真实的棋盘长宽
#define expert_C 32
#define Dis_expert_R expert_R-2 //展示的棋盘长宽
#define Dis_expert_C expert_C-2
#define expertThunder 99
//游戏开始界面
void menu1();
//难度选择界面
void menu2();
//游戏函数
void game();
//扫雷游戏基础难度
void mine_sweeper();
//中级难度
void mine_sweeper2();
//专家难度
void mine_sweeper3();
//初始化棋盘
void Init_board(char *board, int r, int c, char ch);
//打印棋盘
void Disply_board(char* board, int r, int c, int C);
//设置雷
void Set_Bomb(char* board,int r,int c, int C,int num);
//排雷
int de_Bomb(char* board, char* board2, int r, int c,int *st,int R,int C,int num);
//计算周围雷的数量
void Num_Bomb(char* board, char* board2, int x, int y, int C);
//判断是否排完了全部雷
int Ju_Win(char* board, char* board2,int r,int c, int C);
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu1()
{
printf("*******扫雷********\n");
printf("*****1.开始游戏****\n");
printf("*****0.退出********\n");
printf("*******************\n");
printf("格子里的数字表示这个格子的周围一圈(8格)有几个雷\n\n");
}
void menu2()
{
printf("*****1.基础*****\n");
printf("*****2.中级*****\n");
printf("*****3.专家*****\n\n");
}
void game()
{
int input = 0;
while (1)
{
system("cls");
menu2();
printf("请选择游戏难度(1/2/3): ");
scanf("%d", &input);
system("cls");
switch (input)
{
case 1:
mine_sweeper();
goto end;
break;
case 2:
mine_sweeper2();
goto end;
break;
case 3:
mine_sweeper3();
goto end;
break;
default:
printf("请输入合法的选项(1/2/3)");
Sleep(1000);
break;
}
}
end:
printf("游戏已经退出!\n");
Sleep(3000);
}
void mine_sweeper()
{
char hid[BaseR][BaseC] = {'0'};
char sur[BaseR][BaseC] = {'0'};
Init_board(hid, BaseR, BaseC, '0');
Init_board(sur, BaseR, BaseC, '#');
for (int i = 0; i 9)
printf("%d ", i);
else
printf("%d ",i);
}
printf("\n");
for (int i = 1; i <= r; i++)
{
if (i > 9)
printf("%d ", i);
else
printf("%d ", i);
for (int j = 1; j <=c; j++)
{
printf("%c ", *(board + i * C + j));
}
printf("\n");
}
}
void Set_Bomb(char* board, int r, int c, int C,int num)
{
while (num)
{
int x = rand() % r + 1;
int y = rand() % c + 1;
if (*(board + x* C + y) != '1')
{
*(board + x * C + y) = '1';
num--;
}
else
continue;
}
}
int de_Bomb(char* board, char* board2,int r, int c,int *st, int R,int C,int num)
{
int x, y;
int flag = 0;
while (1)
{
printf("标雷是1,排雷是0,请输入(0/1): ");
scanf("%d", &flag);
switch (flag)
{
case 0:
goto de_bomb;
break;
case 1:
goto mark;
break;
default:
printf("请选择(0/1):");
Sleep(1000);
system("cls");
}
}
de_bomb:
while (1)
{
printf("选择你排雷的坐标: ");
scanf("%d %d", &x, &y);
while (1)
{
if (*(board + x * C + y) == '1' && *st > 0)
{
Init_board(board, R, C, '0');
Set_Bomb(board, r, c,C,num);
}
else
break;
}
*st = -2;
if (x >= 1 && x <= r && y >= 1 && y <= c)
{
if (*(board2 + x * C + y) != '#'&& *(board2 + x * C + y) != '*')
{
printf("排雷重复,请选择其他的坐标: ");
continue;
}
if (*(board + x * C + y) == '0')
{
Num_Bomb(board, board2, x, y,C);
return 0;
}
else
{
printf("炸了,游戏失败\n");
return 1;
}
}
printf("请输入正确的坐标x:(1~9)y:(1~9): ");
}
mark:
while (1)
{
printf("选择你标记雷的坐标: ");
scanf("%d %d", &x, &y);
if (*(board2 + x * C + y) == '#')
{
*(board2 + x * C + y) = '*';
return 0;
}
else
printf("请选择未排过的坐标来标记! ");
}
}
void Num_Bomb(char* board, char* board2, int x, int y, int C)
{
*(board2 + x * C + y) ='0'+(
*(board + x * C + y-1) +
*(board + x * C + y+1) +
*(board + (x-1) * C + y) +
*(board + (x-1) * C + y-1) +
*(board + (x-1) * C + y+1) +
*(board + (x+1) * C + y-1) +
*(board + (x+1) * C + y+1) +
*(board + (x+1) * C + y) -8*'0');
if (*(board2 + x * C + y) == '0')
{
if(*(board2 + (x - 1) * C + y) =='#')
Num_Bomb(board, board2, x - 1, y,C);
if (*(board2 + (x + 1) * C + y) == '#')
Num_Bomb(board, board2, x + 1, y, C);
if (*(board2 + x * C + y + 1) == '#')
Num_Bomb(board, board2, x, y + 1, C);
if (*(board2 + (x - 1) * C + y + 1) == '#')
Num_Bomb(board, board2, x - 1, y + 1, C);
if (*(board2 + (x + 1) * C + y + 1) == '#')
Num_Bomb(board, board2, x + 1, y + 1, C);
if (*(board2 + x * C + y - 1) == '#')
Num_Bomb(board, board2, x, y - 1, C);
if (*(board2 + (x - 1) * C + y - 1) == '#')
Num_Bomb(board, board2, x - 1, y - 1, C);
if (*(board2 + (x + 1) * C + y - 1) == '#')
Num_Bomb(board, board2, x + 1, y - 1, C);
}
if (*(board2 + x * C + y) == '0')
*(board2 + x * C + y) = ' ';
}
int Ju_Win(char* board, char* board2,int r,int c,int C)
{
for (int i = 1; i < r; i++)
{
for (int j = 1; j < c; j++)
{
if (*(board + i * C + j) == '0' && *(board2 + i * C + j) == '#')
return 0;
}
}
return 1;
}
void mine_sweeper2()
{
char hid[Mid_R][Mid_C] = { '0' };
char sur[Mid_R][Mid_C] = { '0' };
Init_board(hid, Mid_R, Mid_C, '0');
Init_board(sur, Mid_R, Mid_C, '#');
for (int i = 0; i < Mid_R; i++)
{
if (i == 0 || i == Mid_C - 1)
{
for (int j = 0; j < Mid_R; j++)
{
sur[i][j] = '-';
}
}
else
{
sur[i][0] = '-';
sur[i][Mid_C - 1] = '-';
}
}
Set_Bomb(hid, DisMidR, DisMidC, Mid_C, MidThunder);
int win = 0;
int st = 1;
while (1)
{
Disply_board(sur, DisMidR, DisMidC, Mid_C);
int res = de_Bomb(hid, sur, DisMidR, DisMidC, &st, Mid_R,Mid_C, MidThunder);
if (res)
break;
win = Ju_Win(hid, sur, DisMidR, DisMidC, Mid_C);
if (win)
break;
system("cls");
}
if (win)
printf("你赢了,排完了全部雷!\n");
}
void mine_sweeper3()
{
char hid[expert_R][expert_C] = { '0' };
char sur[expert_R][expert_C] = { '0' };
Init_board(hid, expert_R, expert_C, '0');
Init_board(sur, expert_R, expert_C, '#');
for (int i = 0; i < expert_R; i++)
{
if (i == 0 || i == expert_R - 1)
{
for (int j = 0; j < expert_C; j++)
{
sur[i][j] = '-';
}
}
else
{
sur[i][0] = '-';
sur[i][expert_C - 1] = '-';
}
}
Set_Bomb(hid, Dis_expert_R, Dis_expert_C, expert_C, expertThunder);
int win = 0;
int st = 1;
while (1)
{
Disply_board(sur, Dis_expert_R, Dis_expert_C, expert_C);
int res = de_Bomb(hid, sur, Dis_expert_R, Dis_expert_C, &st, expert_R, expert_C, expertThunder);
if (res)
break;
win = Ju_Win(hid, sur, Dis_expert_R, Dis_expert_C, expert_C);
if (win)
break;
system("cls");
}
if (win)
printf("你赢了,排完了全部雷!\n");
}
void menu1()
{
printf("*******扫雷********\n");
printf("*****1.开始游戏****\n");
printf("*****0.退出********\n");
printf("*******************\n");
}
void menu2()
{
printf("*****1.基础*****\n");
printf("*****2.中级*****\n");
printf("*****3.专家*****\n\n");
}
void game()
{
int input = 0;
while (1)
{
system("cls");
menu2(); //菜单2
printf("请选择游戏难度(1/2/3): ");
scanf("%d", &input);
system("cls");
switch (input)
{
case 1:
mine_sweeper(); //基础难度
goto end;
break;
case 2:
mine_sweeper2(); //中级难度
goto end;
break;
case 3:
mine_sweeper3(); //专家难度
goto end;
break;
default:
printf("请输入合法的选项(1/2/3)");
Sleep(1000);
break;
}
}
end:
printf("游戏已经退出!\n");
Sleep(3000);
}
goto语句可以去到标志的地方,例如end: 是一个标志,只要玩了一把过后就会退出游戏,回到主函数选择是否游戏。
三种难度就只有棋盘大小不同,所以我就只讲基础难度,以下是基础难度的函数。
void mine_sweeper()
{
char hid[BaseR][BaseC] = {'0'}; //先创建两个字符数组
char sur[BaseR][BaseC] = {'0'};
Init_board(hid, BaseR, BaseC, '0'); //这里是初始化两个字符数组
Init_board(sur, BaseR, BaseC, '#');
for (int i = 0; i
void Init_board(char* board, int r, int c, char ch)
{
//r 和 c是真实棋盘的长和宽,其他函数的r和c都是居中棋盘的长和宽,居中棋盘在思路里有定义
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
*( board+i*c+j) = ch;
}
}
}
因为二维数组在内存中是连续存放的,所以二维数组用一级指针接收,c是列数,所以i*c是跳过i行的意思,所以*(board+i*c+j)和数组[i][j]是等价的,char ch存放我们要用什么字符来初始化。
void Disply_board(char* board, int r, int c, int C)
{
这里的r 和 c 是居中棋盘的长和宽,C是真实的列数
for (int i = 0; i <= c; i++)
{
if(i>9)
printf("%d ", i);
else
printf("%d ",i);
}
printf("\n");
for (int i = 1; i <= r; i++)
{
if (i > 9)
printf("%d ", i);
else
printf("%d ", i);
for (int j = 1; j <=c; j++)
{
printf("%c ", *(board + i * C + j));
}
printf("\n");
}
}
一位数和两位数要分情况打印,不然会不整齐。
void Set_Bomb(char* board, int r, int c, int C,int num)
{
//num是雷的个数,C是真实的列数
while (num)
{
int x = rand() % r + 1;
int y = rand() % c + 1;
//x,y的随机范围是1~9
if (*(board + x* C + y) != '1') //如果不是雷的话就布置雷,同时雷的数量-1
{
*(board + x * C + y) = '1';
num--;
}
else
continue;
}
}
int de_Bomb(char* board, char* board2,int r, int c,int *st, int R,int C,int num)
{
int x, y;
int flag = 0; //决定这步操作是标记雷还是排雷
while (1)
{
printf("标雷是1,排雷是0,请输入(0/1): ");
scanf("%d", &flag);
switch (flag)
{
case 0:
goto de_bomb; //去排雷
break;
case 1:
goto mark; //去标记雷
break;
default:
printf("请选择(0/1):");
Sleep(1000);
system("cls");
}
}
de_bomb:
while (1)
{
printf("选择你排雷的坐标: ");
scanf("%d %d", &x, &y);
while (1)
{
if (*(board + x * C + y) == '1' && *st > 0)
{
//如果这一次排到雷并且是第一次
//那么就会初始化棋盘并且重新布置雷的位置
//直到玩家选择的排雷坐标不是雷
Init_board(board, R, C, '0');
Set_Bomb(board, r, c,C,num);
}
else
break;
}
*st = -2; //当第一次不是雷或者已经不是第一次了,就将*st赋值为-2,*st就是之前的st
//这样以后就再也不会用到,上面的代码,确保了第一次操作不是雷
if (x >= 1 && x <= r && y >= 1 && y <= c) //坐标合法
{
if (*(board2 + x * C + y) != '#'&& *(board2 + x * C + y) != '*')
{
//如果棋盘已经排了或者标记过了,就提示重复了
printf("排雷重复,请选择其他的坐标: ");
continue;
}
if (*(board + x * C + y) == '0') //棋盘1如果是'0',就计算它周围雷的数量
{
Num_Bomb(board, board2, x, y,C);
return 0;
}
else //代表棋盘1是'1',是雷
{
printf("炸了,游戏失败\n");
return 1;
}
}
printf("请输入正确的坐标x:(1~9)y:(1~9): ");
}
mark:
while (1)
{
printf("选择你标记雷的坐标: ");
scanf("%d %d", &x, &y);
if (*(board2 + x * C + y) == '#') //只有这个棋盘2还是未知才可以标记
{
*(board2 + x * C + y) = '*'; //标记过后就变成'*'
return 0;
}
else
printf("请选择未排过的坐标来标记! ");
}
}
void Num_Bomb(char* board, char* board2, int x, int y, int C)
{
*(board2 + x * C + y) ='0'+(
*(board + x * C + y-1) +
*(board + x * C + y+1) +
*(board + (x-1) * C + y) +
*(board + (x-1) * C + y-1) +
*(board + (x-1) * C + y+1) +
*(board + (x+1) * C + y-1) +
*(board + (x+1) * C + y+1) +
*(board + (x+1) * C + y) -8*'0');
if (*(board2 + x * C + y) == '0') //周围8个坐标的递归
{
if(*(board2 + (x - 1) * C + y) =='#')
Num_Bomb(board, board2, x - 1, y,C);
if (*(board2 + (x + 1) * C + y) == '#')
Num_Bomb(board, board2, x + 1, y, C);
if (*(board2 + x * C + y + 1) == '#')
Num_Bomb(board, board2, x, y + 1, C);
if (*(board2 + (x - 1) * C + y + 1) == '#')
Num_Bomb(board, board2, x - 1, y + 1, C);
if (*(board2 + (x + 1) * C + y + 1) == '#')
Num_Bomb(board, board2, x + 1, y + 1, C);
if (*(board2 + x * C + y - 1) == '#')
Num_Bomb(board, board2, x, y - 1, C);
if (*(board2 + (x - 1) * C + y - 1) == '#')
Num_Bomb(board, board2, x - 1, y - 1, C);
if (*(board2 + (x + 1) * C + y - 1) == '#')
Num_Bomb(board, board2, x + 1, y - 1, C);
}
if (*(board2 + x * C + y) == '0') //如果这个坐标雷的数量是0个,将它赋值为空格
*(board2 + x * C + y) = ' ';
}
首先将棋盘1中这个坐标周围8个坐标的雷加起来,然后存放到棋盘2中。
那么什么情况下要递归呢
1.这个坐标雷的数量是'0'
2.周围某个坐标还没有被排过
如果没有条件2,因为如果两个相邻的坐标周围雷的数量都是'0',那么他们两个会无限递归
int Ju_Win(char* board, char* board2,int r,int c,int C)
{
for (int i = 1; i < r; i++)
{
for (int j = 1; j < c; j++)
{
if (*(board + i * C + j) == '0' && *(board2 + i * C + j) == '#')
return 0;
}
}
return 1;
}
如果遍历两个数组,如果某个坐标不是雷并且还没被排过,那就还没胜利,就返回0。如果都不满足,就返回1。
写得比较粗糙,因为有些知识我还不太了解,比如二维数组传参,我这里每个函数都是用一级指针接收,并且加了一个变量(列数)C,模拟了二维数组来使用