首先亮一下自己做出的扫雷游戏界面效果:
还没来得急改exe文件的图标,刚下好ico文件,不过没有合适的,所以就还没换。
我做这个扫雷游戏的第一件事当然是想解决怎么让这个界面看起来像扫雷那个样子。我首先想到了按钮控件可以响应鼠标点击事件,所以就决定了使用MFC的对话框,用紧密排列的按钮来当做雷区,索性就拉了几个按钮在界面随意放着,然后Ctrl+c和Ctrl+v的凑够100个按钮。这样一阵随意操作之后,我急后悔了!太乱了,密密麻麻地,再怎么也要弄个10*10的方阵出来才像样嘛~所以干脆把按钮全Delete掉,规规矩矩地先放5个按钮进来,把按钮的文字都删掉,然后调整按钮大小成一个方形,将5个按钮选定布局一横排,之后再复制这5个到后面紧挨着放好,这样才弄好第一行雷区,接下来就10个10个的复制,对齐了。我想要是这个布局有网格的就好了,就不用我这样一排一排的弄了……
第二步就是做上图右边那一列的游戏信息,简单的就不用赘述了。
第三步嘛自然是在雷区安排雷的位置了。这就需要为这100个按钮安排序号以作区别,然后随机数序号的按钮下对应着一个雷。而我又打开该对话框的资源文件头文件Resource.h发现关于这100个按钮的ID设置是这样的:
#define IDC_BUTTON1 1000
#define IDC_BUTTON2 1001
#define IDC_BUTTON3 1002
……………………………………………………
……………………………………………………
#define IDC_BUTTON98 1097
#define IDC_BUTTON99 1098
#define IDC_BUTTON100 1099
所以我就干脆以1000-1099为序号,当然雷的标记是定义一个整数数组flg[100],flg[index]=0则不是雷,flg[index]=1则是雷,其中index+1000对应按钮的ID。
第四步了,需要为这100个按钮添加响应鼠标左键单击事件和右键单击事件,不想做得太复杂了,就没做鼠标左键双击和鼠标左右键同时点击的响应。当然只算鼠标左键和右键就已经是麻烦事了,比较要为100个按钮添加呢!百度了一下多个控件响应同一个事件的添加方法,其中左键单击有比较详细的方法说明,就是先添加一个按钮的左键单击事件,再改Mine_SweepDlg.cpp如下几句代码:
BEGIN_MESSAGE_MAP(CChusDlg, CDialog)
//{{AFX_MSG_MAP(CChusDlg)
…………
ON_BN_CLICKED(IDC_BUTTON1, OnButton1)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
将其中的 ON_BN_CLICKED(IDC_BUTTON1, OnButton1) 改为 ON_CONTROL_RANGE(BN_CLICKED,IDC_BUTTON1,IDC_BUTTON100,OnButton1) 即可实现100个按钮响应同一个鼠标左键单击事件函数OnButton1() 这只适用于ID连续的控件。
当然鼠标左键单击事件函数需要知道是哪一个ID的控件发生的,所以还需要传递一个ID参数,做下面几个位置的修改:
Mine_SweepDlg.h中afx_msg void OnButton1(UINT uID);加入括号的参数,再在Mine_SweepDlg.cpp的事件响应函数中加入参数void CMine_SweepDlg::OnButton1(UINT uID)其中的uID就是100个按钮的序号,而uID-1000就得到雷区标识的index了。
然后我将响应事件作出的效果如图:
即点击某一个方格,判断周边8个方格是否有雷标识,若没有则隐藏该按钮,若有则显示周边雷标识个数。若点击的这个方格就是累标识,则弹出消息框提示游戏结束。以下附上事件响应函数的代码:
void CMine_SweepDlg::OnButton1(UINT uID)
{
// TODO: Add your control notification handler code here
int index=(int)uID-1000;
int chack[9];
int i,j;
if(flg[index]==0)//如果该位置不是雷
{
chack_mine(index,chack);//获取试探方向
for(i=0,j=0;i
if(flg[chack[i+1]]==1)
j++;
}
if(j==0)//周边无雷则隐藏相关按钮
{
if(flg[index]!=2)
{
GetDlgItem(index+1000)->ShowWindow(FALSE);//隐藏该按钮
flg[index]=2;
m_score+=10;
}
for(i=0,j=0;i
int chac[9];
chack_mine(chack[i+1],chac);
for(int m=0,n=0;m
if(flg[chac[m+1]]==1)
n++;
}
if(n==0&&flg[chack[i+1]]!=2)
{
GetDlgItem(chack[i+1]+1000)->ShowWindow(FALSE);
flg[chack[i+1]]=2;
m_score+=10;
}
}
}
else//否则显示周边雷数量
{
CString bu;
bu.Format("%d",j);
SetDlgItemText(index+1000,bu);
m_score+=10;
}
}
else//左键单击了一个雷
{
SetDlgItemText(uID,"*");
m_minenum--;
mine[0]--;
m_score-=20;
if(MessageBox("你触雷了!游戏结束~","提示",MB_OKCANCEL)==IDOK)
{
KillTimer(1);//取消定时器
CDialog::OnOK();
}
}
}
以上是完成了左键单击的响应,至于右键单击的响应,比较麻烦了一点,需要重载一个虚函数PreTranslateMessage(MSG* pMsg)。说是该函数可以获取界面上的鼠标事件,包括了左键单击,左键双击,左键按下放开,右键单击等消息,然后可以根据消息来完成想要的功能。具体的重载方法是在类视图里右键单击Mine_SweepDlg,选择Add Virtual Function ,在弹出对话框中左边选择PreTranslateMessage,然后确定。然后就可以在CMine_SweepDlg.cpp里编辑使得响应控件的右键。以下是我的函数代码:
BOOL CMine_SweepDlg::PreTranslateMessage(MSG* pMsg)
{
// TODO: Add your specialized code here and/or call the base class
if(pMsg->message==WM_RBUTTONDOWN)//鼠标点击右键
{
CRect rc;
CPoint ptCursor;
GetCursorPos(&ptCursor);//获取鼠标的位置信息
int pos;
for(pos=1000;pos<1100;pos++)//IDC_BOUTTON1(1000)--IDC_BOUTTON100(1099)
{
GetDlgItem(pos)-> GetWindowRect(&rc);//获取控件的位置信息
if(PtInRect(&rc,ptCursor))//右键点击位置在某一个控件上
{
SetDlgItemText(pos,"*");
m_minenum--;
if(flg[pos-1000]==1)
mine[0]--;
}
}
GetDlgItem(1100)-> GetWindowRect(&rc);//获取开始按钮的位置信息
if(PtInRect(&rc,ptCursor))//右键点击位置在某一个控件上
{
for(int i=1;i<=mine[0];i++)
{
SetDlgItemText(mine[i]+1000,"*");
}
}
}
return CDialog::PreTranslateMessage(pMsg);
}
到此,按钮的鼠标左键响应,右键响应已经完成,接下来就是设置定时器刷新游戏用时和游戏得分。
第五步,设置定时器。具体方法如下:
1.双击资源对话框,对着对话框空白处右键,选择事件;
2.在左侧的新建windows消息/事件下选择WM_TIMER双击,然后确定;
3.在出现的void CMine_SweepDlg::OnTimer(UINT nIDEvent)中添加需要做的事
4.为开始按钮添加鼠标左键单击响应事件,添加SetTimer(1,1000,NULL);//启动定时器,1秒调用一次OnTimer函数。
以上就是我做这个扫雷游戏的大致过程了。其中遇到的各种问题,也让我学到不少东西,首先就是做一件事之前需要有一个科学合理有效的计划,以免中途出错重做;其次就是对于多个控件响应同一个事件的处理;再有就是处理鼠标消息的响应::PreTranslateMessage(MSG* pMsg)的使用。当然这个扫雷游戏还有许多需要改进的地方,比如隐藏周边无累的方格可以继续扩展隐藏,还有显示周边雷标识个数的形式可以用静态文本来代替该位置的按钮显示,等等……