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;
}

游戏截图




猜你喜欢

转载自blog.csdn.net/qq_35215641/article/details/80704045