5.5 新手保护机制
扫雷对于新手玩家和运气比较差的玩家体验很不友好,可能第一次就踩到雷被炸死了(比如我)。
该机制用于将第一次踩雷的坐标上的雷进行转移,让玩家有良好的游戏体验~
思路:
利用rand
函数生成1-9的随机数,然后转移并给出提示。
对应代码:
static void change_place(char mine[ROWS][COLS], int row, int col, int x, int y) { x = rand() % row + 1;//1 - 9的随机值 y = rand() % col + 1; mine[x][y] == '1';//转移 printf("第一次踩雷,可真有你的,重新选!\n"); }
5.6 标记与取消标记
在扫雷游戏中,右击鼠标可以对你认为是雷的点进行标记,并且可以取消。
我们是否可以用C语言实现一下呢?
示意图:
5.6.1 标记菜单
由于显示界面使用键盘操作,无法使用鼠标。所以不妨我们采用如下思想,设计一个菜单,让玩家决定排雷,或标记,或取消标记。
对应代码:
static void flag_menu() { printf("####################################\n"); printf("######### 1.选择非雷区域 ##########\n"); printf("######### 2.标记雷的位置 ##########\n"); printf("######### 3.取消雷的标记 ##########\n"); printf("####################################\n"); }
5.6.2 设置标记
思路:
将需要标记的坐标和所需参数传过去,首先需要清楚的一点是,标记 = 雷数。每标记一个位置,标记数都需要自增,这里我们选择传址调用的方式改变标记数的大小(注意:形参是实参的一份临时拷贝,所以将标记数直接传入并不能改变值)。
若坐标被排查,则需重新输入,坐标非法需要提示。
对应代码:
static void set_flag(char show[ROWS][COLS], int row, int col,int *pf) { int x = 0; int y = 0; if (*pf == EASY_COUNT) { return ;//返回,停止标记 } while (1) { printf("请输入标记坐标:>"); scanf("%d %d", &x, &y); if (x >= 1 && y >= 1 && x <= row && y <= col)//合法判断 { if (show[x][y] == '*') { show[x][y] = '#'; (*pf)++;//自增 break;//标记完退出 } else { printf("该位置已被排查,请重新输入!\n"); continue; } } else { printf("坐标非法,请重新输入!\n"); continue; } } }
标记效果:
5.6.3 取消标记
思路:
与标记的参数相同,该函数需要判断是否被标记,若被标记
,则取消标记改为*
,并且标记数需要减少
。若未标记,则退出循环,让用户重新选择是否标记,坐标非法需提示。
注:未标记后不要用cotinue
,避免无法取消标记导致死循环。
对应代码:
static void cancel_flag(char show[ROWS][COLS], int row, int col, int *pf) { int x = 0; int y = 0; while (1) { printf("请输入取消标记的坐标:>"); scanf("%d %d", &x, &y); if (x >= 1 && y >= 1 && x <= row && y <= col) { if (show[x][y] == '#') { show[x][y] = '*'; (*pf)--; break;//取消标记后退出 } else { printf("该位置未标记,无法取消标记\n"); break;//一定要break,不能用continue,否则会导致死循环 } } else { printf("坐标非法,请重新输入!\n"); continue; } } }
5.7 获取周围雷的个数
排雷过程中点击某点,当该点无雷时,需要展示该点周围八个坐标的雷的个数。
思路:
采用循环对排查坐标及周围八个点进行遍历即可。
对应代码:
static int get_mine_count(char mine[ROWS][COLS], int x, int y) { int i = 0; int count = 0; //x-1 ~ x+1 && y-1 ~ y+1 for (i = -1; i <= 1; i++) { int j = 0; for (j = -1; j <= 1; j++) { if (mine[x + i][y + j] == '1') count++; } } return count; }
5.8 递归式展开一片和接收雷的个数
扫雷游戏中,当用户点击一个坐标,如果该坐标及其周围的坐标都没有雷,通过连续判断,那么雷盘就会一次性展开一片。
相比较于自己排雷71次,这样的设计也优化了玩家的体验。
示意图:
那么这个思想的原理是什么呢?
展开一片,无非就是对于排查坐标和周围八个坐标进行排查,若周围坐标周围均无雷,便可以循环往复,直到周围坐标已被排查,这也正是递归
的思想。
另外对于周围雷数的返回值也可以在这个函数中接收,并将返回值转化为字符
储存到展示的棋盘中。
注:p为需排查数:win的地址,递归展开时仍需要自增
,让游戏正常结束。
对应代码:
static void boom_board(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* p) { if (x >= 1 && x <= row && y >= 1 && y <= col) { int ret = get_mine_count(mine, x, y);//接收排查点周围坐标雷的个数 if (ret == 0) { (*p)++;//自增 show[x][y] = ' ';//置为空 int i = 0; for (i = -1; i <= 1; i++)//遍历 { int j = 0; for (j = -1; j <= 1; j++) { if (show[x + i][y + j] == '*')//未排查则递归,避免重复形成死递归 boom_board(mine, show, row, col, x + i, y + j, p); } } } else { (*p)++;//自增 show[x][y] = ret + '0';//转化为字符 } } }
5.9 排查雷
美名其曰是排雷,其实该过程就是一个调用展开一片,标记,取消标记等函数,以及根据结果判断是否获胜的过程。
话不多说,我们在代码中看:
void fine_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { int x = 0; int y = 0; int win = 0;//排查数,目前为71 int* p = &win; int op = 0;//菜单选项 int fch = 1;//判断是否为第一次排查的变量 unsigned int flag_count = 0;//该变量为标记数,恒>0,标记数<=雷数 int* pf = &flag_count; while (win < row * col - EASY_COUNT) { again: flag_menu();//调用菜单 scanf("%d", &op); if (op == 1) { printf("请输入要排查的坐标:>"); scanf("%d %d", &x, &y); if (x >= 1 && y >= 1 && x <= row && y <= col)//合法判断 { if (fch == 1 && mine[x][y] == '1')//第一次排查且为雷 { change_place(mine, row, col, x, y); fch++;//自增防止多次调用换位函数 } else { if(show[x][y] == '*') { if (mine[x][y] == '1') { system("cls");//清屏 printf("很遗憾,你被雷炸死了!\n"); printf("游戏结束!\n"); show_board(mine, row, col);//复盘 break; } else { boom_board(mine, show, row, col, x, y, p);//展开一片 system("cls");//清屏 show_board(show, row, col); } fch++;//自增防止多次调用换位函数 } else { printf("该坐标已被排查,请重新输入\n"); } } } else { printf("非法坐标,请重新输入!\n"); continue; } } else if (op == 2)//标记 { set_flag(show, row, col, pf);//传址 flag_count = *pf; system("cls");//清屏 if (*pf == 10) { printf("标记数和雷数相等,无法标记!\n"); } show_board(show, row, col); } else if (op == 3) { cancel_flag(show, row, col, pf);//传址 flag_count = *pf; system("cls");//清屏 show_board(show, row, col); } else { printf("选择错误,请重新选择:>\n"); goto again;//跳转到选择处 } } if (win == row * col -EASY_COUNT) { system("cls"); show_board(show, ROW, COL);//最后一步展示 printf("恭喜你,扫雷成功!\n"); printf("获得称号,排雷战士!\n"); printf("答案揭晓:\n"); show_board(mine, ROW, COL);//初始答案展示 } }
6. 完整代码
game.h
#pragma once #define ROW 9 #define COL 9 #define ROWS ROW + 2 #define COLS COL + 2 #define EASY_COUNT 10//雷数 #include<stdio.h> #include<stdlib.h> #include<time.h> #include<windows.h> //初始化棋盘 void init_board(char arr[ROWS][COLS], int rows, int cols, char set); //布置雷 void set_mine(char mine[ROWS][COLS], int row, int col); //打印棋盘 void show_board(char arr[ROWS][COLS], int row, int col); //排查雷 void fine_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
game.c
#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" void init_board(char arr[ROWS][COLS], int rows, int cols, char set) { int i = 0; for (i = 0; i < rows; i++) { int j = 0; for (j = 0; j < cols; j++) { arr[i][j] = set; } } } void set_mine(char mine[ROWS][COLS], int row, int col) { int count = EASY_COUNT; int x = 0; int y = 0; while (count) { x = rand() % row + 1; y = rand() % col + 1; if (mine[x][y] == '0') { mine[x][y] = '1'; count--; } } } void show_board(char arr[ROWS][COLS], int row, int col) { int i = 0; printf("------------扫雷------------\n");//分割线 for (i = 0; i <= col; i++) { printf("%d ", i);//输出对应列信息 } printf("\n"); for (i = 1; i <= row; i++) { int j = 0; printf("%d ", i);//输出对应行信息 for (j = 1; j <= col; j++) { printf("%c ", arr[i][j]); } printf("\n"); } printf("------------扫雷------------\n"); } static void flag_menu() { printf("####################################\n"); printf("######### 1.选择非雷区域 ##########\n"); printf("######### 2.标记雷的位置 ##########\n"); printf("######### 3.取消雷的标记 ##########\n"); printf("####################################\n"); } //标记雷的位置 static void set_flag(char show[ROWS][COLS], int row, int col,int *pf) { int x = 0; int y = 0; if (*pf == EASY_COUNT) { return ;//返回,停止标记 } while (1) { printf("请输入标记坐标:>"); scanf("%d %d", &x, &y); if (x >= 1 && y >= 1 && x <= row && y <= col)//合法判断 { if (show[x][y] == '*') { show[x][y] = '#'; (*pf)++;//自增 break;//标记完退出 } else { printf("该位置已被排查,请重新输入!\n"); continue; } } else { printf("坐标非法,请重新输入!\n"); continue; } } } //取消标记 static void cancel_flag(char show[ROWS][COLS], int row, int col, int *pf) { int x = 0; int y = 0; while (1) { printf("请输入取消标记的坐标:>"); scanf("%d %d", &x, &y); if (x >= 1 && y >= 1 && x <= row && y <= col) { if (show[x][y] == '#') { show[x][y] = '*'; (*pf)--; break;//取消标记后退出 } else { printf("该位置未标记,无法取消标记\n"); break;//一定要break,不能用continue,否则会导致死循环 } } } else { printf("坐标非法,请重新输入!\n"); continue; } } } static void change_place(char mine[ROWS][COLS], int row, int col, int x, int y) { x = rand() % row + 1; y = rand() % col + 1; mine[x][y] == '1'; printf("第一次踩雷,可真有你的,重新选!\n"); } static int get_mine_count(char mine[ROWS][COLS], int x, int y) { int i = 0; int count = 0; for (i = -1; i <= 1; i++) { int j = 0; for (j = -1; j <= 1; j++) { if (mine[x + i][y + j] == '1') count++; } } return count; } //爆炸展开 static void boom_board(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* p) { if (x >= 1 && x <= row && y >= 1 && y <= col) { int ret = get_mine_count(mine, x, y);//接收排查点周围坐标雷的个数 if (ret == 0) { (*p)++;//自增 show[x][y] = ' ';//置为空 int i = 0; for (i = -1; i <= 1; i++)//遍历 { int j = 0; for (j = -1; j <= 1; j++) { if (show[x + i][y + j] == '*')//未排查则递归,避免重复形成死递归 boom_board(mine, show, row, col, x + i, y + j, p); } } } else { (*p)++;//自增 show[x][y] = ret + '0';//转化为字符 } } } void fine_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { int x = 0; int y = 0; int win = 0;//排查数,目前为71 int* p = &win; int op = 0;//菜单选项 int fch = 1;//判断是否为第一次排查的变量 unsigned int flag_count = 0;//该变量为标记数,恒>0,标记数<=雷数 int* pf = &flag_count; while (win < row * col - EASY_COUNT) { again: flag_menu();//调用菜单 scanf("%d", &op); if (op == 1) { printf("请输入要排查的坐标:>"); scanf("%d %d", &x, &y); if (x >= 1 && y >= 1 && x <= row && y <= col)//合法判断 { if (fch == 1 && mine[x][y] == '1')//第一次排查且为雷 { change_place(mine, row, col, x, y); fch++;//自增防止多次调用换位函数 } else { if(show[x][y] == '*') { if (mine[x][y] == '1') { system("cls");//清屏 printf("很遗憾,你被雷炸死了!\n"); printf("游戏结束!\n"); show_board(mine, row, col);//复盘 break; } else { boom_board(mine, show, row, col, x, y, p);//展开一片 system("cls");//清屏 show_board(show, row, col); } fch++;//自增防止多次调用换位函数 } else { printf("该坐标已被排查,请重新输入\n"); } } } else { printf("非法坐标,请重新输入!\n"); continue; } } else if (op == 2)//标记 { set_flag(show, row, col, pf);//传址 flag_count = *pf; system("cls");//清屏 if (*pf == 10) { printf("标记数和雷数相等,无法标记!\n"); } show_board(show, row, col); } else if (op == 3) { cancel_flag(show, row, col, pf);//传址 flag_count = *pf; system("cls");//清屏 show_board(show, row, col); } else { printf("选择错误,请重新选择:>\n"); goto again;//跳转到选择处 } } if (win == row * col -EASY_COUNT) { system("cls"); show_board(show, ROW, COL);//最后一步展示 printf("恭喜你,扫雷成功!\n"); printf("获得称号,排雷战士!\n"); printf("答案揭晓:\n"); show_board(mine, ROW, COL);//初始答案展示 } }
test.c
#define _CRT_SECURE_NO_WARNINGS 1 #include"game.h" void menu() { printf("###################################\n"); printf("############ 1.play ############\n"); printf("############ 0.exit ############\n"); printf("###################################\n"); } void game() { char mine[ROWS][COLS] = { 0 }; char show[ROWS][COLS] = { 0 }; //初始化棋盘 init_board(mine, ROWS, COLS, '0'); init_board(show, ROWS, COLS, '*'); //布置雷 set_mine(mine, ROW, COL); system("cls"); //打印棋盘 show_board(show, ROW, COL); //排查雷 fine_mine(mine, show, ROW, COL); } int main() { int input = 0; srand((unsigned int)time(NULL)); do { menu(); printf("请选择:>"); scanf("%d", &input); switch (input) { case 1: game(); break; case 0: printf("退出游戏\n"); break; default: printf("选择错误,请重新选择:>\n"); break; } } while (input); return 0; }
7. 动画展示
8. 结语
到这里,一个还原度较高的扫雷游戏就实现成功了!
以上就是C语言实现扫雷的全部内容,如果觉得anduin写的还不错的话,还请一键三连!
我是anduin,一名C语言初学者,我们下期见!