一、 文件的创建
首先要创建3个文件,作用分别如下:
game.h
头文件:负责将一些变量存储,方便后期的修改和调用——调用的方式:
#include "game.h"
//注意,这是自定义的函数,所以要用到双引号而不是<>,<>是库函数调用时用到的符号
game.c
源文件:为了避免游戏的主程序过于冗长,把游戏中所需要的程序单独放在game.c里,这样可以使得游戏的主程序看起来更加清晰。
test.c
存放游戏主程序的文件
二、游戏思路的梳理以及变量的定义
1、思路的梳理
先定义一个函数,用来囊括游戏的所有进程,我们定义他为 Game 函数
下面是Game函数内部的构建
首先,我们定义‘0’为没有雷的地方,‘1’为有雷的地方。
这个步骤就需要利用到二维数组了,我们的目的是要做出来 9 * 9的扫雷棋盘。所以就需要用到
9 * 9的二维数组,而是用到了 11 * 11的二维数组。但实际上并非如此,为什么呢?
在扫雷的过程中,点击某一个地方(在c语言里用的是输入坐标的形式),如果是雷,则游戏结束。
而如果不是雷呢?那我们就需要计算点击(或输入)的地方周围有多少个雷(方法会在后面介绍到)。
但是如果你点击(或者输入的地方)在扫雷棋盘的边缘呢(如图一)?周围你并没有定义数组,这样计算起来会比较麻烦。
为了避免这个问题,我们直接创建 11 * 11的二维数组,在判断的过程中用到 11 * 11的部分,而在游戏的过程中的时候显示只用显示 9 * 9的部分即可,这样可以很方便地解决许多问题。
9 * 9 扫雷棋盘
图1
这样,我们就能开始定义了。
但是在这里又出现了一个问题:我们需要把雷的信息体现在屏幕上。如果我们定义没有雷是‘0’的话,假设检测到雷的数量是1,那么我们把‘1’替换到这个位置,那么这个‘1’到底是显示的雷的数量,还是这里原本就有一个雷?
针对这个问题,我们就需要设计两个扫雷棋盘,一个用来打印在屏幕上,一个用来放置、检测雷的数量。
对于打印在屏幕上扫雷棋盘、判断雷的扫雷棋盘我们都定义为char类型
这个函数只是需要执行指令,而不需要有返回值,所以我们定义他为viod类型
对于印在屏幕上扫雷棋盘,我们定义为 char mine
对于判断雷的扫雷棋盘,我们定义为 char show
下一步:既然知道了我们应该用二维数组 char 和函数 InitBoard 来实现,我们就需要对数组进行初始化。
在这里,我定义一个函数为 InitBoard 用来初始化,为了确认该数组是否完成初始化,我们用另一个函数 DisplayBoard 来实现数组的打印。
下面就是构建布置雷(SetMine)和排查雷(FindMine)
大概处理好游戏的内容的时候,我们开始构建扫雷游戏的主体
2. 程序的构建
首先在test.c写一个程序的框架
用 menu函数来显示给用户的提示
int main()
{
menu();
return 0;
}
void menu()
{
printf("*******************\n");
printf("*** 1. play ***\n");
printf("*** 0. exit ***\n");
printf("*******************\n");
}
(1)我们需要一个初始界面来让用户选择是‘开始游戏’还是‘退出游戏’。
接下来就需要有我们的提示和用户的输入了,这几行代码直接在主程序中完成就可以。
int main()
{
int input = 0;
do
{
menu();
printf("请输入你的选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("你已经成功退出游戏,感谢游玩\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
return 0;
}
这里让用户输入0 / 1是为了方便让下面的do---while循环结构中的while判断是否可以继续运行。
如果用户输入1: 为真,执行语句
0: 为假,不再执行上面的循环
利用scanf让用户输入后,在do while 循环里嵌套switch语句用来执行开始游戏、退出游戏。
如果用户选择‘1’即开始游戏,则执行 game函数后再跳出。
接下来讲解game函数是如何构建的
void game()
{
//构建一个11*11的棋盘
char mine[ROWS][COLS];
char show[ROWS][COLS];
//初始化键盘
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印初始化内容
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
//排查雷
FindMine(mine, show, ROW, COL);
}
如图,经过前面的思路梳理,我们不难写出game函数的框架。
但是为什么多出来 ROWS COLS ROW COL这几个变量呢?
让我先对变量进行一个解释:
ROWS:判断雷棋盘的行数
COLS:判断雷棋盘的列数
ROW:显示棋盘的行数
COL:显示棋盘的列数
为了我们后续改变难度容易一些,所以用变量代替数字,方便替换以及观察
为了能让在test.c文件和game.c文件都能使用以上变量,我们不妨在game.h里定义这几个变量,在game.c和test.c里引用即可。
game.h如下:
#include <time.h>
#define ROWS ROW + 2
#define COLS COL + 2
#define ROW 9
#define COL 9
#define EASY_COUNT 10
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
void DisplayBoard(char board[ROWS][COLS], int row, int col);
void SetMine(char board[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
time.h文件和Easy_Count会在稍后解释。
此时需要在test.c中添加
#include "game.h"
现在开始构建game函数的内部
函数初始化棋盘:InitBoard
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
实参需要传递四个:
mine / show, ROWS, COLS, '0' / '*'
注意:为什么这里写‘0’和‘*’?
因为在游戏开始的时候,玩家看到的是没有雷布置的一张图,我们用星号‘*’表示。
而需要安放雷的我们用‘0’表示。
所以形参写为了:
char board[ROWS][COLS], int rows, int cols, char set
下面就是利用两个for循环遍历两个数组实现完全初始化。
打印初始化棋盘:DisplayBoard
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("******扫雷游戏*****\n");
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
看代码可以知道,我这里写了3个for循环。为什么要三个呢?
第一个for循环目的就是要打印行号和列号,方便读者寻找和输入坐标排雷。
剩下两个for循环是为了遍历数组打印字符。
布置雷函数:SetMine
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
注:EasyCount在这里代表雷的个数,已经在game.h中定义过了。
里面用到了rand函数,所以我们要在test.c文件中添加这一行代码:
srand((unsigned int)time(NULL));
在这里利用了time函数,是为了让种子时刻变化,保证出现的随机值每次会有变化。这里用系统现在的时间减去1970年1月1日的返回值。如此,为了确保x,y变量在 0 - 9之间,利用
rand() % ROW / COL + 1来取 0 - 9的值
这里就出现了1个问题:如果在这10次中有两个一样的坐标会怎么样呢?
那这里就需要一个if函数来判断要存储雷的这个地方是不是已经有雷了。如果没有雷,则放雷
且让count--,直到count为0,跳出循环,不再布置雷。这样,这个函数至少循环了10次。
排查雷的函数:FindMine 标记雷的函数:NumberMine
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 - EASY_COUNT)
{
printf("其请输入你要查找的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("恭喜你被炸死了,能力不行,速速开下一把\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
int count = NumberMine(mine, x, y); //计算周围的炸弹数量
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("你输入的数字有问题,请重新输入\n");
}
}
if (win == ROW * COL - EASY_COUNT)
{
printf("恭喜你,游戏胜利\n");
DisplayBoard(mine, ROW, COL);
}
}
int NumberMine(char mine[ROWS][COLS], int x, int y)
{
return((mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] +
mine[x - 1][y] + mine[x][y] + mine[x + 1][y] +
mine[x - 1][y + 1] + mine[x][y + 1] + mine[x + 1][y + 1]) - '0' * 9);
}
首先我们要清楚获胜的条件:即 排查的数目 = 区域总数 - 雷的数目
设定一个win变量,如果没死,那么win++
为了保证严谨性,如果不小心输错了坐标,那么给出相应的提示。
而当我们排查的时候需要用到标记雷的函数:NumberMine
这个函数的原理是:
0的ACSCII值比1小一个,那么计算包括排查的地方周围 3 * 3的地方的值除以‘0’,得出来的结果就是有几个‘1’,即有几个雷。
这样整个扫雷游戏就结束了
完整代码如下:
game.h:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROWS ROW + 2
#define COLS COL + 2
#define ROW 9
#define COL 9
#define EASY_COUNT 10
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
void DisplayBoard(char board[ROWS][COLS], int row, int col);
void SetMine(char board[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
test.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void game()
{
//构建一个11*11的棋盘
char mine[ROWS][COLS];
char show[ROWS][COLS];
//初始化键盘
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印初始化内容
DisplayBoard(show, ROW, COL);
//布置雷
SetMine(mine, ROW, COL);
//排查雷
FindMine(mine, show, ROW, COL);
}
void menu()
{
printf("*******************\n");
printf("*** 1. play ***\n");
printf("*** 0. exit ***\n");
printf("*******************\n");
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
printf("请输入你的选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("你已经成功退出游戏,感谢游玩\n");
break;
default:
printf("输入错误,请重新输入\n");
break;
}
} while (input);
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)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
printf("******扫雷游戏*****\n");
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
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 - EASY_COUNT)
{
printf("其请输入你要查找的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("恭喜你被炸死了,能力不行,速速开下一把\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
int count = NumberMine(mine, x, y); //计算周围的炸弹数量
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);//为啥要这的了?
win++;
}
}
else
{
printf("你输入的数字有问题,请重新输入\n");
}
}
if (win == ROW * COL - EASY_COUNT)
{
printf("恭喜你,游戏胜利\n");
DisplayBoard(mine, ROW, COL);
}
}
int NumberMine(char mine[ROWS][COLS], int x, int y)
{
return((mine[x - 1][y - 1] + mine[x][y - 1] + mine[x + 1][y - 1] +
mine[x - 1][y] + mine[x][y] + mine[x + 1][y] +
mine[x - 1][y + 1] + mine[x][y + 1] + mine[x + 1][y + 1]) - '0' * 9);
}