*扫雷游戏*

一.前言

        呕吼,第二篇博客。过了大概半个多月的时间,我的c语言的学习之路也走过了一小段。本文带来扫雷游戏的实现过程,也作为这段时间所学知识的一个小总结。扫雷游戏大家应该都不陌生,谁敢说小时候一次没被炸死过呢ಡωಡ ಡωಡ。

        言归正传,首先我们看下扫雷游戏的分析和设计。

二.扫雷游戏分析和设计

        1.界面设计

        上图即为我们所熟悉的扫雷游戏界面,由于具体规则大家应该都比较熟悉,这里不具体细说。本文暂不考虑图形界面的实现(主要是还不会),仅考虑通过控制台实现扫雷游戏,如下图所示:

        2.数据结构分析

        扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些 信息。 因为我们需要在9*9的棋盘上布置雷的信息和排查雷,我们⾸先想到的就是创建⼀个9*9的数组来存放信息,如下图所示:

012345678
0
1
2
3
4
5
6
7
8

        如果这个位置有雷我们就存1, 如果没有雷就存0,如下图所示:

               

012345678
0000000000
1000000000
2001000000
3000000000
4000000001
5000000000
6000000000
7000100000
8000000000

        我们如果要排查(2,4)这个坐标时,访问周围一圈8个位置,统计周围雷的个数为0。但如果我们要排查(5,8)这个坐标时,右边的三个坐标就会发生越界。为了避免这种情况,我们不妨在设计的时候,将数组扩大一圈,雷还是布置在中间9×9的坐标上,而最外面一圈不去布置。所以我们将数组设计为11×11的数组较为合理。如下图所示:

012345678910
0
1000000000
2000000000
3001000000
4000000000
5000000001
6000000000
7000000000
8000100000
9000000000
10

        棋盘问题解决了,我们接下来考虑如何存储一个坐标周围雷的信息,我们都知道扫雷游戏里点击一个不是雷的位置,他会显示周围雷的数量,我们如果将雷的个数信息也存放在布置雷的数组里,雷的信息和周围雷的个数信息可能会相互干扰产生打印上的困难。我们这里考虑用两个数组分开存储,即设置mine数组存放雷的信息,将排查的信息放在show数组。代码如下:

#define ROW 9                 //这里为了方便以后更改棋盘难度 用define定义                
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2

char mine[ROWS][COLS] = {0};    //存放布置雷的信息
char show[ROWS][COLS] = {0};    //存放排出雷的个数信息
        3.文件结构设计

        我们设计三个文件:

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

三.扫雷游戏代码实现

        首先我们为游戏设置一个开始提示,我们用一个函数实现。

       

void menu() {
	printf("---------------------------\n");
	printf("------1.play 开始游戏-------\n");
	printf("------0.exit 结束游戏-------\n");
	printf("---------------------------\n");
}

        当输入1时,我们正式开始游戏,当输入0的时候结束游戏,当输入其他数值时应提示输入错误,请重新输入。

        

void test() {
	int input = 0;
	do {
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input) {
		case 1:
			printf("运行游戏\n");    //为了测试目前代码是否正确,暂时用printf语句替代运行函数
			break;
		case 0:
			printf("游戏结束\n");
			break;
		default:
			printf("输入错误,请重新输入:");
			break;
		}
	} while (input);
}

        代码测试结果如下

        目前我们代码运行逻辑没有问题,开始着手游戏的实现。首先我们先对定义的两个数组进行初始化,我们将mine数组初始化为‘0’,show数组初始化为‘*’。用嵌套for循环来实现对数组的初始化。

//初始化棋盘
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');


void InitBoard(char board[ROWS][COLS], int rows, int cols, char ret) {
	int i = 0;
	for (i = 0; i < rows; i++) {
		int j = 0;
		for (j = 0; j < cols; j++) {
			board[i][j] = ret;
		}
	}
}

        我们再来看棋盘的打印,注意我们只是为了防止数组越界将数组设置为11×11,但其周围一圈并不存放任何信息,我们最后要呈现的依旧是9×9的棋盘。

//打印棋盘
DisplayBoard(show, ROW, COL);
DisplayBoard(mine, 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");
	}
}

        现在棋盘的初始化和打印已经完成,我们开始着手雷的布置,用‘1’代表雷。并且在扫雷游戏中雷每次的出现位置应该是随机的,我们这里用rand()和srand()函数来实现随机。rand()函数能生成0~32767之间的随机数,而我们所要的应该是生成1~9之间的随机数,我们用rand () % ROW + 1即可实现1~9之间的随机数。srand()旨在每次重新开始游戏的时候变换rand()函数随机数种子。代码如下:

#define EASY_COUNT 10

void SetMine(char mine[ROWS][COLS], int row, int col) {
	int count = EASY_COUNT;        //EASY_COUNT是雷的个数,为了便于修改,仍用define进行定义
	int x = 0;
	int y = 0;
	while (count) {
		x = rand() % row + 1;
		y = rand() % col + 1;
		if (mine[x][y] != '1') {
			mine[x][y] = '1';
			count--;
		}
	}
}


void test() {
	srand((unsigned int)time(NULL));
	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);
}

        雷的布置已经完成,接下里我们考虑如何进行排雷。如我们前面所讲,当一个位置不是雷的时候,统计其周围一圈雷的个数,并将其存放到show数组中。分两部来看,首先我们来看如何得到该位置周围雷的个数。假设此时排查位置坐标为(x,y),那么其周围8个位置的坐标范围即为x - 1 ~ x + 1,y - 1 ~ y + 1,由于此时(x,y)不是雷,其数值为'0',故不必单独考虑。值得注意的是,数组中存储的元素数据类型是char类型,想要得到int类型数据需减去‘0’计算差值。

int GetMineCount(char mine[ROWS][COLS], int x, int y) {
	int i = 0;
	int count = 0;
	for (i = -1; i <= 1; i++) {
		int j = 0;
		for (j = -1; j <= 1; j++) {
			count += (mine[x + i][y + j] - '0');
		}
	}
	return count;
}

        我们再来看如何具体进行排雷,首先输入要排查的坐标,先判断坐标是否合法,合法的话判断该位置是否是雷。如果是雷,game over,最后打印布雷数组;如果不是雷,将周围雷的数量赋给该坐标。设置win变量作为循环结束的标志,如果不是雷,win++直至将所有不是雷的坐标都排查过循环结束,排雷成功。

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) {
	int x = 0;
	int y = 0;
	char input = '0';
	int win = 0;
	while (win < row * col - EASY_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;
			}
			//不是雷
			else {
				int count = GetMineCount(mine, x, y);
				show[x][y] = count + '0';
				DisplayBoard(show, ROW, COL);
				win++;
			}
		}
		else
			printf("您输入的坐标有误x(1~9) y(1~9),请重新输入:>");
	}
	if (win == row * col - EASY_COUNT) {
		printf("恭喜你,排雷成功\n");
		DisplayBoard(mine, ROW, COL);
	}
}

       

        我们此时将雷的数量设置为80,看一下成功的情况。

 

        这样一次一次的排在棋盘尺寸比较大,雷的数量比较少的时候可能比较费时间,我们可以减小难度,当排查坐标周围一圈都没有雷的时候,从自身向周围不断平铺直至出现周围有雷的坐标听停止,即递归实现。实现如下:

void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) {
	if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
		int count = GetMineCount(mine, x, y);
		if (count == 0) {
			show[x][y] = ' ';
			int i = 0;
			for (i = x - 1; i <= x + 1; i++) {
				int j = 0;
				for (j = y - 1; j <= y + 1; j++) {
					if (show[i][j] == '*') {
						Unfold(mine, show, i, j);
					}
				}
			}
		}
		else
			show[x][y] = count + '0';
	}
}


//此时要对排雷函数也进行一些改动,不再以排查所以不是雷的地方作为结束条件,而是进行标雷,当所有雷的位置都被标出排雷成功
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) {
	int x = 0;
	int y = 0;
	char input = '0';
	int win = 0;
	while (win < EASY_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;
			}
			//不是雷
			else {
				Unfold(mine, show, x, y);
				DisplayBoard(show, ROW, COL);
				//标雷
				getchar();    //此处getchar用来吸收上面输入的回车
				printf("是否标雷,请输入Y or N:>");
				scanf("%c", &input);
				while (input == 'Y') {
					printf("请输入标雷的坐标:>");
					scanf("%d %d", &x, &y);
					show[x][y] = '#';    //如果判断是雷,标记‘#’
					if (mine[x][y] == '1')    //如果标雷位置与雷的位置一致,win++
						win++;
					break;
				}
				DisplayBoard(show, ROW, COL);
			}
		}
		else
			printf("您输入的坐标有误x(1~9) y(1~9),请重新输入:>");
	}
	if (win == EASY_COUNT) {
		printf("恭喜你,排雷成功\n");
		DisplayBoard(mine, ROW, COL);
	}
}

 

        我们此时将雷的数量设置为1, 来检验一下成功的情况。

        至此,排雷游戏结束,各位如果想变换难度可以将ROW、COL、EASY_COUNT的值自行改动。完结撒花ಡωಡಡωಡಡωಡಡωಡ ,全部代码如下:

        game.h 

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<string.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 ret);

//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col);

//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col);

//排雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

        game.c 

#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"

//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char ret) {
	int i = 0;
	for (i = 0; i < rows; i++) {
		int j = 0;
		for (j = 0; j < cols; j++) {
			board[i][j] = ret;
		}
	}
}


//打印棋盘
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");
	}
}



//布置雷
void SetMine(char mine[ROWS][COLS], int row, int col) {
	int count = EASY_COUNT;
	int x = 0;
	int y = 0;
	while (count) {
		x = rand() % row + 1;
		y = rand() % col + 1;
		if (mine[x][y] != '1') {
			mine[x][y] = '1';
			count--;
		}
	}
}



//得到周围雷的个数
int GetMineCount(char mine[ROWS][COLS], int x, int y) {
	int i = 0;
	int count = 0;
	for (i = -1; i <= 1; i++) {
		int j = 0;
		for (j = -1; j <= 1; j++) {
			count += (mine[x + i][y + j] - '0');
		}
	}
	return count;
}



//实现平铺
void Unfold(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) {
	if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
		int count = GetMineCount(mine, x, y);
		if (count == 0) {
			show[x][y] = ' ';
			int i = 0;
			for (i = x - 1; i <= x + 1; i++) {
				int j = 0;
				for (j = y - 1; j <= y + 1; j++) {
					if (show[i][j] == '*') {
						Unfold(mine, show, i, j);
					}
				}
			}
		}
		else
			show[x][y] = count + '0';
	}
}



//排雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) {
	int x = 0;
	int y = 0;
	char input = '0';
	int win = 0;
	while (win < row * col - EASY_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;
			}
			//不是雷
			else {
				int count = GetMineCount(mine, x, y);
				show[x][y] = count + '0';
				DisplayBoard(show, ROW, COL);
				win++;
			}
		}
		else
			printf("您输入的坐标有误x(1~9) y(1~9),请重新输入:>");
	}
	if (win == row * col - EASY_COUNT) {
		printf("恭喜你,排雷成功\n");
		DisplayBoard(mine, ROW, COL);
	}
}

        test.c 

#include"game.h"
void menu() {
	printf("---------------------------\n");
	printf("------1.play 开始游戏-------\n");
	printf("------0.exit 结束游戏-------\n");
	printf("---------------------------\n");
}


void game() {
	char mine[ROWS][COLS] = { 0 };	//存放雷的信息
	char show[ROWS][COLS] = { 0 };	//存放排出雷的信息
	//初始化棋盘
	InitBoard(mine, ROWS, COLS, '0');
	InitBoard(show, ROWS, COLS, '*');

	//打印棋盘
	DisplayBoard(show, ROW, COL);

	//布置雷
	SetMine(mine, ROW, COL);
	DisplayBoard(mine, ROW, COL);

	//排雷
	FindMine(mine, show, ROW, COL);
}


void test() {
	srand((unsigned int)time(NULL));
	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);
}

int main() {
	test();
	return 0;
}

 

 

四.结语

        这是本人的第二篇博客,距离上篇时间跨度已有大半个月,其实中间应该有一篇循环语句的学习(假期去苏州玩了一圈,小偷懒),希望以后能继续坚持写下去。如果本文对各位有所帮助,我将倍感荣幸。

        吾尽吾心,终亦不悔。天道酬勤,何事难为!

  • 15
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值