[从0开始的C语言] 适合初学者的扫雷(详细解析+流程图+代码)

目录

0. 前言

1. 扫雷游戏介绍

2. 简化版扫雷游戏概述

2.1 程序结构分析

2.1.1 程序流程图(概述)

2.1.2 流程图

3. 程序教学

3.1 菜单创建

3.2 初始化棋盘

3.3 设置地雷

3.3.1 输入地雷数量

3.3.2 设置地雷

3.3.3 显示棋盘

3.4 查找地雷

4. 基础入门扫雷完整代码展示

5. 扫雷扩展(进阶)


0. 前言

大家好!今天我们来学习如何用 C 语言编写一个简单的 扫雷游戏。这个程序将帮助你熟悉 C 语言中的数组、循环、条件判断、自定义函数以及如何进行用户输入输出。

1. 扫雷游戏介绍

扫雷游戏(Minesweeper)是一款经典的单人电脑益智游戏,最早出现在 1989 年随 Windows 3.0 系统发布。游戏的核心玩法是通过逻辑推理,避免点击到隐藏在格子下的地雷。

玩家的目标是打开所有没有地雷的方格,同时避免点击到包含地雷的格子。玩家可以通过数字提示来推测每个方格周围的地雷位置。

扫雷游戏网页版 - Minesweeper

2. 简化版扫雷游戏概述

在这个扫雷游戏中,我们将为玩家提供一个 9x9(也可以修改为其他大小) 的游戏棋盘,玩家通过输入坐标来“访问”某个位置。如果该位置是地雷,游戏结束;如果不是,系统将显示该位置周围有多少个地雷。目标是清除所有非雷格子,并避免点击到地雷。

我们将分步实现:

  • 初始化棋盘:创建两个棋盘,一个存储地雷信息,另一个用于显示给玩家。
  • 设置地雷:随机生成地雷的分布。
  • 显示棋盘:显示玩家的游戏界面。
  • 查找地雷:用户选择一个位置,判断是否为地雷,并显示该位置周围地雷的数量。
  • 判断胜负:判断是否全部非雷区域已被清除。

2.1 程序结构分析

我们的扫雷游戏程序主要由以下几个部分组成:

  1. menu:显示游戏菜单,选择是否开始游戏或退出。
  2. game:管理整个游戏流程,包括设置雷数、初始化棋盘、设置地雷、显示棋盘、查找地雷等。
  3. InitBoard:初始化棋盘,将每个格子填充为默认值。
  4. SetMine:随机选择地雷的位置。
  5. DisplayBoard:显示当前的棋盘状态。
  6. GetMineCount:根据周围的格子,计算该位置周围有多少个地雷。
  7. FindMine:让玩家输入坐标并查找该位置是否有地雷,更新棋盘并判断游戏是否结束。

2.1.1 程序流程图(概述)

  1. 开始

    • 进入 main 函数。
  2. 显示菜单

    • 调用 menu(),显示用户选择的选项(开始游戏或退出)。
  3. 选择操作

    • 用户输入选择,进入 operation()
  4. 选择开始游戏(当用户输入 1

    • 调用 game() 函数,进行游戏。
    • 设置雷的数量(调用 settings())。
  5. 初始化棋盘

    • 调用 InitBoard() 初始化 mine(雷区)和 show(展示区)棋盘。
  6. 设置地雷

    • 调用 SetMine() 随机设置地雷位置。
  7. 显示棋盘

    • 显示初始化后的展示棋盘(show)不显示设置后的雷区棋盘(mine)。
  8. 开始寻找地雷

    • 进入 FindMine(),玩家输入坐标来查找地雷。
  9. 检查游戏状态

    • 如果玩家点击了地雷,游戏失败并显示评分。
    • 如果清除的空格数达到目标,游戏胜利并显示评分。
  10. 退出或重新开始

  • 用户选择退出游戏或重新开始(调用 clc() 清屏)

2.1.2 流程图

3. 程序教学

3.1 菜单创建

创建一个菜单展现进入退出游戏功能,这里主要使用的是do...while循环,当输入1时进入游戏,0时while(0)中0为假,结束循环。

打印菜单界面:

void menu()
{
	//Display Option for Users(Menu) 
	printf("************************************\n");
	printf("*******        1. PLAY       *******\n");
	printf("*******        0. EXIT       *******\n");
	printf("************************************\n");
}

以下为菜单界面:

do...while循环 + switch语句:

void operation()
{
	srand((unsigned int)time(NULL));//随机数发生器的初始化函数,之后会用到 rand()
	int select = 0;
	do
	{
		menu();// 菜单选项
		printf("Please Select the Option: >>> ");
		scanf("%d",&select);
		switch (select)
		{
		case 1:
			printf("Minesweeper ---- GAME START !\n");
			game();
			break;
		case 0:
			printf("Game Exit\n");
			break;
		default:
			printf("Not a Valid Option, Please Re-Select\n");
			break;
		}
	} while (select);

}

int main()
{
	operation();//调用operation自定义函数
	return 0;
}

3.2 初始化棋盘

首先,我们需要为扫雷游戏创建一个棋盘。扫雷的过程中,布置的雷和排查出的雷的信息都需要存储,所以我们需要⼀定的数据结构来存储这些信息。我们的第一个目标是n*n的最基础版扫雷,而创建棋盘就能非常容易联想到二维数组。所以,我们可以先创建一个个9*9的数组来存放信息。那如果这个位置布置雷,我们就存放1,没有布置雷就存放0

为了避免越界问题,我们在设计时将棋盘数组扩展为11×11,但实际雷区仍为中间的9×9区域,周围一圈不布置雷。这样可以轻松处理坐标边界问题。

当检查某个坐标(例如 (2,5))时,我们会访问其周围的8个位置,统计附近的雷数。如果某个位置(如 (8,6))靠近边界,数组扩展能防止越界。

为了避免混淆雷信息和统计数据,我们不在存储雷的数组中记录雷数,而是使用另一个数组存储排查后的雷数信息。这样可以方便区分和打印数据,用作排雷参考。

我们将棋盘分为两部分:一个是显示给用户看的棋盘(show),另一个是存储地雷位置的棋盘(mine)。

show 用来显示玩家的操作界面,初始时全部用 * 填充,表示还没有打开。

mine 用来存储地雷的位置,初始时用字符 0 (‘0’)填充,地雷位置用 字符1(‘1’)标识。

void game()
{
	
	char mine[ROWS][COLS] = { 0 };// operation 
	char show[ROWS][COLS] = { 0 };// user interface
	// Initialization
	InitBoard(mine, ROWS, COLS, '0'); // user COULD NOT see
	InitBoard(show, ROWS, COLS, '*'); // user COULD see

}

这里我们可以通过两个for循环来填充元素,同时我们也可以通过定义ROW和COL的数值来设置一下棋盘的大小

void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

#define ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2

在设置完后,我们可以打印一下当前棋盘查看当前设置的情况。可以在打印的时候加上行列序号从而方便查看坐标。

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	printf("---------Minesweeper---------\n");
	for (int i = 0; i <= col; i++)
	{
		printf("%d ", i);//打印列号
	}
	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		printf("%d ", i);//打印行号
		for (int j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");//打印换行
	}
	printf("-----------------------------\n");
}

DisplayBoard(show, ROW, COL);
DisplayBoard(mine, ROW, COL);

3.3 设置地雷

3.3.1 输入地雷数量

通过scanf函数输入地雷数量

void settings()
{
	printf("Please Set the Number of Mine:\n");
	scanf("%d", &mine_count);
}

settings();
3.3.2 设置地雷

地雷的设置是通过随机数生成的。我们会根据用户输入的雷数,在棋盘上随机放置地雷。

void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int count = mine_count;//雷的数量
	int x = 0;
	int y = 0;
	while (count)// the while loop will run 10 times at least
	{
		x = rand() % row + 1; //生成 1 到 row 的随机数
		y = rand() % row + 1;//生成 1 到 col 的随机数

		if (mine[x][y] != '1')// 如果该位置没有地雷
		{
			mine[x][y] = '1';// 设置为地雷
			count--;
		}
	}
}

SetMine(mine, ROW, COL);

SetMine 函数中,首先使用 rand() 函数生成随机坐标 (x, y),然后检查该位置是否已经有地雷(即 mine[x][y] 是否为 '1')。如果没有地雷,我们就将该位置设置为 '1',表示地雷,并减少雷数。这个过程会一直持续,直到雷数设置完成。

3.3.3 显示棋盘

显示棋盘的函数会根据当前的状态将棋盘展示给玩家。玩家看到的棋盘是 show 数组,它的初始状态为 '*',当玩家点击某个位置时,系统会将该位置显示为数字,表示该位置周围的地雷数量。这里详细的流程图可以参考3.2初始化棋盘的末尾,已经讲过了所以就一笔带过。

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
    printf("---------Minesweeper---------\n");
    for (int i = 0; i <= col; i++) {
        printf("%d ", i);  // 输出列号
    }
    printf("\n");
    for (int i = 1; i <= row; i++) {
        printf("%d ", i);  // 输出行号
        for (int j = 1; j <= col; j++) {
            printf("%c ", board[i][j]);  // 输出每个格子的值
        }
        printf("\n");
    }
    printf("-----------------------------\n");
}

DisplayBoard 函数通过嵌套循环输出每个位置的内容。在此过程中,列号和行号都会显示出来,帮助玩家定位棋盘的位置。

DisplayBoard(show, ROW, COL);// 展现给用户的棋盘
DisplayBoard(mine, ROW, COL); // 仅供自己检查的棋盘(程序全部完成时此行代码须注释掉)

比如说我在此处设计的地雷数量为10,我们可以打印一下show数组和mine数组来检查一下展现给用户的界面和地雷实际的布置情况(“1”为雷)。在检查无误后,我们便可以进行下一步的开发。

左边是开发时方便我们自己查看的界面,右边是实际程序运行时用户看到的界面。

3.4 查找地雷

当玩家选择一个位置时,我们需要判断该位置是否为地雷。如果是地雷,游戏结束,给用户展示雷的排布情况;如果不是,我们计算该位置周围的地雷数量,并更新棋盘。


void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
    int x = 0, y = 0;
    int win = 0;

    while (win < row * col - mine_count) {
        //请输入要查看的坐标 (x y):
        printf("Please Enter the Coordinate You Want to Look Up : >>> ");
        scanf("%d %d", &x, &y);

        if (x >= 1 && x <= row && y >= 1 && y <= col) {
            if (mine[x][y] == '1') {
                printf("BOOM !---YOU DIED---GAME OVER\n");//踩雷了!游戏结束。
                break;
            } else {
                int count = GetMineCount(mine, x, y);
                show[x][y] = count + '0';
                DisplayBoard(show, row, col);
                win++;
            }
        } else {
            printf("Coordinate NOT VALID ! \n");//坐标无效,请重新输入!
        }
    }

    if (win == row * col - mine_count) {
        printf("YOU WIN\n");//恭喜你,赢得了游戏!
    }
}

FindMine(mine, show, ROW, COL);//Find Mine

FindMine 函数中,我们首先检查用户输入的坐标是否合法。如果合法且没有踩到地雷,我们就计算该位置周围的地雷数量,并更新 show 棋盘。

static int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
	return mine[x - 1][y - 1] 
		+ mine[x - 1][y] 
		+ mine[x - 1][y + 1] 
		+ mine[x][y - 1] 
		+ mine[x][y + 1] 
		+ mine[x + 1][y - 1] 
		+ mine[x + 1][y] 
		+ mine[x + 1][y + 1] 
		- 8 * '0';
}

在这里,我们需要通过调用GetMineCount函数来计算周围地雷的个数。我们可以通过把排查的坐标表达为(x,y)通过对x,y进行加减运算,我们就可以得出所有相邻坐标的坐标表达式。通过将8个二维数组中的值相加再减去8个‘0’ ,我们就可以得到周围雷的个数。

看到这里可能有些朋友会对涉及到字符的运算感到困惑,在这里我来详细解释一下。字符串1(‘1’)的ASC II code值为49,字符串0(‘0’)的ASC II code值为48。假设我们排查的坐标周围有 3个雷,那意味着我们有3个字符1(‘1’)和5个字符0(‘0’),我们将它们相加,那也就意味着ASC II code 的值为3 * 49  +  5 * 48 = 387,此时我们再计算8个字符0的值 48 * 8 = 384 。我们用387 - 384 得到了 数值 3, 即为雷的个数。

由于x,y加减的数字范围均为[-1 1],我们在这里便可以通过循环来遍历每一个坐标,从而简化代码。

static int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
    int count = 0;
    for (int i = -1; i <= 1; i++) {
        for (int j = -1; j <= 1; j++) {
            count += (mine[x + i][y + j] - '0');
        }
    }
    return count;
}

每次用户打开一个位置后,我们都会检查是否胜利。如果玩家清除了所有非雷的格子,就表示游戏胜利,此时可以调用一下DisplayBoard来给用户展示一下雷的排布情况。

 if (win == row * col - mine_count) {
        printf("YOU WIN\n");//恭喜你,赢得了游戏!
        DisplayBoard(mine, ROW, COL);//给用户展示一下雷的排布位置
    }

4. 基础入门扫雷完整代码展示

operation.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"


void menu()
{
	//Display Option for Users(Menu) 
	printf("************************************\n");
	printf("*******        1. PLAY       *******\n");
	printf("*******        0. EXIT       *******\n");
	printf("************************************\n");
}

void settings()
{
	printf("Please Set the Number of Mine:\n");
	scanf("%d", &mine_count);
}

void game()
{
	settings();
	char mine[ROWS][COLS] = { 0 };// operation 
	char show[ROWS][COLS] = { 0 };// user interface
	// Initialization
	InitBoard(mine, ROWS, COLS, '0'); // user COULD NOT see
	InitBoard(show, ROWS, COLS, '*'); // user COULD see

	//Print Board
	DisplayBoard(show, ROW, COL);
	//DisplayBoard(mine, ROW, COL); // Display before set, check only

	// Set Mine
	SetMine(mine, ROW, COL);
	//DisplayBoard(mine, ROW, COL); // Admin display only

	//Find Mine
	FindMine(mine, show, ROW, COL);


}

void operation()
{
	srand((unsigned int)time(NULL));
	int select = 0;
	do
	{
		menu();// Option Selection
		printf("Please Select the Option: >>> ");
		scanf("%d",&select);
		switch (select)
		{
		case 1:
			printf("Minesweeper ---- GAME START !\n");
			game();
			break;
		case 0:
			printf("Game Exit\n");
			break;
		default:
			printf("Not a Valid Option, Please Re-Select\n");
			break;
		}
	} while (select);

}

int main()
{
	operation();
	return 0;
}

game.c

#define _CRT_SECURE_NO_WARNINGS 1

#include "game.h"


void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
	for (int i = 0; i < rows; i++)
	{
		for (int j = 0; j < cols; j++)
		{
			board[i][j] = set;
		}
	}
}

void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
	printf("---------Minesweeper---------\n");
	for (int i = 0; i <= col; i++)
	{
		printf("%d ", i);
	}
	printf("\n");
	for (int i = 1; i <= row; i++)
	{
		printf("%d ", i);
		for (int j = 1; j <= col; j++)
		{
			printf("%c ", board[i][j]);
		}
		printf("\n");
	}
	printf("-----------------------------\n");
}
// Setting Mine = Finding 10 coordinates randomly on the board.
// x: 1-9
// y; 1-9
void SetMine(char mine[ROWS][COLS], int row, int col)
{
	int count = mine_count;
	int x = 0;
	int y = 0;
	while (count)// the while loop will run 10 times at least
	{
		x = rand() % row + 1; // If any number is divided by 9, the reminder == range [0,8]
		y = rand() % row + 1;// 0 + 1 = 1 ; 8 + 1 = 9, Thus we could get 1 - 9 randomly

		if (mine[x][y] != '1')// avoid duplicate
		{
			mine[x][y] = '1';// set mine (mine = '1')
			count--;
		}
	}
}



// [x-1,y-1]  [x-1,y]  [x-1,y+1]
// [x,y-1]    [x,y]    [x,y+1]
// [x+1,y-1]  [x+1,y]  [x+1,y+1]
// '1' - '0' = 1 (ASCII code 49 - 48 = 1)
static int GetMineCount(char mine[ROWS][COLS], int x, int y)
{
	/*return mine[x - 1][y - 1] 
		+ mine[x - 1][y] 
		+ mine[x - 1][y + 1] 
		+ mine[x][y - 1] 
		+ mine[x][y + 1] 
		+ mine[x + 1][y - 1] 
		+ mine[x + 1][y] 
		+ mine[x + 1][y + 1] 
		- 8 * '0';*/
	int count = 0;
	for (int i = -1; i <= 1; i++)
	{
		for (int j = -1; j <= 1; j++)
		{
			count += (mine[x + i][y + j] - '0');

		}
	}
	return count;
}


void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
	int x = 0;
	int y = 0;
	int win = 0;
	
	while (win < row * col - mine_count) // total space - num of mine ex: 9 * 9 -10
	{
		printf("Please Enter the Coordinate You Want to Look Up : >>> ");
		scanf("%d %d", &x, &y);

		if (x >= SC && x <= row && y >= SC && y <= col)// SC = 1 (start coordinate)
		{
			if (mine[x][y] == '1')//coord is mine
			{
				printf("BOOM !---YOU DIED---GAME OVER\n");
				DisplayBoard(mine, ROW, COL);
				break;
			}
			else //coord is not mine
			{
				int count = GetMineCount(mine, x, y);
				show[x][y] = count + '0';
				DisplayBoard(show, ROW, COL);
				win++;
			}
		}
		else // corrd out of board range
		{
			printf("Coordinate NOT VALID ! \n");
		}
		
	}

	if (win == row * col - mine_count && mine_count < row * col)
	{
		printf("YOU WIN\n");
		DisplayBoard(mine, ROW, COL);
	}

}

game.h

#define _CRT_SECURE_NO_WARNINGS 1

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

#define ROW 9
#define COL 9

#define ROWS ROW + 2
#define COLS COL + 2

#define EASY_COUNT 10

#define SC 1
//Initialization 11 * 11
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);

//Print Board 9 * 9
void DisplayBoard(char board[ROWS][COLS], int row, int col);

void SetMine(char mine[ROWS][COLS], int row, int col);

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

// int GetMineCount(char mine[ROWS][COLS], int x, int y);

5. 扫雷扩展(进阶)

正在更新中......

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值