项目要求
实现一个9×9的扫雷游戏,玩家通过输入坐标进行操作。玩家如果踩到雷了,就提示游戏结束,如果没踩到雷,就统计这个坐标周围的雷数,显示出来。效果如图:

思路
我们先创建3个文件,game.h,game.c,test.c。test.c用于测试逻辑,game.c用于定义具体的游戏函数,game.h用于声明。然后我们在test.c中开始敲代码,和猜数字三子棋游戏一样,进来就要加载游戏界面,所以我们直接给一个do while循环,让玩家进行选择。这里就不再叙述了。代码如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | #include "game.h" void menu() { ????printf("***************************\n"); ????printf("******???? 1.PLAY??? ******\n"); ????printf("******???? 0.EXIT??? ******\n"); ????printf("***************************\n"); } game() { } int main() ????{ ????srand((unsigned int)time(NULL));//后面的随机值会用到,后面再分析它的作用 ????int input; ????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; } |
基础的游戏菜单搭建好了,接下来我们该如何实现游戏部分呢? 首先分析,我们要得到一个9*9的棋盘,这里就要用到二维数组了。然后我们分析,如果只定义一个二维数组,布置雷 雷-‘1’ 不是雷-‘0’,然后玩家操作以后显示周围雷的数量,如果周围雷的数量刚好为1,则显示1,这就产生歧义了。所就要写两个char数组,1个数组专门存放布置好的雷的信息,另一个数组存放和排查雷的信息。
game()
{
char mine[ROWS][COLS];//用于布置雷
char show[ROWS][COLS];//用于排查雷
}
然后在game.h中定义常量,这样定义的好处就是能够做到一改全改。
| 1 2 3 4 | #define ROWS ROW+2 #define COLS COL+2 #define ROW 9 #define COL 9 |
ROLS和COLS为什么要加2呢? 因为我们之后统计坐标的时候,如果刚好是在四边呢?再往外面统计,不就数组越界了吗? 所以我们要在定义的时候+2,不往里面存东西就行了。如果想实现9*9的棋盘,数组的大小应该设计成11*11,否则会导致数组越界 定义好棋盘后,我们就要对棋盘里面存值,进行初始化。 在数组mine里面我们全部初始化0,0为安全区,1为雷区。 在数组show里面我们全部初始化*,*表示玩家没有探索过的区域 于是我们在game()函数里面调用InitBoard函数,用于初始化
| 1 2 | InitBoard(mine, ROWS, COLS, '0'); InitBoard(show, ROWS, COLS, '*'); |
既然调用函数就要声明函数,我们在game.h中声明,然后在test.c中引入game.h这个头文件就可以了。
| 1 | void InitBoard(char board[ROWS][COLS], int rows, int cols,char ret);//声明初始化函数 |
函数都声明完了,InitBoard函数也该定义一下了,于是在game.c函数中,引入game.h头文件以后,再给定这样一段代码:
| 1 2 3 4 5 6 7 8 9 10 11 | void InitBoard(char board[ROWS][COLS], int rows, int cols,char ret)//初始化棋盘 { ????int i, j; ????for (i = 0; i < ROWS; i++) ????{ ????????for (j = 0; j < COLS; j++) ????????{ ????????????board[i][j] = ret;//如果传过来的字符是*,则初始化为*,传过来什么就初始化为什么 ????????} ????} } |
初始化以后,我们应该定义一个函数来打印棋盘了。打印9*9的棋盘,我们就只需要把数组以及ROW跟COL传过去就行了。 在game()函数中调用DisplayBoard(show, ROW, COL); 然后在game.h中声明DisplayBoard函数
| 1 | void DisplayBoard(char board[ROWS][COLS], int row, int col);//打印棋盘 |
声明完函数以后,函数还没定义,于是在game.c中定义:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | void DisplayBoard(char board[ROWS][COLS], int row, int col)//打印棋盘 { int i, j; printf("-----------------扫雷游戏---------------\n"); for (i = 0; i <=col; i++)//打印列号 { printf("%d ",i); } printf("\n"); for (i = 1; i <=row; i++)//从1开始访问,直到9,这里就是9行 { printf("%d ",i);//打印行号 for (j = 1; j<=col; j++)//9列 { printf("%c ",board[i][j]); } printf("\n");//打印完一行以后换行 } printf("-----------------扫雷游戏---------------\n"); } |
然后调试我们的程序,运行结果如下:

完成初始化以后,我们就要在mine数组里面放雷了。 在game()函数里面调用Put函数:Put(mine, ROW, COL); 然后在game.h里面声明:
| 1 | void Put(char board[ROWS][COLS], int row, int col);//放雷 |
声明完以后,就要在game.c里面定义函数了:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void Put(char board[ROWS][COLS], int row, int col)//放雷 { ????int count = 10;//地雷数 ????while (count) ????{ ????????int x = rand() % row + 1;//生成随机的x,y,即坐标 ????????int y = rand() % col + 1; ????????if (board[x][y] =='0')//如果里面没有存放东西 ????????{ ????????????board[x][y] = '1'; ????????????count--; ????????} ????} } |
rand和srand是由stdlib.h给出的库函数,rand能够生成0~32767的随机数,而srand则是定义它的随机数初始值,在程序中只需要定义一次,所以我们在main函数里面定义了,并且以时间戳time作为它的参数,增大随机度。然后我们再把它们的头文件stdlib.h和time.h放到game.h里面进行包括。
雷布置好了,那么玩家就可以操作了,在game()函数里面调用排查函数:
| 1 | Find(mine, show, ROW, COL);//(排查需要把两个数组都传进来,由mine函数判断是否踩到雷,或者计算周围雷的数量然后返回给show) |
接下来在game.h中声明排查函数:
| 1 | void Find(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);//排雷 |
然后在game.c中定义函数 玩家操作应该是这样的: 1.输入坐标,判断坐标的合法性 2.判断是不是雷 (1)是雷:被炸死了(2)不是雷:计算周围的雷数-放到坐标,游戏继续 3.每次排查完以后,还应该判断输赢。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | void Find(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)//排查 { ????int x, y; ????int win = 0;//记录行动次数 ????while (1) ????{ ????????printf("请输入坐标x=? y="); ????????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] != '*')//如果这个坐标已经被走过了,show里面就会存数字,就不等于初始化的*了 ????????????{ ????????????????printf("坐标已被占用,请重新输入"); ????????????} ????????????else//计算周围的雷数 ????????????{ ????????????????win++;//行动合法则行动次数+1 ????????????????int count = get_mine_count(mine, x, y);//调用获取周围地雷数量的函数 ????????????????show[x][y] = count + '0';//因为返回的count是整形,而show是字符型,所以应该加上字符'0',才可以以对应的字符形式存入数组。 ????????????????system("cls");//清空屏幕 ????????????????DisplayBoard(show, ROW, COL);//打印 ????????????????//每次排查完以后,还应该判断输赢。即当玩家踩完了所有非雷的区域,棋盘的格子数=row*col,雷数=10,当row*col-10=玩家行动合法次数的时候,就判定玩家为赢 ????????????????if (ROW * COL - 10 == win) ????????????????{ ????????????????????printf("恭喜你,赢了!\n"); ????????????????????DisplayBoard(show, ROW, COL); ????????????????????break; ????????????????} ????????????} ????????} ????????else ????????{ ????????????printf("输入值不合法,请重新输入坐标值\n"); ????????} ????} } |
然后我们发现get_mine_count函数还没定义,我们就直接在game.c这个里面进行定义,不需要再声明了。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | static int get_mine_count(char mine[ROWS][COLS], int x, int y)//本函数仅用于计算周围的雷数,所以可以用static修饰 { ????int count = 0; ????if (mine[x - 1][y - 1] == '1')count++; ????if (mine[x][y - 1] == '1')count++; ????if(mine[x + 1][y - 1] == '1') count++; ????if(mine[x - 1][y] == '1')count++; ????if(mine[x + 1][y] == '1')count++; ????if(mine[x + 1][y - 1] == '1')count++; ????if(mine[x + 1][y] == '1') count++; ????if(mine[x + 1][y + 1] == '1')count++; ????return count; } |
计算周围的雷即把周围的坐标都判断一次,如果有一个是1,count就+1,判断完之后返回count。 但这样是不是显得有些冗长了呢?于是我们可以改写一下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //由上面的代码,还可以写成循环的形式,我们观察x的范围是x-1~x+1,y的范围是y-1~y+1 //于是定义两个循环变量 int i, j; for (i = -1; i <= 1; i++) { ????for (j = -1; j <= 1; j++) ????{ ????????if (i == 0 && j == 0)continue; ????????if (mine[x + i][j + i]=='1') ????????{ ????????????count++; ????????} ????} } return count; |
还可以改写成这样的形式:
| 1 2 3 4 | static int get_mine_count(char mine[ROWS][COLS], int x, int y)//本函数仅用于计算周围的雷数,所以可以用static修饰 { return mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] + mine[x - 1][y] + mine[x + 1][y] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0'; } |
于是基本的游戏底层逻辑就这样实现了 全部源码如下:
test.c
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | #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_board[ROWS][COLS] = { 0 };//内部,给开发者测试的,1是雷,0无雷 ?? ?char show_board[ROWS][COLS] = { 0 };//展示给玩家看的,默认存放‘*’ ?? ?//初始化 ?? ?InitBoard(mine_board,ROWS,COLS,'0'); ?? ?InitBoard(show_board, ROWS, COLS, '*'); ?? ?//打印 ?? ?/*DisplayBoard(mine_board, ROW, COL);*/ ?? ?//DisplayBoard(show_board, ROW, COL); ?? ?//布置雷 ?? ?SetMine(mine_board, ROW, COL); ?? ?DisplayBoard(show_board, ROW, COL); ?? ?//排查雷 ?? ?FindMine(mine_board, show_board,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; } |
game.h
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #include <stdio.h> #include <time.h> #include <stdlib.h> #define ROW 9 #define COL 9 #define ROWS ROW+2 #define COLS COL+2 #define PUT 10//雷数 void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);//初始化 void DisplayBoard(char board[ROWS][COLS], int row, int col); //打印 void SetMine(char board[ROWS][COLS], int row, int col);//布置雷 void FindMine(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col);//排查雷 |
game.c
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 | #define ?_CRT_SECURE_NO_WARNINGS 1 #include "game.h" void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)//初始化 { ?? ?int i = 0; ?? ?int j = 0; ?? ?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)//打印 { ?? ?int i = 0; ?? ?int j = 0; ?? ?printf("------------扫雷游戏-------------\n"); ?? ?for (i = 0; i <= row; 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 SetMine(char board[ROWS][COLS], int row, int col)//布置雷 { ?? ?int x = 0; ?? ?int y = 0; ?? ?//雷的下标必然为1-9 ?? ?int count = PUT;//雷数 ?? ?while (count) ?? ?{ ?? ??? ?x = rand() % 9 + 1;//rand%9生成0~8,0~8+1即为1~9 ?? ??? ?y = rand() % 9 + 1; ?? ??? ?//判断合法性 ?? ??? ?if (x >= 1 && x <= row && y >= 1 && y <= col&&board[x][y]=='0') ?? ??? ?{ ?? ??? ??? ?board[x][y] = '1'; ?? ??? ??? ?count--; ?? ??? ?} ?? ?} } static int get_mine_count(char mine_board[ROWS][COLS], int x, int y) { ?? ?return mine_board[x - 1][y + 1] + ?? ??? ?mine_board[x][y + 1] + ?? ??? ?mine_board[x + 1][y + 1] + ?? ??? ?mine_board[x - 1][y] + ?? ??? ?mine_board[x + 1][y] + ?? ??? ?mine_board[x - 1][y - 1] + ?? ??? ?mine_board[x][y - 1] + ?? ??? ?mine_board[x + 1][y - 1] - 8 * '0'; } void FindMine(char mine_board[ROWS][COLS], char show_board[ROWS][COLS], int row, int col) { ?? ?int x = 0; ?? ?int y = 0; ?? ?int win = row * col - PUT; ?? ?while (win) ?? ?{ ?? ??? ?printf("请输入坐标:>\n"); ?? ??? ?scanf("%d%d", &x, &y); ?? ??? ?if (x >= 1 && x <= row && y >= 1 && y <= col)//判断坐标合法性 ?? ??? ?{ ?? ??? ??? ?if (mine_board[x][y] == '1') ?? ??? ??? ?{ ?? ??? ??? ??? ?printf("非常遗憾,你被炸死了\n"); ?? ??? ??? ??? ?DisplayBoard(mine_board, ROW, COL); ?? ??? ??? ??? ?break; ?? ??? ??? ?} ?? ??? ??? ?else if (show_board[x][y] != '*')//如果这个坐标已经被走过了,show里面就会存数字,就不等于初始化的*了 ?? ??? ??? ?{ ?? ??? ??? ??? ?printf("坐标已被占用,请重新输入\n"); ?? ??? ??? ?} ?? ??? ??? ?else ?? ??? ??? ?{ ?? ??? ??? ??? ?int count = get_mine_count(mine_board, x, y); ?? ??? ??? ??? ?show_board[x][y] = '0'+count; ?? ??? ??? ??? ?win--; ?? ??? ??? ??? ?system("cls");//清空屏幕 ?? ??? ??? ??? ?DisplayBoard(show_board, ROW, COL); ?? ??? ??? ?} ?? ??? ?} ?? ??? ?else ?? ??? ?{ ?? ??? ??? ?printf("输入坐标不合法,请重新输入\n"); ?? ??? ?} ?? ?} ?? ?if (win == 0) ?? ?{ ?? ??? ?printf("恭喜你赢了\n"); ?? ?} } |
我们还可以对游戏进行进一步优化
①设置难度,即定义棋盘大小
②如果不是雷,可以展开一片-函数递归
③提供选项,进行标记
等我技术变好了,再回来优化!
|