C++实现扫雷小游戏(控制台版)

 更新时间:2022年05月07日 11:42:49   作者:惊叹号的云  
这篇文章主要为大家详细介绍了C++实现控制台版的扫雷小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

本文为大家分享了C++实现扫雷小游戏的具体代码,供大家参考,具体内容如下

程序功能:

提供三种模式:初级、中级、高级

操作模式:wsad控制光标移动,空格键打开方块

提供扫雷地图的类

map.h

#ifndef MAP_H_
#define MAP_H_
 
#define MAX_LENGTH 32  //可以提供的地图最大长度
#define MAX_WIDTH 18  //可以提供的地图最大宽度
#define UP_EDGE 1   //上边界
#define DOWN_EDGE _wid  //下边界
#define LEFT_EDGE 1  //左边界
#define RIGHT_EDGE _lng //右边界
 
void gotoxy(int, int);  //移动光标的接口函数
 
struct Position{ 
 int x;
 int y;
};
 
struct Info{
 int n;   //用于标记雷、数字、空格的属性
 bool flag;  //用于标记是否要打开方块
};
 
class Map{
private:
 int _lng, _wid;     //长和宽
 int _mines, _blanks;    //雷数、未开启空格数目
 Position _pos = {1, 1};   //光标位置
 Info data[MAX_WIDTH][MAX_LENGTH]; //包含地图信息的矩阵
public:
 void AcceptCond();   //选择模式
 void InitMap();    //初始化地图
 void SetMine();    //布置地雷
 void SetNumber();   //计算数字
 void SetPosition();   //移动光标至指示区域
 void ResetPosition();  //重置初始坐标
 void ShowMap();    //显示地图
 void ShowAll();    //显示全部地图,游戏失败时候调用
 void OpenBlock();   //打开方块,即将 flag 值设置为 true,在 ShowMap() 中将打开方块
 void FirstStep();   //预先处理游戏,防止第一步就触雷导致失败,这是无意义的
 bool PlayGame();    //提供的游戏操作接口
 bool Move(char);    //移动光标,同时改变 _pos 的值用于指代目前要访问(打开)的方块
 bool IfLose();    //游戏失败,则返回真
 bool IfWin();    //游戏成功,则返回真
};
 
#endif

实现思路:

1.接收游戏模式参数,确定地图规模

2.初始化地图,值全部设置为 0,flag 全部设置为 false,表示未曾打开

3.根据用户操作,确定要打开的第一个空格的,然后再开始布雷,避免开局触雷结束,这样没什么意义。

4.布雷采用生成随机数的方法

5.根据地雷分布计算其他空格所对应的数字

6.通过PlayGame() 接口进行游戏操作

Map类的实现

#include <cstdlib>
#include <cstdio>
#include <ctime> //提供时间函数
#include <conio.h> //提供getch()
#include <windows.h>
#include <iostream>
#include "map.h"
 
#define GOTO(pos) gotoxy(2 * (pos.x) - 1, (pos.y) - 1) //定义用于移动光标的 宏
//由于一个方块占 2 个格子,所以 pos.x 每加 1,则光标要移动 2 格
 
using std::cout;
using std::cin;
using std::endl;
 
void gotoxy(int x, int y) {  //移动光标的接口
 COORD pos = { short(x), short(y) };
 HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
 SetConsoleCursorPosition(hOut, pos);
}
 
 
void Map::AcceptCond() {    //接收游戏模式参数
 cout << "Choose Mode" << endl;
 cout << "1 : Beginner" << endl;
 cout << "2 : Intermediate" << endl;
 cout << "3 : Expert" << endl << "Mode: ";
 char mode;
 cin >> mode;
 while (('1' != mode) && ('2' != mode) && ('3' != mode)) {  //仅仅接受 1, 2, 3,其他字符跳过
  cout << "Wrong Mode, Enter number again\n Mode: ";
  cin >> mode;
 }
 switch (mode) {
  case '1' : _lng = 8; _wid = 8; _mines = 10; break;
  case '2' : _lng = 16; _wid = 16; _mines = 40; break;
  default: _lng = 30; _wid = 16; _mines = 99;
 }
 _blanks = _lng * _wid - _mines;  //计算空格数,用于判断是否赢,_blanks = 0 时判定赢
}
 
 
void Map::InitMap() {  //初始化地图,显示的地图下标从 1 - wid, 1 - _lng, 边界外面还有空格,用于计算空格对应数字的,边界相当于0
 for (int i = 0; i < _wid + 2; i++) {
  for (int j = 0; j < _lng + 2; j++) {
   data[i][j].n = 0;
   data[i][j].flag = false;
  }
 }
}
 
 
void Map::SetMine() {
 int i, j;
 int m = _mines;
 srand(time(NULL));
 while (m)
 {
  i = rand() % _wid + 1;
  j = rand() % _lng + 1;
  if ((-1 != data[i][j].n) && (j != _pos.x && i != _pos.y)) { //后面的条件用于避免用户第一个打开的空格处布置地雷
   data[i][j].n = -1;
   m--;
  }
 }
}
 
 
void Map::SetNumber() {
 for (int i = 1; i <= _wid; i++) {
  for (int j = 1; j <= _lng; j++) { //依次检查周围的 8 个空格的雷数
   if (-1 == data[i][j].n) continue;
   if (-1 == data[i-1][j-1].n) data[i][j].n++;
   if (-1 == data[i][j-1].n) data[i][j].n++;
   if (-1 == data[i+1][j-1].n) data[i][j].n++;
   if (-1 == data[i-1][j].n) data[i][j].n++;
   if (-1 == data[i+1][j].n) data[i][j].n++;
   if (-1 == data[i-1][j+1].n) data[i][j].n++;
   if (-1 == data[i][j+1].n) data[i][j].n++;
   if (-1 == data[i+1][j+1].n) data[i][j].n++;
  }
 }
}
 
 
void Map::SetPosition() {
 GOTO(_pos);
}
 
 
void Map::ResetPosition() {
 _pos.x = _pos.y = 1;
}
 
 
void Map::ShowMap() {
 system("cls");   //清屏
 system("color 03");  //调整控制台显示颜色
 SetConsoleOutputCP(437); //使方块能够正常显示
 for (int i = 1; i <= _wid; i++) {
  cout << '|';  //左边界
  for (int j = 1; j <= _lng; j++) {
   if (data[i][j].flag) {
    switch (data[i][j].n) {
     case 0 : cout << " "; break; //由于方块占两个格子,因此其他的输出,如空格、数字等也要占2个格子,对齐
     default: cout << data[i][j].n << ' ';
    }
   }
   else printf("%c", 219);
  }
  cout << '|' << endl; //右边界
 }
 gotoxy(0, _wid+2); //在地图下方输出坐标信息和空格数
 printf("Position : (%d, %d)\n Blanks : %d", _pos.x, _pos.y, _blanks);
 GOTO(_pos); //归位到原先地图坐标对应的位置
}
 
 
void Map::ShowAll() { //类似上面的ShowMap(),但在游戏失败时调用
 system("cls");
 system("color 03");
 SetConsoleOutputCP(437);
 for (int i = 1; i <= _wid; i++) {
  cout << '|';
  for (int j = 1; j <= _lng; j++) {
   switch (data[i][j].n) {
    case 0 : printf("%c", 219); break;
    case -1: 
     if (i == _pos.y && j == _pos.x)
      cout << "X ";
     else
      cout << "* ";
     break;
    default: cout << data[i][j].n << ' ';
   }
  }
  cout << '|' << endl;
 }
}
 
#define SOLVE_IT(t) {stack[++top] = (t); data[(t).y][(t).x].flag = true; _blanks--;}
#define FALSE_FLAG(t) !data[(t).y][(t).x].flag
 
void Map::OpenBlock() {      //用栈来将连着的空格区域遍历一遍,并将其 flag 值置为 true
 if (data[_pos.y][_pos.x].flag) return; //如果已经打开过就不需要再次打开,否则 _blanks--; 会多次执行,无法判断赢
 Position stack[_lng * _wid << 1];
 Position t;
 int top = 0;
 
 stack[top] = _pos;
 data[_pos.y][_pos.x].flag = true;
 _blanks--;
 while (top != -1) {
  t = stack[top--];
  if (0 == data[t.y][t.x].n) { //如果该位置为 0 ,那么它周围的格子都要打开
   t.y--; //判断上方三个格子
   if (t.y >= UP_EDGE) { //如果上方三个格子 y 不越界
    if (FALSE_FLAG(t)) SOLVE_IT(t)
    t.x--;
    if (t.x >= LEFT_EDGE && FALSE_FLAG(t)) SOLVE_IT(t)
    t.x += 2;
    if (t.x <= RIGHT_EDGE && FALSE_FLAG(t)) SOLVE_IT(t)
    t.x--;
   }
    
   t.y++; t.x--;//判断左右两个格子, 此时 t.y 复原
   if (t.x >= LEFT_EDGE && FALSE_FLAG(t)) SOLVE_IT(t)
   t.x += 2;
   if (t.x <= RIGHT_EDGE && FALSE_FLAG(t)) SOLVE_IT(t)
 
   t.y++; //下方三个格子, 此时 t.x 是最右边的格子
   if (t.y <= DOWN_EDGE) { //如果下方三个格子 y 不越界, 与上面判断基本相同
    if (t.x <= RIGHT_EDGE && FALSE_FLAG(t)) SOLVE_IT(t)
    t.x--;
    if (FALSE_FLAG(t)) SOLVE_IT(t)
    t.x--;
    if (t.x >= LEFT_EDGE && FALSE_FLAG(t)) SOLVE_IT(t)
   }
  }
 }
}
 
 
void Map::FirstStep() { //函数结束后将改变 _pos,就是我们用的预先处理函数,防止第一步就触雷的
 char op;
 do {
  op = getch();
  while ((op != 'a') && (op != 's') && (op != 'd') && (op != 'w') && (op !=' '))
   op = getch();
 } while (Move(op));
}
 
 
bool Map::Move(char op) {
 switch (op) { //通过不同的操作,改变坐标,然后再通过 GOTO宏 移动到该位置上
  case ' ':
   return false;
  case 'w':
   if (UP_EDGE != _pos.y) _pos.y--;
   break;
  case 'a':
   if (LEFT_EDGE != _pos.x) _pos.x--;
   break;
  case 's':
   if (DOWN_EDGE != _pos.y) _pos.y++;
   break;
  default:
   if (RIGHT_EDGE != _pos.x) _pos.x++;
 }
 gotoxy(0, _wid + 2);
 printf("Position : (%d, %d)\n Blanks : %d", _pos.x, _pos.y, _blanks);
 GOTO(_pos);
 return true;
}
 
 
bool Map::IfLose() {
 return -1 == data[_pos.y][_pos.x].n;
}
 
 
bool Map::IfWin() {
 return 0 == _blanks;
}
 
 
bool Map::PlayGame() {
 char op;
 float start, end;
 while (!IfWin()) {
  do {
   op = getch();
   while ((op != 'a') && (op != 's') && (op != 'd') && (op != 'w') && (op !=' '))
    op = getch();
  } while (Move(op));
 
  if (IfLose()) { //触雷
   ShowAll(); gotoxy (0, _wid + 3);
   return false;
  }
  else {
   OpenBlock();   //打开方块,实质上时将 flag 的值置为 true,接着 ShowMap()将可以显示该方块信息
   ShowMap();
   GOTO(_pos);
  }
 }
 gotoxy(0, _wid + 3);
 return true;
}

主程序

mineweeper.cpp

#include <iostream>
#include <cstdlib>
#include <conio.h>
#include <ctime>
#include "map.h"
 
using namespace std;
 
int main() {
 Map game;
 float start, end;
 char ch;
 while (1) {
  game.AcceptCond();  //选择模式
  game.InitMap();  //初始化
  game.ShowMap();  //显示地图。 注:此时地图未生成完毕
  game.FirstStep();  //预处理,防止第一步就触雷结束
  game.SetMine();  //设置地雷
  game.SetNumber();  //根据地雷分布计算数字
  game.OpenBlock();  //打开开局预先想要打开的第一个空
  start = clock();
  game.ShowMap();
  if (game.PlayGame()) { //根据PlayGame()接口的返回值判定输赢
   cout << endl << "~ Congratulation ~\n ~ You Win ~" << endl;
  }
  else {
   cout << endl << "BOOM!!! ~ Game Over ~\n" << endl;
  }
  end = clock();
  printf("\nTime : %.2f\n", (end - start) / CLK_TCK); //输出游戏所用时间
  cout << endl << "Please enter 'q' to quit, or any other keys to continue" << endl;
  game.SetPosition(); //用于触雷失败时,将光标返回到触雷的位置,提示哪一步失败,同时触碰的雷也将显示为 ‘X'
  ch = getch();
  if ('q' == ch) { // q 用于退出游戏
   system("cls");
   cout << "~ Bye ~" << endl;
   break;
  }
  else {
   game.ResetPosition();
   system("cls");
  }
 }
 system("pause");
 return 0;
}

游戏截图

更多精彩游戏小代码,请点击《游戏专题》阅读

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • c++ 有趣的动态转换

    c++ 有趣的动态转换

    这篇文章主要介绍了c++ 动态转换的相关资料,帮助大家更好的理解和使用c++编程,感兴趣的朋友可以了解下
    2020-09-09
  • C++中的atoi 函数简介

    C++中的atoi 函数简介

    这篇文章主要给大家分享的是C++中的atoi 函数的简单介绍,在 stdlib.h 中 atoi 函数,可用于将 char 字符串转为 int 整数类型,集体的语法操作请参考下面文章的详细内容
    2021-11-11
  • C++中队列的建立与操作详细解析

    C++中队列的建立与操作详细解析

    队列结构是从数据运算来分类的,也就是说队列结构具有特殊的运算规则。而从数据的逻辑结构来看,队列结构其实就是一种线性结构。如果从数据的存储结构来进一步划分,队列结构可以分成两类
    2013-10-10
  • QT中窗口关闭自动销毁的实现示例

    QT中窗口关闭自动销毁的实现示例

    这篇文章主要介绍了QT中窗口关闭自动销毁,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-05-05
  • C语言 递归解决青蛙跳台阶问题

    C语言 递归解决青蛙跳台阶问题

    递归做为一种算法在程序设计语言中广泛应用。基本含义&#8203;是指函数/过程/子程序在运行过程序中直接或间接调用自身而产生的重入现象。在计算机编程里,递归指的是一个过程:函数不断引用自身,直到引用的对象已知
    2021-11-11
  • 关于C++中定义比较函数的三种方法小结

    关于C++中定义比较函数的三种方法小结

    下面小编就为大家带来一篇关于C++中定义比较函数的三种方法小结。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-10-10
  • C语言实现简单版三子棋

    C语言实现简单版三子棋

    这篇文章主要为大家详细介绍了C语言实现简单版三子棋,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-10-10
  • C++实现字符串删除字符后逆序输出

    C++实现字符串删除字符后逆序输出

    这篇文章主要为大家详细介绍了C++实现字符串删除字符后逆序输出,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-05-05
  • OpenCV图像变换与处理实战方法记录

    OpenCV图像变换与处理实战方法记录

    这篇文章主要给大家介绍了关于OpenCV图像变换与处理实战的相关资料,包括重映射、缩放、旋转、颜色变换、边缘检测、高斯模糊、图像锐化和颜色反转等,通过实例分析详细解释了这些功能的实现原理和代码实现,需要的朋友可以参考下
    2024-12-12
  • 减小VC6编译生成的exe文件的大小的方法

    减小VC6编译生成的exe文件的大小的方法

    这篇文章主要介绍了减小VC6编译生成的exe文件的大小的方法,需要的朋友可以参考下
    2015-01-01

最新评论