扫雷(控制台版本)

目录

一、思路分析:

二、准备工作:

1、定义结构体

2、初始化结构体

3、显示菜单

4、布置地雷

5、获取每个方格周围雷的数目

6、打印棋盘

7、模式设置

9、扫雷

10、参数设置

三、流程控制

四、整体代码


一、思路分析:

每个格子有三种状态: 未打开, 标记, 未知,已打开

每个格子可能存放雷或者周围雷信息

考虑创建一个棋盘结构体,进行存储,当格子的状态为已打开时,其状态不可修改,否则可以修改

初始化棋盘时可以将每个方格中的信息初始化为空格,表示其中没有任何东西

布置雷的时候,利用随机数进行随机摆放

每个格子周围雷的信息可以通过一个循环对以其为中心的周围8个格子进行遍历求得

在打印时,若其为已打开,在显示其内存储的信息,若为未打开则以空格代替,若为标记则用 M 表示, 若为未知则用 ? 表示, 其中 空格 和 ?可以直接打开, M 则不可以直接打开,但可以通过标记将其变为 ?

考虑到用户可以自己定义雷的数目,棋盘的规格,可以利用动态二维数组进行操作,使用参数传递,进行棋盘规模的控制

二、准备工作:

1、定义结构体

typedef struct Board
{
    char sign;    //存储信息
    int state;    //存储当前状态,0 为不显示, 1 为已显示, 2 为标记, 3为未知
}Board;

2、初始化结构体

Board** initBoard(Board** board, size_t row, size_t col)
{
	//实际中为了简化问题,开辟行,列 均 比其显示部分大2的数组
	size_t rows = row + 2;
	size_t cols = col + 2;
	board = (Board**)malloc(sizeof(Board*) * rows);
	//内存开辟失败
	if (!board)
	{
		return NULL;
	}
	for (int i = 0; i < rows; i++)
	{
		board[i] = (Board*)malloc(sizeof(Board) * cols);
		//此时若内存开辟失败,则需要将之前开辟的空间释放掉
		if (!board[i])
		{
			free(board);
			return NULL;
		}
		for (int j = 0; j < cols; j++)
		{
			board[i][j].sign = ' ';
			board[i][j].state = 0;
		}
	}
	return board;
}

3、显示菜单

void showMenu()
{
	printf("----欢迎使用扫雷游戏----\n\n");
	printf("------1、开始游戏------\n");
	printf("------2、设置参数------\n");
	printf("------0、退出游戏------\n");
}

4、布置地雷

void set_mine(Board** board, size_t row, size_t col, size_t mine_count)
{
	//循环放入地雷,因为mine_count是值传递,在此处修改不会影响原有的值,所以在此直接用其进行控制
	while (mine_count)
	{
		//保证每个坐标的值均在 1 ~ row + 1之间
		size_t x = rand() % row + 1;
		size_t y = rand() % col + 1;
		//如果可以放置地雷,将此处放为地雷,同时待布置地雷数目减一
		if (board[x][y].sign == ' ')
		{
			board[x][y].sign = '*';
			mine_count--;
		}
	}
}

5、获取每个方格周围雷的数目

对于每个方格其内存储的信息不同,考虑对单个方格进行查找即代码中的 get_num()函数模块

void get_around_mine(Board** board, size_t rows, size_t cols)
{
	//展示的部分为 1 ~ row + 1 即 1 ~ rows - 1, 列同理 
	for (int i = 1; i < rows - 1; i++)
	{
		for (int j = 1; j < cols - 1; j++)
		{
			if (board[i][j].sign == '*')	//如果当前格是地雷则跳过
			{
				continue;
			}
			board[i][j].sign = get_num(board, i, j);	//将当前个元素修改为周围地雷数量
		}
	}
}
//char类型可以隐士转化为int类型,其相加是其ASCII码相加,可以利用这一原因在c上进行操作
char get_num(Board** board, size_t x, size_t y)
{
	char c = '0';
	for (int i = x - 1; i < x + 2; i++)
	{
		for (int j = y - 1; j < y + 2; j++)
		{
			if (board[i][j].sign == '*')
			{
				c++;
			}
		}
	}
	return c;
}

6、打印棋盘

void display(Board** board, size_t row, size_t col, int mode)
{
	//打印表头,方便用户定位信息
	for (int j = 0; j < col + 1; j++)
	{
		printf("| %2d ", j);
	}
	printf("|\n");
	for (int j = 0; j < col + 1; j++)
	{
		printf("|----");
	}
	printf("|\n");
	for (int i = 1; i < row + 1; i++)
	{
		for (int j = 0; j < col + 2; j++)
		{
			//如果是j == 0 则说明此时应该为第几行,而非棋盘信息
			if (j == 0 && i != row + 1)
			{
				printf("| %2d ", i);
			}
			else {
				int mark = board[i][j].state;
				if (mark == 0)
				{
					printf("| %2c ", ' ');
				}
				else if (mark == 1)
				{
					printf("| %2c ", board[i][j].sign);
				}
				else if (mark == 2)
				{
					printf("| %2c ", 'M');
				}
				else
				{
					printf("| %2c ", '?');
				}
			}
		}
		printf("\n");
		for (int j = 1; j < col + 2; j++)
		{
			printf("|----");
		}
		printf("|\n");
	}
	//说明游戏已经结束
	if (mode == -1)
	{
		return;
	}
	//反映当前模式
	printf("当前模式: ");
	if (mode == 1)
	{
		printf("排雷模式\n");
	}
	else if (mode == 2)
	{
		printf("标记模式\n");
	}

}

7、模式设置

void set_mode(int* mode)
{
	printf("请选择你想设置的模式\n");
	printf("2、标记模式\n");
	printf("1、排雷模式\n");
	do
	{
		scanf("%d", mode);
		if (*mode == 1 || *mode == 2)
		{
			return;
		}
		printf("输入错误,请重新输入\n");
	} while (1);
}

8、标记

void mark_mine(Board** board, int* n, int* m, int row, int col)
{
	int x;
	int y;
	do
	{
		scanf("%d%d", &x, &y);
		//清空缓冲区
		while (getchar() != '\n');
		if (x < 1 || x > row || y < 1 || y > col)
		{
			printf("输入错误,请重新输入\n");
		}
		else
		{
			//实现其从 'M' -> '?'-> ' ' -> 'M' 的转换
			if (board[x][y].state == 2)
			{
				board[x][y].state = 3;
				(*n)--;
				(*m)++;
				break;
			}
			else if (board[x][y].state == 3)
			{
				board[x][y].state = 0;
				break;
			}
			else if (board[x][y].state == 0)
			{
				board[x][y].state = 2;
				(*n)++;
				(*m)--;
				break;
			}
			else
			{
				printf("输入的值无效\n");
			}
		}
	} while (1);
}

9、扫雷

因为一次可以打开多个格子,传入n,m,其中n是指剩余雷的个数,m指剩下未打开和未标记的格子数目,用于判断是否胜利

bool sweap_mine(Board** board, int* n, int* m, int row, int col)
{
	int x;
	int y;
	do
	{
		scanf("%d%d", &x, &y);
		while (getchar() != '\n');
		if (x < 1 || x > row || y < 1 || y > col)
		{
			printf("输入错误,请重新输入\n");
		}
		else
		{
			//如果当前格子不是显示状态或标记状态,则可以打开
			if (board[x][y].state != 1 && board[x][y].state != 2)
			{
				if (board[x][y].sign == '*')
				{
					board[x][y].state = 1;
					(*m)--;
					(*n)--;
					// 失败,游戏终止
					return false;
				}
				//每次方格可以炸开,使用该函数来使得一次打开多个格子
				sweap_function(board, x, y, n, m, row, col);
				break;
			}
			else
			{
				printf("输入值无效,请重新输入\n");
			}
		}
	} while (1);
	//未踩到雷,说明游戏继续
	return true;
}
void sweap_function(Board** board, int x, int y, int* n, int* m, int row, int col)
{
	//利用随机数,一次打开随机的几排
	int t = rand() % row;
	while (t < row)
	{
		int i = x;
		while (i <= row && board[i][y].sign != '*' && board[i][y].sign != ' ' && (board[i][y].state != 1 || board[i][y].state != 2))
		{
			(*m)--;
			board[i][y].state = 1;
			i++;
		}
		y++;
		t++;
	}
}

10、参数设置

void setParam(int* p_row, int* p_col, int* p_mine_count)
{
	printf("请输入行数\n");
	scanf("%d", p_row);
	printf("请输入列数\n");
	scanf("%d", p_col);
	printf("请输入地雷数\n");
	scanf("%d", p_mine_count);
	system("cls");
}

三、流程控制

此处可以封装一个game模块对游戏的每个流程进行控制

void game(Board** board, size_t row, size_t col, size_t mine_count)
{
	//实际行列数比显示部分大2
	size_t rows = row + 2;
	size_t cols = col + 2;
	//当前的模式,1为扫雷,2为标记
	int mode = 1;
	int n = 0;    //存储标记的雷的数目
	int m = row * col;	//未打开和未标记的格子数
	board = initBoard(board, rows, cols);
	bool game_continue = true;
	int select;    //用于与用户进行交互,修改模式
	//此时地图创建失败,无法游戏,直接提示用户并返回
	if (!board)
	{
		printf("地图创建失败\n");
		system("pause");
		system("cls");
		return;
	}
	set_mine(board, row, col, mine_count);	//设置地雷
	get_around_mine(board, rows, cols);	//获取周围地雷的数目
	do
	{
		display(board, row, col, mode);
		printf("是否修改模式或回到主菜单\n");
		printf("1、修改模式\n2、继续游戏\n3、回到主菜单\n");
		scanf("%d", &select);
		if (select == 1)
		{
			system("cls");
			set_mode(&mode);
			system("cls");
			display(board, row, col, mode);
		}
		else if (select == 3)
		{
			break;
		}
		printf("请输入坐标\n");
		if (mode == 1)
		{
			game_continue = sweap_mine(board, &n, &m, row, col);
		}
		else if (mode == 2)
		{
			mark_mine(board, &n, &m, row, col);
		}
		if (game_continue == false)
		{
			break;
		}
	} while (!(n == mine_count && m == 0));
	if (game_continue == false)
	{
		printf("失败\n");
		display(board, row, col, -1);
	}
	else if(n == mine_count && m == 0)
	{
		printf("恭喜获胜\n");
		display(board, row, col, -1);
	}
	//释放内存
	free(board);
	system("pause");
	system("cls");
}

主函数

int main()
{
	srand((unsigned int)time(NULL));
	int row = 10;	//行数
	int col = 10;	//列数
	int mine_count = 10;	//地雷数目
	Board** board = NULL;	//棋盘对象
	int select;	//接收用户的选择
	do
	{
		showMenu();	//展示菜单
		scanf("%d", &select);
		while (getchar() != '\n');
		switch (select)
		{
		case 1:
			game(board, row, col, mine_count);	//游戏进程
			break;
		case 2:
			setParam(&row, &col, &mine_count);	//设置参数
			break;
		case 0:
			printf("感谢您的使用\n");
			break;
		}
	} while (select);
	return 0;
}

四、整体代码

game.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
typedef struct Board
{
    char sign;    //存储信息
    int state;    //存储当前状态,0 为不显示, 1 为已显示, 2 为标记, 3为未知
}Board;
void showMenu();
Board** initBoard(Board** board, size_t row, size_t col);
void game(Board** board, size_t row, size_t col, size_t mine_count);
void setParam(int* p_row, int* p_col, int* p_mine_count);
void sweap_function(Board** board, int x, int y, int* n, int* m, int row, int col);
bool sweap_mine(Board** board, int* n, int* m, int row, int col);
void mark_mine(Board** board, int* n, int* m, int row, int col);
void set_mode(int* mode);
void display(Board** board, size_t row, size_t col, int mode);
char get_num(Board** board, size_t x, size_t y);
void get_around_mine(Board** board, size_t rows, size_t cols);
void set_mine(Board** board, size_t row, size_t col, size_t mine_count);

game.c

#include "game.h"

void game(Board** board, size_t row, size_t col, size_t mine_count)
{
	//实际行列数比显示部分大2
	size_t rows = row + 2;
	size_t cols = col + 2;
	//当前的模式,1为扫雷,2为标记
	int mode = 1;
	int n = 0;    //存储标记的雷的数目
	int m = row * col;	//未打开和未标记的格子数
	board = initBoard(board, rows, cols);
	bool game_continue = true;
	int select;    //用于与用户进行交互,修改模式
	//此时地图创建失败,无法游戏,直接提示用户并返回
	if (!board)
	{
		printf("地图创建失败\n");
		system("pause");
		system("cls");
		return;
	}
	set_mine(board, row, col, mine_count);	//设置地雷
	get_around_mine(board, rows, cols);	//获取周围地雷的数目
	do
	{
		display(board, row, col, mode);
		printf("是否修改模式或回到主菜单\n");
		printf("1、修改模式\n2、继续游戏\n3、回到主菜单\n");
		scanf("%d", &select);
		if (select == 1)
		{
			system("cls");
			set_mode(&mode);
			system("cls");
			display(board, row, col, mode);
		}
		else if (select == 3)
		{
			break;
		}
		printf("请输入坐标\n");
		if (mode == 1)
		{
			game_continue = sweap_mine(board, &n, &m, row, col);
		}
		else if (mode == 2)
		{
			mark_mine(board, &n, &m, row, col);
		}
		if (game_continue == false)
		{
			break;
		}
	} while (!(n == mine_count && m == 0));
	if (game_continue == false)
	{
		printf("失败\n");
		display(board, row, col, -1);
	}
	else if(n == mine_count && m == 0)
	{
		printf("恭喜获胜\n");
		display(board, row, col, -1);
	}
	//释放内存
	free(board);
	system("pause");
	system("cls");
}
void setParam(int* p_row, int* p_col, int* p_mine_count)
{
	printf("请输入行数\n");
	scanf("%d", p_row);
	printf("请输入列数\n");
	scanf("%d", p_col);
	printf("请输入地雷数\n");
	scanf("%d", p_mine_count);
	system("cls");
}
void sweap_function(Board** board, int x, int y, int* n, int* m, int row, int col)
{
	//利用随机数,一次打开随机的几排
	int t = rand() % row;
	while (t < row)
	{
		int i = x;
		while (i <= row && board[i][y].sign != '*' && board[i][y].sign != ' ' && (board[i][y].state != 1 || board[i][y].state != 2))
		{
			(*m)--;
			board[i][y].state = 1;
			i++;
		}
		y++;
		t++;
	}
}
bool sweap_mine(Board** board, int* n, int* m, int row, int col)
{
	int x;
	int y;
	do
	{
		scanf("%d%d", &x, &y);
		while (getchar() != '\n');
		if (x < 1 || x > row || y < 1 || y > col)
		{
			printf("输入错误,请重新输入\n");
		}
		else
		{
			//如果当前格子不是显示状态或标记状态,则可以打开
			if (board[x][y].state != 1 && board[x][y].state != 2)
			{
				if (board[x][y].sign == '*')
				{
					board[x][y].state = 1;
					(*m)--;
					(*n)--;
					// 失败,游戏终止
					return false;
				}
				//每次方格可以炸开,使用该函数来使得一次打开多个格子
				sweap_function(board, x, y, n, m, row, col);
				break;
			}
			else
			{
				printf("输入值无效,请重新输入\n");
			}
		}
	} while (1);
	//未踩到雷,说明游戏继续
	return true;
}
void mark_mine(Board** board, int* n, int* m, int row, int col)
{
	int x;
	int y;
	do
	{
		scanf("%d%d", &x, &y);
		//清空缓冲区
		while (getchar() != '\n');
		if (x < 1 || x > row || y < 1 || y > col)
		{
			printf("输入错误,请重新输入\n");
		}
		else
		{
			//实现其从 'M' -> '?'-> ' ' -> 'M' 的转换
			if (board[x][y].state == 2)
			{
				board[x][y].state = 3;
				(*n)--;
				(*m)++;
				break;
			}
			else if (board[x][y].state == 3)
			{
				board[x][y].state = 0;
				break;
			}
			else if (board[x][y].state == 0)
			{
				board[x][y].state = 2;
				(*n)++;
				(*m)--;
				break;
			}
			else
			{
				printf("输入的值无效\n");
			}
		}
	} while (1);
}
void set_mode(int* mode)
{
	printf("请选择你想设置的模式\n");
	printf("2、标记模式\n");
	printf("1、排雷模式\n");
	do
	{
		scanf("%d", mode);
		if (*mode == 1 || *mode == 2)
		{
			return;
		}
		printf("输入错误,请重新输入\n");
	} while (1);
}
void display(Board** board, size_t row, size_t col, int mode)
{
	//打印表头,方便用户定位信息
	for (int j = 0; j < col + 1; j++)
	{
		printf("| %2d ", j);
	}
	printf("|\n");
	for (int j = 0; j < col + 1; j++)
	{
		printf("|----");
	}
	printf("|\n");
	for (int i = 1; i < row + 1; i++)
	{
		for (int j = 0; j < col + 2; j++)
		{
			//如果是j == 0 则说明此时应该为第几行,而非棋盘信息
			if (j == 0 && i != row + 1)
			{
				printf("| %2d ", i);
			}
			else {
				int mark = board[i][j].state;
				if (mark == 0)
				{
					printf("| %2c ", ' ');
				}
				else if (mark == 1)
				{
					printf("| %2c ", board[i][j].sign);
				}
				else if (mark == 2)
				{
					printf("| %2c ", 'M');
				}
				else
				{
					printf("| %2c ", '?');
				}
			}
		}
		printf("\n");
		for (int j = 1; j < col + 2; j++)
		{
			printf("|----");
		}
		printf("|\n");
	}
	//说明游戏已经结束
	if (mode == -1)
	{
		return;
	}
	//反映当前模式
	printf("当前模式: ");
	if (mode == 1)
	{
		printf("排雷模式\n");
	}
	else if (mode == 2)
	{
		printf("标记模式\n");
	}

}
//char类型可以隐士转化为int类型,其相加是其ASCII码相加,可以利用这一原因在c上进行操作
char get_num(Board** board, size_t x, size_t y)
{
	char c = '0';
	for (int i = x - 1; i < x + 2; i++)
	{
		for (int j = y - 1; j < y + 2; j++)
		{
			if (board[i][j].sign == '*')
			{
				c++;
			}
		}
	}
	return c;
}
void get_around_mine(Board** board, size_t rows, size_t cols)
{
	//展示的部分为 1 ~ row + 1 即 1 ~ rows - 1, 列同理 
	for (int i = 1; i < rows - 1; i++)
	{
		for (int j = 1; j < cols - 1; j++)
		{
			if (board[i][j].sign == '*')	//如果当前格是地雷则跳过
			{
				continue;
			}
			board[i][j].sign = get_num(board, i, j);	//将当前个元素修改为周围地雷数量
		}
	}
}
void set_mine(Board** board, size_t row, size_t col, size_t mine_count)
{
	//循环放入地雷,因为mine_count是值传递,在此处修改不会影响原有的值,所以在此直接用其进行控制
	while (mine_count)
	{
		//保证每个坐标的值均在 1 ~ row + 1之间
		size_t x = rand() % row + 1;
		size_t y = rand() % col + 1;
		//如果可以放置地雷,将此处放为地雷,同时待布置地雷数目减一
		if (board[x][y].sign == ' ')
		{
			board[x][y].sign = '*';
			mine_count--;
		}
	}
}
void showMenu()
{
	printf("----欢迎使用扫雷游戏----\n\n");
	printf("------1、开始游戏------\n");
	printf("------2、设置参数------\n");
	printf("------0、退出游戏------\n");
}
Board** initBoard(Board** board, size_t row, size_t col)
{
	//实际中为了简化问题,开辟行,列 均 比其显示部分大2的数组
	size_t rows = row + 2;
	size_t cols = col + 2;
	board = (Board**)malloc(sizeof(Board*) * rows);
	//内存开辟失败
	if (!board)
	{
		return NULL;
	}
	for (int i = 0; i < rows; i++)
	{
		board[i] = (Board*)malloc(sizeof(Board) * cols);
		//此时若内存开辟失败,则需要将之前开辟的空间释放掉
		if (!board[i])
		{
			free(board);
			return NULL;
		}
		for (int j = 0; j < cols; j++)
		{
			board[i][j].sign = ' ';
			board[i][j].state = 0;
		}
	}
	return board;
}

main.c

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include "game.h"
#include <time.h>

int main()
{
	srand((unsigned int)time(NULL));
	int row = 10;	//行数
	int col = 10;	//列数
	int mine_count = 10;	//地雷数目
	Board** board = NULL;	//棋盘对象
	int select;	//接收用户的选择
	do
	{
		showMenu();	//展示菜单
		scanf("%d", &select);
		while (getchar() != '\n');
		switch (select)
		{
		case 1:
			game(board, row, col, mine_count);	//游戏进程
			break;
		case 2:
			setParam(&row, &col, &mine_count);	//设置参数
			break;
		case 0:
			printf("感谢您的使用\n");
			break;
		}
	} while (select);
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值