[原创+连载]一步一步做拼图游戏,C++版(四)

 

今天,就要开始做游戏的主要部分了,如何控制游戏。
 
4. 控制操作
我们的控制就是,先空出一个格子不显示,然后单击到这个空格子的周围的格子的图片时,就移动过去。
为此,我们要先把空格子画出来。昨天我们的图片其实还是完整显示的。要稍微修改一下我们的 OnPaint 函数的代码了。
先看代码。为了省地方,我就把和昨天一样的地方省略了,用省略号代替。
 
Code:
  1. // 用于重绘   
  2. void CPuzzleView::OnPaint(void)   
  3. {   
  4.       HDC hdcMem;                                                               //内存DC   
  5.       HDC hdcScr = GetDC(m_hWnd);                          //获取屏幕的DC   
  6.       hdcMem = CreateCompatibleDC(hdcScr); //创建兼容屏幕的内存DC   
  7.       //获取窗口大小   
  8.       RECT rect;   
  9.       GetClientRect(m_hWnd, &rect);   
  10.       //按窗口大小创建兼容位图   
  11.       HBITMAP hTemp= CreateCompatibleBitmap(hdcScr, rect.right, rect.bottom);   
  12.       SelectObject(hdcMem, hTemp);                //绑定临时背景到内存DC   
  13.       //创建一个临时背景DC   
  14.       HDC hdcTempBK = CreateCompatibleDC(hdcMem);   
  15.       SelectObject(hdcTempBK, m_hBmpBack);   
  16.       BitBlt(hdcMem, 0, 0, SCRWIDTH, SCRHEIGHT, hdcTempBK, 0, 0, SRCCOPY);//为内存DC画背景   
  17.       DeleteDC(hdcTempBK);   
  18.       //画右上角的小图像   
  19.       ……   
  20.       //画主图像   
  21.       HDC hdcGameMem = CreateCompatibleDC(hdcMem);   
  22.       SelectObject(hdcGameMem, m_hBmpGame);   
  23.       if(m_IsGameStarted==true)   
  24.       {   
  25.            for(int i=0; i<m_FrameNum; ++i)   
  26.            {   
  27.                  for(int j=0; j<m_FrameNum; ++j)   
  28.                  {   
  29.                       ……   
  30.                       if(m_Block[j+m_FrameNum*i] == LASTBLOCK)   
  31.                       {   
  32.                             continue;//略掉最后一块不画   
  33.                       }   
  34.                       ……   
  35.                  }   
  36.            }   
  37.       }   
  38.       else  
  39.       {   
  40.            BitBlt(hdcMem, m_Game_x, m_Game_y, m_Game_width, m_Game_height, hdcGameMem, 0, 0, SRCCOPY);   
  41.       }   
  42. ……   
  43. }   
 
先看这部分。
 
Code:
  1. //获取窗口大小   
  2.       RECT rect;   
  3.       GetClientRect(m_hWnd, &rect);   
  4.       //按窗口大小创建兼容位图   
  5.       HBITMAP hTemp= CreateCompatibleBitmap(hdcScr, rect.right, rect.bottom);   
  6.       SelectObject(hdcMem, hTemp);                //绑定临时背景到内存DC   
  7.       //创建一个临时背景DC   
  8.       HDC hdcTempBK = CreateCompatibleDC(hdcMem);   
  9.       SelectObject(hdcTempBK, m_hBmpBack);   
  10.       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 里的内容。今天的比较抽象,不太好理解,我尽量说得详细些。
 
Code:
  1. // 移动当前点击的,成功移动,返回true,否则返回false   
  2. bool CPuzzleLogic::MoveBlock(int Pos)   
  3. {   
  4.       //判断游戏是否在进行中   
  5.       if(m_IsPlaying == false)   
  6.       {   
  7.            return false;   
  8.       }   
  9.       //如果是最后一个,则是空的,所以不能移动   
  10.       if(m_Block[Pos]==LASTBLOCK)   
  11.       {   
  12.            return false;   
  13.       }   
  14.       //判断一下是否越界了   
  15.       if(Pos<0 || Pos>m_BlockNum*m_BlockNum-1)   
  16.       {   
  17.            return false;   
  18.       }   
  19.       //先进行判断,是否在左右两侧   
  20.       //用一个变量的四位表示这个位置的四个方向是否都在游戏区   
  21.       //1111,表示四个方向都在游戏区内   
  22.       //从左开始,第一位表示左,第二位表示右,第三位表示上,第四位表示下   
  23.       int result = 0;   
  24.       int temp = Pos%m_BlockNum;   
  25.       //不在最左侧列   
  26.       if(temp != 0)   
  27.       {   
  28.            result = 1;   
  29.       }   
  30.       //不在最右侧   
  31.       result = result*10;   
  32.       if(temp != m_BlockNum-1)   
  33.       {   
  34.            result += 1;   
  35.       }   
  36.       //不在最上行   
  37.       result = result*10;   
  38.       if(Pos > m_BlockNum-1)   
  39.       {   
  40.            result += 1;    
  41.       }   
  42.       //不在最下行   
  43.       result = result*10;   
  44.       if(Pos < =m_BlockNum*m_BlockNum-m_BlockNum)   
  45.       {   
  46.            result += 1;    
  47.       }   
  48.       for(int i=0; i<4; ++i)   
  49.       {   
  50.            bool bIsMove=false;   
  51.            int r = result%10*10;   
  52.            r = pow((float)r, i);   
  53.            switch(r)   
  54.            {   
  55.            case 1000://表示左侧有格子   
  56.                  if(m_Block[Pos-1] == LASTBLOCK)   
  57.                  {   
  58.                       MovePos(Pos-1, Pos);   
  59.                       bIsMove = true;   
  60.                  }   
  61.                  break;   
  62.            case 100://表示右侧还有格子   
  63.                  if(m_Block[Pos+1] == LASTBLOCK)   
  64.                  {   
  65.                       MovePos(Pos+1, Pos);   
  66.                       bIsMove = true;   
  67.                  }   
  68.                  break;   
  69.            case 10://表示上面有格子   
  70.                  if(m_Block[Pos-m_BlockNum] == LASTBLOCK)   
  71.                  {   
  72.                       MovePos(Pos-m_BlockNum, Pos);   
  73.                       bIsMove = true;   
  74.                  }   
  75.                  break;   
  76.            case 1://表示下面有格子   
  77.                  if(m_Block[Pos+m_BlockNum] == LASTBLOCK)   
  78.                  {   
  79.                       MovePos(Pos+m_BlockNum, Pos);   
  80.                       bIsMove = true;   
  81.                  }   
  82.                  break;   
  83.            }   
  84.            result /= 10;   
  85.            if(bIsMove)   
  86.            {   
  87.                  return true;   
  88.            }   
  89.       }   
  90.       return false;   
  91. }   
  92.     
  93.     
  94. // 移动位置   
  95. void CPuzzleLogic::MovePos(int dstPos, int srcPos)   
  96. {   
  97.       GameType temp = m_Block[srcPos];   
  98.       m_Block[srcPos] = m_Block[dstPos];   
  99.       m_Block[dstPos] = temp;   
  100. }   
  101.     
  102.     
  103. // 判断是否胜利   
  104. bool CPuzzleLogic::IsVictor(void)   
  105. {   
  106.       for(int i=0; i<m_BlockNum; ++i)   
  107.       {   
  108.            for(int j=0; j<m_BlockNum; ++j)   
  109.            {   
  110.                  if(m_Block[i*m_BlockNum+j] != i*10+j)   
  111.                  {   
  112.                       return false;   
  113.                  }   
  114.            }   
  115.       }   
  116.       return true;   
  117. }   
  118.     
  119.     
  120. // 设置游戏开始与暂停   
  121. void CPuzzleLogic::SetGamePlaying(bool bIsPlaying)   
  122. {   
  123.       m_IsPlaying = bIsPlaying;   
  124. }   
 
这是主要代码。我们的过程是单击一个空格子周围的图片,图片就移到空格子,其实就是把空格子和单击的格子交换了一下位置,因此, 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 恰好在那个位置,我们就可以交换了,这个结果显然是不正确的。
所以,我们要先判断一下单击的位置,它有几个方向可以去查看。
Code:
  1. // 移动当前点击的,成功移动,返回true,否则返回false   
  2. bool CPuzzleLogic::MoveBlock(int Pos)   
  3. {   
  4.       //判断游戏是否在进行中   
  5.       if(m_IsPlaying == false)   
  6.       {   
  7.            return false;   
  8.       }   
  9.       //如果是最后一个,则是空的,所以不能移动   
  10.       if(m_Block[Pos]==LASTBLOCK)   
  11.       {   
  12.            return false;   
  13.       }   
  14.       //判断一下是否越界了   
  15.       if(Pos<0 || Pos>m_BlockNum*m_BlockNum-1)   
  16.       {   
  17.            return false;   
  18.       }   
  19.       //先进行判断,是否在左右两侧   
  20.       //用一个变量的四位表示这个位置的四个方向是否都在游戏区   
  21.       //1111,表示四个方向都在游戏区内   
  22.       //从左开始,第一位表示左,第二位表示右,第三位表示上,第四位表示下   
  23.       int result = 0;   
  24.       int temp = Pos%m_BlockNum;   
  25.       //不在最左侧列   
  26.       if(temp != 0)   
  27.       {   
  28.            result = 1;   
  29.       }   
  30.       //不在最右侧   
  31.       result = result*10;   
  32.       if(temp != m_BlockNum-1)   
  33.       {   
  34.            result += 1;   
  35.       }   
  36.       //不在最上行   
  37.       result = result*10;   
  38.       if(Pos > m_BlockNum-1)   
  39.       {   
  40.            result += 1;    
  41.       }   
  42.       //不在最下行   
  43.       result = result*10;   
  44.       if(Pos < =m_BlockNum*m_BlockNum-m_BlockNum)   
  45.       {   
  46.            result += 1;    
  47.       }   
  48.       for(int i=0; i<4; ++i)   
  49.       {   
  50.            bool bIsMove=false;   
  51.            int r = result%10*10;   
  52.            r = pow((float)r, i);   
  53.            switch(r)   
  54.            {   
  55.            case 1000://表示左侧有格子   
  56.                  if(m_Block[Pos-1] == LASTBLOCK)   
  57.                  {   
  58.                       MovePos(Pos-1, Pos);   
  59.                       bIsMove = true;   
  60.                  }   
  61.                  break;   
  62.            case 100://表示右侧还有格子   
  63.                  if(m_Block[Pos+1] == LASTBLOCK)   
  64.                  {   
  65.                       MovePos(Pos+1, Pos);   
  66.                       bIsMove = true;   
  67.                  }   
  68.                  break;   
  69.            case 10://表示上面有格子   
  70.                  if(m_Block[Pos-m_BlockNum] == LASTBLOCK)   
  71.                  {   
  72.                       MovePos(Pos-m_BlockNum, Pos);   
  73.                       bIsMove = true;   
  74.                  }   
  75.                  break;   
  76.            case 1://表示下面有格子   
  77.                  if(m_Block[Pos+m_BlockNum] == LASTBLOCK)   
  78.                  {   
  79.                       MovePos(Pos+m_BlockNum, Pos);   
  80.                       bIsMove = true;   
  81.                  }   
  82.                  break;   
  83.            }   
  84.            result /= 10;   
  85.            if(bIsMove)   
  86.            {   
  87.                  return true;   
  88.            }   
  89.       }   
  90.       return false;   
  91. }   
  92.     
  93.     
  94. // 移动位置   
  95. void CPuzzleLogic::MovePos(int dstPos, int srcPos)   
  96. {   
  97.       GameType temp = m_Block[srcPos];   
  98.       m_Block[srcPos] = m_Block[dstPos];   
  99.       m_Block[dstPos] = temp;   
  100. }   
  101.     
  102.     
  103. // 判断是否胜利   
  104. bool CPuzzleLogic::IsVictor(void)   
  105. {   
  106.       for(int i=0; i<m_BlockNum; ++i)   
  107.       {   
  108.            for(int j=0; j<m_BlockNum; ++j)   
  109.            {   
  110.                  if(m_Block[i*m_BlockNum+j] != i*10+j)   
  111.                  {   
  112.                       return false;   
  113.                  }   
  114.            }   
  115.       }   
  116.       return true;   
  117. }   
  118.     
  119.     
  120. // 设置游戏开始与暂停   
  121. void CPuzzleLogic::SetGamePlaying(bool bIsPlaying)   
  122. {   
  123.       m_IsPlaying = bIsPlaying;   
  124. }   
就是这段代码,我用的是一个四位数来表示四个方向,其实最好是用 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 ,如果不在这个区域内,则证明不在最下面一行。
当然,在这之前还要判断是否单击在游戏区了,当然,你可能还记得我们在这之前也判断过,多判断一次也不是坏事,还要判断游戏是否在进行中,还要判断单击的是不是那个空格子。都是函数最前面。
移动方法
有了这个位置,就可以写移动的方法了。
Code:
  1. // 移动当前点击的,成功移动,返回true,否则返回false   
  2. bool CPuzzleLogic::MoveBlock(int Pos)   
  3. {   
  4.       //判断游戏是否在进行中   
  5.       if(m_IsPlaying == false)   
  6.       {   
  7.            return false;   
  8.       }   
  9.       //如果是最后一个,则是空的,所以不能移动   
  10.       if(m_Block[Pos]==LASTBLOCK)   
  11.       {   
  12.            return false;   
  13.       }   
  14.       //判断一下是否越界了   
  15.       if(Pos<0 || Pos>m_BlockNum*m_BlockNum-1)   
  16.       {   
  17.            return false;   
  18.       }   
  19.       //先进行判断,是否在左右两侧   
  20.       //用一个变量的四位表示这个位置的四个方向是否都在游戏区   
  21.       //1111,表示四个方向都在游戏区内   
  22.       //从左开始,第一位表示左,第二位表示右,第三位表示上,第四位表示下   
  23.       int result = 0;   
  24.       int temp = Pos%m_BlockNum;   
  25.       //不在最左侧列   
  26.       if(temp != 0)   
  27.       {   
  28.            result = 1;   
  29.       }   
  30.       //不在最右侧   
  31.       result = result*10;   
  32.       if(temp != m_BlockNum-1)   
  33.       {   
  34.            result += 1;   
  35.       }   
  36.       //不在最上行   
  37.       result = result*10;   
  38.       if(Pos > m_BlockNum-1)   
  39.       {   
  40.            result += 1;    
  41.       }   
  42.       //不在最下行   
  43.       result = result*10;   
  44.       if(Pos < =m_BlockNum*m_BlockNum-m_BlockNum)   
  45.       {   
  46.            result += 1;    
  47.       }   
  48.       for(int i=0; i<4; ++i)   
  49.       {   
  50.            bool bIsMove=false;   
  51.            int r = result%10*10;   
  52.            r = pow((float)r, i);   
  53.            switch(r)   
  54.            {   
  55.            case 1000://表示左侧有格子   
  56.                  if(m_Block[Pos-1] == LASTBLOCK)   
  57.                  {   
  58.                       MovePos(Pos-1, Pos);   
  59.                       bIsMove = true;   
  60.                  }   
  61.                  break;   
  62.            case 100://表示右侧还有格子   
  63.                  if(m_Block[Pos+1] == LASTBLOCK)   
  64.                  {   
  65.                       MovePos(Pos+1, Pos);   
  66.                       bIsMove = true;   
  67.                  }   
  68.                  break;   
  69.            case 10://表示上面有格子   
  70.                  if(m_Block[Pos-m_BlockNum] == LASTBLOCK)   
  71.                  {   
  72.                       MovePos(Pos-m_BlockNum, Pos);   
  73.                       bIsMove = true;   
  74.                  }   
  75.                  break;   
  76.            case 1://表示下面有格子   
  77.                  if(m_Block[Pos+m_BlockNum] == LASTBLOCK)   
  78.                  {   
  79.                       MovePos(Pos+m_BlockNum, Pos);   
  80.                       bIsMove = true;   
  81.                  }   
  82.                  break;   
  83.            }   
  84.            result /= 10;   
  85.            if(bIsMove)   
  86.            {   
  87.                  return true;   
  88.            }   
  89.       }   
  90.       return false;   
  91. }   
  92.     
  93.     
  94. // 移动位置   
  95. void CPuzzleLogic::MovePos(int dstPos, int srcPos)   
  96. {   
  97.       GameType temp = m_Block[srcPos];   
  98.       m_Block[srcPos] = m_Block[dstPos];   
  99.       m_Block[dstPos] = temp;   
  100. }   
  101.     
  102.     
  103. // 判断是否胜利   
  104. bool CPuzzleLogic::IsVictor(void)   
  105. {   
  106.       for(int i=0; i<m_BlockNum; ++i)   
  107.       {   
  108.            for(int j=0; j<m_BlockNum; ++j)   
  109.            {   
  110.                  if(m_Block[i*m_BlockNum+j] != i*10+j)   
  111.                  {   
  112.                       return false;   
  113.                  }   
  114.            }   
  115.       }   
  116.       return true;   
  117. }   
  118.     
  119.     
  120. // 设置游戏开始与暂停   
  121. void CPuzzleLogic::SetGamePlaying(bool bIsPlaying)   
  122. {   
  123.       m_IsPlaying = bIsPlaying;   
  124. }   
     
我这是用到一个循环,当然你也可以自己控制,不用循环,就是自己去判断每一位是 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 函数吧。
Code:
  1. // 单击事件   
  2. void CPuzzleMain::OnClick(int x, int y)   
  3. {   
  4.       if(m_View.IsInside(x, y)==true)   
  5.       {   
  6.            int p = m_View.GetPoint(x, y);   
  7.            if(m_Logic.MoveBlock(p))   
  8.            {   
  9.                  m_View.LoadBMPList(m_Logic.GetBlock());   
  10.                  m_View.OnPaint();   
  11.                  if(m_Logic.IsVictor())   
  12.                  {   
  13.                       m_View.SetGameStarted(false);   
  14.                       m_Logic.SetGamePlaying(false);   
  15.                       MessageBox(NULL, _T("恭喜您成功完成了拼图"), _T("恭喜"), MB_OK);   
  16.                  }   
  17.            }   
  18.       }   
  19. }   
 
过程很简单,很判断是否在游戏区,然后获取单击的坐标,然后判断是否可以移动,如果可以就把移动后的那个状态内容复制到 View 的状态变量里,再重画图,判断是否胜利了,如果胜利了,就让游戏停止,弹出提示框。
 
好了,到现在,我们的游戏基本上可以玩了,你还可以自己设置分成多少块。
我们还需要一个初始局面生成的方法,还有其他的美化工作,那将是以后的事,现在主要工作完成了。

[原创+连载]一步一步做拼图游戏,C++版(三):student.csdn.net/space.php

[原创+连载]一步一步做拼图游戏,C++版(二):student.csdn.net/space.php

[原创+连载]一步一步做拼图游戏,C++版(一):student.csdn.net/space.php

-------------------------------------------

代码:download.csdn.net/source/2706170

------------------------------------------------------------------------------------------------------------------------------------------

貌似是我忘写了,单击事件的调用。

在下面添加上一句就可以了。

Code:
  1. LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)   
  2. {   
  3.     int wmId, wmEvent;   
  4.     PAINTSTRUCT ps;   
  5.     HDC hdc;   
  6.     int x=0, y=0;   
  7. ……     
  8. case WM_LBUTTONDOWN:   
  9.         x=LOWORD(lParam);   
  10.         y=HIWORD(lParam);   
  11.         g_PuzzleMain.OnClick(x, y);   
  12. ……}  

x,y是获得坐标,然后调用OnClick函数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 44
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 44
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值