该文章将教你如何用代码写出扫雷小游戏(基础版)
这是个99棋盘,扫雷游戏大家应该不陌生,玩法很简单,点击蓝块,如果蓝块是雷则被炸死,如果蓝块不是雷,则显示周围的雷的情况。
我的想法呢是这样:
这个雷盘我们用一个二维数组来构建,并且构建两个一个是布置雷的,另一个是用来给我们看的也就是打印的那个至于为什么这样弄因为后面有一点想法和这个很好的匹配起来,我先把mine雷盘默认是字符’0’,show雷盘默认为’',布置雷呢放在mine雷盘,然后排查雷的情况放在show雷盘里,这样就不会搞混了,因为两个雷盘一模一样一一对应的,然后在排查雷时,发现就是边缘无法排查,所以我们把这个棋盘扩大一下,增加2行2列,但打印出来的还是9*9的雷盘,我们这里用下宏定义处理
#define ROW 9 行
#define COL 9 列
#define ROWS ROW+2
#define COLS COL+2
#define COUNT 10 这个下面会讲到我先写上去
还有以下我是按照分装函数来写的,是把函数的声明放在一个头文件里面专门声明的,函数的实现还有函数的测试都是分开写在不同的.c文件中的。因为这样能使各函数的功能更独立。
最下面我会把test2.c,game2.h,game2.c都弄出来的。
第一步构建一个简易菜单框架
void menu() { printf("************************\n"); printf("****** 1.play ******\n"); printf("****** 0.exit ******\n"); printf("************************\n"); } void test() { int intput; srand(time(NULL)); do { menu(); printf("请选择:\n"); scanf("%d", &intput); switch(intput) { case 1: game();break; case 0:printf("退出游戏\n");break; default:printf("输入错误,重新输入\n");break; } } while (intput); } int main() { test(); return 0; }
效果:
输入1,进入游戏,输入0退出游戏。接下来就是对game()进行填写了。
第二步初始化雷盘
将mine棋盘初始化成全是‘0’,把show棋盘初始化成全是*,创建一个函数Init_Board();
怎么利用Init_Board()函数将mine和show棋盘都初始化成功呢?
void game() { char mine[ROWS][COLS] = { 0 }; char show[ROWS][COLS] = { 0 }; //1.初始化棋盘 Init_board(mine, ROWS, COLS,'0'); Init_board(show, ROWS, COLS,'*'); }
void Init_board(char board[ROWS][COLS], int rows, int cols,char set) { int i, j; for (i = 0;i < rows;i++) { for (j = 0;j < cols;j++) { board[i][j] = set; } } }
第三步打印雷盘
把mine雷盘和show雷盘都打印出来看下,但实际上,mine雷盘是不能打印出来看的,mine里面要放置雷的。show雷盘才是打印出来看的现在先看一下效果。
void game() { char mine[ROWS][COLS] = { 0 }; char show[ROWS][COLS] = { 0 }; //1.初始化棋盘 Init_board(mine, ROWS, COLS,'0'); Init_board(show, ROWS, COLS,'*'); //2.打印棋盘,实际上只打印出show棋盘; DisplayBoard(mine, ROW, COL); DisplayBoard(show, ROW, COL);
void DisplayBoard(char board[ROWS][COLS], int row, int col) { int i, j; for (i = 0;i <row;i++) { for (j = 0;j <col;j++) { printf("%c ", board[i][j]); } printf("\n"); } }
效果如下:
************************ ****** 1.play ****** ****** 0.exit ****** ************************ 请选择: 1 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 请输入要排查的坐标:
这样发现看总感觉很不对劲,最后发现,这样哪行哪列很迷糊,不容易看出来,所以我们最好给他加上行号,和列号,这样就好找多了,并把界面修整修整,太捞了这样。
void DisplayBoard(char board[ROWS][COLS], int row, int col) { printf("--------扫雷--------\n");//修整修整 int i, j;//打印列号 for (i = 0;i <=col;i++) { printf("%d ", i); } printf("\n");//换行 for (i = 1;i <=row;i++) { printf("%d ", i);//打印行号,每行开头。 for (j = 1;j <= col;j++) { printf("%c ", board[i][j]); } printf("\n"); } printf("--------扫雷--------\n"); }
效果:
************************ ****** 1.play ****** ****** 0.exit ****** ************************ 请选择: 1 --------扫雷-------- 0 1 2 3 4 5 6 7 8 9| 1 * * * * * * * * *| 2 * * * * * * * * *| 3 * * * * * * * * *| 4 * * * * * * * * *| 5 * * * * * * * * *| 6 * * * * * * * * *| 7 * * * * * * * * *| 8 * * * * * * * * *| 9 * * * * * * * * *| --------扫雷-------- --------扫雷-------- 0 1 2 3 4 5 6 7 8 9| 1 0 0 0 0 0 0 0 0 0| 2 0 0 0 0 0 0 0 0 0| 3 0 0 0 0 0 0 0 0 0| 4 0 0 0 0 0 0 0 0 0| 5 0 0 0 0 0 0 0 0 0| 7 0 0 0 0 0 0 0 0 0| 8 0 0 0 0 0 0 0 0 0| 9 0 0 0 0 0 0 0 0 0| --------扫雷-------- 请输入要排查的坐标: 哎,算了,还是很捞。就先这样吧。。。。。
第四步布置雷
我们先给雷盘布置10个雷吧,用宏定义 定义COUNT 10;
首先先生成随机坐标,x,y,这个坐标范围要合法,要在棋盘范围内,
我们知道一个数取模9的话得到的一定是0~8之间的数,所以我们让x%9再加个1就能产生1 ~9之间的数了怎么产生随机数呢,我们来用rand这个库函数,使用rand之前要先调用srand()函数,然后放进去一个时间戳就能产生随机数了。随机数有了,我们可以布置雷了,布置雷之前还要考虑下,布置的地方是否已经有雷,如果没有雷的话才布置。
void Set_mine(char mine[ROWS][COLS], int row, int col) { //先生成随机坐标,再布置雷; int count = COUNT;//布置COUNT颗雷 while (count) { int x = rand() % 9 + 1; int y = rand() % 9 + 1; //判断是否有雷了; if (mine[x][y] == '0') { mine[x][y] = '1'; count--;//布置一个少一个直到布置完为止。 } } }
第五步排查雷
怎么排查雷呢?我们需要从mine雷盘中排查雷的信息然后将雷的信息再传给show雷盘中,比如排查5,5坐标,如果在mine棋盘这个坐标为雷,那么游戏之间结束,抱歉你被炸死了,结束之前要把雷盘公布出来,让你死的明明白白,如果不是雷那就要把周围8个位置的情况排查一下有几个雷然后再传给show雷盘对应的那个5,5坐标,将排雷信息输入。这样mine,和show雷盘是一一对应的。(这里的对周围8个坐标排查我们也用一个函数来分装)
CheckBoard()函数的参数是mine 雷盘,show雷盘,行和列。
void CheckBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { //怎么排查呢,通过对mine棋盘,坐标下,x,y排查,是雷,炸死,不是雷统计周围坐标雷的格个数,传给show棋盘。; int x, y; while (1) { 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 { int n= get_around_mine(mine, x, y);//排查周围雷的信息 show[x][y] = n + '0'; DisplayBoard(show, ROW, COL);//要把信息输入show棋盘并显示 } } else { printf("坐标非法,重新输入:\n"); } } }
get_around_mine()函数对mine棋盘,x,y周围8个坐标进行搜查,怎么搜查呢,我们知道字符’1’怎么才能变成数字1呢?字符’0’的ASCII是48,字符’1’ 的ASCII是49,那只要数字1+‘0’=‘1’,同理,数字n+‘0’=字符’n’,因为mine 数组是char类型的,只要把周围8个字符相加,然后再减去8个字符’0’(相当于每个字符减去一个字符’0’)最后就能转化能一个数字了。这个函数就最后返回这个值给int n;show[x][y]=n+‘0’—这步再把数字n转化为字符n,存到show雷盘里。
int get_around_mine(char mine[ROWS][COLS], int x, int y) { return mine[x - 1][y]+mine[x - 1][y - 1]+mine[x - 1][y + 1]+mine[x][y - 1]+mine[x][y + 1] +mine[x + 1][y]+mine[x + 1][y - 1]+mine[x + 1][y + 1]-8*'0'; }
效果:
************************ ****** 1.play ****** ****** 0.exit ****** ************************ 请选择: 1 --------扫雷-------- 0 1 2 3 4 5 6 7 8 9| 1 * * * * * * * * *| 2 * * * * * * * * *| 3 * * * * * * * * *| 4 * * * * * * * * *| 5 * * * * * * * * *| 6 * * * * * * * * *| 7 * * * * * * * * *| 8 * * * * * * * * *| 9 * * * * * * * * *| --------扫雷-------- 请输入要排查的坐标: 5 5 随便猜了一个直接炸死,这运气没谁了。 抱歉你被炸死了 --------扫雷-------- 0 1 2 3 4 5 6 7 8 9| 1 1 0 1 0 0 0 0 0 0| 2 0 0 0 0 0 1 0 0 0| 3 0 0 0 0 0 1 0 0 0| 4 1 0 0 0 0 0 1 1 0| 5 0 0 0 0 1 0 0 0 0| 6 0 0 0 0 0 0 0 0 0| 7 0 0 0 0 0 0 1 0 0| 8 0 0 0 0 0 0 0 0 0| 9 0 1 0 0 0 0 0 0 0| --------扫雷-------- ************************ ****** 1.play ****** ****** 0.exit ****** ************************ 请选择:
到这里基本上这个简易版扫雷已经完成接下来我们就要修补下一些细节
最后优化
到这里你不会没有问题吧? 嗯? 这扫雷游戏,我怎么赢啊,好像只有被炸死了才能结束。我们想下,行为ROW,列为COL,雷有COUNT个,总共安全扫雷的区域有ROWCOL-COUNT个,所以我们可以定义一个变量win,当win大于ROWCOL-COUNT就表示赢了,小于就表面要继续扫雷。所以我们把win放在排雷函数里面。
void CheckBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { //怎么排查呢,通过对mine棋盘,坐标下,x,y排查,是雷,炸死,不是雷统计周围坐标雷的格个数,传给show棋盘。;3 int x, y; int win = 0; while (win<row*col-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 { int n= get_around_mine(mine, x, y); show[x][y] = n + '0'; DisplayBoard(show, ROW, COL); win++;//下一次++一次 } } else { printf("坐标非法,重新输入:\n"); } }//当win==row*col-COUNT时表明排雷成功。 if (win == row * col - COUNT) { printf("恭喜你排雷成功\n"); } }
然后我们把COUNT雷改成79个试一试,再把mine雷盘打印出来看然后试一试效果。(10个雷要扫太长时间了,所以把雷改成79个)
************************ ****** 1.play ****** ****** 0.exit ****** ************************ 请选择: 1 --------扫雷-------- 0 1 2 3 4 5 6 7 8 9| 1 * * * * * * * * *| 2 * * * * * * * * *| 3 * * * * * * * * *| 4 * * * * * * * * *| 5 * * * * * * * * *| show雷盘 6 * * * * * * * * *| 7 * * * * * * * * *| 8 * * * * * * * * *| 9 * * * * * * * * *| --------扫雷-------- --------扫雷-------- 0 1 2 3 4 5 6 7 8 9| 2 1 1 1 1 1 1 1 1 1| 3 1 1 1 1 1 1 1 1 1| 4 1 1 1 1 1 1 1 1 1| mine雷盘 5 1 1 1 1 1 1 1 1 1| 发现坐标6 ,1和8 ,7不是雷 6 0 1 1 1 1 1 1 1 1| 然后进行排查 7 1 1 1 1 1 1 1 1 1| 8 1 1 1 1 1 1 0 1 1| 9 1 1 1 1 1 1 1 1 1| --------扫雷-------- 请输入要排查的坐标: 6 1 --------扫雷-------- 0 1 2 3 4 5 6 7 8 9| 1 * * * * * * * * *| 2 * * * * * * * * *| 3 * * * * * * * * *| 4 * * * * * * * * *| 5 * * * * * * * * *| 6 5 * * * * * * * *| 7 * * * * * * * * *| 8 * * * * * * * * *| 9 * * * * * * * * *| --------扫雷-------- 请输入要排查的坐标: 8 7 --------扫雷-------- 0 1 2 3 4 5 6 7 8 9| 1 * * * * * * * * *| 2 * * * * * * * * *| 3 * * * * * * * * *| 4 * * * * * * * * *| 5 * * * * * * * * *| 6 5 * * * * * * * *| 7 * * * * * * * * *| 8 * * * * * * 8 * *| 9 * * * * * * * * *| --------扫雷-------- 恭喜你排雷成功 win=row*col-COUNT排雷成功
到这里是不是就结束啦,,不不不,还有一个小bug,,我不说,你知道嘛?
你看嗷,就拿上面的例子来说(6,1)这个坐标没有雷,可以排它,然后(8,7)也可以排,排完这个两个我就赢了对吧,但如果我排查两遍(6,1)呢,win是不是也++,那最后win照样跟row*col-COUNT相同,并表示排查成功,所以这里有问题,在排雷时我们还需要再检查这个坐标是否已经被排查过了如果排查过了那就不要再排查了,win就不会再++;
************************ ****** 1.play ****** ****** 0.exit ****** ************************ 请选择: 1 --------扫雷-------- 0 1 2 3 4 5 6 7 8 9| 1 * * * * * * * * *| 2 * * * * * * * * *| 3 * * * * * * * * *| 4 * * * * * * * * *| show雷盘 5 * * * * * * * * *| 6 * * * * * * * * *| 7 * * * * * * * * *| 8 * * * * * * * * *| 9 * * * * * * * * *| --------扫雷-------- --------扫雷-------- 0 1 2 3 4 5 6 7 8 9| 1 0 1 1 1 1 1 1 1 1| 2 1 1 1 1 1 1 1 1 1| 3 1 1 1 1 1 1 1 1 1| 4 1 1 1 1 1 1 1 1 1| mine雷盘 5 1 1 1 1 1 1 1 1 1| (1,1)(9,8)没雷可以排查 6 1 1 1 1 1 1 1 1 1| 7 1 1 1 1 1 1 1 1 1| 8 1 1 1 1 1 1 1 1 1| 9 1 1 1 1 1 1 1 0 1| --------扫雷-------- 请输入要排查的坐标: 1 1 我排查(1,1) --------扫雷-------- 0 1 2 3 4 5 6 7 8 9| 1 3 * * * * * * * *| 2 * * * * * * * * *| 3 * * * * * * * * *| 4 * * * * * * * * *| 5 * * * * * * * * *| 6 * * * * * * * * *| 7 * * * * * * * * *| 8 * * * * * * * * *| 9 * * * * * * * * *| --------扫雷-------- 请输入要排查的坐标: 1 1 我再排查(1,1) --------扫雷-------- 0 1 2 3 4 5 6 7 8 9| 1 3 * * * * * * * *| 2 * * * * * * * * *| 3 * * * * * * * * *| 4 * * * * * * * * *| 5 * * * * * * * * *| 6 * * * * * * * * *| 7 * * * * * * * * *| 8 * * * * * * * * *| 9 * * * * * * * * *| --------扫雷-------- 恭喜你排雷成功 照样能赢--bug
改良后的代码如下:
void CheckBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { //怎么排查呢,通过对mine棋盘,坐标下,x,y排查,是雷,炸死,不是雷统计周围坐标雷的格个数,传给show棋盘。;3 int x, y; int win = 0; while (win<row*col-COUNT) { printf("请输入要排查的坐标:\n"); //判断坐标合法性 scanf("%d%d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { if (show[x][y] != '*') { printf("抱歉,该坐标已被排查\n"); continue;//排查过了后面就不再进行再换一个地方排查。 } if (mine[x][y] == '1') { printf("抱歉你被炸死了\n"); DisplayBoard(mine, ROW, COL); break; } else { int n= get_around_mine(mine, x, y); show[x][y] = n + '0'; DisplayBoard(show, ROW, COL); win++; } } else { printf("坐标非法,重新输入:\n"); } } if (win == row * col - COUNT) { printf("恭喜你排雷成功\n"); } }
效果如下:
************************ ****** 1.play ****** ****** 0.exit ****** ************************ 请选择: 1 --------扫雷-------- 0 1 2 3 4 5 6 7 8 9| 1 * * * * * * * * *| 2 * * * * * * * * *| 3 * * * * * * * * *| 4 * * * * * * * * *| show雷盘 5 * * * * * * * * *| 6 * * * * * * * * *| 7 * * * * * * * * *| 8 * * * * * * * * *| 9 * * * * * * * * *| --------扫雷-------- --------扫雷-------- 0 1 2 3 4 5 6 7 8 9| 1 1 1 1 1 1 1 1 1 1| 2 1 1 1 1 1 1 1 1 1| 3 1 1 1 1 1 1 1 1 1| 4 1 1 1 1 1 0 1 1 1| mine雷盘 5 1 1 1 1 1 1 1 1 1| (4,6)(8,5)没雷可以排查 6 1 1 1 1 1 1 1 1 1| 7 1 1 1 1 1 1 1 1 1| 8 1 1 1 1 0 1 1 1 1| 9 1 1 1 1 1 1 1 1 1| --------扫雷-------- 请输入要排查的坐标: 4 6 我排查(4,6) --------扫雷-------- 0 1 2 3 4 5 6 7 8 9| 1 * * * * * * * * *| 2 * * * * * * * * *| 3 * * * * * * * * *| 4 * * * * * 8 * * *| 5 * * * * * * * * *| 6 * * * * * * * * *| 7 * * * * * * * * *| 8 * * * * * * * * *| 9 * * * * * * * * *| --------扫雷-------- 请输入要排查的坐标: 4 6 哎,我再排查(4,6)哟,不给排了,咦~ 抱歉,该坐标已被排查 请输入要排查的坐标: 8 5 最后我只能含泪排(8,5)然后排雷成功! --------扫雷-------- 0 1 2 3 4 5 6 7 8 9| 1 * * * * * * * * *| 2 * * * * * * * * *| 3 * * * * * * * * *| 4 * * * * * 8 * * *| 5 * * * * * * * * *| 6 * * * * * * * * *| 7 * * * * * * * * *| 8 * * * * 8 * * * *| 9 * * * * * * * * *| --------扫雷-------- 恭喜你排雷成功
代码
想要代码的在这喔:
game.h头文件
#define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <stdlib.h> #include <time.h> #define COUNT 79 #define ROW 9 #define COL 9 #define ROWS ROW+2 #define COLS COL+2//函数声明 void Init_board(char board[ROWS][COLS], int rows, int cols,char set); void DisplayBoard(char board[ROWS][COLS], int row, int col); void Set_mine(char mine[ROWS][COLS], int row, int col); void CheckBoard(char mine[ROWS][COLS],char show[ROWS][COLS], int row, int col); int get_around_mine(char mine[ROWS][COLS], int x, int y);
game2.c实现文件
#include "game2.h" void Init_board(char board[ROWS][COLS], int rows, int cols,char set) { int i, j; for (i = 0;i < rows;i++) { for (j = 0;j < cols;j++) { board[i][j] = set; } } } void DisplayBoard(char board[ROWS][COLS], int row, int col) { printf("--------扫雷--------\n");//修整修整 int i, j;//打印列号 for (i = 0;i <=col;i++) { printf("%d ", i); } printf("\n");//换行 for (i = 1;i <=row;i++) { printf("%d ", i);//打印行号,每行开头。 for (j = 1;j <= col;j++) { printf("%c ", board[i][j]); } printf("\n"); } printf("--------扫雷--------\n"); } void Set_mine(char mine[ROWS][COLS], int row, int col) { //先生成随机坐标,再布置雷; int count = COUNT; while (count) { int x = rand() % 9 + 1; int y = rand() % 9 + 1; //判断是否有雷了; if (mine[x][y] == '0') { mine[x][y] = '1'; count--; } } } int get_around_mine(char mine[ROWS][COLS], int x, int y) { return mine[x - 1][y]+mine[x - 1][y - 1]+mine[x - 1][y + 1]+mine[x][y - 1]+mine[x][y + 1] +mine[x + 1][y]+mine[x + 1][y - 1]+mine[x + 1][y + 1]-8*'0'; } void CheckBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { //怎么排查呢,通过对mine棋盘,坐标下,x,y排查,是雷,炸死,不是雷统计周围坐标雷的格个数,传给show棋盘。;3 int x, y; int win = 0; while (win<row*col-COUNT) { printf("请输入要排查的坐标:\n"); //判断坐标合法性 scanf("%d%d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col) { if (show[x][y] != '*') { printf("抱歉,该坐标已被排查\n"); continue; } if (mine[x][y] == '1') { printf("抱歉你被炸死了\n"); DisplayBoard(mine, ROW, COL); break; } else { int n= get_around_mine(mine, x, y); show[x][y] = n + '0'; DisplayBoard(show, ROW, COL); win++; } } else { printf("坐标非法,重新输入:\n"); } } if (win == row * col - COUNT) { printf("恭喜你排雷成功\n"); } }
test2.c,测试文件
#include "game2.h" void game() { char mine[ROWS][COLS] = { 0 }; char show[ROWS][COLS] = { 0 }; //1.初始化棋盘 Init_board(mine, ROWS, COLS,'0'); Init_board(show, ROWS, COLS,'*'); //2.打印棋盘,实际上只打印出show棋盘; DisplayBoard(show, ROW, COL); //DisplayBoard(mine, ROW, COL); //3.布置雷 Set_mine(mine, ROW, COL); DisplayBoard(mine, ROW, COL); //4.排雷 CheckBoard(mine, show, ROW, COL); } void menu() { printf("************************\n"); printf("****** 1.play ******\n"); printf("****** 0.exit ******\n"); printf("************************\n"); } void test() { int intput; srand(time(NULL)); do { menu(); printf("请选择:\n"); scanf("%d", &intput); switch(intput) { case 1: game();break; case 0:printf("退出游戏\n");break; default:printf("输入错误,重新输入\n");break; } } while (intput); } int main() { test(); return 0; }