扫雷
效果如上,本文用C/C++ 实现一下扫雷游戏,C++主要用cout 函数用来代替printf, 其他用C的结构体及函数等实现。
一、实现思路:
进入游戏,获取玩家输入
- 判断是否为雷:是,设置雷为点开;否,自动点开每个非雷区;
- 判断是否满足条件:1.雷被点开,输;2.当前剩下格子数等于总雷数 N,赢;3.都不满足继续游戏
函数
- menu函数:获取玩家输入。
- game函数:操控进行一把游戏的整体逻辑。
- ini 函数:函数初始化并设置随机雷。
- show 函数:打印雷盘。
- Click :函数玩家点击。(实际是获取输入坐标XD)
- isOver 函数:判断游戏是否结束。
- extend 函数:递归扩展非雷区。
- cntBombs 函数 :雷区给周围区计数。
变量
Board 结构体 :用来存每一格的属性(包含是否点击,周围雷数)
Bombs : -1表示当前格子为炸弹,>=0即为周围8格炸弹数。
isClick: 1表示点开,0表示未点开。
typedef struct Board
{
int Bombs;
bool isClick;
}Board;
二、代码
1.头文件game.h 用来定义一些宏变量,声明函数,结构体。
#pragma once
#define ROW 10
#define COL 10
#define n 10 //炸弹数
#define WIN 1
#define LOSE -1
#define SPACE_Y " | "
#define SPACE_X "——-"
#define SPACE " "
#include <iostream>
using namespace std;
typedef struct Board
{
int Bombs;
bool isClick;
}Board;
void ini(Board board[ROW][COL], int N, int row, int col);//N为炸弹数
void show(Board board[ROW][COL], int row, int col);
void Click(Board board[ROW][COL], int& x, int& y, int row, int col,int& left_cell);//输入的坐标,剩余格子都需要外部保存
int isOver(Board board[ROW][COL], int x, int y, int left_cell, int N);
void extend(Board board[ROW][COL], int x, int y, int row, int col, int& left_cell);
void cntBombs(Board board[ROW][COL], int x, int y, int row, int col);
2.menu函数
void menu() {
int input;
do {
cout << "1.Start game"<<endl<<"0.Quit game"<<endl;
cin >> input;
switch (input)
{
case 1:
game();
break;
default:
break;
}
} while (input);
}
3.game函数
局部变量 用了 left_cells 表示剩余格子数,x ,y 表示输入坐标,res为游戏结果用来判定
用{0}给结构体数组board [ROW][COL] 初始化。
void game() {
//每把设置一个新时间种子
srand(time(NULL));
//局部变量
int x, y;//输入坐标
int res;//结果
int left_cells = ROW * COL;//记录当前剩余格子
//初始化
Board board[ROW][COL] = { 0 };
ini(board, n, ROW, COL);
show(board,ROW,COL);
//玩家回合
do {
Click(board,x,y, ROW, COL,left_cells);
show(board, ROW, COL);
res = isOver(board, x, y, left_cells, n);
} while (!res);//游戏未结束时循环
switch (res)
{
case 1:
cout << " WIN ! !" << endl;
break;
case -1:
cout<<" BOOM ! ! !"<<endl;
break;
//如果想在结束后展现所有炸弹,需要一开始记录一串炸弹坐标的数组
default:
break;
}
}
4.ini函数
初始化布局 X * Y,并随机生成 N 个炸弹,以当前雷为参数,cntBombs累加周围炸弹
//随机生成{ N }个炸弹布局{ X * Y}根据炸弹计算每格数字 (初始化)
void ini(Board board[ROW][COL], int N,int row,int col) {
//全部 memset为0,直接在外初始化了,貌似不太好
while (N--) {
//随机生成N个 x,y 雷 循环判断是否重复雷
int x, y;
do {
x = rand() % ROW;
y = rand() % COL;
} while (board[x][y].Bombs == -1);//如果随机到的xy是有炸弹的,重新生成
//没有重复 设置为雷
board[x][y].Bombs = -1;
//炸弹周围格子++
cntBombs(board, x, y, row, col);
}
}
5.show函数
//打印(渲染)
void show(Board board[ROW][COL],int row, int col) {
system("cls");
for (int i = -1; i < row; i++) {//-1是为了打印轴
for (int j = -1; j < col; j++) {
//打印横纵轴
//打印首行横坐标 第j列
if (i==-1) {
if (j==-1) {
cout << " ";//开头空两格
continue;
}
cout << SPACE << j << SPACE;//输出横坐标
continue;
}
//当j已经不是第一行,每行首打印纵坐标 第i行
if (j == -1) {
cout << i << " ";
continue;
}
//打印横纵轴↑
//判断是否点击 打印
Board tmpBoard= board[i][j];
if (tmpBoard.isClick) {
switch (tmpBoard.Bombs)
{
case -1://炸弹
cout <<SPACE_Y<< "X" << SPACE_Y;
break;
case 0://周围没有炸弹
cout << SPACE_Y << " " << SPACE_Y;
break;
default:
cout << SPACE_Y << tmpBoard.Bombs <<SPACE_Y;
break;
}
}
else
cout << SPACE_Y << "*"<<SPACE_Y;
}
cout << endl<< endl;
}
}
click函数
玩家输入一个坐标,直到合法,非炸弹就展开,雷设置点开。
// 玩家点击
void Click(Board board[ROW][COL],int &x,int &y, int row, int col,int& left_cell) {
//是否合法循环判断
while (1) {
cin >> y>> x;//实际游戏x,y是反的,所以调了顺序
//判断y,x是否合法
if (x <0 || x >= row || y < 0 || y >= col) {
cout << "非法坐标"<<endl;
continue;
}
if (board[x][y].isClick) {
cout << "重复了" << endl;
continue;
}
break;
}
//合法,当点击的坐标不是雷时,扩展
if (board[x][y].Bombs != -1)
extend(board, x, y, row, col,left_cell);
//是雷,把雷点开
else
board[x][y].isClick = 1;
}
isOver函数
//判定
int isOver(Board board[ROW][COL], int x, int y,int left_cell,int N) {
//输了,雷格子的状态为点击
if (board[x][y].isClick == 1 && board[x][y].Bombs == -1)
return LOSE;
//赢了,剩余格子数等于雷的数量——需要提前统计剩余格子数
if (left_cell == N)
return WIN;
//没输没赢,继续进行
return 0;
}
extend函数
递归打开非雷区,参考了c语言扫雷递归展开非雷位置tangke121的博客-CSDN博客
//扩展非雷
void extend(Board board[ROW][COL],int x,int y, int row, int col,int &left_cell) {
//因为是向外扩展的,有可能是炸弹,只有不是雷才能设置Click为1
//每次迭代,看有无点击过;
//不是炸弹就点开自己:有若干雷不扩展;周围0个雷时拓展;当自己是炸弹时 啥也不做
//是否点击过,点过跳过
if (board[x][y].isClick) return;
//没点过,且不是雷
if (board[x][y].Bombs >= 0) {
board[x][y].isClick = 1;
left_cell--;//剩余减少一格
//非雷向各方位拓开
if (!board[x][y].Bombs) {
for (int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
if (i >= 0 && i < row && j>=0 && j < col)
extend(board, i, j, row, col,left_cell);
}
}
}
}
//全部情况都跳过,说明是雷,啥也不用做
}
cntBombs函数
让雷的周围格子增加雷计数,遍历周围8个让其属性Bombs自增1。
//计数周围的雷
void cntBombs(Board board[ROW][COL], int x, int y, int row, int col) {
for(int i = x - 1; i <= x + 1; i++) {
for (int j = y - 1; j <= y + 1; j++) {
if (i == x && j == y) continue;//跳过自己 不能让自己++
if (i >= 0 && i < row && j >= 0 && j < col)//界限判断
board[i][j].Bombs++;//每个周围++
}
}
}
三、结果
这里我把雷数设置成了2,方便演示。
WIN
LOSE