目录
2.给用户一个进入游戏的循环代码,当用户不想玩游戏可以退出循环。代码如下:
1. 为什么在进入while循环前,有一个DisplayBoard函数调用?
2.打印棋盘还是像第一次那样打印全部棋盘吗?是 11x11 的棋盘打印还是 9x9 呢?
介绍初级扫雷游戏的特点:
1.该扫雷游戏不能做到输入一次坐标而展开附近领域的非雷空间,需要玩家一个一个(输入坐标)的点击。也就是需要输入71次(有10个位置是雷)输入才可以遍历这个扫雷游戏。
2.雷的数量可以设置,不仅仅是10颗雷,也可以是20颗雷,再多一点也行。
3.棋盘的大小也可以设置不同的尺寸,可以是 9x9 ,也可以是 12x12 ,再多一点也行。
首先,创建三个文件,一个是 game.c 文件来存放游戏实现的代码(即函数的定义);第二个是main.c文件,来存放调用函数的必要代码,但 game.c 存放的是功能实现代码(即函数的定义),main函数需要调用game.c 文件的函数;第三个是game.h文件,来包含必要的其他头文件和声明 game.c 里的函数,这样 main.c 和 game.c 文件包含 game.h 文件就好了,不需要每个函数都包含一次必要的其他库文件,如:<stdio.h> , <stdlib.h> 和 <time.h> 。
开始:
1.给用户一个游戏开始的页面:
而后把这个代码分装到game.c文件中并取名为menu函数,在game1.h中声明该函数。
2.给用户一个进入游戏的循环代码,当用户不想玩游戏可以退出循环。代码如下:
当用户输入 1 时,游戏开始。把游戏实现的代码取名为game函数。
当用户输入 0 时,退出游戏。
当用户输入 其他数字 时,用户需要重新输入。
3.设置棋盘(本文以 9x9 棋盘为例子来做介绍)
首先分析一下棋盘的设置,棋盘是 9x9 的话,我们是需要 11x11 的实际大小,9x9 是实际操作空间,多出来的两列两行是方便统计附近的雷数目。
如图所示,当统计四周的雷数量时,如果五角星的位置在棋盘的顶角将导致统计失败,因为没有亲自开辟空间,不知道附近空间存在什么值。为了避免这样的错误发生,我们就要亲自开辟 11x11 的空间。那我们就需要用到二维数组了,那我们用什么类型的数组呢?
既然为了不让雷直接暴露,我们可以拿一个东西去覆盖它的位置,用数字着实有些晃眼,这里使用“ * ”号来覆盖整个棋盘,每当用户输入一个坐标值,“ * ”号展开,露出它的 “ 真面目 ” 。
所以我们使用字符数组来存放我们这些信息。
代码如下:
对棋盘初始化: 一个函数可以完成对mine棋盘和show棋盘的初始化,关键点就是在传参时把要赋予的值也传进函数里
扫雷游戏代码较多,分步进行时可以通过一些手段来检查代码是否符合自己的要求
此时项目中的代码应该分装成这样:
运行后如图:
得到这样的棋盘图,说明初始化成功!
但我们只想要用户看到show棋盘,所以待会我们要把打印mine棋盘的代码注释掉(这步就不展示在文章里了)。
4.设置雷
设置雷在我们的mine棋盘上。我们就需要用到随机数来随机给不同的坐标来埋雷。
在main函数里加上一句代码 srand( (unsigned)time(NULL) ); 来改变随机数种子
在 main.c 文件里的 game函数里面在加上一句代码
SetMine( mine , ROW , COL );
注意从现在开始对 main.c 和 game.h 文件都不会输入太多的代码,main.c 文件保证扫雷游戏可以按照 “ 我 ” 的思路走,但程序实现的代码大多数都是分装在 game.c 文件中,而 game.h 文件是二者的中间桥梁,统一了两边的某些常量(如ROWS,COLS等)又给 main.c 文件声明了 game.c 里的函数。
解释一下为什么 game.h 统一了两边的某些常量:
通过 main.c 和 game.c 文件包含 game.h 文件
#include "game.h"
所以当 game.h 文件中宏定义常量时,其他两个文件的同名变量也会成为所定义的常量。这样做也是为了方便改变棋盘的大小,因为我不需要重复的在很多函数里面去修改值,我只需要改变宏定义的常量值就好了。
为了方便设置雷的数量,我们在game.h里继续宏定义一个常量。
言归正传,回到设置雷的函数SetMine上:
此时打印mine棋盘:
成功埋雷。
5.排查雷
在main.c文件里的game函数里面上代码
FindMine ( mine , show , ROWS , COLS ) ;
在game.c文件里加上如下代码:
浏览这段代码可能会有以下几个疑惑:
1. 为什么在进入while循环前,有一个DisplayBoard函数调用?
首次需要先打印一次show棋盘给用户。当用户进入while循环后每次用户输入完坐标也需要打印一次show棋盘给用户看(前提是用户没被“炸死”,用户被“炸死”,打印的是mine棋盘,因为mine棋盘是存放雷的棋盘,要让用户知道自己是怎么被“炸死”的)。
2.打印棋盘还是像第一次那样打印全部棋盘吗?是 11x11 的棋盘打印还是 9x9 呢?
前面用打印棋盘是为了检查程序是否向预想的方向进行,所以现在的棋盘打印不需要再打印11x11 了,打印 9x9 就好了。改动前的代码如下:
改动后的代码如下(附加运行图):
3.那个GetMineCount函数又是什么?
GetMineCount函数代码如下图:
原理是统计一个坐标附近的八个坐标中的字符,在c语言中每一个字符都对应着一个ASCII码,ASCII码为整形(int)。字符 ' 0 ' 的ASCII码为48。所以想得到把字符数字转变成整形数字,用对应的字符数字减去字符 0 就好了,比如 ' 1 ' - ' 0 '得到的就是整形1。 所以GetMineCount函数返回的值就是附近八个坐标的雷数目。
下图是简陋的棋盘示意图,被橙色线条框起来的地方就是输入show棋盘的有效范围,超出范围则为输入无效。
至此,初级简单扫雷游戏的代码已经全部完成,全部代码如下:
希望对这个游戏感兴趣而且想做出自己第一个小游戏的浏览者可以尝试一下这个代码,体会一下一个项目从无到有的成就感!!
game1.h
#define _CRT_SECURE_NO_WARNINGS 1
/* game1.h */
#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_TYPE 10
//菜单打印
void menu();
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int row, int col, 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);
main1.c
#define _CRT_SECURE_NO_WARNINGS 1
/* main1.c */
#include "game1.h"
void game()
{
char mine[ROWS][COLS];//我们的 11x11 棋盘,存放雷
char show[ROWS][COLS];//展示给用户的 9x9 棋盘,
//但需要创建一个 11x11 的棋盘,为了方便后期操作和mine棋盘对应
//初始化棋盘
InitBoard(mine, ROWS, COLS, '0');
InitBoard(show, ROWS, COLS, '*');
//打印棋盘,检查初始化是否成功
//DisplayBoard(mine, ROWS, COLS);
//DisplayBoard(show, ROWS, COLS);
//布置雷
SetMine(mine, ROW, COL);
//DisplayBoard(mine, ROWS, COLS);
//排查雷
FindMine(mine, show, ROW, COL);
}
int main()
{
srand((unsigned)time(NULL));
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);
}
game1.c
#define _CRT_SECURE_NO_WARNINGS 1
/* game1.c */
#include "game1.h"
void menu()//菜单
{
printf("********************\n");
printf("****** 1.game ******\n");
printf("****** 0.exit ******\n");
printf("********************\n");
}
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)
{ //打印棋盘
for (int i = 0; i <= col; i++)
{ // x 坐标
printf("%d ", i);//给用户一个必要的坐标提示
}
printf("\n");
for (int i = 1; i <= row; i++)
{ //给用户一个必要的坐标提示
printf("%d ", i);// y 坐标
for (int 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_TYPE;
while (count)
{ //棋盘是11x11,但实际给用户展示的是9x9
//所以雷的坐标取得都是 1 ~ 9 ;
int x = rand() % 9 + 1;
int y = rand() % 9 + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';//用 ‘1’ 来表示雷
count--;
}
}
}
int GetMineCount(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 + 1][y]
+ mine[x - 1][y + 1] + mine[x][y + 1] + mine[x + 1][y + 1])
- 8 * '0';
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;//用户输入的坐标
int y = 0;
int win = 0;//不是雷 的 坐标
DisplayBoard(show, ROW, COL);
while (win < ROW * COL - EASY_TYPE)
{
//当用户输入了(81-10)个坐标都没被炸死就获胜了
//可以退出循环了
printf("请输入你要排查的位置(输入格式:1 1):—>");
scanf("%d %d", &x, &y);
if ((x >= 1 && x <= col) && (y >= 1 && y <= row))
{ //保证输入有效,在show棋盘中的 1 ~ 9
if (mine[x][y] != '1')
{ //该位置不是雷
//统计 mine 棋盘该坐标四周的雷数量
int count = GetMineCount(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
else
{
printf("抱歉,你被炸死了,游戏结束\n");
DisplayBoard(mine, ROW, COL);
break;
}
}
else
{
printf("输入无效,请重新输入\n");
}
}
if (win == ROW * COL - EASY_TYPE)
{//用户退出循环时刚好 输入了 9x9 - 10 个坐标
printf("恭喜你,通关了!!\n");
DisplayBoard(mine, ROW, COL);
}
}