MFC俄罗斯方块程序解析
流程:
1. 创建CRussiaGame类(游戏功能,可扩展)(代码在最下面)
2. 创建游戏窗前画初始界面(OnPaint()中调用m_RussiaGame.DrawJiemian( 18, 12, this);)
3. 开始游戏
4. 初始化游戏信息(调用m_RussiaGame.StartInit();)
(1)//游戏数组数据清0
(2)//之前方块和当前方块清0
(3)//初始化当前图形(第一次调用之生成当前图形)
(4)//得到当前图形,和之前图形
(5)//把得到的图形放到数组中
5.重画界面(直接调用OnPaint())
6.使用定时器(SetTimer(1,500,NULL);)(游戏开始自动运行)
(1)OnTimer(UINT_PTR nIDEvent)中的处理代码,方块自动运行
void CMFCRussicDlg::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
if (nIDEvent==1)
{
if (m_RussiaGame.end==TRUE)
{
KillTimer(1);
AfxMessageBox("Game Over");//保证定时器先关闭,这时该对话框只调用一次
}
else
{
//方块自动匀速下落
m_RussiaGame.Move(m_MyEnum_AUTO);
//并现在的图形放到游戏数组中
m_RussiaGame.NowIntoRussia();
//重划界面
OnPaint();
}
}
CDialog::OnTimer(nIDEvent);
}
(2)处理来自键盘的按键处理
对话框程序截取按键信息,由于封包键盘的信息已部分自动处理过了,不能直接OnKeyDown,可能会收不到按键信息,处理方法如下:先在PreTranslateMessage(MSG* pMsg)中处理下信息
BOOL CMFCRussicDlg::PreTranslateMessage(MSG* pMsg)
{
// TODO: 在此添加专用代码和/或调用基类
if (pMsg->message == WM_KEYDOWN)
{
OnKeyDown((UINT)pMsg->wParam, (UINT)pMsg->lParam, (UINT)pMsg->lParam);
}
else
{
return CDialog::PreTranslateMessage(pMsg);
}
}
void CMFCRussicDlg::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
if (m_RussiaGame.end==FALSE)
{
switch (nChar)
{
case VK_LEFT://处理键盘->
m_RussiaGame.Move(m_MyEnum_LEFT);
break;
case VK_UP://处理键盘向上箭头
m_RussiaGame.Move(m_MyEnum_UP);
break;
case VK_RIGHT://处理键盘向右箭头
m_RussiaGame.Move(m_MyEnum_RIGHT);
break;
case VK_DOWN://处理键盘向下箭头
m_RussiaGame.Move(m_MyEnum_DOWN);
break;
default:
break;
}
}
CDialog::OnKeyDown(nChar, nRepCnt, nFlags);
}
方块在移动时
(a)要判断是否重叠或过界,则取下移动
(b)不可以向下移动时,要判断是否可以消行
(c)方块旋转选择时,要判断是否有重叠或过界,有的话取消
游戏结束判断:
当新出现的方块,在游戏数组已经有重叠,说明游戏到顶了,游戏结束,这个放在消行函数中判断(需要判断消行,证明当前方块到底了,已停止运动,要生成新的方块)
CRusssia类的代码
class CRussiaGame
{
public:
CRussiaGame();
virtual ~CRussiaGame();
public:
int m_RowCount; //游戏窗口行数
int m_ColumnCount; //游戏窗口列数
int Russia[100][100]; //游戏数组
int Now[4][4]; //当前图形
int Will[4][4]; //上一个图行
bool end; //游戏结束状态
CPoint NowPos; //当前棋子左上角的位置
/*方向键(←): VK_LEFT(37)方向键(↑): VK_UP(38)方向键(→): VK_RIGHT(39)方向键(↓): VK_DOWN(40)*/
enum MyEnum{m_MyEnum_LEFT=37, m_MyEnum_UP, m_MyEnum_RIGHT, m_MyEnum_DOWN, m_MyEnum_AUTO};//m_MyEnum_AUTO表示无操作是自动向下移动
public:
void DrawJiemian(int m_RowCount, int m_ColumnCount, CWnd* pWnd);//窗口界面画图函数
void Draw_Now_Will(); //画当前图形和前一个图形,并把现在的图形放到游戏数组中
void StartInit(); //游戏开始所需初始化信息
void NowIntoRussia(); //把现在的图形放到游戏数组中
void Move(int movekey); //现在图形在游戏数组中移动
bool CHeckNoMeet(int (*a)[4], int movekey, CPoint p);//方块移动时,如果过届或重叠应取消
void LineDelete(); //消行函数,(可扩展计分功能)
void change(); //棋子转换,并如果出界,或重叠,则还原
};
CRussiaGame::CRussiaGame()
{
}
CRussiaGame::~CRussiaGame()
{
}
//窗口界面画图函数
void CRussiaGame::DrawJiemian(int m_RowCount, int m_ColumnCount, CWnd* pWnd)
{
//获取游戏界面相关画图画图工具
CDC* pDC = pWnd->GetDC();
CClientDC dc(pWnd);
CDC dcCompatible;
CDC dcCompatible1;
//画笔选择
CPen pen(PS_SOLID, 0, RGB(0, 0, 255));
CPen* oldpen = dc.SelectObject(&pen);
//载入背景图片
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAP1);
dcCompatible.CreateCompatibleDC(pDC);
dcCompatible.SelectObject(&bitmap);
//填充背景图片
for (int i = 0; i < m_RowCount; i++)
{
for (int j = 0; j< m_ColumnCount; j++)
{
pDC->BitBlt(0 + j * 35, 20 + i * 35, 35, 35,
&dcCompatible, 0, 0, SRCCOPY);
}
}
DeleteDC(dcCompatible);
//画网格线,从上往下
for (int i = 0, j = 0; i <=m_ColumnCount; i++)
{
dc.MoveTo(j, 20);
dc.LineTo(j, 20 + 35 *m_RowCount);
j += 35;
}
//从左往右
for (int i = 0, j = 0; i <= m_RowCount; i++)
{
dc.MoveTo(0, 20 + j);
dc.LineTo(m_ColumnCount * 35, 20 + j);
j += 35;
}
CBitmap bitmap1;
bitmap1.LoadBitmap(IDB_BITMAP2);
dcCompatible1.CreateCompatibleDC(pDC);
dcCompatible1.SelectObject(&bitmap1);
for (int i = 0; i < m_RowCount; i++)
{
for (int j = 0; j < m_ColumnCount; j++)
{
if (Russia[i][j]==1)
{
pDC->BitBlt(0 + j * 35, 20 + i * 35, 35, 35,&dcCompatible1, 0, 0, SRCCOPY);
}
}
}
DeleteDC(dcCompatible1);
dc.SelectObject(oldpen);
pDC->Detach();
}
//画当前图形和前一个图形,
void CRussiaGame::Draw_Now_Will()
{
int i, j;
//将之前的出现的方块放到现在的方块中,并把之前的方块数字清0
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
Now[i][j] = Will[i][j];
Will[i][j] = 0;
}
}
//初始化随机种子
srand((unsigned)time(NULL));
int nTemp = rand() % 7; //7表示可以方块的种类
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[1][0] = 1;
Will[1][1] = 1;
Will[1][2] = 1;
break;
case 2:
Will[0][0] = 1;
Will[0][1] = 1;
Will[0][2] = 1;
Will[0][3] = 1;
break;
case 3:
Will[0][2] = 1;
Will[1][0] = 1;
Will[1][1] = 1;
Will[1][2] = 1;
break;
case 4:
Will[0][0] = 1;
Will[0][1] = 1;
Will[1][1] = 1;
Will[1][2] = 1;
break;
case 5:
Will[0][1] = 1;
Will[0][2] = 1;
Will[1][0] = 1;
Will[1][1] = 1;
break;
case 6:
Will[0][1] = 1;
Will[1][0] = 1;
Will[1][1] = 1;
Will[1][2] = 1;
break;
//方块扩展备用
case 7:
break;
case 8:
break;
default:
break;
}
NowPos.x = m_ColumnCount / 2;
NowPos.y= 0;
}
//游戏开始所需初始化信息
void CRussiaGame::StartInit()
{
end = FALSE;
m_ColumnCount = 12;
m_RowCount = 18;
//游戏数组数据清0
for (int i = 0; i < m_RowCount; i++)
{
for (int j = 0; j < m_ColumnCount; j++)
{
Russia[i][j] = 0;
}
}
//之前方块和当前方块清0
for (size_t i = 0; i < 4; i++)
{
for (size_t j = 0; j < 4; j++)
{
Now[i][j] = 0;
}
}
//初始化当前图形(第一次调用之生成当前图形)
Draw_Now_Will();
Sleep(500);
//得到当前图形,和之前图形
Draw_Now_Will();
//把得到的图形放到数组中
NowIntoRussia();
}
//把现在的图形放到游戏数组中
void CRussiaGame::NowIntoRussia()
{
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (Now[j][i] == 1)//一定要检测,防止其他是1的地方被清0了
{
Russia[NowPos.y + j][NowPos.x + i] = 1;
}
}
}
}
//现在图形在游戏数组中移动
void CRussiaGame::Move(int movekey)
{
if (end)return;
switch (movekey)
{
case m_MyEnum_LEFT:
if (!CHeckNoMeet(Now, m_MyEnum_LEFT, NowPos)) { break; }
NowPos.x -= 1;
break;
case m_MyEnum_UP:
change();
break;
case m_MyEnum_RIGHT:
if (!CHeckNoMeet(Now, m_MyEnum_RIGHT, NowPos)) { break; }
NowPos.x += 1;
break;
case m_MyEnum_DOWN:
if (!CHeckNoMeet(Now, m_MyEnum_DOWN, NowPos)) { break; }
NowPos.y += 1;
break;
case m_MyEnum_AUTO:
if (!CHeckNoMeet(Now, m_MyEnum_AUTO, NowPos))
{
LineDelete();//消行设计
NowIntoRussia();
break;
}
NowPos.y += 1;
break;
default:
break;
}
//AfxMessageBox("Game Over");没有问题
}
//方块移动时,如果过届或重叠应取消,返回True表示没有过届或重叠,false表示过届或重叠
bool CRussiaGame::CHeckNoMeet(int (*a)[4], int movekey, CPoint p)
{
int i, j;
//把原先位置清0
for ( i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
if (a[j][i] == 1)
{
Russia[p.y + j][p.x + i] = 0;
}
}
}
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
if (a[j][i]==1)//首先把所有存图的点找出来
{
switch (movekey)
{
case m_MyEnum_LEFT:
if ((p.x +i- 1) <0) goto exit;
if (Russia[p.y + j][p.x + i-1] == 1) goto exit;
break;
case m_MyEnum_UP:
break;
case m_MyEnum_RIGHT:
if ((p.x +i+ 1)>=m_ColumnCount) goto exit;
if (Russia[p.y + j][p.x + i +1] == 1) goto exit;
break;
case m_MyEnum_DOWN:
if ((p.y +j+ 1) >= m_RowCount) goto exit;
if (Russia[p.y + j + 1][p.x + i] == 1) goto exit;
break;
case m_MyEnum_AUTO:
if ((p.y+j+1) >= m_RowCount) goto exit;
if (Russia[p.y+j+1][p.x+i]==1) goto exit;
break;
default:
break;
}
}
}
}
//移动位置,重新给数组赋值
switch (movekey)
{
case m_MyEnum_LEFT:
p.x--;
break;
case m_MyEnum_UP:
break;
case m_MyEnum_RIGHT:
p.x++;
break;
case m_MyEnum_DOWN:
p.y++;
break;
case m_MyEnum_AUTO:
p.y++;
break;
default:
break;
}
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
if (Now[j][i] == 1)
{
Russia[p.y + j][p.x + i] = 1;
}
}
}
return true;
//移动过界或重叠,返回失false,还原原样
exit:
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
if (Now[j][i] == 1)
{
Russia[p.y + j][p.x + i] = 1;
}
}
}
return false;
}
//消行函数,(可扩展计分功能,增强难度<调速>),
void CRussiaGame::LineDelete()
{
int m = 0; //本次共消去的行数
bool flag = FALSE; //消行标记
for (int i = 0; i < m_RowCount; i++)
{
flag = TRUE;
for (int j = 0; j < m_ColumnCount; j++)
{
if (Russia[i][j]==0)
{
flag = FALSE;
break;
}
}
//如果要消行
if (flag)
{
m++;
for (int k = i; k>0 ; k--)
{
//上行给下行
for (int l = 0; l < m_ColumnCount; l++)
{
Russia[k][l] = Russia[k - 1][l];
}
}
//第一行为0
for (int l = 0; l < m_ColumnCount; l++)
{
Russia[0][l] = 0;
}
}
}
Draw_Now_Will();//需要判断消行,证明当前方块到底了,已停止运动,要生成新的方块
//判断游戏是否结束
for (int i = 0; i < 4; i++)
{
for (int j = 0; j < 4; j++)
{
if (Now[j][i] == 1)
{
if (Russia[NowPos.y+j][ NowPos.x+i] == 1)//到达顶点,游戏结束
{
end = TRUE;
AfxMessageBox("Game Over1");
return ;
}
}
}
}
//AfxMessageBox("Game Over1");//没有问题
}
//方块旋转
void CRussiaGame::change()
{
int temp[4][4];
int i, j;
//清空图形在游戏数组的数据
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
if (Now[i][j]==1)
{
Russia[NowPos.y+i][NowPos.x+j] = 0;
}
}
}
//把Now数据放到Temp中
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
temp[i][j] = Now[i][j];
}
}
//进行旋转
for (i = 0; i < 2; i++)
{
for (j = 0; j < 2; j++)
{
int t;
t = Now[i][j];
Now[i][j] =Now[3-j][i] ;
Now[3 - j][i] = Now[3-i][3-j];
Now[3 - i][3 - j]=Now[j][3-i];
Now[j][3 - i] = t;
}
}
//判断是否过界,如果过界则还原数据
//(1)循环把所有存图的点找出来
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
if (Now[i][j] == 1)
{
//判断点放到数组中过界
if (NowPos.x+j<0|| NowPos.x + j>=m_ColumnCount||NowPos.y+i<0|| NowPos.y + i>=m_RowCount)
{
goto exit;
}
//判断是否重叠,如果重叠则还原数据
if (Russia[NowPos.y + i][NowPos.x + j] ==1)
{
goto exit;
}
}
}
}
//所有点没有过界或重叠,把旋转后得到Now中数据放到游戏数组中
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
if (Now[i][j] == 1)
{
Russia[NowPos.y + i][NowPos.x + j] = 1;
}
}
}
return;
exit:
//还原数据
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
Now[i][j] = temp[i][j];
}
}
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
if (Now[i][j] == 1)
{
Russia[NowPos.y + i][NowPos.x + j] = 1;
}
}
}
}