【C语言】扫雷游戏

目录

1 扫雷游戏功能说明

1.1 扫雷游戏介绍

1.2 游戏界面

2 游戏分析与设计

2.1 读入用户指令

2.2 地雷数据生成,处理与储存

2.3 地雷标记及展开

2.4 用户界面

2.5 游戏循环主体

3 代码实现

game.h

game.c

test.c


1 扫雷游戏功能说明

1.1 扫雷游戏介绍

        使用控制台实现经典扫雷游戏:

                输入“m n”排查雷,输入“f m n”标记/取消标记,m为行数,n为列数;

        可以选择游戏难度:

                简单 9*9 棋盘,10个雷

                中等 16*16 棋盘,40个雷

                困难 16*30 棋盘,99个雷

        根据当前格雷数及标记,自动展开周围非雷格;

        显示游玩时间。

1.2 游戏界面

        数字代表周围雷数,*为未排查区域,#为标记。失败时*为地雷。

2 游戏分析与设计

2.1 读入用户指令

        为防止用户非法指令造成程序死循环,可以直接使用fgets函数读取整行,再用sscanf解析信息:

int x,y;
char line[100];
while(fgets(line, sizeof(line), stdin)){
		if (sscanf(line, "%d%d", &x, &y) == 2){
			if (x >= 1 && x <= row && y >= 1 && y <= col){
				break;
			}else{
				printf("坐标非法,重新输入\n");
			}
		}else{
			printf("输入错误,请输入两个整数\n");
		}
	}

2.2 地雷数据生成,处理与储存

        如图,以9*9棋盘为例,用一个 长宽=边界+2 的二维数组储存地雷信息,“0”为无雷,“1”为有雷。通过搜索周围确定当前格显示雷数,存入另一个二维数组。因为多开两行,搜索时无需考虑超出边界的问题。

        我们可以设计两个二维数组分别储存实际雷和显示雷数:mine, show。在游戏开始,为保持神秘,用 * 填充数组。

show数组
mine数组

        对于这两个数组,编写函数初始化:

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

        接下来是利用rand函数实现随机填雷。有关rand函数详见【C语言】猜数字游戏-CSDN博客

void SetMine(char board[ROWS][COLS], int row, int col, int a, int b){//生成随机的坐标,布置雷
	int count = COUNT;
	while (count){
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (board[x][y] == '0' && !(a==x && b==y)){//此处传入开局坐标避免开局暴毙
			board[x][y] = '1';
			count--;
		}
	}
}

        找雷函数:

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');
}

2.3 地雷标记及展开

        如果根据已有信息及标记可以判断某些格子没有地雷,需要帮助玩家把这些格子自动展开。

        首先判断周围的标记数量:

int GetFlagCount(char show[ROWS][COLS], int x, int y){
	int cnt = 0;
	for (int dx = -1; dx <= 1; dx++) {
		for (int dy = -1; dy <= 1; dy++) {
			if ((dx == 0 && dy == 0)) continue; // 跳过自身
			if(show[x+dx][y+dy]=='#') cnt++;
		}
	}
	return cnt+'0';
}

        展开:

int explore(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int a,int b){
	if(mine[a][b]=='1') return 1;//踩雷返回1
	if(show[a][b]=='*'){
		int count = GetMineCount(mine, a, b);//该位置不是雷,就统计这个坐标周围有几个雷
		show[a][b] = count + '0';
		win++;//统计已经展开的格子,用于判断胜利条件
	}
	if(GetFlagCount(show, a, b)-show[a][b]==0){	
		for (int dx = -1; dx <= 1; dx++) {
			for (int dy = -1; dy <= 1; dy++) {
				if ((dx == 0 && dy == 0) || (a+dx<1 || a+dx>row) || (b+dy<1 || b+dy>col || show[a+dx][b+dy]!='*')) continue; // 跳过自身、边界及排查过的区域
				if(explore(mine, show, row, col, a+dx, b+dy)) return 1;
			}
		}
	}
	return 0;
}

2.4 用户界面

        扫雷界面展示show数组内容:

void DisplayBoard(char board[ROWS][COLS], int row, int col){
	endTime = clock();//更新时间
	printf("------扫雷游戏------ 用时:%.2lfs\n", ((double) (endTime - startTime)) / CLOCKS_PER_SEC);
	printf(" 0 ");
	for (int i = 1; i <= col; i++){
		if(i<10) printf(" %d", i);
		else if(i==10) printf(" X");
		else if(i==20) printf(" D");
		else printf(" %d", i%10);
	}
	printf("\n\n");
	for (int i = 1; i <= row; i++){
		printf("%2d  ", i);
		for (int j = 1; j <= col; j++){
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

        失败展示mine数组内容:

void DisplayOver(char board[ROWS][COLS], int row, int col){
	printf("------扫雷游戏-----\n");
	printf(" 0 ");
	for (int i = 1; i <= col; i++){
		if(i<10) printf(" %d", i);
		else if(i==10) printf(" X");
		else if(i==20) printf(" D");
		else printf(" %d", i%10);
	}
	printf("\n\n");
	for (int i = 1; i <= row; i++){
		printf("%2d  ", i);
		for (int j = 1; j <= col; j++){
			if(board[i][j]=='1') printf("* ");
			else printf("  ");
		}
		printf("\n");
	}
	printf("\n");
}

2.5 游戏循环主体

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int a,int b){
	win = 0;//记录已展开格数
	int x = a;//传入第一次搜索坐标
	int y = b;
	char f;
	int flag = 1;
	char line[100];
	if(flag) {flag=0;goto here;}
	while(win <row*col- COUNT && fgets(line, sizeof(line), stdin)){
		if (sscanf(line, "%d%d", &x, &y) == 2){
			if (x >= 1 && x <= row && y >= 1 && y <= col){
here:			if (explore(mine, show, row, col, x, y)){
					printf("很遗憾,你被炸死了\n");
					DisplayOver(mine, row, col);
					break;
				}else{
					if(win <row*col- COUNT) DisplayBoard(show, row, col);
				}
			}else{
				printf("坐标非法,重新输入\n");
			}
		}else if(sscanf(line, "%c%d%d", &f, &x, &y) == 3){
			if (x >= 1 && x <= row && y >= 1 && y <= col && f=='f'){
				if (show[x][y] == '*'){
					show[x][y] = '#';
					DisplayBoard(show, row, col);
				}else if(show[x][y] == '#'){
					show[x][y] = '*';
					DisplayBoard(show, row, col);
				}else{
					printf("非法指令,重新输入\n");
				}
			}else{
				printf("非法指令,重新输入\n");
			}
		}else{
			printf("输入错误,请输入两个整数\n");
		}
	}
	if (win == row * col - COUNT){
		endTime = clock();
		printf("恭喜你,排雷成功!\n");
		DisplayBoard(mine, row, col);
	}
}

3 代码实现

        代码分为game.h,game.c,test.c三个文件,分别储存声明,函数,主体,在test文件运行。

game.h

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 16
#define COL 30
#define ROWS ROW+2
#define COLS COL+2
int COUNT,row,col,rows,cols,win;
clock_t startTime, endTime;
//初始化难度
void difficulty();
//初始化棋盘
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 a, int b);
//排查雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int a,int b);
//展开雷
int explore(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int a,int b);
//游戏结束
void DisplayOver(char board[ROWS][COLS], int row, int col);

game.c

#include "game.h"

void difficulty(){
	int diff;
	char l[100];
	printf("*******************\n");
	printf("**** 1. easy ******\n");
	printf("**** 2. medium ****\n");
	printf("**** 3. hard ******\n");
	printf("*******************\n");
	printf("请选择难度:>");
	while(fgets(l, sizeof(l),stdin)){
		if(sscanf(l,"%d",&diff)==1){
			if(diff<=3 && diff>=0){
				switch (diff) {
					case 1:
						COUNT=10;
						row=9;
						col=9;
						break;
					case 2:
						COUNT=40;
						row=16;
						col=16;
						break;
					case 3:
						COUNT=99;
						row=16;
						col=30;
						break;
					default:	
						break;
				}break;
			}else{
				printf("输入错误,请重新输入\n");
			}	
		}else{
			printf("输入错误,请重新输入\n");
		}
	}
	rows=row+2;
	cols=col+2;
}

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

void DisplayBoard(char board[ROWS][COLS], int row, int col){
	endTime = clock();
	printf("------扫雷游戏------ 用时:%.2lfs\n", ((double) (endTime - startTime)) / CLOCKS_PER_SEC);
	printf(" 0 ");
	for (int i = 1; i <= col; i++){
		if(i<10) printf(" %d", i);
		else if(i==10) printf(" X");
		else if(i==20) printf(" D");
		else printf(" %d", i%10);
	}
	printf("\n\n");
	for (int i = 1; i <= row; i++){
		printf("%2d  ", i);
		for (int j = 1; j <= col; j++){
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}

void SetMine(char board[ROWS][COLS], int row, int col, int a, int b){//生成随机的坐标,布置雷
	int count = COUNT;
	while (count){
		int x = rand() % row + 1;
		int y = rand() % col + 1;
		if (board[x][y] == '0' && !(a==x && b==y)){
			board[x][y] = '1';
			count--;
		}
	}
}

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');
}

int GetFlagCount(char show[ROWS][COLS], int x, int y){
	int cnt = 0;
	for (int dx = -1; dx <= 1; dx++) {
		for (int dy = -1; dy <= 1; dy++) {
			if ((dx == 0 && dy == 0)) continue; // 跳过自身
			if(show[x+dx][y+dy]=='#') cnt++;
		}
	}
	return cnt+'0';
}

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int a,int b){
	win = 0;
	int x = a;
	int y = b;
	char f;
	int flag = 1;
	char line[100];
	if(flag) {flag=0;goto here;}
	while(win <row*col- COUNT && fgets(line, sizeof(line), stdin)){
		if (sscanf(line, "%d%d", &x, &y) == 2){
			if (x >= 1 && x <= row && y >= 1 && y <= col){
here:			if (explore(mine, show, row, col, x, y)){
					printf("很遗憾,你被炸死了\n");
					DisplayOver(mine, row, col);
					break;
				}else{
					if(win <row*col- COUNT) DisplayBoard(show, row, col);
				}
			}else{
				printf("坐标非法,重新输入\n");
			}
		}else if(sscanf(line, "%c%d%d", &f, &x, &y) == 3){
			if (x >= 1 && x <= row && y >= 1 && y <= col && f=='f'){
				if (show[x][y] == '*'){
					show[x][y] = '#';
					DisplayBoard(show, row, col);
				}else if(show[x][y] == '#'){
					show[x][y] = '*';
					DisplayBoard(show, row, col);
				}else{
					printf("非法指令,重新输入\n");
				}
			}else{
				printf("非法指令,重新输入\n");
			}
		}else{
			printf("输入错误,请输入两个整数\n");
		}
	}
	if (win == row * col - COUNT){
		endTime = clock();
		printf("恭喜你,排雷成功!\n");
		DisplayBoard(mine, row, col);
	}
}

int explore(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int a,int b){
	if(mine[a][b]=='1') return 1;
	if(show[a][b]=='*'){
		int count = GetMineCount(mine, a, b);//该位置不是雷,就统计这个坐标周围有几个雷
		show[a][b] = count + '0';
		win++;
	}
	if(GetFlagCount(show, a, b)-show[a][b]==0){	
		for (int dx = -1; dx <= 1; dx++) {
			for (int dy = -1; dy <= 1; dy++) {
				if ((dx == 0 && dy == 0) || (a+dx<1 || a+dx>row) || (b+dy<1 || b+dy>col || show[a+dx][b+dy]!='*')) continue; // 跳过自身、边界及排查过的区域
				if(explore(mine, show, row, col, a+dx, b+dy)) return 1;
			}
		}
	}
	return 0;
}

void DisplayOver(char board[ROWS][COLS], int row, int col){
	printf("------扫雷游戏-----\n");
	printf(" 0 ");
	for (int i = 1; i <= col; i++){
		if(i<10) printf(" %d", i);
		else if(i==10) printf(" X");
		else if(i==20) printf(" D");
		else printf(" %d", i%10);
	}
	printf("\n\n");
	for (int i = 1; i <= row; i++){
		printf("%2d  ", i);
		for (int j = 1; j <= col; j++){
			if(board[i][j]=='1') printf("* ");
			else printf("  ");
		}
		printf("\n");
	}
	printf("\n");
}

test.c

#include "game.h"
#include "game.c"
void menu(){
	printf("*******************\n");
	printf("***** 1. play *****\n");
	printf("***** 0. exit *****\n");
	printf("*******************\n");
}
void game(){
	char mine[ROWS][COLS];//存放布置好的雷
	char show[ROWS][COLS];//存放排查出的雷的信息
	difficulty();//设置难度          //初始化棋盘
	InitBoard(mine, rows, cols, '0');//1. mine数组最开始是全'0'
	InitBoard(show, rows, cols, '*');//2. show数组最开始是全'*'
	startTime = clock();
	DisplayBoard(show, row, col);//打印棋盘
	int x,y;//防止开局暴毙
	char line[100];
	printf("请输入要排查的坐标:>");
	while(fgets(line, sizeof(line), stdin)){
		if (sscanf(line, "%d%d", &x, &y) == 2){
			if (x >= 1 && x <= row && y >= 1 && y <= col){
				break;
			}else{
				printf("坐标非法,重新输入\n");
			}
		}else{
			printf("输入错误,请输入两个整数\n");
		}
	}
	SetMine(mine, row, col, x, y);//1. 布置雷 
	FindMine(mine, show, row, col, x, y);//2. 排查雷
}
int main(){
	int input = 0;
	srand((unsigned int)time(NULL));
	do{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		getchar();
		switch (input){
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏\n");
			break;
		default:
			printf("选择错误,重新选择\n");
			break;
		}
	} while (input);
	return 0;
}

  • 60
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值