1:对界面进行了优化
2:修复了会出现的一些bug,如下所示
2.1:通过递归实现排查的时候,排查的范围做了缩小,避免深层次的递归
2.2:还有对一些排查的对象进行了重复排查的(类似递归实现斐波那契数列那样)
2.3:对于用户输入的坐标,使用scanf输入的时候,如果出现了误输入,导致程序死循环的情况
2.4:对于用户对一些已经排查过的坐标,进行再次排查,并且误输入,导致程序死循环的情况
这个程序,还是一样的,我分了三个源文件来写
Mine_Clearance_Game.h
#pragma once
# include <stdio.h>
# include <windows.h>
# include <time.h>
# include <stdlib.h>
#define ROW 9 /*扫雷棋盘显示行*/
#define COL 9 /*扫雷棋盘显示列*/
#define ROWS ROW+2 /*扫雷棋盘实际行*/
#define COLS COL+2 /*扫雷棋盘实际列*/
#define MINE_NUM 10 /*地雷数量*/
/******************控制台函数声明*****************************/
void Gotoxy(int x, int y);/*自定义控制台光标位置*/
void SetWindowSize(void);/*设置控制台窗口标题 大小*/
/******************游戏逻辑函数声明**************************/
void StartGame(void);/*开始游戏*/
void SetMine(char board[][COLS], int row, int col);/*在埋雷棋盘上布置雷*/
void DisplayBoard(char board[][COLS], int row, int col);/*打印扫雷棋盘*/
void ScreenMine(char mine[][COLS], char show[][COLS], int row, int col);/*排查雷*/
static int CalSurroundMineNum(char mine[][COLS], int x, int y);/*统计坐标周边雷数量*/
void ExpandBoard(char mine[ROWS][COLS], char show[ROWS][COLS], int x, int y, int* win);/*递归逐层次排查无雷坐标周边的坐标*/
/******************游戏界面函数声明**************************/
void Menu(void);/*打印游戏开始界面菜单*/
void ShowGameOver(void);/*打印游戏结束画面*/
void ShowSelectError(void);/*选择错误画面*/
void MineBoom(char mine[][COLS], char show[][COLS], int row, int col);/*踩到地雷画面*/
void FindAllMine(char show[][COLS], int row, int col);/*排雷成功画面*/
/***********************************************************/
mine_clearance_game.c
# include "Mine_Clearance_Game.h"
/*
函数名称:SetWindowSize
函数功能:设置控制台窗口标题 大小
编写时间:2022-07-14 19:18:40
*/
void SetWindowSize(void)
{
/*这里如果不行的一个原因可以把工具->选项->调试->常规->调试停止时自动关闭控制台打勾*/
system("title 扫雷");/*设置控制台标题*/
system("mode con cols=70 lines=30");/*设置控制台大小*/
}
/*
函数名称:Gotoxy
函数功能:自定义控制台光标位置
编写时间:2022-07-14 19:13:58
*/
void Gotoxy(int x, int y)
{
HANDLE hout = GetStdHandle(STD_OUTPUT_HANDLE);/*获取控制台句柄*/
COORD pos;
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(hout, pos);/*设置光标坐标*/
}
/*
函数名称:DisplayBoard
函数功能:打印扫雷棋盘
编写时间:2022-07-14 20:53:32
*/
void DisplayBoard(char board[][COLS], int row, int col)
{
/* 定义的时候,我们定义的是11*11的数组,但是显示的时候,实际上只要显示9*9的数组,因为实际使用到的就是9*9棋盘 */
int i = 0, j = 0;
for (i = 1; i <= row; i++)/* 行坐标从1 ~ 9 */
{
Gotoxy(18, 5+i*2-1);
printf(" %d ", i);/* 打印每一行的坐标 */
for (j = 1; j <= col; j++)/* 列坐标从1 ~ 9 */
{
printf(" %c ", board[i][j]);
}
printf("\n");
}
/*打印每一列的坐标*/
Gotoxy(18, 5 + i * 2 - 1);
for (int k = 0; k <= row; k++)
{
printf(" %d ", k);
}
}
/*
函数名称:SetMine
函数功能:在埋雷棋盘上布置雷
编写时间:2022-07-14 21:20:25
*/
void SetMine(char board[][COLS], int row, int col)
{
int mineRow = 0, mineCol = 0, countMine = 0;
while (countMine < MINE_NUM)
{
mineRow = (rand() % row) + 1;/*地雷的行坐标*/
mineCol = (rand() % col) + 1;/*地雷的列坐标*/
/* 判断是为了避免重复放雷 */
if (board[mineRow][mineCol] == '0')
{
board[mineRow][mineCol] = '1';/*埋雷*/
countMine++;/*统计已经埋下的地雷数量*/
}
}
}
/*
函数名称:CalSurroundMineNum
函数功能:统计坐标周边雷数量
编写时间:2022-07-14 22:39:47
*/
static int CalSurroundMineNum(char mine[][COLS], int x, int y)
{
int count = 0;
//循环方式
/*for (int i = x - 1; i <= x + 1; i++)
{
for (int j = y - 1; j <= y + 1; j++)
{
if (mine[i][j] == '1')
{
count++;
}
}
}*/
//非循环方式 因为布置雷的数组被初始化成了由'0'和'1'所组成的,所以将周边八个坐标相加减去8个字符0,就可以得到周边有多少个雷
//相较于循环方式时间复杂度减少
count = 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');
return count;
}
/*
函数名称:ExpandBoard
函数功能:统计坐标周边雷数量
编写时间:2022-07-14 22:39:47
*/
void ExpandBoard(char mine[][COLS], char show[][COLS], int x, int y, int* calNoMineNum)
{
int count = CalSurroundMineNum(mine, x, y);
/*如果该坐标周围坐标都不是雷 就继续递归*/
if (count == 0)
{
(*calNoMineNum)--;/*将除了雷以外的待排查坐标减1 */
show[x][y] = ' ';//没有雷的坐标赋值为空格
//递归周围的八个格子 每个排查的坐标必须满足在8*8的棋盘内 也就是不需要考虑9*9棋盘周围一圈的 因为无法扩展 容易数组越界
if ((show[x - 1][y - 1] == '*') && (x - 1 > 0 && x - 1 < ROWS) && (y - 1 > 0 && y - 1 < COLS))/*排查坐标左上角坐标*/
ExpandBoard(mine, show, x - 1, y - 1, calNoMineNum);
if ((show[x - 1][y] == '*') && (x - 1 > 0 && x - 1 < ROWS) && (y > 0 && y < COLS))/*排查坐标上方坐标*/
ExpandBoard(mine, show, x - 1, y, calNoMineNum);
if ((show[x - 1][y + 1] == '*') && (x - 1 > 0 && x - 1 < ROWS) && (y + 1 > 0 && y + 1 < COLS - 1))/*排查坐标右上角坐标*/
ExpandBoard(mine, show, x - 1, y + 1, calNoMineNum);
if ((show[x][y - 1] == '*') && (x > 0 && x < ROWS) && (y - 1 > 0 && y - 1 < COLS))/*排查坐标左方坐标*/
ExpandBoard(mine, show, x, y - 1, calNoMineNum);
if ((show[x][y + 1] == '*') && (x > 0 && x < ROWS) && (y + 1 > 0 && y + 1 < COLS - 1))/*排查坐标右方坐标*/
ExpandBoard(mine, show, x, y + 1, calNoMineNum);
if ((show[x + 1][y - 1] == '*') && (x + 1 > 0 && x + 1 < ROWS - 1) && (y - 1 > 0 && y - 1 < COLS))/*排查坐标左下角坐标*/
ExpandBoard(mine, show, x + 1, y - 1, calNoMineNum);
if ((show[x + 1][y] == '*') && (x + 1 > 0 && x + 1 < ROWS - 1) && (y > 0 && y < COLS))/*排查坐标下方坐标*/
ExpandBoard(mine, show, x + 1, y, calNoMineNum);
if ((show[x + 1][y + 1] == '*') && (x + 1 > 0 && x + 1 < ROWS - 1) && (y + 1 > 0 && y + 1 < COLS - 1))/*排查坐标右下角坐标*/
ExpandBoard(mine, show, x + 1, y + 1, calNoMineNum);
}
else/*如果该坐标周围坐标存在雷 就停止该坐标的逐层递归*/
{
(*calNoMineNum)--;/*将除了雷以外的待排查坐标减1 */
show[x][y] = count + '0';
}
}
/*
函数名称:ScreenMine
函数功能:排查雷
编写时间:2022-07-14 21:56:54
*/
void ScreenMine(char mine[][COLS], char show[][COLS], int row, int col)
{
int calNoMineNum = (ROW)*(COL) - MINE_NUM;/*注意 这里不能用ROWS 应该除了雷 9*9棋盘实际上只剩下71个坐标*/
/*循环退出的条件 排查完毕*/
while (calNoMineNum > 0)
{
int screenRow = 0, screenCol = 0;/*放在循环中定义作用是为了防止在第N此输入的时候,用户又输入了一个错误的值 导致死循环*/
system("cls");/* 清屏 */
DisplayBoard(show, row, col);/* 打印展示棋盘 */
Gotoxy(13, 3);
printf("请输入要排查的坐标:");
scanf("%d%d", &screenRow, &screenCol);
if ((screenRow >= 1 && screenRow <= 9) && (screenCol >= 1 && screenCol <= 9))
{
if (mine[screenRow][screenCol] == '1')/* 如果踩到了地雷 */
{
MineBoom(mine, show, row, col);
return;
}
else if (show[screenRow][screenCol] != '*')/* 如果位置已经排查过了 */
{
Gotoxy(45, 3);
printf("已排查");
Sleep(500);
}
else/* 如果没有踩到了地雷 并且没有排查过*/
{
/*递归排查 将坐标周边无地雷的进行扩展和显示地雷数量 */
ExpandBoard(mine, show, screenRow, screenCol, &calNoMineNum);
}
}
else
{
char ch;
Gotoxy(45, 3);
printf("坐标异常");
/*循环的作用是为了避免出现用户异常输入的时候 进入死循环 直接把输入缓冲区的内容读取干净*/
while ((ch = getchar()) != '\n')
;
Sleep(500);
}
}
if (calNoMineNum <= 0)/* 如果calNoMineNum等于0,说明排雷成功 */
{
FindAllMine(show, row, col);
}
}
/*
函数名称:StartGame
函数功能:开始游戏
编写时间:2022-07-14 20:49:28
*/
void StartGame(void)
{
system("cls");
char chEixt = '0';
char mineArray[ROWS][COLS] = { 0 };/*定义埋雷数组*/
char ShowArray[ROWS][COLS] = { 0 };/*定义显示给用户看的数组*/
/*将两个数组进行初始化*/
memset(mineArray, '0', (sizeof(char) * (ROWS) * (COLS)));/* 这里要括号括起来 因为出现了宏替换不带括号问题 */
memset(ShowArray, '*', (sizeof(char) * (ROWS) * (COLS)));
SetMine(mineArray, ROW, COL);/*埋地雷*/
/*
测试程序用
DisplayBoard(mineArray, ROW, COL);
Sleep(5000);
system("cls");
*/
ScreenMine(mineArray, ShowArray, ROW, COL);/*排查地雷*/
while (1)
{
Gotoxy(18, 27);
printf("请按下ESC退出当前界面");
if ((chEixt = getch()) == VK_ESCAPE)
break;
}
}
/*
函数名称:Menu
函数功能:打印游戏开始界面菜单
编写时间:2022-07-14 19:13:58
*/
void Menu(void)
{
SetWindowSize();/*设置控制台大小标题等*/
Gotoxy(18, 9);
printf("******************************");
Gotoxy(18, 11);
printf("******* 1:开始游戏 *******");
Gotoxy(18, 13);
printf("******* 0:退出游戏 *******");
Gotoxy(18, 15);
printf("******************************");
Gotoxy(18, 17);
printf("请输入你的选择:");
}
/*
函数名称:FindAllMine
函数功能:排雷成功画面
编写时间:2022-07-14 23:23:21
*/
void FindAllMine(char show[][COLS], int row, int col)/**/
{
system("cls");
Gotoxy(25, 3);
printf("恭喜你 排雷成功");
DisplayBoard(show, row, col);
}
/*
函数名称:MineBoom
函数功能:踩到地雷画面
编写时间:2022-07-14 22:29:37
*/
void MineBoom(char mine[][COLS], char show[][COLS], int row, int col)
{
system("cls");
Gotoxy(18, 3);
printf("踩到地雷,你被炸死了!!");
/*显示当前棋盘上所有地雷*/
for (int i = 1; i <= row; i++)
{
for (int j = 1; j <= col; j++)
{
if (mine[i][j] == '1')
{
show[i][j] = '@';
}
}
}
DisplayBoard(show, row, col);/*打印展示的扫雷棋盘*/
}
/*
函数名称:ShowSelectError
函数功能:选择错误画面
编写时间:2022-07-14 19:25:47
*/
void ShowSelectError(void)
{
system("cls");
Gotoxy(18, 10);
printf("******************************");
Gotoxy(18, 12);
printf("******* 输入错误 *******");
Gotoxy(18, 14);
printf("******************************");
Sleep(1000);
}
/*
函数名称:ShowGameOver
函数功能:打印游戏结束画面
编写时间:2022-07-14 19:25:50
*/
void ShowGameOver(void)
{
system("cls");
Gotoxy(18, 10);
printf("******************************");
Gotoxy(18, 12);
printf("******* Bye Bye ! *******");
Gotoxy(18, 14);
printf("******************************");
printf("\n");
}
如果直接复制代码的话,在vs20XX系列编译器底下,可能会出现无窗口标题,窗口大小无法修改的情况,虽然用了system函数,但是还是无法修改,具体原因,可以在vs编译器中选择工具->选项->调试->常规->调试停止时自动关闭控制台打勾,就可以了
main.c
/**********************************************************/
/**************** 扫雷游戏 *******************/
/*1:界面构造 2:布置两个棋盘 一个布置雷 一个显示给用户 */
/*3:打印界面 4:布置地雷 5:计算雷的数量 */
/*6:排查地雷 7:递归排查某个坐标周边的坐标 8:开始游戏*/
/**********************************************************/
# include "Mine_Clearance_Game.h"
int main(void)
{
/* srand最好放在main函数中,也就是只调用一次
如果调用多次,结果可能不够随机,time(NULL)表示
srand随着时间戳的改变,调整rand每一次随机值*/
srand((unsigned int)time(NULL));
char userSelect = '0';
do
{
Menu();/*打印菜单*/
userSelect = getch();/*等待用户输入*/
switch (userSelect)
{
case '1':
StartGame();/*开始游戏*/
break;
case '0':
ShowGameOver();/*结束游戏*/
break;
default:
ShowSelectError();/*选择错误*/
break;
}
} while (userSelect != '0');
return 0;
}
好啦 ,如果有更优化的,希望多多评论 谢谢!