这个小游戏就是扫雷,相信大家对这个游戏都不陌生吧,这是电脑自带的仅有的几个小游戏之一,以前在学校上电脑课的时候关掉老师的控制偷偷玩的游戏扫雷算一个,我们以前都是玩别人做好的游戏,那如果我们自己写一个扫雷游戏出来让别人玩是不是成就感满满哒。
那我们就开始实现这个扫雷吧。
一、初阶版本
我们需要先创建一个测试模块test.c和游戏模块game.c、game.h
我们先来看一下最终的效果图。
我们要先理清这个游戏的每一步是需要做什么的,需要写在测试模块里。
测试模块的代码如下,每一行都有对应的解释:
声明:下面的ROWS比ROW多2,COLS比COL多2,因为我们扫雷的游戏的规则是排查一个坐标,如果这个位置不是雷的话,我们就以这个位置为中心,检查统计周围的8个位置中又多少个雷,然后把周围雷的数目标在排查雷的位置上提醒玩家。如果我们是9*9的扫雷数组的话,那么当我们排查边上的位置的时候,如果访问边上外面的位置的话,就越界访问了,但是如果每次访问边上的位置的时候加一个判断条件的话,又显得很麻烦,所以,我们干脆把数组设置成11*11,最边上那些位置不设置雷,这样即不影响显示雷的信息,也不会导致数组的越界访问,这样设置的话是恰到好处的。
void menu(void) { printf("**********************\n"); printf("****** 扫雷游戏 *****\n"); printf("******* 1.play *******\n"); printf("******* 0.exit *******\n"); printf("**********************\n"); } void game(void) { char mine[ROWS][COLS] = { 0 };//创建一个设置雷的数组 char show[ROWS][COLS] = { 0 };//创建一个呈现给玩家看的字符数组 init_board(mine, ROWS, COLS,'0');//初始化数组全为字符‘0’ init_board(show, ROWS, COLS, '*');//初始化数组全为字符‘*’ set_mine(mine, ROW,COL,EASY_COUNT);//随机设置雷 print(mine, ROW, COL);//打印有雷的数组观察雷的位置,真正玩的时候不需要打印这个 printf("\n"); print(show, ROW, COL);//呈现在玩家屏幕的字符数组 printf("开始扫雷\n"); while (1)//循环排查雷 { int ret = sweep_mine(mine,show, ROW, COL);//输入坐标,排查雷,赢了或者炸死了返回1 if (ret == 1) { break;//当全部雷排查完了或者踩到雷之后跳出循环 } print(show, ROW, COL);//打印排查一次雷之后的信息 } print(show, ROW, COL); printf("\n"); print(mine, ROW, COL); } void test(void) { srand((unsigned int)time(NULL));//为了后续随机生成雷 int input = 0; do { menu();//打印菜单 printf("请选择:>\n"); scanf("%d", &input); switch (input)//判断是否玩游戏,1为开始游戏,0为退出游戏,其他数字为选择错误 { case 1: { game(); break; } case 0: { printf("退出游戏\n"); } default: { printf("选择错误,请重新选择!\n"); break; } } } while (input); } int main() { test(); return 0; }
可以看到,我们首先需要创建两个二维字符数组,一个是设置雷的数组char mine[ROWS][COLS],一个是呈现在玩家游戏界面的数组 char show[ROWS][COLS] 。
接下来我们需要写一个函数init_board初始化两个字符数组。
1.初始化数组
void init_board(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;//把两个数组里面的每一个位置都置为字符‘set’ //'set'由参数传过来 //我们应该先把设置雷的数组mine初始化为‘0’ //把show数组初始化为‘*’ } } }
初始化完了之后我们应该设置雷了,雷的数目我们可以随机定,我们就来设置10个雷吧,难度小一点。
2.设置雷
//由于真正有雷的地方是在9*9的数组里面,所以我们传过来的参数应该是ROW和COL就行了 void set_mine(char mine[ROWS][COLS], int row, int col,int count) { while (count)//count为设置雷的数目,可自行设定 { int x = rand() % row + 1;//随机生成1-9之间的x轴坐标 int y = rand() % col + 1;//随机生成1-9之间的y轴坐标 if (mine[x][y] == '0') { mine[x][y] = '1';//雷设置为字符‘1’ count--;//由于生成的随机坐标可能是重复的,所以应该是每成功设置了一个雷之后,count-- } } }
3.打印游戏界面数组
//由于真正玩游戏时呈现出来的数组是9*9的,所以只需要把参数ROW和COL传过来即可 void print(char board[ROWS][COLS], int row, int col) { int i = 0; int j = 0; for (j = 0; j <= col; j++) { printf("%d ", j); } printf("\n"); for (i = 1; i <= row; i++) { printf("%d ", i); for (j = 1; j <= col ; j++) { printf("%c ", board[i][j]); } printf("\n"); } }
然后就是正式进入游戏环节了。
4.实现一个扫雷的函数
int get_mine_count(char mine[ROWS][COLS], int x, int y) { //由于字符‘0’的ASC||值是48,字符‘1’的ASC||值49,所以8个位置的字符加起来减去8*‘0’就得到了 //一个整数,数值为8个位置中字符‘1’的数目,返回这个数 return (mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y - 1] + mine[x][y + 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0'); } int sweep_mine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col) { printf("请输入要排雷的坐标:>\n"); int x = 0; int y = 0; int win = 0;//记录排查出的雷的个数 while (win<row*col-EASY_COUNT)//循环进去的条件是所有雷被排查出来之前 { scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col)//判断输入坐标的合法性 { //由于每排查一个位置之后该位置如果不是雷就统计周围8个位置雷的个数放到这个位置上去, //所以当这个位置不等于‘*’是证明该坐标被占用了 if (show[x][y] != '*') { printf("该位置已经被排查过了,请重新输入:>"); continue;//该次循环后面的程序就没有必要执行了,直接去到下一次循环 } if (mine[x][y] == '1')//为雷,那就被炸死了 { printf("很遗憾,你被炸死了!\n"); return 1;//返回1作为一个判断游戏是否结束的判断条件(下一张图会解释) } else { int n = get_mine_count(mine, x, y);//统计周围8个位置雷的个数 show[x][y] = n + '0';//由于show是字符数组,所以整数转为字符应该加上‘0’, //这个数的数值不变,但含义变了,把这个字符数字放到这个 //位置上作为提示信息 print(show, ROWS, COLS); win++;//成功排查出一个雷,win就+1 print(show, ROW, COL); printf("请继续排雷:>"); } } else { printf("坐标非法,请重新输入:>"); } } if (win == row*col - EASY_COUNT)//当所有雷都排查出来之后,就排雷成功 { printf("恭喜你,排雷成功\n"); return 1;//返回1作为一个判断游戏是否结束的判断条件(下一张图会解释) } }
可以看到,sweep_mine前面由一个int类型的变量ret接收,后面紧接着是if语句判断,因为从上面的扫雷代码可以知道如果返回值是1的话,要么是扫雷成功了,要么是被炸死了,也就是说游戏结束了。
来到这的话初阶的扫雷游戏就写完了。你学会了吗?
下面是整个游戏的参考代码
(1.test)
#define _CRT_SECURE_NO_WARNINGS 1 //test.c #include "game.h" void menu(void) { printf("**********************\n"); printf("****** 扫雷游戏 *****\n"); printf("******* 1.play *******\n"); printf("******* 0.exit *******\n"); printf("**********************\n"); } void game(void) { 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,EASY_COUNT); print(mine, ROW, COL); printf("\n"); print(show, ROW, COL); printf("开始扫雷\n"); while (1) { int ret = sweep_mine(mine,show, ROW, COL); if (ret == 1) { break; } print(show, ROW, COL); } print(show, ROW, COL); printf("\n"); print(mine, ROW, COL); } void test(void) { srand((unsigned int)time(NULL)); int input = 0; do { menu(); printf("请选择:>\n"); scanf("%d", &input); switch (input) { case 1: { game(); break; } case 0: { printf("退出游戏\n"); } default: { printf("选择错误,请重新选择!\n"); break; } } } while (input); } int main() { test(); return 0; }
(2.)game.c
#define _CRT_SECURE_NO_WARNINGS 1 //game.c #include "game.h" void init_board(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 print(char board[ROWS][COLS], int row, int col) { int i = 0; int j = 0; for (j = 0; j <= col; j++) { printf("%d ", j); } printf("\n"); for (i = 1; i <= row; i++) { printf("%d ", i); for (j = 1; j <= col ; j++) { printf("%c ", board[i][j]); } printf("\n"); } } void set_mine(char mine[ROWS][COLS], int row, int col,int count) { while (count) { int x = rand() % row + 1; int y = rand() % col + 1; if (mine[x][y] == '0') { mine[x][y] = '1'; count--; } } } int get_mine_count(char mine[ROWS][COLS], int x, int y) { return (mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y - 1] + mine[x][y + 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0'); } int sweep_mine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col) { printf("请输入要排雷的坐标:>\n"); int x = 0; int y = 0; int win = 0; while (win<row*col-EASY_COUNT) { scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { if (show[x][y] != '*') { printf("该位置已经被排查过了,请重新输入:>"); continue; } if (mine[x][y] == '1') { printf("很遗憾,你被炸死了!\n"); return 1; } else { int n = get_mine_count(mine, x, y); show[x][y] = n + '0'; win++; /*expendboard(mine, show, x, y);*/ print(show, ROW, COL); printf("请继续排雷:>"); } } else { printf("坐标非法,请重新输入:>"); } } if (win == row*col - EASY_COUNT) { printf("恭喜你,排雷成功\n"); return 1; } }
(3)game.h
#pragma once //game.h #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 init_board(char board[ROWS][COLS], int rows, int cols,char set); void print(char board[ROWS][COLS], int rows, int cols); void set_mine(char board[ROWS][COLS], int row, int col,int count); int sweep_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
细心的小伙伴已经发现了,真正的扫雷游戏的排雷规则不是这样的呀,这样一个一个地排查需要排到什么时候才能赢呀?确实没错,真正的扫雷游戏是并没有那么简单,所以我才说上面写的这个是的,那我们到底需要怎么改造一下上面的代码使这个游戏更完美呢?接下来我们就来写一下进阶版本的。
二、进阶版扫雷
我们知道扫雷游戏的规则就是当你排查一个位置时,如果这个位置的周围8个位置中一个雷都没有,那它就会再分别以这8个坐标为中心,检查这个中心周围的8个位置是否有雷,如果有,则统计雷的数目并把对应的字符赋值给show数组相应的位置,如果没有,则继续以这个位置为中心排查,直到周围有雷才停止。这个扫雷的代码我们可以以递归的形式实现。
int get_mine_count(char mine[ROWS][COLS], int x, int y) { return (mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y - 1] + mine[x][y + 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0'); } void expendboard(char mine[ROWS][COLS], char show[ROWS][COLS], int i, int j)//进行空白展开 { int a = 0; int b = 0; int count = get_mine_count(mine, i, j);//计算周围雷数 if (count == 0)//如果没雷,进去 { show[i][j] = ' ';//先把输入坐标的位置赋值一个空格 for (a = i - 1; a <= i + 1; a++)//排查周围8个位置是否有雷,某个位置没雷,则继续展开 { for (b = j - 1; b <= j + 1; b++) { //因为只要满足shou[a][b]为‘*’,那么就会进入递归函数 //而进入递归函数首先又先判断这个坐标的位置是否为雷,不 //是雷的情况下先把它赋值为空格,再判断以这个坐标为中心 //的8个坐标,这样的话已经排查过的坐标就已经被赋值为空 //格了,不再是‘*’,也不是数字,这样就不会出现死递归的情况, //并且当递归展开到出现数字为止,而非展到雷为止 if (show[a][b] == '*') { expendboard(mine, show, a, b);//递归:连续展开 } } } } else { show[i][j] = count + '0'; //显示雷数 } } int sweep_mine(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col) { printf("请输入要排雷的坐标:>\n"); int x = 0; int y = 0; int win = 0; while (win<row*col-EASY_COUNT) { scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { if (show[x][y] != '*') { printf("该位置已经被排查过了,请重新输入:>"); continue; } if (mine[x][y] == '1') { printf("很遗憾,你被炸死了!\n"); return 1; } else { /*int n = get_mine_count(mine, x, y); show[x][y] = n + '0'; win++;*/ expendboard(mine, show, x, y); print(show, ROW, COL); printf("请继续排雷:>"); } } else { printf("坐标非法,请重新输入:>"); } } if (win == row*col - EASY_COUNT) { printf("恭喜你,排雷成功\n"); return 1; } }
进阶版扫雷游戏效果图如下
这样是不是就有真正扫雷游戏的感觉了哈。
当然,扫雷游戏里面还应该有小红旗标志有雷的位置,这里交给大家作为拓展部分,有兴趣的小伙伴可以自行实现一下呀,记得回来评论区留言你写好的拓展部分的代码哦。