目錄
- 一、原理及問題分析
- 二、代碼實現
- 2.1 分文件結構設計
- 2.2 棋盤初始化與打印
- 2.3 布置雷與排查雷
- 2.4 游戲主流程實現
- 三、后期優化方向
在上一篇文章中,我們實現了我們的第二個游戲——三子棋小游戲。這次我們繼續結合我們之前所學的所有內容,制作出我們的第三個項目——掃雷小游戲。
一、原理及問題分析
說起掃雷,這是一個非常經典的小游戲。掃雷游戲的核心邏輯是通過玩家輸入的坐標排查雷的位置,若踩雷則游戲結束,否則顯示周圍雷的數量。接下來我們先以結構化的方式來宏觀分析整個掃雷游戲中的關鍵要點:
-
雙棋盤設計
mine
數組:存儲雷的位置(1
為雷,0
為非雷)。show
數組:存儲玩家可見的信息(初始為*
,排查后顯示周圍雷數,因為是字符,所以兩個數組都是char類型)。- 設計意義:若只創建一個數組,雷為1,不是雷為0,若這個坐標周圍只有1個雷,則分不清是雷,還是排查出的雷的信息,有歧義,所以再創建一個額外數組,專門用來存放排查出的雷的信息,只打印這個數組即可,用%c打印。
-
邊界處理
- 實際使用
11x11
的數組(通過ROWS
和COLS
定義),但只操作中間的9x9
區域(通過ROW
和COL
定義)。 - 目的:排查雷時邊界容易越界,所以要實現9x9棋盤實際是創建11x11的數組才不會越界,。 但不要直接寫數字,而是定義行和列的符號,方便后期修改。
- 實際使用
-
模塊化設計
test.c
:處理菜單、循環流程和用戶輸入。game.c
:實現游戲核心邏輯(初始化、布置雷、排查雷)。game.h
:聲明函數和定義常量。
-
游戲流程(在三子棋和之前的猜數字小游戲中已實現過)
- 使用
do-while
循環支持重復游玩。 - 玩家輸入坐標后,通過遞歸展開無雷區域(進階功能需自行實現)。
- 使用
二、代碼實現
2.1 分文件結構設計
文件分工與核心函數:
- game.h:定義常量、聲明函數。
// game.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>// 常量定義:實際操作的棋盤大小為9x9,擴展為11x11避免越界
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY 10 // 默認雷的數量// 函數聲明
void Start(char arr[ROWS][COLS], int rows, int cols, char get); // 初始化棋盤
void Display(char arr[ROWS][COLS], int row, int col); // 打印棋盤
void Set(char arr[ROWS][COLS], int row, int col); // 布置雷
int Choose(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col); // 排雷邏輯
- test.c:主流程和菜單邏輯。
- game.c:核心功能實現。
2.2 棋盤初始化與打印
1. 初始化函數 Start
- 功能:將棋盤所有位置初始化為指定字符(
mine
初始為'0'
,show
初始為'*'
)因為一個函數要實現兩種不同內容初始化,所以多加一個參數。
// game.c
void Start(char arr[ROWS][COLS], int rows, int cols, char get)
{for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {arr[i][j] = get; // 將每個元素設置為傳入的字符('0'或'*')}}
}
2. 打印函數 Display
- 功能:打印棋盤,為了美化棋盤顯示,優化玩家體驗,我們添加了行列號和分隔線。
// game.c
void Display(char arr[ROWS][COLS], int row, int col){printf("--------掃雷游戲--------\n");// 打印列號(頂部標簽)printf(" "); // 對齊行號for (int i = 1; i <= col; i++){printf("%d ", i);}printf("\n");// 打印分隔線printf(" ");for (int i = 1; i <= col; i++) {printf("--");}printf("\n");// 打印棋盤內容(帶行號)for (int i = 1; i <= row; i++) {printf("%d |", i); // 行號+左側豎線for (int j = 1; j <= col; j++) {printf("%c ", arr[i][j]); // 打印棋盤元素}printf("\n");}printf("--------掃雷游戲--------\n");
}
運行效果:
--------掃雷游戲--------1 2 3 4 5 6 7 8 9 -------------------
1 |* * * * * * * * *
2 |* * * * * * * * *
...(略)
2.3 布置雷與排查雷
1. 布置雷函數 Set
- 功能:在
9x9
區域內隨機生成雷。(關于rand函數的用法以及取余的技巧在之前的三子棋和猜數字小游戲中已經詳細介紹過,這里不再贅述)
// game.c
void Set(char arr[ROWS][COLS], int row, int col){int count = EASY; // 雷的數量while (count) {int x = rand() % row + 1; // 生成1~9的隨機坐標int y = rand() % col + 1;if (arr[x][y] == '0') { // 僅當該位置無雷時布置arr[x][y] = '1'; // 標記為雷count--;}}
}
2. 計算周圍雷數 get_mine
- 功能:計算坐標
(x,y)
周圍8個位置的雷數總和。 這個函數可以不用在game.h中聲明,因為只是為了在排查雷的函數中臨時用的,不會用在其他地方。
// game.c
int get_mine(char arr[ROWS][COLS], int x, int y)
{// 周圍8個坐標的字符值相加('0'或'1'),再減去8*'0'得到實際數字return arr[x-1][y-1] + arr[x-1][y] + arr[x-1][y+1] +arr[x][y-1] + arr[x][y+1] +arr[x+1][y-1] + arr[x+1][y] + arr[x+1][y+1] - 8 * '0';
}
3. 排雷邏輯 Choose
- 功能:處理玩家輸入的坐標,判斷是否踩雷或顯示周圍雷數。
// game.c
int Choose(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col){int x = 0, y = 0;int Win = 0; // 記錄已排查的非雷區域數量while (Win < ROW * COL - EASY) // 勝利條件:所有非雷區域均被排查{ printf("請選擇排雷坐標(如2 3表示第二行第三列):>");scanf("%d %d", &x, &y);if (x >= 1 && y >= 1 && x <= row && y <= col)// 坐標合法性檢查{ if (mine[x][y] == '1') // 踩雷{ printf("很遺憾,你被炸死了!\n");Display(mine, ROW, COL); // 展示雷的位置break; // 游戲結束} else // 安全坐標{ int count = get_mine(mine, x, y); // 計算周圍雷數show[x][y] = count + '0'; // 轉換為字符存儲(例如3 -> '3')Display(show, ROW, COL);Win++;}} else {printf("坐標非法!\n");}}if (Win == ROW * COL - EASY) // 勝利條件達成{ printf("恭喜通關!\n");Display(mine, ROW, COL); // 展示雷的位置}
}
2.4 游戲主流程實現
test.c:菜單和主循環邏輯。
// test.c
#include "game.h"void menu() {printf("*********************************\n");printf("*********************************\n");printf("***********掃雷小游戲************\n");printf("*********************************\n");printf("***********1.開始游戲************\n");printf("***********0.退出游戲************\n");printf("*********************************\n");printf("**********版本:Beta1.0***********\n");printf("**********作者:Yang210***********\n");printf("*********************************\n");printf("*********************************\n");printf("*********************************\n");
}void game() {char mine[ROWS][COLS]; // 存儲雷的棋盤char show[ROWS][COLS]; // 顯示給玩家的棋盤Start(mine, ROWS, COLS, '0'); // 初始化雷棋盤為全'0'Start(show, ROWS, COLS, '*'); // 初始化顯示棋盤為全'*'Set(mine, ROW, COL); // 布置雷Display(show, ROW, COL); // 打印初始棋盤Choose(mine, show, ROW, COL); // 進入排雷邏輯
}int main() {srand((unsigned int)time(NULL)); // 設置隨機數種子int input = 0;int add = 0; // 記錄游戲次數do {menu();if (add == 0) {printf("請選擇(輸入1開始游戲,輸入0退出游戲):>");} else {printf("是否繼續游玩(輸入1繼續游玩,輸入0退出游戲):>");}scanf("%d", &input);switch (input) {case 1:game();add++;break;case 0:printf("已退出游戲\n");break;default:printf("輸入錯誤!\n");}} while (input); // input為0時退出循環return 0;
}
三、后期優化方向
- 遞歸展開:若排查坐標周圍無雷(即雷數為0),自動展開相鄰區域。
- 標記雷:允許玩家輸入特殊指令(如
m 3 4
)標記可能為雷的位置。 - 難度調整:通過修改
EASY
的值實現不同難度的雷數設置。 - 界面優化:使用Windows API或第三方庫(如EasyX)添加圖形界面。
簡單地總結一下,在這篇文章中,我們通過分文件設計和模塊化的運用,將掃雷游戲的邏輯逐一地理清并且給出了后期擴展的方向。我們在代碼中通過定義符號常量(如ROW
、EASY
)提高可維護性,方便后期修改,這是一種很重要的編程習慣。我們通過我們現階段所學的知識,做出了這個經典的游戲,這是對我們所學知識的肯定,也是我們對經典跨越時間的致敬。最后,我想送給大家一句話:"人生沒有白走的路,每一步都算數。"我們的決定,決定了我們。在介紹完了兩個C語言的項目之后,下一章,我們將回歸C語言的知識學習,介紹一下操作符的相關知識,敬請期待。
作者其他文章鏈接:
[C語言初階]三子棋小游戲
[C語言初階]數組
[C語言初階]遞歸
Gitee詳細使用教程