一步一步实现扫雷游戏(C语言实现)(四)
此项目相关博文链接
唉!说来就惭愧啊!几星期前我就开始使用win32写着个程序,但是到了中途碰到了许多钉子。以前我写了几篇博客记录以前写的代码,但是那是不成功的代码。今天我把扫雷游戏的基本过程实现了,下面一一介绍:
思维导图:
1.首先说说初始状态时后台随机载入地雷分布的部分,我把这部分用类封装了:
{
private:
int map[MAX_X][MAX_Y];
int m, n;
int num_mines;
public:
//函数声明
RandMap();
void set_data(int in_m, int in_n, int in_num_mines);//使用函数参数改变类内部数据的接口
void get_map(int out_map[MAX_X][MAX_Y]);//获取随机地雷分布图
void set_mines(void);//分布地雷位置
int round_num_mines(int i,int j);//计算周围地雷个数
void rand_map(void);//生成随机分布地雷图
};
基本过程是:由主函数调用RandMap类中函数,获取地雷分布图map[i][j]。
主函数实现着过程的代码:
//生成随机地雷分布图map[i][j]
RandMap RMap;
RMap.set_data(m, n, num_mines);
RMap.rand_map();
RMap.get_map(map);
///////////////////////////////////////
这样就获得了随机分布地雷的数组map[i][j]。
类的实现(不做介绍,请看注释):
#include "randmap.h"
//构造函数
RandMap::RandMap()
{
memset(map, 0, sizeof(map));
}
/*********************************************************************
初始化地雷分布位置和个数
函数功能:根据设置的地雷个数和分布地图(map,数组)给出分布好了地雷的数组
函数原型:void set_mines(void)
*********************************************************************/
void RandMap::set_mines(void)
{
int num =1;
int i,j;
srand((unsigned)time(0));
while (num <= num_mines)
{
//rand()%n 取(0,n-1)的随机数
i = rand() % m +1;
j = rand() % n +1;
//如果出现相同的情况呢?,没事,再循环几次,直到有了足够的地雷为止
if (i<1|| i>m || j<1|| j>n || map[i][j] == MINE)
{
continue;
}
map[i][j] = MINE;
num++;//判断地雷个数
}
}
/****************************************************************************
返回周围地雷个数的函数
函数原型: int round_num_mines(int i,int j);
参 数: int i, int j为当前的坐标
返回值类型: int 返回该坐标处周围的地雷数
返回值情况:(1)返回1-8代表周围有1-8个地雷;
(2)返回0代表周围没有地雷;
(3)返回MINE代表此坐标时地雷;
******************************************************************************/
int RandMap::round_num_mines(int i,int j)
{
int dir[8][2] = {{-1,-1},{-1,0},{-1,1},{0,1},{1,1},{1,0},{1,-1},{0,-1}};
int k =0, mines =0; // round_num_mines 为周围地雷个数
if (map[i][j] == MINE)
{
return MINE;
}
for (k =0; k <8; k++)
{
if (map[i+dir[k][0]][j+dir[k][1]] == MINE)
{
mines++;
}
}
return mines;
}
/******************************************************************************
随机生成地图
函数原型: void rand_map(void)
******************************************************************************/
void RandMap::rand_map(void)
{
int i, j;
set_mines();
for (i =1; i <= m; i++)
{
for (j =1; j <= n; j++)
{
map[i][j] = round_num_mines(i, j);
}
}
}
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
设置m,n,num_mines
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void RandMap::set_data(int in_m, int in_n, int in_num_mines)
{
m = in_m;
n = in_n;
num_mines = in_num_mines;
}
/*+++++++++++++++++++++++++++++++++++++++++++++++++++++++++
获取地图
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
void RandMap::get_map(int out_map[MAX_X][MAX_Y])
{
int i, j;
for (i =1; i <= m; i++)
{
for (j =1; j <= n; j++)
{
out_map[i][j] = map[i][j];
}
}
}
2.画地图前的准备,确定窗口位置和地图中重要位置的像素点的坐标,先看确定窗口位置和大小的函数:
这个函数使用的是用指针做参数来改变变量的值,在C++中也可以使用指针的引用的。
//设置窗口位置和大小
//////////////////////////////////////////////
void set_position_size(int* p_x_position, int* p_y_position, int* p_x_size, int* p_y_size)
{
int width = GetSystemMetrics(SM_CXFULLSCREEN);//读取屏幕大小
int heigh = GetSystemMetrics(SM_CYFULLSCREEN);
* p_x_position = (int)width/3;
* p_y_position =(int)heigh/5;
* p_x_size = (int)width/3;
* p_y_size = (int)width/3;
}
int * p_x_position, int * p_y_position;确定窗口位置(左上角相对于屏幕的x,y坐标)
int * p_x_size, int * p_y_size;窗口大小
再来看看怎么将地图中每个格子的像素点的位置存储到数组 int pixel_x[MAX_X]和int pixel_y[MAX_Y]中:
//获取画格子的像素点并存储到pixel_x[i],pixel_y[j]中
///////////////////////////////////////////////////////
void get_pixel(HWND hwnd)
{
int x1, y1, x2, y2;//(x1,y1),(x2,y2)为窗口位置坐标
int x_position, y_position, x_size, y_size;
set_position_size(&x_position, &y_position, &x_size, &y_size);
int frame_width = GetSystemMetrics(SM_CXSIZEFRAME); //边框宽度
int caption_width = GetSystemMetrics(SM_CYCAPTION); //标题栏宽度
int menu_high = GetSystemMetrics(SM_CYMENU); //菜单高度
x1 = caption_width;
y1 = caption_width;
x2 = x_size - caption_width - frame_width;
y2 = y_size -2*caption_width - frame_width;
int i, j;
int tmp_x = x1;
int tmp_y = y1;
//将画竖向格子线的像素点位置存储到pixel_x[i]中
for (i =0; i <= m; i++)
{
pixel_x[i] = tmp_x;
tmp_x += (x2-x1)/m;
}
//将画横向格子线的像素点位置存储到pixel_y[j]中
for (j =0; j <= n; j++)
{
pixel_y[j] = tmp_y;
tmp_y += (y2-y1)/n;
}
}
代码中有注释,不在作解说。
有了地图中每个格子的像素点坐标,下面作图也就方便了
3.现在来看看消息的响应:
{
int x=LOWORD(lParam);//x,y为鼠标当前的位置坐标
int y=HIWORD(lParam);
switch (message)
{
case WM_PAINT: //画图消息
paint_map(hwnd);
return0;
case WM_LBUTTONDOWN: //单击鼠标左键消息
left_key(hwnd, x, y);
return0;
case WM_RBUTTONDOWN: //单击右键
// right_key(hwnd, x, y);
return0;
case WM_DESTROY:
PostQuitMessage (0);
return0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
WM_PAINT消息是用来载入初始状态下的格子图的(如下图)
paint_map(hwnd)这个函数是实现初始化载入地图(方格子)的函数,函数使用了
//画格子图
////////////////////////////////////////////////////////////
void paint_map(HWND hwnd)
{
HDC hdc;
PAINTSTRUCT ps;
hdc = BeginPaint (hwnd, &ps);
DrawGrid(hdc);//使用直线画格子的
EndPaint (hwnd, &ps);
}
初始态载入图我使用的是BeginPaint ()和BeginPaint ()
DrawGrid函数的实现使用的是API函数MoveToEx()和LineTo()。实现代码如下:
//
//画格子
//
//////////////////////////////
void DrawGrid(HDC hdc)
{
int i, j;
POINT ptLeftTop;
//画横线
for (i =0; i <= m; i++)
{
ptLeftTop.x = pixel_x[i];
ptLeftTop.y = pixel_y[0];
MoveToEx(hdc,ptLeftTop.x,ptLeftTop.y,NULL);
ptLeftTop.x = pixel_x[i];
ptLeftTop.y = pixel_y[n];
LineTo(hdc,ptLeftTop.x,ptLeftTop.y);
}
//画竖线
for (j =0; j <= n; j++)
{
ptLeftTop.x = pixel_x[0];
ptLeftTop.y = pixel_y[j];
MoveToEx(hdc,ptLeftTop.x,ptLeftTop.y,NULL);
ptLeftTop.x = pixel_x[m];
ptLeftTop.y = pixel_y[j];
LineTo(hdc,ptLeftTop.x,ptLeftTop.y);
}
}
WM_LBUTTONDOWN 是单击鼠标左键消息,单击左键说明点开此位置。
4.下面介绍响应左键事件部分(右键标记地雷部分还没实现,但是实现比左键简单,等下次实现了再发上来)
我定义看一个数组int user_map[MAX_X][MAX_Y] = {0};来存放某位置是否点开,user_map[i][j]等于1就说明点开。left_key()函数中画图的实现是使用了API函数GetDC ()和ReleaseDC ()
//
//响应左键事件
//
//////////////////////////////////////
void left_key(HWND hwnd, int x, int y)//x,y为鼠标当前的位置坐标
{
int tmp_x = x, tmp_y = y, i, j;
POINT lpPoint;
PAINTSTRUCT ps;
HDC hdc;
RECT rect;
for (i =0; i <= m; i++)
{
if (tmp_x > pixel_x[i] && tmp_x < pixel_x[i+1]) break;
}
for (j =0; j <= n; j++)
{
if (tmp_y > pixel_y[j] && tmp_y < pixel_y[j+1]) break;
}
hdc = GetDC (hwnd);
i=i+1;
j=j+1;
if (map[i][j] == MINE)
{
GetClientRect(hwnd, &rect);
DrawText (hdc, TEXT ("GAME OVER!"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
}
elseif (map[i][j] ==0&& user_map[i][j] == UNOPEN)
{
search0(i, j);
}
else
{
user_map[i][j] = OPEN;
}
print_user_map(hdc);
ReleaseDC (hwnd, hdc);
}
left_key()函数中出先了下面两个函数。
void print_user_map(HDC hdc);//打印用户点击之后的地图
void search0(int i, int j);//如果用户点开的位置上地雷的个数为0,则程序自动点开一片区域
函数实现分别如下:
//打印用户点击之后的地图
/////////////////////////////////////////////////
void print_user_map(HDC hdc)
{
RECT rect;
int i, j;
for (i =1; i <= m; i++)
{
for (j =1; j <= n; j++)
{
/***************************************************
left : 指定矩形框左上角的x坐标
top: 指定矩形框左上角的y坐标
right: 指定矩形框右下角的x坐标
bottom:指定矩形框右下角的y坐标
****************************************************/
rect.left = pixel_x[i-1];
rect.top = pixel_y[j-1];
rect.right = pixel_x[i];
rect.bottom = pixel_y[j];
if (user_map[i][j] == OPEN)
{
TCHAR str_tmp[10];
sprintf(str_tmp,"%d",map[i][j]);
DrawText (hdc, TEXT (str_tmp), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER);
}
}
}
}
//如果用户点开的位置上地雷的个数为0,则程序自动点开一片区域
//////////////////////////////////////////////////////////////////////////
void search0(int i, int j)
{
user_map[i][j] = OPEN; //点开(i,j)位置
int di, dj, k;
for (k =0; k <8; k++)
{
di = i + dir[k][0];
dj = j + dir[k][1];
if (di <1|| di > m || dj <1|| dj > n)
{
continue;
}
if (map[di][dj] !=0&& map[i][j] != MINE)
{
user_map[di][dj] = OPEN;
}
}
for (k =0; k <8; k++)
{
di = i + dir[k][0];
dj = j + dir[k][1];
if (di <1|| di > m || dj <1|| dj > n)
{
continue;
}
if (map[di][dj] ==0&& user_map[di][dj] == UNOPEN)
{
search0(di, dj);
}
}
}
print_user_map()函数是根据user_map的值来判断是否点开某位置。
search0()函数使用了搜索算法实现点开的位置是0就会点开一片区域。
最后,基本说完了,说这个是C/C++混合编程的原因是,我封装了载入随机地雷分布图部分,而没有封装画图那部分,不知道合理不。下面是某些量的预定义
/*++++++++++++++++++++++++++++++++++++++
预定义
+++++++++++++++++++++++++++++++++++++++*/
#ifndef _DEF_
#define _DEF_
#define DEF_M //默认行坐标
#define DEF_N //默认列坐标
#define MINE -1 //表示地雷
#define MAX_X 22 //最大x方向的格子数
#define MAX_Y 22 //最大y方向的格子数
#define UNOPEN 0 //没有点开
#define OPEN 1 //点开地雷
#define MARK 2 //标记地雷
#endif /* _DEF_ */
作者:涵曦(www.hanxi.cc)
出处:hanxi.cnblogs.com
GitHub:github.com/hanxi
Email:im.hanxi@gmail.com
文章版权归本人所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
《 Skynet 游戏服务器开发实战》
-
学习地址:
-
优惠推荐码:
2CZ2UA5u
-
可以先免费试学前 2 章内容