0
点赞
收藏
分享

微信扫一扫

【C语言】C语言实现的无页面扫雷游戏 2.0

谁知我新 2022-02-19 阅读 11

C语言实现的扫雷 2.0 版

对前几天写的代码进行了亿点点更改,写成了现在这个运行效率更高,规则更贴近原版的无页面扫雷。

首先要明确Windows7扫雷的一些特色规则

  • 第一次点击的时候,一定能触发连锁打开
  • 第一次点击的时候,不会点到雷
  • 点到数字为0的方块时,才可以连锁打开

特色规则实现思路or 第一次打开方块的流程

  1. 定义并填充一个棋盘
  2. 打印棋盘
  3. 响应用户第一次打开,获取安全点坐标
  4. 给棋盘布雷,避开安全点
  5. 给棋盘上的格子设置数字
  6. 调用函数打开用户原来希望打开的方块坐标

程序设计
这次我把代码分成了三个文件,分别是

SweepMine.h   
SweepMine.cpp 
main.cpp      

也就是说,把这个游戏设计成了一个接口,需要用的时候再用main.cpp文件调用。

  1. 用一个square结构体来表示每个格子信息
  2. 用二维数组land[][]来表示棋盘
  3. 既然是接口嘛,我还打算搞一个user_land[][]来表示目前的棋盘,方便后来的程序了解到棋盘的情况

SweepMine.h 文件

#ifndef __SWEEPMINE_H__
#define __SWEEPMINE_H__

#define MAX_LANDMINE  99 //雷数量
#define MAX_WIDTH     16 //棋盘宽度(高度)
#define MAX_LENGTH    30 //棋盘长度

#define AREA          MAX_WIDTH * MAX_LENGTH 
#define REL_WIDTH     MAX_WIDTH + 2
#define REL_LENGTH    MAX_LENGTH + 2

#define OVER         -20220202 //游戏结束的标志
#define MARKED       -1        //方块标记的标志
#define STILLUNK     -2        //方块未知的标志
#define OPENED       -3        //方块被打开的标志

#define isSquAround(x,y,i,j)  ((i + 1 == x || i - 1 == x || i == x) && (j + 1 == y || j - 1 == y || j == y))
#define Sweep_fflush()        {char c;do{scanf("%c",&c);}while(c !='\n');}
	
typedef struct square_struct {
	int number;
	bool landmine; //0无雷,1有雷
	short now;
} square;

int game_start    (void);
int initialize    (int,int);
int set_squ       (void);
int fill          (void);

int show_land     (void);
int player_action (void);

int open_squ      (int,int);
int mark_squ      (int,int);

int game_over     (void);
int game_win      (void);

#endif

其中isSquAround(x,y,i,j)宏用来判断(i,j)在不在(x,y)的周围
另外一个宏Sweep_fflush()用来刷新输入缓冲区,取这个名字是为了避免和fflush()重名

你可能会问,都考虑到重名了,那为什么我不直接用fflush(stdio)呢?
这是因为ANSI C没规定它清理缓冲区,只是某些编译器加上了这个功能。虽然可能没什么人会用我的代码,但是写的时候还是要注意一下。

之前定义表示棋盘的二维数组的时候,都是直接按照棋盘的长宽来定义的。要访问某个格子周围的时候,就比较麻烦,很容易发生数组越界。所以这一次的数组的长宽等于实际棋盘长宽+2也就是REL_WIDTHREL_LENGTH 当访问到边缘方块的周围的时候,就不需要判断坐标的正误了。

SweeoMine.cpp里一些变量的定义

#include <cstdio>
#include <cstdlib>
#include <ctime>
#include "SweepMine.h"

square  land[REL_WIDTH][REL_LENGTH]; //实际雷区 
int     user_land[REL_WIDTH][REL_LENGTH]; //用户雷区 
int     opennum = 0; //打开格子数量 
bool    isGameFirst = false; //第一次打开操作的标记

填充棋盘的函数

int fill(void){
	/*填充方块*/
	for (int i = 0; i <= REL_WIDTH - 1; ++i) {
		for (int j = 0; j <= REL_LENGTH - 1; ++j) {
			land[i][j] = (square) {0, 0, STILLUNK};
			user_land[i][j] = STILLUNK;
		}
	}
	return 0;
}

打印棋盘就比较“个性”,每个人都有自己的独特想法可以打印出来,接下来的代码只展示主要的棋盘部分

int show_land(void) {

	/*打印装饰与坐标*/ 
    ...(3)...
    /*打印棋盘本身*/ 
	for (int i = 1; i <= MAX_WIDTH; ++i) {
		for (int j = 1; j <= MAX_LENGTH; ++j)
		
		    if(land[i][j].now == MARKED)
			    printf("回   "); //标记格子样式 
			else if(land[i][j].now == STILLUNK)
			    printf("口   "); //未知格子样式 
			else
			    printf("%d    ",land[i][j].number);
			    
		printf("\n\n");
	}
	return 0;
}

我用中文的“口”字来代表未知的格子,“回”字代表标记的格子,一打开的格子直接打印该格子的数字就好啦

接下来的响应用户页面,也就是交互页面的设计也很“个性”,每个人都有自己的想法,就不展示了。只要你的程序能知道用户想做什么就可以

打开格子的函数如下,传入打开格子的坐标

int open_squ(int x, int y) {
	if(isGameFirst){ //若是第一次点击,则初始化棋盘 
  	    bray(x,y);//布雷
	    set_squ();//设置每个方块的数字 
  	    isGameFirst = false; //从这以后,就不是第一次打开了
  	    open_squ(x,y); //最后还是要用正常流程打开这个方块
  	} else {
  		
  		if (land[x][y].landmine == 1) {
 	 		opennum = OVER; //若打开的格子是雷,就把opennum设置为常量OVER表示游戏结束
  		} else {
  			
  			/*连锁打开规则:点到0后递归打开周围所有方块*/
			land[x][y].now = OPENED;
  		    user_land[x][y] = land[x][y].number;
  		    
  		    ++opennum; // 打开方块数量++ 
  		    
  		    if (land[x][y].number == 0){
				
  		    	if(y + 1 <= MAX_LENGTH){

  		    		if(land[x][y + 1].now == STILLUNK){
					    open_squ(x, y + 1);
				    }
				
  		    		if(x - 1 >= 1){
  		    			if(land[x - 1][y + 1].now == STILLUNK)
						    open_squ(x - 1, y + 1);
					}
					
					if(x + 1 <= MAX_WIDTH){
						if(land[x + 1][y + 1].now == STILLUNK)
						    open_squ(x + 1, y + 1);
					}
	
				}
				
				if(y - 1 >= 1){
					
					if(land[x][y - 1].now == STILLUNK)
					    open_squ(x, y - 1);
					    
					if(x - 1 >= 1){
						if(land[x - 1][y - 1].now == STILLUNK)
						    open_squ(x - 1, y - 1);
					}
					
					if(x + 1 <= MAX_WIDTH){
						if(land[x + 1][y - 1].now == STILLUNK)
						    open_squ(x + 1, y - 1);
					}
					
				}
				
				if(x - 1 >= 1){
  		    		if(land[x - 1][y].now == STILLUNK)
						open_squ(x - 1, y);
				}
  		    	
  		    	if(x + 1 <= MAX_WIDTH){
  		    		if(land[x + 1][y].now == STILLUNK)
						open_squ(x + 1, y);
				}
			}
		}
	}
	return 0;
}

棋盘的布雷,就是随机取到一个坐标,若该坐标上没有雷(不然就有可能重复布雷!),而且这个坐标不在安全点周围的话,就成功布下一个雷。其实雷格子的数字是什么都无所谓,但是为了当时调试方便,就设置了初始值为-10。

int bray(int x,int y) {
//给棋盘布雷 接收两个坐标 此坐标对应的格子叫做"安全点" 安全点及其周围不会有雷 
//安全点上数字为0 
	srand((unsigned)time(0)); //重置随机数种子 
	
	for (int n = 1;n <= MAX_LANDMINE;) {
		int i = rand() % (MAX_WIDTH) + 1; //随机生成坐标 
		int j = rand() % (MAX_LENGTH) + 1;
		
        if(!isSquAround(x,y,i,j)){
		    //雷如果不在安全点周围,就成功埋下雷 
			if (land[i][j].landmine == 0) {
			    land[i][j] = (square){-10, 1, STILLUNK};
			    ++n;  //只有成功布雷才会推动代码运行,保证雷数量
		    }
		}
		
	}	
	return 0;
}

给方块设置数字:我们知道,每个格子的landmine属性不是0就是1,是0的时候不是雷,是1的时候是雷。若我们要了解某个格子周围的雷数,直接把周围方块的landmine加起来就可以了。

int set_squ(void){
	/*设置方块数字*/
	for (int i = 1; i <= MAX_WIDTH; ++i) {
		for (int j = 1; j <= MAX_LENGTH; ++j) {
			land[i][j].number += \
			    land[i - 1][j    ].landmine + \
			    land[i - 1][j - 1].landmine + \
			    land[i - 1][j + 1].landmine + \
			    land[i + 1][j    ].landmine + \
			    land[i + 1][j - 1].landmine + \
			    land[i + 1][j + 1].landmine + \
			    land[i    ][j - 1].landmine + \
			    land[i    ][j + 1].landmine;      //有雷的landmine值为1,反之为0 
		}
	}
    return 0;
}

标记方块,只需要把方块的now属性变一下就行~

int mark_squ(int x,int y){
	//改变方块的now属性来标记方块 
    land[x][y].now = land[x][y].now == MARKED? STILLUNK : MARKED;
	user_land[x][y] = MARKED;
	return 0;
}

其他的比较简单的东西,比如胜利时的展示,失败时的展示,在这里就不挨个展示了。

全部代码

SweepMine.h

#ifndef __SWEEPMINE_H__
#define __SWEEPMINE_H__

#define MAX_LANDMINE  99 //雷数量
#define MAX_WIDTH     16 //棋盘宽度(高度)
#define MAX_LENGTH    30 //棋盘长度

#define AREA          MAX_WIDTH * MAX_LENGTH 
#define REL_WIDTH     MAX_WIDTH + 2
#define REL_LENGTH    MAX_LENGTH + 2

#define OVER         -20220202
#define MARKED       -1
#define STILLUNK     -2
#define OPENED       -3

#define isSquAround(x,y,i,j)  ((i + 1 == x || i - 1 == x || i == x) && (j + 1 == y || j - 1 == y || j == y))
#define Sweep_fflush()        {char c;do{scanf("%c",&c);}while(c !='\n');}
	
typedef struct square_struct {
	int number;
	bool landmine; //0无雷,1有雷
	short now;
} square;

int game_start    (void);
int initialize    (int,int);
int set_squ       (void);
int fill          (void);

int show_land     (void);
int player_action (void);

int open_squ      (int,int);
int mark_squ      (int,int);

int game_over     (void);
int game_win      (void);

#endif

SweepMine.cpp

#include <cstdio>
#include <cstdlib>
#include <ctime>
#include "SweepMine.h"

square  land[REL_WIDTH][REL_LENGTH]; //实际雷区 
int     user_land[REL_WIDTH][REL_LENGTH]; //用户雷区 
int     opennum = 0; //打开格子数 
bool    isGameFirst = false;

int game_start(void){
	//一盘游戏的开始 
	MAX_LENGTH;MAX_WIDTH;
	
	isGameFirst = true;
	fill(); //先填上空方块 
	show_land(); //直接展示 
	return 0;
}

int bray(int x,int y) {
//给棋盘布雷 接收两个坐标 此坐标对应的格子叫做"安全点" 安全点及其周围不会有雷 
//安全点上数字为0 
	srand((unsigned)time(0)); //重置随机数种子 
	
	for (int n = 1;n <= MAX_LANDMINE;) {
		int i = rand() % (MAX_WIDTH) + 1; //随机生成坐标 
		int j = rand() % (MAX_LENGTH) + 1;
		
        if(!isSquAround(x,y,i,j)){
		    //雷如果不在安全点周围,就成功埋下雷 
			if (land[i][j].landmine == 0) {
			    land[i][j] = (square){-10, 1, STILLUNK};
			    ++n;      //地雷格子上的数字为一定是负数 
		    }
		}
		
	}	
	return 0;
}

int fill(void){
	/*填充方块*/
	for (int i = 0; i <= REL_WIDTH - 1; ++i) {
		for (int j = 0; j <= REL_LENGTH - 1; ++j) {
			land[i][j] = (square) {0, 0, STILLUNK};
			user_land[i][j] = STILLUNK;
		}
	}
	return 0;
}

int set_squ(void){
	/*设置方块数字*/
	for (int i = 1; i <= MAX_WIDTH; ++i) {
		for (int j = 1; j <= MAX_LENGTH; ++j) {
			land[i][j].number += \
			    land[i - 1][j    ].landmine + \
			    land[i - 1][j - 1].landmine + \
			    land[i - 1][j + 1].landmine + \
			    land[i + 1][j    ].landmine + \
			    land[i + 1][j - 1].landmine + \
			    land[i + 1][j + 1].landmine + \
			    land[i    ][j - 1].landmine + \
			    land[i    ][j + 1].landmine;      //有雷的landmine值为1,反之为0 
		}
	}
    return 0;
}


int show_land(void) {
	printf("============================================================================================================\n");
	/*打印装饰与坐标*/ 
	printf("扫 雷 游 戏  ");
	for (int i = 1; i <= MAX_LENGTH; ++i) 
		printf("[%2d] ", i);
	printf("\nBL_hongzhong  |");
	for (int i = 1; i <= MAX_LENGTH - 1; ++i) 
		printf("    |");
	printf("\n");
	printf("2022年2月2日  |");
	for (int i = 1; i <= MAX_LENGTH - 1; ++i) 
		printf("    |");
	printf("\n");
	printf("              V");
	for (int i = 1; i <= MAX_LENGTH - 1; ++i)
		printf("    V");
	printf("\n");
    
    /*打印棋盘本身*/ 
	for (int i = 1; i <= MAX_WIDTH; ++i) {
		printf("[%2d]--------> ", i);
		for (int j = 1; j <= MAX_LENGTH; ++j)
		
		    if(land[i][j].now == MARKED)
			    printf("回   "); //标记格子样式 
			else if(land[i][j].now == STILLUNK)
			    printf("口   "); //未知格子样式 
			else
			    printf("%d    ",land[i][j].number);
			    
		printf("\n\n");
	}
	printf("============================================================================================================\n");
	return 0;
}

int player_action(void) {
	/*获取用户输入 响应输入*/ 
	int choose, input_x, input_y;

	printf("选择您的操作\n输入0:打开方块\n输入1:(取消)标记方块\n");
	printf("您的选择 >>> ");
	scanf("%d", &choose);
	Sweep_fflush(); //清空缓冲区 避免scanf()函数的错误输入导致 程序内存溢出 
	
	printf("请输入待操作方块的坐标 中间用英文逗号隔开 >>> ");
	scanf("%d,%d", &input_x, &input_y);
	Sweep_fflush();
	
	if (choose != 0 && choose != 1) {
		printf("无效的操作 %d\n", choose);
	} else if (input_x < 1 || input_y < 1 || input_x > MAX_WIDTH || input_y > MAX_LENGTH) {
		printf("无效的坐标 (%d,%d)\n", input_x, input_y);
	} else {
		
		if (choose == 0) {

			if (land[input_x][input_y].now == OPENED || land[input_x][input_y].now == MARKED) {
				printf("无法打开方块,可能是因为方块(%d,%d)被标记或已被打开\n", input_x, input_y);
			} else {
				open_squ(input_x, input_y);
			}

		} else if (choose == 1) {

			if (land[input_x][input_y].now == OPENED) {
				printf("无法标记方块,因为方块(%d,%d)已经被打开\n", input_x, input_y);
			} else {
				mark_squ(input_x,input_y);
			}

		}
	}

	return 0;
}

int open_squ(int x, int y) {
	if(isGameFirst){ //若是第一次点击,则初始化棋盘 
    	bray(x,y);//布雷
	    set_squ();//设置每个方块的数字 
  	    isGameFirst = false;
  	    open_squ(x,y);
  	} else {
  		
  		if (land[x][y].landmine == 1) {
 	 		opennum = OVER;
  		} else {
  			
  			/*连锁打开规则:点到0后递归打开周围所有方块*/
			land[x][y].now = OPENED;
  		    user_land[x][y] = land[x][y].number;
  		    
  		    ++opennum; // 打开方块数量++ 
  		    
  		    if (land[x][y].number == 0){
				
  		    	if(y + 1 <= MAX_LENGTH){

  		    		if(land[x][y + 1].now == STILLUNK){
					    open_squ(x, y + 1);
				    }
				
  		    		if(x - 1 >= 1){
  		    			if(land[x - 1][y + 1].now == STILLUNK)
						    open_squ(x - 1, y + 1);
					}
					
					if(x + 1 <= MAX_WIDTH){
						if(land[x + 1][y + 1].now == STILLUNK)
						    open_squ(x + 1, y + 1);
					}
	
				}
				
				if(y - 1 >= 1){
					
					if(land[x][y - 1].now == STILLUNK)
					    open_squ(x, y - 1);
					    
					if(x - 1 >= 1){
						if(land[x - 1][y - 1].now == STILLUNK)
						    open_squ(x - 1, y - 1);
					}
					
					if(x + 1 <= MAX_WIDTH){
						if(land[x + 1][y - 1].now == STILLUNK)
						    open_squ(x + 1, y - 1);
					}
					
				}
				
				if(x - 1 >= 1){
  		    		if(land[x - 1][y].now == STILLUNK)
						open_squ(x - 1, y);
				}
  		    	
  		    	if(x + 1 <= MAX_WIDTH){
  		    		if(land[x + 1][y].now == STILLUNK)
						open_squ(x + 1, y);
				}
			}
		}
	}
	return 0;
}


int mark_squ(int x,int y){
	//改变方块的now属性来标记方块 
    land[x][y].now = land[x][y].now == MARKED? STILLUNK : MARKED;
	user_land[x][y] = MARKED;
	return 0;
}

int game_win(void) {
	for (int i = 0; i <= MAX_WIDTH - 1; ++i) {
		printf("              ");
		for (int j = 0; j <= MAX_LENGTH - 1; ++j) {
			printf("帅   ");
		}
		printf("\n\n");
	}
	printf("============================================================================================================\n");
	printf("恭喜你,你成功通关了游戏!\n");
	return 0;
}

int game_over(void) {
	for (int i = 1; i <= MAX_WIDTH; ++i) {
		printf("              ");
		for (int j = 1; j <= MAX_LENGTH; ++j) {
			if (land[i][j].landmine == 1) {
				printf("雷   ");
			} else {
				printf("口   ");
			}
		}
		printf("\n\n");
	}
	printf("============================================================================================================\n");
	printf("很抱歉,游戏失败了!\n");
	return 0;
}

main.cpp

#include "SweepMine.h" 
extern int opennum; //打开格子数 

int main(void){
	game_start();
	do{
		player_action();
		show_land();
		
		if(AREA - opennum == MAX_LANDMINE){
		    //若棋盘面积 - 打开方块数 == 雷数,说明游戏胜利(没打开的格子此时都是雷了!)
			game_win();
			return 0;
		}
	}while(opennum != OVER);
	
	game_over();
	return 0;
}
举报

相关推荐

0 条评论