设计思路
- 利用字符串和终端模拟扫雷游戏.
- 组成棋盘的数组比实际棋盘大两个单位, 用于显示坐标轴
- 递归模拟扫雷棋盘成片显示
- 利用C语言的特性分割各个功能
- 棋盘大小, 难度均可扩展
效果演示
gif录制工具: LICEcap
为了方便演示, 在一开始先输出了雷的坐标(1有, 0无). 可以在整体逻辑里去掉这一段.
第一次输入: 演示棋盘的拓展功能, 模拟扫雷点一下如果周围没雷则出一片的效果
第二次输入: 演示正常周围有雷的效果
第三次输入: 演示碰到雷的效果.
代码
Main
整体逻辑, 循环play, 想拓展可以在头文件加上不同的mode参数, 再在main里让用户每次选择.
#include "game.h"
void menu() {
printf("#############\n");
printf("## 1. play ##\n");
printf("## 0. exit ##\n");
}
//mine为隐式棋盘, 作为一开始设置雷的标识, 1为有雷, 0为无雷
//show是显式棋盘,输出给用户, *代表没被探索过的位置点位, 数字代表输入点位周围的雷的数量
//第一行和第一列为坐标
void game() {
//创建棋盘对应的数组
char mine[ROWS][COLS]; //存放布置好的雷的信息
char show[ROWS][COLS]; //存放展示的棋盘
initBoard(mine,'0');
initBoard(show, '*');
//打印棋盘
DisplayBoard(show);
SetMine(mine, EASY_MODE);
DisplayBoard(mine);
//排查雷
FindMine(mine, show, EASY_MODE);
}
int main() {
int input = 0;
srand((unsigned)time(NULL));
do {
menu();
printf("请选择: ");
scanf("%d", &input);
switch (input) {
case 1:
game();
break;
case 0:
printf("Game end\n");
break;
default:
printf("Wrong input\n");
break;
}
} while (input);
return 0;
}
头文件
可以通过define不同的模式来让用户在开始时选择难度
这里只define了简单模式, 也就是10个雷
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
#define EASY_MODE 10 //雷的数量
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<time.h>
#include<math.h>
//初始化棋盘
void initBoard(char board[ROWS][COLS], char ch);
void DisplayBoard(char board[ROWS][COLS]);
//布置雷
void SetMine(char board[ROWS][COLS], int count);
//排雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int MineCount);
函数实现
#include "game.h"
//初始化棋盘
void initBoard(char board[ROWS][COLS], char ch) {
for (int i = 0; i < ROWS; i++) {
for (int j = 0; j < COLS; j++) {
board[i][j] = ch;
}
}
}
//输出棋盘, 注意第一行和第一列要输出的是坐标
void DisplayBoard(char board[ROWS][COLS]) {
for (int i = 0; i <= COL; i++) {
printf("%d ", i);
}
putchar('\n');
for (int i = 1; i <= ROW; i++) {
printf("%d ",i);
for (int j = 1; j <= COL; j++) {
printf("%c ", board[i][j]);
}
putchar('\n');
}
}
//利用随机数布置棋盘, ROW COL是实际上棋盘的大小
void SetMine(char board[ROWS][COLS], int count) {
//有雷为1, 无雷为0, 这里用char的1和0 为了后面更方便的排查
while (count) {
int x = rand() % ROW + 1;
int y = rand() % COL + 1;
if (board[x][y] == '0') {
board[x][y] = '1';
count--;
}
}
}
//递归扩展棋盘, 模拟扫雷的点一下出一片的情况.
//每次检查都递归当前点的周围8个点位.
//每个检查过的点位不可再检查, 否则就是无限递归下去, 所以这里show的值一直在变化
//这样不仅输出的时候棋盘有变化, 同时可以用show的值作为标记, 跳出递归.
void GetMineCount(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y) {
if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
int count =
mine[x - 1][y] +
mine[x - 1][y - 1] +
mine[x][y - 1] +
mine[x + 1][y - 1] +
mine[x + 1][y] +
mine[x + 1][y + 1] +
mine[x][y + 1] +
mine[x - 1][y + 1] - 8 * '0';
if (count == 0) {
show[x][y] = ' ';
if (show[x - 1][y] == '*') {
GetMineCount(mine, show, x - 1, y);
}
if (show[x - 1][y - 1] == '*') {
GetMineCount(mine, show, x - 1, y - 1);
}
if (show[x][y - 1] == '*') {
GetMineCount(mine, show, x, y - 1);
}
if (show[x + 1][y - 1] == '*') {
GetMineCount(mine, show, x + 1, y - 1);
}
if (show[x + 1][y] == '*') {
GetMineCount(mine, show, x + 1, y);
}
if (show[x + 1][y + 1] == '*') {
GetMineCount(mine, show, x + 1, y + 1);
}
if (show[x][y + 1] == '*') {
GetMineCount(mine, show, x, y + 1);
}
if (show[x - 1][y + 1] == '*') {
GetMineCount(mine, show, x - 1, y + 1);
}
}
else {
//棋盘里摆放字符数字
show[x][y] = count + '0';
}
}
}
//排雷, 用户输入坐标, 如果没碰雷, 输出周围雷的个数, 如果周围没雷, 递归拓展寻找范围
//如果碰到雷跳出游戏循环, 回到开始界面.
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int Minecount) {
int win = 0;
while (win < ROW * COL - Minecount) {
printf("请输入要排查的坐标:> ");
int x = 0;
int y = 0;
scanf("%d %d", &x, &y);
//1. 坐标的合法性
//2. 该坐标处是不是雷, 不是雷, 统计周围的雷的数量
if (x >= 1 && x <= ROW && y >= 1 && y <= COL) {
if (mine[x][y] == '1') {
printf("这有雷\n");
DisplayBoard(mine);
break;
}
else {
GetMineCount(mine, show, x, y);
DisplayBoard(show);
}
}
else {
printf("输入错误, 请重新输入.\n");
}
//检查雷是否全部排除
if (win == ROW * COL - Minecount) {
printf("恭喜排雷成功\n");
}
}
}