前言:
为了巩固C语言,我运用所学的知识,写了一篇关于扫雷游戏的博客。如果有大佬看到这篇文章,如有不足之处,请你一定要指出来。
游戏的规则:
我们在棋盘格中任意点开一个格子(输入行和列确认这个格子),若这个格子不是雷就排除了这个位置,排除后这个格子会显示将它围起来的几个格子中有几颗雷,如果点开的格子埋有雷则为游戏失败。
一、模块化编程
模块化编程:把各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用函数的声明,其它.c文件想使用其中的代码时,只需要#include "XXX.h"文件即可。使用模块化编程可极大的提高代码的可阅读性、可维护性、可移植性等。
这里我创建了三个文件:game.h文件用来写自定义函数的声明;game.c文件用来写定义;test.c文件用了写整个程序的实现。(这里c文件中只需#include “game.h”,就能使头文件和源文件连接起来)
如下:
二、游戏的思路
1.游戏的设计:
1).扫雷游戏需要存储布置好的雷的信息,需要一个二维数组
2).给两个9* 9的二维数组:
一个存放雷的信息,另一个存放布置好雷的信息
3).为了防止在统计坐标周围的雷的个数的时候数组越界,我们把数组设计成11* 11的二维数组
4).数组是11*11,并且是字符数组(这里我们把‘0’表示非雷,‘1’表示雷)
2.游戏的逻辑:
1).创建菜单函数选择 进入游戏 以及 退出游戏
2).创建游戏函数,初始化棋盘(游戏在走的过程中要进行数据的存储,可以使用11*11的二维数组char board[ROWS][COLS];)
3).打印棋盘
4).布置雷
5).排查雷
三、实现游戏步骤/过程
1…创建菜单函数选择进入游戏以及退出游戏
1).创建菜单
void menu() { printf("**********************\n"); printf("**** 1.开始游戏 ****\n"); printf("**** 0.退出游戏 ****\n"); printf("**********************\n"); }
测试:
2).选择进入游戏以及退出游戏(实现玩一把不过瘾就会在来一次用到do while)
#include "game.h" void menu() { printf("**********************\n"); printf("**** 1.开始游戏 ****\n"); printf("**** 0.退出游戏 ****\n"); printf("**********************\n"); } int main() { int input; do { menu(); scanf("%d", &input); switch (input)//用来进行一次操作 { case 0: break; case 1: { printf("扫雷游戏\n");//可以进入游戏 break; } default: { printf("笨蛋输错了,重新输入:\n"); break; } } }while (input);//可以进行多次游戏 return 0; }
测试:
2.创建游戏函数,初始化棋盘
1).创建游戏函数:
创建两个相同的数组:
因为用户在玩游戏的时候看到的是被覆盖起来的棋盘,看不到哪些地方都埋着雷的,这是显示棋盘。我们还需要一个埋雷的棋盘,这样才能实现玩家在玩游戏时通关或者未通关,将埋雷的点位呈现给玩家。
创建11*11数组的原因:
在扫雷中,我们排查的一个位置上要是显示了数字,就说明在此位置的周围 8 个格子中存在这个数字个雷,那么我们在排四个边上的位置时候格子是不够 8 个的,但是我们的功能在实现的时候还是会去找这 8 个格子的,这样就会造成数组的越界访问的问题
为了方便后期我们能更改棋盘的大小和雷的个数,使用宏定义define
#define ROW 9// 显示棋盘的行数 #define COL 9// 显示棋盘的列数 #define ROWS ROW+2// 实际棋盘的行数 #define COLS COL+2// 实际棋盘的列数 #define mine_count 10//设置雷的数量
2).初始化棋盘
void InitBoard(char board[][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; } printf("\n"); }
3.打印棋盘
void DisPlayBoard(char board[][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++) { printf("%d ", i);//显示列坐标 for (j = 1; j <= col; j++) printf("%c ", board[i][j]); printf("\n"); } }
测试两个棋盘:
测试:
存放雷的棋盘
用户操作的棋盘
4.布置雷
void SetMine(char mine[][COLS], int row, int col) { int i=mine_count,x,y; while(i) { x = rand() % row + 1; y = rand() % col + 1; if (mine[x][y] == '0') { mine[x][y] = '1';//字符1就是雷 i--; } } }
测试(10个雷放数组mine_1里):
5.排查雷
首先我们对玩家输入的坐标位置进行判断,是否合法,坐标必须在 1~row/col 之间(包含 1 和 row/col),如果不在此范围,代码会走第一个 else 语句,提醒玩家“笨蛋,坐标输错了,请重新输入:”的字样。如果合法就会走第一个 if 语句,进去之后我们对该位置进行判断,看是否该位置为字符 ‘1’,如果是字符 ‘1’ 就是踩到了雷,就会提醒玩家“很遗憾,你被炸死了”的字样,并将雷的分布图给玩家呈现出来如果是字符 ‘0’ 就走 else 语句,对该位置的周围 8 个格子的雷的个数进行计算并标注出来,这时我们就调用GetMine()函数来计算:
void FindMine(char mine_1[][COLS],char mine_2[][COLS],int row,int col) { int x,y,count; while (1) { printf("请输入排查雷的坐标:\n"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col)//坐标输入正确 { if (mine_1[x][y] == '1')//踩雷了 { printf("你被炸死了\n"); DisPlayBoard(mine_1, ROW, COL); break; } else//没踩雷 { count = GetMine(mine_1, x, y);//统计雷的四周有几个雷 mine_2[x][y] = '0' + count; //if (count == 0) // expend(mine_1, mine_2, row, col, x, y); DisPlayBoard(mine_2, ROW, COL); } } else printf("笨蛋,坐标输错了,请重新输入:\n"); } }
测试结果:
6.判断输赢
mine_2数组中没有被排查的坐标为‘ * ’,所以只要统计剩下的‘ * ’是否等于雷的个数,等于则游戏胜利。
lswin(char mine_2[][COLS], int row, int col)//判断有几个符号‘*’然后返回 { int i,j,c; for(i=0;i<row;i++) for (j = 0; j < col; j++) { if (mine_2[i][j] == '*') c++; } return c; }
7.扩展
说明:扩展坐标的范围应该加以限制,如果我要排查的坐标有雷,那么直接游戏结束,没雷就要向周围8个坐标扩展,并且没雷就把这个坐标设置为空格,只有设置了才能防止被重复递归!然后要限制坐标范围,如果到了边界,就没必要向边界外再继续递归,会越界的,递归出后就是找到了周围有雷,显示雷的个数就可以了
void expend(char mine_1[][COLS], char mine_2[][COLS], int row, int col, int x, int y) { int count = GetMine(mine_1, x, y);//统计雷的四周有几个雷 if (count == 0) { mine_1[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 (mine_2[i][j] == '*' && i > 0 && i <= row && j > 0 && j <= col) { expend(mine_1, mine_2, row, col, i, j); } } } } else {//递归的出口就是我统计到周围有几个雷 mine_2[x][y] = count + '0'; } }
四、总代码量
1.测试游戏test.c文件的代码
#include "game.h" void menu() { printf("**********************\n"); printf("**** 1.开始游戏 ****\n"); printf("**** 0.退出游戏 ****\n"); printf("**********************\n"); } void game()//创建游戏 { system("cls");//每次玩游戏,都把前面的记录全部清掉 srand((unsigned)time(NULL)); //创建雷的数组(mine_1)显示数组(mine_2)两个数组的类型,大小相同 char mine_1[ROWS][COLS];//存放布置的雷的信息 char mine_2[ROWS][COLS];//存放排查出雷的信息 //初始化棋盘 InitBoard(mine_1, ROWS, COLS,'0');//初始化棋盘里全部赋‘0’ InitBoard(mine_2, ROWS, COLS, '*');//初始化棋盘里全部赋‘*’ //打印棋盘 //DisPlayBoard(mine_1, ROW, COL);//打印存放雷信息的9*9的棋盘 DisPlayBoard(mine_2, ROW, COL);//打印排查出雷信息的9*9的棋盘 //布置雷 SetMine(mine_1, ROW, COL); //排查雷 FindMine(mine_1,mine_2, ROW, COL); } int main() { int input; do { menu(); scanf("%d", &input); switch (input)//用来进行一次操作 { case 0: { printf("退出游戏\n"); break; } case 1: { game();//可以进入游戏 break; } default: { printf("笨蛋输错了,重新输入:\n"); break; } } }while (input);//可以进行多次游戏 return 0; }
2.游戏的实现game.c文件的代码
#include "game.h" void InitBoard(char board[][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; } printf("\n"); } void DisPlayBoard(char board[][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++) { printf("%d ", i);//显示列坐标 for (j = 1; j <= col; j++) printf("%c ", board[i][j]); printf("\n"); } } void SetMine(char mine[][COLS], int row, int col) { int i=mine_count,x,y; while(i) { x = rand() % row + 1; y = rand() % col + 1; if (mine[x][y] == '0') { mine[x][y] = '1';//字符1就是雷 i--; } } } int GetMine(char mine_1[][COLS], int x, int y) { return mine_1[x - 1][y - 1] + mine_1[x - 1][y] + mine_1[x - 1][y + 1] + mine_1[x][y - 1] + mine_1[x][y + 1] + mine_1[x + 1][y - 1] + mine_1[x + 1][y] + mine_1[x + 1][y + 1] - 8 * '0'; } void FindMine(char mine_1[][COLS],char mine_2[][COLS],int row,int col) { int x,y,count; while (1) { printf("请输入排查雷的坐标:\n"); scanf("%d %d", &x, &y); if (x >= 1 && x <= row && y >= 1 && y <= col)//坐标输入正确 { if (mine_1[x][y] == '1')//踩雷了 { printf("你被炸死了\n"); DisPlayBoard(mine_1, ROW, COL); break; } else//没踩雷 { expend(mine_1, mine_2, row, col, x, y); DisPlayBoard(mine_2, ROW, COL); int a=lswin(mine_2, row, col);//判断是否排查完 if (a == ROW * COL - mine_count)//不需要排查了,赢啦 { printf("你赢了,真棒!\n"); break; } } } else printf("笨蛋,坐标输错了,请重新输入:\n"); } } void expend(char mine_1[][COLS], char mine_2[][COLS], int row, int col, int x, int y) { int count = GetMine(mine_1, x, y);//统计雷的四周有几个雷 if (count == 0) { mine_1[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 (mine_2[i][j] == '*' && i > 0 && i <= row && j > 0 && j <= col) { expend(mine_1, mine_2, row, col, i, j); } } } } else {//递归的出口就是我统计到周围有几个雷 mine_2[x][y] = count + '0'; } } lswin(char mine_2[][COLS], int row, int col)//判断有几个符号‘* { int i,j,c=0; for(i=0;i<row;i++) for (j = 0; j < col; j++) { if (mine_2[i][j] == '*') c++; } return c; }
3.游戏函数的声明game.h头文件代码
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> #include <stdlib.h> #include <time.h> #include <windows.h> #define ROW 9// 显示棋盘的行数 #define COL 9// 显示棋盘的列数 #define ROWS ROW+2// 实际棋盘的行数 #define COLS COL+2// 实际棋盘的列数 #define mine_count 10//设置雷的数量 void InitBoard(char board[][COLS], int rows, int cols, char set);//初始化棋盘 void DisPlayBoard(char board[][COLS], int row, int col);//布置棋盘 void SetMine(char mine[][COLS], int row, int col);//布置雷 void FindMine(char mine_1[][COLS], char mian_2[][COLS], int row, int col);//排查雷 int GetMine(char mine_1[][COLS], int x, int y);//统计雷的四周有几个雷 void expend(char mine_1[][COLS], char mine_2[][COLS], int row, int col, int x, int y);//扩展坐标的范围 int lswin(char mine_2[][COLS], int row, int col);//统计有几个字符‘*’
测试