之前做了一直丢在仓库,没时间写成博文,今天正好做作业就顺便写一手博客;
接下来开始:
基本思路
1.首先我需要一块演出区域——地雷盘,地雷盘可以用二维数组组成的矩形区域来体现;
2.其次我需要放置地雷,那么也同样用同等大小的二位数组矩形区域表示,之后使用时间戳随机数来生成地雷位置;
3.还需要有排雷的机制,当选中位置无雷时,则显示选中点周围8格内雷的数量(九宫格);
Step by step
好,接下来实现:
首先创建两个源文件一个头文件,test.c中存放游戏主函数,game.c中存放小功能模块的函数,game.h中存放所有的引用头文件以及函数声明。
把头文件搞进去,#pragma once防止头文件被反复引用造成的冗余,#include <stdio.h>负责输入与打印,#include <time.h>与#include <stdlib.h>负责时间戳和伪随机数生成。
在test.c里把头文件引用,然后浅浅的画一个游戏登陆界面
接下来用dowhile循环加载登陆界面,引导玩家输入选项,再用switch来判断玩家输入并反馈结果
我们期望玩家输入1进入游戏,输入0退出游戏,default下输入其他任意数字都判定为错误值,重新输入,然后浅浅的运行一下,确认整个登陆没有逻辑错误,继续往下走。
当玩家选择1游玩时,我们需要建立一个game()函数,来存放游戏本体相关的东西,让他能玩儿
那么在game()函数里首先不管三七二十一,我得先初始化点什么,对吧,演出需要有舞台,ok,我们先来搞一个矩形的棋盘雷区,棋盘用二维数组初始化,在仅有的条件下我希望视觉表现能够更优秀一些,所以我们用char类型创建数组,这样的话就可以使用*#等符号来表示游戏元素;
这里我们创建了一个9*9的棋盘,这很好,但是这里有几个问题,一是,如果这里我写死了棋盘大小,那我后续想要改变棋盘,增加难度,那怎么搞?我难道还找到所有的char mine[9][9]改成char mine[x][y]吗?二是,我们之前说过,当点击一个小格子排雷时,我期望显示出它周围8个坐标的雷数量,但当雷格子处在边缘时,它周围的坐标是不是存在越界问题,谁知道它访问的那个越界坐标地址里面存的啥数据?
好,我们这样来解决:
我们在头文件里面定义几个值,ROW,COL(column),ROWS,COLS
分别表示你想定义的棋盘行、列大小和向外扩了一圈的实际棋盘大小(防止越界),当然雷不会刷在这些防越界的区域里,它仅仅只作为预防
很好,但现在还有一个问题,当我放很多雷在棋盘上时,玩家不就一眼能看到了吗?三下五除二选中几个坐标不是夸夸排,那很不好,所以我们需要把玩家所看到的演出棋盘和实际存放雷数据的棋盘分开,玩家只看演出棋盘即可
好,两个存放数据的数组创建完毕了,空间准备好了,接下来我们需要两个用来初始化的函数,把两个空间里填满内容,用0来表示所有没放雷的安全格子(程序员用的),用*来表示玩家所看到的棋盘上的小格子
InitBoard()函数共有4个参数,分别是1.初始化棋盘存放数据的数组;2.所要初始化的行数;3.所要初始化的列数;4.所要初始化成什么内容
好,来搞一搞这个函数
这种小功能的具体函数实现统一放在game.c里,方便管理,声明则放在game.h里
这个函数就比较简单了,循环套个循环,内层负责行,外层负责列,都初始化成对应的set字符;
ok,到这里,我们基本上搞定了棋盘的数据存储了,那我想打印出来验证一下,怎么搞?
我们整一个DisplayBoard()函数来负责棋盘的显示,DisplayBoard()函数共有3个参数,分别是1.要修改棋盘的数组;2.所要显示的行数;3.所要显示的列数
注意这里是显示的行列数,是ROW和COL,而不是ROWS和COLS,向外延展的一圈是不给玩家看的,只有我们自己知道
头文件声明搞上,别忘了,不声明报错的
好,接下来实现这个负责打印显示的函数
简单的两层循环,注意条件是row和col,起点是1,数组下标是从0开始,是我们外围延展的那一圈,但你不能要求玩家去做这样反直觉的操作,玩家所看到的棋盘和坐标都应该是从1开始
整完看看效果
很好,两个9*9的显示棋盘,但是有些粗糙,我们加个title,再给棋盘整个行和列,这样方便玩家输入时能有个参考坐标
整好了,看看效果
是不是初具雏形了,接下来我们还缺少一个关键功能——布雷,我需要往棋盘里面丢雷了
我们搞一个SetMine()函数来负责布雷,SetMine()函数共有3个参数,分别是1.要修改棋盘的数组;2.所要布雷的行数限制;3.所要布雷的列数限制,这里注意,是在ROW,和COL的范围内布雷
game.h的头文件声明搞上,别忘了,不声明报错的
此时还有一个问题,我当期望我布雷的数量是灵活可修改的,所以和行列一样,在game.h我们定义一个地雷数量MINE_COUNT
好,接下来来写这个函数,我们期望地雷能够随机的分布在棋盘上,那么我们需要引入一个rand()库函数来帮助我们实现随机生成位置,rand()函数本身是伪随机,通常需要配合 srand()函数来设置随机数生成的种子
srand()函数全局只用调一次就好了,所以我们把它放在主函数里
然后完成SetMine()
int x = rand() % row + 1; int y = rand() % col + 1;
row和col都是9,当随机数取模9时剩下的是不是0~8?再+个1,是不是刚好是1~9,对应了棋盘的下标?完美!if (board[x][y] == '0')检测此地是否已经布过雷了,没布雷就把‘0’标识符变为‘1’表示雷,布过就再进入循环找其他位置。
搞定,看看效果
布上了,接下来我们搞一搞排雷
老规矩,整个FindMine()函数,FindMine()函数共有4个参数,分别是1.存布雷数据的棋盘;2.演出棋盘;3.所要操作的行数范围;4.所要操作的列数范围
和之前一样,记得game.h的头文件声明搞上
定一个胜利条件,限制好玩家输入范围
统计玩家所选择区域周围8个格子的雷数,之前我们用‘1’来表示雷,在这里就起到了作用,直接返回统计的所有的坐标相加再*‘0’就行了,因为1的ASIIC码值就是1,有几个雷就是几,*‘0’转换为char类型,显示在棋盘上
至此基本完成,然后记得把展示布雷的棋盘注释掉,就是一个发布版本了
写完之后记得多测试几次,在game.h中可以减少雷的数量、棋盘尺寸来测试功能
原码
test.c :
#include "game.h";
void menu()
{
printf("***************************\n");
printf("******** 1. PLAY ********\n");
printf("******** 0. EXIT ********\n");
printf("***************************\n");
}
void game()
{
char mine[ROWS][COLS] = { 0 };//存放布置好的地雷的信息
char show[ROWS][COLS] = { 0 };//存放玩家看到排查出地雷的信息
InitBoard(mine, ROWS, COLS, '0');//mine 初始化没有布置雷时全为'0'
InitBoard(show, ROWS, COLS, '*');//show 初始化没有排查雷时全为'*'
SetMine(mine, ROW, COL);//设置雷
//DisplayBoard(mine, ROW, COL);//展示布雷的棋盘
DisplayBoard(show, 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:
printf("扫雷\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("输入有误,请重新输入\n");
break;
}
} while (input);
return 0;
}
game.c:
#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void InitBoard(char board[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
board[i][j] = set;
}
}
}
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("------扫雷游戏------\n");
for (j = 0; j <= col; j++)
{
printf("%d ", j);
}
printf("\n");
for (i = 1; i <= row; i++)
{
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 = MINE_COUNT;
while (count)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y] == '0')
{
board[x][y] = '1';
count--;
}
}
}
int get_mine_count(char board[ROWS][COLS], int x, int y)
{
return (board[x - 1][y] +
board[x - 1][y - 1] +
board[x][y - 1] +
board[x + 1][y - 1] +
board[x + 1][y] +
board[x + 1][y + 1] +
board[x][y + 1] +
board[x - 1][y + 1] - 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 - MINE_COUNT)//81个格子减去9雷数
{
printf("请输入要排查的坐标,先横后纵\n");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y < col)//限制输入坐标只能在棋盘范围内
{
if (show[x][y] != '*')//原本演出棋盘上都是*,不是*那就肯定查过了
{
printf("该位置已被排查过了\n");
continue;//continue跳过接下来的判断,进入下一次循环
}
if (mine[x][y] == '1')//如果是雷
{
printf("碰到地雷啦!!!BOOM!!!\n");
DisplayBoard(mine, ROW, COL);//玩家输了让他看看埋雷的位置
break;
}
else//如果不是雷
{
win++;
int count = get_mine_count(mine, x, y);//统计xy周围8格的雷数
show[x][y] = count + '0';//count原本是个int,+'0'会转换为字符字数,才能显示在char类型的棋盘上
DisplayBoard(show, ROW, COL);
}
}
else
{
printf("输入坐标有误,请重新输入\n");
}
}
if (win == row * col - MINE_COUNT)
{
printf("恭喜你排雷成功!!!\n");
DisplayBoard(mine, ROW, COL);
}
}
//基础的功能已然实现
//接下来留几个任务
//1.标记功能
//2.一片展开的功能
game.h:
#pragma once
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
#define MINE_COUNT 10
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);