前言
本文衔接上文函数与数组内容,以制作扫雷小游戏的形式对知识进行具象化的体现与应用。对于初识函数与数组的读者们,可以先阅读<扫雷游戏设计思路>与<主函数--显示游戏界面>,再按顺序阅读以更好地理解思路。
扫雷游戏设计思路
扫雷游戏的核心在于玩家交互、地图生成、地雷布置、游戏逻辑判断。设计时需要将整个游戏分解为多个模块,每个模块通过函数实现。
地图采用二维数组表示,一个数组用于存储地雷位置,另一个数组用于显示玩家看到的界面。地雷布置使用随机数生成,确保每次游戏地雷位置不同。
初始化游戏地图
定义两个二维数组,一个用于存储地雷分布(mineMap),另一个用于显示玩家操作后的界面(showMap)。初始化时将所有位置设为未探索状态。
c
#define ROW 9
#define COL 9
#define MINES 10
char mine[ROW][COL];
char show[ROW][COL];
void Initboard() {
for (int i = 0; i < ROW; i++) {
for (int j = 0; j < COL; j++) {
mine[i][j] = '0';
show[i][j] = '*';
}
}
}
但在排雷的过程中,我们需要知道周围一圈雷的个数,但当排查位置为边界时,上述代码会出现越界问题。 优化如下:
c
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define COUNT 10
char mine[ROWS][COLS];
char show[ROWS][COLS];
void initMap() {
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
mine[i][j] = '0';
show[i][j] = '*';
}
}
}
随机布置地雷
使用随机数生成地雷位置,确保生成的位置不重复且数量符合设定。地雷在mineMap中用'1'表示。
c
#include <stdlib.h>
#include <time.h>
void SetMine() {
srand((unsigned int)time(NULL));
int count = 0;
while (count < MINE) {
int x = rand() % ROW+1;
int y = rand() % COL+1;
if (mine[x][y] == '0') {
mine[x][y] = '1';
count++;
}
}
}
计算周围地雷数量
遍历每个非地雷格子,统计周围8个格子中的地雷数量,并将结果存入mineMap中。使用递归实现空白区域的自动展开。
c
int Getminecount(int x, int y) {
int count = 0;
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
int nx = x + i;
int ny = y + j;
if (nx >= 0 && nx < ROWS && ny >= 0 && ny < COLS && mine[nx][ny] == '1') {
count++;
}
}
}
return count;
}
void Expand(char mine[][100], char show[][100], int x, int y, int r, int c)
{
int i, j;
if (x < 1 || x > r || y < 1 || y > c)
return;
if (show[x][y] != '*')
return;
if (mine[x][y] == '1')
return;
int count = Getminecount(mine, x, y);
show[x][y] = count + '0';
if (count == 0) {
for (i = x - 1; i <= x + 1; i++) {
for (j = y - 1; j <= y + 1; j++) {
if (i >= 1 && i <= r && j >= 1 && j <= c && show[i][j] == '*')
Expand(mine, show, i, j, r, c);
}
}
}
}
玩家输入与游戏逻辑
玩家通过输入坐标来探索格子,如果踩到地雷游戏结束,否则显示周围地雷数量。排完则游戏结束。
c
void Findmine(char mine[][100], char show[][100], int r, int c, int count)
{
int x, y;
int op;
int win = 0;
int ROWS = r + 2;
int COLS = c + 2;
while (win < r * c - count) {
printf("请选择操作: 1.排查 2.标记/取消标记\n");
if (scanf_s("%d", &op) != 1 || (op != 1 && op != 2)) {
printf("输入错误,请输入1或2!\n");
while (getchar() != '\n');
continue;
}
if (op == 2) {
printf("请输入要标记的坐标(1-%d, 1-%d):", r, c);
if (scanf_s("%d%d", &x, &y) != 2 || x < 1 || x > r || y < 1 || y > c) {
printf("坐标非法,请输入1到%d之间的数字!\n", r);
while (getchar() != '\n');
continue;
}
Flagmine(show, x, y);
} else {
printf("请输入排查坐标(1-%d, 1-%d):", r, c);
if (scanf_s("%d%d", &x, &y) != 2 || x < 1 || x > r || y < 1 || y > c) {
printf("坐标非法,请输入1到%d之间的数字!\n", r);
while (getchar() != '\n');
continue;
}
if (show[x][y] == '!' || show[x][y] == '*') {
if (mine[x][y] == '1') {
printf("很遗憾,你被炸死了!\n");
int i, j;
for (i = 1; i <= r; i++) {
for (j = 1; j <= c; j++) {
if (mine[i][j] == '1')
show[i][j] = '@';
}
}
Displayboard(show, ROWS, COLS, 1, 1);
return;
} else {
Expand(mine, show, x, y, r, c);
win = 0;
int i, j;
for (i = 1; i <= r; i++) {
for (j = 1; j <= c; j++) {
if (show[i][j] >= '0' && show[i][j] <= '8')
win++;
}
}
Displayboard(show, ROWS, COLS, 1, 1);
}
} else {
printf("该位置已被排查,请重新输入坐标!\n");
}
}
}
if (win >= r * c - count) {
printf("恭喜你!你不怕危险,敢于排除万难取得胜利!\n");
int i, j;
for (i = 1; i <= r; i++) {
for (j = 1; j <= c; j++) {
if (mine[i][j] == '1')
show[i][j] = '!';
}
}
Displayboard(show, ROWS, COLS, 1, 1);
}
}
显示游戏界面
打印当前游戏界面,展示玩家已探索的区域和未探索的区域。
c
void Displayboard(char board[][100], int r, int c, int startRow, int startCol)
{
int i, j;
printf(" ");
for (j = startCol; j < startCol + c - 2; j++)
printf("%2d ", j);
printf("\n ");
for (j = startCol; j < startCol + c - 2; j++)
printf("---");
printf("\n");
for (i = startRow; i < startRow + r - 2; i++) {
printf("%2d |", i);
for (j = startCol; j < startCol + c - 2; j++) {
printf(" %c ", board[i][j]);
}
printf("|\n");
}
}
主函数整合
将上述函数整合到主函数中,实现完整的游戏流程。
c
int main() {
initMap();
placeMines();
calculateNumbers();
int gameOver = 0;
while (!gameOver) {
printMap();
gameOver = !playGame();
}
return 0;
}
代码优化与扩展
可以增加游戏难度选择、计时功能、排行榜等功能。通过函数封装和模块化设计,使代码更易于维护和扩展。
c
typedef struct {
int row; // 行数
int col; // 列数
int count; // 雷数
} Difficulty;
int getDiffChoice() {
int choice;
if (scanf_s("%d", &choice) != 1 || choice < 1 || choice > 3) {
printf("输入错误,请输入1、2或3!\n");
while (getchar() != '\n');
} else {
switch (choice) {
case 1:
diff.row = 9;
diff.col = 9;
diff.count = 10;
break;
case 2:
diff.row = 16;
diff.col = 16;
diff.count = 40;
break;
case 3:
diff.row = 30;
diff.col = 16;
diff.count = 99;
break;
}
}
int ROW = diff.row;
int COL = diff.col;
int ROWS = ROW + 2;
int COLS = COL + 2;
int COUNT = diff.count;
char (*mine)[100] = (char(*)[100])malloc(sizeof(char) * 100 * ROWS);
char (*show)[100] = (char(*)[100])malloc(sizeof(char) * 100 * ROWS);
}
通过以上步骤,一个简单的扫雷游戏就实现了。后续可以通过添加图形界面或更多功能来提升游戏体验。 在添加了计时,标记功能和优化后,最终代码如下 game.h
c
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
#include<windows.h>
/*
* 难度结构体 (新增功能4: 难度选择)
* 包含三个难度级别的参数
*/
typedef struct {
int row; // 行数
int col; // 列数
int count; // 雷数
} Difficulty;
/* 游戏主函数 */
void game();
/*
* 函数声明: 初始化棋盘
* 参数: board-二维数组, r-行数, c-列数, set-初始化值
*/
void Initboard(char board[][100], int r, int c, char set);
/*
* 函数声明: 显示棋盘
* 参数: board-二维数组, r-行数, c-列数, startRow-起始行, startCol-起始列
*/
void Displayboard(char board[][100], int r, int c, int startRow, int startCol);
/*
* 函数声明: 布置地雷
* 参数: board-雷阵数组, r-行数, c-列数, count-雷数
*/
void Setmine(char board[][100], int r, int c, int count);
/*
* 函数声明: 计算周围雷数
* 参数: mine-雷阵数组, x-行坐标, y-列坐标
* 返回值: 周围8个格子的雷数
*/
int Getminecount(char mine[][100], int x, int y);
/*
* 函数声明: 查找地雷(核心玩法)
* 参数: mine-雷阵, show-显示阵, r-行数, c-列数, count-雷数
*/
void Findmine(char mine[][100], char show[][100], int r, int c, int count);
/*
* 函数声明: 展开周围(新增功能1: 递归展开无雷区域)
* 参数: mine-雷阵, show-显示阵, x-行坐标, y-列坐标, r-行数, c-列数
*/
void Expand(char mine[][100], char show[][100], int x, int y, int r, int c);
/*
* 函数声明: 标记雷(新增功能2: 标记/取消标记)
* 参数: show-显示阵, x-行坐标, y-列坐标
*/
void Flagmine(char show[][100], int x, int y);
/*
* 函数声明: 获取难度选择(新增功能4: 难度选择)
* 返回值: 用户选择的难度编号(1-3)
*/
int getDiffChoice();
test.c
c
#include"game.h"
/*
* 函数名: main
* 功能: 程序入口,显示游戏菜单并处理用户选择
* 实现:
* 1. 显示开始菜单(1-开始游戏, 0-退出)
* 2. 用户选择1则调用game()开始游戏
* 3. 用户选择0则退出程序
* 4. 选择其他选项提示错误并重新显示菜单
*/
int main()
{
int input;
while (1) {
printf("--------------------------------\n");
printf("----------- 1.play -----------\n");
printf("----------- 0.exit -----------\n");
printf("--------------------------------\n");
printf("请选择(0或1):");
if (scanf_s("%d", &input) != 1 || (input != 0 && input != 1)) {
printf("输入错误,请输入0或1!\n");
while (getchar() != '\n');
continue;
}
if (input == 1) {
printf("开始游戏吧!祝你好运!\n");
game();
} else if (input == 0) {
printf("游戏已退出,感谢游玩!\n");
break;
}
}
return 0;
}
game.c
c
#include"game.h"
/*
* 函数名: getDiffChoice
* 功能: 获取用户选择的游戏难度
* 返回值: 用户选择的难度编号 (1-简单, 2-中等, 3-困难)
*/
int getDiffChoice() {
int choice;
while (1) {
printf("--------------------------------\n");
printf("----------- 难度选择 -----------\n");
printf("----------- 1.简单 9*9 -----------\n");
printf("----------- 2.中等 16*16 --------\n");
printf("----------- 3.困难 30*16 --------\n");
printf("--------------------------------\n");
printf("请选择(1-3):");
if (scanf_s("%d", &choice) != 1 || choice < 1 || choice > 3) {
printf("输入错误,请输入1、2或3!\n");
while (getchar() != '\n');
} else {
return choice;
}
}
}
/*
* 函数名: game
* 功能: 游戏主函数,实现扫雷游戏的核心逻辑
* 1. 根据选择的难度初始化游戏参数
* 2. 分配内存创建雷阵和显示阵
* 3. 布置地雷并开始游戏
* 4. 计时并统计游戏用时
*/
void game()
{
Difficulty diff;
int choice = getDiffChoice();
switch (choice) {
case 1:
diff.row = 9;
diff.col = 9;
diff.count = 10;
break;
case 2:
diff.row = 16;
diff.col = 16;
diff.count = 40;
break;
case 3:
diff.row = 30;
diff.col = 16;
diff.count = 99;
break;
}
int ROW = diff.row;
int COL = diff.col;
int ROWS = ROW + 2;
int COLS = COL + 2;
int COUNT = diff.count;
char (*mine)[100] = (char(*)[100])malloc(sizeof(char) * 100 * ROWS);
char (*show)[100] = (char(*)[100])malloc(sizeof(char) * 100 * ROWS);
srand((unsigned int)time(NULL));
Initboard(mine, ROWS, COLS, '0');
Initboard(show, ROWS, COLS, '*');
Setmine(mine, ROWS, COLS, COUNT);
printf("开始游戏吧!\n");
Displayboard(show, ROWS, COLS, 1, 1);
clock_t start = clock();
Findmine(mine, show, ROW, COL, COUNT);
clock_t end = clock();
double time_used = ((double)(end - start)) / CLOCKS_PER_SEC;
printf("用时: %.2f 秒\n", time_used);
free(mine);
free(show);
}
/*
* 函数名: Initboard
* 功能: 初始化二维数组,将所有元素设置为指定值
* 参数:
* board - 要初始化的二维数组
* r - 数组行数
* c - 数组列数
* set - 要设置的值('0'初始化雷阵, '*'初始化显示阵)
*/
void Initboard(char board[][100], int r, int c, char set)
{
int i, j;
for (i = 0; i < r; i++)
for (j = 0; j < c; j++)
board[i][j] = set;
}
/*
* 函数名: Displayboard
* 功能: 打印游戏棋盘界面,带行列坐标显示
* 参数:
* board - 要显示的二维数组
* r - 数组行数
* c - 数组列数
* startRow - 显示起始行(用于排除边框)
* startCol - 显示起始列(用于排除边框)
*/
void Displayboard(char board[][100], int r, int c, int startRow, int startCol)
{
int i, j;
printf(" ");
for (j = startCol; j < startCol + c - 2; j++)
printf("%2d ", j);
printf("\n ");
for (j = startCol; j < startCol + c - 2; j++)
printf("---");
printf("\n");
for (i = startRow; i < startRow + r - 2; i++) {
printf("%2d |", i);
for (j = startCol; j < startCol + c - 2; j++) {
printf(" %c ", board[i][j]);
}
printf("|\n");
}
}
/*
* 函数名: Setmine
* 功能: 随机布置地雷
* 参数:
* board - 雷阵数组
* r - 数组行数
* c - 数组列数
* count - 要布置的雷数
*/
void Setmine(char board[][100], int r, int c, int count)
{
int x, y;
int ROW = r - 2;
int COL = c - 2;
while (count) {
x = rand() % ROW + 1;
y = rand() % COL + 1;
if (board[x][y] == '0') {
board[x][y] = '1';
count--;
}
}
}
/*
* 函数名: Getminecount
* 功能: 计算指定坐标周围8个格子中的雷数
* 参数:
* mine - 雷阵数组
* x - 要查询的行坐标
* y - 要查询的列坐标
* 返回值: 周围8个格子中雷的数量(0-8)
*/
int Getminecount(char mine[][100], int x, int y)
{
int count = 0;
int i, j;
for (i = x - 1; i <= x + 1; i++) {
for (j = y - 1; j <= y + 1; j++) {
if (mine[i][j] == '1')
count++;
}
}
return count;
}
/*
* 函数名: Expand (新增功能1: 展开周围一片)
* 功能: 递归展开无雷区域,当点击位置周围没有雷时自动展开周围所有格子
* 参数:
* mine - 雷阵数组
* show - 显示数组
* x - 当前排查的行坐标
* y - 当前排查的列坐标
* r - 棋盘行数
* c - 棋盘列数
*/
void Expand(char mine[][100], char show[][100], int x, int y, int r, int c)
{
int i, j;
if (x < 1 || x > r || y < 1 || y > c)
return;
if (show[x][y] != '*')
return;
if (mine[x][y] == '1')
return;
int count = Getminecount(mine, x, y);
show[x][y] = count + '0';
if (count == 0) {
for (i = x - 1; i <= x + 1; i++) {
for (j = y - 1; j <= y + 1; j++) {
if (i >= 1 && i <= r && j >= 1 && j <= c && show[i][j] == '*')
Expand(mine, show, i, j, r, c);
}
}
}
}
/*
* 函数名: Flagmine (新增功能2: 标记雷)
* 功能: 对指定位置进行标记/取消标记,表示怀疑有雷
* 参数:
* show - 显示数组
* x - 要标记的行坐标
* y - 要标记的列坐标
*/
void Flagmine(char show[][100], int x, int y)
{
if (show[x][y] == '*')
show[x][y] = '!';
else if (show[x][y] == '!')
show[x][y] = '*';
}
/*
* 函数名: Findmine
* 功能: 扫雷游戏的核心玩法,处理用户的排查和标记操作
* 参数:
* mine - 雷阵数组(包含雷的位置信息)
* show - 显示数组(用户看到的界面)
* r - 棋盘行数
* c - 棋盘列数
* count - 雷的总数
*/
void Findmine(char mine[][100], char show[][100], int r, int c, int count)
{
int x, y;
int op;
int win = 0;
int ROWS = r + 2;
int COLS = c + 2;
while (win < r * c - count) {
printf("请选择操作: 1.排查 2.标记/取消标记\n");
if (scanf_s("%d", &op) != 1 || (op != 1 && op != 2)) {
printf("输入错误,请输入1或2!\n");
while (getchar() != '\n');
continue;
}
if (op == 2) {
printf("请输入要标记的坐标(1-%d, 1-%d):", r, c);
if (scanf_s("%d%d", &x, &y) != 2 || x < 1 || x > r || y < 1 || y > c) {
printf("坐标非法,请输入1到%d之间的数字!\n", r);
while (getchar() != '\n');
continue;
}
Flagmine(show, x, y);
} else {
printf("请输入排查坐标(1-%d, 1-%d):", r, c);
if (scanf_s("%d%d", &x, &y) != 2 || x < 1 || x > r || y < 1 || y > c) {
printf("坐标非法,请输入1到%d之间的数字!\n", r);
while (getchar() != '\n');
continue;
}
if (show[x][y] == '!' || show[x][y] == '*') {
if (mine[x][y] == '1') {
printf("很遗憾,你被炸死了!\n");
int i, j;
for (i = 1; i <= r; i++) {
for (j = 1; j <= c; j++) {
if (mine[i][j] == '1')
show[i][j] = '@';
}
}
Displayboard(show, ROWS, COLS, 1, 1);
return;
} else {
Expand(mine, show, x, y, r, c);
win = 0;
int i, j;
for (i = 1; i <= r; i++) {
for (j = 1; j <= c; j++) {
if (show[i][j] >= '0' && show[i][j] <= '8')
win++;
}
}
Displayboard(show, ROWS, COLS, 1, 1);
}
} else {
printf("该位置已被排查,请重新输入坐标!\n");
}
}
}
if (win >= r * c - count) {
printf("恭喜你!你不怕危险,敢于排除万难取得胜利!\n");
int i, j;
for (i = 1; i <= r; i++) {
for (j = 1; j <= c; j++) {
if (mine[i][j] == '1')
show[i][j] = '!';
}
}
Displayboard(show, ROWS, COLS, 1, 1);
}
}
此代码只能提供一个思路,并非最优解。