如何生成构架图(结构图生成)
834
2022-05-28
⭐️前面的话⭐️
本篇文章将介绍C语言实现初级的扫雷游戏。也就是9x9的棋盘,10个地雷。
1.扫雷游戏概述
对于扫雷小游戏,我相信大家一定很熟悉,都会玩,就算不会玩,也应该听说过这个游戏。
《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。
以windows XP自带扫雷winmine.exe为例(其它版本的扫雷游戏与之大同小异)。游戏区包括雷区、地雷计数器(位于左上角,记录剩余地雷数)和计时器(位于右上角,记录游戏时间),确定大小的矩形雷区中随机布置一定数量的地雷(初级为9x9个方块10个雷,中级为16x16个方块40个雷,高级为16x30个方块99个雷,自定义级别可以自己设定雷区大小和雷数,但是雷区大小不能超过24x30),玩家需要尽快找出雷区中的所有不是地雷的方块,而不许踩到地雷。
本篇文章将介绍C语言实现初级的扫雷游戏。也就是9x9的棋盘,10个地雷。
2.扫雷小游戏设计目的
作为一款小游戏,无论使用什么方法实现,都应该能够反复玩。
拥有一个简单清晰的菜单,能够让玩家快速上手。
拥有一个清晰的扫雷棋盘界面。
能够显示实时扫雷的概况,并且当玩家踩雷时,也要让玩家知道所有雷的分布。
能够记录完成一局游戏的时间。
扫雷时,如果未扫到雷,会自动扩展排雷,将附近没有雷的范围显示出来。
扫雷时,如果目的扫雷行列坐标周围存在雷,会在目的行列坐标棋盘上显示附近雷的个数。
第一次永远踩不到雷。
3.扫雷小游戏设计思路
3.1预处理数据,定义头文件
在所有游戏模块设计前,应当把所有的模块声明在一个头文件中,方便把握游戏的整体框架。
#pragma once #include
3.2菜单设计
设计菜单的目的是给玩家提供玩或不玩游戏的选择。选择1就开始游戏,选择0就退出游戏,输入其他的内容则重新输入。
//菜单 void meau() { printf("-----------------------------------\n"); printf("***********************************\n"); printf("********** 1 开始扫雷 **********\n"); printf("********** 0 退出游戏 **********\n"); printf("***********************************\n"); printf("-----------------------------------\n"); }
3.3小游戏基础结构
在设计这个游戏时,要让他能够重复玩,所以外部选择do…while循环结构,以用户的输入做判断,输入0退出游戏,输入其他继续循环。内部要实现玩家选择权,所以使用switch结构,根据玩家的输入,来做出相应的程序执行。
#define _CRT_SECURE_NO_WARNINGS 1 #include "game.h" int main() { int input = 0; srand((unsigned int)time(NULL));//生成随机种子数 do { meau(); printf("请根据菜单选择是否游戏\n请输入>"); scanf("%d", &input); switch (input) { case 1: printf("开始(%dx%d %d个雷)扫雷游戏!\n",ROW, COL, MINE_NUMBER); game(); break; case 0: printf("退出游戏!\n"); break; default: printf("输入错误,请重新输入!\n"); break; } } while (input); return 0; }
3.4扫雷小游戏运行原理
扫雷小游戏运行过程封装在一个名为game的函数中,大致运行步骤为创建布置地雷的二维数组,创建玩家扫雷的二维数组,并且分别对它们进行初始化,开始游戏计时,布置雷区,展示玩家扫雷棋盘,玩家扫雷,显示游戏结果与所用时间。
我在布置地雷数组中初始化为‘0’,使用‘1’在布置地雷数组中布置雷区。而用于玩家扫雷的数组中初始化‘*’,使用1-8的数字填充玩家所扫雷的坐标,展示附近区域雷的个数,这样玩家就能通过所展现的地雷个数推断附近哪个地方有地雷哪个地方没有地雷,空格展示附近无雷区。
//扫雷游戏 void game() { char mine[ROWS][COLS] = { 0 };//创建布雷数组 char show[ROWS][COLS] = { 0 };//创建扫雷数组 InitBoard(mine, ROWS, COLS, '0');//初始化布雷数组 InitBoard(show, ROWS, COLS, '*');//初始化扫雷数组 //正式开始游戏,开始计时 clock_t start = clock(); //布置雷 SetMine(mine, ROW, COL); DisplayShow(show, ROW, COL); //DisplayShow(mine, ROW, COL);//开挂 //玩家找雷 CheckMine(mine, show, ROW, COL); SpendTime(start); }
3.5扫雷棋盘数组初始化
将布置地雷棋盘初始化为‘0’,并且只有游戏结束时玩家可见。玩家扫雷棋盘初始化为‘*’,有一定的神秘感,当开始游戏和玩家扫雷时可见。因为在后面判断附近雷的个数时,为了防止数组越界,在初始创建数组时多创建一圈的扫雷棋盘格子。如图,
//初始化棋盘 void InitBoard(char board[ROWS][COLS], int rows, int cols, char ch) { int i = 0; for (i = 0; i < rows; i++) { int j = 0; for (j = 0; j < cols; j++) { board[i][j] = ch; } } }
3.6显示棋盘
大致样式如下,首先打印一排短线(-)然后打印‘|’+0-9的数字。从分割线那一行开始,再将其拆分为‘|’+“—”使用循环并注意打印次数,打印出分割线行,然后在下一行打印数据显示和扫雷信息数据显示,拆分成‘|’+‘空格 数据 空格’,注意每组字符的打印次数用循环实现一个10X10的扫雷信息显示棋盘。
//显示棋盘 void DisplayShow(char board[ROWS][COLS], int row, int col) { int i = 0; //printf("_________________________________________\n"); printf("-----------------------------------------\n"); for (i = 0; i <= col; i++) { printf("|"); printf(" %d ", i); } printf("|\n"); for (i = 0; i < row ; i++) { int j = 0; for (j = 0; j < col + 2; j++) { printf("|"); if (j < col + 1) printf("---"); } printf("\n"); printf("| %d ", i + 1); for (j = 0; j < col + 1; j++) { printf("|"); if (j < col) printf(" %c ", board[i + 1][j + 1]); } printf("\n"); } printf("-----------------------------------------\n"); }
3.7使用随机数生成坐标布置雷区
在创建的数组内部扫雷棋盘区域(9X9)随机布置10个地雷。实质上就是生成10个随机坐标,在没有布置雷的坐标内一共布置10个地雷。使用rand()对内部行列数取模+1,保证了生成的行列坐标在1-9合法范围内。
//布置雷 void SetMine(char mine[ROWS][COLS], int row, int col) { int cnt = MINE_NUMBER; int x = 0; int y = 0; while (cnt) { x = rand() % row + 1; y = rand() % col + 1; if (mine[x][y] != '1') { mine[x][y] = '1'; cnt--; } } }
3.8使用time库显示游戏所用时间
在C语言库函数time.h中有一个时钟函数,能够计算从进入程序到程序运行结束的时间,这个函数就是clock函数。我在一开始进入时加入了开始时间start=clock()表示程序开始时到此语句执行过程的时间,在game函数模块程序最后加入显示游戏时间的自定义函数,函数内部有一个end=clock()表示程序开始运行到执行目前语句的时间,将end-start就能得到玩家完成游戏所花时间,但是得到的结果的单位不是秒,是计算机内部的一个时间单位,想要将他化成秒,需要除以一个库函数中的宏CLOCKS_PER_SEC,这样就能得到秒数了。
clock_t是time库中定义的一个数据类型,实质上是不带符号的整数。
//输出所费时间 void SpendTime(clock_t start) { clock_t end = clock(); clock_t total = end - start; printf("游戏所用时间为:%.1lf秒!\n", 1.0 * total / CLOCKS_PER_SEC); }
3.9周围地雷个数和初始化字符的计算
当玩家对扫雷棋盘中的一个行列坐标(x,y)进行扫雷时,如果不是雷,需要统计周围8个坐标雷的个数,我们可以通过对布置地雷棋盘从x-1到x+1行y-1到y+1列进行扫描,将所有字符ASCII码加起来,然后再减去board[x][y]的字符和8个字符0,就能得到周围地雷个数。(因为字符1比字符0的ASCII码多1)
计算棋盘上初始化字符的个数,可以通过对数组扫描,通过判断是否是初始化时的字符计算初始化时的字符个数。得到的初始字符个数可以用来判断地雷是否全部排完。
//求某坐标周围雷的个数 int GetMineCount(char board[ROWS][COLS], int x, int y) { int mcnt = 0; int i = 0; int j = 0; for (i = x - 1; i <= x + 1; i++) { for (j = y - 1; j <= y + 1; j++) { mcnt += board[i][j]; } } mcnt = mcnt - board[x][y] - 8 * '0'; return mcnt; } //求展示扫雷棋盘上含有初始化字符的个数,用来判断游戏何时终止 int AroundInitCount(char board[ROWS][COLS], int row, int col,char initch) { int i = 0; int j = 0; int count = 0; for (i = 1; i <= row; i++) { for (j = 1; j <= col; j++) { if (board[i][j] == initch) count++; } } return count; }
3.10扩展排雷,展开附近非雷区
扩展非雷区需要满足三个条件,一是目的坐标无雷,二是周围周围雷的个数为0,三是目的坐标为初始字符。根据这三个条件采用递归进行扩展排雷。
//扩展排雷,展开周围非雷区 void ExtendNotMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, char minech, char showch) { int n = 0; n = GetMineCount(mine, x, y); if (n == 0) { show[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[i][j] == minech && show[i][j] == showch)//周围坐标满足自身不是雷且还是初始化字符,进入递归再次扩展排雷 { ExtendNotMine(mine, show, i, j, '0', '*');//递归排雷 } } } } else show[x][y] = n + '0';//如果附近有雷,展示雷的个数 }
3.11让小可爱第一次永远踩不到雷
如果有一个小可爱运气太好,第一次就中雷了,为了提高游戏可玩性,我们应该要把那个雷偷偷移动到另一个没有雷的地方。这样,这个小可爱怎么也察觉不到他第一次就中了雷。
//第一次排雷不被炸死,提高游戏可玩性,如果运气太好第一次踩雷我就偷偷把雷移走 void FirstSafe(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y) { int count = 0; if (mine[x][y] == '1') { mine[x][y] = '0';//把地雷偷偷移走 while (1)//随机放入另一个没有地雷的地方 { int rx = rand() % row + 1; int ry = rand() % col + 1; if (mine[rx][ry] == '0' && (rx != x) && (ry != y)) { mine[rx][ry] = '1'; break; } } count = GetMineCount(mine, x, y);//计算周围雷的个数 show[x][y] = count + '0'; ExtendNotMine(mine, show, x, y, '0', '*');//扩展排雷 DisplayShow(show, row, col);//告诉小可爱没有踩雷 } }
3.12玩家扫雷与判决胜负程序
扫雷肯定不止扫一次,那肯定需要用到循环结构,结束条件就是当玩家扫雷棋盘中的初始字符与雷的个数相等时,就说明扫雷成功。否则要么还没有完成要么被炸死了。
//玩家排查雷 void CheckMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { int x = 0; int y = 0; int count = 0;//判断循环是否继续参数 int ret = 0;//防止第一次被炸死,所用参数 while (1) { printf("请输入排雷行列坐标>"); scanf("%d%d", &x, &y); if ((x >= 1 && x <= 9) && (y >= 1 && y <= 9)) { if (mine[x][y] == '1') { if (ret == 0) { FirstSafe(mine, show, row, col, x, y); ret++; count = AroundInitCount(show, ROW, COL, '*'); if (count == MINE_NUMBER) { printf("恭喜你!扫雷成功!\n"); DisplayShow(mine, ROW, COL); break; } } else { printf("很遗憾!你被炸死了!\n"); DisplayShow(mine, ROW, COL); break; } } else { int around = GetMineCount(mine, x, y); show[x][y] = around + '0';//整型加上'0'得相应字符数字 ExtendNotMine(mine, show, x, y, '0', '*'); DisplayShow(show, ROW, COL); count = AroundInitCount(show, ROW, COL, '*'); if (count == MINE_NUMBER) { printf("恭喜你!扫雷成功!\n"); DisplayShow(mine, ROW, COL); break; } } } else printf("输入坐标越界,请重新输入!\n"); } }
4.代码合集
4.1 头文件 game.h
#pragma once #include
4.2 模块实现文件 game.c
#define _CRT_SECURE_NO_WARNINGS 1 #include "game.h" //菜单 void meau() { printf("-----------------------------------\n"); printf("***********************************\n"); printf("********** 1 开始扫雷 **********\n"); printf("********** 0 退出游戏 **********\n"); printf("***********************************\n"); printf("-----------------------------------\n"); } //扫雷游戏 void game() { char mine[ROWS][COLS] = { 0 };//创建布雷数组 char show[ROWS][COLS] = { 0 };//创建扫雷数组 InitBoard(mine, ROWS, COLS, '0');//初始化布雷数组 InitBoard(show, ROWS, COLS, '*');//初始化扫雷数组 //正式开始游戏,开始计时 clock_t start = clock(); //布置雷 SetMine(mine, ROW, COL); DisplayShow(show, ROW, COL); //DisplayShow(mine, ROW, COL);//开挂 //玩家找雷 CheckMine(mine, show, ROW, COL); SpendTime(start); } //初始化棋盘 void InitBoard(char board[ROWS][COLS], int rows, int cols, char ch) { int i = 0; for (i = 0; i < rows; i++) { int j = 0; for (j = 0; j < cols; j++) { board[i][j] = ch; } } } //输出所费时间 void SpendTime(clock_t start) { clock_t end = clock(); clock_t total = end - start; printf("游戏所用时间为:%.1lf秒!\n", 1.0 * total / CLOCKS_PER_SEC); } //显示棋盘 void DisplayShow(char board[ROWS][COLS], int row, int col) { int i = 0; //printf("_________________________________________\n"); printf("-----------------------------------------\n"); for (i = 0; i <= col; i++) { printf("|"); printf(" %d ", i); } printf("|\n"); for (i = 0; i < row ; i++) { int j = 0; for (j = 0; j < col + 2; j++) { printf("|"); if (j < col + 1) printf("---"); } printf("\n"); printf("| %d ", i + 1); for (j = 0; j < col + 1; j++) { printf("|"); if (j < col) printf(" %c ", board[i + 1][j + 1]); } printf("\n"); } printf("-----------------------------------------\n"); } //布置雷 void SetMine(char mine[ROWS][COLS], int row, int col) { int cnt = MINE_NUMBER; int x = 0; int y = 0; while (cnt) { x = rand() % row + 1; y = rand() % col + 1; if (mine[x][y] != '1') { mine[x][y] = '1'; cnt--; } } } //玩家排查雷 void CheckMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) { int x = 0; int y = 0; int count = 0; int ret = 0; while (1) { printf("请输入排雷行列坐标>"); scanf("%d%d", &x, &y); if ((x >= 1 && x <= 9) && (y >= 1 && y <= 9)) { if (mine[x][y] == '1') { if (ret == 0) { FirstSafe(mine, show, row, col, x, y); ret++; count = AroundInitCount(show, ROW, COL, '*'); if (count == MINE_NUMBER) { printf("恭喜你!扫雷成功!\n"); DisplayShow(mine, ROW, COL); break; } } else { printf("很遗憾!你被炸死了!\n"); DisplayShow(mine, ROW, COL); break; } } else { int around = GetMineCount(mine, x, y); show[x][y] = around + '0';//整型加上'0'得相应字符数字 ExtendNotMine(mine, show, x, y, '0', '*'); DisplayShow(show, ROW, COL); count = AroundInitCount(show, ROW, COL, '*'); if (count == MINE_NUMBER) { printf("恭喜你!扫雷成功!\n"); DisplayShow(mine, ROW, COL); break; } } } else printf("输入坐标越界,请重新输入!\n"); } } //求某坐标周围雷的个数 int GetMineCount(char board[ROWS][COLS], int x, int y) { int mcnt = 0; int i = 0; int j = 0; for (i = x - 1; i <= x + 1; i++) { for (j = y - 1; j <= y + 1; j++) { mcnt += board[i][j]; } } mcnt = mcnt - board[x][y] - 8 * '0'; return mcnt; } //求展示扫雷棋盘上含有初始化字符的个数,用来判断游戏何时终止 int AroundInitCount(char board[ROWS][COLS], int row, int col,char initch) { int i = 0; int j = 0; int count = 0; for (i = 1; i <= row; i++) { for (j = 1; j <= col; j++) { if (board[i][j] == initch) count++; } } return count; } //扩展排雷,展开周围非雷区 void ExtendNotMine(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, char minech, char showch) { int n = 0; n = GetMineCount(mine, x, y); if (n == 0) { show[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[i][j] == minech && show[i][j] == showch)//周围坐标满足自身不是雷且还是初始化字符,进入递归再次扩展排雷 { ExtendNotMine(mine, show, i, j, '0', '*');//递归排雷 } } } } else show[x][y] = n + '0'; } //第一次排雷不被炸死,提高游戏可玩性,如果运气太好第一次踩雷我就偷偷把雷移走 void FirstSafe(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y) { int count = 0; if (mine[x][y] == '1') { mine[x][y] = '0'; while (1) { int rx = rand() % row + 1; int ry = rand() % col + 1; if (mine[rx][ry] == '0' && (rx != x) && (ry != y)) { mine[rx][ry] = '1'; break; } } count = GetMineCount(mine, x, y); show[x][y] = count + '0'; ExtendNotMine(mine, show, x, y, '0', '*'); DisplayShow(show, row, col); } }
4.3 测试文件 test.c
#define _CRT_SECURE_NO_WARNINGS 1 #include "game.h" int main() { int input = 0; srand((unsigned int)time(NULL)); do { meau(); printf("请根据菜单选择是否游戏\n请输入>"); scanf("%d", &input); switch (input) { case 1: printf("开始(%dx%d %d个雷)扫雷游戏!\n",ROW, COL, MINE_NUMBER); game(); break; case 0: printf("退出游戏!\n"); break; default: printf("输入错误,请重新输入!\n"); break; } } while (input); return 0; }
4.4 游戏运行截图
C 语言 IDE
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。