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++实现扫雷小游戏(控制台版) 的全部内容, 来源链接: utcz.com/p/244993.html

回到顶部