目录
1.基本思路
扫雷本质上是两个二维数组,一个二维数组是表(是我们进行扫雷操作的界面)(命名为Scan[][]),另一个二维数组是里(存放着本局随机生成的雷的位置信息)(明明为Bomb[][])。
扫雷程序需要实现的功能包括:
1.初始化两个数组。
2.随机布置本局中雷的位置 。
3.对输入坐标进行判断。 3.1.该位置是雷。
3.2该位置不是雷。3.2.1.显示该位置及周围8格雷的数量。
4.判断游戏是否结束。
2.难点
2.1数组的设置
当玩家输入一个坐标时,需要遍历该坐标周围的8个并计算所有雷的数量,将其显示在改坐标上。
当玩家输入的坐标位于边角时,坐标周围不足8格,这就会导致数组的溢出。因此我们设置的数组行(列)数=想要设置的棋盘行(列)数+2。
因此,定义常量如下:
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define BOOM 10
2.2 使用随机数设置雷
使用rand()函数设置随机数,需要先使用srand()函数设置随机数种子。可以用时间作为随机数种子。
使用rand()、srand()函数时,需要引用的头文件为#include<stdlib.h>
使用time()函数时,需要应用的头文件为#include<time.h>
因此,随机数的生成代码如下:
#include<stdlib.h>
#include<time.h>
srand((unsigned int)time(NULL));
int ran = rand();
随机生成雷的行数应该在1~ROW(行数)之间,列数应该在1~COL(列数)之间。使用%x取余数时,随机数的范围为0~x-1。所以行数和列数生成的代码如下:
i = rand() % row + 1;
j = rand() % col + 1;
2.3统计雷的个数
当玩家输入一个坐标时,需要判断该位置是不是雷。如果这个位置不是雷,需要遍历并统计该位置周围8格中雷的数量。
坐标及周围8格的坐标如下:
所以使用循环[ x - 1 + i ][ y - 1 + j ],就可以遍历这9个格子。
因为数组的中的雷(1)和空格子(0)是用char类型存储的,但统计的雷的个数应该是int类型的,所以我们该如何将char类型和int类型联系起来呢?
答案是ASCII码,'0'的ASCII码为48,'1'的ASCII码为49,差值正好为1,这样就可以代替雷的个数了。
统计函数代码如下:
int CountBomb(char bomb[ROWS][COLS], int x, int y)
{
int i, j;
int result = 0;
for (i = 0; i <= 2; i++)
{
for (j = 0; j <= 2; j++)
{
result = result + bomb[x - 1 + i][y - 1 + j] - '0';
}
}
return result;
}
2.4递归自动扩展无雷格子周围格子
在这个游戏中,当一个位置及周围8个格子没有雷时,该位置显示的数字为0。但这个信息对于排查周围雷的个数并没有任何帮助。因此我们希望实现,当一个位置及周围都没有雷时,排查的范围就自动以该坐标周围的8个格子为起点,向外遍历,直到某一位置的显示的数字不再为0。
递归扩展无雷格子周围格子函数代码如下:
//2.4.2扩展无雷格子周围格子函数
void ExpandBoard(char bomb[ROWS][COLS], char scan[ROWS][COLS], int x, int y)
{
//统计周围8格雷的个数
int count = CountBomb(bomb, x, y);
//周围8格有雷
if (count != 0 && scan[x][y] == '*')
{
scan[x][y] = count + '0';
}
//周围8格无雷,递归,自动向外扩展排查
if (count == 0 && scan[x][y] == '*')
{
scan[x][y] = ' ';
if (scan[x - 1][y - 1] == '*' && x - 1 > 0 && x - 1 <= ROW && y - 1 > 0 && y - 1 <= COL)
ExpandBoard(bomb, scan, x - 1, y - 1);
if (scan[x - 1][y] == '*' && x - 1 > 0 && x - 1 <= ROW && y > 0 && y <= COL)
ExpandBoard(bomb, scan, x - 1, y);
if (scan[x - 1][y + 1] == '*' && x - 1 > 0 && x - 1 <= ROW && y + 1 > 0 && y + 1 <= COL)
ExpandBoard(bomb, scan, x - 1, y + 1);
if (scan[x][y - 1] == '*' && x > 0 && x <= ROW && y - 1 > 0 && y - 1 <= COL)
ExpandBoard(bomb, scan, x, y - 1);
if (scan[x][y] == '*' && x > 0 && x <= ROW && y > 0 && y <= COL)
ExpandBoard(bomb, scan, x, y);
if (scan[x][y + 1] == '*' && x > 0 && x <= ROW && y + 1 > 0 && y + 1 <= COL)
ExpandBoard(bomb, scan, x - 1, y + 1);
if (scan[x + 1][y - 1] == '*' && x + 1 > 0 && x + 1 <= ROW && y - 1 > 0 && y - 1 <= COL)
ExpandBoard(bomb, scan, x + 1, y - 1);
if (scan[x + 1][y] == '*' && x + 1 > 0 && x + 1 <= ROW && y > 0 && y <= COL)
ExpandBoard(bomb, scan, x + 1, y);
if (scan[x + 1][y + 1] == '*' && x + 1 > 0 && x + 1 <= ROW && y + 1 > 0 && y + 1 <= COL)
ExpandBoard(bomb, scan, x + 1, y + 1);
}
}
2.5判断游戏是否结束
每次玩家输入坐标结束后,对scan[][]数组进行遍历,当'*'的数量等于设置的雷的数量时,扫雷成功。
注:(不会存在*下面包括雷和空格的情况,因为雷和*的数量是对应的,如果存在雷和空格,说明必有一颗雷被踩了,这样数才是对的。但是踩到雷就会立即结束,不会进行到这一步判断了。)
//遍历,Scan[][]==*的数量
for (i = 1; i <= ROW; i++)
{
for (j = 1; j <= COL; j++)
{
if (scan[i][j] == '*')
win++;
}
}
3.源代码
3.1头文件 game.h
#pragma once
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define BOOM 10
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
void menu();
void game();
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
void DisplayBoard(char board[ROWS][COLS], int row, int col);
void FindBomb(char bomb[ROWS][COLS], char scan[ROWS][COLS], int row, int col);
int CountBomb(char bomb[ROWS][COLS], int x, int y);
void ContinueMenu();
void ExpandBoard(char bomb[ROWS][COLS], char scan[ROWS][COLS], int x, int y);
3.2源文件 扫雷.c
#define _CRT_SECURE_NO_WARNINGS
//扫雷游戏
#include"game.h"
int main()
{
int input;
srand((unsigned int)time(NULL));
menu();
again:
printf("请选择:");
scanf("%d",&input);
switch (input)
{
case 1:
game();
ContinueMenu();
goto again;
break;
case 2:
printf("******退 出 游 戏******\n");
break;
default:
printf("**输入无效,请重新输入**\n");
printf("\n");
goto again;
}
return 0;
}
3.3源文件 子函数.c
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
//1.菜单函数
void menu()
{
printf("*********扫 雷*********\n");
printf("****1.Play 2.Exit****\n");
printf("***********************\n");
}
//2.游戏函数
void game()
{
printf("\n");
printf("******开 始 游 戏******\n");
//存放随机生成的雷的数组
char Bomb[ROWS][COLS] = {0};
//存放排查出的雷的数组
char Scan[ROWS][COLS] = { 0 };
//2.1初始化
InitBoard(Bomb, ROWS, COLS, '0');
InitBoard(Scan, ROWS, COLS, '*');
//2.2展示棋盘
DisplayBoard(Scan, ROW, COL);
//2.3布置雷
SetBomb(Bomb, ROW, COL);
//展示雷的位置
//DisplayBoard(Bomb, ROW, COL);
//2.4查找雷
FindBomb(Bomb, Scan, ROW, COL);
}
//2.1初始化函数
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i, j;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
//2.2棋盘展示函数
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i, j;
//打印行号
for (i = 0; i <= col; i++)
{
printf("%2d", i);
}
printf("\n");
//打印分割线
//打印列号&棋盘
for (i = 1; i <= row; i++)
{
printf("%2d",i);
for (j = 1; j <= col; j++)
{
printf(" %c", board[i][j]);
}
printf("\n");
}
}
//2.3布置雷函数
void SetBomb(char board[ROWS][COLS],int row, int col)
{
int i, j, k;
for (k = 0; k < BOOM;)
{
i = rand() % row + 1;
j = rand() % col + 1;
if (board[i][j] == '0')
{
board[i][j] = '1';
k++;
}
else
{
;
}
}
}
//2.4查找雷函数
void FindBomb(char bomb[ROWS][COLS], char scan[ROWS][COLS], int row, int col)
{
int i, j;
int x, y;
int win = 0;
while (win != BOOM)
{
win = 0;
//1.输入坐标
printf("请输入要排查的坐标:");
scanf("%d%d", &x, &y);
//1.1坐标有效
if (x > 0 && x <= row && y > 0 && y <= col)
{
//1.1.1踩雷
if (bomb[x][y] == '1')
{
printf("*****踩雷,游戏结束*****\n");
DisplayBoard(bomb, ROW, COL);
break;
}
//1.1.2不是雷
else
{
ExpandBoard(bomb, scan, x, y);
}
DisplayBoard(scan, ROW, COL);
//DisplayBoard(bomb, ROW, COL);
//遍历,Scan[][]==*的数量
for (i = 1; i <= ROW; i++)
{
for (j = 1; j <= COL; j++)
{
if (scan[i][j] == '*')
win++;
}
}
}
//1.2坐标无效
else
printf("坐标无效,");
}
if (win == BOOM)
{
printf("***恭喜你,排雷成功!***\n");
DisplayBoard(bomb, ROW, COL);
}
}
//2.4.1统计周围8格雷的个数函数
int CountBomb(char bomb[ROWS][COLS], int x, int y)
{
int i, j;
int result = 0;
for (i = 0; i <= 2; i++)
{
for (j = 0; j <= 2; j++)
{
result = result + bomb[x - 1 + i][y - 1 + j] - '0';
}
}
return result;
}
//2.4.2扩展无雷周围格子函数
void ExpandBoard(char bomb[ROWS][COLS], char scan[ROWS][COLS], int x, int y)
{
//统计周围8格雷的个数
int count = CountBomb(bomb, x, y);
//周围8格有雷
if (count != 0 && scan[x][y] == '*')
{
scan[x][y] = count + '0';
}
//周围8格无雷,递归,自动向外扩展排查
if (count == 0 && scan[x][y] == '*')
{
scan[x][y] = ' ';
if (scan[x - 1][y - 1] == '*' && x - 1 > 0 && x - 1 <= ROW && y - 1 > 0 && y - 1 <= COL)
ExpandBoard(bomb, scan, x - 1, y - 1);
if (scan[x - 1][y] == '*' && x - 1 > 0 && x - 1 <= ROW && y > 0 && y <= COL)
ExpandBoard(bomb, scan, x - 1, y);
if (scan[x - 1][y + 1] == '*' && x - 1 > 0 && x - 1 <= ROW && y + 1 > 0 && y + 1 <= COL)
ExpandBoard(bomb, scan, x - 1, y + 1);
if (scan[x][y - 1] == '*' && x > 0 && x <= ROW && y - 1 > 0 && y - 1 <= COL)
ExpandBoard(bomb, scan, x, y - 1);
if (scan[x][y] == '*' && x > 0 && x <= ROW && y > 0 && y <= COL)
ExpandBoard(bomb, scan, x, y);
if (scan[x][y + 1] == '*' && x > 0 && x <= ROW && y + 1 > 0 && y + 1 <= COL)
ExpandBoard(bomb, scan, x - 1, y + 1);
if (scan[x + 1][y - 1] == '*' && x + 1 > 0 && x + 1 <= ROW && y - 1 > 0 && y - 1 <= COL)
ExpandBoard(bomb, scan, x + 1, y - 1);
if (scan[x + 1][y] == '*' && x + 1 > 0 && x + 1 <= ROW && y > 0 && y <= COL)
ExpandBoard(bomb, scan, x + 1, y);
if (scan[x + 1][y + 1] == '*' && x + 1 > 0 && x + 1 <= ROW && y + 1 > 0 && y + 1 <= COL)
ExpandBoard(bomb, scan, x + 1, y + 1);
}
}
//3.继续菜单
void ContinueMenu()
{
printf("\n");
printf("*****是否继续游戏?*****\n");
printf("***1.Continue 2.Exit***\n");
printf("***********************\n");
}
4.遗留问题
4.1 没有实现标记功能
4.2没有实现用鼠标操作
码力不够,没能实现用鼠标进行操作。
也是因为这个原因,我觉得每次输入坐标之前都要选择这次是要“标记”还是“排查”,这样未免泰国繁琐了,所以决定放弃“标记”功能。