一,引言
相信大家对扫雷这款经典的游戏并不陌生吧,规则简单却具有着极高的可玩性。我们的目标是避开雷,打开其他所有格子。一个非雷格中的数字表示其相邻8格中的雷数,而我们可以利用这个信息推导出安全格和雷的位置。并且这一经典的游戏可以通过C语言实现,还在等什么?让我们现在开始吧!!
二,多文件创建
对于扫雷游戏,我们这次会采用多文件处理的办法,分别使用到了test.c用于整个函数的逻辑的实现;game.h用于包含扫雷游戏所遇到的函数的声明;game.c则用于函数体的实现。如下:
三,游戏菜单的设置
首先,我们需要一个游戏菜单,当使用者在键盘上输入某个值,则开始游戏或者退出游戏,若输入的值错误则重新输入。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void menu()
{
printf("1,开始游戏\n");
printf("0,退出游戏\n");
}
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");
break;
}
} while (input);
return 0;
}
见上,此处使用了do-while循环语句,而整个代码的巧妙之处就在于input的值的使用。我们使用input的值作为了while语句真假的判断条件,而我们知道,C语言规定,零为假,非零为真,而我们又恰恰地使用了1为开始游戏,0为退出游戏,倘若使用者输入零,则while判断条件为假,则会直接退出循环,从而退出游戏;倘若使用者输入1,则执行game();游戏函数,开始游戏,并且在游戏结束后,执行循环从而让使用者继续选择是否再次游戏;倘若输入其他的,则输入错误,重新输入。
四,初始化棋盘
我们这次会使用9*9的棋盘大小进行游戏,所以我们在game.h的头文件中定义了ROW为9,COL为9,但是在实际游戏代码的编写中中,我们却需要11*11的棋盘大小以方便布置和记录雷的个数,故我们如下定义:
一想到设置棋盘,那么不可避免的就是二维数组的使用,所以我们会在test.c中的game函数中定义两个字符数组:1,mine[ROWS][COLS]={ 0 };用于存放雷的信息。2,show[ROWS][COLS]={ 0 }用于雷的信息的展示。
并且再使用新的函数InitBoard用于将两个二维数组棋盘初始化,先将mine数组全部初始化为‘0’,再将show数组全部初始化为‘*’。
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
五,显示棋盘
棋盘初始化创建好了之后,我们需要在玩家选择开始游戏后显示一个游戏棋盘,并且附带上对应的坐标,当然我们也需要自己创建另一个游戏棋盘,用于存放雷布置的信息(当然,这个棋盘只有研发者自己知道它的存在)。
因此我们设计了一个DisplayBoard函数,用于打印我们的棋盘,其中show是用于呈现于玩家的棋盘,mine则是我们自己操作,用于存放雷的信息的棋盘。最后使用如下代码打印棋盘和行列标。
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
//打印列号
int i = 0;
printf("扫雷开始\n");
for ( i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for ( i = 1; i <= row; i++)
{
int j = 0;
//打印行号
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
六,布置雷
为了更好的布置雷,我们将创建并使用一个SetMine函数。
在函数中我们首先需要定义好雷的数量以确定游戏的难度,所有我们需要先在game.h头文件中定义一个EASY_COUNT为10,以方便日后修改雷的数量。
那么,又如何在一个二维字符数组中布置雷呢?对了!当然是使用x,y坐标最方便不过了!但是其中的坐标设置很有讲究。
我们使用的是一个表面大小为9*9,但真实大小为11*11的二维数组,但其实我们只需要在行标与列标都在1-9之间的范围内设置雷就可以了,就像下图这样。
所以我们如下定义x,y的数值以确定雷的坐标,并通过while循环与if条件判断语句实现雷的布置,并在主函数中加入srand。
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;//0-9 to 1-10
int y = rand() % col + 1;//0-9 to 1-10
if (board[x][y] = '0')
{
board[x][y] = '1';
count--;
}
}
}
注意:while循环的判断部分不可以写为count--,因为假如x,y表示的二维数组坐标对应的字符本身就为‘1’,则雷不会被重复布置,但是count仍然减1,这会导致布置雷的数量小于10个!!
七,排查雷
为了达到排查雷的目的,我们需要设置一个FindMine函数,以完成排查雷的功能。我们将mine与show数组都传进这个函数的原因是在函数中,我们将会对这两个数组都进行操作。
在函数中,我们应首先定义一个x与y并要求玩家输入对应的x,y坐标进行排查,并且如果输入的坐标超出了棋盘范围我们还应该予以提示并要求重新输入;其次,在坐标输入正确的前提下,我们将检查对应坐标所对应的字符是否为雷,如果是雷,则直接游戏结束,然后打印棋盘真实埋雷情况最后break语句跳出循环,如果不是雷,那我们将统计该坐标周围八个坐标的雷的数量并显示于show数组中,游戏继续。代码示例如下。
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
printf("请输入需要排查的坐标:");
int x = 0;
int y = 0;
while (1)
{
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '0')
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
//统计x,y坐标周围的八个坐标中的雷的数量
int count = MineCount(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
}
}
else
{
printf("输入错误,请重新输入\n");
}
}
}
其中,对于MineCount函数(统计x,y周围八个坐标的雷的数量)的操作方式十分巧妙!
我们都知道,棋盘中x,y周围的坐标情况如下:
我们都知道字符1减去字符0也就是它们对应的阿斯克码值相减就为数字1,秉承着这种思想,我们可以直接将x,y周围的八个字符相加并且减去八个字符0,得到的答案不就正好等于雷的数量吗,然后我们再使用count接受返回值并打印在show数组中,那么一次简单的排雷不就完成了吗!
所以MineCount函数的具体内容如下:
int MineCount(char mine[ROWS][COLS], int x, int y)
{
return 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';
}
八,结束游戏
当然,我们需要一个条件来结束游戏。仔细想一想,在一个9*9的棋盘中,我们布置下了10个雷,那么是不是有71个地方不是雷呢?当我们只要找完了那71个地方,这时候游戏就胜利结束了,所以我们选择一个win变量,当成功选择到一个不是雷的坐标时就加一,并将while循环的条件改为(win<ROW*COL-EASY-COUNT),当win不满足while循环的条件,则跳出循环,进入if语句,游戏胜利,打印出原本棋盘的样子,恭喜恭喜!!
所以改良版如下:
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
printf("请输入需要排查的坐标:");
int x = 0;
int y = 0;
int win = 0;
while (win<ROW*COL-EASY_COUNT)
{
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '0')
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
//统计x,y坐标周围的八个坐标中的雷的数量
int count = MineCount(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("输入错误,请重新输入\n");
}
}
if (win == ROW * COLS - EASY_COUNT)
{
printf("恭喜你,游戏胜利!!\n");
DisplayBoard(mine, ROW, COL);
}
}
九,整个游戏的代码
1,test.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include"game.h"
#include<stdlib.h>
#include<time.h>
void menu()
{
printf("1,开始游戏\n");
printf("0,退出游戏\n");
}
void game()//完成整个扫雷游戏
{
char mine[ROWS][COLS] = { 0 };//存放布置好的雷的信息,雷为‘0’,没有雷为‘1’
char show[ROWS][COLS] = { 0 };//存放布置好的雷的信息用于显示,全为*
//初始化棋盘
InitBoard(mine, ROWS, COLS,'0');
InitBoard(show, ROWS, COLS,'*');
//显示棋盘
DisplayBoard(show, ROW, COL);
/*DisplayBoard(mine, ROW, COL);*/
//布置雷
SetMine(mine, ROW, COL);
/*DisplayBoard(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");
break;
}
} while (input);
return 0;
}
2,game.h
#pragma once
#define EASY_COUNT 10
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
//其他初始化
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set);
//显示棋盘
void DisplayBoard(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);
3,game.c
#define _CRT_SECURE_NO_WARNINGS
#include"game.h"
#include<stdio.h>
void InitBoard(char board[ROWS][COLS], int rows, int cols,char set)
{
int i = 0;
for (i = 0; i < rows; i++)
{
int j = 0;
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
//打印列号
int i = 0;
printf("扫雷开始\n");
for ( i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
for ( i = 1; i <= row; i++)
{
int j = 0;
//打印行号
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", board[i][j]);
}
printf("\n");
}
}
void SetMine(char board[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
while (count)
{
int x = rand() % row + 1;//0-9 to 1-10
int y = rand() % col + 1;//0-9 to 1-10
if (board[x][y] = '0')
{
board[x][y] = '1';
count--;
}
}
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
printf("请输入需要排查的坐标:");
int x = 0;
int y = 0;
int win = 0;
while (win<ROW*COL-EASY_COUNT)
{
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '0')
{
printf("很遗憾,你被炸死了\n");
DisplayBoard(mine, ROW, COL);
break;
}
else
{
//统计x,y坐标周围的八个坐标中的雷的数量
int count = MineCount(mine, x, y);
show[x][y] = count + '0';
DisplayBoard(show, ROW, COL);
win++;
}
}
else
{
printf("输入错误,请重新输入\n");
}
}
if (win == ROW * COLS - EASY_COUNT)
{
printf("恭喜你,游戏胜利!!\n");
DisplayBoard(mine, ROW, COL);
}
}
int MineCount(char mine[ROWS][COLS], int x, int y)
{
return 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';
}
十,结束语
好了,这就是整个扫雷游戏的代码逻辑了,这也是我第一次写三文件代码,总共加起来应该有几百行吧,对我来说也不是一个小数目了,但是可以就这个扫雷游戏搞定,将整个逻辑清晰地表达在博客里还是挺让人心满意足的,加油吧,keep on going,希望我的编程技术可以更上一层楼,也希望看到这里的你点点赞,你的支持就是我对我最大的鼓励!!