游戏介绍
扫雷想必大家都听说过吧?我们今天写的小游戏就是扫雷,只不过我们只使用C语言写。我们能够做到的就是实现扫雷的基本逻辑,没有图形化界面。
源代码
这次游戏程序的写法和上一次的三子棋的模式很像,也是三个文件。
1.game.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 EAZY_COUNT 10
//函数声明
//打印菜单
void menu();
//游戏函数
void game();
//初始化地图
void init(char board[ROWS][COLS], char set);
//打印地图
void display_board(char board[ROWS][COLS]);
//埋雷
void set_mine(char board[ROWS][COLS]);
//排雷
void find_mine(char show[ROWS][COLS], char mine[ROWS][COLS]);
//数雷
int mine_count(char board[ROWS][COLS], int r, int c);
//弹开
void ExpandBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y);
//判断游戏状态
int is_win(char board[ROWS][COLS]);
2.test.c(游戏测试)
#include "game.h"
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
//打印菜单,实现选择功能
do
{
menu();
printf("请选择:\n");
scanf("%d", &input);
switch (input)
{
case 1:/*printf("play!\n");*/
game();
break;
case 0:printf("游戏结束!\n");
break;
default:printf("输入错误,请重新输入\n");
break;
}
} while (input);
system("pause");
return 0;
}
game.c(游戏实现)
#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 };
init(mine, '0');
init(show, '*');
set_mine(mine);
//display_board(mine);
display_board(show);
find_mine(show, mine);
}
void init(char board[ROWS][COLS], char set)
{
for (int i = 0; i < ROWS; i++)
{
for (int j = 0; j < COLS; j++)
{
board[i][j] = set;
}
}
}
void display_board(char board[ROWS][COLS])
{
for (int j = 0; j <= COL; j++)
{
printf("%d ", j);
}
printf("\n");
for (int i = 1; i <= ROW; i++)
{
printf("%d ", i);
for (int j = 1; j <= COL; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("\n");
}
void set_mine(char board[ROWS][COLS])
{
int count = EAZY_COUNT;
while (count)
{
int r = rand() % ROW + 1;
int c = rand() % COL + 1;
if (board[r][c] == '0')
{
board[r][c] = '1';
count--;
}
}
void find_mine(char show[ROWS][COLS], char mine[ROWS][COLS]);
}
void find_mine(char show[ROWS][COLS], char mine[ROWS][COLS])
{
int r = 0, c = 0;
while (1)
{
printf("请输入排雷的坐标:\n");
scanf("%d %d", &r, &c);
if (r >= 1 && r <= ROW && c >= 1 && c <= COL)
{
if (show[r][c] == '*')
{
if (mine[r][c] == '0')
{
//show[r][c] = mine_count(mine, r, c) + '0';
ExpandBoard(mine, show, r, c);
if (is_win(show))
{
break;
}
display_board(show);
//display_board(mine);
}
else
{
printf("踩雷了!游戏结束!\n");
display_board(mine);
break;
}
}
else
{
printf("此位置已经排查过了!\n");
}
}
else
{
printf("坐标不合法,请重新输入!\n");
}
}
if (is_win(show))
{
printf("扫雷成功!\n");
display_board(mine);
}
}
int mine_count(char board[ROWS][COLS], int r, int c)
{
return (board[r + 1][c - 1] +
board[r][c - 1] +
board[r - 1][c - 1] +
board[r + 1][c] +
board[r - 1][c] +
board[r + 1][c + 1] +
board[r][c + 1] +
board[r - 1][c + 1] - (8 * '0'));
}
void ExpandBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int r, int c)
{
int ret = mine_count(mine, r, c);
if (ret == 0)
{
show[r][c] = ' ';
int i = 0;
int j = 0;
for (i = -1; i <= 1; i++)
{
for (j = -1; j <= 1; j++)
{
if ((r + i) > 0 && (c + i) > 0 && (r + i < ROWS) && (c + j < COLS) && show[r + i][c + j] == '*')
{
ExpandBoard(mine, show, r + i, c + j);
}
}
}
/*if (show[r - 1][c - 1] == '*' && r - 1 > 0 && r - 1 < ROWS && c - 1 > 0 && c - 1 < COLS)
ExpandBoard(mine, show, r - 1, c - 1, win);
if (show[r - 1][c] == '*' && r - 1 > 0 && r - 1 < ROWS && c > 0 && c < COLS)
ExpandBoard(mine, show, r - 1, c, win);
if (show[r - 1][c + 1] == '*' && r - 1 > 0 && r - 1 < ROWS && c + 1 > 0 && c + 1 < COLS)
ExpandBoard(mine, show, r - 1, c + 1, win);
if (show[r][c - 1] == '*' && r > 0 && r < ROWS && c - 1 > 0 && c - 1 < COLS)
ExpandBoard(mine, show, r, c - 1, win);
if (show[r][c + 1] == '*' && r > 0 && r < ROWS && c + 1 > 0 && c + 1 < COLS)
ExpandBoard(mine, show, r, c + 1, win);
if (show[r + 1][c - 1] == '*' && r + 1 > 0 && r + 1 < ROWS && c - 1 > 0 && c - 1 < COLS)
ExpandBoard(mine, show, r + 1, c - 1, win);
if (show[r + 1][c] == '*' && r + 1 > 0 && r + 1 < ROWS && c > 0 && c < COLS)
ExpandBoard(mine, show, r + 1, c, win);
if (show[r + 1][c + 1] == '*' && r + 1 > 0 && r + 1 < ROWS && c + 1 > 0 && c + 1 < COLS)
ExpandBoard(mine, show, r + 1, c + 1, win);
}*/
}
else
{
show[r][c] = ret + '0';
}
}
int is_win(char board[ROWS][COLS])
{
int count = 0;
for (int i = 1; i <= ROW; i++)
{
for (int j = 1; j <= COL; j++)
{
if (board[i][j] == '*')
{
count++;
}
}
}
if (count == EAZY_COUNT)
{
return 1;
}
else
{
return 0;
}
}
详解
接下来是游戏实现的一些详细的解释,由于很多地方是和上一期的三子棋相似的,我挑一些主要的地方讲解。
1.地图的设计(为什么地图的大小是(9+2)*(9+2)?)
白色的是我们玩家看到的地图。我们之所以要将地图扩大一圈,是因为我们后面的数坐标周围一圈的雷时,可能会导致越界访问的问题,所以我们这样设计。这一点其实和我们上一期的三子棋的棋盘设计是一个原理。我们以最右下角的个子为例,我们数它周围的雷数,需要访问这些棕色的格子,所以如果数组不够的话,就会导致越界访问。
还有一点就是我们这次定义地图时,设计了两个棋盘,他们大小一样,但是作用不同,一个是用来存放雷的信息的mine数组
,另一个是用来展示给玩家的游戏地图show数组
。
2.初始化与打印地图
需要注意的地方有两点,第一点是我们给数组存数据时是char类型,所以记得加上''
,并且最好是给整个数组都初始化一下。第二点是打印数组时,为了方便玩家输入坐标,我们最好是打印一下行号和列号,具体实现方式请看源代码。
效果展示:
3.设置雷(随机数的使用)
设置雷主要知识点就是随机数的使用,我们需要生成1-9的随机数。所以对rand
生成的随机数采取以下措施:
int r = rand() % ROW + 1;
int c = rand() % COL + 1;
记得要设置随机数生成器哟。
srand((unsigned int)time(NULL));
采用循环的方式设置雷是为了保证能够设置多个并且有效的雷,没设置成功一个雷,就离出口更近一步。具体实现请看源代码。
4.寻找雷
玩家游玩的部分,主要逻辑如下:玩家输入坐标,如果坐标合法的话,判断该坐标是否已经排查过了,如果该坐标没有排查过,再判断是否为雷,如果不是雷,就显示该坐标周围雷的个数。
需要注意的地方就是数该坐标周围雷的个数这里,我们需要用到字符的加减法,如果你想要得到整型变量时,需要加减一个'0'
,如果需要将整型转换成字符时,也是同理。
5.将不是雷的地方展开(递归实现)
逻辑:数该坐标周围的雷数,如果为0.则将该位置变成' '
,再依次对该坐标周围的八个格子采用同样的方式,递归出口:坐标周围的雷数不是0。
有两种代码,实现原理一样,但是递归次数不一样,代码量也不一样:
1.
void ExpandBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int r, int c)
{
int ret = mine_count(mine, r, c);
if (ret == 0)
{
show[r][c] = ' ';
int i = 0;
int j = 0;
for (i = -1; i <= 1; i++)
{
for (j = -1; j <= 1; j++)
{
if ((r + i) > 0 && (c + i) > 0 && (r + i < ROWS) && (c + j < COLS) && show[r + i][c + j] == '*')
{
//递归
ExpandBoard(mine, show, r + i, c + j);
}
}
}
}
else
{
show[r][c] = ret + '0';
}
}
void ExpandBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int r, int c)
{
int ret = mine_count(mine, r, c);
if (ret == 0)
{
show[r][c] = ' ';
int i = 0;
int j = 0;
if (show[r - 1][c - 1] == '*' && r - 1 > 0 && r - 1 < ROWS && c - 1 > 0 && c - 1 < COLS)
ExpandBoard(mine, show, r - 1, c - 1);
if (show[r - 1][c] == '*' && r - 1 > 0 && r - 1 < ROWS && c > 0 && c < COLS)
ExpandBoard(mine, show, r - 1, c);
if (show[r - 1][c + 1] == '*' && r - 1 > 0 && r - 1 < ROWS && c + 1 > 0 && c + 1 < COLS)
ExpandBoard(mine, show, r - 1, c + 1);
if (show[r][c - 1] == '*' && r > 0 && r < ROWS && c - 1 > 0 && c - 1 < COLS)
ExpandBoard(mine, show, r, c - 1);
if (show[r][c + 1] == '*' && r > 0 && r < ROWS && c + 1 > 0 && c + 1 < COLS)
ExpandBoard(mine, show, r, c + 1);
if (show[r + 1][c - 1] == '*' && r + 1 > 0 && r + 1 < ROWS && c - 1 > 0 && c - 1 < COLS)
ExpandBoard(mine, show, r + 1, c - 1);
if (show[r + 1][c] == '*' && r + 1 > 0 && r + 1 < ROWS && c > 0 && c < COLS)
ExpandBoard(mine, show, r + 1, c);
if (show[r + 1][c + 1] == '*' && r + 1 > 0 && r + 1 < ROWS && c + 1 > 0 && c + 1 < COLS)
ExpandBoard(mine, show, r + 1, c + 1);
}
}
else
{
show[r][c] = ret + '0';
}
}
总结
这个扫雷小游戏还有需要改进的地方,比如放标记功能,调整难度功能,感兴趣的小伙伴可以继续研究。如果内容有什么错误的话,请大家提出来,一起讨论,一起进步!