今天,就要开始做游戏的主要部分了,如何控制游戏。
4.
控制操作
我们的控制就是,先空出一个格子不显示,然后单击到这个空格子的周围的格子的图片时,就移动过去。
为此,我们要先把空格子画出来。昨天我们的图片其实还是完整显示的。要稍微修改一下我们的
OnPaint
函数的代码了。
先看代码。为了省地方,我就把和昨天一样的地方省略了,用省略号代替。
- // 用于重绘
- void CPuzzleView::OnPaint(void)
- {
- HDC hdcMem; //内存DC
- HDC hdcScr = GetDC(m_hWnd); //获取屏幕的DC
- hdcMem = CreateCompatibleDC(hdcScr); //创建兼容屏幕的内存DC
- //获取窗口大小
- RECT rect;
- GetClientRect(m_hWnd, &rect);
- //按窗口大小创建兼容位图
- HBITMAP hTemp= CreateCompatibleBitmap(hdcScr, rect.right, rect.bottom);
- SelectObject(hdcMem, hTemp); //绑定临时背景到内存DC
- //创建一个临时背景DC
- HDC hdcTempBK = CreateCompatibleDC(hdcMem);
- SelectObject(hdcTempBK, m_hBmpBack);
- BitBlt(hdcMem, 0, 0, SCRWIDTH, SCRHEIGHT, hdcTempBK, 0, 0, SRCCOPY);//为内存DC画背景
- DeleteDC(hdcTempBK);
- //画右上角的小图像
- ……
- //画主图像
- HDC hdcGameMem = CreateCompatibleDC(hdcMem);
- SelectObject(hdcGameMem, m_hBmpGame);
- if(m_IsGameStarted==true)
- {
- for(int i=0; i<m_FrameNum; ++i)
- {
- for(int j=0; j<m_FrameNum; ++j)
- {
- ……
- if(m_Block[j+m_FrameNum*i] == LASTBLOCK)
- {
- continue;//略掉最后一块不画
- }
- ……
- }
- }
- }
- else
- {
- BitBlt(hdcMem, m_Game_x, m_Game_y, m_Game_width, m_Game_height, hdcGameMem, 0, 0, SRCCOPY);
- }
- ……
- }
先看这部分。
- //获取窗口大小
- RECT rect;
- GetClientRect(m_hWnd, &rect);
- //按窗口大小创建兼容位图
- HBITMAP hTemp= CreateCompatibleBitmap(hdcScr, rect.right, rect.bottom);
- SelectObject(hdcMem, hTemp); //绑定临时背景到内存DC
- //创建一个临时背景DC
- HDC hdcTempBK = CreateCompatibleDC(hdcMem);
- SelectObject(hdcTempBK, m_hBmpBack);
- BitBlt(hdcMem, 0, 0, SCRWIDTH, SCRHEIGHT, hdcTempBK, 0, 0, SRCCOPY);//为内存DC画背景
按照昨天的方法,虽然正确显示,但是由于
SelectObject(hdcMem, m_hBmpBack);
是绑定到内存
DC
中的位图,这样,当我们对内存
DC
进行操作时,实际上就是修改了
m_hBmpBack
里的内容,最后显示的就是它的,而这个变量是存储我们的背景图的,它一改,就相当于背景改变了,就会发现你想要的空格根本就出不来。
所以,我们先创建了一个兼容屏幕的位图,这个位图我们还没有填充任何东西,如果直接显示,就是黑色的。然后绑定到内存
DC
中,这样,我们以后更改的就是这个位图,和背景位图没关系了。
但是,我们还要显示背景位图,于是我采用了再创建一个
DC
,用来把背景显示出来的方法。
if(m_Block[j+m_FrameNum*i] == LASTBLOCK)
{
continue;//
略掉最后一块不画
}
然后用这个来判断,是不是最后一个方格,如果是的话,就不要画图了。这样就可以空出来最后一块。
其实今天的主要任务是实现单击命令。单击之后要判断,现在只用图来判断就不太好了,所以今天的主要任务是
CPuzzleLogic
里的内容。今天的比较抽象,不太好理解,我尽量说得详细些。
- // 移动当前点击的,成功移动,返回true,否则返回false
- bool CPuzzleLogic::MoveBlock(int Pos)
- {
- //判断游戏是否在进行中
- if(m_IsPlaying == false)
- {
- return false;
- }
- //如果是最后一个,则是空的,所以不能移动
- if(m_Block[Pos]==LASTBLOCK)
- {
- return false;
- }
- //判断一下是否越界了
- if(Pos<0 || Pos>m_BlockNum*m_BlockNum-1)
- {
- return false;
- }
- //先进行判断,是否在左右两侧
- //用一个变量的四位表示这个位置的四个方向是否都在游戏区
- //1111,表示四个方向都在游戏区内
- //从左开始,第一位表示左,第二位表示右,第三位表示上,第四位表示下
- int result = 0;
- int temp = Pos%m_BlockNum;
- //不在最左侧列
- if(temp != 0)
- {
- result = 1;
- }
- //不在最右侧
- result = result*10;
- if(temp != m_BlockNum-1)
- {
- result += 1;
- }
- //不在最上行
- result = result*10;
- if(Pos > m_BlockNum-1)
- {
- result += 1;
- }
- //不在最下行
- result = result*10;
- if(Pos < =m_BlockNum*m_BlockNum-m_BlockNum)
- {
- result += 1;
- }
- for(int i=0; i<4; ++i)
- {
- bool bIsMove=false;
- int r = result%10*10;
- r = pow((float)r, i);
- switch(r)
- {
- case 1000://表示左侧有格子
- if(m_Block[Pos-1] == LASTBLOCK)
- {
- MovePos(Pos-1, Pos);
- bIsMove = true;
- }
- break;
- case 100://表示右侧还有格子
- if(m_Block[Pos+1] == LASTBLOCK)
- {
- MovePos(Pos+1, Pos);
- bIsMove = true;
- }
- break;
- case 10://表示上面有格子
- if(m_Block[Pos-m_BlockNum] == LASTBLOCK)
- {
- MovePos(Pos-m_BlockNum, Pos);
- bIsMove = true;
- }
- break;
- case 1://表示下面有格子
- if(m_Block[Pos+m_BlockNum] == LASTBLOCK)
- {
- MovePos(Pos+m_BlockNum, Pos);
- bIsMove = true;
- }
- break;
- }
- result /= 10;
- if(bIsMove)
- {
- return true;
- }
- }
- return false;
- }
- // 移动位置
- void CPuzzleLogic::MovePos(int dstPos, int srcPos)
- {
- GameType temp = m_Block[srcPos];
- m_Block[srcPos] = m_Block[dstPos];
- m_Block[dstPos] = temp;
- }
- // 判断是否胜利
- bool CPuzzleLogic::IsVictor(void)
- {
- for(int i=0; i<m_BlockNum; ++i)
- {
- for(int j=0; j<m_BlockNum; ++j)
- {
- if(m_Block[i*m_BlockNum+j] != i*10+j)
- {
- return false;
- }
- }
- }
- return true;
- }
- // 设置游戏开始与暂停
- void CPuzzleLogic::SetGamePlaying(bool bIsPlaying)
- {
- m_IsPlaying = bIsPlaying;
- }
这是主要代码。我们的过程是单击一个空格子周围的图片,图片就移到空格子,其实就是把空格子和单击的格子交换了一下位置,因此,
MovePos(int dstPos, int srcPos)
函数实现了这个功能,这个很简单,就不多说了。
我们要进行的判断是单击的格子四周是否有空格,这个用一下偏移量就可以解决了,因为我们是一维数组,比如我举例一共是
3*3
的,其中最大的
8
是空格(从
0
开始的),如下
0 2 3
1 4 8
5 6 7
很明显的,如果我们单击的是
4
(坐标也为
4
),直接判断坐标
4
-
1
,
4+1
,
4
-
3
,
4+3
位置的是不是
8
就可以了,这没问题。
但是,如果我们点的是
3
(坐标为
2
)
2
-
3
为-
1
,
2+1
就是下一行的开头了,显然这和我们预期的结果不一样,
2
-
3
为-
1
,如果我们去取值判断,就会发生错误。而
2+1
成为下一行第一个,显示这两个在我们看来并没有挨着,是不能交换的,如果
8
恰好在那个位置,我们就可以交换了,这个结果显然是不正确的。
所以,我们要先判断一下单击的位置,它有几个方向可以去查看。
- // 移动当前点击的,成功移动,返回true,否则返回false
- bool CPuzzleLogic::MoveBlock(int Pos)
- {
- //判断游戏是否在进行中
- if(m_IsPlaying == false)
- {
- return false;
- }
- //如果是最后一个,则是空的,所以不能移动
- if(m_Block[Pos]==LASTBLOCK)
- {
- return false;
- }
- //判断一下是否越界了
- if(Pos<0 || Pos>m_BlockNum*m_BlockNum-1)
- {
- return false;
- }
- //先进行判断,是否在左右两侧
- //用一个变量的四位表示这个位置的四个方向是否都在游戏区
- //1111,表示四个方向都在游戏区内
- //从左开始,第一位表示左,第二位表示右,第三位表示上,第四位表示下
- int result = 0;
- int temp = Pos%m_BlockNum;
- //不在最左侧列
- if(temp != 0)
- {
- result = 1;
- }
- //不在最右侧
- result = result*10;
- if(temp != m_BlockNum-1)
- {
- result += 1;
- }
- //不在最上行
- result = result*10;
- if(Pos > m_BlockNum-1)
- {
- result += 1;
- }
- //不在最下行
- result = result*10;
- if(Pos < =m_BlockNum*m_BlockNum-m_BlockNum)
- {
- result += 1;
- }
- for(int i=0; i<4; ++i)
- {
- bool bIsMove=false;
- int r = result%10*10;
- r = pow((float)r, i);
- switch(r)
- {
- case 1000://表示左侧有格子
- if(m_Block[Pos-1] == LASTBLOCK)
- {
- MovePos(Pos-1, Pos);
- bIsMove = true;
- }
- break;
- case 100://表示右侧还有格子
- if(m_Block[Pos+1] == LASTBLOCK)
- {
- MovePos(Pos+1, Pos);
- bIsMove = true;
- }
- break;
- case 10://表示上面有格子
- if(m_Block[Pos-m_BlockNum] == LASTBLOCK)
- {
- MovePos(Pos-m_BlockNum, Pos);
- bIsMove = true;
- }
- break;
- case 1://表示下面有格子
- if(m_Block[Pos+m_BlockNum] == LASTBLOCK)
- {
- MovePos(Pos+m_BlockNum, Pos);
- bIsMove = true;
- }
- break;
- }
- result /= 10;
- if(bIsMove)
- {
- return true;
- }
- }
- return false;
- }
- // 移动位置
- void CPuzzleLogic::MovePos(int dstPos, int srcPos)
- {
- GameType temp = m_Block[srcPos];
- m_Block[srcPos] = m_Block[dstPos];
- m_Block[dstPos] = temp;
- }
- // 判断是否胜利
- bool CPuzzleLogic::IsVictor(void)
- {
- for(int i=0; i<m_BlockNum; ++i)
- {
- for(int j=0; j<m_BlockNum; ++j)
- {
- if(m_Block[i*m_BlockNum+j] != i*10+j)
- {
- return false;
- }
- }
- }
- return true;
- }
- // 设置游戏开始与暂停
- void CPuzzleLogic::SetGamePlaying(bool bIsPlaying)
- {
- m_IsPlaying = bIsPlaying;
- }
就是这段代码,我用的是一个四位数来表示四个方向,其实最好是用
4
位的二进制,可是左移右移我有时候感觉总会弄错,所以用了
10
进制的。
上面的注释有写什么代表什么,
int temp = Pos%m_BlockNum;
Pos
是当前单击的坐标,无论它在哪里,当它除以行数取余的时候,就是它对应的第一行的位置,这样,我们只要再判断它是否是
0
或者列数减
1
就知道是不是左右两侧那一列上的位置了。把相应的可以判断的位置标示为
1.
下面还要判断是不是最上面的和最下面的行的问题。这个比较容易,最上面一行就是
0~ m_BlockNum-1
,如果大于
m_BlockNum-1
就不在最上面一行,最下面一行就是
m_BlockNum*m_BlockNum-m_BlockNum+1~ m_BlockNum*m_BlockNum
,如果不在这个区域内,则证明不在最下面一行。
当然,在这之前还要判断是否单击在游戏区了,当然,你可能还记得我们在这之前也判断过,多判断一次也不是坏事,还要判断游戏是否在进行中,还要判断单击的是不是那个空格子。都是函数最前面。
移动方法
有了这个位置,就可以写移动的方法了。
- // 移动当前点击的,成功移动,返回true,否则返回false
- bool CPuzzleLogic::MoveBlock(int Pos)
- {
- //判断游戏是否在进行中
- if(m_IsPlaying == false)
- {
- return false;
- }
- //如果是最后一个,则是空的,所以不能移动
- if(m_Block[Pos]==LASTBLOCK)
- {
- return false;
- }
- //判断一下是否越界了
- if(Pos<0 || Pos>m_BlockNum*m_BlockNum-1)
- {
- return false;
- }
- //先进行判断,是否在左右两侧
- //用一个变量的四位表示这个位置的四个方向是否都在游戏区
- //1111,表示四个方向都在游戏区内
- //从左开始,第一位表示左,第二位表示右,第三位表示上,第四位表示下
- int result = 0;
- int temp = Pos%m_BlockNum;
- //不在最左侧列
- if(temp != 0)
- {
- result = 1;
- }
- //不在最右侧
- result = result*10;
- if(temp != m_BlockNum-1)
- {
- result += 1;
- }
- //不在最上行
- result = result*10;
- if(Pos > m_BlockNum-1)
- {
- result += 1;
- }
- //不在最下行
- result = result*10;
- if(Pos < =m_BlockNum*m_BlockNum-m_BlockNum)
- {
- result += 1;
- }
- for(int i=0; i<4; ++i)
- {
- bool bIsMove=false;
- int r = result%10*10;
- r = pow((float)r, i);
- switch(r)
- {
- case 1000://表示左侧有格子
- if(m_Block[Pos-1] == LASTBLOCK)
- {
- MovePos(Pos-1, Pos);
- bIsMove = true;
- }
- break;
- case 100://表示右侧还有格子
- if(m_Block[Pos+1] == LASTBLOCK)
- {
- MovePos(Pos+1, Pos);
- bIsMove = true;
- }
- break;
- case 10://表示上面有格子
- if(m_Block[Pos-m_BlockNum] == LASTBLOCK)
- {
- MovePos(Pos-m_BlockNum, Pos);
- bIsMove = true;
- }
- break;
- case 1://表示下面有格子
- if(m_Block[Pos+m_BlockNum] == LASTBLOCK)
- {
- MovePos(Pos+m_BlockNum, Pos);
- bIsMove = true;
- }
- break;
- }
- result /= 10;
- if(bIsMove)
- {
- return true;
- }
- }
- return false;
- }
- // 移动位置
- void CPuzzleLogic::MovePos(int dstPos, int srcPos)
- {
- GameType temp = m_Block[srcPos];
- m_Block[srcPos] = m_Block[dstPos];
- m_Block[dstPos] = temp;
- }
- // 判断是否胜利
- bool CPuzzleLogic::IsVictor(void)
- {
- for(int i=0; i<m_BlockNum; ++i)
- {
- for(int j=0; j<m_BlockNum; ++j)
- {
- if(m_Block[i*m_BlockNum+j] != i*10+j)
- {
- return false;
- }
- }
- }
- return true;
- }
- // 设置游戏开始与暂停
- void CPuzzleLogic::SetGamePlaying(bool bIsPlaying)
- {
- m_IsPlaying = bIsPlaying;
- }
我这是用到一个循环,当然你也可以自己控制,不用循环,就是自己去判断每一位是
1
还是
0
,然后决定是否移动。
我来说我这个,定义一个标志变量,标志是否可以移动。
int r = result%10*10;
r = pow((float)r, i);
再定义一个临时变量
r
,先是获取到每位的值,从右往左的,然后乘以
10
,如果是
1
的话就是
10
,是
0
就是
0
,然后根据
i
的值去乘方,
10
的
0
次方为
1
,
10
的
1
次方为
10
……这样,就可以根据乘方的值来判断到底是哪种,然后用
swtich
切换到正确的移动,调用移动函数就
OK
了。
如果成功移动,则设标志位为
true,
有了标志位就可以返回了。
胜利判定
游戏当然要有一个胜利判定了,这个判定很简单,循环看看是否已经符合初始的条件,就是
0
位置是
00
,
1
位置是
01
,第二行是否为
10
,
11
等等。这个比较简单,就不多说了。
再看看
CPuzzleMain
里的
OnClick
函数吧。
- // 单击事件
- void CPuzzleMain::OnClick(int x, int y)
- {
- if(m_View.IsInside(x, y)==true)
- {
- int p = m_View.GetPoint(x, y);
- if(m_Logic.MoveBlock(p))
- {
- m_View.LoadBMPList(m_Logic.GetBlock());
- m_View.OnPaint();
- if(m_Logic.IsVictor())
- {
- m_View.SetGameStarted(false);
- m_Logic.SetGamePlaying(false);
- MessageBox(NULL, _T("恭喜您成功完成了拼图"), _T("恭喜"), MB_OK);
- }
- }
- }
- }
过程很简单,很判断是否在游戏区,然后获取单击的坐标,然后判断是否可以移动,如果可以就把移动后的那个状态内容复制到
View
的状态变量里,再重画图,判断是否胜利了,如果胜利了,就让游戏停止,弹出提示框。
好了,到现在,我们的游戏基本上可以玩了,你还可以自己设置分成多少块。
我们还需要一个初始局面生成的方法,还有其他的美化工作,那将是以后的事,现在主要工作完成了。
[原创+连载]一步一步做拼图游戏,C++版(三):student.csdn.net/space.php
[原创+连载]一步一步做拼图游戏,C++版(二):student.csdn.net/space.php
[原创+连载]一步一步做拼图游戏,C++版(一):student.csdn.net/space.php
-------------------------------------------
代码:download.csdn.net/source/2706170
------------------------------------------------------------------------------------------------------------------------------------------
貌似是我忘写了,单击事件的调用。
在下面添加上一句就可以了。
- LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
- {
- int wmId, wmEvent;
- PAINTSTRUCT ps;
- HDC hdc;
- int x=0, y=0;
- ……
- case WM_LBUTTONDOWN:
- x=LOWORD(lParam);
- y=HIWORD(lParam);
- g_PuzzleMain.OnClick(x, y);
- ……}
x,y是获得坐标,然后调用OnClick函数