项目要求
实现一个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"); ?? ?} } |
我们还可以对游戏进行进一步优化
①设置难度,即定义棋盘大小
②如果不是雷,可以展开一片-函数递归
③提供选项,进行标记
等我技术变好了,再回来优化!
|