文章目錄
- 前言
- 一、問題描述
- 二、基本框架構思
- 三、具體實作
- 1.掃雷介面實作
- 2.地圖初始化
- 3.設定雷
- 4.顯示界面
- 5.開始掃雷
- 6.計算周圍雷的數量
- 7.排查雷
- 8.空白展開
- 9.標記雷
- 10.取消標記
- 四、結果演示
- 五、完整代碼
- 總結
前言
掃雷,是一個十分經典的小游戲,相信大家小時候都玩過,在實作程序中你將會有很大的成就感,現在就讓我們一起來實作它吧,,
一、問題描述
實作除界面外掃雷游戲的所有功能,包括
實作一個簡單的界面
實作排查雷的功能
實作標記雷的功能
實作顯示周圍雷的數量
實作第一次排查不會遇雷
實作如果周圍沒有雷則展開一片

二、基本框架構思
在開始撰寫代碼之前,我們必須去玩一玩掃雷,熟悉一下它的各種規則和機制,有助于我們形成更加清晰的代碼邏輯,同時在寫完一個功能后,進行測驗并與真實的掃雷功能做對比,為了撰寫代碼,我玩了十余次,
在試玩過后,我們首先想到的便是界面如何展示,雷如何設定,對于二維平面,我們最常用的就是陣列,在陣列里面存放不同的資料,來表示有無雷,
- 假設我們用一個二維陣列,用資料0表示沒有雷,1表示有雷,但是當我們排查一個點之后需要顯示周圍雷的數量,假設也為1,那就會產生沖突,同時也不方便統計周圍雷的數量,
- 還有一個問題陣列容易出界,沒次訪問都需要判斷,比較麻煩,容易出錯,
解決辦法:
-
我們使用兩個陣列,一個表示有無雷,一個展示給用戶看,未排查用‘ *
’表示,已排查用周圍雷的數量表示,為了統一,我們使用字符陣列,遇到整數時將其轉化為字符存放, -
我們可以只使用設定陣列的內圈部分,即最外圈不再使用,用于判斷,
如下圖:

接下來讓我們來實作主函式,簡單選單
#define _CRT_SECURE_NO_WARNINGS 1
void Menu()
{
printf("********************************\n");
printf("********* 1. play ********\n");
printf("********* 0. exit ********\n");
printf("********************************\n");
}
int main()
{
int input = 0;
int count = 0;
srand((unsigned int)time(NULL));
do
{
Menu();
//清除緩沖區,第一次不用
if(count!=0)
{
char ch;
while ((ch = getchar()) != EOF && ch != '\n')
{
;
}
}
count++;
printf("請選擇:>");
scanf("%d", &input);
switch (input)
{
case 1:
//將標記正確的數量重置為0
mark_count = 0;
MineSweeper();
break;
case 0:
printf("退出游戲\n");
break;
default:
printf("選擇錯誤,重新選擇!\n");
break;
}
} while (input);
return 0;
}
設定清除緩沖區代碼的目的:
防止輸入一個有效數字但帶有空格,后面又輸入一個數字存放在緩沖區,導致下一次直接取緩沖區的數字,不符合本意,
如圖:

加上之后:

三、具體實作
1.掃雷介面實作
先定義相關宏定義,方面后面修改
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#define ROW 9 //掃雷地圖的陣列的行
#define COL 9 //掃雷地圖的陣列的行
#define ROWS 11 //真實陣列的行,為了方便查找雷的數量,這樣的話不用判斷訪問陣列是否越界
#define COLS 11 //真實陣列的行,多加一圈
#define MINE_COUNT 9 //設定雷的數量
//設定全域變數
int find_count;//用于判斷是否為第一次排雷,防止第一次被詐
int mark_count;//標記正確的數量
游戲介面,呼叫各函式實作掃雷,
void MineSweeper()
{
//為了統一符號,使用字符陣列
char mine[ROWS][COLS] = { 0 };//用于存放雷的陣列,0表示沒有雷,1表示有雷
char show[ROWS][COLS] = { 0 };//用于存放列印給用戶看的該點周圍雷的數量的陣列,
//默認為*,輸入坐標后其內容為周圍雷的數量
// !為標記點
//陣列初始化
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//設定雷
SetMine(mine, ROW, COL);
system("cls");
//除錯用,可以查看雷的位置
//DisplayBoard(mine, ROW, COL);
//顯示陣列版面
DisplayBoard(show, ROW, COL);
//游戲入口
PlayGame(mine, show, ROW, COL);
}
2.地圖初始化
因為兩個陣列最初都是只存放一種字符,’ 0 ‘和’ * ',所有可以直接把字符當作引數傳入,這樣就可以通用一個函式了,
//陣列初始化
void InitBoard(char board[][COLS], int row, int col, char ch)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
board[i][j] = ch;
}
}
}
3.設定雷
使用隨機函式設定雷,即將mine陣列的部分隨機設定成’ 1 ',需要注意的是,我們只針對陣列內部真正有效的部分,最外一圈不管,所以遍歷是從1開始,
//設定雷
void SetMine(char board[][COLS], int row, int col)
{
int count = MINE_COUNT;
while (count)
{
//隨機獲得雷的坐標
int x = rand() % row + 1;//從1到row-1
int y = rand() % col + 1;
//判斷是否已經是雷
if (board[x][y] != '1')
{
//不是雷,就放
board[x][y] = '1';
count--;
}
}
}
4.顯示界面
列印傳入的陣列,并列印行號,注意只列印陣列內部,遍歷從1開始,
如果做了標記用’ ! '表示,
//顯示陣列版面
void DisplayBoard(char board[][COLS], int row, int col)
{
printf("------------------------\n");
printf(" ");
for (int i = 1; i <= col; i++)
{
printf("%2d", i);
}
printf("\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");
}
5.開始掃雷
排查過的數量等于不是雷的數量 或者 標記正確雷的數量等于設定雷的數量結束,排雷成功,
每次選擇執行完后列印顯示(show)陣列,
//游戲入口,選擇排查或者標記
void PlayGame(char mine[][COLS], char show[][COLS], int row, int col)
{
int win_count = 0;//排查過的數量
//排查過的數量等于‘0’的數量的時候 或者 標記正確雷的數量等于設定的雷數量結束,排雷成功
while (win_count < (row * col - MINE_COUNT) && mark_count < MINE_COUNT)
{
printf("################################\n");
printf("######### 1. 排查雷 ########\n");
printf("######### 2. 標記雷 ########\n");
printf("######### 3. 取消標記 ########\n");
printf("################################\n");
int choice;
printf("請選擇:>");
//清除緩沖區
char ch;
while ((ch = getchar()) != EOF && ch != '\n')
{
;
}
scanf("%d", &choice);
if (choice != 1 && choice != 2 && choice != 3)
{
printf("輸入錯誤,請重新輸入\n");
//跳過本次回圈
continue;
}
if (choice == 1)
{
//排查雷
int judge = FindMine(mine, show, row, col, &win_count);
if (!judge)
{
//被雷炸死,列印藏雷的陣列,并結束
DisplayBoard(mine, row, col);
return;
}
}
else
{
if (choice == 2)
{
//標記雷
MarkMine(mine, show, row, col);
system("cls");
DisplayBoard(show, row, col);
}
else
{
//取消標記雷
CancelMark(mine, show, row, col);
system("cls");
DisplayBoard(show, row, col);
}
}
}
if (win_count == row * col - MINE_COUNT||mark_count==MINE_COUNT)
{
system("cls");
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
return;
}
}
6.計算周圍雷的數量
統計八個方向,上、下、左、右、左上,左下,右上,右下為雷的數量,因為存放的是字符,所有相加后得減去’ 0 ',即可得到整數,
//查找周圍雷的數量
int GetMineCount(char mine[][COLS], int x, int y)
{
return (mine[x - 1][y] +
mine[x + 1][y] +
mine[x][y - 1] +
mine[x][y + 1] +
mine[x - 1][y - 1] +
mine[x + 1][y - 1] +
mine[x - 1][y + 1] +
mine[x + 1][y + 1] - 8 * '0');
}
7.排查雷
排查雷需要設定第一次不被炸死,同時如果被標記則不能再排查,當被炸死或者輸入坐標錯誤則回傳,
//排查雷
//回傳0代表結束,回傳1代表繼續
int FindMine(char mine[][COLS], char show[][COLS], int row, int col, int* pwin)
{
int x, y;
printf("請輸入想要排查的坐標:>");
scanf("%d %d", &x, &y);
//如果是第一次,重新設定后如果還是雷則繼續回圈
while (find_count == 0)
{
if (mine[x][y] == '1')//是雷
{
//現將mine陣列置空,即初始化
InitBoard(mine, row, col, '0');
//重新布雷
SetMine(mine, row, col);
}
else
{
break;
}
}
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '0')//不是雷
{
//如果已經標記,則不能排查
if (show[x][y] == '!')
{
printf("該點已經被標記,請重新輸入\n");
return 1;
}
system("cls");
SpreadBlank(mine, show, x, y, pwin);
DisplayBoard(show, row, col);
}
else
{
printf("很遺憾,你被炸死了!\n");
return 0;
}
}
else
{
printf("輸入坐標錯誤,請重新輸入\n");
return 1;
}
}
標記后不能再被排查:

第一次不會被炸死:

選擇之后:

8.空白展開
目的:如果周圍沒有雷則繼續展開(遞回),遇到雷停止
這是較難實作的一個函式,需要使用遞回實作,而使用遞回就需要確定遞回的終止條件,這里有三個
- 對最外層一圈不做計算,直接回傳,這就是多設定一圈的好處
- 如果show陣列里面不是*,即已經被探查過的,直接回傳,防止死遞回,導致堆疊溢位,
- 如果周圍有雷就停止,
//如果周圍沒有雷,則全部展開
//展開空白區域
void SpreadBlank(char mine[][COLS], char show[][COLS], int x, int y, int* pwin)
{
//對最外層一圈不做計算,直接回傳,這就是多設定一圈的好處
if (x==0||y==0||x==ROWS-1||y==COLS-1)
return;
//如果show陣列里面不是*,即已經被探查過的,直接回傳,防止死遞回,導致堆疊溢位
if (show[x][y] != '*')
return;
int count = GetMineCount(mine, x, y);
if (count > 0)
{
show[x][y] = count + '0';
//增加排查數量
(*pwin)++;
return ;
}
else
{
//八個方向,上、下、左、右、左上,左下,右上,右下
show[x][y] = '0';
//增加排查數量
(*pwin)++;
SpreadBlank(mine, show, x - 1, y, pwin);
SpreadBlank(mine, show, x + 1, y, pwin);
SpreadBlank(mine, show, x, y - 1, pwin);
SpreadBlank(mine, show, x, y + 1, pwin);
SpreadBlank(mine, show, x - 1, y - 1, pwin);
SpreadBlank(mine, show, x + 1, y - 1, pwin);
SpreadBlank(mine, show, x - 1, y + 1, pwin);
SpreadBlank(mine, show, x + 1, y + 1, pwin);
}
}
9.標記雷
用’ ! '為標記符號,如果正確標記雷點,則標記正確數+1
//標記雷點
void MarkMine(char mine[][COLS], char show[][COLS], int row, int col)
{
int x;
int y;
printf("請輸入想要標記的坐標:>");
scanf("%d%d", &x, &y);
//該點需未被探查,即在show陣列為‘*’的點
if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y]=='*')
{
if (mine[x][y] == '1')
{
//正確標記雷點
mark_count++;
}
show[x][y] = '!';
}
else
{
printf("輸入坐標錯誤,請重新輸入\n");
}
}
10.取消標記
與標記類似,只需把標記點改為’ * '即可,如果該點本是正確標記雷點,則標記正確數-1
//取消標記雷點
void CancelMark(char mine[][COLS], char show[][COLS], int row, int col)
{
int x;
int y;
printf("請輸入想要取消標記的坐標:>");
scanf("%d%d", &x, &y);
//該點需已被標記
if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y]=='!')
{
if (mine[x][y] == '1')
{
//如果是正確標記雷點,數量減一
mark_count--;
}
show[x][y] = '*';
}
else
{
printf("輸入坐標錯誤或者不是被標記點,請重新輸入\n");
}
}
四、結果演示
我們將雷的數量設定為1,同時展示mine陣列,讓我們知道雷的位置,方便測驗,
#define MINE_COUNT 1 //設定雷的數量
DisplayBoard(mine, ROW, COL);


展開演示:

全部展開,排雷成功,

當然也有下面種情況,

五、完整代碼
MineSweeper.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#define ROW 9 //掃雷地圖的陣列的行
#define COL 9 //掃雷地圖的陣列的行
#define ROWS 11 //真實陣列的行,為了方便查找類的數量,這樣的話不用判斷訪問陣列是否越界
#define COLS 11 //真實陣列的行,多加一圈
#define MINE_COUNT 1 //設定雷的數量
int find_count;//用于判斷是否為第一次排雷,防止第一次被詐
int mark_count;//標記正確的數量
//陣列初始化
void InitBoard(char board[][COLS], int row, int col, char ch);
//顯示陣列版面
void DisplayBoard(char board[][COLS], int row, int col);
//設定雷
void SetMine(char board[][COLS], int row, int col);
//游戲入口,選擇排查或者標記
void PlayGame(char mine[][COLS], char show[][COLS], int row, int col);
//排查雷
//回傳0代表結束,回傳1代表繼續
int FindMine(char mine[][COLS], char show[][COLS], int row, int col, int* pwin);
//查找周圍雷的數量
int GetMineCount(char mine[][COLS], int x, int y);
//如果周圍沒有雷,則全部展開
//展開空白區域
void SpreadBlank(char mine[][COLS], char show[][COLS], int x, int y, int* pwin);
//標記雷點
void MarkMine(char mine[][COLS], char show[][COLS], int row, int col);
//取消標記雷點
void CancelMark(char mine[][COLS], char show[][COLS], int row, int col);
MineSweeper.c
#pragma once
#include"MineSweeper.h"
//陣列初始化
void InitBoard(char board[][COLS], int row, int col, char ch)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
board[i][j] = ch;
}
}
}
//顯示陣列版面
void DisplayBoard(char board[][COLS], int row, int col)
{
printf("------------------------\n");
printf(" ");
for (int i = 1; i <= col; i++)
{
printf("%2d", i);
}
printf("\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[][COLS], int row, int col)
{
int count = MINE_COUNT;
while (count)
{
//隨機獲得雷的坐標
int x = rand() % row + 1;
int y = rand() % col + 1;
//判斷是否已經是雷
if (board[x][y] != '1')
{
//不是雷,就放
board[x][y] = '1';
count--;
}
}
}
//游戲入口,選擇排查或者標記
void PlayGame(char mine[][COLS], char show[][COLS], int row, int col)
{
int win_count = 0;//排查過的數量
//排查過的數量等于‘0’的數量的時候 或者 標記正確雷的數量等于設定的雷數量結束,排雷成功
while (win_count < (row * col - MINE_COUNT) && mark_count < MINE_COUNT)
{
printf("################################\n");
printf("######### 1. 排查雷 ########\n");
printf("######### 2. 標記雷 ########\n");
printf("######### 3. 取消標記 ########\n");
printf("################################\n");
int choice;
printf("請選擇:>");
//清除緩沖區
char ch;
while ((ch = getchar()) != EOF && ch != '\n')
{
;
}
scanf("%d", &choice);
if (choice != 1 && choice != 2 && choice != 3)
{
printf("輸入錯誤,請重新輸入\n");
//跳過本次回圈
continue;
}
if (choice == 1)
{
//排查雷
int judge = FindMine(mine, show, row, col, &win_count);
if (!judge)
{
//被雷炸死,列印藏雷的陣列,并結束
DisplayBoard(mine, row, col);
return;
}
}
else
{
if (choice == 2)
{
//標記雷
MarkMine(mine, show, row, col);
system("cls");
DisplayBoard(show, row, col);
}
else
{
//取消標記雷
CancelMark(mine, show, row, col);
system("cls");
DisplayBoard(show, row, col);
}
}
}
if (win_count == row * col - MINE_COUNT||mark_count==MINE_COUNT)
{
system("cls");
printf("恭喜你,排雷成功\n");
DisplayBoard(mine, ROW, COL);
return;
}
}
//排查雷
//回傳0代表結束,回傳1代表繼續
int FindMine(char mine[][COLS], char show[][COLS], int row, int col, int* pwin)
{
int x, y;
printf("請輸入想要排查的坐標:>");
scanf("%d %d", &x, &y);
//如果是第一次
while (find_count == 0)
{
if (mine[x][y] == '1')//是雷
{
//現將mine陣列置空,即初始化
InitBoard(mine, row, col, '0');
//重新布雷
SetMine(mine, row, col);
}
else
{
break;
}
}
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '0')//不是雷
{
//如果已經標記,則不能排查
if (show[x][y] == '!')
{
printf("該點已經被標記,請重新輸入\n");
return 1;
}
//int count = GetMineCount(mine, x, y);
//show[x][y] = count + '0';
system("cls");
SpreadBlank(mine, show, x, y, pwin);
DisplayBoard(show, row, col);
//win++;
}
else
{
printf("很遺憾,你被炸死了!\n");
return 0;
}
}
else
{
printf("輸入坐標錯誤,請重新輸入\n");
return 1;
}
}
//查找周圍雷的數量
int GetMineCount(char mine[][COLS], int x, int y)
{
return (mine[x - 1][y] +
mine[x + 1][y] +
mine[x][y - 1] +
mine[x][y + 1] +
mine[x - 1][y - 1] +
mine[x + 1][y - 1] +
mine[x - 1][y + 1] +
mine[x + 1][y + 1] - 8 * '0');
}
//如果周圍沒有雷,則全部展開
//展開空白區域
void SpreadBlank(char mine[][COLS], char show[][COLS], int x, int y, int* pwin)
{
//對最外層一圈不做計算
if (x==0||y==0||x==ROWS-1||y==COLS-1)
return;
//如果show陣列里面不是*,即已經被探查過的,直接回傳,防止死遞回,導致堆疊溢位
if (show[x][y] != '*')
return;
int count = GetMineCount(mine, x, y);
if (count > 0)
{
show[x][y] = count + '0';
//增加排查數量
(*pwin)++;
return ;
}
else
{
//mine[x][y] = '2';
//八個方向,上、下、左、右、左上,左下,右上,右下
show[x][y] = '0';
//增加排查數量
(*pwin)++;
SpreadBlank(mine, show, x - 1, y, pwin);
SpreadBlank(mine, show, x + 1, y, pwin);
SpreadBlank(mine, show, x, y - 1, pwin);
SpreadBlank(mine, show, x, y + 1, pwin);
SpreadBlank(mine, show, x - 1, y - 1, pwin);
SpreadBlank(mine, show, x + 1, y - 1, pwin);
SpreadBlank(mine, show, x - 1, y + 1, pwin);
SpreadBlank(mine, show, x + 1, y + 1, pwin);
}
}
//標記雷點
void MarkMine(char mine[][COLS], char show[][COLS], int row, int col)
{
int x;
int y;
printf("請輸入想要標記的坐標:>");
scanf("%d%d", &x, &y);
//該點需未被探查,即在show陣列為‘*’的點
if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y]=='*')
{
if (mine[x][y] == '1')
{
//正確標記雷點
mark_count++;
}
show[x][y] = '!';
}
else
{
printf("輸入坐標錯誤,請重新輸入\n");
}
}
//取消標記雷點
void CancelMark(char mine[][COLS], char show[][COLS], int row, int col)
{
int x;
int y;
printf("請輸入想要取消標記的坐標:>");
scanf("%d%d", &x, &y);
//該點需已被標記
if (x >= 1 && x <= row && y >= 1 && y <= col && show[x][y]=='!')
{
if (mine[x][y] == '1')
{
//如果是正確標記雷點,數量減一
mark_count--;
}
show[x][y] = '*';
}
else
{
printf("輸入坐標錯誤或者不是被標記點,請重新輸入\n");
}
}
MineSweeperTest.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"MineSweeper.h"
void Menu()
{
printf("********************************\n");
printf("********* 1. play ********\n");
printf("********* 0. exit ********\n");
printf("********************************\n");
}
void MineSweeper()
{
//為了統一符號,使用字符陣列
char mine[ROWS][COLS] = { 0 };//用于存放雷的陣列,0表示沒有雷,1表示有雷
char show[ROWS][COLS] = { 0 };//用于存放列印給用戶看的該點周圍雷的數量的陣列,
//默認為*,輸入坐標后其內容為周圍雷的數量
// !為標記點
//陣列初始化
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//設定雷
SetMine(mine, ROW, COL);
system("cls");
//除錯用
DisplayBoard(mine, ROW, COL);
//顯示陣列版面
DisplayBoard(show, ROW, COL);
//游戲入口
PlayGame(mine, show, ROW, COL);
}
int main()
{
int input = 0;
int count = 0;
srand((unsigned int)time(NULL));
do
{
Menu();
//清除緩沖區,第一次不用
if(count!=0)
{
char ch;
while ((ch = getchar()) != EOF && ch != '\n')
{
;
}
}
count++;
printf("請選擇:>");
scanf("%d", &input);
switch (input)
{
case 1:
//將標記正確的數量重置為0
mark_count = 0;
MineSweeper();
break;
case 0:
printf("退出游戲\n");
break;
default:
printf("選擇錯誤,重新選擇!\n");
break;
}
} while (input);
return 0;
}
總結
對掃雷的完善,將其除界面外全部功能實作,其中最難的一點是展開空白區域,因為涉及遞回,并且在陣列受限條件較多,需要多次除錯,如有錯誤,歡迎大佬指正,轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/302872.html
標籤:其他
