目录
2、在main() 函数中创建并初始化棋盘的规格和地雷的数目
1)、引入easyx.h头文件,创建好窗口并初始化,显示其信息
一、思路分析
每个格子有三种状态: 未打开, 标记, 未知,已打开
每个格子可能存放雷或者周围雷信息
考虑创建一个棋盘结构体,进行存储,当格子的状态为已打开时,其状态不可修改,否则可以修改,可以利用一个是否更改属性进行设置
初始化棋盘时可以将每个方格中的信息初始化为空格,表示其中没有任何东西,同时将其中的是否可以更改属性设置为真
布置雷的时候,利用随机数进行随机摆放
每个格子周围雷的信息可以通过一个循环对以其为中心的周围8个格子进行遍历求得
在打印时,若其为已打开,在显示其内存储的信息,若为未打开则以空格代替,若为标记则用 M 表示, 若为未知则用 ? 表示, 其中 空格 和 ?可以直接打开, M 则不可以直接打开,但可以通过标记将其变为 ?
考虑到用户可以自己定义雷的数目,棋盘的规格,可以利用动态二维数组进行操作,使用参数传递,进行棋盘规模的控制
二、实现
1、定义结构体
//棋盘数据结构
typedef struct Board
{
char sign; //存储信息
short visual; //显示的状态 0 不显示, 1 显示, 2 标记
bool state; //是否可以点击,true 表示可以, false 表示不可以
}Board;
2、在main() 函数中创建并初始化棋盘的规格和地雷的数目
int main()
{
int row = 10; //十行
int col = 10; //十列
int mine_count = 10; //十个雷
Board** board = NULL; //棋盘对象的定义
}
3、实现
1)、引入easyx.h头文件,创建好窗口并初始化,显示其信息
此处涉及到修改参数信息,所以其中的行,列,地雷数,采用地址传递
#include <easyx.h>
int ShowMenu(int* row, int* col, int* num)
{
//用与对应不同的模式将其返回
int value = 0;
//创建窗体,为600 * 600 规格
initgraph(600, 600, NULL);
//设置背景颜色为白色
setbkcolor(WHITE);
//刷新,使其可以正确显示
cleardevice();
//设置线的颜色为黑色
setlinecolor(BLACK);
//设置字体颜色为黑色
settextcolor(BLACK);
//设置字体大小为 宽60 高 40 样式为黑体
settextstyle(60, 40, L"黑体");
//可实现以下功能
//开始游戏
//设置参数
//查看记录
// 退出
//打印出菜单的元素
for (int i = 1; i <= 4; i++)
{
rectangle(100, 100 * i, 500, 100 * i + 80);
}
outtextxy(140, 110, L"开始游戏");
outtextxy(140, 210, L"设置参数");
outtextxy(140, 310, L"查看记录");
outtextxy(140, 410, L" 退出 ");
//创建消息接受的对象
ExMessage msg;
//循环获取消息
while (1)
{
//消息获取
msg = getmessage();
//当鼠标左键按下时,根据其位置的不同进入不同的页面
if (msg.message == WM_LBUTTONDOWN)
{
int x = msg.x;
int y = msg.y;
if (x >= 100 && x <= 500)
{
if (y > 100 && y < 180) //开始游戏
{
value = 1;
break;
}
else if (y > 200 && y < 280) //设置参数
{
value = 2;
break;
}
else if (y > 300 && y < 380) //查看记录
{
value = 3;
break;
}
else if (y > 400 && y < 480) //退出游戏
{
value = 0;
break;
}
}
}
}
//关闭窗口
closegraph();
return value;
}
2)、设置参数模块
void SetParam(int* row, int* col, int* num)
{
initgraph(600, 600, 0);
setbkcolor(WHITE);
cleardevice();
setlinecolor(BLACK);
settextcolor(BLACK);
settextstyle(60, 40, L"黑体");
for (int i = 1; i <= 4; i++)
{
rectangle(100, 100 * i, 500, 100 * i + 80);
}
outtextxy(140, 110, L"设置行数");
outtextxy(140, 210, L"设置列数");
outtextxy(140, 310, L"设置雷数");
outtextxy(140, 410, L"返回菜单");
ExMessage msg;
//用于获取用户输入
wchar_t s[10];
while (1)
{
msg = getmessage();
if (msg.message == WM_LBUTTONDOWN)
{
int x = msg.x;
int y = msg.y;
if (x >= 100 && x <= 500)
{
if (y > 100 && y < 180) //设置行数
{
//弹出窗口,获取用户输入
InputBox(s, 10, L"请输入行数");
*row = _wtoi(s);
}
else if (y > 200 && y < 280) //设置列数
{
InputBox(s, 10, L"请输入列数");
*col = _wtoi(s);
}
else if (y > 300 && y < 380) //设置雷数
{
InputBox(s, 10, L"请输入地雷数");
int c = _wtoi(s);
//有效性判断
if (c <= *row * *col)
{
*num = c;
}
else
{
MessageBox(NULL, L"输入地雷数目过大", L"错误", MB_ICONERROR);
}
}
else if (y > 400 && y < 480) //返回菜单
{
break;
}
}
}
}
closegraph();
}
3)、游戏管理模块
void Manage(Board** board, int* row, int* col, int* num)
{
int select = 0;
do
{
select = ShowMenu(row, col, num);
switch (select)
{
case 1:
//开始游戏
Game(board, *row, *col, *num);
break;
case 2:
//设置参数
SetParam(row, col, num);
break;
case 3:
//查看记录
ViewRecord(); /*目前尚未实现*/
break;
case 0:
//退出程序
break;
}
} while (select);
}
4)、游戏模块
void Game(Board** board, int row, int col, int num)
{
int retry = 0;
do
{
//初始化棋盘
board = InitBoard(board, row, col);
//判断棋盘初始化是否成功
if (!board)
{
MessageBox(NULL, L"游戏初始化失败", L"错误", MB_ICONERROR);
return;
}
//判断地雷数是否合理
if (row * col < num)
{
MessageBox(NULL, L"地雷过多,无法布雷", L"错误", MB_ICONERROR);
delete[] board;
closegraph();
return;
}
//布置地雷
setMine(board, row, col, num);
//设置地雷信息
GetMineNumber(board, row, col);
//打印棋盘,为了防止每次点击后,过多的创建窗口,将真正的游戏模块放入打印中
retry = Display(board, row, col, num);
//此时游戏已经运行结束,应当销毁堆中的棋盘
delete[] board;
} while (retry == IDRETRY);
}
5)、初始化棋盘
//为了防止太多级指针,在此处利用返回值返回初始化后的对象
Board** InitBoard(Board** board, int row, int col)
{
//实际上地图比可看到的地图规模要大所以创建一共row + 2行
board = new Board * [row + 2];
//如果创建失败,直接返回即可
if (!board)
{
return NULL;
}
//循环的创建每一行的元素
for (int i = 0; i < row + 2; i++)
{
board[i] = new Board[col + 2];
//若初始化失败,由于之前已经开辟了空间,所以需要释放空间,然后返回
if (!board[i])
{
delete[] board;
return NULL;
}
//初始化每一个元素为初始值
for (int j = 0; j < col + 2; j++)
{
board[i][j].sign = ' ';
board[i][j].state = true;
board[i][j].visual = 0;
}
}
//返回创建的棋盘
return board;
}
6)、布置地雷
//利用随机数,初始化雷的位置
void setMine(Board** board, int row, int col, int num)
{
//此处的num为值传递,在此处修改不会修改原值
while (num)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
//位置有效性判断
if (board[x][y].sign == ' ')
{
board[x][y].sign = '*';
num--;
}
}
}
7)、获取每个方格周围雷的信息
void GetMineNumber(Board** board, int row, int col)
{
//实际显示的地址为 1 ~ row
for (int i = 1; i < row + 1; i++)
{
for (int j = 1; j < col + 1; j++)
{
//若不是地雷对其值进行修改
if (board[i][j].sign != '*')
{
//对每一个方格周围的雷进行设置
board[i][j].sign = GetAroundMine(board, i, j);
}
}
}
}
char GetAroundMine(Board** const board, int x, int y)
{
//每一个方格周围的8个格子范围为 x - 1 ~ x + 1 , y - 1~ y + 1
char count = '0';
for (int i = x - 1; i < x + 2; i++)
{
for (int j = y - 1; j < y + 2; j++)
{
if (board[i][j].sign == '*')
{
count++;
}
}
}
return count;
}
8)、打印模块
由于此模块中包含实际的游戏逻辑,所以代码量较大
int Display(Board** board, int row, int col, int num)
{
int retry = 0;
int mine_num = num;
int n = row * col;
//每个格子占50 * 50个像素,并且前方有提示当前剩余雷数,因本人实在不会处理,所以提示空却,没有具体的实现,只有一个方格子
initgraph(row * 50, col * 50 + 100, 0);
//设置背景颜色为白色
setbkcolor(WHITE);
//刷新
cleardevice();
//设置线段颜色为黑色
setlinecolor(BLACK);
//用于提示的方格
rectangle(25, 25, 150, 75);
//画出方格
for (int i = 2; i < col + 2; i++)
{
line(0, i * 50, row * 50, i * 50);
}
for (int i = 0; i < row; i++)
{
line(i * 50, 100, i * 50, col * 50 + 100);
}
settextcolor(BLACK);
settextstyle(40, 40, L"黑体");
while (1)
{
bool t = GetLoad(board, row, col, &mine_num, &n);
for (int i = 1; i < row + 1; i++)
{
for (int j = 1; j < col + 1; j++)
{
settextcolor(BLACK);
if (board[i][j].visual == 1)
{
outtextxy(i * 50 - 45, j * 50 - 40 + 100, board[i][j].sign);
}
else if (board[i][j].visual == 2)
{
settextcolor(RED);
outtextxy(i * 50 - 45, j * 50 - 40 + 100, 'M');
}
else if (board[i][j].visual == 0)
{
outtextxy(i * 50 - 45, j * 50 - 40 + 100, ' ');
}
}
}
if (t == false)
{
retry = MessageBox(NULL, L"gameOver", L"提示", MB_RETRYCANCEL);
break;
}
if (n == num && mine_num == 0)
{
retry = MessageBox(NULL, L"胜利", L"提示", MB_OKCANCEL);
break;
}
}
closegraph();
return retry;
}
9)、加载模块实现
对每一此用户操作进行处理
bool GetLoad(Board** board, int row, int col, int* mine_num, int* n)
{
ExMessage msg;
msg = getmessage();
bool t = true;
//确定鼠标点击在有效的位置
if (msg.message == WM_LBUTTONDOWN && msg.y > 100)
{
int x = msg.x / 50 + 1;
int y = (msg.y - 100) / 50 + 1;
if (board[x][y].state == true)
{
//此处HowLoad为一个点击炸开的函数,再次过程中需要一直对n也就是剩余未显示的格子数进行修正
t = HowLoad(board, x, y, row, col, n);
}
}
else if (msg.message == WM_RBUTTONDOWN && msg.y > 100)
{
//右键,实现标记模块,每次标记之后,剩余地雷数将会减1,取消标记则会加1
int x = msg.x / 50 + 1;
int y = (msg.y - 100) / 50 + 1;
if (board[x][y].state && board[x][y].visual == 0)
{
board[x][y].visual = 2;
board[x][y].state = false;
(*mine_num)--;
}
else if (board[x][y].visual == 2)
{
board[x][y].visual = 0;
board[x][y].state = true;
(*mine_num)++;
}
}
return t;
}
10)、炸开模块的实现
//为了简化并且测试其他模块,便于处理和更改,此处利用单层蛇形循环的思路,爆开方格
bool HowLoad(Board** board, int x, int y, int row, int col, int* n)
{
if (board[x][y].sign == '*')
{
board[x][y].state = false;
board[x][y].visual = 1;
return false;
}
while (board[x][y].sign != '*' && board[x][y].visual == 0 && y < col + 1)
{
board[x][y].state = false;
board[x][y].visual = 1;
y++;
(*n)--;
}
y--, x++;
while (board[x][y].sign != '*' && board[x][y].visual == 0 && x < row + 1)
{
board[x][y].state = false;
board[x][y].visual = 1;
x++;
(*n)--;
}
x--, y--;
while (board[x][y].sign != '*' && board[x][y].visual == 0 && y > 0)
{
board[x][y].state = false;
board[x][y].visual = 1;
y--;
(*n)--;
}
y++, x--;
while (board[x][y].sign != '*' && board[x][y].visual == 0 && x < 0)
{
board[x][y].state = false;
board[x][y].visual = 1;
x--;
(*n)--;
}
return true;
}
11)、查看历史记录
//查看历史记录,因此处需要使用文件读写,以及vector容器进行存储,同时对其进行可视化界面处理,工作量较大,且用处不大,目前暂未实现
void ViewRecord()
{
/*尚未实现,请自行插入*/
}
三、整体代码
game.h
#pragma once
#include <easyx.h>
//棋盘数据结构
typedef struct Board
{
char sign; //存储信息
short visual; //显示的状态 0 不显示, 1 显示, 2 标记
bool state; //是否可以点击,true 表示可以, false 表示不可以
}Board;
Board** InitBoard(Board** board, int row, int col);
void Manage(Board** board, int* row, int* col, int* num);
void Game(Board** board, int row, int col, int num);
int ShowMenu(int* row, int* col, int* num);
void GetMineNumber(Board** board, int row, int col);
char GetAroundMine(Board** const board, int x, int y);
void setMine(Board** board, int row, int col, int num);
void SetParam(int* row, int* col, int* num);
void ViewRecord();
bool HowLoad(Board** board, int x, int y, int row, int col, int* num);
bool GetLoad(Board** board, int row, int col, int* mine_num, int* num);
int Display(Board** board, int row, int col, int num);
game.cpp
#include "game.h"
/// <summary>
/// 初始化棋盘
/// </summary>
/// <param name="board">传入的棋盘指针(二级)</param>
/// <param name="row">行数(实际大2)</param>
/// <param name="col">列数(实际大2)</param>
/// <returns>创建是否成功</returns>
Board** InitBoard(Board** board, int row, int col)
{
board = new Board * [row + 2];
if (!(board))
{
return NULL;
}
for (int i = 0; i < row + 2; i++)
{
(board)[i] = new Board[col + 2];
if (!board[i])
{
delete[] board;
return NULL;
}
for (int j = 0; j < col + 2; j++)
{
(board[i][j]).sign = ' ';
(board[i][j]).state = true;
(board[i][j]).visual = 0;
}
}
return board;
}
int ShowMenu(int* row, int* col, int* num)
{
initgraph(600, 600, 0);
setbkcolor(WHITE);
cleardevice();
setlinecolor(BLACK);
settextcolor(BLACK);
settextstyle(60, 40, L"黑体");
//开始游戏
//设置参数
//查看记录
// 退出
for (int i = 1; i <= 4; i++)
{
rectangle(100, 100 * i, 500, 100 * i + 80);
}
outtextxy(140, 110, L"开始游戏");
outtextxy(140, 210, L"设置参数");
outtextxy(140, 310, L"查看记录");
outtextxy(140, 410, L" 退出 ");
ExMessage msg;
while (1)
{
msg = getmessage();
if (msg.message == WM_LBUTTONDOWN)
{
int x = msg.x;
int y = msg.y;
if (x >= 100 && x <= 500)
{
if (y > 100 && y < 180) //开始游戏
{
closegraph();
return 1;
}
else if (y > 200 && y < 280) //设置参数
{
closegraph();
return 2;
}
else if (y > 300 && y < 380) //查看记录
{
closegraph();
return 3;
}
else if (y > 400 && y < 480) //退出游戏
{
closegraph();
return 0;
}
}
}
}
}
void Manage(Board** board, int* row, int* col, int* num)
{
int select = 0;
do
{
select = ShowMenu(row, col, num);
switch (select)
{
case 1:
Game(board, *row, *col, *num);
break;
case 2:
SetParam(row, col, num);
break;
case 3:
break;
case 0:
break;
}
} while (select);
}
void Game(Board** board, int row, int col, int num)
{
int retry = 0;
do
{
//初始化棋盘
board = InitBoard(board, row, col);
//判断棋盘初始化是否成功
if (!board)
{
MessageBox(NULL, L"游戏初始化失败", L"错误", MB_ICONERROR);
return;
}
//判断地雷数是否合理
if (row * col < num)
{
MessageBox(NULL, L"地雷过多,无法布雷", L"错误", MB_ICONERROR);
delete[] board;
closegraph();
return;
}
//布置地雷
setMine(board, row, col, num);
//设置地雷信息
GetMineNumber(board, row, col);
//打印棋盘,为了防止每次点击后,过多的创建窗口,将真正的游戏模块放入打印中
retry = Display(board, row, col, num);
//此时游戏已经运行结束,应当销毁堆中的棋盘
delete[] board;
} while (retry == IDRETRY);
}
/// <summary>
/// 获取所有格子周围的雷的数量
/// </summary>
/// <param name="board">棋盘对象</param>
/// <param name="row">行</param>
/// <param name="col">列</param>
void GetMineNumber(Board** board, int row, int col)
{
for (int i = 1; i < row + 1; i++)
{
for (int j = 1; j < col + 1; j++)
{
if (board[i][j].sign != '*')
{
board[i][j].sign = GetAroundMine(board, i, j);
}
}
}
}
/// <summary>
/// 获取单个地址周围雷的数量
/// </summary>
/// <param name="board">棋盘</param>
/// <param name="x">横坐标</param>
/// <param name="y">纵坐标</param>
/// <returns>雷的数量</returns>
char GetAroundMine(Board** const board, int x, int y)
{
char count = '0';
for (int i = x - 1; i < x + 2; i++)
{
for (int j = y - 1; j < y + 2; j++)
{
if (board[i][j].sign == '*')
{
count++;
}
}
}
return count;
}
/// <summary>
/// 布置雷
/// </summary>
/// <param name="board">棋盘对象</param>
/// <param name="row">行数</param>
/// <param name="col">列数</param>
/// <param name="num">地雷数目</param>
void setMine(Board** board, int row, int col, int num)
{
while (num)
{
int x = rand() % row + 1;
int y = rand() % col + 1;
if (board[x][y].sign == ' ')
{
board[x][y].sign = '*';
num--;
}
}
}
void SetParam(int* row, int* col, int* num)
{
initgraph(600, 600, 0);
setbkcolor(WHITE);
cleardevice();
setlinecolor(BLACK);
settextcolor(BLACK);
settextstyle(60, 40, L"黑体");
for (int i = 1; i <= 4; i++)
{
rectangle(100, 100 * i, 500, 100 * i + 80);
}
outtextxy(140, 110, L"设置行数");
outtextxy(140, 210, L"设置列数");
outtextxy(140, 310, L"设置雷数");
outtextxy(140, 410, L"返回菜单");
ExMessage msg;
wchar_t s[10];
while (1)
{
msg = getmessage();
if (msg.message == WM_LBUTTONDOWN)
{
int x = msg.x;
int y = msg.y;
if (x >= 100 && x <= 500)
{
if (y > 100 && y < 180) //设置行数
{
InputBox(s, 10, L"请输入行数");
*row = _wtoi(s);
}
else if (y > 200 && y < 280) //设置列数
{
InputBox(s, 10, L"请输入列数");
*col = _wtoi(s);
}
else if (y > 300 && y < 380) //设置雷数
{
InputBox(s, 10, L"请输入地雷数");
int c = _wtoi(s);
if (c <= *row * *col)
{
*num = c;
}
else
{
MessageBox(NULL, L"输入地雷数目过大", L"错误", MB_ICONERROR);
}
}
else if (y > 400 && y < 480) //返回菜单
{
break;
}
}
}
}
closegraph();
}
void ViewRecord()
{
}
bool HowLoad(Board** board, int x, int y, int row, int col, int* n)
{
if (board[x][y].sign == '*')
{
board[x][y].state = false;
board[x][y].visual = 1;
return false;
}
while (board[x][y].sign != '*' && board[x][y].visual == 0 && y < col + 1)
{
board[x][y].state = false;
board[x][y].visual = 1;
y++;
(*n)--;
}
y--, x++;
while (board[x][y].sign != '*' && board[x][y].visual == 0 && x < row + 1)
{
board[x][y].state = false;
board[x][y].visual = 1;
x++;
(*n)--;
}
x--, y--;
while (board[x][y].sign != '*' && board[x][y].visual == 0 && y > 0)
{
board[x][y].state = false;
board[x][y].visual = 1;
y--;
(*n)--;
}
y++, x--;
while (board[x][y].sign != '*' && board[x][y].visual == 0 && x < 0)
{
board[x][y].state = false;
board[x][y].visual = 1;
x--;
(*n)--;
}
return true;
}
bool GetLoad(Board** board, int row, int col, int* mine_num, int* n)
{
ExMessage msg;
msg = getmessage();
bool t = true;
if (msg.message == WM_LBUTTONDOWN && msg.y > 100)
{
int x = msg.x / 50 + 1;
int y = (msg.y - 100) / 50 + 1;
if (board[x][y].state == true)
{
t = HowLoad(board, x, y, row, col, n);
}
}
else if (msg.message == WM_RBUTTONDOWN && msg.y > 100)
{
int x = msg.x / 50 + 1;
int y = (msg.y - 100) / 50 + 1;
if (board[x][y].state && board[x][y].visual == 0)
{
board[x][y].visual = 2;
board[x][y].state = false;
(*mine_num)--;
}
else if (board[x][y].visual == 2)
{
board[x][y].visual = 0;
board[x][y].state = true;
(*mine_num)++;
}
}
return t;
}
int Display(Board** board, int row, int col, int num)
{
int retry = 0;
int mine_num = num;
int n = row * col;
//每个格子占50 * 50个像素
initgraph(row * 50, col * 50 + 100, 0);
//设置背景颜色为白色
setbkcolor(WHITE);
//刷新
cleardevice();
//设置线段颜色为黑色
setlinecolor(BLACK);
rectangle(25, 25, 150, 75);
//画出方格
for (int i = 2; i < col + 2; i++)
{
line(0, i * 50, row * 50, i * 50);
}
for (int i = 0; i < row; i++)
{
line(i * 50, 100, i * 50, col * 50 + 100);
}
settextcolor(BLACK);
settextstyle(40, 40, L"黑体");
while (1)
{
bool t = GetLoad(board, row, col, &mine_num, &n);
for (int i = 1; i < row + 1; i++)
{
for (int j = 1; j < col + 1; j++)
{
settextcolor(BLACK);
if (board[i][j].visual == 1)
{
outtextxy(i * 50 - 45, j * 50 - 40 + 100, board[i][j].sign);
}
else if (board[i][j].visual == 2)
{
settextcolor(RED);
outtextxy(i * 50 - 45, j * 50 - 40 + 100, 'M');
}
else if (board[i][j].visual == 0)
{
outtextxy(i * 50 - 45, j * 50 - 40 + 100, ' ');
}
}
}
if (t == false)
{
retry = MessageBox(NULL, L"gameOver", L"提示", MB_RETRYCANCEL);
break;
}
if (n == num && mine_num == 0)
{
retry = MessageBox(NULL, L"胜利", L"提示", MB_OKCANCEL);
break;
}
}
closegraph();
return retry;
}
main.cpp
#include "game.h"
#include <ctime>
int main()
{
srand((unsigned int)time(NULL));
Board** board = NULL; //棋盘对象
int row = 10; //行
int col = 10; //列
int num = 10; //地雷数目
Manage(board, &row, &col,&num);
return 0;
}
四、最终运行效果