一.游戏思路
首先,创建两个二维数组,满足埋雷和在玩家面前显示的需要. 其次,写函数让玩家选择排查位置,实现扩散效果,要注意各种非法输入判定.然后,实现插旗.最后,判定输赢与否.
二.实现的具体内容
1.模拟登录界面
"game.c"中写menu()函数.
//菜单
void menu()
{
printf("**************************\n");
printf("********* 1.paly *********\n");
printf("********* 0.exit *********\n");
printf("**************************\n");
}
"test.c"中写do while循环对玩家的选择做出反应.
int main()
{
srand((unsigned int)time(NULL));
int choice = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &choice);
switch (choice)
{
case 1:
{
printf("扫雷\n");
game();
break;
}
case 0:
{
printf("游戏结束\n");
break;
}
default:
{
printf("输入非法,重输\n");
}
}
} while (choice);
2.初始化棋盘
设置两个9*9棋盘,一个初始化全为'0',为埋雷'1'准备.另一个初始化全为'*',为排查和插旗准备.
//初始化棋盘
void initboard(char arr[ROWS][LINES],int rows,int lines,char set)
{
int i = 0, k = 0;
for (i = 0; i < rows; i++)
{
for (k = 0; k < lines; k++)
{
arr[i][k] = set;
}
}
}
这里要注意的是:
1.为了实现调节难度自由,也就是更简单地改变游戏的长和宽,避免改变长宽时要到程序里一个个改.
在头文件"game.h"中设置ROWS = 9,LINES = 9.改变时仅需在头文件改变一次即可.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define ROW 9
#define LINE 9
#define ROWS ROW+2
#define LINES LINE+2
还有一个重要的点,排查是排查周围8格的'1'数量.若仅建立9*9数组,排查最外面一排时会超出数组范围.因此,实际是建立11*11的数组.(要在"game.c"和"test.c"中引用"game.h",如下)
2.有两个数组需初始化,双for迭代的式子中若用常量'*'或'0',要创建两个初始化函数,太麻烦.所以,建立变量set承接不同的需要.
char inboard[ROWS][LINES] = { 0 };
char outboard[ROWS][LINES] = { 0 };
initboard(inboard, ROWS, LINES, '0');
initboard(outboard, ROWS, LINES, '*');
3.画出棋盘
我们创建了11*11的数组,但呈现在玩家眼中只有9*9.
//画出棋盘
void drawboard(char arr[ROWS][LINES], int rows, int lines)
{
int i = 0;
for (i = 0; i < rows - 1; i++)
{
printf("%2d ", i);
}
printf("\n");
int h = 0, j = 0;
for (h = 1; h < rows - 1; h++)
{
printf("%2d ", h);
for (j = 1; j < lines - 1; j++)
{
printf("%2c ", arr[h][j]);
}
printf("\n");
}
}
效果如下
因为玩家使用"输入坐标"的方式排查,所以标上行列有助于玩家输入.
这里需要注意的点是:"%2d".若呈现的方阵长宽<10,将2删去没有影响.
但长宽>9,会出现不对齐的状况.
4.埋雷
使用rand函数随机指定数字,经过取余加1使之符合数组下标1-9的需要.
//埋雷
void device(char arr1[ROWS][LINES], int row, int line)
{
int i = rand() % (row)+1, j = rand() % (line)+1;
int n = 0;
for (n = 0; n < COUNT; n++)
{
while (arr1[i][j] == '1')
{
i = rand() % (row)+1, j = rand() % (line)+1;
}
arr1[i][j] = '1';
}
}
需要注意的是:
1.用rand()需要在"test.c"中调用srand函数(若放在"game.c"中只能产生固定数)
因为srand()需要unsigned int类型,而time是time_t类型所以用()进行强制类型转换.
"NULL"为空指针引用在time中可达到取随机数效果.
最后,别忘了在"game.h"中引用预处理命令,srand是<stdlib.h>,time是<time.h>.
5.玩家输入坐标
玩家输入坐标后要进行"输入是否非法"和"是否踩到炸弹"的判定.
//人玩
void play(char arr1[ROWS][LINES], char arr2[ROWS][LINES], int row, int line)
{
int a = 0, b = 0;
while (arr1[a][b] == '0')
{
printf("请输入坐标:>");
scanf("%d,%d", &a, &b);
while (1)
{
if (a < 0 || a > 9 || b < 0 || b > 9)
{
printf("输入非法,重输\n");
scanf("%d,%d", &a, &b);
}
else
break;
}
if (arr1[a][b] == '1')
{
printf("你被炸死了\n");
break;
}
此函数play将会包含一整个游戏的进行过程,包含输入\判输\扩散\判赢.(除了初始化棋盘,和埋雷)
6.探测周围8格的炸弹数量
创建一个count函数存放炸弹数量,将周围8格依次判定,为'1'则count+1.
//探测
int detect(char arr1[ROWS][LINES], int a, int b)
{
int count = 0;
if (arr1[a - 1][b - 1] == '1')
count += 1;
if (arr1[a - 1][b] == '1')
count += 1;
if (arr1[a - 1][b + 1] == '1')
count += 1;
if (arr1[a][b - 1] == '1')
count += 1;
if (arr1[a][b + 1] == '1')
count += 1;
if (arr1[a + 1][b - 1] == '1')
count += 1;
if (arr1[a + 1][b] == '1')
count += 1;
if (arr1[a + 1][b + 1] == '1')
count += 1;
return count;
}
这是排查和扩散的基础.
7.扩散效果
我们需要在玩家选择一个坐标后,排查周围8格的地雷数量,若为0,则周围8格依次排查周围8格的地雷数量,直到排查出周围8格的地雷数量不为0,实现扩散效果.需要递归.
//扩散
void dif(char arr1[ROWS][LINES], char arr2[ROWS][LINES], int row, int line, int a, int b)
{
int d = detect(arr1, a, b);
if (a > 0 && a <= row && b > 0 && b <= line)
{
if (d == 0)
{
arr2[a][b] = '0';
int i = 0;
for (i = a - 1; i <= a + 1; i++)
{
int j = 0;
for (j = b - 1; j <= b + 1; j++)
{
if (arr2[i][j] != '0')
{
dif(arr1, arr2, row, line, i, j);
}
}
}
}
else
{
arr2[a][b] = d + '0';
}
}
}
迭代两个for中的式子,若周围已排查过为'0',则无需再排查,减少计算.
8.插旗
玩家每排查一个雷后,可进行无限次插旗.有五种情况:(1)玩家输入下标超出范围(2)玩家输入的下标在数组中的字符为数字(已排查过)(3)玩家输入的下标在数组中的字符为'#'(4)玩家想结束插旗(5)玩家插旗成功.
(1)(2)一样,让玩家重输.(3)则改'#'回'*',相当于把旗拔了.(4)玩家输入"0,0",使得a = 0,while的条件为假,停止循环.(5)玩家输入的下标正常,继续进行下一次插旗.
插完旗画出棋盘让玩家看的更清晰.接着进行两个获胜判定的插旗判定,后面讲到.
//标记
void signal(char arr1[ROWS][LINES], char arr2[ROWS][LINES], int rows, int lines)
{
int a = 1, b = 1;
while (a)
{
printf("请标记(若弃权则输入\"0,0\") :>");
scanf("%d,%d", &a, &b);
if (a < rows - 1 && a > 0 && b < lines - 1 && b > 0)
{
arr2[a][b] = '#';
}
else if (arr2[a][b] != '*')
{
if (arr2[a][b] == '#')
{
arr2[a][b] = '*';
}
printf("输入非法,重输\n");
}
else if (a >= rows - 1 || a < 0 || b >= lines - 1 || b < 0)
{
printf("输入非法,重输\n");
}
drawboard(arr2, rows, lines);
int d = is_win1(arr1, arr2, rows, lines);
if (d == 1)
{
break;
}
}
}
9.获胜判定
获胜判定有两种:剩余判定和插旗判定.
(1)剩余判定
玩家将除雷外的所有格子全部排查过即判定为胜利.(如9*9数组中布置10个雷,画面中有71个格子为数字,即胜利.)
插入位置:玩家完成一次排查之后,插旗之前.
//剩余判定
int is_win2(char arr1[ROWS][LINES], char arr2[ROWS][LINES], int rows, int lines)
{
int i = 0, j = 0, K = 0;
for (i = 1; i < rows - 1; i++)
{
for (j = 1; j < lines - 1; j++)
{
if (arr2[i][j] != '*' && arr2[i][j] != '#')
{
K++;
}
}
}
if (K == ROW * LINE - COUNT)
{
return 1;
}
else
return 0;
}
这里用的是非数字格子的数量,若等同于地雷数量,则判定为胜利.
*COUNT在"game.h"中设定为雷的数量10.
(2)插旗判定
若玩家将所有地雷位置的字符改为'#'(即第一个数组中为'1',第二个数组中为'#'同时成立),则判定胜利.
//插旗判定
int is_win1(char arr1[ROWS][LINES], char arr2[ROWS][LINES], int rows, int lines)
{
int i = 0, j = 0, count = 0;
for (i = 1; i < rows - 1; i++)
{
for (j = 1; j < lines - 1; j++)
{
if (arr1[i][j] == '1' && arr2[i][j] == '#')
{
count += 1;
}
}
}
if (count == COUNT)
{
return 1;
}
else
return 0;
}
这样,一个简单的排雷程序就写完了!
三.完整程序
test.c(存放主流程)
#include "game.h"
void game()
{
char inboard[ROWS][LINES] = { 0 };
char outboard[ROWS][LINES] = { 0 };
initboard(inboard, ROWS, LINES, '0');
initboard(outboard, ROWS, LINES, '*');
drawboard(outboard, ROWS, LINES);
device(inboard, ROW, LINE);
/*drawboard(inboard, ROWS, LINES);*/
play(inboard, outboard, ROW, LINE);
}
int main()
{
srand((unsigned int)time(NULL));
int choice = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &choice);
switch (choice)
{
case 1:
{
printf("扫雷\n");
game();
break;
}
case 0:
{
printf("游戏结束\n");
break;
}
default:
{
printf("输入非法,重输\n");
}
}
} while (choice);
return 0;
}
game.c(存放函数)
#include "game.h"
//菜单
void menu()
{
printf("**************************\n");
printf("********* 1.paly *********\n");
printf("********* 0.exit *********\n");
printf("**************************\n");
}
//初始化棋盘
void initboard(char arr[ROWS][LINES], int rows, int lines, char set)
{
int i = 0, k = 0;
for (i = 0; i < rows; i++)
{
for (k = 0; k < lines; k++)
{
arr[i][k] = set;
}
}
}
//画出棋盘
void drawboard(char arr[ROWS][LINES], int rows, int lines)
{
int i = 0;
for (i = 0; i < rows - 1; i++)
{
printf("%2d ", i);
}
printf("\n");
int h = 0, j = 0;
for (h = 1; h < rows - 1; h++)
{
printf("%2d ", h);
for (j = 1; j < lines - 1; j++)
{
printf("%2c ", arr[h][j]);
}
printf("\n");
}
}
//埋雷
void device(char arr1[ROWS][LINES], int row, int line)
{
int i = rand() % (row)+1, j = rand() % (line)+1;
int n = 0;
for (n = 0; n < COUNT; n++)
{
while (arr1[i][j] == '1')
{
i = rand() % (row)+1, j = rand() % (line)+1;
}
arr1[i][j] = '1';
}
}
//人玩
void play(char arr1[ROWS][LINES], char arr2[ROWS][LINES], int row, int line)
{
int a = 0, b = 0;
while (arr1[a][b] == '0')
{
printf("请输入坐标:>");
scanf("%d,%d", &a, &b);
while (1)
{
if (a < 0 || a > 9 || b < 0 || b > 9)
{
printf("输入非法,重输\n");
scanf("%d,%d", &a, &b);
}
else
break;
}
if (arr1[a][b] == '1')
{
printf("你被炸死了\n");
break;
}
else
{
dif(arr1, arr2, row, line, a, b);
drawboard(arr2, ROWS, LINES);
int k = is_win2(arr1, arr2, row + 2, line + 2);
if (k == 1)
{
printf("你赢了\n");
break;
}
signal(arr1, arr2, row + 2, line + 2);
}
}
}
}
//探测
int detect(char arr1[ROWS][LINES], int a, int b)
{
int count = 0;
if (arr1[a - 1][b - 1] == '1')
count += 1;
if (arr1[a - 1][b] == '1')
count += 1;
if (arr1[a - 1][b + 1] == '1')
count += 1;
if (arr1[a][b - 1] == '1')
count += 1;
if (arr1[a][b + 1] == '1')
count += 1;
if (arr1[a + 1][b - 1] == '1')
count += 1;
if (arr1[a + 1][b] == '1')
count += 1;
if (arr1[a + 1][b + 1] == '1')
count += 1;
return count;
}
//标记
void signal(char arr1[ROWS][LINES], char arr2[ROWS][LINES], int rows, int lines)
{
int a = 1, b = 1;
while (a)
{
printf("请标记(若弃权则输入\"0,0\") :>");
scanf("%d,%d", &a, &b);
if (a < rows - 1 && a > 0 && b < lines - 1 && b > 0)
{
arr2[a][b] = '#';
}
else if (arr2[a][b] != '*')
{
if (arr2[a][b] == '#')
{
arr2[a][b] = '*';
}
printf("输入非法,重输\n");
}
else if (a >= rows - 1 || a < 0 || b >= lines - 1 || b < 0)
{
printf("输入非法,重输\n");
}
drawboard(arr2, rows, lines);
int d = is_win1(arr1, arr2, rows, lines);
if (d == 1)
{
break;
}
}
}
//插旗判定
int is_win1(char arr1[ROWS][LINES], char arr2[ROWS][LINES], int rows, int lines)
{
int i = 0, j = 0, count = 0;
for (i = 1; i < rows - 1; i++)
{
for (j = 1; j < lines - 1; j++)
{
if (arr1[i][j] == '1' && arr2[i][j] == '#')
{
count += 1;
}
}
}
if (count == COUNT)
{
return 1;
}
else
return 0;
}
//剩余判定
int is_win2(char arr1[ROWS][LINES], char arr2[ROWS][LINES], int rows, int lines)
{
int i = 0, j = 0, K = 0;
for (i = 1; i < rows - 1; i++)
{
for (j = 1; j < lines - 1; j++)
{
if (arr2[i][j] != '*' && arr2[i][j] != '#')
{
K++;
}
}
}
if (K == ROW * LINE - COUNT)
{
return 1;
}
else
return 0;
}
//扩散
void dif(char arr1[ROWS][LINES], char arr2[ROWS][LINES], int row, int line, int a, int b)
{
int d = detect(arr1, a, b);
if (a > 0 && a <= row && b > 0 && b <= line)
{
if (d == 0)
{
arr2[a][b] = '0';
int i = 0;
for (i = a - 1; i <= a + 1; i++)
{
int j = 0;
for (j = b - 1; j <= b + 1; j++)
{
if (arr2[i][j] != '0')
{
dif(arr1, arr2, row, line, i, j);
}
}
}
}
else
{
arr2[a][b] = d + '0';
}
}
}
game.h(存放预处理命令和函数声明)
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define ROW 9
#define LINE 9
#define ROWS ROW+2
#define LINES LINE+2
#define COUNT 10
void menu();
void initboard(char arr[ROWS][LINES], int rows, int lines, char set);
void drawboard(char arr[ROWS][LINES], int rows, int lines);
void device(char arr[ROWS][LINES], int rows, int lines);
void play(char arr1[ROWS][LINES], char arr2[ROWS][LINES], int row, int line);
void signal(char arr1[ROWS][LINES], char arr2[ROWS][LINES], int rows, int lines);
void dif(char arr1[ROWS][LINES], char arr2[ROWS][LINES], int row, int line, int a, int b);
int is_win2(char arr1[ROWS][LINES], char arr2[ROWS][LINES], int rows, int lines);
int is_win1(char arr1[ROWS][LINES], char arr2[ROWS][LINES], int rows, int lines);