QQ头像连连看
1、程序说明
本程序名为QQ头像连连看,采用QQ头像为图片,利用MFC对话框程序完成连连看 游戏。
2、设计要求及实现功能
1、分级别的连连看。程序中分低、中、高三级模式,每一个级别响应的图片数目不同。
2、提示功能。当游戏者找不到可以消去的图片对时,可以从程序中得到提示,从而继 续进行游戏。
3、重新排列功能。当程序中图片已经没有可以消去的图片对时,提示游戏者可以点击
"重新排列"进行图片重排,从而可以继续进行图片。
4、程序具有计时功能。游戏者完成游戏应在规定时间范围内,否则应算为失败。不同 级别的游戏时间应不同。
三、概要设计
1、游戏设计思路
所谓连连看游戏,它就是在一个背景下有若干的图片,如果连续点击相同的图片, 并且可以用最多两折的线段连接,之间还没有其他障碍物,就可以消掉这一对图片, 如果所有的图片都被消掉,就可取得胜利。倘若在规定的时间内没有消完所有图片,则游戏失败。
2、图片显示设计
显示的图片必须要实现点击功能。因此本设计中把图片设计为一个由CBitmapButton派生出的自定义的按钮数组。可以实现显示图片及点击功能。
使每个自定义的按钮都有自己的ID 号,用来存储图片类型,还应该有一个CPoint 类型的成员变量,用来存储每个Button 的位置信息,最后在创建时,将和ID 号对应的图片贴到相应的位置上即可。
保证每种图片都是成对出现的:首先定位到第一个按钮的位置,随机选择一种图片, 生成这个按钮,然后随机选择一个位置,仍然放置这种图片,再定位到第二个按钮的位置,随机选择一种图片,生成这个按钮,然后再随机选择一个位置,如果这个位置已经有按钮了,则重新随机选择一个位置,直到选到一个空的位置,放置相同的图片,以此类推,将整个地图布完。
3、消去算法
如果连续点击相同的图片,并且可以用最多两折的线段连接,之间还没有其他障碍物,就可以消掉这一对图片。
根据此设计思路,可以分为三种情况来实现消去算法:
(1)两个图片在同行或同列 (2)两个图片可用带一个折线的线段连接
(3)两个图片可用带两个折线的线段连接。
4、流程图
总流程图
游戏过程流程
四、详细设计
1、数据结构
(1)主窗口类CMyLLKDlg
class CMyLLKDlg : public CDialog
{ ……
private:
int MAXX;
int MAXY;
int m_left;//显示图片时,左上、右下的坐标
int m_top;
int m_right;
int m_bottom;
bool m_flag;//标记是否按开始游戏
int m_NUM;
int m_index;
CPoint m_ptCross1;
CPoint m_ptCross2;
CPoint m_ptCross3;
CPoint m_ptCross4;
public:
int m_remainBtn;
int m_time;//记录倒计时时间
int m_typeNum; //图片种类数
int map[20][20]; //地图数组,存储图片类型
int m_BtnClkNum;
CPtr m_btnGroup; //Button 组
CPoint m_firstBtnPoint;
CPoint m_secondBtnPoint;
void Init(void);
void InitMap(int map[][20]);
void ShowMap(int map[][20]);
void ProcessGame(void);
bool FindHelp(void);
bool FindLine(CPoint p1, CPoint p2);
bool FindSide(CPoint p1, CPoint p2);
bool FindOneConner(CPoint p1, CPoint p2);
bool FindTwoConner(CPoint p1, CPoint p2);
afx_msg void OnTimer(UINT_PTR nIDEvent);
afx_msg void OnHelp();
afx_msg void OnRerange();
afx_msg void OnStartLow();
afx_msg void OnStartMid();
afx_msg void OnStartHig();
afx_msg void OnExit();
afx_msg void OnInstruc();
afx_msg void OnOpenmusic(); };
(2)CMyLLKApp类
(3)连连看按钮类CLLKButton
class CLLKButton : public CBitmapButton
{
DECLARE_DYNAMIC(CLLKButton)
public:
CLLKButton(int id,CPoint p);
virtual ~CLLKButton();
CPoint m_clkPoint;//保存点击的点位置
CPoint m_virPoint;
int m_ID;//标记每张图片的ID
int BitmapID;//位图的ID
BOOL IsClick;//判断是否被按下
protected:
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnLButtonDown(UINT nFlags, CPoint point); };
(4)游戏介绍窗口类CInstrcDlg
(5)关于窗口类CAboutDlg
2、主要函数
(1)CLLKButton::CLLKButton(int id,CPoint p)
CLLKButton::CLLKButton(int id,CPoint p)
{ this->m_clkPoint.x=p.x;
this->m_clkPoint.y=p.y;
this->m_ID=id;
BitmapID=m_ID+IDB_BITMAP1-1;//IDB_BITMAP1==130
m_virPoint.x=0;
m_virPoint.y=0;
IsClick=FALSE; }
(2)CLLKButton类成员函数:void OnLButtonDown(UINT nFlags, CPoint point)
void CLLKButton::OnLButtonDown(UINT nFlags, CPoint point)
{ CMyLLKDlg *parent=(CMyLLKDlg *)GetParent();
m_virPoint.x=m_clkPoint.x;
m_virPoint.y=m_clkPoint.y;
//this->ShowWindow(SW_HIDE);
IsClick=TRUE;
parent->m_BtnClkNum++;
if (parent->m_BtnClkNum==1)
{ parent->m_firstBtnPoint.x=m_virPoint.x;
parent->m_firstBtnPoint.y=m_virPoint.y; }
else if (parent->m_BtnClkNum==2)
{ parent->m_secondBtnPoint.x=m_virPoint.x;
parent->m_secondBtnPoint.y=m_virPoint.y;
if ((parent->FindLine(parent->m_firstBtnPoint,parent->m_secondBtnPoint)==TRUE||
parent->FindOneConner(parent->m_firstBtnPoint,parent->m_secondBtnPoint)||
parent->FindTwoConner(parent->m_firstBtnPoint,parent->m_secondBtnPoint))&&
(parent->map[parent->m_firstBtnPoint.x][parent->m_firstBtnPoint.y]==
parent->map[parent->m_secondBtnPoint.x][parent->m_secondBtnPoint.y]))
{
parent->map[parent->m_firstBtnPoint.x][parent->m_firstBtnPoint.y]=0;
parent->map[parent->m_secondBtnPoint.x][parent->m_secondBtnPoint.y]=0;
parent->m_remainBtn-=2;
parent->ProcessGame();
parent->m_BtnClkNum=0;
parent->m_firstBtnPoint=0;
parent->m_secondBtnPoint=0;
}
else
{ parent->m_BtnClkNum=1;
parent->m_firstBtnPoint.x=parent->m_secondBtnPoint.x;
parent->m_firstBtnPoint.y=parent->m_secondBtnPoint.y;
parent->m_secondBtnPoint=0; }
}
CBitmapButton::OnLButtonDown(nFlags, point); }
(3)主窗口构造函数
CMyLLKDlg::CMyLLKDlg(CWnd* pParent /*=NULL*/)
: CDialog(CMyLLKDlg::IDD, pParent)
, m_ptCross1(0)
{ m_hIcon = AfxGetApp()->LoadIcon(IDI_LLK);
m_index=0;
m_BtnClkNum=0;
m_firstBtnPoint=0;
m_secondBtnPoint=0;
m_time=0;
m_flag=0;
m_NUM=10; }
(4)CMyLLKDlg::OnPaint()
void CMyLLKDlg::OnPaint()//修改显示画面
{ CPaintDC dc(this);
CPen *pRedPen = new CPen;
pRedPen->CreatePen(PS_SOLID, 2, RGB(255,0,0));//设置字体颜色
CGdiObject *pOldPen = dc.SelectObject(pRedPen);//选择画笔
CFont font;
CString str;
str.Format("剩余时间: %d 秒",m_time);//m_time 中存储剩余时间信息
font.CreatePointFont(150,"宋体");//设置字体
dc.SelectObject(&font);
dc.SetTextColor(RGB(255,0,0));//设置字体颜色为红色
dc.SetBkColor(RGB(0,0,0));//设背景为黑色
//设置背景为透明,本例中即为黑色TRANSPARENT
dc.TextOut(500,10,str); //显示倒计时
…… }
(5)CMyLLKDlg::InitMap(int map[][20])
void CMyLLKDlg::InitMap(int map[][20])//初始化图片
{ int i,j;
int x,y;
int type;
srand((unsigned int)time(NULL));//随机数种子
for(i=0;i<MAXX;i++)//初始化,全部置零
for(j=0;j<MAXY;j++)
map[i][j]=0;
for(i=1;i<MAXX-1;i++)//设置各图片的类型,最外一圈不放图片
for(j=1;j<MAXY-1;j++)
{ if(map[i][j]!=0)
continue;
else
{ type=rand()%m_typeNum;//图片种类
map[i][j]=type+1;
do //随机产生x、y,放置相同图片
{ x = rand()%(MAXX-2)+1;
y = rand()%(MAXY-2)+1; }
while(map[x][y]);
map[x][y]=type+1;
}
}
}
(6)CMyLLKDlg::ShowMap(int map[][20])
void CMyLLKDlg::ShowMap(int map[][20])//显示图片
{ int i, j;
CPoint p;
CString str;
for(i=0;i<m_btnGroup.GetSize();i++)//每次开始时清除原有按钮
delete (CLLKButton *)m_btnGroup.GetAt(i);
m_btnGroup.RemoveAll();
for(i=1;i<=MAXX-2;i++)//添加新按钮*12
for(j=1; j<=MAXY-2; j++)
{ p.x = i;
p.y = j;
m_btnGroup.Add(new CLLKButton(map[i][j], p)); }
for(i=0;i<(MAXX-2)*(MAXY-2);i++)//显示按钮
{ CLLKButton *btn = (CLLKButton *)m_btnGroup.GetAt(i);
btn->Create(str, WS_CHILD|BS_BITMAP,
CRect(m_left+(i%(MAXY-2))*40, m_top+(i/(MAXY-2))*40,
m_right+(i%(MAXY-2))*40, m_bottom+(i/(MAXY-2))*40),this,
IDC_BLOCK+i);
if(btn->m_ID)//如果为则不显示
{ str.Format("res\\%d.bmp", btn->m_ID);
HBITMAP m_fkBmp = (HBITMAP)::LoadImage
(AfxGetInstanceHandle(),
str, IMAGE_BITMAP, 0, 0,
LR_CREATEDIBSECTION|LR_LOADFROMFILE);
//加载图片
if(m_fkBmp == NULL)
if (MessageBox ("缺少图片资源!", "错误",MB_ICONERROR|MB_OK)==IDOK)
return;
btn->SetBitmap(m_fkBmp);
btn->ShowWindow(SW_SHOW);
}
else
btn->ShowWindow(SW_HIDE);
}
}
(7)CMyLLKDlg::Init(void)
void CMyLLKDlg::Init(void)//开始按钮点击初始化
{ switch(m_index)
{
case 1:
MAXX=10;
MAXY=15;
m_left=90;
m_top=90;
m_right=130;
m_bottom=130;
m_typeNum=52;
m_time=300;
break;
case 2:
MAXX=12;
MAXY=17;
m_left=90;
m_top=90;
m_right=130;
m_bottom=130;
m_typeNum=75;
m_time=400;
break;
case 3:
MAXX=14;
MAXY=19;
m_left=50;
m_top=50;
m_right=90;
m_bottom=90;
m_typeNum=102;
m_time=520;
break;
default:
break;
}
m_flag=1;
m_NUM=10;
m_remainBtn=(MAXX-2)*(MAXY-2);//剩余的图片数目
}
(8)开始按钮中三个不同等级函数
void CMyLLKDlg::OnStartLow()//初级
{ m_index=1;
Init();
InitMap(map);
ShowMap(map);
SetTimer(1,1000,NULL);//开始计时 }
void CMyLLKDlg::OnStartMid()//中级
{ m_index=2;
Init();
InitMap(map);
ShowMap(map);
SetTimer(1,1000,NULL);//开始计时 }
void CMyLLKDlg::OnStartHig()//高级
{ m_index=3;
Init();
InitMap(map);
ShowMap(map);
SetTimer(1,1000,NULL);//开始计时 }
(9)主流程函数
void CMyLLKDlg::ProcessGame(void)
{ for (int i=1;i<MAXX-1;i++)
for (int j=1;j<MAXY-1;j++)
if (map[i][j]==0)
{
CLLKButton *pBtn=(CLLKButton *)m_btnGroup.GetAt((i-1)*(MAXY-2)+j-1);
pBtn->ShowWindow(SW_HIDE);
}
if (m_remainBtn==0)
{ MessageBox("恭喜您完成游戏!");
KillTimer(1); }
}
(10)查找可消去图片算法
①bool CMyLLKDlg::FindLine(CPoint p1, CPoint p2)//判断是否可以直连
{ int max,min;
int i;
if(p1.x!=p2.x||p1.y!=p2.y)//判断点击的是不是同一个点
{ if (p1.x==p2.x)//如果是同一个同一列上的
{ max=(p1.y>p2.y)?p1.y:p2.y;
min=(p1.y<p2.y)?p1.y:p2.y;
if (max==min+1)//判断是否是相邻的
return TRUE;
for (i=min+1;i<max;i++)//判断中间有没有图片隔着
if (map[p1.x][i]!=0)
return FALSE;
return TRUE;
}
if (p1.y==p2.y)
{ max=(p1.x>p2.x)?p1.x:p2.x;
min=(p1.x<p2.x)?p1.x:p2.x;
if (max==min+1)
return TRUE;
for (i=min+1;i<max;i++)
if (map[i][p1.y]!=0)
return FALSE;
return TRUE;
}
}
return FALSE; }
②bool CMyLLKDlg::FindSide(CPoint p1, CPoint p2)//游戏初期找在一边的图
{ int max, min;
int i;
BOOL line=TRUE;
BOOL col=TRUE;
if( (p1.x) == (p2.x) )
{ max = (p1.y>p2.y)?p1.y:p2.y;
min = (p1.y<p2.y)?p1.y:p2.y;
for(i=min; i<=max; i++) //上侧
{ if(map[p1.x-1][i]!=0)
{ line=FALSE;
break; }
}
if(line)
{ m_ptCross1.x = p1.x-1;
m_ptCross1.y = p1.y;
m_ptCross2.x = p1.x-1;
m_ptCross2.y = p2.y;
return TRUE; }
else
line=TRUE;
for(i=min;i<=max;i++)
{ if(map[p1.x+1][i]!=0) //下侧
{ line=FALSE;
break; }
}
if(line)
{ m_ptCross1.x = p1.x+1;
m_ptCross1.y = p1.y;
m_ptCross2.x = p1.x+1;
m_ptCross2.y = p2.y;
return TRUE; }
}
else
line=FALSE;
if( (p1.y) == (p2.y) )
{ max = (p1.x>p2.x)?p1.x:p2.x;
min = (p1.x<p2.x)?p1.x:p2.x;
for(i=min; i<=max; i++)
{ if(map[i][p1.y-1]!=0) //左侧
{ col=FALSE;
break; } }
if(col)
{ m_ptCross1.x = p1.x;
m_ptCross1.y = p1.y-1;
m_ptCross2.x = p2.x;
m_ptCross2.y = p2.y-1;
return TRUE; }
else
col=TRUE;
for(i=min; i<=max; i++)
{ if(map[i][p1.y+1]!=0) //右侧
{ col=FALSE;
break; } }
if(col)
{ m_ptCross1.x = p1.x;
m_ptCross1.y = p1.y+1;
m_ptCross2.x = p2.x;
m_ptCross2.y = p2.y+1;
return TRUE; }
}
else
col=FALSE;
if(line || col)
return TRUE;
else
return FALSE;
return false; }
③bool CMyLLKDlg::FindOneConner(CPoint p1, CPoint p2)
{ int maxx,maxy,minx,miny;
maxx=(p1.x>p2.x)?p1.x:p2.x;
minx=(p1.x<p2.x)?p1.x:p2.x;
maxy=(p1.y>p2.y)?p1.y:p2.y;
miny=(p1.y<p2.y)?p1.y:p2.y;
CPoint m_ptCross;
if(p1.x!=p2.x&&p1.y!=p2.y)//不同行也不同列
{ if (map[minx][maxy]==0)//如果角点被消去了
{ m_ptCross.x=minx;
m_ptCross.y=maxy;
if ((FindLine(p1,m_ptCross))&&(FindLine(m_ptCross,p2)))
return TRUE;
}
else if (map[maxx][miny]==0)
{ m_ptCross.x=maxx;
m_ptCross.y=miny;
if ((FindLine(p1,m_ptCross))&&(FindLine(m_ptCross,p2)))
return TRUE; }
else if (map[minx][miny]==0)
{ m_ptCross.x=minx;
m_ptCross.y=miny;
if ((FindLine(p1,m_ptCross))&&(FindLine(m_ptCross,p2)))
return TRUE; }
else if (map[maxx][maxy]==0)
{ m_ptCross.x=maxx;
m_ptCross.y=maxy;
if ((FindLine(p1,m_ptCross))&&(FindLine(m_ptCross,p2)))
return TRUE; }
}
return false; }
④bool CMyLLKDlg::FindTwoConner(CPoint p1, CPoint p2)
{ int i;
CPoint tempPoint1=0;
CPoint tempPoint2=0;
for (i=0;i<MAXY;i++)
{ if (i==p1.y||i==p2.y)
continue;
tempPoint1.x=p1.x;
tempPoint1.y=i;
if (map[tempPoint1.x][tempPoint1.y]==0)
if (FindLine(tempPoint1,p1))
{ tempPoint2.x=p2.x;
tempPoint2.y=tempPoint1.y;
if (map[tempPoint2.x][tempPoint2.y]==0)
if (FindLine(tempPoint2,tempPoint1)&&FindLine(tempPoint2,p2))
return TRUE;
}
}
for (i=0;i<MAXX;i++)
{ if (i==p1.x||i==p2.x)
continue;
tempPoint1.x=i;
tempPoint1.y=p1.y;
if (map[tempPoint1.x][tempPoint1.y]==0)
if (FindLine(tempPoint1,p1))
{ tempPoint2.x=tempPoint1.x;
tempPoint2.y=p2.y;
if (map[tempPoint2.x][tempPoint2.y]==0)
if (FindLine(tempPoint2,tempPoint1)&&FindLine(tempPoint2,p2))
return TRUE;
}
}
return FALSE; }
(11)定时器函数
void CMyLLKDlg::OnTimer(UINT_PTR nIDEvent)
{ m_time--;
if (m_time==0)
{ KillTimer(1);
MessageBox("时间到了,游戏失败!请重新开始!");
m_flag=0;
for (int i=1;i<MAXX-1;i++)
for (int j=1;j<MAXY-1;j++)
map[i][j]=0;
ProcessGame();
}
InvalidateRect(CRect(450,5,750,60));
CDialog::OnTimer(nIDEvent); }
(12)点击"提示一下"按钮响应函数
①void CMyLLKDlg::OnHelp()
{ if (m_flag)
{ if (m_NUM)
{ m_NUM--;
if(FindHelp())
ProcessGame();
else
MessageBox("没有可消的成对图片,请按“重新排列!”");
}
else
MessageBox("您本局的次提示已用完!");
}
else
MessageBox("您还没选择开始游戏!"); }
②bool CMyLLKDlg::FindHelp(void)
{ int x1,x2,y1,y2;
CPoint tempPoint1=0;
CPoint tempPoint2=0;
for (x1=1;x1<MAXX-1;x1++)
for (y1=1;y1<MAXY-1;y1++)
for (x2=1;x2<MAXX-1;x2++)
for (y2=1;y2<MAXY-1;y2++)
{ tempPoint1.x=x1;
tempPoint1.y=y1;
tempPoint2.x=x2;
tempPoint2.y=y2;
if ((map[tempPoint1.x][tempPoint1.y]==0||map[tempPoint2.x][tempPoint2.y]==0)||
(map[tempPoint1.x][tempPoint1.y]!=map[tempPoint2.x][tempPoint2.y])||
(x1==x2&&y1==y2))
continue;
if (FindLine(tempPoint1,tempPoint2)||FindOneConner(tempPoint1,tempPoint2)||
FindTwoConner(tempPoint1,tempPoint2))
{ CLLKButton *pBtn1=(CLLKButton *)m_btnGroup.GetAt((tempPoint1.x-1)*(MAXY-2)+
tempPoint1.y-1);
CLLKButton *pBtn2=(CLLKButton *)m_btnGroup.GetAt((tempPoint2.x-1)*(MAXY-2)+
tempPoint2.y-1);
pBtn1->SetButtonStyle(BS_DEFPUSHBUTTON);
pBtn2->SetButtonStyle(BS_DEFPUSHBUTTON);
return TRUE; }
}
return false; }
(13)点击"重新排列"按钮响应函数
void CMyLLKDlg::OnRerange()//没有可消得,重新排列
{ int x,y;
srand((unsigned int)time(NULL));
if(m_flag)
{ for(int i=MAXX-2;i>0;i--)
for (int j=MAXY-2;j>0;j--)
{ if(map[i][j]==0)
continue;
else
{ do
{ x=rand()%(MAXX-2)+1;
y=rand()%(MAXY-2)+1;
} while (map[x][y]!=0);
map[x][y]=map[i][j];
map[i][j]=0;
}
}
ShowMap(map);
}
else
MessageBox("您还没选择开始游戏!"); }
(14)程序说明以及推出按钮响应函数
①void CMyLLKDlg::OnInstruc()
{ CInstrcDlg ins;
ins.DoModal(); }
②void CMyLLKDlg::OnExit()
{ KillTimer(1);
OnCancel(); }
五、效果图
6、设计中问题及解决方案
1、提示时,怎样显示提示的两个图片,可以让游戏者看到。
2、当点击"重新排列"后,继续游戏当全部消去后,时间不会停止,也不会提示游戏者 已成功完成游戏。
七、未完成的问题
1、当点击重排后,消去所有图片,时间不会停止,也不会提示"全部消去"。
解决方法:
m_remainBtn=(MAXX-2)(MAXY-2)的赋值应该在MAXX和MAXY赋值 之后。
2、当点击可以消去的两个图片时,应该用线段显示两图片之间的连线。
本文来源:https://www.2haoxitong.net/k/doc/9b58b75bb8d528ea81c758f5f61fb7360b4c2ba3.html
文档为doc格式