一步一步实现扫雷游戏(C语言实现)(四)

此项目相关博文链接

一步一步实现扫雷游戏(C语言实现)(一)

一步一步实现扫雷游戏(C语言实现)(二)

一步一步实现扫雷游戏(C语言实现)(三)

一步一步实现扫雷游戏(C语言实现)(四)

 

  唉!说来就惭愧啊!几星期前我就开始使用win32写着个程序,但是到了中途碰到了许多钉子。以前我写了几篇博客记录以前写的代码,但是那是不成功的代码。今天我把扫雷游戏的基本过程实现了,下面一一介绍:

 

思维导图:



  1.首先说说初始状态时后台随机载入地雷分布的部分,我把这部分用类封装了:

class RandMap
{
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]。

  类的实现(不做介绍,请看注释):

View Code
//randmap.cpp
#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]中:

View Code
///////////////////////////////////////////////////////
//获取画格子的像素点并存储到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.现在来看看消息的响应

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
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()。实现代码如下:

View Code
///////////////////////////////
//
//画格子
//
//////////////////////////////
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 ()

View Code
///////////////////////////////////////
//
//响应左键事件
//
//////////////////////////////////////
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,则程序自动点开一片区域

函数实现分别如下:

View Code
/////////////////////////////////////////////////
//打印用户点击之后的地图
/////////////////////////////////////////////////
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);
}
}
}
}
View Code
//////////////////////////////////////////////////////////////////////////
//如果用户点开的位置上地雷的个数为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++混合编程的原因是,我封装了载入随机地雷分布图部分,而没有封装画图那部分,不知道合理不。下面是某些量的预定义

//DEF.H
/*
++++++++++++++++++++++++++++++++++++++
预定义
+++++++++++++++++++++++++++++++++++++++
*/


#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_ */
posted @ 2011-04-28 07:31  涵曦  阅读(4564)  评论(5编辑  收藏  举报