说明
这是使用 Visual Studio 2013的MFC单文档 开发的一个 简单的 俄罗斯方块游戏。适合初学者,源码中包含 大量注释 ,下载文件中包含 XMind思维导图 辅助理解。
游戏功能介绍
===== RBox_1.0 功能实现 =====
1.开始游戏
2.方块的创建
3.下一方块的预览
4.方块的自由下落
5.方块的碰撞检测(包含变形时碰撞检测)
6.键盘控制方块的移动
7.游戏结束判定
8.方块下落速度随得分加快
===== RBox_1.1 功能更新 =====
1.添加了游戏暂停
2.添加了按下数字键改变下一方块类型的功能
游戏界面
游戏数据
#define MAX_ROW 22 //设置行数为22
#define MAX_COL 12 //设置列数为12
#define BOX_SIZE 30 //设置方块大小为30
#define MIN_ROW_COL 4 //设置下一方块、下落方块在4行4列方块中
#define KEY_UP 0
#define KEY_DOWN 1
#define KEY_LEFT 2
#define KEY_RIGHT 3
//所有成员变量
bool m_boxMap[MAX_ROW][MAX_COL]; //地图方块
bool m_boxNow[MIN_ROW_COL][MIN_ROW_COL]; //下落方块
bool m_boxWill[MIN_ROW_COL][MIN_ROW_COL]; //下一方块
CPoint m_potNow; //下落方块的左上点坐标
bool m_bisEnd; //游戏结束标志
int m_iScore; //游戏得分
int m_iSpeed; //下落速度
bool m_bisGaming; //是否在游戏中
核心代码
● 创建方块
void CRBoxView::boxCreate() //赋值下落方块,产生下一方块
{
int iRow, iCol;
for (iRow = 0; iRow < MIN_ROW_COL; iRow++)
{
for (iCol = 0; iCol < MIN_ROW_COL; iCol++)
{
m_boxNow[iRow][iCol] = m_boxWill[iRow][iCol];
m_boxWill[iRow][iCol] = false;
}
}
srand((unsigned)time(0));
int iType = rand() % 4; //四种方块,其中L型有两种
switch (iType)
{
case 0:
m_boxWill[0][0] = 1; // ■ □ □ □
m_boxWill[1][0] = 1; // ■ □ □ □
m_boxWill[2][0] = 1; // ■ □ □ □
m_boxWill[3][0] = 1; // ■ □ □ □
break;
case 1:
m_boxWill[0][0] = 1; // ■ ■ □ □
m_boxWill[0][1] = 1; // ■ □ □ □
m_boxWill[1][0] = 1; // ■ □ □ □
m_boxWill[2][0] = 1; // □ □ □ □
break;
case 2:
m_boxWill[0][0] = 1; // ■ ■ □ □
m_boxWill[1][1] = 1; // □ ■ □ □
m_boxWill[0][1] = 1; // □ ■ □ □
m_boxWill[2][1] = 1; // □ □ □ □
break;
case 3:
m_boxWill[0][0] = 1; // ■ ■ □ □
m_boxWill[0][1] = 1; // ■ ■ □ □
m_boxWill[1][0] = 1; // □ □ □ □
m_boxWill[1][1] = 1; // □ □ □ □
break;
default:
break;
}
//重置下落方块出现的位置
m_potNow.x = 0;
m_potNow.y = MAX_COL / 2;
}
● 方块移动
void CRBoxView::boxMove(int iKey)
{
if (m_bisGaming)
{
bool bisHit = false;//碰撞状态
switch (iKey)
{
case KEY_UP:
boxHitJudge(m_boxNow, KEY_UP, m_potNow);
break;
case KEY_DOWN:
bisHit = boxHitJudge(m_boxNow, KEY_DOWN, m_potNow);
if (bisHit == true) //若为下边界碰撞
{
boxLineDel(); //判断消行
boxCreate(); //产生新下落方块和下一方块
m_bisEnd = gameOver(); //游戏是否结束
}
break;
case KEY_LEFT:
boxHitJudge(m_boxNow, KEY_LEFT, m_potNow);
break;
case KEY_RIGHT:
boxHitJudge(m_boxNow, KEY_RIGHT, m_potNow);
break;
default:
break;
}
}
}
● 碰撞检测
bool CRBoxView::boxHitJudge(bool boxTmp[4][4], int iKey, CPoint & p)
{
int iRow, iCol;
//清空下落方块经过的路径
for (iRow = 0; iRow < MIN_ROW_COL; iRow++)
{
for (iCol = 0; iCol < MIN_ROW_COL; iCol++)
{
if (boxTmp[iRow][iCol] == true)
{
m_boxMap[p.x + iRow][p.y + iCol] = false; //把原下落方块擦除
}
}
}
//碰撞检测
for (iRow = 0; iRow < MIN_ROW_COL; iRow++)
{
for (iCol = 0; iCol < MIN_ROW_COL; iCol++)
{
if (boxTmp[iRow][iCol] == true)
{
switch (iKey)
{
case KEY_UP:
{
bool bisRoll = boxRoll(boxTmp, p);
if (bisRoll == true) //可以旋转
return false; //返回未发生碰撞
}break;
case KEY_DOWN:
{
if ((p.x + iRow + 1) >= MAX_ROW) //与底边界碰撞
goto HIT_TRUE;
if (m_boxMap[p.x + iRow + 1][p.y + iCol] == true) //与地图上堆积的方块碰撞
goto HIT_TRUE;
}break;
case KEY_LEFT:
{
if ((p.y + iCol - 1) < 0) //与左边界碰撞
goto HIT_TRUE;
if (m_boxMap[p.x + iRow][p.y + iCol - 1]) //与地图上堆积的方块碰撞
goto HIT_TRUE;
}break;
case KEY_RIGHT:
{
if ((p.y + iCol + 1) >= MAX_COL) //与右边界碰撞
goto HIT_TRUE;
if (m_boxMap[p.x + iRow][p.y + iCol + 1]) //与地图上堆积的方块碰撞
goto HIT_TRUE;
}break;
default:
break;
}
}
}
}
switch (iKey)
{
case KEY_UP:
break;
case KEY_DOWN:
p.x++;
break;
case KEY_LEFT:
p.y--;
break;
case KEY_RIGHT:
p.y++;
break;
default:
break;
}
//未发生碰撞,下落方块位置变动后更新到地图上
for (iRow = 0; iRow < MIN_ROW_COL; iRow++)
{
for (iCol = 0; iCol < MIN_ROW_COL; iCol++)
{
if (boxTmp[iRow][iCol] == true)
{
m_boxMap[p.x + iRow][p.y + iCol] = true;
}
}
}
return false; //返回未发生碰撞
//发生碰撞,下落方块位置不变的更新到地图上
HIT_TRUE:
for (iRow = 0; iRow < MIN_ROW_COL; iRow++)
{
for (iCol = 0; iCol < MIN_ROW_COL; iCol++)
{
if (boxTmp[iRow][iCol] == true)
{
m_boxMap[p.x + iRow][p.y + iCol] = true;
}
}
}
return true; //返回发生碰撞
}
● 方块旋转
bool CRBoxView::boxRoll(bool boxTmp[4][4], CPoint & p)
{
bool boxPre[MIN_ROW_COL][MIN_ROW_COL]; //暂存存放
bool boxFin[MIN_ROW_COL][MIN_ROW_COL]; //完成旋转
int iMinRow = MIN_ROW_COL;
int iMinCol = MIN_ROW_COL;
int iRow, iCol;
bool bisRightOver = false; //超过右边界
int iRightOver = 0; //用于记录超出有边界的列数
if (p.x >= 4) //下落方块完全进入地图界面
{
for (iRow = 0; iRow < MIN_ROW_COL; iRow++) // ■ ■ □ □ □ □ □ □
{ // ■ □ □ □ 逆时针旋转-> □ □ □ □
for (iCol = 0; iCol < MIN_ROW_COL; iCol++) // ■ □ □ □ ■ □ □ □
{ // □ □ □ □ ■ ■ ■ □
boxPre[iRow][iCol] = boxTmp[iCol][3 - iRow]; // 将方块逆时针旋转90°给暂存数组
boxFin[iRow][iCol] = false;
}
}
for (iRow = 0; iRow < MIN_ROW_COL; iRow++)
{
for (iCol = 0; iCol < MIN_ROW_COL; iCol++)
{
if (boxPre[iRow][iCol] == true)
{
if (iRow < iMinRow)
iMinRow = iRow;
if (iCol < iMinCol)
iMinCol = iCol;
}
}
}
for (iRow = iMinRow; iRow < MIN_ROW_COL; iRow++) // □ □ □ □ ■ □ □ □
{ // □ □ □ □ 移动-> ■ ■ ■ □
for (iCol = iMinCol; iCol < MIN_ROW_COL; iCol++) // ■ □ □ □ □ □ □ □
{ // ■ ■ ■ □ □ □ □ □
boxFin[iRow - iMinRow][iCol - iMinCol] = boxPre[iRow][iCol]; // 将暂存数组中的方块移至左上角 给完成数组
}
}
for (iRow = 0; iRow < MIN_ROW_COL; iRow++)
{
for (iCol = 0; iCol < MIN_ROW_COL; iCol++)
{
if (boxFin[iRow][iCol] == false)
continue;
if ((p.x + iRow) >= MAX_ROW) //碰撞地图下边界
return false;
if (m_boxMap[p.x + iRow][p.y + iCol] == true) //碰撞到地图上原本存在的方块
return false;
if ((p.y + iCol) >= MAX_COL) //超过右边界
{
bisRightOver = true;
iRightOver = p.y + iCol + 1 - MAX_COL; //记录超出右边界多少
}
}
}
// 右边界
// ||
if (bisRightOver == true) //超出右边界的情况的处理 // ■ ■ || □ □ ■ □ || □ □ ■ □ □ || □
{ // ■ □ || □ □ -> ■ ■ || ■ □ -> ■ ■ ■ || □
p.y -= iRightOver; //将方块向左移动使其不超出右边界 // ■ □ || □ □ □ □ || □ □ □ □ □ || □
} // □ □ || □ □ □ □ || □ □ □ □ □ || □
for (iRow = 0; iRow < MIN_ROW_COL; iRow++)
{
for (iCol = 0; iCol < MIN_ROW_COL; iCol++)
{
boxTmp[iRow][iCol] = boxFin[iRow][iCol];
}
}
return true; //旋转成功
}
else
return false;
}
● 消行
void CRBoxView::boxLineDel()
{
int iRow, iCol;
bool bisDel; //是否可消行
for (iRow = 0; iRow < MAX_ROW; iRow++)
{
bisDel = true;
for (iCol = 0; iCol < MAX_COL; iCol++)
{
if (m_boxMap[iRow][iCol] == false) //当前行存在空方块就不可消行
{
bisDel = false;
}
}
if (bisDel == true) //当前行可消
{
for (int iRowTmp = iRow; iRowTmp > 0; iRowTmp--)
{
for (int iColTmp = 0; iColTmp < MAX_COL; iColTmp++)
{
//将当前行以上的方块都向下移动一行
m_boxMap[iRowTmp][iColTmp] = m_boxMap[iRowTmp - 1][iColTmp];
}
}
for (int iFirst = 0; iFirst < MAX_COL; iFirst++)
{
m_boxMap[0][iFirst] = false; //手动清空第一行
}
m_iScore +=100; //消行记分
//游戏难度增加
switch (m_iScore/100)
{
case 10:
m_iSpeed = 300;
break;
case 20:
m_iSpeed = 200;
break;
case 30:
m_iSpeed = 100;
default:
break;
}
SetTimer(1, m_iSpeed, NULL);
}
}
}