制作说明
本项目是基于学校校内新生挑战赛所制作的一个已完成任务为目标的项目,故而从产品和再现的角度上来看,都存在巨大的不足。主要思路是将操作界面的划分区块,通过给区块赋值来控制图片加载,和完成程序的判定。希望这一次分享能够帮到你
分模块说明
包含的头文件,变量和宏定义
#include<stdio.h> #include<iostream> #include<graphics.h> #include "gameplay.h" #include<time.h> #include<Windows.h> #include "gamestart.h" #define ROW 10 #define COL 10 #define MINE_NUM 99//雷数 #define GRID_W 16//边框大小 int map[ROW][COL];//核心二维数据数组,储存数据值 IMAGE img[16];//图片数组 IMAGE pic[11];//checknum数组 int cnt = 0;//当前点开的非雷的数量 bool t2 = 1;//初见杀标志 bool t5 = 1;//开启t7标志 bool t6 = 0;///开启初见杀标志 int fla=0;//旗数 int tic = 0;//旗-雷数 int num[3];//checknum三取值 bool t4 = 1;//重来标志 bool t7 = 0;//close结束标志
#pragma once #define ROW 10 #define COL 10 #define MINE_NUM 99//雷数 #define GRID_W 16
处女作难免有重复定义,和部分头文件没有使用的地方,多多包涵。愿意剔出就剔除
主函数
//底层数字显示 void show() { for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { printf(" %2d", map[i][j]); } printf("\n"); } } //点击开盒 //计旗雷数 int main() { gamestart(); initgraph(COL*GRID_W, ROW*GRID_W + 50); setfillcolor(YELLOW); rectangle(60, 20, 80, 40); LPCSTR arrt2 = { "re" }; int text1 = 60 + 20 / 2 - textwidth(arrt2) / 2; int text2 = 20 + 20 / 2 - textheight(arrt2) / 2; outtextxy(text1, text2, arrt2);//re方块加载 gameinit(); show(); while (1) { gameDraw(); mousehit(); if (t6) { if (t2 == 1)//初见杀标志 { cnt = 0; fla = 0; gameinit(); show(); } t6 = 0; } if (t4 == 0)//重来标志 { cnt = 0; fla = 0; gameinit(); t4 = 1; } if (t7 == 1)//close结束标志 { break; } checknum(); } return 0; }
包含一个底层数字显示函数,即打印数组到窗口方便调试的函数,以及游戏进程和由标志位控制的初始化函数,下文会详细介绍
游戏启动界面(gamestart)
void gamestart() { ExMessage msg1;//获取鼠标消息 bool t1 = 1; //开始 initgraph(200, 100); cleardevice(); setbkcolor(BLUE); setfillcolor(YELLOW); rectangle(50, 20, 150, 80); LPCSTR arrt1 = { "START" }; int text1 = 50 + 100 / 2 - textwidth(arrt1) / 2; int text2 = 20 + 60 / 2 - textheight(arrt1) / 2; outtextxy(text1, text2, arrt1); while (t1) { while (peekmessage(&msg1)) { switch (msg1.message) { case WM_LBUTTONDOWN: if (msg1.x < 150 && msg1.x>50 && msg1.y > 20 && msg1.y < 80) { closegraph(); t1 = 0; break; } defaultxg:break; } } } }
较为基础的easyx运用,可以在官网上查到,上述函数的详细内容,主要就是在界面上绘制图形,然后运用ExMesssge
创建一个有关鼠标信息的结构体,使用peekmessage()
函数对鼠标所在区域判定达到点击开始的目的。
效果如下图所示:
初始化(gameinit)
//初始化 void gameinit() { for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { map[i][j] = 0; } } //生成随机数种子 srand((unsigned)time(NULL)); for (int i = 0; i < MINE_NUM; ) { //随机生成下标 int r = rand() % ROW; int c = rand() % COL; if (map[r][c] == 0) { map[r][c] = -1; i++; } } //让以-1为中心的九宫格所有数据加一,-1除外 for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { if (map[i][j] == -1) { //遍历九宫格 for (int p = i - 1; p <= i + 1; p++) { for (int q = j - 1; q <= j + 1; q++) { if (p >= 0 && p < ROW&&q >= 0 && q < COL&&map[p][q] != -1) { map[p][q]++; } } } } } } char str[50] = ""; for (int i = 0; i < 9; i++) { sprintf_s(str, "./图片素材/images1/%d.gif", i); loadimage(&img[i], str, GRID_W, GRID_W); } loadimage(&img[9], "./图片素材/images1/mine.gif", GRID_W, GRID_W); loadimage(&img[10], "./图片素材/images1/blank.gif", GRID_W, GRID_W); loadimage(&img[11], "./图片素材/images1/flag.gif", GRID_W, GRID_W); loadimage(&img[12], "./图片素材/images1/blood.gif", GRID_W, GRID_W); //计旗雷数图片加载 for (int i = 0; i < 11; i++) { sprintf_s(str, "./图片素材/images1/d%d.gif", i); loadimage(&pic[i], str, GRID_W, GRID_W / 2); } //数据加密 -1~8全部数据+20 -19~28 for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { map[i][j] += 20; } } }
完成游戏内容初始化,主要分为三个部分:数据加载,图片加载,数据加密.
-
数据加载
通过
srand((unsigned)time(NULL));
和int r = rand() % ROW; int c = rand() % COL;
随机选定雷区,给雷区赋-1.然后通过for遍历九宫格使周围8格增加1,但注意此时要避开-1处。否则雷区消失。这样我们就完成了10*10的数据初始化
-
图片加载
将两种点开和未点开的方块,以及各种数字块图片通过for加载,另外最好使img编号与图片中数字相匹配,另外checknum图片的pic也是如此
-
数据加密
通过对每一个数据+20以与不同图片相对应,此举大有深意,留待后文。
-
于是我们得到了这样的数据表
界面绘制(gamedraw)
//界面绘制 void gameDraw() { for (int i = 0; i < ROW; i++)//i->y { for (int j = 0; j < COL; j++)//j->x { if (map[i][j] >= 0 && map[i][j] <= 10) { putimage(j*GRID_W, i*GRID_W + 50, &img[map[i][j]]); } else if (map[i][j] == -1) { putimage(j*GRID_W, i*GRID_W + 50, &img[9]); } else if (map[i][j] >= 19 && map[i][j] <= 28) { putimage(j*GRID_W, i*GRID_W + 50, &img[10]); } else if (map[i][j] >= 39 && map[i][j] <= 48) { putimage(j*GRID_W, i*GRID_W + 50, &img[11]); } else if (map[i][j] < -1) { putimage(j*GRID_W, i*GRID_W + 50, &img[0]); } } } }
通过for遍历表格,对值进行判定,将对应图片加载
于是得到界面
-
鼠标点击核心程序(mousehit)
//鼠标点击(核心) void mousehit() { MOUSEMSG msg = GetMouseMsg();//获取鼠标消息 //把鼠标坐标转到二维数组中 int row = (msg.y - 50) / GRID_W; int col = msg.x / GRID_W; if (msg.uMsg == WM_LBUTTONDOWN) { if (msg.y < 50) { //从头再来 if (msg.x < 80 && msg.x>60 && msg.y > 20 && msg.y < 40) { t4 = 0; t5 = 1; t2 = 1; putimage(0, 0, &pic[0]); } //结束程序 if (t5 == 0) { if (msg.x < 150 && msg.x>100 && msg.y > 20 && msg.y < 40) { t7 = 1; } } } else { if (map[row][col] >= 19 && map[row][col] <= 28) { map[row][col] -= 20; cnt++; openNULL(row, col); if (t2) { if (map[row][col] != -1) { t2 = 0; } else { map[row][col] += 20; } } show(); if (t2 == 0) { jude(row, col); } } } t6 = 1; } else if (msg.uMsg == WM_RBUTTONDOWN) { if (map[row][col] >= 19 && map[row][col] <= 28) { map[row][col] += 20; if (t2 == 0) fla++; show(); } else if (map[row][col] >= 39 && map[row][col] <= 48) { map[row][col] -= 20; if (t2 == 0) fla--; } } }
通过GetMouseMsg
来获取鼠标信息,其中在鼠标左键和右键点击时进行判定,通过对x,y的值来确定所属区域,左键点击使区域解密,右键点击使区域增加20,到达插旗状态,同时封锁左键对区域的效果(通过if实现)。
-
初见杀模块
包含在其中,t6和t2为两个核心标志位,
if (t2) { if (map[row][col] != -1) { t2 = 0; } else { map[row][col] += 20; } } show(); if (t2 == 0) { jude(row, col); }
这一部分即初见杀判断,初始为1的t2在进行第一次点击判断后,未初见杀置0,若初见杀置1,主函数中有这样一段代码
if (t6) { if (t2 == 1)//初见杀标志 { cnt = 0; fla = 0; gameinit(); show(); } t6 = 0; }
将一切初始化函数不能初始化的变量复原后,再初始化,从而使程序近乎重新开始,表现上为左击一次无法打开,不会太影响游戏体验。而t6的作用是在鼠标未在方格区点击时,关闭初始化判断,因为t2初为1,以避免不断初始化。
-
replay模块
initgraph(COL*GRID_W, ROW*GRID_W + 50); setfillcolor(YELLOW); rectangle(60, 20, 80, 40); LPCSTR arrt2 = { "re" }; int text1 = 60 + 20 / 2 - textwidth(arrt2) / 2; int text2 = 20 + 20 / 2 - textheight(arrt2) / 2; outtextxy(text1, text2, arrt2);//re方块加载 gameinit(); show();
此为界面绘制,在mousehit中进行执行
if (msg.y < 50) { //从头再来 if (msg.x < 80 && msg.x>60 && msg.y > 20 && msg.y < 40) { t4 = 0; t5 = 1; t2 = 1; putimage(0, 0, &pic[0]); } //结束程序 if (t5 == 0) { if (msg.x < 150 && msg.x>100 && msg.y > 20 && msg.y < 40) { t7 = 1; } }
将诸多标志位复原后,开启另一个初始化标志位,完成replay
递归模块(OPENNULL)
//利用递归炸开方格 void openNULL(int row, int col) { if (map[row][col] == 0) { for (int i = row - 1; i <= row + 1; i++) { for (int j = col - 1; j <= col + 1; j++) { if (i >= 0 && i < ROW&&j >= 0 && j < COL && map[i][j] >19 && map[i][j] < 39) { map[i][j] -= 20; cnt++; openNULL(i, j);//递归 } } } } }
当点击的区块附近8格没有雷时,进入此模块,对九宫格进行遍历,如果不是雷再打开解密,同时在openNULL中套openNULL,如果打开该格也满足条件,以他为核心进行新的遍历,完成后继续执行上一个遍历的一个区块,当然这样的嵌套可以有多层,但注意遍历时避开初始格以防重复,故条件冗杂,效果见前图‘’界面绘制‘’时炸开的方块部分
判断模块(jude)
void jude(int row, int col) { if (map[row][col] == -1) { MessageBox(GetHWnd(), "you lose", "再接再厉", MB_OK); for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { if (map[i][j] == 19)//不是雷 { map[i][j] -= 20; } else if (map[i][j] == 39) { map[i][j] -= 40; } } } gameDraw(); Sleep(1000); //重来/结束 for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { map[i][j] -= 100; } } rectangle(100, 20, 150, 40); LPCSTR arrt2 = { "close" }; int text3 = 100 + 50 / 2 - textwidth(arrt2) / 2; int text4 = 20 + 20 / 2 - textheight(arrt2) / 2; outtextxy(text3, text4, arrt2); t5 = 0; //ind = 0; } if (cnt == COL * ROW - MINE_NUM) { gameDraw(); MessageBox(GetHWnd(), "you win", "不错", MB_OK); Sleep(1000); //重来/结束 for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { map[i][j] -= 100; } } rectangle(100, 20, 150, 40); LPCSTR arrt2 = { "close" }; int text3 = 100 + 50 / 2 - textwidth(arrt2) / 2; int text4 = 20 + 20 / 2 - textheight(arrt2) / 2; outtextxy(text3, text4, arrt2); t5 = 1; } }
关于胜负判断的模块,分两种情况当一个区域解密后,判断其中数值,若=-1,进入失败程序,将所有区域-100,成为空白格,,同时,界面绘制出close键,此时t5标志位置0,开启close判断,但此时replay模块依然有效,达到可以选择重来或者结束的目的
效果如下图
这一步是我失败后,选择遍历方格,解密所有雷区,使使用者明败,的效果
而胜利判断条件为除了雷之外的方格全部点开,主要由cnt变量实现,当一个格子解密后,cnt即跟随其加一,当cnt=总格数-雷数后,开始胜利程序,t5标志位也开启,与失败模块差别不大。但其实最好加一个判断:当cnt=总格数-雷数后,遍历格子,如果=19和=39的区域数=雷数再开启胜利模块,可以避免最后两个格子,即使点到雷也判断胜利的情况,但我添加时出了点bug,但应该没问题的
旗-雷数(checknum)
//旗-雷数 void checknum() { tic = MINE_NUM - fla; if (tic < 0) { putimage(0, 0, &pic[10]); tic = 0 - tic; } num[0] = tic % 10; num[1] = (tic / 10) % 10; num[2] = tic / 100; putimage(GRID_W * 3, 0, &pic[num[0]]); putimage(GRID_W * 2, 0, &pic[num[1]]); putimage(GRID_W, 0, &pic[num[2]]); }
这是复原原版扫雷的显示实时可差旗总数-已插旗数的模块,主要由fla变量计数实现,fla的改变在mousehit模块中鼠标右击时跟随插旗撤旗增减。而num数组则完成对tic变量的十位个位取数,将相应的图片put上去,较创新的点是使之能显示负数,但注意在负号位置于replay和重回正数时用图片覆盖
结语
对于这一次结果不能称之为产品,毕竟太不完善,比如计时器我没能通过sleep实现,初见杀也不完美,如果用结构体做应该会更简洁,也不是一个完整的游戏。但希望给你帮助
下附源代码:
hong.h
#pragma once #define ROW 10 #define COL 10 #define MINE_NUM 10//雷数 #define GRID_W 16
gamestart.h
#pragma once #ifndef _GAMESTART_H_ #define _GAMESTART_H_ void gamestart(); #endif
gameplay.h
#pragma once #ifndef _GAMEPLAY_H_ #define _GAMEPLAY_H_ void jude(int row, int col); void openNULL(int row, int col); void gameinit(); void gameDraw(); void mousehit(); void checknum(); #endif
main.cpp
#include<stdio.h> #include<iostream> #include<graphics.h> #include "gameplay.h" #include<time.h> #include<Windows.h> #include "gamestart.h" #define ROW 10 #define COL 10 #define MINE_NUM 99//雷数 #define GRID_W 16 int map[ROW][COL]; IMAGE img[16]; IMAGE pic[11]; int cnt = 0;//当前点开的非雷的数量 bool t2 = 1;//初见杀标志 bool t5 = 1; bool t6 = 0; int fla=0;//旗数 int tic = 0;//旗-雷数 int num[3]; bool t4 = 1;//重来标志 bool t7 = 0;//close结束标志 void gameDraw(); //底层数字显示 void show() { for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { printf(" %2d", map[i][j]); } printf("\n"); } } //点击开盒 //计旗雷数 int main() { gamestart(); initgraph(COL*GRID_W, ROW*GRID_W + 50); setfillcolor(YELLOW); rectangle(60, 20, 80, 40); LPCSTR arrt2 = { "re" }; int text1 = 60 + 20 / 2 - textwidth(arrt2) / 2; int text2 = 20 + 20 / 2 - textheight(arrt2) / 2; outtextxy(text1, text2, arrt2);//re方块加载 gameinit(); show(); while (1) { gameDraw(); mousehit(); if (t6) { if (t2 == 1)//初见杀标志 { cnt = 0; fla = 0; gameinit(); show(); } t6 = 0; } if (t4 == 0)//重来标志 { cnt = 0; fla = 0; gameinit(); t4 = 1; } if (t7 == 1)//close结束标志 { break; } checknum(); } return 0; }
gamestart.cpp
#include<stdio.h> #include<iostream> #include<graphics.h> #include "hong.h" //开始界面 void gamestart() { ExMessage msg1;//获取鼠标消息 bool t1 = 1; //开始 initgraph(200, 100); cleardevice(); setbkcolor(BLUE); setfillcolor(YELLOW); rectangle(50, 20, 150, 80); LPCSTR arrt1 = { "START" }; int text1 = 50 + 100 / 2 - textwidth(arrt1) / 2; int text2 = 20 + 60 / 2 - textheight(arrt1) / 2; outtextxy(text1, text2, arrt1); while (t1) { while (peekmessage(&msg1)) { switch (msg1.message) { case WM_LBUTTONDOWN: if (msg1.x < 150 && msg1.x>50 && msg1.y > 20 && msg1.y < 80) { closegraph(); t1 = 0; break; } default:break; } } } }
gameplay.cpp
#include<stdio.h> #include<iostream> #include<graphics.h> #include "hong.h" extern int map[ROW][COL]; extern IMAGE img[16]; extern bool t2 ; extern bool t5;//开启t7标志 extern bool t7; extern bool t6; extern int cnt; void show(); extern int fla; extern IMAGE pic[11]; extern bool t4; extern int tic; extern int num[3]; //int ind = 0; #include<time.h> #include<Windows.h> //胜负判断,同时重来 void gameDraw(); void jude(int row, int col) { if (map[row][col] == -1) { MessageBox(GetHWnd(), "you lose", "再接再厉", MB_OK); for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { if (map[i][j] == 19)//不是雷 { map[i][j] -= 20; } else if (map[i][j] == 39) { map[i][j] -= 40; } } } gameDraw(); Sleep(1000); //重来/结束 for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { map[i][j] -= 100; } } rectangle(100, 20, 150, 40); LPCSTR arrt2 = { "close" }; int text3 = 100 + 50 / 2 - textwidth(arrt2) / 2; int text4 = 20 + 20 / 2 - textheight(arrt2) / 2; outtextxy(text3, text4, arrt2); t5 = 0; //ind = 0; } if (cnt == COL * ROW - MINE_NUM) { gameDraw(); MessageBox(GetHWnd(), "you win", "不错", MB_OK); Sleep(1000); //重来/结束 for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { map[i][j] -= 100; } } rectangle(100, 20, 150, 40); LPCSTR arrt2 = { "close" }; int text3 = 100 + 50 / 2 - textwidth(arrt2) / 2; int text4 = 20 + 20 / 2 - textheight(arrt2) / 2; outtextxy(text3, text4, arrt2); t5 = 1; } } //利用递归炸开方格 void openNULL(int row, int col) { if (map[row][col] == 0) { for (int i = row - 1; i <= row + 1; i++) { for (int j = col - 1; j <= col + 1; j++) { if (i >= 0 && i < ROW&&j >= 0 && j < COL && map[i][j] >19 && map[i][j] < 39) { map[i][j] -= 20; cnt++; openNULL(i, j);//递归 } } } } } //初始化 void gameinit() { for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { map[i][j] = 0; } } //生成随机数种子 srand((unsigned)time(NULL)); for (int i = 0; i < MINE_NUM; ) { //随机生成下标 int r = rand() % ROW; int c = rand() % COL; if (map[r][c] == 0) { map[r][c] = -1; i++; } } //让以-1为中心的九宫格所有数据加一,-1除外 for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { if (map[i][j] == -1) { //遍历九宫格 for (int p = i - 1; p <= i + 1; p++) { for (int q = j - 1; q <= j + 1; q++) { if (p >= 0 && p < ROW&&q >= 0 && q < COL&&map[p][q] != -1) { map[p][q]++; } } } } } } char str[50] = ""; for (int i = 0; i < 9; i++) { sprintf_s(str, "./图片素材/images1/%d.gif", i); loadimage(&img[i], str, GRID_W, GRID_W); } loadimage(&img[9], "./图片素材/images1/mine.gif", GRID_W, GRID_W); loadimage(&img[10], "./图片素材/images1/blank.gif", GRID_W, GRID_W); loadimage(&img[11], "./图片素材/images1/flag.gif", GRID_W, GRID_W); loadimage(&img[12], "./图片素材/images1/blood.gif", GRID_W, GRID_W); //计旗雷数图片加载 for (int i = 0; i < 11; i++) { sprintf_s(str, "./图片素材/images1/d%d.gif", i); loadimage(&pic[i], str, GRID_W, GRID_W / 2); } //数据加密 -1~8全部数据+20 -19~28 for (int i = 0; i < ROW; i++) { for (int j = 0; j < COL; j++) { map[i][j] += 20; } } } //界面绘制 void gameDraw() { for (int i = 0; i < ROW; i++)//i->y { for (int j = 0; j < COL; j++)//j->x { if (map[i][j] >= 0 && map[i][j] <= 10) { putimage(j*GRID_W, i*GRID_W + 50, &img[map[i][j]]); } else if (map[i][j] == -1) { putimage(j*GRID_W, i*GRID_W + 50, &img[9]); } else if (map[i][j] >= 19 && map[i][j] <= 28) { putimage(j*GRID_W, i*GRID_W + 50, &img[10]); } else if (map[i][j] >= 39 && map[i][j] <= 48) { putimage(j*GRID_W, i*GRID_W + 50, &img[11]); } else if (map[i][j] < -1) { putimage(j*GRID_W, i*GRID_W + 50, &img[0]); } } } } //旗-雷数 void checknum() { tic = MINE_NUM - fla; if (tic < 0) { putimage(0, 0, &pic[10]); tic = 0 - tic; } num[0] = tic % 10; num[1] = (tic / 10) % 10; num[2] = tic / 100; putimage(GRID_W * 3, 0, &pic[num[0]]); putimage(GRID_W * 2, 0, &pic[num[1]]); putimage(GRID_W, 0, &pic[num[2]]); } //鼠标点击(核心) void mousehit() { MOUSEMSG msg = GetMouseMsg();//获取鼠标消息 //把鼠标坐标转到二维数组中 int row = (msg.y - 50) / GRID_W; int col = msg.x / GRID_W; if (msg.uMsg == WM_LBUTTONDOWN) { if (msg.y < 50) { //从头再来 if (msg.x < 80 && msg.x>60 && msg.y > 20 && msg.y < 40) { t4 = 0; t5 = 1; t2 = 1; putimage(0, 0, &pic[0]); } //结束程序 if (t5 == 0) { if (msg.x < 150 && msg.x>100 && msg.y > 20 && msg.y < 40) { t7 = 1; } } } else { if (map[row][col] >= 19 && map[row][col] <= 28) { map[row][col] -= 20; cnt++; openNULL(row, col); if (t2) { if (map[row][col] != -1) { t2 = 0; } else { map[row][col] += 20; } } show(); if (t2 == 0) { jude(row, col); } } } t6 = 1; } else if (msg.uMsg == WM_RBUTTONDOWN) { if (map[row][col] >= 19 && map[row][col] <= 28) { map[row][col] += 20; if (t2 == 0) fla++; show(); } else if (map[row][col] >= 39 && map[row][col] <= 48) { map[row][col] -= 20; if (t2 == 0) fla--; } } } s