目录
PS:使用 头文件中的 time() 函数即可得到当前的时间,利用时间作为参数,每次播种的时间不同,那么生成的种子就不同,最终的随机数也就不同。
相信阅读本篇文章的你或多或少的接触过一款名为“扫雷”的游戏,在一定区域的矩阵内布置雷,通过标点,排查雷的个数。那么如何用c语言制作属于自己的一款“扫雷(简化版)”游戏呢?
一、扫雷游戏分析
1.1棋盘大小设置
定义一个9*9的棋盘,将棋盘的行,列分别定义为全局变量——方便后面的引用和棋盘大小的更改。
在这里还定义了ROWS和COLS,至于为什么,在后文会有解释。
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
1.2棋盘初始化
开始定义两个大小由ROWS,COLS决定的二维字符型数组,一个用于存储雷命名为mine(真正的棋盘),用'1'表示雷,'0'则表示安全区(无雷);而另外一个数组用于掩盖整个棋盘命名为show(展示的棋盘),让玩家进行猜雷,用'*'来表现它的神秘感。
通过initboard函数,利用两个for循环嵌套来用传入的字符填满整个数组,代码如下:
void InitBoard(char board[ROWS][COLS], int rows, int cols,char m) {
int i = 0;
for (i = 0; i <rows; i++) {
int j = 0;
for (j = 0; j <cols; j++) {
board[i][j] = m;
}
}
}
1.2.1棋盘打印
定义一个displayboard函数来实现棋盘打印功能,代码如下:
void displayboard(char board[ROWS][COLS], int row, int col) {
int i=0;
printf("----------扫雷游戏------------\n");
for (i = 1; i <=row; i++) {
int j = 0;
for (j = 1; j <= col; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("----------扫雷游戏------------\n");
}
棋盘展示:
观察棋盘发现只有字符,当要排雷时,坐标还得一行一列一个个数,所以对上面代码进行修正,对棋盘的上方和左侧分别添加行列数,方便找到排查点坐标,进行排雷,修正后代码如下:
void displayboard(char board[ROWS][COLS], int row, int col) {
int i=0;
printf("----------扫雷游戏------------\n");
for (i = 0; i <=col; i++) {
printf("%d ", i);
}
printf("\n");
for (i = 1; i <=row; i++) {
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("----------扫雷游戏------------\n");
}
修正后棋盘:
1.3雷的布置
雷的布置应该是每次不一样的,就是说是随机的,那么如何在棋盘中随机布雷呢?
在上一步打印棋盘时,为了方便排查雷,给棋盘外围添加了行,列数;那么可以通过坐标来定位某个位置来进行布雷,而c语言中为我们提供了一个rand的函数用来产生随机数,只需引入〈stdlib.h〉的头文件,下面先进行试验看看rand产生的是否是随机数,代码如下:
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
printf("%d\n", rand());
return 0;
}
第一次输出结果:
第二次输出结果:
观察结果发现rand函数单独一次输出结果不同,但两次分别输出的结果相同,知晓rand生产的并不是真正意义上的“随机数”,而是“伪随机数”,是根据一个数值按照某个公式推算出来的,这个数值我们称之为“种子”。种子在每次启动计算机时是随机的,但是一旦计算机启动以后它就不再变化了,一旦启动计算机以后,种子就是定值了,所以根据公式推算出来的结果(也就是生成的随机数)就是固定的。那如何才能产生真正的“随机数”呢?同样在〈stdlib.h〉头文件中有个srand函数,使用时还需要引如〈time.h〉,用法如下:
#include<stdlib.h>
#include<time.h>
srand((unsigned int)time(NULL));//刷新“种子”
int x = rand() % row + 1;//范围0-row
int y = rand() % col + 1;//范围0-col
在文章的末尾会讲解srand产生的是真正的“随机数”的原因。
知道“随机数”的产生,那如何落实到具体位置呢?在这里定义雷的横坐标为x,纵坐标为y,通过rand(rand生成范围是0—(num-1))所以要在棋盘范围布雷,则需要+1;将生成随机数赋予x,y,这样就产生了雷坐标,在将雷坐标给mine棋盘,讲该位置的'0'—>'1',一个雷就此诞生啦!
如果需要布置多个雷,定义一个set_mine函数,同时定义个全局变量set_count来设置雷数目,可利用while/for循环,每循环一次,count--,当count==0,条件为假跳出循环同时意味着布雷完毕。代码实现如下:
#define set_count 10//设置雷数
void set_mine(char board[ROWS][COLS], int row, int col) {
int count = set_count;
while (count) {
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0') {
board[x][y] = '1';
count--;
}
}
}
1.4雷的排查
再次玩扫雷,发现当点击非雷区时会出现大片空白,以及某些方块是会有数字,这些数字代表着在这方块周围一圈内雷的个数,这为我们“扫雷”的实现提供关键的一块“拼图”。所以当我们排查某个坐标时,如果是安全区,则统计在这排查坐标周围一圈的雷数,并把改数在show棋盘展示。当排查坐标[x][y]为安全区时,对排查坐标周围一圈雷数统计,即[x - 1][y - 1] ,[x][y-1] ,[x+1][y + 1] ,[x+1][y] ,[x+1][y + 1] ,[x][y+1] ,[x-1][y+1] ,[x-1][y] 这八个区域统计雷数。那么当棋盘为9*9时,统计边缘位置周围的雷数会出现错误,输出?或' ',这是因为棋盘周边没开辟空间,统计雷数时无法识别,会报乱码。所以在开头定义棋盘大小时讲横纵都拓宽了两行或两列,这是为了方便统计雷数 。
那么棋盘是个二维字符型数字,该如何统计周围的雷数(整型)呢?
1.4.1字符—整型之间的切换
字符型变量在计算机中存储用的是Ascll码(二进制编码),而'0'的Ascll码是48;当其他字符型数字减去'0',相当于对应的Ascll码-48,等于该数。举例:'1'(Ascll码为48)-'0'=1。
既然字符可以转换数字,那么数字也应该可以转换字符,只需要反向操作,+'0'即可。
而完成统计雷数可以写成一个函数命名为getmine,代码如下:
int GetMinecount(char mine[ROWS][COLS], int x, int y) {
return (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] + mine[x-1][y] - 8 * '0');
}
1.4.2排雷次数判定
排雷需要一定的次数限定,毕竟棋盘大小已经规定,不可能无限制的排下去。那么排雷次数<棋盘大小-雷数;同时当排雷次数==棋盘大小-雷数时,是胜利条件哎(扫雷成功)。
1.4.3排雷函数实现
通过嵌套调用getmine函数来获取周围雷数,打印到show棋盘展示,让玩家刚好的排雷,结合上面分析,就能够构建一个排雷函数Findmine,代码如下:
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) {
int x=0;
int y = 0;
int win = 0;
while (win < row * col - set_count) {
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) {
if (mine[x][y] == '1') {
printf("爆炸咯!\n");
displayboard(mine, ROW, COL);
break;
}
else {
int count = GetMinecount(mine, x, y);
show[x][y] = count +'0';
displayboard(show, ROW, COL);
system("cls");
displayboard(show, ROW, COL);
win++;
}
}
else {
printf("非法输入,重新输入\n");
}
}
if (win == row * col - set_count) {
printf("恭喜你,扫雷成功!!!!\n");
displayboard(mine, ROW, COL);
}
}
二、操作界面的实现
2.1界面设置
设置一个menu函数,简述输入不同的数字进行的下一步操作,代码如下:
void menu() {
printf("**************************\n");
printf("******** 1.play ********\n");
printf("******** 0.exit ********\n");
printf("**************************\n");
}
2.2选择操作
利用switch语句,通过输入不同的数字,进行不同的操作,同时将扫雷操作各个函数放置到game函数中,代码如下:
int input=0;
scanf("%d", &input);
switch (input) {
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入:\n");
system("cls");
break;
}
} while (input);
2.3添加清屏功能
发现当进入游戏模式后,在运行窗口每次排雷都会留下记录,看起来不美观,在这里用到〈window.h〉的头文件,其中的system(“cls”)能够实现清屏功能,添加带适当位置能够让游戏体验加倍(ಡωಡ) 。
下面是整个程序全部代码:
game.h:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include<windows.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define set_count 10//设置雷数
//初始化棋盘
void InitBoard(char board[ROWS][COLS], int rows, int cols, char m);
//打印棋盘
void displayboard(char board[ROWS][COLS], int row, int col);
//布置雷
void set_mine(char board[ROWS][COLS], int row, int col);
//排差雷
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS],int row,int col);
game.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols,char m) {
int i = 0;
for (i = 0; i <rows; i++) {
int j = 0;
for (j = 0; j <cols; j++) {
board[i][j] = m;
}
}
}
void displayboard(char board[ROWS][COLS], int row, int col) {
int i=0;
printf("----------扫雷游戏------------\n");
for (i = 0; i <=col; i++) {
printf("%d ", i);
}
printf("\n");
for (i = 1; i <=row; i++) {
printf("%d ", i);
int j = 0;
for (j = 1; j <= col; j++) {
printf("%c ", board[i][j]);
}
printf("\n");
}
printf("----------扫雷游戏------------\n");
}
void set_mine(char board[ROWS][COLS], int row, int col) {
int count = set_count;
while (count) {
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0') {
board[x][y] = '1';
count--;
}
}
}
//找周围雷数(3*3)不含找点
int GetMinecount(char mine[ROWS][COLS], int x, int y) {
return (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] + mine[x-1][y] - 8 * '0');
}
void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col) {
int x=0;
int y = 0;
int win = 0;
while (win < row * col - set_count) {
printf("请输入要排查的坐标:>");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col) {
if (mine[x][y] == '1') {
printf("爆炸咯!\n");
displayboard(mine, ROW, COL);
break;
}
else {
int count = GetMinecount(mine, x, y);
show[x][y] = count +'0';
displayboard(show, ROW, COL);
system("cls");
displayboard(show, ROW, COL);
win++;
}
}
else {
printf("非法输入,重新输入\n");
}
}
if (win == row * col - set_count) {
printf("恭喜你,扫雷成功!!!!\n");
displayboard(mine, ROW, COL);
}
}
test.c:
#define _CRT_SECURE_NO_WARNINGS 1
#include"game.h"
void menu() {
printf("**************************\n");
printf("******** 1.play ********\n");
printf("******** 0.exit ********\n");
printf("**************************\n");
}
void game() {
char mine[ROWS][COLS];
char show[ROWS][COLS];
InitBoard(mine, ROWS,COLS,'0');
InitBoard(show, ROWS, COLS,'*');
set_mine(mine, ROW, COL);
displayboard(show, ROW, COL);
FindMine(mine,show, ROW, COL);
}
int main() {
int input = 0;
srand((unsigned int)time(NULL));
do {
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input) {
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入错误,请重新输入:\n");
system("cls");
break;
}
} while (input);
return 0;
}
PS:使用 <time.h> 头文件中的 time() 函数即可得到当前的时间,利用时间作为参数,每次播种的时间不同,那么生成的种子就不同,最终的随机数也就不同。
若有不足之处,望各位大佬多多指教。