目录
引入
相信你一定游玩过扫雷,在最短时间内标记并找到所有雷(当然有一定的运气成分)的成就感让我们根本停不下来。但是你在游玩娱乐的时候,是否想过可以将娱乐游戏与你已经学习的C语言知识结合起来?
其实根据你已有的C语言知识,已经可以实现扫雷的逻辑,进而还原扫雷的游玩过程。
本篇分享的内容,就是C语言实现扫雷的逻辑,进而还原扫雷的基本游戏过程。
但是,我们要分文件来实现扫雷。
为什么要分文件?
一,实现一个比较复杂的项目,想一想,如果将所有的源码写在一起,就会非常杂乱,无论是调试,还是后期维护 或 更新加入新的游戏玩法 规则 ,由于代码的可读性,逻辑性堪忧,后面的程序员硬要改进这段代码,一定十分令人痛苦。
二,我们将来成为一名优秀的程序员后,我们会写很多功能强大的程序,可能会有一些公司想要购买你写的程序的功能,这时如果我们将具体函数的实现导入静态库,这样我们能就能在不泄露源码的情况下让公司使用你写出的强大功能,更利于保障个人合法权利。
总结来说,分文件写代码有利于:
1.提高代码的可读性,逻辑性;
2.便于写代码时的调试,以及后期的维护;
3.便于 交易 操作;
于是,在实现扫雷的逻辑时,我们选择分文件操作。
程序实现选择分文件操作
一般企业在实现项目的时候,往往项目的代码比较多,所以不会将所有的代码都放在一个文件中;于是,我们往往会根据程序的功能,将代码拆分在多个文件中。
本篇,将要实现的扫雷,我们的打算:
1.将扫雷的代码分为三个文件:test.c game.h game.c
2.test.c——用于实现游戏的整体框架,测试游戏的逻辑;
3.game.c——用于具体实现各个部分子程序的功能;
4.game.h——自己创建的头文件,用于集中包含头文件,常量的定义,函数的声明;
观察扫雷的逻辑(玩游戏,照葫芦画瓢)
在扫雷中, 我们需要找出所有的地雷,在清理完所有的非雷区域后,游戏结束。
我们可以使用鼠标左键:
在判断出不是雷的方块上按下左键,可以打开该方块并在方块上显示数字,该数字表示其周围3×3区域中的地雷数(一般为8个格子,对于边块为5个格子,对于角块为3个格子,所以扫雷中最大的数字为8);如果方块上为空(相当于0),则可以递归地打开与空相邻的方块。
但是,但是!我们发现在靠近边缘的时候我们是需要判断的,这可能会有不少麻烦!
在本篇中,我们仅实现扫雷的左键功能,并且一次只能打开一个格子。
(对于一次左键可以打开一片,鼠标右键标记地雷,鼠标左右双击打开认为安全的格子等功能水墨会在以后迭代扫雷后分享给大家)
通过观察,我们发现:
1.扫雷可以通过菜单实现继续游玩还是退出游戏;
2.扫雷的棋盘大小可变;
3.在排查雷时:
(1)如果此位置是雷,就被炸死,游戏结束;
(2)如果次位置不是雷,就显示周围有几个雷;
(3)把除了10个雷的其他格子都找出来,排雷成功,游戏结束。
实现思路:(先考虑实现思路与方法,分文件后续考虑)
我们根据游戏个游玩顺序,展开实现思路:
游戏界面设计
我们在游玩扫雷时发现,网页的扫雷没有游戏菜单,点开直接就开始扫雷了;
但是我们在实现时,可以自己给扫雷设计一个菜单:
我们通过函数menu()来实现:
void menu(void)
{
printf("********************************************\n");
printf("*************** 扫雷 2.0 ****************\n");
printf("*************** 1.Play ****************\n");
printf("*************** 2.Help ****************\n");
printf("*************** 0.Exit ****************\n");
printf("********************************************\n");
}
函数menu()其实只是一个包装,调用它可以让我们的程序看起来更有条理性。
具体来说:
函数不需要返回值,也不需要参数。
游戏框架实现
我们想要先进行游戏,再考虑是否要选择继续游戏,于是我们采用do while{ } 循环:
int main()
{
int input = 0;
do
{
menu();
scanf("%d", &input);//根据选择的input的值来使游戏进程继续
switch (input)
{
case 1:
{
game();//如果选择1,则进入游戏函数game(),
break;
}
case 2:
{
printf("输入要排雷的坐标,根据信息找出所有雷\n");//如果选择2,则break跳出switch
break;
}
case 0:
{
printf("已退出游戏\n");//如果选择0,则break跳出switch
break;
}
default://输入错误,不是1或2或0
{
printf("Error!\n");
}
}
} while (input);//判断input的值,如果非0,继续循环,如果为0,跳出循环,进程结束
return 0;
}
游戏函数的实现(game()函数)
我们发现,扫雷是一个个的格子的,一个格子下方可能有地雷。
于是,我们可以创建一个二维数组来模拟格子(棋盘),可以用字符 ‘0’来 初始化 棋盘 ,用 字符 ‘1’来表示这个位置有雷,用字符 ‘n’ 来表示此位置周围 8 个格子有多少雷。
也就是说,我们需要访问输入坐标的周围 8 个格子。
但是我们还要面对上文提到的麻烦——数组越界怎么办?(如果访问到数组外怎么办?)
是要先判断,然后再分开写 如果选的坐标是数组边缘 时的代码吗?
其实 我们可以创建一个比实际扫雷区域大一圈的数组,并且把边缘区域初始化为字符 ’ 0 ‘,这样在获取雷的信息的时候就不会被边缘多的一圈影响,也不再用判断是否会访问到数组外部。
这样,数组越界的问题就解决啦。
我们也需要随机放置雷,这样就需要一个可以产生随机数的函数,那就是rand()。
随机数产生
但是rand()产生的随机数实际上是 “ 伪随机数 ”,这些产生的数字并不是完全随机的——通过调试我们发现:
第一次运行:
第二次运行:
rand()在程序运行时每次产生的随机数都是同一个,也就是说,随机数是可预测的。
这时,我们可以调用srand()函数来重置rand()的原点,这样就不会再生成可预测的随机数了。
但是srand()函数又需要一个随机数,可是,我们需要随机数,才使用rand(),这样不就死循环了吗?
这时,还有一个函数time(),它的返回值是个时间戳:
时间戳是用于标识某一时刻的一种数字形式,通常表示自1970年1月1日00:00:00以来经过的秒数或毫秒数。
又由于srand()函数需要一个无符号整形,于是我们可以强制类型转化,写出来全过程,就是:
srand((unsigned int)time(NULL));
将其放在主函数的入口,就成功设置了随机原点。
到这里,扫雷的基本逻辑就想好了,但是当你在具体实现时,会发现地雷信息的字符(当n = 1的时候)很容易与表示字符的 ’ 1 ‘ 混淆,怎么解决呢?
混淆字符含义解决
我们可以创建两个二维数组:
一个棋盘用于存放布置好的雷的信息(Mineboard),另一个棋盘(Showboard)用于存放排查出的雷的信息。
这样就不会相互干扰了:
把雷随机布置到Mineboard数组,在Mineboard数组中排查雷,在Showboard数组中存放排查出的雷的信息,并且每次排雷后只展示Show数组,这样难题就迎刃而解了!
混淆问题也解决了!
函数实现
接下来根据游戏逻辑,我们需要创建多个子程序:(名称与功能一一对应)
初始化数组————initBoard
打印(展示)数组————printboard
布置雷————setMine
排查雷————Pai_Mine
接下来我们来一一实现:
initBoard():
1.不需要返回值
2.传参为数组,行,列,set(初始化的设置值)
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;
}
}
}
printboard():
1.不需要返回值
2.传参:数组,行,列
//展示棋盘函数
void printboard(char Board[ROWS][COLS], int row, int col)
{
printf("****************扫雷**************\n");
for (int i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
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");
}
setMine():
1.不需要返回值
2.传参:数组,行,列
3.要判断输入坐标的合法性
//布置雷
void setMine(char Board[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int count = C;
while (count)
{
x = rand() % (ROW + 1);
y = rand() % (ROW + 1);
if (Board[x][y] == '0' && x > 0 && x < 10 && y > 0 && y < 10)
{
Board[x][y] = '1';
count--;
}
}
}
Pai_Mine():
1.无返回值
2.检查坐标的合法性
3.需要一个新的函数get_mine()来收集周围雷的信息
//游戏逻辑实现
void Pai_Mine(char mineBoard[ROWS][COLS], char showBoard[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
int win = 0;
while (row * col - C > win)
{
printf("请输入要排查的坐标 >");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mineBoard[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
printf("雷的分布:");
printboard(mineBoard, ROW, COL);
printf("3s后游戏结束\n");
Sleep(3000);
break;
}
else
{
int count = get_mine(mineBoard,x,y);//得到周围8个框的地雷数(返回字符)
showBoard[x][y] = count + '0';
printboard(showBoard, ROW, COL);
win++;
}
}
else
{
printf("Error location,reinput!\n");
}
}
if (win == row * col - C)
{
printf("恭喜你,排雷成功!\n");
printboard(showBoard, ROW, COL);
printf("3s后游戏结束\n");
Sleep(3000);
}
}
get_mine():
1.返回值为整形
2.mineBoard数组元素是字符,是ASCII值,所以返回雷的个数需要减去 8 个 ’0‘的 ASCII值
int get_mine(char mineBoard[ROWS][COLS],int x,int y)
{
return mineBoard[x - 1][y - 1] + mineBoard[x][y - 1] + mineBoard[x + 1][y - 1]
+ mineBoard[x - 1][y] + mineBoard[x + 1][y]
+ mineBoard[x - 1][y + 1] + mineBoard[x][y + 1] + mineBoard[x + 1][y + 1] - 8 * '0';
}
至此,我们的游戏逻辑基本上就实现了,按照文件操作的需求我们的程序整体如下:
扫雷实现:(分文件)
game.c
#include"game.h"
//初始化函数
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 printboard(char Board[ROWS][COLS], int row, int col)
{
printf("****************扫雷**************\n");
for (int i = 0; i <= row; i++)
{
printf("%d ", i);
}
printf("\n");
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 x = 0;
int y = 0;
int count = C;
while (count)
{
x = rand() % (ROW + 1);
y = rand() % (ROW + 1);
if (Board[x][y] == '0' && x > 0 && x < 10 && y > 0 && y < 10)
{
Board[x][y] = '1';
count--;
}
}
}
int get_mine(char mineBoard[ROWS][COLS],int x,int y)
{
return mineBoard[x - 1][y - 1] + mineBoard[x][y - 1] + mineBoard[x + 1][y - 1]
+ mineBoard[x - 1][y] + mineBoard[x + 1][y]
+ mineBoard[x - 1][y + 1] + mineBoard[x][y + 1] + mineBoard[x + 1][y + 1] - 8 * '0';
}
//游戏逻辑实现
void Pai_Mine(char mineBoard[ROWS][COLS], char showBoard[ROWS][COLS], int row, int col)
{
int x = 0, y = 0;
int win = 0;
while (row * col - C > win)
{
printf("请输入要排查的坐标 >");
scanf("%d %d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mineBoard[x][y] == '1')
{
printf("很遗憾,你被炸死了\n");
printf("雷的分布:");
printboard(mineBoard, ROW, COL);
printf("3s后游戏结束\n");
Sleep(3000);
break;
}
else
{
int count = get_mine(mineBoard,x,y);//得到周围8个框的地雷数(返回字符)
showBoard[x][y] = count + '0';
printboard(showBoard, ROW, COL);
win++;
}
}
else
{
printf("Error location,reinput!\n");
}
}
if (win == row * col - C)
{
printf("恭喜你,排雷成功!\n");
printboard(showBoard, ROW, COL);
printf("3s后游戏结束\n");
Sleep(3000);
}
}
game.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<windows.h>
#define ROW 9
#define COL 9
#define ROWS ROW+2
#define COLS COL+2
#define C 10
//初始化棋盘函数
void initBoard(char Board[ROWS][COLS], int rows, int cols, char set);
//展示棋盘函数
void printboard(char Board[ROWS][COLS], int row, int col);
//随机布置雷
void setMine(char Board[ROWS][COLS], int row, int col);
//排查雷
void Pai_Mine(char mineBoard[ROWS][COLS], char showBoard[ROWS][COLS], int row, int col);
test.c
#include"game.h"
void menu(void)
{
printf("********************************************\n");
printf("*************** 扫雷 2.0 ****************\n");
printf("*************** 1.Play ****************\n");
printf("*************** 2.Help ****************\n");
printf("*************** 0.Exit ****************\n");
printf("********************************************\n");
}
void game(void)
{
printf("游戏开始\n");
//展示的棋盘
char Showboard[ROWS][COLS];
//含雷的棋盘
char Mineboard[ROWS][COLS];
//初始化函数
initBoard(Showboard, ROWS, COLS,'*');
initBoard(Mineboard,ROWS,COLS,'0');
//展示(打印)棋盘函数
printboard(Showboard, ROW, COL);
//随机布置雷
setMine(Mineboard,ROW,COL);
//printboard(Mineboard, ROW, COL);//看是否布置雷成功
//排查雷
Pai_Mine(Mineboard,Showboard, ROW, COL);
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));
do
{
menu();
scanf("%d", &input);
switch (input)
{
case 1:
{
game();
break;
}
case 2:
{
printf("输入要排雷的坐标,根据信息找出所有雷\n");
break;
}
case 0:
{
printf("已退出游戏\n");
break;
}
default:
{
printf("Error!\n");
}
}
} while (input);
return 0;
}
这就是扫雷的基本逻辑的实现了,
希望一个插曲小游戏能让你在学习中感受到乐趣。
完
未经作者同意禁止转载