C语言---函数和数组实践:扫雷游戏

函数和数组实践:扫雷游戏

在这次的实践项目中,需要编写一个可以在在控制台运行的经典的扫雷游戏。

一、游戏要求

  • 游戏有菜单,可以通过菜单实现继续玩或者退出游戏
  • 游戏要求棋盘9*9,雷(10个)要求随机布置
  • 可以实现排查雷
    • 如果位置不是雷,就显示周围有几个雷
    • 如果位置是雷就炸死结束游戏
    • 把除了雷之外的所有非雷都找出来,排雷成功,游戏结束

二、游戏设计思路

游戏界面:

①首先对于菜单部分,可以使用基本的printf函数根据自己的想法打印出菜单。

/*初始菜单*/
void menu()
{printf("************************\n");printf("******   1.play   ******\n");printf("******   0.exit   ******\n");printf("************************\n");
}

②其次是关于棋盘的设置

  • 如何制造一个像经典扫雷游戏那样的界面? 可以用一个二维数组存放扫雷游戏需要的布置好的雷的信息。
  • 如何将雷和非雷区分开? 首先可以想到用 ‘*’ 表示雷,用 ‘#’ 表示非雷,这种想法没有问题,但是当描述周围有多少雷的时候并不方便,下面是一个新的思路,可以将雷用字符 ‘1’ 表示,非雷用字符 ’0‘ 表示,这种方式在排雷时就是快速得到周围雷的个数,只需要将周围的8个格子的数字相加就是雷的数量
  • 当雷布置完成开始游戏之后,玩家点击一个格子之后,这个格子显示出来的1,到底是雷,还是表示这个不是雷是周围8个格子中存在一个雷这一信息,如何分辨这两种情况呢? 可以通过建立两个相同大小与类型的二位数组,一个用于布置雷 ’1‘ 和非雷 ’0‘ ,这个是玩家看不到的,另一个用于展示给玩家看的棋盘,存放的是玩家排查雷的信息,这样这两种情况就不会发生冲突。展示给玩家看的那一个二维数组为了保持神秘感,最开始都放 ’ *’ ,这样一开始第一种区分雷和非雷的想法也被否决。
  • 当在排雷的时候,如果排查到边缘的格子会出现下列图示中的情况,并且这个格子不是雷的话,在统计周围8个格子中雷的信息的时候就会发生越界访问的情况,这种问题应该如何解决? 可以将一开始设置的9*9大小的二位数组扩张成11*11大小的二位数组即可,这样遍历的时候就不会发生越界访问的情况。

#define ROW 9
#define COL 9#define ROWS ROW+2
#define COLS COL+2char mine[ROWS][COLS];		//存放雷的信息
char show[ROWS][COLS];		//存放显示给玩家的信息

文件结构设计

由于游戏规则的实现需要多种函数与逻辑之间的配合,并且锻炼自己的多文件处理和函数的声明和定义的能力,所以这个项目用下面三个文件实现。

test.c //文件中写游戏的测试逻辑
game.c //文件中写游戏中函数的实现等
game.h //文件中写游戏需要的数据类型和函数声明等

具体逻辑的实现

1、菜单选择部分的逻辑实现

​ 按照游戏要求需要实现持续游玩的要求,所以这里需要利用一个循环语句,所以这里的思路是:设置一个变量,用1表示开始游戏,用0表示结束游戏,利用do-while循环,并检测这个变量,正好在输入0时跳出循环并结束游戏。

int main()
{int input = 0;		//用于菜单选择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;
}
2、初始化棋盘和显示棋盘中的逻辑实现

​ 由于游戏规则设置需要在显示台进行游戏的显示和操作,所以需要写一个自定义函数填入前面分析的字符,并再写一个自定义函数用于将二维数组显示在控制台。

设计思路:

  • 这里因为前面设置的mine和show数组是同一类型的,所以只需要写一个函数就可以适用于这两个数组,这就是mine数组中雷用字符 ‘1’ 表示,非雷用字符 ’0‘ 表示的原因。

  • 而初始化棋盘和在控制台显示棋盘本质都是对二维数组的遍历。

main.c

void game()
{char mine[ROWS][COLS];		//存放雷的信息char show[ROWS][COLS];		//存放显示给玩家的信息/*初始化棋盘*/InitBoard(mine, ROWS, COLS, '0');InitBoard(show, ROWS, COLS, '*');/*显示用于展示给玩家的棋盘*/DisplayBoard(show, ROW, COL);
}

game.c

/*
函数:初始化棋盘函数
参数:board:要操作的二维数组棋盘row:棋盘宽度col:棋盘高度set:要填入二维数组棋盘的字符
返回值:无
*/
void InitBoard(char board[ROWS][COLS],int rows,int cols,char set)
{int i = 0;for (i = 0; i < ROWS; i++){int j = 0;for (j = 0; j < COLS; j++){board[i][j] = set;}}
}/*
函数:显示棋盘函数
参数:board:要显示的二维数组棋盘row:显示的棋盘宽度col:显示的棋盘高度
返回值:无
*/
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{printf("--------扫雷游戏--------\n");int i = 0;for (i = 1; i < row; i++){int j = 0;for (j = 1; j < col; j++){printf("%c", board[i][j]);}printf("\n");}
}	

注意:

  • 因为两个二维数组的类型都是char[ROWS][COLS],所以传过去的数组都是11*11,但是在显示棋盘的函数中遍历的时候只需要遍历中间的9*9的区域即可,所以在显示棋盘的函数中的for循环都将i,j都控制在了1~9的范围内。而初始化棋盘则无所谓为了方便直接全部遍历即可。
  • 其中为了后续排雷部分的坐标输入方便,这里需要在显示棋盘部分的周围列出行号和列号。在一开始就把列号打印出来,然后可以在每一行打印之前,先打印行号即可实现打印出行号和列号的目的。

运行结果:

3、布置雷的逻辑实现

对于布置雷实际上还是把雷放在了中间9*9的这块区域中去,而且这是属于雷的信息只需要对mine数组进行操作即可,但是这一部分最重要的还是如何实现**“随机”放雷**这一要求。

  • 第一点:直接操作mine数组中的9*9这一块区域即可
  • 第二点:利用rand函数,实现随机放雷这一要求

设计思路:

  • 定义变量COUNT规定放置的雷的数量,并循环放置直到雷的数量与COUNT的大小一样
  • 利用rand函数,结合公式**(随机生成a~b的数:a+rand( )%b-a+1)**
    • 注意使用rand函数之前需要添加头文件<stdlib.h> <time.h>
    • 并在main函数中加上语句srand((unsigned int)time(NULL));
  • 定义变量X Y分别作为雷的横坐标和纵坐标,对利用rand函数随机生成X Y
  • 要对生成的雷的坐标利用if语句进行检测,看是否已经放过雷
  • 其中对于生成雷的区域也是在中间的9*9这一区域,所以在调用放置函数是参数要写ROW和COL

game.c

/*
函数:布置雷函数(随机布置10个)
参数:board:要布置雷的二维数组棋盘row:布置雷的棋盘宽度col:布置雷的棋盘高度
返回值:无
*/
void SetMine(char board[ROWS][COLS], int row, int col)
{int count = COUNT;		//定义雷的数量while (count)			//循环放雷{int x = rand() % row + 1;int y = rand() % col + 1;if (board[x][y] == '0')	//判断是否已经放过雷{board[x][y] = '1';count--;}}
}

main.c

int main()
{/*以时间为基准生成用于产生随机数的种子*/srand((unsigned int)time(NULL));
}
4、排查雷的逻辑实现(第一版)

排查雷是在mine数组中查找,然后将查找的数据放在show数组中显示给玩家。

设计思路:

  • 首先编写一个自定义的排查雷函数,将mine和show两数组传过去用于对比
  • 在定义变量x y用于接收玩家选择的扫雷坐标,在定义一个变量win用于记录已经排查过的雷的数量
    • 用win表示已经排查出的雷,并将其作为跳出排查循环的参数
    • 对于接收坐标(x , y)需要加入if判断语句检测是否越界
    • 总结:在外界接收的信息都应该进行判断再使用,防止产生越界造成错误
  • 判断玩家输入的坐标是否是雷,并返回相应结果
    • 输入坐标是雷,结束游戏,显示mine数组展示答案
    • 输入坐标不是雷,显示周围的雷的个数并将该数据展示在show数组中,继续游戏
  • 最后结束游戏,展示最终答案,显示mine数组

game.c

/*
函数:计算周围雷个数函数
参数:mine:要布置雷的二维数组棋盘x:玩家输入的横坐标y:玩家输入的纵坐标
返回值:无
*/
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] + mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');
}/*
函数:排查雷函数
参数:mine:包含雷的信息的二维数组棋盘show:展示给玩家的二维数组棋盘row:排查雷的棋盘宽度col:排查雷的棋盘高度
返回值:无
*/
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{int x = 0;int y = 0;int win = 0;while (win < row * col - COUNT){printf("请输入要排查的坐标:");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;		//直接跳出while循环,结束游戏}else       //未排到雷,显示周围雷的个数{int count = GetMineCount(mine, x, y);show[x][y] = count + '0';	//显示周围的雷的个数DisplayBoard(show, ROW, COL);win++;	//排雷数加1}}else{printf("输入坐标有误,请重新输入\n");  //坐标非法,此时win还不满足跳出循环条件故,重新回到while循环中}}if (win == ROW * COL - COUNT)		//找到除了雷的所有位置,排雷成功{printf("恭喜你,排雷成功\n");DisplayBoard(mine, ROW, COL);}
}

注意:

  • 上述代码中对数字和字符的转化十分灵活。字符‘0’的ASCLL码值是48,数字0的ASCLL码值是0,当数字0加上‘0’的时候,得到的结果就是字符数字‘0’。上述代码通过这种转化将显示出来的字符和实际的数字建立了紧密的联系,这种思想十分重要。

三、最终代码汇总

game.h

#pragma once/*头文件*/
#include <stdio.h>
#include <time.h>
#include <stdlib.h>/*宏定义*/
#define COUNT 10		//雷的数量#define ROW 9			//棋盘宽度
#define COL 9			//棋盘高度#define ROWS ROW+2
#define COLS COL+2/*函数声明*/
/*初始化棋盘函数*/
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);/*计算周围雷的个数*/
int GetMineCount(char mine[ROWS][COLS], int x, int y);/*排雷函数*/
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

game.c

#define _CRT_SECURE_NO_WARNINGS#include "game.h"/*
函数:初始化棋盘函数
参数:board:要操作的二维数组棋盘row:棋盘宽度col:棋盘高度set:要填入二维数组棋盘的字符
返回值:无
*/
void InitBoard(char board[ROWS][COLS],int rows,int cols,char set)
{int i = 0;for (i = 0; i < ROWS; i++){int j = 0;for (j = 0; j < COLS; j++){board[i][j] = set;}}
}/*
函数:显示棋盘函数
参数:board:要显示的二维数组棋盘row:显示的棋盘宽度col:显示的棋盘高度
返回值:无
*/
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{printf("-----扫雷游戏-----\n");int i = 0;for (i = 0; i < col; i++){printf("%d ", i);	//打印行号}printf("\n");		//换行开始打印列号和棋盘for (i = 1; i < row; i++){printf("%d ", i);//打印列号int j = 0;for (j = 1; j < col; j++){printf("%c ", board[i][j]);}printf("\n");}
}	/*
函数:布置雷函数(随机布置10个)
参数:board:要布置雷的二维数组棋盘row:布置雷的棋盘宽度col:布置雷的棋盘高度
返回值:无
*/
void SetMine(char board[ROWS][COLS], int row, int col)
{int count = COUNT;		//定义雷的数量while (count)			//循环放雷{int x = rand() % row + 1;int y = rand() % col + 1;if (board[x][y] == '0')	//判断是否已经放过雷{board[x][y] = '1';count--;}}
}/*
函数:计算周围雷个数函数
参数:mine:要布置雷的二维数组棋盘x:玩家输入的横坐标y:玩家输入的纵坐标
返回值:无
*/
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{return (mine[x - 1][y] + mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] + mine[x + 1][y]+ mine[x + 1][y + 1] + mine[x][y + 1] + mine[x - 1][y + 1] - 8 * '0');
}/*
函数:排查雷函数
参数:mine:包含雷的信息的二维数组棋盘show:展示给玩家的二维数组棋盘row:排查雷的棋盘宽度col:排查雷的棋盘高度
返回值:无
*/
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{int x = 0;int y = 0;int win = 0;while (win < row * col - COUNT){printf("请输入要排查的坐标:");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;		//直接跳出while循环,结束游戏}else       //未排到雷,显示周围雷的个数{int count = GetMineCount(mine, x, y);show[x][y] = count + '0';	//显示周围的雷的个数DisplayBoard(show, ROW, COL);win++;	//排雷数加1}}else{printf("输入坐标有误,请重新输入\n");  //坐标非法,此时win还不满足跳出循环条件故,重新回到while循环中}}if (win == ROW * COL - COUNT)		//找到除了雷的所有位置,排雷成功{printf("恭喜你,排雷成功\n");DisplayBoard(mine, ROW, COL);}
}

main.c

#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>
#include "game.h"/*初始菜单*/
void menu()
{printf("************************\n");printf("******   1.play   ******\n");printf("******   0.exit   ******\n");printf("************************\n");
}/*游戏流程*/
void game()
{char mine[ROWS][COLS];		//存放雷的信息char show[ROWS][COLS];		//存放显示给玩家的信息/*初始化棋盘*/InitBoard(mine, ROWS, COLS, '0');InitBoard(show, ROWS, COLS, '*');/*显示用于展示给玩家的棋盘*/DisplayBoard(show, ROW, COL);/*随机布置雷*/SetMine(mine, ROW, COL);/*排雷*/FindMine(mine, show, 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;
}

我的Gitee链接如下:[扫雷游戏](扫雷游戏/扫雷游戏 · Tanecious./C语言练习代码 - 码云 - 开源中国)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/780.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Web开发中页面出现乱码的解决(Java Web学习笔记:需在编译时用 -encoding utf-8)

目录 1 引言2 乱码表现、原因分析及解决2.1 乱码表现2.2 原因分析2.3 解决 3 总结 1 引言 Web开发的页面出现了乱码&#xff0c;一直不愿写出来&#xff0c;因为网上的解决方案太多了。但本文的所说的页面乱码问题&#xff0c;则是与网上的大多数解决方案不一样&#xff0c;使…

HarmonyOS:@LocalBuilder装饰器: 维持组件父子关系

一、前言 当开发者使用Builder做引用数据传递时&#xff0c;会考虑组件的父子关系&#xff0c;使用了bind(this)之后&#xff0c;组件的父子关系和状态管理的父子关系并不一致。为了解决组件的父子关系和状态管理的父子关系保持一致的问题&#xff0c;引入LocalBuilder装饰器。…

初学stm32 --- DAC输出三角波和正弦波

输出三角波实验简要&#xff1a; 1&#xff0c;功能描述 通过DAC1通道1(PA4)输出三角波&#xff0c;然后通过DS100示波器查看波形 2&#xff0c;关闭通道1触发(即自动) TEN1位置0 3&#xff0c;关闭输出缓冲 BOFF1位置1 4&#xff0c;使用12位右对齐模式 将数字量写入DAC_…

【opencv】第7章 图像变换

7.1 基 于OpenCV 的 边 缘 检 测 本节中&#xff0c;我们将一起学习OpenCV 中边缘检测的各种算子和滤波器——Canny 算子、Sobel 算 子 、Laplacian 算子以及Scharr 滤波器。 7.1.1 边缘检测的一般步骤 在具体介绍之前&#xff0c;先来一起看看边缘检测的一般步骤。 1.【第…

【Python】Python与C的区别

文章目录 语句结束符代码块表示变量声明函数定义注释格式Python的标识符数据输入input()函数数据输出print()函数 语句结束符 C 语言 C 语言中每条语句必须以分号;结束。例如&#xff0c;int a 10;、printf("Hello, World!");。分号是语句的一部分&#xff0c;用于…

web-app uniapp监测屏幕大小的变化对数组一行展示数据作相应处理

web-app uniapp监测屏幕大小的变化对数组一行展示数据作相应处理 1.uni.getSystemInfoSync().screenWidth; 获取屏幕宽度 2.uni.onWindowResize&#xff08;&#xff09; 实时监测屏幕宽度变化 3.根据宽度的大小拿到每行要展示的数量itemsPerRow 4.为了确保样式能够根据 items…

Idea-离线安装SonarLint插件地址

地址&#xff1a; SonarQube for IDE - IntelliJ IDEs Plugin | Marketplace 选择Install Plugin from Disk..&#xff0c;选中下载好的插件&#xff0c;然后重启idea

数据结构与算法之二叉树: LeetCode 637. 二叉树的层平均值 (Ts版)

二叉树的层平均值 https://leetcode.cn/problems/average-of-levels-in-binary-tree/description/ 描述 给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值与实际答案相差 1 0 − 5 10^{-5} 10−5 以内的答案可以被接受 示例 1 输入&#xff1a;root…

MySQL表的增删改查(基础)-下篇

修改 真正在改硬盘了&#xff0c;这样的修改是“持久有效”。一定要确保&#xff0c;update的修改是改对了&#xff0c;改出问题来就麻烦。指定update的时候&#xff0c;如果当前不指定任何条件&#xff0c;就会针对所有的行都能生效&#xff01; (把整个表都给改了)。 案例 --…

项目开发实践——基于SpringBoot+Vue3实现的在线考试系统(五)

文章目录 一、学生管理模块功能实现1、添加学生功能实现1.1 页面设计1.2 前端功能实现1.3 后端功能实现1.4 效果展示2、学生管理功能实现2.1 页面设计2.2 前端功能实现2.3 后端功能实现2.3.1 后端查询接口实现2.3.2 后端编辑接口实现2.3.3 后端删除接口实现2.4 效果展示二、代码…

使用 WPF 和 C# 绘制图形

绘图困难 此示例展示了如何在 WPF 和 C# 中绘制图形。绘制图形总是很棘手&#xff0c;因为您通常需要在至少两个不同的坐标系中工作。首先&#xff0c;您要为图形使用世界坐标。例如&#xff0c;您可能希望 X 值的范围为 2000 年至 2020 年&#xff0c;Y 值的范围为 10,000 美元…

3D滤波器处理遥感tif图像

import cv2 import numpy as np from osgeo import gdal# 定义 Gabor 滤波器的参数 kSize 31 # 滤波器核的大小 g_sigma 3.0 # 高斯包络的标准差 g_theta np.pi / 4 # Gabor 函数的方向 g_lambda 10.0 # 正弦波的波长 g_gamma 0.5 # 空间纵横比 g_psi np.pi / 2 # …

JuiceFS 2024:开源与商业并进,迈向 AI 原生时代

即将过去的 2024 年&#xff0c;是 JuiceFS 开源版本推出的第 4 年&#xff0c;企业版的第 8 个年头。回顾过去这一年&#xff0c;JuiceFS 社区版依旧保持着快速成长的势头&#xff0c;GitHub 星标突破 11.1K&#xff0c;各项使用指标增长均超过 100%&#xff0c;其中文件系统总…

高可用虚拟IP-keepalived

个人觉得华为云这个文档十分详细&#xff1a;使用虚拟IP和Keepalived搭建高可用Web集群_弹性云服务器 ECS_华为云 应用场景&#xff1a;虚拟IP技术。虚拟IP&#xff0c;就是一个未分配给真实主机的IP&#xff0c;也就是说对外提供数据库服务器的主机除了有一个真实IP外还有一个…

支付宝租赁小程序提升租赁行业效率与用户体验

内容概要 在当今数字化的世界里&#xff0c;支付宝租赁小程序的出现构建了一种新的租赁模式&#xff0c;使得用户在使用过程中体验更加流畅。想象一下&#xff0c;你在寻找租赁服务时&#xff0c;不再需要繁琐的流程和冗长的等待&#xff0c;只需通过手机轻松点击几下&#xf…

python异常机制

异常是什么&#xff1f; 软件程序在运行过程中&#xff0c;非常可能遇到刚刚提到的这些问题&#xff0c;我们称之为异常&#xff0c;英文是Exception&#xff0c;意思是例外。遇到这些例外情况&#xff0c;或者交异常&#xff0c;我们怎么让写的程序做出合理的处理&#xff0c…

Git撤销指定commit并更新远端仓库

Git撤销指定commit并更新远端仓库 一、撤销指定commit 1.首先执行git log 命令&#xff0c;查看git历史提交以及commit信息&#xff1a; 由于需要脱敏&#xff0c;所以截图可能看得马赛克比较多&#xff0c;需要关注的就是上面的commit后跟的id&#xff0c;以及HEAD当前指定…

【opencv】第8章 图像轮廓与图像分割修复

8.1 查找并绘制轮廓 一个轮廓一般对应一系列的点&#xff0c;也就是图像中的一条曲线。其表示方法可能 根据不同的情况而有所不同。在OpenCV 中&#xff0c;可以用findContours()函数从二值图 像中查找轮廓 8.1.1 寻找轮廓&#xff1a; findContours() 函数 findContours) 函…

.NET Core NPOI 导出图片到Excel指定单元格并自适应宽度

NPOI&#xff1a;支持xlsx&#xff0c;.xls&#xff0c;版本>2.5.3 XLS&#xff1a;HSSFWorkbook&#xff0c;主要前缀HSS&#xff0c; XLSX&#xff1a;XSSFWorkbook&#xff0c;主要前缀XSS&#xff0c;using NPOI.XSSF.UserModel; 1、导出Excel添加图片效果&#xff0…

SQL分类与数据类型整理

SQL分类与数据类型整理 SQL分类数据类型数值型整数型小数型布尔型字符串型日期型二进制型其他类型&#xff08;空间数据类型&#xff09; 总结 SQL&#xff0c;全称为Structured Query Language&#xff08;结构化查询语言&#xff09;&#xff0c;是一种高度专业化的计算机语言…