一、预览
还是先来看看游戏预览效果。
二、游戏玩法介绍
《俄罗斯方块》(Tetris,俄文:Тетрис)是一款由俄罗斯人阿列克谢·帕基特诺夫于1984年6月发明的休闲游戏。
游戏玩法是由四个小方块组成的不同形状的板块陆续从屏幕上方落下来,玩家通过调整板块的位置和方向,使它们在屏幕底部拼出完整的一条或几条。这些完整的横条会随即消失,给新落下来的板块腾出空间,与此同时,玩家得到分数奖励。没有被消除掉的方块不断堆积起来,一旦堆到屏幕顶端,玩家便告输,游戏结束。
三、主要编写逻辑介绍
俄罗斯方块涉及的主要难点是各种形状方块的表示,通过观察,可以发现所有形状都是由四块小方块组成。而且形状可以通过旋转变换。所以我们首先提取出方块的所有可能形状如下
要表示这种方块结构,我们可以有不同方式,其中一种我们可以发现方块横竖最多是4个方块,所以我们可以用一个4*4的数组点阵来表示。另外一种是直接用四个小方块坐标表示,将在下期讲解。那么先看看4*4的数组点阵如何表示。
直接定义4*4的整型数组
int nBlocks[4][4];
有方块的值为1,没有方块的为0,即可表示如下图形。
代码如下
tagTeris g_TetrsTemplate[] = {
{
1, 1, 1, 1,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0
},
{
1, 1, 1, 0,
1, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0
},
{
1, 1, 1, 0,
0, 1, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0
},
{
1, 1, 1, 0,
0, 0, 1, 0,
0, 0, 0, 0,
0, 0, 0, 0
},
{
0, 0, 0, 0,
0, 1, 1, 0,
0, 1, 1, 0,
0, 0, 0, 0
},
{
0, 0, 0, 0,
1, 1, 0, 0,
0, 1, 1, 0,
0, 0, 0, 0
},
{
0, 0, 0, 0,
0, 1, 1, 0,
1, 1, 0, 0,
0, 0, 0, 0
}
};
第二个难点就是方块的旋转。有了4*4数组,旋转也就迎刃而解了,我们可以直接把整个数组按顺时针或逆时针方向旋转后设置对应值即可。
左转:
for(i=0;i<4;++i)
for(j=0;j<4;++j)
temp.nBlocks[i][j]=g_TetrsRunning.nBlocks[j][3-i];
右转:
for(i=0;i<4;++i)
for(j=0;j<4;++j)
temp.nBlocks[i][j]=g_TetrsRunning.nBlocks[3-j][i];
当方块表示方法有了,旋转问题也解决了,其他的代码逻辑就简单了,这里就不列出来了,有兴趣的同学可以在文末下载全部代码察看。
四、所有主要逻辑代码
框架代码大同小异,有看之前的文章的话很容易对接起来,就不列出来了。以下就是俄罗斯方块的主要逻辑代码。当然为了教学需求,只是最简单的版本,有兴趣的同学可以适当增加难度和添加更多有趣的元素及界面美化。
Tetris.h
#ifndef _TETRIS_H
#define _TETRIS_H
#include "../import/include/SimpleCG.h"
#define C_IMAGE_BLOCK 20
#define C_MAP_WIDTH 10
#define C_MAP_HEIGHT 20
struct _tagTetris
{
int nBlocks[4][4];
};
typedef struct _tagTetris tagTeris;
int GetSpeed();
//初始化
void NewBlock();
//绘制单个方块
void DrawBlock( int nX, int nY, COLORREF nColor );
//绘制背景
void DrawMapBack( );
//绘制界面
void RenderBack();
//绘制内容
void DrawMap( );
//绘制移动中方块
void DrawMovinggBlock( );
//转动左边
void TurnLeft();
//转动右边
void TurnRight();
//移动左边
void MoveLeft();
//移动右边
void MoveRight();
//获取方块最左最右位置
void GetCurLeftRight(tagTeris *pTest, int *pLeft, int *pRight);
//是否可以往左右移动
bool CanMovHor(int nStepX);
//是否可以往下移动
bool IsExisted(tagTeris *pTest);
//往下移动
bool MovingDown();
//清除
void ClearBlock();
//清除
void ClearLine(int nY);
#endif
Tetris.cpp
#include "Tetris.h"
enum ENUM_MAPTYPE
{
enumMAPTYPE_NULL
, enumMAPTYPE_FIXBLOCK
, enumMAPTYPE_DEADBLOCK
};
int g_nMap[ C_MAP_WIDTH * C_MAP_HEIGHT] = {0};
int g_nXMapPos = 100;
int g_nYMapPos = 50;
tagTeris g_TetrsTemplate[] = {
{
1, 1, 1, 1,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0
},
{
1, 1, 1, 0,
1, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0
},
{
1, 1, 1, 0,
0, 1, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0
},
{
1, 1, 1, 0,
0, 0, 1, 0,
0, 0, 0, 0,
0, 0, 0, 0
},
{
0, 0, 0, 0,
0, 1, 1, 0,
0, 1, 1, 0,
0, 0, 0, 0
},
{
0, 0, 0, 0,
1, 1, 0, 0,
0, 1, 1, 0,
0, 0, 0, 0
},
{
0, 0, 0, 0,
0, 1, 1, 0,
1, 1, 0, 0,
0, 0, 0, 0
}
};
tagTeris g_TetrsRunning = {0};
int g_nMovingX = 0;
int g_nMovingY = 0;
tagTeris g_Next=g_TetrsTemplate[1];
int g_nColorBlockNext = RGB(rand()%255,rand()%255,rand()%255);
int g_nColorBlock = g_nColorBlockNext;
int g_nSpeed = 9;
int g_nScore = 0;
int GetSpeed()
{
return g_nSpeed;
}
//创建新的方块
void NewBlock()
{
g_TetrsRunning = g_Next;
int nCount = sizeof(g_TetrsTemplate)/sizeof(g_TetrsTemplate[0]);
g_Next = g_TetrsTemplate[rand()%nCount];
g_nColorBlock = g_nColorBlockNext;
g_nMovingY = 0;
g_nMovingX = 4;
g_nColorBlockNext = RGB(rand()%255,rand()%255,rand()%255);
g_nScore += 10;
}
//绘制单个方块,绝对坐标
void DrawBlockPos( int nX, int nY, COLORREF nColor )
{
setlinewidth(3);
setfillcolor(nColor);
setlinecolor(SCGSubColor(nColor,100));
fillrectangle( nX, nY, nX + C_IMAGE_BLOCK-3, nY + C_IMAGE_BLOCK-3);
setlinecolor(SCGAddColor(nColor,100));
line(nX, nY, nX + C_IMAGE_BLOCK-5,nY);
line(nX, nY, nX,nY + C_IMAGE_BLOCK-5);
}
//绘制单个方块,地图坐标
void DrawBlock( int nX, int nY, COLORREF nColor )
{
int nXPos = g_nXMapPos + nX*C_IMAGE_BLOCK + 2;
int nYPos = g_nYMapPos + nY*C_IMAGE_BLOCK;
DrawBlockPos(nXPos, nYPos,nColor);
}
//绘制形状方块,地图坐标
void DrawTerisBlock( tagTeris *Tetris, int nX, int nY, COLORREF nColor )
{
int i=0;
int j=0;
for(i=0;i<4;++i)
for(j=0;j<4;++j)
if(Tetris->nBlocks[i][j])
{
DrawBlock( nX + j, i + nY, nColor );
};
}
//绘制背景
void DrawMapBack( )
{
clearrectangle(g_nXMapPos+1,g_nYMapPos-1,g_nXMapPos+1+C_IMAGE_BLOCK*C_MAP_WIDTH, g_nYMapPos+C_IMAGE_BLOCK*C_MAP_HEIGHT);
clearrectangle(320,90,350+C_IMAGE_BLOCK*4, 240);
DrawTerisBlock(&g_Next, 12, 3, g_nColorBlockNext);
outtextXY(350,80,_T("下一个"));
outtextXY(320,180,_T("当前级别"));
outtextXY(320,220,_T("当前分数"));
printfXY( 400, 220, _T("%d"), g_nScore );
printfXY( 400, 180, _T("%d"), 10 - g_nSpeed);
}
//绘制界面
void RenderBack()
{
setline(0, 1, 0);
line(g_nXMapPos,g_nYMapPos,g_nXMapPos,g_nYMapPos+C_IMAGE_BLOCK*C_MAP_HEIGHT);
line(g_nXMapPos+C_IMAGE_BLOCK*C_MAP_WIDTH,g_nYMapPos,g_nXMapPos+C_IMAGE_BLOCK*C_MAP_WIDTH,g_nYMapPos+C_IMAGE_BLOCK*C_MAP_HEIGHT);
line(g_nXMapPos,g_nYMapPos+C_IMAGE_BLOCK*C_MAP_HEIGHT,g_nXMapPos+C_IMAGE_BLOCK*C_MAP_WIDTH,g_nYMapPos+C_IMAGE_BLOCK*C_MAP_HEIGHT);
}
//绘制内容
void DrawMap( )
{
int i=0;
int j=0;
for(j=0;j<C_MAP_HEIGHT;++j)
{
for(i=0;i<C_MAP_WIDTH;++i)
{
switch( g_nMap[j*C_MAP_WIDTH+i] )
{
case enumMAPTYPE_FIXBLOCK:
DrawBlock( i, j, RGB(30,60,235) );
break;
case enumMAPTYPE_DEADBLOCK:
break;
}
}
}
}
//绘制移动中方块
void DrawMovinggBlock( )
{
DrawTerisBlock(&g_TetrsRunning, g_nMovingX, g_nMovingY, g_nColorBlock);
}
//转动左边,00=03,01=13,02=23...10=02,11=12,12=22
void TurnLeft()
{
tagTeris temp;
int i=0;
int j=0;
for(i=0;i<4;++i)
for(j=0;j<4;++j)
temp.nBlocks[i][j]=g_TetrsRunning.nBlocks[j][3-i];
if(!IsExisted(&temp))
return;
int nLeft = 0;
int nRight = 0;
GetCurLeftRight(&temp, &nLeft, &nRight);
if( nLeft<0 )
{
g_nMovingX -= nLeft;
}
if( nRight>=C_MAP_WIDTH )
{
g_nMovingX -= nRight - C_MAP_WIDTH + 1;
}
g_TetrsRunning = temp;
}
//转动右边00=30,01=20,02=10...10=31,11=21,12=11
void TurnRight()
{
tagTeris temp;
int i=0;
int j=0;
for(i=0;i<4;++i)
for(j=0;j<4;++j)
temp.nBlocks[i][j]=g_TetrsRunning.nBlocks[3-j][i];
if(!IsExisted(&temp))
return;
int nLeft = 0;
int nRight = 0;
GetCurLeftRight(&temp, &nLeft, &nRight);
if( nLeft<0 )
{
g_nMovingX -= nLeft;
}
if( nRight>=C_MAP_WIDTH )
{
g_nMovingX -= nRight - C_MAP_WIDTH + 1;
}
g_TetrsRunning = temp;
}
//移动左边
void MoveLeft()
{
if(CanMovHor(-1))
--g_nMovingX;
}
//移动右边
void MoveRight()
{
if(CanMovHor(1))
++g_nMovingX;
}
//获取方块最左位置
void GetCurLeftRight(tagTeris *pTest, int *pLeft, int *pRight)
{
int i=0;
int j=0;
*pLeft = 99;
*pRight = -1;
for(i=0;i<4;++i)
for(j=0;j<4;++j)
{
if(pTest->nBlocks[i][j])
{
if( *pLeft>j + g_nMovingX )
*pLeft = j + g_nMovingX;
if( *pRight<j + g_nMovingX )
*pRight = j + g_nMovingX;
};
}
}
//是否可以往左右移动
bool CanMovHor(int nStepX)
{
int i=0;
int j=0;
for(i=0;i<4;++i)
for(j=0;j<4;++j)
{
if(g_TetrsRunning.nBlocks[i][j])
{
int nX=j + g_nMovingX + nStepX;
int nY=i + g_nMovingY;
if(nX<0 || nX>=C_MAP_WIDTH )
return false;
if( g_nMap[nY*C_MAP_WIDTH+nX] != enumMAPTYPE_NULL )
return false;
};
}
return true;
}
//是否与原来方块冲突
bool IsExisted(tagTeris *pTest)
{
int i=0;
int j=0;
for(i=0;i<4;++i)
for(j=0;j<4;++j)
{
if(pTest->nBlocks[i][j])
{
int nY=i + g_nMovingY + 1;
if( nY>=C_MAP_HEIGHT )
return false;
if( g_nMap[nY*C_MAP_WIDTH+j+g_nMovingX] )
return false;
};
}
return true;
}
//往下移动
bool MovingDown()
{
if(!IsExisted(&g_TetrsRunning))
{
int i=0;
int j=0;
for(i=0;i<4;++i)
for(j=0;j<4;++j)
{
if(g_TetrsRunning.nBlocks[i][j])
{
int nY=i + g_nMovingY;
g_nMap[nY*C_MAP_WIDTH+j+g_nMovingX] = enumMAPTYPE_FIXBLOCK;
//测试是否结束
if(nY<=0)
{
outtextXY(200,20,_T("游戏结束"));
return false;
}
}
}
NewBlock();
return false;
}
++g_nMovingY;
return true;
}
//清除
void ClearBlock()
{
int i=0;
int j=0;
bool bIsClear=true;
for(j=C_MAP_HEIGHT-1;j>=0;--j)
{
bIsClear=true;
for(i=0;i<C_MAP_WIDTH;++i)
{
if( g_nMap[j*C_MAP_WIDTH+i]==enumMAPTYPE_NULL )
{
bIsClear = false;
break;
}
}
if(bIsClear)
{
ClearLine(j);
g_nScore += 100;
if(g_nScore>=(10-g_nSpeed)*1000 && g_nSpeed>0)
--g_nSpeed;
++j;
}
}
}
//清除
void ClearLine(int nY)
{
int i=0;
int j=0;
for(j=nY;j>0;--j)
{
for(i=0;i<C_MAP_WIDTH;++i)
{
g_nMap[j*C_MAP_WIDTH+i]=g_nMap[(j-1)*C_MAP_WIDTH+i];
}
}
}
五、相关代码即图形库下载
GameRussiaTetris · master · b2b160 / SimpleCG_Demo · GitCode
bin/GameRussiaTetris.zip · master · b2b160 / SimpleCG_Demo · GitCode
编译此程序需安装SimpleCG库,安装方法如下: