C语言之扫雷游戏实现篇

news/2024/5/18 15:18:46 标签: c语言, 游戏程序

目录

主函数test.c

菜单函数

选择循环

扫雷游戏实现分析

整体思路 

问题1

问题2 

问题3

问题4 

游戏函数(函数调用) 

创建游戏盘数组mine

创建游戏盘数组show

初始化游戏盘数组InitBoard

展示游戏盘DisplayBoard

游戏盘置雷SetMine

游戏盘排雷FindMine

test.c总代码

头文件&函数声明game.h

头文件的包含

游戏符号声明

游戏函数声明

game.h总代码

游戏函数game.c

初始化游戏盘InitBoard

展示游戏盘DisplayBoard

优化1

优化2

游戏盘置雷SetMine

游戏盘排雷FindMine

雷炸死  

非雷计算

找完雷

总循环

game.c总代码


今天我们接着来讲扫雷游戏的实现。🙂🙂

主函数test.c

菜单函数

void menu()
{
	printf("*******************\n");
	printf("*******Play.1******\n");
	printf("*******Over.2******\n");
	printf("*******************\n");
}

选择循环

#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
	printf("*******************\n");
	printf("*******Play.1******\n");
	printf("*******Over.2******\n");
	printf("*******************\n");
}
void game()
{
	printf("开始扫雷游戏\n");
}
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		printf("欢迎来到扫雷游戏!\n");
        menu();
		printf("请输入您的选择:\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("游戏结束\n");
			break;
		default:
			printf("输入选择有误,请重新选择\n");
			break;
		}
	} while (input);
}

以上代码我们已经写过三遍了,相信大家都非常熟悉了,不在过多阐述。 

扫雷游戏实现分析

整体思路 

  • 首先游戏盘9✖9,游戏盘上布置了10个雷
  • 如果游戏盘的某处坐标不是雷,就计算这个位置的周围3✖3的8个坐标有几个雷且显示雷个数
  • 如果游戏盘的某处坐标是雷,就炸死了,显示游戏结束
  • 如果把游戏盘上所有非雷的位置全部找出来了,显示排雷成功,游戏结束。 
  • 两个完全贴合的字符数组游戏盘

问题1

我们用字符'0'表示非雷,'1'表示是雷。
但是格子里还要显示周围3✖3的各自雷的个数,数字1字符'1'会容易搞混,怎么办?

 所以我们需要两个游戏盘

  • mine游戏盘。游戏盘初始化为字符'0'和'1'随机循环的布置10个雷的位置。玩家在扫雷的时候计算雷的个数
  • show游戏盘。游戏盘的展示。游戏盘初始化为字符'*'。玩家扫雷的时候显示周围3✖3的8个坐标的雷的个数
  • 特别提醒:二者必须完全无缝贴合🆗🆗

问题2 

刚刚我们提到mine游戏盘是扫雷时计算某个坐标的周围3✖3的8个坐标的雷的个数,那如果时周围的坐标该怎么办,如果计算,已经数组越界了 ???

 

所以我们要拓展我们的游戏盘,我们创建一个11✖11的游戏盘,但我们只访问9✖9的游戏盘

特别提醒:为了我们的便捷的实现我们的扫雷游戏,我们的两个游戏盘必须无缝贴合,所以我们的show显示游戏盘也要拓展到11✖11。 

问题3

我们扫雷游戏的实现涉及初始化,游戏盘的展示等都需要用到循环 ,那循环条件条件的控制?

特别提醒:

特别需要注意循环条件,数组的下标是从0开始。

初始化数组就是0~10

访问数组就是1~9 

问题4 

当玩家输入坐标,没有输入雷被炸死,这时我们需要显示雷的坐标,那怎样去计算雷的个数?

 游戏盘数组mineshow都初始化为字符。现在我们要将字符转化为数字。

 根据字符和数字的ASCII码值。我们知道'0'数值为48,'1'数值为49。

 所以我们知道 '1'-'0'=1   '0'-'0'=0

 所以我们可以将(x,y)周围8个字符坐标分别减去'0'可以得到数字再全加到一起得总数字

 或者我们也可以先将(x,y)周围8个坐标字符坐标 加到一起再一起减去8*'0'得到总数字

游戏函数(函数调用) 

创建游戏盘数组mine

char mine[ROWS][COLS]={0};

创建游戏盘数组show

char show[ROWS][COLS]={0};

初始化游戏盘数组InitBoard

  • 创建一个InitBoard函数,去分别初始化两个数组mine和show
  •  初始化内容不一样,所以把初始化内容当作参数分别传给函数InitBoard
  • 初始化时传参时11✖11,为了后面计算游戏盘某坐标 周围8个坐标 里雷的个数 
InitBoard(mine, ROWS, COLS, '0');//初始化是11✖11
InitBoard(show, ROWS, COLS, '*');

展示游戏盘DisplayBoard

  • 展示游戏盘,只需要访问9✖9的游戏盘。 
DisplayBoard(mine, ROW, COL);//多余的//访问是9✖9
DisplayBoard(mine, ROW, COL);

游戏盘置雷SetMine

SetMine(mine, ROW, COL);
DisplayBoard(mine, ROW, COL);//多余的

游戏盘排雷FindMine

FindMine(mine, show, ROW, COL);
//传mine数组过去计算雷
//传show数组展示计算雷的结果

test.c总代码

//扫雷游戏的实现
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
	printf("*******************\n");
	printf("*******Play.1******\n");
	printf("*******Over.2******\n");
	printf("*******************\n");
}
void game()
{
	printf("开始扫雷游戏\n");
	char mine[ROWS][COLS] = { 0 };
	char show[ROWS][COLS] = { 0 };
	InitBoard(mine, ROWS, COLS, '0');//
	InitBoard(show, ROWS, COLS, '*');
	//DisplayBoard(mine, ROW, COL);//多余的//访问是9✖9
	DisplayBoard(show, ROW, COL);
	//布置雷
	SetMine(mine, ROW, COL);
	//DisplayBoard(mine, ROW, COL);//多余的
	//排除雷——扫雷
	FindMine(mine, show, ROW, COL);
	//传mine数组过去计算雷
	//传show数组展示计算雷的结果

}
int main()
{
	int input = 0;
	srand((unsigned int)time(NULL));
	do
	{
		printf("欢迎来到扫雷游戏!\n");
		menu();
		printf("请输入您的选择:\n");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("游戏结束\n");
			break;
		default:
			printf("输入选择有误,请重新选择\n");
			break;
		}
	} while (input);
}

头文件&函数声明game.h

头文件的包含

在我们写代码的过程中,会调用库函数,需要包含头文件,和声明函数。

所以我们将所有函数声明和头文件放到我们.h 文件中。

当然,在其他.c文件需要使用时,我们只需要包含 我们创造的 头文件"game.h" 即可。 

//#include"game.h"
#include<stdio.h>
#include<time.h>
#include<stdlib.h>

游戏符号声明

#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 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[ROWS][COLS], char show[ROWS][COLS], int row,int col);
//不能board,重复参数名

game.h总代码

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 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[ROWS][COLS], char show[ROWS][COLS], int row,int col);
//不能board,重复参数名

游戏函数game.c

初始化游戏盘InitBoard

  • 数组下标是从0开始的,所以初始化i是0~10 
#include"game.h"
#define _CRT_SECURE_NO_WARNINGS 1
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)//i=0~10
	{
		for (j = 0; j < cols; j++)//j=0~10
		{
			board[i][j] = set;
		}
	}
}

展示游戏盘DisplayBoard

  •  数组下标是从0开始,所以访问是0~9
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	for (i = 1; i <= row; i++)
	{
		for (j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
}

当然我们的mine函数是不会展示的。当玩家输入坐标时还要去数,所以以上代码还能不能优化? 

优化1

  • 玩家输入坐标时,还是几行几列去寻找,所以我们选择直接把号码打印出来。
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	//打印列号
	for (i = 0; i <= col; i++)//i从0开始,因为行占用了一格
	{
		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");
	}
}

优化2

  • 上下文的文字显得眼花缭乱,所以我们加上分割线就不会缭乱了。 
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	int i = 0;
	int j = 0;
	//打印列号
	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");
	}
	printf("--------------扫雷--------------\n");
}

优化之后 

游戏盘置雷SetMine

  • 关于随机数rand,先调用srand
  • 随机数rand()%row的范围0~8
  • 随机数rand()%row+1的范围1~9
  • 关于布置雷需要在mine函数里面去实现
  • while循环的次数肯定不止EASY_COUNT
void SetMine(char board[ROWS][COLS], int row, int col)
{
	//布置雷——循环随机数直到布置完10个雷停止
	int count = EASY_COUNT;
	while (count)//直到10个雷布置完毕退出循环
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		//产生的坐标就是(0,0)~(9,9)
		if (board[x][y] == '0')
			//条件设置,不能重复计算已经设置过的地方即为1的地方
		{
			board[x][y] = '1';
			count--;
		}
	}
}

 

游戏盘排雷FindMine

雷炸死  

  •  坐标为雷就炸死,游戏结束
printf("请输入要查找的雷\n");
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//玩家输入非法坐标,重新输入
{
	printf("坐标非法,请重新输入\n");
}

非雷计算

  •  坐标不为雷,mine计算雷,show展示雷
  •  计算l雷个数的函数GetMineCount
//统计雷的个数
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
	return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1]
		+ mine[x][y - 1] +  mine[x][y + 1]
		+ mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1]
		-8*'0';
}


int win=0;
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
   if
   {

   }
   else//没有被炸死,显示雷的个数
   {
	 //不是雷,就统计x,y坐标周围有几个雷
	 int c = GetMineCount(mine, x, y);
	 show[x][y] = c + '0';//数字+'0'=字符数字放置到字符数组里去
	 DisplayBoard(show, ROW, COL);//展示字符数字——雷的个数,每排查一次都要显示雷的个数
	 win++;//每排查一次雷,雷的个数减少一次,距离循环结束++一次
   }
}
else//玩家输入非法坐标,重新输入
{
	printf("坐标非法,请重新输入\n");
}

找完雷

  •  坐标找完雷,游戏结束
//炸死和排排完雷都跳出循环
if (win == row * col - EASY_COUNT)//设置条件只有排完雷才通关
{
	printf("恭喜你排雷成功,游戏通关\n");
    DisplayBoard(mine, ROW, COL);
}

总循环

int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
	return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1]
		+ mine[x][y - 1] +  mine[x][y + 1]
		+ mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1]
		-8*'0';
}

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-EASY_COUNT)
	{
		printf("请输入要查找的雷\n");
		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//没有被炸死,显示雷的个数
			{
				//不是雷,就统计x,y坐标周围有几个雷
				int c = GetMineCount(mine, x, y);
				show[x][y] = c + '0';//数字+'0'=字符数字放置到字符数组里去
				DisplayBoard(show, ROW, COL);//展示字符数字——雷的个数
				//每排查一次都要显示雷的个数
				win++;//每排查一次雷,雷的个数减少一次,距离循环结束++一次
			}
		}
		else//玩家输入非法坐标,重新输入
		{
			printf("坐标非法,请重新输入\n");
		}
	}
	//炸死和排排完雷都跳出循环
	if (win == row * col - EASY_COUNT)//设置条件只有排完雷才通关
	{
		printf("恭喜你排雷成功,游戏通关\n");
		DisplayBoard(mine, ROW, COL);
	}
}

game.c总代码

#include"game.h"
#define _CRT_SECURE_NO_WARNINGS 1
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < rows; i++)//i=0~10
	{
		for (j = 0; j < cols; j++)//j=0~10
		{
			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 <= 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");
	}
	printf("--------------扫雷--------------\n");
}

void SetMine(char board[ROWS][COLS], int row, int col)
{
	//布置雷——循环随机数直到布置完10个雷停止
	int count = EASY_COUNT;
	while (count)//直到10个雷布置完毕退出循环
	{
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		//产生的坐标就是(0,0)~(9,9)
		if (board[x][y] == '0')
			//条件设置,不能重复计算已经设置过的地方即为1的地方
		{
			board[x][y] = '1';
			count--;
		}
	}
}
int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
	return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1]
		+ mine[x][y - 1] +  mine[x][y + 1]
		+ mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1]
		-8*'0';
}

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-EASY_COUNT)
	{
		printf("请输入要查找的雷\n");
		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//没有被炸死,显示雷的个数
			{
				//不是雷,就统计x,y坐标周围有几个雷
				int c = GetMineCount(mine, x, y);
				show[x][y] = c + '0';//数字+'0'=字符数字放置到字符数组里去
				DisplayBoard(show, ROW, COL);//展示字符数字——雷的个数
				//每排查一次都要显示雷的个数
				win++;//每排查一次雷,雷的个数减少一次,距离循环结束++一次
			}
		}
		else//玩家输入非法坐标,重新输入
		{
			printf("坐标非法,请重新输入\n");
		}
	}
	//炸死和排排完雷都跳出循环
	if (win == row * col - EASY_COUNT)//设置条件只有排完雷才通关
	{
		printf("恭喜你排雷成功,游戏通关\n");
		DisplayBoard(mine, ROW, COL);
	}
}

✔✔✔✔✔最后,感谢大家的阅读,后续可能会函数递归优化,若有错误和不足,欢迎指正!

迎来新的学期,希望大家继续坚持在每天敲代码的路上。🙂🙂🙂学习的小伙伴

代码---------→【gitee:https://gitee.com/TSQXG】

联系---------→【邮箱:2784139418@qq.com】


http://www.niftyadmin.cn/n/4964656.html

相关文章

【C语言】动态内存管理(malloc,free,calloc,realloc)-- 详解

一、动态内存分配 定义&#xff1a;动态内存分配 (Dynamic Memory Allocation) 就是指在程序执行的过程中&#xff0c;动态地分配或者回收存储空间的分配内存的方法。动态内存分配不像数组等静态内存分配方法那样&#xff0c;需要预先分配存储空间&#xff0c;而是由系统根据程…

Java如何调用接口API并返回数据(两种方法)

Java如何调用接口API并返回数据&#xff08;两种方法&#xff09; java处理请求接口后返回的json数据-直接处理json字符串 处理思路&#xff1a; 将返回的数据接收到一个String对象中&#xff08;有时候需要自己选择性的取舍接收&#xff09; 再将string转换为JSONObject对象 …

用keil的时候没有安装对应MCU的库,以STM32F103F为例

安装keil的时候没有用到这个芯片就没有安装对应的库。重新安装之后遇到的几个坑&#xff1a; 打开keil显示没有这个型号&#xff0c;解决方法是安装对应的库。STM32F103F要安装Keil.STM32F1xx_DFP.2.4.1.pack。 安装完库之后&#xff0c;点击Option for target&#xff0c;查看…

MPLS——多协议标签交换

多协议&#xff1a;可以基于多种不同的2层协议和3层协议来生成2.5层的标签信息&#xff1b; 标签交换&#xff1a; 数据包在进入到的MPLS的域内后&#xff0c;将在第2层和3层中间压入标签号&#xff1b;使得域内的路由器在转发该数据包时&#xff0c;基于2.5层的标签号仅需要查…

【Prometheus】概述及部署

目录 Prometheus 概述 Prometheus 的生态组件 Prometheus 的工作模式 Prometheus 的工作流程 Prometheus 的局限性 部署 Prometheus Prometheust Server 端安装和相关配置 部署 Exporters 监控 MySQL 配置示例 监控 Nginx 配置示例 部署 Grafana 进行展示 部署 Pro…

Jacoco XML 解析

1 XML解析器对比 1. DOM解析器&#xff1a; ○ 优点&#xff1a;易于使用&#xff0c;提供完整的文档树&#xff0c;可以方便地修改和遍历XML文档。 ○ 缺点&#xff1a;对大型文档消耗内存较多&#xff0c;加载整个文档可能会变慢。 ○ 适用场景&#xff1a;适合小型XML文档…

【从零学习python 】68. Python正则表达式中的贪婪和非贪婪模式

文章目录 贪婪和非贪婪模式进阶案例 贪婪和非贪婪模式 Python里数量词默认是贪婪的&#xff08;在少数语言里也可能是默认非贪婪&#xff09;&#xff0c;总是尝试匹配尽可能多的字符&#xff1b; 非贪婪则相反&#xff0c;总是尝试匹配尽可能少的字符。 在*、?、、{m,n}后面…

前端面试:【前端安全】HTTPS、CORS、Content Security Policy

嗨&#xff0c;亲爱的前端开发者&#xff01;前端安全是构建现代Web应用程序时不可或缺的一部分。本文将深入探讨三个关键的前端安全概念&#xff1a;HTTPS、CORS&#xff08;跨源资源共享&#xff09;和内容安全策略&#xff08;CSP&#xff09;&#xff0c;以帮助你确保你的应…