1. 程式人生 > >沒事寫點啥(一)——C++掃雷

沒事寫點啥(一)——C++掃雷



先說明一下,本來這個文章我在新浪部落格上發過一次,但是不知道最近怎麼回事新浪總登不上去= =再加CSDN是專門為IT類服務的(我剛剛註冊的號,給我的印象是這樣)就在這再寫一次吧,這次我會盡量寫的詳細一點。

鄙人呢是個小白,會的不多也就是C++了,而且目前還是命令列版本的,圖形部分還沒學會,有的地方還很麻煩,總之不管怎麼說吧算是稀裡糊塗的寫出來了,發在這和大家交流一下,恭請各路大神賜教~~

掃雷,作為一款小遊戲可以說非常成功,一段時間內風靡全世界,也是您在空餘時間不多時用來消遣的比較好的一個選擇(怎麼像打廣告一樣= =)相信大家對它的規則應該不陌生吧,不過呢我在這還是再囉嗦一會。

1.點選一個方格,如果翻開是地雷的話則失敗

2.如果翻開不是地雷的話,若為非零數字則表示周圍若干個格子中地雷的個數(中間部分則為周圍8個格子,邊緣非頂點處則為5個格子,頂點處為3個格子)

3.若為0則會向外拓展

4.若玩家可以確定一個地方為地雷則可以對此進行標記

5.當非地雷格子被全部翻開時則獲勝

掃雷的演算法裡,我想最複雜的地方我不細說大家也應該知道,就是空格的處理。尤其是遇到一大片空地時,簡直就是多米諾效應。這種情況下,我想最好的方法恐怕也就是遞迴了。

我這裡寫的是40/16*16的掃雷,也就是16*16的地圖40個地雷,屬於中等難度。想調的話,改動幾個地方的數即可

無心之舉,難免有紕漏之處,各路大神若有改進之言,在下洗耳恭聽。

好了廢話不多說了,上程式碼,我會盡量把註釋加全。

#include<iostream>
#include<cstdlib>
#include<ctime>
#include<conio.h>//程式碼中的getche()在這個標頭檔案裡,只取一個字元
#include<windows.h>//程式碼中用到了部分windowsAPI函式
#define LANDMINE 16
using namespace std;
int field[16][16],show[16][16]={{0}};//field陣列是指整個地圖的實際情況,show陣列是指地圖的探測情況,0-未翻開且未標記,1-翻開,2-標記
char answer[16][16];//正確答案,當用戶失敗時輸出
int mine[40];//mine陣列用於儲存地雷位置,具體為16*行數+列數
void gotoxy(int x,int y)/*該函式用於移動游標,其中SetConsoleCursorPosition函式請看這裡<a target=_blank href="http://baike.sogou.com/v73182107.htm?fromTitle=SetConsoleCursorPosition">http://baike.sogou.com/v73182107.htm?fromTitle=SetConsoleCursorPosition</a> */
{
	COORD pos;
	pos.X=2*x;
	pos.Y=y;
	SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE),pos);
}
void color(int a)/*該函式用於改變輸出字元顏色,其中SetConsoleTextAttribute函式請看這裡<a target=_blank href="http://baike.sogou.com/v72432554.htm?fromTitle=SetConsoleTextAttribute">http://baike.sogou.com/v72432554.htm?fromTitle=SetConsoleTextAttribute</a> */
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),a);
}
int once(int mine[],int key,int j)//佈雷時用到,觀看取到隨機數之前是否出現過,出現過則返回0,否則為1
{
	int i;
	for(i=0;i<j;i++)
	{
		if(mine[i]==key)
		return 0;
	}
	return 1;
}
int count(int field[][16],int i,int j)/*這個函式我知道我寫的很麻煩= =用於得到佈雷後非雷格子對應的數字,由於中間部分、邊緣和頂點有一些很小的差別,我這就分了好多情況……*/
{
	int d=0;
	int k,l;
	if(i>0&&i<15&&j>0&&j<15)
	{
		for(k=i-1;k<=i+1;k++)
		{
			for(l=j-1;l<=j+1;l++)
			{
				if(field[k][l]==LANDMINE)
				d++;
			}
		}
	}
	else if(i==0&&j>0&&j<15)
	{
		for(k=i;k<=i+1;k++)
		{
			for(l=j-1;l<=j+1;l++)
			{
				if(field[k][l]==LANDMINE)
				d++;
			}
		}
	}
	else if(i==15&&j>0&&j<15)
	{
		for(k=i-1;k<=i;k++)
		{
			for(l=j-1;l<=j+1;l++)
			{
				if(field[k][l]==LANDMINE)
				d++;
			}
		}
	}
	else if(j==0&&i>0&&i<15)
	{
		for(k=i-1;k<=i+1;k++)
		{
			for(l=j;l<=j+1;l++)
			{
				if(field[k][l]==LANDMINE)
				d++;
			}
		}
	}
	else if(j==15&&i>0&&i<15)
	{
		for(k=i-1;k<=i+1;k++)
		{
			for(l=j-1;l<=j;l++)
			{
				if(field[k][l]==LANDMINE)
				d++;
			}
		}
	}
	else if(i==0&&j==0)
	{
		for(k=0;k<=1;k++)
		{
			for(l=0;l<=1;l++)
			{
				if(field[k][l]==LANDMINE)
				d++;
			}
		}
	}
	else if(i==0&&j==15)
	{
		for(k=0;k<=1;k++)
		{
			for(l=14;l<=15;l++)
			{
				if(field[k][l]==LANDMINE)
				d++;
			}
		}
	}
	else if(i==15&&j==0)
	{
		for(k=14;k<=15;k++)
		{
			for(l=0;l<=1;l++)
			{
				if(field[k][l]==LANDMINE)
				d++;
			}
		}
	}
	else if(i==15&&j==15)
	{
		for(k=14;k<=15;k++)
		{
			for(l=14;l<=15;l++)
			{
				if(field[k][l]==LANDMINE)
				d++;
			}
		}
	}
	return d;
}
void Arrange()//用於得到這個地圖的情況
{
	int i,j;
	for(i=0;i<16;i++)
	{
		for(j=0;j<16;j++)
		field[i][j]=0;
	}
	srand((unsigned)time(NULL)); 
	for(i=0;i<40;i++)
	{
		int t=rand()%256;
		while(once(mine,t,i)==0)
		t=rand()%256;
		mine[i]=t;//每取一個隨機數進行一次判定,如果前面出現過就重新取,否則就取該數
	}
	for(i=0;i<40;i++)
	field[mine[i]/16][mine[i]%16]=LANDMINE;
	for(i=0;i<16;i++)
	{
		for(j=0;j<16;j++)
		{
			if(field[i][j]!=LANDMINE)
			field[i][j]=count(field,i,j);
		}
	}
}
void an(int field[][16])//得到答案矩陣
{
	int i,j;
	for(i=0;i<16;i++)
	{
		for(j=0;j<16;j++)
		{
			if(field[i][j]==LANDMINE)
			answer[i][j]='L';
			else
			answer[i][j]=(char)(field[i][j]+48);
		}
	}
}
void point(int i,int j)//點開格子時的處理
{
	int k,l;
	if(show[i][j]==2)
	show[i][j]==2;
	else if(field[i][j]==LANDMINE)//若觸雷則直接gg,啊不對,應該叫顯示失敗,並輸出正確答案,然後直接退出
	{
		cout<<"不好意思,你輸了!下次走運!"<<endl;
		an(field);
		cout<<"正確答案為:"<<endl;
		for(int p=0;p<16;p++)
		{
			for(int q=0;q<16;q++)
			{
				if(answer[p][q]=='L')
				{
					color(12);
					cout<<"●";
				}
				else
				{
					color(10);
					cout<<answer[p][q]<<" ";
				}
			}
			cout<<endl;
		} 
		color(15);
		system("pause");
		exit(0);
	}
	else if(field[i][j]==0)//若點開為空格子則開始遞迴演算法
	{
		show[i][j]=1;
		if(i>0&&i<15&&j>0&&j<15)
		{
			for(k=i-1;k<=i+1;k++)
			{
				for(l=j-1;l<=j+1;l++)
				{
					if(show[k][l]==0) 
					{
						if(field[k][l]!=LANDMINE)
						{
							show[k][l]=1;
							point(k,l);
						}
					}
				}
			}
		}//周圍未點開的非雷格子全部點開並遞迴,下面同理,只是不同位置的不同情況而已
		else if(i==0&&j>0&&j<15)
		{
			for(k=i;k<=i+1;k++)
			{
				for(l=j-1;l<=j+1;l++)
				{
					if(show[k][l]==0) 
					{
						if(field[k][l]!=LANDMINE)
						{
							show[k][l]=1;
							point(k,l);
						}
					}
				}
			}
		}
		else if(i==15&&j>0&&j<15)
		{
			for(k=i-1;k<=i;k++)
			{
				for(l=j-1;l<=j+1;l++)
				{
					if(show[k][l]==0) 
					{
						if(field[k][l]!=LANDMINE)
						{
							show[k][l]=1;
							point(k,l);
						}
					}
				}
			}
		}
		else if(j==0&&i>0&&i<15)
		{
			for(k=i-1;k<=i+1;k++)
			{
				for(l=j;l<=j+1;l++)
				{
					if(show[k][l]==0) 
					{
						if(field[k][l]!=LANDMINE)
						{
							show[k][l]=1;
							point(k,l);
						}
					}
				}
			}
		}
		else if(j==15&&i>0&&i<15)
		{
			for(k=i-1;k<=i+1;k++)
			{
				for(l=j-1;l<=j;l++)
				{
					if(show[k][l]==0) 
					{
						if(field[k][l]!=LANDMINE)
						{
							show[k][l]=1;
							point(k,l);
						}
					}
				}
			}
		}
		else if(i==0&&j==0)
		{
			for(k=0;k<=1;k++)
			{
				for(l=0;l<=1;l++)
				{
					if(show[k][l]==0) 
					{
						if(field[k][l]!=LANDMINE)
						{
							show[k][l]=1;
							point(k,l);
						}
					}
				}
			}
		}
		else if(i==0&&j==15)
		{
			for(k=0;k<=1;k++)
			{
				for(l=14;l<=15;l++)
				{
					if(show[k][l]==0) 
					{
						if(field[k][l]!=LANDMINE)
						{
							show[k][l]=1;
							point(k,l);
						}
					}
				}
			}
		}
		else if(i==15&&j==0)
		{
			for(k=14;k<=15;k++)
			{
				for(l=0;l<=1;l++)
				{
					if(show[k][l]==0) 
					{
						if(field[k][l]!=LANDMINE)
						{
							show[k][l]=1;
							point(k,l);
						}
					}
				}
			}
		}
		else if(i==15&&j==15)
		{
			for(k=14;k<=15;k++)
			{
				for(l=14;l<=15;l++)
				{
					if(show[k][l]==0) 
					{
						if(field[k][l]!=LANDMINE)
						{
							show[k][l]=1;
							point(k,l);
						}
					}
				}
			}
		}
	}
	else
	show[i][j]=1;//若為非零非雷格子則直接點開該格子
}
int judge(int mind[],int show[][16])//判定勝利函式
{
	int i,j;
	int ans=0,flag=0;
	for(i=0;i<40;i++)
	{
		if(show[mind[i]/16][mind[i]%16]==2)
		ans++;
	}
	for(i=0;i<16;i++)
	{
		for(j=0;j<16;j++)
		{
			if(show[i][j]==2)
			flag++;
		}
	}
	if(ans==40&&flag==40)
	return 1;
	else
	return 0;//中心思想為若標記的個數與地雷的個數相等則返回1,否則返回0
}
void draw(int show[][16])//該函式用於描繪整個地圖
{
	gotoxy(1,0);
	color(10);
	cout<<'A'<<" "<<'B'<<" "<<'C'<<" "<<'D'<<" "<<'E'<<" "<<'F'<<" "<<'G'<<" "<<'H'<<" "<<'I'<<" "<<'J'<<" "<<'K'<<" "<<'L'<<" "<<'M'<<" "<<'N'<<" "<<'O'<<" "<<'P'<<" ";
	gotoxy(0,1);
	int i,j;
	for(i=0;i<16;i++)
	{
		gotoxy(0,i+1);
		color(10);
		cout<<(char)(i+65)<<" ";
		for(j=0;j<16;j++)
		{
			if(show[i][j]==0)
			{
				color(11);
				cout<<"■";
			}
			else if(show[i][j]==1)
			{
				color(14);
				cout<<field[i][j]<<" ";
			}
			else if(show[i][j]==2)
			{
				color(12);
				cout<<"★";
			}
		}
	}
}
int main()
{
	Arrange();
	char ch,c;
	int row=8,col=8;
	int i,j;
	PlaySound("馬克西姆-克羅埃西亞狂想曲.wav",NULL,SND_FILENAME|SND_ASYNC|SND_LOOP);/*PlaySound函式用於播放音訊,詳情點選這裡<a target=_blank href="http://baike.sogou.com/v10930259.htm?fromTitle=playsound">http://baike.sogou.com/v10930259.htm?fromTitle=playsound</a> */
	MessageBox(NULL,"製作人:天妒\n背景音樂:馬克西姆-克羅埃西亞狂想曲","掃雷 V1.0",MB_OK);
	MessageBox(NULL,"按F或L鍵來決定標記地雷還是直接探測\n偶數次標記地雷取消標記\n之後WSAD四鍵來移動格子,其他鍵確定格子位置\n地雷全部標記正確則獲勝\n注意區分大小寫!\n祝你成功!","遊戲規則",MB_OK);/*MessageBox函式用於顯示對話方塊,詳情點選這裡<a target=_blank href="http://baike.sogou.com/v7685382.htm?fromTitle=messagebox">http://baike.sogou.com/v7685382.htm?fromTitle=messagebox</a> */
	clock_t start,finish;//用於計時
	long long totaltime;
	start=clock();
	while(judge(mine,show)!=1)
	{   
		gotoxy(0,0);
		draw(show);
		color(15);
		gotoxy(20,3);	
		cout<<"標記地雷還是直接探測?";
		gotoxy(20,4);
		cout<<"F--標記地雷";
		gotoxy(20,5);
		cout<<"L--直接探測";
		gotoxy(20,6);
		ch=getche();
		while(ch!='F'&&ch!='L')
		{
			gotoxy(20,7);
			cout<<"輸入有誤!";
			gotoxy(20,6);
			ch=getche();
		}
		if(ch=='F')
		{
			gotoxy(20,7);
			cout<<"輸入無誤!"; 
			gotoxy(20,10);
			cout<<"按W A S D移動游標,其他鍵決定格子"; 
			gotoxy(0,18);
			c=getche();
			while(c=='W'||c=='A'||c=='S'||c=='D')
			{
				switch(c)//WASD控制選擇的格子位置
				{
					case 'W':row=row-1<0?0:row-1;break;
					case 'A':col=col-1<0?0:col-1;break;
					case 'S':row=row+1>15?15:row+1;break;
					case 'D':col=col+1>15?15:col+1;break;
				}
				gotoxy(20,15);
			    cout<<"當前格子位置:"<<(char)(row+65)<<" "<<(char)(col+65);
				gotoxy(0,18);
				c=getche();
			}
			show[row][col]=2-show[row][col];
			if(judge(mine,show)==1)
			{
				cout<<"恭喜您!您獲勝了!"<<endl;
				gotoxy(0,20);
				finish=clock();
				totaltime=finish-start;
				cout<<"您本次掃雷用時:"<<(long long)(totaltime/CLOCKS_PER_SEC)<<"s"<<endl;//勝利則顯示用時
				system("pause");
				exit(0);
			}
		}
		else if(ch=='L')
		{
			gotoxy(20,7);
			cout<<"輸入無誤!"; 
			gotoxy(20,10);
			cout<<"按W A S D移動游標,其他鍵決定格子"; 
			gotoxy(20,15);
			cout<<"當前格子位置:"<<(char)(row+65)<<" "<<(char)(col+65);
			gotoxy(0,18);
			c=getche();
			while(c=='W'||c=='A'||c=='S'||c=='D')
			{
				switch(c)
				{
					case 'W':row=row-1<0?0:row-1;break;
					case 'A':col=col-1<0?0:col-1;break;
					case 'S':row=row+1>15?15:row+1;break;
					case 'D':col=col+1>15?15:col+1;break;
				}
				gotoxy(20,15);
			    cout<<"當前格子位置:"<<(char)(row+65)<<" "<<(char)(col+65);
				gotoxy(0,18);
				c=getche();
			}
			point(row,col);
		}
	}
	system("pause");
	return 0;
}