目录
一、游戏简介
二、游戏设计
2.1功能设计
2.2模型设计
三、游戏实现
3.1SaoLeiMain.c
3.3Game.c
3.3.1 playGame(游戏执行模块)
3.3.2?saoLei(玩家扫雷模块)
3.3.3 boardInit(初始化数组模块)
3.3.4 setMine(埋雷模块)
3.3.5 displayBoard(显示模块)
3.3.6 getMineCount(数雷模块)
3.3.7 isNoLei(延展模块)
四、游戏测试
五、游戏代码
5.1?SaoLeiMain.c
5.3 Game.c
5.5 Game.h
一、游戏简介
《扫雷》是一款大众类的益智小游戏,于1992年发行。游戏目标是在最短的时间内根据点击格子出现的数字找出所有非雷格子,同时避免踩雷,踩到一个雷即全盘皆输。
相信大家都在自己的电脑上玩过这款游戏。又想尝试玩玩怎么办?不如自己动手,将这款经典游戏再次复现吧!
二、游戏设计
2.1功能设计
在完成扫雷游戏游玩的最基本功能的同时,也要设计出标记地雷的功能,且在选中一个时,若周围一圈无雷,需要自动点开周围一圈的区域,并如此延展下去,已达到节省游戏时间的目的。
2.2模型设计
三、游戏实现
3.1SaoLeiMain.c
(1)模块功能及思路:本模块为程序的入口,通过此模块调入菜单模块,执行开始游戏或退出游戏的功能。还涉及到rand()函数的配置,在之前的文章中也提到了这一操作。同时设计为循环的形式,以便玩家多次游玩。
(2)模块代码:
int main()
{
int flag = 0;
srand((unsigned int)time(NULL));
do
{
switch (flag = choiceMenu())
{
case 1:playGame(); break;
case 0:printf("退出游戏\n"); break;
default:printf("输入错误,请重新选择!\n"); break;
}
} while (flag);
return 0;
}
(1)模块功能及思路:该函数负责打印游戏开始菜单,同时需要返回玩家的选项值。
(2)模块代码:
int choiceMenu()
{
int input = 0;
printf("********************************\n");
printf("********* 1. play ********\n");
printf("********* 0. exit ********\n");
printf("********************************\n");
printf("请输入选项>");
scanf("%d", &input);
return input;
}
3.3Game.c
3.3.1 playGame(游戏执行模块)
(1)模块功能及思路:想要高效地完成扫雷游戏,需要定义两个二维数组用以存放信息。其中一个数组用来存放布雷图,这是玩家看不到的。而另一个数组用来存放游戏棋盘,这是玩家所能看到的。在定义这两个二维数组时,可采用宏定义的手段,以便随时调整游戏难度。所有的宏定义均设置在Game.h文件下,除控制数组大小的宏定义外,还有控制地雷数目的宏定义。
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define EASY_COUNT 10
接下来就来看看本模块的主要执行流程:初始化布雷数组——>初始化棋盘数组——>设置地雷——>显示棋盘——>玩家扫雷
(2)模块代码:
void playGame()
{
char mine[ROWS][COLS] = { 0 };//雷区图
char show[ROWS][COLS] = { 0 };//玩家图
system("cls");
boardInit(mine, ROWS, COLS, '0');//数组初始化
boardInit(show, ROWS, COLS, '*');//数组初始化
setMine(mine, ROW, COL);//埋雷
displayBoard(show, ROW, COL);//显示玩家图
saoLei(mine, show, ROW, COL);//玩家扫雷
}
3.3.2?saoLei(玩家扫雷模块)
(1)模块功能及思路:玩家在执行扫雷程序时,首先会面临两个选择,一是排雷,二是标记雷。想要实现标记地雷非常简单,只需要根据输入的坐标对棋盘数组进行字符的替换即可,(注意,每次输入坐标时均要判断输入是否正确!)。
而如果玩家选择排雷,又会出现多种情况,一是选中地雷,游戏结束,玩家失败;二是没选中地雷这时就要进入延展模块。
以上过程为一个循环,终止此过程的条件是胜利或失败。那么如何判断忘记是否排雷完成呢?这里我们可以定义一个变量win,每当选中一个各种并且此格非雷,win就会加一。通过简单的数学计算,不难发现,当点完所有非雷格即win = 总格数 - 地雷数时,玩家胜利。因此循环条件为win != row * col - EASY_COUNT
(2)模块代码:
void saoLei(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
int input = 0;
while (win != row * col - EASY_COUNT)
{
printf("排雷(1)或标记雷(2)>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入排雷坐标>");
scanf("%d%d", &x, &y);//输入坐标
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
system("cls");
displayBoard(mine, ROW, COL);
printf("恭喜您被炸飞了\n");
win = row * col - EASY_COUNT;
break;
}
else
{
isNoLei(mine, show, ROW, COL, x, y, &win);
}
}
else
printf("输入错误,请重新输入!\n");
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
displayBoard(mine, ROW, COL);
}
break;
case 2:
printf("请输入标记坐标>");
scanf("%d%d", &x, &y);//输入坐标
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
show[x][y] = '!';
system("cls");
displayBoard(show, ROW, COL);
}
break;
default:printf("输入错误,请重新选择\n");
}
}
}
3.3.3 boardInit(初始化数组模块)
(1)模块功能及思路:本模块的实现也非常简单,只是初始化二维数组的基本步骤。需要注意的是两个二维数组初始化的内容不同,因此需要定义一个参数获取要初始化的内容。
(2)模块代码:
void boardInit(char arr[ROWS][COLS], int row, int col, char n)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
arr[i][j] = n;
}
}
}
3.3.4 setMine(埋雷模块)
(1)模块功能及思路:本模块复制布置地雷。由于地雷是随机放置的,因此只需要利用rand()函数获取随机坐标即可。这一操作在之前的三子棋文章中有具体提到。定义一个变量记录布雷数,每成功布置一次地雷(不能在一个区域上重复布置)雷数加一,知道等于宏定义设置的初值。
(2)模块代码:
void setMine(char arr[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = 0;
do
{
x = rand() % row + 1;
y = rand() % col + 1;
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count++;
}
} while (count < EASY_COUNT);
}
3.3.5 displayBoard(显示模块)
(1)模块功能及思路:本模块的实现同样非常简单,也只是显示二维数组的一般步骤。在显示棋盘的同时,也可以显示一个横纵坐标,便于玩家选择格子。
(2)模块代码:
void displayBoard(char arr[ROWS][COLS], int row, int col)
{
int i = 0;
printf("-------------------\n");
for (i = 0; i <= 9; i++)
{
printf("%d ", i);
}
printf("\n-------------------\n");
for (i = 1; i <= row; i++)
{
printf("%d|", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
printf("-------------------\n");
}
3.3.6 getMineCount(数雷模块)
(1)模块功能及思路:本模块的功能为计算选中格周围一圈的地雷数目。这里使用了一种很简单的计算方法。由于在mine数组中,地雷用字符'1'表示,无雷用'0'表示,所以只需要将周围一圈字符的ASCII码值相加,再减去8个字符'0'的ASCII码值即可。
(2)模块代码:
int getMineCount(char mine[ROWS][COLS], int x, int y)
{
return (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');
}
3.3.7 isNoLei(延展模块)
(1)模块功能及思路:本模块要在一个格子的周边无雷时,自动排查周围一圈的格子,并点开排查格中周围一圈无雷的格子。要想简单的完成这个功能,最好采用递归的方法,递归在解决一些复杂问题时,能够使问题简单化。
在本部分使用递归,需要防止一个格子被重复排查,这样会照成死递归,因此,在每次进入这个函数时,需要判断格子是否已被点开,只要判断show数组中该坐标的字符是否为'*'即可。
每次点开一个格子时,win需要加一,在之前的模块中提到过。因此,想要改变win的值,必须利用指针进行间接操作。
(2)模块代码:
void isNoLei(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* win)
{
if ((show[x][y] == '*')&&(x >= 1 && x <= row && y >= 1 && y <= col))
{
*win = *win + 1;
int count = getMineCount(mine, x, y);
if (count != 0)
{
show[x][y] = count + '0';
system("cls");
displayBoard(show, ROW, COL);
}
else
{
show[x][y] = count + '0';
isNoLei(mine, show, ROW, COL, x - 1, y, win);
isNoLei(mine, show, ROW, COL, x - 1, y - 1, win);
isNoLei(mine, show, ROW, COL, x, y - 1, win);
isNoLei(mine, show, ROW, COL, x + 1, y - 1, win);
isNoLei(mine, show, ROW, COL, x + 1, y, win);
isNoLei(mine, show, ROW, COL, x + 1, y + 1, win);
isNoLei(mine, show, ROW, COL, x, y + 1, win);
isNoLei(mine, show, ROW, COL, x - 1, y + 1, win);
}
}
}
四、游戏测试
?
?
?
?
五、游戏代码
5.1?SaoLeiMain.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Menu.h"
int main()
{
int flag = 0;
srand((unsigned int)time(NULL));
do
{
switch (flag = choiceMenu())
{
case 1:playGame(); break;
case 0:printf("退出游戏\n"); break;
default:printf("输入错误,请重新选择!\n"); break;
}
} while (flag);
return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include "Menu.h"
int choiceMenu()
{
int input = 0;
printf("********************************\n");
printf("********* 1. play ********\n");
printf("********* 0. exit ********\n");
printf("********************************\n");
printf("请输入选项>");
scanf("%d", &input);
return input;
}
5.3 Game.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Game.h"
void boardInit(char arr[ROWS][COLS], int row, int col, char n)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
arr[i][j] = n;
}
}
}
void setMine(char arr[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = 0;
do
{
x = rand() % row + 1;
y = rand() % col + 1;
if (arr[x][y] == '0')
{
arr[x][y] = '1';
count++;
}
} while (count < EASY_COUNT);
}
void displayBoard(char arr[ROWS][COLS], int row, int col)
{
int i = 0;
printf("-------------------\n");
for (i = 0; i <= 9; i++)
{
printf("%d ", i);
}
printf("\n-------------------\n");
for (i = 1; i <= row; i++)
{
printf("%d|", i);
int j = 0;
for (j = 1; j <= col; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
printf("-------------------\n");
}
int getMineCount(char mine[ROWS][COLS], int x, int y)
{
return (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');
}
void isNoLei(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col, int x, int y, int* win)
{
if ((show[x][y] == '*')&&(x >= 1 && x <= row && y >= 1 && y <= col))
{
*win = *win + 1;
int count = getMineCount(mine, x, y);
if (count != 0)
{
show[x][y] = count + '0';
system("cls");
displayBoard(show, ROW, COL);
}
else
{
show[x][y] = count + '0';
isNoLei(mine, show, ROW, COL, x - 1, y, win);
isNoLei(mine, show, ROW, COL, x - 1, y - 1, win);
isNoLei(mine, show, ROW, COL, x, y - 1, win);
isNoLei(mine, show, ROW, COL, x + 1, y - 1, win);
isNoLei(mine, show, ROW, COL, x + 1, y, win);
isNoLei(mine, show, ROW, COL, x + 1, y + 1, win);
isNoLei(mine, show, ROW, COL, x, y + 1, win);
isNoLei(mine, show, ROW, COL, x - 1, y + 1, win);
}
}
}
void saoLei(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
int input = 0;
while (1)
{
printf("排雷(1)或标记雷(2)>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入排雷坐标>");
scanf("%d%d", &x, &y);//输入坐标
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
system("cls");
displayBoard(mine, ROW, COL);
printf("恭喜您被炸飞了\n");
win = row * col - EASY_COUNT;
break;
}
else
{
isNoLei(mine, show, ROW, COL, x, y, &win);
}
}
else
printf("输入错误,请重新输入!\n");
if (win == row * col - EASY_COUNT)
{
printf("恭喜你,排雷成功\n");
displayBoard(mine, ROW, COL);
}
break;
case 2:
printf("请输入标记坐标>");
scanf("%d%d", &x, &y);//输入坐标
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
show[x][y] = '!';
system("cls");
displayBoard(show, ROW, COL);
}
break;
default:printf("输入错误,请重新选择\n");
}
}
}
void playGame()
{
char mine[ROWS][COLS] = { 0 };//雷区图
char show[ROWS][COLS] = { 0 };//玩家图
system("cls");
boardInit(mine, ROWS, COLS, '0');//数组初始化
boardInit(show, ROWS, COLS, '*');//数组初始化
setMine(mine, ROW, COL);//埋雷
displayBoard(show, ROW, COL);//显示玩家图
saoLei(mine, show, ROW, COL);//玩家扫雷
}
#pragma once
#include "Game.h"
int choiceMenu();
5.5 Game.h
#pragma once
#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 playGame();
|