Part3:The Play State

来自:http://www.codeproject.com/Articles/30775/TetroGL-An-OpenGL-Game-Tutorial-in-C-for-Win32-pla

目录:

1、The Play State

2、补充    简单使用

1、The Play State

This state is the most complicated one, because it is there that all the game logic is handled. The play state delegates most of the logic to theCBlocksMatrixclass, which is responsible of the management of the playing area. There are 7 different shapes (also called tetrads), which are named by a letter depending on their shape: I, O, Z, S, T, L and J. Each of these tetrads has a specific class to handle it. It is done so because there is no generic way to handle all the different tetrads. For instance, rotations are not always done in the same way: The I tetrad (the line) has only two different position (vertical and horizontal), you don't simply rotate all the cells around a point. So, for this reason, each tetrad has to be handled separately. They all inherits from theCTetradclass, which looks like:

// Base class for all shapes (tetrad)
class CTetrad
{
public:
  // Construct a new tetrad. The image of the block used to draw
  // the tetrad is loaded depending of the tetrad color.
  CTetrad(CBlocksMatrix* pParent, EBlockColor blockColor) 
    : m_pParentMatrix(pParent), m_iXPos(4), m_iYPos(0), 
      m_Orientation(Rotation0), m_pBlockImg(NULL), m_BlockColor(blockColor)  
  { 
    switch (blockColor)
    {
    case bcCyan:
      m_pBlockImg = CImage::CreateImage("Block.PNG",
          TRectanglei(0,BLOCK_HEIGHT,0,BLOCK_WIDTH));
      break;
    case bcBlue:
      m_pBlockImg = CImage::CreateImage("Block.PNG",
          TRectanglei(0,BLOCK_HEIGHT,BLOCK_WIDTH,2*BLOCK_WIDTH));
      break;
    case bcOrange:
      m_pBlockImg = CImage::CreateImage("Block.PNG",
          TRectanglei(0,BLOCK_HEIGHT,2*BLOCK_WIDTH,3*BLOCK_WIDTH));
      break;
    case bcYellow:
      m_pBlockImg = CImage::CreateImage("Block.PNG",
          TRectanglei(0,BLOCK_HEIGHT,3*BLOCK_WIDTH,4*BLOCK_WIDTH));
      break;
    case bcGreen:
      m_pBlockImg = CImage::CreateImage("Block.PNG",
          TRectanglei(0,BLOCK_HEIGHT,4*BLOCK_WIDTH,5*BLOCK_WIDTH));
      break;
    case bcPurple:
      m_pBlockImg = CImage::CreateImage("Block.PNG",
          TRectanglei(BLOCK_HEIGHT,2*BLOCK_HEIGHT,0,BLOCK_WIDTH));
      break;
    case bcRed:
      m_pBlockImg = CImage::CreateImage("Block.PNG",
          TRectanglei(BLOCK_HEIGHT,2*BLOCK_HEIGHT,BLOCK_WIDTH,2*BLOCK_WIDTH));
      break;
    }
  }
  virtual ~CTetrad() { }

  // Tries to rotate the tetrad. If it can't be rotated, 
  // the function returns false.
  virtual bool Rotate() = 0;
  // Tries to move the tetrad to the left. If it can't be 
  // moved, the function returns false.
  virtual bool MoveLeft() = 0;
  // Tries to move the tetrad to the right. If it can't be 
  // moved, the function returns false.
  virtual bool MoveRight() = 0;
  // Tries to move the tetrad down. If it can't be 
  // moved, the function returns false.
  virtual bool MoveDown() = 0;

  // Ask the tetrad to fill the cells in the matrix.
  // This function is called when the tetrad is positioned.
  virtual void FillMatrix() = 0;
  // Checks if the tetrad is at a valid position (do not 
  // overlap with a filled cell in the matrix). This is 
  // called when the tetrad is created to check for game over.
  virtual bool IsValid() = 0;

  // Draw the tetrad at its position in the matrix.
  virtual void Draw() = 0;
  // Draw the tetrad somewhere on the screen (used to
  // display the next shape). The tetrad is centered
  // in the rectangle.
  virtual void DrawOnScreen(const TRectanglei& rect) = 0;

protected:
  // The play area in which the tetrad is used
  CBlocksMatrix* m_pParentMatrix;

  // The position in the play area (in
  // blocks).
  int m_iXPos;
  int m_iYPos;

  enum EOrientation
  {
    Rotation0,
    Rotation90,
    Rotation180,
    Rotation270,
  };
  // Orientation of the tetrad
  EOrientation m_Orientation;

  // The block image use to draw the tetrad.
  TImagePtr m_pBlockImg;
  // The block color.
  EBlockColor m_BlockColor;
};

The child classes implement those virtual methods. They interract with the CBlocksMatrix class to check if some cells are free or not. Here is an example of the rotation function for the Z tetrad:

bool CTetrad_Z::Rotate()
{
  bool bSuccess = false;
  switch (m_Orientation)
  {
  case Rotation0:
  case Rotation180:
    if (m_pParentMatrix->IsCellFree(m_iXPos,m_iYPos-1) &&

      m_pParentMatrix->IsCellFree(m_iXPos-1,m_iYPos+1) )
    {
      m_Orientation = Rotation90;
      bSuccess = true;
    }
    break;

  case Rotation90:
  case Rotation270:
    if (m_pParentMatrix->IsCellFree(m_iXPos,m_iYPos+1) &&
      m_pParentMatrix->IsCellFree(m_iXPos+1,m_iYPos+1))
    {
      m_Orientation = Rotation0;
      bSuccess = true;
    }
    break;
  }
  return bSuccess;
}
Depending of the current rotation of the tetrad, it will check if the cells that will be occupied after the rotation are free or not. If they are free, the m_Orientation member is updated and the function returns true. The other move or rotate functions of all the tetrads are similar, so I won't put here the code for all of them. The Drawfunction is not very difficult neither:

void CTetrad_Z::Draw()
{
  int screenX=0, screenY=0; 

  switch (m_Orientation)
  {
  case Rotation0:
  case Rotation180:
    m_pParentMatrix->GetScreenPosFromCell(m_iXPos-1,m_iYPos,screenX,screenY);
    m_pBlockImg->BlitImage(screenX,screenY);
    m_pParentMatrix->GetScreenPosFromCell(m_iXPos  ,m_iYPos,screenX,screenY);
    m_pBlockImg->BlitImage(screenX,screenY);
    m_pParentMatrix->GetScreenPosFromCell(m_iXPos  ,m_iYPos+1,screenX,screenY);
    m_pBlockImg->BlitImage(screenX,screenY);
    m_pParentMatrix->GetScreenPosFromCell(m_iXPos+1,m_iYPos+1,screenX,screenY);
    m_pBlockImg->BlitImage(screenX,screenY);
    break;

  case Rotation90:
  case Rotation270:
    m_pParentMatrix->GetScreenPosFromCell(m_iXPos  ,m_iYPos-1,screenX,screenY);
    m_pBlockImg->BlitImage(screenX,screenY);
    m_pParentMatrix->GetScreenPosFromCell(m_iXPos  ,m_iYPos  ,screenX,screenY);
    m_pBlockImg->BlitImage(screenX,screenY);
    m_pParentMatrix->GetScreenPosFromCell(m_iXPos-1,m_iYPos  ,screenX,screenY);
    m_pBlockImg->BlitImage(screenX,screenY);
    m_pParentMatrix->GetScreenPosFromCell(m_iXPos-1,m_iYPos+1,screenX,screenY);
    m_pBlockImg->BlitImage(screenX,screenY);
    break;
  }
}

The screen position of a cell can be retrieved from the CBlocksMatrix class (remember that the m_iXPos and m_iYPos members are the position in the matrix and are not screen positions).

The CBlocksMatrix is responsible to handle all the logic related to checking filled lines and removing them. Let's first look at the class header, we will look at the implementation of some functions later:

// Class managing the playing area (where the shapes are
// falling). It handles all the logic related to lines.
class CBlocksMatrix
{
public:
  // Constructor and destructor
  CBlocksMatrix(CMatrixEventsListener* pListener, int xPos, int yPos);
  ~CBlocksMatrix();

  // Draw and update the matrix
  void Draw();
  void Update(DWORD dwCurrentTime);
  // Reset the matrix to its initial state
  void Reset();

  // Move the current shape
  void ShapeLeft();
  void ShapeRight();
  void ShapeDown();
  void ShapeRotate();

  // Check if the specified cell is free or not.
  bool IsCellFree(int XPos, int YPos);
  // Fill the specified cell with a specific block color
  void FillCell(int XPos, int YPos, EBlockColor BlockColor);
  // Transform a cell coordinates into screen coordinates.
  void GetScreenPosFromCell(int cellXPos, int cellYPos, 
    int& screenXPos, int& screenYPos);

  // Returns the next shape
  CTetrad* GetNextShape() const  { return m_pNextShape; }

  // Sets/Gets the time between two update of the current
  // shape (determines the speed at which it falls).
  void SetTetradUpdate(int iNewUpdate)  { m_iTetradUpdate = iNewUpdate; }
  int  GetTetradUpdate() const  { return m_iTetradUpdate; }

private:
  // Check if there are lines completed in the
  // matrix. This returns true if at least one
  // line is complete
  bool CheckMatrix();
  // Check if the specified line is currently being
  // removed
  bool IsLineRemoved(int iRow);
  // Remove the lines that are complete from the
  // matrix and adjust the remaining blocks.
  void RemoveLines();
  // Tries to create a new shape. If this is not
  // possible (e.g. matrix full), m_bGameOver is
  // set to true.
  void NewShape();

  // The screen coordinates of the top-left 
  // corner.
  int m_iXPos;
  int m_iYPos;

  // The matrix of blocks which are already filled
  int m_pBlocksMatrix[MATRIX_WIDTH][MATRIX_HEIGHT];

  // The images of the 7 different blocks
  TImagePtr m_pBlockImg[7];

  // The tetrad factory
  CTetradFactory m_TetradFactory;
  // Current shape that the player manipulates
  CTetrad* m_pTetrad;
  // Next shape
  CTetrad* m_pNextShape;
  // The last move down of the current shape
  DWORD m_dwLastShapeDown;

  // Flag indicating that one or more
  // lines are being removed (blinking)
  bool m_bRemovingLine;
  // The number of times the line being removed
  // has already blinked.
  int  m_iLineBlinkCount;
  // Specify if the the line being removed is currently
  // visible or not (for blinking)
  bool m_bLineBlinkOn;
  // Vector containing the line numbers of the
  // lines being removed.
  std::vector<int> m_vecLinesRemoved;

  // The event listener
  CMatrixEventsListener* m_pListener;
  // Time (in msec) before the tetrad is moved down
  // one step.
  int m_iTetradUpdate;

  // Flag indicating a game over.
  bool m_bGameOver;
};

The ShapeLeft, ShapeRight, ShapeRotateand ShapeDownfunctions are simply redirected to the current tetrad ( m_pTetrad). The ShapeDownfunction does a bit more, because if a tetrad cannot move down, some special checks need to be done:

void CBlocksMatrix::ShapeDown()
{
  if (m_pTetrad && !m_pTetrad->MoveDown())
  {
    // If the current shape can't move down,
    // we ask it to fill the matrix.
    m_pTetrad->FillMatrix();
    // Then delete the current shape
    delete m_pTetrad; 
    m_pTetrad = NULL;
    
    // We then check if no lines have been completed
    // and create the next shape. The m_bGameOver flag
    // can be set in this NewShape function.
    if (!CheckMatrix())
      NewShape();
  }
  // Set the last update (down) of the shape to 
  // the current time.
  m_dwLastShapeDown = GetCurrentTime();
}

If the shape cannot be moved down (the MoveDownfunction returns false), we first ask it to fill the cells of the matrix where it is located, then delete it. We then check if at least one line of the matrix is complete: TheCheckMatrix function returns true if at least one line is completed and if that is the case, it will push the line numbers of the ones that are filled in them_vecLinesRemoved vector and set m_bRemovingLine to true. If no lines were completed, we try to create a new shape by callingNewShape. If the shape cannot be created (because the matrix is full), them_bGameOver flag will be set to true.

The Update function only checks if the current shape should be moved down:

void CBlocksMatrix::Update(DWORD dwCurrentTime)
{
  if (!m_bGameOver)
  {
    // Check if the current shape should be moved down
    if (dwCurrentTime > m_dwLastShapeDown+m_iTetradUpdate)
      ShapeDown();
  }
}

The m_iTetradUpdate variable specifies the maximum time between two moves down of the current shape. This decreases with the level (the higher the level, the faster the shape will go down). Don't forget that the m_dwLastShapeDown variable is set to the current time in theShapeDownfunction (so if the shape is moved manually, this is also set).

Finally, the Draw function takes care of drawing the current state on the screen:

void CBlocksMatrix::Draw()
{
  int iBlockX=0, iBlockY=0;
  // If some lines are currently being removed,
  // We shouldn't draw them all.
  if (m_bRemovingLine)
  {
    for (int j=0; j<MATRIX_HEIGHT;j++)
    {
      // Don't draw the line if it is being removed and blinking off
      if (IsLineRemoved(j) && !m_bLineBlinkOn)
        continue;

      // Else draw the line
      for (int i=0; i<MATRIX_WIDTH;i++)
      {
        if (m_pBlocksMatrix[i][j])
        {
          int color = m_pBlocksMatrix[i][j]-1;
          GetScreenPosFromCell(i, j, iBlockX, iBlockY);
          m_pBlockImg[color]->BlitImage(iBlockX, iBlockY);
        }     
      }
    }

    // Switch the blinking
    if (m_bLineBlinkOn)
      m_bLineBlinkOn = false;
    else
      m_bLineBlinkOn = true;
    m_iLineBlinkCount++;
    // If blink count equals 10, we stop blinking and remove
    // the lines.
    if (m_iLineBlinkCount == 10)
    {
      RemoveLines();
      m_bRemovingLine = false;
      m_bLineBlinkOn = false;
      m_iLineBlinkCount = 0;
      NewShape();
    }
  }
  else 
  {
    // Draw filled blocks
    for (int j=0; j<MATRIX_HEIGHT;j)
    {
      for (int i=0; i<MATRIX_WIDTH;i++)
      {
        if (m_pBlocksMatrix[i][j])
        {
          int color = m_pBlocksMatrix[i][j]-1;
          GetScreenPosFromCell(i, j, iBlockX, iBlockY);
          m_pBlockImg[color]->BlitImage(iBlockX, iBlockY);
        }
      }
    }

    // Finally, draw the current shape
    if (!m_bGameOver)
      m_pTetrad->Draw();
  }

}

The first part of the function (if m_bRemovingLine is true) is only executed when we are removing lines (the lines which are complete will blink before being removed). Remember that in order to display 'animations', the state should be saved in some way for the next frame to be displayed. That's the reason why we have to remember if the lines are currently visible or not (m_bLineBlinkOn) and the number of times they have already blinked (m_iLineBlinkCount). TheIsLineRemoved function returns true if the line passed in argument is being removed. When the blinking is finished, theRemoveLinesfunction is called which will remove the lines from the matrix and clean everything (blocks above a removed line will be moved down). The second part of the function gets executed the rest of the time (when no lines are being removed). It simply draws all the filled blocks and the current shape.

As you probably saw, there is also a CMatrixEventsListenerclass. In fact, this is just an interface that should be implemented by another class in order to be notified about some events that occurs in the blocks matrix (starting to remove lines, lines removed, game over). TheCPlayStateclass implements this interface (and its address is passed to theCBlocksMatrixwhen constructing it). This technique is used in order to reduce coupling between those classes: TheCBlocksMatrixclass becomes independant of the class which is using it and which should be notified about the events. TheCPlayState looks like:

class CPlayState : public CGameState, 
           public CMatrixEventsListener
{
public:
  ~CPlayState();

  // Implementation of specific events
  void OnKeyDown(WPARAM wKey);
  void Update(DWORD dwCurrentTime);
  void Draw();

  // Implementation of the CMatrixEventsListener class
  void OnStartRemoveLines();
  void OnLinesRemoved(int iLinesCount);
  void OnMatrixFull();

  void Reset();
  bool IsGameOver()  { return m_bGameOver; }

  // Returns the single instance
  static CPlayState* GetInstance(CStateManager* pManager);

protected:
  CPlayState(CStateManager* pManager);

private:
  // The blocks matrix class
  CBlocksMatrix* m_pMatrix;
  // The font used to draw text
  CGameFont* m_pFont;
  // The control in charge of the decreasing 
  // time for the combo score.
  CComboControl* m_pComboControl;

  // The text controls to display the current
  // information.
  CTextControl* m_pScoreControl;
  CTextControl* m_pLevelControl;
  CTextControl* m_pLinesControl;

  // The current number of lines completed
  int m_iTotalLines;
  // The current level
  int m_iCurrentLevel;
  // The current score
  int m_iCurrentScore;

  bool m_bGameOver;

  // The background image
  TImagePtr m_pBackgroundImg;
};

The main role of this class is to coordinate the different elements: The game matrix and the combo control and to manage the score and current lines completed. The implementation of the class is fairly trivial, so I won't describe it here. TheCComboControlclass handles the combo control: This control shows a decreasing time bar when a line has been completed. If a new line is completed before time is over, a multiplier is applied to the points gained for the line(s) completed. The higher the multiplier, the fastest time will decrease.

When the game is over, a semi-transparent black rectangle will be displayed over the full screen with some text on it. This is done with the help of blending: support for blending has been added in theCMainWindow::InitGL function:


// Specifies the blending function
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Enable blending
glEnable(GL_BLEND);
The glEnable(GL_BLEND) simply enables the blending, but you also have to specify a blending function. The blending function tells OpenGL how to blend the incoming pixels with the pixels present in the frame buffer. Specifying this is done through the glBlendFunc: The first argument specifies the factor to apply to the RGB components of the source pixels (the incoming pixels) and the second argument specifies the factor to apply to the destination pixels (the pixels which are already in the frame buffer). The final pixel will be the addition of the resulting values (for each components). The code we use to make the semi-transparent black screen is:


 if (m_bGameOver)
  {
    // In game over, we draw a semi-transparent black screen on top
    // of the background. This is possible because blending has 
    // been enabled.
    glColor4f(0.0,0.0,0.0,0.5);
    // Disable 2D texturing because we want to draw a non
    // textured rectangle over the screen.
    glDisable(GL_TEXTURE_2D);
    glBegin(GL_QUADS);
    glVertex3i(0,0,0);
    glVertex3i(0,600,0);
    glVertex3i(800,600,0);
    glVertex3i(800,0,0);
    glEnd();
    glEnable(GL_TEXTURE_2D);

    m_pFont->DrawText("GAME OVER",340,200);
    m_pFont->DrawText("Press Enter to continue",285,300);
  }
This means that we first select a black color with an alpha channel of 0.5 and then we draw our rectangle over the complete screen. We have to disable texturing when drawing our rectangle, because we want a non textured black rectangle.

//原文end。。。

补充:

CPlayState类的完整实现:

CPlayState::CPlayState(CStateManager* pManager)
 : CGameState(pManager), m_pMatrix(NULL), m_pFont(NULL), 
   m_pComboControl(NULL), m_pScoreControl(NULL), 
   m_pLevelControl(NULL), m_pLinesControl(NULL),
   m_iTotalLines(0), m_iCurrentLevel(0), m_ulCurrentScore(0), 
   m_bGameOver(false)
{
	AddFontResource("01 Digitall.ttf");
	m_pMatrix = new CBlocksMatrix(this,280,34);
	m_pFont = new CGameFont;
	m_pFont->CreateFont("01 Digitall", 20, FW_NORMAL);

	m_pComboControl = new CComboControl(TRectanglei(330,450,50,235),m_pFont);
	m_pScoreControl = new CTextControl(m_pFont,TRectanglei(145,210,620,730));
	m_pScoreControl->SetAlignement(CTextControl::TACenter);
	m_pScoreControl->SetTextColor(1.0f,0.588f,0.039f);
	m_pLinesControl = new CTextControl(m_pFont,TRectanglei(320,385,620,730));
	m_pLinesControl->SetAlignement(CTextControl::TACenter);
	m_pLinesControl->SetTextColor(1.0f,0.588f,0.039f);
	m_pLevelControl = new CTextControl(m_pFont,TRectanglei(500,565,620,730));
	m_pLevelControl->SetAlignement(CTextControl::TACenter);
	m_pLevelControl->SetTextColor(1.0f,0.588f,0.039f);

	m_pBackgroundImg = CImage::CreateImage("PlayBckgnd.png",TRectanglei(0,600,0,800));
}

CPlayState::~CPlayState()
{
	if (m_pMatrix)
	{
		delete m_pMatrix;
		m_pMatrix = NULL;
	}

	if (m_pFont)
	{
		delete m_pFont;
		m_pFont = NULL;
	}
	RemoveFontResource("01 Digitall.ttf");
}

CPlayState* CPlayState::GetInstance(CStateManager* pManager)
{
	static CPlayState Instance(pManager);
	return &Instance;
}

void CPlayState::Reset()
{
	m_iTotalLines = 0;
	m_iCurrentLevel = 0; 
	m_ulCurrentScore = 0;
	m_bGameOver = false;
	m_pMatrix->Reset();
	m_pComboControl->Reset();
}

void CPlayState::OnKeyDown(WPARAM wKey)
{
	switch (wKey)
	{
	case VK_UP:
		if (!m_bGameOver)
			m_pMatrix->ShapeRotate();
		break;
	case VK_DOWN:
		if (!m_bGameOver)	
			m_pMatrix->ShapeDown();
		break;
	case VK_LEFT:
		if (!m_bGameOver)
			m_pMatrix->ShapeLeft();
		break;
	case VK_RIGHT:
		if (!m_bGameOver)
			m_pMatrix->ShapeRight();
		break;
	case VK_ESCAPE:
		ChangeState(CMenuState::GetInstance(m_pStateManager));
		break;
	case VK_RETURN:
		if (m_bGameOver)
		{
			CHighScoreState* pHighScores = 
				CHighScoreState::GetInstance(m_pStateManager);
			pHighScores->SetNewHighScore(m_ulCurrentScore);
			ChangeState(pHighScores);
		}
	}
}

void CPlayState::Update(DWORD dwCurrentTime)
{
	if (!m_bGameOver)
	{
		m_pMatrix->Update(dwCurrentTime);
		m_pComboControl->Update(dwCurrentTime);
	}
}

void CPlayState::Draw()  
{ 
	m_pBackgroundImg->BlitImage(0,0);

	m_pMatrix->Draw();

	stringstream ssScore;
	ssScore << m_ulCurrentScore;
	m_pScoreControl->SetText(ssScore.str());
	m_pScoreControl->Draw();

	stringstream ssLines;
	ssLines << m_iTotalLines;
	m_pLinesControl->SetText(ssLines.str());
	m_pLinesControl->Draw();

	stringstream ssLevel;
	ssLevel << m_iCurrentLevel;
	m_pLevelControl->SetText(ssLevel.str());
	m_pLevelControl->Draw();

	if (m_pMatrix->GetNextShape())
		m_pMatrix->GetNextShape()->DrawOnScreen(TRectanglei(165,220,80,225));

	m_pComboControl->Draw();
	if (m_bGameOver)
	{
		// In game over, we draw a semi-transparent black screen on top
		// of the background. This is possible because blending has 
		// been enabled.
		glColor4f(0.0,0.0,0.0,0.5);
		// Disable 2D texturing because we want to draw a non
		// textured rectangle over the screen.
		glDisable(GL_TEXTURE_2D);
		glBegin(GL_QUADS);
		glVertex3i(0,0,0);
		glVertex3i(0,600,0);
		glVertex3i(800,600,0);
		glVertex3i(800,0,0);
		glEnd();
		glEnable(GL_TEXTURE_2D);

		m_pFont->DrawText("GAME OVER",340,200);
		m_pFont->DrawText("Press Enter to continue",285,300);
	}

}

void CPlayState::OnStartRemoveLines()
{
	m_pComboControl->Pause();
}

void CPlayState::OnLinesRemoved(int iLinesCount)
{
	m_iTotalLines += iLinesCount;
	int comboMultiplier = m_pComboControl->GetMultiplier();
	switch (iLinesCount)
	{
	case 1:
		m_ulCurrentScore += (m_iCurrentLevel+1) * 40 * comboMultiplier;
		break;
	case 2:
		m_ulCurrentScore += (m_iCurrentLevel+1) * 100 * comboMultiplier;
		break;
	case 3:
		m_ulCurrentScore += (m_iCurrentLevel+1) * 300 * comboMultiplier;
		break;
	case 4:
		m_ulCurrentScore += (m_iCurrentLevel+1) * 1200 * comboMultiplier;
		break;
	}

	if (m_iTotalLines/10 > m_iCurrentLevel)
	{
		m_iCurrentLevel++;
		int iNewUpdateRate = (int)(m_pMatrix->GetTetradUpdate() * 0.8);
		m_pMatrix->SetTetradUpdate(iNewUpdateRate);
	}
	m_pComboControl->IncreaseMultiplier();
	m_pComboControl->Unpause();
}

void CPlayState::OnMatrixFull()
{
	m_bGameOver = true;
	m_pComboControl->Pause();
}
CBlocksMatrix类的完整实现:

CBlocksMatrix::CBlocksMatrix(CMatrixEventsListener* pListener, int xPos, int yPos) 
  : m_iXPos(xPos), m_iYPos(yPos), m_pTetrad(NULL),
    m_bRemovingLine(false), m_iLineBlinkCount(0), m_bLineBlinkOn(false), 
	m_vecLinesRemoved(), m_pListener(pListener), m_iTetradUpdate(1000),
	m_bGameOver(false)
{
	// Load the block images
	m_pBlockImg[0] = CImage::CreateImage("Block.PNG",TRectanglei(0,BLOCK_HEIGHT,0,BLOCK_WIDTH));
	m_pBlockImg[1] = CImage::CreateImage("Block.PNG",TRectanglei(0,BLOCK_HEIGHT,BLOCK_WIDTH,2*BLOCK_WIDTH));
	m_pBlockImg[2] = CImage::CreateImage("Block.PNG",TRectanglei(0,BLOCK_HEIGHT,2*BLOCK_WIDTH,3*BLOCK_WIDTH));
	m_pBlockImg[3] = CImage::CreateImage("Block.PNG",TRectanglei(0,BLOCK_HEIGHT,3*BLOCK_WIDTH,4*BLOCK_WIDTH));
	m_pBlockImg[4] = CImage::CreateImage("Block.PNG",TRectanglei(0,BLOCK_HEIGHT,4*BLOCK_WIDTH,5*BLOCK_WIDTH));
	m_pBlockImg[5] = CImage::CreateImage("Block.PNG",TRectanglei(BLOCK_HEIGHT,2*BLOCK_HEIGHT,0,BLOCK_WIDTH));
	m_pBlockImg[6] = CImage::CreateImage("Block.PNG",TRectanglei(BLOCK_HEIGHT,2*BLOCK_HEIGHT,BLOCK_WIDTH,2*BLOCK_WIDTH));

	// Initialize the matrix
	for (int i=0; i<MATRIX_WIDTH;i++)
	{
		for (int j=0; j<MATRIX_HEIGHT;j++)
		{
			m_pBlocksMatrix[i][j] = bcNone;
		}
	}

	// Create the current and next shapes
	m_pTetrad = m_TetradFactory.CreateTetrad(this);
	m_pNextShape = m_TetradFactory.CreateTetrad(this);
	m_dwLastShapeDown = GetCurrentTime();
}

CBlocksMatrix::~CBlocksMatrix()
{
}

void CBlocksMatrix::Reset()
{
	for (int i=0; i<MATRIX_WIDTH;i++)
	{
		for (int j=0; j<MATRIX_HEIGHT;j++)
		{
			m_pBlocksMatrix[i][j] = bcNone;
		}
	}

	delete m_pTetrad;
	delete m_pNextShape;
	m_pTetrad = m_TetradFactory.CreateTetrad(this);
	m_pNextShape = m_TetradFactory.CreateTetrad(this);
	m_dwLastShapeDown = GetCurrentTime();

    m_bRemovingLine = m_bLineBlinkOn = false; 
	m_iLineBlinkCount = 0;
	m_vecLinesRemoved.clear();
	m_iTetradUpdate = 1000;
	m_bGameOver = false;
}

void CBlocksMatrix::Draw()
{
	int iBlockX=0, iBlockY=0;
	// If some lines are currently being removed,
	// We shouldn't draw them all.
	if (m_bRemovingLine)
	{
		for (int j=0; j<MATRIX_HEIGHT;j++)
		{
			// Don't draw the line if it is being removed and blinking off
			if (IsLineRemoved(j) && !m_bLineBlinkOn)
				continue;

			// Else draw the line
			for (int i=0; i<MATRIX_WIDTH;i++)
			{
				if (m_pBlocksMatrix[i][j])
				{
					int color = m_pBlocksMatrix[i][j]-1;
					GetScreenPosFromCell(i, j, iBlockX, iBlockY);
					m_pBlockImg[color]->BlitImage(iBlockX, iBlockY);
				}			
			}
		}

		// Switch the blinking
		if (m_bLineBlinkOn)
			m_bLineBlinkOn = false;
		else
			m_bLineBlinkOn = true;
		m_iLineBlinkCount++;
		// If blink count equals 10, we stop blinking and remove
		// the lines.
		if (m_iLineBlinkCount == 10)
		{
			RemoveLines();
			m_bRemovingLine = false;
			m_bLineBlinkOn = false;
			m_iLineBlinkCount = 0;
			NewShape();
		}
	}
	else 
	{
		// Draw filled blocks
		for (int j=0; j<MATRIX_HEIGHT;j++)
		{
			for (int i=0; i<MATRIX_WIDTH;i++)
			{
				if (m_pBlocksMatrix[i][j])
				{
					int color = m_pBlocksMatrix[i][j]-1;
					GetScreenPosFromCell(i, j, iBlockX, iBlockY);
					m_pBlockImg[color]->BlitImage(iBlockX, iBlockY);
				}
			}
		}

		// Finally, draw the current shape
		if (!m_bGameOver)
			m_pTetrad->Draw();
	}

}

void CBlocksMatrix::Update(DWORD dwCurrentTime)
{
	if (!m_bGameOver)
	{
		// Check if the current shape should be moved down
		if (dwCurrentTime > m_dwLastShapeDown+m_iTetradUpdate)
			ShapeDown();
	}
}

void CBlocksMatrix::ShapeLeft()
{
	if (m_pTetrad)
		m_pTetrad->MoveLeft();
}

void CBlocksMatrix::ShapeRight()
{
	if (m_pTetrad)
		m_pTetrad->MoveRight();
}

void CBlocksMatrix::ShapeDown()
{
	if (m_pTetrad && !m_pTetrad->MoveDown())
	{
		// If the current shape can't move down,
		// we ask it to fill the matrix.
		m_pTetrad->FillMatrix();
		// Then delete the current shape
		delete m_pTetrad;	
		m_pTetrad = NULL;
		
		// We then check if no lines have been completed
		// and create the next shape. The m_bGameOver flag
		// can be set in this NewShape function.
		if (!CheckMatrix())
			NewShape();
	}
	// Set the last update (down) of the shape to 
	// the current time.
	m_dwLastShapeDown = GetCurrentTime();
}

void CBlocksMatrix::NewShape()
{
	m_pTetrad = m_pNextShape;
	m_pNextShape = NULL;
	if (!m_pTetrad->IsValid())
	{
		// If the new shape occupies filled cells,
		// the game is over
		m_bGameOver = true;
		// Inform the listener about it.
		m_pListener->OnMatrixFull();
	}
	else
		m_pNextShape = m_TetradFactory.CreateTetrad(this);
}

void CBlocksMatrix::ShapeRotate()
{
	if (m_pTetrad)
		m_pTetrad->Rotate();
}

bool CBlocksMatrix::IsCellFree(int XPos, int YPos)
{
	if ( (XPos<0) || (XPos>=MATRIX_WIDTH) )
		return false;
	if ( (YPos<0) || (YPos>=MATRIX_HEIGHT) )
		return false;

	if (m_pBlocksMatrix[XPos][YPos] == bcNone)
		return true;
	return false;
}

void CBlocksMatrix::FillCell(int XPos, int YPos, EBlockColor BlockColor)
{
	if ( (XPos<0) || (XPos>=MATRIX_WIDTH) )
		return;
	if ( (YPos<0) || (YPos>=MATRIX_HEIGHT) )
		return;

	m_pBlocksMatrix[XPos][YPos] = BlockColor;
}

void CBlocksMatrix::GetScreenPosFromCell(int cellXPos, 
										 int cellYPos, 
										 int& screenXPos, 
										 int& screenYPos)
{
	screenXPos = m_iXPos + BLOCK_WIDTH * cellXPos;	
	screenYPos = m_iYPos + BLOCK_HEIGHT * cellYPos;	
}

bool CBlocksMatrix::CheckMatrix()
{
	bool bOneLineComplete = false;
	bool bComplete = true;
	// Scan all the lines to see if they are complete
	for (int iRow=0; iRow<MATRIX_HEIGHT;iRow++)
	{
		bComplete = true;
		for (int j=0; j<MATRIX_WIDTH;j++)
		{
			if (m_pBlocksMatrix[j][iRow] == bcNone)
			{
				bComplete = false;
				break;
			}
		}

		// If the current line is complete
		if (bComplete)
		{
			// Push the line index in the vector.
			m_vecLinesRemoved.push_back(iRow);
			// Specify that we switch to the removing line state.
			m_bRemovingLine = true;
			bOneLineComplete = true;
			m_pListener->OnStartRemoveLines();
		}
	}

	return bOneLineComplete;
}

bool CBlocksMatrix::IsLineRemoved(int iRow)
{
	bool bFound = false;
	vector<int>::iterator iter = m_vecLinesRemoved.begin();
	for (iter; iter!=m_vecLinesRemoved.end(); iter++)
	{
		if (iRow == (*iter) )
		{
			bFound = true;
			break;
		}
	}

	return bFound;
}

void CBlocksMatrix::RemoveLines()
{
	vector<int>::iterator iter = m_vecLinesRemoved.begin();
	for (iter; iter!=m_vecLinesRemoved.end();iter++)
	{
		for (int j=0; j<MATRIX_WIDTH;j++)
		{
			// set the current line to empty
			m_pBlocksMatrix[j][(*iter)] = bcNone;

			// Move all the upper blocks one cell down
			for (int iNewRow=(*iter);iNewRow>1;iNewRow--)
			{
				m_pBlocksMatrix[j][iNewRow] = m_pBlocksMatrix[j][iNewRow-1];
			}
		}
	}

	if (m_pListener)
		m_pListener->OnLinesRemoved((int)m_vecLinesRemoved.size());

	m_vecLinesRemoved.clear();
}

CMatrixEventsListener类源码:


// Listener for the blocks matrix
class CMatrixEventsListener
{
public:
	CMatrixEventsListener()  { }
	virtual ~CMatrixEventsListener()  { }

	// Virtual function called when filled lines are
	// detected and will start blinking
	virtual void OnStartRemoveLines() = 0;
	// Virtual function called when the filled lines have
	// been removed
	virtual void OnLinesRemoved(int iLinesCount) = 0;
	// Virtual function called when the game is over.
	virtual void OnMatrixFull() = 0;
};

关于闪烁的原因:

间隔的显示被删除的行,显示在界面上就是方块和背景图案轮流切换,给人一种闪烁的感觉。











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值