📄 vc++saoleigame.txt
字号:
}
//显示黑框里的数字
int nOldDC=pDC->SaveDC();
pDC->SetTextColor(RGB(255,0,0));
pDC->SetBkColor(RGB(0,0,0));
CFont font;
if(0==font.CreatePointFont(160,"Comic Sans MS"))
{
AfxMessageBox("Can't Create Font");
}
pDC->SelectObject(&font);
CString str;
//利用判断显示位数,不够三位前面加0
if(leftnum<10)
str.Format("00%d",leftnum);
else
str.Format("0%d",leftnum);
pDC->TextOut(25,10,str);
if(second<10)
str.Format("00%d",second);
else if(second<100)
str.Format("0%d" ,second);
else
str.Format("%d" ,second);
pDC->TextOut(330,10,str);
pDC->RestoreDC(nOldDC);
}
运行一下,外观已经出来了,只是还不能玩。那我们就来添加一些功能函数,使它可以玩。
当然,如果你对程序已经有一定的经验的话,你就会指出上面的函数太长了。这并不太符合我们编程的要求。我们编程有一个讲究,就是尽量使函数的代码少,一般为一页左右,便于查看。那么,我们可以把上面的函数细分为几个小函数,然后在这个函数里面分别调用。
按下鼠标左键:
用if语句判断,如果在按钮上面,则显示按钮按下位图;如果在扫雷区,先把按钮位图改为张口位图,再判断按下的是否是雷,是就结束,重画,以显示所有的雷;否则,重画相应格子以显示数字。
void CMy2_1View::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
//获取指针pdc
CDC *pDC=GetDC();
CDC Dc;
if(Dc.CreateCompatibleDC(pDC)==FALSE)
AfxMessageBox("Can't create DC");
//显示按下按钮
if(point.x>180&&point.x<210&&point.y>10&&point.y<40)
{
Dc.SelectObject(m_anniu[3]);
pDC->BitBlt(180,10,160,160,&Dc,0,0,SRCCOPY);
}
if((point.x>=10)&&(point.x<=385)&&(point.y>=50)&&(point.y<=290))
{
if(jieshu==1)
return;
//显示张口按钮
Dc.SelectObject(m_anniu[1]);
pDC->BitBlt(180,10,160,160,&Dc,0,0,SRCCOPY);
// secondstart为1时计时有效
secondstart=1;
//鼠标坐标转换为数组坐标
int a=(point.x-10)/15;
int b=(point.y-50)/15;
if(lei[a][b].weitu==0||lei[a][b].weitu==3)
{
if(lei[a][b].shumu==-1)
{
jieshu=1;
//结束时,释放Timer
KillTimer(1);
//重画,因为这次重画将显示全部的雷,
//不能用部分重画
Invalidate();
}
else
{
lei[a][b].weitu=1;
CRect rect;
rect.left=a*15+10;
rect.right=a*15+25;
rect.top=b*15+50;
rect.bottom=b*15+65;
InvalidateRect(&rect);
}
}
} CView::OnLButtonDown(nFlags, point);
}
如果你现在运行的话,你会发现按下按钮时并不还原,这就涉及到鼠标函数:OnLButtonUp(UINT nFlags, CPoint point)
松开鼠标左键:
松开左键时,显示按钮没有按下的位图;再判断,如果结束,就要显示失败的位图;另外,如果是在按钮上松开按钮,即表示我们已经按下了重新开始的按钮,必须调用重新开始函数OnStart()。
由于OnStart()函数是与菜单里的开始共有的,此处先保留不说,若有必要运行,可以先去掉最后两行。
void CMy2_1View::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CDC *pDC=GetDC();
CDC Dc;
if(Dc.CreateCompatibleDC(pDC)==FALSE)
AfxMessageBox("Can't create DC");
//显示按钮
Dc.SelectObject(m_anniu[0]);
pDC->BitBlt(180,10,160,160,&Dc,0,0,SRCCOPY);
if(jieshu==1)
{
//显示按扭位图
Dc.SelectObject(m_anniu[2]);
pDC->BitBlt(180,10,160,160,&Dc,0,0,SRCCOPY);
}
//如果按下的是按扭,重新开始
if(point.x>180&&point.x<210&&point.y>10&&point.y<40)
OnStart();
CView::OnLButtonUp(nFlags, point);
}
按下鼠标右键:
如果是雷,我们按右键,显示旗子,并减少一个剩下雷数;如果我们认为那旗子的格子不是雷,我们按右键,显示问号,并在剩下雷数加上1。有关函数:
void CMy2_1View::OnRButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
//结束,返回
if(jieshu==1)
return;
if((point.x>=10)&&(point.x<=385)&&(point.y>=50)&&(point.y<=290))
{
int a=(point.x-10)/15;
int b=(point.y-50)/15;
if(lei[a][b].weitu==0||lei[a][b].weitu==3)
{
lei[a][b].weitu=2;
leftnum--;
}
else
if(lei[a][b].weitu==2)
{
lei[a][b].weitu=3;
leftnum++;
}
//重画剩下雷数
CRect rect2;
rect2.left=20;
rect2.right=70;
rect2.top=10;
rect2.bottom=40;
InvalidateRect(&rect2);
//重画打击格子
CRect rect;
rect.left=a*15+10;
rect.right=a*15+25;
rect.top=b*15+50;
rect.bottom=b*15+65;
InvalidateRect(&rect);
}
CView::OnRButtonDown(nFlags, point);
}
显示没有雷的区域:
运行,玩一下,你会发现当按下的是一个周围没有雷的格子是它并不会象Window里面的扫雷游戏一样显示它周围的格子雷数。怎么实现呢?
添加一个如下函数:
//扫描,如果是已经被按下且雷数为0,显示它周围的八个格,并重画
void CMy2_1View::leizero()
{
for(int i=0;i<m_RowCount;i++)
for(int j=0;j<m_ColCount;j++)
if(lei[i][j].shumu==0&&lei[i][j].weitu==1)
{
for(int n=i-1;n<i+2;n++)
for(int m=j-1;m<j+2;m++)
if(n>=0&&n<25&&m>=0&&m<m_ColCount)
if(lei[n][m].shumu!=-1&&lei[n][m].weitu==0)
{
lei[n][m].weitu=1;
CRect rect;
rect.left=n*15+10;
rect.right=n*15+25;
rect.top=m*15+50;
rect.bottom=m*15+65;
InvalidateRect(&rect);
}
}
}
再运行,效果是有的,只是它只显示一部分,即这个周围的几个。那么我们应该怎样使它显示全部呢?可以利用计时器函数。
计时器函数:
OnTimer(UINT nIDEvent)函数,同时也可以实现计时显示。添加OnCreate(LPCREATESTRUCT lpCreateStruct)和 OnTimer(UINT nIDEvent):
int CMy2_1View::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: Add your specialized creation code here
//20次为一秒
SetTimer(1,50,NULL);
return 0;
}
void CMy2_1View::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
//结束,返回
if(jieshu==1)
return;
//显示个数为0的方格
leizero();
//计时
if(secondstart>0)
secondstart++;
//二十次为一秒
if(secondstart==20)
{
secondstart=1;
second++;
//重画时间
CRect rect3;
rect3.left=325;
rect3.right=375;
rect3.top=10;
rect3.bottom=40;
InvalidateRect(&rect3);
}
CView::OnTimer(nIDEvent);
}
扫雷游戏就这样就是了。下面是附加内容,它将说明菜单的添加和重新开始函数的算法。
5. 附加内容
修改菜单:
游戏已经可以玩了,只是点到雷之后就完了,无法重新开始。还有,菜单还没有改。下面就修改菜单并实现重新开始功能:
把菜单改为如下图2-3。
图2-3
并在View()函数中按下图添加OnStart()函数(图2-4):
图2-4
开始函数:
OnStart()函数其实只是构造函数的再版。
void CMy2_1View::OnStart()
{
SetTimer(1,50,NULL);
// TODO: Add your command handler code here
second=0;//计时
secondstart=0;//1时开始计时
// num=0;
leftnum=leinum;//剩余雷数
jieshu=0;//jieshu=1时停止
int aa=0;
//初始化0
for(int i=0;i<m_RowCount;i++)
{
for(int j=0;j<m_ColCount;j++)
{
lei[i][j].shumu=0;
lei[i][j].weitu=0;
}
}
//设置leinum个雷
do
{
int k=rand()%m_RowCount;
int l=rand()%m_ColCount;
if(lei[k][l].shumu!=-1)
{
lei[k][l].shumu=-1;
aa++;
}
}while(aa!=leinum);
//给方格赋值
for(int a=0;a<m_RowCount;a++)
for(int b=0;b<m_ColCount;b++)
if(lei[a][b].shumu==0)
{
for(int c=a-1;c<a+2;c++)
for(int d=b-1;d<b+2;d++)
if(c>=0&&c<m_RowCount&&d>=0&&d<m_ColCount)
if(lei[c][d].shumu==-1)
lei[a][b].shumu++;
}
Invalidate();
}
这样,整个程序完成了。
6. 小结
当然,这个游戏比Window自带的简单。但就目前来说,离它其实也不远。添加菜单项,并相应修改参数值:m_RowCount、 m_ColCount、leinum,并重新初始化界面就行了。
本书的例子都只是一些最基本的游戏算法,它教你怎样去实现游戏,而至于游戏的扩展,我只是提一些建议,让你自己去实现。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -