资讯动态

C++ 新手项目之扫雷

发布时间:2026/8/6 11:58:28 来源:尧图企业网站定制
扫雷很简单直接一个大数组记录该方格状态就行了。唯一麻烦的地方在于图形化界面每次点击都要更新画面。我使用的是Easyx实现游戏界面这个库很简单适合新手。游戏展示扫雷前言这次突发奇想如果把所有的代码都写到一个类里会怎么样。结果很糟糕找函数得往上找半天。过度设计要不得偷懒设计也要不得。程序其实还可以添加几个按钮让用户自行选择难度逻辑大致为根据点击区域选择是否更新相应参数并重置游戏。ps:素材是网上一个个扣下来的。给大家介绍个抠图神器Snipaste免费软件非常好用。设计思路逻辑方面其实很简单。假如游戏区域为r * c (r行c列用数组mineMap[r * c]保存每个方格的状态-1表示这个格子是雷0-8表示周围雷的数量。为防止第一次就踩雷采用先点击再初始化地图的方法。以点击方格为中心的 3 * 3 范围设为安全区插入地雷时避开该区域。揭露方块时如果该方格mineMap[i] 0表示周围没有雷则连同四周的方格一块揭露DFS/BFS都能实现建议使用BFS当地图非常大遇到一大批无雷区时会导致递归深度很深。胜利检测用了两个值 leftcell(剩余格子数和 correctflagcount正确插旗的方格数量两者和为地雷总数时获得胜利。接着就是游戏画面的更新。检测鼠标点击信息分左键和右键左键揭露方格右键给方格设置状态插旗不确定正常状态。点击后根据该方格的状态修改mineMap和revealed并更新小方格的图标只改修改过方格即可)。代码展示#pragma once #include windows.h #include random #include vector #include iostream #include graphics.h #include cstdio #include ctime const int CELL 30; // 每个小方格30*30像素 class mineSweeper { public: enum CellState : unsigned char { HIDDEN 0, // 隐藏 REVEALED 1, // 翻开 FLAGGED 2, // 标记 QUESTION 3 // 不确定 }; public: // 加载图片素材 IMAGE img_t[10]; // 时间 IMAGE img_d[9]; // 数字 IMAGE img_c[7]; // 方块的状态,未翻开插旗雷问号 IMAGE img_f[5]; // 笑脸 void loadMyImg(); private: int row, col; // 行列 int game_x, game_y; // 游戏区域尺寸为game_x*game_y int offsetX, offsetY; // cell区域相对00的偏移量 int facex, facey; // 笑脸图标的坐标 int imaget_x; // Time图片缩放后x的尺寸 int mines; // 雷的数量 bool gameover; // 游戏结束 bool gamewin; // 游戏胜利 bool firstgame; // 初次游戏 int leftcell; // 剩余的安全格子数 int correctflagcount; // 正确插旗的雷数 int flagcount; // 插旗数量 int time; // 时间 std::vectorint mineMap; // mineMap[y*colx]jj0表示周围8格雷的数量,j-1表示该格为雷 std::vectorCellState revealed; // mineMap中的格子的状态 public: mineSweeper() {} void setParameter(int r, int c, int num); // 设置参数 void initMapAfterClick(int safe_r, int saft_c); // 点击后再设置雷 int countAroundMine(int r, int c); // 计算该格周围的雷数 void calculateAllNumber(); // 设置mineMap每格的数字 void revealEmptyCell(int r, int c); // 揭露空的格子 void leftClick(int r, int c); // 左击方格 void rightClick(int r, int c); // 右击方格 void checkWin(); // 检查胜利 void resetGame(); // 重置游戏 void updateTime(); // 更新时间 public: void initUI(); // 初始化UI界面 void updateTimeUI(); // 更新右上角的时间 void updateCellImage(int r, int c); // 更新方格图标 void handleMouseClick(int x, int y, bool isLeft); // 处理点击 void updateAllCellImage(); // 更新所有cell图标 void updateFaceImage(); // 更新笑脸图标 void updateMinenumDisplay(); // 更新左上角的剩余地雷数 void FailureShow(); // 游戏失败时显示地雷 void quickReveal(int r, int c); // 点击已揭露的数字方格闪烁提示四周范围并揭露方块 void run(); // 游戏的主循环 };#include mine.h // 加载图片 void mineSweeper::loadMyImg() { char filename[20]; int t_x static_castint((CELL 5) / 2); int t_y CELL 5; // 时间 for (int i 0; i 10; i) { sprintf_s(filename, ./pictures/t%d.png, i); loadimage(img_t[i], filename, t_x, t_y); } // 数字 for (int i 0; i 9; i) { sprintf_s(filename, ./pictures/%d.png, i); loadimage(img_d[i], filename, CELL, CELL); } // 方块状态 for (int i 0; i 7; i) { sprintf_s(filename, ./pictures/c%d.png, i); loadimage(img_c[i], filename, CELL, CELL); } // 笑脸 for (int i 0; i 5; i) { sprintf_s(filename, ./pictures/f%d.png, i); loadimage(img_f[i], filename, CELL 7, CELL 7); } } // 设置参数 void mineSweeper::setParameter(int r, int c, int mine_num) { loadMyImg(); row r; col c; mines mine_num; game_x col * CELL 30; game_y row * CELL 90; facex game_x / 2 - 17; facey 20; imaget_x static_castint(CELL / 2 5); offsetX 15; offsetY 75; gameover false; gamewin false; firstgame true; time 0; leftcell r * c; flagcount 0; correctflagcount 0; mineMap.assign(static_castsize_t(r * c), 0); revealed.assign(static_castsize_t(r * c), CellState::HIDDEN); SetProcessDPIAware(); initgraph(game_x, game_y); } // 点击后设置安全区再插入地雷初始化地图 void mineSweeper::initMapAfterClick(int r, int c) { // 以安全点为中心3*3范围内的格子设置为安全 mineMap[static_castsize_t(r * col c)] -2; for (int dr -1; dr 1; dr) { for (int dc -1; dc 1; dc) { int newr r dr; int newc c dc; if (newr 0 newr row newc 0 newc col) mineMap[static_castsize_t(newr * col newc)] -2; } } // 安全区之外插入地雷 int count mines; std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution distr(0, row - 1); std::uniform_int_distribution distc(0, col - 1); while (count 0) { int rr distr(gen); int cc distc(gen); size_t n static_castsize_t(rr * col cc); // -2为安全区-1为雷区0为可插入区 if (mineMap[n] 0) { mineMap[n] -1; --count; } } // 安全区设置为0 mineMap[static_castsize_t(r * col c)] 0; for (int dr -1; dr 1; dr) { for (int dc -1; dc 1; dc) { int newr r dr; int newc c dc; if (newr 0 newr row newc 0 newc col) mineMap[static_castsize_t(newr * col newc)] 0; } } // 计算地图中每个格子的数字 calculateAllNumber(); firstgame false; } // 计算格子周围的地雷数量 int mineSweeper::countAroundMine(int r, int c) { if (mineMap[static_castsize_t(r * col c)] -1) return -1; int count 0; for (int dr -1; dr 1; dr) { for (int dc -1; dc 1; dc) { int newr r dr; int newc c dc; if (newr 0 newr row newc 0 newc col) if (mineMap[static_castsize_t(newr * col newc)] -1) count; } } return count; } void mineSweeper::calculateAllNumber() { for (int r 0; r row; r) { for (int c 0; c col; c) { { size_t n static_castsize_t(r * col c); if (mineMap[n] ! -1) mineMap[n] countAroundMine(r, c); } } } } // 揭露该格 void mineSweeper::revealEmptyCell(int r, int c) { if (r 0 || r row || c 0 || c col) // 边界检查 return; size_t n static_castsize_t(r * col c); if (revealed[n] ! CellState::HIDDEN) // 只处理隐藏状态的格子 return; if (mineMap[n] -1) // 不处理雷 return; revealed[n] CellState::REVEALED; // 该格被揭露 --leftcell; // test std::cout Leftcell: leftcell \tCorrectflagcount: correctflagcount std::endl; if (mineMap[n] 0) // 如果该格为0则周围格子递归调用showEmpty { for (int dr -1; dr 1; dr) { for (int dc -1; dc 1; dc) { if (dr 0 dc 0) continue; revealEmptyCell(r dr, c dc); } } } } void mineSweeper::leftClick(int r, int c) { // r,c 在handleMouseClick中检测过了不用再检测 // if (gameover || gamewin) // return; // if (r 0 || r row || c 0 || c col) // return; size_t n static_castsize_t(r * col c); // 点击翻开的且数字0的方格闪烁周围未被揭露且未插旗的方格并快速揭露肯定安全的方格 if (revealed[n] CellState::REVEALED mineMap[n] 0) { quickReveal(r, c); return; } // 问号和插旗方格点击无效 if (revealed[n] CellState::FLAGGED || revealed[n] CellState::REVEALED || revealed[n] CellState::QUESTION) return; // 是否初次游戏 if (firstgame) initMapAfterClick(r, c); // 是否中雷了 if (mineMap[n] -1) { gameover true; revealed[n] CellState::REVEALED; FailureShow(); updateFaceImage(); return; } // 揭露方格 revealEmptyCell(r, c); // 检查胜利 checkWin(); } void mineSweeper::rightClick(int r, int c) { // if (gameover || gamewin) // return; // if (r 0 || r row || c 0 || c col) // return; size_t n static_castsize_t(r * col c); if (revealed[n] CellState::REVEALED) return; // 切换状态 FLAGGED - QUESTION - HIDDEN -FLAGGED switch (revealed[n]) { case CellState::FLAGGED: revealed[n] CellState::QUESTION; if (mineMap[n] -1) { --correctflagcount; // 取消正确标记 leftcell; // 剩余的非雷格子数1 } --flagcount; break; case CellState::QUESTION: revealed[n] CellState::HIDDEN; break; case CellState::HIDDEN: if (flagcount mines) // 当插旗数等于地雷数时无法插旗 break; revealed[n] CellState::FLAGGED; if (mineMap[n] -1) { correctflagcount; // 正确标记了雷 --leftcell; // 剩余的非雷格子数-1 } flagcount; break; default: break; } // test std::cout Leftcell: leftcell \tCorrectflagcount: correctflagcount std::endl; } void mineSweeper::checkWin() { // 剩余格子数正确插旗数 雷数时判赢 if (leftcell correctflagcount mines) gamewin true; // 胜利时显示所有未插旗的雷 if (gamewin true) { for (int r 0; r row; r) { for (int c 0; c col; c) { size_t n static_castsize_t(r * col c); if (mineMap[n] -1 revealed[n] ! CellState::FLAGGED) revealed[n] CellState::FLAGGED; } } std::cout 获得胜利了 std::endl; } } void mineSweeper::initUI() { setbkcolor(RGB(204, 204, 204)); cleardevice(); // 雷区的起始位置为(15,75) for (int i 0; i col; i) { for (int j 0; j row; j) { putimage(offsetX i * CELL, offsetY j * CELL, img_c[0]); } } // 边界线模拟阴影 setlinecolor(RGB(128, 128, 128)); // 灰 for (int i 0; i 2; i) { line(13, 15 i, game_x - 14, 15 i); line(13, 73 i, game_x - 14, 73 i); line(13 i, 75, 13 i, game_y - 14); line(13 i, 15, 13 i, 60); } setlinecolor(RGB(255, 255, 255)); // 白 for (int i 0; i 2; i) { line(15, game_y - 15 i, game_x - 14, game_y - 15 i); line(game_x - 15 i, 75, game_x - 15 i, game_y - 14); line(15, 60 i, game_x - 14, 60 i); line(game_x - 15 i, 15, game_x - 15 i, 60); } // 绘制笑脸 putimage(game_x / 2 - 17, 20, img_f[0]); // 时间和雷数 int temp mines; int index; for (int i 0; i 3; i) { index temp % 10; temp static_castint(temp / 10); putimage(20 (2 - i) * imaget_x, 21, img_t[index]); // 左侧的雷数 putimage(game_x - 20 - (i 1) * imaget_x, 21, img_t[0]); // 右侧的时间 } } // time每秒1时间每秒更新一次 void mineSweeper::updateTimeUI() { int temp time; int index; for (int i 0; i 3; i) { index temp % 10; temp static_castint(temp / 10); putimage(game_x - 20 - (i 1) * imaget_x, 21, img_t[index]); // 右侧的时间 } } // 更新cell图标 void mineSweeper::updateCellImage(int r, int c) { if (r 0 || r row || c 0 || c col) return; // cell区域的起始像素坐标 int x offsetX c * CELL; int y offsetY r * CELL; size_t n static_castsize_t(r * col c); if (revealed[n] REVEALED) { if (mineMap[n] -1) putimage(x, y, img_c[3]); // 雷 else { putimage(x, y, img_d[0]); // 先显示个空白 putimage(x, y, img_d[mineMap[n]]); // 再显示数字 } } else if (revealed[n] FLAGGED) putimage(x, y, img_c[1]); // 插旗 else if (revealed[n] QUESTION) putimage(x, y, img_c[5]); // 问号 else putimage(x, y, img_c[0]); // 隐藏 } void mineSweeper::resetGame() { gameover false; gamewin false; firstgame true; leftcell row * col; flagcount 0; correctflagcount 0; time 0; // 重置地图 std::fill(mineMap.begin(), mineMap.end(), 0); std::fill(revealed.begin(), revealed.end(), CellState::HIDDEN); cleardevice(); initUI(); } void mineSweeper::handleMouseClick(int x, int y, bool isLeft) { // 点击笑脸区域重置游戏 printf(%s点击: (%d, %d)\n, (isLeft true) ? 左键 : 右键, x, y); // 调试用 int facesize CELL 7; if (x facex x facex facesize y facey y facey facesize) { resetGame(); return; } // 游戏失败或成功后除了笑脸区域都不能点击 if (gameover || gamewin) return; // 判断是否在区域内 if (x offsetX || x offsetX col * CELL || y offsetY || y offsetY row * CELL) { return; // 不在游戏区域内直接返回 } // -5/CELL 0这会导致计算错误所以先要判断是否在游戏区域内部 int c (x - offsetX) / CELL; int r (y - offsetY) / CELL; if (c 0 || c col || r 0 || r row) return; if (isLeft) { leftClick(r, c); updateAllCellImage(); updateFaceImage(); updateMinenumDisplay(); if (gameover) { FailureShow(); } } else { rightClick(r, c); updateCellImage(r, c); // 右键只需要改变一个图标即可 updateMinenumDisplay(); } } void mineSweeper::updateAllCellImage() { for (int r 0; r row; r) { for (int c 0; c col; c) { updateCellImage(r, c); } } } void mineSweeper::updateFaceImage() { int index 0; if (gameover) index 4; // 失败 else if (gamewin) index 3; // 胜利酷 else return; // 正常不需要改变图标 putimage(facex, facey, img_f[index]); } void mineSweeper::updateMinenumDisplay() { int temp mines - flagcount; // 剩余地雷数 int index; for (int i 0; i 3; i) { index temp % 10; temp temp / 10; putimage(20 (2 - i) * imaget_x, 21, img_t[index]); } } void mineSweeper::quickReveal(int r, int c) { if (gameover || gamewin) return; if (r 0 || r row || c 0 || c col) return; auto n static_castsize_t(r * col c); int aroundflag 0; // 周围插旗的数量 std::vectorstd::pairint, int hiddencells; for (int dr -1; dr 1; dr) { for (int dc -1; dc 1; dc) { if (dr 0 dc 0) continue; int nr r dr; int nc c dc; if (nr 0 || nr row || nc 0 || nc col) continue; auto idx1 static_castsize_t(nr * col nc); if (revealed[idx1] CellState::FLAGGED) aroundflag; else if (revealed[idx1] CellState::HIDDEN || revealed[idx1] CellState::QUESTION) { hiddencells.push_back({nr, nc}); } } } // 周围旗帜的数量等于显示的数字 if (aroundflag mineMap[n] mineMap[n] 0) { bool hitmine false; for (auto cell : hiddencells) { int cr cell.first; int cc cell.second; auto idx2 static_castsize_t(cr * col cc); if (mineMap[idx2] -1) { gameover true; revealed[idx2] CellState::REVEALED; hitmine true; } else { revealEmptyCell(cr, cc); } } if (hitmine) FailureShow(); else checkWin(); } else { // 闪烁提示 for (auto cell : hiddencells) { int x offsetX cell.second * CELL; int y offsetY cell.first * CELL; putimage(x, y, img_d[0]); } Sleep(100); // 恢复原来的图像 for (auto cell : hiddencells) { updateCellImage(cell.first, cell.second); } } flushmessage(EX_MOUSE); //清除堆积的鼠标信息防止延时操作 } void mineSweeper::FailureShow() { // 游戏失败时显示所有雷 for (int r 0; r row; r) { for (int c 0; c col; c) { size_t n static_castsize_t(r * col c); int x offsetX c * CELL; int y offsetY r * CELL; if (mineMap[n] -1) { if (revealed[n] CellState::FLAGGED) { continue; // 不需要改变进行下一轮检查 } else if (revealed[n] CellState::HIDDEN || revealed[n] CellState::QUESTION) { putimage(x, y, img_c[2]); // 没标出的雷 } } else if (mineMap[n] ! -1 revealed[n] CellState::FLAGGED) { putimage(x, y, img_c[4]); // 旗标错了 } } } } void mineSweeper::updateTime() { if (gameover || gamewin || firstgame) return; time; if (time 999) time 999; updateTimeUI(); } void mineSweeper::run() { ExMessage msg; bool running true; clock_t lastTime clock(); initUI(); while (running) { while (peekmessage(msg, EM_MOUSE | EM_KEY)) { switch (msg.message) { case WM_LBUTTONDOWN: // 按下左键 if (!gameover !gamewin) { bool inGame (msg.x offsetX msg.x offsetX col * CELL msg.y offsetY msg.y offsetY row * CELL); bool onFace (msg.x facex msg.x facex CELL 7 msg.y facey msg.y facey CELL 7); if (onFace) { putimage(facex, facey, img_f[1]); } else { if (!inGame) { putimage(facex, facey, img_f[2]); // 点击非cell区显示惊讶 } } } break; case WM_LBUTTONUP: // 松开左键 if (!gameover !gamewin) { putimage(facex, facey, img_f[0]); // 松开左键恢复笑脸 } handleMouseClick(msg.x, msg.y, true); // 处理点击 break; case WM_RBUTTONDOWN: // 按下右键 handleMouseClick(msg.x, msg.y, false); break; case WM_KEYDOWN: // 处理键盘输入 if (msg.vkcode VK_ESCAPE) { running false; // 按 ESC 退出 } break; } } clock_t currentTime clock(); if (currentTime - lastTime 1000) { updateTime(); lastTime currentTime; } Sleep(10); } }

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价