组员:李力 谭超
作业要求:编写一个连连看的小游戏,在写这个游戏的时候,尝试着用MFC去做界面。
需要考虑的主要问题是:
开始制作游戏时,主要要解决的问题有以下几个方面:如何设置整个游戏的界面;如何控制连连看游戏中随机图片的生成且每种图片必须为偶数个;游戏开始后,判断鼠标两次点击的图片能否消去,即图片是否相同且图片之间路径的判断.
开发环境:
Windows 7
Microsoft Vs 2010
游戏功能如下:
关于连连看的功能描述如下:运行游戏并进行初始化工作,将整个游戏区域分成纵向和横向扩展的若干个小方块,并且这些小方块是由多种动物图案成对地分布于游戏区域的不同位置。玩家可以通过选取相同的两个物件来对它们进行消除的操作,直到将游戏区域中的所有方块对都被消除后为胜利。
游戏的整体运行效果如图:
数据初始的问题:
StartNewGame()函数里面v
在游戏进行初始化的过程中,应该先对整个地图中的各个区域做必要的初始化操作,将它们的状态设置为BLANK_START空白方块状态(无动物图案方块),关于BLANK_START空白方块状态的定义,跟其他动物方块的物种定义表达类似,也是用整数ID来对它进行标识,不过不同的是,由于他代表该方块区域无图案,所以这里用-1的宏值来表示,具体定义如下:
#define BLANK_STATE -1 //空方块(没有任何动物)
可以看到,对图案方块的布局,先用srand()函数对时间函数布下随机种子,然后调用rand()函数对具体的图案方块的种类进行随机的获取。在这里需要引入一个临时地图tmpMap,该临时地图的大小与内核数据地图的大小一致,并且先添置好4组完全一样的图案类型ID数据(0~(m_nCol*m_nRow)/4),然后再将已经安放在tmpMap中的图案作随机抽取,并放到内核地图数据中去,将取出的元素从tmpMap中除去。
连连看方块连接问题:
对于选中的两个方块的销毁,他们必须符合下面3个条件:
(1)选中的两个方块图案相同。
(2)选中的两个方块之间没有障碍物阻碍的情况下,可以用若干个垂直的直线线段连接起来。
(3)这些将它们连接起来的直线线段的折点不超过两个(连接线由x轴和y轴的平行线组成)。
现在针对(2)和(3)进行分析,如图1.3。
如图1.3所示可知道,同种物件的连接方式大致可以分成以下3种:
(1) 直接方式
(2) 有一个折点的垂直线段连接。
(3) 有两个折点的垂直线段连接.。
1. 直接连接方式
在直接连接方式中,必须要求所选定的两个方块在同一水平直线上(可以为x方向或y方向),并且两个方块之间没有任何其他图案方块。
一个这点连接方式
所选定的两个方块如果通过折点的方式连接,那么对于折点来说,每个折点必定有且至少有一个坐标(x或y)是和其中一个目标点相同的,即折点必定在两个目标点所在的x方向或y方向的直线上。
此外,对于一个折点连接的情况,折点应该为第一个选中方块的横向线或纵向线与第二个选中方块的纵向线和横向线相交而得出。
3.两个折点的连接方式
这种方式的两个折点所连成的直线与两物件的直接连线可以构成平行线,因此可以根据这个规律,将这条水平线在游戏区域允许的条件上下移动,然后通过判断整条带垂直折线点的曲线之间有无障碍物方式来确定是否可以连同。这种情况可以分为两种情况:
(1)选中的两图案方块在同一直线,两折点间的直连线可在其这两个方块之间的空间位置作移动,其约束是不超过游戏边界区域。
(2)选中的两图案方块不在同一直线,两折点间的直连线可在两个方块之间的空间位置作移动,其约束是两方块之间的区域。
经过上面详细的分析后,可以对选定的两方块是否可以作抵消操作可以这样设计下去。
首先,对简单的直接连情况进行判断,看其是否符合条件,假如不能,再加深一个级别的复杂度,对一个折点的情况进行判断,依次类推。
游戏截图:
游戏主代码:
#include "stdafx.h"
#include "LLS_LLK.h"
#include "LLS_lg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/
// C_LLK_Dlg dialog
#define BKCOLOR RGB(128,128,128) //背景颜色
#define FRONTWIDTH (39+2) //前面方块的宽度
#define FRONTHEIGHT (39+12) //前面方块的高度
#define BKWIDTH 46 //背景方块的宽度
#define BKHEIGHT 56 //背景方块的高度
#define ROWCOUNT 7 //行数
#define COLCOUNT 12 //列数
#define BLANK_STATE -1 //空方块(没有任何动物)
/
// C_LLK_Dlg dialog
C_LLK_Dlg::C_LLK_Dlg(CWnd* pParent /*=NULL*/)
: CDialog(C_LLK_Dlg::IDD, pParent)
{
//{{AFX_DATA_INIT(C_LLK_Dlg)
// NOTE: the ClassWizard will add member initialization here
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
//记录方块置为无效状态
m_nY1= BLANK_STATE;
m_nX1= BLANK_STATE;
//初始化行列数
m_nRow=ROWCOUNT;
m_nCol=COLCOUNT;
//根据行列数动态分配内核数据数组空间
m_map=new int[m_nRow*m_nCol];
}
C_LLK_Dlg::~C_LLK_Dlg()
{
//释放动态数组空间
delete[] m_map;
}
void C_LLK_Dlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(C_LLK_Dlg)
// NOTE: the ClassWizard will add DDX and DDV calls here
//}}AFX_DATA_MAP
}
BEGIN_MESSAGE_MAP(C_LLK_Dlg, CDialog)
//{{AFX_MSG_MAP(C_LLK_Dlg)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_WM_LBUTTONDOWN()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/
// C_LLK_Dlg message handlers
BOOL C_LLK_Dlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Set the icon for this dialog. The framework does this automatically
// when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE); // Set big icon
SetIcon(m_hIcon, FALSE); // Set small icon
//获取程序框架的设备环境
CDC *pWinDC = GetDC();
//内存设备环境以及内存位图的创建,初始化,关联
//3D方块边框图样内存位图
m_mem3DBkDC.CreateCompatibleDC(pWinDC);
m_mem3DBkBmp.LoadBitmap(IDB_BITMAP_3D_FRAMES);
m_mem3DBkDC.SelectObject(&m_mem3DBkBmp);
//动物图样内存位图
m_memAnimalDC.CreateCompatibleDC(pWinDC);
m_memAnimalBmp.LoadBitmap(IDB_BMP_ANIMAL);
m_memAnimalDC.SelectObject(&m_memAnimalBmp);
//整个游戏区域内存位图
m_MemDC.CreateCompatibleDC(pWinDC);
m_memBitmap.CreateCompatibleBitmap(pWinDC,
m_nCol*FRONTWIDTH+5,
m_nRow*FRONTHEIGHT+5);
m_MemDC.SelectObject(&m_memBitmap);
//开始一个新的游戏
StartNewGame();
//放在最桌面的前面显示
HWND hWnd = ::AfxGetMainWnd()->m_hWnd;
::SetWindowPos(hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
return TRUE; // return TRUE unless you set the focus to a control
}
void C_LLK_Dlg::StartNewGame()
{
//初始化地图,将地图中所有方块区域位置置为空方块状态
for(int iNum=0;iNum<(m_nCol*m_nRow);iNum++)
{
m_map[iNum] = BLANK_STATE;
}
//部下随机种子
srand(time(NULL));
//生成随机地图
//将所有匹配成对的动物物种放进一个临时的地图中
CDWordArray tmpMap;
for(int i=0;i<(m_nCol*m_nRow)/4;i++)
for(int j=0;j<4;j++)
tmpMap.Add(i);
//每次从上面的临时地图中取走(获取后并在临时地图删除)
//一个动物放到地图的空方块上
for(int i=0;i<m_nRow*m_nCol;i++)
{
//随机挑选一个位置
int nIndex=(int(rand()*0.1+rand()*0.01+rand()))%tmpMap.GetSize();
//获取该选定物件放到地图的空方块
m_map[i]=tmpMap.GetAt(nIndex);
//在临时地图除去该动物
tmpMap.RemoveAt(nIndex);
}
//更新显示
Invalidate(TRUE);
}
//
// 游戏区域的绘制
//
void C_LLK_Dlg::GameDraw(CDC * pDC)
{
//绘制背景颜色
pDC->FillSolidRect(0,0,m_nCol*FRONTWIDTH+5,m_nRow*FRONTHEIGHT+5,BKCOLOR);
for(int i=0;i<m_nRow;i++)
{
for(int j=0;j<m_nCol;j++)
{
if(m_map[i*m_nCol+j]==BLANK_STATE)
{
continue;
}
//绘制方块边框
pDC->BitBlt(j*FRONTWIDTH,i*FRONTHEIGHT,
BKWIDTH,BKHEIGHT,
&m_mem3DBkDC,
0,BKHEIGHT,
SRCCOPY);
//绘制方块
//因为要使得效果透明,所以由图样的底色以及表面两部分构成
pDC->BitBlt(j*FRONTWIDTH,i*FRONTHEIGHT,
FRONTWIDTH-2,FRONTHEIGHT-12,
&m_memAnimalDC,
FRONTWIDTH-2,m_map[i*m_nCol+j]*(FRONTHEIGHT-12),
SRCAND);
pDC->BitBlt(j*FRONTWIDTH,i*FRONTHEIGHT,
FRONTWIDTH-2,FRONTHEIGHT-12,
&m_memAnimalDC,
0,m_map[i*m_nCol+j]*(FRONTHEIGHT-12),
SRCPAINT);
}
}
}
// The system calls this to obtain the cursor to display while the user drags
// the minimized window.
HCURSOR C_LLK_Dlg::OnQueryDragIcon()
{
return (HCURSOR) m_hIcon;
}
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void C_LLK_Dlg::OnPaint()
{
CPaintDC dc(this); // device context for painting
//先将整个游戏区域的图像绘制到内存位图
GameDraw(&m_MemDC);
//将内存位图中绘制好的图像一次性拷贝到屏幕
dc.BitBlt(0,0,m_nCol*FRONTWIDTH,m_nCol*FRONTHEIGHT,&m_MemDC,0,0,SRCCOPY);
}
//
// 鼠标左键消息处理
//
void C_LLK_Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
//1.计算鼠标点击方块的的位置
int x=point.x/FRONTWIDTH+(point.x%FRONTWIDTH?1:0)-1;
int y=point.y/FRONTHEIGHT+(point.y%FRONTHEIGHT?1:0)-1;
//2.在游戏区域内并且该区域还有该区域不是空的区域
if(x<m_nCol&&y<m_nRow&&m_map[y*m_nCol+x]!= BLANK_STATE)
{
//3.假设尚未记录第一个方块
if(m_nX1==BLANK_STATE)
{
//4.记录第一个方块的位置
m_nX1=x;
m_nY1=y;
//获取程序框架的设备环境
CDC *pWinDC = GetDC();
//临时绘制点中的方块外框
//只绘屏幕不载入内存位图
CPen myPen;
CPen *pOldPen;
myPen.CreatePen(PS_SOLID, 4, RGB(255,0,0));
pOldPen = pWinDC->SelectObject(&myPen);
//方块外框绘制,线条环绕绘制框架
pWinDC->MoveTo(x*FRONTWIDTH,y*FRONTHEIGHT);
pWinDC->LineTo(x*FRONTWIDTH,(y+1)*FRONTHEIGHT);
pWinDC->LineTo((x+1)*FRONTWIDTH,(y+1)*FRONTHEIGHT);
pWinDC->LineTo((x+1)*FRONTWIDTH,y*FRONTHEIGHT);
pWinDC->LineTo(x*FRONTWIDTH,y*FRONTHEIGHT);
//现场恢复
pWinDC->SelectObject(pOldPen);
}
else
{
//5.判断是否点击的方块非本身, 是否点击同一种动物
if((m_nX1!=x||m_nY1!=y)&&
m_map[m_nY1*m_nCol+m_nX1]==m_map[y*m_nCol+x]
)
{
//6.检测是否可以消除
if(IsLink(m_nX1,m_nY1,x,y))
{
//7.数据清理
m_map[m_nY1*m_nCol+m_nX1]=BLANK_STATE;
m_map[y*m_nCol+x]=BLANK_STATE;
}
}
//8.清空记录方块的值
m_nX1=BLANK_STATE;
m_nY1=BLANK_STATE;
//通知重绘
Invalidate(FALSE);
}
}
//察看是否已经胜利
if(IsWin())
{
MessageBox("恭喜您胜利闯关,即将开始新局");
StartNewGame();
}
}
//
//X直接连通
//
BOOL C_LLK_Dlg::X1_Link_X2(int x, int y1,int y2)
{
//保证y1的值小于y2
if(y1>y2)
{
//数据交换
int n=y1;
y1=y2;
y2=n;
}
//直通
for(int i=y1+1;i<=y2;i++)
{
if(i==y2)
return TRUE;
if(m_map[i*m_nCol+x]!=BLANK_STATE)
break;
}
//左通
if(XThrough(x-1,y1,FALSE)&&XThrough(x-1,y2,FALSE))
return TRUE;
//右通
if(XThrough(x+1,y1,TRUE)&&XThrough(x+1,y2,TRUE))
return TRUE;
return FALSE;
}
//
//Y直接连通
//
BOOL C_LLK_Dlg::Y1_Link_Y2(int x1,int x2,int y)
{
if(x1>x2)
{
int x=x1;
x1=x2;
x2=x;
}
//直通
for(int i=x1+1;i<=x2;i++)
{
if(i==x2)
return TRUE;
if(m_map[y*m_nCol+i]!=BLANK_STATE)
break;
}
//上通
if(YThrough(x1,y-1,FALSE)&&YThrough(x2,y-1,FALSE))
return TRUE;
//下通
if(YThrough(x1,y+1,TRUE)&&YThrough(x2,y+1,TRUE))
return TRUE;
return FALSE;
}
//
// 是否同一直线通
//
BOOL C_LLK_Dlg::LineX(int x,int y1,int y2)
{
if(y1>y2)
{
int y=y1;
y1=y2;
y2=y;
}
for(int y=y1;y<=y2;y++)
{
if(m_map[y*m_nCol+x]!=BLANK_STATE)
return FALSE;
if(y==y2)
return TRUE;
}
return FALSE;
}
//
// 是否同一直线通
//
BOOL C_LLK_Dlg::LineY(int x1,int x2,int y)
{
if(x1>x2)
{
int x=x1;
x1=x2;
x2=x;
}
for(int x=x1;x<=x2;x++)
{
if(m_map[y*m_nCol+x]!=BLANK_STATE)
return FALSE;
if(x==x2)
return TRUE;
}
return FALSE;
}
//
// 1直角接口连通
//
BOOL C_LLK_Dlg::OneCornerLink(int x1, int y1,int x2, int y2)
{
if(x1>x2)
{
int n=x1;
x1=x2;
x2=n;
n=y1;
y1=y2;
y2=n;
}
if(y2<y1)
{
if(LineY(x1+1,x2,y1)&&LineX(x2,y1,y2+1))
return TRUE;
if(LineY(x2-1,x1,y2)&&LineX(x1,y2,y1-1))
return TRUE;
return FALSE;
}
else
{
if(LineY(x1+1,x2,y1)&&LineX(x2,y1,y2-1))
return TRUE;
if(LineY(x2-1,x1,y2)&&LineX(x1,y2,y1+1))
return TRUE;
return FALSE;
}
return FALSE;
}
//
// 2直角接口连通
//
BOOL C_LLK_Dlg::TwoCornerLink(int x1, int y1, int x2, int y2)
{
if(x1>x2)
{
int n=x1;
x1=x2;
x2=n;
n=y1;
y1=y2;
y2=n;
}
//右通
if(XThrough(x1+1,y1,TRUE)&&XThrough(x2+1,y2,TRUE))
return TRUE;
//左通
if(XThrough(x1-1,y1,FALSE)&&XThrough(x2-1,y2,FALSE))
return TRUE;
//上通
if(YThrough(x1,y1-1,FALSE)&&YThrough(x2,y2-1,FALSE))
return TRUE;
//下通
if(YThrough(x1,y1+1,TRUE)&&YThrough(x2,y2+1,TRUE))
return TRUE;
//右
for(int x=x1+1;x<m_nCol;x++)
{
if(m_map[y1*m_nCol+x]>-1)
break;
if(OneCornerLink(x,y1,x2,y2))
return TRUE;
}
//左
for(int x=x1-1;x>-1;x--)
{
if(m_map[y1*m_nCol+x]!=BLANK_STATE)
break;
if(OneCornerLink(x,y1,x2,y2))
return TRUE;
}
//上
for(int y=y1-1;y>-1;y--)
{
if(m_map[y*m_nCol+x1]!=BLANK_STATE)
break;
if(OneCornerLink(x1,y,x2,y2))
return TRUE;
}
//下
for(int y=y1+1;y<m_nRow;y++)
{
if(m_map[y*m_nCol+x1]!=BLANK_STATE)
break;
if(OneCornerLink(x1,y,x2,y2))
return TRUE;
}
return FALSE;
}
BOOL C_LLK_Dlg::XThrough(int x, int y, BOOL bAdd)
{
if(bAdd)
{
for(int i=x;i<m_nCol;i++)
if(m_map[y*m_nCol+i]!=BLANK_STATE)
return FALSE;
}
else
{
for(int i=0;i<=x;i++)
if(m_map[y*m_nCol+i]!=BLANK_STATE)
return FALSE;
}
return TRUE;
}
BOOL C_LLK_Dlg::YThrough(int x, int y,BOOL bAdd)
{
if(bAdd)
{
for(int i=y;i<m_nRow;i++)
if(m_map[i*m_nCol+x]!=BLANK_STATE)
return FALSE;
}
else
{
for(int i=0;i<=y;i++)
if(m_map[i*m_nCol+x]!=BLANK_STATE)
return FALSE;
}
return TRUE;
}
//
// 判断选中的两个方块是否可以消除
//
BOOL C_LLK_Dlg::IsLink(int x1, int y1, int x2, int y2)
{
//X直连方式
if(x1==x2)
{
if(X1_Link_X2(x1,y1,y2))
return TRUE;
}
//Y直连方式
else if(y1==y2)
{
if(Y1_Link_Y2(x1,x2,y1))
return TRUE;
}
//一个转弯直角的联通方式
if(OneCornerLink(x1,y1,x2,y2))
{
return TRUE;
}
//两个转弯直角的联通方式
else if(TwoCornerLink(x1,y1,x2,y2))
{
return TRUE;
}
return FALSE;
}
//
// 截获键盘消息 F2 (用于新游戏开始)
//
BOOL C_LLK_Dlg::PreTranslateMessage(MSG* pMsg)
{
if(pMsg->message==WM_KEYDOWN)
{
if(pMsg->wParam==VK_F2)
{
StartNewGame();
}
}
return CDialog::PreTranslateMessage(pMsg);
}
//
// 检测是否已经赢得了游戏
//
BOOL C_LLK_Dlg::IsWin(void)
{
//检测所有是否尚有非未被消除的方块
// (非BLANK_STATE状态)
for(int i=0;i<m_nRow*m_nCol;i++)
{
if(m_map[i] != BLANK_STATE)
{
return FALSE;
}
}
return TRUE;
}
若老师需要运行:http://pan.baidu.com/s/1dDgXFxv