一、扫雷UI和玩家交互
定义class Field,用来表明扫雷的面板。以及一系列member func来操作该面板。
声明如下,具体实现见第2部分
代码语言:cpp复制class Field
{
public:
Field();
void mineTheField(); // 埋雷
void markAdjMineCells(); // 统计每个Cells的周围的雷的个数
void startSweep(int, int); // 执行扫雷
void startSweep(int, int, POSOFCELL, DIR_X, DIR_Y); // 执行扫雷
void drawField(); // 展示UI画面
void checkVictoryAndFlagMines(); // 检查是否胜利并展示所有雷
void getMove(); // 根据用户按键进行响应
private:
int l; // 长度
int b; // 宽度
int m; // 雷的个数
int x; // 当前光标x
int y; // 当前光标y
int flags;
int hiddenCells;
bool firstSweep; // 首次扫雷
std::vector<std::pair<int, int>> mines; //store the location of mines
GRID cells; // using GRID = std::vector<std::vector<Cell>>; 这里GRID网格存储所有单元格的二维数组
};
展示玩家游戏状态:
代码语言:cpp复制void dispVictoryOrDefeat() // 在屏幕打印出游戏状态
{
writeBuf << endl;
COLOUR col = gameState == VICTORY ? green_fg : red_fg;
writeBuf << col << R"( __ _____ _ _ )" << endl;
writeBuf << col << R"( / / _ | | | |)" << endl;
writeBuf << col << R"( V / (_) | |_| |)" << endl;
writeBuf << col << R"( |_| ___/ ___/ )" << endl;
writeBuf << endl;
if (gameState == VICTORY)
{
writeBuf << col << R"( __ _____ _ _ _ )" << endl;
writeBuf << col << R"( / /_ _| | | |)" << endl;
writeBuf << col << R"( // / | || .` |_|)" << endl;
writeBuf << col << R"( _/_/ |___|_|_(_))" << endl;
}
else
{
writeBuf << col << R"( _ ___ ___ ___ _ )" << endl;
writeBuf << col << R"( | | / _ / __| __| |)" << endl;
writeBuf << col << R"( | |_| (_) __ _||_|)" << endl;
writeBuf << col << R"( |_______/|___/___(_))" << endl;
}
}
那么,我们的主体main函数这样实现
代码语言:cpp复制
int main()
{
system("clear"); // 清除屏幕
Field field; // 初始化扫雷盘
while (true)
{
system("clear"); // 玩家每进行一步按键后,清屏
field.drawField(); // 根据当前状态画UI
if (gameState != RUNNING)
dispVictoryOrDefeat(); // 游戏结束,显示玩家游戏结果
writeBuf.disp(); // 打印拼命
writeBuf.clear(); // 清空写入Buf
if (gameState == RUNNING)
field.getMove(); // 根据玩家按键,更新状态
else
break;
}
std::cout << endl
<< reset;
return 0;
}
二、Filed实现
2.1 埋雷实现:
代码语言:cpp复制void Field::mineTheField()
{
std::random_device rd;
std::mt19937 rng(rd()); // 伪随机产生数
std::uniform_int_distribution<int> x_uni(0, l - 1); // 符合自然规律的正态分布埋雷坐标
std::uniform_int_distribution<int> y_uni(0, b - 1);
auto m_copy = m; // 总共要埋得雷
while (m_copy) // 开始埋雷
{
auto i = x_uni(rng); //
auto j = y_uni(rng);
if ((i >= x - 1 && i <= x 1) && // x和y分别是长度和宽度的中位数。
(j >= y - 1 && j <= y 1))
continue; // 如果随机到坐标是盘的中心点,那么跳过
if (cells[i][j].state != MINE) // 当前区域没有埋过雷
{
cells[i][j].setMine(); // 以(x,y)坐标的网格开始埋雷
mines.push_back(std::make_pair(i, j)); // 储存埋雷位置
--m_copy; // 成功埋雷,剩余埋雷数-1
}
}
}
2.2 统计周边雷的个数
代码语言:cpp复制void Field::markAdjMineCells()
{
for (auto mine : mines) // 遍历所有雷
{
int x_pos = mine.first, y_pos = mine.second; // 雷的坐标
for (int i = x_pos - 1; i < x_pos 2; i) // 雷的x坐标范围[x_pos-1, x_pos 1]
{
if (i < 0 || i > l - 1) // 无效区域,不统计超出扫雷盘x坐标的无效区间
continue;
for (int j = y_pos - 1; j < y_pos 2; j) // 雷的y坐标范围[y_pos-1, y_pos 1]
{
if (j < 0 || j > b - 1) // 无效区域,不统计超出扫雷盘y坐标的无效区间
continue;
if (cells[i][j].state == MINE) // 如果当前位置就是雷,那么不记录周边雷的个数
continue;
int mineCount = 0; // 正式开始统计
for (int c = i - 1; c < i 2; c) // 以当前位置(i,j)为中心,检查周围的8个点的区域
{
if (c < 0 || c > l - 1) // 无效区域
continue;
for (int d = j - 1; d < j 2; d)
{
if (d < 0 || d > b - 1) // 无效区域
continue;
if (cells[c][d].state == MINE) // 周围存在1个雷,进行加1操作
mineCount;
}
}
if (mineCount) // 如果有雷,进行标注
cells[i][j].markAdjMine(mineCount);
}
}
}
}
2.3 开始扫雷
代码语言:cpp复制void Field::startSweep(int x, int y)
{
if (gameState != RUNNING)
return;
if (cells[x][y].flagged) // 玩家标记的雷区
return;
if (!cells[x][y].hidden && !QUICKCLEAR) // 当前区域已经扫过
return;
switch (cells[x][y].state)
{
case EMPTY: // 没有扫过雷区
--hiddenCells;
cells[x][y].reveal(); // 展示当前网格状态
checkVictoryAndFlagMines(); // 检查是否踩到雷
break;
case ADJ_TO_MINE: // 是个周边雷的数字
if (cells[x][y].hidden) // 展示
{
--hiddenCells;
cells[x][y].reveal();
checkVictoryAndFlagMines();
return;
}
else if (QUICKCLEAR)
{
bool isValid = checkValidityOfQuickClear();
checkVictoryAndFlagMines();
if(isValid)
break;
else
return;
}
case MINE: // 踩到雷
gameState = DEFEAT; // 游戏失败
for (auto mine : mines) //所有雷展示出来
cells[mine.first][mine.second].reveal();
return;
}
// 四周8个位置扫雷
startSweep(x - 1, y - 1, CORNER, LEFT , UP );
startSweep(x , y - 1, EDGE , NULL_DIR_X, UP );
startSweep(x 1, y - 1, CORNER, RIGHT , UP );
startSweep(x - 1, y , EDGE , LEFT , NULL_DIR_Y);
startSweep(x 1, y , EDGE , RIGHT , NULL_DIR_Y);
startSweep(x - 1, y 1, CORNER, LEFT , DOWN );
startSweep(x , y 1, EDGE , NULL_DIR_X, DOWN );
startSweep(x 1, y 1, CORNER, RIGHT , DOWN );
checkVictoryAndFlagMines();
}
开始扫雷
代码语言:cpp复制void Field::startSweep(int x, int y, POSOFCELL pos, DIR_X x_dir, DIR_Y y_dir)
{
checkVictoryAndFlagMines();
if (x < 0 || x > l - 1 || y < 0 || y > b - 1) // 超出扫雷面板
return;
if (gameState != RUNNING)
return;
if (cells[x][y].flagged) // 当前位置已经被玩家标注
return;
if (!cells[x][y].hidden) // 当前位置已经扫过
return;
switch (cells[x][y].state)
{
case EMPTY: // 空白区域
cells[x][y].reveal(); // 显示空白
--hiddenCells;
if (pos == CORNER) // 如果该网格位置是角落,那么需要下一次递归是2个边和3个角
{
startSweep(x x_dir, y y_dir, CORNER, x_dir , y_dir );
startSweep(x x_dir, y , EDGE , x_dir , NULL_DIR_Y );
startSweep(x , y y_dir, EDGE , NULL_DIR_X , y_dir );
startSweep(x x_dir, y - y_dir, CORNER, x_dir , (DIR_Y)(-y_dir));
startSweep(x - x_dir, y y_dir, CORNER, (DIR_X)(-x_dir), y_dir );
}
else // 如果该网格位置是边缘
{
startSweep(x x_dir, y y_dir, EDGE , x_dir, y_dir); // 扫雷当前位置
if (y_dir == NULL_DIR_Y) //
{
startSweep(x x_dir, y - 1, CORNER, x_dir , UP );
startSweep(x , y - 1, EDGE , NULL_DIR_X, UP );
startSweep(x , y 1, EDGE , NULL_DIR_X, DOWN);
startSweep(x x_dir, y 1, CORNER, x_dir , DOWN);
}
else
{
startSweep(x - 1, y y_dir, CORNER, LEFT , y_dir );
startSweep(x - 1, y , EDGE , LEFT , NULL_DIR_Y);
startSweep(x 1, y , EDGE , RIGHT, NULL_DIR_Y);
startSweep(x 1, y y_dir, CORNER, RIGHT, y_dir );
}
}
break;
case ADJ_TO_MINE: // 位置是周边雷的数字,展示出来
cells[x][y].reveal();
--hiddenCells;
break;
}
}
2.5 画UI
代码语言:cpp复制void Field::drawField()
{
writeBuf << reset;
for (int s = 0; s <= l * 4; s) // 用5个位置画一个格子Cell
writeBuf << " "; // 先画第1行空白行
writeBuf << endl;
writeBuf << reset;
writeBuf << " "; // 第一列1的左边空白区域
if (cells[0][0].hidden) // 如果当前区域未扫过,用比较粗的线
writeBuf << "┏";
else
writeBuf << reset << "┌";
for (int i = 0; i < l - 1; i) // 先画第1行轮廓
{
if (cells[i][0].hidden)
{
writeBuf << "━━━";
if (cells[i 1][0].hidden)
writeBuf << "┳";
else
writeBuf << "┱";
}
else
{
writeBuf << "───";
if (cells[i 1][0].hidden)
writeBuf << "┲";
else
writeBuf << "┬";
}
}
if (cells[l - 1][0].hidden)
writeBuf << "━━━┓";
else
writeBuf << "───┐";
writeBuf << endl;
writeBuf << reset;
for (int j = 0; j < b; j)
{
if (cells[0][j].hidden)
writeBuf << " ┃";
else
writeBuf << reset << " │";
for (int i = 0; i < l; i)
{
if (cells[i][j].state != MINE || cells[i][j].hidden)
writeBuf << " ";
else
writeBuf << " "; //implement different whitespace char here
if (i == x && j == y)
{
if ((cells[i][j].hidden || cells[i][j].state == EMPTY) &&
(!cells[i][j].flagged))
writeBuf << blue_bg << " ";
else
writeBuf << blue_bg << cells[i][j].sym;
}
else
writeBuf << cells[i][j].sym;
if (cells[i][j].hidden || (i != l - 1 && cells[i 1][j].hidden))
writeBuf << reset << " ┃";
else
writeBuf << reset << " │";
}
if (j != b - 1)
{
writeBuf << endl;
writeBuf << reset;
writeBuf << " ";
if (cells[0][j].hidden)
{
if (cells[0][j 1].hidden)
writeBuf << "┣";
else
writeBuf << "┡";
}
else
{
if (cells[0][j 1].hidden)
writeBuf << "┢";
else
writeBuf << "├";
}
for (int k = 0; k < l - 1; k)
{
if (cells[k][j].hidden)
{
writeBuf << "━━━";
if (cells[k 1][j].hidden && !cells[k][j 1].hidden && !cells[k 1][j 1].hidden)
{
writeBuf << "╇";
}
else if (!cells[k 1][j].hidden && cells[k][j 1].hidden && !cells[k 1][j 1].hidden)
{
writeBuf << "╉";
}
else if (!cells[k 1][j].hidden && !cells[k][j 1].hidden && !cells[k 1][j 1].hidden)
{
writeBuf << "╃";
}
else
{
writeBuf << "╋";
}
}
else
{
if (cells[k][j 1].hidden)
writeBuf << "━━━";
else
writeBuf << "───";
if (cells[k 1][j].hidden && !cells[k][j 1].hidden && !cells[k 1][j 1].hidden)
{
writeBuf << "╄";
}
else if (!cells[k 1][j].hidden && cells[k][j 1].hidden && !cells[k 1][j 1].hidden)
{
writeBuf << "╅";
}
else if (!cells[k 1][j].hidden && !cells[k][j 1].hidden && cells[k 1][j 1].hidden)
{
writeBuf << "╆";
}
else if (cells[k 1][j].hidden && !cells[k][j 1].hidden && cells[k 1][j 1].hidden)
{
writeBuf << "╊";
}
else if (!cells[k 1][j].hidden && cells[k][j 1].hidden && cells[k 1][j 1].hidden)
{
writeBuf << "╈";
}
else if (!cells[k 1][j].hidden && !cells[k][j 1].hidden && !cells[k 1][j 1].hidden)
{
writeBuf << "┼";
}
else
{
writeBuf << "╋";
}
}
}
if (cells[l - 1][j].hidden)
{
writeBuf << "━━━";
if (cells[l - 1][j 1].hidden)
writeBuf << "┫";
else
writeBuf << "┩";
}
else
{
if (cells[l - 1][j 1].hidden)
writeBuf << "━━━┪";
else
writeBuf << "───┤";
}
}
writeBuf << endl;
writeBuf << reset;
}
writeBuf << " ";
if (cells[0][b - 1].hidden)
writeBuf << "┗";
else
writeBuf << "└";
for (int i = 0; i < l - 1; i)
{
if (cells[i][b - 1].hidden)
{
writeBuf << "━━━";
if (cells[i 1][b - 1].hidden)
writeBuf << "┻";
else
writeBuf << "┹";
}
else
{
writeBuf << "───";
if (cells[i 1][b - 1].hidden)
writeBuf << "┺";
else
writeBuf << "┴";
}
}
if (cells[l - 1][b - 1].hidden)
writeBuf << "━━━┛";
else
writeBuf << "───┘";
writeBuf << endl;
writeBuf.goToLine(0); // 开始绘画
}
2.6 检查是否扫完雷区
代码语言:cpp复制
void Field::checkVictoryAndFlagMines()
{
if (hiddenCells == m && gameState != DEFEAT)
{
gameState = VICTORY;
//flag all mines that weren't flagged
for (auto mine : mines) // 把所有雷的都展示出来
{
if (!cells[mine.first][mine.second].flagged)
{
cells[mine.first][mine.second].toggleflag();
}
}
}
}
2.7 根据玩家按键进行下一步动作
玩家有效的操作按键包括:
- ↑, ←, ↓, → 或者H, J, K, L
- 扫雷 字母S或者回车键
- 标记雷区 字母F或者空格键
void Field::getMove()
{
KEY k = getKey();
switch (k)
{
case K_K:
case K_k:
case K_UP: // 玩家按键是向上键
if (y)
--y;
return;
case K_J:
case K_j:
case K_DOWN: // 玩家按键是向下键
if (y != b - 1)
y;
return;
case K_H:
case K_h:
case K_LEFT: // 玩家按键是向左键
if (x)
--x;
return;
case K_L:
case K_l:
case K_RIGHT: // 玩家按键是向右键
if (x != l - 1)
x;
return;
case K_SPACE: // 玩家按键是空格,标记雷区
case K_F:
if (cells[x][y].flagged)
{
flags;
flagDisp;
cells[x][y].toggleflag();
}
else if (flags && cells[x][y].hidden)
{
--flags;
--flagDisp;
cells[x][y].toggleflag();
}
return;
case K_ENTER: // 玩家按键是Enter键,执行扫雷
case K_S:
if (firstSweep)
{
mineTheField();
markAdjMineCells();
firstSweep = false;
}
startSweep(x, y);
return;
}
}