游戏介绍。
💡首先给我们一个雷盘,刚开始所有点都是不可见的,然后玩家猜测,如果周围八格没有雷就显示空白,然后周围坐标也没有雷的话展开至有雷的坐标,通过周围八格有几个雷就显示数字几,通过数字来判断周围雷的位置,还可以设置标记,如果排除所有的雷就算游戏成功。
越界访问问题引出。
📜说明:为了防止在后边出现问题,我们在前边就提出这个问题,如果玩家输入的是期盼边缘的位置,那么遍历周围八个位置那就会有越界的情况,例如
💭如何解决呢?可以扩展一下数组,例如要用到三乘三的数组,我们可以创建一个四乘四的数组,只用到里面的三乘三的部分。在初始化时要传入四乘四的部分,这样就不用很麻烦特殊位置特殊处理,还不用担心越界的问题。
我们在game.h里定义一下,需要用到哪个就传哪个
#define ROW 9 #define COL 9 #define COLS COL+2 #define ROWS ROW+2
总体思路。
⛳️我们仍然用分文件的方式编写,棋盘的话,利用数组来模拟实现,假设字符数组刚开始全部都为*,玩家选中该位置后,排查该位置周围的8个位置,如果有雷则改为相应雷的数量,然而我们一个字符数组既要是*,又要改变为雷的数量,那么雷放在哪里呢,再设立一个字符数组,命名为mine数组,里面装着雷,如果该位置为雷,就设立为字符1,在玩家输入坐标后,我们判断该位置是否为雷,不是雷就遍历周围的八个位置,得到雷的数量,在第一个字符数组中打印出来,第一个字符命名为show数组。
游戏的主体。
int main() { srand((unsigned int)time(NULL)); int key = 0; do { menu(); printf("请选择;>"); scanf("%d", &key); switch (key) { case 1: printf("游戏开始\n"); game(); break; case 0:break; default: printf("选择错误,重新选择\n"); } } while(key); return 0; }
📘do while循环先打印菜单,菜单我们就省略了,在这里我们用输入的key来判断是否进入游戏循环(srand函数是因为后边要随机生成雷的位置。)
接下来要进行game函数的实现
⭐ 首先来创建两个数组,一个命名为show,一个命名为mine
初始化函数的编写。
💭在game.c中实现,在game.h中声明。(下边实现某些功能的函数都是如此)
代码如下
void initboard(char board[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++) { board[i][j] = set; } } }
🐞注意:我们要初始化的数组有两个,因为要初始化的内容不同,如果在函数里写死的话,要用两个初始化函数才能初始化完成,我们可以多传一个参数,传过来不同的数组初始化为不同的值。还要注意的是传参行和列要传ROWS和COLS,初始化大的数组。
实现打印功能。
void displayboard(char board[ROWS][COLS], int row, int col)//打印出show数组元素 { printf("--------扫雷游戏-------\n"); int i = 0; for (i = 1; i < row+1; i++) { int j = 0; for (j = 1; j < col+1; j++) { printf("%c ",board[i][j]); } printf("\n"); } }
🌱注意:传入相应的数组,还有行和列,这个时候我们就不需要看外围的了,直接传ROW和COL即可。
然而想让玩家很直观的看到每个位置的行和列,而不是一个一个位置数,可以逐行逐列打印行号和列号。优化代码如下。
void displayboard(char board[ROWS][COLS], int row, int col)//打印出show数组元素 { printf("--------扫雷游戏-------\n"); int i = 0; for (int i = 0; i < 10; i++) { printf("%d ", i); } printf("\n");//打印列号 for (i = 1; i < row+1; i++) { int j = 0; printf("%d ", i );//打印行号 for (j = 1; j < col+1; j++) { printf("%c ",board[i][j]); } printf("\n"); } }
初步工作完成
在game函数中创建数组:
char mine[ROWS][COLS] = {0};
char show[ROWS][COLS] = {0};
在game()中初始化函数如下
initboard(mine, ROWS, COLS, '0');
initboard(show, ROWS, COLS, '*');
在game()函数中,我们将两个字符数组都打印出来
displayboard(mine, ROW, COL);
displayboard(show, ROW, COL);
📑运行后如图
布置雷。
在mine数组中,随机更改部分0变为1,需要使用到srand函数产生随机值,假设有十个雷。
//随机生成雷 void PutMine(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] == '0') { board[x][y] = '1'; count--; } } }
👉这里使用了rand,前边我们已经在主函数中添加了srand((unsigned int)time(NULL));这里写成了一个死循环,只有当生成10个雷之后,才能跳出循环,注意的是,生成雷的坐标在ROW和COL空间内。雷的个数count被定义为EASY_COUNT,这里是我们在game.h中定义的雷的个数,方便我们后续的改写。
查找雷的环节
👉当放置雷之后,接下来就到了,玩家输入坐标,然后判断该位置是否为雷,如果为雷,就提示玩家被炸死了,且打印雷的位置给玩家看,如果该位置不是雷,那就返回周围八个坐标雷的数量,在show数组打印出来。
void findMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col) { int x=0, y = 0; int flag = 0; while(flag<row*col- EASY_COUNT) { printf("请输入猜测坐标\n"); scanf("%d %d", &x, &y); if (x>1&&x<=row&&y>1,y<=col) { if (mine[x][y] == '1') { printf("很遗憾,您被炸死了\n"); displayboard(mine, ROW, COL); break; } else { if (show[x][y] != '*') { printf("该坐标已被查找,请重新输入"); } //不是雷且不重复,就统计雷的个数. else { system("cls");//清空屏幕 int count = GetMineCounter(mine, x, y); show[x][y] = count + '0'; displayboard(show, ROW, COL); flag++; } } } else { printf("坐标非法,请重新输入\n"); } } if (flag == row*col-EASY_COUNT) { printf("恭喜您,排雷成功\n"); displayboard(mine, ROW,COL); } }
判断循环是否结束,就是判断是否查找出所有的雷
①如果玩家输入的位置是雷,就提示游戏结束,同时打印出雷的数组,让玩家看具体那个位置是雷
②如果输入已经输入过的坐标,那就提示玩家该坐标已经被查找过。
如果输入的坐标不正确,就提示玩家,不找出所有雷或者不被炸死,循环就不会结束,跳出循环后。
③如果是被炸死,那么就没有找出所有的雷。如果没被炸死,flag一直加加,知道等于总数减去雷的数量,这是就输出排雷成功。
💡system("cls")清空屏幕,让游戏更加美观。
⭐计算某位置周围雷数量
想要知道该坐标周围雷的数量,利用GetMineCounter函数来解决,在game.h中声明,看代码,原理很简单。
GetMineCounter(char mine[ROWS][COLS], int x, int y) { int i = 0; int j = 0; int count = 0; for (i = x - 1; i <= x + 1; i++) { for (j = y - 1; j <= y + 1; j++) { if (mine[i][j] == '1') { count++; } } } return count; }
返回的是雷的数量,为int型的,可以根据ASCLL,在findmine函数里返回值加上字符‘0’,就将其转化为字符类型。
👑打开网页版排雷小游戏,上面有标记雷的功能,而且选中一个不是雷的位置会展开很大一片
接下来实现这两个功能。
⭐标记功能
在查找一个位置后,直接弹出选项,是否需要标记某一位置,我们在findmine函数中没有踩到雷时加入以下几行代码。
void findMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col) { int x=0, y = 0; int flag = 0; while(flag<row*col- EASY_COUNT) { printf("请输入猜测坐标\n"); scanf("%d %d", &x, &y); if (x>1&&x<=row&&y>1,y<=col) { if (mine[x][y] == '1') { printf("很遗憾,您被炸死了\n"); displayboard(mine, ROW, COL); break; } else { if (show[x][y] != '*') { printf("该坐标已被查找,请重新输入"); } //不是雷且不重复,就统计雷的个数. else { int count = GetMineCounter(mine, x, y); show[x][y] = count + '0'; displayboard(show, ROW, COL); flag++; printf("是否需要标记某位置:(1/0)"); int map = 0; scanf("%d", &map); if (map == 1) { int a = 0, b = 0; scanf("%d %d", &a, &b); show[a][b] = '#'; } system("cls"); displayboard(show, ROW, COL); } } } else { printf("坐标非法,请重新输入\n"); } } if (flag == row*col-EASY_COUNT) { printf("恭喜您,排雷成功\n"); displayboard(mine, ROW,COL); } }
🔥释放一大片。
void ExplosionSpread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* p) { int num = GetMineCounter(mine, x, y); if (num == 0) { (*p)++;//++操作符的优先级比取地址符号高,所以要加括号。 show[x][y] = '@'; int i = 0; int j = 0; for (i = x - 1; i <= x + 1; i++) { for (j = y - 1; j <= y + 1; j++) { if (show[i][j] == '*')//防止已经查找过的坐标再次查找,变成死递归 { ExplosionSpread(mine, show, row, col, i, j, p); } } } } else { (*p)++; show[x][y] = num + '0'; } }
我们将周围没有雷的坐标改为@这样清晰明了一点(棋盘太简单了,设置为空格不好看),for循环的控制部分和遍历查找雷的数量相同,找该位置周围八个坐标,如果有满足条件的就进入函数,然后不断递归,就达到了想要的效果。如果这个位置周围有雷,那就在show数组里改变这个坐标,显示出这个坐标周围雷的个数。
小技巧:将雷的数量EASY_COUNT改为1,方便调试,在game函数里,提前把show数组提前打印出来,可以在输入猜测坐标前提前知道雷的位置。
运行后效果
游戏完整代码
game.h
#pragma once //#pragma pack(1) #include <stdio.h> #include <stdlib.h> #include <time.h> #define EASY_COUNT 1 #define ROW 9 #define COL 9 #define COLS COL+2 #define ROWS ROW+2 void initboard(char board[ROWS][COLS], int rows,int cols,char set ); void displayboard(char board[ROWS][COLS], int row, int col); //放置雷 void PutMine(char board[ROWS][COLS], int row, int col); //查找雷 void findMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col); int GetMineCounter(char mine[ROWS][COLS], int row, int col); //大面积展开 void ExplosionSpread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* p);
game.c
#define _CRT_SECURE_NO_WARNINGS #include "game.h" void initboard(char board[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++) { board[i][j] = set; } } } void displayboard(char board[ROWS][COLS], int row, int col)//打印出show数组元素 { printf("--------扫雷游戏-------\n"); int i = 0; for (int i = 0; i < 10; i++) { printf("%d ", i); } printf("\n"); for (i = 1; i < row+1; i++) { int j = 0; printf("%d ", i ); for (j = 1; j < col+1; j++) { printf("%c ",board[i][j]); } printf("\n"); } } //随机生成雷 void PutMine(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] == '0') { board[x][y] = '1'; count--; } } } //查找雷 void findMine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col) { int x=0, y = 0; int flag = 0; int* p = &flag; while(flag<row*col- EASY_COUNT) { printf("请输入猜测坐标\n"); scanf("%d %d", &x, &y); if (x>1&&x<=row&&y>1,y<=col) { if (mine[x][y] == '1') { printf("很遗憾,您被炸死了\n"); displayboard(mine, ROW, COL); break; } else { if (show[x][y] != '*') { printf("该坐标已被查找,请重新输入"); } //不是雷且不重复,就统计雷的个数. else { ExplosionSpread(mine, show, row, col, x, y, p); system("cls"); displayboard(show, ROW, COL); flag++; printf("是否需要标记某位置:(1/0)"); int map = 0; while ((map = getchar()) != '\n'); scanf("%d", &map); if (map == 1) { int a = 0, b = 0; scanf("%d %d", &a, &b); show[a][b] = '#'; } system("cls"); displayboard(show, ROW, COL); } } } else { printf("坐标非法,请重新输入\n"); } } if (flag == row*col-EASY_COUNT) { printf("恭喜您,排雷成功\n"); displayboard(mine, ROW,COL); } } //GetMineCounter(char mine[ROWS][COLS], int x, int y) //{ // return (mine[x - 1][y] + mine[x][y - 1] + mine[x + 1][y] + mine[x + 1][y - 1] + mine[x - 1][y + 1] + mine[x - 1][y - 1] + mine[x][y + 1] + mine[x + 1][y + 1] -8*'0'); //} GetMineCounter(char mine[ROWS][COLS], int x, int y) { int i = 0; int j = 0; int count = 0; for (i = x - 1; i <= x + 1; i++) { for (j = y - 1; j <= y + 1; j++) { if (mine[i][j] == '1') { count++; } } } return count; } void ExplosionSpread(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* p) { int num = GetMineCounter(mine, x, y); if (num == 0) { (*p)++;//++操作符的优先级比取地址符号高,所以要加括号。 show[x][y] = '@'; int i = 0; int j = 0; for (i = x - 1; i <= x + 1; i++) { for (j = y - 1; j <= y + 1; j++) { if (show[i][j] == '*')//防止已经查找过的坐标再次查找,变成死递归 { ExplosionSpread(mine, show, row, col, i, j, p); } } } } else { (*p)++; show[x][y] = num + '0'; } }
test.c
#define _CRT_SECURE_NO_WARNINGS #include "game.h" void menu() { printf("*****************************\n"); printf("******* 1.play *********\n"); printf("******* 2.exit *********\n"); printf("*****************************\n"); } //在game函数里创建游戏的基本运行条件 void game() { //初始化键盘 char mine[ROWS][COLS] = {0}; char show[ROWS][COLS] = {0}; initboard(mine, ROWS, COLS, '0'); initboard(show, ROWS, COLS, '*'); displayboard(show, ROW, COL); //displayboard(mine, ROW, COL); //接下来随机生成雷 PutMine(mine, ROW, COL); //displayboard(mine, ROWS, COLS); // 可以观察布置雷怎么样。 displayboard(mine, ROW, COL); //玩家查找雷 /*int a, b; scanf("%d %d", &a, &b);*/ findMine(mine,show, ROW, COL); } int main() { srand((unsigned int)time(NULL)); int key = 0; do { menu(); printf("请选择;>"); scanf("%d", &key); switch (key) { case 1: printf("游戏开始\n"); game(); break; case 0:break; default: printf("选择错误,重新选择\n"); } } while(key); return 0; }