(詳解)用C語言實現一個能夠連續展開的掃雷。

q302989778發表於2018-05-08

·功能介紹

我們實現一個掃雷,那麼必須具有其基本的功能,如下圖:


那麼相比較一般的用C實現的掃雷,我們需要新增如下的三個功能。

防止第一次被炸死;標記雷;連續展開。

·程式碼實現以及解釋

一、主函式的實現以及掃雷的構思框架(test.c)

int main()
{
	int input=0;
	do
	{
		menu();
		printf("請選擇\n");
		scanf("%d",&input);
		switch(input)
		{
		case 1:
			printf("請開始遊戲\n");
			game();
			break;
		case 0:
			printf("退出遊戲\n");
			break;
		default:
			printf("請重新選擇\n");
			break;
		}
	}
	while(input);
	return 0;
}

·首先對於主函式我們需要一個選單介面以便於讓我們進行選擇,上面我們使用了switch語句讓玩家進行選擇。

·就像大家看到的,裡面還包括了一個game();以及一個menu();前者是我們進行掃雷所需要的函式,後者是我們的一個選單介面,效果如下圖:


void menu()
{
	printf("*****************************************\n");
	printf("***************1.開始遊戲****************\n");
	printf("************   0.退出遊戲   *************\n");
	printf("*****************************************\n");
}

·這個menu函式相信大家都比較熟悉,再次就不多作說明了。

void game()
{
    char mine[ROW][COL];
    char show[ROW][COL];
    init_board(mine, show);
    display_board(show);
    set_board(mine);
    show_board(mine);
    my_step(mine,show);
}

·這是我們的game函式,下面我們來說明一下我們掃雷的遊戲函式需要什麼?

·mineshow 首先我們定義了兩個9x9大小棋盤分別為mine以及show,其中一個為真實的棋盤用於系統的判定,我們把其中的引數設定為1和0,其中的1為雷。另外一個棋盤為展示棋盤為我們使用者在掃雷的時候看到的棋盤,展開的地方我們用數字表示,隱藏的部分我們用字元'*'來表示。

注意:我們用二維陣列來實現棋盤,row和rol分別表示行和列。

·init_board(); 接著,我們使用了一個初始化函式即init_board();,這個函式的主要功能就是在遊戲開始前將我們的兩個棋盤分別初始化。就像上面我們提到的一個初始化為1和0的棋盤,一個初始為全部是*的棋盤。

·display_board(); 我們實現了棋盤的初始化自然要讓我們看到棋盤的樣子才能進行掃雷,因此我們使用了這個函式。注意:展示棋盤僅僅展示我們的show這個棋盤。

·show_board(); 這個是我們在實現程式時為了看到真實棋盤的樣子所寫的一個函式,用於展示真實的棋盤給使用者看到。

·my_step(); 這個函式就是玩家行動的意思,也就是掃雷已經開始啦!

二、掃雷功能的實現(game.c)

·在開始之前我們需要說明的是,由於判定以及展開的時候需要對周圍八個點進行處理,在處理靠邊的點的時候容易發生越界,因此我們將真實棋盤設定為11x11,但是我們所看到的僅僅是一個9x9的棋盤,其餘的點我們全部設定為0,這樣就避免了錯誤。

void init_board(char mine[ROW][COL], char show[ROW][COL])
{
	int i = 0;
    int j = 0;
    for (i = 1; i < ROW - 1; i++)
    {
        for (j = 1; j < COL - 1; j++)
        {
            mine[i][j] = '0';
        }
    }
    for (i = 1; i < ROW - 1; i++)
    {
        for (j = 1; j < COL - 1; j++)
        {
            show [i][j] = '*';
        }
    }
}

·這邊讓我們的函式分別初始化,真實的函式全部初始為0,放雷放在我們後面的函式進行。

void display_board(char show[ROW][COL])
{
    int i, j;
	for(i=0; i<ROW-1; i++)
	{
		printf("%d ",i);
	}
	for(i=1; i<ROW-1; i++)//僅僅展示9x9的棋盤
	{
		printf("\n");
		printf("%d ",i);
		for(j=1; j<COL-1; j++)
		{
			printf("%c ",show[i][j]);
		}
	}
	printf("\n");
}

·在這個函式中除了展示我們的show陣列之外,我們也給周圍的行和列表明的數字以便於掃雷的時候輸入座標。注意:我們僅僅只展示9x9的棋盤。實現後的效果如下圖:


void set_board(char mine[ROW][COL])
{
	int i,j;
	int count=10;
	while(count)
	{
		i=rand()%8+1;
		j=rand()%8+1;
		if(mine[i][j]=='0')
		{
			mine[i][j]='1';
			count--;
		}
	}
}

·我們這邊使用了一個產生隨機值的語句rand();,當然我們需要引用相關的標頭檔案:

#include<time.h>
#include<stdlib.h>

這邊需要注意的是我們的座標設定的是1~9,所以rand()%9產生0~9就是不對的。當然你可能會考慮到如果重複位置設雷怎麼辦?因此我們使用了一個if語句進行判定。

void show_board(char mine[ROW][COL])
{
	int i, j;
	for(i=0; i<ROW-1; i++)
	{
		printf("%d ",i);
	}
	for(i=1; i<ROW-1; i++)
	{
		printf("\n");
		printf("%d ",i);
		for(j=1; j<COL-1; j++)
		{
			printf("%c ",mine[i][j]);
		}
	}
	printf("\n");
}

·這是展示真實棋盤的函式,同上面的display_board();是一樣的。

int get_mine(char mine[ROW][COL] ,int i,int j)
{
	int count=0;
	if(mine[i-1][j]=='1')
	{
		count++;
	}
	if(mine[i-1][j-1]=='1')
	{
		count++;
	}
	if(mine[i-1][j+1]=='1')
	{
		count++;
	}
	if(mine[i][j+1]=='1')
	{
		count++;
	}
	if(mine[i][j-1]=='1')
	{
		count++;
	}
	if(mine[i+1][j-1]=='1')
	{
		count++;
	}
	if(mine[i+1][j]=='1')
	{
		count++;
	}
	if(mine[i+1][j+1]=='1')

	{
		count++;
	}
	return count;
}

·這邊我們寫了一個新的函式叫做get_mine函式

·這個函式的主要作用是獲取你輸入座標(掃雷位置)周圍八個點有雷的個數,返回一個int值。通過這個函式,我們就可以實現掃雷周圍顯示雷個數的功能了。

void safe_board(char mine[ROW][COL],int i,int j)
{
	int count=1;
	while(count)
	{
		if(mine[i][j]='1')
		{
			int m=rand()%8+1;
			int n=rand()%8+1;
			mine[i][j]='0';
			mine[m][n]='1';
			count--;
		}
		else
			count--;
	}
}

·這邊是一個安全檢查的函式,這個函式的作用就是當你第一輸入位置掃到雷的時候我們可以將這個雷移到其他的地方。這邊我們定義了一個count,為了防止移走的雷在隨機設定的時候設定到另外一個雷上。

void menu_board()
{
	printf("**** 1.掃雷    0.標雷與撤銷 ***\n");
}

·這是我們在掃雷之前的一個選單選項,可以讓我們掃雷或者是進行標記。

void open_board(char mine[ROW][COL],char show[ROW][COL],int i,int j)
{
	if(mine[i][j]=='0'&&i>=0&&j>=0&&show[i][j]=='*')
	{
		show[i][j]=get_mine(mine,i,j)+'0';
	}
	if(mine[i][j-1]=='0'&&i>=0&&j-1>=0&&show[i][j-1]=='*')
	{
		show[i][j-1]=get_mine(mine,i,j-1)+'0';
		if(get_mine(mine,i,j-1)==0)
		{
			open_board(mine,show,i,j-1);
		}
	}
	if(mine[i][j+1]=='0'&&i>=0&&j+1>=0&&show[i][j+1]=='*')
	{
		show[i][j+1]=get_mine(mine,i,j+1)+'0';
		if(get_mine(mine,i,j+1)==0)
		{
			open_board(mine,show,i,j+1);
		}
	}
	if(mine[i-1][j]=='0'&&i-1>=0&&j>=0&&show[i-1][j]=='*')
	{
		show[i-1][j]=get_mine(mine,i-1,j)+'0';
		if(get_mine(mine,i-1,j)==0)
		{
			open_board(mine,show,i-1,j);
		}
	}
	if(mine[i-1][j-1]=='0'&&i-1>=0&&j-1>=0&&show[i-1][j-1]=='*')
	{
		show[i-1][j-1]=get_mine(mine,i-1,j-1)+'0';
		if(get_mine(mine,i-1,j-1)==0)
		{
			open_board(mine,show,i-1,j-1);
		}
	}
	if(mine[i-1][j+1]=='0'&&i-1>=0&&j+1>=0&&show[i-1][j+1]=='*')
	{
		show[i-1][j+1]=get_mine(mine,i-1,j+1)+'0';
		if(get_mine(mine,i-1,j+1)==0)
		{
			open_board(mine,show,i-1,j+1);
		}
	}
	if(mine[i+1][j+1]=='0'&&i+1>=0&&j+1>=0&&show[i+1][j+1]=='*')
	{
		show[i+1][j+1]=get_mine(mine,i+1,j+1)+'0';
		if(get_mine(mine,i+1,j+1)==0)
		{
			open_board(mine,show,i+1,j+1);
		}
	}
	if(mine[i+1][j]=='0'&&i+1>=0&&j>=0&&show[i+1][j]=='*')
	{
		show[i+1][j]=get_mine(mine,i+1,j)+'0';
		if(get_mine(mine,i+1,j)==0)
		{
			open_board(mine,show,i+1,j);
		}
	}
	if(mine[i+1][j-1]=='0'&&i+1>=0&&j-1>=0&&show[i+1][j-1]=='*')
	{
		show[i+1][j-1]=get_mine(mine,i+1,j-1)+'0';
		if(get_mine(mine,i+1,j-1)==0)
		{
			open_board(mine,show,i+1,j-1);
		}
	}
	
}

如上面程式碼,展開函式是我們的重點。

·這個函式包括了兩個部分,一個是展示我們周圍的雷數,另外一個是展開周圍,繼續展示雷數。


·展開就是上圖的功能,當你輸入一個座標的時候,判定周圍有沒有雷,有雷的顯示一個數字,沒有雷的話顯示0。當繼續遇到0的時候將0直接展開,數字的話依然顯示數字。最後迴圈,當週圍全部為大於0的數字時停止展開。

·if中的條件:接著讓我們先來看if中的條件吧。首先i>=0&&j>=0判斷座標的位置,防止無限展開形成越界。第二,(mine[i][j]=='0',判定這個位置是否有雷。第三,show[i][j]=='*',判定這個位置是否已經展開。

·數字的顯示:呼叫我們剛才的get_mine();函式,通過+'0'來將int轉成字元數字便於顯示。

·遞迴:在很多的相關程式碼中僅僅顯示周圍八個點的值,而我們需要實現連續展開,條件中如果為0,改變所傳的引數,呼叫open_board();繼續展開,知道周圍全部的數字不為0時。

void my_step(char mine[ROW][COL], char show[ROW][COL])
{
    int i = 0;
    int j = 0;
    int count=71;
	int input=0;
    while (count)
    {
		do
		{
			menu_board();
			printf("請選擇\n");
			scanf("%d",&input);
			switch(input)
			{
			case 1:
				printf("請輸入座標\n");
				scanf("%d%d", &i,&j);
				if(count==71)
				{
					safe_board(mine,i,j);
				}
				if (mine[i][j] == '1')
				{
					printf("踩到雷了:\n");
					show_board(mine);
					return;
				}
				else
				{
					open_board(mine,show,i,j);
					display_board(show);
					count--;
				}
				break;
			case 0:
				printf("輸入座標\n");
				scanf("%d%d", &i,&j);
				if(show[i][j]=='*')
				{
					show[i][j]='!';
				}
				else
					show[i][j]='*';
				display_board(show);
				break;
			default:
				printf("請重新選擇\n");
				break;
			}
			break;
		}while(input);
    }
    printf("掃雷成功\n");
}

·這是我們game中的最重要的函式。

·我們定義了一個變數為count,count即為安全的位置,一共為71個,每一次掃雷完成都會--,當count為0的時候,我們跳出do while,顯示掃雷成功。

·我們通過switch來實現開始選擇標雷還是掃雷,如果輸入為0,就是簡單的將show中的*改為!,以此來標識是否為雷。

·那麼safe_check();函式是如何呼叫的呢。我們只需要知道第一個踩的是否為雷就行了,這邊我們直接判斷count的值來實現,如果為count==71那麼意思就是我們猜到雷了,而一旦轉移那麼我們就相當於掃出了一個安全區域,那麼count--就小於71這個數值,這樣就巧妙的將safe_check();只實現了一次,以防出現多次呼叫了。

三、標頭檔案的引用(game.h)

#include<stdio.h>
#include<time.h>
#include<stdlib.h>

#define ROW 11
#define COL 11


void init_board(char mine[ROW][COL], char show[ROW][COL]);//初始化	
void display_board(char show[ROW][COL]);//展示棋盤
void set_board(char mine[ROW][COL]);//放雷
void show_board(char mine[ROW][COL]);//展示棋盤
void my_step(char mine[ROW][COL], char show[ROW][COL]);//玩家行動
int get_mine(char mine[ROW][COL]);//探知雷數
void open_board(char mine[ROW][COL],char show[ROW][COL],int i,int j);//展開
void safe_board(char mine[ROW][COL],int i,int j);//第一步安全檢查
void menu_board();//第二個選單

·標頭檔案中,我們需要定義所有的標頭檔案以及引用的各種函式,這邊新增了相關的註釋方便大家觀看。

·其中的define定義了行和列的大小,我們可以直接通過改變ROW和COL的值來改變難度,當然你要是願意的話,寫一個選擇難度的函式也是可以的。

·程式碼展示

最後,直接貼出我們的三個檔案的程式碼,分別為test.c、game.c以及game.h

game.h

#include<stdio.h>
#include<time.h>
#include<stdlib.h>

#define ROW 11
#define COL 11


void init_board(char mine[ROW][COL], char show[ROW][COL]);//初始化	
void display_board(char show[ROW][COL]);//展示棋盤
void set_board(char mine[ROW][COL]);//放雷
void show_board(char mine[ROW][COL]);//展示棋盤
void my_step(char mine[ROW][COL], char show[ROW][COL]);//玩家行動
int get_mine(char mine[ROW][COL]);//探知雷數
void open_board(char mine[ROW][COL],char show[ROW][COL],int i,int j);//展開
void safe_board(char mine[ROW][COL],int i,int j);//第一步安全檢查
void menu_board();//第二個選單

test.c

#include"game.h"

void menu()
{
	printf("*****************************************\n");
	printf("***************1.開始遊戲****************\n");
	printf("************   0.退出遊戲   *************\n");
	printf("*****************************************\n");
}

void game()
{
	char mine[ROW][COL];
    char show[ROW][COL];
    init_board(mine, show);
    display_board(show);
	set_board(mine);
	show_board(mine);
	my_step(mine,show);
}

int main()
{
	int input=0;
	do
	{
		menu();
		printf("請選擇\n");
		scanf("%d",&input);
		switch(input)
		{
		case 1:
			printf("請開始遊戲\n");
			game();
			break;
		case 0:
			printf("退出遊戲\n");
			break;
		default:
			printf("請重新選擇\n");
			break;
		}
	}
	while(input);
	return 0;
}
game.c
#include"game.h"

void init_board(char mine[ROW][COL], char show[ROW][COL])
{
	int i = 0;
    int j = 0;
    for (i = 1; i < ROW - 1; i++)
    {
        for (j = 1; j < COL - 1; j++)
        {
            mine[i][j] = '0';
        }
    }
    for (i = 1; i < ROW - 1; i++)
    {
        for (j = 1; j < COL - 1; j++)
        {
            show [i][j] = '*';
        }
    }
}

void display_board(char show[ROW][COL])
{
    int i, j;
	for(i=0; i<ROW-1; i++)
	{
		printf("%d ",i);
	}
	for(i=1; i<ROW-1; i++)
	{
		printf("\n");
		printf("%d ",i);
		for(j=1; j<COL-1; j++)
		{
			printf("%c ",show[i][j]);
		}
	}
	printf("\n");
}

void set_board(char mine[ROW][COL])
{
	int i,j;
	int count=10;
	while(count)
	{
		i=rand()%8+1;
		j=rand()%8+1;
		if(mine[i][j]=='0')
		{
			mine[i][j]='1';
			count--;
		}
	}
}

void show_board(char mine[ROW][COL])
{
	int i, j;
	for(i=0; i<ROW-1; i++)
	{
		printf("%d ",i);
	}
	for(i=1; i<ROW-1; i++)
	{
		printf("\n");
		printf("%d ",i);
		for(j=1; j<COL-1; j++)
		{
			printf("%c ",mine[i][j]);
		}
	}
	printf("\n");
}

void my_step(char mine[ROW][COL], char show[ROW][COL])
{
    int i = 0;
    int j = 0;
    int count=71;
	int input=0;
    while (count)
    {
		do
		{
			menu_board();
			printf("請選擇\n");
			scanf("%d",&input);
			switch(input)
			{
			case 1:
				printf("請輸入座標\n");
				scanf("%d%d", &i,&j);
				if(count==71)
				{
					safe_board(mine,i,j);
				}
				if (mine[i][j] == '1')
				{
					printf("踩到雷了:\n");
					show_board(mine);
					return;
				}
				else
				{
					open_board(mine,show,i,j);
					display_board(show);
					count--;
				}
				break;
			case 0:
				printf("輸入座標\n");
				scanf("%d%d", &i,&j);
				if(show[i][j]=='*')
				{
					show[i][j]='!';
				}
				else
					show[i][j]='*';
				display_board(show);
				break;
			default:
				printf("請重新選擇\n");
				break;
			}
			break;
		}while(input);
    }
    printf("掃雷成功\n");
}

int get_mine(char mine[ROW][COL] ,int i,int j)
{
	int count=0;
	if(mine[i-1][j]=='1')
	{
		count++;
	}
	if(mine[i-1][j-1]=='1')
	{
		count++;
	}
	if(mine[i-1][j+1]=='1')
	{
		count++;
	}
	if(mine[i][j+1]=='1')
	{
		count++;
	}
	if(mine[i][j-1]=='1')
	{
		count++;
	}
	if(mine[i+1][j-1]=='1')
	{
		count++;
	}
	if(mine[i+1][j]=='1')
	{
		count++;
	}
	if(mine[i+1][j+1]=='1')

	{
		count++;
	}
	return count;
}

void open_board(char mine[ROW][COL],char show[ROW][COL],int i,int j)
{
	if(mine[i][j]=='0'&&i>=0&&j>=0&&show[i][j]=='*')
	{
		show[i][j]=get_mine(mine,i,j)+'0';
	}
	if(mine[i][j-1]=='0'&&i>=0&&j-1>=0&&show[i][j-1]=='*')
	{
		show[i][j-1]=get_mine(mine,i,j-1)+'0';
		if(get_mine(mine,i,j-1)==0)
		{
			open_board(mine,show,i,j-1);
		}
	}
	if(mine[i][j+1]=='0'&&i>=0&&j+1>=0&&show[i][j+1]=='*')
	{
		show[i][j+1]=get_mine(mine,i,j+1)+'0';
		if(get_mine(mine,i,j+1)==0)
		{
			open_board(mine,show,i,j+1);
		}
	}
	if(mine[i-1][j]=='0'&&i-1>=0&&j>=0&&show[i-1][j]=='*')
	{
		show[i-1][j]=get_mine(mine,i-1,j)+'0';
		if(get_mine(mine,i-1,j)==0)
		{
			open_board(mine,show,i-1,j);
		}
	}
	if(mine[i-1][j-1]=='0'&&i-1>=0&&j-1>=0&&show[i-1][j-1]=='*')
	{
		show[i-1][j-1]=get_mine(mine,i-1,j-1)+'0';
		if(get_mine(mine,i-1,j-1)==0)
		{
			open_board(mine,show,i-1,j-1);
		}
	}
	if(mine[i-1][j+1]=='0'&&i-1>=0&&j+1>=0&&show[i-1][j+1]=='*')
	{
		show[i-1][j+1]=get_mine(mine,i-1,j+1)+'0';
		if(get_mine(mine,i-1,j+1)==0)
		{
			open_board(mine,show,i-1,j+1);
		}
	}
	if(mine[i+1][j+1]=='0'&&i+1>=0&&j+1>=0&&show[i+1][j+1]=='*')
	{
		show[i+1][j+1]=get_mine(mine,i+1,j+1)+'0';
		if(get_mine(mine,i+1,j+1)==0)
		{
			open_board(mine,show,i+1,j+1);
		}
	}
	if(mine[i+1][j]=='0'&&i+1>=0&&j>=0&&show[i+1][j]=='*')
	{
		show[i+1][j]=get_mine(mine,i+1,j)+'0';
		if(get_mine(mine,i+1,j)==0)
		{
			open_board(mine,show,i+1,j);
		}
	}
	if(mine[i+1][j-1]=='0'&&i+1>=0&&j-1>=0&&show[i+1][j-1]=='*')
	{
		show[i+1][j-1]=get_mine(mine,i+1,j-1)+'0';
		if(get_mine(mine,i+1,j-1)==0)
		{
			open_board(mine,show,i+1,j-1);
		}
	}
	
}

void safe_board(char mine[ROW][COL],int i,int j)
{
	int count=1;
	while(count)
	{
		if(mine[i][j]='1')
		{
			int m=rand()%8+1;
			int n=rand()%8+1;
			mine[i][j]='0';
			mine[m][n]='1';
			count--;
		}
		else
			count--;
	}
}

void menu_board()
{
	printf("**** 1.掃雷    0.標雷與撤銷 ***\n");
}
最後,記得引用標頭檔案 #include"game.h"。

相關文章