-
-
vc++游戏源码
-
发表于: 2008-9-17 16:31 4097
-
一个vc++游戏源码
1、 游戏实现
俄罗斯方块,或称积木游戏,它是利用一些形状各异却又是用正方形组成的方块,经过不同位置不同角度的变化之后,堆积在一起的一种智力游戏。
而从我们编程的角度讲,我们只需要提供各种方块的图形,提供几个键盘操作键以供方块的形状和位置的变化,提供几个功能函数以供游戏的正常进行。
各种方块图形:利用数组定形,然后利用随机函数随机地不按顺序地按游戏的需要而出现。
键盘操作键:就是四个方向键。其中左、右、下三个键意思一样,上键的功能不是使方块向上,而是使方块的下落角度改变。
功能函数将在变量函数里面介绍。
新建单文档工程4_1。
2、 资源编辑
添加位图:
封面: IDB_BITMAP1
背景: IDB_BITMAP2
方块: IDB_BITMAP4
添加菜单:
开始: ID_MENU_START
3、 变量函数
接着就是定义变量了,但是,由于这个游戏要添加的变量和函数太多了,我们要建一个新类。
是否应该先添加应该类呢?最好是这样。因为新类将会涉及到变量。
添加普通类Crussia,
添加变量函数:
由于两个类一共有很多变量函数,列举如下:
// 4_1View.h :
//俄罗斯类
CRussia russia;
//开始标志
bool start;
//封面
CBitmap fenmian;
//暂停
BOOL m_bPause;
//开始菜单
afx_msg void OnMenuStart();
//计时器
afx_msg void OnTimer(UINT nIDEvent);
//键盘操作
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
// Russia.h:
//游戏数组
int Russia[100][100];
// 当前图形
int Now[4][4];
//上一图形
int Will[4][4];
//变换后的图形
int After[4][4];
//当前图形的左上角位置
CPoint NowPosition;
//当前可能出现的图形形状数,
int Count;
//游戏结束
bool end;
//级别
int m_Level;
//速度
int m_Speed;
//分数
int m_Score;
//行列数
int m_RowCount,m_ColCount;
//方块
CBitmap fangkuai;
//背景
CBitmap jiemian;
//显示分数等内容
void DrawScore(CDC*pDC);
//消行
void LineDelete();
//方块移动
void Move(int direction);
//方块变化,即方向键上键操作
bool Change(int a[][4],CPoint p,int b[][100]);
//是否与原来方块接触,或与边界接触
bool Meet(int a[][4],int direction,CPoint p);
//显示下一个方块
void DrawWill();
//显示界面
void DrawJiemian(CDC*pDC);
//开始
void Start();
4、 具体实现
然后,我们就可以一步一步地实现游戏了。函数依然是一个一个添加,如果有还没定义的函数,添加空函数。以保证程序的条理性和可运行性。
构造函数:
CMy4_1View::CMy4_1View()
{
// TODO: add construction code here
fenmian.LoadBitmap(IDB_BITMAP1);
start=false;
m_bPause=false;
}
CRussia::CRussia()
{
jiemian.LoadBitmap(IDB_BITMAP2);
fangkuai.LoadBitmap(IDB_BITMAP4);
}
画图函数:
void CMy4_1View::OnDraw(CDC* pDC)
{
CMy4_1Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CDC Dc;
if(Dc.CreateCompatibleDC(pDC)==FALSE)
AfxMessageBox("Can't create DC");
//没有开始,显示封面
if( !start)
{
Dc.SelectObject(fenmian);
pDC->BitBlt(0,0,500,550,&Dc,0,0,SRCCOPY);
}
//显示背景
else
russia.DrawJiemian(pDC);
}
开始时我们是设start为假,它就会在OnDraw()函数中画封面,而当我们开始游戏,start为真,那么,它干什么呢?画背景!其函数如下:
界面函数:
还是那个道理,当有一些客户区生效(被挡住或最小化)时,它必须重画,而如果游戏只是玩了一半,它必然在重画时必须把原先已经出现的方块、分数等也显示出来,怎么办?就必须在画封面的同时也画出它们。当然,刚开始时它们是不会符合条件的。
void CRussia::DrawJiemian(CDC*pDC)
{
CDC Dc;
if(Dc.CreateCompatibleDC(pDC)==FALSE)
AfxMessageBox("Can't create DC");
//画背景
Dc.SelectObject(jiemian);
pDC->BitBlt(0,0,500,550,&Dc,0,0,SRCCOPY);
//画分数,速度,难度
DrawScore(pDC);
//如果有方块,显示方块
//游戏区
for(int i=0;i<m_RowCount;i++)
for(int j=0;j<m_ColCount;j++)
if(Russia[i][j]==1)
{
Dc.SelectObject(fangkuai);
pDC->BitBlt(j*30,i*30,30,30,&Dc,0,0,SRCCOPY);
}
//预先图形方块
for(int n=0;n<4;n++)
for(int m=0;m<4;m++)
if(Will[n][m]==1)
{
Dc.SelectObject(fangkuai);
pDC->BitBlt(365+m*30,240+n*30,30,30,&Dc,0,0,SRCCOPY);
}
}
信息函数:
其中还涉及另外一个函数DrawScore(pDC),它是画分数、速度、难度(本程序省略)的。由于它的代码不是太少,另外用了一个函数,这样有利于理解。
void CRussia::DrawScore(CDC*pDC)
{
int nOldDC=pDC->SaveDC();
//设置字体
CFont font;
if(0==font.CreatePointFont(300,"Comic Sans MS"))
{
AfxMessageBox("Can't Create Font");
}
pDC->SelectObject(&font);
//设置字体颜色及其背景颜色
CString str;
pDC->SetTextColor(RGB(39,244,10));
pDC->SetBkColor(RGB(255,255,0));
//输出数字
str.Format("%d",m_Level);
if(m_Level>=0)
pDC->TextOut(440,120,str);
str.Format("%d",m_Speed);
if(m_Speed>=0)
pDC->TextOut(440,64,str);
str.Format("%d",m_Score);
if(m_Score>=0)
pDC->TextOut(440,2,str);
pDC->RestoreDC(nOldDC);
}
至此,可以看的都画完了。程序一般都是会先处理图形界面,因为这样在编核心内容时能够让人有一个检查的机会。
菜单开始函数:
现在,游戏总该开始了吧。添加菜单开始函数:ID_MENU_START
其函数如下:
void CMy4_1View::OnMenuStart()
{
// TODO: Add your command handler code here
start=true;
russia.Start();
SetTimer(1,50*(11-russia.m_Speed ),NULL);
}
先把start赋值为true,再调用russia.Start()函数,让它对俄罗斯方块游戏的相应变量赋值,为了使游戏能够调整速度,设置一个可变的计数器。那么,russia.Start()函数做了什么呢?
开始函数:
void CRussia::Start()
{
end=false;//运行结束标志
m_Score=0; //初始分数
m_Speed=0; //初始速度
m_Level=1; //初始难度
m_RowCount=18; //行数
m_ColCount=12; //列数
Count=7; //方块种类
//清空背景数组
for(int i=0;i<m_RowCount;i++)
for(int j=0;j<m_ColCount;j++)
{
Russia[i][j]=0;
}
//清空方块数组Now[ ][ ] Will[ ][ ]
for(i=0;i<4;i++)
for(int j=0;j<4;j++)
{
Now[i][j]=0;
Will[i][j]=0;
}
//先画Will[][]
DrawWill();
//再画Now[][]&Will[][]
DrawWill();
}
预备方块:
DrawWill()的作用是,把预备方块给当前方块,再生成一个预备方块。由于开始时预备方块自己都没有,它根本就不能拿一个方块给当前数组,它只能自己生成一个,这也是什么连续调用两次的原因。
void CRussia::DrawWill()
{
int i,j;
int k=4,l=4;
//把将要出现的方块给当前数组,并把将要出现数组赋值为零
for(i=0;i<4;i++)
for(j=0;j<4;j++)
{
Now[i][j]=Will[i][j];
Will[i][j]=0;
}
//初始化随即数种子
//其中是什么意思?不知道的话,不如按一下F1。
srand(GetTickCount());
int nTemp=rand()%Count;
//各种图形
switch(nTemp)
{
case 0:
Will[0][0]=1;
Will[0][1]=1;
Will[1][0]=1;
Will[1][1]=1;
break;
case 1:
Will[0][0]=1;
Will[0][1]=1;
Will[1][0]=1;
Will[2][0]=1;
break;
case 2:
Will[0][0]=1;
Will[0][1]=1;
Will[1][1]=1;
Will[2][1]=1;
break;
case 3:
Will[0][1]=1;
Will[1][0]=1;
Will[1][1]=1;
Will[2][0]=1;
break;
case 4:
Will[0][0]=1;
Will[1][0]=1;
Will[1][1]=1;
Will[2][1]=1;
break;
case 5:
Will[0][0]=1;
Will[1][0]=1;
Will[1][1]=1;
Will[2][0]=1;
break;
case 6:
Will[0][0]=1;
Will[1][0]=1;
Will[2][0]=1;
Will[3][0]=1;
break;
//适应于难度扩展
/* case 7:
Will[0][0]=1;
Will[1][0]=1;
Will[1][1]=1;
Will[1][2]=1;
Will[0][2]=1;
break;
case 8:
Will[0][0]=1;
Will[1][0]=1;
Will[2][0]=1;
Will[1][1]=1;
Will[1][2]=1;
break;
*/
}
//转换
int tmp[4][4];
for(i=0;i<4;i++)
for(j=0;j<4;j++)
tmp[i][j]=Will[j][3-i];
//整理,为了移动的需要
for(i=0;i<4;i++)
for(j=0;j<4;j++)
if(tmp[i][j]==1)
{
if(k>i) k=i;
if(l>j) l=j;
}
for(i=0;i<4;i++)
for(j=0;j<4;j++)
Will[i][j]=0;
//把变换后的矩阵移到左上角
for(i=k;i<4;i++)
for(j=l;j<4;j++)
Will[i-k][j-l]=tmp[i][j];
// Now[][]的开始位置
NowPosition.x=0;
NowPosition.y=m_ColCount/2;
}
计时器函数:
有了SetTimer( ),就别忘了OnTimer(UINT nIDEvent)函数:
先下移,再重画。
void CMy4_1View::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
//下移
russia.Move(3);
//重画
russia.DrawJiemian(GetDC());
CView::OnTimer(nIDEvent);
}
下移,用Move()函数,见下面。
那么,其中的参数是什么意思?方向!就是四个方向键。显然,上面只是用了其中之一,即下移。那这个函数究竟是在哪里调用?聪明的读者一定知道就是键盘上的方向键!
键盘操作:
就让我们在讲Move()函数之前,添加一个OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)函数。
void CMy4_1View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call default
//没有开始
if(!start)
return;
//暂停
if(m_bPause==TRUE)
return;
switch(nChar)
{
case VK_LEFT:
russia.Move(1);
break;
case VK_RIGHT:
russia.Move(2);
break;
case VK_UP:
russia.Move(4);
break;
case VK_DOWN:
russia.Move(3);
break;
}
//重画
CDC* pDC=GetDC();
russia.DrawJiemian(pDC);
ReleaseDC(pDC);
CView::OnKeyDown(nChar, nRepCnt, nFlags);
}
移动函数:
很明显,上面其实都是根据下面的函数而工作的。下面的函数,先判断哪个方向,即按了哪个方向键,然后调用一个判断是否过界或重叠的函数Meet(Now,1,NowPosition),然后或者返回,或者移动。
另外,当下移并且被阻挡时,必须判断是否可以消行。
void CRussia::Move(int direction)
{
if(end)
return;
switch(direction)
{
//左
case 1:
if(Meet(Now,1,NowPosition)) break;
NowPosition.y--;
break;
//右
case 2:
if(Meet(Now,2,NowPosition)) break;
NowPosition.y++;
break;
//下
case 3:
if(Meet(Now,3,NowPosition))
{
LineDelete();
break;
}
NowPosition.x++;
break;
//上
case 4:
Meet(Now,4,NowPosition);
break;
default:
break;
}
}
上面涉及两个新的函数,介绍如下:
消行函数:
//消去行
void CRussia::LineDelete()
{
int m=0; //本次共消去的行数
bool flag=0;
for(int i=0;i<m_RowCount;i++)
{
//检查要不要消行
flag=true;
for(int j=0;j<m_ColCount;j++)
if(Russia[i][j]==0)
flag=false;
//如果要
if(flag==true)
{
m++;
for(int k=i;k>0;k--)
{
//上行给下行
for(int l=0;l<m_ColCount;l++)
{
Russia[k][l]=Russia[k-1][l];
}
}
//第一行为零
for(int l=0;l<m_ColCount;l++)
{
Russia[0][l]=0;
}
}
}
//消行,表示运动方块已经停止
//必须生成新的运动方块
DrawWill();
//加分
switch(m)
{
case 1:
m_Score++;
break;
case 2:
m_Score+=3;
break;
case 3:
m_Score+=6;
break;
case 4:
m_Score+=10;
break;
default:
break;
}
//速度赋值
m_Speed=m_Score/100;
for(i=0;i<4;i++)
for(int j=0;j<4;j++)
if(Now[i][j]==1)
//到了顶点
if(Russia[i+NowPosition.x][j+NowPosition.y]==1)
{
end=true;
AfxMessageBox("游戏结束!");
return;
}
}
这个函数容易看懂。一点要说的是,既然要判断消行,证明当前方块已经停止,所以无论如何,都必须在这个时候调用一下DrawWill()函数,生成新的当前运动方块。
过界或重叠:
如果方块移动时出界,或者和已经有了的方块重叠,应该取消操作。
//是否遇到了边界或者有其他方块档住了
bool CRussia::Meet(int a[][4],int direction,CPoint p)
{
int i,j;
//先把原位置清0
for(i=0;i<4;i++)
for(j=0;j<4;j++)
if(a[i][j]==1)
Russia[p.x+i][p.y+j]=0;
for(i=0;i<4;i++)
for(j=0;j<4;j++)
if(a[i][j]==1)
{
switch(direction)
{
case 1: //左移
if((p.y+j-1)<0) goto exit;
if(Russia[p.x+i][p.y+j-1]==1) goto exit;
break;
case 2://右移
if((p.y+j+1)>=m_ColCount) goto exit;
if(Russia[p.x+i][p.y+j+1]==1) goto exit;
break;
case 3://下移
if((p.x+i+1)>=m_RowCount) goto exit;
if(Russia[p.x+i+1][p.y+j]==1) goto exit;
break;
case 4://变换
if(!Change(a,p,Russia)) goto exit;
for(i=0;i<4;i++)
for(j=0;j<4;j++)
{
Now[i][j]=After[i][j];
a[i][j]=Now[i][j];
}
return false;
break;
}
}
int x,y;
x=p.x;
y=p.y;
//移动位置,重新给数组赋值
switch(direction)
{
case 1:
y--;break;
case 2:
y++;break;
case 3:
x++;break;
case 4:
break;
}
for(i=0;i<4;i++)
for(j=0;j<4;j++)
if(a[i][j]==1)
Russia[x+i][y+j]=1;
return false;
exit:
for(i=0;i<4;i++)
for(j=0;j<4;j++)
if(a[i][j]==1)
Russia[p.x+i][p.y+j]=1;
return true;
}
此函数是先把背景数组的相应位置赋值为零,而利用当前数组和一些局部变量数组的交换赋值,然后检查是否符合放下背景数组的要求,是则按照这情况赋值,否则按原先情况赋值。
其中变换时有一个新函数,是检查是否可以变换的。如下面。
变换函数:
转换,就是当按下向上方向键时,也要判断是否出界或重叠。
//转换
bool CRussia::Change(int a[][4], CPoint p,int b[][100])
{
int tmp[4][4];
int i,j;
int k=4,l=4;
for(i=0;i<4;i++)
for(j=0;j<4;j++)
{
tmp[i][j]=a[j][3-i];
After[i][j]=0; //存放变换后的方块矩阵
}
for(i=0;i<4;i++)
for(j=0;j<4;j++)
if(tmp[i][j]==1)
{
if(k>i) k=i;
if(l>j) l=j;
}
for(i=k;i<4;i++)
for(j=l;j<4;j++)
{
After[i-k][j-l]=tmp[i][j];
} //把变换后的矩阵移到左上角
//判断是否接触,是:返回失败
for(i=0;i<4;i++)
for(j=0;j<4;j++)
{
if(After[i][j]==0) continue;
if(((p.x+i)>=m_RowCount)||((p.y+j)<0)||((p.y+j)>=m_ColCount)) return false;
if(b[p.x+i][p.y+j]==1)
return false;
}
return true;
}
现在,我们的程序编好了,可以玩了。
5 暂停
添加菜单如下(ID_MENU_PAUSE)菜单id,关联一个键盘上的热键F3,
,添加函数如下:
void CMy4_1View::OnMenuPause()
{
// TODO: Add your command handler code here
m_bPause=!m_bPause;
//停止计数器
if(m_bPause)
KillTimer(1);
//开始计数器
else
SetTimer(1,50*(11-russia.m_Speed ),NULL);
}
void CMy4_1View::OnUpdateMenuPause(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
//是否显示钩
pCmdUI->SetCheck(m_bPause);
}
l 热键
如上图,打开Accelerator,添加如上ID号,选择如上ID号和热键就行了。
l 艺术字
看了我的封面上的艺术字,是否有些心动,用VC++怎么设置出这样的艺术字?我的答案是这不是用VC++做的,它只是一张位图。不过,这张位图是我自己做的,怎么做?我可以说一下:
利用Word里面的艺术字,做好之后,把它拷贝到位图上就行了
1、 游戏实现
俄罗斯方块,或称积木游戏,它是利用一些形状各异却又是用正方形组成的方块,经过不同位置不同角度的变化之后,堆积在一起的一种智力游戏。
而从我们编程的角度讲,我们只需要提供各种方块的图形,提供几个键盘操作键以供方块的形状和位置的变化,提供几个功能函数以供游戏的正常进行。
各种方块图形:利用数组定形,然后利用随机函数随机地不按顺序地按游戏的需要而出现。
键盘操作键:就是四个方向键。其中左、右、下三个键意思一样,上键的功能不是使方块向上,而是使方块的下落角度改变。
功能函数将在变量函数里面介绍。
新建单文档工程4_1。
2、 资源编辑
添加位图:
封面: IDB_BITMAP1
背景: IDB_BITMAP2
方块: IDB_BITMAP4
添加菜单:
开始: ID_MENU_START
3、 变量函数
接着就是定义变量了,但是,由于这个游戏要添加的变量和函数太多了,我们要建一个新类。
是否应该先添加应该类呢?最好是这样。因为新类将会涉及到变量。
添加普通类Crussia,
添加变量函数:
由于两个类一共有很多变量函数,列举如下:
// 4_1View.h :
//俄罗斯类
CRussia russia;
//开始标志
bool start;
//封面
CBitmap fenmian;
//暂停
BOOL m_bPause;
//开始菜单
afx_msg void OnMenuStart();
//计时器
afx_msg void OnTimer(UINT nIDEvent);
//键盘操作
afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags);
// Russia.h:
//游戏数组
int Russia[100][100];
// 当前图形
int Now[4][4];
//上一图形
int Will[4][4];
//变换后的图形
int After[4][4];
//当前图形的左上角位置
CPoint NowPosition;
//当前可能出现的图形形状数,
int Count;
//游戏结束
bool end;
//级别
int m_Level;
//速度
int m_Speed;
//分数
int m_Score;
//行列数
int m_RowCount,m_ColCount;
//方块
CBitmap fangkuai;
//背景
CBitmap jiemian;
//显示分数等内容
void DrawScore(CDC*pDC);
//消行
void LineDelete();
//方块移动
void Move(int direction);
//方块变化,即方向键上键操作
bool Change(int a[][4],CPoint p,int b[][100]);
//是否与原来方块接触,或与边界接触
bool Meet(int a[][4],int direction,CPoint p);
//显示下一个方块
void DrawWill();
//显示界面
void DrawJiemian(CDC*pDC);
//开始
void Start();
4、 具体实现
然后,我们就可以一步一步地实现游戏了。函数依然是一个一个添加,如果有还没定义的函数,添加空函数。以保证程序的条理性和可运行性。
构造函数:
CMy4_1View::CMy4_1View()
{
// TODO: add construction code here
fenmian.LoadBitmap(IDB_BITMAP1);
start=false;
m_bPause=false;
}
CRussia::CRussia()
{
jiemian.LoadBitmap(IDB_BITMAP2);
fangkuai.LoadBitmap(IDB_BITMAP4);
}
画图函数:
void CMy4_1View::OnDraw(CDC* pDC)
{
CMy4_1Doc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CDC Dc;
if(Dc.CreateCompatibleDC(pDC)==FALSE)
AfxMessageBox("Can't create DC");
//没有开始,显示封面
if( !start)
{
Dc.SelectObject(fenmian);
pDC->BitBlt(0,0,500,550,&Dc,0,0,SRCCOPY);
}
//显示背景
else
russia.DrawJiemian(pDC);
}
开始时我们是设start为假,它就会在OnDraw()函数中画封面,而当我们开始游戏,start为真,那么,它干什么呢?画背景!其函数如下:
界面函数:
还是那个道理,当有一些客户区生效(被挡住或最小化)时,它必须重画,而如果游戏只是玩了一半,它必然在重画时必须把原先已经出现的方块、分数等也显示出来,怎么办?就必须在画封面的同时也画出它们。当然,刚开始时它们是不会符合条件的。
void CRussia::DrawJiemian(CDC*pDC)
{
CDC Dc;
if(Dc.CreateCompatibleDC(pDC)==FALSE)
AfxMessageBox("Can't create DC");
//画背景
Dc.SelectObject(jiemian);
pDC->BitBlt(0,0,500,550,&Dc,0,0,SRCCOPY);
//画分数,速度,难度
DrawScore(pDC);
//如果有方块,显示方块
//游戏区
for(int i=0;i<m_RowCount;i++)
for(int j=0;j<m_ColCount;j++)
if(Russia[i][j]==1)
{
Dc.SelectObject(fangkuai);
pDC->BitBlt(j*30,i*30,30,30,&Dc,0,0,SRCCOPY);
}
//预先图形方块
for(int n=0;n<4;n++)
for(int m=0;m<4;m++)
if(Will[n][m]==1)
{
Dc.SelectObject(fangkuai);
pDC->BitBlt(365+m*30,240+n*30,30,30,&Dc,0,0,SRCCOPY);
}
}
信息函数:
其中还涉及另外一个函数DrawScore(pDC),它是画分数、速度、难度(本程序省略)的。由于它的代码不是太少,另外用了一个函数,这样有利于理解。
void CRussia::DrawScore(CDC*pDC)
{
int nOldDC=pDC->SaveDC();
//设置字体
CFont font;
if(0==font.CreatePointFont(300,"Comic Sans MS"))
{
AfxMessageBox("Can't Create Font");
}
pDC->SelectObject(&font);
//设置字体颜色及其背景颜色
CString str;
pDC->SetTextColor(RGB(39,244,10));
pDC->SetBkColor(RGB(255,255,0));
//输出数字
str.Format("%d",m_Level);
if(m_Level>=0)
pDC->TextOut(440,120,str);
str.Format("%d",m_Speed);
if(m_Speed>=0)
pDC->TextOut(440,64,str);
str.Format("%d",m_Score);
if(m_Score>=0)
pDC->TextOut(440,2,str);
pDC->RestoreDC(nOldDC);
}
至此,可以看的都画完了。程序一般都是会先处理图形界面,因为这样在编核心内容时能够让人有一个检查的机会。
菜单开始函数:
现在,游戏总该开始了吧。添加菜单开始函数:ID_MENU_START
其函数如下:
void CMy4_1View::OnMenuStart()
{
// TODO: Add your command handler code here
start=true;
russia.Start();
SetTimer(1,50*(11-russia.m_Speed ),NULL);
}
先把start赋值为true,再调用russia.Start()函数,让它对俄罗斯方块游戏的相应变量赋值,为了使游戏能够调整速度,设置一个可变的计数器。那么,russia.Start()函数做了什么呢?
开始函数:
void CRussia::Start()
{
end=false;//运行结束标志
m_Score=0; //初始分数
m_Speed=0; //初始速度
m_Level=1; //初始难度
m_RowCount=18; //行数
m_ColCount=12; //列数
Count=7; //方块种类
//清空背景数组
for(int i=0;i<m_RowCount;i++)
for(int j=0;j<m_ColCount;j++)
{
Russia[i][j]=0;
}
//清空方块数组Now[ ][ ] Will[ ][ ]
for(i=0;i<4;i++)
for(int j=0;j<4;j++)
{
Now[i][j]=0;
Will[i][j]=0;
}
//先画Will[][]
DrawWill();
//再画Now[][]&Will[][]
DrawWill();
}
预备方块:
DrawWill()的作用是,把预备方块给当前方块,再生成一个预备方块。由于开始时预备方块自己都没有,它根本就不能拿一个方块给当前数组,它只能自己生成一个,这也是什么连续调用两次的原因。
void CRussia::DrawWill()
{
int i,j;
int k=4,l=4;
//把将要出现的方块给当前数组,并把将要出现数组赋值为零
for(i=0;i<4;i++)
for(j=0;j<4;j++)
{
Now[i][j]=Will[i][j];
Will[i][j]=0;
}
//初始化随即数种子
//其中是什么意思?不知道的话,不如按一下F1。
srand(GetTickCount());
int nTemp=rand()%Count;
//各种图形
switch(nTemp)
{
case 0:
Will[0][0]=1;
Will[0][1]=1;
Will[1][0]=1;
Will[1][1]=1;
break;
case 1:
Will[0][0]=1;
Will[0][1]=1;
Will[1][0]=1;
Will[2][0]=1;
break;
case 2:
Will[0][0]=1;
Will[0][1]=1;
Will[1][1]=1;
Will[2][1]=1;
break;
case 3:
Will[0][1]=1;
Will[1][0]=1;
Will[1][1]=1;
Will[2][0]=1;
break;
case 4:
Will[0][0]=1;
Will[1][0]=1;
Will[1][1]=1;
Will[2][1]=1;
break;
case 5:
Will[0][0]=1;
Will[1][0]=1;
Will[1][1]=1;
Will[2][0]=1;
break;
case 6:
Will[0][0]=1;
Will[1][0]=1;
Will[2][0]=1;
Will[3][0]=1;
break;
//适应于难度扩展
/* case 7:
Will[0][0]=1;
Will[1][0]=1;
Will[1][1]=1;
Will[1][2]=1;
Will[0][2]=1;
break;
case 8:
Will[0][0]=1;
Will[1][0]=1;
Will[2][0]=1;
Will[1][1]=1;
Will[1][2]=1;
break;
*/
}
//转换
int tmp[4][4];
for(i=0;i<4;i++)
for(j=0;j<4;j++)
tmp[i][j]=Will[j][3-i];
//整理,为了移动的需要
for(i=0;i<4;i++)
for(j=0;j<4;j++)
if(tmp[i][j]==1)
{
if(k>i) k=i;
if(l>j) l=j;
}
for(i=0;i<4;i++)
for(j=0;j<4;j++)
Will[i][j]=0;
//把变换后的矩阵移到左上角
for(i=k;i<4;i++)
for(j=l;j<4;j++)
Will[i-k][j-l]=tmp[i][j];
// Now[][]的开始位置
NowPosition.x=0;
NowPosition.y=m_ColCount/2;
}
计时器函数:
有了SetTimer( ),就别忘了OnTimer(UINT nIDEvent)函数:
先下移,再重画。
void CMy4_1View::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
//下移
russia.Move(3);
//重画
russia.DrawJiemian(GetDC());
CView::OnTimer(nIDEvent);
}
下移,用Move()函数,见下面。
那么,其中的参数是什么意思?方向!就是四个方向键。显然,上面只是用了其中之一,即下移。那这个函数究竟是在哪里调用?聪明的读者一定知道就是键盘上的方向键!
键盘操作:
就让我们在讲Move()函数之前,添加一个OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)函数。
void CMy4_1View::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: Add your message handler code here and/or call default
//没有开始
if(!start)
return;
//暂停
if(m_bPause==TRUE)
return;
switch(nChar)
{
case VK_LEFT:
russia.Move(1);
break;
case VK_RIGHT:
russia.Move(2);
break;
case VK_UP:
russia.Move(4);
break;
case VK_DOWN:
russia.Move(3);
break;
}
//重画
CDC* pDC=GetDC();
russia.DrawJiemian(pDC);
ReleaseDC(pDC);
CView::OnKeyDown(nChar, nRepCnt, nFlags);
}
移动函数:
很明显,上面其实都是根据下面的函数而工作的。下面的函数,先判断哪个方向,即按了哪个方向键,然后调用一个判断是否过界或重叠的函数Meet(Now,1,NowPosition),然后或者返回,或者移动。
另外,当下移并且被阻挡时,必须判断是否可以消行。
void CRussia::Move(int direction)
{
if(end)
return;
switch(direction)
{
//左
case 1:
if(Meet(Now,1,NowPosition)) break;
NowPosition.y--;
break;
//右
case 2:
if(Meet(Now,2,NowPosition)) break;
NowPosition.y++;
break;
//下
case 3:
if(Meet(Now,3,NowPosition))
{
LineDelete();
break;
}
NowPosition.x++;
break;
//上
case 4:
Meet(Now,4,NowPosition);
break;
default:
break;
}
}
上面涉及两个新的函数,介绍如下:
消行函数:
//消去行
void CRussia::LineDelete()
{
int m=0; //本次共消去的行数
bool flag=0;
for(int i=0;i<m_RowCount;i++)
{
//检查要不要消行
flag=true;
for(int j=0;j<m_ColCount;j++)
if(Russia[i][j]==0)
flag=false;
//如果要
if(flag==true)
{
m++;
for(int k=i;k>0;k--)
{
//上行给下行
for(int l=0;l<m_ColCount;l++)
{
Russia[k][l]=Russia[k-1][l];
}
}
//第一行为零
for(int l=0;l<m_ColCount;l++)
{
Russia[0][l]=0;
}
}
}
//消行,表示运动方块已经停止
//必须生成新的运动方块
DrawWill();
//加分
switch(m)
{
case 1:
m_Score++;
break;
case 2:
m_Score+=3;
break;
case 3:
m_Score+=6;
break;
case 4:
m_Score+=10;
break;
default:
break;
}
//速度赋值
m_Speed=m_Score/100;
for(i=0;i<4;i++)
for(int j=0;j<4;j++)
if(Now[i][j]==1)
//到了顶点
if(Russia[i+NowPosition.x][j+NowPosition.y]==1)
{
end=true;
AfxMessageBox("游戏结束!");
return;
}
}
这个函数容易看懂。一点要说的是,既然要判断消行,证明当前方块已经停止,所以无论如何,都必须在这个时候调用一下DrawWill()函数,生成新的当前运动方块。
过界或重叠:
如果方块移动时出界,或者和已经有了的方块重叠,应该取消操作。
//是否遇到了边界或者有其他方块档住了
bool CRussia::Meet(int a[][4],int direction,CPoint p)
{
int i,j;
//先把原位置清0
for(i=0;i<4;i++)
for(j=0;j<4;j++)
if(a[i][j]==1)
Russia[p.x+i][p.y+j]=0;
for(i=0;i<4;i++)
for(j=0;j<4;j++)
if(a[i][j]==1)
{
switch(direction)
{
case 1: //左移
if((p.y+j-1)<0) goto exit;
if(Russia[p.x+i][p.y+j-1]==1) goto exit;
break;
case 2://右移
if((p.y+j+1)>=m_ColCount) goto exit;
if(Russia[p.x+i][p.y+j+1]==1) goto exit;
break;
case 3://下移
if((p.x+i+1)>=m_RowCount) goto exit;
if(Russia[p.x+i+1][p.y+j]==1) goto exit;
break;
case 4://变换
if(!Change(a,p,Russia)) goto exit;
for(i=0;i<4;i++)
for(j=0;j<4;j++)
{
Now[i][j]=After[i][j];
a[i][j]=Now[i][j];
}
return false;
break;
}
}
int x,y;
x=p.x;
y=p.y;
//移动位置,重新给数组赋值
switch(direction)
{
case 1:
y--;break;
case 2:
y++;break;
case 3:
x++;break;
case 4:
break;
}
for(i=0;i<4;i++)
for(j=0;j<4;j++)
if(a[i][j]==1)
Russia[x+i][y+j]=1;
return false;
exit:
for(i=0;i<4;i++)
for(j=0;j<4;j++)
if(a[i][j]==1)
Russia[p.x+i][p.y+j]=1;
return true;
}
此函数是先把背景数组的相应位置赋值为零,而利用当前数组和一些局部变量数组的交换赋值,然后检查是否符合放下背景数组的要求,是则按照这情况赋值,否则按原先情况赋值。
其中变换时有一个新函数,是检查是否可以变换的。如下面。
变换函数:
转换,就是当按下向上方向键时,也要判断是否出界或重叠。
//转换
bool CRussia::Change(int a[][4], CPoint p,int b[][100])
{
int tmp[4][4];
int i,j;
int k=4,l=4;
for(i=0;i<4;i++)
for(j=0;j<4;j++)
{
tmp[i][j]=a[j][3-i];
After[i][j]=0; //存放变换后的方块矩阵
}
for(i=0;i<4;i++)
for(j=0;j<4;j++)
if(tmp[i][j]==1)
{
if(k>i) k=i;
if(l>j) l=j;
}
for(i=k;i<4;i++)
for(j=l;j<4;j++)
{
After[i-k][j-l]=tmp[i][j];
} //把变换后的矩阵移到左上角
//判断是否接触,是:返回失败
for(i=0;i<4;i++)
for(j=0;j<4;j++)
{
if(After[i][j]==0) continue;
if(((p.x+i)>=m_RowCount)||((p.y+j)<0)||((p.y+j)>=m_ColCount)) return false;
if(b[p.x+i][p.y+j]==1)
return false;
}
return true;
}
现在,我们的程序编好了,可以玩了。
5 暂停
添加菜单如下(ID_MENU_PAUSE)菜单id,关联一个键盘上的热键F3,
,添加函数如下:
void CMy4_1View::OnMenuPause()
{
// TODO: Add your command handler code here
m_bPause=!m_bPause;
//停止计数器
if(m_bPause)
KillTimer(1);
//开始计数器
else
SetTimer(1,50*(11-russia.m_Speed ),NULL);
}
void CMy4_1View::OnUpdateMenuPause(CCmdUI* pCmdUI)
{
// TODO: Add your command update UI handler code here
//是否显示钩
pCmdUI->SetCheck(m_bPause);
}
l 热键
如上图,打开Accelerator,添加如上ID号,选择如上ID号和热键就行了。
l 艺术字
看了我的封面上的艺术字,是否有些心动,用VC++怎么设置出这样的艺术字?我的答案是这不是用VC++做的,它只是一张位图。不过,这张位图是我自己做的,怎么做?我可以说一下:
利用Word里面的艺术字,做好之后,把它拷贝到位图上就行了
赞赏
他的文章
- [分享]今天开始翻译《计算几何 C语言描述》Joseph O'Rourke 5742
- [原创]vc++ 5867
- [原创]浅谈国产软件 4812
看原图
赞赏
雪币:
留言: