• C++ · 手把手教你写一个扫雷小游戏


    Hello,大家好,我是余同学。这两个月真是太忙了,无暇给大家更新文章…

    暑假不是写了个扫雷小游戏吗(Link)?考虑到很多同学对代码没有透彻的理解,那么,这篇文章,我们来详细分析一下代码.

    我们分为三个部分来讲:生成雷区,生成雷区数字刷新与判断


    Part.1 生成雷区

    随机数

    首先,我们的雷区不能是定义好的矩阵,肯定得用随机数生成
    用随机数的话,就出现了一个问题:

    • C++有一个生成随机数的奇妙特性(也就是在不写srand(time(NULL));的情况下)

    这个特性是什么样的呢?
    我们来看下示例:

    #include 
    using namespace std;
    
    int main ()
    {
    	//srand(time(NULL));
    	int random=rand()%10;
    	cout<<random;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    运行结果:
    第一次:
    1.1

    第二次:
    1.2

    可以看到, 虽然是"随机数",但是,程序每次输出的数却是一样的,这是怎么回事?

    程序每次生成的数来源于一个随机数种子,如果我们不改变它的话,那么程序就会一直使用这个种子,从而导致每次生成的随机数都一样

    因此,我们要使用随机数种子
    其具体原理即是利用每次运行的时间互不相同,生成的随机数也不同
    srand(time(NULL));

    可以使用rand()%x生成随机数,也可以使用宏定义,即使用define

    #define random(x) rand()%(x)
    
    • 1

    那么,我们看看效果吧
    第一次输出:
    2.1
    第二次输出:
    2.2
    也就是说,如果我们要生成一个从ab区间的随机数,可以用以下指令:

    int number=a+rand()%b; //随机数生成区间为:a ~ a+b-1
    int number=rand()%b;   //随机数生成区间为:1 ~ b-1
    
    • 1
    • 2

    随机数搞定了,接下来,就是考虑生成扫雷矩阵的事情了

    生成雷区

    现在,我们面临的最大问题就是:在不使用字符串的情况下,如何用0~9这十个数字实现数字与雷的分别

    其实,实现很简单,我使用的方法是:1~8作为数字,0作为附近9格无雷标志(其实还是数字),9作为雷

    那么,我们的矩阵是 10×10 的,只要通过循环生成 100 个随机数就行了

    看代码吧:

    #include
    /*
    #include 
    #include 
    #include 
    #include 
    #include 
    */
    #define random(x) 1+rand()%(x)
    using namespace std;
    int ui[12][12],b[12][12]; //多开一些没有坏处,原因后面会讲
    
    int main(){
    	srand(time(NULL)); //random seed, srand(time(0));
    	//system("color 1B");
    	system("title MineSweeper");
    	int cnt=10; //地雷个数
    	
    	while(cnt){
    		int x=random(10);//a+rand()%b = [a, a+b-1]
    		int y=random(10);
    		if(!ui[x][y]){//if(ui[x][y]==0){
    			ui[x][y]=9;
    			cnt--;
    		}
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    忘记说了,我设了两个二维数组,其中,ui数组的功能是:存储整个雷区;数组b的功能则是:存储每个方格的状态(详见Part.3

    至此,我们的第一部分结束。

    如果有的同学疑惑上面的代码没有输出时,那是因为我们只写了生成雷区的代码,并没有写生成雷区旁数字的代码,详细请看Part.2 生成雷区数字


    Part. 2 生成雷区数字

    要完成这一部分,我们首先得了解地雷旁数字的生成规律:

    每个数字代表周围8格内的地雷数量

    还是上图片:
    3.1
    这样,我们的逻辑就清晰了,只要用双重循环遍历二维数组b的每一项,判断这个点是否为地雷,如果是,就跳过这个点,继续向下遍历;否之,计算出该点周围8个方格的总雷数(我们暂且定义为sum),sum就是这个点的数值

    来把这一部分的代码实现吧

    for(int i=1;i<=10;i++){
    	for(int j=1;j<=10;j++){
    		int sum=0; //统计周围地雷数量
    		if(ui[i][j]!=9){ //该点不为雷
    			/*
    			下面的这些坐标具体位置详见上图
    			*/
    			if(ui[i-1][j-1]==9) sum++;//sum=!(a[i-1][j-1]-9)+!(a[i-1][j]-9)...
    			if(ui[i-1][j]==9) sum++;
    			if(ui[i-1][j+1]==9) sum++;
    			if(ui[i][j-1]==9) sum++;
    			if(ui[i][j+1]==9) sum++;
    			if(ui[i+1][j-1]==9) sum++;
    			if(ui[i+1][j]==9) sum++;
    			if(ui[i+1][j+1]==9) sum++;
    			ui[i][j]=sum;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    现在,我们就知道了为什么前面的两个二维数组都要开大一点了:

    因为二维数组的索引是从0开始的,而我们使用1作为起始索引(具体见上方代码两个for循环中),就是为了避免在边缘上的方块无法获取到周围地雷数,从而导致程序出bug的原因

    然后,代码中的8个 if 语句都是统计周围地雷数的,如果你想简单亿点的话,把if语句替换成这一行:

    sum=!(ui[i-1][j-1]-9)+!(ui[i-1][j]-9)+!(ui[i-1][j+1]-9)+
    !(ui[i][j-1]-9)+!(ui[i][j+1]-9)+!(ui[i+1][j-1]-9)+!(ui[i+1][j]-9)+!(ui[i+1][j+1]-9);
    
    • 1
    • 2

    这里我分做两行写,是怕有的同学看不清,大家在实际替换中删掉换行符就可以了

    这一部分完整代码:

    #include
    #include "windows.h"
    /*
    #include 
    #include 
    #include 
    #include 
    #include 
    */
    #define random(x) 1+rand()%(x)
    using namespace std;
    
    int ui[12][12],b[12][12]; 
    int main(){
    	srand(time(NULL)); //random seed
    	//system("color 1B");
    	system("title MineSweeper");
    	int cnt=10;//cout<<"Booms:";cin>>cnt;
    	while(cnt){
    		int x=random(10);//a+rand()%b = [a, a+b-1]
    		int y=random(10);
    		if(!ui[x][y]){//if(ui[x][y]==0){
    			ui[x][y]=9;
    			cnt--;
    		}
    	}
    	
    	for(int i=1;i<=10;i++){
    		for(int j=1;j<=10;j++){
    			int sum=0;
    			if(ui[i][j]!=9){
    				if(ui[i-1][j-1]==9) sum++;//sum=!(a[i-1][j-1]-9)+!(a[i-1][j]-9)...
    				if(ui[i-1][j]==9) sum++;
    				if(ui[i-1][j+1]==9) sum++;
    				if(ui[i][j-1]==9) sum++;
    				if(ui[i][j+1]==9) sum++;
    				if(ui[i+1][j-1]==9) sum++;
    				if(ui[i+1][j]==9) sum++;
    				if(ui[i+1][j+1]==9) sum++;
    				/*sum=!(ui[i-1][j-1]-9)+!(ui[i-1][j]-9)+!(ui[i-1][j+1]-9)+
    				!(ui[i][j-1]-9)+!(ui[i][j+1]-9)+!(ui[i+1][j-1]-9)+
    				!(ui[i+1][j]-9)+!(ui[i+1][j+1]-9);*/
    				ui[i][j]=sum;
    				
    			}
    		}
    	}
    	
    	/*for(int i=1;i<=10;i++){
    		for(int j=1;j<=10;j++){
    			cout<
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56

    大家把最后一段的注释符删掉,就可以看到我们的矩阵了
    test1

    但是,我们到现在才做完整个项目的一半,我们还没有写判断操作符的代码,这就要留到Part 3来讲了


    Part. 3 状态更新与判断输入

    刷新屏幕

    我们先来讲刷新屏幕

    为什不先写判断输入的代码呢?
    因为我们得让程序先把扫雷的矩阵输出出来

    我们看看这张图片
    minesweeper
    这是原版的扫雷界面,通过观察每个方格,可以发现,每个方格都会出现三种状态:已翻开(包括空格和数字,用1表示)、未翻开(包括数字和地雷)和旗子(用户标雷处)

    那么,我们可以用"#"表示未翻开的区域,用"P"表示旗子(🚩),用0~9这十个数字表示已翻开区域

    但是,井号(#)和旗子(P)都属于字符串类型,我们开的是数组类型int整型,存储不了字符串类型的字符,所以,我们不能把#P赋值给二维数组ui里的某一项,而是得直接输出

    逻辑:双重for循环遍历每个方格,如果该方块状态为未翻开,输出#;如果此方块状态为已翻开,直接输出二维数组ui里的这一项;如果此方块已被插旗,则输出P

    看看代码实现吧:

    for(int i=1;i<=10;i++){ 
    	for(int j=1;j<=10;j++){ //遍历每个方格
    		if(b[i][j]==0){ //如果此方块状态为未翻开
    				cout<<"#"<<' '; //则输出#
    			}else if(b[i][j]==1){ //如果此方块状态为已翻开
    				cout<<ui[i][j]<<' '; //直接输出就行
    			}else if(b[i][j]==2){ //如果此方块已被插旗
    				cout<<"P"<<' '; //输出P(已插旗)
    			}
    		}cout<<endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    判断输入

    先来定义一下这个游戏的规则:

    游戏一开始首先会打印一个10×10的井号(#)方阵,表示扫雷区,接下来让用户输入一个操作命令,再输入横纵坐标,表示要操作的方格。

    将雷全部扫完即为胜利,否之,踩到雷即为失败


    操作符:

    • q代表翻开坐标处的井号,再输入xy坐标,表示翻开该方格
    • p代表在坐标处插旗,再输入xy坐标,表示在这个方格上插旗(也就是标雷)
    • c代表取消坐标位的旗,再输入xy坐标,表示取消在该位置插旗

    坐标判断依据:

    上方所述的x,y坐标的具体位置:第x行第y个(从左往右数)
    比如这个矩阵(3*3):

    • 5 9 8
    • 2 0 3
    • 8 6 7

    x=2y=3 时,表示的数字为3


    理解了上面这些逻辑,我们就有了一个大体的思路:
    设置状态变量op(char类型)与xy(存储坐标)

    • 如果输入为q,如果该方块为地雷,直接输出You died!;如果为0~8的正常数字,将该方块状态设置为已翻开
    if(op=='q'){
    	if(ui[x][y]==9){
    		cout<<"You died !";
    		scanf("%d");
    		return 0;
    	}
    	else{
    		b[x][y]=1;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    然后,再设置变量kk1k为地雷正确位置(客观),k1为用户标雷位置(主观);作用:存储总地雷数,如果k==cnt(总雷数)k==k1的话,说明所有地雷已经扫完,且位置正确(没有把正常方格当成地雷)

    • 如果输入为p,将该方格设为P,状态设为2(标雷),k1自增;如果当前位置有雷,则说明用户判断正确,k自增
    else if(op=='p'){
    	k1++;
    	if(ui[x][y]==9){
    		k++;
    	}	
    	b[x][y]=2;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 如果输入为c,如果该位置状态为2(插旗),k1自减,如果该位置有地雷,k自减,然后将该格状态设置为0(未翻开);如果该位置没有插旗,用户想撤销(也就是作弊;当然,你也可以删掉这个if,享受当赢家的快乐),直接跳过(continue)
    else if(op=='c'){
    	if(b[x][y]==2){
    		k1--;
    		if(ui[x][y]==9){
    			k--;
    		}
    		b[x][y]=0;
    	}else{
    		continue;
    		}
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 如果所有地雷已标记(扫完),且位置正确,输出You win!
    if(k==cnt && k==k1){
    	cout<<"You win !";
    	scanf("%d");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    最后,这个判断是要循环实现的。贴上我修改过的完整代码:
    #include
    #include "windows.h"
    /*
    #include 
    #include 
    #include 
    #include 
    #include 
    */
    #define random(x) 1+rand()%(x)
    
    using namespace std;
    
    int ui[12][12],b[12][12]; 
    
    int main(){
    	srand(time(NULL)); //random seed
    	//system("color 1B");
    	system("title MineSweeper");
    	int cnt=10;
    	while(cnt){
    		int x=random(10);//a+rand()%b = [a, a+b-1]
    		int y=random(10);
    		if(!ui[x][y]){//if(ui[x][y]==0){
    			ui[x][y]=9;
    			cnt--;
    		}
    	}
    	cnt=10;
    	for(int i=1;i<=cnt;i++){
    		for(int j=1;j<=cnt;j++){
    			int sum=0;
    			if(ui[i][j]!=9){
    				if(ui[i-1][j-1]==9) sum++;//sum=!(a[i-1][j-1]-9)+!(a[i-1][j]-9)...
    				if(ui[i-1][j]==9) sum++;
    				if(ui[i-1][j+1]==9) sum++;
    				if(ui[i][j-1]==9) sum++;
    				if(ui[i][j+1]==9) sum++;
    				if(ui[i+1][j-1]==9) sum++;
    				if(ui[i+1][j]==9) sum++;
    				if(ui[i+1][j+1]==9) sum++;
    				//sum=!(ui[i-1][j-1]-9)+!(ui[i-1][j]-9)+!(ui[i-1][j+1]-9)+!(ui[i][j-1]-9)+!(ui[i][j+1]-9)+!(ui[i+1][j-1]-9)+!(ui[i+1][j]-9)+!(ui[i+1][j+1]-9);
    				ui[i][j]=sum;
    				
    			}
    		}
    	}
    	
    	/*for(int i=1;i<=10;i++){
    		for(int j=1;j<=10;j++){
    			cout<
    	
    	int k=0,k1=0;
    	
    	while(true){
    		/*for(int i=1;i<=cnt1;i++){
    			for(int j=1;j<=cnt1;j++){
    			cout<
    	
    		for(int i=1;i<=cnt;i++){
    			for(int j=1;j<=cnt;j++){
    				if(b[i][j]==0){
    					cout<<"#"<<' ';
    				}else if(b[i][j]==1){
    					cout<<ui[i][j]<<' ';
    				}else if(b[i][j]==2){
    					cout<<"P"<<' ';
    				}
    			}
    			cout<<endl;
    		}
    		char op;
    		cin>>op;
    		int x,y;
    		cin>>x>>y;
    		system("cls");
    		if(op=='q'){
    			if(ui[x][y]==9){
    				cout<<"You died !";
    				scanf("%d");
    				return 0;
    			}
    			else{
    				b[x][y]=1;
    			}
    		}
    		else if(op=='p'){
    			k1++;
    			if(ui[x][y]==9){
    				k++;
    			}	
    			b[x][y]=2;
    		}
    		else if(op=='c'){
    			if(b[x][y]==2){
    				k1--;
    				if(ui[x][y]==9){
    					k--;
    				}
    				b[x][y]=0;
    			}else{
    				continue;
    			}
    		}
    		if(k==cnt && k==k1){
    			cout<<"You win !";
    			scanf("%d");
    			return 0;
    		}
    	}
        return 0;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118

    MineSweeper
    但是,我们现在还有一个问题没有解决:当翻开的数为0,且周围8格有为0的格子(也就是扫雷中的空格)时,我们应该自动显示出来
    下次再解决吧


    Update on 13/2/2024
    鸽了一个学期,终于更新了

    这次增加了一些功能,解决了上次没完成的一个功能(上方斜体字)
    但是还有一个功能(第一次点击不会是雷)没实现,试了很多方案,还问了一个编程很厉害的朋友,这是他给出的解决方案:
    在这里插入图片描述
    我也试过,还是没写出来,在此求大佬指点

    ok,开始正题

    Part. 4 一些更新

    一、自动显示空白

    这个比较简单,只需判断当前点击的方格是否为0,是,判断周围8格是否有空白格(0),是,将其翻开

    if(b[x][y]==1&&ui[x][y]==0){//当前格已翻开且为0
    	if(ui[x-1][y-1]==0) b[x-1][y-1]=1;
    	if(ui[x-1][y]==0) b[x-1][y]=1;
    	if(ui[x-1][y+1]==0) b[x-1][y+1]=1;
    	if(ui[x][y-1]==0) b[x][y-1]=1;
    	if(ui[x][y]==0) b[x][y]=1;
    	if(ui[x][y+1]==0) b[x][y+1]=1;
    	if(ui[x+1][y-1]==0) b[x+1][y-1]=1;
    	if(ui[x+1][y]==0) b[x+1][y]=1;
    	if(ui[x+1][y+1]==0) b[x+1][y+1]=1;	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    加在操作符为“翻开”,也就是 q 的判断普通方块(不为地雷9)分支中,大家不用自己添加,后面会放出完整代码

    二、功能性更新
    1、生命值

    这个功能的增加改善了可玩性
    在程序前面加一个lives的变量,每次踩到雷减一,并判断生命值是否为0,为0,停止游戏即可
    每次生成雷阵后输出生命值
    简单代码:

    int lives=3;
    /*-----中间部分-----*/
    if(ui[x][y]==9){
    	lives--;
    	if(lives==0){
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    2、踩到旗子不掉血&踩到雷自动插旗

    用户插下一个旗子,再次踩到时不会掉血,这个在上个版本没有考虑到
    自动插旗让游戏更加方便,不用用户再次在雷的位置插旗

    if(b[x][y]==2) continue;//anti-blood on a flag
    if(ui[x][y]==9){
    	b[x][y]=2;
    	lives--;
    	if(lives==0){
    		return 0;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    3、上方未完成的功能的替代&非法操作符的判断

    设置一个bool类型的firstClick变量,开始为true,当为首次点击并踩到雷时,先将其设为false,再自动插旗,直接continue(不扣血)

    下方代码为功能性更新中的1,2,3板块的实现:

    bool firstClick=true;
    /*-----中间部分-----*/
    if(op=='q'){
    	if(ui[x][y]==9){//losing
    		if(firstClick){
    			firstClick=false;
    			b[x][y]=2;
    			continue;
    		}
    		if(b[x][y]==2) continue;//anti-blood on a flag
    		lives--;
    		cout<<"oops! you just clicked a mine"<<endl<<endl;
    		if(lives==0){
    			cout<<"You Lose!";
    			return 0;
    		}
    		b[x][y]=2;//auto-flag after one death
    		continue;
    		return 0;
    	}
    	else{
    		firstClick=false;
    		/*.....*/
    	}
    }
    else if(op=='p'){//flagging
    	firstClick=false;
    	/*.....*/
    }
    else{
    	cout<<"invalid operator\n";
    	continue;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    4、自定义地图大小、生命值、雷数

    定义row col mine_sum变量,然后输入,判断数据合法性
    再把后面for循环里面的10,10(上版本预设的地图大小)更改为rowcol即可
    放个判断数据合法性的代码:

    int row,col,lives,mine_sum;
    cin>>row>>col>>lives>>mine_sum;
    while(row>=30||row<=0||col>=30||col<=0||
    	lives>=(row*col)||lives==0||
    	mine_sum>=(row*col)||mine_sum<=0){ //judge input data
    		cout<<"failed to process data, please reset map size/health/mine_sum\n";
    		cin>>row>>col>>lives>>mine_sum;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    这里预设了地图大小不能超过30×30,且行数,列数均大于等于0,生命值必须小于 行数×列数(不然可以试出来),雷数必须小于 行数×列数

    5、Ai功能

    看着像个重头戏,实际上实现很简单
    我将Ai功能的操作符设为a,后面接x,y坐标,表示从(1,1)开始到(x,y)结束的矩阵,Ai会在这个区间内查找 ui[x][y]==9 的点并自动标旗,询问是否输出矩阵,是,就输出从(1,1)开始到(x,y)结束的矩阵(后面实现了用户自定义矩阵)

    另外,由于程序原因,这里的x,y坐标并不是坐标系中的坐标:
    coord
    我加了一个变量用以存储Ai找出的雷(新增雷数),并且做了一个用户自定义矩阵:

    新增变量x1,y1,用以存储结束点的坐标,这样,第一次输入的坐标(如输入a 1 1)将成为起点坐标,接下来判断数据,x1,y1不能小于等于0,也不能大于行数列数

    int x1,y1;
    cin>>x1>>y1;
    while(x1<=0||y1<=0||x1>row||y1>col){
    	cout<<"invalid input\n";
    	cin>>x1>>y1;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    并将搜索范围设置为从x到x1,从y到y1

    这部分的代码如下:

    else if(op=='a'){
    	firstClick=false;
    	cout<<"ai-mode enabled"<<endl;
    	int x1,y1;
    	cout<<"start-point coordinates have been entered\n please enter the end-point coordinates\n";
    	cin>>x1>>y1; //input judging
    	while(x1<=0||y1<=0||x1>row||y1>col){
    		cout<<"invalid input\n";
    		cin>>x1>>y1;
    	}
    	cout<<"looking for mines in: "<<'('<<x<<','<<y<<')'<<'('<<x1<<','<<y1<<')'<<endl; 
    	int new_mine=0;
    	for(int i=x;i<=x1;i++){
    		for(int j=y;j<=y1;j++){
    			if(ui[i][j]==9){
    				k++;
    				k1++;
    				if(b[i][j]!=2){
    					b[i][j]=2;
    					new_mine++;
    				}
    				cout<<"mine position: "<<i<<' '<<j<<endl;
    			}
    		}
    	}
    	cout<<new_mine<<" NEW mine(s) was(were) found\n";
    	cout<<"output array?(y/n)"<<' ';
    	char ans;
    	cin>>ans;
    	if(ans=='y'){
    		cout<<"outputting..."<<'\n';
    		for(int i=x;i<=x1;i++){
    			for(int j=y;j<=y1;j++){
    				cout<<ui[i][j]<<' ';
    			}cout<<endl;
    		}cout<<endl;
    				
    	}
    	cout<<endl;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    游戏的完整代码如下:

    //Made by DingDang 2024-2
    //AntiCheat: Grim v7~
    //UTF-8 Coding
    //Compile Mode:C++11 or higher version System: Win7+ 64x
    #include
    #include "windows.h" //for Windows-Client
    /*
    #include 
    #include 
    #include 
    #include 
    #include 
    */
    #define random(x) 1+rand()%(x)
    /*
    Operator description:
    1, q means to open the grid at the coordinate, continue to enter x and y, means to open the y row x
    2, p indicates that the flag is planted at the coordinates (the coordinates of the user are thunder)
    3, c means cancel the flag at the coordinate (if any)
    4. a indicates ai automatic demining, and the last two coordinates indicate Ai demining range
    */
    using namespace std;
    
    int ui[105][105]={0},b[105][105]={0}; 
    int lives,mine_sum;
    bool firstClick=true;
    int row,col;
    int k=0,k1=0;
    	
    int main(){
    	srand(time(NULL)); //random seed
    	//system("color 1B");
    	system("title MineSweeper");//set window title
    	system("mode con cols=50 lines=30");//set window size
    	system("echo [console]variate initialization succeeded");
    	cout<<"inputting|format:   ]\n";
    	cout<<"example: 10 10 3 10\n";
    	
    	cin>>row>>col>>lives>>mine_sum;
    	
    	while(row>=30||row<=0||col>=30||col<=0||
    		lives>=(row*col)||lives==0||
    		mine_sum>=(row*col)||mine_sum<=0){ //judge input data
    			cout<<"failed to process data, please reset map size/health/mine_sum\n";
    			cin>>row>>col>>lives>>mine_sum;
    	}
    	int tempCalc=mine_sum;
    	while(mine_sum){
    		int x=random(row);//a+rand()%b = [a, a+b-1]
    		int y=random(col);
    		if(!ui[x][y]){//if(ui[x][y]==0){
    			ui[x][y]=9;
    			mine_sum--;
    		}
    	}
    	for(int i=1;i<=row;i++){
    		for(int j=1;j<=col;j++){
    			int sum=0;//sum is the current block number
    			if(ui[i][j]!=9){
    				if(ui[i-1][j-1]==9) sum++;
    				if(ui[i-1][j]==9) sum++;//generate number 
    				if(ui[i-1][j+1]==9) sum++;
    				if(ui[i][j-1]==9) sum++;
    				if(ui[i][j+1]==9) sum++;
    				if(ui[i+1][j-1]==9) sum++;
    				if(ui[i+1][j]==9) sum++;
    				if(ui[i+1][j+1]==9) sum++;
    				//sum=!(ui[i-1][j-1]-9)+!(ui[i-1][j]-9)+!(ui[i-1][j+1]-9)+!(ui[i][j-1]-9)+!(ui[i][j+1]-9)+!(ui[i+1][j-1]-9)+!(ui[i+1][j]-9)+!(ui[i+1][j+1]-9);
    				ui[i][j]=sum;
    				
    			}
    		}
    	}
    	
    	/*for(int i=1;i<=10;i++){
    		for(int j=1;j<=10;j++){
    			cout<
    	
    	while(true){
    		for(int i=1;i<=row;i++){
    			for(int j=1;j<=col;j++){
    				cout<<ui[i][j]<<' '; //output the numbers
    			}
    			cout<<endl;
    		}cout<<endl; 
    	//for debug
    	
    		for(int i=1;i<=row;i++){
    			for(int j=1;j<=col;j++){ //state of b[k][m]: =0 closed | =1 opened | =2 flagged
    				if(b[i][j]==0){
    					cout<<"#"<<' ';
    				}else if(b[i][j]==1){
    					cout<<ui[i][j]<<' ';
    				}else if(b[i][j]==2){
    					cout<<"P"<<' ';
    				}
    			}cout<<endl;
    			//Judge the status of the grid (opened, closed, flag 
    		}
    		cout<<"Lives: "<<lives<<'\n';
    		char op;//input operator
    		cin>>op;
    		int x,y;//input coord
    		cin>>x>>y;
    		while(x>row||y>col){
    			cout<<"invalid input number"<<endl;
    			cin>>op>>x>>y;continue;
    		}
    		
    		if(op=='q'){
    			if(ui[x][y]==9){//losing
    				system("cls");
    				if(firstClick){
    					firstClick=false;
    					b[x][y]=2;
    					continue;
    				}
    				if(b[x][y]==2) continue;//anti-blood on a flag
    				lives--;
    				system("color 47");
    				cout<<"oops! you just clicked a mine"<<endl<<endl;
    				Sleep(200);
    				system("color 07");
    				if(lives==0){
    					cout<<"You Lose!";
    					return 0;
    				}
    				b[x][y]=2;//auto-flag after one death
    				continue;
    				scanf("%d");
    				return 0;
    			}
    			else{
    				firstClick=false;
    				system("cls");
    				b[x][y]=1;
    				if(b[x][y]==1&&ui[x][y]==0){
    					if(ui[x-1][y-1]==0) b[x-1][y-1]=1;
    					if(ui[x-1][y]==0) b[x-1][y]=1;
    					if(ui[x-1][y+1]==0) b[x-1][y+1]=1;
    					if(ui[x][y-1]==0) b[x][y-1]=1;
    					if(ui[x][y]==0) b[x][y]=1;
    					if(ui[x][y+1]==0) b[x][y+1]=1;
    					if(ui[x+1][y-1]==0) b[x+1][y-1]=1;
    					if(ui[x+1][y]==0) b[x+1][y]=1;
    					if(ui[x+1][y+1]==0) b[x+1][y+1]=1;	
    				}
    				//Determine if there are any blank squares around, and if there are, open them automatically
    			}
    		}
    		else if(op=='p'){//flagging
    			firstClick=false; //k stores the correct location of the mine (objective),
    			  			      //k1 is the user's marking location (subjective); Function: Store the total number of mines
    			system("cls"); 
    			k1++;
    		 	if(ui[x][y]==9){
    				k++;
    			}	
    			b[x][y]=2;
    		}
    		else if(op=='c'){//unflagging 
    			system("cls");
    			if(b[x][y]==2){
    				k1--;
    				if(ui[x][y]==9){
    					k--;
    				}
    			b[x][y]=0;
    			}else{
    				system("cls");
    				continue;
    			}
    		}
    		else if(op=='a'){
    			firstClick=false;
    			cout<<"ai-mode enabled"<<endl;
    			int x1,y1;
    			cout<<"start-point coordinates have been entered\n please enter the end-point coordinates\n";
    			cin>>x1>>y1; //input judging
    			while(x1<=0||y1<=0||x1>row||y1>col){
    				cout<<"invalid input\n";
    				cin>>x1>>y1;
    			}
    			cout<<"looking for mines in: "<<'('<<x<<','<<y<<')'<<'('<<x1<<','<<y1<<')'<<endl; 
    			int new_mine=0;
    			for(int i=x;i<=x1;i++){
    				for(int j=y;j<=y1;j++){
    					if(ui[i][j]==9){
    						k++;
    						k1++;
    						if(b[i][j]!=2){
    							b[i][j]=2;
    							new_mine++;
    						}
    						cout<<"mine position: "<<i<<' '<<j<<endl;
    						Sleep(200);
    					}
    				}
    			}
    			cout<<new_mine<<" NEW mine(s) was(were) found\n";
    			cout<<"output array?(y/n)"<<' ';
    			char ans;
    			cin>>ans;
    			if(ans=='y'){
    				cout<<"outputting..."<<'\n';
    				Sleep(1000);
    				system("cls");
    				for(int i=x;i<=x1;i++){
    					for(int j=y;j<=y1;j++){
    						cout<<ui[i][j]<<' ';
    					}cout<<endl;
    				}cout<<endl;
    				
    			}
    			Sleep(1500);
    			cout<<endl;
    		}else{
    			cout<<"invalid operator\n";continue;
    		}
    		if(k==tempCalc && k==k1){//winning
    			cout<<"You Win !";
    			for(int i=1;i<=5;i++){
    				system("color 1a");
    				Sleep(100);
    				system("color 2b");
    				Sleep(100);
    				system("color 3c");
    				Sleep(100);
    				system("color 4d");
    				Sleep(100);
    				system("color 5e");
    			}
    			system("color 07");
    			scanf("%d");
    		}
    	}
        return 0;
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241

    如果有任何bug,请在评论区指出,感谢各位!

    玩法介绍

    • 程序会提示输入: ,分别代表行数,列数(均小于等于30),生命值(小于行数×列数),雷数(小于行数×列数),有数据判断
    • 操作符 q p c a +X,Y分别代表:在 (x,y) 翻开/插旗/撤销插旗/使用Ai,以下为详细介绍
    • q x y: 翻开位于 (x,y) 的方格,如果这是你的首次点击并且踩到雷,程序会自动帮你插旗并不扣血,如果不是,扣一滴血,自动插旗;如果当前格为空白(0),自动帮你翻开周围8格内所有的空白
    • p x y: 在 (x,y) 插旗,表示你认为这里为雷,旗子没有数量限制,但是只有插对地方且数量正确才会胜利
    • c x y: 撤销在 (x,y) 的插旗,如果该位置没有旗则不会操作
    • a x y:将Ai排雷的起始点设置为 (x,y) ,再输入 (x1,y1) (具体坐标说明见下方补充部分),Ai将在这个范围内自动排雷,并为你插旗;你也可以选择输出这个范围内的数字

    Github开源地址:这里,上不去的同学试试FastGithub

    感谢胡老师在我coding过程中给予的帮助和指导
    好了,这就是本篇文章的全部内容(截止到2024/2/15),欢迎各位大佬指点!我们下期再见

  • 相关阅读:
    LNMP架构介绍及配置--部署Discuz社区论坛与wordpress博客
    [贪心算法]java解决背包问题
    glb数据介绍
    第三十四篇 生命周期 - 易理解
    VS2019 添加afxdisp.h文件后提示CString不明确 解决方法
    Java方法案例
    html常用的标签
    React使用哲学
    summer time~ schedule
    工学云打卡签到自动实现关于异地时定位的问题解决|蘑菇钉
  • 原文地址:https://blog.csdn.net/weixin_45122104/article/details/128066651