1. 扫雷游戏分析和设计
1.1 扫雷游戏的功能说明
- 体现菜单功能
- 实现棋盘功能
- 实现展示棋盘功能
- 实现布雷功能
- 使用控制台呈现扫雷游戏
游戏界面:
1.2 游戏的功能分析
扫雷过程中,存储的雷以及玩家搜查的雷的信息都是需要存储的,对于一个9x9的棋盘,我们能够轻松的想到用一个9x9的二维数组来储存。
雷的信息我们便用'1'来存储,无雷则用'0'来存储。
而如果我们下达到像扫雷游戏中,点击一次,便告知周围九宫格内的雷的数量,我们只需将九宫格内的字符相加-'0',得到的便是雷的数量。
但问题随之而来,在检测棋盘周边时,会出现数组越界的情况,为了防止这样的情况,我们可以将9x9的棋盘变成11x11存储进数组内,但在显示时只展示中间的9x9的棋盘。
2. 代码的实现
2.1 菜单的实现
void menu() {
printf("*********************\n");
printf("****** 1.game *****\n");
printf("****** 0.exit *****\n");
printf("*********************\n");
printf("注:只有扫出所有非雷处才可胜利!\n");
printf("\n");
}
对于以上的简易菜单,我们需要实现的功能如下:
- 不断的循环,我们在游戏结束后如果我们还想继续游戏,我们希望接着弹出这个游戏菜单。
- 功能的实现,我们输入1便开始游戏,输入0便退出游戏,但我们也有可能输入别的,这就需要我们对错误信息进行提示。
int main() {
int input = 0;
do {
menu();
printf("公主请输入:");
scanf("%d", &input);
switch (input) {
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请公主重新选择:>\n");
printf("\n");
break;
}
} while (input);
}
在这里,我们利用do...while循环便能够实现功能,若输入1则进入game()函数中进行游戏;若输入0便退出switch,同时因为while条件为input,所以一旦输入0,循环也退出,便达到了退出游戏的效果;若输入其他的,便会进入default中。
2.2 game函数的实现
在上文中,我们输入了1,进入了game()函数,我们便可以开始游戏了。首先我们需要创建数组来存储棋盘,我们需要两个数组,一个用来放雷,一个用来显示,如上图中的棋盘一样。
#define ROW 9
#define COL 9
#define ROWS 11
#define COLS 11
char mine[ROWS][COLS] = { 0 };//存放雷区
char show[ROWS][COLS] = { 0 };//用来显示
而为了实现雷区中1有雷0无雷,玩家眼中数字为雷的数量,'*'为未排查区域的效果,我们首先需要对棋盘初始化,其次需要将棋盘打印出来。
2.2.1 棋盘初始化
在这里我们创建一个Initboard函数以对棋盘初始化,对于这样的函数,我们首先要将数组传过去,其次需要告诉他我们的行列个数,最后需要告诉他我们希望棋盘变成什么样子。
Initboard(mine, ROWS, COLS, '0');//初始化雷区,'0'为无雷,'1'为有雷
Initboard(show, ROWS, COLS, '*');//初始化显示
//初始化函数
void Initboard(char board[ROWS][COLS], int rows, int cols, char set) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
board[i][j] = set;
}
}
}
2.2.2 棋盘展示
对于这样一个Display函数,我们也需要将数组,棋盘的行列进行传参,随后就像初始化函数一样,利用两层循环嵌套实现对棋盘的打印。
Display(show, ROW, COL);//展示
//显示函数
void Display(char board[ROWS][COLS], int row, int col) {
printf("-----扫雷游戏-----\n");
//打印列标
for (int j = 0; j <= col; j++) {
if (j == 0) {
printf(" ");//让第一个不显示0,而是空格
continue;
}
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");
printf("\n");
}
在这里为了让我们的游戏更为直观,我们打印了行标与列标,还打印了扫雷游戏的title,但本质还是对循环的嵌套运用。
2.2.3 棋盘安雷
我们使用rand实现对雷区安雷的随机性,同时我们引入时间戳以实现其初值的随机性。但要注意的是:srand需要的是unsigned int类型的值,所以我们需要对时间戳进行强制类型转换。
#define Easy_Count 10
srand((unsigned int)time(NULL));
//设置雷区函数
void SetMine(char board[ROWS][COLS], int row, int col) {
int count = Easy_Count;
while (count) {
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0') {
board[x][y] = '1';
count--;
}
}
}
在这里我们设置难度为Easy—Count,也就是10个雷。将rand得出的随机值%列与行再+1,得到的便是x与y的准确坐标。
2.2.4 周边雷的数量的显示
在玩家输入坐标后,如果不是雷,我们需要对其周边雷的数量进行统计并展示。
//数出周边雷的函数
int get_mine_count(char board[ROWS][COLS], int x, int y) {
int num = 0;
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
num += board[x + i][y + j];//字符相加
}
}
int ret = 9 * '0';//字符相加=他们的ascall码值相加,再减去9个'0'的ascall码,化为数字
num -= ret;
return num;
}
2.2.5 扫雷与胜负判定
在这里我们实现一个简单的扫雷,玩家需要找出所有没有雷的地方,才能判定胜利。
但对于扫雷函数的实现,我们仍要有注意的地方,玩家是否重复查找,玩家查找时是否踩雷,玩家是否输错坐标等。
我们引入x,y作为坐标;win用来观察是否胜利。
如果win==row*col-Easy-Count,也就是win等于棋盘位置的总数减去雷数,即玩家胜利,除此则进入循环扫雷环节。
首先我们需要对玩家输入的坐标进行判定,如果输错坐标,我们要提示玩家;而如果输对了坐标,我们则需对坐标是否重复排查进行判定。
我们知道在我们的show棋盘,也就是显示棋盘中,如果玩家没有搜查,则全为'*',如果有玩家搜查的地方则为其周边雷的数量,所以如果玩家输入的坐标此时不是'*',则玩家重复排查。
如果玩家没有重复排查,此时我们则只需要对此处坐标是否有雷进行判定。如果有雷,也就是mine棋盘上此处为'1',则我们在提示玩家比赛结束的同时还可以展示棋盘雷的位置。如果无雷则win++,同时展示此处周围雷的数量。
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) {
int x = 0;
int y = 0;
int win = 0;
//如果win<有雷数
while (win < row * col - Easy_Count) {
printf("公主请扫雷(请输入x y):");
scanf("%d %d", &x, &y);
//如果没有输错坐标
if (x >= 1 && x <= row && y >= 1 && y <= col) {
//如果重复排查
if (show[x][y] != '*') {
printf("\n");
printf("这不整过了吗,重整!\n");
printf("\n");
}
//如果没有重复排查
else {
//如果是雷
if (mine[x][y] == '1') {
printf("\n");
printf("被炸死了笨比\n");
printf("\n");
Display(mine, ROW, COL);
break;
}
//如果不是雷
else {
win++;
int count = get_mine_count(mine, x, y);
show[x][y] = count + '0';//转换成数字字符
Display(show, ROW, COL);
}
}
}
//如果输错坐标
else {
printf("输错了笨比!给我重输!\n");
printf("\n");
}
//如果win==无雷数
if (win == row * col - Easy_Count) {
printf("\n");
printf("恭喜你胜利!\n");
printf("\n");
Display(mine, ROW, COL);
}
}
}
游戏展示:
3. 代码展示
如上,我们的游戏便完成了。要注意不要忘记对函数进行声明,虽然在编译器里不声明不一定报错,但为了代码的逻辑性与规范性,我们还是应该要养成这样良好的习惯。
我们可以使用两个文件,一个头文件用来函数声明,一个源文件用以功能实现。
game.h:
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 9
#define COL 9
#define ROWS 11
#define COLS 11
#define Easy_Count 10
void menu();
void game();
void Initboard(char board[ROWS][COLS], int rows, int cols, char set);
void Display(char board[ROWS][COLS], int row, int col);
void SetMine(char board[ROWS][COLS], int row, int col);
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
int get_mine_count(char board[ROWS][COLS], int x, int y);
game.c:
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void menu() {
printf("*********************\n");
printf("****** 1.game *****\n");
printf("****** 0.exit *****\n");
printf("*********************\n");
printf("注:只有扫出所有非雷处才可胜利!\n");
printf("\n");
}
void game() {
char mine[ROWS][COLS] = { 0 };//存放雷区
char show[ROWS][COLS] = { 0 };//用来显示
Initboard(mine, ROWS, COLS, '0');//初始化雷区,'0'为无雷,'1'为有雷
Initboard(show, ROWS, COLS, '*');//初始化显示
Display(show, ROW, COL);//展示
SetMine(mine, ROW, COL);//随机安排雷
FindMine(mine, show, ROW, COL);//开始扫雷
}
int main() {
srand((unsigned int)time(NULL));
int input = 0;
do {
menu();
printf("公主请输入:");
scanf("%d", &input);
switch (input) {
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请公主重新选择:>\n");
printf("\n");
break;
}
} while (input);
}
//初始化函数
void Initboard(char board[ROWS][COLS], int rows, int cols, char set) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
board[i][j] = set;
}
}
}
//显示函数
void Display(char board[ROWS][COLS], int row, int col) {
printf("-----扫雷游戏-----\n");
//打印列标
for (int j = 0; j <= col; j++) {
if (j == 0) {
printf(" ");//让第一个不显示0,而是空格
continue;
}
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");
printf("\n");
}
//设置雷区函数
void SetMine(char board[ROWS][COLS], int row, int col) {
int count = Easy_Count;
while (count) {
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0') {
board[x][y] = '1';
count--;
}
}
}
//扫雷函数
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) {
int x = 0;
int y = 0;
int win = 0;
//如果win<有雷数
while (win < row * col - Easy_Count) {
printf("公主请扫雷(请输入x y):");
scanf("%d %d", &x, &y);
//如果没有输错坐标
if (x >= 1 && x <= row && y >= 1 && y <= col) {
//如果重复排查
if (show[x][y] != '*') {
printf("\n");
printf("这不整过了吗,重整!\n");
printf("\n");
}
//如果没有重复排查
else {
//如果是雷
if (mine[x][y] == '1') {
printf("\n");
printf("被炸死了笨比\n");
printf("\n");
Display(mine, ROW, COL);
break;
}
//如果不是雷
else {
win++;
int count = get_mine_count(mine, x, y);
show[x][y] = count + '0';//转换成数字字符
Display(show, ROW, COL);
}
}
}
//如果输错坐标
else {
printf("输错了笨比!给我重输!\n");
printf("\n");
}
//如果win==无雷数
if (win == row * col - Easy_Count) {
printf("\n");
printf("恭喜你胜利!\n");
printf("\n");
Display(mine, ROW, COL);
}
}
}
//数出周边雷的函数
int get_mine_count(char board[ROWS][COLS], int x, int y) {
int num = 0;
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
num += board[x + i][y + j];//字符相加
}
}
int ret = 9 * '0';//字符相加=他们的ascall码值相加,再减去9个'0'的ascall码,化为数字
num -= ret;
return num;
}