一、 设计思路
思路分析
如果所选位置不是雷,则计算改位置附近八个坐标雷的个数,并记录在该位置,游戏继续;是雷,则被炸死,游戏结束。
若不是雷的位置都被找出来,则成功,游戏结束。
在游戏设计初步,大家会想着设置一个棋盘来进行操作,用‘1’来表示雷,‘0’表示非雷。但这样会出现一个问题,就是不知道棋盘中的‘1’是设置的雷还是附近八个坐标某处有雷,因此我们需要设置两个棋盘(即两个二维数组)来进行操纵。一个棋盘用于存放雷的信息,另一个用于存放排查的雷的信息。
本文设计一个小型的扫雷游戏,展示一个9*9的棋盘,那就应该在代码设计时直接设置行数和列数分别为9吗?
答案是不是的!在玩家选择坐标后若该位置不是雷还应统计该坐标周围的8个坐标的雷数记录在另一张棋盘上。如果是排查下图中绿色位置周围的雷数不会出问题,但如果排查粉色位置处时会有三个坐标出现在棋盘外,对于判断时会造成麻烦,所以为了便于排雷就将棋盘设置为11*11的二维数组,同时为了便于在另一个棋盘上对应位置显示出来雷的数目,将两个数组均设置为11*11的二维数组。
如下:
基本逻辑框架
菜单:
通过此函数选择开始游戏,结束游戏,输入数据非法提示重输
初始化棋盘:
初始化两个棋盘mine和show,将内部元素设为便于区分和使用的字符
展示棋盘:
未选择的位置均为‘*’,已选择的位置处所置数据为周围的雷数
置雷:
通过 rand()函数随机生成一定坐标,将该位置记为雷
排雷:
用户输入想排查的位置,查看mine棋盘中对应位置是否为雷,若是则被炸死;若不是则显示周围的雷数,直至找出所有的雷数
二、函数功能实现
创建三个文件,源文件 test.c,game.c 及头文件 game.h。
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
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char c);
//打印棋盘
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);
<在头函数中定义常量ROW COL ROWS COLS,方便以后修改>
game.c来集中创建game ( )中所需的函数
游戏的主体逻辑写在test.c中
要记得在 test.c 以及 game.c 中包含头文件 game.h ,写为 #include"game-a.h"
各类步骤
构建主逻辑框架
void game()
{
char mine[ROWS][COLS] = { 0 }; //用于保存雷的位置
char show[ROWS][COLS] = { 0 }; //用于展示出给用户操作
//初始化棋盘
InitBoard(mine,ROWS,COLS,'0'); //'0'表示无雷 ‘1’表示为雷
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
DisplayBoard(show, ROW, COL);
//置雷
SetPlay(mine, ROW, COL);
DisplayBoard(mine, ROW, COL);
//排雷
FindMine(mine, show, ROW, COL);
}
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()外部加一层do-while()循环。
菜单函数
显示相应的功能
void menu()
{
printf("************************\n");
printf("**** 1.play ****\n");
printf("**** 0.exit ****\n");
printf("************************\n");
}
初始化棋盘函数
先定义两个字符型的二维数组,初始化函数传参时将要给棋盘赋的值也传过去,这样就不用写两个初始化函数了
char mine[ROWS][COLS] = { 0 }; //用于保存雷的位置
char show[ROWS][COLS] = { 0 }; //用于展示出给用户操作
//初始化棋盘
InitBoard(mine,ROWS,COLS,'0'); //'0'表示无雷 ‘1’表示为雷
InitBoard(show, ROWS, COLS, '*');
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char c)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
for (j = 0; j < cols; j++)
board[i][j] = c;
}
打印棋盘函数
玩家每选一个坐标就打印一次show[ROWS][COLS]数组
//打印棋盘
DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("--------扫雷--------\n");
for (i = 0; i <= col; i++) //打印列号
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i); //打印行号
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("--------扫雷--------\n");
}
因为我们为了便于排雷所以在设置数组时多加了两行两列, 但是对于玩家而言只需要打印出中间9*9的棋盘,所以在嵌套的for循环中i,j都是从1开始的。打印完一行后还要换行,因此在第一层for循环快结束时加上printf("\n");语句。为了更加便于使用再加上一个for循环打印出列号。
置雷函数
//置雷
void SetPlay(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--;
}
}
}
用 rand()函数随机生成坐标( x , y ) 并将该位置置为雷,因为 rand()生成的是随机数,但坐标是有范围的,所以给 rand()%row将生成一个0 ~ row - 1的数,因此 x = rand () % row + 1,同理 y = rand()% y + 1。不仅如此,还要确保每次生成的坐标处不是雷,否则会造成棋盘上雷的数目小于EASY_COUNT。
注意:使用 rand()函数时要在主函数中加上 srand ( ( unsigned int ) time ( NULL ) ) ;
排雷函数
排雷需要同时操作两个数组,所以在参数部分应将两个数组都传过去。
FindMine(mine, show, ROW, COL);
玩家输入坐标后,应先判断位置是否合法,然后再做选择
if (x>=1 && x<=row && y>=1 && y<=col) //坐标合法
如果坐标不合法,提示玩家坐标错误,重新输入,因此在出错后不能直接结束,而是加上一个 while()循环,直至找出所有雷或者被雷炸死就结束。那么非雷的位置就有ROW*COL-EAST_COUNT个,实现代码如下:
while (count<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 //不是雷,计算周围8个坐标有几个雷
{
int c = GetMineCount(mine,x,y);
show[x][y] = c + '0';
DisplayBoard(show, ROW, COL);
count++;
}
}
else
{
printf("坐标非法,请重新输入!\n");
}
}
同时还需要一个计算周围雷数量的函数 GetMineCount(),将周围八个坐标处的值相加后减去8*'0'所得值就为雷的数目。
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';
}
三、源代码
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
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char c);
//打印棋盘
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);
game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char c)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
for (j = 0; j < cols; j++)
board[i][j] = c;
}
//打印棋盘
DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("--------扫雷--------\n");
for (i = 0; i <= col; i++) //打印列号
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i); //打印行号
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("--------扫雷--------\n");
}
//置雷
void SetPlay(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--;
}
}
}
//排雷
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';
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = 0;
while (count<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 //不是雷,计算周围8个坐标有几个雷
{
int c = GetMineCount(mine,x,y);
show[x][y] = c + '0';
DisplayBoard(show, ROW, COL);
count++;
}
}
else
{
printf("坐标非法,请重新输入!\n");
}
}
if (count == row * col - EASY_COUNT)
{
printf("排雷成功,游戏结束!\n");
DisplayBoard(mine, ROW, COL);
}
}
test.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu()
{
printf("************************\n");
printf("**** 1.play ****\n");
printf("**** 0.exit ****\n");
printf("************************\n");
}
void game()
{
char mine[ROWS][COLS] = { 0 }; //用于保存雷的位置
char show[ROWS][COLS] = { 0 }; //用于展示出给用户操作
//初始化棋盘
InitBoard(mine,ROWS,COLS,'0'); //'0'表示无雷 ‘1’表示为雷
InitBoard(show, ROWS, COLS, '*');
//打印棋盘
DisplayBoard(show, ROW, COL);
//置雷
SetPlay(mine, ROW, COL);
//排雷
FindMine(mine, show, ROW, COL);
}
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;
}