8月23日晚10点
本次做的实验是一个基于C的扫雷程序,我大致分为5大模块的实现。
先上代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <Windows.h>
#include <Mmsystem.h>
#include <graphics.h>//图形界面库
#pragma comment(lib,"winmm.lib")
#define ROW 10//行的个数
#define COL 10//列的个数
#define NUM 10//雷的个数
//定义了一个10行10列的二位数字哦
//注意,定义的宏不能最后加;哦
#define SIZE 50
int count = 0; //点开的个数
int map[ROW+2][COL+2];//全局变量
IMAGE img[12]; //图片数组,用来保存12张图片
//初始化函数。
void GameInit() {
//随机数字的种子
srand((unsigned int)time(NULL));
//随时间的变化每次产生不同的随机数
//让所有的二位数组的值变成0
for (int i = 0; i < (ROW+2); i++) {
for (int j = 0; j < (COL+2); j++) {
map[i][j] = 0;
}
}
//如何表示雷,-1表示雷,然后定义了NUM个雷
int n = 0;
while (n < NUM)
{
//随机的行和列
//根据雷的分布,填充内部的雷的数据
int r = rand() % ROW+1;
int c = rand() % COL+1;//产生1-COL+1之间的随机数,
//包含1-10的随机数
//布下雷
if (map[r][c] == 0) {
map[r][c] = -1;
n++;
}
}
//计算每一个方格周围的雷数
for (int i = 1; i <= ROW; i++) {
for (int j = 1; j <= COL; j++) {
if (map[i][j] != -1) {
for (int a = i-1; a <= i+1; a++){
for (int b = j-1; b<=j + 1;b++) {
if (map[a][b] == -1)
{
map[i][j]++;
}
}
}
}
}
}
//简单的加密,也就是为了保证一开始出来的时候是空包的图片
for (int i = 1; i <= ROW; i++) {
for (int j = 1; j <= COL; j++) {
map[i][j] += 20;
}
}
}
//绘制函数,打印二维数组中的元素
void GameDraw() {
//二维数组中打印
for (int i = 1; i <= ROW; i++) {
for (int j = 1; j <= COL; j++) {
printf("%2d ", map[i][j]);
/*
元素 图片
-1 img[9]
0-8 img[0]-img[8]
19-28 img[10]
30以上 img[11]
*/
if (map[i][j] == -1) {
putimage((i - 1) * SIZE, (j - 1) * SIZE, &img[9]);//雷
}
else if (map[i][j] >= 0 && map[i][j] <= 8) {
putimage((i - 1) * SIZE, (j - 1) * SIZE, &img[map[i][j]]);//数字
}
else if(map[i][j]>=19 && map[i][j]<=28)
{
putimage((i - 1) * SIZE, (j - 1) * SIZE, &img[11]);//空白图片
}
else if (map[i][j] > 30) {
putimage((i - 1) * SIZE, (j - 1) * SIZE, &img[10]);//国旗图
}
}
printf("\n");
}
}
void OpenZero(int r, int c) {
//先打开0
map[r][c] -= 20;
count++;
for (int m = r - 1; m <= r + 1; m++) {
for (int n = c - 1; n <= c + 1; n++) {
if (m >= 1 && m <= ROW && n >= 1 && n <= COL) {
if (map[m][n] >= 19 && map[m][n] <= 28) {
if (map[m][n] != 20) {
map[m][n] -= 20;
count++;
}
else
{
OpenZero(m, n);
}
}
}
}
}
}
int PlayGame() {
//定义一个鼠标消息
MOUSEMSG msg = { 0 };
int r, c;
while (1) {
msg = GetMouseMsg();
switch (msg.uMsg)
{
case WM_LBUTTONDOWN://翻开 空白图 19-28
r = msg.x / SIZE + 1;
c = msg.y / SIZE + 1;
if (map[r][c] >= 19 && map[r][c] <= 28) {
if (map[r][c] == 20) {//点到的是0
OpenZero(r,c);
}
else
{
map[r][c] -= 20; //-1-8
count++;
}
}
return map[r][c];
break;
case WM_RBUTTONDOWN:
r = msg.x / SIZE + 1;
c = msg.y / SIZE + 1;
if (map[r][c] >= 19 && map[r][c] <= 28) {
map[r][c] += 50;
}
else if (map[r][c] >= 30)
{
map[r][c] -= 50;
}
return map[r][c];
break;
}
}
}
int main() {
//打开一个图形窗口
HWND hwnd = initgraph(ROW * SIZE, COL * SIZE);
mciSendString(L"open music.mp3 alias music", 0, 0, 0);
mciSendString(L"play music", 0, 0, 0);
loadimage(&img[0], L"0.jpg", 50, 50);
loadimage(&img[1], L"1.jpg", 50, 50);
loadimage(&img[2], L"2.jpg", 50, 50);
loadimage(&img[3], L"3.jpg", 50, 50);
loadimage(&img[4], L"4.jpg", 50, 50);
loadimage(&img[5], L"5.jpg", 50, 50);
loadimage(&img[6], L"6.jpg", 50, 50);
loadimage(&img[7], L"7.jpg", 50, 50);
loadimage(&img[8], L"8.jpg", 50, 50);
loadimage(&img[9], L"9.jpg", 50, 50);
loadimage(&img[10], L"10.jpg", 50, 50);
loadimage(&img[11], L"11.jpg", 50, 50);
//把0.jpgd的图片以50*50的大小放到img这个数组中
GameInit();
while (1)
{
GameDraw();
if (PlayGame() == -1) {
GameDraw();
MessageBox(hwnd,L"Game Over",L"",MB_OK);
break;
}
if (ROW*COL - NUM == count) {
MessageBox(hwnd, L"You Win", L"", MB_OK);
break;
}
}
return 0;
}
/*
根据雷的分布,填充其他不为雷的数据
1.遍历九宫格、
2.边上的数据遍历的时候 会越界
3.***辅助区 游戏区,这两个区其实就是之前的ROW+2和COL+2
*/
具体的实验步骤是:
1.
首先,先定义一个二位数组,10行,10列,其中ROW和COL都放在宏中。以便后期的更改需要,最主要的还是,因为本次的实验相对比较简单,不需要引用外部份文件,只有一个Main,所以不用担心会有变量重复的问题,避免出错。然后把雷的值设置成-1,基于要实现这种需求,本次学习中,新学习到了<time.h>库和两条语句:
srand((unsigned int)time(NULL)); //根据系统的时间产生随机数的种子,比较常用
int r = rand() % ROW+1;//产生[1-row+1)内的数字,注意是包含1,但是不包含row+1的
写入雷的时候也得判定是否有重复输入的情况,加一条if就轻松解决啦。
2.既然基本的雷区已经完成了,那接下就是扫雷中比较经典的,点开后的小数字的实现,
以上的表格里面,首先,中间的坐标(i,j)得是雷哦。那也就是通过for循环来,遍历雷周围的数字来增加1,然后最重要的,也就是边界问题,比方说(0,0),(10,10)的坐标周围并不是8个格子,所以为了解决这一个问题,就需要把原来的表格每行每列往外扩充2格。这样就相当于设置了一个游戏区和一个空闲区。
这样就可以让原来边界的(0,0)----(ROW-1,COL-1)变成(1,1)----(ROW,COL),相当于最外面一圈是全0,这样就可以不考虑坐标越界的问题了。那这样的话也就算出没个方格周围的雷数了。
3.
由于后期没个方格都得是图片,所以需要对鼠标操作,以及相应值得变化得定义。我先声明一下,
0-8的数字刚刚号就是 0-8,雷是-1,而初始化的图片就是19-28,之所以是这个范围是为了后面点击操作后-20然后变成[-1,8]显示出来。旗子的值是>30表示,也就是空包格子+30,必会成>30的数。
结合函数 WM_LBOTTONDOWN和WM_RBOTTONDOWN,进行每一次点击的判定,然后转数字,配合后期的图片加载需要。
4图形化
以上已经对数据有了初步的定义和处理,接下里就是很重要的环节,图形化的处理,这里就得应用一个新的库了<graphics.h>,(这个库需要网上下载哦)
HWND hwnd = initgraph(ROW * SIZE, COL * SIZE);//打开一个图形窗口
loadimage(&img[n], L"n.jpg", 50, 50);//加载图片,其中的n就是图片的编号,接下来就是图片的分配,一开始就对图片背后的值进行了预处理,那只需要对值进行图片的定义就行。这里用的是while(true)就是一直判定。
5.
玩过扫雷的都知道,在点开一个0后会有很多的空白显示出来,那这实际上就是一种递归的思想,定义了一个 OpenZero(int r, int c)
基于这样的一个递归的思想,也就是判定一开始点开的是雷还是数字还是0,三种情况
如果是0那就进行递归处理,接着判断0周围的8个也就是判断(i-1,j-1)-(i+1,j+1)范围内的每一个再次递归。
最后我还加了一个音乐,用到以下的库,和在项目属性中添加winmm.lib操作
#include <Windows.h>
#include <Mmsystem.h>
遇到的问题:
1.一开始添加了休闲区后需要对下标进行修改,当时没改,所以出来的就是一排0或者1列0.
2.播放的时候那几个库有点玄学,一开始怎么弄都不行,后面把#include <Windows.h>放到
#include <Mmsystem.h>上面就好了。够玄学的,holy shit.